From 0b2f945570f4797fe4e90a54f3c1de49bd34c859 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sun, 26 May 2024 20:29:00 -0400 Subject: [PATCH 1/5] Add examples --- examples-jsm/create-examples.js | 3 + examples-jsm/examples/nodes/Nodes.ts | 416 +++++ .../examples/nodes/accessors/TextureNode.ts | 329 ++++ examples-jsm/examples/nodes/core/InputNode.ts | 65 + examples-jsm/examples/nodes/core/Node.ts | 411 +++++ .../examples/nodes/core/NodeAttribute.ts | 11 + .../examples/nodes/core/NodeBuilder.ts | 1011 +++++++++++++ examples-jsm/examples/nodes/core/NodeCache.ts | 18 + examples-jsm/examples/nodes/core/NodeCode.ts | 11 + examples-jsm/examples/nodes/core/NodeFrame.ts | 101 ++ .../examples/nodes/core/NodeKeywords.ts | 58 + .../examples/nodes/core/NodeParser.ts | 7 + .../examples/nodes/core/NodeUniform.ts | 28 + examples-jsm/examples/nodes/core/NodeUtils.ts | 132 ++ examples-jsm/examples/nodes/core/NodeVar.ts | 10 + .../examples/nodes/core/NodeVarying.ts | 13 + .../examples/nodes/core/UniformGroupNode.ts | 30 + .../examples/nodes/core/UniformNode.ts | 83 + 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 | 118 ++ .../examples/nodes/lighting/LightsNode.ts | 157 ++ .../examples/renderers/common/Animation.ts | 38 + .../examples/renderers/common/Attributes.ts | 51 + .../examples/renderers/common/Backend.ts | 167 +++ .../examples/renderers/common/Background.ts | 118 ++ .../examples/renderers/common/Binding.ts | 17 + .../examples/renderers/common/Bindings.ts | 153 ++ .../examples/renderers/common/Buffer.ts | 28 + .../examples/renderers/common/BufferUtils.ts | 23 + .../examples/renderers/common/ChainMap.ts | 59 + .../renderers/common/ClippingContext.ts | 128 ++ .../examples/renderers/common/Color4.ts | 27 + .../renderers/common/ComputePipeline.ts | 13 + .../examples/renderers/common/Constants.ts | 14 + .../examples/renderers/common/DataMap.ts | 38 + .../examples/renderers/common/Geometries.ts | 163 ++ .../examples/renderers/common/Info.ts | 76 + .../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 | 211 +++ .../renderers/common/RenderObjects.ts | 100 ++ .../renderers/common/RenderPipeline.ts | 12 + .../examples/renderers/common/Renderer.ts | 1336 +++++++++++++++++ .../examples/renderers/common/Textures.ts | 288 ++++ .../renderers/common/UniformBuffer.ts | 11 + .../renderers/common/UniformsGroup.ts | 247 +++ .../common/nodes/NodeBuilderState.ts | 43 + .../common/nodes/NodeUniformsGroup.ts | 34 + .../examples/renderers/common/nodes/Nodes.ts | 385 +++++ .../examples/renderers/webgl/WebGLBackend.ts | 1190 +++++++++++++++ .../renderers/webgl/nodes/GLSLNodeBuilder.ts | 655 ++++++++ .../renderers/webgpu/WebGPUBackend.ts | 1186 +++++++++++++++ .../renderers/webgpu/WebGPURenderer.ts | 43 + .../renderers/webgpu/nodes/WGSLNodeBuilder.ts | 921 ++++++++++++ .../webgpu/nodes/WGSLNodeFunction.ts | 87 ++ .../renderers/webgpu/nodes/WGSLNodeParser.ts | 10 + 64 files changed, 11602 insertions(+) create mode 100644 examples-jsm/examples/nodes/Nodes.ts create mode 100644 examples-jsm/examples/nodes/accessors/TextureNode.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/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/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/LightsNode.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/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/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/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/Textures.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/nodes/NodeBuilderState.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/create-examples.js b/examples-jsm/create-examples.js index 0693e1f3a..9d01b3a62 100644 --- a/examples-jsm/create-examples.js +++ b/examples-jsm/create-examples.js @@ -4,7 +4,9 @@ import * as path from 'node:path'; import prettier from 'prettier'; const files = [ + 'nodes/accessors/TextureNode', 'nodes/core/constants', + 'nodes/core/InputNode', 'nodes/core/Node', 'nodes/core/NodeAttribute', 'nodes/core/NodeBuilder', @@ -18,6 +20,7 @@ const files = [ 'nodes/core/NodeVar', 'nodes/core/NodeVarying', 'nodes/core/UniformGroupNode', + 'nodes/core/UniformNode', 'nodes/fog/FogNode', 'nodes/gpgpu/ComputeNode', 'nodes/lighting/EnvironmentNode', diff --git a/examples-jsm/examples/nodes/Nodes.ts b/examples-jsm/examples/nodes/Nodes.ts new file mode 100644 index 000000000..affc6ebf8 --- /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, + 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 } 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 { + default as PositionNode, + positionGeometry, + positionLocal, + positionWorld, + positionWorldDirection, + positionView, + positionViewDirection, +} from './accessors/PositionNode.js'; +export { default as ReferenceNode, reference, referenceBuffer } from './accessors/ReferenceNode.js'; +export { default as ReflectVectorNode, reflectVector } 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 TextureStoreNode, textureStore } from './accessors/TextureStoreNode.js'; +export { default as Texture3DNode } from './accessors/Texture3DNode.js'; +export { default as UVNode, uv } 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, + depthTexture, + depthPixel, +} 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 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/TextureNode.ts b/examples-jsm/examples/nodes/accessors/TextureNode.ts new file mode 100644 index 000000000..73a989fe4 --- /dev/null +++ b/examples-jsm/examples/nodes/accessors/TextureNode.ts @@ -0,0 +1,329 @@ +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}`); + + if (builder.context.tempWrite !== false) { + 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/core/InputNode.ts b/examples-jsm/examples/nodes/core/InputNode.ts new file mode 100644 index 000000000..4d52ec26f --- /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 000000000..438c44dd1 --- /dev/null +++ b/examples-jsm/examples/nodes/core/Node.ts @@ -0,0 +1,411 @@ +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.uuid = MathUtils.generateUUID(); + + this.version = 0; + + this._cacheKey = null; + this._cacheKeyVersion = 0; + + 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 false; + } + + *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; + } + + 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); + + for (const childNode of this.getChildren()) { + nodeProperties['_node' + childNode.id] = 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.'); + } + + 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 || builder.context.tempRead === false) { + 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 /*|| builder.context.tempRead === false*/) { + 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 000000000..190fe8c51 --- /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 000000000..ebdc13ff1 --- /dev/null +++ b/examples-jsm/examples/nodes/core/NodeBuilder.ts @@ -0,0 +1,1011 @@ +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 { + FloatNodeUniform, + Vector2NodeUniform, + Vector3NodeUniform, + Vector4NodeUniform, + ColorNodeUniform, + Matrix3NodeUniform, + Matrix4NodeUniform, +} from '../../renderers/common/nodes/NodeUniform.js'; + +import { + REVISION, + RenderTarget, + Color, + Vector2, + Vector3, + Vector4, + IntType, + UnsignedIntType, + Float16BufferAttribute, +} 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 uniformsGroupCache = new ChainMap(); + +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, scene = null, material = null) { + this.object = object; + this.material = material || (object && object.material) || null; + this.geometry = (object && object.geometry) || null; + this.renderer = renderer; + this.parser = parser; + this.scene = scene; + + this.nodes = []; + this.updateNodes = []; + this.updateBeforeNodes = []; + 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.bindingsOffset = { vertex: 0, fragment: 0, compute: 0 }; + this.bindingsArray = 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.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; + } + + 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); + } + + _getSharedBindings(bindings) { + const shared = []; + + for (const binding of bindings) { + if (binding.shared === true) { + // nodes is the chainmap key + const nodes = binding.getNodes(); + + let sharedBinding = uniformsGroupCache.get(nodes); + + if (sharedBinding === undefined) { + uniformsGroupCache.set(nodes, binding); + + sharedBinding = binding; + } + + shared.push(sharedBinding); + } else { + shared.push(binding); + } + } + + return shared; + } + + getBindings() { + let bindingsArray = this.bindingsArray; + + if (bindingsArray === null) { + const bindings = this.bindings; + + this.bindingsArray = bindingsArray = this._getSharedBindings( + this.material !== null ? [...bindings.vertex, ...bindings.fragment] : bindings.compute, + ); + } + + return bindingsArray; + } + + 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(); + + if (updateType !== NodeUpdateType.NONE) { + this.updateNodes.push(node.getSelf()); + } + + if (updateBeforeType !== NodeUpdateType.NONE) { + this.updateBeforeNodes.push(node); + } + } + } + + get currentNode() { + return this.chaining[this.chaining.length - 1]; + } + + 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; + } + + 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; + } + + generateMethod(method) { + return method; + } + + 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 === '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.getNodeData(node); + + if (nodeData === undefined) { + nodeData = {}; + + cache.setNodeData(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 previousBuildStage = this.buildStage; + + const flow = { + code: '', + }; + + this.flow = flow; + this.vars = {}; + + 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.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') return new FloatNodeUniform(uniformNode); + if (type === 'vec2') return new Vector2NodeUniform(uniformNode); + if (type === 'vec3') return new Vector3NodeUniform(uniformNode); + if (type === 'vec4') 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[0] !== toType[0]) { + // 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} - NodeMaterial 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 000000000..96a7e0c76 --- /dev/null +++ b/examples-jsm/examples/nodes/core/NodeCache.ts @@ -0,0 +1,18 @@ +let id = 0; + +class NodeCache { + constructor() { + this.id = id++; + this.nodesData = new WeakMap(); + } + + getNodeData(node) { + return this.nodesData.get(node); + } + + setNodeData(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 000000000..2ee509037 --- /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 000000000..b8e8d37b6 --- /dev/null +++ b/examples-jsm/examples/nodes/core/NodeFrame.ts @@ -0,0 +1,101 @@ +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.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); + } + } + + 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/NodeKeywords.ts b/examples-jsm/examples/nodes/core/NodeKeywords.ts new file mode 100644 index 000000000..53da9bf50 --- /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 000000000..9849452f1 --- /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 000000000..2918e219c --- /dev/null +++ b/examples-jsm/examples/nodes/core/NodeUniform.ts @@ -0,0 +1,28 @@ +class NodeUniform { + constructor(name, type, node, needsUpdate = undefined) { + this.isNodeUniform = true; + + this.name = name; + this.type = type; + this.node = node.getSelf(); + this.needsUpdate = needsUpdate; + } + + 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 000000000..16a5f3246 --- /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 000000000..e6e935b31 --- /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 000000000..a14823628 --- /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/UniformGroupNode.ts b/examples-jsm/examples/nodes/core/UniformGroupNode.ts new file mode 100644 index 000000000..f8bb2b37d --- /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 000000000..90e866481 --- /dev/null +++ b/examples-jsm/examples/nodes/core/UniformNode.ts @@ -0,0 +1,83 @@ +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.groupNode = objectGroup; + } + + 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, + 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 000000000..3b01a9a6d --- /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 000000000..9417df5a5 --- /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 000000000..ab2925a55 --- /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 000000000..56f8109c2 --- /dev/null +++ b/examples-jsm/examples/nodes/lighting/EnvironmentNode.ts @@ -0,0 +1,118 @@ +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); + + // + + builder.context.radiance.addAssign(isolateRadiance); + + builder.context.iblIrradiance.addAssign(irradiance); + + // + + 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/LightsNode.ts b/examples-jsm/examples/nodes/lighting/LightsNode.ts new file mode 100644 index 000000000..b3695ea8b --- /dev/null +++ b/examples-jsm/examples/nodes/lighting/LightsNode.ts @@ -0,0 +1,157 @@ +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; + } + + 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(); + + // + + 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/renderers/common/Animation.ts b/examples-jsm/examples/renderers/common/Animation.ts new file mode 100644 index 000000000..0b00319a1 --- /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 000000000..ed9e8e9ec --- /dev/null +++ b/examples-jsm/examples/renderers/common/Attributes.ts @@ -0,0 +1,51 @@ +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); + } + } + + 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 000000000..e48a8029a --- /dev/null +++ b/examples-jsm/examples/renderers/common/Backend.ts @@ -0,0 +1,167 @@ +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) {} + + updateBindings(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.isInstancedMesh ? 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 000000000..b7902dd40 --- /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/Binding.ts b/examples-jsm/examples/renderers/common/Binding.ts new file mode 100644 index 000000000..a12f3563b --- /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 000000000..9485ec3b5 --- /dev/null +++ b/examples-jsm/examples/renderers/common/Bindings.ts @@ -0,0 +1,153 @@ +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(); + + const data = this.get(renderObject); + + if (data.bindings !== bindings) { + // each object defines an array of bindings (ubos, textures, samplers etc.) + + data.bindings = bindings; + + this._init(bindings); + + this.backend.createBindings(bindings); + } + + return data.bindings; + } + + getForCompute(computeNode) { + const data = this.get(computeNode); + + if (data.bindings === undefined) { + const nodeBuilderState = this.nodes.getForCompute(computeNode); + + const bindings = nodeBuilderState.bindings; + + data.bindings = bindings; + + this._init(bindings); + + this.backend.createBindings(bindings); + } + + return data.bindings; + } + + updateForCompute(computeNode) { + this._update(computeNode, this.getForCompute(computeNode)); + } + + updateForRender(renderObject) { + this._update(renderObject, this.getForRender(renderObject)); + } + + _init(bindings) { + for (const binding of 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, 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 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(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 000000000..17013c6dc --- /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 000000000..99ddcb48b --- /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 000000000..e233becaf --- /dev/null +++ b/examples-jsm/examples/renderers/common/ChainMap.ts @@ -0,0 +1,59 @@ +export default class ChainMap { + constructor() { + this.weakMap = new WeakMap(); + } + + get(keys) { + if (Array.isArray(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]); + } else { + return super.get(keys); + } + } + + set(keys, value) { + if (Array.isArray(keys)) { + 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); + } else { + return super.set(keys, value); + } + } + + delete(keys) { + if (Array.isArray(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]); + } else { + return super.delete(keys); + } + } + + dispose() { + this.weakMap.clear(); + } +} diff --git a/examples-jsm/examples/renderers/common/ClippingContext.ts b/examples-jsm/examples/renderers/common/ClippingContext.ts new file mode 100644 index 000000000..312e0b779 --- /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 000000000..c681cc908 --- /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 000000000..0fd3ca531 --- /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 000000000..0d0c35a25 --- /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/DataMap.ts b/examples-jsm/examples/renderers/common/DataMap.ts new file mode 100644 index 000000000..006bc2950 --- /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 000000000..5da999460 --- /dev/null +++ b/examples-jsm/examples/renderers/common/Geometries.ts @@ -0,0 +1,163 @@ +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) { + 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 (this.attributeCall.get(attribute) !== callId) { + this.attributes.update(attribute, type); + + 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 000000000..c8e7cb41b --- /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/Pipelines.ts b/examples-jsm/examples/renderers/common/Pipelines.ts new file mode 100644 index 000000000..f6c570c4c --- /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(computeNode); + + 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); + } + } + + 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 000000000..a684e4443 --- /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 000000000..e59e49378 --- /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 000000000..291403652 --- /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 000000000..3b43028eb --- /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 000000000..630a2e42d --- /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 000000000..a72a91dfd --- /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 000000000..3fc3134e6 --- /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 000000000..861c15dc3 --- /dev/null +++ b/examples-jsm/examples/renderers/common/RenderObject.ts @@ -0,0 +1,211 @@ +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 + ','; + } + } + + 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 000000000..76dc482e4 --- /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 000000000..0ec34b043 --- /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 000000000..acf180d84 --- /dev/null +++ b/examples-jsm/examples/renderers/common/Renderer.ts @@ -0,0 +1,1336 @@ +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, + }; + } + + 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); + } + } + } + + 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); + } + + getArrayBuffer(attribute) { + // @deprecated, r155 + + console.warn('THREE.Renderer: getArrayBuffer() is deprecated. Use getArrayBufferAsync() instead.'); + + return this.getArrayBufferAsync(attribute); + } + + 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); + } + + 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); + + material.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); + } + } + + _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); + } + + get compute() { + return this.computeAsync; + } + + get compile() { + return this.compileAsync; + } +} + +export default Renderer; diff --git a/examples-jsm/examples/renderers/common/Textures.ts b/examples-jsm/examples/renderers/common/Textures.ts new file mode 100644 index 000000000..0eb0509ca --- /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/UniformBuffer.ts b/examples-jsm/examples/renderers/common/UniformBuffer.ts new file mode 100644 index 000000000..28aac0d7e --- /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 000000000..beb353479 --- /dev/null +++ b/examples-jsm/examples/renderers/common/UniformsGroup.ts @@ -0,0 +1,247 @@ +import UniformBuffer from './UniformBuffer.js'; +import { GPU_CHUNK_BYTES } from './Constants.js'; + +class UniformsGroup extends UniformBuffer { + constructor(name) { + super(name); + + this.isUniformsGroup = true; + + // 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 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.isFloatUniform) 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.buffer; + const v = uniform.getValue(); + const offset = uniform.offset; + + if (a[offset] !== v) { + a[offset] = v; + updated = true; + } + + return updated; + } + + updateVector2(uniform) { + let updated = false; + + const a = this.buffer; + const v = uniform.getValue(); + const offset = uniform.offset; + + if (a[offset + 0] !== v.x || a[offset + 1] !== v.y) { + a[offset + 0] = v.x; + a[offset + 1] = v.y; + + updated = true; + } + + return updated; + } + + updateVector3(uniform) { + let updated = false; + + const a = this.buffer; + 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 + 0] = v.x; + a[offset + 1] = v.y; + a[offset + 2] = v.z; + + updated = true; + } + + return updated; + } + + updateVector4(uniform) { + let updated = false; + + const a = this.buffer; + 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) { + a[offset + 0] = v.x; + a[offset + 1] = v.y; + a[offset + 2] = v.z; + a[offset + 3] = v.w; + + updated = true; + } + + return updated; + } + + updateColor(uniform) { + let updated = false; + + const a = this.buffer; + const c = uniform.getValue(); + const offset = uniform.offset; + + if (a[offset + 0] !== c.r || a[offset + 1] !== c.g || a[offset + 2] !== c.b) { + a[offset + 0] = c.r; + a[offset + 1] = c.g; + a[offset + 2] = c.b; + + updated = true; + } + + return updated; + } + + updateMatrix3(uniform) { + let updated = false; + + const a = this.buffer; + 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] + ) { + 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]; + + updated = true; + } + + return updated; + } + + updateMatrix4(uniform) { + let updated = false; + + const a = this.buffer; + const e = uniform.getValue().elements; + const offset = uniform.offset; + + if (arraysEqual(a, e, offset) === false) { + a.set(e, offset); + updated = true; + } + + return updated; + } +} + +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/nodes/NodeBuilderState.ts b/examples-jsm/examples/renderers/common/nodes/NodeBuilderState.ts new file mode 100644 index 000000000..5553bd2d0 --- /dev/null +++ b/examples-jsm/examples/renderers/common/nodes/NodeBuilderState.ts @@ -0,0 +1,43 @@ +class NodeBuilderState { + constructor( + vertexShader, + fragmentShader, + computeShader, + nodeAttributes, + bindings, + updateNodes, + updateBeforeNodes, + 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.usedTimes = 0; + } + + createBindings() { + const bindingsArray = []; + + for (const instanceBinding of this.bindings) { + let binding = instanceBinding; + + if (instanceBinding.shared !== true) { + binding = instanceBinding.clone(); + } + + bindingsArray.push(binding); + } + + return bindingsArray; + } +} + +export default NodeBuilderState; 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 000000000..0bbc1adde --- /dev/null +++ b/examples-jsm/examples/renderers/common/nodes/NodeUniformsGroup.ts @@ -0,0 +1,34 @@ +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; + } + + get shared() { + return this.groupNode.shared; + } + + 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 000000000..86df5654c --- /dev/null +++ b/examples-jsm/examples/renderers/common/nodes/Nodes.ts @@ -0,0 +1,385 @@ +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, + renderObject.scene, + ); + nodeBuilder.material = renderObject.material; + 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.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); + } + } + + 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 000000000..b9a999f14 --- /dev/null +++ b/examples-jsm/examples/renderers/webgl/WebGLBackend.ts @@ -0,0 +1,1190 @@ +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, scene = null) { + return new GLSLNodeBuilder(object, renderer, scene); + } + + // 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); + } + + _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) { + console.error('THREE.WebGLBackend:', gl.getProgramInfoLog(programGPU)); + + console.error('THREE.WebGLBackend:', gl.getShaderInfoLog(fragmentShader)); + console.error('THREE.WebGLBackend:', gl.getShaderInfoLog(vertexShader)); + } + + gl.useProgram(programGPU); + + // Bindings + + this._setupBindings(renderObject.getBindings(), 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) { + console.error('THREE.WebGLBackend:', gl.getProgramInfoLog(programGPU)); + + console.error('THREE.WebGLBackend:', gl.getShaderInfoLog(fragmentShader)); + console.error('THREE.WebGLBackend:', gl.getShaderInfoLog(vertexShader)); + } + + gl.useProgram(programGPU); + + // Bindings + + this.createBindings(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(bindings) { + this.updateBindings(bindings); + } + + updateBindings(bindings) { + const { gl } = this; + + let groupIndex = 0; + let textureIndex = 0; + + for (const binding of 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) { + //console.warn( 'Abstract class.' ); + } + + 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 binding of 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 binding of 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 000000000..4910f51f6 --- /dev/null +++ b/examples-jsm/examples/renderers/webgl/nodes/GLSLNodeBuilder.ts @@ -0,0 +1,655 @@ +import { MathNode, GLSLNodeParser, NodeBuilder, UniformNode, 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 { 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 = { + instance: true, + swizzleAssign: true, +}; + +const defaultPrecisions = ` +precision highp float; +precision highp int; +precision highp sampler3D; +precision mediump sampler2DArray; +precision lowp sampler2DShadow; +`; + +class GLSLNodeBuilder extends NodeBuilder { + constructor(object, renderer, scene = null) { + super(object, renderer, new GLSLNodeParser(), scene); + + this.uniformGroups = {}; + this.transforms = []; + } + + getMethod(method) { + return glslMethods[method] || method; + } + + getPropertyName(node, shaderStage) { + if (node.isOutputStructVar) return ''; + + return super.getPropertyName(node, shaderStage); + } + + 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; + let format = RedFormat; + + if (itemSize === 2) { + format = RGFormat; + } else if (itemSize === 3) { + format = RGBFormat; + } else if (itemSize === 4) { + format = RGBAFormat; + } + + 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 Float32Array(newSize); + + newArray.set(originalArray, 0); + + attribute.array = newArray; + + const pboTexture = new DataTexture(attribute.array, width, height, format, FloatType); + pboTexture.needsUpdate = true; + pboTexture.isPBOTexture = true; + + const pbo = new UniformNode(pboTexture); + pbo.setPrecision('high'); + + attribute.pboNode = pbo; + attribute.pbo = pbo.value; + + this.getUniformFromNode(attribute.pboNode, 'texture', this.shaderStage, this.context.label); + } + } + + generatePBO(storageArrayElementNode) { + const { node, indexNode } = storageArrayElementNode; + const attribute = node.value; + + 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'); + + // + + this.addLineFlowCode(`${propertyName} = ${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) { + if (variable.isOutputStructVar) continue; + + 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; + + if (texture.compareFunction) { + snippet = `sampler2DShadow ${uniform.name};`; + } else if (texture.isDataArrayTexture === true) { + snippet = `sampler2DArray ${uniform.name};`; + } else { + snippet = `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} ${uniform.name};`; + + 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 === 'int' || type === 'uint' ? '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 === 'int' || type === 'uint' ? '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) { + return supports[name] === true; + } + + 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) { + if (type === 'texture') { + uniformGPU = new NodeSampledTexture(uniformNode.name, uniformNode.node); + + this.bindings[shaderStage].push(uniformGPU); + } else if (type === 'cubeTexture') { + uniformGPU = new NodeSampledCubeTexture(uniformNode.name, uniformNode.node); + + this.bindings[shaderStage].push(uniformGPU); + } else if (type === 'texture3D') { + uniformGPU = new NodeSampledTexture3D(uniformNode.name, uniformNode.node); + this.bindings[shaderStage].push(uniformGPU); + } else if (type === 'buffer') { + node.name = `NodeBuffer_${node.id}`; + uniformNode.name = `buffer${node.id}`; + + const buffer = new NodeUniformBuffer(node); + buffer.name = node.name; + + this.bindings[shaderStage].push(buffer); + + uniformGPU = buffer; + } else { + const group = node.groupNode; + const groupName = group.name; + + 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; + + this.bindings[shaderStage].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 000000000..97a42577f --- /dev/null +++ b/examples-jsm/examples/renderers/webgpu/WebGPUBackend.ts @@ -0,0 +1,1186 @@ +/*// 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 group + + const bindGroupGPU = this.get(bindings).group; + passEncoderGPU.setBindGroup(0, bindGroupGPU); + + 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 bindingsData = this.get(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 group + + const bindGroupGPU = bindingsData.group; + passEncoderGPU.setBindGroup(0, bindGroupGPU); + + // 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, scene = null) { + return new WGSLNodeBuilder(object, renderer, scene); + } + + // 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(bindings) { + this.bindingUtils.createBindings(bindings); + } + + updateBindings(bindings) { + this.bindingUtils.createBindings(bindings); + } + + 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 000000000..1e548639e --- /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 000000000..ed6e38f97 --- /dev/null +++ b/examples-jsm/examples/renderers/webgpu/nodes/WGSLNodeBuilder.ts @@ -0,0 +1,921 @@ +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'; + +// 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 = { + instance: true, + 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; + +} +`), +}; + +class WGSLNodeBuilder extends NodeBuilder { + constructor(object, renderer, scene = null) { + super(object, renderer, new WGSLNodeParser(), scene); + + 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 { + return this.generateTextureLod(texture, textureProperty, uvSnippet); + } + } + + _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 { + return this.generateTextureLod(texture, textureProperty, uvSnippet, 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); + } + + _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; + } + + 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 bindings = this.bindings[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); + } else if (type === 'cubeTexture') { + texture = new NodeSampledCubeTexture(uniformNode.name, uniformNode.node); + } else if (type === 'texture3D') { + texture = new NodeSampledTexture3D(uniformNode.name, uniformNode.node); + } + + texture.store = node.isStoreTextureNode === 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); + 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); + buffer.setVisibility(gpuShaderStageLib[shaderStage]); + + bindings.push(buffer); + + uniformGPU = buffer; + } else { + const group = node.groupNode; + const groupName = group.name; + + 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; + + if (shaderStage === 'vertex') { + this.bindingsOffset['fragment'] = bindings.length; + } + } + + return uniformNode; + } + + isReference(type) { + return ( + super.isReference(type) || + type === 'texture_2d' || + type === 'texture_cube' || + type === 'texture_depth_2d' || + type === 'texture_storage_2d' || + type === 'texture_3d' + ); + } + + 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}`); + } + + 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); + } + + 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 = {}; + + let index = this.bindingsOffset[shaderStage]; + + for (const uniform of uniforms) { + 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.isStoreTextureNode !== true + ) { + if (texture.isDepthTexture === true && texture.compareFunction !== null) { + bindingSnippets.push( + `@binding( ${index++} ) @group( 0 ) var ${uniform.name}_sampler : sampler_comparison;`, + ); + } else { + bindingSnippets.push( + `@binding( ${index++} ) @group( 0 ) 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.isStoreTextureNode === true) { + const format = getFormat(texture); + + textureType = `texture_storage_2d<${format}, write>`; + } else { + const componentPrefix = this.getComponentTypeFromTexture(texture).charAt(0); + + textureType = `texture_2d<${componentPrefix}32>`; + } + + bindingSnippets.push(`@binding( ${index++} ) @group( 0 ) 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, index++), + ); + } else { + const vectorType = this.getType(this.getVectorType(uniform.type)); + const groupName = uniform.groupNode.name; + + const group = + uniformGroups[groupName] || + (uniformGroups[groupName] = { + index: index++, + 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)); + } + + 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) { + return supports[name] === true; + } + + _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 000000000..3abfc4ad6 --- /dev/null +++ b/examples-jsm/examples/renderers/webgpu/nodes/WGSLNodeFunction.ts @@ -0,0 +1,87 @@ +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]+)?/i; +const propertiesRegexp = /[a-z_0-9]+|<(.*?)>+/gi; + +const wgslTypeLib = { + f32: 'float', +}; + +const parse = source => { + source = source.trim(); + + const declaration = source.match(declarationRegexp); + + if (declaration !== null && declaration.length === 4) { + // tokenizer + + const inputsCode = declaration[2]; + const propsMatches = []; + + let nameMatch = null; + + while ((nameMatch = propertiesRegexp.exec(inputsCode)) !== null) { + propsMatches.push(nameMatch); + } + + // parser + + const inputs = []; + + let i = 0; + + while (i < propsMatches.length) { + // default + + const name = propsMatches[i++][0]; + let type = propsMatches[i++][0]; + + type = wgslTypeLib[type] || type; + + // precision + + if (i < propsMatches.length && propsMatches[i][0].startsWith('<') === true) i++; + + // add input + + inputs.push(new NodeFunctionInput(type, name)); + } + + // + + const blockCode = source.substring(declaration[0].length); + + const name = declaration[1] !== undefined ? declaration[1] : ''; + const type = declaration[3] || 'void'; + + return { + type, + inputs, + name, + inputsCode, + blockCode, + }; + } else { + throw new Error('FunctionNode: Function is not a WGSL code.'); + } +}; + +class WGSLNodeFunction extends NodeFunction { + constructor(source) { + const { type, inputs, name, inputsCode, blockCode } = parse(source); + + super(type, inputs, name); + + this.inputsCode = inputsCode; + this.blockCode = blockCode; + } + + getCode(name = this.name) { + const type = this.type !== 'void' ? '-> ' + this.type : ''; + + return `fn ${name} ( ${this.inputsCode.trim()} ) ${type}` + 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 000000000..c32133df4 --- /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 1490103fd43386b78781582e7a27adf4e814e4dc Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sun, 26 May 2024 20:29:55 -0400 Subject: [PATCH 2/5] Add ShaderNode --- examples-jsm/create-examples.js | 1 + .../examples/nodes/shadernode/ShaderNode.ts | 522 ++++++++++++++++++ 2 files changed, 523 insertions(+) create mode 100644 examples-jsm/examples/nodes/shadernode/ShaderNode.ts diff --git a/examples-jsm/create-examples.js b/examples-jsm/create-examples.js index 9d01b3a62..dcdf73aaa 100644 --- a/examples-jsm/create-examples.js +++ b/examples-jsm/create-examples.js @@ -25,6 +25,7 @@ const files = [ 'nodes/gpgpu/ComputeNode', 'nodes/lighting/EnvironmentNode', 'nodes/lighting/LightsNode', + 'nodes/shadernode/ShaderNode', 'nodes/Nodes', 'renderers/common/nodes/NodeBuilderState', 'renderers/common/nodes/NodeUniformsGroup', diff --git a/examples-jsm/examples/nodes/shadernode/ShaderNode.ts b/examples-jsm/examples/nodes/shadernode/ShaderNode.ts new file mode 100644 index 000000000..d0dca2424 --- /dev/null +++ b/examples-jsm/examples/nodes/shadernode/ShaderNode.ts @@ -0,0 +1,522 @@ +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; + } + + 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); + +// + +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); From 3b7ba278761c8558a1676387ef51f3d804bfdee9 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sun, 26 May 2024 22:05:07 -0400 Subject: [PATCH 3/5] Update patch --- examples-jsm/changes.patch | 580 +++++++++++++++++++++++++++++++++++-- 1 file changed, 553 insertions(+), 27 deletions(-) diff --git a/examples-jsm/changes.patch b/examples-jsm/changes.patch index bbc97a4df..67e264e2b 100644 --- a/examples-jsm/changes.patch +++ b/examples-jsm/changes.patch @@ -1,5 +1,214 @@ +diff --git a/examples-jsm/examples/nodes/accessors/TextureNode.ts b/examples-jsm/examples/nodes/accessors/TextureNode.ts +index 73a989fe..87b7a155 100644 +--- a/examples-jsm/examples/nodes/accessors/TextureNode.ts ++++ b/examples-jsm/examples/nodes/accessors/TextureNode.ts +@@ -3,13 +3,34 @@ 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 Node, { addNodeClass } from '../core/Node.js'; + import { maxMipLevel } from '../utils/MaxMipLevelNode.js'; +-import { addNodeElement, nodeProxy, vec3, nodeObject } from '../shadernode/ShaderNode.js'; ++import { addNodeElement, nodeProxy, vec3, nodeObject, ShaderNodeObject } from '../shadernode/ShaderNode.js'; + import { NodeUpdateType } from '../core/constants.js'; ++import { DepthTexture, Texture } from 'three'; ++import NodeBuilder from '../core/NodeBuilder.js'; + +-class TextureNode extends UniformNode { +- constructor(value, uvNode = null, levelNode = null) { ++class TextureNode extends UniformNode { ++ readonly isTextureNode: true; ++ ++ uvNode: ShaderNodeObject | null; ++ levelNode: ShaderNodeObject | null; ++ compareNode: Node | null; ++ depthNode: Node | null; ++ gradNode: Node | null; ++ ++ sampler: boolean; ++ updateMatrix: boolean; ++ ++ referenceNode: this | null; ++ ++ _value: Texture; ++ ++ constructor( ++ value: Texture, ++ uvNode: ShaderNodeObject | null = null, ++ levelNode: ShaderNodeObject | null = null, ++ ) { + super(value); + + this.isTextureNode = true; +@@ -31,7 +52,7 @@ class TextureNode extends UniformNode { + this.setUpdateMatrix(uvNode === null); + } + +- set value(value) { ++ set value(value: Texture) { + if (this.referenceNode) { + this.referenceNode.value = value; + } else { +@@ -48,7 +69,7 @@ class TextureNode extends UniformNode { + } + + getNodeType(/*builder*/) { +- if (this.value.isDepthTexture === true) return 'float'; ++ if ((this.value as DepthTexture).isDepthTexture === true) return 'float'; + + return 'vec4'; + } +@@ -71,14 +92,14 @@ class TextureNode extends UniformNode { + return uniform(texture.matrix).mul(vec3(uvNode, 1)).xy; + } + +- setUpdateMatrix(value) { ++ setUpdateMatrix(value: boolean) { + this.updateMatrix = value; + this.updateType = value ? NodeUpdateType.FRAME : NodeUpdateType.NONE; + + return this; + } + +- setupUV(builder, uvNode) { ++ setupUV(builder: NodeBuilder, uvNode) { + const texture = this.value; + + if ( +@@ -93,7 +114,7 @@ class TextureNode extends UniformNode { + return uvNode; + } + +- setup(builder) { ++ setup(builder: NodeBuilder) { + const properties = builder.getNodeProperties(this); + + // +@@ -129,11 +150,19 @@ class TextureNode extends UniformNode { + properties.depthNode = this.depthNode; + } + +- generateUV(builder, uvNode) { ++ generateUV(builder: NodeBuilder, uvNode) { + return uvNode.build(builder, this.sampler === true ? 'vec2' : 'ivec2'); + } + +- generateSnippet(builder, textureProperty, uvSnippet, levelSnippet, depthSnippet, compareSnippet, gradSnippet) { ++ generateSnippet( ++ builder: NodeBuilder, ++ textureProperty, ++ uvSnippet, ++ levelSnippet, ++ depthSnippet, ++ compareSnippet, ++ gradSnippet, ++ ) { + const texture = this.value; + + let snippet; +@@ -153,7 +182,7 @@ class TextureNode extends UniformNode { + return snippet; + } + +- generate(builder, output) { ++ generate(builder: NodeBuilder, output) { + const properties = builder.getNodeProperties(this); + + const texture = this.value; +@@ -219,7 +248,7 @@ class TextureNode extends UniformNode { + } + } + +- setSampler(value) { ++ setSampler(value: boolean) { + this.sampler = value; + + return this; +@@ -231,7 +260,7 @@ class TextureNode extends UniformNode { + + // @TODO: Move to TSL + +- uv(uvNode) { ++ uv(uvNode: ShaderNodeObject | null) { + const textureNode = this.clone(); + textureNode.uvNode = uvNode; + textureNode.referenceNode = this; +@@ -239,7 +268,7 @@ class TextureNode extends UniformNode { + return nodeObject(textureNode); + } + +- blur(levelNode) { ++ blur(levelNode: ShaderNodeObject) { + const textureNode = this.clone(); + textureNode.levelNode = levelNode.mul(maxMipLevel(textureNode)); + textureNode.referenceNode = this; +@@ -247,7 +276,7 @@ class TextureNode extends UniformNode { + return nodeObject(textureNode); + } + +- level(levelNode) { ++ level(levelNode: ShaderNodeObject | null) { + const textureNode = this.clone(); + textureNode.levelNode = levelNode; + textureNode.referenceNode = this; +diff --git a/examples-jsm/examples/nodes/core/InputNode.ts b/examples-jsm/examples/nodes/core/InputNode.ts +index 4d52ec26..5987158d 100644 +--- a/examples-jsm/examples/nodes/core/InputNode.ts ++++ b/examples-jsm/examples/nodes/core/InputNode.ts +@@ -1,8 +1,14 @@ + import Node, { addNodeClass } from './Node.js'; + import { getValueType, getValueFromType, arrayBufferToBase64 } from './NodeUtils.js'; ++import NodeBuilder from './NodeBuilder.js'; + +-class InputNode extends Node { +- constructor(value, nodeType = null) { ++class InputNode extends Node { ++ readonly isInputNode: true; ++ ++ value: TValue; ++ precision: 'low' | 'medium' | 'high' | null; ++ ++ constructor(value: TValue, nodeType: string | null = null) { + super(nodeType); + + this.isInputNode = true; +@@ -11,7 +17,7 @@ class InputNode extends Node { + this.precision = null; + } + +- getNodeType(/*builder*/) { ++ getNodeType(builder: NodeBuilder) { + if (this.nodeType === null) { + return getValueType(this.value); + } +@@ -19,11 +25,11 @@ class InputNode extends Node { + return this.nodeType; + } + +- getInputType(builder) { ++ getInputType(builder: NodeBuilder) { + return this.getNodeType(builder); + } + +- setPrecision(precision) { ++ setPrecision(precision: 'low' | 'medium' | 'high' | null) { + this.precision = precision; + + return this; +@@ -54,10 +60,6 @@ class InputNode extends Node { + + if (this.value && this.value.fromArray) this.value = this.value.fromArray(data.value); + } +- +- generate(/*builder, output*/) { +- console.warn('Abstract function.'); +- } + } + + export default InputNode; diff --git a/examples-jsm/examples/nodes/core/Node.ts b/examples-jsm/examples/nodes/core/Node.ts -index 438c44dd..ec4f8b35 100644 +index 438c44dd..e5816758 100644 --- a/examples-jsm/examples/nodes/core/Node.ts +++ b/examples-jsm/examples/nodes/core/Node.ts @@ -2,13 +2,95 @@ import { EventDispatcher } from 'three'; @@ -118,7 +327,7 @@ index 438c44dd..ec4f8b35 100644 } - onUpdate(callback, updateType) { -+ onUpdate(callback: (this: this, frame: NodeFrame) => void, updateType: NodeUpdateType) { ++ onUpdate(callback: (this: this, frame: NodeFrame) => unknown, updateType: NodeUpdateType) { this.updateType = updateType; this.update = callback.bind(this.getSelf()); @@ -141,7 +350,7 @@ index 438c44dd..ec4f8b35 100644 } - onReference(callback) { -+ onReference(callback: (this: this, frame: NodeBuilder | NodeFrame) => this) { ++ onReference(callback: (this: this, frame: NodeBuilder | NodeFrame) => unknown) { this.updateReference = callback.bind(this.getSelf()); return this; @@ -150,7 +359,7 @@ index 438c44dd..ec4f8b35 100644 } - updateReference(/*state*/) { -+ updateReference(state: NodeBuilder | NodeFrame) { ++ updateReference(state: NodeBuilder | NodeFrame): unknown { return this; } @@ -207,7 +416,7 @@ index 438c44dd..ec4f8b35 100644 } - setup(builder) { -+ setup(builder: NodeBuilder): Node | null { ++ setup(builder: NodeBuilder): unknown { const nodeProperties = builder.getNodeProperties(this); for (const childNode of this.getChildren()) { @@ -428,7 +637,7 @@ index 438c44dd..ec4f8b35 100644 export default Node; -export function addNodeClass(type, nodeClass) { -+export function addNodeClass(type: string, nodeClass: typeof Node) { ++export function addNodeClass(type: string, nodeClass: { new (...args: any[]): Node }) { 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}`); @@ -1186,6 +1395,96 @@ index f8bb2b37..cee70486 100644 if (value === true) this.version++; } } +diff --git a/examples-jsm/examples/nodes/core/UniformNode.ts b/examples-jsm/examples/nodes/core/UniformNode.ts +index 90e86648..0987ac55 100644 +--- a/examples-jsm/examples/nodes/core/UniformNode.ts ++++ b/examples-jsm/examples/nodes/core/UniformNode.ts +@@ -1,10 +1,17 @@ + import InputNode from './InputNode.js'; +-import { objectGroup } from './UniformGroupNode.js'; +-import { addNodeClass } from './Node.js'; ++import UniformGroupNode, { objectGroup } from './UniformGroupNode.js'; ++import Node, { addNodeClass } from './Node.js'; + import { nodeObject, getConstNodeType } from '../shadernode/ShaderNode.js'; ++import NodeBuilder from './NodeBuilder.js'; ++import NodeFrame from './NodeFrame.js'; ++import { NodeUpdateType } from './constants.js'; + +-class UniformNode extends InputNode { +- constructor(value, nodeType = null) { ++class UniformNode extends InputNode { ++ readonly isUniformNode: true; ++ ++ groupNode: UniformGroupNode; ++ ++ constructor(value: TValue, nodeType: string | null = null) { + super(value, nodeType); + + this.isUniformNode = true; +@@ -12,7 +19,7 @@ class UniformNode extends InputNode { + this.groupNode = objectGroup; + } + +- setGroup(group) { ++ setGroup(group: UniformGroupNode) { + this.groupNode = group; + + return this; +@@ -22,11 +29,11 @@ class UniformNode extends InputNode { + return this.groupNode; + } + +- getUniformHash(builder) { ++ getUniformHash(builder: NodeBuilder) { + return this.getHash(builder); + } + +- onUpdate(callback, updateType) { ++ onUpdate(callback: (frame: NodeFrame, self: this) => TValue | undefined, updateType: NodeUpdateType) { + const self = this.getSelf(); + + callback = callback.bind(self); +@@ -40,12 +47,12 @@ class UniformNode extends InputNode { + }, updateType); + } + +- generate(builder, output) { ++ generate(builder: NodeBuilder, output: string | null) { + const type = this.getNodeType(builder); + + const hash = this.getUniformHash(builder); + +- let sharedNode = builder.getNodeFromHash(hash); ++ let sharedNode = builder.getNodeFromHash(hash) as this; + + if (sharedNode === undefined) { + builder.setHashNode(this, hash); +@@ -58,7 +65,7 @@ class UniformNode extends InputNode { + const nodeUniform = builder.getUniformFromNode( + sharedNode, + sharedNodeType, +- builder.shaderStage, ++ builder.shaderStage!, + builder.context.label, + ); + const propertyName = builder.getPropertyName(nodeUniform); +@@ -71,11 +78,14 @@ class UniformNode extends InputNode { + + export default UniformNode; + +-export const uniform = (arg1, arg2) => { ++export const uniform = (arg1: InputNode | TValue, arg2?: Node | string) => { + 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; ++ const value = ++ arg1 && (arg1 as Node).isNode === true ++ ? (arg1.node && arg1.node.value) || (arg1 as InputNode).value ++ : arg1; + + return nodeObject(new UniformNode(value, nodeType)); + }; diff --git a/examples-jsm/examples/nodes/core/constants.ts b/examples-jsm/examples/nodes/core/constants.ts index 3b01a9a6..5ff6ad5f 100644 --- a/examples-jsm/examples/nodes/core/constants.ts @@ -1265,6 +1564,221 @@ index b3695ea8..603a4520 100644 const lightNodes = []; lights = sortLights(lights); +diff --git a/examples-jsm/examples/nodes/shadernode/ShaderNode.ts b/examples-jsm/examples/nodes/shadernode/ShaderNode.ts +index d0dca242..dd91e4fc 100644 +--- a/examples-jsm/examples/nodes/shadernode/ShaderNode.ts ++++ b/examples-jsm/examples/nodes/shadernode/ShaderNode.ts +@@ -11,6 +11,43 @@ import { getValueFromType, getValueType } from '../core/NodeUtils.js'; + + let currentStack = null; + ++export interface NodeElements { ++ append: typeof append; ++ ++ toColor: typeof color; ++ toFloat: typeof float; ++ toInt: typeof int; ++ toUint: typeof uint; ++ toBool: typeof bool; ++ toVec2: typeof vec2; ++ toIvec2: typeof ivec2; ++ toUvec2: typeof uvec2; ++ toBvec2: typeof bvec2; ++ toVec3: typeof vec3; ++ toIvec3: typeof ivec3; ++ toUvec3: typeof uvec3; ++ toBvec3: typeof bvec3; ++ toVec4: typeof vec4; ++ toIvec4: typeof ivec4; ++ toUvec4: typeof uvec4; ++ toBvec4: typeof bvec4; ++ toMat2: typeof mat2; ++ toImat2: typeof imat2; ++ toUmat2: typeof umat2; ++ toBmat2: typeof bmat2; ++ toMat3: typeof mat3; ++ toImat3: typeof imat3; ++ toUmat3: typeof umat3; ++ toBmat3: typeof bmat3; ++ toMat4: typeof mat4; ++ toImat4: typeof imat4; ++ toUmat4: typeof umat4; ++ toBmat4: typeof bmat4; ++ ++ element: typeof element; ++ convert: typeof convert; ++} ++ + const NodeElements = new Map(); // @TODO: Currently only a few nodes are added, probably also add others + + export function addNodeElement(name, nodeElement) { +@@ -24,6 +61,141 @@ export function addNodeElement(name, nodeElement) { + NodeElements.set(name, nodeElement); + } + ++export type SwizzleCharacter = 'x' | 'y' | 'z' | 'w' | 'r' | 'g' | 'b' | 'a' | 's' | 't' | 'p' | 'q'; ++ ++export type SwizzleOption = Exclude< ++ | `${SwizzleCharacter}` ++ | `${SwizzleCharacter}${SwizzleCharacter}` ++ | `${SwizzleCharacter}${SwizzleCharacter}${SwizzleCharacter}` ++ | `${SwizzleCharacter}${SwizzleCharacter}${SwizzleCharacter}${SwizzleCharacter}`, ++ 'abs' | 'sqrt' ++>; ++ ++export type Swizzable = T & { ++ [key in SwizzleOption | number]: ShaderNodeObject; ++}; ++ ++export type ShaderNodeObject = T & { ++ [Key in keyof NodeElements]: T extends { [K in Key]: infer M } ++ ? M ++ : NodeElements[Key] extends (node: T, ...args: infer Args) => infer R ++ ? (...args: Args) => R ++ : never; ++} & { ++ [Key in keyof NodeElements as `${Key}Assign`]: T extends { [K in Key]: infer M } ++ ? M ++ : NodeElements[Key] extends (node: T, ...args: infer Args) => unknown ++ ? (...args: Args) => ShaderNodeObject ++ : never; ++} & Swizzable; ++ ++/** anything that can be passed to {@link nodeObject} and returns a proxy */ ++export type NodeRepresentation = number | boolean | Node | ShaderNodeObject; ++ ++/** anything that can be passed to {@link nodeObject} */ ++export type NodeObjectOption = NodeRepresentation | string; ++ ++// same logic as in ShaderNodeObject: number,boolean,node->ShaderNodeObject, otherwise do nothing ++export type NodeObject = T extends Node ++ ? ShaderNodeObject ++ : T extends number | boolean ++ ? ShaderNodeObject> ++ : T; ++ ++// opposite of NodeObject: node -> node|ShaderNodeObject|boolean|number, otherwise do nothing ++type Proxied = T extends Node ? NodeRepresentation : T; ++// https://github.com/microsoft/TypeScript/issues/42435#issuecomment-765557874 ++export type ProxiedTuple = [...{ [index in keyof T]: Proxied }]; ++export type ProxiedObject = { [index in keyof T]: Proxied }; ++type RemoveTail = T extends [unknown, ...infer X] ? X : []; ++type RemoveHeadAndTail = T extends [unknown, ...infer X, unknown] ? X : []; ++ ++/** ++ * Temporary type to save signatures of 4 constructors. Each element may be tuple or undefined. ++ * ++ * We use an object instead of tuple or union as it makes stuff easier, especially in Typescript 4.0. ++ */ ++interface Construtors< ++ A extends undefined | [...unknown[]], ++ B extends undefined | [...unknown[]], ++ C extends undefined | [...unknown[]], ++ D extends undefined | [...unknown[]], ++> { ++ a: A; ++ b: B; ++ c: C; ++ d: D; ++} ++ ++/** ++ * Returns all constructors ++ * ++ * ++ * ++ */ ++type OverloadedConstructorsOf = T extends { ++ new (...args: infer A1): unknown; ++ new (...args: infer A2): unknown; ++ new (...args: infer A3): unknown; ++ new (...args: infer A4): unknown; ++} ++ ? Construtors ++ : T extends { ++ new (...args: infer A1): unknown; ++ new (...args: infer A2): unknown; ++ new (...args: infer A3): unknown; ++ } ++ ? Construtors ++ : T extends { ++ new (...args: infer A1): unknown; ++ new (...args: infer A2): unknown; ++ } ++ ? Construtors ++ : T extends new (...args: infer A) => unknown ++ ? Construtors ++ : Construtors; ++ ++type AnyConstructors = Construtors; ++ ++/** ++ * Returns all constructors where the first paramter is assignable to given "scope" ++ */ ++// eslint-disable-next-line @typescript-eslint/consistent-type-definitions ++type FilterConstructorsByScope = { ++ a: S extends T['a'][0] ? T['a'] : undefined; ++ b: S extends T['b'][0] ? T['b'] : undefined; ++ c: S extends T['c'][0] ? T['c'] : undefined; ++ d: S extends T['d'][0] ? T['d'] : undefined; ++}; ++/** ++ * "flattens" the tuple into an union type ++ */ ++type ConstructorUnion = ++ | Exclude ++ | Exclude ++ | Exclude ++ | Exclude; ++ ++/** ++ * Extract list of possible scopes - union of the first paramter ++ * of all constructors, should it be string ++ */ ++type ExtractScopes = ++ | (T['a'][0] extends string ? T['a'][0] : never) ++ | (T['b'][0] extends string ? T['b'][0] : never) ++ | (T['c'][0] extends string ? T['c'][0] : never) ++ | (T['d'][0] extends string ? T['d'][0] : never); ++ ++type GetConstructorsByScope = ConstructorUnion, S>>; ++type GetConstructors = ConstructorUnion>; ++type GetPossibleScopes = ExtractScopes>; ++ ++export type ConvertType = (...params: unknown[]) => ShaderNodeObject; ++ ++type NodeArray = { [index in keyof T]: NodeObject }; ++type NodeObjects = { [key in keyof T]: T[key] extends NodeObjectOption ? NodeObject : T[key] }; ++type ConstructedNode = T extends new (...args: any[]) => infer R ? (R extends Node ? R : never) : never; ++ + 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 = { +@@ -385,7 +557,23 @@ export function ShaderNode(jsFunc) { + 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); ++ ++interface NodeProxy { ++ (nodeClass: T): (...params: ProxiedTuple>) => ShaderNodeObject>; ++ >( ++ nodeClass: T, ++ scope: S, ++ ): (...params: ProxiedTuple>>) => ShaderNodeObject>; ++ >( ++ nodeClass: T, ++ scope: S, ++ factor: NodeObjectOption, ++ ): ( ++ ...params: ProxiedTuple>> ++ ) => ShaderNodeObject>; ++} ++ ++export const nodeProxy: NodeProxy = (...params) => new ShaderNodeProxy(...params); + export const nodeImmutable = (...params) => new ShaderNodeImmutable(...params); + + export const tslFn = jsFunc => { diff --git a/examples-jsm/examples/renderers/common/Animation.ts b/examples-jsm/examples/renderers/common/Animation.ts index 0b00319a..c190633b 100644 --- a/examples-jsm/examples/renderers/common/Animation.ts @@ -3837,7 +4351,7 @@ index 0bbc1add..783d8db1 100644 this.id = id++; diff --git a/examples-jsm/examples/renderers/common/nodes/Nodes.ts b/examples-jsm/examples/renderers/common/nodes/Nodes.ts -index 86df5654..f8f1f74f 100644 +index 86df5654..1b9cfdb3 100644 --- a/examples-jsm/examples/renderers/common/nodes/Nodes.ts +++ b/examples-jsm/examples/renderers/common/nodes/Nodes.ts @@ -2,10 +2,20 @@ import DataMap from '../DataMap.js'; @@ -3861,9 +4375,11 @@ index 86df5654..f8f1f74f 100644 } from 'three'; import { NodeFrame, -@@ -23,20 +33,63 @@ import { +@@ -22,21 +32,73 @@ import { + normalWorld, pmremTexture, viewportTopLeft, ++ ShaderNodeObject, } from '../../../nodes/Nodes.js'; +import Renderer from '../Renderer.js'; +import Backend from '../Backend.js'; @@ -3875,6 +4391,14 @@ index 86df5654..f8f1f74f 100644 +import Node from '../../../nodes/core/Node.js'; +import UniformGroupNode from '../../../nodes/core/UniformGroupNode.js'; + ++declare module 'three' { ++ interface Scene { ++ environmentNode?: Node | null | undefined; ++ backgroundNode?: Node | null | undefined; ++ fogNode?: Node | null | undefined; ++ } ++} ++ +interface NodeUniformsGroupData { + renderId?: number | undefined; + frameId?: number | undefined; @@ -3929,7 +4453,7 @@ index 86df5654..f8f1f74f 100644 const groupNode = nodeUniformsGroup.groupNode; const name = groupNode.name; -@@ -76,7 +129,7 @@ class Nodes extends DataMap { +@@ -76,7 +138,7 @@ class Nodes extends DataMap { // other groups are updated just when groupNode.needsUpdate is true @@ -3938,7 +4462,7 @@ index 86df5654..f8f1f74f 100644 let groupData = this.groupsData.get(groupChain); if (groupData === undefined) this.groupsData.set(groupChain, (groupData = {})); -@@ -90,11 +143,11 @@ class Nodes extends DataMap { +@@ -90,11 +152,11 @@ class Nodes extends DataMap { return false; } @@ -3952,7 +4476,7 @@ index 86df5654..f8f1f74f 100644 const renderObjectData = this.get(renderObject); let nodeBuilderState = renderObjectData.nodeBuilderState; -@@ -133,20 +186,20 @@ class Nodes extends DataMap { +@@ -133,20 +195,20 @@ class Nodes extends DataMap { return nodeBuilderState; } @@ -3978,7 +4502,7 @@ index 86df5654..f8f1f74f 100644 const computeData = this.get(computeNode); let nodeBuilderState = computeData.nodeBuilderState; -@@ -163,7 +216,7 @@ class Nodes extends DataMap { +@@ -163,7 +225,7 @@ class Nodes extends DataMap { return nodeBuilderState; } @@ -3987,7 +4511,7 @@ index 86df5654..f8f1f74f 100644 return new NodeBuilderState( nodeBuilder.vertexShader, nodeBuilder.fragmentShader, -@@ -176,20 +229,20 @@ class Nodes extends DataMap { +@@ -176,20 +238,20 @@ class Nodes extends DataMap { ); } @@ -4013,7 +4537,7 @@ index 86df5654..f8f1f74f 100644 const callId = this.renderer.info.calls; let cacheKeyData = this.callHashCache.get(chain); -@@ -215,7 +268,7 @@ class Nodes extends DataMap { +@@ -215,7 +277,7 @@ class Nodes extends DataMap { return cacheKeyData.cacheKey; } @@ -4022,7 +4546,7 @@ index 86df5654..f8f1f74f 100644 this.updateEnvironment(scene); this.updateFog(scene); this.updateBackground(scene); -@@ -225,7 +278,7 @@ class Nodes extends DataMap { +@@ -225,7 +287,7 @@ class Nodes extends DataMap { return this.renderer.getRenderTarget() ? false : true; } @@ -4031,7 +4555,7 @@ index 86df5654..f8f1f74f 100644 const sceneData = this.get(scene); const background = scene.background; -@@ -234,15 +287,15 @@ class Nodes extends DataMap { +@@ -234,15 +296,15 @@ class Nodes extends DataMap { let backgroundNode = null; if ( @@ -4054,7 +4578,7 @@ index 86df5654..f8f1f74f 100644 } sceneData.backgroundNode = backgroundNode; -@@ -254,7 +307,7 @@ class Nodes extends DataMap { +@@ -254,7 +316,7 @@ class Nodes extends DataMap { } } @@ -4063,7 +4587,7 @@ index 86df5654..f8f1f74f 100644 const sceneData = this.get(scene); const fog = scene.fog; -@@ -262,9 +315,9 @@ class Nodes extends DataMap { +@@ -262,9 +324,9 @@ class Nodes extends DataMap { if (sceneData.fog !== fog) { let fogNode = null; @@ -4075,7 +4599,7 @@ index 86df5654..f8f1f74f 100644 fogNode = rangeFog( reference('color', 'color', fog), reference('near', 'float', fog), -@@ -283,7 +336,7 @@ class Nodes extends DataMap { +@@ -283,7 +345,7 @@ class Nodes extends DataMap { } } @@ -4084,7 +4608,7 @@ index 86df5654..f8f1f74f 100644 const sceneData = this.get(scene); const environment = scene.environment; -@@ -291,7 +344,7 @@ class Nodes extends DataMap { +@@ -291,7 +353,7 @@ class Nodes extends DataMap { if (sceneData.environment !== environment) { let environmentNode = null; @@ -4093,7 +4617,7 @@ index 86df5654..f8f1f74f 100644 environmentNode = cubeTexture(environment); } else if (environment.isTexture === true) { environmentNode = texture(environment); -@@ -308,7 +361,13 @@ class Nodes extends DataMap { +@@ -308,7 +370,13 @@ class Nodes extends DataMap { } } @@ -4108,7 +4632,7 @@ index 86df5654..f8f1f74f 100644 const nodeFrame = this.nodeFrame; nodeFrame.renderer = renderer; nodeFrame.scene = scene; -@@ -319,7 +378,7 @@ class Nodes extends DataMap { +@@ -319,7 +387,7 @@ class Nodes extends DataMap { return nodeFrame; } @@ -4117,16 +4641,18 @@ index 86df5654..f8f1f74f 100644 return this.getNodeFrame( renderObject.renderer, renderObject.scene, -@@ -329,7 +388,7 @@ class Nodes extends DataMap { +@@ -329,8 +397,8 @@ class Nodes extends DataMap { ); } - getOutputNode(outputTexture) { +- let output = texture(outputTexture, viewportTopLeft); + getOutputNode(outputTexture: Texture) { - let output = texture(outputTexture, viewportTopLeft); ++ let output: ShaderNodeObject = texture(outputTexture, viewportTopLeft); if (this.isToneMappingState) { -@@ -347,7 +406,7 @@ class Nodes extends DataMap { + if (this.renderer.toneMappingNode) { +@@ -347,7 +415,7 @@ class Nodes extends DataMap { return output; } @@ -4135,7 +4661,7 @@ index 86df5654..f8f1f74f 100644 const nodeFrame = this.getNodeFrameForRender(renderObject); const nodeBuilder = renderObject.getNodeBuilderState(); -@@ -356,7 +415,7 @@ class Nodes extends DataMap { +@@ -356,7 +424,7 @@ class Nodes extends DataMap { } } @@ -4144,7 +4670,7 @@ index 86df5654..f8f1f74f 100644 const nodeFrame = this.getNodeFrame(); const nodeBuilder = this.getForCompute(computeNode); -@@ -365,7 +424,7 @@ class Nodes extends DataMap { +@@ -365,7 +433,7 @@ class Nodes extends DataMap { } } From 53a472a18644495fa1229ba398a286e4041788ba Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sun, 26 May 2024 22:06:50 -0400 Subject: [PATCH 4/5] Delete examples --- examples-jsm/examples/nodes/Nodes.ts | 416 ----- .../examples/nodes/accessors/TextureNode.ts | 329 ---- examples-jsm/examples/nodes/core/InputNode.ts | 65 - examples-jsm/examples/nodes/core/Node.ts | 411 ----- .../examples/nodes/core/NodeAttribute.ts | 11 - .../examples/nodes/core/NodeBuilder.ts | 1011 ------------- examples-jsm/examples/nodes/core/NodeCache.ts | 18 - examples-jsm/examples/nodes/core/NodeCode.ts | 11 - examples-jsm/examples/nodes/core/NodeFrame.ts | 101 -- .../examples/nodes/core/NodeKeywords.ts | 58 - .../examples/nodes/core/NodeParser.ts | 7 - .../examples/nodes/core/NodeUniform.ts | 28 - examples-jsm/examples/nodes/core/NodeUtils.ts | 132 -- examples-jsm/examples/nodes/core/NodeVar.ts | 10 - .../examples/nodes/core/NodeVarying.ts | 13 - .../examples/nodes/core/UniformGroupNode.ts | 30 - .../examples/nodes/core/UniformNode.ts | 83 - 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 | 118 -- .../examples/nodes/lighting/LightsNode.ts | 157 -- .../examples/nodes/shadernode/ShaderNode.ts | 522 ------- .../examples/renderers/common/Animation.ts | 38 - .../examples/renderers/common/Attributes.ts | 51 - .../examples/renderers/common/Backend.ts | 167 --- .../examples/renderers/common/Background.ts | 118 -- .../examples/renderers/common/Binding.ts | 17 - .../examples/renderers/common/Bindings.ts | 153 -- .../examples/renderers/common/Buffer.ts | 28 - .../examples/renderers/common/BufferUtils.ts | 23 - .../examples/renderers/common/ChainMap.ts | 59 - .../renderers/common/ClippingContext.ts | 128 -- .../examples/renderers/common/Color4.ts | 27 - .../renderers/common/ComputePipeline.ts | 13 - .../examples/renderers/common/Constants.ts | 14 - .../examples/renderers/common/DataMap.ts | 38 - .../examples/renderers/common/Geometries.ts | 163 -- .../examples/renderers/common/Info.ts | 76 - .../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 | 211 --- .../renderers/common/RenderObjects.ts | 100 -- .../renderers/common/RenderPipeline.ts | 12 - .../examples/renderers/common/Renderer.ts | 1336 ----------------- .../examples/renderers/common/Textures.ts | 288 ---- .../renderers/common/UniformBuffer.ts | 11 - .../renderers/common/UniformsGroup.ts | 247 --- .../common/nodes/NodeBuilderState.ts | 43 - .../common/nodes/NodeUniformsGroup.ts | 34 - .../examples/renderers/common/nodes/Nodes.ts | 385 ----- .../examples/renderers/webgl/WebGLBackend.ts | 1190 --------------- .../renderers/webgl/nodes/GLSLNodeBuilder.ts | 655 -------- .../renderers/webgpu/WebGPUBackend.ts | 1186 --------------- .../renderers/webgpu/WebGPURenderer.ts | 43 - .../renderers/webgpu/nodes/WGSLNodeBuilder.ts | 921 ------------ .../webgpu/nodes/WGSLNodeFunction.ts | 87 -- .../renderers/webgpu/nodes/WGSLNodeParser.ts | 10 - 64 files changed, 12121 deletions(-) delete mode 100644 examples-jsm/examples/nodes/Nodes.ts delete mode 100644 examples-jsm/examples/nodes/accessors/TextureNode.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/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/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/LightsNode.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/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/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/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/Textures.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/nodes/NodeBuilderState.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 affc6ebf8..000000000 --- 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, - 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 } 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 { - default as PositionNode, - positionGeometry, - positionLocal, - positionWorld, - positionWorldDirection, - positionView, - positionViewDirection, -} from './accessors/PositionNode.js'; -export { default as ReferenceNode, reference, referenceBuffer } from './accessors/ReferenceNode.js'; -export { default as ReflectVectorNode, reflectVector } 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 TextureStoreNode, textureStore } from './accessors/TextureStoreNode.js'; -export { default as Texture3DNode } from './accessors/Texture3DNode.js'; -export { default as UVNode, uv } 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, - depthTexture, - depthPixel, -} 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 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/TextureNode.ts b/examples-jsm/examples/nodes/accessors/TextureNode.ts deleted file mode 100644 index 73a989fe4..000000000 --- a/examples-jsm/examples/nodes/accessors/TextureNode.ts +++ /dev/null @@ -1,329 +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}`); - - if (builder.context.tempWrite !== false) { - 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/core/InputNode.ts b/examples-jsm/examples/nodes/core/InputNode.ts deleted file mode 100644 index 4d52ec26f..000000000 --- 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 438c44dd1..000000000 --- a/examples-jsm/examples/nodes/core/Node.ts +++ /dev/null @@ -1,411 +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.uuid = MathUtils.generateUUID(); - - this.version = 0; - - this._cacheKey = null; - this._cacheKeyVersion = 0; - - 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 false; - } - - *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; - } - - 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); - - for (const childNode of this.getChildren()) { - nodeProperties['_node' + childNode.id] = 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.'); - } - - 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 || builder.context.tempRead === false) { - 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 /*|| builder.context.tempRead === false*/) { - 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 190fe8c51..000000000 --- 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 ebdc13ff1..000000000 --- a/examples-jsm/examples/nodes/core/NodeBuilder.ts +++ /dev/null @@ -1,1011 +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 { - FloatNodeUniform, - Vector2NodeUniform, - Vector3NodeUniform, - Vector4NodeUniform, - ColorNodeUniform, - Matrix3NodeUniform, - Matrix4NodeUniform, -} from '../../renderers/common/nodes/NodeUniform.js'; - -import { - REVISION, - RenderTarget, - Color, - Vector2, - Vector3, - Vector4, - IntType, - UnsignedIntType, - Float16BufferAttribute, -} 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 uniformsGroupCache = new ChainMap(); - -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, scene = null, material = null) { - this.object = object; - this.material = material || (object && object.material) || null; - this.geometry = (object && object.geometry) || null; - this.renderer = renderer; - this.parser = parser; - this.scene = scene; - - this.nodes = []; - this.updateNodes = []; - this.updateBeforeNodes = []; - 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.bindingsOffset = { vertex: 0, fragment: 0, compute: 0 }; - this.bindingsArray = 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.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; - } - - 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); - } - - _getSharedBindings(bindings) { - const shared = []; - - for (const binding of bindings) { - if (binding.shared === true) { - // nodes is the chainmap key - const nodes = binding.getNodes(); - - let sharedBinding = uniformsGroupCache.get(nodes); - - if (sharedBinding === undefined) { - uniformsGroupCache.set(nodes, binding); - - sharedBinding = binding; - } - - shared.push(sharedBinding); - } else { - shared.push(binding); - } - } - - return shared; - } - - getBindings() { - let bindingsArray = this.bindingsArray; - - if (bindingsArray === null) { - const bindings = this.bindings; - - this.bindingsArray = bindingsArray = this._getSharedBindings( - this.material !== null ? [...bindings.vertex, ...bindings.fragment] : bindings.compute, - ); - } - - return bindingsArray; - } - - 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(); - - if (updateType !== NodeUpdateType.NONE) { - this.updateNodes.push(node.getSelf()); - } - - if (updateBeforeType !== NodeUpdateType.NONE) { - this.updateBeforeNodes.push(node); - } - } - } - - get currentNode() { - return this.chaining[this.chaining.length - 1]; - } - - 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; - } - - 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; - } - - generateMethod(method) { - return method; - } - - 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 === '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.getNodeData(node); - - if (nodeData === undefined) { - nodeData = {}; - - cache.setNodeData(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 previousBuildStage = this.buildStage; - - const flow = { - code: '', - }; - - this.flow = flow; - this.vars = {}; - - 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.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') return new FloatNodeUniform(uniformNode); - if (type === 'vec2') return new Vector2NodeUniform(uniformNode); - if (type === 'vec3') return new Vector3NodeUniform(uniformNode); - if (type === 'vec4') 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[0] !== toType[0]) { - // 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} - NodeMaterial 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 96a7e0c76..000000000 --- a/examples-jsm/examples/nodes/core/NodeCache.ts +++ /dev/null @@ -1,18 +0,0 @@ -let id = 0; - -class NodeCache { - constructor() { - this.id = id++; - this.nodesData = new WeakMap(); - } - - getNodeData(node) { - return this.nodesData.get(node); - } - - setNodeData(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 2ee509037..000000000 --- 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 b8e8d37b6..000000000 --- a/examples-jsm/examples/nodes/core/NodeFrame.ts +++ /dev/null @@ -1,101 +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.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); - } - } - - 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/NodeKeywords.ts b/examples-jsm/examples/nodes/core/NodeKeywords.ts deleted file mode 100644 index 53da9bf50..000000000 --- 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 9849452f1..000000000 --- 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 2918e219c..000000000 --- a/examples-jsm/examples/nodes/core/NodeUniform.ts +++ /dev/null @@ -1,28 +0,0 @@ -class NodeUniform { - constructor(name, type, node, needsUpdate = undefined) { - this.isNodeUniform = true; - - this.name = name; - this.type = type; - this.node = node.getSelf(); - this.needsUpdate = needsUpdate; - } - - 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 16a5f3246..000000000 --- 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 e6e935b31..000000000 --- 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 a14823628..000000000 --- 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/UniformGroupNode.ts b/examples-jsm/examples/nodes/core/UniformGroupNode.ts deleted file mode 100644 index f8bb2b37d..000000000 --- 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 90e866481..000000000 --- a/examples-jsm/examples/nodes/core/UniformNode.ts +++ /dev/null @@ -1,83 +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.groupNode = objectGroup; - } - - 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, - 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 3b01a9a6d..000000000 --- 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 9417df5a5..000000000 --- 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 ab2925a55..000000000 --- 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 56f8109c2..000000000 --- a/examples-jsm/examples/nodes/lighting/EnvironmentNode.ts +++ /dev/null @@ -1,118 +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); - - // - - builder.context.radiance.addAssign(isolateRadiance); - - builder.context.iblIrradiance.addAssign(irradiance); - - // - - 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/LightsNode.ts b/examples-jsm/examples/nodes/lighting/LightsNode.ts deleted file mode 100644 index b3695ea8b..000000000 --- a/examples-jsm/examples/nodes/lighting/LightsNode.ts +++ /dev/null @@ -1,157 +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; - } - - 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(); - - // - - 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/shadernode/ShaderNode.ts b/examples-jsm/examples/nodes/shadernode/ShaderNode.ts deleted file mode 100644 index d0dca2424..000000000 --- a/examples-jsm/examples/nodes/shadernode/ShaderNode.ts +++ /dev/null @@ -1,522 +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; - } - - 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); - -// - -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 0b00319a1..000000000 --- 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 ed9e8e9ec..000000000 --- a/examples-jsm/examples/renderers/common/Attributes.ts +++ /dev/null @@ -1,51 +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); - } - } - - 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 e48a8029a..000000000 --- a/examples-jsm/examples/renderers/common/Backend.ts +++ /dev/null @@ -1,167 +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) {} - - updateBindings(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.isInstancedMesh ? 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 b7902dd40..000000000 --- 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/Binding.ts b/examples-jsm/examples/renderers/common/Binding.ts deleted file mode 100644 index a12f3563b..000000000 --- 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 9485ec3b5..000000000 --- a/examples-jsm/examples/renderers/common/Bindings.ts +++ /dev/null @@ -1,153 +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(); - - const data = this.get(renderObject); - - if (data.bindings !== bindings) { - // each object defines an array of bindings (ubos, textures, samplers etc.) - - data.bindings = bindings; - - this._init(bindings); - - this.backend.createBindings(bindings); - } - - return data.bindings; - } - - getForCompute(computeNode) { - const data = this.get(computeNode); - - if (data.bindings === undefined) { - const nodeBuilderState = this.nodes.getForCompute(computeNode); - - const bindings = nodeBuilderState.bindings; - - data.bindings = bindings; - - this._init(bindings); - - this.backend.createBindings(bindings); - } - - return data.bindings; - } - - updateForCompute(computeNode) { - this._update(computeNode, this.getForCompute(computeNode)); - } - - updateForRender(renderObject) { - this._update(renderObject, this.getForRender(renderObject)); - } - - _init(bindings) { - for (const binding of 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, 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 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(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 17013c6dc..000000000 --- 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 99ddcb48b..000000000 --- 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 e233becaf..000000000 --- a/examples-jsm/examples/renderers/common/ChainMap.ts +++ /dev/null @@ -1,59 +0,0 @@ -export default class ChainMap { - constructor() { - this.weakMap = new WeakMap(); - } - - get(keys) { - if (Array.isArray(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]); - } else { - return super.get(keys); - } - } - - set(keys, value) { - if (Array.isArray(keys)) { - 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); - } else { - return super.set(keys, value); - } - } - - delete(keys) { - if (Array.isArray(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]); - } else { - return super.delete(keys); - } - } - - dispose() { - this.weakMap.clear(); - } -} diff --git a/examples-jsm/examples/renderers/common/ClippingContext.ts b/examples-jsm/examples/renderers/common/ClippingContext.ts deleted file mode 100644 index 312e0b779..000000000 --- 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 c681cc908..000000000 --- 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 0fd3ca531..000000000 --- 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 0d0c35a25..000000000 --- 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/DataMap.ts b/examples-jsm/examples/renderers/common/DataMap.ts deleted file mode 100644 index 006bc2950..000000000 --- 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 5da999460..000000000 --- a/examples-jsm/examples/renderers/common/Geometries.ts +++ /dev/null @@ -1,163 +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) { - 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 (this.attributeCall.get(attribute) !== callId) { - this.attributes.update(attribute, type); - - 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 c8e7cb41b..000000000 --- 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/Pipelines.ts b/examples-jsm/examples/renderers/common/Pipelines.ts deleted file mode 100644 index f6c570c4c..000000000 --- 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(computeNode); - - 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); - } - } - - 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 a684e4443..000000000 --- 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 e59e49378..000000000 --- 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 291403652..000000000 --- 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 3b43028eb..000000000 --- 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 630a2e42d..000000000 --- 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 a72a91dfd..000000000 --- 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 3fc3134e6..000000000 --- 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 861c15dc3..000000000 --- a/examples-jsm/examples/renderers/common/RenderObject.ts +++ /dev/null @@ -1,211 +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 + ','; - } - } - - 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 76dc482e4..000000000 --- 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 0ec34b043..000000000 --- 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 acf180d84..000000000 --- a/examples-jsm/examples/renderers/common/Renderer.ts +++ /dev/null @@ -1,1336 +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, - }; - } - - 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); - } - } - } - - 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); - } - - getArrayBuffer(attribute) { - // @deprecated, r155 - - console.warn('THREE.Renderer: getArrayBuffer() is deprecated. Use getArrayBufferAsync() instead.'); - - return this.getArrayBufferAsync(attribute); - } - - 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); - } - - 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); - - material.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); - } - } - - _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); - } - - get compute() { - return this.computeAsync; - } - - get compile() { - return this.compileAsync; - } -} - -export default Renderer; diff --git a/examples-jsm/examples/renderers/common/Textures.ts b/examples-jsm/examples/renderers/common/Textures.ts deleted file mode 100644 index 0eb0509ca..000000000 --- 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/UniformBuffer.ts b/examples-jsm/examples/renderers/common/UniformBuffer.ts deleted file mode 100644 index 28aac0d7e..000000000 --- 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 beb353479..000000000 --- a/examples-jsm/examples/renderers/common/UniformsGroup.ts +++ /dev/null @@ -1,247 +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; - - // 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 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.isFloatUniform) 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.buffer; - const v = uniform.getValue(); - const offset = uniform.offset; - - if (a[offset] !== v) { - a[offset] = v; - updated = true; - } - - return updated; - } - - updateVector2(uniform) { - let updated = false; - - const a = this.buffer; - const v = uniform.getValue(); - const offset = uniform.offset; - - if (a[offset + 0] !== v.x || a[offset + 1] !== v.y) { - a[offset + 0] = v.x; - a[offset + 1] = v.y; - - updated = true; - } - - return updated; - } - - updateVector3(uniform) { - let updated = false; - - const a = this.buffer; - 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 + 0] = v.x; - a[offset + 1] = v.y; - a[offset + 2] = v.z; - - updated = true; - } - - return updated; - } - - updateVector4(uniform) { - let updated = false; - - const a = this.buffer; - 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) { - a[offset + 0] = v.x; - a[offset + 1] = v.y; - a[offset + 2] = v.z; - a[offset + 3] = v.w; - - updated = true; - } - - return updated; - } - - updateColor(uniform) { - let updated = false; - - const a = this.buffer; - const c = uniform.getValue(); - const offset = uniform.offset; - - if (a[offset + 0] !== c.r || a[offset + 1] !== c.g || a[offset + 2] !== c.b) { - a[offset + 0] = c.r; - a[offset + 1] = c.g; - a[offset + 2] = c.b; - - updated = true; - } - - return updated; - } - - updateMatrix3(uniform) { - let updated = false; - - const a = this.buffer; - 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] - ) { - 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]; - - updated = true; - } - - return updated; - } - - updateMatrix4(uniform) { - let updated = false; - - const a = this.buffer; - const e = uniform.getValue().elements; - const offset = uniform.offset; - - if (arraysEqual(a, e, offset) === false) { - a.set(e, offset); - updated = true; - } - - return updated; - } -} - -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/nodes/NodeBuilderState.ts b/examples-jsm/examples/renderers/common/nodes/NodeBuilderState.ts deleted file mode 100644 index 5553bd2d0..000000000 --- a/examples-jsm/examples/renderers/common/nodes/NodeBuilderState.ts +++ /dev/null @@ -1,43 +0,0 @@ -class NodeBuilderState { - constructor( - vertexShader, - fragmentShader, - computeShader, - nodeAttributes, - bindings, - updateNodes, - updateBeforeNodes, - 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.usedTimes = 0; - } - - createBindings() { - const bindingsArray = []; - - for (const instanceBinding of this.bindings) { - let binding = instanceBinding; - - if (instanceBinding.shared !== true) { - binding = instanceBinding.clone(); - } - - bindingsArray.push(binding); - } - - return bindingsArray; - } -} - -export default NodeBuilderState; 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 0bbc1adde..000000000 --- a/examples-jsm/examples/renderers/common/nodes/NodeUniformsGroup.ts +++ /dev/null @@ -1,34 +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; - } - - get shared() { - return this.groupNode.shared; - } - - 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 86df5654c..000000000 --- a/examples-jsm/examples/renderers/common/nodes/Nodes.ts +++ /dev/null @@ -1,385 +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, - renderObject.scene, - ); - nodeBuilder.material = renderObject.material; - 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.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); - } - } - - 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 b9a999f14..000000000 --- a/examples-jsm/examples/renderers/webgl/WebGLBackend.ts +++ /dev/null @@ -1,1190 +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, scene = null) { - return new GLSLNodeBuilder(object, renderer, scene); - } - - // 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); - } - - _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) { - console.error('THREE.WebGLBackend:', gl.getProgramInfoLog(programGPU)); - - console.error('THREE.WebGLBackend:', gl.getShaderInfoLog(fragmentShader)); - console.error('THREE.WebGLBackend:', gl.getShaderInfoLog(vertexShader)); - } - - gl.useProgram(programGPU); - - // Bindings - - this._setupBindings(renderObject.getBindings(), 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) { - console.error('THREE.WebGLBackend:', gl.getProgramInfoLog(programGPU)); - - console.error('THREE.WebGLBackend:', gl.getShaderInfoLog(fragmentShader)); - console.error('THREE.WebGLBackend:', gl.getShaderInfoLog(vertexShader)); - } - - gl.useProgram(programGPU); - - // Bindings - - this.createBindings(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(bindings) { - this.updateBindings(bindings); - } - - updateBindings(bindings) { - const { gl } = this; - - let groupIndex = 0; - let textureIndex = 0; - - for (const binding of 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) { - //console.warn( 'Abstract class.' ); - } - - 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 binding of 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 binding of 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 4910f51f6..000000000 --- a/examples-jsm/examples/renderers/webgl/nodes/GLSLNodeBuilder.ts +++ /dev/null @@ -1,655 +0,0 @@ -import { MathNode, GLSLNodeParser, NodeBuilder, UniformNode, 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 { 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 = { - instance: true, - swizzleAssign: true, -}; - -const defaultPrecisions = ` -precision highp float; -precision highp int; -precision highp sampler3D; -precision mediump sampler2DArray; -precision lowp sampler2DShadow; -`; - -class GLSLNodeBuilder extends NodeBuilder { - constructor(object, renderer, scene = null) { - super(object, renderer, new GLSLNodeParser(), scene); - - this.uniformGroups = {}; - this.transforms = []; - } - - getMethod(method) { - return glslMethods[method] || method; - } - - getPropertyName(node, shaderStage) { - if (node.isOutputStructVar) return ''; - - return super.getPropertyName(node, shaderStage); - } - - 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; - let format = RedFormat; - - if (itemSize === 2) { - format = RGFormat; - } else if (itemSize === 3) { - format = RGBFormat; - } else if (itemSize === 4) { - format = RGBAFormat; - } - - 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 Float32Array(newSize); - - newArray.set(originalArray, 0); - - attribute.array = newArray; - - const pboTexture = new DataTexture(attribute.array, width, height, format, FloatType); - pboTexture.needsUpdate = true; - pboTexture.isPBOTexture = true; - - const pbo = new UniformNode(pboTexture); - pbo.setPrecision('high'); - - attribute.pboNode = pbo; - attribute.pbo = pbo.value; - - this.getUniformFromNode(attribute.pboNode, 'texture', this.shaderStage, this.context.label); - } - } - - generatePBO(storageArrayElementNode) { - const { node, indexNode } = storageArrayElementNode; - const attribute = node.value; - - 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'); - - // - - this.addLineFlowCode(`${propertyName} = ${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) { - if (variable.isOutputStructVar) continue; - - 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; - - if (texture.compareFunction) { - snippet = `sampler2DShadow ${uniform.name};`; - } else if (texture.isDataArrayTexture === true) { - snippet = `sampler2DArray ${uniform.name};`; - } else { - snippet = `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} ${uniform.name};`; - - 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 === 'int' || type === 'uint' ? '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 === 'int' || type === 'uint' ? '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) { - return supports[name] === true; - } - - 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) { - if (type === 'texture') { - uniformGPU = new NodeSampledTexture(uniformNode.name, uniformNode.node); - - this.bindings[shaderStage].push(uniformGPU); - } else if (type === 'cubeTexture') { - uniformGPU = new NodeSampledCubeTexture(uniformNode.name, uniformNode.node); - - this.bindings[shaderStage].push(uniformGPU); - } else if (type === 'texture3D') { - uniformGPU = new NodeSampledTexture3D(uniformNode.name, uniformNode.node); - this.bindings[shaderStage].push(uniformGPU); - } else if (type === 'buffer') { - node.name = `NodeBuffer_${node.id}`; - uniformNode.name = `buffer${node.id}`; - - const buffer = new NodeUniformBuffer(node); - buffer.name = node.name; - - this.bindings[shaderStage].push(buffer); - - uniformGPU = buffer; - } else { - const group = node.groupNode; - const groupName = group.name; - - 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; - - this.bindings[shaderStage].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 97a42577f..000000000 --- a/examples-jsm/examples/renderers/webgpu/WebGPUBackend.ts +++ /dev/null @@ -1,1186 +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 group - - const bindGroupGPU = this.get(bindings).group; - passEncoderGPU.setBindGroup(0, bindGroupGPU); - - 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 bindingsData = this.get(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 group - - const bindGroupGPU = bindingsData.group; - passEncoderGPU.setBindGroup(0, bindGroupGPU); - - // 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, scene = null) { - return new WGSLNodeBuilder(object, renderer, scene); - } - - // 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(bindings) { - this.bindingUtils.createBindings(bindings); - } - - updateBindings(bindings) { - this.bindingUtils.createBindings(bindings); - } - - 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 1e548639e..000000000 --- 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 ed6e38f97..000000000 --- a/examples-jsm/examples/renderers/webgpu/nodes/WGSLNodeBuilder.ts +++ /dev/null @@ -1,921 +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'; - -// 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 = { - instance: true, - 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; - -} -`), -}; - -class WGSLNodeBuilder extends NodeBuilder { - constructor(object, renderer, scene = null) { - super(object, renderer, new WGSLNodeParser(), scene); - - 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 { - return this.generateTextureLod(texture, textureProperty, uvSnippet); - } - } - - _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 { - return this.generateTextureLod(texture, textureProperty, uvSnippet, 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); - } - - _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; - } - - 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 bindings = this.bindings[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); - } else if (type === 'cubeTexture') { - texture = new NodeSampledCubeTexture(uniformNode.name, uniformNode.node); - } else if (type === 'texture3D') { - texture = new NodeSampledTexture3D(uniformNode.name, uniformNode.node); - } - - texture.store = node.isStoreTextureNode === 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); - 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); - buffer.setVisibility(gpuShaderStageLib[shaderStage]); - - bindings.push(buffer); - - uniformGPU = buffer; - } else { - const group = node.groupNode; - const groupName = group.name; - - 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; - - if (shaderStage === 'vertex') { - this.bindingsOffset['fragment'] = bindings.length; - } - } - - return uniformNode; - } - - isReference(type) { - return ( - super.isReference(type) || - type === 'texture_2d' || - type === 'texture_cube' || - type === 'texture_depth_2d' || - type === 'texture_storage_2d' || - type === 'texture_3d' - ); - } - - 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}`); - } - - 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); - } - - 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 = {}; - - let index = this.bindingsOffset[shaderStage]; - - for (const uniform of uniforms) { - 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.isStoreTextureNode !== true - ) { - if (texture.isDepthTexture === true && texture.compareFunction !== null) { - bindingSnippets.push( - `@binding( ${index++} ) @group( 0 ) var ${uniform.name}_sampler : sampler_comparison;`, - ); - } else { - bindingSnippets.push( - `@binding( ${index++} ) @group( 0 ) 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.isStoreTextureNode === true) { - const format = getFormat(texture); - - textureType = `texture_storage_2d<${format}, write>`; - } else { - const componentPrefix = this.getComponentTypeFromTexture(texture).charAt(0); - - textureType = `texture_2d<${componentPrefix}32>`; - } - - bindingSnippets.push(`@binding( ${index++} ) @group( 0 ) 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, index++), - ); - } else { - const vectorType = this.getType(this.getVectorType(uniform.type)); - const groupName = uniform.groupNode.name; - - const group = - uniformGroups[groupName] || - (uniformGroups[groupName] = { - index: index++, - 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)); - } - - 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) { - return supports[name] === true; - } - - _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 3abfc4ad6..000000000 --- a/examples-jsm/examples/renderers/webgpu/nodes/WGSLNodeFunction.ts +++ /dev/null @@ -1,87 +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]+)?/i; -const propertiesRegexp = /[a-z_0-9]+|<(.*?)>+/gi; - -const wgslTypeLib = { - f32: 'float', -}; - -const parse = source => { - source = source.trim(); - - const declaration = source.match(declarationRegexp); - - if (declaration !== null && declaration.length === 4) { - // tokenizer - - const inputsCode = declaration[2]; - const propsMatches = []; - - let nameMatch = null; - - while ((nameMatch = propertiesRegexp.exec(inputsCode)) !== null) { - propsMatches.push(nameMatch); - } - - // parser - - const inputs = []; - - let i = 0; - - while (i < propsMatches.length) { - // default - - const name = propsMatches[i++][0]; - let type = propsMatches[i++][0]; - - type = wgslTypeLib[type] || type; - - // precision - - if (i < propsMatches.length && propsMatches[i][0].startsWith('<') === true) i++; - - // add input - - inputs.push(new NodeFunctionInput(type, name)); - } - - // - - const blockCode = source.substring(declaration[0].length); - - const name = declaration[1] !== undefined ? declaration[1] : ''; - const type = declaration[3] || 'void'; - - return { - type, - inputs, - name, - inputsCode, - blockCode, - }; - } else { - throw new Error('FunctionNode: Function is not a WGSL code.'); - } -}; - -class WGSLNodeFunction extends NodeFunction { - constructor(source) { - const { type, inputs, name, inputsCode, blockCode } = parse(source); - - super(type, inputs, name); - - this.inputsCode = inputsCode; - this.blockCode = blockCode; - } - - getCode(name = this.name) { - const type = this.type !== 'void' ? '-> ' + this.type : ''; - - return `fn ${name} ( ${this.inputsCode.trim()} ) ${type}` + 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 c32133df4..000000000 --- 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; From 2107a2c9c840d8f70091e433c50b4f129bb45d86 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sun, 26 May 2024 22:07:07 -0400 Subject: [PATCH 5/5] Updates --- examples-jsm/declarations.js | 1 + types/three/examples/jsm/nodes/core/Node.d.ts | 14 +-- .../jsm/renderers/common/nodes/Nodes.d.ts | 101 ++++++++++++++++++ 3 files changed, 110 insertions(+), 6 deletions(-) create mode 100644 types/three/examples/jsm/renderers/common/nodes/Nodes.d.ts diff --git a/examples-jsm/declarations.js b/examples-jsm/declarations.js index 8865e052b..af45c94b0 100644 --- a/examples-jsm/declarations.js +++ b/examples-jsm/declarations.js @@ -11,6 +11,7 @@ const files = [ 'nodes/core/NodeVar', 'nodes/core/NodeVarying', 'renderers/common/nodes/NodeBuilderState', + 'renderers/common/nodes/Nodes', 'renderers/common/Binding', 'renderers/common/ChainMap', 'renderers/common/ClippingContext', diff --git a/types/three/examples/jsm/nodes/core/Node.d.ts b/types/three/examples/jsm/nodes/core/Node.d.ts index 18635a544..82017523d 100644 --- a/types/three/examples/jsm/nodes/core/Node.d.ts +++ b/types/three/examples/jsm/nodes/core/Node.d.ts @@ -79,13 +79,13 @@ declare class Node extends EventDispatcher<{ constructor(nodeType?: string | null); set needsUpdate(value: boolean); get type(): string | undefined; - onUpdate(callback: (this: this, frame: NodeFrame) => void, updateType: NodeUpdateType): this; + onUpdate(callback: (this: this, frame: NodeFrame) => unknown, updateType: NodeUpdateType): this; onFrameUpdate(callback: (this: this, frame: NodeFrame) => void): this; onRenderUpdate(callback: (this: this, frame: NodeFrame) => void): this; onObjectUpdate(callback: (this: this, frame: NodeFrame) => void): this; - onReference(callback: (this: this, frame: NodeBuilder | NodeFrame) => this): this; + onReference(callback: (this: this, frame: NodeBuilder | NodeFrame) => unknown): this; getSelf(): this; - updateReference(state: NodeBuilder | NodeFrame): this; + updateReference(state: NodeBuilder | NodeFrame): unknown; isGlobal(builder: NodeBuilder): boolean; getChildren(): Generator; dispose(): void; @@ -97,8 +97,8 @@ declare class Node extends EventDispatcher<{ getElementType(builder: NodeBuilder): "bool" | "int" | "float" | "vec2" | "vec3" | "vec4" | "uint" | null; getNodeType(builder: NodeBuilder): string | null; getShared(builder: NodeBuilder): Node; - setup(builder: NodeBuilder): Node | null; - construct(builder: NodeBuilder): Node | null; + setup(builder: NodeBuilder): unknown; + construct(builder: NodeBuilder): unknown; increaseUsage(builder: NodeBuilder): number; analyze(builder: NodeBuilder): void; generate(builder: NodeBuilder, output?: string | null): string | null | undefined; @@ -111,5 +111,7 @@ declare class Node extends EventDispatcher<{ toJSON(meta?: NodeJSONMeta | string): NodeJSONOutputData; } export default Node; -export declare function addNodeClass(type: string, nodeClass: typeof Node): void; +export declare function addNodeClass(type: string, nodeClass: { + new(...args: any[]): Node; +}): void; export declare function createNodeFromType(type: string): Node | undefined; diff --git a/types/three/examples/jsm/renderers/common/nodes/Nodes.d.ts b/types/three/examples/jsm/renderers/common/nodes/Nodes.d.ts new file mode 100644 index 000000000..5d66d4021 --- /dev/null +++ b/types/three/examples/jsm/renderers/common/nodes/Nodes.d.ts @@ -0,0 +1,101 @@ +import { Camera, Color, CubeTexture, FogBase, Material, Object3D, Scene, Texture } from "three"; +import Node from "../../../nodes/core/Node.js"; +import NodeBuilder from "../../../nodes/core/NodeBuilder.js"; +import UniformGroupNode from "../../../nodes/core/UniformGroupNode.js"; +import ComputeNode from "../../../nodes/gpgpu/ComputeNode.js"; +import LightsNode from "../../../nodes/lighting/LightsNode.js"; +import { NodeFrame, ShaderNodeObject } from "../../../nodes/Nodes.js"; +import Backend from "../Backend.js"; +import ChainMap from "../ChainMap.js"; +import DataMap from "../DataMap.js"; +import Renderer from "../Renderer.js"; +import RenderObject from "../RenderObject.js"; +import NodeBuilderState from "./NodeBuilderState.js"; +import NodeUniformsGroup from "./NodeUniformsGroup.js"; +declare module "three" { + interface Scene { + environmentNode?: Node | null | undefined; + backgroundNode?: Node | null | undefined; + fogNode?: Node | null | undefined; + } +} +interface NodeUniformsGroupData { + renderId?: number | undefined; + frameId?: number | undefined; +} +interface RenderObjectData { + nodeBuilderState?: NodeBuilderState | undefined; +} +interface ComputeNodeData { + nodeBuilderState?: NodeBuilderState | undefined; +} +interface SceneData { + background?: Color | Texture | CubeTexture | undefined; + backgroundNode?: Node | undefined; + fog?: FogBase | undefined; + fogNode?: Node | undefined; + environment?: Texture | undefined; + environmentNode?: Node | undefined; +} +declare class Nodes extends DataMap<{ + nodeUniformsGroup: { + key: NodeUniformsGroup; + value: NodeUniformsGroupData; + }; + renderObject: { + key: RenderObject; + value: RenderObjectData; + }; + computeNode: { + key: ComputeNode; + value: ComputeNodeData; + }; + scene: { + key: Scene; + value: SceneData; + }; +}> { + renderer: Renderer; + backend: Backend; + nodeFrame: NodeFrame; + nodeBuilderCache: Map; + callHashCache: ChainMap; + groupsData: ChainMap; + constructor(renderer: Renderer, backend: Backend); + updateGroup(nodeUniformsGroup: NodeUniformsGroup): boolean; + getForRenderCacheKey(renderObject: RenderObject): string; + getForRender(renderObject: RenderObject): NodeBuilderState; + delete( + object: NodeUniformsGroup | RenderObject | ComputeNode | Scene, + ): SceneData | RenderObjectData | NodeUniformsGroupData | ComputeNodeData; + getForCompute(computeNode: ComputeNode): NodeBuilderState; + _createNodeBuilderState(nodeBuilder: NodeBuilder): NodeBuilderState; + getEnvironmentNode(scene: Scene): Node | null; + getBackgroundNode(scene: Scene): Node | null; + getFogNode(scene: Scene): Node | null; + getCacheKey(scene: Scene, lightsNode: LightsNode): string; + updateScene(scene: Scene): void; + get isToneMappingState(): boolean; + updateBackground(scene: Scene): void; + updateFog(scene: Scene): void; + updateEnvironment(scene: Scene): void; + getNodeFrame( + renderer?: Renderer, + scene?: Scene | null, + object?: Object3D | null, + camera?: Camera | null, + material?: Material | null, + ): NodeFrame; + getNodeFrameForRender(renderObject: RenderObject): NodeFrame; + getOutputNode(outputTexture: Texture): ShaderNodeObject; + updateBefore(renderObject: RenderObject): void; + updateForCompute(computeNode: ComputeNode): void; + updateForRender(renderObject: RenderObject): void; + dispose(): void; +} +export default Nodes;