From 28c11f22cd9720fda25e9fe652f20d98409e4eb6 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Wed, 26 Jun 2024 08:26:17 -0400 Subject: [PATCH 1/4] Update three.js --- three.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/three.js b/three.js index 172f0c66..f43a827c 160000 --- a/three.js +++ b/three.js @@ -1 +1 @@ -Subproject commit 172f0c6642e18290c24f3f0abbe1ee9419ad4bcd +Subproject commit f43a827cbd1c129614e3d47562d0d7e865e05bf7 From a63d90e9c7327dda61ef62e1cc635343f5ec76f1 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Wed, 26 Jun 2024 08:26:50 -0400 Subject: [PATCH 2/4] Add examples --- examples-jsm/examples/nodes/Nodes.ts | 416 +++++ .../nodes/accessors/BufferAttributeNode.ts | 127 ++ .../examples/nodes/accessors/TextureNode.ts | 327 ++++ examples-jsm/examples/nodes/code/CodeNode.ts | 66 + .../examples/nodes/code/FunctionNode.ts | 100 ++ .../examples/nodes/core/ContextNode.ts | 55 + examples-jsm/examples/nodes/core/InputNode.ts | 65 + examples-jsm/examples/nodes/core/Node.ts | 424 ++++++ .../examples/nodes/core/NodeAttribute.ts | 11 + .../examples/nodes/core/NodeBuilder.ts | 1119 ++++++++++++++ examples-jsm/examples/nodes/core/NodeCache.ts | 26 + examples-jsm/examples/nodes/core/NodeCode.ts | 11 + examples-jsm/examples/nodes/core/NodeFrame.ts | 127 ++ .../examples/nodes/core/NodeFunction.ts | 16 + .../examples/nodes/core/NodeKeywords.ts | 58 + .../examples/nodes/core/NodeParser.ts | 7 + .../examples/nodes/core/NodeUniform.ts | 27 + examples-jsm/examples/nodes/core/NodeUtils.ts | 132 ++ examples-jsm/examples/nodes/core/NodeVar.ts | 10 + .../examples/nodes/core/NodeVarying.ts | 13 + examples-jsm/examples/nodes/core/StackNode.ts | 71 + .../examples/nodes/core/StructTypeNode.ts | 18 + .../examples/nodes/core/UniformGroupNode.ts | 30 + .../examples/nodes/core/UniformNode.ts | 90 ++ examples-jsm/examples/nodes/core/constants.ts | 28 + examples-jsm/examples/nodes/fog/FogNode.ts | 38 + .../examples/nodes/gpgpu/ComputeNode.ts | 67 + .../nodes/lighting/EnvironmentNode.ts | 119 ++ .../nodes/lighting/LightingContextNode.ts | 58 + .../examples/nodes/lighting/LightsNode.ts | 170 +++ .../examples/nodes/materials/NodeMaterial.ts | 514 +++++++ .../examples/nodes/shadernode/ShaderNode.ts | 532 +++++++ .../examples/renderers/common/Animation.ts | 38 + .../examples/renderers/common/Attributes.ts | 53 + .../examples/renderers/common/Backend.ts | 165 ++ .../examples/renderers/common/Background.ts | 118 ++ .../examples/renderers/common/BindGroup.ts | 12 + .../examples/renderers/common/Binding.ts | 17 + .../examples/renderers/common/Bindings.ts | 161 ++ .../examples/renderers/common/Buffer.ts | 28 + .../examples/renderers/common/BufferUtils.ts | 23 + .../examples/renderers/common/ChainMap.ts | 43 + .../renderers/common/ClippingContext.ts | 128 ++ .../examples/renderers/common/Color4.ts | 27 + .../renderers/common/ComputePipeline.ts | 13 + .../examples/renderers/common/Constants.ts | 14 + .../renderers/common/CubeRenderTarget.ts | 69 + .../examples/renderers/common/DataMap.ts | 38 + .../examples/renderers/common/Geometries.ts | 182 +++ .../examples/renderers/common/Info.ts | 76 + .../examples/renderers/common/Pipeline.ts | 9 + .../examples/renderers/common/Pipelines.ts | 270 ++++ .../renderers/common/ProgrammableStage.ts | 16 + .../examples/renderers/common/RenderBundle.ts | 12 + .../renderers/common/RenderBundles.ts | 28 + .../renderers/common/RenderContext.ts | 39 + .../renderers/common/RenderContexts.ts | 47 + .../examples/renderers/common/RenderList.ts | 145 ++ .../examples/renderers/common/RenderLists.ts | 28 + .../examples/renderers/common/RenderObject.ts | 215 +++ .../renderers/common/RenderObjects.ts | 100 ++ .../renderers/common/RenderPipeline.ts | 12 + .../examples/renderers/common/Renderer.ts | 1345 +++++++++++++++++ .../renderers/common/SampledTexture.ts | 61 + .../examples/renderers/common/Sampler.ts | 14 + .../renderers/common/StorageBuffer.ts | 13 + .../examples/renderers/common/Textures.ts | 288 ++++ .../examples/renderers/common/Uniform.ts | 100 ++ .../renderers/common/UniformBuffer.ts | 11 + .../renderers/common/UniformsGroup.ts | 277 ++++ .../renderers/common/extras/PMREMGenerator.ts | 659 ++++++++ .../common/nodes/NodeBuilderState.ts | 55 + .../renderers/common/nodes/NodeUniform.ts | 103 ++ .../common/nodes/NodeUniformsGroup.ts | 30 + .../examples/renderers/common/nodes/Nodes.ts | 394 +++++ .../examples/renderers/webgl/WebGLBackend.ts | 1268 ++++++++++++++++ .../renderers/webgl/nodes/GLSLNodeBuilder.ts | 747 +++++++++ .../renderers/webgpu/WebGPUBackend.ts | 1194 +++++++++++++++ .../renderers/webgpu/WebGPURenderer.ts | 43 + .../renderers/webgpu/nodes/WGSLNodeBuilder.ts | 1009 +++++++++++++ .../webgpu/nodes/WGSLNodeFunction.ts | 127 ++ .../renderers/webgpu/nodes/WGSLNodeParser.ts | 10 + 82 files changed, 14746 insertions(+) create mode 100644 examples-jsm/examples/nodes/Nodes.ts create mode 100644 examples-jsm/examples/nodes/accessors/BufferAttributeNode.ts create mode 100644 examples-jsm/examples/nodes/accessors/TextureNode.ts create mode 100644 examples-jsm/examples/nodes/code/CodeNode.ts create mode 100644 examples-jsm/examples/nodes/code/FunctionNode.ts create mode 100644 examples-jsm/examples/nodes/core/ContextNode.ts create mode 100644 examples-jsm/examples/nodes/core/InputNode.ts create mode 100644 examples-jsm/examples/nodes/core/Node.ts create mode 100644 examples-jsm/examples/nodes/core/NodeAttribute.ts create mode 100644 examples-jsm/examples/nodes/core/NodeBuilder.ts create mode 100644 examples-jsm/examples/nodes/core/NodeCache.ts create mode 100644 examples-jsm/examples/nodes/core/NodeCode.ts create mode 100644 examples-jsm/examples/nodes/core/NodeFrame.ts create mode 100644 examples-jsm/examples/nodes/core/NodeFunction.ts create mode 100644 examples-jsm/examples/nodes/core/NodeKeywords.ts create mode 100644 examples-jsm/examples/nodes/core/NodeParser.ts create mode 100644 examples-jsm/examples/nodes/core/NodeUniform.ts create mode 100644 examples-jsm/examples/nodes/core/NodeUtils.ts create mode 100644 examples-jsm/examples/nodes/core/NodeVar.ts create mode 100644 examples-jsm/examples/nodes/core/NodeVarying.ts create mode 100644 examples-jsm/examples/nodes/core/StackNode.ts create mode 100644 examples-jsm/examples/nodes/core/StructTypeNode.ts create mode 100644 examples-jsm/examples/nodes/core/UniformGroupNode.ts create mode 100644 examples-jsm/examples/nodes/core/UniformNode.ts create mode 100644 examples-jsm/examples/nodes/core/constants.ts create mode 100644 examples-jsm/examples/nodes/fog/FogNode.ts create mode 100644 examples-jsm/examples/nodes/gpgpu/ComputeNode.ts create mode 100644 examples-jsm/examples/nodes/lighting/EnvironmentNode.ts create mode 100644 examples-jsm/examples/nodes/lighting/LightingContextNode.ts create mode 100644 examples-jsm/examples/nodes/lighting/LightsNode.ts create mode 100644 examples-jsm/examples/nodes/materials/NodeMaterial.ts create mode 100644 examples-jsm/examples/nodes/shadernode/ShaderNode.ts create mode 100644 examples-jsm/examples/renderers/common/Animation.ts create mode 100644 examples-jsm/examples/renderers/common/Attributes.ts create mode 100644 examples-jsm/examples/renderers/common/Backend.ts create mode 100644 examples-jsm/examples/renderers/common/Background.ts create mode 100644 examples-jsm/examples/renderers/common/BindGroup.ts create mode 100644 examples-jsm/examples/renderers/common/Binding.ts create mode 100644 examples-jsm/examples/renderers/common/Bindings.ts create mode 100644 examples-jsm/examples/renderers/common/Buffer.ts create mode 100644 examples-jsm/examples/renderers/common/BufferUtils.ts create mode 100644 examples-jsm/examples/renderers/common/ChainMap.ts create mode 100644 examples-jsm/examples/renderers/common/ClippingContext.ts create mode 100644 examples-jsm/examples/renderers/common/Color4.ts create mode 100644 examples-jsm/examples/renderers/common/ComputePipeline.ts create mode 100644 examples-jsm/examples/renderers/common/Constants.ts create mode 100644 examples-jsm/examples/renderers/common/CubeRenderTarget.ts create mode 100644 examples-jsm/examples/renderers/common/DataMap.ts create mode 100644 examples-jsm/examples/renderers/common/Geometries.ts create mode 100644 examples-jsm/examples/renderers/common/Info.ts create mode 100644 examples-jsm/examples/renderers/common/Pipeline.ts create mode 100644 examples-jsm/examples/renderers/common/Pipelines.ts create mode 100644 examples-jsm/examples/renderers/common/ProgrammableStage.ts create mode 100644 examples-jsm/examples/renderers/common/RenderBundle.ts create mode 100644 examples-jsm/examples/renderers/common/RenderBundles.ts create mode 100644 examples-jsm/examples/renderers/common/RenderContext.ts create mode 100644 examples-jsm/examples/renderers/common/RenderContexts.ts create mode 100644 examples-jsm/examples/renderers/common/RenderList.ts create mode 100644 examples-jsm/examples/renderers/common/RenderLists.ts create mode 100644 examples-jsm/examples/renderers/common/RenderObject.ts create mode 100644 examples-jsm/examples/renderers/common/RenderObjects.ts create mode 100644 examples-jsm/examples/renderers/common/RenderPipeline.ts create mode 100644 examples-jsm/examples/renderers/common/Renderer.ts create mode 100644 examples-jsm/examples/renderers/common/SampledTexture.ts create mode 100644 examples-jsm/examples/renderers/common/Sampler.ts create mode 100644 examples-jsm/examples/renderers/common/StorageBuffer.ts create mode 100644 examples-jsm/examples/renderers/common/Textures.ts create mode 100644 examples-jsm/examples/renderers/common/Uniform.ts create mode 100644 examples-jsm/examples/renderers/common/UniformBuffer.ts create mode 100644 examples-jsm/examples/renderers/common/UniformsGroup.ts create mode 100644 examples-jsm/examples/renderers/common/extras/PMREMGenerator.ts create mode 100644 examples-jsm/examples/renderers/common/nodes/NodeBuilderState.ts create mode 100644 examples-jsm/examples/renderers/common/nodes/NodeUniform.ts create mode 100644 examples-jsm/examples/renderers/common/nodes/NodeUniformsGroup.ts create mode 100644 examples-jsm/examples/renderers/common/nodes/Nodes.ts create mode 100644 examples-jsm/examples/renderers/webgl/WebGLBackend.ts create mode 100644 examples-jsm/examples/renderers/webgl/nodes/GLSLNodeBuilder.ts create mode 100644 examples-jsm/examples/renderers/webgpu/WebGPUBackend.ts create mode 100644 examples-jsm/examples/renderers/webgpu/WebGPURenderer.ts create mode 100644 examples-jsm/examples/renderers/webgpu/nodes/WGSLNodeBuilder.ts create mode 100644 examples-jsm/examples/renderers/webgpu/nodes/WGSLNodeFunction.ts create mode 100644 examples-jsm/examples/renderers/webgpu/nodes/WGSLNodeParser.ts diff --git a/examples-jsm/examples/nodes/Nodes.ts b/examples-jsm/examples/nodes/Nodes.ts new file mode 100644 index 00000000..1bfbf68a --- /dev/null +++ b/examples-jsm/examples/nodes/Nodes.ts @@ -0,0 +1,416 @@ +// @TODO: We can simplify "export { default as SomeNode, other, exports } from '...'" to just "export * from '...'" if we will use only named exports +// this will also solve issues like "import TempNode from '../core/Node.js'" + +// constants +export * from './core/constants.js'; + +// core +export { default as AssignNode, assign } from './core/AssignNode.js'; +export { default as AttributeNode, attribute } from './core/AttributeNode.js'; +export { default as BypassNode, bypass } from './core/BypassNode.js'; +export { default as CacheNode, cache } from './core/CacheNode.js'; +export { default as ConstNode } from './core/ConstNode.js'; +export { default as ContextNode, context, label } from './core/ContextNode.js'; +export { default as IndexNode, vertexIndex, instanceIndex } from './core/IndexNode.js'; +export { default as LightingModel } from './core/LightingModel.js'; +export { default as Node, addNodeClass, createNodeFromType } from './core/Node.js'; +export { default as VarNode, temp } from './core/VarNode.js'; +export { default as NodeAttribute } from './core/NodeAttribute.js'; +export { default as NodeBuilder } from './core/NodeBuilder.js'; +export { default as NodeCache } from './core/NodeCache.js'; +export { default as NodeCode } from './core/NodeCode.js'; +export { default as NodeFrame } from './core/NodeFrame.js'; +export { default as NodeFunctionInput } from './core/NodeFunctionInput.js'; +export { default as NodeKeywords } from './core/NodeKeywords.js'; +export { default as NodeUniform } from './core/NodeUniform.js'; +export { default as NodeVar } from './core/NodeVar.js'; +export { default as NodeVarying } from './core/NodeVarying.js'; +export { default as ParameterNode, parameter } from './core/ParameterNode.js'; +export { + default as PropertyNode, + property, + varyingProperty, + output, + diffuseColor, + roughness, + metalness, + clearcoat, + clearcoatRoughness, + sheen, + sheenRoughness, + iridescence, + iridescenceIOR, + iridescenceThickness, + specularColor, + shininess, + dashSize, + gapSize, + pointWidth, + alphaT, + anisotropy, + anisotropyB, + anisotropyT, +} from './core/PropertyNode.js'; +export { default as StackNode, stack } from './core/StackNode.js'; +export { default as TempNode } from './core/TempNode.js'; +export { + default as UniformGroupNode, + uniformGroup, + objectGroup, + renderGroup, + frameGroup, +} from './core/UniformGroupNode.js'; +export { default as UniformNode, uniform } from './core/UniformNode.js'; +export { default as VaryingNode, varying } from './core/VaryingNode.js'; +export { default as OutputStructNode, outputStruct } from './core/OutputStructNode.js'; + +import * as NodeUtils from './core/NodeUtils.js'; +export { NodeUtils }; + +// math +export { + default as MathNode, + PI, + PI2, + EPSILON, + INFINITY, + radians, + degrees, + exp, + exp2, + log, + log2, + sqrt, + inverseSqrt, + floor, + ceil, + normalize, + fract, + sin, + cos, + tan, + asin, + acos, + atan, + abs, + sign, + length, + lengthSq, + negate, + oneMinus, + dFdx, + dFdy, + round, + reciprocal, + trunc, + fwidth, + bitcast, + atan2, + min, + max, + mod, + step, + reflect, + distance, + difference, + dot, + cross, + pow, + pow2, + pow3, + pow4, + transformDirection, + mix, + clamp, + saturate, + refract, + smoothstep, + faceForward, + cbrt, + transpose, + all, + any, + equals, +} from './math/MathNode.js'; + +export { + default as OperatorNode, + add, + sub, + mul, + div, + remainder, + equal, + lessThan, + greaterThan, + lessThanEqual, + greaterThanEqual, + and, + or, + not, + xor, + bitAnd, + bitNot, + bitOr, + bitXor, + shiftLeft, + shiftRight, +} from './math/OperatorNode.js'; +export { default as CondNode, cond } from './math/CondNode.js'; +export { default as HashNode, hash } from './math/HashNode.js'; + +// math utils +export { parabola, gain, pcurve, sinc } from './math/MathUtils.js'; +export { triNoise3D } from './math/TriNoise3D.js'; + +// utils +export { default as ArrayElementNode } from './utils/ArrayElementNode.js'; +export { default as ConvertNode } from './utils/ConvertNode.js'; +export { default as DiscardNode, discard, Return } from './utils/DiscardNode.js'; +export { default as EquirectUVNode, equirectUV } from './utils/EquirectUVNode.js'; +export { default as FunctionOverloadingNode, overloadingFn } from './utils/FunctionOverloadingNode.js'; +export { default as JoinNode } from './utils/JoinNode.js'; +export { default as LoopNode, loop, Continue, Break } from './utils/LoopNode.js'; +export { default as MatcapUVNode, matcapUV } from './utils/MatcapUVNode.js'; +export { default as MaxMipLevelNode, maxMipLevel } from './utils/MaxMipLevelNode.js'; +export { default as OscNode, oscSine, oscSquare, oscTriangle, oscSawtooth } from './utils/OscNode.js'; +export { default as PackingNode, directionToColor, colorToDirection } from './utils/PackingNode.js'; +export { default as RemapNode, remap, remapClamp } from './utils/RemapNode.js'; +export { default as RotateUVNode, rotateUV } from './utils/RotateUVNode.js'; +export { default as RotateNode, rotate } from './utils/RotateNode.js'; +export { default as SetNode } from './utils/SetNode.js'; +export { default as SplitNode } from './utils/SplitNode.js'; +export { default as SpriteSheetUVNode, spritesheetUV } from './utils/SpriteSheetUVNode.js'; +export { default as StorageArrayElementNode } from './utils/StorageArrayElementNode.js'; +export { default as TimerNode, timerLocal, timerGlobal, timerDelta, frameId } from './utils/TimerNode.js'; +export { + default as TriplanarTexturesNode, + triplanarTextures, + triplanarTexture, +} from './utils/TriplanarTexturesNode.js'; +export { default as ReflectorNode, reflector } from './utils/ReflectorNode.js'; + +// shadernode +export * from './shadernode/ShaderNode.js'; + +// accessors +export { TBNViewMatrix, parallaxDirection, parallaxUV, transformedBentNormalView } from './accessors/AccessorsUtils.js'; +export { default as UniformsNode, uniforms } from './accessors/UniformsNode.js'; +export * from './accessors/BitangentNode.js'; +export { + default as BufferAttributeNode, + bufferAttribute, + dynamicBufferAttribute, + instancedBufferAttribute, + instancedDynamicBufferAttribute, +} from './accessors/BufferAttributeNode.js'; +export { default as BufferNode, buffer } from './accessors/BufferNode.js'; +export * from './accessors/CameraNode.js'; +export { default as VertexColorNode, vertexColor } from './accessors/VertexColorNode.js'; +export { default as CubeTextureNode, cubeTexture } from './accessors/CubeTextureNode.js'; +export { default as InstanceNode, instance } from './accessors/InstanceNode.js'; +export { default as BatchNode, batch } from './accessors/BatchNode.js'; +export { + default as MaterialNode, + materialAlphaTest, + materialColor, + materialShininess, + materialEmissive, + materialOpacity, + materialSpecular, + materialSpecularStrength, + materialReflectivity, + materialRoughness, + materialMetalness, + materialNormal, + materialClearcoat, + materialClearcoatRoughness, + materialClearcoatNormal, + materialRotation, + materialSheen, + materialSheenRoughness, + materialIridescence, + materialIridescenceIOR, + materialIridescenceThickness, + materialLineScale, + materialLineDashSize, + materialLineGapSize, + materialLineWidth, + materialLineDashOffset, + materialPointWidth, + materialAnisotropy, + materialAnisotropyVector, + materialDispersion, +} from './accessors/MaterialNode.js'; +export { default as MaterialReferenceNode, materialReference } from './accessors/MaterialReferenceNode.js'; +export { default as RendererReferenceNode, rendererReference } from './accessors/RendererReferenceNode.js'; +export { default as MorphNode, morphReference } from './accessors/MorphNode.js'; +export { default as TextureBicubicNode, textureBicubic } from './accessors/TextureBicubicNode.js'; +export { + default as ModelNode, + modelDirection, + modelViewMatrix, + modelNormalMatrix, + modelWorldMatrix, + modelPosition, + modelViewPosition, + modelScale, + modelWorldMatrixInverse, +} from './accessors/ModelNode.js'; +export { default as ModelViewProjectionNode, modelViewProjection } from './accessors/ModelViewProjectionNode.js'; +export * from './accessors/NormalNode.js'; +export { + default as Object3DNode, + objectDirection, + objectViewMatrix, + objectNormalMatrix, + objectWorldMatrix, + objectPosition, + objectScale, + objectViewPosition, +} from './accessors/Object3DNode.js'; +export { default as PointUVNode, pointUV } from './accessors/PointUVNode.js'; +export * from './accessors/PositionNode.js'; +export { default as ReferenceNode, reference, referenceBuffer } from './accessors/ReferenceNode.js'; +export * from './accessors/ReflectVectorNode.js'; +export { default as SkinningNode, skinning } from './accessors/SkinningNode.js'; +export { default as SceneNode, backgroundBlurriness, backgroundIntensity } from './accessors/SceneNode.js'; +export { default as StorageBufferNode, storage, storageObject } from './accessors/StorageBufferNode.js'; +export * from './accessors/TangentNode.js'; +export { default as TextureNode, texture, textureLoad, /*textureLevel,*/ sampler } from './accessors/TextureNode.js'; +export { + default as StorageTextureNode, + storageTexture, + textureStore, + storageTextureReadOnly, + storageTextureReadWrite, +} from './accessors/StorageTextureNode.js'; +export { default as Texture3DNode, texture3D } from './accessors/Texture3DNode.js'; +export * from './accessors/UVNode.js'; +export { default as UserDataNode, userData } from './accessors/UserDataNode.js'; + +// display +export { default as BlendModeNode, burn, dodge, overlay, screen } from './display/BlendModeNode.js'; +export { default as BumpMapNode, bumpMap } from './display/BumpMapNode.js'; +export { + default as ColorAdjustmentNode, + saturation, + vibrance, + hue, + lumaCoeffs, + luminance, + threshold, +} from './display/ColorAdjustmentNode.js'; +export { + default as ColorSpaceNode, + linearToColorSpace, + colorSpaceToLinear, + linearTosRGB, + sRGBToLinear, +} from './display/ColorSpaceNode.js'; +export { default as FrontFacingNode, frontFacing, faceDirection } from './display/FrontFacingNode.js'; +export { default as NormalMapNode, normalMap } from './display/NormalMapNode.js'; +export { default as PosterizeNode, posterize } from './display/PosterizeNode.js'; +export { default as ToneMappingNode, toneMapping } from './display/ToneMappingNode.js'; +export { + default as ViewportNode, + viewport, + viewportCoordinate, + viewportResolution, + viewportTopLeft, + viewportBottomLeft, + viewportTopRight, + viewportBottomRight, +} from './display/ViewportNode.js'; +export { default as ViewportTextureNode, viewportTexture, viewportMipTexture } from './display/ViewportTextureNode.js'; +export { default as ViewportSharedTextureNode, viewportSharedTexture } from './display/ViewportSharedTextureNode.js'; +export { default as ViewportDepthTextureNode, viewportDepthTexture } from './display/ViewportDepthTextureNode.js'; +export { + default as ViewportDepthNode, + viewZToOrthographicDepth, + orthographicDepthToViewZ, + viewZToPerspectiveDepth, + perspectiveDepthToViewZ, + depth, + linearDepth, + viewportLinearDepth, +} from './display/ViewportDepthNode.js'; +export { default as GaussianBlurNode, gaussianBlur } from './display/GaussianBlurNode.js'; +export { default as AfterImageNode, afterImage } from './display/AfterImageNode.js'; +export { default as AnamorphicNode, anamorphic } from './display/AnamorphicNode.js'; + +export { default as PassNode, pass, texturePass, depthPass } from './display/PassNode.js'; + +// code +export { default as ExpressionNode, expression } from './code/ExpressionNode.js'; +export { default as CodeNode, code, js, wgsl, glsl } from './code/CodeNode.js'; +export { default as FunctionCallNode, call } from './code/FunctionCallNode.js'; +export { default as FunctionNode, wgslFn, glslFn } from './code/FunctionNode.js'; +export { default as ScriptableNode, scriptable, global } from './code/ScriptableNode.js'; +export { default as ScriptableValueNode, scriptableValue } from './code/ScriptableValueNode.js'; + +// fog +export { default as FogNode, fog } from './fog/FogNode.js'; +export { default as FogRangeNode, rangeFog } from './fog/FogRangeNode.js'; +export { default as FogExp2Node, densityFog } from './fog/FogExp2Node.js'; + +// geometry +export { default as RangeNode, range } from './geometry/RangeNode.js'; + +// gpgpu +export { default as ComputeNode, compute } from './gpgpu/ComputeNode.js'; + +// lighting +export { default as LightNode, lightTargetDirection } from './lighting/LightNode.js'; +export { default as PointLightNode } from './lighting/PointLightNode.js'; +export { default as DirectionalLightNode } from './lighting/DirectionalLightNode.js'; +export { default as RectAreaLightNode } from './lighting/RectAreaLightNode.js'; +export { default as SpotLightNode } from './lighting/SpotLightNode.js'; +export { default as IESSpotLightNode } from './lighting/IESSpotLightNode.js'; +export { default as AmbientLightNode } from './lighting/AmbientLightNode.js'; +export { default as LightsNode, lights, lightsNode, addLightNode } from './lighting/LightsNode.js'; +export { default as LightingNode /* @TODO: lighting (abstract), light */ } from './lighting/LightingNode.js'; +export { default as LightingContextNode, lightingContext } from './lighting/LightingContextNode.js'; +export { default as HemisphereLightNode } from './lighting/HemisphereLightNode.js'; +export { default as EnvironmentNode } from './lighting/EnvironmentNode.js'; +export { default as IrradianceNode } from './lighting/IrradianceNode.js'; +export { default as AONode } from './lighting/AONode.js'; +export { default as AnalyticLightNode } from './lighting/AnalyticLightNode.js'; + +// pmrem +export { default as PMREMNode, pmremTexture } from './pmrem/PMREMNode.js'; +export * as PMREMUtils from './pmrem/PMREMUtils.js'; + +// procedural +export { default as CheckerNode, checker } from './procedural/CheckerNode.js'; + +// loaders +export { default as NodeLoader } from './loaders/NodeLoader.js'; +export { default as NodeObjectLoader } from './loaders/NodeObjectLoader.js'; +export { default as NodeMaterialLoader } from './loaders/NodeMaterialLoader.js'; + +// parsers +export { default as GLSLNodeParser } from './parsers/GLSLNodeParser.js'; // @TODO: Move to jsm/renderers/webgl. + +// materials +export * from './materials/Materials.js'; + +// materialX +export * from './materialx/MaterialXNodes.js'; + +// functions +export { default as BRDF_GGX } from './functions/BSDF/BRDF_GGX.js'; +export { default as BRDF_Lambert } from './functions/BSDF/BRDF_Lambert.js'; +export { default as D_GGX } from './functions/BSDF/D_GGX.js'; +export { default as DFGApprox } from './functions/BSDF/DFGApprox.js'; +export { default as F_Schlick } from './functions/BSDF/F_Schlick.js'; +export { default as Schlick_to_F0 } from './functions/BSDF/Schlick_to_F0.js'; +export { default as V_GGX_SmithCorrelated } from './functions/BSDF/V_GGX_SmithCorrelated.js'; + +export { getDistanceAttenuation } from './lighting/LightUtils.js'; + +export { default as getGeometryRoughness } from './functions/material/getGeometryRoughness.js'; +export { default as getRoughness } from './functions/material/getRoughness.js'; + +export { default as PhongLightingModel } from './functions/PhongLightingModel.js'; +export { default as PhysicalLightingModel } from './functions/PhysicalLightingModel.js'; diff --git a/examples-jsm/examples/nodes/accessors/BufferAttributeNode.ts b/examples-jsm/examples/nodes/accessors/BufferAttributeNode.ts new file mode 100644 index 00000000..b8f1a724 --- /dev/null +++ b/examples-jsm/examples/nodes/accessors/BufferAttributeNode.ts @@ -0,0 +1,127 @@ +import InputNode from '../core/InputNode.js'; +import { addNodeClass } from '../core/Node.js'; +import { varying } from '../core/VaryingNode.js'; +import { nodeObject, addNodeElement } from '../shadernode/ShaderNode.js'; +import { InterleavedBufferAttribute, InterleavedBuffer, StaticDrawUsage, DynamicDrawUsage } from 'three'; + +class BufferAttributeNode extends InputNode { + constructor(value, bufferType = null, bufferStride = 0, bufferOffset = 0) { + super(value, bufferType); + + this.isBufferNode = true; + + this.bufferType = bufferType; + this.bufferStride = bufferStride; + this.bufferOffset = bufferOffset; + + this.usage = StaticDrawUsage; + this.instanced = false; + + this.attribute = null; + + this.global = true; + + if (value && value.isBufferAttribute === true) { + this.attribute = value; + this.usage = value.usage; + this.instanced = value.isInstancedBufferAttribute; + } + } + + getHash(builder) { + if (this.bufferStride === 0 && this.bufferOffset === 0) { + let bufferData = builder.globalCache.getData(this.value); + + if (bufferData === undefined) { + bufferData = { + node: this, + }; + + builder.globalCache.setData(this.value, bufferData); + } + + return bufferData.node.uuid; + } + + return this.uuid; + } + + getNodeType(builder) { + if (this.bufferType === null) { + this.bufferType = builder.getTypeFromAttribute(this.attribute); + } + + return this.bufferType; + } + + setup(builder) { + if (this.attribute !== null) return; + + const type = this.getNodeType(builder); + const array = this.value; + const itemSize = builder.getTypeLength(type); + const stride = this.bufferStride || itemSize; + const offset = this.bufferOffset; + + const buffer = array.isInterleavedBuffer === true ? array : new InterleavedBuffer(array, stride); + const bufferAttribute = new InterleavedBufferAttribute(buffer, itemSize, offset); + + buffer.setUsage(this.usage); + + this.attribute = bufferAttribute; + this.attribute.isInstancedBufferAttribute = this.instanced; // @TODO: Add a possible: InstancedInterleavedBufferAttribute + } + + generate(builder) { + const nodeType = this.getNodeType(builder); + + const nodeAttribute = builder.getBufferAttributeFromNode(this, nodeType); + const propertyName = builder.getPropertyName(nodeAttribute); + + let output = null; + + if (builder.shaderStage === 'vertex' || builder.shaderStage === 'compute') { + this.name = propertyName; + + output = propertyName; + } else { + const nodeVarying = varying(this); + + output = nodeVarying.build(builder, nodeType); + } + + return output; + } + + getInputType(/*builder*/) { + return 'bufferAttribute'; + } + + setUsage(value) { + this.usage = value; + + return this; + } + + setInstanced(value) { + this.instanced = value; + + return this; + } +} + +export default BufferAttributeNode; + +export const bufferAttribute = (array, type, stride, offset) => + nodeObject(new BufferAttributeNode(array, type, stride, offset)); +export const dynamicBufferAttribute = (array, type, stride, offset) => + bufferAttribute(array, type, stride, offset).setUsage(DynamicDrawUsage); + +export const instancedBufferAttribute = (array, type, stride, offset) => + bufferAttribute(array, type, stride, offset).setInstanced(true); +export const instancedDynamicBufferAttribute = (array, type, stride, offset) => + dynamicBufferAttribute(array, type, stride, offset).setInstanced(true); + +addNodeElement('toAttribute', bufferNode => bufferAttribute(bufferNode.value)); + +addNodeClass('BufferAttributeNode', BufferAttributeNode); diff --git a/examples-jsm/examples/nodes/accessors/TextureNode.ts b/examples-jsm/examples/nodes/accessors/TextureNode.ts new file mode 100644 index 00000000..7852696a --- /dev/null +++ b/examples-jsm/examples/nodes/accessors/TextureNode.ts @@ -0,0 +1,327 @@ +import UniformNode, { uniform } from '../core/UniformNode.js'; +import { uv } from './UVNode.js'; +import { textureSize } from './TextureSizeNode.js'; +import { colorSpaceToLinear } from '../display/ColorSpaceNode.js'; +import { expression } from '../code/ExpressionNode.js'; +import { addNodeClass } from '../core/Node.js'; +import { maxMipLevel } from '../utils/MaxMipLevelNode.js'; +import { addNodeElement, nodeProxy, vec3, nodeObject } from '../shadernode/ShaderNode.js'; +import { NodeUpdateType } from '../core/constants.js'; + +class TextureNode extends UniformNode { + constructor(value, uvNode = null, levelNode = null) { + super(value); + + this.isTextureNode = true; + + this.uvNode = uvNode; + this.levelNode = levelNode; + this.compareNode = null; + this.depthNode = null; + this.gradNode = null; + + this.sampler = true; + this.updateMatrix = false; + this.updateType = NodeUpdateType.NONE; + + this.referenceNode = null; + + this._value = value; + + this.setUpdateMatrix(uvNode === null); + } + + set value(value) { + if (this.referenceNode) { + this.referenceNode.value = value; + } else { + this._value = value; + } + } + + get value() { + return this.referenceNode ? this.referenceNode.value : this._value; + } + + getUniformHash(/*builder*/) { + return this.value.uuid; + } + + getNodeType(/*builder*/) { + if (this.value.isDepthTexture === true) return 'float'; + + return 'vec4'; + } + + getInputType(/*builder*/) { + return 'texture'; + } + + getDefaultUV() { + return uv(this.value.channel); + } + + updateReference(/*state*/) { + return this.value; + } + + getTransformedUV(uvNode) { + const texture = this.value; + + return uniform(texture.matrix).mul(vec3(uvNode, 1)).xy; + } + + setUpdateMatrix(value) { + this.updateMatrix = value; + this.updateType = value ? NodeUpdateType.FRAME : NodeUpdateType.NONE; + + return this; + } + + setupUV(builder, uvNode) { + const texture = this.value; + + if ( + builder.isFlipY() && + (texture.isRenderTargetTexture === true || + texture.isFramebufferTexture === true || + texture.isDepthTexture === true) + ) { + uvNode = uvNode.setY(uvNode.y.oneMinus()); + } + + return uvNode; + } + + setup(builder) { + const properties = builder.getNodeProperties(this); + + // + + let uvNode = this.uvNode; + + if ((uvNode === null || builder.context.forceUVContext === true) && builder.context.getUV) { + uvNode = builder.context.getUV(this); + } + + if (!uvNode) uvNode = this.getDefaultUV(); + + if (this.updateMatrix === true) { + uvNode = this.getTransformedUV(uvNode); + } + + uvNode = this.setupUV(builder, uvNode); + + // + + let levelNode = this.levelNode; + + if (levelNode === null && builder.context.getTextureLevel) { + levelNode = builder.context.getTextureLevel(this); + } + + // + + properties.uvNode = uvNode; + properties.levelNode = levelNode; + properties.compareNode = this.compareNode; + properties.gradNode = this.gradNode; + properties.depthNode = this.depthNode; + } + + generateUV(builder, uvNode) { + return uvNode.build(builder, this.sampler === true ? 'vec2' : 'ivec2'); + } + + generateSnippet(builder, textureProperty, uvSnippet, levelSnippet, depthSnippet, compareSnippet, gradSnippet) { + const texture = this.value; + + let snippet; + + if (levelSnippet) { + snippet = builder.generateTextureLevel(texture, textureProperty, uvSnippet, levelSnippet, depthSnippet); + } else if (gradSnippet) { + snippet = builder.generateTextureGrad(texture, textureProperty, uvSnippet, gradSnippet, depthSnippet); + } else if (compareSnippet) { + snippet = builder.generateTextureCompare(texture, textureProperty, uvSnippet, compareSnippet, depthSnippet); + } else if (this.sampler === false) { + snippet = builder.generateTextureLoad(texture, textureProperty, uvSnippet, depthSnippet); + } else { + snippet = builder.generateTexture(texture, textureProperty, uvSnippet, depthSnippet); + } + + return snippet; + } + + generate(builder, output) { + const properties = builder.getNodeProperties(this); + + const texture = this.value; + + if (!texture || texture.isTexture !== true) { + throw new Error('TextureNode: Need a three.js texture.'); + } + + const textureProperty = super.generate(builder, 'property'); + + if (output === 'sampler') { + return textureProperty + '_sampler'; + } else if (builder.isReference(output)) { + return textureProperty; + } else { + const nodeData = builder.getDataFromNode(this); + + let propertyName = nodeData.propertyName; + + if (propertyName === undefined) { + const { uvNode, levelNode, compareNode, depthNode, gradNode } = properties; + + const uvSnippet = this.generateUV(builder, uvNode); + const levelSnippet = levelNode ? levelNode.build(builder, 'float') : null; + const depthSnippet = depthNode ? depthNode.build(builder, 'int') : null; + const compareSnippet = compareNode ? compareNode.build(builder, 'float') : null; + const gradSnippet = gradNode + ? [gradNode[0].build(builder, 'vec2'), gradNode[1].build(builder, 'vec2')] + : null; + + const nodeVar = builder.getVarFromNode(this); + + propertyName = builder.getPropertyName(nodeVar); + + const snippet = this.generateSnippet( + builder, + textureProperty, + uvSnippet, + levelSnippet, + depthSnippet, + compareSnippet, + gradSnippet, + ); + + builder.addLineFlowCode(`${propertyName} = ${snippet}`); + + nodeData.snippet = snippet; + nodeData.propertyName = propertyName; + } + + let snippet = propertyName; + const nodeType = this.getNodeType(builder); + + if (builder.needsColorSpaceToLinear(texture)) { + snippet = colorSpaceToLinear(expression(snippet, nodeType), texture.colorSpace) + .setup(builder) + .build(builder, nodeType); + } + + return builder.format(snippet, nodeType, output); + } + } + + setSampler(value) { + this.sampler = value; + + return this; + } + + getSampler() { + return this.sampler; + } + + // @TODO: Move to TSL + + uv(uvNode) { + const textureNode = this.clone(); + textureNode.uvNode = uvNode; + textureNode.referenceNode = this; + + return nodeObject(textureNode); + } + + blur(levelNode) { + const textureNode = this.clone(); + textureNode.levelNode = levelNode.mul(maxMipLevel(textureNode)); + textureNode.referenceNode = this; + + return nodeObject(textureNode); + } + + level(levelNode) { + const textureNode = this.clone(); + textureNode.levelNode = levelNode; + textureNode.referenceNode = this; + + return textureNode; + } + + size(levelNode) { + return textureSize(this, levelNode); + } + + compare(compareNode) { + const textureNode = this.clone(); + textureNode.compareNode = nodeObject(compareNode); + textureNode.referenceNode = this; + + return nodeObject(textureNode); + } + + grad(gradNodeX, gradNodeY) { + const textureNode = this.clone(); + textureNode.gradNode = [nodeObject(gradNodeX), nodeObject(gradNodeY)]; + + textureNode.referenceNode = this; + + return nodeObject(textureNode); + } + + depth(depthNode) { + const textureNode = this.clone(); + textureNode.depthNode = nodeObject(depthNode); + textureNode.referenceNode = this; + + return nodeObject(textureNode); + } + + // -- + + serialize(data) { + super.serialize(data); + + data.value = this.value.toJSON(data.meta).uuid; + } + + deserialize(data) { + super.deserialize(data); + + this.value = data.meta.textures[data.value]; + } + + update() { + const texture = this.value; + + if (texture.matrixAutoUpdate === true) { + texture.updateMatrix(); + } + } + + clone() { + const newNode = new this.constructor(this.value, this.uvNode, this.levelNode); + newNode.sampler = this.sampler; + + return newNode; + } +} + +export default TextureNode; + +export const texture = nodeProxy(TextureNode); +export const textureLoad = (...params) => texture(...params).setSampler(false); + +//export const textureLevel = ( value, uv, level ) => texture( value, uv ).level( level ); + +export const sampler = aTexture => (aTexture.isNode === true ? aTexture : texture(aTexture)).convert('sampler'); + +addNodeElement('texture', texture); +//addNodeElement( 'textureLevel', textureLevel ); + +addNodeClass('TextureNode', TextureNode); diff --git a/examples-jsm/examples/nodes/code/CodeNode.ts b/examples-jsm/examples/nodes/code/CodeNode.ts new file mode 100644 index 00000000..06347564 --- /dev/null +++ b/examples-jsm/examples/nodes/code/CodeNode.ts @@ -0,0 +1,66 @@ +import Node, { addNodeClass } from '../core/Node.js'; +import { nodeProxy } from '../shadernode/ShaderNode.js'; + +class CodeNode extends Node { + constructor(code = '', includes = [], language = '') { + super('code'); + + this.isCodeNode = true; + + this.code = code; + this.language = language; + + this.includes = includes; + } + + isGlobal() { + return true; + } + + setIncludes(includes) { + this.includes = includes; + + return this; + } + + getIncludes(/*builder*/) { + return this.includes; + } + + generate(builder) { + const includes = this.getIncludes(builder); + + for (const include of includes) { + include.build(builder); + } + + const nodeCode = builder.getCodeFromNode(this, this.getNodeType(builder)); + nodeCode.code = this.code; + + return nodeCode.code; + } + + serialize(data) { + super.serialize(data); + + data.code = this.code; + data.language = this.language; + } + + deserialize(data) { + super.deserialize(data); + + this.code = data.code; + this.language = data.language; + } +} + +export default CodeNode; + +export const code = nodeProxy(CodeNode); + +export const js = (src, includes) => code(src, includes, 'js'); +export const wgsl = (src, includes) => code(src, includes, 'wgsl'); +export const glsl = (src, includes) => code(src, includes, 'glsl'); + +addNodeClass('CodeNode', CodeNode); diff --git a/examples-jsm/examples/nodes/code/FunctionNode.ts b/examples-jsm/examples/nodes/code/FunctionNode.ts new file mode 100644 index 00000000..feeb5a55 --- /dev/null +++ b/examples-jsm/examples/nodes/code/FunctionNode.ts @@ -0,0 +1,100 @@ +import CodeNode from './CodeNode.js'; +import { addNodeClass } from '../core/Node.js'; +import { nodeObject } from '../shadernode/ShaderNode.js'; + +class FunctionNode extends CodeNode { + constructor(code = '', includes = [], language = '') { + super(code, includes, language); + + this.keywords = {}; + } + + getNodeType(builder) { + return this.getNodeFunction(builder).type; + } + + getInputs(builder) { + return this.getNodeFunction(builder).inputs; + } + + getNodeFunction(builder) { + const nodeData = builder.getDataFromNode(this); + + let nodeFunction = nodeData.nodeFunction; + + if (nodeFunction === undefined) { + nodeFunction = builder.parser.parseFunction(this.code); + + nodeData.nodeFunction = nodeFunction; + } + + return nodeFunction; + } + + generate(builder, output) { + super.generate(builder); + + const nodeFunction = this.getNodeFunction(builder); + + const name = nodeFunction.name; + const type = nodeFunction.type; + + const nodeCode = builder.getCodeFromNode(this, type); + + if (name !== '') { + // use a custom property name + + nodeCode.name = name; + } + + const propertyName = builder.getPropertyName(nodeCode); + + let code = this.getNodeFunction(builder).getCode(propertyName); + + const keywords = this.keywords; + const keywordsProperties = Object.keys(keywords); + + if (keywordsProperties.length > 0) { + for (const property of keywordsProperties) { + const propertyRegExp = new RegExp(`\\b${property}\\b`, 'g'); + const nodeProperty = keywords[property].build(builder, 'property'); + + code = code.replace(propertyRegExp, nodeProperty); + } + } + + nodeCode.code = code + '\n'; + + if (output === 'property') { + return propertyName; + } else { + return builder.format(`${propertyName}()`, type, output); + } + } +} + +export default FunctionNode; + +const nativeFn = (code, includes = [], language = '') => { + for (let i = 0; i < includes.length; i++) { + const include = includes[i]; + + // TSL Function: glslFn, wgslFn + + if (typeof include === 'function') { + includes[i] = include.functionNode; + } + } + + const functionNode = nodeObject(new FunctionNode(code, includes, language)); + + const fn = (...params) => functionNode.call(...params); + fn.functionNode = functionNode; + + return fn; +}; + +export const glslFn = (code, includes) => nativeFn(code, includes, 'glsl'); +export const wgslFn = (code, includes) => nativeFn(code, includes, 'wgsl'); + +addNodeClass('FunctionNode', FunctionNode); diff --git a/examples-jsm/examples/nodes/core/ContextNode.ts b/examples-jsm/examples/nodes/core/ContextNode.ts new file mode 100644 index 00000000..fcd488eb --- /dev/null +++ b/examples-jsm/examples/nodes/core/ContextNode.ts @@ -0,0 +1,55 @@ +import Node, { addNodeClass } from './Node.js'; +import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js'; + +class ContextNode extends Node { + constructor(node, context = {}) { + super(); + + this.isContextNode = true; + + this.node = node; + this.context = context; + } + + getNodeType(builder) { + return this.node.getNodeType(builder); + } + + analyze(builder) { + this.node.build(builder); + } + + setup(builder) { + const previousContext = builder.getContext(); + + builder.setContext({ ...builder.context, ...this.context }); + + const node = this.node.build(builder); + + builder.setContext(previousContext); + + return node; + } + + generate(builder, output) { + const previousContext = builder.getContext(); + + builder.setContext({ ...builder.context, ...this.context }); + + const snippet = this.node.build(builder, output); + + builder.setContext(previousContext); + + return snippet; + } +} + +export default ContextNode; + +export const context = nodeProxy(ContextNode); +export const label = (node, name) => context(node, { label: name }); + +addNodeElement('context', context); +addNodeElement('label', label); + +addNodeClass('ContextNode', ContextNode); diff --git a/examples-jsm/examples/nodes/core/InputNode.ts b/examples-jsm/examples/nodes/core/InputNode.ts new file mode 100644 index 00000000..4d52ec26 --- /dev/null +++ b/examples-jsm/examples/nodes/core/InputNode.ts @@ -0,0 +1,65 @@ +import Node, { addNodeClass } from './Node.js'; +import { getValueType, getValueFromType, arrayBufferToBase64 } from './NodeUtils.js'; + +class InputNode extends Node { + constructor(value, nodeType = null) { + super(nodeType); + + this.isInputNode = true; + + this.value = value; + this.precision = null; + } + + getNodeType(/*builder*/) { + if (this.nodeType === null) { + return getValueType(this.value); + } + + return this.nodeType; + } + + getInputType(builder) { + return this.getNodeType(builder); + } + + setPrecision(precision) { + this.precision = precision; + + return this; + } + + serialize(data) { + super.serialize(data); + + data.value = this.value; + + if (this.value && this.value.toArray) data.value = this.value.toArray(); + + data.valueType = getValueType(this.value); + data.nodeType = this.nodeType; + + if (data.valueType === 'ArrayBuffer') data.value = arrayBufferToBase64(data.value); + + data.precision = this.precision; + } + + deserialize(data) { + super.deserialize(data); + + this.nodeType = data.nodeType; + this.value = Array.isArray(data.value) ? getValueFromType(data.valueType, ...data.value) : data.value; + + this.precision = data.precision || null; + + if (this.value && this.value.fromArray) this.value = this.value.fromArray(data.value); + } + + generate(/*builder, output*/) { + console.warn('Abstract function.'); + } +} + +export default InputNode; + +addNodeClass('InputNode', InputNode); diff --git a/examples-jsm/examples/nodes/core/Node.ts b/examples-jsm/examples/nodes/core/Node.ts new file mode 100644 index 00000000..66444172 --- /dev/null +++ b/examples-jsm/examples/nodes/core/Node.ts @@ -0,0 +1,424 @@ +import { EventDispatcher } from 'three'; +import { NodeUpdateType } from './constants.js'; +import { getNodeChildren, getCacheKey } from './NodeUtils.js'; +import { MathUtils } from 'three'; + +const NodeClasses = new Map(); + +let _nodeId = 0; + +class Node extends EventDispatcher { + constructor(nodeType = null) { + super(); + + this.nodeType = nodeType; + + this.updateType = NodeUpdateType.NONE; + this.updateBeforeType = NodeUpdateType.NONE; + this.updateAfterType = NodeUpdateType.NONE; + + this.uuid = MathUtils.generateUUID(); + + this.version = 0; + + this._cacheKey = null; + this._cacheKeyVersion = 0; + + this.global = false; + + this.isNode = true; + + Object.defineProperty(this, 'id', { value: _nodeId++ }); + } + + set needsUpdate(value) { + if (value === true) { + this.version++; + } + } + + get type() { + return this.constructor.type; + } + + onUpdate(callback, updateType) { + this.updateType = updateType; + this.update = callback.bind(this.getSelf()); + + return this; + } + + onFrameUpdate(callback) { + return this.onUpdate(callback, NodeUpdateType.FRAME); + } + + onRenderUpdate(callback) { + return this.onUpdate(callback, NodeUpdateType.RENDER); + } + + onObjectUpdate(callback) { + return this.onUpdate(callback, NodeUpdateType.OBJECT); + } + + onReference(callback) { + this.updateReference = callback.bind(this.getSelf()); + + return this; + } + + getSelf() { + // Returns non-node object. + + return this.self || this; + } + + updateReference(/*state*/) { + return this; + } + + isGlobal(/*builder*/) { + return this.global; + } + + *getChildren() { + for (const { childNode } of getNodeChildren(this)) { + yield childNode; + } + } + + dispose() { + this.dispatchEvent({ type: 'dispose' }); + } + + traverse(callback) { + callback(this); + + for (const childNode of this.getChildren()) { + childNode.traverse(callback); + } + } + + getCacheKey(force = false) { + force = force || this.version !== this._cacheKeyVersion; + + if (force === true || this._cacheKey === null) { + this._cacheKey = getCacheKey(this, force); + this._cacheKeyVersion = this.version; + } + + return this._cacheKey; + } + + getHash(/*builder*/) { + return this.uuid; + } + + getUpdateType() { + return this.updateType; + } + + getUpdateBeforeType() { + return this.updateBeforeType; + } + + getUpdateAfterType() { + return this.updateAfterType; + } + + getElementType(builder) { + const type = this.getNodeType(builder); + const elementType = builder.getElementType(type); + + return elementType; + } + + getNodeType(builder) { + const nodeProperties = builder.getNodeProperties(this); + + if (nodeProperties.outputNode) { + return nodeProperties.outputNode.getNodeType(builder); + } + + return this.nodeType; + } + + getShared(builder) { + const hash = this.getHash(builder); + const nodeFromHash = builder.getNodeFromHash(hash); + + return nodeFromHash || this; + } + + setup(builder) { + const nodeProperties = builder.getNodeProperties(this); + + let index = 0; + + for (const childNode of this.getChildren()) { + nodeProperties['node' + index++] = childNode; + } + + // return a outputNode if exists + return null; + } + + construct(builder) { + // @deprecated, r157 + + console.warn('THREE.Node: construct() is deprecated. Use setup() instead.'); + + return this.setup(builder); + } + + increaseUsage(builder) { + const nodeData = builder.getDataFromNode(this); + nodeData.usageCount = nodeData.usageCount === undefined ? 1 : nodeData.usageCount + 1; + + return nodeData.usageCount; + } + + analyze(builder) { + const usageCount = this.increaseUsage(builder); + + if (usageCount === 1) { + // node flow children + + const nodeProperties = builder.getNodeProperties(this); + + for (const childNode of Object.values(nodeProperties)) { + if (childNode && childNode.isNode === true) { + childNode.build(builder); + } + } + } + } + + generate(builder, output) { + const { outputNode } = builder.getNodeProperties(this); + + if (outputNode && outputNode.isNode === true) { + return outputNode.build(builder, output); + } + } + + updateBefore(/*frame*/) { + console.warn('Abstract function.'); + } + + updateAfter(/*frame*/) { + console.warn('Abstract function.'); + } + + update(/*frame*/) { + console.warn('Abstract function.'); + } + + build(builder, output = null) { + const refNode = this.getShared(builder); + + if (this !== refNode) { + return refNode.build(builder, output); + } + + builder.addNode(this); + builder.addChain(this); + + /* Build stages expected results: + - "setup" -> Node + - "analyze" -> null + - "generate" -> String + */ + let result = null; + + const buildStage = builder.getBuildStage(); + + if (buildStage === 'setup') { + this.updateReference(builder); + + const properties = builder.getNodeProperties(this); + + if (properties.initialized !== true) { + const stackNodesBeforeSetup = builder.stack.nodes.length; + + properties.initialized = true; + properties.outputNode = this.setup(builder); + + if (properties.outputNode !== null && builder.stack.nodes.length !== stackNodesBeforeSetup) { + properties.outputNode = builder.stack; + } + + for (const childNode of Object.values(properties)) { + if (childNode && childNode.isNode === true) { + childNode.build(builder); + } + } + } + } else if (buildStage === 'analyze') { + this.analyze(builder); + } else if (buildStage === 'generate') { + const isGenerateOnce = this.generate.length === 1; + + if (isGenerateOnce) { + const type = this.getNodeType(builder); + const nodeData = builder.getDataFromNode(this); + + result = nodeData.snippet; + + if (result === undefined) { + result = this.generate(builder) || ''; + + nodeData.snippet = result; + } + + result = builder.format(result, type, output); + } else { + result = this.generate(builder, output) || ''; + } + } + + builder.removeChain(this); + + return result; + } + + getSerializeChildren() { + return getNodeChildren(this); + } + + serialize(json) { + const nodeChildren = this.getSerializeChildren(); + + const inputNodes = {}; + + for (const { property, index, childNode } of nodeChildren) { + if (index !== undefined) { + if (inputNodes[property] === undefined) { + inputNodes[property] = Number.isInteger(index) ? [] : {}; + } + + inputNodes[property][index] = childNode.toJSON(json.meta).uuid; + } else { + inputNodes[property] = childNode.toJSON(json.meta).uuid; + } + } + + if (Object.keys(inputNodes).length > 0) { + json.inputNodes = inputNodes; + } + } + + deserialize(json) { + if (json.inputNodes !== undefined) { + const nodes = json.meta.nodes; + + for (const property in json.inputNodes) { + if (Array.isArray(json.inputNodes[property])) { + const inputArray = []; + + for (const uuid of json.inputNodes[property]) { + inputArray.push(nodes[uuid]); + } + + this[property] = inputArray; + } else if (typeof json.inputNodes[property] === 'object') { + const inputObject = {}; + + for (const subProperty in json.inputNodes[property]) { + const uuid = json.inputNodes[property][subProperty]; + + inputObject[subProperty] = nodes[uuid]; + } + + this[property] = inputObject; + } else { + const uuid = json.inputNodes[property]; + + this[property] = nodes[uuid]; + } + } + } + } + + toJSON(meta) { + const { uuid, type } = this; + const isRoot = meta === undefined || typeof meta === 'string'; + + if (isRoot) { + meta = { + textures: {}, + images: {}, + nodes: {}, + }; + } + + // serialize + + let data = meta.nodes[uuid]; + + if (data === undefined) { + data = { + uuid, + type, + meta, + metadata: { + version: 4.6, + type: 'Node', + generator: 'Node.toJSON', + }, + }; + + if (isRoot !== true) meta.nodes[data.uuid] = data; + + this.serialize(data); + + delete data.meta; + } + + // TODO: Copied from Object3D.toJSON + + function extractFromCache(cache) { + const values = []; + + for (const key in cache) { + const data = cache[key]; + delete data.metadata; + values.push(data); + } + + return values; + } + + if (isRoot) { + const textures = extractFromCache(meta.textures); + const images = extractFromCache(meta.images); + const nodes = extractFromCache(meta.nodes); + + if (textures.length > 0) data.textures = textures; + if (images.length > 0) data.images = images; + if (nodes.length > 0) data.nodes = nodes; + } + + return data; + } +} + +export default Node; + +export function addNodeClass(type, nodeClass) { + if (typeof nodeClass !== 'function' || !type) throw new Error(`Node class ${type} is not a class`); + if (NodeClasses.has(type)) { + console.warn(`Redefinition of node class ${type}`); + return; + } + + NodeClasses.set(type, nodeClass); + nodeClass.type = type; +} + +export function createNodeFromType(type) { + const Class = NodeClasses.get(type); + + if (Class !== undefined) { + return new Class(); + } +} diff --git a/examples-jsm/examples/nodes/core/NodeAttribute.ts b/examples-jsm/examples/nodes/core/NodeAttribute.ts new file mode 100644 index 00000000..190fe8c5 --- /dev/null +++ b/examples-jsm/examples/nodes/core/NodeAttribute.ts @@ -0,0 +1,11 @@ +class NodeAttribute { + constructor(name, type, node = null) { + this.isNodeAttribute = true; + + this.name = name; + this.type = type; + this.node = node; + } +} + +export default NodeAttribute; diff --git a/examples-jsm/examples/nodes/core/NodeBuilder.ts b/examples-jsm/examples/nodes/core/NodeBuilder.ts new file mode 100644 index 00000000..2559a675 --- /dev/null +++ b/examples-jsm/examples/nodes/core/NodeBuilder.ts @@ -0,0 +1,1119 @@ +import NodeUniform from './NodeUniform.js'; +import NodeAttribute from './NodeAttribute.js'; +import NodeVarying from './NodeVarying.js'; +import NodeVar from './NodeVar.js'; +import NodeCode from './NodeCode.js'; +import NodeKeywords from './NodeKeywords.js'; +import NodeCache from './NodeCache.js'; +import ParameterNode from './ParameterNode.js'; +import FunctionNode from '../code/FunctionNode.js'; +import { createNodeMaterialFromType, default as NodeMaterial } from '../materials/NodeMaterial.js'; +import { NodeUpdateType, defaultBuildStages, shaderStages } from './constants.js'; + +import { + NumberNodeUniform, + Vector2NodeUniform, + Vector3NodeUniform, + Vector4NodeUniform, + ColorNodeUniform, + Matrix3NodeUniform, + Matrix4NodeUniform, +} from '../../renderers/common/nodes/NodeUniform.js'; + +import BindGroup from '../../renderers/common/BindGroup.js'; + +import { + REVISION, + RenderTarget, + Color, + Vector2, + Vector3, + Vector4, + IntType, + UnsignedIntType, + Float16BufferAttribute, + LinearFilter, + LinearMipmapNearestFilter, + NearestMipmapLinearFilter, + LinearMipmapLinearFilter, +} from 'three'; + +import { stack } from './StackNode.js'; +import { getCurrentStack, setCurrentStack } from '../shadernode/ShaderNode.js'; + +import CubeRenderTarget from '../../renderers/common/CubeRenderTarget.js'; +import ChainMap from '../../renderers/common/ChainMap.js'; + +import PMREMGenerator from '../../renderers/common/extras/PMREMGenerator.js'; + +const rendererCache = new WeakMap(); + +const typeFromLength = new Map([ + [2, 'vec2'], + [3, 'vec3'], + [4, 'vec4'], + [9, 'mat3'], + [16, 'mat4'], +]); + +const typeFromArray = new Map([ + [Int8Array, 'int'], + [Int16Array, 'int'], + [Int32Array, 'int'], + [Uint8Array, 'uint'], + [Uint16Array, 'uint'], + [Uint32Array, 'uint'], + [Float32Array, 'float'], +]); + +const toFloat = value => { + value = Number(value); + + return value + (value % 1 ? '' : '.0'); +}; + +class NodeBuilder { + constructor(object, renderer, parser) { + this.object = object; + this.material = (object && object.material) || null; + this.geometry = (object && object.geometry) || null; + this.renderer = renderer; + this.parser = parser; + this.scene = null; + this.camera = null; + + this.nodes = []; + this.updateNodes = []; + this.updateBeforeNodes = []; + this.updateAfterNodes = []; + this.hashNodes = {}; + + this.lightsNode = null; + this.environmentNode = null; + this.fogNode = null; + + this.clippingContext = null; + + this.vertexShader = null; + this.fragmentShader = null; + this.computeShader = null; + + this.flowNodes = { vertex: [], fragment: [], compute: [] }; + this.flowCode = { vertex: '', fragment: '', compute: '' }; + this.uniforms = { vertex: [], fragment: [], compute: [], index: 0 }; + this.structs = { vertex: [], fragment: [], compute: [], index: 0 }; + this.bindings = { vertex: {}, fragment: {}, compute: {} }; + this.bindingsIndexes = {}; + this.bindGroups = null; + this.attributes = []; + this.bufferAttributes = []; + this.varyings = []; + this.codes = {}; + this.vars = {}; + this.flow = { code: '' }; + this.chaining = []; + this.stack = stack(); + this.stacks = []; + this.tab = '\t'; + + this.instanceBindGroups = true; + + this.currentFunctionNode = null; + + this.context = { + keywords: new NodeKeywords(), + material: this.material, + }; + + this.cache = new NodeCache(); + this.globalCache = this.cache; + + this.flowsData = new WeakMap(); + + this.shaderStage = null; + this.buildStage = null; + } + + getBingGroupsCache() { + let bindGroupsCache = rendererCache.get(this.renderer); + + if (bindGroupsCache === undefined) { + bindGroupsCache = new ChainMap(); + + rendererCache.set(this.renderer, bindGroupsCache); + } + + return bindGroupsCache; + } + + createRenderTarget(width, height, options) { + return new RenderTarget(width, height, options); + } + + createCubeRenderTarget(size, options) { + return new CubeRenderTarget(size, options); + } + + createPMREMGenerator() { + // TODO: Move Materials.js to outside of the Nodes.js in order to remove this function and improve tree-shaking support + + return new PMREMGenerator(this.renderer); + } + + includes(node) { + return this.nodes.includes(node); + } + + _getBindGroup(groupName, bindings) { + const bindGroupsCache = this.getBingGroupsCache(); + + // cache individual uniforms group + + const bindingsArray = []; + + let sharedGroup = true; + + for (const binding of bindings) { + if (binding.groupNode.shared === true) { + // nodes is the chainmap key + const nodes = binding.getNodes(); + + let sharedBinding = bindGroupsCache.get(nodes); + + if (sharedBinding === undefined) { + bindGroupsCache.set(nodes, binding); + + sharedBinding = binding; + } + + bindingsArray.push(sharedBinding); + } else { + bindingsArray.push(binding); + + sharedGroup = false; + } + } + + // + + let bindGroup; + + if (sharedGroup) { + bindGroup = bindGroupsCache.get(bindingsArray); + + if (bindGroup === undefined) { + bindGroup = new BindGroup(groupName, bindingsArray); + bindGroupsCache.set(bindingsArray, bindGroup); + } + } else { + bindGroup = new BindGroup(groupName, bindingsArray); + } + + return bindGroup; + } + + getBindGroupArray(groupName, shaderStage) { + const bindings = this.bindings[shaderStage]; + + let bindGroup = bindings[groupName]; + + if (bindGroup === undefined) { + if (this.bindingsIndexes[groupName] === undefined) { + this.bindingsIndexes[groupName] = { binding: 0, group: Object.keys(this.bindingsIndexes).length }; + } + + bindings[groupName] = bindGroup = []; + } + + return bindGroup; + } + + getBindings() { + let bindingsGroups = this.bindGroups; + + if (bindingsGroups === null) { + const groups = {}; + const bindings = this.bindings; + + for (const shaderStage of shaderStages) { + for (const groupName in bindings[shaderStage]) { + const uniforms = bindings[shaderStage][groupName]; + + const groupUniforms = groups[groupName] || (groups[groupName] = []); + groupUniforms.push(...uniforms); + } + } + + bindingsGroups = []; + + for (const groupName in groups) { + const group = groups[groupName]; + + const bindingsGroup = this._getBindGroup(groupName, group); + + bindingsGroups.push(bindingsGroup); + } + + this.bindGroups = bindingsGroups; + } + + return bindingsGroups; + } + + setHashNode(node, hash) { + this.hashNodes[hash] = node; + } + + addNode(node) { + if (this.nodes.includes(node) === false) { + this.nodes.push(node); + + this.setHashNode(node, node.getHash(this)); + } + } + + buildUpdateNodes() { + for (const node of this.nodes) { + const updateType = node.getUpdateType(); + const updateBeforeType = node.getUpdateBeforeType(); + const updateAfterType = node.getUpdateAfterType(); + + if (updateType !== NodeUpdateType.NONE) { + this.updateNodes.push(node.getSelf()); + } + + if (updateBeforeType !== NodeUpdateType.NONE) { + this.updateBeforeNodes.push(node); + } + + if (updateAfterType !== NodeUpdateType.NONE) { + this.updateAfterNodes.push(node); + } + } + } + + get currentNode() { + return this.chaining[this.chaining.length - 1]; + } + + isFilteredTexture(texture) { + return ( + texture.magFilter === LinearFilter || + texture.magFilter === LinearMipmapNearestFilter || + texture.magFilter === NearestMipmapLinearFilter || + texture.magFilter === LinearMipmapLinearFilter || + texture.minFilter === LinearFilter || + texture.minFilter === LinearMipmapNearestFilter || + texture.minFilter === NearestMipmapLinearFilter || + texture.minFilter === LinearMipmapLinearFilter + ); + } + + addChain(node) { + /* + if ( this.chaining.indexOf( node ) !== - 1 ) { + + console.warn( 'Recursive node: ', node ); + + } + */ + + this.chaining.push(node); + } + + removeChain(node) { + const lastChain = this.chaining.pop(); + + if (lastChain !== node) { + throw new Error('NodeBuilder: Invalid node chaining!'); + } + } + + getMethod(method) { + return method; + } + + getNodeFromHash(hash) { + return this.hashNodes[hash]; + } + + addFlow(shaderStage, node) { + this.flowNodes[shaderStage].push(node); + + return node; + } + + setContext(context) { + this.context = context; + } + + getContext() { + return this.context; + } + + setCache(cache) { + this.cache = cache; + } + + getCache() { + return this.cache; + } + + getCacheFromNode(node, parent = true) { + const data = this.getDataFromNode(node); + if (data.cache === undefined) data.cache = new NodeCache(parent ? this.getCache() : null); + + return data.cache; + } + + isAvailable(/*name*/) { + return false; + } + + getVertexIndex() { + console.warn('Abstract function.'); + } + + getInstanceIndex() { + console.warn('Abstract function.'); + } + + getFrontFacing() { + console.warn('Abstract function.'); + } + + getFragCoord() { + console.warn('Abstract function.'); + } + + isFlipY() { + return false; + } + + generateTexture(/* texture, textureProperty, uvSnippet */) { + console.warn('Abstract function.'); + } + + generateTextureLod(/* texture, textureProperty, uvSnippet, levelSnippet */) { + console.warn('Abstract function.'); + } + + generateConst(type, value = null) { + if (value === null) { + if (type === 'float' || type === 'int' || type === 'uint') value = 0; + else if (type === 'bool') value = false; + else if (type === 'color') value = new Color(); + else if (type === 'vec2') value = new Vector2(); + else if (type === 'vec3') value = new Vector3(); + else if (type === 'vec4') value = new Vector4(); + } + + if (type === 'float') return toFloat(value); + if (type === 'int') return `${Math.round(value)}`; + if (type === 'uint') return value >= 0 ? `${Math.round(value)}u` : '0u'; + if (type === 'bool') return value ? 'true' : 'false'; + if (type === 'color') + return `${this.getType('vec3')}( ${toFloat(value.r)}, ${toFloat(value.g)}, ${toFloat(value.b)} )`; + + const typeLength = this.getTypeLength(type); + + const componentType = this.getComponentType(type); + + const generateConst = value => this.generateConst(componentType, value); + + if (typeLength === 2) { + return `${this.getType(type)}( ${generateConst(value.x)}, ${generateConst(value.y)} )`; + } else if (typeLength === 3) { + return `${this.getType(type)}( ${generateConst(value.x)}, ${generateConst(value.y)}, ${generateConst(value.z)} )`; + } else if (typeLength === 4) { + return `${this.getType(type)}( ${generateConst(value.x)}, ${generateConst(value.y)}, ${generateConst(value.z)}, ${generateConst(value.w)} )`; + } else if (typeLength > 4 && value && (value.isMatrix3 || value.isMatrix4)) { + return `${this.getType(type)}( ${value.elements.map(generateConst).join(', ')} )`; + } else if (typeLength > 4) { + return `${this.getType(type)}()`; + } + + throw new Error(`NodeBuilder: Type '${type}' not found in generate constant attempt.`); + } + + getType(type) { + if (type === 'color') return 'vec3'; + + return type; + } + + hasGeometryAttribute(name) { + return this.geometry && this.geometry.getAttribute(name) !== undefined; + } + + getAttribute(name, type) { + const attributes = this.attributes; + + // find attribute + + for (const attribute of attributes) { + if (attribute.name === name) { + return attribute; + } + } + + // create a new if no exist + + const attribute = new NodeAttribute(name, type); + + attributes.push(attribute); + + return attribute; + } + + getPropertyName(node /*, shaderStage*/) { + return node.name; + } + + isVector(type) { + return /vec\d/.test(type); + } + + isMatrix(type) { + return /mat\d/.test(type); + } + + isReference(type) { + return ( + type === 'void' || + type === 'property' || + type === 'sampler' || + type === 'texture' || + type === 'cubeTexture' || + type === 'storageTexture' || + type === 'depthTexture' || + type === 'texture3D' + ); + } + + needsColorSpaceToLinear(/*texture*/) { + return false; + } + + getComponentTypeFromTexture(texture) { + const type = texture.type; + + if (texture.isDataTexture) { + if (type === IntType) return 'int'; + if (type === UnsignedIntType) return 'uint'; + } + + return 'float'; + } + + getElementType(type) { + if (type === 'mat2') return 'vec2'; + if (type === 'mat3') return 'vec3'; + if (type === 'mat4') return 'vec4'; + + return this.getComponentType(type); + } + + getComponentType(type) { + type = this.getVectorType(type); + + if (type === 'float' || type === 'bool' || type === 'int' || type === 'uint') return type; + + const componentType = /(b|i|u|)(vec|mat)([2-4])/.exec(type); + + if (componentType === null) return null; + + if (componentType[1] === 'b') return 'bool'; + if (componentType[1] === 'i') return 'int'; + if (componentType[1] === 'u') return 'uint'; + + return 'float'; + } + + getVectorType(type) { + if (type === 'color') return 'vec3'; + if (type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' || type === 'texture3D') + return 'vec4'; + + return type; + } + + getTypeFromLength(length, componentType = 'float') { + if (length === 1) return componentType; + + const baseType = typeFromLength.get(length); + const prefix = componentType === 'float' ? '' : componentType[0]; + + return prefix + baseType; + } + + getTypeFromArray(array) { + return typeFromArray.get(array.constructor); + } + + getTypeFromAttribute(attribute) { + let dataAttribute = attribute; + + if (attribute.isInterleavedBufferAttribute) dataAttribute = attribute.data; + + const array = dataAttribute.array; + const itemSize = attribute.itemSize; + const normalized = attribute.normalized; + + let arrayType; + + if (!(attribute instanceof Float16BufferAttribute) && normalized !== true) { + arrayType = this.getTypeFromArray(array); + } + + return this.getTypeFromLength(itemSize, arrayType); + } + + getTypeLength(type) { + const vecType = this.getVectorType(type); + const vecNum = /vec([2-4])/.exec(vecType); + + if (vecNum !== null) return Number(vecNum[1]); + if (vecType === 'float' || vecType === 'bool' || vecType === 'int' || vecType === 'uint') return 1; + if (/mat2/.test(type) === true) return 4; + if (/mat3/.test(type) === true) return 9; + if (/mat4/.test(type) === true) return 16; + + return 0; + } + + getVectorFromMatrix(type) { + return type.replace('mat', 'vec'); + } + + changeComponentType(type, newComponentType) { + return this.getTypeFromLength(this.getTypeLength(type), newComponentType); + } + + getIntegerType(type) { + const componentType = this.getComponentType(type); + + if (componentType === 'int' || componentType === 'uint') return type; + + return this.changeComponentType(type, 'int'); + } + + addStack() { + this.stack = stack(this.stack); + + this.stacks.push(getCurrentStack() || this.stack); + setCurrentStack(this.stack); + + return this.stack; + } + + removeStack() { + const lastStack = this.stack; + this.stack = lastStack.parent; + + setCurrentStack(this.stacks.pop()); + + return lastStack; + } + + getDataFromNode(node, shaderStage = this.shaderStage, cache = null) { + cache = cache === null ? (node.isGlobal(this) ? this.globalCache : this.cache) : cache; + + let nodeData = cache.getData(node); + + if (nodeData === undefined) { + nodeData = {}; + + cache.setData(node, nodeData); + } + + if (nodeData[shaderStage] === undefined) nodeData[shaderStage] = {}; + + return nodeData[shaderStage]; + } + + getNodeProperties(node, shaderStage = 'any') { + const nodeData = this.getDataFromNode(node, shaderStage); + + return nodeData.properties || (nodeData.properties = { outputNode: null }); + } + + getBufferAttributeFromNode(node, type) { + const nodeData = this.getDataFromNode(node); + + let bufferAttribute = nodeData.bufferAttribute; + + if (bufferAttribute === undefined) { + const index = this.uniforms.index++; + + bufferAttribute = new NodeAttribute('nodeAttribute' + index, type, node); + + this.bufferAttributes.push(bufferAttribute); + + nodeData.bufferAttribute = bufferAttribute; + } + + return bufferAttribute; + } + + getStructTypeFromNode(node, shaderStage = this.shaderStage) { + const nodeData = this.getDataFromNode(node, shaderStage); + + if (nodeData.structType === undefined) { + const index = this.structs.index++; + + node.name = `StructType${index}`; + this.structs[shaderStage].push(node); + + nodeData.structType = node; + } + + return node; + } + + getUniformFromNode(node, type, shaderStage = this.shaderStage, name = null) { + const nodeData = this.getDataFromNode(node, shaderStage, this.globalCache); + + let nodeUniform = nodeData.uniform; + + if (nodeUniform === undefined) { + const index = this.uniforms.index++; + + nodeUniform = new NodeUniform(name || 'nodeUniform' + index, type, node); + + this.uniforms[shaderStage].push(nodeUniform); + + nodeData.uniform = nodeUniform; + } + + return nodeUniform; + } + + getVarFromNode(node, name = null, type = node.getNodeType(this), shaderStage = this.shaderStage) { + const nodeData = this.getDataFromNode(node, shaderStage); + + let nodeVar = nodeData.variable; + + if (nodeVar === undefined) { + const vars = this.vars[shaderStage] || (this.vars[shaderStage] = []); + + if (name === null) name = 'nodeVar' + vars.length; + + nodeVar = new NodeVar(name, type); + + vars.push(nodeVar); + + nodeData.variable = nodeVar; + } + + return nodeVar; + } + + getVaryingFromNode(node, name = null, type = node.getNodeType(this)) { + const nodeData = this.getDataFromNode(node, 'any'); + + let nodeVarying = nodeData.varying; + + if (nodeVarying === undefined) { + const varyings = this.varyings; + const index = varyings.length; + + if (name === null) name = 'nodeVarying' + index; + + nodeVarying = new NodeVarying(name, type); + + varyings.push(nodeVarying); + + nodeData.varying = nodeVarying; + } + + return nodeVarying; + } + + getCodeFromNode(node, type, shaderStage = this.shaderStage) { + const nodeData = this.getDataFromNode(node); + + let nodeCode = nodeData.code; + + if (nodeCode === undefined) { + const codes = this.codes[shaderStage] || (this.codes[shaderStage] = []); + const index = codes.length; + + nodeCode = new NodeCode('nodeCode' + index, type); + + codes.push(nodeCode); + + nodeData.code = nodeCode; + } + + return nodeCode; + } + + addLineFlowCode(code) { + if (code === '') return this; + + code = this.tab + code; + + if (!/;\s*$/.test(code)) { + code = code + ';\n'; + } + + this.flow.code += code; + + return this; + } + + addFlowCode(code) { + this.flow.code += code; + + return this; + } + + addFlowTab() { + this.tab += '\t'; + + return this; + } + + removeFlowTab() { + this.tab = this.tab.slice(0, -1); + + return this; + } + + getFlowData(node /*, shaderStage*/) { + return this.flowsData.get(node); + } + + flowNode(node) { + const output = node.getNodeType(this); + + const flowData = this.flowChildNode(node, output); + + this.flowsData.set(node, flowData); + + return flowData; + } + + buildFunctionNode(shaderNode) { + const fn = new FunctionNode(); + + const previous = this.currentFunctionNode; + + this.currentFunctionNode = fn; + + fn.code = this.buildFunctionCode(shaderNode); + + this.currentFunctionNode = previous; + + return fn; + } + + flowShaderNode(shaderNode) { + const layout = shaderNode.layout; + + let inputs; + + if (shaderNode.isArrayInput) { + inputs = []; + + for (const input of layout.inputs) { + inputs.push(new ParameterNode(input.type, input.name)); + } + } else { + inputs = {}; + + for (const input of layout.inputs) { + inputs[input.name] = new ParameterNode(input.type, input.name); + } + } + + // + + shaderNode.layout = null; + + const callNode = shaderNode.call(inputs); + const flowData = this.flowStagesNode(callNode, layout.type); + + shaderNode.layout = layout; + + return flowData; + } + + flowStagesNode(node, output = null) { + const previousFlow = this.flow; + const previousVars = this.vars; + const previousCache = this.cache; + const previousBuildStage = this.buildStage; + const previousStack = this.stack; + + const flow = { + code: '', + }; + + this.flow = flow; + this.vars = {}; + this.cache = new NodeCache(); + this.stack = stack(); + + for (const buildStage of defaultBuildStages) { + this.setBuildStage(buildStage); + + flow.result = node.build(this, output); + } + + flow.vars = this.getVars(this.shaderStage); + + this.flow = previousFlow; + this.vars = previousVars; + this.cache = previousCache; + this.stack = previousStack; + + this.setBuildStage(previousBuildStage); + + return flow; + } + + getFunctionOperator() { + return null; + } + + flowChildNode(node, output = null) { + const previousFlow = this.flow; + + const flow = { + code: '', + }; + + this.flow = flow; + + flow.result = node.build(this, output); + + this.flow = previousFlow; + + return flow; + } + + flowNodeFromShaderStage(shaderStage, node, output = null, propertyName = null) { + const previousShaderStage = this.shaderStage; + + this.setShaderStage(shaderStage); + + const flowData = this.flowChildNode(node, output); + + if (propertyName !== null) { + flowData.code += `${this.tab + propertyName} = ${flowData.result};\n`; + } + + this.flowCode[shaderStage] = this.flowCode[shaderStage] + flowData.code; + + this.setShaderStage(previousShaderStage); + + return flowData; + } + + getAttributesArray() { + return this.attributes.concat(this.bufferAttributes); + } + + getAttributes(/*shaderStage*/) { + console.warn('Abstract function.'); + } + + getVaryings(/*shaderStage*/) { + console.warn('Abstract function.'); + } + + getVar(type, name) { + return `${this.getType(type)} ${name}`; + } + + getVars(shaderStage) { + let snippet = ''; + + const vars = this.vars[shaderStage]; + + if (vars !== undefined) { + for (const variable of vars) { + snippet += `${this.getVar(variable.type, variable.name)}; `; + } + } + + return snippet; + } + + getUniforms(/*shaderStage*/) { + console.warn('Abstract function.'); + } + + getCodes(shaderStage) { + const codes = this.codes[shaderStage]; + + let code = ''; + + if (codes !== undefined) { + for (const nodeCode of codes) { + code += nodeCode.code + '\n'; + } + } + + return code; + } + + getHash() { + return this.vertexShader + this.fragmentShader + this.computeShader; + } + + setShaderStage(shaderStage) { + this.shaderStage = shaderStage; + } + + getShaderStage() { + return this.shaderStage; + } + + setBuildStage(buildStage) { + this.buildStage = buildStage; + } + + getBuildStage() { + return this.buildStage; + } + + buildCode() { + console.warn('Abstract function.'); + } + + build() { + const { object, material } = this; + + if (material !== null) { + NodeMaterial.fromMaterial(material).build(this); + } else { + this.addFlow('compute', object); + } + + // setup() -> stage 1: create possible new nodes and returns an output reference node + // analyze() -> stage 2: analyze nodes to possible optimization and validation + // generate() -> stage 3: generate shader + + for (const buildStage of defaultBuildStages) { + this.setBuildStage(buildStage); + + if (this.context.vertex && this.context.vertex.isNode) { + this.flowNodeFromShaderStage('vertex', this.context.vertex); + } + + for (const shaderStage of shaderStages) { + this.setShaderStage(shaderStage); + + const flowNodes = this.flowNodes[shaderStage]; + + for (const node of flowNodes) { + if (buildStage === 'generate') { + this.flowNode(node); + } else { + node.build(this); + } + } + } + } + + this.setBuildStage(null); + this.setShaderStage(null); + + // stage 4: build code for a specific output + + this.buildCode(); + this.buildUpdateNodes(); + + return this; + } + + getNodeUniform(uniformNode, type) { + if (type === 'float' || type === 'int' || type === 'uint') return new NumberNodeUniform(uniformNode); + if (type === 'vec2' || type === 'ivec2' || type === 'uvec2') return new Vector2NodeUniform(uniformNode); + if (type === 'vec3' || type === 'ivec3' || type === 'uvec3') return new Vector3NodeUniform(uniformNode); + if (type === 'vec4' || type === 'ivec4' || type === 'uvec4') return new Vector4NodeUniform(uniformNode); + if (type === 'color') return new ColorNodeUniform(uniformNode); + if (type === 'mat3') return new Matrix3NodeUniform(uniformNode); + if (type === 'mat4') return new Matrix4NodeUniform(uniformNode); + + throw new Error(`Uniform "${type}" not declared.`); + } + + createNodeMaterial(type = 'NodeMaterial') { + // TODO: Move Materials.js to outside of the Nodes.js in order to remove this function and improve tree-shaking support + + return createNodeMaterialFromType(type); + } + + format(snippet, fromType, toType) { + fromType = this.getVectorType(fromType); + toType = this.getVectorType(toType); + + if (fromType === toType || toType === null || this.isReference(toType)) { + return snippet; + } + + const fromTypeLength = this.getTypeLength(fromType); + const toTypeLength = this.getTypeLength(toType); + + if (fromTypeLength > 4) { + // fromType is matrix-like + + // @TODO: ignore for now + + return snippet; + } + + if (toTypeLength > 4 || toTypeLength === 0) { + // toType is matrix-like or unknown + + // @TODO: ignore for now + + return snippet; + } + + if (fromTypeLength === toTypeLength) { + return `${this.getType(toType)}( ${snippet} )`; + } + + if (fromTypeLength > toTypeLength) { + return this.format( + `${snippet}.${'xyz'.slice(0, toTypeLength)}`, + this.getTypeFromLength(toTypeLength, this.getComponentType(fromType)), + toType, + ); + } + + if (toTypeLength === 4 && fromTypeLength > 1) { + // toType is vec4-like + + return `${this.getType(toType)}( ${this.format(snippet, fromType, 'vec3')}, 1.0 )`; + } + + if (fromTypeLength === 2) { + // fromType is vec2-like and toType is vec3-like + + return `${this.getType(toType)}( ${this.format(snippet, fromType, 'vec2')}, 0.0 )`; + } + + if (fromTypeLength === 1 && toTypeLength > 1 && fromType !== this.getComponentType(toType)) { + // fromType is float-like + + // convert a number value to vector type, e.g: + // vec3( 1u ) -> vec3( float( 1u ) ) + + snippet = `${this.getType(this.getComponentType(toType))}( ${snippet} )`; + } + + return `${this.getType(toType)}( ${snippet} )`; // fromType is float-like + } + + getSignature() { + return `// Three.js r${REVISION} - Node System\n`; + } +} + +export default NodeBuilder; diff --git a/examples-jsm/examples/nodes/core/NodeCache.ts b/examples-jsm/examples/nodes/core/NodeCache.ts new file mode 100644 index 00000000..ad72d50c --- /dev/null +++ b/examples-jsm/examples/nodes/core/NodeCache.ts @@ -0,0 +1,26 @@ +let id = 0; + +class NodeCache { + constructor(parent = null) { + this.id = id++; + this.nodesData = new WeakMap(); + + this.parent = parent; + } + + getData(node) { + let data = this.nodesData.get(node); + + if (data === undefined && this.parent !== null) { + data = this.parent.getData(node); + } + + return data; + } + + setData(node, data) { + this.nodesData.set(node, data); + } +} + +export default NodeCache; diff --git a/examples-jsm/examples/nodes/core/NodeCode.ts b/examples-jsm/examples/nodes/core/NodeCode.ts new file mode 100644 index 00000000..2ee50903 --- /dev/null +++ b/examples-jsm/examples/nodes/core/NodeCode.ts @@ -0,0 +1,11 @@ +class NodeCode { + constructor(name, type, code = '') { + this.name = name; + this.type = type; + this.code = code; + + Object.defineProperty(this, 'isNodeCode', { value: true }); + } +} + +export default NodeCode; diff --git a/examples-jsm/examples/nodes/core/NodeFrame.ts b/examples-jsm/examples/nodes/core/NodeFrame.ts new file mode 100644 index 00000000..ee64620c --- /dev/null +++ b/examples-jsm/examples/nodes/core/NodeFrame.ts @@ -0,0 +1,127 @@ +import { NodeUpdateType } from './constants.js'; + +class NodeFrame { + constructor() { + this.time = 0; + this.deltaTime = 0; + + this.frameId = 0; + this.renderId = 0; + + this.startTime = null; + + this.updateMap = new WeakMap(); + this.updateBeforeMap = new WeakMap(); + this.updateAfterMap = new WeakMap(); + + this.renderer = null; + this.material = null; + this.camera = null; + this.object = null; + this.scene = null; + } + + _getMaps(referenceMap, nodeRef) { + let maps = referenceMap.get(nodeRef); + + if (maps === undefined) { + maps = { + renderMap: new WeakMap(), + frameMap: new WeakMap(), + }; + + referenceMap.set(nodeRef, maps); + } + + return maps; + } + + updateBeforeNode(node) { + const updateType = node.getUpdateBeforeType(); + const reference = node.updateReference(this); + + if (updateType === NodeUpdateType.FRAME) { + const { frameMap } = this._getMaps(this.updateBeforeMap, reference); + + if (frameMap.get(reference) !== this.frameId) { + if (node.updateBefore(this) !== false) { + frameMap.set(reference, this.frameId); + } + } + } else if (updateType === NodeUpdateType.RENDER) { + const { renderMap } = this._getMaps(this.updateBeforeMap, reference); + + if (renderMap.get(reference) !== this.renderId) { + if (node.updateBefore(this) !== false) { + renderMap.set(reference, this.renderId); + } + } + } else if (updateType === NodeUpdateType.OBJECT) { + node.updateBefore(this); + } + } + + updateAfterNode(node) { + const updateType = node.getUpdateAfterType(); + const reference = node.updateReference(this); + + if (updateType === NodeUpdateType.FRAME) { + const { frameMap } = this._getMaps(this.updateAfterMap, reference); + + if (frameMap.get(reference) !== this.frameId) { + if (node.updateAfter(this) !== false) { + frameMap.set(reference, this.frameId); + } + } + } else if (updateType === NodeUpdateType.RENDER) { + const { renderMap } = this._getMaps(this.updateAfterMap, reference); + + if (renderMap.get(reference) !== this.renderId) { + if (node.updateAfter(this) !== false) { + renderMap.set(reference, this.renderId); + } + } + } else if (updateType === NodeUpdateType.OBJECT) { + node.updateAfter(this); + } + } + + updateNode(node) { + const updateType = node.getUpdateType(); + const reference = node.updateReference(this); + + if (updateType === NodeUpdateType.FRAME) { + const { frameMap } = this._getMaps(this.updateMap, reference); + + if (frameMap.get(reference) !== this.frameId) { + if (node.update(this) !== false) { + frameMap.set(reference, this.frameId); + } + } + } else if (updateType === NodeUpdateType.RENDER) { + const { renderMap } = this._getMaps(this.updateMap, reference); + + if (renderMap.get(reference) !== this.renderId) { + if (node.update(this) !== false) { + renderMap.set(reference, this.renderId); + } + } + } else if (updateType === NodeUpdateType.OBJECT) { + node.update(this); + } + } + + update() { + this.frameId++; + + if (this.lastTime === undefined) this.lastTime = performance.now(); + + this.deltaTime = (performance.now() - this.lastTime) / 1000; + + this.lastTime = performance.now(); + + this.time += this.deltaTime; + } +} + +export default NodeFrame; diff --git a/examples-jsm/examples/nodes/core/NodeFunction.ts b/examples-jsm/examples/nodes/core/NodeFunction.ts new file mode 100644 index 00000000..d05afb5e --- /dev/null +++ b/examples-jsm/examples/nodes/core/NodeFunction.ts @@ -0,0 +1,16 @@ +class NodeFunction { + constructor(type, inputs, name = '', precision = '') { + this.type = type; + this.inputs = inputs; + this.name = name; + this.precision = precision; + } + + getCode(/*name = this.name*/) { + console.warn('Abstract function.'); + } +} + +NodeFunction.isNodeFunction = true; + +export default NodeFunction; diff --git a/examples-jsm/examples/nodes/core/NodeKeywords.ts b/examples-jsm/examples/nodes/core/NodeKeywords.ts new file mode 100644 index 00000000..1f468321 --- /dev/null +++ b/examples-jsm/examples/nodes/core/NodeKeywords.ts @@ -0,0 +1,58 @@ +class NodeKeywords { + constructor() { + this.keywords = []; + this.nodes = {}; + this.keywordsCallback = {}; + } + + getNode(name) { + let node = this.nodes[name]; + + if (node === undefined && this.keywordsCallback[name] !== undefined) { + node = this.keywordsCallback[name](name); + + this.nodes[name] = node; + } + + return node; + } + + addKeyword(name, callback) { + this.keywords.push(name); + this.keywordsCallback[name] = callback; + + return this; + } + + parse(code) { + const keywordNames = this.keywords; + + const regExp = new RegExp(`\\b${keywordNames.join('\\b|\\b')}\\b`, 'g'); + + const codeKeywords = code.match(regExp); + + const keywordNodes = []; + + if (codeKeywords !== null) { + for (const keyword of codeKeywords) { + const node = this.getNode(keyword); + + if (node !== undefined && keywordNodes.indexOf(node) === -1) { + keywordNodes.push(node); + } + } + } + + return keywordNodes; + } + + include(builder, code) { + const keywordNodes = this.parse(code); + + for (const keywordNode of keywordNodes) { + keywordNode.build(builder); + } + } +} + +export default NodeKeywords; diff --git a/examples-jsm/examples/nodes/core/NodeParser.ts b/examples-jsm/examples/nodes/core/NodeParser.ts new file mode 100644 index 00000000..9849452f --- /dev/null +++ b/examples-jsm/examples/nodes/core/NodeParser.ts @@ -0,0 +1,7 @@ +class NodeParser { + parseFunction(/*source*/) { + console.warn('Abstract function.'); + } +} + +export default NodeParser; diff --git a/examples-jsm/examples/nodes/core/NodeUniform.ts b/examples-jsm/examples/nodes/core/NodeUniform.ts new file mode 100644 index 00000000..ca43958f --- /dev/null +++ b/examples-jsm/examples/nodes/core/NodeUniform.ts @@ -0,0 +1,27 @@ +class NodeUniform { + constructor(name, type, node) { + this.isNodeUniform = true; + + this.name = name; + this.type = type; + this.node = node.getSelf(); + } + + get value() { + return this.node.value; + } + + set value(val) { + this.node.value = val; + } + + get id() { + return this.node.id; + } + + get groupNode() { + return this.node.groupNode; + } +} + +export default NodeUniform; diff --git a/examples-jsm/examples/nodes/core/NodeUtils.ts b/examples-jsm/examples/nodes/core/NodeUtils.ts new file mode 100644 index 00000000..16a5f324 --- /dev/null +++ b/examples-jsm/examples/nodes/core/NodeUtils.ts @@ -0,0 +1,132 @@ +import { Color, Matrix3, Matrix4, Vector2, Vector3, Vector4 } from 'three'; + +export function getCacheKey(object, force = false) { + let cacheKey = '{'; + + if (object.isNode === true) { + cacheKey += object.id; + } + + for (const { property, childNode } of getNodeChildren(object)) { + cacheKey += ',' + property.slice(0, -4) + ':' + childNode.getCacheKey(force); + } + + cacheKey += '}'; + + return cacheKey; +} + +export function* getNodeChildren(node, toJSON = false) { + for (const property in node) { + // Ignore private properties. + if (property.startsWith('_') === true) continue; + + const object = node[property]; + + if (Array.isArray(object) === true) { + for (let i = 0; i < object.length; i++) { + const child = object[i]; + + if (child && (child.isNode === true || (toJSON && typeof child.toJSON === 'function'))) { + yield { property, index: i, childNode: child }; + } + } + } else if (object && object.isNode === true) { + yield { property, childNode: object }; + } else if (typeof object === 'object') { + for (const subProperty in object) { + const child = object[subProperty]; + + if (child && (child.isNode === true || (toJSON && typeof child.toJSON === 'function'))) { + yield { property, index: subProperty, childNode: child }; + } + } + } + } +} + +export function getValueType(value) { + if (value === undefined || value === null) return null; + + const typeOf = typeof value; + + if (value.isNode === true) { + return 'node'; + } else if (typeOf === 'number') { + return 'float'; + } else if (typeOf === 'boolean') { + return 'bool'; + } else if (typeOf === 'string') { + return 'string'; + } else if (typeOf === 'function') { + return 'shader'; + } else if (value.isVector2 === true) { + return 'vec2'; + } else if (value.isVector3 === true) { + return 'vec3'; + } else if (value.isVector4 === true) { + return 'vec4'; + } else if (value.isMatrix3 === true) { + return 'mat3'; + } else if (value.isMatrix4 === true) { + return 'mat4'; + } else if (value.isColor === true) { + return 'color'; + } else if (value instanceof ArrayBuffer) { + return 'ArrayBuffer'; + } + + return null; +} + +export function getValueFromType(type, ...params) { + const last4 = type ? type.slice(-4) : undefined; + + if (params.length === 1) { + // ensure same behaviour as in NodeBuilder.format() + + if (last4 === 'vec2') params = [params[0], params[0]]; + else if (last4 === 'vec3') params = [params[0], params[0], params[0]]; + else if (last4 === 'vec4') params = [params[0], params[0], params[0], params[0]]; + } + + if (type === 'color') { + return new Color(...params); + } else if (last4 === 'vec2') { + return new Vector2(...params); + } else if (last4 === 'vec3') { + return new Vector3(...params); + } else if (last4 === 'vec4') { + return new Vector4(...params); + } else if (last4 === 'mat3') { + return new Matrix3(...params); + } else if (last4 === 'mat4') { + return new Matrix4(...params); + } else if (type === 'bool') { + return params[0] || false; + } else if (type === 'float' || type === 'int' || type === 'uint') { + return params[0] || 0; + } else if (type === 'string') { + return params[0] || ''; + } else if (type === 'ArrayBuffer') { + return base64ToArrayBuffer(params[0]); + } + + return null; +} + +export function arrayBufferToBase64(arrayBuffer) { + let chars = ''; + + const array = new Uint8Array(arrayBuffer); + + for (let i = 0; i < array.length; i++) { + chars += String.fromCharCode(array[i]); + } + + return btoa(chars); +} + +export function base64ToArrayBuffer(base64) { + return Uint8Array.from(atob(base64), c => c.charCodeAt(0)).buffer; +} diff --git a/examples-jsm/examples/nodes/core/NodeVar.ts b/examples-jsm/examples/nodes/core/NodeVar.ts new file mode 100644 index 00000000..e6e935b3 --- /dev/null +++ b/examples-jsm/examples/nodes/core/NodeVar.ts @@ -0,0 +1,10 @@ +class NodeVar { + constructor(name, type) { + this.isNodeVar = true; + + this.name = name; + this.type = type; + } +} + +export default NodeVar; diff --git a/examples-jsm/examples/nodes/core/NodeVarying.ts b/examples-jsm/examples/nodes/core/NodeVarying.ts new file mode 100644 index 00000000..a1482362 --- /dev/null +++ b/examples-jsm/examples/nodes/core/NodeVarying.ts @@ -0,0 +1,13 @@ +import NodeVar from './NodeVar.js'; + +class NodeVarying extends NodeVar { + constructor(name, type) { + super(name, type); + + this.needsInterpolation = false; + + this.isNodeVarying = true; + } +} + +export default NodeVarying; diff --git a/examples-jsm/examples/nodes/core/StackNode.ts b/examples-jsm/examples/nodes/core/StackNode.ts new file mode 100644 index 00000000..d9322607 --- /dev/null +++ b/examples-jsm/examples/nodes/core/StackNode.ts @@ -0,0 +1,71 @@ +import Node, { addNodeClass } from './Node.js'; +import { cond } from '../math/CondNode.js'; +import { ShaderNode, nodeProxy, getCurrentStack, setCurrentStack } from '../shadernode/ShaderNode.js'; + +class StackNode extends Node { + constructor(parent = null) { + super(); + + this.nodes = []; + this.outputNode = null; + + this.parent = parent; + + this._currentCond = null; + + this.isStackNode = true; + } + + getNodeType(builder) { + return this.outputNode ? this.outputNode.getNodeType(builder) : 'void'; + } + + add(node) { + this.nodes.push(node); + + return this; + } + + if(boolNode, method) { + const methodNode = new ShaderNode(method); + this._currentCond = cond(boolNode, methodNode); + + return this.add(this._currentCond); + } + + elseif(boolNode, method) { + const methodNode = new ShaderNode(method); + const ifNode = cond(boolNode, methodNode); + + this._currentCond.elseNode = ifNode; + this._currentCond = ifNode; + + return this; + } + + else(method) { + this._currentCond.elseNode = new ShaderNode(method); + + return this; + } + + build(builder, ...params) { + const previousStack = getCurrentStack(); + + setCurrentStack(this); + + for (const node of this.nodes) { + node.build(builder, 'void'); + } + + setCurrentStack(previousStack); + + return this.outputNode ? this.outputNode.build(builder, ...params) : super.build(builder, ...params); + } +} + +export default StackNode; + +export const stack = nodeProxy(StackNode); + +addNodeClass('StackNode', StackNode); diff --git a/examples-jsm/examples/nodes/core/StructTypeNode.ts b/examples-jsm/examples/nodes/core/StructTypeNode.ts new file mode 100644 index 00000000..69718799 --- /dev/null +++ b/examples-jsm/examples/nodes/core/StructTypeNode.ts @@ -0,0 +1,18 @@ +import Node, { addNodeClass } from './Node.js'; + +class StructTypeNode extends Node { + constructor(types) { + super(); + + this.types = types; + this.isStructTypeNode = true; + } + + getMemberTypes() { + return this.types; + } +} + +export default StructTypeNode; + +addNodeClass('StructTypeNode', StructTypeNode); diff --git a/examples-jsm/examples/nodes/core/UniformGroupNode.ts b/examples-jsm/examples/nodes/core/UniformGroupNode.ts new file mode 100644 index 00000000..f8bb2b37 --- /dev/null +++ b/examples-jsm/examples/nodes/core/UniformGroupNode.ts @@ -0,0 +1,30 @@ +import Node from './Node.js'; +import { addNodeClass } from './Node.js'; + +class UniformGroupNode extends Node { + constructor(name, shared = false) { + super('string'); + + this.name = name; + this.version = 0; + + this.shared = shared; + + this.isUniformGroup = true; + } + + set needsUpdate(value) { + if (value === true) this.version++; + } +} + +export const uniformGroup = name => new UniformGroupNode(name); +export const sharedUniformGroup = name => new UniformGroupNode(name, true); + +export const frameGroup = sharedUniformGroup('frame'); +export const renderGroup = sharedUniformGroup('render'); +export const objectGroup = uniformGroup('object'); + +export default UniformGroupNode; + +addNodeClass('UniformGroupNode', UniformGroupNode); diff --git a/examples-jsm/examples/nodes/core/UniformNode.ts b/examples-jsm/examples/nodes/core/UniformNode.ts new file mode 100644 index 00000000..41e523c4 --- /dev/null +++ b/examples-jsm/examples/nodes/core/UniformNode.ts @@ -0,0 +1,90 @@ +import InputNode from './InputNode.js'; +import { objectGroup } from './UniformGroupNode.js'; +import { addNodeClass } from './Node.js'; +import { nodeObject, getConstNodeType } from '../shadernode/ShaderNode.js'; + +class UniformNode extends InputNode { + constructor(value, nodeType = null) { + super(value, nodeType); + + this.isUniformNode = true; + + this.name = ''; + this.groupNode = objectGroup; + } + + label(name) { + this.name = name; + + return this; + } + + setGroup(group) { + this.groupNode = group; + + return this; + } + + getGroup() { + return this.groupNode; + } + + getUniformHash(builder) { + return this.getHash(builder); + } + + onUpdate(callback, updateType) { + const self = this.getSelf(); + + callback = callback.bind(self); + + return super.onUpdate(frame => { + const value = callback(frame, self); + + if (value !== undefined) { + this.value = value; + } + }, updateType); + } + + generate(builder, output) { + const type = this.getNodeType(builder); + + const hash = this.getUniformHash(builder); + + let sharedNode = builder.getNodeFromHash(hash); + + if (sharedNode === undefined) { + builder.setHashNode(this, hash); + + sharedNode = this; + } + + const sharedNodeType = sharedNode.getInputType(builder); + + const nodeUniform = builder.getUniformFromNode( + sharedNode, + sharedNodeType, + builder.shaderStage, + this.name || builder.context.label, + ); + const propertyName = builder.getPropertyName(nodeUniform); + + if (builder.context.label !== undefined) delete builder.context.label; + + return builder.format(propertyName, type, output); + } +} + +export default UniformNode; + +export const uniform = (arg1, arg2) => { + const nodeType = getConstNodeType(arg2 || arg1); + + // @TODO: get ConstNode from .traverse() in the future + const value = arg1 && arg1.isNode === true ? (arg1.node && arg1.node.value) || arg1.value : arg1; + + return nodeObject(new UniformNode(value, nodeType)); +}; + +addNodeClass('UniformNode', UniformNode); diff --git a/examples-jsm/examples/nodes/core/constants.ts b/examples-jsm/examples/nodes/core/constants.ts new file mode 100644 index 00000000..3b01a9a6 --- /dev/null +++ b/examples-jsm/examples/nodes/core/constants.ts @@ -0,0 +1,28 @@ +export const NodeShaderStage = { + VERTEX: 'vertex', + FRAGMENT: 'fragment', +}; + +export const NodeUpdateType = { + NONE: 'none', + FRAME: 'frame', + RENDER: 'render', + OBJECT: 'object', +}; + +export const NodeType = { + BOOLEAN: 'bool', + INTEGER: 'int', + FLOAT: 'float', + VECTOR2: 'vec2', + VECTOR3: 'vec3', + VECTOR4: 'vec4', + MATRIX2: 'mat2', + MATRIX3: 'mat3', + MATRIX4: 'mat4', +}; + +export const defaultShaderStages = ['fragment', 'vertex']; +export const defaultBuildStages = ['setup', 'analyze', 'generate']; +export const shaderStages = [...defaultShaderStages, 'compute']; +export const vectorComponents = ['x', 'y', 'z', 'w']; diff --git a/examples-jsm/examples/nodes/fog/FogNode.ts b/examples-jsm/examples/nodes/fog/FogNode.ts new file mode 100644 index 00000000..9417df5a --- /dev/null +++ b/examples-jsm/examples/nodes/fog/FogNode.ts @@ -0,0 +1,38 @@ +import Node, { addNodeClass } from '../core/Node.js'; +import { positionView } from '../accessors/PositionNode.js'; +import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js'; + +class FogNode extends Node { + constructor(colorNode, factorNode) { + super('float'); + + this.isFogNode = true; + + this.colorNode = colorNode; + this.factorNode = factorNode; + } + + getViewZNode(builder) { + let viewZ; + + const getViewZ = builder.context.getViewZ; + + if (getViewZ !== undefined) { + viewZ = getViewZ(this); + } + + return (viewZ || positionView.z).negate(); + } + + setup() { + return this.factorNode; + } +} + +export default FogNode; + +export const fog = nodeProxy(FogNode); + +addNodeElement('fog', fog); + +addNodeClass('FogNode', FogNode); diff --git a/examples-jsm/examples/nodes/gpgpu/ComputeNode.ts b/examples-jsm/examples/nodes/gpgpu/ComputeNode.ts new file mode 100644 index 00000000..ab2925a5 --- /dev/null +++ b/examples-jsm/examples/nodes/gpgpu/ComputeNode.ts @@ -0,0 +1,67 @@ +import Node, { addNodeClass } from '../core/Node.js'; +import { NodeUpdateType } from '../core/constants.js'; +import { addNodeElement, nodeObject } from '../shadernode/ShaderNode.js'; + +class ComputeNode extends Node { + constructor(computeNode, count, workgroupSize = [64]) { + super('void'); + + this.isComputeNode = true; + + this.computeNode = computeNode; + + this.count = count; + this.workgroupSize = workgroupSize; + this.dispatchCount = 0; + + this.version = 1; + this.updateBeforeType = NodeUpdateType.OBJECT; + + this.updateDispatchCount(); + } + + dispose() { + this.dispatchEvent({ type: 'dispose' }); + } + + set needsUpdate(value) { + if (value === true) this.version++; + } + + updateDispatchCount() { + const { count, workgroupSize } = this; + + let size = workgroupSize[0]; + + for (let i = 1; i < workgroupSize.length; i++) size *= workgroupSize[i]; + + this.dispatchCount = Math.ceil(count / size); + } + + onInit() {} + + updateBefore({ renderer }) { + renderer.compute(this); + } + + generate(builder) { + const { shaderStage } = builder; + + if (shaderStage === 'compute') { + const snippet = this.computeNode.build(builder, 'void'); + + if (snippet !== '') { + builder.addLineFlowCode(snippet); + } + } + } +} + +export default ComputeNode; + +export const compute = (node, count, workgroupSize) => + nodeObject(new ComputeNode(nodeObject(node), count, workgroupSize)); + +addNodeElement('compute', compute); + +addNodeClass('ComputeNode', ComputeNode); diff --git a/examples-jsm/examples/nodes/lighting/EnvironmentNode.ts b/examples-jsm/examples/nodes/lighting/EnvironmentNode.ts new file mode 100644 index 00000000..31ddc8b0 --- /dev/null +++ b/examples-jsm/examples/nodes/lighting/EnvironmentNode.ts @@ -0,0 +1,119 @@ +import LightingNode from './LightingNode.js'; +import { cache } from '../core/CacheNode.js'; +import { context } from '../core/ContextNode.js'; +import { roughness, clearcoatRoughness } from '../core/PropertyNode.js'; +import { cameraViewMatrix } from '../accessors/CameraNode.js'; +import { + transformedClearcoatNormalView, + transformedNormalView, + transformedNormalWorld, +} from '../accessors/NormalNode.js'; +import { positionViewDirection } from '../accessors/PositionNode.js'; +import { addNodeClass } from '../core/Node.js'; +import { float } from '../shadernode/ShaderNode.js'; +import { reference } from '../accessors/ReferenceNode.js'; +import { transformedBentNormalView } from '../accessors/AccessorsUtils.js'; +import { pmremTexture } from '../pmrem/PMREMNode.js'; + +const envNodeCache = new WeakMap(); + +class EnvironmentNode extends LightingNode { + constructor(envNode = null) { + super(); + + this.envNode = envNode; + } + + setup(builder) { + let envNode = this.envNode; + + if (envNode.isTextureNode) { + let cacheEnvNode = envNodeCache.get(envNode.value); + + if (cacheEnvNode === undefined) { + cacheEnvNode = pmremTexture(envNode.value); + + envNodeCache.set(envNode.value, cacheEnvNode); + } + + envNode = cacheEnvNode; + } + + // + + const { material } = builder; + + const envMap = material.envMap; + const intensity = envMap + ? reference('envMapIntensity', 'float', builder.material) + : reference('environmentIntensity', 'float', builder.scene); // @TODO: Add materialEnvIntensity in MaterialNode + + const useAnisotropy = material.useAnisotropy === true || material.anisotropy > 0; + const radianceNormalView = useAnisotropy ? transformedBentNormalView : transformedNormalView; + + const radiance = context(envNode, createRadianceContext(roughness, radianceNormalView)).mul(intensity); + const irradiance = context(envNode, createIrradianceContext(transformedNormalWorld)) + .mul(Math.PI) + .mul(intensity); + + const isolateRadiance = cache(radiance); + const isolateIrradiance = cache(irradiance); + + // + + builder.context.radiance.addAssign(isolateRadiance); + + builder.context.iblIrradiance.addAssign(isolateIrradiance); + + // + + const clearcoatRadiance = builder.context.lightingModel.clearcoatRadiance; + + if (clearcoatRadiance) { + const clearcoatRadianceContext = context( + envNode, + createRadianceContext(clearcoatRoughness, transformedClearcoatNormalView), + ).mul(intensity); + const isolateClearcoatRadiance = cache(clearcoatRadianceContext); + + clearcoatRadiance.addAssign(isolateClearcoatRadiance); + } + } +} + +const createRadianceContext = (roughnessNode, normalViewNode) => { + let reflectVec = null; + + return { + getUV: () => { + if (reflectVec === null) { + reflectVec = positionViewDirection.negate().reflect(normalViewNode); + + // Mixing the reflection with the normal is more accurate and keeps rough objects from gathering light from behind their tangent plane. + reflectVec = roughnessNode.mul(roughnessNode).mix(reflectVec, normalViewNode).normalize(); + + reflectVec = reflectVec.transformDirection(cameraViewMatrix); + } + + return reflectVec; + }, + getTextureLevel: () => { + return roughnessNode; + }, + }; +}; + +const createIrradianceContext = normalWorldNode => { + return { + getUV: () => { + return normalWorldNode; + }, + getTextureLevel: () => { + return float(1.0); + }, + }; +}; + +export default EnvironmentNode; + +addNodeClass('EnvironmentNode', EnvironmentNode); diff --git a/examples-jsm/examples/nodes/lighting/LightingContextNode.ts b/examples-jsm/examples/nodes/lighting/LightingContextNode.ts new file mode 100644 index 00000000..02a8b51f --- /dev/null +++ b/examples-jsm/examples/nodes/lighting/LightingContextNode.ts @@ -0,0 +1,58 @@ +import ContextNode from '../core/ContextNode.js'; +import { addNodeClass } from '../core/Node.js'; +import { addNodeElement, nodeProxy, float, vec3 } from '../shadernode/ShaderNode.js'; + +class LightingContextNode extends ContextNode { + constructor(node, lightingModel = null, backdropNode = null, backdropAlphaNode = null) { + super(node); + + this.lightingModel = lightingModel; + this.backdropNode = backdropNode; + this.backdropAlphaNode = backdropAlphaNode; + + this._context = null; + } + + getContext() { + const { backdropNode, backdropAlphaNode } = this; + + const directDiffuse = vec3().temp('directDiffuse'), + directSpecular = vec3().temp('directSpecular'), + indirectDiffuse = vec3().temp('indirectDiffuse'), + indirectSpecular = vec3().temp('indirectSpecular'); + + const reflectedLight = { + directDiffuse, + directSpecular, + indirectDiffuse, + indirectSpecular, + }; + + const context = { + radiance: vec3().temp('radiance'), + irradiance: vec3().temp('irradiance'), + iblIrradiance: vec3().temp('iblIrradiance'), + ambientOcclusion: float(1).temp('ambientOcclusion'), + reflectedLight, + backdrop: backdropNode, + backdropAlpha: backdropAlphaNode, + }; + + return context; + } + + setup(builder) { + this.context = this._context || (this._context = this.getContext()); + this.context.lightingModel = this.lightingModel || builder.context.lightingModel; + + return super.setup(builder); + } +} + +export default LightingContextNode; + +export const lightingContext = nodeProxy(LightingContextNode); + +addNodeElement('lightingContext', lightingContext); + +addNodeClass('LightingContextNode', LightingContextNode); diff --git a/examples-jsm/examples/nodes/lighting/LightsNode.ts b/examples-jsm/examples/nodes/lighting/LightsNode.ts new file mode 100644 index 00000000..96e5c60a --- /dev/null +++ b/examples-jsm/examples/nodes/lighting/LightsNode.ts @@ -0,0 +1,170 @@ +import Node from '../core/Node.js'; +import AnalyticLightNode from './AnalyticLightNode.js'; +import { nodeObject, nodeProxy, vec3 } from '../shadernode/ShaderNode.js'; + +const LightNodes = new WeakMap(); + +const sortLights = lights => { + return lights.sort((a, b) => a.id - b.id); +}; + +class LightsNode extends Node { + constructor(lightNodes = []) { + super('vec3'); + + this.totalDiffuseNode = vec3().temp('totalDiffuse'); + this.totalSpecularNode = vec3().temp('totalSpecular'); + + this.outgoingLightNode = vec3().temp('outgoingLight'); + + this.lightNodes = lightNodes; + + this._hash = null; + } + + get hasLight() { + return this.lightNodes.length > 0; + } + + getHash() { + if (this._hash === null) { + const hash = []; + + for (const lightNode of this.lightNodes) { + hash.push(lightNode.getHash()); + } + + this._hash = 'lights-' + hash.join(','); + } + + return this._hash; + } + + analyze(builder) { + const properties = builder.getDataFromNode(this); + + for (const node of properties.nodes) { + node.build(builder); + } + } + + setup(builder) { + const context = builder.context; + const lightingModel = context.lightingModel; + + let outgoingLightNode = this.outgoingLightNode; + + if (lightingModel) { + const { lightNodes, totalDiffuseNode, totalSpecularNode } = this; + + context.outgoingLight = outgoingLightNode; + + const stack = builder.addStack(); + + // + + const properties = builder.getDataFromNode(this); + properties.nodes = stack.nodes; + + // + + lightingModel.start(context, stack, builder); + + // lights + + for (const lightNode of lightNodes) { + lightNode.build(builder); + } + + // + + lightingModel.indirectDiffuse(context, stack, builder); + lightingModel.indirectSpecular(context, stack, builder); + lightingModel.ambientOcclusion(context, stack, builder); + + // + + const { backdrop, backdropAlpha } = context; + const { directDiffuse, directSpecular, indirectDiffuse, indirectSpecular } = context.reflectedLight; + + let totalDiffuse = directDiffuse.add(indirectDiffuse); + + if (backdrop !== null) { + if (backdropAlpha !== null) { + totalDiffuse = vec3(backdropAlpha.mix(totalDiffuse, backdrop)); + } else { + totalDiffuse = vec3(backdrop); + } + + context.material.transparent = true; + } + + totalDiffuseNode.assign(totalDiffuse); + totalSpecularNode.assign(directSpecular.add(indirectSpecular)); + + outgoingLightNode.assign(totalDiffuseNode.add(totalSpecularNode)); + + // + + lightingModel.finish(context, stack, builder); + + // + + outgoingLightNode = outgoingLightNode.bypass(builder.removeStack()); + } + + return outgoingLightNode; + } + + _getLightNodeById(id) { + for (const lightNode of this.lightNodes) { + if (lightNode.isAnalyticLightNode && lightNode.light.id === id) { + return lightNode; + } + } + + return null; + } + + fromLights(lights = []) { + const lightNodes = []; + + lights = sortLights(lights); + + for (const light of lights) { + let lightNode = this._getLightNodeById(light.id); + + if (lightNode === null) { + const lightClass = light.constructor; + const lightNodeClass = LightNodes.has(lightClass) ? LightNodes.get(lightClass) : AnalyticLightNode; + + lightNode = nodeObject(new lightNodeClass(light)); + } + + lightNodes.push(lightNode); + } + + this.lightNodes = lightNodes; + this._hash = null; + + return this; + } +} + +export default LightsNode; + +export const lights = lights => nodeObject(new LightsNode().fromLights(lights)); +export const lightsNode = nodeProxy(LightsNode); + +export function addLightNode(lightClass, lightNodeClass) { + if (LightNodes.has(lightClass)) { + console.warn(`Redefinition of light node ${lightNodeClass.type}`); + return; + } + + if (typeof lightClass !== 'function') throw new Error(`Light ${lightClass.name} is not a class`); + if (typeof lightNodeClass !== 'function' || !lightNodeClass.type) + throw new Error(`Light node ${lightNodeClass.type} is not a class`); + + LightNodes.set(lightClass, lightNodeClass); +} diff --git a/examples-jsm/examples/nodes/materials/NodeMaterial.ts b/examples-jsm/examples/nodes/materials/NodeMaterial.ts new file mode 100644 index 00000000..d0c7c6cd --- /dev/null +++ b/examples-jsm/examples/nodes/materials/NodeMaterial.ts @@ -0,0 +1,514 @@ +import { Material, NormalBlending } from 'three'; +import { getNodeChildren, getCacheKey } from '../core/NodeUtils.js'; +import { attribute } from '../core/AttributeNode.js'; +import { output, diffuseColor, varyingProperty } from '../core/PropertyNode.js'; +import { + materialAlphaTest, + materialColor, + materialOpacity, + materialEmissive, + materialNormal, +} from '../accessors/MaterialNode.js'; +import { modelViewProjection } from '../accessors/ModelViewProjectionNode.js'; +import { transformedNormalView, normalLocal } from '../accessors/NormalNode.js'; +import { instance } from '../accessors/InstanceNode.js'; +import { batch } from '../accessors/BatchNode.js'; +import { materialReference } from '../accessors/MaterialReferenceNode.js'; +import { positionLocal, positionView } from '../accessors/PositionNode.js'; +import { skinningReference } from '../accessors/SkinningNode.js'; +import { morphReference } from '../accessors/MorphNode.js'; +import { texture } from '../accessors/TextureNode.js'; +import { cubeTexture } from '../accessors/CubeTextureNode.js'; +import { lightsNode } from '../lighting/LightsNode.js'; +import { mix } from '../math/MathNode.js'; +import { float, vec3, vec4 } from '../shadernode/ShaderNode.js'; +import AONode from '../lighting/AONode.js'; +import { lightingContext } from '../lighting/LightingContextNode.js'; +import EnvironmentNode from '../lighting/EnvironmentNode.js'; +import IrradianceNode from '../lighting/IrradianceNode.js'; +import { depth } from '../display/ViewportDepthNode.js'; +import { cameraLogDepth } from '../accessors/CameraNode.js'; +import { clipping, clippingAlpha } from '../accessors/ClippingNode.js'; +import { faceDirection } from '../display/FrontFacingNode.js'; + +const NodeMaterials = new Map(); + +class NodeMaterial extends Material { + constructor() { + super(); + + this.isNodeMaterial = true; + + this.type = this.constructor.type; + + this.forceSinglePass = false; + + this.fog = true; + this.lights = true; + this.normals = true; + + this.lightsNode = null; + this.envNode = null; + this.aoNode = null; + + this.colorNode = null; + this.normalNode = null; + this.opacityNode = null; + this.backdropNode = null; + this.backdropAlphaNode = null; + this.alphaTestNode = null; + + this.positionNode = null; + + this.depthNode = null; + this.shadowNode = null; + this.shadowPositionNode = null; + + this.outputNode = null; + + this.fragmentNode = null; + this.vertexNode = null; + } + + customProgramCacheKey() { + return this.type + getCacheKey(this); + } + + build(builder) { + this.setup(builder); + } + + setup(builder) { + // < VERTEX STAGE > + + builder.addStack(); + + builder.stack.outputNode = this.vertexNode || this.setupPosition(builder); + + builder.addFlow('vertex', builder.removeStack()); + + // < FRAGMENT STAGE > + + builder.addStack(); + + let resultNode; + + const clippingNode = this.setupClipping(builder); + + if (this.depthWrite === true) this.setupDepth(builder); + + if (this.fragmentNode === null) { + if (this.normals === true) this.setupNormal(builder); + + this.setupDiffuseColor(builder); + this.setupVariants(builder); + + const outgoingLightNode = this.setupLighting(builder); + + if (clippingNode !== null) builder.stack.add(clippingNode); + + // force unsigned floats - useful for RenderTargets + + const basicOutput = vec4(outgoingLightNode, diffuseColor.a).max(0); + + resultNode = this.setupOutput(builder, basicOutput); + + // OUTPUT NODE + + output.assign(resultNode); + + // + + if (this.outputNode !== null) resultNode = this.outputNode; + } else { + let fragmentNode = this.fragmentNode; + + if (fragmentNode.isOutputStructNode !== true) { + fragmentNode = vec4(fragmentNode); + } + + resultNode = this.setupOutput(builder, fragmentNode); + } + + builder.stack.outputNode = resultNode; + + builder.addFlow('fragment', builder.removeStack()); + } + + setupClipping(builder) { + if (builder.clippingContext === null) return null; + + const { globalClippingCount, localClippingCount } = builder.clippingContext; + + let result = null; + + if (globalClippingCount || localClippingCount) { + if (this.alphaToCoverage) { + // to be added to flow when the color/alpha value has been determined + result = clippingAlpha(); + } else { + builder.stack.add(clipping()); + } + } + + return result; + } + + setupDepth(builder) { + const { renderer } = builder; + + // Depth + + let depthNode = this.depthNode; + + if (depthNode === null && renderer.logarithmicDepthBuffer === true) { + const fragDepth = modelViewProjection().w.add(1); + + depthNode = fragDepth.log2().mul(cameraLogDepth).mul(0.5); + } + + if (depthNode !== null) { + depth.assign(depthNode).append(); + } + } + + setupPosition(builder) { + const { object } = builder; + const geometry = object.geometry; + + builder.addStack(); + + // Vertex + + if (geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color) { + morphReference(object).append(); + } + + if (object.isSkinnedMesh === true) { + skinningReference(object).append(); + } + + if (this.displacementMap) { + const displacementMap = materialReference('displacementMap', 'texture'); + const displacementScale = materialReference('displacementScale', 'float'); + const displacementBias = materialReference('displacementBias', 'float'); + + positionLocal.addAssign( + normalLocal.normalize().mul(displacementMap.x.mul(displacementScale).add(displacementBias)), + ); + } + + if (object.isBatchedMesh) { + batch(object).append(); + } + + if (object.instanceMatrix && object.instanceMatrix.isInstancedBufferAttribute === true) { + instance(object).append(); + } + + if (this.positionNode !== null) { + positionLocal.assign(this.positionNode); + } + + const mvp = modelViewProjection(); + + builder.context.vertex = builder.removeStack(); + builder.context.mvp = mvp; + + return mvp; + } + + setupDiffuseColor({ object, geometry }) { + let colorNode = this.colorNode ? vec4(this.colorNode) : materialColor; + + // VERTEX COLORS + + if (this.vertexColors === true && geometry.hasAttribute('color')) { + colorNode = vec4(colorNode.xyz.mul(attribute('color', 'vec3')), colorNode.a); + } + + // Instanced colors + + if (object.instanceColor) { + const instanceColor = varyingProperty('vec3', 'vInstanceColor'); + + colorNode = instanceColor.mul(colorNode); + } + + // COLOR + + diffuseColor.assign(colorNode); + + // OPACITY + + const opacityNode = this.opacityNode ? float(this.opacityNode) : materialOpacity; + diffuseColor.a.assign(diffuseColor.a.mul(opacityNode)); + + // ALPHA TEST + + if (this.alphaTestNode !== null || this.alphaTest > 0) { + const alphaTestNode = this.alphaTestNode !== null ? float(this.alphaTestNode) : materialAlphaTest; + + diffuseColor.a.lessThanEqual(alphaTestNode).discard(); + } + + if (this.transparent === false && this.blending === NormalBlending && this.alphaToCoverage === false) { + diffuseColor.a.assign(1.0); + } + } + + setupVariants(/*builder*/) { + // Interface function. + } + + setupNormal() { + // NORMAL VIEW + + if (this.flatShading === true) { + const normalNode = positionView.dFdx().cross(positionView.dFdy()).normalize(); + + transformedNormalView.assign(normalNode.mul(faceDirection)); + } else { + const normalNode = this.normalNode ? vec3(this.normalNode) : materialNormal; + + transformedNormalView.assign(normalNode.mul(faceDirection)); + } + } + + getEnvNode(builder) { + let node = null; + + if (this.envNode) { + node = this.envNode; + } else if (this.envMap) { + node = this.envMap.isCubeTexture ? cubeTexture(this.envMap) : texture(this.envMap); + } else if (builder.environmentNode) { + node = builder.environmentNode; + } + + return node; + } + + setupLights(builder) { + const envNode = this.getEnvNode(builder); + + // + + const materialLightsNode = []; + + if (envNode) { + materialLightsNode.push(new EnvironmentNode(envNode)); + } + + if (builder.material.lightMap) { + materialLightsNode.push(new IrradianceNode(materialReference('lightMap', 'texture'))); + } + + if (this.aoNode !== null || builder.material.aoMap) { + const aoNode = this.aoNode !== null ? this.aoNode : texture(builder.material.aoMap); + + materialLightsNode.push(new AONode(aoNode)); + } + + let lightsN = this.lightsNode || builder.lightsNode; + + if (materialLightsNode.length > 0) { + lightsN = lightsNode([...lightsN.lightNodes, ...materialLightsNode]); + } + + return lightsN; + } + + setupLightingModel(/*builder*/) { + // Interface function. + } + + setupLighting(builder) { + const { material } = builder; + const { backdropNode, backdropAlphaNode, emissiveNode } = this; + + // OUTGOING LIGHT + + const lights = this.lights === true || this.lightsNode !== null; + + const lightsNode = lights ? this.setupLights(builder) : null; + + let outgoingLightNode = diffuseColor.rgb; + + if (lightsNode && lightsNode.hasLight !== false) { + const lightingModel = this.setupLightingModel(builder); + + outgoingLightNode = lightingContext(lightsNode, lightingModel, backdropNode, backdropAlphaNode); + } else if (backdropNode !== null) { + outgoingLightNode = vec3( + backdropAlphaNode !== null ? mix(outgoingLightNode, backdropNode, backdropAlphaNode) : backdropNode, + ); + } + + // EMISSIVE + + if ( + (emissiveNode && emissiveNode.isNode === true) || + (material.emissive && material.emissive.isColor === true) + ) { + outgoingLightNode = outgoingLightNode.add(vec3(emissiveNode ? emissiveNode : materialEmissive)); + } + + return outgoingLightNode; + } + + setupOutput(builder, outputNode) { + // FOG + + if (this.fog === true) { + const fogNode = builder.fogNode; + + if (fogNode) outputNode = vec4(fogNode.mix(outputNode.rgb, fogNode.colorNode), outputNode.a); + } + + return outputNode; + } + + setDefaultValues(material) { + // This approach is to reuse the native refreshUniforms* + // and turn available the use of features like transmission and environment in core + + for (const property in material) { + const value = material[property]; + + if (this[property] === undefined) { + this[property] = value; + + if (value && value.clone) this[property] = value.clone(); + } + } + + const descriptors = Object.getOwnPropertyDescriptors(material.constructor.prototype); + + for (const key in descriptors) { + if ( + Object.getOwnPropertyDescriptor(this.constructor.prototype, key) === undefined && + descriptors[key].get !== undefined + ) { + Object.defineProperty(this.constructor.prototype, key, descriptors[key]); + } + } + } + + toJSON(meta) { + const isRoot = meta === undefined || typeof meta === 'string'; + + if (isRoot) { + meta = { + textures: {}, + images: {}, + nodes: {}, + }; + } + + const data = Material.prototype.toJSON.call(this, meta); + const nodeChildren = getNodeChildren(this); + + data.inputNodes = {}; + + for (const { property, childNode } of nodeChildren) { + data.inputNodes[property] = childNode.toJSON(meta).uuid; + } + + // TODO: Copied from Object3D.toJSON + + function extractFromCache(cache) { + const values = []; + + for (const key in cache) { + const data = cache[key]; + delete data.metadata; + values.push(data); + } + + return values; + } + + if (isRoot) { + const textures = extractFromCache(meta.textures); + const images = extractFromCache(meta.images); + const nodes = extractFromCache(meta.nodes); + + if (textures.length > 0) data.textures = textures; + if (images.length > 0) data.images = images; + if (nodes.length > 0) data.nodes = nodes; + } + + return data; + } + + copy(source) { + this.lightsNode = source.lightsNode; + this.envNode = source.envNode; + + this.colorNode = source.colorNode; + this.normalNode = source.normalNode; + this.opacityNode = source.opacityNode; + this.backdropNode = source.backdropNode; + this.backdropAlphaNode = source.backdropAlphaNode; + this.alphaTestNode = source.alphaTestNode; + + this.positionNode = source.positionNode; + + this.depthNode = source.depthNode; + this.shadowNode = source.shadowNode; + this.shadowPositionNode = source.shadowPositionNode; + + this.outputNode = source.outputNode; + + this.fragmentNode = source.fragmentNode; + this.vertexNode = source.vertexNode; + + return super.copy(source); + } + + static fromMaterial(material) { + if (material.isNodeMaterial === true) { + // is already a node material + + return material; + } + + const type = material.type.replace('Material', 'NodeMaterial'); + + const nodeMaterial = createNodeMaterialFromType(type); + + if (nodeMaterial === undefined) { + throw new Error(`NodeMaterial: Material "${material.type}" is not compatible.`); + } + + for (const key in material) { + nodeMaterial[key] = material[key]; + } + + return nodeMaterial; + } +} + +export default NodeMaterial; + +export function addNodeMaterial(type, nodeMaterial) { + if (typeof nodeMaterial !== 'function' || !type) throw new Error(`Node material ${type} is not a class`); + if (NodeMaterials.has(type)) { + console.warn(`Redefinition of node material ${type}`); + return; + } + + NodeMaterials.set(type, nodeMaterial); + nodeMaterial.type = type; +} + +export function createNodeMaterialFromType(type) { + const Material = NodeMaterials.get(type); + + if (Material !== undefined) { + return new Material(); + } +} + +addNodeMaterial('NodeMaterial', NodeMaterial); diff --git a/examples-jsm/examples/nodes/shadernode/ShaderNode.ts b/examples-jsm/examples/nodes/shadernode/ShaderNode.ts new file mode 100644 index 00000000..288ffa3d --- /dev/null +++ b/examples-jsm/examples/nodes/shadernode/ShaderNode.ts @@ -0,0 +1,532 @@ +import Node, { addNodeClass } from '../core/Node.js'; +import ArrayElementNode from '../utils/ArrayElementNode.js'; +import ConvertNode from '../utils/ConvertNode.js'; +import JoinNode from '../utils/JoinNode.js'; +import SplitNode from '../utils/SplitNode.js'; +import SetNode from '../utils/SetNode.js'; +import ConstNode from '../core/ConstNode.js'; +import { getValueFromType, getValueType } from '../core/NodeUtils.js'; + +// + +let currentStack = null; + +const NodeElements = new Map(); // @TODO: Currently only a few nodes are added, probably also add others + +export function addNodeElement(name, nodeElement) { + if (NodeElements.has(name)) { + console.warn(`Redefinition of node element ${name}`); + return; + } + + if (typeof nodeElement !== 'function') throw new Error(`Node element ${name} is not a function`); + + NodeElements.set(name, nodeElement); +} + +const parseSwizzle = props => props.replace(/r|s/g, 'x').replace(/g|t/g, 'y').replace(/b|p/g, 'z').replace(/a|q/g, 'w'); + +const shaderNodeHandler = { + setup(NodeClosure, params) { + const inputs = params.shift(); + + return NodeClosure(nodeObjects(inputs), ...params); + }, + + get(node, prop, nodeObj) { + if (typeof prop === 'string' && node[prop] === undefined) { + if (node.isStackNode !== true && prop === 'assign') { + return (...params) => { + currentStack.assign(nodeObj, ...params); + + return nodeObj; + }; + } else if (NodeElements.has(prop)) { + const nodeElement = NodeElements.get(prop); + + return node.isStackNode + ? (...params) => nodeObj.add(nodeElement(...params)) + : (...params) => nodeElement(nodeObj, ...params); + } else if (prop === 'self') { + return node; + } else if (prop.endsWith('Assign') && NodeElements.has(prop.slice(0, prop.length - 'Assign'.length))) { + const nodeElement = NodeElements.get(prop.slice(0, prop.length - 'Assign'.length)); + + return node.isStackNode + ? (...params) => nodeObj.assign(params[0], nodeElement(...params)) + : (...params) => nodeObj.assign(nodeElement(nodeObj, ...params)); + } else if (/^[xyzwrgbastpq]{1,4}$/.test(prop) === true) { + // accessing properties ( swizzle ) + + prop = parseSwizzle(prop); + + return nodeObject(new SplitNode(nodeObj, prop)); + } else if (/^set[XYZWRGBASTPQ]{1,4}$/.test(prop) === true) { + // set properties ( swizzle ) + + prop = parseSwizzle(prop.slice(3).toLowerCase()); + + // sort to xyzw sequence + + prop = prop.split('').sort().join(''); + + return value => nodeObject(new SetNode(node, prop, value)); + } else if (prop === 'width' || prop === 'height' || prop === 'depth') { + // accessing property + + if (prop === 'width') prop = 'x'; + else if (prop === 'height') prop = 'y'; + else if (prop === 'depth') prop = 'z'; + + return nodeObject(new SplitNode(node, prop)); + } else if (/^\d+$/.test(prop) === true) { + // accessing array + + return nodeObject(new ArrayElementNode(nodeObj, new ConstNode(Number(prop), 'uint'))); + } + } + + return Reflect.get(node, prop, nodeObj); + }, + + set(node, prop, value, nodeObj) { + if (typeof prop === 'string' && node[prop] === undefined) { + // setting properties + + if ( + /^[xyzwrgbastpq]{1,4}$/.test(prop) === true || + prop === 'width' || + prop === 'height' || + prop === 'depth' || + /^\d+$/.test(prop) === true + ) { + nodeObj[prop].assign(value); + + return true; + } + } + + return Reflect.set(node, prop, value, nodeObj); + }, +}; + +const nodeObjectsCacheMap = new WeakMap(); +const nodeBuilderFunctionsCacheMap = new WeakMap(); + +const ShaderNodeObject = function (obj, altType = null) { + const type = getValueType(obj); + + if (type === 'node') { + let nodeObject = nodeObjectsCacheMap.get(obj); + + if (nodeObject === undefined) { + nodeObject = new Proxy(obj, shaderNodeHandler); + + nodeObjectsCacheMap.set(obj, nodeObject); + nodeObjectsCacheMap.set(nodeObject, nodeObject); + } + + return nodeObject; + } else if ( + (altType === null && (type === 'float' || type === 'boolean')) || + (type && type !== 'shader' && type !== 'string') + ) { + return nodeObject(getConstNode(obj, altType)); + } else if (type === 'shader') { + return tslFn(obj); + } + + return obj; +}; + +const ShaderNodeObjects = function (objects, altType = null) { + for (const name in objects) { + objects[name] = nodeObject(objects[name], altType); + } + + return objects; +}; + +const ShaderNodeArray = function (array, altType = null) { + const len = array.length; + + for (let i = 0; i < len; i++) { + array[i] = nodeObject(array[i], altType); + } + + return array; +}; + +const ShaderNodeProxy = function (NodeClass, scope = null, factor = null, settings = null) { + const assignNode = node => nodeObject(settings !== null ? Object.assign(node, settings) : node); + + if (scope === null) { + return (...params) => { + return assignNode(new NodeClass(...nodeArray(params))); + }; + } else if (factor !== null) { + factor = nodeObject(factor); + + return (...params) => { + return assignNode(new NodeClass(scope, ...nodeArray(params), factor)); + }; + } else { + return (...params) => { + return assignNode(new NodeClass(scope, ...nodeArray(params))); + }; + } +}; + +const ShaderNodeImmutable = function (NodeClass, ...params) { + return nodeObject(new NodeClass(...nodeArray(params))); +}; + +class ShaderCallNodeInternal extends Node { + constructor(shaderNode, inputNodes) { + super(); + + this.shaderNode = shaderNode; + this.inputNodes = inputNodes; + } + + getNodeType(builder) { + const properties = builder.getNodeProperties(this); + + if (properties.outputNode === null) { + properties.outputNode = this.setupOutput(builder); + } + + return properties.outputNode.getNodeType(builder); + } + + call(builder) { + const { shaderNode, inputNodes } = this; + + if (shaderNode.layout) { + let functionNodesCacheMap = nodeBuilderFunctionsCacheMap.get(builder.constructor); + + if (functionNodesCacheMap === undefined) { + functionNodesCacheMap = new WeakMap(); + + nodeBuilderFunctionsCacheMap.set(builder.constructor, functionNodesCacheMap); + } + + let functionNode = functionNodesCacheMap.get(shaderNode); + + if (functionNode === undefined) { + functionNode = nodeObject(builder.buildFunctionNode(shaderNode)); + + functionNodesCacheMap.set(shaderNode, functionNode); + } + + if (builder.currentFunctionNode !== null) { + builder.currentFunctionNode.includes.push(functionNode); + } + + return nodeObject(functionNode.call(inputNodes)); + } + + const jsFunc = shaderNode.jsFunc; + const outputNode = + inputNodes !== null ? jsFunc(inputNodes, builder.stack, builder) : jsFunc(builder.stack, builder); + + return nodeObject(outputNode); + } + + setup(builder) { + const { outputNode } = builder.getNodeProperties(this); + + return outputNode || this.setupOutput(builder); + } + + setupOutput(builder) { + builder.addStack(); + + builder.stack.outputNode = this.call(builder); + + return builder.removeStack(); + } + + generate(builder, output) { + const { outputNode } = builder.getNodeProperties(this); + + if (outputNode === null) { + // TSL: It's recommended to use `tslFn` in setup() pass. + + return this.call(builder).build(builder, output); + } + + return super.generate(builder, output); + } +} + +class ShaderNodeInternal extends Node { + constructor(jsFunc) { + super(); + + this.jsFunc = jsFunc; + this.layout = null; + + this.global = true; + } + + get isArrayInput() { + return /^\((\s+)?\[/.test(this.jsFunc.toString()); + } + + setLayout(layout) { + this.layout = layout; + + return this; + } + + call(inputs = null) { + nodeObjects(inputs); + + return nodeObject(new ShaderCallNodeInternal(this, inputs)); + } + + setup() { + return this.call(); + } +} + +const bools = [false, true]; +const uints = [0, 1, 2, 3]; +const ints = [-1, -2]; +const floats = [ + 0.5, + 1.5, + 1 / 3, + 1e-6, + 1e6, + Math.PI, + Math.PI * 2, + 1 / Math.PI, + 2 / Math.PI, + 1 / (Math.PI * 2), + Math.PI / 2, +]; + +const boolsCacheMap = new Map(); +for (const bool of bools) boolsCacheMap.set(bool, new ConstNode(bool)); + +const uintsCacheMap = new Map(); +for (const uint of uints) uintsCacheMap.set(uint, new ConstNode(uint, 'uint')); + +const intsCacheMap = new Map([...uintsCacheMap].map(el => new ConstNode(el.value, 'int'))); +for (const int of ints) intsCacheMap.set(int, new ConstNode(int, 'int')); + +const floatsCacheMap = new Map([...intsCacheMap].map(el => new ConstNode(el.value))); +for (const float of floats) floatsCacheMap.set(float, new ConstNode(float)); +for (const float of floats) floatsCacheMap.set(-float, new ConstNode(-float)); + +const cacheMaps = { bool: boolsCacheMap, uint: uintsCacheMap, ints: intsCacheMap, float: floatsCacheMap }; + +const constNodesCacheMap = new Map([...boolsCacheMap, ...floatsCacheMap]); + +const getConstNode = (value, type) => { + if (constNodesCacheMap.has(value)) { + return constNodesCacheMap.get(value); + } else if (value.isNode === true) { + return value; + } else { + return new ConstNode(value, type); + } +}; + +const safeGetNodeType = node => { + try { + return node.getNodeType(); + } catch (_) { + return undefined; + } +}; + +const ConvertType = function (type, cacheMap = null) { + return (...params) => { + if ( + params.length === 0 || + (!['bool', 'float', 'int', 'uint'].includes(type) && params.every(param => typeof param !== 'object')) + ) { + params = [getValueFromType(type, ...params)]; + } + + if (params.length === 1 && cacheMap !== null && cacheMap.has(params[0])) { + return nodeObject(cacheMap.get(params[0])); + } + + if (params.length === 1) { + const node = getConstNode(params[0], type); + if (safeGetNodeType(node) === type) return nodeObject(node); + return nodeObject(new ConvertNode(node, type)); + } + + const nodes = params.map(param => getConstNode(param)); + return nodeObject(new JoinNode(nodes, type)); + }; +}; + +// exports + +export const defined = value => value && value.value; + +// utils + +export const getConstNodeType = value => + value !== undefined && value !== null + ? value.nodeType || value.convertTo || (typeof value === 'string' ? value : null) + : null; + +// shader node base + +export function ShaderNode(jsFunc) { + return new Proxy(new ShaderNodeInternal(jsFunc), shaderNodeHandler); +} + +export const nodeObject = (val, altType = null) => /* new */ ShaderNodeObject(val, altType); +export const nodeObjects = (val, altType = null) => new ShaderNodeObjects(val, altType); +export const nodeArray = (val, altType = null) => new ShaderNodeArray(val, altType); +export const nodeProxy = (...params) => new ShaderNodeProxy(...params); +export const nodeImmutable = (...params) => new ShaderNodeImmutable(...params); + +export const tslFn = jsFunc => { + const shaderNode = new ShaderNode(jsFunc); + + const fn = (...params) => { + let inputs; + + nodeObjects(params); + + if (params[0] && params[0].isNode) { + inputs = [...params]; + } else { + inputs = params[0]; + } + + return shaderNode.call(inputs); + }; + + fn.shaderNode = shaderNode; + fn.setLayout = layout => { + shaderNode.setLayout(layout); + + return fn; + }; + + return fn; +}; + +addNodeClass('ShaderNode', ShaderNode); + +// + +addNodeElement('toGlobal', node => { + node.global = true; + + return node; +}); + +// + +export const setCurrentStack = stack => { + if (currentStack === stack) { + //throw new Error( 'Stack already defined.' ); + } + + currentStack = stack; +}; + +export const getCurrentStack = () => currentStack; + +export const If = (...params) => currentStack.if(...params); + +export function append(node) { + if (currentStack) currentStack.add(node); + + return node; +} + +addNodeElement('append', append); + +// types +// @TODO: Maybe export from ConstNode.js? + +export const color = new ConvertType('color'); + +export const float = new ConvertType('float', cacheMaps.float); +export const int = new ConvertType('int', cacheMaps.ints); +export const uint = new ConvertType('uint', cacheMaps.uint); +export const bool = new ConvertType('bool', cacheMaps.bool); + +export const vec2 = new ConvertType('vec2'); +export const ivec2 = new ConvertType('ivec2'); +export const uvec2 = new ConvertType('uvec2'); +export const bvec2 = new ConvertType('bvec2'); + +export const vec3 = new ConvertType('vec3'); +export const ivec3 = new ConvertType('ivec3'); +export const uvec3 = new ConvertType('uvec3'); +export const bvec3 = new ConvertType('bvec3'); + +export const vec4 = new ConvertType('vec4'); +export const ivec4 = new ConvertType('ivec4'); +export const uvec4 = new ConvertType('uvec4'); +export const bvec4 = new ConvertType('bvec4'); + +export const mat2 = new ConvertType('mat2'); +export const imat2 = new ConvertType('imat2'); +export const umat2 = new ConvertType('umat2'); +export const bmat2 = new ConvertType('bmat2'); + +export const mat3 = new ConvertType('mat3'); +export const imat3 = new ConvertType('imat3'); +export const umat3 = new ConvertType('umat3'); +export const bmat3 = new ConvertType('bmat3'); + +export const mat4 = new ConvertType('mat4'); +export const imat4 = new ConvertType('imat4'); +export const umat4 = new ConvertType('umat4'); +export const bmat4 = new ConvertType('bmat4'); + +export const string = (value = '') => nodeObject(new ConstNode(value, 'string')); +export const arrayBuffer = value => nodeObject(new ConstNode(value, 'ArrayBuffer')); + +addNodeElement('toColor', color); +addNodeElement('toFloat', float); +addNodeElement('toInt', int); +addNodeElement('toUint', uint); +addNodeElement('toBool', bool); +addNodeElement('toVec2', vec2); +addNodeElement('toIvec2', ivec2); +addNodeElement('toUvec2', uvec2); +addNodeElement('toBvec2', bvec2); +addNodeElement('toVec3', vec3); +addNodeElement('toIvec3', ivec3); +addNodeElement('toUvec3', uvec3); +addNodeElement('toBvec3', bvec3); +addNodeElement('toVec4', vec4); +addNodeElement('toIvec4', ivec4); +addNodeElement('toUvec4', uvec4); +addNodeElement('toBvec4', bvec4); +addNodeElement('toMat2', mat2); +addNodeElement('toImat2', imat2); +addNodeElement('toUmat2', umat2); +addNodeElement('toBmat2', bmat2); +addNodeElement('toMat3', mat3); +addNodeElement('toImat3', imat3); +addNodeElement('toUmat3', umat3); +addNodeElement('toBmat3', bmat3); +addNodeElement('toMat4', mat4); +addNodeElement('toImat4', imat4); +addNodeElement('toUmat4', umat4); +addNodeElement('toBmat4', bmat4); + +// basic nodes +// HACK - we cannot export them from the corresponding files because of the cyclic dependency +export const element = nodeProxy(ArrayElementNode); +export const convert = (node, types) => nodeObject(new ConvertNode(nodeObject(node), types)); +export const split = (node, channels) => nodeObject(new SplitNode(nodeObject(node), channels)); + +addNodeElement('element', element); +addNodeElement('convert', convert); diff --git a/examples-jsm/examples/renderers/common/Animation.ts b/examples-jsm/examples/renderers/common/Animation.ts new file mode 100644 index 00000000..0b00319a --- /dev/null +++ b/examples-jsm/examples/renderers/common/Animation.ts @@ -0,0 +1,38 @@ +class Animation { + constructor(nodes, info) { + this.nodes = nodes; + this.info = info; + + this.animationLoop = null; + this.requestId = null; + + this._init(); + } + + _init() { + const update = (time, frame) => { + this.requestId = self.requestAnimationFrame(update); + + if (this.info.autoReset === true) this.info.reset(); + + this.nodes.nodeFrame.update(); + + this.info.frame = this.nodes.nodeFrame.frameId; + + if (this.animationLoop !== null) this.animationLoop(time, frame); + }; + + update(); + } + + dispose() { + self.cancelAnimationFrame(this.requestId); + this.requestId = null; + } + + setAnimationLoop(callback) { + this.animationLoop = callback; + } +} + +export default Animation; diff --git a/examples-jsm/examples/renderers/common/Attributes.ts b/examples-jsm/examples/renderers/common/Attributes.ts new file mode 100644 index 00000000..29553540 --- /dev/null +++ b/examples-jsm/examples/renderers/common/Attributes.ts @@ -0,0 +1,53 @@ +import DataMap from './DataMap.js'; +import { AttributeType } from './Constants.js'; +import { DynamicDrawUsage } from 'three'; + +class Attributes extends DataMap { + constructor(backend) { + super(); + + this.backend = backend; + } + + delete(attribute) { + const attributeData = super.delete(attribute); + + if (attributeData !== undefined) { + this.backend.destroyAttribute(attribute); + } + + return attributeData; + } + + update(attribute, type) { + const data = this.get(attribute); + + if (data.version === undefined) { + if (type === AttributeType.VERTEX) { + this.backend.createAttribute(attribute); + } else if (type === AttributeType.INDEX) { + this.backend.createIndexAttribute(attribute); + } else if (type === AttributeType.STORAGE) { + this.backend.createStorageAttribute(attribute); + } + + data.version = this._getBufferAttribute(attribute).version; + } else { + const bufferAttribute = this._getBufferAttribute(attribute); + + if (data.version < bufferAttribute.version || bufferAttribute.usage === DynamicDrawUsage) { + this.backend.updateAttribute(attribute); + + data.version = bufferAttribute.version; + } + } + } + + _getBufferAttribute(attribute) { + if (attribute.isInterleavedBufferAttribute) attribute = attribute.data; + + return attribute; + } +} + +export default Attributes; diff --git a/examples-jsm/examples/renderers/common/Backend.ts b/examples-jsm/examples/renderers/common/Backend.ts new file mode 100644 index 00000000..efa5649d --- /dev/null +++ b/examples-jsm/examples/renderers/common/Backend.ts @@ -0,0 +1,165 @@ +let vector2 = null; +let vector4 = null; +let color4 = null; + +import Color4 from './Color4.js'; +import { Vector2, Vector4, REVISION, createCanvasElement } from 'three'; + +class Backend { + constructor(parameters = {}) { + this.parameters = Object.assign({}, parameters); + this.data = new WeakMap(); + this.renderer = null; + this.domElement = null; + } + + async init(renderer) { + this.renderer = renderer; + } + + // render context + + begin(renderContext) {} + + finish(renderContext) {} + + // render object + + draw(renderObject, info) {} + + // program + + createProgram(program) {} + + destroyProgram(program) {} + + // bindings + + createBindings(renderObject) {} + + // pipeline + + createRenderPipeline(renderObject) {} + + createComputePipeline(computeNode, pipeline) {} + + destroyPipeline(pipeline) {} + + // cache key + + needsRenderUpdate(renderObject) {} // return Boolean ( fast test ) + + getRenderCacheKey(renderObject) {} // return String + + // node builder + + createNodeBuilder(renderObject) {} // return NodeBuilder (ADD IT) + + // textures + + createSampler(texture) {} + + createDefaultTexture(texture) {} + + createTexture(texture) {} + + copyTextureToBuffer(texture, x, y, width, height) {} + + // attributes + + createAttribute(attribute) {} + + createIndexAttribute(attribute) {} + + updateAttribute(attribute) {} + + destroyAttribute(attribute) {} + + // canvas + + getContext() {} + + updateSize() {} + + // utils + + resolveTimestampAsync(renderContext, type) {} + + hasFeatureAsync(name) {} // return Boolean + + hasFeature(name) {} // return Boolean + + getInstanceCount(renderObject) { + const { object, geometry } = renderObject; + + return geometry.isInstancedBufferGeometry ? geometry.instanceCount : object.count > 1 ? object.count : 1; + } + + getDrawingBufferSize() { + vector2 = vector2 || new Vector2(); + + return this.renderer.getDrawingBufferSize(vector2); + } + + getScissor() { + vector4 = vector4 || new Vector4(); + + return this.renderer.getScissor(vector4); + } + + setScissorTest(boolean) {} + + getClearColor() { + const renderer = this.renderer; + + color4 = color4 || new Color4(); + + renderer.getClearColor(color4); + + color4.getRGB(color4, this.renderer.currentColorSpace); + + return color4; + } + + getDomElement() { + let domElement = this.domElement; + + if (domElement === null) { + domElement = this.parameters.canvas !== undefined ? this.parameters.canvas : createCanvasElement(); + + // OffscreenCanvas does not have setAttribute, see #22811 + if ('setAttribute' in domElement) domElement.setAttribute('data-engine', `three.js r${REVISION} webgpu`); + + this.domElement = domElement; + } + + return domElement; + } + + // resource properties + + set(object, value) { + this.data.set(object, value); + } + + get(object) { + let map = this.data.get(object); + + if (map === undefined) { + map = {}; + this.data.set(object, map); + } + + return map; + } + + has(object) { + return this.data.has(object); + } + + delete(object) { + this.data.delete(object); + } +} + +export default Backend; diff --git a/examples-jsm/examples/renderers/common/Background.ts b/examples-jsm/examples/renderers/common/Background.ts new file mode 100644 index 00000000..b7902dd4 --- /dev/null +++ b/examples-jsm/examples/renderers/common/Background.ts @@ -0,0 +1,118 @@ +import DataMap from './DataMap.js'; +import Color4 from './Color4.js'; +import { Mesh, SphereGeometry, BackSide, LinearSRGBColorSpace } from 'three'; +import { + vec4, + context, + normalWorld, + backgroundBlurriness, + backgroundIntensity, + NodeMaterial, + modelViewProjection, +} from '../../nodes/Nodes.js'; + +const _clearColor = new Color4(); + +class Background extends DataMap { + constructor(renderer, nodes) { + super(); + + this.renderer = renderer; + this.nodes = nodes; + } + + update(scene, renderList, renderContext) { + const renderer = this.renderer; + const background = this.nodes.getBackgroundNode(scene) || scene.background; + + let forceClear = false; + + if (background === null) { + // no background settings, use clear color configuration from the renderer + + renderer._clearColor.getRGB(_clearColor, LinearSRGBColorSpace); + _clearColor.a = renderer._clearColor.a; + } else if (background.isColor === true) { + // background is an opaque color + + background.getRGB(_clearColor, LinearSRGBColorSpace); + _clearColor.a = 1; + + forceClear = true; + } else if (background.isNode === true) { + const sceneData = this.get(scene); + const backgroundNode = background; + + _clearColor.copy(renderer._clearColor); + + let backgroundMesh = sceneData.backgroundMesh; + + if (backgroundMesh === undefined) { + const backgroundMeshNode = context(vec4(backgroundNode).mul(backgroundIntensity), { + // @TODO: Add Texture2D support using node context + getUV: () => normalWorld, + getTextureLevel: () => backgroundBlurriness, + }); + + let viewProj = modelViewProjection(); + viewProj = viewProj.setZ(viewProj.w); + + const nodeMaterial = new NodeMaterial(); + nodeMaterial.side = BackSide; + nodeMaterial.depthTest = false; + nodeMaterial.depthWrite = false; + nodeMaterial.fog = false; + nodeMaterial.vertexNode = viewProj; + nodeMaterial.fragmentNode = backgroundMeshNode; + + sceneData.backgroundMeshNode = backgroundMeshNode; + sceneData.backgroundMesh = backgroundMesh = new Mesh(new SphereGeometry(1, 32, 32), nodeMaterial); + backgroundMesh.frustumCulled = false; + + backgroundMesh.onBeforeRender = function (renderer, scene, camera) { + this.matrixWorld.copyPosition(camera.matrixWorld); + }; + } + + const backgroundCacheKey = backgroundNode.getCacheKey(); + + if (sceneData.backgroundCacheKey !== backgroundCacheKey) { + sceneData.backgroundMeshNode.node = vec4(backgroundNode).mul(backgroundIntensity); + + backgroundMesh.material.needsUpdate = true; + + sceneData.backgroundCacheKey = backgroundCacheKey; + } + + renderList.unshift(backgroundMesh, backgroundMesh.geometry, backgroundMesh.material, 0, 0, null); + } else { + console.error('THREE.Renderer: Unsupported background configuration.', background); + } + + // + + if (renderer.autoClear === true || forceClear === true) { + _clearColor.multiplyScalar(_clearColor.a); + + const clearColorValue = renderContext.clearColorValue; + + clearColorValue.r = _clearColor.r; + clearColorValue.g = _clearColor.g; + clearColorValue.b = _clearColor.b; + clearColorValue.a = _clearColor.a; + + renderContext.depthClearValue = renderer._clearDepth; + renderContext.stencilClearValue = renderer._clearStencil; + + renderContext.clearColor = renderer.autoClearColor === true; + renderContext.clearDepth = renderer.autoClearDepth === true; + renderContext.clearStencil = renderer.autoClearStencil === true; + } else { + renderContext.clearColor = false; + renderContext.clearDepth = false; + renderContext.clearStencil = false; + } + } +} + +export default Background; diff --git a/examples-jsm/examples/renderers/common/BindGroup.ts b/examples-jsm/examples/renderers/common/BindGroup.ts new file mode 100644 index 00000000..6b7ffa21 --- /dev/null +++ b/examples-jsm/examples/renderers/common/BindGroup.ts @@ -0,0 +1,12 @@ +let _id = 0; + +class BindGroup { + constructor(name = '', bindings = []) { + this.name = name; + this.bindings = bindings; + + this.id = _id++; + } +} + +export default BindGroup; diff --git a/examples-jsm/examples/renderers/common/Binding.ts b/examples-jsm/examples/renderers/common/Binding.ts new file mode 100644 index 00000000..a12f3563 --- /dev/null +++ b/examples-jsm/examples/renderers/common/Binding.ts @@ -0,0 +1,17 @@ +class Binding { + constructor(name = '') { + this.name = name; + + this.visibility = 0; + } + + setVisibility(visibility) { + this.visibility |= visibility; + } + + clone() { + return Object.assign(new this.constructor(), this); + } +} + +export default Binding; diff --git a/examples-jsm/examples/renderers/common/Bindings.ts b/examples-jsm/examples/renderers/common/Bindings.ts new file mode 100644 index 00000000..51aa31f0 --- /dev/null +++ b/examples-jsm/examples/renderers/common/Bindings.ts @@ -0,0 +1,161 @@ +import DataMap from './DataMap.js'; +import { AttributeType } from './Constants.js'; + +class Bindings extends DataMap { + constructor(backend, nodes, textures, attributes, pipelines, info) { + super(); + + this.backend = backend; + this.textures = textures; + this.pipelines = pipelines; + this.attributes = attributes; + this.nodes = nodes; + this.info = info; + + this.pipelines.bindings = this; // assign bindings to pipelines + } + + getForRender(renderObject) { + const bindings = renderObject.getBindings(); + + for (const bindGroup of bindings) { + const groupData = this.get(bindGroup); + + if (groupData.bindGroup === undefined) { + // each object defines an array of bindings (ubos, textures, samplers etc.) + + this._init(bindGroup); + + this.backend.createBindings(bindGroup, bindings); + + groupData.bindGroup = bindGroup; + } + } + + return bindings; + } + + getForCompute(computeNode) { + const bindings = this.nodes.getForCompute(computeNode).bindings; + + for (const bindGroup of bindings) { + const groupData = this.get(bindGroup); + + if (groupData.bindGroup === undefined) { + this._init(bindGroup); + + this.backend.createBindings(bindGroup, bindings); + + groupData.bindGroup = bindGroup; + } + } + + return bindings; + } + + updateForCompute(computeNode) { + this._updateBindings(computeNode, this.getForCompute(computeNode)); + } + + updateForRender(renderObject) { + this._updateBindings(renderObject, this.getForRender(renderObject)); + } + + _updateBindings(object, bindings) { + for (const bindGroup of bindings) { + this._update(object, bindGroup, bindings); + } + } + + _init(bindGroup) { + for (const binding of bindGroup.bindings) { + if (binding.isSampledTexture) { + this.textures.updateTexture(binding.texture); + } else if (binding.isStorageBuffer) { + const attribute = binding.attribute; + + this.attributes.update(attribute, AttributeType.STORAGE); + } + } + } + + _update(object, bindGroup, bindings) { + const { backend } = this; + + let needsBindingsUpdate = false; + + // iterate over all bindings and check if buffer updates or a new binding group is required + + for (const binding of bindGroup.bindings) { + if (binding.isNodeUniformsGroup) { + const updated = this.nodes.updateGroup(binding); + + if (!updated) continue; + } + + if (binding.isUniformBuffer) { + const updated = binding.update(); + + if (updated) { + backend.updateBinding(binding); + } + } else if (binding.isSampler) { + binding.update(); + } else if (binding.isSampledTexture) { + const texture = binding.texture; + + if (binding.needsBindingsUpdate) needsBindingsUpdate = true; + + const updated = binding.update(); + + if (updated) { + this.textures.updateTexture(binding.texture); + } + + const textureData = backend.get(binding.texture); + + if ( + backend.isWebGPUBackend === true && + textureData.texture === undefined && + textureData.externalTexture === undefined + ) { + // TODO: Remove this once we found why updated === false isn't bound to a texture in the WebGPU backend + console.error( + 'Bindings._update: binding should be available:', + binding, + updated, + binding.texture, + binding.textureNode.value, + ); + + this.textures.updateTexture(binding.texture); + needsBindingsUpdate = true; + } + + if (texture.isStorageTexture === true) { + const textureData = this.get(texture); + + if (binding.store === true) { + textureData.needsMipmap = true; + } else if ( + texture.generateMipmaps === true && + this.textures.needsMipmaps(texture) && + textureData.needsMipmap === true + ) { + this.backend.generateMipmaps(texture); + + textureData.needsMipmap = false; + } + } + } + } + + if (needsBindingsUpdate === true) { + const pipeline = this.pipelines.getForRender(object); + + this.backend.updateBindings(bindGroup, bindings, pipeline); + } + } +} + +export default Bindings; diff --git a/examples-jsm/examples/renderers/common/Buffer.ts b/examples-jsm/examples/renderers/common/Buffer.ts new file mode 100644 index 00000000..17013c6d --- /dev/null +++ b/examples-jsm/examples/renderers/common/Buffer.ts @@ -0,0 +1,28 @@ +import Binding from './Binding.js'; +import { getFloatLength } from './BufferUtils.js'; + +class Buffer extends Binding { + constructor(name, buffer = null) { + super(name); + + this.isBuffer = true; + + this.bytesPerElement = Float32Array.BYTES_PER_ELEMENT; + + this._buffer = buffer; + } + + get byteLength() { + return getFloatLength(this._buffer.byteLength); + } + + get buffer() { + return this._buffer; + } + + update() { + return true; + } +} + +export default Buffer; diff --git a/examples-jsm/examples/renderers/common/BufferUtils.ts b/examples-jsm/examples/renderers/common/BufferUtils.ts new file mode 100644 index 00000000..99ddcb48 --- /dev/null +++ b/examples-jsm/examples/renderers/common/BufferUtils.ts @@ -0,0 +1,23 @@ +import { GPU_CHUNK_BYTES } from './Constants.js'; + +function getFloatLength(floatLength) { + // ensure chunk size alignment (STD140 layout) + + return floatLength + ((GPU_CHUNK_BYTES - (floatLength % GPU_CHUNK_BYTES)) % GPU_CHUNK_BYTES); +} + +function getVectorLength(count, vectorLength = 4) { + const strideLength = getStrideLength(vectorLength); + + const floatLength = strideLength * count; + + return getFloatLength(floatLength); +} + +function getStrideLength(vectorLength) { + const strideLength = 4; + + return vectorLength + ((strideLength - (vectorLength % strideLength)) % strideLength); +} + +export { getFloatLength, getVectorLength, getStrideLength }; diff --git a/examples-jsm/examples/renderers/common/ChainMap.ts b/examples-jsm/examples/renderers/common/ChainMap.ts new file mode 100644 index 00000000..b17e7080 --- /dev/null +++ b/examples-jsm/examples/renderers/common/ChainMap.ts @@ -0,0 +1,43 @@ +export default class ChainMap { + constructor() { + this.weakMap = new WeakMap(); + } + + get(keys) { + let map = this.weakMap; + + for (let i = 0; i < keys.length; i++) { + map = map.get(keys[i]); + + if (map === undefined) return undefined; + } + + return map.get(keys[keys.length - 1]); + } + + set(keys, value) { + let map = this.weakMap; + + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + + if (map.has(key) === false) map.set(key, new WeakMap()); + + map = map.get(key); + } + + return map.set(keys[keys.length - 1], value); + } + + delete(keys) { + let map = this.weakMap; + + for (let i = 0; i < keys.length; i++) { + map = map.get(keys[i]); + + if (map === undefined) return false; + } + + return map.delete(keys[keys.length - 1]); + } +} diff --git a/examples-jsm/examples/renderers/common/ClippingContext.ts b/examples-jsm/examples/renderers/common/ClippingContext.ts new file mode 100644 index 00000000..312e0b77 --- /dev/null +++ b/examples-jsm/examples/renderers/common/ClippingContext.ts @@ -0,0 +1,128 @@ +import { Matrix3, Plane, Vector4 } from 'three'; + +const _plane = new Plane(); + +let _clippingContextVersion = 0; + +class ClippingContext { + constructor() { + this.version = ++_clippingContextVersion; + + this.globalClippingCount = 0; + + this.localClippingCount = 0; + this.localClippingEnabled = false; + this.localClipIntersection = false; + + this.planes = []; + + this.parentVersion = 0; + this.viewNormalMatrix = new Matrix3(); + } + + projectPlanes(source, offset) { + const l = source.length; + const planes = this.planes; + + for (let i = 0; i < l; i++) { + _plane.copy(source[i]).applyMatrix4(this.viewMatrix, this.viewNormalMatrix); + + const v = planes[offset + i]; + const normal = _plane.normal; + + v.x = -normal.x; + v.y = -normal.y; + v.z = -normal.z; + v.w = _plane.constant; + } + } + + updateGlobal(renderer, camera) { + const rendererClippingPlanes = renderer.clippingPlanes; + this.viewMatrix = camera.matrixWorldInverse; + + this.viewNormalMatrix.getNormalMatrix(this.viewMatrix); + + let update = false; + + if (Array.isArray(rendererClippingPlanes) && rendererClippingPlanes.length !== 0) { + const l = rendererClippingPlanes.length; + + if (l !== this.globalClippingCount) { + const planes = []; + + for (let i = 0; i < l; i++) { + planes.push(new Vector4()); + } + + this.globalClippingCount = l; + this.planes = planes; + + update = true; + } + + this.projectPlanes(rendererClippingPlanes, 0); + } else if (this.globalClippingCount !== 0) { + this.globalClippingCount = 0; + this.planes = []; + update = true; + } + + if (renderer.localClippingEnabled !== this.localClippingEnabled) { + this.localClippingEnabled = renderer.localClippingEnabled; + update = true; + } + + if (update) this.version = _clippingContextVersion++; + } + + update(parent, material) { + let update = false; + + if (this !== parent && parent.version !== this.parentVersion) { + this.globalClippingCount = material.isShadowNodeMaterial ? 0 : parent.globalClippingCount; + this.localClippingEnabled = parent.localClippingEnabled; + this.planes = Array.from(parent.planes); + this.parentVersion = parent.version; + this.viewMatrix = parent.viewMatrix; + this.viewNormalMatrix = parent.viewNormalMatrix; + + update = true; + } + + if (this.localClippingEnabled) { + const localClippingPlanes = material.clippingPlanes; + + if (Array.isArray(localClippingPlanes) && localClippingPlanes.length !== 0) { + const l = localClippingPlanes.length; + const planes = this.planes; + const offset = this.globalClippingCount; + + if (update || l !== this.localClippingCount) { + planes.length = offset + l; + + for (let i = 0; i < l; i++) { + planes[offset + i] = new Vector4(); + } + + this.localClippingCount = l; + update = true; + } + + this.projectPlanes(localClippingPlanes, offset); + } else if (this.localClippingCount !== 0) { + this.localClippingCount = 0; + update = true; + } + + if (this.localClipIntersection !== material.clipIntersection) { + this.localClipIntersection = material.clipIntersection; + update = true; + } + } + + if (update) this.version = _clippingContextVersion++; + } +} + +export default ClippingContext; diff --git a/examples-jsm/examples/renderers/common/Color4.ts b/examples-jsm/examples/renderers/common/Color4.ts new file mode 100644 index 00000000..c681cc90 --- /dev/null +++ b/examples-jsm/examples/renderers/common/Color4.ts @@ -0,0 +1,27 @@ +import { Color } from 'three'; + +class Color4 extends Color { + constructor(r, g, b, a = 1) { + super(r, g, b); + + this.a = a; + } + + set(r, g, b, a = 1) { + this.a = a; + + return super.set(r, g, b); + } + + copy(color) { + if (color.a !== undefined) this.a = color.a; + + return super.copy(color); + } + + clone() { + return new this.constructor(this.r, this.g, this.b, this.a); + } +} + +export default Color4; diff --git a/examples-jsm/examples/renderers/common/ComputePipeline.ts b/examples-jsm/examples/renderers/common/ComputePipeline.ts new file mode 100644 index 00000000..0fd3ca53 --- /dev/null +++ b/examples-jsm/examples/renderers/common/ComputePipeline.ts @@ -0,0 +1,13 @@ +import Pipeline from './Pipeline.js'; + +class ComputePipeline extends Pipeline { + constructor(cacheKey, computeProgram) { + super(cacheKey); + + this.computeProgram = computeProgram; + + this.isComputePipeline = true; + } +} + +export default ComputePipeline; diff --git a/examples-jsm/examples/renderers/common/Constants.ts b/examples-jsm/examples/renderers/common/Constants.ts new file mode 100644 index 00000000..0d0c35a2 --- /dev/null +++ b/examples-jsm/examples/renderers/common/Constants.ts @@ -0,0 +1,14 @@ +export const AttributeType = { + VERTEX: 1, + INDEX: 2, + STORAGE: 4, +}; + +// size of a chunk in bytes (STD140 layout) + +export const GPU_CHUNK_BYTES = 16; + +// @TODO: Move to src/constants.js + +export const BlendColorFactor = 211; +export const OneMinusBlendColorFactor = 212; diff --git a/examples-jsm/examples/renderers/common/CubeRenderTarget.ts b/examples-jsm/examples/renderers/common/CubeRenderTarget.ts new file mode 100644 index 00000000..74d04912 --- /dev/null +++ b/examples-jsm/examples/renderers/common/CubeRenderTarget.ts @@ -0,0 +1,69 @@ +import { + WebGLCubeRenderTarget, + Scene, + CubeCamera, + BoxGeometry, + Mesh, + BackSide, + NoBlending, + LinearFilter, + LinearMipmapLinearFilter, +} from 'three'; +import { equirectUV } from '../../nodes/utils/EquirectUVNode.js'; +import { texture as TSL_Texture } from '../../nodes/accessors/TextureNode.js'; +import { positionWorldDirection } from '../../nodes/accessors/PositionNode.js'; +import { createNodeMaterialFromType } from '../../nodes/materials/NodeMaterial.js'; + +// @TODO: Consider rename WebGLCubeRenderTarget to just CubeRenderTarget + +class CubeRenderTarget extends WebGLCubeRenderTarget { + constructor(size = 1, options = {}) { + super(size, options); + + this.isCubeRenderTarget = true; + } + + fromEquirectangularTexture(renderer, texture) { + const currentMinFilter = texture.minFilter; + const currentGenerateMipmaps = texture.generateMipmaps; + + texture.generateMipmaps = true; + + this.texture.type = texture.type; + this.texture.colorSpace = texture.colorSpace; + + this.texture.generateMipmaps = texture.generateMipmaps; + this.texture.minFilter = texture.minFilter; + this.texture.magFilter = texture.magFilter; + + const geometry = new BoxGeometry(5, 5, 5); + + const uvNode = equirectUV(positionWorldDirection); + + const material = createNodeMaterialFromType('MeshBasicNodeMaterial'); + material.colorNode = TSL_Texture(texture, uvNode, 0); + material.side = BackSide; + material.blending = NoBlending; + + const mesh = new Mesh(geometry, material); + + const scene = new Scene(); + scene.add(mesh); + + // Avoid blurred poles + if (texture.minFilter === LinearMipmapLinearFilter) texture.minFilter = LinearFilter; + + const camera = new CubeCamera(1, 10, this); + camera.update(renderer, scene); + + texture.minFilter = currentMinFilter; + texture.currentGenerateMipmaps = currentGenerateMipmaps; + + mesh.geometry.dispose(); + mesh.material.dispose(); + + return this; + } +} + +export default CubeRenderTarget; diff --git a/examples-jsm/examples/renderers/common/DataMap.ts b/examples-jsm/examples/renderers/common/DataMap.ts new file mode 100644 index 00000000..006bc295 --- /dev/null +++ b/examples-jsm/examples/renderers/common/DataMap.ts @@ -0,0 +1,38 @@ +class DataMap { + constructor() { + this.data = new WeakMap(); + } + + get(object) { + let map = this.data.get(object); + + if (map === undefined) { + map = {}; + this.data.set(object, map); + } + + return map; + } + + delete(object) { + let map; + + if (this.data.has(object)) { + map = this.data.get(object); + + this.data.delete(object); + } + + return map; + } + + has(object) { + return this.data.has(object); + } + + dispose() { + this.data = new WeakMap(); + } +} + +export default DataMap; diff --git a/examples-jsm/examples/renderers/common/Geometries.ts b/examples-jsm/examples/renderers/common/Geometries.ts new file mode 100644 index 00000000..ca0cd225 --- /dev/null +++ b/examples-jsm/examples/renderers/common/Geometries.ts @@ -0,0 +1,182 @@ +import DataMap from './DataMap.js'; +import { AttributeType } from './Constants.js'; +import { Uint32BufferAttribute, Uint16BufferAttribute } from 'three'; + +function arrayNeedsUint32(array) { + // assumes larger values usually on last + + for (let i = array.length - 1; i >= 0; --i) { + if (array[i] >= 65535) return true; // account for PRIMITIVE_RESTART_FIXED_INDEX, #24565 + } + + return false; +} + +function getWireframeVersion(geometry) { + return geometry.index !== null ? geometry.index.version : geometry.attributes.position.version; +} + +function getWireframeIndex(geometry) { + const indices = []; + + const geometryIndex = geometry.index; + const geometryPosition = geometry.attributes.position; + + if (geometryIndex !== null) { + const array = geometryIndex.array; + + for (let i = 0, l = array.length; i < l; i += 3) { + const a = array[i + 0]; + const b = array[i + 1]; + const c = array[i + 2]; + + indices.push(a, b, b, c, c, a); + } + } else { + const array = geometryPosition.array; + + for (let i = 0, l = array.length / 3 - 1; i < l; i += 3) { + const a = i + 0; + const b = i + 1; + const c = i + 2; + + indices.push(a, b, b, c, c, a); + } + } + + const attribute = new (arrayNeedsUint32(indices) ? Uint32BufferAttribute : Uint16BufferAttribute)(indices, 1); + attribute.version = getWireframeVersion(geometry); + + return attribute; +} + +class Geometries extends DataMap { + constructor(attributes, info) { + super(); + + this.attributes = attributes; + this.info = info; + + this.wireframes = new WeakMap(); + + this.attributeCall = new WeakMap(); + } + + has(renderObject) { + const geometry = renderObject.geometry; + + return super.has(geometry) && this.get(geometry).initialized === true; + } + + updateForRender(renderObject) { + if (this.has(renderObject) === false) this.initGeometry(renderObject); + + this.updateAttributes(renderObject); + } + + initGeometry(renderObject) { + const geometry = renderObject.geometry; + const geometryData = this.get(geometry); + + geometryData.initialized = true; + + this.info.memory.geometries++; + + const onDispose = () => { + this.info.memory.geometries--; + + const index = geometry.index; + const geometryAttributes = renderObject.getAttributes(); + + if (index !== null) { + this.attributes.delete(index); + } + + for (const geometryAttribute of geometryAttributes) { + this.attributes.delete(geometryAttribute); + } + + const wireframeAttribute = this.wireframes.get(geometry); + + if (wireframeAttribute !== undefined) { + this.attributes.delete(wireframeAttribute); + } + + geometry.removeEventListener('dispose', onDispose); + }; + + geometry.addEventListener('dispose', onDispose); + } + + updateAttributes(renderObject) { + const attributes = renderObject.getAttributes(); + + for (const attribute of attributes) { + if (attribute.isStorageBufferAttribute || attribute.isStorageInstancedBufferAttribute) { + this.updateAttribute(attribute, AttributeType.STORAGE); + } else { + this.updateAttribute(attribute, AttributeType.VERTEX); + } + } + + const index = this.getIndex(renderObject); + + if (index !== null) { + this.updateAttribute(index, AttributeType.INDEX); + } + } + + updateAttribute(attribute, type) { + const callId = this.info.render.calls; + + if (!attribute.isInterleavedBufferAttribute) { + if (this.attributeCall.get(attribute) !== callId) { + this.attributes.update(attribute, type); + + this.attributeCall.set(attribute, callId); + } + } else { + if (this.attributeCall.get(attribute) === undefined) { + this.attributes.update(attribute, type); + + this.attributeCall.set(attribute, callId); + } else if (this.attributeCall.get(attribute.data) !== callId) { + this.attributes.update(attribute, type); + + this.attributeCall.set(attribute.data, callId); + + this.attributeCall.set(attribute, callId); + } + } + } + + getIndex(renderObject) { + const { geometry, material } = renderObject; + + let index = geometry.index; + + if (material.wireframe === true) { + const wireframes = this.wireframes; + + let wireframeAttribute = wireframes.get(geometry); + + if (wireframeAttribute === undefined) { + wireframeAttribute = getWireframeIndex(geometry); + + wireframes.set(geometry, wireframeAttribute); + } else if (wireframeAttribute.version !== getWireframeVersion(geometry)) { + this.attributes.delete(wireframeAttribute); + + wireframeAttribute = getWireframeIndex(geometry); + + wireframes.set(geometry, wireframeAttribute); + } + + index = wireframeAttribute; + } + + return index; + } +} + +export default Geometries; diff --git a/examples-jsm/examples/renderers/common/Info.ts b/examples-jsm/examples/renderers/common/Info.ts new file mode 100644 index 00000000..c8e7cb41 --- /dev/null +++ b/examples-jsm/examples/renderers/common/Info.ts @@ -0,0 +1,76 @@ +class Info { + constructor() { + this.autoReset = true; + + this.frame = 0; + this.calls = 0; + + this.render = { + calls: 0, + drawCalls: 0, + triangles: 0, + points: 0, + lines: 0, + timestamp: 0, + }; + + this.compute = { + calls: 0, + computeCalls: 0, + timestamp: 0, + }; + + this.memory = { + geometries: 0, + textures: 0, + }; + } + + update(object, count, instanceCount) { + this.render.drawCalls++; + + if (object.isMesh || object.isSprite) { + this.render.triangles += instanceCount * (count / 3); + } else if (object.isPoints) { + this.render.points += instanceCount * count; + } else if (object.isLineSegments) { + this.render.lines += instanceCount * (count / 2); + } else if (object.isLine) { + this.render.lines += instanceCount * (count - 1); + } else { + console.error('THREE.WebGPUInfo: Unknown object type.'); + } + } + + updateTimestamp(type, time) { + this[type].timestamp += time; + } + + reset() { + this.render.drawCalls = 0; + this.compute.computeCalls = 0; + + this.render.triangles = 0; + this.render.points = 0; + this.render.lines = 0; + + this.render.timestamp = 0; + this.compute.timestamp = 0; + } + + dispose() { + this.reset(); + + this.calls = 0; + + this.render.calls = 0; + this.compute.calls = 0; + + this.render.timestamp = 0; + this.compute.timestamp = 0; + this.memory.geometries = 0; + this.memory.textures = 0; + } +} + +export default Info; diff --git a/examples-jsm/examples/renderers/common/Pipeline.ts b/examples-jsm/examples/renderers/common/Pipeline.ts new file mode 100644 index 00000000..16017455 --- /dev/null +++ b/examples-jsm/examples/renderers/common/Pipeline.ts @@ -0,0 +1,9 @@ +class Pipeline { + constructor(cacheKey) { + this.cacheKey = cacheKey; + + this.usedTimes = 0; + } +} + +export default Pipeline; diff --git a/examples-jsm/examples/renderers/common/Pipelines.ts b/examples-jsm/examples/renderers/common/Pipelines.ts new file mode 100644 index 00000000..68c8f223 --- /dev/null +++ b/examples-jsm/examples/renderers/common/Pipelines.ts @@ -0,0 +1,270 @@ +import DataMap from './DataMap.js'; +import RenderPipeline from './RenderPipeline.js'; +import ComputePipeline from './ComputePipeline.js'; +import ProgrammableStage from './ProgrammableStage.js'; + +class Pipelines extends DataMap { + constructor(backend, nodes) { + super(); + + this.backend = backend; + this.nodes = nodes; + + this.bindings = null; // set by the bindings + + this.caches = new Map(); + this.programs = { + vertex: new Map(), + fragment: new Map(), + compute: new Map(), + }; + } + + getForCompute(computeNode, bindings) { + const { backend } = this; + + const data = this.get(computeNode); + + if (this._needsComputeUpdate(computeNode)) { + const previousPipeline = data.pipeline; + + if (previousPipeline) { + previousPipeline.usedTimes--; + previousPipeline.computeProgram.usedTimes--; + } + + // get shader + + const nodeBuilderState = this.nodes.getForCompute(computeNode); + + // programmable stage + + let stageCompute = this.programs.compute.get(nodeBuilderState.computeShader); + + if (stageCompute === undefined) { + if (previousPipeline && previousPipeline.computeProgram.usedTimes === 0) + this._releaseProgram(previousPipeline.computeProgram); + + stageCompute = new ProgrammableStage( + nodeBuilderState.computeShader, + 'compute', + nodeBuilderState.transforms, + nodeBuilderState.nodeAttributes, + ); + this.programs.compute.set(nodeBuilderState.computeShader, stageCompute); + + backend.createProgram(stageCompute); + } + + // determine compute pipeline + + const cacheKey = this._getComputeCacheKey(computeNode, stageCompute); + + let pipeline = this.caches.get(cacheKey); + + if (pipeline === undefined) { + if (previousPipeline && previousPipeline.usedTimes === 0) this._releasePipeline(previousPipeline); + + pipeline = this._getComputePipeline(computeNode, stageCompute, cacheKey, bindings); + } + + // keep track of all used times + + pipeline.usedTimes++; + stageCompute.usedTimes++; + + // + + data.version = computeNode.version; + data.pipeline = pipeline; + } + + return data.pipeline; + } + + getForRender(renderObject, promises = null) { + const { backend } = this; + + const data = this.get(renderObject); + + if (this._needsRenderUpdate(renderObject)) { + const previousPipeline = data.pipeline; + + if (previousPipeline) { + previousPipeline.usedTimes--; + previousPipeline.vertexProgram.usedTimes--; + previousPipeline.fragmentProgram.usedTimes--; + } + + // get shader + + const nodeBuilderState = renderObject.getNodeBuilderState(); + + // programmable stages + + let stageVertex = this.programs.vertex.get(nodeBuilderState.vertexShader); + + if (stageVertex === undefined) { + if (previousPipeline && previousPipeline.vertexProgram.usedTimes === 0) + this._releaseProgram(previousPipeline.vertexProgram); + + stageVertex = new ProgrammableStage(nodeBuilderState.vertexShader, 'vertex'); + this.programs.vertex.set(nodeBuilderState.vertexShader, stageVertex); + + backend.createProgram(stageVertex); + } + + let stageFragment = this.programs.fragment.get(nodeBuilderState.fragmentShader); + + if (stageFragment === undefined) { + if (previousPipeline && previousPipeline.fragmentProgram.usedTimes === 0) + this._releaseProgram(previousPipeline.fragmentProgram); + + stageFragment = new ProgrammableStage(nodeBuilderState.fragmentShader, 'fragment'); + this.programs.fragment.set(nodeBuilderState.fragmentShader, stageFragment); + + backend.createProgram(stageFragment); + } + + // determine render pipeline + + const cacheKey = this._getRenderCacheKey(renderObject, stageVertex, stageFragment); + + let pipeline = this.caches.get(cacheKey); + + if (pipeline === undefined) { + if (previousPipeline && previousPipeline.usedTimes === 0) this._releasePipeline(previousPipeline); + + pipeline = this._getRenderPipeline(renderObject, stageVertex, stageFragment, cacheKey, promises); + } else { + renderObject.pipeline = pipeline; + } + + // keep track of all used times + + pipeline.usedTimes++; + stageVertex.usedTimes++; + stageFragment.usedTimes++; + + // + + data.pipeline = pipeline; + } + + return data.pipeline; + } + + delete(object) { + const pipeline = this.get(object).pipeline; + + if (pipeline) { + // pipeline + + pipeline.usedTimes--; + + if (pipeline.usedTimes === 0) this._releasePipeline(pipeline); + + // programs + + if (pipeline.isComputePipeline) { + pipeline.computeProgram.usedTimes--; + + if (pipeline.computeProgram.usedTimes === 0) this._releaseProgram(pipeline.computeProgram); + } else { + pipeline.fragmentProgram.usedTimes--; + pipeline.vertexProgram.usedTimes--; + + if (pipeline.vertexProgram.usedTimes === 0) this._releaseProgram(pipeline.vertexProgram); + if (pipeline.fragmentProgram.usedTimes === 0) this._releaseProgram(pipeline.fragmentProgram); + } + } + + return super.delete(object); + } + + dispose() { + super.dispose(); + + this.caches = new Map(); + this.programs = { + vertex: new Map(), + fragment: new Map(), + compute: new Map(), + }; + } + + updateForRender(renderObject) { + this.getForRender(renderObject); + } + + _getComputePipeline(computeNode, stageCompute, cacheKey, bindings) { + // check for existing pipeline + + cacheKey = cacheKey || this._getComputeCacheKey(computeNode, stageCompute); + + let pipeline = this.caches.get(cacheKey); + + if (pipeline === undefined) { + pipeline = new ComputePipeline(cacheKey, stageCompute); + + this.caches.set(cacheKey, pipeline); + + this.backend.createComputePipeline(pipeline, bindings); + } + + return pipeline; + } + + _getRenderPipeline(renderObject, stageVertex, stageFragment, cacheKey, promises) { + // check for existing pipeline + + cacheKey = cacheKey || this._getRenderCacheKey(renderObject, stageVertex, stageFragment); + + let pipeline = this.caches.get(cacheKey); + + if (pipeline === undefined) { + pipeline = new RenderPipeline(cacheKey, stageVertex, stageFragment); + + this.caches.set(cacheKey, pipeline); + + renderObject.pipeline = pipeline; + + this.backend.createRenderPipeline(renderObject, promises); + } + + return pipeline; + } + + _getComputeCacheKey(computeNode, stageCompute) { + return computeNode.id + ',' + stageCompute.id; + } + + _getRenderCacheKey(renderObject, stageVertex, stageFragment) { + return stageVertex.id + ',' + stageFragment.id + ',' + this.backend.getRenderCacheKey(renderObject); + } + + _releasePipeline(pipeline) { + this.caches.delete(pipeline.cacheKey); + } + + _releaseProgram(program) { + const code = program.code; + const stage = program.stage; + + this.programs[stage].delete(code); + } + + _needsComputeUpdate(computeNode) { + const data = this.get(computeNode); + + return data.pipeline === undefined || data.version !== computeNode.version; + } + + _needsRenderUpdate(renderObject) { + const data = this.get(renderObject); + + return data.pipeline === undefined || this.backend.needsRenderUpdate(renderObject); + } +} + +export default Pipelines; diff --git a/examples-jsm/examples/renderers/common/ProgrammableStage.ts b/examples-jsm/examples/renderers/common/ProgrammableStage.ts new file mode 100644 index 00000000..a684e444 --- /dev/null +++ b/examples-jsm/examples/renderers/common/ProgrammableStage.ts @@ -0,0 +1,16 @@ +let _id = 0; + +class ProgrammableStage { + constructor(code, type, transforms = null, attributes = null) { + this.id = _id++; + + this.code = code; + this.stage = type; + this.transforms = transforms; + this.attributes = attributes; + + this.usedTimes = 0; + } +} + +export default ProgrammableStage; diff --git a/examples-jsm/examples/renderers/common/RenderBundle.ts b/examples-jsm/examples/renderers/common/RenderBundle.ts new file mode 100644 index 00000000..e59e4937 --- /dev/null +++ b/examples-jsm/examples/renderers/common/RenderBundle.ts @@ -0,0 +1,12 @@ +class RenderBundle { + constructor(scene, camera) { + this.scene = scene; + this.camera = camera; + } + + clone() { + return Object.assign(new this.constructor(), this); + } +} + +export default RenderBundle; diff --git a/examples-jsm/examples/renderers/common/RenderBundles.ts b/examples-jsm/examples/renderers/common/RenderBundles.ts new file mode 100644 index 00000000..29140365 --- /dev/null +++ b/examples-jsm/examples/renderers/common/RenderBundles.ts @@ -0,0 +1,28 @@ +import ChainMap from './ChainMap.js'; +import RenderBundle from './RenderBundle.js'; + +class RenderBundles { + constructor() { + this.lists = new ChainMap(); + } + + get(scene, camera) { + const lists = this.lists; + const keys = [scene, camera]; + + let list = lists.get(keys); + + if (list === undefined) { + list = new RenderBundle(scene, camera); + lists.set(keys, list); + } + + return list; + } + + dispose() { + this.lists = new ChainMap(); + } +} + +export default RenderBundles; diff --git a/examples-jsm/examples/renderers/common/RenderContext.ts b/examples-jsm/examples/renderers/common/RenderContext.ts new file mode 100644 index 00000000..3b43028e --- /dev/null +++ b/examples-jsm/examples/renderers/common/RenderContext.ts @@ -0,0 +1,39 @@ +import { Vector4 } from 'three'; + +let id = 0; + +class RenderContext { + constructor() { + this.id = id++; + + this.color = true; + this.clearColor = true; + this.clearColorValue = { r: 0, g: 0, b: 0, a: 1 }; + + this.depth = true; + this.clearDepth = true; + this.clearDepthValue = 1; + + this.stencil = false; + this.clearStencil = true; + this.clearStencilValue = 1; + + this.viewport = false; + this.viewportValue = new Vector4(); + + this.scissor = false; + this.scissorValue = new Vector4(); + + this.textures = null; + this.depthTexture = null; + this.activeCubeFace = 0; + this.sampleCount = 1; + + this.width = 0; + this.height = 0; + + this.isRenderContext = true; + } +} + +export default RenderContext; diff --git a/examples-jsm/examples/renderers/common/RenderContexts.ts b/examples-jsm/examples/renderers/common/RenderContexts.ts new file mode 100644 index 00000000..630a2e42 --- /dev/null +++ b/examples-jsm/examples/renderers/common/RenderContexts.ts @@ -0,0 +1,47 @@ +import ChainMap from './ChainMap.js'; +import RenderContext from './RenderContext.js'; + +class RenderContexts { + constructor() { + this.chainMaps = {}; + } + + get(scene, camera, renderTarget = null) { + const chainKey = [scene, camera]; + + let attachmentState; + + if (renderTarget === null) { + attachmentState = 'default'; + } else { + const format = renderTarget.texture.format; + const count = renderTarget.count; + + attachmentState = `${count}:${format}:${renderTarget.samples}:${renderTarget.depthBuffer}:${renderTarget.stencilBuffer}`; + } + + const chainMap = this.getChainMap(attachmentState); + + let renderState = chainMap.get(chainKey); + + if (renderState === undefined) { + renderState = new RenderContext(); + + chainMap.set(chainKey, renderState); + } + + if (renderTarget !== null) renderState.sampleCount = renderTarget.samples === 0 ? 1 : renderTarget.samples; + + return renderState; + } + + getChainMap(attachmentState) { + return this.chainMaps[attachmentState] || (this.chainMaps[attachmentState] = new ChainMap()); + } + + dispose() { + this.chainMaps = {}; + } +} + +export default RenderContexts; diff --git a/examples-jsm/examples/renderers/common/RenderList.ts b/examples-jsm/examples/renderers/common/RenderList.ts new file mode 100644 index 00000000..a72a91df --- /dev/null +++ b/examples-jsm/examples/renderers/common/RenderList.ts @@ -0,0 +1,145 @@ +import { LightsNode } from '../../nodes/Nodes.js'; + +function painterSortStable(a, b) { + if (a.groupOrder !== b.groupOrder) { + return a.groupOrder - b.groupOrder; + } else if (a.renderOrder !== b.renderOrder) { + return a.renderOrder - b.renderOrder; + } else if (a.material.id !== b.material.id) { + return a.material.id - b.material.id; + } else if (a.z !== b.z) { + return a.z - b.z; + } else { + return a.id - b.id; + } +} + +function reversePainterSortStable(a, b) { + if (a.groupOrder !== b.groupOrder) { + return a.groupOrder - b.groupOrder; + } else if (a.renderOrder !== b.renderOrder) { + return a.renderOrder - b.renderOrder; + } else if (a.z !== b.z) { + return b.z - a.z; + } else { + return a.id - b.id; + } +} + +class RenderList { + constructor() { + this.renderItems = []; + this.renderItemsIndex = 0; + + this.opaque = []; + this.transparent = []; + this.bundles = []; + + this.lightsNode = new LightsNode([]); + this.lightsArray = []; + + this.occlusionQueryCount = 0; + } + + begin() { + this.renderItemsIndex = 0; + + this.opaque.length = 0; + this.transparent.length = 0; + this.bundles.length = 0; + + this.lightsArray.length = 0; + + this.occlusionQueryCount = 0; + + return this; + } + + getNextRenderItem(object, geometry, material, groupOrder, z, group) { + let renderItem = this.renderItems[this.renderItemsIndex]; + + if (renderItem === undefined) { + renderItem = { + id: object.id, + object: object, + geometry: geometry, + material: material, + groupOrder: groupOrder, + renderOrder: object.renderOrder, + z: z, + group: group, + }; + + this.renderItems[this.renderItemsIndex] = renderItem; + } else { + renderItem.id = object.id; + renderItem.object = object; + renderItem.geometry = geometry; + renderItem.material = material; + renderItem.groupOrder = groupOrder; + renderItem.renderOrder = object.renderOrder; + renderItem.z = z; + renderItem.group = group; + } + + this.renderItemsIndex++; + + return renderItem; + } + + push(object, geometry, material, groupOrder, z, group) { + const renderItem = this.getNextRenderItem(object, geometry, material, groupOrder, z, group); + + if (object.occlusionTest === true) this.occlusionQueryCount++; + + (material.transparent === true || material.transmission > 0 ? this.transparent : this.opaque).push(renderItem); + } + + unshift(object, geometry, material, groupOrder, z, group) { + const renderItem = this.getNextRenderItem(object, geometry, material, groupOrder, z, group); + + (material.transparent === true ? this.transparent : this.opaque).unshift(renderItem); + } + + pushBundle(group) { + this.bundles.push(group); + } + + pushLight(light) { + this.lightsArray.push(light); + } + + getLightsNode() { + return this.lightsNode.fromLights(this.lightsArray); + } + + sort(customOpaqueSort, customTransparentSort) { + if (this.opaque.length > 1) this.opaque.sort(customOpaqueSort || painterSortStable); + if (this.transparent.length > 1) this.transparent.sort(customTransparentSort || reversePainterSortStable); + } + + finish() { + // update lights + + this.lightsNode.fromLights(this.lightsArray); + + // Clear references from inactive renderItems in the list + + for (let i = this.renderItemsIndex, il = this.renderItems.length; i < il; i++) { + const renderItem = this.renderItems[i]; + + if (renderItem.id === null) break; + + renderItem.id = null; + renderItem.object = null; + renderItem.geometry = null; + renderItem.material = null; + renderItem.groupOrder = null; + renderItem.renderOrder = null; + renderItem.z = null; + renderItem.group = null; + } + } +} + +export default RenderList; diff --git a/examples-jsm/examples/renderers/common/RenderLists.ts b/examples-jsm/examples/renderers/common/RenderLists.ts new file mode 100644 index 00000000..3fc3134e --- /dev/null +++ b/examples-jsm/examples/renderers/common/RenderLists.ts @@ -0,0 +1,28 @@ +import ChainMap from './ChainMap.js'; +import RenderList from './RenderList.js'; + +class RenderLists { + constructor() { + this.lists = new ChainMap(); + } + + get(scene, camera) { + const lists = this.lists; + const keys = [scene, camera]; + + let list = lists.get(keys); + + if (list === undefined) { + list = new RenderList(); + lists.set(keys, list); + } + + return list; + } + + dispose() { + this.lists = new ChainMap(); + } +} + +export default RenderLists; diff --git a/examples-jsm/examples/renderers/common/RenderObject.ts b/examples-jsm/examples/renderers/common/RenderObject.ts new file mode 100644 index 00000000..91ba0f2a --- /dev/null +++ b/examples-jsm/examples/renderers/common/RenderObject.ts @@ -0,0 +1,215 @@ +import ClippingContext from './ClippingContext.js'; + +let id = 0; + +function getKeys(obj) { + const keys = Object.keys(obj); + + let proto = Object.getPrototypeOf(obj); + + while (proto) { + const descriptors = Object.getOwnPropertyDescriptors(proto); + + for (const key in descriptors) { + if (descriptors[key] !== undefined) { + const descriptor = descriptors[key]; + + if (descriptor && typeof descriptor.get === 'function') { + keys.push(key); + } + } + } + + proto = Object.getPrototypeOf(proto); + } + + return keys; +} + +export default class RenderObject { + constructor(nodes, geometries, renderer, object, material, scene, camera, lightsNode, renderContext) { + this._nodes = nodes; + this._geometries = geometries; + + this.id = id++; + + this.renderer = renderer; + this.object = object; + this.material = material; + this.scene = scene; + this.camera = camera; + this.lightsNode = lightsNode; + this.context = renderContext; + + this.geometry = object.geometry; + this.version = material.version; + + this.drawRange = null; + + this.attributes = null; + this.pipeline = null; + this.vertexBuffers = null; + + this.updateClipping(renderContext.clippingContext); + + this.clippingContextVersion = this.clippingContext.version; + + this.initialNodesCacheKey = this.getNodesCacheKey(); + this.initialCacheKey = this.getCacheKey(); + + this._nodeBuilderState = null; + this._bindings = null; + + this.onDispose = null; + + this.isRenderObject = true; + + this.onMaterialDispose = () => { + this.dispose(); + }; + + this.material.addEventListener('dispose', this.onMaterialDispose); + } + + updateClipping(parent) { + const material = this.material; + + let clippingContext = this.clippingContext; + + if (Array.isArray(material.clippingPlanes)) { + if (clippingContext === parent || !clippingContext) { + clippingContext = new ClippingContext(); + this.clippingContext = clippingContext; + } + + clippingContext.update(parent, material); + } else if (this.clippingContext !== parent) { + this.clippingContext = parent; + } + } + + get clippingNeedsUpdate() { + if (this.clippingContext.version === this.clippingContextVersion) return false; + + this.clippingContextVersion = this.clippingContext.version; + + return true; + } + + getNodeBuilderState() { + return this._nodeBuilderState || (this._nodeBuilderState = this._nodes.getForRender(this)); + } + + getBindings() { + return this._bindings || (this._bindings = this.getNodeBuilderState().createBindings()); + } + + getIndex() { + return this._geometries.getIndex(this); + } + + getChainArray() { + return [this.object, this.material, this.context, this.lightsNode]; + } + + getAttributes() { + if (this.attributes !== null) return this.attributes; + + const nodeAttributes = this.getNodeBuilderState().nodeAttributes; + const geometry = this.geometry; + + const attributes = []; + const vertexBuffers = new Set(); + + for (const nodeAttribute of nodeAttributes) { + const attribute = + nodeAttribute.node && nodeAttribute.node.attribute + ? nodeAttribute.node.attribute + : geometry.getAttribute(nodeAttribute.name); + + if (attribute === undefined) continue; + + attributes.push(attribute); + + const bufferAttribute = attribute.isInterleavedBufferAttribute ? attribute.data : attribute; + vertexBuffers.add(bufferAttribute); + } + + this.attributes = attributes; + this.vertexBuffers = Array.from(vertexBuffers.values()); + + return attributes; + } + + getVertexBuffers() { + if (this.vertexBuffers === null) this.getAttributes(); + + return this.vertexBuffers; + } + + getMaterialCacheKey() { + const { object, material } = this; + + let cacheKey = material.customProgramCacheKey(); + + for (const property of getKeys(material)) { + if (/^(is[A-Z]|_)|^(visible|version|uuid|name|opacity|userData)$/.test(property)) continue; + + let value = material[property]; + + if (value !== null) { + const type = typeof value; + + if (type === 'number') + value = value !== 0 ? '1' : '0'; // Convert to on/off, important for clearcoat, transmission, etc + else if (type === 'object') value = '{}'; + } + + cacheKey += /*property + ':' +*/ value + ','; + } + + cacheKey += this.clippingContextVersion + ','; + + if (object.skeleton) { + cacheKey += object.skeleton.bones.length + ','; + } + + if (object.morphTargetInfluences) { + cacheKey += object.morphTargetInfluences.length + ','; + } + + if (object.isBatchedMesh) { + cacheKey += object._matricesTexture.uuid + ','; + + if (object._colorsTexture !== null) { + cacheKey += object._colorsTexture.uuid + ','; + } + } + + if (object.count > 1) { + cacheKey += object.count + ','; + } + + return cacheKey; + } + + get needsUpdate() { + return this.initialNodesCacheKey !== this.getNodesCacheKey() || this.clippingNeedsUpdate; + } + + getNodesCacheKey() { + // Environment Nodes Cache Key + + return this._nodes.getCacheKey(this.scene, this.lightsNode); + } + + getCacheKey() { + return this.getMaterialCacheKey() + ',' + this.getNodesCacheKey(); + } + + dispose() { + this.material.removeEventListener('dispose', this.onMaterialDispose); + + this.onDispose(); + } +} diff --git a/examples-jsm/examples/renderers/common/RenderObjects.ts b/examples-jsm/examples/renderers/common/RenderObjects.ts new file mode 100644 index 00000000..76dc482e --- /dev/null +++ b/examples-jsm/examples/renderers/common/RenderObjects.ts @@ -0,0 +1,100 @@ +import ChainMap from './ChainMap.js'; +import RenderObject from './RenderObject.js'; + +class RenderObjects { + constructor(renderer, nodes, geometries, pipelines, bindings, info) { + this.renderer = renderer; + this.nodes = nodes; + this.geometries = geometries; + this.pipelines = pipelines; + this.bindings = bindings; + this.info = info; + + this.chainMaps = {}; + } + + get(object, material, scene, camera, lightsNode, renderContext, passId) { + const chainMap = this.getChainMap(passId); + const chainArray = [object, material, renderContext, lightsNode]; + + let renderObject = chainMap.get(chainArray); + + if (renderObject === undefined) { + renderObject = this.createRenderObject( + this.nodes, + this.geometries, + this.renderer, + object, + material, + scene, + camera, + lightsNode, + renderContext, + passId, + ); + + chainMap.set(chainArray, renderObject); + } else { + renderObject.updateClipping(renderContext.clippingContext); + + if (renderObject.version !== material.version || renderObject.needsUpdate) { + if (renderObject.initialCacheKey !== renderObject.getCacheKey()) { + renderObject.dispose(); + + renderObject = this.get(object, material, scene, camera, lightsNode, renderContext, passId); + } else { + renderObject.version = material.version; + } + } + } + + return renderObject; + } + + getChainMap(passId = 'default') { + return this.chainMaps[passId] || (this.chainMaps[passId] = new ChainMap()); + } + + dispose() { + this.chainMaps = {}; + } + + createRenderObject( + nodes, + geometries, + renderer, + object, + material, + scene, + camera, + lightsNode, + renderContext, + passId, + ) { + const chainMap = this.getChainMap(passId); + + const renderObject = new RenderObject( + nodes, + geometries, + renderer, + object, + material, + scene, + camera, + lightsNode, + renderContext, + ); + + renderObject.onDispose = () => { + this.pipelines.delete(renderObject); + this.bindings.delete(renderObject); + this.nodes.delete(renderObject); + + chainMap.delete(renderObject.getChainArray()); + }; + + return renderObject; + } +} + +export default RenderObjects; diff --git a/examples-jsm/examples/renderers/common/RenderPipeline.ts b/examples-jsm/examples/renderers/common/RenderPipeline.ts new file mode 100644 index 00000000..0ec34b04 --- /dev/null +++ b/examples-jsm/examples/renderers/common/RenderPipeline.ts @@ -0,0 +1,12 @@ +import Pipeline from './Pipeline.js'; + +class RenderPipeline extends Pipeline { + constructor(cacheKey, vertexProgram, fragmentProgram) { + super(cacheKey); + + this.vertexProgram = vertexProgram; + this.fragmentProgram = fragmentProgram; + } +} + +export default RenderPipeline; diff --git a/examples-jsm/examples/renderers/common/Renderer.ts b/examples-jsm/examples/renderers/common/Renderer.ts new file mode 100644 index 00000000..dc2980c1 --- /dev/null +++ b/examples-jsm/examples/renderers/common/Renderer.ts @@ -0,0 +1,1345 @@ +import Animation from './Animation.js'; +import RenderObjects from './RenderObjects.js'; +import Attributes from './Attributes.js'; +import Geometries from './Geometries.js'; +import Info from './Info.js'; +import Pipelines from './Pipelines.js'; +import Bindings from './Bindings.js'; +import RenderLists from './RenderLists.js'; +import RenderContexts from './RenderContexts.js'; +import Textures from './Textures.js'; +import Background from './Background.js'; +import Nodes from './nodes/Nodes.js'; +import Color4 from './Color4.js'; +import ClippingContext from './ClippingContext.js'; +import { + Scene, + Frustum, + Matrix4, + Vector2, + Vector3, + Vector4, + DoubleSide, + BackSide, + FrontSide, + SRGBColorSpace, + NoColorSpace, + NoToneMapping, + LinearFilter, + LinearSRGBColorSpace, + RenderTarget, + HalfFloatType, + RGBAFormat, +} from 'three'; +import { NodeMaterial } from '../../nodes/Nodes.js'; +import QuadMesh from '../../objects/QuadMesh.js'; +import RenderBundles from './RenderBundles.js'; + +const _scene = new Scene(); +const _drawingBufferSize = new Vector2(); +const _screen = new Vector4(); +const _frustum = new Frustum(); +const _projScreenMatrix = new Matrix4(); +const _vector3 = new Vector3(); +const _quad = new QuadMesh(new NodeMaterial()); + +class Renderer { + constructor(backend, parameters = {}) { + this.isRenderer = true; + + // + + const { logarithmicDepthBuffer = false, alpha = true } = parameters; + + // public + + this.domElement = backend.getDomElement(); + + this.backend = backend; + + this.autoClear = true; + this.autoClearColor = true; + this.autoClearDepth = true; + this.autoClearStencil = true; + + this.alpha = alpha; + + this.logarithmicDepthBuffer = logarithmicDepthBuffer; + + this.outputColorSpace = SRGBColorSpace; + + this.toneMapping = NoToneMapping; + this.toneMappingExposure = 1.0; + + this.sortObjects = true; + + this.depth = true; + this.stencil = false; + + this.clippingPlanes = []; + + this.info = new Info(); + + // nodes + + this.toneMappingNode = null; + + // internals + + this._pixelRatio = 1; + this._width = this.domElement.width; + this._height = this.domElement.height; + + this._viewport = new Vector4(0, 0, this._width, this._height); + this._scissor = new Vector4(0, 0, this._width, this._height); + this._scissorTest = false; + + this._attributes = null; + this._geometries = null; + this._nodes = null; + this._animation = null; + this._bindings = null; + this._objects = null; + this._pipelines = null; + this._bundles = null; + this._renderLists = null; + this._renderContexts = null; + this._textures = null; + this._background = null; + + this._currentRenderContext = null; + + this._opaqueSort = null; + this._transparentSort = null; + + this._frameBufferTarget = null; + + const alphaClear = this.alpha === true ? 0 : 1; + + this._clearColor = new Color4(0, 0, 0, alphaClear); + this._clearDepth = 1; + this._clearStencil = 0; + + this._renderTarget = null; + this._activeCubeFace = 0; + this._activeMipmapLevel = 0; + + this._renderObjectFunction = null; + this._currentRenderObjectFunction = null; + this._currentRenderBundle = null; + + this._handleObjectFunction = this._renderObjectDirect; + + this._initialized = false; + this._initPromise = null; + + this._compilationPromises = null; + + // backwards compatibility + + this.shadowMap = { + enabled: false, + type: null, + }; + + this.xr = { + enabled: false, + }; + + this.debug = { + checkShaderErrors: true, + onShaderError: null, + }; + } + + async init() { + if (this._initialized) { + throw new Error('Renderer: Backend has already been initialized.'); + } + + if (this._initPromise !== null) { + return this._initPromise; + } + + this._initPromise = new Promise(async (resolve, reject) => { + const backend = this.backend; + + try { + await backend.init(this); + } catch (error) { + reject(error); + return; + } + + this._nodes = new Nodes(this, backend); + this._animation = new Animation(this._nodes, this.info); + this._attributes = new Attributes(backend); + this._background = new Background(this, this._nodes); + this._geometries = new Geometries(this._attributes, this.info); + this._textures = new Textures(this, backend, this.info); + this._pipelines = new Pipelines(backend, this._nodes); + this._bindings = new Bindings( + backend, + this._nodes, + this._textures, + this._attributes, + this._pipelines, + this.info, + ); + this._objects = new RenderObjects( + this, + this._nodes, + this._geometries, + this._pipelines, + this._bindings, + this.info, + ); + this._renderLists = new RenderLists(); + this._bundles = new RenderBundles(); + this._renderContexts = new RenderContexts(); + + // + + this._initialized = true; + + resolve(); + }); + + return this._initPromise; + } + + get coordinateSystem() { + return this.backend.coordinateSystem; + } + + async compileAsync(scene, camera, targetScene = null) { + if (this._initialized === false) await this.init(); + + // preserve render tree + + const nodeFrame = this._nodes.nodeFrame; + + const previousRenderId = nodeFrame.renderId; + const previousRenderContext = this._currentRenderContext; + const previousRenderObjectFunction = this._currentRenderObjectFunction; + const previousCompilationPromises = this._compilationPromises; + + // + + const sceneRef = scene.isScene === true ? scene : _scene; + + if (targetScene === null) targetScene = scene; + + const renderTarget = this._renderTarget; + const renderContext = this._renderContexts.get(targetScene, camera, renderTarget); + const activeMipmapLevel = this._activeMipmapLevel; + + const compilationPromises = []; + + this._currentRenderContext = renderContext; + this._currentRenderObjectFunction = this.renderObject; + + this._handleObjectFunction = this._createObjectPipeline; + + this._compilationPromises = compilationPromises; + + nodeFrame.renderId++; + + // + + nodeFrame.update(); + + // + + renderContext.depth = this.depth; + renderContext.stencil = this.stencil; + + if (!renderContext.clippingContext) renderContext.clippingContext = new ClippingContext(); + renderContext.clippingContext.updateGlobal(this, camera); + + // + + sceneRef.onBeforeRender(this, scene, camera, renderTarget); + + // + + const renderList = this._renderLists.get(scene, camera); + renderList.begin(); + + this._projectObject(scene, camera, 0, renderList); + + // include lights from target scene + if (targetScene !== scene) { + targetScene.traverseVisible(function (object) { + if (object.isLight && object.layers.test(camera.layers)) { + renderList.pushLight(object); + } + }); + } + + renderList.finish(); + + // + + if (renderTarget !== null) { + this._textures.updateRenderTarget(renderTarget, activeMipmapLevel); + + const renderTargetData = this._textures.get(renderTarget); + + renderContext.textures = renderTargetData.textures; + renderContext.depthTexture = renderTargetData.depthTexture; + } else { + renderContext.textures = null; + renderContext.depthTexture = null; + } + + // + + this._nodes.updateScene(sceneRef); + + // + + this._background.update(sceneRef, renderList, renderContext); + + // process render lists + + const opaqueObjects = renderList.opaque; + const transparentObjects = renderList.transparent; + const lightsNode = renderList.lightsNode; + + if (opaqueObjects.length > 0) this._renderObjects(opaqueObjects, camera, sceneRef, lightsNode); + if (transparentObjects.length > 0) this._renderObjects(transparentObjects, camera, sceneRef, lightsNode); + + // restore render tree + + nodeFrame.renderId = previousRenderId; + + this._currentRenderContext = previousRenderContext; + this._currentRenderObjectFunction = previousRenderObjectFunction; + this._compilationPromises = previousCompilationPromises; + + this._handleObjectFunction = this._renderObjectDirect; + + // wait for all promises setup by backends awaiting compilation/linking/pipeline creation to complete + + await Promise.all(compilationPromises); + } + + async renderAsync(scene, camera) { + if (this._initialized === false) await this.init(); + + const renderContext = this._renderScene(scene, camera); + + await this.backend.resolveTimestampAsync(renderContext, 'render'); + } + + _renderBundle(bundle, sceneRef, lightsNode) { + const { object, camera, renderList } = bundle; + + const renderContext = this._currentRenderContext; + const renderContextData = this.backend.get(renderContext); + + // + + const renderBundle = this._bundles.get(object, camera); + + const renderBundleData = this.backend.get(renderBundle); + if (renderBundleData.renderContexts === undefined) renderBundleData.renderContexts = new Set(); + + // + + const renderBundleNeedsUpdate = + renderBundleData.renderContexts.has(renderContext) === false || object.needsUpdate === true; + + renderBundleData.renderContexts.add(renderContext); + + if (renderBundleNeedsUpdate) { + if (renderContextData.renderObjects === undefined || object.needsUpdate === true) { + const nodeFrame = this._nodes.nodeFrame; + + renderContextData.renderObjects = []; + renderContextData.renderBundles = []; + renderContextData.scene = sceneRef; + renderContextData.camera = camera; + renderContextData.renderId = nodeFrame.renderId; + + renderContextData.registerBundlesPhase = true; + } + + this._currentRenderBundle = renderBundle; + + const opaqueObjects = renderList.opaque; + + if (opaqueObjects.length > 0) this._renderObjects(opaqueObjects, camera, sceneRef, lightsNode); + + this._currentRenderBundle = null; + + // + + object.needsUpdate = false; + } else { + const renderContext = this._currentRenderContext; + const renderContextData = this.backend.get(renderContext); + + for (let i = 0, l = renderContextData.renderObjects.length; i < l; i++) { + const renderObject = renderContextData.renderObjects[i]; + + this._nodes.updateBefore(renderObject); + + // + + renderObject.object.modelViewMatrix.multiplyMatrices( + camera.matrixWorldInverse, + renderObject.object.matrixWorld, + ); + renderObject.object.normalMatrix.getNormalMatrix(renderObject.object.modelViewMatrix); + + this._nodes.updateForRender(renderObject); + this._bindings.updateForRender(renderObject); + + this.backend.draw(renderObject, this.info); + + this._nodes.updateAfter(renderObject); + } + } + } + + render(scene, camera) { + if (this._initialized === false) { + console.warn( + 'THREE.Renderer: .render() called before the backend is initialized. Try using .renderAsync() instead.', + ); + + return this.renderAsync(scene, camera); + } + + this._renderScene(scene, camera); + } + + _getFrameBufferTarget() { + const { currentColorSpace } = this; + + const useToneMapping = + this._renderTarget === null && (this.toneMapping !== NoToneMapping || this.toneMappingNode !== null); + const useColorSpace = currentColorSpace !== LinearSRGBColorSpace && currentColorSpace !== NoColorSpace; + + if (useToneMapping === false && useColorSpace === false) return null; + + const { width, height } = this.getDrawingBufferSize(_drawingBufferSize); + const { depth, stencil } = this; + + let frameBufferTarget = this._frameBufferTarget; + + if (frameBufferTarget === null) { + frameBufferTarget = new RenderTarget(width, height, { + depthBuffer: depth, + stencilBuffer: stencil, + type: HalfFloatType, // FloatType + format: RGBAFormat, + colorSpace: LinearSRGBColorSpace, + generateMipmaps: false, + minFilter: LinearFilter, + magFilter: LinearFilter, + samples: this.backend.parameters.antialias ? 4 : 0, + }); + + frameBufferTarget.isPostProcessingRenderTarget = true; + + this._frameBufferTarget = frameBufferTarget; + } + + frameBufferTarget.depthBuffer = depth; + frameBufferTarget.stencilBuffer = stencil; + frameBufferTarget.setSize(width, height); + frameBufferTarget.viewport.copy(this._viewport); + frameBufferTarget.scissor.copy(this._scissor); + frameBufferTarget.viewport.multiplyScalar(this._pixelRatio); + frameBufferTarget.scissor.multiplyScalar(this._pixelRatio); + frameBufferTarget.scissorTest = this._scissorTest; + + return frameBufferTarget; + } + + _renderScene(scene, camera, useFrameBufferTarget = true) { + const frameBufferTarget = useFrameBufferTarget ? this._getFrameBufferTarget() : null; + + // preserve render tree + + const nodeFrame = this._nodes.nodeFrame; + + const previousRenderId = nodeFrame.renderId; + const previousRenderContext = this._currentRenderContext; + const previousRenderObjectFunction = this._currentRenderObjectFunction; + + // + + const sceneRef = scene.isScene === true ? scene : _scene; + + const outputRenderTarget = this._renderTarget; + + const activeCubeFace = this._activeCubeFace; + const activeMipmapLevel = this._activeMipmapLevel; + + // + + let renderTarget; + + if (frameBufferTarget !== null) { + renderTarget = frameBufferTarget; + + this.setRenderTarget(renderTarget); + } else { + renderTarget = outputRenderTarget; + } + + // + + const renderContext = this._renderContexts.get(scene, camera, renderTarget); + + this._currentRenderContext = renderContext; + this._currentRenderObjectFunction = this._renderObjectFunction || this.renderObject; + + // + + this.info.calls++; + this.info.render.calls++; + + nodeFrame.renderId = this.info.calls; + + // + + const coordinateSystem = this.coordinateSystem; + + if (camera.coordinateSystem !== coordinateSystem) { + camera.coordinateSystem = coordinateSystem; + + camera.updateProjectionMatrix(); + } + + // + + if (scene.matrixWorldAutoUpdate === true) scene.updateMatrixWorld(); + + if (camera.parent === null && camera.matrixWorldAutoUpdate === true) camera.updateMatrixWorld(); + + // + + let viewport = this._viewport; + let scissor = this._scissor; + let pixelRatio = this._pixelRatio; + + if (renderTarget !== null) { + viewport = renderTarget.viewport; + scissor = renderTarget.scissor; + pixelRatio = 1; + } + + this.getDrawingBufferSize(_drawingBufferSize); + + _screen.set(0, 0, _drawingBufferSize.width, _drawingBufferSize.height); + + const minDepth = viewport.minDepth === undefined ? 0 : viewport.minDepth; + const maxDepth = viewport.maxDepth === undefined ? 1 : viewport.maxDepth; + + renderContext.viewportValue.copy(viewport).multiplyScalar(pixelRatio).floor(); + renderContext.viewportValue.width >>= activeMipmapLevel; + renderContext.viewportValue.height >>= activeMipmapLevel; + renderContext.viewportValue.minDepth = minDepth; + renderContext.viewportValue.maxDepth = maxDepth; + renderContext.viewport = renderContext.viewportValue.equals(_screen) === false; + + renderContext.scissorValue.copy(scissor).multiplyScalar(pixelRatio).floor(); + renderContext.scissor = this._scissorTest && renderContext.scissorValue.equals(_screen) === false; + renderContext.scissorValue.width >>= activeMipmapLevel; + renderContext.scissorValue.height >>= activeMipmapLevel; + + if (!renderContext.clippingContext) renderContext.clippingContext = new ClippingContext(); + renderContext.clippingContext.updateGlobal(this, camera); + + // + + sceneRef.onBeforeRender(this, scene, camera, renderTarget); + + // + + _projScreenMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse); + _frustum.setFromProjectionMatrix(_projScreenMatrix, coordinateSystem); + + const renderList = this._renderLists.get(scene, camera); + renderList.begin(); + + this._projectObject(scene, camera, 0, renderList); + + renderList.finish(); + + if (this.sortObjects === true) { + renderList.sort(this._opaqueSort, this._transparentSort); + } + + // + + if (renderTarget !== null) { + this._textures.updateRenderTarget(renderTarget, activeMipmapLevel); + + const renderTargetData = this._textures.get(renderTarget); + + renderContext.textures = renderTargetData.textures; + renderContext.depthTexture = renderTargetData.depthTexture; + renderContext.width = renderTargetData.width; + renderContext.height = renderTargetData.height; + renderContext.renderTarget = renderTarget; + renderContext.depth = renderTarget.depthBuffer; + renderContext.stencil = renderTarget.stencilBuffer; + } else { + renderContext.textures = null; + renderContext.depthTexture = null; + renderContext.width = this.domElement.width; + renderContext.height = this.domElement.height; + renderContext.depth = this.depth; + renderContext.stencil = this.stencil; + } + + renderContext.width >>= activeMipmapLevel; + renderContext.height >>= activeMipmapLevel; + renderContext.activeCubeFace = activeCubeFace; + renderContext.activeMipmapLevel = activeMipmapLevel; + renderContext.occlusionQueryCount = renderList.occlusionQueryCount; + + // + + this._nodes.updateScene(sceneRef); + + // + + this._background.update(sceneRef, renderList, renderContext); + + // + + this.backend.beginRender(renderContext); + + // process render lists + + const opaqueObjects = renderList.opaque; + const transparentObjects = renderList.transparent; + const bundles = renderList.bundles; + const lightsNode = renderList.lightsNode; + + if (bundles.length > 0) this._renderBundles(bundles, sceneRef, lightsNode); + if (opaqueObjects.length > 0) this._renderObjects(opaqueObjects, camera, sceneRef, lightsNode); + if (transparentObjects.length > 0) this._renderObjects(transparentObjects, camera, sceneRef, lightsNode); + + // finish render pass + + this.backend.finishRender(renderContext); + + // restore render tree + + nodeFrame.renderId = previousRenderId; + + this._currentRenderContext = previousRenderContext; + this._currentRenderObjectFunction = previousRenderObjectFunction; + + // + + if (frameBufferTarget !== null) { + this.setRenderTarget(outputRenderTarget, activeCubeFace, activeMipmapLevel); + + _quad.material.fragmentNode = this._nodes.getOutputNode(renderTarget.texture); + + this._renderScene(_quad, _quad.camera, false); + } + + // + + sceneRef.onAfterRender(this, scene, camera, renderTarget); + + // + + return renderContext; + } + + getMaxAnisotropy() { + return this.backend.getMaxAnisotropy(); + } + + getActiveCubeFace() { + return this._activeCubeFace; + } + + getActiveMipmapLevel() { + return this._activeMipmapLevel; + } + + async setAnimationLoop(callback) { + if (this._initialized === false) await this.init(); + + this._animation.setAnimationLoop(callback); + } + + async getArrayBufferAsync(attribute) { + return await this.backend.getArrayBufferAsync(attribute); + } + + getContext() { + return this.backend.getContext(); + } + + getPixelRatio() { + return this._pixelRatio; + } + + getDrawingBufferSize(target) { + return target.set(this._width * this._pixelRatio, this._height * this._pixelRatio).floor(); + } + + getSize(target) { + return target.set(this._width, this._height); + } + + setPixelRatio(value = 1) { + this._pixelRatio = value; + + this.setSize(this._width, this._height, false); + } + + setDrawingBufferSize(width, height, pixelRatio) { + this._width = width; + this._height = height; + + this._pixelRatio = pixelRatio; + + this.domElement.width = Math.floor(width * pixelRatio); + this.domElement.height = Math.floor(height * pixelRatio); + + this.setViewport(0, 0, width, height); + + if (this._initialized) this.backend.updateSize(); + } + + setSize(width, height, updateStyle = true) { + this._width = width; + this._height = height; + + this.domElement.width = Math.floor(width * this._pixelRatio); + this.domElement.height = Math.floor(height * this._pixelRatio); + + if (updateStyle === true) { + this.domElement.style.width = width + 'px'; + this.domElement.style.height = height + 'px'; + } + + this.setViewport(0, 0, width, height); + + if (this._initialized) this.backend.updateSize(); + } + + setOpaqueSort(method) { + this._opaqueSort = method; + } + + setTransparentSort(method) { + this._transparentSort = method; + } + + getScissor(target) { + const scissor = this._scissor; + + target.x = scissor.x; + target.y = scissor.y; + target.width = scissor.width; + target.height = scissor.height; + + return target; + } + + setScissor(x, y, width, height) { + const scissor = this._scissor; + + if (x.isVector4) { + scissor.copy(x); + } else { + scissor.set(x, y, width, height); + } + } + + getScissorTest() { + return this._scissorTest; + } + + setScissorTest(boolean) { + this._scissorTest = boolean; + + this.backend.setScissorTest(boolean); + } + + getViewport(target) { + return target.copy(this._viewport); + } + + setViewport(x, y, width, height, minDepth = 0, maxDepth = 1) { + const viewport = this._viewport; + + if (x.isVector4) { + viewport.copy(x); + } else { + viewport.set(x, y, width, height); + } + + viewport.minDepth = minDepth; + viewport.maxDepth = maxDepth; + } + + getClearColor(target) { + return target.copy(this._clearColor); + } + + setClearColor(color, alpha = 1) { + this._clearColor.set(color); + this._clearColor.a = alpha; + } + + getClearAlpha() { + return this._clearColor.a; + } + + setClearAlpha(alpha) { + this._clearColor.a = alpha; + } + + getClearDepth() { + return this._clearDepth; + } + + setClearDepth(depth) { + this._clearDepth = depth; + } + + getClearStencil() { + return this._clearStencil; + } + + setClearStencil(stencil) { + this._clearStencil = stencil; + } + + isOccluded(object) { + const renderContext = this._currentRenderContext; + + return renderContext && this.backend.isOccluded(renderContext, object); + } + + clear(color = true, depth = true, stencil = true) { + if (this._initialized === false) { + console.warn( + 'THREE.Renderer: .clear() called before the backend is initialized. Try using .clearAsync() instead.', + ); + + return this.clearAsync(color, depth, stencil); + } + + const renderTarget = this._renderTarget || this._getFrameBufferTarget(); + + let renderTargetData = null; + + if (renderTarget !== null) { + this._textures.updateRenderTarget(renderTarget); + + renderTargetData = this._textures.get(renderTarget); + } + + this.backend.clear(color, depth, stencil, renderTargetData); + + if (renderTarget !== null && this._renderTarget === null) { + // If a color space transform or tone mapping is required, + // the clear operation clears the intermediate renderTarget texture, but does not update the screen canvas. + + _quad.material.fragmentNode = this._nodes.getOutputNode(renderTarget.texture); + this._renderScene(_quad, _quad.camera, false); + } + } + + clearColor() { + return this.clear(true, false, false); + } + + clearDepth() { + return this.clear(false, true, false); + } + + clearStencil() { + return this.clear(false, false, true); + } + + async clearAsync(color = true, depth = true, stencil = true) { + if (this._initialized === false) await this.init(); + + this.clear(color, depth, stencil); + } + + clearColorAsync() { + return this.clearAsync(true, false, false); + } + + clearDepthAsync() { + return this.clearAsync(false, true, false); + } + + clearStencilAsync() { + return this.clearAsync(false, false, true); + } + + get currentColorSpace() { + const renderTarget = this._renderTarget; + + if (renderTarget !== null) { + const texture = renderTarget.texture; + + return (Array.isArray(texture) ? texture[0] : texture).colorSpace; + } + + return this.outputColorSpace; + } + + dispose() { + this.info.dispose(); + + this._animation.dispose(); + this._objects.dispose(); + this._pipelines.dispose(); + this._nodes.dispose(); + this._bindings.dispose(); + this._renderLists.dispose(); + this._renderContexts.dispose(); + this._textures.dispose(); + + this.setRenderTarget(null); + this.setAnimationLoop(null); + } + + setRenderTarget(renderTarget, activeCubeFace = 0, activeMipmapLevel = 0) { + this._renderTarget = renderTarget; + this._activeCubeFace = activeCubeFace; + this._activeMipmapLevel = activeMipmapLevel; + } + + getRenderTarget() { + return this._renderTarget; + } + + setRenderObjectFunction(renderObjectFunction) { + this._renderObjectFunction = renderObjectFunction; + } + + getRenderObjectFunction() { + return this._renderObjectFunction; + } + + async computeAsync(computeNodes) { + if (this._initialized === false) await this.init(); + + const nodeFrame = this._nodes.nodeFrame; + + const previousRenderId = nodeFrame.renderId; + + // + + this.info.calls++; + this.info.compute.calls++; + this.info.compute.computeCalls++; + + nodeFrame.renderId = this.info.calls; + + // + + const backend = this.backend; + const pipelines = this._pipelines; + const bindings = this._bindings; + const nodes = this._nodes; + const computeList = Array.isArray(computeNodes) ? computeNodes : [computeNodes]; + + if (computeList[0] === undefined || computeList[0].isComputeNode !== true) { + throw new Error('THREE.Renderer: .compute() expects a ComputeNode.'); + } + + backend.beginCompute(computeNodes); + + for (const computeNode of computeList) { + // onInit + + if (pipelines.has(computeNode) === false) { + const dispose = () => { + computeNode.removeEventListener('dispose', dispose); + + pipelines.delete(computeNode); + bindings.delete(computeNode); + nodes.delete(computeNode); + }; + + computeNode.addEventListener('dispose', dispose); + + // + + computeNode.onInit({ renderer: this }); + } + + nodes.updateForCompute(computeNode); + bindings.updateForCompute(computeNode); + + const computeBindings = bindings.getForCompute(computeNode); + const computePipeline = pipelines.getForCompute(computeNode, computeBindings); + + backend.compute(computeNodes, computeNode, computeBindings, computePipeline); + } + + backend.finishCompute(computeNodes); + + await this.backend.resolveTimestampAsync(computeNodes, 'compute'); + + // + + nodeFrame.renderId = previousRenderId; + } + + async hasFeatureAsync(name) { + if (this._initialized === false) await this.init(); + + return this.backend.hasFeature(name); + } + + hasFeature(name) { + if (this._initialized === false) { + console.warn( + 'THREE.Renderer: .hasFeature() called before the backend is initialized. Try using .hasFeatureAsync() instead.', + ); + + return false; + } + + return this.backend.hasFeature(name); + } + + copyFramebufferToTexture(framebufferTexture) { + const renderContext = this._currentRenderContext; + + this._textures.updateTexture(framebufferTexture); + + this.backend.copyFramebufferToTexture(framebufferTexture, renderContext); + } + + copyTextureToTexture(srcTexture, dstTexture, srcRegion = null, dstPosition = null, level = 0) { + this._textures.updateTexture(srcTexture); + this._textures.updateTexture(dstTexture); + + this.backend.copyTextureToTexture(srcTexture, dstTexture, srcRegion, dstPosition, level); + } + + readRenderTargetPixelsAsync(renderTarget, x, y, width, height, index = 0) { + return this.backend.copyTextureToBuffer(renderTarget.textures[index], x, y, width, height); + } + + _projectObject(object, camera, groupOrder, renderList) { + if (object.visible === false) return; + + const visible = object.layers.test(camera.layers); + + if (visible) { + if (object.isGroup) { + groupOrder = object.renderOrder; + } else if (object.isLOD) { + if (object.autoUpdate === true) object.update(camera); + } else if (object.isLight) { + renderList.pushLight(object); + } else if (object.isSprite) { + if (!object.frustumCulled || _frustum.intersectsSprite(object)) { + if (this.sortObjects === true) { + _vector3.setFromMatrixPosition(object.matrixWorld).applyMatrix4(_projScreenMatrix); + } + + const geometry = object.geometry; + const material = object.material; + + if (material.visible) { + renderList.push(object, geometry, material, groupOrder, _vector3.z, null); + } + } + } else if (object.isLineLoop) { + console.error( + 'THREE.Renderer: Objects of type THREE.LineLoop are not supported. Please use THREE.Line or THREE.LineSegments.', + ); + } else if (object.isMesh || object.isLine || object.isPoints) { + if (!object.frustumCulled || _frustum.intersectsObject(object)) { + const geometry = object.geometry; + const material = object.material; + + if (this.sortObjects === true) { + if (geometry.boundingSphere === null) geometry.computeBoundingSphere(); + + _vector3 + .copy(geometry.boundingSphere.center) + .applyMatrix4(object.matrixWorld) + .applyMatrix4(_projScreenMatrix); + } + + if (Array.isArray(material)) { + const groups = geometry.groups; + + for (let i = 0, l = groups.length; i < l; i++) { + const group = groups[i]; + const groupMaterial = material[group.materialIndex]; + + if (groupMaterial && groupMaterial.visible) { + renderList.push(object, geometry, groupMaterial, groupOrder, _vector3.z, group); + } + } + } else if (material.visible) { + renderList.push(object, geometry, material, groupOrder, _vector3.z, null); + } + } + } + } + + if (object.static === true) { + const baseRenderList = renderList; + + // replace render list + renderList = this._renderLists.get(object, camera); + + renderList.begin(); + + baseRenderList.pushBundle({ + object, + camera, + renderList, + }); + + renderList.finish(); + } + + const children = object.children; + + for (let i = 0, l = children.length; i < l; i++) { + this._projectObject(children[i], camera, groupOrder, renderList); + } + } + + _renderBundles(bundles, sceneRef, lightsNode) { + for (const bundle of bundles) { + this._renderBundle(bundle, sceneRef, lightsNode); + } + } + + _renderObjects(renderList, camera, scene, lightsNode) { + // process renderable objects + + for (let i = 0, il = renderList.length; i < il; i++) { + const renderItem = renderList[i]; + + // @TODO: Add support for multiple materials per object. This will require to extract + // the material from the renderItem object and pass it with its group data to renderObject(). + + const { object, geometry, material, group } = renderItem; + + if (camera.isArrayCamera) { + const cameras = camera.cameras; + + for (let j = 0, jl = cameras.length; j < jl; j++) { + const camera2 = cameras[j]; + + if (object.layers.test(camera2.layers)) { + const vp = camera2.viewport; + const minDepth = vp.minDepth === undefined ? 0 : vp.minDepth; + const maxDepth = vp.maxDepth === undefined ? 1 : vp.maxDepth; + + const viewportValue = this._currentRenderContext.viewportValue; + viewportValue.copy(vp).multiplyScalar(this._pixelRatio).floor(); + viewportValue.minDepth = minDepth; + viewportValue.maxDepth = maxDepth; + + this.backend.updateViewport(this._currentRenderContext); + + this._currentRenderObjectFunction( + object, + scene, + camera2, + geometry, + material, + group, + lightsNode, + ); + } + } + } else { + this._currentRenderObjectFunction(object, scene, camera, geometry, material, group, lightsNode); + } + } + } + + renderObject(object, scene, camera, geometry, material, group, lightsNode) { + let overridePositionNode; + let overrideFragmentNode; + let overrideDepthNode; + + // + + object.onBeforeRender(this, scene, camera, geometry, material, group); + + // + + if (scene.overrideMaterial !== null) { + const overrideMaterial = scene.overrideMaterial; + + if (material.positionNode && material.positionNode.isNode) { + overridePositionNode = overrideMaterial.positionNode; + overrideMaterial.positionNode = material.positionNode; + } + + if (overrideMaterial.isShadowNodeMaterial) { + overrideMaterial.side = material.shadowSide === null ? material.side : material.shadowSide; + + if (material.depthNode && material.depthNode.isNode) { + overrideDepthNode = overrideMaterial.depthNode; + overrideMaterial.depthNode = material.depthNode; + } + + if (material.shadowNode && material.shadowNode.isNode) { + overrideFragmentNode = overrideMaterial.fragmentNode; + overrideMaterial.fragmentNode = material.shadowNode; + } + + if (this.localClippingEnabled) { + if (material.clipShadows) { + if (overrideMaterial.clippingPlanes !== material.clippingPlanes) { + overrideMaterial.clippingPlanes = material.clippingPlanes; + overrideMaterial.needsUpdate = true; + } + + if (overrideMaterial.clipIntersection !== material.clipIntersection) { + overrideMaterial.clipIntersection = material.clipIntersection; + } + } else if (Array.isArray(overrideMaterial.clippingPlanes)) { + overrideMaterial.clippingPlanes = null; + overrideMaterial.needsUpdate = true; + } + } + } + + material = overrideMaterial; + } + + // + + if (material.transparent === true && material.side === DoubleSide && material.forceSinglePass === false) { + material.side = BackSide; + this._handleObjectFunction(object, material, scene, camera, lightsNode, group, 'backSide'); // create backSide pass id + + material.side = FrontSide; + this._handleObjectFunction(object, material, scene, camera, lightsNode, group); // use default pass id + + material.side = DoubleSide; + } else { + this._handleObjectFunction(object, material, scene, camera, lightsNode, group); + } + + // + + if (overridePositionNode !== undefined) { + scene.overrideMaterial.positionNode = overridePositionNode; + } + + if (overrideDepthNode !== undefined) { + scene.overrideMaterial.depthNode = overrideDepthNode; + } + + if (overrideFragmentNode !== undefined) { + scene.overrideMaterial.fragmentNode = overrideFragmentNode; + } + + // + + object.onAfterRender(this, scene, camera, geometry, material, group); + } + + _renderObjectDirect(object, material, scene, camera, lightsNode, group, passId) { + const renderObject = this._objects.get( + object, + material, + scene, + camera, + lightsNode, + this._currentRenderContext, + passId, + ); + renderObject.drawRange = group || object.geometry.drawRange; + + // + + this._nodes.updateBefore(renderObject); + + // + + object.modelViewMatrix.multiplyMatrices(camera.matrixWorldInverse, object.matrixWorld); + object.normalMatrix.getNormalMatrix(object.modelViewMatrix); + + // + + this._nodes.updateForRender(renderObject); + this._geometries.updateForRender(renderObject); + this._bindings.updateForRender(renderObject); + this._pipelines.updateForRender(renderObject); + + // + + if (this._currentRenderBundle !== null && this._currentRenderBundle.needsUpdate === true) { + const renderObjectData = this.backend.get(renderObject); + + renderObjectData.bundleEncoder = undefined; + renderObjectData.lastPipelineGPU = undefined; + } + + this.backend.draw(renderObject, this.info); + + if (this._currentRenderBundle !== null) { + const renderContextData = this.backend.get(this._currentRenderContext); + + renderContextData.renderObjects.push(renderObject); + } + + this._nodes.updateAfter(renderObject); + } + + _createObjectPipeline(object, material, scene, camera, lightsNode, passId) { + const renderObject = this._objects.get( + object, + material, + scene, + camera, + lightsNode, + this._currentRenderContext, + passId, + ); + + // + + this._nodes.updateBefore(renderObject); + + // + + this._nodes.updateForRender(renderObject); + this._geometries.updateForRender(renderObject); + this._bindings.updateForRender(renderObject); + + this._pipelines.getForRender(renderObject, this._compilationPromises); + + this._nodes.updateAfter(renderObject); + } + + get compute() { + return this.computeAsync; + } + + get compile() { + return this.compileAsync; + } +} + +export default Renderer; diff --git a/examples-jsm/examples/renderers/common/SampledTexture.ts b/examples-jsm/examples/renderers/common/SampledTexture.ts new file mode 100644 index 00000000..d995a59f --- /dev/null +++ b/examples-jsm/examples/renderers/common/SampledTexture.ts @@ -0,0 +1,61 @@ +import Binding from './Binding.js'; + +let id = 0; + +class SampledTexture extends Binding { + constructor(name, texture) { + super(name); + + this.id = id++; + + this.texture = texture; + this.version = texture ? texture.version : 0; + this.store = false; + + this.isSampledTexture = true; + } + + get needsBindingsUpdate() { + const { texture, version } = this; + + return texture.isVideoTexture ? true : version !== texture.version; // @TODO: version === 0 && texture.version > 0 ( add it just to External Textures like PNG,JPG ) + } + + update() { + const { texture, version } = this; + + if (version !== texture.version) { + this.version = texture.version; + + return true; + } + + return false; + } +} + +class SampledArrayTexture extends SampledTexture { + constructor(name, texture) { + super(name, texture); + + this.isSampledArrayTexture = true; + } +} + +class Sampled3DTexture extends SampledTexture { + constructor(name, texture) { + super(name, texture); + + this.isSampled3DTexture = true; + } +} + +class SampledCubeTexture extends SampledTexture { + constructor(name, texture) { + super(name, texture); + + this.isSampledCubeTexture = true; + } +} + +export { SampledTexture, SampledArrayTexture, Sampled3DTexture, SampledCubeTexture }; diff --git a/examples-jsm/examples/renderers/common/Sampler.ts b/examples-jsm/examples/renderers/common/Sampler.ts new file mode 100644 index 00000000..8cd20d04 --- /dev/null +++ b/examples-jsm/examples/renderers/common/Sampler.ts @@ -0,0 +1,14 @@ +import Binding from './Binding.js'; + +class Sampler extends Binding { + constructor(name, texture) { + super(name); + + this.texture = texture; + this.version = texture ? texture.version : 0; + + this.isSampler = true; + } +} + +export default Sampler; diff --git a/examples-jsm/examples/renderers/common/StorageBuffer.ts b/examples-jsm/examples/renderers/common/StorageBuffer.ts new file mode 100644 index 00000000..ef5d3e46 --- /dev/null +++ b/examples-jsm/examples/renderers/common/StorageBuffer.ts @@ -0,0 +1,13 @@ +import Buffer from './Buffer.js'; + +class StorageBuffer extends Buffer { + constructor(name, attribute) { + super(name, attribute ? attribute.array : null); + + this.attribute = attribute; + + this.isStorageBuffer = true; + } +} + +export default StorageBuffer; diff --git a/examples-jsm/examples/renderers/common/Textures.ts b/examples-jsm/examples/renderers/common/Textures.ts new file mode 100644 index 00000000..0eb0509c --- /dev/null +++ b/examples-jsm/examples/renderers/common/Textures.ts @@ -0,0 +1,288 @@ +import DataMap from './DataMap.js'; + +import { + Vector3, + DepthTexture, + DepthStencilFormat, + DepthFormat, + UnsignedIntType, + UnsignedInt248Type, + LinearFilter, + NearestFilter, + EquirectangularReflectionMapping, + EquirectangularRefractionMapping, + CubeReflectionMapping, + CubeRefractionMapping, + UnsignedByteType, +} from 'three'; + +const _size = new Vector3(); + +class Textures extends DataMap { + constructor(renderer, backend, info) { + super(); + + this.renderer = renderer; + this.backend = backend; + this.info = info; + } + + updateRenderTarget(renderTarget, activeMipmapLevel = 0) { + const renderTargetData = this.get(renderTarget); + + const sampleCount = renderTarget.samples === 0 ? 1 : renderTarget.samples; + const depthTextureMips = renderTargetData.depthTextureMips || (renderTargetData.depthTextureMips = {}); + + const texture = renderTarget.texture; + const textures = renderTarget.textures; + + const size = this.getSize(texture); + + const mipWidth = size.width >> activeMipmapLevel; + const mipHeight = size.height >> activeMipmapLevel; + + let depthTexture = renderTarget.depthTexture || depthTextureMips[activeMipmapLevel]; + let textureNeedsUpdate = false; + + if (depthTexture === undefined) { + depthTexture = new DepthTexture(); + depthTexture.format = renderTarget.stencilBuffer ? DepthStencilFormat : DepthFormat; + depthTexture.type = renderTarget.stencilBuffer ? UnsignedInt248Type : UnsignedIntType; // FloatType + depthTexture.image.width = mipWidth; + depthTexture.image.height = mipHeight; + + depthTextureMips[activeMipmapLevel] = depthTexture; + } + + if (renderTargetData.width !== size.width || size.height !== renderTargetData.height) { + textureNeedsUpdate = true; + depthTexture.needsUpdate = true; + + depthTexture.image.width = mipWidth; + depthTexture.image.height = mipHeight; + } + + renderTargetData.width = size.width; + renderTargetData.height = size.height; + renderTargetData.textures = textures; + renderTargetData.depthTexture = depthTexture; + renderTargetData.depth = renderTarget.depthBuffer; + renderTargetData.stencil = renderTarget.stencilBuffer; + renderTargetData.renderTarget = renderTarget; + + if (renderTargetData.sampleCount !== sampleCount) { + textureNeedsUpdate = true; + depthTexture.needsUpdate = true; + + renderTargetData.sampleCount = sampleCount; + } + + // + + const options = { sampleCount }; + + for (let i = 0; i < textures.length; i++) { + const texture = textures[i]; + + if (textureNeedsUpdate) texture.needsUpdate = true; + + this.updateTexture(texture, options); + } + + this.updateTexture(depthTexture, options); + + // dispose handler + + if (renderTargetData.initialized !== true) { + renderTargetData.initialized = true; + + // dispose + + const onDispose = () => { + renderTarget.removeEventListener('dispose', onDispose); + + if (textures !== undefined) { + for (let i = 0; i < textures.length; i++) { + this._destroyTexture(textures[i]); + } + } else { + this._destroyTexture(texture); + } + + this._destroyTexture(depthTexture); + }; + + renderTarget.addEventListener('dispose', onDispose); + } + } + + updateTexture(texture, options = {}) { + const textureData = this.get(texture); + if (textureData.initialized === true && textureData.version === texture.version) return; + + const isRenderTarget = texture.isRenderTargetTexture || texture.isDepthTexture || texture.isFramebufferTexture; + const backend = this.backend; + + if (isRenderTarget && textureData.initialized === true) { + // it's an update + + backend.destroySampler(texture); + backend.destroyTexture(texture); + } + + // + + if (texture.isFramebufferTexture) { + const renderer = this.renderer; + const renderTarget = renderer.getRenderTarget(); + + if (renderTarget) { + texture.type = renderTarget.texture.type; + } else { + texture.type = UnsignedByteType; + } + } + + // + + const { width, height, depth } = this.getSize(texture); + + options.width = width; + options.height = height; + options.depth = depth; + options.needsMipmaps = this.needsMipmaps(texture); + options.levels = options.needsMipmaps ? this.getMipLevels(texture, width, height) : 1; + + // + + if (isRenderTarget || texture.isStorageTexture === true) { + backend.createSampler(texture); + backend.createTexture(texture, options); + } else { + const needsCreate = textureData.initialized !== true; + + if (needsCreate) backend.createSampler(texture); + + if (texture.version > 0) { + const image = texture.image; + + if (image === undefined) { + console.warn('THREE.Renderer: Texture marked for update but image is undefined.'); + } else if (image.complete === false) { + console.warn('THREE.Renderer: Texture marked for update but image is incomplete.'); + } else { + if (texture.images) { + const images = []; + + for (const image of texture.images) { + images.push(image); + } + + options.images = images; + } else { + options.image = image; + } + + if (textureData.isDefaultTexture === undefined || textureData.isDefaultTexture === true) { + backend.createTexture(texture, options); + + textureData.isDefaultTexture = false; + } + + if (texture.source.dataReady === true) backend.updateTexture(texture, options); + + if (options.needsMipmaps && texture.mipmaps.length === 0) backend.generateMipmaps(texture); + } + } else { + // async update + + backend.createDefaultTexture(texture); + + textureData.isDefaultTexture = true; + } + } + + // dispose handler + + if (textureData.initialized !== true) { + textureData.initialized = true; + + // + + this.info.memory.textures++; + + // dispose + + const onDispose = () => { + texture.removeEventListener('dispose', onDispose); + + this._destroyTexture(texture); + + this.info.memory.textures--; + }; + + texture.addEventListener('dispose', onDispose); + } + + // + + textureData.version = texture.version; + } + + getSize(texture, target = _size) { + let image = texture.images ? texture.images[0] : texture.image; + + if (image) { + if (image.image !== undefined) image = image.image; + + target.width = image.width; + target.height = image.height; + target.depth = texture.isCubeTexture ? 6 : image.depth || 1; + } else { + target.width = target.height = target.depth = 1; + } + + return target; + } + + getMipLevels(texture, width, height) { + let mipLevelCount; + + if (texture.isCompressedTexture) { + mipLevelCount = texture.mipmaps.length; + } else { + mipLevelCount = Math.floor(Math.log2(Math.max(width, height))) + 1; + } + + return mipLevelCount; + } + + needsMipmaps(texture) { + if (this.isEnvironmentTexture(texture)) return true; + + return ( + texture.isCompressedTexture === true || + (texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter) + ); + } + + isEnvironmentTexture(texture) { + const mapping = texture.mapping; + + return ( + mapping === EquirectangularReflectionMapping || + mapping === EquirectangularRefractionMapping || + mapping === CubeReflectionMapping || + mapping === CubeRefractionMapping + ); + } + + _destroyTexture(texture) { + this.backend.destroySampler(texture); + this.backend.destroyTexture(texture); + + this.delete(texture); + } +} + +export default Textures; diff --git a/examples-jsm/examples/renderers/common/Uniform.ts b/examples-jsm/examples/renderers/common/Uniform.ts new file mode 100644 index 00000000..3d58b44c --- /dev/null +++ b/examples-jsm/examples/renderers/common/Uniform.ts @@ -0,0 +1,100 @@ +import { Color, Matrix3, Matrix4, Vector2, Vector3, Vector4 } from 'three'; + +class Uniform { + constructor(name, value) { + this.name = name; + this.value = value; + + this.boundary = 0; // used to build the uniform buffer according to the STD140 layout + this.itemSize = 0; + + this.offset = 0; // this property is set by WebGPUUniformsGroup and marks the start position in the uniform buffer + } + + setValue(value) { + this.value = value; + } + + getValue() { + return this.value; + } +} + +class NumberUniform extends Uniform { + constructor(name, value = 0) { + super(name, value); + + this.isNumberUniform = true; + + this.boundary = 4; + this.itemSize = 1; + } +} + +class Vector2Uniform extends Uniform { + constructor(name, value = new Vector2()) { + super(name, value); + + this.isVector2Uniform = true; + + this.boundary = 8; + this.itemSize = 2; + } +} + +class Vector3Uniform extends Uniform { + constructor(name, value = new Vector3()) { + super(name, value); + + this.isVector3Uniform = true; + + this.boundary = 16; + this.itemSize = 3; + } +} + +class Vector4Uniform extends Uniform { + constructor(name, value = new Vector4()) { + super(name, value); + + this.isVector4Uniform = true; + + this.boundary = 16; + this.itemSize = 4; + } +} + +class ColorUniform extends Uniform { + constructor(name, value = new Color()) { + super(name, value); + + this.isColorUniform = true; + + this.boundary = 16; + this.itemSize = 3; + } +} + +class Matrix3Uniform extends Uniform { + constructor(name, value = new Matrix3()) { + super(name, value); + + this.isMatrix3Uniform = true; + + this.boundary = 48; + this.itemSize = 12; + } +} + +class Matrix4Uniform extends Uniform { + constructor(name, value = new Matrix4()) { + super(name, value); + + this.isMatrix4Uniform = true; + + this.boundary = 64; + this.itemSize = 16; + } +} + +export { NumberUniform, Vector2Uniform, Vector3Uniform, Vector4Uniform, ColorUniform, Matrix3Uniform, Matrix4Uniform }; diff --git a/examples-jsm/examples/renderers/common/UniformBuffer.ts b/examples-jsm/examples/renderers/common/UniformBuffer.ts new file mode 100644 index 00000000..28aac0d7 --- /dev/null +++ b/examples-jsm/examples/renderers/common/UniformBuffer.ts @@ -0,0 +1,11 @@ +import Buffer from './Buffer.js'; + +class UniformBuffer extends Buffer { + constructor(name, buffer = null) { + super(name, buffer); + + this.isUniformBuffer = true; + } +} + +export default UniformBuffer; diff --git a/examples-jsm/examples/renderers/common/UniformsGroup.ts b/examples-jsm/examples/renderers/common/UniformsGroup.ts new file mode 100644 index 00000000..e2b62671 --- /dev/null +++ b/examples-jsm/examples/renderers/common/UniformsGroup.ts @@ -0,0 +1,277 @@ +import UniformBuffer from './UniformBuffer.js'; +import { GPU_CHUNK_BYTES } from './Constants.js'; + +class UniformsGroup extends UniformBuffer { + constructor(name) { + super(name); + + this.isUniformsGroup = true; + + this._values = null; + + // the order of uniforms in this array must match the order of uniforms in the shader + + this.uniforms = []; + } + + addUniform(uniform) { + this.uniforms.push(uniform); + + return this; + } + + removeUniform(uniform) { + const index = this.uniforms.indexOf(uniform); + + if (index !== -1) { + this.uniforms.splice(index, 1); + } + + return this; + } + + get values() { + if (this._values === null) { + this._values = Array.from(this.buffer); + } + + return this._values; + } + + get buffer() { + let buffer = this._buffer; + + if (buffer === null) { + const byteLength = this.byteLength; + + buffer = new Float32Array(new ArrayBuffer(byteLength)); + + this._buffer = buffer; + } + + return buffer; + } + + get byteLength() { + let offset = 0; // global buffer offset in bytes + + for (let i = 0, l = this.uniforms.length; i < l; i++) { + const uniform = this.uniforms[i]; + + const { boundary, itemSize } = uniform; + + // offset within a single chunk in bytes + + const chunkOffset = offset % GPU_CHUNK_BYTES; + const remainingSizeInChunk = GPU_CHUNK_BYTES - chunkOffset; + + // conformance tests + + if (chunkOffset !== 0 && remainingSizeInChunk - boundary < 0) { + // check for chunk overflow + + offset += GPU_CHUNK_BYTES - chunkOffset; + } else if (chunkOffset % boundary !== 0) { + // check for correct alignment + + offset += chunkOffset % boundary; + } + + uniform.offset = offset / this.bytesPerElement; + + offset += itemSize * this.bytesPerElement; + } + + return Math.ceil(offset / GPU_CHUNK_BYTES) * GPU_CHUNK_BYTES; + } + + update() { + let updated = false; + + for (const uniform of this.uniforms) { + if (this.updateByType(uniform) === true) { + updated = true; + } + } + + return updated; + } + + updateByType(uniform) { + if (uniform.isNumberUniform) return this.updateNumber(uniform); + if (uniform.isVector2Uniform) return this.updateVector2(uniform); + if (uniform.isVector3Uniform) return this.updateVector3(uniform); + if (uniform.isVector4Uniform) return this.updateVector4(uniform); + if (uniform.isColorUniform) return this.updateColor(uniform); + if (uniform.isMatrix3Uniform) return this.updateMatrix3(uniform); + if (uniform.isMatrix4Uniform) return this.updateMatrix4(uniform); + + console.error('THREE.WebGPUUniformsGroup: Unsupported uniform type.', uniform); + } + + updateNumber(uniform) { + let updated = false; + + const a = this.values; + const v = uniform.getValue(); + const offset = uniform.offset; + + if (a[offset] !== v) { + const b = this.buffer; + + b[offset] = a[offset] = v; + updated = true; + } + + return updated; + } + + updateVector2(uniform) { + let updated = false; + + const a = this.values; + const v = uniform.getValue(); + const offset = uniform.offset; + + if (a[offset + 0] !== v.x || a[offset + 1] !== v.y) { + const b = this.buffer; + + b[offset + 0] = a[offset + 0] = v.x; + b[offset + 1] = a[offset + 1] = v.y; + + updated = true; + } + + return updated; + } + + updateVector3(uniform) { + let updated = false; + + const a = this.values; + const v = uniform.getValue(); + const offset = uniform.offset; + + if (a[offset + 0] !== v.x || a[offset + 1] !== v.y || a[offset + 2] !== v.z) { + const b = this.buffer; + + b[offset + 0] = a[offset + 0] = v.x; + b[offset + 1] = a[offset + 1] = v.y; + b[offset + 2] = a[offset + 2] = v.z; + + updated = true; + } + + return updated; + } + + updateVector4(uniform) { + let updated = false; + + const a = this.values; + const v = uniform.getValue(); + const offset = uniform.offset; + + if (a[offset + 0] !== v.x || a[offset + 1] !== v.y || a[offset + 2] !== v.z || a[offset + 4] !== v.w) { + const b = this.buffer; + + b[offset + 0] = a[offset + 0] = v.x; + b[offset + 1] = a[offset + 1] = v.y; + b[offset + 2] = a[offset + 2] = v.z; + b[offset + 3] = a[offset + 3] = v.w; + + updated = true; + } + + return updated; + } + + updateColor(uniform) { + let updated = false; + + const a = this.values; + const c = uniform.getValue(); + const offset = uniform.offset; + + if (a[offset + 0] !== c.r || a[offset + 1] !== c.g || a[offset + 2] !== c.b) { + const b = this.buffer; + + b[offset + 0] = a[offset + 0] = c.r; + b[offset + 1] = a[offset + 1] = c.g; + b[offset + 2] = a[offset + 2] = c.b; + + updated = true; + } + + return updated; + } + + updateMatrix3(uniform) { + let updated = false; + + const a = this.values; + const e = uniform.getValue().elements; + const offset = uniform.offset; + + if ( + a[offset + 0] !== e[0] || + a[offset + 1] !== e[1] || + a[offset + 2] !== e[2] || + a[offset + 4] !== e[3] || + a[offset + 5] !== e[4] || + a[offset + 6] !== e[5] || + a[offset + 8] !== e[6] || + a[offset + 9] !== e[7] || + a[offset + 10] !== e[8] + ) { + const b = this.buffer; + + b[offset + 0] = a[offset + 0] = e[0]; + b[offset + 1] = a[offset + 1] = e[1]; + b[offset + 2] = a[offset + 2] = e[2]; + b[offset + 4] = a[offset + 4] = e[3]; + b[offset + 5] = a[offset + 5] = e[4]; + b[offset + 6] = a[offset + 6] = e[5]; + b[offset + 8] = a[offset + 8] = e[6]; + b[offset + 9] = a[offset + 9] = e[7]; + b[offset + 10] = a[offset + 10] = e[8]; + + updated = true; + } + + return updated; + } + + updateMatrix4(uniform) { + let updated = false; + + const a = this.values; + const e = uniform.getValue().elements; + const offset = uniform.offset; + + if (arraysEqual(a, e, offset) === false) { + const b = this.buffer; + b.set(e, offset); + setArray(a, e, offset); + updated = true; + } + + return updated; + } +} + +function setArray(a, b, offset) { + for (let i = 0, l = b.length; i < l; i++) { + a[offset + i] = b[i]; + } +} + +function arraysEqual(a, b, offset) { + for (let i = 0, l = b.length; i < l; i++) { + if (a[offset + i] !== b[i]) return false; + } + + return true; +} + +export default UniformsGroup; diff --git a/examples-jsm/examples/renderers/common/extras/PMREMGenerator.ts b/examples-jsm/examples/renderers/common/extras/PMREMGenerator.ts new file mode 100644 index 00000000..b0aa2f8e --- /dev/null +++ b/examples-jsm/examples/renderers/common/extras/PMREMGenerator.ts @@ -0,0 +1,659 @@ +import NodeMaterial from '../../../nodes/materials/NodeMaterial.js'; +import { getDirection, blur } from '../../../nodes/pmrem/PMREMUtils.js'; +import { equirectUV } from '../../../nodes/utils/EquirectUVNode.js'; +import { uniform } from '../../../nodes/core/UniformNode.js'; +import { uniforms } from '../../../nodes/accessors/UniformsNode.js'; +import { texture } from '../../../nodes/accessors/TextureNode.js'; +import { cubeTexture } from '../../../nodes/accessors/CubeTextureNode.js'; +import { float, vec3 } from '../../../nodes/shadernode/ShaderNode.js'; +import { uv } from '../../../nodes/accessors/UVNode.js'; +import { attribute } from '../../../nodes/core/AttributeNode.js'; +import { + OrthographicCamera, + Color, + Vector3, + BufferGeometry, + BufferAttribute, + RenderTarget, + Mesh, + CubeReflectionMapping, + CubeRefractionMapping, + CubeUVReflectionMapping, + LinearFilter, + NoBlending, + RGBAFormat, + HalfFloatType, + BackSide, + LinearSRGBColorSpace, + PerspectiveCamera, + MeshBasicMaterial, + BoxGeometry, +} from 'three'; + +const LOD_MIN = 4; + +// The standard deviations (radians) associated with the extra mips. These are +// chosen to approximate a Trowbridge-Reitz distribution function times the +// geometric shadowing function. These sigma values squared must match the +// variance #defines in cube_uv_reflection_fragment.glsl.js. +const EXTRA_LOD_SIGMA = [0.125, 0.215, 0.35, 0.446, 0.526, 0.582]; + +// The maximum length of the blur for loop. Smaller sigmas will use fewer +// samples and exit early, but not recompile the shader. +const MAX_SAMPLES = 20; + +const _flatCamera = /*@__PURE__*/ new OrthographicCamera(-1, 1, 1, -1, 0, 1); +const _cubeCamera = /*@__PURE__*/ new PerspectiveCamera(90, 1); +const _clearColor = /*@__PURE__*/ new Color(); +let _oldTarget = null; +let _oldActiveCubeFace = 0; +let _oldActiveMipmapLevel = 0; + +// Golden Ratio +const PHI = (1 + Math.sqrt(5)) / 2; +const INV_PHI = 1 / PHI; + +// Vertices of a dodecahedron (except the opposites, which represent the +// same axis), used as axis directions evenly spread on a sphere. +const _axisDirections = [ + /*@__PURE__*/ new Vector3(-PHI, INV_PHI, 0), + /*@__PURE__*/ new Vector3(PHI, INV_PHI, 0), + /*@__PURE__*/ new Vector3(-INV_PHI, 0, PHI), + /*@__PURE__*/ new Vector3(INV_PHI, 0, PHI), + /*@__PURE__*/ new Vector3(0, PHI, -INV_PHI), + /*@__PURE__*/ new Vector3(0, PHI, INV_PHI), + /*@__PURE__*/ new Vector3(-1, 1, -1), + /*@__PURE__*/ new Vector3(1, 1, -1), + /*@__PURE__*/ new Vector3(-1, 1, 1), + /*@__PURE__*/ new Vector3(1, 1, 1), +]; + +// + +// WebGPU Face indices +const _faceLib = [3, 1, 5, 0, 4, 2]; + +const direction = getDirection(uv(), attribute('faceIndex')).normalize(); +const outputDirection = vec3(direction.x, direction.y.negate(), direction.z); + +/** + * This class generates a Prefiltered, Mipmapped Radiance Environment Map + * (PMREM) from a cubeMap environment texture. This allows different levels of + * blur to be quickly accessed based on material roughness. It is packed into a + * special CubeUV format that allows us to perform custom interpolation so that + * we can support nonlinear formats such as RGBE. Unlike a traditional mipmap + * chain, it only goes down to the LOD_MIN level (above), and then creates extra + * even more filtered 'mips' at the same LOD_MIN resolution, associated with + * higher roughness levels. In this way we maintain resolution to smoothly + * interpolate diffuse lighting while limiting sampling computation. + * + * Paper: Fast, Accurate Image-Based Lighting + * https://drive.google.com/file/d/15y8r_UpKlU9SvV4ILb0C3qCPecS8pvLz/view + */ + +class PMREMGenerator { + constructor(renderer) { + this._renderer = renderer; + this._pingPongRenderTarget = null; + + this._lodMax = 0; + this._cubeSize = 0; + this._lodPlanes = []; + this._sizeLods = []; + this._sigmas = []; + this._lodMeshes = []; + + this._blurMaterial = null; + this._cubemapMaterial = null; + this._equirectMaterial = null; + this._backgroundBox = null; + } + + /** + * Generates a PMREM from a supplied Scene, which can be faster than using an + * image if networking bandwidth is low. Optional sigma specifies a blur radius + * in radians to be applied to the scene before PMREM generation. Optional near + * and far planes ensure the scene is rendered in its entirety (the cubeCamera + * is placed at the origin). + */ + fromScene(scene, sigma = 0, near = 0.1, far = 100) { + _oldTarget = this._renderer.getRenderTarget(); + _oldActiveCubeFace = this._renderer.getActiveCubeFace(); + _oldActiveMipmapLevel = this._renderer.getActiveMipmapLevel(); + + this._setSize(256); + + const cubeUVRenderTarget = this._allocateTargets(); + cubeUVRenderTarget.depthBuffer = true; + + this._sceneToCubeUV(scene, near, far, cubeUVRenderTarget); + + if (sigma > 0) { + this._blur(cubeUVRenderTarget, 0, 0, sigma); + } + + this._applyPMREM(cubeUVRenderTarget); + + this._cleanup(cubeUVRenderTarget); + + return cubeUVRenderTarget; + } + + /** + * Generates a PMREM from an equirectangular texture, which can be either LDR + * or HDR. The ideal input image size is 1k (1024 x 512), + * as this matches best with the 256 x 256 cubemap output. + */ + fromEquirectangular(equirectangular, renderTarget = null) { + return this._fromTexture(equirectangular, renderTarget); + } + + /** + * Generates a PMREM from an cubemap texture, which can be either LDR + * or HDR. The ideal input cube size is 256 x 256, + * as this matches best with the 256 x 256 cubemap output. + */ + fromCubemap(cubemap, renderTarget = null) { + return this._fromTexture(cubemap, renderTarget); + } + + /** + * Pre-compiles the cubemap shader. You can get faster start-up by invoking this method during + * your texture's network fetch for increased concurrency. + */ + compileCubemapShader() { + if (this._cubemapMaterial === null) { + this._cubemapMaterial = _getCubemapMaterial(); + this._compileMaterial(this._cubemapMaterial); + } + } + + /** + * Pre-compiles the equirectangular shader. You can get faster start-up by invoking this method during + * your texture's network fetch for increased concurrency. + */ + compileEquirectangularShader() { + if (this._equirectMaterial === null) { + this._equirectMaterial = _getEquirectMaterial(); + this._compileMaterial(this._equirectMaterial); + } + } + + /** + * Disposes of the PMREMGenerator's internal memory. Note that PMREMGenerator is a static class, + * so you should not need more than one PMREMGenerator object. If you do, calling dispose() on + * one of them will cause any others to also become unusable. + */ + dispose() { + this._dispose(); + + if (this._cubemapMaterial !== null) this._cubemapMaterial.dispose(); + if (this._equirectMaterial !== null) this._equirectMaterial.dispose(); + if (this._backgroundBox !== null) { + this._backgroundBox.geometry.dispose(); + this._backgroundBox.material.dispose(); + } + } + + // private interface + + _setSize(cubeSize) { + this._lodMax = Math.floor(Math.log2(cubeSize)); + this._cubeSize = Math.pow(2, this._lodMax); + } + + _dispose() { + if (this._blurMaterial !== null) this._blurMaterial.dispose(); + + if (this._pingPongRenderTarget !== null) this._pingPongRenderTarget.dispose(); + + for (let i = 0; i < this._lodPlanes.length; i++) { + this._lodPlanes[i].dispose(); + } + } + + _cleanup(outputTarget) { + this._renderer.setRenderTarget(_oldTarget, _oldActiveCubeFace, _oldActiveMipmapLevel); + outputTarget.scissorTest = false; + _setViewport(outputTarget, 0, 0, outputTarget.width, outputTarget.height); + } + + _fromTexture(texture, renderTarget) { + if (texture.mapping === CubeReflectionMapping || texture.mapping === CubeRefractionMapping) { + this._setSize(texture.image.length === 0 ? 16 : texture.image[0].width || texture.image[0].image.width); + } else { + // Equirectangular + + this._setSize(texture.image.width / 4); + } + + _oldTarget = this._renderer.getRenderTarget(); + _oldActiveCubeFace = this._renderer.getActiveCubeFace(); + _oldActiveMipmapLevel = this._renderer.getActiveMipmapLevel(); + + const cubeUVRenderTarget = renderTarget || this._allocateTargets(); + this._textureToCubeUV(texture, cubeUVRenderTarget); + this._applyPMREM(cubeUVRenderTarget); + this._cleanup(cubeUVRenderTarget); + + return cubeUVRenderTarget; + } + + _allocateTargets() { + const width = 3 * Math.max(this._cubeSize, 16 * 7); + const height = 4 * this._cubeSize; + + const params = { + magFilter: LinearFilter, + minFilter: LinearFilter, + generateMipmaps: false, + type: HalfFloatType, + format: RGBAFormat, + colorSpace: LinearSRGBColorSpace, + //depthBuffer: false + }; + + const cubeUVRenderTarget = _createRenderTarget(width, height, params); + + if ( + this._pingPongRenderTarget === null || + this._pingPongRenderTarget.width !== width || + this._pingPongRenderTarget.height !== height + ) { + if (this._pingPongRenderTarget !== null) { + this._dispose(); + } + + this._pingPongRenderTarget = _createRenderTarget(width, height, params); + + const { _lodMax } = this; + ({ + sizeLods: this._sizeLods, + lodPlanes: this._lodPlanes, + sigmas: this._sigmas, + lodMeshes: this._lodMeshes, + } = _createPlanes(_lodMax)); + + this._blurMaterial = _getBlurShader(_lodMax, width, height); + } + + return cubeUVRenderTarget; + } + + _compileMaterial(material) { + const tmpMesh = this._lodMeshes[0]; + tmpMesh.material = material; + + this._renderer.compile(tmpMesh, _flatCamera); + } + + _sceneToCubeUV(scene, near, far, cubeUVRenderTarget) { + const cubeCamera = _cubeCamera; + cubeCamera.near = near; + cubeCamera.far = far; + + // px, py, pz, nx, ny, nz + const upSign = [-1, 1, -1, -1, -1, -1]; + const forwardSign = [1, 1, 1, -1, -1, -1]; + + const renderer = this._renderer; + + const originalAutoClear = renderer.autoClear; + + renderer.getClearColor(_clearColor); + + renderer.autoClear = false; + + let backgroundBox = this._backgroundBox; + + if (backgroundBox === null) { + const backgroundMaterial = new MeshBasicMaterial({ + name: 'PMREM.Background', + side: BackSide, + depthWrite: false, + depthTest: false, + }); + + backgroundBox = new Mesh(new BoxGeometry(), backgroundMaterial); + } + + let useSolidColor = false; + const background = scene.background; + + if (background) { + if (background.isColor) { + backgroundBox.material.color.copy(background); + scene.background = null; + useSolidColor = true; + } + } else { + backgroundBox.material.color.copy(_clearColor); + useSolidColor = true; + } + + renderer.setRenderTarget(cubeUVRenderTarget); + + renderer.clear(); + + if (useSolidColor) { + renderer.render(backgroundBox, cubeCamera); + } + + for (let i = 0; i < 6; i++) { + const col = i % 3; + + if (col === 0) { + cubeCamera.up.set(0, upSign[i], 0); + cubeCamera.lookAt(forwardSign[i], 0, 0); + } else if (col === 1) { + cubeCamera.up.set(0, 0, upSign[i]); + cubeCamera.lookAt(0, forwardSign[i], 0); + } else { + cubeCamera.up.set(0, upSign[i], 0); + cubeCamera.lookAt(0, 0, forwardSign[i]); + } + + const size = this._cubeSize; + + _setViewport(cubeUVRenderTarget, col * size, i > 2 ? size : 0, size, size); + + renderer.render(scene, cubeCamera); + } + + renderer.autoClear = originalAutoClear; + scene.background = background; + } + + _textureToCubeUV(texture, cubeUVRenderTarget) { + const renderer = this._renderer; + + const isCubeTexture = texture.mapping === CubeReflectionMapping || texture.mapping === CubeRefractionMapping; + + if (isCubeTexture) { + if (this._cubemapMaterial === null) { + this._cubemapMaterial = _getCubemapMaterial(texture); + } + } else { + if (this._equirectMaterial === null) { + this._equirectMaterial = _getEquirectMaterial(texture); + } + } + + const material = isCubeTexture ? this._cubemapMaterial : this._equirectMaterial; + material.fragmentNode.value = texture; + + const mesh = this._lodMeshes[0]; + mesh.material = material; + + const size = this._cubeSize; + + _setViewport(cubeUVRenderTarget, 0, 0, 3 * size, 2 * size); + + renderer.setRenderTarget(cubeUVRenderTarget); + renderer.render(mesh, _flatCamera); + } + + _applyPMREM(cubeUVRenderTarget) { + const renderer = this._renderer; + const autoClear = renderer.autoClear; + renderer.autoClear = false; + const n = this._lodPlanes.length; + + for (let i = 1; i < n; i++) { + const sigma = Math.sqrt(this._sigmas[i] * this._sigmas[i] - this._sigmas[i - 1] * this._sigmas[i - 1]); + + const poleAxis = _axisDirections[(n - i - 1) % _axisDirections.length]; + + this._blur(cubeUVRenderTarget, i - 1, i, sigma, poleAxis); + } + + renderer.autoClear = autoClear; + } + + /** + * This is a two-pass Gaussian blur for a cubemap. Normally this is done + * vertically and horizontally, but this breaks down on a cube. Here we apply + * the blur latitudinally (around the poles), and then longitudinally (towards + * the poles) to approximate the orthogonally-separable blur. It is least + * accurate at the poles, but still does a decent job. + */ + _blur(cubeUVRenderTarget, lodIn, lodOut, sigma, poleAxis) { + const pingPongRenderTarget = this._pingPongRenderTarget; + + this._halfBlur(cubeUVRenderTarget, pingPongRenderTarget, lodIn, lodOut, sigma, 'latitudinal', poleAxis); + + this._halfBlur(pingPongRenderTarget, cubeUVRenderTarget, lodOut, lodOut, sigma, 'longitudinal', poleAxis); + } + + _halfBlur(targetIn, targetOut, lodIn, lodOut, sigmaRadians, direction, poleAxis) { + const renderer = this._renderer; + const blurMaterial = this._blurMaterial; + + if (direction !== 'latitudinal' && direction !== 'longitudinal') { + console.error('blur direction must be either latitudinal or longitudinal!'); + } + + // Number of standard deviations at which to cut off the discrete approximation. + const STANDARD_DEVIATIONS = 3; + + const blurMesh = this._lodMeshes[lodOut]; + blurMesh.material = blurMaterial; + + const blurUniforms = blurMaterial.uniforms; + + const pixels = this._sizeLods[lodIn] - 1; + const radiansPerPixel = isFinite(sigmaRadians) ? Math.PI / (2 * pixels) : (2 * Math.PI) / (2 * MAX_SAMPLES - 1); + const sigmaPixels = sigmaRadians / radiansPerPixel; + const samples = isFinite(sigmaRadians) ? 1 + Math.floor(STANDARD_DEVIATIONS * sigmaPixels) : MAX_SAMPLES; + + if (samples > MAX_SAMPLES) { + console.warn( + `sigmaRadians, ${sigmaRadians}, is too large and will clip, as it requested ${ + samples + } samples when the maximum is set to ${MAX_SAMPLES}`, + ); + } + + const weights = []; + let sum = 0; + + for (let i = 0; i < MAX_SAMPLES; ++i) { + const x = i / sigmaPixels; + const weight = Math.exp((-x * x) / 2); + weights.push(weight); + + if (i === 0) { + sum += weight; + } else if (i < samples) { + sum += 2 * weight; + } + } + + for (let i = 0; i < weights.length; i++) { + weights[i] = weights[i] / sum; + } + + targetIn.texture.frame = (targetIn.texture.frame || 0) + 1; + + blurUniforms.envMap.value = targetIn.texture; + blurUniforms.samples.value = samples; + blurUniforms.weights.array = weights; + blurUniforms.latitudinal.value = direction === 'latitudinal' ? 1 : 0; + + if (poleAxis) { + blurUniforms.poleAxis.value = poleAxis; + } + + const { _lodMax } = this; + blurUniforms.dTheta.value = radiansPerPixel; + blurUniforms.mipInt.value = _lodMax - lodIn; + + const outputSize = this._sizeLods[lodOut]; + const x = 3 * outputSize * (lodOut > _lodMax - LOD_MIN ? lodOut - _lodMax + LOD_MIN : 0); + const y = 4 * (this._cubeSize - outputSize); + + _setViewport(targetOut, x, y, 3 * outputSize, 2 * outputSize); + renderer.setRenderTarget(targetOut); + renderer.render(blurMesh, _flatCamera); + } +} + +function _createPlanes(lodMax) { + const lodPlanes = []; + const sizeLods = []; + const sigmas = []; + const lodMeshes = []; + + let lod = lodMax; + + const totalLods = lodMax - LOD_MIN + 1 + EXTRA_LOD_SIGMA.length; + + for (let i = 0; i < totalLods; i++) { + const sizeLod = Math.pow(2, lod); + sizeLods.push(sizeLod); + let sigma = 1.0 / sizeLod; + + if (i > lodMax - LOD_MIN) { + sigma = EXTRA_LOD_SIGMA[i - lodMax + LOD_MIN - 1]; + } else if (i === 0) { + sigma = 0; + } + + sigmas.push(sigma); + + const texelSize = 1.0 / (sizeLod - 2); + const min = -texelSize; + const max = 1 + texelSize; + const uv1 = [min, min, max, min, max, max, min, min, max, max, min, max]; + + const cubeFaces = 6; + const vertices = 6; + const positionSize = 3; + const uvSize = 2; + const faceIndexSize = 1; + + const position = new Float32Array(positionSize * vertices * cubeFaces); + const uv = new Float32Array(uvSize * vertices * cubeFaces); + const faceIndex = new Float32Array(faceIndexSize * vertices * cubeFaces); + + for (let face = 0; face < cubeFaces; face++) { + const x = ((face % 3) * 2) / 3 - 1; + const y = face > 2 ? 0 : -1; + const coordinates = [ + x, + y, + 0, + x + 2 / 3, + y, + 0, + x + 2 / 3, + y + 1, + 0, + x, + y, + 0, + x + 2 / 3, + y + 1, + 0, + x, + y + 1, + 0, + ]; + + const faceIdx = _faceLib[face]; + position.set(coordinates, positionSize * vertices * faceIdx); + uv.set(uv1, uvSize * vertices * faceIdx); + const fill = [faceIdx, faceIdx, faceIdx, faceIdx, faceIdx, faceIdx]; + faceIndex.set(fill, faceIndexSize * vertices * faceIdx); + } + + const planes = new BufferGeometry(); + planes.setAttribute('position', new BufferAttribute(position, positionSize)); + planes.setAttribute('uv', new BufferAttribute(uv, uvSize)); + planes.setAttribute('faceIndex', new BufferAttribute(faceIndex, faceIndexSize)); + lodPlanes.push(planes); + lodMeshes.push(new Mesh(planes, null)); + + if (lod > LOD_MIN) { + lod--; + } + } + + return { lodPlanes, sizeLods, sigmas, lodMeshes }; +} + +function _createRenderTarget(width, height, params) { + const cubeUVRenderTarget = new RenderTarget(width, height, params); + cubeUVRenderTarget.texture.mapping = CubeUVReflectionMapping; + cubeUVRenderTarget.texture.name = 'PMREM.cubeUv'; + cubeUVRenderTarget.texture.isPMREMTexture = true; + cubeUVRenderTarget.scissorTest = true; + return cubeUVRenderTarget; +} + +function _setViewport(target, x, y, width, height) { + const viewY = target.height - height - y; + + target.viewport.set(x, viewY, width, height); + target.scissor.set(x, viewY, width, height); +} + +function _getMaterial() { + const material = new NodeMaterial(); + material.depthTest = false; + material.depthWrite = false; + material.blending = NoBlending; + + return material; +} + +function _getBlurShader(lodMax, width, height) { + const weights = uniforms(new Array(MAX_SAMPLES).fill(0)); + const poleAxis = uniform(new Vector3(0, 1, 0)); + const dTheta = uniform(0); + const n = float(MAX_SAMPLES); + const latitudinal = uniform(0); // false, bool + const samples = uniform(1); // int + const envMap = texture(null); + const mipInt = uniform(0); // int + const CUBEUV_TEXEL_WIDTH = float(1 / width); + const CUBEUV_TEXEL_HEIGHT = float(1 / height); + const CUBEUV_MAX_MIP = float(lodMax); + + const materialUniforms = { + n, + latitudinal, + weights, + poleAxis, + outputDirection, + dTheta, + samples, + envMap, + mipInt, + CUBEUV_TEXEL_WIDTH, + CUBEUV_TEXEL_HEIGHT, + CUBEUV_MAX_MIP, + }; + + const material = _getMaterial(); + material.uniforms = materialUniforms; // TODO: Move to outside of the material + material.fragmentNode = blur({ ...materialUniforms, latitudinal: latitudinal.equal(1) }); + + return material; +} + +function _getCubemapMaterial(envTexture) { + const material = _getMaterial(); + material.fragmentNode = cubeTexture(envTexture, outputDirection); + + return material; +} + +function _getEquirectMaterial(envTexture) { + const material = _getMaterial(); + material.fragmentNode = texture(envTexture, equirectUV(outputDirection), 0); + + return material; +} + +export default PMREMGenerator; diff --git a/examples-jsm/examples/renderers/common/nodes/NodeBuilderState.ts b/examples-jsm/examples/renderers/common/nodes/NodeBuilderState.ts new file mode 100644 index 00000000..e94d965f --- /dev/null +++ b/examples-jsm/examples/renderers/common/nodes/NodeBuilderState.ts @@ -0,0 +1,55 @@ +import BindGroup from '../BindGroup.js'; + +class NodeBuilderState { + constructor( + vertexShader, + fragmentShader, + computeShader, + nodeAttributes, + bindings, + updateNodes, + updateBeforeNodes, + updateAfterNodes, + instanceBindGroups = true, + transforms = [], + ) { + this.vertexShader = vertexShader; + this.fragmentShader = fragmentShader; + this.computeShader = computeShader; + this.transforms = transforms; + + this.nodeAttributes = nodeAttributes; + this.bindings = bindings; + + this.updateNodes = updateNodes; + this.updateBeforeNodes = updateBeforeNodes; + this.updateAfterNodes = updateAfterNodes; + + this.instanceBindGroups = instanceBindGroups; + + this.usedTimes = 0; + } + + createBindings() { + const bindings = []; + + for (const instanceGroup of this.bindings) { + const shared = this.instanceBindGroups && instanceGroup.bindings[0].groupNode.shared; + + if (shared !== true) { + const bindingsGroup = new BindGroup(instanceGroup.name); + bindings.push(bindingsGroup); + + for (const instanceBinding of instanceGroup.bindings) { + bindingsGroup.bindings.push(instanceBinding.clone()); + } + } else { + bindings.push(instanceGroup); + } + } + + return bindings; + } +} + +export default NodeBuilderState; diff --git a/examples-jsm/examples/renderers/common/nodes/NodeUniform.ts b/examples-jsm/examples/renderers/common/nodes/NodeUniform.ts new file mode 100644 index 00000000..659f5a82 --- /dev/null +++ b/examples-jsm/examples/renderers/common/nodes/NodeUniform.ts @@ -0,0 +1,103 @@ +import { + NumberUniform, + Vector2Uniform, + Vector3Uniform, + Vector4Uniform, + ColorUniform, + Matrix3Uniform, + Matrix4Uniform, +} from '../Uniform.js'; + +class NumberNodeUniform extends NumberUniform { + constructor(nodeUniform) { + super(nodeUniform.name, nodeUniform.value); + + this.nodeUniform = nodeUniform; + } + + getValue() { + return this.nodeUniform.value; + } +} + +class Vector2NodeUniform extends Vector2Uniform { + constructor(nodeUniform) { + super(nodeUniform.name, nodeUniform.value); + + this.nodeUniform = nodeUniform; + } + + getValue() { + return this.nodeUniform.value; + } +} + +class Vector3NodeUniform extends Vector3Uniform { + constructor(nodeUniform) { + super(nodeUniform.name, nodeUniform.value); + + this.nodeUniform = nodeUniform; + } + + getValue() { + return this.nodeUniform.value; + } +} + +class Vector4NodeUniform extends Vector4Uniform { + constructor(nodeUniform) { + super(nodeUniform.name, nodeUniform.value); + + this.nodeUniform = nodeUniform; + } + + getValue() { + return this.nodeUniform.value; + } +} + +class ColorNodeUniform extends ColorUniform { + constructor(nodeUniform) { + super(nodeUniform.name, nodeUniform.value); + + this.nodeUniform = nodeUniform; + } + + getValue() { + return this.nodeUniform.value; + } +} + +class Matrix3NodeUniform extends Matrix3Uniform { + constructor(nodeUniform) { + super(nodeUniform.name, nodeUniform.value); + + this.nodeUniform = nodeUniform; + } + + getValue() { + return this.nodeUniform.value; + } +} + +class Matrix4NodeUniform extends Matrix4Uniform { + constructor(nodeUniform) { + super(nodeUniform.name, nodeUniform.value); + + this.nodeUniform = nodeUniform; + } + + getValue() { + return this.nodeUniform.value; + } +} + +export { + NumberNodeUniform, + Vector2NodeUniform, + Vector3NodeUniform, + Vector4NodeUniform, + ColorNodeUniform, + Matrix3NodeUniform, + Matrix4NodeUniform, +}; diff --git a/examples-jsm/examples/renderers/common/nodes/NodeUniformsGroup.ts b/examples-jsm/examples/renderers/common/nodes/NodeUniformsGroup.ts new file mode 100644 index 00000000..e7b389cd --- /dev/null +++ b/examples-jsm/examples/renderers/common/nodes/NodeUniformsGroup.ts @@ -0,0 +1,30 @@ +import UniformsGroup from '../UniformsGroup.js'; + +let id = 0; + +class NodeUniformsGroup extends UniformsGroup { + constructor(name, groupNode) { + super(name); + + this.id = id++; + this.groupNode = groupNode; + + this.isNodeUniformsGroup = true; + } + + getNodes() { + const nodes = []; + + for (const uniform of this.uniforms) { + const node = uniform.nodeUniform.node; + + if (!node) throw new Error('NodeUniformsGroup: Uniform has no node.'); + + nodes.push(node); + } + + return nodes; + } +} + +export default NodeUniformsGroup; diff --git a/examples-jsm/examples/renderers/common/nodes/Nodes.ts b/examples-jsm/examples/renderers/common/nodes/Nodes.ts new file mode 100644 index 00000000..db035f6d --- /dev/null +++ b/examples-jsm/examples/renderers/common/nodes/Nodes.ts @@ -0,0 +1,394 @@ +import DataMap from '../DataMap.js'; +import ChainMap from '../ChainMap.js'; +import NodeBuilderState from './NodeBuilderState.js'; +import { + EquirectangularReflectionMapping, + EquirectangularRefractionMapping, + NoToneMapping, + SRGBColorSpace, +} from 'three'; +import { + NodeFrame, + vec4, + objectGroup, + renderGroup, + frameGroup, + cubeTexture, + texture, + rangeFog, + densityFog, + reference, + viewportBottomLeft, + normalWorld, + pmremTexture, + viewportTopLeft, +} from '../../../nodes/Nodes.js'; + +class Nodes extends DataMap { + constructor(renderer, backend) { + super(); + + this.renderer = renderer; + this.backend = backend; + this.nodeFrame = new NodeFrame(); + this.nodeBuilderCache = new Map(); + this.callHashCache = new ChainMap(); + this.groupsData = new ChainMap(); + } + + updateGroup(nodeUniformsGroup) { + const groupNode = nodeUniformsGroup.groupNode; + const name = groupNode.name; + + // objectGroup is every updated + + if (name === objectGroup.name) return true; + + // renderGroup is updated once per render/compute call + + if (name === renderGroup.name) { + const uniformsGroupData = this.get(nodeUniformsGroup); + const renderId = this.nodeFrame.renderId; + + if (uniformsGroupData.renderId !== renderId) { + uniformsGroupData.renderId = renderId; + + return true; + } + + return false; + } + + // frameGroup is updated once per frame + + if (name === frameGroup.name) { + const uniformsGroupData = this.get(nodeUniformsGroup); + const frameId = this.nodeFrame.frameId; + + if (uniformsGroupData.frameId !== frameId) { + uniformsGroupData.frameId = frameId; + + return true; + } + + return false; + } + + // other groups are updated just when groupNode.needsUpdate is true + + const groupChain = [groupNode, nodeUniformsGroup]; + + let groupData = this.groupsData.get(groupChain); + if (groupData === undefined) this.groupsData.set(groupChain, (groupData = {})); + + if (groupData.version !== groupNode.version) { + groupData.version = groupNode.version; + + return true; + } + + return false; + } + + getForRenderCacheKey(renderObject) { + return renderObject.initialCacheKey; + } + + getForRender(renderObject) { + const renderObjectData = this.get(renderObject); + + let nodeBuilderState = renderObjectData.nodeBuilderState; + + if (nodeBuilderState === undefined) { + const { nodeBuilderCache } = this; + + const cacheKey = this.getForRenderCacheKey(renderObject); + + nodeBuilderState = nodeBuilderCache.get(cacheKey); + + if (nodeBuilderState === undefined) { + const nodeBuilder = this.backend.createNodeBuilder(renderObject.object, this.renderer); + nodeBuilder.scene = renderObject.scene; + nodeBuilder.material = renderObject.material; + nodeBuilder.camera = renderObject.camera; + nodeBuilder.context.material = renderObject.material; + nodeBuilder.lightsNode = renderObject.lightsNode; + nodeBuilder.environmentNode = this.getEnvironmentNode(renderObject.scene); + nodeBuilder.fogNode = this.getFogNode(renderObject.scene); + nodeBuilder.clippingContext = renderObject.clippingContext; + nodeBuilder.build(); + + nodeBuilderState = this._createNodeBuilderState(nodeBuilder); + + nodeBuilderCache.set(cacheKey, nodeBuilderState); + } + + nodeBuilderState.usedTimes++; + + renderObjectData.nodeBuilderState = nodeBuilderState; + } + + return nodeBuilderState; + } + + delete(object) { + if (object.isRenderObject) { + const nodeBuilderState = this.get(object).nodeBuilderState; + nodeBuilderState.usedTimes--; + + if (nodeBuilderState.usedTimes === 0) { + this.nodeBuilderCache.delete(this.getForRenderCacheKey(object)); + } + } + + return super.delete(object); + } + + getForCompute(computeNode) { + const computeData = this.get(computeNode); + + let nodeBuilderState = computeData.nodeBuilderState; + + if (nodeBuilderState === undefined) { + const nodeBuilder = this.backend.createNodeBuilder(computeNode, this.renderer); + nodeBuilder.build(); + + nodeBuilderState = this._createNodeBuilderState(nodeBuilder); + + computeData.nodeBuilderState = nodeBuilderState; + } + + return nodeBuilderState; + } + + _createNodeBuilderState(nodeBuilder) { + return new NodeBuilderState( + nodeBuilder.vertexShader, + nodeBuilder.fragmentShader, + nodeBuilder.computeShader, + nodeBuilder.getAttributesArray(), + nodeBuilder.getBindings(), + nodeBuilder.updateNodes, + nodeBuilder.updateBeforeNodes, + nodeBuilder.updateAfterNodes, + nodeBuilder.instanceBindGroups, + nodeBuilder.transforms, + ); + } + + getEnvironmentNode(scene) { + return scene.environmentNode || this.get(scene).environmentNode || null; + } + + getBackgroundNode(scene) { + return scene.backgroundNode || this.get(scene).backgroundNode || null; + } + + getFogNode(scene) { + return scene.fogNode || this.get(scene).fogNode || null; + } + + getCacheKey(scene, lightsNode) { + const chain = [scene, lightsNode]; + const callId = this.renderer.info.calls; + + let cacheKeyData = this.callHashCache.get(chain); + + if (cacheKeyData === undefined || cacheKeyData.callId !== callId) { + const environmentNode = this.getEnvironmentNode(scene); + const fogNode = this.getFogNode(scene); + + const cacheKey = []; + + if (lightsNode) cacheKey.push(lightsNode.getCacheKey()); + if (environmentNode) cacheKey.push(environmentNode.getCacheKey()); + if (fogNode) cacheKey.push(fogNode.getCacheKey()); + + cacheKeyData = { + callId, + cacheKey: cacheKey.join(','), + }; + + this.callHashCache.set(chain, cacheKeyData); + } + + return cacheKeyData.cacheKey; + } + + updateScene(scene) { + this.updateEnvironment(scene); + this.updateFog(scene); + this.updateBackground(scene); + } + + get isToneMappingState() { + return this.renderer.getRenderTarget() ? false : true; + } + + updateBackground(scene) { + const sceneData = this.get(scene); + const background = scene.background; + + if (background) { + if (sceneData.background !== background) { + let backgroundNode = null; + + if ( + background.isCubeTexture === true || + background.mapping === EquirectangularReflectionMapping || + background.mapping === EquirectangularRefractionMapping + ) { + backgroundNode = pmremTexture(background, normalWorld); + } else if (background.isTexture === true) { + backgroundNode = texture(background, viewportBottomLeft).setUpdateMatrix(true); + } else if (background.isColor !== true) { + console.error('WebGPUNodes: Unsupported background configuration.', background); + } + + sceneData.backgroundNode = backgroundNode; + sceneData.background = background; + } + } else if (sceneData.backgroundNode) { + delete sceneData.backgroundNode; + delete sceneData.background; + } + } + + updateFog(scene) { + const sceneData = this.get(scene); + const fog = scene.fog; + + if (fog) { + if (sceneData.fog !== fog) { + let fogNode = null; + + if (fog.isFogExp2) { + fogNode = densityFog(reference('color', 'color', fog), reference('density', 'float', fog)); + } else if (fog.isFog) { + fogNode = rangeFog( + reference('color', 'color', fog), + reference('near', 'float', fog), + reference('far', 'float', fog), + ); + } else { + console.error('WebGPUNodes: Unsupported fog configuration.', fog); + } + + sceneData.fogNode = fogNode; + sceneData.fog = fog; + } + } else { + delete sceneData.fogNode; + delete sceneData.fog; + } + } + + updateEnvironment(scene) { + const sceneData = this.get(scene); + const environment = scene.environment; + + if (environment) { + if (sceneData.environment !== environment) { + let environmentNode = null; + + if (environment.isCubeTexture === true) { + environmentNode = cubeTexture(environment); + } else if (environment.isTexture === true) { + environmentNode = texture(environment); + } else { + console.error('Nodes: Unsupported environment configuration.', environment); + } + + sceneData.environmentNode = environmentNode; + sceneData.environment = environment; + } + } else if (sceneData.environmentNode) { + delete sceneData.environmentNode; + delete sceneData.environment; + } + } + + getNodeFrame(renderer = this.renderer, scene = null, object = null, camera = null, material = null) { + const nodeFrame = this.nodeFrame; + nodeFrame.renderer = renderer; + nodeFrame.scene = scene; + nodeFrame.object = object; + nodeFrame.camera = camera; + nodeFrame.material = material; + + return nodeFrame; + } + + getNodeFrameForRender(renderObject) { + return this.getNodeFrame( + renderObject.renderer, + renderObject.scene, + renderObject.object, + renderObject.camera, + renderObject.material, + ); + } + + getOutputNode(outputTexture) { + let output = texture(outputTexture, viewportTopLeft); + + if (this.isToneMappingState) { + if (this.renderer.toneMappingNode) { + output = vec4(this.renderer.toneMappingNode.context({ color: output.rgb }), output.a); + } else if (this.renderer.toneMapping !== NoToneMapping) { + output = output.toneMapping(this.renderer.toneMapping); + } + } + + if (this.renderer.currentColorSpace === SRGBColorSpace) { + output = output.linearToColorSpace(this.renderer.currentColorSpace); + } + + return output; + } + + updateBefore(renderObject) { + const nodeFrame = this.getNodeFrameForRender(renderObject); + const nodeBuilder = renderObject.getNodeBuilderState(); + + for (const node of nodeBuilder.updateBeforeNodes) { + nodeFrame.updateBeforeNode(node); + } + } + + updateAfter(renderObject) { + const nodeFrame = this.getNodeFrameForRender(renderObject); + const nodeBuilder = renderObject.getNodeBuilderState(); + + for (const node of nodeBuilder.updateAfterNodes) { + nodeFrame.updateAfterNode(node); + } + } + + updateForCompute(computeNode) { + const nodeFrame = this.getNodeFrame(); + const nodeBuilder = this.getForCompute(computeNode); + + for (const node of nodeBuilder.updateNodes) { + nodeFrame.updateNode(node); + } + } + + updateForRender(renderObject) { + const nodeFrame = this.getNodeFrameForRender(renderObject); + const nodeBuilder = renderObject.getNodeBuilderState(); + + for (const node of nodeBuilder.updateNodes) { + nodeFrame.updateNode(node); + } + } + + dispose() { + super.dispose(); + + this.nodeFrame = new NodeFrame(); + this.nodeBuilderCache = new Map(); + } +} + +export default Nodes; diff --git a/examples-jsm/examples/renderers/webgl/WebGLBackend.ts b/examples-jsm/examples/renderers/webgl/WebGLBackend.ts new file mode 100644 index 00000000..f32af434 --- /dev/null +++ b/examples-jsm/examples/renderers/webgl/WebGLBackend.ts @@ -0,0 +1,1268 @@ +import { WebGLCoordinateSystem } from 'three'; + +import GLSLNodeBuilder from './nodes/GLSLNodeBuilder.js'; +import Backend from '../common/Backend.js'; + +import WebGLAttributeUtils from './utils/WebGLAttributeUtils.js'; +import WebGLState from './utils/WebGLState.js'; +import WebGLUtils from './utils/WebGLUtils.js'; +import WebGLTextureUtils from './utils/WebGLTextureUtils.js'; +import WebGLExtensions from './utils/WebGLExtensions.js'; +import WebGLCapabilities from './utils/WebGLCapabilities.js'; +import { GLFeatureName } from './utils/WebGLConstants.js'; +import { WebGLBufferRenderer } from './WebGLBufferRenderer.js'; + +// + +class WebGLBackend extends Backend { + constructor(parameters = {}) { + super(parameters); + + this.isWebGLBackend = true; + } + + init(renderer) { + super.init(renderer); + + // + + const parameters = this.parameters; + + const glContext = + parameters.context !== undefined ? parameters.context : renderer.domElement.getContext('webgl2'); + + this.gl = glContext; + + this.extensions = new WebGLExtensions(this); + this.capabilities = new WebGLCapabilities(this); + this.attributeUtils = new WebGLAttributeUtils(this); + this.textureUtils = new WebGLTextureUtils(this); + this.bufferRenderer = new WebGLBufferRenderer(this); + + this.state = new WebGLState(this); + this.utils = new WebGLUtils(this); + + this.vaoCache = {}; + this.transformFeedbackCache = {}; + this.discard = false; + this.trackTimestamp = parameters.trackTimestamp === true; + + this.extensions.get('EXT_color_buffer_float'); + this.disjoint = this.extensions.get('EXT_disjoint_timer_query_webgl2'); + this.parallel = this.extensions.get('KHR_parallel_shader_compile'); + this._currentContext = null; + } + + get coordinateSystem() { + return WebGLCoordinateSystem; + } + + async getArrayBufferAsync(attribute) { + return await this.attributeUtils.getArrayBufferAsync(attribute); + } + + initTimestampQuery(renderContext) { + if (!this.disjoint || !this.trackTimestamp) return; + + const renderContextData = this.get(renderContext); + + if (this.queryRunning) { + if (!renderContextData.queryQueue) renderContextData.queryQueue = []; + renderContextData.queryQueue.push(renderContext); + return; + } + + if (renderContextData.activeQuery) { + this.gl.endQuery(this.disjoint.TIME_ELAPSED_EXT); + renderContextData.activeQuery = null; + } + + renderContextData.activeQuery = this.gl.createQuery(); + + if (renderContextData.activeQuery !== null) { + this.gl.beginQuery(this.disjoint.TIME_ELAPSED_EXT, renderContextData.activeQuery); + this.queryRunning = true; + } + } + + // timestamp utils + + prepareTimestampBuffer(renderContext) { + if (!this.disjoint || !this.trackTimestamp) return; + + const renderContextData = this.get(renderContext); + + if (renderContextData.activeQuery) { + this.gl.endQuery(this.disjoint.TIME_ELAPSED_EXT); + + if (!renderContextData.gpuQueries) renderContextData.gpuQueries = []; + renderContextData.gpuQueries.push({ query: renderContextData.activeQuery }); + renderContextData.activeQuery = null; + this.queryRunning = false; + + if (renderContextData.queryQueue && renderContextData.queryQueue.length > 0) { + const nextRenderContext = renderContextData.queryQueue.shift(); + this.initTimestampQuery(nextRenderContext); + } + } + } + + async resolveTimestampAsync(renderContext, type = 'render') { + if (!this.disjoint || !this.trackTimestamp) return; + + const renderContextData = this.get(renderContext); + + if (!renderContextData.gpuQueries) renderContextData.gpuQueries = []; + + for (let i = 0; i < renderContextData.gpuQueries.length; i++) { + const queryInfo = renderContextData.gpuQueries[i]; + const available = this.gl.getQueryParameter(queryInfo.query, this.gl.QUERY_RESULT_AVAILABLE); + const disjoint = this.gl.getParameter(this.disjoint.GPU_DISJOINT_EXT); + + if (available && !disjoint) { + const elapsed = this.gl.getQueryParameter(queryInfo.query, this.gl.QUERY_RESULT); + const duration = Number(elapsed) / 1000000; // Convert nanoseconds to milliseconds + this.gl.deleteQuery(queryInfo.query); + renderContextData.gpuQueries.splice(i, 1); // Remove the processed query + i--; + this.renderer.info.updateTimestamp(type, duration); + } + } + } + + getContext() { + return this.gl; + } + + beginRender(renderContext) { + const { gl } = this; + const renderContextData = this.get(renderContext); + + // + + // + + this.initTimestampQuery(renderContext); + + renderContextData.previousContext = this._currentContext; + this._currentContext = renderContext; + + this._setFramebuffer(renderContext); + + this.clear( + renderContext.clearColor, + renderContext.clearDepth, + renderContext.clearStencil, + renderContext, + false, + ); + + // + if (renderContext.viewport) { + this.updateViewport(renderContext); + } else { + gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); + } + + if (renderContext.scissor) { + const { x, y, width, height } = renderContext.scissorValue; + + gl.scissor(x, y, width, height); + } + + const occlusionQueryCount = renderContext.occlusionQueryCount; + + if (occlusionQueryCount > 0) { + // Get a reference to the array of objects with queries. The renderContextData property + // can be changed by another render pass before the async reading of all previous queries complete + renderContextData.currentOcclusionQueries = renderContextData.occlusionQueries; + renderContextData.currentOcclusionQueryObjects = renderContextData.occlusionQueryObjects; + + renderContextData.lastOcclusionObject = null; + renderContextData.occlusionQueries = new Array(occlusionQueryCount); + renderContextData.occlusionQueryObjects = new Array(occlusionQueryCount); + renderContextData.occlusionQueryIndex = 0; + } + } + + finishRender(renderContext) { + const { gl, state } = this; + const renderContextData = this.get(renderContext); + const previousContext = renderContextData.previousContext; + + const textures = renderContext.textures; + + if (textures !== null) { + for (let i = 0; i < textures.length; i++) { + const texture = textures[i]; + + if (texture.generateMipmaps) { + this.generateMipmaps(texture); + } + } + } + + this._currentContext = previousContext; + + if (renderContext.textures !== null && renderContext.renderTarget) { + const renderTargetContextData = this.get(renderContext.renderTarget); + + const { samples } = renderContext.renderTarget; + const fb = renderTargetContextData.framebuffer; + + const mask = gl.COLOR_BUFFER_BIT; + + if (samples > 0) { + const msaaFrameBuffer = renderTargetContextData.msaaFrameBuffer; + + const textures = renderContext.textures; + + state.bindFramebuffer(gl.READ_FRAMEBUFFER, msaaFrameBuffer); + state.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fb); + + for (let i = 0; i < textures.length; i++) { + // TODO Add support for MRT + + gl.blitFramebuffer( + 0, + 0, + renderContext.width, + renderContext.height, + 0, + 0, + renderContext.width, + renderContext.height, + mask, + gl.NEAREST, + ); + + gl.invalidateFramebuffer(gl.READ_FRAMEBUFFER, renderTargetContextData.invalidationArray); + } + } + } + + if (previousContext !== null) { + this._setFramebuffer(previousContext); + + if (previousContext.viewport) { + this.updateViewport(previousContext); + } else { + const gl = this.gl; + + gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); + } + } + + const occlusionQueryCount = renderContext.occlusionQueryCount; + + if (occlusionQueryCount > 0) { + const renderContextData = this.get(renderContext); + + if (occlusionQueryCount > renderContextData.occlusionQueryIndex) { + const { gl } = this; + + gl.endQuery(gl.ANY_SAMPLES_PASSED); + } + + this.resolveOccludedAsync(renderContext); + } + + this.prepareTimestampBuffer(renderContext); + } + + resolveOccludedAsync(renderContext) { + const renderContextData = this.get(renderContext); + + // handle occlusion query results + + const { currentOcclusionQueries, currentOcclusionQueryObjects } = renderContextData; + + if (currentOcclusionQueries && currentOcclusionQueryObjects) { + const occluded = new WeakSet(); + const { gl } = this; + + renderContextData.currentOcclusionQueryObjects = null; + renderContextData.currentOcclusionQueries = null; + + const check = () => { + let completed = 0; + + // check all queries and requeue as appropriate + for (let i = 0; i < currentOcclusionQueries.length; i++) { + const query = currentOcclusionQueries[i]; + + if (query === null) continue; + + if (gl.getQueryParameter(query, gl.QUERY_RESULT_AVAILABLE)) { + if (gl.getQueryParameter(query, gl.QUERY_RESULT) > 0) + occluded.add(currentOcclusionQueryObjects[i]); + + currentOcclusionQueries[i] = null; + gl.deleteQuery(query); + + completed++; + } + } + + if (completed < currentOcclusionQueries.length) { + requestAnimationFrame(check); + } else { + renderContextData.occluded = occluded; + } + }; + + check(); + } + } + + isOccluded(renderContext, object) { + const renderContextData = this.get(renderContext); + + return renderContextData.occluded && renderContextData.occluded.has(object); + } + + updateViewport(renderContext) { + const gl = this.gl; + const { x, y, width, height } = renderContext.viewportValue; + + gl.viewport(x, y, width, height); + } + + setScissorTest(boolean) { + const gl = this.gl; + + if (boolean) { + gl.enable(gl.SCISSOR_TEST); + } else { + gl.disable(gl.SCISSOR_TEST); + } + } + + clear(color, depth, stencil, descriptor = null, setFrameBuffer = true) { + const { gl } = this; + + if (descriptor === null) { + descriptor = { + textures: null, + clearColorValue: this.getClearColor(), + }; + } + + // + + let clear = 0; + + if (color) clear |= gl.COLOR_BUFFER_BIT; + if (depth) clear |= gl.DEPTH_BUFFER_BIT; + if (stencil) clear |= gl.STENCIL_BUFFER_BIT; + + if (clear !== 0) { + const clearColor = descriptor.clearColorValue || this.getClearColor(); + + if (depth) this.state.setDepthMask(true); + + if (descriptor.textures === null) { + gl.clearColor(clearColor.r, clearColor.g, clearColor.b, clearColor.a); + gl.clear(clear); + } else { + if (setFrameBuffer) this._setFramebuffer(descriptor); + + if (color) { + for (let i = 0; i < descriptor.textures.length; i++) { + gl.clearBufferfv(gl.COLOR, i, [clearColor.r, clearColor.g, clearColor.b, clearColor.a]); + } + } + + if (depth && stencil) { + gl.clearBufferfi(gl.DEPTH_STENCIL, 0, 1, 0); + } else if (depth) { + gl.clearBufferfv(gl.DEPTH, 0, [1.0]); + } else if (stencil) { + gl.clearBufferiv(gl.STENCIL, 0, [0]); + } + } + } + } + + beginCompute(computeGroup) { + const gl = this.gl; + + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + this.initTimestampQuery(computeGroup); + } + + compute(computeGroup, computeNode, bindings, pipeline) { + const gl = this.gl; + + if (!this.discard) { + // required here to handle async behaviour of render.compute() + gl.enable(gl.RASTERIZER_DISCARD); + this.discard = true; + } + + const { programGPU, transformBuffers, attributes } = this.get(pipeline); + + const vaoKey = this._getVaoKey(null, attributes); + + const vaoGPU = this.vaoCache[vaoKey]; + + if (vaoGPU === undefined) { + this._createVao(null, attributes); + } else { + gl.bindVertexArray(vaoGPU); + } + + gl.useProgram(programGPU); + + this._bindUniforms(bindings); + + const transformFeedbackGPU = this._getTransformFeedback(transformBuffers); + + gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, transformFeedbackGPU); + gl.beginTransformFeedback(gl.POINTS); + + if (attributes[0].isStorageInstancedBufferAttribute) { + gl.drawArraysInstanced(gl.POINTS, 0, 1, computeNode.count); + } else { + gl.drawArrays(gl.POINTS, 0, computeNode.count); + } + + gl.endTransformFeedback(); + gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null); + + // switch active buffers + + for (let i = 0; i < transformBuffers.length; i++) { + const dualAttributeData = transformBuffers[i]; + + if (dualAttributeData.pbo) { + this.textureUtils.copyBufferToTexture(dualAttributeData.transformBuffer, dualAttributeData.pbo); + } + + dualAttributeData.switchBuffers(); + } + } + + finishCompute(computeGroup) { + const gl = this.gl; + + this.discard = false; + + gl.disable(gl.RASTERIZER_DISCARD); + + this.prepareTimestampBuffer(computeGroup); + } + + draw(renderObject /*, info*/) { + const { object, pipeline, material, context } = renderObject; + const { programGPU } = this.get(pipeline); + + const { gl, state } = this; + + const contextData = this.get(context); + + // + + this._bindUniforms(renderObject.getBindings()); + + const frontFaceCW = object.isMesh && object.matrixWorld.determinant() < 0; + + state.setMaterial(material, frontFaceCW); + + gl.useProgram(programGPU); + + // + + let vaoGPU = renderObject.staticVao; + + if (vaoGPU === undefined) { + const vaoKey = this._getVaoKey(renderObject.getIndex(), renderObject.getAttributes()); + + vaoGPU = this.vaoCache[vaoKey]; + + if (vaoGPU === undefined) { + let staticVao; + + ({ vaoGPU, staticVao } = this._createVao(renderObject.getIndex(), renderObject.getAttributes())); + + if (staticVao) renderObject.staticVao = vaoGPU; + } + } + + gl.bindVertexArray(vaoGPU); + + // + + const index = renderObject.getIndex(); + + const geometry = renderObject.geometry; + const drawRange = renderObject.drawRange; + const firstVertex = drawRange.start; + + // + + const lastObject = contextData.lastOcclusionObject; + + if (lastObject !== object && lastObject !== undefined) { + if (lastObject !== null && lastObject.occlusionTest === true) { + gl.endQuery(gl.ANY_SAMPLES_PASSED); + + contextData.occlusionQueryIndex++; + } + + if (object.occlusionTest === true) { + const query = gl.createQuery(); + + gl.beginQuery(gl.ANY_SAMPLES_PASSED, query); + + contextData.occlusionQueries[contextData.occlusionQueryIndex] = query; + contextData.occlusionQueryObjects[contextData.occlusionQueryIndex] = object; + } + + contextData.lastOcclusionObject = object; + } + + // + + const renderer = this.bufferRenderer; + + if (object.isPoints) renderer.mode = gl.POINTS; + else if (object.isLineSegments) renderer.mode = gl.LINES; + else if (object.isLine) renderer.mode = gl.LINE_STRIP; + else if (object.isLineLoop) renderer.mode = gl.LINE_LOOP; + else { + if (material.wireframe === true) { + state.setLineWidth(material.wireframeLinewidth * this.renderer.getPixelRatio()); + renderer.mode = gl.LINES; + } else { + renderer.mode = gl.TRIANGLES; + } + } + + // + + let count; + + renderer.object = object; + + if (index !== null) { + const indexData = this.get(index); + const indexCount = drawRange.count !== Infinity ? drawRange.count : index.count; + + renderer.index = index.count; + renderer.type = indexData.type; + + count = indexCount; + } else { + renderer.index = 0; + + const vertexCount = drawRange.count !== Infinity ? drawRange.count : geometry.attributes.position.count; + + count = vertexCount; + } + + const instanceCount = this.getInstanceCount(renderObject); + + if (object.isBatchedMesh) { + if (object._multiDrawInstances !== null) { + renderer.renderMultiDrawInstances( + object._multiDrawStarts, + object._multiDrawCounts, + object._multiDrawCount, + object._multiDrawInstances, + ); + } else { + renderer.renderMultiDraw(object._multiDrawStarts, object._multiDrawCounts, object._multiDrawCount); + } + } else if (instanceCount > 1) { + renderer.renderInstances(firstVertex, count, instanceCount); + } else { + renderer.render(firstVertex, count); + } + // + + gl.bindVertexArray(null); + } + + needsRenderUpdate(/*renderObject*/) { + return false; + } + + getRenderCacheKey(renderObject) { + return renderObject.id; + } + + // textures + + createDefaultTexture(texture) { + this.textureUtils.createDefaultTexture(texture); + } + + createTexture(texture, options) { + this.textureUtils.createTexture(texture, options); + } + + updateTexture(texture, options) { + this.textureUtils.updateTexture(texture, options); + } + + generateMipmaps(texture) { + this.textureUtils.generateMipmaps(texture); + } + + destroyTexture(texture) { + this.textureUtils.destroyTexture(texture); + } + + copyTextureToBuffer(texture, x, y, width, height) { + return this.textureUtils.copyTextureToBuffer(texture, x, y, width, height); + } + + createSampler(/*texture*/) { + //console.warn( 'Abstract class.' ); + } + + destroySampler() {} + + // node builder + + createNodeBuilder(object, renderer) { + return new GLSLNodeBuilder(object, renderer); + } + + // program + + createProgram(program) { + const gl = this.gl; + const { stage, code } = program; + + const shader = stage === 'fragment' ? gl.createShader(gl.FRAGMENT_SHADER) : gl.createShader(gl.VERTEX_SHADER); + + gl.shaderSource(shader, code); + gl.compileShader(shader); + + this.set(program, { + shaderGPU: shader, + }); + } + + destroyProgram(/*program*/) { + console.warn('Abstract class.'); + } + + createRenderPipeline(renderObject, promises) { + const gl = this.gl; + const pipeline = renderObject.pipeline; + + // Program + + const { fragmentProgram, vertexProgram } = pipeline; + + const programGPU = gl.createProgram(); + + const fragmentShader = this.get(fragmentProgram).shaderGPU; + const vertexShader = this.get(vertexProgram).shaderGPU; + + gl.attachShader(programGPU, fragmentShader); + gl.attachShader(programGPU, vertexShader); + gl.linkProgram(programGPU); + + this.set(pipeline, { + programGPU, + fragmentShader, + vertexShader, + }); + + if (promises !== null && this.parallel) { + const p = new Promise((resolve /*, reject*/) => { + const parallel = this.parallel; + const checkStatus = () => { + if (gl.getProgramParameter(programGPU, parallel.COMPLETION_STATUS_KHR)) { + this._completeCompile(renderObject, pipeline); + resolve(); + } else { + requestAnimationFrame(checkStatus); + } + }; + + checkStatus(); + }); + + promises.push(p); + + return; + } + + this._completeCompile(renderObject, pipeline); + } + + _handleSource(string, errorLine) { + const lines = string.split('\n'); + const lines2 = []; + + const from = Math.max(errorLine - 6, 0); + const to = Math.min(errorLine + 6, lines.length); + + for (let i = from; i < to; i++) { + const line = i + 1; + lines2.push(`${line === errorLine ? '>' : ' '} ${line}: ${lines[i]}`); + } + + return lines2.join('\n'); + } + + _getShaderErrors(gl, shader, type) { + const status = gl.getShaderParameter(shader, gl.COMPILE_STATUS); + const errors = gl.getShaderInfoLog(shader).trim(); + + if (status && errors === '') return ''; + + const errorMatches = /ERROR: 0:(\d+)/.exec(errors); + if (errorMatches) { + const errorLine = parseInt(errorMatches[1]); + return ( + type.toUpperCase() + + '\n\n' + + errors + + '\n\n' + + this._handleSource(gl.getShaderSource(shader), errorLine) + ); + } else { + return errors; + } + } + + _logProgramError(programGPU, glFragmentShader, glVertexShader) { + if (this.renderer.debug.checkShaderErrors) { + const gl = this.gl; + + const programLog = gl.getProgramInfoLog(programGPU).trim(); + + if (gl.getProgramParameter(programGPU, gl.LINK_STATUS) === false) { + if (typeof this.renderer.debug.onShaderError === 'function') { + this.renderer.debug.onShaderError(gl, programGPU, glVertexShader, glFragmentShader); + } else { + // default error reporting + + const vertexErrors = this._getShaderErrors(gl, glVertexShader, 'vertex'); + const fragmentErrors = this._getShaderErrors(gl, glFragmentShader, 'fragment'); + + console.error( + 'THREE.WebGLProgram: Shader Error ' + + gl.getError() + + ' - ' + + 'VALIDATE_STATUS ' + + gl.getProgramParameter(programGPU, gl.VALIDATE_STATUS) + + '\n\n' + + 'Program Info Log: ' + + programLog + + '\n' + + vertexErrors + + '\n' + + fragmentErrors, + ); + } + } else if (programLog !== '') { + console.warn('THREE.WebGLProgram: Program Info Log:', programLog); + } + } + } + + _completeCompile(renderObject, pipeline) { + const gl = this.gl; + const pipelineData = this.get(pipeline); + const { programGPU, fragmentShader, vertexShader } = pipelineData; + + if (gl.getProgramParameter(programGPU, gl.LINK_STATUS) === false) { + this._logProgramError(programGPU, fragmentShader, vertexShader); + } + + gl.useProgram(programGPU); + + // Bindings + + const bindings = renderObject.getBindings(); + + this._setupBindings(bindings, programGPU); + + // + + this.set(pipeline, { + programGPU, + }); + } + + createComputePipeline(computePipeline, bindings) { + const gl = this.gl; + + // Program + + const fragmentProgram = { + stage: 'fragment', + code: '#version 300 es\nprecision highp float;\nvoid main() {}', + }; + + this.createProgram(fragmentProgram); + + const { computeProgram } = computePipeline; + + const programGPU = gl.createProgram(); + + const fragmentShader = this.get(fragmentProgram).shaderGPU; + const vertexShader = this.get(computeProgram).shaderGPU; + + const transforms = computeProgram.transforms; + + const transformVaryingNames = []; + const transformAttributeNodes = []; + + for (let i = 0; i < transforms.length; i++) { + const transform = transforms[i]; + + transformVaryingNames.push(transform.varyingName); + transformAttributeNodes.push(transform.attributeNode); + } + + gl.attachShader(programGPU, fragmentShader); + gl.attachShader(programGPU, vertexShader); + + gl.transformFeedbackVaryings(programGPU, transformVaryingNames, gl.SEPARATE_ATTRIBS); + + gl.linkProgram(programGPU); + + if (gl.getProgramParameter(programGPU, gl.LINK_STATUS) === false) { + this._logProgramError(programGPU, fragmentShader, vertexShader); + } + + gl.useProgram(programGPU); + + // Bindings + + this.createBindings(null, bindings); + + this._setupBindings(bindings, programGPU); + + const attributeNodes = computeProgram.attributes; + const attributes = []; + const transformBuffers = []; + + for (let i = 0; i < attributeNodes.length; i++) { + const attribute = attributeNodes[i].node.attribute; + + attributes.push(attribute); + + if (!this.has(attribute)) this.attributeUtils.createAttribute(attribute, gl.ARRAY_BUFFER); + } + + for (let i = 0; i < transformAttributeNodes.length; i++) { + const attribute = transformAttributeNodes[i].attribute; + + if (!this.has(attribute)) this.attributeUtils.createAttribute(attribute, gl.ARRAY_BUFFER); + + const attributeData = this.get(attribute); + + transformBuffers.push(attributeData); + } + + // + + this.set(computePipeline, { + programGPU, + transformBuffers, + attributes, + }); + } + + createBindings(bindGroup, bindings) { + this.updateBindings(bindGroup, bindings); + } + + updateBindings(bindGroup, bindings) { + const { gl } = this; + + let groupIndex = 0; + let textureIndex = 0; + + for (const bindGroup of bindings) { + for (const binding of bindGroup.bindings) { + if (binding.isUniformsGroup || binding.isUniformBuffer) { + const bufferGPU = gl.createBuffer(); + const data = binding.buffer; + + gl.bindBuffer(gl.UNIFORM_BUFFER, bufferGPU); + gl.bufferData(gl.UNIFORM_BUFFER, data, gl.DYNAMIC_DRAW); + gl.bindBufferBase(gl.UNIFORM_BUFFER, groupIndex, bufferGPU); + + this.set(binding, { + index: groupIndex++, + bufferGPU, + }); + } else if (binding.isSampledTexture) { + const { textureGPU, glTextureType } = this.get(binding.texture); + + this.set(binding, { + index: textureIndex++, + textureGPU, + glTextureType, + }); + } + } + } + } + + updateBinding(binding) { + const gl = this.gl; + + if (binding.isUniformsGroup || binding.isUniformBuffer) { + const bindingData = this.get(binding); + const bufferGPU = bindingData.bufferGPU; + const data = binding.buffer; + + gl.bindBuffer(gl.UNIFORM_BUFFER, bufferGPU); + gl.bufferData(gl.UNIFORM_BUFFER, data, gl.DYNAMIC_DRAW); + } + } + + // attributes + + createIndexAttribute(attribute) { + const gl = this.gl; + + this.attributeUtils.createAttribute(attribute, gl.ELEMENT_ARRAY_BUFFER); + } + + createAttribute(attribute) { + if (this.has(attribute)) return; + + const gl = this.gl; + + this.attributeUtils.createAttribute(attribute, gl.ARRAY_BUFFER); + } + + createStorageAttribute(attribute) { + if (this.has(attribute)) return; + + const gl = this.gl; + + this.attributeUtils.createAttribute(attribute, gl.ARRAY_BUFFER); + } + + updateAttribute(attribute) { + this.attributeUtils.updateAttribute(attribute); + } + + destroyAttribute(attribute) { + this.attributeUtils.destroyAttribute(attribute); + } + + updateSize() { + //console.warn( 'Abstract class.' ); + } + + hasFeature(name) { + const keysMatching = Object.keys(GLFeatureName).filter(key => GLFeatureName[key] === name); + + const extensions = this.extensions; + + for (let i = 0; i < keysMatching.length; i++) { + if (extensions.has(keysMatching[i])) return true; + } + + return false; + } + + getMaxAnisotropy() { + return this.capabilities.getMaxAnisotropy(); + } + + copyTextureToTexture(position, srcTexture, dstTexture, level) { + this.textureUtils.copyTextureToTexture(position, srcTexture, dstTexture, level); + } + + copyFramebufferToTexture(texture, renderContext) { + this.textureUtils.copyFramebufferToTexture(texture, renderContext); + } + + _setFramebuffer(renderContext) { + const { gl, state } = this; + + let currentFrameBuffer = null; + + if (renderContext.textures !== null) { + const renderTarget = renderContext.renderTarget; + const renderTargetContextData = this.get(renderTarget); + const { samples, depthBuffer, stencilBuffer } = renderTarget; + const cubeFace = this.renderer._activeCubeFace; + const isCube = renderTarget.isWebGLCubeRenderTarget === true; + + let msaaFb = renderTargetContextData.msaaFrameBuffer; + let depthRenderbuffer = renderTargetContextData.depthRenderbuffer; + + let fb; + + if (isCube) { + if (renderTargetContextData.cubeFramebuffers === undefined) { + renderTargetContextData.cubeFramebuffers = []; + } + + fb = renderTargetContextData.cubeFramebuffers[cubeFace]; + } else { + fb = renderTargetContextData.framebuffer; + } + + if (fb === undefined) { + fb = gl.createFramebuffer(); + + state.bindFramebuffer(gl.FRAMEBUFFER, fb); + + const textures = renderContext.textures; + + if (isCube) { + renderTargetContextData.cubeFramebuffers[cubeFace] = fb; + const { textureGPU } = this.get(textures[0]); + + gl.framebufferTexture2D( + gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0, + gl.TEXTURE_CUBE_MAP_POSITIVE_X + cubeFace, + textureGPU, + 0, + ); + } else { + for (let i = 0; i < textures.length; i++) { + const texture = textures[i]; + const textureData = this.get(texture); + textureData.renderTarget = renderContext.renderTarget; + + const attachment = gl.COLOR_ATTACHMENT0 + i; + + gl.framebufferTexture2D(gl.FRAMEBUFFER, attachment, gl.TEXTURE_2D, textureData.textureGPU, 0); + } + + renderTargetContextData.framebuffer = fb; + + state.drawBuffers(renderContext, fb); + } + + if (renderContext.depthTexture !== null) { + const textureData = this.get(renderContext.depthTexture); + const depthStyle = stencilBuffer ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT; + + gl.framebufferTexture2D(gl.FRAMEBUFFER, depthStyle, gl.TEXTURE_2D, textureData.textureGPU, 0); + } + } + + if (samples > 0) { + if (msaaFb === undefined) { + const invalidationArray = []; + + msaaFb = gl.createFramebuffer(); + + state.bindFramebuffer(gl.FRAMEBUFFER, msaaFb); + + const msaaRenderbuffers = []; + + const textures = renderContext.textures; + + for (let i = 0; i < textures.length; i++) { + msaaRenderbuffers[i] = gl.createRenderbuffer(); + + gl.bindRenderbuffer(gl.RENDERBUFFER, msaaRenderbuffers[i]); + + invalidationArray.push(gl.COLOR_ATTACHMENT0 + i); + + if (depthBuffer) { + const depthStyle = stencilBuffer ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT; + invalidationArray.push(depthStyle); + } + + const texture = renderContext.textures[i]; + const textureData = this.get(texture); + + gl.renderbufferStorageMultisample( + gl.RENDERBUFFER, + samples, + textureData.glInternalFormat, + renderContext.width, + renderContext.height, + ); + gl.framebufferRenderbuffer( + gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.RENDERBUFFER, + msaaRenderbuffers[i], + ); + } + + renderTargetContextData.msaaFrameBuffer = msaaFb; + renderTargetContextData.msaaRenderbuffers = msaaRenderbuffers; + + if (depthRenderbuffer === undefined) { + depthRenderbuffer = gl.createRenderbuffer(); + this.textureUtils.setupRenderBufferStorage(depthRenderbuffer, renderContext); + + renderTargetContextData.depthRenderbuffer = depthRenderbuffer; + + const depthStyle = stencilBuffer ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT; + invalidationArray.push(depthStyle); + } + + renderTargetContextData.invalidationArray = invalidationArray; + } + + currentFrameBuffer = renderTargetContextData.msaaFrameBuffer; + } else { + currentFrameBuffer = fb; + } + } + + state.bindFramebuffer(gl.FRAMEBUFFER, currentFrameBuffer); + } + + _getVaoKey(index, attributes) { + let key = []; + + if (index !== null) { + const indexData = this.get(index); + + key += ':' + indexData.id; + } + + for (let i = 0; i < attributes.length; i++) { + const attributeData = this.get(attributes[i]); + + key += ':' + attributeData.id; + } + + return key; + } + + _createVao(index, attributes) { + const { gl } = this; + + const vaoGPU = gl.createVertexArray(); + let key = ''; + + let staticVao = true; + + gl.bindVertexArray(vaoGPU); + + if (index !== null) { + const indexData = this.get(index); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexData.bufferGPU); + + key += ':' + indexData.id; + } + + for (let i = 0; i < attributes.length; i++) { + const attribute = attributes[i]; + const attributeData = this.get(attribute); + + key += ':' + attributeData.id; + + gl.bindBuffer(gl.ARRAY_BUFFER, attributeData.bufferGPU); + gl.enableVertexAttribArray(i); + + if (attribute.isStorageBufferAttribute || attribute.isStorageInstancedBufferAttribute) staticVao = false; + + let stride, offset; + + if (attribute.isInterleavedBufferAttribute === true) { + stride = attribute.data.stride * attributeData.bytesPerElement; + offset = attribute.offset * attributeData.bytesPerElement; + } else { + stride = 0; + offset = 0; + } + + if (attributeData.isInteger) { + gl.vertexAttribIPointer(i, attribute.itemSize, attributeData.type, stride, offset); + } else { + gl.vertexAttribPointer(i, attribute.itemSize, attributeData.type, attribute.normalized, stride, offset); + } + + if (attribute.isInstancedBufferAttribute && !attribute.isInterleavedBufferAttribute) { + gl.vertexAttribDivisor(i, attribute.meshPerAttribute); + } else if (attribute.isInterleavedBufferAttribute && attribute.data.isInstancedInterleavedBuffer) { + gl.vertexAttribDivisor(i, attribute.data.meshPerAttribute); + } + } + + gl.bindBuffer(gl.ARRAY_BUFFER, null); + + this.vaoCache[key] = vaoGPU; + + return { vaoGPU, staticVao }; + } + + _getTransformFeedback(transformBuffers) { + let key = ''; + + for (let i = 0; i < transformBuffers.length; i++) { + key += ':' + transformBuffers[i].id; + } + + let transformFeedbackGPU = this.transformFeedbackCache[key]; + + if (transformFeedbackGPU !== undefined) { + return transformFeedbackGPU; + } + + const gl = this.gl; + + transformFeedbackGPU = gl.createTransformFeedback(); + + gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, transformFeedbackGPU); + + for (let i = 0; i < transformBuffers.length; i++) { + const attributeData = transformBuffers[i]; + + gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, i, attributeData.transformBuffer); + } + + gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null); + + this.transformFeedbackCache[key] = transformFeedbackGPU; + + return transformFeedbackGPU; + } + + _setupBindings(bindings, programGPU) { + const gl = this.gl; + + for (const bindGroup of bindings) { + for (const binding of bindGroup.bindings) { + const bindingData = this.get(binding); + const index = bindingData.index; + + if (binding.isUniformsGroup || binding.isUniformBuffer) { + const location = gl.getUniformBlockIndex(programGPU, binding.name); + gl.uniformBlockBinding(programGPU, location, index); + } else if (binding.isSampledTexture) { + const location = gl.getUniformLocation(programGPU, binding.name); + gl.uniform1i(location, index); + } + } + } + } + + _bindUniforms(bindings) { + const { gl, state } = this; + + for (const bindGroup of bindings) { + for (const binding of bindGroup.bindings) { + const bindingData = this.get(binding); + const index = bindingData.index; + + if (binding.isUniformsGroup || binding.isUniformBuffer) { + gl.bindBufferBase(gl.UNIFORM_BUFFER, index, bindingData.bufferGPU); + } else if (binding.isSampledTexture) { + state.bindTexture(bindingData.glTextureType, bindingData.textureGPU, gl.TEXTURE0 + index); + } + } + } + } +} + +export default WebGLBackend; diff --git a/examples-jsm/examples/renderers/webgl/nodes/GLSLNodeBuilder.ts b/examples-jsm/examples/renderers/webgl/nodes/GLSLNodeBuilder.ts new file mode 100644 index 00000000..816b83ea --- /dev/null +++ b/examples-jsm/examples/renderers/webgl/nodes/GLSLNodeBuilder.ts @@ -0,0 +1,747 @@ +import { MathNode, GLSLNodeParser, NodeBuilder, TextureNode, vectorComponents } from '../../../nodes/Nodes.js'; + +import NodeUniformBuffer from '../../common/nodes/NodeUniformBuffer.js'; +import NodeUniformsGroup from '../../common/nodes/NodeUniformsGroup.js'; + +import { + NodeSampledTexture, + NodeSampledCubeTexture, + NodeSampledTexture3D, +} from '../../common/nodes/NodeSampledTexture.js'; + +import { + ByteType, + ShortType, + RGBAIntegerFormat, + RGBIntegerFormat, + RedIntegerFormat, + RGIntegerFormat, + UnsignedByteType, + UnsignedIntType, + UnsignedShortType, + RedFormat, + RGFormat, + IntType, + DataTexture, + RGBFormat, + RGBAFormat, + FloatType, +} from 'three'; + +const glslMethods = { + [MathNode.ATAN2]: 'atan', + textureDimensions: 'textureSize', + equals: 'equal', +}; + +const precisionLib = { + low: 'lowp', + medium: 'mediump', + high: 'highp', +}; + +const supports = { + swizzleAssign: true, + storageBuffer: false, +}; + +const defaultPrecisions = ` +precision highp float; +precision highp int; +precision highp sampler2D; +precision highp sampler3D; +precision highp samplerCube; +precision highp sampler2DArray; + +precision highp usampler2D; +precision highp usampler3D; +precision highp usamplerCube; +precision highp usampler2DArray; + +precision highp isampler2D; +precision highp isampler3D; +precision highp isamplerCube; +precision highp isampler2DArray; + +precision lowp sampler2DShadow; +`; + +class GLSLNodeBuilder extends NodeBuilder { + constructor(object, renderer) { + super(object, renderer, new GLSLNodeParser()); + + this.uniformGroups = {}; + this.transforms = []; + + this.instanceBindGroups = false; + } + + getMethod(method) { + return glslMethods[method] || method; + } + + getOutputStructName() { + return ''; + } + + buildFunctionCode(shaderNode) { + const layout = shaderNode.layout; + const flowData = this.flowShaderNode(shaderNode); + + const parameters = []; + + for (const input of layout.inputs) { + parameters.push(this.getType(input.type) + ' ' + input.name); + } + + // + + const code = `${this.getType(layout.type)} ${layout.name}( ${parameters.join(', ')} ) { + + ${flowData.vars} + +${flowData.code} + return ${flowData.result}; + +}`; + + // + + return code; + } + + setupPBO(storageBufferNode) { + const attribute = storageBufferNode.value; + + if (attribute.pbo === undefined) { + const originalArray = attribute.array; + const numElements = attribute.count * attribute.itemSize; + + const { itemSize } = attribute; + + const isInteger = attribute.array.constructor.name.toLowerCase().includes('int'); + + let format = isInteger ? RedIntegerFormat : RedFormat; + + if (itemSize === 2) { + format = isInteger ? RGIntegerFormat : RGFormat; + } else if (itemSize === 3) { + format = isInteger ? RGBIntegerFormat : RGBFormat; + } else if (itemSize === 4) { + format = isInteger ? RGBAIntegerFormat : RGBAFormat; + } + + const typeMap = { + Float32Array: FloatType, + Uint8Array: UnsignedByteType, + Uint16Array: UnsignedShortType, + Uint32Array: UnsignedIntType, + Int8Array: ByteType, + Int16Array: ShortType, + Int32Array: IntType, + Uint8ClampedArray: UnsignedByteType, + }; + + const width = Math.pow(2, Math.ceil(Math.log2(Math.sqrt(numElements / itemSize)))); + let height = Math.ceil(numElements / itemSize / width); + if (width * height * itemSize < numElements) height++; // Ensure enough space + + const newSize = width * height * itemSize; + + const newArray = new originalArray.constructor(newSize); + + newArray.set(originalArray, 0); + + attribute.array = newArray; + + const pboTexture = new DataTexture( + attribute.array, + width, + height, + format, + typeMap[attribute.array.constructor.name] || FloatType, + ); + pboTexture.needsUpdate = true; + pboTexture.isPBOTexture = true; + + const pbo = new TextureNode(pboTexture); + pbo.setPrecision('high'); + + attribute.pboNode = pbo; + attribute.pbo = pbo.value; + + this.getUniformFromNode(attribute.pboNode, 'texture', this.shaderStage, this.context.label); + } + } + + getPropertyName(node, shaderStage = this.shaderStage) { + if (node.isNodeUniform && node.node.isTextureNode !== true && node.node.isBufferNode !== true) { + return shaderStage.charAt(0) + '_' + node.name; + } + + return super.getPropertyName(node, shaderStage); + } + + generatePBO(storageArrayElementNode) { + const { node, indexNode } = storageArrayElementNode; + const attribute = node.value; + + if (this.renderer.backend.has(attribute)) { + const attributeData = this.renderer.backend.get(attribute); + attributeData.pbo = attribute.pbo; + } + + const nodeUniform = this.getUniformFromNode(attribute.pboNode, 'texture', this.shaderStage, this.context.label); + const textureName = this.getPropertyName(nodeUniform); + + indexNode.increaseUsage(this); // force cache generate to be used as index in x,y + const indexSnippet = indexNode.build(this, 'uint'); + + const elementNodeData = this.getDataFromNode(storageArrayElementNode); + + let propertyName = elementNodeData.propertyName; + + if (propertyName === undefined) { + // property element + + const nodeVar = this.getVarFromNode(storageArrayElementNode); + + propertyName = this.getPropertyName(nodeVar); + + // property size + + const bufferNodeData = this.getDataFromNode(node); + + let propertySizeName = bufferNodeData.propertySizeName; + + if (propertySizeName === undefined) { + propertySizeName = propertyName + 'Size'; + + this.getVarFromNode(node, propertySizeName, 'uint'); + + this.addLineFlowCode(`${propertySizeName} = uint( textureSize( ${textureName}, 0 ).x )`); + + bufferNodeData.propertySizeName = propertySizeName; + } + + // + + const { itemSize } = attribute; + + const channel = '.' + vectorComponents.join('').slice(0, itemSize); + const uvSnippet = `ivec2(${indexSnippet} % ${propertySizeName}, ${indexSnippet} / ${propertySizeName})`; + + const snippet = this.generateTextureLoad(null, textureName, uvSnippet, null, '0'); + + // + + const typePrefix = attribute.array.constructor.name.toLowerCase().charAt(0); + + let prefix = 'vec4'; + if (typePrefix === 'u') { + prefix = 'uvec4'; + } else if (typePrefix === 'i') { + prefix = 'ivec4'; + } + + this.addLineFlowCode(`${propertyName} = ${prefix}(${snippet})${channel}`); + + elementNodeData.propertyName = propertyName; + } + + return propertyName; + } + + generateTextureLoad(texture, textureProperty, uvIndexSnippet, depthSnippet, levelSnippet = '0') { + if (depthSnippet) { + return `texelFetch( ${textureProperty}, ivec3( ${uvIndexSnippet}, ${depthSnippet} ), ${levelSnippet} )`; + } else { + return `texelFetch( ${textureProperty}, ${uvIndexSnippet}, ${levelSnippet} )`; + } + } + + generateTexture(texture, textureProperty, uvSnippet, depthSnippet) { + if (texture.isDepthTexture) { + return `texture( ${textureProperty}, ${uvSnippet} ).x`; + } else { + if (depthSnippet) uvSnippet = `vec3( ${uvSnippet}, ${depthSnippet} )`; + + return `texture( ${textureProperty}, ${uvSnippet} )`; + } + } + + generateTextureLevel(texture, textureProperty, uvSnippet, levelSnippet) { + return `textureLod( ${textureProperty}, ${uvSnippet}, ${levelSnippet} )`; + } + + generateTextureGrad(texture, textureProperty, uvSnippet, gradSnippet) { + return `textureGrad( ${textureProperty}, ${uvSnippet}, ${gradSnippet[0]}, ${gradSnippet[1]} )`; + } + + generateTextureCompare( + texture, + textureProperty, + uvSnippet, + compareSnippet, + depthSnippet, + shaderStage = this.shaderStage, + ) { + if (shaderStage === 'fragment') { + return `texture( ${textureProperty}, vec3( ${uvSnippet}, ${compareSnippet} ) )`; + } else { + console.error( + `WebGPURenderer: THREE.DepthTexture.compareFunction() does not support ${shaderStage} shader.`, + ); + } + } + + getVars(shaderStage) { + const snippets = []; + + const vars = this.vars[shaderStage]; + + if (vars !== undefined) { + for (const variable of vars) { + snippets.push(`${this.getVar(variable.type, variable.name)};`); + } + } + + return snippets.join('\n\t'); + } + + getUniforms(shaderStage) { + const uniforms = this.uniforms[shaderStage]; + + const bindingSnippets = []; + const uniformGroups = {}; + + for (const uniform of uniforms) { + let snippet = null; + let group = false; + + if (uniform.type === 'texture') { + const texture = uniform.node.value; + + let typePrefix = ''; + + if (texture.isPBOTexture === true) { + const prefix = texture.source.data.data.constructor.name.toLowerCase().charAt(0); + + if (prefix === 'u' || prefix === 'i') { + typePrefix = prefix; + } + } + + if (texture.compareFunction) { + snippet = `sampler2DShadow ${uniform.name};`; + } else if (texture.isDataArrayTexture === true) { + snippet = `${typePrefix}sampler2DArray ${uniform.name};`; + } else { + snippet = `${typePrefix}sampler2D ${uniform.name};`; + } + } else if (uniform.type === 'cubeTexture') { + snippet = `samplerCube ${uniform.name};`; + } else if (uniform.type === 'texture3D') { + snippet = `sampler3D ${uniform.name};`; + } else if (uniform.type === 'buffer') { + const bufferNode = uniform.node; + const bufferType = this.getType(bufferNode.bufferType); + const bufferCount = bufferNode.bufferCount; + + const bufferCountSnippet = bufferCount > 0 ? bufferCount : ''; + snippet = `${bufferNode.name} {\n\t${bufferType} ${uniform.name}[${bufferCountSnippet}];\n};\n`; + } else { + const vectorType = this.getVectorType(uniform.type); + + snippet = `${vectorType} ${this.getPropertyName(uniform, shaderStage)};`; + + group = true; + } + + const precision = uniform.node.precision; + + if (precision !== null) { + snippet = precisionLib[precision] + ' ' + snippet; + } + + if (group) { + snippet = '\t' + snippet; + + const groupName = uniform.groupNode.name; + const groupSnippets = uniformGroups[groupName] || (uniformGroups[groupName] = []); + + groupSnippets.push(snippet); + } else { + snippet = 'uniform ' + snippet; + + bindingSnippets.push(snippet); + } + } + + let output = ''; + + for (const name in uniformGroups) { + const groupSnippets = uniformGroups[name]; + + output += this._getGLSLUniformStruct(shaderStage + '_' + name, groupSnippets.join('\n')) + '\n'; + } + + output += bindingSnippets.join('\n'); + + return output; + } + + getTypeFromAttribute(attribute) { + let nodeType = super.getTypeFromAttribute(attribute); + + if (/^[iu]/.test(nodeType) && attribute.gpuType !== IntType) { + let dataAttribute = attribute; + + if (attribute.isInterleavedBufferAttribute) dataAttribute = attribute.data; + + const array = dataAttribute.array; + + if ( + (array instanceof Uint32Array || + array instanceof Int32Array || + array instanceof Uint16Array || + array instanceof Int16Array) === false + ) { + nodeType = nodeType.slice(1); + } + } + + return nodeType; + } + + getAttributes(shaderStage) { + let snippet = ''; + + if (shaderStage === 'vertex' || shaderStage === 'compute') { + const attributes = this.getAttributesArray(); + + let location = 0; + + for (const attribute of attributes) { + snippet += `layout( location = ${location++} ) in ${attribute.type} ${attribute.name};\n`; + } + } + + return snippet; + } + + getStructMembers(struct) { + const snippets = []; + const members = struct.getMemberTypes(); + + for (let i = 0; i < members.length; i++) { + const member = members[i]; + snippets.push(`layout( location = ${i} ) out ${member} m${i};`); + } + + return snippets.join('\n'); + } + + getStructs(shaderStage) { + const snippets = []; + const structs = this.structs[shaderStage]; + + if (structs.length === 0) { + return 'layout( location = 0 ) out vec4 fragColor;\n'; + } + + for (let index = 0, length = structs.length; index < length; index++) { + const struct = structs[index]; + + let snippet = '\n'; + snippet += this.getStructMembers(struct); + snippet += '\n'; + + snippets.push(snippet); + } + + return snippets.join('\n\n'); + } + + getVaryings(shaderStage) { + let snippet = ''; + + const varyings = this.varyings; + + if (shaderStage === 'vertex' || shaderStage === 'compute') { + for (const varying of varyings) { + if (shaderStage === 'compute') varying.needsInterpolation = true; + const type = varying.type; + const flat = type.includes('int') || type.includes('uv') || type.includes('iv') ? 'flat ' : ''; + + snippet += `${flat}${varying.needsInterpolation ? 'out' : '/*out*/'} ${type} ${varying.name};\n`; + } + } else if (shaderStage === 'fragment') { + for (const varying of varyings) { + if (varying.needsInterpolation) { + const type = varying.type; + const flat = type.includes('int') || type.includes('uv') || type.includes('iv') ? 'flat ' : ''; + + snippet += `${flat}in ${type} ${varying.name};\n`; + } + } + } + + return snippet; + } + + getVertexIndex() { + return 'uint( gl_VertexID )'; + } + + getInstanceIndex() { + return 'uint( gl_InstanceID )'; + } + + getFrontFacing() { + return 'gl_FrontFacing'; + } + + getFragCoord() { + return 'gl_FragCoord'; + } + + getFragDepth() { + return 'gl_FragDepth'; + } + + isAvailable(name) { + let result = supports[name]; + + if (result === undefined) { + if (name === 'float32Filterable') { + const extentions = this.renderer.backend.extensions; + + if (extentions.has('OES_texture_float_linear')) { + extentions.get('OES_texture_float_linear'); + result = true; + } else { + result = false; + } + } + + supports[name] = result; + } + + return result; + } + + isFlipY() { + return true; + } + + registerTransform(varyingName, attributeNode) { + this.transforms.push({ varyingName, attributeNode }); + } + + getTransforms(/* shaderStage */) { + const transforms = this.transforms; + + let snippet = ''; + + for (let i = 0; i < transforms.length; i++) { + const transform = transforms[i]; + + const attributeName = this.getPropertyName(transform.attributeNode); + + snippet += `${transform.varyingName} = ${attributeName};\n\t`; + } + + return snippet; + } + + _getGLSLUniformStruct(name, vars) { + return ` +layout( std140 ) uniform ${name} { +${vars} +};`; + } + + _getGLSLVertexCode(shaderData) { + return `#version 300 es + +${this.getSignature()} + +// precision +${defaultPrecisions} + +// uniforms +${shaderData.uniforms} + +// varyings +${shaderData.varyings} + +// attributes +${shaderData.attributes} + +// codes +${shaderData.codes} + +void main() { + + // vars + ${shaderData.vars} + + // transforms + ${shaderData.transforms} + + // flow + ${shaderData.flow} + + gl_PointSize = 1.0; + +} +`; + } + + _getGLSLFragmentCode(shaderData) { + return `#version 300 es + +${this.getSignature()} + +// precision +${defaultPrecisions} + +// uniforms +${shaderData.uniforms} + +// varyings +${shaderData.varyings} + +// codes +${shaderData.codes} + +${shaderData.structs} + +void main() { + + // vars + ${shaderData.vars} + + // flow + ${shaderData.flow} + +} +`; + } + + buildCode() { + const shadersData = this.material !== null ? { fragment: {}, vertex: {} } : { compute: {} }; + + for (const shaderStage in shadersData) { + let flow = '// code\n\n'; + flow += this.flowCode[shaderStage]; + + const flowNodes = this.flowNodes[shaderStage]; + const mainNode = flowNodes[flowNodes.length - 1]; + + for (const node of flowNodes) { + const flowSlotData = this.getFlowData(node /*, shaderStage*/); + const slotName = node.name; + + if (slotName) { + if (flow.length > 0) flow += '\n'; + + flow += `\t// flow -> ${slotName}\n\t`; + } + + flow += `${flowSlotData.code}\n\t`; + + if (node === mainNode && shaderStage !== 'compute') { + flow += '// result\n\t'; + + if (shaderStage === 'vertex') { + flow += 'gl_Position = '; + flow += `${flowSlotData.result};`; + } else if (shaderStage === 'fragment') { + if (!node.outputNode.isOutputStructNode) { + flow += 'fragColor = '; + flow += `${flowSlotData.result};`; + } + } + } + } + + const stageData = shadersData[shaderStage]; + + stageData.uniforms = this.getUniforms(shaderStage); + stageData.attributes = this.getAttributes(shaderStage); + stageData.varyings = this.getVaryings(shaderStage); + stageData.vars = this.getVars(shaderStage); + stageData.structs = this.getStructs(shaderStage); + stageData.codes = this.getCodes(shaderStage); + stageData.transforms = this.getTransforms(shaderStage); + stageData.flow = flow; + } + + if (this.material !== null) { + this.vertexShader = this._getGLSLVertexCode(shadersData.vertex); + this.fragmentShader = this._getGLSLFragmentCode(shadersData.fragment); + } else { + this.computeShader = this._getGLSLVertexCode(shadersData.compute); + } + } + + getUniformFromNode(node, type, shaderStage, name = null) { + const uniformNode = super.getUniformFromNode(node, type, shaderStage, name); + const nodeData = this.getDataFromNode(node, shaderStage, this.globalCache); + + let uniformGPU = nodeData.uniformGPU; + + if (uniformGPU === undefined) { + const group = node.groupNode; + const groupName = group.name; + + const bindings = this.getBindGroupArray(groupName, shaderStage); + + if (type === 'texture') { + uniformGPU = new NodeSampledTexture(uniformNode.name, uniformNode.node, group); + bindings.push(uniformGPU); + } else if (type === 'cubeTexture') { + uniformGPU = new NodeSampledCubeTexture(uniformNode.name, uniformNode.node, group); + bindings.push(uniformGPU); + } else if (type === 'texture3D') { + uniformGPU = new NodeSampledTexture3D(uniformNode.name, uniformNode.node, group); + bindings.push(uniformGPU); + } else if (type === 'buffer') { + node.name = `NodeBuffer_${node.id}`; + uniformNode.name = `buffer${node.id}`; + + const buffer = new NodeUniformBuffer(node, group); + buffer.name = node.name; + + bindings.push(buffer); + + uniformGPU = buffer; + } else { + const uniformsStage = this.uniformGroups[shaderStage] || (this.uniformGroups[shaderStage] = {}); + + let uniformsGroup = uniformsStage[groupName]; + + if (uniformsGroup === undefined) { + uniformsGroup = new NodeUniformsGroup(shaderStage + '_' + groupName, group); + //uniformsGroup.setVisibility( gpuShaderStageLib[ shaderStage ] ); + + uniformsStage[groupName] = uniformsGroup; + + bindings.push(uniformsGroup); + } + + uniformGPU = this.getNodeUniform(uniformNode, type); + + uniformsGroup.addUniform(uniformGPU); + } + + nodeData.uniformGPU = uniformGPU; + } + + return uniformNode; + } +} + +export default GLSLNodeBuilder; diff --git a/examples-jsm/examples/renderers/webgpu/WebGPUBackend.ts b/examples-jsm/examples/renderers/webgpu/WebGPUBackend.ts new file mode 100644 index 00000000..5cbbafc0 --- /dev/null +++ b/examples-jsm/examples/renderers/webgpu/WebGPUBackend.ts @@ -0,0 +1,1194 @@ +/*// debugger tools +import 'https://greggman.github.io/webgpu-avoid-redundant-state-setting/webgpu-check-redundant-state-setting.js'; +//*/ + +import { WebGPUCoordinateSystem } from 'three'; + +import { + GPUFeatureName, + GPUTextureFormat, + GPULoadOp, + GPUStoreOp, + GPUIndexFormat, + GPUTextureViewDimension, +} from './utils/WebGPUConstants.js'; + +import WGSLNodeBuilder from './nodes/WGSLNodeBuilder.js'; +import Backend from '../common/Backend.js'; + +import WebGPUUtils from './utils/WebGPUUtils.js'; +import WebGPUAttributeUtils from './utils/WebGPUAttributeUtils.js'; +import WebGPUBindingUtils from './utils/WebGPUBindingUtils.js'; +import WebGPUPipelineUtils from './utils/WebGPUPipelineUtils.js'; +import WebGPUTextureUtils from './utils/WebGPUTextureUtils.js'; + +// + +class WebGPUBackend extends Backend { + constructor(parameters = {}) { + super(parameters); + + this.isWebGPUBackend = true; + + // some parameters require default values other than "undefined" + this.parameters.alpha = parameters.alpha === undefined ? true : parameters.alpha; + + this.parameters.antialias = parameters.antialias === true; + + if (this.parameters.antialias === true) { + this.parameters.sampleCount = parameters.sampleCount === undefined ? 4 : parameters.sampleCount; + } else { + this.parameters.sampleCount = 1; + } + + this.parameters.requiredLimits = parameters.requiredLimits === undefined ? {} : parameters.requiredLimits; + + this.trackTimestamp = parameters.trackTimestamp === true; + + this.device = null; + this.context = null; + this.colorBuffer = null; + this.defaultRenderPassdescriptor = null; + + this.utils = new WebGPUUtils(this); + this.attributeUtils = new WebGPUAttributeUtils(this); + this.bindingUtils = new WebGPUBindingUtils(this); + this.pipelineUtils = new WebGPUPipelineUtils(this); + this.textureUtils = new WebGPUTextureUtils(this); + this.occludedResolveCache = new Map(); + } + + async init(renderer) { + await super.init(renderer); + + // + + const parameters = this.parameters; + + // create the device if it is not passed with parameters + + let device; + + if (parameters.device === undefined) { + const adapterOptions = { + powerPreference: parameters.powerPreference, + }; + + const adapter = await navigator.gpu.requestAdapter(adapterOptions); + + if (adapter === null) { + throw new Error('WebGPUBackend: Unable to create WebGPU adapter.'); + } + + // feature support + + const features = Object.values(GPUFeatureName); + + const supportedFeatures = []; + + for (const name of features) { + if (adapter.features.has(name)) { + supportedFeatures.push(name); + } + } + + const deviceDescriptor = { + requiredFeatures: supportedFeatures, + requiredLimits: parameters.requiredLimits, + }; + + device = await adapter.requestDevice(deviceDescriptor); + } else { + device = parameters.device; + } + + const context = + parameters.context !== undefined ? parameters.context : renderer.domElement.getContext('webgpu'); + + this.device = device; + this.context = context; + + const alphaMode = parameters.alpha ? 'premultiplied' : 'opaque'; + + this.context.configure({ + device: this.device, + format: GPUTextureFormat.BGRA8Unorm, + usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC, + alphaMode: alphaMode, + }); + + this.updateSize(); + } + + get coordinateSystem() { + return WebGPUCoordinateSystem; + } + + async getArrayBufferAsync(attribute) { + return await this.attributeUtils.getArrayBufferAsync(attribute); + } + + getContext() { + return this.context; + } + + _getDefaultRenderPassDescriptor() { + let descriptor = this.defaultRenderPassdescriptor; + + const antialias = this.parameters.antialias; + + if (descriptor === null) { + const renderer = this.renderer; + + descriptor = { + colorAttachments: [ + { + view: null, + }, + ], + depthStencilAttachment: { + view: this.textureUtils.getDepthBuffer(renderer.depth, renderer.stencil).createView(), + }, + }; + + const colorAttachment = descriptor.colorAttachments[0]; + + if (antialias === true) { + colorAttachment.view = this.colorBuffer.createView(); + } else { + colorAttachment.resolveTarget = undefined; + } + + this.defaultRenderPassdescriptor = descriptor; + } + + const colorAttachment = descriptor.colorAttachments[0]; + + if (antialias === true) { + colorAttachment.resolveTarget = this.context.getCurrentTexture().createView(); + } else { + colorAttachment.view = this.context.getCurrentTexture().createView(); + } + + return descriptor; + } + + _getRenderPassDescriptor(renderContext) { + const renderTarget = renderContext.renderTarget; + const renderTargetData = this.get(renderTarget); + + let descriptors = renderTargetData.descriptors; + + if (descriptors === undefined) { + descriptors = []; + + renderTargetData.descriptors = descriptors; + } + + if ( + renderTargetData.width !== renderTarget.width || + renderTargetData.height !== renderTarget.height || + renderTargetData.activeMipmapLevel !== renderTarget.activeMipmapLevel || + renderTargetData.samples !== renderTarget.samples + ) { + descriptors.length = 0; + } + + let descriptor = descriptors[renderContext.activeCubeFace]; + + if (descriptor === undefined) { + const textures = renderContext.textures; + const colorAttachments = []; + + for (let i = 0; i < textures.length; i++) { + const textureData = this.get(textures[i]); + + const textureView = textureData.texture.createView({ + baseMipLevel: renderContext.activeMipmapLevel, + mipLevelCount: 1, + baseArrayLayer: renderContext.activeCubeFace, + dimension: GPUTextureViewDimension.TwoD, + }); + + let view, resolveTarget; + + if (textureData.msaaTexture !== undefined) { + view = textureData.msaaTexture.createView(); + resolveTarget = textureView; + } else { + view = textureView; + resolveTarget = undefined; + } + + colorAttachments.push({ + view, + resolveTarget, + loadOp: GPULoadOp.Load, + storeOp: GPUStoreOp.Store, + }); + } + + const depthTextureData = this.get(renderContext.depthTexture); + + const depthStencilAttachment = { + view: depthTextureData.texture.createView(), + }; + + descriptor = { + colorAttachments, + depthStencilAttachment, + }; + + descriptors[renderContext.activeCubeFace] = descriptor; + + renderTargetData.width = renderTarget.width; + renderTargetData.height = renderTarget.height; + renderTargetData.samples = renderTarget.samples; + renderTargetData.activeMipmapLevel = renderTarget.activeMipmapLevel; + } + + return descriptor; + } + + beginRender(renderContext) { + const renderContextData = this.get(renderContext); + + const device = this.device; + const occlusionQueryCount = renderContext.occlusionQueryCount; + + let occlusionQuerySet; + + if (occlusionQueryCount > 0) { + if (renderContextData.currentOcclusionQuerySet) renderContextData.currentOcclusionQuerySet.destroy(); + if (renderContextData.currentOcclusionQueryBuffer) renderContextData.currentOcclusionQueryBuffer.destroy(); + + // Get a reference to the array of objects with queries. The renderContextData property + // can be changed by another render pass before the buffer.mapAsyc() completes. + renderContextData.currentOcclusionQuerySet = renderContextData.occlusionQuerySet; + renderContextData.currentOcclusionQueryBuffer = renderContextData.occlusionQueryBuffer; + renderContextData.currentOcclusionQueryObjects = renderContextData.occlusionQueryObjects; + + // + + occlusionQuerySet = device.createQuerySet({ type: 'occlusion', count: occlusionQueryCount }); + + renderContextData.occlusionQuerySet = occlusionQuerySet; + renderContextData.occlusionQueryIndex = 0; + renderContextData.occlusionQueryObjects = new Array(occlusionQueryCount); + + renderContextData.lastOcclusionObject = null; + } + + let descriptor; + + if (renderContext.textures === null) { + descriptor = this._getDefaultRenderPassDescriptor(); + } else { + descriptor = this._getRenderPassDescriptor(renderContext); + } + + this.initTimestampQuery(renderContext, descriptor); + + descriptor.occlusionQuerySet = occlusionQuerySet; + + const depthStencilAttachment = descriptor.depthStencilAttachment; + + if (renderContext.textures !== null) { + const colorAttachments = descriptor.colorAttachments; + + for (let i = 0; i < colorAttachments.length; i++) { + const colorAttachment = colorAttachments[i]; + + if (renderContext.clearColor) { + colorAttachment.clearValue = renderContext.clearColorValue; + colorAttachment.loadOp = GPULoadOp.Clear; + colorAttachment.storeOp = GPUStoreOp.Store; + } else { + colorAttachment.loadOp = GPULoadOp.Load; + colorAttachment.storeOp = GPUStoreOp.Store; + } + } + } else { + const colorAttachment = descriptor.colorAttachments[0]; + + if (renderContext.clearColor) { + colorAttachment.clearValue = renderContext.clearColorValue; + colorAttachment.loadOp = GPULoadOp.Clear; + colorAttachment.storeOp = GPUStoreOp.Store; + } else { + colorAttachment.loadOp = GPULoadOp.Load; + colorAttachment.storeOp = GPUStoreOp.Store; + } + } + + // + + if (renderContext.depth) { + if (renderContext.clearDepth) { + depthStencilAttachment.depthClearValue = renderContext.clearDepthValue; + depthStencilAttachment.depthLoadOp = GPULoadOp.Clear; + depthStencilAttachment.depthStoreOp = GPUStoreOp.Store; + } else { + depthStencilAttachment.depthLoadOp = GPULoadOp.Load; + depthStencilAttachment.depthStoreOp = GPUStoreOp.Store; + } + } + + if (renderContext.stencil) { + if (renderContext.clearStencil) { + depthStencilAttachment.stencilClearValue = renderContext.clearStencilValue; + depthStencilAttachment.stencilLoadOp = GPULoadOp.Clear; + depthStencilAttachment.stencilStoreOp = GPUStoreOp.Store; + } else { + depthStencilAttachment.stencilLoadOp = GPULoadOp.Load; + depthStencilAttachment.stencilStoreOp = GPUStoreOp.Store; + } + } + + // + + const encoder = device.createCommandEncoder({ label: 'renderContext_' + renderContext.id }); + const currentPass = encoder.beginRenderPass(descriptor); + + // + + renderContextData.descriptor = descriptor; + renderContextData.encoder = encoder; + renderContextData.currentPass = currentPass; + renderContextData.currentSets = { attributes: {} }; + + // + + if (renderContext.viewport) { + this.updateViewport(renderContext); + } + + if (renderContext.scissor) { + const { x, y, width, height } = renderContext.scissorValue; + + currentPass.setScissorRect(x, renderContext.height - height - y, width, height); + } + } + + finishRender(renderContext) { + const renderContextData = this.get(renderContext); + const occlusionQueryCount = renderContext.occlusionQueryCount; + + if (renderContextData.renderBundles !== undefined && renderContextData.renderBundles.length > 0) { + renderContextData.registerBundlesPhase = false; + renderContextData.currentPass.executeBundles(renderContextData.renderBundles); + } + + if (occlusionQueryCount > renderContextData.occlusionQueryIndex) { + renderContextData.currentPass.endOcclusionQuery(); + } + + renderContextData.currentPass.end(); + + if (occlusionQueryCount > 0) { + const bufferSize = occlusionQueryCount * 8; // 8 byte entries for query results + + // + + let queryResolveBuffer = this.occludedResolveCache.get(bufferSize); + + if (queryResolveBuffer === undefined) { + queryResolveBuffer = this.device.createBuffer({ + size: bufferSize, + usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC, + }); + + this.occludedResolveCache.set(bufferSize, queryResolveBuffer); + } + + // + + const readBuffer = this.device.createBuffer({ + size: bufferSize, + usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ, + }); + + // two buffers required here - WebGPU doesn't allow usage of QUERY_RESOLVE & MAP_READ to be combined + renderContextData.encoder.resolveQuerySet( + renderContextData.occlusionQuerySet, + 0, + occlusionQueryCount, + queryResolveBuffer, + 0, + ); + renderContextData.encoder.copyBufferToBuffer(queryResolveBuffer, 0, readBuffer, 0, bufferSize); + + renderContextData.occlusionQueryBuffer = readBuffer; + + // + + this.resolveOccludedAsync(renderContext); + } + + this.prepareTimestampBuffer(renderContext, renderContextData.encoder); + + this.device.queue.submit([renderContextData.encoder.finish()]); + + // + + if (renderContext.textures !== null) { + const textures = renderContext.textures; + + for (let i = 0; i < textures.length; i++) { + const texture = textures[i]; + + if (texture.generateMipmaps === true) { + this.textureUtils.generateMipmaps(texture); + } + } + } + } + + isOccluded(renderContext, object) { + const renderContextData = this.get(renderContext); + + return renderContextData.occluded && renderContextData.occluded.has(object); + } + + async resolveOccludedAsync(renderContext) { + const renderContextData = this.get(renderContext); + + // handle occlusion query results + + const { currentOcclusionQueryBuffer, currentOcclusionQueryObjects } = renderContextData; + + if (currentOcclusionQueryBuffer && currentOcclusionQueryObjects) { + const occluded = new WeakSet(); + + renderContextData.currentOcclusionQueryObjects = null; + renderContextData.currentOcclusionQueryBuffer = null; + + await currentOcclusionQueryBuffer.mapAsync(GPUMapMode.READ); + + const buffer = currentOcclusionQueryBuffer.getMappedRange(); + const results = new BigUint64Array(buffer); + + for (let i = 0; i < currentOcclusionQueryObjects.length; i++) { + if (results[i] !== 0n) { + occluded.add(currentOcclusionQueryObjects[i]); + } + } + + currentOcclusionQueryBuffer.destroy(); + + renderContextData.occluded = occluded; + } + } + + updateViewport(renderContext) { + const { currentPass } = this.get(renderContext); + const { x, y, width, height, minDepth, maxDepth } = renderContext.viewportValue; + + currentPass.setViewport(x, renderContext.height - height - y, width, height, minDepth, maxDepth); + } + + clear(color, depth, stencil, renderTargetData = null) { + const device = this.device; + const renderer = this.renderer; + + let colorAttachments = []; + + let depthStencilAttachment; + let clearValue; + + let supportsDepth; + let supportsStencil; + + if (color) { + const clearColor = this.getClearColor(); + + clearValue = { r: clearColor.r, g: clearColor.g, b: clearColor.b, a: clearColor.a }; + } + + if (renderTargetData === null) { + supportsDepth = renderer.depth; + supportsStencil = renderer.stencil; + + const descriptor = this._getDefaultRenderPassDescriptor(); + + if (color) { + colorAttachments = descriptor.colorAttachments; + + const colorAttachment = colorAttachments[0]; + + colorAttachment.clearValue = clearValue; + colorAttachment.loadOp = GPULoadOp.Clear; + colorAttachment.storeOp = GPUStoreOp.Store; + } + + if (supportsDepth || supportsStencil) { + depthStencilAttachment = descriptor.depthStencilAttachment; + } + } else { + supportsDepth = renderTargetData.depth; + supportsStencil = renderTargetData.stencil; + + if (color) { + for (const texture of renderTargetData.textures) { + const textureData = this.get(texture); + const textureView = textureData.texture.createView(); + + let view, resolveTarget; + + if (textureData.msaaTexture !== undefined) { + view = textureData.msaaTexture.createView(); + resolveTarget = textureView; + } else { + view = textureView; + resolveTarget = undefined; + } + + colorAttachments.push({ + view, + resolveTarget, + clearValue, + loadOp: GPULoadOp.Clear, + storeOp: GPUStoreOp.Store, + }); + } + } + + if (supportsDepth || supportsStencil) { + const depthTextureData = this.get(renderTargetData.depthTexture); + + depthStencilAttachment = { + view: depthTextureData.texture.createView(), + }; + } + } + + // + + if (supportsDepth) { + if (depth) { + depthStencilAttachment.depthLoadOp = GPULoadOp.Clear; + depthStencilAttachment.depthClearValue = renderer.getClearDepth(); + depthStencilAttachment.depthStoreOp = GPUStoreOp.Store; + } else { + depthStencilAttachment.depthLoadOp = GPULoadOp.Load; + depthStencilAttachment.depthStoreOp = GPUStoreOp.Store; + } + } + + // + + if (supportsStencil) { + if (stencil) { + depthStencilAttachment.stencilLoadOp = GPULoadOp.Clear; + depthStencilAttachment.stencilClearValue = renderer.getClearStencil(); + depthStencilAttachment.stencilStoreOp = GPUStoreOp.Store; + } else { + depthStencilAttachment.stencilLoadOp = GPULoadOp.Load; + depthStencilAttachment.stencilStoreOp = GPUStoreOp.Store; + } + } + + // + + const encoder = device.createCommandEncoder({}); + const currentPass = encoder.beginRenderPass({ + colorAttachments, + depthStencilAttachment, + }); + + currentPass.end(); + + device.queue.submit([encoder.finish()]); + } + + // compute + + beginCompute(computeGroup) { + const groupGPU = this.get(computeGroup); + + const descriptor = {}; + + this.initTimestampQuery(computeGroup, descriptor); + + groupGPU.cmdEncoderGPU = this.device.createCommandEncoder(); + + groupGPU.passEncoderGPU = groupGPU.cmdEncoderGPU.beginComputePass(descriptor); + } + + compute(computeGroup, computeNode, bindings, pipeline) { + const { passEncoderGPU } = this.get(computeGroup); + + // pipeline + + const pipelineGPU = this.get(pipeline).pipeline; + passEncoderGPU.setPipeline(pipelineGPU); + + // bind groups + + for (let i = 0, l = bindings.length; i < l; i++) { + const bindGroup = bindings[i]; + const bindingsData = this.get(bindGroup); + + passEncoderGPU.setBindGroup(i, bindingsData.group); + } + + passEncoderGPU.dispatchWorkgroups(computeNode.dispatchCount); + } + + finishCompute(computeGroup) { + const groupData = this.get(computeGroup); + + groupData.passEncoderGPU.end(); + + this.prepareTimestampBuffer(computeGroup, groupData.cmdEncoderGPU); + + this.device.queue.submit([groupData.cmdEncoderGPU.finish()]); + } + + // render object + + draw(renderObject, info) { + const { object, geometry, context, pipeline } = renderObject; + + const bindings = renderObject.getBindings(); + const contextData = this.get(context); + const pipelineGPU = this.get(pipeline).pipeline; + const currentSets = contextData.currentSets; + + const renderObjectData = this.get(renderObject); + + const { bundleEncoder, renderBundle, lastPipelineGPU } = renderObjectData; + + const renderContextData = this.get(context); + + if ( + renderContextData.registerBundlesPhase === true && + bundleEncoder !== undefined && + lastPipelineGPU === pipelineGPU + ) { + renderContextData.renderBundles.push(renderBundle); + return; + } + + const passEncoderGPU = this.renderer._currentRenderBundle + ? this.createBundleEncoder(context, renderObject) + : contextData.currentPass; + + // pipeline + + if (currentSets.pipeline !== pipelineGPU) { + passEncoderGPU.setPipeline(pipelineGPU); + + currentSets.pipeline = pipelineGPU; + } + + // bind groups + + for (let i = 0, l = bindings.length; i < l; i++) { + const bindGroup = bindings[i]; + const bindingsData = this.get(bindGroup); + + passEncoderGPU.setBindGroup(i, bindingsData.group); + } + + // attributes + + const index = renderObject.getIndex(); + + const hasIndex = index !== null; + + // index + + if (hasIndex === true) { + if (currentSets.index !== index) { + const buffer = this.get(index).buffer; + const indexFormat = index.array instanceof Uint16Array ? GPUIndexFormat.Uint16 : GPUIndexFormat.Uint32; + + passEncoderGPU.setIndexBuffer(buffer, indexFormat); + + currentSets.index = index; + } + } + + // vertex buffers + + const vertexBuffers = renderObject.getVertexBuffers(); + + for (let i = 0, l = vertexBuffers.length; i < l; i++) { + const vertexBuffer = vertexBuffers[i]; + + if (currentSets.attributes[i] !== vertexBuffer) { + const buffer = this.get(vertexBuffer).buffer; + passEncoderGPU.setVertexBuffer(i, buffer); + + currentSets.attributes[i] = vertexBuffer; + } + } + + // occlusion queries - handle multiple consecutive draw calls for an object + + if (contextData.occlusionQuerySet !== undefined) { + const lastObject = contextData.lastOcclusionObject; + + if (lastObject !== object) { + if (lastObject !== null && lastObject.occlusionTest === true) { + passEncoderGPU.endOcclusionQuery(); + contextData.occlusionQueryIndex++; + } + + if (object.occlusionTest === true) { + passEncoderGPU.beginOcclusionQuery(contextData.occlusionQueryIndex); + contextData.occlusionQueryObjects[contextData.occlusionQueryIndex] = object; + } + + contextData.lastOcclusionObject = object; + } + } + + // draw + + const drawRange = renderObject.drawRange; + const firstVertex = drawRange.start; + + const instanceCount = this.getInstanceCount(renderObject); + if (instanceCount === 0) return; + + if (hasIndex === true) { + const indexCount = drawRange.count !== Infinity ? drawRange.count : index.count; + + passEncoderGPU.drawIndexed(indexCount, instanceCount, firstVertex, 0, 0); + + info.update(object, indexCount, instanceCount); + } else { + const positionAttribute = geometry.attributes.position; + const vertexCount = drawRange.count !== Infinity ? drawRange.count : positionAttribute.count; + + passEncoderGPU.draw(vertexCount, instanceCount, firstVertex, 0); + + info.update(object, vertexCount, instanceCount); + } + + if (this.renderer._currentRenderBundle) { + const renderBundle = passEncoderGPU.finish(); + renderObjectData.lastPipelineGPU = pipelineGPU; + renderObjectData.renderBundle = renderBundle; + renderObjectData.bundleEncoder = passEncoderGPU; + } + } + + // cache key + + needsRenderUpdate(renderObject) { + const data = this.get(renderObject); + + const { object, material } = renderObject; + + const utils = this.utils; + + const sampleCount = utils.getSampleCount(renderObject.context); + const colorSpace = utils.getCurrentColorSpace(renderObject.context); + const colorFormat = utils.getCurrentColorFormat(renderObject.context); + const depthStencilFormat = utils.getCurrentDepthStencilFormat(renderObject.context); + const primitiveTopology = utils.getPrimitiveTopology(object, material); + + let needsUpdate = false; + + if ( + data.material !== material || + data.materialVersion !== material.version || + data.transparent !== material.transparent || + data.blending !== material.blending || + data.premultipliedAlpha !== material.premultipliedAlpha || + data.blendSrc !== material.blendSrc || + data.blendDst !== material.blendDst || + data.blendEquation !== material.blendEquation || + data.blendSrcAlpha !== material.blendSrcAlpha || + data.blendDstAlpha !== material.blendDstAlpha || + data.blendEquationAlpha !== material.blendEquationAlpha || + data.colorWrite !== material.colorWrite || + data.depthWrite !== material.depthWrite || + data.depthTest !== material.depthTest || + data.depthFunc !== material.depthFunc || + data.stencilWrite !== material.stencilWrite || + data.stencilFunc !== material.stencilFunc || + data.stencilFail !== material.stencilFail || + data.stencilZFail !== material.stencilZFail || + data.stencilZPass !== material.stencilZPass || + data.stencilFuncMask !== material.stencilFuncMask || + data.stencilWriteMask !== material.stencilWriteMask || + data.side !== material.side || + data.alphaToCoverage !== material.alphaToCoverage || + data.sampleCount !== sampleCount || + data.colorSpace !== colorSpace || + data.colorFormat !== colorFormat || + data.depthStencilFormat !== depthStencilFormat || + data.primitiveTopology !== primitiveTopology || + data.clippingContextVersion !== renderObject.clippingContextVersion + ) { + data.material = material; + data.materialVersion = material.version; + data.transparent = material.transparent; + data.blending = material.blending; + data.premultipliedAlpha = material.premultipliedAlpha; + data.blendSrc = material.blendSrc; + data.blendDst = material.blendDst; + data.blendEquation = material.blendEquation; + data.blendSrcAlpha = material.blendSrcAlpha; + data.blendDstAlpha = material.blendDstAlpha; + data.blendEquationAlpha = material.blendEquationAlpha; + data.colorWrite = material.colorWrite; + data.depthWrite = material.depthWrite; + data.depthTest = material.depthTest; + data.depthFunc = material.depthFunc; + data.stencilWrite = material.stencilWrite; + data.stencilFunc = material.stencilFunc; + data.stencilFail = material.stencilFail; + data.stencilZFail = material.stencilZFail; + data.stencilZPass = material.stencilZPass; + data.stencilFuncMask = material.stencilFuncMask; + data.stencilWriteMask = material.stencilWriteMask; + data.side = material.side; + data.alphaToCoverage = material.alphaToCoverage; + data.sampleCount = sampleCount; + data.colorSpace = colorSpace; + data.colorFormat = colorFormat; + data.depthStencilFormat = depthStencilFormat; + data.primitiveTopology = primitiveTopology; + data.clippingContextVersion = renderObject.clippingContextVersion; + + needsUpdate = true; + } + + return needsUpdate; + } + + getRenderCacheKey(renderObject) { + const { object, material } = renderObject; + + const utils = this.utils; + const renderContext = renderObject.context; + + return [ + material.transparent, + material.blending, + material.premultipliedAlpha, + material.blendSrc, + material.blendDst, + material.blendEquation, + material.blendSrcAlpha, + material.blendDstAlpha, + material.blendEquationAlpha, + material.colorWrite, + material.depthWrite, + material.depthTest, + material.depthFunc, + material.stencilWrite, + material.stencilFunc, + material.stencilFail, + material.stencilZFail, + material.stencilZPass, + material.stencilFuncMask, + material.stencilWriteMask, + material.side, + utils.getSampleCount(renderContext), + utils.getCurrentColorSpace(renderContext), + utils.getCurrentColorFormat(renderContext), + utils.getCurrentDepthStencilFormat(renderContext), + utils.getPrimitiveTopology(object, material), + renderObject.clippingContextVersion, + ].join(); + } + + // textures + + createSampler(texture) { + this.textureUtils.createSampler(texture); + } + + destroySampler(texture) { + this.textureUtils.destroySampler(texture); + } + + createDefaultTexture(texture) { + this.textureUtils.createDefaultTexture(texture); + } + + createTexture(texture, options) { + this.textureUtils.createTexture(texture, options); + } + + updateTexture(texture, options) { + this.textureUtils.updateTexture(texture, options); + } + + generateMipmaps(texture) { + this.textureUtils.generateMipmaps(texture); + } + + destroyTexture(texture) { + this.textureUtils.destroyTexture(texture); + } + + copyTextureToBuffer(texture, x, y, width, height) { + return this.textureUtils.copyTextureToBuffer(texture, x, y, width, height); + } + + initTimestampQuery(renderContext, descriptor) { + if (!this.hasFeature(GPUFeatureName.TimestampQuery) || !this.trackTimestamp) return; + + const renderContextData = this.get(renderContext); + + if (!renderContextData.timeStampQuerySet) { + // Create a GPUQuerySet which holds 2 timestamp query results: one for the + // beginning and one for the end of compute pass execution. + const timeStampQuerySet = this.device.createQuerySet({ type: 'timestamp', count: 2 }); + + const timestampWrites = { + querySet: timeStampQuerySet, + beginningOfPassWriteIndex: 0, // Write timestamp in index 0 when pass begins. + endOfPassWriteIndex: 1, // Write timestamp in index 1 when pass ends. + }; + + Object.assign(descriptor, { + timestampWrites, + }); + + renderContextData.timeStampQuerySet = timeStampQuerySet; + } + } + + // timestamp utils + + prepareTimestampBuffer(renderContext, encoder) { + if (!this.hasFeature(GPUFeatureName.TimestampQuery) || !this.trackTimestamp) return; + + const renderContextData = this.get(renderContext); + + const size = 2 * BigInt64Array.BYTES_PER_ELEMENT; + const resolveBuffer = this.device.createBuffer({ + size, + usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC, + }); + + const resultBuffer = this.device.createBuffer({ + size, + usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ, + }); + + encoder.resolveQuerySet(renderContextData.timeStampQuerySet, 0, 2, resolveBuffer, 0); + encoder.copyBufferToBuffer(resolveBuffer, 0, resultBuffer, 0, size); + + renderContextData.currentTimestampQueryBuffer = resultBuffer; + } + + async resolveTimestampAsync(renderContext, type = 'render') { + if (!this.hasFeature(GPUFeatureName.TimestampQuery) || !this.trackTimestamp) return; + + const renderContextData = this.get(renderContext); + const { currentTimestampQueryBuffer } = renderContextData; + + if (currentTimestampQueryBuffer === undefined) return; + + const buffer = currentTimestampQueryBuffer; + + try { + await buffer.mapAsync(GPUMapMode.READ); + const times = new BigUint64Array(buffer.getMappedRange()); + const duration = Number(times[1] - times[0]) / 1000000; + this.renderer.info.updateTimestamp(type, duration); + } catch (error) { + console.error(`Error mapping buffer: ${error}`); + // Optionally handle the error, e.g., re-queue the buffer or skip it + } finally { + buffer.unmap(); + } + } + + // node builder + + createNodeBuilder(object, renderer) { + return new WGSLNodeBuilder(object, renderer); + } + + // program + + createProgram(program) { + const programGPU = this.get(program); + + programGPU.module = { + module: this.device.createShaderModule({ code: program.code, label: program.stage }), + entryPoint: 'main', + }; + } + + destroyProgram(program) { + this.delete(program); + } + + // pipelines + + createRenderPipeline(renderObject, promises) { + this.pipelineUtils.createRenderPipeline(renderObject, promises); + } + + createComputePipeline(computePipeline, bindings) { + this.pipelineUtils.createComputePipeline(computePipeline, bindings); + } + + createBundleEncoder(renderContext, renderObject) { + return this.pipelineUtils.createBundleEncoder(renderContext, renderObject); + } + + // bindings + + createBindings(bindGroup) { + this.bindingUtils.createBindings(bindGroup); + } + + updateBindings(bindGroup) { + this.bindingUtils.createBindings(bindGroup); + } + + updateBinding(binding) { + this.bindingUtils.updateBinding(binding); + } + + // attributes + + createIndexAttribute(attribute) { + this.attributeUtils.createAttribute( + attribute, + GPUBufferUsage.INDEX | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST, + ); + } + + createAttribute(attribute) { + this.attributeUtils.createAttribute( + attribute, + GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST, + ); + } + + createStorageAttribute(attribute) { + this.attributeUtils.createAttribute( + attribute, + GPUBufferUsage.STORAGE | GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST, + ); + } + + updateAttribute(attribute) { + this.attributeUtils.updateAttribute(attribute); + } + + destroyAttribute(attribute) { + this.attributeUtils.destroyAttribute(attribute); + } + + // canvas + + updateSize() { + this.colorBuffer = this.textureUtils.getColorBuffer(); + this.defaultRenderPassdescriptor = null; + } + + // utils public + + getMaxAnisotropy() { + return 16; + } + + hasFeature(name) { + return this.device.features.has(name); + } + + copyTextureToTexture(srcTexture, dstTexture, srcRegion = null, dstPosition = null, level = 0) { + let dstX = 0; + let dstY = 0; + + if (dstPosition !== null) { + dstX = dstPosition.x; + dstY = dstPosition.y; + } + + const encoder = this.device.createCommandEncoder({ + label: 'copyTextureToTexture_' + srcTexture.id + '_' + dstTexture.id, + }); + + const sourceGPU = this.get(srcTexture).texture; + const destinationGPU = this.get(dstTexture).texture; + + encoder.copyTextureToTexture( + { + texture: sourceGPU, + mipLevel: level, + origin: { x: 0, y: 0, z: 0 }, + }, + { + texture: destinationGPU, + mipLevel: level, + origin: { x: dstX, y: dstY, z: 0 }, + }, + [srcTexture.image.width, srcTexture.image.height], + ); + + this.device.queue.submit([encoder.finish()]); + } + + copyFramebufferToTexture(texture, renderContext) { + const renderContextData = this.get(renderContext); + + const { encoder, descriptor } = renderContextData; + + let sourceGPU = null; + + if (renderContext.renderTarget) { + if (texture.isDepthTexture) { + sourceGPU = this.get(renderContext.depthTexture).texture; + } else { + sourceGPU = this.get(renderContext.textures[0]).texture; + } + } else { + if (texture.isDepthTexture) { + sourceGPU = this.textureUtils.getDepthBuffer(renderContext.depth, renderContext.stencil); + } else { + sourceGPU = this.context.getCurrentTexture(); + } + } + + const destinationGPU = this.get(texture).texture; + + if (sourceGPU.format !== destinationGPU.format) { + console.error( + 'WebGPUBackend: copyFramebufferToTexture: Source and destination formats do not match.', + sourceGPU.format, + destinationGPU.format, + ); + + return; + } + + renderContextData.currentPass.end(); + + encoder.copyTextureToTexture( + { + texture: sourceGPU, + origin: { x: 0, y: 0, z: 0 }, + }, + { + texture: destinationGPU, + }, + [texture.image.width, texture.image.height], + ); + + if (texture.generateMipmaps) this.textureUtils.generateMipmaps(texture); + + descriptor.colorAttachments[0].loadOp = GPULoadOp.Load; + if (renderContext.depth) descriptor.depthStencilAttachment.depthLoadOp = GPULoadOp.Load; + if (renderContext.stencil) descriptor.depthStencilAttachment.stencilLoadOp = GPULoadOp.Load; + + renderContextData.currentPass = encoder.beginRenderPass(descriptor); + renderContextData.currentSets = { attributes: {} }; + } +} + +export default WebGPUBackend; diff --git a/examples-jsm/examples/renderers/webgpu/WebGPURenderer.ts b/examples-jsm/examples/renderers/webgpu/WebGPURenderer.ts new file mode 100644 index 00000000..1e548639 --- /dev/null +++ b/examples-jsm/examples/renderers/webgpu/WebGPURenderer.ts @@ -0,0 +1,43 @@ +import WebGPU from '../../capabilities/WebGPU.js'; + +import Renderer from '../common/Renderer.js'; +import WebGLBackend from '../webgl/WebGLBackend.js'; +import WebGPUBackend from './WebGPUBackend.js'; +/* +const debugHandler = { + + get: function ( target, name ) { + + // Add |update + if ( /^(create|destroy)/.test( name ) ) console.log( 'WebGPUBackend.' + name ); + + return target[ name ]; + + } + +}; +*/ +class WebGPURenderer extends Renderer { + constructor(parameters = {}) { + let BackendClass; + + if (parameters.forceWebGL) { + BackendClass = WebGLBackend; + } else if (WebGPU.isAvailable()) { + BackendClass = WebGPUBackend; + } else { + BackendClass = WebGLBackend; + + console.warn('THREE.WebGPURenderer: WebGPU is not available, running under WebGL2 backend.'); + } + + const backend = new BackendClass(parameters); + + //super( new Proxy( backend, debugHandler ) ); + super(backend, parameters); + + this.isWebGPURenderer = true; + } +} + +export default WebGPURenderer; diff --git a/examples-jsm/examples/renderers/webgpu/nodes/WGSLNodeBuilder.ts b/examples-jsm/examples/renderers/webgpu/nodes/WGSLNodeBuilder.ts new file mode 100644 index 00000000..410f2189 --- /dev/null +++ b/examples-jsm/examples/renderers/webgpu/nodes/WGSLNodeBuilder.ts @@ -0,0 +1,1009 @@ +import { NoColorSpace, FloatType } from 'three'; + +import NodeUniformsGroup from '../../common/nodes/NodeUniformsGroup.js'; + +import NodeSampler from '../../common/nodes/NodeSampler.js'; +import { + NodeSampledTexture, + NodeSampledCubeTexture, + NodeSampledTexture3D, +} from '../../common/nodes/NodeSampledTexture.js'; + +import NodeUniformBuffer from '../../common/nodes/NodeUniformBuffer.js'; +import NodeStorageBuffer from '../../common/nodes/NodeStorageBuffer.js'; + +import { NodeBuilder, CodeNode } from '../../../nodes/Nodes.js'; + +import { getFormat } from '../utils/WebGPUTextureUtils.js'; + +import WGSLNodeParser from './WGSLNodeParser.js'; +import { GPUStorageTextureAccess } from '../utils/WebGPUConstants.js'; + +// GPUShaderStage is not defined in browsers not supporting WebGPU +const GPUShaderStage = self.GPUShaderStage; + +const gpuShaderStageLib = { + vertex: GPUShaderStage ? GPUShaderStage.VERTEX : 1, + fragment: GPUShaderStage ? GPUShaderStage.FRAGMENT : 2, + compute: GPUShaderStage ? GPUShaderStage.COMPUTE : 4, +}; + +const supports = { + swizzleAssign: false, + storageBuffer: true, +}; + +const wgslFnOpLib = { + '^^': 'threejs_xor', +}; + +const wgslTypeLib = { + float: 'f32', + int: 'i32', + uint: 'u32', + bool: 'bool', + color: 'vec3', + + vec2: 'vec2', + ivec2: 'vec2', + uvec2: 'vec2', + bvec2: 'vec2', + + vec3: 'vec3', + ivec3: 'vec3', + uvec3: 'vec3', + bvec3: 'vec3', + + vec4: 'vec4', + ivec4: 'vec4', + uvec4: 'vec4', + bvec4: 'vec4', + + mat2: 'mat2x2', + imat2: 'mat2x2', + umat2: 'mat2x2', + bmat2: 'mat2x2', + + mat3: 'mat3x3', + imat3: 'mat3x3', + umat3: 'mat3x3', + bmat3: 'mat3x3', + + mat4: 'mat4x4', + imat4: 'mat4x4', + umat4: 'mat4x4', + bmat4: 'mat4x4', +}; + +const wgslMethods = { + dFdx: 'dpdx', + dFdy: '- dpdy', + mod_float: 'threejs_mod_float', + mod_vec2: 'threejs_mod_vec2', + mod_vec3: 'threejs_mod_vec3', + mod_vec4: 'threejs_mod_vec4', + equals_bool: 'threejs_equals_bool', + equals_bvec2: 'threejs_equals_bvec2', + equals_bvec3: 'threejs_equals_bvec3', + equals_bvec4: 'threejs_equals_bvec4', + lessThanEqual: 'threejs_lessThanEqual', + greaterThan: 'threejs_greaterThan', + inversesqrt: 'inverseSqrt', + bitcast: 'bitcast', +}; + +const wgslPolyfill = { + threejs_xor: new CodeNode(` +fn threejs_xor( a : bool, b : bool ) -> bool { + + return ( a || b ) && !( a && b ); + +} +`), + lessThanEqual: new CodeNode(` +fn threejs_lessThanEqual( a : vec3, b : vec3 ) -> vec3 { + + return vec3( a.x <= b.x, a.y <= b.y, a.z <= b.z ); + +} +`), + greaterThan: new CodeNode(` +fn threejs_greaterThan( a : vec3, b : vec3 ) -> vec3 { + + return vec3( a.x > b.x, a.y > b.y, a.z > b.z ); + +} +`), + mod_float: new CodeNode('fn threejs_mod_float( x : f32, y : f32 ) -> f32 { return x - y * floor( x / y ); }'), + mod_vec2: new CodeNode('fn threejs_mod_vec2( x : vec2f, y : vec2f ) -> vec2f { return x - y * floor( x / y ); }'), + mod_vec3: new CodeNode('fn threejs_mod_vec3( x : vec3f, y : vec3f ) -> vec3f { return x - y * floor( x / y ); }'), + mod_vec4: new CodeNode('fn threejs_mod_vec4( x : vec4f, y : vec4f ) -> vec4f { return x - y * floor( x / y ); }'), + equals_bool: new CodeNode('fn threejs_equals_bool( a : bool, b : bool ) -> bool { return a == b; }'), + equals_bvec2: new CodeNode( + 'fn threejs_equals_bvec2( a : vec2f, b : vec2f ) -> vec2 { return vec2( a.x == b.x, a.y == b.y ); }', + ), + equals_bvec3: new CodeNode( + 'fn threejs_equals_bvec3( a : vec3f, b : vec3f ) -> vec3 { return vec3( a.x == b.x, a.y == b.y, a.z == b.z ); }', + ), + equals_bvec4: new CodeNode( + 'fn threejs_equals_bvec4( a : vec4f, b : vec4f ) -> vec4 { return vec4( a.x == b.x, a.y == b.y, a.z == b.z, a.w == b.w ); }', + ), + repeatWrapping: new CodeNode(` +fn threejs_repeatWrapping( uv : vec2, dimension : vec2 ) -> vec2 { + + let uvScaled = vec2( uv * vec2( dimension ) ); + + return ( ( uvScaled % dimension ) + dimension ) % dimension; + +} +`), + biquadraticTexture: new CodeNode(` +fn threejs_biquadraticTexture( map : texture_2d, coord : vec2f, level : i32 ) -> vec4f { + + let res = vec2f( textureDimensions( map, level ) ); + + let uvScaled = coord * res; + let uvWrapping = ( ( uvScaled % res ) + res ) % res; + + // https://www.shadertoy.com/view/WtyXRy + + let uv = uvWrapping - 0.5; + let iuv = floor( uv ); + let f = fract( uv ); + + let rg1 = textureLoad( map, vec2i( iuv + vec2( 0.5, 0.5 ) ), level ); + let rg2 = textureLoad( map, vec2i( iuv + vec2( 1.5, 0.5 ) ), level ); + let rg3 = textureLoad( map, vec2i( iuv + vec2( 0.5, 1.5 ) ), level ); + let rg4 = textureLoad( map, vec2i( iuv + vec2( 1.5, 1.5 ) ), level ); + + return mix( mix( rg1, rg2, f.x ), mix( rg3, rg4, f.x ), f.y ); + +} +`), +}; + +class WGSLNodeBuilder extends NodeBuilder { + constructor(object, renderer) { + super(object, renderer, new WGSLNodeParser()); + + this.uniformGroups = {}; + + this.builtins = {}; + } + + needsColorSpaceToLinear(texture) { + return texture.isVideoTexture === true && texture.colorSpace !== NoColorSpace; + } + + _generateTextureSample(texture, textureProperty, uvSnippet, depthSnippet, shaderStage = this.shaderStage) { + if (shaderStage === 'fragment') { + if (depthSnippet) { + return `textureSample( ${textureProperty}, ${textureProperty}_sampler, ${uvSnippet}, ${depthSnippet} )`; + } else { + return `textureSample( ${textureProperty}, ${textureProperty}_sampler, ${uvSnippet} )`; + } + } else if (this.isFilteredTexture(texture)) { + return this.generateFilteredTexture(texture, textureProperty, uvSnippet); + } else { + return this.generateTextureLod(texture, textureProperty, uvSnippet, '0'); + } + } + + _generateVideoSample(textureProperty, uvSnippet, shaderStage = this.shaderStage) { + if (shaderStage === 'fragment') { + return `textureSampleBaseClampToEdge( ${textureProperty}, ${textureProperty}_sampler, vec2( ${uvSnippet}.x, 1.0 - ${uvSnippet}.y ) )`; + } else { + console.error(`WebGPURenderer: THREE.VideoTexture does not support ${shaderStage} shader.`); + } + } + + _generateTextureSampleLevel( + texture, + textureProperty, + uvSnippet, + levelSnippet, + depthSnippet, + shaderStage = this.shaderStage, + ) { + if (shaderStage === 'fragment' && this.isUnfilterable(texture) === false) { + return `textureSampleLevel( ${textureProperty}, ${textureProperty}_sampler, ${uvSnippet}, ${levelSnippet} )`; + } else if (this.isFilteredTexture(texture)) { + return this.generateFilteredTexture(texture, textureProperty, uvSnippet, levelSnippet); + } else { + return this.generateTextureLod(texture, textureProperty, uvSnippet, levelSnippet); + } + } + + generateFilteredTexture(texture, textureProperty, uvSnippet, levelSnippet = '0') { + this._include('biquadraticTexture'); + + return `threejs_biquadraticTexture( ${textureProperty}, ${uvSnippet}, i32( ${levelSnippet} ) )`; + } + + generateTextureLod(texture, textureProperty, uvSnippet, levelSnippet = '0') { + this._include('repeatWrapping'); + + const dimension = `textureDimensions( ${textureProperty}, 0 )`; + + return `textureLoad( ${textureProperty}, threejs_repeatWrapping( ${uvSnippet}, ${dimension} ), i32( ${levelSnippet} ) )`; + } + + generateTextureLoad(texture, textureProperty, uvIndexSnippet, depthSnippet, levelSnippet = '0u') { + if (depthSnippet) { + return `textureLoad( ${textureProperty}, ${uvIndexSnippet}, ${depthSnippet}, ${levelSnippet} )`; + } else { + return `textureLoad( ${textureProperty}, ${uvIndexSnippet}, ${levelSnippet} )`; + } + } + + generateTextureStore(texture, textureProperty, uvIndexSnippet, valueSnippet) { + return `textureStore( ${textureProperty}, ${uvIndexSnippet}, ${valueSnippet} )`; + } + + isUnfilterable(texture) { + return ( + this.getComponentTypeFromTexture(texture) !== 'float' || + (texture.isDataTexture === true && texture.type === FloatType) + ); + } + + generateTexture(texture, textureProperty, uvSnippet, depthSnippet, shaderStage = this.shaderStage) { + let snippet = null; + + if (texture.isVideoTexture === true) { + snippet = this._generateVideoSample(textureProperty, uvSnippet, shaderStage); + } else if (this.isUnfilterable(texture)) { + snippet = this.generateTextureLod(texture, textureProperty, uvSnippet, '0', depthSnippet, shaderStage); + } else { + snippet = this._generateTextureSample(texture, textureProperty, uvSnippet, depthSnippet, shaderStage); + } + + return snippet; + } + + generateTextureGrad( + texture, + textureProperty, + uvSnippet, + gradSnippet, + depthSnippet, + shaderStage = this.shaderStage, + ) { + if (shaderStage === 'fragment') { + // TODO handle i32 or u32 --> uvSnippet, array_index: A, ddx, ddy + return `textureSampleGrad( ${textureProperty}, ${textureProperty}_sampler, ${uvSnippet}, ${gradSnippet[0]}, ${gradSnippet[1]} )`; + } else { + console.error(`WebGPURenderer: THREE.TextureNode.gradient() does not support ${shaderStage} shader.`); + } + } + + generateTextureCompare( + texture, + textureProperty, + uvSnippet, + compareSnippet, + depthSnippet, + shaderStage = this.shaderStage, + ) { + if (shaderStage === 'fragment') { + return `textureSampleCompare( ${textureProperty}, ${textureProperty}_sampler, ${uvSnippet}, ${compareSnippet} )`; + } else { + console.error( + `WebGPURenderer: THREE.DepthTexture.compareFunction() does not support ${shaderStage} shader.`, + ); + } + } + + generateTextureLevel( + texture, + textureProperty, + uvSnippet, + levelSnippet, + depthSnippet, + shaderStage = this.shaderStage, + ) { + let snippet = null; + + if (texture.isVideoTexture === true) { + snippet = this._generateVideoSample(textureProperty, uvSnippet, shaderStage); + } else { + snippet = this._generateTextureSampleLevel( + texture, + textureProperty, + uvSnippet, + levelSnippet, + depthSnippet, + shaderStage, + ); + } + + return snippet; + } + + getPropertyName(node, shaderStage = this.shaderStage) { + if (node.isNodeVarying === true && node.needsInterpolation === true) { + if (shaderStage === 'vertex') { + return `varyings.${node.name}`; + } + } else if (node.isNodeUniform === true) { + const name = node.name; + const type = node.type; + + if (type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' || type === 'texture3D') { + return name; + } else if (type === 'buffer' || type === 'storageBuffer') { + return `NodeBuffer_${node.id}.${name}`; + } else { + return node.groupNode.name + '.' + name; + } + } + + return super.getPropertyName(node); + } + + getOutputStructName() { + return 'output'; + } + + _getUniformGroupCount(shaderStage) { + return Object.keys(this.uniforms[shaderStage]).length; + } + + getFunctionOperator(op) { + const fnOp = wgslFnOpLib[op]; + + if (fnOp !== undefined) { + this._include(fnOp); + + return fnOp; + } + + return null; + } + + getStorageAccess(node) { + if (node.isStorageTextureNode) { + switch (node.access) { + case GPUStorageTextureAccess.ReadOnly: { + return 'read'; + } + + case GPUStorageTextureAccess.WriteOnly: { + return 'write'; + } + + default: { + return 'read_write'; + } + } + } else { + // @TODO: Account for future read-only storage buffer pull request + return 'read_write'; + } + } + + getUniformFromNode(node, type, shaderStage, name = null) { + const uniformNode = super.getUniformFromNode(node, type, shaderStage, name); + const nodeData = this.getDataFromNode(node, shaderStage, this.globalCache); + + if (nodeData.uniformGPU === undefined) { + let uniformGPU; + + const group = node.groupNode; + const groupName = group.name; + + const bindings = this.getBindGroupArray(groupName, shaderStage); + + if (type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' || type === 'texture3D') { + let texture = null; + + if (type === 'texture' || type === 'storageTexture') { + texture = new NodeSampledTexture( + uniformNode.name, + uniformNode.node, + group, + node.access ? node.access : null, + ); + } else if (type === 'cubeTexture') { + texture = new NodeSampledCubeTexture( + uniformNode.name, + uniformNode.node, + group, + node.access ? node.access : null, + ); + } else if (type === 'texture3D') { + texture = new NodeSampledTexture3D( + uniformNode.name, + uniformNode.node, + group, + node.access ? node.access : null, + ); + } + + texture.store = node.isStorageTextureNode === true; + texture.setVisibility(gpuShaderStageLib[shaderStage]); + + if ( + shaderStage === 'fragment' && + this.isUnfilterable(node.value) === false && + texture.store === false + ) { + const sampler = new NodeSampler(`${uniformNode.name}_sampler`, uniformNode.node, group); + sampler.setVisibility(gpuShaderStageLib[shaderStage]); + + bindings.push(sampler, texture); + + uniformGPU = [sampler, texture]; + } else { + bindings.push(texture); + + uniformGPU = [texture]; + } + } else if (type === 'buffer' || type === 'storageBuffer') { + const bufferClass = type === 'storageBuffer' ? NodeStorageBuffer : NodeUniformBuffer; + const buffer = new bufferClass(node, group); + buffer.setVisibility(gpuShaderStageLib[shaderStage]); + + bindings.push(buffer); + + uniformGPU = buffer; + } else { + const uniformsStage = this.uniformGroups[shaderStage] || (this.uniformGroups[shaderStage] = {}); + + let uniformsGroup = uniformsStage[groupName]; + + if (uniformsGroup === undefined) { + uniformsGroup = new NodeUniformsGroup(groupName, group); + uniformsGroup.setVisibility(gpuShaderStageLib[shaderStage]); + + uniformsStage[groupName] = uniformsGroup; + + bindings.push(uniformsGroup); + } + + uniformGPU = this.getNodeUniform(uniformNode, type); + + uniformsGroup.addUniform(uniformGPU); + } + + nodeData.uniformGPU = uniformGPU; + } + + return uniformNode; + } + + getBuiltin(name, property, type, shaderStage = this.shaderStage) { + const map = this.builtins[shaderStage] || (this.builtins[shaderStage] = new Map()); + + if (map.has(name) === false) { + map.set(name, { + name, + property, + type, + }); + } + + return property; + } + + getVertexIndex() { + if (this.shaderStage === 'vertex') { + return this.getBuiltin('vertex_index', 'vertexIndex', 'u32', 'attribute'); + } + + return 'vertexIndex'; + } + + buildFunctionCode(shaderNode) { + const layout = shaderNode.layout; + const flowData = this.flowShaderNode(shaderNode); + + const parameters = []; + + for (const input of layout.inputs) { + parameters.push(input.name + ' : ' + this.getType(input.type)); + } + + // + + const code = `fn ${layout.name}( ${parameters.join(', ')} ) -> ${this.getType(layout.type)} { +${flowData.vars} +${flowData.code} + return ${flowData.result}; + +}`; + + // + + return code; + } + + getInstanceIndex() { + if (this.shaderStage === 'vertex') { + return this.getBuiltin('instance_index', 'instanceIndex', 'u32', 'attribute'); + } + + return 'instanceIndex'; + } + + getFrontFacing() { + return this.getBuiltin('front_facing', 'isFront', 'bool'); + } + + getFragCoord() { + return this.getBuiltin('position', 'fragCoord', 'vec4') + '.xyz'; + } + + getFragDepth() { + return 'output.' + this.getBuiltin('frag_depth', 'depth', 'f32', 'output'); + } + + isFlipY() { + return false; + } + + getBuiltins(shaderStage) { + const snippets = []; + const builtins = this.builtins[shaderStage]; + + if (builtins !== undefined) { + for (const { name, property, type } of builtins.values()) { + snippets.push(`@builtin( ${name} ) ${property} : ${type}`); + } + } + + return snippets.join(',\n\t'); + } + + getAttributes(shaderStage) { + const snippets = []; + + if (shaderStage === 'compute') { + this.getBuiltin('global_invocation_id', 'id', 'vec3', 'attribute'); + } + + if (shaderStage === 'vertex' || shaderStage === 'compute') { + const builtins = this.getBuiltins('attribute'); + + if (builtins) snippets.push(builtins); + + const attributes = this.getAttributesArray(); + + for (let index = 0, length = attributes.length; index < length; index++) { + const attribute = attributes[index]; + const name = attribute.name; + const type = this.getType(attribute.type); + + snippets.push(`@location( ${index} ) ${name} : ${type}`); + } + } + + return snippets.join(',\n\t'); + } + + getStructMembers(struct) { + const snippets = []; + const members = struct.getMemberTypes(); + + for (let i = 0; i < members.length; i++) { + const member = members[i]; + snippets.push(`\t@location( ${i} ) m${i} : ${member}`); + } + + const builtins = this.getBuiltins('output'); + + if (builtins) snippets.push(builtins); + + return snippets.join(',\n'); + } + + getStructs(shaderStage) { + const snippets = []; + const structs = this.structs[shaderStage]; + + for (let index = 0, length = structs.length; index < length; index++) { + const struct = structs[index]; + const name = struct.name; + + let snippet = `\struct ${name} {\n`; + snippet += this.getStructMembers(struct); + snippet += '\n}'; + + snippets.push(snippet); + + snippets.push(`\nvar output : ${name};\n\n`); + } + + return snippets.join('\n\n'); + } + + getVar(type, name) { + return `var ${name} : ${this.getType(type)}`; + } + + getVars(shaderStage) { + const snippets = []; + const vars = this.vars[shaderStage]; + + if (vars !== undefined) { + for (const variable of vars) { + snippets.push(`\t${this.getVar(variable.type, variable.name)};`); + } + } + + return `\n${snippets.join('\n')}\n`; + } + + getVaryings(shaderStage) { + const snippets = []; + + if (shaderStage === 'vertex') { + this.getBuiltin('position', 'Vertex', 'vec4', 'vertex'); + } + + if (shaderStage === 'vertex' || shaderStage === 'fragment') { + const varyings = this.varyings; + const vars = this.vars[shaderStage]; + + for (let index = 0; index < varyings.length; index++) { + const varying = varyings[index]; + + if (varying.needsInterpolation) { + let attributesSnippet = `@location( ${index} )`; + + if (/^(int|uint|ivec|uvec)/.test(varying.type)) { + attributesSnippet += ' @interpolate( flat )'; + } + + snippets.push(`${attributesSnippet} ${varying.name} : ${this.getType(varying.type)}`); + } else if (shaderStage === 'vertex' && vars.includes(varying) === false) { + vars.push(varying); + } + } + } + + const builtins = this.getBuiltins(shaderStage); + + if (builtins) snippets.push(builtins); + + const code = snippets.join(',\n\t'); + + return shaderStage === 'vertex' ? this._getWGSLStruct('VaryingsStruct', '\t' + code) : code; + } + + getUniforms(shaderStage) { + const uniforms = this.uniforms[shaderStage]; + + const bindingSnippets = []; + const bufferSnippets = []; + const structSnippets = []; + const uniformGroups = {}; + + for (const uniform of uniforms) { + const groundName = uniform.groupNode.name; + const uniformIndexes = this.bindingsIndexes[groundName]; + + if ( + uniform.type === 'texture' || + uniform.type === 'cubeTexture' || + uniform.type === 'storageTexture' || + uniform.type === 'texture3D' + ) { + const texture = uniform.node.value; + + if ( + shaderStage === 'fragment' && + this.isUnfilterable(texture) === false && + uniform.node.isStorageTextureNode !== true + ) { + if (texture.isDepthTexture === true && texture.compareFunction !== null) { + bindingSnippets.push( + `@binding( ${uniformIndexes.binding++} ) @group( ${uniformIndexes.group} ) var ${uniform.name}_sampler : sampler_comparison;`, + ); + } else { + bindingSnippets.push( + `@binding( ${uniformIndexes.binding++} ) @group( ${uniformIndexes.group} ) var ${uniform.name}_sampler : sampler;`, + ); + } + } + + let textureType; + + if (texture.isCubeTexture === true) { + textureType = 'texture_cube'; + } else if (texture.isDataArrayTexture === true) { + textureType = 'texture_2d_array'; + } else if (texture.isDepthTexture === true) { + textureType = 'texture_depth_2d'; + } else if (texture.isVideoTexture === true) { + textureType = 'texture_external'; + } else if (texture.isData3DTexture === true) { + textureType = 'texture_3d'; + } else if (uniform.node.isStorageTextureNode === true) { + const format = getFormat(texture); + const access = this.getStorageAccess(uniform.node); + + textureType = `texture_storage_2d<${format}, ${access}>`; + } else { + const componentPrefix = this.getComponentTypeFromTexture(texture).charAt(0); + + textureType = `texture_2d<${componentPrefix}32>`; + } + + bindingSnippets.push( + `@binding( ${uniformIndexes.binding++} ) @group( ${uniformIndexes.group} ) var ${uniform.name} : ${textureType};`, + ); + } else if (uniform.type === 'buffer' || uniform.type === 'storageBuffer') { + const bufferNode = uniform.node; + const bufferType = this.getType(bufferNode.bufferType); + const bufferCount = bufferNode.bufferCount; + + const bufferCountSnippet = bufferCount > 0 ? ', ' + bufferCount : ''; + const bufferSnippet = `\t${uniform.name} : array< ${bufferType}${bufferCountSnippet} >\n`; + const bufferAccessMode = bufferNode.isStorageBufferNode ? 'storage,read_write' : 'uniform'; + + bufferSnippets.push( + this._getWGSLStructBinding( + 'NodeBuffer_' + bufferNode.id, + bufferSnippet, + bufferAccessMode, + uniformIndexes.binding++, + uniformIndexes.group, + ), + ); + } else { + const vectorType = this.getType(this.getVectorType(uniform.type)); + const groupName = uniform.groupNode.name; + + const group = + uniformGroups[groupName] || + (uniformGroups[groupName] = { + index: uniformIndexes.binding++, + id: uniformIndexes.group, + snippets: [], + }); + + group.snippets.push(`\t${uniform.name} : ${vectorType}`); + } + } + + for (const name in uniformGroups) { + const group = uniformGroups[name]; + + structSnippets.push( + this._getWGSLStructBinding(name, group.snippets.join(',\n'), 'uniform', group.index, group.id), + ); + } + + let code = bindingSnippets.join('\n'); + code += bufferSnippets.join('\n'); + code += structSnippets.join('\n'); + + return code; + } + + buildCode() { + const shadersData = this.material !== null ? { fragment: {}, vertex: {} } : { compute: {} }; + + for (const shaderStage in shadersData) { + const stageData = shadersData[shaderStage]; + stageData.uniforms = this.getUniforms(shaderStage); + stageData.attributes = this.getAttributes(shaderStage); + stageData.varyings = this.getVaryings(shaderStage); + stageData.structs = this.getStructs(shaderStage); + stageData.vars = this.getVars(shaderStage); + stageData.codes = this.getCodes(shaderStage); + + // + + let flow = '// code\n\n'; + flow += this.flowCode[shaderStage]; + + const flowNodes = this.flowNodes[shaderStage]; + const mainNode = flowNodes[flowNodes.length - 1]; + + const outputNode = mainNode.outputNode; + const isOutputStruct = outputNode !== undefined && outputNode.isOutputStructNode === true; + + for (const node of flowNodes) { + const flowSlotData = this.getFlowData(node /*, shaderStage*/); + const slotName = node.name; + + if (slotName) { + if (flow.length > 0) flow += '\n'; + + flow += `\t// flow -> ${slotName}\n\t`; + } + + flow += `${flowSlotData.code}\n\t`; + + if (node === mainNode && shaderStage !== 'compute') { + flow += '// result\n\n\t'; + + if (shaderStage === 'vertex') { + flow += `varyings.Vertex = ${flowSlotData.result};`; + } else if (shaderStage === 'fragment') { + if (isOutputStruct) { + stageData.returnType = outputNode.nodeType; + + flow += `return ${flowSlotData.result};`; + } else { + let structSnippet = '\t@location(0) color: vec4'; + + const builtins = this.getBuiltins('output'); + + if (builtins) structSnippet += ',\n\t' + builtins; + + stageData.returnType = 'OutputStruct'; + stageData.structs += this._getWGSLStruct('OutputStruct', structSnippet); + stageData.structs += '\nvar output : OutputStruct;\n\n'; + + flow += `output.color = ${flowSlotData.result};\n\n\treturn output;`; + } + } + } + } + + stageData.flow = flow; + } + + if (this.material !== null) { + this.vertexShader = this._getWGSLVertexCode(shadersData.vertex); + this.fragmentShader = this._getWGSLFragmentCode(shadersData.fragment); + } else { + this.computeShader = this._getWGSLComputeCode( + shadersData.compute, + (this.object.workgroupSize || [64]).join(', '), + ); + } + } + + getMethod(method, output = null) { + let wgslMethod; + + if (output !== null) { + wgslMethod = this._getWGSLMethod(method + '_' + output); + } + + if (wgslMethod === undefined) { + wgslMethod = this._getWGSLMethod(method); + } + + return wgslMethod || method; + } + + getType(type) { + return wgslTypeLib[type] || type; + } + + isAvailable(name) { + let result = supports[name]; + + if (result === undefined) { + if (name === 'float32Filterable') { + result = this.renderer.hasFeature('float32-filterable'); + } + + supports[name] = result; + } + + return result; + } + + _getWGSLMethod(method) { + if (wgslPolyfill[method] !== undefined) { + this._include(method); + } + + return wgslMethods[method]; + } + + _include(name) { + const codeNode = wgslPolyfill[name]; + codeNode.build(this); + + if (this.currentFunctionNode !== null) { + this.currentFunctionNode.includes.push(codeNode); + } + + return codeNode; + } + + _getWGSLVertexCode(shaderData) { + return `${this.getSignature()} + +// uniforms +${shaderData.uniforms} + +// varyings +${shaderData.varyings} +var varyings : VaryingsStruct; + +// codes +${shaderData.codes} + +@vertex +fn main( ${shaderData.attributes} ) -> VaryingsStruct { + + // vars + ${shaderData.vars} + + // flow + ${shaderData.flow} + + return varyings; + +} +`; + } + + _getWGSLFragmentCode(shaderData) { + return `${this.getSignature()} + +// uniforms +${shaderData.uniforms} + +// structs +${shaderData.structs} + +// codes +${shaderData.codes} + +@fragment +fn main( ${shaderData.varyings} ) -> ${shaderData.returnType} { + + // vars + ${shaderData.vars} + + // flow + ${shaderData.flow} + +} +`; + } + + _getWGSLComputeCode(shaderData, workgroupSize) { + return `${this.getSignature()} +// system +var instanceIndex : u32; + +// uniforms +${shaderData.uniforms} + +// codes +${shaderData.codes} + +@compute @workgroup_size( ${workgroupSize} ) +fn main( ${shaderData.attributes} ) { + + // system + instanceIndex = id.x; + + // vars + ${shaderData.vars} + + // flow + ${shaderData.flow} + +} +`; + } + + _getWGSLStruct(name, vars) { + return ` +struct ${name} { +${vars} +};`; + } + + _getWGSLStructBinding(name, vars, access, binding = 0, group = 0) { + const structName = name + 'Struct'; + const structSnippet = this._getWGSLStruct(structName, vars); + + return `${structSnippet} +@binding( ${binding} ) @group( ${group} ) +var<${access}> ${name} : ${structName};`; + } +} + +export default WGSLNodeBuilder; diff --git a/examples-jsm/examples/renderers/webgpu/nodes/WGSLNodeFunction.ts b/examples-jsm/examples/renderers/webgpu/nodes/WGSLNodeFunction.ts new file mode 100644 index 00000000..dfe1a2f3 --- /dev/null +++ b/examples-jsm/examples/renderers/webgpu/nodes/WGSLNodeFunction.ts @@ -0,0 +1,127 @@ +import NodeFunction from '../../../nodes/core/NodeFunction.js'; +import NodeFunctionInput from '../../../nodes/core/NodeFunctionInput.js'; + +const declarationRegexp = /^[fn]*\s*([a-z_0-9]+)?\s*\(([\s\S]*?)\)\s*[\-\>]*\s*([a-z_0-9]+(?:<[\s\S]+?>)?)/i; +const propertiesRegexp = /([a-z_0-9]+)\s*:\s*([a-z_0-9]+(?:<[\s\S]+?>)?)/gi; + +const wgslTypeLib = { + f32: 'float', + i32: 'int', + u32: 'uint', + bool: 'bool', + + 'vec2': 'vec2', + 'vec2': 'ivec2', + 'vec2': 'uvec2', + 'vec2': 'bvec2', + + vec2f: 'vec2', + vec2i: 'ivec2', + vec2u: 'uvec2', + vec2b: 'bvec2', + + 'vec3': 'vec3', + 'vec3': 'ivec3', + 'vec3': 'uvec3', + 'vec3': 'bvec3', + + vec3f: 'vec3', + vec3i: 'ivec3', + vec3u: 'uvec3', + vec3b: 'bvec3', + + 'vec4': 'vec4', + 'vec4': 'ivec4', + 'vec4': 'uvec4', + 'vec4': 'bvec4', + + vec4f: 'vec4', + vec4i: 'ivec4', + vec4u: 'uvec4', + vec4b: 'bvec4', + + 'mat2x2': 'mat2', + mat2x2f: 'mat2', + + 'mat3x3': 'mat3', + mat3x3f: 'mat3', + + 'mat4x4': 'mat4', + mat4x4f: 'mat4', + + sampler: 'sampler', + texture_2d: 'texture', + texture_cube: 'cubeTexture', + texture_depth_2d: 'depthTexture', + texture_storage_2d: 'storageTexture', + texture_3d: 'texture3D', +}; + +const parse = source => { + source = source.trim(); + + const declaration = source.match(declarationRegexp); + + if (declaration !== null && declaration.length === 4) { + const inputsCode = declaration[2]; + const propsMatches = []; + let match = null; + + while ((match = propertiesRegexp.exec(inputsCode)) !== null) { + propsMatches.push({ name: match[1], type: match[2] }); + } + + // Process matches to correctly pair names and types + const inputs = []; + for (let i = 0; i < propsMatches.length; i++) { + const { name, type } = propsMatches[i]; + + let resolvedType = type; + + if (resolvedType.startsWith('texture')) { + resolvedType = type.split('<')[0]; + } + + resolvedType = wgslTypeLib[resolvedType] || resolvedType; + + inputs.push(new NodeFunctionInput(resolvedType, name)); + } + + const blockCode = source.substring(declaration[0].length); + const outputType = declaration[3] || 'void'; + + const name = declaration[1] !== undefined ? declaration[1] : ''; + const type = wgslTypeLib[outputType] || outputType; + + return { + type, + inputs, + name, + inputsCode, + blockCode, + outputType, + }; + } else { + throw new Error('FunctionNode: Function is not a WGSL code.'); + } +}; + +class WGSLNodeFunction extends NodeFunction { + constructor(source) { + const { type, inputs, name, inputsCode, blockCode, outputType } = parse(source); + + super(type, inputs, name); + + this.inputsCode = inputsCode; + this.blockCode = blockCode; + this.outputType = outputType; + } + + getCode(name = this.name) { + const outputType = this.outputType !== 'void' ? '-> ' + this.outputType : ''; + + return `fn ${name} ( ${this.inputsCode.trim()} ) ${outputType}` + this.blockCode; + } +} + +export default WGSLNodeFunction; diff --git a/examples-jsm/examples/renderers/webgpu/nodes/WGSLNodeParser.ts b/examples-jsm/examples/renderers/webgpu/nodes/WGSLNodeParser.ts new file mode 100644 index 00000000..c32133df --- /dev/null +++ b/examples-jsm/examples/renderers/webgpu/nodes/WGSLNodeParser.ts @@ -0,0 +1,10 @@ +import NodeParser from '../../../nodes/core/NodeParser.js'; +import WGSLNodeFunction from './WGSLNodeFunction.js'; + +class WGSLNodeParser extends NodeParser { + parseFunction(source) { + return new WGSLNodeFunction(source); + } +} + +export default WGSLNodeParser; From ec05669bf4ec6e0d22a5067f38aefafe467ad9b8 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Wed, 26 Jun 2024 08:30:50 -0400 Subject: [PATCH 3/4] Update patch --- examples-jsm/changes.patch | 113 ++++++++++++++++++++----------------- 1 file changed, 60 insertions(+), 53 deletions(-) diff --git a/examples-jsm/changes.patch b/examples-jsm/changes.patch index 5b89c51f..77070650 100644 --- a/examples-jsm/changes.patch +++ b/examples-jsm/changes.patch @@ -1004,7 +1004,7 @@ index 190fe8c5..d873bb24 100644 this.name = name; diff --git a/examples-jsm/examples/nodes/core/NodeBuilder.ts b/examples-jsm/examples/nodes/core/NodeBuilder.ts -index 62ea78a5..50d7f631 100644 +index 2559a675..e7f46dfb 100644 --- a/examples-jsm/examples/nodes/core/NodeBuilder.ts +++ b/examples-jsm/examples/nodes/core/NodeBuilder.ts @@ -8,7 +8,7 @@ import NodeCache from './NodeCache.js'; @@ -1016,7 +1016,7 @@ index 62ea78a5..50d7f631 100644 import { NumberNodeUniform, -@@ -36,17 +36,41 @@ import { +@@ -36,17 +36,44 @@ import { LinearMipmapNearestFilter, NearestMipmapLinearFilter, LinearMipmapLinearFilter, @@ -1046,7 +1046,7 @@ index 62ea78a5..50d7f631 100644 import PMREMGenerator from '../../renderers/common/extras/PMREMGenerator.js'; - --const bindGroupsCache = new ChainMap(); +-const rendererCache = new WeakMap(); +import Renderer from '../../renderers/common/Renderer.js'; +import NodeParser from './NodeParser.js'; +import Node from './Node.js'; @@ -1058,11 +1058,14 @@ index 62ea78a5..50d7f631 100644 +import UniformNode from './UniformNode.js'; +import StructTypeNode from './StructTypeNode.js'; + -+const bindGroupsCache = new ChainMap[] | NodeUniformsGroup[], NodeUniformsGroup | BindGroup>(); ++const rendererCache = new WeakMap< ++ Renderer, ++ ChainMap[] | NodeUniformsGroup[], NodeUniformsGroup | BindGroup> ++>(); const typeFromLength = new Map([ [2, 'vec2'], -@@ -56,7 +80,16 @@ const typeFromLength = new Map([ +@@ -56,7 +83,16 @@ const typeFromLength = new Map([ [16, 'mat4'], ]); @@ -1080,7 +1083,7 @@ index 62ea78a5..50d7f631 100644 [Int8Array, 'int'], [Int16Array, 'int'], [Int32Array, 'int'], -@@ -66,17 +99,94 @@ const typeFromArray = new Map([ +@@ -66,17 +102,94 @@ const typeFromArray = new Map([ [Float32Array, 'float'], ]); @@ -1181,7 +1184,7 @@ index 62ea78a5..50d7f631 100644 this.renderer = renderer; this.parser = parser; this.scene = null; -@@ -128,17 +238,17 @@ class NodeBuilder { +@@ -128,7 +241,7 @@ class NodeBuilder { this.cache = new NodeCache(); this.globalCache = this.cache; @@ -1190,6 +1193,8 @@ index 62ea78a5..50d7f631 100644 this.shaderStage = null; this.buildStage = null; +@@ -146,11 +259,11 @@ class NodeBuilder { + return bindGroupsCache; } - createRenderTarget(width, height, options) { @@ -1202,7 +1207,7 @@ index 62ea78a5..50d7f631 100644 return new CubeRenderTarget(size, options); } -@@ -148,14 +258,14 @@ class NodeBuilder { +@@ -160,16 +273,16 @@ class NodeBuilder { return new PMREMGenerator(this.renderer); } @@ -1213,6 +1218,8 @@ index 62ea78a5..50d7f631 100644 - _getBindGroup(groupName, bindings) { + _getBindGroup(groupName: string, bindings: NodeUniformsGroup[]) { + const bindGroupsCache = this.getBingGroupsCache(); + // cache individual uniforms group - const bindingsArray = []; @@ -1220,7 +1227,7 @@ index 62ea78a5..50d7f631 100644 let sharedGroup = true; -@@ -164,7 +274,7 @@ class NodeBuilder { +@@ -178,7 +291,7 @@ class NodeBuilder { // nodes is the chainmap key const nodes = binding.getNodes(); @@ -1229,7 +1236,7 @@ index 62ea78a5..50d7f631 100644 if (sharedBinding === undefined) { bindGroupsCache.set(nodes, binding); -@@ -185,7 +295,7 @@ class NodeBuilder { +@@ -199,7 +312,7 @@ class NodeBuilder { let bindGroup; if (sharedGroup) { @@ -1238,7 +1245,7 @@ index 62ea78a5..50d7f631 100644 if (bindGroup === undefined) { bindGroup = new BindGroup(groupName, bindingsArray); -@@ -198,7 +308,7 @@ class NodeBuilder { +@@ -212,7 +325,7 @@ class NodeBuilder { return bindGroup; } @@ -1247,7 +1254,7 @@ index 62ea78a5..50d7f631 100644 const bindings = this.bindings[shaderStage]; let bindGroup = bindings[groupName]; -@@ -218,12 +328,12 @@ class NodeBuilder { +@@ -232,12 +345,12 @@ class NodeBuilder { let bindingsGroups = this.bindGroups; if (bindingsGroups === null) { @@ -1262,7 +1269,7 @@ index 62ea78a5..50d7f631 100644 const groupUniforms = groups[groupName] || (groups[groupName] = []); groupUniforms.push(...uniforms); -@@ -233,7 +343,7 @@ class NodeBuilder { +@@ -247,7 +360,7 @@ class NodeBuilder { bindingsGroups = []; for (const groupName in groups) { @@ -1271,7 +1278,7 @@ index 62ea78a5..50d7f631 100644 const bindingsGroup = this._getBindGroup(groupName, group); -@@ -246,11 +356,11 @@ class NodeBuilder { +@@ -260,11 +373,11 @@ class NodeBuilder { return bindingsGroups; } @@ -1285,7 +1292,7 @@ index 62ea78a5..50d7f631 100644 if (this.nodes.includes(node) === false) { this.nodes.push(node); -@@ -282,7 +392,7 @@ class NodeBuilder { +@@ -296,7 +409,7 @@ class NodeBuilder { return this.chaining[this.chaining.length - 1]; } @@ -1294,7 +1301,7 @@ index 62ea78a5..50d7f631 100644 return ( texture.magFilter === LinearFilter || texture.magFilter === LinearMipmapNearestFilter || -@@ -295,7 +405,7 @@ class NodeBuilder { +@@ -309,7 +422,7 @@ class NodeBuilder { ); } @@ -1303,7 +1310,7 @@ index 62ea78a5..50d7f631 100644 /* if ( this.chaining.indexOf( node ) !== - 1 ) { -@@ -307,7 +417,7 @@ class NodeBuilder { +@@ -321,7 +434,7 @@ class NodeBuilder { this.chaining.push(node); } @@ -1312,7 +1319,7 @@ index 62ea78a5..50d7f631 100644 const lastChain = this.chaining.pop(); if (lastChain !== node) { -@@ -315,21 +425,21 @@ class NodeBuilder { +@@ -329,21 +442,21 @@ class NodeBuilder { } } @@ -1338,7 +1345,7 @@ index 62ea78a5..50d7f631 100644 this.context = context; } -@@ -337,7 +447,7 @@ class NodeBuilder { +@@ -351,7 +464,7 @@ class NodeBuilder { return this.context; } @@ -1347,7 +1354,7 @@ index 62ea78a5..50d7f631 100644 this.cache = cache; } -@@ -345,14 +455,14 @@ class NodeBuilder { +@@ -359,14 +472,14 @@ class NodeBuilder { return this.cache; } @@ -1364,7 +1371,7 @@ index 62ea78a5..50d7f631 100644 return false; } -@@ -376,15 +486,45 @@ class NodeBuilder { +@@ -390,15 +503,45 @@ class NodeBuilder { return false; } @@ -1419,7 +1426,7 @@ index 62ea78a5..50d7f631 100644 if (value === null) { if (type === 'float' || type === 'int' || type === 'uint') value = 0; else if (type === 'bool') value = false; -@@ -395,26 +535,26 @@ class NodeBuilder { +@@ -409,26 +552,26 @@ class NodeBuilder { } if (type === 'float') return toFloat(value); @@ -1455,7 +1462,7 @@ index 62ea78a5..50d7f631 100644 } else if (typeLength > 4) { return `${this.getType(type)}()`; } -@@ -422,17 +562,17 @@ class NodeBuilder { +@@ -436,17 +579,17 @@ class NodeBuilder { throw new Error(`NodeBuilder: Type '${type}' not found in generate constant attempt.`); } @@ -1476,7 +1483,7 @@ index 62ea78a5..50d7f631 100644 const attributes = this.attributes; // find attribute -@@ -452,19 +592,19 @@ class NodeBuilder { +@@ -466,19 +609,19 @@ class NodeBuilder { return attribute; } @@ -1503,7 +1510,7 @@ index 62ea78a5..50d7f631 100644 return ( type === 'void' || type === 'property' || -@@ -481,10 +621,10 @@ class NodeBuilder { +@@ -495,10 +638,10 @@ class NodeBuilder { return false; } @@ -1516,7 +1523,7 @@ index 62ea78a5..50d7f631 100644 if (type === IntType) return 'int'; if (type === UnsignedIntType) return 'uint'; } -@@ -492,7 +632,7 @@ class NodeBuilder { +@@ -506,7 +649,7 @@ class NodeBuilder { return 'float'; } @@ -1525,7 +1532,7 @@ index 62ea78a5..50d7f631 100644 if (type === 'mat2') return 'vec2'; if (type === 'mat3') return 'vec3'; if (type === 'mat4') return 'vec4'; -@@ -500,7 +640,7 @@ class NodeBuilder { +@@ -514,7 +657,7 @@ class NodeBuilder { return this.getComponentType(type); } @@ -1534,7 +1541,7 @@ index 62ea78a5..50d7f631 100644 type = this.getVectorType(type); if (type === 'float' || type === 'bool' || type === 'int' || type === 'uint') return type; -@@ -516,7 +656,7 @@ class NodeBuilder { +@@ -530,7 +673,7 @@ class NodeBuilder { return 'float'; } @@ -1543,7 +1550,7 @@ index 62ea78a5..50d7f631 100644 if (type === 'color') return 'vec3'; if (type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' || type === 'texture3D') return 'vec4'; -@@ -524,23 +664,23 @@ class NodeBuilder { +@@ -538,23 +681,23 @@ class NodeBuilder { return type; } @@ -1575,7 +1582,7 @@ index 62ea78a5..50d7f631 100644 const array = dataAttribute.array; const itemSize = attribute.itemSize; -@@ -555,28 +695,28 @@ class NodeBuilder { +@@ -569,28 +712,28 @@ class NodeBuilder { return this.getTypeFromLength(itemSize, arrayType); } @@ -1612,7 +1619,7 @@ index 62ea78a5..50d7f631 100644 const componentType = this.getComponentType(type); if (componentType === 'int' || componentType === 'uint') return type; -@@ -602,7 +742,11 @@ class NodeBuilder { +@@ -616,7 +759,11 @@ class NodeBuilder { return lastStack; } @@ -1625,7 +1632,7 @@ index 62ea78a5..50d7f631 100644 cache = cache === null ? (node.isGlobal(this) ? this.globalCache : this.cache) : cache; let nodeData = cache.getData(node); -@@ -615,16 +759,16 @@ class NodeBuilder { +@@ -629,16 +776,16 @@ class NodeBuilder { if (nodeData[shaderStage] === undefined) nodeData[shaderStage] = {}; @@ -1645,7 +1652,7 @@ index 62ea78a5..50d7f631 100644 const nodeData = this.getDataFromNode(node); let bufferAttribute = nodeData.bufferAttribute; -@@ -642,7 +786,7 @@ class NodeBuilder { +@@ -656,7 +803,7 @@ class NodeBuilder { return bufferAttribute; } @@ -1654,7 +1661,7 @@ index 62ea78a5..50d7f631 100644 const nodeData = this.getDataFromNode(node, shaderStage); if (nodeData.structType === undefined) { -@@ -657,7 +801,12 @@ class NodeBuilder { +@@ -671,7 +818,12 @@ class NodeBuilder { return node; } @@ -1668,7 +1675,7 @@ index 62ea78a5..50d7f631 100644 const nodeData = this.getDataFromNode(node, shaderStage, this.globalCache); let nodeUniform = nodeData.uniform; -@@ -675,7 +824,12 @@ class NodeBuilder { +@@ -689,7 +841,12 @@ class NodeBuilder { return nodeUniform; } @@ -1682,7 +1689,7 @@ index 62ea78a5..50d7f631 100644 const nodeData = this.getDataFromNode(node, shaderStage); let nodeVar = nodeData.variable; -@@ -695,7 +849,7 @@ class NodeBuilder { +@@ -709,7 +866,7 @@ class NodeBuilder { return nodeVar; } @@ -1691,7 +1698,7 @@ index 62ea78a5..50d7f631 100644 const nodeData = this.getDataFromNode(node, 'any'); let nodeVarying = nodeData.varying; -@@ -716,7 +870,7 @@ class NodeBuilder { +@@ -730,7 +887,7 @@ class NodeBuilder { return nodeVarying; } @@ -1700,7 +1707,7 @@ index 62ea78a5..50d7f631 100644 const nodeData = this.getDataFromNode(node); let nodeCode = nodeData.code; -@@ -735,7 +889,7 @@ class NodeBuilder { +@@ -749,7 +906,7 @@ class NodeBuilder { return nodeCode; } @@ -1709,7 +1716,7 @@ index 62ea78a5..50d7f631 100644 if (code === '') return this; code = this.tab + code; -@@ -749,7 +903,7 @@ class NodeBuilder { +@@ -763,7 +920,7 @@ class NodeBuilder { return this; } @@ -1718,7 +1725,7 @@ index 62ea78a5..50d7f631 100644 this.flow.code += code; return this; -@@ -767,11 +921,11 @@ class NodeBuilder { +@@ -781,11 +938,11 @@ class NodeBuilder { return this; } @@ -1732,7 +1739,7 @@ index 62ea78a5..50d7f631 100644 const output = node.getNodeType(this); const flowData = this.flowChildNode(node, output); -@@ -781,7 +935,9 @@ class NodeBuilder { +@@ -795,7 +952,9 @@ class NodeBuilder { return flowData; } @@ -1743,7 +1750,7 @@ index 62ea78a5..50d7f631 100644 const fn = new FunctionNode(); const previous = this.currentFunctionNode; -@@ -795,7 +951,7 @@ class NodeBuilder { +@@ -809,7 +968,7 @@ class NodeBuilder { return fn; } @@ -1752,7 +1759,7 @@ index 62ea78a5..50d7f631 100644 const layout = shaderNode.layout; let inputs; -@@ -803,13 +959,13 @@ class NodeBuilder { +@@ -817,13 +976,13 @@ class NodeBuilder { if (shaderNode.isArrayInput) { inputs = []; @@ -1768,7 +1775,7 @@ index 62ea78a5..50d7f631 100644 inputs[input.name] = new ParameterNode(input.type, input.name); } } -@@ -826,14 +982,14 @@ class NodeBuilder { +@@ -840,14 +999,14 @@ class NodeBuilder { return flowData; } @@ -1785,7 +1792,7 @@ index 62ea78a5..50d7f631 100644 code: '', }; -@@ -848,7 +1004,7 @@ class NodeBuilder { +@@ -862,7 +1021,7 @@ class NodeBuilder { flow.result = node.build(this, output); } @@ -1794,7 +1801,7 @@ index 62ea78a5..50d7f631 100644 this.flow = previousFlow; this.vars = previousVars; -@@ -864,10 +1020,10 @@ class NodeBuilder { +@@ -878,10 +1037,10 @@ class NodeBuilder { return null; } @@ -1807,7 +1814,7 @@ index 62ea78a5..50d7f631 100644 code: '', }; -@@ -880,7 +1036,12 @@ class NodeBuilder { +@@ -894,7 +1053,12 @@ class NodeBuilder { return flow; } @@ -1821,7 +1828,7 @@ index 62ea78a5..50d7f631 100644 const previousShaderStage = this.shaderStage; this.setShaderStage(shaderStage); -@@ -902,19 +1063,15 @@ class NodeBuilder { +@@ -916,19 +1080,15 @@ class NodeBuilder { return this.attributes.concat(this.bufferAttributes); } @@ -1845,7 +1852,7 @@ index 62ea78a5..50d7f631 100644 let snippet = ''; const vars = this.vars[shaderStage]; -@@ -928,11 +1085,9 @@ class NodeBuilder { +@@ -942,11 +1102,9 @@ class NodeBuilder { return snippet; } @@ -1859,7 +1866,7 @@ index 62ea78a5..50d7f631 100644 const codes = this.codes[shaderStage]; let code = ''; -@@ -947,10 +1102,10 @@ class NodeBuilder { +@@ -961,10 +1119,10 @@ class NodeBuilder { } getHash() { @@ -1872,7 +1879,7 @@ index 62ea78a5..50d7f631 100644 this.shaderStage = shaderStage; } -@@ -958,7 +1113,7 @@ class NodeBuilder { +@@ -972,7 +1130,7 @@ class NodeBuilder { return this.shaderStage; } @@ -1881,7 +1888,7 @@ index 62ea78a5..50d7f631 100644 this.buildStage = buildStage; } -@@ -1016,7 +1171,7 @@ class NodeBuilder { +@@ -1030,7 +1188,7 @@ class NodeBuilder { return this; } @@ -1890,7 +1897,7 @@ index 62ea78a5..50d7f631 100644 if (type === 'float' || type === 'int' || type === 'uint') return new NumberNodeUniform(uniformNode); if (type === 'vec2' || type === 'ivec2' || type === 'uvec2') return new Vector2NodeUniform(uniformNode); if (type === 'vec3' || type === 'ivec3' || type === 'uvec3') return new Vector3NodeUniform(uniformNode); -@@ -1034,7 +1189,7 @@ class NodeBuilder { +@@ -1048,7 +1206,7 @@ class NodeBuilder { return createNodeMaterialFromType(type); } From e42fd965d2476d23352dffe9a747b6a133b735f6 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Wed, 26 Jun 2024 08:30:59 -0400 Subject: [PATCH 4/4] Delete examples --- examples-jsm/examples/nodes/Nodes.ts | 416 ----- .../nodes/accessors/BufferAttributeNode.ts | 127 -- .../examples/nodes/accessors/TextureNode.ts | 327 ---- examples-jsm/examples/nodes/code/CodeNode.ts | 66 - .../examples/nodes/code/FunctionNode.ts | 100 -- .../examples/nodes/core/ContextNode.ts | 55 - examples-jsm/examples/nodes/core/InputNode.ts | 65 - examples-jsm/examples/nodes/core/Node.ts | 424 ------ .../examples/nodes/core/NodeAttribute.ts | 11 - .../examples/nodes/core/NodeBuilder.ts | 1119 -------------- examples-jsm/examples/nodes/core/NodeCache.ts | 26 - examples-jsm/examples/nodes/core/NodeCode.ts | 11 - examples-jsm/examples/nodes/core/NodeFrame.ts | 127 -- .../examples/nodes/core/NodeFunction.ts | 16 - .../examples/nodes/core/NodeKeywords.ts | 58 - .../examples/nodes/core/NodeParser.ts | 7 - .../examples/nodes/core/NodeUniform.ts | 27 - examples-jsm/examples/nodes/core/NodeUtils.ts | 132 -- examples-jsm/examples/nodes/core/NodeVar.ts | 10 - .../examples/nodes/core/NodeVarying.ts | 13 - examples-jsm/examples/nodes/core/StackNode.ts | 71 - .../examples/nodes/core/StructTypeNode.ts | 18 - .../examples/nodes/core/UniformGroupNode.ts | 30 - .../examples/nodes/core/UniformNode.ts | 90 -- examples-jsm/examples/nodes/core/constants.ts | 28 - examples-jsm/examples/nodes/fog/FogNode.ts | 38 - .../examples/nodes/gpgpu/ComputeNode.ts | 67 - .../nodes/lighting/EnvironmentNode.ts | 119 -- .../nodes/lighting/LightingContextNode.ts | 58 - .../examples/nodes/lighting/LightsNode.ts | 170 --- .../examples/nodes/materials/NodeMaterial.ts | 514 ------- .../examples/nodes/shadernode/ShaderNode.ts | 532 ------- .../examples/renderers/common/Animation.ts | 38 - .../examples/renderers/common/Attributes.ts | 53 - .../examples/renderers/common/Backend.ts | 165 -- .../examples/renderers/common/Background.ts | 118 -- .../examples/renderers/common/BindGroup.ts | 12 - .../examples/renderers/common/Binding.ts | 17 - .../examples/renderers/common/Bindings.ts | 161 -- .../examples/renderers/common/Buffer.ts | 28 - .../examples/renderers/common/BufferUtils.ts | 23 - .../examples/renderers/common/ChainMap.ts | 43 - .../renderers/common/ClippingContext.ts | 128 -- .../examples/renderers/common/Color4.ts | 27 - .../renderers/common/ComputePipeline.ts | 13 - .../examples/renderers/common/Constants.ts | 14 - .../renderers/common/CubeRenderTarget.ts | 69 - .../examples/renderers/common/DataMap.ts | 38 - .../examples/renderers/common/Geometries.ts | 182 --- .../examples/renderers/common/Info.ts | 76 - .../examples/renderers/common/Pipeline.ts | 9 - .../examples/renderers/common/Pipelines.ts | 270 ---- .../renderers/common/ProgrammableStage.ts | 16 - .../examples/renderers/common/RenderBundle.ts | 12 - .../renderers/common/RenderBundles.ts | 28 - .../renderers/common/RenderContext.ts | 39 - .../renderers/common/RenderContexts.ts | 47 - .../examples/renderers/common/RenderList.ts | 145 -- .../examples/renderers/common/RenderLists.ts | 28 - .../examples/renderers/common/RenderObject.ts | 215 --- .../renderers/common/RenderObjects.ts | 100 -- .../renderers/common/RenderPipeline.ts | 12 - .../examples/renderers/common/Renderer.ts | 1345 ----------------- .../renderers/common/SampledTexture.ts | 61 - .../examples/renderers/common/Sampler.ts | 14 - .../renderers/common/StorageBuffer.ts | 13 - .../examples/renderers/common/Textures.ts | 288 ---- .../examples/renderers/common/Uniform.ts | 100 -- .../renderers/common/UniformBuffer.ts | 11 - .../renderers/common/UniformsGroup.ts | 277 ---- .../renderers/common/extras/PMREMGenerator.ts | 659 -------- .../common/nodes/NodeBuilderState.ts | 55 - .../renderers/common/nodes/NodeUniform.ts | 103 -- .../common/nodes/NodeUniformsGroup.ts | 30 - .../examples/renderers/common/nodes/Nodes.ts | 394 ----- .../examples/renderers/webgl/WebGLBackend.ts | 1268 ---------------- .../renderers/webgl/nodes/GLSLNodeBuilder.ts | 747 --------- .../renderers/webgpu/WebGPUBackend.ts | 1194 --------------- .../renderers/webgpu/WebGPURenderer.ts | 43 - .../renderers/webgpu/nodes/WGSLNodeBuilder.ts | 1009 ------------- .../webgpu/nodes/WGSLNodeFunction.ts | 127 -- .../renderers/webgpu/nodes/WGSLNodeParser.ts | 10 - 82 files changed, 14746 deletions(-) delete mode 100644 examples-jsm/examples/nodes/Nodes.ts delete mode 100644 examples-jsm/examples/nodes/accessors/BufferAttributeNode.ts delete mode 100644 examples-jsm/examples/nodes/accessors/TextureNode.ts delete mode 100644 examples-jsm/examples/nodes/code/CodeNode.ts delete mode 100644 examples-jsm/examples/nodes/code/FunctionNode.ts delete mode 100644 examples-jsm/examples/nodes/core/ContextNode.ts delete mode 100644 examples-jsm/examples/nodes/core/InputNode.ts delete mode 100644 examples-jsm/examples/nodes/core/Node.ts delete mode 100644 examples-jsm/examples/nodes/core/NodeAttribute.ts delete mode 100644 examples-jsm/examples/nodes/core/NodeBuilder.ts delete mode 100644 examples-jsm/examples/nodes/core/NodeCache.ts delete mode 100644 examples-jsm/examples/nodes/core/NodeCode.ts delete mode 100644 examples-jsm/examples/nodes/core/NodeFrame.ts delete mode 100644 examples-jsm/examples/nodes/core/NodeFunction.ts delete mode 100644 examples-jsm/examples/nodes/core/NodeKeywords.ts delete mode 100644 examples-jsm/examples/nodes/core/NodeParser.ts delete mode 100644 examples-jsm/examples/nodes/core/NodeUniform.ts delete mode 100644 examples-jsm/examples/nodes/core/NodeUtils.ts delete mode 100644 examples-jsm/examples/nodes/core/NodeVar.ts delete mode 100644 examples-jsm/examples/nodes/core/NodeVarying.ts delete mode 100644 examples-jsm/examples/nodes/core/StackNode.ts delete mode 100644 examples-jsm/examples/nodes/core/StructTypeNode.ts delete mode 100644 examples-jsm/examples/nodes/core/UniformGroupNode.ts delete mode 100644 examples-jsm/examples/nodes/core/UniformNode.ts delete mode 100644 examples-jsm/examples/nodes/core/constants.ts delete mode 100644 examples-jsm/examples/nodes/fog/FogNode.ts delete mode 100644 examples-jsm/examples/nodes/gpgpu/ComputeNode.ts delete mode 100644 examples-jsm/examples/nodes/lighting/EnvironmentNode.ts delete mode 100644 examples-jsm/examples/nodes/lighting/LightingContextNode.ts delete mode 100644 examples-jsm/examples/nodes/lighting/LightsNode.ts delete mode 100644 examples-jsm/examples/nodes/materials/NodeMaterial.ts delete mode 100644 examples-jsm/examples/nodes/shadernode/ShaderNode.ts delete mode 100644 examples-jsm/examples/renderers/common/Animation.ts delete mode 100644 examples-jsm/examples/renderers/common/Attributes.ts delete mode 100644 examples-jsm/examples/renderers/common/Backend.ts delete mode 100644 examples-jsm/examples/renderers/common/Background.ts delete mode 100644 examples-jsm/examples/renderers/common/BindGroup.ts delete mode 100644 examples-jsm/examples/renderers/common/Binding.ts delete mode 100644 examples-jsm/examples/renderers/common/Bindings.ts delete mode 100644 examples-jsm/examples/renderers/common/Buffer.ts delete mode 100644 examples-jsm/examples/renderers/common/BufferUtils.ts delete mode 100644 examples-jsm/examples/renderers/common/ChainMap.ts delete mode 100644 examples-jsm/examples/renderers/common/ClippingContext.ts delete mode 100644 examples-jsm/examples/renderers/common/Color4.ts delete mode 100644 examples-jsm/examples/renderers/common/ComputePipeline.ts delete mode 100644 examples-jsm/examples/renderers/common/Constants.ts delete mode 100644 examples-jsm/examples/renderers/common/CubeRenderTarget.ts delete mode 100644 examples-jsm/examples/renderers/common/DataMap.ts delete mode 100644 examples-jsm/examples/renderers/common/Geometries.ts delete mode 100644 examples-jsm/examples/renderers/common/Info.ts delete mode 100644 examples-jsm/examples/renderers/common/Pipeline.ts delete mode 100644 examples-jsm/examples/renderers/common/Pipelines.ts delete mode 100644 examples-jsm/examples/renderers/common/ProgrammableStage.ts delete mode 100644 examples-jsm/examples/renderers/common/RenderBundle.ts delete mode 100644 examples-jsm/examples/renderers/common/RenderBundles.ts delete mode 100644 examples-jsm/examples/renderers/common/RenderContext.ts delete mode 100644 examples-jsm/examples/renderers/common/RenderContexts.ts delete mode 100644 examples-jsm/examples/renderers/common/RenderList.ts delete mode 100644 examples-jsm/examples/renderers/common/RenderLists.ts delete mode 100644 examples-jsm/examples/renderers/common/RenderObject.ts delete mode 100644 examples-jsm/examples/renderers/common/RenderObjects.ts delete mode 100644 examples-jsm/examples/renderers/common/RenderPipeline.ts delete mode 100644 examples-jsm/examples/renderers/common/Renderer.ts delete mode 100644 examples-jsm/examples/renderers/common/SampledTexture.ts delete mode 100644 examples-jsm/examples/renderers/common/Sampler.ts delete mode 100644 examples-jsm/examples/renderers/common/StorageBuffer.ts delete mode 100644 examples-jsm/examples/renderers/common/Textures.ts delete mode 100644 examples-jsm/examples/renderers/common/Uniform.ts delete mode 100644 examples-jsm/examples/renderers/common/UniformBuffer.ts delete mode 100644 examples-jsm/examples/renderers/common/UniformsGroup.ts delete mode 100644 examples-jsm/examples/renderers/common/extras/PMREMGenerator.ts delete mode 100644 examples-jsm/examples/renderers/common/nodes/NodeBuilderState.ts delete mode 100644 examples-jsm/examples/renderers/common/nodes/NodeUniform.ts delete mode 100644 examples-jsm/examples/renderers/common/nodes/NodeUniformsGroup.ts delete mode 100644 examples-jsm/examples/renderers/common/nodes/Nodes.ts delete mode 100644 examples-jsm/examples/renderers/webgl/WebGLBackend.ts delete mode 100644 examples-jsm/examples/renderers/webgl/nodes/GLSLNodeBuilder.ts delete mode 100644 examples-jsm/examples/renderers/webgpu/WebGPUBackend.ts delete mode 100644 examples-jsm/examples/renderers/webgpu/WebGPURenderer.ts delete mode 100644 examples-jsm/examples/renderers/webgpu/nodes/WGSLNodeBuilder.ts delete mode 100644 examples-jsm/examples/renderers/webgpu/nodes/WGSLNodeFunction.ts delete mode 100644 examples-jsm/examples/renderers/webgpu/nodes/WGSLNodeParser.ts diff --git a/examples-jsm/examples/nodes/Nodes.ts b/examples-jsm/examples/nodes/Nodes.ts deleted file mode 100644 index 1bfbf68a..00000000 --- a/examples-jsm/examples/nodes/Nodes.ts +++ /dev/null @@ -1,416 +0,0 @@ -// @TODO: We can simplify "export { default as SomeNode, other, exports } from '...'" to just "export * from '...'" if we will use only named exports -// this will also solve issues like "import TempNode from '../core/Node.js'" - -// constants -export * from './core/constants.js'; - -// core -export { default as AssignNode, assign } from './core/AssignNode.js'; -export { default as AttributeNode, attribute } from './core/AttributeNode.js'; -export { default as BypassNode, bypass } from './core/BypassNode.js'; -export { default as CacheNode, cache } from './core/CacheNode.js'; -export { default as ConstNode } from './core/ConstNode.js'; -export { default as ContextNode, context, label } from './core/ContextNode.js'; -export { default as IndexNode, vertexIndex, instanceIndex } from './core/IndexNode.js'; -export { default as LightingModel } from './core/LightingModel.js'; -export { default as Node, addNodeClass, createNodeFromType } from './core/Node.js'; -export { default as VarNode, temp } from './core/VarNode.js'; -export { default as NodeAttribute } from './core/NodeAttribute.js'; -export { default as NodeBuilder } from './core/NodeBuilder.js'; -export { default as NodeCache } from './core/NodeCache.js'; -export { default as NodeCode } from './core/NodeCode.js'; -export { default as NodeFrame } from './core/NodeFrame.js'; -export { default as NodeFunctionInput } from './core/NodeFunctionInput.js'; -export { default as NodeKeywords } from './core/NodeKeywords.js'; -export { default as NodeUniform } from './core/NodeUniform.js'; -export { default as NodeVar } from './core/NodeVar.js'; -export { default as NodeVarying } from './core/NodeVarying.js'; -export { default as ParameterNode, parameter } from './core/ParameterNode.js'; -export { - default as PropertyNode, - property, - varyingProperty, - output, - diffuseColor, - roughness, - metalness, - clearcoat, - clearcoatRoughness, - sheen, - sheenRoughness, - iridescence, - iridescenceIOR, - iridescenceThickness, - specularColor, - shininess, - dashSize, - gapSize, - pointWidth, - alphaT, - anisotropy, - anisotropyB, - anisotropyT, -} from './core/PropertyNode.js'; -export { default as StackNode, stack } from './core/StackNode.js'; -export { default as TempNode } from './core/TempNode.js'; -export { - default as UniformGroupNode, - uniformGroup, - objectGroup, - renderGroup, - frameGroup, -} from './core/UniformGroupNode.js'; -export { default as UniformNode, uniform } from './core/UniformNode.js'; -export { default as VaryingNode, varying } from './core/VaryingNode.js'; -export { default as OutputStructNode, outputStruct } from './core/OutputStructNode.js'; - -import * as NodeUtils from './core/NodeUtils.js'; -export { NodeUtils }; - -// math -export { - default as MathNode, - PI, - PI2, - EPSILON, - INFINITY, - radians, - degrees, - exp, - exp2, - log, - log2, - sqrt, - inverseSqrt, - floor, - ceil, - normalize, - fract, - sin, - cos, - tan, - asin, - acos, - atan, - abs, - sign, - length, - lengthSq, - negate, - oneMinus, - dFdx, - dFdy, - round, - reciprocal, - trunc, - fwidth, - bitcast, - atan2, - min, - max, - mod, - step, - reflect, - distance, - difference, - dot, - cross, - pow, - pow2, - pow3, - pow4, - transformDirection, - mix, - clamp, - saturate, - refract, - smoothstep, - faceForward, - cbrt, - transpose, - all, - any, - equals, -} from './math/MathNode.js'; - -export { - default as OperatorNode, - add, - sub, - mul, - div, - remainder, - equal, - lessThan, - greaterThan, - lessThanEqual, - greaterThanEqual, - and, - or, - not, - xor, - bitAnd, - bitNot, - bitOr, - bitXor, - shiftLeft, - shiftRight, -} from './math/OperatorNode.js'; -export { default as CondNode, cond } from './math/CondNode.js'; -export { default as HashNode, hash } from './math/HashNode.js'; - -// math utils -export { parabola, gain, pcurve, sinc } from './math/MathUtils.js'; -export { triNoise3D } from './math/TriNoise3D.js'; - -// utils -export { default as ArrayElementNode } from './utils/ArrayElementNode.js'; -export { default as ConvertNode } from './utils/ConvertNode.js'; -export { default as DiscardNode, discard, Return } from './utils/DiscardNode.js'; -export { default as EquirectUVNode, equirectUV } from './utils/EquirectUVNode.js'; -export { default as FunctionOverloadingNode, overloadingFn } from './utils/FunctionOverloadingNode.js'; -export { default as JoinNode } from './utils/JoinNode.js'; -export { default as LoopNode, loop, Continue, Break } from './utils/LoopNode.js'; -export { default as MatcapUVNode, matcapUV } from './utils/MatcapUVNode.js'; -export { default as MaxMipLevelNode, maxMipLevel } from './utils/MaxMipLevelNode.js'; -export { default as OscNode, oscSine, oscSquare, oscTriangle, oscSawtooth } from './utils/OscNode.js'; -export { default as PackingNode, directionToColor, colorToDirection } from './utils/PackingNode.js'; -export { default as RemapNode, remap, remapClamp } from './utils/RemapNode.js'; -export { default as RotateUVNode, rotateUV } from './utils/RotateUVNode.js'; -export { default as RotateNode, rotate } from './utils/RotateNode.js'; -export { default as SetNode } from './utils/SetNode.js'; -export { default as SplitNode } from './utils/SplitNode.js'; -export { default as SpriteSheetUVNode, spritesheetUV } from './utils/SpriteSheetUVNode.js'; -export { default as StorageArrayElementNode } from './utils/StorageArrayElementNode.js'; -export { default as TimerNode, timerLocal, timerGlobal, timerDelta, frameId } from './utils/TimerNode.js'; -export { - default as TriplanarTexturesNode, - triplanarTextures, - triplanarTexture, -} from './utils/TriplanarTexturesNode.js'; -export { default as ReflectorNode, reflector } from './utils/ReflectorNode.js'; - -// shadernode -export * from './shadernode/ShaderNode.js'; - -// accessors -export { TBNViewMatrix, parallaxDirection, parallaxUV, transformedBentNormalView } from './accessors/AccessorsUtils.js'; -export { default as UniformsNode, uniforms } from './accessors/UniformsNode.js'; -export * from './accessors/BitangentNode.js'; -export { - default as BufferAttributeNode, - bufferAttribute, - dynamicBufferAttribute, - instancedBufferAttribute, - instancedDynamicBufferAttribute, -} from './accessors/BufferAttributeNode.js'; -export { default as BufferNode, buffer } from './accessors/BufferNode.js'; -export * from './accessors/CameraNode.js'; -export { default as VertexColorNode, vertexColor } from './accessors/VertexColorNode.js'; -export { default as CubeTextureNode, cubeTexture } from './accessors/CubeTextureNode.js'; -export { default as InstanceNode, instance } from './accessors/InstanceNode.js'; -export { default as BatchNode, batch } from './accessors/BatchNode.js'; -export { - default as MaterialNode, - materialAlphaTest, - materialColor, - materialShininess, - materialEmissive, - materialOpacity, - materialSpecular, - materialSpecularStrength, - materialReflectivity, - materialRoughness, - materialMetalness, - materialNormal, - materialClearcoat, - materialClearcoatRoughness, - materialClearcoatNormal, - materialRotation, - materialSheen, - materialSheenRoughness, - materialIridescence, - materialIridescenceIOR, - materialIridescenceThickness, - materialLineScale, - materialLineDashSize, - materialLineGapSize, - materialLineWidth, - materialLineDashOffset, - materialPointWidth, - materialAnisotropy, - materialAnisotropyVector, - materialDispersion, -} from './accessors/MaterialNode.js'; -export { default as MaterialReferenceNode, materialReference } from './accessors/MaterialReferenceNode.js'; -export { default as RendererReferenceNode, rendererReference } from './accessors/RendererReferenceNode.js'; -export { default as MorphNode, morphReference } from './accessors/MorphNode.js'; -export { default as TextureBicubicNode, textureBicubic } from './accessors/TextureBicubicNode.js'; -export { - default as ModelNode, - modelDirection, - modelViewMatrix, - modelNormalMatrix, - modelWorldMatrix, - modelPosition, - modelViewPosition, - modelScale, - modelWorldMatrixInverse, -} from './accessors/ModelNode.js'; -export { default as ModelViewProjectionNode, modelViewProjection } from './accessors/ModelViewProjectionNode.js'; -export * from './accessors/NormalNode.js'; -export { - default as Object3DNode, - objectDirection, - objectViewMatrix, - objectNormalMatrix, - objectWorldMatrix, - objectPosition, - objectScale, - objectViewPosition, -} from './accessors/Object3DNode.js'; -export { default as PointUVNode, pointUV } from './accessors/PointUVNode.js'; -export * from './accessors/PositionNode.js'; -export { default as ReferenceNode, reference, referenceBuffer } from './accessors/ReferenceNode.js'; -export * from './accessors/ReflectVectorNode.js'; -export { default as SkinningNode, skinning } from './accessors/SkinningNode.js'; -export { default as SceneNode, backgroundBlurriness, backgroundIntensity } from './accessors/SceneNode.js'; -export { default as StorageBufferNode, storage, storageObject } from './accessors/StorageBufferNode.js'; -export * from './accessors/TangentNode.js'; -export { default as TextureNode, texture, textureLoad, /*textureLevel,*/ sampler } from './accessors/TextureNode.js'; -export { - default as StorageTextureNode, - storageTexture, - textureStore, - storageTextureReadOnly, - storageTextureReadWrite, -} from './accessors/StorageTextureNode.js'; -export { default as Texture3DNode, texture3D } from './accessors/Texture3DNode.js'; -export * from './accessors/UVNode.js'; -export { default as UserDataNode, userData } from './accessors/UserDataNode.js'; - -// display -export { default as BlendModeNode, burn, dodge, overlay, screen } from './display/BlendModeNode.js'; -export { default as BumpMapNode, bumpMap } from './display/BumpMapNode.js'; -export { - default as ColorAdjustmentNode, - saturation, - vibrance, - hue, - lumaCoeffs, - luminance, - threshold, -} from './display/ColorAdjustmentNode.js'; -export { - default as ColorSpaceNode, - linearToColorSpace, - colorSpaceToLinear, - linearTosRGB, - sRGBToLinear, -} from './display/ColorSpaceNode.js'; -export { default as FrontFacingNode, frontFacing, faceDirection } from './display/FrontFacingNode.js'; -export { default as NormalMapNode, normalMap } from './display/NormalMapNode.js'; -export { default as PosterizeNode, posterize } from './display/PosterizeNode.js'; -export { default as ToneMappingNode, toneMapping } from './display/ToneMappingNode.js'; -export { - default as ViewportNode, - viewport, - viewportCoordinate, - viewportResolution, - viewportTopLeft, - viewportBottomLeft, - viewportTopRight, - viewportBottomRight, -} from './display/ViewportNode.js'; -export { default as ViewportTextureNode, viewportTexture, viewportMipTexture } from './display/ViewportTextureNode.js'; -export { default as ViewportSharedTextureNode, viewportSharedTexture } from './display/ViewportSharedTextureNode.js'; -export { default as ViewportDepthTextureNode, viewportDepthTexture } from './display/ViewportDepthTextureNode.js'; -export { - default as ViewportDepthNode, - viewZToOrthographicDepth, - orthographicDepthToViewZ, - viewZToPerspectiveDepth, - perspectiveDepthToViewZ, - depth, - linearDepth, - viewportLinearDepth, -} from './display/ViewportDepthNode.js'; -export { default as GaussianBlurNode, gaussianBlur } from './display/GaussianBlurNode.js'; -export { default as AfterImageNode, afterImage } from './display/AfterImageNode.js'; -export { default as AnamorphicNode, anamorphic } from './display/AnamorphicNode.js'; - -export { default as PassNode, pass, texturePass, depthPass } from './display/PassNode.js'; - -// code -export { default as ExpressionNode, expression } from './code/ExpressionNode.js'; -export { default as CodeNode, code, js, wgsl, glsl } from './code/CodeNode.js'; -export { default as FunctionCallNode, call } from './code/FunctionCallNode.js'; -export { default as FunctionNode, wgslFn, glslFn } from './code/FunctionNode.js'; -export { default as ScriptableNode, scriptable, global } from './code/ScriptableNode.js'; -export { default as ScriptableValueNode, scriptableValue } from './code/ScriptableValueNode.js'; - -// fog -export { default as FogNode, fog } from './fog/FogNode.js'; -export { default as FogRangeNode, rangeFog } from './fog/FogRangeNode.js'; -export { default as FogExp2Node, densityFog } from './fog/FogExp2Node.js'; - -// geometry -export { default as RangeNode, range } from './geometry/RangeNode.js'; - -// gpgpu -export { default as ComputeNode, compute } from './gpgpu/ComputeNode.js'; - -// lighting -export { default as LightNode, lightTargetDirection } from './lighting/LightNode.js'; -export { default as PointLightNode } from './lighting/PointLightNode.js'; -export { default as DirectionalLightNode } from './lighting/DirectionalLightNode.js'; -export { default as RectAreaLightNode } from './lighting/RectAreaLightNode.js'; -export { default as SpotLightNode } from './lighting/SpotLightNode.js'; -export { default as IESSpotLightNode } from './lighting/IESSpotLightNode.js'; -export { default as AmbientLightNode } from './lighting/AmbientLightNode.js'; -export { default as LightsNode, lights, lightsNode, addLightNode } from './lighting/LightsNode.js'; -export { default as LightingNode /* @TODO: lighting (abstract), light */ } from './lighting/LightingNode.js'; -export { default as LightingContextNode, lightingContext } from './lighting/LightingContextNode.js'; -export { default as HemisphereLightNode } from './lighting/HemisphereLightNode.js'; -export { default as EnvironmentNode } from './lighting/EnvironmentNode.js'; -export { default as IrradianceNode } from './lighting/IrradianceNode.js'; -export { default as AONode } from './lighting/AONode.js'; -export { default as AnalyticLightNode } from './lighting/AnalyticLightNode.js'; - -// pmrem -export { default as PMREMNode, pmremTexture } from './pmrem/PMREMNode.js'; -export * as PMREMUtils from './pmrem/PMREMUtils.js'; - -// procedural -export { default as CheckerNode, checker } from './procedural/CheckerNode.js'; - -// loaders -export { default as NodeLoader } from './loaders/NodeLoader.js'; -export { default as NodeObjectLoader } from './loaders/NodeObjectLoader.js'; -export { default as NodeMaterialLoader } from './loaders/NodeMaterialLoader.js'; - -// parsers -export { default as GLSLNodeParser } from './parsers/GLSLNodeParser.js'; // @TODO: Move to jsm/renderers/webgl. - -// materials -export * from './materials/Materials.js'; - -// materialX -export * from './materialx/MaterialXNodes.js'; - -// functions -export { default as BRDF_GGX } from './functions/BSDF/BRDF_GGX.js'; -export { default as BRDF_Lambert } from './functions/BSDF/BRDF_Lambert.js'; -export { default as D_GGX } from './functions/BSDF/D_GGX.js'; -export { default as DFGApprox } from './functions/BSDF/DFGApprox.js'; -export { default as F_Schlick } from './functions/BSDF/F_Schlick.js'; -export { default as Schlick_to_F0 } from './functions/BSDF/Schlick_to_F0.js'; -export { default as V_GGX_SmithCorrelated } from './functions/BSDF/V_GGX_SmithCorrelated.js'; - -export { getDistanceAttenuation } from './lighting/LightUtils.js'; - -export { default as getGeometryRoughness } from './functions/material/getGeometryRoughness.js'; -export { default as getRoughness } from './functions/material/getRoughness.js'; - -export { default as PhongLightingModel } from './functions/PhongLightingModel.js'; -export { default as PhysicalLightingModel } from './functions/PhysicalLightingModel.js'; diff --git a/examples-jsm/examples/nodes/accessors/BufferAttributeNode.ts b/examples-jsm/examples/nodes/accessors/BufferAttributeNode.ts deleted file mode 100644 index b8f1a724..00000000 --- a/examples-jsm/examples/nodes/accessors/BufferAttributeNode.ts +++ /dev/null @@ -1,127 +0,0 @@ -import InputNode from '../core/InputNode.js'; -import { addNodeClass } from '../core/Node.js'; -import { varying } from '../core/VaryingNode.js'; -import { nodeObject, addNodeElement } from '../shadernode/ShaderNode.js'; -import { InterleavedBufferAttribute, InterleavedBuffer, StaticDrawUsage, DynamicDrawUsage } from 'three'; - -class BufferAttributeNode extends InputNode { - constructor(value, bufferType = null, bufferStride = 0, bufferOffset = 0) { - super(value, bufferType); - - this.isBufferNode = true; - - this.bufferType = bufferType; - this.bufferStride = bufferStride; - this.bufferOffset = bufferOffset; - - this.usage = StaticDrawUsage; - this.instanced = false; - - this.attribute = null; - - this.global = true; - - if (value && value.isBufferAttribute === true) { - this.attribute = value; - this.usage = value.usage; - this.instanced = value.isInstancedBufferAttribute; - } - } - - getHash(builder) { - if (this.bufferStride === 0 && this.bufferOffset === 0) { - let bufferData = builder.globalCache.getData(this.value); - - if (bufferData === undefined) { - bufferData = { - node: this, - }; - - builder.globalCache.setData(this.value, bufferData); - } - - return bufferData.node.uuid; - } - - return this.uuid; - } - - getNodeType(builder) { - if (this.bufferType === null) { - this.bufferType = builder.getTypeFromAttribute(this.attribute); - } - - return this.bufferType; - } - - setup(builder) { - if (this.attribute !== null) return; - - const type = this.getNodeType(builder); - const array = this.value; - const itemSize = builder.getTypeLength(type); - const stride = this.bufferStride || itemSize; - const offset = this.bufferOffset; - - const buffer = array.isInterleavedBuffer === true ? array : new InterleavedBuffer(array, stride); - const bufferAttribute = new InterleavedBufferAttribute(buffer, itemSize, offset); - - buffer.setUsage(this.usage); - - this.attribute = bufferAttribute; - this.attribute.isInstancedBufferAttribute = this.instanced; // @TODO: Add a possible: InstancedInterleavedBufferAttribute - } - - generate(builder) { - const nodeType = this.getNodeType(builder); - - const nodeAttribute = builder.getBufferAttributeFromNode(this, nodeType); - const propertyName = builder.getPropertyName(nodeAttribute); - - let output = null; - - if (builder.shaderStage === 'vertex' || builder.shaderStage === 'compute') { - this.name = propertyName; - - output = propertyName; - } else { - const nodeVarying = varying(this); - - output = nodeVarying.build(builder, nodeType); - } - - return output; - } - - getInputType(/*builder*/) { - return 'bufferAttribute'; - } - - setUsage(value) { - this.usage = value; - - return this; - } - - setInstanced(value) { - this.instanced = value; - - return this; - } -} - -export default BufferAttributeNode; - -export const bufferAttribute = (array, type, stride, offset) => - nodeObject(new BufferAttributeNode(array, type, stride, offset)); -export const dynamicBufferAttribute = (array, type, stride, offset) => - bufferAttribute(array, type, stride, offset).setUsage(DynamicDrawUsage); - -export const instancedBufferAttribute = (array, type, stride, offset) => - bufferAttribute(array, type, stride, offset).setInstanced(true); -export const instancedDynamicBufferAttribute = (array, type, stride, offset) => - dynamicBufferAttribute(array, type, stride, offset).setInstanced(true); - -addNodeElement('toAttribute', bufferNode => bufferAttribute(bufferNode.value)); - -addNodeClass('BufferAttributeNode', BufferAttributeNode); diff --git a/examples-jsm/examples/nodes/accessors/TextureNode.ts b/examples-jsm/examples/nodes/accessors/TextureNode.ts deleted file mode 100644 index 7852696a..00000000 --- a/examples-jsm/examples/nodes/accessors/TextureNode.ts +++ /dev/null @@ -1,327 +0,0 @@ -import UniformNode, { uniform } from '../core/UniformNode.js'; -import { uv } from './UVNode.js'; -import { textureSize } from './TextureSizeNode.js'; -import { colorSpaceToLinear } from '../display/ColorSpaceNode.js'; -import { expression } from '../code/ExpressionNode.js'; -import { addNodeClass } from '../core/Node.js'; -import { maxMipLevel } from '../utils/MaxMipLevelNode.js'; -import { addNodeElement, nodeProxy, vec3, nodeObject } from '../shadernode/ShaderNode.js'; -import { NodeUpdateType } from '../core/constants.js'; - -class TextureNode extends UniformNode { - constructor(value, uvNode = null, levelNode = null) { - super(value); - - this.isTextureNode = true; - - this.uvNode = uvNode; - this.levelNode = levelNode; - this.compareNode = null; - this.depthNode = null; - this.gradNode = null; - - this.sampler = true; - this.updateMatrix = false; - this.updateType = NodeUpdateType.NONE; - - this.referenceNode = null; - - this._value = value; - - this.setUpdateMatrix(uvNode === null); - } - - set value(value) { - if (this.referenceNode) { - this.referenceNode.value = value; - } else { - this._value = value; - } - } - - get value() { - return this.referenceNode ? this.referenceNode.value : this._value; - } - - getUniformHash(/*builder*/) { - return this.value.uuid; - } - - getNodeType(/*builder*/) { - if (this.value.isDepthTexture === true) return 'float'; - - return 'vec4'; - } - - getInputType(/*builder*/) { - return 'texture'; - } - - getDefaultUV() { - return uv(this.value.channel); - } - - updateReference(/*state*/) { - return this.value; - } - - getTransformedUV(uvNode) { - const texture = this.value; - - return uniform(texture.matrix).mul(vec3(uvNode, 1)).xy; - } - - setUpdateMatrix(value) { - this.updateMatrix = value; - this.updateType = value ? NodeUpdateType.FRAME : NodeUpdateType.NONE; - - return this; - } - - setupUV(builder, uvNode) { - const texture = this.value; - - if ( - builder.isFlipY() && - (texture.isRenderTargetTexture === true || - texture.isFramebufferTexture === true || - texture.isDepthTexture === true) - ) { - uvNode = uvNode.setY(uvNode.y.oneMinus()); - } - - return uvNode; - } - - setup(builder) { - const properties = builder.getNodeProperties(this); - - // - - let uvNode = this.uvNode; - - if ((uvNode === null || builder.context.forceUVContext === true) && builder.context.getUV) { - uvNode = builder.context.getUV(this); - } - - if (!uvNode) uvNode = this.getDefaultUV(); - - if (this.updateMatrix === true) { - uvNode = this.getTransformedUV(uvNode); - } - - uvNode = this.setupUV(builder, uvNode); - - // - - let levelNode = this.levelNode; - - if (levelNode === null && builder.context.getTextureLevel) { - levelNode = builder.context.getTextureLevel(this); - } - - // - - properties.uvNode = uvNode; - properties.levelNode = levelNode; - properties.compareNode = this.compareNode; - properties.gradNode = this.gradNode; - properties.depthNode = this.depthNode; - } - - generateUV(builder, uvNode) { - return uvNode.build(builder, this.sampler === true ? 'vec2' : 'ivec2'); - } - - generateSnippet(builder, textureProperty, uvSnippet, levelSnippet, depthSnippet, compareSnippet, gradSnippet) { - const texture = this.value; - - let snippet; - - if (levelSnippet) { - snippet = builder.generateTextureLevel(texture, textureProperty, uvSnippet, levelSnippet, depthSnippet); - } else if (gradSnippet) { - snippet = builder.generateTextureGrad(texture, textureProperty, uvSnippet, gradSnippet, depthSnippet); - } else if (compareSnippet) { - snippet = builder.generateTextureCompare(texture, textureProperty, uvSnippet, compareSnippet, depthSnippet); - } else if (this.sampler === false) { - snippet = builder.generateTextureLoad(texture, textureProperty, uvSnippet, depthSnippet); - } else { - snippet = builder.generateTexture(texture, textureProperty, uvSnippet, depthSnippet); - } - - return snippet; - } - - generate(builder, output) { - const properties = builder.getNodeProperties(this); - - const texture = this.value; - - if (!texture || texture.isTexture !== true) { - throw new Error('TextureNode: Need a three.js texture.'); - } - - const textureProperty = super.generate(builder, 'property'); - - if (output === 'sampler') { - return textureProperty + '_sampler'; - } else if (builder.isReference(output)) { - return textureProperty; - } else { - const nodeData = builder.getDataFromNode(this); - - let propertyName = nodeData.propertyName; - - if (propertyName === undefined) { - const { uvNode, levelNode, compareNode, depthNode, gradNode } = properties; - - const uvSnippet = this.generateUV(builder, uvNode); - const levelSnippet = levelNode ? levelNode.build(builder, 'float') : null; - const depthSnippet = depthNode ? depthNode.build(builder, 'int') : null; - const compareSnippet = compareNode ? compareNode.build(builder, 'float') : null; - const gradSnippet = gradNode - ? [gradNode[0].build(builder, 'vec2'), gradNode[1].build(builder, 'vec2')] - : null; - - const nodeVar = builder.getVarFromNode(this); - - propertyName = builder.getPropertyName(nodeVar); - - const snippet = this.generateSnippet( - builder, - textureProperty, - uvSnippet, - levelSnippet, - depthSnippet, - compareSnippet, - gradSnippet, - ); - - builder.addLineFlowCode(`${propertyName} = ${snippet}`); - - nodeData.snippet = snippet; - nodeData.propertyName = propertyName; - } - - let snippet = propertyName; - const nodeType = this.getNodeType(builder); - - if (builder.needsColorSpaceToLinear(texture)) { - snippet = colorSpaceToLinear(expression(snippet, nodeType), texture.colorSpace) - .setup(builder) - .build(builder, nodeType); - } - - return builder.format(snippet, nodeType, output); - } - } - - setSampler(value) { - this.sampler = value; - - return this; - } - - getSampler() { - return this.sampler; - } - - // @TODO: Move to TSL - - uv(uvNode) { - const textureNode = this.clone(); - textureNode.uvNode = uvNode; - textureNode.referenceNode = this; - - return nodeObject(textureNode); - } - - blur(levelNode) { - const textureNode = this.clone(); - textureNode.levelNode = levelNode.mul(maxMipLevel(textureNode)); - textureNode.referenceNode = this; - - return nodeObject(textureNode); - } - - level(levelNode) { - const textureNode = this.clone(); - textureNode.levelNode = levelNode; - textureNode.referenceNode = this; - - return textureNode; - } - - size(levelNode) { - return textureSize(this, levelNode); - } - - compare(compareNode) { - const textureNode = this.clone(); - textureNode.compareNode = nodeObject(compareNode); - textureNode.referenceNode = this; - - return nodeObject(textureNode); - } - - grad(gradNodeX, gradNodeY) { - const textureNode = this.clone(); - textureNode.gradNode = [nodeObject(gradNodeX), nodeObject(gradNodeY)]; - - textureNode.referenceNode = this; - - return nodeObject(textureNode); - } - - depth(depthNode) { - const textureNode = this.clone(); - textureNode.depthNode = nodeObject(depthNode); - textureNode.referenceNode = this; - - return nodeObject(textureNode); - } - - // -- - - serialize(data) { - super.serialize(data); - - data.value = this.value.toJSON(data.meta).uuid; - } - - deserialize(data) { - super.deserialize(data); - - this.value = data.meta.textures[data.value]; - } - - update() { - const texture = this.value; - - if (texture.matrixAutoUpdate === true) { - texture.updateMatrix(); - } - } - - clone() { - const newNode = new this.constructor(this.value, this.uvNode, this.levelNode); - newNode.sampler = this.sampler; - - return newNode; - } -} - -export default TextureNode; - -export const texture = nodeProxy(TextureNode); -export const textureLoad = (...params) => texture(...params).setSampler(false); - -//export const textureLevel = ( value, uv, level ) => texture( value, uv ).level( level ); - -export const sampler = aTexture => (aTexture.isNode === true ? aTexture : texture(aTexture)).convert('sampler'); - -addNodeElement('texture', texture); -//addNodeElement( 'textureLevel', textureLevel ); - -addNodeClass('TextureNode', TextureNode); diff --git a/examples-jsm/examples/nodes/code/CodeNode.ts b/examples-jsm/examples/nodes/code/CodeNode.ts deleted file mode 100644 index 06347564..00000000 --- a/examples-jsm/examples/nodes/code/CodeNode.ts +++ /dev/null @@ -1,66 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; -import { nodeProxy } from '../shadernode/ShaderNode.js'; - -class CodeNode extends Node { - constructor(code = '', includes = [], language = '') { - super('code'); - - this.isCodeNode = true; - - this.code = code; - this.language = language; - - this.includes = includes; - } - - isGlobal() { - return true; - } - - setIncludes(includes) { - this.includes = includes; - - return this; - } - - getIncludes(/*builder*/) { - return this.includes; - } - - generate(builder) { - const includes = this.getIncludes(builder); - - for (const include of includes) { - include.build(builder); - } - - const nodeCode = builder.getCodeFromNode(this, this.getNodeType(builder)); - nodeCode.code = this.code; - - return nodeCode.code; - } - - serialize(data) { - super.serialize(data); - - data.code = this.code; - data.language = this.language; - } - - deserialize(data) { - super.deserialize(data); - - this.code = data.code; - this.language = data.language; - } -} - -export default CodeNode; - -export const code = nodeProxy(CodeNode); - -export const js = (src, includes) => code(src, includes, 'js'); -export const wgsl = (src, includes) => code(src, includes, 'wgsl'); -export const glsl = (src, includes) => code(src, includes, 'glsl'); - -addNodeClass('CodeNode', CodeNode); diff --git a/examples-jsm/examples/nodes/code/FunctionNode.ts b/examples-jsm/examples/nodes/code/FunctionNode.ts deleted file mode 100644 index feeb5a55..00000000 --- a/examples-jsm/examples/nodes/code/FunctionNode.ts +++ /dev/null @@ -1,100 +0,0 @@ -import CodeNode from './CodeNode.js'; -import { addNodeClass } from '../core/Node.js'; -import { nodeObject } from '../shadernode/ShaderNode.js'; - -class FunctionNode extends CodeNode { - constructor(code = '', includes = [], language = '') { - super(code, includes, language); - - this.keywords = {}; - } - - getNodeType(builder) { - return this.getNodeFunction(builder).type; - } - - getInputs(builder) { - return this.getNodeFunction(builder).inputs; - } - - getNodeFunction(builder) { - const nodeData = builder.getDataFromNode(this); - - let nodeFunction = nodeData.nodeFunction; - - if (nodeFunction === undefined) { - nodeFunction = builder.parser.parseFunction(this.code); - - nodeData.nodeFunction = nodeFunction; - } - - return nodeFunction; - } - - generate(builder, output) { - super.generate(builder); - - const nodeFunction = this.getNodeFunction(builder); - - const name = nodeFunction.name; - const type = nodeFunction.type; - - const nodeCode = builder.getCodeFromNode(this, type); - - if (name !== '') { - // use a custom property name - - nodeCode.name = name; - } - - const propertyName = builder.getPropertyName(nodeCode); - - let code = this.getNodeFunction(builder).getCode(propertyName); - - const keywords = this.keywords; - const keywordsProperties = Object.keys(keywords); - - if (keywordsProperties.length > 0) { - for (const property of keywordsProperties) { - const propertyRegExp = new RegExp(`\\b${property}\\b`, 'g'); - const nodeProperty = keywords[property].build(builder, 'property'); - - code = code.replace(propertyRegExp, nodeProperty); - } - } - - nodeCode.code = code + '\n'; - - if (output === 'property') { - return propertyName; - } else { - return builder.format(`${propertyName}()`, type, output); - } - } -} - -export default FunctionNode; - -const nativeFn = (code, includes = [], language = '') => { - for (let i = 0; i < includes.length; i++) { - const include = includes[i]; - - // TSL Function: glslFn, wgslFn - - if (typeof include === 'function') { - includes[i] = include.functionNode; - } - } - - const functionNode = nodeObject(new FunctionNode(code, includes, language)); - - const fn = (...params) => functionNode.call(...params); - fn.functionNode = functionNode; - - return fn; -}; - -export const glslFn = (code, includes) => nativeFn(code, includes, 'glsl'); -export const wgslFn = (code, includes) => nativeFn(code, includes, 'wgsl'); - -addNodeClass('FunctionNode', FunctionNode); diff --git a/examples-jsm/examples/nodes/core/ContextNode.ts b/examples-jsm/examples/nodes/core/ContextNode.ts deleted file mode 100644 index fcd488eb..00000000 --- a/examples-jsm/examples/nodes/core/ContextNode.ts +++ /dev/null @@ -1,55 +0,0 @@ -import Node, { addNodeClass } from './Node.js'; -import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js'; - -class ContextNode extends Node { - constructor(node, context = {}) { - super(); - - this.isContextNode = true; - - this.node = node; - this.context = context; - } - - getNodeType(builder) { - return this.node.getNodeType(builder); - } - - analyze(builder) { - this.node.build(builder); - } - - setup(builder) { - const previousContext = builder.getContext(); - - builder.setContext({ ...builder.context, ...this.context }); - - const node = this.node.build(builder); - - builder.setContext(previousContext); - - return node; - } - - generate(builder, output) { - const previousContext = builder.getContext(); - - builder.setContext({ ...builder.context, ...this.context }); - - const snippet = this.node.build(builder, output); - - builder.setContext(previousContext); - - return snippet; - } -} - -export default ContextNode; - -export const context = nodeProxy(ContextNode); -export const label = (node, name) => context(node, { label: name }); - -addNodeElement('context', context); -addNodeElement('label', label); - -addNodeClass('ContextNode', ContextNode); diff --git a/examples-jsm/examples/nodes/core/InputNode.ts b/examples-jsm/examples/nodes/core/InputNode.ts deleted file mode 100644 index 4d52ec26..00000000 --- a/examples-jsm/examples/nodes/core/InputNode.ts +++ /dev/null @@ -1,65 +0,0 @@ -import Node, { addNodeClass } from './Node.js'; -import { getValueType, getValueFromType, arrayBufferToBase64 } from './NodeUtils.js'; - -class InputNode extends Node { - constructor(value, nodeType = null) { - super(nodeType); - - this.isInputNode = true; - - this.value = value; - this.precision = null; - } - - getNodeType(/*builder*/) { - if (this.nodeType === null) { - return getValueType(this.value); - } - - return this.nodeType; - } - - getInputType(builder) { - return this.getNodeType(builder); - } - - setPrecision(precision) { - this.precision = precision; - - return this; - } - - serialize(data) { - super.serialize(data); - - data.value = this.value; - - if (this.value && this.value.toArray) data.value = this.value.toArray(); - - data.valueType = getValueType(this.value); - data.nodeType = this.nodeType; - - if (data.valueType === 'ArrayBuffer') data.value = arrayBufferToBase64(data.value); - - data.precision = this.precision; - } - - deserialize(data) { - super.deserialize(data); - - this.nodeType = data.nodeType; - this.value = Array.isArray(data.value) ? getValueFromType(data.valueType, ...data.value) : data.value; - - this.precision = data.precision || null; - - if (this.value && this.value.fromArray) this.value = this.value.fromArray(data.value); - } - - generate(/*builder, output*/) { - console.warn('Abstract function.'); - } -} - -export default InputNode; - -addNodeClass('InputNode', InputNode); diff --git a/examples-jsm/examples/nodes/core/Node.ts b/examples-jsm/examples/nodes/core/Node.ts deleted file mode 100644 index 66444172..00000000 --- a/examples-jsm/examples/nodes/core/Node.ts +++ /dev/null @@ -1,424 +0,0 @@ -import { EventDispatcher } from 'three'; -import { NodeUpdateType } from './constants.js'; -import { getNodeChildren, getCacheKey } from './NodeUtils.js'; -import { MathUtils } from 'three'; - -const NodeClasses = new Map(); - -let _nodeId = 0; - -class Node extends EventDispatcher { - constructor(nodeType = null) { - super(); - - this.nodeType = nodeType; - - this.updateType = NodeUpdateType.NONE; - this.updateBeforeType = NodeUpdateType.NONE; - this.updateAfterType = NodeUpdateType.NONE; - - this.uuid = MathUtils.generateUUID(); - - this.version = 0; - - this._cacheKey = null; - this._cacheKeyVersion = 0; - - this.global = false; - - this.isNode = true; - - Object.defineProperty(this, 'id', { value: _nodeId++ }); - } - - set needsUpdate(value) { - if (value === true) { - this.version++; - } - } - - get type() { - return this.constructor.type; - } - - onUpdate(callback, updateType) { - this.updateType = updateType; - this.update = callback.bind(this.getSelf()); - - return this; - } - - onFrameUpdate(callback) { - return this.onUpdate(callback, NodeUpdateType.FRAME); - } - - onRenderUpdate(callback) { - return this.onUpdate(callback, NodeUpdateType.RENDER); - } - - onObjectUpdate(callback) { - return this.onUpdate(callback, NodeUpdateType.OBJECT); - } - - onReference(callback) { - this.updateReference = callback.bind(this.getSelf()); - - return this; - } - - getSelf() { - // Returns non-node object. - - return this.self || this; - } - - updateReference(/*state*/) { - return this; - } - - isGlobal(/*builder*/) { - return this.global; - } - - *getChildren() { - for (const { childNode } of getNodeChildren(this)) { - yield childNode; - } - } - - dispose() { - this.dispatchEvent({ type: 'dispose' }); - } - - traverse(callback) { - callback(this); - - for (const childNode of this.getChildren()) { - childNode.traverse(callback); - } - } - - getCacheKey(force = false) { - force = force || this.version !== this._cacheKeyVersion; - - if (force === true || this._cacheKey === null) { - this._cacheKey = getCacheKey(this, force); - this._cacheKeyVersion = this.version; - } - - return this._cacheKey; - } - - getHash(/*builder*/) { - return this.uuid; - } - - getUpdateType() { - return this.updateType; - } - - getUpdateBeforeType() { - return this.updateBeforeType; - } - - getUpdateAfterType() { - return this.updateAfterType; - } - - getElementType(builder) { - const type = this.getNodeType(builder); - const elementType = builder.getElementType(type); - - return elementType; - } - - getNodeType(builder) { - const nodeProperties = builder.getNodeProperties(this); - - if (nodeProperties.outputNode) { - return nodeProperties.outputNode.getNodeType(builder); - } - - return this.nodeType; - } - - getShared(builder) { - const hash = this.getHash(builder); - const nodeFromHash = builder.getNodeFromHash(hash); - - return nodeFromHash || this; - } - - setup(builder) { - const nodeProperties = builder.getNodeProperties(this); - - let index = 0; - - for (const childNode of this.getChildren()) { - nodeProperties['node' + index++] = childNode; - } - - // return a outputNode if exists - return null; - } - - construct(builder) { - // @deprecated, r157 - - console.warn('THREE.Node: construct() is deprecated. Use setup() instead.'); - - return this.setup(builder); - } - - increaseUsage(builder) { - const nodeData = builder.getDataFromNode(this); - nodeData.usageCount = nodeData.usageCount === undefined ? 1 : nodeData.usageCount + 1; - - return nodeData.usageCount; - } - - analyze(builder) { - const usageCount = this.increaseUsage(builder); - - if (usageCount === 1) { - // node flow children - - const nodeProperties = builder.getNodeProperties(this); - - for (const childNode of Object.values(nodeProperties)) { - if (childNode && childNode.isNode === true) { - childNode.build(builder); - } - } - } - } - - generate(builder, output) { - const { outputNode } = builder.getNodeProperties(this); - - if (outputNode && outputNode.isNode === true) { - return outputNode.build(builder, output); - } - } - - updateBefore(/*frame*/) { - console.warn('Abstract function.'); - } - - updateAfter(/*frame*/) { - console.warn('Abstract function.'); - } - - update(/*frame*/) { - console.warn('Abstract function.'); - } - - build(builder, output = null) { - const refNode = this.getShared(builder); - - if (this !== refNode) { - return refNode.build(builder, output); - } - - builder.addNode(this); - builder.addChain(this); - - /* Build stages expected results: - - "setup" -> Node - - "analyze" -> null - - "generate" -> String - */ - let result = null; - - const buildStage = builder.getBuildStage(); - - if (buildStage === 'setup') { - this.updateReference(builder); - - const properties = builder.getNodeProperties(this); - - if (properties.initialized !== true) { - const stackNodesBeforeSetup = builder.stack.nodes.length; - - properties.initialized = true; - properties.outputNode = this.setup(builder); - - if (properties.outputNode !== null && builder.stack.nodes.length !== stackNodesBeforeSetup) { - properties.outputNode = builder.stack; - } - - for (const childNode of Object.values(properties)) { - if (childNode && childNode.isNode === true) { - childNode.build(builder); - } - } - } - } else if (buildStage === 'analyze') { - this.analyze(builder); - } else if (buildStage === 'generate') { - const isGenerateOnce = this.generate.length === 1; - - if (isGenerateOnce) { - const type = this.getNodeType(builder); - const nodeData = builder.getDataFromNode(this); - - result = nodeData.snippet; - - if (result === undefined) { - result = this.generate(builder) || ''; - - nodeData.snippet = result; - } - - result = builder.format(result, type, output); - } else { - result = this.generate(builder, output) || ''; - } - } - - builder.removeChain(this); - - return result; - } - - getSerializeChildren() { - return getNodeChildren(this); - } - - serialize(json) { - const nodeChildren = this.getSerializeChildren(); - - const inputNodes = {}; - - for (const { property, index, childNode } of nodeChildren) { - if (index !== undefined) { - if (inputNodes[property] === undefined) { - inputNodes[property] = Number.isInteger(index) ? [] : {}; - } - - inputNodes[property][index] = childNode.toJSON(json.meta).uuid; - } else { - inputNodes[property] = childNode.toJSON(json.meta).uuid; - } - } - - if (Object.keys(inputNodes).length > 0) { - json.inputNodes = inputNodes; - } - } - - deserialize(json) { - if (json.inputNodes !== undefined) { - const nodes = json.meta.nodes; - - for (const property in json.inputNodes) { - if (Array.isArray(json.inputNodes[property])) { - const inputArray = []; - - for (const uuid of json.inputNodes[property]) { - inputArray.push(nodes[uuid]); - } - - this[property] = inputArray; - } else if (typeof json.inputNodes[property] === 'object') { - const inputObject = {}; - - for (const subProperty in json.inputNodes[property]) { - const uuid = json.inputNodes[property][subProperty]; - - inputObject[subProperty] = nodes[uuid]; - } - - this[property] = inputObject; - } else { - const uuid = json.inputNodes[property]; - - this[property] = nodes[uuid]; - } - } - } - } - - toJSON(meta) { - const { uuid, type } = this; - const isRoot = meta === undefined || typeof meta === 'string'; - - if (isRoot) { - meta = { - textures: {}, - images: {}, - nodes: {}, - }; - } - - // serialize - - let data = meta.nodes[uuid]; - - if (data === undefined) { - data = { - uuid, - type, - meta, - metadata: { - version: 4.6, - type: 'Node', - generator: 'Node.toJSON', - }, - }; - - if (isRoot !== true) meta.nodes[data.uuid] = data; - - this.serialize(data); - - delete data.meta; - } - - // TODO: Copied from Object3D.toJSON - - function extractFromCache(cache) { - const values = []; - - for (const key in cache) { - const data = cache[key]; - delete data.metadata; - values.push(data); - } - - return values; - } - - if (isRoot) { - const textures = extractFromCache(meta.textures); - const images = extractFromCache(meta.images); - const nodes = extractFromCache(meta.nodes); - - if (textures.length > 0) data.textures = textures; - if (images.length > 0) data.images = images; - if (nodes.length > 0) data.nodes = nodes; - } - - return data; - } -} - -export default Node; - -export function addNodeClass(type, nodeClass) { - if (typeof nodeClass !== 'function' || !type) throw new Error(`Node class ${type} is not a class`); - if (NodeClasses.has(type)) { - console.warn(`Redefinition of node class ${type}`); - return; - } - - NodeClasses.set(type, nodeClass); - nodeClass.type = type; -} - -export function createNodeFromType(type) { - const Class = NodeClasses.get(type); - - if (Class !== undefined) { - return new Class(); - } -} diff --git a/examples-jsm/examples/nodes/core/NodeAttribute.ts b/examples-jsm/examples/nodes/core/NodeAttribute.ts deleted file mode 100644 index 190fe8c5..00000000 --- a/examples-jsm/examples/nodes/core/NodeAttribute.ts +++ /dev/null @@ -1,11 +0,0 @@ -class NodeAttribute { - constructor(name, type, node = null) { - this.isNodeAttribute = true; - - this.name = name; - this.type = type; - this.node = node; - } -} - -export default NodeAttribute; diff --git a/examples-jsm/examples/nodes/core/NodeBuilder.ts b/examples-jsm/examples/nodes/core/NodeBuilder.ts deleted file mode 100644 index 2559a675..00000000 --- a/examples-jsm/examples/nodes/core/NodeBuilder.ts +++ /dev/null @@ -1,1119 +0,0 @@ -import NodeUniform from './NodeUniform.js'; -import NodeAttribute from './NodeAttribute.js'; -import NodeVarying from './NodeVarying.js'; -import NodeVar from './NodeVar.js'; -import NodeCode from './NodeCode.js'; -import NodeKeywords from './NodeKeywords.js'; -import NodeCache from './NodeCache.js'; -import ParameterNode from './ParameterNode.js'; -import FunctionNode from '../code/FunctionNode.js'; -import { createNodeMaterialFromType, default as NodeMaterial } from '../materials/NodeMaterial.js'; -import { NodeUpdateType, defaultBuildStages, shaderStages } from './constants.js'; - -import { - NumberNodeUniform, - Vector2NodeUniform, - Vector3NodeUniform, - Vector4NodeUniform, - ColorNodeUniform, - Matrix3NodeUniform, - Matrix4NodeUniform, -} from '../../renderers/common/nodes/NodeUniform.js'; - -import BindGroup from '../../renderers/common/BindGroup.js'; - -import { - REVISION, - RenderTarget, - Color, - Vector2, - Vector3, - Vector4, - IntType, - UnsignedIntType, - Float16BufferAttribute, - LinearFilter, - LinearMipmapNearestFilter, - NearestMipmapLinearFilter, - LinearMipmapLinearFilter, -} from 'three'; - -import { stack } from './StackNode.js'; -import { getCurrentStack, setCurrentStack } from '../shadernode/ShaderNode.js'; - -import CubeRenderTarget from '../../renderers/common/CubeRenderTarget.js'; -import ChainMap from '../../renderers/common/ChainMap.js'; - -import PMREMGenerator from '../../renderers/common/extras/PMREMGenerator.js'; - -const rendererCache = new WeakMap(); - -const typeFromLength = new Map([ - [2, 'vec2'], - [3, 'vec3'], - [4, 'vec4'], - [9, 'mat3'], - [16, 'mat4'], -]); - -const typeFromArray = new Map([ - [Int8Array, 'int'], - [Int16Array, 'int'], - [Int32Array, 'int'], - [Uint8Array, 'uint'], - [Uint16Array, 'uint'], - [Uint32Array, 'uint'], - [Float32Array, 'float'], -]); - -const toFloat = value => { - value = Number(value); - - return value + (value % 1 ? '' : '.0'); -}; - -class NodeBuilder { - constructor(object, renderer, parser) { - this.object = object; - this.material = (object && object.material) || null; - this.geometry = (object && object.geometry) || null; - this.renderer = renderer; - this.parser = parser; - this.scene = null; - this.camera = null; - - this.nodes = []; - this.updateNodes = []; - this.updateBeforeNodes = []; - this.updateAfterNodes = []; - this.hashNodes = {}; - - this.lightsNode = null; - this.environmentNode = null; - this.fogNode = null; - - this.clippingContext = null; - - this.vertexShader = null; - this.fragmentShader = null; - this.computeShader = null; - - this.flowNodes = { vertex: [], fragment: [], compute: [] }; - this.flowCode = { vertex: '', fragment: '', compute: '' }; - this.uniforms = { vertex: [], fragment: [], compute: [], index: 0 }; - this.structs = { vertex: [], fragment: [], compute: [], index: 0 }; - this.bindings = { vertex: {}, fragment: {}, compute: {} }; - this.bindingsIndexes = {}; - this.bindGroups = null; - this.attributes = []; - this.bufferAttributes = []; - this.varyings = []; - this.codes = {}; - this.vars = {}; - this.flow = { code: '' }; - this.chaining = []; - this.stack = stack(); - this.stacks = []; - this.tab = '\t'; - - this.instanceBindGroups = true; - - this.currentFunctionNode = null; - - this.context = { - keywords: new NodeKeywords(), - material: this.material, - }; - - this.cache = new NodeCache(); - this.globalCache = this.cache; - - this.flowsData = new WeakMap(); - - this.shaderStage = null; - this.buildStage = null; - } - - getBingGroupsCache() { - let bindGroupsCache = rendererCache.get(this.renderer); - - if (bindGroupsCache === undefined) { - bindGroupsCache = new ChainMap(); - - rendererCache.set(this.renderer, bindGroupsCache); - } - - return bindGroupsCache; - } - - createRenderTarget(width, height, options) { - return new RenderTarget(width, height, options); - } - - createCubeRenderTarget(size, options) { - return new CubeRenderTarget(size, options); - } - - createPMREMGenerator() { - // TODO: Move Materials.js to outside of the Nodes.js in order to remove this function and improve tree-shaking support - - return new PMREMGenerator(this.renderer); - } - - includes(node) { - return this.nodes.includes(node); - } - - _getBindGroup(groupName, bindings) { - const bindGroupsCache = this.getBingGroupsCache(); - - // cache individual uniforms group - - const bindingsArray = []; - - let sharedGroup = true; - - for (const binding of bindings) { - if (binding.groupNode.shared === true) { - // nodes is the chainmap key - const nodes = binding.getNodes(); - - let sharedBinding = bindGroupsCache.get(nodes); - - if (sharedBinding === undefined) { - bindGroupsCache.set(nodes, binding); - - sharedBinding = binding; - } - - bindingsArray.push(sharedBinding); - } else { - bindingsArray.push(binding); - - sharedGroup = false; - } - } - - // - - let bindGroup; - - if (sharedGroup) { - bindGroup = bindGroupsCache.get(bindingsArray); - - if (bindGroup === undefined) { - bindGroup = new BindGroup(groupName, bindingsArray); - bindGroupsCache.set(bindingsArray, bindGroup); - } - } else { - bindGroup = new BindGroup(groupName, bindingsArray); - } - - return bindGroup; - } - - getBindGroupArray(groupName, shaderStage) { - const bindings = this.bindings[shaderStage]; - - let bindGroup = bindings[groupName]; - - if (bindGroup === undefined) { - if (this.bindingsIndexes[groupName] === undefined) { - this.bindingsIndexes[groupName] = { binding: 0, group: Object.keys(this.bindingsIndexes).length }; - } - - bindings[groupName] = bindGroup = []; - } - - return bindGroup; - } - - getBindings() { - let bindingsGroups = this.bindGroups; - - if (bindingsGroups === null) { - const groups = {}; - const bindings = this.bindings; - - for (const shaderStage of shaderStages) { - for (const groupName in bindings[shaderStage]) { - const uniforms = bindings[shaderStage][groupName]; - - const groupUniforms = groups[groupName] || (groups[groupName] = []); - groupUniforms.push(...uniforms); - } - } - - bindingsGroups = []; - - for (const groupName in groups) { - const group = groups[groupName]; - - const bindingsGroup = this._getBindGroup(groupName, group); - - bindingsGroups.push(bindingsGroup); - } - - this.bindGroups = bindingsGroups; - } - - return bindingsGroups; - } - - setHashNode(node, hash) { - this.hashNodes[hash] = node; - } - - addNode(node) { - if (this.nodes.includes(node) === false) { - this.nodes.push(node); - - this.setHashNode(node, node.getHash(this)); - } - } - - buildUpdateNodes() { - for (const node of this.nodes) { - const updateType = node.getUpdateType(); - const updateBeforeType = node.getUpdateBeforeType(); - const updateAfterType = node.getUpdateAfterType(); - - if (updateType !== NodeUpdateType.NONE) { - this.updateNodes.push(node.getSelf()); - } - - if (updateBeforeType !== NodeUpdateType.NONE) { - this.updateBeforeNodes.push(node); - } - - if (updateAfterType !== NodeUpdateType.NONE) { - this.updateAfterNodes.push(node); - } - } - } - - get currentNode() { - return this.chaining[this.chaining.length - 1]; - } - - isFilteredTexture(texture) { - return ( - texture.magFilter === LinearFilter || - texture.magFilter === LinearMipmapNearestFilter || - texture.magFilter === NearestMipmapLinearFilter || - texture.magFilter === LinearMipmapLinearFilter || - texture.minFilter === LinearFilter || - texture.minFilter === LinearMipmapNearestFilter || - texture.minFilter === NearestMipmapLinearFilter || - texture.minFilter === LinearMipmapLinearFilter - ); - } - - addChain(node) { - /* - if ( this.chaining.indexOf( node ) !== - 1 ) { - - console.warn( 'Recursive node: ', node ); - - } - */ - - this.chaining.push(node); - } - - removeChain(node) { - const lastChain = this.chaining.pop(); - - if (lastChain !== node) { - throw new Error('NodeBuilder: Invalid node chaining!'); - } - } - - getMethod(method) { - return method; - } - - getNodeFromHash(hash) { - return this.hashNodes[hash]; - } - - addFlow(shaderStage, node) { - this.flowNodes[shaderStage].push(node); - - return node; - } - - setContext(context) { - this.context = context; - } - - getContext() { - return this.context; - } - - setCache(cache) { - this.cache = cache; - } - - getCache() { - return this.cache; - } - - getCacheFromNode(node, parent = true) { - const data = this.getDataFromNode(node); - if (data.cache === undefined) data.cache = new NodeCache(parent ? this.getCache() : null); - - return data.cache; - } - - isAvailable(/*name*/) { - return false; - } - - getVertexIndex() { - console.warn('Abstract function.'); - } - - getInstanceIndex() { - console.warn('Abstract function.'); - } - - getFrontFacing() { - console.warn('Abstract function.'); - } - - getFragCoord() { - console.warn('Abstract function.'); - } - - isFlipY() { - return false; - } - - generateTexture(/* texture, textureProperty, uvSnippet */) { - console.warn('Abstract function.'); - } - - generateTextureLod(/* texture, textureProperty, uvSnippet, levelSnippet */) { - console.warn('Abstract function.'); - } - - generateConst(type, value = null) { - if (value === null) { - if (type === 'float' || type === 'int' || type === 'uint') value = 0; - else if (type === 'bool') value = false; - else if (type === 'color') value = new Color(); - else if (type === 'vec2') value = new Vector2(); - else if (type === 'vec3') value = new Vector3(); - else if (type === 'vec4') value = new Vector4(); - } - - if (type === 'float') return toFloat(value); - if (type === 'int') return `${Math.round(value)}`; - if (type === 'uint') return value >= 0 ? `${Math.round(value)}u` : '0u'; - if (type === 'bool') return value ? 'true' : 'false'; - if (type === 'color') - return `${this.getType('vec3')}( ${toFloat(value.r)}, ${toFloat(value.g)}, ${toFloat(value.b)} )`; - - const typeLength = this.getTypeLength(type); - - const componentType = this.getComponentType(type); - - const generateConst = value => this.generateConst(componentType, value); - - if (typeLength === 2) { - return `${this.getType(type)}( ${generateConst(value.x)}, ${generateConst(value.y)} )`; - } else if (typeLength === 3) { - return `${this.getType(type)}( ${generateConst(value.x)}, ${generateConst(value.y)}, ${generateConst(value.z)} )`; - } else if (typeLength === 4) { - return `${this.getType(type)}( ${generateConst(value.x)}, ${generateConst(value.y)}, ${generateConst(value.z)}, ${generateConst(value.w)} )`; - } else if (typeLength > 4 && value && (value.isMatrix3 || value.isMatrix4)) { - return `${this.getType(type)}( ${value.elements.map(generateConst).join(', ')} )`; - } else if (typeLength > 4) { - return `${this.getType(type)}()`; - } - - throw new Error(`NodeBuilder: Type '${type}' not found in generate constant attempt.`); - } - - getType(type) { - if (type === 'color') return 'vec3'; - - return type; - } - - hasGeometryAttribute(name) { - return this.geometry && this.geometry.getAttribute(name) !== undefined; - } - - getAttribute(name, type) { - const attributes = this.attributes; - - // find attribute - - for (const attribute of attributes) { - if (attribute.name === name) { - return attribute; - } - } - - // create a new if no exist - - const attribute = new NodeAttribute(name, type); - - attributes.push(attribute); - - return attribute; - } - - getPropertyName(node /*, shaderStage*/) { - return node.name; - } - - isVector(type) { - return /vec\d/.test(type); - } - - isMatrix(type) { - return /mat\d/.test(type); - } - - isReference(type) { - return ( - type === 'void' || - type === 'property' || - type === 'sampler' || - type === 'texture' || - type === 'cubeTexture' || - type === 'storageTexture' || - type === 'depthTexture' || - type === 'texture3D' - ); - } - - needsColorSpaceToLinear(/*texture*/) { - return false; - } - - getComponentTypeFromTexture(texture) { - const type = texture.type; - - if (texture.isDataTexture) { - if (type === IntType) return 'int'; - if (type === UnsignedIntType) return 'uint'; - } - - return 'float'; - } - - getElementType(type) { - if (type === 'mat2') return 'vec2'; - if (type === 'mat3') return 'vec3'; - if (type === 'mat4') return 'vec4'; - - return this.getComponentType(type); - } - - getComponentType(type) { - type = this.getVectorType(type); - - if (type === 'float' || type === 'bool' || type === 'int' || type === 'uint') return type; - - const componentType = /(b|i|u|)(vec|mat)([2-4])/.exec(type); - - if (componentType === null) return null; - - if (componentType[1] === 'b') return 'bool'; - if (componentType[1] === 'i') return 'int'; - if (componentType[1] === 'u') return 'uint'; - - return 'float'; - } - - getVectorType(type) { - if (type === 'color') return 'vec3'; - if (type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' || type === 'texture3D') - return 'vec4'; - - return type; - } - - getTypeFromLength(length, componentType = 'float') { - if (length === 1) return componentType; - - const baseType = typeFromLength.get(length); - const prefix = componentType === 'float' ? '' : componentType[0]; - - return prefix + baseType; - } - - getTypeFromArray(array) { - return typeFromArray.get(array.constructor); - } - - getTypeFromAttribute(attribute) { - let dataAttribute = attribute; - - if (attribute.isInterleavedBufferAttribute) dataAttribute = attribute.data; - - const array = dataAttribute.array; - const itemSize = attribute.itemSize; - const normalized = attribute.normalized; - - let arrayType; - - if (!(attribute instanceof Float16BufferAttribute) && normalized !== true) { - arrayType = this.getTypeFromArray(array); - } - - return this.getTypeFromLength(itemSize, arrayType); - } - - getTypeLength(type) { - const vecType = this.getVectorType(type); - const vecNum = /vec([2-4])/.exec(vecType); - - if (vecNum !== null) return Number(vecNum[1]); - if (vecType === 'float' || vecType === 'bool' || vecType === 'int' || vecType === 'uint') return 1; - if (/mat2/.test(type) === true) return 4; - if (/mat3/.test(type) === true) return 9; - if (/mat4/.test(type) === true) return 16; - - return 0; - } - - getVectorFromMatrix(type) { - return type.replace('mat', 'vec'); - } - - changeComponentType(type, newComponentType) { - return this.getTypeFromLength(this.getTypeLength(type), newComponentType); - } - - getIntegerType(type) { - const componentType = this.getComponentType(type); - - if (componentType === 'int' || componentType === 'uint') return type; - - return this.changeComponentType(type, 'int'); - } - - addStack() { - this.stack = stack(this.stack); - - this.stacks.push(getCurrentStack() || this.stack); - setCurrentStack(this.stack); - - return this.stack; - } - - removeStack() { - const lastStack = this.stack; - this.stack = lastStack.parent; - - setCurrentStack(this.stacks.pop()); - - return lastStack; - } - - getDataFromNode(node, shaderStage = this.shaderStage, cache = null) { - cache = cache === null ? (node.isGlobal(this) ? this.globalCache : this.cache) : cache; - - let nodeData = cache.getData(node); - - if (nodeData === undefined) { - nodeData = {}; - - cache.setData(node, nodeData); - } - - if (nodeData[shaderStage] === undefined) nodeData[shaderStage] = {}; - - return nodeData[shaderStage]; - } - - getNodeProperties(node, shaderStage = 'any') { - const nodeData = this.getDataFromNode(node, shaderStage); - - return nodeData.properties || (nodeData.properties = { outputNode: null }); - } - - getBufferAttributeFromNode(node, type) { - const nodeData = this.getDataFromNode(node); - - let bufferAttribute = nodeData.bufferAttribute; - - if (bufferAttribute === undefined) { - const index = this.uniforms.index++; - - bufferAttribute = new NodeAttribute('nodeAttribute' + index, type, node); - - this.bufferAttributes.push(bufferAttribute); - - nodeData.bufferAttribute = bufferAttribute; - } - - return bufferAttribute; - } - - getStructTypeFromNode(node, shaderStage = this.shaderStage) { - const nodeData = this.getDataFromNode(node, shaderStage); - - if (nodeData.structType === undefined) { - const index = this.structs.index++; - - node.name = `StructType${index}`; - this.structs[shaderStage].push(node); - - nodeData.structType = node; - } - - return node; - } - - getUniformFromNode(node, type, shaderStage = this.shaderStage, name = null) { - const nodeData = this.getDataFromNode(node, shaderStage, this.globalCache); - - let nodeUniform = nodeData.uniform; - - if (nodeUniform === undefined) { - const index = this.uniforms.index++; - - nodeUniform = new NodeUniform(name || 'nodeUniform' + index, type, node); - - this.uniforms[shaderStage].push(nodeUniform); - - nodeData.uniform = nodeUniform; - } - - return nodeUniform; - } - - getVarFromNode(node, name = null, type = node.getNodeType(this), shaderStage = this.shaderStage) { - const nodeData = this.getDataFromNode(node, shaderStage); - - let nodeVar = nodeData.variable; - - if (nodeVar === undefined) { - const vars = this.vars[shaderStage] || (this.vars[shaderStage] = []); - - if (name === null) name = 'nodeVar' + vars.length; - - nodeVar = new NodeVar(name, type); - - vars.push(nodeVar); - - nodeData.variable = nodeVar; - } - - return nodeVar; - } - - getVaryingFromNode(node, name = null, type = node.getNodeType(this)) { - const nodeData = this.getDataFromNode(node, 'any'); - - let nodeVarying = nodeData.varying; - - if (nodeVarying === undefined) { - const varyings = this.varyings; - const index = varyings.length; - - if (name === null) name = 'nodeVarying' + index; - - nodeVarying = new NodeVarying(name, type); - - varyings.push(nodeVarying); - - nodeData.varying = nodeVarying; - } - - return nodeVarying; - } - - getCodeFromNode(node, type, shaderStage = this.shaderStage) { - const nodeData = this.getDataFromNode(node); - - let nodeCode = nodeData.code; - - if (nodeCode === undefined) { - const codes = this.codes[shaderStage] || (this.codes[shaderStage] = []); - const index = codes.length; - - nodeCode = new NodeCode('nodeCode' + index, type); - - codes.push(nodeCode); - - nodeData.code = nodeCode; - } - - return nodeCode; - } - - addLineFlowCode(code) { - if (code === '') return this; - - code = this.tab + code; - - if (!/;\s*$/.test(code)) { - code = code + ';\n'; - } - - this.flow.code += code; - - return this; - } - - addFlowCode(code) { - this.flow.code += code; - - return this; - } - - addFlowTab() { - this.tab += '\t'; - - return this; - } - - removeFlowTab() { - this.tab = this.tab.slice(0, -1); - - return this; - } - - getFlowData(node /*, shaderStage*/) { - return this.flowsData.get(node); - } - - flowNode(node) { - const output = node.getNodeType(this); - - const flowData = this.flowChildNode(node, output); - - this.flowsData.set(node, flowData); - - return flowData; - } - - buildFunctionNode(shaderNode) { - const fn = new FunctionNode(); - - const previous = this.currentFunctionNode; - - this.currentFunctionNode = fn; - - fn.code = this.buildFunctionCode(shaderNode); - - this.currentFunctionNode = previous; - - return fn; - } - - flowShaderNode(shaderNode) { - const layout = shaderNode.layout; - - let inputs; - - if (shaderNode.isArrayInput) { - inputs = []; - - for (const input of layout.inputs) { - inputs.push(new ParameterNode(input.type, input.name)); - } - } else { - inputs = {}; - - for (const input of layout.inputs) { - inputs[input.name] = new ParameterNode(input.type, input.name); - } - } - - // - - shaderNode.layout = null; - - const callNode = shaderNode.call(inputs); - const flowData = this.flowStagesNode(callNode, layout.type); - - shaderNode.layout = layout; - - return flowData; - } - - flowStagesNode(node, output = null) { - const previousFlow = this.flow; - const previousVars = this.vars; - const previousCache = this.cache; - const previousBuildStage = this.buildStage; - const previousStack = this.stack; - - const flow = { - code: '', - }; - - this.flow = flow; - this.vars = {}; - this.cache = new NodeCache(); - this.stack = stack(); - - for (const buildStage of defaultBuildStages) { - this.setBuildStage(buildStage); - - flow.result = node.build(this, output); - } - - flow.vars = this.getVars(this.shaderStage); - - this.flow = previousFlow; - this.vars = previousVars; - this.cache = previousCache; - this.stack = previousStack; - - this.setBuildStage(previousBuildStage); - - return flow; - } - - getFunctionOperator() { - return null; - } - - flowChildNode(node, output = null) { - const previousFlow = this.flow; - - const flow = { - code: '', - }; - - this.flow = flow; - - flow.result = node.build(this, output); - - this.flow = previousFlow; - - return flow; - } - - flowNodeFromShaderStage(shaderStage, node, output = null, propertyName = null) { - const previousShaderStage = this.shaderStage; - - this.setShaderStage(shaderStage); - - const flowData = this.flowChildNode(node, output); - - if (propertyName !== null) { - flowData.code += `${this.tab + propertyName} = ${flowData.result};\n`; - } - - this.flowCode[shaderStage] = this.flowCode[shaderStage] + flowData.code; - - this.setShaderStage(previousShaderStage); - - return flowData; - } - - getAttributesArray() { - return this.attributes.concat(this.bufferAttributes); - } - - getAttributes(/*shaderStage*/) { - console.warn('Abstract function.'); - } - - getVaryings(/*shaderStage*/) { - console.warn('Abstract function.'); - } - - getVar(type, name) { - return `${this.getType(type)} ${name}`; - } - - getVars(shaderStage) { - let snippet = ''; - - const vars = this.vars[shaderStage]; - - if (vars !== undefined) { - for (const variable of vars) { - snippet += `${this.getVar(variable.type, variable.name)}; `; - } - } - - return snippet; - } - - getUniforms(/*shaderStage*/) { - console.warn('Abstract function.'); - } - - getCodes(shaderStage) { - const codes = this.codes[shaderStage]; - - let code = ''; - - if (codes !== undefined) { - for (const nodeCode of codes) { - code += nodeCode.code + '\n'; - } - } - - return code; - } - - getHash() { - return this.vertexShader + this.fragmentShader + this.computeShader; - } - - setShaderStage(shaderStage) { - this.shaderStage = shaderStage; - } - - getShaderStage() { - return this.shaderStage; - } - - setBuildStage(buildStage) { - this.buildStage = buildStage; - } - - getBuildStage() { - return this.buildStage; - } - - buildCode() { - console.warn('Abstract function.'); - } - - build() { - const { object, material } = this; - - if (material !== null) { - NodeMaterial.fromMaterial(material).build(this); - } else { - this.addFlow('compute', object); - } - - // setup() -> stage 1: create possible new nodes and returns an output reference node - // analyze() -> stage 2: analyze nodes to possible optimization and validation - // generate() -> stage 3: generate shader - - for (const buildStage of defaultBuildStages) { - this.setBuildStage(buildStage); - - if (this.context.vertex && this.context.vertex.isNode) { - this.flowNodeFromShaderStage('vertex', this.context.vertex); - } - - for (const shaderStage of shaderStages) { - this.setShaderStage(shaderStage); - - const flowNodes = this.flowNodes[shaderStage]; - - for (const node of flowNodes) { - if (buildStage === 'generate') { - this.flowNode(node); - } else { - node.build(this); - } - } - } - } - - this.setBuildStage(null); - this.setShaderStage(null); - - // stage 4: build code for a specific output - - this.buildCode(); - this.buildUpdateNodes(); - - return this; - } - - getNodeUniform(uniformNode, type) { - if (type === 'float' || type === 'int' || type === 'uint') return new NumberNodeUniform(uniformNode); - if (type === 'vec2' || type === 'ivec2' || type === 'uvec2') return new Vector2NodeUniform(uniformNode); - if (type === 'vec3' || type === 'ivec3' || type === 'uvec3') return new Vector3NodeUniform(uniformNode); - if (type === 'vec4' || type === 'ivec4' || type === 'uvec4') return new Vector4NodeUniform(uniformNode); - if (type === 'color') return new ColorNodeUniform(uniformNode); - if (type === 'mat3') return new Matrix3NodeUniform(uniformNode); - if (type === 'mat4') return new Matrix4NodeUniform(uniformNode); - - throw new Error(`Uniform "${type}" not declared.`); - } - - createNodeMaterial(type = 'NodeMaterial') { - // TODO: Move Materials.js to outside of the Nodes.js in order to remove this function and improve tree-shaking support - - return createNodeMaterialFromType(type); - } - - format(snippet, fromType, toType) { - fromType = this.getVectorType(fromType); - toType = this.getVectorType(toType); - - if (fromType === toType || toType === null || this.isReference(toType)) { - return snippet; - } - - const fromTypeLength = this.getTypeLength(fromType); - const toTypeLength = this.getTypeLength(toType); - - if (fromTypeLength > 4) { - // fromType is matrix-like - - // @TODO: ignore for now - - return snippet; - } - - if (toTypeLength > 4 || toTypeLength === 0) { - // toType is matrix-like or unknown - - // @TODO: ignore for now - - return snippet; - } - - if (fromTypeLength === toTypeLength) { - return `${this.getType(toType)}( ${snippet} )`; - } - - if (fromTypeLength > toTypeLength) { - return this.format( - `${snippet}.${'xyz'.slice(0, toTypeLength)}`, - this.getTypeFromLength(toTypeLength, this.getComponentType(fromType)), - toType, - ); - } - - if (toTypeLength === 4 && fromTypeLength > 1) { - // toType is vec4-like - - return `${this.getType(toType)}( ${this.format(snippet, fromType, 'vec3')}, 1.0 )`; - } - - if (fromTypeLength === 2) { - // fromType is vec2-like and toType is vec3-like - - return `${this.getType(toType)}( ${this.format(snippet, fromType, 'vec2')}, 0.0 )`; - } - - if (fromTypeLength === 1 && toTypeLength > 1 && fromType !== this.getComponentType(toType)) { - // fromType is float-like - - // convert a number value to vector type, e.g: - // vec3( 1u ) -> vec3( float( 1u ) ) - - snippet = `${this.getType(this.getComponentType(toType))}( ${snippet} )`; - } - - return `${this.getType(toType)}( ${snippet} )`; // fromType is float-like - } - - getSignature() { - return `// Three.js r${REVISION} - Node System\n`; - } -} - -export default NodeBuilder; diff --git a/examples-jsm/examples/nodes/core/NodeCache.ts b/examples-jsm/examples/nodes/core/NodeCache.ts deleted file mode 100644 index ad72d50c..00000000 --- a/examples-jsm/examples/nodes/core/NodeCache.ts +++ /dev/null @@ -1,26 +0,0 @@ -let id = 0; - -class NodeCache { - constructor(parent = null) { - this.id = id++; - this.nodesData = new WeakMap(); - - this.parent = parent; - } - - getData(node) { - let data = this.nodesData.get(node); - - if (data === undefined && this.parent !== null) { - data = this.parent.getData(node); - } - - return data; - } - - setData(node, data) { - this.nodesData.set(node, data); - } -} - -export default NodeCache; diff --git a/examples-jsm/examples/nodes/core/NodeCode.ts b/examples-jsm/examples/nodes/core/NodeCode.ts deleted file mode 100644 index 2ee50903..00000000 --- a/examples-jsm/examples/nodes/core/NodeCode.ts +++ /dev/null @@ -1,11 +0,0 @@ -class NodeCode { - constructor(name, type, code = '') { - this.name = name; - this.type = type; - this.code = code; - - Object.defineProperty(this, 'isNodeCode', { value: true }); - } -} - -export default NodeCode; diff --git a/examples-jsm/examples/nodes/core/NodeFrame.ts b/examples-jsm/examples/nodes/core/NodeFrame.ts deleted file mode 100644 index ee64620c..00000000 --- a/examples-jsm/examples/nodes/core/NodeFrame.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { NodeUpdateType } from './constants.js'; - -class NodeFrame { - constructor() { - this.time = 0; - this.deltaTime = 0; - - this.frameId = 0; - this.renderId = 0; - - this.startTime = null; - - this.updateMap = new WeakMap(); - this.updateBeforeMap = new WeakMap(); - this.updateAfterMap = new WeakMap(); - - this.renderer = null; - this.material = null; - this.camera = null; - this.object = null; - this.scene = null; - } - - _getMaps(referenceMap, nodeRef) { - let maps = referenceMap.get(nodeRef); - - if (maps === undefined) { - maps = { - renderMap: new WeakMap(), - frameMap: new WeakMap(), - }; - - referenceMap.set(nodeRef, maps); - } - - return maps; - } - - updateBeforeNode(node) { - const updateType = node.getUpdateBeforeType(); - const reference = node.updateReference(this); - - if (updateType === NodeUpdateType.FRAME) { - const { frameMap } = this._getMaps(this.updateBeforeMap, reference); - - if (frameMap.get(reference) !== this.frameId) { - if (node.updateBefore(this) !== false) { - frameMap.set(reference, this.frameId); - } - } - } else if (updateType === NodeUpdateType.RENDER) { - const { renderMap } = this._getMaps(this.updateBeforeMap, reference); - - if (renderMap.get(reference) !== this.renderId) { - if (node.updateBefore(this) !== false) { - renderMap.set(reference, this.renderId); - } - } - } else if (updateType === NodeUpdateType.OBJECT) { - node.updateBefore(this); - } - } - - updateAfterNode(node) { - const updateType = node.getUpdateAfterType(); - const reference = node.updateReference(this); - - if (updateType === NodeUpdateType.FRAME) { - const { frameMap } = this._getMaps(this.updateAfterMap, reference); - - if (frameMap.get(reference) !== this.frameId) { - if (node.updateAfter(this) !== false) { - frameMap.set(reference, this.frameId); - } - } - } else if (updateType === NodeUpdateType.RENDER) { - const { renderMap } = this._getMaps(this.updateAfterMap, reference); - - if (renderMap.get(reference) !== this.renderId) { - if (node.updateAfter(this) !== false) { - renderMap.set(reference, this.renderId); - } - } - } else if (updateType === NodeUpdateType.OBJECT) { - node.updateAfter(this); - } - } - - updateNode(node) { - const updateType = node.getUpdateType(); - const reference = node.updateReference(this); - - if (updateType === NodeUpdateType.FRAME) { - const { frameMap } = this._getMaps(this.updateMap, reference); - - if (frameMap.get(reference) !== this.frameId) { - if (node.update(this) !== false) { - frameMap.set(reference, this.frameId); - } - } - } else if (updateType === NodeUpdateType.RENDER) { - const { renderMap } = this._getMaps(this.updateMap, reference); - - if (renderMap.get(reference) !== this.renderId) { - if (node.update(this) !== false) { - renderMap.set(reference, this.renderId); - } - } - } else if (updateType === NodeUpdateType.OBJECT) { - node.update(this); - } - } - - update() { - this.frameId++; - - if (this.lastTime === undefined) this.lastTime = performance.now(); - - this.deltaTime = (performance.now() - this.lastTime) / 1000; - - this.lastTime = performance.now(); - - this.time += this.deltaTime; - } -} - -export default NodeFrame; diff --git a/examples-jsm/examples/nodes/core/NodeFunction.ts b/examples-jsm/examples/nodes/core/NodeFunction.ts deleted file mode 100644 index d05afb5e..00000000 --- a/examples-jsm/examples/nodes/core/NodeFunction.ts +++ /dev/null @@ -1,16 +0,0 @@ -class NodeFunction { - constructor(type, inputs, name = '', precision = '') { - this.type = type; - this.inputs = inputs; - this.name = name; - this.precision = precision; - } - - getCode(/*name = this.name*/) { - console.warn('Abstract function.'); - } -} - -NodeFunction.isNodeFunction = true; - -export default NodeFunction; diff --git a/examples-jsm/examples/nodes/core/NodeKeywords.ts b/examples-jsm/examples/nodes/core/NodeKeywords.ts deleted file mode 100644 index 1f468321..00000000 --- a/examples-jsm/examples/nodes/core/NodeKeywords.ts +++ /dev/null @@ -1,58 +0,0 @@ -class NodeKeywords { - constructor() { - this.keywords = []; - this.nodes = {}; - this.keywordsCallback = {}; - } - - getNode(name) { - let node = this.nodes[name]; - - if (node === undefined && this.keywordsCallback[name] !== undefined) { - node = this.keywordsCallback[name](name); - - this.nodes[name] = node; - } - - return node; - } - - addKeyword(name, callback) { - this.keywords.push(name); - this.keywordsCallback[name] = callback; - - return this; - } - - parse(code) { - const keywordNames = this.keywords; - - const regExp = new RegExp(`\\b${keywordNames.join('\\b|\\b')}\\b`, 'g'); - - const codeKeywords = code.match(regExp); - - const keywordNodes = []; - - if (codeKeywords !== null) { - for (const keyword of codeKeywords) { - const node = this.getNode(keyword); - - if (node !== undefined && keywordNodes.indexOf(node) === -1) { - keywordNodes.push(node); - } - } - } - - return keywordNodes; - } - - include(builder, code) { - const keywordNodes = this.parse(code); - - for (const keywordNode of keywordNodes) { - keywordNode.build(builder); - } - } -} - -export default NodeKeywords; diff --git a/examples-jsm/examples/nodes/core/NodeParser.ts b/examples-jsm/examples/nodes/core/NodeParser.ts deleted file mode 100644 index 9849452f..00000000 --- a/examples-jsm/examples/nodes/core/NodeParser.ts +++ /dev/null @@ -1,7 +0,0 @@ -class NodeParser { - parseFunction(/*source*/) { - console.warn('Abstract function.'); - } -} - -export default NodeParser; diff --git a/examples-jsm/examples/nodes/core/NodeUniform.ts b/examples-jsm/examples/nodes/core/NodeUniform.ts deleted file mode 100644 index ca43958f..00000000 --- a/examples-jsm/examples/nodes/core/NodeUniform.ts +++ /dev/null @@ -1,27 +0,0 @@ -class NodeUniform { - constructor(name, type, node) { - this.isNodeUniform = true; - - this.name = name; - this.type = type; - this.node = node.getSelf(); - } - - get value() { - return this.node.value; - } - - set value(val) { - this.node.value = val; - } - - get id() { - return this.node.id; - } - - get groupNode() { - return this.node.groupNode; - } -} - -export default NodeUniform; diff --git a/examples-jsm/examples/nodes/core/NodeUtils.ts b/examples-jsm/examples/nodes/core/NodeUtils.ts deleted file mode 100644 index 16a5f324..00000000 --- a/examples-jsm/examples/nodes/core/NodeUtils.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { Color, Matrix3, Matrix4, Vector2, Vector3, Vector4 } from 'three'; - -export function getCacheKey(object, force = false) { - let cacheKey = '{'; - - if (object.isNode === true) { - cacheKey += object.id; - } - - for (const { property, childNode } of getNodeChildren(object)) { - cacheKey += ',' + property.slice(0, -4) + ':' + childNode.getCacheKey(force); - } - - cacheKey += '}'; - - return cacheKey; -} - -export function* getNodeChildren(node, toJSON = false) { - for (const property in node) { - // Ignore private properties. - if (property.startsWith('_') === true) continue; - - const object = node[property]; - - if (Array.isArray(object) === true) { - for (let i = 0; i < object.length; i++) { - const child = object[i]; - - if (child && (child.isNode === true || (toJSON && typeof child.toJSON === 'function'))) { - yield { property, index: i, childNode: child }; - } - } - } else if (object && object.isNode === true) { - yield { property, childNode: object }; - } else if (typeof object === 'object') { - for (const subProperty in object) { - const child = object[subProperty]; - - if (child && (child.isNode === true || (toJSON && typeof child.toJSON === 'function'))) { - yield { property, index: subProperty, childNode: child }; - } - } - } - } -} - -export function getValueType(value) { - if (value === undefined || value === null) return null; - - const typeOf = typeof value; - - if (value.isNode === true) { - return 'node'; - } else if (typeOf === 'number') { - return 'float'; - } else if (typeOf === 'boolean') { - return 'bool'; - } else if (typeOf === 'string') { - return 'string'; - } else if (typeOf === 'function') { - return 'shader'; - } else if (value.isVector2 === true) { - return 'vec2'; - } else if (value.isVector3 === true) { - return 'vec3'; - } else if (value.isVector4 === true) { - return 'vec4'; - } else if (value.isMatrix3 === true) { - return 'mat3'; - } else if (value.isMatrix4 === true) { - return 'mat4'; - } else if (value.isColor === true) { - return 'color'; - } else if (value instanceof ArrayBuffer) { - return 'ArrayBuffer'; - } - - return null; -} - -export function getValueFromType(type, ...params) { - const last4 = type ? type.slice(-4) : undefined; - - if (params.length === 1) { - // ensure same behaviour as in NodeBuilder.format() - - if (last4 === 'vec2') params = [params[0], params[0]]; - else if (last4 === 'vec3') params = [params[0], params[0], params[0]]; - else if (last4 === 'vec4') params = [params[0], params[0], params[0], params[0]]; - } - - if (type === 'color') { - return new Color(...params); - } else if (last4 === 'vec2') { - return new Vector2(...params); - } else if (last4 === 'vec3') { - return new Vector3(...params); - } else if (last4 === 'vec4') { - return new Vector4(...params); - } else if (last4 === 'mat3') { - return new Matrix3(...params); - } else if (last4 === 'mat4') { - return new Matrix4(...params); - } else if (type === 'bool') { - return params[0] || false; - } else if (type === 'float' || type === 'int' || type === 'uint') { - return params[0] || 0; - } else if (type === 'string') { - return params[0] || ''; - } else if (type === 'ArrayBuffer') { - return base64ToArrayBuffer(params[0]); - } - - return null; -} - -export function arrayBufferToBase64(arrayBuffer) { - let chars = ''; - - const array = new Uint8Array(arrayBuffer); - - for (let i = 0; i < array.length; i++) { - chars += String.fromCharCode(array[i]); - } - - return btoa(chars); -} - -export function base64ToArrayBuffer(base64) { - return Uint8Array.from(atob(base64), c => c.charCodeAt(0)).buffer; -} diff --git a/examples-jsm/examples/nodes/core/NodeVar.ts b/examples-jsm/examples/nodes/core/NodeVar.ts deleted file mode 100644 index e6e935b3..00000000 --- a/examples-jsm/examples/nodes/core/NodeVar.ts +++ /dev/null @@ -1,10 +0,0 @@ -class NodeVar { - constructor(name, type) { - this.isNodeVar = true; - - this.name = name; - this.type = type; - } -} - -export default NodeVar; diff --git a/examples-jsm/examples/nodes/core/NodeVarying.ts b/examples-jsm/examples/nodes/core/NodeVarying.ts deleted file mode 100644 index a1482362..00000000 --- a/examples-jsm/examples/nodes/core/NodeVarying.ts +++ /dev/null @@ -1,13 +0,0 @@ -import NodeVar from './NodeVar.js'; - -class NodeVarying extends NodeVar { - constructor(name, type) { - super(name, type); - - this.needsInterpolation = false; - - this.isNodeVarying = true; - } -} - -export default NodeVarying; diff --git a/examples-jsm/examples/nodes/core/StackNode.ts b/examples-jsm/examples/nodes/core/StackNode.ts deleted file mode 100644 index d9322607..00000000 --- a/examples-jsm/examples/nodes/core/StackNode.ts +++ /dev/null @@ -1,71 +0,0 @@ -import Node, { addNodeClass } from './Node.js'; -import { cond } from '../math/CondNode.js'; -import { ShaderNode, nodeProxy, getCurrentStack, setCurrentStack } from '../shadernode/ShaderNode.js'; - -class StackNode extends Node { - constructor(parent = null) { - super(); - - this.nodes = []; - this.outputNode = null; - - this.parent = parent; - - this._currentCond = null; - - this.isStackNode = true; - } - - getNodeType(builder) { - return this.outputNode ? this.outputNode.getNodeType(builder) : 'void'; - } - - add(node) { - this.nodes.push(node); - - return this; - } - - if(boolNode, method) { - const methodNode = new ShaderNode(method); - this._currentCond = cond(boolNode, methodNode); - - return this.add(this._currentCond); - } - - elseif(boolNode, method) { - const methodNode = new ShaderNode(method); - const ifNode = cond(boolNode, methodNode); - - this._currentCond.elseNode = ifNode; - this._currentCond = ifNode; - - return this; - } - - else(method) { - this._currentCond.elseNode = new ShaderNode(method); - - return this; - } - - build(builder, ...params) { - const previousStack = getCurrentStack(); - - setCurrentStack(this); - - for (const node of this.nodes) { - node.build(builder, 'void'); - } - - setCurrentStack(previousStack); - - return this.outputNode ? this.outputNode.build(builder, ...params) : super.build(builder, ...params); - } -} - -export default StackNode; - -export const stack = nodeProxy(StackNode); - -addNodeClass('StackNode', StackNode); diff --git a/examples-jsm/examples/nodes/core/StructTypeNode.ts b/examples-jsm/examples/nodes/core/StructTypeNode.ts deleted file mode 100644 index 69718799..00000000 --- a/examples-jsm/examples/nodes/core/StructTypeNode.ts +++ /dev/null @@ -1,18 +0,0 @@ -import Node, { addNodeClass } from './Node.js'; - -class StructTypeNode extends Node { - constructor(types) { - super(); - - this.types = types; - this.isStructTypeNode = true; - } - - getMemberTypes() { - return this.types; - } -} - -export default StructTypeNode; - -addNodeClass('StructTypeNode', StructTypeNode); diff --git a/examples-jsm/examples/nodes/core/UniformGroupNode.ts b/examples-jsm/examples/nodes/core/UniformGroupNode.ts deleted file mode 100644 index f8bb2b37..00000000 --- a/examples-jsm/examples/nodes/core/UniformGroupNode.ts +++ /dev/null @@ -1,30 +0,0 @@ -import Node from './Node.js'; -import { addNodeClass } from './Node.js'; - -class UniformGroupNode extends Node { - constructor(name, shared = false) { - super('string'); - - this.name = name; - this.version = 0; - - this.shared = shared; - - this.isUniformGroup = true; - } - - set needsUpdate(value) { - if (value === true) this.version++; - } -} - -export const uniformGroup = name => new UniformGroupNode(name); -export const sharedUniformGroup = name => new UniformGroupNode(name, true); - -export const frameGroup = sharedUniformGroup('frame'); -export const renderGroup = sharedUniformGroup('render'); -export const objectGroup = uniformGroup('object'); - -export default UniformGroupNode; - -addNodeClass('UniformGroupNode', UniformGroupNode); diff --git a/examples-jsm/examples/nodes/core/UniformNode.ts b/examples-jsm/examples/nodes/core/UniformNode.ts deleted file mode 100644 index 41e523c4..00000000 --- a/examples-jsm/examples/nodes/core/UniformNode.ts +++ /dev/null @@ -1,90 +0,0 @@ -import InputNode from './InputNode.js'; -import { objectGroup } from './UniformGroupNode.js'; -import { addNodeClass } from './Node.js'; -import { nodeObject, getConstNodeType } from '../shadernode/ShaderNode.js'; - -class UniformNode extends InputNode { - constructor(value, nodeType = null) { - super(value, nodeType); - - this.isUniformNode = true; - - this.name = ''; - this.groupNode = objectGroup; - } - - label(name) { - this.name = name; - - return this; - } - - setGroup(group) { - this.groupNode = group; - - return this; - } - - getGroup() { - return this.groupNode; - } - - getUniformHash(builder) { - return this.getHash(builder); - } - - onUpdate(callback, updateType) { - const self = this.getSelf(); - - callback = callback.bind(self); - - return super.onUpdate(frame => { - const value = callback(frame, self); - - if (value !== undefined) { - this.value = value; - } - }, updateType); - } - - generate(builder, output) { - const type = this.getNodeType(builder); - - const hash = this.getUniformHash(builder); - - let sharedNode = builder.getNodeFromHash(hash); - - if (sharedNode === undefined) { - builder.setHashNode(this, hash); - - sharedNode = this; - } - - const sharedNodeType = sharedNode.getInputType(builder); - - const nodeUniform = builder.getUniformFromNode( - sharedNode, - sharedNodeType, - builder.shaderStage, - this.name || builder.context.label, - ); - const propertyName = builder.getPropertyName(nodeUniform); - - if (builder.context.label !== undefined) delete builder.context.label; - - return builder.format(propertyName, type, output); - } -} - -export default UniformNode; - -export const uniform = (arg1, arg2) => { - const nodeType = getConstNodeType(arg2 || arg1); - - // @TODO: get ConstNode from .traverse() in the future - const value = arg1 && arg1.isNode === true ? (arg1.node && arg1.node.value) || arg1.value : arg1; - - return nodeObject(new UniformNode(value, nodeType)); -}; - -addNodeClass('UniformNode', UniformNode); diff --git a/examples-jsm/examples/nodes/core/constants.ts b/examples-jsm/examples/nodes/core/constants.ts deleted file mode 100644 index 3b01a9a6..00000000 --- a/examples-jsm/examples/nodes/core/constants.ts +++ /dev/null @@ -1,28 +0,0 @@ -export const NodeShaderStage = { - VERTEX: 'vertex', - FRAGMENT: 'fragment', -}; - -export const NodeUpdateType = { - NONE: 'none', - FRAME: 'frame', - RENDER: 'render', - OBJECT: 'object', -}; - -export const NodeType = { - BOOLEAN: 'bool', - INTEGER: 'int', - FLOAT: 'float', - VECTOR2: 'vec2', - VECTOR3: 'vec3', - VECTOR4: 'vec4', - MATRIX2: 'mat2', - MATRIX3: 'mat3', - MATRIX4: 'mat4', -}; - -export const defaultShaderStages = ['fragment', 'vertex']; -export const defaultBuildStages = ['setup', 'analyze', 'generate']; -export const shaderStages = [...defaultShaderStages, 'compute']; -export const vectorComponents = ['x', 'y', 'z', 'w']; diff --git a/examples-jsm/examples/nodes/fog/FogNode.ts b/examples-jsm/examples/nodes/fog/FogNode.ts deleted file mode 100644 index 9417df5a..00000000 --- a/examples-jsm/examples/nodes/fog/FogNode.ts +++ /dev/null @@ -1,38 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; -import { positionView } from '../accessors/PositionNode.js'; -import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js'; - -class FogNode extends Node { - constructor(colorNode, factorNode) { - super('float'); - - this.isFogNode = true; - - this.colorNode = colorNode; - this.factorNode = factorNode; - } - - getViewZNode(builder) { - let viewZ; - - const getViewZ = builder.context.getViewZ; - - if (getViewZ !== undefined) { - viewZ = getViewZ(this); - } - - return (viewZ || positionView.z).negate(); - } - - setup() { - return this.factorNode; - } -} - -export default FogNode; - -export const fog = nodeProxy(FogNode); - -addNodeElement('fog', fog); - -addNodeClass('FogNode', FogNode); diff --git a/examples-jsm/examples/nodes/gpgpu/ComputeNode.ts b/examples-jsm/examples/nodes/gpgpu/ComputeNode.ts deleted file mode 100644 index ab2925a5..00000000 --- a/examples-jsm/examples/nodes/gpgpu/ComputeNode.ts +++ /dev/null @@ -1,67 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; -import { NodeUpdateType } from '../core/constants.js'; -import { addNodeElement, nodeObject } from '../shadernode/ShaderNode.js'; - -class ComputeNode extends Node { - constructor(computeNode, count, workgroupSize = [64]) { - super('void'); - - this.isComputeNode = true; - - this.computeNode = computeNode; - - this.count = count; - this.workgroupSize = workgroupSize; - this.dispatchCount = 0; - - this.version = 1; - this.updateBeforeType = NodeUpdateType.OBJECT; - - this.updateDispatchCount(); - } - - dispose() { - this.dispatchEvent({ type: 'dispose' }); - } - - set needsUpdate(value) { - if (value === true) this.version++; - } - - updateDispatchCount() { - const { count, workgroupSize } = this; - - let size = workgroupSize[0]; - - for (let i = 1; i < workgroupSize.length; i++) size *= workgroupSize[i]; - - this.dispatchCount = Math.ceil(count / size); - } - - onInit() {} - - updateBefore({ renderer }) { - renderer.compute(this); - } - - generate(builder) { - const { shaderStage } = builder; - - if (shaderStage === 'compute') { - const snippet = this.computeNode.build(builder, 'void'); - - if (snippet !== '') { - builder.addLineFlowCode(snippet); - } - } - } -} - -export default ComputeNode; - -export const compute = (node, count, workgroupSize) => - nodeObject(new ComputeNode(nodeObject(node), count, workgroupSize)); - -addNodeElement('compute', compute); - -addNodeClass('ComputeNode', ComputeNode); diff --git a/examples-jsm/examples/nodes/lighting/EnvironmentNode.ts b/examples-jsm/examples/nodes/lighting/EnvironmentNode.ts deleted file mode 100644 index 31ddc8b0..00000000 --- a/examples-jsm/examples/nodes/lighting/EnvironmentNode.ts +++ /dev/null @@ -1,119 +0,0 @@ -import LightingNode from './LightingNode.js'; -import { cache } from '../core/CacheNode.js'; -import { context } from '../core/ContextNode.js'; -import { roughness, clearcoatRoughness } from '../core/PropertyNode.js'; -import { cameraViewMatrix } from '../accessors/CameraNode.js'; -import { - transformedClearcoatNormalView, - transformedNormalView, - transformedNormalWorld, -} from '../accessors/NormalNode.js'; -import { positionViewDirection } from '../accessors/PositionNode.js'; -import { addNodeClass } from '../core/Node.js'; -import { float } from '../shadernode/ShaderNode.js'; -import { reference } from '../accessors/ReferenceNode.js'; -import { transformedBentNormalView } from '../accessors/AccessorsUtils.js'; -import { pmremTexture } from '../pmrem/PMREMNode.js'; - -const envNodeCache = new WeakMap(); - -class EnvironmentNode extends LightingNode { - constructor(envNode = null) { - super(); - - this.envNode = envNode; - } - - setup(builder) { - let envNode = this.envNode; - - if (envNode.isTextureNode) { - let cacheEnvNode = envNodeCache.get(envNode.value); - - if (cacheEnvNode === undefined) { - cacheEnvNode = pmremTexture(envNode.value); - - envNodeCache.set(envNode.value, cacheEnvNode); - } - - envNode = cacheEnvNode; - } - - // - - const { material } = builder; - - const envMap = material.envMap; - const intensity = envMap - ? reference('envMapIntensity', 'float', builder.material) - : reference('environmentIntensity', 'float', builder.scene); // @TODO: Add materialEnvIntensity in MaterialNode - - const useAnisotropy = material.useAnisotropy === true || material.anisotropy > 0; - const radianceNormalView = useAnisotropy ? transformedBentNormalView : transformedNormalView; - - const radiance = context(envNode, createRadianceContext(roughness, radianceNormalView)).mul(intensity); - const irradiance = context(envNode, createIrradianceContext(transformedNormalWorld)) - .mul(Math.PI) - .mul(intensity); - - const isolateRadiance = cache(radiance); - const isolateIrradiance = cache(irradiance); - - // - - builder.context.radiance.addAssign(isolateRadiance); - - builder.context.iblIrradiance.addAssign(isolateIrradiance); - - // - - const clearcoatRadiance = builder.context.lightingModel.clearcoatRadiance; - - if (clearcoatRadiance) { - const clearcoatRadianceContext = context( - envNode, - createRadianceContext(clearcoatRoughness, transformedClearcoatNormalView), - ).mul(intensity); - const isolateClearcoatRadiance = cache(clearcoatRadianceContext); - - clearcoatRadiance.addAssign(isolateClearcoatRadiance); - } - } -} - -const createRadianceContext = (roughnessNode, normalViewNode) => { - let reflectVec = null; - - return { - getUV: () => { - if (reflectVec === null) { - reflectVec = positionViewDirection.negate().reflect(normalViewNode); - - // Mixing the reflection with the normal is more accurate and keeps rough objects from gathering light from behind their tangent plane. - reflectVec = roughnessNode.mul(roughnessNode).mix(reflectVec, normalViewNode).normalize(); - - reflectVec = reflectVec.transformDirection(cameraViewMatrix); - } - - return reflectVec; - }, - getTextureLevel: () => { - return roughnessNode; - }, - }; -}; - -const createIrradianceContext = normalWorldNode => { - return { - getUV: () => { - return normalWorldNode; - }, - getTextureLevel: () => { - return float(1.0); - }, - }; -}; - -export default EnvironmentNode; - -addNodeClass('EnvironmentNode', EnvironmentNode); diff --git a/examples-jsm/examples/nodes/lighting/LightingContextNode.ts b/examples-jsm/examples/nodes/lighting/LightingContextNode.ts deleted file mode 100644 index 02a8b51f..00000000 --- a/examples-jsm/examples/nodes/lighting/LightingContextNode.ts +++ /dev/null @@ -1,58 +0,0 @@ -import ContextNode from '../core/ContextNode.js'; -import { addNodeClass } from '../core/Node.js'; -import { addNodeElement, nodeProxy, float, vec3 } from '../shadernode/ShaderNode.js'; - -class LightingContextNode extends ContextNode { - constructor(node, lightingModel = null, backdropNode = null, backdropAlphaNode = null) { - super(node); - - this.lightingModel = lightingModel; - this.backdropNode = backdropNode; - this.backdropAlphaNode = backdropAlphaNode; - - this._context = null; - } - - getContext() { - const { backdropNode, backdropAlphaNode } = this; - - const directDiffuse = vec3().temp('directDiffuse'), - directSpecular = vec3().temp('directSpecular'), - indirectDiffuse = vec3().temp('indirectDiffuse'), - indirectSpecular = vec3().temp('indirectSpecular'); - - const reflectedLight = { - directDiffuse, - directSpecular, - indirectDiffuse, - indirectSpecular, - }; - - const context = { - radiance: vec3().temp('radiance'), - irradiance: vec3().temp('irradiance'), - iblIrradiance: vec3().temp('iblIrradiance'), - ambientOcclusion: float(1).temp('ambientOcclusion'), - reflectedLight, - backdrop: backdropNode, - backdropAlpha: backdropAlphaNode, - }; - - return context; - } - - setup(builder) { - this.context = this._context || (this._context = this.getContext()); - this.context.lightingModel = this.lightingModel || builder.context.lightingModel; - - return super.setup(builder); - } -} - -export default LightingContextNode; - -export const lightingContext = nodeProxy(LightingContextNode); - -addNodeElement('lightingContext', lightingContext); - -addNodeClass('LightingContextNode', LightingContextNode); diff --git a/examples-jsm/examples/nodes/lighting/LightsNode.ts b/examples-jsm/examples/nodes/lighting/LightsNode.ts deleted file mode 100644 index 96e5c60a..00000000 --- a/examples-jsm/examples/nodes/lighting/LightsNode.ts +++ /dev/null @@ -1,170 +0,0 @@ -import Node from '../core/Node.js'; -import AnalyticLightNode from './AnalyticLightNode.js'; -import { nodeObject, nodeProxy, vec3 } from '../shadernode/ShaderNode.js'; - -const LightNodes = new WeakMap(); - -const sortLights = lights => { - return lights.sort((a, b) => a.id - b.id); -}; - -class LightsNode extends Node { - constructor(lightNodes = []) { - super('vec3'); - - this.totalDiffuseNode = vec3().temp('totalDiffuse'); - this.totalSpecularNode = vec3().temp('totalSpecular'); - - this.outgoingLightNode = vec3().temp('outgoingLight'); - - this.lightNodes = lightNodes; - - this._hash = null; - } - - get hasLight() { - return this.lightNodes.length > 0; - } - - getHash() { - if (this._hash === null) { - const hash = []; - - for (const lightNode of this.lightNodes) { - hash.push(lightNode.getHash()); - } - - this._hash = 'lights-' + hash.join(','); - } - - return this._hash; - } - - analyze(builder) { - const properties = builder.getDataFromNode(this); - - for (const node of properties.nodes) { - node.build(builder); - } - } - - setup(builder) { - const context = builder.context; - const lightingModel = context.lightingModel; - - let outgoingLightNode = this.outgoingLightNode; - - if (lightingModel) { - const { lightNodes, totalDiffuseNode, totalSpecularNode } = this; - - context.outgoingLight = outgoingLightNode; - - const stack = builder.addStack(); - - // - - const properties = builder.getDataFromNode(this); - properties.nodes = stack.nodes; - - // - - lightingModel.start(context, stack, builder); - - // lights - - for (const lightNode of lightNodes) { - lightNode.build(builder); - } - - // - - lightingModel.indirectDiffuse(context, stack, builder); - lightingModel.indirectSpecular(context, stack, builder); - lightingModel.ambientOcclusion(context, stack, builder); - - // - - const { backdrop, backdropAlpha } = context; - const { directDiffuse, directSpecular, indirectDiffuse, indirectSpecular } = context.reflectedLight; - - let totalDiffuse = directDiffuse.add(indirectDiffuse); - - if (backdrop !== null) { - if (backdropAlpha !== null) { - totalDiffuse = vec3(backdropAlpha.mix(totalDiffuse, backdrop)); - } else { - totalDiffuse = vec3(backdrop); - } - - context.material.transparent = true; - } - - totalDiffuseNode.assign(totalDiffuse); - totalSpecularNode.assign(directSpecular.add(indirectSpecular)); - - outgoingLightNode.assign(totalDiffuseNode.add(totalSpecularNode)); - - // - - lightingModel.finish(context, stack, builder); - - // - - outgoingLightNode = outgoingLightNode.bypass(builder.removeStack()); - } - - return outgoingLightNode; - } - - _getLightNodeById(id) { - for (const lightNode of this.lightNodes) { - if (lightNode.isAnalyticLightNode && lightNode.light.id === id) { - return lightNode; - } - } - - return null; - } - - fromLights(lights = []) { - const lightNodes = []; - - lights = sortLights(lights); - - for (const light of lights) { - let lightNode = this._getLightNodeById(light.id); - - if (lightNode === null) { - const lightClass = light.constructor; - const lightNodeClass = LightNodes.has(lightClass) ? LightNodes.get(lightClass) : AnalyticLightNode; - - lightNode = nodeObject(new lightNodeClass(light)); - } - - lightNodes.push(lightNode); - } - - this.lightNodes = lightNodes; - this._hash = null; - - return this; - } -} - -export default LightsNode; - -export const lights = lights => nodeObject(new LightsNode().fromLights(lights)); -export const lightsNode = nodeProxy(LightsNode); - -export function addLightNode(lightClass, lightNodeClass) { - if (LightNodes.has(lightClass)) { - console.warn(`Redefinition of light node ${lightNodeClass.type}`); - return; - } - - if (typeof lightClass !== 'function') throw new Error(`Light ${lightClass.name} is not a class`); - if (typeof lightNodeClass !== 'function' || !lightNodeClass.type) - throw new Error(`Light node ${lightNodeClass.type} is not a class`); - - LightNodes.set(lightClass, lightNodeClass); -} diff --git a/examples-jsm/examples/nodes/materials/NodeMaterial.ts b/examples-jsm/examples/nodes/materials/NodeMaterial.ts deleted file mode 100644 index d0c7c6cd..00000000 --- a/examples-jsm/examples/nodes/materials/NodeMaterial.ts +++ /dev/null @@ -1,514 +0,0 @@ -import { Material, NormalBlending } from 'three'; -import { getNodeChildren, getCacheKey } from '../core/NodeUtils.js'; -import { attribute } from '../core/AttributeNode.js'; -import { output, diffuseColor, varyingProperty } from '../core/PropertyNode.js'; -import { - materialAlphaTest, - materialColor, - materialOpacity, - materialEmissive, - materialNormal, -} from '../accessors/MaterialNode.js'; -import { modelViewProjection } from '../accessors/ModelViewProjectionNode.js'; -import { transformedNormalView, normalLocal } from '../accessors/NormalNode.js'; -import { instance } from '../accessors/InstanceNode.js'; -import { batch } from '../accessors/BatchNode.js'; -import { materialReference } from '../accessors/MaterialReferenceNode.js'; -import { positionLocal, positionView } from '../accessors/PositionNode.js'; -import { skinningReference } from '../accessors/SkinningNode.js'; -import { morphReference } from '../accessors/MorphNode.js'; -import { texture } from '../accessors/TextureNode.js'; -import { cubeTexture } from '../accessors/CubeTextureNode.js'; -import { lightsNode } from '../lighting/LightsNode.js'; -import { mix } from '../math/MathNode.js'; -import { float, vec3, vec4 } from '../shadernode/ShaderNode.js'; -import AONode from '../lighting/AONode.js'; -import { lightingContext } from '../lighting/LightingContextNode.js'; -import EnvironmentNode from '../lighting/EnvironmentNode.js'; -import IrradianceNode from '../lighting/IrradianceNode.js'; -import { depth } from '../display/ViewportDepthNode.js'; -import { cameraLogDepth } from '../accessors/CameraNode.js'; -import { clipping, clippingAlpha } from '../accessors/ClippingNode.js'; -import { faceDirection } from '../display/FrontFacingNode.js'; - -const NodeMaterials = new Map(); - -class NodeMaterial extends Material { - constructor() { - super(); - - this.isNodeMaterial = true; - - this.type = this.constructor.type; - - this.forceSinglePass = false; - - this.fog = true; - this.lights = true; - this.normals = true; - - this.lightsNode = null; - this.envNode = null; - this.aoNode = null; - - this.colorNode = null; - this.normalNode = null; - this.opacityNode = null; - this.backdropNode = null; - this.backdropAlphaNode = null; - this.alphaTestNode = null; - - this.positionNode = null; - - this.depthNode = null; - this.shadowNode = null; - this.shadowPositionNode = null; - - this.outputNode = null; - - this.fragmentNode = null; - this.vertexNode = null; - } - - customProgramCacheKey() { - return this.type + getCacheKey(this); - } - - build(builder) { - this.setup(builder); - } - - setup(builder) { - // < VERTEX STAGE > - - builder.addStack(); - - builder.stack.outputNode = this.vertexNode || this.setupPosition(builder); - - builder.addFlow('vertex', builder.removeStack()); - - // < FRAGMENT STAGE > - - builder.addStack(); - - let resultNode; - - const clippingNode = this.setupClipping(builder); - - if (this.depthWrite === true) this.setupDepth(builder); - - if (this.fragmentNode === null) { - if (this.normals === true) this.setupNormal(builder); - - this.setupDiffuseColor(builder); - this.setupVariants(builder); - - const outgoingLightNode = this.setupLighting(builder); - - if (clippingNode !== null) builder.stack.add(clippingNode); - - // force unsigned floats - useful for RenderTargets - - const basicOutput = vec4(outgoingLightNode, diffuseColor.a).max(0); - - resultNode = this.setupOutput(builder, basicOutput); - - // OUTPUT NODE - - output.assign(resultNode); - - // - - if (this.outputNode !== null) resultNode = this.outputNode; - } else { - let fragmentNode = this.fragmentNode; - - if (fragmentNode.isOutputStructNode !== true) { - fragmentNode = vec4(fragmentNode); - } - - resultNode = this.setupOutput(builder, fragmentNode); - } - - builder.stack.outputNode = resultNode; - - builder.addFlow('fragment', builder.removeStack()); - } - - setupClipping(builder) { - if (builder.clippingContext === null) return null; - - const { globalClippingCount, localClippingCount } = builder.clippingContext; - - let result = null; - - if (globalClippingCount || localClippingCount) { - if (this.alphaToCoverage) { - // to be added to flow when the color/alpha value has been determined - result = clippingAlpha(); - } else { - builder.stack.add(clipping()); - } - } - - return result; - } - - setupDepth(builder) { - const { renderer } = builder; - - // Depth - - let depthNode = this.depthNode; - - if (depthNode === null && renderer.logarithmicDepthBuffer === true) { - const fragDepth = modelViewProjection().w.add(1); - - depthNode = fragDepth.log2().mul(cameraLogDepth).mul(0.5); - } - - if (depthNode !== null) { - depth.assign(depthNode).append(); - } - } - - setupPosition(builder) { - const { object } = builder; - const geometry = object.geometry; - - builder.addStack(); - - // Vertex - - if (geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color) { - morphReference(object).append(); - } - - if (object.isSkinnedMesh === true) { - skinningReference(object).append(); - } - - if (this.displacementMap) { - const displacementMap = materialReference('displacementMap', 'texture'); - const displacementScale = materialReference('displacementScale', 'float'); - const displacementBias = materialReference('displacementBias', 'float'); - - positionLocal.addAssign( - normalLocal.normalize().mul(displacementMap.x.mul(displacementScale).add(displacementBias)), - ); - } - - if (object.isBatchedMesh) { - batch(object).append(); - } - - if (object.instanceMatrix && object.instanceMatrix.isInstancedBufferAttribute === true) { - instance(object).append(); - } - - if (this.positionNode !== null) { - positionLocal.assign(this.positionNode); - } - - const mvp = modelViewProjection(); - - builder.context.vertex = builder.removeStack(); - builder.context.mvp = mvp; - - return mvp; - } - - setupDiffuseColor({ object, geometry }) { - let colorNode = this.colorNode ? vec4(this.colorNode) : materialColor; - - // VERTEX COLORS - - if (this.vertexColors === true && geometry.hasAttribute('color')) { - colorNode = vec4(colorNode.xyz.mul(attribute('color', 'vec3')), colorNode.a); - } - - // Instanced colors - - if (object.instanceColor) { - const instanceColor = varyingProperty('vec3', 'vInstanceColor'); - - colorNode = instanceColor.mul(colorNode); - } - - // COLOR - - diffuseColor.assign(colorNode); - - // OPACITY - - const opacityNode = this.opacityNode ? float(this.opacityNode) : materialOpacity; - diffuseColor.a.assign(diffuseColor.a.mul(opacityNode)); - - // ALPHA TEST - - if (this.alphaTestNode !== null || this.alphaTest > 0) { - const alphaTestNode = this.alphaTestNode !== null ? float(this.alphaTestNode) : materialAlphaTest; - - diffuseColor.a.lessThanEqual(alphaTestNode).discard(); - } - - if (this.transparent === false && this.blending === NormalBlending && this.alphaToCoverage === false) { - diffuseColor.a.assign(1.0); - } - } - - setupVariants(/*builder*/) { - // Interface function. - } - - setupNormal() { - // NORMAL VIEW - - if (this.flatShading === true) { - const normalNode = positionView.dFdx().cross(positionView.dFdy()).normalize(); - - transformedNormalView.assign(normalNode.mul(faceDirection)); - } else { - const normalNode = this.normalNode ? vec3(this.normalNode) : materialNormal; - - transformedNormalView.assign(normalNode.mul(faceDirection)); - } - } - - getEnvNode(builder) { - let node = null; - - if (this.envNode) { - node = this.envNode; - } else if (this.envMap) { - node = this.envMap.isCubeTexture ? cubeTexture(this.envMap) : texture(this.envMap); - } else if (builder.environmentNode) { - node = builder.environmentNode; - } - - return node; - } - - setupLights(builder) { - const envNode = this.getEnvNode(builder); - - // - - const materialLightsNode = []; - - if (envNode) { - materialLightsNode.push(new EnvironmentNode(envNode)); - } - - if (builder.material.lightMap) { - materialLightsNode.push(new IrradianceNode(materialReference('lightMap', 'texture'))); - } - - if (this.aoNode !== null || builder.material.aoMap) { - const aoNode = this.aoNode !== null ? this.aoNode : texture(builder.material.aoMap); - - materialLightsNode.push(new AONode(aoNode)); - } - - let lightsN = this.lightsNode || builder.lightsNode; - - if (materialLightsNode.length > 0) { - lightsN = lightsNode([...lightsN.lightNodes, ...materialLightsNode]); - } - - return lightsN; - } - - setupLightingModel(/*builder*/) { - // Interface function. - } - - setupLighting(builder) { - const { material } = builder; - const { backdropNode, backdropAlphaNode, emissiveNode } = this; - - // OUTGOING LIGHT - - const lights = this.lights === true || this.lightsNode !== null; - - const lightsNode = lights ? this.setupLights(builder) : null; - - let outgoingLightNode = diffuseColor.rgb; - - if (lightsNode && lightsNode.hasLight !== false) { - const lightingModel = this.setupLightingModel(builder); - - outgoingLightNode = lightingContext(lightsNode, lightingModel, backdropNode, backdropAlphaNode); - } else if (backdropNode !== null) { - outgoingLightNode = vec3( - backdropAlphaNode !== null ? mix(outgoingLightNode, backdropNode, backdropAlphaNode) : backdropNode, - ); - } - - // EMISSIVE - - if ( - (emissiveNode && emissiveNode.isNode === true) || - (material.emissive && material.emissive.isColor === true) - ) { - outgoingLightNode = outgoingLightNode.add(vec3(emissiveNode ? emissiveNode : materialEmissive)); - } - - return outgoingLightNode; - } - - setupOutput(builder, outputNode) { - // FOG - - if (this.fog === true) { - const fogNode = builder.fogNode; - - if (fogNode) outputNode = vec4(fogNode.mix(outputNode.rgb, fogNode.colorNode), outputNode.a); - } - - return outputNode; - } - - setDefaultValues(material) { - // This approach is to reuse the native refreshUniforms* - // and turn available the use of features like transmission and environment in core - - for (const property in material) { - const value = material[property]; - - if (this[property] === undefined) { - this[property] = value; - - if (value && value.clone) this[property] = value.clone(); - } - } - - const descriptors = Object.getOwnPropertyDescriptors(material.constructor.prototype); - - for (const key in descriptors) { - if ( - Object.getOwnPropertyDescriptor(this.constructor.prototype, key) === undefined && - descriptors[key].get !== undefined - ) { - Object.defineProperty(this.constructor.prototype, key, descriptors[key]); - } - } - } - - toJSON(meta) { - const isRoot = meta === undefined || typeof meta === 'string'; - - if (isRoot) { - meta = { - textures: {}, - images: {}, - nodes: {}, - }; - } - - const data = Material.prototype.toJSON.call(this, meta); - const nodeChildren = getNodeChildren(this); - - data.inputNodes = {}; - - for (const { property, childNode } of nodeChildren) { - data.inputNodes[property] = childNode.toJSON(meta).uuid; - } - - // TODO: Copied from Object3D.toJSON - - function extractFromCache(cache) { - const values = []; - - for (const key in cache) { - const data = cache[key]; - delete data.metadata; - values.push(data); - } - - return values; - } - - if (isRoot) { - const textures = extractFromCache(meta.textures); - const images = extractFromCache(meta.images); - const nodes = extractFromCache(meta.nodes); - - if (textures.length > 0) data.textures = textures; - if (images.length > 0) data.images = images; - if (nodes.length > 0) data.nodes = nodes; - } - - return data; - } - - copy(source) { - this.lightsNode = source.lightsNode; - this.envNode = source.envNode; - - this.colorNode = source.colorNode; - this.normalNode = source.normalNode; - this.opacityNode = source.opacityNode; - this.backdropNode = source.backdropNode; - this.backdropAlphaNode = source.backdropAlphaNode; - this.alphaTestNode = source.alphaTestNode; - - this.positionNode = source.positionNode; - - this.depthNode = source.depthNode; - this.shadowNode = source.shadowNode; - this.shadowPositionNode = source.shadowPositionNode; - - this.outputNode = source.outputNode; - - this.fragmentNode = source.fragmentNode; - this.vertexNode = source.vertexNode; - - return super.copy(source); - } - - static fromMaterial(material) { - if (material.isNodeMaterial === true) { - // is already a node material - - return material; - } - - const type = material.type.replace('Material', 'NodeMaterial'); - - const nodeMaterial = createNodeMaterialFromType(type); - - if (nodeMaterial === undefined) { - throw new Error(`NodeMaterial: Material "${material.type}" is not compatible.`); - } - - for (const key in material) { - nodeMaterial[key] = material[key]; - } - - return nodeMaterial; - } -} - -export default NodeMaterial; - -export function addNodeMaterial(type, nodeMaterial) { - if (typeof nodeMaterial !== 'function' || !type) throw new Error(`Node material ${type} is not a class`); - if (NodeMaterials.has(type)) { - console.warn(`Redefinition of node material ${type}`); - return; - } - - NodeMaterials.set(type, nodeMaterial); - nodeMaterial.type = type; -} - -export function createNodeMaterialFromType(type) { - const Material = NodeMaterials.get(type); - - if (Material !== undefined) { - return new Material(); - } -} - -addNodeMaterial('NodeMaterial', NodeMaterial); diff --git a/examples-jsm/examples/nodes/shadernode/ShaderNode.ts b/examples-jsm/examples/nodes/shadernode/ShaderNode.ts deleted file mode 100644 index 288ffa3d..00000000 --- a/examples-jsm/examples/nodes/shadernode/ShaderNode.ts +++ /dev/null @@ -1,532 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; -import ArrayElementNode from '../utils/ArrayElementNode.js'; -import ConvertNode from '../utils/ConvertNode.js'; -import JoinNode from '../utils/JoinNode.js'; -import SplitNode from '../utils/SplitNode.js'; -import SetNode from '../utils/SetNode.js'; -import ConstNode from '../core/ConstNode.js'; -import { getValueFromType, getValueType } from '../core/NodeUtils.js'; - -// - -let currentStack = null; - -const NodeElements = new Map(); // @TODO: Currently only a few nodes are added, probably also add others - -export function addNodeElement(name, nodeElement) { - if (NodeElements.has(name)) { - console.warn(`Redefinition of node element ${name}`); - return; - } - - if (typeof nodeElement !== 'function') throw new Error(`Node element ${name} is not a function`); - - NodeElements.set(name, nodeElement); -} - -const parseSwizzle = props => props.replace(/r|s/g, 'x').replace(/g|t/g, 'y').replace(/b|p/g, 'z').replace(/a|q/g, 'w'); - -const shaderNodeHandler = { - setup(NodeClosure, params) { - const inputs = params.shift(); - - return NodeClosure(nodeObjects(inputs), ...params); - }, - - get(node, prop, nodeObj) { - if (typeof prop === 'string' && node[prop] === undefined) { - if (node.isStackNode !== true && prop === 'assign') { - return (...params) => { - currentStack.assign(nodeObj, ...params); - - return nodeObj; - }; - } else if (NodeElements.has(prop)) { - const nodeElement = NodeElements.get(prop); - - return node.isStackNode - ? (...params) => nodeObj.add(nodeElement(...params)) - : (...params) => nodeElement(nodeObj, ...params); - } else if (prop === 'self') { - return node; - } else if (prop.endsWith('Assign') && NodeElements.has(prop.slice(0, prop.length - 'Assign'.length))) { - const nodeElement = NodeElements.get(prop.slice(0, prop.length - 'Assign'.length)); - - return node.isStackNode - ? (...params) => nodeObj.assign(params[0], nodeElement(...params)) - : (...params) => nodeObj.assign(nodeElement(nodeObj, ...params)); - } else if (/^[xyzwrgbastpq]{1,4}$/.test(prop) === true) { - // accessing properties ( swizzle ) - - prop = parseSwizzle(prop); - - return nodeObject(new SplitNode(nodeObj, prop)); - } else if (/^set[XYZWRGBASTPQ]{1,4}$/.test(prop) === true) { - // set properties ( swizzle ) - - prop = parseSwizzle(prop.slice(3).toLowerCase()); - - // sort to xyzw sequence - - prop = prop.split('').sort().join(''); - - return value => nodeObject(new SetNode(node, prop, value)); - } else if (prop === 'width' || prop === 'height' || prop === 'depth') { - // accessing property - - if (prop === 'width') prop = 'x'; - else if (prop === 'height') prop = 'y'; - else if (prop === 'depth') prop = 'z'; - - return nodeObject(new SplitNode(node, prop)); - } else if (/^\d+$/.test(prop) === true) { - // accessing array - - return nodeObject(new ArrayElementNode(nodeObj, new ConstNode(Number(prop), 'uint'))); - } - } - - return Reflect.get(node, prop, nodeObj); - }, - - set(node, prop, value, nodeObj) { - if (typeof prop === 'string' && node[prop] === undefined) { - // setting properties - - if ( - /^[xyzwrgbastpq]{1,4}$/.test(prop) === true || - prop === 'width' || - prop === 'height' || - prop === 'depth' || - /^\d+$/.test(prop) === true - ) { - nodeObj[prop].assign(value); - - return true; - } - } - - return Reflect.set(node, prop, value, nodeObj); - }, -}; - -const nodeObjectsCacheMap = new WeakMap(); -const nodeBuilderFunctionsCacheMap = new WeakMap(); - -const ShaderNodeObject = function (obj, altType = null) { - const type = getValueType(obj); - - if (type === 'node') { - let nodeObject = nodeObjectsCacheMap.get(obj); - - if (nodeObject === undefined) { - nodeObject = new Proxy(obj, shaderNodeHandler); - - nodeObjectsCacheMap.set(obj, nodeObject); - nodeObjectsCacheMap.set(nodeObject, nodeObject); - } - - return nodeObject; - } else if ( - (altType === null && (type === 'float' || type === 'boolean')) || - (type && type !== 'shader' && type !== 'string') - ) { - return nodeObject(getConstNode(obj, altType)); - } else if (type === 'shader') { - return tslFn(obj); - } - - return obj; -}; - -const ShaderNodeObjects = function (objects, altType = null) { - for (const name in objects) { - objects[name] = nodeObject(objects[name], altType); - } - - return objects; -}; - -const ShaderNodeArray = function (array, altType = null) { - const len = array.length; - - for (let i = 0; i < len; i++) { - array[i] = nodeObject(array[i], altType); - } - - return array; -}; - -const ShaderNodeProxy = function (NodeClass, scope = null, factor = null, settings = null) { - const assignNode = node => nodeObject(settings !== null ? Object.assign(node, settings) : node); - - if (scope === null) { - return (...params) => { - return assignNode(new NodeClass(...nodeArray(params))); - }; - } else if (factor !== null) { - factor = nodeObject(factor); - - return (...params) => { - return assignNode(new NodeClass(scope, ...nodeArray(params), factor)); - }; - } else { - return (...params) => { - return assignNode(new NodeClass(scope, ...nodeArray(params))); - }; - } -}; - -const ShaderNodeImmutable = function (NodeClass, ...params) { - return nodeObject(new NodeClass(...nodeArray(params))); -}; - -class ShaderCallNodeInternal extends Node { - constructor(shaderNode, inputNodes) { - super(); - - this.shaderNode = shaderNode; - this.inputNodes = inputNodes; - } - - getNodeType(builder) { - const properties = builder.getNodeProperties(this); - - if (properties.outputNode === null) { - properties.outputNode = this.setupOutput(builder); - } - - return properties.outputNode.getNodeType(builder); - } - - call(builder) { - const { shaderNode, inputNodes } = this; - - if (shaderNode.layout) { - let functionNodesCacheMap = nodeBuilderFunctionsCacheMap.get(builder.constructor); - - if (functionNodesCacheMap === undefined) { - functionNodesCacheMap = new WeakMap(); - - nodeBuilderFunctionsCacheMap.set(builder.constructor, functionNodesCacheMap); - } - - let functionNode = functionNodesCacheMap.get(shaderNode); - - if (functionNode === undefined) { - functionNode = nodeObject(builder.buildFunctionNode(shaderNode)); - - functionNodesCacheMap.set(shaderNode, functionNode); - } - - if (builder.currentFunctionNode !== null) { - builder.currentFunctionNode.includes.push(functionNode); - } - - return nodeObject(functionNode.call(inputNodes)); - } - - const jsFunc = shaderNode.jsFunc; - const outputNode = - inputNodes !== null ? jsFunc(inputNodes, builder.stack, builder) : jsFunc(builder.stack, builder); - - return nodeObject(outputNode); - } - - setup(builder) { - const { outputNode } = builder.getNodeProperties(this); - - return outputNode || this.setupOutput(builder); - } - - setupOutput(builder) { - builder.addStack(); - - builder.stack.outputNode = this.call(builder); - - return builder.removeStack(); - } - - generate(builder, output) { - const { outputNode } = builder.getNodeProperties(this); - - if (outputNode === null) { - // TSL: It's recommended to use `tslFn` in setup() pass. - - return this.call(builder).build(builder, output); - } - - return super.generate(builder, output); - } -} - -class ShaderNodeInternal extends Node { - constructor(jsFunc) { - super(); - - this.jsFunc = jsFunc; - this.layout = null; - - this.global = true; - } - - get isArrayInput() { - return /^\((\s+)?\[/.test(this.jsFunc.toString()); - } - - setLayout(layout) { - this.layout = layout; - - return this; - } - - call(inputs = null) { - nodeObjects(inputs); - - return nodeObject(new ShaderCallNodeInternal(this, inputs)); - } - - setup() { - return this.call(); - } -} - -const bools = [false, true]; -const uints = [0, 1, 2, 3]; -const ints = [-1, -2]; -const floats = [ - 0.5, - 1.5, - 1 / 3, - 1e-6, - 1e6, - Math.PI, - Math.PI * 2, - 1 / Math.PI, - 2 / Math.PI, - 1 / (Math.PI * 2), - Math.PI / 2, -]; - -const boolsCacheMap = new Map(); -for (const bool of bools) boolsCacheMap.set(bool, new ConstNode(bool)); - -const uintsCacheMap = new Map(); -for (const uint of uints) uintsCacheMap.set(uint, new ConstNode(uint, 'uint')); - -const intsCacheMap = new Map([...uintsCacheMap].map(el => new ConstNode(el.value, 'int'))); -for (const int of ints) intsCacheMap.set(int, new ConstNode(int, 'int')); - -const floatsCacheMap = new Map([...intsCacheMap].map(el => new ConstNode(el.value))); -for (const float of floats) floatsCacheMap.set(float, new ConstNode(float)); -for (const float of floats) floatsCacheMap.set(-float, new ConstNode(-float)); - -const cacheMaps = { bool: boolsCacheMap, uint: uintsCacheMap, ints: intsCacheMap, float: floatsCacheMap }; - -const constNodesCacheMap = new Map([...boolsCacheMap, ...floatsCacheMap]); - -const getConstNode = (value, type) => { - if (constNodesCacheMap.has(value)) { - return constNodesCacheMap.get(value); - } else if (value.isNode === true) { - return value; - } else { - return new ConstNode(value, type); - } -}; - -const safeGetNodeType = node => { - try { - return node.getNodeType(); - } catch (_) { - return undefined; - } -}; - -const ConvertType = function (type, cacheMap = null) { - return (...params) => { - if ( - params.length === 0 || - (!['bool', 'float', 'int', 'uint'].includes(type) && params.every(param => typeof param !== 'object')) - ) { - params = [getValueFromType(type, ...params)]; - } - - if (params.length === 1 && cacheMap !== null && cacheMap.has(params[0])) { - return nodeObject(cacheMap.get(params[0])); - } - - if (params.length === 1) { - const node = getConstNode(params[0], type); - if (safeGetNodeType(node) === type) return nodeObject(node); - return nodeObject(new ConvertNode(node, type)); - } - - const nodes = params.map(param => getConstNode(param)); - return nodeObject(new JoinNode(nodes, type)); - }; -}; - -// exports - -export const defined = value => value && value.value; - -// utils - -export const getConstNodeType = value => - value !== undefined && value !== null - ? value.nodeType || value.convertTo || (typeof value === 'string' ? value : null) - : null; - -// shader node base - -export function ShaderNode(jsFunc) { - return new Proxy(new ShaderNodeInternal(jsFunc), shaderNodeHandler); -} - -export const nodeObject = (val, altType = null) => /* new */ ShaderNodeObject(val, altType); -export const nodeObjects = (val, altType = null) => new ShaderNodeObjects(val, altType); -export const nodeArray = (val, altType = null) => new ShaderNodeArray(val, altType); -export const nodeProxy = (...params) => new ShaderNodeProxy(...params); -export const nodeImmutable = (...params) => new ShaderNodeImmutable(...params); - -export const tslFn = jsFunc => { - const shaderNode = new ShaderNode(jsFunc); - - const fn = (...params) => { - let inputs; - - nodeObjects(params); - - if (params[0] && params[0].isNode) { - inputs = [...params]; - } else { - inputs = params[0]; - } - - return shaderNode.call(inputs); - }; - - fn.shaderNode = shaderNode; - fn.setLayout = layout => { - shaderNode.setLayout(layout); - - return fn; - }; - - return fn; -}; - -addNodeClass('ShaderNode', ShaderNode); - -// - -addNodeElement('toGlobal', node => { - node.global = true; - - return node; -}); - -// - -export const setCurrentStack = stack => { - if (currentStack === stack) { - //throw new Error( 'Stack already defined.' ); - } - - currentStack = stack; -}; - -export const getCurrentStack = () => currentStack; - -export const If = (...params) => currentStack.if(...params); - -export function append(node) { - if (currentStack) currentStack.add(node); - - return node; -} - -addNodeElement('append', append); - -// types -// @TODO: Maybe export from ConstNode.js? - -export const color = new ConvertType('color'); - -export const float = new ConvertType('float', cacheMaps.float); -export const int = new ConvertType('int', cacheMaps.ints); -export const uint = new ConvertType('uint', cacheMaps.uint); -export const bool = new ConvertType('bool', cacheMaps.bool); - -export const vec2 = new ConvertType('vec2'); -export const ivec2 = new ConvertType('ivec2'); -export const uvec2 = new ConvertType('uvec2'); -export const bvec2 = new ConvertType('bvec2'); - -export const vec3 = new ConvertType('vec3'); -export const ivec3 = new ConvertType('ivec3'); -export const uvec3 = new ConvertType('uvec3'); -export const bvec3 = new ConvertType('bvec3'); - -export const vec4 = new ConvertType('vec4'); -export const ivec4 = new ConvertType('ivec4'); -export const uvec4 = new ConvertType('uvec4'); -export const bvec4 = new ConvertType('bvec4'); - -export const mat2 = new ConvertType('mat2'); -export const imat2 = new ConvertType('imat2'); -export const umat2 = new ConvertType('umat2'); -export const bmat2 = new ConvertType('bmat2'); - -export const mat3 = new ConvertType('mat3'); -export const imat3 = new ConvertType('imat3'); -export const umat3 = new ConvertType('umat3'); -export const bmat3 = new ConvertType('bmat3'); - -export const mat4 = new ConvertType('mat4'); -export const imat4 = new ConvertType('imat4'); -export const umat4 = new ConvertType('umat4'); -export const bmat4 = new ConvertType('bmat4'); - -export const string = (value = '') => nodeObject(new ConstNode(value, 'string')); -export const arrayBuffer = value => nodeObject(new ConstNode(value, 'ArrayBuffer')); - -addNodeElement('toColor', color); -addNodeElement('toFloat', float); -addNodeElement('toInt', int); -addNodeElement('toUint', uint); -addNodeElement('toBool', bool); -addNodeElement('toVec2', vec2); -addNodeElement('toIvec2', ivec2); -addNodeElement('toUvec2', uvec2); -addNodeElement('toBvec2', bvec2); -addNodeElement('toVec3', vec3); -addNodeElement('toIvec3', ivec3); -addNodeElement('toUvec3', uvec3); -addNodeElement('toBvec3', bvec3); -addNodeElement('toVec4', vec4); -addNodeElement('toIvec4', ivec4); -addNodeElement('toUvec4', uvec4); -addNodeElement('toBvec4', bvec4); -addNodeElement('toMat2', mat2); -addNodeElement('toImat2', imat2); -addNodeElement('toUmat2', umat2); -addNodeElement('toBmat2', bmat2); -addNodeElement('toMat3', mat3); -addNodeElement('toImat3', imat3); -addNodeElement('toUmat3', umat3); -addNodeElement('toBmat3', bmat3); -addNodeElement('toMat4', mat4); -addNodeElement('toImat4', imat4); -addNodeElement('toUmat4', umat4); -addNodeElement('toBmat4', bmat4); - -// basic nodes -// HACK - we cannot export them from the corresponding files because of the cyclic dependency -export const element = nodeProxy(ArrayElementNode); -export const convert = (node, types) => nodeObject(new ConvertNode(nodeObject(node), types)); -export const split = (node, channels) => nodeObject(new SplitNode(nodeObject(node), channels)); - -addNodeElement('element', element); -addNodeElement('convert', convert); diff --git a/examples-jsm/examples/renderers/common/Animation.ts b/examples-jsm/examples/renderers/common/Animation.ts deleted file mode 100644 index 0b00319a..00000000 --- a/examples-jsm/examples/renderers/common/Animation.ts +++ /dev/null @@ -1,38 +0,0 @@ -class Animation { - constructor(nodes, info) { - this.nodes = nodes; - this.info = info; - - this.animationLoop = null; - this.requestId = null; - - this._init(); - } - - _init() { - const update = (time, frame) => { - this.requestId = self.requestAnimationFrame(update); - - if (this.info.autoReset === true) this.info.reset(); - - this.nodes.nodeFrame.update(); - - this.info.frame = this.nodes.nodeFrame.frameId; - - if (this.animationLoop !== null) this.animationLoop(time, frame); - }; - - update(); - } - - dispose() { - self.cancelAnimationFrame(this.requestId); - this.requestId = null; - } - - setAnimationLoop(callback) { - this.animationLoop = callback; - } -} - -export default Animation; diff --git a/examples-jsm/examples/renderers/common/Attributes.ts b/examples-jsm/examples/renderers/common/Attributes.ts deleted file mode 100644 index 29553540..00000000 --- a/examples-jsm/examples/renderers/common/Attributes.ts +++ /dev/null @@ -1,53 +0,0 @@ -import DataMap from './DataMap.js'; -import { AttributeType } from './Constants.js'; -import { DynamicDrawUsage } from 'three'; - -class Attributes extends DataMap { - constructor(backend) { - super(); - - this.backend = backend; - } - - delete(attribute) { - const attributeData = super.delete(attribute); - - if (attributeData !== undefined) { - this.backend.destroyAttribute(attribute); - } - - return attributeData; - } - - update(attribute, type) { - const data = this.get(attribute); - - if (data.version === undefined) { - if (type === AttributeType.VERTEX) { - this.backend.createAttribute(attribute); - } else if (type === AttributeType.INDEX) { - this.backend.createIndexAttribute(attribute); - } else if (type === AttributeType.STORAGE) { - this.backend.createStorageAttribute(attribute); - } - - data.version = this._getBufferAttribute(attribute).version; - } else { - const bufferAttribute = this._getBufferAttribute(attribute); - - if (data.version < bufferAttribute.version || bufferAttribute.usage === DynamicDrawUsage) { - this.backend.updateAttribute(attribute); - - data.version = bufferAttribute.version; - } - } - } - - _getBufferAttribute(attribute) { - if (attribute.isInterleavedBufferAttribute) attribute = attribute.data; - - return attribute; - } -} - -export default Attributes; diff --git a/examples-jsm/examples/renderers/common/Backend.ts b/examples-jsm/examples/renderers/common/Backend.ts deleted file mode 100644 index efa5649d..00000000 --- a/examples-jsm/examples/renderers/common/Backend.ts +++ /dev/null @@ -1,165 +0,0 @@ -let vector2 = null; -let vector4 = null; -let color4 = null; - -import Color4 from './Color4.js'; -import { Vector2, Vector4, REVISION, createCanvasElement } from 'three'; - -class Backend { - constructor(parameters = {}) { - this.parameters = Object.assign({}, parameters); - this.data = new WeakMap(); - this.renderer = null; - this.domElement = null; - } - - async init(renderer) { - this.renderer = renderer; - } - - // render context - - begin(renderContext) {} - - finish(renderContext) {} - - // render object - - draw(renderObject, info) {} - - // program - - createProgram(program) {} - - destroyProgram(program) {} - - // bindings - - createBindings(renderObject) {} - - // pipeline - - createRenderPipeline(renderObject) {} - - createComputePipeline(computeNode, pipeline) {} - - destroyPipeline(pipeline) {} - - // cache key - - needsRenderUpdate(renderObject) {} // return Boolean ( fast test ) - - getRenderCacheKey(renderObject) {} // return String - - // node builder - - createNodeBuilder(renderObject) {} // return NodeBuilder (ADD IT) - - // textures - - createSampler(texture) {} - - createDefaultTexture(texture) {} - - createTexture(texture) {} - - copyTextureToBuffer(texture, x, y, width, height) {} - - // attributes - - createAttribute(attribute) {} - - createIndexAttribute(attribute) {} - - updateAttribute(attribute) {} - - destroyAttribute(attribute) {} - - // canvas - - getContext() {} - - updateSize() {} - - // utils - - resolveTimestampAsync(renderContext, type) {} - - hasFeatureAsync(name) {} // return Boolean - - hasFeature(name) {} // return Boolean - - getInstanceCount(renderObject) { - const { object, geometry } = renderObject; - - return geometry.isInstancedBufferGeometry ? geometry.instanceCount : object.count > 1 ? object.count : 1; - } - - getDrawingBufferSize() { - vector2 = vector2 || new Vector2(); - - return this.renderer.getDrawingBufferSize(vector2); - } - - getScissor() { - vector4 = vector4 || new Vector4(); - - return this.renderer.getScissor(vector4); - } - - setScissorTest(boolean) {} - - getClearColor() { - const renderer = this.renderer; - - color4 = color4 || new Color4(); - - renderer.getClearColor(color4); - - color4.getRGB(color4, this.renderer.currentColorSpace); - - return color4; - } - - getDomElement() { - let domElement = this.domElement; - - if (domElement === null) { - domElement = this.parameters.canvas !== undefined ? this.parameters.canvas : createCanvasElement(); - - // OffscreenCanvas does not have setAttribute, see #22811 - if ('setAttribute' in domElement) domElement.setAttribute('data-engine', `three.js r${REVISION} webgpu`); - - this.domElement = domElement; - } - - return domElement; - } - - // resource properties - - set(object, value) { - this.data.set(object, value); - } - - get(object) { - let map = this.data.get(object); - - if (map === undefined) { - map = {}; - this.data.set(object, map); - } - - return map; - } - - has(object) { - return this.data.has(object); - } - - delete(object) { - this.data.delete(object); - } -} - -export default Backend; diff --git a/examples-jsm/examples/renderers/common/Background.ts b/examples-jsm/examples/renderers/common/Background.ts deleted file mode 100644 index b7902dd4..00000000 --- a/examples-jsm/examples/renderers/common/Background.ts +++ /dev/null @@ -1,118 +0,0 @@ -import DataMap from './DataMap.js'; -import Color4 from './Color4.js'; -import { Mesh, SphereGeometry, BackSide, LinearSRGBColorSpace } from 'three'; -import { - vec4, - context, - normalWorld, - backgroundBlurriness, - backgroundIntensity, - NodeMaterial, - modelViewProjection, -} from '../../nodes/Nodes.js'; - -const _clearColor = new Color4(); - -class Background extends DataMap { - constructor(renderer, nodes) { - super(); - - this.renderer = renderer; - this.nodes = nodes; - } - - update(scene, renderList, renderContext) { - const renderer = this.renderer; - const background = this.nodes.getBackgroundNode(scene) || scene.background; - - let forceClear = false; - - if (background === null) { - // no background settings, use clear color configuration from the renderer - - renderer._clearColor.getRGB(_clearColor, LinearSRGBColorSpace); - _clearColor.a = renderer._clearColor.a; - } else if (background.isColor === true) { - // background is an opaque color - - background.getRGB(_clearColor, LinearSRGBColorSpace); - _clearColor.a = 1; - - forceClear = true; - } else if (background.isNode === true) { - const sceneData = this.get(scene); - const backgroundNode = background; - - _clearColor.copy(renderer._clearColor); - - let backgroundMesh = sceneData.backgroundMesh; - - if (backgroundMesh === undefined) { - const backgroundMeshNode = context(vec4(backgroundNode).mul(backgroundIntensity), { - // @TODO: Add Texture2D support using node context - getUV: () => normalWorld, - getTextureLevel: () => backgroundBlurriness, - }); - - let viewProj = modelViewProjection(); - viewProj = viewProj.setZ(viewProj.w); - - const nodeMaterial = new NodeMaterial(); - nodeMaterial.side = BackSide; - nodeMaterial.depthTest = false; - nodeMaterial.depthWrite = false; - nodeMaterial.fog = false; - nodeMaterial.vertexNode = viewProj; - nodeMaterial.fragmentNode = backgroundMeshNode; - - sceneData.backgroundMeshNode = backgroundMeshNode; - sceneData.backgroundMesh = backgroundMesh = new Mesh(new SphereGeometry(1, 32, 32), nodeMaterial); - backgroundMesh.frustumCulled = false; - - backgroundMesh.onBeforeRender = function (renderer, scene, camera) { - this.matrixWorld.copyPosition(camera.matrixWorld); - }; - } - - const backgroundCacheKey = backgroundNode.getCacheKey(); - - if (sceneData.backgroundCacheKey !== backgroundCacheKey) { - sceneData.backgroundMeshNode.node = vec4(backgroundNode).mul(backgroundIntensity); - - backgroundMesh.material.needsUpdate = true; - - sceneData.backgroundCacheKey = backgroundCacheKey; - } - - renderList.unshift(backgroundMesh, backgroundMesh.geometry, backgroundMesh.material, 0, 0, null); - } else { - console.error('THREE.Renderer: Unsupported background configuration.', background); - } - - // - - if (renderer.autoClear === true || forceClear === true) { - _clearColor.multiplyScalar(_clearColor.a); - - const clearColorValue = renderContext.clearColorValue; - - clearColorValue.r = _clearColor.r; - clearColorValue.g = _clearColor.g; - clearColorValue.b = _clearColor.b; - clearColorValue.a = _clearColor.a; - - renderContext.depthClearValue = renderer._clearDepth; - renderContext.stencilClearValue = renderer._clearStencil; - - renderContext.clearColor = renderer.autoClearColor === true; - renderContext.clearDepth = renderer.autoClearDepth === true; - renderContext.clearStencil = renderer.autoClearStencil === true; - } else { - renderContext.clearColor = false; - renderContext.clearDepth = false; - renderContext.clearStencil = false; - } - } -} - -export default Background; diff --git a/examples-jsm/examples/renderers/common/BindGroup.ts b/examples-jsm/examples/renderers/common/BindGroup.ts deleted file mode 100644 index 6b7ffa21..00000000 --- a/examples-jsm/examples/renderers/common/BindGroup.ts +++ /dev/null @@ -1,12 +0,0 @@ -let _id = 0; - -class BindGroup { - constructor(name = '', bindings = []) { - this.name = name; - this.bindings = bindings; - - this.id = _id++; - } -} - -export default BindGroup; diff --git a/examples-jsm/examples/renderers/common/Binding.ts b/examples-jsm/examples/renderers/common/Binding.ts deleted file mode 100644 index a12f3563..00000000 --- a/examples-jsm/examples/renderers/common/Binding.ts +++ /dev/null @@ -1,17 +0,0 @@ -class Binding { - constructor(name = '') { - this.name = name; - - this.visibility = 0; - } - - setVisibility(visibility) { - this.visibility |= visibility; - } - - clone() { - return Object.assign(new this.constructor(), this); - } -} - -export default Binding; diff --git a/examples-jsm/examples/renderers/common/Bindings.ts b/examples-jsm/examples/renderers/common/Bindings.ts deleted file mode 100644 index 51aa31f0..00000000 --- a/examples-jsm/examples/renderers/common/Bindings.ts +++ /dev/null @@ -1,161 +0,0 @@ -import DataMap from './DataMap.js'; -import { AttributeType } from './Constants.js'; - -class Bindings extends DataMap { - constructor(backend, nodes, textures, attributes, pipelines, info) { - super(); - - this.backend = backend; - this.textures = textures; - this.pipelines = pipelines; - this.attributes = attributes; - this.nodes = nodes; - this.info = info; - - this.pipelines.bindings = this; // assign bindings to pipelines - } - - getForRender(renderObject) { - const bindings = renderObject.getBindings(); - - for (const bindGroup of bindings) { - const groupData = this.get(bindGroup); - - if (groupData.bindGroup === undefined) { - // each object defines an array of bindings (ubos, textures, samplers etc.) - - this._init(bindGroup); - - this.backend.createBindings(bindGroup, bindings); - - groupData.bindGroup = bindGroup; - } - } - - return bindings; - } - - getForCompute(computeNode) { - const bindings = this.nodes.getForCompute(computeNode).bindings; - - for (const bindGroup of bindings) { - const groupData = this.get(bindGroup); - - if (groupData.bindGroup === undefined) { - this._init(bindGroup); - - this.backend.createBindings(bindGroup, bindings); - - groupData.bindGroup = bindGroup; - } - } - - return bindings; - } - - updateForCompute(computeNode) { - this._updateBindings(computeNode, this.getForCompute(computeNode)); - } - - updateForRender(renderObject) { - this._updateBindings(renderObject, this.getForRender(renderObject)); - } - - _updateBindings(object, bindings) { - for (const bindGroup of bindings) { - this._update(object, bindGroup, bindings); - } - } - - _init(bindGroup) { - for (const binding of bindGroup.bindings) { - if (binding.isSampledTexture) { - this.textures.updateTexture(binding.texture); - } else if (binding.isStorageBuffer) { - const attribute = binding.attribute; - - this.attributes.update(attribute, AttributeType.STORAGE); - } - } - } - - _update(object, bindGroup, bindings) { - const { backend } = this; - - let needsBindingsUpdate = false; - - // iterate over all bindings and check if buffer updates or a new binding group is required - - for (const binding of bindGroup.bindings) { - if (binding.isNodeUniformsGroup) { - const updated = this.nodes.updateGroup(binding); - - if (!updated) continue; - } - - if (binding.isUniformBuffer) { - const updated = binding.update(); - - if (updated) { - backend.updateBinding(binding); - } - } else if (binding.isSampler) { - binding.update(); - } else if (binding.isSampledTexture) { - const texture = binding.texture; - - if (binding.needsBindingsUpdate) needsBindingsUpdate = true; - - const updated = binding.update(); - - if (updated) { - this.textures.updateTexture(binding.texture); - } - - const textureData = backend.get(binding.texture); - - if ( - backend.isWebGPUBackend === true && - textureData.texture === undefined && - textureData.externalTexture === undefined - ) { - // TODO: Remove this once we found why updated === false isn't bound to a texture in the WebGPU backend - console.error( - 'Bindings._update: binding should be available:', - binding, - updated, - binding.texture, - binding.textureNode.value, - ); - - this.textures.updateTexture(binding.texture); - needsBindingsUpdate = true; - } - - if (texture.isStorageTexture === true) { - const textureData = this.get(texture); - - if (binding.store === true) { - textureData.needsMipmap = true; - } else if ( - texture.generateMipmaps === true && - this.textures.needsMipmaps(texture) && - textureData.needsMipmap === true - ) { - this.backend.generateMipmaps(texture); - - textureData.needsMipmap = false; - } - } - } - } - - if (needsBindingsUpdate === true) { - const pipeline = this.pipelines.getForRender(object); - - this.backend.updateBindings(bindGroup, bindings, pipeline); - } - } -} - -export default Bindings; diff --git a/examples-jsm/examples/renderers/common/Buffer.ts b/examples-jsm/examples/renderers/common/Buffer.ts deleted file mode 100644 index 17013c6d..00000000 --- a/examples-jsm/examples/renderers/common/Buffer.ts +++ /dev/null @@ -1,28 +0,0 @@ -import Binding from './Binding.js'; -import { getFloatLength } from './BufferUtils.js'; - -class Buffer extends Binding { - constructor(name, buffer = null) { - super(name); - - this.isBuffer = true; - - this.bytesPerElement = Float32Array.BYTES_PER_ELEMENT; - - this._buffer = buffer; - } - - get byteLength() { - return getFloatLength(this._buffer.byteLength); - } - - get buffer() { - return this._buffer; - } - - update() { - return true; - } -} - -export default Buffer; diff --git a/examples-jsm/examples/renderers/common/BufferUtils.ts b/examples-jsm/examples/renderers/common/BufferUtils.ts deleted file mode 100644 index 99ddcb48..00000000 --- a/examples-jsm/examples/renderers/common/BufferUtils.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { GPU_CHUNK_BYTES } from './Constants.js'; - -function getFloatLength(floatLength) { - // ensure chunk size alignment (STD140 layout) - - return floatLength + ((GPU_CHUNK_BYTES - (floatLength % GPU_CHUNK_BYTES)) % GPU_CHUNK_BYTES); -} - -function getVectorLength(count, vectorLength = 4) { - const strideLength = getStrideLength(vectorLength); - - const floatLength = strideLength * count; - - return getFloatLength(floatLength); -} - -function getStrideLength(vectorLength) { - const strideLength = 4; - - return vectorLength + ((strideLength - (vectorLength % strideLength)) % strideLength); -} - -export { getFloatLength, getVectorLength, getStrideLength }; diff --git a/examples-jsm/examples/renderers/common/ChainMap.ts b/examples-jsm/examples/renderers/common/ChainMap.ts deleted file mode 100644 index b17e7080..00000000 --- a/examples-jsm/examples/renderers/common/ChainMap.ts +++ /dev/null @@ -1,43 +0,0 @@ -export default class ChainMap { - constructor() { - this.weakMap = new WeakMap(); - } - - get(keys) { - let map = this.weakMap; - - for (let i = 0; i < keys.length; i++) { - map = map.get(keys[i]); - - if (map === undefined) return undefined; - } - - return map.get(keys[keys.length - 1]); - } - - set(keys, value) { - let map = this.weakMap; - - for (let i = 0; i < keys.length; i++) { - const key = keys[i]; - - if (map.has(key) === false) map.set(key, new WeakMap()); - - map = map.get(key); - } - - return map.set(keys[keys.length - 1], value); - } - - delete(keys) { - let map = this.weakMap; - - for (let i = 0; i < keys.length; i++) { - map = map.get(keys[i]); - - if (map === undefined) return false; - } - - return map.delete(keys[keys.length - 1]); - } -} diff --git a/examples-jsm/examples/renderers/common/ClippingContext.ts b/examples-jsm/examples/renderers/common/ClippingContext.ts deleted file mode 100644 index 312e0b77..00000000 --- a/examples-jsm/examples/renderers/common/ClippingContext.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { Matrix3, Plane, Vector4 } from 'three'; - -const _plane = new Plane(); - -let _clippingContextVersion = 0; - -class ClippingContext { - constructor() { - this.version = ++_clippingContextVersion; - - this.globalClippingCount = 0; - - this.localClippingCount = 0; - this.localClippingEnabled = false; - this.localClipIntersection = false; - - this.planes = []; - - this.parentVersion = 0; - this.viewNormalMatrix = new Matrix3(); - } - - projectPlanes(source, offset) { - const l = source.length; - const planes = this.planes; - - for (let i = 0; i < l; i++) { - _plane.copy(source[i]).applyMatrix4(this.viewMatrix, this.viewNormalMatrix); - - const v = planes[offset + i]; - const normal = _plane.normal; - - v.x = -normal.x; - v.y = -normal.y; - v.z = -normal.z; - v.w = _plane.constant; - } - } - - updateGlobal(renderer, camera) { - const rendererClippingPlanes = renderer.clippingPlanes; - this.viewMatrix = camera.matrixWorldInverse; - - this.viewNormalMatrix.getNormalMatrix(this.viewMatrix); - - let update = false; - - if (Array.isArray(rendererClippingPlanes) && rendererClippingPlanes.length !== 0) { - const l = rendererClippingPlanes.length; - - if (l !== this.globalClippingCount) { - const planes = []; - - for (let i = 0; i < l; i++) { - planes.push(new Vector4()); - } - - this.globalClippingCount = l; - this.planes = planes; - - update = true; - } - - this.projectPlanes(rendererClippingPlanes, 0); - } else if (this.globalClippingCount !== 0) { - this.globalClippingCount = 0; - this.planes = []; - update = true; - } - - if (renderer.localClippingEnabled !== this.localClippingEnabled) { - this.localClippingEnabled = renderer.localClippingEnabled; - update = true; - } - - if (update) this.version = _clippingContextVersion++; - } - - update(parent, material) { - let update = false; - - if (this !== parent && parent.version !== this.parentVersion) { - this.globalClippingCount = material.isShadowNodeMaterial ? 0 : parent.globalClippingCount; - this.localClippingEnabled = parent.localClippingEnabled; - this.planes = Array.from(parent.planes); - this.parentVersion = parent.version; - this.viewMatrix = parent.viewMatrix; - this.viewNormalMatrix = parent.viewNormalMatrix; - - update = true; - } - - if (this.localClippingEnabled) { - const localClippingPlanes = material.clippingPlanes; - - if (Array.isArray(localClippingPlanes) && localClippingPlanes.length !== 0) { - const l = localClippingPlanes.length; - const planes = this.planes; - const offset = this.globalClippingCount; - - if (update || l !== this.localClippingCount) { - planes.length = offset + l; - - for (let i = 0; i < l; i++) { - planes[offset + i] = new Vector4(); - } - - this.localClippingCount = l; - update = true; - } - - this.projectPlanes(localClippingPlanes, offset); - } else if (this.localClippingCount !== 0) { - this.localClippingCount = 0; - update = true; - } - - if (this.localClipIntersection !== material.clipIntersection) { - this.localClipIntersection = material.clipIntersection; - update = true; - } - } - - if (update) this.version = _clippingContextVersion++; - } -} - -export default ClippingContext; diff --git a/examples-jsm/examples/renderers/common/Color4.ts b/examples-jsm/examples/renderers/common/Color4.ts deleted file mode 100644 index c681cc90..00000000 --- a/examples-jsm/examples/renderers/common/Color4.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Color } from 'three'; - -class Color4 extends Color { - constructor(r, g, b, a = 1) { - super(r, g, b); - - this.a = a; - } - - set(r, g, b, a = 1) { - this.a = a; - - return super.set(r, g, b); - } - - copy(color) { - if (color.a !== undefined) this.a = color.a; - - return super.copy(color); - } - - clone() { - return new this.constructor(this.r, this.g, this.b, this.a); - } -} - -export default Color4; diff --git a/examples-jsm/examples/renderers/common/ComputePipeline.ts b/examples-jsm/examples/renderers/common/ComputePipeline.ts deleted file mode 100644 index 0fd3ca53..00000000 --- a/examples-jsm/examples/renderers/common/ComputePipeline.ts +++ /dev/null @@ -1,13 +0,0 @@ -import Pipeline from './Pipeline.js'; - -class ComputePipeline extends Pipeline { - constructor(cacheKey, computeProgram) { - super(cacheKey); - - this.computeProgram = computeProgram; - - this.isComputePipeline = true; - } -} - -export default ComputePipeline; diff --git a/examples-jsm/examples/renderers/common/Constants.ts b/examples-jsm/examples/renderers/common/Constants.ts deleted file mode 100644 index 0d0c35a2..00000000 --- a/examples-jsm/examples/renderers/common/Constants.ts +++ /dev/null @@ -1,14 +0,0 @@ -export const AttributeType = { - VERTEX: 1, - INDEX: 2, - STORAGE: 4, -}; - -// size of a chunk in bytes (STD140 layout) - -export const GPU_CHUNK_BYTES = 16; - -// @TODO: Move to src/constants.js - -export const BlendColorFactor = 211; -export const OneMinusBlendColorFactor = 212; diff --git a/examples-jsm/examples/renderers/common/CubeRenderTarget.ts b/examples-jsm/examples/renderers/common/CubeRenderTarget.ts deleted file mode 100644 index 74d04912..00000000 --- a/examples-jsm/examples/renderers/common/CubeRenderTarget.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { - WebGLCubeRenderTarget, - Scene, - CubeCamera, - BoxGeometry, - Mesh, - BackSide, - NoBlending, - LinearFilter, - LinearMipmapLinearFilter, -} from 'three'; -import { equirectUV } from '../../nodes/utils/EquirectUVNode.js'; -import { texture as TSL_Texture } from '../../nodes/accessors/TextureNode.js'; -import { positionWorldDirection } from '../../nodes/accessors/PositionNode.js'; -import { createNodeMaterialFromType } from '../../nodes/materials/NodeMaterial.js'; - -// @TODO: Consider rename WebGLCubeRenderTarget to just CubeRenderTarget - -class CubeRenderTarget extends WebGLCubeRenderTarget { - constructor(size = 1, options = {}) { - super(size, options); - - this.isCubeRenderTarget = true; - } - - fromEquirectangularTexture(renderer, texture) { - const currentMinFilter = texture.minFilter; - const currentGenerateMipmaps = texture.generateMipmaps; - - texture.generateMipmaps = true; - - this.texture.type = texture.type; - this.texture.colorSpace = texture.colorSpace; - - this.texture.generateMipmaps = texture.generateMipmaps; - this.texture.minFilter = texture.minFilter; - this.texture.magFilter = texture.magFilter; - - const geometry = new BoxGeometry(5, 5, 5); - - const uvNode = equirectUV(positionWorldDirection); - - const material = createNodeMaterialFromType('MeshBasicNodeMaterial'); - material.colorNode = TSL_Texture(texture, uvNode, 0); - material.side = BackSide; - material.blending = NoBlending; - - const mesh = new Mesh(geometry, material); - - const scene = new Scene(); - scene.add(mesh); - - // Avoid blurred poles - if (texture.minFilter === LinearMipmapLinearFilter) texture.minFilter = LinearFilter; - - const camera = new CubeCamera(1, 10, this); - camera.update(renderer, scene); - - texture.minFilter = currentMinFilter; - texture.currentGenerateMipmaps = currentGenerateMipmaps; - - mesh.geometry.dispose(); - mesh.material.dispose(); - - return this; - } -} - -export default CubeRenderTarget; diff --git a/examples-jsm/examples/renderers/common/DataMap.ts b/examples-jsm/examples/renderers/common/DataMap.ts deleted file mode 100644 index 006bc295..00000000 --- a/examples-jsm/examples/renderers/common/DataMap.ts +++ /dev/null @@ -1,38 +0,0 @@ -class DataMap { - constructor() { - this.data = new WeakMap(); - } - - get(object) { - let map = this.data.get(object); - - if (map === undefined) { - map = {}; - this.data.set(object, map); - } - - return map; - } - - delete(object) { - let map; - - if (this.data.has(object)) { - map = this.data.get(object); - - this.data.delete(object); - } - - return map; - } - - has(object) { - return this.data.has(object); - } - - dispose() { - this.data = new WeakMap(); - } -} - -export default DataMap; diff --git a/examples-jsm/examples/renderers/common/Geometries.ts b/examples-jsm/examples/renderers/common/Geometries.ts deleted file mode 100644 index ca0cd225..00000000 --- a/examples-jsm/examples/renderers/common/Geometries.ts +++ /dev/null @@ -1,182 +0,0 @@ -import DataMap from './DataMap.js'; -import { AttributeType } from './Constants.js'; -import { Uint32BufferAttribute, Uint16BufferAttribute } from 'three'; - -function arrayNeedsUint32(array) { - // assumes larger values usually on last - - for (let i = array.length - 1; i >= 0; --i) { - if (array[i] >= 65535) return true; // account for PRIMITIVE_RESTART_FIXED_INDEX, #24565 - } - - return false; -} - -function getWireframeVersion(geometry) { - return geometry.index !== null ? geometry.index.version : geometry.attributes.position.version; -} - -function getWireframeIndex(geometry) { - const indices = []; - - const geometryIndex = geometry.index; - const geometryPosition = geometry.attributes.position; - - if (geometryIndex !== null) { - const array = geometryIndex.array; - - for (let i = 0, l = array.length; i < l; i += 3) { - const a = array[i + 0]; - const b = array[i + 1]; - const c = array[i + 2]; - - indices.push(a, b, b, c, c, a); - } - } else { - const array = geometryPosition.array; - - for (let i = 0, l = array.length / 3 - 1; i < l; i += 3) { - const a = i + 0; - const b = i + 1; - const c = i + 2; - - indices.push(a, b, b, c, c, a); - } - } - - const attribute = new (arrayNeedsUint32(indices) ? Uint32BufferAttribute : Uint16BufferAttribute)(indices, 1); - attribute.version = getWireframeVersion(geometry); - - return attribute; -} - -class Geometries extends DataMap { - constructor(attributes, info) { - super(); - - this.attributes = attributes; - this.info = info; - - this.wireframes = new WeakMap(); - - this.attributeCall = new WeakMap(); - } - - has(renderObject) { - const geometry = renderObject.geometry; - - return super.has(geometry) && this.get(geometry).initialized === true; - } - - updateForRender(renderObject) { - if (this.has(renderObject) === false) this.initGeometry(renderObject); - - this.updateAttributes(renderObject); - } - - initGeometry(renderObject) { - const geometry = renderObject.geometry; - const geometryData = this.get(geometry); - - geometryData.initialized = true; - - this.info.memory.geometries++; - - const onDispose = () => { - this.info.memory.geometries--; - - const index = geometry.index; - const geometryAttributes = renderObject.getAttributes(); - - if (index !== null) { - this.attributes.delete(index); - } - - for (const geometryAttribute of geometryAttributes) { - this.attributes.delete(geometryAttribute); - } - - const wireframeAttribute = this.wireframes.get(geometry); - - if (wireframeAttribute !== undefined) { - this.attributes.delete(wireframeAttribute); - } - - geometry.removeEventListener('dispose', onDispose); - }; - - geometry.addEventListener('dispose', onDispose); - } - - updateAttributes(renderObject) { - const attributes = renderObject.getAttributes(); - - for (const attribute of attributes) { - if (attribute.isStorageBufferAttribute || attribute.isStorageInstancedBufferAttribute) { - this.updateAttribute(attribute, AttributeType.STORAGE); - } else { - this.updateAttribute(attribute, AttributeType.VERTEX); - } - } - - const index = this.getIndex(renderObject); - - if (index !== null) { - this.updateAttribute(index, AttributeType.INDEX); - } - } - - updateAttribute(attribute, type) { - const callId = this.info.render.calls; - - if (!attribute.isInterleavedBufferAttribute) { - if (this.attributeCall.get(attribute) !== callId) { - this.attributes.update(attribute, type); - - this.attributeCall.set(attribute, callId); - } - } else { - if (this.attributeCall.get(attribute) === undefined) { - this.attributes.update(attribute, type); - - this.attributeCall.set(attribute, callId); - } else if (this.attributeCall.get(attribute.data) !== callId) { - this.attributes.update(attribute, type); - - this.attributeCall.set(attribute.data, callId); - - this.attributeCall.set(attribute, callId); - } - } - } - - getIndex(renderObject) { - const { geometry, material } = renderObject; - - let index = geometry.index; - - if (material.wireframe === true) { - const wireframes = this.wireframes; - - let wireframeAttribute = wireframes.get(geometry); - - if (wireframeAttribute === undefined) { - wireframeAttribute = getWireframeIndex(geometry); - - wireframes.set(geometry, wireframeAttribute); - } else if (wireframeAttribute.version !== getWireframeVersion(geometry)) { - this.attributes.delete(wireframeAttribute); - - wireframeAttribute = getWireframeIndex(geometry); - - wireframes.set(geometry, wireframeAttribute); - } - - index = wireframeAttribute; - } - - return index; - } -} - -export default Geometries; diff --git a/examples-jsm/examples/renderers/common/Info.ts b/examples-jsm/examples/renderers/common/Info.ts deleted file mode 100644 index c8e7cb41..00000000 --- a/examples-jsm/examples/renderers/common/Info.ts +++ /dev/null @@ -1,76 +0,0 @@ -class Info { - constructor() { - this.autoReset = true; - - this.frame = 0; - this.calls = 0; - - this.render = { - calls: 0, - drawCalls: 0, - triangles: 0, - points: 0, - lines: 0, - timestamp: 0, - }; - - this.compute = { - calls: 0, - computeCalls: 0, - timestamp: 0, - }; - - this.memory = { - geometries: 0, - textures: 0, - }; - } - - update(object, count, instanceCount) { - this.render.drawCalls++; - - if (object.isMesh || object.isSprite) { - this.render.triangles += instanceCount * (count / 3); - } else if (object.isPoints) { - this.render.points += instanceCount * count; - } else if (object.isLineSegments) { - this.render.lines += instanceCount * (count / 2); - } else if (object.isLine) { - this.render.lines += instanceCount * (count - 1); - } else { - console.error('THREE.WebGPUInfo: Unknown object type.'); - } - } - - updateTimestamp(type, time) { - this[type].timestamp += time; - } - - reset() { - this.render.drawCalls = 0; - this.compute.computeCalls = 0; - - this.render.triangles = 0; - this.render.points = 0; - this.render.lines = 0; - - this.render.timestamp = 0; - this.compute.timestamp = 0; - } - - dispose() { - this.reset(); - - this.calls = 0; - - this.render.calls = 0; - this.compute.calls = 0; - - this.render.timestamp = 0; - this.compute.timestamp = 0; - this.memory.geometries = 0; - this.memory.textures = 0; - } -} - -export default Info; diff --git a/examples-jsm/examples/renderers/common/Pipeline.ts b/examples-jsm/examples/renderers/common/Pipeline.ts deleted file mode 100644 index 16017455..00000000 --- a/examples-jsm/examples/renderers/common/Pipeline.ts +++ /dev/null @@ -1,9 +0,0 @@ -class Pipeline { - constructor(cacheKey) { - this.cacheKey = cacheKey; - - this.usedTimes = 0; - } -} - -export default Pipeline; diff --git a/examples-jsm/examples/renderers/common/Pipelines.ts b/examples-jsm/examples/renderers/common/Pipelines.ts deleted file mode 100644 index 68c8f223..00000000 --- a/examples-jsm/examples/renderers/common/Pipelines.ts +++ /dev/null @@ -1,270 +0,0 @@ -import DataMap from './DataMap.js'; -import RenderPipeline from './RenderPipeline.js'; -import ComputePipeline from './ComputePipeline.js'; -import ProgrammableStage from './ProgrammableStage.js'; - -class Pipelines extends DataMap { - constructor(backend, nodes) { - super(); - - this.backend = backend; - this.nodes = nodes; - - this.bindings = null; // set by the bindings - - this.caches = new Map(); - this.programs = { - vertex: new Map(), - fragment: new Map(), - compute: new Map(), - }; - } - - getForCompute(computeNode, bindings) { - const { backend } = this; - - const data = this.get(computeNode); - - if (this._needsComputeUpdate(computeNode)) { - const previousPipeline = data.pipeline; - - if (previousPipeline) { - previousPipeline.usedTimes--; - previousPipeline.computeProgram.usedTimes--; - } - - // get shader - - const nodeBuilderState = this.nodes.getForCompute(computeNode); - - // programmable stage - - let stageCompute = this.programs.compute.get(nodeBuilderState.computeShader); - - if (stageCompute === undefined) { - if (previousPipeline && previousPipeline.computeProgram.usedTimes === 0) - this._releaseProgram(previousPipeline.computeProgram); - - stageCompute = new ProgrammableStage( - nodeBuilderState.computeShader, - 'compute', - nodeBuilderState.transforms, - nodeBuilderState.nodeAttributes, - ); - this.programs.compute.set(nodeBuilderState.computeShader, stageCompute); - - backend.createProgram(stageCompute); - } - - // determine compute pipeline - - const cacheKey = this._getComputeCacheKey(computeNode, stageCompute); - - let pipeline = this.caches.get(cacheKey); - - if (pipeline === undefined) { - if (previousPipeline && previousPipeline.usedTimes === 0) this._releasePipeline(previousPipeline); - - pipeline = this._getComputePipeline(computeNode, stageCompute, cacheKey, bindings); - } - - // keep track of all used times - - pipeline.usedTimes++; - stageCompute.usedTimes++; - - // - - data.version = computeNode.version; - data.pipeline = pipeline; - } - - return data.pipeline; - } - - getForRender(renderObject, promises = null) { - const { backend } = this; - - const data = this.get(renderObject); - - if (this._needsRenderUpdate(renderObject)) { - const previousPipeline = data.pipeline; - - if (previousPipeline) { - previousPipeline.usedTimes--; - previousPipeline.vertexProgram.usedTimes--; - previousPipeline.fragmentProgram.usedTimes--; - } - - // get shader - - const nodeBuilderState = renderObject.getNodeBuilderState(); - - // programmable stages - - let stageVertex = this.programs.vertex.get(nodeBuilderState.vertexShader); - - if (stageVertex === undefined) { - if (previousPipeline && previousPipeline.vertexProgram.usedTimes === 0) - this._releaseProgram(previousPipeline.vertexProgram); - - stageVertex = new ProgrammableStage(nodeBuilderState.vertexShader, 'vertex'); - this.programs.vertex.set(nodeBuilderState.vertexShader, stageVertex); - - backend.createProgram(stageVertex); - } - - let stageFragment = this.programs.fragment.get(nodeBuilderState.fragmentShader); - - if (stageFragment === undefined) { - if (previousPipeline && previousPipeline.fragmentProgram.usedTimes === 0) - this._releaseProgram(previousPipeline.fragmentProgram); - - stageFragment = new ProgrammableStage(nodeBuilderState.fragmentShader, 'fragment'); - this.programs.fragment.set(nodeBuilderState.fragmentShader, stageFragment); - - backend.createProgram(stageFragment); - } - - // determine render pipeline - - const cacheKey = this._getRenderCacheKey(renderObject, stageVertex, stageFragment); - - let pipeline = this.caches.get(cacheKey); - - if (pipeline === undefined) { - if (previousPipeline && previousPipeline.usedTimes === 0) this._releasePipeline(previousPipeline); - - pipeline = this._getRenderPipeline(renderObject, stageVertex, stageFragment, cacheKey, promises); - } else { - renderObject.pipeline = pipeline; - } - - // keep track of all used times - - pipeline.usedTimes++; - stageVertex.usedTimes++; - stageFragment.usedTimes++; - - // - - data.pipeline = pipeline; - } - - return data.pipeline; - } - - delete(object) { - const pipeline = this.get(object).pipeline; - - if (pipeline) { - // pipeline - - pipeline.usedTimes--; - - if (pipeline.usedTimes === 0) this._releasePipeline(pipeline); - - // programs - - if (pipeline.isComputePipeline) { - pipeline.computeProgram.usedTimes--; - - if (pipeline.computeProgram.usedTimes === 0) this._releaseProgram(pipeline.computeProgram); - } else { - pipeline.fragmentProgram.usedTimes--; - pipeline.vertexProgram.usedTimes--; - - if (pipeline.vertexProgram.usedTimes === 0) this._releaseProgram(pipeline.vertexProgram); - if (pipeline.fragmentProgram.usedTimes === 0) this._releaseProgram(pipeline.fragmentProgram); - } - } - - return super.delete(object); - } - - dispose() { - super.dispose(); - - this.caches = new Map(); - this.programs = { - vertex: new Map(), - fragment: new Map(), - compute: new Map(), - }; - } - - updateForRender(renderObject) { - this.getForRender(renderObject); - } - - _getComputePipeline(computeNode, stageCompute, cacheKey, bindings) { - // check for existing pipeline - - cacheKey = cacheKey || this._getComputeCacheKey(computeNode, stageCompute); - - let pipeline = this.caches.get(cacheKey); - - if (pipeline === undefined) { - pipeline = new ComputePipeline(cacheKey, stageCompute); - - this.caches.set(cacheKey, pipeline); - - this.backend.createComputePipeline(pipeline, bindings); - } - - return pipeline; - } - - _getRenderPipeline(renderObject, stageVertex, stageFragment, cacheKey, promises) { - // check for existing pipeline - - cacheKey = cacheKey || this._getRenderCacheKey(renderObject, stageVertex, stageFragment); - - let pipeline = this.caches.get(cacheKey); - - if (pipeline === undefined) { - pipeline = new RenderPipeline(cacheKey, stageVertex, stageFragment); - - this.caches.set(cacheKey, pipeline); - - renderObject.pipeline = pipeline; - - this.backend.createRenderPipeline(renderObject, promises); - } - - return pipeline; - } - - _getComputeCacheKey(computeNode, stageCompute) { - return computeNode.id + ',' + stageCompute.id; - } - - _getRenderCacheKey(renderObject, stageVertex, stageFragment) { - return stageVertex.id + ',' + stageFragment.id + ',' + this.backend.getRenderCacheKey(renderObject); - } - - _releasePipeline(pipeline) { - this.caches.delete(pipeline.cacheKey); - } - - _releaseProgram(program) { - const code = program.code; - const stage = program.stage; - - this.programs[stage].delete(code); - } - - _needsComputeUpdate(computeNode) { - const data = this.get(computeNode); - - return data.pipeline === undefined || data.version !== computeNode.version; - } - - _needsRenderUpdate(renderObject) { - const data = this.get(renderObject); - - return data.pipeline === undefined || this.backend.needsRenderUpdate(renderObject); - } -} - -export default Pipelines; diff --git a/examples-jsm/examples/renderers/common/ProgrammableStage.ts b/examples-jsm/examples/renderers/common/ProgrammableStage.ts deleted file mode 100644 index a684e444..00000000 --- a/examples-jsm/examples/renderers/common/ProgrammableStage.ts +++ /dev/null @@ -1,16 +0,0 @@ -let _id = 0; - -class ProgrammableStage { - constructor(code, type, transforms = null, attributes = null) { - this.id = _id++; - - this.code = code; - this.stage = type; - this.transforms = transforms; - this.attributes = attributes; - - this.usedTimes = 0; - } -} - -export default ProgrammableStage; diff --git a/examples-jsm/examples/renderers/common/RenderBundle.ts b/examples-jsm/examples/renderers/common/RenderBundle.ts deleted file mode 100644 index e59e4937..00000000 --- a/examples-jsm/examples/renderers/common/RenderBundle.ts +++ /dev/null @@ -1,12 +0,0 @@ -class RenderBundle { - constructor(scene, camera) { - this.scene = scene; - this.camera = camera; - } - - clone() { - return Object.assign(new this.constructor(), this); - } -} - -export default RenderBundle; diff --git a/examples-jsm/examples/renderers/common/RenderBundles.ts b/examples-jsm/examples/renderers/common/RenderBundles.ts deleted file mode 100644 index 29140365..00000000 --- a/examples-jsm/examples/renderers/common/RenderBundles.ts +++ /dev/null @@ -1,28 +0,0 @@ -import ChainMap from './ChainMap.js'; -import RenderBundle from './RenderBundle.js'; - -class RenderBundles { - constructor() { - this.lists = new ChainMap(); - } - - get(scene, camera) { - const lists = this.lists; - const keys = [scene, camera]; - - let list = lists.get(keys); - - if (list === undefined) { - list = new RenderBundle(scene, camera); - lists.set(keys, list); - } - - return list; - } - - dispose() { - this.lists = new ChainMap(); - } -} - -export default RenderBundles; diff --git a/examples-jsm/examples/renderers/common/RenderContext.ts b/examples-jsm/examples/renderers/common/RenderContext.ts deleted file mode 100644 index 3b43028e..00000000 --- a/examples-jsm/examples/renderers/common/RenderContext.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Vector4 } from 'three'; - -let id = 0; - -class RenderContext { - constructor() { - this.id = id++; - - this.color = true; - this.clearColor = true; - this.clearColorValue = { r: 0, g: 0, b: 0, a: 1 }; - - this.depth = true; - this.clearDepth = true; - this.clearDepthValue = 1; - - this.stencil = false; - this.clearStencil = true; - this.clearStencilValue = 1; - - this.viewport = false; - this.viewportValue = new Vector4(); - - this.scissor = false; - this.scissorValue = new Vector4(); - - this.textures = null; - this.depthTexture = null; - this.activeCubeFace = 0; - this.sampleCount = 1; - - this.width = 0; - this.height = 0; - - this.isRenderContext = true; - } -} - -export default RenderContext; diff --git a/examples-jsm/examples/renderers/common/RenderContexts.ts b/examples-jsm/examples/renderers/common/RenderContexts.ts deleted file mode 100644 index 630a2e42..00000000 --- a/examples-jsm/examples/renderers/common/RenderContexts.ts +++ /dev/null @@ -1,47 +0,0 @@ -import ChainMap from './ChainMap.js'; -import RenderContext from './RenderContext.js'; - -class RenderContexts { - constructor() { - this.chainMaps = {}; - } - - get(scene, camera, renderTarget = null) { - const chainKey = [scene, camera]; - - let attachmentState; - - if (renderTarget === null) { - attachmentState = 'default'; - } else { - const format = renderTarget.texture.format; - const count = renderTarget.count; - - attachmentState = `${count}:${format}:${renderTarget.samples}:${renderTarget.depthBuffer}:${renderTarget.stencilBuffer}`; - } - - const chainMap = this.getChainMap(attachmentState); - - let renderState = chainMap.get(chainKey); - - if (renderState === undefined) { - renderState = new RenderContext(); - - chainMap.set(chainKey, renderState); - } - - if (renderTarget !== null) renderState.sampleCount = renderTarget.samples === 0 ? 1 : renderTarget.samples; - - return renderState; - } - - getChainMap(attachmentState) { - return this.chainMaps[attachmentState] || (this.chainMaps[attachmentState] = new ChainMap()); - } - - dispose() { - this.chainMaps = {}; - } -} - -export default RenderContexts; diff --git a/examples-jsm/examples/renderers/common/RenderList.ts b/examples-jsm/examples/renderers/common/RenderList.ts deleted file mode 100644 index a72a91df..00000000 --- a/examples-jsm/examples/renderers/common/RenderList.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { LightsNode } from '../../nodes/Nodes.js'; - -function painterSortStable(a, b) { - if (a.groupOrder !== b.groupOrder) { - return a.groupOrder - b.groupOrder; - } else if (a.renderOrder !== b.renderOrder) { - return a.renderOrder - b.renderOrder; - } else if (a.material.id !== b.material.id) { - return a.material.id - b.material.id; - } else if (a.z !== b.z) { - return a.z - b.z; - } else { - return a.id - b.id; - } -} - -function reversePainterSortStable(a, b) { - if (a.groupOrder !== b.groupOrder) { - return a.groupOrder - b.groupOrder; - } else if (a.renderOrder !== b.renderOrder) { - return a.renderOrder - b.renderOrder; - } else if (a.z !== b.z) { - return b.z - a.z; - } else { - return a.id - b.id; - } -} - -class RenderList { - constructor() { - this.renderItems = []; - this.renderItemsIndex = 0; - - this.opaque = []; - this.transparent = []; - this.bundles = []; - - this.lightsNode = new LightsNode([]); - this.lightsArray = []; - - this.occlusionQueryCount = 0; - } - - begin() { - this.renderItemsIndex = 0; - - this.opaque.length = 0; - this.transparent.length = 0; - this.bundles.length = 0; - - this.lightsArray.length = 0; - - this.occlusionQueryCount = 0; - - return this; - } - - getNextRenderItem(object, geometry, material, groupOrder, z, group) { - let renderItem = this.renderItems[this.renderItemsIndex]; - - if (renderItem === undefined) { - renderItem = { - id: object.id, - object: object, - geometry: geometry, - material: material, - groupOrder: groupOrder, - renderOrder: object.renderOrder, - z: z, - group: group, - }; - - this.renderItems[this.renderItemsIndex] = renderItem; - } else { - renderItem.id = object.id; - renderItem.object = object; - renderItem.geometry = geometry; - renderItem.material = material; - renderItem.groupOrder = groupOrder; - renderItem.renderOrder = object.renderOrder; - renderItem.z = z; - renderItem.group = group; - } - - this.renderItemsIndex++; - - return renderItem; - } - - push(object, geometry, material, groupOrder, z, group) { - const renderItem = this.getNextRenderItem(object, geometry, material, groupOrder, z, group); - - if (object.occlusionTest === true) this.occlusionQueryCount++; - - (material.transparent === true || material.transmission > 0 ? this.transparent : this.opaque).push(renderItem); - } - - unshift(object, geometry, material, groupOrder, z, group) { - const renderItem = this.getNextRenderItem(object, geometry, material, groupOrder, z, group); - - (material.transparent === true ? this.transparent : this.opaque).unshift(renderItem); - } - - pushBundle(group) { - this.bundles.push(group); - } - - pushLight(light) { - this.lightsArray.push(light); - } - - getLightsNode() { - return this.lightsNode.fromLights(this.lightsArray); - } - - sort(customOpaqueSort, customTransparentSort) { - if (this.opaque.length > 1) this.opaque.sort(customOpaqueSort || painterSortStable); - if (this.transparent.length > 1) this.transparent.sort(customTransparentSort || reversePainterSortStable); - } - - finish() { - // update lights - - this.lightsNode.fromLights(this.lightsArray); - - // Clear references from inactive renderItems in the list - - for (let i = this.renderItemsIndex, il = this.renderItems.length; i < il; i++) { - const renderItem = this.renderItems[i]; - - if (renderItem.id === null) break; - - renderItem.id = null; - renderItem.object = null; - renderItem.geometry = null; - renderItem.material = null; - renderItem.groupOrder = null; - renderItem.renderOrder = null; - renderItem.z = null; - renderItem.group = null; - } - } -} - -export default RenderList; diff --git a/examples-jsm/examples/renderers/common/RenderLists.ts b/examples-jsm/examples/renderers/common/RenderLists.ts deleted file mode 100644 index 3fc3134e..00000000 --- a/examples-jsm/examples/renderers/common/RenderLists.ts +++ /dev/null @@ -1,28 +0,0 @@ -import ChainMap from './ChainMap.js'; -import RenderList from './RenderList.js'; - -class RenderLists { - constructor() { - this.lists = new ChainMap(); - } - - get(scene, camera) { - const lists = this.lists; - const keys = [scene, camera]; - - let list = lists.get(keys); - - if (list === undefined) { - list = new RenderList(); - lists.set(keys, list); - } - - return list; - } - - dispose() { - this.lists = new ChainMap(); - } -} - -export default RenderLists; diff --git a/examples-jsm/examples/renderers/common/RenderObject.ts b/examples-jsm/examples/renderers/common/RenderObject.ts deleted file mode 100644 index 91ba0f2a..00000000 --- a/examples-jsm/examples/renderers/common/RenderObject.ts +++ /dev/null @@ -1,215 +0,0 @@ -import ClippingContext from './ClippingContext.js'; - -let id = 0; - -function getKeys(obj) { - const keys = Object.keys(obj); - - let proto = Object.getPrototypeOf(obj); - - while (proto) { - const descriptors = Object.getOwnPropertyDescriptors(proto); - - for (const key in descriptors) { - if (descriptors[key] !== undefined) { - const descriptor = descriptors[key]; - - if (descriptor && typeof descriptor.get === 'function') { - keys.push(key); - } - } - } - - proto = Object.getPrototypeOf(proto); - } - - return keys; -} - -export default class RenderObject { - constructor(nodes, geometries, renderer, object, material, scene, camera, lightsNode, renderContext) { - this._nodes = nodes; - this._geometries = geometries; - - this.id = id++; - - this.renderer = renderer; - this.object = object; - this.material = material; - this.scene = scene; - this.camera = camera; - this.lightsNode = lightsNode; - this.context = renderContext; - - this.geometry = object.geometry; - this.version = material.version; - - this.drawRange = null; - - this.attributes = null; - this.pipeline = null; - this.vertexBuffers = null; - - this.updateClipping(renderContext.clippingContext); - - this.clippingContextVersion = this.clippingContext.version; - - this.initialNodesCacheKey = this.getNodesCacheKey(); - this.initialCacheKey = this.getCacheKey(); - - this._nodeBuilderState = null; - this._bindings = null; - - this.onDispose = null; - - this.isRenderObject = true; - - this.onMaterialDispose = () => { - this.dispose(); - }; - - this.material.addEventListener('dispose', this.onMaterialDispose); - } - - updateClipping(parent) { - const material = this.material; - - let clippingContext = this.clippingContext; - - if (Array.isArray(material.clippingPlanes)) { - if (clippingContext === parent || !clippingContext) { - clippingContext = new ClippingContext(); - this.clippingContext = clippingContext; - } - - clippingContext.update(parent, material); - } else if (this.clippingContext !== parent) { - this.clippingContext = parent; - } - } - - get clippingNeedsUpdate() { - if (this.clippingContext.version === this.clippingContextVersion) return false; - - this.clippingContextVersion = this.clippingContext.version; - - return true; - } - - getNodeBuilderState() { - return this._nodeBuilderState || (this._nodeBuilderState = this._nodes.getForRender(this)); - } - - getBindings() { - return this._bindings || (this._bindings = this.getNodeBuilderState().createBindings()); - } - - getIndex() { - return this._geometries.getIndex(this); - } - - getChainArray() { - return [this.object, this.material, this.context, this.lightsNode]; - } - - getAttributes() { - if (this.attributes !== null) return this.attributes; - - const nodeAttributes = this.getNodeBuilderState().nodeAttributes; - const geometry = this.geometry; - - const attributes = []; - const vertexBuffers = new Set(); - - for (const nodeAttribute of nodeAttributes) { - const attribute = - nodeAttribute.node && nodeAttribute.node.attribute - ? nodeAttribute.node.attribute - : geometry.getAttribute(nodeAttribute.name); - - if (attribute === undefined) continue; - - attributes.push(attribute); - - const bufferAttribute = attribute.isInterleavedBufferAttribute ? attribute.data : attribute; - vertexBuffers.add(bufferAttribute); - } - - this.attributes = attributes; - this.vertexBuffers = Array.from(vertexBuffers.values()); - - return attributes; - } - - getVertexBuffers() { - if (this.vertexBuffers === null) this.getAttributes(); - - return this.vertexBuffers; - } - - getMaterialCacheKey() { - const { object, material } = this; - - let cacheKey = material.customProgramCacheKey(); - - for (const property of getKeys(material)) { - if (/^(is[A-Z]|_)|^(visible|version|uuid|name|opacity|userData)$/.test(property)) continue; - - let value = material[property]; - - if (value !== null) { - const type = typeof value; - - if (type === 'number') - value = value !== 0 ? '1' : '0'; // Convert to on/off, important for clearcoat, transmission, etc - else if (type === 'object') value = '{}'; - } - - cacheKey += /*property + ':' +*/ value + ','; - } - - cacheKey += this.clippingContextVersion + ','; - - if (object.skeleton) { - cacheKey += object.skeleton.bones.length + ','; - } - - if (object.morphTargetInfluences) { - cacheKey += object.morphTargetInfluences.length + ','; - } - - if (object.isBatchedMesh) { - cacheKey += object._matricesTexture.uuid + ','; - - if (object._colorsTexture !== null) { - cacheKey += object._colorsTexture.uuid + ','; - } - } - - if (object.count > 1) { - cacheKey += object.count + ','; - } - - return cacheKey; - } - - get needsUpdate() { - return this.initialNodesCacheKey !== this.getNodesCacheKey() || this.clippingNeedsUpdate; - } - - getNodesCacheKey() { - // Environment Nodes Cache Key - - return this._nodes.getCacheKey(this.scene, this.lightsNode); - } - - getCacheKey() { - return this.getMaterialCacheKey() + ',' + this.getNodesCacheKey(); - } - - dispose() { - this.material.removeEventListener('dispose', this.onMaterialDispose); - - this.onDispose(); - } -} diff --git a/examples-jsm/examples/renderers/common/RenderObjects.ts b/examples-jsm/examples/renderers/common/RenderObjects.ts deleted file mode 100644 index 76dc482e..00000000 --- a/examples-jsm/examples/renderers/common/RenderObjects.ts +++ /dev/null @@ -1,100 +0,0 @@ -import ChainMap from './ChainMap.js'; -import RenderObject from './RenderObject.js'; - -class RenderObjects { - constructor(renderer, nodes, geometries, pipelines, bindings, info) { - this.renderer = renderer; - this.nodes = nodes; - this.geometries = geometries; - this.pipelines = pipelines; - this.bindings = bindings; - this.info = info; - - this.chainMaps = {}; - } - - get(object, material, scene, camera, lightsNode, renderContext, passId) { - const chainMap = this.getChainMap(passId); - const chainArray = [object, material, renderContext, lightsNode]; - - let renderObject = chainMap.get(chainArray); - - if (renderObject === undefined) { - renderObject = this.createRenderObject( - this.nodes, - this.geometries, - this.renderer, - object, - material, - scene, - camera, - lightsNode, - renderContext, - passId, - ); - - chainMap.set(chainArray, renderObject); - } else { - renderObject.updateClipping(renderContext.clippingContext); - - if (renderObject.version !== material.version || renderObject.needsUpdate) { - if (renderObject.initialCacheKey !== renderObject.getCacheKey()) { - renderObject.dispose(); - - renderObject = this.get(object, material, scene, camera, lightsNode, renderContext, passId); - } else { - renderObject.version = material.version; - } - } - } - - return renderObject; - } - - getChainMap(passId = 'default') { - return this.chainMaps[passId] || (this.chainMaps[passId] = new ChainMap()); - } - - dispose() { - this.chainMaps = {}; - } - - createRenderObject( - nodes, - geometries, - renderer, - object, - material, - scene, - camera, - lightsNode, - renderContext, - passId, - ) { - const chainMap = this.getChainMap(passId); - - const renderObject = new RenderObject( - nodes, - geometries, - renderer, - object, - material, - scene, - camera, - lightsNode, - renderContext, - ); - - renderObject.onDispose = () => { - this.pipelines.delete(renderObject); - this.bindings.delete(renderObject); - this.nodes.delete(renderObject); - - chainMap.delete(renderObject.getChainArray()); - }; - - return renderObject; - } -} - -export default RenderObjects; diff --git a/examples-jsm/examples/renderers/common/RenderPipeline.ts b/examples-jsm/examples/renderers/common/RenderPipeline.ts deleted file mode 100644 index 0ec34b04..00000000 --- a/examples-jsm/examples/renderers/common/RenderPipeline.ts +++ /dev/null @@ -1,12 +0,0 @@ -import Pipeline from './Pipeline.js'; - -class RenderPipeline extends Pipeline { - constructor(cacheKey, vertexProgram, fragmentProgram) { - super(cacheKey); - - this.vertexProgram = vertexProgram; - this.fragmentProgram = fragmentProgram; - } -} - -export default RenderPipeline; diff --git a/examples-jsm/examples/renderers/common/Renderer.ts b/examples-jsm/examples/renderers/common/Renderer.ts deleted file mode 100644 index dc2980c1..00000000 --- a/examples-jsm/examples/renderers/common/Renderer.ts +++ /dev/null @@ -1,1345 +0,0 @@ -import Animation from './Animation.js'; -import RenderObjects from './RenderObjects.js'; -import Attributes from './Attributes.js'; -import Geometries from './Geometries.js'; -import Info from './Info.js'; -import Pipelines from './Pipelines.js'; -import Bindings from './Bindings.js'; -import RenderLists from './RenderLists.js'; -import RenderContexts from './RenderContexts.js'; -import Textures from './Textures.js'; -import Background from './Background.js'; -import Nodes from './nodes/Nodes.js'; -import Color4 from './Color4.js'; -import ClippingContext from './ClippingContext.js'; -import { - Scene, - Frustum, - Matrix4, - Vector2, - Vector3, - Vector4, - DoubleSide, - BackSide, - FrontSide, - SRGBColorSpace, - NoColorSpace, - NoToneMapping, - LinearFilter, - LinearSRGBColorSpace, - RenderTarget, - HalfFloatType, - RGBAFormat, -} from 'three'; -import { NodeMaterial } from '../../nodes/Nodes.js'; -import QuadMesh from '../../objects/QuadMesh.js'; -import RenderBundles from './RenderBundles.js'; - -const _scene = new Scene(); -const _drawingBufferSize = new Vector2(); -const _screen = new Vector4(); -const _frustum = new Frustum(); -const _projScreenMatrix = new Matrix4(); -const _vector3 = new Vector3(); -const _quad = new QuadMesh(new NodeMaterial()); - -class Renderer { - constructor(backend, parameters = {}) { - this.isRenderer = true; - - // - - const { logarithmicDepthBuffer = false, alpha = true } = parameters; - - // public - - this.domElement = backend.getDomElement(); - - this.backend = backend; - - this.autoClear = true; - this.autoClearColor = true; - this.autoClearDepth = true; - this.autoClearStencil = true; - - this.alpha = alpha; - - this.logarithmicDepthBuffer = logarithmicDepthBuffer; - - this.outputColorSpace = SRGBColorSpace; - - this.toneMapping = NoToneMapping; - this.toneMappingExposure = 1.0; - - this.sortObjects = true; - - this.depth = true; - this.stencil = false; - - this.clippingPlanes = []; - - this.info = new Info(); - - // nodes - - this.toneMappingNode = null; - - // internals - - this._pixelRatio = 1; - this._width = this.domElement.width; - this._height = this.domElement.height; - - this._viewport = new Vector4(0, 0, this._width, this._height); - this._scissor = new Vector4(0, 0, this._width, this._height); - this._scissorTest = false; - - this._attributes = null; - this._geometries = null; - this._nodes = null; - this._animation = null; - this._bindings = null; - this._objects = null; - this._pipelines = null; - this._bundles = null; - this._renderLists = null; - this._renderContexts = null; - this._textures = null; - this._background = null; - - this._currentRenderContext = null; - - this._opaqueSort = null; - this._transparentSort = null; - - this._frameBufferTarget = null; - - const alphaClear = this.alpha === true ? 0 : 1; - - this._clearColor = new Color4(0, 0, 0, alphaClear); - this._clearDepth = 1; - this._clearStencil = 0; - - this._renderTarget = null; - this._activeCubeFace = 0; - this._activeMipmapLevel = 0; - - this._renderObjectFunction = null; - this._currentRenderObjectFunction = null; - this._currentRenderBundle = null; - - this._handleObjectFunction = this._renderObjectDirect; - - this._initialized = false; - this._initPromise = null; - - this._compilationPromises = null; - - // backwards compatibility - - this.shadowMap = { - enabled: false, - type: null, - }; - - this.xr = { - enabled: false, - }; - - this.debug = { - checkShaderErrors: true, - onShaderError: null, - }; - } - - async init() { - if (this._initialized) { - throw new Error('Renderer: Backend has already been initialized.'); - } - - if (this._initPromise !== null) { - return this._initPromise; - } - - this._initPromise = new Promise(async (resolve, reject) => { - const backend = this.backend; - - try { - await backend.init(this); - } catch (error) { - reject(error); - return; - } - - this._nodes = new Nodes(this, backend); - this._animation = new Animation(this._nodes, this.info); - this._attributes = new Attributes(backend); - this._background = new Background(this, this._nodes); - this._geometries = new Geometries(this._attributes, this.info); - this._textures = new Textures(this, backend, this.info); - this._pipelines = new Pipelines(backend, this._nodes); - this._bindings = new Bindings( - backend, - this._nodes, - this._textures, - this._attributes, - this._pipelines, - this.info, - ); - this._objects = new RenderObjects( - this, - this._nodes, - this._geometries, - this._pipelines, - this._bindings, - this.info, - ); - this._renderLists = new RenderLists(); - this._bundles = new RenderBundles(); - this._renderContexts = new RenderContexts(); - - // - - this._initialized = true; - - resolve(); - }); - - return this._initPromise; - } - - get coordinateSystem() { - return this.backend.coordinateSystem; - } - - async compileAsync(scene, camera, targetScene = null) { - if (this._initialized === false) await this.init(); - - // preserve render tree - - const nodeFrame = this._nodes.nodeFrame; - - const previousRenderId = nodeFrame.renderId; - const previousRenderContext = this._currentRenderContext; - const previousRenderObjectFunction = this._currentRenderObjectFunction; - const previousCompilationPromises = this._compilationPromises; - - // - - const sceneRef = scene.isScene === true ? scene : _scene; - - if (targetScene === null) targetScene = scene; - - const renderTarget = this._renderTarget; - const renderContext = this._renderContexts.get(targetScene, camera, renderTarget); - const activeMipmapLevel = this._activeMipmapLevel; - - const compilationPromises = []; - - this._currentRenderContext = renderContext; - this._currentRenderObjectFunction = this.renderObject; - - this._handleObjectFunction = this._createObjectPipeline; - - this._compilationPromises = compilationPromises; - - nodeFrame.renderId++; - - // - - nodeFrame.update(); - - // - - renderContext.depth = this.depth; - renderContext.stencil = this.stencil; - - if (!renderContext.clippingContext) renderContext.clippingContext = new ClippingContext(); - renderContext.clippingContext.updateGlobal(this, camera); - - // - - sceneRef.onBeforeRender(this, scene, camera, renderTarget); - - // - - const renderList = this._renderLists.get(scene, camera); - renderList.begin(); - - this._projectObject(scene, camera, 0, renderList); - - // include lights from target scene - if (targetScene !== scene) { - targetScene.traverseVisible(function (object) { - if (object.isLight && object.layers.test(camera.layers)) { - renderList.pushLight(object); - } - }); - } - - renderList.finish(); - - // - - if (renderTarget !== null) { - this._textures.updateRenderTarget(renderTarget, activeMipmapLevel); - - const renderTargetData = this._textures.get(renderTarget); - - renderContext.textures = renderTargetData.textures; - renderContext.depthTexture = renderTargetData.depthTexture; - } else { - renderContext.textures = null; - renderContext.depthTexture = null; - } - - // - - this._nodes.updateScene(sceneRef); - - // - - this._background.update(sceneRef, renderList, renderContext); - - // process render lists - - const opaqueObjects = renderList.opaque; - const transparentObjects = renderList.transparent; - const lightsNode = renderList.lightsNode; - - if (opaqueObjects.length > 0) this._renderObjects(opaqueObjects, camera, sceneRef, lightsNode); - if (transparentObjects.length > 0) this._renderObjects(transparentObjects, camera, sceneRef, lightsNode); - - // restore render tree - - nodeFrame.renderId = previousRenderId; - - this._currentRenderContext = previousRenderContext; - this._currentRenderObjectFunction = previousRenderObjectFunction; - this._compilationPromises = previousCompilationPromises; - - this._handleObjectFunction = this._renderObjectDirect; - - // wait for all promises setup by backends awaiting compilation/linking/pipeline creation to complete - - await Promise.all(compilationPromises); - } - - async renderAsync(scene, camera) { - if (this._initialized === false) await this.init(); - - const renderContext = this._renderScene(scene, camera); - - await this.backend.resolveTimestampAsync(renderContext, 'render'); - } - - _renderBundle(bundle, sceneRef, lightsNode) { - const { object, camera, renderList } = bundle; - - const renderContext = this._currentRenderContext; - const renderContextData = this.backend.get(renderContext); - - // - - const renderBundle = this._bundles.get(object, camera); - - const renderBundleData = this.backend.get(renderBundle); - if (renderBundleData.renderContexts === undefined) renderBundleData.renderContexts = new Set(); - - // - - const renderBundleNeedsUpdate = - renderBundleData.renderContexts.has(renderContext) === false || object.needsUpdate === true; - - renderBundleData.renderContexts.add(renderContext); - - if (renderBundleNeedsUpdate) { - if (renderContextData.renderObjects === undefined || object.needsUpdate === true) { - const nodeFrame = this._nodes.nodeFrame; - - renderContextData.renderObjects = []; - renderContextData.renderBundles = []; - renderContextData.scene = sceneRef; - renderContextData.camera = camera; - renderContextData.renderId = nodeFrame.renderId; - - renderContextData.registerBundlesPhase = true; - } - - this._currentRenderBundle = renderBundle; - - const opaqueObjects = renderList.opaque; - - if (opaqueObjects.length > 0) this._renderObjects(opaqueObjects, camera, sceneRef, lightsNode); - - this._currentRenderBundle = null; - - // - - object.needsUpdate = false; - } else { - const renderContext = this._currentRenderContext; - const renderContextData = this.backend.get(renderContext); - - for (let i = 0, l = renderContextData.renderObjects.length; i < l; i++) { - const renderObject = renderContextData.renderObjects[i]; - - this._nodes.updateBefore(renderObject); - - // - - renderObject.object.modelViewMatrix.multiplyMatrices( - camera.matrixWorldInverse, - renderObject.object.matrixWorld, - ); - renderObject.object.normalMatrix.getNormalMatrix(renderObject.object.modelViewMatrix); - - this._nodes.updateForRender(renderObject); - this._bindings.updateForRender(renderObject); - - this.backend.draw(renderObject, this.info); - - this._nodes.updateAfter(renderObject); - } - } - } - - render(scene, camera) { - if (this._initialized === false) { - console.warn( - 'THREE.Renderer: .render() called before the backend is initialized. Try using .renderAsync() instead.', - ); - - return this.renderAsync(scene, camera); - } - - this._renderScene(scene, camera); - } - - _getFrameBufferTarget() { - const { currentColorSpace } = this; - - const useToneMapping = - this._renderTarget === null && (this.toneMapping !== NoToneMapping || this.toneMappingNode !== null); - const useColorSpace = currentColorSpace !== LinearSRGBColorSpace && currentColorSpace !== NoColorSpace; - - if (useToneMapping === false && useColorSpace === false) return null; - - const { width, height } = this.getDrawingBufferSize(_drawingBufferSize); - const { depth, stencil } = this; - - let frameBufferTarget = this._frameBufferTarget; - - if (frameBufferTarget === null) { - frameBufferTarget = new RenderTarget(width, height, { - depthBuffer: depth, - stencilBuffer: stencil, - type: HalfFloatType, // FloatType - format: RGBAFormat, - colorSpace: LinearSRGBColorSpace, - generateMipmaps: false, - minFilter: LinearFilter, - magFilter: LinearFilter, - samples: this.backend.parameters.antialias ? 4 : 0, - }); - - frameBufferTarget.isPostProcessingRenderTarget = true; - - this._frameBufferTarget = frameBufferTarget; - } - - frameBufferTarget.depthBuffer = depth; - frameBufferTarget.stencilBuffer = stencil; - frameBufferTarget.setSize(width, height); - frameBufferTarget.viewport.copy(this._viewport); - frameBufferTarget.scissor.copy(this._scissor); - frameBufferTarget.viewport.multiplyScalar(this._pixelRatio); - frameBufferTarget.scissor.multiplyScalar(this._pixelRatio); - frameBufferTarget.scissorTest = this._scissorTest; - - return frameBufferTarget; - } - - _renderScene(scene, camera, useFrameBufferTarget = true) { - const frameBufferTarget = useFrameBufferTarget ? this._getFrameBufferTarget() : null; - - // preserve render tree - - const nodeFrame = this._nodes.nodeFrame; - - const previousRenderId = nodeFrame.renderId; - const previousRenderContext = this._currentRenderContext; - const previousRenderObjectFunction = this._currentRenderObjectFunction; - - // - - const sceneRef = scene.isScene === true ? scene : _scene; - - const outputRenderTarget = this._renderTarget; - - const activeCubeFace = this._activeCubeFace; - const activeMipmapLevel = this._activeMipmapLevel; - - // - - let renderTarget; - - if (frameBufferTarget !== null) { - renderTarget = frameBufferTarget; - - this.setRenderTarget(renderTarget); - } else { - renderTarget = outputRenderTarget; - } - - // - - const renderContext = this._renderContexts.get(scene, camera, renderTarget); - - this._currentRenderContext = renderContext; - this._currentRenderObjectFunction = this._renderObjectFunction || this.renderObject; - - // - - this.info.calls++; - this.info.render.calls++; - - nodeFrame.renderId = this.info.calls; - - // - - const coordinateSystem = this.coordinateSystem; - - if (camera.coordinateSystem !== coordinateSystem) { - camera.coordinateSystem = coordinateSystem; - - camera.updateProjectionMatrix(); - } - - // - - if (scene.matrixWorldAutoUpdate === true) scene.updateMatrixWorld(); - - if (camera.parent === null && camera.matrixWorldAutoUpdate === true) camera.updateMatrixWorld(); - - // - - let viewport = this._viewport; - let scissor = this._scissor; - let pixelRatio = this._pixelRatio; - - if (renderTarget !== null) { - viewport = renderTarget.viewport; - scissor = renderTarget.scissor; - pixelRatio = 1; - } - - this.getDrawingBufferSize(_drawingBufferSize); - - _screen.set(0, 0, _drawingBufferSize.width, _drawingBufferSize.height); - - const minDepth = viewport.minDepth === undefined ? 0 : viewport.minDepth; - const maxDepth = viewport.maxDepth === undefined ? 1 : viewport.maxDepth; - - renderContext.viewportValue.copy(viewport).multiplyScalar(pixelRatio).floor(); - renderContext.viewportValue.width >>= activeMipmapLevel; - renderContext.viewportValue.height >>= activeMipmapLevel; - renderContext.viewportValue.minDepth = minDepth; - renderContext.viewportValue.maxDepth = maxDepth; - renderContext.viewport = renderContext.viewportValue.equals(_screen) === false; - - renderContext.scissorValue.copy(scissor).multiplyScalar(pixelRatio).floor(); - renderContext.scissor = this._scissorTest && renderContext.scissorValue.equals(_screen) === false; - renderContext.scissorValue.width >>= activeMipmapLevel; - renderContext.scissorValue.height >>= activeMipmapLevel; - - if (!renderContext.clippingContext) renderContext.clippingContext = new ClippingContext(); - renderContext.clippingContext.updateGlobal(this, camera); - - // - - sceneRef.onBeforeRender(this, scene, camera, renderTarget); - - // - - _projScreenMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse); - _frustum.setFromProjectionMatrix(_projScreenMatrix, coordinateSystem); - - const renderList = this._renderLists.get(scene, camera); - renderList.begin(); - - this._projectObject(scene, camera, 0, renderList); - - renderList.finish(); - - if (this.sortObjects === true) { - renderList.sort(this._opaqueSort, this._transparentSort); - } - - // - - if (renderTarget !== null) { - this._textures.updateRenderTarget(renderTarget, activeMipmapLevel); - - const renderTargetData = this._textures.get(renderTarget); - - renderContext.textures = renderTargetData.textures; - renderContext.depthTexture = renderTargetData.depthTexture; - renderContext.width = renderTargetData.width; - renderContext.height = renderTargetData.height; - renderContext.renderTarget = renderTarget; - renderContext.depth = renderTarget.depthBuffer; - renderContext.stencil = renderTarget.stencilBuffer; - } else { - renderContext.textures = null; - renderContext.depthTexture = null; - renderContext.width = this.domElement.width; - renderContext.height = this.domElement.height; - renderContext.depth = this.depth; - renderContext.stencil = this.stencil; - } - - renderContext.width >>= activeMipmapLevel; - renderContext.height >>= activeMipmapLevel; - renderContext.activeCubeFace = activeCubeFace; - renderContext.activeMipmapLevel = activeMipmapLevel; - renderContext.occlusionQueryCount = renderList.occlusionQueryCount; - - // - - this._nodes.updateScene(sceneRef); - - // - - this._background.update(sceneRef, renderList, renderContext); - - // - - this.backend.beginRender(renderContext); - - // process render lists - - const opaqueObjects = renderList.opaque; - const transparentObjects = renderList.transparent; - const bundles = renderList.bundles; - const lightsNode = renderList.lightsNode; - - if (bundles.length > 0) this._renderBundles(bundles, sceneRef, lightsNode); - if (opaqueObjects.length > 0) this._renderObjects(opaqueObjects, camera, sceneRef, lightsNode); - if (transparentObjects.length > 0) this._renderObjects(transparentObjects, camera, sceneRef, lightsNode); - - // finish render pass - - this.backend.finishRender(renderContext); - - // restore render tree - - nodeFrame.renderId = previousRenderId; - - this._currentRenderContext = previousRenderContext; - this._currentRenderObjectFunction = previousRenderObjectFunction; - - // - - if (frameBufferTarget !== null) { - this.setRenderTarget(outputRenderTarget, activeCubeFace, activeMipmapLevel); - - _quad.material.fragmentNode = this._nodes.getOutputNode(renderTarget.texture); - - this._renderScene(_quad, _quad.camera, false); - } - - // - - sceneRef.onAfterRender(this, scene, camera, renderTarget); - - // - - return renderContext; - } - - getMaxAnisotropy() { - return this.backend.getMaxAnisotropy(); - } - - getActiveCubeFace() { - return this._activeCubeFace; - } - - getActiveMipmapLevel() { - return this._activeMipmapLevel; - } - - async setAnimationLoop(callback) { - if (this._initialized === false) await this.init(); - - this._animation.setAnimationLoop(callback); - } - - async getArrayBufferAsync(attribute) { - return await this.backend.getArrayBufferAsync(attribute); - } - - getContext() { - return this.backend.getContext(); - } - - getPixelRatio() { - return this._pixelRatio; - } - - getDrawingBufferSize(target) { - return target.set(this._width * this._pixelRatio, this._height * this._pixelRatio).floor(); - } - - getSize(target) { - return target.set(this._width, this._height); - } - - setPixelRatio(value = 1) { - this._pixelRatio = value; - - this.setSize(this._width, this._height, false); - } - - setDrawingBufferSize(width, height, pixelRatio) { - this._width = width; - this._height = height; - - this._pixelRatio = pixelRatio; - - this.domElement.width = Math.floor(width * pixelRatio); - this.domElement.height = Math.floor(height * pixelRatio); - - this.setViewport(0, 0, width, height); - - if (this._initialized) this.backend.updateSize(); - } - - setSize(width, height, updateStyle = true) { - this._width = width; - this._height = height; - - this.domElement.width = Math.floor(width * this._pixelRatio); - this.domElement.height = Math.floor(height * this._pixelRatio); - - if (updateStyle === true) { - this.domElement.style.width = width + 'px'; - this.domElement.style.height = height + 'px'; - } - - this.setViewport(0, 0, width, height); - - if (this._initialized) this.backend.updateSize(); - } - - setOpaqueSort(method) { - this._opaqueSort = method; - } - - setTransparentSort(method) { - this._transparentSort = method; - } - - getScissor(target) { - const scissor = this._scissor; - - target.x = scissor.x; - target.y = scissor.y; - target.width = scissor.width; - target.height = scissor.height; - - return target; - } - - setScissor(x, y, width, height) { - const scissor = this._scissor; - - if (x.isVector4) { - scissor.copy(x); - } else { - scissor.set(x, y, width, height); - } - } - - getScissorTest() { - return this._scissorTest; - } - - setScissorTest(boolean) { - this._scissorTest = boolean; - - this.backend.setScissorTest(boolean); - } - - getViewport(target) { - return target.copy(this._viewport); - } - - setViewport(x, y, width, height, minDepth = 0, maxDepth = 1) { - const viewport = this._viewport; - - if (x.isVector4) { - viewport.copy(x); - } else { - viewport.set(x, y, width, height); - } - - viewport.minDepth = minDepth; - viewport.maxDepth = maxDepth; - } - - getClearColor(target) { - return target.copy(this._clearColor); - } - - setClearColor(color, alpha = 1) { - this._clearColor.set(color); - this._clearColor.a = alpha; - } - - getClearAlpha() { - return this._clearColor.a; - } - - setClearAlpha(alpha) { - this._clearColor.a = alpha; - } - - getClearDepth() { - return this._clearDepth; - } - - setClearDepth(depth) { - this._clearDepth = depth; - } - - getClearStencil() { - return this._clearStencil; - } - - setClearStencil(stencil) { - this._clearStencil = stencil; - } - - isOccluded(object) { - const renderContext = this._currentRenderContext; - - return renderContext && this.backend.isOccluded(renderContext, object); - } - - clear(color = true, depth = true, stencil = true) { - if (this._initialized === false) { - console.warn( - 'THREE.Renderer: .clear() called before the backend is initialized. Try using .clearAsync() instead.', - ); - - return this.clearAsync(color, depth, stencil); - } - - const renderTarget = this._renderTarget || this._getFrameBufferTarget(); - - let renderTargetData = null; - - if (renderTarget !== null) { - this._textures.updateRenderTarget(renderTarget); - - renderTargetData = this._textures.get(renderTarget); - } - - this.backend.clear(color, depth, stencil, renderTargetData); - - if (renderTarget !== null && this._renderTarget === null) { - // If a color space transform or tone mapping is required, - // the clear operation clears the intermediate renderTarget texture, but does not update the screen canvas. - - _quad.material.fragmentNode = this._nodes.getOutputNode(renderTarget.texture); - this._renderScene(_quad, _quad.camera, false); - } - } - - clearColor() { - return this.clear(true, false, false); - } - - clearDepth() { - return this.clear(false, true, false); - } - - clearStencil() { - return this.clear(false, false, true); - } - - async clearAsync(color = true, depth = true, stencil = true) { - if (this._initialized === false) await this.init(); - - this.clear(color, depth, stencil); - } - - clearColorAsync() { - return this.clearAsync(true, false, false); - } - - clearDepthAsync() { - return this.clearAsync(false, true, false); - } - - clearStencilAsync() { - return this.clearAsync(false, false, true); - } - - get currentColorSpace() { - const renderTarget = this._renderTarget; - - if (renderTarget !== null) { - const texture = renderTarget.texture; - - return (Array.isArray(texture) ? texture[0] : texture).colorSpace; - } - - return this.outputColorSpace; - } - - dispose() { - this.info.dispose(); - - this._animation.dispose(); - this._objects.dispose(); - this._pipelines.dispose(); - this._nodes.dispose(); - this._bindings.dispose(); - this._renderLists.dispose(); - this._renderContexts.dispose(); - this._textures.dispose(); - - this.setRenderTarget(null); - this.setAnimationLoop(null); - } - - setRenderTarget(renderTarget, activeCubeFace = 0, activeMipmapLevel = 0) { - this._renderTarget = renderTarget; - this._activeCubeFace = activeCubeFace; - this._activeMipmapLevel = activeMipmapLevel; - } - - getRenderTarget() { - return this._renderTarget; - } - - setRenderObjectFunction(renderObjectFunction) { - this._renderObjectFunction = renderObjectFunction; - } - - getRenderObjectFunction() { - return this._renderObjectFunction; - } - - async computeAsync(computeNodes) { - if (this._initialized === false) await this.init(); - - const nodeFrame = this._nodes.nodeFrame; - - const previousRenderId = nodeFrame.renderId; - - // - - this.info.calls++; - this.info.compute.calls++; - this.info.compute.computeCalls++; - - nodeFrame.renderId = this.info.calls; - - // - - const backend = this.backend; - const pipelines = this._pipelines; - const bindings = this._bindings; - const nodes = this._nodes; - const computeList = Array.isArray(computeNodes) ? computeNodes : [computeNodes]; - - if (computeList[0] === undefined || computeList[0].isComputeNode !== true) { - throw new Error('THREE.Renderer: .compute() expects a ComputeNode.'); - } - - backend.beginCompute(computeNodes); - - for (const computeNode of computeList) { - // onInit - - if (pipelines.has(computeNode) === false) { - const dispose = () => { - computeNode.removeEventListener('dispose', dispose); - - pipelines.delete(computeNode); - bindings.delete(computeNode); - nodes.delete(computeNode); - }; - - computeNode.addEventListener('dispose', dispose); - - // - - computeNode.onInit({ renderer: this }); - } - - nodes.updateForCompute(computeNode); - bindings.updateForCompute(computeNode); - - const computeBindings = bindings.getForCompute(computeNode); - const computePipeline = pipelines.getForCompute(computeNode, computeBindings); - - backend.compute(computeNodes, computeNode, computeBindings, computePipeline); - } - - backend.finishCompute(computeNodes); - - await this.backend.resolveTimestampAsync(computeNodes, 'compute'); - - // - - nodeFrame.renderId = previousRenderId; - } - - async hasFeatureAsync(name) { - if (this._initialized === false) await this.init(); - - return this.backend.hasFeature(name); - } - - hasFeature(name) { - if (this._initialized === false) { - console.warn( - 'THREE.Renderer: .hasFeature() called before the backend is initialized. Try using .hasFeatureAsync() instead.', - ); - - return false; - } - - return this.backend.hasFeature(name); - } - - copyFramebufferToTexture(framebufferTexture) { - const renderContext = this._currentRenderContext; - - this._textures.updateTexture(framebufferTexture); - - this.backend.copyFramebufferToTexture(framebufferTexture, renderContext); - } - - copyTextureToTexture(srcTexture, dstTexture, srcRegion = null, dstPosition = null, level = 0) { - this._textures.updateTexture(srcTexture); - this._textures.updateTexture(dstTexture); - - this.backend.copyTextureToTexture(srcTexture, dstTexture, srcRegion, dstPosition, level); - } - - readRenderTargetPixelsAsync(renderTarget, x, y, width, height, index = 0) { - return this.backend.copyTextureToBuffer(renderTarget.textures[index], x, y, width, height); - } - - _projectObject(object, camera, groupOrder, renderList) { - if (object.visible === false) return; - - const visible = object.layers.test(camera.layers); - - if (visible) { - if (object.isGroup) { - groupOrder = object.renderOrder; - } else if (object.isLOD) { - if (object.autoUpdate === true) object.update(camera); - } else if (object.isLight) { - renderList.pushLight(object); - } else if (object.isSprite) { - if (!object.frustumCulled || _frustum.intersectsSprite(object)) { - if (this.sortObjects === true) { - _vector3.setFromMatrixPosition(object.matrixWorld).applyMatrix4(_projScreenMatrix); - } - - const geometry = object.geometry; - const material = object.material; - - if (material.visible) { - renderList.push(object, geometry, material, groupOrder, _vector3.z, null); - } - } - } else if (object.isLineLoop) { - console.error( - 'THREE.Renderer: Objects of type THREE.LineLoop are not supported. Please use THREE.Line or THREE.LineSegments.', - ); - } else if (object.isMesh || object.isLine || object.isPoints) { - if (!object.frustumCulled || _frustum.intersectsObject(object)) { - const geometry = object.geometry; - const material = object.material; - - if (this.sortObjects === true) { - if (geometry.boundingSphere === null) geometry.computeBoundingSphere(); - - _vector3 - .copy(geometry.boundingSphere.center) - .applyMatrix4(object.matrixWorld) - .applyMatrix4(_projScreenMatrix); - } - - if (Array.isArray(material)) { - const groups = geometry.groups; - - for (let i = 0, l = groups.length; i < l; i++) { - const group = groups[i]; - const groupMaterial = material[group.materialIndex]; - - if (groupMaterial && groupMaterial.visible) { - renderList.push(object, geometry, groupMaterial, groupOrder, _vector3.z, group); - } - } - } else if (material.visible) { - renderList.push(object, geometry, material, groupOrder, _vector3.z, null); - } - } - } - } - - if (object.static === true) { - const baseRenderList = renderList; - - // replace render list - renderList = this._renderLists.get(object, camera); - - renderList.begin(); - - baseRenderList.pushBundle({ - object, - camera, - renderList, - }); - - renderList.finish(); - } - - const children = object.children; - - for (let i = 0, l = children.length; i < l; i++) { - this._projectObject(children[i], camera, groupOrder, renderList); - } - } - - _renderBundles(bundles, sceneRef, lightsNode) { - for (const bundle of bundles) { - this._renderBundle(bundle, sceneRef, lightsNode); - } - } - - _renderObjects(renderList, camera, scene, lightsNode) { - // process renderable objects - - for (let i = 0, il = renderList.length; i < il; i++) { - const renderItem = renderList[i]; - - // @TODO: Add support for multiple materials per object. This will require to extract - // the material from the renderItem object and pass it with its group data to renderObject(). - - const { object, geometry, material, group } = renderItem; - - if (camera.isArrayCamera) { - const cameras = camera.cameras; - - for (let j = 0, jl = cameras.length; j < jl; j++) { - const camera2 = cameras[j]; - - if (object.layers.test(camera2.layers)) { - const vp = camera2.viewport; - const minDepth = vp.minDepth === undefined ? 0 : vp.minDepth; - const maxDepth = vp.maxDepth === undefined ? 1 : vp.maxDepth; - - const viewportValue = this._currentRenderContext.viewportValue; - viewportValue.copy(vp).multiplyScalar(this._pixelRatio).floor(); - viewportValue.minDepth = minDepth; - viewportValue.maxDepth = maxDepth; - - this.backend.updateViewport(this._currentRenderContext); - - this._currentRenderObjectFunction( - object, - scene, - camera2, - geometry, - material, - group, - lightsNode, - ); - } - } - } else { - this._currentRenderObjectFunction(object, scene, camera, geometry, material, group, lightsNode); - } - } - } - - renderObject(object, scene, camera, geometry, material, group, lightsNode) { - let overridePositionNode; - let overrideFragmentNode; - let overrideDepthNode; - - // - - object.onBeforeRender(this, scene, camera, geometry, material, group); - - // - - if (scene.overrideMaterial !== null) { - const overrideMaterial = scene.overrideMaterial; - - if (material.positionNode && material.positionNode.isNode) { - overridePositionNode = overrideMaterial.positionNode; - overrideMaterial.positionNode = material.positionNode; - } - - if (overrideMaterial.isShadowNodeMaterial) { - overrideMaterial.side = material.shadowSide === null ? material.side : material.shadowSide; - - if (material.depthNode && material.depthNode.isNode) { - overrideDepthNode = overrideMaterial.depthNode; - overrideMaterial.depthNode = material.depthNode; - } - - if (material.shadowNode && material.shadowNode.isNode) { - overrideFragmentNode = overrideMaterial.fragmentNode; - overrideMaterial.fragmentNode = material.shadowNode; - } - - if (this.localClippingEnabled) { - if (material.clipShadows) { - if (overrideMaterial.clippingPlanes !== material.clippingPlanes) { - overrideMaterial.clippingPlanes = material.clippingPlanes; - overrideMaterial.needsUpdate = true; - } - - if (overrideMaterial.clipIntersection !== material.clipIntersection) { - overrideMaterial.clipIntersection = material.clipIntersection; - } - } else if (Array.isArray(overrideMaterial.clippingPlanes)) { - overrideMaterial.clippingPlanes = null; - overrideMaterial.needsUpdate = true; - } - } - } - - material = overrideMaterial; - } - - // - - if (material.transparent === true && material.side === DoubleSide && material.forceSinglePass === false) { - material.side = BackSide; - this._handleObjectFunction(object, material, scene, camera, lightsNode, group, 'backSide'); // create backSide pass id - - material.side = FrontSide; - this._handleObjectFunction(object, material, scene, camera, lightsNode, group); // use default pass id - - material.side = DoubleSide; - } else { - this._handleObjectFunction(object, material, scene, camera, lightsNode, group); - } - - // - - if (overridePositionNode !== undefined) { - scene.overrideMaterial.positionNode = overridePositionNode; - } - - if (overrideDepthNode !== undefined) { - scene.overrideMaterial.depthNode = overrideDepthNode; - } - - if (overrideFragmentNode !== undefined) { - scene.overrideMaterial.fragmentNode = overrideFragmentNode; - } - - // - - object.onAfterRender(this, scene, camera, geometry, material, group); - } - - _renderObjectDirect(object, material, scene, camera, lightsNode, group, passId) { - const renderObject = this._objects.get( - object, - material, - scene, - camera, - lightsNode, - this._currentRenderContext, - passId, - ); - renderObject.drawRange = group || object.geometry.drawRange; - - // - - this._nodes.updateBefore(renderObject); - - // - - object.modelViewMatrix.multiplyMatrices(camera.matrixWorldInverse, object.matrixWorld); - object.normalMatrix.getNormalMatrix(object.modelViewMatrix); - - // - - this._nodes.updateForRender(renderObject); - this._geometries.updateForRender(renderObject); - this._bindings.updateForRender(renderObject); - this._pipelines.updateForRender(renderObject); - - // - - if (this._currentRenderBundle !== null && this._currentRenderBundle.needsUpdate === true) { - const renderObjectData = this.backend.get(renderObject); - - renderObjectData.bundleEncoder = undefined; - renderObjectData.lastPipelineGPU = undefined; - } - - this.backend.draw(renderObject, this.info); - - if (this._currentRenderBundle !== null) { - const renderContextData = this.backend.get(this._currentRenderContext); - - renderContextData.renderObjects.push(renderObject); - } - - this._nodes.updateAfter(renderObject); - } - - _createObjectPipeline(object, material, scene, camera, lightsNode, passId) { - const renderObject = this._objects.get( - object, - material, - scene, - camera, - lightsNode, - this._currentRenderContext, - passId, - ); - - // - - this._nodes.updateBefore(renderObject); - - // - - this._nodes.updateForRender(renderObject); - this._geometries.updateForRender(renderObject); - this._bindings.updateForRender(renderObject); - - this._pipelines.getForRender(renderObject, this._compilationPromises); - - this._nodes.updateAfter(renderObject); - } - - get compute() { - return this.computeAsync; - } - - get compile() { - return this.compileAsync; - } -} - -export default Renderer; diff --git a/examples-jsm/examples/renderers/common/SampledTexture.ts b/examples-jsm/examples/renderers/common/SampledTexture.ts deleted file mode 100644 index d995a59f..00000000 --- a/examples-jsm/examples/renderers/common/SampledTexture.ts +++ /dev/null @@ -1,61 +0,0 @@ -import Binding from './Binding.js'; - -let id = 0; - -class SampledTexture extends Binding { - constructor(name, texture) { - super(name); - - this.id = id++; - - this.texture = texture; - this.version = texture ? texture.version : 0; - this.store = false; - - this.isSampledTexture = true; - } - - get needsBindingsUpdate() { - const { texture, version } = this; - - return texture.isVideoTexture ? true : version !== texture.version; // @TODO: version === 0 && texture.version > 0 ( add it just to External Textures like PNG,JPG ) - } - - update() { - const { texture, version } = this; - - if (version !== texture.version) { - this.version = texture.version; - - return true; - } - - return false; - } -} - -class SampledArrayTexture extends SampledTexture { - constructor(name, texture) { - super(name, texture); - - this.isSampledArrayTexture = true; - } -} - -class Sampled3DTexture extends SampledTexture { - constructor(name, texture) { - super(name, texture); - - this.isSampled3DTexture = true; - } -} - -class SampledCubeTexture extends SampledTexture { - constructor(name, texture) { - super(name, texture); - - this.isSampledCubeTexture = true; - } -} - -export { SampledTexture, SampledArrayTexture, Sampled3DTexture, SampledCubeTexture }; diff --git a/examples-jsm/examples/renderers/common/Sampler.ts b/examples-jsm/examples/renderers/common/Sampler.ts deleted file mode 100644 index 8cd20d04..00000000 --- a/examples-jsm/examples/renderers/common/Sampler.ts +++ /dev/null @@ -1,14 +0,0 @@ -import Binding from './Binding.js'; - -class Sampler extends Binding { - constructor(name, texture) { - super(name); - - this.texture = texture; - this.version = texture ? texture.version : 0; - - this.isSampler = true; - } -} - -export default Sampler; diff --git a/examples-jsm/examples/renderers/common/StorageBuffer.ts b/examples-jsm/examples/renderers/common/StorageBuffer.ts deleted file mode 100644 index ef5d3e46..00000000 --- a/examples-jsm/examples/renderers/common/StorageBuffer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import Buffer from './Buffer.js'; - -class StorageBuffer extends Buffer { - constructor(name, attribute) { - super(name, attribute ? attribute.array : null); - - this.attribute = attribute; - - this.isStorageBuffer = true; - } -} - -export default StorageBuffer; diff --git a/examples-jsm/examples/renderers/common/Textures.ts b/examples-jsm/examples/renderers/common/Textures.ts deleted file mode 100644 index 0eb0509c..00000000 --- a/examples-jsm/examples/renderers/common/Textures.ts +++ /dev/null @@ -1,288 +0,0 @@ -import DataMap from './DataMap.js'; - -import { - Vector3, - DepthTexture, - DepthStencilFormat, - DepthFormat, - UnsignedIntType, - UnsignedInt248Type, - LinearFilter, - NearestFilter, - EquirectangularReflectionMapping, - EquirectangularRefractionMapping, - CubeReflectionMapping, - CubeRefractionMapping, - UnsignedByteType, -} from 'three'; - -const _size = new Vector3(); - -class Textures extends DataMap { - constructor(renderer, backend, info) { - super(); - - this.renderer = renderer; - this.backend = backend; - this.info = info; - } - - updateRenderTarget(renderTarget, activeMipmapLevel = 0) { - const renderTargetData = this.get(renderTarget); - - const sampleCount = renderTarget.samples === 0 ? 1 : renderTarget.samples; - const depthTextureMips = renderTargetData.depthTextureMips || (renderTargetData.depthTextureMips = {}); - - const texture = renderTarget.texture; - const textures = renderTarget.textures; - - const size = this.getSize(texture); - - const mipWidth = size.width >> activeMipmapLevel; - const mipHeight = size.height >> activeMipmapLevel; - - let depthTexture = renderTarget.depthTexture || depthTextureMips[activeMipmapLevel]; - let textureNeedsUpdate = false; - - if (depthTexture === undefined) { - depthTexture = new DepthTexture(); - depthTexture.format = renderTarget.stencilBuffer ? DepthStencilFormat : DepthFormat; - depthTexture.type = renderTarget.stencilBuffer ? UnsignedInt248Type : UnsignedIntType; // FloatType - depthTexture.image.width = mipWidth; - depthTexture.image.height = mipHeight; - - depthTextureMips[activeMipmapLevel] = depthTexture; - } - - if (renderTargetData.width !== size.width || size.height !== renderTargetData.height) { - textureNeedsUpdate = true; - depthTexture.needsUpdate = true; - - depthTexture.image.width = mipWidth; - depthTexture.image.height = mipHeight; - } - - renderTargetData.width = size.width; - renderTargetData.height = size.height; - renderTargetData.textures = textures; - renderTargetData.depthTexture = depthTexture; - renderTargetData.depth = renderTarget.depthBuffer; - renderTargetData.stencil = renderTarget.stencilBuffer; - renderTargetData.renderTarget = renderTarget; - - if (renderTargetData.sampleCount !== sampleCount) { - textureNeedsUpdate = true; - depthTexture.needsUpdate = true; - - renderTargetData.sampleCount = sampleCount; - } - - // - - const options = { sampleCount }; - - for (let i = 0; i < textures.length; i++) { - const texture = textures[i]; - - if (textureNeedsUpdate) texture.needsUpdate = true; - - this.updateTexture(texture, options); - } - - this.updateTexture(depthTexture, options); - - // dispose handler - - if (renderTargetData.initialized !== true) { - renderTargetData.initialized = true; - - // dispose - - const onDispose = () => { - renderTarget.removeEventListener('dispose', onDispose); - - if (textures !== undefined) { - for (let i = 0; i < textures.length; i++) { - this._destroyTexture(textures[i]); - } - } else { - this._destroyTexture(texture); - } - - this._destroyTexture(depthTexture); - }; - - renderTarget.addEventListener('dispose', onDispose); - } - } - - updateTexture(texture, options = {}) { - const textureData = this.get(texture); - if (textureData.initialized === true && textureData.version === texture.version) return; - - const isRenderTarget = texture.isRenderTargetTexture || texture.isDepthTexture || texture.isFramebufferTexture; - const backend = this.backend; - - if (isRenderTarget && textureData.initialized === true) { - // it's an update - - backend.destroySampler(texture); - backend.destroyTexture(texture); - } - - // - - if (texture.isFramebufferTexture) { - const renderer = this.renderer; - const renderTarget = renderer.getRenderTarget(); - - if (renderTarget) { - texture.type = renderTarget.texture.type; - } else { - texture.type = UnsignedByteType; - } - } - - // - - const { width, height, depth } = this.getSize(texture); - - options.width = width; - options.height = height; - options.depth = depth; - options.needsMipmaps = this.needsMipmaps(texture); - options.levels = options.needsMipmaps ? this.getMipLevels(texture, width, height) : 1; - - // - - if (isRenderTarget || texture.isStorageTexture === true) { - backend.createSampler(texture); - backend.createTexture(texture, options); - } else { - const needsCreate = textureData.initialized !== true; - - if (needsCreate) backend.createSampler(texture); - - if (texture.version > 0) { - const image = texture.image; - - if (image === undefined) { - console.warn('THREE.Renderer: Texture marked for update but image is undefined.'); - } else if (image.complete === false) { - console.warn('THREE.Renderer: Texture marked for update but image is incomplete.'); - } else { - if (texture.images) { - const images = []; - - for (const image of texture.images) { - images.push(image); - } - - options.images = images; - } else { - options.image = image; - } - - if (textureData.isDefaultTexture === undefined || textureData.isDefaultTexture === true) { - backend.createTexture(texture, options); - - textureData.isDefaultTexture = false; - } - - if (texture.source.dataReady === true) backend.updateTexture(texture, options); - - if (options.needsMipmaps && texture.mipmaps.length === 0) backend.generateMipmaps(texture); - } - } else { - // async update - - backend.createDefaultTexture(texture); - - textureData.isDefaultTexture = true; - } - } - - // dispose handler - - if (textureData.initialized !== true) { - textureData.initialized = true; - - // - - this.info.memory.textures++; - - // dispose - - const onDispose = () => { - texture.removeEventListener('dispose', onDispose); - - this._destroyTexture(texture); - - this.info.memory.textures--; - }; - - texture.addEventListener('dispose', onDispose); - } - - // - - textureData.version = texture.version; - } - - getSize(texture, target = _size) { - let image = texture.images ? texture.images[0] : texture.image; - - if (image) { - if (image.image !== undefined) image = image.image; - - target.width = image.width; - target.height = image.height; - target.depth = texture.isCubeTexture ? 6 : image.depth || 1; - } else { - target.width = target.height = target.depth = 1; - } - - return target; - } - - getMipLevels(texture, width, height) { - let mipLevelCount; - - if (texture.isCompressedTexture) { - mipLevelCount = texture.mipmaps.length; - } else { - mipLevelCount = Math.floor(Math.log2(Math.max(width, height))) + 1; - } - - return mipLevelCount; - } - - needsMipmaps(texture) { - if (this.isEnvironmentTexture(texture)) return true; - - return ( - texture.isCompressedTexture === true || - (texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter) - ); - } - - isEnvironmentTexture(texture) { - const mapping = texture.mapping; - - return ( - mapping === EquirectangularReflectionMapping || - mapping === EquirectangularRefractionMapping || - mapping === CubeReflectionMapping || - mapping === CubeRefractionMapping - ); - } - - _destroyTexture(texture) { - this.backend.destroySampler(texture); - this.backend.destroyTexture(texture); - - this.delete(texture); - } -} - -export default Textures; diff --git a/examples-jsm/examples/renderers/common/Uniform.ts b/examples-jsm/examples/renderers/common/Uniform.ts deleted file mode 100644 index 3d58b44c..00000000 --- a/examples-jsm/examples/renderers/common/Uniform.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { Color, Matrix3, Matrix4, Vector2, Vector3, Vector4 } from 'three'; - -class Uniform { - constructor(name, value) { - this.name = name; - this.value = value; - - this.boundary = 0; // used to build the uniform buffer according to the STD140 layout - this.itemSize = 0; - - this.offset = 0; // this property is set by WebGPUUniformsGroup and marks the start position in the uniform buffer - } - - setValue(value) { - this.value = value; - } - - getValue() { - return this.value; - } -} - -class NumberUniform extends Uniform { - constructor(name, value = 0) { - super(name, value); - - this.isNumberUniform = true; - - this.boundary = 4; - this.itemSize = 1; - } -} - -class Vector2Uniform extends Uniform { - constructor(name, value = new Vector2()) { - super(name, value); - - this.isVector2Uniform = true; - - this.boundary = 8; - this.itemSize = 2; - } -} - -class Vector3Uniform extends Uniform { - constructor(name, value = new Vector3()) { - super(name, value); - - this.isVector3Uniform = true; - - this.boundary = 16; - this.itemSize = 3; - } -} - -class Vector4Uniform extends Uniform { - constructor(name, value = new Vector4()) { - super(name, value); - - this.isVector4Uniform = true; - - this.boundary = 16; - this.itemSize = 4; - } -} - -class ColorUniform extends Uniform { - constructor(name, value = new Color()) { - super(name, value); - - this.isColorUniform = true; - - this.boundary = 16; - this.itemSize = 3; - } -} - -class Matrix3Uniform extends Uniform { - constructor(name, value = new Matrix3()) { - super(name, value); - - this.isMatrix3Uniform = true; - - this.boundary = 48; - this.itemSize = 12; - } -} - -class Matrix4Uniform extends Uniform { - constructor(name, value = new Matrix4()) { - super(name, value); - - this.isMatrix4Uniform = true; - - this.boundary = 64; - this.itemSize = 16; - } -} - -export { NumberUniform, Vector2Uniform, Vector3Uniform, Vector4Uniform, ColorUniform, Matrix3Uniform, Matrix4Uniform }; diff --git a/examples-jsm/examples/renderers/common/UniformBuffer.ts b/examples-jsm/examples/renderers/common/UniformBuffer.ts deleted file mode 100644 index 28aac0d7..00000000 --- a/examples-jsm/examples/renderers/common/UniformBuffer.ts +++ /dev/null @@ -1,11 +0,0 @@ -import Buffer from './Buffer.js'; - -class UniformBuffer extends Buffer { - constructor(name, buffer = null) { - super(name, buffer); - - this.isUniformBuffer = true; - } -} - -export default UniformBuffer; diff --git a/examples-jsm/examples/renderers/common/UniformsGroup.ts b/examples-jsm/examples/renderers/common/UniformsGroup.ts deleted file mode 100644 index e2b62671..00000000 --- a/examples-jsm/examples/renderers/common/UniformsGroup.ts +++ /dev/null @@ -1,277 +0,0 @@ -import UniformBuffer from './UniformBuffer.js'; -import { GPU_CHUNK_BYTES } from './Constants.js'; - -class UniformsGroup extends UniformBuffer { - constructor(name) { - super(name); - - this.isUniformsGroup = true; - - this._values = null; - - // the order of uniforms in this array must match the order of uniforms in the shader - - this.uniforms = []; - } - - addUniform(uniform) { - this.uniforms.push(uniform); - - return this; - } - - removeUniform(uniform) { - const index = this.uniforms.indexOf(uniform); - - if (index !== -1) { - this.uniforms.splice(index, 1); - } - - return this; - } - - get values() { - if (this._values === null) { - this._values = Array.from(this.buffer); - } - - return this._values; - } - - get buffer() { - let buffer = this._buffer; - - if (buffer === null) { - const byteLength = this.byteLength; - - buffer = new Float32Array(new ArrayBuffer(byteLength)); - - this._buffer = buffer; - } - - return buffer; - } - - get byteLength() { - let offset = 0; // global buffer offset in bytes - - for (let i = 0, l = this.uniforms.length; i < l; i++) { - const uniform = this.uniforms[i]; - - const { boundary, itemSize } = uniform; - - // offset within a single chunk in bytes - - const chunkOffset = offset % GPU_CHUNK_BYTES; - const remainingSizeInChunk = GPU_CHUNK_BYTES - chunkOffset; - - // conformance tests - - if (chunkOffset !== 0 && remainingSizeInChunk - boundary < 0) { - // check for chunk overflow - - offset += GPU_CHUNK_BYTES - chunkOffset; - } else if (chunkOffset % boundary !== 0) { - // check for correct alignment - - offset += chunkOffset % boundary; - } - - uniform.offset = offset / this.bytesPerElement; - - offset += itemSize * this.bytesPerElement; - } - - return Math.ceil(offset / GPU_CHUNK_BYTES) * GPU_CHUNK_BYTES; - } - - update() { - let updated = false; - - for (const uniform of this.uniforms) { - if (this.updateByType(uniform) === true) { - updated = true; - } - } - - return updated; - } - - updateByType(uniform) { - if (uniform.isNumberUniform) return this.updateNumber(uniform); - if (uniform.isVector2Uniform) return this.updateVector2(uniform); - if (uniform.isVector3Uniform) return this.updateVector3(uniform); - if (uniform.isVector4Uniform) return this.updateVector4(uniform); - if (uniform.isColorUniform) return this.updateColor(uniform); - if (uniform.isMatrix3Uniform) return this.updateMatrix3(uniform); - if (uniform.isMatrix4Uniform) return this.updateMatrix4(uniform); - - console.error('THREE.WebGPUUniformsGroup: Unsupported uniform type.', uniform); - } - - updateNumber(uniform) { - let updated = false; - - const a = this.values; - const v = uniform.getValue(); - const offset = uniform.offset; - - if (a[offset] !== v) { - const b = this.buffer; - - b[offset] = a[offset] = v; - updated = true; - } - - return updated; - } - - updateVector2(uniform) { - let updated = false; - - const a = this.values; - const v = uniform.getValue(); - const offset = uniform.offset; - - if (a[offset + 0] !== v.x || a[offset + 1] !== v.y) { - const b = this.buffer; - - b[offset + 0] = a[offset + 0] = v.x; - b[offset + 1] = a[offset + 1] = v.y; - - updated = true; - } - - return updated; - } - - updateVector3(uniform) { - let updated = false; - - const a = this.values; - const v = uniform.getValue(); - const offset = uniform.offset; - - if (a[offset + 0] !== v.x || a[offset + 1] !== v.y || a[offset + 2] !== v.z) { - const b = this.buffer; - - b[offset + 0] = a[offset + 0] = v.x; - b[offset + 1] = a[offset + 1] = v.y; - b[offset + 2] = a[offset + 2] = v.z; - - updated = true; - } - - return updated; - } - - updateVector4(uniform) { - let updated = false; - - const a = this.values; - const v = uniform.getValue(); - const offset = uniform.offset; - - if (a[offset + 0] !== v.x || a[offset + 1] !== v.y || a[offset + 2] !== v.z || a[offset + 4] !== v.w) { - const b = this.buffer; - - b[offset + 0] = a[offset + 0] = v.x; - b[offset + 1] = a[offset + 1] = v.y; - b[offset + 2] = a[offset + 2] = v.z; - b[offset + 3] = a[offset + 3] = v.w; - - updated = true; - } - - return updated; - } - - updateColor(uniform) { - let updated = false; - - const a = this.values; - const c = uniform.getValue(); - const offset = uniform.offset; - - if (a[offset + 0] !== c.r || a[offset + 1] !== c.g || a[offset + 2] !== c.b) { - const b = this.buffer; - - b[offset + 0] = a[offset + 0] = c.r; - b[offset + 1] = a[offset + 1] = c.g; - b[offset + 2] = a[offset + 2] = c.b; - - updated = true; - } - - return updated; - } - - updateMatrix3(uniform) { - let updated = false; - - const a = this.values; - const e = uniform.getValue().elements; - const offset = uniform.offset; - - if ( - a[offset + 0] !== e[0] || - a[offset + 1] !== e[1] || - a[offset + 2] !== e[2] || - a[offset + 4] !== e[3] || - a[offset + 5] !== e[4] || - a[offset + 6] !== e[5] || - a[offset + 8] !== e[6] || - a[offset + 9] !== e[7] || - a[offset + 10] !== e[8] - ) { - const b = this.buffer; - - b[offset + 0] = a[offset + 0] = e[0]; - b[offset + 1] = a[offset + 1] = e[1]; - b[offset + 2] = a[offset + 2] = e[2]; - b[offset + 4] = a[offset + 4] = e[3]; - b[offset + 5] = a[offset + 5] = e[4]; - b[offset + 6] = a[offset + 6] = e[5]; - b[offset + 8] = a[offset + 8] = e[6]; - b[offset + 9] = a[offset + 9] = e[7]; - b[offset + 10] = a[offset + 10] = e[8]; - - updated = true; - } - - return updated; - } - - updateMatrix4(uniform) { - let updated = false; - - const a = this.values; - const e = uniform.getValue().elements; - const offset = uniform.offset; - - if (arraysEqual(a, e, offset) === false) { - const b = this.buffer; - b.set(e, offset); - setArray(a, e, offset); - updated = true; - } - - return updated; - } -} - -function setArray(a, b, offset) { - for (let i = 0, l = b.length; i < l; i++) { - a[offset + i] = b[i]; - } -} - -function arraysEqual(a, b, offset) { - for (let i = 0, l = b.length; i < l; i++) { - if (a[offset + i] !== b[i]) return false; - } - - return true; -} - -export default UniformsGroup; diff --git a/examples-jsm/examples/renderers/common/extras/PMREMGenerator.ts b/examples-jsm/examples/renderers/common/extras/PMREMGenerator.ts deleted file mode 100644 index b0aa2f8e..00000000 --- a/examples-jsm/examples/renderers/common/extras/PMREMGenerator.ts +++ /dev/null @@ -1,659 +0,0 @@ -import NodeMaterial from '../../../nodes/materials/NodeMaterial.js'; -import { getDirection, blur } from '../../../nodes/pmrem/PMREMUtils.js'; -import { equirectUV } from '../../../nodes/utils/EquirectUVNode.js'; -import { uniform } from '../../../nodes/core/UniformNode.js'; -import { uniforms } from '../../../nodes/accessors/UniformsNode.js'; -import { texture } from '../../../nodes/accessors/TextureNode.js'; -import { cubeTexture } from '../../../nodes/accessors/CubeTextureNode.js'; -import { float, vec3 } from '../../../nodes/shadernode/ShaderNode.js'; -import { uv } from '../../../nodes/accessors/UVNode.js'; -import { attribute } from '../../../nodes/core/AttributeNode.js'; -import { - OrthographicCamera, - Color, - Vector3, - BufferGeometry, - BufferAttribute, - RenderTarget, - Mesh, - CubeReflectionMapping, - CubeRefractionMapping, - CubeUVReflectionMapping, - LinearFilter, - NoBlending, - RGBAFormat, - HalfFloatType, - BackSide, - LinearSRGBColorSpace, - PerspectiveCamera, - MeshBasicMaterial, - BoxGeometry, -} from 'three'; - -const LOD_MIN = 4; - -// The standard deviations (radians) associated with the extra mips. These are -// chosen to approximate a Trowbridge-Reitz distribution function times the -// geometric shadowing function. These sigma values squared must match the -// variance #defines in cube_uv_reflection_fragment.glsl.js. -const EXTRA_LOD_SIGMA = [0.125, 0.215, 0.35, 0.446, 0.526, 0.582]; - -// The maximum length of the blur for loop. Smaller sigmas will use fewer -// samples and exit early, but not recompile the shader. -const MAX_SAMPLES = 20; - -const _flatCamera = /*@__PURE__*/ new OrthographicCamera(-1, 1, 1, -1, 0, 1); -const _cubeCamera = /*@__PURE__*/ new PerspectiveCamera(90, 1); -const _clearColor = /*@__PURE__*/ new Color(); -let _oldTarget = null; -let _oldActiveCubeFace = 0; -let _oldActiveMipmapLevel = 0; - -// Golden Ratio -const PHI = (1 + Math.sqrt(5)) / 2; -const INV_PHI = 1 / PHI; - -// Vertices of a dodecahedron (except the opposites, which represent the -// same axis), used as axis directions evenly spread on a sphere. -const _axisDirections = [ - /*@__PURE__*/ new Vector3(-PHI, INV_PHI, 0), - /*@__PURE__*/ new Vector3(PHI, INV_PHI, 0), - /*@__PURE__*/ new Vector3(-INV_PHI, 0, PHI), - /*@__PURE__*/ new Vector3(INV_PHI, 0, PHI), - /*@__PURE__*/ new Vector3(0, PHI, -INV_PHI), - /*@__PURE__*/ new Vector3(0, PHI, INV_PHI), - /*@__PURE__*/ new Vector3(-1, 1, -1), - /*@__PURE__*/ new Vector3(1, 1, -1), - /*@__PURE__*/ new Vector3(-1, 1, 1), - /*@__PURE__*/ new Vector3(1, 1, 1), -]; - -// - -// WebGPU Face indices -const _faceLib = [3, 1, 5, 0, 4, 2]; - -const direction = getDirection(uv(), attribute('faceIndex')).normalize(); -const outputDirection = vec3(direction.x, direction.y.negate(), direction.z); - -/** - * This class generates a Prefiltered, Mipmapped Radiance Environment Map - * (PMREM) from a cubeMap environment texture. This allows different levels of - * blur to be quickly accessed based on material roughness. It is packed into a - * special CubeUV format that allows us to perform custom interpolation so that - * we can support nonlinear formats such as RGBE. Unlike a traditional mipmap - * chain, it only goes down to the LOD_MIN level (above), and then creates extra - * even more filtered 'mips' at the same LOD_MIN resolution, associated with - * higher roughness levels. In this way we maintain resolution to smoothly - * interpolate diffuse lighting while limiting sampling computation. - * - * Paper: Fast, Accurate Image-Based Lighting - * https://drive.google.com/file/d/15y8r_UpKlU9SvV4ILb0C3qCPecS8pvLz/view - */ - -class PMREMGenerator { - constructor(renderer) { - this._renderer = renderer; - this._pingPongRenderTarget = null; - - this._lodMax = 0; - this._cubeSize = 0; - this._lodPlanes = []; - this._sizeLods = []; - this._sigmas = []; - this._lodMeshes = []; - - this._blurMaterial = null; - this._cubemapMaterial = null; - this._equirectMaterial = null; - this._backgroundBox = null; - } - - /** - * Generates a PMREM from a supplied Scene, which can be faster than using an - * image if networking bandwidth is low. Optional sigma specifies a blur radius - * in radians to be applied to the scene before PMREM generation. Optional near - * and far planes ensure the scene is rendered in its entirety (the cubeCamera - * is placed at the origin). - */ - fromScene(scene, sigma = 0, near = 0.1, far = 100) { - _oldTarget = this._renderer.getRenderTarget(); - _oldActiveCubeFace = this._renderer.getActiveCubeFace(); - _oldActiveMipmapLevel = this._renderer.getActiveMipmapLevel(); - - this._setSize(256); - - const cubeUVRenderTarget = this._allocateTargets(); - cubeUVRenderTarget.depthBuffer = true; - - this._sceneToCubeUV(scene, near, far, cubeUVRenderTarget); - - if (sigma > 0) { - this._blur(cubeUVRenderTarget, 0, 0, sigma); - } - - this._applyPMREM(cubeUVRenderTarget); - - this._cleanup(cubeUVRenderTarget); - - return cubeUVRenderTarget; - } - - /** - * Generates a PMREM from an equirectangular texture, which can be either LDR - * or HDR. The ideal input image size is 1k (1024 x 512), - * as this matches best with the 256 x 256 cubemap output. - */ - fromEquirectangular(equirectangular, renderTarget = null) { - return this._fromTexture(equirectangular, renderTarget); - } - - /** - * Generates a PMREM from an cubemap texture, which can be either LDR - * or HDR. The ideal input cube size is 256 x 256, - * as this matches best with the 256 x 256 cubemap output. - */ - fromCubemap(cubemap, renderTarget = null) { - return this._fromTexture(cubemap, renderTarget); - } - - /** - * Pre-compiles the cubemap shader. You can get faster start-up by invoking this method during - * your texture's network fetch for increased concurrency. - */ - compileCubemapShader() { - if (this._cubemapMaterial === null) { - this._cubemapMaterial = _getCubemapMaterial(); - this._compileMaterial(this._cubemapMaterial); - } - } - - /** - * Pre-compiles the equirectangular shader. You can get faster start-up by invoking this method during - * your texture's network fetch for increased concurrency. - */ - compileEquirectangularShader() { - if (this._equirectMaterial === null) { - this._equirectMaterial = _getEquirectMaterial(); - this._compileMaterial(this._equirectMaterial); - } - } - - /** - * Disposes of the PMREMGenerator's internal memory. Note that PMREMGenerator is a static class, - * so you should not need more than one PMREMGenerator object. If you do, calling dispose() on - * one of them will cause any others to also become unusable. - */ - dispose() { - this._dispose(); - - if (this._cubemapMaterial !== null) this._cubemapMaterial.dispose(); - if (this._equirectMaterial !== null) this._equirectMaterial.dispose(); - if (this._backgroundBox !== null) { - this._backgroundBox.geometry.dispose(); - this._backgroundBox.material.dispose(); - } - } - - // private interface - - _setSize(cubeSize) { - this._lodMax = Math.floor(Math.log2(cubeSize)); - this._cubeSize = Math.pow(2, this._lodMax); - } - - _dispose() { - if (this._blurMaterial !== null) this._blurMaterial.dispose(); - - if (this._pingPongRenderTarget !== null) this._pingPongRenderTarget.dispose(); - - for (let i = 0; i < this._lodPlanes.length; i++) { - this._lodPlanes[i].dispose(); - } - } - - _cleanup(outputTarget) { - this._renderer.setRenderTarget(_oldTarget, _oldActiveCubeFace, _oldActiveMipmapLevel); - outputTarget.scissorTest = false; - _setViewport(outputTarget, 0, 0, outputTarget.width, outputTarget.height); - } - - _fromTexture(texture, renderTarget) { - if (texture.mapping === CubeReflectionMapping || texture.mapping === CubeRefractionMapping) { - this._setSize(texture.image.length === 0 ? 16 : texture.image[0].width || texture.image[0].image.width); - } else { - // Equirectangular - - this._setSize(texture.image.width / 4); - } - - _oldTarget = this._renderer.getRenderTarget(); - _oldActiveCubeFace = this._renderer.getActiveCubeFace(); - _oldActiveMipmapLevel = this._renderer.getActiveMipmapLevel(); - - const cubeUVRenderTarget = renderTarget || this._allocateTargets(); - this._textureToCubeUV(texture, cubeUVRenderTarget); - this._applyPMREM(cubeUVRenderTarget); - this._cleanup(cubeUVRenderTarget); - - return cubeUVRenderTarget; - } - - _allocateTargets() { - const width = 3 * Math.max(this._cubeSize, 16 * 7); - const height = 4 * this._cubeSize; - - const params = { - magFilter: LinearFilter, - minFilter: LinearFilter, - generateMipmaps: false, - type: HalfFloatType, - format: RGBAFormat, - colorSpace: LinearSRGBColorSpace, - //depthBuffer: false - }; - - const cubeUVRenderTarget = _createRenderTarget(width, height, params); - - if ( - this._pingPongRenderTarget === null || - this._pingPongRenderTarget.width !== width || - this._pingPongRenderTarget.height !== height - ) { - if (this._pingPongRenderTarget !== null) { - this._dispose(); - } - - this._pingPongRenderTarget = _createRenderTarget(width, height, params); - - const { _lodMax } = this; - ({ - sizeLods: this._sizeLods, - lodPlanes: this._lodPlanes, - sigmas: this._sigmas, - lodMeshes: this._lodMeshes, - } = _createPlanes(_lodMax)); - - this._blurMaterial = _getBlurShader(_lodMax, width, height); - } - - return cubeUVRenderTarget; - } - - _compileMaterial(material) { - const tmpMesh = this._lodMeshes[0]; - tmpMesh.material = material; - - this._renderer.compile(tmpMesh, _flatCamera); - } - - _sceneToCubeUV(scene, near, far, cubeUVRenderTarget) { - const cubeCamera = _cubeCamera; - cubeCamera.near = near; - cubeCamera.far = far; - - // px, py, pz, nx, ny, nz - const upSign = [-1, 1, -1, -1, -1, -1]; - const forwardSign = [1, 1, 1, -1, -1, -1]; - - const renderer = this._renderer; - - const originalAutoClear = renderer.autoClear; - - renderer.getClearColor(_clearColor); - - renderer.autoClear = false; - - let backgroundBox = this._backgroundBox; - - if (backgroundBox === null) { - const backgroundMaterial = new MeshBasicMaterial({ - name: 'PMREM.Background', - side: BackSide, - depthWrite: false, - depthTest: false, - }); - - backgroundBox = new Mesh(new BoxGeometry(), backgroundMaterial); - } - - let useSolidColor = false; - const background = scene.background; - - if (background) { - if (background.isColor) { - backgroundBox.material.color.copy(background); - scene.background = null; - useSolidColor = true; - } - } else { - backgroundBox.material.color.copy(_clearColor); - useSolidColor = true; - } - - renderer.setRenderTarget(cubeUVRenderTarget); - - renderer.clear(); - - if (useSolidColor) { - renderer.render(backgroundBox, cubeCamera); - } - - for (let i = 0; i < 6; i++) { - const col = i % 3; - - if (col === 0) { - cubeCamera.up.set(0, upSign[i], 0); - cubeCamera.lookAt(forwardSign[i], 0, 0); - } else if (col === 1) { - cubeCamera.up.set(0, 0, upSign[i]); - cubeCamera.lookAt(0, forwardSign[i], 0); - } else { - cubeCamera.up.set(0, upSign[i], 0); - cubeCamera.lookAt(0, 0, forwardSign[i]); - } - - const size = this._cubeSize; - - _setViewport(cubeUVRenderTarget, col * size, i > 2 ? size : 0, size, size); - - renderer.render(scene, cubeCamera); - } - - renderer.autoClear = originalAutoClear; - scene.background = background; - } - - _textureToCubeUV(texture, cubeUVRenderTarget) { - const renderer = this._renderer; - - const isCubeTexture = texture.mapping === CubeReflectionMapping || texture.mapping === CubeRefractionMapping; - - if (isCubeTexture) { - if (this._cubemapMaterial === null) { - this._cubemapMaterial = _getCubemapMaterial(texture); - } - } else { - if (this._equirectMaterial === null) { - this._equirectMaterial = _getEquirectMaterial(texture); - } - } - - const material = isCubeTexture ? this._cubemapMaterial : this._equirectMaterial; - material.fragmentNode.value = texture; - - const mesh = this._lodMeshes[0]; - mesh.material = material; - - const size = this._cubeSize; - - _setViewport(cubeUVRenderTarget, 0, 0, 3 * size, 2 * size); - - renderer.setRenderTarget(cubeUVRenderTarget); - renderer.render(mesh, _flatCamera); - } - - _applyPMREM(cubeUVRenderTarget) { - const renderer = this._renderer; - const autoClear = renderer.autoClear; - renderer.autoClear = false; - const n = this._lodPlanes.length; - - for (let i = 1; i < n; i++) { - const sigma = Math.sqrt(this._sigmas[i] * this._sigmas[i] - this._sigmas[i - 1] * this._sigmas[i - 1]); - - const poleAxis = _axisDirections[(n - i - 1) % _axisDirections.length]; - - this._blur(cubeUVRenderTarget, i - 1, i, sigma, poleAxis); - } - - renderer.autoClear = autoClear; - } - - /** - * This is a two-pass Gaussian blur for a cubemap. Normally this is done - * vertically and horizontally, but this breaks down on a cube. Here we apply - * the blur latitudinally (around the poles), and then longitudinally (towards - * the poles) to approximate the orthogonally-separable blur. It is least - * accurate at the poles, but still does a decent job. - */ - _blur(cubeUVRenderTarget, lodIn, lodOut, sigma, poleAxis) { - const pingPongRenderTarget = this._pingPongRenderTarget; - - this._halfBlur(cubeUVRenderTarget, pingPongRenderTarget, lodIn, lodOut, sigma, 'latitudinal', poleAxis); - - this._halfBlur(pingPongRenderTarget, cubeUVRenderTarget, lodOut, lodOut, sigma, 'longitudinal', poleAxis); - } - - _halfBlur(targetIn, targetOut, lodIn, lodOut, sigmaRadians, direction, poleAxis) { - const renderer = this._renderer; - const blurMaterial = this._blurMaterial; - - if (direction !== 'latitudinal' && direction !== 'longitudinal') { - console.error('blur direction must be either latitudinal or longitudinal!'); - } - - // Number of standard deviations at which to cut off the discrete approximation. - const STANDARD_DEVIATIONS = 3; - - const blurMesh = this._lodMeshes[lodOut]; - blurMesh.material = blurMaterial; - - const blurUniforms = blurMaterial.uniforms; - - const pixels = this._sizeLods[lodIn] - 1; - const radiansPerPixel = isFinite(sigmaRadians) ? Math.PI / (2 * pixels) : (2 * Math.PI) / (2 * MAX_SAMPLES - 1); - const sigmaPixels = sigmaRadians / radiansPerPixel; - const samples = isFinite(sigmaRadians) ? 1 + Math.floor(STANDARD_DEVIATIONS * sigmaPixels) : MAX_SAMPLES; - - if (samples > MAX_SAMPLES) { - console.warn( - `sigmaRadians, ${sigmaRadians}, is too large and will clip, as it requested ${ - samples - } samples when the maximum is set to ${MAX_SAMPLES}`, - ); - } - - const weights = []; - let sum = 0; - - for (let i = 0; i < MAX_SAMPLES; ++i) { - const x = i / sigmaPixels; - const weight = Math.exp((-x * x) / 2); - weights.push(weight); - - if (i === 0) { - sum += weight; - } else if (i < samples) { - sum += 2 * weight; - } - } - - for (let i = 0; i < weights.length; i++) { - weights[i] = weights[i] / sum; - } - - targetIn.texture.frame = (targetIn.texture.frame || 0) + 1; - - blurUniforms.envMap.value = targetIn.texture; - blurUniforms.samples.value = samples; - blurUniforms.weights.array = weights; - blurUniforms.latitudinal.value = direction === 'latitudinal' ? 1 : 0; - - if (poleAxis) { - blurUniforms.poleAxis.value = poleAxis; - } - - const { _lodMax } = this; - blurUniforms.dTheta.value = radiansPerPixel; - blurUniforms.mipInt.value = _lodMax - lodIn; - - const outputSize = this._sizeLods[lodOut]; - const x = 3 * outputSize * (lodOut > _lodMax - LOD_MIN ? lodOut - _lodMax + LOD_MIN : 0); - const y = 4 * (this._cubeSize - outputSize); - - _setViewport(targetOut, x, y, 3 * outputSize, 2 * outputSize); - renderer.setRenderTarget(targetOut); - renderer.render(blurMesh, _flatCamera); - } -} - -function _createPlanes(lodMax) { - const lodPlanes = []; - const sizeLods = []; - const sigmas = []; - const lodMeshes = []; - - let lod = lodMax; - - const totalLods = lodMax - LOD_MIN + 1 + EXTRA_LOD_SIGMA.length; - - for (let i = 0; i < totalLods; i++) { - const sizeLod = Math.pow(2, lod); - sizeLods.push(sizeLod); - let sigma = 1.0 / sizeLod; - - if (i > lodMax - LOD_MIN) { - sigma = EXTRA_LOD_SIGMA[i - lodMax + LOD_MIN - 1]; - } else if (i === 0) { - sigma = 0; - } - - sigmas.push(sigma); - - const texelSize = 1.0 / (sizeLod - 2); - const min = -texelSize; - const max = 1 + texelSize; - const uv1 = [min, min, max, min, max, max, min, min, max, max, min, max]; - - const cubeFaces = 6; - const vertices = 6; - const positionSize = 3; - const uvSize = 2; - const faceIndexSize = 1; - - const position = new Float32Array(positionSize * vertices * cubeFaces); - const uv = new Float32Array(uvSize * vertices * cubeFaces); - const faceIndex = new Float32Array(faceIndexSize * vertices * cubeFaces); - - for (let face = 0; face < cubeFaces; face++) { - const x = ((face % 3) * 2) / 3 - 1; - const y = face > 2 ? 0 : -1; - const coordinates = [ - x, - y, - 0, - x + 2 / 3, - y, - 0, - x + 2 / 3, - y + 1, - 0, - x, - y, - 0, - x + 2 / 3, - y + 1, - 0, - x, - y + 1, - 0, - ]; - - const faceIdx = _faceLib[face]; - position.set(coordinates, positionSize * vertices * faceIdx); - uv.set(uv1, uvSize * vertices * faceIdx); - const fill = [faceIdx, faceIdx, faceIdx, faceIdx, faceIdx, faceIdx]; - faceIndex.set(fill, faceIndexSize * vertices * faceIdx); - } - - const planes = new BufferGeometry(); - planes.setAttribute('position', new BufferAttribute(position, positionSize)); - planes.setAttribute('uv', new BufferAttribute(uv, uvSize)); - planes.setAttribute('faceIndex', new BufferAttribute(faceIndex, faceIndexSize)); - lodPlanes.push(planes); - lodMeshes.push(new Mesh(planes, null)); - - if (lod > LOD_MIN) { - lod--; - } - } - - return { lodPlanes, sizeLods, sigmas, lodMeshes }; -} - -function _createRenderTarget(width, height, params) { - const cubeUVRenderTarget = new RenderTarget(width, height, params); - cubeUVRenderTarget.texture.mapping = CubeUVReflectionMapping; - cubeUVRenderTarget.texture.name = 'PMREM.cubeUv'; - cubeUVRenderTarget.texture.isPMREMTexture = true; - cubeUVRenderTarget.scissorTest = true; - return cubeUVRenderTarget; -} - -function _setViewport(target, x, y, width, height) { - const viewY = target.height - height - y; - - target.viewport.set(x, viewY, width, height); - target.scissor.set(x, viewY, width, height); -} - -function _getMaterial() { - const material = new NodeMaterial(); - material.depthTest = false; - material.depthWrite = false; - material.blending = NoBlending; - - return material; -} - -function _getBlurShader(lodMax, width, height) { - const weights = uniforms(new Array(MAX_SAMPLES).fill(0)); - const poleAxis = uniform(new Vector3(0, 1, 0)); - const dTheta = uniform(0); - const n = float(MAX_SAMPLES); - const latitudinal = uniform(0); // false, bool - const samples = uniform(1); // int - const envMap = texture(null); - const mipInt = uniform(0); // int - const CUBEUV_TEXEL_WIDTH = float(1 / width); - const CUBEUV_TEXEL_HEIGHT = float(1 / height); - const CUBEUV_MAX_MIP = float(lodMax); - - const materialUniforms = { - n, - latitudinal, - weights, - poleAxis, - outputDirection, - dTheta, - samples, - envMap, - mipInt, - CUBEUV_TEXEL_WIDTH, - CUBEUV_TEXEL_HEIGHT, - CUBEUV_MAX_MIP, - }; - - const material = _getMaterial(); - material.uniforms = materialUniforms; // TODO: Move to outside of the material - material.fragmentNode = blur({ ...materialUniforms, latitudinal: latitudinal.equal(1) }); - - return material; -} - -function _getCubemapMaterial(envTexture) { - const material = _getMaterial(); - material.fragmentNode = cubeTexture(envTexture, outputDirection); - - return material; -} - -function _getEquirectMaterial(envTexture) { - const material = _getMaterial(); - material.fragmentNode = texture(envTexture, equirectUV(outputDirection), 0); - - return material; -} - -export default PMREMGenerator; diff --git a/examples-jsm/examples/renderers/common/nodes/NodeBuilderState.ts b/examples-jsm/examples/renderers/common/nodes/NodeBuilderState.ts deleted file mode 100644 index e94d965f..00000000 --- a/examples-jsm/examples/renderers/common/nodes/NodeBuilderState.ts +++ /dev/null @@ -1,55 +0,0 @@ -import BindGroup from '../BindGroup.js'; - -class NodeBuilderState { - constructor( - vertexShader, - fragmentShader, - computeShader, - nodeAttributes, - bindings, - updateNodes, - updateBeforeNodes, - updateAfterNodes, - instanceBindGroups = true, - transforms = [], - ) { - this.vertexShader = vertexShader; - this.fragmentShader = fragmentShader; - this.computeShader = computeShader; - this.transforms = transforms; - - this.nodeAttributes = nodeAttributes; - this.bindings = bindings; - - this.updateNodes = updateNodes; - this.updateBeforeNodes = updateBeforeNodes; - this.updateAfterNodes = updateAfterNodes; - - this.instanceBindGroups = instanceBindGroups; - - this.usedTimes = 0; - } - - createBindings() { - const bindings = []; - - for (const instanceGroup of this.bindings) { - const shared = this.instanceBindGroups && instanceGroup.bindings[0].groupNode.shared; - - if (shared !== true) { - const bindingsGroup = new BindGroup(instanceGroup.name); - bindings.push(bindingsGroup); - - for (const instanceBinding of instanceGroup.bindings) { - bindingsGroup.bindings.push(instanceBinding.clone()); - } - } else { - bindings.push(instanceGroup); - } - } - - return bindings; - } -} - -export default NodeBuilderState; diff --git a/examples-jsm/examples/renderers/common/nodes/NodeUniform.ts b/examples-jsm/examples/renderers/common/nodes/NodeUniform.ts deleted file mode 100644 index 659f5a82..00000000 --- a/examples-jsm/examples/renderers/common/nodes/NodeUniform.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { - NumberUniform, - Vector2Uniform, - Vector3Uniform, - Vector4Uniform, - ColorUniform, - Matrix3Uniform, - Matrix4Uniform, -} from '../Uniform.js'; - -class NumberNodeUniform extends NumberUniform { - constructor(nodeUniform) { - super(nodeUniform.name, nodeUniform.value); - - this.nodeUniform = nodeUniform; - } - - getValue() { - return this.nodeUniform.value; - } -} - -class Vector2NodeUniform extends Vector2Uniform { - constructor(nodeUniform) { - super(nodeUniform.name, nodeUniform.value); - - this.nodeUniform = nodeUniform; - } - - getValue() { - return this.nodeUniform.value; - } -} - -class Vector3NodeUniform extends Vector3Uniform { - constructor(nodeUniform) { - super(nodeUniform.name, nodeUniform.value); - - this.nodeUniform = nodeUniform; - } - - getValue() { - return this.nodeUniform.value; - } -} - -class Vector4NodeUniform extends Vector4Uniform { - constructor(nodeUniform) { - super(nodeUniform.name, nodeUniform.value); - - this.nodeUniform = nodeUniform; - } - - getValue() { - return this.nodeUniform.value; - } -} - -class ColorNodeUniform extends ColorUniform { - constructor(nodeUniform) { - super(nodeUniform.name, nodeUniform.value); - - this.nodeUniform = nodeUniform; - } - - getValue() { - return this.nodeUniform.value; - } -} - -class Matrix3NodeUniform extends Matrix3Uniform { - constructor(nodeUniform) { - super(nodeUniform.name, nodeUniform.value); - - this.nodeUniform = nodeUniform; - } - - getValue() { - return this.nodeUniform.value; - } -} - -class Matrix4NodeUniform extends Matrix4Uniform { - constructor(nodeUniform) { - super(nodeUniform.name, nodeUniform.value); - - this.nodeUniform = nodeUniform; - } - - getValue() { - return this.nodeUniform.value; - } -} - -export { - NumberNodeUniform, - Vector2NodeUniform, - Vector3NodeUniform, - Vector4NodeUniform, - ColorNodeUniform, - Matrix3NodeUniform, - Matrix4NodeUniform, -}; diff --git a/examples-jsm/examples/renderers/common/nodes/NodeUniformsGroup.ts b/examples-jsm/examples/renderers/common/nodes/NodeUniformsGroup.ts deleted file mode 100644 index e7b389cd..00000000 --- a/examples-jsm/examples/renderers/common/nodes/NodeUniformsGroup.ts +++ /dev/null @@ -1,30 +0,0 @@ -import UniformsGroup from '../UniformsGroup.js'; - -let id = 0; - -class NodeUniformsGroup extends UniformsGroup { - constructor(name, groupNode) { - super(name); - - this.id = id++; - this.groupNode = groupNode; - - this.isNodeUniformsGroup = true; - } - - getNodes() { - const nodes = []; - - for (const uniform of this.uniforms) { - const node = uniform.nodeUniform.node; - - if (!node) throw new Error('NodeUniformsGroup: Uniform has no node.'); - - nodes.push(node); - } - - return nodes; - } -} - -export default NodeUniformsGroup; diff --git a/examples-jsm/examples/renderers/common/nodes/Nodes.ts b/examples-jsm/examples/renderers/common/nodes/Nodes.ts deleted file mode 100644 index db035f6d..00000000 --- a/examples-jsm/examples/renderers/common/nodes/Nodes.ts +++ /dev/null @@ -1,394 +0,0 @@ -import DataMap from '../DataMap.js'; -import ChainMap from '../ChainMap.js'; -import NodeBuilderState from './NodeBuilderState.js'; -import { - EquirectangularReflectionMapping, - EquirectangularRefractionMapping, - NoToneMapping, - SRGBColorSpace, -} from 'three'; -import { - NodeFrame, - vec4, - objectGroup, - renderGroup, - frameGroup, - cubeTexture, - texture, - rangeFog, - densityFog, - reference, - viewportBottomLeft, - normalWorld, - pmremTexture, - viewportTopLeft, -} from '../../../nodes/Nodes.js'; - -class Nodes extends DataMap { - constructor(renderer, backend) { - super(); - - this.renderer = renderer; - this.backend = backend; - this.nodeFrame = new NodeFrame(); - this.nodeBuilderCache = new Map(); - this.callHashCache = new ChainMap(); - this.groupsData = new ChainMap(); - } - - updateGroup(nodeUniformsGroup) { - const groupNode = nodeUniformsGroup.groupNode; - const name = groupNode.name; - - // objectGroup is every updated - - if (name === objectGroup.name) return true; - - // renderGroup is updated once per render/compute call - - if (name === renderGroup.name) { - const uniformsGroupData = this.get(nodeUniformsGroup); - const renderId = this.nodeFrame.renderId; - - if (uniformsGroupData.renderId !== renderId) { - uniformsGroupData.renderId = renderId; - - return true; - } - - return false; - } - - // frameGroup is updated once per frame - - if (name === frameGroup.name) { - const uniformsGroupData = this.get(nodeUniformsGroup); - const frameId = this.nodeFrame.frameId; - - if (uniformsGroupData.frameId !== frameId) { - uniformsGroupData.frameId = frameId; - - return true; - } - - return false; - } - - // other groups are updated just when groupNode.needsUpdate is true - - const groupChain = [groupNode, nodeUniformsGroup]; - - let groupData = this.groupsData.get(groupChain); - if (groupData === undefined) this.groupsData.set(groupChain, (groupData = {})); - - if (groupData.version !== groupNode.version) { - groupData.version = groupNode.version; - - return true; - } - - return false; - } - - getForRenderCacheKey(renderObject) { - return renderObject.initialCacheKey; - } - - getForRender(renderObject) { - const renderObjectData = this.get(renderObject); - - let nodeBuilderState = renderObjectData.nodeBuilderState; - - if (nodeBuilderState === undefined) { - const { nodeBuilderCache } = this; - - const cacheKey = this.getForRenderCacheKey(renderObject); - - nodeBuilderState = nodeBuilderCache.get(cacheKey); - - if (nodeBuilderState === undefined) { - const nodeBuilder = this.backend.createNodeBuilder(renderObject.object, this.renderer); - nodeBuilder.scene = renderObject.scene; - nodeBuilder.material = renderObject.material; - nodeBuilder.camera = renderObject.camera; - nodeBuilder.context.material = renderObject.material; - nodeBuilder.lightsNode = renderObject.lightsNode; - nodeBuilder.environmentNode = this.getEnvironmentNode(renderObject.scene); - nodeBuilder.fogNode = this.getFogNode(renderObject.scene); - nodeBuilder.clippingContext = renderObject.clippingContext; - nodeBuilder.build(); - - nodeBuilderState = this._createNodeBuilderState(nodeBuilder); - - nodeBuilderCache.set(cacheKey, nodeBuilderState); - } - - nodeBuilderState.usedTimes++; - - renderObjectData.nodeBuilderState = nodeBuilderState; - } - - return nodeBuilderState; - } - - delete(object) { - if (object.isRenderObject) { - const nodeBuilderState = this.get(object).nodeBuilderState; - nodeBuilderState.usedTimes--; - - if (nodeBuilderState.usedTimes === 0) { - this.nodeBuilderCache.delete(this.getForRenderCacheKey(object)); - } - } - - return super.delete(object); - } - - getForCompute(computeNode) { - const computeData = this.get(computeNode); - - let nodeBuilderState = computeData.nodeBuilderState; - - if (nodeBuilderState === undefined) { - const nodeBuilder = this.backend.createNodeBuilder(computeNode, this.renderer); - nodeBuilder.build(); - - nodeBuilderState = this._createNodeBuilderState(nodeBuilder); - - computeData.nodeBuilderState = nodeBuilderState; - } - - return nodeBuilderState; - } - - _createNodeBuilderState(nodeBuilder) { - return new NodeBuilderState( - nodeBuilder.vertexShader, - nodeBuilder.fragmentShader, - nodeBuilder.computeShader, - nodeBuilder.getAttributesArray(), - nodeBuilder.getBindings(), - nodeBuilder.updateNodes, - nodeBuilder.updateBeforeNodes, - nodeBuilder.updateAfterNodes, - nodeBuilder.instanceBindGroups, - nodeBuilder.transforms, - ); - } - - getEnvironmentNode(scene) { - return scene.environmentNode || this.get(scene).environmentNode || null; - } - - getBackgroundNode(scene) { - return scene.backgroundNode || this.get(scene).backgroundNode || null; - } - - getFogNode(scene) { - return scene.fogNode || this.get(scene).fogNode || null; - } - - getCacheKey(scene, lightsNode) { - const chain = [scene, lightsNode]; - const callId = this.renderer.info.calls; - - let cacheKeyData = this.callHashCache.get(chain); - - if (cacheKeyData === undefined || cacheKeyData.callId !== callId) { - const environmentNode = this.getEnvironmentNode(scene); - const fogNode = this.getFogNode(scene); - - const cacheKey = []; - - if (lightsNode) cacheKey.push(lightsNode.getCacheKey()); - if (environmentNode) cacheKey.push(environmentNode.getCacheKey()); - if (fogNode) cacheKey.push(fogNode.getCacheKey()); - - cacheKeyData = { - callId, - cacheKey: cacheKey.join(','), - }; - - this.callHashCache.set(chain, cacheKeyData); - } - - return cacheKeyData.cacheKey; - } - - updateScene(scene) { - this.updateEnvironment(scene); - this.updateFog(scene); - this.updateBackground(scene); - } - - get isToneMappingState() { - return this.renderer.getRenderTarget() ? false : true; - } - - updateBackground(scene) { - const sceneData = this.get(scene); - const background = scene.background; - - if (background) { - if (sceneData.background !== background) { - let backgroundNode = null; - - if ( - background.isCubeTexture === true || - background.mapping === EquirectangularReflectionMapping || - background.mapping === EquirectangularRefractionMapping - ) { - backgroundNode = pmremTexture(background, normalWorld); - } else if (background.isTexture === true) { - backgroundNode = texture(background, viewportBottomLeft).setUpdateMatrix(true); - } else if (background.isColor !== true) { - console.error('WebGPUNodes: Unsupported background configuration.', background); - } - - sceneData.backgroundNode = backgroundNode; - sceneData.background = background; - } - } else if (sceneData.backgroundNode) { - delete sceneData.backgroundNode; - delete sceneData.background; - } - } - - updateFog(scene) { - const sceneData = this.get(scene); - const fog = scene.fog; - - if (fog) { - if (sceneData.fog !== fog) { - let fogNode = null; - - if (fog.isFogExp2) { - fogNode = densityFog(reference('color', 'color', fog), reference('density', 'float', fog)); - } else if (fog.isFog) { - fogNode = rangeFog( - reference('color', 'color', fog), - reference('near', 'float', fog), - reference('far', 'float', fog), - ); - } else { - console.error('WebGPUNodes: Unsupported fog configuration.', fog); - } - - sceneData.fogNode = fogNode; - sceneData.fog = fog; - } - } else { - delete sceneData.fogNode; - delete sceneData.fog; - } - } - - updateEnvironment(scene) { - const sceneData = this.get(scene); - const environment = scene.environment; - - if (environment) { - if (sceneData.environment !== environment) { - let environmentNode = null; - - if (environment.isCubeTexture === true) { - environmentNode = cubeTexture(environment); - } else if (environment.isTexture === true) { - environmentNode = texture(environment); - } else { - console.error('Nodes: Unsupported environment configuration.', environment); - } - - sceneData.environmentNode = environmentNode; - sceneData.environment = environment; - } - } else if (sceneData.environmentNode) { - delete sceneData.environmentNode; - delete sceneData.environment; - } - } - - getNodeFrame(renderer = this.renderer, scene = null, object = null, camera = null, material = null) { - const nodeFrame = this.nodeFrame; - nodeFrame.renderer = renderer; - nodeFrame.scene = scene; - nodeFrame.object = object; - nodeFrame.camera = camera; - nodeFrame.material = material; - - return nodeFrame; - } - - getNodeFrameForRender(renderObject) { - return this.getNodeFrame( - renderObject.renderer, - renderObject.scene, - renderObject.object, - renderObject.camera, - renderObject.material, - ); - } - - getOutputNode(outputTexture) { - let output = texture(outputTexture, viewportTopLeft); - - if (this.isToneMappingState) { - if (this.renderer.toneMappingNode) { - output = vec4(this.renderer.toneMappingNode.context({ color: output.rgb }), output.a); - } else if (this.renderer.toneMapping !== NoToneMapping) { - output = output.toneMapping(this.renderer.toneMapping); - } - } - - if (this.renderer.currentColorSpace === SRGBColorSpace) { - output = output.linearToColorSpace(this.renderer.currentColorSpace); - } - - return output; - } - - updateBefore(renderObject) { - const nodeFrame = this.getNodeFrameForRender(renderObject); - const nodeBuilder = renderObject.getNodeBuilderState(); - - for (const node of nodeBuilder.updateBeforeNodes) { - nodeFrame.updateBeforeNode(node); - } - } - - updateAfter(renderObject) { - const nodeFrame = this.getNodeFrameForRender(renderObject); - const nodeBuilder = renderObject.getNodeBuilderState(); - - for (const node of nodeBuilder.updateAfterNodes) { - nodeFrame.updateAfterNode(node); - } - } - - updateForCompute(computeNode) { - const nodeFrame = this.getNodeFrame(); - const nodeBuilder = this.getForCompute(computeNode); - - for (const node of nodeBuilder.updateNodes) { - nodeFrame.updateNode(node); - } - } - - updateForRender(renderObject) { - const nodeFrame = this.getNodeFrameForRender(renderObject); - const nodeBuilder = renderObject.getNodeBuilderState(); - - for (const node of nodeBuilder.updateNodes) { - nodeFrame.updateNode(node); - } - } - - dispose() { - super.dispose(); - - this.nodeFrame = new NodeFrame(); - this.nodeBuilderCache = new Map(); - } -} - -export default Nodes; diff --git a/examples-jsm/examples/renderers/webgl/WebGLBackend.ts b/examples-jsm/examples/renderers/webgl/WebGLBackend.ts deleted file mode 100644 index f32af434..00000000 --- a/examples-jsm/examples/renderers/webgl/WebGLBackend.ts +++ /dev/null @@ -1,1268 +0,0 @@ -import { WebGLCoordinateSystem } from 'three'; - -import GLSLNodeBuilder from './nodes/GLSLNodeBuilder.js'; -import Backend from '../common/Backend.js'; - -import WebGLAttributeUtils from './utils/WebGLAttributeUtils.js'; -import WebGLState from './utils/WebGLState.js'; -import WebGLUtils from './utils/WebGLUtils.js'; -import WebGLTextureUtils from './utils/WebGLTextureUtils.js'; -import WebGLExtensions from './utils/WebGLExtensions.js'; -import WebGLCapabilities from './utils/WebGLCapabilities.js'; -import { GLFeatureName } from './utils/WebGLConstants.js'; -import { WebGLBufferRenderer } from './WebGLBufferRenderer.js'; - -// - -class WebGLBackend extends Backend { - constructor(parameters = {}) { - super(parameters); - - this.isWebGLBackend = true; - } - - init(renderer) { - super.init(renderer); - - // - - const parameters = this.parameters; - - const glContext = - parameters.context !== undefined ? parameters.context : renderer.domElement.getContext('webgl2'); - - this.gl = glContext; - - this.extensions = new WebGLExtensions(this); - this.capabilities = new WebGLCapabilities(this); - this.attributeUtils = new WebGLAttributeUtils(this); - this.textureUtils = new WebGLTextureUtils(this); - this.bufferRenderer = new WebGLBufferRenderer(this); - - this.state = new WebGLState(this); - this.utils = new WebGLUtils(this); - - this.vaoCache = {}; - this.transformFeedbackCache = {}; - this.discard = false; - this.trackTimestamp = parameters.trackTimestamp === true; - - this.extensions.get('EXT_color_buffer_float'); - this.disjoint = this.extensions.get('EXT_disjoint_timer_query_webgl2'); - this.parallel = this.extensions.get('KHR_parallel_shader_compile'); - this._currentContext = null; - } - - get coordinateSystem() { - return WebGLCoordinateSystem; - } - - async getArrayBufferAsync(attribute) { - return await this.attributeUtils.getArrayBufferAsync(attribute); - } - - initTimestampQuery(renderContext) { - if (!this.disjoint || !this.trackTimestamp) return; - - const renderContextData = this.get(renderContext); - - if (this.queryRunning) { - if (!renderContextData.queryQueue) renderContextData.queryQueue = []; - renderContextData.queryQueue.push(renderContext); - return; - } - - if (renderContextData.activeQuery) { - this.gl.endQuery(this.disjoint.TIME_ELAPSED_EXT); - renderContextData.activeQuery = null; - } - - renderContextData.activeQuery = this.gl.createQuery(); - - if (renderContextData.activeQuery !== null) { - this.gl.beginQuery(this.disjoint.TIME_ELAPSED_EXT, renderContextData.activeQuery); - this.queryRunning = true; - } - } - - // timestamp utils - - prepareTimestampBuffer(renderContext) { - if (!this.disjoint || !this.trackTimestamp) return; - - const renderContextData = this.get(renderContext); - - if (renderContextData.activeQuery) { - this.gl.endQuery(this.disjoint.TIME_ELAPSED_EXT); - - if (!renderContextData.gpuQueries) renderContextData.gpuQueries = []; - renderContextData.gpuQueries.push({ query: renderContextData.activeQuery }); - renderContextData.activeQuery = null; - this.queryRunning = false; - - if (renderContextData.queryQueue && renderContextData.queryQueue.length > 0) { - const nextRenderContext = renderContextData.queryQueue.shift(); - this.initTimestampQuery(nextRenderContext); - } - } - } - - async resolveTimestampAsync(renderContext, type = 'render') { - if (!this.disjoint || !this.trackTimestamp) return; - - const renderContextData = this.get(renderContext); - - if (!renderContextData.gpuQueries) renderContextData.gpuQueries = []; - - for (let i = 0; i < renderContextData.gpuQueries.length; i++) { - const queryInfo = renderContextData.gpuQueries[i]; - const available = this.gl.getQueryParameter(queryInfo.query, this.gl.QUERY_RESULT_AVAILABLE); - const disjoint = this.gl.getParameter(this.disjoint.GPU_DISJOINT_EXT); - - if (available && !disjoint) { - const elapsed = this.gl.getQueryParameter(queryInfo.query, this.gl.QUERY_RESULT); - const duration = Number(elapsed) / 1000000; // Convert nanoseconds to milliseconds - this.gl.deleteQuery(queryInfo.query); - renderContextData.gpuQueries.splice(i, 1); // Remove the processed query - i--; - this.renderer.info.updateTimestamp(type, duration); - } - } - } - - getContext() { - return this.gl; - } - - beginRender(renderContext) { - const { gl } = this; - const renderContextData = this.get(renderContext); - - // - - // - - this.initTimestampQuery(renderContext); - - renderContextData.previousContext = this._currentContext; - this._currentContext = renderContext; - - this._setFramebuffer(renderContext); - - this.clear( - renderContext.clearColor, - renderContext.clearDepth, - renderContext.clearStencil, - renderContext, - false, - ); - - // - if (renderContext.viewport) { - this.updateViewport(renderContext); - } else { - gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); - } - - if (renderContext.scissor) { - const { x, y, width, height } = renderContext.scissorValue; - - gl.scissor(x, y, width, height); - } - - const occlusionQueryCount = renderContext.occlusionQueryCount; - - if (occlusionQueryCount > 0) { - // Get a reference to the array of objects with queries. The renderContextData property - // can be changed by another render pass before the async reading of all previous queries complete - renderContextData.currentOcclusionQueries = renderContextData.occlusionQueries; - renderContextData.currentOcclusionQueryObjects = renderContextData.occlusionQueryObjects; - - renderContextData.lastOcclusionObject = null; - renderContextData.occlusionQueries = new Array(occlusionQueryCount); - renderContextData.occlusionQueryObjects = new Array(occlusionQueryCount); - renderContextData.occlusionQueryIndex = 0; - } - } - - finishRender(renderContext) { - const { gl, state } = this; - const renderContextData = this.get(renderContext); - const previousContext = renderContextData.previousContext; - - const textures = renderContext.textures; - - if (textures !== null) { - for (let i = 0; i < textures.length; i++) { - const texture = textures[i]; - - if (texture.generateMipmaps) { - this.generateMipmaps(texture); - } - } - } - - this._currentContext = previousContext; - - if (renderContext.textures !== null && renderContext.renderTarget) { - const renderTargetContextData = this.get(renderContext.renderTarget); - - const { samples } = renderContext.renderTarget; - const fb = renderTargetContextData.framebuffer; - - const mask = gl.COLOR_BUFFER_BIT; - - if (samples > 0) { - const msaaFrameBuffer = renderTargetContextData.msaaFrameBuffer; - - const textures = renderContext.textures; - - state.bindFramebuffer(gl.READ_FRAMEBUFFER, msaaFrameBuffer); - state.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fb); - - for (let i = 0; i < textures.length; i++) { - // TODO Add support for MRT - - gl.blitFramebuffer( - 0, - 0, - renderContext.width, - renderContext.height, - 0, - 0, - renderContext.width, - renderContext.height, - mask, - gl.NEAREST, - ); - - gl.invalidateFramebuffer(gl.READ_FRAMEBUFFER, renderTargetContextData.invalidationArray); - } - } - } - - if (previousContext !== null) { - this._setFramebuffer(previousContext); - - if (previousContext.viewport) { - this.updateViewport(previousContext); - } else { - const gl = this.gl; - - gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); - } - } - - const occlusionQueryCount = renderContext.occlusionQueryCount; - - if (occlusionQueryCount > 0) { - const renderContextData = this.get(renderContext); - - if (occlusionQueryCount > renderContextData.occlusionQueryIndex) { - const { gl } = this; - - gl.endQuery(gl.ANY_SAMPLES_PASSED); - } - - this.resolveOccludedAsync(renderContext); - } - - this.prepareTimestampBuffer(renderContext); - } - - resolveOccludedAsync(renderContext) { - const renderContextData = this.get(renderContext); - - // handle occlusion query results - - const { currentOcclusionQueries, currentOcclusionQueryObjects } = renderContextData; - - if (currentOcclusionQueries && currentOcclusionQueryObjects) { - const occluded = new WeakSet(); - const { gl } = this; - - renderContextData.currentOcclusionQueryObjects = null; - renderContextData.currentOcclusionQueries = null; - - const check = () => { - let completed = 0; - - // check all queries and requeue as appropriate - for (let i = 0; i < currentOcclusionQueries.length; i++) { - const query = currentOcclusionQueries[i]; - - if (query === null) continue; - - if (gl.getQueryParameter(query, gl.QUERY_RESULT_AVAILABLE)) { - if (gl.getQueryParameter(query, gl.QUERY_RESULT) > 0) - occluded.add(currentOcclusionQueryObjects[i]); - - currentOcclusionQueries[i] = null; - gl.deleteQuery(query); - - completed++; - } - } - - if (completed < currentOcclusionQueries.length) { - requestAnimationFrame(check); - } else { - renderContextData.occluded = occluded; - } - }; - - check(); - } - } - - isOccluded(renderContext, object) { - const renderContextData = this.get(renderContext); - - return renderContextData.occluded && renderContextData.occluded.has(object); - } - - updateViewport(renderContext) { - const gl = this.gl; - const { x, y, width, height } = renderContext.viewportValue; - - gl.viewport(x, y, width, height); - } - - setScissorTest(boolean) { - const gl = this.gl; - - if (boolean) { - gl.enable(gl.SCISSOR_TEST); - } else { - gl.disable(gl.SCISSOR_TEST); - } - } - - clear(color, depth, stencil, descriptor = null, setFrameBuffer = true) { - const { gl } = this; - - if (descriptor === null) { - descriptor = { - textures: null, - clearColorValue: this.getClearColor(), - }; - } - - // - - let clear = 0; - - if (color) clear |= gl.COLOR_BUFFER_BIT; - if (depth) clear |= gl.DEPTH_BUFFER_BIT; - if (stencil) clear |= gl.STENCIL_BUFFER_BIT; - - if (clear !== 0) { - const clearColor = descriptor.clearColorValue || this.getClearColor(); - - if (depth) this.state.setDepthMask(true); - - if (descriptor.textures === null) { - gl.clearColor(clearColor.r, clearColor.g, clearColor.b, clearColor.a); - gl.clear(clear); - } else { - if (setFrameBuffer) this._setFramebuffer(descriptor); - - if (color) { - for (let i = 0; i < descriptor.textures.length; i++) { - gl.clearBufferfv(gl.COLOR, i, [clearColor.r, clearColor.g, clearColor.b, clearColor.a]); - } - } - - if (depth && stencil) { - gl.clearBufferfi(gl.DEPTH_STENCIL, 0, 1, 0); - } else if (depth) { - gl.clearBufferfv(gl.DEPTH, 0, [1.0]); - } else if (stencil) { - gl.clearBufferiv(gl.STENCIL, 0, [0]); - } - } - } - } - - beginCompute(computeGroup) { - const gl = this.gl; - - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - this.initTimestampQuery(computeGroup); - } - - compute(computeGroup, computeNode, bindings, pipeline) { - const gl = this.gl; - - if (!this.discard) { - // required here to handle async behaviour of render.compute() - gl.enable(gl.RASTERIZER_DISCARD); - this.discard = true; - } - - const { programGPU, transformBuffers, attributes } = this.get(pipeline); - - const vaoKey = this._getVaoKey(null, attributes); - - const vaoGPU = this.vaoCache[vaoKey]; - - if (vaoGPU === undefined) { - this._createVao(null, attributes); - } else { - gl.bindVertexArray(vaoGPU); - } - - gl.useProgram(programGPU); - - this._bindUniforms(bindings); - - const transformFeedbackGPU = this._getTransformFeedback(transformBuffers); - - gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, transformFeedbackGPU); - gl.beginTransformFeedback(gl.POINTS); - - if (attributes[0].isStorageInstancedBufferAttribute) { - gl.drawArraysInstanced(gl.POINTS, 0, 1, computeNode.count); - } else { - gl.drawArrays(gl.POINTS, 0, computeNode.count); - } - - gl.endTransformFeedback(); - gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null); - - // switch active buffers - - for (let i = 0; i < transformBuffers.length; i++) { - const dualAttributeData = transformBuffers[i]; - - if (dualAttributeData.pbo) { - this.textureUtils.copyBufferToTexture(dualAttributeData.transformBuffer, dualAttributeData.pbo); - } - - dualAttributeData.switchBuffers(); - } - } - - finishCompute(computeGroup) { - const gl = this.gl; - - this.discard = false; - - gl.disable(gl.RASTERIZER_DISCARD); - - this.prepareTimestampBuffer(computeGroup); - } - - draw(renderObject /*, info*/) { - const { object, pipeline, material, context } = renderObject; - const { programGPU } = this.get(pipeline); - - const { gl, state } = this; - - const contextData = this.get(context); - - // - - this._bindUniforms(renderObject.getBindings()); - - const frontFaceCW = object.isMesh && object.matrixWorld.determinant() < 0; - - state.setMaterial(material, frontFaceCW); - - gl.useProgram(programGPU); - - // - - let vaoGPU = renderObject.staticVao; - - if (vaoGPU === undefined) { - const vaoKey = this._getVaoKey(renderObject.getIndex(), renderObject.getAttributes()); - - vaoGPU = this.vaoCache[vaoKey]; - - if (vaoGPU === undefined) { - let staticVao; - - ({ vaoGPU, staticVao } = this._createVao(renderObject.getIndex(), renderObject.getAttributes())); - - if (staticVao) renderObject.staticVao = vaoGPU; - } - } - - gl.bindVertexArray(vaoGPU); - - // - - const index = renderObject.getIndex(); - - const geometry = renderObject.geometry; - const drawRange = renderObject.drawRange; - const firstVertex = drawRange.start; - - // - - const lastObject = contextData.lastOcclusionObject; - - if (lastObject !== object && lastObject !== undefined) { - if (lastObject !== null && lastObject.occlusionTest === true) { - gl.endQuery(gl.ANY_SAMPLES_PASSED); - - contextData.occlusionQueryIndex++; - } - - if (object.occlusionTest === true) { - const query = gl.createQuery(); - - gl.beginQuery(gl.ANY_SAMPLES_PASSED, query); - - contextData.occlusionQueries[contextData.occlusionQueryIndex] = query; - contextData.occlusionQueryObjects[contextData.occlusionQueryIndex] = object; - } - - contextData.lastOcclusionObject = object; - } - - // - - const renderer = this.bufferRenderer; - - if (object.isPoints) renderer.mode = gl.POINTS; - else if (object.isLineSegments) renderer.mode = gl.LINES; - else if (object.isLine) renderer.mode = gl.LINE_STRIP; - else if (object.isLineLoop) renderer.mode = gl.LINE_LOOP; - else { - if (material.wireframe === true) { - state.setLineWidth(material.wireframeLinewidth * this.renderer.getPixelRatio()); - renderer.mode = gl.LINES; - } else { - renderer.mode = gl.TRIANGLES; - } - } - - // - - let count; - - renderer.object = object; - - if (index !== null) { - const indexData = this.get(index); - const indexCount = drawRange.count !== Infinity ? drawRange.count : index.count; - - renderer.index = index.count; - renderer.type = indexData.type; - - count = indexCount; - } else { - renderer.index = 0; - - const vertexCount = drawRange.count !== Infinity ? drawRange.count : geometry.attributes.position.count; - - count = vertexCount; - } - - const instanceCount = this.getInstanceCount(renderObject); - - if (object.isBatchedMesh) { - if (object._multiDrawInstances !== null) { - renderer.renderMultiDrawInstances( - object._multiDrawStarts, - object._multiDrawCounts, - object._multiDrawCount, - object._multiDrawInstances, - ); - } else { - renderer.renderMultiDraw(object._multiDrawStarts, object._multiDrawCounts, object._multiDrawCount); - } - } else if (instanceCount > 1) { - renderer.renderInstances(firstVertex, count, instanceCount); - } else { - renderer.render(firstVertex, count); - } - // - - gl.bindVertexArray(null); - } - - needsRenderUpdate(/*renderObject*/) { - return false; - } - - getRenderCacheKey(renderObject) { - return renderObject.id; - } - - // textures - - createDefaultTexture(texture) { - this.textureUtils.createDefaultTexture(texture); - } - - createTexture(texture, options) { - this.textureUtils.createTexture(texture, options); - } - - updateTexture(texture, options) { - this.textureUtils.updateTexture(texture, options); - } - - generateMipmaps(texture) { - this.textureUtils.generateMipmaps(texture); - } - - destroyTexture(texture) { - this.textureUtils.destroyTexture(texture); - } - - copyTextureToBuffer(texture, x, y, width, height) { - return this.textureUtils.copyTextureToBuffer(texture, x, y, width, height); - } - - createSampler(/*texture*/) { - //console.warn( 'Abstract class.' ); - } - - destroySampler() {} - - // node builder - - createNodeBuilder(object, renderer) { - return new GLSLNodeBuilder(object, renderer); - } - - // program - - createProgram(program) { - const gl = this.gl; - const { stage, code } = program; - - const shader = stage === 'fragment' ? gl.createShader(gl.FRAGMENT_SHADER) : gl.createShader(gl.VERTEX_SHADER); - - gl.shaderSource(shader, code); - gl.compileShader(shader); - - this.set(program, { - shaderGPU: shader, - }); - } - - destroyProgram(/*program*/) { - console.warn('Abstract class.'); - } - - createRenderPipeline(renderObject, promises) { - const gl = this.gl; - const pipeline = renderObject.pipeline; - - // Program - - const { fragmentProgram, vertexProgram } = pipeline; - - const programGPU = gl.createProgram(); - - const fragmentShader = this.get(fragmentProgram).shaderGPU; - const vertexShader = this.get(vertexProgram).shaderGPU; - - gl.attachShader(programGPU, fragmentShader); - gl.attachShader(programGPU, vertexShader); - gl.linkProgram(programGPU); - - this.set(pipeline, { - programGPU, - fragmentShader, - vertexShader, - }); - - if (promises !== null && this.parallel) { - const p = new Promise((resolve /*, reject*/) => { - const parallel = this.parallel; - const checkStatus = () => { - if (gl.getProgramParameter(programGPU, parallel.COMPLETION_STATUS_KHR)) { - this._completeCompile(renderObject, pipeline); - resolve(); - } else { - requestAnimationFrame(checkStatus); - } - }; - - checkStatus(); - }); - - promises.push(p); - - return; - } - - this._completeCompile(renderObject, pipeline); - } - - _handleSource(string, errorLine) { - const lines = string.split('\n'); - const lines2 = []; - - const from = Math.max(errorLine - 6, 0); - const to = Math.min(errorLine + 6, lines.length); - - for (let i = from; i < to; i++) { - const line = i + 1; - lines2.push(`${line === errorLine ? '>' : ' '} ${line}: ${lines[i]}`); - } - - return lines2.join('\n'); - } - - _getShaderErrors(gl, shader, type) { - const status = gl.getShaderParameter(shader, gl.COMPILE_STATUS); - const errors = gl.getShaderInfoLog(shader).trim(); - - if (status && errors === '') return ''; - - const errorMatches = /ERROR: 0:(\d+)/.exec(errors); - if (errorMatches) { - const errorLine = parseInt(errorMatches[1]); - return ( - type.toUpperCase() + - '\n\n' + - errors + - '\n\n' + - this._handleSource(gl.getShaderSource(shader), errorLine) - ); - } else { - return errors; - } - } - - _logProgramError(programGPU, glFragmentShader, glVertexShader) { - if (this.renderer.debug.checkShaderErrors) { - const gl = this.gl; - - const programLog = gl.getProgramInfoLog(programGPU).trim(); - - if (gl.getProgramParameter(programGPU, gl.LINK_STATUS) === false) { - if (typeof this.renderer.debug.onShaderError === 'function') { - this.renderer.debug.onShaderError(gl, programGPU, glVertexShader, glFragmentShader); - } else { - // default error reporting - - const vertexErrors = this._getShaderErrors(gl, glVertexShader, 'vertex'); - const fragmentErrors = this._getShaderErrors(gl, glFragmentShader, 'fragment'); - - console.error( - 'THREE.WebGLProgram: Shader Error ' + - gl.getError() + - ' - ' + - 'VALIDATE_STATUS ' + - gl.getProgramParameter(programGPU, gl.VALIDATE_STATUS) + - '\n\n' + - 'Program Info Log: ' + - programLog + - '\n' + - vertexErrors + - '\n' + - fragmentErrors, - ); - } - } else if (programLog !== '') { - console.warn('THREE.WebGLProgram: Program Info Log:', programLog); - } - } - } - - _completeCompile(renderObject, pipeline) { - const gl = this.gl; - const pipelineData = this.get(pipeline); - const { programGPU, fragmentShader, vertexShader } = pipelineData; - - if (gl.getProgramParameter(programGPU, gl.LINK_STATUS) === false) { - this._logProgramError(programGPU, fragmentShader, vertexShader); - } - - gl.useProgram(programGPU); - - // Bindings - - const bindings = renderObject.getBindings(); - - this._setupBindings(bindings, programGPU); - - // - - this.set(pipeline, { - programGPU, - }); - } - - createComputePipeline(computePipeline, bindings) { - const gl = this.gl; - - // Program - - const fragmentProgram = { - stage: 'fragment', - code: '#version 300 es\nprecision highp float;\nvoid main() {}', - }; - - this.createProgram(fragmentProgram); - - const { computeProgram } = computePipeline; - - const programGPU = gl.createProgram(); - - const fragmentShader = this.get(fragmentProgram).shaderGPU; - const vertexShader = this.get(computeProgram).shaderGPU; - - const transforms = computeProgram.transforms; - - const transformVaryingNames = []; - const transformAttributeNodes = []; - - for (let i = 0; i < transforms.length; i++) { - const transform = transforms[i]; - - transformVaryingNames.push(transform.varyingName); - transformAttributeNodes.push(transform.attributeNode); - } - - gl.attachShader(programGPU, fragmentShader); - gl.attachShader(programGPU, vertexShader); - - gl.transformFeedbackVaryings(programGPU, transformVaryingNames, gl.SEPARATE_ATTRIBS); - - gl.linkProgram(programGPU); - - if (gl.getProgramParameter(programGPU, gl.LINK_STATUS) === false) { - this._logProgramError(programGPU, fragmentShader, vertexShader); - } - - gl.useProgram(programGPU); - - // Bindings - - this.createBindings(null, bindings); - - this._setupBindings(bindings, programGPU); - - const attributeNodes = computeProgram.attributes; - const attributes = []; - const transformBuffers = []; - - for (let i = 0; i < attributeNodes.length; i++) { - const attribute = attributeNodes[i].node.attribute; - - attributes.push(attribute); - - if (!this.has(attribute)) this.attributeUtils.createAttribute(attribute, gl.ARRAY_BUFFER); - } - - for (let i = 0; i < transformAttributeNodes.length; i++) { - const attribute = transformAttributeNodes[i].attribute; - - if (!this.has(attribute)) this.attributeUtils.createAttribute(attribute, gl.ARRAY_BUFFER); - - const attributeData = this.get(attribute); - - transformBuffers.push(attributeData); - } - - // - - this.set(computePipeline, { - programGPU, - transformBuffers, - attributes, - }); - } - - createBindings(bindGroup, bindings) { - this.updateBindings(bindGroup, bindings); - } - - updateBindings(bindGroup, bindings) { - const { gl } = this; - - let groupIndex = 0; - let textureIndex = 0; - - for (const bindGroup of bindings) { - for (const binding of bindGroup.bindings) { - if (binding.isUniformsGroup || binding.isUniformBuffer) { - const bufferGPU = gl.createBuffer(); - const data = binding.buffer; - - gl.bindBuffer(gl.UNIFORM_BUFFER, bufferGPU); - gl.bufferData(gl.UNIFORM_BUFFER, data, gl.DYNAMIC_DRAW); - gl.bindBufferBase(gl.UNIFORM_BUFFER, groupIndex, bufferGPU); - - this.set(binding, { - index: groupIndex++, - bufferGPU, - }); - } else if (binding.isSampledTexture) { - const { textureGPU, glTextureType } = this.get(binding.texture); - - this.set(binding, { - index: textureIndex++, - textureGPU, - glTextureType, - }); - } - } - } - } - - updateBinding(binding) { - const gl = this.gl; - - if (binding.isUniformsGroup || binding.isUniformBuffer) { - const bindingData = this.get(binding); - const bufferGPU = bindingData.bufferGPU; - const data = binding.buffer; - - gl.bindBuffer(gl.UNIFORM_BUFFER, bufferGPU); - gl.bufferData(gl.UNIFORM_BUFFER, data, gl.DYNAMIC_DRAW); - } - } - - // attributes - - createIndexAttribute(attribute) { - const gl = this.gl; - - this.attributeUtils.createAttribute(attribute, gl.ELEMENT_ARRAY_BUFFER); - } - - createAttribute(attribute) { - if (this.has(attribute)) return; - - const gl = this.gl; - - this.attributeUtils.createAttribute(attribute, gl.ARRAY_BUFFER); - } - - createStorageAttribute(attribute) { - if (this.has(attribute)) return; - - const gl = this.gl; - - this.attributeUtils.createAttribute(attribute, gl.ARRAY_BUFFER); - } - - updateAttribute(attribute) { - this.attributeUtils.updateAttribute(attribute); - } - - destroyAttribute(attribute) { - this.attributeUtils.destroyAttribute(attribute); - } - - updateSize() { - //console.warn( 'Abstract class.' ); - } - - hasFeature(name) { - const keysMatching = Object.keys(GLFeatureName).filter(key => GLFeatureName[key] === name); - - const extensions = this.extensions; - - for (let i = 0; i < keysMatching.length; i++) { - if (extensions.has(keysMatching[i])) return true; - } - - return false; - } - - getMaxAnisotropy() { - return this.capabilities.getMaxAnisotropy(); - } - - copyTextureToTexture(position, srcTexture, dstTexture, level) { - this.textureUtils.copyTextureToTexture(position, srcTexture, dstTexture, level); - } - - copyFramebufferToTexture(texture, renderContext) { - this.textureUtils.copyFramebufferToTexture(texture, renderContext); - } - - _setFramebuffer(renderContext) { - const { gl, state } = this; - - let currentFrameBuffer = null; - - if (renderContext.textures !== null) { - const renderTarget = renderContext.renderTarget; - const renderTargetContextData = this.get(renderTarget); - const { samples, depthBuffer, stencilBuffer } = renderTarget; - const cubeFace = this.renderer._activeCubeFace; - const isCube = renderTarget.isWebGLCubeRenderTarget === true; - - let msaaFb = renderTargetContextData.msaaFrameBuffer; - let depthRenderbuffer = renderTargetContextData.depthRenderbuffer; - - let fb; - - if (isCube) { - if (renderTargetContextData.cubeFramebuffers === undefined) { - renderTargetContextData.cubeFramebuffers = []; - } - - fb = renderTargetContextData.cubeFramebuffers[cubeFace]; - } else { - fb = renderTargetContextData.framebuffer; - } - - if (fb === undefined) { - fb = gl.createFramebuffer(); - - state.bindFramebuffer(gl.FRAMEBUFFER, fb); - - const textures = renderContext.textures; - - if (isCube) { - renderTargetContextData.cubeFramebuffers[cubeFace] = fb; - const { textureGPU } = this.get(textures[0]); - - gl.framebufferTexture2D( - gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0, - gl.TEXTURE_CUBE_MAP_POSITIVE_X + cubeFace, - textureGPU, - 0, - ); - } else { - for (let i = 0; i < textures.length; i++) { - const texture = textures[i]; - const textureData = this.get(texture); - textureData.renderTarget = renderContext.renderTarget; - - const attachment = gl.COLOR_ATTACHMENT0 + i; - - gl.framebufferTexture2D(gl.FRAMEBUFFER, attachment, gl.TEXTURE_2D, textureData.textureGPU, 0); - } - - renderTargetContextData.framebuffer = fb; - - state.drawBuffers(renderContext, fb); - } - - if (renderContext.depthTexture !== null) { - const textureData = this.get(renderContext.depthTexture); - const depthStyle = stencilBuffer ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT; - - gl.framebufferTexture2D(gl.FRAMEBUFFER, depthStyle, gl.TEXTURE_2D, textureData.textureGPU, 0); - } - } - - if (samples > 0) { - if (msaaFb === undefined) { - const invalidationArray = []; - - msaaFb = gl.createFramebuffer(); - - state.bindFramebuffer(gl.FRAMEBUFFER, msaaFb); - - const msaaRenderbuffers = []; - - const textures = renderContext.textures; - - for (let i = 0; i < textures.length; i++) { - msaaRenderbuffers[i] = gl.createRenderbuffer(); - - gl.bindRenderbuffer(gl.RENDERBUFFER, msaaRenderbuffers[i]); - - invalidationArray.push(gl.COLOR_ATTACHMENT0 + i); - - if (depthBuffer) { - const depthStyle = stencilBuffer ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT; - invalidationArray.push(depthStyle); - } - - const texture = renderContext.textures[i]; - const textureData = this.get(texture); - - gl.renderbufferStorageMultisample( - gl.RENDERBUFFER, - samples, - textureData.glInternalFormat, - renderContext.width, - renderContext.height, - ); - gl.framebufferRenderbuffer( - gl.FRAMEBUFFER, - gl.COLOR_ATTACHMENT0 + i, - gl.RENDERBUFFER, - msaaRenderbuffers[i], - ); - } - - renderTargetContextData.msaaFrameBuffer = msaaFb; - renderTargetContextData.msaaRenderbuffers = msaaRenderbuffers; - - if (depthRenderbuffer === undefined) { - depthRenderbuffer = gl.createRenderbuffer(); - this.textureUtils.setupRenderBufferStorage(depthRenderbuffer, renderContext); - - renderTargetContextData.depthRenderbuffer = depthRenderbuffer; - - const depthStyle = stencilBuffer ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT; - invalidationArray.push(depthStyle); - } - - renderTargetContextData.invalidationArray = invalidationArray; - } - - currentFrameBuffer = renderTargetContextData.msaaFrameBuffer; - } else { - currentFrameBuffer = fb; - } - } - - state.bindFramebuffer(gl.FRAMEBUFFER, currentFrameBuffer); - } - - _getVaoKey(index, attributes) { - let key = []; - - if (index !== null) { - const indexData = this.get(index); - - key += ':' + indexData.id; - } - - for (let i = 0; i < attributes.length; i++) { - const attributeData = this.get(attributes[i]); - - key += ':' + attributeData.id; - } - - return key; - } - - _createVao(index, attributes) { - const { gl } = this; - - const vaoGPU = gl.createVertexArray(); - let key = ''; - - let staticVao = true; - - gl.bindVertexArray(vaoGPU); - - if (index !== null) { - const indexData = this.get(index); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexData.bufferGPU); - - key += ':' + indexData.id; - } - - for (let i = 0; i < attributes.length; i++) { - const attribute = attributes[i]; - const attributeData = this.get(attribute); - - key += ':' + attributeData.id; - - gl.bindBuffer(gl.ARRAY_BUFFER, attributeData.bufferGPU); - gl.enableVertexAttribArray(i); - - if (attribute.isStorageBufferAttribute || attribute.isStorageInstancedBufferAttribute) staticVao = false; - - let stride, offset; - - if (attribute.isInterleavedBufferAttribute === true) { - stride = attribute.data.stride * attributeData.bytesPerElement; - offset = attribute.offset * attributeData.bytesPerElement; - } else { - stride = 0; - offset = 0; - } - - if (attributeData.isInteger) { - gl.vertexAttribIPointer(i, attribute.itemSize, attributeData.type, stride, offset); - } else { - gl.vertexAttribPointer(i, attribute.itemSize, attributeData.type, attribute.normalized, stride, offset); - } - - if (attribute.isInstancedBufferAttribute && !attribute.isInterleavedBufferAttribute) { - gl.vertexAttribDivisor(i, attribute.meshPerAttribute); - } else if (attribute.isInterleavedBufferAttribute && attribute.data.isInstancedInterleavedBuffer) { - gl.vertexAttribDivisor(i, attribute.data.meshPerAttribute); - } - } - - gl.bindBuffer(gl.ARRAY_BUFFER, null); - - this.vaoCache[key] = vaoGPU; - - return { vaoGPU, staticVao }; - } - - _getTransformFeedback(transformBuffers) { - let key = ''; - - for (let i = 0; i < transformBuffers.length; i++) { - key += ':' + transformBuffers[i].id; - } - - let transformFeedbackGPU = this.transformFeedbackCache[key]; - - if (transformFeedbackGPU !== undefined) { - return transformFeedbackGPU; - } - - const gl = this.gl; - - transformFeedbackGPU = gl.createTransformFeedback(); - - gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, transformFeedbackGPU); - - for (let i = 0; i < transformBuffers.length; i++) { - const attributeData = transformBuffers[i]; - - gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, i, attributeData.transformBuffer); - } - - gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null); - - this.transformFeedbackCache[key] = transformFeedbackGPU; - - return transformFeedbackGPU; - } - - _setupBindings(bindings, programGPU) { - const gl = this.gl; - - for (const bindGroup of bindings) { - for (const binding of bindGroup.bindings) { - const bindingData = this.get(binding); - const index = bindingData.index; - - if (binding.isUniformsGroup || binding.isUniformBuffer) { - const location = gl.getUniformBlockIndex(programGPU, binding.name); - gl.uniformBlockBinding(programGPU, location, index); - } else if (binding.isSampledTexture) { - const location = gl.getUniformLocation(programGPU, binding.name); - gl.uniform1i(location, index); - } - } - } - } - - _bindUniforms(bindings) { - const { gl, state } = this; - - for (const bindGroup of bindings) { - for (const binding of bindGroup.bindings) { - const bindingData = this.get(binding); - const index = bindingData.index; - - if (binding.isUniformsGroup || binding.isUniformBuffer) { - gl.bindBufferBase(gl.UNIFORM_BUFFER, index, bindingData.bufferGPU); - } else if (binding.isSampledTexture) { - state.bindTexture(bindingData.glTextureType, bindingData.textureGPU, gl.TEXTURE0 + index); - } - } - } - } -} - -export default WebGLBackend; diff --git a/examples-jsm/examples/renderers/webgl/nodes/GLSLNodeBuilder.ts b/examples-jsm/examples/renderers/webgl/nodes/GLSLNodeBuilder.ts deleted file mode 100644 index 816b83ea..00000000 --- a/examples-jsm/examples/renderers/webgl/nodes/GLSLNodeBuilder.ts +++ /dev/null @@ -1,747 +0,0 @@ -import { MathNode, GLSLNodeParser, NodeBuilder, TextureNode, vectorComponents } from '../../../nodes/Nodes.js'; - -import NodeUniformBuffer from '../../common/nodes/NodeUniformBuffer.js'; -import NodeUniformsGroup from '../../common/nodes/NodeUniformsGroup.js'; - -import { - NodeSampledTexture, - NodeSampledCubeTexture, - NodeSampledTexture3D, -} from '../../common/nodes/NodeSampledTexture.js'; - -import { - ByteType, - ShortType, - RGBAIntegerFormat, - RGBIntegerFormat, - RedIntegerFormat, - RGIntegerFormat, - UnsignedByteType, - UnsignedIntType, - UnsignedShortType, - RedFormat, - RGFormat, - IntType, - DataTexture, - RGBFormat, - RGBAFormat, - FloatType, -} from 'three'; - -const glslMethods = { - [MathNode.ATAN2]: 'atan', - textureDimensions: 'textureSize', - equals: 'equal', -}; - -const precisionLib = { - low: 'lowp', - medium: 'mediump', - high: 'highp', -}; - -const supports = { - swizzleAssign: true, - storageBuffer: false, -}; - -const defaultPrecisions = ` -precision highp float; -precision highp int; -precision highp sampler2D; -precision highp sampler3D; -precision highp samplerCube; -precision highp sampler2DArray; - -precision highp usampler2D; -precision highp usampler3D; -precision highp usamplerCube; -precision highp usampler2DArray; - -precision highp isampler2D; -precision highp isampler3D; -precision highp isamplerCube; -precision highp isampler2DArray; - -precision lowp sampler2DShadow; -`; - -class GLSLNodeBuilder extends NodeBuilder { - constructor(object, renderer) { - super(object, renderer, new GLSLNodeParser()); - - this.uniformGroups = {}; - this.transforms = []; - - this.instanceBindGroups = false; - } - - getMethod(method) { - return glslMethods[method] || method; - } - - getOutputStructName() { - return ''; - } - - buildFunctionCode(shaderNode) { - const layout = shaderNode.layout; - const flowData = this.flowShaderNode(shaderNode); - - const parameters = []; - - for (const input of layout.inputs) { - parameters.push(this.getType(input.type) + ' ' + input.name); - } - - // - - const code = `${this.getType(layout.type)} ${layout.name}( ${parameters.join(', ')} ) { - - ${flowData.vars} - -${flowData.code} - return ${flowData.result}; - -}`; - - // - - return code; - } - - setupPBO(storageBufferNode) { - const attribute = storageBufferNode.value; - - if (attribute.pbo === undefined) { - const originalArray = attribute.array; - const numElements = attribute.count * attribute.itemSize; - - const { itemSize } = attribute; - - const isInteger = attribute.array.constructor.name.toLowerCase().includes('int'); - - let format = isInteger ? RedIntegerFormat : RedFormat; - - if (itemSize === 2) { - format = isInteger ? RGIntegerFormat : RGFormat; - } else if (itemSize === 3) { - format = isInteger ? RGBIntegerFormat : RGBFormat; - } else if (itemSize === 4) { - format = isInteger ? RGBAIntegerFormat : RGBAFormat; - } - - const typeMap = { - Float32Array: FloatType, - Uint8Array: UnsignedByteType, - Uint16Array: UnsignedShortType, - Uint32Array: UnsignedIntType, - Int8Array: ByteType, - Int16Array: ShortType, - Int32Array: IntType, - Uint8ClampedArray: UnsignedByteType, - }; - - const width = Math.pow(2, Math.ceil(Math.log2(Math.sqrt(numElements / itemSize)))); - let height = Math.ceil(numElements / itemSize / width); - if (width * height * itemSize < numElements) height++; // Ensure enough space - - const newSize = width * height * itemSize; - - const newArray = new originalArray.constructor(newSize); - - newArray.set(originalArray, 0); - - attribute.array = newArray; - - const pboTexture = new DataTexture( - attribute.array, - width, - height, - format, - typeMap[attribute.array.constructor.name] || FloatType, - ); - pboTexture.needsUpdate = true; - pboTexture.isPBOTexture = true; - - const pbo = new TextureNode(pboTexture); - pbo.setPrecision('high'); - - attribute.pboNode = pbo; - attribute.pbo = pbo.value; - - this.getUniformFromNode(attribute.pboNode, 'texture', this.shaderStage, this.context.label); - } - } - - getPropertyName(node, shaderStage = this.shaderStage) { - if (node.isNodeUniform && node.node.isTextureNode !== true && node.node.isBufferNode !== true) { - return shaderStage.charAt(0) + '_' + node.name; - } - - return super.getPropertyName(node, shaderStage); - } - - generatePBO(storageArrayElementNode) { - const { node, indexNode } = storageArrayElementNode; - const attribute = node.value; - - if (this.renderer.backend.has(attribute)) { - const attributeData = this.renderer.backend.get(attribute); - attributeData.pbo = attribute.pbo; - } - - const nodeUniform = this.getUniformFromNode(attribute.pboNode, 'texture', this.shaderStage, this.context.label); - const textureName = this.getPropertyName(nodeUniform); - - indexNode.increaseUsage(this); // force cache generate to be used as index in x,y - const indexSnippet = indexNode.build(this, 'uint'); - - const elementNodeData = this.getDataFromNode(storageArrayElementNode); - - let propertyName = elementNodeData.propertyName; - - if (propertyName === undefined) { - // property element - - const nodeVar = this.getVarFromNode(storageArrayElementNode); - - propertyName = this.getPropertyName(nodeVar); - - // property size - - const bufferNodeData = this.getDataFromNode(node); - - let propertySizeName = bufferNodeData.propertySizeName; - - if (propertySizeName === undefined) { - propertySizeName = propertyName + 'Size'; - - this.getVarFromNode(node, propertySizeName, 'uint'); - - this.addLineFlowCode(`${propertySizeName} = uint( textureSize( ${textureName}, 0 ).x )`); - - bufferNodeData.propertySizeName = propertySizeName; - } - - // - - const { itemSize } = attribute; - - const channel = '.' + vectorComponents.join('').slice(0, itemSize); - const uvSnippet = `ivec2(${indexSnippet} % ${propertySizeName}, ${indexSnippet} / ${propertySizeName})`; - - const snippet = this.generateTextureLoad(null, textureName, uvSnippet, null, '0'); - - // - - const typePrefix = attribute.array.constructor.name.toLowerCase().charAt(0); - - let prefix = 'vec4'; - if (typePrefix === 'u') { - prefix = 'uvec4'; - } else if (typePrefix === 'i') { - prefix = 'ivec4'; - } - - this.addLineFlowCode(`${propertyName} = ${prefix}(${snippet})${channel}`); - - elementNodeData.propertyName = propertyName; - } - - return propertyName; - } - - generateTextureLoad(texture, textureProperty, uvIndexSnippet, depthSnippet, levelSnippet = '0') { - if (depthSnippet) { - return `texelFetch( ${textureProperty}, ivec3( ${uvIndexSnippet}, ${depthSnippet} ), ${levelSnippet} )`; - } else { - return `texelFetch( ${textureProperty}, ${uvIndexSnippet}, ${levelSnippet} )`; - } - } - - generateTexture(texture, textureProperty, uvSnippet, depthSnippet) { - if (texture.isDepthTexture) { - return `texture( ${textureProperty}, ${uvSnippet} ).x`; - } else { - if (depthSnippet) uvSnippet = `vec3( ${uvSnippet}, ${depthSnippet} )`; - - return `texture( ${textureProperty}, ${uvSnippet} )`; - } - } - - generateTextureLevel(texture, textureProperty, uvSnippet, levelSnippet) { - return `textureLod( ${textureProperty}, ${uvSnippet}, ${levelSnippet} )`; - } - - generateTextureGrad(texture, textureProperty, uvSnippet, gradSnippet) { - return `textureGrad( ${textureProperty}, ${uvSnippet}, ${gradSnippet[0]}, ${gradSnippet[1]} )`; - } - - generateTextureCompare( - texture, - textureProperty, - uvSnippet, - compareSnippet, - depthSnippet, - shaderStage = this.shaderStage, - ) { - if (shaderStage === 'fragment') { - return `texture( ${textureProperty}, vec3( ${uvSnippet}, ${compareSnippet} ) )`; - } else { - console.error( - `WebGPURenderer: THREE.DepthTexture.compareFunction() does not support ${shaderStage} shader.`, - ); - } - } - - getVars(shaderStage) { - const snippets = []; - - const vars = this.vars[shaderStage]; - - if (vars !== undefined) { - for (const variable of vars) { - snippets.push(`${this.getVar(variable.type, variable.name)};`); - } - } - - return snippets.join('\n\t'); - } - - getUniforms(shaderStage) { - const uniforms = this.uniforms[shaderStage]; - - const bindingSnippets = []; - const uniformGroups = {}; - - for (const uniform of uniforms) { - let snippet = null; - let group = false; - - if (uniform.type === 'texture') { - const texture = uniform.node.value; - - let typePrefix = ''; - - if (texture.isPBOTexture === true) { - const prefix = texture.source.data.data.constructor.name.toLowerCase().charAt(0); - - if (prefix === 'u' || prefix === 'i') { - typePrefix = prefix; - } - } - - if (texture.compareFunction) { - snippet = `sampler2DShadow ${uniform.name};`; - } else if (texture.isDataArrayTexture === true) { - snippet = `${typePrefix}sampler2DArray ${uniform.name};`; - } else { - snippet = `${typePrefix}sampler2D ${uniform.name};`; - } - } else if (uniform.type === 'cubeTexture') { - snippet = `samplerCube ${uniform.name};`; - } else if (uniform.type === 'texture3D') { - snippet = `sampler3D ${uniform.name};`; - } else if (uniform.type === 'buffer') { - const bufferNode = uniform.node; - const bufferType = this.getType(bufferNode.bufferType); - const bufferCount = bufferNode.bufferCount; - - const bufferCountSnippet = bufferCount > 0 ? bufferCount : ''; - snippet = `${bufferNode.name} {\n\t${bufferType} ${uniform.name}[${bufferCountSnippet}];\n};\n`; - } else { - const vectorType = this.getVectorType(uniform.type); - - snippet = `${vectorType} ${this.getPropertyName(uniform, shaderStage)};`; - - group = true; - } - - const precision = uniform.node.precision; - - if (precision !== null) { - snippet = precisionLib[precision] + ' ' + snippet; - } - - if (group) { - snippet = '\t' + snippet; - - const groupName = uniform.groupNode.name; - const groupSnippets = uniformGroups[groupName] || (uniformGroups[groupName] = []); - - groupSnippets.push(snippet); - } else { - snippet = 'uniform ' + snippet; - - bindingSnippets.push(snippet); - } - } - - let output = ''; - - for (const name in uniformGroups) { - const groupSnippets = uniformGroups[name]; - - output += this._getGLSLUniformStruct(shaderStage + '_' + name, groupSnippets.join('\n')) + '\n'; - } - - output += bindingSnippets.join('\n'); - - return output; - } - - getTypeFromAttribute(attribute) { - let nodeType = super.getTypeFromAttribute(attribute); - - if (/^[iu]/.test(nodeType) && attribute.gpuType !== IntType) { - let dataAttribute = attribute; - - if (attribute.isInterleavedBufferAttribute) dataAttribute = attribute.data; - - const array = dataAttribute.array; - - if ( - (array instanceof Uint32Array || - array instanceof Int32Array || - array instanceof Uint16Array || - array instanceof Int16Array) === false - ) { - nodeType = nodeType.slice(1); - } - } - - return nodeType; - } - - getAttributes(shaderStage) { - let snippet = ''; - - if (shaderStage === 'vertex' || shaderStage === 'compute') { - const attributes = this.getAttributesArray(); - - let location = 0; - - for (const attribute of attributes) { - snippet += `layout( location = ${location++} ) in ${attribute.type} ${attribute.name};\n`; - } - } - - return snippet; - } - - getStructMembers(struct) { - const snippets = []; - const members = struct.getMemberTypes(); - - for (let i = 0; i < members.length; i++) { - const member = members[i]; - snippets.push(`layout( location = ${i} ) out ${member} m${i};`); - } - - return snippets.join('\n'); - } - - getStructs(shaderStage) { - const snippets = []; - const structs = this.structs[shaderStage]; - - if (structs.length === 0) { - return 'layout( location = 0 ) out vec4 fragColor;\n'; - } - - for (let index = 0, length = structs.length; index < length; index++) { - const struct = structs[index]; - - let snippet = '\n'; - snippet += this.getStructMembers(struct); - snippet += '\n'; - - snippets.push(snippet); - } - - return snippets.join('\n\n'); - } - - getVaryings(shaderStage) { - let snippet = ''; - - const varyings = this.varyings; - - if (shaderStage === 'vertex' || shaderStage === 'compute') { - for (const varying of varyings) { - if (shaderStage === 'compute') varying.needsInterpolation = true; - const type = varying.type; - const flat = type.includes('int') || type.includes('uv') || type.includes('iv') ? 'flat ' : ''; - - snippet += `${flat}${varying.needsInterpolation ? 'out' : '/*out*/'} ${type} ${varying.name};\n`; - } - } else if (shaderStage === 'fragment') { - for (const varying of varyings) { - if (varying.needsInterpolation) { - const type = varying.type; - const flat = type.includes('int') || type.includes('uv') || type.includes('iv') ? 'flat ' : ''; - - snippet += `${flat}in ${type} ${varying.name};\n`; - } - } - } - - return snippet; - } - - getVertexIndex() { - return 'uint( gl_VertexID )'; - } - - getInstanceIndex() { - return 'uint( gl_InstanceID )'; - } - - getFrontFacing() { - return 'gl_FrontFacing'; - } - - getFragCoord() { - return 'gl_FragCoord'; - } - - getFragDepth() { - return 'gl_FragDepth'; - } - - isAvailable(name) { - let result = supports[name]; - - if (result === undefined) { - if (name === 'float32Filterable') { - const extentions = this.renderer.backend.extensions; - - if (extentions.has('OES_texture_float_linear')) { - extentions.get('OES_texture_float_linear'); - result = true; - } else { - result = false; - } - } - - supports[name] = result; - } - - return result; - } - - isFlipY() { - return true; - } - - registerTransform(varyingName, attributeNode) { - this.transforms.push({ varyingName, attributeNode }); - } - - getTransforms(/* shaderStage */) { - const transforms = this.transforms; - - let snippet = ''; - - for (let i = 0; i < transforms.length; i++) { - const transform = transforms[i]; - - const attributeName = this.getPropertyName(transform.attributeNode); - - snippet += `${transform.varyingName} = ${attributeName};\n\t`; - } - - return snippet; - } - - _getGLSLUniformStruct(name, vars) { - return ` -layout( std140 ) uniform ${name} { -${vars} -};`; - } - - _getGLSLVertexCode(shaderData) { - return `#version 300 es - -${this.getSignature()} - -// precision -${defaultPrecisions} - -// uniforms -${shaderData.uniforms} - -// varyings -${shaderData.varyings} - -// attributes -${shaderData.attributes} - -// codes -${shaderData.codes} - -void main() { - - // vars - ${shaderData.vars} - - // transforms - ${shaderData.transforms} - - // flow - ${shaderData.flow} - - gl_PointSize = 1.0; - -} -`; - } - - _getGLSLFragmentCode(shaderData) { - return `#version 300 es - -${this.getSignature()} - -// precision -${defaultPrecisions} - -// uniforms -${shaderData.uniforms} - -// varyings -${shaderData.varyings} - -// codes -${shaderData.codes} - -${shaderData.structs} - -void main() { - - // vars - ${shaderData.vars} - - // flow - ${shaderData.flow} - -} -`; - } - - buildCode() { - const shadersData = this.material !== null ? { fragment: {}, vertex: {} } : { compute: {} }; - - for (const shaderStage in shadersData) { - let flow = '// code\n\n'; - flow += this.flowCode[shaderStage]; - - const flowNodes = this.flowNodes[shaderStage]; - const mainNode = flowNodes[flowNodes.length - 1]; - - for (const node of flowNodes) { - const flowSlotData = this.getFlowData(node /*, shaderStage*/); - const slotName = node.name; - - if (slotName) { - if (flow.length > 0) flow += '\n'; - - flow += `\t// flow -> ${slotName}\n\t`; - } - - flow += `${flowSlotData.code}\n\t`; - - if (node === mainNode && shaderStage !== 'compute') { - flow += '// result\n\t'; - - if (shaderStage === 'vertex') { - flow += 'gl_Position = '; - flow += `${flowSlotData.result};`; - } else if (shaderStage === 'fragment') { - if (!node.outputNode.isOutputStructNode) { - flow += 'fragColor = '; - flow += `${flowSlotData.result};`; - } - } - } - } - - const stageData = shadersData[shaderStage]; - - stageData.uniforms = this.getUniforms(shaderStage); - stageData.attributes = this.getAttributes(shaderStage); - stageData.varyings = this.getVaryings(shaderStage); - stageData.vars = this.getVars(shaderStage); - stageData.structs = this.getStructs(shaderStage); - stageData.codes = this.getCodes(shaderStage); - stageData.transforms = this.getTransforms(shaderStage); - stageData.flow = flow; - } - - if (this.material !== null) { - this.vertexShader = this._getGLSLVertexCode(shadersData.vertex); - this.fragmentShader = this._getGLSLFragmentCode(shadersData.fragment); - } else { - this.computeShader = this._getGLSLVertexCode(shadersData.compute); - } - } - - getUniformFromNode(node, type, shaderStage, name = null) { - const uniformNode = super.getUniformFromNode(node, type, shaderStage, name); - const nodeData = this.getDataFromNode(node, shaderStage, this.globalCache); - - let uniformGPU = nodeData.uniformGPU; - - if (uniformGPU === undefined) { - const group = node.groupNode; - const groupName = group.name; - - const bindings = this.getBindGroupArray(groupName, shaderStage); - - if (type === 'texture') { - uniformGPU = new NodeSampledTexture(uniformNode.name, uniformNode.node, group); - bindings.push(uniformGPU); - } else if (type === 'cubeTexture') { - uniformGPU = new NodeSampledCubeTexture(uniformNode.name, uniformNode.node, group); - bindings.push(uniformGPU); - } else if (type === 'texture3D') { - uniformGPU = new NodeSampledTexture3D(uniformNode.name, uniformNode.node, group); - bindings.push(uniformGPU); - } else if (type === 'buffer') { - node.name = `NodeBuffer_${node.id}`; - uniformNode.name = `buffer${node.id}`; - - const buffer = new NodeUniformBuffer(node, group); - buffer.name = node.name; - - bindings.push(buffer); - - uniformGPU = buffer; - } else { - const uniformsStage = this.uniformGroups[shaderStage] || (this.uniformGroups[shaderStage] = {}); - - let uniformsGroup = uniformsStage[groupName]; - - if (uniformsGroup === undefined) { - uniformsGroup = new NodeUniformsGroup(shaderStage + '_' + groupName, group); - //uniformsGroup.setVisibility( gpuShaderStageLib[ shaderStage ] ); - - uniformsStage[groupName] = uniformsGroup; - - bindings.push(uniformsGroup); - } - - uniformGPU = this.getNodeUniform(uniformNode, type); - - uniformsGroup.addUniform(uniformGPU); - } - - nodeData.uniformGPU = uniformGPU; - } - - return uniformNode; - } -} - -export default GLSLNodeBuilder; diff --git a/examples-jsm/examples/renderers/webgpu/WebGPUBackend.ts b/examples-jsm/examples/renderers/webgpu/WebGPUBackend.ts deleted file mode 100644 index 5cbbafc0..00000000 --- a/examples-jsm/examples/renderers/webgpu/WebGPUBackend.ts +++ /dev/null @@ -1,1194 +0,0 @@ -/*// debugger tools -import 'https://greggman.github.io/webgpu-avoid-redundant-state-setting/webgpu-check-redundant-state-setting.js'; -//*/ - -import { WebGPUCoordinateSystem } from 'three'; - -import { - GPUFeatureName, - GPUTextureFormat, - GPULoadOp, - GPUStoreOp, - GPUIndexFormat, - GPUTextureViewDimension, -} from './utils/WebGPUConstants.js'; - -import WGSLNodeBuilder from './nodes/WGSLNodeBuilder.js'; -import Backend from '../common/Backend.js'; - -import WebGPUUtils from './utils/WebGPUUtils.js'; -import WebGPUAttributeUtils from './utils/WebGPUAttributeUtils.js'; -import WebGPUBindingUtils from './utils/WebGPUBindingUtils.js'; -import WebGPUPipelineUtils from './utils/WebGPUPipelineUtils.js'; -import WebGPUTextureUtils from './utils/WebGPUTextureUtils.js'; - -// - -class WebGPUBackend extends Backend { - constructor(parameters = {}) { - super(parameters); - - this.isWebGPUBackend = true; - - // some parameters require default values other than "undefined" - this.parameters.alpha = parameters.alpha === undefined ? true : parameters.alpha; - - this.parameters.antialias = parameters.antialias === true; - - if (this.parameters.antialias === true) { - this.parameters.sampleCount = parameters.sampleCount === undefined ? 4 : parameters.sampleCount; - } else { - this.parameters.sampleCount = 1; - } - - this.parameters.requiredLimits = parameters.requiredLimits === undefined ? {} : parameters.requiredLimits; - - this.trackTimestamp = parameters.trackTimestamp === true; - - this.device = null; - this.context = null; - this.colorBuffer = null; - this.defaultRenderPassdescriptor = null; - - this.utils = new WebGPUUtils(this); - this.attributeUtils = new WebGPUAttributeUtils(this); - this.bindingUtils = new WebGPUBindingUtils(this); - this.pipelineUtils = new WebGPUPipelineUtils(this); - this.textureUtils = new WebGPUTextureUtils(this); - this.occludedResolveCache = new Map(); - } - - async init(renderer) { - await super.init(renderer); - - // - - const parameters = this.parameters; - - // create the device if it is not passed with parameters - - let device; - - if (parameters.device === undefined) { - const adapterOptions = { - powerPreference: parameters.powerPreference, - }; - - const adapter = await navigator.gpu.requestAdapter(adapterOptions); - - if (adapter === null) { - throw new Error('WebGPUBackend: Unable to create WebGPU adapter.'); - } - - // feature support - - const features = Object.values(GPUFeatureName); - - const supportedFeatures = []; - - for (const name of features) { - if (adapter.features.has(name)) { - supportedFeatures.push(name); - } - } - - const deviceDescriptor = { - requiredFeatures: supportedFeatures, - requiredLimits: parameters.requiredLimits, - }; - - device = await adapter.requestDevice(deviceDescriptor); - } else { - device = parameters.device; - } - - const context = - parameters.context !== undefined ? parameters.context : renderer.domElement.getContext('webgpu'); - - this.device = device; - this.context = context; - - const alphaMode = parameters.alpha ? 'premultiplied' : 'opaque'; - - this.context.configure({ - device: this.device, - format: GPUTextureFormat.BGRA8Unorm, - usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC, - alphaMode: alphaMode, - }); - - this.updateSize(); - } - - get coordinateSystem() { - return WebGPUCoordinateSystem; - } - - async getArrayBufferAsync(attribute) { - return await this.attributeUtils.getArrayBufferAsync(attribute); - } - - getContext() { - return this.context; - } - - _getDefaultRenderPassDescriptor() { - let descriptor = this.defaultRenderPassdescriptor; - - const antialias = this.parameters.antialias; - - if (descriptor === null) { - const renderer = this.renderer; - - descriptor = { - colorAttachments: [ - { - view: null, - }, - ], - depthStencilAttachment: { - view: this.textureUtils.getDepthBuffer(renderer.depth, renderer.stencil).createView(), - }, - }; - - const colorAttachment = descriptor.colorAttachments[0]; - - if (antialias === true) { - colorAttachment.view = this.colorBuffer.createView(); - } else { - colorAttachment.resolveTarget = undefined; - } - - this.defaultRenderPassdescriptor = descriptor; - } - - const colorAttachment = descriptor.colorAttachments[0]; - - if (antialias === true) { - colorAttachment.resolveTarget = this.context.getCurrentTexture().createView(); - } else { - colorAttachment.view = this.context.getCurrentTexture().createView(); - } - - return descriptor; - } - - _getRenderPassDescriptor(renderContext) { - const renderTarget = renderContext.renderTarget; - const renderTargetData = this.get(renderTarget); - - let descriptors = renderTargetData.descriptors; - - if (descriptors === undefined) { - descriptors = []; - - renderTargetData.descriptors = descriptors; - } - - if ( - renderTargetData.width !== renderTarget.width || - renderTargetData.height !== renderTarget.height || - renderTargetData.activeMipmapLevel !== renderTarget.activeMipmapLevel || - renderTargetData.samples !== renderTarget.samples - ) { - descriptors.length = 0; - } - - let descriptor = descriptors[renderContext.activeCubeFace]; - - if (descriptor === undefined) { - const textures = renderContext.textures; - const colorAttachments = []; - - for (let i = 0; i < textures.length; i++) { - const textureData = this.get(textures[i]); - - const textureView = textureData.texture.createView({ - baseMipLevel: renderContext.activeMipmapLevel, - mipLevelCount: 1, - baseArrayLayer: renderContext.activeCubeFace, - dimension: GPUTextureViewDimension.TwoD, - }); - - let view, resolveTarget; - - if (textureData.msaaTexture !== undefined) { - view = textureData.msaaTexture.createView(); - resolveTarget = textureView; - } else { - view = textureView; - resolveTarget = undefined; - } - - colorAttachments.push({ - view, - resolveTarget, - loadOp: GPULoadOp.Load, - storeOp: GPUStoreOp.Store, - }); - } - - const depthTextureData = this.get(renderContext.depthTexture); - - const depthStencilAttachment = { - view: depthTextureData.texture.createView(), - }; - - descriptor = { - colorAttachments, - depthStencilAttachment, - }; - - descriptors[renderContext.activeCubeFace] = descriptor; - - renderTargetData.width = renderTarget.width; - renderTargetData.height = renderTarget.height; - renderTargetData.samples = renderTarget.samples; - renderTargetData.activeMipmapLevel = renderTarget.activeMipmapLevel; - } - - return descriptor; - } - - beginRender(renderContext) { - const renderContextData = this.get(renderContext); - - const device = this.device; - const occlusionQueryCount = renderContext.occlusionQueryCount; - - let occlusionQuerySet; - - if (occlusionQueryCount > 0) { - if (renderContextData.currentOcclusionQuerySet) renderContextData.currentOcclusionQuerySet.destroy(); - if (renderContextData.currentOcclusionQueryBuffer) renderContextData.currentOcclusionQueryBuffer.destroy(); - - // Get a reference to the array of objects with queries. The renderContextData property - // can be changed by another render pass before the buffer.mapAsyc() completes. - renderContextData.currentOcclusionQuerySet = renderContextData.occlusionQuerySet; - renderContextData.currentOcclusionQueryBuffer = renderContextData.occlusionQueryBuffer; - renderContextData.currentOcclusionQueryObjects = renderContextData.occlusionQueryObjects; - - // - - occlusionQuerySet = device.createQuerySet({ type: 'occlusion', count: occlusionQueryCount }); - - renderContextData.occlusionQuerySet = occlusionQuerySet; - renderContextData.occlusionQueryIndex = 0; - renderContextData.occlusionQueryObjects = new Array(occlusionQueryCount); - - renderContextData.lastOcclusionObject = null; - } - - let descriptor; - - if (renderContext.textures === null) { - descriptor = this._getDefaultRenderPassDescriptor(); - } else { - descriptor = this._getRenderPassDescriptor(renderContext); - } - - this.initTimestampQuery(renderContext, descriptor); - - descriptor.occlusionQuerySet = occlusionQuerySet; - - const depthStencilAttachment = descriptor.depthStencilAttachment; - - if (renderContext.textures !== null) { - const colorAttachments = descriptor.colorAttachments; - - for (let i = 0; i < colorAttachments.length; i++) { - const colorAttachment = colorAttachments[i]; - - if (renderContext.clearColor) { - colorAttachment.clearValue = renderContext.clearColorValue; - colorAttachment.loadOp = GPULoadOp.Clear; - colorAttachment.storeOp = GPUStoreOp.Store; - } else { - colorAttachment.loadOp = GPULoadOp.Load; - colorAttachment.storeOp = GPUStoreOp.Store; - } - } - } else { - const colorAttachment = descriptor.colorAttachments[0]; - - if (renderContext.clearColor) { - colorAttachment.clearValue = renderContext.clearColorValue; - colorAttachment.loadOp = GPULoadOp.Clear; - colorAttachment.storeOp = GPUStoreOp.Store; - } else { - colorAttachment.loadOp = GPULoadOp.Load; - colorAttachment.storeOp = GPUStoreOp.Store; - } - } - - // - - if (renderContext.depth) { - if (renderContext.clearDepth) { - depthStencilAttachment.depthClearValue = renderContext.clearDepthValue; - depthStencilAttachment.depthLoadOp = GPULoadOp.Clear; - depthStencilAttachment.depthStoreOp = GPUStoreOp.Store; - } else { - depthStencilAttachment.depthLoadOp = GPULoadOp.Load; - depthStencilAttachment.depthStoreOp = GPUStoreOp.Store; - } - } - - if (renderContext.stencil) { - if (renderContext.clearStencil) { - depthStencilAttachment.stencilClearValue = renderContext.clearStencilValue; - depthStencilAttachment.stencilLoadOp = GPULoadOp.Clear; - depthStencilAttachment.stencilStoreOp = GPUStoreOp.Store; - } else { - depthStencilAttachment.stencilLoadOp = GPULoadOp.Load; - depthStencilAttachment.stencilStoreOp = GPUStoreOp.Store; - } - } - - // - - const encoder = device.createCommandEncoder({ label: 'renderContext_' + renderContext.id }); - const currentPass = encoder.beginRenderPass(descriptor); - - // - - renderContextData.descriptor = descriptor; - renderContextData.encoder = encoder; - renderContextData.currentPass = currentPass; - renderContextData.currentSets = { attributes: {} }; - - // - - if (renderContext.viewport) { - this.updateViewport(renderContext); - } - - if (renderContext.scissor) { - const { x, y, width, height } = renderContext.scissorValue; - - currentPass.setScissorRect(x, renderContext.height - height - y, width, height); - } - } - - finishRender(renderContext) { - const renderContextData = this.get(renderContext); - const occlusionQueryCount = renderContext.occlusionQueryCount; - - if (renderContextData.renderBundles !== undefined && renderContextData.renderBundles.length > 0) { - renderContextData.registerBundlesPhase = false; - renderContextData.currentPass.executeBundles(renderContextData.renderBundles); - } - - if (occlusionQueryCount > renderContextData.occlusionQueryIndex) { - renderContextData.currentPass.endOcclusionQuery(); - } - - renderContextData.currentPass.end(); - - if (occlusionQueryCount > 0) { - const bufferSize = occlusionQueryCount * 8; // 8 byte entries for query results - - // - - let queryResolveBuffer = this.occludedResolveCache.get(bufferSize); - - if (queryResolveBuffer === undefined) { - queryResolveBuffer = this.device.createBuffer({ - size: bufferSize, - usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC, - }); - - this.occludedResolveCache.set(bufferSize, queryResolveBuffer); - } - - // - - const readBuffer = this.device.createBuffer({ - size: bufferSize, - usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ, - }); - - // two buffers required here - WebGPU doesn't allow usage of QUERY_RESOLVE & MAP_READ to be combined - renderContextData.encoder.resolveQuerySet( - renderContextData.occlusionQuerySet, - 0, - occlusionQueryCount, - queryResolveBuffer, - 0, - ); - renderContextData.encoder.copyBufferToBuffer(queryResolveBuffer, 0, readBuffer, 0, bufferSize); - - renderContextData.occlusionQueryBuffer = readBuffer; - - // - - this.resolveOccludedAsync(renderContext); - } - - this.prepareTimestampBuffer(renderContext, renderContextData.encoder); - - this.device.queue.submit([renderContextData.encoder.finish()]); - - // - - if (renderContext.textures !== null) { - const textures = renderContext.textures; - - for (let i = 0; i < textures.length; i++) { - const texture = textures[i]; - - if (texture.generateMipmaps === true) { - this.textureUtils.generateMipmaps(texture); - } - } - } - } - - isOccluded(renderContext, object) { - const renderContextData = this.get(renderContext); - - return renderContextData.occluded && renderContextData.occluded.has(object); - } - - async resolveOccludedAsync(renderContext) { - const renderContextData = this.get(renderContext); - - // handle occlusion query results - - const { currentOcclusionQueryBuffer, currentOcclusionQueryObjects } = renderContextData; - - if (currentOcclusionQueryBuffer && currentOcclusionQueryObjects) { - const occluded = new WeakSet(); - - renderContextData.currentOcclusionQueryObjects = null; - renderContextData.currentOcclusionQueryBuffer = null; - - await currentOcclusionQueryBuffer.mapAsync(GPUMapMode.READ); - - const buffer = currentOcclusionQueryBuffer.getMappedRange(); - const results = new BigUint64Array(buffer); - - for (let i = 0; i < currentOcclusionQueryObjects.length; i++) { - if (results[i] !== 0n) { - occluded.add(currentOcclusionQueryObjects[i]); - } - } - - currentOcclusionQueryBuffer.destroy(); - - renderContextData.occluded = occluded; - } - } - - updateViewport(renderContext) { - const { currentPass } = this.get(renderContext); - const { x, y, width, height, minDepth, maxDepth } = renderContext.viewportValue; - - currentPass.setViewport(x, renderContext.height - height - y, width, height, minDepth, maxDepth); - } - - clear(color, depth, stencil, renderTargetData = null) { - const device = this.device; - const renderer = this.renderer; - - let colorAttachments = []; - - let depthStencilAttachment; - let clearValue; - - let supportsDepth; - let supportsStencil; - - if (color) { - const clearColor = this.getClearColor(); - - clearValue = { r: clearColor.r, g: clearColor.g, b: clearColor.b, a: clearColor.a }; - } - - if (renderTargetData === null) { - supportsDepth = renderer.depth; - supportsStencil = renderer.stencil; - - const descriptor = this._getDefaultRenderPassDescriptor(); - - if (color) { - colorAttachments = descriptor.colorAttachments; - - const colorAttachment = colorAttachments[0]; - - colorAttachment.clearValue = clearValue; - colorAttachment.loadOp = GPULoadOp.Clear; - colorAttachment.storeOp = GPUStoreOp.Store; - } - - if (supportsDepth || supportsStencil) { - depthStencilAttachment = descriptor.depthStencilAttachment; - } - } else { - supportsDepth = renderTargetData.depth; - supportsStencil = renderTargetData.stencil; - - if (color) { - for (const texture of renderTargetData.textures) { - const textureData = this.get(texture); - const textureView = textureData.texture.createView(); - - let view, resolveTarget; - - if (textureData.msaaTexture !== undefined) { - view = textureData.msaaTexture.createView(); - resolveTarget = textureView; - } else { - view = textureView; - resolveTarget = undefined; - } - - colorAttachments.push({ - view, - resolveTarget, - clearValue, - loadOp: GPULoadOp.Clear, - storeOp: GPUStoreOp.Store, - }); - } - } - - if (supportsDepth || supportsStencil) { - const depthTextureData = this.get(renderTargetData.depthTexture); - - depthStencilAttachment = { - view: depthTextureData.texture.createView(), - }; - } - } - - // - - if (supportsDepth) { - if (depth) { - depthStencilAttachment.depthLoadOp = GPULoadOp.Clear; - depthStencilAttachment.depthClearValue = renderer.getClearDepth(); - depthStencilAttachment.depthStoreOp = GPUStoreOp.Store; - } else { - depthStencilAttachment.depthLoadOp = GPULoadOp.Load; - depthStencilAttachment.depthStoreOp = GPUStoreOp.Store; - } - } - - // - - if (supportsStencil) { - if (stencil) { - depthStencilAttachment.stencilLoadOp = GPULoadOp.Clear; - depthStencilAttachment.stencilClearValue = renderer.getClearStencil(); - depthStencilAttachment.stencilStoreOp = GPUStoreOp.Store; - } else { - depthStencilAttachment.stencilLoadOp = GPULoadOp.Load; - depthStencilAttachment.stencilStoreOp = GPUStoreOp.Store; - } - } - - // - - const encoder = device.createCommandEncoder({}); - const currentPass = encoder.beginRenderPass({ - colorAttachments, - depthStencilAttachment, - }); - - currentPass.end(); - - device.queue.submit([encoder.finish()]); - } - - // compute - - beginCompute(computeGroup) { - const groupGPU = this.get(computeGroup); - - const descriptor = {}; - - this.initTimestampQuery(computeGroup, descriptor); - - groupGPU.cmdEncoderGPU = this.device.createCommandEncoder(); - - groupGPU.passEncoderGPU = groupGPU.cmdEncoderGPU.beginComputePass(descriptor); - } - - compute(computeGroup, computeNode, bindings, pipeline) { - const { passEncoderGPU } = this.get(computeGroup); - - // pipeline - - const pipelineGPU = this.get(pipeline).pipeline; - passEncoderGPU.setPipeline(pipelineGPU); - - // bind groups - - for (let i = 0, l = bindings.length; i < l; i++) { - const bindGroup = bindings[i]; - const bindingsData = this.get(bindGroup); - - passEncoderGPU.setBindGroup(i, bindingsData.group); - } - - passEncoderGPU.dispatchWorkgroups(computeNode.dispatchCount); - } - - finishCompute(computeGroup) { - const groupData = this.get(computeGroup); - - groupData.passEncoderGPU.end(); - - this.prepareTimestampBuffer(computeGroup, groupData.cmdEncoderGPU); - - this.device.queue.submit([groupData.cmdEncoderGPU.finish()]); - } - - // render object - - draw(renderObject, info) { - const { object, geometry, context, pipeline } = renderObject; - - const bindings = renderObject.getBindings(); - const contextData = this.get(context); - const pipelineGPU = this.get(pipeline).pipeline; - const currentSets = contextData.currentSets; - - const renderObjectData = this.get(renderObject); - - const { bundleEncoder, renderBundle, lastPipelineGPU } = renderObjectData; - - const renderContextData = this.get(context); - - if ( - renderContextData.registerBundlesPhase === true && - bundleEncoder !== undefined && - lastPipelineGPU === pipelineGPU - ) { - renderContextData.renderBundles.push(renderBundle); - return; - } - - const passEncoderGPU = this.renderer._currentRenderBundle - ? this.createBundleEncoder(context, renderObject) - : contextData.currentPass; - - // pipeline - - if (currentSets.pipeline !== pipelineGPU) { - passEncoderGPU.setPipeline(pipelineGPU); - - currentSets.pipeline = pipelineGPU; - } - - // bind groups - - for (let i = 0, l = bindings.length; i < l; i++) { - const bindGroup = bindings[i]; - const bindingsData = this.get(bindGroup); - - passEncoderGPU.setBindGroup(i, bindingsData.group); - } - - // attributes - - const index = renderObject.getIndex(); - - const hasIndex = index !== null; - - // index - - if (hasIndex === true) { - if (currentSets.index !== index) { - const buffer = this.get(index).buffer; - const indexFormat = index.array instanceof Uint16Array ? GPUIndexFormat.Uint16 : GPUIndexFormat.Uint32; - - passEncoderGPU.setIndexBuffer(buffer, indexFormat); - - currentSets.index = index; - } - } - - // vertex buffers - - const vertexBuffers = renderObject.getVertexBuffers(); - - for (let i = 0, l = vertexBuffers.length; i < l; i++) { - const vertexBuffer = vertexBuffers[i]; - - if (currentSets.attributes[i] !== vertexBuffer) { - const buffer = this.get(vertexBuffer).buffer; - passEncoderGPU.setVertexBuffer(i, buffer); - - currentSets.attributes[i] = vertexBuffer; - } - } - - // occlusion queries - handle multiple consecutive draw calls for an object - - if (contextData.occlusionQuerySet !== undefined) { - const lastObject = contextData.lastOcclusionObject; - - if (lastObject !== object) { - if (lastObject !== null && lastObject.occlusionTest === true) { - passEncoderGPU.endOcclusionQuery(); - contextData.occlusionQueryIndex++; - } - - if (object.occlusionTest === true) { - passEncoderGPU.beginOcclusionQuery(contextData.occlusionQueryIndex); - contextData.occlusionQueryObjects[contextData.occlusionQueryIndex] = object; - } - - contextData.lastOcclusionObject = object; - } - } - - // draw - - const drawRange = renderObject.drawRange; - const firstVertex = drawRange.start; - - const instanceCount = this.getInstanceCount(renderObject); - if (instanceCount === 0) return; - - if (hasIndex === true) { - const indexCount = drawRange.count !== Infinity ? drawRange.count : index.count; - - passEncoderGPU.drawIndexed(indexCount, instanceCount, firstVertex, 0, 0); - - info.update(object, indexCount, instanceCount); - } else { - const positionAttribute = geometry.attributes.position; - const vertexCount = drawRange.count !== Infinity ? drawRange.count : positionAttribute.count; - - passEncoderGPU.draw(vertexCount, instanceCount, firstVertex, 0); - - info.update(object, vertexCount, instanceCount); - } - - if (this.renderer._currentRenderBundle) { - const renderBundle = passEncoderGPU.finish(); - renderObjectData.lastPipelineGPU = pipelineGPU; - renderObjectData.renderBundle = renderBundle; - renderObjectData.bundleEncoder = passEncoderGPU; - } - } - - // cache key - - needsRenderUpdate(renderObject) { - const data = this.get(renderObject); - - const { object, material } = renderObject; - - const utils = this.utils; - - const sampleCount = utils.getSampleCount(renderObject.context); - const colorSpace = utils.getCurrentColorSpace(renderObject.context); - const colorFormat = utils.getCurrentColorFormat(renderObject.context); - const depthStencilFormat = utils.getCurrentDepthStencilFormat(renderObject.context); - const primitiveTopology = utils.getPrimitiveTopology(object, material); - - let needsUpdate = false; - - if ( - data.material !== material || - data.materialVersion !== material.version || - data.transparent !== material.transparent || - data.blending !== material.blending || - data.premultipliedAlpha !== material.premultipliedAlpha || - data.blendSrc !== material.blendSrc || - data.blendDst !== material.blendDst || - data.blendEquation !== material.blendEquation || - data.blendSrcAlpha !== material.blendSrcAlpha || - data.blendDstAlpha !== material.blendDstAlpha || - data.blendEquationAlpha !== material.blendEquationAlpha || - data.colorWrite !== material.colorWrite || - data.depthWrite !== material.depthWrite || - data.depthTest !== material.depthTest || - data.depthFunc !== material.depthFunc || - data.stencilWrite !== material.stencilWrite || - data.stencilFunc !== material.stencilFunc || - data.stencilFail !== material.stencilFail || - data.stencilZFail !== material.stencilZFail || - data.stencilZPass !== material.stencilZPass || - data.stencilFuncMask !== material.stencilFuncMask || - data.stencilWriteMask !== material.stencilWriteMask || - data.side !== material.side || - data.alphaToCoverage !== material.alphaToCoverage || - data.sampleCount !== sampleCount || - data.colorSpace !== colorSpace || - data.colorFormat !== colorFormat || - data.depthStencilFormat !== depthStencilFormat || - data.primitiveTopology !== primitiveTopology || - data.clippingContextVersion !== renderObject.clippingContextVersion - ) { - data.material = material; - data.materialVersion = material.version; - data.transparent = material.transparent; - data.blending = material.blending; - data.premultipliedAlpha = material.premultipliedAlpha; - data.blendSrc = material.blendSrc; - data.blendDst = material.blendDst; - data.blendEquation = material.blendEquation; - data.blendSrcAlpha = material.blendSrcAlpha; - data.blendDstAlpha = material.blendDstAlpha; - data.blendEquationAlpha = material.blendEquationAlpha; - data.colorWrite = material.colorWrite; - data.depthWrite = material.depthWrite; - data.depthTest = material.depthTest; - data.depthFunc = material.depthFunc; - data.stencilWrite = material.stencilWrite; - data.stencilFunc = material.stencilFunc; - data.stencilFail = material.stencilFail; - data.stencilZFail = material.stencilZFail; - data.stencilZPass = material.stencilZPass; - data.stencilFuncMask = material.stencilFuncMask; - data.stencilWriteMask = material.stencilWriteMask; - data.side = material.side; - data.alphaToCoverage = material.alphaToCoverage; - data.sampleCount = sampleCount; - data.colorSpace = colorSpace; - data.colorFormat = colorFormat; - data.depthStencilFormat = depthStencilFormat; - data.primitiveTopology = primitiveTopology; - data.clippingContextVersion = renderObject.clippingContextVersion; - - needsUpdate = true; - } - - return needsUpdate; - } - - getRenderCacheKey(renderObject) { - const { object, material } = renderObject; - - const utils = this.utils; - const renderContext = renderObject.context; - - return [ - material.transparent, - material.blending, - material.premultipliedAlpha, - material.blendSrc, - material.blendDst, - material.blendEquation, - material.blendSrcAlpha, - material.blendDstAlpha, - material.blendEquationAlpha, - material.colorWrite, - material.depthWrite, - material.depthTest, - material.depthFunc, - material.stencilWrite, - material.stencilFunc, - material.stencilFail, - material.stencilZFail, - material.stencilZPass, - material.stencilFuncMask, - material.stencilWriteMask, - material.side, - utils.getSampleCount(renderContext), - utils.getCurrentColorSpace(renderContext), - utils.getCurrentColorFormat(renderContext), - utils.getCurrentDepthStencilFormat(renderContext), - utils.getPrimitiveTopology(object, material), - renderObject.clippingContextVersion, - ].join(); - } - - // textures - - createSampler(texture) { - this.textureUtils.createSampler(texture); - } - - destroySampler(texture) { - this.textureUtils.destroySampler(texture); - } - - createDefaultTexture(texture) { - this.textureUtils.createDefaultTexture(texture); - } - - createTexture(texture, options) { - this.textureUtils.createTexture(texture, options); - } - - updateTexture(texture, options) { - this.textureUtils.updateTexture(texture, options); - } - - generateMipmaps(texture) { - this.textureUtils.generateMipmaps(texture); - } - - destroyTexture(texture) { - this.textureUtils.destroyTexture(texture); - } - - copyTextureToBuffer(texture, x, y, width, height) { - return this.textureUtils.copyTextureToBuffer(texture, x, y, width, height); - } - - initTimestampQuery(renderContext, descriptor) { - if (!this.hasFeature(GPUFeatureName.TimestampQuery) || !this.trackTimestamp) return; - - const renderContextData = this.get(renderContext); - - if (!renderContextData.timeStampQuerySet) { - // Create a GPUQuerySet which holds 2 timestamp query results: one for the - // beginning and one for the end of compute pass execution. - const timeStampQuerySet = this.device.createQuerySet({ type: 'timestamp', count: 2 }); - - const timestampWrites = { - querySet: timeStampQuerySet, - beginningOfPassWriteIndex: 0, // Write timestamp in index 0 when pass begins. - endOfPassWriteIndex: 1, // Write timestamp in index 1 when pass ends. - }; - - Object.assign(descriptor, { - timestampWrites, - }); - - renderContextData.timeStampQuerySet = timeStampQuerySet; - } - } - - // timestamp utils - - prepareTimestampBuffer(renderContext, encoder) { - if (!this.hasFeature(GPUFeatureName.TimestampQuery) || !this.trackTimestamp) return; - - const renderContextData = this.get(renderContext); - - const size = 2 * BigInt64Array.BYTES_PER_ELEMENT; - const resolveBuffer = this.device.createBuffer({ - size, - usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC, - }); - - const resultBuffer = this.device.createBuffer({ - size, - usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ, - }); - - encoder.resolveQuerySet(renderContextData.timeStampQuerySet, 0, 2, resolveBuffer, 0); - encoder.copyBufferToBuffer(resolveBuffer, 0, resultBuffer, 0, size); - - renderContextData.currentTimestampQueryBuffer = resultBuffer; - } - - async resolveTimestampAsync(renderContext, type = 'render') { - if (!this.hasFeature(GPUFeatureName.TimestampQuery) || !this.trackTimestamp) return; - - const renderContextData = this.get(renderContext); - const { currentTimestampQueryBuffer } = renderContextData; - - if (currentTimestampQueryBuffer === undefined) return; - - const buffer = currentTimestampQueryBuffer; - - try { - await buffer.mapAsync(GPUMapMode.READ); - const times = new BigUint64Array(buffer.getMappedRange()); - const duration = Number(times[1] - times[0]) / 1000000; - this.renderer.info.updateTimestamp(type, duration); - } catch (error) { - console.error(`Error mapping buffer: ${error}`); - // Optionally handle the error, e.g., re-queue the buffer or skip it - } finally { - buffer.unmap(); - } - } - - // node builder - - createNodeBuilder(object, renderer) { - return new WGSLNodeBuilder(object, renderer); - } - - // program - - createProgram(program) { - const programGPU = this.get(program); - - programGPU.module = { - module: this.device.createShaderModule({ code: program.code, label: program.stage }), - entryPoint: 'main', - }; - } - - destroyProgram(program) { - this.delete(program); - } - - // pipelines - - createRenderPipeline(renderObject, promises) { - this.pipelineUtils.createRenderPipeline(renderObject, promises); - } - - createComputePipeline(computePipeline, bindings) { - this.pipelineUtils.createComputePipeline(computePipeline, bindings); - } - - createBundleEncoder(renderContext, renderObject) { - return this.pipelineUtils.createBundleEncoder(renderContext, renderObject); - } - - // bindings - - createBindings(bindGroup) { - this.bindingUtils.createBindings(bindGroup); - } - - updateBindings(bindGroup) { - this.bindingUtils.createBindings(bindGroup); - } - - updateBinding(binding) { - this.bindingUtils.updateBinding(binding); - } - - // attributes - - createIndexAttribute(attribute) { - this.attributeUtils.createAttribute( - attribute, - GPUBufferUsage.INDEX | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST, - ); - } - - createAttribute(attribute) { - this.attributeUtils.createAttribute( - attribute, - GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST, - ); - } - - createStorageAttribute(attribute) { - this.attributeUtils.createAttribute( - attribute, - GPUBufferUsage.STORAGE | GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST, - ); - } - - updateAttribute(attribute) { - this.attributeUtils.updateAttribute(attribute); - } - - destroyAttribute(attribute) { - this.attributeUtils.destroyAttribute(attribute); - } - - // canvas - - updateSize() { - this.colorBuffer = this.textureUtils.getColorBuffer(); - this.defaultRenderPassdescriptor = null; - } - - // utils public - - getMaxAnisotropy() { - return 16; - } - - hasFeature(name) { - return this.device.features.has(name); - } - - copyTextureToTexture(srcTexture, dstTexture, srcRegion = null, dstPosition = null, level = 0) { - let dstX = 0; - let dstY = 0; - - if (dstPosition !== null) { - dstX = dstPosition.x; - dstY = dstPosition.y; - } - - const encoder = this.device.createCommandEncoder({ - label: 'copyTextureToTexture_' + srcTexture.id + '_' + dstTexture.id, - }); - - const sourceGPU = this.get(srcTexture).texture; - const destinationGPU = this.get(dstTexture).texture; - - encoder.copyTextureToTexture( - { - texture: sourceGPU, - mipLevel: level, - origin: { x: 0, y: 0, z: 0 }, - }, - { - texture: destinationGPU, - mipLevel: level, - origin: { x: dstX, y: dstY, z: 0 }, - }, - [srcTexture.image.width, srcTexture.image.height], - ); - - this.device.queue.submit([encoder.finish()]); - } - - copyFramebufferToTexture(texture, renderContext) { - const renderContextData = this.get(renderContext); - - const { encoder, descriptor } = renderContextData; - - let sourceGPU = null; - - if (renderContext.renderTarget) { - if (texture.isDepthTexture) { - sourceGPU = this.get(renderContext.depthTexture).texture; - } else { - sourceGPU = this.get(renderContext.textures[0]).texture; - } - } else { - if (texture.isDepthTexture) { - sourceGPU = this.textureUtils.getDepthBuffer(renderContext.depth, renderContext.stencil); - } else { - sourceGPU = this.context.getCurrentTexture(); - } - } - - const destinationGPU = this.get(texture).texture; - - if (sourceGPU.format !== destinationGPU.format) { - console.error( - 'WebGPUBackend: copyFramebufferToTexture: Source and destination formats do not match.', - sourceGPU.format, - destinationGPU.format, - ); - - return; - } - - renderContextData.currentPass.end(); - - encoder.copyTextureToTexture( - { - texture: sourceGPU, - origin: { x: 0, y: 0, z: 0 }, - }, - { - texture: destinationGPU, - }, - [texture.image.width, texture.image.height], - ); - - if (texture.generateMipmaps) this.textureUtils.generateMipmaps(texture); - - descriptor.colorAttachments[0].loadOp = GPULoadOp.Load; - if (renderContext.depth) descriptor.depthStencilAttachment.depthLoadOp = GPULoadOp.Load; - if (renderContext.stencil) descriptor.depthStencilAttachment.stencilLoadOp = GPULoadOp.Load; - - renderContextData.currentPass = encoder.beginRenderPass(descriptor); - renderContextData.currentSets = { attributes: {} }; - } -} - -export default WebGPUBackend; diff --git a/examples-jsm/examples/renderers/webgpu/WebGPURenderer.ts b/examples-jsm/examples/renderers/webgpu/WebGPURenderer.ts deleted file mode 100644 index 1e548639..00000000 --- a/examples-jsm/examples/renderers/webgpu/WebGPURenderer.ts +++ /dev/null @@ -1,43 +0,0 @@ -import WebGPU from '../../capabilities/WebGPU.js'; - -import Renderer from '../common/Renderer.js'; -import WebGLBackend from '../webgl/WebGLBackend.js'; -import WebGPUBackend from './WebGPUBackend.js'; -/* -const debugHandler = { - - get: function ( target, name ) { - - // Add |update - if ( /^(create|destroy)/.test( name ) ) console.log( 'WebGPUBackend.' + name ); - - return target[ name ]; - - } - -}; -*/ -class WebGPURenderer extends Renderer { - constructor(parameters = {}) { - let BackendClass; - - if (parameters.forceWebGL) { - BackendClass = WebGLBackend; - } else if (WebGPU.isAvailable()) { - BackendClass = WebGPUBackend; - } else { - BackendClass = WebGLBackend; - - console.warn('THREE.WebGPURenderer: WebGPU is not available, running under WebGL2 backend.'); - } - - const backend = new BackendClass(parameters); - - //super( new Proxy( backend, debugHandler ) ); - super(backend, parameters); - - this.isWebGPURenderer = true; - } -} - -export default WebGPURenderer; diff --git a/examples-jsm/examples/renderers/webgpu/nodes/WGSLNodeBuilder.ts b/examples-jsm/examples/renderers/webgpu/nodes/WGSLNodeBuilder.ts deleted file mode 100644 index 410f2189..00000000 --- a/examples-jsm/examples/renderers/webgpu/nodes/WGSLNodeBuilder.ts +++ /dev/null @@ -1,1009 +0,0 @@ -import { NoColorSpace, FloatType } from 'three'; - -import NodeUniformsGroup from '../../common/nodes/NodeUniformsGroup.js'; - -import NodeSampler from '../../common/nodes/NodeSampler.js'; -import { - NodeSampledTexture, - NodeSampledCubeTexture, - NodeSampledTexture3D, -} from '../../common/nodes/NodeSampledTexture.js'; - -import NodeUniformBuffer from '../../common/nodes/NodeUniformBuffer.js'; -import NodeStorageBuffer from '../../common/nodes/NodeStorageBuffer.js'; - -import { NodeBuilder, CodeNode } from '../../../nodes/Nodes.js'; - -import { getFormat } from '../utils/WebGPUTextureUtils.js'; - -import WGSLNodeParser from './WGSLNodeParser.js'; -import { GPUStorageTextureAccess } from '../utils/WebGPUConstants.js'; - -// GPUShaderStage is not defined in browsers not supporting WebGPU -const GPUShaderStage = self.GPUShaderStage; - -const gpuShaderStageLib = { - vertex: GPUShaderStage ? GPUShaderStage.VERTEX : 1, - fragment: GPUShaderStage ? GPUShaderStage.FRAGMENT : 2, - compute: GPUShaderStage ? GPUShaderStage.COMPUTE : 4, -}; - -const supports = { - swizzleAssign: false, - storageBuffer: true, -}; - -const wgslFnOpLib = { - '^^': 'threejs_xor', -}; - -const wgslTypeLib = { - float: 'f32', - int: 'i32', - uint: 'u32', - bool: 'bool', - color: 'vec3', - - vec2: 'vec2', - ivec2: 'vec2', - uvec2: 'vec2', - bvec2: 'vec2', - - vec3: 'vec3', - ivec3: 'vec3', - uvec3: 'vec3', - bvec3: 'vec3', - - vec4: 'vec4', - ivec4: 'vec4', - uvec4: 'vec4', - bvec4: 'vec4', - - mat2: 'mat2x2', - imat2: 'mat2x2', - umat2: 'mat2x2', - bmat2: 'mat2x2', - - mat3: 'mat3x3', - imat3: 'mat3x3', - umat3: 'mat3x3', - bmat3: 'mat3x3', - - mat4: 'mat4x4', - imat4: 'mat4x4', - umat4: 'mat4x4', - bmat4: 'mat4x4', -}; - -const wgslMethods = { - dFdx: 'dpdx', - dFdy: '- dpdy', - mod_float: 'threejs_mod_float', - mod_vec2: 'threejs_mod_vec2', - mod_vec3: 'threejs_mod_vec3', - mod_vec4: 'threejs_mod_vec4', - equals_bool: 'threejs_equals_bool', - equals_bvec2: 'threejs_equals_bvec2', - equals_bvec3: 'threejs_equals_bvec3', - equals_bvec4: 'threejs_equals_bvec4', - lessThanEqual: 'threejs_lessThanEqual', - greaterThan: 'threejs_greaterThan', - inversesqrt: 'inverseSqrt', - bitcast: 'bitcast', -}; - -const wgslPolyfill = { - threejs_xor: new CodeNode(` -fn threejs_xor( a : bool, b : bool ) -> bool { - - return ( a || b ) && !( a && b ); - -} -`), - lessThanEqual: new CodeNode(` -fn threejs_lessThanEqual( a : vec3, b : vec3 ) -> vec3 { - - return vec3( a.x <= b.x, a.y <= b.y, a.z <= b.z ); - -} -`), - greaterThan: new CodeNode(` -fn threejs_greaterThan( a : vec3, b : vec3 ) -> vec3 { - - return vec3( a.x > b.x, a.y > b.y, a.z > b.z ); - -} -`), - mod_float: new CodeNode('fn threejs_mod_float( x : f32, y : f32 ) -> f32 { return x - y * floor( x / y ); }'), - mod_vec2: new CodeNode('fn threejs_mod_vec2( x : vec2f, y : vec2f ) -> vec2f { return x - y * floor( x / y ); }'), - mod_vec3: new CodeNode('fn threejs_mod_vec3( x : vec3f, y : vec3f ) -> vec3f { return x - y * floor( x / y ); }'), - mod_vec4: new CodeNode('fn threejs_mod_vec4( x : vec4f, y : vec4f ) -> vec4f { return x - y * floor( x / y ); }'), - equals_bool: new CodeNode('fn threejs_equals_bool( a : bool, b : bool ) -> bool { return a == b; }'), - equals_bvec2: new CodeNode( - 'fn threejs_equals_bvec2( a : vec2f, b : vec2f ) -> vec2 { return vec2( a.x == b.x, a.y == b.y ); }', - ), - equals_bvec3: new CodeNode( - 'fn threejs_equals_bvec3( a : vec3f, b : vec3f ) -> vec3 { return vec3( a.x == b.x, a.y == b.y, a.z == b.z ); }', - ), - equals_bvec4: new CodeNode( - 'fn threejs_equals_bvec4( a : vec4f, b : vec4f ) -> vec4 { return vec4( a.x == b.x, a.y == b.y, a.z == b.z, a.w == b.w ); }', - ), - repeatWrapping: new CodeNode(` -fn threejs_repeatWrapping( uv : vec2, dimension : vec2 ) -> vec2 { - - let uvScaled = vec2( uv * vec2( dimension ) ); - - return ( ( uvScaled % dimension ) + dimension ) % dimension; - -} -`), - biquadraticTexture: new CodeNode(` -fn threejs_biquadraticTexture( map : texture_2d, coord : vec2f, level : i32 ) -> vec4f { - - let res = vec2f( textureDimensions( map, level ) ); - - let uvScaled = coord * res; - let uvWrapping = ( ( uvScaled % res ) + res ) % res; - - // https://www.shadertoy.com/view/WtyXRy - - let uv = uvWrapping - 0.5; - let iuv = floor( uv ); - let f = fract( uv ); - - let rg1 = textureLoad( map, vec2i( iuv + vec2( 0.5, 0.5 ) ), level ); - let rg2 = textureLoad( map, vec2i( iuv + vec2( 1.5, 0.5 ) ), level ); - let rg3 = textureLoad( map, vec2i( iuv + vec2( 0.5, 1.5 ) ), level ); - let rg4 = textureLoad( map, vec2i( iuv + vec2( 1.5, 1.5 ) ), level ); - - return mix( mix( rg1, rg2, f.x ), mix( rg3, rg4, f.x ), f.y ); - -} -`), -}; - -class WGSLNodeBuilder extends NodeBuilder { - constructor(object, renderer) { - super(object, renderer, new WGSLNodeParser()); - - this.uniformGroups = {}; - - this.builtins = {}; - } - - needsColorSpaceToLinear(texture) { - return texture.isVideoTexture === true && texture.colorSpace !== NoColorSpace; - } - - _generateTextureSample(texture, textureProperty, uvSnippet, depthSnippet, shaderStage = this.shaderStage) { - if (shaderStage === 'fragment') { - if (depthSnippet) { - return `textureSample( ${textureProperty}, ${textureProperty}_sampler, ${uvSnippet}, ${depthSnippet} )`; - } else { - return `textureSample( ${textureProperty}, ${textureProperty}_sampler, ${uvSnippet} )`; - } - } else if (this.isFilteredTexture(texture)) { - return this.generateFilteredTexture(texture, textureProperty, uvSnippet); - } else { - return this.generateTextureLod(texture, textureProperty, uvSnippet, '0'); - } - } - - _generateVideoSample(textureProperty, uvSnippet, shaderStage = this.shaderStage) { - if (shaderStage === 'fragment') { - return `textureSampleBaseClampToEdge( ${textureProperty}, ${textureProperty}_sampler, vec2( ${uvSnippet}.x, 1.0 - ${uvSnippet}.y ) )`; - } else { - console.error(`WebGPURenderer: THREE.VideoTexture does not support ${shaderStage} shader.`); - } - } - - _generateTextureSampleLevel( - texture, - textureProperty, - uvSnippet, - levelSnippet, - depthSnippet, - shaderStage = this.shaderStage, - ) { - if (shaderStage === 'fragment' && this.isUnfilterable(texture) === false) { - return `textureSampleLevel( ${textureProperty}, ${textureProperty}_sampler, ${uvSnippet}, ${levelSnippet} )`; - } else if (this.isFilteredTexture(texture)) { - return this.generateFilteredTexture(texture, textureProperty, uvSnippet, levelSnippet); - } else { - return this.generateTextureLod(texture, textureProperty, uvSnippet, levelSnippet); - } - } - - generateFilteredTexture(texture, textureProperty, uvSnippet, levelSnippet = '0') { - this._include('biquadraticTexture'); - - return `threejs_biquadraticTexture( ${textureProperty}, ${uvSnippet}, i32( ${levelSnippet} ) )`; - } - - generateTextureLod(texture, textureProperty, uvSnippet, levelSnippet = '0') { - this._include('repeatWrapping'); - - const dimension = `textureDimensions( ${textureProperty}, 0 )`; - - return `textureLoad( ${textureProperty}, threejs_repeatWrapping( ${uvSnippet}, ${dimension} ), i32( ${levelSnippet} ) )`; - } - - generateTextureLoad(texture, textureProperty, uvIndexSnippet, depthSnippet, levelSnippet = '0u') { - if (depthSnippet) { - return `textureLoad( ${textureProperty}, ${uvIndexSnippet}, ${depthSnippet}, ${levelSnippet} )`; - } else { - return `textureLoad( ${textureProperty}, ${uvIndexSnippet}, ${levelSnippet} )`; - } - } - - generateTextureStore(texture, textureProperty, uvIndexSnippet, valueSnippet) { - return `textureStore( ${textureProperty}, ${uvIndexSnippet}, ${valueSnippet} )`; - } - - isUnfilterable(texture) { - return ( - this.getComponentTypeFromTexture(texture) !== 'float' || - (texture.isDataTexture === true && texture.type === FloatType) - ); - } - - generateTexture(texture, textureProperty, uvSnippet, depthSnippet, shaderStage = this.shaderStage) { - let snippet = null; - - if (texture.isVideoTexture === true) { - snippet = this._generateVideoSample(textureProperty, uvSnippet, shaderStage); - } else if (this.isUnfilterable(texture)) { - snippet = this.generateTextureLod(texture, textureProperty, uvSnippet, '0', depthSnippet, shaderStage); - } else { - snippet = this._generateTextureSample(texture, textureProperty, uvSnippet, depthSnippet, shaderStage); - } - - return snippet; - } - - generateTextureGrad( - texture, - textureProperty, - uvSnippet, - gradSnippet, - depthSnippet, - shaderStage = this.shaderStage, - ) { - if (shaderStage === 'fragment') { - // TODO handle i32 or u32 --> uvSnippet, array_index: A, ddx, ddy - return `textureSampleGrad( ${textureProperty}, ${textureProperty}_sampler, ${uvSnippet}, ${gradSnippet[0]}, ${gradSnippet[1]} )`; - } else { - console.error(`WebGPURenderer: THREE.TextureNode.gradient() does not support ${shaderStage} shader.`); - } - } - - generateTextureCompare( - texture, - textureProperty, - uvSnippet, - compareSnippet, - depthSnippet, - shaderStage = this.shaderStage, - ) { - if (shaderStage === 'fragment') { - return `textureSampleCompare( ${textureProperty}, ${textureProperty}_sampler, ${uvSnippet}, ${compareSnippet} )`; - } else { - console.error( - `WebGPURenderer: THREE.DepthTexture.compareFunction() does not support ${shaderStage} shader.`, - ); - } - } - - generateTextureLevel( - texture, - textureProperty, - uvSnippet, - levelSnippet, - depthSnippet, - shaderStage = this.shaderStage, - ) { - let snippet = null; - - if (texture.isVideoTexture === true) { - snippet = this._generateVideoSample(textureProperty, uvSnippet, shaderStage); - } else { - snippet = this._generateTextureSampleLevel( - texture, - textureProperty, - uvSnippet, - levelSnippet, - depthSnippet, - shaderStage, - ); - } - - return snippet; - } - - getPropertyName(node, shaderStage = this.shaderStage) { - if (node.isNodeVarying === true && node.needsInterpolation === true) { - if (shaderStage === 'vertex') { - return `varyings.${node.name}`; - } - } else if (node.isNodeUniform === true) { - const name = node.name; - const type = node.type; - - if (type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' || type === 'texture3D') { - return name; - } else if (type === 'buffer' || type === 'storageBuffer') { - return `NodeBuffer_${node.id}.${name}`; - } else { - return node.groupNode.name + '.' + name; - } - } - - return super.getPropertyName(node); - } - - getOutputStructName() { - return 'output'; - } - - _getUniformGroupCount(shaderStage) { - return Object.keys(this.uniforms[shaderStage]).length; - } - - getFunctionOperator(op) { - const fnOp = wgslFnOpLib[op]; - - if (fnOp !== undefined) { - this._include(fnOp); - - return fnOp; - } - - return null; - } - - getStorageAccess(node) { - if (node.isStorageTextureNode) { - switch (node.access) { - case GPUStorageTextureAccess.ReadOnly: { - return 'read'; - } - - case GPUStorageTextureAccess.WriteOnly: { - return 'write'; - } - - default: { - return 'read_write'; - } - } - } else { - // @TODO: Account for future read-only storage buffer pull request - return 'read_write'; - } - } - - getUniformFromNode(node, type, shaderStage, name = null) { - const uniformNode = super.getUniformFromNode(node, type, shaderStage, name); - const nodeData = this.getDataFromNode(node, shaderStage, this.globalCache); - - if (nodeData.uniformGPU === undefined) { - let uniformGPU; - - const group = node.groupNode; - const groupName = group.name; - - const bindings = this.getBindGroupArray(groupName, shaderStage); - - if (type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' || type === 'texture3D') { - let texture = null; - - if (type === 'texture' || type === 'storageTexture') { - texture = new NodeSampledTexture( - uniformNode.name, - uniformNode.node, - group, - node.access ? node.access : null, - ); - } else if (type === 'cubeTexture') { - texture = new NodeSampledCubeTexture( - uniformNode.name, - uniformNode.node, - group, - node.access ? node.access : null, - ); - } else if (type === 'texture3D') { - texture = new NodeSampledTexture3D( - uniformNode.name, - uniformNode.node, - group, - node.access ? node.access : null, - ); - } - - texture.store = node.isStorageTextureNode === true; - texture.setVisibility(gpuShaderStageLib[shaderStage]); - - if ( - shaderStage === 'fragment' && - this.isUnfilterable(node.value) === false && - texture.store === false - ) { - const sampler = new NodeSampler(`${uniformNode.name}_sampler`, uniformNode.node, group); - sampler.setVisibility(gpuShaderStageLib[shaderStage]); - - bindings.push(sampler, texture); - - uniformGPU = [sampler, texture]; - } else { - bindings.push(texture); - - uniformGPU = [texture]; - } - } else if (type === 'buffer' || type === 'storageBuffer') { - const bufferClass = type === 'storageBuffer' ? NodeStorageBuffer : NodeUniformBuffer; - const buffer = new bufferClass(node, group); - buffer.setVisibility(gpuShaderStageLib[shaderStage]); - - bindings.push(buffer); - - uniformGPU = buffer; - } else { - const uniformsStage = this.uniformGroups[shaderStage] || (this.uniformGroups[shaderStage] = {}); - - let uniformsGroup = uniformsStage[groupName]; - - if (uniformsGroup === undefined) { - uniformsGroup = new NodeUniformsGroup(groupName, group); - uniformsGroup.setVisibility(gpuShaderStageLib[shaderStage]); - - uniformsStage[groupName] = uniformsGroup; - - bindings.push(uniformsGroup); - } - - uniformGPU = this.getNodeUniform(uniformNode, type); - - uniformsGroup.addUniform(uniformGPU); - } - - nodeData.uniformGPU = uniformGPU; - } - - return uniformNode; - } - - getBuiltin(name, property, type, shaderStage = this.shaderStage) { - const map = this.builtins[shaderStage] || (this.builtins[shaderStage] = new Map()); - - if (map.has(name) === false) { - map.set(name, { - name, - property, - type, - }); - } - - return property; - } - - getVertexIndex() { - if (this.shaderStage === 'vertex') { - return this.getBuiltin('vertex_index', 'vertexIndex', 'u32', 'attribute'); - } - - return 'vertexIndex'; - } - - buildFunctionCode(shaderNode) { - const layout = shaderNode.layout; - const flowData = this.flowShaderNode(shaderNode); - - const parameters = []; - - for (const input of layout.inputs) { - parameters.push(input.name + ' : ' + this.getType(input.type)); - } - - // - - const code = `fn ${layout.name}( ${parameters.join(', ')} ) -> ${this.getType(layout.type)} { -${flowData.vars} -${flowData.code} - return ${flowData.result}; - -}`; - - // - - return code; - } - - getInstanceIndex() { - if (this.shaderStage === 'vertex') { - return this.getBuiltin('instance_index', 'instanceIndex', 'u32', 'attribute'); - } - - return 'instanceIndex'; - } - - getFrontFacing() { - return this.getBuiltin('front_facing', 'isFront', 'bool'); - } - - getFragCoord() { - return this.getBuiltin('position', 'fragCoord', 'vec4') + '.xyz'; - } - - getFragDepth() { - return 'output.' + this.getBuiltin('frag_depth', 'depth', 'f32', 'output'); - } - - isFlipY() { - return false; - } - - getBuiltins(shaderStage) { - const snippets = []; - const builtins = this.builtins[shaderStage]; - - if (builtins !== undefined) { - for (const { name, property, type } of builtins.values()) { - snippets.push(`@builtin( ${name} ) ${property} : ${type}`); - } - } - - return snippets.join(',\n\t'); - } - - getAttributes(shaderStage) { - const snippets = []; - - if (shaderStage === 'compute') { - this.getBuiltin('global_invocation_id', 'id', 'vec3', 'attribute'); - } - - if (shaderStage === 'vertex' || shaderStage === 'compute') { - const builtins = this.getBuiltins('attribute'); - - if (builtins) snippets.push(builtins); - - const attributes = this.getAttributesArray(); - - for (let index = 0, length = attributes.length; index < length; index++) { - const attribute = attributes[index]; - const name = attribute.name; - const type = this.getType(attribute.type); - - snippets.push(`@location( ${index} ) ${name} : ${type}`); - } - } - - return snippets.join(',\n\t'); - } - - getStructMembers(struct) { - const snippets = []; - const members = struct.getMemberTypes(); - - for (let i = 0; i < members.length; i++) { - const member = members[i]; - snippets.push(`\t@location( ${i} ) m${i} : ${member}`); - } - - const builtins = this.getBuiltins('output'); - - if (builtins) snippets.push(builtins); - - return snippets.join(',\n'); - } - - getStructs(shaderStage) { - const snippets = []; - const structs = this.structs[shaderStage]; - - for (let index = 0, length = structs.length; index < length; index++) { - const struct = structs[index]; - const name = struct.name; - - let snippet = `\struct ${name} {\n`; - snippet += this.getStructMembers(struct); - snippet += '\n}'; - - snippets.push(snippet); - - snippets.push(`\nvar output : ${name};\n\n`); - } - - return snippets.join('\n\n'); - } - - getVar(type, name) { - return `var ${name} : ${this.getType(type)}`; - } - - getVars(shaderStage) { - const snippets = []; - const vars = this.vars[shaderStage]; - - if (vars !== undefined) { - for (const variable of vars) { - snippets.push(`\t${this.getVar(variable.type, variable.name)};`); - } - } - - return `\n${snippets.join('\n')}\n`; - } - - getVaryings(shaderStage) { - const snippets = []; - - if (shaderStage === 'vertex') { - this.getBuiltin('position', 'Vertex', 'vec4', 'vertex'); - } - - if (shaderStage === 'vertex' || shaderStage === 'fragment') { - const varyings = this.varyings; - const vars = this.vars[shaderStage]; - - for (let index = 0; index < varyings.length; index++) { - const varying = varyings[index]; - - if (varying.needsInterpolation) { - let attributesSnippet = `@location( ${index} )`; - - if (/^(int|uint|ivec|uvec)/.test(varying.type)) { - attributesSnippet += ' @interpolate( flat )'; - } - - snippets.push(`${attributesSnippet} ${varying.name} : ${this.getType(varying.type)}`); - } else if (shaderStage === 'vertex' && vars.includes(varying) === false) { - vars.push(varying); - } - } - } - - const builtins = this.getBuiltins(shaderStage); - - if (builtins) snippets.push(builtins); - - const code = snippets.join(',\n\t'); - - return shaderStage === 'vertex' ? this._getWGSLStruct('VaryingsStruct', '\t' + code) : code; - } - - getUniforms(shaderStage) { - const uniforms = this.uniforms[shaderStage]; - - const bindingSnippets = []; - const bufferSnippets = []; - const structSnippets = []; - const uniformGroups = {}; - - for (const uniform of uniforms) { - const groundName = uniform.groupNode.name; - const uniformIndexes = this.bindingsIndexes[groundName]; - - if ( - uniform.type === 'texture' || - uniform.type === 'cubeTexture' || - uniform.type === 'storageTexture' || - uniform.type === 'texture3D' - ) { - const texture = uniform.node.value; - - if ( - shaderStage === 'fragment' && - this.isUnfilterable(texture) === false && - uniform.node.isStorageTextureNode !== true - ) { - if (texture.isDepthTexture === true && texture.compareFunction !== null) { - bindingSnippets.push( - `@binding( ${uniformIndexes.binding++} ) @group( ${uniformIndexes.group} ) var ${uniform.name}_sampler : sampler_comparison;`, - ); - } else { - bindingSnippets.push( - `@binding( ${uniformIndexes.binding++} ) @group( ${uniformIndexes.group} ) var ${uniform.name}_sampler : sampler;`, - ); - } - } - - let textureType; - - if (texture.isCubeTexture === true) { - textureType = 'texture_cube'; - } else if (texture.isDataArrayTexture === true) { - textureType = 'texture_2d_array'; - } else if (texture.isDepthTexture === true) { - textureType = 'texture_depth_2d'; - } else if (texture.isVideoTexture === true) { - textureType = 'texture_external'; - } else if (texture.isData3DTexture === true) { - textureType = 'texture_3d'; - } else if (uniform.node.isStorageTextureNode === true) { - const format = getFormat(texture); - const access = this.getStorageAccess(uniform.node); - - textureType = `texture_storage_2d<${format}, ${access}>`; - } else { - const componentPrefix = this.getComponentTypeFromTexture(texture).charAt(0); - - textureType = `texture_2d<${componentPrefix}32>`; - } - - bindingSnippets.push( - `@binding( ${uniformIndexes.binding++} ) @group( ${uniformIndexes.group} ) var ${uniform.name} : ${textureType};`, - ); - } else if (uniform.type === 'buffer' || uniform.type === 'storageBuffer') { - const bufferNode = uniform.node; - const bufferType = this.getType(bufferNode.bufferType); - const bufferCount = bufferNode.bufferCount; - - const bufferCountSnippet = bufferCount > 0 ? ', ' + bufferCount : ''; - const bufferSnippet = `\t${uniform.name} : array< ${bufferType}${bufferCountSnippet} >\n`; - const bufferAccessMode = bufferNode.isStorageBufferNode ? 'storage,read_write' : 'uniform'; - - bufferSnippets.push( - this._getWGSLStructBinding( - 'NodeBuffer_' + bufferNode.id, - bufferSnippet, - bufferAccessMode, - uniformIndexes.binding++, - uniformIndexes.group, - ), - ); - } else { - const vectorType = this.getType(this.getVectorType(uniform.type)); - const groupName = uniform.groupNode.name; - - const group = - uniformGroups[groupName] || - (uniformGroups[groupName] = { - index: uniformIndexes.binding++, - id: uniformIndexes.group, - snippets: [], - }); - - group.snippets.push(`\t${uniform.name} : ${vectorType}`); - } - } - - for (const name in uniformGroups) { - const group = uniformGroups[name]; - - structSnippets.push( - this._getWGSLStructBinding(name, group.snippets.join(',\n'), 'uniform', group.index, group.id), - ); - } - - let code = bindingSnippets.join('\n'); - code += bufferSnippets.join('\n'); - code += structSnippets.join('\n'); - - return code; - } - - buildCode() { - const shadersData = this.material !== null ? { fragment: {}, vertex: {} } : { compute: {} }; - - for (const shaderStage in shadersData) { - const stageData = shadersData[shaderStage]; - stageData.uniforms = this.getUniforms(shaderStage); - stageData.attributes = this.getAttributes(shaderStage); - stageData.varyings = this.getVaryings(shaderStage); - stageData.structs = this.getStructs(shaderStage); - stageData.vars = this.getVars(shaderStage); - stageData.codes = this.getCodes(shaderStage); - - // - - let flow = '// code\n\n'; - flow += this.flowCode[shaderStage]; - - const flowNodes = this.flowNodes[shaderStage]; - const mainNode = flowNodes[flowNodes.length - 1]; - - const outputNode = mainNode.outputNode; - const isOutputStruct = outputNode !== undefined && outputNode.isOutputStructNode === true; - - for (const node of flowNodes) { - const flowSlotData = this.getFlowData(node /*, shaderStage*/); - const slotName = node.name; - - if (slotName) { - if (flow.length > 0) flow += '\n'; - - flow += `\t// flow -> ${slotName}\n\t`; - } - - flow += `${flowSlotData.code}\n\t`; - - if (node === mainNode && shaderStage !== 'compute') { - flow += '// result\n\n\t'; - - if (shaderStage === 'vertex') { - flow += `varyings.Vertex = ${flowSlotData.result};`; - } else if (shaderStage === 'fragment') { - if (isOutputStruct) { - stageData.returnType = outputNode.nodeType; - - flow += `return ${flowSlotData.result};`; - } else { - let structSnippet = '\t@location(0) color: vec4'; - - const builtins = this.getBuiltins('output'); - - if (builtins) structSnippet += ',\n\t' + builtins; - - stageData.returnType = 'OutputStruct'; - stageData.structs += this._getWGSLStruct('OutputStruct', structSnippet); - stageData.structs += '\nvar output : OutputStruct;\n\n'; - - flow += `output.color = ${flowSlotData.result};\n\n\treturn output;`; - } - } - } - } - - stageData.flow = flow; - } - - if (this.material !== null) { - this.vertexShader = this._getWGSLVertexCode(shadersData.vertex); - this.fragmentShader = this._getWGSLFragmentCode(shadersData.fragment); - } else { - this.computeShader = this._getWGSLComputeCode( - shadersData.compute, - (this.object.workgroupSize || [64]).join(', '), - ); - } - } - - getMethod(method, output = null) { - let wgslMethod; - - if (output !== null) { - wgslMethod = this._getWGSLMethod(method + '_' + output); - } - - if (wgslMethod === undefined) { - wgslMethod = this._getWGSLMethod(method); - } - - return wgslMethod || method; - } - - getType(type) { - return wgslTypeLib[type] || type; - } - - isAvailable(name) { - let result = supports[name]; - - if (result === undefined) { - if (name === 'float32Filterable') { - result = this.renderer.hasFeature('float32-filterable'); - } - - supports[name] = result; - } - - return result; - } - - _getWGSLMethod(method) { - if (wgslPolyfill[method] !== undefined) { - this._include(method); - } - - return wgslMethods[method]; - } - - _include(name) { - const codeNode = wgslPolyfill[name]; - codeNode.build(this); - - if (this.currentFunctionNode !== null) { - this.currentFunctionNode.includes.push(codeNode); - } - - return codeNode; - } - - _getWGSLVertexCode(shaderData) { - return `${this.getSignature()} - -// uniforms -${shaderData.uniforms} - -// varyings -${shaderData.varyings} -var varyings : VaryingsStruct; - -// codes -${shaderData.codes} - -@vertex -fn main( ${shaderData.attributes} ) -> VaryingsStruct { - - // vars - ${shaderData.vars} - - // flow - ${shaderData.flow} - - return varyings; - -} -`; - } - - _getWGSLFragmentCode(shaderData) { - return `${this.getSignature()} - -// uniforms -${shaderData.uniforms} - -// structs -${shaderData.structs} - -// codes -${shaderData.codes} - -@fragment -fn main( ${shaderData.varyings} ) -> ${shaderData.returnType} { - - // vars - ${shaderData.vars} - - // flow - ${shaderData.flow} - -} -`; - } - - _getWGSLComputeCode(shaderData, workgroupSize) { - return `${this.getSignature()} -// system -var instanceIndex : u32; - -// uniforms -${shaderData.uniforms} - -// codes -${shaderData.codes} - -@compute @workgroup_size( ${workgroupSize} ) -fn main( ${shaderData.attributes} ) { - - // system - instanceIndex = id.x; - - // vars - ${shaderData.vars} - - // flow - ${shaderData.flow} - -} -`; - } - - _getWGSLStruct(name, vars) { - return ` -struct ${name} { -${vars} -};`; - } - - _getWGSLStructBinding(name, vars, access, binding = 0, group = 0) { - const structName = name + 'Struct'; - const structSnippet = this._getWGSLStruct(structName, vars); - - return `${structSnippet} -@binding( ${binding} ) @group( ${group} ) -var<${access}> ${name} : ${structName};`; - } -} - -export default WGSLNodeBuilder; diff --git a/examples-jsm/examples/renderers/webgpu/nodes/WGSLNodeFunction.ts b/examples-jsm/examples/renderers/webgpu/nodes/WGSLNodeFunction.ts deleted file mode 100644 index dfe1a2f3..00000000 --- a/examples-jsm/examples/renderers/webgpu/nodes/WGSLNodeFunction.ts +++ /dev/null @@ -1,127 +0,0 @@ -import NodeFunction from '../../../nodes/core/NodeFunction.js'; -import NodeFunctionInput from '../../../nodes/core/NodeFunctionInput.js'; - -const declarationRegexp = /^[fn]*\s*([a-z_0-9]+)?\s*\(([\s\S]*?)\)\s*[\-\>]*\s*([a-z_0-9]+(?:<[\s\S]+?>)?)/i; -const propertiesRegexp = /([a-z_0-9]+)\s*:\s*([a-z_0-9]+(?:<[\s\S]+?>)?)/gi; - -const wgslTypeLib = { - f32: 'float', - i32: 'int', - u32: 'uint', - bool: 'bool', - - 'vec2': 'vec2', - 'vec2': 'ivec2', - 'vec2': 'uvec2', - 'vec2': 'bvec2', - - vec2f: 'vec2', - vec2i: 'ivec2', - vec2u: 'uvec2', - vec2b: 'bvec2', - - 'vec3': 'vec3', - 'vec3': 'ivec3', - 'vec3': 'uvec3', - 'vec3': 'bvec3', - - vec3f: 'vec3', - vec3i: 'ivec3', - vec3u: 'uvec3', - vec3b: 'bvec3', - - 'vec4': 'vec4', - 'vec4': 'ivec4', - 'vec4': 'uvec4', - 'vec4': 'bvec4', - - vec4f: 'vec4', - vec4i: 'ivec4', - vec4u: 'uvec4', - vec4b: 'bvec4', - - 'mat2x2': 'mat2', - mat2x2f: 'mat2', - - 'mat3x3': 'mat3', - mat3x3f: 'mat3', - - 'mat4x4': 'mat4', - mat4x4f: 'mat4', - - sampler: 'sampler', - texture_2d: 'texture', - texture_cube: 'cubeTexture', - texture_depth_2d: 'depthTexture', - texture_storage_2d: 'storageTexture', - texture_3d: 'texture3D', -}; - -const parse = source => { - source = source.trim(); - - const declaration = source.match(declarationRegexp); - - if (declaration !== null && declaration.length === 4) { - const inputsCode = declaration[2]; - const propsMatches = []; - let match = null; - - while ((match = propertiesRegexp.exec(inputsCode)) !== null) { - propsMatches.push({ name: match[1], type: match[2] }); - } - - // Process matches to correctly pair names and types - const inputs = []; - for (let i = 0; i < propsMatches.length; i++) { - const { name, type } = propsMatches[i]; - - let resolvedType = type; - - if (resolvedType.startsWith('texture')) { - resolvedType = type.split('<')[0]; - } - - resolvedType = wgslTypeLib[resolvedType] || resolvedType; - - inputs.push(new NodeFunctionInput(resolvedType, name)); - } - - const blockCode = source.substring(declaration[0].length); - const outputType = declaration[3] || 'void'; - - const name = declaration[1] !== undefined ? declaration[1] : ''; - const type = wgslTypeLib[outputType] || outputType; - - return { - type, - inputs, - name, - inputsCode, - blockCode, - outputType, - }; - } else { - throw new Error('FunctionNode: Function is not a WGSL code.'); - } -}; - -class WGSLNodeFunction extends NodeFunction { - constructor(source) { - const { type, inputs, name, inputsCode, blockCode, outputType } = parse(source); - - super(type, inputs, name); - - this.inputsCode = inputsCode; - this.blockCode = blockCode; - this.outputType = outputType; - } - - getCode(name = this.name) { - const outputType = this.outputType !== 'void' ? '-> ' + this.outputType : ''; - - return `fn ${name} ( ${this.inputsCode.trim()} ) ${outputType}` + this.blockCode; - } -} - -export default WGSLNodeFunction; diff --git a/examples-jsm/examples/renderers/webgpu/nodes/WGSLNodeParser.ts b/examples-jsm/examples/renderers/webgpu/nodes/WGSLNodeParser.ts deleted file mode 100644 index c32133df..00000000 --- a/examples-jsm/examples/renderers/webgpu/nodes/WGSLNodeParser.ts +++ /dev/null @@ -1,10 +0,0 @@ -import NodeParser from '../../../nodes/core/NodeParser.js'; -import WGSLNodeFunction from './WGSLNodeFunction.js'; - -class WGSLNodeParser extends NodeParser { - parseFunction(source) { - return new WGSLNodeFunction(source); - } -} - -export default WGSLNodeParser;