From 96aeda63fd401a500fa13e57c442597cbb375b85 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Tue, 7 Apr 2026 18:57:32 +0800 Subject: [PATCH 01/91] feat(core): add GPU instancing auto-batching with UBO-based per-instance data Introduce automatic GPU instancing for MeshRenderer. The system scans renderer-group uniforms across shader passes, builds a unified std140 UBO layout, and packs per-instance data (ModelMat, Layer, etc.) each frame. Key changes: - InstanceDataPacker: packs renderer data into shared UBO for instanced draw - ShaderFactory: unified _scanInstanceUniforms, _buildLayout, _injectInstanceUBO - MeshRenderer._canBatch/_batch: instancing merge logic - ShaderPass/SubShader: instance-aware compilation with macro cache - GLSLIfdefResolver: compile-time #ifdef resolution for instance field scanning - MacroCachePool: pooled ShaderMacroCollection for shader program caching - RenderQueue: instance-aware draw path with UBO binding --- packages/core/src/2d/sprite/SpriteMask.ts | 2 +- packages/core/src/Engine.ts | 9 +- .../src/RenderPipeline/BasicRenderPipeline.ts | 15 +- .../core/src/RenderPipeline/BatchUtils.ts | 2 +- .../core/src/RenderPipeline/BatcherManager.ts | 19 ++ .../core/src/RenderPipeline/CullingResults.ts | 1 + .../src/RenderPipeline/InstanceDataPacker.ts | 114 +++++++++ .../RenderPipeline/InstanceDataPackerPool.ts | 44 ++++ .../core/src/RenderPipeline/RenderQueue.ts | 84 +++++-- .../src/RenderPipeline/SubRenderElement.ts | 9 +- packages/core/src/Renderer.ts | 7 +- .../src/graphic/TransformFeedbackShader.ts | 2 +- .../core/src/graphic/enums/BufferBindFlag.ts | 4 +- packages/core/src/mesh/MeshRenderer.ts | 46 ++++ packages/core/src/mesh/SkinnedMeshRenderer.ts | 8 + packages/core/src/shader/MacroCachePool.ts | 99 ++++++++ packages/core/src/shader/Shader.ts | 10 +- .../core/src/shader/ShaderBlockProperty.ts | 28 +++ packages/core/src/shader/ShaderMacro.ts | 2 + packages/core/src/shader/ShaderPass.ts | 125 +++++++--- packages/core/src/shader/ShaderProgram.ts | 27 ++ packages/core/src/shader/ShaderProgramPool.ts | 110 --------- packages/core/src/shader/SubShader.ts | 35 +++ .../enums/ConstantBufferBindingPoint.ts | 7 + .../core/src/shaderlib/GLSLIfdefResolver.ts | 41 ++++ packages/core/src/shaderlib/ShaderFactory.ts | 231 +++++++++++++++++- .../src/shaderlib/extra/shadow-map.vs.glsl | 2 +- .../core/src/shaderlib/light_frag_define.glsl | 1 - packages/core/src/shaderlib/normal_vert.glsl | 5 +- .../core/src/shaderlib/transform_declare.glsl | 6 +- packages/rhi-webgl/src/GLBuffer.ts | 13 +- packages/rhi-webgl/src/WebGLGraphicDevice.ts | 19 ++ packages/shader/src/shaders/Light.glsl | 1 - packages/shader/src/shaders/Transform.glsl | 14 +- .../src/shaders/shadingPBR/VertexPBR.glsl | 7 +- packages/ui/src/component/advanced/Image.ts | 2 +- packages/ui/src/component/advanced/Text.ts | 2 +- 37 files changed, 930 insertions(+), 223 deletions(-) create mode 100644 packages/core/src/RenderPipeline/InstanceDataPacker.ts create mode 100644 packages/core/src/RenderPipeline/InstanceDataPackerPool.ts create mode 100644 packages/core/src/shader/MacroCachePool.ts create mode 100644 packages/core/src/shader/ShaderBlockProperty.ts delete mode 100644 packages/core/src/shader/ShaderProgramPool.ts create mode 100644 packages/core/src/shader/enums/ConstantBufferBindingPoint.ts create mode 100644 packages/core/src/shaderlib/GLSLIfdefResolver.ts diff --git a/packages/core/src/2d/sprite/SpriteMask.ts b/packages/core/src/2d/sprite/SpriteMask.ts index 141f352d35..c2e41586d5 100644 --- a/packages/core/src/2d/sprite/SpriteMask.ts +++ b/packages/core/src/2d/sprite/SpriteMask.ts @@ -302,7 +302,7 @@ export class SpriteMask extends Renderer implements ISpriteRenderer { const subChunk = this._subChunk; subRenderElement.set(this, material, subChunk.chunk.primitive, subChunk.subMesh, this.sprite.texture, subChunk); - subRenderElement.shaderPasses = material.shader.subShaders[0].passes; + subRenderElement.subShader = material.shader.subShaders[0]; subRenderElement.renderQueueFlags = RenderQueueFlags.All; renderElement.addSubRenderElement(subRenderElement); } diff --git a/packages/core/src/Engine.ts b/packages/core/src/Engine.ts index 55b0ea0b4c..bbf0b3d1fd 100644 --- a/packages/core/src/Engine.ts +++ b/packages/core/src/Engine.ts @@ -33,7 +33,8 @@ import { Shader } from "./shader/Shader"; import { ShaderMacro } from "./shader/ShaderMacro"; import { ShaderMacroCollection } from "./shader/ShaderMacroCollection"; import { ShaderPool } from "./shader/ShaderPool"; -import { ShaderProgramPool } from "./shader/ShaderProgramPool"; +import { MacroCachePool } from "./shader/MacroCachePool"; +import { ShaderProgram } from "./shader/ShaderProgram"; import { RenderState } from "./shader/state/RenderState"; import { Texture2D, TextureFormat } from "./texture"; import { UIUtils } from "./ui/UIUtils"; @@ -112,7 +113,7 @@ export class Engine extends EventDispatcher { /* @internal */ _renderCount: number = 0; /* @internal */ - _shaderProgramPools: ShaderProgramPool[] = []; + _shaderProgramPools: MacroCachePool[] = []; /** @internal */ _fontMap: Record = {}; /** @internal */ @@ -541,7 +542,7 @@ export class Engine extends EventDispatcher { /** * @internal */ - _getShaderProgramPool(index: number, trackPools?: ShaderProgramPool[]): ShaderProgramPool { + _getMacroCachePool(index: number, trackPools?: MacroCachePool[]): MacroCachePool { const shaderProgramPools = this._shaderProgramPools; let pool = shaderProgramPools[index]; if (!pool) { @@ -549,7 +550,7 @@ export class Engine extends EventDispatcher { if (length > shaderProgramPools.length) { shaderProgramPools.length = length; } - shaderProgramPools[index] = pool = new ShaderProgramPool(this); + shaderProgramPools[index] = pool = new MacroCachePool(this); trackPools?.push(pool); } return pool; diff --git a/packages/core/src/RenderPipeline/BasicRenderPipeline.ts b/packages/core/src/RenderPipeline/BasicRenderPipeline.ts index dee2231a0a..613f61d372 100644 --- a/packages/core/src/RenderPipeline/BasicRenderPipeline.ts +++ b/packages/core/src/RenderPipeline/BasicRenderPipeline.ts @@ -10,7 +10,7 @@ import { ScalableAmbientObscurancePass } from "../lighting/ambientOcclusion/Scal import { FinalPass } from "../postProcess"; import { Shader } from "../shader/Shader"; import { ShaderMacroCollection } from "../shader/ShaderMacroCollection"; -import { ShaderPass } from "../shader/ShaderPass"; +import { SubShader } from "../shader/SubShader"; import { RenderQueueType } from "../shader/enums/RenderQueueType"; import { RenderState } from "../shader/state/RenderState"; import { CascadedShadowCasterPass } from "../shadow/CascadedShadowCasterPass"; @@ -385,7 +385,7 @@ export class BasicRenderPipeline { for (let j = 0, m = replacementSubShaders.length; j < m; j++) { const subShader = replacementSubShaders[j]; if (subShader.getTagValue(replacementTag) === materialSubShader.getTagValue(replacementTag)) { - this.pushRenderElementByType(renderElement, subRenderElement, subShader.passes, renderStates); + this.pushRenderElementByType(renderElement, subRenderElement, subShader, renderStates); replacementSuccess = true; } } @@ -394,13 +394,13 @@ export class BasicRenderPipeline { !replacementSuccess && context.replacementFailureStrategy === ReplacementFailureStrategy.KeepOriginalShader ) { - this.pushRenderElementByType(renderElement, subRenderElement, materialSubShader.passes, renderStates); + this.pushRenderElementByType(renderElement, subRenderElement, materialSubShader, renderStates); } } else { - this.pushRenderElementByType(renderElement, subRenderElement, replacementSubShaders[0].passes, renderStates); + this.pushRenderElementByType(renderElement, subRenderElement, replacementSubShaders[0], renderStates); } } else { - this.pushRenderElementByType(renderElement, subRenderElement, materialSubShader.passes, renderStates); + this.pushRenderElementByType(renderElement, subRenderElement, materialSubShader, renderStates); } } } @@ -408,9 +408,10 @@ export class BasicRenderPipeline { private pushRenderElementByType( renderElement: RenderElement, subRenderElement: SubRenderElement, - shaderPasses: ReadonlyArray, + subShader: SubShader, renderStates: ReadonlyArray ): void { + const shaderPasses = subShader.passes; const cullingResults = this._cullingResults; for (let i = 0, n = shaderPasses.length; i < n; i++) { // Get render queue type @@ -428,7 +429,7 @@ export class BasicRenderPipeline { const flag = 1 << renderQueueType; - subRenderElement.shaderPasses = shaderPasses; + subRenderElement.subShader = subShader; subRenderElement.renderQueueFlags |= flag; if (renderElement.renderQueueFlags & flag) { diff --git a/packages/core/src/RenderPipeline/BatchUtils.ts b/packages/core/src/RenderPipeline/BatchUtils.ts index 387c951f93..d646aad3cc 100644 --- a/packages/core/src/RenderPipeline/BatchUtils.ts +++ b/packages/core/src/RenderPipeline/BatchUtils.ts @@ -9,7 +9,7 @@ export class BatchUtils { protected static _disableBatchTag: ShaderTagKey = ShaderTagKey.getByName("spriteDisableBatching"); static canBatchSprite(elementA: SubRenderElement, elementB: SubRenderElement): boolean { - if (elementB.shaderPasses[0].getTagValue(BatchUtils._disableBatchTag) === true) { + if (elementB.subShader.passes[0].getTagValue(BatchUtils._disableBatchTag) === true) { return false; } if (elementA.subChunk.chunk !== elementB.subChunk.chunk) { diff --git a/packages/core/src/RenderPipeline/BatcherManager.ts b/packages/core/src/RenderPipeline/BatcherManager.ts index 702a4a01e8..d4196ebe83 100644 --- a/packages/core/src/RenderPipeline/BatcherManager.ts +++ b/packages/core/src/RenderPipeline/BatcherManager.ts @@ -1,5 +1,6 @@ import { Engine } from "../Engine"; import { Renderer } from "../Renderer"; +import { InstanceDataPackerPool } from "./InstanceDataPackerPool"; import { PrimitiveChunkManager } from "./PrimitiveChunkManager"; import { RenderQueue } from "./RenderQueue"; import { SubRenderElement } from "./SubRenderElement"; @@ -11,9 +12,14 @@ export class BatcherManager { private _primitiveChunkManager2D: PrimitiveChunkManager; private _primitiveChunkManagerMask: PrimitiveChunkManager; private _primitiveChunkManagerUI: PrimitiveChunkManager; + private _instanceDataPackerPool: InstanceDataPackerPool; constructor(public engine: Engine) {} + get instanceDataPackerPool(): InstanceDataPackerPool { + return (this._instanceDataPackerPool ||= new InstanceDataPackerPool(this.engine)); + } + get primitiveChunkManager2D(): PrimitiveChunkManager { return (this._primitiveChunkManager2D ||= new PrimitiveChunkManager(this.engine)); } @@ -39,10 +45,22 @@ export class BatcherManager { this._primitiveChunkManagerUI.destroy(); this._primitiveChunkManagerUI = null; } + if (this._instanceDataPackerPool) { + this._instanceDataPackerPool.destroy(); + this._instanceDataPackerPool = null; + } + } + + /** + * Reset instance batch pool at the start of each frame's batch phase. + */ + resetInstanceDataPackerPool(): void { + this._instanceDataPackerPool?.reset(); } batch(renderQueue: RenderQueue): void { const { elements, batchedSubElements, renderQueueType } = renderQueue; + let preSubElement: SubRenderElement; let preRenderer: Renderer; let preConstructor: Function; @@ -86,5 +104,6 @@ export class BatcherManager { this._primitiveChunkManager2D?.uploadBuffer(); this._primitiveChunkManagerMask?.uploadBuffer(); this._primitiveChunkManagerUI?.uploadBuffer(); + this._instanceDataPackerPool?.uploadBuffer(); } } diff --git a/packages/core/src/RenderPipeline/CullingResults.ts b/packages/core/src/RenderPipeline/CullingResults.ts index 673e0694d3..76c377c280 100644 --- a/packages/core/src/RenderPipeline/CullingResults.ts +++ b/packages/core/src/RenderPipeline/CullingResults.ts @@ -25,6 +25,7 @@ export class CullingResults { } sortBatch(batcherManager: BatcherManager) { + batcherManager.resetInstanceDataPackerPool(); this.opaqueQueue.sortBatch(RenderQueue.compareForOpaque, batcherManager); this.alphaTestQueue.sortBatch(RenderQueue.compareForOpaque, batcherManager); this.transparentQueue.sortBatch(RenderQueue.compareForTransparent, batcherManager); diff --git a/packages/core/src/RenderPipeline/InstanceDataPacker.ts b/packages/core/src/RenderPipeline/InstanceDataPacker.ts new file mode 100644 index 0000000000..28960c35b0 --- /dev/null +++ b/packages/core/src/RenderPipeline/InstanceDataPacker.ts @@ -0,0 +1,114 @@ +import { Engine } from "../Engine"; +import { Buffer } from "../graphic/Buffer"; +import { BufferBindFlag } from "../graphic/enums/BufferBindFlag"; +import { BufferUsage } from "../graphic/enums/BufferUsage"; +import { SetDataOptions } from "../graphic/enums/SetDataOptions"; +import { Renderer } from "../Renderer"; +import { ShaderMacroCollection } from "../shader/ShaderMacroCollection"; +import { ShaderBlockProperty } from "../shader/ShaderBlockProperty"; +import { ConstantBufferBindingPoint } from "../shader/enums/ConstantBufferBindingPoint"; +import { InstanceFieldInfo, ShaderFactory } from "../shaderlib/ShaderFactory"; + +/** + * @internal + * Packs per-instance renderer data (ModelMat, Layer, etc.) into a shared UBO for GPU instancing. + */ +export class InstanceDataPacker { + static readonly uniformBlockBindingMap: Record = { + [ShaderBlockProperty.getByName(ShaderFactory.RENDERER_INSTANCE_BLOCK_NAME)._uniqueId]: + ConstantBufferBindingPoint.RendererInstance + }; + + instanceCount = 0; + maxInstanceCount = Infinity; + instanceFields: InstanceFieldInfo[]; + compileMacros: ShaderMacroCollection = new ShaderMacroCollection(); + uboBuffer: Buffer; + + private _engine: Engine; + private _uboData: ArrayBuffer; + private _floatView: Float32Array; + private _intView: Int32Array; + private _renderers: Renderer[] = []; + private _structSize = 0; + + constructor(engine: Engine) { + this._engine = engine; + } + + /** + * Set UBO layout from instance fields. + */ + setLayout(instanceFields: InstanceFieldInfo[], maxInstanceCount: number, structSize: number): void { + this.instanceFields = instanceFields; + this.maxInstanceCount = maxInstanceCount; + this._structSize = structSize; + const totalBytes = maxInstanceCount * structSize; + // Only reallocate when buffer is too small + if (!this.uboBuffer || totalBytes > this.uboBuffer.byteLength) { + this._uboData = new ArrayBuffer(totalBytes); + this._floatView = new Float32Array(this._uboData); + this._intView = new Int32Array(this._uboData); + this.uboBuffer?.destroy(); + this.uboBuffer = new Buffer(this._engine, BufferBindFlag.ConstantBuffer, totalBytes, BufferUsage.Dynamic); + } + } + + addRenderer(renderer: Renderer): void { + this._renderers[this.instanceCount++] = renderer; + } + + prepare(): void { + const count = this.instanceCount; + if (count === 0) return; + + const fields = this.instanceFields; + if (!fields) return; + const structSize = this._structSize; + const elementsPerInstance = structSize / 4; + const floatView = this._floatView; + const intView = this._intView; + const renderers = this._renderers; + const modelMatId = Renderer._worldMatrixProperty._uniqueId; + + for (let i = 0; i < count; i++) { + const renderer = renderers[i]; + const propertyValueMap = renderer.shaderData._propertyValueMap; + const baseOffset = i * elementsPerInstance; + + for (let j = 0, n = fields.length; j < n; j++) { + const field = fields[j]; + const fieldOffset = baseOffset + field.offset / 4; + const propertyId = field.property._uniqueId; + + if (propertyId === modelMatId) { + // ModelMat must go through getter to trigger Transform lazy update + const e = renderer.entity.transform.worldMatrix.elements; + for (let k = 0; k < 16; k++) { + floatView[fieldOffset + k] = e[k]; + } + } else { + const value = propertyValueMap[propertyId]; + if (value != null) { + field.pack(field.useIntView ? intView : floatView, fieldOffset, value); + } + } + } + } + + const uploadElements = count * elementsPerInstance; + this.uboBuffer.setData(floatView, 0, 0, uploadElements, SetDataOptions.Discard); + } + + reset(): void { + this.instanceCount = 0; + } + + destroy(): void { + this.uboBuffer?.destroy(); + this._uboData = null; + this._floatView = null; + this._intView = null; + this._renderers = null; + } +} diff --git a/packages/core/src/RenderPipeline/InstanceDataPackerPool.ts b/packages/core/src/RenderPipeline/InstanceDataPackerPool.ts new file mode 100644 index 0000000000..70b34e09a1 --- /dev/null +++ b/packages/core/src/RenderPipeline/InstanceDataPackerPool.ts @@ -0,0 +1,44 @@ +import { Engine } from "../Engine"; +import { InstanceDataPacker } from "./InstanceDataPacker"; + +/** + * @internal + */ +export class InstanceDataPackerPool { + private _engine: Engine; + private _pool = new Array(); + private _poolIndex = 0; + + constructor(engine: Engine) { + this._engine = engine; + } + + getOrCreate(): InstanceDataPacker { + return this._pool[this._poolIndex++] ||= new InstanceDataPacker(this._engine); + } + + uploadBuffer(): void { + const pool = this._pool; + for (let i = 0, n = this._poolIndex; i < n; i++) { + const batch = pool[i]; + if (batch.instanceCount > 1) { + batch.prepare(); + } + } + } + + reset(): void { + const pool = this._pool; + for (let i = 0, n = this._poolIndex; i < n; i++) { + pool[i].reset(); + } + this._poolIndex = 0; + } + + destroy(): void { + const pool = this._pool; + for (let i = 0, n = pool.length; i < n; i++) { + pool[i].destroy(); + } + } +} diff --git a/packages/core/src/RenderPipeline/RenderQueue.ts b/packages/core/src/RenderPipeline/RenderQueue.ts index 3558679996..30ec6fc34d 100644 --- a/packages/core/src/RenderPipeline/RenderQueue.ts +++ b/packages/core/src/RenderPipeline/RenderQueue.ts @@ -2,8 +2,10 @@ import { SpriteMaskInteraction } from "../2d/enums/SpriteMaskInteraction"; import { BasicResources, RenderStateElementMap } from "../BasicResources"; import { Utils } from "../Utils"; import { RenderQueueType, Shader } from "../shader"; +import { ConstantBufferBindingPoint } from "../shader/enums/ConstantBufferBindingPoint"; import { ShaderMacroCollection } from "../shader/ShaderMacroCollection"; import { BatcherManager } from "./BatcherManager"; +import { InstanceDataPacker } from "./InstanceDataPacker"; import { ContextRendererUpdateFlag, RenderContext } from "./RenderContext"; import { RenderElement } from "./RenderElement"; import { SubRenderElement } from "./SubRenderElement"; @@ -60,19 +62,24 @@ export class RenderQueue { for (let i = 0; i < length; i++) { const subElement = batchedSubElements[i]; - const { component: renderer, batched, material } = subElement; - - // @todo: Can optimize update view projection matrix updated - if ( - this.rendererUpdateFlag & ContextRendererUpdateFlag.WorldViewMatrix || - renderer._batchedTransformShaderData != batched - ) { - // Update world matrix and view matrix and model matrix - renderer._updateTransformShaderData(context, false, batched); - renderer._batchedTransformShaderData = batched; - } else if (this.rendererUpdateFlag & ContextRendererUpdateFlag.ProjectionMatrix) { - // Only projection matrix need updated - renderer._updateTransformShaderData(context, true, batched); + const { component: renderer, material, instanceDataPacker } = subElement; + const isInstanced = !!instanceDataPacker; + + // Instancing: transform data is packed in UBO, skip per-renderer update + if (!isInstanced) { + // @todo: Can optimize update view projection matrix updated + const batched = subElement.batched; + if ( + this.rendererUpdateFlag & ContextRendererUpdateFlag.WorldViewMatrix || + renderer._batchedTransformShaderData != batched + ) { + // Update world matrix and view matrix and model matrix + renderer._updateTransformShaderData(context, false, batched); + renderer._batchedTransformShaderData = batched; + } else if (this.rendererUpdateFlag & ContextRendererUpdateFlag.ProjectionMatrix) { + // Only projection matrix need updated + renderer._updateTransformShaderData(context, true, batched); + } } const maskInteraction = renderer._maskInteraction; @@ -92,14 +99,20 @@ export class RenderQueue { maskManager.isStencilWritten(material) && (maskManager.hasStencilWritten = true); } - const compileMacros = Shader._compileMacros; - const { primitive, shaderPasses, shaderData: renderElementShaderData } = subElement; + const { primitive, shaderData: renderElementShaderData } = subElement; + const shaderPasses = subElement.subShader.passes; const { shaderData: rendererData, instanceId: rendererId } = renderer; const { shaderData: materialData, instanceId: materialId, renderStates } = material; - // Union render global macro and material self macro - ShaderMacroCollection.unionCollection(renderer._globalShaderMacro, materialData._macroCollection, compileMacros); - ShaderMacroCollection.unionCollection(compileMacros, engine._macroCollection, compileMacros); + let compileMacros: ShaderMacroCollection; + if (isInstanced) { + compileMacros = instanceDataPacker.compileMacros; + } else { + // Union render global macro and material self macro + compileMacros = Shader._compileMacros; + ShaderMacroCollection.unionCollection(renderer._globalShaderMacro, materialData._macroCollection, compileMacros); + ShaderMacroCollection.unionCollection(compileMacros, engine._macroCollection, compileMacros); + } for (let j = 0, m = shaderPasses.length; j < m; j++) { const shaderPass = shaderPasses[j]; @@ -126,7 +139,7 @@ export class RenderQueue { } } - const program = shaderPass._getShaderProgram(engine, compileMacros); + const program = shaderPass._getShaderProgram(engine, compileMacros, isInstanced ? instanceDataPacker.instanceFields : undefined); if (!program.isValid) { continue; } @@ -138,14 +151,18 @@ export class RenderQueue { program.groupingOtherUniformBlock(); program.uploadAll(program.sceneUniformBlock, sceneData); program.uploadAll(program.cameraUniformBlock, cameraData); - program.uploadAll(program.rendererUniformBlock, rendererData); + if (isInstanced) { + program._uploadRendererId = -1; + } else { + program.uploadAll(program.rendererUniformBlock, rendererData); + program._uploadRendererId = rendererId; + } program.uploadAll(program.materialUniformBlock, materialData); renderElementShaderData && program.uploadAll(program.renderElementUniformBlock, renderElementShaderData); // UnGroup textures should upload default value, texture uint maybe change by logic of texture bind. program.uploadUnGroupTextures(); program._uploadSceneId = sceneId; program._uploadCameraId = cameraId; - program._uploadRendererId = rendererId; program._uploadMaterialId = materialId; program._uploadRenderCount = renderCount; } else { @@ -163,11 +180,13 @@ export class RenderQueue { program.uploadTextures(program.cameraUniformBlock, cameraData); } - if (program._uploadRendererId !== rendererId) { - program.uploadAll(program.rendererUniformBlock, rendererData); - program._uploadRendererId = rendererId; - } else if (switchProgram) { - program.uploadTextures(program.rendererUniformBlock, rendererData); + if (!isInstanced) { + if (program._uploadRendererId !== rendererId) { + program.uploadAll(program.rendererUniformBlock, rendererData); + program._uploadRendererId = rendererId; + } else if (switchProgram) { + program.uploadTextures(program.rendererUniformBlock, rendererData); + } } if (program._uploadMaterialId !== materialId) { @@ -192,7 +211,18 @@ export class RenderQueue { material.shaderData, customStates ); - rhi.drawPrimitive(primitive, subElement.subPrimitive, program); + + if (isInstanced) { + if (instanceDataPacker.uboBuffer) { + program.bindUniformBlocks(InstanceDataPacker.uniformBlockBindingMap); + rhi.bindUniformBufferBase(ConstantBufferBindingPoint.RendererInstance, instanceDataPacker.uboBuffer._platformBuffer); + } + primitive.instanceCount = instanceDataPacker.instanceCount; + rhi.drawPrimitive(primitive, subElement.subPrimitive, program); + primitive.instanceCount = 0; + } else { + rhi.drawPrimitive(primitive, subElement.subPrimitive, program); + } } } diff --git a/packages/core/src/RenderPipeline/SubRenderElement.ts b/packages/core/src/RenderPipeline/SubRenderElement.ts index f8c86c114d..245d2a7dd4 100644 --- a/packages/core/src/RenderPipeline/SubRenderElement.ts +++ b/packages/core/src/RenderPipeline/SubRenderElement.ts @@ -1,10 +1,11 @@ import { Renderer } from "../Renderer"; import { Primitive, SubMesh } from "../graphic"; import { Material } from "../material"; -import { ShaderData, ShaderPass } from "../shader"; +import { ShaderData, SubShader } from "../shader"; import { Texture2D } from "../texture"; import { IPoolElement } from "../utils/ObjectPool"; import { RenderQueueFlags } from "./BasicRenderPipeline"; +import { InstanceDataPacker } from "./InstanceDataPacker"; import { SubPrimitiveChunk } from "./SubPrimitiveChunk"; export class SubRenderElement implements IPoolElement { @@ -12,10 +13,11 @@ export class SubRenderElement implements IPoolElement { primitive: Primitive; material: Material; subPrimitive: SubMesh; - shaderPasses: ReadonlyArray; + subShader: SubShader; shaderData?: ShaderData; batched: boolean; renderQueueFlags: RenderQueueFlags; + instanceDataPacker: InstanceDataPacker; // @todo: maybe should remove later texture?: Texture2D; @@ -42,8 +44,9 @@ export class SubRenderElement implements IPoolElement { this.material = null; this.primitive = null; this.subPrimitive = null; - this.shaderPasses = null; + this.subShader = null; this.shaderData && (this.shaderData = null); + this.instanceDataPacker = null; this.texture && (this.texture = null); this.subChunk && (this.subChunk = null); diff --git a/packages/core/src/Renderer.ts b/packages/core/src/Renderer.ts index ab9f743c0c..4544e91698 100644 --- a/packages/core/src/Renderer.ts +++ b/packages/core/src/Renderer.ts @@ -23,14 +23,17 @@ import { ShaderDataGroup } from "./shader/enums/ShaderDataGroup"; export class Renderer extends Component { private static _tempVector0 = new Vector3(); + /** @internal */ + static _worldMatrixProperty = ShaderProperty.getByName("renderer_ModelMat"); + /** @internal */ + static _rendererLayerProperty = ShaderProperty.getByName("renderer_Layer"); + private static _receiveShadowMacro = ShaderMacro.getByName("RENDERER_IS_RECEIVE_SHADOWS"); private static _localMatrixProperty = ShaderProperty.getByName("renderer_LocalMat"); - private static _worldMatrixProperty = ShaderProperty.getByName("renderer_ModelMat"); private static _mvMatrixProperty = ShaderProperty.getByName("renderer_MVMat"); private static _mvpMatrixProperty = ShaderProperty.getByName("renderer_MVPMat"); private static _mvInvMatrixProperty = ShaderProperty.getByName("renderer_MVInvMat"); private static _normalMatrixProperty = ShaderProperty.getByName("renderer_NormalMat"); - private static _rendererLayerProperty = ShaderProperty.getByName("renderer_Layer"); /** @internal */ @ignoreClone diff --git a/packages/core/src/graphic/TransformFeedbackShader.ts b/packages/core/src/graphic/TransformFeedbackShader.ts index c200e41597..b3e61b9d48 100644 --- a/packages/core/src/graphic/TransformFeedbackShader.ts +++ b/packages/core/src/graphic/TransformFeedbackShader.ts @@ -28,7 +28,7 @@ export class TransformFeedbackShader { * Get or compile a shader program for the given engine and macro combination. */ getProgram(engine: Engine, macroCollection: ShaderMacroCollection): ShaderProgram | null { - const pool = engine._getShaderProgramPool(this._id); + const pool = engine._getMacroCachePool(this._id); let program = pool.get(macroCollection); if (program) return program; diff --git a/packages/core/src/graphic/enums/BufferBindFlag.ts b/packages/core/src/graphic/enums/BufferBindFlag.ts index 152b9d2e54..4616d1eaff 100644 --- a/packages/core/src/graphic/enums/BufferBindFlag.ts +++ b/packages/core/src/graphic/enums/BufferBindFlag.ts @@ -5,5 +5,7 @@ export enum BufferBindFlag { /** Vertex buffer binding flag. */ VertexBuffer, /** Index buffer binding flag. */ - IndexBuffer + IndexBuffer, + /** Constant buffer binding flag (WebGL2 only). */ + ConstantBuffer } diff --git a/packages/core/src/mesh/MeshRenderer.ts b/packages/core/src/mesh/MeshRenderer.ts index e08e51ac4c..26cd10a84d 100644 --- a/packages/core/src/mesh/MeshRenderer.ts +++ b/packages/core/src/mesh/MeshRenderer.ts @@ -1,11 +1,14 @@ import { BoundingBox } from "@galacean/engine-math"; import { Entity } from "../Entity"; import { RenderContext } from "../RenderPipeline/RenderContext"; +import { SubRenderElement } from "../RenderPipeline/SubRenderElement"; import { Renderer, RendererUpdateFlags } from "../Renderer"; import { Logger } from "../base/Logger"; import { ignoreClone } from "../clone/CloneManager"; import { Mesh, MeshModifyFlags } from "../graphic/Mesh"; import { ShaderMacro } from "../shader/ShaderMacro"; +import { ShaderMacroCollection } from "../shader/ShaderMacroCollection"; + /** * MeshRenderer Component. @@ -170,6 +173,49 @@ export class MeshRenderer extends Renderer { context.camera._renderPipeline.pushRenderElement(context, renderElement); } + + /** + * @internal + */ + override _canBatch(elementA: SubRenderElement, elementB: SubRenderElement): boolean { + if (!this._engine._hardwareRenderer.isWebGL2) return false; + + const batch = elementA.instanceDataPacker; + return ( + (!batch || batch.instanceCount < batch.maxInstanceCount) && + elementA.primitive === elementB.primitive && + elementA.subPrimitive === elementB.subPrimitive && + elementA.material === elementB.material && + this._isFrontFaceInvert() === (elementB.component)._isFrontFaceInvert() + ); + } + + /** + * @internal + */ + override _batch(elementA: SubRenderElement, elementB?: SubRenderElement): void { + if (!elementB) return; + + let batch = elementA.instanceDataPacker; + if (!batch) { + const engine = this._engine; + batch = engine._batcherManager.instanceDataPackerPool.getOrCreate(); + const compileMacros = batch.compileMacros; + const materialData = elementA.material.shaderData; + ShaderMacroCollection.unionCollection(this._globalShaderMacro, materialData._macroCollection, compileMacros); + ShaderMacroCollection.unionCollection(compileMacros, engine._macroCollection, compileMacros); + compileMacros.enable(ShaderMacro._gpuInstanceMacro); + + const layout = elementA.subShader._getInstanceLayout(engine, compileMacros); + if (layout) { + batch.setLayout(layout.instanceFields, layout.instanceMaxCount, layout.structSize); + } + batch.addRenderer(elementA.component); + elementA.instanceDataPacker = batch; + } + batch.addRenderer(elementB.component); + } + private _setMesh(mesh: Mesh): void { const lastMesh = this._mesh; if (lastMesh) { diff --git a/packages/core/src/mesh/SkinnedMeshRenderer.ts b/packages/core/src/mesh/SkinnedMeshRenderer.ts index d8e83e16bb..e320a8d23b 100644 --- a/packages/core/src/mesh/SkinnedMeshRenderer.ts +++ b/packages/core/src/mesh/SkinnedMeshRenderer.ts @@ -1,6 +1,7 @@ import { BoundingBox, Vector2 } from "@galacean/engine-math"; import { Entity } from "../Entity"; import { RenderContext } from "../RenderPipeline/RenderContext"; +import { SubRenderElement } from "../RenderPipeline/SubRenderElement"; import { RendererUpdateFlags } from "../Renderer"; import { Logger } from "../base/Logger"; import { deepClone, ignoreClone } from "../clone/CloneManager"; @@ -120,6 +121,13 @@ export class SkinnedMeshRenderer extends MeshRenderer { localBounds.max._onValueChanged = this._onLocalBoundsChanged; } + /** + * @internal + */ + override _canBatch(_elementA: SubRenderElement, _elementB: SubRenderElement): boolean { + return false; + } + /** * @internal */ diff --git a/packages/core/src/shader/MacroCachePool.ts b/packages/core/src/shader/MacroCachePool.ts new file mode 100644 index 0000000000..ed3747a018 --- /dev/null +++ b/packages/core/src/shader/MacroCachePool.ts @@ -0,0 +1,99 @@ +import { Engine } from "../Engine"; +import { ShaderMacroCollection } from "./ShaderMacroCollection"; + +type Tree = { + [key: number]: Tree | T; +}; + +/** + * Cache pool keyed by ShaderMacroCollection bitmask. + * @internal + */ +export class MacroCachePool { + engine: Engine; + + private _cacheHierarchyDepth: number = 1; + private _cacheMap: Tree = Object.create(null); + private _lastQueryMap: Record; + private _lastQueryKey: number; + + constructor(engine?: Engine) { + this.engine = engine; + } + + get(macros: ShaderMacroCollection): T | null { + let cacheMap = this._cacheMap; + const maskLength = macros._length; + const cacheHierarchyDepth = this._cacheHierarchyDepth; + if (maskLength > cacheHierarchyDepth) { + this._resizeCacheMapHierarchy(cacheMap, 0, cacheHierarchyDepth, maskLength - cacheHierarchyDepth); + this._cacheHierarchyDepth = maskLength; + } + + const mask = macros._mask; + const endIndex = macros._length - 1; + const maxEndIndex = this._cacheHierarchyDepth - 1; + for (let i = 0; i < maxEndIndex; i++) { + const subMask = endIndex < i ? 0 : mask[i]; + let subCache = >cacheMap[subMask]; + subCache || (cacheMap[subMask] = subCache = Object.create(null)); + cacheMap = subCache; + } + + const cacheKey = endIndex < maxEndIndex ? 0 : mask[maxEndIndex]; + const value = (>cacheMap)[cacheKey]; + if (!value) { + this._lastQueryKey = cacheKey; + this._lastQueryMap = >cacheMap; + } + return value; + } + + cache(value: T): void { + this._lastQueryMap[this._lastQueryKey] = value; + } + + clear(callback?: (value: T) => void): void { + if (callback) { + this._recursiveForEach(0, this._cacheMap, callback); + } + this._cacheMap = Object.create(null); + this._cacheHierarchyDepth = 1; + } + + private _recursiveForEach(hierarchy: number, cacheMap: Tree, callback: (value: T) => void): void { + if (hierarchy === this._cacheHierarchyDepth - 1) { + for (let k in cacheMap) { + callback(cacheMap[k]); + } + return; + } + ++hierarchy; + for (let k in cacheMap) { + this._recursiveForEach(hierarchy, >cacheMap[k], callback); + } + } + + private _resizeCacheMapHierarchy( + cacheMap: Tree, + hierarchy: number, + currentHierarchy: number, + increaseHierarchy: number + ): void { + if (hierarchy == currentHierarchy - 1) { + for (let k in cacheMap) { + const value = cacheMap[k]; + let subCacheMap = cacheMap; + for (let i = 0; i < increaseHierarchy; i++) { + subCacheMap[i == 0 ? k : 0] = subCacheMap = Object.create(null); + } + subCacheMap[0] = value; + } + } else { + hierarchy++; + for (let k in cacheMap) { + this._resizeCacheMapHierarchy(>cacheMap[k], hierarchy, currentHierarchy, increaseHierarchy); + } + } + } +} diff --git a/packages/core/src/shader/Shader.ts b/packages/core/src/shader/Shader.ts index f824462eb2..7d8c599f17 100644 --- a/packages/core/src/shader/Shader.ts +++ b/packages/core/src/shader/Shader.ts @@ -239,9 +239,9 @@ export class Shader implements IReferable { const pass = passes[j]; const passShaderProgramPools = pass._shaderProgramPools; for (let k = passShaderProgramPools.length - 1; k >= 0; k--) { - const shaderProgramPool = passShaderProgramPools[k]; - if (shaderProgramPool.engine !== engine) continue; - shaderProgramPool._destroy(); + const pool = passShaderProgramPools[k]; + if (pool.engine !== engine) continue; + pool.clear((program) => program.destroy()); passShaderProgramPools.splice(k, 1); } } @@ -438,7 +438,9 @@ export class Shader implements IReferable { const subShaders = this._subShaders; for (let i = 0, n = subShaders.length; i < n; i++) { - const passes = subShaders[i].passes; + const subShader = subShaders[i]; + subShader._destroy(); + const passes = subShader.passes; for (let j = 0, m = passes.length; j < m; j++) { passes[j]._destroy(); } diff --git a/packages/core/src/shader/ShaderBlockProperty.ts b/packages/core/src/shader/ShaderBlockProperty.ts new file mode 100644 index 0000000000..cbfb28f6a5 --- /dev/null +++ b/packages/core/src/shader/ShaderBlockProperty.ts @@ -0,0 +1,28 @@ +/** + * @internal + * Shader block property, used to identify uniform blocks by id. + */ +export class ShaderBlockProperty { + private static _counter = 0; + private static _nameMap: Record = Object.create(null); + + /** + * Get shader block property by name. + * @param name - Name of the uniform block + * @returns Shader block property + */ + static getByName(name: string): ShaderBlockProperty { + const nameMap = ShaderBlockProperty._nameMap; + return nameMap[name] ?? (nameMap[name] = new ShaderBlockProperty(name)); + } + + /** Uniform block name. */ + readonly name: string; + /** @internal */ + readonly _uniqueId: number; + + private constructor(name: string) { + this.name = name; + this._uniqueId = ShaderBlockProperty._counter++; + } +} diff --git a/packages/core/src/shader/ShaderMacro.ts b/packages/core/src/shader/ShaderMacro.ts index 59507aaced..9fafbe3ec7 100644 --- a/packages/core/src/shader/ShaderMacro.ts +++ b/packages/core/src/shader/ShaderMacro.ts @@ -8,6 +8,8 @@ export class ShaderMacro { static _macroMaskMap: ShaderMacro[][] = []; /** @internal */ static _macroNameIdMap: Record = Object.create(null); + /** @internal */ + static _gpuInstanceMacro = ShaderMacro.getByName("RENDERER_GPU_INSTANCE"); private static _macroNameCounter: number = 0; private static _macroCounter: number = 0; diff --git a/packages/core/src/shader/ShaderPass.ts b/packages/core/src/shader/ShaderPass.ts index 3473926037..4c4cccc488 100644 --- a/packages/core/src/shader/ShaderPass.ts +++ b/packages/core/src/shader/ShaderPass.ts @@ -2,12 +2,13 @@ import type { ShaderInstruction } from "@galacean/engine-design"; import { Engine } from "../Engine"; import { PipelineStage } from "../RenderPipeline/enums/PipelineStage"; import { GLCapabilityType } from "../base/Constant"; -import { ShaderFactory } from "../shaderlib"; +import { ShaderFactory, InstanceFieldInfo } from "../shaderlib/ShaderFactory"; +import { resolveIfdef } from "../shaderlib/GLSLIfdefResolver"; import { ShaderMacro } from "./ShaderMacro"; import { ShaderMacroCollection } from "./ShaderMacroCollection"; import { ShaderPart } from "./ShaderPart"; +import { MacroCachePool } from "./MacroCachePool"; import { ShaderProgram } from "./ShaderProgram"; -import { ShaderProgramPool } from "./ShaderProgramPool"; import { ShaderProperty } from "./ShaderProperty"; import { ShaderLanguage } from "./enums/ShaderLanguage"; import { ShaderMacroProcessor } from "./ShaderMacroProcessor"; @@ -32,6 +33,7 @@ export class ShaderPass extends ShaderPart { /** @internal */ static _shaderRootPath = "shaders://root/"; + /** * @internal */ @@ -53,7 +55,7 @@ export class ShaderPass extends ShaderPart { /** @internal */ _renderStateDataMap: Record = {}; /** @internal */ - _shaderProgramPools: ShaderProgramPool[] = []; + _shaderProgramPools: MacroCachePool[] = []; private _vertexSource?: string; private _fragmentSource?: string; @@ -61,6 +63,27 @@ export class ShaderPass extends ShaderPart { private static _shaderMacroList: ShaderMacro[] = []; private static _macroMap: Map = new Map(); + private static _buildMacroMap(engine: Engine, macroCollection: ShaderMacroCollection): Map { + const rhi = engine._hardwareRenderer; + const shaderMacroList = ShaderPass._shaderMacroList; + shaderMacroList.length = 0; + ShaderMacro._getMacrosElements(macroCollection, shaderMacroList); + shaderMacroList.push(ShaderMacro.getByName(rhi.isWebGL2 ? "GRAPHICS_API_WEBGL2" : "GRAPHICS_API_WEBGL1")); + if (rhi.canIUse(GLCapabilityType.shaderTextureLod)) { + shaderMacroList.push(ShaderMacro.getByName("HAS_TEX_LOD")); + } + if (rhi.canIUse(GLCapabilityType.standardDerivatives)) { + shaderMacroList.push(ShaderMacro.getByName("HAS_DERIVATIVES")); + } + const macroMap = ShaderPass._macroMap; + macroMap.clear(); + for (let i = 0, n = shaderMacroList.length; i < n; i++) { + const macro = shaderMacroList[i]; + macroMap.set(macro.name, macro.value ?? ""); + } + return macroMap; + } + /** * Create a shader pass. * @param name - Shader pass name @@ -144,74 +167,102 @@ export class ShaderPass extends ShaderPart { /** * @internal */ - _getShaderProgram(engine: Engine, macroCollection: ShaderMacroCollection): ShaderProgram { - const shaderProgramPool = engine._getShaderProgramPool(this._shaderPassId, this._shaderProgramPools); + _getShaderProgram(engine: Engine, macroCollection: ShaderMacroCollection, instanceFields?: InstanceFieldInfo[]): ShaderProgram { + const shaderProgramPool = engine._getMacroCachePool(this._shaderPassId, this._shaderProgramPools); let shaderProgram = shaderProgramPool.get(macroCollection); if (shaderProgram) { return shaderProgram; } - shaderProgram = this._getCanonicalShaderProgram(engine, macroCollection); + shaderProgram = this._getCanonicalShaderProgram(engine, macroCollection, instanceFields); shaderProgramPool.cache(shaderProgram); return shaderProgram; } + /** + * @internal + * Scan renderer-group uniforms from this pass into fieldMap, without GPU compilation. + */ + _scanInstanceFields(engine: Engine, macroCollection: ShaderMacroCollection, fieldMap: Record): boolean { + let vertexSource: string; + let fragmentSource: string; + + if (this._platformTarget != undefined) { + const macroMap = ShaderPass._buildMacroMap(engine, macroCollection); + vertexSource = ShaderMacroProcessor.evaluate(this._vertexShaderInstructions, macroMap); + fragmentSource = ShaderMacroProcessor.evaluate(this._fragmentShaderInstructions, macroMap); + } else { + vertexSource = ShaderFactory.parseIncludes(this._vertexSource); + fragmentSource = ShaderFactory.parseIncludes(this._fragmentSource); + + const macroMap = ShaderPass._buildMacroMap(engine, macroCollection); + vertexSource = resolveIfdef(vertexSource, macroMap); + fragmentSource = resolveIfdef(fragmentSource, macroMap); + } + + const a = ShaderFactory._scanInstanceUniforms(vertexSource, fieldMap); + const b = ShaderFactory._scanInstanceUniforms(fragmentSource, fieldMap); + return a || b; + } + /** * @internal */ _destroy(): void { const shaderProgramPools = this._shaderProgramPools; for (let i = 0, n = shaderProgramPools.length; i < n; i++) { - const shaderProgramPool = shaderProgramPools[i]; - shaderProgramPool._destroy(); - delete shaderProgramPool.engine._shaderProgramPools[this._shaderPassId]; + const pool = shaderProgramPools[i]; + pool.clear((program) => program.destroy()); + delete pool.engine._shaderProgramPools[this._shaderPassId]; } - // Clear array storing multiple engine shader program pools shaderProgramPools.length = 0; } - private _getCanonicalShaderProgram(engine: Engine, macroCollection: ShaderMacroCollection): ShaderProgram { + private _getCanonicalShaderProgram( + engine: Engine, + macroCollection: ShaderMacroCollection, + instanceFields?: InstanceFieldInfo[] + ): ShaderProgram { + const isGpuInstance = macroCollection.isEnable(ShaderMacro._gpuInstanceMacro); const { vertexSource, fragmentSource } = this._platformTarget != undefined - ? this._compileShaderLabSource(engine, macroCollection) - : this._compilePlatformSource(engine, macroCollection); + ? this._compileShaderLabSource(engine, macroCollection, isGpuInstance, instanceFields) + : this._compilePlatformSource(engine, macroCollection, isGpuInstance, instanceFields); return new ShaderProgram(engine, vertexSource, fragmentSource); } private _compilePlatformSource( engine: Engine, - macroCollection: ShaderMacroCollection - ): { vertexSource: string; fragmentSource: string } { - return ShaderFactory.compilePlatformSource(engine, macroCollection, this._vertexSource, this._fragmentSource); + macroCollection: ShaderMacroCollection, + isGpuInstance: boolean, + instanceFields?: InstanceFieldInfo[] + ): { vertexSource: string; fragmentSource: string; instanceFields: InstanceFieldInfo[]; instanceMaxCount: number } { + return ShaderFactory.compilePlatformSource(engine, macroCollection, this._vertexSource, this._fragmentSource, isGpuInstance, instanceFields); } private _compileShaderLabSource( engine: Engine, - macroCollection: ShaderMacroCollection - ): { vertexSource: string; fragmentSource: string } { + macroCollection: ShaderMacroCollection, + isGpuInstance: boolean, + instanceFields?: InstanceFieldInfo[] + ): { vertexSource: string; fragmentSource: string; instanceFields: InstanceFieldInfo[]; instanceMaxCount: number } { const isWebGL2: boolean = engine._hardwareRenderer.isWebGL2; - const shaderMacroList = ShaderPass._shaderMacroList; - shaderMacroList.length = 0; - ShaderMacro._getMacrosElements(macroCollection, shaderMacroList); - shaderMacroList.push(ShaderMacro.getByName(isWebGL2 ? "GRAPHICS_API_WEBGL2" : "GRAPHICS_API_WEBGL1")); - if (engine._hardwareRenderer.canIUse(GLCapabilityType.shaderTextureLod)) { - shaderMacroList.push(ShaderMacro.getByName("HAS_TEX_LOD")); - } - if (engine._hardwareRenderer.canIUse(GLCapabilityType.standardDerivatives)) { - shaderMacroList.push(ShaderMacro.getByName("HAS_DERIVATIVES")); - } - - const macroMap = ShaderPass._macroMap; - macroMap.clear(); - for (let i = 0, n = shaderMacroList.length; i < n; i++) { - const macro = shaderMacroList[i]; - macroMap.set(macro.name, macro.value ?? ""); - } + const macroMap = ShaderPass._buildMacroMap(engine, macroCollection); let vertexSource = ShaderMacroProcessor.evaluate(this._vertexShaderInstructions, macroMap); let fragmentSource = ShaderMacroProcessor.evaluate(this._fragmentShaderInstructions, macroMap); + let injectedInstanceFields: InstanceFieldInfo[] = null; + let injectedInstanceMaxCount = 0; + if (isGpuInstance) { + const injected = ShaderFactory._injectInstanceUBO(engine, vertexSource, fragmentSource, instanceFields); + vertexSource = injected.vertexSource; + fragmentSource = injected.fragmentSource; + injectedInstanceFields = injected.instanceFields; + injectedInstanceMaxCount = injected.instanceMaxCount; + } + if (isWebGL2 && this._platformTarget === ShaderLanguage.GLSLES100) { vertexSource = ShaderFactory.convertTo300(vertexSource); fragmentSource = ShaderFactory.convertTo300(fragmentSource, true); @@ -227,7 +278,9 @@ export class ShaderPass extends ShaderPart { ${isWebGL2 ? "" : ShaderFactory._shaderExtension} ${precisionStr} ${fragmentSource} - ` + `, + instanceFields: injectedInstanceFields, + instanceMaxCount: injectedInstanceMaxCount }; } } diff --git a/packages/core/src/shader/ShaderProgram.ts b/packages/core/src/shader/ShaderProgram.ts index 22151e5f77..622b1dfc3f 100644 --- a/packages/core/src/shader/ShaderProgram.ts +++ b/packages/core/src/shader/ShaderProgram.ts @@ -7,6 +7,7 @@ import { ShaderData } from "./ShaderData"; import { ShaderProperty } from "./ShaderProperty"; import { ShaderUniform } from "./ShaderUniform"; import { ShaderUniformBlock } from "./ShaderUniformBlock"; +import { ShaderBlockProperty } from "./ShaderBlockProperty"; import { ShaderDataGroup } from "./enums/ShaderDataGroup"; /** @@ -32,6 +33,7 @@ export class ShaderProgram { .join("\n"); } + id: number; readonly sceneUniformBlock: ShaderUniformBlock = new ShaderUniformBlock(); @@ -53,6 +55,7 @@ export class ShaderProgram { _uploadMaterialId: number = -1; attributeLocation: Record = Object.create(null); + uniformBlockIds: number[] = []; // @todo: move to RHI. private _isValid: boolean; @@ -476,6 +479,30 @@ export class ShaderProgram { attributeInfos.forEach(({ name }) => { this.attributeLocation[name] = gl.getAttribLocation(program, name); }); + + // Record uniform block indices (WebGL2 only) + if (this._engine._hardwareRenderer.isWebGL2) { + const gl2 = gl; + const blockCount = gl2.getProgramParameter(program, gl2.ACTIVE_UNIFORM_BLOCKS) ?? 0; + for (let i = 0; i < blockCount; i++) { + this.uniformBlockIds[i] = ShaderBlockProperty.getByName(gl2.getActiveUniformBlockName(program, i))._uniqueId; + } + } + } + + /** + * Bind uniform blocks to the specified binding points. + * @param bindingMap - Map of ShaderBlockProperty._uniqueId to binding point + */ + bindUniformBlocks(bindingMap: Record): void { + const gl = this._gl; + const ids = this.uniformBlockIds; + for (let i = 0, n = ids.length; i < n; i++) { + const bindingPoint = bindingMap[ids[i]]; + if (bindingPoint !== undefined) { + gl.uniformBlockBinding(this._glProgram, i, bindingPoint); + } + } } private _getUniformInfos(): WebGLActiveInfo[] { diff --git a/packages/core/src/shader/ShaderProgramPool.ts b/packages/core/src/shader/ShaderProgramPool.ts deleted file mode 100644 index 3b43a07afa..0000000000 --- a/packages/core/src/shader/ShaderProgramPool.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { Engine } from "../Engine"; -import { ShaderMacroCollection } from "./ShaderMacroCollection"; -import { ShaderProgram } from "./ShaderProgram"; - -/** - * Shader program pool. - * @internal - */ -export class ShaderProgramPool { - private _cacheHierarchyDepth: number = 1; - private _cacheMap: Tree = Object.create(null); - private _lastQueryMap: Record; - private _lastQueryKey: number; - - constructor(public engine: Engine) {} - - /** - * Get shader program by macro collection. - * @param macros - macro collection - * @returns shader program - */ - get(macros: ShaderMacroCollection): ShaderProgram | null { - let cacheMap = this._cacheMap; - const maskLength = macros._length; - const cacheHierarchyDepth = this._cacheHierarchyDepth; - if (maskLength > cacheHierarchyDepth) { - this._resizeCacheMapHierarchy(cacheMap, 0, cacheHierarchyDepth, maskLength - cacheHierarchyDepth); - this._cacheHierarchyDepth = maskLength; - } - - const mask = macros._mask; - const endIndex = macros._length - 1; - const maxEndIndex = this._cacheHierarchyDepth - 1; - for (let i = 0; i < maxEndIndex; i++) { - const subMask = endIndex < i ? 0 : mask[i]; - let subCacheShaders = >cacheMap[subMask]; - subCacheShaders || (cacheMap[subMask] = subCacheShaders = Object.create(null)); - cacheMap = subCacheShaders; - } - - const cacheKey = endIndex < maxEndIndex ? 0 : mask[maxEndIndex]; - const shader = (>cacheMap)[cacheKey]; - if (!shader) { - this._lastQueryKey = cacheKey; - this._lastQueryMap = >cacheMap; - } - return shader; - } - - /** - * Cache the shader program. - * - * @remarks - * The method must return an empty value after calling get() to run normally. - * - * @param shaderProgram - shader program - */ - cache(shaderProgram: ShaderProgram): void { - this._lastQueryMap[this._lastQueryKey] = shaderProgram; - } - - /** - * @internal - */ - _destroy(): void { - this._recursiveDestroy(0, this._cacheMap); - this._cacheMap = Object.create(null); - } - - private _recursiveDestroy(hierarchy: number, cacheMap: Tree): void { - if (hierarchy === this._cacheHierarchyDepth - 1) { - for (let k in cacheMap) { - (cacheMap[k]).destroy(); - } - return; - } - ++hierarchy; - for (let k in cacheMap) { - this._recursiveDestroy(hierarchy, >cacheMap[k]); - } - } - - private _resizeCacheMapHierarchy( - cacheMap: Tree, - hierarchy: number, - currentHierarchy: number, - increaseHierarchy: number - ): void { - // Only expand but not shrink - if (hierarchy == currentHierarchy - 1) { - for (let k in cacheMap) { - const shader = cacheMap[k]; - let subCacheMap = cacheMap; - for (let i = 0; i < increaseHierarchy; i++) { - subCacheMap[i == 0 ? k : 0] = subCacheMap = Object.create(null); - } - subCacheMap[0] = shader; - } - } else { - hierarchy++; - for (let k in cacheMap) { - this._resizeCacheMapHierarchy(>cacheMap[k], hierarchy, currentHierarchy, increaseHierarchy); - } - } - } -} - -type Tree = { - [key: number]: Tree | T; -}; diff --git a/packages/core/src/shader/SubShader.ts b/packages/core/src/shader/SubShader.ts index d020acc4e0..71775ab2e3 100644 --- a/packages/core/src/shader/SubShader.ts +++ b/packages/core/src/shader/SubShader.ts @@ -1,3 +1,7 @@ +import { Engine } from "../Engine"; +import { ShaderFactory, InstanceLayout } from "../shaderlib/ShaderFactory"; +import { MacroCachePool } from "./MacroCachePool"; +import { ShaderMacroCollection } from "./ShaderMacroCollection"; import { ShaderPart } from "./ShaderPart"; import { ShaderPass } from "./ShaderPass"; @@ -6,6 +10,7 @@ import { ShaderPass } from "./ShaderPass"; */ export class SubShader extends ShaderPart { private _passes: ShaderPass[]; + private _layoutCache: MacroCachePool = new MacroCachePool(); /** * Sub shader passes. @@ -32,4 +37,34 @@ export class SubShader extends ShaderPart { this.setTag(key, tags[key]); } } + + /** + * @internal + */ + _getInstanceLayout( + engine: Engine, + macroCollection: ShaderMacroCollection + ): InstanceLayout | null { + const cached = this._layoutCache.get(macroCollection); + if (cached) return cached; + + const passes = this._passes; + const fieldMap: Record = Object.create(null); + let hasField = false; + for (let i = 0, n = passes.length; i < n; i++) { + if (passes[i]._scanInstanceFields(engine, macroCollection, fieldMap)) hasField = true; + } + if (!hasField) return null; + + const result = ShaderFactory._buildLayout(engine, fieldMap); + this._layoutCache.cache(result); + return result; + } + + /** + * @internal + */ + _destroy(): void { + this._layoutCache.clear(); + } } diff --git a/packages/core/src/shader/enums/ConstantBufferBindingPoint.ts b/packages/core/src/shader/enums/ConstantBufferBindingPoint.ts new file mode 100644 index 0000000000..595d159924 --- /dev/null +++ b/packages/core/src/shader/enums/ConstantBufferBindingPoint.ts @@ -0,0 +1,7 @@ +/** + * @internal + * Constant buffer binding point allocation. + */ +export enum ConstantBufferBindingPoint { + RendererInstance = 0 +} diff --git a/packages/core/src/shaderlib/GLSLIfdefResolver.ts b/packages/core/src/shaderlib/GLSLIfdefResolver.ts new file mode 100644 index 0000000000..f3c45de00e --- /dev/null +++ b/packages/core/src/shaderlib/GLSLIfdefResolver.ts @@ -0,0 +1,41 @@ +/** + * @internal + * Simple #ifdef/#ifndef/#else/#endif resolver based on a macro set. + * Temporary utility for GLSL platform path — will be removed when GLSL path is deleted. + */ +export function resolveIfdef(source: string, macroSet: { has(name: string): boolean }): string { + const lines = source.split("\n"); + const result: string[] = []; + const stack: boolean[] = [true]; + const taken: boolean[] = [false]; + + for (let i = 0, n = lines.length; i < n; i++) { + const trimmed = lines[i].trimStart(); + + if (trimmed.startsWith("#ifdef ")) { + const macro = trimmed.substring(7).trim(); + const active = stack[stack.length - 1] && macroSet.has(macro); + stack.push(active); + taken.push(active); + } else if (trimmed.startsWith("#ifndef ")) { + const macro = trimmed.substring(8).trim(); + const active = stack[stack.length - 1] && !macroSet.has(macro); + stack.push(active); + taken.push(active); + } else if (trimmed.startsWith("#else")) { + const parentActive = stack.length > 1 ? stack[stack.length - 2] : true; + const active = parentActive && !taken[taken.length - 1]; + stack[stack.length - 1] = active; + if (active) taken[taken.length - 1] = true; + } else if (trimmed.startsWith("#endif")) { + if (stack.length > 1) { + stack.pop(); + taken.pop(); + } + } else if (stack[stack.length - 1]) { + result.push(lines[i]); + } + } + + return result.join("\n"); +} diff --git a/packages/core/src/shaderlib/ShaderFactory.ts b/packages/core/src/shaderlib/ShaderFactory.ts index e314e4409d..0649f7653d 100644 --- a/packages/core/src/shaderlib/ShaderFactory.ts +++ b/packages/core/src/shaderlib/ShaderFactory.ts @@ -1,11 +1,36 @@ +import { Matrix, Vector2, Vector3, Vector4 } from "@galacean/engine-math"; import { GLCapabilityType } from "../base/Constant"; import { Logger } from "../base/Logger"; import { Engine } from "../Engine"; +import { Renderer } from "../Renderer"; +import { ShaderDataGroup } from "../shader/enums/ShaderDataGroup"; import { ShaderMacro } from "../shader/ShaderMacro"; import { ShaderMacroCollection } from "../shader/ShaderMacroCollection"; +import { ShaderProperty } from "../shader/ShaderProperty"; import { ShaderLib } from "./ShaderLib"; +/** + * @internal + */ +export interface InstanceFieldInfo { + property: ShaderProperty; + type: string; + offset: number; + useIntView: boolean; + pack: (view: Float32Array | Int32Array, offset: number, value: any) => void; +} + +export interface InstanceLayout { + instanceFields: InstanceFieldInfo[]; + instanceMaxCount: number; + structSize: number; +} + export class ShaderFactory { + /** @internal */ + static readonly RENDERER_INSTANCE_BLOCK_NAME = "RendererInstanceData"; + + /** @internal */ static readonly _shaderExtension = [ "GL_EXT_shader_texture_lod", @@ -35,16 +60,19 @@ export class ShaderFactory { engine: Engine, macroCollection: ShaderMacroCollection, vertexSource: string, - fragmentSource: string - ): { vertexSource: string; fragmentSource: string } { - const isWebGL2 = engine._hardwareRenderer.isWebGL2; + fragmentSource: string, + isGpuInstance: boolean = false, + instanceFields?: InstanceFieldInfo[] + ): { vertexSource: string; fragmentSource: string; instanceFields: InstanceFieldInfo[]; instanceMaxCount: number } { + const rhi = engine._hardwareRenderer; + const isWebGL2 = rhi.isWebGL2; const shaderMacroList = new Array(); ShaderMacro._getMacrosElements(macroCollection, shaderMacroList); shaderMacroList.push(ShaderMacro.getByName(isWebGL2 ? "GRAPHICS_API_WEBGL2" : "GRAPHICS_API_WEBGL1")); - if (engine._hardwareRenderer.canIUse(GLCapabilityType.shaderTextureLod)) { + if (rhi.canIUse(GLCapabilityType.shaderTextureLod)) { shaderMacroList.push(ShaderMacro.getByName("HAS_TEX_LOD")); } - if (engine._hardwareRenderer.canIUse(GLCapabilityType.standardDerivatives)) { + if (rhi.canIUse(GLCapabilityType.standardDerivatives)) { shaderMacroList.push(ShaderMacro.getByName("HAS_DERIVATIVES")); } @@ -55,6 +83,16 @@ export class ShaderFactory { noIncludeVertex = macroStr + noIncludeVertex; noIncludeFrag = macroStr + noIncludeFrag; + let injectedInstanceFields: InstanceFieldInfo[] = null; + let injectedInstanceMaxCount = 0; + if (isGpuInstance) { + const injected = ShaderFactory._injectInstanceUBO(engine, noIncludeVertex, noIncludeFrag, instanceFields); + noIncludeVertex = injected.vertexSource; + noIncludeFrag = injected.fragmentSource; + injectedInstanceFields = injected.instanceFields; + injectedInstanceMaxCount = injected.instanceMaxCount; + } + if (isWebGL2) { noIncludeVertex = ShaderFactory.convertTo300(noIncludeVertex); noIncludeFrag = ShaderFactory.convertTo300(noIncludeFrag, true); @@ -73,10 +111,189 @@ export class ShaderFactory { return { vertexSource: `${versionStr}\nprecision highp float;\n${noIncludeVertex}`, - fragmentSource: `${versionStr}\n${isWebGL2 ? "" : ShaderFactory._shaderExtension}${precisionStr}${noIncludeFrag}` + fragmentSource: `${versionStr}\n${isWebGL2 ? "" : ShaderFactory._shaderExtension}${precisionStr}${noIncludeFrag}`, + instanceFields: injectedInstanceFields, + instanceMaxCount: injectedInstanceMaxCount }; } + /** Names that are macro-derived in instancing mode — remove but do not add to UBO. */ + private static _uboDerivedNames = new Set([ + "renderer_MVMat", "renderer_MVPMat", + "renderer_NormalMat", "renderer_MVInvMat", + "renderer_LocalMat" + ]); + + private static _uboUniformRegex = /^([ \t]*)uniform\s+(?:(?:lowp|mediump|highp)\s+)?(\w+)\s+(\w+)\s*;/gm; + + /** @internal std140 layout info by GLSL type string. */ + static _std140Map: Record = { + float: { size: 4, align: 4 }, + int: { size: 4, align: 4 }, + uint: { size: 4, align: 4 }, + vec2: { size: 8, align: 8 }, + ivec2: { size: 8, align: 8 }, + vec3: { size: 12, align: 16 }, + ivec3: { size: 12, align: 16 }, + vec4: { size: 16, align: 16 }, + ivec4: { size: 16, align: 16 }, + mat4: { size: 64, align: 16 } + }; + + /** Pack functions for writing typed values into ArrayBuffer views. */ + private static _packFuncMap: Record = { + float: (v, o, val: number) => { v[o] = val; }, + int: (v, o, val: number) => { v[o] = val; }, + uint: (v, o, val: number) => { v[o] = val; }, + vec2: (v, o, val: Vector2) => { v[o] = val.x; v[o + 1] = val.y; }, + ivec2: (v, o, val: Vector2) => { v[o] = val.x; v[o + 1] = val.y; }, + vec3: (v, o, val: Vector3) => { v[o] = val.x; v[o + 1] = val.y; v[o + 2] = val.z; }, + ivec3: (v, o, val: Vector3) => { v[o] = val.x; v[o + 1] = val.y; v[o + 2] = val.z; }, + vec4: (v, o, val: Vector4) => { v[o] = val.x; v[o + 1] = val.y; v[o + 2] = val.z; v[o + 3] = val.w; }, + ivec4: (v, o, val: Vector4) => { v[o] = val.x; v[o + 1] = val.y; v[o + 2] = val.z; v[o + 3] = val.w; }, + mat4: (v, o, val: Matrix) => { const e = val.elements; for (let k = 0; k < 16; k++) v[o + k] = e[k]; } + }; + + + /** + * @internal + * For GPU instancing shaders, scan VS and FS for `uniform ... renderer_*` declarations, + * compute their union, generate a full UBO struct + `#define` remapping, and inject into source. + * Also computes std140 layout and INSTANCE_MAX_COUNT from maxUBOSize. + */ + static _injectInstanceUBO( + engine: Engine, + vertexSource: string, + fragmentSource: string, + externalFields?: InstanceFieldInfo[] + ): { vertexSource: string; fragmentSource: string; instanceFields: InstanceFieldInfo[]; instanceMaxCount: number } { + + const fieldMap: Record = Object.create(null); + vertexSource = ShaderFactory._scanInstanceUniforms(vertexSource, fieldMap, true); + fragmentSource = ShaderFactory._scanInstanceUniforms(fragmentSource, fieldMap, true); + + let instanceFields: InstanceFieldInfo[]; + let instanceMaxCount: number; + if (externalFields) { + instanceFields = externalFields; + const maxUBOSize = engine._hardwareRenderer.getMaxUniformBlockSize(); + const last = externalFields[externalFields.length - 1]; + const lastSize = ShaderFactory._std140Map[last.type]?.size ?? 0; + const structSize = Math.ceil((last.offset + lastSize) / 16) * 16; + instanceMaxCount = Math.floor(maxUBOSize / structSize); + } else { + let hasField = false; + for (const _ in fieldMap) { hasField = true; break; } + if (!hasField) return { vertexSource, fragmentSource, instanceFields: null, instanceMaxCount: 0 }; + ({ instanceFields, instanceMaxCount } = ShaderFactory._buildLayout(engine, fieldMap)); + } + + // Generate UBO struct and #define remapping + const structFields = instanceFields.map((f) => ` ${f.type} ${f.property.name};`).join("\n"); + const defines = instanceFields.map((f) => `#define ${f.property.name} rendererData[gl_InstanceID].${f.property.name}`).join("\n"); + const derivedDefines = [ + "#define renderer_MVMat (camera_ViewMat * renderer_ModelMat)", + "#define renderer_MVPMat (camera_VPMat * renderer_ModelMat)", + "#define renderer_NormalMat transpose(inverse(mat3(renderer_ModelMat)))" + ].join("\n"); + + const uboBlock = + `#define INSTANCE_MAX_COUNT ${instanceMaxCount}\n` + + `layout(std140) uniform ${ShaderFactory.RENDERER_INSTANCE_BLOCK_NAME} {\n` + + ` struct {\n` + + `${structFields}\n` + + ` } rendererData[INSTANCE_MAX_COUNT];\n` + + `};\n` + + `${defines}\n` + + `${derivedDefines}\n`; + + vertexSource = ShaderFactory._insertUBOBlock(vertexSource, uboBlock); + fragmentSource = ShaderFactory._insertUBOBlock(fragmentSource, uboBlock); + + return { vertexSource, fragmentSource, instanceFields, instanceMaxCount }; + } + + /** + * @internal + * Scan source for renderer-group uniforms and collect into fieldMap. + * @param remove - If true, remove matched declarations from source. + */ + static _scanInstanceUniforms(source: string, fieldMap: Record, remove: true): string; + static _scanInstanceUniforms(source: string, fieldMap: Record): boolean; + static _scanInstanceUniforms(source: string, fieldMap: Record, remove?: boolean): string | boolean { + const derivedNames = ShaderFactory._uboDerivedNames; + let found = false; + const result = source.replace(ShaderFactory._uboUniformRegex, (match, _indent, type, name) => { + if (type.indexOf("sampler") !== -1) return match; + if (ShaderProperty._getShaderPropertyGroup(name) !== ShaderDataGroup.Renderer) return match; + if (derivedNames.has(name)) return remove ? "" : match; + fieldMap[ShaderProperty.getByName(name)._uniqueId] = type; + found = true; + return remove ? "" : match; + }); + return remove ? result : found; + } + + + /** @internal */ + static _buildLayout( + engine: Engine, + fieldMap: Record + ): InstanceLayout { + const maxUBOSize = engine._hardwareRenderer.getMaxUniformBlockSize(); + const std140Map = ShaderFactory._std140Map; + const instanceFields: InstanceFieldInfo[] = []; + let currentOffset = 0; + + const packFuncMap = ShaderFactory._packFuncMap; + const addField = (id: number): void => { + const type = fieldMap[id]; + const info = std140Map[type]; + if (!info) return; + currentOffset = Math.ceil(currentOffset / info.align) * info.align; + instanceFields.push({ + property: ShaderProperty._propertyIdMap[id], + type, + offset: currentOffset, + useIntView: type[0] === "i" || type[0] === "u", + pack: packFuncMap[type] + }); + currentOffset += info.size; + }; + + // Priority fields first + const modelMatId = Renderer._worldMatrixProperty._uniqueId; + const layerId = Renderer._rendererLayerProperty._uniqueId; + if (modelMatId in fieldMap) { addField(modelMatId); delete fieldMap[modelMatId]; } + if (layerId in fieldMap) { addField(layerId); delete fieldMap[layerId]; } + + // Remaining fields sorted by id + const keys: number[] = []; + for (const k in fieldMap) keys.push(+k); + keys.sort((a, b) => a - b); + for (let i = 0; i < keys.length; i++) addField(keys[i]); + + const structSize = Math.ceil(currentOffset / 16) * 16; + const instanceMaxCount = Math.floor(maxUBOSize / structSize); + + return { instanceFields, instanceMaxCount, structSize }; + } + + /** + * Insert a UBO block into source after the macro section. + */ + private static _insertUBOBlock(source: string, uboBlock: string): string { + const lines = source.split("\n"); + let insertIdx = 0; + for (let i = 0; i < lines.length; i++) { + if (lines[i].trimStart().startsWith("#define ")) { + insertIdx = i + 1; + } + } + lines.splice(insertIdx, 0, uboBlock); + return lines.join("\n"); + } + static registerInclude(includeName: string, includeSource: string) { if (ShaderLib[includeName]) { throw `The "${includeName}" shader include already exist`; @@ -111,7 +328,7 @@ export class ShaderFactory { * Convert lower GLSL version to GLSL 300 es. * @param shader - code * @param isFrag - Whether it is a fragment shader. - * */ + */ static convertTo300(shader: string, isFrag?: boolean) { shader = shader.replace(/\bvarying\b/g, isFrag ? "in" : "out"); shader = shader.replace(/\btexture(2D|Cube)\b/g, "texture"); diff --git a/packages/core/src/shaderlib/extra/shadow-map.vs.glsl b/packages/core/src/shaderlib/extra/shadow-map.vs.glsl index fae6a4b849..89d84226ed 100644 --- a/packages/core/src/shaderlib/extra/shadow-map.vs.glsl +++ b/packages/core/src/shaderlib/extra/shadow-map.vs.glsl @@ -26,7 +26,7 @@ void main() { #include #include #include - + vec4 positionWS = renderer_ModelMat * position; positionWS.xyz = applyShadowBias(positionWS.xyz); diff --git a/packages/core/src/shaderlib/light_frag_define.glsl b/packages/core/src/shaderlib/light_frag_define.glsl index 316bbba991..a977b28ca2 100644 --- a/packages/core/src/shaderlib/light_frag_define.glsl +++ b/packages/core/src/shaderlib/light_frag_define.glsl @@ -62,7 +62,6 @@ struct EnvMapLight { uniform EnvMapLight scene_EnvMapLight; -uniform ivec4 renderer_Layer; #ifdef SCENE_USE_SH uniform vec3 scene_EnvSH[9]; diff --git a/packages/core/src/shaderlib/normal_vert.glsl b/packages/core/src/shaderlib/normal_vert.glsl index 096a4595d1..906be70ef7 100644 --- a/packages/core/src/shaderlib/normal_vert.glsl +++ b/packages/core/src/shaderlib/normal_vert.glsl @@ -1,9 +1,10 @@ #ifndef MATERIAL_OMIT_NORMAL #ifdef RENDERER_HAS_NORMAL - v_normal = normalize( mat3(renderer_NormalMat) * normal ); + mat3 normalMat = mat3(renderer_NormalMat); + v_normal = normalize( normalMat * normal ); #if defined(RENDERER_HAS_TANGENT) && ( defined(MATERIAL_HAS_NORMALTEXTURE) || defined(MATERIAL_HAS_CLEAR_COAT_NORMAL_TEXTURE) || defined(MATERIAL_ENABLE_ANISOTROPY) ) - vec3 tangentW = normalize( mat3(renderer_NormalMat) * tangent.xyz ); + vec3 tangentW = normalize( normalMat * tangent.xyz ); vec3 bitangentW = cross( v_normal, tangentW ) * tangent.w; v_TBN = mat3( tangentW, bitangentW, v_normal ); diff --git a/packages/core/src/shaderlib/transform_declare.glsl b/packages/core/src/shaderlib/transform_declare.glsl index 00e1d5edba..131ca2b213 100644 --- a/packages/core/src/shaderlib/transform_declare.glsl +++ b/packages/core/src/shaderlib/transform_declare.glsl @@ -1,7 +1,9 @@ -uniform mat4 renderer_LocalMat; -uniform mat4 renderer_ModelMat; uniform mat4 camera_ViewMat; uniform mat4 camera_ProjMat; + +uniform mat4 renderer_LocalMat; +uniform mat4 renderer_ModelMat; +uniform ivec4 renderer_Layer; uniform mat4 renderer_MVMat; uniform mat4 renderer_MVPMat; uniform mat4 renderer_NormalMat; \ No newline at end of file diff --git a/packages/rhi-webgl/src/GLBuffer.ts b/packages/rhi-webgl/src/GLBuffer.ts index 87f635dba9..aea7b38842 100644 --- a/packages/rhi-webgl/src/GLBuffer.ts +++ b/packages/rhi-webgl/src/GLBuffer.ts @@ -21,7 +21,18 @@ export class GLBuffer implements IPlatformBuffer { const gl = rhi.gl; const glBuffer = gl.createBuffer(); const glBufferUsage = this._getGLBufferUsage(gl, bufferUsage); - const glBindTarget = type === BufferBindFlag.VertexBuffer ? gl.ARRAY_BUFFER : gl.ELEMENT_ARRAY_BUFFER; + let glBindTarget: number; + switch (type) { + case BufferBindFlag.VertexBuffer: + glBindTarget = gl.ARRAY_BUFFER; + break; + case BufferBindFlag.IndexBuffer: + glBindTarget = gl.ELEMENT_ARRAY_BUFFER; + break; + case BufferBindFlag.ConstantBuffer: + glBindTarget = (gl).UNIFORM_BUFFER; + break; + } this._gl = gl; this._glBuffer = glBuffer; this._glBufferUsage = glBufferUsage; diff --git a/packages/rhi-webgl/src/WebGLGraphicDevice.ts b/packages/rhi-webgl/src/WebGLGraphicDevice.ts index c937742f36..92f038b816 100644 --- a/packages/rhi-webgl/src/WebGLGraphicDevice.ts +++ b/packages/rhi-webgl/src/WebGLGraphicDevice.ts @@ -280,6 +280,25 @@ export class WebGLGraphicDevice implements IHardwareRenderer { return new GLTransformFeedbackPrimitive(this._gl); } + bindUniformBufferBase(bindingPoint: number, buffer: IPlatformBuffer | null): void { + const gl = this._gl; + gl.bindBufferBase(gl.UNIFORM_BUFFER, bindingPoint, buffer ? (buffer)._glBuffer : null); + } + + bindUniformBlock(program: WebGLProgram, blockName: string, bindingPoint: number): number { + const gl = this._gl; + const blockIndex = gl.getUniformBlockIndex(program, blockName); + if (blockIndex !== gl.INVALID_INDEX) { + gl.uniformBlockBinding(program, blockIndex, bindingPoint); + } + return blockIndex; + } + + getMaxUniformBlockSize(): number { + const gl = this._gl; + return gl.getParameter(gl.MAX_UNIFORM_BLOCK_SIZE); + } + /** * Enable GL_RASTERIZER_DISCARD (WebGL2 only). */ diff --git a/packages/shader/src/shaders/Light.glsl b/packages/shader/src/shaders/Light.glsl index 3101ae1cf1..ed463484d6 100644 --- a/packages/shader/src/shaders/Light.glsl +++ b/packages/shader/src/shaders/Light.glsl @@ -2,7 +2,6 @@ #define LIGHT_INCLUDED -ivec4 renderer_Layer; #ifndef GRAPHICS_API_WEBGL2 bool isBitSet(float value, float mask, float bitIndex){ return mod(floor(value / pow(2.0, bitIndex)), 2.0) == 1.0 && mod(floor(mask / pow(2.0, bitIndex)), 2.0) == 1.0; diff --git a/packages/shader/src/shaders/Transform.glsl b/packages/shader/src/shaders/Transform.glsl index b5d1a52a68..499d30d517 100644 --- a/packages/shader/src/shaders/Transform.glsl +++ b/packages/shader/src/shaders/Transform.glsl @@ -1,16 +1,18 @@ #ifndef TRANSFORM_INCLUDED #define TRANSFORM_INCLUDED -mat4 renderer_LocalMat; -mat4 renderer_ModelMat; mat4 camera_ViewMat; mat4 camera_ProjMat; -mat4 renderer_MVMat; -mat4 renderer_MVPMat; -mat4 renderer_NormalMat; vec3 camera_Position; -vec3 camera_Forward; +vec3 camera_Forward; vec4 camera_ProjectionParams; +mat4 renderer_LocalMat; +mat4 renderer_ModelMat; +mat4 renderer_MVMat; +mat4 renderer_MVPMat; +mat4 renderer_NormalMat; +ivec4 renderer_Layer; + #endif \ No newline at end of file diff --git a/packages/shader/src/shaders/shadingPBR/VertexPBR.glsl b/packages/shader/src/shaders/shadingPBR/VertexPBR.glsl index 134f2405f2..c9e964d6ec 100644 --- a/packages/shader/src/shaders/shadingPBR/VertexPBR.glsl +++ b/packages/shader/src/shaders/shadingPBR/VertexPBR.glsl @@ -69,10 +69,11 @@ VertexInputs getVertexInputs(Attributes attributes){ // TBN world space #ifdef RENDERER_HAS_NORMAL - inputs.normalWS = normalize( mat3(renderer_NormalMat) * normal ); + mat3 normalMat = mat3(renderer_NormalMat); + inputs.normalWS = normalize( normalMat * normal ); #ifdef RENDERER_HAS_TANGENT - vec3 tangentWS = normalize( mat3(renderer_NormalMat) * tangent.xyz ); + vec3 tangentWS = normalize( normalMat * tangent.xyz ); vec3 bitangentWS = cross( inputs.normalWS, tangentWS ) * tangent.w; inputs.tangentWS = tangentWS; @@ -85,7 +86,7 @@ VertexInputs getVertexInputs(Attributes attributes){ vec4 positionWS = renderer_ModelMat * position; inputs.positionWS = positionWS.xyz / positionWS.w; - #if SCENE_FOG_MODE != 0 + #if SCENE_FOG_MODE != 0 vec4 positionVS = renderer_MVMat * position; inputs.positionVS = positionVS.xyz / positionVS.w; #endif diff --git a/packages/ui/src/component/advanced/Image.ts b/packages/ui/src/component/advanced/Image.ts index b8ca6ffe55..d6806d726a 100644 --- a/packages/ui/src/component/advanced/Image.ts +++ b/packages/ui/src/component/advanced/Image.ts @@ -242,7 +242,7 @@ export class Image extends UIRenderer implements ISpriteRenderer { const subChunk = this._subChunk; subRenderElement.set(this, material, subChunk.chunk.primitive, subChunk.subMesh, this.sprite.texture, subChunk); if (canvas._realRenderMode === CanvasRenderMode.ScreenSpaceOverlay) { - subRenderElement.shaderPasses = material.shader.subShaders[0].passes; + subRenderElement.subShader = material.shader.subShaders[0]; subRenderElement.renderQueueFlags = RenderQueueFlags.All; } canvas._renderElement.addSubRenderElement(subRenderElement); diff --git a/packages/ui/src/component/advanced/Text.ts b/packages/ui/src/component/advanced/Text.ts index 0eb5edeb93..7f156b37bc 100644 --- a/packages/ui/src/component/advanced/Text.ts +++ b/packages/ui/src/component/advanced/Text.ts @@ -363,7 +363,7 @@ export class Text extends UIRenderer implements ITextRenderer { subRenderElement.shaderData ||= new ShaderData(ShaderDataGroup.RenderElement); subRenderElement.shaderData.setTexture(Text._textTextureProperty, texture); if (isOverlay) { - subRenderElement.shaderPasses = material.shader.subShaders[0].passes; + subRenderElement.subShader = material.shader.subShaders[0]; subRenderElement.renderQueueFlags = RenderQueueFlags.All; } renderElement.addSubRenderElement(subRenderElement); From 3279bb6fac1178cedd5a82f8a03c546d2ae76ca6 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Tue, 7 Apr 2026 19:19:21 +0800 Subject: [PATCH 02/91] fix: static init order for _gpuInstanceMacro and prettier formatting Move _gpuInstanceMacro after _macroMap declaration to fix static initialization order. Also apply prettier formatting fixes. --- .../RenderPipeline/InstanceDataPackerPool.ts | 2 +- .../core/src/RenderPipeline/RenderQueue.ts | 17 +++- packages/core/src/shader/ShaderMacro.ts | 5 +- packages/core/src/shader/ShaderPass.ts | 22 ++++- packages/core/src/shader/SubShader.ts | 5 +- packages/core/src/shaderlib/ShaderFactory.ts | 87 ++++++++++++++----- 6 files changed, 100 insertions(+), 38 deletions(-) diff --git a/packages/core/src/RenderPipeline/InstanceDataPackerPool.ts b/packages/core/src/RenderPipeline/InstanceDataPackerPool.ts index 70b34e09a1..654c1a2bd7 100644 --- a/packages/core/src/RenderPipeline/InstanceDataPackerPool.ts +++ b/packages/core/src/RenderPipeline/InstanceDataPackerPool.ts @@ -14,7 +14,7 @@ export class InstanceDataPackerPool { } getOrCreate(): InstanceDataPacker { - return this._pool[this._poolIndex++] ||= new InstanceDataPacker(this._engine); + return (this._pool[this._poolIndex++] ||= new InstanceDataPacker(this._engine)); } uploadBuffer(): void { diff --git a/packages/core/src/RenderPipeline/RenderQueue.ts b/packages/core/src/RenderPipeline/RenderQueue.ts index 30ec6fc34d..abf673f2c5 100644 --- a/packages/core/src/RenderPipeline/RenderQueue.ts +++ b/packages/core/src/RenderPipeline/RenderQueue.ts @@ -110,7 +110,11 @@ export class RenderQueue { } else { // Union render global macro and material self macro compileMacros = Shader._compileMacros; - ShaderMacroCollection.unionCollection(renderer._globalShaderMacro, materialData._macroCollection, compileMacros); + ShaderMacroCollection.unionCollection( + renderer._globalShaderMacro, + materialData._macroCollection, + compileMacros + ); ShaderMacroCollection.unionCollection(compileMacros, engine._macroCollection, compileMacros); } @@ -139,7 +143,11 @@ export class RenderQueue { } } - const program = shaderPass._getShaderProgram(engine, compileMacros, isInstanced ? instanceDataPacker.instanceFields : undefined); + const program = shaderPass._getShaderProgram( + engine, + compileMacros, + isInstanced ? instanceDataPacker.instanceFields : undefined + ); if (!program.isValid) { continue; } @@ -215,7 +223,10 @@ export class RenderQueue { if (isInstanced) { if (instanceDataPacker.uboBuffer) { program.bindUniformBlocks(InstanceDataPacker.uniformBlockBindingMap); - rhi.bindUniformBufferBase(ConstantBufferBindingPoint.RendererInstance, instanceDataPacker.uboBuffer._platformBuffer); + rhi.bindUniformBufferBase( + ConstantBufferBindingPoint.RendererInstance, + instanceDataPacker.uboBuffer._platformBuffer + ); } primitive.instanceCount = instanceDataPacker.instanceCount; rhi.drawPrimitive(primitive, subElement.subPrimitive, program); diff --git a/packages/core/src/shader/ShaderMacro.ts b/packages/core/src/shader/ShaderMacro.ts index 9fafbe3ec7..c18c0d4ab3 100644 --- a/packages/core/src/shader/ShaderMacro.ts +++ b/packages/core/src/shader/ShaderMacro.ts @@ -8,13 +8,14 @@ export class ShaderMacro { static _macroMaskMap: ShaderMacro[][] = []; /** @internal */ static _macroNameIdMap: Record = Object.create(null); - /** @internal */ - static _gpuInstanceMacro = ShaderMacro.getByName("RENDERER_GPU_INSTANCE"); private static _macroNameCounter: number = 0; private static _macroCounter: number = 0; private static _macroMap: Record = Object.create(null); + /** @internal */ + static _gpuInstanceMacro = ShaderMacro.getByName("RENDERER_GPU_INSTANCE"); + /** * Get shader macro by name. * @param name - Name of the shader macro diff --git a/packages/core/src/shader/ShaderPass.ts b/packages/core/src/shader/ShaderPass.ts index 4c4cccc488..9853cf6a96 100644 --- a/packages/core/src/shader/ShaderPass.ts +++ b/packages/core/src/shader/ShaderPass.ts @@ -33,7 +33,6 @@ export class ShaderPass extends ShaderPart { /** @internal */ static _shaderRootPath = "shaders://root/"; - /** * @internal */ @@ -167,7 +166,11 @@ export class ShaderPass extends ShaderPart { /** * @internal */ - _getShaderProgram(engine: Engine, macroCollection: ShaderMacroCollection, instanceFields?: InstanceFieldInfo[]): ShaderProgram { + _getShaderProgram( + engine: Engine, + macroCollection: ShaderMacroCollection, + instanceFields?: InstanceFieldInfo[] + ): ShaderProgram { const shaderProgramPool = engine._getMacroCachePool(this._shaderPassId, this._shaderProgramPools); let shaderProgram = shaderProgramPool.get(macroCollection); if (shaderProgram) { @@ -184,7 +187,11 @@ export class ShaderPass extends ShaderPart { * @internal * Scan renderer-group uniforms from this pass into fieldMap, without GPU compilation. */ - _scanInstanceFields(engine: Engine, macroCollection: ShaderMacroCollection, fieldMap: Record): boolean { + _scanInstanceFields( + engine: Engine, + macroCollection: ShaderMacroCollection, + fieldMap: Record + ): boolean { let vertexSource: string; let fragmentSource: string; @@ -239,7 +246,14 @@ export class ShaderPass extends ShaderPart { isGpuInstance: boolean, instanceFields?: InstanceFieldInfo[] ): { vertexSource: string; fragmentSource: string; instanceFields: InstanceFieldInfo[]; instanceMaxCount: number } { - return ShaderFactory.compilePlatformSource(engine, macroCollection, this._vertexSource, this._fragmentSource, isGpuInstance, instanceFields); + return ShaderFactory.compilePlatformSource( + engine, + macroCollection, + this._vertexSource, + this._fragmentSource, + isGpuInstance, + instanceFields + ); } private _compileShaderLabSource( diff --git a/packages/core/src/shader/SubShader.ts b/packages/core/src/shader/SubShader.ts index 71775ab2e3..e22a8e7399 100644 --- a/packages/core/src/shader/SubShader.ts +++ b/packages/core/src/shader/SubShader.ts @@ -41,10 +41,7 @@ export class SubShader extends ShaderPart { /** * @internal */ - _getInstanceLayout( - engine: Engine, - macroCollection: ShaderMacroCollection - ): InstanceLayout | null { + _getInstanceLayout(engine: Engine, macroCollection: ShaderMacroCollection): InstanceLayout | null { const cached = this._layoutCache.get(macroCollection); if (cached) return cached; diff --git a/packages/core/src/shaderlib/ShaderFactory.ts b/packages/core/src/shaderlib/ShaderFactory.ts index 0649f7653d..5dc268ba9d 100644 --- a/packages/core/src/shaderlib/ShaderFactory.ts +++ b/packages/core/src/shaderlib/ShaderFactory.ts @@ -30,7 +30,6 @@ export class ShaderFactory { /** @internal */ static readonly RENDERER_INSTANCE_BLOCK_NAME = "RendererInstanceData"; - /** @internal */ static readonly _shaderExtension = [ "GL_EXT_shader_texture_lod", @@ -119,8 +118,10 @@ export class ShaderFactory { /** Names that are macro-derived in instancing mode — remove but do not add to UBO. */ private static _uboDerivedNames = new Set([ - "renderer_MVMat", "renderer_MVPMat", - "renderer_NormalMat", "renderer_MVInvMat", + "renderer_MVMat", + "renderer_MVPMat", + "renderer_NormalMat", + "renderer_MVInvMat", "renderer_LocalMat" ]); @@ -142,19 +143,51 @@ export class ShaderFactory { /** Pack functions for writing typed values into ArrayBuffer views. */ private static _packFuncMap: Record = { - float: (v, o, val: number) => { v[o] = val; }, - int: (v, o, val: number) => { v[o] = val; }, - uint: (v, o, val: number) => { v[o] = val; }, - vec2: (v, o, val: Vector2) => { v[o] = val.x; v[o + 1] = val.y; }, - ivec2: (v, o, val: Vector2) => { v[o] = val.x; v[o + 1] = val.y; }, - vec3: (v, o, val: Vector3) => { v[o] = val.x; v[o + 1] = val.y; v[o + 2] = val.z; }, - ivec3: (v, o, val: Vector3) => { v[o] = val.x; v[o + 1] = val.y; v[o + 2] = val.z; }, - vec4: (v, o, val: Vector4) => { v[o] = val.x; v[o + 1] = val.y; v[o + 2] = val.z; v[o + 3] = val.w; }, - ivec4: (v, o, val: Vector4) => { v[o] = val.x; v[o + 1] = val.y; v[o + 2] = val.z; v[o + 3] = val.w; }, - mat4: (v, o, val: Matrix) => { const e = val.elements; for (let k = 0; k < 16; k++) v[o + k] = e[k]; } + float: (v, o, val: number) => { + v[o] = val; + }, + int: (v, o, val: number) => { + v[o] = val; + }, + uint: (v, o, val: number) => { + v[o] = val; + }, + vec2: (v, o, val: Vector2) => { + v[o] = val.x; + v[o + 1] = val.y; + }, + ivec2: (v, o, val: Vector2) => { + v[o] = val.x; + v[o + 1] = val.y; + }, + vec3: (v, o, val: Vector3) => { + v[o] = val.x; + v[o + 1] = val.y; + v[o + 2] = val.z; + }, + ivec3: (v, o, val: Vector3) => { + v[o] = val.x; + v[o + 1] = val.y; + v[o + 2] = val.z; + }, + vec4: (v, o, val: Vector4) => { + v[o] = val.x; + v[o + 1] = val.y; + v[o + 2] = val.z; + v[o + 3] = val.w; + }, + ivec4: (v, o, val: Vector4) => { + v[o] = val.x; + v[o + 1] = val.y; + v[o + 2] = val.z; + v[o + 3] = val.w; + }, + mat4: (v, o, val: Matrix) => { + const e = val.elements; + for (let k = 0; k < 16; k++) v[o + k] = e[k]; + } }; - /** * @internal * For GPU instancing shaders, scan VS and FS for `uniform ... renderer_*` declarations, @@ -167,7 +200,6 @@ export class ShaderFactory { fragmentSource: string, externalFields?: InstanceFieldInfo[] ): { vertexSource: string; fragmentSource: string; instanceFields: InstanceFieldInfo[]; instanceMaxCount: number } { - const fieldMap: Record = Object.create(null); vertexSource = ShaderFactory._scanInstanceUniforms(vertexSource, fieldMap, true); fragmentSource = ShaderFactory._scanInstanceUniforms(fragmentSource, fieldMap, true); @@ -183,14 +215,19 @@ export class ShaderFactory { instanceMaxCount = Math.floor(maxUBOSize / structSize); } else { let hasField = false; - for (const _ in fieldMap) { hasField = true; break; } + for (const _ in fieldMap) { + hasField = true; + break; + } if (!hasField) return { vertexSource, fragmentSource, instanceFields: null, instanceMaxCount: 0 }; ({ instanceFields, instanceMaxCount } = ShaderFactory._buildLayout(engine, fieldMap)); } // Generate UBO struct and #define remapping const structFields = instanceFields.map((f) => ` ${f.type} ${f.property.name};`).join("\n"); - const defines = instanceFields.map((f) => `#define ${f.property.name} rendererData[gl_InstanceID].${f.property.name}`).join("\n"); + const defines = instanceFields + .map((f) => `#define ${f.property.name} rendererData[gl_InstanceID].${f.property.name}`) + .join("\n"); const derivedDefines = [ "#define renderer_MVMat (camera_ViewMat * renderer_ModelMat)", "#define renderer_MVPMat (camera_VPMat * renderer_ModelMat)", @@ -234,12 +271,8 @@ export class ShaderFactory { return remove ? result : found; } - /** @internal */ - static _buildLayout( - engine: Engine, - fieldMap: Record - ): InstanceLayout { + static _buildLayout(engine: Engine, fieldMap: Record): InstanceLayout { const maxUBOSize = engine._hardwareRenderer.getMaxUniformBlockSize(); const std140Map = ShaderFactory._std140Map; const instanceFields: InstanceFieldInfo[] = []; @@ -264,8 +297,14 @@ export class ShaderFactory { // Priority fields first const modelMatId = Renderer._worldMatrixProperty._uniqueId; const layerId = Renderer._rendererLayerProperty._uniqueId; - if (modelMatId in fieldMap) { addField(modelMatId); delete fieldMap[modelMatId]; } - if (layerId in fieldMap) { addField(layerId); delete fieldMap[layerId]; } + if (modelMatId in fieldMap) { + addField(modelMatId); + delete fieldMap[modelMatId]; + } + if (layerId in fieldMap) { + addField(layerId); + delete fieldMap[layerId]; + } // Remaining fields sorted by id const keys: number[] = []; From 2318007a20a1a5c2a77b55b5824380545b2acfe7 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Tue, 7 Apr 2026 22:50:16 +0800 Subject: [PATCH 03/91] style: remove extra blank lines --- packages/core/src/mesh/MeshRenderer.ts | 2 -- packages/core/src/shader/ShaderProgram.ts | 1 - 2 files changed, 3 deletions(-) diff --git a/packages/core/src/mesh/MeshRenderer.ts b/packages/core/src/mesh/MeshRenderer.ts index 26cd10a84d..7fbed8f2c7 100644 --- a/packages/core/src/mesh/MeshRenderer.ts +++ b/packages/core/src/mesh/MeshRenderer.ts @@ -9,7 +9,6 @@ import { Mesh, MeshModifyFlags } from "../graphic/Mesh"; import { ShaderMacro } from "../shader/ShaderMacro"; import { ShaderMacroCollection } from "../shader/ShaderMacroCollection"; - /** * MeshRenderer Component. */ @@ -173,7 +172,6 @@ export class MeshRenderer extends Renderer { context.camera._renderPipeline.pushRenderElement(context, renderElement); } - /** * @internal */ diff --git a/packages/core/src/shader/ShaderProgram.ts b/packages/core/src/shader/ShaderProgram.ts index 622b1dfc3f..53b8a387b5 100644 --- a/packages/core/src/shader/ShaderProgram.ts +++ b/packages/core/src/shader/ShaderProgram.ts @@ -33,7 +33,6 @@ export class ShaderProgram { .join("\n"); } - id: number; readonly sceneUniformBlock: ShaderUniformBlock = new ShaderUniformBlock(); From 94ef0553ccb7284529dd7caf0f4abcf1bd0ff792 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Tue, 7 Apr 2026 23:04:33 +0800 Subject: [PATCH 04/91] revert: restore original shader files, instancing UBO injection handles layout The instance UBO is injected at compile time by ShaderFactory, so the original shader uniform declarations don't need modification. --- .../core/src/shaderlib/extra/shadow-map.vs.glsl | 2 +- packages/core/src/shaderlib/light_frag_define.glsl | 1 + packages/core/src/shaderlib/normal_vert.glsl | 5 ++--- packages/core/src/shaderlib/transform_declare.glsl | 6 ++---- packages/shader/src/shaders/Light.glsl | 1 + packages/shader/src/shaders/Transform.glsl | 14 ++++++-------- .../shader/src/shaders/shadingPBR/VertexPBR.glsl | 7 +++---- 7 files changed, 16 insertions(+), 20 deletions(-) diff --git a/packages/core/src/shaderlib/extra/shadow-map.vs.glsl b/packages/core/src/shaderlib/extra/shadow-map.vs.glsl index 89d84226ed..fae6a4b849 100644 --- a/packages/core/src/shaderlib/extra/shadow-map.vs.glsl +++ b/packages/core/src/shaderlib/extra/shadow-map.vs.glsl @@ -26,7 +26,7 @@ void main() { #include #include #include - + vec4 positionWS = renderer_ModelMat * position; positionWS.xyz = applyShadowBias(positionWS.xyz); diff --git a/packages/core/src/shaderlib/light_frag_define.glsl b/packages/core/src/shaderlib/light_frag_define.glsl index a977b28ca2..316bbba991 100644 --- a/packages/core/src/shaderlib/light_frag_define.glsl +++ b/packages/core/src/shaderlib/light_frag_define.glsl @@ -62,6 +62,7 @@ struct EnvMapLight { uniform EnvMapLight scene_EnvMapLight; +uniform ivec4 renderer_Layer; #ifdef SCENE_USE_SH uniform vec3 scene_EnvSH[9]; diff --git a/packages/core/src/shaderlib/normal_vert.glsl b/packages/core/src/shaderlib/normal_vert.glsl index 906be70ef7..096a4595d1 100644 --- a/packages/core/src/shaderlib/normal_vert.glsl +++ b/packages/core/src/shaderlib/normal_vert.glsl @@ -1,10 +1,9 @@ #ifndef MATERIAL_OMIT_NORMAL #ifdef RENDERER_HAS_NORMAL - mat3 normalMat = mat3(renderer_NormalMat); - v_normal = normalize( normalMat * normal ); + v_normal = normalize( mat3(renderer_NormalMat) * normal ); #if defined(RENDERER_HAS_TANGENT) && ( defined(MATERIAL_HAS_NORMALTEXTURE) || defined(MATERIAL_HAS_CLEAR_COAT_NORMAL_TEXTURE) || defined(MATERIAL_ENABLE_ANISOTROPY) ) - vec3 tangentW = normalize( normalMat * tangent.xyz ); + vec3 tangentW = normalize( mat3(renderer_NormalMat) * tangent.xyz ); vec3 bitangentW = cross( v_normal, tangentW ) * tangent.w; v_TBN = mat3( tangentW, bitangentW, v_normal ); diff --git a/packages/core/src/shaderlib/transform_declare.glsl b/packages/core/src/shaderlib/transform_declare.glsl index 131ca2b213..00e1d5edba 100644 --- a/packages/core/src/shaderlib/transform_declare.glsl +++ b/packages/core/src/shaderlib/transform_declare.glsl @@ -1,9 +1,7 @@ -uniform mat4 camera_ViewMat; -uniform mat4 camera_ProjMat; - uniform mat4 renderer_LocalMat; uniform mat4 renderer_ModelMat; -uniform ivec4 renderer_Layer; +uniform mat4 camera_ViewMat; +uniform mat4 camera_ProjMat; uniform mat4 renderer_MVMat; uniform mat4 renderer_MVPMat; uniform mat4 renderer_NormalMat; \ No newline at end of file diff --git a/packages/shader/src/shaders/Light.glsl b/packages/shader/src/shaders/Light.glsl index ed463484d6..3101ae1cf1 100644 --- a/packages/shader/src/shaders/Light.glsl +++ b/packages/shader/src/shaders/Light.glsl @@ -2,6 +2,7 @@ #define LIGHT_INCLUDED +ivec4 renderer_Layer; #ifndef GRAPHICS_API_WEBGL2 bool isBitSet(float value, float mask, float bitIndex){ return mod(floor(value / pow(2.0, bitIndex)), 2.0) == 1.0 && mod(floor(mask / pow(2.0, bitIndex)), 2.0) == 1.0; diff --git a/packages/shader/src/shaders/Transform.glsl b/packages/shader/src/shaders/Transform.glsl index 499d30d517..b5d1a52a68 100644 --- a/packages/shader/src/shaders/Transform.glsl +++ b/packages/shader/src/shaders/Transform.glsl @@ -1,18 +1,16 @@ #ifndef TRANSFORM_INCLUDED #define TRANSFORM_INCLUDED -mat4 camera_ViewMat; -mat4 camera_ProjMat; - -vec3 camera_Position; -vec3 camera_Forward; -vec4 camera_ProjectionParams; - mat4 renderer_LocalMat; mat4 renderer_ModelMat; +mat4 camera_ViewMat; +mat4 camera_ProjMat; mat4 renderer_MVMat; mat4 renderer_MVPMat; mat4 renderer_NormalMat; -ivec4 renderer_Layer; + +vec3 camera_Position; +vec3 camera_Forward; +vec4 camera_ProjectionParams; #endif \ No newline at end of file diff --git a/packages/shader/src/shaders/shadingPBR/VertexPBR.glsl b/packages/shader/src/shaders/shadingPBR/VertexPBR.glsl index c9e964d6ec..134f2405f2 100644 --- a/packages/shader/src/shaders/shadingPBR/VertexPBR.glsl +++ b/packages/shader/src/shaders/shadingPBR/VertexPBR.glsl @@ -69,11 +69,10 @@ VertexInputs getVertexInputs(Attributes attributes){ // TBN world space #ifdef RENDERER_HAS_NORMAL - mat3 normalMat = mat3(renderer_NormalMat); - inputs.normalWS = normalize( normalMat * normal ); + inputs.normalWS = normalize( mat3(renderer_NormalMat) * normal ); #ifdef RENDERER_HAS_TANGENT - vec3 tangentWS = normalize( normalMat * tangent.xyz ); + vec3 tangentWS = normalize( mat3(renderer_NormalMat) * tangent.xyz ); vec3 bitangentWS = cross( inputs.normalWS, tangentWS ) * tangent.w; inputs.tangentWS = tangentWS; @@ -86,7 +85,7 @@ VertexInputs getVertexInputs(Attributes attributes){ vec4 positionWS = renderer_ModelMat * position; inputs.positionWS = positionWS.xyz / positionWS.w; - #if SCENE_FOG_MODE != 0 + #if SCENE_FOG_MODE != 0 vec4 positionVS = renderer_MVMat * position; inputs.positionVS = positionVS.xyz / positionVS.w; #endif From 128b16e04f5a9ac7896801572b2ed8ccff6e9c36 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Tue, 7 Apr 2026 23:15:24 +0800 Subject: [PATCH 05/91] fix: restore non-Layer shader improvements that were over-reverted Bring back normalMat extraction, transform_declare reordering, trailing whitespace fixes, and VertexPBR indent fix. Only the renderer_Layer relocation stays reverted. --- .../core/src/shaderlib/extra/shadow-map.vs.glsl | 2 +- packages/core/src/shaderlib/normal_vert.glsl | 5 +++-- packages/core/src/shaderlib/transform_declare.glsl | 5 +++-- packages/shader/src/shaders/Transform.glsl | 13 +++++++------ .../shader/src/shaders/shadingPBR/VertexPBR.glsl | 7 ++++--- 5 files changed, 18 insertions(+), 14 deletions(-) diff --git a/packages/core/src/shaderlib/extra/shadow-map.vs.glsl b/packages/core/src/shaderlib/extra/shadow-map.vs.glsl index fae6a4b849..89d84226ed 100644 --- a/packages/core/src/shaderlib/extra/shadow-map.vs.glsl +++ b/packages/core/src/shaderlib/extra/shadow-map.vs.glsl @@ -26,7 +26,7 @@ void main() { #include #include #include - + vec4 positionWS = renderer_ModelMat * position; positionWS.xyz = applyShadowBias(positionWS.xyz); diff --git a/packages/core/src/shaderlib/normal_vert.glsl b/packages/core/src/shaderlib/normal_vert.glsl index 096a4595d1..906be70ef7 100644 --- a/packages/core/src/shaderlib/normal_vert.glsl +++ b/packages/core/src/shaderlib/normal_vert.glsl @@ -1,9 +1,10 @@ #ifndef MATERIAL_OMIT_NORMAL #ifdef RENDERER_HAS_NORMAL - v_normal = normalize( mat3(renderer_NormalMat) * normal ); + mat3 normalMat = mat3(renderer_NormalMat); + v_normal = normalize( normalMat * normal ); #if defined(RENDERER_HAS_TANGENT) && ( defined(MATERIAL_HAS_NORMALTEXTURE) || defined(MATERIAL_HAS_CLEAR_COAT_NORMAL_TEXTURE) || defined(MATERIAL_ENABLE_ANISOTROPY) ) - vec3 tangentW = normalize( mat3(renderer_NormalMat) * tangent.xyz ); + vec3 tangentW = normalize( normalMat * tangent.xyz ); vec3 bitangentW = cross( v_normal, tangentW ) * tangent.w; v_TBN = mat3( tangentW, bitangentW, v_normal ); diff --git a/packages/core/src/shaderlib/transform_declare.glsl b/packages/core/src/shaderlib/transform_declare.glsl index 00e1d5edba..b7d63a5a12 100644 --- a/packages/core/src/shaderlib/transform_declare.glsl +++ b/packages/core/src/shaderlib/transform_declare.glsl @@ -1,7 +1,8 @@ -uniform mat4 renderer_LocalMat; -uniform mat4 renderer_ModelMat; uniform mat4 camera_ViewMat; uniform mat4 camera_ProjMat; + +uniform mat4 renderer_LocalMat; +uniform mat4 renderer_ModelMat; uniform mat4 renderer_MVMat; uniform mat4 renderer_MVPMat; uniform mat4 renderer_NormalMat; \ No newline at end of file diff --git a/packages/shader/src/shaders/Transform.glsl b/packages/shader/src/shaders/Transform.glsl index b5d1a52a68..e5e3d95589 100644 --- a/packages/shader/src/shaders/Transform.glsl +++ b/packages/shader/src/shaders/Transform.glsl @@ -1,16 +1,17 @@ #ifndef TRANSFORM_INCLUDED #define TRANSFORM_INCLUDED -mat4 renderer_LocalMat; -mat4 renderer_ModelMat; mat4 camera_ViewMat; mat4 camera_ProjMat; -mat4 renderer_MVMat; -mat4 renderer_MVPMat; -mat4 renderer_NormalMat; vec3 camera_Position; -vec3 camera_Forward; +vec3 camera_Forward; vec4 camera_ProjectionParams; +mat4 renderer_LocalMat; +mat4 renderer_ModelMat; +mat4 renderer_MVMat; +mat4 renderer_MVPMat; +mat4 renderer_NormalMat; + #endif \ No newline at end of file diff --git a/packages/shader/src/shaders/shadingPBR/VertexPBR.glsl b/packages/shader/src/shaders/shadingPBR/VertexPBR.glsl index 134f2405f2..c9e964d6ec 100644 --- a/packages/shader/src/shaders/shadingPBR/VertexPBR.glsl +++ b/packages/shader/src/shaders/shadingPBR/VertexPBR.glsl @@ -69,10 +69,11 @@ VertexInputs getVertexInputs(Attributes attributes){ // TBN world space #ifdef RENDERER_HAS_NORMAL - inputs.normalWS = normalize( mat3(renderer_NormalMat) * normal ); + mat3 normalMat = mat3(renderer_NormalMat); + inputs.normalWS = normalize( normalMat * normal ); #ifdef RENDERER_HAS_TANGENT - vec3 tangentWS = normalize( mat3(renderer_NormalMat) * tangent.xyz ); + vec3 tangentWS = normalize( normalMat * tangent.xyz ); vec3 bitangentWS = cross( inputs.normalWS, tangentWS ) * tangent.w; inputs.tangentWS = tangentWS; @@ -85,7 +86,7 @@ VertexInputs getVertexInputs(Attributes attributes){ vec4 positionWS = renderer_ModelMat * position; inputs.positionWS = positionWS.xyz / positionWS.w; - #if SCENE_FOG_MODE != 0 + #if SCENE_FOG_MODE != 0 vec4 positionVS = renderer_MVMat * position; inputs.positionVS = positionVS.xyz / positionVS.w; #endif From 4d8f2b739c35ff68e79a7fbe81bee13c4d90195d Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Tue, 7 Apr 2026 23:22:41 +0800 Subject: [PATCH 06/91] refactor: revert _getMacroCachePool rename back to _getShaderProgramPool --- packages/core/src/Engine.ts | 2 +- packages/core/src/graphic/TransformFeedbackShader.ts | 2 +- packages/core/src/shader/ShaderPass.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/src/Engine.ts b/packages/core/src/Engine.ts index bbf0b3d1fd..6976ac2b03 100644 --- a/packages/core/src/Engine.ts +++ b/packages/core/src/Engine.ts @@ -542,7 +542,7 @@ export class Engine extends EventDispatcher { /** * @internal */ - _getMacroCachePool(index: number, trackPools?: MacroCachePool[]): MacroCachePool { + _getShaderProgramPool(index: number, trackPools?: MacroCachePool[]): MacroCachePool { const shaderProgramPools = this._shaderProgramPools; let pool = shaderProgramPools[index]; if (!pool) { diff --git a/packages/core/src/graphic/TransformFeedbackShader.ts b/packages/core/src/graphic/TransformFeedbackShader.ts index b3e61b9d48..c200e41597 100644 --- a/packages/core/src/graphic/TransformFeedbackShader.ts +++ b/packages/core/src/graphic/TransformFeedbackShader.ts @@ -28,7 +28,7 @@ export class TransformFeedbackShader { * Get or compile a shader program for the given engine and macro combination. */ getProgram(engine: Engine, macroCollection: ShaderMacroCollection): ShaderProgram | null { - const pool = engine._getMacroCachePool(this._id); + const pool = engine._getShaderProgramPool(this._id); let program = pool.get(macroCollection); if (program) return program; diff --git a/packages/core/src/shader/ShaderPass.ts b/packages/core/src/shader/ShaderPass.ts index 9853cf6a96..2122050d4c 100644 --- a/packages/core/src/shader/ShaderPass.ts +++ b/packages/core/src/shader/ShaderPass.ts @@ -171,7 +171,7 @@ export class ShaderPass extends ShaderPart { macroCollection: ShaderMacroCollection, instanceFields?: InstanceFieldInfo[] ): ShaderProgram { - const shaderProgramPool = engine._getMacroCachePool(this._shaderPassId, this._shaderProgramPools); + const shaderProgramPool = engine._getShaderProgramPool(this._shaderPassId, this._shaderProgramPools); let shaderProgram = shaderProgramPool.get(macroCollection); if (shaderProgram) { return shaderProgram; From 75a1ba277e727a5a223ee1a2be28673cda024038 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Wed, 8 Apr 2026 02:19:14 +0800 Subject: [PATCH 07/91] =?UTF-8?q?feat(instancing):=20fix=20batch=20reuse?= =?UTF-8?q?=20bug=20and=20optimize=20ModelMat=20to=20affine=203=C3=97vec4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix SubRenderElement.set() not resetting instanceDataPacker, causing stale packer references from previous frames to break all batching - Use whitelist + _group fallback for identifying renderer uniforms in _scanInstanceUniforms (fixes _group===undefined for ModelMat) - Store ModelMat as 3×vec4 (affine rows) instead of mat4 in UBO, saving 16 bytes per instance (structSize 80→64, +25% instances/batch) - Add camera_VPMat to transform_declare.glsl for derived MVP define - Extract struct definition outside uniform block (GLSL ES 3.00 compat) - Fix _insertUBOBlock to only scan initial #define preamble - Pass instanceID to fragment shader via flat varying --- examples/src/gpu-instancing-auto-batch.ts | 62 ++++++++++ .../src/RenderPipeline/InstanceDataPacker.ts | 5 +- .../src/RenderPipeline/SubRenderElement.ts | 1 + packages/core/src/shaderlib/ShaderFactory.ts | 112 ++++++++++++------ .../core/src/shaderlib/transform_declare.glsl | 1 + 5 files changed, 144 insertions(+), 37 deletions(-) create mode 100644 examples/src/gpu-instancing-auto-batch.ts diff --git a/examples/src/gpu-instancing-auto-batch.ts b/examples/src/gpu-instancing-auto-batch.ts new file mode 100644 index 0000000000..865fb029b7 --- /dev/null +++ b/examples/src/gpu-instancing-auto-batch.ts @@ -0,0 +1,62 @@ +/** + * @title GPU Instancing Auto Batch + * @category Mesh + * @thumbnail https://mdn.alipayobjects.com/merchant_appfe/afts/img/A*jjZMTrp-vU8AAAAAAAAAAAAADiR2AQ/original + */ +import { OrbitControl } from "@galacean/engine-toolkit"; +import { + BlinnPhongMaterial, + Camera, + Color, + DirectLight, + Logger, + MeshRenderer, + PrimitiveMesh, + Vector3, + WebGLEngine +} from "@galacean/engine"; + +Logger.enable(); +WebGLEngine.create({ canvas: "canvas" }).then((engine) => { + engine.canvas.resizeByClientSize(); + + const scene = engine.sceneManager.activeScene; + const rootEntity = scene.createRootEntity("Root"); + + // Camera + const cameraEntity = rootEntity.createChild("Camera"); + cameraEntity.transform.setPosition(0, 10, 80); + cameraEntity.transform.lookAt(new Vector3(0, 0, 0)); + const camera = cameraEntity.addComponent(Camera); + camera.farClipPlane = 300; + cameraEntity.addComponent(OrbitControl); + + // Light + const lightEntity = rootEntity.createChild("Light"); + lightEntity.transform.setRotation(-45, -45, 0); + lightEntity.addComponent(DirectLight).color = new Color(1, 1, 1, 1); + + // Shared mesh and material — all renderers use the same instances to enable auto-batching + const mesh = PrimitiveMesh.createCuboid(engine, 1, 1, 1); + const material = new BlinnPhongMaterial(engine); + material.baseColor = new Color(0.6, 0.75, 1.0, 1.0); + + // Create 1000 cubes with random positions + const count = 1000; + const spread = 50; + for (let i = 0; i < count; i++) { + const entity = rootEntity.createChild("Cube" + i); + entity.transform.setPosition( + (Math.random() - 0.5) * spread, + (Math.random() - 0.5) * spread, + (Math.random() - 0.5) * spread + ); + entity.transform.setRotation(Math.random() * 360, Math.random() * 360, Math.random() * 360); + + const renderer = entity.addComponent(MeshRenderer); + renderer.mesh = mesh; + renderer.setMaterial(material); + } + + engine.run(); +}); diff --git a/packages/core/src/RenderPipeline/InstanceDataPacker.ts b/packages/core/src/RenderPipeline/InstanceDataPacker.ts index 28960c35b0..64e4f5bf39 100644 --- a/packages/core/src/RenderPipeline/InstanceDataPacker.ts +++ b/packages/core/src/RenderPipeline/InstanceDataPacker.ts @@ -83,10 +83,7 @@ export class InstanceDataPacker { if (propertyId === modelMatId) { // ModelMat must go through getter to trigger Transform lazy update - const e = renderer.entity.transform.worldMatrix.elements; - for (let k = 0; k < 16; k++) { - floatView[fieldOffset + k] = e[k]; - } + field.pack(floatView, fieldOffset, renderer.entity.transform.worldMatrix); } else { const value = propertyValueMap[propertyId]; if (value != null) { diff --git a/packages/core/src/RenderPipeline/SubRenderElement.ts b/packages/core/src/RenderPipeline/SubRenderElement.ts index 245d2a7dd4..fd3f1b249b 100644 --- a/packages/core/src/RenderPipeline/SubRenderElement.ts +++ b/packages/core/src/RenderPipeline/SubRenderElement.ts @@ -37,6 +37,7 @@ export class SubRenderElement implements IPoolElement { this.subPrimitive = subPrimitive; this.texture = texture; this.subChunk = subChunk; + this.instanceDataPacker = null; } dispose(): void { diff --git a/packages/core/src/shaderlib/ShaderFactory.ts b/packages/core/src/shaderlib/ShaderFactory.ts index 5dc268ba9d..96dfa5f632 100644 --- a/packages/core/src/shaderlib/ShaderFactory.ts +++ b/packages/core/src/shaderlib/ShaderFactory.ts @@ -116,13 +116,15 @@ export class ShaderFactory { }; } - /** Names that are macro-derived in instancing mode — remove but do not add to UBO. */ - private static _uboDerivedNames = new Set([ - "renderer_MVMat", - "renderer_MVPMat", - "renderer_NormalMat", - "renderer_MVInvMat", - "renderer_LocalMat" + /** Built-in renderer uniforms. value=true means derived (remove but not added to UBO). */ + private static _builtinRendererUniforms = new Map([ + ["renderer_ModelMat", false], + ["renderer_Layer", false], + ["renderer_LocalMat", true], + ["renderer_MVMat", true], + ["renderer_MVPMat", true], + ["renderer_NormalMat", true], + ["renderer_MVInvMat", true] ]); private static _uboUniformRegex = /^([ \t]*)uniform\s+(?:(?:lowp|mediump|highp)\s+)?(\w+)\s+(\w+)\s*;/gm; @@ -138,7 +140,8 @@ export class ShaderFactory { ivec3: { size: 12, align: 16 }, vec4: { size: 16, align: 16 }, ivec4: { size: 16, align: 16 }, - mat4: { size: 64, align: 16 } + mat4: { size: 64, align: 16 }, + mat4_affine: { size: 48, align: 16 } }; /** Pack functions for writing typed values into ArrayBuffer views. */ @@ -185,6 +188,17 @@ export class ShaderFactory { mat4: (v, o, val: Matrix) => { const e = val.elements; for (let k = 0; k < 16; k++) v[o + k] = e[k]; + }, + // Affine mat4 → 3 rows (vec4 each), skip row3 (0,0,0,1). Transposed layout. + mat4_affine: (v, o, val: Matrix) => { + const e = val.elements; + // row0=(e0,e4,e8,e12) row1=(e1,e5,e9,e13) row2=(e2,e6,e10,e14) + for (let r = 0; r < 3; r++) { + v[o + r * 4] = e[r]; + v[o + r * 4 + 1] = e[r + 4]; + v[o + r * 4 + 2] = e[r + 8]; + v[o + r * 4 + 3] = e[r + 12]; + } } }; @@ -223,29 +237,34 @@ export class ShaderFactory { ({ instanceFields, instanceMaxCount } = ShaderFactory._buildLayout(engine, fieldMap)); } - // Generate UBO struct and #define remapping - const structFields = instanceFields.map((f) => ` ${f.type} ${f.property.name};`).join("\n"); - const defines = instanceFields - .map((f) => `#define ${f.property.name} rendererData[gl_InstanceID].${f.property.name}`) - .join("\n"); - const derivedDefines = [ - "#define renderer_MVMat (camera_ViewMat * renderer_ModelMat)", - "#define renderer_MVPMat (camera_VPMat * renderer_ModelMat)", - "#define renderer_NormalMat transpose(inverse(mat3(renderer_ModelMat)))" - ].join("\n"); - - const uboBlock = + // Generate UBO struct fields and per-field #define remapping + const structFieldLines: string[] = []; + for (let i = 0; i < instanceFields.length; i++) { + const { type, property } = instanceFields[i]; + if (type === "mat4_affine") { + for (let r = 0; r < 3; r++) structFieldLines.push(` vec4 ${property.name}R${r};`); + } else { + structFieldLines.push(` ${type} ${property.name};`); + } + } + + const uboStruct = `#define INSTANCE_MAX_COUNT ${instanceMaxCount}\n` + + `struct RendererInstanceStruct {\n${structFieldLines.join("\n")}\n};\n` + `layout(std140) uniform ${ShaderFactory.RENDERER_INSTANCE_BLOCK_NAME} {\n` + - ` struct {\n` + - `${structFields}\n` + - ` } rendererData[INSTANCE_MAX_COUNT];\n` + - `};\n` + - `${defines}\n` + - `${derivedDefines}\n`; + ` RendererInstanceStruct rendererData[INSTANCE_MAX_COUNT];\n};\n`; + + const derivedDefines = + "#define renderer_MVMat (camera_ViewMat * renderer_ModelMat)\n" + + "#define renderer_MVPMat (camera_VPMat * renderer_ModelMat)\n" + + "#define renderer_NormalMat transpose(inverse(mat3(renderer_ModelMat)))"; - vertexSource = ShaderFactory._insertUBOBlock(vertexSource, uboBlock); - fragmentSource = ShaderFactory._insertUBOBlock(fragmentSource, uboBlock); + const vsUboBlock = `${uboStruct}flat out int v_instanceID;\n${ShaderFactory._buildFieldDefines(instanceFields, "gl_InstanceID")}\n${derivedDefines}\n`; + const fsUboBlock = `${uboStruct}flat in int v_instanceID;\n${ShaderFactory._buildFieldDefines(instanceFields, "v_instanceID")}\n${derivedDefines}\n`; + + vertexSource = ShaderFactory._insertUBOBlock(vertexSource, vsUboBlock); + vertexSource = vertexSource.replace(/void\s+main\s*\(\s*\)\s*\{/, "void main() {\n v_instanceID = gl_InstanceID;"); + fragmentSource = ShaderFactory._insertUBOBlock(fragmentSource, fsUboBlock); return { vertexSource, fragmentSource, instanceFields, instanceMaxCount }; } @@ -258,13 +277,15 @@ export class ShaderFactory { static _scanInstanceUniforms(source: string, fieldMap: Record, remove: true): string; static _scanInstanceUniforms(source: string, fieldMap: Record): boolean; static _scanInstanceUniforms(source: string, fieldMap: Record, remove?: boolean): string | boolean { - const derivedNames = ShaderFactory._uboDerivedNames; + const builtinUniforms = ShaderFactory._builtinRendererUniforms; let found = false; const result = source.replace(ShaderFactory._uboUniformRegex, (match, _indent, type, name) => { if (type.indexOf("sampler") !== -1) return match; - if (ShaderProperty._getShaderPropertyGroup(name) !== ShaderDataGroup.Renderer) return match; - if (derivedNames.has(name)) return remove ? "" : match; - fieldMap[ShaderProperty.getByName(name)._uniqueId] = type; + const isDerived = builtinUniforms.get(name); + if (isDerived === undefined && ShaderProperty._getShaderPropertyGroup(name) !== ShaderDataGroup.Renderer) return match; + if (isDerived) return remove ? "" : match; + // Store ModelMat as affine (3×vec4) to save UBO space + fieldMap[ShaderProperty.getByName(name)._uniqueId] = type === "mat4" && name === "renderer_ModelMat" ? "mat4_affine" : type; found = true; return remove ? "" : match; }); @@ -318,6 +339,28 @@ export class ShaderFactory { return { instanceFields, instanceMaxCount, structSize }; } + /** Build per-field #define lines, using `idExpr` as the instance index (gl_InstanceID or v_instanceID). */ + private static _buildFieldDefines(fields: InstanceFieldInfo[], idExpr: string): string { + const lines: string[] = []; + for (let i = 0; i < fields.length; i++) { + const { type, property } = fields[i]; + const d = `rendererData[${idExpr}]`; + if (type === "mat4_affine") { + const n = property.name; + lines.push( + `#define ${n} mat4(` + + `vec4(${d}.${n}R0.x,${d}.${n}R1.x,${d}.${n}R2.x,0.0),` + + `vec4(${d}.${n}R0.y,${d}.${n}R1.y,${d}.${n}R2.y,0.0),` + + `vec4(${d}.${n}R0.z,${d}.${n}R1.z,${d}.${n}R2.z,0.0),` + + `vec4(${d}.${n}R0.w,${d}.${n}R1.w,${d}.${n}R2.w,1.0))` + ); + } else { + lines.push(`#define ${property.name} ${d}.${property.name}`); + } + } + return lines.join("\n"); + } + /** * Insert a UBO block into source after the macro section. */ @@ -325,8 +368,11 @@ export class ShaderFactory { const lines = source.split("\n"); let insertIdx = 0; for (let i = 0; i < lines.length; i++) { - if (lines[i].trimStart().startsWith("#define ")) { + const trimmed = lines[i].trimStart(); + if (trimmed.startsWith("#define ")) { insertIdx = i + 1; + } else if (trimmed.length > 0) { + break; } } lines.splice(insertIdx, 0, uboBlock); diff --git a/packages/core/src/shaderlib/transform_declare.glsl b/packages/core/src/shaderlib/transform_declare.glsl index b7d63a5a12..e098136edc 100644 --- a/packages/core/src/shaderlib/transform_declare.glsl +++ b/packages/core/src/shaderlib/transform_declare.glsl @@ -1,5 +1,6 @@ uniform mat4 camera_ViewMat; uniform mat4 camera_ProjMat; +uniform mat4 camera_VPMat; uniform mat4 renderer_LocalMat; uniform mat4 renderer_ModelMat; From 9d015d6610a597815b45f40d1b234dcef9878dfe Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Wed, 8 Apr 2026 11:46:01 +0800 Subject: [PATCH 08/91] feat(gpu-instancing): load ambient light and clone ducks for instancing --- examples/src/gpu-instancing-auto-batch.ts | 37 +++++++++++++---------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/examples/src/gpu-instancing-auto-batch.ts b/examples/src/gpu-instancing-auto-batch.ts index 865fb029b7..2f2d0e64df 100644 --- a/examples/src/gpu-instancing-auto-batch.ts +++ b/examples/src/gpu-instancing-auto-batch.ts @@ -5,19 +5,19 @@ */ import { OrbitControl } from "@galacean/engine-toolkit"; import { - BlinnPhongMaterial, + AmbientLight, + AssetType, Camera, Color, DirectLight, + GLTFResource, Logger, - MeshRenderer, - PrimitiveMesh, Vector3, WebGLEngine } from "@galacean/engine"; Logger.enable(); -WebGLEngine.create({ canvas: "canvas" }).then((engine) => { +WebGLEngine.create({ canvas: "canvas" }).then(async (engine) => { engine.canvas.resizeByClientSize(); const scene = engine.sceneManager.activeScene; @@ -36,26 +36,31 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { lightEntity.transform.setRotation(-45, -45, 0); lightEntity.addComponent(DirectLight).color = new Color(1, 1, 1, 1); - // Shared mesh and material — all renderers use the same instances to enable auto-batching - const mesh = PrimitiveMesh.createCuboid(engine, 1, 1, 1); - const material = new BlinnPhongMaterial(engine); - material.baseColor = new Color(0.6, 0.75, 1.0, 1.0); + // Load Duck model and ambient light + const [glTF, ambientLight] = await Promise.all([ + engine.resourceManager.load({ + url: "https://gw.alipayobjects.com/os/bmw-prod/6cb8f543-285c-491a-8cfd-57a1160dc9ab.glb", + type: AssetType.GLTF + }), + engine.resourceManager.load({ + url: "https://mdn.alipayobjects.com/oasis_be/afts/file/A*eRJ8QKzf5zAAAAAAgBAAAAgAekp5AQ/ambient.ambLight", + type: AssetType.AmbientLight + }) + ]); + scene.ambientLight = ambientLight; - // Create 1000 cubes with random positions + // Clone 1000 ducks with random positions const count = 1000; const spread = 50; for (let i = 0; i < count; i++) { - const entity = rootEntity.createChild("Cube" + i); - entity.transform.setPosition( + const duck = glTF.instantiateSceneRoot(); + duck.transform.setPosition( (Math.random() - 0.5) * spread, (Math.random() - 0.5) * spread, (Math.random() - 0.5) * spread ); - entity.transform.setRotation(Math.random() * 360, Math.random() * 360, Math.random() * 360); - - const renderer = entity.addComponent(MeshRenderer); - renderer.mesh = mesh; - renderer.setMaterial(material); + duck.transform.setRotation(Math.random() * 360, Math.random() * 360, Math.random() * 360); + rootEntity.addChild(duck); } engine.run(); From 7b4933557186edc2315d5666b8add5d459841208 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Wed, 8 Apr 2026 13:31:17 +0800 Subject: [PATCH 09/91] fix(instancing): keep renderer_NormalMat as mat4 for shader compatibility Wrap derived NormalMat define with mat4() so instancing and non-instancing paths both produce mat4, avoiding shader compilation errors. Add custom instance data example to verify per-renderer uniform batching. --- examples/src/gpu-instancing-custom-data.ts | 97 ++++++++++++++++++++ packages/core/src/shaderlib/ShaderFactory.ts | 2 +- 2 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 examples/src/gpu-instancing-custom-data.ts diff --git a/examples/src/gpu-instancing-custom-data.ts b/examples/src/gpu-instancing-custom-data.ts new file mode 100644 index 0000000000..8ebba0e68f --- /dev/null +++ b/examples/src/gpu-instancing-custom-data.ts @@ -0,0 +1,97 @@ +/** + * @title GPU Instancing Custom Data + * @category Mesh + * @thumbnail https://mdn.alipayobjects.com/merchant_appfe/afts/img/A*jjZMTrp-vU8AAAAAAAAAAAAADiR2AQ/original + */ +import { OrbitControl } from "@galacean/engine-toolkit"; +import { + Camera, + Color, + DirectLight, + Logger, + Material, + MeshRenderer, + PrimitiveMesh, + Shader, + ShaderProperty, + Vector3, + Vector4, + WebGLEngine +} from "@galacean/engine"; + +Logger.enable(); + +// Custom shader: uses renderer_CustomColor (per-instance) for fragment output +Shader.create( + "CustomInstanceShader", + ` + #include + attribute vec3 POSITION; + attribute vec3 NORMAL; + + varying vec3 v_normal; + + void main() { + gl_Position = renderer_MVPMat * vec4(POSITION, 1.0); + v_normal = normalize((renderer_NormalMat * vec4(NORMAL, 0.0)).xyz); + } + `, + ` + uniform vec4 renderer_CustomColor; + + varying vec3 v_normal; + + void main() { + vec3 lightDir = normalize(vec3(1.0, 1.0, 1.0)); + float NdotL = max(dot(v_normal, lightDir), 0.2); + gl_FragColor = vec4(renderer_CustomColor.rgb * NdotL, 1.0); + } + ` +); + +WebGLEngine.create({ canvas: "canvas" }).then((engine) => { + engine.canvas.resizeByClientSize(); + + const scene = engine.sceneManager.activeScene; + const rootEntity = scene.createRootEntity("Root"); + + // Camera + const cameraEntity = rootEntity.createChild("Camera"); + cameraEntity.transform.setPosition(0, 10, 80); + cameraEntity.transform.lookAt(new Vector3(0, 0, 0)); + const camera = cameraEntity.addComponent(Camera); + camera.farClipPlane = 300; + cameraEntity.addComponent(OrbitControl); + + // Light + const lightEntity = rootEntity.createChild("Light"); + lightEntity.transform.setRotation(-45, -45, 0); + lightEntity.addComponent(DirectLight).color = new Color(1, 1, 1, 1); + + // Shared mesh and material + const mesh = PrimitiveMesh.createCuboid(engine, 1, 1, 1); + const material = new Material(engine, Shader.find("CustomInstanceShader")); + const customColorProperty = ShaderProperty.getByName("renderer_CustomColor"); + + // Create 1000 cubes, each with a unique color via renderer shaderData + const count = 1000; + const spread = 50; + for (let i = 0; i < count; i++) { + const entity = rootEntity.createChild("Cube" + i); + entity.transform.setPosition( + (Math.random() - 0.5) * spread, + (Math.random() - 0.5) * spread, + (Math.random() - 0.5) * spread + ); + entity.transform.setRotation(Math.random() * 360, Math.random() * 360, Math.random() * 360); + + const renderer = entity.addComponent(MeshRenderer); + renderer.mesh = mesh; + renderer.setMaterial(material); + + // Set per-instance custom color on renderer's shaderData (not material's) + renderer.shaderData.setVector4(customColorProperty, new Vector4(Math.random(), Math.random(), Math.random(), 1.0)); + } + + engine.run(); +}); diff --git a/packages/core/src/shaderlib/ShaderFactory.ts b/packages/core/src/shaderlib/ShaderFactory.ts index 96dfa5f632..14ad35ef84 100644 --- a/packages/core/src/shaderlib/ShaderFactory.ts +++ b/packages/core/src/shaderlib/ShaderFactory.ts @@ -257,7 +257,7 @@ export class ShaderFactory { const derivedDefines = "#define renderer_MVMat (camera_ViewMat * renderer_ModelMat)\n" + "#define renderer_MVPMat (camera_VPMat * renderer_ModelMat)\n" + - "#define renderer_NormalMat transpose(inverse(mat3(renderer_ModelMat)))"; + "#define renderer_NormalMat mat4(transpose(inverse(mat3(renderer_ModelMat))))"; const vsUboBlock = `${uboStruct}flat out int v_instanceID;\n${ShaderFactory._buildFieldDefines(instanceFields, "gl_InstanceID")}\n${derivedDefines}\n`; const fsUboBlock = `${uboStruct}flat in int v_instanceID;\n${ShaderFactory._buildFieldDefines(instanceFields, "v_instanceID")}\n${derivedDefines}\n`; From 73c19fa90bb08157d8cb9ed20141865066848db4 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Wed, 8 Apr 2026 16:58:41 +0800 Subject: [PATCH 10/91] refactor: rename batch to packer for consistency with InstanceDataPacker --- .../RenderPipeline/InstanceDataPackerPool.ts | 6 +++--- packages/core/src/mesh/MeshRenderer.ts | 20 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/core/src/RenderPipeline/InstanceDataPackerPool.ts b/packages/core/src/RenderPipeline/InstanceDataPackerPool.ts index 654c1a2bd7..0be3791388 100644 --- a/packages/core/src/RenderPipeline/InstanceDataPackerPool.ts +++ b/packages/core/src/RenderPipeline/InstanceDataPackerPool.ts @@ -20,9 +20,9 @@ export class InstanceDataPackerPool { uploadBuffer(): void { const pool = this._pool; for (let i = 0, n = this._poolIndex; i < n; i++) { - const batch = pool[i]; - if (batch.instanceCount > 1) { - batch.prepare(); + const packer = pool[i]; + if (packer.instanceCount > 1) { + packer.prepare(); } } } diff --git a/packages/core/src/mesh/MeshRenderer.ts b/packages/core/src/mesh/MeshRenderer.ts index 7fbed8f2c7..279319a0e9 100644 --- a/packages/core/src/mesh/MeshRenderer.ts +++ b/packages/core/src/mesh/MeshRenderer.ts @@ -178,9 +178,9 @@ export class MeshRenderer extends Renderer { override _canBatch(elementA: SubRenderElement, elementB: SubRenderElement): boolean { if (!this._engine._hardwareRenderer.isWebGL2) return false; - const batch = elementA.instanceDataPacker; + const packer = elementA.instanceDataPacker; return ( - (!batch || batch.instanceCount < batch.maxInstanceCount) && + (!packer || packer.instanceCount < packer.maxInstanceCount) && elementA.primitive === elementB.primitive && elementA.subPrimitive === elementB.subPrimitive && elementA.material === elementB.material && @@ -194,11 +194,11 @@ export class MeshRenderer extends Renderer { override _batch(elementA: SubRenderElement, elementB?: SubRenderElement): void { if (!elementB) return; - let batch = elementA.instanceDataPacker; - if (!batch) { + let packer = elementA.instanceDataPacker; + if (!packer) { const engine = this._engine; - batch = engine._batcherManager.instanceDataPackerPool.getOrCreate(); - const compileMacros = batch.compileMacros; + packer = engine._batcherManager.instanceDataPackerPool.getOrCreate(); + const compileMacros = packer.compileMacros; const materialData = elementA.material.shaderData; ShaderMacroCollection.unionCollection(this._globalShaderMacro, materialData._macroCollection, compileMacros); ShaderMacroCollection.unionCollection(compileMacros, engine._macroCollection, compileMacros); @@ -206,12 +206,12 @@ export class MeshRenderer extends Renderer { const layout = elementA.subShader._getInstanceLayout(engine, compileMacros); if (layout) { - batch.setLayout(layout.instanceFields, layout.instanceMaxCount, layout.structSize); + packer.setLayout(layout.instanceFields, layout.instanceMaxCount, layout.structSize); } - batch.addRenderer(elementA.component); - elementA.instanceDataPacker = batch; + packer.addRenderer(elementA.component); + elementA.instanceDataPacker = packer; } - batch.addRenderer(elementB.component); + packer.addRenderer(elementB.component); } private _setMesh(mesh: Mesh): void { From 4a55540badcda6a125fe6ea4b5a4a4dd0a80a7cb Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Wed, 8 Apr 2026 23:04:36 +0800 Subject: [PATCH 11/91] refactor: clarify _canBatch/_batch parameter semantics Rename elementA/elementB to preSubElement/subElement across all renderer subclasses and BatchUtils. Change _batch signature so preSubElement is nullable (null = batch head, no previous element to merge with), and subElement is always required. --- packages/core/src/2d/sprite/SpriteMask.ts | 8 ++-- packages/core/src/2d/sprite/SpriteRenderer.ts | 8 ++-- packages/core/src/2d/text/TextRenderer.ts | 8 ++-- .../core/src/RenderPipeline/BatchUtils.ts | 38 +++++++++---------- .../core/src/RenderPipeline/BatcherManager.ts | 4 +- packages/core/src/Renderer.ts | 4 +- packages/core/src/mesh/MeshRenderer.ts | 28 +++++++------- packages/core/src/mesh/SkinnedMeshRenderer.ts | 2 +- packages/ui/src/component/UIRenderer.ts | 8 ++-- 9 files changed, 54 insertions(+), 54 deletions(-) diff --git a/packages/core/src/2d/sprite/SpriteMask.ts b/packages/core/src/2d/sprite/SpriteMask.ts index c2e41586d5..3d99a09b81 100644 --- a/packages/core/src/2d/sprite/SpriteMask.ts +++ b/packages/core/src/2d/sprite/SpriteMask.ts @@ -204,15 +204,15 @@ export class SpriteMask extends Renderer implements ISpriteRenderer { /** * @internal */ - override _canBatch(elementA: SubRenderElement, elementB: SubRenderElement): boolean { - return BatchUtils.canBatchSpriteMask(elementA, elementB); + override _canBatch(preSubElement: SubRenderElement, subElement: SubRenderElement): boolean { + return BatchUtils.canBatchSpriteMask(preSubElement, subElement); } /** * @internal */ - override _batch(elementA: SubRenderElement, elementB?: SubRenderElement): void { - BatchUtils.batchFor2D(elementA, elementB); + override _batch(preSubElement: SubRenderElement | null, subElement: SubRenderElement): void { + BatchUtils.batchFor2D(preSubElement, subElement); } /** diff --git a/packages/core/src/2d/sprite/SpriteRenderer.ts b/packages/core/src/2d/sprite/SpriteRenderer.ts index c1b183ee7a..6eac9898d1 100644 --- a/packages/core/src/2d/sprite/SpriteRenderer.ts +++ b/packages/core/src/2d/sprite/SpriteRenderer.ts @@ -295,15 +295,15 @@ export class SpriteRenderer extends Renderer implements ISpriteRenderer { /** * @internal */ - override _canBatch(elementA: SubRenderElement, elementB: SubRenderElement): boolean { - return BatchUtils.canBatchSprite(elementA, elementB); + override _canBatch(preSubElement: SubRenderElement, subElement: SubRenderElement): boolean { + return BatchUtils.canBatchSprite(preSubElement, subElement); } /** * @internal */ - override _batch(elementA: SubRenderElement, elementB?: SubRenderElement): void { - BatchUtils.batchFor2D(elementA, elementB); + override _batch(preSubElement: SubRenderElement | null, subElement: SubRenderElement): void { + BatchUtils.batchFor2D(preSubElement, subElement); } /** diff --git a/packages/core/src/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts index 143e46a7da..6fd6ead38d 100644 --- a/packages/core/src/2d/text/TextRenderer.ts +++ b/packages/core/src/2d/text/TextRenderer.ts @@ -381,15 +381,15 @@ export class TextRenderer extends Renderer implements ITextRenderer { /** * @internal */ - override _canBatch(elementA: SubRenderElement, elementB: SubRenderElement): boolean { - return BatchUtils.canBatchSprite(elementA, elementB); + override _canBatch(preSubElement: SubRenderElement, subElement: SubRenderElement): boolean { + return BatchUtils.canBatchSprite(preSubElement, subElement); } /** * @internal */ - override _batch(elementA: SubRenderElement, elementB?: SubRenderElement): void { - BatchUtils.batchFor2D(elementA, elementB); + override _batch(preSubElement: SubRenderElement | null, subElement: SubRenderElement): void { + BatchUtils.batchFor2D(preSubElement, subElement); } /** diff --git a/packages/core/src/RenderPipeline/BatchUtils.ts b/packages/core/src/RenderPipeline/BatchUtils.ts index d646aad3cc..5c7345183a 100644 --- a/packages/core/src/RenderPipeline/BatchUtils.ts +++ b/packages/core/src/RenderPipeline/BatchUtils.ts @@ -8,29 +8,29 @@ import { SubRenderElement } from "./SubRenderElement"; export class BatchUtils { protected static _disableBatchTag: ShaderTagKey = ShaderTagKey.getByName("spriteDisableBatching"); - static canBatchSprite(elementA: SubRenderElement, elementB: SubRenderElement): boolean { - if (elementB.subShader.passes[0].getTagValue(BatchUtils._disableBatchTag) === true) { + static canBatchSprite(preSubElement: SubRenderElement, subElement: SubRenderElement): boolean { + if (subElement.subShader.passes[0].getTagValue(BatchUtils._disableBatchTag) === true) { return false; } - if (elementA.subChunk.chunk !== elementB.subChunk.chunk) { + if (preSubElement.subChunk.chunk !== subElement.subChunk.chunk) { return false; } - const rendererA = elementA.component; - const rendererB = elementB.component; - const maskInteractionA = rendererA.maskInteraction; + const preRenderer = preSubElement.component; + const renderer = subElement.component; + const maskInteractionA = preRenderer.maskInteraction; // Compare mask, texture and material return ( - maskInteractionA === rendererB.maskInteraction && - (maskInteractionA === SpriteMaskInteraction.None || rendererA.maskLayer === rendererB.maskLayer) && - elementA.texture === elementB.texture && - elementA.material === elementB.material + maskInteractionA === renderer.maskInteraction && + (maskInteractionA === SpriteMaskInteraction.None || preRenderer.maskLayer === renderer.maskLayer) && + preSubElement.texture === subElement.texture && + preSubElement.material === subElement.material ); } - static canBatchSpriteMask(elementA: SubRenderElement, elementB: SubRenderElement): boolean { - if (elementA.subChunk.chunk !== elementB.subChunk.chunk) { + static canBatchSpriteMask(preSubElement: SubRenderElement, subElement: SubRenderElement): boolean { + if (preSubElement.subChunk.chunk !== subElement.subChunk.chunk) { return false; } @@ -38,20 +38,20 @@ export class BatchUtils { // Compare renderer property return ( - elementA.texture === elementB.texture && - (elementA.component).shaderData.getFloat(alphaCutoffProperty) === - (elementB.component).shaderData.getFloat(alphaCutoffProperty) + preSubElement.texture === subElement.texture && + (preSubElement.component).shaderData.getFloat(alphaCutoffProperty) === + (subElement.component).shaderData.getFloat(alphaCutoffProperty) ); } - static batchFor2D(elementA: SubRenderElement, elementB?: SubRenderElement): void { - const subChunk = elementB ? elementB.subChunk : elementA.subChunk; + static batchFor2D(preSubElement: SubRenderElement | null, subElement: SubRenderElement): void { + const subChunk = subElement.subChunk; const { chunk, indices: subChunkIndices } = subChunk; const length = subChunkIndices.length; let startIndex = chunk.updateIndexLength; - if (elementB) { - elementA.subChunk.subMesh.count += length; + if (preSubElement) { + preSubElement.subChunk.subMesh.count += length; } else { // Reset subMesh const subMesh = subChunk.subMesh; diff --git a/packages/core/src/RenderPipeline/BatcherManager.ts b/packages/core/src/RenderPipeline/BatcherManager.ts index d4196ebe83..135015a5d5 100644 --- a/packages/core/src/RenderPipeline/BatcherManager.ts +++ b/packages/core/src/RenderPipeline/BatcherManager.ts @@ -85,14 +85,14 @@ export class BatcherManager { preSubElement = subElement; preRenderer = renderer; preConstructor = constructor; - renderer._batch(subElement); + renderer._batch(null, subElement); subElement.batched = false; } } else { preSubElement = subElement; preRenderer = renderer; preConstructor = constructor; - renderer._batch(subElement); + renderer._batch(null, subElement); subElement.batched = false; } } diff --git a/packages/core/src/Renderer.ts b/packages/core/src/Renderer.ts index 4544e91698..e4b880164f 100644 --- a/packages/core/src/Renderer.ts +++ b/packages/core/src/Renderer.ts @@ -408,14 +408,14 @@ export class Renderer extends Component { /** * @internal */ - _canBatch(elementA: SubRenderElement, elementB: SubRenderElement): boolean { + _canBatch(preSubElement: SubRenderElement, subElement: SubRenderElement): boolean { return false; } /** * @internal */ - _batch(elementA: SubRenderElement, elementB?: SubRenderElement): void {} + _batch(preSubElement: SubRenderElement | null, subElement: SubRenderElement): void {} /** * Update once per frame per renderer, not influenced by batched. diff --git a/packages/core/src/mesh/MeshRenderer.ts b/packages/core/src/mesh/MeshRenderer.ts index 279319a0e9..730b0b9a75 100644 --- a/packages/core/src/mesh/MeshRenderer.ts +++ b/packages/core/src/mesh/MeshRenderer.ts @@ -175,43 +175,43 @@ export class MeshRenderer extends Renderer { /** * @internal */ - override _canBatch(elementA: SubRenderElement, elementB: SubRenderElement): boolean { + override _canBatch(preSubElement: SubRenderElement, subElement: SubRenderElement): boolean { if (!this._engine._hardwareRenderer.isWebGL2) return false; - const packer = elementA.instanceDataPacker; + const packer = preSubElement.instanceDataPacker; return ( (!packer || packer.instanceCount < packer.maxInstanceCount) && - elementA.primitive === elementB.primitive && - elementA.subPrimitive === elementB.subPrimitive && - elementA.material === elementB.material && - this._isFrontFaceInvert() === (elementB.component)._isFrontFaceInvert() + preSubElement.primitive === subElement.primitive && + preSubElement.subPrimitive === subElement.subPrimitive && + preSubElement.material === subElement.material && + this._isFrontFaceInvert() === (subElement.component)._isFrontFaceInvert() ); } /** * @internal */ - override _batch(elementA: SubRenderElement, elementB?: SubRenderElement): void { - if (!elementB) return; + override _batch(preSubElement: SubRenderElement | null, subElement: SubRenderElement): void { + if (!preSubElement) return; - let packer = elementA.instanceDataPacker; + let packer = preSubElement.instanceDataPacker; if (!packer) { const engine = this._engine; packer = engine._batcherManager.instanceDataPackerPool.getOrCreate(); const compileMacros = packer.compileMacros; - const materialData = elementA.material.shaderData; + const materialData = preSubElement.material.shaderData; ShaderMacroCollection.unionCollection(this._globalShaderMacro, materialData._macroCollection, compileMacros); ShaderMacroCollection.unionCollection(compileMacros, engine._macroCollection, compileMacros); compileMacros.enable(ShaderMacro._gpuInstanceMacro); - const layout = elementA.subShader._getInstanceLayout(engine, compileMacros); + const layout = preSubElement.subShader._getInstanceLayout(engine, compileMacros); if (layout) { packer.setLayout(layout.instanceFields, layout.instanceMaxCount, layout.structSize); } - packer.addRenderer(elementA.component); - elementA.instanceDataPacker = packer; + packer.addRenderer(preSubElement.component); + preSubElement.instanceDataPacker = packer; } - packer.addRenderer(elementB.component); + packer.addRenderer(subElement.component); } private _setMesh(mesh: Mesh): void { diff --git a/packages/core/src/mesh/SkinnedMeshRenderer.ts b/packages/core/src/mesh/SkinnedMeshRenderer.ts index e320a8d23b..d580dcd608 100644 --- a/packages/core/src/mesh/SkinnedMeshRenderer.ts +++ b/packages/core/src/mesh/SkinnedMeshRenderer.ts @@ -124,7 +124,7 @@ export class SkinnedMeshRenderer extends MeshRenderer { /** * @internal */ - override _canBatch(_elementA: SubRenderElement, _elementB: SubRenderElement): boolean { + override _canBatch(_preSubElement: SubRenderElement, _subElement: SubRenderElement): boolean { return false; } diff --git a/packages/ui/src/component/UIRenderer.ts b/packages/ui/src/component/UIRenderer.ts index 59a2fc434c..139932446f 100644 --- a/packages/ui/src/component/UIRenderer.ts +++ b/packages/ui/src/component/UIRenderer.ts @@ -113,13 +113,13 @@ export class UIRenderer extends Renderer implements IGraphics { } // @ts-ignore - override _canBatch(elementA, elementB): boolean { - return BatchUtils.canBatchSprite(elementA, elementB); + override _canBatch(preSubElement, subElement): boolean { + return BatchUtils.canBatchSprite(preSubElement, subElement); } // @ts-ignore - override _batch(elementA, elementB?): void { - BatchUtils.batchFor2D(elementA, elementB); + override _batch(preSubElement, subElement): void { + BatchUtils.batchFor2D(preSubElement, subElement); } // @ts-ignore From 9002cb41d66c7e1265759275d74fd5dc4fc073f4 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Wed, 8 Apr 2026 23:24:30 +0800 Subject: [PATCH 12/91] refactor: move gpuInstanceMacro to InstanceDataPacker and cleanup - Move RENDERER_GPU_INSTANCE macro from ShaderMacro to InstanceDataPacker - Rename getOrCreate() to get() in InstanceDataPackerPool - Clear compileMacros in InstanceDataPacker.reset() --- packages/core/src/RenderPipeline/InstanceDataPacker.ts | 4 ++++ packages/core/src/RenderPipeline/InstanceDataPackerPool.ts | 2 +- packages/core/src/mesh/MeshRenderer.ts | 5 +++-- packages/core/src/shader/ShaderMacro.ts | 3 --- packages/core/src/shader/ShaderPass.ts | 3 ++- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/core/src/RenderPipeline/InstanceDataPacker.ts b/packages/core/src/RenderPipeline/InstanceDataPacker.ts index 64e4f5bf39..4d85feb768 100644 --- a/packages/core/src/RenderPipeline/InstanceDataPacker.ts +++ b/packages/core/src/RenderPipeline/InstanceDataPacker.ts @@ -7,6 +7,7 @@ import { Renderer } from "../Renderer"; import { ShaderMacroCollection } from "../shader/ShaderMacroCollection"; import { ShaderBlockProperty } from "../shader/ShaderBlockProperty"; import { ConstantBufferBindingPoint } from "../shader/enums/ConstantBufferBindingPoint"; +import { ShaderMacro } from "../shader/ShaderMacro"; import { InstanceFieldInfo, ShaderFactory } from "../shaderlib/ShaderFactory"; /** @@ -14,6 +15,8 @@ import { InstanceFieldInfo, ShaderFactory } from "../shaderlib/ShaderFactory"; * Packs per-instance renderer data (ModelMat, Layer, etc.) into a shared UBO for GPU instancing. */ export class InstanceDataPacker { + static gpuInstanceMacro = ShaderMacro.getByName("RENDERER_GPU_INSTANCE"); + static readonly uniformBlockBindingMap: Record = { [ShaderBlockProperty.getByName(ShaderFactory.RENDERER_INSTANCE_BLOCK_NAME)._uniqueId]: ConstantBufferBindingPoint.RendererInstance @@ -99,6 +102,7 @@ export class InstanceDataPacker { reset(): void { this.instanceCount = 0; + this.compileMacros.clear(); } destroy(): void { diff --git a/packages/core/src/RenderPipeline/InstanceDataPackerPool.ts b/packages/core/src/RenderPipeline/InstanceDataPackerPool.ts index 0be3791388..dd974a731c 100644 --- a/packages/core/src/RenderPipeline/InstanceDataPackerPool.ts +++ b/packages/core/src/RenderPipeline/InstanceDataPackerPool.ts @@ -13,7 +13,7 @@ export class InstanceDataPackerPool { this._engine = engine; } - getOrCreate(): InstanceDataPacker { + get(): InstanceDataPacker { return (this._pool[this._poolIndex++] ||= new InstanceDataPacker(this._engine)); } diff --git a/packages/core/src/mesh/MeshRenderer.ts b/packages/core/src/mesh/MeshRenderer.ts index 730b0b9a75..7d712ab1ca 100644 --- a/packages/core/src/mesh/MeshRenderer.ts +++ b/packages/core/src/mesh/MeshRenderer.ts @@ -1,6 +1,7 @@ import { BoundingBox } from "@galacean/engine-math"; import { Entity } from "../Entity"; import { RenderContext } from "../RenderPipeline/RenderContext"; +import { InstanceDataPacker } from "../RenderPipeline/InstanceDataPacker"; import { SubRenderElement } from "../RenderPipeline/SubRenderElement"; import { Renderer, RendererUpdateFlags } from "../Renderer"; import { Logger } from "../base/Logger"; @@ -197,12 +198,12 @@ export class MeshRenderer extends Renderer { let packer = preSubElement.instanceDataPacker; if (!packer) { const engine = this._engine; - packer = engine._batcherManager.instanceDataPackerPool.getOrCreate(); + packer = engine._batcherManager.instanceDataPackerPool.get(); const compileMacros = packer.compileMacros; const materialData = preSubElement.material.shaderData; ShaderMacroCollection.unionCollection(this._globalShaderMacro, materialData._macroCollection, compileMacros); ShaderMacroCollection.unionCollection(compileMacros, engine._macroCollection, compileMacros); - compileMacros.enable(ShaderMacro._gpuInstanceMacro); + compileMacros.enable(InstanceDataPacker.gpuInstanceMacro); const layout = preSubElement.subShader._getInstanceLayout(engine, compileMacros); if (layout) { diff --git a/packages/core/src/shader/ShaderMacro.ts b/packages/core/src/shader/ShaderMacro.ts index c18c0d4ab3..59507aaced 100644 --- a/packages/core/src/shader/ShaderMacro.ts +++ b/packages/core/src/shader/ShaderMacro.ts @@ -13,9 +13,6 @@ export class ShaderMacro { private static _macroCounter: number = 0; private static _macroMap: Record = Object.create(null); - /** @internal */ - static _gpuInstanceMacro = ShaderMacro.getByName("RENDERER_GPU_INSTANCE"); - /** * Get shader macro by name. * @param name - Name of the shader macro diff --git a/packages/core/src/shader/ShaderPass.ts b/packages/core/src/shader/ShaderPass.ts index 2122050d4c..94d4ff788b 100644 --- a/packages/core/src/shader/ShaderPass.ts +++ b/packages/core/src/shader/ShaderPass.ts @@ -1,5 +1,6 @@ import type { ShaderInstruction } from "@galacean/engine-design"; import { Engine } from "../Engine"; +import { InstanceDataPacker } from "../RenderPipeline/InstanceDataPacker"; import { PipelineStage } from "../RenderPipeline/enums/PipelineStage"; import { GLCapabilityType } from "../base/Constant"; import { ShaderFactory, InstanceFieldInfo } from "../shaderlib/ShaderFactory"; @@ -231,7 +232,7 @@ export class ShaderPass extends ShaderPart { macroCollection: ShaderMacroCollection, instanceFields?: InstanceFieldInfo[] ): ShaderProgram { - const isGpuInstance = macroCollection.isEnable(ShaderMacro._gpuInstanceMacro); + const isGpuInstance = macroCollection.isEnable(InstanceDataPacker.gpuInstanceMacro); const { vertexSource, fragmentSource } = this._platformTarget != undefined ? this._compileShaderLabSource(engine, macroCollection, isGpuInstance, instanceFields) From fdc380a4e9b0312d81174fbb1805286fb451f138 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Thu, 9 Apr 2026 00:05:26 +0800 Subject: [PATCH 13/91] refactor: defer instancing layout computation to render phase MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move macro merging, layout computation, and UBO packing from batch phase to render phase. _batch now only collects renderers into a pre-allocated list. RenderQueue.render handles macro union, layout lookup, and splits by maxInstanceCount for sub-batch rendering. - SubRenderElement: instanceDataPacker → instancedRenderers (pooled array) - MeshRenderer._canBatch: remove maxInstanceCount check - MeshRenderer._batch: only push renderers, zero allocation - InstanceDataPacker: remove compileMacros/addRenderer/instanceCount, add packAndUpload(renderers, start, count) - InstanceDataPackerPool: remove uploadBuffer, simplify reset - BatcherManager: remove instancing uploadBuffer call --- .../core/src/RenderPipeline/BatcherManager.ts | 1 - .../src/RenderPipeline/InstanceDataPacker.ts | 31 +++------- .../RenderPipeline/InstanceDataPackerPool.ts | 14 ----- .../core/src/RenderPipeline/RenderQueue.ts | 59 +++++++++++-------- .../src/RenderPipeline/SubRenderElement.ts | 7 +-- packages/core/src/mesh/MeshRenderer.ts | 27 ++------- 6 files changed, 50 insertions(+), 89 deletions(-) diff --git a/packages/core/src/RenderPipeline/BatcherManager.ts b/packages/core/src/RenderPipeline/BatcherManager.ts index 135015a5d5..28771db477 100644 --- a/packages/core/src/RenderPipeline/BatcherManager.ts +++ b/packages/core/src/RenderPipeline/BatcherManager.ts @@ -104,6 +104,5 @@ export class BatcherManager { this._primitiveChunkManager2D?.uploadBuffer(); this._primitiveChunkManagerMask?.uploadBuffer(); this._primitiveChunkManagerUI?.uploadBuffer(); - this._instanceDataPackerPool?.uploadBuffer(); } } diff --git a/packages/core/src/RenderPipeline/InstanceDataPacker.ts b/packages/core/src/RenderPipeline/InstanceDataPacker.ts index 4d85feb768..87003064a0 100644 --- a/packages/core/src/RenderPipeline/InstanceDataPacker.ts +++ b/packages/core/src/RenderPipeline/InstanceDataPacker.ts @@ -4,10 +4,9 @@ import { BufferBindFlag } from "../graphic/enums/BufferBindFlag"; import { BufferUsage } from "../graphic/enums/BufferUsage"; import { SetDataOptions } from "../graphic/enums/SetDataOptions"; import { Renderer } from "../Renderer"; -import { ShaderMacroCollection } from "../shader/ShaderMacroCollection"; import { ShaderBlockProperty } from "../shader/ShaderBlockProperty"; -import { ConstantBufferBindingPoint } from "../shader/enums/ConstantBufferBindingPoint"; import { ShaderMacro } from "../shader/ShaderMacro"; +import { ConstantBufferBindingPoint } from "../shader/enums/ConstantBufferBindingPoint"; import { InstanceFieldInfo, ShaderFactory } from "../shaderlib/ShaderFactory"; /** @@ -22,17 +21,13 @@ export class InstanceDataPacker { ConstantBufferBindingPoint.RendererInstance }; - instanceCount = 0; - maxInstanceCount = Infinity; instanceFields: InstanceFieldInfo[]; - compileMacros: ShaderMacroCollection = new ShaderMacroCollection(); uboBuffer: Buffer; private _engine: Engine; private _uboData: ArrayBuffer; private _floatView: Float32Array; private _intView: Int32Array; - private _renderers: Renderer[] = []; private _structSize = 0; constructor(engine: Engine) { @@ -40,11 +35,10 @@ export class InstanceDataPacker { } /** - * Set UBO layout from instance fields. + * Set UBO layout and allocate buffer if needed. */ setLayout(instanceFields: InstanceFieldInfo[], maxInstanceCount: number, structSize: number): void { this.instanceFields = instanceFields; - this.maxInstanceCount = maxInstanceCount; this._structSize = structSize; const totalBytes = maxInstanceCount * structSize; // Only reallocate when buffer is too small @@ -57,25 +51,20 @@ export class InstanceDataPacker { } } - addRenderer(renderer: Renderer): void { - this._renderers[this.instanceCount++] = renderer; - } - - prepare(): void { - const count = this.instanceCount; - if (count === 0) return; - + /** + * Pack renderer data into UBO and upload to GPU. + */ + packAndUpload(renderers: Renderer[], start: number, count: number): void { const fields = this.instanceFields; if (!fields) return; const structSize = this._structSize; const elementsPerInstance = structSize / 4; const floatView = this._floatView; const intView = this._intView; - const renderers = this._renderers; const modelMatId = Renderer._worldMatrixProperty._uniqueId; for (let i = 0; i < count; i++) { - const renderer = renderers[i]; + const renderer = renderers[start + i]; const propertyValueMap = renderer.shaderData._propertyValueMap; const baseOffset = i * elementsPerInstance; @@ -100,16 +89,12 @@ export class InstanceDataPacker { this.uboBuffer.setData(floatView, 0, 0, uploadElements, SetDataOptions.Discard); } - reset(): void { - this.instanceCount = 0; - this.compileMacros.clear(); - } + destroy(): void { this.uboBuffer?.destroy(); this._uboData = null; this._floatView = null; this._intView = null; - this._renderers = null; } } diff --git a/packages/core/src/RenderPipeline/InstanceDataPackerPool.ts b/packages/core/src/RenderPipeline/InstanceDataPackerPool.ts index dd974a731c..b67d3f4cd2 100644 --- a/packages/core/src/RenderPipeline/InstanceDataPackerPool.ts +++ b/packages/core/src/RenderPipeline/InstanceDataPackerPool.ts @@ -17,21 +17,7 @@ export class InstanceDataPackerPool { return (this._pool[this._poolIndex++] ||= new InstanceDataPacker(this._engine)); } - uploadBuffer(): void { - const pool = this._pool; - for (let i = 0, n = this._poolIndex; i < n; i++) { - const packer = pool[i]; - if (packer.instanceCount > 1) { - packer.prepare(); - } - } - } - reset(): void { - const pool = this._pool; - for (let i = 0, n = this._poolIndex; i < n; i++) { - pool[i].reset(); - } this._poolIndex = 0; } diff --git a/packages/core/src/RenderPipeline/RenderQueue.ts b/packages/core/src/RenderPipeline/RenderQueue.ts index abf673f2c5..787587caee 100644 --- a/packages/core/src/RenderPipeline/RenderQueue.ts +++ b/packages/core/src/RenderPipeline/RenderQueue.ts @@ -62,8 +62,8 @@ export class RenderQueue { for (let i = 0; i < length; i++) { const subElement = batchedSubElements[i]; - const { component: renderer, material, instanceDataPacker } = subElement; - const isInstanced = !!instanceDataPacker; + const { component: renderer, material, instancedRenderers } = subElement; + const isInstanced = instancedRenderers.length > 0; // Instancing: transform data is packed in UBO, skip per-renderer update if (!isInstanced) { @@ -104,18 +104,24 @@ export class RenderQueue { const { shaderData: rendererData, instanceId: rendererId } = renderer; const { shaderData: materialData, instanceId: materialId, renderStates } = material; - let compileMacros: ShaderMacroCollection; + // Build compile macros + const compileMacros = Shader._compileMacros; + ShaderMacroCollection.unionCollection( + renderer._globalShaderMacro, + materialData._macroCollection, + compileMacros + ); + ShaderMacroCollection.unionCollection(compileMacros, engine._macroCollection, compileMacros); + + // For instancing: enable macro and get layout + let instanceFields = undefined; + let layout = undefined; if (isInstanced) { - compileMacros = instanceDataPacker.compileMacros; - } else { - // Union render global macro and material self macro - compileMacros = Shader._compileMacros; - ShaderMacroCollection.unionCollection( - renderer._globalShaderMacro, - materialData._macroCollection, - compileMacros - ); - ShaderMacroCollection.unionCollection(compileMacros, engine._macroCollection, compileMacros); + compileMacros.enable(InstanceDataPacker.gpuInstanceMacro); + layout = subElement.subShader._getInstanceLayout(engine, compileMacros); + if (layout) { + instanceFields = layout.instanceFields; + } } for (let j = 0, m = shaderPasses.length; j < m; j++) { @@ -143,11 +149,7 @@ export class RenderQueue { } } - const program = shaderPass._getShaderProgram( - engine, - compileMacros, - isInstanced ? instanceDataPacker.instanceFields : undefined - ); + const program = shaderPass._getShaderProgram(engine, compileMacros, instanceFields); if (!program.isValid) { continue; } @@ -220,17 +222,26 @@ export class RenderQueue { customStates ); - if (isInstanced) { - if (instanceDataPacker.uboBuffer) { + if (isInstanced && layout) { + const totalCount = instancedRenderers.length; + const maxCount = layout.instanceMaxCount; + const packerPool = engine._batcherManager.instanceDataPackerPool; + + for (let start = 0; start < totalCount; start += maxCount) { + const count = Math.min(maxCount, totalCount - start); + const packer = packerPool.get(); + packer.setLayout(instanceFields, maxCount, layout.structSize); + packer.packAndUpload(instancedRenderers, start, count); + program.bindUniformBlocks(InstanceDataPacker.uniformBlockBindingMap); rhi.bindUniformBufferBase( ConstantBufferBindingPoint.RendererInstance, - instanceDataPacker.uboBuffer._platformBuffer + packer.uboBuffer._platformBuffer ); + primitive.instanceCount = count; + rhi.drawPrimitive(primitive, subElement.subPrimitive, program); + primitive.instanceCount = 0; } - primitive.instanceCount = instanceDataPacker.instanceCount; - rhi.drawPrimitive(primitive, subElement.subPrimitive, program); - primitive.instanceCount = 0; } else { rhi.drawPrimitive(primitive, subElement.subPrimitive, program); } diff --git a/packages/core/src/RenderPipeline/SubRenderElement.ts b/packages/core/src/RenderPipeline/SubRenderElement.ts index fd3f1b249b..d6887b6a72 100644 --- a/packages/core/src/RenderPipeline/SubRenderElement.ts +++ b/packages/core/src/RenderPipeline/SubRenderElement.ts @@ -5,7 +5,6 @@ import { ShaderData, SubShader } from "../shader"; import { Texture2D } from "../texture"; import { IPoolElement } from "../utils/ObjectPool"; import { RenderQueueFlags } from "./BasicRenderPipeline"; -import { InstanceDataPacker } from "./InstanceDataPacker"; import { SubPrimitiveChunk } from "./SubPrimitiveChunk"; export class SubRenderElement implements IPoolElement { @@ -17,7 +16,7 @@ export class SubRenderElement implements IPoolElement { shaderData?: ShaderData; batched: boolean; renderQueueFlags: RenderQueueFlags; - instanceDataPacker: InstanceDataPacker; + instancedRenderers: Renderer[] = []; // @todo: maybe should remove later texture?: Texture2D; @@ -37,7 +36,7 @@ export class SubRenderElement implements IPoolElement { this.subPrimitive = subPrimitive; this.texture = texture; this.subChunk = subChunk; - this.instanceDataPacker = null; + this.instancedRenderers.length = 0; } dispose(): void { @@ -47,7 +46,7 @@ export class SubRenderElement implements IPoolElement { this.subPrimitive = null; this.subShader = null; this.shaderData && (this.shaderData = null); - this.instanceDataPacker = null; + this.instancedRenderers.length = 0; this.texture && (this.texture = null); this.subChunk && (this.subChunk = null); diff --git a/packages/core/src/mesh/MeshRenderer.ts b/packages/core/src/mesh/MeshRenderer.ts index 7d712ab1ca..d26f470499 100644 --- a/packages/core/src/mesh/MeshRenderer.ts +++ b/packages/core/src/mesh/MeshRenderer.ts @@ -1,14 +1,12 @@ import { BoundingBox } from "@galacean/engine-math"; import { Entity } from "../Entity"; import { RenderContext } from "../RenderPipeline/RenderContext"; -import { InstanceDataPacker } from "../RenderPipeline/InstanceDataPacker"; import { SubRenderElement } from "../RenderPipeline/SubRenderElement"; import { Renderer, RendererUpdateFlags } from "../Renderer"; import { Logger } from "../base/Logger"; import { ignoreClone } from "../clone/CloneManager"; import { Mesh, MeshModifyFlags } from "../graphic/Mesh"; import { ShaderMacro } from "../shader/ShaderMacro"; -import { ShaderMacroCollection } from "../shader/ShaderMacroCollection"; /** * MeshRenderer Component. @@ -178,10 +176,7 @@ export class MeshRenderer extends Renderer { */ override _canBatch(preSubElement: SubRenderElement, subElement: SubRenderElement): boolean { if (!this._engine._hardwareRenderer.isWebGL2) return false; - - const packer = preSubElement.instanceDataPacker; return ( - (!packer || packer.instanceCount < packer.maxInstanceCount) && preSubElement.primitive === subElement.primitive && preSubElement.subPrimitive === subElement.subPrimitive && preSubElement.material === subElement.material && @@ -194,25 +189,11 @@ export class MeshRenderer extends Renderer { */ override _batch(preSubElement: SubRenderElement | null, subElement: SubRenderElement): void { if (!preSubElement) return; - - let packer = preSubElement.instanceDataPacker; - if (!packer) { - const engine = this._engine; - packer = engine._batcherManager.instanceDataPackerPool.get(); - const compileMacros = packer.compileMacros; - const materialData = preSubElement.material.shaderData; - ShaderMacroCollection.unionCollection(this._globalShaderMacro, materialData._macroCollection, compileMacros); - ShaderMacroCollection.unionCollection(compileMacros, engine._macroCollection, compileMacros); - compileMacros.enable(InstanceDataPacker.gpuInstanceMacro); - - const layout = preSubElement.subShader._getInstanceLayout(engine, compileMacros); - if (layout) { - packer.setLayout(layout.instanceFields, layout.instanceMaxCount, layout.structSize); - } - packer.addRenderer(preSubElement.component); - preSubElement.instanceDataPacker = packer; + const renderers = preSubElement.instancedRenderers; + if (renderers.length === 0) { + renderers.push(preSubElement.component); } - packer.addRenderer(subElement.component); + renderers.push(subElement.component); } private _setMesh(mesh: Mesh): void { From c40a679467ed284c42584972e0b087a55e5b7cb7 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Thu, 9 Apr 2026 00:26:46 +0800 Subject: [PATCH 14/91] refactor: replace InstanceDataPackerPool with single shared packer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Packer is now stateless (setLayout + packAndUpload + draw), so only one instance is needed. Discard upload ensures no GPU stall when reusing the same buffer across shadow and main passes. - Delete InstanceDataPackerPool.ts - BatcherManager: instanceDataPackerPool → instanceDataPacker - Remove resetInstanceDataPackerPool lifecycle - Saves GPU memory by using single buffer instead of pool --- .../src/RenderPipeline/BasicRenderPipeline.ts | 3 +- .../core/src/RenderPipeline/BatcherManager.ts | 21 +++++-------- .../core/src/RenderPipeline/CullingResults.ts | 1 - .../RenderPipeline/InstanceDataPackerPool.ts | 30 ------------------- .../core/src/RenderPipeline/RenderQueue.ts | 5 ++-- 5 files changed, 11 insertions(+), 49 deletions(-) delete mode 100644 packages/core/src/RenderPipeline/InstanceDataPackerPool.ts diff --git a/packages/core/src/RenderPipeline/BasicRenderPipeline.ts b/packages/core/src/RenderPipeline/BasicRenderPipeline.ts index 613f61d372..9342e974d2 100644 --- a/packages/core/src/RenderPipeline/BasicRenderPipeline.ts +++ b/packages/core/src/RenderPipeline/BasicRenderPipeline.ts @@ -128,11 +128,12 @@ export class BasicRenderPipeline { !(finalClearFlags & CameraClearFlags.Color) && (!this._canUseBlitFrameBuffer || isSRGBBackground); + const batcherManager = engine._batcherManager; + if (scene.castShadows && sunlight && sunlight.shadowType !== ShadowType.None) { this._cascadedShadowCasterPass.onRender(context); } - const batcherManager = engine._batcherManager; cullingResults.reset(); // Depth use camera's view and projection matrix diff --git a/packages/core/src/RenderPipeline/BatcherManager.ts b/packages/core/src/RenderPipeline/BatcherManager.ts index 28771db477..05fc95b721 100644 --- a/packages/core/src/RenderPipeline/BatcherManager.ts +++ b/packages/core/src/RenderPipeline/BatcherManager.ts @@ -1,6 +1,6 @@ import { Engine } from "../Engine"; import { Renderer } from "../Renderer"; -import { InstanceDataPackerPool } from "./InstanceDataPackerPool"; +import { InstanceDataPacker } from "./InstanceDataPacker"; import { PrimitiveChunkManager } from "./PrimitiveChunkManager"; import { RenderQueue } from "./RenderQueue"; import { SubRenderElement } from "./SubRenderElement"; @@ -12,12 +12,12 @@ export class BatcherManager { private _primitiveChunkManager2D: PrimitiveChunkManager; private _primitiveChunkManagerMask: PrimitiveChunkManager; private _primitiveChunkManagerUI: PrimitiveChunkManager; - private _instanceDataPackerPool: InstanceDataPackerPool; + private _instanceDataPacker: InstanceDataPacker; constructor(public engine: Engine) {} - get instanceDataPackerPool(): InstanceDataPackerPool { - return (this._instanceDataPackerPool ||= new InstanceDataPackerPool(this.engine)); + get instanceDataPacker(): InstanceDataPacker { + return (this._instanceDataPacker ||= new InstanceDataPacker(this.engine)); } get primitiveChunkManager2D(): PrimitiveChunkManager { @@ -45,19 +45,12 @@ export class BatcherManager { this._primitiveChunkManagerUI.destroy(); this._primitiveChunkManagerUI = null; } - if (this._instanceDataPackerPool) { - this._instanceDataPackerPool.destroy(); - this._instanceDataPackerPool = null; + if (this._instanceDataPacker) { + this._instanceDataPacker.destroy(); + this._instanceDataPacker = null; } } - /** - * Reset instance batch pool at the start of each frame's batch phase. - */ - resetInstanceDataPackerPool(): void { - this._instanceDataPackerPool?.reset(); - } - batch(renderQueue: RenderQueue): void { const { elements, batchedSubElements, renderQueueType } = renderQueue; diff --git a/packages/core/src/RenderPipeline/CullingResults.ts b/packages/core/src/RenderPipeline/CullingResults.ts index 76c377c280..673e0694d3 100644 --- a/packages/core/src/RenderPipeline/CullingResults.ts +++ b/packages/core/src/RenderPipeline/CullingResults.ts @@ -25,7 +25,6 @@ export class CullingResults { } sortBatch(batcherManager: BatcherManager) { - batcherManager.resetInstanceDataPackerPool(); this.opaqueQueue.sortBatch(RenderQueue.compareForOpaque, batcherManager); this.alphaTestQueue.sortBatch(RenderQueue.compareForOpaque, batcherManager); this.transparentQueue.sortBatch(RenderQueue.compareForTransparent, batcherManager); diff --git a/packages/core/src/RenderPipeline/InstanceDataPackerPool.ts b/packages/core/src/RenderPipeline/InstanceDataPackerPool.ts deleted file mode 100644 index b67d3f4cd2..0000000000 --- a/packages/core/src/RenderPipeline/InstanceDataPackerPool.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Engine } from "../Engine"; -import { InstanceDataPacker } from "./InstanceDataPacker"; - -/** - * @internal - */ -export class InstanceDataPackerPool { - private _engine: Engine; - private _pool = new Array(); - private _poolIndex = 0; - - constructor(engine: Engine) { - this._engine = engine; - } - - get(): InstanceDataPacker { - return (this._pool[this._poolIndex++] ||= new InstanceDataPacker(this._engine)); - } - - reset(): void { - this._poolIndex = 0; - } - - destroy(): void { - const pool = this._pool; - for (let i = 0, n = pool.length; i < n; i++) { - pool[i].destroy(); - } - } -} diff --git a/packages/core/src/RenderPipeline/RenderQueue.ts b/packages/core/src/RenderPipeline/RenderQueue.ts index 787587caee..1673c09f10 100644 --- a/packages/core/src/RenderPipeline/RenderQueue.ts +++ b/packages/core/src/RenderPipeline/RenderQueue.ts @@ -225,12 +225,11 @@ export class RenderQueue { if (isInstanced && layout) { const totalCount = instancedRenderers.length; const maxCount = layout.instanceMaxCount; - const packerPool = engine._batcherManager.instanceDataPackerPool; + const packer = engine._batcherManager.instanceDataPacker; + packer.setLayout(instanceFields, maxCount, layout.structSize); for (let start = 0; start < totalCount; start += maxCount) { const count = Math.min(maxCount, totalCount - start); - const packer = packerPool.get(); - packer.setLayout(instanceFields, maxCount, layout.structSize); packer.packAndUpload(instancedRenderers, start, count); program.bindUniformBlocks(InstanceDataPacker.uniformBlockBindingMap); From b8935e3bb58f3be89bde9cfea01a32e4685952c5 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Thu, 9 Apr 2026 00:28:25 +0800 Subject: [PATCH 15/91] refactor: move batcherManager declaration to first usage --- packages/core/src/RenderPipeline/BasicRenderPipeline.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/core/src/RenderPipeline/BasicRenderPipeline.ts b/packages/core/src/RenderPipeline/BasicRenderPipeline.ts index 9342e974d2..2c99c9eda9 100644 --- a/packages/core/src/RenderPipeline/BasicRenderPipeline.ts +++ b/packages/core/src/RenderPipeline/BasicRenderPipeline.ts @@ -128,8 +128,6 @@ export class BasicRenderPipeline { !(finalClearFlags & CameraClearFlags.Color) && (!this._canUseBlitFrameBuffer || isSRGBBackground); - const batcherManager = engine._batcherManager; - if (scene.castShadows && sunlight && sunlight.shadowType !== ShadowType.None) { this._cascadedShadowCasterPass.onRender(context); } @@ -141,6 +139,7 @@ export class BasicRenderPipeline { context.applyVirtualCamera(camera._virtualCamera, depthPassEnabled); this._prepareRender(context); + const batcherManager = engine._batcherManager; cullingResults.sortBatch(batcherManager); batcherManager.uploadBuffer(); From 002d7ab5cc5db65094295a402f2375dbf1d6bd95 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Thu, 9 Apr 2026 00:42:06 +0800 Subject: [PATCH 16/91] refactor: rename InstanceDataPacker to InstanceBatch Rename class, file, and all references to better reflect its role as an instance batch manager rather than a generic data packer. --- .../core/src/RenderPipeline/BatcherManager.ts | 14 ++++++------- ...InstanceDataPacker.ts => InstanceBatch.ts} | 20 +++++++++---------- .../core/src/RenderPipeline/RenderQueue.ts | 14 ++++++------- packages/core/src/shader/ShaderPass.ts | 4 ++-- 4 files changed, 25 insertions(+), 27 deletions(-) rename packages/core/src/RenderPipeline/{InstanceDataPacker.ts => InstanceBatch.ts} (84%) diff --git a/packages/core/src/RenderPipeline/BatcherManager.ts b/packages/core/src/RenderPipeline/BatcherManager.ts index 05fc95b721..03d965825f 100644 --- a/packages/core/src/RenderPipeline/BatcherManager.ts +++ b/packages/core/src/RenderPipeline/BatcherManager.ts @@ -1,6 +1,6 @@ import { Engine } from "../Engine"; import { Renderer } from "../Renderer"; -import { InstanceDataPacker } from "./InstanceDataPacker"; +import { InstanceBatch } from "./InstanceBatch"; import { PrimitiveChunkManager } from "./PrimitiveChunkManager"; import { RenderQueue } from "./RenderQueue"; import { SubRenderElement } from "./SubRenderElement"; @@ -12,12 +12,12 @@ export class BatcherManager { private _primitiveChunkManager2D: PrimitiveChunkManager; private _primitiveChunkManagerMask: PrimitiveChunkManager; private _primitiveChunkManagerUI: PrimitiveChunkManager; - private _instanceDataPacker: InstanceDataPacker; + private _instanceBatch: InstanceBatch; constructor(public engine: Engine) {} - get instanceDataPacker(): InstanceDataPacker { - return (this._instanceDataPacker ||= new InstanceDataPacker(this.engine)); + get instanceBatch(): InstanceBatch { + return (this._instanceBatch ||= new InstanceBatch(this.engine)); } get primitiveChunkManager2D(): PrimitiveChunkManager { @@ -45,9 +45,9 @@ export class BatcherManager { this._primitiveChunkManagerUI.destroy(); this._primitiveChunkManagerUI = null; } - if (this._instanceDataPacker) { - this._instanceDataPacker.destroy(); - this._instanceDataPacker = null; + if (this._instanceBatch) { + this._instanceBatch.destroy(); + this._instanceBatch = null; } } diff --git a/packages/core/src/RenderPipeline/InstanceDataPacker.ts b/packages/core/src/RenderPipeline/InstanceBatch.ts similarity index 84% rename from packages/core/src/RenderPipeline/InstanceDataPacker.ts rename to packages/core/src/RenderPipeline/InstanceBatch.ts index 87003064a0..3b03f14364 100644 --- a/packages/core/src/RenderPipeline/InstanceDataPacker.ts +++ b/packages/core/src/RenderPipeline/InstanceBatch.ts @@ -11,9 +11,9 @@ import { InstanceFieldInfo, ShaderFactory } from "../shaderlib/ShaderFactory"; /** * @internal - * Packs per-instance renderer data (ModelMat, Layer, etc.) into a shared UBO for GPU instancing. + * Manages a UBO for GPU instancing, packing per-instance renderer data (ModelMat, Layer, etc.). */ -export class InstanceDataPacker { +export class InstanceBatch { static gpuInstanceMacro = ShaderMacro.getByName("RENDERER_GPU_INSTANCE"); static readonly uniformBlockBindingMap: Record = { @@ -22,7 +22,7 @@ export class InstanceDataPacker { }; instanceFields: InstanceFieldInfo[]; - uboBuffer: Buffer; + nativeBuffer: Buffer; private _engine: Engine; private _uboData: ArrayBuffer; @@ -42,19 +42,19 @@ export class InstanceDataPacker { this._structSize = structSize; const totalBytes = maxInstanceCount * structSize; // Only reallocate when buffer is too small - if (!this.uboBuffer || totalBytes > this.uboBuffer.byteLength) { + if (!this.nativeBuffer || totalBytes > this.nativeBuffer.byteLength) { this._uboData = new ArrayBuffer(totalBytes); this._floatView = new Float32Array(this._uboData); this._intView = new Int32Array(this._uboData); - this.uboBuffer?.destroy(); - this.uboBuffer = new Buffer(this._engine, BufferBindFlag.ConstantBuffer, totalBytes, BufferUsage.Dynamic); + this.nativeBuffer?.destroy(); + this.nativeBuffer = new Buffer(this._engine, BufferBindFlag.ConstantBuffer, totalBytes, BufferUsage.Dynamic); } } /** * Pack renderer data into UBO and upload to GPU. */ - packAndUpload(renderers: Renderer[], start: number, count: number): void { + upload(renderers: Renderer[], start: number, count: number): void { const fields = this.instanceFields; if (!fields) return; const structSize = this._structSize; @@ -86,13 +86,11 @@ export class InstanceDataPacker { } const uploadElements = count * elementsPerInstance; - this.uboBuffer.setData(floatView, 0, 0, uploadElements, SetDataOptions.Discard); + this.nativeBuffer.setData(floatView, 0, 0, uploadElements, SetDataOptions.Discard); } - - destroy(): void { - this.uboBuffer?.destroy(); + this.nativeBuffer?.destroy(); this._uboData = null; this._floatView = null; this._intView = null; diff --git a/packages/core/src/RenderPipeline/RenderQueue.ts b/packages/core/src/RenderPipeline/RenderQueue.ts index 1673c09f10..518dc7871f 100644 --- a/packages/core/src/RenderPipeline/RenderQueue.ts +++ b/packages/core/src/RenderPipeline/RenderQueue.ts @@ -5,7 +5,7 @@ import { RenderQueueType, Shader } from "../shader"; import { ConstantBufferBindingPoint } from "../shader/enums/ConstantBufferBindingPoint"; import { ShaderMacroCollection } from "../shader/ShaderMacroCollection"; import { BatcherManager } from "./BatcherManager"; -import { InstanceDataPacker } from "./InstanceDataPacker"; +import { InstanceBatch } from "./InstanceBatch"; import { ContextRendererUpdateFlag, RenderContext } from "./RenderContext"; import { RenderElement } from "./RenderElement"; import { SubRenderElement } from "./SubRenderElement"; @@ -117,7 +117,7 @@ export class RenderQueue { let instanceFields = undefined; let layout = undefined; if (isInstanced) { - compileMacros.enable(InstanceDataPacker.gpuInstanceMacro); + compileMacros.enable(InstanceBatch.gpuInstanceMacro); layout = subElement.subShader._getInstanceLayout(engine, compileMacros); if (layout) { instanceFields = layout.instanceFields; @@ -225,17 +225,17 @@ export class RenderQueue { if (isInstanced && layout) { const totalCount = instancedRenderers.length; const maxCount = layout.instanceMaxCount; - const packer = engine._batcherManager.instanceDataPacker; + const instanceBatch = engine._batcherManager.instanceBatch; - packer.setLayout(instanceFields, maxCount, layout.structSize); + instanceBatch.setLayout(instanceFields, maxCount, layout.structSize); for (let start = 0; start < totalCount; start += maxCount) { const count = Math.min(maxCount, totalCount - start); - packer.packAndUpload(instancedRenderers, start, count); + instanceBatch.upload(instancedRenderers, start, count); - program.bindUniformBlocks(InstanceDataPacker.uniformBlockBindingMap); + program.bindUniformBlocks(InstanceBatch.uniformBlockBindingMap); rhi.bindUniformBufferBase( ConstantBufferBindingPoint.RendererInstance, - packer.uboBuffer._platformBuffer + instanceBatch.nativeBuffer._platformBuffer ); primitive.instanceCount = count; rhi.drawPrimitive(primitive, subElement.subPrimitive, program); diff --git a/packages/core/src/shader/ShaderPass.ts b/packages/core/src/shader/ShaderPass.ts index 94d4ff788b..3951d02257 100644 --- a/packages/core/src/shader/ShaderPass.ts +++ b/packages/core/src/shader/ShaderPass.ts @@ -1,6 +1,6 @@ import type { ShaderInstruction } from "@galacean/engine-design"; import { Engine } from "../Engine"; -import { InstanceDataPacker } from "../RenderPipeline/InstanceDataPacker"; +import { InstanceBatch } from "../RenderPipeline/InstanceBatch"; import { PipelineStage } from "../RenderPipeline/enums/PipelineStage"; import { GLCapabilityType } from "../base/Constant"; import { ShaderFactory, InstanceFieldInfo } from "../shaderlib/ShaderFactory"; @@ -232,7 +232,7 @@ export class ShaderPass extends ShaderPart { macroCollection: ShaderMacroCollection, instanceFields?: InstanceFieldInfo[] ): ShaderProgram { - const isGpuInstance = macroCollection.isEnable(InstanceDataPacker.gpuInstanceMacro); + const isGpuInstance = macroCollection.isEnable(InstanceBatch.gpuInstanceMacro); const { vertexSource, fragmentSource } = this._platformTarget != undefined ? this._compileShaderLabSource(engine, macroCollection, isGpuInstance, instanceFields) From f98486702325d7ec8e91bf98f0a5c2165785be45 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Thu, 9 Apr 2026 00:50:36 +0800 Subject: [PATCH 17/91] refactor: rename MacroCachePool to MacroMap It's a macro-keyed map, not a pool (no borrow/return semantics). --- packages/core/src/Engine.ts | 8 ++++---- .../core/src/shader/{MacroCachePool.ts => MacroMap.ts} | 2 +- packages/core/src/shader/ShaderPass.ts | 4 ++-- packages/core/src/shader/SubShader.ts | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) rename packages/core/src/shader/{MacroCachePool.ts => MacroMap.ts} (98%) diff --git a/packages/core/src/Engine.ts b/packages/core/src/Engine.ts index 6976ac2b03..e266a94416 100644 --- a/packages/core/src/Engine.ts +++ b/packages/core/src/Engine.ts @@ -33,7 +33,7 @@ import { Shader } from "./shader/Shader"; import { ShaderMacro } from "./shader/ShaderMacro"; import { ShaderMacroCollection } from "./shader/ShaderMacroCollection"; import { ShaderPool } from "./shader/ShaderPool"; -import { MacroCachePool } from "./shader/MacroCachePool"; +import { MacroMap } from "./shader/MacroMap"; import { ShaderProgram } from "./shader/ShaderProgram"; import { RenderState } from "./shader/state/RenderState"; import { Texture2D, TextureFormat } from "./texture"; @@ -113,7 +113,7 @@ export class Engine extends EventDispatcher { /* @internal */ _renderCount: number = 0; /* @internal */ - _shaderProgramPools: MacroCachePool[] = []; + _shaderProgramPools: MacroMap[] = []; /** @internal */ _fontMap: Record = {}; /** @internal */ @@ -542,7 +542,7 @@ export class Engine extends EventDispatcher { /** * @internal */ - _getShaderProgramPool(index: number, trackPools?: MacroCachePool[]): MacroCachePool { + _getShaderProgramPool(index: number, trackPools?: MacroMap[]): MacroMap { const shaderProgramPools = this._shaderProgramPools; let pool = shaderProgramPools[index]; if (!pool) { @@ -550,7 +550,7 @@ export class Engine extends EventDispatcher { if (length > shaderProgramPools.length) { shaderProgramPools.length = length; } - shaderProgramPools[index] = pool = new MacroCachePool(this); + shaderProgramPools[index] = pool = new MacroMap(this); trackPools?.push(pool); } return pool; diff --git a/packages/core/src/shader/MacroCachePool.ts b/packages/core/src/shader/MacroMap.ts similarity index 98% rename from packages/core/src/shader/MacroCachePool.ts rename to packages/core/src/shader/MacroMap.ts index ed3747a018..eddb296bc8 100644 --- a/packages/core/src/shader/MacroCachePool.ts +++ b/packages/core/src/shader/MacroMap.ts @@ -9,7 +9,7 @@ type Tree = { * Cache pool keyed by ShaderMacroCollection bitmask. * @internal */ -export class MacroCachePool { +export class MacroMap { engine: Engine; private _cacheHierarchyDepth: number = 1; diff --git a/packages/core/src/shader/ShaderPass.ts b/packages/core/src/shader/ShaderPass.ts index 3951d02257..4b685c1a91 100644 --- a/packages/core/src/shader/ShaderPass.ts +++ b/packages/core/src/shader/ShaderPass.ts @@ -8,7 +8,7 @@ import { resolveIfdef } from "../shaderlib/GLSLIfdefResolver"; import { ShaderMacro } from "./ShaderMacro"; import { ShaderMacroCollection } from "./ShaderMacroCollection"; import { ShaderPart } from "./ShaderPart"; -import { MacroCachePool } from "./MacroCachePool"; +import { MacroMap } from "./MacroMap"; import { ShaderProgram } from "./ShaderProgram"; import { ShaderProperty } from "./ShaderProperty"; import { ShaderLanguage } from "./enums/ShaderLanguage"; @@ -55,7 +55,7 @@ export class ShaderPass extends ShaderPart { /** @internal */ _renderStateDataMap: Record = {}; /** @internal */ - _shaderProgramPools: MacroCachePool[] = []; + _shaderProgramPools: MacroMap[] = []; private _vertexSource?: string; private _fragmentSource?: string; diff --git a/packages/core/src/shader/SubShader.ts b/packages/core/src/shader/SubShader.ts index e22a8e7399..758a9f84b2 100644 --- a/packages/core/src/shader/SubShader.ts +++ b/packages/core/src/shader/SubShader.ts @@ -1,6 +1,6 @@ import { Engine } from "../Engine"; import { ShaderFactory, InstanceLayout } from "../shaderlib/ShaderFactory"; -import { MacroCachePool } from "./MacroCachePool"; +import { MacroMap } from "./MacroMap"; import { ShaderMacroCollection } from "./ShaderMacroCollection"; import { ShaderPart } from "./ShaderPart"; import { ShaderPass } from "./ShaderPass"; @@ -10,7 +10,7 @@ import { ShaderPass } from "./ShaderPass"; */ export class SubShader extends ShaderPart { private _passes: ShaderPass[]; - private _layoutCache: MacroCachePool = new MacroCachePool(); + private _layoutCache: MacroMap = new MacroMap(); /** * Sub shader passes. From 1fc3f4f24b6b39da4c6ab77e1125f8908475d944 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Thu, 9 Apr 2026 00:53:13 +0800 Subject: [PATCH 18/91] refactor: rename shaderProgramPool(s) variables to shaderProgramMap(s) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Align variable and method names with MacroMap rename — these are maps, not pools. --- packages/core/src/Engine.ts | 24 ++++++++++++------------ packages/core/src/shader/MacroMap.ts | 2 +- packages/core/src/shader/Shader.ts | 12 ++++++------ packages/core/src/shader/ShaderPass.ts | 20 ++++++++++---------- 4 files changed, 29 insertions(+), 29 deletions(-) diff --git a/packages/core/src/Engine.ts b/packages/core/src/Engine.ts index e266a94416..b1171175cd 100644 --- a/packages/core/src/Engine.ts +++ b/packages/core/src/Engine.ts @@ -113,7 +113,7 @@ export class Engine extends EventDispatcher { /* @internal */ _renderCount: number = 0; /* @internal */ - _shaderProgramPools: MacroMap[] = []; + _shaderProgramMaps: MacroMap[] = []; /** @internal */ _fontMap: Record = {}; /** @internal */ @@ -542,18 +542,18 @@ export class Engine extends EventDispatcher { /** * @internal */ - _getShaderProgramPool(index: number, trackPools?: MacroMap[]): MacroMap { - const shaderProgramPools = this._shaderProgramPools; - let pool = shaderProgramPools[index]; - if (!pool) { + _getShaderProgramMap(index: number, trackMaps?: MacroMap[]): MacroMap { + const shaderProgramMaps = this._shaderProgramMaps; + let map = shaderProgramMaps[index]; + if (!map) { const length = index + 1; - if (length > shaderProgramPools.length) { - shaderProgramPools.length = length; + if (length > shaderProgramMaps.length) { + shaderProgramMaps.length = length; } - shaderProgramPools[index] = pool = new MacroMap(this); - trackPools?.push(pool); + shaderProgramMaps[index] = map = new MacroMap(this); + trackMaps?.push(map); } - return pool; + return map; } /** @@ -675,9 +675,9 @@ export class Engine extends EventDispatcher { private _onDeviceRestored(): void { this._hardwareRenderer.resetState(); this._lastRenderState = new RenderState(); - // Clear shader pools + // Clear shader program maps Shader._clear(this); - this._shaderProgramPools.length = 0; + this._shaderProgramMaps.length = 0; const { resourceManager } = this; // Restore graphic resources diff --git a/packages/core/src/shader/MacroMap.ts b/packages/core/src/shader/MacroMap.ts index eddb296bc8..96f7849911 100644 --- a/packages/core/src/shader/MacroMap.ts +++ b/packages/core/src/shader/MacroMap.ts @@ -6,7 +6,7 @@ type Tree = { }; /** - * Cache pool keyed by ShaderMacroCollection bitmask. + * Map keyed by ShaderMacroCollection bitmask. * @internal */ export class MacroMap { diff --git a/packages/core/src/shader/Shader.ts b/packages/core/src/shader/Shader.ts index 7d8c599f17..ed8456c8ca 100644 --- a/packages/core/src/shader/Shader.ts +++ b/packages/core/src/shader/Shader.ts @@ -237,12 +237,12 @@ export class Shader implements IReferable { const passes = subShaders[i].passes; for (let j = 0, m = passes.length; j < m; j++) { const pass = passes[j]; - const passShaderProgramPools = pass._shaderProgramPools; - for (let k = passShaderProgramPools.length - 1; k >= 0; k--) { - const pool = passShaderProgramPools[k]; - if (pool.engine !== engine) continue; - pool.clear((program) => program.destroy()); - passShaderProgramPools.splice(k, 1); + const passShaderProgramMaps = pass._shaderProgramMaps; + for (let k = passShaderProgramMaps.length - 1; k >= 0; k--) { + const map = passShaderProgramMaps[k]; + if (map.engine !== engine) continue; + map.clear((program) => program.destroy()); + passShaderProgramMaps.splice(k, 1); } } } diff --git a/packages/core/src/shader/ShaderPass.ts b/packages/core/src/shader/ShaderPass.ts index 4b685c1a91..7fdc33febe 100644 --- a/packages/core/src/shader/ShaderPass.ts +++ b/packages/core/src/shader/ShaderPass.ts @@ -55,7 +55,7 @@ export class ShaderPass extends ShaderPart { /** @internal */ _renderStateDataMap: Record = {}; /** @internal */ - _shaderProgramPools: MacroMap[] = []; + _shaderProgramMaps: MacroMap[] = []; private _vertexSource?: string; private _fragmentSource?: string; @@ -172,15 +172,15 @@ export class ShaderPass extends ShaderPart { macroCollection: ShaderMacroCollection, instanceFields?: InstanceFieldInfo[] ): ShaderProgram { - const shaderProgramPool = engine._getShaderProgramPool(this._shaderPassId, this._shaderProgramPools); - let shaderProgram = shaderProgramPool.get(macroCollection); + const shaderProgramMap = engine._getShaderProgramMap(this._shaderPassId, this._shaderProgramMaps); + let shaderProgram = shaderProgramMap.get(macroCollection); if (shaderProgram) { return shaderProgram; } shaderProgram = this._getCanonicalShaderProgram(engine, macroCollection, instanceFields); - shaderProgramPool.cache(shaderProgram); + shaderProgramMap.cache(shaderProgram); return shaderProgram; } @@ -218,13 +218,13 @@ export class ShaderPass extends ShaderPart { * @internal */ _destroy(): void { - const shaderProgramPools = this._shaderProgramPools; - for (let i = 0, n = shaderProgramPools.length; i < n; i++) { - const pool = shaderProgramPools[i]; - pool.clear((program) => program.destroy()); - delete pool.engine._shaderProgramPools[this._shaderPassId]; + const shaderProgramMaps = this._shaderProgramMaps; + for (let i = 0, n = shaderProgramMaps.length; i < n; i++) { + const map = shaderProgramMaps[i]; + map.clear((program) => program.destroy()); + delete map.engine._shaderProgramMaps[this._shaderPassId]; } - shaderProgramPools.length = 0; + shaderProgramMaps.length = 0; } private _getCanonicalShaderProgram( From 53a9a9f0d6d211177b2f04c51a03f3ab067ca15c Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Thu, 9 Apr 2026 00:59:07 +0800 Subject: [PATCH 19/91] refactor: simplify bindUniformBufferBase - remove unnecessary null check Callers always pass a valid buffer, no need for null guard. --- packages/rhi-webgl/src/WebGLGraphicDevice.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/rhi-webgl/src/WebGLGraphicDevice.ts b/packages/rhi-webgl/src/WebGLGraphicDevice.ts index 92f038b816..ec3e7ce1a6 100644 --- a/packages/rhi-webgl/src/WebGLGraphicDevice.ts +++ b/packages/rhi-webgl/src/WebGLGraphicDevice.ts @@ -280,9 +280,9 @@ export class WebGLGraphicDevice implements IHardwareRenderer { return new GLTransformFeedbackPrimitive(this._gl); } - bindUniformBufferBase(bindingPoint: number, buffer: IPlatformBuffer | null): void { + bindUniformBufferBase(bindingPoint: number, buffer: IPlatformBuffer): void { const gl = this._gl; - gl.bindBufferBase(gl.UNIFORM_BUFFER, bindingPoint, buffer ? (buffer)._glBuffer : null); + gl.bindBufferBase(gl.UNIFORM_BUFFER, bindingPoint, (buffer)._glBuffer); } bindUniformBlock(program: WebGLProgram, blockName: string, bindingPoint: number): number { From c63b83b0c40674f687388e211510e9791f19f3d3 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sat, 11 Apr 2026 15:50:10 +0800 Subject: [PATCH 20/91] refactor: simplify InstanceBatch - store layout directly, rename fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - nativeBuffer → buffer, _uboData → _data - Replace separate instanceFields/_structSize with single _layout ref - setLayout() now takes InstanceLayout directly --- .../core/src/RenderPipeline/InstanceBatch.ts | 40 +++++++++---------- .../core/src/RenderPipeline/RenderQueue.ts | 16 ++------ 2 files changed, 23 insertions(+), 33 deletions(-) diff --git a/packages/core/src/RenderPipeline/InstanceBatch.ts b/packages/core/src/RenderPipeline/InstanceBatch.ts index 3b03f14364..3c300b4a3d 100644 --- a/packages/core/src/RenderPipeline/InstanceBatch.ts +++ b/packages/core/src/RenderPipeline/InstanceBatch.ts @@ -7,7 +7,7 @@ import { Renderer } from "../Renderer"; import { ShaderBlockProperty } from "../shader/ShaderBlockProperty"; import { ShaderMacro } from "../shader/ShaderMacro"; import { ConstantBufferBindingPoint } from "../shader/enums/ConstantBufferBindingPoint"; -import { InstanceFieldInfo, ShaderFactory } from "../shaderlib/ShaderFactory"; +import { InstanceLayout, ShaderFactory } from "../shaderlib/ShaderFactory"; /** * @internal @@ -21,14 +21,13 @@ export class InstanceBatch { ConstantBufferBindingPoint.RendererInstance }; - instanceFields: InstanceFieldInfo[]; - nativeBuffer: Buffer; + buffer: Buffer; private _engine: Engine; - private _uboData: ArrayBuffer; + private _layout: InstanceLayout; + private _data: ArrayBuffer; private _floatView: Float32Array; private _intView: Int32Array; - private _structSize = 0; constructor(engine: Engine) { this._engine = engine; @@ -37,17 +36,16 @@ export class InstanceBatch { /** * Set UBO layout and allocate buffer if needed. */ - setLayout(instanceFields: InstanceFieldInfo[], maxInstanceCount: number, structSize: number): void { - this.instanceFields = instanceFields; - this._structSize = structSize; - const totalBytes = maxInstanceCount * structSize; + setLayout(layout: InstanceLayout): void { + this._layout = layout; + const totalBytes = layout.instanceMaxCount * layout.structSize; // Only reallocate when buffer is too small - if (!this.nativeBuffer || totalBytes > this.nativeBuffer.byteLength) { - this._uboData = new ArrayBuffer(totalBytes); - this._floatView = new Float32Array(this._uboData); - this._intView = new Int32Array(this._uboData); - this.nativeBuffer?.destroy(); - this.nativeBuffer = new Buffer(this._engine, BufferBindFlag.ConstantBuffer, totalBytes, BufferUsage.Dynamic); + if (!this.buffer || totalBytes > this.buffer.byteLength) { + this._data = new ArrayBuffer(totalBytes); + this._floatView = new Float32Array(this._data); + this._intView = new Int32Array(this._data); + this.buffer?.destroy(); + this.buffer = new Buffer(this._engine, BufferBindFlag.ConstantBuffer, totalBytes, BufferUsage.Dynamic); } } @@ -55,9 +53,9 @@ export class InstanceBatch { * Pack renderer data into UBO and upload to GPU. */ upload(renderers: Renderer[], start: number, count: number): void { - const fields = this.instanceFields; - if (!fields) return; - const structSize = this._structSize; + const layout = this._layout; + if (!layout) return; + const { instanceFields: fields, structSize } = layout; const elementsPerInstance = structSize / 4; const floatView = this._floatView; const intView = this._intView; @@ -86,12 +84,12 @@ export class InstanceBatch { } const uploadElements = count * elementsPerInstance; - this.nativeBuffer.setData(floatView, 0, 0, uploadElements, SetDataOptions.Discard); + this.buffer.setData(floatView, 0, 0, uploadElements, SetDataOptions.Discard); } destroy(): void { - this.nativeBuffer?.destroy(); - this._uboData = null; + this.buffer?.destroy(); + this._data = null; this._floatView = null; this._intView = null; } diff --git a/packages/core/src/RenderPipeline/RenderQueue.ts b/packages/core/src/RenderPipeline/RenderQueue.ts index 518dc7871f..504003f588 100644 --- a/packages/core/src/RenderPipeline/RenderQueue.ts +++ b/packages/core/src/RenderPipeline/RenderQueue.ts @@ -106,22 +106,14 @@ export class RenderQueue { // Build compile macros const compileMacros = Shader._compileMacros; - ShaderMacroCollection.unionCollection( - renderer._globalShaderMacro, - materialData._macroCollection, - compileMacros - ); + ShaderMacroCollection.unionCollection(renderer._globalShaderMacro, materialData._macroCollection, compileMacros); ShaderMacroCollection.unionCollection(compileMacros, engine._macroCollection, compileMacros); // For instancing: enable macro and get layout - let instanceFields = undefined; let layout = undefined; if (isInstanced) { compileMacros.enable(InstanceBatch.gpuInstanceMacro); layout = subElement.subShader._getInstanceLayout(engine, compileMacros); - if (layout) { - instanceFields = layout.instanceFields; - } } for (let j = 0, m = shaderPasses.length; j < m; j++) { @@ -149,7 +141,7 @@ export class RenderQueue { } } - const program = shaderPass._getShaderProgram(engine, compileMacros, instanceFields); + const program = shaderPass._getShaderProgram(engine, compileMacros, layout?.instanceFields); if (!program.isValid) { continue; } @@ -227,7 +219,7 @@ export class RenderQueue { const maxCount = layout.instanceMaxCount; const instanceBatch = engine._batcherManager.instanceBatch; - instanceBatch.setLayout(instanceFields, maxCount, layout.structSize); + instanceBatch.setLayout(layout); for (let start = 0; start < totalCount; start += maxCount) { const count = Math.min(maxCount, totalCount - start); instanceBatch.upload(instancedRenderers, start, count); @@ -235,7 +227,7 @@ export class RenderQueue { program.bindUniformBlocks(InstanceBatch.uniformBlockBindingMap); rhi.bindUniformBufferBase( ConstantBufferBindingPoint.RendererInstance, - instanceBatch.nativeBuffer._platformBuffer + instanceBatch.buffer._platformBuffer ); primitive.instanceCount = count; rhi.drawPrimitive(primitive, subElement.subPrimitive, program); From b9f4168d96af4bf63c33d22c5705f659b6bdacde Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sat, 11 Apr 2026 15:59:02 +0800 Subject: [PATCH 21/91] refactor: clean up InstanceBatch upload method - Remove unnecessary null guard on _layout - Inline uploadElements variable - Destructure floatView/intView from this - Improve worldMatrix comment --- .../core/src/RenderPipeline/InstanceBatch.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/packages/core/src/RenderPipeline/InstanceBatch.ts b/packages/core/src/RenderPipeline/InstanceBatch.ts index 3c300b4a3d..f72337e934 100644 --- a/packages/core/src/RenderPipeline/InstanceBatch.ts +++ b/packages/core/src/RenderPipeline/InstanceBatch.ts @@ -53,12 +53,9 @@ export class InstanceBatch { * Pack renderer data into UBO and upload to GPU. */ upload(renderers: Renderer[], start: number, count: number): void { - const layout = this._layout; - if (!layout) return; - const { instanceFields: fields, structSize } = layout; + const { instanceFields, structSize } = this._layout; const elementsPerInstance = structSize / 4; - const floatView = this._floatView; - const intView = this._intView; + const { _floatView: floatView, _intView: intView } = this; const modelMatId = Renderer._worldMatrixProperty._uniqueId; for (let i = 0; i < count; i++) { @@ -66,13 +63,14 @@ export class InstanceBatch { const propertyValueMap = renderer.shaderData._propertyValueMap; const baseOffset = i * elementsPerInstance; - for (let j = 0, n = fields.length; j < n; j++) { - const field = fields[j]; + for (let j = 0, n = instanceFields.length; j < n; j++) { + const field = instanceFields[j]; const fieldOffset = baseOffset + field.offset / 4; const propertyId = field.property._uniqueId; if (propertyId === modelMatId) { - // ModelMat must go through getter to trigger Transform lazy update + // Instancing skips _updateTransformShaderData, so worldMatrix is not in propertyValueMap + // Must read from transform getter to trigger lazy update field.pack(floatView, fieldOffset, renderer.entity.transform.worldMatrix); } else { const value = propertyValueMap[propertyId]; @@ -83,8 +81,7 @@ export class InstanceBatch { } } - const uploadElements = count * elementsPerInstance; - this.buffer.setData(floatView, 0, 0, uploadElements, SetDataOptions.Discard); + this.buffer.setData(floatView, 0, 0, count * elementsPerInstance, SetDataOptions.Discard); } destroy(): void { From 8c704a76cd5287f1ff76b424d06cdd7ee60c7a9f Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sat, 11 Apr 2026 16:14:00 +0800 Subject: [PATCH 22/91] refactor: clean up RenderQueue instancing render loop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove unnecessary component→renderer alias, use component directly - Hoist bindUniformBlocks/bindUniformBufferBase out of sub-batch loop - Move primitive.instanceCount=0 after loop (only need to reset once) - Remove redundant let layout = undefined --- .../core/src/RenderPipeline/RenderQueue.ts | 35 +++++++++---------- packages/core/src/shaderlib/ShaderFactory.ts | 19 ++++++---- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/packages/core/src/RenderPipeline/RenderQueue.ts b/packages/core/src/RenderPipeline/RenderQueue.ts index 504003f588..9a496fc367 100644 --- a/packages/core/src/RenderPipeline/RenderQueue.ts +++ b/packages/core/src/RenderPipeline/RenderQueue.ts @@ -62,7 +62,7 @@ export class RenderQueue { for (let i = 0; i < length; i++) { const subElement = batchedSubElements[i]; - const { component: renderer, material, instancedRenderers } = subElement; + const { component, material, instancedRenderers } = subElement; const isInstanced = instancedRenderers.length > 0; // Instancing: transform data is packed in UBO, skip per-renderer update @@ -71,18 +71,18 @@ export class RenderQueue { const batched = subElement.batched; if ( this.rendererUpdateFlag & ContextRendererUpdateFlag.WorldViewMatrix || - renderer._batchedTransformShaderData != batched + component._batchedTransformShaderData != batched ) { // Update world matrix and view matrix and model matrix - renderer._updateTransformShaderData(context, false, batched); - renderer._batchedTransformShaderData = batched; + component._updateTransformShaderData(context, false, batched); + component._batchedTransformShaderData = batched; } else if (this.rendererUpdateFlag & ContextRendererUpdateFlag.ProjectionMatrix) { // Only projection matrix need updated - renderer._updateTransformShaderData(context, true, batched); + component._updateTransformShaderData(context, true, batched); } } - const maskInteraction = renderer._maskInteraction; + const maskInteraction = component._maskInteraction; const needMaskInteraction = maskInteraction !== SpriteMaskInteraction.None; const needMaskType = maskType !== RenderQueueMaskType.No; let customStates: RenderStateElementMap = null; @@ -91,7 +91,7 @@ export class RenderQueue { customStates = BasicResources.getMaskTypeRenderStates(maskType); } else { if (needMaskInteraction) { - maskManager.drawMask(context, pipelineStageTagValue, subElement.component._maskLayer); + maskManager.drawMask(context, pipelineStageTagValue, component._maskLayer); customStates = BasicResources.getMaskInteractionRenderStates(maskInteraction); } else { maskManager.isReadStencil(material) && maskManager.clearMask(context, pipelineStageTagValue); @@ -101,16 +101,16 @@ export class RenderQueue { const { primitive, shaderData: renderElementShaderData } = subElement; const shaderPasses = subElement.subShader.passes; - const { shaderData: rendererData, instanceId: rendererId } = renderer; + const { shaderData: rendererData, instanceId: rendererId } = component; const { shaderData: materialData, instanceId: materialId, renderStates } = material; // Build compile macros const compileMacros = Shader._compileMacros; - ShaderMacroCollection.unionCollection(renderer._globalShaderMacro, materialData._macroCollection, compileMacros); + ShaderMacroCollection.unionCollection(component._globalShaderMacro, materialData._macroCollection, compileMacros); ShaderMacroCollection.unionCollection(compileMacros, engine._macroCollection, compileMacros); // For instancing: enable macro and get layout - let layout = undefined; + let layout; if (isInstanced) { compileMacros.enable(InstanceBatch.gpuInstanceMacro); layout = subElement.subShader._getInstanceLayout(engine, compileMacros); @@ -208,7 +208,7 @@ export class RenderQueue { renderState._applyStates( engine, - renderer._isFrontFaceInvert(), + component._isFrontFaceInvert(), shaderPass._renderStateDataMap, material.shaderData, customStates @@ -220,19 +220,18 @@ export class RenderQueue { const instanceBatch = engine._batcherManager.instanceBatch; instanceBatch.setLayout(layout); + program.bindUniformBlocks(InstanceBatch.uniformBlockBindingMap); + rhi.bindUniformBufferBase( + ConstantBufferBindingPoint.RendererInstance, + instanceBatch.buffer._platformBuffer + ); for (let start = 0; start < totalCount; start += maxCount) { const count = Math.min(maxCount, totalCount - start); instanceBatch.upload(instancedRenderers, start, count); - - program.bindUniformBlocks(InstanceBatch.uniformBlockBindingMap); - rhi.bindUniformBufferBase( - ConstantBufferBindingPoint.RendererInstance, - instanceBatch.buffer._platformBuffer - ); primitive.instanceCount = count; rhi.drawPrimitive(primitive, subElement.subPrimitive, program); - primitive.instanceCount = 0; } + primitive.instanceCount = 0; } else { rhi.drawPrimitive(primitive, subElement.subPrimitive, program); } diff --git a/packages/core/src/shaderlib/ShaderFactory.ts b/packages/core/src/shaderlib/ShaderFactory.ts index 14ad35ef84..dfd24b1e64 100644 --- a/packages/core/src/shaderlib/ShaderFactory.ts +++ b/packages/core/src/shaderlib/ShaderFactory.ts @@ -263,7 +263,10 @@ export class ShaderFactory { const fsUboBlock = `${uboStruct}flat in int v_instanceID;\n${ShaderFactory._buildFieldDefines(instanceFields, "v_instanceID")}\n${derivedDefines}\n`; vertexSource = ShaderFactory._insertUBOBlock(vertexSource, vsUboBlock); - vertexSource = vertexSource.replace(/void\s+main\s*\(\s*\)\s*\{/, "void main() {\n v_instanceID = gl_InstanceID;"); + vertexSource = vertexSource.replace( + /void\s+main\s*\(\s*\)\s*\{/, + "void main() {\n v_instanceID = gl_InstanceID;" + ); fragmentSource = ShaderFactory._insertUBOBlock(fragmentSource, fsUboBlock); return { vertexSource, fragmentSource, instanceFields, instanceMaxCount }; @@ -282,10 +285,12 @@ export class ShaderFactory { const result = source.replace(ShaderFactory._uboUniformRegex, (match, _indent, type, name) => { if (type.indexOf("sampler") !== -1) return match; const isDerived = builtinUniforms.get(name); - if (isDerived === undefined && ShaderProperty._getShaderPropertyGroup(name) !== ShaderDataGroup.Renderer) return match; + if (isDerived === undefined && ShaderProperty._getShaderPropertyGroup(name) !== ShaderDataGroup.Renderer) + return match; if (isDerived) return remove ? "" : match; // Store ModelMat as affine (3×vec4) to save UBO space - fieldMap[ShaderProperty.getByName(name)._uniqueId] = type === "mat4" && name === "renderer_ModelMat" ? "mat4_affine" : type; + fieldMap[ShaderProperty.getByName(name)._uniqueId] = + type === "mat4" && name === "renderer_ModelMat" ? "mat4_affine" : type; found = true; return remove ? "" : match; }); @@ -349,10 +354,10 @@ export class ShaderFactory { const n = property.name; lines.push( `#define ${n} mat4(` + - `vec4(${d}.${n}R0.x,${d}.${n}R1.x,${d}.${n}R2.x,0.0),` + - `vec4(${d}.${n}R0.y,${d}.${n}R1.y,${d}.${n}R2.y,0.0),` + - `vec4(${d}.${n}R0.z,${d}.${n}R1.z,${d}.${n}R2.z,0.0),` + - `vec4(${d}.${n}R0.w,${d}.${n}R1.w,${d}.${n}R2.w,1.0))` + `vec4(${d}.${n}R0.x,${d}.${n}R1.x,${d}.${n}R2.x,0.0),` + + `vec4(${d}.${n}R0.y,${d}.${n}R1.y,${d}.${n}R2.y,0.0),` + + `vec4(${d}.${n}R0.z,${d}.${n}R1.z,${d}.${n}R2.z,0.0),` + + `vec4(${d}.${n}R0.w,${d}.${n}R1.w,${d}.${n}R2.w,1.0))` ); } else { lines.push(`#define ${property.name} ${d}.${property.name}`); From d75acb76451b5f35c83a15fa9212c327ca913faa Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sat, 11 Apr 2026 16:20:43 +0800 Subject: [PATCH 23/91] style: fix prettier formatting in RenderQueue --- packages/core/src/RenderPipeline/RenderQueue.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/core/src/RenderPipeline/RenderQueue.ts b/packages/core/src/RenderPipeline/RenderQueue.ts index 9a496fc367..5c63efe8e8 100644 --- a/packages/core/src/RenderPipeline/RenderQueue.ts +++ b/packages/core/src/RenderPipeline/RenderQueue.ts @@ -221,10 +221,7 @@ export class RenderQueue { instanceBatch.setLayout(layout); program.bindUniformBlocks(InstanceBatch.uniformBlockBindingMap); - rhi.bindUniformBufferBase( - ConstantBufferBindingPoint.RendererInstance, - instanceBatch.buffer._platformBuffer - ); + rhi.bindUniformBufferBase(ConstantBufferBindingPoint.RendererInstance, instanceBatch.buffer._platformBuffer); for (let start = 0; start < totalCount; start += maxCount) { const count = Math.min(maxCount, totalCount - start); instanceBatch.upload(instancedRenderers, start, count); From 82c0d60ec9589db1b1529ae2702ab8f18760e7bd Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sat, 11 Apr 2026 16:28:07 +0800 Subject: [PATCH 24/91] chore: upgrade lint-staged to v16 and fix glob pattern - Upgrade lint-staged from v10.5 to v16.4.0 - Fix glob from *.{ts} to **/*.ts to match subdirectory files - Remove redundant git add from tasks --- package.json | 7 +- pnpm-lock.yaml | 413 ++++++++++++++++++++++--------------------------- 2 files changed, 187 insertions(+), 233 deletions(-) diff --git a/package.json b/package.json index 882675522a..00df043ab1 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "eslint-plugin-prettier": "^5.0.0", "fs-extra": "^10.1.0", "husky": "^8.0.0", - "lint-staged": "^10.5.3", + "lint-staged": "^16.4.0", "nyc": "^15.1.0", "odiff-bin": "^2.5.0", "prettier": "^3.0.0", @@ -65,9 +65,8 @@ "vitest": "2.1.3" }, "lint-staged": { - "*.{ts}": [ - "eslint --fix", - "git add" + "**/*.ts": [ + "eslint --fix" ] }, "repository": "git@github.com:galacean/runtime.git" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ed565e145b..8e3e3134ad 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -78,8 +78,8 @@ importers: specifier: ^8.0.0 version: 8.0.3 lint-staged: - specifier: ^10.5.3 - version: 10.5.4 + specifier: ^16.4.0 + version: 16.4.0 nyc: specifier: ^15.1.0 version: 15.1.0 @@ -1694,14 +1694,14 @@ packages: ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - ansi-colors@4.1.3: - resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} - engines: {node: '>=6'} - ansi-escapes@4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} + ansi-escapes@7.3.0: + resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} + engines: {node: '>=18'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -1710,6 +1710,10 @@ packages: resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} engines: {node: '>=12'} + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -1722,6 +1726,10 @@ packages: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -1760,10 +1768,6 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} - astral-regex@2.0.0: - resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} - engines: {node: '>=8'} - at-least-node@1.0.0: resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} engines: {node: '>= 4.0.0'} @@ -1882,13 +1886,13 @@ packages: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} - cli-cursor@3.1.0: - resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} - engines: {node: '>=8'} + cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} - cli-truncate@2.1.0: - resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==} - engines: {node: '>=8'} + cli-truncate@5.2.0: + resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==} + engines: {node: '>=20'} cli-width@4.1.0: resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} @@ -1914,13 +1918,13 @@ packages: colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + commander@14.0.3: + resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} + engines: {node: '>=20'} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - commander@6.2.1: - resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} - engines: {node: '>= 6'} - commondir@1.0.1: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} @@ -2029,9 +2033,6 @@ packages: resolution: {integrity: sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==} engines: {node: '>=4'} - dedent@0.7.0: - resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} - deep-eql@5.0.2: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} @@ -2113,6 +2114,9 @@ packages: engines: {node: '>= 8.6'} hasBin: true + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -2126,14 +2130,14 @@ packages: end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} - enquirer@2.4.1: - resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} - engines: {node: '>=8.6'} - env-paths@2.2.1: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} + environment@1.1.0: + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} + engines: {node: '>=18'} + error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} @@ -2365,9 +2369,8 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} - execa@4.1.0: - resolution: {integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==} - engines: {node: '>=10'} + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} execa@8.0.1: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} @@ -2489,13 +2492,14 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} + get-east-asian-width@1.5.0: + resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} + engines: {node: '>=18'} + get-intrinsic@1.2.4: resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} engines: {node: '>= 0.4'} - get-own-enumerable-property-symbols@3.0.2: - resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==} - get-package-type@0.1.0: resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} engines: {node: '>=8.0.0'} @@ -2632,10 +2636,6 @@ packages: http-cache-semantics@4.1.1: resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} - human-signals@1.1.1: - resolution: {integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==} - engines: {node: '>=8.12.0'} - human-signals@5.0.0: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} @@ -2693,6 +2693,10 @@ packages: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} + is-fullwidth-code-point@5.1.0: + resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} + engines: {node: '>=18'} + is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -2707,10 +2711,6 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - is-obj@1.0.1: - resolution: {integrity: sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==} - engines: {node: '>=0.10.0'} - is-obj@2.0.0: resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} engines: {node: '>=8'} @@ -2726,10 +2726,6 @@ packages: is-reference@1.2.1: resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} - is-regexp@1.0.0: - resolution: {integrity: sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==} - engines: {node: '>=0.10.0'} - is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} @@ -2745,10 +2741,6 @@ packages: is-typedarray@1.0.0: resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} - is-unicode-supported@0.1.0: - resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} - engines: {node: '>=10'} - is-windows@1.0.2: resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} engines: {node: '>=0.10.0'} @@ -2875,18 +2867,14 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - lint-staged@10.5.4: - resolution: {integrity: sha512-EechC3DdFic/TdOPgj/RB3FicqE6932LTHCUm0Y2fsD9KGlLB+RwJl2q1IYBIvEsKzDOgn0D4gll+YxG5RsrKg==} + lint-staged@16.4.0: + resolution: {integrity: sha512-lBWt8hujh/Cjysw5GYVmZpFHXDCgZzhrOm8vbcUdobADZNOK/bRshr2kM3DfgrrtR1DQhfupW9gnIXOfiFi+bw==} + engines: {node: '>=20.17'} hasBin: true - listr2@3.14.0: - resolution: {integrity: sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==} - engines: {node: '>=10.0.0'} - peerDependencies: - enquirer: '>= 2.3.0 < 3' - peerDependenciesMeta: - enquirer: - optional: true + listr2@9.0.5: + resolution: {integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==} + engines: {node: '>=20.0.0'} locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} @@ -2905,13 +2893,9 @@ packages: lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - log-symbols@4.1.0: - resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} - engines: {node: '>=10'} - - log-update@4.0.0: - resolution: {integrity: sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==} - engines: {node: '>=10'} + log-update@6.1.0: + resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} + engines: {node: '>=18'} loupe@3.1.2: resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==} @@ -2990,14 +2974,14 @@ packages: engines: {node: '>=4.0.0'} hasBin: true - mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - mimic-fn@4.0.0: resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} engines: {node: '>=12'} + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + mimic-response@1.0.1: resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} engines: {node: '>=4'} @@ -3119,10 +3103,6 @@ packages: resolution: {integrity: sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==} engines: {node: '>=4'} - npm-run-path@4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} - engines: {node: '>=8'} - npm-run-path@5.3.0: resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -3151,14 +3131,14 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} - onetime@6.0.0: resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} engines: {node: '>=12'} + onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + opener@1.5.2: resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} hasBin: true @@ -3194,10 +3174,6 @@ packages: resolution: {integrity: sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==} engines: {node: '>=8'} - p-map@4.0.0: - resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} - engines: {node: '>=10'} - p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} @@ -3279,6 +3255,10 @@ packages: resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} engines: {node: '>=12'} + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + pify@3.0.0: resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} engines: {node: '>=4'} @@ -3300,9 +3280,6 @@ packages: engines: {node: '>=18'} hasBin: true - please-upgrade-node@3.2.0: - resolution: {integrity: sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==} - postcss@8.4.49: resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} engines: {node: ^10 || ^12 || >=14} @@ -3446,9 +3423,9 @@ packages: responselike@1.0.2: resolution: {integrity: sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==} - restore-cursor@3.1.0: - resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} - engines: {node: '>=8'} + restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} @@ -3514,9 +3491,6 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - rxjs@7.8.1: - resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} - safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} @@ -3597,13 +3571,13 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} - slice-ansi@3.0.0: - resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} - engines: {node: '>=8'} + slice-ansi@7.1.2: + resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} + engines: {node: '>=18'} - slice-ansi@4.0.0: - resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} - engines: {node: '>=10'} + slice-ansi@8.0.0: + resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==} + engines: {node: '>=20'} source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} @@ -3658,10 +3632,6 @@ packages: strict-event-emitter@0.5.1: resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} - string-argv@0.3.1: - resolution: {integrity: sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==} - engines: {node: '>=0.6.19'} - string-argv@0.3.2: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} @@ -3674,16 +3644,20 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + string-width@8.2.0: + resolution: {integrity: sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==} + engines: {node: '>=20'} + string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - stringify-object@3.3.0: - resolution: {integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==} - engines: {node: '>=4'} - strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -3692,14 +3666,14 @@ packages: resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} engines: {node: '>=12'} + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + strip-bom@4.0.0: resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} engines: {node: '>=8'} - strip-final-newline@2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} - engines: {node: '>=6'} - strip-final-newline@3.0.0: resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} engines: {node: '>=12'} @@ -3764,6 +3738,10 @@ packages: tinyexec@0.3.1: resolution: {integrity: sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==} + tinyexec@1.1.1: + resolution: {integrity: sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==} + engines: {node: '>=18'} + tinyglobby@0.2.10: resolution: {integrity: sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==} engines: {node: '>=12.0.0'} @@ -4092,6 +4070,10 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -4127,6 +4109,11 @@ packages: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} + yaml@2.8.3: + resolution: {integrity: sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==} + engines: {node: '>= 14.6'} + hasBin: true + yargs-parser@18.1.3: resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} engines: {node: '>=6'} @@ -5455,16 +5442,20 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 - ansi-colors@4.1.3: {} - ansi-escapes@4.3.2: dependencies: type-fest: 0.21.3 + ansi-escapes@7.3.0: + dependencies: + environment: 1.1.0 + ansi-regex@5.0.1: {} ansi-regex@6.1.0: {} + ansi-regex@6.2.2: {} + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 @@ -5473,6 +5464,8 @@ snapshots: ansi-styles@6.2.1: {} + ansi-styles@6.2.3: {} + anymatch@3.1.3: dependencies: normalize-path: 3.0.0 @@ -5504,8 +5497,6 @@ snapshots: assertion-error@2.0.1: {} - astral-regex@2.0.0: {} - at-least-node@1.0.0: {} balanced-match@1.0.2: {} @@ -5649,14 +5640,14 @@ snapshots: clean-stack@2.2.0: {} - cli-cursor@3.1.0: + cli-cursor@5.0.0: dependencies: - restore-cursor: 3.1.0 + restore-cursor: 5.1.0 - cli-truncate@2.1.0: + cli-truncate@5.2.0: dependencies: - slice-ansi: 3.0.0 - string-width: 4.2.3 + slice-ansi: 8.0.0 + string-width: 8.2.0 cli-width@4.1.0: {} @@ -5684,11 +5675,11 @@ snapshots: colorette@2.0.20: {} + commander@14.0.3: {} + commander@2.20.3: optional: true - commander@6.2.1: {} - commondir@1.0.1: {} compare-func@2.0.0: @@ -5796,8 +5787,6 @@ snapshots: dependencies: mimic-response: 1.0.1 - dedent@0.7.0: {} - deep-eql@5.0.2: {} deep-is@0.1.4: {} @@ -5868,6 +5857,8 @@ snapshots: transitivePeerDependencies: - supports-color + emoji-regex@10.6.0: {} + emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} @@ -5879,13 +5870,10 @@ snapshots: dependencies: once: 1.4.0 - enquirer@2.4.1: - dependencies: - ansi-colors: 4.1.3 - strip-ansi: 6.0.1 - env-paths@2.2.1: {} + environment@1.1.0: {} + error-ex@1.3.2: dependencies: is-arrayish: 0.2.1 @@ -6135,17 +6123,7 @@ snapshots: esutils@2.0.3: {} - execa@4.1.0: - dependencies: - cross-spawn: 7.0.5 - get-stream: 5.2.0 - human-signals: 1.1.1 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 + eventemitter3@5.0.4: {} execa@8.0.1: dependencies: @@ -6279,6 +6257,8 @@ snapshots: get-caller-file@2.0.5: {} + get-east-asian-width@1.5.0: {} + get-intrinsic@1.2.4: dependencies: es-errors: 1.3.0 @@ -6288,8 +6268,6 @@ snapshots: hasown: 2.0.2 optional: true - get-own-enumerable-property-symbols@3.0.2: {} - get-package-type@0.1.0: {} get-stdin@8.0.0: {} @@ -6459,8 +6437,6 @@ snapshots: http-cache-semantics@4.1.1: {} - human-signals@1.1.1: {} - human-signals@5.0.0: {} husky@8.0.3: {} @@ -6501,6 +6477,10 @@ snapshots: is-fullwidth-code-point@3.0.0: {} + is-fullwidth-code-point@5.1.0: + dependencies: + get-east-asian-width: 1.5.0 + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 @@ -6511,8 +6491,6 @@ snapshots: is-number@7.0.0: {} - is-obj@1.0.1: {} - is-obj@2.0.0: {} is-path-inside@3.0.3: {} @@ -6523,8 +6501,6 @@ snapshots: dependencies: '@types/estree': 1.0.6 - is-regexp@1.0.0: {} - is-stream@2.0.1: {} is-stream@3.0.0: {} @@ -6535,8 +6511,6 @@ snapshots: is-typedarray@1.0.0: {} - is-unicode-supported@0.1.0: {} - is-windows@1.0.2: {} isarray@1.0.0: {} @@ -6671,38 +6645,23 @@ snapshots: lines-and-columns@1.2.4: {} - lint-staged@10.5.4: + lint-staged@16.4.0: dependencies: - chalk: 4.1.2 - cli-truncate: 2.1.0 - commander: 6.2.1 - cosmiconfig: 7.1.0 - debug: 4.3.7 - dedent: 0.7.0 - enquirer: 2.4.1 - execa: 4.1.0 - listr2: 3.14.0(enquirer@2.4.1) - log-symbols: 4.1.0 - micromatch: 4.0.8 - normalize-path: 3.0.0 - please-upgrade-node: 3.2.0 - string-argv: 0.3.1 - stringify-object: 3.3.0 - transitivePeerDependencies: - - supports-color + commander: 14.0.3 + listr2: 9.0.5 + picomatch: 4.0.4 + string-argv: 0.3.2 + tinyexec: 1.1.1 + yaml: 2.8.3 - listr2@3.14.0(enquirer@2.4.1): + listr2@9.0.5: dependencies: - cli-truncate: 2.1.0 + cli-truncate: 5.2.0 colorette: 2.0.20 - log-update: 4.0.0 - p-map: 4.0.0 + eventemitter3: 5.0.4 + log-update: 6.1.0 rfdc: 1.4.1 - rxjs: 7.8.1 - through: 2.3.8 - wrap-ansi: 7.0.0 - optionalDependencies: - enquirer: 2.4.1 + wrap-ansi: 9.0.2 locate-path@5.0.0: dependencies: @@ -6718,17 +6677,13 @@ snapshots: lodash@4.17.21: {} - log-symbols@4.1.0: - dependencies: - chalk: 4.1.2 - is-unicode-supported: 0.1.0 - - log-update@4.0.0: + log-update@6.1.0: dependencies: - ansi-escapes: 4.3.2 - cli-cursor: 3.1.0 - slice-ansi: 4.0.0 - wrap-ansi: 6.2.0 + ansi-escapes: 7.3.0 + cli-cursor: 5.0.0 + slice-ansi: 7.1.2 + strip-ansi: 7.1.0 + wrap-ansi: 9.0.2 loupe@3.1.2: {} @@ -6806,10 +6761,10 @@ snapshots: mime@2.6.0: {} - mimic-fn@2.1.0: {} - mimic-fn@4.0.0: {} + mimic-function@5.0.1: {} + mimic-response@1.0.1: {} min-indent@1.0.1: {} @@ -6934,10 +6889,6 @@ snapshots: pify: 3.0.0 optional: true - npm-run-path@4.0.1: - dependencies: - path-key: 3.1.1 - npm-run-path@5.3.0: dependencies: path-key: 4.0.0 @@ -6994,14 +6945,14 @@ snapshots: dependencies: wrappy: 1.0.2 - onetime@5.1.2: - dependencies: - mimic-fn: 2.1.0 - onetime@6.0.0: dependencies: mimic-fn: 4.0.0 + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + opener@1.5.2: {} optionator@0.9.4: @@ -7037,10 +6988,6 @@ snapshots: dependencies: aggregate-error: 3.1.0 - p-map@4.0.0: - dependencies: - aggregate-error: 3.1.0 - p-try@2.2.0: {} package-hash@4.0.0: @@ -7100,6 +7047,8 @@ snapshots: picomatch@4.0.2: {} + picomatch@4.0.4: {} + pify@3.0.0: optional: true @@ -7121,10 +7070,6 @@ snapshots: optionalDependencies: fsevents: 2.3.2 - please-upgrade-node@3.2.0: - dependencies: - semver-compare: 1.0.0 - postcss@8.4.49: dependencies: nanoid: 3.3.7 @@ -7261,10 +7206,10 @@ snapshots: dependencies: lowercase-keys: 1.0.1 - restore-cursor@3.1.0: + restore-cursor@5.1.0: dependencies: - onetime: 5.1.2 - signal-exit: 3.0.7 + onetime: 7.0.0 + signal-exit: 4.1.0 reusify@1.0.4: {} @@ -7355,10 +7300,6 @@ snapshots: dependencies: queue-microtask: 1.2.3 - rxjs@7.8.1: - dependencies: - tslib: 2.8.1 - safe-buffer@5.1.2: {} safe-buffer@5.2.1: {} @@ -7371,7 +7312,8 @@ snapshots: optionalDependencies: '@parcel/watcher': 2.5.0 - semver-compare@1.0.0: {} + semver-compare@1.0.0: + optional: true semver@5.7.2: {} @@ -7418,17 +7360,15 @@ snapshots: slash@3.0.0: {} - slice-ansi@3.0.0: + slice-ansi@7.1.2: dependencies: - ansi-styles: 4.3.0 - astral-regex: 2.0.0 - is-fullwidth-code-point: 3.0.0 + ansi-styles: 6.2.1 + is-fullwidth-code-point: 5.1.0 - slice-ansi@4.0.0: + slice-ansi@8.0.0: dependencies: - ansi-styles: 4.3.0 - astral-regex: 2.0.0 - is-fullwidth-code-point: 3.0.0 + ansi-styles: 6.2.3 + is-fullwidth-code-point: 5.1.0 source-map-js@1.2.1: {} @@ -7482,8 +7422,6 @@ snapshots: strict-event-emitter@0.5.1: {} - string-argv@0.3.1: {} - string-argv@0.3.2: {} string-width@4.2.3: @@ -7498,6 +7436,17 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.0 + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.5.0 + strip-ansi: 7.1.0 + + string-width@8.2.0: + dependencies: + get-east-asian-width: 1.5.0 + strip-ansi: 7.2.0 + string_decoder@1.1.1: dependencies: safe-buffer: 5.1.2 @@ -7506,12 +7455,6 @@ snapshots: dependencies: safe-buffer: 5.2.1 - stringify-object@3.3.0: - dependencies: - get-own-enumerable-property-symbols: 3.0.2 - is-obj: 1.0.1 - is-regexp: 1.0.0 - strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -7520,9 +7463,11 @@ snapshots: dependencies: ansi-regex: 6.1.0 - strip-bom@4.0.0: {} + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 - strip-final-newline@2.0.0: {} + strip-bom@4.0.0: {} strip-final-newline@3.0.0: {} @@ -7592,6 +7537,8 @@ snapshots: tinyexec@0.3.1: {} + tinyexec@1.1.1: {} + tinyglobby@0.2.10: dependencies: fdir: 6.4.2(picomatch@4.0.2) @@ -7892,6 +7839,12 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.1.0 + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.1 + string-width: 7.2.0 + strip-ansi: 7.1.0 + wrappy@1.0.2: {} write-file-atomic@3.0.3: @@ -7913,6 +7866,8 @@ snapshots: yaml@1.10.2: {} + yaml@2.8.3: {} + yargs-parser@18.1.3: dependencies: camelcase: 5.3.1 From baeaa3d4e92c60a3bfea4e579e40e27489980546 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sat, 11 Apr 2026 16:33:37 +0800 Subject: [PATCH 25/91] chore: upgrade eslint and @typescript-eslint to support TS 5.6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - eslint 8.44 → 8.57 - @typescript-eslint/parser and eslint-plugin 6.x → 8.x - Eliminates "unsupported TypeScript version" warning --- package.json | 6 +- pnpm-lock.yaml | 383 ++++++++++++++++++++++++++----------------------- 2 files changed, 210 insertions(+), 179 deletions(-) diff --git a/package.json b/package.json index 00df043ab1..3548235692 100644 --- a/package.json +++ b/package.json @@ -41,13 +41,13 @@ "@types/dom-webcodecs": "^0.1.13", "@types/node": "^18.7.16", "@types/webxr": "latest", - "@typescript-eslint/eslint-plugin": "^6.1.0", - "@typescript-eslint/parser": "^6.1.0", + "@typescript-eslint/eslint-plugin": "^8.58.1", + "@typescript-eslint/parser": "^8.58.1", "@vitest/coverage-v8": "2.1.3", "bumpp": "^9.5.2", "cross-env": "^5.2.0", "electron": "^13", - "eslint": "^8.44.0", + "eslint": "^8.57.1", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^5.0.0", "fs-extra": "^10.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8e3e3134ad..ef3b22c6ce 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,11 +45,11 @@ importers: specifier: latest version: 0.5.22 '@typescript-eslint/eslint-plugin': - specifier: ^6.1.0 - version: 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3) + specifier: ^8.58.1 + version: 8.58.1(@typescript-eslint/parser@8.58.1(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3) '@typescript-eslint/parser': - specifier: ^6.1.0 - version: 6.21.0(eslint@8.57.1)(typescript@5.6.3) + specifier: ^8.58.1 + version: 8.58.1(eslint@8.57.1)(typescript@5.6.3) '@vitest/coverage-v8': specifier: 2.1.3 version: 2.1.3(@vitest/browser@2.1.3(@types/node@18.19.64)(@vitest/spy@2.1.3)(typescript@5.6.3)(vite@5.4.11(@types/node@18.19.64)(sass@1.81.0)(terser@5.44.1))(vitest@2.1.3))(vitest@2.1.3(@types/node@18.19.64)(@vitest/browser@2.1.3)(msw@2.6.5(@types/node@18.19.64)(typescript@5.6.3))(sass@1.81.0)(terser@5.44.1)) @@ -63,7 +63,7 @@ importers: specifier: ^13 version: 13.6.9 eslint: - specifier: ^8.44.0 + specifier: ^8.57.1 version: 8.57.1 eslint-config-prettier: specifier: ^8.8.0 @@ -804,10 +804,20 @@ packages: peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/regexpp@4.12.1': resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + '@eslint/eslintrc@2.1.4': resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1481,9 +1491,6 @@ packages: '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} - '@types/json-schema@7.0.15': - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/keyv@3.1.4': resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} @@ -1508,9 +1515,6 @@ packages: '@types/responselike@1.0.3': resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} - '@types/semver@7.5.8': - resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} - '@types/statuses@2.0.5': resolution: {integrity: sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A==} @@ -1520,63 +1524,64 @@ packages: '@types/webxr@0.5.22': resolution: {integrity: sha512-Vr6Stjv5jPRqH690f5I5GLjVk8GSsoQSYJ2FVd/3jJF7KaqfwPi3ehfBS96mlQ2kPCwZaX6U0rG2+NGHBKkA/A==} - '@typescript-eslint/eslint-plugin@6.21.0': - resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==} - engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/eslint-plugin@8.58.1': + resolution: {integrity: sha512-eSkwoemjo76bdXl2MYqtxg51HNwUSkWfODUOQ3PaTLZGh9uIWWFZIjyjaJnex7wXDu+TRx+ATsnSxdN9YWfRTQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@typescript-eslint/parser': ^8.58.1 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@6.21.0': - resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} - engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/parser@8.58.1': + resolution: {integrity: sha512-gGkiNMPqerb2cJSVcruigx9eHBlLG14fSdPdqMoOcBfh+vvn4iCq2C8MzUB89PrxOXk0y3GZ1yIWb9aOzL93bw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/project-service@8.58.1': + resolution: {integrity: sha512-gfQ8fk6cxhtptek+/8ZIqw8YrRW5048Gug8Ts5IYcMLCw18iUgrZAEY/D7s4hkI0FxEfGakKuPK/XUMPzPxi5g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@6.21.0': - resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==} - engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/scope-manager@8.58.1': + resolution: {integrity: sha512-TPYUEqJK6avLcEjumWsIuTpuYODTTDAtoMdt8ZZa93uWMTX13Nb8L5leSje1NluammvU+oI3QRr5lLXPgihX3w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/type-utils@6.21.0': - resolution: {integrity: sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==} - engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/tsconfig-utils@8.58.1': + resolution: {integrity: sha512-JAr2hOIct2Q+qk3G+8YFfqkqi7sC86uNryT+2i5HzMa2MPjw4qNFvtjnw1IiA1rP7QhNKVe21mSSLaSjwA1Olw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@6.21.0': - resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==} - engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/type-utils@8.58.1': + resolution: {integrity: sha512-HUFxvTJVroT+0rXVJC7eD5zol6ID+Sn5npVPWoFuHGg9Ncq5Q4EYstqR+UOqaNRFXi5TYkpXXkLhoCHe3G0+7w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/typescript-estree@6.21.0': - resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} - engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/types@8.58.1': + resolution: {integrity: sha512-io/dV5Aw5ezwzfPBBWLoT+5QfVtP8O7q4Kftjn5azJ88bYyp/ZMCsyW1lpKK46EXJcaYMZ1JtYj+s/7TdzmQMw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.58.1': + resolution: {integrity: sha512-w4w7WR7GHOjqqPnvAYbazq+Y5oS68b9CzasGtnd6jIeOIeKUzYzupGTB2T4LTPSv4d+WPeccbxuneTFHYgAAWg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@6.21.0': - resolution: {integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==} - engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/utils@8.58.1': + resolution: {integrity: sha512-Ln8R0tmWC7pTtLOzgJzYTXSCjJ9rDNHAqTaVONF4FEi2qwce8mD9iSOxOpLFFvWp/wBFlew0mjM1L1ihYWfBdQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^7.0.0 || ^8.0.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@6.21.0': - resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==} - engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/visitor-keys@8.58.1': + resolution: {integrity: sha512-y+vH7QE8ycjoa0bWciFg7OpFcipUuem1ujhrdLtq1gByKwfbC7bPeKsiny9e0urg93DqwGcHey+bGRKCnF1nZQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} @@ -1756,10 +1761,6 @@ packages: array-ify@1.0.0: resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} - array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} - arrify@1.0.1: resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} engines: {node: '>=0.10.0'} @@ -1775,6 +1776,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} @@ -1789,6 +1794,10 @@ packages: brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + brace-expansion@5.0.5: + resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} + engines: {node: 18 || 20 || >=22} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -2021,6 +2030,15 @@ packages: supports-color: optional: true + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decamelize-keys@1.1.1: resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} engines: {node: '>=0.10.0'} @@ -2081,10 +2099,6 @@ packages: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} - dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} - doctrine@3.0.0: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} @@ -2326,6 +2340,10 @@ packages: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + eslint@8.57.1: resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2390,10 +2408,6 @@ packages: fast-diff@1.3.0: resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} - fast-glob@3.3.2: - resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} - engines: {node: '>=8.6.0'} - fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} @@ -2414,6 +2428,15 @@ packages: picomatch: optional: true + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -2572,10 +2595,6 @@ packages: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} - globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} - gopd@1.0.1: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} @@ -2649,6 +2668,10 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + immutable@5.0.2: resolution: {integrity: sha512-1NU7hWZDkV7hJ4PJ9dur9gTNQ4ePNPN4k9/0YhwjzykTi/+3Q5pF93YU5QoVj8BuOnhLgaY8gs0U2pj4kSYVcw==} @@ -2961,10 +2984,6 @@ packages: merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} @@ -2990,13 +3009,13 @@ packages: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - minimatch@9.0.3: - resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} - engines: {node: '>=16 || 14 >=14.17'} - minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} @@ -3523,6 +3542,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + serialize-error@7.0.1: resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} engines: {node: '>=10'} @@ -3567,10 +3591,6 @@ packages: resolution: {integrity: sha512-pEjMUbwJ5Pl/6Vn6FsamXHXItJXSRftcibixDmNCWbWhic0hzHrwkMZo0IZ7fMRH9KxcWDFSkzhccB4285PutA==} engines: {node: '>=4.2'} - slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - slice-ansi@7.1.2: resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} engines: {node: '>=18'} @@ -3746,6 +3766,10 @@ packages: resolution: {integrity: sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==} engines: {node: '>=12.0.0'} + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} + tinypool@1.0.2: resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} engines: {node: ^18.0.0 || >=20.0.0} @@ -3778,11 +3802,11 @@ packages: resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} engines: {node: '>=8'} - ts-api-utils@1.4.0: - resolution: {integrity: sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==} - engines: {node: '>=16'} + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} peerDependencies: - typescript: '>=4.2.0' + typescript: '>=4.8.4' ts-node@10.9.2: resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} @@ -4526,8 +4550,15 @@ snapshots: eslint: 8.57.1 eslint-visitor-keys: 3.4.3 + '@eslint-community/eslint-utils@4.9.1(eslint@8.57.1)': + dependencies: + eslint: 8.57.1 + eslint-visitor-keys: 3.4.3 + '@eslint-community/regexpp@4.12.1': {} + '@eslint-community/regexpp@4.12.2': {} + '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 @@ -5136,8 +5167,6 @@ snapshots: '@types/estree@1.0.6': {} - '@types/json-schema@7.0.15': {} - '@types/keyv@3.1.4': dependencies: '@types/node': 18.19.64 @@ -5162,99 +5191,102 @@ snapshots: dependencies: '@types/node': 18.19.64 - '@types/semver@7.5.8': {} - '@types/statuses@2.0.5': {} '@types/tough-cookie@4.0.5': {} '@types/webxr@0.5.22': {} - '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3)': + '@typescript-eslint/eslint-plugin@8.58.1(@typescript-eslint/parser@8.58.1(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3)': dependencies: - '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.6.3) - '@typescript-eslint/scope-manager': 6.21.0 - '@typescript-eslint/type-utils': 6.21.0(eslint@8.57.1)(typescript@5.6.3) - '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.6.3) - '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.3.7 + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.58.1(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/scope-manager': 8.58.1 + '@typescript-eslint/type-utils': 8.58.1(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/utils': 8.58.1(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 8.58.1 eslint: 8.57.1 - graphemer: 1.4.0 - ignore: 5.3.2 + ignore: 7.0.5 natural-compare: 1.4.0 - semver: 7.6.3 - ts-api-utils: 1.4.0(typescript@5.6.3) - optionalDependencies: + ts-api-utils: 2.5.0(typescript@5.6.3) typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3)': + '@typescript-eslint/parser@8.58.1(eslint@8.57.1)(typescript@5.6.3)': dependencies: - '@typescript-eslint/scope-manager': 6.21.0 - '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.6.3) - '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.3.7 + '@typescript-eslint/scope-manager': 8.58.1 + '@typescript-eslint/types': 8.58.1 + '@typescript-eslint/typescript-estree': 8.58.1(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 8.58.1 + debug: 4.4.3 eslint: 8.57.1 - optionalDependencies: typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@6.21.0': + '@typescript-eslint/project-service@8.58.1(typescript@5.6.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.58.1(typescript@5.6.3) + '@typescript-eslint/types': 8.58.1 + debug: 4.4.3 + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.58.1': dependencies: - '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/visitor-keys': 6.21.0 + '@typescript-eslint/types': 8.58.1 + '@typescript-eslint/visitor-keys': 8.58.1 - '@typescript-eslint/type-utils@6.21.0(eslint@8.57.1)(typescript@5.6.3)': + '@typescript-eslint/tsconfig-utils@8.58.1(typescript@5.6.3)': dependencies: - '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.6.3) - '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.6.3) - debug: 4.3.7 + typescript: 5.6.3 + + '@typescript-eslint/type-utils@8.58.1(eslint@8.57.1)(typescript@5.6.3)': + dependencies: + '@typescript-eslint/types': 8.58.1 + '@typescript-eslint/typescript-estree': 8.58.1(typescript@5.6.3) + '@typescript-eslint/utils': 8.58.1(eslint@8.57.1)(typescript@5.6.3) + debug: 4.4.3 eslint: 8.57.1 - ts-api-utils: 1.4.0(typescript@5.6.3) - optionalDependencies: + ts-api-utils: 2.5.0(typescript@5.6.3) typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@6.21.0': {} + '@typescript-eslint/types@8.58.1': {} - '@typescript-eslint/typescript-estree@6.21.0(typescript@5.6.3)': + '@typescript-eslint/typescript-estree@8.58.1(typescript@5.6.3)': dependencies: - '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.3.7 - globby: 11.1.0 - is-glob: 4.0.3 - minimatch: 9.0.3 - semver: 7.6.3 - ts-api-utils: 1.4.0(typescript@5.6.3) - optionalDependencies: + '@typescript-eslint/project-service': 8.58.1(typescript@5.6.3) + '@typescript-eslint/tsconfig-utils': 8.58.1(typescript@5.6.3) + '@typescript-eslint/types': 8.58.1 + '@typescript-eslint/visitor-keys': 8.58.1 + debug: 4.4.3 + minimatch: 10.2.5 + semver: 7.7.4 + tinyglobby: 0.2.16 + ts-api-utils: 2.5.0(typescript@5.6.3) typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@6.21.0(eslint@8.57.1)(typescript@5.6.3)': + '@typescript-eslint/utils@8.58.1(eslint@8.57.1)(typescript@5.6.3)': dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) - '@types/json-schema': 7.0.15 - '@types/semver': 7.5.8 - '@typescript-eslint/scope-manager': 6.21.0 - '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.6.3) + '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) + '@typescript-eslint/scope-manager': 8.58.1 + '@typescript-eslint/types': 8.58.1 + '@typescript-eslint/typescript-estree': 8.58.1(typescript@5.6.3) eslint: 8.57.1 - semver: 7.6.3 + typescript: 5.6.3 transitivePeerDependencies: - supports-color - - typescript - '@typescript-eslint/visitor-keys@6.21.0': + '@typescript-eslint/visitor-keys@8.58.1': dependencies: - '@typescript-eslint/types': 6.21.0 - eslint-visitor-keys: 3.4.3 + '@typescript-eslint/types': 8.58.1 + eslint-visitor-keys: 5.0.1 '@ungap/structured-clone@1.2.0': {} @@ -5491,8 +5523,6 @@ snapshots: array-ify@1.0.0: {} - array-union@2.1.0: {} - arrify@1.0.1: {} assertion-error@2.0.1: {} @@ -5501,6 +5531,8 @@ snapshots: balanced-match@1.0.2: {} + balanced-match@4.0.4: {} + binary-extensions@2.3.0: {} boolean@3.2.0: @@ -5515,6 +5547,10 @@ snapshots: dependencies: balanced-match: 1.0.2 + brace-expansion@5.0.5: + dependencies: + balanced-match: 4.0.4 + braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -5776,6 +5812,10 @@ snapshots: dependencies: ms: 2.1.3 + debug@4.4.3: + dependencies: + ms: 2.1.3 + decamelize-keys@1.1.1: dependencies: decamelize: 1.2.0 @@ -5827,10 +5867,6 @@ snapshots: diff@4.0.2: {} - dir-glob@3.0.1: - dependencies: - path-type: 4.0.0 - doctrine@3.0.0: dependencies: esutils: 2.0.3 @@ -6050,6 +6086,8 @@ snapshots: eslint-visitor-keys@3.4.3: {} + eslint-visitor-keys@5.0.1: {} + eslint@8.57.1: dependencies: '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) @@ -6152,14 +6190,6 @@ snapshots: fast-diff@1.3.0: {} - fast-glob@3.3.2: - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.8 - fast-json-stable-stringify@2.1.0: {} fast-levenshtein@2.0.6: {} @@ -6176,6 +6206,10 @@ snapshots: optionalDependencies: picomatch: 4.0.2 + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + file-entry-cache@6.0.1: dependencies: flat-cache: 3.2.0 @@ -6365,15 +6399,6 @@ snapshots: gopd: 1.0.1 optional: true - globby@11.1.0: - dependencies: - array-union: 2.1.0 - dir-glob: 3.0.1 - fast-glob: 3.3.2 - ignore: 5.3.2 - merge2: 1.4.1 - slash: 3.0.0 - gopd@1.0.1: dependencies: get-intrinsic: 1.2.4 @@ -6443,6 +6468,8 @@ snapshots: ignore@5.3.2: {} + ignore@7.0.5: {} + immutable@5.0.2: {} import-fresh@3.3.0: @@ -6752,12 +6779,11 @@ snapshots: merge-stream@2.0.0: {} - merge2@1.4.1: {} - micromatch@4.0.8: dependencies: braces: 3.0.3 picomatch: 2.3.1 + optional: true mime@2.6.0: {} @@ -6769,13 +6795,13 @@ snapshots: min-indent@1.0.1: {} - minimatch@3.1.2: + minimatch@10.2.5: dependencies: - brace-expansion: 1.1.11 + brace-expansion: 5.0.5 - minimatch@9.0.3: + minimatch@3.1.2: dependencies: - brace-expansion: 2.0.1 + brace-expansion: 1.1.11 minimatch@9.0.5: dependencies: @@ -7323,6 +7349,8 @@ snapshots: semver@7.6.3: {} + semver@7.7.4: {} + serialize-error@7.0.1: dependencies: type-fest: 0.13.1 @@ -7358,8 +7386,6 @@ snapshots: skip-regex@1.0.2: {} - slash@3.0.0: {} - slice-ansi@7.1.2: dependencies: ansi-styles: 6.2.1 @@ -7544,6 +7570,11 @@ snapshots: fdir: 6.4.2(picomatch@4.0.2) picomatch: 4.0.2 + tinyglobby@0.2.16: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + tinypool@1.0.2: {} tinyrainbow@1.2.0: {} @@ -7567,7 +7598,7 @@ snapshots: trim-newlines@3.0.1: {} - ts-api-utils@1.4.0(typescript@5.6.3): + ts-api-utils@2.5.0(typescript@5.6.3): dependencies: typescript: 5.6.3 From 99ec5d138aa4aec8c7eeaea4c4c08a2abafeb3c2 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sat, 11 Apr 2026 16:40:54 +0800 Subject: [PATCH 26/91] refactor: null out instancedRenderers in dispose Dispose means the object is permanently released, so null the array to free memory instead of just clearing length. --- packages/core/src/RenderPipeline/SubRenderElement.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/RenderPipeline/SubRenderElement.ts b/packages/core/src/RenderPipeline/SubRenderElement.ts index d6887b6a72..f49d159f31 100644 --- a/packages/core/src/RenderPipeline/SubRenderElement.ts +++ b/packages/core/src/RenderPipeline/SubRenderElement.ts @@ -46,7 +46,7 @@ export class SubRenderElement implements IPoolElement { this.subPrimitive = null; this.subShader = null; this.shaderData && (this.shaderData = null); - this.instancedRenderers.length = 0; + this.instancedRenderers = null; this.texture && (this.texture = null); this.subChunk && (this.subChunk = null); From fd747eb31930537bfcc6d52044b2ec37157c0c96 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sat, 11 Apr 2026 17:49:56 +0800 Subject: [PATCH 27/91] refactor: store InstanceLayout on ShaderProgram during compilation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Eliminate redundant SubShader._getInstanceLayout / ShaderPass._scanInstanceFields by reusing the shader compilation chain — _injectInstanceUBO now returns InstanceLayout directly, stored on ShaderProgram._instanceLayout. --- .../core/src/RenderPipeline/RenderQueue.ts | 6 +- .../src/graphic/TransformFeedbackShader.ts | 6 +- .../core/src/shader/ShaderBlockProperty.ts | 2 +- packages/core/src/shader/ShaderPass.ts | 81 +++++-------------- packages/core/src/shader/ShaderProgram.ts | 4 + packages/core/src/shader/SubShader.ts | 29 +------ packages/core/src/shaderlib/ShaderFactory.ts | 73 ++++++----------- 7 files changed, 56 insertions(+), 145 deletions(-) diff --git a/packages/core/src/RenderPipeline/RenderQueue.ts b/packages/core/src/RenderPipeline/RenderQueue.ts index 5c63efe8e8..686459e79e 100644 --- a/packages/core/src/RenderPipeline/RenderQueue.ts +++ b/packages/core/src/RenderPipeline/RenderQueue.ts @@ -109,11 +109,8 @@ export class RenderQueue { ShaderMacroCollection.unionCollection(component._globalShaderMacro, materialData._macroCollection, compileMacros); ShaderMacroCollection.unionCollection(compileMacros, engine._macroCollection, compileMacros); - // For instancing: enable macro and get layout - let layout; if (isInstanced) { compileMacros.enable(InstanceBatch.gpuInstanceMacro); - layout = subElement.subShader._getInstanceLayout(engine, compileMacros); } for (let j = 0, m = shaderPasses.length; j < m; j++) { @@ -141,7 +138,7 @@ export class RenderQueue { } } - const program = shaderPass._getShaderProgram(engine, compileMacros, layout?.instanceFields); + const program = shaderPass._getShaderProgram(engine, compileMacros); if (!program.isValid) { continue; } @@ -214,6 +211,7 @@ export class RenderQueue { customStates ); + const layout = program._instanceLayout; if (isInstanced && layout) { const totalCount = instancedRenderers.length; const maxCount = layout.instanceMaxCount; diff --git a/packages/core/src/graphic/TransformFeedbackShader.ts b/packages/core/src/graphic/TransformFeedbackShader.ts index c200e41597..9bd34cad06 100644 --- a/packages/core/src/graphic/TransformFeedbackShader.ts +++ b/packages/core/src/graphic/TransformFeedbackShader.ts @@ -28,9 +28,9 @@ export class TransformFeedbackShader { * Get or compile a shader program for the given engine and macro combination. */ getProgram(engine: Engine, macroCollection: ShaderMacroCollection): ShaderProgram | null { - const pool = engine._getShaderProgramPool(this._id); + const map = engine._getShaderProgramMap(this._id); - let program = pool.get(macroCollection); + let program = map.get(macroCollection); if (program) return program; const { vertexSource, fragmentSource } = ShaderFactory.compilePlatformSource( @@ -47,7 +47,7 @@ export class TransformFeedbackShader { return null; } - pool.cache(program); + map.cache(program); return program; } } diff --git a/packages/core/src/shader/ShaderBlockProperty.ts b/packages/core/src/shader/ShaderBlockProperty.ts index cbfb28f6a5..fd47b4cecc 100644 --- a/packages/core/src/shader/ShaderBlockProperty.ts +++ b/packages/core/src/shader/ShaderBlockProperty.ts @@ -13,7 +13,7 @@ export class ShaderBlockProperty { */ static getByName(name: string): ShaderBlockProperty { const nameMap = ShaderBlockProperty._nameMap; - return nameMap[name] ?? (nameMap[name] = new ShaderBlockProperty(name)); + return (nameMap[name] ??= new ShaderBlockProperty(name)); } /** Uniform block name. */ diff --git a/packages/core/src/shader/ShaderPass.ts b/packages/core/src/shader/ShaderPass.ts index 7fdc33febe..5470400b8c 100644 --- a/packages/core/src/shader/ShaderPass.ts +++ b/packages/core/src/shader/ShaderPass.ts @@ -3,8 +3,7 @@ import { Engine } from "../Engine"; import { InstanceBatch } from "../RenderPipeline/InstanceBatch"; import { PipelineStage } from "../RenderPipeline/enums/PipelineStage"; import { GLCapabilityType } from "../base/Constant"; -import { ShaderFactory, InstanceFieldInfo } from "../shaderlib/ShaderFactory"; -import { resolveIfdef } from "../shaderlib/GLSLIfdefResolver"; +import { ShaderFactory, InstanceLayout } from "../shaderlib/ShaderFactory"; import { ShaderMacro } from "./ShaderMacro"; import { ShaderMacroCollection } from "./ShaderMacroCollection"; import { ShaderPart } from "./ShaderPart"; @@ -167,53 +166,19 @@ export class ShaderPass extends ShaderPart { /** * @internal */ - _getShaderProgram( - engine: Engine, - macroCollection: ShaderMacroCollection, - instanceFields?: InstanceFieldInfo[] - ): ShaderProgram { + _getShaderProgram(engine: Engine, macroCollection: ShaderMacroCollection): ShaderProgram { const shaderProgramMap = engine._getShaderProgramMap(this._shaderPassId, this._shaderProgramMaps); let shaderProgram = shaderProgramMap.get(macroCollection); if (shaderProgram) { return shaderProgram; } - shaderProgram = this._getCanonicalShaderProgram(engine, macroCollection, instanceFields); + shaderProgram = this._getCanonicalShaderProgram(engine, macroCollection); shaderProgramMap.cache(shaderProgram); return shaderProgram; } - /** - * @internal - * Scan renderer-group uniforms from this pass into fieldMap, without GPU compilation. - */ - _scanInstanceFields( - engine: Engine, - macroCollection: ShaderMacroCollection, - fieldMap: Record - ): boolean { - let vertexSource: string; - let fragmentSource: string; - - if (this._platformTarget != undefined) { - const macroMap = ShaderPass._buildMacroMap(engine, macroCollection); - vertexSource = ShaderMacroProcessor.evaluate(this._vertexShaderInstructions, macroMap); - fragmentSource = ShaderMacroProcessor.evaluate(this._fragmentShaderInstructions, macroMap); - } else { - vertexSource = ShaderFactory.parseIncludes(this._vertexSource); - fragmentSource = ShaderFactory.parseIncludes(this._fragmentSource); - - const macroMap = ShaderPass._buildMacroMap(engine, macroCollection); - vertexSource = resolveIfdef(vertexSource, macroMap); - fragmentSource = resolveIfdef(fragmentSource, macroMap); - } - - const a = ShaderFactory._scanInstanceUniforms(vertexSource, fieldMap); - const b = ShaderFactory._scanInstanceUniforms(fragmentSource, fieldMap); - return a || b; - } - /** * @internal */ @@ -227,55 +192,48 @@ export class ShaderPass extends ShaderPart { shaderProgramMaps.length = 0; } - private _getCanonicalShaderProgram( - engine: Engine, - macroCollection: ShaderMacroCollection, - instanceFields?: InstanceFieldInfo[] - ): ShaderProgram { + private _getCanonicalShaderProgram(engine: Engine, macroCollection: ShaderMacroCollection): ShaderProgram { const isGpuInstance = macroCollection.isEnable(InstanceBatch.gpuInstanceMacro); - const { vertexSource, fragmentSource } = + const { vertexSource, fragmentSource, instanceLayout } = this._platformTarget != undefined - ? this._compileShaderLabSource(engine, macroCollection, isGpuInstance, instanceFields) - : this._compilePlatformSource(engine, macroCollection, isGpuInstance, instanceFields); + ? this._compileShaderLabSource(engine, macroCollection, isGpuInstance) + : this._compilePlatformSource(engine, macroCollection, isGpuInstance); - return new ShaderProgram(engine, vertexSource, fragmentSource); + const program = new ShaderProgram(engine, vertexSource, fragmentSource); + program._instanceLayout = instanceLayout; + return program; } private _compilePlatformSource( engine: Engine, macroCollection: ShaderMacroCollection, - isGpuInstance: boolean, - instanceFields?: InstanceFieldInfo[] - ): { vertexSource: string; fragmentSource: string; instanceFields: InstanceFieldInfo[]; instanceMaxCount: number } { + isGpuInstance: boolean + ): { vertexSource: string; fragmentSource: string; instanceLayout: InstanceLayout | null } { return ShaderFactory.compilePlatformSource( engine, macroCollection, this._vertexSource, this._fragmentSource, - isGpuInstance, - instanceFields + isGpuInstance ); } private _compileShaderLabSource( engine: Engine, macroCollection: ShaderMacroCollection, - isGpuInstance: boolean, - instanceFields?: InstanceFieldInfo[] - ): { vertexSource: string; fragmentSource: string; instanceFields: InstanceFieldInfo[]; instanceMaxCount: number } { + isGpuInstance: boolean + ): { vertexSource: string; fragmentSource: string; instanceLayout: InstanceLayout | null } { const isWebGL2: boolean = engine._hardwareRenderer.isWebGL2; const macroMap = ShaderPass._buildMacroMap(engine, macroCollection); let vertexSource = ShaderMacroProcessor.evaluate(this._vertexShaderInstructions, macroMap); let fragmentSource = ShaderMacroProcessor.evaluate(this._fragmentShaderInstructions, macroMap); - let injectedInstanceFields: InstanceFieldInfo[] = null; - let injectedInstanceMaxCount = 0; + let instanceLayout: InstanceLayout | null = null; if (isGpuInstance) { - const injected = ShaderFactory._injectInstanceUBO(engine, vertexSource, fragmentSource, instanceFields); + const injected = ShaderFactory._injectInstanceUBO(engine, vertexSource, fragmentSource); vertexSource = injected.vertexSource; fragmentSource = injected.fragmentSource; - injectedInstanceFields = injected.instanceFields; - injectedInstanceMaxCount = injected.instanceMaxCount; + instanceLayout = injected.instanceLayout; } if (isWebGL2 && this._platformTarget === ShaderLanguage.GLSLES100) { @@ -294,8 +252,7 @@ export class ShaderPass extends ShaderPart { ${precisionStr} ${fragmentSource} `, - instanceFields: injectedInstanceFields, - instanceMaxCount: injectedInstanceMaxCount + instanceLayout }; } } diff --git a/packages/core/src/shader/ShaderProgram.ts b/packages/core/src/shader/ShaderProgram.ts index 53b8a387b5..d50b0ebfe5 100644 --- a/packages/core/src/shader/ShaderProgram.ts +++ b/packages/core/src/shader/ShaderProgram.ts @@ -9,6 +9,7 @@ import { ShaderUniform } from "./ShaderUniform"; import { ShaderUniformBlock } from "./ShaderUniformBlock"; import { ShaderBlockProperty } from "./ShaderBlockProperty"; import { ShaderDataGroup } from "./enums/ShaderDataGroup"; +import { InstanceLayout } from "../shaderlib/ShaderFactory"; /** * Shader program, corresponding to the GPU shader program. @@ -53,6 +54,9 @@ export class ShaderProgram { /** @internal */ _uploadMaterialId: number = -1; + /** @internal */ + _instanceLayout: InstanceLayout | null = null; + attributeLocation: Record = Object.create(null); uniformBlockIds: number[] = []; diff --git a/packages/core/src/shader/SubShader.ts b/packages/core/src/shader/SubShader.ts index 758a9f84b2..f9bef85e3a 100644 --- a/packages/core/src/shader/SubShader.ts +++ b/packages/core/src/shader/SubShader.ts @@ -1,7 +1,3 @@ -import { Engine } from "../Engine"; -import { ShaderFactory, InstanceLayout } from "../shaderlib/ShaderFactory"; -import { MacroMap } from "./MacroMap"; -import { ShaderMacroCollection } from "./ShaderMacroCollection"; import { ShaderPart } from "./ShaderPart"; import { ShaderPass } from "./ShaderPass"; @@ -10,7 +6,6 @@ import { ShaderPass } from "./ShaderPass"; */ export class SubShader extends ShaderPart { private _passes: ShaderPass[]; - private _layoutCache: MacroMap = new MacroMap(); /** * Sub shader passes. @@ -41,27 +36,5 @@ export class SubShader extends ShaderPart { /** * @internal */ - _getInstanceLayout(engine: Engine, macroCollection: ShaderMacroCollection): InstanceLayout | null { - const cached = this._layoutCache.get(macroCollection); - if (cached) return cached; - - const passes = this._passes; - const fieldMap: Record = Object.create(null); - let hasField = false; - for (let i = 0, n = passes.length; i < n; i++) { - if (passes[i]._scanInstanceFields(engine, macroCollection, fieldMap)) hasField = true; - } - if (!hasField) return null; - - const result = ShaderFactory._buildLayout(engine, fieldMap); - this._layoutCache.cache(result); - return result; - } - - /** - * @internal - */ - _destroy(): void { - this._layoutCache.clear(); - } + _destroy(): void {} } diff --git a/packages/core/src/shaderlib/ShaderFactory.ts b/packages/core/src/shaderlib/ShaderFactory.ts index dfd24b1e64..ba0cd9ab3d 100644 --- a/packages/core/src/shaderlib/ShaderFactory.ts +++ b/packages/core/src/shaderlib/ShaderFactory.ts @@ -60,9 +60,8 @@ export class ShaderFactory { macroCollection: ShaderMacroCollection, vertexSource: string, fragmentSource: string, - isGpuInstance: boolean = false, - instanceFields?: InstanceFieldInfo[] - ): { vertexSource: string; fragmentSource: string; instanceFields: InstanceFieldInfo[]; instanceMaxCount: number } { + isGpuInstance: boolean = false + ): { vertexSource: string; fragmentSource: string; instanceLayout: InstanceLayout | null } { const rhi = engine._hardwareRenderer; const isWebGL2 = rhi.isWebGL2; const shaderMacroList = new Array(); @@ -82,14 +81,12 @@ export class ShaderFactory { noIncludeVertex = macroStr + noIncludeVertex; noIncludeFrag = macroStr + noIncludeFrag; - let injectedInstanceFields: InstanceFieldInfo[] = null; - let injectedInstanceMaxCount = 0; + let instanceLayout: InstanceLayout | null = null; if (isGpuInstance) { - const injected = ShaderFactory._injectInstanceUBO(engine, noIncludeVertex, noIncludeFrag, instanceFields); + const injected = ShaderFactory._injectInstanceUBO(engine, noIncludeVertex, noIncludeFrag); noIncludeVertex = injected.vertexSource; noIncludeFrag = injected.fragmentSource; - injectedInstanceFields = injected.instanceFields; - injectedInstanceMaxCount = injected.instanceMaxCount; + instanceLayout = injected.instanceLayout; } if (isWebGL2) { @@ -111,8 +108,7 @@ export class ShaderFactory { return { vertexSource: `${versionStr}\nprecision highp float;\n${noIncludeVertex}`, fragmentSource: `${versionStr}\n${isWebGL2 ? "" : ShaderFactory._shaderExtension}${precisionStr}${noIncludeFrag}`, - instanceFields: injectedInstanceFields, - instanceMaxCount: injectedInstanceMaxCount + instanceLayout }; } @@ -211,31 +207,21 @@ export class ShaderFactory { static _injectInstanceUBO( engine: Engine, vertexSource: string, - fragmentSource: string, - externalFields?: InstanceFieldInfo[] - ): { vertexSource: string; fragmentSource: string; instanceFields: InstanceFieldInfo[]; instanceMaxCount: number } { + fragmentSource: string + ): { vertexSource: string; fragmentSource: string; instanceLayout: InstanceLayout | null } { const fieldMap: Record = Object.create(null); - vertexSource = ShaderFactory._scanInstanceUniforms(vertexSource, fieldMap, true); - fragmentSource = ShaderFactory._scanInstanceUniforms(fragmentSource, fieldMap, true); - - let instanceFields: InstanceFieldInfo[]; - let instanceMaxCount: number; - if (externalFields) { - instanceFields = externalFields; - const maxUBOSize = engine._hardwareRenderer.getMaxUniformBlockSize(); - const last = externalFields[externalFields.length - 1]; - const lastSize = ShaderFactory._std140Map[last.type]?.size ?? 0; - const structSize = Math.ceil((last.offset + lastSize) / 16) * 16; - instanceMaxCount = Math.floor(maxUBOSize / structSize); - } else { - let hasField = false; - for (const _ in fieldMap) { - hasField = true; - break; - } - if (!hasField) return { vertexSource, fragmentSource, instanceFields: null, instanceMaxCount: 0 }; - ({ instanceFields, instanceMaxCount } = ShaderFactory._buildLayout(engine, fieldMap)); + vertexSource = ShaderFactory._scanInstanceUniforms(vertexSource, fieldMap); + fragmentSource = ShaderFactory._scanInstanceUniforms(fragmentSource, fieldMap); + + let hasField = false; + for (const _ in fieldMap) { + hasField = true; + break; } + if (!hasField) return { vertexSource, fragmentSource, instanceLayout: null }; + const instanceLayout = ShaderFactory._buildLayout(engine, fieldMap); + + const { instanceFields, instanceMaxCount } = instanceLayout; // Generate UBO struct fields and per-field #define remapping const structFieldLines: string[] = []; @@ -269,36 +255,29 @@ export class ShaderFactory { ); fragmentSource = ShaderFactory._insertUBOBlock(fragmentSource, fsUboBlock); - return { vertexSource, fragmentSource, instanceFields, instanceMaxCount }; + return { vertexSource, fragmentSource, instanceLayout }; } /** * @internal - * Scan source for renderer-group uniforms and collect into fieldMap. - * @param remove - If true, remove matched declarations from source. + * Scan source for renderer-group uniforms, collect into fieldMap, and remove matched declarations */ - static _scanInstanceUniforms(source: string, fieldMap: Record, remove: true): string; - static _scanInstanceUniforms(source: string, fieldMap: Record): boolean; - static _scanInstanceUniforms(source: string, fieldMap: Record, remove?: boolean): string | boolean { + static _scanInstanceUniforms(source: string, fieldMap: Record): string { const builtinUniforms = ShaderFactory._builtinRendererUniforms; - let found = false; - const result = source.replace(ShaderFactory._uboUniformRegex, (match, _indent, type, name) => { + return source.replace(ShaderFactory._uboUniformRegex, (match, _indent, type, name) => { if (type.indexOf("sampler") !== -1) return match; const isDerived = builtinUniforms.get(name); if (isDerived === undefined && ShaderProperty._getShaderPropertyGroup(name) !== ShaderDataGroup.Renderer) return match; - if (isDerived) return remove ? "" : match; + if (isDerived) return ""; // Store ModelMat as affine (3×vec4) to save UBO space fieldMap[ShaderProperty.getByName(name)._uniqueId] = type === "mat4" && name === "renderer_ModelMat" ? "mat4_affine" : type; - found = true; - return remove ? "" : match; + return ""; }); - return remove ? result : found; } - /** @internal */ - static _buildLayout(engine: Engine, fieldMap: Record): InstanceLayout { + private static _buildLayout(engine: Engine, fieldMap: Record): InstanceLayout { const maxUBOSize = engine._hardwareRenderer.getMaxUniformBlockSize(); const std140Map = ShaderFactory._std140Map; const instanceFields: InstanceFieldInfo[] = []; From 910b8ccc78deb138403108c07b9ff5aea4eddc5e Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sat, 11 Apr 2026 18:01:40 +0800 Subject: [PATCH 28/91] refactor: inline _buildMacroMap into _compileShaderLabSource Only one caller remains after removing _scanInstanceFields --- packages/core/src/shader/ShaderPass.ts | 41 +++++++++++--------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/packages/core/src/shader/ShaderPass.ts b/packages/core/src/shader/ShaderPass.ts index 5470400b8c..8d4acff416 100644 --- a/packages/core/src/shader/ShaderPass.ts +++ b/packages/core/src/shader/ShaderPass.ts @@ -62,27 +62,6 @@ export class ShaderPass extends ShaderPart { private static _shaderMacroList: ShaderMacro[] = []; private static _macroMap: Map = new Map(); - private static _buildMacroMap(engine: Engine, macroCollection: ShaderMacroCollection): Map { - const rhi = engine._hardwareRenderer; - const shaderMacroList = ShaderPass._shaderMacroList; - shaderMacroList.length = 0; - ShaderMacro._getMacrosElements(macroCollection, shaderMacroList); - shaderMacroList.push(ShaderMacro.getByName(rhi.isWebGL2 ? "GRAPHICS_API_WEBGL2" : "GRAPHICS_API_WEBGL1")); - if (rhi.canIUse(GLCapabilityType.shaderTextureLod)) { - shaderMacroList.push(ShaderMacro.getByName("HAS_TEX_LOD")); - } - if (rhi.canIUse(GLCapabilityType.standardDerivatives)) { - shaderMacroList.push(ShaderMacro.getByName("HAS_DERIVATIVES")); - } - const macroMap = ShaderPass._macroMap; - macroMap.clear(); - for (let i = 0, n = shaderMacroList.length; i < n; i++) { - const macro = shaderMacroList[i]; - macroMap.set(macro.name, macro.value ?? ""); - } - return macroMap; - } - /** * Create a shader pass. * @param name - Shader pass name @@ -223,8 +202,24 @@ export class ShaderPass extends ShaderPart { macroCollection: ShaderMacroCollection, isGpuInstance: boolean ): { vertexSource: string; fragmentSource: string; instanceLayout: InstanceLayout | null } { - const isWebGL2: boolean = engine._hardwareRenderer.isWebGL2; - const macroMap = ShaderPass._buildMacroMap(engine, macroCollection); + const rhi = engine._hardwareRenderer; + const isWebGL2 = rhi.isWebGL2; + const shaderMacroList = ShaderPass._shaderMacroList; + shaderMacroList.length = 0; + ShaderMacro._getMacrosElements(macroCollection, shaderMacroList); + shaderMacroList.push(ShaderMacro.getByName(isWebGL2 ? "GRAPHICS_API_WEBGL2" : "GRAPHICS_API_WEBGL1")); + if (rhi.canIUse(GLCapabilityType.shaderTextureLod)) { + shaderMacroList.push(ShaderMacro.getByName("HAS_TEX_LOD")); + } + if (rhi.canIUse(GLCapabilityType.standardDerivatives)) { + shaderMacroList.push(ShaderMacro.getByName("HAS_DERIVATIVES")); + } + const macroMap = ShaderPass._macroMap; + macroMap.clear(); + for (let i = 0, n = shaderMacroList.length; i < n; i++) { + const macro = shaderMacroList[i]; + macroMap.set(macro.name, macro.value ?? ""); + } let vertexSource = ShaderMacroProcessor.evaluate(this._vertexShaderInstructions, macroMap); let fragmentSource = ShaderMacroProcessor.evaluate(this._fragmentShaderInstructions, macroMap); From 891d2c253aa075e71d8b489815cddb71760123a3 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sat, 11 Apr 2026 18:05:12 +0800 Subject: [PATCH 29/91] refactor: rename isGpuInstance to isGPUInstance --- packages/core/src/shader/ShaderPass.ts | 14 +++++++------- packages/core/src/shaderlib/ShaderFactory.ts | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/core/src/shader/ShaderPass.ts b/packages/core/src/shader/ShaderPass.ts index 8d4acff416..a1ba6cae1e 100644 --- a/packages/core/src/shader/ShaderPass.ts +++ b/packages/core/src/shader/ShaderPass.ts @@ -172,11 +172,11 @@ export class ShaderPass extends ShaderPart { } private _getCanonicalShaderProgram(engine: Engine, macroCollection: ShaderMacroCollection): ShaderProgram { - const isGpuInstance = macroCollection.isEnable(InstanceBatch.gpuInstanceMacro); + const isGPUInstance = macroCollection.isEnable(InstanceBatch.gpuInstanceMacro); const { vertexSource, fragmentSource, instanceLayout } = this._platformTarget != undefined - ? this._compileShaderLabSource(engine, macroCollection, isGpuInstance) - : this._compilePlatformSource(engine, macroCollection, isGpuInstance); + ? this._compileShaderLabSource(engine, macroCollection, isGPUInstance) + : this._compilePlatformSource(engine, macroCollection, isGPUInstance); const program = new ShaderProgram(engine, vertexSource, fragmentSource); program._instanceLayout = instanceLayout; @@ -186,21 +186,21 @@ export class ShaderPass extends ShaderPart { private _compilePlatformSource( engine: Engine, macroCollection: ShaderMacroCollection, - isGpuInstance: boolean + isGPUInstance: boolean ): { vertexSource: string; fragmentSource: string; instanceLayout: InstanceLayout | null } { return ShaderFactory.compilePlatformSource( engine, macroCollection, this._vertexSource, this._fragmentSource, - isGpuInstance + isGPUInstance ); } private _compileShaderLabSource( engine: Engine, macroCollection: ShaderMacroCollection, - isGpuInstance: boolean + isGPUInstance: boolean ): { vertexSource: string; fragmentSource: string; instanceLayout: InstanceLayout | null } { const rhi = engine._hardwareRenderer; const isWebGL2 = rhi.isWebGL2; @@ -224,7 +224,7 @@ export class ShaderPass extends ShaderPart { let fragmentSource = ShaderMacroProcessor.evaluate(this._fragmentShaderInstructions, macroMap); let instanceLayout: InstanceLayout | null = null; - if (isGpuInstance) { + if (isGPUInstance) { const injected = ShaderFactory._injectInstanceUBO(engine, vertexSource, fragmentSource); vertexSource = injected.vertexSource; fragmentSource = injected.fragmentSource; diff --git a/packages/core/src/shaderlib/ShaderFactory.ts b/packages/core/src/shaderlib/ShaderFactory.ts index ba0cd9ab3d..8fa7935879 100644 --- a/packages/core/src/shaderlib/ShaderFactory.ts +++ b/packages/core/src/shaderlib/ShaderFactory.ts @@ -60,7 +60,7 @@ export class ShaderFactory { macroCollection: ShaderMacroCollection, vertexSource: string, fragmentSource: string, - isGpuInstance: boolean = false + isGPUInstance: boolean = false ): { vertexSource: string; fragmentSource: string; instanceLayout: InstanceLayout | null } { const rhi = engine._hardwareRenderer; const isWebGL2 = rhi.isWebGL2; @@ -82,7 +82,7 @@ export class ShaderFactory { noIncludeFrag = macroStr + noIncludeFrag; let instanceLayout: InstanceLayout | null = null; - if (isGpuInstance) { + if (isGPUInstance) { const injected = ShaderFactory._injectInstanceUBO(engine, noIncludeVertex, noIncludeFrag); noIncludeVertex = injected.vertexSource; noIncludeFrag = injected.fragmentSource; From 5d02bbe188d4d702f2e83942940d1a5383e23040 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sat, 11 Apr 2026 18:13:30 +0800 Subject: [PATCH 30/91] refactor: inline _compilePlatformSource into _getCanonicalShaderProgram --- packages/core/src/shader/ShaderPass.ts | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/packages/core/src/shader/ShaderPass.ts b/packages/core/src/shader/ShaderPass.ts index a1ba6cae1e..f93a88080f 100644 --- a/packages/core/src/shader/ShaderPass.ts +++ b/packages/core/src/shader/ShaderPass.ts @@ -176,27 +176,19 @@ export class ShaderPass extends ShaderPart { const { vertexSource, fragmentSource, instanceLayout } = this._platformTarget != undefined ? this._compileShaderLabSource(engine, macroCollection, isGPUInstance) - : this._compilePlatformSource(engine, macroCollection, isGPUInstance); + : ShaderFactory.compilePlatformSource( + engine, + macroCollection, + this._vertexSource, + this._fragmentSource, + isGPUInstance + ); const program = new ShaderProgram(engine, vertexSource, fragmentSource); program._instanceLayout = instanceLayout; return program; } - private _compilePlatformSource( - engine: Engine, - macroCollection: ShaderMacroCollection, - isGPUInstance: boolean - ): { vertexSource: string; fragmentSource: string; instanceLayout: InstanceLayout | null } { - return ShaderFactory.compilePlatformSource( - engine, - macroCollection, - this._vertexSource, - this._fragmentSource, - isGPUInstance - ); - } - private _compileShaderLabSource( engine: Engine, macroCollection: ShaderMacroCollection, From 6a01018981940290babec229b5415913e3cd30ef Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sat, 11 Apr 2026 18:14:27 +0800 Subject: [PATCH 31/91] refactor: rename _getCanonicalShaderProgram to _compileShaderProgram --- packages/core/src/shader/ShaderPass.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/shader/ShaderPass.ts b/packages/core/src/shader/ShaderPass.ts index f93a88080f..e25b589bb2 100644 --- a/packages/core/src/shader/ShaderPass.ts +++ b/packages/core/src/shader/ShaderPass.ts @@ -152,7 +152,7 @@ export class ShaderPass extends ShaderPart { return shaderProgram; } - shaderProgram = this._getCanonicalShaderProgram(engine, macroCollection); + shaderProgram = this._compileShaderProgram(engine, macroCollection); shaderProgramMap.cache(shaderProgram); return shaderProgram; @@ -171,7 +171,7 @@ export class ShaderPass extends ShaderPart { shaderProgramMaps.length = 0; } - private _getCanonicalShaderProgram(engine: Engine, macroCollection: ShaderMacroCollection): ShaderProgram { + private _compileShaderProgram(engine: Engine, macroCollection: ShaderMacroCollection): ShaderProgram { const isGPUInstance = macroCollection.isEnable(InstanceBatch.gpuInstanceMacro); const { vertexSource, fragmentSource, instanceLayout } = this._platformTarget != undefined From 967fc6be3bbe26f147f23baee5d9e0ce9c2fa9ce Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sat, 11 Apr 2026 18:17:38 +0800 Subject: [PATCH 32/91] refactor: move bindUniformBlocks next to other public methods --- packages/core/src/shader/ShaderProgram.ts | 30 +++++++++++------------ 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/core/src/shader/ShaderProgram.ts b/packages/core/src/shader/ShaderProgram.ts index d50b0ebfe5..7c98b718ab 100644 --- a/packages/core/src/shader/ShaderProgram.ts +++ b/packages/core/src/shader/ShaderProgram.ts @@ -175,6 +175,21 @@ export class ShaderProgram { } } + /** + * Bind uniform blocks to the specified binding points. + * @param bindingMap - Map of ShaderBlockProperty._uniqueId to binding point + */ + bindUniformBlocks(bindingMap: Record): void { + const gl = this._gl; + const ids = this.uniformBlockIds; + for (let i = 0, n = ids.length; i < n; i++) { + const bindingPoint = bindingMap[ids[i]]; + if (bindingPoint !== undefined) { + gl.uniformBlockBinding(this._glProgram, i, bindingPoint); + } + } + } + /** * Destroy this shader program. */ @@ -493,21 +508,6 @@ export class ShaderProgram { } } - /** - * Bind uniform blocks to the specified binding points. - * @param bindingMap - Map of ShaderBlockProperty._uniqueId to binding point - */ - bindUniformBlocks(bindingMap: Record): void { - const gl = this._gl; - const ids = this.uniformBlockIds; - for (let i = 0, n = ids.length; i < n; i++) { - const bindingPoint = bindingMap[ids[i]]; - if (bindingPoint !== undefined) { - gl.uniformBlockBinding(this._glProgram, i, bindingPoint); - } - } - } - private _getUniformInfos(): WebGLActiveInfo[] { const gl = this._gl; const program = this._glProgram; From b420f61321f37a64d9d898f570b395e21d6afc5e Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sat, 11 Apr 2026 18:20:05 +0800 Subject: [PATCH 33/91] refactor: remove empty SubShader._destroy --- packages/core/src/shader/Shader.ts | 1 - packages/core/src/shader/SubShader.ts | 5 ----- 2 files changed, 6 deletions(-) diff --git a/packages/core/src/shader/Shader.ts b/packages/core/src/shader/Shader.ts index ed8456c8ca..47aca7fe49 100644 --- a/packages/core/src/shader/Shader.ts +++ b/packages/core/src/shader/Shader.ts @@ -439,7 +439,6 @@ export class Shader implements IReferable { const subShaders = this._subShaders; for (let i = 0, n = subShaders.length; i < n; i++) { const subShader = subShaders[i]; - subShader._destroy(); const passes = subShader.passes; for (let j = 0, m = passes.length; j < m; j++) { passes[j]._destroy(); diff --git a/packages/core/src/shader/SubShader.ts b/packages/core/src/shader/SubShader.ts index f9bef85e3a..d020acc4e0 100644 --- a/packages/core/src/shader/SubShader.ts +++ b/packages/core/src/shader/SubShader.ts @@ -32,9 +32,4 @@ export class SubShader extends ShaderPart { this.setTag(key, tags[key]); } } - - /** - * @internal - */ - _destroy(): void {} } From 1d846102d2d445b3812218a11ecb7f65adcdc99a Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sat, 11 Apr 2026 18:23:05 +0800 Subject: [PATCH 34/91] refactor: inline single-use subShader variable --- packages/core/src/shader/Shader.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/core/src/shader/Shader.ts b/packages/core/src/shader/Shader.ts index 47aca7fe49..6237ab923b 100644 --- a/packages/core/src/shader/Shader.ts +++ b/packages/core/src/shader/Shader.ts @@ -438,8 +438,7 @@ export class Shader implements IReferable { const subShaders = this._subShaders; for (let i = 0, n = subShaders.length; i < n; i++) { - const subShader = subShaders[i]; - const passes = subShader.passes; + const passes = subShaders[i].passes; for (let j = 0, m = passes.length; j < m; j++) { passes[j]._destroy(); } From a85020078d9eabebc3aa915f7fa59acbf2ac136b Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sat, 11 Apr 2026 18:24:08 +0800 Subject: [PATCH 35/91] refactor: remove unused GLSLIfdefResolver No callers remain after eliminating _scanInstanceFields --- .../core/src/shaderlib/GLSLIfdefResolver.ts | 41 ------------------- 1 file changed, 41 deletions(-) delete mode 100644 packages/core/src/shaderlib/GLSLIfdefResolver.ts diff --git a/packages/core/src/shaderlib/GLSLIfdefResolver.ts b/packages/core/src/shaderlib/GLSLIfdefResolver.ts deleted file mode 100644 index f3c45de00e..0000000000 --- a/packages/core/src/shaderlib/GLSLIfdefResolver.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * @internal - * Simple #ifdef/#ifndef/#else/#endif resolver based on a macro set. - * Temporary utility for GLSL platform path — will be removed when GLSL path is deleted. - */ -export function resolveIfdef(source: string, macroSet: { has(name: string): boolean }): string { - const lines = source.split("\n"); - const result: string[] = []; - const stack: boolean[] = [true]; - const taken: boolean[] = [false]; - - for (let i = 0, n = lines.length; i < n; i++) { - const trimmed = lines[i].trimStart(); - - if (trimmed.startsWith("#ifdef ")) { - const macro = trimmed.substring(7).trim(); - const active = stack[stack.length - 1] && macroSet.has(macro); - stack.push(active); - taken.push(active); - } else if (trimmed.startsWith("#ifndef ")) { - const macro = trimmed.substring(8).trim(); - const active = stack[stack.length - 1] && !macroSet.has(macro); - stack.push(active); - taken.push(active); - } else if (trimmed.startsWith("#else")) { - const parentActive = stack.length > 1 ? stack[stack.length - 2] : true; - const active = parentActive && !taken[taken.length - 1]; - stack[stack.length - 1] = active; - if (active) taken[taken.length - 1] = true; - } else if (trimmed.startsWith("#endif")) { - if (stack.length > 1) { - stack.pop(); - taken.pop(); - } - } else if (stack[stack.length - 1]) { - result.push(lines[i]); - } - } - - return result.join("\n"); -} From 34d5570fa277d97e08d93805c5f548c95c41ca7e Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sat, 11 Apr 2026 18:37:51 +0800 Subject: [PATCH 36/91] refactor: move InstanceFieldInfo and InstanceLayout after ShaderFactory class --- packages/core/src/shaderlib/ShaderFactory.ts | 37 +++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/packages/core/src/shaderlib/ShaderFactory.ts b/packages/core/src/shaderlib/ShaderFactory.ts index 8fa7935879..dcc3544e1f 100644 --- a/packages/core/src/shaderlib/ShaderFactory.ts +++ b/packages/core/src/shaderlib/ShaderFactory.ts @@ -9,23 +9,6 @@ import { ShaderMacroCollection } from "../shader/ShaderMacroCollection"; import { ShaderProperty } from "../shader/ShaderProperty"; import { ShaderLib } from "./ShaderLib"; -/** - * @internal - */ -export interface InstanceFieldInfo { - property: ShaderProperty; - type: string; - offset: number; - useIntView: boolean; - pack: (view: Float32Array | Int32Array, offset: number, value: any) => void; -} - -export interface InstanceLayout { - instanceFields: InstanceFieldInfo[]; - instanceMaxCount: number; - structSize: number; -} - export class ShaderFactory { /** @internal */ static readonly RENDERER_INSTANCE_BLOCK_NAME = "RendererInstanceData"; @@ -452,3 +435,23 @@ export class ShaderFactory { return shader; } } + +/** + * @internal + */ +export interface InstanceFieldInfo { + property: ShaderProperty; + type: string; + offset: number; + useIntView: boolean; + pack: (view: Float32Array | Int32Array, offset: number, value: any) => void; +} + +/** + * @internal + */ +export interface InstanceLayout { + instanceFields: InstanceFieldInfo[]; + instanceMaxCount: number; + structSize: number; +} From a62929246cc60446ccb0a12cedb17faf0d753d5a Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sat, 11 Apr 2026 18:40:14 +0800 Subject: [PATCH 37/91] refactor: remove default value from isGPUInstance parameter --- packages/core/src/graphic/TransformFeedbackShader.ts | 3 ++- packages/core/src/shaderlib/ShaderFactory.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/core/src/graphic/TransformFeedbackShader.ts b/packages/core/src/graphic/TransformFeedbackShader.ts index 9bd34cad06..64ffd7d114 100644 --- a/packages/core/src/graphic/TransformFeedbackShader.ts +++ b/packages/core/src/graphic/TransformFeedbackShader.ts @@ -37,7 +37,8 @@ export class TransformFeedbackShader { engine, macroCollection, this.vertexSource, - this.fragmentSource + this.fragmentSource, + false ); program = new ShaderProgram(engine, vertexSource, fragmentSource, this.feedbackVaryings); diff --git a/packages/core/src/shaderlib/ShaderFactory.ts b/packages/core/src/shaderlib/ShaderFactory.ts index dcc3544e1f..dfe0302c13 100644 --- a/packages/core/src/shaderlib/ShaderFactory.ts +++ b/packages/core/src/shaderlib/ShaderFactory.ts @@ -43,7 +43,7 @@ export class ShaderFactory { macroCollection: ShaderMacroCollection, vertexSource: string, fragmentSource: string, - isGPUInstance: boolean = false + isGPUInstance: boolean ): { vertexSource: string; fragmentSource: string; instanceLayout: InstanceLayout | null } { const rhi = engine._hardwareRenderer; const isWebGL2 = rhi.isWebGL2; From f7ad8774a59095fcbeab40ebf1ad44a177d4bdf2 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sat, 11 Apr 2026 18:42:35 +0800 Subject: [PATCH 38/91] refactor: reorder ShaderFactory - group static fields before methods --- packages/core/src/shaderlib/ShaderFactory.ts | 304 +++++++++---------- 1 file changed, 152 insertions(+), 152 deletions(-) diff --git a/packages/core/src/shaderlib/ShaderFactory.ts b/packages/core/src/shaderlib/ShaderFactory.ts index dfe0302c13..1389e74504 100644 --- a/packages/core/src/shaderlib/ShaderFactory.ts +++ b/packages/core/src/shaderlib/ShaderFactory.ts @@ -23,8 +23,94 @@ export class ShaderFactory { .map((e) => `#extension ${e} : enable\n`) .join(""); + /** @internal std140 layout info by GLSL type string. */ + static _std140Map: Record = { + float: { size: 4, align: 4 }, + int: { size: 4, align: 4 }, + uint: { size: 4, align: 4 }, + vec2: { size: 8, align: 8 }, + ivec2: { size: 8, align: 8 }, + vec3: { size: 12, align: 16 }, + ivec3: { size: 12, align: 16 }, + vec4: { size: 16, align: 16 }, + ivec4: { size: 16, align: 16 }, + mat4: { size: 64, align: 16 }, + mat4_affine: { size: 48, align: 16 } + }; + private static readonly _has300OutInFragReg = /\bout\s+(?:\w+\s+)?(?:vec4)\s+(?:\w+)\s*;/; // [layout(location = 0)] out [highp] vec4 [color]; + /** Built-in renderer uniforms. value=true means derived (remove but not added to UBO) */ + private static _builtinRendererUniforms = new Map([ + ["renderer_ModelMat", false], + ["renderer_Layer", false], + ["renderer_LocalMat", true], + ["renderer_MVMat", true], + ["renderer_MVPMat", true], + ["renderer_NormalMat", true], + ["renderer_MVInvMat", true] + ]); + + private static _uboUniformRegex = /^([ \t]*)uniform\s+(?:(?:lowp|mediump|highp)\s+)?(\w+)\s+(\w+)\s*;/gm; + + /** Pack functions for writing typed values into ArrayBuffer views */ + private static _packFuncMap: Record = { + float: (v, o, val: number) => { + v[o] = val; + }, + int: (v, o, val: number) => { + v[o] = val; + }, + uint: (v, o, val: number) => { + v[o] = val; + }, + vec2: (v, o, val: Vector2) => { + v[o] = val.x; + v[o + 1] = val.y; + }, + ivec2: (v, o, val: Vector2) => { + v[o] = val.x; + v[o + 1] = val.y; + }, + vec3: (v, o, val: Vector3) => { + v[o] = val.x; + v[o + 1] = val.y; + v[o + 2] = val.z; + }, + ivec3: (v, o, val: Vector3) => { + v[o] = val.x; + v[o + 1] = val.y; + v[o + 2] = val.z; + }, + vec4: (v, o, val: Vector4) => { + v[o] = val.x; + v[o + 1] = val.y; + v[o + 2] = val.z; + v[o + 3] = val.w; + }, + ivec4: (v, o, val: Vector4) => { + v[o] = val.x; + v[o + 1] = val.y; + v[o + 2] = val.z; + v[o + 3] = val.w; + }, + mat4: (v, o, val: Matrix) => { + const e = val.elements; + for (let k = 0; k < 16; k++) v[o + k] = e[k]; + }, + // Affine mat4 → 3 rows (vec4 each), skip row3 (0,0,0,1). Transposed layout + mat4_affine: (v, o, val: Matrix) => { + const e = val.elements; + // row0=(e0,e4,e8,e12) row1=(e1,e5,e9,e13) row2=(e2,e6,e10,e14) + for (let r = 0; r < 3; r++) { + v[o + r * 4] = e[r]; + v[o + r * 4 + 1] = e[r + 4]; + v[o + r * 4 + 2] = e[r + 8]; + v[o + r * 4 + 3] = e[r + 12]; + } + } + }; + static parseCustomMacros(macros: ShaderMacro[]) { return macros.map((m) => `#define ${m.value ? m.name + ` ` + m.value : m.name}\n`).join(""); } @@ -95,92 +181,6 @@ export class ShaderFactory { }; } - /** Built-in renderer uniforms. value=true means derived (remove but not added to UBO). */ - private static _builtinRendererUniforms = new Map([ - ["renderer_ModelMat", false], - ["renderer_Layer", false], - ["renderer_LocalMat", true], - ["renderer_MVMat", true], - ["renderer_MVPMat", true], - ["renderer_NormalMat", true], - ["renderer_MVInvMat", true] - ]); - - private static _uboUniformRegex = /^([ \t]*)uniform\s+(?:(?:lowp|mediump|highp)\s+)?(\w+)\s+(\w+)\s*;/gm; - - /** @internal std140 layout info by GLSL type string. */ - static _std140Map: Record = { - float: { size: 4, align: 4 }, - int: { size: 4, align: 4 }, - uint: { size: 4, align: 4 }, - vec2: { size: 8, align: 8 }, - ivec2: { size: 8, align: 8 }, - vec3: { size: 12, align: 16 }, - ivec3: { size: 12, align: 16 }, - vec4: { size: 16, align: 16 }, - ivec4: { size: 16, align: 16 }, - mat4: { size: 64, align: 16 }, - mat4_affine: { size: 48, align: 16 } - }; - - /** Pack functions for writing typed values into ArrayBuffer views. */ - private static _packFuncMap: Record = { - float: (v, o, val: number) => { - v[o] = val; - }, - int: (v, o, val: number) => { - v[o] = val; - }, - uint: (v, o, val: number) => { - v[o] = val; - }, - vec2: (v, o, val: Vector2) => { - v[o] = val.x; - v[o + 1] = val.y; - }, - ivec2: (v, o, val: Vector2) => { - v[o] = val.x; - v[o + 1] = val.y; - }, - vec3: (v, o, val: Vector3) => { - v[o] = val.x; - v[o + 1] = val.y; - v[o + 2] = val.z; - }, - ivec3: (v, o, val: Vector3) => { - v[o] = val.x; - v[o + 1] = val.y; - v[o + 2] = val.z; - }, - vec4: (v, o, val: Vector4) => { - v[o] = val.x; - v[o + 1] = val.y; - v[o + 2] = val.z; - v[o + 3] = val.w; - }, - ivec4: (v, o, val: Vector4) => { - v[o] = val.x; - v[o + 1] = val.y; - v[o + 2] = val.z; - v[o + 3] = val.w; - }, - mat4: (v, o, val: Matrix) => { - const e = val.elements; - for (let k = 0; k < 16; k++) v[o + k] = e[k]; - }, - // Affine mat4 → 3 rows (vec4 each), skip row3 (0,0,0,1). Transposed layout. - mat4_affine: (v, o, val: Matrix) => { - const e = val.elements; - // row0=(e0,e4,e8,e12) row1=(e1,e5,e9,e13) row2=(e2,e6,e10,e14) - for (let r = 0; r < 3; r++) { - v[o + r * 4] = e[r]; - v[o + r * 4 + 1] = e[r + 4]; - v[o + r * 4 + 2] = e[r + 8]; - v[o + r * 4 + 3] = e[r + 12]; - } - } - }; - /** * @internal * For GPU instancing shaders, scan VS and FS for `uniform ... renderer_*` declarations, @@ -260,6 +260,71 @@ export class ShaderFactory { }); } + static registerInclude(includeName: string, includeSource: string) { + if (ShaderLib[includeName]) { + throw `The "${includeName}" shader include already exist`; + } + ShaderLib[includeName] = includeSource; + } + + static unRegisterInclude(includeName: string) { + delete ShaderLib[includeName]; + } + + /** + * @param regex The default regex is for engine's builtin glsl `#include` syntax, + * since `ShaderLab` use the same parsing function but different syntax for `#include` --- `/^[ \t]*#include +"([\w\d.]+)"/gm` + */ + static parseIncludes(src: string, regex = /^[ \t]*#include +<([\w\d.]+)>/gm) { + function replace(match, slice) { + var replace = ShaderLib[slice]; + + if (replace === undefined) { + Logger.error(`Shader slice "${match.trim()}" not founded.`); + return ""; + } + + return ShaderFactory.parseIncludes(replace, regex); + } + + return src.replace(regex, replace); + } + + /** + * Convert lower GLSL version to GLSL 300 es. + * @param shader - code + * @param isFrag - Whether it is a fragment shader. + */ + static convertTo300(shader: string, isFrag?: boolean) { + shader = shader.replace(/\bvarying\b/g, isFrag ? "in" : "out"); + shader = shader.replace(/\btexture(2D|Cube)\b/g, "texture"); + shader = shader.replace(/\btexture2DProj\b/g, "textureProj"); + shader = shader.replace(/\btexture(2D|Cube)LodEXT\b/g, "textureLod"); + shader = shader.replace(/\btexture(2D|Cube)GradEXT\b/g, "textureGrad"); + shader = shader.replace(/\btexture2DProjLodEXT\b/g, "textureProjLod"); + shader = shader.replace(/\btexture2DProjGradEXT\b/g, "textureProjGrad"); + + if (isFrag) { + shader = shader.replace(/\bgl_FragDepthEXT\b/g, "gl_FragDepth"); + + if (!ShaderFactory._has300Output(shader)) { + const isMRT = /\bgl_FragData\[.+?\]/g.test(shader); + if (isMRT) { + shader = shader.replace(/\bgl_FragColor\b/g, "gl_FragData[0]"); + const result = shader.match(/\bgl_FragData\[.+?\]/g); + shader = this._replaceMRTShader(shader, result); + } else { + shader = "out vec4 glFragColor;\n" + shader; + shader = shader.replace(/\bgl_FragColor\b/g, "glFragColor"); + } + } + } else { + shader = shader.replace(/\battribute\b/g, "in"); + } + + return shader; + } + private static _buildLayout(engine: Engine, fieldMap: Record): InstanceLayout { const maxUBOSize = engine._hardwareRenderer.getMaxUniformBlockSize(); const std140Map = ShaderFactory._std140Map; @@ -306,7 +371,7 @@ export class ShaderFactory { return { instanceFields, instanceMaxCount, structSize }; } - /** Build per-field #define lines, using `idExpr` as the instance index (gl_InstanceID or v_instanceID). */ + /** Build per-field #define lines, using `idExpr` as the instance index (gl_InstanceID or v_instanceID) */ private static _buildFieldDefines(fields: InstanceFieldInfo[], idExpr: string): string { const lines: string[] = []; for (let i = 0; i < fields.length; i++) { @@ -346,71 +411,6 @@ export class ShaderFactory { return lines.join("\n"); } - static registerInclude(includeName: string, includeSource: string) { - if (ShaderLib[includeName]) { - throw `The "${includeName}" shader include already exist`; - } - ShaderLib[includeName] = includeSource; - } - - static unRegisterInclude(includeName: string) { - delete ShaderLib[includeName]; - } - - /** - * @param regex The default regex is for engine's builtin glsl `#include` syntax, - * since `ShaderLab` use the same parsing function but different syntax for `#include` --- `/^[ \t]*#include +"([\w\d.]+)"/gm` - */ - static parseIncludes(src: string, regex = /^[ \t]*#include +<([\w\d.]+)>/gm) { - function replace(match, slice) { - var replace = ShaderLib[slice]; - - if (replace === undefined) { - Logger.error(`Shader slice "${match.trim()}" not founded.`); - return ""; - } - - return ShaderFactory.parseIncludes(replace, regex); - } - - return src.replace(regex, replace); - } - - /** - * Convert lower GLSL version to GLSL 300 es. - * @param shader - code - * @param isFrag - Whether it is a fragment shader. - */ - static convertTo300(shader: string, isFrag?: boolean) { - shader = shader.replace(/\bvarying\b/g, isFrag ? "in" : "out"); - shader = shader.replace(/\btexture(2D|Cube)\b/g, "texture"); - shader = shader.replace(/\btexture2DProj\b/g, "textureProj"); - shader = shader.replace(/\btexture(2D|Cube)LodEXT\b/g, "textureLod"); - shader = shader.replace(/\btexture(2D|Cube)GradEXT\b/g, "textureGrad"); - shader = shader.replace(/\btexture2DProjLodEXT\b/g, "textureProjLod"); - shader = shader.replace(/\btexture2DProjGradEXT\b/g, "textureProjGrad"); - - if (isFrag) { - shader = shader.replace(/\bgl_FragDepthEXT\b/g, "gl_FragDepth"); - - if (!ShaderFactory._has300Output(shader)) { - const isMRT = /\bgl_FragData\[.+?\]/g.test(shader); - if (isMRT) { - shader = shader.replace(/\bgl_FragColor\b/g, "gl_FragData[0]"); - const result = shader.match(/\bgl_FragData\[.+?\]/g); - shader = this._replaceMRTShader(shader, result); - } else { - shader = "out vec4 glFragColor;\n" + shader; - shader = shader.replace(/\bgl_FragColor\b/g, "glFragColor"); - } - } - } else { - shader = shader.replace(/\battribute\b/g, "in"); - } - - return shader; - } - private static _has300Output(fragmentShader: string): boolean { return ShaderFactory._has300OutInFragReg.test(fragmentShader); } From 76b23a0c767116074f4fbc099c11786e8554998f Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sat, 11 Apr 2026 18:58:11 +0800 Subject: [PATCH 39/91] refactor: clean up ShaderFactory naming and visibility - Add @internal to class, remove per-member @internal - Public members drop underscore prefix, private members keep it - Make std140Map private --- packages/core/src/shader/ShaderPass.ts | 4 +- packages/core/src/shaderlib/ShaderFactory.ts | 61 +++++++++----------- 2 files changed, 29 insertions(+), 36 deletions(-) diff --git a/packages/core/src/shader/ShaderPass.ts b/packages/core/src/shader/ShaderPass.ts index e25b589bb2..b8135fdab3 100644 --- a/packages/core/src/shader/ShaderPass.ts +++ b/packages/core/src/shader/ShaderPass.ts @@ -217,7 +217,7 @@ export class ShaderPass extends ShaderPart { let instanceLayout: InstanceLayout | null = null; if (isGPUInstance) { - const injected = ShaderFactory._injectInstanceUBO(engine, vertexSource, fragmentSource); + const injected = ShaderFactory.injectInstanceUBO(engine, vertexSource, fragmentSource); vertexSource = injected.vertexSource; fragmentSource = injected.fragmentSource; instanceLayout = injected.instanceLayout; @@ -235,7 +235,7 @@ export class ShaderPass extends ShaderPart { ${vertexSource} `, fragmentSource: ` ${versionStr} - ${isWebGL2 ? "" : ShaderFactory._shaderExtension} + ${isWebGL2 ? "" : ShaderFactory.shaderExtension} ${precisionStr} ${fragmentSource} `, diff --git a/packages/core/src/shaderlib/ShaderFactory.ts b/packages/core/src/shaderlib/ShaderFactory.ts index 1389e74504..6a52b83af0 100644 --- a/packages/core/src/shaderlib/ShaderFactory.ts +++ b/packages/core/src/shaderlib/ShaderFactory.ts @@ -9,12 +9,13 @@ import { ShaderMacroCollection } from "../shader/ShaderMacroCollection"; import { ShaderProperty } from "../shader/ShaderProperty"; import { ShaderLib } from "./ShaderLib"; +/** + * @internal + */ export class ShaderFactory { - /** @internal */ static readonly RENDERER_INSTANCE_BLOCK_NAME = "RendererInstanceData"; - /** @internal */ - static readonly _shaderExtension = [ + static readonly shaderExtension = [ "GL_EXT_shader_texture_lod", "GL_OES_standard_derivatives", "GL_EXT_draw_buffers", @@ -23,8 +24,8 @@ export class ShaderFactory { .map((e) => `#extension ${e} : enable\n`) .join(""); - /** @internal std140 layout info by GLSL type string. */ - static _std140Map: Record = { + /** std140 layout info by GLSL type string */ + private static readonly _std140Map: Record = { float: { size: 4, align: 4 }, int: { size: 4, align: 4 }, uint: { size: 4, align: 4 }, @@ -116,13 +117,7 @@ export class ShaderFactory { } /** - * @internal * Compile vertex and fragment source with standard macros, includes, and version header. - * @param engine - Engine instance - * @param macroCollection - Current macro collection - * @param vertexSource - Raw vertex shader source (may contain #include) - * @param fragmentSource - Raw fragment shader source - * @returns Compiled { vertexSource, fragmentSource } ready for ShaderProgram */ static compilePlatformSource( engine: Engine, @@ -152,7 +147,7 @@ export class ShaderFactory { let instanceLayout: InstanceLayout | null = null; if (isGPUInstance) { - const injected = ShaderFactory._injectInstanceUBO(engine, noIncludeVertex, noIncludeFrag); + const injected = ShaderFactory.injectInstanceUBO(engine, noIncludeVertex, noIncludeFrag); noIncludeVertex = injected.vertexSource; noIncludeFrag = injected.fragmentSource; instanceLayout = injected.instanceLayout; @@ -176,18 +171,17 @@ export class ShaderFactory { return { vertexSource: `${versionStr}\nprecision highp float;\n${noIncludeVertex}`, - fragmentSource: `${versionStr}\n${isWebGL2 ? "" : ShaderFactory._shaderExtension}${precisionStr}${noIncludeFrag}`, + fragmentSource: `${versionStr}\n${isWebGL2 ? "" : ShaderFactory.shaderExtension}${precisionStr}${noIncludeFrag}`, instanceLayout }; } /** - * @internal * For GPU instancing shaders, scan VS and FS for `uniform ... renderer_*` declarations, * compute their union, generate a full UBO struct + `#define` remapping, and inject into source. * Also computes std140 layout and INSTANCE_MAX_COUNT from maxUBOSize. */ - static _injectInstanceUBO( + static injectInstanceUBO( engine: Engine, vertexSource: string, fragmentSource: string @@ -241,25 +235,6 @@ export class ShaderFactory { return { vertexSource, fragmentSource, instanceLayout }; } - /** - * @internal - * Scan source for renderer-group uniforms, collect into fieldMap, and remove matched declarations - */ - static _scanInstanceUniforms(source: string, fieldMap: Record): string { - const builtinUniforms = ShaderFactory._builtinRendererUniforms; - return source.replace(ShaderFactory._uboUniformRegex, (match, _indent, type, name) => { - if (type.indexOf("sampler") !== -1) return match; - const isDerived = builtinUniforms.get(name); - if (isDerived === undefined && ShaderProperty._getShaderPropertyGroup(name) !== ShaderDataGroup.Renderer) - return match; - if (isDerived) return ""; - // Store ModelMat as affine (3×vec4) to save UBO space - fieldMap[ShaderProperty.getByName(name)._uniqueId] = - type === "mat4" && name === "renderer_ModelMat" ? "mat4_affine" : type; - return ""; - }); - } - static registerInclude(includeName: string, includeSource: string) { if (ShaderLib[includeName]) { throw `The "${includeName}" shader include already exist`; @@ -325,6 +300,24 @@ export class ShaderFactory { return shader; } + /** + * Scan source for renderer-group uniforms, collect into fieldMap, and remove matched declarations + */ + private static _scanInstanceUniforms(source: string, fieldMap: Record): string { + const builtinUniforms = ShaderFactory._builtinRendererUniforms; + return source.replace(ShaderFactory._uboUniformRegex, (match, _indent, type, name) => { + if (type.indexOf("sampler") !== -1) return match; + const isDerived = builtinUniforms.get(name); + if (isDerived === undefined && ShaderProperty._getShaderPropertyGroup(name) !== ShaderDataGroup.Renderer) + return match; + if (isDerived) return ""; + // Store ModelMat as affine (3×vec4) to save UBO space + fieldMap[ShaderProperty.getByName(name)._uniqueId] = + type === "mat4" && name === "renderer_ModelMat" ? "mat4_affine" : type; + return ""; + }); + } + private static _buildLayout(engine: Engine, fieldMap: Record): InstanceLayout { const maxUBOSize = engine._hardwareRenderer.getMaxUniformBlockSize(); const std140Map = ShaderFactory._std140Map; From 9a82af5e52117dec604f7dc62d5b47682e5d0ad8 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sat, 11 Apr 2026 19:06:12 +0800 Subject: [PATCH 40/91] refactor: optimize ShaderFactory - merge pack functions, extract constants, inline helper --- packages/core/src/shaderlib/ShaderFactory.ts | 133 ++++++++----------- 1 file changed, 59 insertions(+), 74 deletions(-) diff --git a/packages/core/src/shaderlib/ShaderFactory.ts b/packages/core/src/shaderlib/ShaderFactory.ts index 6a52b83af0..ecb0aa0b47 100644 --- a/packages/core/src/shaderlib/ShaderFactory.ts +++ b/packages/core/src/shaderlib/ShaderFactory.ts @@ -39,7 +39,22 @@ export class ShaderFactory { mat4_affine: { size: 48, align: 16 } }; - private static readonly _has300OutInFragReg = /\bout\s+(?:\w+\s+)?(?:vec4)\s+(?:\w+)\s*;/; // [layout(location = 0)] out [highp] vec4 [color]; + private static readonly _has300OutInFragReg = /\bout\s+(?:\w+\s+)?vec4\s+\w+\s*;/; + + private static readonly _precisionStr = ` +#ifdef GL_FRAGMENT_PRECISION_HIGH + precision highp float; + precision highp int; +#else + precision mediump float; + precision mediump int; +#endif +`; + + private static readonly _derivedDefines = + "#define renderer_MVMat (camera_ViewMat * renderer_ModelMat)\n" + + "#define renderer_MVPMat (camera_VPMat * renderer_ModelMat)\n" + + "#define renderer_NormalMat mat4(transpose(inverse(mat3(renderer_ModelMat))))"; /** Built-in renderer uniforms. value=true means derived (remove but not added to UBO) */ private static _builtinRendererUniforms = new Map([ @@ -55,62 +70,52 @@ export class ShaderFactory { private static _uboUniformRegex = /^([ \t]*)uniform\s+(?:(?:lowp|mediump|highp)\s+)?(\w+)\s+(\w+)\s*;/gm; /** Pack functions for writing typed values into ArrayBuffer views */ - private static _packFuncMap: Record = { - float: (v, o, val: number) => { - v[o] = val; - }, - int: (v, o, val: number) => { - v[o] = val; - }, - uint: (v, o, val: number) => { + private static _packFuncMap: Record = (() => { + const packScalar = (v: Float32Array | Int32Array, o: number, val: number) => { v[o] = val; - }, - vec2: (v, o, val: Vector2) => { - v[o] = val.x; - v[o + 1] = val.y; - }, - ivec2: (v, o, val: Vector2) => { - v[o] = val.x; - v[o + 1] = val.y; - }, - vec3: (v, o, val: Vector3) => { - v[o] = val.x; - v[o + 1] = val.y; - v[o + 2] = val.z; - }, - ivec3: (v, o, val: Vector3) => { + }; + const packVec2 = (v: Float32Array | Int32Array, o: number, val: Vector2) => { v[o] = val.x; v[o + 1] = val.y; - v[o + 2] = val.z; - }, - vec4: (v, o, val: Vector4) => { + }; + const packVec3 = (v: Float32Array | Int32Array, o: number, val: Vector3) => { v[o] = val.x; v[o + 1] = val.y; v[o + 2] = val.z; - v[o + 3] = val.w; - }, - ivec4: (v, o, val: Vector4) => { + }; + const packVec4 = (v: Float32Array | Int32Array, o: number, val: Vector4) => { v[o] = val.x; v[o + 1] = val.y; v[o + 2] = val.z; v[o + 3] = val.w; - }, - mat4: (v, o, val: Matrix) => { - const e = val.elements; - for (let k = 0; k < 16; k++) v[o + k] = e[k]; - }, - // Affine mat4 → 3 rows (vec4 each), skip row3 (0,0,0,1). Transposed layout - mat4_affine: (v, o, val: Matrix) => { - const e = val.elements; - // row0=(e0,e4,e8,e12) row1=(e1,e5,e9,e13) row2=(e2,e6,e10,e14) - for (let r = 0; r < 3; r++) { - v[o + r * 4] = e[r]; - v[o + r * 4 + 1] = e[r + 4]; - v[o + r * 4 + 2] = e[r + 8]; - v[o + r * 4 + 3] = e[r + 12]; + }; + return { + float: packScalar, + int: packScalar, + uint: packScalar, + vec2: packVec2, + ivec2: packVec2, + vec3: packVec3, + ivec3: packVec3, + vec4: packVec4, + ivec4: packVec4, + mat4: (v: Float32Array | Int32Array, o: number, val: Matrix) => { + const e = val.elements; + for (let k = 0; k < 16; k++) v[o + k] = e[k]; + }, + // Affine mat4 → 3 rows (vec4 each), skip row3 (0,0,0,1). Transposed layout + mat4_affine: (v: Float32Array | Int32Array, o: number, val: Matrix) => { + const e = val.elements; + // row0=(e0,e4,e8,e12) row1=(e1,e5,e9,e13) row2=(e2,e6,e10,e14) + for (let r = 0; r < 3; r++) { + v[o + r * 4] = e[r]; + v[o + r * 4 + 1] = e[r + 4]; + v[o + r * 4 + 2] = e[r + 8]; + v[o + r * 4 + 3] = e[r + 12]; + } } - } - }; + }; + })(); static parseCustomMacros(macros: ShaderMacro[]) { return macros.map((m) => `#define ${m.value ? m.name + ` ` + m.value : m.name}\n`).join(""); @@ -159,19 +164,10 @@ export class ShaderFactory { } const versionStr = isWebGL2 ? "#version 300 es" : "#version 100"; - const precisionStr = ` -#ifdef GL_FRAGMENT_PRECISION_HIGH - precision highp float; - precision highp int; -#else - precision mediump float; - precision mediump int; -#endif -`; return { vertexSource: `${versionStr}\nprecision highp float;\n${noIncludeVertex}`, - fragmentSource: `${versionStr}\n${isWebGL2 ? "" : ShaderFactory.shaderExtension}${precisionStr}${noIncludeFrag}`, + fragmentSource: `${versionStr}\n${isWebGL2 ? "" : ShaderFactory.shaderExtension}${ShaderFactory._precisionStr}${noIncludeFrag}`, instanceLayout }; } @@ -217,10 +213,7 @@ export class ShaderFactory { `layout(std140) uniform ${ShaderFactory.RENDERER_INSTANCE_BLOCK_NAME} {\n` + ` RendererInstanceStruct rendererData[INSTANCE_MAX_COUNT];\n};\n`; - const derivedDefines = - "#define renderer_MVMat (camera_ViewMat * renderer_ModelMat)\n" + - "#define renderer_MVPMat (camera_VPMat * renderer_ModelMat)\n" + - "#define renderer_NormalMat mat4(transpose(inverse(mat3(renderer_ModelMat))))"; + const derivedDefines = ShaderFactory._derivedDefines; const vsUboBlock = `${uboStruct}flat out int v_instanceID;\n${ShaderFactory._buildFieldDefines(instanceFields, "gl_InstanceID")}\n${derivedDefines}\n`; const fsUboBlock = `${uboStruct}flat in int v_instanceID;\n${ShaderFactory._buildFieldDefines(instanceFields, "v_instanceID")}\n${derivedDefines}\n`; @@ -251,18 +244,14 @@ export class ShaderFactory { * since `ShaderLab` use the same parsing function but different syntax for `#include` --- `/^[ \t]*#include +"([\w\d.]+)"/gm` */ static parseIncludes(src: string, regex = /^[ \t]*#include +<([\w\d.]+)>/gm) { - function replace(match, slice) { - var replace = ShaderLib[slice]; - - if (replace === undefined) { + return src.replace(regex, (match, slice) => { + const replacement = ShaderLib[slice]; + if (replacement === undefined) { Logger.error(`Shader slice "${match.trim()}" not founded.`); return ""; } - - return ShaderFactory.parseIncludes(replace, regex); - } - - return src.replace(regex, replace); + return ShaderFactory.parseIncludes(replacement, regex); + }); } /** @@ -282,7 +271,7 @@ export class ShaderFactory { if (isFrag) { shader = shader.replace(/\bgl_FragDepthEXT\b/g, "gl_FragDepth"); - if (!ShaderFactory._has300Output(shader)) { + if (!ShaderFactory._has300OutInFragReg.test(shader)) { const isMRT = /\bgl_FragData\[.+?\]/g.test(shader); if (isMRT) { shader = shader.replace(/\bgl_FragColor\b/g, "gl_FragData[0]"); @@ -404,10 +393,6 @@ export class ShaderFactory { return lines.join("\n"); } - private static _has300Output(fragmentShader: string): boolean { - return ShaderFactory._has300OutInFragReg.test(fragmentShader); - } - private static _replaceMRTShader(shader: string, result: string[]): string { let declaration = ""; const mrtIndexSet = new Set(); From 4f76b6b97c20ed9e291fddde2f1d22307860a5be Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sat, 11 Apr 2026 19:32:19 +0800 Subject: [PATCH 41/91] refactor: replace mat4_affine with mat3x4, clean up ShaderFactory details - Use GLSL native mat3x4 type instead of custom mat4_affine internal type - Simplify pack function (consecutive 12-float copy), UBO declaration and field defines - Extract _buildUBODeclaration from injectInstanceUBO for clarity - Add readonly to _builtinRendererUniforms and _uboUniformRegex - Remove unused regex capture group in _uboUniformRegex - Use includes() instead of indexOf() !== -1 --- packages/core/src/shaderlib/ShaderFactory.ts | 105 +++++++++---------- 1 file changed, 51 insertions(+), 54 deletions(-) diff --git a/packages/core/src/shaderlib/ShaderFactory.ts b/packages/core/src/shaderlib/ShaderFactory.ts index ecb0aa0b47..87731a6119 100644 --- a/packages/core/src/shaderlib/ShaderFactory.ts +++ b/packages/core/src/shaderlib/ShaderFactory.ts @@ -25,7 +25,7 @@ export class ShaderFactory { .join(""); /** std140 layout info by GLSL type string */ - private static readonly _std140Map: Record = { + private static readonly _std140TypeInfoMap: Record = { float: { size: 4, align: 4 }, int: { size: 4, align: 4 }, uint: { size: 4, align: 4 }, @@ -36,7 +36,7 @@ export class ShaderFactory { vec4: { size: 16, align: 16 }, ivec4: { size: 16, align: 16 }, mat4: { size: 64, align: 16 }, - mat4_affine: { size: 48, align: 16 } + mat3x4: { size: 48, align: 16 } }; private static readonly _has300OutInFragReg = /\bout\s+(?:\w+\s+)?vec4\s+\w+\s*;/; @@ -57,7 +57,7 @@ export class ShaderFactory { "#define renderer_NormalMat mat4(transpose(inverse(mat3(renderer_ModelMat))))"; /** Built-in renderer uniforms. value=true means derived (remove but not added to UBO) */ - private static _builtinRendererUniforms = new Map([ + private static readonly _builtinRendererUniforms = new Map([ ["renderer_ModelMat", false], ["renderer_Layer", false], ["renderer_LocalMat", true], @@ -67,7 +67,7 @@ export class ShaderFactory { ["renderer_MVInvMat", true] ]); - private static _uboUniformRegex = /^([ \t]*)uniform\s+(?:(?:lowp|mediump|highp)\s+)?(\w+)\s+(\w+)\s*;/gm; + private static readonly _uboUniformRegex = /^[ \t]*uniform\s+(?:(?:lowp|mediump|highp)\s+)?(\w+)\s+(\w+)\s*;/gm; /** Pack functions for writing typed values into ArrayBuffer views */ private static _packFuncMap: Record = (() => { @@ -103,16 +103,10 @@ export class ShaderFactory { const e = val.elements; for (let k = 0; k < 16; k++) v[o + k] = e[k]; }, - // Affine mat4 → 3 rows (vec4 each), skip row3 (0,0,0,1). Transposed layout - mat4_affine: (v: Float32Array | Int32Array, o: number, val: Matrix) => { + // Affine mat4 stored as mat3x4: write first 3 columns (skip col3 which is 0,0,0,1) + mat3x4: (v: Float32Array | Int32Array, o: number, val: Matrix) => { const e = val.elements; - // row0=(e0,e4,e8,e12) row1=(e1,e5,e9,e13) row2=(e2,e6,e10,e14) - for (let r = 0; r < 3; r++) { - v[o + r * 4] = e[r]; - v[o + r * 4 + 1] = e[r + 4]; - v[o + r * 4 + 2] = e[r + 8]; - v[o + r * 4 + 3] = e[r + 12]; - } + for (let k = 0; k < 12; k++) v[o + k] = e[k]; } }; })(); @@ -173,57 +167,47 @@ export class ShaderFactory { } /** - * For GPU instancing shaders, scan VS and FS for `uniform ... renderer_*` declarations, - * compute their union, generate a full UBO struct + `#define` remapping, and inject into source. - * Also computes std140 layout and INSTANCE_MAX_COUNT from maxUBOSize. + * Scan VS/FS for renderer-group `uniform` declarations, replace them with a shared + * std140 UBO (instanced array), and emit `#define` remapping so original uniform + * names resolve to `rendererData[instanceID].field`. */ static injectInstanceUBO( engine: Engine, vertexSource: string, fragmentSource: string ): { vertexSource: string; fragmentSource: string; instanceLayout: InstanceLayout | null } { + // 1. Scan & strip renderer uniforms from both stages, collect into fieldMap const fieldMap: Record = Object.create(null); vertexSource = ShaderFactory._scanInstanceUniforms(vertexSource, fieldMap); fragmentSource = ShaderFactory._scanInstanceUniforms(fragmentSource, fieldMap); + // Fast empty check without allocating an array let hasField = false; for (const _ in fieldMap) { hasField = true; break; } if (!hasField) return { vertexSource, fragmentSource, instanceLayout: null }; - const instanceLayout = ShaderFactory._buildLayout(engine, fieldMap); - - const { instanceFields, instanceMaxCount } = instanceLayout; - - // Generate UBO struct fields and per-field #define remapping - const structFieldLines: string[] = []; - for (let i = 0; i < instanceFields.length; i++) { - const { type, property } = instanceFields[i]; - if (type === "mat4_affine") { - for (let r = 0; r < 3; r++) structFieldLines.push(` vec4 ${property.name}R${r};`); - } else { - structFieldLines.push(` ${type} ${property.name};`); - } - } - const uboStruct = - `#define INSTANCE_MAX_COUNT ${instanceMaxCount}\n` + - `struct RendererInstanceStruct {\n${structFieldLines.join("\n")}\n};\n` + - `layout(std140) uniform ${ShaderFactory.RENDERER_INSTANCE_BLOCK_NAME} {\n` + - ` RendererInstanceStruct rendererData[INSTANCE_MAX_COUNT];\n};\n`; + // 2. Compute std140 layout (field offsets, struct size, max instance count) + const instanceLayout = ShaderFactory._buildLayout(engine, fieldMap); + // 3. Generate GLSL UBO block and inject into both stages + const { instanceFields } = instanceLayout; + const uboDecl = ShaderFactory._buildUBODeclaration(instanceLayout); + const fieldDefinesVS = ShaderFactory._buildFieldDefines(instanceFields, "gl_InstanceID"); + const fieldDefinesFS = ShaderFactory._buildFieldDefines(instanceFields, "v_instanceID"); const derivedDefines = ShaderFactory._derivedDefines; - const vsUboBlock = `${uboStruct}flat out int v_instanceID;\n${ShaderFactory._buildFieldDefines(instanceFields, "gl_InstanceID")}\n${derivedDefines}\n`; - const fsUboBlock = `${uboStruct}flat in int v_instanceID;\n${ShaderFactory._buildFieldDefines(instanceFields, "v_instanceID")}\n${derivedDefines}\n`; + const vsBlock = `${uboDecl}flat out int v_instanceID;\n${fieldDefinesVS}\n${derivedDefines}\n`; + const fsBlock = `${uboDecl}flat in int v_instanceID;\n${fieldDefinesFS}\n${derivedDefines}\n`; - vertexSource = ShaderFactory._insertUBOBlock(vertexSource, vsUboBlock); + vertexSource = ShaderFactory._insertUBOBlock(vertexSource, vsBlock); vertexSource = vertexSource.replace( /void\s+main\s*\(\s*\)\s*\{/, "void main() {\n v_instanceID = gl_InstanceID;" ); - fragmentSource = ShaderFactory._insertUBOBlock(fragmentSource, fsUboBlock); + fragmentSource = ShaderFactory._insertUBOBlock(fragmentSource, fsBlock); return { vertexSource, fragmentSource, instanceLayout }; } @@ -294,22 +278,22 @@ export class ShaderFactory { */ private static _scanInstanceUniforms(source: string, fieldMap: Record): string { const builtinUniforms = ShaderFactory._builtinRendererUniforms; - return source.replace(ShaderFactory._uboUniformRegex, (match, _indent, type, name) => { - if (type.indexOf("sampler") !== -1) return match; + return source.replace(ShaderFactory._uboUniformRegex, (match, type, name) => { + if (type.includes("sampler")) return match; const isDerived = builtinUniforms.get(name); if (isDerived === undefined && ShaderProperty._getShaderPropertyGroup(name) !== ShaderDataGroup.Renderer) return match; if (isDerived) return ""; - // Store ModelMat as affine (3×vec4) to save UBO space + // ModelMat is affine, store as mat3x4 (3 columns) to save 16 bytes per instance fieldMap[ShaderProperty.getByName(name)._uniqueId] = - type === "mat4" && name === "renderer_ModelMat" ? "mat4_affine" : type; + type === "mat4" && name === "renderer_ModelMat" ? "mat3x4" : type; return ""; }); } private static _buildLayout(engine: Engine, fieldMap: Record): InstanceLayout { const maxUBOSize = engine._hardwareRenderer.getMaxUniformBlockSize(); - const std140Map = ShaderFactory._std140Map; + const std140Map = ShaderFactory._std140TypeInfoMap; const instanceFields: InstanceFieldInfo[] = []; let currentOffset = 0; @@ -353,23 +337,36 @@ export class ShaderFactory { return { instanceFields, instanceMaxCount, structSize }; } - /** Build per-field #define lines, using `idExpr` as the instance index (gl_InstanceID or v_instanceID) */ + /** Generate the GLSL UBO struct declaration + layout uniform block */ + private static _buildUBODeclaration(layout: InstanceLayout): string { + const { instanceFields, instanceMaxCount } = layout; + const structLines: string[] = []; + for (let i = 0; i < instanceFields.length; i++) { + const { type, property } = instanceFields[i]; + structLines.push(` ${type} ${property.name};`); + } + return ( + `#define INSTANCE_MAX_COUNT ${instanceMaxCount}\n` + + `struct RendererInstanceStruct {\n${structLines.join("\n")}\n};\n` + + `layout(std140) uniform ${ShaderFactory.RENDERER_INSTANCE_BLOCK_NAME} {\n` + + ` RendererInstanceStruct rendererData[INSTANCE_MAX_COUNT];\n};\n` + ); + } + + /** Build per-field #define lines remapping uniform names to UBO array access */ private static _buildFieldDefines(fields: InstanceFieldInfo[], idExpr: string): string { + const accessor = `rendererData[${idExpr}]`; const lines: string[] = []; for (let i = 0; i < fields.length; i++) { const { type, property } = fields[i]; - const d = `rendererData[${idExpr}]`; - if (type === "mat4_affine") { - const n = property.name; + const n = property.name; + if (type === "mat3x4") { + // Reconstruct mat4 from mat3x4 columns + implicit (0,0,0,1) last column lines.push( - `#define ${n} mat4(` + - `vec4(${d}.${n}R0.x,${d}.${n}R1.x,${d}.${n}R2.x,0.0),` + - `vec4(${d}.${n}R0.y,${d}.${n}R1.y,${d}.${n}R2.y,0.0),` + - `vec4(${d}.${n}R0.z,${d}.${n}R1.z,${d}.${n}R2.z,0.0),` + - `vec4(${d}.${n}R0.w,${d}.${n}R1.w,${d}.${n}R2.w,1.0))` + `#define ${n} mat4(${accessor}.${n}[0],${accessor}.${n}[1],${accessor}.${n}[2],vec4(0.0,0.0,0.0,1.0))` ); } else { - lines.push(`#define ${property.name} ${d}.${property.name}`); + lines.push(`#define ${n} ${accessor}.${n}`); } } return lines.join("\n"); From 1052411e1fddf3e01d9eec070714c0cfc6ed0f0c Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sat, 11 Apr 2026 19:56:57 +0800 Subject: [PATCH 42/91] refactor: simplify UBO injection and skip UBO members in uniform reflection - Remove _insertUBOBlock, prepend UBO block directly to source - Fix mat3x4 pack/reconstruct to use transposed row storage - Skip UBO members (location === null) in _recordLocation to avoid unnecessary ShaderUniform creation and unsupported type errors --- packages/core/src/shader/ShaderProgram.ts | 3 ++ packages/core/src/shaderlib/ShaderFactory.ts | 41 ++++++++------------ 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/packages/core/src/shader/ShaderProgram.ts b/packages/core/src/shader/ShaderProgram.ts index 7c98b718ab..05ce3ea0ee 100644 --- a/packages/core/src/shader/ShaderProgram.ts +++ b/packages/core/src/shader/ShaderProgram.ts @@ -360,6 +360,9 @@ export class ShaderProgram { } const location = gl.getUniformLocation(program, name); + // UBO members have no individual location, skip them + if (location === null) return; + shaderUniform.name = name; shaderUniform.propertyId = ShaderProperty.getByName(name)._uniqueId; shaderUniform.location = location; diff --git a/packages/core/src/shaderlib/ShaderFactory.ts b/packages/core/src/shaderlib/ShaderFactory.ts index 87731a6119..67232d676a 100644 --- a/packages/core/src/shaderlib/ShaderFactory.ts +++ b/packages/core/src/shaderlib/ShaderFactory.ts @@ -103,10 +103,16 @@ export class ShaderFactory { const e = val.elements; for (let k = 0; k < 16; k++) v[o + k] = e[k]; }, - // Affine mat4 stored as mat3x4: write first 3 columns (skip col3 which is 0,0,0,1) + // Affine mat4 stored as mat3x4: write 3 transposed rows (row3 is always 0,0,0,1) mat3x4: (v: Float32Array | Int32Array, o: number, val: Matrix) => { const e = val.elements; - for (let k = 0; k < 12; k++) v[o + k] = e[k]; + // col0=(row0) col1=(row1) col2=(row2), each vec4 = one row of the original mat4 + for (let r = 0; r < 3; r++) { + v[o + r * 4] = e[r]; + v[o + r * 4 + 1] = e[r + 4]; + v[o + r * 4 + 2] = e[r + 8]; + v[o + r * 4 + 3] = e[r + 12]; + } } }; })(); @@ -202,12 +208,12 @@ export class ShaderFactory { const vsBlock = `${uboDecl}flat out int v_instanceID;\n${fieldDefinesVS}\n${derivedDefines}\n`; const fsBlock = `${uboDecl}flat in int v_instanceID;\n${fieldDefinesFS}\n${derivedDefines}\n`; - vertexSource = ShaderFactory._insertUBOBlock(vertexSource, vsBlock); + vertexSource = vsBlock + vertexSource; vertexSource = vertexSource.replace( /void\s+main\s*\(\s*\)\s*\{/, "void main() {\n v_instanceID = gl_InstanceID;" ); - fragmentSource = ShaderFactory._insertUBOBlock(fragmentSource, fsBlock); + fragmentSource = fsBlock + fragmentSource; return { vertexSource, fragmentSource, instanceLayout }; } @@ -361,9 +367,14 @@ export class ShaderFactory { const { type, property } = fields[i]; const n = property.name; if (type === "mat3x4") { - // Reconstruct mat4 from mat3x4 columns + implicit (0,0,0,1) last column + // mat3x4 stores 3 transposed rows; reconstruct column-major mat4 with row3=(0,0,0,1) + const m = `${accessor}.${n}`; lines.push( - `#define ${n} mat4(${accessor}.${n}[0],${accessor}.${n}[1],${accessor}.${n}[2],vec4(0.0,0.0,0.0,1.0))` + `#define ${n} mat4(` + + `vec4(${m}[0].x,${m}[1].x,${m}[2].x,0.0),` + + `vec4(${m}[0].y,${m}[1].y,${m}[2].y,0.0),` + + `vec4(${m}[0].z,${m}[1].z,${m}[2].z,0.0),` + + `vec4(${m}[0].w,${m}[1].w,${m}[2].w,1.0))` ); } else { lines.push(`#define ${n} ${accessor}.${n}`); @@ -372,24 +383,6 @@ export class ShaderFactory { return lines.join("\n"); } - /** - * Insert a UBO block into source after the macro section. - */ - private static _insertUBOBlock(source: string, uboBlock: string): string { - const lines = source.split("\n"); - let insertIdx = 0; - for (let i = 0; i < lines.length; i++) { - const trimmed = lines[i].trimStart(); - if (trimmed.startsWith("#define ")) { - insertIdx = i + 1; - } else if (trimmed.length > 0) { - break; - } - } - lines.splice(insertIdx, 0, uboBlock); - return lines.join("\n"); - } - private static _replaceMRTShader(shader: string, result: string[]): string { let declaration = ""; const mrtIndexSet = new Set(); From 7ba4137ef43efbaff2ccaa7d9d4b517498748fbe Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sat, 11 Apr 2026 20:09:52 +0800 Subject: [PATCH 43/91] refactor: extract InstancePackFunc type alias --- packages/core/src/shaderlib/ShaderFactory.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/core/src/shaderlib/ShaderFactory.ts b/packages/core/src/shaderlib/ShaderFactory.ts index 67232d676a..4ef0b67693 100644 --- a/packages/core/src/shaderlib/ShaderFactory.ts +++ b/packages/core/src/shaderlib/ShaderFactory.ts @@ -70,7 +70,7 @@ export class ShaderFactory { private static readonly _uboUniformRegex = /^[ \t]*uniform\s+(?:(?:lowp|mediump|highp)\s+)?(\w+)\s+(\w+)\s*;/gm; /** Pack functions for writing typed values into ArrayBuffer views */ - private static _packFuncMap: Record = (() => { + private static _packFuncMap: Record = (() => { const packScalar = (v: Float32Array | Int32Array, o: number, val: number) => { v[o] = val; }; @@ -407,12 +407,14 @@ export class ShaderFactory { /** * @internal */ +type InstancePackFunc = (view: Float32Array | Int32Array, offset: number, value: any) => void; + export interface InstanceFieldInfo { property: ShaderProperty; type: string; offset: number; useIntView: boolean; - pack: (view: Float32Array | Int32Array, offset: number, value: any) => void; + pack: InstancePackFunc; } /** From f06aa398dd9d51296a5fdc9d53040755623324aa Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sat, 11 Apr 2026 20:18:22 +0800 Subject: [PATCH 44/91] fix: update test references from _getCanonicalShaderProgram to _compileShaderProgram --- tests/src/shader-lab/PrecompileABTest.test.ts | 8 ++++---- tests/src/shader-lab/PrecompileBenchmark.test.ts | 4 ++-- tests/src/shader-lab/ShaderValidate.ts | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/src/shader-lab/PrecompileABTest.test.ts b/tests/src/shader-lab/PrecompileABTest.test.ts index 95a8e152cd..7d50a8ab39 100644 --- a/tests/src/shader-lab/PrecompileABTest.test.ts +++ b/tests/src/shader-lab/PrecompileABTest.test.ts @@ -204,7 +204,7 @@ describe("Precompile A/B Test: Live vs Precompiled", async () => { ); // @ts-ignore - const program = shaderPass._getCanonicalShaderProgram(engine, macroCollection); + const program = shaderPass._compileShaderProgram(engine, macroCollection); expect(program.isValid, `Pass "${pass.name}" should compile to valid WebGL`).toBe(true); } } @@ -322,7 +322,7 @@ describe("Precompile A/B Test: Live vs Precompiled", async () => { ); // @ts-ignore - const program = shaderPass._getCanonicalShaderProgram(engine, macroCollection); + const program = shaderPass._compileShaderProgram(engine, macroCollection); expect(program.isValid, `.gsp round-trip pass "${pass.name}" should compile`).toBe(true); } } @@ -539,7 +539,7 @@ describe("Precompile A/B Test: Live vs Precompiled", async () => { pass.tags ); // @ts-ignore - const program = shaderPass._getCanonicalShaderProgram(engine, macroCollection); + const program = shaderPass._compileShaderProgram(engine, macroCollection); expect(program.isValid).toBe(true); } } @@ -569,7 +569,7 @@ describe("Precompile A/B Test: Live vs Precompiled", async () => { pass.tags ); // @ts-ignore - const program = shaderPass._getCanonicalShaderProgram(engine, macroCollection); + const program = shaderPass._compileShaderProgram(engine, macroCollection); expect(program.isValid).toBe(true); } } diff --git a/tests/src/shader-lab/PrecompileBenchmark.test.ts b/tests/src/shader-lab/PrecompileBenchmark.test.ts index c3f0cb7422..e914a44afd 100644 --- a/tests/src/shader-lab/PrecompileBenchmark.test.ts +++ b/tests/src/shader-lab/PrecompileBenchmark.test.ts @@ -357,7 +357,7 @@ describe("Precompile Benchmark", async () => { // 6. Variant switch breakdown: CPU / GPU / Total // // CPU measured via _compileShaderLabSource / compilePlatformSource - // Total measured via _getCanonicalShaderProgram (CPU + GPU) + // Total measured via _compileShaderProgram (CPU + GPU) // GPU = Total - CPU // // GSP CPU: buildMacroList + evaluateShaderInstructions + convertTo300 + assemble @@ -425,7 +425,7 @@ describe("Precompile Benchmark", async () => { // Warmup for (let i = 0; i < warmup; i++) { // @ts-ignore - shaderPass._getCanonicalShaderProgram(engine, macroCollection); + shaderPass._compileShaderProgram(engine, macroCollection); } const cpuTimes: number[] = []; diff --git a/tests/src/shader-lab/ShaderValidate.ts b/tests/src/shader-lab/ShaderValidate.ts index 0aeff8d118..fee308568a 100644 --- a/tests/src/shader-lab/ShaderValidate.ts +++ b/tests/src/shader-lab/ShaderValidate.ts @@ -64,7 +64,7 @@ export function glslValidate( }); // @ts-ignore - const shaderProgram = shaderPass._getCanonicalShaderProgram(engine, macroMockCollection); + const shaderProgram = shaderPass._compileShaderProgram(engine, macroMockCollection); expect(shaderProgram.isValid).to.be.true; }); }); From 3c58dd889ef2680dd60896c0827d557365ea6b85 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sat, 11 Apr 2026 21:25:41 +0800 Subject: [PATCH 45/91] fix: resolve shader compile errors and add missing matrix uniform types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove duplicate `uniform mat4 camera_VPMat` from skybox.vs.glsl, shadow-map.vs.glsl, depthOnly.vs.glsl — they already get it via common_vert → transform_declare include chain - Skip UBO members (location === null) in uniform reflection to avoid creating unnecessary ShaderUniform objects - Add support for all standard matrix uniform types: mat2, mat3, mat2x3, mat2x4, mat3x2, mat3x4, mat4x2, mat4x3 - Restore _compilePlatformSource wrapper on ShaderPass to avoid importing ShaderFactory in test files (triggers .glsl parse errors) - Update test references to use _compileShaderProgram --- packages/core/src/shader/ShaderPass.ts | 22 +++++++++---- packages/core/src/shader/ShaderProgram.ts | 24 ++++++++++++++ packages/core/src/shader/ShaderUniform.ts | 32 +++++++++++++++++++ .../src/shaderlib/extra/depthOnly.vs.glsl | 2 -- .../src/shaderlib/extra/shadow-map.vs.glsl | 1 - .../core/src/shaderlib/extra/skybox.vs.glsl | 2 -- .../shader-lab/PrecompileBenchmark.test.ts | 20 +++++++----- 7 files changed, 83 insertions(+), 20 deletions(-) diff --git a/packages/core/src/shader/ShaderPass.ts b/packages/core/src/shader/ShaderPass.ts index b8135fdab3..75b37d509b 100644 --- a/packages/core/src/shader/ShaderPass.ts +++ b/packages/core/src/shader/ShaderPass.ts @@ -176,19 +176,27 @@ export class ShaderPass extends ShaderPart { const { vertexSource, fragmentSource, instanceLayout } = this._platformTarget != undefined ? this._compileShaderLabSource(engine, macroCollection, isGPUInstance) - : ShaderFactory.compilePlatformSource( - engine, - macroCollection, - this._vertexSource, - this._fragmentSource, - isGPUInstance - ); + : this._compilePlatformSource(engine, macroCollection, isGPUInstance); const program = new ShaderProgram(engine, vertexSource, fragmentSource); program._instanceLayout = instanceLayout; return program; } + private _compilePlatformSource( + engine: Engine, + macroCollection: ShaderMacroCollection, + isGPUInstance: boolean + ): { vertexSource: string; fragmentSource: string; instanceLayout: InstanceLayout | null } { + return ShaderFactory.compilePlatformSource( + engine, + macroCollection, + this._vertexSource, + this._fragmentSource, + isGPUInstance + ); + } + private _compileShaderLabSource( engine: Engine, macroCollection: ShaderMacroCollection, diff --git a/packages/core/src/shader/ShaderProgram.ts b/packages/core/src/shader/ShaderProgram.ts index 05ce3ea0ee..09b3f02263 100644 --- a/packages/core/src/shader/ShaderProgram.ts +++ b/packages/core/src/shader/ShaderProgram.ts @@ -436,9 +436,33 @@ export class ShaderProgram { shaderUniform.cacheValue = new Vector4(0, 0, 0); } break; + case gl.FLOAT_MAT2: + shaderUniform.applyFunc = shaderUniform.uploadMat2; + break; + case gl.FLOAT_MAT3: + shaderUniform.applyFunc = shaderUniform.uploadMat3; + break; case gl.FLOAT_MAT4: shaderUniform.applyFunc = isArray ? shaderUniform.uploadMat4v : shaderUniform.uploadMat4; break; + case (gl).FLOAT_MAT2x3: + shaderUniform.applyFunc = shaderUniform.uploadMat2x3; + break; + case (gl).FLOAT_MAT2x4: + shaderUniform.applyFunc = shaderUniform.uploadMat2x4; + break; + case (gl).FLOAT_MAT3x2: + shaderUniform.applyFunc = shaderUniform.uploadMat3x2; + break; + case (gl).FLOAT_MAT3x4: + shaderUniform.applyFunc = shaderUniform.uploadMat3x4; + break; + case (gl).FLOAT_MAT4x2: + shaderUniform.applyFunc = shaderUniform.uploadMat4x2; + break; + case (gl).FLOAT_MAT4x3: + shaderUniform.applyFunc = shaderUniform.uploadMat4x3; + break; case gl.SAMPLER_2D: case gl.SAMPLER_CUBE: case (gl).UNSIGNED_INT_SAMPLER_2D: diff --git a/packages/core/src/shader/ShaderUniform.ts b/packages/core/src/shader/ShaderUniform.ts index 07017a4abc..90d9233c1b 100644 --- a/packages/core/src/shader/ShaderUniform.ts +++ b/packages/core/src/shader/ShaderUniform.ts @@ -237,6 +237,14 @@ export class ShaderUniform { this._gl.uniform4iv(shaderUniform.location, value); } + uploadMat2(shaderUniform: ShaderUniform, value: Float32Array): void { + this._gl.uniformMatrix2fv(shaderUniform.location, false, value); + } + + uploadMat3(shaderUniform: ShaderUniform, value: Float32Array): void { + this._gl.uniformMatrix3fv(shaderUniform.location, false, value); + } + uploadMat4(shaderUniform: ShaderUniform, value: Matrix): void { this._gl.uniformMatrix4fv(shaderUniform.location, false, value.elements); } @@ -245,6 +253,30 @@ export class ShaderUniform { this._gl.uniformMatrix4fv(shaderUniform.location, false, value); } + uploadMat2x3(shaderUniform: ShaderUniform, value: Float32Array): void { + (this._gl).uniformMatrix2x3fv(shaderUniform.location, false, value); + } + + uploadMat2x4(shaderUniform: ShaderUniform, value: Float32Array): void { + (this._gl).uniformMatrix2x4fv(shaderUniform.location, false, value); + } + + uploadMat3x2(shaderUniform: ShaderUniform, value: Float32Array): void { + (this._gl).uniformMatrix3x2fv(shaderUniform.location, false, value); + } + + uploadMat3x4(shaderUniform: ShaderUniform, value: Float32Array): void { + (this._gl).uniformMatrix3x4fv(shaderUniform.location, false, value); + } + + uploadMat4x2(shaderUniform: ShaderUniform, value: Float32Array): void { + (this._gl).uniformMatrix4x2fv(shaderUniform.location, false, value); + } + + uploadMat4x3(shaderUniform: ShaderUniform, value: Float32Array): void { + (this._gl).uniformMatrix4x3fv(shaderUniform.location, false, value); + } + uploadTexture(shaderUniform: ShaderUniform, value: Texture): void { const rhi = this._rhi; rhi.activeTexture(shaderUniform.textureIndex as GLenum); diff --git a/packages/core/src/shaderlib/extra/depthOnly.vs.glsl b/packages/core/src/shaderlib/extra/depthOnly.vs.glsl index 06ea2dc057..ecaea3e6ce 100644 --- a/packages/core/src/shaderlib/extra/depthOnly.vs.glsl +++ b/packages/core/src/shaderlib/extra/depthOnly.vs.glsl @@ -2,8 +2,6 @@ #include #include #include -uniform mat4 camera_VPMat; - void main() { diff --git a/packages/core/src/shaderlib/extra/shadow-map.vs.glsl b/packages/core/src/shaderlib/extra/shadow-map.vs.glsl index 89d84226ed..2da9dc1092 100644 --- a/packages/core/src/shaderlib/extra/shadow-map.vs.glsl +++ b/packages/core/src/shaderlib/extra/shadow-map.vs.glsl @@ -3,7 +3,6 @@ #include #include #include -uniform mat4 camera_VPMat; uniform vec2 scene_ShadowBias; // x: depth bias, y: normal bias uniform vec3 scene_LightDirection; diff --git a/packages/core/src/shaderlib/extra/skybox.vs.glsl b/packages/core/src/shaderlib/extra/skybox.vs.glsl index 16eeb428e0..415145308a 100644 --- a/packages/core/src/shaderlib/extra/skybox.vs.glsl +++ b/packages/core/src/shaderlib/extra/skybox.vs.glsl @@ -1,7 +1,5 @@ #include -uniform mat4 camera_VPMat; - varying vec3 v_cubeUV; uniform float material_Rotation; diff --git a/tests/src/shader-lab/PrecompileBenchmark.test.ts b/tests/src/shader-lab/PrecompileBenchmark.test.ts index e914a44afd..6d2fed972b 100644 --- a/tests/src/shader-lab/PrecompileBenchmark.test.ts +++ b/tests/src/shader-lab/PrecompileBenchmark.test.ts @@ -416,16 +416,14 @@ describe("Precompile Benchmark", async () => { // Split-timing bench: measure CPU and GPU within the same iteration function benchSplit( - shaderPass: ShaderPass, + compileFn: (macros: ShaderMacroCollection) => { vertexSource: string; fragmentSource: string }, macroCollection: ShaderMacroCollection, - compileMethod: string, runs: number, warmup: number ): { cpu: number; gpu: number; total: number; vsLen: number; fsLen: number } { // Warmup for (let i = 0; i < warmup; i++) { - // @ts-ignore - shaderPass._compileShaderProgram(engine, macroCollection); + compileFn(macroCollection); } const cpuTimes: number[] = []; @@ -436,8 +434,7 @@ describe("Precompile Benchmark", async () => { for (let i = 0; i < runs; i++) { // CPU: compile source const t0 = performance.now(); - // @ts-ignore - const { vertexSource, fragmentSource } = shaderPass[compileMethod](engine, macroCollection); + const { vertexSource, fragmentSource } = compileFn(macroCollection); const t1 = performance.now(); vsLen = vertexSource.length; fsLen = fragmentSource.length; @@ -458,9 +455,16 @@ describe("Precompile Benchmark", async () => { return { cpu: cpuAvg, gpu: gpuAvg, total: cpuAvg + gpuAvg, vsLen, fsLen }; } + // @ts-ignore + const gspCompile = (macros: ShaderMacroCollection) => + gspShaderPass._compileShaderLabSource(engine, macros, false); + // @ts-ignore + const glslCompile = (macros: ShaderMacroCollection) => + glslShaderPass._compilePlatformSource(engine, macros, false); + for (const { label, macros } of scenarios) { - const gsp = benchSplit(gspShaderPass, macros, "_compileShaderLabSource", 10, 3); - const glsl = benchSplit(glslShaderPass, macros, "_compilePlatformSource", 10, 3); + const gsp = benchSplit(gspCompile, macros, 10, 3); + const glsl = benchSplit(glslCompile, macros, 10, 3); console.log( `| ${label.padEnd(8)} | ${gsp.cpu.toFixed(3).padStart(11)} | ${glsl.cpu.toFixed(3).padStart(12)} | ${gsp.gpu.toFixed(2).padStart(11)} | ${glsl.gpu.toFixed(2).padStart(12)} | ${gsp.total.toFixed(2).padStart(13)} | ${glsl.total.toFixed(2).padStart(14)} | ${(gsp.vsLen + gsp.fsLen).toString().padStart(9)} | ${(glsl.vsLen + glsl.fsLen).toString().padStart(10)} |` From 74cbb0ec99d98a36963880f72a7e38694635bb5e Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sat, 11 Apr 2026 21:41:53 +0800 Subject: [PATCH 46/91] feat: add GPU instancing e2e tests and complete matrix uniform support - Add e2e cases for gpu-instancing-auto-batch and gpu-instancing-custom-data - Add all missing matrix uniform types (mat2, mat3, mat2x3, mat2x4, mat3x2, mat3x4, mat4x2, mat4x3) to ShaderUniform and ShaderProgram - Skip UBO members (location === null) in uniform reflection - Restore throw for truly unsupported uniform types in default branch --- e2e/case/gpu-instancing-auto-batch.ts | 68 ++++++++++++ e2e/case/gpu-instancing-custom-data.ts | 100 ++++++++++++++++++ e2e/config.ts | 16 ++- ...PUInstancing_gpu-instancing-auto-batch.jpg | 3 + ...UInstancing_gpu-instancing-custom-data.jpg | 3 + 5 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 e2e/case/gpu-instancing-auto-batch.ts create mode 100644 e2e/case/gpu-instancing-custom-data.ts create mode 100644 e2e/fixtures/originImage/GPUInstancing_gpu-instancing-auto-batch.jpg create mode 100644 e2e/fixtures/originImage/GPUInstancing_gpu-instancing-custom-data.jpg diff --git a/e2e/case/gpu-instancing-auto-batch.ts b/e2e/case/gpu-instancing-auto-batch.ts new file mode 100644 index 0000000000..e9f80e35ab --- /dev/null +++ b/e2e/case/gpu-instancing-auto-batch.ts @@ -0,0 +1,68 @@ +/** + * @title GPU Instancing Auto Batch + * @category Mesh + */ +import { + AmbientLight, + AssetType, + Camera, + Color, + DirectLight, + GLTFResource, + Logger, + Vector3, + WebGLEngine +} from "@galacean/engine"; +import { initScreenshot, updateForE2E } from "./.mockForE2E"; + +Logger.enable(); +WebGLEngine.create({ canvas: "canvas" }).then(async (engine) => { + engine.canvas.resizeByClientSize(2); + + const scene = engine.sceneManager.activeScene; + const rootEntity = scene.createRootEntity("Root"); + + // Camera + const cameraEntity = rootEntity.createChild("Camera"); + cameraEntity.transform.setPosition(0, 10, 80); + cameraEntity.transform.lookAt(new Vector3(0, 0, 0)); + const camera = cameraEntity.addComponent(Camera); + camera.farClipPlane = 300; + + // Light + const lightEntity = rootEntity.createChild("Light"); + lightEntity.transform.setRotation(-45, -45, 0); + lightEntity.addComponent(DirectLight).color = new Color(1, 1, 1, 1); + + // Load Duck model and ambient light + const [glTF, ambientLight] = await Promise.all([ + engine.resourceManager.load({ + url: "https://gw.alipayobjects.com/os/bmw-prod/6cb8f543-285c-491a-8cfd-57a1160dc9ab.glb", + type: AssetType.GLTF + }), + engine.resourceManager.load({ + url: "https://mdn.alipayobjects.com/oasis_be/afts/file/A*eRJ8QKzf5zAAAAAAgBAAAAgAekp5AQ/ambient.ambLight", + type: AssetType.AmbientLight + }) + ]); + scene.ambientLight = ambientLight; + + // Clone ducks with fixed seed positions for deterministic output + const count = 500; + const spread = 50; + for (let i = 0; i < count; i++) { + const duck = glTF.instantiateSceneRoot(); + // Use deterministic positions based on index + const t = i / count; + duck.transform.setPosition( + Math.sin(t * 137.5) * spread * 0.5, + Math.cos(t * 97.3) * spread * 0.5, + Math.sin(t * 59.1) * spread * 0.5 + ); + duck.transform.setRotation(t * 360, t * 720, t * 1080); + rootEntity.addChild(duck); + } + + updateForE2E(engine); + initScreenshot(engine, camera); +}); diff --git a/e2e/case/gpu-instancing-custom-data.ts b/e2e/case/gpu-instancing-custom-data.ts new file mode 100644 index 0000000000..f64b01a8f7 --- /dev/null +++ b/e2e/case/gpu-instancing-custom-data.ts @@ -0,0 +1,100 @@ +/** + * @title GPU Instancing Custom Data + * @category Mesh + */ +import { + Camera, + Color, + DirectLight, + Logger, + Material, + MeshRenderer, + PrimitiveMesh, + Shader, + ShaderProperty, + Vector3, + Vector4, + WebGLEngine +} from "@galacean/engine"; +import { initScreenshot, updateForE2E } from "./.mockForE2E"; + +Logger.enable(); + +// Custom shader: uses renderer_CustomColor (per-instance) for fragment output +Shader.create( + "CustomInstanceShader", + ` + #include + attribute vec3 POSITION; + attribute vec3 NORMAL; + + varying vec3 v_normal; + + void main() { + gl_Position = renderer_MVPMat * vec4(POSITION, 1.0); + v_normal = normalize((renderer_NormalMat * vec4(NORMAL, 0.0)).xyz); + } + `, + ` + uniform vec4 renderer_CustomColor; + + varying vec3 v_normal; + + void main() { + vec3 lightDir = normalize(vec3(1.0, 1.0, 1.0)); + float NdotL = max(dot(v_normal, lightDir), 0.2); + gl_FragColor = vec4(renderer_CustomColor.rgb * NdotL, 1.0); + } + ` +); + +WebGLEngine.create({ canvas: "canvas" }).then((engine) => { + engine.canvas.resizeByClientSize(2); + + const scene = engine.sceneManager.activeScene; + const rootEntity = scene.createRootEntity("Root"); + + // Camera + const cameraEntity = rootEntity.createChild("Camera"); + cameraEntity.transform.setPosition(0, 10, 80); + cameraEntity.transform.lookAt(new Vector3(0, 0, 0)); + const camera = cameraEntity.addComponent(Camera); + camera.farClipPlane = 300; + + // Light + const lightEntity = rootEntity.createChild("Light"); + lightEntity.transform.setRotation(-45, -45, 0); + lightEntity.addComponent(DirectLight).color = new Color(1, 1, 1, 1); + + // Shared mesh and material + const mesh = PrimitiveMesh.createCuboid(engine, 1, 1, 1); + const material = new Material(engine, Shader.find("CustomInstanceShader")); + const customColorProperty = ShaderProperty.getByName("renderer_CustomColor"); + + // Create cubes with deterministic positions and colors + const count = 500; + const spread = 50; + for (let i = 0; i < count; i++) { + const entity = rootEntity.createChild("Cube" + i); + const t = i / count; + entity.transform.setPosition( + Math.sin(t * 137.5) * spread * 0.5, + Math.cos(t * 97.3) * spread * 0.5, + Math.sin(t * 59.1) * spread * 0.5 + ); + entity.transform.setRotation(t * 360, t * 720, t * 1080); + + const renderer = entity.addComponent(MeshRenderer); + renderer.mesh = mesh; + renderer.setMaterial(material); + + // Deterministic colors based on index + renderer.shaderData.setVector4( + customColorProperty, + new Vector4(Math.sin(t * 6.28) * 0.5 + 0.5, Math.cos(t * 4.71) * 0.5 + 0.5, Math.sin(t * 3.14) * 0.5 + 0.5, 1.0) + ); + } + + updateForE2E(engine); + initScreenshot(engine, camera); +}); diff --git a/e2e/config.ts b/e2e/config.ts index 05c61ad750..d57bd22097 100644 --- a/e2e/config.ts +++ b/e2e/config.ts @@ -135,7 +135,7 @@ export const E2E_CONFIG = { category: "Material", caseFileName: "material-pbr", threshold: 0, - diffPercentage: 0.0080 + diffPercentage: 0.008 }, shaderLab: { category: "Material", @@ -464,6 +464,20 @@ export const E2E_CONFIG = { diffPercentage: 0.03 } }, + GPUInstancing: { + autoBatch: { + category: "GPUInstancing", + caseFileName: "gpu-instancing-auto-batch", + threshold: 0, + diffPercentage: 0 + }, + customData: { + category: "GPUInstancing", + caseFileName: "gpu-instancing-custom-data", + threshold: 0, + diffPercentage: 0 + } + }, SpriteMask: { CustomStencil: { category: "SpriteMask", diff --git a/e2e/fixtures/originImage/GPUInstancing_gpu-instancing-auto-batch.jpg b/e2e/fixtures/originImage/GPUInstancing_gpu-instancing-auto-batch.jpg new file mode 100644 index 0000000000..82e40384f4 --- /dev/null +++ b/e2e/fixtures/originImage/GPUInstancing_gpu-instancing-auto-batch.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f5424ba406ffe08f68ecfb28174027deb171efff7d55d8ca1be63caaea3bf898 +size 590077 diff --git a/e2e/fixtures/originImage/GPUInstancing_gpu-instancing-custom-data.jpg b/e2e/fixtures/originImage/GPUInstancing_gpu-instancing-custom-data.jpg new file mode 100644 index 0000000000..79399c097c --- /dev/null +++ b/e2e/fixtures/originImage/GPUInstancing_gpu-instancing-custom-data.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:baece2c4437803e1474699eb4e99214b38caa05393f747fe0f7e47bd6469a693 +size 427005 From 6ab1b9c4fc01c1d4aaedede5890d5762384e694d Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sat, 11 Apr 2026 22:16:54 +0800 Subject: [PATCH 47/91] feat: convert GPU instancing examples to animated benchmarks with Stats - 3000 objects with spherical spiral animation (position, rotation, scale) - Custom-data cubes have per-instance color cycling - Add Stats panel and engine-toolkit-stats dependency --- examples/package.json | 1 + examples/src/gpu-instancing-auto-batch.ts | 74 ++++++++++++++++--- examples/src/gpu-instancing-custom-data.ts | 85 ++++++++++++++++++---- pnpm-lock.yaml | 16 +++- 4 files changed, 146 insertions(+), 30 deletions(-) diff --git a/examples/package.json b/examples/package.json index 9fb7a2f83f..52a00fa29a 100644 --- a/examples/package.json +++ b/examples/package.json @@ -22,6 +22,7 @@ "@galacean/engine-shader": "workspace:*", "@galacean/engine-shaderlab": "workspace:*", "@galacean/engine-toolkit": "latest", + "@galacean/engine-toolkit-stats": "latest", "@galacean/engine-ui": "workspace:*" }, "devDependencies": { diff --git a/examples/src/gpu-instancing-auto-batch.ts b/examples/src/gpu-instancing-auto-batch.ts index 2f2d0e64df..3a4da92a37 100644 --- a/examples/src/gpu-instancing-auto-batch.ts +++ b/examples/src/gpu-instancing-auto-batch.ts @@ -3,19 +3,58 @@ * @category Mesh * @thumbnail https://mdn.alipayobjects.com/merchant_appfe/afts/img/A*jjZMTrp-vU8AAAAAAAAAAAAADiR2AQ/original */ -import { OrbitControl } from "@galacean/engine-toolkit"; +import { OrbitControl, Stats } from "@galacean/engine-toolkit"; import { AmbientLight, AssetType, Camera, Color, DirectLight, + Entity, GLTFResource, Logger, + Script, Vector3, WebGLEngine } from "@galacean/engine"; +class SpiralAnimate extends Script { + // Spherical spiral parameters — unique per object + radius: number = 0; + radiusSpeed: number = 0; + theta: number = 0; + thetaSpeed: number = 0; + phi: number = 0; + phiSpeed: number = 0; + rotateSpeed: Vector3 = new Vector3(); + scaleBase: number = 1; + scaleFreq: number = 0; + private _time: number = 0; + + onUpdate(deltaTime: number): void { + this._time += deltaTime; + const t = this._time; + const transform = this.entity.transform; + + // Spiral outward and inward with a breathing motion + const r = this.radius * (0.6 + 0.4 * Math.sin(t * this.radiusSpeed)); + const theta = this.theta + t * this.thetaSpeed; + const phi = this.phi + t * this.phiSpeed; + + // Spherical to cartesian + const sinTheta = Math.sin(theta); + transform.setPosition(r * sinTheta * Math.cos(phi), r * Math.cos(theta), r * sinTheta * Math.sin(phi)); + + // Rotation + const { rotateSpeed } = this; + transform.rotate(rotateSpeed.x * deltaTime, rotateSpeed.y * deltaTime, rotateSpeed.z * deltaTime); + + // Scale pulse + const s = this.scaleBase * (0.7 + 0.3 * Math.sin(t * this.scaleFreq)); + transform.setScale(s, s, s); + } +} + Logger.enable(); WebGLEngine.create({ canvas: "canvas" }).then(async (engine) => { engine.canvas.resizeByClientSize(); @@ -25,12 +64,15 @@ WebGLEngine.create({ canvas: "canvas" }).then(async (engine) => { // Camera const cameraEntity = rootEntity.createChild("Camera"); - cameraEntity.transform.setPosition(0, 10, 80); + cameraEntity.transform.setPosition(0, 0, 100); cameraEntity.transform.lookAt(new Vector3(0, 0, 0)); const camera = cameraEntity.addComponent(Camera); - camera.farClipPlane = 300; + camera.farClipPlane = 500; cameraEntity.addComponent(OrbitControl); + // Stats + cameraEntity.addComponent(Stats); + // Light const lightEntity = rootEntity.createChild("Light"); lightEntity.transform.setRotation(-45, -45, 0); @@ -49,17 +91,25 @@ WebGLEngine.create({ canvas: "canvas" }).then(async (engine) => { ]); scene.ambientLight = ambientLight; - // Clone 1000 ducks with random positions - const count = 1000; - const spread = 50; + const count = 3000; for (let i = 0; i < count; i++) { const duck = glTF.instantiateSceneRoot(); - duck.transform.setPosition( - (Math.random() - 0.5) * spread, - (Math.random() - 0.5) * spread, - (Math.random() - 0.5) * spread - ); - duck.transform.setRotation(Math.random() * 360, Math.random() * 360, Math.random() * 360); + const t = i / count; + + const anim = duck.addComponent(SpiralAnimate); + // Distribute across a sphere with varying radii + anim.radius = 10 + Math.random() * 40; + anim.radiusSpeed = 0.3 + Math.random() * 0.6; + // Start at different points on the sphere + anim.theta = t * Math.PI * 2 * 13.7; // Golden-angle-ish spread + anim.phi = t * Math.PI * 2 * 7.3; + // Different orbit speeds + anim.thetaSpeed = (0.2 + Math.random() * 0.4) * (Math.random() > 0.5 ? 1 : -1); + anim.phiSpeed = (0.3 + Math.random() * 0.5) * (Math.random() > 0.5 ? 1 : -1); + anim.rotateSpeed = new Vector3((Math.random() - 0.5) * 60, (Math.random() - 0.5) * 60, (Math.random() - 0.5) * 60); + anim.scaleBase = 0.6 + Math.random() * 0.8; + anim.scaleFreq = 0.5 + Math.random() * 2; + rootEntity.addChild(duck); } diff --git a/examples/src/gpu-instancing-custom-data.ts b/examples/src/gpu-instancing-custom-data.ts index 8ebba0e68f..228a3bfef4 100644 --- a/examples/src/gpu-instancing-custom-data.ts +++ b/examples/src/gpu-instancing-custom-data.ts @@ -3,7 +3,7 @@ * @category Mesh * @thumbnail https://mdn.alipayobjects.com/merchant_appfe/afts/img/A*jjZMTrp-vU8AAAAAAAAAAAAADiR2AQ/original */ -import { OrbitControl } from "@galacean/engine-toolkit"; +import { OrbitControl, Stats } from "@galacean/engine-toolkit"; import { Camera, Color, @@ -12,6 +12,7 @@ import { Material, MeshRenderer, PrimitiveMesh, + Script, Shader, ShaderProperty, Vector3, @@ -19,9 +20,53 @@ import { WebGLEngine } from "@galacean/engine"; +const _customColorProperty = ShaderProperty.getByName("renderer_CustomColor"); + +class SpiralFlash extends Script { + radius: number = 0; + radiusSpeed: number = 0; + theta: number = 0; + thetaSpeed: number = 0; + phi: number = 0; + phiSpeed: number = 0; + rotateSpeed: Vector3 = new Vector3(); + scaleBase: number = 1; + scaleFreq: number = 0; + colorPhase: number = 0; + colorSpeed: number = 1; + private _time: number = 0; + private _color: Vector4 = new Vector4(); + + onUpdate(deltaTime: number): void { + this._time += deltaTime; + const t = this._time; + const transform = this.entity.transform; + + // Spiral breathing motion + const r = this.radius * (0.6 + 0.4 * Math.sin(t * this.radiusSpeed)); + const theta = this.theta + t * this.thetaSpeed; + const phi = this.phi + t * this.phiSpeed; + + const sinTheta = Math.sin(theta); + transform.setPosition(r * sinTheta * Math.cos(phi), r * Math.cos(theta), r * sinTheta * Math.sin(phi)); + + // Rotation + const { rotateSpeed } = this; + transform.rotate(rotateSpeed.x * deltaTime, rotateSpeed.y * deltaTime, rotateSpeed.z * deltaTime); + + // Scale pulse + const s = this.scaleBase * (0.7 + 0.3 * Math.sin(t * this.scaleFreq)); + transform.setScale(s, s, s); + + // Color cycles through hue based on time + unique phase + const ct = t * this.colorSpeed + this.colorPhase; + this._color.set(0.5 + 0.5 * Math.sin(ct), 0.5 + 0.5 * Math.sin(ct + 2.094), 0.5 + 0.5 * Math.sin(ct + 4.189), 1.0); + this.entity.getComponent(MeshRenderer).shaderData.setVector4(_customColorProperty, this._color); + } +} + Logger.enable(); -// Custom shader: uses renderer_CustomColor (per-instance) for fragment output Shader.create( "CustomInstanceShader", ` @@ -57,40 +102,48 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { // Camera const cameraEntity = rootEntity.createChild("Camera"); - cameraEntity.transform.setPosition(0, 10, 80); + cameraEntity.transform.setPosition(0, 0, 100); cameraEntity.transform.lookAt(new Vector3(0, 0, 0)); const camera = cameraEntity.addComponent(Camera); - camera.farClipPlane = 300; + camera.farClipPlane = 500; cameraEntity.addComponent(OrbitControl); + // Stats + cameraEntity.addComponent(Stats); + // Light const lightEntity = rootEntity.createChild("Light"); lightEntity.transform.setRotation(-45, -45, 0); lightEntity.addComponent(DirectLight).color = new Color(1, 1, 1, 1); - // Shared mesh and material const mesh = PrimitiveMesh.createCuboid(engine, 1, 1, 1); const material = new Material(engine, Shader.find("CustomInstanceShader")); const customColorProperty = ShaderProperty.getByName("renderer_CustomColor"); - // Create 1000 cubes, each with a unique color via renderer shaderData - const count = 1000; - const spread = 50; + const count = 3000; for (let i = 0; i < count; i++) { const entity = rootEntity.createChild("Cube" + i); - entity.transform.setPosition( - (Math.random() - 0.5) * spread, - (Math.random() - 0.5) * spread, - (Math.random() - 0.5) * spread - ); - entity.transform.setRotation(Math.random() * 360, Math.random() * 360, Math.random() * 360); + const ti = i / count; const renderer = entity.addComponent(MeshRenderer); renderer.mesh = mesh; renderer.setMaterial(material); - // Set per-instance custom color on renderer's shaderData (not material's) - renderer.shaderData.setVector4(customColorProperty, new Vector4(Math.random(), Math.random(), Math.random(), 1.0)); + const initColor = new Vector4(Math.random(), Math.random(), Math.random(), 1.0); + renderer.shaderData.setVector4(customColorProperty, initColor); + + const anim = entity.addComponent(SpiralFlash); + anim.radius = 10 + Math.random() * 40; + anim.radiusSpeed = 0.3 + Math.random() * 0.6; + anim.theta = ti * Math.PI * 2 * 13.7; + anim.phi = ti * Math.PI * 2 * 7.3; + anim.thetaSpeed = (0.2 + Math.random() * 0.4) * (Math.random() > 0.5 ? 1 : -1); + anim.phiSpeed = (0.3 + Math.random() * 0.5) * (Math.random() > 0.5 ? 1 : -1); + anim.rotateSpeed = new Vector3((Math.random() - 0.5) * 60, (Math.random() - 0.5) * 60, (Math.random() - 0.5) * 60); + anim.scaleBase = 0.6 + Math.random() * 0.8; + anim.scaleFreq = 0.5 + Math.random() * 2; + anim.colorPhase = Math.random() * Math.PI * 2; + anim.colorSpeed = 0.5 + Math.random() * 2; } engine.run(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ef3b22c6ce..f6dbf1d371 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -188,6 +188,9 @@ importers: '@galacean/engine-toolkit': specifier: latest version: 1.5.3(@galacean/engine-ui@packages+ui)(@galacean/engine@packages+galacean) + '@galacean/engine-toolkit-stats': + specifier: latest + version: 1.6.0(@galacean/engine@packages+galacean) '@galacean/engine-ui': specifier: workspace:* version: link:../packages/ui @@ -945,6 +948,11 @@ packages: peerDependencies: '@galacean/engine': ^1.5.0 + '@galacean/engine-toolkit-stats@1.6.0': + resolution: {integrity: sha512-63LLxTWg15xR000jbtEONnK6lBBMylvl5m+3VqqC7b09YAuMWlm9CuPfaM8dlbctOYT6nmPu9bpQiq3JfdgtWg==} + peerDependencies: + '@galacean/engine': '>=1.6.0-0' + '@galacean/engine-toolkit@1.3.9': resolution: {integrity: sha512-sxE7QfzH61O9Q1wtwnjIEjcg3n0ZZVz9B6CyqBLOWyWgWsZmefcjZLnnH4HIkvc5ZLNA+gMuJ1ekmwJgfkck+g==} @@ -4680,6 +4688,10 @@ snapshots: dependencies: '@galacean/engine': link:packages/galacean + '@galacean/engine-toolkit-stats@1.6.0(@galacean/engine@packages+galacean)': + dependencies: + '@galacean/engine': link:packages/galacean + '@galacean/engine-toolkit@1.3.9(@galacean/engine@packages+galacean)': dependencies: '@galacean/engine-toolkit-auxiliary-lines': 1.3.9(@galacean/engine@packages+galacean) @@ -7716,7 +7728,7 @@ snapshots: vite-node@2.1.5(@types/node@18.19.64)(sass@1.81.0)(terser@5.44.1): dependencies: cac: 6.7.14 - debug: 4.3.7 + debug: 4.4.3 es-module-lexer: 1.5.4 pathe: 1.1.2 vite: 5.4.11(@types/node@18.19.64)(sass@1.81.0)(terser@5.44.1) @@ -7809,7 +7821,7 @@ snapshots: '@vitest/spy': 2.1.5 '@vitest/utils': 2.1.5 chai: 5.1.2 - debug: 4.3.7 + debug: 4.4.3 expect-type: 1.1.0 magic-string: 0.30.12 pathe: 1.1.2 From 86f3e5b50475eeecd0f6f8ba217a1e1ed51071be Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sat, 11 Apr 2026 23:50:25 +0800 Subject: [PATCH 48/91] refactor: sort opaque objects by material/primitive instead of distance Sort opaque render elements by material and primitive ID to maximize GPU instancing batches. Distance sorting is dropped for opaque queue since batch reduction outweighs Early-Z benefits without a depth prepass. --- examples/src/gpu-instancing-auto-batch.ts | 115 +++++++++++++++--- examples/src/gpu-instancing-custom-data.ts | 7 +- .../core/src/RenderPipeline/RenderQueue.ts | 9 +- 3 files changed, 108 insertions(+), 23 deletions(-) diff --git a/examples/src/gpu-instancing-auto-batch.ts b/examples/src/gpu-instancing-auto-batch.ts index 3a4da92a37..bc497feec9 100644 --- a/examples/src/gpu-instancing-auto-batch.ts +++ b/examples/src/gpu-instancing-auto-batch.ts @@ -10,16 +10,23 @@ import { Camera, Color, DirectLight, - Entity, GLTFResource, Logger, + Material, + MeshRenderer, + PrimitiveMesh, Script, + Shader, + ShaderProperty, Vector3, - WebGLEngine + Vector4, + WebGLEngine, + WebGLMode } from "@galacean/engine"; +const _customColorProperty = ShaderProperty.getByName("renderer_CustomColor"); + class SpiralAnimate extends Script { - // Spherical spiral parameters — unique per object radius: number = 0; radiusSpeed: number = 0; theta: number = 0; @@ -29,19 +36,22 @@ class SpiralAnimate extends Script { rotateSpeed: Vector3 = new Vector3(); scaleBase: number = 1; scaleFreq: number = 0; + colorPhase: number = 0; + colorSpeed: number = 0; private _time: number = 0; + private _color: Vector4 = new Vector4(); + private _hasColor: boolean = false; onUpdate(deltaTime: number): void { this._time += deltaTime; const t = this._time; const transform = this.entity.transform; - // Spiral outward and inward with a breathing motion + // Spiral breathing motion const r = this.radius * (0.6 + 0.4 * Math.sin(t * this.radiusSpeed)); const theta = this.theta + t * this.thetaSpeed; const phi = this.phi + t * this.phiSpeed; - // Spherical to cartesian const sinTheta = Math.sin(theta); transform.setPosition(r * sinTheta * Math.cos(phi), r * Math.cos(theta), r * sinTheta * Math.sin(phi)); @@ -52,11 +62,54 @@ class SpiralAnimate extends Script { // Scale pulse const s = this.scaleBase * (0.7 + 0.3 * Math.sin(t * this.scaleFreq)); transform.setScale(s, s, s); + + // Color animation (cubes only) + if (this._hasColor) { + const ct = t * this.colorSpeed + this.colorPhase; + this._color.set( + 0.5 + 0.5 * Math.sin(ct), + 0.5 + 0.5 * Math.sin(ct + 2.094), + 0.5 + 0.5 * Math.sin(ct + 4.189), + 1.0 + ); + this.entity.getComponent(MeshRenderer).shaderData.setVector4(_customColorProperty, this._color); + } + } + + enableColor(): void { + this._hasColor = true; } } -Logger.enable(); -WebGLEngine.create({ canvas: "canvas" }).then(async (engine) => { +// Custom shader for cubes +Shader.create( + "CustomInstanceShader", + ` + #include + attribute vec3 POSITION; + attribute vec3 NORMAL; + + varying vec3 v_normal; + + void main() { + gl_Position = renderer_MVPMat * vec4(POSITION, 1.0); + v_normal = normalize((renderer_NormalMat * vec4(NORMAL, 0.0)).xyz); + } + `, + ` + uniform vec4 renderer_CustomColor; + + varying vec3 v_normal; + + void main() { + vec3 lightDir = normalize(vec3(1.0, 1.0, 1.0)); + float NdotL = max(dot(v_normal, lightDir), 0.2); + gl_FragColor = vec4(renderer_CustomColor.rgb * NdotL, 1.0); + } + ` +); + +WebGLEngine.create({ canvas: "canvas", graphicDeviceOptions: { webGLMode: WebGLMode.WebGL2 } }).then(async (engine) => { engine.canvas.resizeByClientSize(); const scene = engine.sceneManager.activeScene; @@ -91,26 +144,50 @@ WebGLEngine.create({ canvas: "canvas" }).then(async (engine) => { ]); scene.ambientLight = ambientLight; - const count = 3000; - for (let i = 0; i < count; i++) { - const duck = glTF.instantiateSceneRoot(); - const t = i / count; - - const anim = duck.addComponent(SpiralAnimate); - // Distribute across a sphere with varying radii + // Cube resources + const cubeMesh = PrimitiveMesh.createCuboid(engine, 1, 1, 1); + const cubeMaterial = new Material(engine, Shader.find("CustomInstanceShader")); + const customColorProperty = ShaderProperty.getByName("renderer_CustomColor"); + + // Interleave ducks and cubes to break batching — instancing shines here + const duckCount = 1500; + const cubeCount = 1500; + const totalCount = duckCount + cubeCount; + + for (let i = 0; i < totalCount; i++) { + const ti = i / totalCount; + const isDuck = i % 2 === 0 && i / 2 < duckCount; + const isCube = !isDuck; + + let entity; + if (isDuck) { + entity = glTF.instantiateSceneRoot(); + rootEntity.addChild(entity); + } else { + entity = rootEntity.createChild("Cube" + i); + const renderer = entity.addComponent(MeshRenderer); + renderer.mesh = cubeMesh; + renderer.setMaterial(cubeMaterial); + const initColor = new Vector4(Math.random(), Math.random(), Math.random(), 1.0); + renderer.shaderData.setVector4(customColorProperty, initColor); + } + + const anim = entity.addComponent(SpiralAnimate); anim.radius = 10 + Math.random() * 40; anim.radiusSpeed = 0.3 + Math.random() * 0.6; - // Start at different points on the sphere - anim.theta = t * Math.PI * 2 * 13.7; // Golden-angle-ish spread - anim.phi = t * Math.PI * 2 * 7.3; - // Different orbit speeds + anim.theta = ti * Math.PI * 2 * 13.7; + anim.phi = ti * Math.PI * 2 * 7.3; anim.thetaSpeed = (0.2 + Math.random() * 0.4) * (Math.random() > 0.5 ? 1 : -1); anim.phiSpeed = (0.3 + Math.random() * 0.5) * (Math.random() > 0.5 ? 1 : -1); anim.rotateSpeed = new Vector3((Math.random() - 0.5) * 60, (Math.random() - 0.5) * 60, (Math.random() - 0.5) * 60); anim.scaleBase = 0.6 + Math.random() * 0.8; anim.scaleFreq = 0.5 + Math.random() * 2; - rootEntity.addChild(duck); + if (isCube) { + anim.colorPhase = Math.random() * Math.PI * 2; + anim.colorSpeed = 0.5 + Math.random() * 2; + anim.enableColor(); + } } engine.run(); diff --git a/examples/src/gpu-instancing-custom-data.ts b/examples/src/gpu-instancing-custom-data.ts index 228a3bfef4..27857d5730 100644 --- a/examples/src/gpu-instancing-custom-data.ts +++ b/examples/src/gpu-instancing-custom-data.ts @@ -17,7 +17,8 @@ import { ShaderProperty, Vector3, Vector4, - WebGLEngine + WebGLEngine, + WebGLMode } from "@galacean/engine"; const _customColorProperty = ShaderProperty.getByName("renderer_CustomColor"); @@ -94,7 +95,7 @@ Shader.create( ` ); -WebGLEngine.create({ canvas: "canvas" }).then((engine) => { +WebGLEngine.create({ canvas: "canvas", graphicDeviceOptions: { webGLMode: WebGLMode.WebGL2 } }).then((engine) => { engine.canvas.resizeByClientSize(); const scene = engine.sceneManager.activeScene; @@ -120,7 +121,7 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { const material = new Material(engine, Shader.find("CustomInstanceShader")); const customColorProperty = ShaderProperty.getByName("renderer_CustomColor"); - const count = 3000; + const count = 5000; for (let i = 0; i < count; i++) { const entity = rootEntity.createChild("Cube" + i); const ti = i / count; diff --git a/packages/core/src/RenderPipeline/RenderQueue.ts b/packages/core/src/RenderPipeline/RenderQueue.ts index 686459e79e..729910dbcc 100644 --- a/packages/core/src/RenderPipeline/RenderQueue.ts +++ b/packages/core/src/RenderPipeline/RenderQueue.ts @@ -15,8 +15,15 @@ import { RenderQueueMaskType } from "./enums/RenderQueueMaskType"; * @internal */ export class RenderQueue { + // @todo: Sort at SubRenderElement level instead of RenderElement level to avoid multi-submesh objects breaking batches static compareForOpaque(a: RenderElement, b: RenderElement): number { - return a.priority - b.priority || a.distanceForSort - b.distanceForSort; + const sa = a.subRenderElements[0], + sb = b.subRenderElements[0]; + return ( + a.priority - b.priority || + sa.material.instanceId - sb.material.instanceId || + sa.primitive.instanceId - sb.primitive.instanceId + ); } static compareForTransparent(a: RenderElement, b: RenderElement): number { From ec70753ae275e594f8ef35d185c81e510a539ece Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sun, 12 Apr 2026 01:09:57 +0800 Subject: [PATCH 49/91] refactor: unroll mat3x4 pack loop and precompute field offset --- .../core/src/RenderPipeline/InstanceBatch.ts | 2 +- packages/core/src/shaderlib/ShaderFactory.ts | 25 +++++++++++++------ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/packages/core/src/RenderPipeline/InstanceBatch.ts b/packages/core/src/RenderPipeline/InstanceBatch.ts index f72337e934..1291229f2c 100644 --- a/packages/core/src/RenderPipeline/InstanceBatch.ts +++ b/packages/core/src/RenderPipeline/InstanceBatch.ts @@ -65,7 +65,7 @@ export class InstanceBatch { for (let j = 0, n = instanceFields.length; j < n; j++) { const field = instanceFields[j]; - const fieldOffset = baseOffset + field.offset / 4; + const fieldOffset = baseOffset + field.offsetInElements; const propertyId = field.property._uniqueId; if (propertyId === modelMatId) { diff --git a/packages/core/src/shaderlib/ShaderFactory.ts b/packages/core/src/shaderlib/ShaderFactory.ts index 4ef0b67693..3b7ec0aecb 100644 --- a/packages/core/src/shaderlib/ShaderFactory.ts +++ b/packages/core/src/shaderlib/ShaderFactory.ts @@ -106,13 +106,21 @@ export class ShaderFactory { // Affine mat4 stored as mat3x4: write 3 transposed rows (row3 is always 0,0,0,1) mat3x4: (v: Float32Array | Int32Array, o: number, val: Matrix) => { const e = val.elements; - // col0=(row0) col1=(row1) col2=(row2), each vec4 = one row of the original mat4 - for (let r = 0; r < 3; r++) { - v[o + r * 4] = e[r]; - v[o + r * 4 + 1] = e[r + 4]; - v[o + r * 4 + 2] = e[r + 8]; - v[o + r * 4 + 3] = e[r + 12]; - } + // Row 0 + v[o] = e[0]; + v[o + 1] = e[4]; + v[o + 2] = e[8]; + v[o + 3] = e[12]; + // Row 1 + v[o + 4] = e[1]; + v[o + 5] = e[5]; + v[o + 6] = e[9]; + v[o + 7] = e[13]; + // Row 2 + v[o + 8] = e[2]; + v[o + 9] = e[6]; + v[o + 10] = e[10]; + v[o + 11] = e[14]; } }; })(); @@ -313,6 +321,7 @@ export class ShaderFactory { property: ShaderProperty._propertyIdMap[id], type, offset: currentOffset, + offsetInElements: currentOffset / 4, useIntView: type[0] === "i" || type[0] === "u", pack: packFuncMap[type] }); @@ -413,6 +422,8 @@ export interface InstanceFieldInfo { property: ShaderProperty; type: string; offset: number; + /** offset / 4, precomputed to avoid repeated division in upload loop */ + offsetInElements: number; useIntView: boolean; pack: InstancePackFunc; } From 38239edddec3705bd9273bbe4683e40e63ab7064 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sun, 12 Apr 2026 01:28:29 +0800 Subject: [PATCH 50/91] test: update e2e baseline images for opaque sorting change --- e2e/fixtures/originImage/Animator_animator-crossfade.jpg | 4 ++-- e2e/fixtures/originImage/Animator_animator-play.jpg | 4 ++-- e2e/fixtures/originImage/Camera_camera-opaque-texture.jpg | 4 ++-- .../originImage/GPUInstancing_gpu-instancing-auto-batch.jpg | 4 ++-- e2e/fixtures/originImage/Physics_physx-collision.jpg | 4 ++-- e2e/fixtures/originImage/Physics_physx-customUrl.jpg | 4 ++-- e2e/fixtures/originImage/Physics_physx-mesh-collider-data.jpg | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/e2e/fixtures/originImage/Animator_animator-crossfade.jpg b/e2e/fixtures/originImage/Animator_animator-crossfade.jpg index ab638de414..c71cd431c8 100644 --- a/e2e/fixtures/originImage/Animator_animator-crossfade.jpg +++ b/e2e/fixtures/originImage/Animator_animator-crossfade.jpg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e18021c1e0a3b682259046e812eba199c907ffd9a634dda12379f1f0c3b8b43a -size 45710 +oid sha256:a1dfa2435f26222617b08ce2830ca28d8d84e083678dde1a9d945479c7b6d019 +size 45716 diff --git a/e2e/fixtures/originImage/Animator_animator-play.jpg b/e2e/fixtures/originImage/Animator_animator-play.jpg index 79170b806b..ddfb02c4e7 100644 --- a/e2e/fixtures/originImage/Animator_animator-play.jpg +++ b/e2e/fixtures/originImage/Animator_animator-play.jpg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4910985a371b9022e73d2a7efbd71ca6754bcc7bf4c25edc2a41391f1bb1533b -size 46499 +oid sha256:82d8ba4776b6b27b923d0f1f0094d48d0d4c59e61e8666275690fec70c39e854 +size 46527 diff --git a/e2e/fixtures/originImage/Camera_camera-opaque-texture.jpg b/e2e/fixtures/originImage/Camera_camera-opaque-texture.jpg index bcc2d5f331..1d409ae544 100644 --- a/e2e/fixtures/originImage/Camera_camera-opaque-texture.jpg +++ b/e2e/fixtures/originImage/Camera_camera-opaque-texture.jpg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fdde03beb8ead802b61a14e8649102c18ccfe6b9e1e176d3b9f8bc0dfe7f9c76 -size 47500 +oid sha256:a2068be8d0b35c94ec1ddaed9da63494f1a93294ea6128e2036563df1947e854 +size 47482 diff --git a/e2e/fixtures/originImage/GPUInstancing_gpu-instancing-auto-batch.jpg b/e2e/fixtures/originImage/GPUInstancing_gpu-instancing-auto-batch.jpg index 82e40384f4..ecec018d8c 100644 --- a/e2e/fixtures/originImage/GPUInstancing_gpu-instancing-auto-batch.jpg +++ b/e2e/fixtures/originImage/GPUInstancing_gpu-instancing-auto-batch.jpg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f5424ba406ffe08f68ecfb28174027deb171efff7d55d8ca1be63caaea3bf898 -size 590077 +oid sha256:dc2a26d0ad06c2dfa722484b2e914b43c4c6671c76ec31d18715dc4a89ec1d88 +size 590047 diff --git a/e2e/fixtures/originImage/Physics_physx-collision.jpg b/e2e/fixtures/originImage/Physics_physx-collision.jpg index eb5586184e..147cd98364 100644 --- a/e2e/fixtures/originImage/Physics_physx-collision.jpg +++ b/e2e/fixtures/originImage/Physics_physx-collision.jpg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:affc2095f2ab25f0f6e4f1726d9740348359a91de089655ef0fd314445690d45 -size 39157 +oid sha256:18a160d400ffc84bfdcd75b221255a3bbea5522e8a45c868a039424544af8a8b +size 39815 diff --git a/e2e/fixtures/originImage/Physics_physx-customUrl.jpg b/e2e/fixtures/originImage/Physics_physx-customUrl.jpg index eb5586184e..147cd98364 100644 --- a/e2e/fixtures/originImage/Physics_physx-customUrl.jpg +++ b/e2e/fixtures/originImage/Physics_physx-customUrl.jpg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:affc2095f2ab25f0f6e4f1726d9740348359a91de089655ef0fd314445690d45 -size 39157 +oid sha256:18a160d400ffc84bfdcd75b221255a3bbea5522e8a45c868a039424544af8a8b +size 39815 diff --git a/e2e/fixtures/originImage/Physics_physx-mesh-collider-data.jpg b/e2e/fixtures/originImage/Physics_physx-mesh-collider-data.jpg index b179343be2..b18cb45c89 100644 --- a/e2e/fixtures/originImage/Physics_physx-mesh-collider-data.jpg +++ b/e2e/fixtures/originImage/Physics_physx-mesh-collider-data.jpg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a1e13bb7569d857bbf63e2f2fe9ab43d738d12c1fa5689b3d9659912930989df -size 143176 +oid sha256:0fd92c9589df7c7cd9474a0b5b14315bedda98f5984858308756dffe3dad8689 +size 143304 From 1566c69480ccc9e80e9a00e83d83266fb5e40e9a Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sun, 12 Apr 2026 01:38:34 +0800 Subject: [PATCH 51/91] test: adjust e2e diff threshold for gpu-instancing-auto-batch --- e2e/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/config.ts b/e2e/config.ts index d57bd22097..a02a996033 100644 --- a/e2e/config.ts +++ b/e2e/config.ts @@ -469,7 +469,7 @@ export const E2E_CONFIG = { category: "GPUInstancing", caseFileName: "gpu-instancing-auto-batch", threshold: 0, - diffPercentage: 0 + diffPercentage: 0.00126 }, customData: { category: "GPUInstancing", From 0c0373d96cd3f8d1635ea13551882aca63bccaec Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sun, 12 Apr 2026 12:40:25 +0800 Subject: [PATCH 52/91] refactor: update auto-batch example with Avocado model and 5000 instances --- examples/src/gpu-instancing-auto-batch.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/src/gpu-instancing-auto-batch.ts b/examples/src/gpu-instancing-auto-batch.ts index bc497feec9..d7aa1b9f80 100644 --- a/examples/src/gpu-instancing-auto-batch.ts +++ b/examples/src/gpu-instancing-auto-batch.ts @@ -134,7 +134,7 @@ WebGLEngine.create({ canvas: "canvas", graphicDeviceOptions: { webGLMode: WebGLM // Load Duck model and ambient light const [glTF, ambientLight] = await Promise.all([ engine.resourceManager.load({ - url: "https://gw.alipayobjects.com/os/bmw-prod/6cb8f543-285c-491a-8cfd-57a1160dc9ab.glb", + url: "https://mdn.alipayobjects.com/rms/afts/file/A*9R-_TY9K_6oAAAAAgIAAAAgAehQnAQ/Avocado.glb", type: AssetType.GLTF }), engine.resourceManager.load({ @@ -150,8 +150,8 @@ WebGLEngine.create({ canvas: "canvas", graphicDeviceOptions: { webGLMode: WebGLM const customColorProperty = ShaderProperty.getByName("renderer_CustomColor"); // Interleave ducks and cubes to break batching — instancing shines here - const duckCount = 1500; - const cubeCount = 1500; + const duckCount = 2500; + const cubeCount = 2500; const totalCount = duckCount + cubeCount; for (let i = 0; i < totalCount; i++) { @@ -180,7 +180,7 @@ WebGLEngine.create({ canvas: "canvas", graphicDeviceOptions: { webGLMode: WebGLM anim.thetaSpeed = (0.2 + Math.random() * 0.4) * (Math.random() > 0.5 ? 1 : -1); anim.phiSpeed = (0.3 + Math.random() * 0.5) * (Math.random() > 0.5 ? 1 : -1); anim.rotateSpeed = new Vector3((Math.random() - 0.5) * 60, (Math.random() - 0.5) * 60, (Math.random() - 0.5) * 60); - anim.scaleBase = 0.6 + Math.random() * 0.8; + anim.scaleBase = (isDuck ? 20 : 1) * (0.6 + Math.random() * 0.8); anim.scaleFreq = 0.5 + Math.random() * 2; if (isCube) { From 9533e2e37fc04ab6d65ec585bfcfb7b2e536f22e Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sun, 12 Apr 2026 14:28:55 +0800 Subject: [PATCH 53/91] refactor: rename MacroMap to ShaderProgramMap and remove generic abstraction --- packages/core/src/Engine.ts | 8 ++-- packages/core/src/shader/Shader.ts | 2 +- packages/core/src/shader/ShaderPass.ts | 6 +-- .../{MacroMap.ts => ShaderProgramMap.ts} | 41 +++++++++---------- 4 files changed, 28 insertions(+), 29 deletions(-) rename packages/core/src/shader/{MacroMap.ts => ShaderProgramMap.ts} (65%) diff --git a/packages/core/src/Engine.ts b/packages/core/src/Engine.ts index b1171175cd..0ea112b967 100644 --- a/packages/core/src/Engine.ts +++ b/packages/core/src/Engine.ts @@ -33,7 +33,7 @@ import { Shader } from "./shader/Shader"; import { ShaderMacro } from "./shader/ShaderMacro"; import { ShaderMacroCollection } from "./shader/ShaderMacroCollection"; import { ShaderPool } from "./shader/ShaderPool"; -import { MacroMap } from "./shader/MacroMap"; +import { ShaderProgramMap } from "./shader/ShaderProgramMap"; import { ShaderProgram } from "./shader/ShaderProgram"; import { RenderState } from "./shader/state/RenderState"; import { Texture2D, TextureFormat } from "./texture"; @@ -113,7 +113,7 @@ export class Engine extends EventDispatcher { /* @internal */ _renderCount: number = 0; /* @internal */ - _shaderProgramMaps: MacroMap[] = []; + _shaderProgramMaps: ShaderProgramMap[] = []; /** @internal */ _fontMap: Record = {}; /** @internal */ @@ -542,7 +542,7 @@ export class Engine extends EventDispatcher { /** * @internal */ - _getShaderProgramMap(index: number, trackMaps?: MacroMap[]): MacroMap { + _getShaderProgramMap(index: number, trackMaps?: ShaderProgramMap[]): ShaderProgramMap { const shaderProgramMaps = this._shaderProgramMaps; let map = shaderProgramMaps[index]; if (!map) { @@ -550,7 +550,7 @@ export class Engine extends EventDispatcher { if (length > shaderProgramMaps.length) { shaderProgramMaps.length = length; } - shaderProgramMaps[index] = map = new MacroMap(this); + shaderProgramMaps[index] = map = new ShaderProgramMap(this); trackMaps?.push(map); } return map; diff --git a/packages/core/src/shader/Shader.ts b/packages/core/src/shader/Shader.ts index 6237ab923b..f0c826c7b0 100644 --- a/packages/core/src/shader/Shader.ts +++ b/packages/core/src/shader/Shader.ts @@ -241,7 +241,7 @@ export class Shader implements IReferable { for (let k = passShaderProgramMaps.length - 1; k >= 0; k--) { const map = passShaderProgramMaps[k]; if (map.engine !== engine) continue; - map.clear((program) => program.destroy()); + map.destroy(); passShaderProgramMaps.splice(k, 1); } } diff --git a/packages/core/src/shader/ShaderPass.ts b/packages/core/src/shader/ShaderPass.ts index 75b37d509b..4540f0d276 100644 --- a/packages/core/src/shader/ShaderPass.ts +++ b/packages/core/src/shader/ShaderPass.ts @@ -7,7 +7,7 @@ import { ShaderFactory, InstanceLayout } from "../shaderlib/ShaderFactory"; import { ShaderMacro } from "./ShaderMacro"; import { ShaderMacroCollection } from "./ShaderMacroCollection"; import { ShaderPart } from "./ShaderPart"; -import { MacroMap } from "./MacroMap"; +import { ShaderProgramMap } from "./ShaderProgramMap"; import { ShaderProgram } from "./ShaderProgram"; import { ShaderProperty } from "./ShaderProperty"; import { ShaderLanguage } from "./enums/ShaderLanguage"; @@ -54,7 +54,7 @@ export class ShaderPass extends ShaderPart { /** @internal */ _renderStateDataMap: Record = {}; /** @internal */ - _shaderProgramMaps: MacroMap[] = []; + _shaderProgramMaps: ShaderProgramMap[] = []; private _vertexSource?: string; private _fragmentSource?: string; @@ -165,7 +165,7 @@ export class ShaderPass extends ShaderPart { const shaderProgramMaps = this._shaderProgramMaps; for (let i = 0, n = shaderProgramMaps.length; i < n; i++) { const map = shaderProgramMaps[i]; - map.clear((program) => program.destroy()); + map.destroy(); delete map.engine._shaderProgramMaps[this._shaderPassId]; } shaderProgramMaps.length = 0; diff --git a/packages/core/src/shader/MacroMap.ts b/packages/core/src/shader/ShaderProgramMap.ts similarity index 65% rename from packages/core/src/shader/MacroMap.ts rename to packages/core/src/shader/ShaderProgramMap.ts index 96f7849911..6135c8067b 100644 --- a/packages/core/src/shader/MacroMap.ts +++ b/packages/core/src/shader/ShaderProgramMap.ts @@ -1,27 +1,28 @@ import { Engine } from "../Engine"; import { ShaderMacroCollection } from "./ShaderMacroCollection"; +import { ShaderProgram } from "./ShaderProgram"; -type Tree = { - [key: number]: Tree | T; +type Tree = { + [key: number]: Tree | ShaderProgram; }; /** - * Map keyed by ShaderMacroCollection bitmask. + * Map keyed by ShaderMacroCollection bitmask, caching ShaderProgram instances. * @internal */ -export class MacroMap { +export class ShaderProgramMap { engine: Engine; private _cacheHierarchyDepth: number = 1; - private _cacheMap: Tree = Object.create(null); - private _lastQueryMap: Record; + private _cacheMap: Tree = Object.create(null); + private _lastQueryMap: Record; private _lastQueryKey: number; constructor(engine?: Engine) { this.engine = engine; } - get(macros: ShaderMacroCollection): T | null { + get(macros: ShaderMacroCollection): ShaderProgram | null { let cacheMap = this._cacheMap; const maskLength = macros._length; const cacheHierarchyDepth = this._cacheHierarchyDepth; @@ -35,54 +36,52 @@ export class MacroMap { const maxEndIndex = this._cacheHierarchyDepth - 1; for (let i = 0; i < maxEndIndex; i++) { const subMask = endIndex < i ? 0 : mask[i]; - let subCache = >cacheMap[subMask]; + let subCache = cacheMap[subMask]; subCache || (cacheMap[subMask] = subCache = Object.create(null)); cacheMap = subCache; } const cacheKey = endIndex < maxEndIndex ? 0 : mask[maxEndIndex]; - const value = (>cacheMap)[cacheKey]; + const value = (>cacheMap)[cacheKey]; if (!value) { this._lastQueryKey = cacheKey; - this._lastQueryMap = >cacheMap; + this._lastQueryMap = >cacheMap; } return value; } - cache(value: T): void { + cache(value: ShaderProgram): void { this._lastQueryMap[this._lastQueryKey] = value; } - clear(callback?: (value: T) => void): void { - if (callback) { - this._recursiveForEach(0, this._cacheMap, callback); - } + destroy(): void { + this._recursiveForEach(0, this._cacheMap); this._cacheMap = Object.create(null); this._cacheHierarchyDepth = 1; } - private _recursiveForEach(hierarchy: number, cacheMap: Tree, callback: (value: T) => void): void { + private _recursiveForEach(hierarchy: number, cacheMap: Tree): void { if (hierarchy === this._cacheHierarchyDepth - 1) { for (let k in cacheMap) { - callback(cacheMap[k]); + (cacheMap[k]).destroy(); } return; } ++hierarchy; for (let k in cacheMap) { - this._recursiveForEach(hierarchy, >cacheMap[k], callback); + this._recursiveForEach(hierarchy, cacheMap[k]); } } private _resizeCacheMapHierarchy( - cacheMap: Tree, + cacheMap: Tree, hierarchy: number, currentHierarchy: number, increaseHierarchy: number ): void { if (hierarchy == currentHierarchy - 1) { for (let k in cacheMap) { - const value = cacheMap[k]; + const value = cacheMap[k]; let subCacheMap = cacheMap; for (let i = 0; i < increaseHierarchy; i++) { subCacheMap[i == 0 ? k : 0] = subCacheMap = Object.create(null); @@ -92,7 +91,7 @@ export class MacroMap { } else { hierarchy++; for (let k in cacheMap) { - this._resizeCacheMapHierarchy(>cacheMap[k], hierarchy, currentHierarchy, increaseHierarchy); + this._resizeCacheMapHierarchy(cacheMap[k], hierarchy, currentHierarchy, increaseHierarchy); } } } From d58aaa3788d93413bacf8a4fcac137a96064c442 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sun, 12 Apr 2026 18:35:40 +0800 Subject: [PATCH 54/91] feat: add macro collection equality check to _canBatch Prevent incorrect batching when renderers have different shader macros (enableVertexColor, custom macros on renderer.shaderData, etc.) --- packages/core/src/mesh/MeshRenderer.ts | 3 ++- packages/core/src/shader/ShaderMacroCollection.ts | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/core/src/mesh/MeshRenderer.ts b/packages/core/src/mesh/MeshRenderer.ts index d26f470499..5649d95414 100644 --- a/packages/core/src/mesh/MeshRenderer.ts +++ b/packages/core/src/mesh/MeshRenderer.ts @@ -180,7 +180,8 @@ export class MeshRenderer extends Renderer { preSubElement.primitive === subElement.primitive && preSubElement.subPrimitive === subElement.subPrimitive && preSubElement.material === subElement.material && - this._isFrontFaceInvert() === (subElement.component)._isFrontFaceInvert() + this._isFrontFaceInvert() === (subElement.component)._isFrontFaceInvert() && + this.shaderData._macroCollection.isEqual(subElement.component.shaderData._macroCollection) ); } diff --git a/packages/core/src/shader/ShaderMacroCollection.ts b/packages/core/src/shader/ShaderMacroCollection.ts index 749abc22ee..3efb98b77c 100644 --- a/packages/core/src/shader/ShaderMacroCollection.ts +++ b/packages/core/src/shader/ShaderMacroCollection.ts @@ -158,6 +158,20 @@ export class ShaderMacroCollection { return (this._mask[index] & macro._maskValue) !== 0; } + /** + * Whether this macro collection is equal to another. + * @param other - macro collection to compare + */ + isEqual(other: ShaderMacroCollection): boolean { + if (this._length !== other._length) return false; + const mask = this._mask; + const otherMask = other._mask; + for (let i = 0, n = this._length; i < n; i++) { + if (mask[i] !== otherMask[i]) return false; + } + return true; + } + /** * Clear this macro collection. */ From 761bec40645c6e0249fc6aba41f8ebeb4be53362 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sun, 12 Apr 2026 18:52:12 +0800 Subject: [PATCH 55/91] refactor: remove unused renderer_LocalMat and renderer_MVInvMat Neither uniform is used in any engine shader. Unity/Unreal/Godot also don't provide equivalents. Removes per-frame Matrix.invert overhead --- .../material/examples/shaderlab-05-advance.mdx | 1 - docs/en/graphics/material/shaderAPI.mdx | 1 - docs/en/graphics/material/variables.mdx | 1 - .../material/examples/shaderlab-05-advance.mdx | 1 - docs/zh/graphics/material/shaderAPI.mdx | 1 - docs/zh/graphics/material/variables.mdx | 1 - packages/core/src/Renderer.ts | 14 +------------- packages/core/src/shaderlib/ShaderFactory.ts | 4 +--- packages/core/src/shaderlib/transform_declare.glsl | 1 - packages/shader/src/shaders/Transform.glsl | 1 - 10 files changed, 2 insertions(+), 24 deletions(-) diff --git a/docs/en/graphics/material/examples/shaderlab-05-advance.mdx b/docs/en/graphics/material/examples/shaderlab-05-advance.mdx index 4a21ce1c25..4e8c257ea1 100644 --- a/docs/en/graphics/material/examples/shaderlab-05-advance.mdx +++ b/docs/en/graphics/material/examples/shaderlab-05-advance.mdx @@ -190,7 +190,6 @@ gl_FragColor = vec4(lighting, 1.0); The engine provides a rich set of built-in variables that can be used directly, such as transformation matrix variables: ```glsl -mat4 renderer_LocalMat; // Local transformation matrix mat4 renderer_ModelMat; // Model matrix mat4 renderer_MVMat; // Model-view matrix mat4 renderer_MVPMat; // MVP matrix diff --git a/docs/en/graphics/material/shaderAPI.mdx b/docs/en/graphics/material/shaderAPI.mdx index f546447e19..63e646c37e 100644 --- a/docs/en/graphics/material/shaderAPI.mdx +++ b/docs/en/graphics/material/shaderAPI.mdx @@ -27,7 +27,6 @@ vec4 fog(vec4 color, vec3 positionVS); Provides system variables for model space, view space, world space, and camera coordinates: ```glsl -mat4 renderer_LocalMat; mat4 renderer_ModelMat; mat4 camera_ViewMat; mat4 camera_ProjMat; diff --git a/docs/en/graphics/material/variables.mdx b/docs/en/graphics/material/variables.mdx index f64f02b3a3..779f86130b 100644 --- a/docs/en/graphics/material/variables.mdx +++ b/docs/en/graphics/material/variables.mdx @@ -25,7 +25,6 @@ Below are the engine's built-in variables for reference when writing Shaders: | Name | Type | Description | | :---------------- | :--- | :--------------------------- | -| renderer_LocalMat | mat4 | Model local coordinate matrix | | renderer_ModelMat | mat4 | Model world coordinate matrix | | renderer_MVMat | mat4 | Model view matrix | | renderer_MVPMat | mat4 | Model view projection matrix | diff --git a/docs/zh/graphics/material/examples/shaderlab-05-advance.mdx b/docs/zh/graphics/material/examples/shaderlab-05-advance.mdx index c2d4d779dc..a9490581ac 100644 --- a/docs/zh/graphics/material/examples/shaderlab-05-advance.mdx +++ b/docs/zh/graphics/material/examples/shaderlab-05-advance.mdx @@ -193,7 +193,6 @@ void frag() { 引擎提供了丰富的内置变量,直接声明使用即可,比如变换矩阵相关变量: ```glsl -mat4 renderer_LocalMat; // 本地变换矩阵 mat4 renderer_ModelMat; // 模型矩阵 mat4 renderer_MVMat; // 模型视图矩阵 mat4 renderer_MVPMat; // MVP矩阵 diff --git a/docs/zh/graphics/material/shaderAPI.mdx b/docs/zh/graphics/material/shaderAPI.mdx index d6fcd4ac25..0c0879054b 100644 --- a/docs/zh/graphics/material/shaderAPI.mdx +++ b/docs/zh/graphics/material/shaderAPI.mdx @@ -29,7 +29,6 @@ vec4 fog(vec4 color, vec3 positionVS); 提供了模型空间、视图空间、世界空间、相机坐标等[系统变量](/docs/graphics/material/variables/): ```glsl -mat4 renderer_LocalMat; mat4 renderer_ModelMat; mat4 camera_ViewMat; mat4 camera_ProjMat; diff --git a/docs/zh/graphics/material/variables.mdx b/docs/zh/graphics/material/variables.mdx index 6059e0d0e1..48deab814b 100644 --- a/docs/zh/graphics/material/variables.mdx +++ b/docs/zh/graphics/material/variables.mdx @@ -25,7 +25,6 @@ Shader 代码中会经常用到内置变量,一种是**逐顶点**的 `attribu | 名字 | 类型 | 解释 | | :----------------- | :--- | ------------------ | -| renderer_LocalMat | mat4 | 模型本地坐标系矩阵 | | renderer_ModelMat | mat4 | 模型世界坐标系矩阵 | | renderer_MVMat | mat4 | 模型视口矩阵 | | renderer_MVPMat | mat4 | 模型视口投影矩阵 | diff --git a/packages/core/src/Renderer.ts b/packages/core/src/Renderer.ts index e4b880164f..a7e6f4e51e 100644 --- a/packages/core/src/Renderer.ts +++ b/packages/core/src/Renderer.ts @@ -29,10 +29,8 @@ export class Renderer extends Component { static _rendererLayerProperty = ShaderProperty.getByName("renderer_Layer"); private static _receiveShadowMacro = ShaderMacro.getByName("RENDERER_IS_RECEIVE_SHADOWS"); - private static _localMatrixProperty = ShaderProperty.getByName("renderer_LocalMat"); private static _mvMatrixProperty = ShaderProperty.getByName("renderer_MVMat"); private static _mvpMatrixProperty = ShaderProperty.getByName("renderer_MVPMat"); - private static _mvInvMatrixProperty = ShaderProperty.getByName("renderer_MVInvMat"); private static _normalMatrixProperty = ShaderProperty.getByName("renderer_NormalMat"); /** @internal */ @@ -77,8 +75,6 @@ export class Renderer extends Component { @ignoreClone private _mvpMatrix: Matrix = new Matrix(); @ignoreClone - private _mvInvMatrix: Matrix = new Matrix(); - @ignoreClone private _normalMatrix: Matrix = new Matrix(); @ignoreClone private _materialsInstanced: boolean[] = []; @@ -387,7 +383,6 @@ export class Renderer extends Component { this._shaderData = null; this._mvMatrix = null; this._mvpMatrix = null; - this._mvInvMatrix = null; this._normalMatrix = null; this._materialsInstanced = null; this._rendererLayer = null; @@ -426,31 +421,24 @@ export class Renderer extends Component { } protected _updateWorldViewRelatedShaderData(context: RenderContext, worldMatrix: Matrix, batched: boolean): void { - const { shaderData, _mvInvMatrix: mvInvMatrix } = this; + const { shaderData } = this; if (batched) { // @ts-ignore const identityMatrix = Matrix._identity; - Matrix.invert(context.viewMatrix, mvInvMatrix); - - shaderData.setMatrix(Renderer._localMatrixProperty, identityMatrix); shaderData.setMatrix(Renderer._worldMatrixProperty, identityMatrix); shaderData.setMatrix(Renderer._mvMatrixProperty, context.viewMatrix); - shaderData.setMatrix(Renderer._mvInvMatrixProperty, mvInvMatrix); shaderData.setMatrix(Renderer._normalMatrixProperty, identityMatrix); } else { const mvMatrix = this._mvMatrix; const normalMatrix = this._normalMatrix; Matrix.multiply(context.viewMatrix, worldMatrix, mvMatrix); - Matrix.invert(mvMatrix, mvInvMatrix); Matrix.invert(worldMatrix, normalMatrix); normalMatrix.transpose(); - shaderData.setMatrix(Renderer._localMatrixProperty, this._transformEntity.transform.localMatrix); shaderData.setMatrix(Renderer._worldMatrixProperty, worldMatrix); shaderData.setMatrix(Renderer._mvMatrixProperty, mvMatrix); - shaderData.setMatrix(Renderer._mvInvMatrixProperty, mvInvMatrix); shaderData.setMatrix(Renderer._normalMatrixProperty, normalMatrix); } diff --git a/packages/core/src/shaderlib/ShaderFactory.ts b/packages/core/src/shaderlib/ShaderFactory.ts index 3b7ec0aecb..541eac12ce 100644 --- a/packages/core/src/shaderlib/ShaderFactory.ts +++ b/packages/core/src/shaderlib/ShaderFactory.ts @@ -60,11 +60,9 @@ export class ShaderFactory { private static readonly _builtinRendererUniforms = new Map([ ["renderer_ModelMat", false], ["renderer_Layer", false], - ["renderer_LocalMat", true], ["renderer_MVMat", true], ["renderer_MVPMat", true], - ["renderer_NormalMat", true], - ["renderer_MVInvMat", true] + ["renderer_NormalMat", true] ]); private static readonly _uboUniformRegex = /^[ \t]*uniform\s+(?:(?:lowp|mediump|highp)\s+)?(\w+)\s+(\w+)\s*;/gm; diff --git a/packages/core/src/shaderlib/transform_declare.glsl b/packages/core/src/shaderlib/transform_declare.glsl index e098136edc..86048dcbde 100644 --- a/packages/core/src/shaderlib/transform_declare.glsl +++ b/packages/core/src/shaderlib/transform_declare.glsl @@ -2,7 +2,6 @@ uniform mat4 camera_ViewMat; uniform mat4 camera_ProjMat; uniform mat4 camera_VPMat; -uniform mat4 renderer_LocalMat; uniform mat4 renderer_ModelMat; uniform mat4 renderer_MVMat; uniform mat4 renderer_MVPMat; diff --git a/packages/shader/src/shaders/Transform.glsl b/packages/shader/src/shaders/Transform.glsl index e5e3d95589..4002ad931f 100644 --- a/packages/shader/src/shaders/Transform.glsl +++ b/packages/shader/src/shaders/Transform.glsl @@ -8,7 +8,6 @@ vec3 camera_Position; vec3 camera_Forward; vec4 camera_ProjectionParams; -mat4 renderer_LocalMat; mat4 renderer_ModelMat; mat4 renderer_MVMat; mat4 renderer_MVPMat; From 91e2ce295dddba8f295b1e293b3036253d059394 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sun, 12 Apr 2026 19:04:50 +0800 Subject: [PATCH 56/91] refactor: make ShaderProgramMap constructor engine param required --- packages/core/src/shader/ShaderProgramMap.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/shader/ShaderProgramMap.ts b/packages/core/src/shader/ShaderProgramMap.ts index 6135c8067b..5673708863 100644 --- a/packages/core/src/shader/ShaderProgramMap.ts +++ b/packages/core/src/shader/ShaderProgramMap.ts @@ -18,7 +18,7 @@ export class ShaderProgramMap { private _lastQueryMap: Record; private _lastQueryKey: number; - constructor(engine?: Engine) { + constructor(engine: Engine) { this.engine = engine; } From ee3aa6162ef76aa0549085e78b8e3f6e9aceedf8 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sun, 12 Apr 2026 20:43:39 +0800 Subject: [PATCH 57/91] refactor: detect array uniforms in instancing and use Record for builtin map MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Log error when renderer-group array uniforms are found during instancing UBO injection — arrays are not supported (consistent with Unity/Unreal/Godot). Also replace Map with Record for _builtinRendererUniforms. --- packages/core/src/shaderlib/ShaderFactory.ts | 26 ++++++++++++-------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/packages/core/src/shaderlib/ShaderFactory.ts b/packages/core/src/shaderlib/ShaderFactory.ts index 541eac12ce..90b2484a05 100644 --- a/packages/core/src/shaderlib/ShaderFactory.ts +++ b/packages/core/src/shaderlib/ShaderFactory.ts @@ -57,15 +57,16 @@ export class ShaderFactory { "#define renderer_NormalMat mat4(transpose(inverse(mat3(renderer_ModelMat))))"; /** Built-in renderer uniforms. value=true means derived (remove but not added to UBO) */ - private static readonly _builtinRendererUniforms = new Map([ - ["renderer_ModelMat", false], - ["renderer_Layer", false], - ["renderer_MVMat", true], - ["renderer_MVPMat", true], - ["renderer_NormalMat", true] - ]); + private static readonly _builtinRendererUniforms: Record = { + renderer_ModelMat: false, + renderer_Layer: false, + renderer_MVMat: true, + renderer_MVPMat: true, + renderer_NormalMat: true + }; - private static readonly _uboUniformRegex = /^[ \t]*uniform\s+(?:(?:lowp|mediump|highp)\s+)?(\w+)\s+(\w+)\s*;/gm; + private static readonly _uboUniformRegex = + /^[ \t]*uniform\s+(?:(?:lowp|mediump|highp)\s+)?(\w+)\s+(\w+)\s*(\[.+?\])?\s*;/gm; /** Pack functions for writing typed values into ArrayBuffer views */ private static _packFuncMap: Record = (() => { @@ -290,12 +291,17 @@ export class ShaderFactory { */ private static _scanInstanceUniforms(source: string, fieldMap: Record): string { const builtinUniforms = ShaderFactory._builtinRendererUniforms; - return source.replace(ShaderFactory._uboUniformRegex, (match, type, name) => { + return source.replace(ShaderFactory._uboUniformRegex, (match, type, name, arraySize) => { if (type.includes("sampler")) return match; - const isDerived = builtinUniforms.get(name); + const isDerived = builtinUniforms[name]; if (isDerived === undefined && ShaderProperty._getShaderPropertyGroup(name) !== ShaderDataGroup.Renderer) return match; if (isDerived) return ""; + // Array uniforms not supported in instancing UBO, remove to fail shader compilation + if (arraySize) { + Logger.error(`GPU Instancing does not support array uniform "${name}${arraySize}"`); + return ""; + } // ModelMat is affine, store as mat3x4 (3 columns) to save 16 bytes per instance fieldMap[ShaderProperty.getByName(name)._uniqueId] = type === "mat4" && name === "renderer_ModelMat" ? "mat3x4" : type; From 266c346a79ccf1261170f7b3a03192c4f42b1aaf Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Wed, 15 Apr 2026 10:54:37 +0800 Subject: [PATCH 58/91] refactor: reorder types in ShaderFactory, remove unnecessary @internal --- packages/core/src/shaderlib/ShaderFactory.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/core/src/shaderlib/ShaderFactory.ts b/packages/core/src/shaderlib/ShaderFactory.ts index 90b2484a05..593f42e90b 100644 --- a/packages/core/src/shaderlib/ShaderFactory.ts +++ b/packages/core/src/shaderlib/ShaderFactory.ts @@ -417,11 +417,6 @@ export class ShaderFactory { } } -/** - * @internal - */ -type InstancePackFunc = (view: Float32Array | Int32Array, offset: number, value: any) => void; - export interface InstanceFieldInfo { property: ShaderProperty; type: string; @@ -440,3 +435,5 @@ export interface InstanceLayout { instanceMaxCount: number; structSize: number; } + +type InstancePackFunc = (view: Float32Array | Int32Array, offset: number, value: any) => void; From de638a3ea93cb5eacec3c348c0efb7e2683c6cf5 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Wed, 15 Apr 2026 11:10:26 +0800 Subject: [PATCH 59/91] refactor: clean up redundant comments in ShaderFactory --- packages/core/src/shaderlib/ShaderFactory.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/packages/core/src/shaderlib/ShaderFactory.ts b/packages/core/src/shaderlib/ShaderFactory.ts index 593f42e90b..ddf9ed39b6 100644 --- a/packages/core/src/shaderlib/ShaderFactory.ts +++ b/packages/core/src/shaderlib/ShaderFactory.ts @@ -24,7 +24,6 @@ export class ShaderFactory { .map((e) => `#extension ${e} : enable\n`) .join(""); - /** std140 layout info by GLSL type string */ private static readonly _std140TypeInfoMap: Record = { float: { size: 4, align: 4 }, int: { size: 4, align: 4 }, @@ -56,7 +55,7 @@ export class ShaderFactory { "#define renderer_MVPMat (camera_VPMat * renderer_ModelMat)\n" + "#define renderer_NormalMat mat4(transpose(inverse(mat3(renderer_ModelMat))))"; - /** Built-in renderer uniforms. value=true means derived (remove but not added to UBO) */ + // Built-in renderer uniforms. value=true means derived (remove but not added to UBO) private static readonly _builtinRendererUniforms: Record = { renderer_ModelMat: false, renderer_Layer: false, @@ -68,7 +67,6 @@ export class ShaderFactory { private static readonly _uboUniformRegex = /^[ \t]*uniform\s+(?:(?:lowp|mediump|highp)\s+)?(\w+)\s+(\w+)\s*(\[.+?\])?\s*;/gm; - /** Pack functions for writing typed values into ArrayBuffer views */ private static _packFuncMap: Record = (() => { const packScalar = (v: Float32Array | Int32Array, o: number, val: number) => { v[o] = val; @@ -286,9 +284,6 @@ export class ShaderFactory { return shader; } - /** - * Scan source for renderer-group uniforms, collect into fieldMap, and remove matched declarations - */ private static _scanInstanceUniforms(source: string, fieldMap: Record): string { const builtinUniforms = ShaderFactory._builtinRendererUniforms; return source.replace(ShaderFactory._uboUniformRegex, (match, type, name, arraySize) => { @@ -356,7 +351,6 @@ export class ShaderFactory { return { instanceFields, instanceMaxCount, structSize }; } - /** Generate the GLSL UBO struct declaration + layout uniform block */ private static _buildUBODeclaration(layout: InstanceLayout): string { const { instanceFields, instanceMaxCount } = layout; const structLines: string[] = []; @@ -372,7 +366,6 @@ export class ShaderFactory { ); } - /** Build per-field #define lines remapping uniform names to UBO array access */ private static _buildFieldDefines(fields: InstanceFieldInfo[], idExpr: string): string { const accessor = `rendererData[${idExpr}]`; const lines: string[] = []; From db80ad529c9a76b2d07b9e9fc05e10ddd85fc9b6 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Wed, 15 Apr 2026 11:59:41 +0800 Subject: [PATCH 60/91] refactor: use template literal for _derivedDefines in ShaderFactory --- packages/core/src/shaderlib/ShaderFactory.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/core/src/shaderlib/ShaderFactory.ts b/packages/core/src/shaderlib/ShaderFactory.ts index ddf9ed39b6..4d1c746b9e 100644 --- a/packages/core/src/shaderlib/ShaderFactory.ts +++ b/packages/core/src/shaderlib/ShaderFactory.ts @@ -50,10 +50,10 @@ export class ShaderFactory { #endif `; - private static readonly _derivedDefines = - "#define renderer_MVMat (camera_ViewMat * renderer_ModelMat)\n" + - "#define renderer_MVPMat (camera_VPMat * renderer_ModelMat)\n" + - "#define renderer_NormalMat mat4(transpose(inverse(mat3(renderer_ModelMat))))"; + private static readonly _derivedDefines = `\ +#define renderer_MVMat (camera_ViewMat * renderer_ModelMat) +#define renderer_MVPMat (camera_VPMat * renderer_ModelMat) +#define renderer_NormalMat mat4(transpose(inverse(mat3(renderer_ModelMat))))`; // Built-in renderer uniforms. value=true means derived (remove but not added to UBO) private static readonly _builtinRendererUniforms: Record = { From bfa32df1cf04cfe0482eb6c325dbbdb4017eb62e Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Wed, 15 Apr 2026 14:26:33 +0800 Subject: [PATCH 61/91] refactor: cache maxUniformBlockSize and bind UBO at link time --- .../core/src/RenderPipeline/RenderQueue.ts | 1 - packages/core/src/shader/ShaderProgram.ts | 26 +++++++------------ packages/core/src/shaderlib/ShaderFactory.ts | 2 +- .../IHardwareRenderer.ts | 2 ++ packages/rhi-webgl/src/WebGLGraphicDevice.ts | 12 +++++---- 5 files changed, 19 insertions(+), 24 deletions(-) diff --git a/packages/core/src/RenderPipeline/RenderQueue.ts b/packages/core/src/RenderPipeline/RenderQueue.ts index 729910dbcc..386e6ef70f 100644 --- a/packages/core/src/RenderPipeline/RenderQueue.ts +++ b/packages/core/src/RenderPipeline/RenderQueue.ts @@ -225,7 +225,6 @@ export class RenderQueue { const instanceBatch = engine._batcherManager.instanceBatch; instanceBatch.setLayout(layout); - program.bindUniformBlocks(InstanceBatch.uniformBlockBindingMap); rhi.bindUniformBufferBase(ConstantBufferBindingPoint.RendererInstance, instanceBatch.buffer._platformBuffer); for (let start = 0; start < totalCount; start += maxCount) { const count = Math.min(maxCount, totalCount - start); diff --git a/packages/core/src/shader/ShaderProgram.ts b/packages/core/src/shader/ShaderProgram.ts index 09b3f02263..708c6c9440 100644 --- a/packages/core/src/shader/ShaderProgram.ts +++ b/packages/core/src/shader/ShaderProgram.ts @@ -10,6 +10,7 @@ import { ShaderUniformBlock } from "./ShaderUniformBlock"; import { ShaderBlockProperty } from "./ShaderBlockProperty"; import { ShaderDataGroup } from "./enums/ShaderDataGroup"; import { InstanceLayout } from "../shaderlib/ShaderFactory"; +import { InstanceBatch } from "../RenderPipeline/InstanceBatch"; /** * Shader program, corresponding to the GPU shader program. @@ -175,21 +176,6 @@ export class ShaderProgram { } } - /** - * Bind uniform blocks to the specified binding points. - * @param bindingMap - Map of ShaderBlockProperty._uniqueId to binding point - */ - bindUniformBlocks(bindingMap: Record): void { - const gl = this._gl; - const ids = this.uniformBlockIds; - for (let i = 0, n = ids.length; i < n; i++) { - const bindingPoint = bindingMap[ids[i]]; - if (bindingPoint !== undefined) { - gl.uniformBlockBinding(this._glProgram, i, bindingPoint); - } - } - } - /** * Destroy this shader program. */ @@ -525,12 +511,18 @@ export class ShaderProgram { this.attributeLocation[name] = gl.getAttribLocation(program, name); }); - // Record uniform block indices (WebGL2 only) + // Record uniform block indices and bind binding points (WebGL2 only) if (this._engine._hardwareRenderer.isWebGL2) { const gl2 = gl; + const bindingMap = InstanceBatch.uniformBlockBindingMap; const blockCount = gl2.getProgramParameter(program, gl2.ACTIVE_UNIFORM_BLOCKS) ?? 0; for (let i = 0; i < blockCount; i++) { - this.uniformBlockIds[i] = ShaderBlockProperty.getByName(gl2.getActiveUniformBlockName(program, i))._uniqueId; + const id = ShaderBlockProperty.getByName(gl2.getActiveUniformBlockName(program, i))._uniqueId; + this.uniformBlockIds[i] = id; + const bindingPoint = bindingMap[id]; + if (bindingPoint !== undefined) { + gl2.uniformBlockBinding(program, i, bindingPoint); + } } } } diff --git a/packages/core/src/shaderlib/ShaderFactory.ts b/packages/core/src/shaderlib/ShaderFactory.ts index 4d1c746b9e..4d63eba514 100644 --- a/packages/core/src/shaderlib/ShaderFactory.ts +++ b/packages/core/src/shaderlib/ShaderFactory.ts @@ -305,7 +305,7 @@ export class ShaderFactory { } private static _buildLayout(engine: Engine, fieldMap: Record): InstanceLayout { - const maxUBOSize = engine._hardwareRenderer.getMaxUniformBlockSize(); + const maxUBOSize = engine._hardwareRenderer.maxUniformBlockSize; const std140Map = ShaderFactory._std140TypeInfoMap; const instanceFields: InstanceFieldInfo[] = []; let currentOffset = 0; diff --git a/packages/design/src/renderingHardwareInterface/IHardwareRenderer.ts b/packages/design/src/renderingHardwareInterface/IHardwareRenderer.ts index bccb92bf65..193ef7a558 100644 --- a/packages/design/src/renderingHardwareInterface/IHardwareRenderer.ts +++ b/packages/design/src/renderingHardwareInterface/IHardwareRenderer.ts @@ -2,6 +2,8 @@ * Hardware graphics API renderer. */ export interface IHardwareRenderer { + readonly maxUniformBlockSize: number; + // todo: implements [key: string]: any; } diff --git a/packages/rhi-webgl/src/WebGLGraphicDevice.ts b/packages/rhi-webgl/src/WebGLGraphicDevice.ts index ec3e7ce1a6..7394b0409a 100644 --- a/packages/rhi-webgl/src/WebGLGraphicDevice.ts +++ b/packages/rhi-webgl/src/WebGLGraphicDevice.ts @@ -89,6 +89,8 @@ export interface WebGLGraphicDeviceOptions { * WebGL graphic device, including WebGL1.0 and WebGL2.0. */ export class WebGLGraphicDevice implements IHardwareRenderer { + maxUniformBlockSize: number; + /** @internal */ _readFrameBuffer: WebGLFramebuffer = null; /** @internal */ @@ -294,11 +296,6 @@ export class WebGLGraphicDevice implements IHardwareRenderer { return blockIndex; } - getMaxUniformBlockSize(): number { - const gl = this._gl; - return gl.getParameter(gl.MAX_UNIFORM_BLOCK_SIZE); - } - /** * Enable GL_RASTERIZER_DISCARD (WebGL2 only). */ @@ -642,6 +639,11 @@ export class WebGLGraphicDevice implements IHardwareRenderer { if (debugRenderInfo != null) { this._renderer = gl.getParameter(debugRenderInfo.UNMASKED_RENDERER_WEBGL); } + if (this._isWebGL2) { + this.maxUniformBlockSize = (gl).getParameter( + (gl).MAX_UNIFORM_BLOCK_SIZE + ); + } } destroy(): void { From 2c46325aad538ab1eda537bf8a9ec7a6d8db8e4a Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Wed, 15 Apr 2026 15:00:48 +0800 Subject: [PATCH 62/91] fix: avoid circular dependency in ShaderProgram UBO binding --- packages/core/src/shader/ShaderProgram.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/core/src/shader/ShaderProgram.ts b/packages/core/src/shader/ShaderProgram.ts index 708c6c9440..6e2d29a882 100644 --- a/packages/core/src/shader/ShaderProgram.ts +++ b/packages/core/src/shader/ShaderProgram.ts @@ -9,8 +9,8 @@ import { ShaderUniform } from "./ShaderUniform"; import { ShaderUniformBlock } from "./ShaderUniformBlock"; import { ShaderBlockProperty } from "./ShaderBlockProperty"; import { ShaderDataGroup } from "./enums/ShaderDataGroup"; -import { InstanceLayout } from "../shaderlib/ShaderFactory"; -import { InstanceBatch } from "../RenderPipeline/InstanceBatch"; +import { InstanceLayout, ShaderFactory } from "../shaderlib/ShaderFactory"; +import { ConstantBufferBindingPoint } from "./enums/ConstantBufferBindingPoint"; /** * Shader program, corresponding to the GPU shader program. @@ -514,14 +514,13 @@ export class ShaderProgram { // Record uniform block indices and bind binding points (WebGL2 only) if (this._engine._hardwareRenderer.isWebGL2) { const gl2 = gl; - const bindingMap = InstanceBatch.uniformBlockBindingMap; + const instanceBlockId = ShaderBlockProperty.getByName(ShaderFactory.RENDERER_INSTANCE_BLOCK_NAME)._uniqueId; const blockCount = gl2.getProgramParameter(program, gl2.ACTIVE_UNIFORM_BLOCKS) ?? 0; for (let i = 0; i < blockCount; i++) { const id = ShaderBlockProperty.getByName(gl2.getActiveUniformBlockName(program, i))._uniqueId; this.uniformBlockIds[i] = id; - const bindingPoint = bindingMap[id]; - if (bindingPoint !== undefined) { - gl2.uniformBlockBinding(program, i, bindingPoint); + if (id === instanceBlockId) { + gl2.uniformBlockBinding(program, i, ConstantBufferBindingPoint.RendererInstance); } } } From 875ff92be938869ee9976fe383ecca4a5bcf56cc Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Wed, 15 Apr 2026 16:18:32 +0800 Subject: [PATCH 63/91] refactor: move uniformBlockBindingMap from InstanceBatch to ShaderFactory --- packages/core/src/RenderPipeline/InstanceBatch.ts | 9 +-------- packages/core/src/shader/ShaderProgram.ts | 8 ++++---- packages/core/src/shaderlib/ShaderFactory.ts | 7 +++++++ 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/core/src/RenderPipeline/InstanceBatch.ts b/packages/core/src/RenderPipeline/InstanceBatch.ts index 1291229f2c..f03c2ac329 100644 --- a/packages/core/src/RenderPipeline/InstanceBatch.ts +++ b/packages/core/src/RenderPipeline/InstanceBatch.ts @@ -4,10 +4,8 @@ import { BufferBindFlag } from "../graphic/enums/BufferBindFlag"; import { BufferUsage } from "../graphic/enums/BufferUsage"; import { SetDataOptions } from "../graphic/enums/SetDataOptions"; import { Renderer } from "../Renderer"; -import { ShaderBlockProperty } from "../shader/ShaderBlockProperty"; import { ShaderMacro } from "../shader/ShaderMacro"; -import { ConstantBufferBindingPoint } from "../shader/enums/ConstantBufferBindingPoint"; -import { InstanceLayout, ShaderFactory } from "../shaderlib/ShaderFactory"; +import { InstanceLayout } from "../shaderlib/ShaderFactory"; /** * @internal @@ -16,11 +14,6 @@ import { InstanceLayout, ShaderFactory } from "../shaderlib/ShaderFactory"; export class InstanceBatch { static gpuInstanceMacro = ShaderMacro.getByName("RENDERER_GPU_INSTANCE"); - static readonly uniformBlockBindingMap: Record = { - [ShaderBlockProperty.getByName(ShaderFactory.RENDERER_INSTANCE_BLOCK_NAME)._uniqueId]: - ConstantBufferBindingPoint.RendererInstance - }; - buffer: Buffer; private _engine: Engine; diff --git a/packages/core/src/shader/ShaderProgram.ts b/packages/core/src/shader/ShaderProgram.ts index 6e2d29a882..80a9342340 100644 --- a/packages/core/src/shader/ShaderProgram.ts +++ b/packages/core/src/shader/ShaderProgram.ts @@ -10,7 +10,6 @@ import { ShaderUniformBlock } from "./ShaderUniformBlock"; import { ShaderBlockProperty } from "./ShaderBlockProperty"; import { ShaderDataGroup } from "./enums/ShaderDataGroup"; import { InstanceLayout, ShaderFactory } from "../shaderlib/ShaderFactory"; -import { ConstantBufferBindingPoint } from "./enums/ConstantBufferBindingPoint"; /** * Shader program, corresponding to the GPU shader program. @@ -514,13 +513,14 @@ export class ShaderProgram { // Record uniform block indices and bind binding points (WebGL2 only) if (this._engine._hardwareRenderer.isWebGL2) { const gl2 = gl; - const instanceBlockId = ShaderBlockProperty.getByName(ShaderFactory.RENDERER_INSTANCE_BLOCK_NAME)._uniqueId; + const bindingMap = ShaderFactory.uniformBlockBindingMap; const blockCount = gl2.getProgramParameter(program, gl2.ACTIVE_UNIFORM_BLOCKS) ?? 0; for (let i = 0; i < blockCount; i++) { const id = ShaderBlockProperty.getByName(gl2.getActiveUniformBlockName(program, i))._uniqueId; this.uniformBlockIds[i] = id; - if (id === instanceBlockId) { - gl2.uniformBlockBinding(program, i, ConstantBufferBindingPoint.RendererInstance); + const bindingPoint = bindingMap[id]; + if (bindingPoint !== undefined) { + gl2.uniformBlockBinding(program, i, bindingPoint); } } } diff --git a/packages/core/src/shaderlib/ShaderFactory.ts b/packages/core/src/shaderlib/ShaderFactory.ts index 4d63eba514..f64467bc3e 100644 --- a/packages/core/src/shaderlib/ShaderFactory.ts +++ b/packages/core/src/shaderlib/ShaderFactory.ts @@ -7,6 +7,8 @@ import { ShaderDataGroup } from "../shader/enums/ShaderDataGroup"; import { ShaderMacro } from "../shader/ShaderMacro"; import { ShaderMacroCollection } from "../shader/ShaderMacroCollection"; import { ShaderProperty } from "../shader/ShaderProperty"; +import { ShaderBlockProperty } from "../shader/ShaderBlockProperty"; +import { ConstantBufferBindingPoint } from "../shader/enums/ConstantBufferBindingPoint"; import { ShaderLib } from "./ShaderLib"; /** @@ -15,6 +17,11 @@ import { ShaderLib } from "./ShaderLib"; export class ShaderFactory { static readonly RENDERER_INSTANCE_BLOCK_NAME = "RendererInstanceData"; + static readonly uniformBlockBindingMap: Record = { + [ShaderBlockProperty.getByName(ShaderFactory.RENDERER_INSTANCE_BLOCK_NAME)._uniqueId]: + ConstantBufferBindingPoint.RendererInstance + }; + static readonly shaderExtension = [ "GL_EXT_shader_texture_lod", "GL_OES_standard_derivatives", From 454727b94d060a95aeecdb1e816e1cb2433f7eb1 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Wed, 15 Apr 2026 16:45:09 +0800 Subject: [PATCH 64/91] fix: add missing camera_VPMat declaration in Transform.glsl --- packages/shader/src/shaders/Transform.glsl | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/shader/src/shaders/Transform.glsl b/packages/shader/src/shaders/Transform.glsl index 4002ad931f..18ce2385b0 100644 --- a/packages/shader/src/shaders/Transform.glsl +++ b/packages/shader/src/shaders/Transform.glsl @@ -3,6 +3,7 @@ mat4 camera_ViewMat; mat4 camera_ProjMat; +mat4 camera_VPMat; vec3 camera_Position; vec3 camera_Forward; From 4b49ec80bc07b80f2f040f10e45f7ffcff0688bf Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Wed, 15 Apr 2026 17:31:52 +0800 Subject: [PATCH 65/91] test: remove PrecompileBenchmark that conflicts with deep source imports --- .../shader-lab/PrecompileBenchmark.test.ts | 477 ------------------ 1 file changed, 477 deletions(-) delete mode 100644 tests/src/shader-lab/PrecompileBenchmark.test.ts diff --git a/tests/src/shader-lab/PrecompileBenchmark.test.ts b/tests/src/shader-lab/PrecompileBenchmark.test.ts deleted file mode 100644 index 6d2fed972b..0000000000 --- a/tests/src/shader-lab/PrecompileBenchmark.test.ts +++ /dev/null @@ -1,477 +0,0 @@ -/** - * Precompile Benchmark — performance comparison between old and new paths - */ - -import { Shader, ShaderLanguage, ShaderMacro, ShaderMacroCollection, ShaderPass } from "@galacean/engine-core"; -import { ShaderProgram } from "@galacean/engine-core/src/shader/ShaderProgram"; -import type { ShaderInstruction } from "@galacean/engine-design"; -import { registerIncludes, PBRSource } from "@galacean/engine-shader"; -import { ShaderLab } from "@galacean/engine-shaderlab"; -import { ShaderInstructionEncoder } from "@galacean/engine-shaderlab/src/ShaderInstructionEncoder"; -import { ShaderMacroProcessor } from "@galacean/engine-core/src/shader/ShaderMacroProcessor"; - -import { Logger, WebGLEngine } from "@galacean/engine"; -import { server } from "@vitest/browser/context"; -import { describe, expect, it } from "vitest"; - -const { readFile } = server.commands; -Logger.enable(); -registerIncludes(); - -const shaderLab = new ShaderLab(); - -// ─── Bench utility ───────────────────────────────────────────────────── - -interface BenchResult { - label: string; - avg: number; - min: number; - max: number; - median: number; -} - -function bench(label: string, fn: () => void, runs = 10, warmup = 3): BenchResult { - for (let i = 0; i < warmup; i++) fn(); - - const times: number[] = []; - for (let i = 0; i < runs; i++) { - const t0 = performance.now(); - fn(); - times.push(performance.now() - t0); - } - - times.sort((a, b) => a - b); - const avg = times.reduce((s, t) => s + t, 0) / times.length; - const median = times[Math.floor(times.length / 2)]; - return { label, avg, min: times[0], max: times[times.length - 1], median }; -} - -function logTable(title: string, results: BenchResult[]) { - console.log(`\n=== ${title} ===`); - console.log("| Shader | Avg (ms) | Min (ms) | Max (ms) | Median (ms) |"); - console.log("|--------|----------|----------|----------|-------------|"); - for (const r of results) { - console.log( - `| ${r.label.padEnd(20)} | ${r.avg.toFixed(2).padStart(8)} | ${r.min.toFixed(2).padStart(8)} | ${r.max.toFixed(2).padStart(8)} | ${r.median.toFixed(2).padStart(11)} |` - ); - } -} - -function logComparison(title: string, rows: Array<{ label: string; live: number; precompiled: number }>) { - console.log(`\n=== ${title} ===`); - console.log("| Item | Live (ms) | Precompiled (ms) | Speedup |"); - console.log("|------|-----------|------------------|---------|"); - for (const r of rows) { - const speedup = r.live > 0 ? (r.live / r.precompiled).toFixed(1) + "x" : "N/A"; - console.log( - `| ${r.label.padEnd(20)} | ${r.live.toFixed(2).padStart(9)} | ${r.precompiled.toFixed(2).padStart(16)} | ${speedup.padStart(7)} |` - ); - } -} - -// ─── Macro sets ──────────────────────────────────────────────────────── - -const baseMacros: { name: string; value?: string }[] = [ - { name: "RENDERER_IS_RECEIVE_SHADOWS" }, - { name: "RENDERER_HAS_NORMAL" }, - { name: "SCENE_USE_SH" }, - { name: "SCENE_USE_SPECULAR_ENV" }, - { name: "SCENE_FOG_MODE", value: "0" }, - { name: "SCENE_SHADOW_CASCADED_COUNT", value: "1" }, - { name: "MATERIAL_NEED_WORLD_POS" }, - { name: "MATERIAL_NEED_TILING_OFFSET" }, - { name: "REFRACTION_MODE", value: "1" }, - { name: "SCENE_DIRECT_LIGHT_COUNT", value: "1" }, - { name: "SCENE_SHADOW_TYPE", value: "2" } -]; - -const materialVariantMacros: { name: string; value?: string }[] = [ - { name: "MATERIAL_HAS_ROUGHNESS_METALLIC_TEXTURE" }, - { name: "MATERIAL_ENABLE_IRIDESCENCE" }, - { name: "MATERIAL_ENABLE_ANISOTROPY" }, - { name: "MATERIAL_ENABLE_SHEEN" }, - { name: "MATERIAL_HAS_SHEEN_TEXTURE" }, - { name: "MATERIAL_ENABLE_TRANSMISSION" }, - { name: "MATERIAL_HAS_THICKNESS" } -]; - -function buildMacroCollection(macros: { name: string; value?: string }[]): ShaderMacroCollection { - const collection = new ShaderMacroCollection(); - for (const { name, value } of macros) { - collection.enable(ShaderMacro.getByName(name, value)); - } - return collection; -} - -function makeMacroMap(macros: { name: string; value?: string }[]): Map { - return new Map(macros.map(({ name, value }) => [name, value ?? ""])); -} - -let nameCounter = 0; -function uid(base: string) { - return `__bench_${base}_${nameCounter++}`; -} - -// ─── Tests ───────────────────────────────────────────────────────────── - -describe("Precompile Benchmark", async () => { - const canvas = document.createElement("canvas"); - const engine = await WebGLEngine.create({ canvas }); - // @ts-ignore - Shader._shaderLab = shaderLab; - // @ts-ignore - const basePath = new URL("", ShaderPass._shaderRootPath).href; - - // Load all test shaders upfront - const shaderFiles = [ - { label: "PBR (complex)", source: PBRSource, file: null }, - { label: "waterfull (medium)", source: null as string | null, file: "waterfull.shader" }, - { label: "multi-pass", source: null as string | null, file: "multi-pass.shader" }, - { label: "macro-pre", source: null as string | null, file: "macro-pre.shader" }, - { label: "noFragArgs (simple)", source: null as string | null, file: "noFragArgs.shader" }, - { label: "mrt-struct", source: null as string | null, file: "mrt-struct.shader" } - ]; - - for (const entry of shaderFiles) { - if (!entry.source && entry.file) { - entry.source = await readFile(`./shaders/${entry.file}`); - } - } - - // ═══════════════════════════════════════════════════════════ - // 1. Full _precompile() pipeline - // ═══════════════════════════════════════════════════════════ - describe("1. Full _precompile() pipeline", () => { - it("benchmark each shader", () => { - Logger.disable(); - const results: BenchResult[] = []; - for (const { label, source } of shaderFiles) { - results.push( - bench( - label, - () => { - shaderLab._precompile(source!, ShaderLanguage.GLSLES100, basePath); - }, - 10, - 2 - ) - ); - } - Logger.enable(); - logTable("Full _precompile() Pipeline", results); - }); - }); - - // ═══════════════════════════════════════════════════════════ - // 2. Per-stage: parseShaderInstructions - // ═══════════════════════════════════════════════════════════ - describe("2. Per-stage: parseShaderInstructions", () => { - it("parseShaderInstructions timing for PBR vertex/fragment", () => { - const precompiled = shaderLab._precompile(PBRSource, ShaderLanguage.GLSLES100, basePath); - const results: BenchResult[] = []; - - for (const sub of precompiled.subShaders) { - for (const pass of sub.passes) { - if (pass.isUsePass || !pass.vertexShaderInstructions) continue; - // Get raw source for re-parsing timing - const rawVertex = ShaderMacroProcessor.evaluate(pass.vertexShaderInstructions, new Map()); - const rawFragment = pass.fragmentShaderInstructions - ? ShaderMacroProcessor.evaluate(pass.fragmentShaderInstructions, new Map()) - : ""; - if (pass.vertexShaderInstructions.length > 1) { - results.push( - bench( - `${pass.name} vertex`, - () => { - ShaderInstructionEncoder.parse(rawVertex); - }, - 20, - 5 - ) - ); - } - if (pass.fragmentShaderInstructions && pass.fragmentShaderInstructions.length > 1) { - results.push( - bench( - `${pass.name} fragment`, - () => { - ShaderInstructionEncoder.parse(rawFragment); - }, - 20, - 5 - ) - ); - } - } - } - - logTable("parseShaderInstructions (build-time cost)", results); - }); - }); - - // ═══════════════════════════════════════════════════════════ - // 3. JSON Serialize / Parse - // ═══════════════════════════════════════════════════════════ - describe("3. JSON Serialize / Parse", () => { - it("stringify + parse timing for each shader", () => { - const results: Array<{ label: string; size: number; stringify: BenchResult; parse: BenchResult }> = []; - - for (const { label, source } of shaderFiles) { - const precompiled = shaderLab._precompile(source!, ShaderLanguage.GLSLES100, basePath); - const strResult = bench( - `${label} stringify`, - () => { - JSON.stringify(precompiled); - }, - 20, - 5 - ); - const jsonStr = JSON.stringify(precompiled); - const parseResult = bench( - `${label} parse`, - () => { - JSON.parse(jsonStr); - }, - 20, - 5 - ); - results.push({ label, size: jsonStr.length, stringify: strResult, parse: parseResult }); - } - - console.log("\n=== JSON Serialize / Parse ==="); - console.log("| Shader | .gsp Size | Stringify (ms) | Parse (ms) |"); - console.log("|--------|-----------|----------------|------------|"); - for (const r of results) { - const sizeKB = (r.size / 1024).toFixed(1) + "KB"; - console.log( - `| ${r.label.padEnd(20)} | ${sizeKB.padStart(9)} | ${r.stringify.avg.toFixed(3).padStart(14)} | ${r.parse.avg.toFixed(3).padStart(10)} |` - ); - } - }); - }); - - // ═══════════════════════════════════════════════════════════ - // 4. Shader reconstruction - // ═══════════════════════════════════════════════════════════ - describe("4. Shader reconstruction", () => { - it("_createFromPrecompiled vs Shader.create (PBR)", () => { - const precompiled = shaderLab._precompile(PBRSource, ShaderLanguage.GLSLES100, basePath); - const jsonStr = JSON.stringify(precompiled); - - Logger.disable(); - - const liveResult = bench( - "Shader.create (live)", - () => { - const name = uid("PBR_live"); - Shader.create(PBRSource); - Shader.find(name)?.destroy(true); - }, - 5, - 1 - ); - - const preResult = bench( - "JSON.parse + _createFromPrecompiled", - () => { - const parsed = JSON.parse(jsonStr); - const name = uid("PBR_pre"); - const shader = Shader._createFromPrecompiled({ ...parsed, name }); - shader?.destroy(true); - }, - 5, - 1 - ); - - Logger.enable(); - - logComparison("Shader Reconstruction (PBR)", [ - { - label: "PBR reconstruction", - live: liveResult.avg, - precompiled: preResult.avg - } - ]); - }); - }); - - // ═══════════════════════════════════════════════════════════ - // 5. Macro expansion: evaluateShaderInstructions benchmark - // ═══════════════════════════════════════════════════════════ - describe("5. Macro expansion: evaluateShaderInstructions", () => { - it("PBR fragment with different macro combos", () => { - const precompiled = shaderLab._precompile(PBRSource, ShaderLanguage.GLSLES100, basePath); - - let fragShaderInstructions: ShaderInstruction[] | undefined; - for (const sub of precompiled.subShaders) { - for (const pass of sub.passes) { - if (!pass.isUsePass && pass.fragmentShaderInstructions && pass.fragmentShaderInstructions.length > 1) { - fragShaderInstructions = pass.fragmentShaderInstructions; - break; - } - } - if (fragShaderInstructions) break; - } - - if (!fragShaderInstructions) { - console.log("No PBR pass with fragment instructions found, skipping."); - return; - } - - const macroSets: Array<{ label: string; macros: { name: string; value?: string }[] }> = [ - { label: "empty", macros: [] }, - { label: "base (11 macros)", macros: baseMacros }, - { label: "full (18 macros)", macros: [...baseMacros, ...materialVariantMacros] } - ]; - - const results: BenchResult[] = []; - for (const { label, macros } of macroSets) { - const macroMap = makeMacroMap(macros); - results.push( - bench( - `evaluateShaderInstructions [${label}]`, - () => { - ShaderMacroProcessor.evaluate(fragShaderInstructions!, new Map(macroMap)); - }, - 50, - 10 - ) - ); - } - - logTable("evaluateShaderInstructions (PBR fragment)", results); - - const rtResult = bench( - "runtime evaluator [base]", - () => { - ShaderMacroProcessor.evaluate(fragShaderInstructions!, new Map(makeMacroMap(baseMacros))); - }, - 50, - 10 - ); - console.log(`\nRuntime evaluator: ${rtResult.avg.toFixed(3)}ms avg`); - }); - }); - - // ═══════════════════════════════════════════════════════════ - // 6. Variant switch breakdown: CPU / GPU / Total - // - // CPU measured via _compileShaderLabSource / compilePlatformSource - // Total measured via _compileShaderProgram (CPU + GPU) - // GPU = Total - CPU - // - // GSP CPU: buildMacroList + evaluateShaderInstructions + convertTo300 + assemble - // GLSL CPU: buildMacroList + parseIncludes + parseCustomMacros + convertTo300 + assemble - // GPU: new ShaderProgram(engine, vs, fs) — WebGL compile + link - // ═══════════════════════════════════════════════════════════ - describe("6. Variant switch: CPU / GPU / Total (PBR)", () => { - it("precompiled (GSP) vs raw GLSL path", () => { - // @ts-ignore - Shader._shaderLab = shaderLab; - const precompiled = shaderLab._precompile(PBRSource, ShaderLanguage.GLSLES100, basePath); - const forwardPassData = precompiled.subShaders[0].passes.find((p) => !p.isUsePass)!; - - // GSP ShaderPass (with instructions) - const gspShaderPass = new ShaderPass( - forwardPassData.name, - forwardPassData.vertexShaderInstructions!, - forwardPassData.fragmentShaderInstructions!, - ShaderLanguage.GLSLES100, - forwardPassData.tags - ); - - // GLSL ShaderPass (raw source, no instructions → compilePlatformSource path) - const parsed = shaderLab._parseShaderSource(PBRSource); - const livePassSource = parsed.subShaders[0].passes.find((p) => !p.isUsePass)!; - const liveProg = shaderLab._parseShaderPass( - livePassSource.contents, - livePassSource.vertexEntry, - livePassSource.fragmentEntry, - ShaderLanguage.GLSLES100, - basePath - )!; - // Use original CodeGen GLSL (with all #ifdef branches preserved) - const glslShaderPass = new ShaderPass( - livePassSource.name, - liveProg.vertex, - liveProg.fragment, - livePassSource.tags - ); - - Logger.disable(); - - const scenarios: Array<{ label: string; macros: ShaderMacroCollection }> = [ - { label: "empty", macros: new ShaderMacroCollection() }, - { label: "base (11)", macros: buildMacroCollection(baseMacros) }, - { label: "full (18)", macros: buildMacroCollection([...baseMacros, ...materialVariantMacros]) } - ]; - - console.log("\n=== Variant Switch Breakdown (PBR Forward Pass) ==="); - console.log( - "| Scenario | GSP CPU (ms) | GLSL CPU (ms) | GSP GPU (ms) | GLSL GPU (ms) | GSP Total (ms) | GLSL Total (ms) | GSP Size | GLSL Size |" - ); - console.log( - "|----------|-------------|--------------|-------------|--------------|---------------|----------------|----------|------------|" - ); - - // Split-timing bench: measure CPU and GPU within the same iteration - function benchSplit( - compileFn: (macros: ShaderMacroCollection) => { vertexSource: string; fragmentSource: string }, - macroCollection: ShaderMacroCollection, - runs: number, - warmup: number - ): { cpu: number; gpu: number; total: number; vsLen: number; fsLen: number } { - // Warmup - for (let i = 0; i < warmup; i++) { - compileFn(macroCollection); - } - - const cpuTimes: number[] = []; - const gpuTimes: number[] = []; - let vsLen = 0; - let fsLen = 0; - - for (let i = 0; i < runs; i++) { - // CPU: compile source - const t0 = performance.now(); - const { vertexSource, fragmentSource } = compileFn(macroCollection); - const t1 = performance.now(); - vsLen = vertexSource.length; - fsLen = fragmentSource.length; - - // GPU: create ShaderProgram - // @ts-ignore - new ShaderProgram(engine, vertexSource, fragmentSource); - const t2 = performance.now(); - - cpuTimes.push(t1 - t0); - gpuTimes.push(t2 - t1); - } - - cpuTimes.sort((a, b) => a - b); - gpuTimes.sort((a, b) => a - b); - const cpuAvg = cpuTimes.reduce((s, t) => s + t, 0) / cpuTimes.length; - const gpuAvg = gpuTimes.reduce((s, t) => s + t, 0) / gpuTimes.length; - return { cpu: cpuAvg, gpu: gpuAvg, total: cpuAvg + gpuAvg, vsLen, fsLen }; - } - - // @ts-ignore - const gspCompile = (macros: ShaderMacroCollection) => - gspShaderPass._compileShaderLabSource(engine, macros, false); - // @ts-ignore - const glslCompile = (macros: ShaderMacroCollection) => - glslShaderPass._compilePlatformSource(engine, macros, false); - - for (const { label, macros } of scenarios) { - const gsp = benchSplit(gspCompile, macros, 10, 3); - const glsl = benchSplit(glslCompile, macros, 10, 3); - - console.log( - `| ${label.padEnd(8)} | ${gsp.cpu.toFixed(3).padStart(11)} | ${glsl.cpu.toFixed(3).padStart(12)} | ${gsp.gpu.toFixed(2).padStart(11)} | ${glsl.gpu.toFixed(2).padStart(12)} | ${gsp.total.toFixed(2).padStart(13)} | ${glsl.total.toFixed(2).padStart(14)} | ${(gsp.vsLen + gsp.fsLen).toString().padStart(9)} | ${(glsl.vsLen + glsl.fsLen).toString().padStart(10)} |` - ); - } - - Logger.enable(); - }); - }); -}); From e12a5c1edfaff5975321315beb58177013accb80 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Thu, 16 Apr 2026 04:10:09 +0800 Subject: [PATCH 66/91] refactor: sort at RenderElement level to improve batch quality Remove the RenderElement container layer and promote SubRenderElement (renamed to RenderElement) as the direct sort/render unit. Previously, sorting was done at the container level while batching operated on individual sub-elements, causing multi-submesh objects to break batches. Now sorting and batching are aligned at the same granularity, allowing same-material elements from different objects to be properly batched together, reducing draw calls. --- packages/core/src/2d/sprite/SpriteMask.ts | 24 +++---- packages/core/src/2d/sprite/SpriteRenderer.ts | 17 +++-- packages/core/src/2d/text/TextRenderer.ts | 30 ++++---- packages/core/src/Engine.ts | 13 ++-- .../src/RenderPipeline/BasicRenderPipeline.ts | 71 +++++++++---------- .../core/src/RenderPipeline/BatchUtils.ts | 34 ++++----- .../core/src/RenderPipeline/BatcherManager.ts | 51 ++++++------- .../core/src/RenderPipeline/RenderElement.ts | 54 ++++++++++---- .../core/src/RenderPipeline/RenderQueue.ts | 30 ++++---- .../src/RenderPipeline/SubRenderElement.ts | 54 -------------- packages/core/src/Renderer.ts | 6 +- packages/core/src/mesh/MeshRenderer.ts | 40 ++++++----- packages/core/src/mesh/SkinnedMeshRenderer.ts | 4 +- .../core/src/particle/ParticleRenderer.ts | 7 +- packages/core/src/trail/TrailRenderer.ts | 36 ++++++---- packages/core/src/ui/IUICanvas.ts | 2 +- packages/core/src/ui/UIUtils.ts | 5 +- packages/ui/src/component/UICanvas.ts | 7 +- packages/ui/src/component/UIRenderer.ts | 8 +-- packages/ui/src/component/advanced/Image.ts | 13 ++-- packages/ui/src/component/advanced/Text.ts | 22 +++--- 21 files changed, 245 insertions(+), 283 deletions(-) delete mode 100644 packages/core/src/RenderPipeline/SubRenderElement.ts diff --git a/packages/core/src/2d/sprite/SpriteMask.ts b/packages/core/src/2d/sprite/SpriteMask.ts index 3d99a09b81..87886df3c7 100644 --- a/packages/core/src/2d/sprite/SpriteMask.ts +++ b/packages/core/src/2d/sprite/SpriteMask.ts @@ -1,12 +1,10 @@ import { BoundingBox } from "@galacean/engine-math"; import { Entity } from "../../Entity"; -import { RenderQueueFlags } from "../../RenderPipeline/BasicRenderPipeline"; import { BatchUtils } from "../../RenderPipeline/BatchUtils"; import { PrimitiveChunkManager } from "../../RenderPipeline/PrimitiveChunkManager"; import { RenderContext } from "../../RenderPipeline/RenderContext"; -import { RenderElement } from "../../RenderPipeline/RenderElement"; import { SubPrimitiveChunk } from "../../RenderPipeline/SubPrimitiveChunk"; -import { SubRenderElement } from "../../RenderPipeline/SubRenderElement"; +import { RenderElement } from "../../RenderPipeline/RenderElement"; import { Renderer, RendererUpdateFlags } from "../../Renderer"; import { assignmentClone, ignoreClone } from "../../clone/CloneManager"; import { SpriteMaskLayer } from "../../enums/SpriteMaskLayer"; @@ -181,7 +179,6 @@ export class SpriteMask extends Renderer implements ISpriteRenderer { this.setMaterial(this._engine._basicResources.spriteMaskDefaultMaterial); this.shaderData.setFloat(SpriteMask._alphaCutoffProperty, this._alphaCutoff); this._renderElement = new RenderElement(); - this._renderElement.addSubRenderElement(new SubRenderElement()); this._onSpriteChange = this._onSpriteChange.bind(this); } @@ -204,15 +201,15 @@ export class SpriteMask extends Renderer implements ISpriteRenderer { /** * @internal */ - override _canBatch(preSubElement: SubRenderElement, subElement: SubRenderElement): boolean { - return BatchUtils.canBatchSpriteMask(preSubElement, subElement); + override _canBatch(preElement: RenderElement, curElement: RenderElement): boolean { + return BatchUtils.canBatchSpriteMask(preElement, curElement); } /** * @internal */ - override _batch(preSubElement: SubRenderElement | null, subElement: SubRenderElement): void { - BatchUtils.batchFor2D(preSubElement, subElement); + override _batch(preElement: RenderElement | null, curElement: RenderElement): void { + BatchUtils.batchFor2D(preElement, curElement); } /** @@ -297,14 +294,11 @@ export class SpriteMask extends Renderer implements ISpriteRenderer { } const renderElement = this._renderElement; - const subRenderElement = renderElement.subRenderElements[0]; - renderElement.set(this.priority, this._distanceForSort); - const subChunk = this._subChunk; - subRenderElement.set(this, material, subChunk.chunk.primitive, subChunk.subMesh, this.sprite.texture, subChunk); - subRenderElement.subShader = material.shader.subShaders[0]; - subRenderElement.renderQueueFlags = RenderQueueFlags.All; - renderElement.addSubRenderElement(subRenderElement); + renderElement.set(this, material, subChunk.chunk.primitive, subChunk.subMesh, this.sprite.texture, subChunk); + renderElement.priority = this.priority; + renderElement.distanceForSort = this._distanceForSort; + renderElement.subShader = material.shader.subShaders[0]; } /** diff --git a/packages/core/src/2d/sprite/SpriteRenderer.ts b/packages/core/src/2d/sprite/SpriteRenderer.ts index 6eac9898d1..f1f2d96586 100644 --- a/packages/core/src/2d/sprite/SpriteRenderer.ts +++ b/packages/core/src/2d/sprite/SpriteRenderer.ts @@ -4,7 +4,7 @@ import { BatchUtils } from "../../RenderPipeline/BatchUtils"; import { PrimitiveChunkManager } from "../../RenderPipeline/PrimitiveChunkManager"; import { RenderContext } from "../../RenderPipeline/RenderContext"; import { SubPrimitiveChunk } from "../../RenderPipeline/SubPrimitiveChunk"; -import { SubRenderElement } from "../../RenderPipeline/SubRenderElement"; +import { RenderElement } from "../../RenderPipeline/RenderElement"; import { Renderer, RendererUpdateFlags } from "../../Renderer"; import { assignmentClone, deepClone, ignoreClone } from "../../clone/CloneManager"; import { ShaderProperty } from "../../shader/ShaderProperty"; @@ -295,15 +295,15 @@ export class SpriteRenderer extends Renderer implements ISpriteRenderer { /** * @internal */ - override _canBatch(preSubElement: SubRenderElement, subElement: SubRenderElement): boolean { - return BatchUtils.canBatchSprite(preSubElement, subElement); + override _canBatch(preElement: RenderElement, curElement: RenderElement): boolean { + return BatchUtils.canBatchSprite(preElement, curElement); } /** * @internal */ - override _batch(preSubElement: SubRenderElement | null, subElement: SubRenderElement): void { - BatchUtils.batchFor2D(preSubElement, subElement); + override _batch(preElement: RenderElement | null, curElement: RenderElement): void { + BatchUtils.batchFor2D(preElement, curElement); } /** @@ -377,11 +377,10 @@ export class SpriteRenderer extends Renderer implements ISpriteRenderer { const camera = context.camera; const engine = camera.engine; const renderElement = engine._renderElementPool.get(); - renderElement.set(this.priority, this._distanceForSort); - const subRenderElement = engine._subRenderElementPool.get(); const subChunk = this._subChunk; - subRenderElement.set(this, material, subChunk.chunk.primitive, subChunk.subMesh, this.sprite.texture, subChunk); - renderElement.addSubRenderElement(subRenderElement); + renderElement.set(this, material, subChunk.chunk.primitive, subChunk.subMesh, this.sprite.texture, subChunk); + renderElement.priority = this.priority; + renderElement.distanceForSort = this._distanceForSort; camera._renderPipeline.pushRenderElement(context, renderElement); } diff --git a/packages/core/src/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts index 6fd6ead38d..2238018a0a 100644 --- a/packages/core/src/2d/text/TextRenderer.ts +++ b/packages/core/src/2d/text/TextRenderer.ts @@ -5,7 +5,7 @@ import { BatchUtils } from "../../RenderPipeline/BatchUtils"; import { PrimitiveChunkManager } from "../../RenderPipeline/PrimitiveChunkManager"; import { RenderContext } from "../../RenderPipeline/RenderContext"; import { SubPrimitiveChunk } from "../../RenderPipeline/SubPrimitiveChunk"; -import { SubRenderElement } from "../../RenderPipeline/SubRenderElement"; +import { RenderElement } from "../../RenderPipeline/RenderElement"; import { Renderer } from "../../Renderer"; import { TransformModifyFlags } from "../../Transform"; import { assignmentClone, deepClone, ignoreClone } from "../../clone/CloneManager"; @@ -381,15 +381,15 @@ export class TextRenderer extends Renderer implements ITextRenderer { /** * @internal */ - override _canBatch(preSubElement: SubRenderElement, subElement: SubRenderElement): boolean { - return BatchUtils.canBatchSprite(preSubElement, subElement); + override _canBatch(preElement: RenderElement, curElement: RenderElement): boolean { + return BatchUtils.canBatchSprite(preElement, curElement); } /** * @internal */ - override _batch(preSubElement: SubRenderElement | null, subElement: SubRenderElement): void { - BatchUtils.batchFor2D(preSubElement, subElement); + override _batch(preElement: RenderElement | null, curElement: RenderElement): void { + BatchUtils.batchFor2D(preElement, curElement); } /** @@ -430,20 +430,22 @@ export class TextRenderer extends Renderer implements ITextRenderer { const camera = context.camera; const engine = camera.engine; - const textSubRenderElementPool = engine._textSubRenderElementPool; + const textRenderElementPool = engine._textRenderElementPool; const material = this.getMaterial(); - const renderElement = engine._renderElementPool.get(); - renderElement.set(this.priority, this._distanceForSort); + const priority = this.priority; + const distanceForSort = this._distanceForSort; + const renderPipeline = camera._renderPipeline; const textChunks = this._textChunks; for (let i = 0, n = textChunks.length; i < n; ++i) { const { subChunk, texture } = textChunks[i]; - const subRenderElement = textSubRenderElementPool.get(); - subRenderElement.set(this, material, subChunk.chunk.primitive, subChunk.subMesh, texture, subChunk); - subRenderElement.shaderData ||= new ShaderData(ShaderDataGroup.RenderElement); - subRenderElement.shaderData.setTexture(TextRenderer._textureProperty, texture); - renderElement.addSubRenderElement(subRenderElement); + const renderElement = textRenderElementPool.get(); + renderElement.set(this, material, subChunk.chunk.primitive, subChunk.subMesh, texture, subChunk); + renderElement.shaderData ||= new ShaderData(ShaderDataGroup.RenderElement); + renderElement.shaderData.setTexture(TextRenderer._textureProperty, texture); + renderElement.priority = priority; + renderElement.distanceForSort = distanceForSort; + renderPipeline.pushRenderElement(context, renderElement); } - camera._renderPipeline.pushRenderElement(context, renderElement); } private _resetSubFont(): void { diff --git a/packages/core/src/Engine.ts b/packages/core/src/Engine.ts index 0ea112b967..ea6b2d3079 100644 --- a/packages/core/src/Engine.ts +++ b/packages/core/src/Engine.ts @@ -15,9 +15,8 @@ import { EngineSettings } from "./EngineSettings"; import { Entity } from "./Entity"; import { BatcherManager } from "./RenderPipeline/BatcherManager"; import { RenderContext } from "./RenderPipeline/RenderContext"; -import { RenderElement } from "./RenderPipeline/RenderElement"; import { RenderTargetPool } from "./RenderPipeline/RenderTargetPool"; -import { SubRenderElement } from "./RenderPipeline/SubRenderElement"; +import { RenderElement } from "./RenderPipeline/RenderElement"; import { Scene } from "./Scene"; import { SceneManager } from "./SceneManager"; import { RenderingStatistics } from "./asset/RenderingStatistics"; @@ -94,9 +93,7 @@ export class Engine extends EventDispatcher { /* @internal */ _renderElementPool = new ClearableObjectPool(RenderElement); /* @internal */ - _subRenderElementPool = new ClearableObjectPool(SubRenderElement); - /* @internal */ - _textSubRenderElementPool = new ClearableObjectPool(SubRenderElement); + _textRenderElementPool = new ClearableObjectPool(RenderElement); /* @internal */ _charRenderInfoPool = new ReturnableObjectPool(CharRenderInfo, 50); @@ -330,9 +327,8 @@ export class Engine extends EventDispatcher { const deltaTime = time.deltaTime; this._frameInProcess = true; - this._subRenderElementPool.clear(); - this._textSubRenderElementPool.clear(); this._renderElementPool.clear(); + this._textRenderElementPool.clear(); this.xrManager?._update(); const { inputManager, _physicsInitialized: physicsInitialized } = this; @@ -698,9 +694,8 @@ export class Engine extends EventDispatcher { } private _gc(): void { - this._subRenderElementPool.garbageCollection(); - this._textSubRenderElementPool.garbageCollection(); this._renderElementPool.garbageCollection(); + this._textRenderElementPool.garbageCollection(); this._renderContext.garbageCollection(); const scenes = this._sceneManager._scenes.getLoopArray(); for (let i = 0, n = scenes.length; i < n; i++) { diff --git a/packages/core/src/RenderPipeline/BasicRenderPipeline.ts b/packages/core/src/RenderPipeline/BasicRenderPipeline.ts index 2c99c9eda9..84f5dcb7c7 100644 --- a/packages/core/src/RenderPipeline/BasicRenderPipeline.ts +++ b/packages/core/src/RenderPipeline/BasicRenderPipeline.ts @@ -30,7 +30,6 @@ import { OpaqueTexturePass } from "./OpaqueTexturePass"; import { PipelineUtils } from "./PipelineUtils"; import { ContextRendererUpdateFlag, RenderContext } from "./RenderContext"; import { RenderElement } from "./RenderElement"; -import { SubRenderElement } from "./SubRenderElement"; import { PipelineStage } from "./enums/PipelineStage"; /** * Basic render pipeline. @@ -369,59 +368,53 @@ export class BasicRenderPipeline { * @param renderElement - Render element */ pushRenderElement(context: RenderContext, renderElement: RenderElement): void { - renderElement.renderQueueFlags = RenderQueueFlags.None; - const subRenderElements = renderElement.subRenderElements; - for (let i = 0, n = subRenderElements.length; i < n; ++i) { - const subRenderElement = subRenderElements[i]; - const { material } = subRenderElement; - const { renderStates } = material; - const materialSubShader = material.shader.subShaders[0]; - const replacementShader = context.replacementShader; - if (replacementShader) { - const replacementSubShaders = replacementShader.subShaders; - const { replacementTag } = context; - if (replacementTag) { - let replacementSuccess = false; - for (let j = 0, m = replacementSubShaders.length; j < m; j++) { - const subShader = replacementSubShaders[j]; - if (subShader.getTagValue(replacementTag) === materialSubShader.getTagValue(replacementTag)) { - this.pushRenderElementByType(renderElement, subRenderElement, subShader, renderStates); - replacementSuccess = true; - } + const { material } = renderElement; + const { renderStates } = material; + const materialSubShader = material.shader.subShaders[0]; + const replacementShader = context.replacementShader; + if (replacementShader) { + const replacementSubShaders = replacementShader.subShaders; + const { replacementTag } = context; + if (replacementTag) { + let replacementSuccess = false; + for (let j = 0, m = replacementSubShaders.length; j < m; j++) { + const subShader = replacementSubShaders[j]; + if (subShader.getTagValue(replacementTag) === materialSubShader.getTagValue(replacementTag)) { + this._pushRenderElementByType(renderElement, subShader, renderStates); + replacementSuccess = true; } + } - if ( - !replacementSuccess && - context.replacementFailureStrategy === ReplacementFailureStrategy.KeepOriginalShader - ) { - this.pushRenderElementByType(renderElement, subRenderElement, materialSubShader, renderStates); - } - } else { - this.pushRenderElementByType(renderElement, subRenderElement, replacementSubShaders[0], renderStates); + if ( + !replacementSuccess && + context.replacementFailureStrategy === ReplacementFailureStrategy.KeepOriginalShader + ) { + this._pushRenderElementByType(renderElement, materialSubShader, renderStates); } } else { - this.pushRenderElementByType(renderElement, subRenderElement, materialSubShader, renderStates); + this._pushRenderElementByType(renderElement, replacementSubShaders[0], renderStates); } + } else { + this._pushRenderElementByType(renderElement, materialSubShader, renderStates); } } - private pushRenderElementByType( + private _pushRenderElementByType( renderElement: RenderElement, - subRenderElement: SubRenderElement, subShader: SubShader, renderStates: ReadonlyArray ): void { const shaderPasses = subShader.passes; const cullingResults = this._cullingResults; + let pushedQueueFlags = RenderQueueFlags.None; for (let i = 0, n = shaderPasses.length; i < n; i++) { - // Get render queue type let renderQueueType: RenderQueueType; const shaderPass = shaderPasses[i]; const renderState = shaderPass._renderState; if (renderState) { renderQueueType = renderState._getRenderQueueByShaderData( shaderPass._renderStateDataMap, - subRenderElement.material.shaderData + renderElement.material.shaderData ); } else { renderQueueType = renderStates[i].renderQueueType; @@ -429,10 +422,9 @@ export class BasicRenderPipeline { const flag = 1 << renderQueueType; - subRenderElement.subShader = subShader; - subRenderElement.renderQueueFlags |= flag; + renderElement.subShader = subShader; - if (renderElement.renderQueueFlags & flag) { + if (pushedQueueFlags & flag) { continue; } @@ -447,7 +439,7 @@ export class BasicRenderPipeline { cullingResults.transparentQueue.pushRenderElement(renderElement); break; } - renderElement.renderQueueFlags |= flag; + pushedQueueFlags |= flag; } } @@ -517,7 +509,10 @@ export class BasicRenderPipeline { continue; } canvas._prepareRender(context); - this.pushRenderElement(context, canvas._renderElement); + const canvasElements = canvas._renderElements; + for (let j = 0, m = canvasElements.length; j < m; j++) { + this.pushRenderElement(context, canvasElements[j]); + } } } } diff --git a/packages/core/src/RenderPipeline/BatchUtils.ts b/packages/core/src/RenderPipeline/BatchUtils.ts index 5c7345183a..bedf157077 100644 --- a/packages/core/src/RenderPipeline/BatchUtils.ts +++ b/packages/core/src/RenderPipeline/BatchUtils.ts @@ -1,6 +1,6 @@ import { SpriteMask, SpriteMaskInteraction, SpriteRenderer } from "../2d"; import { ShaderTagKey } from "../shader"; -import { SubRenderElement } from "./SubRenderElement"; +import { RenderElement } from "./RenderElement"; /** * @internal @@ -8,29 +8,29 @@ import { SubRenderElement } from "./SubRenderElement"; export class BatchUtils { protected static _disableBatchTag: ShaderTagKey = ShaderTagKey.getByName("spriteDisableBatching"); - static canBatchSprite(preSubElement: SubRenderElement, subElement: SubRenderElement): boolean { - if (subElement.subShader.passes[0].getTagValue(BatchUtils._disableBatchTag) === true) { + static canBatchSprite(preElement: RenderElement, curElement: RenderElement): boolean { + if (curElement.subShader.passes[0].getTagValue(BatchUtils._disableBatchTag) === true) { return false; } - if (preSubElement.subChunk.chunk !== subElement.subChunk.chunk) { + if (preElement.subChunk.chunk !== curElement.subChunk.chunk) { return false; } - const preRenderer = preSubElement.component; - const renderer = subElement.component; + const preRenderer = preElement.component; + const renderer = curElement.component; const maskInteractionA = preRenderer.maskInteraction; // Compare mask, texture and material return ( maskInteractionA === renderer.maskInteraction && (maskInteractionA === SpriteMaskInteraction.None || preRenderer.maskLayer === renderer.maskLayer) && - preSubElement.texture === subElement.texture && - preSubElement.material === subElement.material + preElement.texture === curElement.texture && + preElement.material === curElement.material ); } - static canBatchSpriteMask(preSubElement: SubRenderElement, subElement: SubRenderElement): boolean { - if (preSubElement.subChunk.chunk !== subElement.subChunk.chunk) { + static canBatchSpriteMask(preElement: RenderElement, curElement: RenderElement): boolean { + if (preElement.subChunk.chunk !== curElement.subChunk.chunk) { return false; } @@ -38,20 +38,20 @@ export class BatchUtils { // Compare renderer property return ( - preSubElement.texture === subElement.texture && - (preSubElement.component).shaderData.getFloat(alphaCutoffProperty) === - (subElement.component).shaderData.getFloat(alphaCutoffProperty) + preElement.texture === curElement.texture && + (preElement.component).shaderData.getFloat(alphaCutoffProperty) === + (curElement.component).shaderData.getFloat(alphaCutoffProperty) ); } - static batchFor2D(preSubElement: SubRenderElement | null, subElement: SubRenderElement): void { - const subChunk = subElement.subChunk; + static batchFor2D(preElement: RenderElement | null, curElement: RenderElement): void { + const subChunk = curElement.subChunk; const { chunk, indices: subChunkIndices } = subChunk; const length = subChunkIndices.length; let startIndex = chunk.updateIndexLength; - if (preSubElement) { - preSubElement.subChunk.subMesh.count += length; + if (preElement) { + preElement.subChunk.subMesh.count += length; } else { // Reset subMesh const subMesh = subChunk.subMesh; diff --git a/packages/core/src/RenderPipeline/BatcherManager.ts b/packages/core/src/RenderPipeline/BatcherManager.ts index 03d965825f..fff5ee2290 100644 --- a/packages/core/src/RenderPipeline/BatcherManager.ts +++ b/packages/core/src/RenderPipeline/BatcherManager.ts @@ -3,7 +3,7 @@ import { Renderer } from "../Renderer"; import { InstanceBatch } from "./InstanceBatch"; import { PrimitiveChunkManager } from "./PrimitiveChunkManager"; import { RenderQueue } from "./RenderQueue"; -import { SubRenderElement } from "./SubRenderElement"; +import { RenderElement } from "./RenderElement"; /** * @internal @@ -52,45 +52,36 @@ export class BatcherManager { } batch(renderQueue: RenderQueue): void { - const { elements, batchedSubElements, renderQueueType } = renderQueue; + const { elements, batchedElements } = renderQueue; - let preSubElement: SubRenderElement; + let preElement: RenderElement; let preRenderer: Renderer; let preConstructor: Function; for (let i = 0, n = elements.length; i < n; ++i) { - const subElements = elements[i].subRenderElements; - for (let j = 0, m = subElements.length; j < m; ++j) { - const subElement = subElements[j]; - - // Some sub render elements may not belong to the current render queue - if (!(subElement.renderQueueFlags & (1 << renderQueueType))) { - continue; - } - - const renderer = subElement.component; - const constructor = renderer.constructor; - if (preSubElement) { - if (preConstructor === constructor && preRenderer._canBatch(preSubElement, subElement)) { - preRenderer._batch(preSubElement, subElement); - preSubElement.batched = true; - } else { - batchedSubElements.push(preSubElement); - preSubElement = subElement; - preRenderer = renderer; - preConstructor = constructor; - renderer._batch(null, subElement); - subElement.batched = false; - } + const curElement = elements[i]; + const renderer = curElement.component; + const constructor = renderer.constructor; + if (preElement) { + if (preConstructor === constructor && preRenderer._canBatch(preElement, curElement)) { + preRenderer._batch(preElement, curElement); + preElement.batched = true; } else { - preSubElement = subElement; + batchedElements.push(preElement); + preElement = curElement; preRenderer = renderer; preConstructor = constructor; - renderer._batch(null, subElement); - subElement.batched = false; + renderer._batch(null, curElement); + curElement.batched = false; } + } else { + preElement = curElement; + preRenderer = renderer; + preConstructor = constructor; + renderer._batch(null, curElement); + curElement.batched = false; } } - preSubElement && batchedSubElements.push(preSubElement); + preElement && batchedElements.push(preElement); } uploadBuffer() { diff --git a/packages/core/src/RenderPipeline/RenderElement.ts b/packages/core/src/RenderPipeline/RenderElement.ts index 582e72f47d..55176e4d58 100644 --- a/packages/core/src/RenderPipeline/RenderElement.ts +++ b/packages/core/src/RenderPipeline/RenderElement.ts @@ -1,24 +1,54 @@ +import { Renderer } from "../Renderer"; +import { Primitive, SubMesh } from "../graphic"; +import { Material } from "../material"; +import { ShaderData, SubShader } from "../shader"; +import { Texture2D } from "../texture"; import { IPoolElement } from "../utils/ObjectPool"; -import { RenderQueueFlags } from "./BasicRenderPipeline"; -import { SubRenderElement } from "./SubRenderElement"; +import { SubPrimitiveChunk } from "./SubPrimitiveChunk"; export class RenderElement implements IPoolElement { priority: number; distanceForSort: number; - subRenderElements = Array(); - renderQueueFlags: RenderQueueFlags; + component: Renderer; + primitive: Primitive; + material: Material; + subPrimitive: SubMesh; + subShader: SubShader; + shaderData?: ShaderData; + batched: boolean; + instancedRenderers: Renderer[] = []; - set(priority: number, distanceForSort: number): void { - this.priority = priority; - this.distanceForSort = distanceForSort; - this.subRenderElements.length = 0; - } + // @todo: maybe should remove later + texture?: Texture2D; + subChunk?: SubPrimitiveChunk; - addSubRenderElement(element: SubRenderElement): void { - this.subRenderElements.push(element); + set( + component: Renderer, + material: Material, + primitive: Primitive, + subPrimitive: SubMesh, + texture?: Texture2D, + subChunk?: SubPrimitiveChunk + ): void { + this.component = component; + this.material = material; + this.primitive = primitive; + this.subPrimitive = subPrimitive; + this.texture = texture; + this.subChunk = subChunk; + this.instancedRenderers.length = 0; } dispose(): void { - this.subRenderElements.length = 0; + this.component = null; + this.material = null; + this.primitive = null; + this.subPrimitive = null; + this.subShader = null; + this.shaderData && (this.shaderData = null); + this.instancedRenderers = null; + + this.texture && (this.texture = null); + this.subChunk && (this.subChunk = null); } } diff --git a/packages/core/src/RenderPipeline/RenderQueue.ts b/packages/core/src/RenderPipeline/RenderQueue.ts index 386e6ef70f..a84161ad7d 100644 --- a/packages/core/src/RenderPipeline/RenderQueue.ts +++ b/packages/core/src/RenderPipeline/RenderQueue.ts @@ -8,21 +8,17 @@ import { BatcherManager } from "./BatcherManager"; import { InstanceBatch } from "./InstanceBatch"; import { ContextRendererUpdateFlag, RenderContext } from "./RenderContext"; import { RenderElement } from "./RenderElement"; -import { SubRenderElement } from "./SubRenderElement"; import { RenderQueueMaskType } from "./enums/RenderQueueMaskType"; /** * @internal */ export class RenderQueue { - // @todo: Sort at SubRenderElement level instead of RenderElement level to avoid multi-submesh objects breaking batches static compareForOpaque(a: RenderElement, b: RenderElement): number { - const sa = a.subRenderElements[0], - sb = b.subRenderElements[0]; return ( a.priority - b.priority || - sa.material.instanceId - sb.material.instanceId || - sa.primitive.instanceId - sb.primitive.instanceId + a.material.instanceId - b.material.instanceId || + a.primitive.instanceId - b.primitive.instanceId ); } @@ -31,7 +27,7 @@ export class RenderQueue { } readonly elements = new Array(); - readonly batchedSubElements = new Array(); + readonly batchedElements = new Array(); rendererUpdateFlag = ContextRendererUpdateFlag.None; constructor(public renderQueueType: RenderQueueType) {} @@ -54,8 +50,8 @@ export class RenderQueue { pipelineStageTagValue: string, maskType: RenderQueueMaskType = RenderQueueMaskType.No ): void { - const batchedSubElements = this.batchedSubElements; - const length = batchedSubElements.length; + const batchedElements = this.batchedElements; + const length = batchedElements.length; if (length === 0) { return; } @@ -68,14 +64,14 @@ export class RenderQueue { const renderQueueType = this.renderQueueType; for (let i = 0; i < length; i++) { - const subElement = batchedSubElements[i]; - const { component, material, instancedRenderers } = subElement; + const curElement = batchedElements[i]; + const { component, material, instancedRenderers } = curElement; const isInstanced = instancedRenderers.length > 0; // Instancing: transform data is packed in UBO, skip per-renderer update if (!isInstanced) { // @todo: Can optimize update view projection matrix updated - const batched = subElement.batched; + const batched = curElement.batched; if ( this.rendererUpdateFlag & ContextRendererUpdateFlag.WorldViewMatrix || component._batchedTransformShaderData != batched @@ -106,8 +102,8 @@ export class RenderQueue { maskManager.isStencilWritten(material) && (maskManager.hasStencilWritten = true); } - const { primitive, shaderData: renderElementShaderData } = subElement; - const shaderPasses = subElement.subShader.passes; + const { primitive, shaderData: renderElementShaderData } = curElement; + const shaderPasses = curElement.subShader.passes; const { shaderData: rendererData, instanceId: rendererId } = component; const { shaderData: materialData, instanceId: materialId, renderStates } = material; @@ -230,11 +226,11 @@ export class RenderQueue { const count = Math.min(maxCount, totalCount - start); instanceBatch.upload(instancedRenderers, start, count); primitive.instanceCount = count; - rhi.drawPrimitive(primitive, subElement.subPrimitive, program); + rhi.drawPrimitive(primitive, curElement.subPrimitive, program); } primitive.instanceCount = 0; } else { - rhi.drawPrimitive(primitive, subElement.subPrimitive, program); + rhi.drawPrimitive(primitive, curElement.subPrimitive, program); } } } @@ -244,7 +240,7 @@ export class RenderQueue { clear(): void { this.elements.length = 0; - this.batchedSubElements.length = 0; + this.batchedElements.length = 0; } destroy(): void {} diff --git a/packages/core/src/RenderPipeline/SubRenderElement.ts b/packages/core/src/RenderPipeline/SubRenderElement.ts deleted file mode 100644 index f49d159f31..0000000000 --- a/packages/core/src/RenderPipeline/SubRenderElement.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Renderer } from "../Renderer"; -import { Primitive, SubMesh } from "../graphic"; -import { Material } from "../material"; -import { ShaderData, SubShader } from "../shader"; -import { Texture2D } from "../texture"; -import { IPoolElement } from "../utils/ObjectPool"; -import { RenderQueueFlags } from "./BasicRenderPipeline"; -import { SubPrimitiveChunk } from "./SubPrimitiveChunk"; - -export class SubRenderElement implements IPoolElement { - component: Renderer; - primitive: Primitive; - material: Material; - subPrimitive: SubMesh; - subShader: SubShader; - shaderData?: ShaderData; - batched: boolean; - renderQueueFlags: RenderQueueFlags; - instancedRenderers: Renderer[] = []; - - // @todo: maybe should remove later - texture?: Texture2D; - subChunk?: SubPrimitiveChunk; - - set( - component: Renderer, - material: Material, - primitive: Primitive, - subPrimitive: SubMesh, - texture?: Texture2D, - subChunk?: SubPrimitiveChunk - ): void { - this.component = component; - this.material = material; - this.primitive = primitive; - this.subPrimitive = subPrimitive; - this.texture = texture; - this.subChunk = subChunk; - this.instancedRenderers.length = 0; - } - - dispose(): void { - this.component = null; - this.material = null; - this.primitive = null; - this.subPrimitive = null; - this.subShader = null; - this.shaderData && (this.shaderData = null); - this.instancedRenderers = null; - - this.texture && (this.texture = null); - this.subChunk && (this.subChunk = null); - } -} diff --git a/packages/core/src/Renderer.ts b/packages/core/src/Renderer.ts index a7e6f4e51e..fbf43b67f8 100644 --- a/packages/core/src/Renderer.ts +++ b/packages/core/src/Renderer.ts @@ -5,7 +5,7 @@ import { Component } from "./Component"; import { DependentMode, dependentComponents } from "./ComponentsDependencies"; import { Entity } from "./Entity"; import { RenderContext } from "./RenderPipeline/RenderContext"; -import { SubRenderElement } from "./RenderPipeline/SubRenderElement"; +import { RenderElement } from "./RenderPipeline/RenderElement"; import { Transform, TransformModifyFlags } from "./Transform"; import { assignmentClone, deepClone, ignoreClone } from "./clone/CloneManager"; import { SpriteMaskLayer } from "./enums/SpriteMaskLayer"; @@ -403,14 +403,14 @@ export class Renderer extends Component { /** * @internal */ - _canBatch(preSubElement: SubRenderElement, subElement: SubRenderElement): boolean { + _canBatch(preElement: RenderElement, curElement: RenderElement): boolean { return false; } /** * @internal */ - _batch(preSubElement: SubRenderElement | null, subElement: SubRenderElement): void {} + _batch(preElement: RenderElement | null, curElement: RenderElement): void {} /** * Update once per frame per renderer, not influenced by batched. diff --git a/packages/core/src/mesh/MeshRenderer.ts b/packages/core/src/mesh/MeshRenderer.ts index 5649d95414..7eaf45be7b 100644 --- a/packages/core/src/mesh/MeshRenderer.ts +++ b/packages/core/src/mesh/MeshRenderer.ts @@ -1,7 +1,7 @@ import { BoundingBox } from "@galacean/engine-math"; import { Entity } from "../Entity"; import { RenderContext } from "../RenderPipeline/RenderContext"; -import { SubRenderElement } from "../RenderPipeline/SubRenderElement"; +import { RenderElement } from "../RenderPipeline/RenderElement"; import { Renderer, RendererUpdateFlags } from "../Renderer"; import { Logger } from "../base/Logger"; import { ignoreClone } from "../clone/CloneManager"; @@ -152,9 +152,10 @@ export class MeshRenderer extends Renderer { const { _materials: materials, _engine: engine } = this; const subMeshes = mesh.subMeshes; - const renderElement = engine._renderElementPool.get(); - renderElement.set(this.priority, this._distanceForSort); - const subRenderElementPool = engine._subRenderElementPool; + const priority = this.priority; + const distanceForSort = this._distanceForSort; + const renderElementPool = engine._renderElementPool; + const renderPipeline = context.camera._renderPipeline; for (let i = 0, n = subMeshes.length; i < n; i++) { let material = materials[i]; if (!material) { @@ -164,37 +165,38 @@ export class MeshRenderer extends Renderer { material = this.engine._basicResources.meshMagentaMaterial; } - const subRenderElement = subRenderElementPool.get(); - subRenderElement.set(this, material, mesh._primitive, subMeshes[i]); - renderElement.addSubRenderElement(subRenderElement); + const renderElement = renderElementPool.get(); + renderElement.set(this, material, mesh._primitive, subMeshes[i]); + renderElement.priority = priority; + renderElement.distanceForSort = distanceForSort; + renderPipeline.pushRenderElement(context, renderElement); } - context.camera._renderPipeline.pushRenderElement(context, renderElement); } /** * @internal */ - override _canBatch(preSubElement: SubRenderElement, subElement: SubRenderElement): boolean { + override _canBatch(preElement: RenderElement, curElement: RenderElement): boolean { if (!this._engine._hardwareRenderer.isWebGL2) return false; return ( - preSubElement.primitive === subElement.primitive && - preSubElement.subPrimitive === subElement.subPrimitive && - preSubElement.material === subElement.material && - this._isFrontFaceInvert() === (subElement.component)._isFrontFaceInvert() && - this.shaderData._macroCollection.isEqual(subElement.component.shaderData._macroCollection) + preElement.primitive === curElement.primitive && + preElement.subPrimitive === curElement.subPrimitive && + preElement.material === curElement.material && + this._isFrontFaceInvert() === (curElement.component)._isFrontFaceInvert() && + this.shaderData._macroCollection.isEqual(curElement.component.shaderData._macroCollection) ); } /** * @internal */ - override _batch(preSubElement: SubRenderElement | null, subElement: SubRenderElement): void { - if (!preSubElement) return; - const renderers = preSubElement.instancedRenderers; + override _batch(preElement: RenderElement | null, curElement: RenderElement): void { + if (!preElement) return; + const renderers = preElement.instancedRenderers; if (renderers.length === 0) { - renderers.push(preSubElement.component); + renderers.push(preElement.component); } - renderers.push(subElement.component); + renderers.push(curElement.component); } private _setMesh(mesh: Mesh): void { diff --git a/packages/core/src/mesh/SkinnedMeshRenderer.ts b/packages/core/src/mesh/SkinnedMeshRenderer.ts index d580dcd608..4d7f726184 100644 --- a/packages/core/src/mesh/SkinnedMeshRenderer.ts +++ b/packages/core/src/mesh/SkinnedMeshRenderer.ts @@ -1,7 +1,7 @@ import { BoundingBox, Vector2 } from "@galacean/engine-math"; import { Entity } from "../Entity"; import { RenderContext } from "../RenderPipeline/RenderContext"; -import { SubRenderElement } from "../RenderPipeline/SubRenderElement"; +import { RenderElement } from "../RenderPipeline/RenderElement"; import { RendererUpdateFlags } from "../Renderer"; import { Logger } from "../base/Logger"; import { deepClone, ignoreClone } from "../clone/CloneManager"; @@ -124,7 +124,7 @@ export class SkinnedMeshRenderer extends MeshRenderer { /** * @internal */ - override _canBatch(_preSubElement: SubRenderElement, _subElement: SubRenderElement): boolean { + override _canBatch(_preElement: RenderElement, _curElement: RenderElement): boolean { return false; } diff --git a/packages/core/src/particle/ParticleRenderer.ts b/packages/core/src/particle/ParticleRenderer.ts index 82d2965142..61aa0d19db 100644 --- a/packages/core/src/particle/ParticleRenderer.ts +++ b/packages/core/src/particle/ParticleRenderer.ts @@ -251,10 +251,9 @@ export class ParticleRenderer extends Renderer { const engine = this._engine; const renderElement = engine._renderElementPool.get(); - renderElement.set(this.priority, this._distanceForSort); - const subRenderElement = engine._subRenderElementPool.get(); - subRenderElement.set(this, material, generator._primitive, generator._subPrimitive); - renderElement.addSubRenderElement(subRenderElement); + renderElement.set(this, material, generator._primitive, generator._subPrimitive); + renderElement.priority = this.priority; + renderElement.distanceForSort = this._distanceForSort; context.camera._renderPipeline.pushRenderElement(context, renderElement); } diff --git a/packages/core/src/trail/TrailRenderer.ts b/packages/core/src/trail/TrailRenderer.ts index b8d97f4365..6485d658ca 100644 --- a/packages/core/src/trail/TrailRenderer.ts +++ b/packages/core/src/trail/TrailRenderer.ts @@ -1,7 +1,6 @@ import { BoundingBox, Color, Vector2, Vector3, Vector4 } from "@galacean/engine-math"; import { Entity } from "../Entity"; import { RenderContext } from "../RenderPipeline/RenderContext"; -import { RenderElement } from "../RenderPipeline/RenderElement"; import { Renderer, RendererUpdateFlags } from "../Renderer"; import { deepClone, ignoreClone } from "../clone/CloneManager"; import { Buffer } from "../graphic/Buffer"; @@ -232,8 +231,9 @@ export class TrailRenderer extends Renderer { const { _firstActiveElement: firstActive, _firstFreeElement: firstFree } = this; - const renderElement = this._engine._renderElementPool.get(); - renderElement.set(this.priority, this._distanceForSort); + const priority = this.priority; + const distanceForSort = this._distanceForSort; + const renderPipeline = context.camera._renderPipeline; // spansBoundary: active points cross buffer end // wrapped: spansBoundary AND point 0 has been written (need bridge + second segment) @@ -241,13 +241,19 @@ export class TrailRenderer extends Renderer { const wrapped = spansBoundary && firstFree > 0; const mainCount = (spansBoundary ? this._currentPointCapacity - firstActive + (wrapped ? 1 : 0) : firstFree - firstActive) * 2; - this._addSubRenderElement(renderElement, material, this._mainSubPrimitive, firstActive * 2, mainCount); + this._addRenderElement( + context, + material, + this._mainSubPrimitive, + firstActive * 2, + mainCount, + priority, + distanceForSort + ); if (wrapped) { - this._addSubRenderElement(renderElement, material, this._wrapSubPrimitive, 0, firstFree * 2); + this._addRenderElement(context, material, this._wrapSubPrimitive, 0, firstFree * 2, priority, distanceForSort); } - - context.camera._renderPipeline.pushRenderElement(context, renderElement); } protected override _updateBounds(worldBounds: BoundingBox): void { @@ -588,18 +594,22 @@ export class TrailRenderer extends Renderer { this._bufferResized = false; } - private _addSubRenderElement( - renderElement: RenderElement, + private _addRenderElement( + context: RenderContext, material: Material, subPrimitive: SubPrimitive, start: number, - count: number + count: number, + priority: number, + distanceForSort: number ): void { subPrimitive.start = start; subPrimitive.count = count; - const subRenderElement = this._engine._subRenderElementPool.get(); - subRenderElement.set(this, material, this._primitive, subPrimitive); - renderElement.addSubRenderElement(subRenderElement); + const renderElement = this._engine._renderElementPool.get(); + renderElement.set(this, material, this._primitive, subPrimitive); + renderElement.priority = priority; + renderElement.distanceForSort = distanceForSort; + context.camera._renderPipeline.pushRenderElement(context, renderElement); } @ignoreClone diff --git a/packages/core/src/ui/IUICanvas.ts b/packages/core/src/ui/IUICanvas.ts index 3139b6dba8..e90e209aa2 100644 --- a/packages/core/src/ui/IUICanvas.ts +++ b/packages/core/src/ui/IUICanvas.ts @@ -10,7 +10,7 @@ export interface IUICanvas { entity: Entity; sortOrder: number; _canvasIndex: number; - _renderElement: RenderElement; + _renderElements: RenderElement[]; _canRender(camera: Camera): boolean; _prepareRender(renderContext: RenderContext): void; } diff --git a/packages/core/src/ui/UIUtils.ts b/packages/core/src/ui/UIUtils.ts index c57c8bdf1f..a6f7822a03 100644 --- a/packages/core/src/ui/UIUtils.ts +++ b/packages/core/src/ui/UIUtils.ts @@ -44,7 +44,10 @@ export class UIUtils { renderContext.applyVirtualCamera(virtualCamera, false); uiRenderQueue.rendererUpdateFlag |= ContextRendererUpdateFlag.ProjectionMatrix; uiCanvas._prepareRender(renderContext); - uiRenderQueue.pushRenderElement(uiCanvas._renderElement); + const curElements = uiCanvas._renderElements; + for (let j = 0, m = curElements.length; j < m; j++) { + uiRenderQueue.pushRenderElement(curElements[j]); + } uiRenderQueue.batch(batcherManager); batcherManager.uploadBuffer(); uiRenderQueue.render(renderContext, "Forward"); diff --git a/packages/ui/src/component/UICanvas.ts b/packages/ui/src/component/UICanvas.ts index 9e5b33c405..f4bf699135 100644 --- a/packages/ui/src/component/UICanvas.ts +++ b/packages/ui/src/component/UICanvas.ts @@ -62,7 +62,7 @@ export class UICanvas extends Component implements IElement { _isRootCanvas: boolean = false; /** @internal */ @ignoreClone - _renderElement: any; + _renderElements: any[] = []; /** @internal */ @ignoreClone _sortDistance: number = 0; @@ -307,11 +307,10 @@ export class UICanvas extends Component implements IElement { const { engine, _realRenderMode: mode } = this; const { enableFrustumCulling, cullingMask, _frustum: frustum } = context.camera; const { frameCount } = engine.time; - // @ts-ignore - const renderElement = (this._renderElement = engine._renderElementPool.get()); + const renderElements = this._renderElements; + renderElements.length = 0; const virtualCamera = context.virtualCamera; this._updateSortDistance(virtualCamera.isOrthographic, virtualCamera.position, virtualCamera.forward); - renderElement.set(this.sortOrder, this._sortDistance); const { width, height } = engine.canvas; const renderers = this._getRenderers(); for (let i = 0, n = renderers.length; i < n; i++) { diff --git a/packages/ui/src/component/UIRenderer.ts b/packages/ui/src/component/UIRenderer.ts index 139932446f..a9cf0067a4 100644 --- a/packages/ui/src/component/UIRenderer.ts +++ b/packages/ui/src/component/UIRenderer.ts @@ -113,13 +113,13 @@ export class UIRenderer extends Renderer implements IGraphics { } // @ts-ignore - override _canBatch(preSubElement, subElement): boolean { - return BatchUtils.canBatchSprite(preSubElement, subElement); + override _canBatch(preElement, curElement): boolean { + return BatchUtils.canBatchSprite(preElement, curElement); } // @ts-ignore - override _batch(preSubElement, subElement): void { - BatchUtils.batchFor2D(preSubElement, subElement); + override _batch(preElement, curElement): void { + BatchUtils.batchFor2D(preElement, curElement); } // @ts-ignore diff --git a/packages/ui/src/component/advanced/Image.ts b/packages/ui/src/component/advanced/Image.ts index d6806d726a..2a965f6db2 100644 --- a/packages/ui/src/component/advanced/Image.ts +++ b/packages/ui/src/component/advanced/Image.ts @@ -4,7 +4,6 @@ import { ISpriteAssembler, ISpriteRenderer, MathUtil, - RenderQueueFlags, RendererUpdateFlags, SimpleSpriteAssembler, SlicedSpriteAssembler, @@ -236,16 +235,16 @@ export class Image extends UIRenderer implements ISpriteRenderer { } this._dirtyUpdateFlag = dirtyUpdateFlag; - // Init sub render element. const { engine } = context.camera; - const subRenderElement = engine._subRenderElementPool.get(); + const renderElement = engine._renderElementPool.get(); const subChunk = this._subChunk; - subRenderElement.set(this, material, subChunk.chunk.primitive, subChunk.subMesh, this.sprite.texture, subChunk); + renderElement.set(this, material, subChunk.chunk.primitive, subChunk.subMesh, this.sprite.texture, subChunk); if (canvas._realRenderMode === CanvasRenderMode.ScreenSpaceOverlay) { - subRenderElement.subShader = material.shader.subShaders[0]; - subRenderElement.renderQueueFlags = RenderQueueFlags.All; + renderElement.subShader = material.shader.subShaders[0]; } - canvas._renderElement.addSubRenderElement(subRenderElement); + renderElement.priority = canvas.sortOrder; + renderElement.distanceForSort = canvas._sortDistance; + canvas._renderElements.push(renderElement); } @ignoreClone diff --git a/packages/ui/src/component/advanced/Text.ts b/packages/ui/src/component/advanced/Text.ts index 7f156b37bc..6f20830716 100644 --- a/packages/ui/src/component/advanced/Text.ts +++ b/packages/ui/src/component/advanced/Text.ts @@ -7,7 +7,6 @@ import { FontStyle, ITextRenderer, OverflowMode, - RenderQueueFlags, RendererUpdateFlags, ShaderData, ShaderDataGroup, @@ -350,23 +349,26 @@ export class Text extends UIRenderer implements ITextRenderer { } const engine = context.camera.engine; - const textSubRenderElementPool = engine._textSubRenderElementPool; + const textRenderElementPool = engine._textRenderElementPool; const material = this.getMaterial(); - const renderElement = canvas._renderElement; + const renderElements = canvas._renderElements; + const priority = canvas.sortOrder; + const distanceForSort = canvas._sortDistance; const textChunks = this._textChunks; const isOverlay = canvas._realRenderMode === CanvasRenderMode.ScreenSpaceOverlay; for (let i = 0, n = textChunks.length; i < n; ++i) { const { subChunk, texture } = textChunks[i]; - const subRenderElement = textSubRenderElementPool.get(); - subRenderElement.set(this, material, subChunk.chunk.primitive, subChunk.subMesh, texture, subChunk); + const renderElement = textRenderElementPool.get(); + renderElement.set(this, material, subChunk.chunk.primitive, subChunk.subMesh, texture, subChunk); // @ts-ignore - subRenderElement.shaderData ||= new ShaderData(ShaderDataGroup.RenderElement); - subRenderElement.shaderData.setTexture(Text._textTextureProperty, texture); + renderElement.shaderData ||= new ShaderData(ShaderDataGroup.RenderElement); + renderElement.shaderData.setTexture(Text._textTextureProperty, texture); if (isOverlay) { - subRenderElement.subShader = material.shader.subShaders[0]; - subRenderElement.renderQueueFlags = RenderQueueFlags.All; + renderElement.subShader = material.shader.subShaders[0]; } - renderElement.addSubRenderElement(subRenderElement); + renderElement.priority = priority; + renderElement.distanceForSort = distanceForSort; + renderElements.push(renderElement); } } From 607c5a39eb6a05c38a76b0ad78de64683c0f9072 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Thu, 16 Apr 2026 23:21:25 +0800 Subject: [PATCH 67/91] refactor: remove unused batched field and simplify transform update The batched field on RenderElement was written by BatcherManager but never effectively consumed - 2D overrides hardcoded true, 3D path skipped via isInstanced. Remove the field, _batchedTransformShaderData cache, and consolidate transform update methods into two clear paths: _updateTransformShaderData (3D) and _updateWorldSpaceTransformShaderData (2D). --- packages/core/src/2d/sprite/SpriteMask.ts | 4 +- packages/core/src/2d/sprite/SpriteRenderer.ts | 4 +- packages/core/src/2d/text/TextRenderer.ts | 4 +- .../core/src/RenderPipeline/BatcherManager.ts | 3 - .../core/src/RenderPipeline/RenderElement.ts | 1 - .../core/src/RenderPipeline/RenderQueue.ts | 14 +---- packages/core/src/Renderer.ts | 62 +++++++++---------- .../core/src/particle/ParticleRenderer.ts | 4 +- packages/ui/src/component/UIRenderer.ts | 4 +- 9 files changed, 42 insertions(+), 58 deletions(-) diff --git a/packages/core/src/2d/sprite/SpriteMask.ts b/packages/core/src/2d/sprite/SpriteMask.ts index 87886df3c7..2a56f3dc05 100644 --- a/packages/core/src/2d/sprite/SpriteMask.ts +++ b/packages/core/src/2d/sprite/SpriteMask.ts @@ -185,9 +185,9 @@ export class SpriteMask extends Renderer implements ISpriteRenderer { /** * @internal */ - override _updateTransformShaderData(context: RenderContext, onlyMVP: boolean, batched: boolean): void { + override _updateTransformShaderData(context: RenderContext, onlyMVP: boolean): void { //@todo: Always update world positions to buffer, should opt - super._updateTransformShaderData(context, onlyMVP, true); + this._updateWorldSpaceTransformShaderData(context, onlyMVP); } /** diff --git a/packages/core/src/2d/sprite/SpriteRenderer.ts b/packages/core/src/2d/sprite/SpriteRenderer.ts index f1f2d96586..c35fb02228 100644 --- a/packages/core/src/2d/sprite/SpriteRenderer.ts +++ b/packages/core/src/2d/sprite/SpriteRenderer.ts @@ -278,9 +278,9 @@ export class SpriteRenderer extends Renderer implements ISpriteRenderer { /** * @internal */ - override _updateTransformShaderData(context: RenderContext, onlyMVP: boolean, batched: boolean): void { + override _updateTransformShaderData(context: RenderContext, onlyMVP: boolean): void { //@todo: Always update world positions to buffer, should opt - super._updateTransformShaderData(context, onlyMVP, true); + this._updateWorldSpaceTransformShaderData(context, onlyMVP); } /** diff --git a/packages/core/src/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts index 2238018a0a..22a4cec868 100644 --- a/packages/core/src/2d/text/TextRenderer.ts +++ b/packages/core/src/2d/text/TextRenderer.ts @@ -373,9 +373,9 @@ export class TextRenderer extends Renderer implements ITextRenderer { /** * @internal */ - override _updateTransformShaderData(context: RenderContext, onlyMVP: boolean, batched: boolean): void { + override _updateTransformShaderData(context: RenderContext, onlyMVP: boolean): void { //@todo: Always update world positions to buffer, should opt - super._updateTransformShaderData(context, onlyMVP, true); + this._updateWorldSpaceTransformShaderData(context, onlyMVP); } /** diff --git a/packages/core/src/RenderPipeline/BatcherManager.ts b/packages/core/src/RenderPipeline/BatcherManager.ts index fff5ee2290..4a94d82abc 100644 --- a/packages/core/src/RenderPipeline/BatcherManager.ts +++ b/packages/core/src/RenderPipeline/BatcherManager.ts @@ -64,21 +64,18 @@ export class BatcherManager { if (preElement) { if (preConstructor === constructor && preRenderer._canBatch(preElement, curElement)) { preRenderer._batch(preElement, curElement); - preElement.batched = true; } else { batchedElements.push(preElement); preElement = curElement; preRenderer = renderer; preConstructor = constructor; renderer._batch(null, curElement); - curElement.batched = false; } } else { preElement = curElement; preRenderer = renderer; preConstructor = constructor; renderer._batch(null, curElement); - curElement.batched = false; } } preElement && batchedElements.push(preElement); diff --git a/packages/core/src/RenderPipeline/RenderElement.ts b/packages/core/src/RenderPipeline/RenderElement.ts index 55176e4d58..89a67b8fd9 100644 --- a/packages/core/src/RenderPipeline/RenderElement.ts +++ b/packages/core/src/RenderPipeline/RenderElement.ts @@ -15,7 +15,6 @@ export class RenderElement implements IPoolElement { subPrimitive: SubMesh; subShader: SubShader; shaderData?: ShaderData; - batched: boolean; instancedRenderers: Renderer[] = []; // @todo: maybe should remove later diff --git a/packages/core/src/RenderPipeline/RenderQueue.ts b/packages/core/src/RenderPipeline/RenderQueue.ts index a84161ad7d..c5a3f143c6 100644 --- a/packages/core/src/RenderPipeline/RenderQueue.ts +++ b/packages/core/src/RenderPipeline/RenderQueue.ts @@ -70,18 +70,10 @@ export class RenderQueue { // Instancing: transform data is packed in UBO, skip per-renderer update if (!isInstanced) { - // @todo: Can optimize update view projection matrix updated - const batched = curElement.batched; - if ( - this.rendererUpdateFlag & ContextRendererUpdateFlag.WorldViewMatrix || - component._batchedTransformShaderData != batched - ) { - // Update world matrix and view matrix and model matrix - component._updateTransformShaderData(context, false, batched); - component._batchedTransformShaderData = batched; + if (this.rendererUpdateFlag & ContextRendererUpdateFlag.WorldViewMatrix) { + component._updateTransformShaderData(context, false); } else if (this.rendererUpdateFlag & ContextRendererUpdateFlag.ProjectionMatrix) { - // Only projection matrix need updated - component._updateTransformShaderData(context, true, batched); + component._updateTransformShaderData(context, true); } } diff --git a/packages/core/src/Renderer.ts b/packages/core/src/Renderer.ts index fbf43b67f8..5af1e71750 100644 --- a/packages/core/src/Renderer.ts +++ b/packages/core/src/Renderer.ts @@ -50,9 +50,6 @@ export class Renderer extends Component { /** @internal */ @assignmentClone _maskInteraction: SpriteMaskInteraction = SpriteMaskInteraction.None; - /** @internal */ - @ignoreClone - _batchedTransformShaderData: boolean = false; @assignmentClone _maskLayer: SpriteMaskLayer = SpriteMaskLayer.Layer0; @@ -391,12 +388,28 @@ export class Renderer extends Component { /** * @internal */ - _updateTransformShaderData(context: RenderContext, onlyMVP: boolean, batched: boolean): void { + _updateTransformShaderData(context: RenderContext, onlyMVP: boolean): void { const worldMatrix = this._transformEntity.transform.worldMatrix; + const { shaderData } = this; if (onlyMVP) { - this._updateProjectionRelatedShaderData(context, worldMatrix, batched); + const mvpMatrix = this._mvpMatrix; + Matrix.multiply(context.viewProjectionMatrix, worldMatrix, mvpMatrix); + shaderData.setMatrix(Renderer._mvpMatrixProperty, mvpMatrix); } else { - this._updateWorldViewRelatedShaderData(context, worldMatrix, batched); + const mvMatrix = this._mvMatrix; + const normalMatrix = this._normalMatrix; + + Matrix.multiply(context.viewMatrix, worldMatrix, mvMatrix); + Matrix.invert(worldMatrix, normalMatrix); + normalMatrix.transpose(); + + shaderData.setMatrix(Renderer._worldMatrixProperty, worldMatrix); + shaderData.setMatrix(Renderer._mvMatrixProperty, mvMatrix); + shaderData.setMatrix(Renderer._normalMatrixProperty, normalMatrix); + + const mvpMatrix = this._mvpMatrix; + Matrix.multiply(context.viewProjectionMatrix, worldMatrix, mvpMatrix); + shaderData.setMatrix(Renderer._mvpMatrixProperty, mvpMatrix); } } @@ -413,45 +426,28 @@ export class Renderer extends Component { _batch(preElement: RenderElement | null, curElement: RenderElement): void {} /** - * Update once per frame per renderer, not influenced by batched. + * Update once per frame per renderer. */ protected _update(context: RenderContext): void { const { layer } = this.entity; this._rendererLayer.set(layer & 65535, (layer >>> 16) & 65535, 0, 0); } - protected _updateWorldViewRelatedShaderData(context: RenderContext, worldMatrix: Matrix, batched: boolean): void { + /** + * Update transform shader data for world-space vertices (2D renderers). + * Vertices are already in world space, so model matrix is identity. + */ + protected _updateWorldSpaceTransformShaderData(context: RenderContext, onlyMVP: boolean): void { const { shaderData } = this; - if (batched) { + if (onlyMVP) { + shaderData.setMatrix(Renderer._mvpMatrixProperty, context.viewProjectionMatrix); + } else { // @ts-ignore const identityMatrix = Matrix._identity; - shaderData.setMatrix(Renderer._worldMatrixProperty, identityMatrix); shaderData.setMatrix(Renderer._mvMatrixProperty, context.viewMatrix); shaderData.setMatrix(Renderer._normalMatrixProperty, identityMatrix); - } else { - const mvMatrix = this._mvMatrix; - const normalMatrix = this._normalMatrix; - - Matrix.multiply(context.viewMatrix, worldMatrix, mvMatrix); - Matrix.invert(worldMatrix, normalMatrix); - normalMatrix.transpose(); - - shaderData.setMatrix(Renderer._worldMatrixProperty, worldMatrix); - shaderData.setMatrix(Renderer._mvMatrixProperty, mvMatrix); - shaderData.setMatrix(Renderer._normalMatrixProperty, normalMatrix); - } - - this._updateProjectionRelatedShaderData(context, worldMatrix, batched); - } - - protected _updateProjectionRelatedShaderData(context: RenderContext, worldMatrix: Matrix, batched: boolean): void { - if (batched) { - this.shaderData.setMatrix(Renderer._mvpMatrixProperty, context.viewProjectionMatrix); - } else { - const mvpMatrix = this._mvpMatrix; - Matrix.multiply(context.viewProjectionMatrix, worldMatrix, mvpMatrix); - this.shaderData.setMatrix(Renderer._mvpMatrixProperty, mvpMatrix); + shaderData.setMatrix(Renderer._mvpMatrixProperty, context.viewProjectionMatrix); } } diff --git a/packages/core/src/particle/ParticleRenderer.ts b/packages/core/src/particle/ParticleRenderer.ts index 61aa0d19db..041040a357 100644 --- a/packages/core/src/particle/ParticleRenderer.ts +++ b/packages/core/src/particle/ParticleRenderer.ts @@ -174,9 +174,9 @@ export class ParticleRenderer extends Renderer { /** * @internal */ - override _updateTransformShaderData(context: RenderContext, onlyMVP: boolean, batched: boolean): void { + override _updateTransformShaderData(context: RenderContext, onlyMVP: boolean): void { //@todo: Don't need to update transform shader data, temp solution - super._updateTransformShaderData(context, onlyMVP, true); + this._updateWorldSpaceTransformShaderData(context, onlyMVP); } protected override _updateBounds(worldBounds: BoundingBox): void { const { generator } = this; diff --git a/packages/ui/src/component/UIRenderer.ts b/packages/ui/src/component/UIRenderer.ts index a9cf0067a4..dbc7b222d0 100644 --- a/packages/ui/src/component/UIRenderer.ts +++ b/packages/ui/src/component/UIRenderer.ts @@ -123,9 +123,9 @@ export class UIRenderer extends Renderer implements IGraphics { } // @ts-ignore - override _updateTransformShaderData(context, onlyMVP: boolean, batched: boolean): void { + override _updateTransformShaderData(context, onlyMVP: boolean): void { // @ts-ignore - super._updateTransformShaderData(context, onlyMVP, true); + this._updateWorldSpaceTransformShaderData(context, onlyMVP); } // @ts-ignore From 06759e6c2c2505e9ce214aa6cd080030e77dae4a Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Thu, 16 Apr 2026 23:49:47 +0800 Subject: [PATCH 68/91] refactor: rename InstanceBatch to InstanceBuffer and InstanceLayout to InstanceBufferLayout --- packages/core/src/RenderPipeline/BatcherManager.ts | 8 ++++---- .../{InstanceBatch.ts => InstanceBuffer.ts} | 8 ++++---- packages/core/src/RenderPipeline/RenderQueue.ts | 4 ++-- packages/core/src/shader/ShaderPass.ts | 12 ++++++------ packages/core/src/shader/ShaderProgram.ts | 4 ++-- packages/core/src/shaderlib/ShaderFactory.ts | 12 ++++++------ 6 files changed, 24 insertions(+), 24 deletions(-) rename packages/core/src/RenderPipeline/{InstanceBatch.ts => InstanceBuffer.ts} (93%) diff --git a/packages/core/src/RenderPipeline/BatcherManager.ts b/packages/core/src/RenderPipeline/BatcherManager.ts index 4a94d82abc..3cfe7924b6 100644 --- a/packages/core/src/RenderPipeline/BatcherManager.ts +++ b/packages/core/src/RenderPipeline/BatcherManager.ts @@ -1,6 +1,6 @@ import { Engine } from "../Engine"; import { Renderer } from "../Renderer"; -import { InstanceBatch } from "./InstanceBatch"; +import { InstanceBuffer } from "./InstanceBuffer"; import { PrimitiveChunkManager } from "./PrimitiveChunkManager"; import { RenderQueue } from "./RenderQueue"; import { RenderElement } from "./RenderElement"; @@ -12,12 +12,12 @@ export class BatcherManager { private _primitiveChunkManager2D: PrimitiveChunkManager; private _primitiveChunkManagerMask: PrimitiveChunkManager; private _primitiveChunkManagerUI: PrimitiveChunkManager; - private _instanceBatch: InstanceBatch; + private _instanceBatch: InstanceBuffer; constructor(public engine: Engine) {} - get instanceBatch(): InstanceBatch { - return (this._instanceBatch ||= new InstanceBatch(this.engine)); + get instanceBatch(): InstanceBuffer { + return (this._instanceBatch ||= new InstanceBuffer(this.engine)); } get primitiveChunkManager2D(): PrimitiveChunkManager { diff --git a/packages/core/src/RenderPipeline/InstanceBatch.ts b/packages/core/src/RenderPipeline/InstanceBuffer.ts similarity index 93% rename from packages/core/src/RenderPipeline/InstanceBatch.ts rename to packages/core/src/RenderPipeline/InstanceBuffer.ts index f03c2ac329..f62253f78c 100644 --- a/packages/core/src/RenderPipeline/InstanceBatch.ts +++ b/packages/core/src/RenderPipeline/InstanceBuffer.ts @@ -5,19 +5,19 @@ import { BufferUsage } from "../graphic/enums/BufferUsage"; import { SetDataOptions } from "../graphic/enums/SetDataOptions"; import { Renderer } from "../Renderer"; import { ShaderMacro } from "../shader/ShaderMacro"; -import { InstanceLayout } from "../shaderlib/ShaderFactory"; +import { InstanceBufferLayout } from "../shaderlib/ShaderFactory"; /** * @internal * Manages a UBO for GPU instancing, packing per-instance renderer data (ModelMat, Layer, etc.). */ -export class InstanceBatch { +export class InstanceBuffer { static gpuInstanceMacro = ShaderMacro.getByName("RENDERER_GPU_INSTANCE"); buffer: Buffer; private _engine: Engine; - private _layout: InstanceLayout; + private _layout: InstanceBufferLayout; private _data: ArrayBuffer; private _floatView: Float32Array; private _intView: Int32Array; @@ -29,7 +29,7 @@ export class InstanceBatch { /** * Set UBO layout and allocate buffer if needed. */ - setLayout(layout: InstanceLayout): void { + setLayout(layout: InstanceBufferLayout): void { this._layout = layout; const totalBytes = layout.instanceMaxCount * layout.structSize; // Only reallocate when buffer is too small diff --git a/packages/core/src/RenderPipeline/RenderQueue.ts b/packages/core/src/RenderPipeline/RenderQueue.ts index c5a3f143c6..f391e936d8 100644 --- a/packages/core/src/RenderPipeline/RenderQueue.ts +++ b/packages/core/src/RenderPipeline/RenderQueue.ts @@ -5,7 +5,7 @@ import { RenderQueueType, Shader } from "../shader"; import { ConstantBufferBindingPoint } from "../shader/enums/ConstantBufferBindingPoint"; import { ShaderMacroCollection } from "../shader/ShaderMacroCollection"; import { BatcherManager } from "./BatcherManager"; -import { InstanceBatch } from "./InstanceBatch"; +import { InstanceBuffer } from "./InstanceBuffer"; import { ContextRendererUpdateFlag, RenderContext } from "./RenderContext"; import { RenderElement } from "./RenderElement"; import { RenderQueueMaskType } from "./enums/RenderQueueMaskType"; @@ -105,7 +105,7 @@ export class RenderQueue { ShaderMacroCollection.unionCollection(compileMacros, engine._macroCollection, compileMacros); if (isInstanced) { - compileMacros.enable(InstanceBatch.gpuInstanceMacro); + compileMacros.enable(InstanceBuffer.gpuInstanceMacro); } for (let j = 0, m = shaderPasses.length; j < m; j++) { diff --git a/packages/core/src/shader/ShaderPass.ts b/packages/core/src/shader/ShaderPass.ts index 4540f0d276..c385c4cf13 100644 --- a/packages/core/src/shader/ShaderPass.ts +++ b/packages/core/src/shader/ShaderPass.ts @@ -1,9 +1,9 @@ import type { ShaderInstruction } from "@galacean/engine-design"; import { Engine } from "../Engine"; -import { InstanceBatch } from "../RenderPipeline/InstanceBatch"; +import { InstanceBuffer } from "../RenderPipeline/InstanceBuffer"; import { PipelineStage } from "../RenderPipeline/enums/PipelineStage"; import { GLCapabilityType } from "../base/Constant"; -import { ShaderFactory, InstanceLayout } from "../shaderlib/ShaderFactory"; +import { ShaderFactory, InstanceBufferLayout } from "../shaderlib/ShaderFactory"; import { ShaderMacro } from "./ShaderMacro"; import { ShaderMacroCollection } from "./ShaderMacroCollection"; import { ShaderPart } from "./ShaderPart"; @@ -172,7 +172,7 @@ export class ShaderPass extends ShaderPart { } private _compileShaderProgram(engine: Engine, macroCollection: ShaderMacroCollection): ShaderProgram { - const isGPUInstance = macroCollection.isEnable(InstanceBatch.gpuInstanceMacro); + const isGPUInstance = macroCollection.isEnable(InstanceBuffer.gpuInstanceMacro); const { vertexSource, fragmentSource, instanceLayout } = this._platformTarget != undefined ? this._compileShaderLabSource(engine, macroCollection, isGPUInstance) @@ -187,7 +187,7 @@ export class ShaderPass extends ShaderPart { engine: Engine, macroCollection: ShaderMacroCollection, isGPUInstance: boolean - ): { vertexSource: string; fragmentSource: string; instanceLayout: InstanceLayout | null } { + ): { vertexSource: string; fragmentSource: string; instanceLayout: InstanceBufferLayout | null } { return ShaderFactory.compilePlatformSource( engine, macroCollection, @@ -201,7 +201,7 @@ export class ShaderPass extends ShaderPart { engine: Engine, macroCollection: ShaderMacroCollection, isGPUInstance: boolean - ): { vertexSource: string; fragmentSource: string; instanceLayout: InstanceLayout | null } { + ): { vertexSource: string; fragmentSource: string; instanceLayout: InstanceBufferLayout | null } { const rhi = engine._hardwareRenderer; const isWebGL2 = rhi.isWebGL2; const shaderMacroList = ShaderPass._shaderMacroList; @@ -223,7 +223,7 @@ export class ShaderPass extends ShaderPart { let vertexSource = ShaderMacroProcessor.evaluate(this._vertexShaderInstructions, macroMap); let fragmentSource = ShaderMacroProcessor.evaluate(this._fragmentShaderInstructions, macroMap); - let instanceLayout: InstanceLayout | null = null; + let instanceLayout: InstanceBufferLayout | null = null; if (isGPUInstance) { const injected = ShaderFactory.injectInstanceUBO(engine, vertexSource, fragmentSource); vertexSource = injected.vertexSource; diff --git a/packages/core/src/shader/ShaderProgram.ts b/packages/core/src/shader/ShaderProgram.ts index 80a9342340..9548548db6 100644 --- a/packages/core/src/shader/ShaderProgram.ts +++ b/packages/core/src/shader/ShaderProgram.ts @@ -9,7 +9,7 @@ import { ShaderUniform } from "./ShaderUniform"; import { ShaderUniformBlock } from "./ShaderUniformBlock"; import { ShaderBlockProperty } from "./ShaderBlockProperty"; import { ShaderDataGroup } from "./enums/ShaderDataGroup"; -import { InstanceLayout, ShaderFactory } from "../shaderlib/ShaderFactory"; +import { InstanceBufferLayout, ShaderFactory } from "../shaderlib/ShaderFactory"; /** * Shader program, corresponding to the GPU shader program. @@ -55,7 +55,7 @@ export class ShaderProgram { _uploadMaterialId: number = -1; /** @internal */ - _instanceLayout: InstanceLayout | null = null; + _instanceLayout: InstanceBufferLayout | null = null; attributeLocation: Record = Object.create(null); uniformBlockIds: number[] = []; diff --git a/packages/core/src/shaderlib/ShaderFactory.ts b/packages/core/src/shaderlib/ShaderFactory.ts index f64467bc3e..907d918320 100644 --- a/packages/core/src/shaderlib/ShaderFactory.ts +++ b/packages/core/src/shaderlib/ShaderFactory.ts @@ -142,7 +142,7 @@ export class ShaderFactory { vertexSource: string, fragmentSource: string, isGPUInstance: boolean - ): { vertexSource: string; fragmentSource: string; instanceLayout: InstanceLayout | null } { + ): { vertexSource: string; fragmentSource: string; instanceLayout: InstanceBufferLayout | null } { const rhi = engine._hardwareRenderer; const isWebGL2 = rhi.isWebGL2; const shaderMacroList = new Array(); @@ -162,7 +162,7 @@ export class ShaderFactory { noIncludeVertex = macroStr + noIncludeVertex; noIncludeFrag = macroStr + noIncludeFrag; - let instanceLayout: InstanceLayout | null = null; + let instanceLayout: InstanceBufferLayout | null = null; if (isGPUInstance) { const injected = ShaderFactory.injectInstanceUBO(engine, noIncludeVertex, noIncludeFrag); noIncludeVertex = injected.vertexSource; @@ -193,7 +193,7 @@ export class ShaderFactory { engine: Engine, vertexSource: string, fragmentSource: string - ): { vertexSource: string; fragmentSource: string; instanceLayout: InstanceLayout | null } { + ): { vertexSource: string; fragmentSource: string; instanceLayout: InstanceBufferLayout | null } { // 1. Scan & strip renderer uniforms from both stages, collect into fieldMap const fieldMap: Record = Object.create(null); vertexSource = ShaderFactory._scanInstanceUniforms(vertexSource, fieldMap); @@ -311,7 +311,7 @@ export class ShaderFactory { }); } - private static _buildLayout(engine: Engine, fieldMap: Record): InstanceLayout { + private static _buildLayout(engine: Engine, fieldMap: Record): InstanceBufferLayout { const maxUBOSize = engine._hardwareRenderer.maxUniformBlockSize; const std140Map = ShaderFactory._std140TypeInfoMap; const instanceFields: InstanceFieldInfo[] = []; @@ -358,7 +358,7 @@ export class ShaderFactory { return { instanceFields, instanceMaxCount, structSize }; } - private static _buildUBODeclaration(layout: InstanceLayout): string { + private static _buildUBODeclaration(layout: InstanceBufferLayout): string { const { instanceFields, instanceMaxCount } = layout; const structLines: string[] = []; for (let i = 0; i < instanceFields.length; i++) { @@ -430,7 +430,7 @@ export interface InstanceFieldInfo { /** * @internal */ -export interface InstanceLayout { +export interface InstanceBufferLayout { instanceFields: InstanceFieldInfo[]; instanceMaxCount: number; structSize: number; From a367d125efce7a0aabba107f045eff3cdd2287d8 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sun, 19 Apr 2026 00:14:24 +0800 Subject: [PATCH 69/91] refactor: rename BatchUtils to VertexMergeBatcher and clean up RenderQueue - BatchUtils -> VertexMergeBatcher (file and class); batchFor2D -> batch - BatcherManager.instanceBatch -> instanceBuffer (align with class name) - canBatchSprite: reorder conditions for better short-circuit - RenderQueue: hoist needMaskType out of loop, add phase comments --- packages/core/src/2d/sprite/SpriteMask.ts | 6 ++-- packages/core/src/2d/sprite/SpriteRenderer.ts | 6 ++-- packages/core/src/2d/text/TextRenderer.ts | 6 ++-- .../core/src/RenderPipeline/BatcherManager.ts | 12 +++---- .../core/src/RenderPipeline/RenderQueue.ts | 33 +++++++++++-------- .../{BatchUtils.ts => VertexMergeBatcher.ts} | 23 +++++-------- packages/core/src/RenderPipeline/index.ts | 2 +- 7 files changed, 45 insertions(+), 43 deletions(-) rename packages/core/src/RenderPipeline/{BatchUtils.ts => VertexMergeBatcher.ts} (75%) diff --git a/packages/core/src/2d/sprite/SpriteMask.ts b/packages/core/src/2d/sprite/SpriteMask.ts index 2a56f3dc05..1f740081f9 100644 --- a/packages/core/src/2d/sprite/SpriteMask.ts +++ b/packages/core/src/2d/sprite/SpriteMask.ts @@ -1,6 +1,6 @@ import { BoundingBox } from "@galacean/engine-math"; import { Entity } from "../../Entity"; -import { BatchUtils } from "../../RenderPipeline/BatchUtils"; +import { VertexMergeBatcher } from "../../RenderPipeline/VertexMergeBatcher"; import { PrimitiveChunkManager } from "../../RenderPipeline/PrimitiveChunkManager"; import { RenderContext } from "../../RenderPipeline/RenderContext"; import { SubPrimitiveChunk } from "../../RenderPipeline/SubPrimitiveChunk"; @@ -202,14 +202,14 @@ export class SpriteMask extends Renderer implements ISpriteRenderer { * @internal */ override _canBatch(preElement: RenderElement, curElement: RenderElement): boolean { - return BatchUtils.canBatchSpriteMask(preElement, curElement); + return VertexMergeBatcher.canBatchSpriteMask(preElement, curElement); } /** * @internal */ override _batch(preElement: RenderElement | null, curElement: RenderElement): void { - BatchUtils.batchFor2D(preElement, curElement); + VertexMergeBatcher.batch(preElement, curElement); } /** diff --git a/packages/core/src/2d/sprite/SpriteRenderer.ts b/packages/core/src/2d/sprite/SpriteRenderer.ts index c35fb02228..2a6986af6a 100644 --- a/packages/core/src/2d/sprite/SpriteRenderer.ts +++ b/packages/core/src/2d/sprite/SpriteRenderer.ts @@ -1,6 +1,6 @@ import { BoundingBox, Color, MathUtil } from "@galacean/engine-math"; import { Entity } from "../../Entity"; -import { BatchUtils } from "../../RenderPipeline/BatchUtils"; +import { VertexMergeBatcher } from "../../RenderPipeline/VertexMergeBatcher"; import { PrimitiveChunkManager } from "../../RenderPipeline/PrimitiveChunkManager"; import { RenderContext } from "../../RenderPipeline/RenderContext"; import { SubPrimitiveChunk } from "../../RenderPipeline/SubPrimitiveChunk"; @@ -296,14 +296,14 @@ export class SpriteRenderer extends Renderer implements ISpriteRenderer { * @internal */ override _canBatch(preElement: RenderElement, curElement: RenderElement): boolean { - return BatchUtils.canBatchSprite(preElement, curElement); + return VertexMergeBatcher.canBatchSprite(preElement, curElement); } /** * @internal */ override _batch(preElement: RenderElement | null, curElement: RenderElement): void { - BatchUtils.batchFor2D(preElement, curElement); + VertexMergeBatcher.batch(preElement, curElement); } /** diff --git a/packages/core/src/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts index 22a4cec868..3649180c79 100644 --- a/packages/core/src/2d/text/TextRenderer.ts +++ b/packages/core/src/2d/text/TextRenderer.ts @@ -1,7 +1,7 @@ import { BoundingBox, Color, Vector3 } from "@galacean/engine-math"; import { Engine } from "../../Engine"; import { Entity } from "../../Entity"; -import { BatchUtils } from "../../RenderPipeline/BatchUtils"; +import { VertexMergeBatcher } from "../../RenderPipeline/VertexMergeBatcher"; import { PrimitiveChunkManager } from "../../RenderPipeline/PrimitiveChunkManager"; import { RenderContext } from "../../RenderPipeline/RenderContext"; import { SubPrimitiveChunk } from "../../RenderPipeline/SubPrimitiveChunk"; @@ -382,14 +382,14 @@ export class TextRenderer extends Renderer implements ITextRenderer { * @internal */ override _canBatch(preElement: RenderElement, curElement: RenderElement): boolean { - return BatchUtils.canBatchSprite(preElement, curElement); + return VertexMergeBatcher.canBatchSprite(preElement, curElement); } /** * @internal */ override _batch(preElement: RenderElement | null, curElement: RenderElement): void { - BatchUtils.batchFor2D(preElement, curElement); + VertexMergeBatcher.batch(preElement, curElement); } /** diff --git a/packages/core/src/RenderPipeline/BatcherManager.ts b/packages/core/src/RenderPipeline/BatcherManager.ts index 3cfe7924b6..05ec1b7e89 100644 --- a/packages/core/src/RenderPipeline/BatcherManager.ts +++ b/packages/core/src/RenderPipeline/BatcherManager.ts @@ -12,12 +12,12 @@ export class BatcherManager { private _primitiveChunkManager2D: PrimitiveChunkManager; private _primitiveChunkManagerMask: PrimitiveChunkManager; private _primitiveChunkManagerUI: PrimitiveChunkManager; - private _instanceBatch: InstanceBuffer; + private _instanceBuffer: InstanceBuffer; constructor(public engine: Engine) {} - get instanceBatch(): InstanceBuffer { - return (this._instanceBatch ||= new InstanceBuffer(this.engine)); + get instanceBuffer(): InstanceBuffer { + return (this._instanceBuffer ||= new InstanceBuffer(this.engine)); } get primitiveChunkManager2D(): PrimitiveChunkManager { @@ -45,9 +45,9 @@ export class BatcherManager { this._primitiveChunkManagerUI.destroy(); this._primitiveChunkManagerUI = null; } - if (this._instanceBatch) { - this._instanceBatch.destroy(); - this._instanceBatch = null; + if (this._instanceBuffer) { + this._instanceBuffer.destroy(); + this._instanceBuffer = null; } } diff --git a/packages/core/src/RenderPipeline/RenderQueue.ts b/packages/core/src/RenderPipeline/RenderQueue.ts index f391e936d8..2290f33a6d 100644 --- a/packages/core/src/RenderPipeline/RenderQueue.ts +++ b/packages/core/src/RenderPipeline/RenderQueue.ts @@ -62,13 +62,15 @@ export class RenderQueue { const rhi = engine._hardwareRenderer; const pipelineStageKey = RenderContext.pipelineStageKey; const renderQueueType = this.renderQueueType; + const needMaskType = maskType !== RenderQueueMaskType.No; for (let i = 0; i < length; i++) { const curElement = batchedElements[i]; - const { component, material, instancedRenderers } = curElement; - const isInstanced = instancedRenderers.length > 0; + const { component, material } = curElement; + const isInstanced = curElement.instancedRenderers.length > 0; - // Instancing: transform data is packed in UBO, skip per-renderer update + // Update transform shader data + // Instancing packs per-renderer transforms into the instance UBO at draw time, so skip here if (!isInstanced) { if (this.rendererUpdateFlag & ContextRendererUpdateFlag.WorldViewMatrix) { component._updateTransformShaderData(context, false); @@ -77,9 +79,9 @@ export class RenderQueue { } } + // Resolve mask render states const maskInteraction = component._maskInteraction; const needMaskInteraction = maskInteraction !== SpriteMaskInteraction.None; - const needMaskType = maskType !== RenderQueueMaskType.No; let customStates: RenderStateElementMap = null; if (needMaskType) { @@ -94,7 +96,7 @@ export class RenderQueue { maskManager.isStencilWritten(material) && (maskManager.hasStencilWritten = true); } - const { primitive, shaderData: renderElementShaderData } = curElement; + const { shaderData: renderElementShaderData } = curElement; const shaderPasses = curElement.subShader.passes; const { shaderData: rendererData, instanceId: rendererId } = component; const { shaderData: materialData, instanceId: materialId, renderStates } = material; @@ -114,6 +116,7 @@ export class RenderQueue { continue; } + // Pick render state and filter by queue type let renderState = shaderPass._renderState; if (needMaskType) { // Mask don't care render queue type @@ -141,6 +144,7 @@ export class RenderQueue { const switchProgram = program.bind(); const switchRenderCount = renderCount !== program._uploadRenderCount; + // Upload uniforms (cache-aware per block) if (switchRenderCount) { program.groupingOtherUniformBlock(); program.uploadAll(program.sceneUniformBlock, sceneData); @@ -153,7 +157,7 @@ export class RenderQueue { } program.uploadAll(program.materialUniformBlock, materialData); renderElementShaderData && program.uploadAll(program.renderElementUniformBlock, renderElementShaderData); - // UnGroup textures should upload default value, texture uint maybe change by logic of texture bind. + // UnGroup textures should upload default value, texture uint maybe change by logic of texture bind program.uploadUnGroupTextures(); program._uploadSceneId = sceneId; program._uploadCameraId = cameraId; @@ -192,12 +196,13 @@ export class RenderQueue { renderElementShaderData && program.uploadAll(program.renderElementUniformBlock, renderElementShaderData); - // We only consider switchProgram case, because UnGroup texture's value is always default. + // We only consider switchProgram case, because UnGroup texture's value is always default if (switchProgram) { program.uploadUnGroupTextures(); } } + // Apply render state renderState._applyStates( engine, component._isFrontFaceInvert(), @@ -206,23 +211,25 @@ export class RenderQueue { customStates ); + // Draw const layout = program._instanceLayout; if (isInstanced && layout) { + const { primitive, subPrimitive, instancedRenderers } = curElement; const totalCount = instancedRenderers.length; const maxCount = layout.instanceMaxCount; - const instanceBatch = engine._batcherManager.instanceBatch; + const instanceBuffer = engine._batcherManager.instanceBuffer; - instanceBatch.setLayout(layout); - rhi.bindUniformBufferBase(ConstantBufferBindingPoint.RendererInstance, instanceBatch.buffer._platformBuffer); + instanceBuffer.setLayout(layout); + rhi.bindUniformBufferBase(ConstantBufferBindingPoint.RendererInstance, instanceBuffer.buffer._platformBuffer); for (let start = 0; start < totalCount; start += maxCount) { const count = Math.min(maxCount, totalCount - start); - instanceBatch.upload(instancedRenderers, start, count); + instanceBuffer.upload(instancedRenderers, start, count); primitive.instanceCount = count; - rhi.drawPrimitive(primitive, curElement.subPrimitive, program); + rhi.drawPrimitive(primitive, subPrimitive, program); } primitive.instanceCount = 0; } else { - rhi.drawPrimitive(primitive, curElement.subPrimitive, program); + rhi.drawPrimitive(curElement.primitive, curElement.subPrimitive, program); } } } diff --git a/packages/core/src/RenderPipeline/BatchUtils.ts b/packages/core/src/RenderPipeline/VertexMergeBatcher.ts similarity index 75% rename from packages/core/src/RenderPipeline/BatchUtils.ts rename to packages/core/src/RenderPipeline/VertexMergeBatcher.ts index bedf157077..c337ec21eb 100644 --- a/packages/core/src/RenderPipeline/BatchUtils.ts +++ b/packages/core/src/RenderPipeline/VertexMergeBatcher.ts @@ -5,27 +5,22 @@ import { RenderElement } from "./RenderElement"; /** * @internal */ -export class BatchUtils { +export class VertexMergeBatcher { protected static _disableBatchTag: ShaderTagKey = ShaderTagKey.getByName("spriteDisableBatching"); static canBatchSprite(preElement: RenderElement, curElement: RenderElement): boolean { - if (curElement.subShader.passes[0].getTagValue(BatchUtils._disableBatchTag) === true) { - return false; - } - if (preElement.subChunk.chunk !== curElement.subChunk.chunk) { - return false; - } - const preRenderer = preElement.component; const renderer = curElement.component; - const maskInteractionA = preRenderer.maskInteraction; + const maskInteraction = preRenderer.maskInteraction; - // Compare mask, texture and material + // Order: cheap reference checks → mask state → tag lookup (rare opt-out) return ( - maskInteractionA === renderer.maskInteraction && - (maskInteractionA === SpriteMaskInteraction.None || preRenderer.maskLayer === renderer.maskLayer) && + preElement.subChunk.chunk === curElement.subChunk.chunk && preElement.texture === curElement.texture && - preElement.material === curElement.material + preElement.material === curElement.material && + maskInteraction === renderer.maskInteraction && + (maskInteraction === SpriteMaskInteraction.None || preRenderer.maskLayer === renderer.maskLayer) && + curElement.subShader.passes[0].getTagValue(VertexMergeBatcher._disableBatchTag) !== true ); } @@ -44,7 +39,7 @@ export class BatchUtils { ); } - static batchFor2D(preElement: RenderElement | null, curElement: RenderElement): void { + static batch(preElement: RenderElement | null, curElement: RenderElement): void { const subChunk = curElement.subChunk; const { chunk, indices: subChunkIndices } = subChunk; diff --git a/packages/core/src/RenderPipeline/index.ts b/packages/core/src/RenderPipeline/index.ts index 7161b57757..29431e2740 100644 --- a/packages/core/src/RenderPipeline/index.ts +++ b/packages/core/src/RenderPipeline/index.ts @@ -1,5 +1,5 @@ export { BasicRenderPipeline, RenderQueueFlags } from "./BasicRenderPipeline"; -export { BatchUtils } from "./BatchUtils"; +export { VertexMergeBatcher } from "./VertexMergeBatcher"; export { Blitter } from "./Blitter"; export { RenderQueue } from "./RenderQueue"; export { PipelineStage } from "./enums/PipelineStage"; From 29567302c4d2366df826387f79f00f04d6e681f4 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Thu, 23 Apr 2026 20:37:08 +0800 Subject: [PATCH 70/91] fix(shader): scan instance uniforms with macro awareness for raw GLSL _scanInstanceUniforms regex-matches uniform declarations without understanding #ifdef blocks. For raw GLSL paths the source still contains preprocessor directives at scan time, so uniforms inside inactive branches (e.g. renderer_JointMatrix under #ifdef RENDERER_HAS_SKIN) get matched even when they won't compile. This caused "GPU Instancing does not support array uniform" errors for plain MeshRenderer batching whenever a SkinnedMeshRenderer had previously registered renderer_JointMatrix under ShaderDataGroup.Renderer. Add _scanInstanceUniformsWithMacros that walks the source line-by-line with a branch stack for #ifdef/#ifndef/#else/#endif, delegating active lines to the original scanner. compilePlatformSource passes its active macro set; the ShaderLab path keeps using the plain scanner since ShaderMacroProcessor.evaluate already expands directives there. Also change the array-uniform fallback from deletion to keeping the declaration as a regular uniform, so stray matches never directly fail shader compilation. --- packages/core/src/shaderlib/ShaderFactory.ts | 73 ++++++++++++++++++-- 1 file changed, 67 insertions(+), 6 deletions(-) diff --git a/packages/core/src/shaderlib/ShaderFactory.ts b/packages/core/src/shaderlib/ShaderFactory.ts index 907d918320..678eb1b55b 100644 --- a/packages/core/src/shaderlib/ShaderFactory.ts +++ b/packages/core/src/shaderlib/ShaderFactory.ts @@ -164,7 +164,9 @@ export class ShaderFactory { let instanceLayout: InstanceBufferLayout | null = null; if (isGPUInstance) { - const injected = ShaderFactory.injectInstanceUBO(engine, noIncludeVertex, noIncludeFrag); + const activeMacros = new Set(); + for (let i = 0, len = shaderMacroList.length; i < len; i++) activeMacros.add(shaderMacroList[i].name); + const injected = ShaderFactory.injectInstanceUBO(engine, noIncludeVertex, noIncludeFrag, activeMacros); noIncludeVertex = injected.vertexSource; noIncludeFrag = injected.fragmentSource; instanceLayout = injected.instanceLayout; @@ -192,12 +194,18 @@ export class ShaderFactory { static injectInstanceUBO( engine: Engine, vertexSource: string, - fragmentSource: string + fragmentSource: string, + activeMacros?: Set ): { vertexSource: string; fragmentSource: string; instanceLayout: InstanceBufferLayout | null } { // 1. Scan & strip renderer uniforms from both stages, collect into fieldMap const fieldMap: Record = Object.create(null); - vertexSource = ShaderFactory._scanInstanceUniforms(vertexSource, fieldMap); - fragmentSource = ShaderFactory._scanInstanceUniforms(fragmentSource, fieldMap); + if (activeMacros) { + vertexSource = ShaderFactory._scanInstanceUniformsWithMacros(vertexSource, fieldMap, activeMacros); + fragmentSource = ShaderFactory._scanInstanceUniformsWithMacros(fragmentSource, fieldMap, activeMacros); + } else { + vertexSource = ShaderFactory._scanInstanceUniforms(vertexSource, fieldMap); + fragmentSource = ShaderFactory._scanInstanceUniforms(fragmentSource, fieldMap); + } // Fast empty check without allocating an array let hasField = false; @@ -299,10 +307,10 @@ export class ShaderFactory { if (isDerived === undefined && ShaderProperty._getShaderPropertyGroup(name) !== ShaderDataGroup.Renderer) return match; if (isDerived) return ""; - // Array uniforms not supported in instancing UBO, remove to fail shader compilation + // Array uniforms not supported in instancing UBO, keep as regular uniform if (arraySize) { Logger.error(`GPU Instancing does not support array uniform "${name}${arraySize}"`); - return ""; + return match; } // ModelMat is affine, store as mat3x4 (3 columns) to save 16 bytes per instance fieldMap[ShaderProperty.getByName(name)._uniqueId] = @@ -311,6 +319,59 @@ export class ShaderFactory { }); } + // Matches preprocessor directives at line start. Only `#ifdef / #ifndef / #else / #endif` are + // supported; `#if` with expressions is not recognized (such blocks are treated as always-active). + private static readonly _ifdefRegex = /^[ \t]*#ifdef\s+(\w+)/; + private static readonly _ifndefRegex = /^[ \t]*#ifndef\s+(\w+)/; + private static readonly _elseRegex = /^[ \t]*#else\b/; + private static readonly _endifRegex = /^[ \t]*#endif\b/; + + /** + * Scan with preprocessor awareness, for raw GLSL paths where `#ifdef` blocks are not yet + * expanded. Uniforms inside inactive branches are skipped. + */ + private static _scanInstanceUniformsWithMacros( + source: string, + fieldMap: Record, + activeMacros: Set + ): string { + // Preprocessor branch stack: each frame tracks whether current branch is active + const branchStack: boolean[] = [true]; + const lines = source.split("\n"); + + for (let i = 0, n = lines.length; i < n; i++) { + const line = lines[i]; + + let m = line.match(ShaderFactory._ifdefRegex); + if (m) { + const parentActive = branchStack[branchStack.length - 1]; + branchStack.push(parentActive && activeMacros.has(m[1])); + continue; + } + m = line.match(ShaderFactory._ifndefRegex); + if (m) { + const parentActive = branchStack[branchStack.length - 1]; + branchStack.push(parentActive && !activeMacros.has(m[1])); + continue; + } + if (ShaderFactory._elseRegex.test(line)) { + const parentActive = branchStack.length >= 2 ? branchStack[branchStack.length - 2] : true; + const currentActive = branchStack[branchStack.length - 1]; + branchStack[branchStack.length - 1] = parentActive && !currentActive; + continue; + } + if (ShaderFactory._endifRegex.test(line)) { + if (branchStack.length > 1) branchStack.pop(); + continue; + } + if (!branchStack[branchStack.length - 1]) continue; + + lines[i] = ShaderFactory._scanInstanceUniforms(line, fieldMap); + } + + return lines.join("\n"); + } + private static _buildLayout(engine: Engine, fieldMap: Record): InstanceBufferLayout { const maxUBOSize = engine._hardwareRenderer.maxUniformBlockSize; const std140Map = ShaderFactory._std140TypeInfoMap; From c40602441baba2a54f94489f184cbc7f97ba52ae Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sat, 25 Apr 2026 16:39:24 +0800 Subject: [PATCH 71/91] fix(ui): update UIRenderer to use renamed VertexMergeBatcher Missed in earlier rename of BatchUtils -> VertexMergeBatcher / batchFor2D -> batch, which broke type-check for the ui package and blocked CI. --- packages/ui/src/component/UIRenderer.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/component/UIRenderer.ts b/packages/ui/src/component/UIRenderer.ts index dbc7b222d0..82af6480bd 100644 --- a/packages/ui/src/component/UIRenderer.ts +++ b/packages/ui/src/component/UIRenderer.ts @@ -1,5 +1,5 @@ import { - BatchUtils, + VertexMergeBatcher, Color, DependentMode, Entity, @@ -114,12 +114,12 @@ export class UIRenderer extends Renderer implements IGraphics { // @ts-ignore override _canBatch(preElement, curElement): boolean { - return BatchUtils.canBatchSprite(preElement, curElement); + return VertexMergeBatcher.canBatchSprite(preElement, curElement); } // @ts-ignore override _batch(preElement, curElement): void { - BatchUtils.batchFor2D(preElement, curElement); + VertexMergeBatcher.batch(preElement, curElement); } // @ts-ignore From d4fb81f3ae47018416a9f5795e1479ff75c0a643 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sun, 26 Apr 2026 15:57:08 +0800 Subject: [PATCH 72/91] perf(ui): canvas-internal batching with visual-layering driven sort MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds UIBatchSorter that runs inside each canvas to cluster sub-elements by (depth, material, texture, hierarchy) before they enter the main render queue. Combined with VertexMergeBatcher, multi-material UI buttons collapse from N draw calls to ~M (M = visual layer count), dramatically improving fps on dense layouts (6000 buttons: 40fps → 67fps). - UIBatchSorter: spatial-hash accelerated BatchSorting algorithm; cell size derives from canvas referenceResolution to stay optimal across designs. - RenderElement.subDistancePriority: tiebreaker so canvas leaders keep their relative order when interleaved with 3D under unstable quicksort. - RenderElement._isBatched: protects already-batched leaders from main-pipeline _batch(null, leader) re-init that would corrupt subMesh.start/count. - Image/Text always populate renderElement.subShader at production (was conditional on overlay mode); needed because canvas-internal batching now runs before pushRenderElement which used to backfill it. - Tests: 12 unit cases for sort correctness; e2e + example for batch demo. --- e2e/case/ui-batch-order.ts | 84 ++++++++ e2e/config.ts | 8 + e2e/package.json | 1 + examples/src/ui-batch-massive.ts | 130 +++++++++++++ .../src/RenderPipeline/BasicRenderPipeline.ts | 2 +- .../core/src/RenderPipeline/BatcherManager.ts | 17 +- .../core/src/RenderPipeline/RenderElement.ts | 5 + .../core/src/RenderPipeline/RenderQueue.ts | 6 +- .../core/src/RenderPipeline/UIBatchSorter.ts | 150 +++++++++++++++ .../src/RenderPipeline/VertexMergeBatcher.ts | 19 +- packages/core/src/RenderPipeline/index.ts | 1 + packages/core/src/ui/IUICanvas.ts | 1 + packages/core/src/ui/UIUtils.ts | 2 +- packages/ui/src/component/UICanvas.ts | 15 +- packages/ui/src/component/advanced/Image.ts | 4 +- packages/ui/src/component/advanced/Text.ts | 6 +- pnpm-lock.yaml | 3 + tests/src/core/UIBatchSorter.test.ts | 180 ++++++++++++++++++ 18 files changed, 606 insertions(+), 28 deletions(-) create mode 100644 e2e/case/ui-batch-order.ts create mode 100644 examples/src/ui-batch-massive.ts create mode 100644 packages/core/src/RenderPipeline/UIBatchSorter.ts create mode 100644 tests/src/core/UIBatchSorter.test.ts diff --git a/e2e/case/ui-batch-order.ts b/e2e/case/ui-batch-order.ts new file mode 100644 index 0000000000..65da16fcb9 --- /dev/null +++ b/e2e/case/ui-batch-order.ts @@ -0,0 +1,84 @@ +/** + * @title UI Batch Order + * @category UI + * @description Verifies that canvas-internal batching preserves hierarchy order: + * each "button" stacks (bg, text) where text must render on top of bg. + * Multiple buttons of the same materials should batch together without breaking + * the per-button visual stacking. Regression for the canvas-vs-3D sort bug. + */ +import { Camera, Color, Sprite, Texture2D, TextureFormat, WebGLEngine } from "@galacean/engine"; +import { CanvasRenderMode, Image, registerGUI, UICanvas, UITransform } from "@galacean/engine-ui"; +import { initScreenshot, updateForE2E } from "./.mockForE2E"; + +registerGUI(); + +WebGLEngine.create({ canvas: "canvas" }).then((engine) => { + engine.canvas.resizeByClientSize(2); + + const scene = engine.sceneManager.activeScene; + const root = scene.createRootEntity("root"); + + // Camera + const cameraEntity = root.createChild("camera"); + const camera = cameraEntity.addComponent(Camera); + + // Canvas (overlay) + const canvasEntity = root.createChild("canvas"); + const canvas = canvasEntity.addComponent(UICanvas); + canvas.renderMode = CanvasRenderMode.ScreenSpaceOverlay; + + // Two solid-color textures → distinct materials, so bg/text are batch-incompatible + const bgTex = createSolidTexture(engine, 64, 64, [255, 80, 80, 255]); // red + const textTex = createSolidTexture(engine, 64, 64, [80, 255, 80, 255]); // green + const bgSprite = new Sprite(engine, bgTex); + const textSprite = new Sprite(engine, textTex); + + // Build a 4×3 grid of buttons; each button = bg (red) + text (green) overlapping. + // Hierarchy creation order: bg0, text0, bg1, text1, ... — text must paint over bg. + // If sort breaks per-button order, green will be hidden under red somewhere. + const cols = 4; + const rows = 3; + const cellW = 180; + const cellH = 100; + const startX = -((cols - 1) * cellW) / 2; + const startY = -((rows - 1) * cellH) / 2; + + for (let r = 0; r < rows; r++) { + for (let c = 0; c < cols; c++) { + const x = startX + c * cellW; + const y = startY + r * cellH; + + const bgEntity = canvasEntity.createChild(`bg-${r}-${c}`); + const bgTransform = bgEntity.transform as UITransform; + bgTransform.position.set(x, y, 0); + bgTransform.size.set(140, 60); + const bgImage = bgEntity.addComponent(Image); + bgImage.sprite = bgSprite; + bgImage.color = new Color(1, 1, 1, 1); + + const textEntity = canvasEntity.createChild(`text-${r}-${c}`); + const textTransform = textEntity.transform as UITransform; + textTransform.position.set(x, y, 0); + textTransform.size.set(80, 30); + const textImage = textEntity.addComponent(Image); + textImage.sprite = textSprite; + textImage.color = new Color(1, 1, 1, 1); + } + } + + updateForE2E(engine); + initScreenshot(engine, camera); +}); + +function createSolidTexture(engine: WebGLEngine, w: number, h: number, rgba: number[]): Texture2D { + const tex = new Texture2D(engine, w, h, TextureFormat.R8G8B8A8, false); + const data = new Uint8Array(w * h * 4); + for (let i = 0; i < w * h; i++) { + data[i * 4] = rgba[0]; + data[i * 4 + 1] = rgba[1]; + data[i * 4 + 2] = rgba[2]; + data[i * 4 + 3] = rgba[3]; + } + tex.setPixelBuffer(data); + return tex; +} diff --git a/e2e/config.ts b/e2e/config.ts index 101ba33ce4..123334b438 100644 --- a/e2e/config.ts +++ b/e2e/config.ts @@ -540,5 +540,13 @@ export const E2E_CONFIG = { threshold: 0, diffPercentage: 0.044 } + }, + UI: { + batchOrder: { + category: "UI", + caseFileName: "ui-batch-order", + threshold: 0, + diffPercentage: 0.01 + } } }; diff --git a/e2e/package.json b/e2e/package.json index 3b4e784534..903c5271bd 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -20,6 +20,7 @@ "@galacean/engine-rhi-webgl": "workspace:*", "@galacean/engine-physics-lite": "workspace:*", "@galacean/engine-physics-physx": "workspace:*", + "@galacean/engine-ui": "workspace:*", "dat.gui": "^0.7.9", "vite": "3.1.6", "sass": "^1.55.0" diff --git a/examples/src/ui-batch-massive.ts b/examples/src/ui-batch-massive.ts new file mode 100644 index 0000000000..4ec8c9040b --- /dev/null +++ b/examples/src/ui-batch-massive.ts @@ -0,0 +1,130 @@ +/** + * @title UI Batch Massive Buttons + * @category UI + * @thumbnail https://mdn.alipayobjects.com/merchant_appfe/afts/img/A*jjZMTrp-vU8AAAAAAAAAAAAADiR2AQ/original + * + * Stress test on dev/2.0: 96 buttons (192 sub-elements) on a single canvas. + * Each button = bg sprite + icon sprite (different sprites, must overlap correctly). + * Watch the Stats panel — DrawCall reflects current batching strategy. + */ +import { Stats } from "@galacean/engine-toolkit-stats"; +import { Camera, Color, Sprite, Texture2D, TextureFormat, WebGLEngine } from "@galacean/engine"; +import { + CanvasRenderMode, + Image, + registerGUI, + ResolutionAdaptationMode, + UICanvas, + UITransform +} from "@galacean/engine-ui"; + +registerGUI(); + +WebGLEngine.create({ canvas: "canvas" }).then((engine) => { + engine.canvas.resizeByClientSize(); + + const scene = engine.sceneManager.activeScene; + const root = scene.createRootEntity("root"); + + // Camera + const cameraEntity = root.createChild("camera"); + cameraEntity.transform.setPosition(0, 0, 50); + const camera = cameraEntity.addComponent(Camera); + cameraEntity.addComponent(Stats); + + // Canvas + const canvasEntity = root.createChild("canvas"); + const canvas = canvasEntity.addComponent(UICanvas); + canvas.renderMode = CanvasRenderMode.ScreenSpaceCamera; + canvas.renderCamera = camera; + canvas.referenceResolution.set(1280, 720); + canvas.resolutionAdaptationMode = ResolutionAdaptationMode.BothAdaptation; + + const bgSprite = new Sprite(engine, makeRoundedTexture(engine, [50, 90, 180, 255])); + const iconSprite = new Sprite(engine, makeStarTexture(engine, [255, 200, 60, 255])); + + // 100×60 = 6000 buttons → 12000 sub-elements + const cols = 100; + const rows = 60; + const buttonW = 10; + const buttonH = 8; + const gapX = 12; + const gapY = 10; + const startX = -((cols - 1) * gapX) / 2; + const startY = -((rows - 1) * gapY) / 2; + + for (let r = 0; r < rows; r++) { + for (let c = 0; c < cols; c++) { + const x = startX + c * gapX; + const y = startY + r * gapY; + + const bg = canvasEntity.createChild(`bg-${r}-${c}`); + bg.transform.setPosition(x, y, 0); + const bgT = bg.transform as UITransform; + bgT.size.set(buttonW, buttonH); + const bgImg = bg.addComponent(Image); + bgImg.sprite = bgSprite; + bgImg.color = new Color(1, 1, 1, 1); + + const icon = canvasEntity.createChild(`icon-${r}-${c}`); + icon.transform.setPosition(x, y, 0); + const iconT = icon.transform as UITransform; + iconT.size.set(buttonW * 0.5, buttonH * 0.5); + const iconImg = icon.addComponent(Image); + iconImg.sprite = iconSprite; + iconImg.color = new Color(1, 1, 1, 1); + } + } + + engine.run(); +}); + +// Rounded panel: filled rect with darker border (mimics 9-slice button background) +function makeRoundedTexture(engine: WebGLEngine, rgba: number[]): Texture2D { + const size = 32; + const tex = new Texture2D(engine, size, size, TextureFormat.R8G8B8A8, false); + const data = new Uint8Array(size * size * 4); + const border = 3; + for (let y = 0; y < size; y++) { + for (let x = 0; x < size; x++) { + const i = (y * size + x) * 4; + const isBorder = x < border || x >= size - border || y < border || y >= size - border; + const tint = isBorder ? 0.6 : 1.0; + data[i] = rgba[0] * tint; + data[i + 1] = rgba[1] * tint; + data[i + 2] = rgba[2] * tint; + data[i + 3] = rgba[3]; + } + } + tex.setPixelBuffer(data); + return tex; +} + +// 5-pointed star silhouette on transparent background (mimics atlas icon) +function makeStarTexture(engine: WebGLEngine, rgba: number[]): Texture2D { + const size = 32; + const tex = new Texture2D(engine, size, size, TextureFormat.R8G8B8A8, false); + const data = new Uint8Array(size * size * 4); + const cx = size / 2; + const cy = size / 2; + const outer = size * 0.45; + const inner = outer * 0.4; + for (let y = 0; y < size; y++) { + for (let x = 0; x < size; x++) { + const i = (y * size + x) * 4; + const dx = x + 0.5 - cx; + const dy = y + 0.5 - cy; + const angle = Math.atan2(dy, dx); + const dist = Math.hypot(dx, dy); + const t = (Math.cos(5 * angle - Math.PI / 2) + 1) * 0.5; + const radius = inner + (outer - inner) * t; + const inside = dist <= radius; + data[i] = inside ? rgba[0] : 0; + data[i + 1] = inside ? rgba[1] : 0; + data[i + 2] = inside ? rgba[2] : 0; + data[i + 3] = inside ? rgba[3] : 0; + } + } + tex.setPixelBuffer(data); + return tex; +} diff --git a/packages/core/src/RenderPipeline/BasicRenderPipeline.ts b/packages/core/src/RenderPipeline/BasicRenderPipeline.ts index 84f5dcb7c7..b5ce144243 100644 --- a/packages/core/src/RenderPipeline/BasicRenderPipeline.ts +++ b/packages/core/src/RenderPipeline/BasicRenderPipeline.ts @@ -509,7 +509,7 @@ export class BasicRenderPipeline { continue; } canvas._prepareRender(context); - const canvasElements = canvas._renderElements; + const canvasElements = canvas._batchedRenderElements; for (let j = 0, m = canvasElements.length; j < m; j++) { this.pushRenderElement(context, canvasElements[j]); } diff --git a/packages/core/src/RenderPipeline/BatcherManager.ts b/packages/core/src/RenderPipeline/BatcherManager.ts index 05ec1b7e89..6f733eb0ad 100644 --- a/packages/core/src/RenderPipeline/BatcherManager.ts +++ b/packages/core/src/RenderPipeline/BatcherManager.ts @@ -2,7 +2,6 @@ import { Engine } from "../Engine"; import { Renderer } from "../Renderer"; import { InstanceBuffer } from "./InstanceBuffer"; import { PrimitiveChunkManager } from "./PrimitiveChunkManager"; -import { RenderQueue } from "./RenderQueue"; import { RenderElement } from "./RenderElement"; /** @@ -51,21 +50,20 @@ export class BatcherManager { } } - batch(renderQueue: RenderQueue): void { - const { elements, batchedElements } = renderQueue; - + batch(input: RenderElement[], output: RenderElement[]): void { let preElement: RenderElement; let preRenderer: Renderer; let preConstructor: Function; - for (let i = 0, n = elements.length; i < n; ++i) { - const curElement = elements[i]; + for (let i = 0, n = input.length; i < n; ++i) { + const curElement = input[i]; const renderer = curElement.component; const constructor = renderer.constructor; if (preElement) { if (preConstructor === constructor && preRenderer._canBatch(preElement, curElement)) { preRenderer._batch(preElement, curElement); } else { - batchedElements.push(preElement); + preElement._isBatched = true; + output.push(preElement); preElement = curElement; preRenderer = renderer; preConstructor = constructor; @@ -78,7 +76,10 @@ export class BatcherManager { renderer._batch(null, curElement); } } - preElement && batchedElements.push(preElement); + if (preElement) { + preElement._isBatched = true; + output.push(preElement); + } } uploadBuffer() { diff --git a/packages/core/src/RenderPipeline/RenderElement.ts b/packages/core/src/RenderPipeline/RenderElement.ts index 89a67b8fd9..184e1decaa 100644 --- a/packages/core/src/RenderPipeline/RenderElement.ts +++ b/packages/core/src/RenderPipeline/RenderElement.ts @@ -16,6 +16,9 @@ export class RenderElement implements IPoolElement { subShader: SubShader; shaderData?: ShaderData; instancedRenderers: Renderer[] = []; + subDistancePriority: number = 0; + /** @internal Set when canvas-internal batching wrote to the chunk this frame; protects leader from main-pipeline _batch(null, leader) re-init within the same frame */ + _isBatched: boolean = false; // @todo: maybe should remove later texture?: Texture2D; @@ -36,6 +39,8 @@ export class RenderElement implements IPoolElement { this.texture = texture; this.subChunk = subChunk; this.instancedRenderers.length = 0; + this.subDistancePriority = 0; + this._isBatched = false; } dispose(): void { diff --git a/packages/core/src/RenderPipeline/RenderQueue.ts b/packages/core/src/RenderPipeline/RenderQueue.ts index 2290f33a6d..36531a035b 100644 --- a/packages/core/src/RenderPipeline/RenderQueue.ts +++ b/packages/core/src/RenderPipeline/RenderQueue.ts @@ -23,7 +23,9 @@ export class RenderQueue { } static compareForTransparent(a: RenderElement, b: RenderElement): number { - return a.priority - b.priority || b.distanceForSort - a.distanceForSort; + return ( + a.priority - b.priority || b.distanceForSort - a.distanceForSort || a.subDistancePriority - b.subDistancePriority + ); } readonly elements = new Array(); @@ -42,7 +44,7 @@ export class RenderQueue { } batch(batcherManager: BatcherManager): void { - batcherManager.batch(this); + batcherManager.batch(this.elements, this.batchedElements); } render( diff --git a/packages/core/src/RenderPipeline/UIBatchSorter.ts b/packages/core/src/RenderPipeline/UIBatchSorter.ts new file mode 100644 index 0000000000..26f6da8f92 --- /dev/null +++ b/packages/core/src/RenderPipeline/UIBatchSorter.ts @@ -0,0 +1,150 @@ +import { BoundingBox, Matrix } from "@galacean/engine-math"; +import { Utils } from "../Utils"; +import { RenderElement } from "./RenderElement"; + +/** + * @internal + * Visual-layering driven UI batching: solve visual correctness first, batching falls out for free. + * + * Think of it as stacking elements onto numbered shelves (depths): + * - Non-overlapping elements share shelf 0 — free to cluster regardless of material. + * - Overlapping + batch-compatible — share the same shelf, still cluster. + * - Overlapping + batch-incompatible — bumped one shelf higher (drawn on top). + * + * `depth` is a global tag, not a spatial group: distant elements at the same visual layer + * share the same depth and get clustered together. Once depth is assigned, sort by + * (depth, material, texture, hierarchyIndex) — visual order is locked, batching is the + * by-product of materials clustering within each depth band. + * + * Overlap detection is accelerated by a 10×10 spatial grid (bucket size = canvas longest + * edge / 10), pruning O(N²) checks to ~O(N). + */ +export class UIBatchSorter { + // Spatial-hash is fastest when cell size ≈ typical element size (one element per cell). + // UI elements typically span ~10% of the canvas → 10×10 grid. + private static readonly _gridDim = 10; + + private static readonly _entries = new Array(); + private static readonly _grid: GridCell[][] = []; + // Interleaved (x, y) cell coords touched in the previous frame + private static readonly _dirtyCells = new Array(); + private static readonly _localBoundsPool = new Array(); + private static readonly _worldToLocal = new Matrix(); + + /** + * Reorders `elements` in place for optimal batching. + * @param elements - in hierarchy order (depth-first traversal); mutated in place + * @param canvasWorldMatrix - reduces world bounds to canvas-local for precise overlap + * @param canvasLongestEdge - canvas-local longest edge in pixels + */ + static sort(elements: RenderElement[], canvasWorldMatrix: Matrix, canvasLongestEdge: number): void { + const count = elements.length; + if (count <= 1) return; + + const gridDim = this._gridDim; + const gridSize = canvasLongestEdge / gridDim; + const entries = this._entries; + const grid = this._grid; + const dirtyCells = this._dirtyCells; + const localBoundsPool = this._localBoundsPool; + const worldToLocal = this._worldToLocal; + Matrix.invert(canvasWorldMatrix, worldToLocal); + while (entries.length < count) entries.push(new SortEntry()); + + // Allocate grid once, reuse forever (each cell must be a real [], not a sparse hole) + if (grid.length < gridDim) { + while (grid.length < gridDim) grid.push([]); + for (let i = 0; i < gridDim; i++) { + const row = grid[i]; + while (row.length < gridDim) row.push([]); + } + } + + // Clear only previously-used cells (no full sweep) + for (let k = 0, n = dirtyCells.length; k < n; k += 2) { + grid[dirtyCells[k]][dirtyCells[k + 1]].length = 0; + } + dirtyCells.length = 0; + + for (let i = 0; i < count; i++) { + const element = elements[i]; + const localBounds = (localBoundsPool[i] ||= new BoundingBox()); + BoundingBox.transform(element.component.bounds, worldToLocal, localBounds); + const materialId = element.material.instanceId; + const textureId = element.texture ? element.texture.instanceId : 0; + + // Clamp to grid range; out-of-bounds elements collapse to edge cells + const minCellX = Math.max(0, Math.floor(localBounds.min.x / gridSize)); + const maxCellX = Math.min(gridDim - 1, Math.floor(localBounds.max.x / gridSize)); + const minCellY = Math.max(0, Math.floor(localBounds.min.y / gridSize)); + const maxCellY = Math.min(gridDim - 1, Math.floor(localBounds.max.y / gridSize)); + + // Find the deepest overlapping prior, and whether that shelf has any incompatible occupant + let maxDepth = -1; + let maxDepthIncompatible = false; + for (let cellY = minCellY; cellY <= maxCellY; cellY++) { + for (let cellX = minCellX; cellX <= maxCellX; cellX++) { + const cell = grid[cellX][cellY]; + for (let j = 0, m = cell.length; j < m; j++) { + const other = cell[j]; + if (!UIBatchSorter._rectOverlap(localBounds, other.bounds)) continue; + const otherDepth = other.depth; + const otherIncompatible = other.materialId !== materialId || other.textureId !== textureId; + if (otherDepth > maxDepth) { + maxDepth = otherDepth; + maxDepthIncompatible = otherIncompatible; + } else if (otherDepth === maxDepth && otherIncompatible) { + maxDepthIncompatible = true; + } + } + } + } + + const entry = entries[i]; + entry.element = element; + entry.hierarchyIndex = i; + // Share the deepest shelf if compatible; bump up one if blocked by an incompatible occupant + entry.depth = maxDepth < 0 ? 0 : maxDepth + (maxDepthIncompatible ? 1 : 0); + entry.materialId = materialId; + entry.textureId = textureId; + entry.bounds = localBounds; + + for (let cellY = minCellY; cellY <= maxCellY; cellY++) { + for (let cellX = minCellX; cellX <= maxCellX; cellX++) { + const cell = grid[cellX][cellY]; + if (cell.length === 0) dirtyCells.push(cellX, cellY); + cell.push(entry); + } + } + } + + Utils._quickSort(entries, 0, count, UIBatchSorter._compareEntries); + for (let i = 0; i < count; i++) elements[i] = entries[i].element; + } + + private static _rectOverlap(a: BoundingBox, b: BoundingBox): boolean { + return a.min.x < b.max.x && a.max.x > b.min.x && a.min.y < b.max.y && a.max.y > b.min.y; + } + + private static _compareEntries(a: SortEntry, b: SortEntry): number { + return ( + a.depth - b.depth || + a.materialId - b.materialId || + a.textureId - b.textureId || + a.hierarchyIndex - b.hierarchyIndex + ); + } +} + +type GridCell = SortEntry[]; + +// materialId/textureId/bounds are flattened from RenderElement to avoid +// multi-hop property access in the hot inner loop and the sort comparator. +class SortEntry { + element: RenderElement; + hierarchyIndex = 0; + depth = 0; + materialId = 0; + textureId = 0; + bounds: BoundingBox; +} diff --git a/packages/core/src/RenderPipeline/VertexMergeBatcher.ts b/packages/core/src/RenderPipeline/VertexMergeBatcher.ts index c337ec21eb..ad03379888 100644 --- a/packages/core/src/RenderPipeline/VertexMergeBatcher.ts +++ b/packages/core/src/RenderPipeline/VertexMergeBatcher.ts @@ -41,24 +41,27 @@ export class VertexMergeBatcher { static batch(preElement: RenderElement | null, curElement: RenderElement): void { const subChunk = curElement.subChunk; - const { chunk, indices: subChunkIndices } = subChunk; + const { chunk, indices: localIndices } = subChunk; - const length = subChunkIndices.length; - let startIndex = chunk.updateIndexLength; + const length = localIndices.length; if (preElement) { preElement.subChunk.subMesh.count += length; + } else if (curElement._isBatched) { + // Already wrote to chunk this frame (canvas-internal batching) — main pipeline must not re-init + return; } else { - // Reset subMesh - const subMesh = subChunk.subMesh; - subMesh.start = startIndex; - subMesh.count = length; + // First write this frame — init subMesh range + subChunk.subMesh.start = chunk.updateIndexLength; + subChunk.subMesh.count = length; } + let startIndex = chunk.updateIndexLength; const { start, size } = subChunk.vertexArea; + // PrimitiveChunk vertex layout: pos(3) + uv(2) + color(4) = 9 floats per vertex const vertexOffset = start / 9; const indices = chunk.indices; for (let i = 0; i < length; ++i) { - indices[startIndex++] = vertexOffset + subChunkIndices[i]; + indices[startIndex++] = vertexOffset + localIndices[i]; } chunk.updateIndexLength += length; chunk.updateVertexStart = Math.min(chunk.updateVertexStart, start); diff --git a/packages/core/src/RenderPipeline/index.ts b/packages/core/src/RenderPipeline/index.ts index 29431e2740..ddfa25bf59 100644 --- a/packages/core/src/RenderPipeline/index.ts +++ b/packages/core/src/RenderPipeline/index.ts @@ -1,5 +1,6 @@ export { BasicRenderPipeline, RenderQueueFlags } from "./BasicRenderPipeline"; export { VertexMergeBatcher } from "./VertexMergeBatcher"; +export { UIBatchSorter } from "./UIBatchSorter"; export { Blitter } from "./Blitter"; export { RenderQueue } from "./RenderQueue"; export { PipelineStage } from "./enums/PipelineStage"; diff --git a/packages/core/src/ui/IUICanvas.ts b/packages/core/src/ui/IUICanvas.ts index e90e209aa2..444242657c 100644 --- a/packages/core/src/ui/IUICanvas.ts +++ b/packages/core/src/ui/IUICanvas.ts @@ -11,6 +11,7 @@ export interface IUICanvas { sortOrder: number; _canvasIndex: number; _renderElements: RenderElement[]; + _batchedRenderElements: RenderElement[]; _canRender(camera: Camera): boolean; _prepareRender(renderContext: RenderContext): void; } diff --git a/packages/core/src/ui/UIUtils.ts b/packages/core/src/ui/UIUtils.ts index a6f7822a03..a61a7d54d3 100644 --- a/packages/core/src/ui/UIUtils.ts +++ b/packages/core/src/ui/UIUtils.ts @@ -44,7 +44,7 @@ export class UIUtils { renderContext.applyVirtualCamera(virtualCamera, false); uiRenderQueue.rendererUpdateFlag |= ContextRendererUpdateFlag.ProjectionMatrix; uiCanvas._prepareRender(renderContext); - const curElements = uiCanvas._renderElements; + const curElements = uiCanvas._batchedRenderElements; for (let j = 0, m = curElements.length; j < m; j++) { uiRenderQueue.pushRenderElement(curElements[j]); } diff --git a/packages/ui/src/component/UICanvas.ts b/packages/ui/src/component/UICanvas.ts index f4bf699135..3a11eaefb1 100644 --- a/packages/ui/src/component/UICanvas.ts +++ b/packages/ui/src/component/UICanvas.ts @@ -17,7 +17,8 @@ import { deepClone, dependentComponents, ignoreClone, - CloneUtils + // @ts-ignore — internal API + UIBatchSorter } from "@galacean/engine"; import { Utils } from "../Utils"; import { CanvasRenderMode } from "../enums/CanvasRenderMode"; @@ -65,6 +66,9 @@ export class UICanvas extends Component implements IElement { _renderElements: any[] = []; /** @internal */ @ignoreClone + _batchedRenderElements: any[] = []; + /** @internal */ + @ignoreClone _sortDistance: number = 0; /** @internal */ @ignoreClone @@ -341,6 +345,15 @@ export class UICanvas extends Component implements IElement { renderer._prepareRender(context); renderer._renderFrameCount = frameCount; } + + const batchedRenderElements = this._batchedRenderElements; + batchedRenderElements.length = 0; + const { x: refX, y: refY } = this._referenceResolution; + UIBatchSorter.sort(renderElements, this.entity.transform.worldMatrix, Math.max(refX, refY)); + (engine as any)._batcherManager.batch(renderElements, batchedRenderElements); + for (let i = 0, n = batchedRenderElements.length; i < n; i++) { + batchedRenderElements[i].subDistancePriority = i; + } } /** diff --git a/packages/ui/src/component/advanced/Image.ts b/packages/ui/src/component/advanced/Image.ts index 2a965f6db2..b364a2e303 100644 --- a/packages/ui/src/component/advanced/Image.ts +++ b/packages/ui/src/component/advanced/Image.ts @@ -239,9 +239,7 @@ export class Image extends UIRenderer implements ISpriteRenderer { const renderElement = engine._renderElementPool.get(); const subChunk = this._subChunk; renderElement.set(this, material, subChunk.chunk.primitive, subChunk.subMesh, this.sprite.texture, subChunk); - if (canvas._realRenderMode === CanvasRenderMode.ScreenSpaceOverlay) { - renderElement.subShader = material.shader.subShaders[0]; - } + renderElement.subShader = material.shader.subShaders[0]; renderElement.priority = canvas.sortOrder; renderElement.distanceForSort = canvas._sortDistance; canvas._renderElements.push(renderElement); diff --git a/packages/ui/src/component/advanced/Text.ts b/packages/ui/src/component/advanced/Text.ts index 6f20830716..f54654c95c 100644 --- a/packages/ui/src/component/advanced/Text.ts +++ b/packages/ui/src/component/advanced/Text.ts @@ -355,7 +355,7 @@ export class Text extends UIRenderer implements ITextRenderer { const priority = canvas.sortOrder; const distanceForSort = canvas._sortDistance; const textChunks = this._textChunks; - const isOverlay = canvas._realRenderMode === CanvasRenderMode.ScreenSpaceOverlay; + const subShader = material.shader.subShaders[0]; for (let i = 0, n = textChunks.length; i < n; ++i) { const { subChunk, texture } = textChunks[i]; const renderElement = textRenderElementPool.get(); @@ -363,9 +363,7 @@ export class Text extends UIRenderer implements ITextRenderer { // @ts-ignore renderElement.shaderData ||= new ShaderData(ShaderDataGroup.RenderElement); renderElement.shaderData.setTexture(Text._textTextureProperty, texture); - if (isOverlay) { - renderElement.subShader = material.shader.subShaders[0]; - } + renderElement.subShader = subShader; renderElement.priority = priority; renderElement.distanceForSort = distanceForSort; renderElements.push(renderElement); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f6dbf1d371..5a94b7b3cf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -146,6 +146,9 @@ importers: '@galacean/engine-toolkit': specifier: ^1.3.9 version: 1.3.9(@galacean/engine@packages+galacean) + '@galacean/engine-ui': + specifier: workspace:* + version: link:../packages/ui dat.gui: specifier: ^0.7.9 version: 0.7.9 diff --git a/tests/src/core/UIBatchSorter.test.ts b/tests/src/core/UIBatchSorter.test.ts new file mode 100644 index 0000000000..d2703dec9e --- /dev/null +++ b/tests/src/core/UIBatchSorter.test.ts @@ -0,0 +1,180 @@ +import { UIBatchSorter } from "@galacean/engine-core"; +import { BoundingBox, Matrix, Quaternion, Vector3 } from "@galacean/engine-math"; +import { describe, expect, it } from "vitest"; + +function rect(minX: number, minY: number, maxX: number, maxY: number): BoundingBox { + return new BoundingBox(new Vector3(minX, minY, 0), new Vector3(maxX, maxY, 0)); +} + +function fakeElement(materialId: number, textureId: number | null, bounds: BoundingBox): any { + return { + component: { bounds }, + material: { instanceId: materialId }, + texture: textureId === null ? null : { instanceId: textureId } + }; +} + +const identity = new Matrix(); + +function countBatches(elements: any[]): number { + if (elements.length === 0) return 0; + let batches = 1; + for (let i = 1; i < elements.length; i++) { + const a = elements[i - 1]; + const b = elements[i]; + const at = a.texture ? a.texture.instanceId : 0; + const bt = b.texture ? b.texture.instanceId : 0; + if (a.material.instanceId !== b.material.instanceId || at !== bt) batches++; + } + return batches; +} + +describe("UIBatchSorter", () => { + it("empty input is a no-op", () => { + const elements: any[] = []; + expect(() => UIBatchSorter.sort(elements, identity, 200)).to.not.throw(); + expect(elements.length).to.equal(0); + }); + + it("single element is unchanged", () => { + const elements = [fakeElement(1, 1, rect(0, 0, 10, 10))]; + UIBatchSorter.sort(elements, identity, 200); + expect(elements.length).to.equal(1); + expect(elements[0].material.instanceId).to.equal(1); + }); + + it("non-overlapping interleaved buttons cluster by material", () => { + // 4 (bg, txt) buttons, non-overlapping → optimal: 2 batches (all bg, all txt) + const elements = [ + fakeElement(1, 1, rect(0, 0, 100, 100)), + fakeElement(2, 2, rect(10, 10, 90, 50)), + fakeElement(1, 1, rect(200, 0, 300, 100)), + fakeElement(2, 2, rect(210, 10, 290, 50)), + fakeElement(1, 1, rect(0, 200, 100, 300)), + fakeElement(2, 2, rect(10, 210, 90, 250)), + fakeElement(1, 1, rect(200, 200, 300, 300)), + fakeElement(2, 2, rect(210, 210, 290, 250)) + ]; + UIBatchSorter.sort(elements, identity, 200); + expect(countBatches(elements)).to.equal(2); + }); + + it("overlapping batch-compatible elements stay in one batch", () => { + const elements = [ + fakeElement(1, 1, rect(0, 0, 100, 100)), + fakeElement(1, 1, rect(50, 50, 150, 150)), + fakeElement(1, 1, rect(80, 80, 180, 180)) + ]; + UIBatchSorter.sort(elements, identity, 200); + expect(countBatches(elements)).to.equal(1); + }); + + it("identical-material overlap preserves hierarchy order", () => { + const e0 = fakeElement(1, 1, rect(0, 0, 100, 100)); + const e1 = fakeElement(1, 1, rect(0, 0, 100, 100)); + const e2 = fakeElement(1, 1, rect(0, 0, 100, 100)); + const elements = [e0, e1, e2]; + UIBatchSorter.sort(elements, identity, 200); + expect(elements).to.deep.equal([e0, e1, e2]); + }); + + it("incompatible overlap forces strict hierarchy order", () => { + const e0 = fakeElement(1, 1, rect(0, 0, 100, 100)); + const e1 = fakeElement(2, 2, rect(0, 0, 100, 100)); + const e2 = fakeElement(3, 3, rect(0, 0, 100, 100)); + const elements = [e0, e1, e2]; + UIBatchSorter.sort(elements, identity, 200); + expect(elements).to.deep.equal([e0, e1, e2]); + }); + + it("transitive overlap (A-B, B-C, A∩C=∅) keeps hierarchy", () => { + const e0 = fakeElement(1, 1, rect(0, 0, 100, 100)); + const e1 = fakeElement(2, 2, rect(50, 0, 150, 100)); + const e2 = fakeElement(3, 3, rect(120, 0, 220, 100)); + const elements = [e0, e1, e2]; + UIBatchSorter.sort(elements, identity, 200); + expect(elements).to.deep.equal([e0, e1, e2]); + expect(countBatches(elements)).to.equal(3); + }); + + it("isolated clusters batch independently", () => { + // (A,B) overlap at left, (C,D) overlap at right; A,C share material; B,D share material + const elements = [ + fakeElement(1, 1, rect(0, 0, 100, 100)), + fakeElement(2, 2, rect(0, 0, 100, 100)), + fakeElement(1, 1, rect(200, 0, 300, 100)), + fakeElement(2, 2, rect(200, 0, 300, 100)) + ]; + UIBatchSorter.sort(elements, identity, 200); + expect(countBatches(elements)).to.equal(2); + }); + + it("rect spanning multiple grid cells still detects overlap", () => { + // canvasLongestEdge=200 → gridSize=20; A (400×400) spans 20×20 cells + const elements = [fakeElement(1, 1, rect(0, 0, 400, 400)), fakeElement(2, 2, rect(200, 200, 300, 300))]; + UIBatchSorter.sort(elements, identity, 200); + expect(elements[0].material.instanceId).to.equal(1); + expect(elements[1].material.instanceId).to.equal(2); + }); + + it("complex 2x3 button grid yields 2 batches", () => { + const elements: any[] = []; + for (let r = 0; r < 2; r++) { + for (let c = 0; c < 3; c++) { + const x = c * 200; + const y = r * 200; + elements.push(fakeElement(1, 1, rect(x, y, x + 100, y + 100))); + elements.push(fakeElement(2, 2, rect(x + 10, y + 10, x + 90, y + 50))); + } + } + UIBatchSorter.sort(elements, identity, 200); + expect(countBatches(elements)).to.equal(2); + }); + + it("rotated canvas: world AABBs reduce to canvas-local for precise overlap", () => { + // Inflated world AABBs of a rotated canvas would falsely overlap; canvas-local recovers truth + const cosA = Math.cos(Math.PI / 6); + const sinA = Math.sin(Math.PI / 6); + const worldRectFromLocal = (lx0: number, ly0: number, lx1: number, ly1: number): BoundingBox => { + const xs = [lx0, lx1, lx0, lx1]; + const ys = [ly0, ly0, ly1, ly1]; + let minX = Infinity, + maxX = -Infinity, + minY = Infinity, + maxY = -Infinity; + for (let k = 0; k < 4; k++) { + const wx = xs[k] * cosA - ys[k] * sinA; + const wy = xs[k] * sinA + ys[k] * cosA; + if (wx < minX) minX = wx; + if (wx > maxX) maxX = wx; + if (wy < minY) minY = wy; + if (wy > maxY) maxY = wy; + } + return new BoundingBox(new Vector3(minX, minY, 0), new Vector3(maxX, maxY, 0)); + }; + + const elements: any[] = []; + for (let r = 0; r < 2; r++) { + for (let c = 0; c < 3; c++) { + const x = c * 200; + const y = r * 200; + elements.push(fakeElement(1, 1, worldRectFromLocal(x, y, x + 100, y + 100))); + elements.push(fakeElement(2, 2, worldRectFromLocal(x + 10, y + 10, x + 90, y + 50))); + } + } + + const canvasWorldMatrix = new Matrix(); + const q = new Quaternion(); + Quaternion.rotationAxisAngle(new Vector3(0, 0, 1), Math.PI / 6, q); + Matrix.affineTransformation(new Vector3(1, 1, 1), q, new Vector3(0, 0, 0), canvasWorldMatrix); + + UIBatchSorter.sort(elements, canvasWorldMatrix, 200); + expect(countBatches(elements)).to.equal(2); + }); + + it("missing texture treated as id 0", () => { + const elements = [fakeElement(1, null, rect(0, 0, 50, 50)), fakeElement(1, null, rect(100, 0, 150, 50))]; + expect(() => UIBatchSorter.sort(elements, identity, 200)).to.not.throw(); + expect(countBatches(elements)).to.equal(1); + }); +}); From e30dd3e815313b913ed61f84b711853f3ab668e7 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sun, 26 Apr 2026 16:04:25 +0800 Subject: [PATCH 73/91] docs(e2e): clarify ui-batch-order is a regression guard The case verifies the canvas hierarchy-order bug stays fixed: when SubRenderElement was flattened, canvas sub-elements could be shuffled in the main transparent queue under equal (priority, distance). Fix is in place (canvas-internal batching + subDistancePriority); this e2e prevents silent regression. --- e2e/case/ui-batch-order.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/e2e/case/ui-batch-order.ts b/e2e/case/ui-batch-order.ts index 65da16fcb9..999ad9c042 100644 --- a/e2e/case/ui-batch-order.ts +++ b/e2e/case/ui-batch-order.ts @@ -1,10 +1,13 @@ /** * @title UI Batch Order * @category UI - * @description Verifies that canvas-internal batching preserves hierarchy order: - * each "button" stacks (bg, text) where text must render on top of bg. - * Multiple buttons of the same materials should batch together without breaking - * the per-button visual stacking. Regression for the canvas-vs-3D sort bug. + * @description Regression guard for the canvas hierarchy-order bug introduced when + * SubRenderElement was flattened into RenderElement: canvas sub-elements entered + * the main transparent queue directly and could be shuffled by unstable quicksort + * under equal (priority, distance), making `text` render below `bg` on some buttons. + * Fixed by canvas-internal batching + subDistancePriority tiebreaker. + * This case lays out a 4×3 grid of (bg + text) buttons; if hierarchy order ever + * breaks again, the green text would be hidden behind red bg in the screenshot. */ import { Camera, Color, Sprite, Texture2D, TextureFormat, WebGLEngine } from "@galacean/engine"; import { CanvasRenderMode, Image, registerGUI, UICanvas, UITransform } from "@galacean/engine-ui"; From 72e079e731ac518d0cdbf39865f19b2ebe0ff1de Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sun, 26 Apr 2026 16:35:52 +0800 Subject: [PATCH 74/91] test(e2e): update particle shape-transform baseline for gpu-instancing pipeline The render pipeline rewrite for GPU instancing on feat/gpu-instancing produces slightly different particle output than dev/2.0 (semi-transparent particles are sensitive to draw-order microchanges). Visual is correct; baseline regenerated to match the new pipeline output. Tolerance stays at 0.334%. --- .../originImage/Particle_particleRenderer-shape-transform.jpg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/e2e/fixtures/originImage/Particle_particleRenderer-shape-transform.jpg b/e2e/fixtures/originImage/Particle_particleRenderer-shape-transform.jpg index 6fb96ec8f6..64e2946e1d 100644 --- a/e2e/fixtures/originImage/Particle_particleRenderer-shape-transform.jpg +++ b/e2e/fixtures/originImage/Particle_particleRenderer-shape-transform.jpg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aa7a4bc8c57d966ad7ca49f6ea4f7177bb5d4e9c4c666f3c92ecc600c08b28f5 -size 23645 +oid sha256:9c71ce04939b22f17b03033d4b3601e0170b57bb7f3a6ac6887b42237be975e8 +size 22930 From fb9561e41de703de00129a94ec393952c9a47c84 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sun, 26 Apr 2026 16:47:32 +0800 Subject: [PATCH 75/91] test(e2e): switch ui-batch-order to ScreenSpaceCamera and add baseline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ScreenSpaceOverlay UI renders in a separate overlay pass that bypasses camera.render(); initScreenshot's off-screen render-target therefore couldn't capture it (downloaded image was empty grey). Switch the case to ScreenSpaceCamera so the canvas goes through the main camera path and the screenshot captures the full 4×3 button grid. With the new pipeline + canvas-internal batching, the layout is now deterministic across runs, so diffPercentage is set to 0. --- e2e/case/ui-batch-order.ts | 11 ++++++++--- e2e/config.ts | 2 +- e2e/fixtures/originImage/UI_ui-batch-order.jpg | 3 +++ 3 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 e2e/fixtures/originImage/UI_ui-batch-order.jpg diff --git a/e2e/case/ui-batch-order.ts b/e2e/case/ui-batch-order.ts index 999ad9c042..a0d234004f 100644 --- a/e2e/case/ui-batch-order.ts +++ b/e2e/case/ui-batch-order.ts @@ -21,14 +21,19 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { const scene = engine.sceneManager.activeScene; const root = scene.createRootEntity("root"); - // Camera + // Camera (ScreenSpaceCamera mode is required: ScreenSpaceOverlay UI renders in a + // separate overlay pass that bypasses camera.render(), so initScreenshot's + // off-screen render-target won't capture it.) const cameraEntity = root.createChild("camera"); + cameraEntity.transform.setPosition(0, 0, 50); const camera = cameraEntity.addComponent(Camera); - // Canvas (overlay) + // Canvas const canvasEntity = root.createChild("canvas"); const canvas = canvasEntity.addComponent(UICanvas); - canvas.renderMode = CanvasRenderMode.ScreenSpaceOverlay; + canvas.renderMode = CanvasRenderMode.ScreenSpaceCamera; + canvas.renderCamera = camera; + canvas.referenceResolution.set(1280, 720); // Two solid-color textures → distinct materials, so bg/text are batch-incompatible const bgTex = createSolidTexture(engine, 64, 64, [255, 80, 80, 255]); // red diff --git a/e2e/config.ts b/e2e/config.ts index da0e18a0d9..4a767935c0 100644 --- a/e2e/config.ts +++ b/e2e/config.ts @@ -552,7 +552,7 @@ export const E2E_CONFIG = { category: "UI", caseFileName: "ui-batch-order", threshold: 0, - diffPercentage: 0.01 + diffPercentage: 0 } } }; diff --git a/e2e/fixtures/originImage/UI_ui-batch-order.jpg b/e2e/fixtures/originImage/UI_ui-batch-order.jpg new file mode 100644 index 0000000000..f0085f7116 --- /dev/null +++ b/e2e/fixtures/originImage/UI_ui-batch-order.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa01567d7e322a9491b0e3381c6cbce620417395c4e51ec63b022e111bc028ce +size 39022 From 25bef75d00c09ea584f5ab960e012a7b2e1ff356 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sun, 26 Apr 2026 16:55:58 +0800 Subject: [PATCH 76/91] test(examples): upgrade ui-batch-massive with atlas icons & gradient panels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Programmatic textures are now closer to real game UI: - gradient panel bg (electric-blue gradient + double-tone border) - 64×64 icon atlas with 4 icons (sword/heart/bolt/gem); buttons cycle through atlas regions via Sprite Rect Demonstrates the realistic batching scenario: many sprites sharing one atlas texture still batch into a single draw call. --- examples/src/ui-batch-massive.ts | 189 ++++++++++++++++++++++++------- 1 file changed, 149 insertions(+), 40 deletions(-) diff --git a/examples/src/ui-batch-massive.ts b/examples/src/ui-batch-massive.ts index 4ec8c9040b..5f120c4826 100644 --- a/examples/src/ui-batch-massive.ts +++ b/examples/src/ui-batch-massive.ts @@ -3,12 +3,12 @@ * @category UI * @thumbnail https://mdn.alipayobjects.com/merchant_appfe/afts/img/A*jjZMTrp-vU8AAAAAAAAAAAAADiR2AQ/original * - * Stress test on dev/2.0: 96 buttons (192 sub-elements) on a single canvas. - * Each button = bg sprite + icon sprite (different sprites, must overlap correctly). + * Stress test: 6000 buttons (12000 sub-elements) on a single canvas. + * Each button = bg sprite (gradient panel) + icon sprite (atlas-sampled). * Watch the Stats panel — DrawCall reflects current batching strategy. */ import { Stats } from "@galacean/engine-toolkit-stats"; -import { Camera, Color, Sprite, Texture2D, TextureFormat, WebGLEngine } from "@galacean/engine"; +import { Camera, Color, Sprite, Texture2D, TextureFormat, WebGLEngine, Rect } from "@galacean/engine"; import { CanvasRenderMode, Image, @@ -40,8 +40,20 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { canvas.referenceResolution.set(1280, 720); canvas.resolutionAdaptationMode = ResolutionAdaptationMode.BothAdaptation; - const bgSprite = new Sprite(engine, makeRoundedTexture(engine, [50, 90, 180, 255])); - const iconSprite = new Sprite(engine, makeStarTexture(engine, [255, 200, 60, 255])); + // Two textures: a gradient bg panel and an icon atlas (4 icons in 2×2) + // All buttons share these two textures, so all bg-sprites are batchable, and + // all icon-sprites (using different atlas regions) are also batchable — + // demonstrating cluster-by-(material, texture). + const bgSprite = new Sprite(engine, makeGradientPanel(engine)); + const iconAtlasTexture = makeIconAtlas(engine); + + // 4 sub-sprites of the same atlas, rotating per button + const iconSprites = [ + new Sprite(engine, iconAtlasTexture, new Rect(0, 0, 0.5, 0.5)), // sword + new Sprite(engine, iconAtlasTexture, new Rect(0.5, 0, 0.5, 0.5)), // heart + new Sprite(engine, iconAtlasTexture, new Rect(0, 0.5, 0.5, 0.5)), // bolt + new Sprite(engine, iconAtlasTexture, new Rect(0.5, 0.5, 0.5, 0.5)) // gem + ]; // 100×60 = 6000 buttons → 12000 sub-elements const cols = 100; @@ -69,9 +81,9 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { const icon = canvasEntity.createChild(`icon-${r}-${c}`); icon.transform.setPosition(x, y, 0); const iconT = icon.transform as UITransform; - iconT.size.set(buttonW * 0.5, buttonH * 0.5); + iconT.size.set(buttonW * 0.55, buttonH * 0.55); const iconImg = icon.addComponent(Image); - iconImg.sprite = iconSprite; + iconImg.sprite = iconSprites[(r + c) % 4]; iconImg.color = new Color(1, 1, 1, 1); } } @@ -79,52 +91,149 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { engine.run(); }); -// Rounded panel: filled rect with darker border (mimics 9-slice button background) -function makeRoundedTexture(engine: WebGLEngine, rgba: number[]): Texture2D { - const size = 32; +// Vertical gradient panel with double border (atlas-style game button background) +function makeGradientPanel(engine: WebGLEngine): Texture2D { + const size = 64; const tex = new Texture2D(engine, size, size, TextureFormat.R8G8B8A8, false); const data = new Uint8Array(size * size * 4); - const border = 3; + // Base palette: deep navy → electric blue gradient + const top = [70, 130, 240]; + const bottom = [25, 55, 130]; + const outerBorder = [12, 25, 60]; + const innerHighlight = [180, 220, 255]; + for (let y = 0; y < size; y++) { for (let x = 0; x < size; x++) { const i = (y * size + x) * 4; - const isBorder = x < border || x >= size - border || y < border || y >= size - border; - const tint = isBorder ? 0.6 : 1.0; - data[i] = rgba[0] * tint; - data[i + 1] = rgba[1] * tint; - data[i + 2] = rgba[2] * tint; - data[i + 3] = rgba[3]; + const t = y / (size - 1); + // Vertical gradient + let r = top[0] * (1 - t) + bottom[0] * t; + let g = top[1] * (1 - t) + bottom[1] * t; + let b = top[2] * (1 - t) + bottom[2] * t; + + // Outer 2px border + if (x < 2 || x >= size - 2 || y < 2 || y >= size - 2) { + r = outerBorder[0]; + g = outerBorder[1]; + b = outerBorder[2]; + } + // Inner 1px highlight on top edge + else if (y < 4 && x >= 2 && x < size - 2) { + r = (r + innerHighlight[0]) * 0.5; + g = (g + innerHighlight[1]) * 0.5; + b = (b + innerHighlight[2]) * 0.5; + } + + data[i] = r | 0; + data[i + 1] = g | 0; + data[i + 2] = b | 0; + data[i + 3] = 255; } } tex.setPixelBuffer(data); return tex; } -// 5-pointed star silhouette on transparent background (mimics atlas icon) -function makeStarTexture(engine: WebGLEngine, rgba: number[]): Texture2D { - const size = 32; - const tex = new Texture2D(engine, size, size, TextureFormat.R8G8B8A8, false); - const data = new Uint8Array(size * size * 4); - const cx = size / 2; - const cy = size / 2; - const outer = size * 0.45; - const inner = outer * 0.4; - for (let y = 0; y < size; y++) { - for (let x = 0; x < size; x++) { - const i = (y * size + x) * 4; - const dx = x + 0.5 - cx; - const dy = y + 0.5 - cy; - const angle = Math.atan2(dy, dx); - const dist = Math.hypot(dx, dy); - const t = (Math.cos(5 * angle - Math.PI / 2) + 1) * 0.5; - const radius = inner + (outer - inner) * t; - const inside = dist <= radius; - data[i] = inside ? rgba[0] : 0; - data[i + 1] = inside ? rgba[1] : 0; - data[i + 2] = inside ? rgba[2] : 0; - data[i + 3] = inside ? rgba[3] : 0; +// 64×64 atlas with 4 icons in 2×2 grid (32×32 each), transparent background +function makeIconAtlas(engine: WebGLEngine): Texture2D { + const cellSize = 32; + const atlasSize = cellSize * 2; + const tex = new Texture2D(engine, atlasSize, atlasSize, TextureFormat.R8G8B8A8, false); + const data = new Uint8Array(atlasSize * atlasSize * 4); + + const writePixel = (cx: number, cy: number, lx: number, ly: number, r: number, g: number, b: number, a: number) => { + const x = cx * cellSize + lx; + const y = cy * cellSize + ly; + if (x < 0 || x >= atlasSize || y < 0 || y >= atlasSize) return; + const i = (y * atlasSize + x) * 4; + data[i] = r; + data[i + 1] = g; + data[i + 2] = b; + data[i + 3] = a; + }; + + // Cell (0, 0): sword icon (silver blade + gold hilt) + for (let y = 0; y < cellSize; y++) { + for (let x = 0; x < cellSize; x++) { + const cx = cellSize / 2 - 0.5; + const cy = cellSize / 2; + const dx = x - cx; + const dy = y - cy; + // diagonal blade strip + const along = (dx + dy) * 0.7071; + const across = (dx - dy) * 0.7071; + const inBlade = Math.abs(across) < 2 && along > -10 && along < 8; + const inHilt = Math.abs(across) < 5 && along >= 8 && along < 11; + const inGuard = Math.abs(across) < 7 && Math.abs(along - 7.5) < 1; + if (inBlade) writePixel(0, 0, x, y, 220, 230, 240, 255); + else if (inHilt) writePixel(0, 0, x, y, 200, 150, 50, 255); + else if (inGuard) writePixel(0, 0, x, y, 150, 110, 30, 255); } } + + // Cell (1, 0): heart icon + for (let y = 0; y < cellSize; y++) { + for (let x = 0; x < cellSize; x++) { + // Heart equation: ((x²+y²-1)³ - x²y³) <= 0, with proper scaling + const nx = (x - cellSize / 2) / (cellSize / 2.5); + const ny = -(y - cellSize / 2.2) / (cellSize / 2.5); + const v = Math.pow(nx * nx + ny * ny - 1, 3) - nx * nx * Math.pow(ny, 3); + if (v <= 0) { + // Soft red gradient + const t = (ny + 0.5) * 0.5; + const r = (235 - t * 30) | 0; + const g = (50 + t * 30) | 0; + const b = (70 + t * 20) | 0; + writePixel(1, 0, x, y, r, g, b, 255); + } + } + } + + // Cell (0, 1): lightning bolt (zigzag) + const boltPath: [number, number][] = [ + [16, 4], + [10, 16], + [14, 16], + [11, 28], + [22, 14], + [17, 14], + [21, 4] + ]; + for (let y = 0; y < cellSize; y++) { + for (let x = 0; x < cellSize; x++) { + // Point-in-polygon + let inside = false; + for (let i = 0, j = boltPath.length - 1; i < boltPath.length; j = i++) { + const [xi, yi] = boltPath[i]; + const [xj, yj] = boltPath[j]; + if (yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi) inside = !inside; + } + if (inside) writePixel(0, 1, x, y, 255, 215, 60, 255); + } + } + + // Cell (1, 1): gem (diamond shape with highlight) + for (let y = 0; y < cellSize; y++) { + for (let x = 0; x < cellSize; x++) { + const cx = cellSize / 2; + const cy = cellSize / 2; + const dx = Math.abs(x - cx); + const dy = Math.abs(y - cy); + // Diamond: |dx| + |dy| < r + if (dx + dy < cellSize * 0.4) { + // Cyan with white highlight at top-left + const high = Math.max(0, 1 - ((x - cx + 6) ** 2 + (y - cy + 6) ** 2) / 50); + const baseR = 80; + const baseG = 220; + const baseB = 230; + const r = (baseR + (255 - baseR) * high) | 0; + const g = (baseG + (255 - baseG) * high) | 0; + const b = (baseB + (255 - baseB) * high) | 0; + writePixel(1, 1, x, y, r, g, b, 255); + } + } + } + tex.setPixelBuffer(data); return tex; } From 1c2b7f22a7e61958fb4cfc8a26675913f9974178 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Sun, 26 Apr 2026 17:08:25 +0800 Subject: [PATCH 77/91] test(examples): bump ui-batch-massive to 9216 buttons (18432 sub-elements) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stress level tuned to push DrawCall contrast against dev/2.0 (no batch clustering) without overwhelming CPU. UI is typically static, so no animation — keeps the test focused on batching algorithm cost. --- examples/src/ui-batch-massive.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/src/ui-batch-massive.ts b/examples/src/ui-batch-massive.ts index 5f120c4826..62d3a5c2b2 100644 --- a/examples/src/ui-batch-massive.ts +++ b/examples/src/ui-batch-massive.ts @@ -55,13 +55,13 @@ WebGLEngine.create({ canvas: "canvas" }).then((engine) => { new Sprite(engine, iconAtlasTexture, new Rect(0.5, 0.5, 0.5, 0.5)) // gem ]; - // 100×60 = 6000 buttons → 12000 sub-elements - const cols = 100; - const rows = 60; - const buttonW = 10; - const buttonH = 8; - const gapX = 12; - const gapY = 10; + // 128×72 = 9216 buttons → 18432 sub-elements + const cols = 128; + const rows = 72; + const buttonW = 7; + const buttonH = 6; + const gapX = 9; + const gapY = 8; const startX = -((cols - 1) * gapX) / 2; const startY = -((rows - 1) * gapY) / 2; From bc4493f717a005440f789d2dc56e6dceda261510 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Mon, 11 May 2026 22:28:36 +0800 Subject: [PATCH 78/91] fix(shader): drop duplicate camera_ProjectionParams from Transform.glsl Common.glsl already declares camera_ProjectionParams. Re-declaring it in Transform.glsl makes every shader that includes both fail to compile under WebGL (the entire PBR family among them). Regenerate the affected compiled .shaderc artifacts (PBR, ShadowCaster, DepthOnly). --- packages/shader/compiledShaders/PBR.shaderc | 2 +- packages/shader/compiledShaders/Pipeline/DepthOnly.shaderc | 2 +- packages/shader/compiledShaders/Pipeline/ShadowCaster.shaderc | 2 +- packages/shader/src/ShaderLibrary/Common/Transform.glsl | 1 - 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/shader/compiledShaders/PBR.shaderc b/packages/shader/compiledShaders/PBR.shaderc index 630e503b1d..409082bae0 100644 --- a/packages/shader/compiledShaders/PBR.shaderc +++ b/packages/shader/compiledShaders/PBR.shaderc @@ -1 +1 @@ -{"name":"PBR","platformTarget":0,"subShaders":[{"name":"Default","tags":{},"passes":[{"name":"Pipeline/ShadowCaster/Default/ShadowCaster","isUsePass":true,"tags":{},"renderStates":{"constantMap":{},"variableMap":{}}},{"name":"Pipeline/DepthOnly/Default/DepthOnly","isUsePass":true,"tags":{},"renderStates":{"constantMap":{},"variableMap":{}}},{"name":"Forward Pass","isUsePass":false,"tags":{"pipelineStage":"Forward"},"renderStates":{"constantMap":{},"variableMap":{"0":"blendEnabled","3":"sourceColorBlendFactor","4":"sourceAlphaBlendFactor","5":"destinationColorBlendFactor","6":"destinationAlphaBlendFactor","11":"depthWriteEnabled","25":"rasterStateCullMode","28":"renderQueueType"}},"vertexShaderInstructions":[[0,"\n"],[2,"FORWARD_PASS_PBR_INCLUDED",714],[0,"\n\n"],[7,"FORWARD_PASS_PBR_INCLUDED"],[0,"\n\n"],[4,{"t":"and","l":{"t":"and","l":{"t":"def","m":"RENDERER_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_HAS_TANGENT"}},"r":{"t":"or","l":{"t":"def","m":"MATERIAL_HAS_NORMALTEXTURE"},"r":{"t":"def","m":"MATERIAL_HAS_CLEAR_COAT_NORMAL_TEXTURE"}}},10],[0,"\n\n"],[7,"NEED_VERTEX_TANGENT"],[0,"\n\n"],[6],[0,"\n\n"],[4,{"t":"or","l":{"t":"or","l":{"t":"def","m":"MATERIAL_HAS_NORMALTEXTURE"},"r":{"t":"def","m":"MATERIAL_HAS_CLEAR_COAT_NORMAL_TEXTURE"}},"r":{"t":"def","m":"MATERIAL_ENABLE_ANISOTROPY"}},16],[0,"\n\n"],[7,"NEED_TANGENT_SPACE"],[0,"\n\n"],[6],[0,"\n\n"],[2,"COMMON_INCLUDED",46],[0,"\n\n"],[7,"COMMON_INCLUDED"],[0,"\n\n"],[8,"PI","3.14159265359"],[0,"\n\n"],[8,"RECIPROCAL_PI","0.31830988618"],[0,"\n\n"],[8,"EPSILON","1e-6"],[0,"\n\n"],[8,"LOG2","1.442695"],[0,"\n\n"],[8,"HALF_MIN","6.103515625e-5"],[0,"\n\n"],[8,"HALF_EPS","4.8828125e-4"],[0,"\n\n"],[9,"saturate",["a"],"clamp( a, 0.0, 1.0 )"],[0,"\n\n"],[1,"GRAPHICS_API_WEBGL2",40],[0,"\n\n"],[9,"INVERSE_MAT",["mat"],"inverse(mat)"],[0,"\n\n"],[5,44],[0,"\nmat2 inverseMat ( mat2 m ) { return mat2 ( m[1][1] , - m[0][1] , - m[1][0] , m[0][0] ) / ( m[0][0] * m[1][1] - m[0][1] * m[1][0] ) ; }\nmat3 inverseMat ( mat3 m ) { float a00 = m[0][0] , a01 = m[0][1] , a02 = m[0][2] ;\nfloat a10 = m[1][0] , a11 = m[1][1] , a12 = m[1][2] ;\nfloat a20 = m[2][0] , a21 = m[2][1] , a22 = m[2][2] ;\nfloat b01 = a22 * a11 - a12 * a21 ;\nfloat b11 = - a22 * a10 + a12 * a20 ;\nfloat b21 = a21 * a10 - a11 * a20 ;\nfloat det = a00 * b01 + a01 * b11 + a02 * b21 ;\nreturn mat3 ( b01 , ( - a22 * a01 + a02 * a21 ) , ( a12 * a01 - a02 * a11 ) , b11 , ( a22 * a00 - a02 * a20 ) , ( - a12 * a00 + a02 * a10 ) , b21 , ( - a21 * a00 + a01 * a20 ) , ( a11 * a00 - a01 * a10 ) ) / det ; }\nmat4 inverseMat ( mat4 m ) { float a00 = m[0][0] , a01 = m[0][1] , a02 = m[0][2] , a03 = m[0][3] , a10 = m[1][0] , a11 = m[1][1] , a12 = m[1][2] , a13 = m[1][3] , a20 = m[2][0] , a21 = m[2][1] , a22 = m[2][2] , a23 = m[2][3] , a30 = m[3][0] , a31 = m[3][1] , a32 = m[3][2] , a33 = m[3][3] , b00 = a00 * a11 - a01 * a10 , b01 = a00 * a12 - a02 * a10 , b02 = a00 * a13 - a03 * a10 , b03 = a01 * a12 - a02 * a11 , b04 = a01 * a13 - a03 * a11 , b05 = a02 * a13 - a03 * a12 , b06 = a20 * a31 - a21 * a30 , b07 = a20 * a32 - a22 * a30 , b08 = a20 * a33 - a23 * a30 , b09 = a21 * a32 - a22 * a31 , b10 = a21 * a33 - a23 * a31 , b11 = a22 * a33 - a23 * a32 , det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06 ;\nreturn mat4 ( a11 * b11 - a12 * b10 + a13 * b09 , a02 * b10 - a01 * b11 - a03 * b09 , a31 * b05 - a32 * b04 + a33 * b03 , a22 * b04 - a21 * b05 - a23 * b03 , a12 * b08 - a10 * b11 - a13 * b07 , a00 * b11 - a02 * b08 + a03 * b07 , a32 * b02 - a30 * b05 - a33 * b01 , a20 * b05 - a22 * b02 + a23 * b01 , a10 * b10 - a11 * b08 + a13 * b06 , a01 * b08 - a00 * b10 - a03 * b06 , a30 * b04 - a31 * b02 + a33 * b00 , a21 * b02 - a20 * b04 - a23 * b00 , a11 * b07 - a10 * b09 - a12 * b06 , a00 * b09 - a01 * b07 + a02 * b06 , a31 * b01 - a30 * b03 - a32 * b00 , a20 * b03 - a21 * b01 + a22 * b00 ) / det ; }\n\n"],[9,"INVERSE_MAT",["mat"],"inverseMat(mat)"],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"FOG_INCLUDED",56],[0,"\n\n"],[7,"FOG_INCLUDED"],[0,"\n\n"],[3,"SCENE_FOG_MODE","!=",0,54],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"TRANSFORM_INCLUDED",62],[0,"\n\n"],[7,"TRANSFORM_INCLUDED"],[0,"\nuniform mat4 renderer_ModelMat;\nuniform mat4 renderer_MVMat;\nuniform mat4 renderer_MVPMat;\nuniform mat4 renderer_NormalMat;\n\n"],[6],[0,"\n\n"],[2,"ATTRIBUTES_INCLUDED",120],[0,"\n\n"],[7,"ATTRIBUTES_INCLUDED"],[0,"\nattribute vec3 POSITION;\n\n"],[1,"RENDERER_HAS_BLENDSHAPE",94],[0,"\n"],[2,"RENDERER_BLENDSHAPE_USE_TEXTURE",92],[0,"attribute vec3 POSITION_BS0;\nattribute vec3 POSITION_BS1;\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},74],[0,"attribute vec3 NORMAL_BS0;\nattribute vec3 NORMAL_BS1;\nattribute vec3 TANGENT_BS0;\nattribute vec3 TANGENT_BS1;\n\n"],[5,90],[0,"\n"],[4,{"t":"or","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},86],[0,"attribute vec3 POSITION_BS2;\nattribute vec3 POSITION_BS3;\n\n"],[1,"RENDERER_BLENDSHAPE_HAS_NORMAL",80],[0,"attribute vec3 NORMAL_BS0;\nattribute vec3 NORMAL_BS1;\nattribute vec3 NORMAL_BS2;\nattribute vec3 NORMAL_BS3;\n\n"],[6],[0,"\n"],[1,"RENDERER_BLENDSHAPE_HAS_TANGENT",84],[0,"attribute vec3 TANGENT_BS0;\nattribute vec3 TANGENT_BS1;\nattribute vec3 TANGENT_BS2;\nattribute vec3 TANGENT_BS3;\n\n"],[6],[0,"\n"],[5,88],[0,"attribute vec3 POSITION_BS2;\nattribute vec3 POSITION_BS3;\nattribute vec3 POSITION_BS4;\nattribute vec3 POSITION_BS5;\nattribute vec3 POSITION_BS6;\nattribute vec3 POSITION_BS7;\n\n"],[6],[0,"\n"],[6],[0,"\n"],[6],[0,"\n"],[6],[0,"\n"],[1,"RENDERER_HAS_UV",98],[0,"attribute vec2 TEXCOORD_0;\n\n"],[6],[0,"\n"],[1,"RENDERER_HAS_UV1",102],[0,"attribute vec2 TEXCOORD_1;\n\n"],[6],[0,"\n"],[1,"RENDERER_HAS_SKIN",106],[0,"attribute vec4 JOINTS_0;\nattribute vec4 WEIGHTS_0;\n\n"],[6],[0,"\n"],[1,"RENDERER_ENABLE_VERTEXCOLOR",110],[0,"attribute vec4 COLOR_0;\n\n"],[6],[0,"\n"],[1,"RENDERER_HAS_NORMAL",114],[0,"attribute vec3 NORMAL;\n\n"],[6],[0,"\n"],[1,"RENDERER_HAS_TANGENT",118],[0,"attribute vec4 TANGENT;\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"SKIN_INCLUDED",142],[0,"\n\n"],[7,"SKIN_INCLUDED"],[0,"\n\n"],[1,"RENDERER_HAS_SKIN",140],[0,"\n\n"],[1,"RENDERER_USE_JOINT_TEXTURE",130],[0,"\nuniform sampler2D renderer_JointSampler;\nuniform float renderer_JointCount;\nmat4 getJointMatrix ( sampler2D smp, float index ) { float base = index / renderer_JointCount ;\nfloat hf = 0.5 / renderer_JointCount ;\nfloat v = base + hf ;\nvec4 m0 = texture2D ( smp , vec2 ( 0.125 , v ) ) ;\nvec4 m1 = texture2D ( smp , vec2 ( 0.375 , v ) ) ;\nvec4 m2 = texture2D ( smp , vec2 ( 0.625 , v ) ) ;\nvec4 m3 = texture2D ( smp , vec2 ( 0.875 , v ) ) ;\nreturn mat4 ( m0 , m1 , m2 , m3 ) ; }\n\n"],[5,132],[0,"\nuniform mat4 renderer_JointMatrix [ RENDERER_JOINTS_NUM ];\n\n"],[6],[0,"\nmat4 getSkinMatrix ( ) { \n"],[1,"RENDERER_USE_JOINT_TEXTURE",136],[0," mat4 skinMatrix = WEIGHTS_0.x * getJointMatrix(renderer_JointSampler, JOINTS_0.x) + WEIGHTS_0.y * getJointMatrix(renderer_JointSampler, JOINTS_0.y) + WEIGHTS_0.z * getJointMatrix(renderer_JointSampler, JOINTS_0.z) + WEIGHTS_0.w * getJointMatrix(renderer_JointSampler, JOINTS_0.w) ; \n"],[5,138],[0," mat4 skinMatrix = WEIGHTS_0.x * renderer_JointMatrix[int ( JOINTS_0.x )] + WEIGHTS_0.y * renderer_JointMatrix[int ( JOINTS_0.y )] + WEIGHTS_0.z * renderer_JointMatrix[int ( JOINTS_0.z )] + WEIGHTS_0.w * renderer_JointMatrix[int ( JOINTS_0.w )] ; \n"],[6],[0,"\nreturn skinMatrix ; }\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"BLENDSHAPE_INCLUDED",220],[0,"\n\n"],[7,"BLENDSHAPE_INCLUDED"],[0,"\n\n"],[1,"RENDERER_HAS_BLENDSHAPE",218],[0,"\n\n"],[1,"RENDERER_BLENDSHAPE_USE_TEXTURE",152],[0,"\nuniform mediump sampler2DArray renderer_BlendShapeTexture;\nuniform ivec3 renderer_BlendShapeTextureInfo;\nuniform float renderer_BlendShapeWeights [ RENDERER_BLENDSHAPE_COUNT ];\nvec3 getBlendShapeVertexElement ( int blendShapeIndex, int vertexElementIndex ) { int y = vertexElementIndex / renderer_BlendShapeTextureInfo.y ;\nint x = vertexElementIndex - y * renderer_BlendShapeTextureInfo.y ;\nivec3 uv = ivec3 ( x , y , blendShapeIndex ) ;\nreturn ( texelFetch ( renderer_BlendShapeTexture , uv , 0 ) ).xyz ; }\n\n"],[5,166],[0,"\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},156],[0,"\nuniform float renderer_BlendShapeWeights [ 2 ];\n\n"],[5,164],[0,"\n\n"],[4,{"t":"or","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},160],[0,"\nuniform float renderer_BlendShapeWeights [ 4 ];\n\n"],[5,162],[0,"\nuniform float renderer_BlendShapeWeights [ 8 ];\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\nvoid calculateBlendShape ( inout vec4 position\n"],[1,"RENDERER_HAS_NORMAL",174],[0," , inout vec3 normal \n"],[1,"RENDERER_HAS_TANGENT",172],[0," , inout vec4 tangent \n"],[6],[0," \n"],[6],[0," ) { \n"],[1,"RENDERER_BLENDSHAPE_USE_TEXTURE",186],[0," int vertexOffset = gl_VertexID * renderer_BlendShapeTextureInfo.x ;\nfor ( int i = 0 ; i < RENDERER_BLENDSHAPE_COUNT ; i ++ ) { int vertexElementOffset = vertexOffset ;\nfloat weight = renderer_BlendShapeWeights[i] ;\nif ( weight != 0.0 ) { position.xyz += getBlendShapeVertexElement(i, vertexElementOffset) * weight ;\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"}},180],[0," vertexElementOffset += 1 ;\nnormal += getBlendShapeVertexElement(i, vertexElementOffset) * weight ; \n"],[6],[0,"\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_HAS_TANGENT"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},184],[0," vertexElementOffset += 1 ;\ntangent.xyz += getBlendShapeVertexElement(i, vertexElementOffset) * weight ; \n"],[6],[0," } } \n"],[5,216],[0," position.xyz += POSITION_BS0 * renderer_BlendShapeWeights[0] ;\nposition.xyz += POSITION_BS1 * renderer_BlendShapeWeights[1] ;\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},198],[0," \n"],[1,"RENDERER_HAS_NORMAL",192],[0," normal += NORMAL_BS0 * renderer_BlendShapeWeights[0] ;\nnormal += NORMAL_BS1 * renderer_BlendShapeWeights[1] ; \n"],[6],[0,"\n\n"],[1,"RENDERER_HAS_TANGENT",196],[0," tangent.xyz += TANGENT_BS0 * renderer_BlendShapeWeights[0] ;\ntangent.xyz += TANGENT_BS1 * renderer_BlendShapeWeights[1] ; \n"],[6],[0," \n"],[5,214],[0," \n"],[4,{"t":"or","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},210],[0," position.xyz += POSITION_BS2 * renderer_BlendShapeWeights[2] ;\nposition.xyz += POSITION_BS3 * renderer_BlendShapeWeights[3] ;\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_HAS_NORMAL"}},204],[0," normal += NORMAL_BS0 * renderer_BlendShapeWeights[0] ;\nnormal += NORMAL_BS1 * renderer_BlendShapeWeights[1] ;\nnormal += NORMAL_BS2 * renderer_BlendShapeWeights[2] ;\nnormal += NORMAL_BS3 * renderer_BlendShapeWeights[3] ; \n"],[6],[0,"\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"},"r":{"t":"def","m":"RENDERER_HAS_TANGENT"}},208],[0," tangent.xyz += TANGENT_BS0 * renderer_BlendShapeWeights[0] ;\ntangent.xyz += TANGENT_BS1 * renderer_BlendShapeWeights[1] ;\ntangent.xyz += TANGENT_BS2 * renderer_BlendShapeWeights[2] ;\ntangent.xyz += TANGENT_BS3 * renderer_BlendShapeWeights[3] ; \n"],[6],[0," \n"],[5,212],[0," position.xyz += POSITION_BS2 * renderer_BlendShapeWeights[2] ;\nposition.xyz += POSITION_BS3 * renderer_BlendShapeWeights[3] ;\nposition.xyz += POSITION_BS4 * renderer_BlendShapeWeights[4] ;\nposition.xyz += POSITION_BS5 * renderer_BlendShapeWeights[5] ;\nposition.xyz += POSITION_BS6 * renderer_BlendShapeWeights[6] ;\nposition.xyz += POSITION_BS7 * renderer_BlendShapeWeights[7] ; \n"],[6],[0," \n"],[6],[0," \n"],[6],[0," }\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"SHADOW_INCLUDED",300],[0,"\n\n"],[7,"SHADOW_INCLUDED"],[0,"\n\n"],[4,{"t":"and","l":{"t":"def","m":"SCENE_SHADOW_TYPE"},"r":{"t":"def","m":"RENDERER_IS_RECEIVE_SHADOWS"}},230],[0,"\n\n"],[7,"NEED_CALCULATE_SHADOWS"],[0,"\n\n"],[6],[0,"\n\n"],[1,"NEED_CALCULATE_SHADOWS",258],[0,"\nuniform mat4 scene_ShadowMatrices [ SCENE_SHADOW_CASCADED_COUNT + 1 ];\nuniform vec4 scene_ShadowSplitSpheres [ 4 ];\nmediump int computeCascadeIndex ( vec3 positionWS ) { vec3 fromCenter0 = positionWS - scene_ShadowSplitSpheres[0].xyz ;\nvec3 fromCenter1 = positionWS - scene_ShadowSplitSpheres[1].xyz ;\nvec3 fromCenter2 = positionWS - scene_ShadowSplitSpheres[2].xyz ;\nvec3 fromCenter3 = positionWS - scene_ShadowSplitSpheres[3].xyz ;\nmediump vec4 comparison = vec4 ( ( dot ( fromCenter0 , fromCenter0 ) < scene_ShadowSplitSpheres[0].w ) , ( dot ( fromCenter1 , fromCenter1 ) < scene_ShadowSplitSpheres[1].w ) , ( dot ( fromCenter2 , fromCenter2 ) < scene_ShadowSplitSpheres[2].w ) , ( dot ( fromCenter3 , fromCenter3 ) < scene_ShadowSplitSpheres[3].w ) ) ;\ncomparison.yzw = clamp ( comparison.yzw - comparison.xyz , 0.0 , 1.0 ) ;\nmediump vec4 indexCoefficient = vec4 ( 4.0 , 3.0 , 2.0 , 1.0 ) ;\nmediump int index = 4 - int ( dot ( comparison , indexCoefficient ) ) ;\nreturn index ; }\nvec3 getShadowCoord ( vec3 positionWS ) { \n"],[3,"SCENE_SHADOW_CASCADED_COUNT","==",1,236],[0," mediump int cascadeIndex = 0 ; \n"],[5,238],[0," mediump int cascadeIndex = computeCascadeIndex(positionWS) ; \n"],[6],[0,"\n\n"],[1,"GRAPHICS_API_WEBGL2",242],[0," mat4 shadowMatrix = scene_ShadowMatrices[cascadeIndex] ; \n"],[5,256],[0," mat4 shadowMatrix ;\n\n"],[3,"SCENE_SHADOW_CASCADED_COUNT","==",4,246],[0," if ( cascadeIndex == 0 ) { shadowMatrix = scene_ShadowMatrices[0] ; } else if ( cascadeIndex == 1 ) { shadowMatrix = scene_ShadowMatrices[1] ; } else if ( cascadeIndex == 2 ) { shadowMatrix = scene_ShadowMatrices[2] ; } else if ( cascadeIndex == 3 ) { shadowMatrix = scene_ShadowMatrices[3] ; } else { shadowMatrix = scene_ShadowMatrices[4] ; } \n"],[6],[0,"\n\n"],[3,"SCENE_SHADOW_CASCADED_COUNT","==",2,250],[0," if ( cascadeIndex == 0 ) { shadowMatrix = scene_ShadowMatrices[0] ; } else if ( cascadeIndex == 1 ) { shadowMatrix = scene_ShadowMatrices[1] ; } else { shadowMatrix = scene_ShadowMatrices[2] ; } \n"],[6],[0,"\n\n"],[3,"SCENE_SHADOW_CASCADED_COUNT","==",1,254],[0," if ( cascadeIndex == 0 ) { shadowMatrix = scene_ShadowMatrices[0] ; } else { shadowMatrix = scene_ShadowMatrices[1] ; } \n"],[6],[0," \n"],[6],[0,"\nvec4 shadowCoord = shadowMatrix * vec4 ( positionWS , 1.0 ) ;\nreturn shadowCoord.xyz ; }\n\n"],[6],[0,"\n\n"],[1,"NEED_CALCULATE_SHADOWS",298],[0,"\n\n"],[1,"GRAPHICS_API_WEBGL2",268],[0,"\n\n"],[9,"SAMPLE_TEXTURE2D_SHADOW",["textureName","coord3"],"textureLod(textureName, coord3 , 0.0)"],[0,"\n\n"],[9,"TEXTURE2D_SHADOW_PARAM",["shadowMap"],"mediump sampler2DShadow shadowMap"],[0,"\n\n"],[5,282],[0,"\n\n"],[1,"ENGINE_NO_DEPTH_TEXTURE",274],[0,"\n\n"],[9,"SAMPLE_TEXTURE2D_SHADOW",["textureName","coord3"],"textureShadowMapDowngrade(textureName, coord3)"],[0,"\n\n"],[5,278],[0,"\n\n"],[9,"SAMPLE_TEXTURE2D_SHADOW",["textureName","coord3"],"textureShadowMapDowngrade(textureName, coord3)"],[0,"\n\n"],[6],[0,"\n\n"],[9,"TEXTURE2D_SHADOW_PARAM",["shadowMap"],"mediump sampler2D shadowMap"],[0,"\n\n"],[6],[0,"\n\n"],[3,"SCENE_SHADOW_TYPE","==",2,286],[0,"\n\n"],[6],[0,"\n\n"],[3,"SCENE_SHADOW_TYPE","==",3,296],[0,"\n\n"],[2,"SHADOW_SAMPLE_TENT_INCLUDED",294],[0,"\n\n"],[7,"SHADOW_SAMPLE_TENT_INCLUDED"],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"VARYINGS_PBR_INCLUDED",330],[0,"\n\n"],[7,"VARYINGS_PBR_INCLUDED"],[0,"\nvarying vec2 uv;\n\n"],[1,"RENDERER_HAS_UV1",308],[0,"varying vec2 uv1;\n\n"],[6],[0,"\n"],[1,"RENDERER_ENABLE_VERTEXCOLOR",312],[0,"varying vec4 vertexColor;\n\n"],[6],[0,"varying vec3 positionWS;\n\n"],[3,"SCENE_FOG_MODE","!=",0,316],[0,"varying vec3 positionVS;\n\n"],[6],[0,"\n"],[1,"RENDERER_HAS_NORMAL",324],[0,"varying vec3 normalWS;\n\n"],[1,"NEED_VERTEX_TANGENT",322],[0,"varying vec3 tangentWS;\nvarying vec3 bitangentWS;\n\n"],[6],[0,"\n"],[6],[0,"\n"],[4,{"t":"and","l":{"t":"def","m":"NEED_CALCULATE_SHADOWS"},"r":{"t":"cmp","m":"SCENE_SHADOW_CASCADED_COUNT","op":"==","v":1}},328],[0,"varying vec3 shadowCoord;\n\n"],[6],[0,"varying vec4 positionCS;\n\n\n"],[6],[0,"\n\n"],[2,"LIGHT_DIRECT_PBR_INCLUDED",478],[0,"\n\n"],[7,"LIGHT_DIRECT_PBR_INCLUDED"],[0,"\n\n"],[2,"FUNCTION_SURFACE_SHADING",340],[0,"\n\n"],[8,"FUNCTION_SURFACE_SHADING","surfaceShading"],[0,"\n\n"],[6],[0,"\n\n"],[2,"FUNCTION_DIFFUSE_LOBE",346],[0,"\n\n"],[8,"FUNCTION_DIFFUSE_LOBE","diffuseLobe"],[0,"\n\n"],[6],[0,"\n\n"],[2,"FUNCTION_SPECULAR_LOBE",352],[0,"\n\n"],[8,"FUNCTION_SPECULAR_LOBE","specularLobe"],[0,"\n\n"],[6],[0,"\n\n"],[2,"FUNCTION_CLEAR_COAT_LOBE",358],[0,"\n\n"],[8,"FUNCTION_CLEAR_COAT_LOBE","clearCoatLobe"],[0,"\n\n"],[6],[0,"\n\n"],[2,"FUNCTION_SHEEN_LOBE",364],[0,"\n\n"],[8,"FUNCTION_SHEEN_LOBE","sheenLobe"],[0,"\n\n"],[6],[0,"\n\n"],[2,"BSDF_INCLUDED",416],[0,"\n\n"],[7,"BSDF_INCLUDED"],[0,"\n\n"],[2,"REFRACTION_INCLUDED",378],[0,"\n\n"],[7,"REFRACTION_INCLUDED"],[0,"\n\n"],[1,"MATERIAL_ENABLE_TRANSMISSION",376],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[8,"MIN_PERCEPTUAL_ROUGHNESS","0.045"],[0,"\n\n"],[8,"MIN_ROUGHNESS","0.002025"],[0,"\n\n"],[1,"MATERIAL_ENABLE_SHEEN",386],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_ANISOTROPY",390],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_ANISOTROPY",394],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_ANISOTROPY",398],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_IRIDESCENCE",402],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_SHEEN",406],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_TRANSMISSION",410],[0,"\n\n"],[6],[0,"\n\n"],[1,"SCENE_ENABLE_AMBIENT_OCCLUSION",414],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"LIGHT_INCLUDED",458],[0,"\n\n"],[7,"LIGHT_INCLUDED"],[0,"\n\n"],[2,"GRAPHICS_API_WEBGL2",424],[0,"\n\n"],[6],[0,"\n\n"],[1,"SCENE_DIRECT_LIGHT_COUNT",432],[0,"\n\n"],[1,"GRAPHICS_API_WEBGL2",430],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[1,"SCENE_POINT_LIGHT_COUNT",440],[0,"\n\n"],[1,"GRAPHICS_API_WEBGL2",438],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[1,"SCENE_SPOT_LIGHT_COUNT",448],[0,"\n\n"],[1,"GRAPHICS_API_WEBGL2",446],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[1,"SCENE_USE_SH",452],[0,"\n\n"],[6],[0,"\n\n"],[1,"SCENE_USE_SPECULAR_ENV",456],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"REFLECTION_LOBE_INCLUDED",464],[0,"\n\n"],[7,"REFLECTION_LOBE_INCLUDED"],[0,"\n\n"],[6],[0,"\n\n"],[1,"SCENE_DIRECT_LIGHT_COUNT",468],[0,"\n\n"],[6],[0,"\n\n"],[1,"SCENE_POINT_LIGHT_COUNT",472],[0,"\n\n"],[6],[0,"\n\n"],[1,"SCENE_SPOT_LIGHT_COUNT",476],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"LIGHT_INDIRECT_PBR_INCLUDED",514],[0,"\n\n"],[7,"LIGHT_INDIRECT_PBR_INCLUDED"],[0,"\n\n"],[2,"FUNCTION_DIFFUSE_IBL",488],[0,"\n\n"],[8,"FUNCTION_DIFFUSE_IBL","evaluateDiffuseIBL"],[0,"\n\n"],[6],[0,"\n\n"],[2,"FUNCTION_SPECULAR_IBL",494],[0,"\n\n"],[8,"FUNCTION_SPECULAR_IBL","evaluateSpecularIBL"],[0,"\n\n"],[6],[0,"\n\n"],[2,"FUNCTION_CLEAR_COAT_IBL",500],[0,"\n\n"],[8,"FUNCTION_CLEAR_COAT_IBL","evaluateClearCoatIBL"],[0,"\n\n"],[6],[0,"\n\n"],[2,"FUNCTION_SHEEN_IBL",506],[0,"\n\n"],[8,"FUNCTION_SHEEN_IBL","evaluateSheenIBL"],[0,"\n\n"],[6],[0,"\n\n"],[2,"LIGHT_INDIRECT_FUNCTIONS_INCLUDED",512],[0,"\n\n"],[7,"LIGHT_INDIRECT_FUNCTIONS_INCLUDED"],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"VERTEX_INCLUDE",580],[0,"\n\n"],[7,"VERTEX_INCLUDE"],[0,"\nstruct VertexInputs { vec4 positionOS ; vec3 positionWS ; \n"],[3,"SCENE_FOG_MODE","!=",0,522],[0," vec3 positionVS ; \n"],[6],[0," \n"],[1,"RENDERER_HAS_NORMAL",530],[0," vec3 normalWS ; \n"],[1,"NEED_VERTEX_TANGENT",528],[0," vec3 tangentWS ; vec3 bitangentWS ; \n"],[6],[0," \n"],[6],[0," } ;\nuniform vec4 material_TilingOffset;\nvec2 getUV0 ( ) { vec2 uv0 = vec2 ( 0 ) ;\n\n"],[1,"RENDERER_HAS_UV",534],[0," uv0 = TEXCOORD_0 ; \n"],[6],[0,"\nreturn uv0 * material_TilingOffset.xy + material_TilingOffset.zw ; }\nVertexInputs getVertexInputs ( ) { VertexInputs inputs ;\nvec4 position = vec4 ( POSITION , 1.0 ) ;\n\n"],[1,"RENDERER_HAS_NORMAL",542],[0," vec3 normal = vec3 ( NORMAL ) ;\n\n"],[1,"RENDERER_HAS_TANGENT",540],[0," vec4 tangent = vec4 ( TANGENT ) ; \n"],[6],[0," \n"],[6],[0,"\n\n"],[1,"RENDERER_HAS_BLENDSHAPE",554],[0," calculateBlendShape(position\n"],[1,"RENDERER_HAS_NORMAL",552],[0," , normal \n"],[1,"RENDERER_HAS_TANGENT",550],[0," , tangent \n"],[6],[0," \n"],[6],[0,") ; \n"],[6],[0,"\n\n"],[1,"RENDERER_HAS_SKIN",566],[0," mat4 skinMatrix = getSkinMatrix() ;\nposition = skinMatrix * position ;\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_HAS_NORMAL"},"r":{"t":"not","c":{"t":"def","m":"MATERIAL_OMIT_NORMAL"}}},564],[0," mat3 skinNormalMatrix = INVERSE_MAT(mat3 ( skinMatrix )) ;\nnormal = normal * skinNormalMatrix ;\n\n"],[1,"NEED_VERTEX_TANGENT",562],[0," tangent.xyz = tangent.xyz * skinNormalMatrix ; \n"],[6],[0," \n"],[6],[0," \n"],[6],[0,"\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_HAS_NORMAL"},"r":{"t":"not","c":{"t":"def","m":"MATERIAL_OMIT_NORMAL"}}},574],[0," inputs.normalWS = normalize ( mat3 ( renderer_NormalMat ) * normal ) ;\n\n"],[1,"NEED_VERTEX_TANGENT",572],[0," vec3 tangentWS = normalize ( mat3 ( renderer_NormalMat ) * tangent.xyz ) ;\nvec3 bitangentWS = cross ( inputs.normalWS , tangentWS ) * tangent.w ;\ninputs.tangentWS = tangentWS ;\ninputs.bitangentWS = bitangentWS ; \n"],[6],[0," \n"],[6],[0,"\ninputs.positionOS = position ;\nvec4 positionWS = renderer_ModelMat * position ;\ninputs.positionWS = positionWS.xyz / positionWS.w ;\n\n"],[3,"SCENE_FOG_MODE","!=",0,578],[0," vec4 positionVS = renderer_MVMat * position ;\ninputs.positionVS = positionVS.xyz / positionVS.w ; \n"],[6],[0,"\nreturn inputs ; }\n\n"],[6],[0,"\n\n"],[2,"MATERIAL_INPUT_PBR_INCLUDED",688],[0,"\n\n"],[7,"MATERIAL_INPUT_PBR_INCLUDED"],[0,"\n\n"],[2,"NORMAL_INCLUDED",590],[0,"\n\n"],[7,"NORMAL_INCLUDED"],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_SPECULAR_TEXTURE",594],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_SPECULAR_COLOR_TEXTURE",598],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_CLEAR_COAT",614],[0,"\n\n"],[1,"MATERIAL_HAS_CLEAR_COAT_TEXTURE",604],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_CLEAR_COAT_ROUGHNESS_TEXTURE",608],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_CLEAR_COAT_NORMAL_TEXTURE",612],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_ANISOTROPY",622],[0,"\n\n"],[1,"MATERIAL_HAS_ANISOTROPY_TEXTURE",620],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_IRIDESCENCE",634],[0,"\n\n"],[1,"MATERIAL_HAS_IRIDESCENCE_THICKNESS_TEXTURE",628],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_IRIDESCENCE_TEXTURE",632],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_SHEEN",646],[0,"\n\n"],[1,"MATERIAL_HAS_SHEEN_TEXTURE",640],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_SHEEN_ROUGHNESS_TEXTURE",644],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_TRANSMISSION",662],[0,"\n\n"],[1,"MATERIAL_HAS_TRANSMISSION_TEXTURE",652],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_THICKNESS",660],[0,"\n\n"],[1,"MATERIAL_HAS_THICKNESS_TEXTURE",658],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_BASETEXTURE",666],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_NORMALTEXTURE",670],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_EMISSIVETEXTURE",674],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_ROUGHNESS_METALLIC_TEXTURE",678],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_OCCLUSION_TEXTURE",682],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_ANISOTROPY",686],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\nvoid main() { \nuv = getUV0() ;\n\n"],[1,"RENDERER_HAS_UV1",692],[0," uv1 = TEXCOORD_1 ; \n"],[6],[0,"\n\n"],[1,"RENDERER_ENABLE_VERTEXCOLOR",696],[0," vertexColor = COLOR_0 ; \n"],[6],[0,"\nVertexInputs vertexInputs = getVertexInputs() ;\npositionWS = vertexInputs.positionWS ;\n\n"],[3,"SCENE_FOG_MODE","!=",0,700],[0," positionVS = vertexInputs.positionVS ; \n"],[6],[0,"\n\n"],[1,"RENDERER_HAS_NORMAL",708],[0," normalWS = vertexInputs.normalWS ;\n\n"],[1,"NEED_VERTEX_TANGENT",706],[0," tangentWS = vertexInputs.tangentWS ;\nbitangentWS = vertexInputs.bitangentWS ; \n"],[6],[0," \n"],[6],[0,"\n\n"],[4,{"t":"and","l":{"t":"def","m":"NEED_CALCULATE_SHADOWS"},"r":{"t":"cmp","m":"SCENE_SHADOW_CASCADED_COUNT","op":"==","v":1}},712],[0," shadowCoord = getShadowCoord(vertexInputs.positionWS) ; \n"],[6],[0,"\ngl_Position = renderer_MVPMat * vertexInputs.positionOS ;\npositionCS = gl_Position ;\n }\n\n"],[6]],"fragmentShaderInstructions":[[0,"\n"],[2,"FORWARD_PASS_PBR_INCLUDED",896],[0,"\n\n"],[7,"FORWARD_PASS_PBR_INCLUDED"],[0,"\n\n"],[4,{"t":"and","l":{"t":"and","l":{"t":"def","m":"RENDERER_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_HAS_TANGENT"}},"r":{"t":"or","l":{"t":"def","m":"MATERIAL_HAS_NORMALTEXTURE"},"r":{"t":"def","m":"MATERIAL_HAS_CLEAR_COAT_NORMAL_TEXTURE"}}},10],[0,"\n\n"],[7,"NEED_VERTEX_TANGENT"],[0,"\n\n"],[6],[0,"\n\n"],[4,{"t":"or","l":{"t":"or","l":{"t":"def","m":"MATERIAL_HAS_NORMALTEXTURE"},"r":{"t":"def","m":"MATERIAL_HAS_CLEAR_COAT_NORMAL_TEXTURE"}},"r":{"t":"def","m":"MATERIAL_ENABLE_ANISOTROPY"}},16],[0,"\n\n"],[7,"NEED_TANGENT_SPACE"],[0,"\n\n"],[6],[0,"\n\n"],[2,"COMMON_INCLUDED",50],[0,"\n\n"],[7,"COMMON_INCLUDED"],[0,"\n\n"],[8,"PI","3.14159265359"],[0,"\n\n"],[8,"RECIPROCAL_PI","0.31830988618"],[0,"\n\n"],[8,"EPSILON","1e-6"],[0,"\n\n"],[8,"LOG2","1.442695"],[0,"\n\n"],[8,"HALF_MIN","6.103515625e-5"],[0,"\n\n"],[8,"HALF_EPS","4.8828125e-4"],[0,"\n\n"],[9,"saturate",["a"],"clamp( a, 0.0, 1.0 )"],[0,"\nfloat pow2 ( float x ) { return x * x ; }\nfloat sRGBToLinear ( float value ) { float linearRGBLo = value / 12.92 ;\nfloat linearRGBHi = pow ( ( value + 0.055 ) / 1.055 , 2.4 ) ;\nfloat linearRGB = ( value <= 0.04045 ) ? linearRGBLo : linearRGBHi ;\nreturn linearRGB ; }\nvec4 sRGBToLinear ( vec4 value ) { return vec4 ( sRGBToLinear(value.r) , sRGBToLinear(value.g) , sRGBToLinear(value.b) , value.a ) ; }\nvec4 texture2DSRGB ( sampler2D tex, vec2 uv ) { vec4 color = texture2D ( tex , uv ) ;\n\n"],[1,"ENGINE_NO_SRGB",38],[0," color = sRGBToLinear(color) ; \n"],[6],[0,"\nreturn color ; }\nuniform vec4 camera_ProjectionParams;\n\n"],[1,"GRAPHICS_API_WEBGL2",44],[0,"\n\n"],[9,"INVERSE_MAT",["mat"],"inverse(mat)"],[0,"\n\n"],[5,48],[0,"\n\n"],[9,"INVERSE_MAT",["mat"],"inverseMat(mat)"],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"FOG_INCLUDED",70],[0,"\n\n"],[7,"FOG_INCLUDED"],[0,"\n\n"],[3,"SCENE_FOG_MODE","!=",0,68],[0,"\nuniform vec4 scene_FogColor;\nuniform vec4 scene_FogParams;\nvec4 fog ( vec4 color, vec3 positionVS ) { float fogDepth = length ( positionVS ) ;\n\n"],[3,"SCENE_FOG_MODE","==",1,60],[0," float fogIntensity = clamp ( fogDepth * scene_FogParams.x + scene_FogParams.y , 0.0 , 1.0 ) ; \n"],[5,66],[3,"SCENE_FOG_MODE","==",2,63],[0," float fogIntensity = clamp ( exp2 ( - fogDepth * scene_FogParams.z ) , 0.0 , 1.0 ) ; \n"],[5,66],[3,"SCENE_FOG_MODE","==",3,66],[0," float factor = fogDepth * scene_FogParams.w ;\nfloat fogIntensity = clamp ( exp2 ( - factor * factor ) , 0.0 , 1.0 ) ; \n"],[6],[0,"\ncolor.rgb = mix ( scene_FogColor.rgb , color.rgb , fogIntensity ) ;\nreturn color ; }\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"TRANSFORM_INCLUDED",76],[0,"\n\n"],[7,"TRANSFORM_INCLUDED"],[0,"\nuniform mat4 camera_ViewMat;\nuniform mat4 camera_ProjMat;\nuniform vec3 camera_Position;\nuniform vec3 camera_Forward;\n\n"],[6],[0,"\n\n"],[2,"ATTRIBUTES_INCLUDED",82],[0,"\n\n"],[7,"ATTRIBUTES_INCLUDED"],[0,"\n\n"],[6],[0,"\n\n"],[2,"SKIN_INCLUDED",98],[0,"\n\n"],[7,"SKIN_INCLUDED"],[0,"\n\n"],[1,"RENDERER_HAS_SKIN",96],[0,"\n\n"],[1,"RENDERER_USE_JOINT_TEXTURE",92],[0,"\n\n"],[5,94],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"BLENDSHAPE_INCLUDED",126],[0,"\n\n"],[7,"BLENDSHAPE_INCLUDED"],[0,"\n\n"],[1,"RENDERER_HAS_BLENDSHAPE",124],[0,"\n\n"],[1,"RENDERER_BLENDSHAPE_USE_TEXTURE",108],[0,"\n\n"],[5,122],[0,"\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},112],[0,"\n\n"],[5,120],[0,"\n\n"],[4,{"t":"or","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},116],[0,"\n\n"],[5,118],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"SHADOW_INCLUDED",218],[0,"\n\n"],[7,"SHADOW_INCLUDED"],[0,"\n\n"],[4,{"t":"and","l":{"t":"def","m":"SCENE_SHADOW_TYPE"},"r":{"t":"def","m":"RENDERER_IS_RECEIVE_SHADOWS"}},136],[0,"\n\n"],[7,"NEED_CALCULATE_SHADOWS"],[0,"\n\n"],[6],[0,"\n\n"],[1,"NEED_CALCULATE_SHADOWS",164],[0,"\nuniform mat4 scene_ShadowMatrices [ SCENE_SHADOW_CASCADED_COUNT + 1 ];\nuniform vec4 scene_ShadowSplitSpheres [ 4 ];\nmediump int computeCascadeIndex ( vec3 positionWS ) { vec3 fromCenter0 = positionWS - scene_ShadowSplitSpheres[0].xyz ;\nvec3 fromCenter1 = positionWS - scene_ShadowSplitSpheres[1].xyz ;\nvec3 fromCenter2 = positionWS - scene_ShadowSplitSpheres[2].xyz ;\nvec3 fromCenter3 = positionWS - scene_ShadowSplitSpheres[3].xyz ;\nmediump vec4 comparison = vec4 ( ( dot ( fromCenter0 , fromCenter0 ) < scene_ShadowSplitSpheres[0].w ) , ( dot ( fromCenter1 , fromCenter1 ) < scene_ShadowSplitSpheres[1].w ) , ( dot ( fromCenter2 , fromCenter2 ) < scene_ShadowSplitSpheres[2].w ) , ( dot ( fromCenter3 , fromCenter3 ) < scene_ShadowSplitSpheres[3].w ) ) ;\ncomparison.yzw = clamp ( comparison.yzw - comparison.xyz , 0.0 , 1.0 ) ;\nmediump vec4 indexCoefficient = vec4 ( 4.0 , 3.0 , 2.0 , 1.0 ) ;\nmediump int index = 4 - int ( dot ( comparison , indexCoefficient ) ) ;\nreturn index ; }\nvec3 getShadowCoord ( vec3 positionWS ) { \n"],[3,"SCENE_SHADOW_CASCADED_COUNT","==",1,142],[0," mediump int cascadeIndex = 0 ; \n"],[5,144],[0," mediump int cascadeIndex = computeCascadeIndex(positionWS) ; \n"],[6],[0,"\n\n"],[1,"GRAPHICS_API_WEBGL2",148],[0," mat4 shadowMatrix = scene_ShadowMatrices[cascadeIndex] ; \n"],[5,162],[0," mat4 shadowMatrix ;\n\n"],[3,"SCENE_SHADOW_CASCADED_COUNT","==",4,152],[0," if ( cascadeIndex == 0 ) { shadowMatrix = scene_ShadowMatrices[0] ; } else if ( cascadeIndex == 1 ) { shadowMatrix = scene_ShadowMatrices[1] ; } else if ( cascadeIndex == 2 ) { shadowMatrix = scene_ShadowMatrices[2] ; } else if ( cascadeIndex == 3 ) { shadowMatrix = scene_ShadowMatrices[3] ; } else { shadowMatrix = scene_ShadowMatrices[4] ; } \n"],[6],[0,"\n\n"],[3,"SCENE_SHADOW_CASCADED_COUNT","==",2,156],[0," if ( cascadeIndex == 0 ) { shadowMatrix = scene_ShadowMatrices[0] ; } else if ( cascadeIndex == 1 ) { shadowMatrix = scene_ShadowMatrices[1] ; } else { shadowMatrix = scene_ShadowMatrices[2] ; } \n"],[6],[0,"\n\n"],[3,"SCENE_SHADOW_CASCADED_COUNT","==",1,160],[0," if ( cascadeIndex == 0 ) { shadowMatrix = scene_ShadowMatrices[0] ; } else { shadowMatrix = scene_ShadowMatrices[1] ; } \n"],[6],[0," \n"],[6],[0,"\nvec4 shadowCoord = shadowMatrix * vec4 ( positionWS , 1.0 ) ;\nreturn shadowCoord.xyz ; }\n\n"],[6],[0,"\n\n"],[1,"NEED_CALCULATE_SHADOWS",216],[0,"\nuniform vec4 scene_ShadowInfo;\nuniform vec4 scene_ShadowMapSize;\n\n"],[1,"GRAPHICS_API_WEBGL2",174],[0,"\nuniform mediump sampler2DShadow scene_ShadowMap;\n\n"],[9,"SAMPLE_TEXTURE2D_SHADOW",["textureName","coord3"],"textureLod(textureName, coord3 , 0.0)"],[0,"\n\n"],[9,"TEXTURE2D_SHADOW_PARAM",["shadowMap"],"mediump sampler2DShadow shadowMap"],[0,"\n\n"],[5,188],[0,"\nuniform sampler2D scene_ShadowMap;\n\n"],[1,"ENGINE_NO_DEPTH_TEXTURE",180],[0,"\nconst vec4 bitShift = vec4 ( 1.0 , 1.0 / 256.0 , 1.0 / ( 256.0 * 256.0 ) , 1.0 / ( 256.0 * 256.0 * 256.0 ) );\nfloat textureShadowMapDowngrade ( sampler2D scene_ShadowMap, vec3 shadowCoord ) { vec4 rgbaDepth = texture2D ( scene_ShadowMap , shadowCoord.xy ) ;\nfloat unpackDepth = dot ( rgbaDepth , bitShift ) ;\nreturn unpackDepth < shadowCoord.z ? 0.0 : 1.0 ; }\n\n"],[9,"SAMPLE_TEXTURE2D_SHADOW",["textureName","coord3"],"textureShadowMapDowngrade(textureName, coord3)"],[0,"\n\n"],[5,184],[0,"\nfloat textureShadowMapDowngrade ( sampler2D scene_ShadowMap, vec3 shadowCoord ) { float depth = texture2D ( scene_ShadowMap , shadowCoord.xy ).r ;\nreturn depth < shadowCoord.z ? 0.0 : 1.0 ; }\n\n"],[9,"SAMPLE_TEXTURE2D_SHADOW",["textureName","coord3"],"textureShadowMapDowngrade(textureName, coord3)"],[0,"\n\n"],[6],[0,"\n\n"],[9,"TEXTURE2D_SHADOW_PARAM",["shadowMap"],"mediump sampler2D shadowMap"],[0,"\n\n"],[6],[0,"\n\n"],[3,"SCENE_SHADOW_TYPE","==",2,192],[0,"\nfloat sampleShadowMapFiltered4 ( TEXTURE2D_SHADOW_PARAM(shadowMap), vec3 shadowCoord, vec4 shadowMapSize ) { float attenuation ;\nvec4 attenuation4 ;\nvec2 offset = shadowMapSize.xy / 2.0 ;\nvec3 shadowCoord0 = shadowCoord + vec3 ( - offset , 0.0 ) ;\nvec3 shadowCoord1 = shadowCoord + vec3 ( offset.x , - offset.y , 0.0 ) ;\nvec3 shadowCoord2 = shadowCoord + vec3 ( - offset.x , offset.y , 0.0 ) ;\nvec3 shadowCoord3 = shadowCoord + vec3 ( offset , 0.0 ) ;\nattenuation4.x = SAMPLE_TEXTURE2D_SHADOW(shadowMap, shadowCoord0) ;\nattenuation4.y = SAMPLE_TEXTURE2D_SHADOW(shadowMap, shadowCoord1) ;\nattenuation4.z = SAMPLE_TEXTURE2D_SHADOW(shadowMap, shadowCoord2) ;\nattenuation4.w = SAMPLE_TEXTURE2D_SHADOW(shadowMap, shadowCoord3) ;\nattenuation = dot ( attenuation4 , vec4 ( 0.25 ) ) ;\nreturn attenuation ; }\n\n"],[6],[0,"\n\n"],[3,"SCENE_SHADOW_TYPE","==",3,202],[0,"\n\n"],[2,"SHADOW_SAMPLE_TENT_INCLUDED",200],[0,"\n\n"],[7,"SHADOW_SAMPLE_TENT_INCLUDED"],[0,"\nfloat sampleShadowGetIRTriangleTexelArea ( float triangleHeight ) { return triangleHeight - 0.5 ; }\nvoid sampleShadowGetTexelAreasTent3x3 ( float offset, out vec4 computedArea, out vec4 computedAreaUncut ) { float a = offset + 0.5 ;\nfloat offsetSquaredHalved = a * a * 0.5 ;\ncomputedAreaUncut.x = computedArea.x = offsetSquaredHalved - offset ;\ncomputedAreaUncut.w = computedArea.w = offsetSquaredHalved ;\ncomputedAreaUncut.y = sampleShadowGetIRTriangleTexelArea(1.5 - offset) ;\nfloat clampedOffsetLeft = min ( offset , 0.0 ) ;\nfloat areaOfSmallLeftTriangle = clampedOffsetLeft * clampedOffsetLeft ;\ncomputedArea.y = computedAreaUncut.y - areaOfSmallLeftTriangle ;\ncomputedAreaUncut.z = sampleShadowGetIRTriangleTexelArea(1.5 + offset) ;\nfloat clampedOffsetRight = max ( offset , 0.0 ) ;\nfloat areaOfSmallRightTriangle = clampedOffsetRight * clampedOffsetRight ;\ncomputedArea.z = computedAreaUncut.z - areaOfSmallRightTriangle ; }\nvoid sampleShadowGetTexelWeightsTent5x5 ( float offset, out vec3 texelsWeightsA, out vec3 texelsWeightsB ) { vec4 areaFrom3texelTriangle ;\nvec4 areaUncutFrom3texelTriangle ;\nsampleShadowGetTexelAreasTent3x3(offset, areaFrom3texelTriangle, areaUncutFrom3texelTriangle) ;\ntexelsWeightsA.x = 0.16 * ( areaFrom3texelTriangle.x ) ;\ntexelsWeightsA.y = 0.16 * ( areaUncutFrom3texelTriangle.y ) ;\ntexelsWeightsA.z = 0.16 * ( areaFrom3texelTriangle.y + 1.0 ) ;\ntexelsWeightsB.x = 0.16 * ( areaFrom3texelTriangle.z + 1.0 ) ;\ntexelsWeightsB.y = 0.16 * ( areaUncutFrom3texelTriangle.z ) ;\ntexelsWeightsB.z = 0.16 * ( areaFrom3texelTriangle.w ) ; }\nvoid sampleShadowComputeSamplesTent5x5 ( vec4 shadowMapTextureTexelSize, vec2 coord, out float fetchesWeights [ 9 ], out vec2 fetchesUV [ 9 ] ) { vec2 tentCenterInTexelSpace = coord.xy * shadowMapTextureTexelSize.zw ;\nvec2 centerOfFetchesInTexelSpace = floor ( tentCenterInTexelSpace + 0.5 ) ;\nvec2 offsetFromTentCenterToCenterOfFetches = tentCenterInTexelSpace - centerOfFetchesInTexelSpace ;\nvec3 texelsWeightsUA , texelsWeightsUB ;\nvec3 texelsWeightsVA , texelsWeightsVB ;\nsampleShadowGetTexelWeightsTent5x5(offsetFromTentCenterToCenterOfFetches.x, texelsWeightsUA, texelsWeightsUB) ;\nsampleShadowGetTexelWeightsTent5x5(offsetFromTentCenterToCenterOfFetches.y, texelsWeightsVA, texelsWeightsVB) ;\nvec3 fetchesWeightsU = vec3 ( texelsWeightsUA.xz , texelsWeightsUB.y ) + vec3 ( texelsWeightsUA.y , texelsWeightsUB.xz ) ;\nvec3 fetchesWeightsV = vec3 ( texelsWeightsVA.xz , texelsWeightsVB.y ) + vec3 ( texelsWeightsVA.y , texelsWeightsVB.xz ) ;\nvec3 fetchesOffsetsU = vec3 ( texelsWeightsUA.y , texelsWeightsUB.xz ) / fetchesWeightsU.xyz + vec3 ( - 2.5 , - 0.5 , 1.5 ) ;\nvec3 fetchesOffsetsV = vec3 ( texelsWeightsVA.y , texelsWeightsVB.xz ) / fetchesWeightsV.xyz + vec3 ( - 2.5 , - 0.5 , 1.5 ) ;\nfetchesOffsetsU *= shadowMapTextureTexelSize.xxx ;\nfetchesOffsetsV *= shadowMapTextureTexelSize.yyy ;\nvec2 bilinearFetchOrigin = centerOfFetchesInTexelSpace * shadowMapTextureTexelSize.xy ;\nfetchesUV[0] = bilinearFetchOrigin + vec2 ( fetchesOffsetsU.x , fetchesOffsetsV.x ) ;\nfetchesUV[1] = bilinearFetchOrigin + vec2 ( fetchesOffsetsU.y , fetchesOffsetsV.x ) ;\nfetchesUV[2] = bilinearFetchOrigin + vec2 ( fetchesOffsetsU.z , fetchesOffsetsV.x ) ;\nfetchesUV[3] = bilinearFetchOrigin + vec2 ( fetchesOffsetsU.x , fetchesOffsetsV.y ) ;\nfetchesUV[4] = bilinearFetchOrigin + vec2 ( fetchesOffsetsU.y , fetchesOffsetsV.y ) ;\nfetchesUV[5] = bilinearFetchOrigin + vec2 ( fetchesOffsetsU.z , fetchesOffsetsV.y ) ;\nfetchesUV[6] = bilinearFetchOrigin + vec2 ( fetchesOffsetsU.x , fetchesOffsetsV.z ) ;\nfetchesUV[7] = bilinearFetchOrigin + vec2 ( fetchesOffsetsU.y , fetchesOffsetsV.z ) ;\nfetchesUV[8] = bilinearFetchOrigin + vec2 ( fetchesOffsetsU.z , fetchesOffsetsV.z ) ;\nfetchesWeights[0] = fetchesWeightsU.x * fetchesWeightsV.x ;\nfetchesWeights[1] = fetchesWeightsU.y * fetchesWeightsV.x ;\nfetchesWeights[2] = fetchesWeightsU.z * fetchesWeightsV.x ;\nfetchesWeights[3] = fetchesWeightsU.x * fetchesWeightsV.y ;\nfetchesWeights[4] = fetchesWeightsU.y * fetchesWeightsV.y ;\nfetchesWeights[5] = fetchesWeightsU.z * fetchesWeightsV.y ;\nfetchesWeights[6] = fetchesWeightsU.x * fetchesWeightsV.z ;\nfetchesWeights[7] = fetchesWeightsU.y * fetchesWeightsV.z ;\nfetchesWeights[8] = fetchesWeightsU.z * fetchesWeightsV.z ; }\n\n"],[6],[0,"\nfloat sampleShadowMapFiltered9 ( TEXTURE2D_SHADOW_PARAM(shadowMap), vec3 shadowCoord, vec4 shadowmapSize ) { float attenuation ;\nfloat fetchesWeights [ 9 ] ;\nvec2 fetchesUV [ 9 ] ;\nsampleShadowComputeSamplesTent5x5(shadowmapSize, shadowCoord.xy, fetchesWeights, fetchesUV) ;\nattenuation = fetchesWeights[0] * SAMPLE_TEXTURE2D_SHADOW(shadowMap, vec3 ( fetchesUV[0].xy , shadowCoord.z )) ;\nattenuation += fetchesWeights[1] * SAMPLE_TEXTURE2D_SHADOW(shadowMap, vec3 ( fetchesUV[1].xy , shadowCoord.z )) ;\nattenuation += fetchesWeights[2] * SAMPLE_TEXTURE2D_SHADOW(shadowMap, vec3 ( fetchesUV[2].xy , shadowCoord.z )) ;\nattenuation += fetchesWeights[3] * SAMPLE_TEXTURE2D_SHADOW(shadowMap, vec3 ( fetchesUV[3].xy , shadowCoord.z )) ;\nattenuation += fetchesWeights[4] * SAMPLE_TEXTURE2D_SHADOW(shadowMap, vec3 ( fetchesUV[4].xy , shadowCoord.z )) ;\nattenuation += fetchesWeights[5] * SAMPLE_TEXTURE2D_SHADOW(shadowMap, vec3 ( fetchesUV[5].xy , shadowCoord.z )) ;\nattenuation += fetchesWeights[6] * SAMPLE_TEXTURE2D_SHADOW(shadowMap, vec3 ( fetchesUV[6].xy , shadowCoord.z )) ;\nattenuation += fetchesWeights[7] * SAMPLE_TEXTURE2D_SHADOW(shadowMap, vec3 ( fetchesUV[7].xy , shadowCoord.z )) ;\nattenuation += fetchesWeights[8] * SAMPLE_TEXTURE2D_SHADOW(shadowMap, vec3 ( fetchesUV[8].xy , shadowCoord.z )) ;\nreturn attenuation ; }\n\n"],[6],[0,"\nfloat getShadowFade ( vec3 positionWS ) { vec3 camToPixel = positionWS - camera_Position ;\nfloat distanceCamToPixel2 = dot ( camToPixel , camToPixel ) ;\nreturn saturate(distanceCamToPixel2 * scene_ShadowInfo.z + scene_ShadowInfo.w) ; }\nfloat sampleShadowMap ( vec3 positionWS, vec3 shadowCoord ) { float attenuation = 1.0 ;\nif ( shadowCoord.z > 0.0 && shadowCoord.z < 1.0 ) { \n"],[3,"SCENE_SHADOW_TYPE","==",1,206],[0," attenuation = SAMPLE_TEXTURE2D_SHADOW(scene_ShadowMap, shadowCoord) ; \n"],[6],[0,"\n\n"],[3,"SCENE_SHADOW_TYPE","==",2,210],[0," attenuation = sampleShadowMapFiltered4(scene_ShadowMap, shadowCoord, scene_ShadowMapSize) ; \n"],[6],[0,"\n\n"],[3,"SCENE_SHADOW_TYPE","==",3,214],[0," attenuation = sampleShadowMapFiltered9(scene_ShadowMap, shadowCoord, scene_ShadowMapSize) ; \n"],[6],[0,"\nfloat shadowFade = getShadowFade(positionWS) ;\nattenuation = mix ( 1.0 , mix ( attenuation , 1.0 , shadowFade ) , scene_ShadowInfo.x ) ; }\nreturn attenuation ; }\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"VARYINGS_PBR_INCLUDED",248],[0,"\n\n"],[7,"VARYINGS_PBR_INCLUDED"],[0,"\nvarying vec2 uv;\n\n"],[1,"RENDERER_HAS_UV1",226],[0,"varying vec2 uv1;\n\n"],[6],[0,"\n"],[1,"RENDERER_ENABLE_VERTEXCOLOR",230],[0,"varying vec4 vertexColor;\n\n"],[6],[0,"varying vec3 positionWS;\n\n"],[3,"SCENE_FOG_MODE","!=",0,234],[0,"varying vec3 positionVS;\n\n"],[6],[0,"\n"],[1,"RENDERER_HAS_NORMAL",242],[0,"varying vec3 normalWS;\n\n"],[1,"NEED_VERTEX_TANGENT",240],[0,"varying vec3 tangentWS;\nvarying vec3 bitangentWS;\n\n"],[6],[0,"\n"],[6],[0,"\n"],[4,{"t":"and","l":{"t":"def","m":"NEED_CALCULATE_SHADOWS"},"r":{"t":"cmp","m":"SCENE_SHADOW_CASCADED_COUNT","op":"==","v":1}},246],[0,"varying vec3 shadowCoord;\n\n"],[6],[0,"varying vec4 positionCS;\n\n\n"],[6],[0,"\n\n"],[2,"LIGHT_DIRECT_PBR_INCLUDED",539],[0,"\n\n"],[7,"LIGHT_DIRECT_PBR_INCLUDED"],[0,"\n\n"],[2,"FUNCTION_SURFACE_SHADING",258],[0,"\n\n"],[8,"FUNCTION_SURFACE_SHADING","surfaceShading"],[0,"\n\n"],[6],[0,"\n\n"],[2,"FUNCTION_DIFFUSE_LOBE",264],[0,"\n\n"],[8,"FUNCTION_DIFFUSE_LOBE","diffuseLobe"],[0,"\n\n"],[6],[0,"\n\n"],[2,"FUNCTION_SPECULAR_LOBE",270],[0,"\n\n"],[8,"FUNCTION_SPECULAR_LOBE","specularLobe"],[0,"\n\n"],[6],[0,"\n\n"],[2,"FUNCTION_CLEAR_COAT_LOBE",276],[0,"\n\n"],[8,"FUNCTION_CLEAR_COAT_LOBE","clearCoatLobe"],[0,"\n\n"],[6],[0,"\n\n"],[2,"FUNCTION_SHEEN_LOBE",282],[0,"\n\n"],[8,"FUNCTION_SHEEN_LOBE","sheenLobe"],[0,"\n\n"],[6],[0,"\n\n"],[2,"BSDF_INCLUDED",429],[0,"\n\n"],[7,"BSDF_INCLUDED"],[0,"\n\n"],[2,"REFRACTION_INCLUDED",296],[0,"\n\n"],[7,"REFRACTION_INCLUDED"],[0,"\n\n"],[1,"MATERIAL_ENABLE_TRANSMISSION",294],[0,"\nstruct RefractionModelResult { float transmissionLength ; vec3 positionExit ; } ;\nvoid refractionModelSphere ( vec3 V, vec3 positionWS, vec3 normalWS, float ior, float thickness, out RefractionModelResult ray ) { vec3 R1 = refract ( V , normalWS , 1.0 / ior ) ;\nfloat dist = dot ( - normalWS , R1 ) * thickness ;\nvec3 P1 = positionWS + R1 * dist ;\nray.transmissionLength = dist ;\nray.positionExit = P1 ; }\nvoid refractionModelPlanar ( vec3 V, vec3 positionWS, vec3 normalWS, float ior, float thickness, out RefractionModelResult ray ) { vec3 R = refract ( V , normalWS , 1.0 / ior ) ;\nfloat dist = thickness / max ( dot ( - normalWS , R ) , 1e-5f ) ;\nray.transmissionLength = dist ;\nray.positionExit = vec3 ( positionWS + R * dist ) ; }\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[8,"MIN_PERCEPTUAL_ROUGHNESS","0.045"],[0,"\n\n"],[8,"MIN_ROUGHNESS","0.002025"],[0,"\n\n"],[1,"MATERIAL_ENABLE_SHEEN",304],[0,"\nuniform sampler2D scene_PrefilteredDFG;\n\n"],[6],[0,"\nstruct SurfaceData { vec3 albedoColor ; vec3 emissiveColor ; float metallic ; float roughness ; float ambientOcclusion ; float opacity ; float IOR ; vec3 position ; vec4 positionCS ; vec3 normal ; \n"],[1,"NEED_TANGENT_SPACE",308],[0," vec3 tangent ; vec3 bitangent ; \n"],[6],[0," vec3 viewDir ; float dotNV ; float specularIntensity ; vec3 specularColor ; \n"],[1,"MATERIAL_ENABLE_ANISOTROPY",312],[0," float anisotropy ; vec3 anisotropicT ; vec3 anisotropicB ; vec3 anisotropicN ; \n"],[6],[0," \n"],[1,"MATERIAL_ENABLE_CLEAR_COAT",316],[0," float clearCoat ; float clearCoatRoughness ; vec3 clearCoatNormal ; float clearCoatDotNV ; \n"],[6],[0," \n"],[1,"MATERIAL_ENABLE_IRIDESCENCE",320],[0," float iridescenceIOR ; float iridescenceFactor ; float iridescenceThickness ; \n"],[6],[0," \n"],[1,"MATERIAL_ENABLE_SHEEN",324],[0," float sheenRoughness ; vec3 sheenColor ; \n"],[6],[0," \n"],[1,"MATERIAL_ENABLE_TRANSMISSION",328],[0," vec3 absorptionCoefficient ; float transmission ; float thickness ; \n"],[6],[0," } ;\nstruct BSDFData { vec3 diffuseColor ; float roughness ; vec3 envSpecularDFG ; float diffuseAO ; vec3 specularF0 ; vec3 resolvedSpecularF0 ; float specularF90 ; vec3 energyCompensation ; \n"],[1,"MATERIAL_ENABLE_CLEAR_COAT",332],[0," vec3 clearCoatSpecularColor ; float clearCoatRoughness ; \n"],[6],[0," \n"],[1,"MATERIAL_ENABLE_IRIDESCENCE",336],[0," vec3 iridescenceSpecularColor ; \n"],[6],[0," \n"],[1,"MATERIAL_ENABLE_SHEEN",340],[0," float sheenRoughness ; float sheenScaling ; float approxIBLSheenDG ; \n"],[6],[0," } ;\nfloat getAARoughnessFactor ( vec3 normal ) { \n"],[1,"HAS_DERIVATIVES",344],[0," vec3 dxy = max ( abs ( dFdx ( normal ) ) , abs ( dFdy ( normal ) ) ) ;\nreturn max ( max ( dxy.x , dxy.y ) , dxy.z ) ; \n"],[5,346],[0," return 0.0 ; \n"],[6],[0," }\nfloat F_Schlick ( float f0, float f90, float dotLH ) { return f0 + ( f90 - f0 ) * ( pow ( 1.0 - dotLH , 5.0 ) ) ; }\nvec3 F_Schlick ( vec3 f0, float f90, float dotLH ) { float fresnel = exp2 ( ( - 5.55473 * dotLH - 6.98316 ) * dotLH ) ;\nreturn ( f90 - f0 ) * fresnel + f0 ; }\nfloat G_GGX_SmithCorrelated ( float alpha, float dotNL, float dotNV ) { float a2 = pow2(alpha) ;\nfloat gv = dotNL * sqrt ( a2 + ( 1.0 - a2 ) * pow2(dotNV) ) ;\nfloat gl = dotNV * sqrt ( a2 + ( 1.0 - a2 ) * pow2(dotNL) ) ;\nreturn 0.5 / max ( gv + gl , EPSILON ) ; }\n\n"],[1,"MATERIAL_ENABLE_ANISOTROPY",350],[0,"\nfloat G_GGX_SmithCorrelated_Anisotropic ( float at, float ab, float ToV, float BoV, float ToL, float BoL, float NoV, float NoL ) { float lambdaV = NoL * length ( vec3 ( at * ToV , ab * BoV , NoV ) ) ;\nfloat lambdaL = NoV * length ( vec3 ( at * ToL , ab * BoL , NoL ) ) ;\nreturn 0.5 / max ( lambdaV + lambdaL , EPSILON ) ; }\n\n"],[6],[0,"\nfloat D_GGX ( float alpha, float dotNH ) { float a2 = pow2(alpha) ;\nfloat denom = pow2(dotNH) * ( a2 - 1.0 ) + 1.0 ;\nreturn RECIPROCAL_PI * a2 / pow2(denom) ; }\n\n"],[1,"MATERIAL_ENABLE_ANISOTROPY",354],[0,"\nfloat D_GGX_Anisotropic ( float at, float ab, float ToH, float BoH, float NoH ) { float a2 = at * ab ;\nhighp vec3 d = vec3 ( ab * ToH , at * BoH , a2 * NoH ) ;\nhighp float d2 = dot ( d , d ) ;\nfloat b2 = a2 / d2 ;\nreturn a2 * b2 * b2 * RECIPROCAL_PI ; }\n\n"],[6],[0,"\nfloat DG_GGX ( float alpha, float dotNV, float dotNL, float dotNH ) { float D = D_GGX(alpha, dotNH) ;\nfloat G = G_GGX_SmithCorrelated(alpha, dotNL, dotNV) ;\nreturn G * D ; }\n\n"],[1,"MATERIAL_ENABLE_ANISOTROPY",358],[0,"\nfloat DG_GGX_anisotropic ( vec3 h, vec3 l, SurfaceData surfaceData, float alpha, float dotNV, float dotNL, float dotNH ) { vec3 t = surfaceData.anisotropicT ;\nvec3 b = surfaceData.anisotropicB ;\nvec3 v = surfaceData.viewDir ;\nfloat dotTV = dot ( t , v ) ;\nfloat dotBV = dot ( b , v ) ;\nfloat dotTL = dot ( t , l ) ;\nfloat dotBL = dot ( b , l ) ;\nfloat dotTH = dot ( t , h ) ;\nfloat dotBH = dot ( b , h ) ;\nfloat at = max ( alpha * ( 1.0 + surfaceData.anisotropy ) , MIN_ROUGHNESS ) ;\nfloat ab = max ( alpha * ( 1.0 - surfaceData.anisotropy ) , MIN_ROUGHNESS ) ;\nfloat D = D_GGX_Anisotropic(at, ab, dotTH, dotBH, dotNH) ;\nfloat G = G_GGX_SmithCorrelated_Anisotropic(at, ab, dotTV, dotBV, dotTL, dotBL, dotNV, dotNL) ;\nreturn G * D ; }\n\n"],[6],[0,"\nvec3 BRDF_Specular_GGX ( vec3 incidentDirection, SurfaceData surfaceData, BSDFData bsdfData, vec3 normal, vec3 specularColor, float roughness ) { float alpha = pow2(roughness) ;\nvec3 halfDir = normalize ( incidentDirection + surfaceData.viewDir ) ;\nfloat dotNL = saturate(dot ( normal , incidentDirection )) ;\nfloat dotNV = saturate(dot ( normal , surfaceData.viewDir )) ;\nfloat dotNH = saturate(dot ( normal , halfDir )) ;\nfloat dotLH = saturate(dot ( incidentDirection , halfDir )) ;\nvec3 F = F_Schlick(specularColor, bsdfData.specularF90, dotLH) ;\n\n"],[1,"MATERIAL_ENABLE_IRIDESCENCE",362],[0," F = mix ( F , bsdfData.iridescenceSpecularColor , surfaceData.iridescenceFactor ) ; \n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_ANISOTROPY",366],[0," float GD = DG_GGX_anisotropic(halfDir, incidentDirection, surfaceData, alpha, dotNV, dotNL, dotNH) ; \n"],[5,368],[0," float GD = DG_GGX(alpha, dotNV, dotNL, dotNH) ; \n"],[6],[0,"\nreturn F * GD ; }\nvec3 BRDF_Diffuse_Lambert ( vec3 diffuseColor ) { return RECIPROCAL_PI * diffuseColor ; }\n\n"],[1,"MATERIAL_ENABLE_IRIDESCENCE",372],[0,"\nvec3 iorToFresnel0 ( vec3 transmittedIOR, float incidentIOR ) { return pow ( ( transmittedIOR - incidentIOR ) / ( transmittedIOR + incidentIOR ) , vec3 ( 2.0 ) ) ; }\nfloat iorToFresnel0 ( float transmittedIOR, float incidentIOR ) { return pow ( ( transmittedIOR - incidentIOR ) / ( transmittedIOR + incidentIOR ) , 2.0 ) ; }\nvec3 fresnelToIOR ( vec3 f0 ) { vec3 sqrtF0 = sqrt ( f0 ) ;\nreturn ( vec3 ( 1.0 ) + sqrtF0 ) / ( vec3 ( 1.0 ) - sqrtF0 ) ; }\nvec3 evalSensitivity ( float opd, vec3 shift ) { float phase = 2.0 * PI * opd * 1.0e-9 ;\nconst vec3 val = vec3 ( 5.4856e-13 , 4.4201e-13 , 5.2481e-13 ) ;\nconst vec3 pos = vec3 ( 1.6810e+06 , 1.7953e+06 , 2.2084e+06 ) ;\nconst vec3 var = vec3 ( 4.3278e+09 , 9.3046e+09 , 6.6121e+09 ) ;\nvec3 xyz = val * sqrt ( 2.0 * PI * var ) * cos ( pos * phase + shift ) * exp ( - var * pow2(phase) ) ;\nxyz.x += 9.7470e-14 * sqrt ( 2.0 * PI * 4.5282e+09 ) * cos ( 2.2399e+06 * phase + shift[0] ) * exp ( - 4.5282e+09 * pow2(phase) ) ;\nxyz /= 1.0685e-7 ;\nconst mat3 XYZ_TO_RGB = mat3 ( 3.2404542 , - 0.9692660 , 0.0556434 , - 1.5371385 , 1.8760108 , - 0.2040259 , - 0.4985314 , 0.0415560 , 1.0572252 ) ;\nvec3 rgb = XYZ_TO_RGB * xyz ;\nreturn rgb ; }\nvec3 evalIridescenceSpecular ( float outsideIOR, float dotNV, float thinIOR, vec3 baseF0, float baseF90, float iridescenceThickness ) { vec3 iridescence = vec3 ( 1.0 ) ;\nfloat iridescenceIOR = mix ( outsideIOR , thinIOR , smoothstep ( 0.0 , 0.03 , iridescenceThickness ) ) ;\nfloat sinTheta2Sq = pow ( outsideIOR / iridescenceIOR , 2.0 ) * ( 1.0 - pow ( dotNV , 2.0 ) ) ;\nfloat cosTheta2Sq = 1.0 - sinTheta2Sq ;\nif ( cosTheta2Sq < 0.0 ) { return iridescence ; }\nfloat cosTheta2 = sqrt ( cosTheta2Sq ) ;\nfloat f0 = iorToFresnel0(iridescenceIOR, outsideIOR) ;\nfloat reflectance = F_Schlick(f0, baseF90, dotNV) ;\nfloat t121 = 1.0 - reflectance ;\nfloat phi12 = 0.0 ;\nfloat phi21 = PI - phi12 ;\nvec3 baseIOR = fresnelToIOR(clamp ( baseF0 , 0.0 , 0.9999 )) ;\nvec3 r1 = iorToFresnel0(baseIOR, iridescenceIOR) ;\nvec3 r23 = F_Schlick(r1, baseF90, cosTheta2) ;\nvec3 phi23 = vec3 ( 0.0 ) ;\nif ( baseIOR[0] < iridescenceIOR ) { phi23[0] = PI ; }\nif ( baseIOR[1] < iridescenceIOR ) { phi23[1] = PI ; }\nif ( baseIOR[2] < iridescenceIOR ) { phi23[2] = PI ; }\nfloat opd = 2.0 * iridescenceIOR * iridescenceThickness * cosTheta2 ;\nvec3 phi = vec3 ( phi21 ) + phi23 ;\nvec3 r123 = clamp ( reflectance * r23 , 1e-5 , 0.9999 ) ;\nvec3 sr123 = sqrt ( r123 ) ;\nvec3 rs = pow2(t121) * r23 / ( vec3 ( 1.0 ) - r123 ) ;\nvec3 c0 = reflectance + rs ;\niridescence = c0 ;\nvec3 cm = rs - t121 ;\nfor ( int m = 1 ; m <= 2 ; ++ m ) { cm *= sr123 ;\nvec3 sm = 2.0 * evalSensitivity(float ( m ) * opd, float ( m ) * phi) ;\niridescence += cm * sm ; }\nreturn iridescence = max ( iridescence , vec3 ( 0.0 ) ) ; }\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_SHEEN",382],[0,"\nfloat D_Charlie ( float roughness, float dotNH ) { float invAlpha = 1.0 / roughness ;\nfloat cos2h = dotNH * dotNH ;\nfloat sin2h = max ( 1.0 - cos2h , 0.0078125 ) ;\nreturn ( 2.0 + invAlpha ) * pow ( sin2h , invAlpha * 0.5 ) / ( 2.0 * PI ) ; }\nfloat V_Neubelt ( float NoV, float NoL ) { return saturate(1.0 / ( 4.0 * ( NoL + NoV - NoL * NoV ) )) ; }\nvec3 sheenBRDF ( vec3 incidentDirection, SurfaceData surfaceData, vec3 sheenColor, float sheenRoughness ) { vec3 halfDir = normalize ( incidentDirection + surfaceData.viewDir ) ;\nfloat dotNL = saturate(dot ( surfaceData.normal , incidentDirection )) ;\nfloat dotNH = saturate(dot ( surfaceData.normal , halfDir )) ;\nfloat D = D_Charlie(sheenRoughness, dotNH) ;\nfloat V = V_Neubelt(surfaceData.dotNV, dotNL) ;\nvec3 F = sheenColor ;\nreturn D * V * F ; }\nfloat prefilteredSheenDFG ( float dotNV, float sheenRoughness ) { \n"],[1,"HAS_TEX_LOD",378],[0," return texture2DLodEXT ( scene_PrefilteredDFG , vec2 ( dotNV , sheenRoughness ) , 0.0 ).b ; \n"],[5,380],[0," return texture2D ( scene_PrefilteredDFG , vec2 ( dotNV , sheenRoughness ) , 0.0 ).b ; \n"],[6],[0," }\n\n"],[6],[0,"\nvec2 envDFGApprox ( float roughness, float dotNV ) { const vec4 c0 = vec4 ( - 1 , - 0.0275 , - 0.572 , 0.022 ) ;\nconst vec4 c1 = vec4 ( 1 , 0.0425 , 1.04 , - 0.04 ) ;\nvec4 r = roughness * c0 + c1 ;\nfloat a004 = min ( r.x * r.x , exp2 ( - 9.28 * dotNV ) ) * r.x + r.y ;\nreturn vec2 ( - 1.04 , 1.04 ) * a004 + r.zw ; }\nvec3 envBRDFApprox ( vec3 f0, float f90, float roughness, float dotNV ) { vec2 AB = envDFGApprox(roughness, dotNV) ;\nreturn f0 * AB.x + f90 * AB.y ; }\n\n"],[1,"MATERIAL_ENABLE_TRANSMISSION",397],[0,"\nuniform sampler2D camera_OpaqueTexture;\nvec3 evaluateTransmission ( SurfaceData surfaceData, BSDFData bsdfData ) { RefractionModelResult ray ;\n\n"],[3,"REFRACTION_MODE","==",0,388],[0," refractionModelSphere(- surfaceData.viewDir, surfaceData.position, surfaceData.normal, surfaceData.IOR, surfaceData.thickness, ray) ; \n"],[5,391],[3,"REFRACTION_MODE","==",1,391],[0," refractionModelPlanar(- surfaceData.viewDir, surfaceData.position, surfaceData.normal, surfaceData.IOR, surfaceData.thickness, ray) ; \n"],[6],[0,"\nvec3 refractedRayExit = ray.positionExit ;\nvec4 samplingPositionNDC = camera_ProjMat * camera_ViewMat * vec4 ( refractedRayExit , 1.0 ) ;\nvec2 refractionCoords = ( samplingPositionNDC.xy / samplingPositionNDC.w ) * 0.5 + 0.5 ;\nvec3 refractionTransmitted = texture2DSRGB(camera_OpaqueTexture, refractionCoords).rgb ;\nrefractionTransmitted *= bsdfData.diffuseColor ;\nrefractionTransmitted *= ( 1.0 - max ( max ( bsdfData.envSpecularDFG.r , bsdfData.envSpecularDFG.g ) , bsdfData.envSpecularDFG.b ) ) ;\n\n"],[1,"MATERIAL_HAS_THICKNESS",395],[0," vec3 transmittance = min ( vec3 ( 1.0 ) , exp ( - surfaceData.absorptionCoefficient * ray.transmissionLength ) ) ;\nrefractionTransmitted *= transmittance ; \n"],[6],[0,"\nreturn refractionTransmitted ; }\n\n"],[6],[0,"\n\n"],[1,"SCENE_ENABLE_AMBIENT_OCCLUSION",407],[0,"\nuniform sampler2D camera_AOTexture;\nfloat evaluateAmbientOcclusion ( vec2 uv ) { \n"],[1,"MATERIAL_IS_TRANSPARENT",403],[0," return 1.0 ; \n"],[5,405],[0," return texture2D ( camera_AOTexture , uv ).r ; \n"],[6],[0," }\n\n"],[6],[0,"\nvoid initBSDFData ( SurfaceData surfaceData, out BSDFData bsdfData ) { vec3 albedoColor = surfaceData.albedoColor ;\nfloat metallic = surfaceData.metallic ;\nfloat roughness = surfaceData.roughness ;\nvec3 dielectricBaseF0 = vec3 ( pow2(( surfaceData.IOR - 1.0 ) / ( surfaceData.IOR + 1.0 )) ) ;\nvec3 dielectricF0 = min ( dielectricBaseF0 * surfaceData.specularColor , vec3 ( 1.0 ) ) * surfaceData.specularIntensity ;\nfloat dielectricF90 = surfaceData.specularIntensity ;\nbsdfData.specularF0 = mix ( dielectricF0 , albedoColor , metallic ) ;\nbsdfData.specularF90 = mix ( dielectricF90 , 1.0 , metallic ) ;\nbsdfData.diffuseColor = albedoColor * ( 1.0 - metallic ) * ( 1.0 - max ( max ( dielectricF0.r , dielectricF0.g ) , dielectricF0.b ) ) ;\nbsdfData.roughness = max ( MIN_PERCEPTUAL_ROUGHNESS , min ( roughness + getAARoughnessFactor(surfaceData.normal) , 1.0 ) ) ;\n\n"],[1,"MATERIAL_ENABLE_IRIDESCENCE",411],[0," float topIOR = 1.0 ;\nbsdfData.iridescenceSpecularColor = evalIridescenceSpecular(topIOR, surfaceData.dotNV, surfaceData.iridescenceIOR, bsdfData.specularF0, bsdfData.specularF90, surfaceData.iridescenceThickness) ; \n"],[6],[0,"\nbsdfData.resolvedSpecularF0 = bsdfData.specularF0 ;\n\n"],[1,"MATERIAL_ENABLE_IRIDESCENCE",415],[0," bsdfData.resolvedSpecularF0 = mix ( bsdfData.resolvedSpecularF0 , bsdfData.iridescenceSpecularColor , surfaceData.iridescenceFactor ) ; \n"],[6],[0,"\nvec2 dfg = envDFGApprox(bsdfData.roughness, surfaceData.dotNV) ;\nbsdfData.envSpecularDFG = bsdfData.resolvedSpecularF0 * dfg.x + bsdfData.specularF90 * dfg.y ;\nbsdfData.energyCompensation = 1.0 + bsdfData.resolvedSpecularF0 * ( 1.0 / max ( dfg.x + dfg.y , EPSILON ) - 1.0 ) ;\nbsdfData.diffuseAO = surfaceData.ambientOcclusion ;\n\n"],[1,"SCENE_ENABLE_AMBIENT_OCCLUSION",419],[0," float ambientAO = evaluateAmbientOcclusion(( surfaceData.positionCS.xy / surfaceData.positionCS.w ) * 0.5 + 0.5) ;\nbsdfData.diffuseAO = min ( bsdfData.diffuseAO , ambientAO ) ; \n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_CLEAR_COAT",423],[0," bsdfData.clearCoatRoughness = max ( MIN_PERCEPTUAL_ROUGHNESS , min ( surfaceData.clearCoatRoughness + getAARoughnessFactor(surfaceData.clearCoatNormal) , 1.0 ) ) ;\nbsdfData.clearCoatSpecularColor = vec3 ( 0.04 ) ; \n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_SHEEN",427],[0," bsdfData.sheenRoughness = max ( MIN_PERCEPTUAL_ROUGHNESS , min ( surfaceData.sheenRoughness + getAARoughnessFactor(surfaceData.normal) , 1.0 ) ) ;\nbsdfData.approxIBLSheenDG = prefilteredSheenDFG(surfaceData.dotNV, bsdfData.sheenRoughness) ;\nbsdfData.sheenScaling = 1.0 - bsdfData.approxIBLSheenDG * max ( max ( surfaceData.sheenColor.r , surfaceData.sheenColor.g ) , surfaceData.sheenColor.b ) ; \n"],[6],[0," }\n\n"],[6],[0,"\n\n"],[2,"LIGHT_INCLUDED",477],[0,"\n\n"],[7,"LIGHT_INCLUDED"],[0,"\nuniform ivec4 renderer_Layer;\n\n"],[2,"GRAPHICS_API_WEBGL2",437],[0,"\nbool isBitSet ( float value, float mask, float bitIndex ) { return mod ( floor ( value / pow ( 2.0 , bitIndex ) ) , 2.0 ) == 1.0 && mod ( floor ( mask / pow ( 2.0 , bitIndex ) ) , 2.0 ) == 1.0 ; }\n\n"],[6],[0,"\nbool isRendererCulledByLight ( ivec2 rendererLayer, ivec2 lightCullingMask ) { \n"],[1,"GRAPHICS_API_WEBGL2",441],[0," return ! ( ( rendererLayer.x & lightCullingMask.x ) != 0 || ( rendererLayer.y & lightCullingMask.y ) != 0 ) ; \n"],[5,443],[0," for ( int i = 0 ; i < 16 ; i ++ ) { if ( isBitSet(float ( rendererLayer.x ), float ( lightCullingMask.x ), float ( i )) || isBitSet(float ( rendererLayer.y ), float ( lightCullingMask.y ), float ( i )) ) { return false ; } }\nreturn true ; \n"],[6],[0," }\n\n"],[1,"SCENE_DIRECT_LIGHT_COUNT",451],[0,"\nstruct DirectLight { vec3 color ; vec3 direction ; } ;\nuniform ivec2 scene_DirectLightCullingMask [ SCENE_DIRECT_LIGHT_COUNT ];\nuniform vec3 scene_DirectLightColor [ SCENE_DIRECT_LIGHT_COUNT ];\nuniform vec3 scene_DirectLightDirection [ SCENE_DIRECT_LIGHT_COUNT ];\n\n"],[1,"GRAPHICS_API_WEBGL2",449],[0,"\nDirectLight getDirectLight ( int index ) { DirectLight light ;\nlight.color = scene_DirectLightColor[index] ;\nlight.direction = scene_DirectLightDirection[index] ;\nreturn light ; }\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[1,"SCENE_POINT_LIGHT_COUNT",459],[0,"\nstruct PointLight { vec3 color ; vec3 position ; float distance ; } ;\nuniform ivec2 scene_PointLightCullingMask [ SCENE_POINT_LIGHT_COUNT ];\nuniform vec3 scene_PointLightColor [ SCENE_POINT_LIGHT_COUNT ];\nuniform vec3 scene_PointLightPosition [ SCENE_POINT_LIGHT_COUNT ];\nuniform float scene_PointLightDistance [ SCENE_POINT_LIGHT_COUNT ];\n\n"],[1,"GRAPHICS_API_WEBGL2",457],[0,"\nPointLight getPointLight ( int index ) { PointLight light ;\nlight.color = scene_PointLightColor[index] ;\nlight.position = scene_PointLightPosition[index] ;\nlight.distance = scene_PointLightDistance[index] ;\nreturn light ; }\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[1,"SCENE_SPOT_LIGHT_COUNT",467],[0,"\nstruct SpotLight { vec3 color ; vec3 position ; vec3 direction ; float distance ; float angleCos ; float penumbraCos ; } ;\nuniform ivec2 scene_SpotLightCullingMask [ SCENE_SPOT_LIGHT_COUNT ];\nuniform vec3 scene_SpotLightColor [ SCENE_SPOT_LIGHT_COUNT ];\nuniform vec3 scene_SpotLightPosition [ SCENE_SPOT_LIGHT_COUNT ];\nuniform vec3 scene_SpotLightDirection [ SCENE_SPOT_LIGHT_COUNT ];\nuniform float scene_SpotLightDistance [ SCENE_SPOT_LIGHT_COUNT ];\nuniform float scene_SpotLightAngleCos [ SCENE_SPOT_LIGHT_COUNT ];\nuniform float scene_SpotLightPenumbraCos [ SCENE_SPOT_LIGHT_COUNT ];\n\n"],[1,"GRAPHICS_API_WEBGL2",465],[0,"\nSpotLight getSpotLight ( int index ) { SpotLight light ;\nlight.color = scene_SpotLightColor[index] ;\nlight.position = scene_SpotLightPosition[index] ;\nlight.direction = scene_SpotLightDirection[index] ;\nlight.distance = scene_SpotLightDistance[index] ;\nlight.angleCos = scene_SpotLightAngleCos[index] ;\nlight.penumbraCos = scene_SpotLightPenumbraCos[index] ;\nreturn light ; }\n\n"],[6],[0,"\n\n"],[6],[0,"\nstruct EnvMapLight { vec3 diffuse ; float mipMapLevel ; float diffuseIntensity ; float specularIntensity ; } ;\nuniform EnvMapLight scene_EnvMapLight;\n\n"],[1,"SCENE_USE_SH",471],[0,"\nuniform vec3 scene_EnvSH [ 9 ];\n\n"],[6],[0,"\n\n"],[1,"SCENE_USE_SPECULAR_ENV",475],[0,"\nuniform samplerCube scene_EnvSpecularSampler;\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"REFLECTION_LOBE_INCLUDED",491],[0,"\n\n"],[7,"REFLECTION_LOBE_INCLUDED"],[0,"\nvoid diffuseLobe ( SurfaceData surfaceData, BSDFData bsdfData, vec3 attenuationIrradiance, inout vec3 diffuseColor ) { diffuseColor += attenuationIrradiance * BRDF_Diffuse_Lambert(bsdfData.diffuseColor) ; }\nvoid specularLobe ( SurfaceData surfaceData, BSDFData bsdfData, vec3 incidentDirection, vec3 attenuationIrradiance, inout vec3 specularColor ) { specularColor += attenuationIrradiance * BRDF_Specular_GGX(incidentDirection, surfaceData, bsdfData, surfaceData.normal, bsdfData.specularF0, bsdfData.roughness) * bsdfData.energyCompensation ; }\nvoid sheenLobe ( SurfaceData surfaceData, BSDFData bsdfData, vec3 incidentDirection, vec3 attenuationIrradiance, inout vec3 diffuseColor, inout vec3 specularColor ) { \n"],[1,"MATERIAL_ENABLE_SHEEN",485],[0," diffuseColor *= bsdfData.sheenScaling ;\nspecularColor *= bsdfData.sheenScaling ;\nspecularColor += attenuationIrradiance * sheenBRDF(incidentDirection, surfaceData, surfaceData.sheenColor, bsdfData.sheenRoughness) ; \n"],[6],[0," }\nfloat clearCoatLobe ( SurfaceData surfaceData, BSDFData bsdfData, vec3 incidentDirection, vec3 color, inout vec3 specularColor ) { float attenuation = 1.0 ;\n\n"],[1,"MATERIAL_ENABLE_CLEAR_COAT",489],[0," float clearCoatDotNL = saturate(dot ( surfaceData.clearCoatNormal , incidentDirection )) ;\nvec3 clearCoatIrradiance = clearCoatDotNL * color ;\nspecularColor += surfaceData.clearCoat * clearCoatIrradiance * BRDF_Specular_GGX(incidentDirection, surfaceData, bsdfData, surfaceData.clearCoatNormal, bsdfData.clearCoatSpecularColor, bsdfData.clearCoatRoughness) ;\nattenuation -= surfaceData.clearCoat * F_Schlick(0.04, 1.0, surfaceData.clearCoatDotNV) ; \n"],[6],[0,"\nreturn attenuation ; }\n\n"],[6],[0,"\nvoid surfaceShading ( SurfaceData surfaceData, BSDFData bsdfData, vec3 incidentDirection, vec3 lightColor, inout vec3 totalDiffuseColor, inout vec3 totalSpecularColor ) { vec3 diffuseColor = vec3 ( 0 ) ;\nvec3 specularColor = vec3 ( 0 ) ;\nfloat dotNL = saturate(dot ( surfaceData.normal , incidentDirection )) ;\nvec3 irradiance = dotNL * lightColor * PI ;\nfloat attenuation = FUNCTION_CLEAR_COAT_LOBE(surfaceData, bsdfData, incidentDirection, lightColor, specularColor) ;\nvec3 attenuationIrradiance = attenuation * irradiance ;\nFUNCTION_DIFFUSE_LOBE(surfaceData, bsdfData, attenuationIrradiance, diffuseColor) ;\nFUNCTION_SPECULAR_LOBE(surfaceData, bsdfData, incidentDirection, attenuationIrradiance, specularColor) ;\nFUNCTION_SHEEN_LOBE(surfaceData, bsdfData, incidentDirection, attenuationIrradiance, diffuseColor, specularColor) ;\ntotalDiffuseColor += diffuseColor ;\ntotalSpecularColor += specularColor ; }\n\n"],[1,"SCENE_DIRECT_LIGHT_COUNT",495],[0,"\nvoid addDirectionalDirectLightRadiance ( SurfaceData surfaceData, BSDFData bsdfData, DirectLight directionalLight, inout vec3 totalDiffuseColor, inout vec3 totalSpecularColor ) { vec3 lightColor = directionalLight.color ;\nvec3 direction = - directionalLight.direction ;\nFUNCTION_SURFACE_SHADING(surfaceData, bsdfData, direction, lightColor, totalDiffuseColor, totalSpecularColor) ; }\n\n"],[6],[0,"\n\n"],[1,"SCENE_POINT_LIGHT_COUNT",499],[0,"\nvoid addPointDirectLightRadiance ( SurfaceData surfaceData, BSDFData bsdfData, PointLight pointLight, inout vec3 totalDiffuseColor, inout vec3 totalSpecularColor ) { vec3 lVector = pointLight.position - surfaceData.position ;\nvec3 direction = normalize ( lVector ) ;\nfloat lightDistance = length ( lVector ) ;\nvec3 lightColor = pointLight.color ;\nlightColor *= clamp ( 1.0 - pow ( lightDistance / pointLight.distance , 4.0 ) , 0.0 , 1.0 ) ;\nFUNCTION_SURFACE_SHADING(surfaceData, bsdfData, direction, lightColor, totalDiffuseColor, totalSpecularColor) ; }\n\n"],[6],[0,"\n\n"],[1,"SCENE_SPOT_LIGHT_COUNT",503],[0,"\nvoid addSpotDirectLightRadiance ( SurfaceData surfaceData, BSDFData bsdfData, SpotLight spotLight, inout vec3 totalDiffuseColor, inout vec3 totalSpecularColor ) { vec3 lVector = spotLight.position - surfaceData.position ;\nvec3 direction = normalize ( lVector ) ;\nfloat lightDistance = length ( lVector ) ;\nfloat angleCos = dot ( direction , - spotLight.direction ) ;\nfloat spotEffect = smoothstep ( spotLight.penumbraCos , spotLight.angleCos , angleCos ) ;\nfloat decayEffect = clamp ( 1.0 - pow ( lightDistance / spotLight.distance , 4.0 ) , 0.0 , 1.0 ) ;\nvec3 lightColor = spotLight.color ;\nlightColor *= spotEffect * decayEffect ;\nFUNCTION_SURFACE_SHADING(surfaceData, bsdfData, direction, lightColor, totalDiffuseColor, totalSpecularColor) ; }\n\n"],[6],[0,"\nvoid evaluateDirectRadiance ( SurfaceData surfaceData, BSDFData bsdfData, float shadowAttenuation, inout vec3 totalDiffuseColor, inout vec3 totalSpecularColor ) { \n"],[1,"SCENE_DIRECT_LIGHT_COUNT",517],[0," for ( int i = 0 ; i < SCENE_DIRECT_LIGHT_COUNT ; i ++ ) { if ( ! isRendererCulledByLight(renderer_Layer.xy, scene_DirectLightCullingMask[i]) ) { \n"],[1,"GRAPHICS_API_WEBGL2",509],[0," DirectLight directionalLight = getDirectLight(i) ; \n"],[5,511],[0," DirectLight directionalLight ;\ndirectionalLight.color = scene_DirectLightColor[i] ;\ndirectionalLight.direction = scene_DirectLightDirection[i] ; \n"],[6],[0,"\n\n"],[1,"NEED_CALCULATE_SHADOWS",515],[0," if ( i == 0 ) { directionalLight.color *= shadowAttenuation ; } \n"],[6],[0,"\naddDirectionalDirectLightRadiance(surfaceData, bsdfData, directionalLight, totalDiffuseColor, totalSpecularColor) ; } } \n"],[6],[0,"\n\n"],[1,"SCENE_POINT_LIGHT_COUNT",527],[0," for ( int i = 0 ; i < SCENE_POINT_LIGHT_COUNT ; i ++ ) { if ( ! isRendererCulledByLight(renderer_Layer.xy, scene_PointLightCullingMask[i]) ) { \n"],[1,"GRAPHICS_API_WEBGL2",523],[0," PointLight pointLight = getPointLight(i) ; \n"],[5,525],[0," PointLight pointLight ;\npointLight.color = scene_PointLightColor[i] ;\npointLight.position = scene_PointLightPosition[i] ;\npointLight.distance = scene_PointLightDistance[i] ; \n"],[6],[0,"\naddPointDirectLightRadiance(surfaceData, bsdfData, pointLight, totalDiffuseColor, totalSpecularColor) ; } } \n"],[6],[0,"\n\n"],[1,"SCENE_SPOT_LIGHT_COUNT",537],[0," for ( int i = 0 ; i < SCENE_SPOT_LIGHT_COUNT ; i ++ ) { if ( ! isRendererCulledByLight(renderer_Layer.xy, scene_SpotLightCullingMask[i]) ) { \n"],[1,"GRAPHICS_API_WEBGL2",533],[0," SpotLight spotLight = getSpotLight(i) ; \n"],[5,535],[0," SpotLight spotLight ;\nspotLight.color = scene_SpotLightColor[i] ;\nspotLight.position = scene_SpotLightPosition[i] ;\nspotLight.direction = scene_SpotLightDirection[i] ;\nspotLight.distance = scene_SpotLightDistance[i] ;\nspotLight.angleCos = scene_SpotLightAngleCos[i] ;\nspotLight.penumbraCos = scene_SpotLightPenumbraCos[i] ; \n"],[6],[0,"\naddSpotDirectLightRadiance(surfaceData, bsdfData, spotLight, totalDiffuseColor, totalSpecularColor) ; } } \n"],[6],[0," }\n\n"],[6],[0,"\n\n"],[2,"LIGHT_INDIRECT_PBR_INCLUDED",615],[0,"\n\n"],[7,"LIGHT_INDIRECT_PBR_INCLUDED"],[0,"\n\n"],[2,"FUNCTION_DIFFUSE_IBL",549],[0,"\n\n"],[8,"FUNCTION_DIFFUSE_IBL","evaluateDiffuseIBL"],[0,"\n\n"],[6],[0,"\n\n"],[2,"FUNCTION_SPECULAR_IBL",555],[0,"\n\n"],[8,"FUNCTION_SPECULAR_IBL","evaluateSpecularIBL"],[0,"\n\n"],[6],[0,"\n\n"],[2,"FUNCTION_CLEAR_COAT_IBL",561],[0,"\n\n"],[8,"FUNCTION_CLEAR_COAT_IBL","evaluateClearCoatIBL"],[0,"\n\n"],[6],[0,"\n\n"],[2,"FUNCTION_SHEEN_IBL",567],[0,"\n\n"],[8,"FUNCTION_SHEEN_IBL","evaluateSheenIBL"],[0,"\n\n"],[6],[0,"\n\n"],[2,"LIGHT_INDIRECT_FUNCTIONS_INCLUDED",599],[0,"\n\n"],[7,"LIGHT_INDIRECT_FUNCTIONS_INCLUDED"],[0,"\nvec3 getReflectedVector ( SurfaceData surfaceData, vec3 n ) { \n"],[1,"MATERIAL_ENABLE_ANISOTROPY",575],[0," vec3 r = reflect ( - surfaceData.viewDir , surfaceData.anisotropicN ) ; \n"],[5,577],[0," vec3 r = reflect ( - surfaceData.viewDir , n ) ; \n"],[6],[0,"\nreturn r ; }\nfloat getSpecularMIPLevel ( float roughness, int maxMIPLevel ) { return roughness * float ( maxMIPLevel ) ; }\nvec3 getLightProbeRadiance ( SurfaceData surfaceData, vec3 normal, float roughness ) { \n"],[2,"SCENE_USE_SPECULAR_ENV",581],[0," return vec3 ( 0 ) ; \n"],[5,593],[0," vec3 reflectVec = getReflectedVector(surfaceData, normal) ;\nfloat specularMIPLevel = getSpecularMIPLevel(roughness, int ( scene_EnvMapLight.mipMapLevel )) ;\n\n"],[1,"HAS_TEX_LOD",585],[0," vec4 envMapColor = textureCubeLodEXT ( scene_EnvSpecularSampler , reflectVec , specularMIPLevel ) ; \n"],[5,587],[0," vec4 envMapColor = textureCube ( scene_EnvSpecularSampler , reflectVec , specularMIPLevel ) ; \n"],[6],[0,"\n\n"],[1,"ENGINE_NO_SRGB",591],[0," envMapColor = sRGBToLinear(envMapColor) ; \n"],[6],[0,"\nreturn envMapColor.rgb * scene_EnvMapLight.specularIntensity ; \n"],[6],[0," }\nfloat evaluateSpecularOcclusion ( float dotNV, float diffuseAO, float roughness ) { float specularAOFactor = 1.0 ;\n\n"],[4,{"t":"and","l":{"t":"or","l":{"t":"def","m":"MATERIAL_HAS_OCCLUSION_TEXTURE"},"r":{"t":"def","m":"SCENE_ENABLE_AMBIENT_OCCLUSION"}},"r":{"t":"def","m":"SCENE_USE_SPECULAR_ENV"}},597],[0," specularAOFactor = saturate(pow ( dotNV + diffuseAO , exp2 ( - 16.0 * roughness - 1.0 ) ) - 1.0 + diffuseAO) ; \n"],[6],[0,"\nreturn specularAOFactor ; }\n\n"],[6],[0,"\nvec3 getLightProbeIrradiance ( vec3 sh [ 9 ], vec3 normal ) { vec3 result = sh[0] + sh[1] * ( normal.y ) + sh[2] * ( normal.z ) + sh[3] * ( normal.x ) + sh[4] * ( normal.y * normal.x ) + sh[5] * ( normal.y * normal.z ) + sh[6] * ( 3.0 * normal.z * normal.z - 1.0 ) + sh[7] * ( normal.z * normal.x ) + sh[8] * ( normal.x * normal.x - normal.y * normal.y ) ;\nreturn max ( result , vec3 ( 0.0 ) ) ; }\nvoid evaluateDiffuseIBL ( SurfaceData surfaceData, BSDFData bsdfData, inout vec3 diffuseColor ) { \n"],[1,"SCENE_USE_SH",603],[0," vec3 irradiance = getLightProbeIrradiance(scene_EnvSH, surfaceData.normal) ;\nirradiance *= scene_EnvMapLight.diffuseIntensity ; \n"],[5,605],[0," vec3 irradiance = scene_EnvMapLight.diffuse * scene_EnvMapLight.diffuseIntensity ;\nirradiance *= PI ; \n"],[6],[0,"\ndiffuseColor += bsdfData.diffuseAO * irradiance * BRDF_Diffuse_Lambert(bsdfData.diffuseColor) ; }\nfloat evaluateClearCoatIBL ( SurfaceData surfaceData, BSDFData bsdfData, inout vec3 specularColor ) { float radianceAttenuation = 1.0 ;\n\n"],[1,"MATERIAL_ENABLE_CLEAR_COAT",609],[0," vec3 clearCoatRadiance = getLightProbeRadiance(surfaceData, surfaceData.clearCoatNormal, bsdfData.clearCoatRoughness) ;\nfloat specularAO = evaluateSpecularOcclusion(surfaceData.dotNV, bsdfData.diffuseAO, bsdfData.clearCoatRoughness) ;\nspecularColor += specularAO * clearCoatRadiance * surfaceData.clearCoat * envBRDFApprox(bsdfData.clearCoatSpecularColor, 1.0, bsdfData.clearCoatRoughness, surfaceData.clearCoatDotNV) ;\nradianceAttenuation -= surfaceData.clearCoat * F_Schlick(0.04, 1.0, surfaceData.clearCoatDotNV) ; \n"],[6],[0,"\nreturn radianceAttenuation ; }\nvoid evaluateSpecularIBL ( SurfaceData surfaceData, BSDFData bsdfData, float radianceAttenuation, inout vec3 outSpecularColor ) { vec3 radiance = getLightProbeRadiance(surfaceData, surfaceData.normal, bsdfData.roughness) ;\nfloat specularAO = evaluateSpecularOcclusion(surfaceData.dotNV, bsdfData.diffuseAO, bsdfData.roughness) ;\noutSpecularColor += specularAO * radianceAttenuation * radiance * envBRDFApprox(bsdfData.resolvedSpecularF0, bsdfData.specularF90, bsdfData.roughness, surfaceData.dotNV) * bsdfData.energyCompensation ; }\nvoid evaluateSheenIBL ( SurfaceData surfaceData, BSDFData bsdfData, float radianceAttenuation, inout vec3 diffuseColor, inout vec3 specularColor ) { \n"],[1,"MATERIAL_ENABLE_SHEEN",613],[0," diffuseColor *= bsdfData.sheenScaling ;\nspecularColor *= bsdfData.sheenScaling ;\nfloat specularAO = evaluateSpecularOcclusion(surfaceData.dotNV, bsdfData.diffuseAO, bsdfData.sheenRoughness) ;\nvec3 reflectance = specularAO * radianceAttenuation * bsdfData.approxIBLSheenDG * surfaceData.sheenColor ;\nspecularColor += reflectance ; \n"],[6],[0," }\nvoid evaluateIBL ( SurfaceData surfaceData, BSDFData bsdfData, inout vec3 totalDiffuseColor, inout vec3 totalSpecularColor ) { vec3 diffuseColor = vec3 ( 0 ) ;\nvec3 specularColor = vec3 ( 0 ) ;\nFUNCTION_DIFFUSE_IBL(surfaceData, bsdfData, diffuseColor) ;\nfloat radianceAttenuation = FUNCTION_CLEAR_COAT_IBL(surfaceData, bsdfData, specularColor) ;\nFUNCTION_SPECULAR_IBL(surfaceData, bsdfData, radianceAttenuation, specularColor) ;\nFUNCTION_SHEEN_IBL(surfaceData, bsdfData, radianceAttenuation, diffuseColor, specularColor) ;\ntotalDiffuseColor += diffuseColor ;\ntotalSpecularColor += specularColor ; }\n\n"],[6],[0,"\n\n"],[2,"VERTEX_INCLUDE",621],[0,"\n\n"],[7,"VERTEX_INCLUDE"],[0,"\n\n"],[6],[0,"\n\n"],[2,"MATERIAL_INPUT_PBR_INCLUDED",872],[0,"\n\n"],[7,"MATERIAL_INPUT_PBR_INCLUDED"],[0,"\n\n"],[2,"NORMAL_INCLUDED",637],[0,"\n\n"],[7,"NORMAL_INCLUDED"],[0,"\nvec3 getNormalByNormalTexture ( mat3 tbn, sampler2D normalTexture, float normalIntensity, vec2 uv, bool isFrontFacing ) { vec3 normal = ( texture2D ( normalTexture , uv ) ).rgb ;\nnormal = normalize ( tbn * ( ( 2.0 * normal - 1.0 ) * vec3 ( normalIntensity , normalIntensity , 1.0 ) ) ) ;\nnormal *= float ( isFrontFacing ) * 2.0 - 1.0 ;\nreturn normal ; }\nmat3 getTBNByDerivatives ( vec2 uv, vec3 normal, vec3 position, bool isFrontFacing ) { \n"],[1,"HAS_DERIVATIVES",633],[0," uv = isFrontFacing ? uv : - uv ;\nvec3 dp1 = dFdx ( position ) ;\nvec3 dp2 = dFdy ( position ) ;\nvec2 duv1 = dFdx ( uv ) ;\nvec2 duv2 = dFdy ( uv ) ;\nvec3 dp2perp = cross ( dp2 , normal ) ;\nvec3 dp1perp = cross ( normal , dp1 ) ;\nvec3 tangent = dp2perp * duv1.x + dp1perp * duv2.x ;\nvec3 bitangent = dp2perp * duv1.y + dp1perp * duv2.y ;\nfloat denom = max ( dot ( tangent , tangent ) , dot ( bitangent , bitangent ) ) ;\nfloat invmax = ( denom == 0.0 ) ? 0.0 : camera_ProjectionParams.x / sqrt ( denom ) ;\nreturn mat3 ( tangent * invmax , bitangent * invmax , normal ) ; \n"],[5,635],[0," return mat3 ( vec3 ( 0.0 ) , vec3 ( 0.0 ) , normal ) ; \n"],[6],[0," }\n\n"],[6],[0,"\nuniform float material_AlphaCutoff;\nuniform vec4 material_BaseColor;\nuniform float material_Metal;\nuniform float material_Roughness;\nuniform float material_IOR;\nuniform vec3 material_EmissiveColor;\nuniform float material_NormalIntensity;\nuniform float material_OcclusionIntensity;\nuniform float material_OcclusionTextureCoord;\nuniform float material_SpecularIntensity;\nuniform vec3 material_SpecularColor;\n\n"],[1,"MATERIAL_HAS_SPECULAR_TEXTURE",641],[0,"\nuniform sampler2D material_SpecularIntensityTexture;\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_SPECULAR_COLOR_TEXTURE",645],[0,"\nuniform sampler2D material_SpecularColorTexture;\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_CLEAR_COAT",661],[0,"\nuniform float material_ClearCoat;\nuniform float material_ClearCoatRoughness;\n\n"],[1,"MATERIAL_HAS_CLEAR_COAT_TEXTURE",651],[0,"\nuniform sampler2D material_ClearCoatTexture;\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_CLEAR_COAT_ROUGHNESS_TEXTURE",655],[0,"\nuniform sampler2D material_ClearCoatRoughnessTexture;\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_CLEAR_COAT_NORMAL_TEXTURE",659],[0,"\nuniform sampler2D material_ClearCoatNormalTexture;\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_ANISOTROPY",669],[0,"\nuniform vec3 material_AnisotropyInfo;\n\n"],[1,"MATERIAL_HAS_ANISOTROPY_TEXTURE",667],[0,"\nuniform sampler2D material_AnisotropyTexture;\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_IRIDESCENCE",681],[0,"\nuniform vec4 material_IridescenceInfo;\n\n"],[1,"MATERIAL_HAS_IRIDESCENCE_THICKNESS_TEXTURE",675],[0,"\nuniform sampler2D material_IridescenceThicknessTexture;\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_IRIDESCENCE_TEXTURE",679],[0,"\nuniform sampler2D material_IridescenceTexture;\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_SHEEN",693],[0,"\nuniform float material_SheenRoughness;\nuniform vec3 material_SheenColor;\n\n"],[1,"MATERIAL_HAS_SHEEN_TEXTURE",687],[0,"\nuniform sampler2D material_SheenTexture;\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_SHEEN_ROUGHNESS_TEXTURE",691],[0,"\nuniform sampler2D material_SheenRoughnessTexture;\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_TRANSMISSION",709],[0,"\nuniform float material_Transmission;\n\n"],[1,"MATERIAL_HAS_TRANSMISSION_TEXTURE",699],[0,"\nuniform sampler2D material_TransmissionTexture;\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_THICKNESS",707],[0,"\nuniform vec3 material_AttenuationColor;\nuniform float material_AttenuationDistance;\nuniform float material_Thickness;\n\n"],[1,"MATERIAL_HAS_THICKNESS_TEXTURE",705],[0,"\nuniform sampler2D material_ThicknessTexture;\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_BASETEXTURE",713],[0,"\nuniform sampler2D material_BaseTexture;\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_NORMALTEXTURE",717],[0,"\nuniform sampler2D material_NormalTexture;\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_EMISSIVETEXTURE",721],[0,"\nuniform sampler2D material_EmissiveTexture;\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_ROUGHNESS_METALLIC_TEXTURE",725],[0,"\nuniform sampler2D material_RoughnessMetallicTexture;\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_OCCLUSION_TEXTURE",729],[0,"\nuniform sampler2D material_OcclusionTexture;\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_ANISOTROPY",733],[0,"\nvec3 getAnisotropicBentNormal ( SurfaceData surfaceData ) { vec3 anisotropyDirection = ( surfaceData.anisotropy >= 0.0 ) ? surfaceData.anisotropicB : surfaceData.anisotropicT ;\nvec3 anisotropicTangent = cross ( anisotropyDirection , surfaceData.viewDir ) ;\nvec3 anisotropicNormal = cross ( anisotropicTangent , anisotropyDirection ) ;\nvec3 bentNormal = normalize ( mix ( surfaceData.normal , anisotropicNormal , abs ( surfaceData.anisotropy ) * saturate(5.0 * surfaceData.roughness) ) ) ;\nreturn bentNormal ; }\n\n"],[6],[0,"\nSurfaceData getSurfaceData ( vec2 aoUV, bool isFrontFacing ) { SurfaceData surfaceData ;\nvec2 uv = uv ;\nvec4 baseColor = material_BaseColor ;\nfloat metallic = material_Metal ;\nfloat roughness = material_Roughness ;\nvec3 emissiveRadiance = material_EmissiveColor ;\n\n"],[1,"MATERIAL_HAS_BASETEXTURE",737],[0," baseColor *= texture2DSRGB(material_BaseTexture, uv) ; \n"],[6],[0,"\n\n"],[1,"RENDERER_ENABLE_VERTEXCOLOR",741],[0," baseColor *= vertexColor ; \n"],[6],[0,"\n\n"],[1,"MATERIAL_IS_ALPHA_CUTOFF",745],[0," if ( baseColor.a < material_AlphaCutoff ) { discard ; } \n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_ROUGHNESS_METALLIC_TEXTURE",749],[0," vec4 metalRoughMapColor = texture2D ( material_RoughnessMetallicTexture , uv ) ;\nroughness *= metalRoughMapColor.g ;\nmetallic *= metalRoughMapColor.b ; \n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_EMISSIVETEXTURE",753],[0," emissiveRadiance *= texture2DSRGB(material_EmissiveTexture, uv).rgb ; \n"],[6],[0,"\nsurfaceData.albedoColor = baseColor.rgb ;\nsurfaceData.emissiveColor = emissiveRadiance ;\nsurfaceData.metallic = metallic ;\nsurfaceData.roughness = roughness ;\nsurfaceData.IOR = material_IOR ;\n\n"],[1,"MATERIAL_IS_TRANSPARENT",757],[0," surfaceData.opacity = baseColor.a ; \n"],[5,759],[0," surfaceData.opacity = 1.0 ; \n"],[6],[0,"\nsurfaceData.position = positionWS ;\nsurfaceData.positionCS = positionCS ;\n\n"],[1,"CAMERA_ORTHOGRAPHIC",763],[0," surfaceData.viewDir = - camera_Forward ; \n"],[5,765],[0," surfaceData.viewDir = normalize ( camera_Position - positionWS ) ; \n"],[6],[0,"\n\n"],[1,"RENDERER_HAS_NORMAL",769],[0," vec3 normal = normalize ( normalWS ) ; \n"],[5,774],[1,"HAS_DERIVATIVES",772],[0," vec3 pos_dx = dFdx ( positionWS ) ;\nvec3 pos_dy = dFdy ( positionWS ) ;\nvec3 normal = normalize ( cross ( pos_dx , pos_dy ) ) ;\nnormal *= camera_ProjectionParams.x ; \n"],[5,774],[0," vec3 normal = vec3 ( 0 , 0 , 1 ) ; \n"],[6],[0,"\nnormal *= float ( isFrontFacing ) * 2.0 - 1.0 ;\nsurfaceData.normal = normal ;\n\n"],[1,"NEED_TANGENT_SPACE",788],[0," \n"],[1,"NEED_VERTEX_TANGENT",780],[0," surfaceData.tangent = tangentWS ;\nsurfaceData.bitangent = bitangentWS ;\nmat3 tbn = mat3 ( tangentWS , bitangentWS , normalWS ) ; \n"],[5,782],[0," mat3 tbn = getTBNByDerivatives(uv, normal, positionWS, isFrontFacing) ;\nsurfaceData.tangent = tbn[0] ;\nsurfaceData.bitangent = tbn[1] ; \n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_NORMALTEXTURE",786],[0," surfaceData.normal = getNormalByNormalTexture(tbn, material_NormalTexture, material_NormalIntensity, uv, isFrontFacing) ; \n"],[6],[0," \n"],[6],[0,"\nsurfaceData.dotNV = saturate(dot ( surfaceData.normal , surfaceData.viewDir )) ;\nsurfaceData.specularIntensity = material_SpecularIntensity ;\nsurfaceData.specularColor = material_SpecularColor ;\n\n"],[1,"MATERIAL_HAS_SPECULAR_TEXTURE",792],[0," surfaceData.specularIntensity *= texture2D ( material_SpecularIntensityTexture , uv ).a ; \n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_SPECULAR_COLOR_TEXTURE",796],[0," surfaceData.specularColor *= texture2D ( material_SpecularColorTexture , uv ).rgb ; \n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_CLEAR_COAT",814],[0," \n"],[1,"MATERIAL_HAS_CLEAR_COAT_NORMAL_TEXTURE",802],[0," surfaceData.clearCoatNormal = getNormalByNormalTexture(tbn, material_ClearCoatNormalTexture, material_NormalIntensity, uv, isFrontFacing) ; \n"],[5,804],[0," surfaceData.clearCoatNormal = normal ; \n"],[6],[0,"\nsurfaceData.clearCoatDotNV = saturate(dot ( surfaceData.clearCoatNormal , surfaceData.viewDir )) ;\nsurfaceData.clearCoat = material_ClearCoat ;\nsurfaceData.clearCoatRoughness = material_ClearCoatRoughness ;\n\n"],[1,"MATERIAL_HAS_CLEAR_COAT_TEXTURE",808],[0," surfaceData.clearCoat *= ( texture2D ( material_ClearCoatTexture , uv ) ).r ; \n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_CLEAR_COAT_ROUGHNESS_TEXTURE",812],[0," surfaceData.clearCoatRoughness *= ( texture2D ( material_ClearCoatRoughnessTexture , uv ) ).g ; \n"],[6],[0,"\nsurfaceData.clearCoat = saturate(surfaceData.clearCoat) ; \n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_ANISOTROPY",822],[0," float anisotropy = material_AnisotropyInfo.z ;\nvec3 anisotropicDirection = vec3 ( material_AnisotropyInfo.xy , 0.0 ) ;\n\n"],[1,"MATERIAL_HAS_ANISOTROPY_TEXTURE",820],[0," vec3 anisotropyTextureInfo = ( texture2D ( material_AnisotropyTexture , uv ) ).rgb ;\nanisotropy *= anisotropyTextureInfo.b ;\nanisotropicDirection.xy *= anisotropyTextureInfo.rg * 2.0 - 1.0 ; \n"],[6],[0,"\nsurfaceData.anisotropy = anisotropy ;\nsurfaceData.anisotropicT = normalize ( mat3 ( surfaceData.tangent , surfaceData.bitangent , surfaceData.normal ) * anisotropicDirection ) ;\nsurfaceData.anisotropicB = normalize ( cross ( surfaceData.normal , surfaceData.anisotropicT ) ) ;\nsurfaceData.anisotropicN = getAnisotropicBentNormal(surfaceData) ; \n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_IRIDESCENCE",836],[0," surfaceData.iridescenceFactor = material_IridescenceInfo.x ;\nsurfaceData.iridescenceIOR = material_IridescenceInfo.y ;\n\n"],[1,"MATERIAL_HAS_IRIDESCENCE_THICKNESS_TEXTURE",828],[0," float iridescenceThicknessWeight = texture2D ( material_IridescenceThicknessTexture , uv ).g ;\nsurfaceData.iridescenceThickness = mix ( material_IridescenceInfo.z , material_IridescenceInfo.w , iridescenceThicknessWeight ) ; \n"],[5,830],[0," surfaceData.iridescenceThickness = material_IridescenceInfo.w ; \n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_IRIDESCENCE_TEXTURE",834],[0," surfaceData.iridescenceFactor *= texture2D ( material_IridescenceTexture , uv ).r ; \n"],[6],[0," \n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_SHEEN",848],[0," vec3 sheenColor = material_SheenColor ;\n\n"],[1,"MATERIAL_HAS_SHEEN_TEXTURE",842],[0," sheenColor *= texture2DSRGB(material_SheenTexture, uv).rgb ; \n"],[6],[0,"\nsurfaceData.sheenColor = sheenColor ;\nsurfaceData.sheenRoughness = material_SheenRoughness ;\n\n"],[1,"MATERIAL_HAS_SHEEN_ROUGHNESS_TEXTURE",846],[0," surfaceData.sheenRoughness *= texture2D ( material_SheenRoughnessTexture , uv ).a ; \n"],[6],[0," \n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_TRANSMISSION",864],[0," surfaceData.transmission = material_Transmission ;\n\n"],[1,"MATERIAL_HAS_TRANSMISSION_TEXTURE",854],[0," surfaceData.transmission *= texture2D ( material_TransmissionTexture , uv ).r ; \n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_THICKNESS",862],[0," surfaceData.absorptionCoefficient = - log ( material_AttenuationColor + HALF_EPS ) / max ( HALF_EPS , material_AttenuationDistance ) ;\nsurfaceData.thickness = max ( material_Thickness , 0.0001 ) ;\n\n"],[1,"MATERIAL_HAS_THICKNESS_TEXTURE",860],[0," surfaceData.thickness *= texture2D ( material_ThicknessTexture , uv ).g ; \n"],[6],[0," \n"],[6],[0," \n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_OCCLUSION_TEXTURE",868],[0," surfaceData.ambientOcclusion = ( ( texture2D ( material_OcclusionTexture , aoUV ) ).r - 1.0 ) * material_OcclusionIntensity + 1.0 ; \n"],[5,870],[0," surfaceData.ambientOcclusion = 1.0 ; \n"],[6],[0,"\nreturn surfaceData ; }\n\n"],[6],[0,"\nvoid main() { BSDFData bsdfData ;\nvec2 aoUV = uv ;\n\n"],[4,{"t":"and","l":{"t":"def","m":"MATERIAL_HAS_OCCLUSION_TEXTURE"},"r":{"t":"def","m":"RENDERER_HAS_UV1"}},876],[0," if ( material_OcclusionTextureCoord == 1.0 ) { aoUV = uv1 ; } \n"],[6],[0,"\nSurfaceData surfaceData = getSurfaceData(aoUV, gl_FrontFacing) ;\ninitBSDFData(surfaceData, bsdfData) ;\nvec3 totalDiffuseColor = vec3 ( 0 , 0 , 0 ) ;\nvec3 totalSpecularColor = vec3 ( 0 , 0 , 0 ) ;\nfloat shadowAttenuation = 1.0 ;\n\n"],[4,{"t":"and","l":{"t":"def","m":"SCENE_DIRECT_LIGHT_COUNT"},"r":{"t":"def","m":"NEED_CALCULATE_SHADOWS"}},886],[0," \n"],[3,"SCENE_SHADOW_CASCADED_COUNT","==",1,882],[0," vec3 shadowCoord = shadowCoord ; \n"],[5,884],[0," vec3 shadowCoord = getShadowCoord(positionWS) ; \n"],[6],[0,"\nshadowAttenuation *= sampleShadowMap(positionWS, shadowCoord) ; \n"],[6],[0,"\nevaluateDirectRadiance(surfaceData, bsdfData, shadowAttenuation, totalDiffuseColor, totalSpecularColor) ;\nevaluateIBL(surfaceData, bsdfData, totalDiffuseColor, totalSpecularColor) ;\n\n"],[1,"MATERIAL_ENABLE_TRANSMISSION",890],[0," vec3 refractionTransmitted = evaluateTransmission(surfaceData, bsdfData) ;\ntotalDiffuseColor = mix ( totalDiffuseColor , refractionTransmitted , surfaceData.transmission ) ; \n"],[6],[0,"\nvec4 color = vec4 ( ( totalDiffuseColor + totalSpecularColor ).rgb , surfaceData.opacity ) ;\ncolor.rgb += surfaceData.emissiveColor ;\n\n"],[3,"SCENE_FOG_MODE","!=",0,894],[0," color = fog(color, positionVS) ; \n"],[6],[0,"\ngl_FragColor = color ; }\n\n"],[6]]}]}]} \ No newline at end of file +{"name":"PBR","platformTarget":0,"subShaders":[{"name":"Default","tags":{},"passes":[{"name":"Pipeline/ShadowCaster/Default/ShadowCaster","isUsePass":true,"tags":{},"renderStates":{"constantMap":{},"variableMap":{}}},{"name":"Pipeline/DepthOnly/Default/DepthOnly","isUsePass":true,"tags":{},"renderStates":{"constantMap":{},"variableMap":{}}},{"name":"Forward Pass","isUsePass":false,"tags":{"pipelineStage":"Forward"},"renderStates":{"constantMap":{},"variableMap":{"0":"blendEnabled","3":"sourceColorBlendFactor","4":"sourceAlphaBlendFactor","5":"destinationColorBlendFactor","6":"destinationAlphaBlendFactor","11":"depthWriteEnabled","25":"rasterStateCullMode","28":"renderQueueType"}},"vertexShaderInstructions":[[0,"\n"],[2,"FORWARD_PASS_PBR_INCLUDED",714],[0,"\n\n"],[7,"FORWARD_PASS_PBR_INCLUDED"],[0,"\n\n"],[4,{"t":"and","l":{"t":"and","l":{"t":"def","m":"RENDERER_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_HAS_TANGENT"}},"r":{"t":"or","l":{"t":"def","m":"MATERIAL_HAS_NORMALTEXTURE"},"r":{"t":"def","m":"MATERIAL_HAS_CLEAR_COAT_NORMAL_TEXTURE"}}},10],[0,"\n\n"],[7,"NEED_VERTEX_TANGENT"],[0,"\n\n"],[6],[0,"\n\n"],[4,{"t":"or","l":{"t":"or","l":{"t":"def","m":"MATERIAL_HAS_NORMALTEXTURE"},"r":{"t":"def","m":"MATERIAL_HAS_CLEAR_COAT_NORMAL_TEXTURE"}},"r":{"t":"def","m":"MATERIAL_ENABLE_ANISOTROPY"}},16],[0,"\n\n"],[7,"NEED_TANGENT_SPACE"],[0,"\n\n"],[6],[0,"\n\n"],[2,"COMMON_INCLUDED",46],[0,"\n\n"],[7,"COMMON_INCLUDED"],[0,"\n\n"],[8,"PI","3.14159265359"],[0,"\n\n"],[8,"RECIPROCAL_PI","0.31830988618"],[0,"\n\n"],[8,"EPSILON","1e-6"],[0,"\n\n"],[8,"LOG2","1.442695"],[0,"\n\n"],[8,"HALF_MIN","6.103515625e-5"],[0,"\n\n"],[8,"HALF_EPS","4.8828125e-4"],[0,"\n\n"],[9,"saturate",["a"],"clamp( a, 0.0, 1.0 )"],[0,"\n\n"],[1,"GRAPHICS_API_WEBGL2",40],[0,"\n\n"],[9,"INVERSE_MAT",["mat"],"inverse(mat)"],[0,"\n\n"],[5,44],[0,"\nmat2 inverseMat ( mat2 m ) { return mat2 ( m[1][1] , - m[0][1] , - m[1][0] , m[0][0] ) / ( m[0][0] * m[1][1] - m[0][1] * m[1][0] ) ; }\nmat3 inverseMat ( mat3 m ) { float a00 = m[0][0] , a01 = m[0][1] , a02 = m[0][2] ;\nfloat a10 = m[1][0] , a11 = m[1][1] , a12 = m[1][2] ;\nfloat a20 = m[2][0] , a21 = m[2][1] , a22 = m[2][2] ;\nfloat b01 = a22 * a11 - a12 * a21 ;\nfloat b11 = - a22 * a10 + a12 * a20 ;\nfloat b21 = a21 * a10 - a11 * a20 ;\nfloat det = a00 * b01 + a01 * b11 + a02 * b21 ;\nreturn mat3 ( b01 , ( - a22 * a01 + a02 * a21 ) , ( a12 * a01 - a02 * a11 ) , b11 , ( a22 * a00 - a02 * a20 ) , ( - a12 * a00 + a02 * a10 ) , b21 , ( - a21 * a00 + a01 * a20 ) , ( a11 * a00 - a01 * a10 ) ) / det ; }\nmat4 inverseMat ( mat4 m ) { float a00 = m[0][0] , a01 = m[0][1] , a02 = m[0][2] , a03 = m[0][3] , a10 = m[1][0] , a11 = m[1][1] , a12 = m[1][2] , a13 = m[1][3] , a20 = m[2][0] , a21 = m[2][1] , a22 = m[2][2] , a23 = m[2][3] , a30 = m[3][0] , a31 = m[3][1] , a32 = m[3][2] , a33 = m[3][3] , b00 = a00 * a11 - a01 * a10 , b01 = a00 * a12 - a02 * a10 , b02 = a00 * a13 - a03 * a10 , b03 = a01 * a12 - a02 * a11 , b04 = a01 * a13 - a03 * a11 , b05 = a02 * a13 - a03 * a12 , b06 = a20 * a31 - a21 * a30 , b07 = a20 * a32 - a22 * a30 , b08 = a20 * a33 - a23 * a30 , b09 = a21 * a32 - a22 * a31 , b10 = a21 * a33 - a23 * a31 , b11 = a22 * a33 - a23 * a32 , det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06 ;\nreturn mat4 ( a11 * b11 - a12 * b10 + a13 * b09 , a02 * b10 - a01 * b11 - a03 * b09 , a31 * b05 - a32 * b04 + a33 * b03 , a22 * b04 - a21 * b05 - a23 * b03 , a12 * b08 - a10 * b11 - a13 * b07 , a00 * b11 - a02 * b08 + a03 * b07 , a32 * b02 - a30 * b05 - a33 * b01 , a20 * b05 - a22 * b02 + a23 * b01 , a10 * b10 - a11 * b08 + a13 * b06 , a01 * b08 - a00 * b10 - a03 * b06 , a30 * b04 - a31 * b02 + a33 * b00 , a21 * b02 - a20 * b04 - a23 * b00 , a11 * b07 - a10 * b09 - a12 * b06 , a00 * b09 - a01 * b07 + a02 * b06 , a31 * b01 - a30 * b03 - a32 * b00 , a20 * b03 - a21 * b01 + a22 * b00 ) / det ; }\n\n"],[9,"INVERSE_MAT",["mat"],"inverseMat(mat)"],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"FOG_INCLUDED",56],[0,"\n\n"],[7,"FOG_INCLUDED"],[0,"\n\n"],[3,"SCENE_FOG_MODE","!=",0,54],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"TRANSFORM_INCLUDED",62],[0,"\n\n"],[7,"TRANSFORM_INCLUDED"],[0,"\nuniform mat4 renderer_ModelMat;\nuniform mat4 renderer_MVMat;\nuniform mat4 renderer_MVPMat;\nuniform mat4 renderer_NormalMat;\n\n"],[6],[0,"\n\n"],[2,"ATTRIBUTES_INCLUDED",120],[0,"\n\n"],[7,"ATTRIBUTES_INCLUDED"],[0,"\nattribute vec3 POSITION;\n\n"],[1,"RENDERER_HAS_BLENDSHAPE",94],[0,"\n"],[2,"RENDERER_BLENDSHAPE_USE_TEXTURE",92],[0,"attribute vec3 POSITION_BS0;\nattribute vec3 POSITION_BS1;\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},74],[0,"attribute vec3 NORMAL_BS0;\nattribute vec3 NORMAL_BS1;\nattribute vec3 TANGENT_BS0;\nattribute vec3 TANGENT_BS1;\n\n"],[5,90],[0,"\n"],[4,{"t":"or","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},86],[0,"attribute vec3 POSITION_BS2;\nattribute vec3 POSITION_BS3;\n\n"],[1,"RENDERER_BLENDSHAPE_HAS_NORMAL",80],[0,"attribute vec3 NORMAL_BS0;\nattribute vec3 NORMAL_BS1;\nattribute vec3 NORMAL_BS2;\nattribute vec3 NORMAL_BS3;\n\n"],[6],[0,"\n"],[1,"RENDERER_BLENDSHAPE_HAS_TANGENT",84],[0,"attribute vec3 TANGENT_BS0;\nattribute vec3 TANGENT_BS1;\nattribute vec3 TANGENT_BS2;\nattribute vec3 TANGENT_BS3;\n\n"],[6],[0,"\n"],[5,88],[0,"attribute vec3 POSITION_BS2;\nattribute vec3 POSITION_BS3;\nattribute vec3 POSITION_BS4;\nattribute vec3 POSITION_BS5;\nattribute vec3 POSITION_BS6;\nattribute vec3 POSITION_BS7;\n\n"],[6],[0,"\n"],[6],[0,"\n"],[6],[0,"\n"],[6],[0,"\n"],[1,"RENDERER_HAS_UV",98],[0,"attribute vec2 TEXCOORD_0;\n\n"],[6],[0,"\n"],[1,"RENDERER_HAS_UV1",102],[0,"attribute vec2 TEXCOORD_1;\n\n"],[6],[0,"\n"],[1,"RENDERER_HAS_SKIN",106],[0,"attribute vec4 JOINTS_0;\nattribute vec4 WEIGHTS_0;\n\n"],[6],[0,"\n"],[1,"RENDERER_ENABLE_VERTEXCOLOR",110],[0,"attribute vec4 COLOR_0;\n\n"],[6],[0,"\n"],[1,"RENDERER_HAS_NORMAL",114],[0,"attribute vec3 NORMAL;\n\n"],[6],[0,"\n"],[1,"RENDERER_HAS_TANGENT",118],[0,"attribute vec4 TANGENT;\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"SKIN_INCLUDED",142],[0,"\n\n"],[7,"SKIN_INCLUDED"],[0,"\n\n"],[1,"RENDERER_HAS_SKIN",140],[0,"\n\n"],[1,"RENDERER_USE_JOINT_TEXTURE",130],[0,"\nuniform sampler2D renderer_JointSampler;\nuniform float renderer_JointCount;\nmat4 getJointMatrix ( sampler2D smp, float index ) { float base = index / renderer_JointCount ;\nfloat hf = 0.5 / renderer_JointCount ;\nfloat v = base + hf ;\nvec4 m0 = texture2D ( smp , vec2 ( 0.125 , v ) ) ;\nvec4 m1 = texture2D ( smp , vec2 ( 0.375 , v ) ) ;\nvec4 m2 = texture2D ( smp , vec2 ( 0.625 , v ) ) ;\nvec4 m3 = texture2D ( smp , vec2 ( 0.875 , v ) ) ;\nreturn mat4 ( m0 , m1 , m2 , m3 ) ; }\n\n"],[5,132],[0,"\nuniform mat4 renderer_JointMatrix [ RENDERER_JOINTS_NUM ];\n\n"],[6],[0,"\nmat4 getSkinMatrix ( ) { \n"],[1,"RENDERER_USE_JOINT_TEXTURE",136],[0," mat4 skinMatrix = WEIGHTS_0.x * getJointMatrix(renderer_JointSampler, JOINTS_0.x) + WEIGHTS_0.y * getJointMatrix(renderer_JointSampler, JOINTS_0.y) + WEIGHTS_0.z * getJointMatrix(renderer_JointSampler, JOINTS_0.z) + WEIGHTS_0.w * getJointMatrix(renderer_JointSampler, JOINTS_0.w) ; \n"],[5,138],[0," mat4 skinMatrix = WEIGHTS_0.x * renderer_JointMatrix[int ( JOINTS_0.x )] + WEIGHTS_0.y * renderer_JointMatrix[int ( JOINTS_0.y )] + WEIGHTS_0.z * renderer_JointMatrix[int ( JOINTS_0.z )] + WEIGHTS_0.w * renderer_JointMatrix[int ( JOINTS_0.w )] ; \n"],[6],[0,"\nreturn skinMatrix ; }\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"BLENDSHAPE_INCLUDED",220],[0,"\n\n"],[7,"BLENDSHAPE_INCLUDED"],[0,"\n\n"],[1,"RENDERER_HAS_BLENDSHAPE",218],[0,"\n\n"],[1,"RENDERER_BLENDSHAPE_USE_TEXTURE",152],[0,"\nuniform mediump sampler2DArray renderer_BlendShapeTexture;\nuniform ivec3 renderer_BlendShapeTextureInfo;\nuniform float renderer_BlendShapeWeights [ RENDERER_BLENDSHAPE_COUNT ];\nvec3 getBlendShapeVertexElement ( int blendShapeIndex, int vertexElementIndex ) { int y = vertexElementIndex / renderer_BlendShapeTextureInfo.y ;\nint x = vertexElementIndex - y * renderer_BlendShapeTextureInfo.y ;\nivec3 uv = ivec3 ( x , y , blendShapeIndex ) ;\nreturn ( texelFetch ( renderer_BlendShapeTexture , uv , 0 ) ).xyz ; }\n\n"],[5,166],[0,"\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},156],[0,"\nuniform float renderer_BlendShapeWeights [ 2 ];\n\n"],[5,164],[0,"\n\n"],[4,{"t":"or","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},160],[0,"\nuniform float renderer_BlendShapeWeights [ 4 ];\n\n"],[5,162],[0,"\nuniform float renderer_BlendShapeWeights [ 8 ];\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\nvoid calculateBlendShape ( inout vec4 position\n"],[1,"RENDERER_HAS_NORMAL",174],[0," , inout vec3 normal \n"],[1,"RENDERER_HAS_TANGENT",172],[0," , inout vec4 tangent \n"],[6],[0," \n"],[6],[0," ) { \n"],[1,"RENDERER_BLENDSHAPE_USE_TEXTURE",186],[0," int vertexOffset = gl_VertexID * renderer_BlendShapeTextureInfo.x ;\nfor ( int i = 0 ; i < RENDERER_BLENDSHAPE_COUNT ; i ++ ) { int vertexElementOffset = vertexOffset ;\nfloat weight = renderer_BlendShapeWeights[i] ;\nif ( weight != 0.0 ) { position.xyz += getBlendShapeVertexElement(i, vertexElementOffset) * weight ;\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"}},180],[0," vertexElementOffset += 1 ;\nnormal += getBlendShapeVertexElement(i, vertexElementOffset) * weight ; \n"],[6],[0,"\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_HAS_TANGENT"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},184],[0," vertexElementOffset += 1 ;\ntangent.xyz += getBlendShapeVertexElement(i, vertexElementOffset) * weight ; \n"],[6],[0," } } \n"],[5,216],[0," position.xyz += POSITION_BS0 * renderer_BlendShapeWeights[0] ;\nposition.xyz += POSITION_BS1 * renderer_BlendShapeWeights[1] ;\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},198],[0," \n"],[1,"RENDERER_HAS_NORMAL",192],[0," normal += NORMAL_BS0 * renderer_BlendShapeWeights[0] ;\nnormal += NORMAL_BS1 * renderer_BlendShapeWeights[1] ; \n"],[6],[0,"\n\n"],[1,"RENDERER_HAS_TANGENT",196],[0," tangent.xyz += TANGENT_BS0 * renderer_BlendShapeWeights[0] ;\ntangent.xyz += TANGENT_BS1 * renderer_BlendShapeWeights[1] ; \n"],[6],[0," \n"],[5,214],[0," \n"],[4,{"t":"or","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},210],[0," position.xyz += POSITION_BS2 * renderer_BlendShapeWeights[2] ;\nposition.xyz += POSITION_BS3 * renderer_BlendShapeWeights[3] ;\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_HAS_NORMAL"}},204],[0," normal += NORMAL_BS0 * renderer_BlendShapeWeights[0] ;\nnormal += NORMAL_BS1 * renderer_BlendShapeWeights[1] ;\nnormal += NORMAL_BS2 * renderer_BlendShapeWeights[2] ;\nnormal += NORMAL_BS3 * renderer_BlendShapeWeights[3] ; \n"],[6],[0,"\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"},"r":{"t":"def","m":"RENDERER_HAS_TANGENT"}},208],[0," tangent.xyz += TANGENT_BS0 * renderer_BlendShapeWeights[0] ;\ntangent.xyz += TANGENT_BS1 * renderer_BlendShapeWeights[1] ;\ntangent.xyz += TANGENT_BS2 * renderer_BlendShapeWeights[2] ;\ntangent.xyz += TANGENT_BS3 * renderer_BlendShapeWeights[3] ; \n"],[6],[0," \n"],[5,212],[0," position.xyz += POSITION_BS2 * renderer_BlendShapeWeights[2] ;\nposition.xyz += POSITION_BS3 * renderer_BlendShapeWeights[3] ;\nposition.xyz += POSITION_BS4 * renderer_BlendShapeWeights[4] ;\nposition.xyz += POSITION_BS5 * renderer_BlendShapeWeights[5] ;\nposition.xyz += POSITION_BS6 * renderer_BlendShapeWeights[6] ;\nposition.xyz += POSITION_BS7 * renderer_BlendShapeWeights[7] ; \n"],[6],[0," \n"],[6],[0," \n"],[6],[0," }\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"SHADOW_INCLUDED",300],[0,"\n\n"],[7,"SHADOW_INCLUDED"],[0,"\n\n"],[4,{"t":"and","l":{"t":"def","m":"SCENE_SHADOW_TYPE"},"r":{"t":"def","m":"RENDERER_IS_RECEIVE_SHADOWS"}},230],[0,"\n\n"],[7,"NEED_CALCULATE_SHADOWS"],[0,"\n\n"],[6],[0,"\n\n"],[1,"NEED_CALCULATE_SHADOWS",258],[0,"\nuniform mat4 scene_ShadowMatrices [ SCENE_SHADOW_CASCADED_COUNT + 1 ];\nuniform vec4 scene_ShadowSplitSpheres [ 4 ];\nmediump int computeCascadeIndex ( vec3 positionWS ) { vec3 fromCenter0 = positionWS - scene_ShadowSplitSpheres[0].xyz ;\nvec3 fromCenter1 = positionWS - scene_ShadowSplitSpheres[1].xyz ;\nvec3 fromCenter2 = positionWS - scene_ShadowSplitSpheres[2].xyz ;\nvec3 fromCenter3 = positionWS - scene_ShadowSplitSpheres[3].xyz ;\nmediump vec4 comparison = vec4 ( ( dot ( fromCenter0 , fromCenter0 ) < scene_ShadowSplitSpheres[0].w ) , ( dot ( fromCenter1 , fromCenter1 ) < scene_ShadowSplitSpheres[1].w ) , ( dot ( fromCenter2 , fromCenter2 ) < scene_ShadowSplitSpheres[2].w ) , ( dot ( fromCenter3 , fromCenter3 ) < scene_ShadowSplitSpheres[3].w ) ) ;\ncomparison.yzw = clamp ( comparison.yzw - comparison.xyz , 0.0 , 1.0 ) ;\nmediump vec4 indexCoefficient = vec4 ( 4.0 , 3.0 , 2.0 , 1.0 ) ;\nmediump int index = 4 - int ( dot ( comparison , indexCoefficient ) ) ;\nreturn index ; }\nvec3 getShadowCoord ( vec3 positionWS ) { \n"],[3,"SCENE_SHADOW_CASCADED_COUNT","==",1,236],[0," mediump int cascadeIndex = 0 ; \n"],[5,238],[0," mediump int cascadeIndex = computeCascadeIndex(positionWS) ; \n"],[6],[0,"\n\n"],[1,"GRAPHICS_API_WEBGL2",242],[0," mat4 shadowMatrix = scene_ShadowMatrices[cascadeIndex] ; \n"],[5,256],[0," mat4 shadowMatrix ;\n\n"],[3,"SCENE_SHADOW_CASCADED_COUNT","==",4,246],[0," if ( cascadeIndex == 0 ) { shadowMatrix = scene_ShadowMatrices[0] ; } else if ( cascadeIndex == 1 ) { shadowMatrix = scene_ShadowMatrices[1] ; } else if ( cascadeIndex == 2 ) { shadowMatrix = scene_ShadowMatrices[2] ; } else if ( cascadeIndex == 3 ) { shadowMatrix = scene_ShadowMatrices[3] ; } else { shadowMatrix = scene_ShadowMatrices[4] ; } \n"],[6],[0,"\n\n"],[3,"SCENE_SHADOW_CASCADED_COUNT","==",2,250],[0," if ( cascadeIndex == 0 ) { shadowMatrix = scene_ShadowMatrices[0] ; } else if ( cascadeIndex == 1 ) { shadowMatrix = scene_ShadowMatrices[1] ; } else { shadowMatrix = scene_ShadowMatrices[2] ; } \n"],[6],[0,"\n\n"],[3,"SCENE_SHADOW_CASCADED_COUNT","==",1,254],[0," if ( cascadeIndex == 0 ) { shadowMatrix = scene_ShadowMatrices[0] ; } else { shadowMatrix = scene_ShadowMatrices[1] ; } \n"],[6],[0," \n"],[6],[0,"\nvec4 shadowCoord = shadowMatrix * vec4 ( positionWS , 1.0 ) ;\nreturn shadowCoord.xyz ; }\n\n"],[6],[0,"\n\n"],[1,"NEED_CALCULATE_SHADOWS",298],[0,"\n\n"],[1,"GRAPHICS_API_WEBGL2",268],[0,"\n\n"],[9,"SAMPLE_TEXTURE2D_SHADOW",["textureName","coord3"],"textureLod(textureName, coord3 , 0.0)"],[0,"\n\n"],[9,"TEXTURE2D_SHADOW_PARAM",["shadowMap"],"mediump sampler2DShadow shadowMap"],[0,"\n\n"],[5,282],[0,"\n\n"],[1,"ENGINE_NO_DEPTH_TEXTURE",274],[0,"\n\n"],[9,"SAMPLE_TEXTURE2D_SHADOW",["textureName","coord3"],"textureShadowMapDowngrade(textureName, coord3)"],[0,"\n\n"],[5,278],[0,"\n\n"],[9,"SAMPLE_TEXTURE2D_SHADOW",["textureName","coord3"],"textureShadowMapDowngrade(textureName, coord3)"],[0,"\n\n"],[6],[0,"\n\n"],[9,"TEXTURE2D_SHADOW_PARAM",["shadowMap"],"mediump sampler2D shadowMap"],[0,"\n\n"],[6],[0,"\n\n"],[3,"SCENE_SHADOW_TYPE","==",2,286],[0,"\n\n"],[6],[0,"\n\n"],[3,"SCENE_SHADOW_TYPE","==",3,296],[0,"\n\n"],[2,"SHADOW_SAMPLE_TENT_INCLUDED",294],[0,"\n\n"],[7,"SHADOW_SAMPLE_TENT_INCLUDED"],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"VARYINGS_PBR_INCLUDED",330],[0,"\n\n"],[7,"VARYINGS_PBR_INCLUDED"],[0,"\nvarying vec2 uv;\n\n"],[1,"RENDERER_HAS_UV1",308],[0,"varying vec2 uv1;\n\n"],[6],[0,"\n"],[1,"RENDERER_ENABLE_VERTEXCOLOR",312],[0,"varying vec4 vertexColor;\n\n"],[6],[0,"varying vec3 positionWS;\n\n"],[3,"SCENE_FOG_MODE","!=",0,316],[0,"varying vec3 positionVS;\n\n"],[6],[0,"\n"],[1,"RENDERER_HAS_NORMAL",324],[0,"varying vec3 normalWS;\n\n"],[1,"NEED_VERTEX_TANGENT",322],[0,"varying vec3 tangentWS;\nvarying vec3 bitangentWS;\n\n"],[6],[0,"\n"],[6],[0,"\n"],[4,{"t":"and","l":{"t":"def","m":"NEED_CALCULATE_SHADOWS"},"r":{"t":"cmp","m":"SCENE_SHADOW_CASCADED_COUNT","op":"==","v":1}},328],[0,"varying vec3 shadowCoord;\n\n"],[6],[0,"varying vec4 positionCS;\n\n\n"],[6],[0,"\n\n"],[2,"LIGHT_DIRECT_PBR_INCLUDED",478],[0,"\n\n"],[7,"LIGHT_DIRECT_PBR_INCLUDED"],[0,"\n\n"],[2,"FUNCTION_SURFACE_SHADING",340],[0,"\n\n"],[8,"FUNCTION_SURFACE_SHADING","surfaceShading"],[0,"\n\n"],[6],[0,"\n\n"],[2,"FUNCTION_DIFFUSE_LOBE",346],[0,"\n\n"],[8,"FUNCTION_DIFFUSE_LOBE","diffuseLobe"],[0,"\n\n"],[6],[0,"\n\n"],[2,"FUNCTION_SPECULAR_LOBE",352],[0,"\n\n"],[8,"FUNCTION_SPECULAR_LOBE","specularLobe"],[0,"\n\n"],[6],[0,"\n\n"],[2,"FUNCTION_CLEAR_COAT_LOBE",358],[0,"\n\n"],[8,"FUNCTION_CLEAR_COAT_LOBE","clearCoatLobe"],[0,"\n\n"],[6],[0,"\n\n"],[2,"FUNCTION_SHEEN_LOBE",364],[0,"\n\n"],[8,"FUNCTION_SHEEN_LOBE","sheenLobe"],[0,"\n\n"],[6],[0,"\n\n"],[2,"BSDF_INCLUDED",416],[0,"\n\n"],[7,"BSDF_INCLUDED"],[0,"\n\n"],[2,"REFRACTION_INCLUDED",378],[0,"\n\n"],[7,"REFRACTION_INCLUDED"],[0,"\n\n"],[1,"MATERIAL_ENABLE_TRANSMISSION",376],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[8,"MIN_PERCEPTUAL_ROUGHNESS","0.045"],[0,"\n\n"],[8,"MIN_ROUGHNESS","0.002025"],[0,"\n\n"],[1,"MATERIAL_ENABLE_SHEEN",386],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_ANISOTROPY",390],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_ANISOTROPY",394],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_ANISOTROPY",398],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_IRIDESCENCE",402],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_SHEEN",406],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_TRANSMISSION",410],[0,"\n\n"],[6],[0,"\n\n"],[1,"SCENE_ENABLE_AMBIENT_OCCLUSION",414],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"LIGHT_INCLUDED",458],[0,"\n\n"],[7,"LIGHT_INCLUDED"],[0,"\n\n"],[2,"GRAPHICS_API_WEBGL2",424],[0,"\n\n"],[6],[0,"\n\n"],[1,"SCENE_DIRECT_LIGHT_COUNT",432],[0,"\n\n"],[1,"GRAPHICS_API_WEBGL2",430],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[1,"SCENE_POINT_LIGHT_COUNT",440],[0,"\n\n"],[1,"GRAPHICS_API_WEBGL2",438],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[1,"SCENE_SPOT_LIGHT_COUNT",448],[0,"\n\n"],[1,"GRAPHICS_API_WEBGL2",446],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[1,"SCENE_USE_SH",452],[0,"\n\n"],[6],[0,"\n\n"],[1,"SCENE_USE_SPECULAR_ENV",456],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"REFLECTION_LOBE_INCLUDED",464],[0,"\n\n"],[7,"REFLECTION_LOBE_INCLUDED"],[0,"\n\n"],[6],[0,"\n\n"],[1,"SCENE_DIRECT_LIGHT_COUNT",468],[0,"\n\n"],[6],[0,"\n\n"],[1,"SCENE_POINT_LIGHT_COUNT",472],[0,"\n\n"],[6],[0,"\n\n"],[1,"SCENE_SPOT_LIGHT_COUNT",476],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"LIGHT_INDIRECT_PBR_INCLUDED",514],[0,"\n\n"],[7,"LIGHT_INDIRECT_PBR_INCLUDED"],[0,"\n\n"],[2,"FUNCTION_DIFFUSE_IBL",488],[0,"\n\n"],[8,"FUNCTION_DIFFUSE_IBL","evaluateDiffuseIBL"],[0,"\n\n"],[6],[0,"\n\n"],[2,"FUNCTION_SPECULAR_IBL",494],[0,"\n\n"],[8,"FUNCTION_SPECULAR_IBL","evaluateSpecularIBL"],[0,"\n\n"],[6],[0,"\n\n"],[2,"FUNCTION_CLEAR_COAT_IBL",500],[0,"\n\n"],[8,"FUNCTION_CLEAR_COAT_IBL","evaluateClearCoatIBL"],[0,"\n\n"],[6],[0,"\n\n"],[2,"FUNCTION_SHEEN_IBL",506],[0,"\n\n"],[8,"FUNCTION_SHEEN_IBL","evaluateSheenIBL"],[0,"\n\n"],[6],[0,"\n\n"],[2,"LIGHT_INDIRECT_FUNCTIONS_INCLUDED",512],[0,"\n\n"],[7,"LIGHT_INDIRECT_FUNCTIONS_INCLUDED"],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"VERTEX_INCLUDE",580],[0,"\n\n"],[7,"VERTEX_INCLUDE"],[0,"\nstruct VertexInputs { vec4 positionOS ; vec3 positionWS ; \n"],[3,"SCENE_FOG_MODE","!=",0,522],[0," vec3 positionVS ; \n"],[6],[0," \n"],[1,"RENDERER_HAS_NORMAL",530],[0," vec3 normalWS ; \n"],[1,"NEED_VERTEX_TANGENT",528],[0," vec3 tangentWS ; vec3 bitangentWS ; \n"],[6],[0," \n"],[6],[0," } ;\nuniform vec4 material_TilingOffset;\nvec2 getUV0 ( ) { vec2 uv0 = vec2 ( 0 ) ;\n\n"],[1,"RENDERER_HAS_UV",534],[0," uv0 = TEXCOORD_0 ; \n"],[6],[0,"\nreturn uv0 * material_TilingOffset.xy + material_TilingOffset.zw ; }\nVertexInputs getVertexInputs ( ) { VertexInputs inputs ;\nvec4 position = vec4 ( POSITION , 1.0 ) ;\n\n"],[1,"RENDERER_HAS_NORMAL",542],[0," vec3 normal = vec3 ( NORMAL ) ;\n\n"],[1,"RENDERER_HAS_TANGENT",540],[0," vec4 tangent = vec4 ( TANGENT ) ; \n"],[6],[0," \n"],[6],[0,"\n\n"],[1,"RENDERER_HAS_BLENDSHAPE",554],[0," calculateBlendShape(position\n"],[1,"RENDERER_HAS_NORMAL",552],[0," , normal \n"],[1,"RENDERER_HAS_TANGENT",550],[0," , tangent \n"],[6],[0," \n"],[6],[0,") ; \n"],[6],[0,"\n\n"],[1,"RENDERER_HAS_SKIN",566],[0," mat4 skinMatrix = getSkinMatrix() ;\nposition = skinMatrix * position ;\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_HAS_NORMAL"},"r":{"t":"not","c":{"t":"def","m":"MATERIAL_OMIT_NORMAL"}}},564],[0," mat3 skinNormalMatrix = INVERSE_MAT(mat3 ( skinMatrix )) ;\nnormal = normal * skinNormalMatrix ;\n\n"],[1,"NEED_VERTEX_TANGENT",562],[0," tangent.xyz = tangent.xyz * skinNormalMatrix ; \n"],[6],[0," \n"],[6],[0," \n"],[6],[0,"\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_HAS_NORMAL"},"r":{"t":"not","c":{"t":"def","m":"MATERIAL_OMIT_NORMAL"}}},574],[0," mat3 normalMat = mat3 ( renderer_NormalMat ) ;\ninputs.normalWS = normalize ( normalMat * normal ) ;\n\n"],[1,"NEED_VERTEX_TANGENT",572],[0," vec3 tangentWS = normalize ( normalMat * tangent.xyz ) ;\nvec3 bitangentWS = cross ( inputs.normalWS , tangentWS ) * tangent.w ;\ninputs.tangentWS = tangentWS ;\ninputs.bitangentWS = bitangentWS ; \n"],[6],[0," \n"],[6],[0,"\ninputs.positionOS = position ;\nvec4 positionWS = renderer_ModelMat * position ;\ninputs.positionWS = positionWS.xyz / positionWS.w ;\n\n"],[3,"SCENE_FOG_MODE","!=",0,578],[0," vec4 positionVS = renderer_MVMat * position ;\ninputs.positionVS = positionVS.xyz / positionVS.w ; \n"],[6],[0,"\nreturn inputs ; }\n\n"],[6],[0,"\n\n"],[2,"MATERIAL_INPUT_PBR_INCLUDED",688],[0,"\n\n"],[7,"MATERIAL_INPUT_PBR_INCLUDED"],[0,"\n\n"],[2,"NORMAL_INCLUDED",590],[0,"\n\n"],[7,"NORMAL_INCLUDED"],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_SPECULAR_TEXTURE",594],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_SPECULAR_COLOR_TEXTURE",598],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_CLEAR_COAT",614],[0,"\n\n"],[1,"MATERIAL_HAS_CLEAR_COAT_TEXTURE",604],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_CLEAR_COAT_ROUGHNESS_TEXTURE",608],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_CLEAR_COAT_NORMAL_TEXTURE",612],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_ANISOTROPY",622],[0,"\n\n"],[1,"MATERIAL_HAS_ANISOTROPY_TEXTURE",620],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_IRIDESCENCE",634],[0,"\n\n"],[1,"MATERIAL_HAS_IRIDESCENCE_THICKNESS_TEXTURE",628],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_IRIDESCENCE_TEXTURE",632],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_SHEEN",646],[0,"\n\n"],[1,"MATERIAL_HAS_SHEEN_TEXTURE",640],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_SHEEN_ROUGHNESS_TEXTURE",644],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_TRANSMISSION",662],[0,"\n\n"],[1,"MATERIAL_HAS_TRANSMISSION_TEXTURE",652],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_THICKNESS",660],[0,"\n\n"],[1,"MATERIAL_HAS_THICKNESS_TEXTURE",658],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_BASETEXTURE",666],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_NORMALTEXTURE",670],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_EMISSIVETEXTURE",674],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_ROUGHNESS_METALLIC_TEXTURE",678],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_OCCLUSION_TEXTURE",682],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_ANISOTROPY",686],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\nvoid main() { \nuv = getUV0() ;\n\n"],[1,"RENDERER_HAS_UV1",692],[0," uv1 = TEXCOORD_1 ; \n"],[6],[0,"\n\n"],[1,"RENDERER_ENABLE_VERTEXCOLOR",696],[0," vertexColor = COLOR_0 ; \n"],[6],[0,"\nVertexInputs vertexInputs = getVertexInputs() ;\npositionWS = vertexInputs.positionWS ;\n\n"],[3,"SCENE_FOG_MODE","!=",0,700],[0," positionVS = vertexInputs.positionVS ; \n"],[6],[0,"\n\n"],[1,"RENDERER_HAS_NORMAL",708],[0," normalWS = vertexInputs.normalWS ;\n\n"],[1,"NEED_VERTEX_TANGENT",706],[0," tangentWS = vertexInputs.tangentWS ;\nbitangentWS = vertexInputs.bitangentWS ; \n"],[6],[0," \n"],[6],[0,"\n\n"],[4,{"t":"and","l":{"t":"def","m":"NEED_CALCULATE_SHADOWS"},"r":{"t":"cmp","m":"SCENE_SHADOW_CASCADED_COUNT","op":"==","v":1}},712],[0," shadowCoord = getShadowCoord(vertexInputs.positionWS) ; \n"],[6],[0,"\ngl_Position = renderer_MVPMat * vertexInputs.positionOS ;\npositionCS = gl_Position ;\n }\n\n"],[6]],"fragmentShaderInstructions":[[0,"\n"],[2,"FORWARD_PASS_PBR_INCLUDED",896],[0,"\n\n"],[7,"FORWARD_PASS_PBR_INCLUDED"],[0,"\n\n"],[4,{"t":"and","l":{"t":"and","l":{"t":"def","m":"RENDERER_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_HAS_TANGENT"}},"r":{"t":"or","l":{"t":"def","m":"MATERIAL_HAS_NORMALTEXTURE"},"r":{"t":"def","m":"MATERIAL_HAS_CLEAR_COAT_NORMAL_TEXTURE"}}},10],[0,"\n\n"],[7,"NEED_VERTEX_TANGENT"],[0,"\n\n"],[6],[0,"\n\n"],[4,{"t":"or","l":{"t":"or","l":{"t":"def","m":"MATERIAL_HAS_NORMALTEXTURE"},"r":{"t":"def","m":"MATERIAL_HAS_CLEAR_COAT_NORMAL_TEXTURE"}},"r":{"t":"def","m":"MATERIAL_ENABLE_ANISOTROPY"}},16],[0,"\n\n"],[7,"NEED_TANGENT_SPACE"],[0,"\n\n"],[6],[0,"\n\n"],[2,"COMMON_INCLUDED",50],[0,"\n\n"],[7,"COMMON_INCLUDED"],[0,"\n\n"],[8,"PI","3.14159265359"],[0,"\n\n"],[8,"RECIPROCAL_PI","0.31830988618"],[0,"\n\n"],[8,"EPSILON","1e-6"],[0,"\n\n"],[8,"LOG2","1.442695"],[0,"\n\n"],[8,"HALF_MIN","6.103515625e-5"],[0,"\n\n"],[8,"HALF_EPS","4.8828125e-4"],[0,"\n\n"],[9,"saturate",["a"],"clamp( a, 0.0, 1.0 )"],[0,"\nfloat pow2 ( float x ) { return x * x ; }\nfloat sRGBToLinear ( float value ) { float linearRGBLo = value / 12.92 ;\nfloat linearRGBHi = pow ( ( value + 0.055 ) / 1.055 , 2.4 ) ;\nfloat linearRGB = ( value <= 0.04045 ) ? linearRGBLo : linearRGBHi ;\nreturn linearRGB ; }\nvec4 sRGBToLinear ( vec4 value ) { return vec4 ( sRGBToLinear(value.r) , sRGBToLinear(value.g) , sRGBToLinear(value.b) , value.a ) ; }\nvec4 texture2DSRGB ( sampler2D tex, vec2 uv ) { vec4 color = texture2D ( tex , uv ) ;\n\n"],[1,"ENGINE_NO_SRGB",38],[0," color = sRGBToLinear(color) ; \n"],[6],[0,"\nreturn color ; }\nuniform vec4 camera_ProjectionParams;\n\n"],[1,"GRAPHICS_API_WEBGL2",44],[0,"\n\n"],[9,"INVERSE_MAT",["mat"],"inverse(mat)"],[0,"\n\n"],[5,48],[0,"\n\n"],[9,"INVERSE_MAT",["mat"],"inverseMat(mat)"],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"FOG_INCLUDED",70],[0,"\n\n"],[7,"FOG_INCLUDED"],[0,"\n\n"],[3,"SCENE_FOG_MODE","!=",0,68],[0,"\nuniform vec4 scene_FogColor;\nuniform vec4 scene_FogParams;\nvec4 fog ( vec4 color, vec3 positionVS ) { float fogDepth = length ( positionVS ) ;\n\n"],[3,"SCENE_FOG_MODE","==",1,60],[0," float fogIntensity = clamp ( fogDepth * scene_FogParams.x + scene_FogParams.y , 0.0 , 1.0 ) ; \n"],[5,66],[3,"SCENE_FOG_MODE","==",2,63],[0," float fogIntensity = clamp ( exp2 ( - fogDepth * scene_FogParams.z ) , 0.0 , 1.0 ) ; \n"],[5,66],[3,"SCENE_FOG_MODE","==",3,66],[0," float factor = fogDepth * scene_FogParams.w ;\nfloat fogIntensity = clamp ( exp2 ( - factor * factor ) , 0.0 , 1.0 ) ; \n"],[6],[0,"\ncolor.rgb = mix ( scene_FogColor.rgb , color.rgb , fogIntensity ) ;\nreturn color ; }\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"TRANSFORM_INCLUDED",76],[0,"\n\n"],[7,"TRANSFORM_INCLUDED"],[0,"\nuniform mat4 camera_ViewMat;\nuniform mat4 camera_ProjMat;\nuniform vec3 camera_Position;\nuniform vec3 camera_Forward;\n\n"],[6],[0,"\n\n"],[2,"ATTRIBUTES_INCLUDED",82],[0,"\n\n"],[7,"ATTRIBUTES_INCLUDED"],[0,"\n\n"],[6],[0,"\n\n"],[2,"SKIN_INCLUDED",98],[0,"\n\n"],[7,"SKIN_INCLUDED"],[0,"\n\n"],[1,"RENDERER_HAS_SKIN",96],[0,"\n\n"],[1,"RENDERER_USE_JOINT_TEXTURE",92],[0,"\n\n"],[5,94],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"BLENDSHAPE_INCLUDED",126],[0,"\n\n"],[7,"BLENDSHAPE_INCLUDED"],[0,"\n\n"],[1,"RENDERER_HAS_BLENDSHAPE",124],[0,"\n\n"],[1,"RENDERER_BLENDSHAPE_USE_TEXTURE",108],[0,"\n\n"],[5,122],[0,"\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},112],[0,"\n\n"],[5,120],[0,"\n\n"],[4,{"t":"or","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},116],[0,"\n\n"],[5,118],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"SHADOW_INCLUDED",218],[0,"\n\n"],[7,"SHADOW_INCLUDED"],[0,"\n\n"],[4,{"t":"and","l":{"t":"def","m":"SCENE_SHADOW_TYPE"},"r":{"t":"def","m":"RENDERER_IS_RECEIVE_SHADOWS"}},136],[0,"\n\n"],[7,"NEED_CALCULATE_SHADOWS"],[0,"\n\n"],[6],[0,"\n\n"],[1,"NEED_CALCULATE_SHADOWS",164],[0,"\nuniform mat4 scene_ShadowMatrices [ SCENE_SHADOW_CASCADED_COUNT + 1 ];\nuniform vec4 scene_ShadowSplitSpheres [ 4 ];\nmediump int computeCascadeIndex ( vec3 positionWS ) { vec3 fromCenter0 = positionWS - scene_ShadowSplitSpheres[0].xyz ;\nvec3 fromCenter1 = positionWS - scene_ShadowSplitSpheres[1].xyz ;\nvec3 fromCenter2 = positionWS - scene_ShadowSplitSpheres[2].xyz ;\nvec3 fromCenter3 = positionWS - scene_ShadowSplitSpheres[3].xyz ;\nmediump vec4 comparison = vec4 ( ( dot ( fromCenter0 , fromCenter0 ) < scene_ShadowSplitSpheres[0].w ) , ( dot ( fromCenter1 , fromCenter1 ) < scene_ShadowSplitSpheres[1].w ) , ( dot ( fromCenter2 , fromCenter2 ) < scene_ShadowSplitSpheres[2].w ) , ( dot ( fromCenter3 , fromCenter3 ) < scene_ShadowSplitSpheres[3].w ) ) ;\ncomparison.yzw = clamp ( comparison.yzw - comparison.xyz , 0.0 , 1.0 ) ;\nmediump vec4 indexCoefficient = vec4 ( 4.0 , 3.0 , 2.0 , 1.0 ) ;\nmediump int index = 4 - int ( dot ( comparison , indexCoefficient ) ) ;\nreturn index ; }\nvec3 getShadowCoord ( vec3 positionWS ) { \n"],[3,"SCENE_SHADOW_CASCADED_COUNT","==",1,142],[0," mediump int cascadeIndex = 0 ; \n"],[5,144],[0," mediump int cascadeIndex = computeCascadeIndex(positionWS) ; \n"],[6],[0,"\n\n"],[1,"GRAPHICS_API_WEBGL2",148],[0," mat4 shadowMatrix = scene_ShadowMatrices[cascadeIndex] ; \n"],[5,162],[0," mat4 shadowMatrix ;\n\n"],[3,"SCENE_SHADOW_CASCADED_COUNT","==",4,152],[0," if ( cascadeIndex == 0 ) { shadowMatrix = scene_ShadowMatrices[0] ; } else if ( cascadeIndex == 1 ) { shadowMatrix = scene_ShadowMatrices[1] ; } else if ( cascadeIndex == 2 ) { shadowMatrix = scene_ShadowMatrices[2] ; } else if ( cascadeIndex == 3 ) { shadowMatrix = scene_ShadowMatrices[3] ; } else { shadowMatrix = scene_ShadowMatrices[4] ; } \n"],[6],[0,"\n\n"],[3,"SCENE_SHADOW_CASCADED_COUNT","==",2,156],[0," if ( cascadeIndex == 0 ) { shadowMatrix = scene_ShadowMatrices[0] ; } else if ( cascadeIndex == 1 ) { shadowMatrix = scene_ShadowMatrices[1] ; } else { shadowMatrix = scene_ShadowMatrices[2] ; } \n"],[6],[0,"\n\n"],[3,"SCENE_SHADOW_CASCADED_COUNT","==",1,160],[0," if ( cascadeIndex == 0 ) { shadowMatrix = scene_ShadowMatrices[0] ; } else { shadowMatrix = scene_ShadowMatrices[1] ; } \n"],[6],[0," \n"],[6],[0,"\nvec4 shadowCoord = shadowMatrix * vec4 ( positionWS , 1.0 ) ;\nreturn shadowCoord.xyz ; }\n\n"],[6],[0,"\n\n"],[1,"NEED_CALCULATE_SHADOWS",216],[0,"\nuniform vec4 scene_ShadowInfo;\nuniform vec4 scene_ShadowMapSize;\n\n"],[1,"GRAPHICS_API_WEBGL2",174],[0,"\nuniform mediump sampler2DShadow scene_ShadowMap;\n\n"],[9,"SAMPLE_TEXTURE2D_SHADOW",["textureName","coord3"],"textureLod(textureName, coord3 , 0.0)"],[0,"\n\n"],[9,"TEXTURE2D_SHADOW_PARAM",["shadowMap"],"mediump sampler2DShadow shadowMap"],[0,"\n\n"],[5,188],[0,"\nuniform sampler2D scene_ShadowMap;\n\n"],[1,"ENGINE_NO_DEPTH_TEXTURE",180],[0,"\nconst vec4 bitShift = vec4 ( 1.0 , 1.0 / 256.0 , 1.0 / ( 256.0 * 256.0 ) , 1.0 / ( 256.0 * 256.0 * 256.0 ) );\nfloat textureShadowMapDowngrade ( sampler2D scene_ShadowMap, vec3 shadowCoord ) { vec4 rgbaDepth = texture2D ( scene_ShadowMap , shadowCoord.xy ) ;\nfloat unpackDepth = dot ( rgbaDepth , bitShift ) ;\nreturn unpackDepth < shadowCoord.z ? 0.0 : 1.0 ; }\n\n"],[9,"SAMPLE_TEXTURE2D_SHADOW",["textureName","coord3"],"textureShadowMapDowngrade(textureName, coord3)"],[0,"\n\n"],[5,184],[0,"\nfloat textureShadowMapDowngrade ( sampler2D scene_ShadowMap, vec3 shadowCoord ) { float depth = texture2D ( scene_ShadowMap , shadowCoord.xy ).r ;\nreturn depth < shadowCoord.z ? 0.0 : 1.0 ; }\n\n"],[9,"SAMPLE_TEXTURE2D_SHADOW",["textureName","coord3"],"textureShadowMapDowngrade(textureName, coord3)"],[0,"\n\n"],[6],[0,"\n\n"],[9,"TEXTURE2D_SHADOW_PARAM",["shadowMap"],"mediump sampler2D shadowMap"],[0,"\n\n"],[6],[0,"\n\n"],[3,"SCENE_SHADOW_TYPE","==",2,192],[0,"\nfloat sampleShadowMapFiltered4 ( TEXTURE2D_SHADOW_PARAM(shadowMap), vec3 shadowCoord, vec4 shadowMapSize ) { float attenuation ;\nvec4 attenuation4 ;\nvec2 offset = shadowMapSize.xy / 2.0 ;\nvec3 shadowCoord0 = shadowCoord + vec3 ( - offset , 0.0 ) ;\nvec3 shadowCoord1 = shadowCoord + vec3 ( offset.x , - offset.y , 0.0 ) ;\nvec3 shadowCoord2 = shadowCoord + vec3 ( - offset.x , offset.y , 0.0 ) ;\nvec3 shadowCoord3 = shadowCoord + vec3 ( offset , 0.0 ) ;\nattenuation4.x = SAMPLE_TEXTURE2D_SHADOW(shadowMap, shadowCoord0) ;\nattenuation4.y = SAMPLE_TEXTURE2D_SHADOW(shadowMap, shadowCoord1) ;\nattenuation4.z = SAMPLE_TEXTURE2D_SHADOW(shadowMap, shadowCoord2) ;\nattenuation4.w = SAMPLE_TEXTURE2D_SHADOW(shadowMap, shadowCoord3) ;\nattenuation = dot ( attenuation4 , vec4 ( 0.25 ) ) ;\nreturn attenuation ; }\n\n"],[6],[0,"\n\n"],[3,"SCENE_SHADOW_TYPE","==",3,202],[0,"\n\n"],[2,"SHADOW_SAMPLE_TENT_INCLUDED",200],[0,"\n\n"],[7,"SHADOW_SAMPLE_TENT_INCLUDED"],[0,"\nfloat sampleShadowGetIRTriangleTexelArea ( float triangleHeight ) { return triangleHeight - 0.5 ; }\nvoid sampleShadowGetTexelAreasTent3x3 ( float offset, out vec4 computedArea, out vec4 computedAreaUncut ) { float a = offset + 0.5 ;\nfloat offsetSquaredHalved = a * a * 0.5 ;\ncomputedAreaUncut.x = computedArea.x = offsetSquaredHalved - offset ;\ncomputedAreaUncut.w = computedArea.w = offsetSquaredHalved ;\ncomputedAreaUncut.y = sampleShadowGetIRTriangleTexelArea(1.5 - offset) ;\nfloat clampedOffsetLeft = min ( offset , 0.0 ) ;\nfloat areaOfSmallLeftTriangle = clampedOffsetLeft * clampedOffsetLeft ;\ncomputedArea.y = computedAreaUncut.y - areaOfSmallLeftTriangle ;\ncomputedAreaUncut.z = sampleShadowGetIRTriangleTexelArea(1.5 + offset) ;\nfloat clampedOffsetRight = max ( offset , 0.0 ) ;\nfloat areaOfSmallRightTriangle = clampedOffsetRight * clampedOffsetRight ;\ncomputedArea.z = computedAreaUncut.z - areaOfSmallRightTriangle ; }\nvoid sampleShadowGetTexelWeightsTent5x5 ( float offset, out vec3 texelsWeightsA, out vec3 texelsWeightsB ) { vec4 areaFrom3texelTriangle ;\nvec4 areaUncutFrom3texelTriangle ;\nsampleShadowGetTexelAreasTent3x3(offset, areaFrom3texelTriangle, areaUncutFrom3texelTriangle) ;\ntexelsWeightsA.x = 0.16 * ( areaFrom3texelTriangle.x ) ;\ntexelsWeightsA.y = 0.16 * ( areaUncutFrom3texelTriangle.y ) ;\ntexelsWeightsA.z = 0.16 * ( areaFrom3texelTriangle.y + 1.0 ) ;\ntexelsWeightsB.x = 0.16 * ( areaFrom3texelTriangle.z + 1.0 ) ;\ntexelsWeightsB.y = 0.16 * ( areaUncutFrom3texelTriangle.z ) ;\ntexelsWeightsB.z = 0.16 * ( areaFrom3texelTriangle.w ) ; }\nvoid sampleShadowComputeSamplesTent5x5 ( vec4 shadowMapTextureTexelSize, vec2 coord, out float fetchesWeights [ 9 ], out vec2 fetchesUV [ 9 ] ) { vec2 tentCenterInTexelSpace = coord.xy * shadowMapTextureTexelSize.zw ;\nvec2 centerOfFetchesInTexelSpace = floor ( tentCenterInTexelSpace + 0.5 ) ;\nvec2 offsetFromTentCenterToCenterOfFetches = tentCenterInTexelSpace - centerOfFetchesInTexelSpace ;\nvec3 texelsWeightsUA , texelsWeightsUB ;\nvec3 texelsWeightsVA , texelsWeightsVB ;\nsampleShadowGetTexelWeightsTent5x5(offsetFromTentCenterToCenterOfFetches.x, texelsWeightsUA, texelsWeightsUB) ;\nsampleShadowGetTexelWeightsTent5x5(offsetFromTentCenterToCenterOfFetches.y, texelsWeightsVA, texelsWeightsVB) ;\nvec3 fetchesWeightsU = vec3 ( texelsWeightsUA.xz , texelsWeightsUB.y ) + vec3 ( texelsWeightsUA.y , texelsWeightsUB.xz ) ;\nvec3 fetchesWeightsV = vec3 ( texelsWeightsVA.xz , texelsWeightsVB.y ) + vec3 ( texelsWeightsVA.y , texelsWeightsVB.xz ) ;\nvec3 fetchesOffsetsU = vec3 ( texelsWeightsUA.y , texelsWeightsUB.xz ) / fetchesWeightsU.xyz + vec3 ( - 2.5 , - 0.5 , 1.5 ) ;\nvec3 fetchesOffsetsV = vec3 ( texelsWeightsVA.y , texelsWeightsVB.xz ) / fetchesWeightsV.xyz + vec3 ( - 2.5 , - 0.5 , 1.5 ) ;\nfetchesOffsetsU *= shadowMapTextureTexelSize.xxx ;\nfetchesOffsetsV *= shadowMapTextureTexelSize.yyy ;\nvec2 bilinearFetchOrigin = centerOfFetchesInTexelSpace * shadowMapTextureTexelSize.xy ;\nfetchesUV[0] = bilinearFetchOrigin + vec2 ( fetchesOffsetsU.x , fetchesOffsetsV.x ) ;\nfetchesUV[1] = bilinearFetchOrigin + vec2 ( fetchesOffsetsU.y , fetchesOffsetsV.x ) ;\nfetchesUV[2] = bilinearFetchOrigin + vec2 ( fetchesOffsetsU.z , fetchesOffsetsV.x ) ;\nfetchesUV[3] = bilinearFetchOrigin + vec2 ( fetchesOffsetsU.x , fetchesOffsetsV.y ) ;\nfetchesUV[4] = bilinearFetchOrigin + vec2 ( fetchesOffsetsU.y , fetchesOffsetsV.y ) ;\nfetchesUV[5] = bilinearFetchOrigin + vec2 ( fetchesOffsetsU.z , fetchesOffsetsV.y ) ;\nfetchesUV[6] = bilinearFetchOrigin + vec2 ( fetchesOffsetsU.x , fetchesOffsetsV.z ) ;\nfetchesUV[7] = bilinearFetchOrigin + vec2 ( fetchesOffsetsU.y , fetchesOffsetsV.z ) ;\nfetchesUV[8] = bilinearFetchOrigin + vec2 ( fetchesOffsetsU.z , fetchesOffsetsV.z ) ;\nfetchesWeights[0] = fetchesWeightsU.x * fetchesWeightsV.x ;\nfetchesWeights[1] = fetchesWeightsU.y * fetchesWeightsV.x ;\nfetchesWeights[2] = fetchesWeightsU.z * fetchesWeightsV.x ;\nfetchesWeights[3] = fetchesWeightsU.x * fetchesWeightsV.y ;\nfetchesWeights[4] = fetchesWeightsU.y * fetchesWeightsV.y ;\nfetchesWeights[5] = fetchesWeightsU.z * fetchesWeightsV.y ;\nfetchesWeights[6] = fetchesWeightsU.x * fetchesWeightsV.z ;\nfetchesWeights[7] = fetchesWeightsU.y * fetchesWeightsV.z ;\nfetchesWeights[8] = fetchesWeightsU.z * fetchesWeightsV.z ; }\n\n"],[6],[0,"\nfloat sampleShadowMapFiltered9 ( TEXTURE2D_SHADOW_PARAM(shadowMap), vec3 shadowCoord, vec4 shadowmapSize ) { float attenuation ;\nfloat fetchesWeights [ 9 ] ;\nvec2 fetchesUV [ 9 ] ;\nsampleShadowComputeSamplesTent5x5(shadowmapSize, shadowCoord.xy, fetchesWeights, fetchesUV) ;\nattenuation = fetchesWeights[0] * SAMPLE_TEXTURE2D_SHADOW(shadowMap, vec3 ( fetchesUV[0].xy , shadowCoord.z )) ;\nattenuation += fetchesWeights[1] * SAMPLE_TEXTURE2D_SHADOW(shadowMap, vec3 ( fetchesUV[1].xy , shadowCoord.z )) ;\nattenuation += fetchesWeights[2] * SAMPLE_TEXTURE2D_SHADOW(shadowMap, vec3 ( fetchesUV[2].xy , shadowCoord.z )) ;\nattenuation += fetchesWeights[3] * SAMPLE_TEXTURE2D_SHADOW(shadowMap, vec3 ( fetchesUV[3].xy , shadowCoord.z )) ;\nattenuation += fetchesWeights[4] * SAMPLE_TEXTURE2D_SHADOW(shadowMap, vec3 ( fetchesUV[4].xy , shadowCoord.z )) ;\nattenuation += fetchesWeights[5] * SAMPLE_TEXTURE2D_SHADOW(shadowMap, vec3 ( fetchesUV[5].xy , shadowCoord.z )) ;\nattenuation += fetchesWeights[6] * SAMPLE_TEXTURE2D_SHADOW(shadowMap, vec3 ( fetchesUV[6].xy , shadowCoord.z )) ;\nattenuation += fetchesWeights[7] * SAMPLE_TEXTURE2D_SHADOW(shadowMap, vec3 ( fetchesUV[7].xy , shadowCoord.z )) ;\nattenuation += fetchesWeights[8] * SAMPLE_TEXTURE2D_SHADOW(shadowMap, vec3 ( fetchesUV[8].xy , shadowCoord.z )) ;\nreturn attenuation ; }\n\n"],[6],[0,"\nfloat getShadowFade ( vec3 positionWS ) { vec3 camToPixel = positionWS - camera_Position ;\nfloat distanceCamToPixel2 = dot ( camToPixel , camToPixel ) ;\nreturn saturate(distanceCamToPixel2 * scene_ShadowInfo.z + scene_ShadowInfo.w) ; }\nfloat sampleShadowMap ( vec3 positionWS, vec3 shadowCoord ) { float attenuation = 1.0 ;\nif ( shadowCoord.z > 0.0 && shadowCoord.z < 1.0 ) { \n"],[3,"SCENE_SHADOW_TYPE","==",1,206],[0," attenuation = SAMPLE_TEXTURE2D_SHADOW(scene_ShadowMap, shadowCoord) ; \n"],[6],[0,"\n\n"],[3,"SCENE_SHADOW_TYPE","==",2,210],[0," attenuation = sampleShadowMapFiltered4(scene_ShadowMap, shadowCoord, scene_ShadowMapSize) ; \n"],[6],[0,"\n\n"],[3,"SCENE_SHADOW_TYPE","==",3,214],[0," attenuation = sampleShadowMapFiltered9(scene_ShadowMap, shadowCoord, scene_ShadowMapSize) ; \n"],[6],[0,"\nfloat shadowFade = getShadowFade(positionWS) ;\nattenuation = mix ( 1.0 , mix ( attenuation , 1.0 , shadowFade ) , scene_ShadowInfo.x ) ; }\nreturn attenuation ; }\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"VARYINGS_PBR_INCLUDED",248],[0,"\n\n"],[7,"VARYINGS_PBR_INCLUDED"],[0,"\nvarying vec2 uv;\n\n"],[1,"RENDERER_HAS_UV1",226],[0,"varying vec2 uv1;\n\n"],[6],[0,"\n"],[1,"RENDERER_ENABLE_VERTEXCOLOR",230],[0,"varying vec4 vertexColor;\n\n"],[6],[0,"varying vec3 positionWS;\n\n"],[3,"SCENE_FOG_MODE","!=",0,234],[0,"varying vec3 positionVS;\n\n"],[6],[0,"\n"],[1,"RENDERER_HAS_NORMAL",242],[0,"varying vec3 normalWS;\n\n"],[1,"NEED_VERTEX_TANGENT",240],[0,"varying vec3 tangentWS;\nvarying vec3 bitangentWS;\n\n"],[6],[0,"\n"],[6],[0,"\n"],[4,{"t":"and","l":{"t":"def","m":"NEED_CALCULATE_SHADOWS"},"r":{"t":"cmp","m":"SCENE_SHADOW_CASCADED_COUNT","op":"==","v":1}},246],[0,"varying vec3 shadowCoord;\n\n"],[6],[0,"varying vec4 positionCS;\n\n\n"],[6],[0,"\n\n"],[2,"LIGHT_DIRECT_PBR_INCLUDED",539],[0,"\n\n"],[7,"LIGHT_DIRECT_PBR_INCLUDED"],[0,"\n\n"],[2,"FUNCTION_SURFACE_SHADING",258],[0,"\n\n"],[8,"FUNCTION_SURFACE_SHADING","surfaceShading"],[0,"\n\n"],[6],[0,"\n\n"],[2,"FUNCTION_DIFFUSE_LOBE",264],[0,"\n\n"],[8,"FUNCTION_DIFFUSE_LOBE","diffuseLobe"],[0,"\n\n"],[6],[0,"\n\n"],[2,"FUNCTION_SPECULAR_LOBE",270],[0,"\n\n"],[8,"FUNCTION_SPECULAR_LOBE","specularLobe"],[0,"\n\n"],[6],[0,"\n\n"],[2,"FUNCTION_CLEAR_COAT_LOBE",276],[0,"\n\n"],[8,"FUNCTION_CLEAR_COAT_LOBE","clearCoatLobe"],[0,"\n\n"],[6],[0,"\n\n"],[2,"FUNCTION_SHEEN_LOBE",282],[0,"\n\n"],[8,"FUNCTION_SHEEN_LOBE","sheenLobe"],[0,"\n\n"],[6],[0,"\n\n"],[2,"BSDF_INCLUDED",429],[0,"\n\n"],[7,"BSDF_INCLUDED"],[0,"\n\n"],[2,"REFRACTION_INCLUDED",296],[0,"\n\n"],[7,"REFRACTION_INCLUDED"],[0,"\n\n"],[1,"MATERIAL_ENABLE_TRANSMISSION",294],[0,"\nstruct RefractionModelResult { float transmissionLength ; vec3 positionExit ; } ;\nvoid refractionModelSphere ( vec3 V, vec3 positionWS, vec3 normalWS, float ior, float thickness, out RefractionModelResult ray ) { vec3 R1 = refract ( V , normalWS , 1.0 / ior ) ;\nfloat dist = dot ( - normalWS , R1 ) * thickness ;\nvec3 P1 = positionWS + R1 * dist ;\nray.transmissionLength = dist ;\nray.positionExit = P1 ; }\nvoid refractionModelPlanar ( vec3 V, vec3 positionWS, vec3 normalWS, float ior, float thickness, out RefractionModelResult ray ) { vec3 R = refract ( V , normalWS , 1.0 / ior ) ;\nfloat dist = thickness / max ( dot ( - normalWS , R ) , 1e-5f ) ;\nray.transmissionLength = dist ;\nray.positionExit = vec3 ( positionWS + R * dist ) ; }\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[8,"MIN_PERCEPTUAL_ROUGHNESS","0.045"],[0,"\n\n"],[8,"MIN_ROUGHNESS","0.002025"],[0,"\n\n"],[1,"MATERIAL_ENABLE_SHEEN",304],[0,"\nuniform sampler2D scene_PrefilteredDFG;\n\n"],[6],[0,"\nstruct SurfaceData { vec3 albedoColor ; vec3 emissiveColor ; float metallic ; float roughness ; float ambientOcclusion ; float opacity ; float IOR ; vec3 position ; vec4 positionCS ; vec3 normal ; \n"],[1,"NEED_TANGENT_SPACE",308],[0," vec3 tangent ; vec3 bitangent ; \n"],[6],[0," vec3 viewDir ; float dotNV ; float specularIntensity ; vec3 specularColor ; \n"],[1,"MATERIAL_ENABLE_ANISOTROPY",312],[0," float anisotropy ; vec3 anisotropicT ; vec3 anisotropicB ; vec3 anisotropicN ; \n"],[6],[0," \n"],[1,"MATERIAL_ENABLE_CLEAR_COAT",316],[0," float clearCoat ; float clearCoatRoughness ; vec3 clearCoatNormal ; float clearCoatDotNV ; \n"],[6],[0," \n"],[1,"MATERIAL_ENABLE_IRIDESCENCE",320],[0," float iridescenceIOR ; float iridescenceFactor ; float iridescenceThickness ; \n"],[6],[0," \n"],[1,"MATERIAL_ENABLE_SHEEN",324],[0," float sheenRoughness ; vec3 sheenColor ; \n"],[6],[0," \n"],[1,"MATERIAL_ENABLE_TRANSMISSION",328],[0," vec3 absorptionCoefficient ; float transmission ; float thickness ; \n"],[6],[0," } ;\nstruct BSDFData { vec3 diffuseColor ; float roughness ; vec3 envSpecularDFG ; float diffuseAO ; vec3 specularF0 ; vec3 resolvedSpecularF0 ; float specularF90 ; vec3 energyCompensation ; \n"],[1,"MATERIAL_ENABLE_CLEAR_COAT",332],[0," vec3 clearCoatSpecularColor ; float clearCoatRoughness ; \n"],[6],[0," \n"],[1,"MATERIAL_ENABLE_IRIDESCENCE",336],[0," vec3 iridescenceSpecularColor ; \n"],[6],[0," \n"],[1,"MATERIAL_ENABLE_SHEEN",340],[0," float sheenRoughness ; float sheenScaling ; float approxIBLSheenDG ; \n"],[6],[0," } ;\nfloat getAARoughnessFactor ( vec3 normal ) { \n"],[1,"HAS_DERIVATIVES",344],[0," vec3 dxy = max ( abs ( dFdx ( normal ) ) , abs ( dFdy ( normal ) ) ) ;\nreturn max ( max ( dxy.x , dxy.y ) , dxy.z ) ; \n"],[5,346],[0," return 0.0 ; \n"],[6],[0," }\nfloat F_Schlick ( float f0, float f90, float dotLH ) { return f0 + ( f90 - f0 ) * ( pow ( 1.0 - dotLH , 5.0 ) ) ; }\nvec3 F_Schlick ( vec3 f0, float f90, float dotLH ) { float fresnel = exp2 ( ( - 5.55473 * dotLH - 6.98316 ) * dotLH ) ;\nreturn ( f90 - f0 ) * fresnel + f0 ; }\nfloat G_GGX_SmithCorrelated ( float alpha, float dotNL, float dotNV ) { float a2 = pow2(alpha) ;\nfloat gv = dotNL * sqrt ( a2 + ( 1.0 - a2 ) * pow2(dotNV) ) ;\nfloat gl = dotNV * sqrt ( a2 + ( 1.0 - a2 ) * pow2(dotNL) ) ;\nreturn 0.5 / max ( gv + gl , EPSILON ) ; }\n\n"],[1,"MATERIAL_ENABLE_ANISOTROPY",350],[0,"\nfloat G_GGX_SmithCorrelated_Anisotropic ( float at, float ab, float ToV, float BoV, float ToL, float BoL, float NoV, float NoL ) { float lambdaV = NoL * length ( vec3 ( at * ToV , ab * BoV , NoV ) ) ;\nfloat lambdaL = NoV * length ( vec3 ( at * ToL , ab * BoL , NoL ) ) ;\nreturn 0.5 / max ( lambdaV + lambdaL , EPSILON ) ; }\n\n"],[6],[0,"\nfloat D_GGX ( float alpha, float dotNH ) { float a2 = pow2(alpha) ;\nfloat denom = pow2(dotNH) * ( a2 - 1.0 ) + 1.0 ;\nreturn RECIPROCAL_PI * a2 / pow2(denom) ; }\n\n"],[1,"MATERIAL_ENABLE_ANISOTROPY",354],[0,"\nfloat D_GGX_Anisotropic ( float at, float ab, float ToH, float BoH, float NoH ) { float a2 = at * ab ;\nhighp vec3 d = vec3 ( ab * ToH , at * BoH , a2 * NoH ) ;\nhighp float d2 = dot ( d , d ) ;\nfloat b2 = a2 / d2 ;\nreturn a2 * b2 * b2 * RECIPROCAL_PI ; }\n\n"],[6],[0,"\nfloat DG_GGX ( float alpha, float dotNV, float dotNL, float dotNH ) { float D = D_GGX(alpha, dotNH) ;\nfloat G = G_GGX_SmithCorrelated(alpha, dotNL, dotNV) ;\nreturn G * D ; }\n\n"],[1,"MATERIAL_ENABLE_ANISOTROPY",358],[0,"\nfloat DG_GGX_anisotropic ( vec3 h, vec3 l, SurfaceData surfaceData, float alpha, float dotNV, float dotNL, float dotNH ) { vec3 t = surfaceData.anisotropicT ;\nvec3 b = surfaceData.anisotropicB ;\nvec3 v = surfaceData.viewDir ;\nfloat dotTV = dot ( t , v ) ;\nfloat dotBV = dot ( b , v ) ;\nfloat dotTL = dot ( t , l ) ;\nfloat dotBL = dot ( b , l ) ;\nfloat dotTH = dot ( t , h ) ;\nfloat dotBH = dot ( b , h ) ;\nfloat at = max ( alpha * ( 1.0 + surfaceData.anisotropy ) , MIN_ROUGHNESS ) ;\nfloat ab = max ( alpha * ( 1.0 - surfaceData.anisotropy ) , MIN_ROUGHNESS ) ;\nfloat D = D_GGX_Anisotropic(at, ab, dotTH, dotBH, dotNH) ;\nfloat G = G_GGX_SmithCorrelated_Anisotropic(at, ab, dotTV, dotBV, dotTL, dotBL, dotNV, dotNL) ;\nreturn G * D ; }\n\n"],[6],[0,"\nvec3 BRDF_Specular_GGX ( vec3 incidentDirection, SurfaceData surfaceData, BSDFData bsdfData, vec3 normal, vec3 specularColor, float roughness ) { float alpha = pow2(roughness) ;\nvec3 halfDir = normalize ( incidentDirection + surfaceData.viewDir ) ;\nfloat dotNL = saturate(dot ( normal , incidentDirection )) ;\nfloat dotNV = saturate(dot ( normal , surfaceData.viewDir )) ;\nfloat dotNH = saturate(dot ( normal , halfDir )) ;\nfloat dotLH = saturate(dot ( incidentDirection , halfDir )) ;\nvec3 F = F_Schlick(specularColor, bsdfData.specularF90, dotLH) ;\n\n"],[1,"MATERIAL_ENABLE_IRIDESCENCE",362],[0," F = mix ( F , bsdfData.iridescenceSpecularColor , surfaceData.iridescenceFactor ) ; \n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_ANISOTROPY",366],[0," float GD = DG_GGX_anisotropic(halfDir, incidentDirection, surfaceData, alpha, dotNV, dotNL, dotNH) ; \n"],[5,368],[0," float GD = DG_GGX(alpha, dotNV, dotNL, dotNH) ; \n"],[6],[0,"\nreturn F * GD ; }\nvec3 BRDF_Diffuse_Lambert ( vec3 diffuseColor ) { return RECIPROCAL_PI * diffuseColor ; }\n\n"],[1,"MATERIAL_ENABLE_IRIDESCENCE",372],[0,"\nvec3 iorToFresnel0 ( vec3 transmittedIOR, float incidentIOR ) { return pow ( ( transmittedIOR - incidentIOR ) / ( transmittedIOR + incidentIOR ) , vec3 ( 2.0 ) ) ; }\nfloat iorToFresnel0 ( float transmittedIOR, float incidentIOR ) { return pow ( ( transmittedIOR - incidentIOR ) / ( transmittedIOR + incidentIOR ) , 2.0 ) ; }\nvec3 fresnelToIOR ( vec3 f0 ) { vec3 sqrtF0 = sqrt ( f0 ) ;\nreturn ( vec3 ( 1.0 ) + sqrtF0 ) / ( vec3 ( 1.0 ) - sqrtF0 ) ; }\nvec3 evalSensitivity ( float opd, vec3 shift ) { float phase = 2.0 * PI * opd * 1.0e-9 ;\nconst vec3 val = vec3 ( 5.4856e-13 , 4.4201e-13 , 5.2481e-13 ) ;\nconst vec3 pos = vec3 ( 1.6810e+06 , 1.7953e+06 , 2.2084e+06 ) ;\nconst vec3 var = vec3 ( 4.3278e+09 , 9.3046e+09 , 6.6121e+09 ) ;\nvec3 xyz = val * sqrt ( 2.0 * PI * var ) * cos ( pos * phase + shift ) * exp ( - var * pow2(phase) ) ;\nxyz.x += 9.7470e-14 * sqrt ( 2.0 * PI * 4.5282e+09 ) * cos ( 2.2399e+06 * phase + shift[0] ) * exp ( - 4.5282e+09 * pow2(phase) ) ;\nxyz /= 1.0685e-7 ;\nconst mat3 XYZ_TO_RGB = mat3 ( 3.2404542 , - 0.9692660 , 0.0556434 , - 1.5371385 , 1.8760108 , - 0.2040259 , - 0.4985314 , 0.0415560 , 1.0572252 ) ;\nvec3 rgb = XYZ_TO_RGB * xyz ;\nreturn rgb ; }\nvec3 evalIridescenceSpecular ( float outsideIOR, float dotNV, float thinIOR, vec3 baseF0, float baseF90, float iridescenceThickness ) { vec3 iridescence = vec3 ( 1.0 ) ;\nfloat iridescenceIOR = mix ( outsideIOR , thinIOR , smoothstep ( 0.0 , 0.03 , iridescenceThickness ) ) ;\nfloat sinTheta2Sq = pow ( outsideIOR / iridescenceIOR , 2.0 ) * ( 1.0 - pow ( dotNV , 2.0 ) ) ;\nfloat cosTheta2Sq = 1.0 - sinTheta2Sq ;\nif ( cosTheta2Sq < 0.0 ) { return iridescence ; }\nfloat cosTheta2 = sqrt ( cosTheta2Sq ) ;\nfloat f0 = iorToFresnel0(iridescenceIOR, outsideIOR) ;\nfloat reflectance = F_Schlick(f0, baseF90, dotNV) ;\nfloat t121 = 1.0 - reflectance ;\nfloat phi12 = 0.0 ;\nfloat phi21 = PI - phi12 ;\nvec3 baseIOR = fresnelToIOR(clamp ( baseF0 , 0.0 , 0.9999 )) ;\nvec3 r1 = iorToFresnel0(baseIOR, iridescenceIOR) ;\nvec3 r23 = F_Schlick(r1, baseF90, cosTheta2) ;\nvec3 phi23 = vec3 ( 0.0 ) ;\nif ( baseIOR[0] < iridescenceIOR ) { phi23[0] = PI ; }\nif ( baseIOR[1] < iridescenceIOR ) { phi23[1] = PI ; }\nif ( baseIOR[2] < iridescenceIOR ) { phi23[2] = PI ; }\nfloat opd = 2.0 * iridescenceIOR * iridescenceThickness * cosTheta2 ;\nvec3 phi = vec3 ( phi21 ) + phi23 ;\nvec3 r123 = clamp ( reflectance * r23 , 1e-5 , 0.9999 ) ;\nvec3 sr123 = sqrt ( r123 ) ;\nvec3 rs = pow2(t121) * r23 / ( vec3 ( 1.0 ) - r123 ) ;\nvec3 c0 = reflectance + rs ;\niridescence = c0 ;\nvec3 cm = rs - t121 ;\nfor ( int m = 1 ; m <= 2 ; ++ m ) { cm *= sr123 ;\nvec3 sm = 2.0 * evalSensitivity(float ( m ) * opd, float ( m ) * phi) ;\niridescence += cm * sm ; }\nreturn iridescence = max ( iridescence , vec3 ( 0.0 ) ) ; }\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_SHEEN",382],[0,"\nfloat D_Charlie ( float roughness, float dotNH ) { float invAlpha = 1.0 / roughness ;\nfloat cos2h = dotNH * dotNH ;\nfloat sin2h = max ( 1.0 - cos2h , 0.0078125 ) ;\nreturn ( 2.0 + invAlpha ) * pow ( sin2h , invAlpha * 0.5 ) / ( 2.0 * PI ) ; }\nfloat V_Neubelt ( float NoV, float NoL ) { return saturate(1.0 / ( 4.0 * ( NoL + NoV - NoL * NoV ) )) ; }\nvec3 sheenBRDF ( vec3 incidentDirection, SurfaceData surfaceData, vec3 sheenColor, float sheenRoughness ) { vec3 halfDir = normalize ( incidentDirection + surfaceData.viewDir ) ;\nfloat dotNL = saturate(dot ( surfaceData.normal , incidentDirection )) ;\nfloat dotNH = saturate(dot ( surfaceData.normal , halfDir )) ;\nfloat D = D_Charlie(sheenRoughness, dotNH) ;\nfloat V = V_Neubelt(surfaceData.dotNV, dotNL) ;\nvec3 F = sheenColor ;\nreturn D * V * F ; }\nfloat prefilteredSheenDFG ( float dotNV, float sheenRoughness ) { \n"],[1,"HAS_TEX_LOD",378],[0," return texture2DLodEXT ( scene_PrefilteredDFG , vec2 ( dotNV , sheenRoughness ) , 0.0 ).b ; \n"],[5,380],[0," return texture2D ( scene_PrefilteredDFG , vec2 ( dotNV , sheenRoughness ) , 0.0 ).b ; \n"],[6],[0," }\n\n"],[6],[0,"\nvec2 envDFGApprox ( float roughness, float dotNV ) { const vec4 c0 = vec4 ( - 1 , - 0.0275 , - 0.572 , 0.022 ) ;\nconst vec4 c1 = vec4 ( 1 , 0.0425 , 1.04 , - 0.04 ) ;\nvec4 r = roughness * c0 + c1 ;\nfloat a004 = min ( r.x * r.x , exp2 ( - 9.28 * dotNV ) ) * r.x + r.y ;\nreturn vec2 ( - 1.04 , 1.04 ) * a004 + r.zw ; }\nvec3 envBRDFApprox ( vec3 f0, float f90, float roughness, float dotNV ) { vec2 AB = envDFGApprox(roughness, dotNV) ;\nreturn f0 * AB.x + f90 * AB.y ; }\n\n"],[1,"MATERIAL_ENABLE_TRANSMISSION",397],[0,"\nuniform sampler2D camera_OpaqueTexture;\nvec3 evaluateTransmission ( SurfaceData surfaceData, BSDFData bsdfData ) { RefractionModelResult ray ;\n\n"],[3,"REFRACTION_MODE","==",0,388],[0," refractionModelSphere(- surfaceData.viewDir, surfaceData.position, surfaceData.normal, surfaceData.IOR, surfaceData.thickness, ray) ; \n"],[5,391],[3,"REFRACTION_MODE","==",1,391],[0," refractionModelPlanar(- surfaceData.viewDir, surfaceData.position, surfaceData.normal, surfaceData.IOR, surfaceData.thickness, ray) ; \n"],[6],[0,"\nvec3 refractedRayExit = ray.positionExit ;\nvec4 samplingPositionNDC = camera_ProjMat * camera_ViewMat * vec4 ( refractedRayExit , 1.0 ) ;\nvec2 refractionCoords = ( samplingPositionNDC.xy / samplingPositionNDC.w ) * 0.5 + 0.5 ;\nvec3 refractionTransmitted = texture2DSRGB(camera_OpaqueTexture, refractionCoords).rgb ;\nrefractionTransmitted *= bsdfData.diffuseColor ;\nrefractionTransmitted *= ( 1.0 - max ( max ( bsdfData.envSpecularDFG.r , bsdfData.envSpecularDFG.g ) , bsdfData.envSpecularDFG.b ) ) ;\n\n"],[1,"MATERIAL_HAS_THICKNESS",395],[0," vec3 transmittance = min ( vec3 ( 1.0 ) , exp ( - surfaceData.absorptionCoefficient * ray.transmissionLength ) ) ;\nrefractionTransmitted *= transmittance ; \n"],[6],[0,"\nreturn refractionTransmitted ; }\n\n"],[6],[0,"\n\n"],[1,"SCENE_ENABLE_AMBIENT_OCCLUSION",407],[0,"\nuniform sampler2D camera_AOTexture;\nfloat evaluateAmbientOcclusion ( vec2 uv ) { \n"],[1,"MATERIAL_IS_TRANSPARENT",403],[0," return 1.0 ; \n"],[5,405],[0," return texture2D ( camera_AOTexture , uv ).r ; \n"],[6],[0," }\n\n"],[6],[0,"\nvoid initBSDFData ( SurfaceData surfaceData, out BSDFData bsdfData ) { vec3 albedoColor = surfaceData.albedoColor ;\nfloat metallic = surfaceData.metallic ;\nfloat roughness = surfaceData.roughness ;\nvec3 dielectricBaseF0 = vec3 ( pow2(( surfaceData.IOR - 1.0 ) / ( surfaceData.IOR + 1.0 )) ) ;\nvec3 dielectricF0 = min ( dielectricBaseF0 * surfaceData.specularColor , vec3 ( 1.0 ) ) * surfaceData.specularIntensity ;\nfloat dielectricF90 = surfaceData.specularIntensity ;\nbsdfData.specularF0 = mix ( dielectricF0 , albedoColor , metallic ) ;\nbsdfData.specularF90 = mix ( dielectricF90 , 1.0 , metallic ) ;\nbsdfData.diffuseColor = albedoColor * ( 1.0 - metallic ) * ( 1.0 - max ( max ( dielectricF0.r , dielectricF0.g ) , dielectricF0.b ) ) ;\nbsdfData.roughness = max ( MIN_PERCEPTUAL_ROUGHNESS , min ( roughness + getAARoughnessFactor(surfaceData.normal) , 1.0 ) ) ;\n\n"],[1,"MATERIAL_ENABLE_IRIDESCENCE",411],[0," float topIOR = 1.0 ;\nbsdfData.iridescenceSpecularColor = evalIridescenceSpecular(topIOR, surfaceData.dotNV, surfaceData.iridescenceIOR, bsdfData.specularF0, bsdfData.specularF90, surfaceData.iridescenceThickness) ; \n"],[6],[0,"\nbsdfData.resolvedSpecularF0 = bsdfData.specularF0 ;\n\n"],[1,"MATERIAL_ENABLE_IRIDESCENCE",415],[0," bsdfData.resolvedSpecularF0 = mix ( bsdfData.resolvedSpecularF0 , bsdfData.iridescenceSpecularColor , surfaceData.iridescenceFactor ) ; \n"],[6],[0,"\nvec2 dfg = envDFGApprox(bsdfData.roughness, surfaceData.dotNV) ;\nbsdfData.envSpecularDFG = bsdfData.resolvedSpecularF0 * dfg.x + bsdfData.specularF90 * dfg.y ;\nbsdfData.energyCompensation = 1.0 + bsdfData.resolvedSpecularF0 * ( 1.0 / max ( dfg.x + dfg.y , EPSILON ) - 1.0 ) ;\nbsdfData.diffuseAO = surfaceData.ambientOcclusion ;\n\n"],[1,"SCENE_ENABLE_AMBIENT_OCCLUSION",419],[0," float ambientAO = evaluateAmbientOcclusion(( surfaceData.positionCS.xy / surfaceData.positionCS.w ) * 0.5 + 0.5) ;\nbsdfData.diffuseAO = min ( bsdfData.diffuseAO , ambientAO ) ; \n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_CLEAR_COAT",423],[0," bsdfData.clearCoatRoughness = max ( MIN_PERCEPTUAL_ROUGHNESS , min ( surfaceData.clearCoatRoughness + getAARoughnessFactor(surfaceData.clearCoatNormal) , 1.0 ) ) ;\nbsdfData.clearCoatSpecularColor = vec3 ( 0.04 ) ; \n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_SHEEN",427],[0," bsdfData.sheenRoughness = max ( MIN_PERCEPTUAL_ROUGHNESS , min ( surfaceData.sheenRoughness + getAARoughnessFactor(surfaceData.normal) , 1.0 ) ) ;\nbsdfData.approxIBLSheenDG = prefilteredSheenDFG(surfaceData.dotNV, bsdfData.sheenRoughness) ;\nbsdfData.sheenScaling = 1.0 - bsdfData.approxIBLSheenDG * max ( max ( surfaceData.sheenColor.r , surfaceData.sheenColor.g ) , surfaceData.sheenColor.b ) ; \n"],[6],[0," }\n\n"],[6],[0,"\n\n"],[2,"LIGHT_INCLUDED",477],[0,"\n\n"],[7,"LIGHT_INCLUDED"],[0,"\nuniform ivec4 renderer_Layer;\n\n"],[2,"GRAPHICS_API_WEBGL2",437],[0,"\nbool isBitSet ( float value, float mask, float bitIndex ) { return mod ( floor ( value / pow ( 2.0 , bitIndex ) ) , 2.0 ) == 1.0 && mod ( floor ( mask / pow ( 2.0 , bitIndex ) ) , 2.0 ) == 1.0 ; }\n\n"],[6],[0,"\nbool isRendererCulledByLight ( ivec2 rendererLayer, ivec2 lightCullingMask ) { \n"],[1,"GRAPHICS_API_WEBGL2",441],[0," return ! ( ( rendererLayer.x & lightCullingMask.x ) != 0 || ( rendererLayer.y & lightCullingMask.y ) != 0 ) ; \n"],[5,443],[0," for ( int i = 0 ; i < 16 ; i ++ ) { if ( isBitSet(float ( rendererLayer.x ), float ( lightCullingMask.x ), float ( i )) || isBitSet(float ( rendererLayer.y ), float ( lightCullingMask.y ), float ( i )) ) { return false ; } }\nreturn true ; \n"],[6],[0," }\n\n"],[1,"SCENE_DIRECT_LIGHT_COUNT",451],[0,"\nstruct DirectLight { vec3 color ; vec3 direction ; } ;\nuniform ivec2 scene_DirectLightCullingMask [ SCENE_DIRECT_LIGHT_COUNT ];\nuniform vec3 scene_DirectLightColor [ SCENE_DIRECT_LIGHT_COUNT ];\nuniform vec3 scene_DirectLightDirection [ SCENE_DIRECT_LIGHT_COUNT ];\n\n"],[1,"GRAPHICS_API_WEBGL2",449],[0,"\nDirectLight getDirectLight ( int index ) { DirectLight light ;\nlight.color = scene_DirectLightColor[index] ;\nlight.direction = scene_DirectLightDirection[index] ;\nreturn light ; }\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[1,"SCENE_POINT_LIGHT_COUNT",459],[0,"\nstruct PointLight { vec3 color ; vec3 position ; float distance ; } ;\nuniform ivec2 scene_PointLightCullingMask [ SCENE_POINT_LIGHT_COUNT ];\nuniform vec3 scene_PointLightColor [ SCENE_POINT_LIGHT_COUNT ];\nuniform vec3 scene_PointLightPosition [ SCENE_POINT_LIGHT_COUNT ];\nuniform float scene_PointLightDistance [ SCENE_POINT_LIGHT_COUNT ];\n\n"],[1,"GRAPHICS_API_WEBGL2",457],[0,"\nPointLight getPointLight ( int index ) { PointLight light ;\nlight.color = scene_PointLightColor[index] ;\nlight.position = scene_PointLightPosition[index] ;\nlight.distance = scene_PointLightDistance[index] ;\nreturn light ; }\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[1,"SCENE_SPOT_LIGHT_COUNT",467],[0,"\nstruct SpotLight { vec3 color ; vec3 position ; vec3 direction ; float distance ; float angleCos ; float penumbraCos ; } ;\nuniform ivec2 scene_SpotLightCullingMask [ SCENE_SPOT_LIGHT_COUNT ];\nuniform vec3 scene_SpotLightColor [ SCENE_SPOT_LIGHT_COUNT ];\nuniform vec3 scene_SpotLightPosition [ SCENE_SPOT_LIGHT_COUNT ];\nuniform vec3 scene_SpotLightDirection [ SCENE_SPOT_LIGHT_COUNT ];\nuniform float scene_SpotLightDistance [ SCENE_SPOT_LIGHT_COUNT ];\nuniform float scene_SpotLightAngleCos [ SCENE_SPOT_LIGHT_COUNT ];\nuniform float scene_SpotLightPenumbraCos [ SCENE_SPOT_LIGHT_COUNT ];\n\n"],[1,"GRAPHICS_API_WEBGL2",465],[0,"\nSpotLight getSpotLight ( int index ) { SpotLight light ;\nlight.color = scene_SpotLightColor[index] ;\nlight.position = scene_SpotLightPosition[index] ;\nlight.direction = scene_SpotLightDirection[index] ;\nlight.distance = scene_SpotLightDistance[index] ;\nlight.angleCos = scene_SpotLightAngleCos[index] ;\nlight.penumbraCos = scene_SpotLightPenumbraCos[index] ;\nreturn light ; }\n\n"],[6],[0,"\n\n"],[6],[0,"\nstruct EnvMapLight { vec3 diffuse ; float mipMapLevel ; float diffuseIntensity ; float specularIntensity ; } ;\nuniform EnvMapLight scene_EnvMapLight;\n\n"],[1,"SCENE_USE_SH",471],[0,"\nuniform vec3 scene_EnvSH [ 9 ];\n\n"],[6],[0,"\n\n"],[1,"SCENE_USE_SPECULAR_ENV",475],[0,"\nuniform samplerCube scene_EnvSpecularSampler;\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"REFLECTION_LOBE_INCLUDED",491],[0,"\n\n"],[7,"REFLECTION_LOBE_INCLUDED"],[0,"\nvoid diffuseLobe ( SurfaceData surfaceData, BSDFData bsdfData, vec3 attenuationIrradiance, inout vec3 diffuseColor ) { diffuseColor += attenuationIrradiance * BRDF_Diffuse_Lambert(bsdfData.diffuseColor) ; }\nvoid specularLobe ( SurfaceData surfaceData, BSDFData bsdfData, vec3 incidentDirection, vec3 attenuationIrradiance, inout vec3 specularColor ) { specularColor += attenuationIrradiance * BRDF_Specular_GGX(incidentDirection, surfaceData, bsdfData, surfaceData.normal, bsdfData.specularF0, bsdfData.roughness) * bsdfData.energyCompensation ; }\nvoid sheenLobe ( SurfaceData surfaceData, BSDFData bsdfData, vec3 incidentDirection, vec3 attenuationIrradiance, inout vec3 diffuseColor, inout vec3 specularColor ) { \n"],[1,"MATERIAL_ENABLE_SHEEN",485],[0," diffuseColor *= bsdfData.sheenScaling ;\nspecularColor *= bsdfData.sheenScaling ;\nspecularColor += attenuationIrradiance * sheenBRDF(incidentDirection, surfaceData, surfaceData.sheenColor, bsdfData.sheenRoughness) ; \n"],[6],[0," }\nfloat clearCoatLobe ( SurfaceData surfaceData, BSDFData bsdfData, vec3 incidentDirection, vec3 color, inout vec3 specularColor ) { float attenuation = 1.0 ;\n\n"],[1,"MATERIAL_ENABLE_CLEAR_COAT",489],[0," float clearCoatDotNL = saturate(dot ( surfaceData.clearCoatNormal , incidentDirection )) ;\nvec3 clearCoatIrradiance = clearCoatDotNL * color ;\nspecularColor += surfaceData.clearCoat * clearCoatIrradiance * BRDF_Specular_GGX(incidentDirection, surfaceData, bsdfData, surfaceData.clearCoatNormal, bsdfData.clearCoatSpecularColor, bsdfData.clearCoatRoughness) ;\nattenuation -= surfaceData.clearCoat * F_Schlick(0.04, 1.0, surfaceData.clearCoatDotNV) ; \n"],[6],[0,"\nreturn attenuation ; }\n\n"],[6],[0,"\nvoid surfaceShading ( SurfaceData surfaceData, BSDFData bsdfData, vec3 incidentDirection, vec3 lightColor, inout vec3 totalDiffuseColor, inout vec3 totalSpecularColor ) { vec3 diffuseColor = vec3 ( 0 ) ;\nvec3 specularColor = vec3 ( 0 ) ;\nfloat dotNL = saturate(dot ( surfaceData.normal , incidentDirection )) ;\nvec3 irradiance = dotNL * lightColor * PI ;\nfloat attenuation = FUNCTION_CLEAR_COAT_LOBE(surfaceData, bsdfData, incidentDirection, lightColor, specularColor) ;\nvec3 attenuationIrradiance = attenuation * irradiance ;\nFUNCTION_DIFFUSE_LOBE(surfaceData, bsdfData, attenuationIrradiance, diffuseColor) ;\nFUNCTION_SPECULAR_LOBE(surfaceData, bsdfData, incidentDirection, attenuationIrradiance, specularColor) ;\nFUNCTION_SHEEN_LOBE(surfaceData, bsdfData, incidentDirection, attenuationIrradiance, diffuseColor, specularColor) ;\ntotalDiffuseColor += diffuseColor ;\ntotalSpecularColor += specularColor ; }\n\n"],[1,"SCENE_DIRECT_LIGHT_COUNT",495],[0,"\nvoid addDirectionalDirectLightRadiance ( SurfaceData surfaceData, BSDFData bsdfData, DirectLight directionalLight, inout vec3 totalDiffuseColor, inout vec3 totalSpecularColor ) { vec3 lightColor = directionalLight.color ;\nvec3 direction = - directionalLight.direction ;\nFUNCTION_SURFACE_SHADING(surfaceData, bsdfData, direction, lightColor, totalDiffuseColor, totalSpecularColor) ; }\n\n"],[6],[0,"\n\n"],[1,"SCENE_POINT_LIGHT_COUNT",499],[0,"\nvoid addPointDirectLightRadiance ( SurfaceData surfaceData, BSDFData bsdfData, PointLight pointLight, inout vec3 totalDiffuseColor, inout vec3 totalSpecularColor ) { vec3 lVector = pointLight.position - surfaceData.position ;\nvec3 direction = normalize ( lVector ) ;\nfloat lightDistance = length ( lVector ) ;\nvec3 lightColor = pointLight.color ;\nlightColor *= clamp ( 1.0 - pow ( lightDistance / pointLight.distance , 4.0 ) , 0.0 , 1.0 ) ;\nFUNCTION_SURFACE_SHADING(surfaceData, bsdfData, direction, lightColor, totalDiffuseColor, totalSpecularColor) ; }\n\n"],[6],[0,"\n\n"],[1,"SCENE_SPOT_LIGHT_COUNT",503],[0,"\nvoid addSpotDirectLightRadiance ( SurfaceData surfaceData, BSDFData bsdfData, SpotLight spotLight, inout vec3 totalDiffuseColor, inout vec3 totalSpecularColor ) { vec3 lVector = spotLight.position - surfaceData.position ;\nvec3 direction = normalize ( lVector ) ;\nfloat lightDistance = length ( lVector ) ;\nfloat angleCos = dot ( direction , - spotLight.direction ) ;\nfloat spotEffect = smoothstep ( spotLight.penumbraCos , spotLight.angleCos , angleCos ) ;\nfloat decayEffect = clamp ( 1.0 - pow ( lightDistance / spotLight.distance , 4.0 ) , 0.0 , 1.0 ) ;\nvec3 lightColor = spotLight.color ;\nlightColor *= spotEffect * decayEffect ;\nFUNCTION_SURFACE_SHADING(surfaceData, bsdfData, direction, lightColor, totalDiffuseColor, totalSpecularColor) ; }\n\n"],[6],[0,"\nvoid evaluateDirectRadiance ( SurfaceData surfaceData, BSDFData bsdfData, float shadowAttenuation, inout vec3 totalDiffuseColor, inout vec3 totalSpecularColor ) { \n"],[1,"SCENE_DIRECT_LIGHT_COUNT",517],[0," for ( int i = 0 ; i < SCENE_DIRECT_LIGHT_COUNT ; i ++ ) { if ( ! isRendererCulledByLight(renderer_Layer.xy, scene_DirectLightCullingMask[i]) ) { \n"],[1,"GRAPHICS_API_WEBGL2",509],[0," DirectLight directionalLight = getDirectLight(i) ; \n"],[5,511],[0," DirectLight directionalLight ;\ndirectionalLight.color = scene_DirectLightColor[i] ;\ndirectionalLight.direction = scene_DirectLightDirection[i] ; \n"],[6],[0,"\n\n"],[1,"NEED_CALCULATE_SHADOWS",515],[0," if ( i == 0 ) { directionalLight.color *= shadowAttenuation ; } \n"],[6],[0,"\naddDirectionalDirectLightRadiance(surfaceData, bsdfData, directionalLight, totalDiffuseColor, totalSpecularColor) ; } } \n"],[6],[0,"\n\n"],[1,"SCENE_POINT_LIGHT_COUNT",527],[0," for ( int i = 0 ; i < SCENE_POINT_LIGHT_COUNT ; i ++ ) { if ( ! isRendererCulledByLight(renderer_Layer.xy, scene_PointLightCullingMask[i]) ) { \n"],[1,"GRAPHICS_API_WEBGL2",523],[0," PointLight pointLight = getPointLight(i) ; \n"],[5,525],[0," PointLight pointLight ;\npointLight.color = scene_PointLightColor[i] ;\npointLight.position = scene_PointLightPosition[i] ;\npointLight.distance = scene_PointLightDistance[i] ; \n"],[6],[0,"\naddPointDirectLightRadiance(surfaceData, bsdfData, pointLight, totalDiffuseColor, totalSpecularColor) ; } } \n"],[6],[0,"\n\n"],[1,"SCENE_SPOT_LIGHT_COUNT",537],[0," for ( int i = 0 ; i < SCENE_SPOT_LIGHT_COUNT ; i ++ ) { if ( ! isRendererCulledByLight(renderer_Layer.xy, scene_SpotLightCullingMask[i]) ) { \n"],[1,"GRAPHICS_API_WEBGL2",533],[0," SpotLight spotLight = getSpotLight(i) ; \n"],[5,535],[0," SpotLight spotLight ;\nspotLight.color = scene_SpotLightColor[i] ;\nspotLight.position = scene_SpotLightPosition[i] ;\nspotLight.direction = scene_SpotLightDirection[i] ;\nspotLight.distance = scene_SpotLightDistance[i] ;\nspotLight.angleCos = scene_SpotLightAngleCos[i] ;\nspotLight.penumbraCos = scene_SpotLightPenumbraCos[i] ; \n"],[6],[0,"\naddSpotDirectLightRadiance(surfaceData, bsdfData, spotLight, totalDiffuseColor, totalSpecularColor) ; } } \n"],[6],[0," }\n\n"],[6],[0,"\n\n"],[2,"LIGHT_INDIRECT_PBR_INCLUDED",615],[0,"\n\n"],[7,"LIGHT_INDIRECT_PBR_INCLUDED"],[0,"\n\n"],[2,"FUNCTION_DIFFUSE_IBL",549],[0,"\n\n"],[8,"FUNCTION_DIFFUSE_IBL","evaluateDiffuseIBL"],[0,"\n\n"],[6],[0,"\n\n"],[2,"FUNCTION_SPECULAR_IBL",555],[0,"\n\n"],[8,"FUNCTION_SPECULAR_IBL","evaluateSpecularIBL"],[0,"\n\n"],[6],[0,"\n\n"],[2,"FUNCTION_CLEAR_COAT_IBL",561],[0,"\n\n"],[8,"FUNCTION_CLEAR_COAT_IBL","evaluateClearCoatIBL"],[0,"\n\n"],[6],[0,"\n\n"],[2,"FUNCTION_SHEEN_IBL",567],[0,"\n\n"],[8,"FUNCTION_SHEEN_IBL","evaluateSheenIBL"],[0,"\n\n"],[6],[0,"\n\n"],[2,"LIGHT_INDIRECT_FUNCTIONS_INCLUDED",599],[0,"\n\n"],[7,"LIGHT_INDIRECT_FUNCTIONS_INCLUDED"],[0,"\nvec3 getReflectedVector ( SurfaceData surfaceData, vec3 n ) { \n"],[1,"MATERIAL_ENABLE_ANISOTROPY",575],[0," vec3 r = reflect ( - surfaceData.viewDir , surfaceData.anisotropicN ) ; \n"],[5,577],[0," vec3 r = reflect ( - surfaceData.viewDir , n ) ; \n"],[6],[0,"\nreturn r ; }\nfloat getSpecularMIPLevel ( float roughness, int maxMIPLevel ) { return roughness * float ( maxMIPLevel ) ; }\nvec3 getLightProbeRadiance ( SurfaceData surfaceData, vec3 normal, float roughness ) { \n"],[2,"SCENE_USE_SPECULAR_ENV",581],[0," return vec3 ( 0 ) ; \n"],[5,593],[0," vec3 reflectVec = getReflectedVector(surfaceData, normal) ;\nfloat specularMIPLevel = getSpecularMIPLevel(roughness, int ( scene_EnvMapLight.mipMapLevel )) ;\n\n"],[1,"HAS_TEX_LOD",585],[0," vec4 envMapColor = textureCubeLodEXT ( scene_EnvSpecularSampler , reflectVec , specularMIPLevel ) ; \n"],[5,587],[0," vec4 envMapColor = textureCube ( scene_EnvSpecularSampler , reflectVec , specularMIPLevel ) ; \n"],[6],[0,"\n\n"],[1,"ENGINE_NO_SRGB",591],[0," envMapColor = sRGBToLinear(envMapColor) ; \n"],[6],[0,"\nreturn envMapColor.rgb * scene_EnvMapLight.specularIntensity ; \n"],[6],[0," }\nfloat evaluateSpecularOcclusion ( float dotNV, float diffuseAO, float roughness ) { float specularAOFactor = 1.0 ;\n\n"],[4,{"t":"and","l":{"t":"or","l":{"t":"def","m":"MATERIAL_HAS_OCCLUSION_TEXTURE"},"r":{"t":"def","m":"SCENE_ENABLE_AMBIENT_OCCLUSION"}},"r":{"t":"def","m":"SCENE_USE_SPECULAR_ENV"}},597],[0," specularAOFactor = saturate(pow ( dotNV + diffuseAO , exp2 ( - 16.0 * roughness - 1.0 ) ) - 1.0 + diffuseAO) ; \n"],[6],[0,"\nreturn specularAOFactor ; }\n\n"],[6],[0,"\nvec3 getLightProbeIrradiance ( vec3 sh [ 9 ], vec3 normal ) { vec3 result = sh[0] + sh[1] * ( normal.y ) + sh[2] * ( normal.z ) + sh[3] * ( normal.x ) + sh[4] * ( normal.y * normal.x ) + sh[5] * ( normal.y * normal.z ) + sh[6] * ( 3.0 * normal.z * normal.z - 1.0 ) + sh[7] * ( normal.z * normal.x ) + sh[8] * ( normal.x * normal.x - normal.y * normal.y ) ;\nreturn max ( result , vec3 ( 0.0 ) ) ; }\nvoid evaluateDiffuseIBL ( SurfaceData surfaceData, BSDFData bsdfData, inout vec3 diffuseColor ) { \n"],[1,"SCENE_USE_SH",603],[0," vec3 irradiance = getLightProbeIrradiance(scene_EnvSH, surfaceData.normal) ;\nirradiance *= scene_EnvMapLight.diffuseIntensity ; \n"],[5,605],[0," vec3 irradiance = scene_EnvMapLight.diffuse * scene_EnvMapLight.diffuseIntensity ;\nirradiance *= PI ; \n"],[6],[0,"\ndiffuseColor += bsdfData.diffuseAO * irradiance * BRDF_Diffuse_Lambert(bsdfData.diffuseColor) ; }\nfloat evaluateClearCoatIBL ( SurfaceData surfaceData, BSDFData bsdfData, inout vec3 specularColor ) { float radianceAttenuation = 1.0 ;\n\n"],[1,"MATERIAL_ENABLE_CLEAR_COAT",609],[0," vec3 clearCoatRadiance = getLightProbeRadiance(surfaceData, surfaceData.clearCoatNormal, bsdfData.clearCoatRoughness) ;\nfloat specularAO = evaluateSpecularOcclusion(surfaceData.dotNV, bsdfData.diffuseAO, bsdfData.clearCoatRoughness) ;\nspecularColor += specularAO * clearCoatRadiance * surfaceData.clearCoat * envBRDFApprox(bsdfData.clearCoatSpecularColor, 1.0, bsdfData.clearCoatRoughness, surfaceData.clearCoatDotNV) ;\nradianceAttenuation -= surfaceData.clearCoat * F_Schlick(0.04, 1.0, surfaceData.clearCoatDotNV) ; \n"],[6],[0,"\nreturn radianceAttenuation ; }\nvoid evaluateSpecularIBL ( SurfaceData surfaceData, BSDFData bsdfData, float radianceAttenuation, inout vec3 outSpecularColor ) { vec3 radiance = getLightProbeRadiance(surfaceData, surfaceData.normal, bsdfData.roughness) ;\nfloat specularAO = evaluateSpecularOcclusion(surfaceData.dotNV, bsdfData.diffuseAO, bsdfData.roughness) ;\noutSpecularColor += specularAO * radianceAttenuation * radiance * envBRDFApprox(bsdfData.resolvedSpecularF0, bsdfData.specularF90, bsdfData.roughness, surfaceData.dotNV) * bsdfData.energyCompensation ; }\nvoid evaluateSheenIBL ( SurfaceData surfaceData, BSDFData bsdfData, float radianceAttenuation, inout vec3 diffuseColor, inout vec3 specularColor ) { \n"],[1,"MATERIAL_ENABLE_SHEEN",613],[0," diffuseColor *= bsdfData.sheenScaling ;\nspecularColor *= bsdfData.sheenScaling ;\nfloat specularAO = evaluateSpecularOcclusion(surfaceData.dotNV, bsdfData.diffuseAO, bsdfData.sheenRoughness) ;\nvec3 reflectance = specularAO * radianceAttenuation * bsdfData.approxIBLSheenDG * surfaceData.sheenColor ;\nspecularColor += reflectance ; \n"],[6],[0," }\nvoid evaluateIBL ( SurfaceData surfaceData, BSDFData bsdfData, inout vec3 totalDiffuseColor, inout vec3 totalSpecularColor ) { vec3 diffuseColor = vec3 ( 0 ) ;\nvec3 specularColor = vec3 ( 0 ) ;\nFUNCTION_DIFFUSE_IBL(surfaceData, bsdfData, diffuseColor) ;\nfloat radianceAttenuation = FUNCTION_CLEAR_COAT_IBL(surfaceData, bsdfData, specularColor) ;\nFUNCTION_SPECULAR_IBL(surfaceData, bsdfData, radianceAttenuation, specularColor) ;\nFUNCTION_SHEEN_IBL(surfaceData, bsdfData, radianceAttenuation, diffuseColor, specularColor) ;\ntotalDiffuseColor += diffuseColor ;\ntotalSpecularColor += specularColor ; }\n\n"],[6],[0,"\n\n"],[2,"VERTEX_INCLUDE",621],[0,"\n\n"],[7,"VERTEX_INCLUDE"],[0,"\n\n"],[6],[0,"\n\n"],[2,"MATERIAL_INPUT_PBR_INCLUDED",872],[0,"\n\n"],[7,"MATERIAL_INPUT_PBR_INCLUDED"],[0,"\n\n"],[2,"NORMAL_INCLUDED",637],[0,"\n\n"],[7,"NORMAL_INCLUDED"],[0,"\nvec3 getNormalByNormalTexture ( mat3 tbn, sampler2D normalTexture, float normalIntensity, vec2 uv, bool isFrontFacing ) { vec3 normal = ( texture2D ( normalTexture , uv ) ).rgb ;\nnormal = normalize ( tbn * ( ( 2.0 * normal - 1.0 ) * vec3 ( normalIntensity , normalIntensity , 1.0 ) ) ) ;\nnormal *= float ( isFrontFacing ) * 2.0 - 1.0 ;\nreturn normal ; }\nmat3 getTBNByDerivatives ( vec2 uv, vec3 normal, vec3 position, bool isFrontFacing ) { \n"],[1,"HAS_DERIVATIVES",633],[0," uv = isFrontFacing ? uv : - uv ;\nvec3 dp1 = dFdx ( position ) ;\nvec3 dp2 = dFdy ( position ) ;\nvec2 duv1 = dFdx ( uv ) ;\nvec2 duv2 = dFdy ( uv ) ;\nvec3 dp2perp = cross ( dp2 , normal ) ;\nvec3 dp1perp = cross ( normal , dp1 ) ;\nvec3 tangent = dp2perp * duv1.x + dp1perp * duv2.x ;\nvec3 bitangent = dp2perp * duv1.y + dp1perp * duv2.y ;\nfloat denom = max ( dot ( tangent , tangent ) , dot ( bitangent , bitangent ) ) ;\nfloat invmax = ( denom == 0.0 ) ? 0.0 : camera_ProjectionParams.x / sqrt ( denom ) ;\nreturn mat3 ( tangent * invmax , bitangent * invmax , normal ) ; \n"],[5,635],[0," return mat3 ( vec3 ( 0.0 ) , vec3 ( 0.0 ) , normal ) ; \n"],[6],[0," }\n\n"],[6],[0,"\nuniform float material_AlphaCutoff;\nuniform vec4 material_BaseColor;\nuniform float material_Metal;\nuniform float material_Roughness;\nuniform float material_IOR;\nuniform vec3 material_EmissiveColor;\nuniform float material_NormalIntensity;\nuniform float material_OcclusionIntensity;\nuniform float material_OcclusionTextureCoord;\nuniform float material_SpecularIntensity;\nuniform vec3 material_SpecularColor;\n\n"],[1,"MATERIAL_HAS_SPECULAR_TEXTURE",641],[0,"\nuniform sampler2D material_SpecularIntensityTexture;\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_SPECULAR_COLOR_TEXTURE",645],[0,"\nuniform sampler2D material_SpecularColorTexture;\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_CLEAR_COAT",661],[0,"\nuniform float material_ClearCoat;\nuniform float material_ClearCoatRoughness;\n\n"],[1,"MATERIAL_HAS_CLEAR_COAT_TEXTURE",651],[0,"\nuniform sampler2D material_ClearCoatTexture;\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_CLEAR_COAT_ROUGHNESS_TEXTURE",655],[0,"\nuniform sampler2D material_ClearCoatRoughnessTexture;\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_CLEAR_COAT_NORMAL_TEXTURE",659],[0,"\nuniform sampler2D material_ClearCoatNormalTexture;\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_ANISOTROPY",669],[0,"\nuniform vec3 material_AnisotropyInfo;\n\n"],[1,"MATERIAL_HAS_ANISOTROPY_TEXTURE",667],[0,"\nuniform sampler2D material_AnisotropyTexture;\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_IRIDESCENCE",681],[0,"\nuniform vec4 material_IridescenceInfo;\n\n"],[1,"MATERIAL_HAS_IRIDESCENCE_THICKNESS_TEXTURE",675],[0,"\nuniform sampler2D material_IridescenceThicknessTexture;\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_IRIDESCENCE_TEXTURE",679],[0,"\nuniform sampler2D material_IridescenceTexture;\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_SHEEN",693],[0,"\nuniform float material_SheenRoughness;\nuniform vec3 material_SheenColor;\n\n"],[1,"MATERIAL_HAS_SHEEN_TEXTURE",687],[0,"\nuniform sampler2D material_SheenTexture;\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_SHEEN_ROUGHNESS_TEXTURE",691],[0,"\nuniform sampler2D material_SheenRoughnessTexture;\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_TRANSMISSION",709],[0,"\nuniform float material_Transmission;\n\n"],[1,"MATERIAL_HAS_TRANSMISSION_TEXTURE",699],[0,"\nuniform sampler2D material_TransmissionTexture;\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_THICKNESS",707],[0,"\nuniform vec3 material_AttenuationColor;\nuniform float material_AttenuationDistance;\nuniform float material_Thickness;\n\n"],[1,"MATERIAL_HAS_THICKNESS_TEXTURE",705],[0,"\nuniform sampler2D material_ThicknessTexture;\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_BASETEXTURE",713],[0,"\nuniform sampler2D material_BaseTexture;\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_NORMALTEXTURE",717],[0,"\nuniform sampler2D material_NormalTexture;\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_EMISSIVETEXTURE",721],[0,"\nuniform sampler2D material_EmissiveTexture;\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_ROUGHNESS_METALLIC_TEXTURE",725],[0,"\nuniform sampler2D material_RoughnessMetallicTexture;\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_OCCLUSION_TEXTURE",729],[0,"\nuniform sampler2D material_OcclusionTexture;\n\n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_ANISOTROPY",733],[0,"\nvec3 getAnisotropicBentNormal ( SurfaceData surfaceData ) { vec3 anisotropyDirection = ( surfaceData.anisotropy >= 0.0 ) ? surfaceData.anisotropicB : surfaceData.anisotropicT ;\nvec3 anisotropicTangent = cross ( anisotropyDirection , surfaceData.viewDir ) ;\nvec3 anisotropicNormal = cross ( anisotropicTangent , anisotropyDirection ) ;\nvec3 bentNormal = normalize ( mix ( surfaceData.normal , anisotropicNormal , abs ( surfaceData.anisotropy ) * saturate(5.0 * surfaceData.roughness) ) ) ;\nreturn bentNormal ; }\n\n"],[6],[0,"\nSurfaceData getSurfaceData ( vec2 aoUV, bool isFrontFacing ) { SurfaceData surfaceData ;\nvec2 uv = uv ;\nvec4 baseColor = material_BaseColor ;\nfloat metallic = material_Metal ;\nfloat roughness = material_Roughness ;\nvec3 emissiveRadiance = material_EmissiveColor ;\n\n"],[1,"MATERIAL_HAS_BASETEXTURE",737],[0," baseColor *= texture2DSRGB(material_BaseTexture, uv) ; \n"],[6],[0,"\n\n"],[1,"RENDERER_ENABLE_VERTEXCOLOR",741],[0," baseColor *= vertexColor ; \n"],[6],[0,"\n\n"],[1,"MATERIAL_IS_ALPHA_CUTOFF",745],[0," if ( baseColor.a < material_AlphaCutoff ) { discard ; } \n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_ROUGHNESS_METALLIC_TEXTURE",749],[0," vec4 metalRoughMapColor = texture2D ( material_RoughnessMetallicTexture , uv ) ;\nroughness *= metalRoughMapColor.g ;\nmetallic *= metalRoughMapColor.b ; \n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_EMISSIVETEXTURE",753],[0," emissiveRadiance *= texture2DSRGB(material_EmissiveTexture, uv).rgb ; \n"],[6],[0,"\nsurfaceData.albedoColor = baseColor.rgb ;\nsurfaceData.emissiveColor = emissiveRadiance ;\nsurfaceData.metallic = metallic ;\nsurfaceData.roughness = roughness ;\nsurfaceData.IOR = material_IOR ;\n\n"],[1,"MATERIAL_IS_TRANSPARENT",757],[0," surfaceData.opacity = baseColor.a ; \n"],[5,759],[0," surfaceData.opacity = 1.0 ; \n"],[6],[0,"\nsurfaceData.position = positionWS ;\nsurfaceData.positionCS = positionCS ;\n\n"],[1,"CAMERA_ORTHOGRAPHIC",763],[0," surfaceData.viewDir = - camera_Forward ; \n"],[5,765],[0," surfaceData.viewDir = normalize ( camera_Position - positionWS ) ; \n"],[6],[0,"\n\n"],[1,"RENDERER_HAS_NORMAL",769],[0," vec3 normal = normalize ( normalWS ) ; \n"],[5,774],[1,"HAS_DERIVATIVES",772],[0," vec3 pos_dx = dFdx ( positionWS ) ;\nvec3 pos_dy = dFdy ( positionWS ) ;\nvec3 normal = normalize ( cross ( pos_dx , pos_dy ) ) ;\nnormal *= camera_ProjectionParams.x ; \n"],[5,774],[0," vec3 normal = vec3 ( 0 , 0 , 1 ) ; \n"],[6],[0,"\nnormal *= float ( isFrontFacing ) * 2.0 - 1.0 ;\nsurfaceData.normal = normal ;\n\n"],[1,"NEED_TANGENT_SPACE",788],[0," \n"],[1,"NEED_VERTEX_TANGENT",780],[0," surfaceData.tangent = tangentWS ;\nsurfaceData.bitangent = bitangentWS ;\nmat3 tbn = mat3 ( tangentWS , bitangentWS , normalWS ) ; \n"],[5,782],[0," mat3 tbn = getTBNByDerivatives(uv, normal, positionWS, isFrontFacing) ;\nsurfaceData.tangent = tbn[0] ;\nsurfaceData.bitangent = tbn[1] ; \n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_NORMALTEXTURE",786],[0," surfaceData.normal = getNormalByNormalTexture(tbn, material_NormalTexture, material_NormalIntensity, uv, isFrontFacing) ; \n"],[6],[0," \n"],[6],[0,"\nsurfaceData.dotNV = saturate(dot ( surfaceData.normal , surfaceData.viewDir )) ;\nsurfaceData.specularIntensity = material_SpecularIntensity ;\nsurfaceData.specularColor = material_SpecularColor ;\n\n"],[1,"MATERIAL_HAS_SPECULAR_TEXTURE",792],[0," surfaceData.specularIntensity *= texture2D ( material_SpecularIntensityTexture , uv ).a ; \n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_SPECULAR_COLOR_TEXTURE",796],[0," surfaceData.specularColor *= texture2D ( material_SpecularColorTexture , uv ).rgb ; \n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_CLEAR_COAT",814],[0," \n"],[1,"MATERIAL_HAS_CLEAR_COAT_NORMAL_TEXTURE",802],[0," surfaceData.clearCoatNormal = getNormalByNormalTexture(tbn, material_ClearCoatNormalTexture, material_NormalIntensity, uv, isFrontFacing) ; \n"],[5,804],[0," surfaceData.clearCoatNormal = normal ; \n"],[6],[0,"\nsurfaceData.clearCoatDotNV = saturate(dot ( surfaceData.clearCoatNormal , surfaceData.viewDir )) ;\nsurfaceData.clearCoat = material_ClearCoat ;\nsurfaceData.clearCoatRoughness = material_ClearCoatRoughness ;\n\n"],[1,"MATERIAL_HAS_CLEAR_COAT_TEXTURE",808],[0," surfaceData.clearCoat *= ( texture2D ( material_ClearCoatTexture , uv ) ).r ; \n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_CLEAR_COAT_ROUGHNESS_TEXTURE",812],[0," surfaceData.clearCoatRoughness *= ( texture2D ( material_ClearCoatRoughnessTexture , uv ) ).g ; \n"],[6],[0,"\nsurfaceData.clearCoat = saturate(surfaceData.clearCoat) ; \n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_ANISOTROPY",822],[0," float anisotropy = material_AnisotropyInfo.z ;\nvec3 anisotropicDirection = vec3 ( material_AnisotropyInfo.xy , 0.0 ) ;\n\n"],[1,"MATERIAL_HAS_ANISOTROPY_TEXTURE",820],[0," vec3 anisotropyTextureInfo = ( texture2D ( material_AnisotropyTexture , uv ) ).rgb ;\nanisotropy *= anisotropyTextureInfo.b ;\nanisotropicDirection.xy *= anisotropyTextureInfo.rg * 2.0 - 1.0 ; \n"],[6],[0,"\nsurfaceData.anisotropy = anisotropy ;\nsurfaceData.anisotropicT = normalize ( mat3 ( surfaceData.tangent , surfaceData.bitangent , surfaceData.normal ) * anisotropicDirection ) ;\nsurfaceData.anisotropicB = normalize ( cross ( surfaceData.normal , surfaceData.anisotropicT ) ) ;\nsurfaceData.anisotropicN = getAnisotropicBentNormal(surfaceData) ; \n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_IRIDESCENCE",836],[0," surfaceData.iridescenceFactor = material_IridescenceInfo.x ;\nsurfaceData.iridescenceIOR = material_IridescenceInfo.y ;\n\n"],[1,"MATERIAL_HAS_IRIDESCENCE_THICKNESS_TEXTURE",828],[0," float iridescenceThicknessWeight = texture2D ( material_IridescenceThicknessTexture , uv ).g ;\nsurfaceData.iridescenceThickness = mix ( material_IridescenceInfo.z , material_IridescenceInfo.w , iridescenceThicknessWeight ) ; \n"],[5,830],[0," surfaceData.iridescenceThickness = material_IridescenceInfo.w ; \n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_IRIDESCENCE_TEXTURE",834],[0," surfaceData.iridescenceFactor *= texture2D ( material_IridescenceTexture , uv ).r ; \n"],[6],[0," \n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_SHEEN",848],[0," vec3 sheenColor = material_SheenColor ;\n\n"],[1,"MATERIAL_HAS_SHEEN_TEXTURE",842],[0," sheenColor *= texture2DSRGB(material_SheenTexture, uv).rgb ; \n"],[6],[0,"\nsurfaceData.sheenColor = sheenColor ;\nsurfaceData.sheenRoughness = material_SheenRoughness ;\n\n"],[1,"MATERIAL_HAS_SHEEN_ROUGHNESS_TEXTURE",846],[0," surfaceData.sheenRoughness *= texture2D ( material_SheenRoughnessTexture , uv ).a ; \n"],[6],[0," \n"],[6],[0,"\n\n"],[1,"MATERIAL_ENABLE_TRANSMISSION",864],[0," surfaceData.transmission = material_Transmission ;\n\n"],[1,"MATERIAL_HAS_TRANSMISSION_TEXTURE",854],[0," surfaceData.transmission *= texture2D ( material_TransmissionTexture , uv ).r ; \n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_THICKNESS",862],[0," surfaceData.absorptionCoefficient = - log ( material_AttenuationColor + HALF_EPS ) / max ( HALF_EPS , material_AttenuationDistance ) ;\nsurfaceData.thickness = max ( material_Thickness , 0.0001 ) ;\n\n"],[1,"MATERIAL_HAS_THICKNESS_TEXTURE",860],[0," surfaceData.thickness *= texture2D ( material_ThicknessTexture , uv ).g ; \n"],[6],[0," \n"],[6],[0," \n"],[6],[0,"\n\n"],[1,"MATERIAL_HAS_OCCLUSION_TEXTURE",868],[0," surfaceData.ambientOcclusion = ( ( texture2D ( material_OcclusionTexture , aoUV ) ).r - 1.0 ) * material_OcclusionIntensity + 1.0 ; \n"],[5,870],[0," surfaceData.ambientOcclusion = 1.0 ; \n"],[6],[0,"\nreturn surfaceData ; }\n\n"],[6],[0,"\nvoid main() { BSDFData bsdfData ;\nvec2 aoUV = uv ;\n\n"],[4,{"t":"and","l":{"t":"def","m":"MATERIAL_HAS_OCCLUSION_TEXTURE"},"r":{"t":"def","m":"RENDERER_HAS_UV1"}},876],[0," if ( material_OcclusionTextureCoord == 1.0 ) { aoUV = uv1 ; } \n"],[6],[0,"\nSurfaceData surfaceData = getSurfaceData(aoUV, gl_FrontFacing) ;\ninitBSDFData(surfaceData, bsdfData) ;\nvec3 totalDiffuseColor = vec3 ( 0 , 0 , 0 ) ;\nvec3 totalSpecularColor = vec3 ( 0 , 0 , 0 ) ;\nfloat shadowAttenuation = 1.0 ;\n\n"],[4,{"t":"and","l":{"t":"def","m":"SCENE_DIRECT_LIGHT_COUNT"},"r":{"t":"def","m":"NEED_CALCULATE_SHADOWS"}},886],[0," \n"],[3,"SCENE_SHADOW_CASCADED_COUNT","==",1,882],[0," vec3 shadowCoord = shadowCoord ; \n"],[5,884],[0," vec3 shadowCoord = getShadowCoord(positionWS) ; \n"],[6],[0,"\nshadowAttenuation *= sampleShadowMap(positionWS, shadowCoord) ; \n"],[6],[0,"\nevaluateDirectRadiance(surfaceData, bsdfData, shadowAttenuation, totalDiffuseColor, totalSpecularColor) ;\nevaluateIBL(surfaceData, bsdfData, totalDiffuseColor, totalSpecularColor) ;\n\n"],[1,"MATERIAL_ENABLE_TRANSMISSION",890],[0," vec3 refractionTransmitted = evaluateTransmission(surfaceData, bsdfData) ;\ntotalDiffuseColor = mix ( totalDiffuseColor , refractionTransmitted , surfaceData.transmission ) ; \n"],[6],[0,"\nvec4 color = vec4 ( ( totalDiffuseColor + totalSpecularColor ).rgb , surfaceData.opacity ) ;\ncolor.rgb += surfaceData.emissiveColor ;\n\n"],[3,"SCENE_FOG_MODE","!=",0,894],[0," color = fog(color, positionVS) ; \n"],[6],[0,"\ngl_FragColor = color ; }\n\n"],[6]]}]}]} \ No newline at end of file diff --git a/packages/shader/compiledShaders/Pipeline/DepthOnly.shaderc b/packages/shader/compiledShaders/Pipeline/DepthOnly.shaderc index 2bc07250b6..61357caae3 100644 --- a/packages/shader/compiledShaders/Pipeline/DepthOnly.shaderc +++ b/packages/shader/compiledShaders/Pipeline/DepthOnly.shaderc @@ -1 +1 @@ -{"name":"Pipeline/DepthOnly","platformTarget":0,"subShaders":[{"name":"Default","tags":{},"passes":[{"name":"DepthOnly","isUsePass":false,"tags":{"pipelineStage":"DepthOnly"},"renderStates":{"constantMap":{},"variableMap":{"28":"material_DepthOnlyRenderQueue"}},"vertexShaderInstructions":[[0,"\n"],[2,"COMMON_INCLUDED",30],[0,"\n\n"],[7,"COMMON_INCLUDED"],[0,"\n\n"],[8,"PI","3.14159265359"],[0,"\n\n"],[8,"RECIPROCAL_PI","0.31830988618"],[0,"\n\n"],[8,"EPSILON","1e-6"],[0,"\n\n"],[8,"LOG2","1.442695"],[0,"\n\n"],[8,"HALF_MIN","6.103515625e-5"],[0,"\n\n"],[8,"HALF_EPS","4.8828125e-4"],[0,"\n\n"],[9,"saturate",["a"],"clamp( a, 0.0, 1.0 )"],[0,"\n\n"],[1,"GRAPHICS_API_WEBGL2",24],[0,"\n\n"],[9,"INVERSE_MAT",["mat"],"inverse(mat)"],[0,"\n\n"],[5,28],[0,"\n\n"],[9,"INVERSE_MAT",["mat"],"inverseMat(mat)"],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"TRANSFORM_INCLUDED",36],[0,"\n\n"],[7,"TRANSFORM_INCLUDED"],[0,"\nuniform mat4 renderer_ModelMat;\nuniform mat4 camera_VPMat;\n\n"],[6],[0,"\n\n"],[2,"ATTRIBUTES_INCLUDED",94],[0,"\n\n"],[7,"ATTRIBUTES_INCLUDED"],[0,"\nattribute vec3 POSITION;\n\n"],[1,"RENDERER_HAS_BLENDSHAPE",68],[0,"\n"],[2,"RENDERER_BLENDSHAPE_USE_TEXTURE",66],[0,"attribute vec3 POSITION_BS0;\nattribute vec3 POSITION_BS1;\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},48],[0,"attribute vec3 NORMAL_BS0;\nattribute vec3 NORMAL_BS1;\nattribute vec3 TANGENT_BS0;\nattribute vec3 TANGENT_BS1;\n\n"],[5,64],[0,"\n"],[4,{"t":"or","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},60],[0,"attribute vec3 POSITION_BS2;\nattribute vec3 POSITION_BS3;\n\n"],[1,"RENDERER_BLENDSHAPE_HAS_NORMAL",54],[0,"attribute vec3 NORMAL_BS0;\nattribute vec3 NORMAL_BS1;\nattribute vec3 NORMAL_BS2;\nattribute vec3 NORMAL_BS3;\n\n"],[6],[0,"\n"],[1,"RENDERER_BLENDSHAPE_HAS_TANGENT",58],[0,"attribute vec3 TANGENT_BS0;\nattribute vec3 TANGENT_BS1;\nattribute vec3 TANGENT_BS2;\nattribute vec3 TANGENT_BS3;\n\n"],[6],[0,"\n"],[5,62],[0,"attribute vec3 POSITION_BS2;\nattribute vec3 POSITION_BS3;\nattribute vec3 POSITION_BS4;\nattribute vec3 POSITION_BS5;\nattribute vec3 POSITION_BS6;\nattribute vec3 POSITION_BS7;\n\n"],[6],[0,"\n"],[6],[0,"\n"],[6],[0,"\n"],[6],[0,"\n"],[1,"RENDERER_HAS_UV",72],[0,"\n"],[6],[0,"\n"],[1,"RENDERER_HAS_UV1",76],[0,"\n"],[6],[0,"\n"],[1,"RENDERER_HAS_SKIN",80],[0,"attribute vec4 JOINTS_0;\nattribute vec4 WEIGHTS_0;\n\n"],[6],[0,"\n"],[1,"RENDERER_ENABLE_VERTEXCOLOR",84],[0,"\n"],[6],[0,"\n"],[1,"RENDERER_HAS_NORMAL",88],[0,"attribute vec3 NORMAL;\n\n"],[6],[0,"\n"],[1,"RENDERER_HAS_TANGENT",92],[0,"attribute vec4 TANGENT;\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"SKIN_INCLUDED",116],[0,"\n\n"],[7,"SKIN_INCLUDED"],[0,"\n\n"],[1,"RENDERER_HAS_SKIN",114],[0,"\n\n"],[1,"RENDERER_USE_JOINT_TEXTURE",104],[0,"\nuniform sampler2D renderer_JointSampler;\nuniform float renderer_JointCount;\nmat4 getJointMatrix ( sampler2D smp, float index ) { float base = index / renderer_JointCount ;\nfloat hf = 0.5 / renderer_JointCount ;\nfloat v = base + hf ;\nvec4 m0 = texture2D ( smp , vec2 ( 0.125 , v ) ) ;\nvec4 m1 = texture2D ( smp , vec2 ( 0.375 , v ) ) ;\nvec4 m2 = texture2D ( smp , vec2 ( 0.625 , v ) ) ;\nvec4 m3 = texture2D ( smp , vec2 ( 0.875 , v ) ) ;\nreturn mat4 ( m0 , m1 , m2 , m3 ) ; }\n\n"],[5,106],[0,"\nuniform mat4 renderer_JointMatrix [ RENDERER_JOINTS_NUM ];\n\n"],[6],[0,"\nmat4 getSkinMatrix ( ) { \n"],[1,"RENDERER_USE_JOINT_TEXTURE",110],[0," mat4 skinMatrix = WEIGHTS_0.x * getJointMatrix(renderer_JointSampler, JOINTS_0.x) + WEIGHTS_0.y * getJointMatrix(renderer_JointSampler, JOINTS_0.y) + WEIGHTS_0.z * getJointMatrix(renderer_JointSampler, JOINTS_0.z) + WEIGHTS_0.w * getJointMatrix(renderer_JointSampler, JOINTS_0.w) ; \n"],[5,112],[0," mat4 skinMatrix = WEIGHTS_0.x * renderer_JointMatrix[int ( JOINTS_0.x )] + WEIGHTS_0.y * renderer_JointMatrix[int ( JOINTS_0.y )] + WEIGHTS_0.z * renderer_JointMatrix[int ( JOINTS_0.z )] + WEIGHTS_0.w * renderer_JointMatrix[int ( JOINTS_0.w )] ; \n"],[6],[0,"\nreturn skinMatrix ; }\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"BLENDSHAPE_INCLUDED",194],[0,"\n\n"],[7,"BLENDSHAPE_INCLUDED"],[0,"\n\n"],[1,"RENDERER_HAS_BLENDSHAPE",192],[0,"\n\n"],[1,"RENDERER_BLENDSHAPE_USE_TEXTURE",126],[0,"\nuniform mediump sampler2DArray renderer_BlendShapeTexture;\nuniform ivec3 renderer_BlendShapeTextureInfo;\nuniform float renderer_BlendShapeWeights [ RENDERER_BLENDSHAPE_COUNT ];\nvec3 getBlendShapeVertexElement ( int blendShapeIndex, int vertexElementIndex ) { int y = vertexElementIndex / renderer_BlendShapeTextureInfo.y ;\nint x = vertexElementIndex - y * renderer_BlendShapeTextureInfo.y ;\nivec3 uv = ivec3 ( x , y , blendShapeIndex ) ;\nreturn ( texelFetch ( renderer_BlendShapeTexture , uv , 0 ) ).xyz ; }\n\n"],[5,140],[0,"\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},130],[0,"\nuniform float renderer_BlendShapeWeights [ 2 ];\n\n"],[5,138],[0,"\n\n"],[4,{"t":"or","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},134],[0,"\nuniform float renderer_BlendShapeWeights [ 4 ];\n\n"],[5,136],[0,"\nuniform float renderer_BlendShapeWeights [ 8 ];\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\nvoid calculateBlendShape ( inout vec4 position\n"],[1,"RENDERER_HAS_NORMAL",148],[0," , inout vec3 normal \n"],[1,"RENDERER_HAS_TANGENT",146],[0," , inout vec4 tangent \n"],[6],[0," \n"],[6],[0," ) { \n"],[1,"RENDERER_BLENDSHAPE_USE_TEXTURE",160],[0," int vertexOffset = gl_VertexID * renderer_BlendShapeTextureInfo.x ;\nfor ( int i = 0 ; i < RENDERER_BLENDSHAPE_COUNT ; i ++ ) { int vertexElementOffset = vertexOffset ;\nfloat weight = renderer_BlendShapeWeights[i] ;\nif ( weight != 0.0 ) { position.xyz += getBlendShapeVertexElement(i, vertexElementOffset) * weight ;\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"}},154],[0," vertexElementOffset += 1 ;\nnormal += getBlendShapeVertexElement(i, vertexElementOffset) * weight ; \n"],[6],[0,"\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_HAS_TANGENT"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},158],[0," vertexElementOffset += 1 ;\ntangent.xyz += getBlendShapeVertexElement(i, vertexElementOffset) * weight ; \n"],[6],[0," } } \n"],[5,190],[0," position.xyz += POSITION_BS0 * renderer_BlendShapeWeights[0] ;\nposition.xyz += POSITION_BS1 * renderer_BlendShapeWeights[1] ;\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},172],[0," \n"],[1,"RENDERER_HAS_NORMAL",166],[0," normal += NORMAL_BS0 * renderer_BlendShapeWeights[0] ;\nnormal += NORMAL_BS1 * renderer_BlendShapeWeights[1] ; \n"],[6],[0,"\n\n"],[1,"RENDERER_HAS_TANGENT",170],[0," tangent.xyz += TANGENT_BS0 * renderer_BlendShapeWeights[0] ;\ntangent.xyz += TANGENT_BS1 * renderer_BlendShapeWeights[1] ; \n"],[6],[0," \n"],[5,188],[0," \n"],[4,{"t":"or","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},184],[0," position.xyz += POSITION_BS2 * renderer_BlendShapeWeights[2] ;\nposition.xyz += POSITION_BS3 * renderer_BlendShapeWeights[3] ;\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_HAS_NORMAL"}},178],[0," normal += NORMAL_BS0 * renderer_BlendShapeWeights[0] ;\nnormal += NORMAL_BS1 * renderer_BlendShapeWeights[1] ;\nnormal += NORMAL_BS2 * renderer_BlendShapeWeights[2] ;\nnormal += NORMAL_BS3 * renderer_BlendShapeWeights[3] ; \n"],[6],[0,"\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"},"r":{"t":"def","m":"RENDERER_HAS_TANGENT"}},182],[0," tangent.xyz += TANGENT_BS0 * renderer_BlendShapeWeights[0] ;\ntangent.xyz += TANGENT_BS1 * renderer_BlendShapeWeights[1] ;\ntangent.xyz += TANGENT_BS2 * renderer_BlendShapeWeights[2] ;\ntangent.xyz += TANGENT_BS3 * renderer_BlendShapeWeights[3] ; \n"],[6],[0," \n"],[5,186],[0," position.xyz += POSITION_BS2 * renderer_BlendShapeWeights[2] ;\nposition.xyz += POSITION_BS3 * renderer_BlendShapeWeights[3] ;\nposition.xyz += POSITION_BS4 * renderer_BlendShapeWeights[4] ;\nposition.xyz += POSITION_BS5 * renderer_BlendShapeWeights[5] ;\nposition.xyz += POSITION_BS6 * renderer_BlendShapeWeights[6] ;\nposition.xyz += POSITION_BS7 * renderer_BlendShapeWeights[7] ; \n"],[6],[0," \n"],[6],[0," \n"],[6],[0," }\n\n"],[6],[0,"\n\n"],[6],[0,"\nvoid main() { vec4 position = vec4 ( POSITION , 1.0 ) ;\n\n"],[1,"RENDERER_HAS_NORMAL",202],[0," vec3 normal = vec3 ( NORMAL ) ;\n\n"],[1,"RENDERER_HAS_TANGENT",200],[0," vec4 tangent = vec4 ( TANGENT ) ; \n"],[6],[0," \n"],[6],[0,"\n\n"],[1,"RENDERER_HAS_BLENDSHAPE",214],[0," calculateBlendShape(position\n"],[1,"RENDERER_HAS_NORMAL",212],[0," , normal \n"],[1,"RENDERER_HAS_TANGENT",210],[0," , tangent \n"],[6],[0," \n"],[6],[0,") ; \n"],[6],[0,"\n\n"],[1,"RENDERER_HAS_SKIN",218],[0," mat4 skinMatrix = getSkinMatrix() ;\nposition = skinMatrix * position ; \n"],[6],[0,"\ngl_Position = camera_VPMat * renderer_ModelMat * position ; }"]],"fragmentShaderInstructions":[[0,"\n"],[2,"COMMON_INCLUDED",30],[0,"\n\n"],[7,"COMMON_INCLUDED"],[0,"\n\n"],[8,"PI","3.14159265359"],[0,"\n\n"],[8,"RECIPROCAL_PI","0.31830988618"],[0,"\n\n"],[8,"EPSILON","1e-6"],[0,"\n\n"],[8,"LOG2","1.442695"],[0,"\n\n"],[8,"HALF_MIN","6.103515625e-5"],[0,"\n\n"],[8,"HALF_EPS","4.8828125e-4"],[0,"\n\n"],[9,"saturate",["a"],"clamp( a, 0.0, 1.0 )"],[0,"\n\n"],[1,"GRAPHICS_API_WEBGL2",24],[0,"\n\n"],[9,"INVERSE_MAT",["mat"],"inverse(mat)"],[0,"\n\n"],[5,28],[0,"\n\n"],[9,"INVERSE_MAT",["mat"],"inverseMat(mat)"],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"TRANSFORM_INCLUDED",36],[0,"\n\n"],[7,"TRANSFORM_INCLUDED"],[0,"\n\n"],[6],[0,"\n\n"],[2,"ATTRIBUTES_INCLUDED",42],[0,"\n\n"],[7,"ATTRIBUTES_INCLUDED"],[0,"\n\n"],[6],[0,"\n\n"],[2,"SKIN_INCLUDED",58],[0,"\n\n"],[7,"SKIN_INCLUDED"],[0,"\n\n"],[1,"RENDERER_HAS_SKIN",56],[0,"\n\n"],[1,"RENDERER_USE_JOINT_TEXTURE",52],[0,"\n\n"],[5,54],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"BLENDSHAPE_INCLUDED",86],[0,"\n\n"],[7,"BLENDSHAPE_INCLUDED"],[0,"\n\n"],[1,"RENDERER_HAS_BLENDSHAPE",84],[0,"\n\n"],[1,"RENDERER_BLENDSHAPE_USE_TEXTURE",68],[0,"\n\n"],[5,82],[0,"\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},72],[0,"\n\n"],[5,80],[0,"\n\n"],[4,{"t":"or","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},76],[0,"\n\n"],[5,78],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\nvoid main() { }"]]}]}]} \ No newline at end of file +{"name":"Pipeline/DepthOnly","platformTarget":0,"subShaders":[{"name":"Default","tags":{},"passes":[{"name":"DepthOnly","isUsePass":false,"tags":{"pipelineStage":"DepthOnly"},"renderStates":{"constantMap":{},"variableMap":{"28":"material_DepthOnlyRenderQueue"}},"vertexShaderInstructions":[[0,"\n"],[2,"COMMON_INCLUDED",30],[0,"\n\n"],[7,"COMMON_INCLUDED"],[0,"\n\n"],[8,"PI","3.14159265359"],[0,"\n\n"],[8,"RECIPROCAL_PI","0.31830988618"],[0,"\n\n"],[8,"EPSILON","1e-6"],[0,"\n\n"],[8,"LOG2","1.442695"],[0,"\n\n"],[8,"HALF_MIN","6.103515625e-5"],[0,"\n\n"],[8,"HALF_EPS","4.8828125e-4"],[0,"\n\n"],[9,"saturate",["a"],"clamp( a, 0.0, 1.0 )"],[0,"\n\n"],[1,"GRAPHICS_API_WEBGL2",24],[0,"\n\n"],[9,"INVERSE_MAT",["mat"],"inverse(mat)"],[0,"\n\n"],[5,28],[0,"\n\n"],[9,"INVERSE_MAT",["mat"],"inverseMat(mat)"],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"TRANSFORM_INCLUDED",36],[0,"\n\n"],[7,"TRANSFORM_INCLUDED"],[0,"\nuniform mat4 camera_VPMat;\nuniform mat4 renderer_ModelMat;\n\n"],[6],[0,"\n\n"],[2,"ATTRIBUTES_INCLUDED",94],[0,"\n\n"],[7,"ATTRIBUTES_INCLUDED"],[0,"\nattribute vec3 POSITION;\n\n"],[1,"RENDERER_HAS_BLENDSHAPE",68],[0,"\n"],[2,"RENDERER_BLENDSHAPE_USE_TEXTURE",66],[0,"attribute vec3 POSITION_BS0;\nattribute vec3 POSITION_BS1;\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},48],[0,"attribute vec3 NORMAL_BS0;\nattribute vec3 NORMAL_BS1;\nattribute vec3 TANGENT_BS0;\nattribute vec3 TANGENT_BS1;\n\n"],[5,64],[0,"\n"],[4,{"t":"or","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},60],[0,"attribute vec3 POSITION_BS2;\nattribute vec3 POSITION_BS3;\n\n"],[1,"RENDERER_BLENDSHAPE_HAS_NORMAL",54],[0,"attribute vec3 NORMAL_BS0;\nattribute vec3 NORMAL_BS1;\nattribute vec3 NORMAL_BS2;\nattribute vec3 NORMAL_BS3;\n\n"],[6],[0,"\n"],[1,"RENDERER_BLENDSHAPE_HAS_TANGENT",58],[0,"attribute vec3 TANGENT_BS0;\nattribute vec3 TANGENT_BS1;\nattribute vec3 TANGENT_BS2;\nattribute vec3 TANGENT_BS3;\n\n"],[6],[0,"\n"],[5,62],[0,"attribute vec3 POSITION_BS2;\nattribute vec3 POSITION_BS3;\nattribute vec3 POSITION_BS4;\nattribute vec3 POSITION_BS5;\nattribute vec3 POSITION_BS6;\nattribute vec3 POSITION_BS7;\n\n"],[6],[0,"\n"],[6],[0,"\n"],[6],[0,"\n"],[6],[0,"\n"],[1,"RENDERER_HAS_UV",72],[0,"\n"],[6],[0,"\n"],[1,"RENDERER_HAS_UV1",76],[0,"\n"],[6],[0,"\n"],[1,"RENDERER_HAS_SKIN",80],[0,"attribute vec4 JOINTS_0;\nattribute vec4 WEIGHTS_0;\n\n"],[6],[0,"\n"],[1,"RENDERER_ENABLE_VERTEXCOLOR",84],[0,"\n"],[6],[0,"\n"],[1,"RENDERER_HAS_NORMAL",88],[0,"attribute vec3 NORMAL;\n\n"],[6],[0,"\n"],[1,"RENDERER_HAS_TANGENT",92],[0,"attribute vec4 TANGENT;\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"SKIN_INCLUDED",116],[0,"\n\n"],[7,"SKIN_INCLUDED"],[0,"\n\n"],[1,"RENDERER_HAS_SKIN",114],[0,"\n\n"],[1,"RENDERER_USE_JOINT_TEXTURE",104],[0,"\nuniform sampler2D renderer_JointSampler;\nuniform float renderer_JointCount;\nmat4 getJointMatrix ( sampler2D smp, float index ) { float base = index / renderer_JointCount ;\nfloat hf = 0.5 / renderer_JointCount ;\nfloat v = base + hf ;\nvec4 m0 = texture2D ( smp , vec2 ( 0.125 , v ) ) ;\nvec4 m1 = texture2D ( smp , vec2 ( 0.375 , v ) ) ;\nvec4 m2 = texture2D ( smp , vec2 ( 0.625 , v ) ) ;\nvec4 m3 = texture2D ( smp , vec2 ( 0.875 , v ) ) ;\nreturn mat4 ( m0 , m1 , m2 , m3 ) ; }\n\n"],[5,106],[0,"\nuniform mat4 renderer_JointMatrix [ RENDERER_JOINTS_NUM ];\n\n"],[6],[0,"\nmat4 getSkinMatrix ( ) { \n"],[1,"RENDERER_USE_JOINT_TEXTURE",110],[0," mat4 skinMatrix = WEIGHTS_0.x * getJointMatrix(renderer_JointSampler, JOINTS_0.x) + WEIGHTS_0.y * getJointMatrix(renderer_JointSampler, JOINTS_0.y) + WEIGHTS_0.z * getJointMatrix(renderer_JointSampler, JOINTS_0.z) + WEIGHTS_0.w * getJointMatrix(renderer_JointSampler, JOINTS_0.w) ; \n"],[5,112],[0," mat4 skinMatrix = WEIGHTS_0.x * renderer_JointMatrix[int ( JOINTS_0.x )] + WEIGHTS_0.y * renderer_JointMatrix[int ( JOINTS_0.y )] + WEIGHTS_0.z * renderer_JointMatrix[int ( JOINTS_0.z )] + WEIGHTS_0.w * renderer_JointMatrix[int ( JOINTS_0.w )] ; \n"],[6],[0,"\nreturn skinMatrix ; }\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"BLENDSHAPE_INCLUDED",194],[0,"\n\n"],[7,"BLENDSHAPE_INCLUDED"],[0,"\n\n"],[1,"RENDERER_HAS_BLENDSHAPE",192],[0,"\n\n"],[1,"RENDERER_BLENDSHAPE_USE_TEXTURE",126],[0,"\nuniform mediump sampler2DArray renderer_BlendShapeTexture;\nuniform ivec3 renderer_BlendShapeTextureInfo;\nuniform float renderer_BlendShapeWeights [ RENDERER_BLENDSHAPE_COUNT ];\nvec3 getBlendShapeVertexElement ( int blendShapeIndex, int vertexElementIndex ) { int y = vertexElementIndex / renderer_BlendShapeTextureInfo.y ;\nint x = vertexElementIndex - y * renderer_BlendShapeTextureInfo.y ;\nivec3 uv = ivec3 ( x , y , blendShapeIndex ) ;\nreturn ( texelFetch ( renderer_BlendShapeTexture , uv , 0 ) ).xyz ; }\n\n"],[5,140],[0,"\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},130],[0,"\nuniform float renderer_BlendShapeWeights [ 2 ];\n\n"],[5,138],[0,"\n\n"],[4,{"t":"or","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},134],[0,"\nuniform float renderer_BlendShapeWeights [ 4 ];\n\n"],[5,136],[0,"\nuniform float renderer_BlendShapeWeights [ 8 ];\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\nvoid calculateBlendShape ( inout vec4 position\n"],[1,"RENDERER_HAS_NORMAL",148],[0," , inout vec3 normal \n"],[1,"RENDERER_HAS_TANGENT",146],[0," , inout vec4 tangent \n"],[6],[0," \n"],[6],[0," ) { \n"],[1,"RENDERER_BLENDSHAPE_USE_TEXTURE",160],[0," int vertexOffset = gl_VertexID * renderer_BlendShapeTextureInfo.x ;\nfor ( int i = 0 ; i < RENDERER_BLENDSHAPE_COUNT ; i ++ ) { int vertexElementOffset = vertexOffset ;\nfloat weight = renderer_BlendShapeWeights[i] ;\nif ( weight != 0.0 ) { position.xyz += getBlendShapeVertexElement(i, vertexElementOffset) * weight ;\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"}},154],[0," vertexElementOffset += 1 ;\nnormal += getBlendShapeVertexElement(i, vertexElementOffset) * weight ; \n"],[6],[0,"\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_HAS_TANGENT"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},158],[0," vertexElementOffset += 1 ;\ntangent.xyz += getBlendShapeVertexElement(i, vertexElementOffset) * weight ; \n"],[6],[0," } } \n"],[5,190],[0," position.xyz += POSITION_BS0 * renderer_BlendShapeWeights[0] ;\nposition.xyz += POSITION_BS1 * renderer_BlendShapeWeights[1] ;\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},172],[0," \n"],[1,"RENDERER_HAS_NORMAL",166],[0," normal += NORMAL_BS0 * renderer_BlendShapeWeights[0] ;\nnormal += NORMAL_BS1 * renderer_BlendShapeWeights[1] ; \n"],[6],[0,"\n\n"],[1,"RENDERER_HAS_TANGENT",170],[0," tangent.xyz += TANGENT_BS0 * renderer_BlendShapeWeights[0] ;\ntangent.xyz += TANGENT_BS1 * renderer_BlendShapeWeights[1] ; \n"],[6],[0," \n"],[5,188],[0," \n"],[4,{"t":"or","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},184],[0," position.xyz += POSITION_BS2 * renderer_BlendShapeWeights[2] ;\nposition.xyz += POSITION_BS3 * renderer_BlendShapeWeights[3] ;\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_HAS_NORMAL"}},178],[0," normal += NORMAL_BS0 * renderer_BlendShapeWeights[0] ;\nnormal += NORMAL_BS1 * renderer_BlendShapeWeights[1] ;\nnormal += NORMAL_BS2 * renderer_BlendShapeWeights[2] ;\nnormal += NORMAL_BS3 * renderer_BlendShapeWeights[3] ; \n"],[6],[0,"\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"},"r":{"t":"def","m":"RENDERER_HAS_TANGENT"}},182],[0," tangent.xyz += TANGENT_BS0 * renderer_BlendShapeWeights[0] ;\ntangent.xyz += TANGENT_BS1 * renderer_BlendShapeWeights[1] ;\ntangent.xyz += TANGENT_BS2 * renderer_BlendShapeWeights[2] ;\ntangent.xyz += TANGENT_BS3 * renderer_BlendShapeWeights[3] ; \n"],[6],[0," \n"],[5,186],[0," position.xyz += POSITION_BS2 * renderer_BlendShapeWeights[2] ;\nposition.xyz += POSITION_BS3 * renderer_BlendShapeWeights[3] ;\nposition.xyz += POSITION_BS4 * renderer_BlendShapeWeights[4] ;\nposition.xyz += POSITION_BS5 * renderer_BlendShapeWeights[5] ;\nposition.xyz += POSITION_BS6 * renderer_BlendShapeWeights[6] ;\nposition.xyz += POSITION_BS7 * renderer_BlendShapeWeights[7] ; \n"],[6],[0," \n"],[6],[0," \n"],[6],[0," }\n\n"],[6],[0,"\n\n"],[6],[0,"\nvoid main() { vec4 position = vec4 ( POSITION , 1.0 ) ;\n\n"],[1,"RENDERER_HAS_NORMAL",202],[0," vec3 normal = vec3 ( NORMAL ) ;\n\n"],[1,"RENDERER_HAS_TANGENT",200],[0," vec4 tangent = vec4 ( TANGENT ) ; \n"],[6],[0," \n"],[6],[0,"\n\n"],[1,"RENDERER_HAS_BLENDSHAPE",214],[0," calculateBlendShape(position\n"],[1,"RENDERER_HAS_NORMAL",212],[0," , normal \n"],[1,"RENDERER_HAS_TANGENT",210],[0," , tangent \n"],[6],[0," \n"],[6],[0,") ; \n"],[6],[0,"\n\n"],[1,"RENDERER_HAS_SKIN",218],[0," mat4 skinMatrix = getSkinMatrix() ;\nposition = skinMatrix * position ; \n"],[6],[0,"\ngl_Position = camera_VPMat * renderer_ModelMat * position ; }"]],"fragmentShaderInstructions":[[0,"\n"],[2,"COMMON_INCLUDED",30],[0,"\n\n"],[7,"COMMON_INCLUDED"],[0,"\n\n"],[8,"PI","3.14159265359"],[0,"\n\n"],[8,"RECIPROCAL_PI","0.31830988618"],[0,"\n\n"],[8,"EPSILON","1e-6"],[0,"\n\n"],[8,"LOG2","1.442695"],[0,"\n\n"],[8,"HALF_MIN","6.103515625e-5"],[0,"\n\n"],[8,"HALF_EPS","4.8828125e-4"],[0,"\n\n"],[9,"saturate",["a"],"clamp( a, 0.0, 1.0 )"],[0,"\n\n"],[1,"GRAPHICS_API_WEBGL2",24],[0,"\n\n"],[9,"INVERSE_MAT",["mat"],"inverse(mat)"],[0,"\n\n"],[5,28],[0,"\n\n"],[9,"INVERSE_MAT",["mat"],"inverseMat(mat)"],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"TRANSFORM_INCLUDED",36],[0,"\n\n"],[7,"TRANSFORM_INCLUDED"],[0,"\n\n"],[6],[0,"\n\n"],[2,"ATTRIBUTES_INCLUDED",42],[0,"\n\n"],[7,"ATTRIBUTES_INCLUDED"],[0,"\n\n"],[6],[0,"\n\n"],[2,"SKIN_INCLUDED",58],[0,"\n\n"],[7,"SKIN_INCLUDED"],[0,"\n\n"],[1,"RENDERER_HAS_SKIN",56],[0,"\n\n"],[1,"RENDERER_USE_JOINT_TEXTURE",52],[0,"\n\n"],[5,54],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"BLENDSHAPE_INCLUDED",86],[0,"\n\n"],[7,"BLENDSHAPE_INCLUDED"],[0,"\n\n"],[1,"RENDERER_HAS_BLENDSHAPE",84],[0,"\n\n"],[1,"RENDERER_BLENDSHAPE_USE_TEXTURE",68],[0,"\n\n"],[5,82],[0,"\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},72],[0,"\n\n"],[5,80],[0,"\n\n"],[4,{"t":"or","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},76],[0,"\n\n"],[5,78],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\nvoid main() { }"]]}]}]} \ No newline at end of file diff --git a/packages/shader/compiledShaders/Pipeline/ShadowCaster.shaderc b/packages/shader/compiledShaders/Pipeline/ShadowCaster.shaderc index abd9bb779e..655cffc3b0 100644 --- a/packages/shader/compiledShaders/Pipeline/ShadowCaster.shaderc +++ b/packages/shader/compiledShaders/Pipeline/ShadowCaster.shaderc @@ -1 +1 @@ -{"name":"Pipeline/ShadowCaster","platformTarget":0,"subShaders":[{"name":"Default","tags":{},"passes":[{"name":"ShadowCaster","isUsePass":false,"tags":{"pipelineStage":"ShadowCaster"},"renderStates":{"constantMap":{},"variableMap":{"28":"material_ShadowCasterRenderQueue"}},"vertexShaderInstructions":[[0,"\n"],[2,"COMMON_INCLUDED",30],[0,"\n\n"],[7,"COMMON_INCLUDED"],[0,"\n\n"],[8,"PI","3.14159265359"],[0,"\n\n"],[8,"RECIPROCAL_PI","0.31830988618"],[0,"\n\n"],[8,"EPSILON","1e-6"],[0,"\n\n"],[8,"LOG2","1.442695"],[0,"\n\n"],[8,"HALF_MIN","6.103515625e-5"],[0,"\n\n"],[8,"HALF_EPS","4.8828125e-4"],[0,"\n\n"],[9,"saturate",["a"],"clamp( a, 0.0, 1.0 )"],[0,"\n\n"],[1,"GRAPHICS_API_WEBGL2",24],[0,"\n\n"],[9,"INVERSE_MAT",["mat"],"inverse(mat)"],[0,"\n\n"],[5,28],[0,"\nmat2 inverseMat ( mat2 m ) { return mat2 ( m[1][1] , - m[0][1] , - m[1][0] , m[0][0] ) / ( m[0][0] * m[1][1] - m[0][1] * m[1][0] ) ; }\nmat3 inverseMat ( mat3 m ) { float a00 = m[0][0] , a01 = m[0][1] , a02 = m[0][2] ;\nfloat a10 = m[1][0] , a11 = m[1][1] , a12 = m[1][2] ;\nfloat a20 = m[2][0] , a21 = m[2][1] , a22 = m[2][2] ;\nfloat b01 = a22 * a11 - a12 * a21 ;\nfloat b11 = - a22 * a10 + a12 * a20 ;\nfloat b21 = a21 * a10 - a11 * a20 ;\nfloat det = a00 * b01 + a01 * b11 + a02 * b21 ;\nreturn mat3 ( b01 , ( - a22 * a01 + a02 * a21 ) , ( a12 * a01 - a02 * a11 ) , b11 , ( a22 * a00 - a02 * a20 ) , ( - a12 * a00 + a02 * a10 ) , b21 , ( - a21 * a00 + a01 * a20 ) , ( a11 * a00 - a01 * a10 ) ) / det ; }\nmat4 inverseMat ( mat4 m ) { float a00 = m[0][0] , a01 = m[0][1] , a02 = m[0][2] , a03 = m[0][3] , a10 = m[1][0] , a11 = m[1][1] , a12 = m[1][2] , a13 = m[1][3] , a20 = m[2][0] , a21 = m[2][1] , a22 = m[2][2] , a23 = m[2][3] , a30 = m[3][0] , a31 = m[3][1] , a32 = m[3][2] , a33 = m[3][3] , b00 = a00 * a11 - a01 * a10 , b01 = a00 * a12 - a02 * a10 , b02 = a00 * a13 - a03 * a10 , b03 = a01 * a12 - a02 * a11 , b04 = a01 * a13 - a03 * a11 , b05 = a02 * a13 - a03 * a12 , b06 = a20 * a31 - a21 * a30 , b07 = a20 * a32 - a22 * a30 , b08 = a20 * a33 - a23 * a30 , b09 = a21 * a32 - a22 * a31 , b10 = a21 * a33 - a23 * a31 , b11 = a22 * a33 - a23 * a32 , det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06 ;\nreturn mat4 ( a11 * b11 - a12 * b10 + a13 * b09 , a02 * b10 - a01 * b11 - a03 * b09 , a31 * b05 - a32 * b04 + a33 * b03 , a22 * b04 - a21 * b05 - a23 * b03 , a12 * b08 - a10 * b11 - a13 * b07 , a00 * b11 - a02 * b08 + a03 * b07 , a32 * b02 - a30 * b05 - a33 * b01 , a20 * b05 - a22 * b02 + a23 * b01 , a10 * b10 - a11 * b08 + a13 * b06 , a01 * b08 - a00 * b10 - a03 * b06 , a30 * b04 - a31 * b02 + a33 * b00 , a21 * b02 - a20 * b04 - a23 * b00 , a11 * b07 - a10 * b09 - a12 * b06 , a00 * b09 - a01 * b07 + a02 * b06 , a31 * b01 - a30 * b03 - a32 * b00 , a20 * b03 - a21 * b01 + a22 * b00 ) / det ; }\n\n"],[9,"INVERSE_MAT",["mat"],"inverseMat(mat)"],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"TRANSFORM_INCLUDED",36],[0,"\n\n"],[7,"TRANSFORM_INCLUDED"],[0,"\nuniform mat4 renderer_ModelMat;\nuniform mat4 camera_VPMat;\nuniform mat4 renderer_NormalMat;\n\n"],[6],[0,"\n\n"],[2,"ATTRIBUTES_INCLUDED",94],[0,"\n\n"],[7,"ATTRIBUTES_INCLUDED"],[0,"\nattribute vec3 POSITION;\n\n"],[1,"RENDERER_HAS_BLENDSHAPE",68],[0,"\n"],[2,"RENDERER_BLENDSHAPE_USE_TEXTURE",66],[0,"attribute vec3 POSITION_BS0;\nattribute vec3 POSITION_BS1;\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},48],[0,"attribute vec3 NORMAL_BS0;\nattribute vec3 NORMAL_BS1;\nattribute vec3 TANGENT_BS0;\nattribute vec3 TANGENT_BS1;\n\n"],[5,64],[0,"\n"],[4,{"t":"or","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},60],[0,"attribute vec3 POSITION_BS2;\nattribute vec3 POSITION_BS3;\n\n"],[1,"RENDERER_BLENDSHAPE_HAS_NORMAL",54],[0,"attribute vec3 NORMAL_BS0;\nattribute vec3 NORMAL_BS1;\nattribute vec3 NORMAL_BS2;\nattribute vec3 NORMAL_BS3;\n\n"],[6],[0,"\n"],[1,"RENDERER_BLENDSHAPE_HAS_TANGENT",58],[0,"attribute vec3 TANGENT_BS0;\nattribute vec3 TANGENT_BS1;\nattribute vec3 TANGENT_BS2;\nattribute vec3 TANGENT_BS3;\n\n"],[6],[0,"\n"],[5,62],[0,"attribute vec3 POSITION_BS2;\nattribute vec3 POSITION_BS3;\nattribute vec3 POSITION_BS4;\nattribute vec3 POSITION_BS5;\nattribute vec3 POSITION_BS6;\nattribute vec3 POSITION_BS7;\n\n"],[6],[0,"\n"],[6],[0,"\n"],[6],[0,"\n"],[6],[0,"\n"],[1,"RENDERER_HAS_UV",72],[0,"attribute vec2 TEXCOORD_0;\n\n"],[6],[0,"\n"],[1,"RENDERER_HAS_UV1",76],[0,"\n"],[6],[0,"\n"],[1,"RENDERER_HAS_SKIN",80],[0,"attribute vec4 JOINTS_0;\nattribute vec4 WEIGHTS_0;\n\n"],[6],[0,"\n"],[1,"RENDERER_ENABLE_VERTEXCOLOR",84],[0,"\n"],[6],[0,"\n"],[1,"RENDERER_HAS_NORMAL",88],[0,"attribute vec3 NORMAL;\n\n"],[6],[0,"\n"],[1,"RENDERER_HAS_TANGENT",92],[0,"attribute vec4 TANGENT;\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"SKIN_INCLUDED",116],[0,"\n\n"],[7,"SKIN_INCLUDED"],[0,"\n\n"],[1,"RENDERER_HAS_SKIN",114],[0,"\n\n"],[1,"RENDERER_USE_JOINT_TEXTURE",104],[0,"\nuniform sampler2D renderer_JointSampler;\nuniform float renderer_JointCount;\nmat4 getJointMatrix ( sampler2D smp, float index ) { float base = index / renderer_JointCount ;\nfloat hf = 0.5 / renderer_JointCount ;\nfloat v = base + hf ;\nvec4 m0 = texture2D ( smp , vec2 ( 0.125 , v ) ) ;\nvec4 m1 = texture2D ( smp , vec2 ( 0.375 , v ) ) ;\nvec4 m2 = texture2D ( smp , vec2 ( 0.625 , v ) ) ;\nvec4 m3 = texture2D ( smp , vec2 ( 0.875 , v ) ) ;\nreturn mat4 ( m0 , m1 , m2 , m3 ) ; }\n\n"],[5,106],[0,"\nuniform mat4 renderer_JointMatrix [ RENDERER_JOINTS_NUM ];\n\n"],[6],[0,"\nmat4 getSkinMatrix ( ) { \n"],[1,"RENDERER_USE_JOINT_TEXTURE",110],[0," mat4 skinMatrix = WEIGHTS_0.x * getJointMatrix(renderer_JointSampler, JOINTS_0.x) + WEIGHTS_0.y * getJointMatrix(renderer_JointSampler, JOINTS_0.y) + WEIGHTS_0.z * getJointMatrix(renderer_JointSampler, JOINTS_0.z) + WEIGHTS_0.w * getJointMatrix(renderer_JointSampler, JOINTS_0.w) ; \n"],[5,112],[0," mat4 skinMatrix = WEIGHTS_0.x * renderer_JointMatrix[int ( JOINTS_0.x )] + WEIGHTS_0.y * renderer_JointMatrix[int ( JOINTS_0.y )] + WEIGHTS_0.z * renderer_JointMatrix[int ( JOINTS_0.z )] + WEIGHTS_0.w * renderer_JointMatrix[int ( JOINTS_0.w )] ; \n"],[6],[0,"\nreturn skinMatrix ; }\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"BLENDSHAPE_INCLUDED",194],[0,"\n\n"],[7,"BLENDSHAPE_INCLUDED"],[0,"\n\n"],[1,"RENDERER_HAS_BLENDSHAPE",192],[0,"\n\n"],[1,"RENDERER_BLENDSHAPE_USE_TEXTURE",126],[0,"\nuniform mediump sampler2DArray renderer_BlendShapeTexture;\nuniform ivec3 renderer_BlendShapeTextureInfo;\nuniform float renderer_BlendShapeWeights [ RENDERER_BLENDSHAPE_COUNT ];\nvec3 getBlendShapeVertexElement ( int blendShapeIndex, int vertexElementIndex ) { int y = vertexElementIndex / renderer_BlendShapeTextureInfo.y ;\nint x = vertexElementIndex - y * renderer_BlendShapeTextureInfo.y ;\nivec3 uv = ivec3 ( x , y , blendShapeIndex ) ;\nreturn ( texelFetch ( renderer_BlendShapeTexture , uv , 0 ) ).xyz ; }\n\n"],[5,140],[0,"\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},130],[0,"\nuniform float renderer_BlendShapeWeights [ 2 ];\n\n"],[5,138],[0,"\n\n"],[4,{"t":"or","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},134],[0,"\nuniform float renderer_BlendShapeWeights [ 4 ];\n\n"],[5,136],[0,"\nuniform float renderer_BlendShapeWeights [ 8 ];\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\nvoid calculateBlendShape ( inout vec4 position\n"],[1,"RENDERER_HAS_NORMAL",148],[0," , inout vec3 normal \n"],[1,"RENDERER_HAS_TANGENT",146],[0," , inout vec4 tangent \n"],[6],[0," \n"],[6],[0," ) { \n"],[1,"RENDERER_BLENDSHAPE_USE_TEXTURE",160],[0," int vertexOffset = gl_VertexID * renderer_BlendShapeTextureInfo.x ;\nfor ( int i = 0 ; i < RENDERER_BLENDSHAPE_COUNT ; i ++ ) { int vertexElementOffset = vertexOffset ;\nfloat weight = renderer_BlendShapeWeights[i] ;\nif ( weight != 0.0 ) { position.xyz += getBlendShapeVertexElement(i, vertexElementOffset) * weight ;\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"}},154],[0," vertexElementOffset += 1 ;\nnormal += getBlendShapeVertexElement(i, vertexElementOffset) * weight ; \n"],[6],[0,"\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_HAS_TANGENT"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},158],[0," vertexElementOffset += 1 ;\ntangent.xyz += getBlendShapeVertexElement(i, vertexElementOffset) * weight ; \n"],[6],[0," } } \n"],[5,190],[0," position.xyz += POSITION_BS0 * renderer_BlendShapeWeights[0] ;\nposition.xyz += POSITION_BS1 * renderer_BlendShapeWeights[1] ;\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},172],[0," \n"],[1,"RENDERER_HAS_NORMAL",166],[0," normal += NORMAL_BS0 * renderer_BlendShapeWeights[0] ;\nnormal += NORMAL_BS1 * renderer_BlendShapeWeights[1] ; \n"],[6],[0,"\n\n"],[1,"RENDERER_HAS_TANGENT",170],[0," tangent.xyz += TANGENT_BS0 * renderer_BlendShapeWeights[0] ;\ntangent.xyz += TANGENT_BS1 * renderer_BlendShapeWeights[1] ; \n"],[6],[0," \n"],[5,188],[0," \n"],[4,{"t":"or","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},184],[0," position.xyz += POSITION_BS2 * renderer_BlendShapeWeights[2] ;\nposition.xyz += POSITION_BS3 * renderer_BlendShapeWeights[3] ;\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_HAS_NORMAL"}},178],[0," normal += NORMAL_BS0 * renderer_BlendShapeWeights[0] ;\nnormal += NORMAL_BS1 * renderer_BlendShapeWeights[1] ;\nnormal += NORMAL_BS2 * renderer_BlendShapeWeights[2] ;\nnormal += NORMAL_BS3 * renderer_BlendShapeWeights[3] ; \n"],[6],[0,"\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"},"r":{"t":"def","m":"RENDERER_HAS_TANGENT"}},182],[0," tangent.xyz += TANGENT_BS0 * renderer_BlendShapeWeights[0] ;\ntangent.xyz += TANGENT_BS1 * renderer_BlendShapeWeights[1] ;\ntangent.xyz += TANGENT_BS2 * renderer_BlendShapeWeights[2] ;\ntangent.xyz += TANGENT_BS3 * renderer_BlendShapeWeights[3] ; \n"],[6],[0," \n"],[5,186],[0," position.xyz += POSITION_BS2 * renderer_BlendShapeWeights[2] ;\nposition.xyz += POSITION_BS3 * renderer_BlendShapeWeights[3] ;\nposition.xyz += POSITION_BS4 * renderer_BlendShapeWeights[4] ;\nposition.xyz += POSITION_BS5 * renderer_BlendShapeWeights[5] ;\nposition.xyz += POSITION_BS6 * renderer_BlendShapeWeights[6] ;\nposition.xyz += POSITION_BS7 * renderer_BlendShapeWeights[7] ; \n"],[6],[0," \n"],[6],[0," \n"],[6],[0," }\n\n"],[6],[0,"\n\n"],[6],[0,"\nuniform vec2 scene_ShadowBias;\nuniform vec3 scene_LightDirection;\nvarying vec2 v_uv;\n\nvec3 applyShadowBias ( vec3 positionWS ) { positionWS -= scene_LightDirection * scene_ShadowBias.x ;\nreturn positionWS ; }\nvec3 applyShadowNormalBias ( vec3 positionWS, vec3 normalWS ) { float invNdotL = 1.0 - clamp ( dot ( - scene_LightDirection , normalWS ) , 0.0 , 1.0 ) ;\nfloat scale = invNdotL * scene_ShadowBias.y ;\npositionWS += normalWS * vec3 ( scale ) ;\nreturn positionWS ; }\nvoid main() { \nvec4 position = vec4 ( POSITION , 1.0 ) ;\n\n"],[1,"RENDERER_HAS_NORMAL",202],[0," vec3 normal = vec3 ( NORMAL ) ;\n\n"],[1,"RENDERER_HAS_TANGENT",200],[0," vec4 tangent = vec4 ( TANGENT ) ; \n"],[6],[0," \n"],[6],[0,"\n\n"],[1,"RENDERER_HAS_BLENDSHAPE",214],[0," calculateBlendShape(position\n"],[1,"RENDERER_HAS_NORMAL",212],[0," , normal \n"],[1,"RENDERER_HAS_TANGENT",210],[0," , tangent \n"],[6],[0," \n"],[6],[0,") ; \n"],[6],[0,"\n\n"],[1,"RENDERER_HAS_SKIN",222],[0," mat4 skinMatrix = getSkinMatrix() ;\nposition = skinMatrix * position ;\n\n"],[1,"RENDERER_HAS_NORMAL",220],[0," mat3 skinNormalMatrix = INVERSE_MAT(mat3 ( skinMatrix )) ;\nnormal = normal * skinNormalMatrix ; \n"],[6],[0," \n"],[6],[0,"\n\n"],[1,"RENDERER_HAS_UV",226],[0," v_uv = TEXCOORD_0 ; \n"],[5,228],[0," v_uv = vec2 ( 0.0 , 0.0 ) ; \n"],[6],[0,"\nvec4 positionWS = renderer_ModelMat * position ;\npositionWS.xyz = applyShadowBias(positionWS.xyz) ;\n\n"],[1,"RENDERER_HAS_NORMAL",232],[0," vec3 normalWS = normalize ( mat3 ( renderer_NormalMat ) * normal ) ;\npositionWS.xyz = applyShadowNormalBias(positionWS.xyz, normalWS) ; \n"],[6],[0,"\nvec4 positionCS = camera_VPMat * positionWS ;\npositionCS.z = max ( positionCS.z , - 1.0 ) ;\ngl_Position = positionCS ;\n }\n\n"],[1,"ENGINE_NO_DEPTH_TEXTURE",236],[0,"\n\n"],[6]],"fragmentShaderInstructions":[[0,"\n"],[2,"COMMON_INCLUDED",30],[0,"\n\n"],[7,"COMMON_INCLUDED"],[0,"\n\n"],[8,"PI","3.14159265359"],[0,"\n\n"],[8,"RECIPROCAL_PI","0.31830988618"],[0,"\n\n"],[8,"EPSILON","1e-6"],[0,"\n\n"],[8,"LOG2","1.442695"],[0,"\n\n"],[8,"HALF_MIN","6.103515625e-5"],[0,"\n\n"],[8,"HALF_EPS","4.8828125e-4"],[0,"\n\n"],[9,"saturate",["a"],"clamp( a, 0.0, 1.0 )"],[0,"\n\n"],[1,"GRAPHICS_API_WEBGL2",24],[0,"\n\n"],[9,"INVERSE_MAT",["mat"],"inverse(mat)"],[0,"\n\n"],[5,28],[0,"\n\n"],[9,"INVERSE_MAT",["mat"],"inverseMat(mat)"],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"TRANSFORM_INCLUDED",36],[0,"\n\n"],[7,"TRANSFORM_INCLUDED"],[0,"\n\n"],[6],[0,"\n\n"],[2,"ATTRIBUTES_INCLUDED",42],[0,"\n\n"],[7,"ATTRIBUTES_INCLUDED"],[0,"\n\n"],[6],[0,"\n\n"],[2,"SKIN_INCLUDED",58],[0,"\n\n"],[7,"SKIN_INCLUDED"],[0,"\n\n"],[1,"RENDERER_HAS_SKIN",56],[0,"\n\n"],[1,"RENDERER_USE_JOINT_TEXTURE",52],[0,"\n\n"],[5,54],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"BLENDSHAPE_INCLUDED",86],[0,"\n\n"],[7,"BLENDSHAPE_INCLUDED"],[0,"\n\n"],[1,"RENDERER_HAS_BLENDSHAPE",84],[0,"\n\n"],[1,"RENDERER_BLENDSHAPE_USE_TEXTURE",68],[0,"\n\n"],[5,82],[0,"\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},72],[0,"\n\n"],[5,80],[0,"\n\n"],[4,{"t":"or","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},76],[0,"\n\n"],[5,78],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\nuniform vec4 material_BaseColor;\nuniform sampler2D material_BaseTexture;\nuniform float material_AlphaCutoff;\nvarying vec2 v_uv;\n\n\n"],[1,"ENGINE_NO_DEPTH_TEXTURE",90],[0,"\nvec4 pack ( float depth ) { const vec4 bitShift = vec4 ( 1.0 , 256.0 , 256.0 * 256.0 , 256.0 * 256.0 * 256.0 ) ;\nconst vec4 bitMask = vec4 ( 1.0 / 256.0 , 1.0 / 256.0 , 1.0 / 256.0 , 0.0 ) ;\nvec4 rgbaDepth = fract ( depth * bitShift ) ;\nrgbaDepth -= rgbaDepth.gbaa * bitMask ;\nreturn rgbaDepth ; }\n\n"],[6],[0,"\nvoid main() { \n"],[4,{"t":"or","l":{"t":"def","m":"MATERIAL_IS_ALPHA_CUTOFF"},"r":{"t":"and","l":{"t":"def","m":"SCENE_ENABLE_TRANSPARENT_SHADOW"},"r":{"t":"def","m":"MATERIAL_IS_TRANSPARENT"}}},106],[0," float alpha = material_BaseColor.a ;\n\n"],[1,"MATERIAL_HAS_BASETEXTURE",96],[0," alpha *= texture2D ( material_BaseTexture , v_uv ).a ; \n"],[6],[0,"\n\n"],[1,"MATERIAL_IS_ALPHA_CUTOFF",100],[0," if ( alpha < material_AlphaCutoff ) { discard ; } \n"],[6],[0,"\n\n"],[4,{"t":"and","l":{"t":"def","m":"SCENE_ENABLE_TRANSPARENT_SHADOW"},"r":{"t":"def","m":"MATERIAL_IS_TRANSPARENT"}},104],[0," float noise = fract ( 52.982919 * fract ( dot ( vec2 ( 0.06711 , 0.00584 ) , gl_FragCoord.xy ) ) ) ;\nif ( alpha <= noise ) { discard ; } \n"],[6],[0," \n"],[6],[0,"\n\n"],[1,"ENGINE_NO_DEPTH_TEXTURE",110],[0," gl_FragColor = pack(gl_FragCoord.z) ; \n"],[5,112],[0," gl_FragColor = vec4 ( 0.0 , 0.0 , 0.0 , 0.0 ) ; \n"],[6],[0," }"]]}]}]} \ No newline at end of file +{"name":"Pipeline/ShadowCaster","platformTarget":0,"subShaders":[{"name":"Default","tags":{},"passes":[{"name":"ShadowCaster","isUsePass":false,"tags":{"pipelineStage":"ShadowCaster"},"renderStates":{"constantMap":{},"variableMap":{"28":"material_ShadowCasterRenderQueue"}},"vertexShaderInstructions":[[0,"\n"],[2,"COMMON_INCLUDED",30],[0,"\n\n"],[7,"COMMON_INCLUDED"],[0,"\n\n"],[8,"PI","3.14159265359"],[0,"\n\n"],[8,"RECIPROCAL_PI","0.31830988618"],[0,"\n\n"],[8,"EPSILON","1e-6"],[0,"\n\n"],[8,"LOG2","1.442695"],[0,"\n\n"],[8,"HALF_MIN","6.103515625e-5"],[0,"\n\n"],[8,"HALF_EPS","4.8828125e-4"],[0,"\n\n"],[9,"saturate",["a"],"clamp( a, 0.0, 1.0 )"],[0,"\n\n"],[1,"GRAPHICS_API_WEBGL2",24],[0,"\n\n"],[9,"INVERSE_MAT",["mat"],"inverse(mat)"],[0,"\n\n"],[5,28],[0,"\nmat2 inverseMat ( mat2 m ) { return mat2 ( m[1][1] , - m[0][1] , - m[1][0] , m[0][0] ) / ( m[0][0] * m[1][1] - m[0][1] * m[1][0] ) ; }\nmat3 inverseMat ( mat3 m ) { float a00 = m[0][0] , a01 = m[0][1] , a02 = m[0][2] ;\nfloat a10 = m[1][0] , a11 = m[1][1] , a12 = m[1][2] ;\nfloat a20 = m[2][0] , a21 = m[2][1] , a22 = m[2][2] ;\nfloat b01 = a22 * a11 - a12 * a21 ;\nfloat b11 = - a22 * a10 + a12 * a20 ;\nfloat b21 = a21 * a10 - a11 * a20 ;\nfloat det = a00 * b01 + a01 * b11 + a02 * b21 ;\nreturn mat3 ( b01 , ( - a22 * a01 + a02 * a21 ) , ( a12 * a01 - a02 * a11 ) , b11 , ( a22 * a00 - a02 * a20 ) , ( - a12 * a00 + a02 * a10 ) , b21 , ( - a21 * a00 + a01 * a20 ) , ( a11 * a00 - a01 * a10 ) ) / det ; }\nmat4 inverseMat ( mat4 m ) { float a00 = m[0][0] , a01 = m[0][1] , a02 = m[0][2] , a03 = m[0][3] , a10 = m[1][0] , a11 = m[1][1] , a12 = m[1][2] , a13 = m[1][3] , a20 = m[2][0] , a21 = m[2][1] , a22 = m[2][2] , a23 = m[2][3] , a30 = m[3][0] , a31 = m[3][1] , a32 = m[3][2] , a33 = m[3][3] , b00 = a00 * a11 - a01 * a10 , b01 = a00 * a12 - a02 * a10 , b02 = a00 * a13 - a03 * a10 , b03 = a01 * a12 - a02 * a11 , b04 = a01 * a13 - a03 * a11 , b05 = a02 * a13 - a03 * a12 , b06 = a20 * a31 - a21 * a30 , b07 = a20 * a32 - a22 * a30 , b08 = a20 * a33 - a23 * a30 , b09 = a21 * a32 - a22 * a31 , b10 = a21 * a33 - a23 * a31 , b11 = a22 * a33 - a23 * a32 , det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06 ;\nreturn mat4 ( a11 * b11 - a12 * b10 + a13 * b09 , a02 * b10 - a01 * b11 - a03 * b09 , a31 * b05 - a32 * b04 + a33 * b03 , a22 * b04 - a21 * b05 - a23 * b03 , a12 * b08 - a10 * b11 - a13 * b07 , a00 * b11 - a02 * b08 + a03 * b07 , a32 * b02 - a30 * b05 - a33 * b01 , a20 * b05 - a22 * b02 + a23 * b01 , a10 * b10 - a11 * b08 + a13 * b06 , a01 * b08 - a00 * b10 - a03 * b06 , a30 * b04 - a31 * b02 + a33 * b00 , a21 * b02 - a20 * b04 - a23 * b00 , a11 * b07 - a10 * b09 - a12 * b06 , a00 * b09 - a01 * b07 + a02 * b06 , a31 * b01 - a30 * b03 - a32 * b00 , a20 * b03 - a21 * b01 + a22 * b00 ) / det ; }\n\n"],[9,"INVERSE_MAT",["mat"],"inverseMat(mat)"],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"TRANSFORM_INCLUDED",36],[0,"\n\n"],[7,"TRANSFORM_INCLUDED"],[0,"\nuniform mat4 camera_VPMat;\nuniform mat4 renderer_ModelMat;\nuniform mat4 renderer_NormalMat;\n\n"],[6],[0,"\n\n"],[2,"ATTRIBUTES_INCLUDED",94],[0,"\n\n"],[7,"ATTRIBUTES_INCLUDED"],[0,"\nattribute vec3 POSITION;\n\n"],[1,"RENDERER_HAS_BLENDSHAPE",68],[0,"\n"],[2,"RENDERER_BLENDSHAPE_USE_TEXTURE",66],[0,"attribute vec3 POSITION_BS0;\nattribute vec3 POSITION_BS1;\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},48],[0,"attribute vec3 NORMAL_BS0;\nattribute vec3 NORMAL_BS1;\nattribute vec3 TANGENT_BS0;\nattribute vec3 TANGENT_BS1;\n\n"],[5,64],[0,"\n"],[4,{"t":"or","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},60],[0,"attribute vec3 POSITION_BS2;\nattribute vec3 POSITION_BS3;\n\n"],[1,"RENDERER_BLENDSHAPE_HAS_NORMAL",54],[0,"attribute vec3 NORMAL_BS0;\nattribute vec3 NORMAL_BS1;\nattribute vec3 NORMAL_BS2;\nattribute vec3 NORMAL_BS3;\n\n"],[6],[0,"\n"],[1,"RENDERER_BLENDSHAPE_HAS_TANGENT",58],[0,"attribute vec3 TANGENT_BS0;\nattribute vec3 TANGENT_BS1;\nattribute vec3 TANGENT_BS2;\nattribute vec3 TANGENT_BS3;\n\n"],[6],[0,"\n"],[5,62],[0,"attribute vec3 POSITION_BS2;\nattribute vec3 POSITION_BS3;\nattribute vec3 POSITION_BS4;\nattribute vec3 POSITION_BS5;\nattribute vec3 POSITION_BS6;\nattribute vec3 POSITION_BS7;\n\n"],[6],[0,"\n"],[6],[0,"\n"],[6],[0,"\n"],[6],[0,"\n"],[1,"RENDERER_HAS_UV",72],[0,"attribute vec2 TEXCOORD_0;\n\n"],[6],[0,"\n"],[1,"RENDERER_HAS_UV1",76],[0,"\n"],[6],[0,"\n"],[1,"RENDERER_HAS_SKIN",80],[0,"attribute vec4 JOINTS_0;\nattribute vec4 WEIGHTS_0;\n\n"],[6],[0,"\n"],[1,"RENDERER_ENABLE_VERTEXCOLOR",84],[0,"\n"],[6],[0,"\n"],[1,"RENDERER_HAS_NORMAL",88],[0,"attribute vec3 NORMAL;\n\n"],[6],[0,"\n"],[1,"RENDERER_HAS_TANGENT",92],[0,"attribute vec4 TANGENT;\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"SKIN_INCLUDED",116],[0,"\n\n"],[7,"SKIN_INCLUDED"],[0,"\n\n"],[1,"RENDERER_HAS_SKIN",114],[0,"\n\n"],[1,"RENDERER_USE_JOINT_TEXTURE",104],[0,"\nuniform sampler2D renderer_JointSampler;\nuniform float renderer_JointCount;\nmat4 getJointMatrix ( sampler2D smp, float index ) { float base = index / renderer_JointCount ;\nfloat hf = 0.5 / renderer_JointCount ;\nfloat v = base + hf ;\nvec4 m0 = texture2D ( smp , vec2 ( 0.125 , v ) ) ;\nvec4 m1 = texture2D ( smp , vec2 ( 0.375 , v ) ) ;\nvec4 m2 = texture2D ( smp , vec2 ( 0.625 , v ) ) ;\nvec4 m3 = texture2D ( smp , vec2 ( 0.875 , v ) ) ;\nreturn mat4 ( m0 , m1 , m2 , m3 ) ; }\n\n"],[5,106],[0,"\nuniform mat4 renderer_JointMatrix [ RENDERER_JOINTS_NUM ];\n\n"],[6],[0,"\nmat4 getSkinMatrix ( ) { \n"],[1,"RENDERER_USE_JOINT_TEXTURE",110],[0," mat4 skinMatrix = WEIGHTS_0.x * getJointMatrix(renderer_JointSampler, JOINTS_0.x) + WEIGHTS_0.y * getJointMatrix(renderer_JointSampler, JOINTS_0.y) + WEIGHTS_0.z * getJointMatrix(renderer_JointSampler, JOINTS_0.z) + WEIGHTS_0.w * getJointMatrix(renderer_JointSampler, JOINTS_0.w) ; \n"],[5,112],[0," mat4 skinMatrix = WEIGHTS_0.x * renderer_JointMatrix[int ( JOINTS_0.x )] + WEIGHTS_0.y * renderer_JointMatrix[int ( JOINTS_0.y )] + WEIGHTS_0.z * renderer_JointMatrix[int ( JOINTS_0.z )] + WEIGHTS_0.w * renderer_JointMatrix[int ( JOINTS_0.w )] ; \n"],[6],[0,"\nreturn skinMatrix ; }\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"BLENDSHAPE_INCLUDED",194],[0,"\n\n"],[7,"BLENDSHAPE_INCLUDED"],[0,"\n\n"],[1,"RENDERER_HAS_BLENDSHAPE",192],[0,"\n\n"],[1,"RENDERER_BLENDSHAPE_USE_TEXTURE",126],[0,"\nuniform mediump sampler2DArray renderer_BlendShapeTexture;\nuniform ivec3 renderer_BlendShapeTextureInfo;\nuniform float renderer_BlendShapeWeights [ RENDERER_BLENDSHAPE_COUNT ];\nvec3 getBlendShapeVertexElement ( int blendShapeIndex, int vertexElementIndex ) { int y = vertexElementIndex / renderer_BlendShapeTextureInfo.y ;\nint x = vertexElementIndex - y * renderer_BlendShapeTextureInfo.y ;\nivec3 uv = ivec3 ( x , y , blendShapeIndex ) ;\nreturn ( texelFetch ( renderer_BlendShapeTexture , uv , 0 ) ).xyz ; }\n\n"],[5,140],[0,"\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},130],[0,"\nuniform float renderer_BlendShapeWeights [ 2 ];\n\n"],[5,138],[0,"\n\n"],[4,{"t":"or","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},134],[0,"\nuniform float renderer_BlendShapeWeights [ 4 ];\n\n"],[5,136],[0,"\nuniform float renderer_BlendShapeWeights [ 8 ];\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\nvoid calculateBlendShape ( inout vec4 position\n"],[1,"RENDERER_HAS_NORMAL",148],[0," , inout vec3 normal \n"],[1,"RENDERER_HAS_TANGENT",146],[0," , inout vec4 tangent \n"],[6],[0," \n"],[6],[0," ) { \n"],[1,"RENDERER_BLENDSHAPE_USE_TEXTURE",160],[0," int vertexOffset = gl_VertexID * renderer_BlendShapeTextureInfo.x ;\nfor ( int i = 0 ; i < RENDERER_BLENDSHAPE_COUNT ; i ++ ) { int vertexElementOffset = vertexOffset ;\nfloat weight = renderer_BlendShapeWeights[i] ;\nif ( weight != 0.0 ) { position.xyz += getBlendShapeVertexElement(i, vertexElementOffset) * weight ;\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"}},154],[0," vertexElementOffset += 1 ;\nnormal += getBlendShapeVertexElement(i, vertexElementOffset) * weight ; \n"],[6],[0,"\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_HAS_TANGENT"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},158],[0," vertexElementOffset += 1 ;\ntangent.xyz += getBlendShapeVertexElement(i, vertexElementOffset) * weight ; \n"],[6],[0," } } \n"],[5,190],[0," position.xyz += POSITION_BS0 * renderer_BlendShapeWeights[0] ;\nposition.xyz += POSITION_BS1 * renderer_BlendShapeWeights[1] ;\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},172],[0," \n"],[1,"RENDERER_HAS_NORMAL",166],[0," normal += NORMAL_BS0 * renderer_BlendShapeWeights[0] ;\nnormal += NORMAL_BS1 * renderer_BlendShapeWeights[1] ; \n"],[6],[0,"\n\n"],[1,"RENDERER_HAS_TANGENT",170],[0," tangent.xyz += TANGENT_BS0 * renderer_BlendShapeWeights[0] ;\ntangent.xyz += TANGENT_BS1 * renderer_BlendShapeWeights[1] ; \n"],[6],[0," \n"],[5,188],[0," \n"],[4,{"t":"or","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},184],[0," position.xyz += POSITION_BS2 * renderer_BlendShapeWeights[2] ;\nposition.xyz += POSITION_BS3 * renderer_BlendShapeWeights[3] ;\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_HAS_NORMAL"}},178],[0," normal += NORMAL_BS0 * renderer_BlendShapeWeights[0] ;\nnormal += NORMAL_BS1 * renderer_BlendShapeWeights[1] ;\nnormal += NORMAL_BS2 * renderer_BlendShapeWeights[2] ;\nnormal += NORMAL_BS3 * renderer_BlendShapeWeights[3] ; \n"],[6],[0,"\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"},"r":{"t":"def","m":"RENDERER_HAS_TANGENT"}},182],[0," tangent.xyz += TANGENT_BS0 * renderer_BlendShapeWeights[0] ;\ntangent.xyz += TANGENT_BS1 * renderer_BlendShapeWeights[1] ;\ntangent.xyz += TANGENT_BS2 * renderer_BlendShapeWeights[2] ;\ntangent.xyz += TANGENT_BS3 * renderer_BlendShapeWeights[3] ; \n"],[6],[0," \n"],[5,186],[0," position.xyz += POSITION_BS2 * renderer_BlendShapeWeights[2] ;\nposition.xyz += POSITION_BS3 * renderer_BlendShapeWeights[3] ;\nposition.xyz += POSITION_BS4 * renderer_BlendShapeWeights[4] ;\nposition.xyz += POSITION_BS5 * renderer_BlendShapeWeights[5] ;\nposition.xyz += POSITION_BS6 * renderer_BlendShapeWeights[6] ;\nposition.xyz += POSITION_BS7 * renderer_BlendShapeWeights[7] ; \n"],[6],[0," \n"],[6],[0," \n"],[6],[0," }\n\n"],[6],[0,"\n\n"],[6],[0,"\nuniform vec2 scene_ShadowBias;\nuniform vec3 scene_LightDirection;\nvarying vec2 v_uv;\n\nvec3 applyShadowBias ( vec3 positionWS ) { positionWS -= scene_LightDirection * scene_ShadowBias.x ;\nreturn positionWS ; }\nvec3 applyShadowNormalBias ( vec3 positionWS, vec3 normalWS ) { float invNdotL = 1.0 - clamp ( dot ( - scene_LightDirection , normalWS ) , 0.0 , 1.0 ) ;\nfloat scale = invNdotL * scene_ShadowBias.y ;\npositionWS += normalWS * vec3 ( scale ) ;\nreturn positionWS ; }\nvoid main() { \nvec4 position = vec4 ( POSITION , 1.0 ) ;\n\n"],[1,"RENDERER_HAS_NORMAL",202],[0," vec3 normal = vec3 ( NORMAL ) ;\n\n"],[1,"RENDERER_HAS_TANGENT",200],[0," vec4 tangent = vec4 ( TANGENT ) ; \n"],[6],[0," \n"],[6],[0,"\n\n"],[1,"RENDERER_HAS_BLENDSHAPE",214],[0," calculateBlendShape(position\n"],[1,"RENDERER_HAS_NORMAL",212],[0," , normal \n"],[1,"RENDERER_HAS_TANGENT",210],[0," , tangent \n"],[6],[0," \n"],[6],[0,") ; \n"],[6],[0,"\n\n"],[1,"RENDERER_HAS_SKIN",222],[0," mat4 skinMatrix = getSkinMatrix() ;\nposition = skinMatrix * position ;\n\n"],[1,"RENDERER_HAS_NORMAL",220],[0," mat3 skinNormalMatrix = INVERSE_MAT(mat3 ( skinMatrix )) ;\nnormal = normal * skinNormalMatrix ; \n"],[6],[0," \n"],[6],[0,"\n\n"],[1,"RENDERER_HAS_UV",226],[0," v_uv = TEXCOORD_0 ; \n"],[5,228],[0," v_uv = vec2 ( 0.0 , 0.0 ) ; \n"],[6],[0,"\nvec4 positionWS = renderer_ModelMat * position ;\npositionWS.xyz = applyShadowBias(positionWS.xyz) ;\n\n"],[1,"RENDERER_HAS_NORMAL",232],[0," vec3 normalWS = normalize ( mat3 ( renderer_NormalMat ) * normal ) ;\npositionWS.xyz = applyShadowNormalBias(positionWS.xyz, normalWS) ; \n"],[6],[0,"\nvec4 positionCS = camera_VPMat * positionWS ;\npositionCS.z = max ( positionCS.z , - 1.0 ) ;\ngl_Position = positionCS ;\n }\n\n"],[1,"ENGINE_NO_DEPTH_TEXTURE",236],[0,"\n\n"],[6]],"fragmentShaderInstructions":[[0,"\n"],[2,"COMMON_INCLUDED",30],[0,"\n\n"],[7,"COMMON_INCLUDED"],[0,"\n\n"],[8,"PI","3.14159265359"],[0,"\n\n"],[8,"RECIPROCAL_PI","0.31830988618"],[0,"\n\n"],[8,"EPSILON","1e-6"],[0,"\n\n"],[8,"LOG2","1.442695"],[0,"\n\n"],[8,"HALF_MIN","6.103515625e-5"],[0,"\n\n"],[8,"HALF_EPS","4.8828125e-4"],[0,"\n\n"],[9,"saturate",["a"],"clamp( a, 0.0, 1.0 )"],[0,"\n\n"],[1,"GRAPHICS_API_WEBGL2",24],[0,"\n\n"],[9,"INVERSE_MAT",["mat"],"inverse(mat)"],[0,"\n\n"],[5,28],[0,"\n\n"],[9,"INVERSE_MAT",["mat"],"inverseMat(mat)"],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"TRANSFORM_INCLUDED",36],[0,"\n\n"],[7,"TRANSFORM_INCLUDED"],[0,"\n\n"],[6],[0,"\n\n"],[2,"ATTRIBUTES_INCLUDED",42],[0,"\n\n"],[7,"ATTRIBUTES_INCLUDED"],[0,"\n\n"],[6],[0,"\n\n"],[2,"SKIN_INCLUDED",58],[0,"\n\n"],[7,"SKIN_INCLUDED"],[0,"\n\n"],[1,"RENDERER_HAS_SKIN",56],[0,"\n\n"],[1,"RENDERER_USE_JOINT_TEXTURE",52],[0,"\n\n"],[5,54],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[2,"BLENDSHAPE_INCLUDED",86],[0,"\n\n"],[7,"BLENDSHAPE_INCLUDED"],[0,"\n\n"],[1,"RENDERER_HAS_BLENDSHAPE",84],[0,"\n\n"],[1,"RENDERER_BLENDSHAPE_USE_TEXTURE",68],[0,"\n\n"],[5,82],[0,"\n\n"],[4,{"t":"and","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},72],[0,"\n\n"],[5,80],[0,"\n\n"],[4,{"t":"or","l":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_NORMAL"},"r":{"t":"def","m":"RENDERER_BLENDSHAPE_HAS_TANGENT"}},76],[0,"\n\n"],[5,78],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\n\n"],[6],[0,"\nuniform vec4 material_BaseColor;\nuniform sampler2D material_BaseTexture;\nuniform float material_AlphaCutoff;\nvarying vec2 v_uv;\n\n\n"],[1,"ENGINE_NO_DEPTH_TEXTURE",90],[0,"\nvec4 pack ( float depth ) { const vec4 bitShift = vec4 ( 1.0 , 256.0 , 256.0 * 256.0 , 256.0 * 256.0 * 256.0 ) ;\nconst vec4 bitMask = vec4 ( 1.0 / 256.0 , 1.0 / 256.0 , 1.0 / 256.0 , 0.0 ) ;\nvec4 rgbaDepth = fract ( depth * bitShift ) ;\nrgbaDepth -= rgbaDepth.gbaa * bitMask ;\nreturn rgbaDepth ; }\n\n"],[6],[0,"\nvoid main() { \n"],[4,{"t":"or","l":{"t":"def","m":"MATERIAL_IS_ALPHA_CUTOFF"},"r":{"t":"and","l":{"t":"def","m":"SCENE_ENABLE_TRANSPARENT_SHADOW"},"r":{"t":"def","m":"MATERIAL_IS_TRANSPARENT"}}},106],[0," float alpha = material_BaseColor.a ;\n\n"],[1,"MATERIAL_HAS_BASETEXTURE",96],[0," alpha *= texture2D ( material_BaseTexture , v_uv ).a ; \n"],[6],[0,"\n\n"],[1,"MATERIAL_IS_ALPHA_CUTOFF",100],[0," if ( alpha < material_AlphaCutoff ) { discard ; } \n"],[6],[0,"\n\n"],[4,{"t":"and","l":{"t":"def","m":"SCENE_ENABLE_TRANSPARENT_SHADOW"},"r":{"t":"def","m":"MATERIAL_IS_TRANSPARENT"}},104],[0," float noise = fract ( 52.982919 * fract ( dot ( vec2 ( 0.06711 , 0.00584 ) , gl_FragCoord.xy ) ) ) ;\nif ( alpha <= noise ) { discard ; } \n"],[6],[0," \n"],[6],[0,"\n\n"],[1,"ENGINE_NO_DEPTH_TEXTURE",110],[0," gl_FragColor = pack(gl_FragCoord.z) ; \n"],[5,112],[0," gl_FragColor = vec4 ( 0.0 , 0.0 , 0.0 , 0.0 ) ; \n"],[6],[0," }"]]}]}]} \ No newline at end of file diff --git a/packages/shader/src/ShaderLibrary/Common/Transform.glsl b/packages/shader/src/ShaderLibrary/Common/Transform.glsl index 1af76a0f0e..1c829ed32b 100644 --- a/packages/shader/src/ShaderLibrary/Common/Transform.glsl +++ b/packages/shader/src/ShaderLibrary/Common/Transform.glsl @@ -7,7 +7,6 @@ mat4 camera_VPMat; vec3 camera_Position; vec3 camera_Forward; -vec4 camera_ProjectionParams; mat4 renderer_ModelMat; mat4 renderer_MVMat; From 086dff0164f25af5c7211dd19a9f1e035eda1e1a Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Mon, 11 May 2026 23:10:50 +0800 Subject: [PATCH 79/91] fix(shader): declare camera matrices when instance UBO rewrites derived defines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `injectInstanceUBO` rewrites `renderer_MVMat` / `renderer_MVPMat` to `(camera_ViewMat * renderer_ModelMat)` / `(camera_VPMat * renderer_ModelMat)`, introducing references the shader source itself may not have. shader-compiler performs DCE on Transform.glsl declarations, so any vertex shader that only reads `renderer_MVPMat` as a plain uniform ends up without `camera_VPMat` visible to the rewritten GLSL — WebGL then fails to compile with `'camera_VPMat' : undeclared identifier`. Make the injector self-contained: scan the post-evaluate GLSL and emit `uniform mat4 camera_*` declarations only for matrices not already present. Sources that explicitly include them (e.g. PBR fragment using camera_ViewMat for refraction) keep their single declaration intact. --- packages/core/src/shader/ShaderFactory.ts | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/packages/core/src/shader/ShaderFactory.ts b/packages/core/src/shader/ShaderFactory.ts index 2383fe90d8..68425cb8e6 100644 --- a/packages/core/src/shader/ShaderFactory.ts +++ b/packages/core/src/shader/ShaderFactory.ts @@ -49,6 +49,10 @@ export class ShaderFactory { // [layout(location = 0)] out [highp] vec4 [color]; private static readonly _has300OutInFragReg = /\bout\s+(?:\w+\s+)?vec4\s+\w+\s*;/; + // Camera matrices the derived defines reference; declared on demand because + // shader-compiler DCE may have stripped them. + private static readonly _cameraMatrixCandidates: ReadonlyArray = ["camera_ViewMat", "camera_VPMat"]; + private static readonly _derivedDefines = `\ #define renderer_MVMat (camera_ViewMat * renderer_ModelMat) #define renderer_MVPMat (camera_VPMat * renderer_ModelMat) @@ -217,9 +221,11 @@ export class ShaderFactory { const fieldDefinesVS = ShaderFactory._buildFieldDefines(instanceFields, "gl_InstanceID"); const fieldDefinesFS = ShaderFactory._buildFieldDefines(instanceFields, "v_instanceID"); const derivedDefines = ShaderFactory._derivedDefines; + const vsCameraDecls = ShaderFactory._buildMissingCameraDecls(vertexSource); + const fsCameraDecls = ShaderFactory._buildMissingCameraDecls(fragmentSource); - const vsBlock = `${uboDecl}flat out int v_instanceID;\n${fieldDefinesVS}\n${derivedDefines}\n`; - const fsBlock = `${uboDecl}flat in int v_instanceID;\n${fieldDefinesFS}\n${derivedDefines}\n`; + const vsBlock = `${uboDecl}flat out int v_instanceID;\n${vsCameraDecls}${fieldDefinesVS}\n${derivedDefines}\n`; + const fsBlock = `${uboDecl}flat in int v_instanceID;\n${fsCameraDecls}${fieldDefinesFS}\n${derivedDefines}\n`; vertexSource = vsBlock + vertexSource; vertexSource = vertexSource.replace( @@ -339,6 +345,19 @@ export class ShaderFactory { return { instanceFields, instanceMaxCount, structSize }; } + private static _buildMissingCameraDecls(source: string): string { + let out = ""; + const candidates = ShaderFactory._cameraMatrixCandidates; + for (let i = 0; i < candidates.length; i++) { + const name = candidates[i]; + const decl = new RegExp(`^\\s*uniform\\s+(?:(?:lowp|mediump|highp)\\s+)?mat4\\s+${name}\\s*;`, "m"); + if (!decl.test(source)) { + out += `uniform mat4 ${name};\n`; + } + } + return out; + } + private static _buildUBODeclaration(layout: InstanceBufferLayout): string { const { instanceFields, instanceMaxCount } = layout; const structLines: string[] = []; From a87acc8289eef5c0286fe22338d201e0867f18c7 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Mon, 11 May 2026 23:11:15 +0800 Subject: [PATCH 80/91] test(e2e,examples): rewrite gpu-instancing cases as ShaderLab MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 3-arg `Shader.create(name, vertexSource, fragmentSource)` overload no longer exists after PR #2961 — passing GLSL strings drops into the SubShader branch and crashes with `Cannot read properties of undefined (reading 'length')` from BasicRenderPipeline. Convert the two custom-instancing cases to ShaderLab syntax with an explicit `ShaderCompiler` wired through `WebGLEngine.create`. Also pick up the upstream LFS baselines (particle/physx jpgs) — the merge left their pointers at our pre-merge oid even though the working tree had been resolved to upstream. --- e2e/case/gpu-instancing-custom-data.ts | 60 +++++++++------ ...ticle_particleRenderer-shape-transform.jpg | 4 +- .../originImage/Physics_physx-collision.jpg | 4 +- .../originImage/Physics_physx-customUrl.jpg | 4 +- examples/src/gpu-instancing-auto-batch.ts | 74 +++++++++++-------- examples/src/gpu-instancing-custom-data.ts | 74 +++++++++++-------- 6 files changed, 126 insertions(+), 94 deletions(-) diff --git a/e2e/case/gpu-instancing-custom-data.ts b/e2e/case/gpu-instancing-custom-data.ts index f64b01a8f7..1c6f1755dd 100644 --- a/e2e/case/gpu-instancing-custom-data.ts +++ b/e2e/case/gpu-instancing-custom-data.ts @@ -16,39 +16,51 @@ import { Vector4, WebGLEngine } from "@galacean/engine"; +import { ShaderCompiler } from "@galacean/engine-shader-compiler"; import { initScreenshot, updateForE2E } from "./.mockForE2E"; Logger.enable(); +const shaderCompiler = new ShaderCompiler(); + // Custom shader: uses renderer_CustomColor (per-instance) for fragment output -Shader.create( - "CustomInstanceShader", - ` - #include - attribute vec3 POSITION; - attribute vec3 NORMAL; - - varying vec3 v_normal; - - void main() { - gl_Position = renderer_MVPMat * vec4(POSITION, 1.0); - v_normal = normalize((renderer_NormalMat * vec4(NORMAL, 0.0)).xyz); - } - `, - ` - uniform vec4 renderer_CustomColor; +const customInstanceShaderSource = `Shader "CustomInstanceShader" { + SubShader "Default" { + Pass "Forward" { + struct Attributes { + vec3 POSITION; + vec3 NORMAL; + }; + + struct Varyings { + vec3 v_normal; + }; + + mat4 renderer_MVPMat; + mat4 renderer_NormalMat; + vec4 renderer_CustomColor; + + VertexShader = vert; + FragmentShader = frag; - varying vec3 v_normal; + Varyings vert(Attributes attr) { + Varyings v; + gl_Position = renderer_MVPMat * vec4(attr.POSITION, 1.0); + v.v_normal = normalize((renderer_NormalMat * vec4(attr.NORMAL, 0.0)).xyz); + return v; + } - void main() { - vec3 lightDir = normalize(vec3(1.0, 1.0, 1.0)); - float NdotL = max(dot(v_normal, lightDir), 0.2); - gl_FragColor = vec4(renderer_CustomColor.rgb * NdotL, 1.0); + vec4 frag(Varyings v) { + vec3 lightDir = normalize(vec3(1.0, 1.0, 1.0)); + float NdotL = max(dot(v.v_normal, lightDir), 0.2); + return vec4(renderer_CustomColor.rgb * NdotL, 1.0); + } + } } - ` -); +}`; -WebGLEngine.create({ canvas: "canvas" }).then((engine) => { +WebGLEngine.create({ canvas: "canvas", shaderCompiler }).then((engine) => { + Shader.create(customInstanceShaderSource); engine.canvas.resizeByClientSize(2); const scene = engine.sceneManager.activeScene; diff --git a/e2e/fixtures/originImage/Particle_particleRenderer-shape-transform.jpg b/e2e/fixtures/originImage/Particle_particleRenderer-shape-transform.jpg index 64e2946e1d..49c379109b 100644 --- a/e2e/fixtures/originImage/Particle_particleRenderer-shape-transform.jpg +++ b/e2e/fixtures/originImage/Particle_particleRenderer-shape-transform.jpg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9c71ce04939b22f17b03033d4b3601e0170b57bb7f3a6ac6887b42237be975e8 -size 22930 +oid sha256:b9fff6f4b67bf5a05150c37b5e39b5057e8557a682e6e3eae3829b3573a4ac70 +size 23076 diff --git a/e2e/fixtures/originImage/Physics_physx-collision.jpg b/e2e/fixtures/originImage/Physics_physx-collision.jpg index 147cd98364..5716d22a3f 100644 --- a/e2e/fixtures/originImage/Physics_physx-collision.jpg +++ b/e2e/fixtures/originImage/Physics_physx-collision.jpg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:18a160d400ffc84bfdcd75b221255a3bbea5522e8a45c868a039424544af8a8b -size 39815 +oid sha256:2426a2b92ad4d62b28f5c54c255c6e072d7b02e4b79eff839ea8f75f16c34061 +size 36011 diff --git a/e2e/fixtures/originImage/Physics_physx-customUrl.jpg b/e2e/fixtures/originImage/Physics_physx-customUrl.jpg index 147cd98364..5716d22a3f 100644 --- a/e2e/fixtures/originImage/Physics_physx-customUrl.jpg +++ b/e2e/fixtures/originImage/Physics_physx-customUrl.jpg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:18a160d400ffc84bfdcd75b221255a3bbea5522e8a45c868a039424544af8a8b -size 39815 +oid sha256:2426a2b92ad4d62b28f5c54c255c6e072d7b02e4b79eff839ea8f75f16c34061 +size 36011 diff --git a/examples/src/gpu-instancing-auto-batch.ts b/examples/src/gpu-instancing-auto-batch.ts index d7aa1b9f80..e82a863af2 100644 --- a/examples/src/gpu-instancing-auto-batch.ts +++ b/examples/src/gpu-instancing-auto-batch.ts @@ -3,7 +3,6 @@ * @category Mesh * @thumbnail https://mdn.alipayobjects.com/merchant_appfe/afts/img/A*jjZMTrp-vU8AAAAAAAAAAAAADiR2AQ/original */ -import { OrbitControl, Stats } from "@galacean/engine-toolkit"; import { AmbientLight, AssetType, @@ -23,7 +22,9 @@ import { WebGLEngine, WebGLMode } from "@galacean/engine"; +import { ShaderCompiler } from "@galacean/engine-shader-compiler"; +const shaderCompiler = new ShaderCompiler(); const _customColorProperty = ShaderProperty.getByName("renderer_CustomColor"); class SpiralAnimate extends Script { @@ -82,34 +83,47 @@ class SpiralAnimate extends Script { } // Custom shader for cubes -Shader.create( - "CustomInstanceShader", - ` - #include - attribute vec3 POSITION; - attribute vec3 NORMAL; - - varying vec3 v_normal; - - void main() { - gl_Position = renderer_MVPMat * vec4(POSITION, 1.0); - v_normal = normalize((renderer_NormalMat * vec4(NORMAL, 0.0)).xyz); - } - `, - ` - uniform vec4 renderer_CustomColor; - - varying vec3 v_normal; - - void main() { - vec3 lightDir = normalize(vec3(1.0, 1.0, 1.0)); - float NdotL = max(dot(v_normal, lightDir), 0.2); - gl_FragColor = vec4(renderer_CustomColor.rgb * NdotL, 1.0); +const customInstanceShaderSource = `Shader "CustomInstanceShader" { + SubShader "Default" { + Pass "Forward" { + struct Attributes { + vec3 POSITION; + vec3 NORMAL; + }; + + struct Varyings { + vec3 v_normal; + }; + + mat4 renderer_MVPMat; + mat4 renderer_NormalMat; + vec4 renderer_CustomColor; + + VertexShader = vert; + FragmentShader = frag; + + Varyings vert(Attributes attr) { + Varyings v; + gl_Position = renderer_MVPMat * vec4(attr.POSITION, 1.0); + v.v_normal = normalize((renderer_NormalMat * vec4(attr.NORMAL, 0.0)).xyz); + return v; + } + + vec4 frag(Varyings v) { + vec3 lightDir = normalize(vec3(1.0, 1.0, 1.0)); + float NdotL = max(dot(v.v_normal, lightDir), 0.2); + return vec4(renderer_CustomColor.rgb * NdotL, 1.0); + } + } } - ` -); - -WebGLEngine.create({ canvas: "canvas", graphicDeviceOptions: { webGLMode: WebGLMode.WebGL2 } }).then(async (engine) => { +}`; + +WebGLEngine.create({ + canvas: "canvas", + graphicDeviceOptions: { webGLMode: WebGLMode.WebGL2 }, + shaderCompiler +}).then(async (engine) => { + Shader.create(customInstanceShaderSource); engine.canvas.resizeByClientSize(); const scene = engine.sceneManager.activeScene; @@ -121,10 +135,6 @@ WebGLEngine.create({ canvas: "canvas", graphicDeviceOptions: { webGLMode: WebGLM cameraEntity.transform.lookAt(new Vector3(0, 0, 0)); const camera = cameraEntity.addComponent(Camera); camera.farClipPlane = 500; - cameraEntity.addComponent(OrbitControl); - - // Stats - cameraEntity.addComponent(Stats); // Light const lightEntity = rootEntity.createChild("Light"); diff --git a/examples/src/gpu-instancing-custom-data.ts b/examples/src/gpu-instancing-custom-data.ts index 27857d5730..9b4150b232 100644 --- a/examples/src/gpu-instancing-custom-data.ts +++ b/examples/src/gpu-instancing-custom-data.ts @@ -3,7 +3,6 @@ * @category Mesh * @thumbnail https://mdn.alipayobjects.com/merchant_appfe/afts/img/A*jjZMTrp-vU8AAAAAAAAAAAAADiR2AQ/original */ -import { OrbitControl, Stats } from "@galacean/engine-toolkit"; import { Camera, Color, @@ -20,7 +19,9 @@ import { WebGLEngine, WebGLMode } from "@galacean/engine"; +import { ShaderCompiler } from "@galacean/engine-shader-compiler"; +const shaderCompiler = new ShaderCompiler(); const _customColorProperty = ShaderProperty.getByName("renderer_CustomColor"); class SpiralFlash extends Script { @@ -68,34 +69,47 @@ class SpiralFlash extends Script { Logger.enable(); -Shader.create( - "CustomInstanceShader", - ` - #include - attribute vec3 POSITION; - attribute vec3 NORMAL; - - varying vec3 v_normal; - - void main() { - gl_Position = renderer_MVPMat * vec4(POSITION, 1.0); - v_normal = normalize((renderer_NormalMat * vec4(NORMAL, 0.0)).xyz); +const customInstanceShaderSource = `Shader "CustomInstanceShader" { + SubShader "Default" { + Pass "Forward" { + struct Attributes { + vec3 POSITION; + vec3 NORMAL; + }; + + struct Varyings { + vec3 v_normal; + }; + + mat4 renderer_MVPMat; + mat4 renderer_NormalMat; + vec4 renderer_CustomColor; + + VertexShader = vert; + FragmentShader = frag; + + Varyings vert(Attributes attr) { + Varyings v; + gl_Position = renderer_MVPMat * vec4(attr.POSITION, 1.0); + v.v_normal = normalize((renderer_NormalMat * vec4(attr.NORMAL, 0.0)).xyz); + return v; + } + + vec4 frag(Varyings v) { + vec3 lightDir = normalize(vec3(1.0, 1.0, 1.0)); + float NdotL = max(dot(v.v_normal, lightDir), 0.2); + return vec4(renderer_CustomColor.rgb * NdotL, 1.0); + } + } } - `, - ` - uniform vec4 renderer_CustomColor; - - varying vec3 v_normal; - - void main() { - vec3 lightDir = normalize(vec3(1.0, 1.0, 1.0)); - float NdotL = max(dot(v_normal, lightDir), 0.2); - gl_FragColor = vec4(renderer_CustomColor.rgb * NdotL, 1.0); - } - ` -); - -WebGLEngine.create({ canvas: "canvas", graphicDeviceOptions: { webGLMode: WebGLMode.WebGL2 } }).then((engine) => { +}`; + +WebGLEngine.create({ + canvas: "canvas", + graphicDeviceOptions: { webGLMode: WebGLMode.WebGL2 }, + shaderCompiler +}).then((engine) => { + Shader.create(customInstanceShaderSource); engine.canvas.resizeByClientSize(); const scene = engine.sceneManager.activeScene; @@ -107,10 +121,6 @@ WebGLEngine.create({ canvas: "canvas", graphicDeviceOptions: { webGLMode: WebGLM cameraEntity.transform.lookAt(new Vector3(0, 0, 0)); const camera = cameraEntity.addComponent(Camera); camera.farClipPlane = 500; - cameraEntity.addComponent(OrbitControl); - - // Stats - cameraEntity.addComponent(Stats); // Light const lightEntity = rootEntity.createChild("Light"); From 50f9281355d157f473eb2e8f538c1e591993db5f Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Tue, 12 May 2026 00:29:23 +0800 Subject: [PATCH 81/91] fix(shader): force-inject renderer_ModelMat into instance UBO and declare camera matrices on demand MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a shader enables RENDERER_GPU_INSTANCE but never declares `renderer_ModelMat` itself (e.g. it only references `renderer_MVPMat`), the derived defines we inject — which expand to expressions using `renderer_ModelMat` and `camera_ViewMat` / `camera_VPMat` — would resolve to undeclared identifiers under WebGL. Two complementary fixes: - `_buildLayout` force-injects `renderer_ModelMat` (as mat3x4 affine pack) into the UBO whenever it's missing from `fieldMap`. Layout ordering is now an explicit priority skip rather than the `addField + delete` mutation pattern, which read like "add then delete" at a glance. - `_buildMissingCameraDecls` scans the post-evaluate GLSL for an existing `uniform mat4 camera_*;` declaration and emits one only when shader-compiler DCE stripped it from Transform.glsl. Sources that legitimately pulled the camera matrix in (e.g. PBR fragment using camera_ViewMat for refraction) keep their single declaration. --- packages/core/src/shader/ShaderFactory.ts | 30 +++++++++++++---------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/packages/core/src/shader/ShaderFactory.ts b/packages/core/src/shader/ShaderFactory.ts index 68425cb8e6..144b4e888e 100644 --- a/packages/core/src/shader/ShaderFactory.ts +++ b/packages/core/src/shader/ShaderFactory.ts @@ -49,16 +49,13 @@ export class ShaderFactory { // [layout(location = 0)] out [highp] vec4 [color]; private static readonly _has300OutInFragReg = /\bout\s+(?:\w+\s+)?vec4\s+\w+\s*;/; - // Camera matrices the derived defines reference; declared on demand because - // shader-compiler DCE may have stripped them. - private static readonly _cameraMatrixCandidates: ReadonlyArray = ["camera_ViewMat", "camera_VPMat"]; - private static readonly _derivedDefines = `\ #define renderer_MVMat (camera_ViewMat * renderer_ModelMat) #define renderer_MVPMat (camera_VPMat * renderer_ModelMat) #define renderer_NormalMat mat4(transpose(inverse(mat3(renderer_ModelMat))))`; // Built-in renderer uniforms. value=true means derived (remove but not added to UBO) + // NOTE: keep this in sync with _derivedDefines / _cameraMatrixCandidates above. private static readonly _builtinRendererUniforms: Record = { renderer_ModelMat: false, renderer_Layer: false, @@ -67,6 +64,11 @@ export class ShaderFactory { renderer_NormalMat: true }; + // Camera matrices the derived defines reference; declared on demand because + // shader-compiler DCE may have stripped them from Transform.glsl. + // NOTE: keep this in sync with _derivedDefines above. + private static readonly _cameraMatrixCandidates: ReadonlyArray = ["camera_ViewMat", "camera_VPMat"]; + private static readonly _uboUniformRegex = /^[ \t]*uniform\s+(?:(?:lowp|mediump|highp)\s+)?(\w+)\s+(\w+)\s*(\[.+?\])?\s*;/gm; @@ -249,6 +251,7 @@ export class ShaderFactory { Logger.error(`GPU Instancing does not support array uniform "${name}${arraySize}"`); return match; } + // ModelMat is affine, store as mat3x4 (3 columns) to save 16 bytes per instance fieldMap[ShaderProperty.getByName(name)._uniqueId] = type === "mat4" && name === "renderer_ModelMat" ? "mat3x4" : type; return ""; @@ -323,19 +326,20 @@ export class ShaderFactory { currentOffset += info.size; }; + // renderer_ModelMat is always required: derived defines reference it, so + // even shaders that never declared the plain uniform need it in the UBO. const modelMatId = Renderer._worldMatrixProperty._uniqueId; const layerId = Renderer._rendererLayerProperty._uniqueId; - if (modelMatId in fieldMap) { - addField(modelMatId); - delete fieldMap[modelMatId]; - } - if (layerId in fieldMap) { - addField(layerId); - delete fieldMap[layerId]; - } + if (!(modelMatId in fieldMap)) fieldMap[modelMatId] = "mat3x4"; + // Priority order: ModelMat first, Layer second, rest by property id. + addField(modelMatId); + if (layerId in fieldMap) addField(layerId); const keys: number[] = []; - for (const k in fieldMap) keys.push(+k); + for (const k in fieldMap) { + const id = +k; + if (id !== modelMatId && id !== layerId) keys.push(id); + } keys.sort((a, b) => a - b); for (let i = 0; i < keys.length; i++) addField(keys[i]); From 41bca944524a1ca96bf6ea69837176d7bc7cdebe Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Tue, 12 May 2026 00:29:51 +0800 Subject: [PATCH 82/91] chore(examples): wire Shader.create after WebGLEngine.create and add Stats Calling Shader.create at module scope throws because the shader compiler is only installed on Shader._shaderCompiler during engine init. Move the call into the engine-create then callback. Re-add the Stats overlay from @galacean/engine-toolkit-stats for dev observability. --- examples/src/gpu-instancing-auto-batch.ts | 3 +++ examples/src/gpu-instancing-custom-data.ts | 2 ++ pnpm-lock.yaml | 4 ++-- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/examples/src/gpu-instancing-auto-batch.ts b/examples/src/gpu-instancing-auto-batch.ts index e82a863af2..3bf57fc429 100644 --- a/examples/src/gpu-instancing-auto-batch.ts +++ b/examples/src/gpu-instancing-auto-batch.ts @@ -3,6 +3,7 @@ * @category Mesh * @thumbnail https://mdn.alipayobjects.com/merchant_appfe/afts/img/A*jjZMTrp-vU8AAAAAAAAAAAAADiR2AQ/original */ +import { Stats } from "@galacean/engine-toolkit-stats"; import { AmbientLight, AssetType, @@ -118,6 +119,7 @@ const customInstanceShaderSource = `Shader "CustomInstanceShader" { } }`; +Logger.enable(); WebGLEngine.create({ canvas: "canvas", graphicDeviceOptions: { webGLMode: WebGLMode.WebGL2 }, @@ -135,6 +137,7 @@ WebGLEngine.create({ cameraEntity.transform.lookAt(new Vector3(0, 0, 0)); const camera = cameraEntity.addComponent(Camera); camera.farClipPlane = 500; + cameraEntity.addComponent(Stats); // Light const lightEntity = rootEntity.createChild("Light"); diff --git a/examples/src/gpu-instancing-custom-data.ts b/examples/src/gpu-instancing-custom-data.ts index 9b4150b232..eb7c9e8917 100644 --- a/examples/src/gpu-instancing-custom-data.ts +++ b/examples/src/gpu-instancing-custom-data.ts @@ -3,6 +3,7 @@ * @category Mesh * @thumbnail https://mdn.alipayobjects.com/merchant_appfe/afts/img/A*jjZMTrp-vU8AAAAAAAAAAAAADiR2AQ/original */ +import { Stats } from "@galacean/engine-toolkit-stats"; import { Camera, Color, @@ -121,6 +122,7 @@ WebGLEngine.create({ cameraEntity.transform.lookAt(new Vector3(0, 0, 0)); const camera = cameraEntity.addComponent(Camera); camera.farClipPlane = 500; + cameraEntity.addComponent(Stats); // Light const lightEntity = rootEntity.createChild("Light"); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5cb1233b5d..d052455a06 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7591,7 +7591,7 @@ snapshots: vite-node@2.1.5(@types/node@18.19.64)(sass@1.81.0)(terser@5.44.1): dependencies: cac: 6.7.14 - debug: 4.3.7 + debug: 4.4.3 es-module-lexer: 1.5.4 pathe: 1.1.2 vite: 5.4.11(@types/node@18.19.64)(sass@1.81.0)(terser@5.44.1) @@ -7684,7 +7684,7 @@ snapshots: '@vitest/spy': 2.1.5 '@vitest/utils': 2.1.5 chai: 5.1.2 - debug: 4.3.7 + debug: 4.4.3 expect-type: 1.1.0 magic-string: 0.30.12 pathe: 1.1.2 From 7ac1913bade14a25561df609cf097e5bad781e1e Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Tue, 12 May 2026 17:40:20 +0800 Subject: [PATCH 83/91] refactor(ui): move UIBatchSorter from core to ui UIBatchSorter is only used by UICanvas; keeping it in core forced a cross-package export plus a ts-ignore at the UICanvas import site for an @internal symbol. Move it next to its sole consumer in the ui package and add RenderElement to core's RenderPipeline barrel so ui can type the sort input. Utils._quickSort stays @internal; the new call site carries a single ts-ignore acknowledging the reuse. --- packages/core/src/RenderPipeline/index.ts | 2 +- .../src/RenderPipeline => ui/src/component}/UIBatchSorter.ts | 5 ++--- packages/ui/src/component/UICanvas.ts | 5 ++--- tests/src/{core => ui}/UIBatchSorter.test.ts | 2 +- 4 files changed, 6 insertions(+), 8 deletions(-) rename packages/{core/src/RenderPipeline => ui/src/component}/UIBatchSorter.ts (97%) rename tests/src/{core => ui}/UIBatchSorter.test.ts (98%) diff --git a/packages/core/src/RenderPipeline/index.ts b/packages/core/src/RenderPipeline/index.ts index ddfa25bf59..61855cbaee 100644 --- a/packages/core/src/RenderPipeline/index.ts +++ b/packages/core/src/RenderPipeline/index.ts @@ -1,6 +1,6 @@ export { BasicRenderPipeline, RenderQueueFlags } from "./BasicRenderPipeline"; export { VertexMergeBatcher } from "./VertexMergeBatcher"; -export { UIBatchSorter } from "./UIBatchSorter"; export { Blitter } from "./Blitter"; +export { RenderElement } from "./RenderElement"; export { RenderQueue } from "./RenderQueue"; export { PipelineStage } from "./enums/PipelineStage"; diff --git a/packages/core/src/RenderPipeline/UIBatchSorter.ts b/packages/ui/src/component/UIBatchSorter.ts similarity index 97% rename from packages/core/src/RenderPipeline/UIBatchSorter.ts rename to packages/ui/src/component/UIBatchSorter.ts index 26f6da8f92..0a7897a41b 100644 --- a/packages/core/src/RenderPipeline/UIBatchSorter.ts +++ b/packages/ui/src/component/UIBatchSorter.ts @@ -1,6 +1,4 @@ -import { BoundingBox, Matrix } from "@galacean/engine-math"; -import { Utils } from "../Utils"; -import { RenderElement } from "./RenderElement"; +import { BoundingBox, Matrix, RenderElement, Utils } from "@galacean/engine"; /** * @internal @@ -118,6 +116,7 @@ export class UIBatchSorter { } } + // @ts-ignore — Utils._quickSort is @internal Utils._quickSort(entries, 0, count, UIBatchSorter._compareEntries); for (let i = 0; i < count; i++) elements[i] = entries[i].element; } diff --git a/packages/ui/src/component/UICanvas.ts b/packages/ui/src/component/UICanvas.ts index 3a11eaefb1..cf3d743288 100644 --- a/packages/ui/src/component/UICanvas.ts +++ b/packages/ui/src/component/UICanvas.ts @@ -16,11 +16,10 @@ import { assignmentClone, deepClone, dependentComponents, - ignoreClone, - // @ts-ignore — internal API - UIBatchSorter + ignoreClone } from "@galacean/engine"; import { Utils } from "../Utils"; +import { UIBatchSorter } from "./UIBatchSorter"; import { CanvasRenderMode } from "../enums/CanvasRenderMode"; import { ResolutionAdaptationMode } from "../enums/ResolutionAdaptationMode"; import { UIHitResult } from "../input/UIHitResult"; diff --git a/tests/src/core/UIBatchSorter.test.ts b/tests/src/ui/UIBatchSorter.test.ts similarity index 98% rename from tests/src/core/UIBatchSorter.test.ts rename to tests/src/ui/UIBatchSorter.test.ts index d2703dec9e..a5cd3b9b71 100644 --- a/tests/src/core/UIBatchSorter.test.ts +++ b/tests/src/ui/UIBatchSorter.test.ts @@ -1,4 +1,4 @@ -import { UIBatchSorter } from "@galacean/engine-core"; +import { UIBatchSorter } from "@galacean/engine-ui/src/component/UIBatchSorter"; import { BoundingBox, Matrix, Quaternion, Vector3 } from "@galacean/engine-math"; import { describe, expect, it } from "vitest"; From 884cb97a9ab3559474c316f2348c2d2ce6789fc8 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Tue, 12 May 2026 21:44:26 +0800 Subject: [PATCH 84/91] fix(instancing): pack the renderer's transform-source matrix, not entity's InstanceBuffer read renderer.entity.transform.worldMatrix, while Renderer._updateTransformShaderData uses _transformEntity.transform. They diverge whenever a subclass remaps _transformEntity (e.g. SkinnedMeshRenderer points it at the root bone); switch InstanceBuffer to _transformEntity to align with the shader-data path. --- packages/core/src/RenderPipeline/InstanceBuffer.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/src/RenderPipeline/InstanceBuffer.ts b/packages/core/src/RenderPipeline/InstanceBuffer.ts index b421b5ad8b..5d0d4368c2 100644 --- a/packages/core/src/RenderPipeline/InstanceBuffer.ts +++ b/packages/core/src/RenderPipeline/InstanceBuffer.ts @@ -62,9 +62,9 @@ export class InstanceBuffer { const propertyId = field.property._uniqueId; if (propertyId === modelMatId) { - // Instancing skips _updateTransformShaderData, so worldMatrix is not in propertyValueMap - // Must read from transform getter to trigger lazy update - field.pack(floatView, fieldOffset, renderer.entity.transform.worldMatrix); + // Instancing skips _updateTransformShaderData; mirror its transform source + // @ts-ignore — _transformEntity is protected + field.pack(floatView, fieldOffset, renderer._transformEntity.transform.worldMatrix); } else { const value = propertyValueMap[propertyId]; if (value != null) { From bd96c42919fe4000f3dde1229cf1e5f1f26244e9 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Tue, 12 May 2026 21:44:47 +0800 Subject: [PATCH 85/91] chore(ui): type UICanvas render element arrays and release them on disable Replace `any[]` with `RenderElement[]` on `_renderElements` / `_batchedRenderElements`, and clear both in `_onDisable` so the pooled elements they hold don't surface stale references when other renderers reuse the same pool slots while the canvas is disabled. --- packages/ui/src/component/UICanvas.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/component/UICanvas.ts b/packages/ui/src/component/UICanvas.ts index cf3d743288..b9c2cb8834 100644 --- a/packages/ui/src/component/UICanvas.ts +++ b/packages/ui/src/component/UICanvas.ts @@ -11,6 +11,7 @@ import { MathUtil, Matrix, Ray, + RenderElement, Vector2, Vector3, assignmentClone, @@ -62,10 +63,10 @@ export class UICanvas extends Component implements IElement { _isRootCanvas: boolean = false; /** @internal */ @ignoreClone - _renderElements: any[] = []; + _renderElements: RenderElement[] = []; /** @internal */ @ignoreClone - _batchedRenderElements: any[] = []; + _batchedRenderElements: RenderElement[] = []; /** @internal */ @ignoreClone _sortDistance: number = 0; @@ -395,6 +396,12 @@ export class UICanvas extends Component implements IElement { Utils.cleanRootCanvas(this); } + // @ts-ignore + override _onDisable(): void { + this._renderElements.length = 0; + this._batchedRenderElements.length = 0; + } + /** * @internal */ From 00b4462123f587211b355f07d10d91947b9de6da Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Tue, 12 May 2026 22:01:04 +0800 Subject: [PATCH 86/91] feat(instancing): support bool / bvec uniform types in instance UBO MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ShaderData.setInt documents bool support and setVector* documents bvec support, but the instance UBO layout table didn't list them — declaring a `bool` or `bvec` renderer uniform would silently drop the declaration without producing a #define, causing the shader to fail with an undeclared identifier. Add std140 size/align entries, reuse existing packScalar/packVec pack functions, and recognize the `b` prefix for the intView dispatch. --- packages/core/src/shader/ShaderFactory.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/core/src/shader/ShaderFactory.ts b/packages/core/src/shader/ShaderFactory.ts index 144b4e888e..d9606aa7fb 100644 --- a/packages/core/src/shader/ShaderFactory.ts +++ b/packages/core/src/shader/ShaderFactory.ts @@ -36,12 +36,16 @@ export class ShaderFactory { float: { size: 4, align: 4 }, int: { size: 4, align: 4 }, uint: { size: 4, align: 4 }, + bool: { size: 4, align: 4 }, vec2: { size: 8, align: 8 }, ivec2: { size: 8, align: 8 }, + bvec2: { size: 8, align: 8 }, vec3: { size: 12, align: 16 }, ivec3: { size: 12, align: 16 }, + bvec3: { size: 12, align: 16 }, vec4: { size: 16, align: 16 }, ivec4: { size: 16, align: 16 }, + bvec4: { size: 16, align: 16 }, mat4: { size: 64, align: 16 }, mat3x4: { size: 48, align: 16 } }; @@ -102,12 +106,16 @@ export class ShaderFactory { float: packScalar, int: packScalar, uint: packScalar, + bool: packScalar, vec2: packVec2, ivec2: packVec2, + bvec2: packVec2, vec3: packVec3, ivec3: packVec3, + bvec3: packVec3, vec4: packVec4, ivec4: packVec4, + bvec4: packVec4, mat4: (v: Float32Array | Int32Array, o: number, val: Matrix) => { const e = val.elements; for (let k = 0; k < 16; k++) v[o + k] = e[k]; @@ -320,7 +328,7 @@ export class ShaderFactory { type, offset: currentOffset, offsetInElements: currentOffset / 4, - useIntView: type[0] === "i" || type[0] === "u", + useIntView: type[0] === "i" || type[0] === "u" || type[0] === "b", pack: packFuncMap[type] }); currentOffset += info.size; From 04ac8b3b7a660411fd19af01212435fe2667b72a Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Wed, 13 May 2026 17:38:59 +0800 Subject: [PATCH 87/91] fix(instancing): always run inject path so derived built-ins keep compiling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shaders that only declare derived built-ins (e.g. `renderer_MVPMat`) had their declarations stripped by `_scanInstanceUniforms` but skipped the `#define` rewrite because `fieldMap` was empty, leaving dangling `renderer_ModelMat` references that fail to compile. This also fixed a latent bug where transform-free shaders with `RENDERER_INSTANCING` enabled would silently lose `N-1` instances — `_canBatch` returned true and only the leader was drawn. --- packages/core/src/shader/ShaderFactory.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/core/src/shader/ShaderFactory.ts b/packages/core/src/shader/ShaderFactory.ts index d9606aa7fb..cfe63f82a9 100644 --- a/packages/core/src/shader/ShaderFactory.ts +++ b/packages/core/src/shader/ShaderFactory.ts @@ -217,13 +217,8 @@ export class ShaderFactory { fragmentSource = ShaderFactory._scanInstanceUniforms(fragmentSource, fieldMap); } - let hasField = false; - for (const _ in fieldMap) { - hasField = true; - break; - } - if (!hasField) return { vertexSource, fragmentSource, instanceLayout: null }; - + // Even when fieldMap is empty, derived built-ins (e.g. `renderer_MVPMat`) may have + // had their declarations stripped by scan and still need a `#define` to compile const instanceLayout = ShaderFactory._buildLayout(engine, fieldMap); const { instanceFields } = instanceLayout; From 9606bf3de8011ba0465f691fdc3f1862cdf2a065 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Wed, 13 May 2026 20:30:45 +0800 Subject: [PATCH 88/91] fix(ui): skip re-batching of canvas-internal leaders in main pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UICanvas pre-batches its children into leaders that carry a self-contained draw range. When two canvases sharing the same atlas push their leaders into the transparent queue, the main-pipeline batcher previously fed those leaders back into `_canBatch`/`_batch` as if they were single sub-elements. That corrupted `subMesh.count` and re-appended already-written indices, dropping draws or overlapping ranges. The batch boundary is the canvas — matches Unity uGUI's behavior — so detect already-batched leaders at the batcher entry and pass them through. --- .../core/src/RenderPipeline/BatcherManager.ts | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/packages/core/src/RenderPipeline/BatcherManager.ts b/packages/core/src/RenderPipeline/BatcherManager.ts index 6f733eb0ad..923c258071 100644 --- a/packages/core/src/RenderPipeline/BatcherManager.ts +++ b/packages/core/src/RenderPipeline/BatcherManager.ts @@ -56,30 +56,34 @@ export class BatcherManager { let preConstructor: Function; for (let i = 0, n = input.length; i < n; ++i) { const curElement = input[i]; + + // Already-batched leaders (e.g. produced by UICanvas pre-batching) are terminal — + // each carries an opaque, self-contained draw range that must not be merged again. + // Flush any pending pre and pass the leader straight through + if (curElement._isBatched) { + preElement && (BatcherManager._flush(output, preElement), (preElement = null)); + output.push(curElement); + continue; + } + const renderer = curElement.component; const constructor = renderer.constructor; - if (preElement) { - if (preConstructor === constructor && preRenderer._canBatch(preElement, curElement)) { - preRenderer._batch(preElement, curElement); - } else { - preElement._isBatched = true; - output.push(preElement); - preElement = curElement; - preRenderer = renderer; - preConstructor = constructor; - renderer._batch(null, curElement); - } + if (preElement && preConstructor === constructor && preRenderer._canBatch(preElement, curElement)) { + preRenderer._batch(preElement, curElement); } else { + preElement && BatcherManager._flush(output, preElement); preElement = curElement; preRenderer = renderer; preConstructor = constructor; renderer._batch(null, curElement); } } - if (preElement) { - preElement._isBatched = true; - output.push(preElement); - } + preElement && BatcherManager._flush(output, preElement); + } + + private static _flush(output: RenderElement[], element: RenderElement): void { + element._isBatched = true; + output.push(element); } uploadBuffer() { From a25abab76edccce4da6c7e9e8f6777ec1591bf0c Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Wed, 13 May 2026 20:42:11 +0800 Subject: [PATCH 89/91] refactor(shader): drop dead macro-aware uniform scan in instance UBO inject MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `_scanInstanceUniformsWithMacros` and `injectInstanceUBO`'s `activeMacros` parameter only served the raw GLSL path, which was removed in e08af33d9 ("refactor(shader): remove raw GLSL shader path"). The 4 preprocessor regexes are only used by this function. All inputs are now ShaderLab preprocessor-evaluated before reaching the injector. Cleanup that should have landed alongside e08af33d9. Also reported by reviewer as a dormant `#if`/`#elif` branch-stack bug — moot once the function is gone. --- packages/core/src/shader/ShaderFactory.ts | 67 ++--------------------- 1 file changed, 4 insertions(+), 63 deletions(-) diff --git a/packages/core/src/shader/ShaderFactory.ts b/packages/core/src/shader/ShaderFactory.ts index cfe63f82a9..43ea0563e6 100644 --- a/packages/core/src/shader/ShaderFactory.ts +++ b/packages/core/src/shader/ShaderFactory.ts @@ -76,13 +76,6 @@ export class ShaderFactory { private static readonly _uboUniformRegex = /^[ \t]*uniform\s+(?:(?:lowp|mediump|highp)\s+)?(\w+)\s+(\w+)\s*(\[.+?\])?\s*;/gm; - // Preprocessor directives — only `#ifdef / #ifndef / #else / #endif` are - // supported; `#if` with expressions is treated as always-active. - private static readonly _ifdefRegex = /^[ \t]*#ifdef\s+(\w+)/; - private static readonly _ifndefRegex = /^[ \t]*#ifndef\s+(\w+)/; - private static readonly _elseRegex = /^[ \t]*#else\b/; - private static readonly _endifRegex = /^[ \t]*#endif\b/; - private static _packFuncMap: Record = (() => { const packScalar = (v: Float32Array | Int32Array, o: number, val: number) => { v[o] = val; @@ -199,23 +192,16 @@ export class ShaderFactory { * std140 UBO (instanced array), and emit `#define` remapping so original uniform * names resolve to `rendererData[instanceID].field`. * - * @param activeMacros - When supplied, scanning honors `#ifdef`/`#ifndef` blocks so - * uniforms in inactive branches are not collected. + * Inputs are expected to be already preprocessor-evaluated (no `#ifdef` left). */ static injectInstanceUBO( engine: Engine, vertexSource: string, - fragmentSource: string, - activeMacros?: Set + fragmentSource: string ): { vertexSource: string; fragmentSource: string; instanceLayout: InstanceBufferLayout | null } { const fieldMap: Record = Object.create(null); - if (activeMacros) { - vertexSource = ShaderFactory._scanInstanceUniformsWithMacros(vertexSource, fieldMap, activeMacros); - fragmentSource = ShaderFactory._scanInstanceUniformsWithMacros(fragmentSource, fieldMap, activeMacros); - } else { - vertexSource = ShaderFactory._scanInstanceUniforms(vertexSource, fieldMap); - fragmentSource = ShaderFactory._scanInstanceUniforms(fragmentSource, fieldMap); - } + vertexSource = ShaderFactory._scanInstanceUniforms(vertexSource, fieldMap); + fragmentSource = ShaderFactory._scanInstanceUniforms(fragmentSource, fieldMap); // Even when fieldMap is empty, derived built-ins (e.g. `renderer_MVPMat`) may have // had their declarations stripped by scan and still need a `#define` to compile @@ -261,51 +247,6 @@ export class ShaderFactory { }); } - /** - * Scan with preprocessor awareness, for raw GLSL paths where `#ifdef` blocks are not yet - * expanded. Uniforms inside inactive branches are skipped. - */ - private static _scanInstanceUniformsWithMacros( - source: string, - fieldMap: Record, - activeMacros: Set - ): string { - const branchStack: boolean[] = [true]; - const lines = source.split("\n"); - - for (let i = 0, n = lines.length; i < n; i++) { - const line = lines[i]; - - let m = line.match(ShaderFactory._ifdefRegex); - if (m) { - const parentActive = branchStack[branchStack.length - 1]; - branchStack.push(parentActive && activeMacros.has(m[1])); - continue; - } - m = line.match(ShaderFactory._ifndefRegex); - if (m) { - const parentActive = branchStack[branchStack.length - 1]; - branchStack.push(parentActive && !activeMacros.has(m[1])); - continue; - } - if (ShaderFactory._elseRegex.test(line)) { - const parentActive = branchStack.length >= 2 ? branchStack[branchStack.length - 2] : true; - const currentActive = branchStack[branchStack.length - 1]; - branchStack[branchStack.length - 1] = parentActive && !currentActive; - continue; - } - if (ShaderFactory._endifRegex.test(line)) { - if (branchStack.length > 1) branchStack.pop(); - continue; - } - if (!branchStack[branchStack.length - 1]) continue; - - lines[i] = ShaderFactory._scanInstanceUniforms(line, fieldMap); - } - - return lines.join("\n"); - } - private static _buildLayout(engine: Engine, fieldMap: Record): InstanceBufferLayout { const maxUBOSize = engine._hardwareRenderer.maxUniformBlockSize; const std140Map = ShaderFactory._std140TypeInfoMap; From 5b82d694e391cdb33a22d50a6e3f80407d7c04c4 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Wed, 13 May 2026 20:42:23 +0800 Subject: [PATCH 90/91] chore(batcher): drop dead leader guard in VertexMergeBatcher MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `else if (curElement._isBatched) return` in `VertexMergeBatcher.batch` was defensive: it caught the case where main pipeline fed an already- batched leader back into `_batch(null, leader)`. After commit 9606bf3de moved that check to `BatcherManager.batch`'s entry, the leader never reaches this branch. Refresh `RenderElement._isBatched`'s doc to match its current meaning ("batch leader — must not be merged again"). --- packages/core/src/RenderPipeline/RenderElement.ts | 2 +- packages/core/src/RenderPipeline/VertexMergeBatcher.ts | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/core/src/RenderPipeline/RenderElement.ts b/packages/core/src/RenderPipeline/RenderElement.ts index 184e1decaa..fe2f86936c 100644 --- a/packages/core/src/RenderPipeline/RenderElement.ts +++ b/packages/core/src/RenderPipeline/RenderElement.ts @@ -17,7 +17,7 @@ export class RenderElement implements IPoolElement { shaderData?: ShaderData; instancedRenderers: Renderer[] = []; subDistancePriority: number = 0; - /** @internal Set when canvas-internal batching wrote to the chunk this frame; protects leader from main-pipeline _batch(null, leader) re-init within the same frame */ + /** @internal Marks a batch leader — a self-contained draw range that must not be merged again downstream */ _isBatched: boolean = false; // @todo: maybe should remove later diff --git a/packages/core/src/RenderPipeline/VertexMergeBatcher.ts b/packages/core/src/RenderPipeline/VertexMergeBatcher.ts index ad03379888..9bf7777b62 100644 --- a/packages/core/src/RenderPipeline/VertexMergeBatcher.ts +++ b/packages/core/src/RenderPipeline/VertexMergeBatcher.ts @@ -46,9 +46,6 @@ export class VertexMergeBatcher { const length = localIndices.length; if (preElement) { preElement.subChunk.subMesh.count += length; - } else if (curElement._isBatched) { - // Already wrote to chunk this frame (canvas-internal batching) — main pipeline must not re-init - return; } else { // First write this frame — init subMesh range subChunk.subMesh.start = chunk.updateIndexLength; From 10b9265663c7c95400150007be6f404211f770b2 Mon Sep 17 00:00:00 2001 From: "chenmo.gl" Date: Wed, 13 May 2026 21:21:00 +0800 Subject: [PATCH 91/91] fix(instancing): reject unsupported uniform types at scan time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `_scanInstanceUniforms` stripped renderer-group uniform declarations before `_buildLayout` decided whether they could fit the std140 layout. For types like `mat3` (which std140's row-padded layout doesn't support in the current `_std140TypeInfoMap`), the declaration was removed but no UBO field or `#define` replaced it — every later reference became an undeclared identifier and the whole pass silently failed to compile. Check the storage type against the std140 map up front. Unsupported types stay declared in the source and emit a clear log; supported types follow the existing strip-and-collect path. --- packages/core/src/shader/ShaderFactory.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/core/src/shader/ShaderFactory.ts b/packages/core/src/shader/ShaderFactory.ts index 43ea0563e6..8301b7d5fa 100644 --- a/packages/core/src/shader/ShaderFactory.ts +++ b/packages/core/src/shader/ShaderFactory.ts @@ -230,6 +230,7 @@ export class ShaderFactory { private static _scanInstanceUniforms(source: string, fieldMap: Record): string { const builtinUniforms = ShaderFactory._builtinRendererUniforms; + const std140Map = ShaderFactory._std140TypeInfoMap; return source.replace(ShaderFactory._uboUniformRegex, (match, type, name, arraySize) => { if (type.includes("sampler")) return match; const isDerived = builtinUniforms[name]; @@ -240,9 +241,13 @@ export class ShaderFactory { Logger.error(`GPU Instancing does not support array uniform "${name}${arraySize}"`); return match; } - // ModelMat is affine, store as mat3x4 (3 columns) to save 16 bytes per instance - fieldMap[ShaderProperty.getByName(name)._uniqueId] = - type === "mat4" && name === "renderer_ModelMat" ? "mat3x4" : type; + // ModelMat is affine, stored as mat3x4 (3 columns) to save 16 bytes per instance + const storageType = type === "mat4" && name === "renderer_ModelMat" ? "mat3x4" : type; + if (!std140Map[storageType]) { + Logger.error(`GPU Instancing does not support uniform "${name}" of type "${type}"`); + return match; + } + fieldMap[ShaderProperty.getByName(name)._uniqueId] = storageType; return ""; }); } @@ -256,6 +261,8 @@ export class ShaderFactory { const packFuncMap = ShaderFactory._packFuncMap; const addField = (id: number): void => { const type = fieldMap[id]; + // Unsupported types are filtered out in `_scanInstanceUniforms` with a clear error; + // this only triggers if the scan/build contract is violated const info = std140Map[type]; if (!info) return; currentOffset = Math.ceil(currentOffset / info.align) * info.align;