From caf24699f36882762425492ce0c05b5efa730c15 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sat, 25 May 2024 15:16:22 -0400 Subject: [PATCH 01/16] Add examples --- 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-jsm/examples/nodes/core/constants.ts | 28 + examples-jsm/examples/nodes/fog/FogNode.ts | 38 + .../nodes/lighting/EnvironmentNode.ts | 118 ++ .../examples/nodes/lighting/LightsNode.ts | 157 ++ .../examples/renderers/common/Backend.ts | 167 +++ .../examples/renderers/common/Renderer.ts | 1336 +++++++++++++++++ .../examples/renderers/webgl/WebGLBackend.ts | 1190 +++++++++++++++ .../renderers/webgpu/WebGPUBackend.ts | 1186 +++++++++++++++ .../renderers/webgpu/WebGPURenderer.ts | 43 + examples-jsm/index.js | 4 + 22 files changed, 6078 insertions(+) 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/constants.ts create mode 100644 examples-jsm/examples/nodes/fog/FogNode.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/Backend.ts create mode 100644 examples-jsm/examples/renderers/common/Renderer.ts create mode 100644 examples-jsm/examples/renderers/webgl/WebGLBackend.ts create mode 100644 examples-jsm/examples/renderers/webgpu/WebGPUBackend.ts create mode 100644 examples-jsm/examples/renderers/webgpu/WebGPURenderer.ts 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/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/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/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/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/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/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/index.js b/examples-jsm/index.js index 4b135bfb8..957a77571 100644 --- a/examples-jsm/index.js +++ b/examples-jsm/index.js @@ -20,7 +20,11 @@ const files = [ 'nodes/fog/FogNode', 'nodes/lighting/EnvironmentNode', 'nodes/lighting/LightsNode', + 'renderers/common/Backend', 'renderers/common/Renderer', + 'renderers/webgl/WebGLBackend', + 'renderers/webgpu/WebGPUBackend', + 'renderers/webgpu/WebGPURenderer', ]; const inDir = '../three.js/examples/jsm'; From 58070dba4e67920a372420f62c613aa4f80da04f Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sat, 25 May 2024 15:44:31 -0400 Subject: [PATCH 02/16] Update patch --- examples-jsm/changes.patch | 475 +++++++++++++++++++++++++++++++++++++ 1 file changed, 475 insertions(+) diff --git a/examples-jsm/changes.patch b/examples-jsm/changes.patch index d1bff8eb9..87134e2f1 100644 --- a/examples-jsm/changes.patch +++ b/examples-jsm/changes.patch @@ -1192,3 +1192,478 @@ index 9417df5..4376155 100644 let viewZ; const getViewZ = builder.context.getViewZ; +diff --git a/examples-jsm/examples/renderers/common/Backend.ts b/examples-jsm/examples/renderers/common/Backend.ts +index e48a802..e56230a 100644 +--- a/examples-jsm/examples/renderers/common/Backend.ts ++++ b/examples-jsm/examples/renderers/common/Backend.ts +@@ -1,19 +1,28 @@ ++import Color4 from './Color4.js'; ++import { Vector2, Vector4, REVISION, createCanvasElement } from 'three'; ++import Renderer from './Renderer.js'; ++ + let vector2 = null; + let vector4 = null; + let color4 = null; + +-import Color4 from './Color4.js'; +-import { Vector2, Vector4, REVISION, createCanvasElement } from 'three'; ++export interface BackendParameters { ++ canvas?: HTMLCanvasElement | undefined; ++} + + class Backend { +- constructor(parameters = {}) { ++ parameters: BackendParameters; ++ renderer: Renderer | null; ++ domElement: HTMLCanvasElement | null; ++ ++ constructor(parameters: BackendParameters = {}) { + this.parameters = Object.assign({}, parameters); + this.data = new WeakMap(); + this.renderer = null; + this.domElement = null; + } + +- async init(renderer) { ++ async init(renderer: Renderer) { + this.renderer = renderer; + } + +diff --git a/examples-jsm/examples/renderers/common/Renderer.ts b/examples-jsm/examples/renderers/common/Renderer.ts +index acf180d..e4fcfbd 100644 +--- a/examples-jsm/examples/renderers/common/Renderer.ts ++++ b/examples-jsm/examples/renderers/common/Renderer.ts +@@ -30,10 +30,26 @@ import { + RenderTarget, + HalfFloatType, + RGBAFormat, ++ ColorSpace, ++ ToneMapping, ++ Plane, ++ ShadowMapType, ++ Camera, ++ Color, ++ Object3D, ++ FramebufferTexture, ++ Texture, ++ Box2, ++ BufferGeometry, ++ Material, ++ Group, + } from 'three'; + import { NodeMaterial } from '../../nodes/Nodes.js'; + import QuadMesh from '../../objects/QuadMesh.js'; + import RenderBundles from './RenderBundles.js'; ++import Backend from './Backend.js'; ++import Node from '../../nodes/core/Node.js'; ++import LightsNode from '../../nodes/lighting/LightsNode.js'; + + const _scene = new Scene(); + const _drawingBufferSize = new Vector2(); +@@ -43,8 +59,101 @@ const _projScreenMatrix = new Matrix4(); + const _vector3 = new Vector3(); + const _quad = new QuadMesh(new NodeMaterial()); + ++export interface RendererParameters { ++ logarithmicDepthBuffer?: boolean | undefined; ++ alpha?: boolean | undefined; ++} ++ + class Renderer { +- constructor(backend, parameters = {}) { ++ readonly isRenderer: true; ++ ++ domElement: HTMLCanvasElement | null; ++ ++ backend: Backend; ++ ++ autoClear: boolean; ++ autoClearColor: boolean; ++ autoClearDepth: boolean; ++ autoClearStencil: boolean; ++ ++ alpha: boolean; ++ ++ logarithmicDepthBuffer: boolean; ++ ++ outputColorSpace: ColorSpace; ++ ++ toneMapping: ToneMapping; ++ toneMappingExposure: number; ++ ++ sortObjects: boolean; ++ ++ depth: boolean; ++ stencil: boolean; ++ ++ clippingPlanes: Plane[]; ++ ++ info: Info; ++ ++ toneMappingNode: Node | null; ++ ++ _pixelRatio: number; ++ _width: number; ++ _height: number; ++ ++ _viewport: Vector4; ++ _scissor: Vector4; ++ _scissorTest: boolean; ++ ++ _attributes: Attributes | null; ++ _geometries: Geometries | null; ++ _nodes: Nodes | null; ++ _animation: Animation | null; ++ _bindings: Bindings | null; ++ _objects: RenderObjects | null; ++ _pipelines: Pipelines | null; ++ _bundles: RenderBundles | null; ++ _renderLists: RenderLists | null; ++ _renderContexts: RenderContexts | null; ++ _textures: Textures | null; ++ _background: Background | null; ++ ++ // TODO ++ // this._currentRenderContext = null; ++ ++ _opaqueSort: ((a: unknown, b: unknown) => number) | null; ++ _transparentSort: ((a: unknown, b: unknown) => number) | null; ++ ++ // TODO ++ // this._frameBufferTarget = null; ++ ++ _clearColor: ColorSpace; ++ _clearDepth: number; ++ _clearStencil: number; ++ ++ _renderTarget: RenderTarget | null; ++ _activeCubeFace: number; ++ _activeMipmapLevel: number; ++ ++ // TODO ++ // this._renderObjectFunction = null; ++ // this._currentRenderObjectFunction = null; ++ // this._currentRenderBundle = null; ++ ++ // TODO ++ // this._handleObjectFunction = this._renderObjectDirect; ++ ++ // TODO ++ _initialized: boolean; ++ _initPromise: Promise | null; ++ ++ // TODO ++ // this._compilationPromises = null; ++ ++ shadowMap: { enabled: boolean; type: ShadowMapType | null }; ++ ++ xr: { enabled: boolean }; ++ ++ constructor(backend: Backend, parameters: RendererParameters = {}) { + this.isRenderer = true; + + // +@@ -207,7 +316,7 @@ class Renderer { + return this.backend.coordinateSystem; + } + +- async compileAsync(scene, camera, targetScene = null) { ++ async compileAsync(scene: Scene, camera: Camera, targetScene: Scene | null = null) { + if (this._initialized === false) await this.init(); + + // preserve render tree +@@ -320,7 +429,7 @@ class Renderer { + await Promise.all(compilationPromises); + } + +- async renderAsync(scene, camera) { ++ async renderAsync(scene: Scene, camera: Camera) { + if (this._initialized === false) await this.init(); + + const renderContext = this._renderScene(scene, camera); +@@ -397,7 +506,7 @@ class Renderer { + } + } + +- render(scene, camera) { ++ render(scene: Scene, camera: Camera) { + if (this._initialized === false) { + console.warn( + 'THREE.Renderer: .render() called before the backend is initialized. Try using .renderAsync() instead.', +@@ -453,7 +562,7 @@ class Renderer { + return frameBufferTarget; + } + +- _renderScene(scene, camera, useFrameBufferTarget = true) { ++ _renderScene(scene: Scene, camera: Camera, useFrameBufferTarget = true) { + const frameBufferTarget = useFrameBufferTarget ? this._getFrameBufferTarget() : null; + + // preserve render tree +@@ -663,7 +772,7 @@ class Renderer { + return this._activeMipmapLevel; + } + +- async setAnimationLoop(callback) { ++ async setAnimationLoop(callback: ((time: DOMHighResTimeStamp) => void) | null) { + if (this._initialized === false) await this.init(); + + this._animation.setAnimationLoop(callback); +@@ -689,11 +798,11 @@ class Renderer { + return this._pixelRatio; + } + +- getDrawingBufferSize(target) { ++ getDrawingBufferSize(target: Vector2) { + return target.set(this._width * this._pixelRatio, this._height * this._pixelRatio).floor(); + } + +- getSize(target) { ++ getSize(target: Vector2) { + return target.set(this._width, this._height); + } + +@@ -703,30 +812,30 @@ class Renderer { + this.setSize(this._width, this._height, false); + } + +- setDrawingBufferSize(width, height, pixelRatio) { ++ setDrawingBufferSize(width: number, height: number, pixelRatio: number) { + this._width = width; + this._height = height; + + this._pixelRatio = pixelRatio; + +- this.domElement.width = Math.floor(width * pixelRatio); +- this.domElement.height = Math.floor(height * 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) { ++ setSize(width: number, height: number, updateStyle = true) { + this._width = width; + this._height = height; + +- this.domElement.width = Math.floor(width * this._pixelRatio); +- this.domElement.height = Math.floor(height * this._pixelRatio); ++ 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.domElement!.style.width = width + 'px'; ++ this.domElement!.style.height = height + 'px'; + } + + this.setViewport(0, 0, width, height); +@@ -734,15 +843,15 @@ class Renderer { + if (this._initialized) this.backend.updateSize(); + } + +- setOpaqueSort(method) { ++ setOpaqueSort(method: ((a: unknown, b: unknown) => number) | null) { + this._opaqueSort = method; + } + +- setTransparentSort(method) { ++ setTransparentSort(method: ((a: unknown, b: unknown) => number) | null) { + this._transparentSort = method; + } + +- getScissor(target) { ++ getScissor(target: Vector4) { + const scissor = this._scissor; + + target.x = scissor.x; +@@ -753,13 +862,15 @@ class Renderer { + return target; + } + +- setScissor(x, y, width, height) { ++ setScissor(x: Vector4): void; ++ setScissor(x: number, y: number, width: number, height: number): void; ++ setScissor(x: Vector4 | number, y?: number, width?: number, height?: number) { + const scissor = this._scissor; + +- if (x.isVector4) { +- scissor.copy(x); ++ if ((x as Vector4).isVector4) { ++ scissor.copy(x as Vector4); + } else { +- scissor.set(x, y, width, height); ++ scissor.set(x as number, y!, width!, height!); + } + } + +@@ -767,34 +878,36 @@ class Renderer { + return this._scissorTest; + } + +- setScissorTest(boolean) { ++ setScissorTest(boolean: boolean) { + this._scissorTest = boolean; + + this.backend.setScissorTest(boolean); + } + +- getViewport(target) { ++ getViewport(target: Vector4) { + return target.copy(this._viewport); + } + +- setViewport(x, y, width, height, minDepth = 0, maxDepth = 1) { ++ setScissor(x: Vector4): void; ++ setScissor(x: number, y: number, width: number, height: number, minDepth?: number, maxDepth?: number): void; ++ setViewport(x: Vector4 | number, y?: number, width?: number, height?: number, minDepth = 0, maxDepth = 1) { + const viewport = this._viewport; + +- if (x.isVector4) { +- viewport.copy(x); ++ if ((x as Vector4).isVector4) { ++ viewport.copy(x as Vector4); + } else { +- viewport.set(x, y, width, height); ++ viewport.set(x as number, y!, width!, height!); + } + + viewport.minDepth = minDepth; + viewport.maxDepth = maxDepth; + } + +- getClearColor(target) { ++ getClearColor(target: Color4) { + return target.copy(this._clearColor); + } + +- setClearColor(color, alpha = 1) { ++ setClearColor(color: Color, alpha = 1) { + this._clearColor.set(color); + this._clearColor.a = alpha; + } +@@ -803,7 +916,7 @@ class Renderer { + return this._clearColor.a; + } + +- setClearAlpha(alpha) { ++ setClearAlpha(alpha: number) { + this._clearColor.a = alpha; + } + +@@ -811,7 +924,7 @@ class Renderer { + return this._clearDepth; + } + +- setClearDepth(depth) { ++ setClearDepth(depth: number) { + this._clearDepth = depth; + } + +@@ -819,11 +932,11 @@ class Renderer { + return this._clearStencil; + } + +- setClearStencil(stencil) { ++ setClearStencil(stencil: number) { + this._clearStencil = stencil; + } + +- isOccluded(object) { ++ isOccluded(object: Object3D) { + const renderContext = this._currentRenderContext; + + return renderContext && this.backend.isOccluded(renderContext, object); +@@ -909,7 +1022,7 @@ class Renderer { + this.setAnimationLoop(null); + } + +- setRenderTarget(renderTarget, activeCubeFace = 0, activeMipmapLevel = 0) { ++ setRenderTarget(renderTarget: RenderTarget | null, activeCubeFace = 0, activeMipmapLevel = 0) { + this._renderTarget = renderTarget; + this._activeCubeFace = activeCubeFace; + this._activeMipmapLevel = activeMipmapLevel; +@@ -927,7 +1040,7 @@ class Renderer { + return this._renderObjectFunction; + } + +- async computeAsync(computeNodes) { ++ async computeAsync(computeNodes: ComputeNode | ComputeNode[]) { + if (this._initialized === false) await this.init(); + + const nodeFrame = this._nodes.nodeFrame; +@@ -993,13 +1106,13 @@ class Renderer { + nodeFrame.renderId = previousRenderId; + } + +- async hasFeatureAsync(name) { ++ async hasFeatureAsync(name: string) { + if (this._initialized === false) await this.init(); + + return this.backend.hasFeature(name); + } + +- hasFeature(name) { ++ hasFeature(name: string) { + if (this._initialized === false) { + console.warn( + 'THREE.Renderer: .hasFeature() called before the backend is initialized. Try using .hasFeatureAsync() instead.', +@@ -1011,7 +1124,7 @@ class Renderer { + return this.backend.hasFeature(name); + } + +- copyFramebufferToTexture(framebufferTexture) { ++ copyFramebufferToTexture(framebufferTexture: FramebufferTexture) { + const renderContext = this._currentRenderContext; + + this._textures.updateTexture(framebufferTexture); +@@ -1019,14 +1132,27 @@ class Renderer { + this.backend.copyFramebufferToTexture(framebufferTexture, renderContext); + } + +- copyTextureToTexture(srcTexture, dstTexture, srcRegion = null, dstPosition = null, level = 0) { ++ copyTextureToTexture( ++ srcTexture: Texture, ++ dstTexture: Texture, ++ srcRegion: Box2 | null = null, ++ dstPosition: Vector2 | null = 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) { ++ readRenderTargetPixelsAsync( ++ renderTarget: RenderTarget, ++ x: number, ++ y: number, ++ width: number, ++ height: number, ++ index = 0, ++ ) { + return this.backend.copyTextureToBuffer(renderTarget.textures[index], x, y, width, height); + } + +@@ -1167,7 +1293,15 @@ class Renderer { + } + } + +- renderObject(object, scene, camera, geometry, material, group, lightsNode) { ++ renderObject( ++ object: Object3D, ++ scene: Scene, ++ camera: Camera, ++ geometry: BufferGeometry, ++ material: Material, ++ group: Group, ++ lightsNode: LightsNode, ++ ) { + let overridePositionNode; + let overrideFragmentNode; + let overrideDepthNode; From d9b9e329977b69e8b7623ff19bf32cadb94946e3 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sat, 25 May 2024 15:44:43 -0400 Subject: [PATCH 03/16] Delete examples --- 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-jsm/examples/nodes/core/constants.ts | 28 - examples-jsm/examples/nodes/fog/FogNode.ts | 38 - .../nodes/lighting/EnvironmentNode.ts | 118 -- .../examples/nodes/lighting/LightsNode.ts | 157 -- .../examples/renderers/common/Backend.ts | 167 --- .../examples/renderers/common/Renderer.ts | 1336 ----------------- .../examples/renderers/webgl/WebGLBackend.ts | 1190 --------------- .../renderers/webgpu/WebGPUBackend.ts | 1186 --------------- .../renderers/webgpu/WebGPURenderer.ts | 43 - 21 files changed, 6074 deletions(-) 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/constants.ts delete mode 100644 examples-jsm/examples/nodes/fog/FogNode.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/renderers/common/Backend.ts delete mode 100644 examples-jsm/examples/renderers/common/Renderer.ts delete mode 100644 examples-jsm/examples/renderers/webgl/WebGLBackend.ts delete mode 100644 examples-jsm/examples/renderers/webgpu/WebGPUBackend.ts delete mode 100644 examples-jsm/examples/renderers/webgpu/WebGPURenderer.ts 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/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/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/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/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/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/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; From 38a6f9fe785e97744587847d8c9af30281741c7f Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sat, 25 May 2024 16:06:29 -0400 Subject: [PATCH 04/16] Add examples --- 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-jsm/examples/nodes/core/constants.ts | 28 + examples-jsm/examples/nodes/fog/FogNode.ts | 38 + .../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/Bindings.ts | 153 ++ .../examples/renderers/common/BufferUtils.ts | 23 + .../renderers/common/ClippingContext.ts | 128 ++ .../examples/renderers/common/Color4.ts | 27 + .../examples/renderers/common/Geometries.ts | 163 ++ .../examples/renderers/common/Info.ts | 76 + .../examples/renderers/common/Pipelines.ts | 270 ++++ .../renderers/common/RenderObjects.ts | 100 ++ .../examples/renderers/common/Renderer.ts | 1336 +++++++++++++++++ .../examples/renderers/common/Textures.ts | 288 ++++ .../examples/renderers/common/nodes/Nodes.ts | 385 +++++ .../examples/renderers/webgl/WebGLBackend.ts | 1190 +++++++++++++++ .../renderers/webgpu/WebGPUBackend.ts | 1186 +++++++++++++++ .../renderers/webgpu/WebGPURenderer.ts | 43 + examples-jsm/index.js | 13 + 35 files changed, 7907 insertions(+) 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/constants.ts create mode 100644 examples-jsm/examples/nodes/fog/FogNode.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/Bindings.ts create mode 100644 examples-jsm/examples/renderers/common/BufferUtils.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/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/RenderObjects.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/nodes/Nodes.ts create mode 100644 examples-jsm/examples/renderers/webgl/WebGLBackend.ts create mode 100644 examples-jsm/examples/renderers/webgpu/WebGPUBackend.ts create mode 100644 examples-jsm/examples/renderers/webgpu/WebGPURenderer.ts 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/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/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/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/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/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/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/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/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/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/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/index.js b/examples-jsm/index.js index 957a77571..3028f258c 100644 --- a/examples-jsm/index.js +++ b/examples-jsm/index.js @@ -20,8 +20,21 @@ const files = [ 'nodes/fog/FogNode', 'nodes/lighting/EnvironmentNode', 'nodes/lighting/LightsNode', + 'renderers/common/nodes/Nodes', + 'renderers/common/Animation', + 'renderers/common/Attributes', 'renderers/common/Backend', + 'renderers/common/Background', + 'renderers/common/Bindings', + 'renderers/common/BufferUtils', + 'renderers/common/ClippingContext', + 'renderers/common/Color4', + 'renderers/common/Geometries', + 'renderers/common/Info', + 'renderers/common/Pipelines', 'renderers/common/Renderer', + 'renderers/common/RenderObjects', + 'renderers/common/Textures', 'renderers/webgl/WebGLBackend', 'renderers/webgpu/WebGPUBackend', 'renderers/webgpu/WebGPURenderer', From 04a17271a36d77860e91b124cf3597ba41954c83 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sat, 25 May 2024 16:50:19 -0400 Subject: [PATCH 05/16] Update --- types/three/src/math/Color.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/three/src/math/Color.d.ts b/types/three/src/math/Color.d.ts index f476a83a6..487d4c57a 100644 --- a/types/three/src/math/Color.d.ts +++ b/types/three/src/math/Color.d.ts @@ -255,7 +255,7 @@ export class Color { * Copies given color. * @param color Color to copy. */ - copy(color: this): this; + copy(color: Color): this; /** * Copies given color making conversion from sRGB to linear space. From 4b3e281ec6b5e5f804f8d3041653e8cc1bd1be86 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sat, 25 May 2024 16:50:42 -0400 Subject: [PATCH 06/16] Update patch --- examples-jsm/changes.patch | 756 ++++++++++++++++++++++++++++++++++++- 1 file changed, 740 insertions(+), 16 deletions(-) diff --git a/examples-jsm/changes.patch b/examples-jsm/changes.patch index 87134e2f1..54c47466b 100644 --- a/examples-jsm/changes.patch +++ b/examples-jsm/changes.patch @@ -1192,6 +1192,75 @@ index 9417df5..4376155 100644 let viewZ; const getViewZ = builder.context.getViewZ; +diff --git a/examples-jsm/examples/renderers/common/Animation.ts b/examples-jsm/examples/renderers/common/Animation.ts +index 0b00319..c190633 100644 +--- a/examples-jsm/examples/renderers/common/Animation.ts ++++ b/examples-jsm/examples/renderers/common/Animation.ts +@@ -1,5 +1,14 @@ ++import Nodes from './nodes/Nodes.js'; ++import Info from './Info.js'; ++ + class Animation { +- constructor(nodes, info) { ++ nodes: Nodes; ++ info: Info; ++ ++ animationLoop: ((time: DOMHighResTimeStamp, frame?: XRFrame) => void) | null; ++ requestId: number | null; ++ ++ constructor(nodes: Nodes, info: Info) { + this.nodes = nodes; + this.info = info; + +@@ -10,7 +19,7 @@ class Animation { + } + + _init() { +- const update = (time, frame) => { ++ const update = (time?: DOMHighResTimeStamp, frame?: XRFrame) => { + this.requestId = self.requestAnimationFrame(update); + + if (this.info.autoReset === true) this.info.reset(); +@@ -19,18 +28,18 @@ class Animation { + + this.info.frame = this.nodes.nodeFrame.frameId; + +- if (this.animationLoop !== null) this.animationLoop(time, frame); ++ if (this.animationLoop !== null) this.animationLoop(time!, frame); + }; + + update(); + } + + dispose() { +- self.cancelAnimationFrame(this.requestId); ++ self.cancelAnimationFrame(this.requestId!); + this.requestId = null; + } + +- setAnimationLoop(callback) { ++ setAnimationLoop(callback: ((time: DOMHighResTimeStamp, frame?: XRFrame) => void) | null) { + this.animationLoop = callback; + } + } +diff --git a/examples-jsm/examples/renderers/common/Attributes.ts b/examples-jsm/examples/renderers/common/Attributes.ts +index ed9e8e9..f0f7502 100644 +--- a/examples-jsm/examples/renderers/common/Attributes.ts ++++ b/examples-jsm/examples/renderers/common/Attributes.ts +@@ -1,9 +1,12 @@ + import DataMap from './DataMap.js'; + import { AttributeType } from './Constants.js'; + import { DynamicDrawUsage } from 'three'; ++import Backend from './Backend.js'; + + class Attributes extends DataMap { +- constructor(backend) { ++ backend: Backend; ++ ++ constructor(backend: Backend) { + super(); + + this.backend = backend; diff --git a/examples-jsm/examples/renderers/common/Backend.ts b/examples-jsm/examples/renderers/common/Backend.ts index e48a802..e56230a 100644 --- a/examples-jsm/examples/renderers/common/Backend.ts @@ -1229,8 +1298,272 @@ index e48a802..e56230a 100644 this.renderer = renderer; } +diff --git a/examples-jsm/examples/renderers/common/Background.ts b/examples-jsm/examples/renderers/common/Background.ts +index b7902dd..a2b5e9e 100644 +--- a/examples-jsm/examples/renderers/common/Background.ts ++++ b/examples-jsm/examples/renderers/common/Background.ts +@@ -1,6 +1,6 @@ + import DataMap from './DataMap.js'; + import Color4 from './Color4.js'; +-import { Mesh, SphereGeometry, BackSide, LinearSRGBColorSpace } from 'three'; ++import { Mesh, SphereGeometry, BackSide, LinearSRGBColorSpace, Scene } from 'three'; + import { + vec4, + context, +@@ -10,18 +10,23 @@ import { + NodeMaterial, + modelViewProjection, + } from '../../nodes/Nodes.js'; ++import Renderer from './Renderer.js'; ++import Nodes from './nodes/Nodes.js'; + + const _clearColor = new Color4(); + + class Background extends DataMap { +- constructor(renderer, nodes) { ++ renderer: Renderer; ++ nodes: Nodes; ++ ++ constructor(renderer: Renderer, nodes: Nodes) { + super(); + + this.renderer = renderer; + this.nodes = nodes; + } + +- update(scene, renderList, renderContext) { ++ update(scene: Scene, renderList, renderContext) { + const renderer = this.renderer; + const background = this.nodes.getBackgroundNode(scene) || scene.background; + +diff --git a/examples-jsm/examples/renderers/common/Bindings.ts b/examples-jsm/examples/renderers/common/Bindings.ts +index 9485ec3..6a39a1d 100644 +--- a/examples-jsm/examples/renderers/common/Bindings.ts ++++ b/examples-jsm/examples/renderers/common/Bindings.ts +@@ -1,8 +1,28 @@ + import DataMap from './DataMap.js'; + import { AttributeType } from './Constants.js'; ++import Backend from './Backend.js'; ++import Nodes from './nodes/Nodes.js'; ++import Textures from './Textures.js'; ++import Attributes from './Attributes.js'; ++import Pipelines from './Pipelines.js'; ++import Info from './Info.js'; + + class Bindings extends DataMap { +- constructor(backend, nodes, textures, attributes, pipelines, info) { ++ backend: Backend; ++ textures: Textures; ++ pipelines: Pipelines; ++ attributes: Attributes; ++ nodes: Nodes; ++ info: Info; ++ ++ constructor( ++ backend: Backend, ++ nodes: Nodes, ++ textures: Textures, ++ attributes: Attributes, ++ pipelines: Pipelines, ++ info: Info, ++ ) { + super(); + + this.backend = backend; +diff --git a/examples-jsm/examples/renderers/common/Color4.ts b/examples-jsm/examples/renderers/common/Color4.ts +index c681cc9..5d03ade 100644 +--- a/examples-jsm/examples/renderers/common/Color4.ts ++++ b/examples-jsm/examples/renderers/common/Color4.ts +@@ -1,26 +1,31 @@ +-import { Color } from 'three'; ++import { Color, ColorRepresentation } from 'three'; + + class Color4 extends Color { +- constructor(r, g, b, a = 1) { +- super(r, g, b); ++ a: number; ++ ++ constructor(color?: ColorRepresentation); ++ constructor(r: number, g: number, b: number, a?: number); ++ constructor(r?: number, g?: number, b?: number, a = 1) { ++ super(r as number, g!, b!); + + this.a = a; + } + +- set(r, g, b, a = 1) { ++ set(...args: [color: ColorRepresentation] | [r: number, g: number, b: number, a?: number]): this; ++ set(r: number, g: number, b: number, a = 1) { + this.a = a; + + return super.set(r, g, b); + } + +- copy(color) { +- if (color.a !== undefined) this.a = color.a; ++ copy(color: Color) { ++ if ((color as Color4).a !== undefined) this.a = (color as Color4).a; + + return super.copy(color); + } + +- clone() { +- return new this.constructor(this.r, this.g, this.b, this.a); ++ clone(): this { ++ return new (this.constructor as typeof Color4)(this.r, this.g, this.b, this.a) as this; + } + } + +diff --git a/examples-jsm/examples/renderers/common/Geometries.ts b/examples-jsm/examples/renderers/common/Geometries.ts +index 5da9994..79bed8a 100644 +--- a/examples-jsm/examples/renderers/common/Geometries.ts ++++ b/examples-jsm/examples/renderers/common/Geometries.ts +@@ -1,6 +1,8 @@ + import DataMap from './DataMap.js'; + import { AttributeType } from './Constants.js'; + import { Uint32BufferAttribute, Uint16BufferAttribute } from 'three'; ++import Attributes from './Attributes.js'; ++import Info from './Info.js'; + + function arrayNeedsUint32(array) { + // assumes larger values usually on last +@@ -51,7 +53,10 @@ function getWireframeIndex(geometry) { + } + + class Geometries extends DataMap { +- constructor(attributes, info) { ++ attributes: Attributes; ++ info: Info; ++ ++ constructor(attributes: Attributes, info: Info) { + super(); + + this.attributes = attributes; +diff --git a/examples-jsm/examples/renderers/common/Info.ts b/examples-jsm/examples/renderers/common/Info.ts +index c8e7cb4..083e7c4 100644 +--- a/examples-jsm/examples/renderers/common/Info.ts ++++ b/examples-jsm/examples/renderers/common/Info.ts +@@ -1,4 +1,31 @@ ++import { Line, LineSegments, Mesh, Object3D, Points, Sprite } from 'three'; ++ + class Info { ++ autoReset: boolean; ++ ++ frame: number; ++ calls: number; ++ ++ render: { ++ calls: number; ++ drawCalls: number; ++ triangles: number; ++ points: number; ++ lines: number; ++ timestamp: number; ++ }; ++ ++ compute: { ++ calls: number; ++ computeCalls: number; ++ timestamp: number; ++ }; ++ ++ memory: { ++ geometries: number; ++ textures: number; ++ }; ++ + constructor() { + this.autoReset = true; + +@@ -26,23 +53,23 @@ class Info { + }; + } + +- update(object, count, instanceCount) { ++ update(object: Object3D, count: number, instanceCount: number) { + this.render.drawCalls++; + +- if (object.isMesh || object.isSprite) { ++ if ((object as Mesh).isMesh || (object as Sprite).isSprite) { + this.render.triangles += instanceCount * (count / 3); +- } else if (object.isPoints) { ++ } else if ((object as Points).isPoints) { + this.render.points += instanceCount * count; +- } else if (object.isLineSegments) { ++ } else if ((object as LineSegments).isLineSegments) { + this.render.lines += instanceCount * (count / 2); +- } else if (object.isLine) { ++ } else if ((object as Line).isLine) { + this.render.lines += instanceCount * (count - 1); + } else { + console.error('THREE.WebGPUInfo: Unknown object type.'); + } + } + +- updateTimestamp(type, time) { ++ updateTimestamp(type: 'render' | 'compute', time: number) { + this[type].timestamp += time; + } + +diff --git a/examples-jsm/examples/renderers/common/Pipelines.ts b/examples-jsm/examples/renderers/common/Pipelines.ts +index f6c570c..704790f 100644 +--- a/examples-jsm/examples/renderers/common/Pipelines.ts ++++ b/examples-jsm/examples/renderers/common/Pipelines.ts +@@ -2,9 +2,17 @@ import DataMap from './DataMap.js'; + import RenderPipeline from './RenderPipeline.js'; + import ComputePipeline from './ComputePipeline.js'; + import ProgrammableStage from './ProgrammableStage.js'; ++import Backend from './Backend.js'; ++import Nodes from './nodes/Nodes.js'; ++import Bindings from './Bindings.js'; + + class Pipelines extends DataMap { +- constructor(backend, nodes) { ++ backend: Backend; ++ nodes: Nodes; ++ ++ bindings: Bindings | null; ++ ++ constructor(backend: Backend, nodes: Nodes) { + super(); + + this.backend = backend; +diff --git a/examples-jsm/examples/renderers/common/RenderObjects.ts b/examples-jsm/examples/renderers/common/RenderObjects.ts +index 76dc482..b4d04b1 100644 +--- a/examples-jsm/examples/renderers/common/RenderObjects.ts ++++ b/examples-jsm/examples/renderers/common/RenderObjects.ts +@@ -1,8 +1,28 @@ + import ChainMap from './ChainMap.js'; + import RenderObject from './RenderObject.js'; ++import Renderer from './Renderer.js'; ++import Nodes from './nodes/Nodes.js'; ++import Geometries from './Geometries.js'; ++import Pipelines from './Pipelines.js'; ++import Bindings from './Bindings.js'; ++import Info from './Info.js'; + + class RenderObjects { +- constructor(renderer, nodes, geometries, pipelines, bindings, info) { ++ renderer: RenderObject; ++ nodes: Nodes; ++ geometries: Geometries; ++ pipelines: Pipelines; ++ bindings: Bindings; ++ info: Info; ++ ++ constructor( ++ renderer: Renderer, ++ nodes: Nodes, ++ geometries: Geometries, ++ pipelines: Pipelines, ++ bindings: Bindings, ++ info: Info, ++ ) { + this.renderer = renderer; + this.nodes = nodes; + this.geometries = geometries; diff --git a/examples-jsm/examples/renderers/common/Renderer.ts b/examples-jsm/examples/renderers/common/Renderer.ts -index acf180d..e4fcfbd 100644 +index acf180d..60d8c8f 100644 --- a/examples-jsm/examples/renderers/common/Renderer.ts +++ b/examples-jsm/examples/renderers/common/Renderer.ts @@ -30,10 +30,26 @@ import { @@ -1332,7 +1665,7 @@ index acf180d..e4fcfbd 100644 + // TODO + // this._frameBufferTarget = null; + -+ _clearColor: ColorSpace; ++ _clearColor: Color4; + _clearDepth: number; + _clearStencil: number; + @@ -1363,7 +1696,7 @@ index acf180d..e4fcfbd 100644 this.isRenderer = true; // -@@ -207,7 +316,7 @@ class Renderer { +@@ -207,12 +316,12 @@ class Renderer { return this.backend.coordinateSystem; } @@ -1372,6 +1705,38 @@ index acf180d..e4fcfbd 100644 if (this._initialized === false) await this.init(); // preserve render tree + +- const nodeFrame = this._nodes.nodeFrame; ++ const nodeFrame = this._nodes!.nodeFrame; + + const previousRenderId = nodeFrame.renderId; + const previousRenderContext = this._currentRenderContext; +@@ -277,9 +386,9 @@ class Renderer { + // + + if (renderTarget !== null) { +- this._textures.updateRenderTarget(renderTarget, activeMipmapLevel); ++ this._textures!.updateRenderTarget(renderTarget, activeMipmapLevel); + +- const renderTargetData = this._textures.get(renderTarget); ++ const renderTargetData = this._textures!.get(renderTarget); + + renderContext.textures = renderTargetData.textures; + renderContext.depthTexture = renderTargetData.depthTexture; +@@ -290,11 +399,11 @@ class Renderer { + + // + +- this._nodes.updateScene(sceneRef); ++ this._nodes!.updateScene(sceneRef); + + // + +- this._background.update(sceneRef, renderList, renderContext); ++ this._background!.update(sceneRef, renderList, renderContext); + + // process render lists + @@ -320,7 +429,7 @@ class Renderer { await Promise.all(compilationPromises); } @@ -1381,7 +1746,35 @@ index acf180d..e4fcfbd 100644 if (this._initialized === false) await this.init(); const renderContext = this._renderScene(scene, camera); -@@ -397,7 +506,7 @@ class Renderer { +@@ -350,7 +459,7 @@ class Renderer { + + if (renderBundleNeedsUpdate) { + if (renderContextData.renderObjects === undefined || object.needsUpdate === true) { +- const nodeFrame = this._nodes.nodeFrame; ++ const nodeFrame = this._nodes!.nodeFrame; + + renderContextData.renderObjects = []; + renderContextData.renderBundles = []; +@@ -379,7 +488,7 @@ class Renderer { + for (let i = 0, l = renderContextData.renderObjects.length; i < l; i++) { + const renderObject = renderContextData.renderObjects[i]; + +- this._nodes.updateBefore(renderObject); ++ this._nodes!.updateBefore(renderObject); + + // + +@@ -389,15 +498,15 @@ class Renderer { + ); + renderObject.object.normalMatrix.getNormalMatrix(renderObject.object.modelViewMatrix); + +- this._nodes.updateForRender(renderObject); +- this._bindings.updateForRender(renderObject); ++ this._nodes!.updateForRender(renderObject); ++ this._bindings!.updateForRender(renderObject); + + this.backend.draw(renderObject, this.info); + } } } @@ -1390,7 +1783,7 @@ index acf180d..e4fcfbd 100644 if (this._initialized === false) { console.warn( 'THREE.Renderer: .render() called before the backend is initialized. Try using .renderAsync() instead.', -@@ -453,7 +562,7 @@ class Renderer { +@@ -453,12 +562,12 @@ class Renderer { return frameBufferTarget; } @@ -1399,15 +1792,60 @@ index acf180d..e4fcfbd 100644 const frameBufferTarget = useFrameBufferTarget ? this._getFrameBufferTarget() : null; // preserve render tree -@@ -663,7 +772,7 @@ class Renderer { + +- const nodeFrame = this._nodes.nodeFrame; ++ const nodeFrame = this._nodes!.nodeFrame; + + const previousRenderId = nodeFrame.renderId; + const previousRenderContext = this._currentRenderContext; +@@ -572,9 +681,9 @@ class Renderer { + // + + if (renderTarget !== null) { +- this._textures.updateRenderTarget(renderTarget, activeMipmapLevel); ++ this._textures!.updateRenderTarget(renderTarget, activeMipmapLevel); + +- const renderTargetData = this._textures.get(renderTarget); ++ const renderTargetData = this._textures!.get(renderTarget); + + renderContext.textures = renderTargetData.textures; + renderContext.depthTexture = renderTargetData.depthTexture; +@@ -600,11 +709,11 @@ class Renderer { + + // + +- this._nodes.updateScene(sceneRef); ++ this._nodes!.updateScene(sceneRef); + + // + +- this._background.update(sceneRef, renderList, renderContext); ++ this._background!.update(sceneRef, renderList, renderContext); + + // + +@@ -637,7 +746,7 @@ class Renderer { + if (frameBufferTarget !== null) { + this.setRenderTarget(outputRenderTarget, activeCubeFace, activeMipmapLevel); + +- _quad.material.fragmentNode = this._nodes.getOutputNode(renderTarget.texture); ++ _quad.material.fragmentNode = this._nodes!.getOutputNode(renderTarget.texture); + + this._renderScene(_quad, _quad.camera, false); + } +@@ -663,10 +772,10 @@ class Renderer { return this._activeMipmapLevel; } - async setAnimationLoop(callback) { -+ async setAnimationLoop(callback: ((time: DOMHighResTimeStamp) => void) | null) { ++ async setAnimationLoop(callback: ((time: DOMHighResTimeStamp, frame?: XRFrame) => void) | null) { if (this._initialized === false) await this.init(); - this._animation.setAnimationLoop(callback); +- this._animation.setAnimationLoop(callback); ++ this._animation!.setAnimationLoop(callback); + } + + getArrayBuffer(attribute) { @@ -689,11 +798,11 @@ class Renderer { return this._pixelRatio; } @@ -1577,7 +2015,38 @@ index acf180d..e4fcfbd 100644 const renderContext = this._currentRenderContext; return renderContext && this.backend.isOccluded(renderContext, object); -@@ -909,7 +1022,7 @@ class Renderer { +@@ -843,9 +956,9 @@ class Renderer { + let renderTargetData = null; + + if (renderTarget !== null) { +- this._textures.updateRenderTarget(renderTarget); ++ this._textures!.updateRenderTarget(renderTarget); + +- renderTargetData = this._textures.get(renderTarget); ++ renderTargetData = this._textures!.get(renderTarget); + } + + this.backend.clear(color, depth, stencil, renderTargetData); +@@ -896,20 +1009,20 @@ class Renderer { + dispose() { + this.info.dispose(); + +- this._animation.dispose(); +- this._objects.dispose(); +- this._pipelines.dispose(); +- this._nodes.dispose(); +- this._bindings.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._textures!.dispose(); + + this.setRenderTarget(null); this.setAnimationLoop(null); } @@ -1586,7 +2055,7 @@ index acf180d..e4fcfbd 100644 this._renderTarget = renderTarget; this._activeCubeFace = activeCubeFace; this._activeMipmapLevel = activeMipmapLevel; -@@ -927,7 +1040,7 @@ class Renderer { +@@ -927,10 +1040,10 @@ class Renderer { return this._renderObjectFunction; } @@ -1594,7 +2063,11 @@ index acf180d..e4fcfbd 100644 + async computeAsync(computeNodes: ComputeNode | ComputeNode[]) { if (this._initialized === false) await this.init(); - const nodeFrame = this._nodes.nodeFrame; +- const nodeFrame = this._nodes.nodeFrame; ++ const nodeFrame = this._nodes!.nodeFrame; + + const previousRenderId = nodeFrame.renderId; + @@ -993,13 +1106,13 @@ class Renderer { nodeFrame.renderId = previousRenderId; } @@ -1611,7 +2084,7 @@ index acf180d..e4fcfbd 100644 if (this._initialized === false) { console.warn( 'THREE.Renderer: .hasFeature() called before the backend is initialized. Try using .hasFeatureAsync() instead.', -@@ -1011,7 +1124,7 @@ class Renderer { +@@ -1011,22 +1124,35 @@ class Renderer { return this.backend.hasFeature(name); } @@ -1619,12 +2092,15 @@ index acf180d..e4fcfbd 100644 + copyFramebufferToTexture(framebufferTexture: FramebufferTexture) { const renderContext = this._currentRenderContext; - this._textures.updateTexture(framebufferTexture); -@@ -1019,14 +1132,27 @@ class Renderer { +- this._textures.updateTexture(framebufferTexture); ++ 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); + copyTextureToTexture( + srcTexture: Texture, + dstTexture: Texture, @@ -1632,8 +2108,8 @@ index acf180d..e4fcfbd 100644 + dstPosition: Vector2 | null = null, + level = 0, + ) { - this._textures.updateTexture(srcTexture); - this._textures.updateTexture(dstTexture); ++ this._textures!.updateTexture(srcTexture); ++ this._textures!.updateTexture(dstTexture); this.backend.copyTextureToTexture(srcTexture, dstTexture, srcRegion, dstPosition, level); } @@ -1667,3 +2143,251 @@ index acf180d..e4fcfbd 100644 let overridePositionNode; let overrideFragmentNode; let overrideDepthNode; +@@ -1268,7 +1402,7 @@ class Renderer { + + // + +- this._nodes.updateBefore(renderObject); ++ this._nodes!.updateBefore(renderObject); + + // + +@@ -1277,10 +1411,10 @@ class Renderer { + + // + +- this._nodes.updateForRender(renderObject); +- this._geometries.updateForRender(renderObject); +- this._bindings.updateForRender(renderObject); +- this._pipelines.updateForRender(renderObject); ++ this._nodes!.updateForRender(renderObject); ++ this._geometries!.updateForRender(renderObject); ++ this._bindings!.updateForRender(renderObject); ++ this._pipelines!.updateForRender(renderObject); + + // + +@@ -1313,15 +1447,15 @@ class Renderer { + + // + +- this._nodes.updateBefore(renderObject); ++ this._nodes!.updateBefore(renderObject); + + // + +- this._nodes.updateForRender(renderObject); +- this._geometries.updateForRender(renderObject); +- this._bindings.updateForRender(renderObject); ++ this._nodes!.updateForRender(renderObject); ++ this._geometries!.updateForRender(renderObject); ++ this._bindings!.updateForRender(renderObject); + +- this._pipelines.getForRender(renderObject, this._compilationPromises); ++ this._pipelines!.getForRender(renderObject, this._compilationPromises); + } + + get compute() { +diff --git a/examples-jsm/examples/renderers/common/Textures.ts b/examples-jsm/examples/renderers/common/Textures.ts +index 0eb0509..7606890 100644 +--- a/examples-jsm/examples/renderers/common/Textures.ts ++++ b/examples-jsm/examples/renderers/common/Textures.ts +@@ -14,12 +14,21 @@ import { + CubeReflectionMapping, + CubeRefractionMapping, + UnsignedByteType, ++ RenderTarget, ++ Texture, + } from 'three'; ++import Renderer from './Renderer.js'; ++import Backend from './Backend.js'; ++import Info from './Info.js'; + + const _size = new Vector3(); + + class Textures extends DataMap { +- constructor(renderer, backend, info) { ++ renderer: Renderer; ++ backend: Backend; ++ info: Info; ++ ++ constructor(renderer: Renderer, backend: Backend, info: Info) { + super(); + + this.renderer = renderer; +@@ -27,7 +36,7 @@ class Textures extends DataMap { + this.info = info; + } + +- updateRenderTarget(renderTarget, activeMipmapLevel = 0) { ++ updateRenderTarget(renderTarget: RenderTarget, activeMipmapLevel = 0) { + const renderTargetData = this.get(renderTarget); + + const sampleCount = renderTarget.samples === 0 ? 1 : renderTarget.samples; +@@ -116,7 +125,7 @@ class Textures extends DataMap { + } + } + +- updateTexture(texture, options = {}) { ++ updateTexture(texture: Texture, options = {}) { + const textureData = this.get(texture); + if (textureData.initialized === true && textureData.version === texture.version) return; + +@@ -229,7 +238,7 @@ class Textures extends DataMap { + textureData.version = texture.version; + } + +- getSize(texture, target = _size) { ++ getSize(texture: Texture, target = _size) { + let image = texture.images ? texture.images[0] : texture.image; + + if (image) { +@@ -245,7 +254,7 @@ class Textures extends DataMap { + return target; + } + +- getMipLevels(texture, width, height) { ++ getMipLevels(texture: Texture, width: number, height: number) { + let mipLevelCount; + + if (texture.isCompressedTexture) { +@@ -257,7 +266,7 @@ class Textures extends DataMap { + return mipLevelCount; + } + +- needsMipmaps(texture) { ++ needsMipmaps(texture: Texture) { + if (this.isEnvironmentTexture(texture)) return true; + + return ( +@@ -266,7 +275,7 @@ class Textures extends DataMap { + ); + } + +- isEnvironmentTexture(texture) { ++ isEnvironmentTexture(texture: Texture) { + const mapping = texture.mapping; + + return ( +@@ -277,7 +286,7 @@ class Textures extends DataMap { + ); + } + +- _destroyTexture(texture) { ++ _destroyTexture(texture: Texture) { + this.backend.destroySampler(texture); + this.backend.destroyTexture(texture); + +diff --git a/examples-jsm/examples/renderers/common/nodes/Nodes.ts b/examples-jsm/examples/renderers/common/nodes/Nodes.ts +index 86df565..819ea47 100644 +--- a/examples-jsm/examples/renderers/common/nodes/Nodes.ts ++++ b/examples-jsm/examples/renderers/common/nodes/Nodes.ts +@@ -2,10 +2,16 @@ import DataMap from '../DataMap.js'; + import ChainMap from '../ChainMap.js'; + import NodeBuilderState from './NodeBuilderState.js'; + import { ++ Color, ++ CubeTexture, + EquirectangularReflectionMapping, + EquirectangularRefractionMapping, ++ Fog, ++ FogExp2, + NoToneMapping, ++ Scene, + SRGBColorSpace, ++ Texture, + } from 'three'; + import { + NodeFrame, +@@ -23,9 +29,18 @@ import { + pmremTexture, + viewportTopLeft, + } from '../../../nodes/Nodes.js'; ++import Renderer from '../Renderer.js'; ++import Backend from '../Backend.js'; + + class Nodes extends DataMap { +- constructor(renderer, backend) { ++ renderer: Renderer; ++ backend: Backend; ++ nodeFrame: NodeFrame; ++ nodeBuilderCache: Map; ++ callHashCache: ChainMap; ++ groupsData: ChainMap; ++ ++ constructor(renderer: Renderer, backend: Backend) { + super(); + + this.renderer = renderer; +@@ -215,7 +230,7 @@ class Nodes extends DataMap { + return cacheKeyData.cacheKey; + } + +- updateScene(scene) { ++ updateScene(scene: Scene) { + this.updateEnvironment(scene); + this.updateFog(scene); + this.updateBackground(scene); +@@ -225,7 +240,7 @@ class Nodes extends DataMap { + return this.renderer.getRenderTarget() ? false : true; + } + +- updateBackground(scene) { ++ updateBackground(scene: Scene) { + const sceneData = this.get(scene); + const background = scene.background; + +@@ -239,10 +254,10 @@ class Nodes extends DataMap { + 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); ++ } else if ((background as Texture).isTexture === true) { ++ backgroundNode = texture(background as Texture, viewportBottomLeft).setUpdateMatrix(true); ++ } else if ((background as Color).isColor !== true) { ++ console.error('WebGPUNodes: Unsupported background configuration.', background as Color); + } + + sceneData.backgroundNode = backgroundNode; +@@ -254,7 +269,7 @@ class Nodes extends DataMap { + } + } + +- updateFog(scene) { ++ updateFog(scene: Scene) { + const sceneData = this.get(scene); + const fog = scene.fog; + +@@ -262,9 +277,9 @@ class Nodes extends DataMap { + if (sceneData.fog !== fog) { + let fogNode = null; + +- if (fog.isFogExp2) { ++ if ((fog as FogExp2).isFogExp2) { + fogNode = densityFog(reference('color', 'color', fog), reference('density', 'float', fog)); +- } else if (fog.isFog) { ++ } else if ((fog as Fog).isFog) { + fogNode = rangeFog( + reference('color', 'color', fog), + reference('near', 'float', fog), +@@ -283,7 +298,7 @@ class Nodes extends DataMap { + } + } + +- updateEnvironment(scene) { ++ updateEnvironment(scene: Scene) { + const sceneData = this.get(scene); + const environment = scene.environment; + +@@ -291,7 +306,7 @@ class Nodes extends DataMap { + if (sceneData.environment !== environment) { + let environmentNode = null; + +- if (environment.isCubeTexture === true) { ++ if ((environment as CubeTexture).isCubeTexture === true) { + environmentNode = cubeTexture(environment); + } else if (environment.isTexture === true) { + environmentNode = texture(environment); From 4dd6330c83851d32051871a8a3fa731b65b8d98a Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sat, 25 May 2024 16:50:54 -0400 Subject: [PATCH 07/16] Delete examples --- 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-jsm/examples/nodes/core/constants.ts | 28 - examples-jsm/examples/nodes/fog/FogNode.ts | 38 - .../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/Bindings.ts | 153 -- .../examples/renderers/common/BufferUtils.ts | 23 - .../renderers/common/ClippingContext.ts | 128 -- .../examples/renderers/common/Color4.ts | 27 - .../examples/renderers/common/Geometries.ts | 163 -- .../examples/renderers/common/Info.ts | 76 - .../examples/renderers/common/Pipelines.ts | 270 ---- .../renderers/common/RenderObjects.ts | 100 -- .../examples/renderers/common/Renderer.ts | 1336 ----------------- .../examples/renderers/common/Textures.ts | 288 ---- .../examples/renderers/common/nodes/Nodes.ts | 385 ----- .../examples/renderers/webgl/WebGLBackend.ts | 1190 --------------- .../renderers/webgpu/WebGPUBackend.ts | 1186 --------------- .../renderers/webgpu/WebGPURenderer.ts | 43 - 34 files changed, 7894 deletions(-) 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/constants.ts delete mode 100644 examples-jsm/examples/nodes/fog/FogNode.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/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/Bindings.ts delete mode 100644 examples-jsm/examples/renderers/common/BufferUtils.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/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/RenderObjects.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/nodes/Nodes.ts delete mode 100644 examples-jsm/examples/renderers/webgl/WebGLBackend.ts delete mode 100644 examples-jsm/examples/renderers/webgpu/WebGPUBackend.ts delete mode 100644 examples-jsm/examples/renderers/webgpu/WebGPURenderer.ts 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/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/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/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/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/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/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/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/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/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/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/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; From ee1f264462702afe0ea5dd73a3683df731bcb401 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sat, 25 May 2024 17:51:13 -0400 Subject: [PATCH 08/16] Add examples --- 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-jsm/examples/nodes/core/constants.ts | 28 + examples-jsm/examples/nodes/fog/FogNode.ts | 38 + .../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/Bindings.ts | 153 ++ .../examples/renderers/common/BufferUtils.ts | 23 + .../renderers/common/ClippingContext.ts | 128 ++ .../examples/renderers/common/Color4.ts | 27 + .../examples/renderers/common/Geometries.ts | 163 ++ .../examples/renderers/common/Info.ts | 76 + .../examples/renderers/common/Pipelines.ts | 270 ++++ .../renderers/common/RenderObjects.ts | 100 ++ .../examples/renderers/common/Renderer.ts | 1336 +++++++++++++++++ .../examples/renderers/common/Textures.ts | 288 ++++ .../examples/renderers/common/nodes/Nodes.ts | 385 +++++ .../examples/renderers/webgl/WebGLBackend.ts | 1190 +++++++++++++++ .../renderers/webgpu/WebGPUBackend.ts | 1186 +++++++++++++++ .../renderers/webgpu/WebGPURenderer.ts | 43 + 34 files changed, 7894 insertions(+) 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/constants.ts create mode 100644 examples-jsm/examples/nodes/fog/FogNode.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/Bindings.ts create mode 100644 examples-jsm/examples/renderers/common/BufferUtils.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/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/RenderObjects.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/nodes/Nodes.ts create mode 100644 examples-jsm/examples/renderers/webgl/WebGLBackend.ts create mode 100644 examples-jsm/examples/renderers/webgpu/WebGPUBackend.ts create mode 100644 examples-jsm/examples/renderers/webgpu/WebGPURenderer.ts 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/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/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/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/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/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/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/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/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/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/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; From ae054fdc3d22d16ae5fe2c8bc84e49923d8c4713 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sat, 25 May 2024 17:57:56 -0400 Subject: [PATCH 09/16] Add more files --- .../examples/renderers/common/ChainMap.ts | 59 +++++ .../renderers/common/ComputePipeline.ts | 13 ++ .../examples/renderers/common/Constants.ts | 14 ++ .../examples/renderers/common/DataMap.ts | 38 ++++ .../renderers/common/ProgrammableStage.ts | 16 ++ .../renderers/common/RenderBundles.ts | 28 +++ .../renderers/common/RenderContexts.ts | 47 ++++ .../examples/renderers/common/RenderLists.ts | 28 +++ .../examples/renderers/common/RenderObject.ts | 211 ++++++++++++++++++ .../renderers/common/RenderPipeline.ts | 12 + .../common/nodes/NodeBuilderState.ts | 43 ++++ examples-jsm/index.js | 11 + 12 files changed, 520 insertions(+) create mode 100644 examples-jsm/examples/renderers/common/ChainMap.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/ProgrammableStage.ts create mode 100644 examples-jsm/examples/renderers/common/RenderBundles.ts create mode 100644 examples-jsm/examples/renderers/common/RenderContexts.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/RenderPipeline.ts create mode 100644 examples-jsm/examples/renderers/common/nodes/NodeBuilderState.ts 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/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/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/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/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/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/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/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/index.js b/examples-jsm/index.js index 3028f258c..bb19d4e86 100644 --- a/examples-jsm/index.js +++ b/examples-jsm/index.js @@ -20,6 +20,7 @@ const files = [ 'nodes/fog/FogNode', 'nodes/lighting/EnvironmentNode', 'nodes/lighting/LightsNode', + 'renderers/common/nodes/NodeBuilderState', 'renderers/common/nodes/Nodes', 'renderers/common/Animation', 'renderers/common/Attributes', @@ -27,13 +28,23 @@ const files = [ 'renderers/common/Background', 'renderers/common/Bindings', 'renderers/common/BufferUtils', + 'renderers/common/ChainMap', 'renderers/common/ClippingContext', 'renderers/common/Color4', + 'renderers/common/ComputePipeline', + 'renderers/common/Constants', + 'renderers/common/DataMap', 'renderers/common/Geometries', 'renderers/common/Info', 'renderers/common/Pipelines', + 'renderers/common/ProgrammableStage', + 'renderers/common/RenderBundles', + 'renderers/common/RenderContexts', + 'renderers/common/RenderLists', 'renderers/common/Renderer', + 'renderers/common/RenderObject', 'renderers/common/RenderObjects', + 'renderers/common/RenderPipeline', 'renderers/common/Textures', 'renderers/webgl/WebGLBackend', 'renderers/webgpu/WebGPUBackend', From b93ac61b09dbab151f841665f41d8d6cbbaf207a Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sat, 25 May 2024 18:12:52 -0400 Subject: [PATCH 10/16] Update patch --- examples-jsm/changes.patch | 217 ++- 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-jsm/examples/nodes/core/constants.ts | 28 - examples-jsm/examples/nodes/fog/FogNode.ts | 38 - .../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/Bindings.ts | 153 -- .../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 - .../renderers/common/RenderBundles.ts | 28 - .../renderers/common/RenderContexts.ts | 47 - .../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 ---- .../common/nodes/NodeBuilderState.ts | 43 - .../examples/renderers/common/nodes/Nodes.ts | 385 ----- .../examples/renderers/webgl/WebGLBackend.ts | 1190 --------------- .../renderers/webgpu/WebGPUBackend.ts | 1186 --------------- .../renderers/webgpu/WebGPURenderer.ts | 43 - 46 files changed, 156 insertions(+), 8464 deletions(-) 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/constants.ts delete mode 100644 examples-jsm/examples/nodes/fog/FogNode.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/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/Bindings.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/RenderBundles.ts delete mode 100644 examples-jsm/examples/renderers/common/RenderContexts.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/nodes/NodeBuilderState.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/webgpu/WebGPUBackend.ts delete mode 100644 examples-jsm/examples/renderers/webgpu/WebGPURenderer.ts diff --git a/examples-jsm/changes.patch b/examples-jsm/changes.patch index 54c47466b..771c44d3b 100644 --- a/examples-jsm/changes.patch +++ b/examples-jsm/changes.patch @@ -1,5 +1,5 @@ diff --git a/examples-jsm/examples/nodes/core/Node.ts b/examples-jsm/examples/nodes/core/Node.ts -index 438c44d..22392df 100644 +index 438c44dd..22392dfd 100644 --- a/examples-jsm/examples/nodes/core/Node.ts +++ b/examples-jsm/examples/nodes/core/Node.ts @@ -2,12 +2,94 @@ import { EventDispatcher } from 'three'; @@ -444,7 +444,7 @@ index 438c44d..22392df 100644 if (Class !== undefined) { diff --git a/examples-jsm/examples/nodes/core/NodeAttribute.ts b/examples-jsm/examples/nodes/core/NodeAttribute.ts -index 190fe8c..d873bb2 100644 +index 190fe8c5..d873bb24 100644 --- a/examples-jsm/examples/nodes/core/NodeAttribute.ts +++ b/examples-jsm/examples/nodes/core/NodeAttribute.ts @@ -1,5 +1,13 @@ @@ -463,7 +463,7 @@ index 190fe8c..d873bb2 100644 this.name = name; diff --git a/examples-jsm/examples/nodes/core/NodeBuilder.ts b/examples-jsm/examples/nodes/core/NodeBuilder.ts -index ebdc13f..a077bb0 100644 +index ebdc13ff..a077bb0c 100644 --- a/examples-jsm/examples/nodes/core/NodeBuilder.ts +++ b/examples-jsm/examples/nodes/core/NodeBuilder.ts @@ -8,7 +8,7 @@ import NodeCache from './NodeCache.js'; @@ -841,7 +841,7 @@ index ebdc13f..a077bb0 100644 toType = this.getVectorType(toType); diff --git a/examples-jsm/examples/nodes/core/NodeCache.ts b/examples-jsm/examples/nodes/core/NodeCache.ts -index 96a7e0c..b1a61b3 100644 +index 96a7e0c7..b1a61b35 100644 --- a/examples-jsm/examples/nodes/core/NodeCache.ts +++ b/examples-jsm/examples/nodes/core/NodeCache.ts @@ -1,16 +1,52 @@ @@ -900,7 +900,7 @@ index 96a7e0c..b1a61b3 100644 } } diff --git a/examples-jsm/examples/nodes/core/NodeFrame.ts b/examples-jsm/examples/nodes/core/NodeFrame.ts -index b8e8d37..399c788 100644 +index b8e8d37b..399c788b 100644 --- a/examples-jsm/examples/nodes/core/NodeFrame.ts +++ b/examples-jsm/examples/nodes/core/NodeFrame.ts @@ -1,6 +1,16 @@ @@ -939,7 +939,7 @@ index b8e8d37..399c788 100644 const reference = node.updateReference(this); diff --git a/examples-jsm/examples/nodes/core/NodeKeywords.ts b/examples-jsm/examples/nodes/core/NodeKeywords.ts -index 53da9bf..36bdcab 100644 +index 53da9bf5..36bdcabe 100644 --- a/examples-jsm/examples/nodes/core/NodeKeywords.ts +++ b/examples-jsm/examples/nodes/core/NodeKeywords.ts @@ -1,11 +1,18 @@ @@ -990,7 +990,7 @@ index 53da9bf..36bdcab 100644 for (const keywordNode of keywordNodes) { diff --git a/examples-jsm/examples/nodes/core/NodeUniform.ts b/examples-jsm/examples/nodes/core/NodeUniform.ts -index 2918e21..c407d0a 100644 +index 2918e219..c407d0a5 100644 --- a/examples-jsm/examples/nodes/core/NodeUniform.ts +++ b/examples-jsm/examples/nodes/core/NodeUniform.ts @@ -1,5 +1,15 @@ @@ -1011,7 +1011,7 @@ index 2918e21..c407d0a 100644 this.name = name; diff --git a/examples-jsm/examples/nodes/core/NodeUtils.ts b/examples-jsm/examples/nodes/core/NodeUtils.ts -index 16a5f32..6adbec5 100644 +index 16a5f324..6adbec50 100644 --- a/examples-jsm/examples/nodes/core/NodeUtils.ts +++ b/examples-jsm/examples/nodes/core/NodeUtils.ts @@ -1,10 +1,11 @@ @@ -1102,7 +1102,7 @@ index 16a5f32..6adbec5 100644 const typeOf = typeof value; diff --git a/examples-jsm/examples/nodes/core/NodeVar.ts b/examples-jsm/examples/nodes/core/NodeVar.ts -index e6e935b..8d04ff1 100644 +index e6e935b3..8d04ff16 100644 --- a/examples-jsm/examples/nodes/core/NodeVar.ts +++ b/examples-jsm/examples/nodes/core/NodeVar.ts @@ -1,5 +1,10 @@ @@ -1118,7 +1118,7 @@ index e6e935b..8d04ff1 100644 this.name = name; diff --git a/examples-jsm/examples/nodes/core/NodeVarying.ts b/examples-jsm/examples/nodes/core/NodeVarying.ts -index a148236..0a62b23 100644 +index a1482362..0a62b23d 100644 --- a/examples-jsm/examples/nodes/core/NodeVarying.ts +++ b/examples-jsm/examples/nodes/core/NodeVarying.ts @@ -1,7 +1,11 @@ @@ -1135,7 +1135,7 @@ index a148236..0a62b23 100644 this.needsInterpolation = false; diff --git a/examples-jsm/examples/nodes/core/constants.ts b/examples-jsm/examples/nodes/core/constants.ts -index 3b01a9a..5ff6ad5 100644 +index 3b01a9a6..5ff6ad5f 100644 --- a/examples-jsm/examples/nodes/core/constants.ts +++ b/examples-jsm/examples/nodes/core/constants.ts @@ -1,14 +1,14 @@ @@ -1172,7 +1172,7 @@ index 3b01a9a..5ff6ad5 100644 +export const shaderStages: NodeShaderStage[] = [...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 -index 9417df5..4376155 100644 +index 9417df5a..43761555 100644 --- a/examples-jsm/examples/nodes/fog/FogNode.ts +++ b/examples-jsm/examples/nodes/fog/FogNode.ts @@ -1,6 +1,7 @@ @@ -1193,7 +1193,7 @@ index 9417df5..4376155 100644 const getViewZ = builder.context.getViewZ; diff --git a/examples-jsm/examples/renderers/common/Animation.ts b/examples-jsm/examples/renderers/common/Animation.ts -index 0b00319..c190633 100644 +index 0b00319a..c190633b 100644 --- a/examples-jsm/examples/renderers/common/Animation.ts +++ b/examples-jsm/examples/renderers/common/Animation.ts @@ -1,5 +1,14 @@ @@ -1244,7 +1244,7 @@ index 0b00319..c190633 100644 } } diff --git a/examples-jsm/examples/renderers/common/Attributes.ts b/examples-jsm/examples/renderers/common/Attributes.ts -index ed9e8e9..f0f7502 100644 +index ed9e8e9e..f0f75022 100644 --- a/examples-jsm/examples/renderers/common/Attributes.ts +++ b/examples-jsm/examples/renderers/common/Attributes.ts @@ -1,9 +1,12 @@ @@ -1262,7 +1262,7 @@ index ed9e8e9..f0f7502 100644 this.backend = backend; diff --git a/examples-jsm/examples/renderers/common/Backend.ts b/examples-jsm/examples/renderers/common/Backend.ts -index e48a802..e56230a 100644 +index e48a8029..e56230ab 100644 --- a/examples-jsm/examples/renderers/common/Backend.ts +++ b/examples-jsm/examples/renderers/common/Backend.ts @@ -1,19 +1,28 @@ @@ -1299,7 +1299,7 @@ index e48a802..e56230a 100644 } diff --git a/examples-jsm/examples/renderers/common/Background.ts b/examples-jsm/examples/renderers/common/Background.ts -index b7902dd..a2b5e9e 100644 +index b7902dd4..82ee22cb 100644 --- a/examples-jsm/examples/renderers/common/Background.ts +++ b/examples-jsm/examples/renderers/common/Background.ts @@ -1,6 +1,6 @@ @@ -1319,8 +1319,9 @@ index b7902dd..a2b5e9e 100644 const _clearColor = new Color4(); - class Background extends DataMap { +-class Background extends DataMap { - constructor(renderer, nodes) { ++class Background extends DataMap { + renderer: Renderer; + nodes: Nodes; + @@ -1337,7 +1338,7 @@ index b7902dd..a2b5e9e 100644 const background = this.nodes.getBackgroundNode(scene) || scene.background; diff --git a/examples-jsm/examples/renderers/common/Bindings.ts b/examples-jsm/examples/renderers/common/Bindings.ts -index 9485ec3..6a39a1d 100644 +index 9485ec3b..6a39a1db 100644 --- a/examples-jsm/examples/renderers/common/Bindings.ts +++ b/examples-jsm/examples/renderers/common/Bindings.ts @@ -1,8 +1,28 @@ @@ -1371,7 +1372,7 @@ index 9485ec3..6a39a1d 100644 this.backend = backend; diff --git a/examples-jsm/examples/renderers/common/Color4.ts b/examples-jsm/examples/renderers/common/Color4.ts -index c681cc9..5d03ade 100644 +index c681cc90..5d03adec 100644 --- a/examples-jsm/examples/renderers/common/Color4.ts +++ b/examples-jsm/examples/renderers/common/Color4.ts @@ -1,26 +1,31 @@ @@ -1414,8 +1415,62 @@ index c681cc9..5d03ade 100644 } } +diff --git a/examples-jsm/examples/renderers/common/Constants.ts b/examples-jsm/examples/renderers/common/Constants.ts +index 0d0c35a2..53caf06e 100644 +--- a/examples-jsm/examples/renderers/common/Constants.ts ++++ b/examples-jsm/examples/renderers/common/Constants.ts +@@ -2,7 +2,7 @@ export const AttributeType = { + VERTEX: 1, + INDEX: 2, + STORAGE: 4, +-}; ++} as const; + + // size of a chunk in bytes (STD140 layout) + +diff --git a/examples-jsm/examples/renderers/common/DataMap.ts b/examples-jsm/examples/renderers/common/DataMap.ts +index 006bc295..f4eab25c 100644 +--- a/examples-jsm/examples/renderers/common/DataMap.ts ++++ b/examples-jsm/examples/renderers/common/DataMap.ts +@@ -1,20 +1,22 @@ +-class DataMap { ++class DataMap { ++ data: WeakMap; ++ + constructor() { +- this.data = new WeakMap(); ++ this.data = new WeakMap(); + } + +- get(object) { ++ get(object: K) { + let map = this.data.get(object); + + if (map === undefined) { +- map = {}; ++ map = {} as V; + this.data.set(object, map); + } + + return map; + } + +- delete(object) { ++ delete(object: K) { + let map; + + if (this.data.has(object)) { +@@ -26,7 +28,7 @@ class DataMap { + return map; + } + +- has(object) { ++ has(object: K) { + return this.data.has(object); + } + diff --git a/examples-jsm/examples/renderers/common/Geometries.ts b/examples-jsm/examples/renderers/common/Geometries.ts -index 5da9994..79bed8a 100644 +index 5da99946..79bed8a5 100644 --- a/examples-jsm/examples/renderers/common/Geometries.ts +++ b/examples-jsm/examples/renderers/common/Geometries.ts @@ -1,6 +1,8 @@ @@ -1440,7 +1495,7 @@ index 5da9994..79bed8a 100644 this.attributes = attributes; diff --git a/examples-jsm/examples/renderers/common/Info.ts b/examples-jsm/examples/renderers/common/Info.ts -index c8e7cb4..083e7c4 100644 +index c8e7cb41..083e7c42 100644 --- a/examples-jsm/examples/renderers/common/Info.ts +++ b/examples-jsm/examples/renderers/common/Info.ts @@ -1,4 +1,31 @@ @@ -1506,7 +1561,7 @@ index c8e7cb4..083e7c4 100644 } diff --git a/examples-jsm/examples/renderers/common/Pipelines.ts b/examples-jsm/examples/renderers/common/Pipelines.ts -index f6c570c..704790f 100644 +index f6c570c4..704790fe 100644 --- a/examples-jsm/examples/renderers/common/Pipelines.ts +++ b/examples-jsm/examples/renderers/common/Pipelines.ts @@ -2,9 +2,17 @@ import DataMap from './DataMap.js'; @@ -1528,8 +1583,38 @@ index f6c570c..704790f 100644 super(); this.backend = backend; +diff --git a/examples-jsm/examples/renderers/common/RenderContexts.ts b/examples-jsm/examples/renderers/common/RenderContexts.ts +index 630a2e42..1328da1e 100644 +--- a/examples-jsm/examples/renderers/common/RenderContexts.ts ++++ b/examples-jsm/examples/renderers/common/RenderContexts.ts +@@ -1,12 +1,15 @@ ++import { Camera, RenderTarget, Scene } from 'three'; + import ChainMap from './ChainMap.js'; + import RenderContext from './RenderContext.js'; + + class RenderContexts { ++ chainMaps: { [attachmentState: string]: ChainMap | undefined }; ++ + constructor() { + this.chainMaps = {}; + } + +- get(scene, camera, renderTarget = null) { ++ get(scene: Scene, camera: Camera, renderTarget: RenderTarget | null = null) { + const chainKey = [scene, camera]; + + let attachmentState; +@@ -35,7 +38,7 @@ class RenderContexts { + return renderState; + } + +- getChainMap(attachmentState) { ++ getChainMap(attachmentState: string) { + return this.chainMaps[attachmentState] || (this.chainMaps[attachmentState] = new ChainMap()); + } + diff --git a/examples-jsm/examples/renderers/common/RenderObjects.ts b/examples-jsm/examples/renderers/common/RenderObjects.ts -index 76dc482..b4d04b1 100644 +index 76dc482e..b4d04b17 100644 --- a/examples-jsm/examples/renderers/common/RenderObjects.ts +++ b/examples-jsm/examples/renderers/common/RenderObjects.ts @@ -1,8 +1,28 @@ @@ -1563,7 +1648,7 @@ index 76dc482..b4d04b1 100644 this.nodes = nodes; this.geometries = geometries; diff --git a/examples-jsm/examples/renderers/common/Renderer.ts b/examples-jsm/examples/renderers/common/Renderer.ts -index acf180d..60d8c8f 100644 +index acf180d8..48ed1bf4 100644 --- a/examples-jsm/examples/renderers/common/Renderer.ts +++ b/examples-jsm/examples/renderers/common/Renderer.ts @@ -30,10 +30,26 @@ import { @@ -1593,7 +1678,7 @@ index acf180d..60d8c8f 100644 const _scene = new Scene(); const _drawingBufferSize = new Vector2(); -@@ -43,8 +59,101 @@ const _projScreenMatrix = new Matrix4(); +@@ -43,8 +59,100 @@ const _projScreenMatrix = new Matrix4(); const _vector3 = new Vector3(); const _quad = new QuadMesh(new NodeMaterial()); @@ -1656,8 +1741,7 @@ index acf180d..60d8c8f 100644 + _textures: Textures | null; + _background: Background | null; + -+ // TODO -+ // this._currentRenderContext = null; ++ _currentRenderContext: RenderContext | null; + + _opaqueSort: ((a: unknown, b: unknown) => number) | null; + _transparentSort: ((a: unknown, b: unknown) => number) | null; @@ -1696,7 +1780,7 @@ index acf180d..60d8c8f 100644 this.isRenderer = true; // -@@ -207,12 +316,12 @@ class Renderer { +@@ -207,12 +315,12 @@ class Renderer { return this.backend.coordinateSystem; } @@ -1711,7 +1795,16 @@ index acf180d..60d8c8f 100644 const previousRenderId = nodeFrame.renderId; const previousRenderContext = this._currentRenderContext; -@@ -277,9 +386,9 @@ class Renderer { +@@ -226,7 +334,7 @@ class Renderer { + if (targetScene === null) targetScene = scene; + + const renderTarget = this._renderTarget; +- const renderContext = this._renderContexts.get(targetScene, camera, renderTarget); ++ const renderContext = this._renderContexts!.get(targetScene, camera, renderTarget); + const activeMipmapLevel = this._activeMipmapLevel; + + const compilationPromises = []; +@@ -277,9 +385,9 @@ class Renderer { // if (renderTarget !== null) { @@ -1723,7 +1816,7 @@ index acf180d..60d8c8f 100644 renderContext.textures = renderTargetData.textures; renderContext.depthTexture = renderTargetData.depthTexture; -@@ -290,11 +399,11 @@ class Renderer { +@@ -290,11 +398,11 @@ class Renderer { // @@ -1737,7 +1830,7 @@ index acf180d..60d8c8f 100644 // process render lists -@@ -320,7 +429,7 @@ class Renderer { +@@ -320,7 +428,7 @@ class Renderer { await Promise.all(compilationPromises); } @@ -1746,7 +1839,7 @@ index acf180d..60d8c8f 100644 if (this._initialized === false) await this.init(); const renderContext = this._renderScene(scene, camera); -@@ -350,7 +459,7 @@ class Renderer { +@@ -350,7 +458,7 @@ class Renderer { if (renderBundleNeedsUpdate) { if (renderContextData.renderObjects === undefined || object.needsUpdate === true) { @@ -1755,7 +1848,7 @@ index acf180d..60d8c8f 100644 renderContextData.renderObjects = []; renderContextData.renderBundles = []; -@@ -379,7 +488,7 @@ class Renderer { +@@ -379,7 +487,7 @@ class Renderer { for (let i = 0, l = renderContextData.renderObjects.length; i < l; i++) { const renderObject = renderContextData.renderObjects[i]; @@ -1764,7 +1857,7 @@ index acf180d..60d8c8f 100644 // -@@ -389,15 +498,15 @@ class Renderer { +@@ -389,15 +497,15 @@ class Renderer { ); renderObject.object.normalMatrix.getNormalMatrix(renderObject.object.modelViewMatrix); @@ -1783,7 +1876,7 @@ index acf180d..60d8c8f 100644 if (this._initialized === false) { console.warn( 'THREE.Renderer: .render() called before the backend is initialized. Try using .renderAsync() instead.', -@@ -453,12 +562,12 @@ class Renderer { +@@ -453,12 +561,12 @@ class Renderer { return frameBufferTarget; } @@ -1798,7 +1891,7 @@ index acf180d..60d8c8f 100644 const previousRenderId = nodeFrame.renderId; const previousRenderContext = this._currentRenderContext; -@@ -572,9 +681,9 @@ class Renderer { +@@ -572,9 +680,9 @@ class Renderer { // if (renderTarget !== null) { @@ -1810,7 +1903,7 @@ index acf180d..60d8c8f 100644 renderContext.textures = renderTargetData.textures; renderContext.depthTexture = renderTargetData.depthTexture; -@@ -600,11 +709,11 @@ class Renderer { +@@ -600,11 +708,11 @@ class Renderer { // @@ -1824,7 +1917,7 @@ index acf180d..60d8c8f 100644 // -@@ -637,7 +746,7 @@ class Renderer { +@@ -637,7 +745,7 @@ class Renderer { if (frameBufferTarget !== null) { this.setRenderTarget(outputRenderTarget, activeCubeFace, activeMipmapLevel); @@ -1833,7 +1926,7 @@ index acf180d..60d8c8f 100644 this._renderScene(_quad, _quad.camera, false); } -@@ -663,10 +772,10 @@ class Renderer { +@@ -663,10 +771,10 @@ class Renderer { return this._activeMipmapLevel; } @@ -1846,7 +1939,7 @@ index acf180d..60d8c8f 100644 } getArrayBuffer(attribute) { -@@ -689,11 +798,11 @@ class Renderer { +@@ -689,11 +797,11 @@ class Renderer { return this._pixelRatio; } @@ -1860,7 +1953,7 @@ index acf180d..60d8c8f 100644 return target.set(this._width, this._height); } -@@ -703,30 +812,30 @@ class Renderer { +@@ -703,30 +811,30 @@ class Renderer { this.setSize(this._width, this._height, false); } @@ -1899,7 +1992,7 @@ index acf180d..60d8c8f 100644 } this.setViewport(0, 0, width, height); -@@ -734,15 +843,15 @@ class Renderer { +@@ -734,15 +842,15 @@ class Renderer { if (this._initialized) this.backend.updateSize(); } @@ -1918,7 +2011,7 @@ index acf180d..60d8c8f 100644 const scissor = this._scissor; target.x = scissor.x; -@@ -753,13 +862,15 @@ class Renderer { +@@ -753,13 +861,15 @@ class Renderer { return target; } @@ -1938,7 +2031,7 @@ index acf180d..60d8c8f 100644 } } -@@ -767,34 +878,36 @@ class Renderer { +@@ -767,34 +877,36 @@ class Renderer { return this._scissorTest; } @@ -1983,7 +2076,7 @@ index acf180d..60d8c8f 100644 this._clearColor.set(color); this._clearColor.a = alpha; } -@@ -803,7 +916,7 @@ class Renderer { +@@ -803,7 +915,7 @@ class Renderer { return this._clearColor.a; } @@ -1992,7 +2085,7 @@ index acf180d..60d8c8f 100644 this._clearColor.a = alpha; } -@@ -811,7 +924,7 @@ class Renderer { +@@ -811,7 +923,7 @@ class Renderer { return this._clearDepth; } @@ -2001,7 +2094,7 @@ index acf180d..60d8c8f 100644 this._clearDepth = depth; } -@@ -819,11 +932,11 @@ class Renderer { +@@ -819,11 +931,11 @@ class Renderer { return this._clearStencil; } @@ -2015,7 +2108,7 @@ index acf180d..60d8c8f 100644 const renderContext = this._currentRenderContext; return renderContext && this.backend.isOccluded(renderContext, object); -@@ -843,9 +956,9 @@ class Renderer { +@@ -843,9 +955,9 @@ class Renderer { let renderTargetData = null; if (renderTarget !== null) { @@ -2027,7 +2120,7 @@ index acf180d..60d8c8f 100644 } this.backend.clear(color, depth, stencil, renderTargetData); -@@ -896,20 +1009,20 @@ class Renderer { +@@ -896,20 +1008,20 @@ class Renderer { dispose() { this.info.dispose(); @@ -2036,14 +2129,16 @@ index acf180d..60d8c8f 100644 - this._pipelines.dispose(); - this._nodes.dispose(); - this._bindings.dispose(); +- this._renderLists.dispose(); +- this._renderContexts.dispose(); +- this._textures.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._renderLists!.dispose(); ++ this._renderContexts!.dispose(); + this._textures!.dispose(); this.setRenderTarget(null); @@ -2055,7 +2150,7 @@ index acf180d..60d8c8f 100644 this._renderTarget = renderTarget; this._activeCubeFace = activeCubeFace; this._activeMipmapLevel = activeMipmapLevel; -@@ -927,10 +1040,10 @@ class Renderer { +@@ -927,10 +1039,10 @@ class Renderer { return this._renderObjectFunction; } @@ -2068,7 +2163,7 @@ index acf180d..60d8c8f 100644 const previousRenderId = nodeFrame.renderId; -@@ -993,13 +1106,13 @@ class Renderer { +@@ -993,13 +1105,13 @@ class Renderer { nodeFrame.renderId = previousRenderId; } @@ -2084,7 +2179,7 @@ index acf180d..60d8c8f 100644 if (this._initialized === false) { console.warn( 'THREE.Renderer: .hasFeature() called before the backend is initialized. Try using .hasFeatureAsync() instead.', -@@ -1011,22 +1124,35 @@ class Renderer { +@@ -1011,22 +1123,35 @@ class Renderer { return this.backend.hasFeature(name); } @@ -2126,7 +2221,7 @@ index acf180d..60d8c8f 100644 return this.backend.copyTextureToBuffer(renderTarget.textures[index], x, y, width, height); } -@@ -1167,7 +1293,15 @@ class Renderer { +@@ -1167,7 +1292,15 @@ class Renderer { } } @@ -2143,7 +2238,7 @@ index acf180d..60d8c8f 100644 let overridePositionNode; let overrideFragmentNode; let overrideDepthNode; -@@ -1268,7 +1402,7 @@ class Renderer { +@@ -1268,7 +1401,7 @@ class Renderer { // @@ -2152,7 +2247,7 @@ index acf180d..60d8c8f 100644 // -@@ -1277,10 +1411,10 @@ class Renderer { +@@ -1277,10 +1410,10 @@ class Renderer { // @@ -2167,7 +2262,7 @@ index acf180d..60d8c8f 100644 // -@@ -1313,15 +1447,15 @@ class Renderer { +@@ -1313,15 +1446,15 @@ class Renderer { // @@ -2189,7 +2284,7 @@ index acf180d..60d8c8f 100644 get compute() { diff --git a/examples-jsm/examples/renderers/common/Textures.ts b/examples-jsm/examples/renderers/common/Textures.ts -index 0eb0509..7606890 100644 +index 0eb0509c..76068908 100644 --- a/examples-jsm/examples/renderers/common/Textures.ts +++ b/examples-jsm/examples/renderers/common/Textures.ts @@ -14,12 +14,21 @@ import { @@ -2279,7 +2374,7 @@ index 0eb0509..7606890 100644 this.backend.destroyTexture(texture); diff --git a/examples-jsm/examples/renderers/common/nodes/Nodes.ts b/examples-jsm/examples/renderers/common/nodes/Nodes.ts -index 86df565..819ea47 100644 +index 86df5654..819ea472 100644 --- a/examples-jsm/examples/renderers/common/nodes/Nodes.ts +++ b/examples-jsm/examples/renderers/common/nodes/Nodes.ts @@ -2,10 +2,16 @@ import DataMap from '../DataMap.js'; 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/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/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/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/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/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/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/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/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/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/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/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; From b0ce847d471093bd1bad9181cd1b627dc3846ceb Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sat, 25 May 2024 18:13:36 -0400 Subject: [PATCH 11/16] Add examples --- 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-jsm/examples/nodes/core/constants.ts | 28 + examples-jsm/examples/nodes/fog/FogNode.ts | 38 + .../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/Bindings.ts | 153 ++ .../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/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 ++++ .../common/nodes/NodeBuilderState.ts | 43 + .../examples/renderers/common/nodes/Nodes.ts | 385 +++++ .../examples/renderers/webgl/WebGLBackend.ts | 1190 +++++++++++++++ .../renderers/webgpu/WebGPUBackend.ts | 1186 +++++++++++++++ .../renderers/webgpu/WebGPURenderer.ts | 43 + examples-jsm/index.js | 2 + 48 files changed, 8456 insertions(+) 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/constants.ts create mode 100644 examples-jsm/examples/nodes/fog/FogNode.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/Bindings.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/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/nodes/NodeBuilderState.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/webgpu/WebGPUBackend.ts create mode 100644 examples-jsm/examples/renderers/webgpu/WebGPURenderer.ts 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/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/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/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/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/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/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/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/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/index.js b/examples-jsm/index.js index bb19d4e86..1fc0bd01a 100644 --- a/examples-jsm/index.js +++ b/examples-jsm/index.js @@ -38,7 +38,9 @@ const files = [ 'renderers/common/Info', 'renderers/common/Pipelines', 'renderers/common/ProgrammableStage', + 'renderers/common/RenderBundle', 'renderers/common/RenderBundles', + 'renderers/common/RenderContext', 'renderers/common/RenderContexts', 'renderers/common/RenderLists', 'renderers/common/Renderer', From 2bb33ce961d98afdb6add6de8b777cb14cc42051 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sat, 25 May 2024 18:35:44 -0400 Subject: [PATCH 12/16] Update patch --- examples-jsm/changes.patch | 252 +++- 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-jsm/examples/nodes/core/constants.ts | 28 - examples-jsm/examples/nodes/fog/FogNode.ts | 38 - .../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/Bindings.ts | 153 -- .../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/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 ---- .../common/nodes/NodeBuilderState.ts | 43 - .../examples/renderers/common/nodes/Nodes.ts | 385 ----- .../examples/renderers/webgl/WebGLBackend.ts | 1190 --------------- .../renderers/webgpu/WebGPUBackend.ts | 1186 --------------- .../renderers/webgpu/WebGPURenderer.ts | 43 - 48 files changed, 216 insertions(+), 8490 deletions(-) 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/constants.ts delete mode 100644 examples-jsm/examples/nodes/fog/FogNode.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/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/Bindings.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/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/nodes/NodeBuilderState.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/webgpu/WebGPUBackend.ts delete mode 100644 examples-jsm/examples/renderers/webgpu/WebGPURenderer.ts diff --git a/examples-jsm/changes.patch b/examples-jsm/changes.patch index 771c44d3b..695cb2a9d 100644 --- a/examples-jsm/changes.patch +++ b/examples-jsm/changes.patch @@ -1262,13 +1262,14 @@ index ed9e8e9e..f0f75022 100644 this.backend = backend; diff --git a/examples-jsm/examples/renderers/common/Backend.ts b/examples-jsm/examples/renderers/common/Backend.ts -index e48a8029..e56230ab 100644 +index e48a8029..ebc97439 100644 --- a/examples-jsm/examples/renderers/common/Backend.ts +++ b/examples-jsm/examples/renderers/common/Backend.ts -@@ -1,19 +1,28 @@ +@@ -1,19 +1,32 @@ +import Color4 from './Color4.js'; +import { Vector2, Vector4, REVISION, createCanvasElement } from 'three'; +import Renderer from './Renderer.js'; ++import RenderContext from './RenderContext.js'; + let vector2 = null; let vector4 = null; @@ -1279,16 +1280,20 @@ index e48a8029..e56230ab 100644 +export interface BackendParameters { + canvas?: HTMLCanvasElement | undefined; +} ++ ++interface RenderContextData {} class Backend { - constructor(parameters = {}) { + parameters: BackendParameters; ++ data: WeakMap; + renderer: Renderer | null; + domElement: HTMLCanvasElement | null; + + constructor(parameters: BackendParameters = {}) { this.parameters = Object.assign({}, parameters); - this.data = new WeakMap(); +- this.data = new WeakMap(); ++ this.data = new WeakMap(); this.renderer = null; this.domElement = null; } @@ -1298,6 +1303,29 @@ index e48a8029..e56230ab 100644 this.renderer = renderer; } +@@ -144,7 +157,7 @@ class Backend { + this.data.set(object, value); + } + +- get(object) { ++ get(object: RenderContext) { + let map = this.data.get(object); + + if (map === undefined) { +@@ -155,11 +168,11 @@ class Backend { + return map; + } + +- has(object) { ++ has(object: RenderContext) { + return this.data.has(object); + } + +- delete(object) { ++ delete(object: RenderContext) { + this.data.delete(object); + } + } diff --git a/examples-jsm/examples/renderers/common/Background.ts b/examples-jsm/examples/renderers/common/Background.ts index b7902dd4..82ee22cb 100644 --- a/examples-jsm/examples/renderers/common/Background.ts @@ -1371,6 +1399,71 @@ index 9485ec3b..6a39a1db 100644 super(); this.backend = backend; +diff --git a/examples-jsm/examples/renderers/common/ChainMap.ts b/examples-jsm/examples/renderers/common/ChainMap.ts +index e233beca..b43faed4 100644 +--- a/examples-jsm/examples/renderers/common/ChainMap.ts ++++ b/examples-jsm/examples/renderers/common/ChainMap.ts +@@ -1,14 +1,18 @@ +-export default class ChainMap { ++type RecursiveWeakMap = WeakMap>; ++ ++export default class ChainMap { ++ weakMap: RecursiveWeakMap; ++ + constructor() { +- this.weakMap = new WeakMap(); ++ this.weakMap = new WeakMap>(); + } + +- get(keys) { ++ get(keys: K[]) { + if (Array.isArray(keys)) { +- let map = this.weakMap; ++ let map: RecursiveWeakMap | undefined = this.weakMap; + + for (let i = 0; i < keys.length; i++) { +- map = map.get(keys[i]); ++ map = map.get(keys[i]) as RecursiveWeakMap | undefined; + + if (map === undefined) return undefined; + } +@@ -19,16 +23,16 @@ export default class ChainMap { + } + } + +- set(keys, value) { ++ set(keys: K[], value: V) { + if (Array.isArray(keys)) { +- let map = this.weakMap; ++ let map: RecursiveWeakMap = this.weakMap; + + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + +- if (map.has(key) === false) map.set(key, new WeakMap()); ++ if (map.has(key) === false) map.set(key, new WeakMap>()); + +- map = map.get(key); ++ map = map.get(key) as RecursiveWeakMap; + } + + return map.set(keys[keys.length - 1], value); +@@ -37,12 +41,12 @@ export default class ChainMap { + } + } + +- delete(keys) { ++ delete(keys: K[]) { + if (Array.isArray(keys)) { +- let map = this.weakMap; ++ let map: RecursiveWeakMap | undefined = this.weakMap; + + for (let i = 0; i < keys.length; i++) { +- map = map.get(keys[i]); ++ map = map.get(keys[i]) as RecursiveWeakMap | undefined; + + if (map === undefined) return false; + } diff --git a/examples-jsm/examples/renderers/common/Color4.ts b/examples-jsm/examples/renderers/common/Color4.ts index c681cc90..5d03adec 100644 --- a/examples-jsm/examples/renderers/common/Color4.ts @@ -1583,6 +1676,47 @@ index f6c570c4..704790fe 100644 super(); this.backend = backend; +diff --git a/examples-jsm/examples/renderers/common/RenderContext.ts b/examples-jsm/examples/renderers/common/RenderContext.ts +index 3b43028e..3efe66b9 100644 +--- a/examples-jsm/examples/renderers/common/RenderContext.ts ++++ b/examples-jsm/examples/renderers/common/RenderContext.ts +@@ -3,6 +3,36 @@ import { Vector4 } from 'three'; + let id = 0; + + class RenderContext { ++ id: number; ++ ++ color: boolean; ++ clearColor: boolean; ++ clearColorValue: { r: number; g: number; b: number; a: number }; ++ ++ depth: boolean; ++ clearDepth: boolean; ++ clearDepthValue: number; ++ ++ stencil: boolean; ++ clearStencil: boolean; ++ clearStencilValue: number; ++ ++ viewport: boolean; ++ viewportValue: Vector4; ++ scissor: boolean; ++ scissorValue: Vector4; ++ ++ // TODO ++ // textures ++ // depthTexture ++ activeCubeFace: number; ++ sampleCount: number; ++ ++ width: number; ++ height: number; ++ ++ readonly isRenderContext: true; ++ + constructor() { + this.id = id++; + diff --git a/examples-jsm/examples/renderers/common/RenderContexts.ts b/examples-jsm/examples/renderers/common/RenderContexts.ts index 630a2e42..1328da1e 100644 --- a/examples-jsm/examples/renderers/common/RenderContexts.ts @@ -1613,6 +1747,27 @@ index 630a2e42..1328da1e 100644 return this.chainMaps[attachmentState] || (this.chainMaps[attachmentState] = new ChainMap()); } +diff --git a/examples-jsm/examples/renderers/common/RenderLists.ts b/examples-jsm/examples/renderers/common/RenderLists.ts +index 3fc3134e..e61a7fea 100644 +--- a/examples-jsm/examples/renderers/common/RenderLists.ts ++++ b/examples-jsm/examples/renderers/common/RenderLists.ts +@@ -1,12 +1,15 @@ ++import { Camera, Scene } from 'three'; + import ChainMap from './ChainMap.js'; + import RenderList from './RenderList.js'; + + class RenderLists { ++ lists: ChainMap; ++ + constructor() { + this.lists = new ChainMap(); + } + +- get(scene, camera) { ++ get(scene: Scene, camera: Camera) { + const lists = this.lists; + const keys = [scene, camera]; + diff --git a/examples-jsm/examples/renderers/common/RenderObjects.ts b/examples-jsm/examples/renderers/common/RenderObjects.ts index 76dc482e..b4d04b17 100644 --- a/examples-jsm/examples/renderers/common/RenderObjects.ts @@ -1648,10 +1803,10 @@ index 76dc482e..b4d04b17 100644 this.nodes = nodes; this.geometries = geometries; diff --git a/examples-jsm/examples/renderers/common/Renderer.ts b/examples-jsm/examples/renderers/common/Renderer.ts -index acf180d8..48ed1bf4 100644 +index acf180d8..572d78df 100644 --- a/examples-jsm/examples/renderers/common/Renderer.ts +++ b/examples-jsm/examples/renderers/common/Renderer.ts -@@ -30,10 +30,26 @@ import { +@@ -30,10 +30,27 @@ import { RenderTarget, HalfFloatType, RGBAFormat, @@ -1675,10 +1830,11 @@ index acf180d8..48ed1bf4 100644 +import Backend from './Backend.js'; +import Node from '../../nodes/core/Node.js'; +import LightsNode from '../../nodes/lighting/LightsNode.js'; ++import RenderContext from './RenderContext.js'; const _scene = new Scene(); const _drawingBufferSize = new Vector2(); -@@ -43,8 +59,100 @@ const _projScreenMatrix = new Matrix4(); +@@ -43,8 +60,100 @@ const _projScreenMatrix = new Matrix4(); const _vector3 = new Vector3(); const _quad = new QuadMesh(new NodeMaterial()); @@ -1780,7 +1936,7 @@ index acf180d8..48ed1bf4 100644 this.isRenderer = true; // -@@ -207,12 +315,12 @@ class Renderer { +@@ -207,12 +316,12 @@ class Renderer { return this.backend.coordinateSystem; } @@ -1795,7 +1951,7 @@ index acf180d8..48ed1bf4 100644 const previousRenderId = nodeFrame.renderId; const previousRenderContext = this._currentRenderContext; -@@ -226,7 +334,7 @@ class Renderer { +@@ -226,7 +335,7 @@ class Renderer { if (targetScene === null) targetScene = scene; const renderTarget = this._renderTarget; @@ -1804,7 +1960,7 @@ index acf180d8..48ed1bf4 100644 const activeMipmapLevel = this._activeMipmapLevel; const compilationPromises = []; -@@ -277,9 +385,9 @@ class Renderer { +@@ -277,9 +386,9 @@ class Renderer { // if (renderTarget !== null) { @@ -1816,7 +1972,7 @@ index acf180d8..48ed1bf4 100644 renderContext.textures = renderTargetData.textures; renderContext.depthTexture = renderTargetData.depthTexture; -@@ -290,11 +398,11 @@ class Renderer { +@@ -290,11 +399,11 @@ class Renderer { // @@ -1830,7 +1986,7 @@ index acf180d8..48ed1bf4 100644 // process render lists -@@ -320,7 +428,7 @@ class Renderer { +@@ -320,7 +429,7 @@ class Renderer { await Promise.all(compilationPromises); } @@ -1839,7 +1995,22 @@ index acf180d8..48ed1bf4 100644 if (this._initialized === false) await this.init(); const renderContext = this._renderScene(scene, camera); -@@ -350,7 +458,7 @@ class Renderer { +@@ -331,12 +440,12 @@ class Renderer { + _renderBundle(bundle, sceneRef, lightsNode) { + const { object, camera, renderList } = bundle; + +- const renderContext = this._currentRenderContext; ++ const renderContext = this._currentRenderContext!; + const renderContextData = this.backend.get(renderContext); + + // + +- const renderBundle = this._bundles.get(object, camera); ++ const renderBundle = this._bundles!.get(object, camera); + + const renderBundleData = this.backend.get(renderBundle); + if (renderBundleData.renderContexts === undefined) renderBundleData.renderContexts = new Set(); +@@ -350,7 +459,7 @@ class Renderer { if (renderBundleNeedsUpdate) { if (renderContextData.renderObjects === undefined || object.needsUpdate === true) { @@ -1848,7 +2019,7 @@ index acf180d8..48ed1bf4 100644 renderContextData.renderObjects = []; renderContextData.renderBundles = []; -@@ -379,7 +487,7 @@ class Renderer { +@@ -379,7 +488,7 @@ class Renderer { for (let i = 0, l = renderContextData.renderObjects.length; i < l; i++) { const renderObject = renderContextData.renderObjects[i]; @@ -1857,7 +2028,7 @@ index acf180d8..48ed1bf4 100644 // -@@ -389,15 +497,15 @@ class Renderer { +@@ -389,15 +498,15 @@ class Renderer { ); renderObject.object.normalMatrix.getNormalMatrix(renderObject.object.modelViewMatrix); @@ -1876,7 +2047,7 @@ index acf180d8..48ed1bf4 100644 if (this._initialized === false) { console.warn( 'THREE.Renderer: .render() called before the backend is initialized. Try using .renderAsync() instead.', -@@ -453,12 +561,12 @@ class Renderer { +@@ -453,12 +562,12 @@ class Renderer { return frameBufferTarget; } @@ -1891,7 +2062,16 @@ index acf180d8..48ed1bf4 100644 const previousRenderId = nodeFrame.renderId; const previousRenderContext = this._currentRenderContext; -@@ -572,9 +680,9 @@ class Renderer { +@@ -558,7 +667,7 @@ class Renderer { + _projScreenMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse); + _frustum.setFromProjectionMatrix(_projScreenMatrix, coordinateSystem); + +- const renderList = this._renderLists.get(scene, camera); ++ const renderList = this._renderLists!.get(scene, camera); + renderList.begin(); + + this._projectObject(scene, camera, 0, renderList); +@@ -572,9 +681,9 @@ class Renderer { // if (renderTarget !== null) { @@ -1903,7 +2083,7 @@ index acf180d8..48ed1bf4 100644 renderContext.textures = renderTargetData.textures; renderContext.depthTexture = renderTargetData.depthTexture; -@@ -600,11 +708,11 @@ class Renderer { +@@ -600,11 +709,11 @@ class Renderer { // @@ -1917,7 +2097,7 @@ index acf180d8..48ed1bf4 100644 // -@@ -637,7 +745,7 @@ class Renderer { +@@ -637,7 +746,7 @@ class Renderer { if (frameBufferTarget !== null) { this.setRenderTarget(outputRenderTarget, activeCubeFace, activeMipmapLevel); @@ -1926,7 +2106,7 @@ index acf180d8..48ed1bf4 100644 this._renderScene(_quad, _quad.camera, false); } -@@ -663,10 +771,10 @@ class Renderer { +@@ -663,10 +772,10 @@ class Renderer { return this._activeMipmapLevel; } @@ -1939,7 +2119,7 @@ index acf180d8..48ed1bf4 100644 } getArrayBuffer(attribute) { -@@ -689,11 +797,11 @@ class Renderer { +@@ -689,11 +798,11 @@ class Renderer { return this._pixelRatio; } @@ -1953,7 +2133,7 @@ index acf180d8..48ed1bf4 100644 return target.set(this._width, this._height); } -@@ -703,30 +811,30 @@ class Renderer { +@@ -703,30 +812,30 @@ class Renderer { this.setSize(this._width, this._height, false); } @@ -1992,7 +2172,7 @@ index acf180d8..48ed1bf4 100644 } this.setViewport(0, 0, width, height); -@@ -734,15 +842,15 @@ class Renderer { +@@ -734,15 +843,15 @@ class Renderer { if (this._initialized) this.backend.updateSize(); } @@ -2011,7 +2191,7 @@ index acf180d8..48ed1bf4 100644 const scissor = this._scissor; target.x = scissor.x; -@@ -753,13 +861,15 @@ class Renderer { +@@ -753,13 +862,15 @@ class Renderer { return target; } @@ -2031,7 +2211,7 @@ index acf180d8..48ed1bf4 100644 } } -@@ -767,34 +877,36 @@ class Renderer { +@@ -767,34 +878,36 @@ class Renderer { return this._scissorTest; } @@ -2076,7 +2256,7 @@ index acf180d8..48ed1bf4 100644 this._clearColor.set(color); this._clearColor.a = alpha; } -@@ -803,7 +915,7 @@ class Renderer { +@@ -803,7 +916,7 @@ class Renderer { return this._clearColor.a; } @@ -2085,7 +2265,7 @@ index acf180d8..48ed1bf4 100644 this._clearColor.a = alpha; } -@@ -811,7 +923,7 @@ class Renderer { +@@ -811,7 +924,7 @@ class Renderer { return this._clearDepth; } @@ -2094,7 +2274,7 @@ index acf180d8..48ed1bf4 100644 this._clearDepth = depth; } -@@ -819,11 +931,11 @@ class Renderer { +@@ -819,11 +932,11 @@ class Renderer { return this._clearStencil; } @@ -2108,7 +2288,7 @@ index acf180d8..48ed1bf4 100644 const renderContext = this._currentRenderContext; return renderContext && this.backend.isOccluded(renderContext, object); -@@ -843,9 +955,9 @@ class Renderer { +@@ -843,9 +956,9 @@ class Renderer { let renderTargetData = null; if (renderTarget !== null) { @@ -2120,7 +2300,7 @@ index acf180d8..48ed1bf4 100644 } this.backend.clear(color, depth, stencil, renderTargetData); -@@ -896,20 +1008,20 @@ class Renderer { +@@ -896,20 +1009,20 @@ class Renderer { dispose() { this.info.dispose(); @@ -2150,7 +2330,7 @@ index acf180d8..48ed1bf4 100644 this._renderTarget = renderTarget; this._activeCubeFace = activeCubeFace; this._activeMipmapLevel = activeMipmapLevel; -@@ -927,10 +1039,10 @@ class Renderer { +@@ -927,10 +1040,10 @@ class Renderer { return this._renderObjectFunction; } @@ -2163,7 +2343,7 @@ index acf180d8..48ed1bf4 100644 const previousRenderId = nodeFrame.renderId; -@@ -993,13 +1105,13 @@ class Renderer { +@@ -993,13 +1106,13 @@ class Renderer { nodeFrame.renderId = previousRenderId; } @@ -2179,7 +2359,7 @@ index acf180d8..48ed1bf4 100644 if (this._initialized === false) { console.warn( 'THREE.Renderer: .hasFeature() called before the backend is initialized. Try using .hasFeatureAsync() instead.', -@@ -1011,22 +1123,35 @@ class Renderer { +@@ -1011,22 +1124,35 @@ class Renderer { return this.backend.hasFeature(name); } @@ -2221,7 +2401,7 @@ index acf180d8..48ed1bf4 100644 return this.backend.copyTextureToBuffer(renderTarget.textures[index], x, y, width, height); } -@@ -1167,7 +1292,15 @@ class Renderer { +@@ -1167,7 +1293,15 @@ class Renderer { } } @@ -2238,7 +2418,7 @@ index acf180d8..48ed1bf4 100644 let overridePositionNode; let overrideFragmentNode; let overrideDepthNode; -@@ -1268,7 +1401,7 @@ class Renderer { +@@ -1268,7 +1402,7 @@ class Renderer { // @@ -2247,7 +2427,7 @@ index acf180d8..48ed1bf4 100644 // -@@ -1277,10 +1410,10 @@ class Renderer { +@@ -1277,10 +1411,10 @@ class Renderer { // @@ -2262,7 +2442,7 @@ index acf180d8..48ed1bf4 100644 // -@@ -1313,15 +1446,15 @@ class Renderer { +@@ -1313,15 +1447,15 @@ class Renderer { // 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/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/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/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/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/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/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/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/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/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; From 5896b4293a7760988a97e3d62e319e5f1a2a8da2 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sat, 25 May 2024 18:36:21 -0400 Subject: [PATCH 13/16] Add examples --- 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-jsm/examples/nodes/core/constants.ts | 28 + examples-jsm/examples/nodes/fog/FogNode.ts | 38 + .../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/Bindings.ts | 153 ++ .../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 ++++ .../common/nodes/NodeBuilderState.ts | 43 + .../examples/renderers/common/nodes/Nodes.ts | 385 +++++ .../examples/renderers/webgl/WebGLBackend.ts | 1190 +++++++++++++++ .../renderers/webgpu/WebGPUBackend.ts | 1186 +++++++++++++++ .../renderers/webgpu/WebGPURenderer.ts | 43 + examples-jsm/index.js | 1 + 49 files changed, 8600 insertions(+) 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/constants.ts create mode 100644 examples-jsm/examples/nodes/fog/FogNode.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/Bindings.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/nodes/NodeBuilderState.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/webgpu/WebGPUBackend.ts create mode 100644 examples-jsm/examples/renderers/webgpu/WebGPURenderer.ts 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/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/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/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/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/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/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/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/index.js b/examples-jsm/index.js index 1fc0bd01a..cfc5da7eb 100644 --- a/examples-jsm/index.js +++ b/examples-jsm/index.js @@ -42,6 +42,7 @@ const files = [ 'renderers/common/RenderBundles', 'renderers/common/RenderContext', 'renderers/common/RenderContexts', + 'renderers/common/RenderList', 'renderers/common/RenderLists', 'renderers/common/Renderer', 'renderers/common/RenderObject', From 43b02449787c5b035515ae743dab0f9383d31c55 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sat, 25 May 2024 18:42:55 -0400 Subject: [PATCH 14/16] Update patch --- examples-jsm/changes.patch | 121 +- 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-jsm/examples/nodes/core/constants.ts | 28 - examples-jsm/examples/nodes/fog/FogNode.ts | 38 - .../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/Bindings.ts | 153 -- .../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 ---- .../common/nodes/NodeBuilderState.ts | 43 - .../examples/renderers/common/nodes/Nodes.ts | 385 ----- .../examples/renderers/webgl/WebGLBackend.ts | 1190 --------------- .../renderers/webgpu/WebGPUBackend.ts | 1186 --------------- .../renderers/webgpu/WebGPURenderer.ts | 43 - 49 files changed, 80 insertions(+), 8640 deletions(-) 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/constants.ts delete mode 100644 examples-jsm/examples/nodes/fog/FogNode.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/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/Bindings.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/nodes/NodeBuilderState.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/webgpu/WebGPUBackend.ts delete mode 100644 examples-jsm/examples/renderers/webgpu/WebGPURenderer.ts diff --git a/examples-jsm/changes.patch b/examples-jsm/changes.patch index 695cb2a9d..626cf79f8 100644 --- a/examples-jsm/changes.patch +++ b/examples-jsm/changes.patch @@ -1803,10 +1803,10 @@ index 76dc482e..b4d04b17 100644 this.nodes = nodes; this.geometries = geometries; diff --git a/examples-jsm/examples/renderers/common/Renderer.ts b/examples-jsm/examples/renderers/common/Renderer.ts -index acf180d8..572d78df 100644 +index acf180d8..50fde9da 100644 --- a/examples-jsm/examples/renderers/common/Renderer.ts +++ b/examples-jsm/examples/renderers/common/Renderer.ts -@@ -30,10 +30,27 @@ import { +@@ -30,10 +30,28 @@ import { RenderTarget, HalfFloatType, RGBAFormat, @@ -1831,10 +1831,11 @@ index acf180d8..572d78df 100644 +import Node from '../../nodes/core/Node.js'; +import LightsNode from '../../nodes/lighting/LightsNode.js'; +import RenderContext from './RenderContext.js'; ++import RenderBundle from './RenderBundle.js'; const _scene = new Scene(); const _drawingBufferSize = new Vector2(); -@@ -43,8 +60,100 @@ const _projScreenMatrix = new Matrix4(); +@@ -43,8 +61,118 @@ const _projScreenMatrix = new Matrix4(); const _vector3 = new Vector3(); const _quad = new QuadMesh(new NodeMaterial()); @@ -1902,8 +1903,7 @@ index acf180d8..572d78df 100644 + _opaqueSort: ((a: unknown, b: unknown) => number) | null; + _transparentSort: ((a: unknown, b: unknown) => number) | null; + -+ // TODO -+ // this._frameBufferTarget = null; ++ _frameBufferTarget: RenderTarget | null; + + _clearColor: Color4; + _clearDepth: number; @@ -1913,10 +1913,29 @@ index acf180d8..572d78df 100644 + _activeCubeFace: number; + _activeMipmapLevel: number; + -+ // TODO -+ // this._renderObjectFunction = null; -+ // this._currentRenderObjectFunction = null; -+ // this._currentRenderBundle = null; ++ _renderObjectFunction: ++ | (( ++ object: Object3D, ++ scene: Scene, ++ camera: Camera, ++ geometry: BufferGeometry, ++ material: Material, ++ group: Group, ++ lightsNode: LightsNode, ++ ) => void) ++ | null; ++ _currentRenderObjectFunction: ++ | (( ++ object: Object3D, ++ scene: Scene, ++ camera: Camera, ++ geometry: BufferGeometry, ++ material: Material, ++ group: Group, ++ lightsNode: LightsNode, ++ ) => void) ++ | null; ++ _currentRenderBundle: RenderBundle | null; + + // TODO + // this._handleObjectFunction = this._renderObjectDirect; @@ -1936,7 +1955,7 @@ index acf180d8..572d78df 100644 this.isRenderer = true; // -@@ -207,12 +316,12 @@ class Renderer { +@@ -207,12 +335,12 @@ class Renderer { return this.backend.coordinateSystem; } @@ -1951,7 +1970,7 @@ index acf180d8..572d78df 100644 const previousRenderId = nodeFrame.renderId; const previousRenderContext = this._currentRenderContext; -@@ -226,7 +335,7 @@ class Renderer { +@@ -226,7 +354,7 @@ class Renderer { if (targetScene === null) targetScene = scene; const renderTarget = this._renderTarget; @@ -1960,7 +1979,7 @@ index acf180d8..572d78df 100644 const activeMipmapLevel = this._activeMipmapLevel; const compilationPromises = []; -@@ -277,9 +386,9 @@ class Renderer { +@@ -277,9 +405,9 @@ class Renderer { // if (renderTarget !== null) { @@ -1972,7 +1991,7 @@ index acf180d8..572d78df 100644 renderContext.textures = renderTargetData.textures; renderContext.depthTexture = renderTargetData.depthTexture; -@@ -290,11 +399,11 @@ class Renderer { +@@ -290,11 +418,11 @@ class Renderer { // @@ -1986,7 +2005,7 @@ index acf180d8..572d78df 100644 // process render lists -@@ -320,7 +429,7 @@ class Renderer { +@@ -320,7 +448,7 @@ class Renderer { await Promise.all(compilationPromises); } @@ -1995,7 +2014,7 @@ index acf180d8..572d78df 100644 if (this._initialized === false) await this.init(); const renderContext = this._renderScene(scene, camera); -@@ -331,12 +440,12 @@ class Renderer { +@@ -331,12 +459,12 @@ class Renderer { _renderBundle(bundle, sceneRef, lightsNode) { const { object, camera, renderList } = bundle; @@ -2010,7 +2029,7 @@ index acf180d8..572d78df 100644 const renderBundleData = this.backend.get(renderBundle); if (renderBundleData.renderContexts === undefined) renderBundleData.renderContexts = new Set(); -@@ -350,7 +459,7 @@ class Renderer { +@@ -350,7 +478,7 @@ class Renderer { if (renderBundleNeedsUpdate) { if (renderContextData.renderObjects === undefined || object.needsUpdate === true) { @@ -2019,7 +2038,7 @@ index acf180d8..572d78df 100644 renderContextData.renderObjects = []; renderContextData.renderBundles = []; -@@ -379,7 +488,7 @@ class Renderer { +@@ -379,7 +507,7 @@ class Renderer { for (let i = 0, l = renderContextData.renderObjects.length; i < l; i++) { const renderObject = renderContextData.renderObjects[i]; @@ -2028,7 +2047,7 @@ index acf180d8..572d78df 100644 // -@@ -389,15 +498,15 @@ class Renderer { +@@ -389,15 +517,15 @@ class Renderer { ); renderObject.object.normalMatrix.getNormalMatrix(renderObject.object.modelViewMatrix); @@ -2047,7 +2066,7 @@ index acf180d8..572d78df 100644 if (this._initialized === false) { console.warn( 'THREE.Renderer: .render() called before the backend is initialized. Try using .renderAsync() instead.', -@@ -453,12 +562,12 @@ class Renderer { +@@ -453,12 +581,12 @@ class Renderer { return frameBufferTarget; } @@ -2062,7 +2081,16 @@ index acf180d8..572d78df 100644 const previousRenderId = nodeFrame.renderId; const previousRenderContext = this._currentRenderContext; -@@ -558,7 +667,7 @@ class Renderer { +@@ -487,7 +615,7 @@ class Renderer { + + // + +- const renderContext = this._renderContexts.get(scene, camera, renderTarget); ++ const renderContext = this._renderContexts!.get(scene, camera, renderTarget); + + this._currentRenderContext = renderContext; + this._currentRenderObjectFunction = this._renderObjectFunction || this.renderObject; +@@ -558,7 +686,7 @@ class Renderer { _projScreenMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse); _frustum.setFromProjectionMatrix(_projScreenMatrix, coordinateSystem); @@ -2071,7 +2099,7 @@ index acf180d8..572d78df 100644 renderList.begin(); this._projectObject(scene, camera, 0, renderList); -@@ -572,9 +681,9 @@ class Renderer { +@@ -572,9 +700,9 @@ class Renderer { // if (renderTarget !== null) { @@ -2083,7 +2111,18 @@ index acf180d8..572d78df 100644 renderContext.textures = renderTargetData.textures; renderContext.depthTexture = renderTargetData.depthTexture; -@@ -600,11 +709,11 @@ class Renderer { +@@ -586,8 +714,8 @@ class Renderer { + } else { + renderContext.textures = null; + renderContext.depthTexture = null; +- renderContext.width = this.domElement.width; +- renderContext.height = this.domElement.height; ++ renderContext.width = this.domElement!.width; ++ renderContext.height = this.domElement!.height; + renderContext.depth = this.depth; + renderContext.stencil = this.stencil; + } +@@ -600,11 +728,11 @@ class Renderer { // @@ -2097,7 +2136,7 @@ index acf180d8..572d78df 100644 // -@@ -637,7 +746,7 @@ class Renderer { +@@ -637,7 +765,7 @@ class Renderer { if (frameBufferTarget !== null) { this.setRenderTarget(outputRenderTarget, activeCubeFace, activeMipmapLevel); @@ -2106,7 +2145,7 @@ index acf180d8..572d78df 100644 this._renderScene(_quad, _quad.camera, false); } -@@ -663,10 +772,10 @@ class Renderer { +@@ -663,10 +791,10 @@ class Renderer { return this._activeMipmapLevel; } @@ -2119,7 +2158,7 @@ index acf180d8..572d78df 100644 } getArrayBuffer(attribute) { -@@ -689,11 +798,11 @@ class Renderer { +@@ -689,11 +817,11 @@ class Renderer { return this._pixelRatio; } @@ -2133,7 +2172,7 @@ index acf180d8..572d78df 100644 return target.set(this._width, this._height); } -@@ -703,30 +812,30 @@ class Renderer { +@@ -703,30 +831,30 @@ class Renderer { this.setSize(this._width, this._height, false); } @@ -2172,7 +2211,7 @@ index acf180d8..572d78df 100644 } this.setViewport(0, 0, width, height); -@@ -734,15 +843,15 @@ class Renderer { +@@ -734,15 +862,15 @@ class Renderer { if (this._initialized) this.backend.updateSize(); } @@ -2191,7 +2230,7 @@ index acf180d8..572d78df 100644 const scissor = this._scissor; target.x = scissor.x; -@@ -753,13 +862,15 @@ class Renderer { +@@ -753,13 +881,15 @@ class Renderer { return target; } @@ -2211,7 +2250,7 @@ index acf180d8..572d78df 100644 } } -@@ -767,34 +878,36 @@ class Renderer { +@@ -767,34 +897,36 @@ class Renderer { return this._scissorTest; } @@ -2256,7 +2295,7 @@ index acf180d8..572d78df 100644 this._clearColor.set(color); this._clearColor.a = alpha; } -@@ -803,7 +916,7 @@ class Renderer { +@@ -803,7 +935,7 @@ class Renderer { return this._clearColor.a; } @@ -2265,7 +2304,7 @@ index acf180d8..572d78df 100644 this._clearColor.a = alpha; } -@@ -811,7 +924,7 @@ class Renderer { +@@ -811,7 +943,7 @@ class Renderer { return this._clearDepth; } @@ -2274,7 +2313,7 @@ index acf180d8..572d78df 100644 this._clearDepth = depth; } -@@ -819,11 +932,11 @@ class Renderer { +@@ -819,11 +951,11 @@ class Renderer { return this._clearStencil; } @@ -2288,7 +2327,7 @@ index acf180d8..572d78df 100644 const renderContext = this._currentRenderContext; return renderContext && this.backend.isOccluded(renderContext, object); -@@ -843,9 +956,9 @@ class Renderer { +@@ -843,9 +975,9 @@ class Renderer { let renderTargetData = null; if (renderTarget !== null) { @@ -2300,7 +2339,7 @@ index acf180d8..572d78df 100644 } this.backend.clear(color, depth, stencil, renderTargetData); -@@ -896,20 +1009,20 @@ class Renderer { +@@ -896,20 +1028,20 @@ class Renderer { dispose() { this.info.dispose(); @@ -2330,7 +2369,7 @@ index acf180d8..572d78df 100644 this._renderTarget = renderTarget; this._activeCubeFace = activeCubeFace; this._activeMipmapLevel = activeMipmapLevel; -@@ -927,10 +1040,10 @@ class Renderer { +@@ -927,10 +1059,10 @@ class Renderer { return this._renderObjectFunction; } @@ -2343,7 +2382,7 @@ index acf180d8..572d78df 100644 const previousRenderId = nodeFrame.renderId; -@@ -993,13 +1106,13 @@ class Renderer { +@@ -993,13 +1125,13 @@ class Renderer { nodeFrame.renderId = previousRenderId; } @@ -2359,7 +2398,7 @@ index acf180d8..572d78df 100644 if (this._initialized === false) { console.warn( 'THREE.Renderer: .hasFeature() called before the backend is initialized. Try using .hasFeatureAsync() instead.', -@@ -1011,22 +1124,35 @@ class Renderer { +@@ -1011,22 +1143,35 @@ class Renderer { return this.backend.hasFeature(name); } @@ -2401,7 +2440,7 @@ index acf180d8..572d78df 100644 return this.backend.copyTextureToBuffer(renderTarget.textures[index], x, y, width, height); } -@@ -1167,7 +1293,15 @@ class Renderer { +@@ -1167,7 +1312,15 @@ class Renderer { } } @@ -2418,7 +2457,7 @@ index acf180d8..572d78df 100644 let overridePositionNode; let overrideFragmentNode; let overrideDepthNode; -@@ -1268,7 +1402,7 @@ class Renderer { +@@ -1268,7 +1421,7 @@ class Renderer { // @@ -2427,7 +2466,7 @@ index acf180d8..572d78df 100644 // -@@ -1277,10 +1411,10 @@ class Renderer { +@@ -1277,10 +1430,10 @@ class Renderer { // @@ -2442,7 +2481,7 @@ index acf180d8..572d78df 100644 // -@@ -1313,15 +1447,15 @@ class Renderer { +@@ -1313,15 +1466,15 @@ class Renderer { // 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/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/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/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/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/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/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/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/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; From 7b0e3614c90a45d11cf4833b5aa0867bb205b4f6 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sat, 25 May 2024 23:13:04 -0400 Subject: [PATCH 15/16] Add examples --- 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-jsm/examples/nodes/core/constants.ts | 28 + examples-jsm/examples/nodes/fog/FogNode.ts | 38 + .../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/Bindings.ts | 153 ++ .../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 ++++ .../common/nodes/NodeBuilderState.ts | 43 + .../examples/renderers/common/nodes/Nodes.ts | 385 +++++ .../examples/renderers/webgl/WebGLBackend.ts | 1190 +++++++++++++++ .../renderers/webgpu/WebGPUBackend.ts | 1186 +++++++++++++++ .../renderers/webgpu/WebGPURenderer.ts | 43 + 48 files changed, 8599 insertions(+) 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/constants.ts create mode 100644 examples-jsm/examples/nodes/fog/FogNode.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/Bindings.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/nodes/NodeBuilderState.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/webgpu/WebGPUBackend.ts create mode 100644 examples-jsm/examples/renderers/webgpu/WebGPURenderer.ts 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/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/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/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/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/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/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/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; From 5ef02db9439b0f68d1d26f272553f5024f486961 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sun, 26 May 2024 00:05:33 -0400 Subject: [PATCH 16/16] Update patch --- examples-jsm/changes.patch | 708 +++++++-- 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-jsm/examples/nodes/core/constants.ts | 28 - examples-jsm/examples/nodes/fog/FogNode.ts | 38 - .../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/Bindings.ts | 153 -- .../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 ---- .../common/nodes/NodeBuilderState.ts | 43 - .../examples/renderers/common/nodes/Nodes.ts | 385 ----- .../examples/renderers/webgl/WebGLBackend.ts | 1190 --------------- .../renderers/webgpu/WebGPUBackend.ts | 1186 --------------- .../renderers/webgpu/WebGPURenderer.ts | 43 - 49 files changed, 621 insertions(+), 8686 deletions(-) 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/constants.ts delete mode 100644 examples-jsm/examples/nodes/fog/FogNode.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/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/Bindings.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/nodes/NodeBuilderState.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/webgpu/WebGPUBackend.ts delete mode 100644 examples-jsm/examples/renderers/webgpu/WebGPURenderer.ts diff --git a/examples-jsm/changes.patch b/examples-jsm/changes.patch index 626cf79f8..03abf7cb9 100644 --- a/examples-jsm/changes.patch +++ b/examples-jsm/changes.patch @@ -1262,14 +1262,15 @@ index ed9e8e9e..f0f75022 100644 this.backend = backend; diff --git a/examples-jsm/examples/renderers/common/Backend.ts b/examples-jsm/examples/renderers/common/Backend.ts -index e48a8029..ebc97439 100644 +index e48a8029..4916a325 100644 --- a/examples-jsm/examples/renderers/common/Backend.ts +++ b/examples-jsm/examples/renderers/common/Backend.ts -@@ -1,19 +1,32 @@ +@@ -1,19 +1,35 @@ +import Color4 from './Color4.js'; +import { Vector2, Vector4, REVISION, createCanvasElement } from 'three'; +import Renderer from './Renderer.js'; +import RenderContext from './RenderContext.js'; ++import RenderBundle from './RenderBundle.js'; + let vector2 = null; let vector4 = null; @@ -1282,11 +1283,13 @@ index e48a8029..ebc97439 100644 +} + +interface RenderContextData {} ++ ++interface RenderBundleData {} class Backend { - constructor(parameters = {}) { + parameters: BackendParameters; -+ data: WeakMap; ++ data: WeakMap; + renderer: Renderer | null; + domElement: HTMLCanvasElement | null; + @@ -1303,16 +1306,16 @@ index e48a8029..ebc97439 100644 this.renderer = renderer; } -@@ -144,7 +157,7 @@ class Backend { +@@ -144,7 +160,7 @@ class Backend { this.data.set(object, value); } - get(object) { -+ get(object: RenderContext) { ++ get(object: RenderContext | RenderBundle) { let map = this.data.get(object); if (map === undefined) { -@@ -155,11 +168,11 @@ class Backend { +@@ -155,11 +171,11 @@ class Backend { return map; } @@ -1327,7 +1330,7 @@ index e48a8029..ebc97439 100644 } } diff --git a/examples-jsm/examples/renderers/common/Background.ts b/examples-jsm/examples/renderers/common/Background.ts -index b7902dd4..82ee22cb 100644 +index b7902dd4..025bdf5c 100644 --- a/examples-jsm/examples/renderers/common/Background.ts +++ b/examples-jsm/examples/renderers/common/Background.ts @@ -1,6 +1,6 @@ @@ -1338,12 +1341,14 @@ index b7902dd4..82ee22cb 100644 import { vec4, context, -@@ -10,18 +10,23 @@ import { +@@ -10,18 +10,25 @@ import { NodeMaterial, modelViewProjection, } from '../../nodes/Nodes.js'; +import Renderer from './Renderer.js'; +import Nodes from './nodes/Nodes.js'; ++import RenderList from './RenderList.js'; ++import RenderContext from './RenderContext.js'; const _clearColor = new Color4(); @@ -1361,7 +1366,7 @@ index b7902dd4..82ee22cb 100644 } - update(scene, renderList, renderContext) { -+ update(scene: Scene, renderList, renderContext) { ++ update(scene: Scene, renderList: RenderList, renderContext: RenderContext) { const renderer = this.renderer; const background = this.nodes.getBackgroundNode(scene) || scene.background; @@ -1400,10 +1405,10 @@ index 9485ec3b..6a39a1db 100644 this.backend = backend; diff --git a/examples-jsm/examples/renderers/common/ChainMap.ts b/examples-jsm/examples/renderers/common/ChainMap.ts -index e233beca..b43faed4 100644 +index e233beca..3ef8e095 100644 --- a/examples-jsm/examples/renderers/common/ChainMap.ts +++ b/examples-jsm/examples/renderers/common/ChainMap.ts -@@ -1,14 +1,18 @@ +@@ -1,34 +1,38 @@ -export default class ChainMap { +type RecursiveWeakMap = WeakMap>; + @@ -1416,7 +1421,7 @@ index e233beca..b43faed4 100644 } - get(keys) { -+ get(keys: K[]) { ++ get(keys: K[]): V | undefined { if (Array.isArray(keys)) { - let map = this.weakMap; + let map: RecursiveWeakMap | undefined = this.weakMap; @@ -1427,7 +1432,11 @@ index e233beca..b43faed4 100644 if (map === undefined) return undefined; } -@@ -19,16 +23,16 @@ export default class ChainMap { + +- return map.get(keys[keys.length - 1]); ++ return map.get(keys[keys.length - 1]) as V | undefined; + } else { + return super.get(keys); } } @@ -1464,6 +1473,31 @@ index e233beca..b43faed4 100644 if (map === undefined) return false; } +diff --git a/examples-jsm/examples/renderers/common/ClippingContext.ts b/examples-jsm/examples/renderers/common/ClippingContext.ts +index 312e0b77..807083f6 100644 +--- a/examples-jsm/examples/renderers/common/ClippingContext.ts ++++ b/examples-jsm/examples/renderers/common/ClippingContext.ts +@@ -5,6 +5,20 @@ const _plane = new Plane(); + let _clippingContextVersion = 0; + + class ClippingContext { ++ version: number; ++ ++ globalClippingCount: number; ++ ++ localClippingCount: number; ++ localClippingEnabled: boolean; ++ localClipIntersection: boolean; ++ ++ // TODO ++ // planes ++ ++ parentVersion: number; ++ viewNormalMatrix: Matrix3; ++ + constructor() { + this.version = ++_clippingContextVersion; + diff --git a/examples-jsm/examples/renderers/common/Color4.ts b/examples-jsm/examples/renderers/common/Color4.ts index c681cc90..5d03adec 100644 --- a/examples-jsm/examples/renderers/common/Color4.ts @@ -1676,11 +1710,82 @@ index f6c570c4..704790fe 100644 super(); this.backend = backend; +diff --git a/examples-jsm/examples/renderers/common/ProgrammableStage.ts b/examples-jsm/examples/renderers/common/ProgrammableStage.ts +index a684e444..6b643625 100644 +--- a/examples-jsm/examples/renderers/common/ProgrammableStage.ts ++++ b/examples-jsm/examples/renderers/common/ProgrammableStage.ts +@@ -1,6 +1,16 @@ + let _id = 0; + + class ProgrammableStage { ++ id: number; ++ ++ // TODO ++ // code ++ // stage ++ // transforms ++ // attributes ++ ++ usedTimes: number; ++ + constructor(code, type, transforms = null, attributes = null) { + this.id = _id++; + +diff --git a/examples-jsm/examples/renderers/common/RenderBundle.ts b/examples-jsm/examples/renderers/common/RenderBundle.ts +index e59e4937..c7ba8cd4 100644 +--- a/examples-jsm/examples/renderers/common/RenderBundle.ts ++++ b/examples-jsm/examples/renderers/common/RenderBundle.ts +@@ -1,5 +1,10 @@ ++import { Camera, Object3D } from 'three'; ++ + class RenderBundle { +- constructor(scene, camera) { ++ scene: Object3D; ++ camera: Camera; ++ ++ constructor(scene: Object3D, camera: Camera) { + this.scene = scene; + this.camera = camera; + } +diff --git a/examples-jsm/examples/renderers/common/RenderBundles.ts b/examples-jsm/examples/renderers/common/RenderBundles.ts +index 29140365..fbd2c210 100644 +--- a/examples-jsm/examples/renderers/common/RenderBundles.ts ++++ b/examples-jsm/examples/renderers/common/RenderBundles.ts +@@ -1,12 +1,15 @@ ++import { Camera, Object3D } from 'three'; + import ChainMap from './ChainMap.js'; + import RenderBundle from './RenderBundle.js'; + + class RenderBundles { ++ lists: ChainMap; ++ + constructor() { +- this.lists = new ChainMap(); ++ this.lists = new ChainMap(); + } + +- get(scene, camera) { ++ get(scene: Object3D, camera: Camera) { + const lists = this.lists; + const keys = [scene, camera]; + +@@ -21,7 +24,7 @@ class RenderBundles { + } + + dispose() { +- this.lists = new ChainMap(); ++ this.lists = new ChainMap(); + } + } + diff --git a/examples-jsm/examples/renderers/common/RenderContext.ts b/examples-jsm/examples/renderers/common/RenderContext.ts -index 3b43028e..3efe66b9 100644 +index 3b43028e..b53cfc72 100644 --- a/examples-jsm/examples/renderers/common/RenderContext.ts +++ b/examples-jsm/examples/renderers/common/RenderContext.ts -@@ -3,6 +3,36 @@ import { Vector4 } from 'three'; +@@ -1,8 +1,41 @@ + import { Vector4 } from 'three'; ++import ClippingContext from './ClippingContext.js'; + let id = 0; class RenderContext { @@ -1713,12 +1818,14 @@ index 3b43028e..3efe66b9 100644 + height: number; + + readonly isRenderContext: true; ++ ++ clippingContext?: ClippingContext | undefined; + constructor() { this.id = id++; diff --git a/examples-jsm/examples/renderers/common/RenderContexts.ts b/examples-jsm/examples/renderers/common/RenderContexts.ts -index 630a2e42..1328da1e 100644 +index 630a2e42..9b1c1942 100644 --- a/examples-jsm/examples/renderers/common/RenderContexts.ts +++ b/examples-jsm/examples/renderers/common/RenderContexts.ts @@ -1,12 +1,15 @@ @@ -1727,7 +1834,7 @@ index 630a2e42..1328da1e 100644 import RenderContext from './RenderContext.js'; class RenderContexts { -+ chainMaps: { [attachmentState: string]: ChainMap | undefined }; ++ chainMaps: { [attachmentState: string]: ChainMap | undefined }; + constructor() { this.chainMaps = {}; @@ -1738,41 +1845,222 @@ index 630a2e42..1328da1e 100644 const chainKey = [scene, camera]; let attachmentState; -@@ -35,7 +38,7 @@ class RenderContexts { +@@ -35,8 +38,11 @@ class RenderContexts { return renderState; } - getChainMap(attachmentState) { +- return this.chainMaps[attachmentState] || (this.chainMaps[attachmentState] = new ChainMap()); + getChainMap(attachmentState: string) { - return this.chainMaps[attachmentState] || (this.chainMaps[attachmentState] = new ChainMap()); ++ return ( ++ this.chainMaps[attachmentState] || ++ (this.chainMaps[attachmentState] = new ChainMap()) ++ ); + } + + dispose() { +diff --git a/examples-jsm/examples/renderers/common/RenderList.ts b/examples-jsm/examples/renderers/common/RenderList.ts +index a72a91df..77c6c4a4 100644 +--- a/examples-jsm/examples/renderers/common/RenderList.ts ++++ b/examples-jsm/examples/renderers/common/RenderList.ts +@@ -1,3 +1,4 @@ ++import { Camera, Light, Object3D } from 'three'; + import { LightsNode } from '../../nodes/Nodes.js'; + + function painterSortStable(a, b) { +@@ -26,7 +27,27 @@ function reversePainterSortStable(a, b) { + } + } + ++export interface Bundle { ++ object: Object3D; ++ camera: Camera; ++ renderList: RenderList; ++} ++ + class RenderList { ++ // TODO ++ // renderItems ++ renderItemsIndex: number; ++ ++ // TODO ++ // opaque ++ // transparent ++ bundles: Bundle[]; ++ ++ lightsNode: LightsNode; ++ lightsArray: Light[]; ++ ++ occlusionQueryCount: number; ++ + constructor() { + this.renderItems = []; + this.renderItemsIndex = 0; +@@ -101,11 +122,11 @@ class RenderList { + (material.transparent === true ? this.transparent : this.opaque).unshift(renderItem); + } + +- pushBundle(group) { ++ pushBundle(group: Bundle) { + this.bundles.push(group); + } + +- pushLight(light) { ++ pushLight(light: Light) { + this.lightsArray.push(light); } diff --git a/examples-jsm/examples/renderers/common/RenderLists.ts b/examples-jsm/examples/renderers/common/RenderLists.ts -index 3fc3134e..e61a7fea 100644 +index 3fc3134e..66c64756 100644 --- a/examples-jsm/examples/renderers/common/RenderLists.ts +++ b/examples-jsm/examples/renderers/common/RenderLists.ts @@ -1,12 +1,15 @@ -+import { Camera, Scene } from 'three'; ++import { Camera, Object3D, Scene } from 'three'; import ChainMap from './ChainMap.js'; import RenderList from './RenderList.js'; class RenderLists { -+ lists: ChainMap; ++ lists: ChainMap; + constructor() { - this.lists = new ChainMap(); +- this.lists = new ChainMap(); ++ this.lists = new ChainMap(); } - get(scene, camera) { -+ get(scene: Scene, camera: Camera) { ++ get(scene: Object3D, camera: Camera) { const lists = this.lists; const keys = [scene, camera]; +@@ -21,7 +24,7 @@ class RenderLists { + } + + dispose() { +- this.lists = new ChainMap(); ++ this.lists = new ChainMap(); + } + } + +diff --git a/examples-jsm/examples/renderers/common/RenderObject.ts b/examples-jsm/examples/renderers/common/RenderObject.ts +index 861c15dc..876f48a1 100644 +--- a/examples-jsm/examples/renderers/common/RenderObject.ts ++++ b/examples-jsm/examples/renderers/common/RenderObject.ts +@@ -1,4 +1,10 @@ + import ClippingContext from './ClippingContext.js'; ++import Nodes from './nodes/Nodes.js'; ++import Geometries from './Geometries.js'; ++import Renderer from './Renderer.js'; ++import { Camera, Material, Object3D, Scene } from 'three'; ++import LightsNode from '../../nodes/lighting/LightsNode.js'; ++import RenderContext from './RenderContext.js'; + + let id = 0; + +@@ -27,7 +33,60 @@ function getKeys(obj) { + } + + export default class RenderObject { +- constructor(nodes, geometries, renderer, object, material, scene, camera, lightsNode, renderContext) { ++ _nodes: Nodes; ++ _geometries: Geometries; ++ ++ id: number; ++ ++ renderer: Renderer; ++ object: Object3D; ++ material: Material; ++ scene: Scene; ++ camera: Camera; ++ lightsNode: LightsNode; ++ context: RenderContext; ++ ++ // TODO ++ // geometry: BufferGeometry | undefined; ?? ++ version: number; ++ ++ // TODO ++ // drawRange ++ ++ // TODO ++ // attributes ++ // pipeline ++ // vertexBuffers ++ ++ clippingContext?: ClippingContext | undefined; ++ ++ clippingContextVersion: number; ++ ++ // TODO ++ // initialNodesCacheKey ++ initialCacheKey: string; ++ ++ // TODO ++ // this._nodeBuilderState = null; ++ // this._bindings = null; ++ ++ onDispose: (() => void) | null; ++ ++ readonly isRenderObject: true; ++ ++ onMaterialDispose: () => void; ++ ++ constructor( ++ nodes: Nodes, ++ geometries: Geometries, ++ renderer: Renderer, ++ object: Object3D, ++ material: Material, ++ scene: Scene, ++ camera: Camera, ++ lightsNode: LightsNode, ++ renderContext: RenderContext, ++ ) { + this._nodes = nodes; + this._geometries = geometries; + +@@ -52,7 +111,7 @@ export default class RenderObject { + + this.updateClipping(renderContext.clippingContext); + +- this.clippingContextVersion = this.clippingContext.version; ++ this.clippingContextVersion = this.clippingContext!.version; + + this.initialNodesCacheKey = this.getNodesCacheKey(); + this.initialCacheKey = this.getCacheKey(); +@@ -71,7 +130,7 @@ export default class RenderObject { + this.material.addEventListener('dispose', this.onMaterialDispose); + } + +- updateClipping(parent) { ++ updateClipping(parent: ClippingContext | undefined) { + const material = this.material; + + let clippingContext = this.clippingContext; +@@ -89,9 +148,9 @@ export default class RenderObject { + } + + get clippingNeedsUpdate() { +- if (this.clippingContext.version === this.clippingContextVersion) return false; ++ if (this.clippingContext!.version === this.clippingContextVersion) return false; + +- this.clippingContextVersion = this.clippingContext.version; ++ this.clippingContextVersion = this.clippingContext!.version; + + return true; + } +@@ -206,6 +265,6 @@ export default class RenderObject { + dispose() { + this.material.removeEventListener('dispose', this.onMaterialDispose); + +- this.onDispose(); ++ this.onDispose!(); + } + } diff --git a/examples-jsm/examples/renderers/common/RenderObjects.ts b/examples-jsm/examples/renderers/common/RenderObjects.ts -index 76dc482e..b4d04b17 100644 +index 76dc482e..f0b87b98 100644 --- a/examples-jsm/examples/renderers/common/RenderObjects.ts +++ b/examples-jsm/examples/renderers/common/RenderObjects.ts -@@ -1,8 +1,28 @@ +@@ -1,8 +1,33 @@ import ChainMap from './ChainMap.js'; import RenderObject from './RenderObject.js'; +import Renderer from './Renderer.js'; @@ -1781,16 +2069,21 @@ index 76dc482e..b4d04b17 100644 +import Pipelines from './Pipelines.js'; +import Bindings from './Bindings.js'; +import Info from './Info.js'; ++import LightsNode from '../../nodes/lighting/LightsNode.js'; ++import { Camera, Material, Object3D, Scene } from 'three'; ++import RenderContext from './RenderContext.js'; class RenderObjects { - constructor(renderer, nodes, geometries, pipelines, bindings, info) { -+ renderer: RenderObject; ++ renderer: Renderer; + nodes: Nodes; + geometries: Geometries; + pipelines: Pipelines; + bindings: Bindings; + info: Info; + ++ chainMaps: { [passId: string]: ChainMap }; ++ + constructor( + renderer: Renderer, + nodes: Nodes, @@ -1802,11 +2095,67 @@ index 76dc482e..b4d04b17 100644 this.renderer = renderer; this.nodes = nodes; this.geometries = geometries; +@@ -13,7 +38,15 @@ class RenderObjects { + this.chainMaps = {}; + } + +- get(object, material, scene, camera, lightsNode, renderContext, passId) { ++ get( ++ object: Object3D, ++ material: Material, ++ scene: Scene, ++ camera: Camera, ++ lightsNode: LightsNode, ++ renderContext: RenderContext, ++ passId: string | undefined, ++ ): RenderObject { + const chainMap = this.getChainMap(passId); + const chainArray = [object, material, renderContext, lightsNode]; + +@@ -52,7 +85,10 @@ class RenderObjects { + } + + getChainMap(passId = 'default') { +- return this.chainMaps[passId] || (this.chainMaps[passId] = new ChainMap()); ++ return ( ++ this.chainMaps[passId] || ++ (this.chainMaps[passId] = new ChainMap()) ++ ); + } + + dispose() { +@@ -60,16 +96,16 @@ class RenderObjects { + } + + createRenderObject( +- nodes, +- geometries, +- renderer, +- object, +- material, +- scene, +- camera, +- lightsNode, +- renderContext, +- passId, ++ nodes: Nodes, ++ geometries: Geometries, ++ renderer: Renderer, ++ object: Object3D, ++ material: Material, ++ scene: Scene, ++ camera: Camera, ++ lightsNode: LightsNode, ++ renderContext: RenderContext, ++ passId: string | undefined, + ) { + const chainMap = this.getChainMap(passId); + diff --git a/examples-jsm/examples/renderers/common/Renderer.ts b/examples-jsm/examples/renderers/common/Renderer.ts -index acf180d8..50fde9da 100644 +index acf180d8..eecff683 100644 --- a/examples-jsm/examples/renderers/common/Renderer.ts +++ b/examples-jsm/examples/renderers/common/Renderer.ts -@@ -30,10 +30,28 @@ import { +@@ -30,10 +30,36 @@ import { RenderTarget, HalfFloatType, RGBAFormat, @@ -1823,6 +2172,13 @@ index acf180d8..50fde9da 100644 + BufferGeometry, + Material, + Group, ++ Light, ++ LOD, ++ Sprite, ++ LineLoop, ++ Mesh, ++ Line, ++ Points, } from 'three'; import { NodeMaterial } from '../../nodes/Nodes.js'; import QuadMesh from '../../objects/QuadMesh.js'; @@ -1832,10 +2188,11 @@ index acf180d8..50fde9da 100644 +import LightsNode from '../../nodes/lighting/LightsNode.js'; +import RenderContext from './RenderContext.js'; +import RenderBundle from './RenderBundle.js'; ++import RenderList, { Bundle } from './RenderList.js'; const _scene = new Scene(); const _drawingBufferSize = new Vector2(); -@@ -43,8 +61,118 @@ const _projScreenMatrix = new Matrix4(); +@@ -43,8 +69,124 @@ const _projScreenMatrix = new Matrix4(); const _vector3 = new Vector3(); const _quad = new QuadMesh(new NodeMaterial()); @@ -1937,10 +2294,16 @@ index acf180d8..50fde9da 100644 + | null; + _currentRenderBundle: RenderBundle | null; + -+ // TODO -+ // this._handleObjectFunction = this._renderObjectDirect; ++ _handleObjectFunction: ( ++ object: Object3D, ++ material: Material, ++ scene: Scene, ++ camera: Camera, ++ lightsNode: LightsNode, ++ group: Group, ++ passId?: string, ++ ) => void; + -+ // TODO + _initialized: boolean; + _initPromise: Promise | null; + @@ -1955,7 +2318,7 @@ index acf180d8..50fde9da 100644 this.isRenderer = true; // -@@ -207,12 +335,12 @@ class Renderer { +@@ -207,12 +349,12 @@ class Renderer { return this.backend.coordinateSystem; } @@ -1970,7 +2333,7 @@ index acf180d8..50fde9da 100644 const previousRenderId = nodeFrame.renderId; const previousRenderContext = this._currentRenderContext; -@@ -226,7 +354,7 @@ class Renderer { +@@ -226,7 +368,7 @@ class Renderer { if (targetScene === null) targetScene = scene; const renderTarget = this._renderTarget; @@ -1979,7 +2342,27 @@ index acf180d8..50fde9da 100644 const activeMipmapLevel = this._activeMipmapLevel; const compilationPromises = []; -@@ -277,9 +405,9 @@ class Renderer { +@@ -258,7 +400,7 @@ class Renderer { + + // + +- const renderList = this._renderLists.get(scene, camera); ++ const renderList = this._renderLists!.get(scene, camera); + renderList.begin(); + + this._projectObject(scene, camera, 0, renderList); +@@ -266,8 +408,8 @@ class Renderer { + // include lights from target scene + if (targetScene !== scene) { + targetScene.traverseVisible(function (object) { +- if (object.isLight && object.layers.test(camera.layers)) { +- renderList.pushLight(object); ++ if ((object as Light).isLight && object.layers.test(camera.layers)) { ++ renderList.pushLight(object as Light); + } + }); + } +@@ -277,9 +419,9 @@ class Renderer { // if (renderTarget !== null) { @@ -1991,7 +2374,7 @@ index acf180d8..50fde9da 100644 renderContext.textures = renderTargetData.textures; renderContext.depthTexture = renderTargetData.depthTexture; -@@ -290,11 +418,11 @@ class Renderer { +@@ -290,11 +432,11 @@ class Renderer { // @@ -2005,7 +2388,7 @@ index acf180d8..50fde9da 100644 // process render lists -@@ -320,7 +448,7 @@ class Renderer { +@@ -320,7 +462,7 @@ class Renderer { await Promise.all(compilationPromises); } @@ -2014,8 +2397,12 @@ index acf180d8..50fde9da 100644 if (this._initialized === false) await this.init(); const renderContext = this._renderScene(scene, camera); -@@ -331,12 +459,12 @@ class Renderer { - _renderBundle(bundle, sceneRef, lightsNode) { +@@ -328,15 +470,15 @@ class Renderer { + await this.backend.resolveTimestampAsync(renderContext, 'render'); + } + +- _renderBundle(bundle, sceneRef, lightsNode) { ++ _renderBundle(bundle: Bundle, sceneRef: Scene, lightsNode: LightsNode) { const { object, camera, renderList } = bundle; - const renderContext = this._currentRenderContext; @@ -2029,7 +2416,7 @@ index acf180d8..50fde9da 100644 const renderBundleData = this.backend.get(renderBundle); if (renderBundleData.renderContexts === undefined) renderBundleData.renderContexts = new Set(); -@@ -350,7 +478,7 @@ class Renderer { +@@ -350,7 +492,7 @@ class Renderer { if (renderBundleNeedsUpdate) { if (renderContextData.renderObjects === undefined || object.needsUpdate === true) { @@ -2038,7 +2425,14 @@ index acf180d8..50fde9da 100644 renderContextData.renderObjects = []; renderContextData.renderBundles = []; -@@ -379,7 +507,7 @@ class Renderer { +@@ -373,13 +515,13 @@ class Renderer { + + object.needsUpdate = false; + } else { +- const renderContext = this._currentRenderContext; ++ 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]; @@ -2047,7 +2441,7 @@ index acf180d8..50fde9da 100644 // -@@ -389,15 +517,15 @@ class Renderer { +@@ -389,15 +531,15 @@ class Renderer { ); renderObject.object.normalMatrix.getNormalMatrix(renderObject.object.modelViewMatrix); @@ -2066,7 +2460,7 @@ index acf180d8..50fde9da 100644 if (this._initialized === false) { console.warn( 'THREE.Renderer: .render() called before the backend is initialized. Try using .renderAsync() instead.', -@@ -453,12 +581,12 @@ class Renderer { +@@ -453,12 +595,12 @@ class Renderer { return frameBufferTarget; } @@ -2081,7 +2475,7 @@ index acf180d8..50fde9da 100644 const previousRenderId = nodeFrame.renderId; const previousRenderContext = this._currentRenderContext; -@@ -487,7 +615,7 @@ class Renderer { +@@ -487,7 +629,7 @@ class Renderer { // @@ -2090,7 +2484,7 @@ index acf180d8..50fde9da 100644 this._currentRenderContext = renderContext; this._currentRenderObjectFunction = this._renderObjectFunction || this.renderObject; -@@ -558,7 +686,7 @@ class Renderer { +@@ -558,7 +700,7 @@ class Renderer { _projScreenMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse); _frustum.setFromProjectionMatrix(_projScreenMatrix, coordinateSystem); @@ -2099,7 +2493,7 @@ index acf180d8..50fde9da 100644 renderList.begin(); this._projectObject(scene, camera, 0, renderList); -@@ -572,9 +700,9 @@ class Renderer { +@@ -572,9 +714,9 @@ class Renderer { // if (renderTarget !== null) { @@ -2111,7 +2505,7 @@ index acf180d8..50fde9da 100644 renderContext.textures = renderTargetData.textures; renderContext.depthTexture = renderTargetData.depthTexture; -@@ -586,8 +714,8 @@ class Renderer { +@@ -586,8 +728,8 @@ class Renderer { } else { renderContext.textures = null; renderContext.depthTexture = null; @@ -2122,7 +2516,7 @@ index acf180d8..50fde9da 100644 renderContext.depth = this.depth; renderContext.stencil = this.stencil; } -@@ -600,11 +728,11 @@ class Renderer { +@@ -600,11 +742,11 @@ class Renderer { // @@ -2136,7 +2530,7 @@ index acf180d8..50fde9da 100644 // -@@ -637,7 +765,7 @@ class Renderer { +@@ -637,7 +779,7 @@ class Renderer { if (frameBufferTarget !== null) { this.setRenderTarget(outputRenderTarget, activeCubeFace, activeMipmapLevel); @@ -2145,7 +2539,7 @@ index acf180d8..50fde9da 100644 this._renderScene(_quad, _quad.camera, false); } -@@ -663,10 +791,10 @@ class Renderer { +@@ -663,10 +805,10 @@ class Renderer { return this._activeMipmapLevel; } @@ -2158,7 +2552,7 @@ index acf180d8..50fde9da 100644 } getArrayBuffer(attribute) { -@@ -689,11 +817,11 @@ class Renderer { +@@ -689,11 +831,11 @@ class Renderer { return this._pixelRatio; } @@ -2172,7 +2566,7 @@ index acf180d8..50fde9da 100644 return target.set(this._width, this._height); } -@@ -703,30 +831,30 @@ class Renderer { +@@ -703,30 +845,30 @@ class Renderer { this.setSize(this._width, this._height, false); } @@ -2211,7 +2605,7 @@ index acf180d8..50fde9da 100644 } this.setViewport(0, 0, width, height); -@@ -734,15 +862,15 @@ class Renderer { +@@ -734,15 +876,15 @@ class Renderer { if (this._initialized) this.backend.updateSize(); } @@ -2230,7 +2624,7 @@ index acf180d8..50fde9da 100644 const scissor = this._scissor; target.x = scissor.x; -@@ -753,13 +881,15 @@ class Renderer { +@@ -753,13 +895,15 @@ class Renderer { return target; } @@ -2250,7 +2644,7 @@ index acf180d8..50fde9da 100644 } } -@@ -767,34 +897,36 @@ class Renderer { +@@ -767,34 +911,36 @@ class Renderer { return this._scissorTest; } @@ -2295,7 +2689,7 @@ index acf180d8..50fde9da 100644 this._clearColor.set(color); this._clearColor.a = alpha; } -@@ -803,7 +935,7 @@ class Renderer { +@@ -803,7 +949,7 @@ class Renderer { return this._clearColor.a; } @@ -2304,7 +2698,7 @@ index acf180d8..50fde9da 100644 this._clearColor.a = alpha; } -@@ -811,7 +943,7 @@ class Renderer { +@@ -811,7 +957,7 @@ class Renderer { return this._clearDepth; } @@ -2313,7 +2707,7 @@ index acf180d8..50fde9da 100644 this._clearDepth = depth; } -@@ -819,11 +951,11 @@ class Renderer { +@@ -819,11 +965,11 @@ class Renderer { return this._clearStencil; } @@ -2327,7 +2721,7 @@ index acf180d8..50fde9da 100644 const renderContext = this._currentRenderContext; return renderContext && this.backend.isOccluded(renderContext, object); -@@ -843,9 +975,9 @@ class Renderer { +@@ -843,9 +989,9 @@ class Renderer { let renderTargetData = null; if (renderTarget !== null) { @@ -2339,7 +2733,7 @@ index acf180d8..50fde9da 100644 } this.backend.clear(color, depth, stencil, renderTargetData); -@@ -896,20 +1028,20 @@ class Renderer { +@@ -896,20 +1042,20 @@ class Renderer { dispose() { this.info.dispose(); @@ -2369,7 +2763,7 @@ index acf180d8..50fde9da 100644 this._renderTarget = renderTarget; this._activeCubeFace = activeCubeFace; this._activeMipmapLevel = activeMipmapLevel; -@@ -927,10 +1059,10 @@ class Renderer { +@@ -927,10 +1073,10 @@ class Renderer { return this._renderObjectFunction; } @@ -2382,7 +2776,7 @@ index acf180d8..50fde9da 100644 const previousRenderId = nodeFrame.renderId; -@@ -993,13 +1125,13 @@ class Renderer { +@@ -993,13 +1139,13 @@ class Renderer { nodeFrame.renderId = previousRenderId; } @@ -2398,7 +2792,7 @@ index acf180d8..50fde9da 100644 if (this._initialized === false) { console.warn( 'THREE.Renderer: .hasFeature() called before the backend is initialized. Try using .hasFeatureAsync() instead.', -@@ -1011,22 +1143,35 @@ class Renderer { +@@ -1011,64 +1157,77 @@ class Renderer { return this.backend.hasFeature(name); } @@ -2440,7 +2834,91 @@ index acf180d8..50fde9da 100644 return this.backend.copyTextureToBuffer(renderTarget.textures[index], x, y, width, height); } -@@ -1167,7 +1312,15 @@ class Renderer { +- _projectObject(object, camera, groupOrder, renderList) { ++ _projectObject(object: Object3D, camera: Camera, groupOrder: number, renderList: RenderList) { + if (object.visible === false) return; + + const visible = object.layers.test(camera.layers); + + if (visible) { +- if (object.isGroup) { ++ if ((object as Group).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)) { ++ } else if ((object as LOD).isLOD) { ++ if ((object as LOD).autoUpdate === true) (object as LOD).update(camera); ++ } else if ((object as Light).isLight) { ++ renderList.pushLight(object as Light); ++ } else if ((object as Sprite).isSprite) { ++ if (!object.frustumCulled || _frustum.intersectsSprite(object as Sprite)) { + if (this.sortObjects === true) { + _vector3.setFromMatrixPosition(object.matrixWorld).applyMatrix4(_projScreenMatrix); + } + +- const geometry = object.geometry; +- const material = object.material; ++ const geometry = (object as Sprite).geometry; ++ const material = (object as Sprite).material; + + if (material.visible) { + renderList.push(object, geometry, material, groupOrder, _vector3.z, null); + } + } +- } else if (object.isLineLoop) { ++ } else if ((object as LineLoop).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) { ++ } else if ((object as Mesh).isMesh || (object as Line).isLine || (object as Points).isPoints) { + if (!object.frustumCulled || _frustum.intersectsObject(object)) { +- const geometry = object.geometry; +- const material = object.material; ++ const geometry = (object as Mesh | Line | Points).geometry; ++ const material = (object as Mesh | Line | Points).material; + + if (this.sortObjects === true) { + if (geometry.boundingSphere === null) geometry.computeBoundingSphere(); + + _vector3 +- .copy(geometry.boundingSphere.center) ++ .copy(geometry.boundingSphere!.center) + .applyMatrix4(object.matrixWorld) + .applyMatrix4(_projScreenMatrix); + } +@@ -1078,7 +1237,7 @@ class Renderer { + + for (let i = 0, l = groups.length; i < l; i++) { + const group = groups[i]; +- const groupMaterial = material[group.materialIndex]; ++ const groupMaterial = material[group.materialIndex!]; + + if (groupMaterial && groupMaterial.visible) { + renderList.push(object, geometry, groupMaterial, groupOrder, _vector3.z, group); +@@ -1095,7 +1254,7 @@ class Renderer { + const baseRenderList = renderList; + + // replace render list +- renderList = this._renderLists.get(object, camera); ++ renderList = this._renderLists!.get(object, camera); + + renderList.begin(); + +@@ -1115,7 +1274,7 @@ class Renderer { + } + } + +- _renderBundles(bundles, sceneRef, lightsNode) { ++ _renderBundles(bundles: Bundle[], sceneRef: Scene, lightsNode: LightsNode) { + for (const bundle of bundles) { + this._renderBundle(bundle, sceneRef, lightsNode); + } +@@ -1167,7 +1326,15 @@ class Renderer { } } @@ -2457,7 +2935,26 @@ index acf180d8..50fde9da 100644 let overridePositionNode; let overrideFragmentNode; let overrideDepthNode; -@@ -1268,7 +1421,7 @@ class Renderer { +@@ -1254,8 +1421,16 @@ class Renderer { + object.onAfterRender(this, scene, camera, geometry, material, group); + } + +- _renderObjectDirect(object, material, scene, camera, lightsNode, group, passId) { +- const renderObject = this._objects.get( ++ _renderObjectDirect( ++ object: Object3D, ++ material: Material, ++ scene: Scene, ++ camera: Camera, ++ lightsNode: LightsNode, ++ group: Group, ++ passId?: string, ++ ) { ++ const renderObject = this._objects!.get( + object, + material, + scene, +@@ -1268,7 +1443,7 @@ class Renderer { // @@ -2466,7 +2963,7 @@ index acf180d8..50fde9da 100644 // -@@ -1277,10 +1430,10 @@ class Renderer { +@@ -1277,10 +1452,10 @@ class Renderer { // @@ -2481,7 +2978,31 @@ index acf180d8..50fde9da 100644 // -@@ -1313,15 +1466,15 @@ class Renderer { +@@ -1300,28 +1475,36 @@ class Renderer { + } + } + +- _createObjectPipeline(object, material, scene, camera, lightsNode, passId) { +- const renderObject = this._objects.get( ++ _createObjectPipeline( ++ object: Object3D, ++ material: Material, ++ scene: Scene, ++ camera: Camera, ++ lightsNode: LightsNode, ++ group: Group, ++ passId?: string, ++ ) { ++ const renderObject = this._objects!.get( + object, + material, + scene, + camera, + lightsNode, +- this._currentRenderContext, ++ this._currentRenderContext!, + passId, + ); // @@ -2503,10 +3024,10 @@ index acf180d8..50fde9da 100644 get compute() { diff --git a/examples-jsm/examples/renderers/common/Textures.ts b/examples-jsm/examples/renderers/common/Textures.ts -index 0eb0509c..76068908 100644 +index 0eb0509c..23c04c1b 100644 --- a/examples-jsm/examples/renderers/common/Textures.ts +++ b/examples-jsm/examples/renderers/common/Textures.ts -@@ -14,12 +14,21 @@ import { +@@ -14,12 +14,23 @@ import { CubeReflectionMapping, CubeRefractionMapping, UnsignedByteType, @@ -2519,8 +3040,11 @@ index 0eb0509c..76068908 100644 const _size = new Vector3(); - class Textures extends DataMap { +-class Textures extends DataMap { - constructor(renderer, backend, info) { ++interface RenderTargetData {} ++ ++class Textures extends DataMap { + renderer: Renderer; + backend: Backend; + info: Info; @@ -2529,7 +3053,7 @@ index 0eb0509c..76068908 100644 super(); this.renderer = renderer; -@@ -27,7 +36,7 @@ class Textures extends DataMap { +@@ -27,7 +38,7 @@ class Textures extends DataMap { this.info = info; } @@ -2538,7 +3062,7 @@ index 0eb0509c..76068908 100644 const renderTargetData = this.get(renderTarget); const sampleCount = renderTarget.samples === 0 ? 1 : renderTarget.samples; -@@ -116,7 +125,7 @@ class Textures extends DataMap { +@@ -116,7 +127,7 @@ class Textures extends DataMap { } } @@ -2547,7 +3071,7 @@ index 0eb0509c..76068908 100644 const textureData = this.get(texture); if (textureData.initialized === true && textureData.version === texture.version) return; -@@ -229,7 +238,7 @@ class Textures extends DataMap { +@@ -229,7 +240,7 @@ class Textures extends DataMap { textureData.version = texture.version; } @@ -2556,7 +3080,7 @@ index 0eb0509c..76068908 100644 let image = texture.images ? texture.images[0] : texture.image; if (image) { -@@ -245,7 +254,7 @@ class Textures extends DataMap { +@@ -245,7 +256,7 @@ class Textures extends DataMap { return target; } @@ -2565,7 +3089,7 @@ index 0eb0509c..76068908 100644 let mipLevelCount; if (texture.isCompressedTexture) { -@@ -257,7 +266,7 @@ class Textures extends DataMap { +@@ -257,7 +268,7 @@ class Textures extends DataMap { return mipLevelCount; } @@ -2574,7 +3098,7 @@ index 0eb0509c..76068908 100644 if (this.isEnvironmentTexture(texture)) return true; return ( -@@ -266,7 +275,7 @@ class Textures extends DataMap { +@@ -266,7 +277,7 @@ class Textures extends DataMap { ); } @@ -2583,7 +3107,7 @@ index 0eb0509c..76068908 100644 const mapping = texture.mapping; return ( -@@ -277,7 +286,7 @@ class Textures extends DataMap { +@@ -277,7 +288,7 @@ class Textures extends DataMap { ); } @@ -2593,7 +3117,7 @@ index 0eb0509c..76068908 100644 this.backend.destroyTexture(texture); diff --git a/examples-jsm/examples/renderers/common/nodes/Nodes.ts b/examples-jsm/examples/renderers/common/nodes/Nodes.ts -index 86df5654..819ea472 100644 +index 86df5654..b0389629 100644 --- a/examples-jsm/examples/renderers/common/nodes/Nodes.ts +++ b/examples-jsm/examples/renderers/common/nodes/Nodes.ts @@ -2,10 +2,16 @@ import DataMap from '../DataMap.js'; @@ -2613,12 +3137,13 @@ index 86df5654..819ea472 100644 } from 'three'; import { NodeFrame, -@@ -23,9 +29,18 @@ import { +@@ -23,9 +29,19 @@ import { pmremTexture, viewportTopLeft, } from '../../../nodes/Nodes.js'; +import Renderer from '../Renderer.js'; +import Backend from '../Backend.js'; ++import LightsNode from '../../../nodes/lighting/LightsNode.js'; class Nodes extends DataMap { - constructor(renderer, backend) { @@ -2633,7 +3158,16 @@ index 86df5654..819ea472 100644 super(); this.renderer = renderer; -@@ -215,7 +230,7 @@ class Nodes extends DataMap { +@@ -188,7 +204,7 @@ class Nodes extends DataMap { + return scene.fogNode || this.get(scene).fogNode || null; + } + +- getCacheKey(scene, lightsNode) { ++ getCacheKey(scene: Scene, lightsNode: LightsNode) { + const chain = [scene, lightsNode]; + const callId = this.renderer.info.calls; + +@@ -215,7 +231,7 @@ class Nodes extends DataMap { return cacheKeyData.cacheKey; } @@ -2642,7 +3176,7 @@ index 86df5654..819ea472 100644 this.updateEnvironment(scene); this.updateFog(scene); this.updateBackground(scene); -@@ -225,7 +240,7 @@ class Nodes extends DataMap { +@@ -225,7 +241,7 @@ class Nodes extends DataMap { return this.renderer.getRenderTarget() ? false : true; } @@ -2651,7 +3185,7 @@ index 86df5654..819ea472 100644 const sceneData = this.get(scene); const background = scene.background; -@@ -239,10 +254,10 @@ class Nodes extends DataMap { +@@ -239,10 +255,10 @@ class Nodes extends DataMap { background.mapping === EquirectangularRefractionMapping ) { backgroundNode = pmremTexture(background, normalWorld); @@ -2666,7 +3200,7 @@ index 86df5654..819ea472 100644 } sceneData.backgroundNode = backgroundNode; -@@ -254,7 +269,7 @@ class Nodes extends DataMap { +@@ -254,7 +270,7 @@ class Nodes extends DataMap { } } @@ -2675,7 +3209,7 @@ index 86df5654..819ea472 100644 const sceneData = this.get(scene); const fog = scene.fog; -@@ -262,9 +277,9 @@ class Nodes extends DataMap { +@@ -262,9 +278,9 @@ class Nodes extends DataMap { if (sceneData.fog !== fog) { let fogNode = null; @@ -2687,7 +3221,7 @@ index 86df5654..819ea472 100644 fogNode = rangeFog( reference('color', 'color', fog), reference('near', 'float', fog), -@@ -283,7 +298,7 @@ class Nodes extends DataMap { +@@ -283,7 +299,7 @@ class Nodes extends DataMap { } } @@ -2696,7 +3230,7 @@ index 86df5654..819ea472 100644 const sceneData = this.get(scene); const environment = scene.environment; -@@ -291,7 +306,7 @@ class Nodes extends DataMap { +@@ -291,7 +307,7 @@ class Nodes extends DataMap { if (sceneData.environment !== environment) { let environmentNode = null; 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/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/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/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/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/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/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/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/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;