diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8607040f17..6e0dc2dcba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,7 +41,7 @@ jobs: - uses: actions/checkout@v3 - name: Install pnpm uses: pnpm/action-setup@v2 - + - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v3 with: @@ -63,6 +63,12 @@ jobs: node-version: 16.x cache: pnpm + - name: Install + run: pnpm install + - name: Build + run: npm run build + - name: Test + run: npm run test-cov - run: pnpm install codecov -w - name: Upload coverage to Codecov run: ./node_modules/.bin/codecov diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000000..eeeb7be52f --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +pnpm-lock.yaml \ No newline at end of file diff --git a/package.json b/package.json index 7c749b10e1..3f45717801 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@galacean/engine-root", "version": "1.0.0", - "packageManager": "pnpm@8.3.1", + "packageManager": "pnpm@8.6.2", "private": true, "scripts": { "preinstall": "npx only-allow pnpm", diff --git a/packages/core/src/2d/atlas/SpriteAtlas.ts b/packages/core/src/2d/atlas/SpriteAtlas.ts index bf2e42da11..44adadbfdd 100644 --- a/packages/core/src/2d/atlas/SpriteAtlas.ts +++ b/packages/core/src/2d/atlas/SpriteAtlas.ts @@ -63,6 +63,8 @@ export class SpriteAtlas extends ReferResource { */ _addSprite(sprite: Sprite): void { this._spriteNamesToIndex[sprite.name] = this._sprites.push(sprite) - 1; + sprite._atlas = this; + sprite.isGCIgnored = true; } /** @@ -70,6 +72,11 @@ export class SpriteAtlas extends ReferResource { */ protected override _onDestroy(): void { super._onDestroy(); + const { _sprites: sprites } = this; + for (let i = 0, n = sprites.length; i < n; i++) { + sprites[i].destroy(); + } + sprites.length = 0; this._sprites = null; this._spriteNamesToIndex = null; } diff --git a/packages/core/src/2d/sprite/Sprite.ts b/packages/core/src/2d/sprite/Sprite.ts index 85cf820992..29d9c285cb 100644 --- a/packages/core/src/2d/sprite/Sprite.ts +++ b/packages/core/src/2d/sprite/Sprite.ts @@ -4,6 +4,7 @@ import { UpdateFlagManager } from "../../UpdateFlagManager"; import { ReferResource } from "../../asset/ReferResource"; import { Texture2D } from "../../texture/Texture2D"; import { SpriteModifyFlags } from "../enums/SpriteModifyFlags"; +import { SpriteAtlas } from "../atlas/SpriteAtlas"; /** * 2D sprite. @@ -32,6 +33,8 @@ export class Sprite extends ReferResource { private _dirtyUpdateFlag: SpriteUpdateFlags = SpriteUpdateFlags.all; + /** @internal */ + _atlas: SpriteAtlas; /** @internal */ _updateFlagManager: UpdateFlagManager = new UpdateFlagManager(); @@ -153,14 +156,7 @@ export class Sprite extends ReferResource { } set region(value: Rect) { - const region = this._region; - const x = MathUtil.clamp(value.x, 0, 1); - const y = MathUtil.clamp(value.y, 0, 1); - region.set(x, y, MathUtil.clamp(value.width, 0, 1 - x), MathUtil.clamp(value.height, 0, 1 - y)); - this._dispatchSpriteChange(SpriteModifyFlags.region); - if (this._customWidth === undefined || this._customHeight === undefined) { - this._dispatchSpriteChange(SpriteModifyFlags.size); - } + this._region !== value && this._region.copyFrom(value); } /** @@ -172,16 +168,7 @@ export class Sprite extends ReferResource { } set pivot(value: Vector2) { - const pivot = this._pivot; - if (pivot === value) { - this._dispatchSpriteChange(SpriteModifyFlags.pivot); - } else { - const { x, y } = value; - if (pivot.x !== x || pivot.y !== y) { - pivot.set(x, y); - this._dispatchSpriteChange(SpriteModifyFlags.pivot); - } - } + this._pivot !== value && this._pivot.copyFrom(value); } /** @@ -196,11 +183,7 @@ export class Sprite extends ReferResource { } set border(value: Vector4) { - const border = this._border; - const x = MathUtil.clamp(value.x, 0, 1); - const y = MathUtil.clamp(value.y, 0, 1); - border.set(x, y, MathUtil.clamp(value.z, 0, 1 - x), MathUtil.clamp(value.w, 0, 1 - y)); - this._dispatchSpriteChange(SpriteModifyFlags.border); + this._border !== value && this._border.copyFrom(value); } /** @@ -222,6 +205,15 @@ export class Sprite extends ReferResource { ) { super(engine); this._texture = texture; + this._onRegionChange = this._onRegionChange.bind(this); + this._onPivotChange = this._onPivotChange.bind(this); + this._onBorderChange = this._onBorderChange.bind(this); + // @ts-ignore + this._region._onValueChanged = this._onRegionChange; + // @ts-ignore + this._pivot._onValueChanged = this._onPivotChange; + // @ts-ignore + this._border._onValueChanged = this._onBorderChange; region && this._region.copyFrom(region); pivot && this._pivot.copyFrom(pivot); border && this._border.copyFrom(border); @@ -264,12 +256,32 @@ export class Sprite extends ReferResource { return this._bounds; } + /** + * @internal + */ + override _addReferCount(value: number): void { + super._addReferCount(value); + this._atlas?._addReferCount(value); + } + /** * @internal */ protected override _onDestroy(): void { super._onDestroy(); + this._positions.length = 0; + this._positions = null; + this._uvs.length = 0; + this._uvs = null; + this._atlasRegion = null; + this._atlasRegionOffset = null; + this._region = null; + this._pivot = null; + this._border = null; + this._bounds = null; + this._atlas = null; this._texture = null; + this._updateFlagManager = null; } private _calDefaultSize(): void { @@ -368,6 +380,37 @@ export class Sprite extends ReferResource { } this._updateFlagManager.dispatch(type); } + + private _onRegionChange(): void { + const { _region: region } = this; + // @ts-ignore + region._onValueChanged = null; + const x = MathUtil.clamp(region.x, 0, 1); + const y = MathUtil.clamp(region.y, 0, 1); + region.set(x, y, MathUtil.clamp(region.width, 0, 1 - x), MathUtil.clamp(region.height, 0, 1 - y)); + this._dispatchSpriteChange(SpriteModifyFlags.region); + if (this._customWidth === undefined || this._customHeight === undefined) { + this._dispatchSpriteChange(SpriteModifyFlags.size); + } + // @ts-ignore + region._onValueChanged = this._onRegionChange; + } + + private _onPivotChange(): void { + this._dispatchSpriteChange(SpriteModifyFlags.pivot); + } + + private _onBorderChange(): void { + const { _border: border } = this; + // @ts-ignore + border._onValueChanged = null; + const x = MathUtil.clamp(border.x, 0, 1); + const y = MathUtil.clamp(border.y, 0, 1); + border.set(x, y, MathUtil.clamp(border.z, 0, 1 - x), MathUtil.clamp(border.w, 0, 1 - y)); + this._dispatchSpriteChange(SpriteModifyFlags.border); + // @ts-ignore + border._onValueChanged = this._onBorderChange; + } } enum SpriteUpdateFlags { diff --git a/packages/core/src/2d/sprite/SpriteMask.ts b/packages/core/src/2d/sprite/SpriteMask.ts index a6fd40a724..2b627dfc78 100644 --- a/packages/core/src/2d/sprite/SpriteMask.ts +++ b/packages/core/src/2d/sprite/SpriteMask.ts @@ -132,9 +132,13 @@ export class SpriteMask extends Renderer { set sprite(value: Sprite | null) { const lastSprite = this._sprite; if (lastSprite !== value) { - lastSprite && lastSprite._updateFlagManager.removeListener(this._onSpriteChange); + if (lastSprite) { + lastSprite._addReferCount(-1); + lastSprite._updateFlagManager.removeListener(this._onSpriteChange); + } this._dirtyUpdateFlag |= SpriteMaskUpdateFlags.All; if (value) { + value._addReferCount(1); value._updateFlagManager.addListener(this._onSpriteChange); this.shaderData.setTexture(SpriteMask._textureProperty, value.texture); } else { @@ -228,7 +232,12 @@ export class SpriteMask extends Renderer { */ protected override _onDestroy(): void { super._onDestroy(); - this._sprite?._updateFlagManager.removeListener(this._onSpriteChange); + const sprite = this._sprite; + if (sprite) { + sprite._addReferCount(-1); + sprite._updateFlagManager.removeListener(this._onSpriteChange); + } + this._entity = null; this._sprite = null; this._verticesData = null; } diff --git a/packages/core/src/2d/sprite/SpriteRenderer.ts b/packages/core/src/2d/sprite/SpriteRenderer.ts index 11df2ad889..2204e1e2e4 100644 --- a/packages/core/src/2d/sprite/SpriteRenderer.ts +++ b/packages/core/src/2d/sprite/SpriteRenderer.ts @@ -131,9 +131,13 @@ export class SpriteRenderer extends Renderer { set sprite(value: Sprite | null) { const lastSprite = this._sprite; if (lastSprite !== value) { - lastSprite && lastSprite._updateFlagManager.removeListener(this._onSpriteChange); + if (lastSprite) { + lastSprite._addReferCount(-1); + lastSprite._updateFlagManager.removeListener(this._onSpriteChange); + } this._dirtyUpdateFlag |= SpriteRendererUpdateFlags.All; if (value) { + value._addReferCount(1); value._updateFlagManager.addListener(this._onSpriteChange); this.shaderData.setTexture(SpriteRenderer._textureProperty, value.texture); } else { @@ -329,7 +333,12 @@ export class SpriteRenderer extends Renderer { */ protected override _onDestroy(): void { super._onDestroy(); - this._sprite?._updateFlagManager.removeListener(this._onSpriteChange); + const sprite = this._sprite; + if (sprite) { + sprite._addReferCount(-1); + sprite._updateFlagManager.removeListener(this._onSpriteChange); + } + this._entity = null; this._color = null; this._sprite = null; this._assembler = null; diff --git a/packages/core/src/2d/text/SubFont.ts b/packages/core/src/2d/text/SubFont.ts index 945cadb792..eb3479209a 100644 --- a/packages/core/src/2d/text/SubFont.ts +++ b/packages/core/src/2d/text/SubFont.ts @@ -95,6 +95,7 @@ export class SubFont { const fontAtlas = new FontAtlas(engine); const texture = new Texture2D(engine, 256, 256); fontAtlas.texture = texture; + fontAtlas.isGCIgnored = texture.isGCIgnored = true; this._fontAtlases.push(fontAtlas); const nativeFontString = this.nativeFontString; diff --git a/packages/core/src/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts index 9b0aa08ff5..dc75e7366c 100644 --- a/packages/core/src/2d/text/TextRenderer.ts +++ b/packages/core/src/2d/text/TextRenderer.ts @@ -480,97 +480,102 @@ export class TextRenderer extends Renderer { } private _updateLocalData(): void { - const { color, horizontalAlignment, verticalAlignment, _charRenderDatas: charRenderDatas } = this; + const { color, _charRenderDatas: charRenderDatas, _subFont: charFont } = this; const { min, max } = this._localBounds; - const { _pixelsPerUnit } = Engine; - const pixelsPerUnitReciprocal = 1.0 / _pixelsPerUnit; - const charFont = this._subFont; - const rendererWidth = this.width * _pixelsPerUnit; - const halfRendererWidth = rendererWidth * 0.5; - const rendererHeight = this.height * _pixelsPerUnit; - const textMetrics = this.enableWrapping ? TextUtils.measureTextWithWrap(this) : TextUtils.measureTextWithoutWrap(this); const { height, lines, lineWidths, lineHeight, lineMaxSizes } = textMetrics; const charRenderDataPool = TextRenderer._charRenderDataPool; - const halfLineHeight = lineHeight * 0.5; const linesLen = lines.length; + let renderDataCount = 0; - let startY = 0; - const topDiff = lineHeight * 0.5 - lineMaxSizes[0].ascent; - const bottomDiff = lineHeight * 0.5 - lineMaxSizes[linesLen - 1].descent - 1; - switch (verticalAlignment) { - case TextVerticalAlignment.Top: - startY = rendererHeight * 0.5 - halfLineHeight + topDiff; - break; - case TextVerticalAlignment.Center: - startY = height * 0.5 - halfLineHeight - (bottomDiff - topDiff) * 0.5; - break; - case TextVerticalAlignment.Bottom: - startY = height - rendererHeight * 0.5 - halfLineHeight - bottomDiff; - break; - } + if (linesLen > 0) { + const { _pixelsPerUnit } = Engine; + const { horizontalAlignment } = this; + const pixelsPerUnitReciprocal = 1.0 / _pixelsPerUnit; + const rendererWidth = this.width * _pixelsPerUnit; + const halfRendererWidth = rendererWidth * 0.5; + const rendererHeight = this.height * _pixelsPerUnit; + const halfLineHeight = lineHeight * 0.5; + + let startY = 0; + const topDiff = lineHeight * 0.5 - lineMaxSizes[0].ascent; + const bottomDiff = lineHeight * 0.5 - lineMaxSizes[linesLen - 1].descent - 1; + switch (this.verticalAlignment) { + case TextVerticalAlignment.Top: + startY = rendererHeight * 0.5 - halfLineHeight + topDiff; + break; + case TextVerticalAlignment.Center: + startY = height * 0.5 - halfLineHeight - (bottomDiff - topDiff) * 0.5; + break; + case TextVerticalAlignment.Bottom: + startY = height - rendererHeight * 0.5 - halfLineHeight - bottomDiff; + break; + } - let renderDataCount = 0; - let firstLine = -1; - let minX = Number.MAX_SAFE_INTEGER; - let minY = Number.MAX_SAFE_INTEGER; - let maxX = Number.MIN_SAFE_INTEGER; - let maxY = Number.MIN_SAFE_INTEGER; - for (let i = 0; i < linesLen; ++i) { - const lineWidth = lineWidths[i]; - if (lineWidth > 0) { - const line = lines[i]; - let startX = 0; - let firstRow = -1; - if (firstLine < 0) { - firstLine = i; - } - switch (horizontalAlignment) { - case TextHorizontalAlignment.Left: - startX = -halfRendererWidth; - break; - case TextHorizontalAlignment.Center: - startX = -lineWidth * 0.5; - break; - case TextHorizontalAlignment.Right: - startX = halfRendererWidth - lineWidth; - break; - } - for (let j = 0, n = line.length; j < n; ++j) { - const char = line[j]; - const charInfo = charFont._getCharInfo(char); - if (charInfo.h > 0) { - firstRow < 0 && (firstRow = j); - const charRenderData = (charRenderDatas[renderDataCount++] ||= charRenderDataPool.get()); - const { renderData, localPositions } = charRenderData; - charRenderData.texture = charFont._getTextureByIndex(charInfo.index); - renderData.color = color; - renderData.uvs = charInfo.uvs; - - const { w, ascent, descent } = charInfo; - const left = startX * pixelsPerUnitReciprocal; - const right = (startX + w) * pixelsPerUnitReciprocal; - const top = (startY + ascent) * pixelsPerUnitReciprocal; - const bottom = (startY - descent + 1) * pixelsPerUnitReciprocal; - localPositions.set(left, top, right, bottom); - i === firstLine && (maxY = Math.max(maxY, top)); - minY = Math.min(minY, bottom); - j === firstRow && (minX = Math.min(minX, left)); - maxX = Math.max(maxX, right); + let firstLine = -1; + let minX = Number.MAX_SAFE_INTEGER; + let minY = Number.MAX_SAFE_INTEGER; + let maxX = Number.MIN_SAFE_INTEGER; + let maxY = Number.MIN_SAFE_INTEGER; + for (let i = 0; i < linesLen; ++i) { + const lineWidth = lineWidths[i]; + if (lineWidth > 0) { + const line = lines[i]; + let startX = 0; + let firstRow = -1; + if (firstLine < 0) { + firstLine = i; + } + switch (horizontalAlignment) { + case TextHorizontalAlignment.Left: + startX = -halfRendererWidth; + break; + case TextHorizontalAlignment.Center: + startX = -lineWidth * 0.5; + break; + case TextHorizontalAlignment.Right: + startX = halfRendererWidth - lineWidth; + break; + } + for (let j = 0, n = line.length; j < n; ++j) { + const char = line[j]; + const charInfo = charFont._getCharInfo(char); + if (charInfo.h > 0) { + firstRow < 0 && (firstRow = j); + const charRenderData = (charRenderDatas[renderDataCount++] ||= charRenderDataPool.get()); + const { renderData, localPositions } = charRenderData; + charRenderData.texture = charFont._getTextureByIndex(charInfo.index); + renderData.color = color; + renderData.uvs = charInfo.uvs; + + const { w, ascent, descent } = charInfo; + const left = startX * pixelsPerUnitReciprocal; + const right = (startX + w) * pixelsPerUnitReciprocal; + const top = (startY + ascent) * pixelsPerUnitReciprocal; + const bottom = (startY - descent + 1) * pixelsPerUnitReciprocal; + localPositions.set(left, top, right, bottom); + i === firstLine && (maxY = Math.max(maxY, top)); + minY = Math.min(minY, bottom); + j === firstRow && (minX = Math.min(minX, left)); + maxX = Math.max(maxX, right); + } + startX += charInfo.xAdvance; } - startX += charInfo.xAdvance; } + startY -= lineHeight; } - startY -= lineHeight; - } - if (firstLine < 0) { + if (firstLine < 0) { + min.set(0, 0, 0); + max.set(0, 0, 0); + } else { + min.set(minX, minY, 0); + max.set(maxX, maxY, 0); + } + } else { min.set(0, 0, 0); max.set(0, 0, 0); - } else { - min.set(minX, minY, 0); - max.set(maxX, maxY, 0); } // Revert excess render data to pool. diff --git a/packages/core/src/2d/text/TextUtils.ts b/packages/core/src/2d/text/TextUtils.ts index 3b5e21e55b..e22fa890e8 100644 --- a/packages/core/src/2d/text/TextUtils.ts +++ b/packages/core/src/2d/text/TextUtils.ts @@ -110,6 +110,11 @@ export class TextUtils { subFont.nativeFontString = fontString; for (let i = 0, n = subTexts.length; i < n; i++) { const subText = subTexts[i]; + // If subText is empty, push an empty line directly + if (subText.length === 0) { + this._pushLine(lines, lineWidths, lineMaxSizes, "", 0, 0, 0); + continue; + } let word = ""; let wordWidth = 0; @@ -144,7 +149,11 @@ export class TextUtils { // If it is a word before, need to handle the previous word and line if (word.length > 0) { if (lineWidth + wordWidth > wrapWidth) { - this._pushLine(lines, lineWidths, lineMaxSizes, line, lineWidth, lineMaxAscent, lineMaxDescent); + // Push if before line is not empty + if (lineWidth > 0) { + this._pushLine(lines, lineWidths, lineMaxSizes, line, lineWidth, lineMaxAscent, lineMaxDescent); + } + textWidth = Math.max(textWidth, lineWidth); notFirstLine = true; line = word; @@ -191,7 +200,12 @@ export class TextUtils { line = ""; lineWidth = lineMaxAscent = lineMaxDescent = 0; } - this._pushLine(lines, lineWidths, lineMaxSizes, word, wordWidth, wordMaxAscent, wordMaxDescent); + + // Push if before word is not empty + if (wordWidth > 0) { + this._pushLine(lines, lineWidths, lineMaxSizes, word, wordWidth, wordMaxAscent, wordMaxDescent); + } + textWidth = Math.max(textWidth, wordWidth); notFirstLine = true; word = char; @@ -211,12 +225,16 @@ export class TextUtils { // If the total width from line and word exceed wrap width if (lineWidth + wordWidth > wrapWidth) { // Push chars to a single line - this._pushLine(lines, lineWidths, lineMaxSizes, line, lineWidth, lineMaxAscent, lineMaxDescent); + if (lineWidth > 0) { + this._pushLine(lines, lineWidths, lineMaxSizes, line, lineWidth, lineMaxAscent, lineMaxDescent); + } textWidth = Math.max(textWidth, lineWidth); lineWidth = 0; // Push word to a single line - this._pushLine(lines, lineWidths, lineMaxSizes, word, wordWidth, wordMaxAscent, wordMaxDescent); + if (wordWidth > 0) { + this._pushLine(lines, lineWidths, lineMaxSizes, word, wordWidth, wordMaxAscent, wordMaxDescent); + } textWidth = Math.max(textWidth, wordWidth); } else { // Merge to chars @@ -252,25 +270,21 @@ export class TextUtils { const { _subFont: subFont } = renderer; const fontString = subFont.nativeFontString; const fontSizeInfo = TextUtils.measureFont(fontString); - const lines = renderer.text.split(/(?:\r\n|\r|\n)/); - const lineCount = lines.length; + const subTexts = renderer.text.split(/(?:\r\n|\r|\n)/); + const textCount = subTexts.length; + const lines = new Array(); const lineWidths = new Array(); const lineMaxSizes = new Array(); const { _pixelsPerUnit } = Engine; const lineHeight = fontSizeInfo.size + renderer.lineSpacing * _pixelsPerUnit; let width = 0; - let height = renderer.height * _pixelsPerUnit; - if (renderer.overflowMode === OverflowMode.Overflow) { - height = lineHeight * lineCount; - } - subFont.nativeFontString = fontString; - for (let i = 0; i < lineCount; ++i) { - const line = lines[i]; + for (let i = 0; i < textCount; ++i) { + const line = subTexts[i]; let curWidth = 0; - let maxAscent = -1; - let maxDescent = -1; + let maxAscent = 0; + let maxDescent = 0; for (let j = 0, m = line.length; j < m; ++j) { const charInfo = TextUtils._getCharInfo(line[j], fontString, subFont); @@ -282,17 +296,18 @@ export class TextUtils { maxAscent < ascent && (maxAscent = ascent); maxDescent < descent && (maxDescent = descent); } - lineWidths[i] = curWidth; - lineMaxSizes[i] = { - ascent: maxAscent, - descent: maxDescent, - size: maxAscent + maxDescent - }; - if (curWidth > width) { - width = curWidth; + + if (curWidth > 0) { + this._pushLine(lines, lineWidths, lineMaxSizes, line, curWidth, maxAscent, maxDescent); + width = Math.max(width, curWidth); } } + let height = renderer.height * _pixelsPerUnit; + if (renderer.overflowMode === OverflowMode.Overflow) { + height = lineHeight * lines.length; + } + return { width, height, diff --git a/packages/core/src/Background.ts b/packages/core/src/Background.ts index 817c268f59..de3c44f403 100644 --- a/packages/core/src/Background.ts +++ b/packages/core/src/Background.ts @@ -48,6 +48,8 @@ export class Background { set texture(value: Texture2D) { if (this._texture !== value) { + value?._addReferCount(1); + this._texture?._addReferCount(-1); this._texture = value; this._engine._backgroundTextureMaterial.shaderData.setTexture("material_BaseTexture", value); } @@ -70,6 +72,17 @@ export class Background { } } + /** + * @internal + */ + destroy(): void { + this._mesh._addReferCount(-1); + this._mesh = null; + this.texture = null; + this.solidColor = null; + this.sky.destroy(); + } + /** * Constructor of Background. * @param _engine Engine Which the background belongs to. @@ -84,6 +97,7 @@ export class Background { */ _initMesh(engine): void { this._mesh = this._createPlane(engine); + this._mesh._addReferCount(1); } /** diff --git a/packages/core/src/Camera.ts b/packages/core/src/Camera.ts index cd4727fd63..857a5cdf4b 100644 --- a/packages/core/src/Camera.ts +++ b/packages/core/src/Camera.ts @@ -39,9 +39,6 @@ export class Camera extends Component { private static _cameraPositionProperty = ShaderProperty.getByName("camera_Position"); private static _cameraDepthBufferParamsProperty = ShaderProperty.getByName("camera_DepthBufferParams"); - /** Shader data. */ - readonly shaderData: ShaderData = new ShaderData(ShaderDataGroup.Camera); - /** Whether to enable frustum culling, it is enabled by default. */ enableFrustumCulling: boolean = true; @@ -80,6 +77,7 @@ export class Camera extends Component { _replacementSubShaderTag: ShaderTagKey = null; private _priority: number = 0; + private _shaderData: ShaderData = new ShaderData(ShaderDataGroup.Camera); private _isProjMatSetting = false; private _nearClipPlane: number = 0.1; private _farClipPlane: number = 100; @@ -111,6 +109,13 @@ export class Camera extends Component { @deepClone private _invViewProjMat: Matrix = new Matrix(); + /** + * Shader data. + */ + get shaderData(): ShaderData { + return this._shaderData; + } + /** * Near clip plane - the closest point to the camera when rendering occurs. */ @@ -305,6 +310,8 @@ export class Camera extends Component { set renderTarget(value: RenderTarget | null) { if (this._renderTarget !== value) { + value?._addReferCount(1); + this._renderTarget?._addReferCount(-1); this._renderTarget = value; this._updatePixelViewport(); } @@ -576,6 +583,21 @@ export class Camera extends Component { this._isInvViewProjDirty.destroy(); this._isViewMatrixDirty.destroy(); this.shaderData._addReferCount(-1); + + this._entity = null; + this._globalShaderMacro = null; + this._frustum = null; + this._renderPipeline = null; + this._virtualCamera = null; + this._shaderData = null; + this._frustumViewChangeFlag = null; + this._transform = null; + this._isViewMatrixDirty = null; + this._isInvViewProjDirty = null; + this._viewport = null; + this._inverseProjectionMatrix = null; + this._lastAspectSize = null; + this._invViewProjMat = null; } private _updatePixelViewport(): void { diff --git a/packages/core/src/ComponentsManager.ts b/packages/core/src/ComponentsManager.ts index b1bd601ef6..4dc9edf643 100644 --- a/packages/core/src/ComponentsManager.ts +++ b/packages/core/src/ComponentsManager.ts @@ -228,4 +228,17 @@ export class ComponentsManager { componentContainer.length = 0; this._componentsContainerPool.push(componentContainer); } + + /** + * @internal + */ + _gc() { + this._renderers.garbageCollection(); + this._onStartScripts.garbageCollection(); + this._onUpdateScripts.garbageCollection(); + this._onLateUpdateScripts.garbageCollection(); + this._onPhysicsUpdateScripts.garbageCollection(); + this._onUpdateAnimations.garbageCollection(); + this._onUpdateRenderers.garbageCollection(); + } } diff --git a/packages/core/src/DisorderedArray.ts b/packages/core/src/DisorderedArray.ts index 9fba79af52..62cf6ff340 100644 --- a/packages/core/src/DisorderedArray.ts +++ b/packages/core/src/DisorderedArray.ts @@ -11,17 +11,27 @@ export class DisorderedArray { } add(element: T): void { - if (this.length === this._elements.length) this._elements.push(element); - else this._elements[this.length] = element; + if (this.length === this._elements.length) { + this._elements.push(element); + } else { + this._elements[this.length] = element; + } this.length++; } delete(element: T): void { - //TODO: It can be optimized for custom binary search and other algorithms, currently this._elements>=this.length wastes performance. + // @todo: It can be optimized for custom binary search and other algorithms, currently this._elements>=this.length wastes performance. const index = this._elements.indexOf(element); this.deleteByIndex(index); } + set(index: number, element: T): void { + if (index >= this.length) { + throw "Index is out of range."; + } + this._elements[index] = element; + } + get(index: number): T { if (index >= this.length) { throw "Index is out of range."; @@ -30,9 +40,9 @@ export class DisorderedArray { } /** - * - * @param index - * @returns The replaced item is used to reset its index. + * Delete the element at the specified index. + * @param index - The index of the element to be deleted + * @returns The replaced item is used to reset its index */ deleteByIndex(index: number): T { var elements: T[] = this._elements; diff --git a/packages/core/src/Engine.ts b/packages/core/src/Engine.ts index adf604dba6..c0341f673a 100644 --- a/packages/core/src/Engine.ts +++ b/packages/core/src/Engine.ts @@ -1,4 +1,4 @@ -import { IPhysics, IPhysicsManager } from "@galacean/engine-design"; +import { IPhysics, IPhysicsManager, IShaderLab } from "@galacean/engine-design"; import { Color } from "@galacean/engine-math/src/Color"; import { Font } from "./2d/text/Font"; import { Canvas } from "./Canvas"; @@ -132,6 +132,7 @@ export class Engine extends EventDispatcher { private _frameInProcess: boolean = false; private _waitingDestroy: boolean = false; private _isDeviceLost: boolean = false; + private _waitingGC: boolean = false; private _animate = () => { if (this._vSyncCount) { @@ -234,7 +235,7 @@ export class Engine extends EventDispatcher { this._spriteDefaultMaterial = this._createSpriteMaterial(); this._spriteMaskDefaultMaterial = this._createSpriteMaskMaterial(); this._textDefaultFont = Font.createFromOS(this, "Arial"); - this._textDefaultFont.isGCIgnored = false; + this._textDefaultFont.isGCIgnored = true; this.inputManager = new InputManager(this); @@ -249,6 +250,7 @@ export class Engine extends EventDispatcher { } const magentaMaterial = new Material(this, Shader.find("unlit")); + magentaMaterial.isGCIgnored = true; magentaMaterial.shaderData.setColor("material_BaseColor", new Color(1.0, 0.0, 1.01, 1.0)); this._magentaMaterial = magentaMaterial; @@ -372,6 +374,10 @@ export class Engine extends EventDispatcher { if (this._waitingDestroy) { this._destroy(); } + if (this._waitingGC) { + this._gc(); + this._waitingGC = false; + } this._frameInProcess = false; } @@ -568,12 +574,28 @@ export class Engine extends EventDispatcher { } } + /** + * @internal + */ + _pendingGC() { + if (this._frameInProcess) { + this._waitingGC = true; + } else { + this._gc(); + } + } + /** * @internal */ protected _initialize(configuration: EngineConfiguration): Promise { - const physics = configuration.physics; - const initializePromises: Promise[] = []; + const { shaderLab, physics } = configuration; + + if (shaderLab) { + Shader._shaderLab = shaderLab; + } + + const initializePromises = new Array>(); if (physics) { initializePromises.push( physics.initialize().then(() => { @@ -650,6 +672,14 @@ export class Engine extends EventDispatcher { }); } + private _gc(): void { + this._renderElementPool.garbageCollection(); + this._meshRenderDataPool.garbageCollection(); + this._spriteRenderDataPool.garbageCollection(); + this._spriteMaskRenderDataPool.garbageCollection(); + this._textRenderDataPool.garbageCollection(); + } + /** * @deprecated * The first scene physics manager. @@ -667,4 +697,6 @@ export interface EngineConfiguration { physics?: IPhysics; /** Color space. */ colorSpace?: ColorSpace; + /** Shader lab */ + shaderLab?: IShaderLab; } diff --git a/packages/core/src/Entity.ts b/packages/core/src/Entity.ts index 88224468b7..a777426316 100644 --- a/packages/core/src/Entity.ts +++ b/packages/core/src/Entity.ts @@ -9,6 +9,7 @@ import { Scene } from "./Scene"; import { Script } from "./Script"; import { Transform } from "./Transform"; import { EngineObject } from "./base"; +import { ReferResource } from "./asset/ReferResource"; import { ComponentCloner } from "./clone/ComponentCloner"; import { ActiveChangeFlag } from "./enums/ActiveChangeFlag"; @@ -66,6 +67,8 @@ export class Entity extends EngineObject { _isActive: boolean = true; /** @internal */ _siblingIndex: number = -1; + /** @internal @todo: temporary solution */ + _hookResource: ReferResource; private _parent: Entity = null; private _activeChangedComponents: Component[]; @@ -386,7 +389,12 @@ export class Entity extends EngineObject { */ clone(): Entity { const cloneEntity = new Entity(this._engine, this.name); - + const { _hookResource: hookResource } = this; + if (hookResource) { + cloneEntity._hookResource = hookResource; + hookResource._addReferCount(1); + } + cloneEntity.layer = this.layer; cloneEntity._isActive = this._isActive; cloneEntity.transform.localMatrix = this.transform.localMatrix; @@ -417,6 +425,10 @@ export class Entity extends EngineObject { } super.destroy(); + if (this._hookResource) { + this._hookResource._addReferCount(-1); + this._hookResource = null; + } const components = this._components; for (let i = components.length - 1; i >= 0; i--) { components[i].destroy(); diff --git a/packages/core/src/RenderPipeline/Basic2DBatcher.ts b/packages/core/src/RenderPipeline/Basic2DBatcher.ts index c782eb0008..eb04bf272d 100644 --- a/packages/core/src/RenderPipeline/Basic2DBatcher.ts +++ b/packages/core/src/RenderPipeline/Basic2DBatcher.ts @@ -140,26 +140,28 @@ export abstract class Basic2DBatcher { private _createMesh(engine: Engine, index: number): BufferMesh { const { MAX_VERTEX_COUNT } = Basic2DBatcher; const mesh = new BufferMesh(engine, `BufferMesh${index}`); - + mesh.isGCIgnored = true; const vertexElements: VertexElement[] = []; const vertexStride = this.createVertexElements(vertexElements); // vertices - this._vertexBuffers[index] = new Buffer( + const vertexBuffer = (this._vertexBuffers[index] = new Buffer( engine, BufferBindFlag.VertexBuffer, - MAX_VERTEX_COUNT * 4 * vertexStride, + MAX_VERTEX_COUNT * vertexStride, BufferUsage.Dynamic - ); + )); + vertexBuffer.isGCIgnored = true; // indices - this._indiceBuffers[index] = new Buffer( + const indiceBuffer = (this._indiceBuffers[index] = new Buffer( engine, BufferBindFlag.IndexBuffer, - MAX_VERTEX_COUNT * 2 * 3, + MAX_VERTEX_COUNT * 6, BufferUsage.Dynamic - ); - mesh.setVertexBufferBinding(this._vertexBuffers[index], vertexStride); - mesh.setIndexBufferBinding(this._indiceBuffers[index], IndexFormat.UInt16); + )); + indiceBuffer.isGCIgnored = true; + mesh.setVertexBufferBinding(vertexBuffer, vertexStride); + mesh.setIndexBufferBinding(indiceBuffer, IndexFormat.UInt16); mesh.setVertexElements(vertexElements); return mesh; diff --git a/packages/core/src/RenderPipeline/BasicRenderPipeline.ts b/packages/core/src/RenderPipeline/BasicRenderPipeline.ts index e48cbe6530..fca70c1398 100644 --- a/packages/core/src/RenderPipeline/BasicRenderPipeline.ts +++ b/packages/core/src/RenderPipeline/BasicRenderPipeline.ts @@ -278,7 +278,7 @@ export class BasicRenderPipeline { private _drawBackgroundTexture(engine: Engine, background: Background) { const rhi = engine._hardwareRenderer; - const { _backgroundTextureMaterial, canvas } = engine; + const { _backgroundTextureMaterial: material, canvas } = engine; const mesh = background._mesh; if ( @@ -289,15 +289,13 @@ export class BasicRenderPipeline { background._resizeBackgroundTexture(); } - const program = _backgroundTextureMaterial.shader.subShaders[0].passes[0]._getShaderProgram( - engine, - Shader._compileMacros - ); + const pass = material.shader.subShaders[0].passes[0]; + const program = pass._getShaderProgram(engine, Shader._compileMacros); program.bind(); - program.uploadAll(program.materialUniformBlock, _backgroundTextureMaterial.shaderData); + program.uploadAll(program.materialUniformBlock, material.shaderData); program.uploadUnGroupTextures(); - _backgroundTextureMaterial.renderState._apply(engine, false); + (pass._renderState || material.renderState)._apply(engine, false, pass._renderStateDataMap, material.shaderData); rhi.drawPrimitive(mesh, mesh.subMesh, program); } diff --git a/packages/core/src/RenderPipeline/ClassPool.ts b/packages/core/src/RenderPipeline/ClassPool.ts index de4fda8b01..98e17cde2c 100644 --- a/packages/core/src/RenderPipeline/ClassPool.ts +++ b/packages/core/src/RenderPipeline/ClassPool.ts @@ -1,7 +1,9 @@ +import { IPoolElement } from "./IPoolElement"; + /** * Class pool utils. */ -export class ClassPool { +export class ClassPool { private _elementPoolIndex: number = 0; private _elementPool: T[] = []; private _type: new () => T; @@ -31,4 +33,11 @@ export class ClassPool { resetPool(): void { this._elementPoolIndex = 0; } + + garbageCollection(): void { + const { _elementPool: pool } = this; + for (let i = pool.length - 1; i >= 0; i--) { + pool[i].dispose && pool[i].dispose(); + } + } } diff --git a/packages/core/src/RenderPipeline/IPoolElement.ts b/packages/core/src/RenderPipeline/IPoolElement.ts new file mode 100644 index 0000000000..547ba01e4d --- /dev/null +++ b/packages/core/src/RenderPipeline/IPoolElement.ts @@ -0,0 +1,3 @@ +export interface IPoolElement { + dispose?(): void; +} diff --git a/packages/core/src/RenderPipeline/MeshRenderData.ts b/packages/core/src/RenderPipeline/MeshRenderData.ts index 7737428f08..cfc41cd32c 100644 --- a/packages/core/src/RenderPipeline/MeshRenderData.ts +++ b/packages/core/src/RenderPipeline/MeshRenderData.ts @@ -2,12 +2,13 @@ import { Mesh } from "../graphic/Mesh"; import { SubMesh } from "../graphic/SubMesh"; import { Material } from "../material/Material"; import { Renderer } from "../Renderer"; +import { IPoolElement } from "./IPoolElement"; import { RenderData } from "./RenderData"; /** * Render element. */ -export class MeshRenderData extends RenderData { +export class MeshRenderData extends RenderData implements IPoolElement { /** Mesh. */ mesh: Mesh; /** Sub mesh. */ @@ -20,4 +21,8 @@ export class MeshRenderData extends RenderData { this.mesh = mesh; this.subMesh = subMesh; } + + dispose(): void { + this.component = this.material = this.mesh = this.subMesh = null; + } } diff --git a/packages/core/src/RenderPipeline/RenderElement.ts b/packages/core/src/RenderPipeline/RenderElement.ts index 21ffeecba6..519d7ad73a 100644 --- a/packages/core/src/RenderPipeline/RenderElement.ts +++ b/packages/core/src/RenderPipeline/RenderElement.ts @@ -1,7 +1,8 @@ import { ShaderPass } from "../shader/ShaderPass"; +import { IPoolElement } from "./IPoolElement"; import { RenderData } from "./RenderData"; -export class RenderElement { +export class RenderElement implements IPoolElement { data: RenderData; shaderPasses: ReadonlyArray; @@ -9,4 +10,8 @@ export class RenderElement { this.data = data; this.shaderPasses = shaderPasses; } + + dispose(): void { + this.data = this.shaderPasses = null; + } } diff --git a/packages/core/src/RenderPipeline/RenderQueue.ts b/packages/core/src/RenderPipeline/RenderQueue.ts index 434f61afe2..c8503be8b8 100644 --- a/packages/core/src/RenderPipeline/RenderQueue.ts +++ b/packages/core/src/RenderPipeline/RenderQueue.ts @@ -148,7 +148,12 @@ export class RenderQueue { program.uploadUnGroupTextures(); } } - renderStates[j]._apply(engine, renderer.entity.transform._isFrontFaceInvert()); + renderStates[j]._apply( + engine, + renderer.entity.transform._isFrontFaceInvert(), + shaderPass._renderStateDataMap, + material.shaderData + ); rhi.drawPrimitive(meshData.mesh, meshData.subMesh, program); } diff --git a/packages/core/src/RenderPipeline/SpriteBatcher.ts b/packages/core/src/RenderPipeline/SpriteBatcher.ts index f0db8169e2..ecdc3c054f 100644 --- a/packages/core/src/RenderPipeline/SpriteBatcher.ts +++ b/packages/core/src/RenderPipeline/SpriteBatcher.ts @@ -110,7 +110,8 @@ export class SpriteBatcher extends Basic2DBatcher { compileMacros ); - const program = spriteElement.shaderPasses[0]._getShaderProgram(engine, compileMacros); + const shaderPass = spriteElement.shaderPasses[0]; + const program = shaderPass._getShaderProgram(engine, compileMacros); if (!program.isValid) { return; } @@ -124,7 +125,7 @@ export class SpriteBatcher extends Basic2DBatcher { program.uploadAll(program.rendererUniformBlock, renderer.shaderData); program.uploadAll(program.materialUniformBlock, material.shaderData); - material.renderStates[0]._apply(engine, false); + material.renderState[0]._apply(engine, false, shaderPass._renderStateDataMap, material.shaderData); engine._hardwareRenderer.drawPrimitive(mesh, subMesh, program); maskManager.postRender(renderer); diff --git a/packages/core/src/RenderPipeline/SpriteMaskBatcher.ts b/packages/core/src/RenderPipeline/SpriteMaskBatcher.ts index 1a0cca0544..50b14ba5a3 100644 --- a/packages/core/src/RenderPipeline/SpriteMaskBatcher.ts +++ b/packages/core/src/RenderPipeline/SpriteMaskBatcher.ts @@ -84,7 +84,8 @@ export class SpriteMaskBatcher extends Basic2DBatcher { stencilState.passOperationFront = op; stencilState.passOperationBack = op; - const program = material.shader.subShaders[0].passes[0]._getShaderProgram(engine, compileMacros); + const pass = material.shader.subShaders[0].passes[0]; + const program = pass._getShaderProgram(engine, compileMacros); if (!program.isValid) { return; } @@ -96,7 +97,7 @@ export class SpriteMaskBatcher extends Basic2DBatcher { program.uploadAll(program.rendererUniformBlock, renderer.shaderData); program.uploadAll(program.materialUniformBlock, material.shaderData); - material.renderState._apply(engine, false); + material.renderState._apply(engine, false, pass._renderStateDataMap, material.shaderData); engine._hardwareRenderer.drawPrimitive(mesh, subMesh, program); } diff --git a/packages/core/src/RenderPipeline/SpriteMaskRenderData.ts b/packages/core/src/RenderPipeline/SpriteMaskRenderData.ts index fb01c2ff50..d07d62cef6 100644 --- a/packages/core/src/RenderPipeline/SpriteMaskRenderData.ts +++ b/packages/core/src/RenderPipeline/SpriteMaskRenderData.ts @@ -1,9 +1,10 @@ import { VertexData2D } from "../2d/data/VertexData2D"; import { Material } from "../material/Material"; import { Renderer } from "../Renderer"; +import { IPoolElement } from "./IPoolElement"; import { RenderData } from "./RenderData"; -export class SpriteMaskRenderData extends RenderData { +export class SpriteMaskRenderData extends RenderData implements IPoolElement { isAdd: boolean = true; verticesData: VertexData2D; @@ -17,4 +18,8 @@ export class SpriteMaskRenderData extends RenderData { this.material = material; this.verticesData = verticesData; } + + dispose(): void { + this.component = this.material = this.verticesData = null; + } } diff --git a/packages/core/src/RenderPipeline/SpriteRenderData.ts b/packages/core/src/RenderPipeline/SpriteRenderData.ts index 50f3541285..2cd8984bb3 100644 --- a/packages/core/src/RenderPipeline/SpriteRenderData.ts +++ b/packages/core/src/RenderPipeline/SpriteRenderData.ts @@ -2,9 +2,10 @@ import { VertexData2D } from "../2d/data/VertexData2D"; import { Material } from "../material/Material"; import { Renderer } from "../Renderer"; import { Texture2D } from "../texture"; +import { IPoolElement } from "./IPoolElement"; import { RenderData } from "./RenderData"; -export class SpriteRenderData extends RenderData { +export class SpriteRenderData extends RenderData implements IPoolElement { verticesData: VertexData2D; texture: Texture2D; dataIndex: number; // Add for CanvasRenderer plugin. @@ -28,4 +29,8 @@ export class SpriteRenderData extends RenderData { this.texture = texture; this.dataIndex = dataIndex; } + + dispose(): void { + this.component = this.material = this.verticesData = this.texture = null; + } } diff --git a/packages/core/src/RenderPipeline/TextRenderData.ts b/packages/core/src/RenderPipeline/TextRenderData.ts index 1bb74d8fc7..c02fcfec2f 100644 --- a/packages/core/src/RenderPipeline/TextRenderData.ts +++ b/packages/core/src/RenderPipeline/TextRenderData.ts @@ -1,11 +1,17 @@ +import { IPoolElement } from "./IPoolElement"; import { RenderData } from "./RenderData"; import { SpriteRenderData } from "./SpriteRenderData"; -export class TextRenderData extends RenderData { +export class TextRenderData extends RenderData implements IPoolElement { charsData: SpriteRenderData[] = []; constructor() { super(); this.multiRenderData = true; } + + dispose(): void { + this.component = this.material = null; + this.charsData.length = 0; + } } diff --git a/packages/core/src/Renderer.ts b/packages/core/src/Renderer.ts index e263183bd2..c95dd2e8ba 100644 --- a/packages/core/src/Renderer.ts +++ b/packages/core/src/Renderer.ts @@ -30,10 +30,6 @@ export class Renderer extends Component implements ICustomClone { private static _normalMatrixProperty = ShaderProperty.getByName("renderer_NormalMat"); private static _rendererLayerProperty = ShaderProperty.getByName("renderer_Layer"); - /** ShaderData related to renderer. */ - @deepClone - readonly shaderData: ShaderData = new ShaderData(ShaderDataGroup.Renderer); - /** @internal */ @ignoreClone _distanceForSort: number; @@ -59,6 +55,8 @@ export class Renderer extends Component implements ICustomClone { @ignoreClone protected _dirtyUpdateFlag: number = 0; + @deepClone + private _shaderData: ShaderData = new ShaderData(ShaderDataGroup.Renderer); @ignoreClone private _mvMatrix: Matrix = new Matrix(); @ignoreClone @@ -77,6 +75,13 @@ export class Renderer extends Component implements ICustomClone { @ignoreClone protected _rendererLayer: Vector4 = new Vector4(); + /** + * ShaderData related to renderer. + */ + get shaderData(): ShaderData { + return this._shaderData; + } + /** * Whether it is culled in the current frame and does not participate in rendering. */ @@ -352,6 +357,18 @@ export class Renderer extends Component implements ICustomClone { for (let i = 0, n = materials.length; i < n; i++) { materials[i]?._addReferCount(-1); } + + this._entity = null; + this._globalShaderMacro = null; + this._bounds = null; + this._materials = null; + this._shaderData = null; + this._mvMatrix = null; + this._mvpMatrix = null; + this._mvInvMatrix = null; + this._normalMatrix = null; + this._materialsInstanced = null; + this._rendererLayer = null; } /** diff --git a/packages/core/src/Scene.ts b/packages/core/src/Scene.ts index b8bac9477f..c05dc1ee59 100644 --- a/packages/core/src/Scene.ts +++ b/packages/core/src/Scene.ts @@ -33,10 +33,6 @@ export class Scene extends EngineObject { /** Physics. */ readonly physics: PhysicsScene = new PhysicsScene(this); - /** The background of the scene. */ - readonly background: Background = new Background(this._engine); - /** Scene-related shader data. */ - readonly shaderData: ShaderData = new ShaderData(ShaderDataGroup.Scene); /** If cast shadows. */ castShadows: boolean = true; @@ -66,6 +62,8 @@ export class Scene extends EngineObject { /** @internal */ _sunLight: Light; + private _background: Background = new Background(this._engine); + private _shaderData: ShaderData = new ShaderData(ShaderDataGroup.Scene); private _shadowCascades: ShadowCascadesMode = ShadowCascadesMode.NoCascades; private _ambientLight: AmbientLight; private _fogMode: FogMode = FogMode.None; @@ -75,6 +73,20 @@ export class Scene extends EngineObject { private _fogDensity: number = 0.01; private _fogParams: Vector4 = new Vector4(); + /** + * Scene-related shader data. + */ + get shaderData(): ShaderData { + return this._shaderData; + } + + /** + * The background of the scene. + */ + get background(): Background { + return this._background; + } + /** * Number of cascades to use for directional light shadows. */ @@ -209,7 +221,7 @@ export class Scene extends EngineObject { const shaderData = this.shaderData; shaderData._addReferCount(1); - this.ambientLight = new AmbientLight(); + this.ambientLight = new AmbientLight(engine); engine.sceneManager._allCreatedScenes.push(this); shaderData.enableMacro("SCENE_FOG_MODE", this._fogMode.toString()); @@ -417,13 +429,16 @@ export class Scene extends EngineObject { engine.time._updateSceneShaderData(shaderData); lightManager._updateShaderData(this.shaderData); + lightManager._updateSunLightIndex(); + + if (lightManager._directLights.length > 0) { + const sunlight = lightManager._directLights.get(0); - const sunLightIndex = lightManager._getSunLightIndex(); - if (sunLightIndex !== -1) { - const sunlight = lightManager._directLights.get(sunLightIndex); shaderData.setColor(Scene._sunlightColorProperty, sunlight._getLightIntensityColor()); shaderData.setVector3(Scene._sunlightDirectionProperty, sunlight.direction); this._sunLight = sunlight; + } else { + this._sunLight = null; } if (this.castShadows && this._sunLight && this._sunLight.shadowType !== ShadowType.None) { @@ -467,6 +482,8 @@ export class Scene extends EngineObject { this._rootEntities[0].destroy(); } this._activeCameras.length = 0; + this.background.destroy(); + this._ambientLight && this._ambientLight._removeFromScene(this); this.shaderData._addReferCount(-1); this._componentsManager.handlingInvalidScripts(); diff --git a/packages/core/src/Utils.ts b/packages/core/src/Utils.ts index 41eb543b13..d9d853c870 100644 --- a/packages/core/src/Utils.ts +++ b/packages/core/src/Utils.ts @@ -124,12 +124,7 @@ export class Utils { return relativeUrl; } - const char0 = relativeUrl.charAt(0); - if (char0 === ".") { - return Utils._formatRelativePath(relativeUrl + relativeUrl); - } - - return baseUrl.substring(0, baseUrl.lastIndexOf("/") + 1) + relativeUrl; + return baseUrl.substring(0, baseUrl.lastIndexOf("/") + 1) + this._formatRelativePath(relativeUrl); } private static _stringToPath(string): string[] { @@ -149,15 +144,17 @@ export class Utils { return result; } - private static _formatRelativePath(value: string): string { - const parts = value.split("/"); - for (let i = 0, n = parts.length; i < n; i++) { - if (parts[i] == "..") { - parts.splice(i - 1, 2); - i -= 2; - } - } - return parts.join("/"); + private static _formatRelativePath(path: string): string { + // For example input is "a/b", "/a/b", "./a/b", "./a/./b", "./a/../a/b", output is "a/b" + return path + .split("/") + .filter(Boolean) + .reduce((acc, cur) => { + if (cur === "..") acc.pop(); + else if (cur !== ".") acc.push(cur); + return acc; + }, []) + .join("/"); } } diff --git a/packages/core/src/animation/AnimationClip.ts b/packages/core/src/animation/AnimationClip.ts index 28f37d84e5..efde87f435 100644 --- a/packages/core/src/animation/AnimationClip.ts +++ b/packages/core/src/animation/AnimationClip.ts @@ -123,7 +123,8 @@ export class AnimationClip extends EngineObject { const targetEntity = entity.findByPath(curveData.relativePath); if (targetEntity) { const curveOwner = curveData._getTempCurveOwner(targetEntity); - curveOwner.evaluateAndApplyValue(curveData.curve, time, 1, false); + const value = curveOwner.evaluateValue(curveData.curve, time, false); + curveOwner.applyValue(value, 1, false); } } } diff --git a/packages/core/src/animation/AnimationClipCurveBinding.ts b/packages/core/src/animation/AnimationClipCurveBinding.ts index 6dfa8341c6..605c79a3fd 100644 --- a/packages/core/src/animation/AnimationClipCurveBinding.ts +++ b/packages/core/src/animation/AnimationClipCurveBinding.ts @@ -31,6 +31,7 @@ export class AnimationClipCurveBinding { const curveType = (this.curve.constructor) as IAnimationCurveCalculator; const owner = new AnimationCurveOwner(entity, this.type, this.property, curveType); curveType._initializeOwner(owner); + owner.saveDefaultValue(); return owner; } @@ -38,8 +39,12 @@ export class AnimationClipCurveBinding { * @internal */ _createCurveLayerOwner(owner: AnimationCurveOwner): AnimationCurveLayerOwner { + const curveType = (this.curve.constructor) as IAnimationCurveCalculator; const layerOwner = new AnimationCurveLayerOwner(); layerOwner.curveOwner = owner; + curveType._initializeLayerOwner(layerOwner); + // If curve.keys.length is 0, updateFinishedState will assign 0 to the target, causing an error, so initialize by assigning defaultValue to finalValue. + layerOwner.initFinalValue(); return layerOwner; } diff --git a/packages/core/src/animation/Animator.ts b/packages/core/src/animation/Animator.ts index da28677f38..651bafed19 100644 --- a/packages/core/src/animation/Animator.ts +++ b/packages/core/src/animation/Animator.ts @@ -3,6 +3,8 @@ import { Component } from "../Component"; import { Entity } from "../Entity"; import { ClassPool } from "../RenderPipeline/ClassPool"; import { Renderer } from "../Renderer"; +import { Script } from "../Script"; +import { Logger } from "../base/Logger"; import { assignmentClone, ignoreClone } from "../clone/CloneManager"; import { AnimatorController } from "./AnimatorController"; import { AnimatorState } from "./AnimatorState"; @@ -33,6 +35,8 @@ export class Animator extends Component { @ignoreClone protected _controllerUpdateFlag: BoolUpdateFlag; + @ignoreClone + protected _updateMark: number = 0; @ignoreClone private _animatorLayersData: AnimatorLayerData[] = []; @@ -82,20 +86,20 @@ export class Animator extends Component { } const stateInfo = this._getAnimatorStateInfo(stateName, layerIndex); - const { state } = stateInfo; + const { state, layerIndex: playLayerIndex } = stateInfo; if (!state) { return; } if (!state.clip) { - console.warn(`The state named ${stateName} has no AnimationClip data.`); + Logger.warn(`The state named ${stateName} has no AnimationClip data.`); return; } - const animatorLayerData = this._getAnimatorLayerData(stateInfo.layerIndex); - const animatorStateData = this._getAnimatorStateData(stateName, state, animatorLayerData); + const animatorLayerData = this._getAnimatorLayerData(playLayerIndex); + const animatorStateData = this._getAnimatorStateData(stateName, state, animatorLayerData, playLayerIndex); - this._preparePlay(animatorLayerData, state, animatorStateData); + this._preparePlay(animatorLayerData, state); animatorLayerData.layerState = LayerState.Playing; animatorLayerData.srcPlayData.reset(state, animatorStateData, state._getDuration() * normalizedTimeOffset); @@ -118,8 +122,8 @@ export class Animator extends Component { this._reset(); } - const { state } = this._getAnimatorStateInfo(stateName, layerIndex); - const { manuallyTransition } = this._getAnimatorLayerData(layerIndex); + const { state, layerIndex: playLayerIndex } = this._getAnimatorStateInfo(stateName, layerIndex); + const { manuallyTransition } = this._getAnimatorLayerData(playLayerIndex); manuallyTransition.duration = normalizedTransitionDuration; manuallyTransition.offset = normalizedTimeOffset; manuallyTransition.destinationState = state; @@ -155,6 +159,9 @@ export class Animator extends Component { } deltaTime *= this.speed; + + this._updateMark++; + for (let i = 0, n = animatorController.layers.length; i < n; i++) { const animatorLayerData = this._getAnimatorLayerData(i); if (animatorLayerData.layerState === LayerState.Standby) { @@ -212,7 +219,7 @@ export class Animator extends Component { const propertyOwners = animationCurveOwners[instanceId]; for (let property in propertyOwners) { const owner = propertyOwners[property]; - owner.hasSavedDefaultValue && owner.revertDefaultValue(); + owner.revertDefaultValue(); } } @@ -247,24 +254,18 @@ export class Animator extends Component { return stateInfo; } - private _saveDefaultValues(stateData: AnimatorStateData): void { - const { curveLayerOwner } = stateData; - for (let i = curveLayerOwner.length - 1; i >= 0; i--) { - curveLayerOwner[i]?.curveOwner.saveDefaultValue(); - } - } - private _getAnimatorStateData( stateName: string, animatorState: AnimatorState, - animatorLayerData: AnimatorLayerData + animatorLayerData: AnimatorLayerData, + layerIndex: number ): AnimatorStateData { const { animatorStateDataMap } = animatorLayerData; let animatorStateData = animatorStateDataMap[stateName]; if (!animatorStateData) { animatorStateData = new AnimatorStateData(); animatorStateDataMap[stateName] = animatorStateData; - this._saveAnimatorStateData(animatorState, animatorStateData, animatorLayerData); + this._saveAnimatorStateData(animatorState, animatorStateData, animatorLayerData, layerIndex); this._saveAnimatorEventHandlers(animatorState, animatorStateData); } return animatorStateData; @@ -273,13 +274,15 @@ export class Animator extends Component { private _saveAnimatorStateData( animatorState: AnimatorState, animatorStateData: AnimatorStateData, - animatorLayerData: AnimatorLayerData + animatorLayerData: AnimatorLayerData, + layerIndex: number ): void { const { entity, _curveOwnerPool: curveOwnerPool } = this; const { curveLayerOwner } = animatorStateData; - const { curveOwnerPool: layerCurveOwnerPool } = animatorLayerData; const { _curveBindings: curves } = animatorState.clip; + const { curveOwnerPool: layerCurveOwnerPool } = animatorLayerData; + for (let i = curves.length - 1; i >= 0; i--) { const curve = curves[i]; const targetEntity = curve.relativePath === "" ? entity : entity.findByPath(curve.relativePath); @@ -298,14 +301,15 @@ export class Animator extends Component { curveLayerOwner[i] = layerOwner; } else { curveLayerOwner[i] = null; - console.warn(`The entity don\'t have the child entity which path is ${curve.relativePath}.`); + Logger.warn(`The entity don\'t have the child entity which path is ${curve.relativePath}.`); } } } private _saveAnimatorEventHandlers(state: AnimatorState, animatorStateData: AnimatorStateData): void { const eventHandlerPool = this._animationEventHandlerPool; - const scripts = this._entity._scripts; + const scripts = []; + this._entity.getComponents(Script, scripts); const scriptCount = scripts.length; const { eventHandlers } = animatorStateData; const { events } = state.clip; @@ -320,7 +324,7 @@ export class Animator extends Component { eventHandler.event = event; handlers.length = 0; for (let j = scriptCount - 1; j >= 0; j--) { - const handler = scripts.get(j)[funcName]; + const handler = scripts[j][funcName]; handler && handlers.push(handler); } eventHandlers.push(eventHandler); @@ -329,7 +333,7 @@ export class Animator extends Component { private _clearCrossData(animatorLayerData: AnimatorLayerData): void { animatorLayerData.crossCurveMark++; - animatorLayerData.crossOwnerLayerDataCollection.length = 0; + animatorLayerData.crossLayerOwnerCollection.length = 0; } private _addCrossOwner( @@ -340,7 +344,7 @@ export class Animator extends Component { ): void { layerOwner.crossSrcCurveIndex = curCurveIndex; layerOwner.crossDestCurveIndex = nextCurveIndex; - animatorLayerData.crossOwnerLayerDataCollection.push(layerOwner); + animatorLayerData.crossLayerOwnerCollection.push(layerOwner); } private _prepareCrossFading(animatorLayerData: AnimatorLayerData): void { @@ -358,11 +362,11 @@ export class Animator extends Component { } private _prepareFixedPoseCrossFading(animatorLayerData: AnimatorLayerData): void { - const { crossOwnerLayerDataCollection } = animatorLayerData; + const { crossLayerOwnerCollection } = animatorLayerData; // Save current cross curve data owner fixed pose. - for (let i = crossOwnerLayerDataCollection.length - 1; i >= 0; i--) { - const layerOwner = crossOwnerLayerDataCollection[i]; + for (let i = crossLayerOwnerCollection.length - 1; i >= 0; i--) { + const layerOwner = crossLayerOwnerCollection[i]; if (!layerOwner) continue; layerOwner.curveOwner.saveFixedPoseValue(); // Reset destCurveIndex When fixed pose crossFading again. @@ -392,7 +396,6 @@ export class Animator extends Component { layerOwner.crossDestCurveIndex = i; } else { const owner = layerOwner.curveOwner; - owner.saveDefaultValue(); saveFixed && owner.saveFixedPoseValue(); layerOwner.crossCurveMark = animatorLayerData.crossCurveMark; this._addCrossOwner(animatorLayerData, layerOwner, -1, i); @@ -412,7 +415,7 @@ export class Animator extends Component { const { srcPlayData, destPlayData, crossFadeTransition: crossFadeTransitionInfo } = layerData; const additive = blendingMode === AnimatorLayerBlendingMode.Additive; firstLayer && (weight = 1.0); - //TODO: 任意情况都应该检查,后面要优化 + //@todo: All situations should be checked, optimizations will follow later. layerData.layerState !== LayerState.FixedCrossFading && this._checkTransition(srcPlayData, crossFadeTransitionInfo, layerIndex); @@ -426,6 +429,9 @@ export class Animator extends Component { case LayerState.CrossFading: this._updateCrossFade(srcPlayData, destPlayData, layerData, layerIndex, weight, deltaTime, additive, aniUpdate); break; + case LayerState.Finished: + this._updateFinishedState(srcPlayData, weight, additive, aniUpdate); + break; } } @@ -444,23 +450,35 @@ export class Animator extends Component { playData.update(this.speed < 0); - if (!aniUpdate) { - return; - } - const { clipTime, playState } = playData; - eventHandlers.length && this._fireAnimationEvents(playData, eventHandlers, lastClipTime, clipTime); + const finished = playState === AnimatorStatePlayState.Finished; - for (let i = curveBindings.length - 1; i >= 0; i--) { - curveLayerOwner[i]?.curveOwner.evaluateAndApplyValue(curveBindings[i].curve, clipTime, weight, additive); + if (aniUpdate || finished) { + for (let i = curveBindings.length - 1; i >= 0; i--) { + const layerOwner = curveLayerOwner[i]; + const owner = layerOwner?.curveOwner; + + if (!owner) continue; + + const curve = curveBindings[i].curve; + if (curve.keys.length) { + this._checkRevertOwner(owner, additive); + + const value = owner.evaluateValue(curve, clipTime, additive); + aniUpdate && owner.applyValue(value, weight, additive); + finished && layerOwner.saveFinalValue(); + } + } } playData.frameTime += state.speed * delta; if (playState === AnimatorStatePlayState.Finished) { - layerData.layerState = LayerState.Standby; + layerData.layerState = LayerState.Finished; } + eventHandlers.length && this._fireAnimationEvents(playData, eventHandlers, lastClipTime, clipTime); + if (lastPlayState === AnimatorStatePlayState.UnStarted) { this._callAnimatorScriptOnEnter(state, layerIndex); } @@ -481,7 +499,7 @@ export class Animator extends Component { additive: boolean, aniUpdate: boolean ) { - const { crossOwnerLayerDataCollection } = layerData; + const { crossLayerOwnerCollection } = layerData; const { _curveBindings: srcCurves } = srcPlayData.state.clip; const { state: srcState, stateData: srcStateData, playState: lastSrcPlayState } = srcPlayData; const { eventHandlers: srcEventHandlers } = srcStateData; @@ -491,24 +509,43 @@ export class Animator extends Component { const { clipTime: lastSrcClipTime } = srcPlayData; const { clipTime: lastDestClipTime } = destPlayData; - let crossWeight = - Math.abs(destPlayData.frameTime) / (destState._getDuration() * layerData.crossFadeTransition.duration); - crossWeight >= 1.0 && (crossWeight = 1.0); + const duration = destState._getDuration() * layerData.crossFadeTransition.duration; + let crossWeight = Math.abs(destPlayData.frameTime) / duration; + (crossWeight >= 1.0 || duration === 0) && (crossWeight = 1.0); srcPlayData.update(this.speed < 0); destPlayData.update(this.speed < 0); - const { playState: srcPlayState } = srcPlayData; - const { playState: destPlayState } = destPlayData; + const { clipTime: srcClipTime, playState: srcPlayState } = srcPlayData; + const { clipTime: destClipTime, playState: destPlayState } = destPlayData; + const finished = destPlayData.playState === AnimatorStatePlayState.Finished; - this._updateCrossFadeData(layerData, crossWeight, delta, false); + if (aniUpdate || finished) { + for (let i = crossLayerOwnerCollection.length - 1; i >= 0; i--) { + const layerOwner = crossLayerOwnerCollection[i]; + const owner = layerOwner?.curveOwner; - if (!aniUpdate) { - return; + if (!owner) continue; + + const srcCurveIndex = layerOwner.crossSrcCurveIndex; + const destCurveIndex = layerOwner.crossDestCurveIndex; + + this._checkRevertOwner(owner, additive); + + const value = owner.evaluateCrossFadeValue( + srcCurveIndex >= 0 ? srcCurves[srcCurveIndex].curve : null, + destCurveIndex >= 0 ? destCurves[destCurveIndex].curve : null, + srcClipTime, + destClipTime, + crossWeight, + additive + ); + aniUpdate && owner.applyValue(value, weight, additive); + finished && layerOwner.saveFinalValue(); + } } - const { clipTime: srcClipTime } = srcPlayData; - const { clipTime: destClipTime } = destPlayData; + this._updateCrossFadeData(layerData, crossWeight, delta, false); srcEventHandlers.length && this._fireAnimationEvents(srcPlayData, srcEventHandlers, lastSrcClipTime, srcClipTime); destEventHandlers.length && @@ -531,44 +568,26 @@ export class Animator extends Component { } else { this._callAnimatorScriptOnUpdate(destState, layerIndex); } - - for (let i = crossOwnerLayerDataCollection.length - 1; i >= 0; i--) { - const layerOwner = crossOwnerLayerDataCollection[i]; - - if (!layerOwner) continue; - - const srcCurveIndex = layerOwner.crossSrcCurveIndex; - const destCurveIndex = layerOwner.crossDestCurveIndex; - layerOwner.curveOwner.crossFadeAndApplyValue( - srcCurveIndex >= 0 ? srcCurves[srcCurveIndex].curve : null, - destCurveIndex >= 0 ? destCurves[destCurveIndex].curve : null, - srcClipTime, - destClipTime, - crossWeight, - weight, - additive - ); - } } private _updateCrossFadeFromPose( destPlayData: AnimatorStatePlayData, layerData: AnimatorLayerData, layerIndex: number, - layerWeight: number, + weight: number, delta: number, additive: boolean, aniUpdate: boolean ) { - const { crossOwnerLayerDataCollection } = layerData; + const { crossLayerOwnerCollection } = layerData; const { state, stateData, playState: lastPlayState } = destPlayData; const { eventHandlers } = stateData; const { _curveBindings: curveBindings } = state.clip; const { clipTime: lastDestClipTime } = destPlayData; - let crossWeight = - Math.abs(destPlayData.frameTime) / (state._getDuration() * layerData.crossFadeTransition.duration); - crossWeight >= 1.0 && (crossWeight = 1.0); + const duration = state._getDuration() * layerData.crossFadeTransition.duration; + let crossWeight = Math.abs(destPlayData.frameTime) / duration; + (crossWeight >= 1.0 || duration === 0) && (crossWeight = 1.0); destPlayData.update(this.speed < 0); @@ -576,12 +595,33 @@ export class Animator extends Component { this._updateCrossFadeData(layerData, crossWeight, delta, true); - if (!aniUpdate) { - return; + const { clipTime: destClipTime } = destPlayData; + const finished = playState === AnimatorStatePlayState.Finished; + + // When the animator is culled (aniUpdate=false), if the play state has finished, the final value needs to be calculated and saved to be applied directly. + if (aniUpdate || finished) { + for (let i = crossLayerOwnerCollection.length - 1; i >= 0; i--) { + const layerOwner = crossLayerOwnerCollection[i]; + const owner = layerOwner?.curveOwner; + + if (!owner) continue; + + const curveIndex = layerOwner.crossDestCurveIndex; + + this._checkRevertOwner(owner, additive); + + const value = layerOwner.curveOwner.crossFadeFromPoseAndApplyValue( + curveIndex >= 0 ? curveBindings[curveIndex].curve : null, + destClipTime, + crossWeight, + additive + ); + aniUpdate && owner.applyValue(value, weight, additive); + finished && layerOwner.saveFinalValue(); + } } - const { clipTime: destClipTime } = destPlayData; - //TODO: srcState 少了最新一段时间的判断 + //@todo: srcState is missing the judgment of the most recent period." eventHandlers.length && this._fireAnimationEvents(destPlayData, eventHandlers, lastDestClipTime, destClipTime); if (lastPlayState === AnimatorStatePlayState.UnStarted) { @@ -592,20 +632,30 @@ export class Animator extends Component { } else { this._callAnimatorScriptOnUpdate(state, layerIndex); } + } + + private _updateFinishedState( + playData: AnimatorStatePlayData, + weight: number, + additive: boolean, + aniUpdate: boolean + ): void { + if (!aniUpdate) { + return; + } - for (let i = crossOwnerLayerDataCollection.length - 1; i >= 0; i--) { - const layerOwner = crossOwnerLayerDataCollection[i]; + const { curveLayerOwner } = playData.stateData; + const { _curveBindings: curveBindings } = playData.state.clip; - if (!layerOwner) continue; + for (let i = curveBindings.length - 1; i >= 0; i--) { + const layerOwner = curveLayerOwner[i]; + const owner = layerOwner?.curveOwner; + + if (!owner) continue; - const curveIndex = layerOwner.crossDestCurveIndex; - layerOwner.curveOwner.crossFadeFromPoseAndApplyValue( - curveIndex >= 0 ? curveBindings[curveIndex].curve : null, - destClipTime, - crossWeight, - layerWeight, - additive - ); + this._checkRevertOwner(owner, additive); + + owner.applyValue(layerOwner.finalValue, weight, additive); } } @@ -614,7 +664,7 @@ export class Animator extends Component { destPlayData.frameTime += destPlayData.state.speed * delta; if (crossWeight === 1.0) { if (destPlayData.playState === AnimatorStatePlayState.Finished) { - layerData.layerState = LayerState.Standby; + layerData.layerState = LayerState.Finished; } else { layerData.layerState = LayerState.Playing; } @@ -625,25 +675,21 @@ export class Animator extends Component { } } - private _preparePlay(layerData: AnimatorLayerData, playState: AnimatorState, playStateData: AnimatorStateData): void { + private _preparePlay(layerData: AnimatorLayerData, playState: AnimatorState): void { if (layerData.layerState === LayerState.Playing) { const srcPlayData = layerData.srcPlayData; if (srcPlayData.state !== playState) { const { curveLayerOwner } = srcPlayData.stateData; for (let i = curveLayerOwner.length - 1; i >= 0; i--) { - const owner = curveLayerOwner[i]?.curveOwner; - owner?.hasSavedDefaultValue && owner.revertDefaultValue(); + curveLayerOwner[i]?.curveOwner.revertDefaultValue(); } - this._saveDefaultValues(playStateData); } } else { - // layerState is CrossFading, FixedCrossFading, Standby - const { crossOwnerLayerDataCollection } = layerData; - for (let i = crossOwnerLayerDataCollection.length - 1; i >= 0; i--) { - const owner = crossOwnerLayerDataCollection[i].curveOwner; - owner.hasSavedDefaultValue && owner.revertDefaultValue(); + // layerState is CrossFading, FixedCrossFading, Standby, Finished + const { crossLayerOwnerCollection } = layerData; + for (let i = crossLayerOwnerCollection.length - 1; i >= 0; i--) { + crossLayerOwnerCollection[i].curveOwner.revertDefaultValue(); } - this._saveDefaultValues(playStateData); } } @@ -666,27 +712,27 @@ export class Animator extends Component { private _crossFadeByTransition(transition: AnimatorStateTransition, layerIndex: number): void { const { name } = transition.destinationState; const stateInfo = this._getAnimatorStateInfo(name, layerIndex); - const { state: crossState } = stateInfo; + const { state: crossState, layerIndex: playLayerIndex } = stateInfo; if (!crossState) { return; } if (!crossState.clip) { - console.warn(`The state named ${name} has no AnimationClip data.`); + Logger.warn(`The state named ${name} has no AnimationClip data.`); return; } - const animatorLayerData = this._getAnimatorLayerData(stateInfo.layerIndex); + const animatorLayerData = this._getAnimatorLayerData(playLayerIndex); const layerState = animatorLayerData.layerState; const { destPlayData } = animatorLayerData; - const animatorStateData = this._getAnimatorStateData(name, crossState, animatorLayerData); + const animatorStateData = this._getAnimatorStateData(name, crossState, animatorLayerData, playLayerIndex); const duration = crossState._getDuration(); const offset = duration * transition.offset; destPlayData.reset(crossState, animatorStateData, offset); switch (layerState) { - // Maybe not play, maybe end. case LayerState.Standby: + case LayerState.Finished: animatorLayerData.layerState = LayerState.FixedCrossFading; this._clearCrossData(animatorLayerData); this._prepareStandbyCrossFading(animatorLayerData); @@ -820,6 +866,13 @@ export class Animator extends Component { } } } + + private _checkRevertOwner(owner: AnimationCurveOwner, additive: boolean): void { + if (additive && owner.updateMark !== this._updateMark) { + owner.revertDefaultValue(); + } + owner.updateMark = this._updateMark; + } } interface IAnimatorStateInfo { diff --git a/packages/core/src/animation/animationCurve/AnimationArrayCurve.ts b/packages/core/src/animation/animationCurve/AnimationArrayCurve.ts index f5092d1e39..b573373744 100644 --- a/packages/core/src/animation/animationCurve/AnimationArrayCurve.ts +++ b/packages/core/src/animation/animationCurve/AnimationArrayCurve.ts @@ -1,4 +1,5 @@ import { StaticInterfaceImplement } from "../../base/StaticInterfaceImplement"; +import { AnimationCurveLayerOwner } from "../internal/AnimationCurveLayerOwner"; import { AnimationCurveOwner } from "../internal/animationCurveOwner/AnimationCurveOwner"; import { Keyframe } from "../Keyframe"; import { AnimationCurve } from "./AnimationCurve"; @@ -24,6 +25,13 @@ export class AnimationArrayCurve extends AnimationCurve { owner.crossEvaluateData.value = []; } + /** + * @internal + */ + static _initializeLayerOwner(owner: AnimationCurveLayerOwner): void { + owner.finalValue = []; + } + /** * @internal */ diff --git a/packages/core/src/animation/animationCurve/AnimationBoolCurve.ts b/packages/core/src/animation/animationCurve/AnimationBoolCurve.ts index 4cf4f5db05..27a470f53a 100644 --- a/packages/core/src/animation/animationCurve/AnimationBoolCurve.ts +++ b/packages/core/src/animation/animationCurve/AnimationBoolCurve.ts @@ -1,4 +1,5 @@ import { StaticInterfaceImplement } from "../../base/StaticInterfaceImplement"; +import { AnimationCurveLayerOwner } from "../internal/AnimationCurveLayerOwner"; import { AnimationCurveOwner } from "../internal/animationCurveOwner/AnimationCurveOwner"; import { Keyframe } from "../Keyframe"; import { AnimationCurve } from "./AnimationCurve"; @@ -23,6 +24,14 @@ export class AnimationBoolCurve extends AnimationCurve { owner.baseEvaluateData.value = false; owner.crossEvaluateData.value = false; } + + /** + * @internal + */ + static _initializeLayerOwner(owner: AnimationCurveLayerOwner): void { + owner.finalValue = false; + } + /** * @internal */ diff --git a/packages/core/src/animation/animationCurve/AnimationColorCurve.ts b/packages/core/src/animation/animationCurve/AnimationColorCurve.ts index 90410fefb5..72a5c177fc 100644 --- a/packages/core/src/animation/animationCurve/AnimationColorCurve.ts +++ b/packages/core/src/animation/animationCurve/AnimationColorCurve.ts @@ -1,5 +1,6 @@ import { Color } from "@galacean/engine-math"; import { StaticInterfaceImplement } from "../../base/StaticInterfaceImplement"; +import { AnimationCurveLayerOwner } from "../internal/AnimationCurveLayerOwner"; import { AnimationCurveOwner } from "../internal/animationCurveOwner/AnimationCurveOwner"; import { Keyframe } from "../Keyframe"; import { AnimationCurve } from "./AnimationCurve"; @@ -25,6 +26,13 @@ export class AnimationColorCurve extends AnimationCurve { owner.crossEvaluateData.value = new Color(); } + /** + * @internal + */ + static _initializeLayerOwner(owner: AnimationCurveLayerOwner): void { + owner.finalValue = new Color(); + } + /** * @internal */ diff --git a/packages/core/src/animation/animationCurve/AnimationCurve.ts b/packages/core/src/animation/animationCurve/AnimationCurve.ts index 320aa56be3..f664a1bb3b 100644 --- a/packages/core/src/animation/animationCurve/AnimationCurve.ts +++ b/packages/core/src/animation/animationCurve/AnimationCurve.ts @@ -146,6 +146,9 @@ export abstract class AnimationCurve { break; } } + + evaluateData.value = value; + return value; } diff --git a/packages/core/src/animation/animationCurve/AnimationFloatArrayCurve.ts b/packages/core/src/animation/animationCurve/AnimationFloatArrayCurve.ts index 1300ef185e..7ee7b8c785 100644 --- a/packages/core/src/animation/animationCurve/AnimationFloatArrayCurve.ts +++ b/packages/core/src/animation/animationCurve/AnimationFloatArrayCurve.ts @@ -1,4 +1,5 @@ import { StaticInterfaceImplement } from "../../base/StaticInterfaceImplement"; +import { AnimationCurveLayerOwner } from "../internal/AnimationCurveLayerOwner"; import { AnimationCurveOwner } from "../internal/animationCurveOwner/AnimationCurveOwner"; import { Keyframe } from "../Keyframe"; import { AnimationCurve } from "./AnimationCurve"; @@ -25,6 +26,14 @@ export class AnimationFloatArrayCurve extends AnimationCurve { owner.crossEvaluateData.value = new Float32Array(size); } + /** + * @internal + */ + static _initializeLayerOwner(owner: AnimationCurveLayerOwner): void { + const size = (owner.curveOwner.referenceTargetValue).length; + owner.finalValue = new Float32Array(size); + } + /** * @internal */ diff --git a/packages/core/src/animation/animationCurve/AnimationFloatCurve.ts b/packages/core/src/animation/animationCurve/AnimationFloatCurve.ts index 7d8e66b7b8..0461d87e00 100644 --- a/packages/core/src/animation/animationCurve/AnimationFloatCurve.ts +++ b/packages/core/src/animation/animationCurve/AnimationFloatCurve.ts @@ -1,4 +1,5 @@ import { StaticInterfaceImplement } from "../../base/StaticInterfaceImplement"; +import { AnimationCurveLayerOwner } from "../internal/AnimationCurveLayerOwner"; import { AnimationCurveOwner } from "../internal/animationCurveOwner/AnimationCurveOwner"; import { Keyframe } from "../Keyframe"; import { AnimationCurve } from "./AnimationCurve"; @@ -24,6 +25,13 @@ export class AnimationFloatCurve extends AnimationCurve { owner.crossEvaluateData.value = 0; } + /** + * @internal + */ + static _initializeLayerOwner(owner: AnimationCurveLayerOwner): void { + owner.finalValue = 0; + } + /** * @internal */ diff --git a/packages/core/src/animation/animationCurve/AnimationQuaternionCurve.ts b/packages/core/src/animation/animationCurve/AnimationQuaternionCurve.ts index 8848e65b08..ed579bf9d3 100644 --- a/packages/core/src/animation/animationCurve/AnimationQuaternionCurve.ts +++ b/packages/core/src/animation/animationCurve/AnimationQuaternionCurve.ts @@ -1,5 +1,6 @@ import { Quaternion } from "@galacean/engine-math"; import { StaticInterfaceImplement } from "../../base/StaticInterfaceImplement"; +import { AnimationCurveLayerOwner } from "../internal/AnimationCurveLayerOwner"; import { AnimationCurveOwner } from "../internal/animationCurveOwner/AnimationCurveOwner"; import { Keyframe } from "../Keyframe"; import { AnimationCurve } from "./AnimationCurve"; @@ -28,6 +29,13 @@ export class AnimationQuaternionCurve extends AnimationCurve { owner.crossEvaluateData.value = new Quaternion(); } + /** + * @internal + */ + static _initializeLayerOwner(owner: AnimationCurveLayerOwner): void { + owner.finalValue = new Quaternion(); + } + /** * @internal */ diff --git a/packages/core/src/animation/animationCurve/AnimationVector2Curve.ts b/packages/core/src/animation/animationCurve/AnimationVector2Curve.ts index 49e344b33d..eb51dda7ff 100644 --- a/packages/core/src/animation/animationCurve/AnimationVector2Curve.ts +++ b/packages/core/src/animation/animationCurve/AnimationVector2Curve.ts @@ -1,5 +1,6 @@ import { Vector2 } from "@galacean/engine-math"; import { StaticInterfaceImplement } from "../../base/StaticInterfaceImplement"; +import { AnimationCurveLayerOwner } from "../internal/AnimationCurveLayerOwner"; import { AnimationCurveOwner } from "../internal/animationCurveOwner/AnimationCurveOwner"; import { Keyframe } from "../Keyframe"; import { AnimationCurve } from "./AnimationCurve"; @@ -25,6 +26,13 @@ export class AnimationVector2Curve extends AnimationCurve { owner.crossEvaluateData.value = new Vector2(); } + /** + * @internal + */ + static _initializeLayerOwner(owner: AnimationCurveLayerOwner): void { + owner.finalValue = new Vector2(); + } + /** * @internal */ diff --git a/packages/core/src/animation/animationCurve/AnimationVector3Curve.ts b/packages/core/src/animation/animationCurve/AnimationVector3Curve.ts index 77d43ae70e..e1f3eb3f57 100644 --- a/packages/core/src/animation/animationCurve/AnimationVector3Curve.ts +++ b/packages/core/src/animation/animationCurve/AnimationVector3Curve.ts @@ -1,5 +1,6 @@ import { Vector3 } from "@galacean/engine-math"; import { StaticInterfaceImplement } from "../../base/StaticInterfaceImplement"; +import { AnimationCurveLayerOwner } from "../internal/AnimationCurveLayerOwner"; import { AnimationCurveOwner } from "../internal/animationCurveOwner/AnimationCurveOwner"; import { Keyframe } from "../Keyframe"; import { AnimationCurve } from "./AnimationCurve"; @@ -25,6 +26,13 @@ export class AnimationVector3Curve extends AnimationCurve { owner.crossEvaluateData.value = new Vector3(); } + /** + * @internal + */ + static _initializeLayerOwner(owner: AnimationCurveLayerOwner): void { + owner.finalValue = new Vector3(); + } + /** * @internal */ diff --git a/packages/core/src/animation/animationCurve/AnimationVector4Curve.ts b/packages/core/src/animation/animationCurve/AnimationVector4Curve.ts index 94f7159bfd..e1f70e916d 100644 --- a/packages/core/src/animation/animationCurve/AnimationVector4Curve.ts +++ b/packages/core/src/animation/animationCurve/AnimationVector4Curve.ts @@ -1,5 +1,6 @@ import { Vector4 } from "@galacean/engine-math"; import { StaticInterfaceImplement } from "../../base/StaticInterfaceImplement"; +import { AnimationCurveLayerOwner } from "../internal/AnimationCurveLayerOwner"; import { AnimationCurveOwner } from "../internal/animationCurveOwner/AnimationCurveOwner"; import { Keyframe } from "../Keyframe"; import { AnimationCurve } from "./AnimationCurve"; @@ -25,6 +26,13 @@ export class AnimationVector4Curve extends AnimationCurve { owner.crossEvaluateData.value = new Vector4(); } + /** + * @internal + */ + static _initializeLayerOwner(owner: AnimationCurveLayerOwner): void { + owner.finalValue = new Vector4(); + } + /** * @internal */ diff --git a/packages/core/src/animation/animationCurve/interfaces/IAnimationCurveCalculator.ts b/packages/core/src/animation/animationCurve/interfaces/IAnimationCurveCalculator.ts index bb3509c735..dea6aadc0d 100644 --- a/packages/core/src/animation/animationCurve/interfaces/IAnimationCurveCalculator.ts +++ b/packages/core/src/animation/animationCurve/interfaces/IAnimationCurveCalculator.ts @@ -1,3 +1,4 @@ +import { AnimationCurveLayerOwner } from "../../internal/AnimationCurveLayerOwner"; import { AnimationCurveOwner } from "../../internal/animationCurveOwner/AnimationCurveOwner"; import { Keyframe, KeyframeValueType } from "../../Keyframe"; @@ -9,6 +10,7 @@ export interface IAnimationCurveCalculator { _isInterpolationType: boolean; _initializeOwner(owner: AnimationCurveOwner); + _initializeLayerOwner(owner: AnimationCurveLayerOwner); _lerpValue(src: V, dest: V, weight: number, out?: V): V; _additiveValue(additive: V, weight: number, srcOut: V): V; diff --git a/packages/core/src/animation/enums/LayerState.ts b/packages/core/src/animation/enums/LayerState.ts index 163cf660db..e121375893 100644 --- a/packages/core/src/animation/enums/LayerState.ts +++ b/packages/core/src/animation/enums/LayerState.ts @@ -3,11 +3,13 @@ */ export enum LayerState { /** Standby state. */ - Standby, //CM: Standby 优化 + Standby, /** Playing state. */ Playing, /** CrossFading state. */ CrossFading, /** FixedCrossFading state. */ - FixedCrossFading + FixedCrossFading, + /** Finished state. */ + Finished } diff --git a/packages/core/src/animation/internal/AnimationCurveLayerOwner.ts b/packages/core/src/animation/internal/AnimationCurveLayerOwner.ts index 471cb85c09..45a1190da4 100644 --- a/packages/core/src/animation/internal/AnimationCurveLayerOwner.ts +++ b/packages/core/src/animation/internal/AnimationCurveLayerOwner.ts @@ -9,4 +9,19 @@ export class AnimationCurveLayerOwner { crossDestCurveIndex: number; crossCurveMark: number = 0; curveOwner: AnimationCurveOwner; + finalValue: KeyframeValueType; + + initFinalValue() { + const { cureType, defaultValue } = this.curveOwner; + + if (cureType._isReferenceType) { + cureType._copyValue(defaultValue, this.finalValue); + } else { + this.finalValue = defaultValue; + } + } + + saveFinalValue(): void { + this.finalValue = this.curveOwner.getEvaluateValue(this.finalValue); + } } diff --git a/packages/core/src/animation/internal/AnimationEventHandler.ts b/packages/core/src/animation/internal/AnimationEventHandler.ts index 2d2e6c7ea3..53d1cf267e 100644 --- a/packages/core/src/animation/internal/AnimationEventHandler.ts +++ b/packages/core/src/animation/internal/AnimationEventHandler.ts @@ -1,8 +1,11 @@ +import { IPoolElement } from "../../RenderPipeline/IPoolElement"; import { AnimationEvent } from "../AnimationEvent"; /** * @internal */ -export class AnimationEventHandler { +export class AnimationEventHandler implements IPoolElement { event: AnimationEvent; handlers: Function[] = []; + + dispose() {} } diff --git a/packages/core/src/animation/internal/AnimatorLayerData.ts b/packages/core/src/animation/internal/AnimatorLayerData.ts index d89f73f542..9ea1f21564 100644 --- a/packages/core/src/animation/internal/AnimatorLayerData.ts +++ b/packages/core/src/animation/internal/AnimatorLayerData.ts @@ -17,7 +17,7 @@ export class AnimatorLayerData { crossCurveMark: number = 0; manuallyTransition: AnimatorStateTransition = new AnimatorStateTransition(); crossFadeTransition: AnimatorStateTransition; - crossOwnerLayerDataCollection: AnimationCurveLayerOwner[] = []; + crossLayerOwnerCollection: AnimationCurveLayerOwner[] = []; switchPlayData(): void { const srcPlayData = this.destPlayData; diff --git a/packages/core/src/animation/internal/animationCurveOwner/AnimationCurveOwner.ts b/packages/core/src/animation/internal/animationCurveOwner/AnimationCurveOwner.ts index 415cb39f8e..a1cea575f6 100644 --- a/packages/core/src/animation/internal/animationCurveOwner/AnimationCurveOwner.ts +++ b/packages/core/src/animation/internal/animationCurveOwner/AnimationCurveOwner.ts @@ -34,15 +34,13 @@ export class AnimationCurveOwner { defaultValue: V; fixedPoseValue: V; - hasSavedDefaultValue: boolean = false; baseEvaluateData: IEvaluateData = { curKeyframeIndex: 0, value: null }; - crossEvaluateData: IEvaluateData = { curKeyframeIndex: 0, value: null }; - referenceTargetValue: V; + cureType: IAnimationCurveCalculator; + updateMark: number = 0; private _assembler: IAnimationCurveOwnerAssembler; - private _cureType: IAnimationCurveCalculator; constructor( target: Entity, @@ -54,7 +52,7 @@ export class AnimationCurveOwner { this.type = type; this.property = property; this.component = target.getComponent(type); - this._cureType = cureType; + this.cureType = cureType; const assemblerType = AnimationCurveOwner.getAssemblerType(type, property); this._assembler = >new assemblerType(); @@ -65,44 +63,27 @@ export class AnimationCurveOwner { } } - evaluateAndApplyValue(curve: AnimationCurve, time: number, layerWeight: number, additive: boolean): void { - if (curve.keys.length) { - if (additive) { - const value = curve._evaluateAdditive(time, this.baseEvaluateData); - - const cureType = this._cureType; - if (cureType._isReferenceType) { - cureType._additiveValue(value, layerWeight, this.referenceTargetValue); - } else { - const assembler = this._assembler; - const originValue = assembler.getTargetValue(); - const additiveValue = cureType._additiveValue(value, layerWeight, originValue); - assembler.setTargetValue(additiveValue); - } - } else { - const value = curve._evaluate(time, this.baseEvaluateData); - - this._applyValue(value, layerWeight); - } - } + evaluateValue(curve: AnimationCurve, time: number, additive: boolean): KeyframeValueType { + return additive + ? curve._evaluateAdditive(time, this.baseEvaluateData) + : curve._evaluate(time, this.baseEvaluateData); } - crossFadeAndApplyValue( + evaluateCrossFadeValue( srcCurve: AnimationCurve, destCurve: AnimationCurve, srcTime: number, destTime: number, crossWeight: number, - layerWeight: number, additive: boolean - ): void { + ): KeyframeValueType { const srcValue = srcCurve && srcCurve.keys.length ? additive ? srcCurve._evaluateAdditive(srcTime, this.baseEvaluateData) : srcCurve._evaluate(srcTime, this.baseEvaluateData) : additive - ? this._cureType._getZeroValue(this.baseEvaluateData.value) + ? this.cureType._getZeroValue(this.baseEvaluateData.value) : this.defaultValue; const destValue = @@ -111,21 +92,20 @@ export class AnimationCurveOwner { ? destCurve._evaluateAdditive(destTime, this.crossEvaluateData) : destCurve._evaluate(destTime, this.crossEvaluateData) : additive - ? this._cureType._getZeroValue(this.crossEvaluateData.value) + ? this.cureType._getZeroValue(this.crossEvaluateData.value) : this.defaultValue; - this._applyCrossValue(srcValue, destValue, crossWeight, layerWeight, additive); + return this._lerpValue(srcValue, destValue, crossWeight); } crossFadeFromPoseAndApplyValue( destCurve: AnimationCurve, destTime: number, crossWeight: number, - layerWeight: number, additive: boolean - ): void { + ): KeyframeValueType { const srcValue = additive - ? this._cureType._subtractValue(this.fixedPoseValue, this.defaultValue, this.baseEvaluateData.value) + ? this.cureType._subtractValue(this.fixedPoseValue, this.defaultValue, this.baseEvaluateData.value) : this.fixedPoseValue; const destValue = destCurve && destCurve.keys.length @@ -133,77 +113,78 @@ export class AnimationCurveOwner { ? destCurve._evaluateAdditive(destTime, this.crossEvaluateData) : destCurve._evaluate(destTime, this.crossEvaluateData) : additive - ? this._cureType._getZeroValue(this.crossEvaluateData.value) + ? this.cureType._getZeroValue(this.crossEvaluateData.value) : this.defaultValue; - this._applyCrossValue(srcValue, destValue, crossWeight, layerWeight, additive); + return this._lerpValue(srcValue, destValue, crossWeight); } revertDefaultValue(): void { this._assembler.setTargetValue(this.defaultValue); } + getEvaluateValue(out: V): V { + if (this.cureType._isReferenceType) { + this.cureType._copyValue(this.baseEvaluateData.value, out); + return out; + } else { + return this.baseEvaluateData.value; + } + } + saveDefaultValue(): void { - if (this._cureType._isReferenceType) { - this._cureType._copyValue(this.referenceTargetValue, this.defaultValue); + if (this.cureType._isReferenceType) { + this.cureType._copyValue(this.referenceTargetValue, this.defaultValue); } else { this.defaultValue = this._assembler.getTargetValue(); } - this.hasSavedDefaultValue = true; } saveFixedPoseValue(): void { - if (this._cureType._isReferenceType) { - this._cureType._copyValue(this.referenceTargetValue, this.fixedPoseValue); + if (this.cureType._isReferenceType) { + this.cureType._copyValue(this.referenceTargetValue, this.fixedPoseValue); } else { this.fixedPoseValue = this._assembler.getTargetValue(); } } - private _applyValue(value: V, weight: number): void { - if (weight === 1.0) { - if (this._cureType._isReferenceType) { - this._cureType._copyValue(value, this.referenceTargetValue); + applyValue(value: V, weight: number, additive: boolean): void { + const cureType = this.cureType; + if (additive) { + if (cureType._isReferenceType) { + cureType._additiveValue(value, weight, this.referenceTargetValue); } else { - this._assembler.setTargetValue(value); + const assembler = this._assembler; + const originValue = assembler.getTargetValue(); + const additiveValue = cureType._additiveValue(value, weight, originValue); + assembler.setTargetValue(additiveValue); } } else { - if (this._cureType._isReferenceType) { - const targetValue = this.referenceTargetValue; - this._cureType._lerpValue(targetValue, value, weight, targetValue); + if (weight === 1.0) { + if (cureType._isReferenceType) { + cureType._copyValue(value, this.referenceTargetValue); + } else { + this._assembler.setTargetValue(value); + } } else { - const originValue = this._assembler.getTargetValue(); - const lerpValue = this._cureType._lerpValue(originValue, value, weight); - this._assembler.setTargetValue(lerpValue); + if (cureType._isReferenceType) { + const targetValue = this.referenceTargetValue; + cureType._lerpValue(targetValue, value, weight, targetValue); + } else { + const originValue = this._assembler.getTargetValue(); + const lerpValue = cureType._lerpValue(originValue, value, weight); + this._assembler.setTargetValue(lerpValue); + } } } } - private _applyCrossValue( - srcValue: V, - destValue: V, - crossWeight: number, - layerWeight: number, - additive: boolean - ): void { - let out: V; - if (this._cureType._isReferenceType) { - out = this.baseEvaluateData.value; - this._cureType._lerpValue(srcValue, destValue, crossWeight, out); - } else { - out = this._cureType._lerpValue(srcValue, destValue, crossWeight); - } - - if (additive) { - if (this._cureType._isReferenceType) { - this._cureType._additiveValue(out, layerWeight, this.referenceTargetValue); - } else { - const originValue = this._assembler.getTargetValue(); - const lerpValue = this._cureType._additiveValue(out, layerWeight, originValue); - this._assembler.setTargetValue(lerpValue); - } + private _lerpValue(srcValue: V, destValue: V, crossWeight: number): KeyframeValueType { + if (this.cureType._isReferenceType) { + return this.cureType._lerpValue(srcValue, destValue, crossWeight, this.baseEvaluateData.value); } else { - this._applyValue(out, layerWeight); + this.baseEvaluateData.value = this.cureType._lerpValue(srcValue, destValue, crossWeight); + return this.baseEvaluateData.value; } } } diff --git a/packages/core/src/asset/ResourceManager.ts b/packages/core/src/asset/ResourceManager.ts index 95f352198f..5bb29bbcaf 100644 --- a/packages/core/src/asset/ResourceManager.ts +++ b/packages/core/src/asset/ResourceManager.ts @@ -148,6 +148,7 @@ export class ResourceManager { */ gc(): void { this._gc(false); + this.engine._pendingGC(); } /** diff --git a/packages/core/src/base/EventDispatcher.ts b/packages/core/src/base/EventDispatcher.ts index aa1e0892c4..1c5399bcfc 100644 --- a/packages/core/src/base/EventDispatcher.ts +++ b/packages/core/src/base/EventDispatcher.ts @@ -2,9 +2,10 @@ * EventDispatcher, which can be inherited as a base class. */ export class EventDispatcher { + private static _dispatchingListenersPool: EventData[][] = []; + private _events: Record = Object.create(null); private _eventCount: number = 0; - private _dispatchingListeners: EventData[] = []; /** * Determine whether there is event listening. @@ -54,7 +55,8 @@ export class EventDispatcher { const count = listeners.length; // cloning list to avoid structure breaking - const dispatchingListeners = this._dispatchingListeners; + const { _dispatchingListenersPool: pool } = EventDispatcher; + const dispatchingListeners = pool.length > 0 ? pool.pop() : []; dispatchingListeners.length = count; for (let i = 0; i < count; i++) { dispatchingListeners[i] = listeners[i]; @@ -70,6 +72,7 @@ export class EventDispatcher { // remove hooked function to avoid gc problem dispatchingListeners.length = 0; + pool.push(dispatchingListeners); } else { if (listeners.once) this.off(event, listeners.fn); listeners.fn(data); diff --git a/packages/core/src/graphic/Mesh.ts b/packages/core/src/graphic/Mesh.ts index 59e458a622..784d041fd1 100644 --- a/packages/core/src/graphic/Mesh.ts +++ b/packages/core/src/graphic/Mesh.ts @@ -219,10 +219,10 @@ export abstract class Mesh extends GraphicsResource { * @internal */ _setVertexBufferBinding(index: number, binding: VertexBufferBinding): void { - if (this._getReferCount() > 0) { - const lastBinding = this._vertexBufferBindings[index]; - lastBinding && lastBinding.buffer._addReferCount(-1); - binding.buffer._addReferCount(1); + const referCount = this._getReferCount(); + if (referCount > 0) { + this._vertexBufferBindings[index]?.buffer._addReferCount(-referCount); + binding?.buffer._addReferCount(referCount); } this._vertexBufferBindings[index] = binding; this._bufferStructChanged = true; @@ -242,6 +242,7 @@ export abstract class Mesh extends GraphicsResource { for (let i = 0, n = vertexBufferBindings.length; i < n; i++) { vertexBufferBindings[i]?.buffer._addReferCount(value); } + this._indexBufferBinding?._buffer._addReferCount(value); } override _rebuild(): void { @@ -275,6 +276,11 @@ export abstract class Mesh extends GraphicsResource { */ protected _setIndexBufferBinding(binding: IndexBufferBinding | null): void { const lastBinding = this._indexBufferBinding; + const referCount = this._getReferCount(); + if (referCount > 0) { + lastBinding?.buffer._addReferCount(-referCount); + binding?.buffer._addReferCount(referCount); + } if (binding) { this._indexBufferBinding = binding; this._glIndexType = BufferUtil._getGLIndexType(binding.format); diff --git a/packages/core/src/graphic/SubMesh.ts b/packages/core/src/graphic/SubMesh.ts index c031e25593..edb0f7f68a 100644 --- a/packages/core/src/graphic/SubMesh.ts +++ b/packages/core/src/graphic/SubMesh.ts @@ -1,9 +1,10 @@ +import { IPoolElement } from "../RenderPipeline/IPoolElement"; import { MeshTopology } from "./enums/MeshTopology"; /** * Sub-mesh, mainly contains drawing information. */ -export class SubMesh { +export class SubMesh implements IPoolElement { /** Start drawing offset. */ start: number; /** Drawing count. */ @@ -22,4 +23,6 @@ export class SubMesh { this.count = count; this.topology = topology; } + + dispose?(): void {} } diff --git a/packages/core/src/lighting/AmbientLight.ts b/packages/core/src/lighting/AmbientLight.ts index 8e74ad3472..89987fc2e3 100644 --- a/packages/core/src/lighting/AmbientLight.ts +++ b/packages/core/src/lighting/AmbientLight.ts @@ -5,11 +5,13 @@ import { ShaderMacro } from "../shader/ShaderMacro"; import { ShaderProperty } from "../shader/ShaderProperty"; import { TextureCube } from "../texture"; import { DiffuseMode } from "./enums/DiffuseMode"; +import { ReferResource } from "../asset/ReferResource"; +import { Engine } from "../Engine"; /** * Ambient light. */ -export class AmbientLight { +export class AmbientLight extends ReferResource { private static _shMacro: ShaderMacro = ShaderMacro.getByName("SCENE_USE_SH"); private static _specularMacro: ShaderMacro = ShaderMacro.getByName("SCENE_USE_SPECULAR_ENV"); private static _decodeRGBMMacro: ShaderMacro = ShaderMacro.getByName("SCENE_IS_DECODE_ENV_RGBM"); @@ -151,8 +153,8 @@ export class AmbientLight { * @internal */ _addToScene(scene: Scene): void { + this._addReferCount(1); this._scenes.push(scene); - const shaderData = scene.shaderData; shaderData.setColor(AmbientLight._diffuseColorProperty, this._diffuseSolidColor); shaderData.setFloat(AmbientLight._diffuseIntensityProperty, this._diffuseIntensity); @@ -168,9 +170,17 @@ export class AmbientLight { * @internal */ _removeFromScene(scene: Scene): void { + this._addReferCount(-1); const scenes = this._scenes; const index = scenes.indexOf(scene); scenes.splice(index, 1); + const shaderData = scene.shaderData; + shaderData.setTexture(AmbientLight._specularTextureProperty, null); + shaderData.disableMacro(AmbientLight._specularMacro); + } + + constructor(engine: Engine) { + super(engine); } private _setDiffuseMode(sceneShaderData: ShaderData): void { diff --git a/packages/core/src/lighting/LightManager.ts b/packages/core/src/lighting/LightManager.ts index ddcf94aa24..ea74efa3d3 100644 --- a/packages/core/src/lighting/LightManager.ts +++ b/packages/core/src/lighting/LightManager.ts @@ -70,32 +70,19 @@ export class LightManager { /** * @internal */ - _getSunLightIndex(): number { + _updateSunLightIndex(): void { const directLights = this._directLights; - - let sunLightIndex = -1; - let maxIntensity = Number.NEGATIVE_INFINITY; - let hasShadowLight = false; - for (let i = 0, n = directLights.length; i < n; i++) { - const directLight = directLights.get(i); - if (directLight.shadowType !== ShadowType.None && !hasShadowLight) { - maxIntensity = Number.NEGATIVE_INFINITY; - hasShadowLight = true; - } - const intensity = directLight.intensity * directLight.color.getBrightness(); - if (hasShadowLight) { - if (directLight.shadowType !== ShadowType.None && maxIntensity < intensity) { - maxIntensity = intensity; - sunLightIndex = i; - } - } else { - if (maxIntensity < intensity) { - maxIntensity = intensity; - sunLightIndex = i; - } - } + const index = this._getSunLightIndex(); + // -1 means no sun light, 0 means the first direct light already is sun light + if (index > 0) { + const firstLight = directLights.get(0); + const sunLight = directLights.get(index); + directLights.set(0, sunLight); + directLights.set(index, firstLight); + + sunLight._lightIndex = 0; + firstLight._lightIndex = index; } - return sunLightIndex; } /** @@ -145,4 +132,41 @@ export class LightManager { shaderData.disableMacro("SCENE_SPOT_LIGHT_COUNT"); } } + + /** + * @internal + */ + _gc() { + this._spotLights.garbageCollection(); + this._pointLights.garbageCollection(); + this._directLights.garbageCollection(); + } + + private _getSunLightIndex(): number { + const directLights = this._directLights; + + let sunLightIndex = -1; + let maxIntensity = Number.NEGATIVE_INFINITY; + let hasShadowLight = false; + for (let i = 0, n = directLights.length; i < n; i++) { + const directLight = directLights.get(i); + if (directLight.shadowType !== ShadowType.None && !hasShadowLight) { + maxIntensity = Number.NEGATIVE_INFINITY; + hasShadowLight = true; + } + const intensity = directLight.intensity * directLight.color.getBrightness(); + if (hasShadowLight) { + if (directLight.shadowType !== ShadowType.None && maxIntensity < intensity) { + maxIntensity = intensity; + sunLightIndex = i; + } + } else { + if (maxIntensity < intensity) { + maxIntensity = intensity; + sunLightIndex = i; + } + } + } + return sunLightIndex; + } } diff --git a/packages/core/src/material/Material.ts b/packages/core/src/material/Material.ts index 01f1031196..aa8b203a5c 100644 --- a/packages/core/src/material/Material.ts +++ b/packages/core/src/material/Material.ts @@ -13,14 +13,21 @@ import { RenderState } from "../shader/state/RenderState"; export class Material extends ReferResource implements IClone { /** Name. */ name: string; - /** Shader data. */ - readonly shaderData: ShaderData = new ShaderData(ShaderDataGroup.Material); /** @internal */ _shader: Shader; /** @internal */ _renderStates: RenderState[] = []; // todo: later will as a part of shaderData when shader effect frame is OK, that is more powerful and flexible. + private _shaderData: ShaderData = new ShaderData(ShaderDataGroup.Material); + + /** + * Shader data. + */ + get shaderData(): ShaderData { + return this._shaderData; + } + /** * Shader used by the material. */ @@ -96,4 +103,15 @@ export class Material extends ReferResource implements IClone { super._addReferCount(value); this.shaderData._addReferCount(value); } + + /** + * @override + */ + protected override _onDestroy(): void { + super._onDestroy(); + this._shader = null; + this._shaderData = null; + this._renderStates.length = 0; + this._renderStates = null; + } } diff --git a/packages/core/src/mesh/MeshRenderer.ts b/packages/core/src/mesh/MeshRenderer.ts index 97def29b92..de2ab5363f 100644 --- a/packages/core/src/mesh/MeshRenderer.ts +++ b/packages/core/src/mesh/MeshRenderer.ts @@ -64,13 +64,24 @@ export class MeshRenderer extends Renderer { protected override _onDestroy(): void { super._onDestroy(); const mesh = this._mesh; - if (mesh && !mesh.destroyed) { - mesh._addReferCount(-1); + if (mesh) { + mesh.destroyed || mesh._addReferCount(-1); mesh._updateFlagManager.removeListener(this._onMeshChanged); this._mesh = null; } } + /** + * @internal + */ + override _prepareRender(context: RenderContext): void { + if (!this._mesh) { + Logger.error("mesh is null."); + return; + } + super._prepareRender(context); + } + /** * @internal */ @@ -99,52 +110,48 @@ export class MeshRenderer extends Renderer { */ protected override _render(context: RenderContext): void { const mesh = this._mesh; - if (mesh) { - if (this._dirtyUpdateFlag & MeshRendererUpdateFlags.VertexElementMacro) { - const shaderData = this.shaderData; - const vertexElements = mesh._vertexElements; - - shaderData.disableMacro(MeshRenderer._uvMacro); - shaderData.disableMacro(MeshRenderer._uv1Macro); - shaderData.disableMacro(MeshRenderer._normalMacro); - shaderData.disableMacro(MeshRenderer._tangentMacro); - shaderData.disableMacro(MeshRenderer._enableVertexColorMacro); - - for (let i = 0, n = vertexElements.length; i < n; i++) { - switch (vertexElements[i].semantic) { - case "TEXCOORD_0": - shaderData.enableMacro(MeshRenderer._uvMacro); - break; - case "TEXCOORD_1": - shaderData.enableMacro(MeshRenderer._uv1Macro); - break; - case "NORMAL": - shaderData.enableMacro(MeshRenderer._normalMacro); - break; - case "TANGENT": - shaderData.enableMacro(MeshRenderer._tangentMacro); - break; - case "COLOR_0": - this._enableVertexColor && shaderData.enableMacro(MeshRenderer._enableVertexColorMacro); - break; - } + if (this._dirtyUpdateFlag & MeshRendererUpdateFlags.VertexElementMacro) { + const shaderData = this.shaderData; + const vertexElements = mesh._vertexElements; + + shaderData.disableMacro(MeshRenderer._uvMacro); + shaderData.disableMacro(MeshRenderer._uv1Macro); + shaderData.disableMacro(MeshRenderer._normalMacro); + shaderData.disableMacro(MeshRenderer._tangentMacro); + shaderData.disableMacro(MeshRenderer._enableVertexColorMacro); + + for (let i = 0, n = vertexElements.length; i < n; i++) { + switch (vertexElements[i].semantic) { + case "TEXCOORD_0": + shaderData.enableMacro(MeshRenderer._uvMacro); + break; + case "TEXCOORD_1": + shaderData.enableMacro(MeshRenderer._uv1Macro); + break; + case "NORMAL": + shaderData.enableMacro(MeshRenderer._normalMacro); + break; + case "TANGENT": + shaderData.enableMacro(MeshRenderer._tangentMacro); + break; + case "COLOR_0": + this._enableVertexColor && shaderData.enableMacro(MeshRenderer._enableVertexColorMacro); + break; } - this._dirtyUpdateFlag &= ~MeshRendererUpdateFlags.VertexElementMacro; } + this._dirtyUpdateFlag &= ~MeshRendererUpdateFlags.VertexElementMacro; + } - const materials = this._materials; - const subMeshes = mesh.subMeshes; - const renderPipeline = context.camera._renderPipeline; - const meshRenderDataPool = this._engine._meshRenderDataPool; - for (let i = 0, n = subMeshes.length; i < n; i++) { - const material = materials[i]; - if (!material) continue; - const renderData = meshRenderDataPool.getFromPool(); - renderData.set(this, material, mesh, subMeshes[i]); - renderPipeline.pushRenderData(context, renderData); - } - } else { - Logger.error("mesh is null."); + const materials = this._materials; + const subMeshes = mesh.subMeshes; + const renderPipeline = context.camera._renderPipeline; + const meshRenderDataPool = this._engine._meshRenderDataPool; + for (let i = 0, n = subMeshes.length; i < n; i++) { + const material = materials[i]; + if (!material) continue; + const renderData = meshRenderDataPool.getFromPool(); + renderData.set(this, material, mesh, subMeshes[i]); + renderPipeline.pushRenderData(context, renderData); } } diff --git a/packages/core/src/mesh/ModelMesh.ts b/packages/core/src/mesh/ModelMesh.ts index 978fac6ae1..3c39f0df40 100644 --- a/packages/core/src/mesh/ModelMesh.ts +++ b/packages/core/src/mesh/ModelMesh.ts @@ -606,8 +606,8 @@ export class ModelMesh extends Mesh { uploadData(releaseData: boolean): void { this._updateVertexElements(); - // If releaseData is false, we shouldn't update buffer data version - releaseData || (this._advancedDataSyncToBuffer = true); + // Shouldn't update buffer data version when sync advanced data to buffer + this._advancedDataSyncToBuffer = true; // Update internal vertex buffer if needed this._updateInternalVertexBuffer(releaseData); @@ -1296,6 +1296,14 @@ export class ModelMesh extends Mesh { if (!isDestroy) { this._vertexBufferBindings[this._internalVertexBufferIndex]?.buffer.markAsUnreadable(); + + // If release data, we need update buffer data version to ensure get data method can read buffer + const dataVersion = this._dataVersionCounter++; + const vertexBufferInfos = this._vertexBufferInfos; + for (let i = 0, n = vertexBufferInfos.length; i < n; i++) { + const vertexBufferInfo = vertexBufferInfos[i]; + vertexBufferInfo && (vertexBufferInfo.dataVersion = dataVersion); + } } } diff --git a/packages/core/src/mesh/SkinnedMeshRenderer.ts b/packages/core/src/mesh/SkinnedMeshRenderer.ts index e7ecdd41e0..0cfc581d64 100644 --- a/packages/core/src/mesh/SkinnedMeshRenderer.ts +++ b/packages/core/src/mesh/SkinnedMeshRenderer.ts @@ -119,7 +119,7 @@ export class SkinnedMeshRenderer extends MeshRenderer { // Limit size to 256 to avoid some problem: // For renderer is "Apple GPU", when uniform is large than 256 the skeleton matrix array access in shader very slow in Safari or WKWebview. This may be a apple bug, Chrome and Firefox is OK! // For renderer is "ANGLE (AMD, AMD Radeon(TM) Graphics Direct3011 vs_5_0 ps_5_0, D3011)", compile shader si very slow because of max uniform is 4096. - maxVertexUniformVectors = Math.min(maxVertexUniformVectors, 256); + maxVertexUniformVectors = Math.min(maxVertexUniformVectors, rhi._options._maxAllowSkinUniformVectorCount); this._maxVertexUniformVectors = maxVertexUniformVectors; @@ -226,8 +226,19 @@ export class SkinnedMeshRenderer extends MeshRenderer { */ override _onDestroy(): void { super._onDestroy(); - this.rootBone?.transform._updateFlagManager.removeListener(this._onTransformChanged); + this._rootBone?.transform._updateFlagManager.removeListener(this._onTransformChanged); + this._rootBone = null; + this._jointDataCreateCache = null; + this._skin = null; + this._blendShapeWeights = null; + this._localBounds = null; + this._jointMatrices = null; this._jointTexture?.destroy(); + this._jointTexture = null; + if (this._jointEntities) { + this._jointEntities.length = 0; + this._jointEntities = null; + } } /** diff --git a/packages/core/src/physics/PhysicsScene.ts b/packages/core/src/physics/PhysicsScene.ts index c9a85aee66..1b056de895 100644 --- a/packages/core/src/physics/PhysicsScene.ts +++ b/packages/core/src/physics/PhysicsScene.ts @@ -406,6 +406,13 @@ export class PhysicsScene { } } + /** + * @internal + */ + _gc(): void { + this._colliders.garbageCollection(); + } + private _setGravity(): void { this._nativePhysicsScene.setGravity(this._gravity); } diff --git a/packages/core/src/shader/Shader.ts b/packages/core/src/shader/Shader.ts index 70706e17f9..4a341f713f 100644 --- a/packages/core/src/shader/Shader.ts +++ b/packages/core/src/shader/Shader.ts @@ -1,9 +1,11 @@ +import { IShaderLab } from "@galacean/engine-design"; import { Engine } from "../Engine"; import { ShaderMacro } from "./ShaderMacro"; import { ShaderMacroCollection } from "./ShaderMacroCollection"; import { ShaderPass } from "./ShaderPass"; import { ShaderProperty } from "./ShaderProperty"; import { SubShader } from "./SubShader"; +import { RenderState } from "./state/RenderState"; /** * Shader for rendering. @@ -17,9 +19,33 @@ export class Shader { "GL_OES_standard_derivatives", "GL_EXT_draw_buffers" ]; + /** @internal */ + static _shaderLab?: IShaderLab; private static _shaderMap: Record = Object.create(null); + /** + * Create a shader by source code. + * + * @remarks + * + * ShaderLab must be enabled first as follows: + * ```ts + * // Import shaderLab + * import { ShaderLab } from "@galacean/engine-shader-lab"; + * // Create engine with shaderLab + * const engine = await WebGLEngine.create({ canvas: "canvas", new ShaderLab() }); + * ... + * ``` + * + * @param shaderSource - shader code + * @returns Shader + * + * @throws + * Throw string exception if shaderLab has not been enabled properly. + */ + static create(shaderSource: string): Shader; + /** * Create a shader. * @param name - Name of the shader @@ -46,30 +72,56 @@ export class Shader { static create(name: string, subShaders: SubShader[]): Shader; static create( - name: string, - vertexSourceOrShaderPassesOrSubShaders: SubShader[] | ShaderPass[] | string, + nameOrShaderSource: string, + vertexSourceOrShaderPassesOrSubShaders?: SubShader[] | ShaderPass[] | string, fragmentSource?: string ): Shader { - const shaderMap = Shader._shaderMap; - if (shaderMap[name]) { - throw `Shader named "${name}" already exists.`; - } let shader: Shader; - if (typeof vertexSourceOrShaderPassesOrSubShaders === "string") { - const shaderPass = new ShaderPass(vertexSourceOrShaderPassesOrSubShaders, fragmentSource); - shader = new Shader(name, [new SubShader("Default", [shaderPass])]); + const shaderMap = Shader._shaderMap; + + if (!vertexSourceOrShaderPassesOrSubShaders) { + if (!Shader._shaderLab) { + throw "ShaderLab has not been set up yet."; + } + + const shaderInfo = Shader._shaderLab.parseShader(nameOrShaderSource); + const subShaderList = shaderInfo.subShaders.map((subShader) => { + const passList = subShader.passes.map((pass) => { + const shaderPass = new ShaderPass(pass.vert, pass.frag, pass.tags); + shaderPass._renderState = new RenderState(); + // TODO: render state with `shaderPass._renderStateDataMap`, key is `RenderStateDataKey`,value is `ShaderProperty` + shaderPass._renderStateDataMap = {}; + return shaderPass; + }); + return new SubShader(shaderInfo.name, passList, subShader.tags); + }); + + shader = new Shader(shaderInfo.name, subShaderList); + shaderMap[shaderInfo.name] = shader; + return shader; } else { - if (vertexSourceOrShaderPassesOrSubShaders.length > 0) { - if (vertexSourceOrShaderPassesOrSubShaders[0].constructor === ShaderPass) { - shader = new Shader(name, [new SubShader("Default", vertexSourceOrShaderPassesOrSubShaders)]); + if (shaderMap[nameOrShaderSource]) { + throw `Shader named "${nameOrShaderSource}" already exists.`; + } + if (typeof vertexSourceOrShaderPassesOrSubShaders === "string") { + const shaderPass = new ShaderPass(vertexSourceOrShaderPassesOrSubShaders, fragmentSource); + shader = new Shader(nameOrShaderSource, [new SubShader("Default", [shaderPass])]); + } else { + if (vertexSourceOrShaderPassesOrSubShaders.length > 0) { + if (vertexSourceOrShaderPassesOrSubShaders[0].constructor === ShaderPass) { + shader = new Shader(nameOrShaderSource, [ + new SubShader("Default", vertexSourceOrShaderPassesOrSubShaders) + ]); + } else { + shader = new Shader(nameOrShaderSource, vertexSourceOrShaderPassesOrSubShaders.slice()); + } } else { - shader = new Shader(name, vertexSourceOrShaderPassesOrSubShaders.slice()); + throw "SubShader or ShaderPass count must large than 0."; } - } else { - throw "SubShader or ShaderPass count must large than 0."; } } - shaderMap[name] = shader; + + shaderMap[nameOrShaderSource] = shader; return shader; } diff --git a/packages/core/src/shader/ShaderData.ts b/packages/core/src/shader/ShaderData.ts index e48489e0eb..a52c03fa04 100644 --- a/packages/core/src/shader/ShaderData.ts +++ b/packages/core/src/shader/ShaderData.ts @@ -601,7 +601,7 @@ export class ShaderData implements IReferable, IClone { cloneTo(target: ShaderData): void { CloneManager.deepCloneObject(this._macroCollection, target._macroCollection); Object.assign(target._macroMap, this._macroMap); - + const referCount = target._getReferCount(); const propertyValueMap = this._propertyValueMap; const targetPropertyValueMap = target._propertyValueMap; const keys = Object.keys(propertyValueMap); @@ -613,6 +613,7 @@ export class ShaderData implements IReferable, IClone { targetPropertyValueMap[k] = property; } else if (property instanceof Texture) { targetPropertyValueMap[k] = property; + referCount > 0 && property._addReferCount(referCount); } else if (property instanceof Array || property instanceof Float32Array || property instanceof Int32Array) { targetPropertyValueMap[k] = property.slice(); } else { diff --git a/packages/core/src/shader/ShaderPass.ts b/packages/core/src/shader/ShaderPass.ts index 5e0dba2639..40cddb7d6b 100644 --- a/packages/core/src/shader/ShaderPass.ts +++ b/packages/core/src/shader/ShaderPass.ts @@ -1,12 +1,14 @@ -import { GLCapabilityType } from "../base/Constant"; import { Engine } from "../Engine"; import { PipelineStage } from "../RenderPipeline/enums/PipelineStage"; +import { GLCapabilityType } from "../base/Constant"; import { ShaderFactory } from "../shaderlib/ShaderFactory"; import { Shader } from "./Shader"; import { ShaderMacro } from "./ShaderMacro"; import { ShaderMacroCollection } from "./ShaderMacroCollection"; import { ShaderPart } from "./ShaderPart"; import { ShaderProgram } from "./ShaderProgram"; +import { ShaderProperty } from "./ShaderProperty"; +import { RenderState } from "./state/RenderState"; /** * Shader pass containing vertex and fragment source. @@ -17,6 +19,14 @@ export class ShaderPass extends ShaderPart { /** @internal */ _shaderPassId: number = 0; + /** + * @internal + * @remarks If undefined, the blend state of the material will be used ( deprecate mode ). + */ + _renderState: RenderState; + /** @internal */ + _renderStateDataMap: Record = {}; + private _vertexSource: string; private _fragmentSource: string; diff --git a/packages/core/src/shader/enums/RenderStateElementKey.ts b/packages/core/src/shader/enums/RenderStateElementKey.ts new file mode 100644 index 0000000000..1851ca2cc5 --- /dev/null +++ b/packages/core/src/shader/enums/RenderStateElementKey.ts @@ -0,0 +1,61 @@ +export enum RenderStateDataKey { + /** Blend state enabled for target 0 key. */ + BlendStateEnabled0 = 0, + /** Blend state color blend operation for target 0 key. */ + BlendStateColorBlendOperation0 = 1, + /** Blend state alpha blend operation for target 0 key. */ + BlendStateAlphaBlendOperation0 = 2, + /** Blend state source color blend factor for target 0 key. */ + BlendStateSourceColorBlendFactor0 = 3, + /** Blend state source alpha blend factor for target 0 key. */ + BlendStateSourceAlphaBlendFactor0 = 4, + /** Blend state destination color blend factor for target 0 key. */ + BlendStateDestinationColorBlendFactor0 = 5, + /** Blend state destination alpha blend factor for target 0 key. */ + BlendStateDestinationAlphaBlendFactor0 = 6, + /** Blend state color write mask for target 0 key. */ + BlendStateColorWriteMask0 = 7, + /** Blend state blend color key. */ + BlendStateBlendColor = 8, + /** Blend state alpha to coverage key. */ + BlendStateAlphaToCoverage = 9, + + /** Depth state enabled key. */ + DepthStateEnabled = 10, + /** Depth state write enabled key. */ + DepthStateWriteEnabled = 11, + /** Depth state compare function key. */ + DepthStateCompareFunction = 12, + + /** Stencil state enabled key. */ + StencilStateEnabled = 13, + /** Stencil state reference value key. */ + StencilStateReferenceValue = 14, + /** Stencil state read mask key. */ + StencilStateMask = 15, + /** Stencil state write mask key. */ + StencilStateWriteMask = 16, + /** Stencil state compare function front key. */ + StencilStateCompareFunctionFront = 17, + /** Stencil state compare function back key. */ + StencilStateCompareFunctionBack = 18, + /** Stencil state pass operation front key. */ + StencilStatePassOperationFront = 19, + /** Stencil state pass operation back key. */ + StencilStatePassOperationBack = 20, + /** Stencil state fail operation front key. */ + StencilStateFailOperationFront = 21, + /** Stencil state fail operation back key. */ + StencilStateFailOperationBack = 22, + /** Stencil state z fail operation front key. */ + StencilStateZFailOperationFront = 23, + /** Stencil state z fail operation back key. */ + StencilStateZFailOperationBack = 24, + + /** Raster state fill mode key. */ + RasterStateCullMode = 25, + /** Raster state cull mode key. */ + RasterStateDepthBias = 26, + /** Raster state depth bias key. */ + RasterStateSlopeScaledDepthBias = 27 +} diff --git a/packages/core/src/shader/state/BlendState.ts b/packages/core/src/shader/state/BlendState.ts index 1b82c02a1c..357fe31275 100644 --- a/packages/core/src/shader/state/BlendState.ts +++ b/packages/core/src/shader/state/BlendState.ts @@ -1,9 +1,12 @@ import { Color } from "@galacean/engine-math"; import { GLCapabilityType } from "../../base/Constant"; import { IHardwareRenderer } from "../../renderingHardwareInterface/IHardwareRenderer"; +import { ShaderData } from "../ShaderData"; +import { ShaderProperty } from "../ShaderProperty"; import { BlendFactor } from "../enums/BlendFactor"; import { BlendOperation } from "../enums/BlendOperation"; import { ColorWriteMask } from "../enums/ColorWriteMask"; +import { RenderStateDataKey } from "../enums/RenderStateElementKey"; import { RenderState } from "./RenderState"; import { RenderTargetBlendState } from "./RenderTargetBlendState"; @@ -74,6 +77,70 @@ export class BlendState { /** Whether to use (Alpha-to-Coverage) technology. */ alphaToCoverage: boolean = false; + /** + * @internal + */ + _applyShaderDataValue(renderStateDataMap: Record, shaderData: ShaderData): void { + const blendState = this.targetBlendState; + + const enable0Property = renderStateDataMap[RenderStateDataKey.BlendStateEnabled0]; + if (enable0Property !== undefined) { + const enabled = shaderData.getFloat(enable0Property); + blendState.enabled = enabled !== undefined ? !!enabled : false; + } + + const colorBlendOperation0Property = renderStateDataMap[RenderStateDataKey.BlendStateColorBlendOperation0]; + if (colorBlendOperation0Property !== undefined) { + blendState.colorBlendOperation = shaderData.getFloat(colorBlendOperation0Property) ?? BlendOperation.Add; + } + + const alphaBlendOperation0Property = renderStateDataMap[RenderStateDataKey.BlendStateAlphaBlendOperation0]; + if (alphaBlendOperation0Property !== undefined) { + blendState.alphaBlendOperation = shaderData.getFloat(alphaBlendOperation0Property) ?? BlendOperation.Add; + } + + const sourceColorBlendFactor0Property = renderStateDataMap[RenderStateDataKey.BlendStateSourceColorBlendFactor0]; + if (sourceColorBlendFactor0Property !== undefined) { + blendState.sourceColorBlendFactor = shaderData.getFloat(sourceColorBlendFactor0Property) ?? BlendFactor.One; + } + + const sourceAlphaBlendFactor0Property = renderStateDataMap[RenderStateDataKey.BlendStateSourceAlphaBlendFactor0]; + if (sourceAlphaBlendFactor0Property !== undefined) { + blendState.sourceAlphaBlendFactor = shaderData.getFloat(sourceAlphaBlendFactor0Property) ?? BlendFactor.One; + } + + const destinationColorBlendFactor0Property = + renderStateDataMap[RenderStateDataKey.BlendStateDestinationColorBlendFactor0]; + if (destinationColorBlendFactor0Property !== undefined) { + blendState.destinationColorBlendFactor = + shaderData.getFloat(destinationColorBlendFactor0Property) ?? BlendFactor.Zero; + } + + const destinationAlphaBlendFactor0Property = + renderStateDataMap[RenderStateDataKey.BlendStateDestinationAlphaBlendFactor0]; + if (destinationAlphaBlendFactor0Property !== undefined) { + blendState.destinationAlphaBlendFactor = + shaderData.getFloat(destinationAlphaBlendFactor0Property) ?? BlendFactor.Zero; + } + + const colorWriteMask0Property = renderStateDataMap[RenderStateDataKey.BlendStateColorWriteMask0]; + if (colorWriteMask0Property !== undefined) { + blendState.colorWriteMask = shaderData.getFloat(colorWriteMask0Property) ?? ColorWriteMask.All; + } + + const blendColorProperty = renderStateDataMap[RenderStateDataKey.BlendStateBlendColor]; + if (blendColorProperty !== undefined) { + const blendColor = shaderData.getColor(blendColorProperty); + blendColor !== undefined && this.blendColor.copyFrom(blendColor); + } + + const alphaToCoverageProperty = renderStateDataMap[RenderStateDataKey.BlendStateAlphaToCoverage]; + if (alphaToCoverageProperty !== undefined) { + const alphaToCoverage = shaderData.getFloat(alphaToCoverageProperty); + this.alphaToCoverage = alphaToCoverage !== undefined ? !!alphaToCoverage : false; + } + } + /** * @internal * Apply the current blend state by comparing with the last blend state. diff --git a/packages/core/src/shader/state/DepthState.ts b/packages/core/src/shader/state/DepthState.ts index 29c8f03d3e..72ec8383fb 100644 --- a/packages/core/src/shader/state/DepthState.ts +++ b/packages/core/src/shader/state/DepthState.ts @@ -1,5 +1,8 @@ import { IHardwareRenderer } from "../../renderingHardwareInterface/IHardwareRenderer"; +import { ShaderData } from "../ShaderData"; +import { ShaderProperty } from "../ShaderProperty"; import { CompareFunction } from "../enums/CompareFunction"; +import { RenderStateDataKey } from "../enums/RenderStateElementKey"; import { RenderState } from "./RenderState"; /** @@ -36,6 +39,28 @@ export class DepthState { /** Depth comparison function. */ compareFunction: CompareFunction = CompareFunction.Less; + /** + * @internal + */ + _applyShaderDataValue(renderStateDataMap: Record, shaderData: ShaderData): void { + const enableProperty = renderStateDataMap[RenderStateDataKey.DepthStateEnabled]; + if (enableProperty !== undefined) { + const enabled = shaderData.getFloat(enableProperty); + this.enabled = enabled !== undefined ? !!enabled : false; + } + + const writeEnabledProperty = renderStateDataMap[RenderStateDataKey.DepthStateWriteEnabled]; + if (writeEnabledProperty !== undefined) { + const writeEnabled = shaderData.getFloat(writeEnabledProperty); + this.writeEnabled = writeEnabled !== undefined ? !!writeEnabled : false; + } + + const compareFunctionProperty = renderStateDataMap[RenderStateDataKey.DepthStateCompareFunction]; + if (compareFunctionProperty !== undefined) { + this.compareFunction = shaderData.getFloat(compareFunctionProperty) ?? CompareFunction.Less; + } + } + /** * @internal * Apply the current depth state by comparing with the last depth state. diff --git a/packages/core/src/shader/state/RasterState.ts b/packages/core/src/shader/state/RasterState.ts index e700a814cb..b92002af9a 100644 --- a/packages/core/src/shader/state/RasterState.ts +++ b/packages/core/src/shader/state/RasterState.ts @@ -1,5 +1,8 @@ import { IHardwareRenderer } from "../../renderingHardwareInterface/IHardwareRenderer"; +import { ShaderData } from "../ShaderData"; +import { ShaderProperty } from "../ShaderProperty"; import { CullMode } from "../enums/CullMode"; +import { RenderStateDataKey } from "../enums/RenderStateElementKey"; import { RenderState } from "./RenderState"; /** @@ -18,6 +21,26 @@ export class RasterState { /** @internal */ _frontFaceInvert: boolean = false; + /** + * @internal + */ + _applyShaderDataValue(renderStateDataMap: Record, shaderData: ShaderData): void { + const cullModeProperty = renderStateDataMap[RenderStateDataKey.RasterStateCullMode]; + if (cullModeProperty !== undefined) { + this.cullMode = shaderData.getFloat(cullModeProperty) ?? CullMode.Back; + } + + const depthBiasProperty = renderStateDataMap[RenderStateDataKey.RasterStateDepthBias]; + if (depthBiasProperty !== undefined) { + this.depthBias = shaderData.getFloat(depthBiasProperty) ?? 0; + } + + const slopeScaledDepthBiasProperty = renderStateDataMap[RenderStateDataKey.RasterStateSlopeScaledDepthBias]; + if (slopeScaledDepthBiasProperty !== undefined) { + this.slopeScaledDepthBias = shaderData.getFloat(slopeScaledDepthBiasProperty) ?? 0; + } + } + /** * @internal */ diff --git a/packages/core/src/shader/state/RenderState.ts b/packages/core/src/shader/state/RenderState.ts index 6416f41867..369bbd942b 100644 --- a/packages/core/src/shader/state/RenderState.ts +++ b/packages/core/src/shader/state/RenderState.ts @@ -1,3 +1,4 @@ +import { ShaderData, ShaderProperty } from ".."; import { Engine } from "../../Engine"; import { RenderQueueType } from "../enums/RenderQueueType"; import { BlendState } from "./BlendState"; @@ -23,8 +24,25 @@ export class RenderState { /** * @internal + * @todo Should merge when we can delete material render state. */ - _apply(engine: Engine, frontFaceInvert: boolean): void { + _applyShaderDataValue(renderStateDataMap: Record, shaderData: ShaderData): void { + this.blendState._applyShaderDataValue(renderStateDataMap, shaderData); + this.depthState._applyShaderDataValue(renderStateDataMap, shaderData); + this.stencilState._applyShaderDataValue(renderStateDataMap, shaderData); + this.rasterState._applyShaderDataValue(renderStateDataMap, shaderData); + } + + /** + * @internal + */ + _apply( + engine: Engine, + frontFaceInvert: boolean, + renderStateDataMap: Record, + shaderData: ShaderData + ): void { + renderStateDataMap && this._applyShaderDataValue(renderStateDataMap, shaderData); const hardwareRenderer = engine._hardwareRenderer; const lastRenderState = engine._lastRenderState; this.blendState._apply(hardwareRenderer, lastRenderState); diff --git a/packages/core/src/shader/state/StencilState.ts b/packages/core/src/shader/state/StencilState.ts index 59e05856ab..734e31adc0 100644 --- a/packages/core/src/shader/state/StencilState.ts +++ b/packages/core/src/shader/state/StencilState.ts @@ -1,5 +1,8 @@ import { IHardwareRenderer } from "../../renderingHardwareInterface/IHardwareRenderer"; +import { ShaderData } from "../ShaderData"; +import { ShaderProperty } from "../ShaderProperty"; import { CompareFunction } from "../enums/CompareFunction"; +import { RenderStateDataKey } from "../enums/RenderStateElementKey"; import { StencilOperation } from "../enums/StencilOperation"; import { RenderState } from "./RenderState"; @@ -78,6 +81,72 @@ export class StencilState { /** specifying the function to use for back face when the stencil test passes, but the depth test fails. */ zFailOperationBack: StencilOperation = StencilOperation.Keep; + /** + * @internal + */ + _applyShaderDataValue(renderStateDataMap: Record, shaderData: ShaderData): void { + const enableProperty = renderStateDataMap[RenderStateDataKey.StencilStateEnabled]; + if (enableProperty !== undefined) { + const enabled = shaderData.getFloat(enableProperty); + this.enabled = enabled !== undefined ? !!enabled : false; + } + + const referenceValueProperty = renderStateDataMap[RenderStateDataKey.StencilStateReferenceValue]; + if (referenceValueProperty !== undefined) { + this.referenceValue = shaderData.getFloat(referenceValueProperty) ?? 0; + } + + const maskProperty = renderStateDataMap[RenderStateDataKey.StencilStateMask]; + if (maskProperty !== undefined) { + this.mask = shaderData.getFloat(maskProperty) ?? 0xff; + } + + const writeMaskProperty = renderStateDataMap[RenderStateDataKey.StencilStateWriteMask]; + if (writeMaskProperty !== undefined) { + this.writeMask = shaderData.getFloat(writeMaskProperty) ?? 0xff; + } + + const compareFunctionFrontProperty = renderStateDataMap[RenderStateDataKey.StencilStateCompareFunctionFront]; + if (compareFunctionFrontProperty !== undefined) { + this.compareFunctionFront = shaderData.getFloat(compareFunctionFrontProperty) ?? CompareFunction.Always; + } + + const compareFunctionBackProperty = renderStateDataMap[RenderStateDataKey.StencilStateCompareFunctionBack]; + if (compareFunctionBackProperty !== undefined) { + this.compareFunctionBack = shaderData.getFloat(compareFunctionBackProperty) ?? CompareFunction.Always; + } + + const passOperationFrontProperty = renderStateDataMap[RenderStateDataKey.StencilStatePassOperationFront]; + if (passOperationFrontProperty !== undefined) { + this.passOperationFront = shaderData.getFloat(passOperationFrontProperty) ?? StencilOperation.Keep; + } + + const passOperationBackProperty = renderStateDataMap[RenderStateDataKey.StencilStatePassOperationBack]; + if (passOperationBackProperty !== undefined) { + this.passOperationBack = shaderData.getFloat(passOperationBackProperty) ?? StencilOperation.Keep; + } + + const failOperationFrontProperty = renderStateDataMap[RenderStateDataKey.StencilStateFailOperationFront]; + if (failOperationFrontProperty !== undefined) { + this.failOperationFront = shaderData.getFloat(failOperationFrontProperty) ?? StencilOperation.Keep; + } + + const failOperationBackProperty = renderStateDataMap[RenderStateDataKey.StencilStateFailOperationBack]; + if (failOperationBackProperty !== undefined) { + this.failOperationBack = shaderData.getFloat(failOperationBackProperty) ?? StencilOperation.Keep; + } + + const zFailOperationFrontProperty = renderStateDataMap[RenderStateDataKey.StencilStateZFailOperationFront]; + if (zFailOperationFrontProperty !== undefined) { + this.zFailOperationFront = shaderData.getFloat(zFailOperationFrontProperty) ?? StencilOperation.Keep; + } + + const zFailOperationBackProperty = renderStateDataMap[RenderStateDataKey.StencilStateZFailOperationBack]; + if (zFailOperationBackProperty !== undefined) { + this.zFailOperationBack = shaderData.getFloat(zFailOperationBackProperty) ?? StencilOperation.Keep; + } + } + /** * @internal */ diff --git a/packages/core/src/shaderlib/mobile_blinnphong_frag.glsl b/packages/core/src/shaderlib/mobile_blinnphong_frag.glsl index bbe8ab50bd..411cc81b14 100644 --- a/packages/core/src/shaderlib/mobile_blinnphong_frag.glsl +++ b/packages/core/src/shaderlib/mobile_blinnphong_frag.glsl @@ -13,7 +13,7 @@ shadowAttenuation = 1.0; #ifdef SCENE_IS_CALCULATE_SHADOWS shadowAttenuation *= sampleShadowMap(); - int sunIndex = int(scene_ShadowInfo.z); + // int sunIndex = int(scene_ShadowInfo.z); #endif DirectLight directionalLight; @@ -23,7 +23,7 @@ directionalLight.color = scene_DirectLightColor[i]; #ifdef SCENE_IS_CALCULATE_SHADOWS - if (i == sunIndex) { + if (i == 0) { // Sun light index is always 0 directionalLight.color *= shadowAttenuation; } #endif diff --git a/packages/core/src/shaderlib/pbr/direct_irradiance_frag_define.glsl b/packages/core/src/shaderlib/pbr/direct_irradiance_frag_define.glsl index e1385d016f..7876a532d2 100644 --- a/packages/core/src/shaderlib/pbr/direct_irradiance_frag_define.glsl +++ b/packages/core/src/shaderlib/pbr/direct_irradiance_frag_define.glsl @@ -79,7 +79,7 @@ void addTotalDirectRadiance(Geometry geometry, Material material, inout Reflecte shadowAttenuation = 1.0; #ifdef SCENE_IS_CALCULATE_SHADOWS shadowAttenuation *= sampleShadowMap(); - int sunIndex = int(scene_ShadowInfo.z); + // int sunIndex = int(scene_ShadowInfo.z); #endif DirectLight directionalLight; @@ -89,7 +89,7 @@ void addTotalDirectRadiance(Geometry geometry, Material material, inout Reflecte directionalLight.color = scene_DirectLightColor[i]; #ifdef SCENE_IS_CALCULATE_SHADOWS - if (i == sunIndex) { + if (i == 0) { // Sun light index is always 0 directionalLight.color *= shadowAttenuation; } #endif diff --git a/packages/core/src/shaderlib/pbr/pbr_frag_define.glsl b/packages/core/src/shaderlib/pbr/pbr_frag_define.glsl index 49917a9e9f..10b00f62a2 100644 --- a/packages/core/src/shaderlib/pbr/pbr_frag_define.glsl +++ b/packages/core/src/shaderlib/pbr/pbr_frag_define.glsl @@ -87,4 +87,4 @@ struct Material { float clearCoatRoughness; #endif -}; +}; \ No newline at end of file diff --git a/packages/core/src/shadow/CascadedShadowCasterPass.ts b/packages/core/src/shadow/CascadedShadowCasterPass.ts index 5dcb22247c..eece5f6c5e 100644 --- a/packages/core/src/shadow/CascadedShadowCasterPass.ts +++ b/packages/core/src/shadow/CascadedShadowCasterPass.ts @@ -108,10 +108,8 @@ export class CascadedShadowCasterPass extends PipelinePass { const lightSide = this._lightSide; const lightForward = shadowSliceData.virtualCamera.forward; - const sunLightIndex = scene._lightManager._getSunLightIndex(); - - if (sunLightIndex !== -1) { - const light = camera.scene._sunLight; + const light = camera.scene._sunLight; + if (light) { const shadowFar = Math.min(camera.scene.shadowDistance, camera.farClipPlane); this._getCascadesSplitDistance(shadowFar); @@ -160,7 +158,7 @@ export class CascadedShadowCasterPass extends PipelinePass { rhi.clearRenderTarget(engine, CameraClearFlags.All, CascadedShadowCasterPass._clearColor); } this._shadowInfos.x = light.shadowStrength; - this._shadowInfos.z = sunLightIndex; + this._shadowInfos.z = 0; // @todo: sun light index always 0 // prepare light and camera direction Matrix.rotationQuaternion(light.entity.transform.worldRotationQuaternion, lightWorld); diff --git a/packages/core/src/sky/Sky.ts b/packages/core/src/sky/Sky.ts index d75ef5f3ae..8c0e537c18 100644 --- a/packages/core/src/sky/Sky.ts +++ b/packages/core/src/sky/Sky.ts @@ -1,8 +1,8 @@ import { MathUtil, Matrix } from "@galacean/engine-math"; +import { RenderContext } from "../RenderPipeline/RenderContext"; import { Logger } from "../base/Logger"; import { Mesh } from "../graphic/Mesh"; import { Material } from "../material"; -import { RenderContext } from "../RenderPipeline/RenderContext"; import { Shader } from "../shader/Shader"; import { ShaderMacroCollection } from "../shader/ShaderMacroCollection"; @@ -14,10 +14,46 @@ export class Sky { private static _viewProjMatrix: Matrix = new Matrix(); private static _projectionMatrix: Matrix = new Matrix(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, Sky._epsilon - 1, -1, 0, 0, 0, 0); - /** Material of the sky. */ - material: Material; - /** Mesh of the sky. */ - mesh: Mesh; + private _material: Material; + private _mesh: Mesh; + + /** + * Material of the sky. + */ + get material() { + return this._material; + } + + set material(value: Material) { + if (this._material !== value) { + value?._addReferCount(1); + this._material?._addReferCount(-1); + this._material = value; + } + } + + /** + * Mesh of the sky. + */ + get mesh() { + return this._mesh; + } + + set mesh(value: Mesh) { + if (this._mesh !== value) { + value?._addReferCount(1); + this._mesh?._addReferCount(-1); + this._mesh = value; + } + } + + /** + * @internal + */ + destroy(): void { + this.mesh = null; + this.material = null; + } /** * @internal @@ -60,7 +96,9 @@ export class Sky { materialShaderData._macroCollection, compileMacros ); - const program = shader.subShaders[0].passes[0]._getShaderProgram(engine, compileMacros); + + const pass = shader.subShaders[0].passes[0]; + const program = pass._getShaderProgram(engine, compileMacros); program.bind(); program.groupingOtherUniformBlock(); program.uploadAll(program.sceneUniformBlock, sceneData); @@ -68,7 +106,7 @@ export class Sky { program.uploadAll(program.materialUniformBlock, materialShaderData); program.uploadUnGroupTextures(); - renderState._apply(engine, false); + renderState._apply(engine, false, pass._renderStateDataMap, materialShaderData); rhi.drawPrimitive(mesh, mesh.subMesh, program); cameraShaderData.setMatrix(RenderContext.vpMatrixProperty, originViewProjMatrix); } diff --git a/packages/core/src/texture/RenderTarget.ts b/packages/core/src/texture/RenderTarget.ts index 5c4fced241..0ae785b5ac 100644 --- a/packages/core/src/texture/RenderTarget.ts +++ b/packages/core/src/texture/RenderTarget.ts @@ -164,9 +164,11 @@ export class RenderTarget extends GraphicsResource { if (renderTexture) { const colorTextures = renderTexture instanceof Array ? renderTexture.slice() : [renderTexture]; for (let i = 0, n = colorTextures.length; i < n; i++) { - if (colorTextures[i]._isDepthTexture) { + const colorTexture = colorTextures[i]; + if (colorTexture._isDepthTexture) { throw "Render texture can't use depth format."; } + colorTexture._addReferCount(1); } this._colorTextures = colorTextures; } else { @@ -178,6 +180,7 @@ export class RenderTarget extends GraphicsResource { throw "Depth texture must use depth format."; } this._depthTexture = depth; + this._depthTexture._addReferCount(1); } this._platformRenderTarget = engine._hardwareRenderer.createPlatformRenderTarget(this); @@ -211,7 +214,12 @@ export class RenderTarget extends GraphicsResource { protected override _onDestroy(): void { super._onDestroy(); this._platformRenderTarget.destroy(); - this._colorTextures.length = 0; + const { _colorTextures: colorTextures } = this; + for (let i = 0, n = colorTextures.length; i < n; i++) { + colorTextures[i]._addReferCount(-1); + } + colorTextures.length = 0; + this._depthTexture?._addReferCount(-1); this._depthTexture = null; this._depth = null; } diff --git a/packages/design/src/index.ts b/packages/design/src/index.ts index e53565cd8b..a5aee19ae7 100644 --- a/packages/design/src/index.ts +++ b/packages/design/src/index.ts @@ -1,3 +1,4 @@ +export type { IClone } from "./IClone"; export * from "./physics/index"; export * from "./renderingHardwareInterface/index"; -export type { IClone } from "./IClone"; +export * from "./shader-lab/index"; diff --git a/packages/design/src/shader-lab/IShaderInfo.ts b/packages/design/src/shader-lab/IShaderInfo.ts new file mode 100644 index 0000000000..1dd5594a08 --- /dev/null +++ b/packages/design/src/shader-lab/IShaderInfo.ts @@ -0,0 +1,6 @@ +import { ISubShaderInfo } from "./ISubShaderInfo"; + +export interface IShaderInfo { + name: string; + subShaders: ISubShaderInfo[]; +} diff --git a/packages/design/src/shader-lab/IShaderLab.ts b/packages/design/src/shader-lab/IShaderLab.ts new file mode 100644 index 0000000000..2b6d013ce8 --- /dev/null +++ b/packages/design/src/shader-lab/IShaderLab.ts @@ -0,0 +1,8 @@ +import { IShaderInfo } from "./IShaderInfo"; + +/** + * Shader lab interface. + */ +export interface IShaderLab { + parseShader(shaderSource: string): IShaderInfo; +} diff --git a/packages/design/src/shader-lab/IShaderPassInfo.ts b/packages/design/src/shader-lab/IShaderPassInfo.ts new file mode 100644 index 0000000000..a456937c7f --- /dev/null +++ b/packages/design/src/shader-lab/IShaderPassInfo.ts @@ -0,0 +1,7 @@ +export interface IShaderPassInfo { + name: string; + vert: string; + frag: string; + tags?: Record; + renderStates: Record; +} diff --git a/packages/design/src/shader-lab/ISubShaderInfo.ts b/packages/design/src/shader-lab/ISubShaderInfo.ts new file mode 100644 index 0000000000..69b15e1097 --- /dev/null +++ b/packages/design/src/shader-lab/ISubShaderInfo.ts @@ -0,0 +1,6 @@ +import { IShaderPassInfo } from "./IShaderPassInfo"; + +export interface ISubShaderInfo { + passes: IShaderPassInfo[]; + tags?: Record; +} diff --git a/packages/design/src/shader-lab/index.ts b/packages/design/src/shader-lab/index.ts new file mode 100644 index 0000000000..e394c79b74 --- /dev/null +++ b/packages/design/src/shader-lab/index.ts @@ -0,0 +1,4 @@ +export type { IShaderInfo } from "./IShaderInfo"; +export type { IShaderLab } from "./IShaderLab"; +export type { IShaderPassInfo } from "./IShaderPassInfo"; +export type { ISubShaderInfo } from "./ISubShaderInfo"; diff --git a/packages/loader/src/AnimationClipLoader.ts b/packages/loader/src/AnimationClipLoader.ts index d70abdaea4..de638b27a8 100644 --- a/packages/loader/src/AnimationClipLoader.ts +++ b/packages/loader/src/AnimationClipLoader.ts @@ -18,9 +18,7 @@ class AnimationClipLoader extends Loader { ...item, type: "arraybuffer" }) - .then((data) => { - return decode(data, resourceManager.engine); - }) + .then((data) => decode(data, resourceManager.engine).then(resolve)) .catch(reject); }); } diff --git a/packages/loader/src/AnimatorControllerLoader.ts b/packages/loader/src/AnimatorControllerLoader.ts index 42034fcab2..023a446a50 100644 --- a/packages/loader/src/AnimatorControllerLoader.ts +++ b/packages/loader/src/AnimatorControllerLoader.ts @@ -48,7 +48,8 @@ class AnimatorControllerLoader extends Loader { state.wrapMode = wrapMode; state.clipStartTime = clipStartNormalizedTime; state.clipEndTime = clipEndNormalizedTime; - scripts?.forEach((script) => { + const scriptsObject = JSON.parse(scripts); + scriptsObject?.forEach((script) => { state.addStateMachineScript(Loader.getClass(script)); }); if (clipData) { diff --git a/packages/loader/src/EnvLoader.ts b/packages/loader/src/EnvLoader.ts index 2502460a90..0809c00c7f 100644 --- a/packages/loader/src/EnvLoader.ts +++ b/packages/loader/src/EnvLoader.ts @@ -23,7 +23,8 @@ class EnvLoader extends Loader { const shByteLength = 27 * 4; const size = new Uint16Array(arraybuffer, shByteLength, 1)?.[0]; - const texture = new TextureCube(resourceManager.engine, size); + const { engine } = resourceManager; + const texture = new TextureCube(engine, size); texture.filterMode = TextureFilterMode.Trilinear; const mipmapCount = texture.mipmapCount; let offset = shByteLength + 2; @@ -39,7 +40,7 @@ class EnvLoader extends Loader { } } - const ambientLight = new AmbientLight(); + const ambientLight = new AmbientLight(engine); const sh = new SphericalHarmonics3(); ambientLight.diffuseMode = DiffuseMode.SphericalHarmonics; diff --git a/packages/loader/src/GLTFContentRestorer.ts b/packages/loader/src/GLTFContentRestorer.ts index 0b1c1829f6..cd9d8160e0 100644 --- a/packages/loader/src/GLTFContentRestorer.ts +++ b/packages/loader/src/GLTFContentRestorer.ts @@ -12,6 +12,7 @@ import { Vector2 } from "@galacean/engine-math"; import { GLTFResource } from "./gltf/GLTFResource"; import type { IBufferView } from "./gltf/GLTFSchema"; import { GLTFUtils } from "./gltf/GLTFUtils"; +import { KTX2Loader } from "./ktx2/KTX2Loader"; /** * @internal @@ -49,11 +50,21 @@ export class GLTFContentRestorer extends ContentRestorer { const { bufferView } = textureRestoreInfo; const buffer = buffers[bufferView.buffer]; const bufferData = new Uint8Array(buffer, bufferView.byteOffset ?? 0, bufferView.byteLength); - - return GLTFUtils.loadImageBuffer(bufferData, textureRestoreInfo.mimeType).then((image) => { - textureRestoreInfo.texture.setImageSource(image); - textureRestoreInfo.texture.generateMipmaps(); - }); + const texture = textureRestoreInfo.texture; + if (textureRestoreInfo.mimeType === "image/ktx2") { + return KTX2Loader._parseBuffer(bufferData, texture.engine).then(({ result }) => { + const { faces } = result; + const mipmaps = faces[0]; + for (let i = 0; i < mipmaps.length; i++) { + texture.setPixelBuffer(mipmaps[i].data, i); + } + }); + } else { + return GLTFUtils.loadImageBuffer(bufferData, textureRestoreInfo.mimeType).then((image) => { + texture.setImageSource(image); + texture.generateMipmaps(); + }); + } }) ) .then(() => { diff --git a/packages/loader/src/GLTFLoader.ts b/packages/loader/src/GLTFLoader.ts index 3c7c72a365..45ca66103f 100644 --- a/packages/loader/src/GLTFLoader.ts +++ b/packages/loader/src/GLTFLoader.ts @@ -1,4 +1,12 @@ -import { AssetPromise, AssetType, Loader, LoadItem, resourceLoader, ResourceManager } from "@galacean/engine-core"; +import { + AssetPromise, + AssetType, + Loader, + LoadItem, + Logger, + resourceLoader, + ResourceManager +} from "@galacean/engine-core"; import { GLTFPipeline } from "./gltf/GLTFPipeline"; import { GLTFResource } from "./gltf/GLTFResource"; import { GLTFParserContext } from "./gltf/parser"; @@ -32,8 +40,14 @@ export class GLTFLoader extends Loader { masterPromiseInfo.resolve(glTFResource); }) .catch((e) => { - console.error(e); - masterPromiseInfo.reject(`Error loading glTF model from ${url} .`); + const msg = `Error loading glTF model from ${url} : ${e}`; + Logger.error(msg); + masterPromiseInfo.reject(msg); + context.defaultSceneRootPromiseInfo.reject(e); + context.texturesPromiseInfo.reject(e); + context.materialsPromiseInfo.reject(e); + context.meshesPromiseInfo.reject(e); + context.animationClipsPromiseInfo.reject(e); }); return context.promiseMap; diff --git a/packages/loader/src/Texture2DLoader.ts b/packages/loader/src/Texture2DLoader.ts index 60bbe9429c..241182f582 100644 --- a/packages/loader/src/Texture2DLoader.ts +++ b/packages/loader/src/Texture2DLoader.ts @@ -22,7 +22,7 @@ class Texture2DLoader extends Loader { }; this.request(url, requestConfig) .then((image) => { - const params = item.params; + const params = item.params as Texture2DParams; const texture = new Texture2D( resourceManager.engine, image.width, diff --git a/packages/loader/src/gltf/GLTFResource.ts b/packages/loader/src/gltf/GLTFResource.ts index a57dcb6aef..98c2740b7d 100644 --- a/packages/loader/src/gltf/GLTFResource.ts +++ b/packages/loader/src/gltf/GLTFResource.ts @@ -2,11 +2,11 @@ import { AnimationClip, Camera, Engine, - EngineObject, Entity, Light, Material, ModelMesh, + ReferResource, Skin, Texture2D } from "@galacean/engine-core"; @@ -14,7 +14,7 @@ import { /** * Product after glTF parser, usually, `defaultSceneRoot` is only needed to use. */ -export class GLTFResource extends EngineObject { +export class GLTFResource extends ReferResource { /** glTF file url. */ url: string; /** Texture2D after TextureParser. */ @@ -44,23 +44,4 @@ export class GLTFResource extends EngineObject { super(engine); this.url = url; } - - /** - * @internal - */ - protected override _onDestroy(): void { - super._onDestroy(); - this.defaultSceneRoot.destroy(); - - this.textures = null; - this.materials = null; - this.meshes = null; - this.skins = null; - this.animations = null; - this.entities = null; - this.cameras = null; - this.lights = null; - this.sceneRoots = null; - this.extensionsData = null; - } } diff --git a/packages/loader/src/gltf/GLTFSchema.ts b/packages/loader/src/gltf/GLTFSchema.ts index 293a4f45fb..5ab681027c 100644 --- a/packages/loader/src/gltf/GLTFSchema.ts +++ b/packages/loader/src/gltf/GLTFSchema.ts @@ -2,7 +2,7 @@ * Module for glTF 2.0 Interface */ -import { MeshTopology } from "@galacean/engine-core"; +import type { MeshTopology, TextureFilterMode, TextureWrapMode as EngineTextureWrapMode } from "@galacean/engine-core"; /** * The datatype of the components in the attribute @@ -856,3 +856,10 @@ export interface IGLTF extends IProperty { /** glTF extensible owner schema. */ export type GLTFExtensionOwnerSchema = IMeshPrimitive | IMaterial | ITextureInfo | INode; + +export interface ISamplerInfo { + filterMode?: TextureFilterMode; + wrapModeU?: EngineTextureWrapMode; + wrapModeV?: EngineTextureWrapMode; + mipmap?: boolean; +} diff --git a/packages/loader/src/gltf/GLTFUtils.ts b/packages/loader/src/gltf/GLTFUtils.ts index c13f6a825d..56217f0c5e 100644 --- a/packages/loader/src/gltf/GLTFUtils.ts +++ b/packages/loader/src/gltf/GLTFUtils.ts @@ -1,8 +1,26 @@ -import { IndexFormat, TypedArray, Utils, VertexElementFormat } from "@galacean/engine-core"; +import { + IndexFormat, + Texture2D, + TextureFilterMode, + TypedArray, + Utils, + VertexElementFormat +} from "@galacean/engine-core"; import { Color, Vector2, Vector3, Vector4 } from "@galacean/engine-math"; import { BufferDataRestoreInfo, RestoreDataAccessor } from "../GLTFContentRestorer"; -import { AccessorComponentType, AccessorType, IAccessor, IBufferView, IGLTF } from "./GLTFSchema"; +import { + AccessorComponentType, + AccessorType, + IAccessor, + IBufferView, + IGLTF, + ISampler, + ISamplerInfo, + TextureMagFilter, + TextureMinFilter +} from "./GLTFSchema"; import { BufferInfo, GLTFParserContext } from "./parser/GLTFParserContext"; +import { GLTFTextureParser } from "./parser"; /** * @internal @@ -110,54 +128,58 @@ export class GLTFUtils { } } - static getAccessorBuffer(context: GLTFParserContext, bufferViews: IBufferView[], accessor: IAccessor): BufferInfo { - const { buffers } = context; - + static getAccessorBuffer( + context: GLTFParserContext, + bufferViews: IBufferView[], + accessor: IAccessor + ): Promise { const componentType = accessor.componentType; const bufferView = bufferViews[accessor.bufferView]; - const bufferIndex = bufferView.buffer; - const buffer = buffers[bufferIndex]; - const bufferByteOffset = bufferView.byteOffset || 0; - const byteOffset = accessor.byteOffset || 0; - - const TypedArray = GLTFUtils.getComponentType(componentType); - const dataElementSize = GLTFUtils.getAccessorTypeSize(accessor.type); - const dataElementBytes = TypedArray.BYTES_PER_ELEMENT; - const elementStride = dataElementSize * dataElementBytes; - const accessorCount = accessor.count; - const bufferStride = bufferView.byteStride; - - let bufferInfo: BufferInfo; - // According to the glTF official documentation only byteStride not undefined is allowed - if (bufferStride !== undefined && bufferStride !== elementStride) { - const bufferSlice = Math.floor(byteOffset / bufferStride); - const bufferCacheKey = accessor.bufferView + ":" + componentType + ":" + bufferSlice + ":" + accessorCount; - const accessorBufferCache = context.accessorBufferCache; - bufferInfo = accessorBufferCache[bufferCacheKey]; - if (!bufferInfo) { - const offset = bufferByteOffset + bufferSlice * bufferStride; - const count = accessorCount * (bufferStride / dataElementBytes); + return context.getBuffers().then((buffers) => { + const bufferIndex = bufferView.buffer; + const buffer = buffers[bufferIndex]; + const bufferByteOffset = bufferView.byteOffset || 0; + const byteOffset = accessor.byteOffset || 0; + + const TypedArray = GLTFUtils.getComponentType(componentType); + const dataElementSize = GLTFUtils.getAccessorTypeSize(accessor.type); + const dataElementBytes = TypedArray.BYTES_PER_ELEMENT; + const elementStride = dataElementSize * dataElementBytes; + const accessorCount = accessor.count; + const bufferStride = bufferView.byteStride; + + let bufferInfo: BufferInfo; + // According to the glTF official documentation only byteStride not undefined is allowed + if (bufferStride !== undefined && bufferStride !== elementStride) { + const bufferSlice = Math.floor(byteOffset / bufferStride); + const bufferCacheKey = accessor.bufferView + ":" + componentType + ":" + bufferSlice + ":" + accessorCount; + const accessorBufferCache = context.accessorBufferCache; + bufferInfo = accessorBufferCache[bufferCacheKey]; + if (!bufferInfo) { + const offset = bufferByteOffset + bufferSlice * bufferStride; + const count = accessorCount * (bufferStride / dataElementBytes); + const data = new TypedArray(buffer, offset, count); + accessorBufferCache[bufferCacheKey] = bufferInfo = new BufferInfo(data, true, bufferStride); + bufferInfo.restoreInfo = new BufferDataRestoreInfo( + new RestoreDataAccessor(bufferIndex, TypedArray, offset, count) + ); + } + } else { + const offset = bufferByteOffset + byteOffset; + const count = accessorCount * dataElementSize; const data = new TypedArray(buffer, offset, count); - accessorBufferCache[bufferCacheKey] = bufferInfo = new BufferInfo(data, true, bufferStride); + bufferInfo = new BufferInfo(data, false, elementStride); bufferInfo.restoreInfo = new BufferDataRestoreInfo( new RestoreDataAccessor(bufferIndex, TypedArray, offset, count) ); } - } else { - const offset = bufferByteOffset + byteOffset; - const count = accessorCount * dataElementSize; - const data = new TypedArray(buffer, offset, count); - bufferInfo = new BufferInfo(data, false, elementStride); - bufferInfo.restoreInfo = new BufferDataRestoreInfo( - new RestoreDataAccessor(bufferIndex, TypedArray, offset, count) - ); - } - if (accessor.sparse) { - GLTFUtils.processingSparseData(bufferViews, accessor, buffers, bufferInfo); - } - return bufferInfo; + if (accessor.sparse) { + GLTFUtils.processingSparseData(bufferViews, accessor, buffers, bufferInfo); + } + return bufferInfo; + }); } /** @@ -444,16 +466,48 @@ export class GLTFUtils { }; } - private static _formatRelativePath(path: string): string { - // For example input is "a/b", "/a/b", "./a/b", "./a/./b", "./a/../a/b", output is "a/b" - return path - .split("/") - .filter(Boolean) - .reduce((acc, cur) => { - if (cur === "..") acc.pop(); - else if (cur !== ".") acc.push(cur); - return acc; - }, []) - .join("/"); + static parseSampler(texture: Texture2D, samplerInfo: ISamplerInfo): void { + const { filterMode, wrapModeU, wrapModeV } = samplerInfo; + + if (filterMode !== undefined) { + texture.filterMode = filterMode; + } + + if (wrapModeU !== undefined) { + texture.wrapModeU = wrapModeU; + } + + if (wrapModeV !== undefined) { + texture.wrapModeV = wrapModeV; + } + } + + static getSamplerInfo(sampler: ISampler): ISamplerInfo { + const { minFilter, magFilter, wrapS, wrapT } = sampler; + const info = {}; + + if (minFilter || magFilter) { + info.mipmap = minFilter >= TextureMinFilter.NEAREST_MIPMAP_NEAREST; + + if (magFilter === TextureMagFilter.NEAREST) { + info.filterMode = TextureFilterMode.Point; + } else { + if (minFilter <= TextureMinFilter.LINEAR_MIPMAP_NEAREST) { + info.filterMode = TextureFilterMode.Bilinear; + } else { + info.filterMode = TextureFilterMode.Trilinear; + } + } + } + + if (wrapS) { + info.wrapModeU = GLTFTextureParser._wrapMap[wrapS]; + } + + if (wrapT) { + info.wrapModeV = GLTFTextureParser._wrapMap[wrapT]; + } + + return info; } } diff --git a/packages/loader/src/gltf/extensions/KHR_draco_mesh_compression.ts b/packages/loader/src/gltf/extensions/KHR_draco_mesh_compression.ts index 68f5b80a79..e6d8c5e471 100644 --- a/packages/loader/src/gltf/extensions/KHR_draco_mesh_compression.ts +++ b/packages/loader/src/gltf/extensions/KHR_draco_mesh_compression.ts @@ -28,7 +28,6 @@ class KHR_draco_mesh_compression extends GLTFExtensionParser { ) { const { glTF, - buffers, glTFResource: { engine } } = context; const { bufferViews, accessors } = glTF; @@ -53,30 +52,33 @@ class KHR_draco_mesh_compression extends GLTFExtensionParser { useUniqueIDs: true, indexType }; - const buffer = GLTFUtils.getBufferViewData(bufferViews[bufferViewIndex], buffers); - return KHR_draco_mesh_compression._decoder.decode(buffer, taskConfig).then((decodedGeometry) => { - const mesh = new ModelMesh(engine, glTFMesh.name); - return this._parseMeshFromGLTFPrimitiveDraco( - mesh, - glTFMesh, - glTFPrimitive, - glTF, - (attributeSemantic) => { - for (let j = 0; j < decodedGeometry.attributes.length; j++) { - if (decodedGeometry.attributes[j].name === attributeSemantic) { - return decodedGeometry.attributes[j].array; + + return context.getBuffers().then((buffers) => { + const buffer = GLTFUtils.getBufferViewData(bufferViews[bufferViewIndex], buffers); + return KHR_draco_mesh_compression._decoder.decode(buffer, taskConfig).then((decodedGeometry) => { + const mesh = new ModelMesh(engine, glTFMesh.name); + return this._parseMeshFromGLTFPrimitiveDraco( + mesh, + glTFMesh, + glTFPrimitive, + glTF, + (attributeSemantic) => { + for (let j = 0; j < decodedGeometry.attributes.length; j++) { + if (decodedGeometry.attributes[j].name === attributeSemantic) { + return decodedGeometry.attributes[j].array; + } } - } - return null; - }, - (attributeSemantic, shapeIndex) => { - throw "BlendShape animation is not supported when using draco."; - }, - () => { - return decodedGeometry.index.array; - }, - context.keepMeshData - ); + return null; + }, + (attributeSemantic, shapeIndex) => { + throw "BlendShape animation is not supported when using draco."; + }, + () => { + return decodedGeometry.index.array; + }, + context.keepMeshData + ); + }); }); } @@ -86,7 +88,7 @@ class KHR_draco_mesh_compression extends GLTFExtensionParser { gltfPrimitive: IMeshPrimitive, gltf: IGLTF, getVertexBufferData: (semantic: string) => TypedArray, - getBlendShapeData: (semantic: string, shapeIndex: number) => BufferInfo, + getBlendShapeData: (semantic: string, shapeIndex: number) => Promise, getIndexBufferData: () => TypedArray, keepMeshData: boolean ): Promise { diff --git a/packages/loader/src/gltf/extensions/KHR_texture_basisu.ts b/packages/loader/src/gltf/extensions/KHR_texture_basisu.ts index e69de29bb2..fe3cb1073b 100644 --- a/packages/loader/src/gltf/extensions/KHR_texture_basisu.ts +++ b/packages/loader/src/gltf/extensions/KHR_texture_basisu.ts @@ -0,0 +1,67 @@ +import { AssetType, Texture2D, Utils } from "@galacean/engine-core"; +import type { ITexture } from "../GLTFSchema"; +import { registerGLTFExtension } from "../parser/GLTFParser"; +import { GLTFParserContext } from "../parser/GLTFParserContext"; +import { GLTFExtensionMode, GLTFExtensionParser } from "./GLTFExtensionParser"; +import { GLTFUtils } from "../GLTFUtils"; +import { BufferTextureRestoreInfo } from "../../GLTFContentRestorer"; +import { KTX2Loader } from "../../ktx2/KTX2Loader"; + +interface KHRBasisSchema { + source: number; +} + +@registerGLTFExtension("KHR_texture_basisu", GLTFExtensionMode.CreateAndParse) +class KHR_texture_basisu extends GLTFExtensionParser { + override async createAndParse( + context: GLTFParserContext, + schema: KHRBasisSchema, + textureInfo: ITexture + ): Promise { + const { glTF, glTFResource } = context; + const { engine, url } = glTFResource; + + const { sampler, name: textureName } = textureInfo; + const { source } = schema; + const { uri, bufferView: bufferViewIndex, mimeType, name: imageName } = glTF.images[source]; + const samplerInfo = sampler !== undefined && GLTFUtils.getSamplerInfo(glTF.samplers[sampler]); + if (uri) { + const index = uri.lastIndexOf("."); + return engine.resourceManager + .load({ + url: Utils.resolveAbsoluteUrl(url, uri), + type: AssetType.KTX2 + }) + .then((texture) => { + if (!texture.name) { + texture.name = textureName || imageName || `texture_${index}`; + } + if (sampler !== undefined) { + GLTFUtils.parseSampler(texture, samplerInfo); + } + return texture; + }); + } else { + const bufferView = glTF.bufferViews[bufferViewIndex]; + + return context.getBuffers().then((buffers) => { + const buffer = buffers[bufferView.buffer]; + const imageBuffer = new Uint8Array(buffer, bufferView.byteOffset, bufferView.byteLength); + + return KTX2Loader._parseBuffer(imageBuffer, engine) + .then(({ engine, result, targetFormat, params }) => + KTX2Loader._createTextureByBuffer(engine, result, targetFormat, params) + ) + .then((texture: Texture2D) => { + texture.name = textureName || imageName || `texture_${bufferViewIndex}`; + if (sampler !== undefined) { + GLTFUtils.parseSampler(texture, samplerInfo); + } + const bufferTextureRestoreInfo = new BufferTextureRestoreInfo(texture, bufferView, mimeType); + context.contentRestorer.bufferTextures.push(bufferTextureRestoreInfo); + return texture; + }); + }); + } + } +} diff --git a/packages/loader/src/gltf/parser/GLTFAnimationParser.ts b/packages/loader/src/gltf/parser/GLTFAnimationParser.ts index 62a4c50154..bbe70fe128 100644 --- a/packages/loader/src/gltf/parser/GLTFAnimationParser.ts +++ b/packages/loader/src/gltf/parser/GLTFAnimationParser.ts @@ -28,7 +28,11 @@ export class GLTFAnimationParser extends GLTFParser { /** * @internal */ - static _parseStandardProperty(context: GLTFParserContext, animationClip: AnimationClip, animationInfo: IAnimation) { + static _parseStandardProperty( + context: GLTFParserContext, + animationClip: AnimationClip, + animationInfo: IAnimation + ): Promise { const { glTF, glTFResource } = context; const { entities } = glTFResource; const { accessors, bufferViews } = glTF; @@ -37,92 +41,99 @@ export class GLTFAnimationParser extends GLTFParser { const sampleDataCollection = new Array(); let duration = -1; - + let promises = new Array>(); // parse samplers for (let j = 0, m = samplers.length; j < m; j++) { const gltfSampler = samplers[j]; const inputAccessor = accessors[gltfSampler.input]; const outputAccessor = accessors[gltfSampler.output]; - const input = GLTFUtils.getAccessorBuffer(context, bufferViews, inputAccessor).data; - let output = GLTFUtils.getAccessorBuffer(context, bufferViews, outputAccessor).data; - - if (outputAccessor.normalized) { - const scale = GLTFUtils.getNormalizedComponentScale(outputAccessor.componentType); - const scaled = new Float32Array(output.length); - for (let k = 0, v = output.length; k < v; k++) { - scaled[k] = output[k] * scale; + const promise = Promise.all([ + GLTFUtils.getAccessorBuffer(context, bufferViews, inputAccessor), + GLTFUtils.getAccessorBuffer(context, bufferViews, outputAccessor) + ]).then((bufferInfos) => { + const input = bufferInfos[0].data; + let output = bufferInfos[1].data; + if (outputAccessor.normalized) { + const scale = GLTFUtils.getNormalizedComponentScale(outputAccessor.componentType); + const scaled = new Float32Array(output.length); + for (let k = 0, v = output.length; k < v; k++) { + scaled[k] = output[k] * scale; + } + output = scaled; } - output = scaled; - } - const outputStride = output.length / input.length; - - const interpolation = gltfSampler.interpolation ?? AnimationSamplerInterpolation.Linear; - let samplerInterpolation: InterpolationType; - switch (interpolation) { - case AnimationSamplerInterpolation.CubicSpine: - samplerInterpolation = InterpolationType.CubicSpine; - break; - case AnimationSamplerInterpolation.Step: - samplerInterpolation = InterpolationType.Step; - break; - case AnimationSamplerInterpolation.Linear: - samplerInterpolation = InterpolationType.Linear; - break; - } + const outputStride = output.length / input.length; - const maxTime = input[input.length - 1]; - if (maxTime > duration) { - duration = maxTime; - } + const interpolation = gltfSampler.interpolation ?? AnimationSamplerInterpolation.Linear; + let samplerInterpolation: InterpolationType; + switch (interpolation) { + case AnimationSamplerInterpolation.CubicSpine: + samplerInterpolation = InterpolationType.CubicSpine; + break; + case AnimationSamplerInterpolation.Step: + samplerInterpolation = InterpolationType.Step; + break; + case AnimationSamplerInterpolation.Linear: + samplerInterpolation = InterpolationType.Linear; + break; + } + + const maxTime = input[input.length - 1]; + if (maxTime > duration) { + duration = maxTime; + } - sampleDataCollection.push({ - type: outputAccessor.type, - interpolation: samplerInterpolation, - input, - output, - outputSize: outputStride + sampleDataCollection.push({ + type: outputAccessor.type, + interpolation: samplerInterpolation, + input, + output, + outputSize: outputStride + }); }); + promises.push(promise); } - for (let j = 0, m = channels.length; j < m; j++) { - const gltfChannel = channels[j]; - const { target } = gltfChannel; + return Promise.all(promises).then(() => { + for (let j = 0, m = channels.length; j < m; j++) { + const gltfChannel = channels[j]; + const { target } = gltfChannel; - const channelTargetEntity = entities[target.node]; - let relativePath = ""; - let entity = channelTargetEntity; - while (entity.parent) { - relativePath = relativePath === "" ? `${entity.name}` : `${entity.name}/${relativePath}`; - entity = entity.parent; - } + const channelTargetEntity = entities[target.node]; + let relativePath = ""; + let entity = channelTargetEntity; + while (entity.parent) { + relativePath = relativePath === "" ? `${entity.name}` : `${entity.name}/${relativePath}`; + entity = entity.parent; + } - let ComponentType: new (entity: Entity) => Component; - let propertyName: string; - switch (target.path) { - case AnimationChannelTargetPath.TRANSLATION: - ComponentType = Transform; - propertyName = "position"; - break; - case AnimationChannelTargetPath.ROTATION: - ComponentType = Transform; - propertyName = "rotationQuaternion"; - break; - case AnimationChannelTargetPath.SCALE: - ComponentType = Transform; - propertyName = "scale"; - break; - case AnimationChannelTargetPath.WEIGHTS: - ComponentType = SkinnedMeshRenderer; - propertyName = "blendShapeWeights"; - break; - default: - } + let ComponentType: new (entity: Entity) => Component; + let propertyName: string; + switch (target.path) { + case AnimationChannelTargetPath.TRANSLATION: + ComponentType = Transform; + propertyName = "position"; + break; + case AnimationChannelTargetPath.ROTATION: + ComponentType = Transform; + propertyName = "rotationQuaternion"; + break; + case AnimationChannelTargetPath.SCALE: + ComponentType = Transform; + propertyName = "scale"; + break; + case AnimationChannelTargetPath.WEIGHTS: + ComponentType = SkinnedMeshRenderer; + propertyName = "blendShapeWeights"; + break; + default: + } - const curve = this._addCurve(target.path, gltfChannel, sampleDataCollection); - animationClip.addCurveBinding(relativePath, ComponentType, propertyName, curve); - } + const curve = this._addCurve(target.path, gltfChannel, sampleDataCollection); + animationClip.addCurveBinding(relativePath, ComponentType, propertyName, curve); + } + }); } private static _addCurve( @@ -199,8 +210,8 @@ export class GLTFAnimationParser extends GLTFParser { } } - parse(context: GLTFParserContext): AssetPromise { - const { glTF, buffers, glTFResource } = context; + parse(context: GLTFParserContext): AssetPromise | void { + const { glTF, glTFResource } = context; const { entities } = glTFResource; const { animations, accessors, bufferViews } = glTF; if (!animations) { @@ -215,6 +226,7 @@ export class GLTFAnimationParser extends GLTFParser { index: number; }>(animationClipCount); + let parseStandardPropertyPromises = new Array>(); for (let i = 0; i < animationClipCount; i++) { const animationInfo = animations[i]; const { name = `AnimationClip${i}` } = animationInfo; @@ -225,24 +237,29 @@ export class GLTFAnimationParser extends GLTFParser { if (!animationClip) { animationClip = new AnimationClip(name); - GLTFAnimationParser._parseStandardProperty(context, animationClip, animationInfo); + parseStandardPropertyPromises.push( + GLTFAnimationParser._parseStandardProperty(context, animationClip, animationInfo) + ); } animationClipPromises.push(animationClip); } - return AssetPromise.all(animationClipPromises).then((animationClips) => { - glTFResource.animations = animationClips; - for (let i = 0; i < glTF.animations.length; i++) { - const animationInfo = glTF.animations[i]; - GLTFParser.executeExtensionsAdditiveAndParse( - animationInfo.extensions, - context, - animationClips[i], - animationInfo - ); - } - animationClipsPromiseInfo.resolve(animationClips); - return animationClipsPromiseInfo.promise; + + return AssetPromise.all(parseStandardPropertyPromises).then(() => { + return AssetPromise.all(animationClipPromises).then((animationClips) => { + glTFResource.animations = animationClips; + for (let i = 0; i < glTF.animations.length; i++) { + const animationInfo = glTF.animations[i]; + GLTFParser.executeExtensionsAdditiveAndParse( + animationInfo.extensions, + context, + animationClips[i], + animationInfo + ); + } + animationClipsPromiseInfo.resolve(animationClips); + return animationClipsPromiseInfo.promise; + }); }); } } diff --git a/packages/loader/src/gltf/parser/GLTFBufferParser.ts b/packages/loader/src/gltf/parser/GLTFBufferParser.ts index d1cf26ae3f..8d505a2ae2 100644 --- a/packages/loader/src/gltf/parser/GLTFBufferParser.ts +++ b/packages/loader/src/gltf/parser/GLTFBufferParser.ts @@ -23,7 +23,7 @@ export class GLTFBufferParser extends GLTFParser { }) .then(({ glTF, buffers }) => { context.glTF = glTF; - context.buffers = buffers; + context._buffers = buffers; }); } else { return request(url, { @@ -31,15 +31,13 @@ export class GLTFBufferParser extends GLTFParser { }).then((glTF: IGLTF) => { context.glTF = glTF; - return Promise.all( + context._buffers = Promise.all( glTF.buffers.map((buffer: IBuffer) => { const absoluteUrl = Utils.resolveAbsoluteUrl(url, buffer.uri); restoreBufferRequests.push(new BufferRequestInfo(absoluteUrl, requestConfig)); return request(absoluteUrl, requestConfig); }) - ).then((buffers: ArrayBuffer[]) => { - context.buffers = buffers; - }); + ); }); } } diff --git a/packages/loader/src/gltf/parser/GLTFEntityParser.ts b/packages/loader/src/gltf/parser/GLTFEntityParser.ts index 0921ad0f77..ff93ac22dd 100644 --- a/packages/loader/src/gltf/parser/GLTFEntityParser.ts +++ b/packages/loader/src/gltf/parser/GLTFEntityParser.ts @@ -95,5 +95,9 @@ export class GLTFEntityParser extends GLTFParser { glTFResource.sceneRoots = sceneRoots; glTFResource.defaultSceneRoot = sceneRoots[sceneID]; + // @ts-ignore + glTFResource.defaultSceneRoot._hookResource = glTFResource; + // @ts-ignore + glTFResource._addReferCount(1); } } diff --git a/packages/loader/src/gltf/parser/GLTFMeshParser.ts b/packages/loader/src/gltf/parser/GLTFMeshParser.ts index a97320a706..a883ebee43 100644 --- a/packages/loader/src/gltf/parser/GLTFMeshParser.ts +++ b/packages/loader/src/gltf/parser/GLTFMeshParser.ts @@ -29,12 +29,11 @@ export class GLTFMeshParser extends GLTFParser { gltfPrimitive: IMeshPrimitive, gltf: IGLTF, getVertexBufferData: (semantic: string) => TypedArray, - getBlendShapeData: (semantic: string, shapeIndex: number) => BufferInfo, - getIndexBufferData: () => TypedArray, + getBlendShapeData: (semantic: string, shapeIndex: number) => Promise, + getIndexBufferData: () => Promise, keepMeshData: boolean ): Promise { const { accessors } = gltf; - const { buffers } = context; const { attributes, targets, indices, mode } = gltfPrimitive; const engine = mesh.engine; @@ -43,107 +42,119 @@ export class GLTFMeshParser extends GLTFParser { let vertexCount: number; let bufferBindIndex = 0; + const promises = new Array>(); for (const attribute in attributes) { const accessor = accessors[attributes[attribute]]; - const accessorBuffer = GLTFUtils.getAccessorBuffer(context, gltf.bufferViews, accessor); - - const dataElementSize = GLTFUtils.getAccessorTypeSize(accessor.type); - const accessorCount = accessor.count; - const vertices = accessorBuffer.data; - - let vertexElement: VertexElement; - const meshId = mesh.instanceId; - const vertexBindingInfos = accessorBuffer.vertexBindingInfos; - const elementNormalized = accessor.normalized; - const elementFormat = GLTFUtils.getElementFormat(accessor.componentType, dataElementSize, elementNormalized); - - let scaleFactor: number; - elementNormalized && (scaleFactor = GLTFUtils.getNormalizedComponentScale(accessor.componentType)); - - let elementOffset: number; - if (accessorBuffer.interleaved) { - const byteOffset = accessor.byteOffset || 0; - const stride = accessorBuffer.stride; - elementOffset = byteOffset % stride; - if (vertexBindingInfos[meshId] === undefined) { + const promise = GLTFUtils.getAccessorBuffer(context, gltf.bufferViews, accessor).then((accessorBuffer) => { + const dataElementSize = GLTFUtils.getAccessorTypeSize(accessor.type); + const accessorCount = accessor.count; + const vertices = accessorBuffer.data; + + let vertexElement: VertexElement; + const meshId = mesh.instanceId; + const vertexBindingInfos = accessorBuffer.vertexBindingInfos; + const elementNormalized = accessor.normalized; + const elementFormat = GLTFUtils.getElementFormat(accessor.componentType, dataElementSize, elementNormalized); + + let scaleFactor: number; + elementNormalized && (scaleFactor = GLTFUtils.getNormalizedComponentScale(accessor.componentType)); + + let elementOffset: number; + if (accessorBuffer.interleaved) { + const byteOffset = accessor.byteOffset || 0; + const stride = accessorBuffer.stride; + elementOffset = byteOffset % stride; + if (vertexBindingInfos[meshId] === undefined) { + vertexElement = new VertexElement(attribute, elementOffset, elementFormat, bufferBindIndex); + + let vertexBuffer = accessorBuffer.vertexBuffer; + if (!vertexBuffer) { + vertexBuffer = new Buffer( + engine, + BufferBindFlag.VertexBuffer, + vertices, + BufferUsage.Static, + keepMeshData + ); + accessorBuffer.vertexBuffer = vertexBuffer; + meshRestoreInfo.vertexBuffers.push(new BufferRestoreInfo(vertexBuffer, accessorBuffer.restoreInfo)); + } + mesh.setVertexBufferBinding(vertexBuffer, stride, bufferBindIndex); + vertexBindingInfos[meshId] = bufferBindIndex++; + } else { + vertexElement = new VertexElement(attribute, elementOffset, elementFormat, vertexBindingInfos[meshId]); + } + } else { + elementOffset = 0; vertexElement = new VertexElement(attribute, elementOffset, elementFormat, bufferBindIndex); let vertexBuffer = accessorBuffer.vertexBuffer; if (!vertexBuffer) { vertexBuffer = new Buffer(engine, BufferBindFlag.VertexBuffer, vertices, BufferUsage.Static, keepMeshData); - accessorBuffer.vertexBuffer = vertexBuffer; meshRestoreInfo.vertexBuffers.push(new BufferRestoreInfo(vertexBuffer, accessorBuffer.restoreInfo)); } - mesh.setVertexBufferBinding(vertexBuffer, stride, bufferBindIndex); + mesh.setVertexBufferBinding(vertexBuffer, accessorBuffer.stride, bufferBindIndex); vertexBindingInfos[meshId] = bufferBindIndex++; - } else { - vertexElement = new VertexElement(attribute, elementOffset, elementFormat, vertexBindingInfos[meshId]); } - } else { - elementOffset = 0; - vertexElement = new VertexElement(attribute, elementOffset, elementFormat, bufferBindIndex); - - const vertexBuffer = new Buffer( - engine, - BufferBindFlag.VertexBuffer, - vertices.byteLength, - BufferUsage.Static, - keepMeshData - ); - vertexBuffer.setData(vertices); - meshRestoreInfo.vertexBuffers.push(new BufferRestoreInfo(vertexBuffer, accessorBuffer.restoreInfo)); + vertexElements.push(vertexElement); - mesh.setVertexBufferBinding(vertexBuffer, accessorBuffer.stride, bufferBindIndex); - vertexBindingInfos[meshId] = bufferBindIndex++; - } - vertexElements.push(vertexElement); - - if (attribute === "POSITION") { - vertexCount = accessorCount; + if (attribute === "POSITION") { + vertexCount = accessorCount; - const { min, max } = mesh.bounds; - if (accessor.min && accessor.max) { - min.copyFromArray(accessor.min); - max.copyFromArray(accessor.max); - } else { - const position = GLTFMeshParser._tempVector3; - min.set(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE); - max.set(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE); - - const baseOffset = elementOffset / vertices.BYTES_PER_ELEMENT; - const stride = vertices.length / accessorCount; - for (let j = 0; j < accessorCount; j++) { - const offset = baseOffset + j * stride; - position.copyFromArray(vertices, offset); - Vector3.min(min, position, min); - Vector3.max(max, position, max); + const { min, max } = mesh.bounds; + if (accessor.min && accessor.max) { + min.copyFromArray(accessor.min); + max.copyFromArray(accessor.max); + } else { + const position = GLTFMeshParser._tempVector3; + min.set(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE); + max.set(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE); + + const baseOffset = elementOffset / vertices.BYTES_PER_ELEMENT; + const stride = vertices.length / accessorCount; + for (let j = 0; j < accessorCount; j++) { + const offset = baseOffset + j * stride; + position.copyFromArray(vertices, offset); + Vector3.min(min, position, min); + Vector3.max(max, position, max); + } + } + if (elementNormalized) { + min.scale(scaleFactor); + max.scale(scaleFactor); } } - if (elementNormalized) { - min.scale(scaleFactor); - max.scale(scaleFactor); - } - } - } - mesh.setVertexElements(vertexElements); - - // Indices - if (indices !== undefined) { - const indexAccessor = gltf.accessors[indices]; - const accessorBuffer = GLTFUtils.getAccessorBuffer(context, gltf.bufferViews, indexAccessor); - mesh.setIndices(accessorBuffer.data); - mesh.addSubMesh(0, indexAccessor.count, mode); - meshRestoreInfo.indexBuffer = accessorBuffer.restoreInfo; - } else { - mesh.addSubMesh(0, vertexCount, mode); + }); + promises.push(promise); } - // BlendShapes - targets && GLTFMeshParser._createBlendShape(mesh, meshRestoreInfo, gltfMesh, targets, getBlendShapeData); + return Promise.all(promises).then(() => { + mesh.setVertexElements(vertexElements); + + // Indices + if (indices !== undefined) { + const indexAccessor = gltf.accessors[indices]; + const promise = GLTFUtils.getAccessorBuffer(context, gltf.bufferViews, indexAccessor).then((accessorBuffer) => { + mesh.setIndices(accessorBuffer.data); + mesh.addSubMesh(0, indexAccessor.count, mode); + meshRestoreInfo.indexBuffer = accessorBuffer.restoreInfo; + }); + promises.push(promise); + } else { + mesh.addSubMesh(0, vertexCount, mode); + } + + // BlendShapes + if (targets) { + promises.push(GLTFMeshParser._createBlendShape(mesh, meshRestoreInfo, gltfMesh, targets, getBlendShapeData)); + } - mesh.uploadData(!keepMeshData); + return Promise.all(promises).then(() => { + mesh.uploadData(!keepMeshData); - return Promise.resolve(mesh); + return Promise.resolve(mesh); + }); + }); } /** @@ -156,43 +167,51 @@ export class GLTFMeshParser extends GLTFParser { glTFTargets: { [name: string]: number; }[], - getBlendShapeData: (semantic: string, shapeIndex: number) => BufferInfo - ): void { + getBlendShapeData: (semantic: string, shapeIndex: number) => Promise + ): Promise { const blendShapeNames = glTFMesh.extras ? glTFMesh.extras.targetNames : null; - + let promises = new Array>(); for (let i = 0, n = glTFTargets.length; i < n; i++) { const name = blendShapeNames ? blendShapeNames[i] : `blendShape${i}`; - const deltaPosBufferInfo = getBlendShapeData("POSITION", i); - const deltaNorBufferInfo = getBlendShapeData("NORMAL", i); - const deltaTanBufferInfo = getBlendShapeData("TANGENT", i); - - const deltaPositions = deltaPosBufferInfo.data - ? GLTFUtils.floatBufferToVector3Array(deltaPosBufferInfo.data) - : null; - const deltaNormals = deltaNorBufferInfo?.data - ? GLTFUtils.floatBufferToVector3Array(deltaNorBufferInfo?.data) - : null; - const deltaTangents = deltaTanBufferInfo?.data - ? GLTFUtils.floatBufferToVector3Array(deltaTanBufferInfo?.data) - : null; - - const blendShape = new BlendShape(name); - blendShape.addFrame(1.0, deltaPositions, deltaNormals, deltaTangents); - mesh.addBlendShape(blendShape); - meshRestoreInfo.blendShapes.push( - new BlendShapeRestoreInfo( - blendShape, - deltaPosBufferInfo.restoreInfo, - deltaNorBufferInfo?.restoreInfo, - deltaTanBufferInfo?.restoreInfo - ) - ); + const promise = Promise.all([ + getBlendShapeData("POSITION", i), + getBlendShapeData("NORMAL", i), + getBlendShapeData("TANGENT", i) + ]).then((infos) => { + const deltaPosBufferInfo = infos[0]; + const deltaNorBufferInfo = infos[1]; + const deltaTanBufferInfo = infos[2]; + const deltaPositions = deltaPosBufferInfo.data + ? GLTFUtils.floatBufferToVector3Array(deltaPosBufferInfo.data) + : null; + const deltaNormals = deltaNorBufferInfo?.data + ? GLTFUtils.floatBufferToVector3Array(deltaNorBufferInfo?.data) + : null; + const deltaTangents = deltaTanBufferInfo?.data + ? GLTFUtils.floatBufferToVector3Array(deltaTanBufferInfo?.data) + : null; + + const blendShape = new BlendShape(name); + blendShape.addFrame(1.0, deltaPositions, deltaNormals, deltaTangents); + mesh.addBlendShape(blendShape); + meshRestoreInfo.blendShapes.push( + new BlendShapeRestoreInfo( + blendShape, + deltaPosBufferInfo.restoreInfo, + deltaNorBufferInfo?.restoreInfo, + deltaTanBufferInfo?.restoreInfo + ) + ); + }); + promises.push(promise); } + + return Promise.all(promises); } - parse(context: GLTFParserContext) { - const { glTF, buffers, glTFResource } = context; + parse(context: GLTFParserContext): AssetPromise | void { + const { glTF, glTFResource } = context; const { engine } = glTFResource; if (!glTF.meshes) return; @@ -246,7 +265,9 @@ export class GLTFMeshParser extends GLTFParser { }, () => { const indexAccessor = glTF.accessors[gltfPrimitive.indices]; - return GLTFUtils.getAccessorData(glTF, indexAccessor, buffers); + return context.getBuffers().then((buffers) => { + return GLTFUtils.getAccessorData(glTF, indexAccessor, buffers); + }); }, context.keepMeshData ).then(resolve); diff --git a/packages/loader/src/gltf/parser/GLTFParserContext.ts b/packages/loader/src/gltf/parser/GLTFParserContext.ts index cc93f1a73c..f035742322 100644 --- a/packages/loader/src/gltf/parser/GLTFParserContext.ts +++ b/packages/loader/src/gltf/parser/GLTFParserContext.ts @@ -17,7 +17,6 @@ import type { IGLTF } from "../GLTFSchema"; */ export class GLTFParserContext { glTF: IGLTF; - buffers: ArrayBuffer[]; glTFResource: GLTFResource; keepMeshData: boolean; hasSkinned: boolean = false; @@ -34,6 +33,9 @@ export class GLTFParserContext { contentRestorer: GLTFContentRestorer; + /** @internal */ + _buffers: ArrayBuffer[] | Promise; + constructor(url: string) { const promiseMap = this.promiseMap; promiseMap[`${url}?q=textures`] = this._initPromiseInfo(this.texturesPromiseInfo); @@ -44,6 +46,13 @@ export class GLTFParserContext { promiseMap[`${url}`] = this._initPromiseInfo(this.masterPromiseInfo); } + /** + * Get all the buffer data. + */ + getBuffers(): Promise { + return Promise.resolve(this._buffers); + } + private _initPromiseInfo(promiseInfo: PromiseInfo): AssetPromise { const promise = new AssetPromise((resolve, reject, setProgress, onCancel) => { promiseInfo.resolve = resolve; diff --git a/packages/loader/src/gltf/parser/GLTFSceneParser.ts b/packages/loader/src/gltf/parser/GLTFSceneParser.ts index 4bd92dbeff..ca6e4307e6 100644 --- a/packages/loader/src/gltf/parser/GLTFSceneParser.ts +++ b/packages/loader/src/gltf/parser/GLTFSceneParser.ts @@ -3,6 +3,7 @@ import { AnimatorController, AnimatorControllerLayer, AnimatorStateMachine, + AssetPromise, BlinnPhongMaterial, Camera, Engine, @@ -26,7 +27,7 @@ export class GLTFSceneParser extends GLTFParser { return GLTFSceneParser._defaultMaterial; } - parse(context: GLTFParserContext) { + parse(context: GLTFParserContext): AssetPromise | void { const { glTFResource, glTF } = context; const { entities } = glTFResource; const { nodes, cameras } = glTF; diff --git a/packages/loader/src/gltf/parser/GLTFSkinParser.ts b/packages/loader/src/gltf/parser/GLTFSkinParser.ts index eb45f080a9..3a71a269c5 100644 --- a/packages/loader/src/gltf/parser/GLTFSkinParser.ts +++ b/packages/loader/src/gltf/parser/GLTFSkinParser.ts @@ -1,19 +1,19 @@ -import { Entity, Skin } from "@galacean/engine-core"; +import { AssetPromise, Entity, Skin } from "@galacean/engine-core"; import { Matrix } from "@galacean/engine-math"; import { GLTFParserContext } from "."; import { GLTFUtils } from "../GLTFUtils"; import { GLTFParser } from "./GLTFParser"; export class GLTFSkinParser extends GLTFParser { - parse(context: GLTFParserContext): void { - const { glTFResource, glTF, buffers } = context; + parse(context: GLTFParserContext): AssetPromise { + const { glTFResource, glTF } = context; const { entities } = glTFResource; const gltfSkins = glTF.skins; if (!gltfSkins) return; const count = gltfSkins.length; - const skins = new Array(count); + const promises = new Array>(); for (let i = 0; i < count; i++) { const { inverseBindMatrices, skeleton, joints, name = `SKIN_${i}` } = gltfSkins[i]; @@ -24,42 +24,46 @@ export class GLTFSkinParser extends GLTFParser { // parse IBM const accessor = glTF.accessors[inverseBindMatrices]; - const buffer = GLTFUtils.getAccessorBuffer(context, glTF.bufferViews, accessor).data; - for (let i = 0; i < jointCount; i++) { - const inverseBindMatrix = new Matrix(); - inverseBindMatrix.copyFromArray(buffer, i * 16); - skin.inverseBindMatrices[i] = inverseBindMatrix; - } - - // get joints - for (let i = 0; i < jointCount; i++) { - const jointIndex = joints[i]; - const jointName = entities[jointIndex].name; - skin.joints[i] = jointName; - // @todo Temporary solution, but it can alleviate the current BUG, and the skinning data mechanism of SkinnedMeshRenderer will be completely refactored in the future - for (let j = entities.length - 1; j >= 0; j--) { - if (jointIndex !== j && entities[j].name === jointName) { - entities[j].name = `${jointName}_${j}`; + const promise = GLTFUtils.getAccessorBuffer(context, glTF.bufferViews, accessor).then((bufferInfo) => { + const buffer = bufferInfo.data; + for (let i = 0; i < jointCount; i++) { + const inverseBindMatrix = new Matrix(); + inverseBindMatrix.copyFromArray(buffer, i * 16); + skin.inverseBindMatrices[i] = inverseBindMatrix; + // get joints + for (let i = 0; i < jointCount; i++) { + const jointIndex = joints[i]; + const jointName = entities[jointIndex].name; + skin.joints[i] = jointName; + // @todo Temporary solution, but it can alleviate the current BUG, and the skinning data mechanism of SkinnedMeshRenderer will be completely refactored in the future + for (let j = entities.length - 1; j >= 0; j--) { + if (jointIndex !== j && entities[j].name === jointName) { + entities[j].name = `${jointName}_${j}`; + } + } } - } - } - // get skeleton - if (skeleton !== undefined) { - skin.skeleton = entities[skeleton].name; - } else { - const rootBone = this._findSkeletonRootBone(joints, entities); - if (rootBone) { - skin.skeleton = rootBone.name; - } else { - throw "Failed to find skeleton root bone."; + // get skeleton + if (skeleton !== undefined) { + skin.skeleton = entities[skeleton].name; + } else { + const rootBone = this._findSkeletonRootBone(joints, entities); + if (rootBone) { + skin.skeleton = rootBone.name; + } else { + throw "Failed to find skeleton root bone."; + } + } } - } + return skin; + }); - skins[i] = skin; + promises.push(promise); } - glTFResource.skins = skins; + return AssetPromise.all(promises).then((skins) => { + glTFResource.skins = skins; + }); } private _findSkeletonRootBone(joints: number[], entities: Entity[]): Entity { diff --git a/packages/loader/src/gltf/parser/GLTFTextureParser.ts b/packages/loader/src/gltf/parser/GLTFTextureParser.ts index 16f0476968..6810e5d99d 100644 --- a/packages/loader/src/gltf/parser/GLTFTextureParser.ts +++ b/packages/loader/src/gltf/parser/GLTFTextureParser.ts @@ -1,27 +1,20 @@ -import { - AssetPromise, - AssetType, - Texture, - Texture2D, - TextureFilterMode, - TextureWrapMode, - Utils -} from "@galacean/engine-core"; +import { AssetPromise, AssetType, Texture, Texture2D, TextureWrapMode, Utils } from "@galacean/engine-core"; import { BufferTextureRestoreInfo } from "../../GLTFContentRestorer"; -import { TextureWrapMode as GLTFTextureWrapMode, ISampler, TextureMagFilter, TextureMinFilter } from "../GLTFSchema"; +import { TextureWrapMode as GLTFTextureWrapMode } from "../GLTFSchema"; import { GLTFUtils } from "../GLTFUtils"; import { GLTFParser } from "./GLTFParser"; import { GLTFParserContext } from "./GLTFParserContext"; export class GLTFTextureParser extends GLTFParser { - private static _wrapMap = { + /** @internal */ + static _wrapMap = { [GLTFTextureWrapMode.CLAMP_TO_EDGE]: TextureWrapMode.Clamp, [GLTFTextureWrapMode.MIRRORED_REPEAT]: TextureWrapMode.Mirror, [GLTFTextureWrapMode.REPEAT]: TextureWrapMode.Repeat }; - parse(context: GLTFParserContext): AssetPromise { - const { glTFResource, glTF, buffers } = context; + parse(context: GLTFParserContext): AssetPromise | void { + const { glTFResource, glTF } = context; const { engine, url } = glTFResource; if (glTF.textures) { @@ -37,42 +30,49 @@ export class GLTFTextureParser extends GLTFParser { ); if (!texture) { + const samplerInfo = sampler !== undefined && GLTFUtils.getSamplerInfo(glTF.samplers[sampler]); if (uri) { - // TODO: support ktx extension https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_texture_basisu/README.md + // TODO: deleted in 2.0 const index = uri.lastIndexOf("."); const ext = uri.substring(index + 1); const type = ext.startsWith("ktx") ? AssetType.KTX : AssetType.Texture2D; texture = engine.resourceManager .load({ url: Utils.resolveAbsoluteUrl(url, uri), - type: type + type: type, + params: { + mipmap: samplerInfo?.mipmap + } }) .then((texture) => { if (!texture.name) { texture.name = textureName || imageName || `texture_${index}`; } if (sampler !== undefined) { - this._parseSampler(texture, glTF.samplers[sampler]); + GLTFUtils.parseSampler(texture, samplerInfo); } return texture; }); } else { const bufferView = glTF.bufferViews[bufferViewIndex]; - const buffer = buffers[bufferView.buffer]; - const imageBuffer = new Uint8Array(buffer, bufferView.byteOffset, bufferView.byteLength); - texture = GLTFUtils.loadImageBuffer(imageBuffer, mimeType).then((image) => { - const texture = new Texture2D(engine, image.width, image.height); - texture.setImageSource(image); - texture.generateMipmaps(); - texture.name = textureName || imageName || `texture_${index}`; - if (sampler !== undefined) { - this._parseSampler(texture, glTF.samplers[sampler]); - } - const bufferTextureRestoreInfo = new BufferTextureRestoreInfo(texture, bufferView, mimeType); - context.contentRestorer.bufferTextures.push(bufferTextureRestoreInfo); + texture = context.getBuffers().then((buffers) => { + const buffer = buffers[bufferView.buffer]; + const imageBuffer = new Uint8Array(buffer, bufferView.byteOffset, bufferView.byteLength); + + return GLTFUtils.loadImageBuffer(imageBuffer, mimeType).then((image) => { + const texture = new Texture2D(engine, image.width, image.height, undefined, samplerInfo?.mipmap); + texture.setImageSource(image); + texture.generateMipmaps(); + texture.name = textureName || imageName || `texture_${index}`; + if (sampler !== undefined) { + GLTFUtils.parseSampler(texture, samplerInfo); + } + const bufferTextureRestoreInfo = new BufferTextureRestoreInfo(texture, bufferView, mimeType); + context.contentRestorer.bufferTextures.push(bufferTextureRestoreInfo); - return texture; + return texture; + }); }); } } @@ -91,26 +91,4 @@ export class GLTFTextureParser extends GLTFParser { return texturesPromiseInfo.promise; } } - - private _parseSampler(texture: Texture2D, sampler: ISampler): void { - const { magFilter, minFilter, wrapS, wrapT } = sampler; - - if (magFilter || minFilter) { - if (magFilter === TextureMagFilter.NEAREST) { - texture.filterMode = TextureFilterMode.Point; - } else if (minFilter <= TextureMinFilter.LINEAR_MIPMAP_NEAREST) { - texture.filterMode = TextureFilterMode.Bilinear; - } else { - texture.filterMode = TextureFilterMode.Trilinear; - } - } - - if (wrapS) { - texture.wrapModeU = GLTFTextureParser._wrapMap[wrapS]; - } - - if (wrapT) { - texture.wrapModeV = GLTFTextureParser._wrapMap[wrapT]; - } - } } diff --git a/packages/loader/src/ktx2/KTX2Container.ts b/packages/loader/src/ktx2/KTX2Container.ts index 0af6dfcb68..ea32bdc079 100644 --- a/packages/loader/src/ktx2/KTX2Container.ts +++ b/packages/loader/src/ktx2/KTX2Container.ts @@ -44,7 +44,7 @@ export class KTX2Container { globalData: KTX2GlobalDataBasisLZ | null = null; - constructor(buffer: ArrayBuffer) { + constructor(buffer: Uint8Array) { this.parse(buffer); } @@ -56,8 +56,10 @@ export class KTX2Container { return this.dataFormatDescriptor.colorModel === ColorModel.UASTC; } - private parse(buffer: ArrayBuffer) { - const headerBufferReader = new BufferReader(buffer, 12, 68); + private parse(data: Uint8Array) { + const buffer = data.buffer; + const byteOffset = data.byteOffset; + const headerBufferReader = new BufferReader(data, 12); this.vkFormat = headerBufferReader.nextUint32(); this.typeSize = headerBufferReader.nextUint32(); this.pixelWidth = headerBufferReader.nextUint32(); @@ -82,17 +84,17 @@ export class KTX2Container { // level index const ktxLevels = new Array(levelCount); const levelByteLength = levelCount * 3 * 8; - const levelReader = new BufferReader(buffer, headerBufferReader.offset, levelByteLength, true); + const levelReader = new BufferReader(data, headerBufferReader.offset, levelByteLength); this.levels = ktxLevels; for (let i = 0; i < levelCount; i++) { ktxLevels[i] = { - levelData: new Uint8Array(buffer, levelReader.nextUint64(), levelReader.nextUint64()), + levelData: new Uint8Array(buffer, byteOffset + levelReader.nextUint64(), levelReader.nextUint64()), uncompressedByteLength: levelReader.nextUint64() }; } // Data Format Descriptor (DFD). - const dfdReader = new BufferReader(buffer, dfdByteOffset, dfdByteLength, true); + const dfdReader = new BufferReader(data, dfdByteOffset, dfdByteLength); const dfd: KTX2DataFormatDescriptorBasicFormat = { vendorId: dfdReader.skip(4 /* totalSize */).nextUint16(), @@ -144,7 +146,7 @@ export class KTX2Container { dfd.samples[i] = sample; } - const kvdReader = new BufferReader(buffer, kvdByteOffset, kvdByteLength, true); + const kvdReader = new BufferReader(data, kvdByteOffset, kvdByteLength, true); while (kvdReader.position < kvdByteLength) { const keyValueByteLength = kvdReader.nextUint32(); @@ -152,15 +154,17 @@ export class KTX2Container { const key = Utils.decodeText(keyData); // 4-byte alignment. - const kvPadding = keyValueByteLength % 4 ? 4 - (keyValueByteLength % 4) : 0; - const valueData = kvdReader.nextUint8Array(keyValueByteLength - keyData.byteLength - 2); - this.keyValue[key] = key.match(/^ktx/i) ? Utils.decodeText(valueData) : valueData; - kvdReader.skip(kvPadding + 1); + const valueData = kvdReader.nextUint8Array(keyValueByteLength - keyData.byteLength - 1); + this.keyValue[key] = key.match(/^ktx/i) ? Utils.decodeText(valueData).replace(/^(.*)\x00$/, "$1") : valueData; + + const kvPadding = keyValueByteLength % 4 ? 4 - (keyValueByteLength % 4) : 0; // align(4) + // 4-byte alignment. + kvdReader.skip(kvPadding); } if (sgdByteLength <= 0) return this; - const sgdReader = new BufferReader(buffer, sgdByteOffset, sgdByteLength, true); + const sgdReader = new BufferReader(data, sgdByteOffset, sgdByteLength, true); const endpointCount = sgdReader.nextUint16(); const selectorCount = sgdReader.nextUint16(); @@ -186,10 +190,10 @@ export class KTX2Container { const tablesByteOffset = selectorsByteOffset + selectorsByteLength; const extendedByteOffset = tablesByteOffset + tablesByteLength; - const endpointsData = new Uint8Array(buffer, endpointsByteOffset, endpointsByteLength); - const selectorsData = new Uint8Array(buffer, selectorsByteOffset, selectorsByteLength); - const tablesData = new Uint8Array(buffer, tablesByteOffset, tablesByteLength); - const extendedData = new Uint8Array(buffer, extendedByteOffset, extendedByteLength); + const endpointsData = new Uint8Array(buffer, byteOffset + endpointsByteOffset, endpointsByteLength); + const selectorsData = new Uint8Array(buffer, byteOffset + selectorsByteOffset, selectorsByteLength); + const tablesData = new Uint8Array(buffer, byteOffset + tablesByteOffset, tablesByteLength); + const extendedData = new Uint8Array(buffer, byteOffset + extendedByteOffset, extendedByteLength); this.globalData = { endpointCount, diff --git a/packages/loader/src/ktx2/KTX2Loader.ts b/packages/loader/src/ktx2/KTX2Loader.ts index ccb2d8a23c..78403e3f7c 100644 --- a/packages/loader/src/ktx2/KTX2Loader.ts +++ b/packages/loader/src/ktx2/KTX2Loader.ts @@ -22,8 +22,9 @@ import { TranscodeResult } from "./transcoder/AbstractTranscoder"; import { BinomialLLCTranscoder } from "./transcoder/BinomialLLCTranscoder"; import { KhronosTranscoder } from "./transcoder/KhronosTranscoder"; -@resourceLoader(AssetType.KTX, ["ktx2"]) +@resourceLoader(AssetType.KTX2, ["ktx2"]) export class KTX2Loader extends Loader { + private static _isBinomialInit: boolean = false; private static _binomialLLCTranscoder: BinomialLLCTranscoder; private static _khronosTranscoder: KhronosTranscoder; private static _supportedMap = { @@ -42,17 +43,72 @@ export class KTX2Loader extends Loader { if (this._khronosTranscoder) this._khronosTranscoder.destroy(); this._binomialLLCTranscoder = null; this._khronosTranscoder = null; + this._isBinomialInit = false; + } + + /** @internal */ + static _parseBuffer(buffer: Uint8Array, engine: Engine, params?: KTX2Params) { + const ktx2Container = new KTX2Container(buffer); + const formatPriorities = params?.priorityFormats; + const targetFormat = KTX2Loader._decideTargetFormat(engine, ktx2Container, formatPriorities); + let transcodeResultPromise: Promise; + if (KTX2Loader._isBinomialInit || !KhronosTranscoder.transcoderMap[targetFormat] || !ktx2Container.isUASTC) { + const binomialLLCWorker = KTX2Loader._getBinomialLLCTranscoder(); + transcodeResultPromise = binomialLLCWorker.init().then(() => binomialLLCWorker.transcode(buffer, targetFormat)); + } else { + const khronosWorker = KTX2Loader._getKhronosTranscoder(); + transcodeResultPromise = khronosWorker.init().then(() => khronosWorker.transcode(ktx2Container)); + } + return transcodeResultPromise.then((result) => { + return { engine, result, targetFormat, params: ktx2Container.keyValue["GalaceanTextureParams"] as Uint8Array }; + }); + } + + /** @internal */ + static _createTextureByBuffer( + engine: Engine, + transcodeResult: TranscodeResult, + targetFormat: KTX2TargetFormat, + params?: Uint8Array + ): Texture2D | TextureCube { + const { width, height, faces } = transcodeResult; + const faceCount = faces.length; + const mipmaps = faces[0]; + const mipmap = mipmaps.length > 1; + const engineFormat = this._getEngineTextureFormat(targetFormat, transcodeResult); + let texture: Texture2D | TextureCube; + if (faceCount !== 6) { + texture = new Texture2D(engine, width, height, engineFormat, mipmap); + for (let mipLevel = 0; mipLevel < mipmaps.length; mipLevel++) { + const { data } = mipmaps[mipLevel]; + texture.setPixelBuffer(data, mipLevel); + } + } else { + texture = new TextureCube(engine, height, engineFormat, mipmap); + for (let i = 0; i < faces.length; i++) { + const faceData = faces[i]; + for (let mipLevel = 0; mipLevel < mipmaps.length; mipLevel++) { + texture.setPixelBuffer(TextureCubeFace.PositiveX + i, faceData[mipLevel].data, mipLevel); + } + } + } + if (params) { + texture.wrapModeU = params[0]; + texture.wrapModeV = params[1]; + texture.filterMode = params[2]; + texture.anisoLevel = params[3]; + } + return texture as Texture2D | TextureCube; } private static _decideTargetFormat( engine: Engine, ktx2Container: KTX2Container, - formatPriorities?: KTX2TargetFormat[] + priorityFormats?: KTX2TargetFormat[] ): KTX2TargetFormat { - // @ts-ignore - const renderer = engine._hardwareRenderer as WebGLRenderer; + const renderer = (engine as any)._hardwareRenderer; - const targetFormat = this._detectSupportedFormat(renderer, formatPriorities) as KTX2TargetFormat; + const targetFormat = this._detectSupportedFormat(renderer, priorityFormats) as KTX2TargetFormat; if ( targetFormat === KTX2TargetFormat.PVRTC && @@ -68,13 +124,12 @@ export class KTX2Loader extends Loader { Logger.warn("Can't support any compressed texture, downgrade to RGBA8"); return KTX2TargetFormat.R8G8B8A8; } - // TODO support bc7: https://github.com/galacean/engine/issues/1371 return targetFormat; } private static _detectSupportedFormat( renderer: any, - formatPriorities: KTX2TargetFormat[] = [ + priorityFormats: KTX2TargetFormat[] = [ KTX2TargetFormat.ASTC, KTX2TargetFormat.ETC, KTX2TargetFormat.BC7, @@ -82,11 +137,11 @@ export class KTX2Loader extends Loader { KTX2TargetFormat.PVRTC ] ): KTX2TargetFormat | null { - for (let i = 0; i < formatPriorities.length; i++) { - const capabilities = this._supportedMap[formatPriorities[i]]; + for (let i = 0; i < priorityFormats.length; i++) { + const capabilities = this._supportedMap[priorityFormats[i]]; for (let j = 0; j < capabilities.length; j++) { if (renderer.canIUse(capabilities[j])) { - return formatPriorities[i]; + return priorityFormats[i]; } } } @@ -94,6 +149,7 @@ export class KTX2Loader extends Loader { } private static _getBinomialLLCTranscoder(workerCount: number = 4) { + KTX2Loader._isBinomialInit = true; return (this._binomialLLCTranscoder ??= new BinomialLLCTranscoder(workerCount)); } @@ -101,13 +157,31 @@ export class KTX2Loader extends Loader { return (this._khronosTranscoder ??= new KhronosTranscoder(workerCount, KTX2TargetFormat.ASTC)); } + private static _getEngineTextureFormat( + basisFormat: KTX2TargetFormat, + transcodeResult: TranscodeResult + ): TextureFormat { + const { hasAlpha } = transcodeResult; + switch (basisFormat) { + case KTX2TargetFormat.ASTC: + return TextureFormat.ASTC_4x4; + case KTX2TargetFormat.ETC: + return hasAlpha ? TextureFormat.ETC2_RGBA8 : TextureFormat.ETC2_RGB; + case KTX2TargetFormat.BC7: + return TextureFormat.BC7; + case KTX2TargetFormat.BC1_BC3: + return hasAlpha ? TextureFormat.BC3 : TextureFormat.BC1; + case KTX2TargetFormat.PVRTC: + return hasAlpha ? TextureFormat.PVRTC_RGBA4 : TextureFormat.PVRTC_RGB4; + case KTX2TargetFormat.R8G8B8A8: + return TextureFormat.R8G8B8A8; + } + } + override initialize(engine: Engine, configuration: EngineConfiguration): Promise { if (configuration.ktx2Loader) { const options = configuration.ktx2Loader; - if ( - // @ts-ignore - KTX2Loader._detectSupportedFormat(engine._hardwareRenderer, options.formatPriorities) === KTX2TargetFormat.ASTC - ) { + if (this._isKhronosSupported(options.priorityFormats, engine)) { return KTX2Loader._getKhronosTranscoder(options.workerCount).init(); } else { return KTX2Loader._getBinomialLLCTranscoder(options.workerCount).init(); @@ -122,68 +196,38 @@ export class KTX2Loader extends Loader { item: LoadItem & { params?: KTX2Params }, resourceManager: ResourceManager ): AssetPromise { - return this.request(item.url!, { type: "arraybuffer" }).then((buffer) => { - const ktx2Container = new KTX2Container(buffer); - const formatPriorities = item.params?.formatPriorities; - const targetFormat = KTX2Loader._decideTargetFormat(resourceManager.engine, ktx2Container, formatPriorities); - let transcodeResultPromise: Promise; - if (targetFormat === KTX2TargetFormat.ASTC && ktx2Container.isUASTC) { - const khronosWorker = KTX2Loader._getKhronosTranscoder(); - transcodeResultPromise = khronosWorker.init().then(() => khronosWorker.transcode(ktx2Container)); - } else { - const binomialLLCWorker = KTX2Loader._getBinomialLLCTranscoder(); - transcodeResultPromise = binomialLLCWorker.init().then(() => binomialLLCWorker.transcode(buffer, targetFormat)); - } - return transcodeResultPromise.then((result) => { - const { width, height, faces } = result; - const faceCount = faces.length; - const mipmaps = faces[0]; - const mipmap = mipmaps.length > 1; - const engineFormat = this._getEngineTextureFormat(targetFormat, result); - let texture: Texture; - if (faceCount !== 6) { - texture = new Texture2D(resourceManager.engine, width, height, engineFormat, mipmap); - for (let mipLevel = 0; mipLevel < mipmaps.length; mipLevel++) { - const { data } = mipmaps[mipLevel]; - (texture as Texture2D).setPixelBuffer(data, mipLevel); - } - } else { - texture = new TextureCube(resourceManager.engine, height, engineFormat, mipmap); - for (let i = 0; i < faces.length; i++) { - const faceData = faces[i]; - for (let mipLevel = 0; mipLevel < mipmaps.length; mipLevel++) { - (texture as TextureCube).setPixelBuffer(TextureCubeFace.PositiveX + i, faceData[mipLevel].data, mipLevel); - } - } - } - const params = ktx2Container.keyValue["GalaceanTextureParams"] as Uint8Array; - if (params) { - texture.wrapModeU = params[0]; - texture.wrapModeV = params[1]; - texture.filterMode = params[2]; - texture.anisoLevel = params[3]; - } - return texture as Texture2D | TextureCube; - }); - }); + return this.request(item.url!, { type: "arraybuffer" }).then((buffer) => + KTX2Loader._parseBuffer(new Uint8Array(buffer), resourceManager.engine, item.params).then( + ({ engine, result, targetFormat, params }) => + KTX2Loader._createTextureByBuffer(engine, result, targetFormat, params) + ) + ); } - private _getEngineTextureFormat(basisFormat: KTX2TargetFormat, transcodeResult: TranscodeResult) { - const { hasAlpha } = transcodeResult; - switch (basisFormat) { - case KTX2TargetFormat.ASTC: - return TextureFormat.ASTC_4x4; - case KTX2TargetFormat.ETC: - return hasAlpha ? TextureFormat.ETC2_RGBA8 : TextureFormat.ETC2_RGB; - case KTX2TargetFormat.BC7: - return TextureFormat.BC7; - case KTX2TargetFormat.BC1_BC3: - return hasAlpha ? TextureFormat.BC1 : TextureFormat.BC3; - case KTX2TargetFormat.PVRTC: - return hasAlpha ? TextureFormat.PVRTC_RGBA4 : TextureFormat.PVRTC_RGB4; - case KTX2TargetFormat.R8G8B8A8: - return TextureFormat.R8G8B8A8; + private _isKhronosSupported( + priorityFormats: KTX2TargetFormat[] | KTX2TargetFormat[][] = [ + KTX2TargetFormat.ASTC, + KTX2TargetFormat.ETC, + KTX2TargetFormat.BC7, + KTX2TargetFormat.BC1_BC3, + KTX2TargetFormat.PVRTC, + KTX2TargetFormat.R8G8B8A8 + ], + engine: any + ): boolean { + const supportedList = new Array(); + if (Array.isArray(priorityFormats[0])) { + for (let i = 0; i < priorityFormats.length; i++) { + supportedList.push( + KTX2Loader._detectSupportedFormat(engine._hardwareRenderer, priorityFormats[i]) + ); + } + } else { + supportedList.push( + KTX2Loader._detectSupportedFormat(engine._hardwareRenderer, priorityFormats) + ); } + return supportedList.every((format) => KhronosTranscoder.transcoderMap[format]); } } @@ -191,8 +235,8 @@ export class KTX2Loader extends Loader { * KTX2 loader params interface. */ export interface KTX2Params { - /** Transcoder Format priorities, default is ASTC/ETC/DXT/PVRTC/RGBA8. */ - formatPriorities: KTX2TargetFormat[]; + /** Priority transcoding format queue, default is ASTC/ETC/DXT/PVRTC/RGBA8. */ + priorityFormats: KTX2TargetFormat[]; } declare module "@galacean/engine-core" { @@ -201,8 +245,8 @@ declare module "@galacean/engine-core" { ktx2Loader?: { /** Worker count for transcoder, default is 4. */ workerCount?: number; - /** Transcoder Format priorities, default is ASTC/ETC/DXT/PVRTC/RGBA8. */ - formatPriorities?: KTX2TargetFormat[]; + /** Pre-initialization according to the priority transcoding format queue, default is ASTC/ETC/DXT/PVRTC/RGBA8. */ + priorityFormats?: KTX2TargetFormat[] | KTX2TargetFormat[][]; }; } } diff --git a/packages/loader/src/ktx2/WorkerPool.ts b/packages/loader/src/ktx2/WorkerPool.ts index 9f08ee6512..09a42afba9 100644 --- a/packages/loader/src/ktx2/WorkerPool.ts +++ b/packages/loader/src/ktx2/WorkerPool.ts @@ -30,21 +30,23 @@ export class WorkerPool { /** * Post message to worker. - * @param message - message which posted to worker - * @returns return a promise of message + * @param message - Message which posted to worker + * @returns Return a promise of message */ postMessage(message: T): Promise { return new Promise((resolve, reject) => { const workerId = this._getIdleWorkerId(); if (workerId !== -1) { + this._workerStatus |= 1 << workerId; const workerItems = this._workerItems; - Promise.resolve(workerItems[workerId] ?? this._initWorker(workerId)).then(() => { - this._workerStatus |= 1 << workerId; - const workerItem = workerItems[workerId]; - workerItem.resolve = resolve; - workerItem.reject = reject; - workerItem.worker.postMessage(message); - }); + Promise.resolve(workerItems[workerId] ?? this._initWorker(workerId)) + .then(() => { + const workerItem = workerItems[workerId]; + workerItem.resolve = resolve; + workerItem.reject = reject; + workerItem.worker.postMessage(message); + }) + .catch(reject); } else { this._taskQueue.push({ resolve, reject, message }); } @@ -70,7 +72,6 @@ export class WorkerPool { private _initWorker(workerId: number): Promise { return Promise.resolve(this._workerCreator()).then((worker) => { worker.addEventListener("message", this._onMessage.bind(this, workerId)); - worker.addEventListener("error", this._onError.bind(this, workerId)); this._workerItems[workerId] = { worker, resolve: null, reject: null }; return worker; }); @@ -83,13 +84,14 @@ export class WorkerPool { return -1; } - private _onError(workerId, e: ErrorEvent) { - this._workerItems[workerId].reject(e); - this._nextTask(workerId); - } - - private _onMessage(workerId: number, msg: U) { - this._workerItems[workerId].resolve(msg); + private _onMessage(workerId: number, msg: MessageEvent) { + // onerror of web worker can't catch error in promise + const error = (msg.data as ErrorMessageData).error; + if (error) { + this._workerItems[workerId].reject(error); + } else { + this._workerItems[workerId].resolve(msg.data); + } this._nextTask(workerId); } @@ -106,6 +108,10 @@ export class WorkerPool { } } +interface ErrorMessageData { + error: unknown; +} + interface WorkerItem { worker: Worker; resolve: (item: U | PromiseLike) => void; diff --git a/packages/loader/src/ktx2/transcoder/BinomialLLCTranscoder.ts b/packages/loader/src/ktx2/transcoder/BinomialLLCTranscoder.ts index b97f51ec05..a519b7fcd3 100644 --- a/packages/loader/src/ktx2/transcoder/BinomialLLCTranscoder.ts +++ b/packages/loader/src/ktx2/transcoder/BinomialLLCTranscoder.ts @@ -31,12 +31,10 @@ export class BinomialLLCTranscoder extends AbstractTranscoder { } transcode(buffer: ArrayBuffer, format: KTX2TargetFormat): Promise { - return this._transcodeWorkerPool - .postMessage({ - buffer, - format, - type: "transcode" - }) - .then((ev) => ev.data); + return this._transcodeWorkerPool.postMessage({ + buffer, + format, + type: "transcode" + }); } } diff --git a/packages/loader/src/ktx2/transcoder/BinomialLLCWorkerCode.ts b/packages/loader/src/ktx2/transcoder/BinomialLLCWorkerCode.ts index f63809b18d..d161d33ad4 100644 --- a/packages/loader/src/ktx2/transcoder/BinomialLLCWorkerCode.ts +++ b/packages/loader/src/ktx2/transcoder/BinomialLLCWorkerCode.ts @@ -11,15 +11,17 @@ export function TranscodeWorkerCode() { BC3 = 3, BC4 = 4, BC5 = 5, + BC7 = 7, PVRTC1_4_RGB = 8, PVRTC1_4_RGBA = 9, + ASTC_4x4 = 10, RGBA8 = 13 } enum TargetFormat { ASTC, BC7, - DXT, + BC1_BC3, PVRTC, ETC, R8, @@ -70,29 +72,19 @@ export function TranscodeWorkerCode() { }; function getTranscodeFormatFromTarget(target: TargetFormat, hasAlpha: boolean) { - if (target === TargetFormat.DXT) { - if (hasAlpha) { - return BasisFormat.BC3; - } else { - return BasisFormat.BC1; - } - } - if (target === TargetFormat.ETC) { - if (hasAlpha) { - return BasisFormat.ETC2; - } else { - return BasisFormat.ETC1; - } - } - if (target === TargetFormat.PVRTC) { - if (hasAlpha) { - return BasisFormat.PVRTC1_4_RGBA; - } else { - return BasisFormat.PVRTC1_4_RGB; - } - } - if (target === TargetFormat.RGBA8) { - return BasisFormat.RGBA8; + switch (target) { + case TargetFormat.BC1_BC3: + return hasAlpha ? BasisFormat.BC3 : BasisFormat.BC1; + case TargetFormat.ETC: + return hasAlpha ? BasisFormat.ETC2 : BasisFormat.ETC1; + case TargetFormat.PVRTC: + return hasAlpha ? BasisFormat.PVRTC1_4_RGBA : BasisFormat.PVRTC1_4_RGB; + case TargetFormat.RGBA8: + return BasisFormat.RGBA8; + case TargetFormat.ASTC: + return BasisFormat.ASTC_4x4; + case TargetFormat.BC7: + return BasisFormat.BC7; } } diff --git a/packages/loader/src/ktx2/transcoder/KhronosTranscoder.ts b/packages/loader/src/ktx2/transcoder/KhronosTranscoder.ts index e680312157..f3dc7666b4 100644 --- a/packages/loader/src/ktx2/transcoder/KhronosTranscoder.ts +++ b/packages/loader/src/ktx2/transcoder/KhronosTranscoder.ts @@ -74,8 +74,8 @@ export class KhronosTranscoder extends AbstractTranscoder { messageData[faceIndex] = mipmapData; } - return this._transcodeWorkerPool.postMessage(postMessageData).then((message) => { - decodedData.faces = message.data; + return this._transcodeWorkerPool.postMessage(postMessageData).then((data) => { + decodedData.faces = data; decodedData.hasAlpha = true; return decodedData; }); diff --git a/packages/loader/src/resource-deserialize/index.ts b/packages/loader/src/resource-deserialize/index.ts index 064ec78e59..1ca3bdccf9 100644 --- a/packages/loader/src/resource-deserialize/index.ts +++ b/packages/loader/src/resource-deserialize/index.ts @@ -18,7 +18,7 @@ export type { IModelMesh } from "./resources/mesh/IModelMesh"; */ export function decode(arrayBuffer: ArrayBuffer, engine: Engine): Promise { const header = FileHeader.decode(arrayBuffer); - const bufferReader = new BufferReader(arrayBuffer, header.headerLength, header.dataLength); + const bufferReader = new BufferReader(new Uint8Array(arrayBuffer), header.headerLength, header.dataLength); return decoderMap[header.type].decode(engine, bufferReader).then((object) => { object.name = header.name; return object; diff --git a/packages/loader/src/resource-deserialize/resources/mesh/MeshDecoder.ts b/packages/loader/src/resource-deserialize/resources/mesh/MeshDecoder.ts index bec4976034..4d386846b6 100644 --- a/packages/loader/src/resource-deserialize/resources/mesh/MeshDecoder.ts +++ b/packages/loader/src/resource-deserialize/resources/mesh/MeshDecoder.ts @@ -20,10 +20,12 @@ export class MeshDecoder { encodedMeshData.bounds && modelMesh.bounds.copyFrom(encodedMeshData.bounds); const offset = Math.ceil(bufferReader.offset / 4) * 4; + const buffer = bufferReader.data.buffer; + const byteOffset = bufferReader.data.byteOffset; const float32Array = new Float32Array( - bufferReader.buffer, - encodedMeshData.positions.start + offset, + buffer, + encodedMeshData.positions.start + offset + byteOffset, (encodedMeshData.positions.end - encodedMeshData.positions.start) / 4 ); const vertexCount = float32Array.length / 3; @@ -31,8 +33,8 @@ export class MeshDecoder { modelMesh.setPositions(positions); if (encodedMeshData.normals) { const float32Array = new Float32Array( - bufferReader.buffer, - encodedMeshData.normals.start + offset, + buffer, + encodedMeshData.normals.start + offset + byteOffset, (encodedMeshData.normals.end - encodedMeshData.normals.start) / 4 ); const normals = float32ArrayToVector3(float32Array, vertexCount); @@ -40,88 +42,88 @@ export class MeshDecoder { } if (encodedMeshData.uvs) { const float32Array = new Float32Array( - bufferReader.buffer, - encodedMeshData.uvs.start + offset, + buffer, + encodedMeshData.uvs.start + offset + byteOffset, (encodedMeshData.uvs.end - encodedMeshData.uvs.start) / 4 ); modelMesh.setUVs(float32ArrayToVector2(float32Array, vertexCount)); } if (encodedMeshData.uv1) { const float32Array = new Float32Array( - bufferReader.buffer, - encodedMeshData.uv1.start + offset, + buffer, + encodedMeshData.uv1.start + offset + byteOffset, (encodedMeshData.uv1.end - encodedMeshData.uv1.start) / 4 ); modelMesh.setUVs(float32ArrayToVector2(float32Array, vertexCount), 1); } if (encodedMeshData.uv2) { const float32Array = new Float32Array( - bufferReader.buffer, - encodedMeshData.uv2.start + offset, + buffer, + encodedMeshData.uv2.start + offset + byteOffset, (encodedMeshData.uv2.end - encodedMeshData.uv2.start) / 4 ); modelMesh.setUVs(float32ArrayToVector2(float32Array, vertexCount), 2); } if (encodedMeshData.uv3) { const float32Array = new Float32Array( - bufferReader.buffer, - encodedMeshData.uv3.start + offset, + buffer, + encodedMeshData.uv3.start + offset + byteOffset, (encodedMeshData.uv3.end - encodedMeshData.uv3.start) / 4 ); modelMesh.setUVs(float32ArrayToVector2(float32Array, vertexCount), 3); } if (encodedMeshData.uv4) { const float32Array = new Float32Array( - bufferReader.buffer, - encodedMeshData.uv4.start + offset, + buffer, + encodedMeshData.uv4.start + offset + byteOffset, (encodedMeshData.uv4.end - encodedMeshData.uv4.start) / 4 ); modelMesh.setUVs(float32ArrayToVector2(float32Array, vertexCount), 4); } if (encodedMeshData.uv5) { const float32Array = new Float32Array( - bufferReader.buffer, - encodedMeshData.uv5.start + offset, + buffer, + encodedMeshData.uv5.start + offset + byteOffset, (encodedMeshData.uv5.end - encodedMeshData.uv5.start) / 4 ); modelMesh.setUVs(float32ArrayToVector2(float32Array, vertexCount), 5); } if (encodedMeshData.uv6) { const float32Array = new Float32Array( - bufferReader.buffer, - encodedMeshData.uv6.start + offset, + buffer, + encodedMeshData.uv6.start + offset + byteOffset, (encodedMeshData.uv6.end - encodedMeshData.uv6.start) / 4 ); modelMesh.setUVs(float32ArrayToVector2(float32Array, vertexCount), 6); } if (encodedMeshData.uv7) { const float32Array = new Float32Array( - bufferReader.buffer, - encodedMeshData.uv7.start + offset, + buffer, + encodedMeshData.uv7.start + offset + byteOffset, (encodedMeshData.uv7.end - encodedMeshData.uv7.start) / 4 ); modelMesh.setUVs(float32ArrayToVector2(float32Array, vertexCount), 7); } if (encodedMeshData.colors) { const float32Array = new Float32Array( - bufferReader.buffer, - encodedMeshData.colors.start + offset, + buffer, + encodedMeshData.colors.start + offset + byteOffset, (encodedMeshData.colors.end - encodedMeshData.colors.start) / 4 ); modelMesh.setColors(float32ArrayToVColor(float32Array, vertexCount)); } if (encodedMeshData.boneWeights) { const float32Array = new Float32Array( - bufferReader.buffer, - encodedMeshData.boneWeights.start + offset, + buffer, + encodedMeshData.boneWeights.start + offset + byteOffset, (encodedMeshData.boneWeights.end - encodedMeshData.boneWeights.start) / 4 ); modelMesh.setBoneWeights(float32ArrayToVector4(float32Array, vertexCount)); } if (encodedMeshData.boneIndices) { const float32Array = new Float32Array( - bufferReader.buffer, - encodedMeshData.boneIndices.start + offset, + buffer, + encodedMeshData.boneIndices.start + offset + byteOffset, (encodedMeshData.boneIndices.end - encodedMeshData.boneIndices.start) / 4 ); modelMesh.setBoneIndices(float32ArrayToVector4(float32Array, vertexCount)); @@ -131,8 +133,8 @@ export class MeshDecoder { const blendShape = new BlendShape(blendShapeData.name); blendShapeData.frames.forEach((frameData) => { const positionArray = new Float32Array( - bufferReader.buffer, - frameData.deltaPosition.start + offset, + buffer, + frameData.deltaPosition.start + offset + byteOffset, (frameData.deltaPosition.end - frameData.deltaPosition.start) / 4 ); const count = positionArray.length / 3; @@ -140,8 +142,8 @@ export class MeshDecoder { let deltaNormals: Vector3[] | null = null; if (frameData.deltaNormals) { const normalsArray = new Float32Array( - bufferReader.buffer, - frameData.deltaNormals.start + offset, + buffer, + frameData.deltaNormals.start + offset + byteOffset, (frameData.deltaNormals.end - frameData.deltaNormals.start) / 4 ); deltaNormals = float32ArrayToVector3(normalsArray, count); @@ -149,8 +151,8 @@ export class MeshDecoder { let deltaTangents: Vector4[] | null = null; if (frameData.deltaTangents) { const tangentsArray = new Float32Array( - bufferReader.buffer, - frameData.deltaTangents.start + offset, + buffer, + frameData.deltaTangents.start + offset + byteOffset, (frameData.deltaTangents.end - frameData.deltaTangents.start) / 4 ); deltaTangents = float32ArrayToVector4(tangentsArray, count); @@ -164,14 +166,14 @@ export class MeshDecoder { let indices: Uint16Array | Uint32Array = null; if (encodedMeshData.indices.type === 0) { indices = new Uint16Array( - bufferReader.buffer, - encodedMeshData.indices.start + offset, + buffer, + encodedMeshData.indices.start + offset + byteOffset, (encodedMeshData.indices.end - encodedMeshData.indices.start) / 2 ); } else { indices = new Uint32Array( - bufferReader.buffer, - encodedMeshData.indices.start + offset, + buffer, + encodedMeshData.indices.start + offset + byteOffset, (encodedMeshData.indices.end - encodedMeshData.indices.start) / 4 ); } diff --git a/packages/loader/src/resource-deserialize/resources/scene/EditorTextureLoader.ts b/packages/loader/src/resource-deserialize/resources/scene/EditorTextureLoader.ts index 1c73e452b3..bd12137fcf 100644 --- a/packages/loader/src/resource-deserialize/resources/scene/EditorTextureLoader.ts +++ b/packages/loader/src/resource-deserialize/resources/scene/EditorTextureLoader.ts @@ -4,12 +4,14 @@ import { decode } from "../.."; @resourceLoader("EditorTexture2D", ["prefab"], true) export class EditorTextureLoader extends Loader { load(item: LoadItem, resourceManager: ResourceManager): AssetPromise { - return new AssetPromise((resolve) => { - this.request(item.url, { type: "arraybuffer" }).then((data) => { - decode(data, resourceManager.engine).then((texture) => { - resolve(texture); - }); - }); + return new AssetPromise((resolve, reject) => { + this.request(item.url, { type: "arraybuffer" }) + .then((data) => { + decode(data, resourceManager.engine).then((texture) => { + resolve(texture); + }); + }) + .catch(reject); }); } } diff --git a/packages/loader/src/resource-deserialize/resources/scene/MeshLoader.ts b/packages/loader/src/resource-deserialize/resources/scene/MeshLoader.ts index 6dcf7a1f45..346c23f418 100644 --- a/packages/loader/src/resource-deserialize/resources/scene/MeshLoader.ts +++ b/packages/loader/src/resource-deserialize/resources/scene/MeshLoader.ts @@ -5,11 +5,13 @@ import { decode } from "../.."; export class MeshLoader extends Loader { load(item: LoadItem, resourceManager: ResourceManager): AssetPromise { return new AssetPromise((resolve, reject) => { - this.request(item.url, { type: "arraybuffer" }).then((data) => { - decode(data, resourceManager.engine).then((mesh) => { - resolve(mesh); - }); - }); + this.request(item.url, { type: "arraybuffer" }) + .then((data) => { + decode(data, resourceManager.engine).then((mesh) => { + resolve(mesh); + }); + }) + .catch(reject); }); } } diff --git a/packages/loader/src/resource-deserialize/resources/texture2D/TextureDecoder.ts b/packages/loader/src/resource-deserialize/resources/texture2D/TextureDecoder.ts index 3be25877b5..21b1eb38ab 100644 --- a/packages/loader/src/resource-deserialize/resources/texture2D/TextureDecoder.ts +++ b/packages/loader/src/resource-deserialize/resources/texture2D/TextureDecoder.ts @@ -27,12 +27,12 @@ export class Texture2DDecoder { texture2D.wrapModeV = wrapModeV; if (isPixelBuffer) { - const pixelBuffer = new Uint8Array(imagesData[0]); + const pixelBuffer = imagesData[0]; texture2D.setPixelBuffer(pixelBuffer); if (mipmap) { texture2D.generateMipmaps(); for (let i = 1; i < mipCount; i++) { - const pixelBuffer = new Uint8Array(imagesData[i]); + const pixelBuffer = imagesData[i]; texture2D.setPixelBuffer(pixelBuffer, i); } } diff --git a/packages/loader/src/resource-deserialize/utils/BufferReader.ts b/packages/loader/src/resource-deserialize/utils/BufferReader.ts index cff2955f12..c18ce702b3 100644 --- a/packages/loader/src/resource-deserialize/utils/BufferReader.ts +++ b/packages/loader/src/resource-deserialize/utils/BufferReader.ts @@ -6,24 +6,27 @@ export class BufferReader { private _baseOffset: number; constructor( - public buffer: ArrayBuffer, + public data: Uint8Array, byteOffset: number = 0, byteLength?: number, littleEndian: boolean = true ) { - // byteLength = byteLength ?? _buffer.byteLength; - this._dataView = new DataView(buffer); + this._dataView = new DataView( + data.buffer, + data.byteOffset + byteOffset, + byteLength ?? data.byteLength - byteOffset + ); this._littleEndian = littleEndian; - this._offset = byteOffset; + this._offset = 0; this._baseOffset = byteOffset; } get position() { - return this._offset - this._baseOffset; + return this._offset; } get offset() { - return this._offset; + return this._offset + this._baseOffset; } nextUint8() { @@ -51,7 +54,7 @@ export class BufferReader { } nextInt32Array(len: number) { - const value = new Int32Array(this.buffer, this._offset, len); + const value = new Int32Array(this.data.buffer, this._offset + this._dataView.byteOffset, len); this._offset += 4 * len; return value; } @@ -63,19 +66,19 @@ export class BufferReader { } nextFloat32Array(len: number) { - const value = new Float32Array(this.buffer, this._offset, len); + const value = new Float32Array(this.data.buffer, this._offset + this._dataView.byteOffset, len); this._offset += 4 * len; return value; } nextUint32Array(len: number) { - const value = new Uint32Array(this.buffer, this._offset, len); + const value = new Uint32Array(this.data.buffer, this._offset + this._dataView.byteOffset, len); this._offset += 4 * len; return value; } nextUint8Array(len: number) { - const value = new Uint8Array(this.buffer, this._offset, len); + const value = new Uint8Array(this.data.buffer, this._offset + this._dataView.byteOffset, len); this._offset += len; return value; } @@ -90,7 +93,7 @@ export class BufferReader { nextStr(): string { const strByteLength = this.nextUint16(); - const uint8Array = new Uint8Array(this.buffer, this._offset, strByteLength); + const uint8Array = new Uint8Array(this.data.buffer, this._offset + this._dataView.byteOffset, strByteLength); this._offset += strByteLength; return Utils.decodeText(uint8Array); } @@ -98,11 +101,11 @@ export class BufferReader { /** * image data 放在最后 */ - nextImageData(count: number = 0): ArrayBuffer { - return this.buffer.slice(this._offset); + nextImageData(count: number = 0): Uint8Array { + return new Uint8Array(this.data.buffer, this.data.byteOffset + this._offset); } - nextImagesData(count: number): ArrayBuffer[] { + nextImagesData(count: number): Uint8Array[] { const imagesLen = new Array(count); // Start offset of Uint32Array should be a multiple of 4. ref: https://stackoverflow.com/questions/15417310/why-typed-array-constructors-require-offset-to-be-multiple-of-underlying-type-si for (let i = 0; i < count; i++) { @@ -110,11 +113,11 @@ export class BufferReader { imagesLen[i] = len; this._offset += 4; } - const imagesData: ArrayBuffer[] = []; + const imagesData: Uint8Array[] = []; for (let i = 0; i < count; i++) { const len = imagesLen[i]; - const buffer = this.buffer.slice(this._offset, this._offset + len); + const buffer = new Uint8Array(this.data.buffer, this._dataView.byteOffset + this._offset, len); this._offset += len; imagesData.push(buffer); } diff --git a/packages/math/src/Rect.ts b/packages/math/src/Rect.ts index b10aeb832b..415779fce4 100644 --- a/packages/math/src/Rect.ts +++ b/packages/math/src/Rect.ts @@ -3,14 +3,64 @@ import { ICopy } from "./ICopy"; // A 2d rectangle defined by x and y position, width and height. export class Rect implements IClone, ICopy { - /** The x coordinate of the rectangle. */ - public x: number; - /** The y coordinate of the rectangle. */ - public y: number; - /** The width of the rectangle, measured from the x position. */ - public width: number; - /** The height of the rectangle, measured from the y position. */ - public height: number; + /** @internal */ + _x: number; + /** @internal */ + _y: number; + /** @internal */ + _width: number; + /** @internal */ + _height: number; + /** @internal */ + _onValueChanged: () => void = null; + + /** + * The x coordinate of the rectangle. + */ + get x(): number { + return this._x; + } + + set x(value: number) { + this._x = value; + this._onValueChanged && this._onValueChanged(); + } + + /** + * The y coordinate of the rectangle. + */ + get y(): number { + return this._y; + } + + set y(value: number) { + this._y = value; + this._onValueChanged && this._onValueChanged(); + } + + /** + * The width of the rectangle, measured from the x position. + */ + get width(): number { + return this._width; + } + + set width(value: number) { + this._width = value; + this._onValueChanged && this._onValueChanged(); + } + + /** + * The height of the rectangle, measured from the y position. + */ + get height(): number { + return this._height; + } + + set height(value: number) { + this._height = value; + this._onValueChanged && this._onValueChanged(); + } /** * Constructor of Rect. @@ -20,10 +70,10 @@ export class Rect implements IClone, ICopy { * @param height - The height of the rectangle, measured from the y position, default 0 */ constructor(x: number = 0, y: number = 0, width: number = 0, height: number = 0) { - this.x = x; - this.y = y; - this.width = width; - this.height = height; + this._x = x; + this._y = y; + this._width = width; + this._height = height; } /** @@ -35,10 +85,11 @@ export class Rect implements IClone, ICopy { * @returns This rectangle */ set(x: number, y: number, width: number, height: number): Rect { - this.x = x; - this.y = y; - this.width = width; - this.height = height; + this._x = x; + this._y = y; + this._width = width; + this._height = height; + this._onValueChanged && this._onValueChanged(); return this; } @@ -56,10 +107,11 @@ export class Rect implements IClone, ICopy { * @returns This rect */ copyFrom(source: Rect): Rect { - this.x = source.x; - this.y = source.y; - this.width = source.width; - this.height = source.height; + this._x = source.x; + this._y = source.y; + this._width = source.width; + this._height = source.height; + this._onValueChanged && this._onValueChanged(); return this; } } diff --git a/packages/rhi-webgl/src/WebGLGraphicDevice.ts b/packages/rhi-webgl/src/WebGLGraphicDevice.ts index 73086c207f..e826b90458 100644 --- a/packages/rhi-webgl/src/WebGLGraphicDevice.ts +++ b/packages/rhi-webgl/src/WebGLGraphicDevice.ts @@ -60,6 +60,14 @@ export interface WebGLGraphicDeviceOptions extends WebGLContextAttributes { * iOS 15 webgl implement has bug, maybe should force call flush command buffer, for example iPhone13(iOS 15.4.1). */ _forceFlush?: boolean; + + /** + * @internal + * Max allow skin uniform vectors count, default is 256 + * + * @remarks large count maybe cause performance issue. + */ + _maxAllowSkinUniformVectorCount?: number; } /** @@ -126,6 +134,7 @@ export class WebGLGraphicDevice implements IHardwareRenderer { webGLMode: WebGLMode.Auto, stencil: true, _forceFlush: false, + _maxAllowSkinUniformVectorCount: 256, ...initializeOptions }; if (SystemInfo.platform === Platform.IPhone || SystemInfo.platform === Platform.IPad) { diff --git a/packages/shader-lab/README.md b/packages/shader-lab/README.md new file mode 100644 index 0000000000..d72816a01a --- /dev/null +++ b/packages/shader-lab/README.md @@ -0,0 +1,27 @@ +## Installation + +```sh +npm install @galacean/engine-shader-lab +``` + +## Usage + +```typescript +import { ShaderLab } from "@galacean/engine-shader-lab"; + +// Create ShaderLab +const shaderLab = new ShaderLab(); + +// Create engine with shaderLab +const engine = await WebGLEngine.create({ canvas: "canvas", shaderLab }); + +...... + +// Create shader by galacean shader code directly +const shader = Shader.create(galaceanShaderCode); + +....... + +// Run engine +engine.run() +``` diff --git a/packages/shader-lab/package.json b/packages/shader-lab/package.json new file mode 100644 index 0000000000..f4a7ced177 --- /dev/null +++ b/packages/shader-lab/package.json @@ -0,0 +1,34 @@ +{ + "name": "@galacean/engine-shader-lab", + "version": "1.1.0-alpha.0", + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org" + }, + "license": "MIT", + "main": "dist/main.js", + "module": "dist/module.js", + "debug": "src/index.ts", + "types": "types/index.d.ts", + "scripts": { + "b:types": "tsc", + "gen_diagram": "ts-node ./scripts/genDiagram.ts", + "gen_dts": "ts-node ./scripts/genDts.ts" + }, + "umd": { + "name": "Galacean.ShaderLab", + "globals": { + "@galacean/engine": "Galacean" + } + }, + "files": [ + "dist/**/*", + "types/**/*" + ], + "dependencies": { + "chevrotain": "^10.5.0" + }, + "devDependencies": { + "@galacean/engine-design": "workspace:*" + } +} diff --git a/packages/shader-lab/scripts/genDiagram.ts b/packages/shader-lab/scripts/genDiagram.ts new file mode 100755 index 0000000000..76d3707e1d --- /dev/null +++ b/packages/shader-lab/scripts/genDiagram.ts @@ -0,0 +1,29 @@ +#! /usr/bin/env ts-node + +import { createSyntaxDiagramsCode } from "chevrotain"; +import { ShaderParser } from "../src"; +import { exec } from "child_process"; +import path from "path"; +import fs from "fs"; + +function generateDiagram(opts?: { outDir?: string; pattern?: RegExp }) { + const out = opts?.outDir ?? path.join(__dirname, "../output"); + if (!fs.existsSync(out)) { + fs.mkdirSync(out); + } + const parser = new ShaderParser(); + + const serializeGrammar = parser.getSerializedGastProductions(); + const html = createSyntaxDiagramsCode( + serializeGrammar.filter((grammer) => + (opts?.pattern ?? /^(? fn.content.name === vertexFnProperty.content.value + ); + if (!vertFnAst) { + context.diagnostics.push({ + severity: DiagnosticSeverity.Error, + message: `Not found vertex shader definition: ${vertexFnProperty.content.value}`, + token: vertexFnProperty.position + }); + return ""; + } + context.setMainFnAst(vertFnAst); + context.varyingTypeAstNode = vertFnAst.content.returnType; + + // parse varying variables + const varyingStructAstNode = context.findGlobal(vertFnAst.content.returnType.content.text) as StructAstNode; + if (!varyingStructAstNode) { + context.diagnostics.push({ + severity: DiagnosticSeverity.Error, + message: "no varying struct definition", + token: vertFnAst.content.returnType.position + }); + return ""; + } + context.varyingStructInfo.structAstNode = varyingStructAstNode; + context.varyingStructInfo.reference = varyingStructAstNode.content.variables.map((v) => ({ + referenced: false, + property: v, + text: `varying ${v.content.type.serialize(context)} ${v.content.variable}` + })); + + // parsing attribute variables + vertFnAst.content.args.forEach((arg) => { + const type = arg.content.type; + if (type.isCustom) { + const structAstNode = context.findGlobal(type.text) as StructAstNode; + if (!structAstNode) { + context.diagnostics.push({ + severity: DiagnosticSeverity.Error, + message: "no attribute struct definition", + token: arg.position + }); + return; + } else { + const reference = structAstNode.content.variables.map((v) => ({ + referenced: false, + property: v, + text: `attribute ${v.content.type.serialize(context)} ${v.content.variable}` + })); + context.attributeStructListInfo.push({ objectName: arg.content.name, structAstNode, reference }); + } + } else { + context.attributesVariableListInfo.push({ + name: arg.content.name, + astNode: arg, + referenced: false, + text: `attribute ${type.text} ${arg.content.name}` + }); + } + }); + + const vertexFnStr = vertFnAst.serialize(context); + + return [context.getAttribText(), context.getGlobalText(), context.getVaryingText(), vertexFnStr].join("\n"); + } + + static stringifyFragmentFunction(fragmentFnProperty: PassPropertyAssignmentAstNode, context: RuntimeContext): string { + const fragFnAst = context.passAst.content.functions.find( + (fn) => fn.content.name === fragmentFnProperty.content.value + ); + if (!fragFnAst) { + context.diagnostics.push({ + severity: DiagnosticSeverity.Error, + message: `Not found fragment shader definition: ${fragmentFnProperty.content.value}`, + token: fragmentFnProperty.position + }); + return ""; + } + context.setMainFnAst(fragFnAst); + + context.varyingStructInfo.objectName = fragFnAst.content.args[0].content.name; + const fragmentFnStr = fragFnAst.serialize(context); + return [context.getVaryingText(), context.getGlobalText(), fragmentFnStr].join("\n"); + } +} diff --git a/packages/shader-lab/src/AstNodeUtils.ts b/packages/shader-lab/src/AstNodeUtils.ts new file mode 100644 index 0000000000..6c13c93fb0 --- /dev/null +++ b/packages/shader-lab/src/AstNodeUtils.ts @@ -0,0 +1,141 @@ +import { CstElement, CstNode, ICstVisitor, IToken, CstChildrenDictionary } from "chevrotain"; + +import { AstNode, ObjectAstNode } from "./ast-node"; +import { IPosition, IPositionRange } from "./ast-node/"; +import { ShaderVisitor, parser } from "./ShaderVisitor"; +import RuntimeContext, { IDiagnostic } from "./RuntimeContext"; +import { IShaderInfo } from "@galacean/engine-design"; + +export class AstNodeUtils { + static isCstNode(node: any) { + return !!node.children; + } + + static extractCstToken( + ctx: CstNode | CstChildrenDictionary, + opts?: { + fnToken?: (element: IToken) => any; + fnNode?: (element: CstNode) => any; + } + ): any { + if (!ctx) return undefined; + + const obj = ctx.children ?? ctx; + for (const tk in obj) { + const value = obj[tk][0]; + if (AstNodeUtils.isCstNode(value)) return opts?.fnNode?.(value) ?? AstNodeUtils.extractCstToken(value, opts); + else return opts?.fnToken?.(value) ?? value.image; + } + return undefined; + } + + static defaultVisit(this: ICstVisitor, ctx: CstChildrenDictionary): ObjectAstNode { + const content = {} as Record; + let start: IPosition = { line: Number.MAX_SAFE_INTEGER, offset: -1 }, + end: IPosition = { line: 0, offset: -1 }; + + for (const k in ctx) { + if (AstNodeUtils.isCstNode(ctx[k][0])) { + const astInfo = this.visit(ctx[k][0] as CstNode); + if (astInfo.position.start.line < start.line) { + start = astInfo.position.start; + } + if (astInfo.position.end.line > end.line) { + end = astInfo.position.end; + } + content[k] = astInfo; + } else { + const token = ctx[k][0] as IToken; + const position = AstNodeUtils.getTokenPosition(token); + if (position.start.line < start.line) { + start = position.start; + } + if (position.end.line > end.line) { + end = position.end; + } + content[k] = new AstNode({ + content: token.image, + position + }); + } + } + return new ObjectAstNode({ position: { start, end }, content }); + } + + /** + * order not guaranteed + */ + static extractCstString(node: CstElement): string[] { + const ret: string[] = []; + // @ts-ignore IToken + if (node.image) return [node.image]; + // @ts-ignore CstNode + if (node.name) { + const $ = node as CstNode; + for (const k in $.children) { + // @ts-ignore + const n: CstElement[] = $.children[k]; + if (!n) continue; + for (const item of n) { + ret.push(...AstNodeUtils.extractCstString(item)); + } + } + } + return ret; + } + + static getTokenPosition(token: IToken): IPositionRange { + return { + start: { + line: token.startLine, + offset: token.startColumn + }, + end: { + line: token.endLine, + offset: token.endColumn + } + }; + } + + /** + * get OR-Type CstNode position + */ + static getOrTypeCstNodePosition(node: IToken | { children: CstChildrenDictionary }): IPositionRange { + if (!AstNodeUtils.isCstNode(node)) return AstNodeUtils.getTokenPosition(node as IToken); + const cstNode = node as CstNode; + for (const k in cstNode.children) { + const child = cstNode.children[k]; + if (!child) continue; + + return AstNodeUtils.getOrTypeCstNodePosition(child[0]); + } + } + + static astSortAsc(a: AstNode, b: AstNode) { + return a.position.start.line > b.position.start.line || + (a.position.start.line === b.position.start.line && a.position.start.offset >= b.position.start.offset) + ? 1 + : -1; + } + + static astSortDesc(a: AstNode, b: AstNode) { + return -AstNodeUtils.astSortAsc(a, b); + } + + static parseShader(input: string): IShaderInfo & { diagnostics?: Array } { + parser.parse(input); + const cst = parser.ruleShader(); + if (parser.errors.length > 0) { + console.log(parser.errors); + throw parser.errors; + } + + const visitor = new ShaderVisitor(); + const ast = visitor.visit(cst); + + const context = new RuntimeContext(); + const shaderInfo: IShaderInfo & { diagnostics?: Array } = context.parse(ast); + shaderInfo.diagnostics = context.diagnostics; + return shaderInfo; + } +} diff --git a/packages/shader-lab/src/Constants.ts b/packages/shader-lab/src/Constants.ts new file mode 100644 index 0000000000..09ab50f420 --- /dev/null +++ b/packages/shader-lab/src/Constants.ts @@ -0,0 +1,23 @@ +export const enum DiagnosticSeverity { + /** + * Reports an error. + */ + Error = 1, + /** + * Reports a warning. + */ + Warning = 2, + /** + * Reports an information. + */ + Information = 3, + /** + * Reports a hint. + */ + Hint = 4 +} + +/** The shader pass property name which reference the fragment shader main function */ +export const FRAG_FN_NAME = "FragmentShader"; +/** The shader pass property name which reference the vertex shader main function */ +export const VERT_FN_NAME = "VertexShader"; diff --git a/packages/shader-lab/src/RuntimeContext.ts b/packages/shader-lab/src/RuntimeContext.ts new file mode 100644 index 0000000000..77c4edbbe5 --- /dev/null +++ b/packages/shader-lab/src/RuntimeContext.ts @@ -0,0 +1,211 @@ +import { Ast2GLSLUtils } from "./Ast2GLSLUtils"; +import { + AstNode, + DeclarationAstNode, + FnArgAstNode, + FnAstNode, + FnVariableAstNode, + ReturnTypeAstNode, + StructAstNode +} from "./ast-node/AstNode"; + +import { IPassAstContent, IShaderAstContent, ISubShaderAstContent, IPositionRange } from "./ast-node"; +import { DiagnosticSeverity, FRAG_FN_NAME, VERT_FN_NAME } from "./Constants"; +import { IShaderInfo, IShaderPassInfo, ISubShaderInfo } from "@galacean/engine-design"; + +export interface IDiagnostic { + severity: DiagnosticSeverity; + message: string; + /** + * The token which caused the parser error. + */ + token: IPositionRange; +} + +interface IReference { + referenced: boolean; +} + +interface IGlobal extends IReference { + ast: AstNode; + name: string; +} + +interface IReferenceStructInfo { + /** varying or attribute object name */ + objectName?: string; + structAstNode?: StructAstNode; + /** reference info */ + reference?: Array<{ property: DeclarationAstNode; referenced: boolean; text: string }>; +} + +export default class RuntimeContext { + shaderAst: AstNode; + passAst: AstNode; + functionAstStack: Array<{ fnAst: FnAstNode; localDeclaration: DeclarationAstNode[] }> = []; + /** Diagnostic for linting service */ + diagnostics: Array = []; + /** Global variables e.g. Uniforms */ + globalList: Array = []; + /** Global text */ + globalTextList: Array = []; + /** Varying info */ + varyingTypeAstNode?: ReturnTypeAstNode; + /** Varying */ + varyingStructInfo: IReferenceStructInfo; + /** Attributes struct list */ + attributeStructListInfo: Array = []; + /** Attributes variable list */ + attributesVariableListInfo: Array<{ + name: string; + astNode: FnArgAstNode; + referenced: boolean; + text: string; + }> = []; + /** Current position */ + serializingAstNode?: AstNode; + + /** The main function */ + private _currentMainFnAst?: FnAstNode; + + constructor() {} + + get currentFunctionInfo() { + return this.functionAstStack[this.functionAstStack.length - 1]; + } + + subShaderReset() { + this.passReset(); + } + + passReset() { + this.globalList.length = 0; + this.functionAstStack.length = 0; + this.attributeStructListInfo.length = 0; + this.attributesVariableListInfo.length = 0; + this.varyingTypeAstNode = undefined; + this._currentMainFnAst = undefined; + this.passAst = undefined; + this.serializingAstNode = undefined; + this.varyingStructInfo = {}; + } + + get currentMainFnAst() { + return this._currentMainFnAst; + } + + setMainFnAst(ast: FnAstNode) { + this.globalTextList.length = 0; + this._currentMainFnAst = ast; + } + + private _initGlobalList() { + this.globalList = [ + ...this.passAst.content.functions.map((fn) => ({ referenced: false, ast: fn, name: fn.content.name })), + ...this.passAst.content.structs.map((struct) => ({ referenced: false, ast: struct, name: struct.content.name })), + ...this.passAst.content.variables.map((v) => ({ referenced: false, ast: v, name: v.content.variable })) + ]; + } + + referenceGlobal(name: string): IGlobal | undefined { + const globalV = this.globalList.find((global) => global.name === name); + if (globalV) { + this.globalTextList.push(globalV.ast.serialize(this, { global: true })); + globalV.referenced = true; + } + return globalV; + } + + parse(ast: AstNode): IShaderInfo { + this.shaderAst = ast; + const ret = {} as IShaderInfo; + // ret.ast = ast; + // ret.editorProperties = ast.content.editorProperties?.toJson(); + ret.name = ast.content.name; + ret.subShaders = ast.content.subShader.map((ast) => this.parseSubShaderInfo(ast)); + + return ret; + } + + parseSubShaderInfo(ast: AstNode): ISubShaderInfo { + this.subShaderReset(); + + const ret = {} as ISubShaderInfo; + ret.tags = ast.content.tags?.toObj(); + ret.passes = ast.content.pass.map((item) => this.parsePassInfo(item)); + return ret; + } + + parsePassInfo(ast: AstNode): IShaderPassInfo { + this.passReset(); + this.passAst = ast; + this._initGlobalList(); + + const ret = {} as IShaderPassInfo; + ret.name = ast.content.name; + ret.tags = ast.content.tags?.toObj(); + ret.renderStates = {}; + ast.content.properties.forEach((prop) => { + if (prop.content.type === VERT_FN_NAME) { + if (ret.vert) { + this.diagnostics.push({ + severity: DiagnosticSeverity.Error, + message: "multiple vertex main function found", + token: prop.position + }); + return; + } + ret.vert = Ast2GLSLUtils.stringifyVertexFunction(prop, this); + } else if (prop.content.type === FRAG_FN_NAME) { + if (ret.frag) { + this.diagnostics.push({ + severity: DiagnosticSeverity.Error, + message: "multiple fragment main function found", + token: prop.position + }); + return; + } + ret.frag = Ast2GLSLUtils.stringifyFragmentFunction(prop, this); + } + }); + + return ret; + } + + findGlobal(variable: string): StructAstNode | FnVariableAstNode | FnArgAstNode | undefined { + let ret: any = this.passAst.content.structs.find((struct) => struct.content.name === variable); + if (!ret) { + ret = this.passAst.content.variables.find((v) => v.content.variable === variable); + } + if (!ret) { + ret = this.passAst.content.functions.find((fn) => fn.content.name === variable); + } + return ret; + } + + findLocal(variable: string): DeclarationAstNode | undefined { + return this.currentFunctionInfo?.localDeclaration.find((declare) => declare.content.variable === variable); + } + + getAttribText(): string { + return this.attributeStructListInfo + .map((struct) => + struct.reference + .filter((item) => item.referenced) + .map((item) => `${item.text};`) + .join("\n") + ) + .join("\n"); + } + + getVaryingText(): string { + return this.varyingStructInfo.reference + .filter((item) => item.referenced) + .map((item) => `${item.text};`) + .join("\n"); + } + + getGlobalText(): string { + return this.globalTextList.join("\n"); + } +} diff --git a/packages/shader-lab/src/ShaderLab.ts b/packages/shader-lab/src/ShaderLab.ts new file mode 100644 index 0000000000..78f5a26589 --- /dev/null +++ b/packages/shader-lab/src/ShaderLab.ts @@ -0,0 +1,8 @@ +import { AstNodeUtils } from "./AstNodeUtils"; +import { IShaderLab } from "@galacean/engine-design"; + +export class ShaderLab implements IShaderLab { + parseShader(shaderSource: string) { + return AstNodeUtils.parseShader(shaderSource); + } +} diff --git a/packages/shader-lab/src/ShaderVisitor.ts b/packages/shader-lab/src/ShaderVisitor.ts new file mode 100644 index 0000000000..50b4fffc5e --- /dev/null +++ b/packages/shader-lab/src/ShaderVisitor.ts @@ -0,0 +1,762 @@ +import { CstNode } from "chevrotain"; +import { ShaderParser } from "./parser/ShaderParser"; +import { AstNodeUtils } from "./AstNodeUtils"; +import { + AddExprAstNode, + AddOperatorAstNode, + AssignLoAstNode, + AssignableValueAstNode, + AstNode, + BooleanAstNode, + DeclarationAstNode, + FnArgAstNode, + FnAssignStatementAstNode, + FnAstNode, + FnAtomicExprAstNode, + FnBlockStatementAstNode, + FnBodyAstNode, + FnCallAstNode, + FnConditionStatementAstNode, + FnMacroConditionAstNode, + FnMacroConditionBranchAstNode, + FnMacroDefineAstNode, + FnMacroIncludeAstNode, + FnReturnStatementAstNode, + FnVariableAstNode, + MultiplicationOperatorAstNode, + MultiplicationExprAstNode, + NumberAstNode, + PassPropertyAssignmentAstNode, + PropertyAstNode, + PropertyItemAstNode, + RangeAstNode, + RelationExprAstNode, + RelationOperatorAstNode, + RenderStateDeclarationAstNode, + ReturnTypeAstNode, + StatePropertyAssignAstNode, + StructAstNode, + TagAssignmentAstNode, + TagAstNode, + TupleNumber4AstNode, + VariableDeclarationAstNode, + VariableTypeAstNode, + ObjectAstNode +} from "./ast-node"; +import { IPassAstContent, IPosition, IPositionRange, IShaderAstContent, ISubShaderAstContent } from "./ast-node/"; +import { + ICstNodeVisitor, + _ruleShaderCstChildren, + _ruleFnMultiplicationExprCstChildren, + _ruleAddOperatorCstChildren, + _ruleAssignableValueCstChildren, + _ruleBooleanCstChildren, + _ruleDeclarationCstChildren, + _ruleFnAddExprCstChildren, + _ruleFnArgCstChildren, + _ruleFnAssignLOCstChildren, + _ruleFnAssignStatementCstChildren, + _ruleFnAtomicExprCstChildren, + _ruleFnBlockStatementCstChildren, + _ruleFnBodyCstChildren, + _ruleFnCallCstChildren, + _ruleFnConditionStatementCstChildren, + _ruleFnCstChildren, + _ruleFnExpressionCstChildren, + _ruleFnMacroConditionBranchCstChildren, + _ruleFnMacroConditionCstChildren, + _ruleFnMacroCstChildren, + _ruleFnMacroDefineCstChildren, + _ruleFnMacroIncludeCstChildren, + _ruleFnParenthesisExprCstChildren, + _ruleFnRelationExprCstChildren, + _ruleFnReturnStatementCstChildren, + _ruleFnReturnTypeCstChildren, + _ruleFnStatementCstChildren, + _ruleFnVariableCstChildren, + _ruleFnVariableDeclarationCstChildren, + _ruleMultiplicationOperatorCstChildren, + _ruleNumberCstChildren, + _rulePropertyCstChildren, + _rulePropertyItemCstChildren, + _rulePropertyItemValueCstChildren, + _ruleRangeCstChildren, + _ruleRelationOperatorCstChildren, + _ruleRenderStateDeclarationCstChildren, + _ruleShaderPassCstChildren, + _ruleStatePropertyAssignCstChildren, + _ruleStructCstChildren, + _ruleSubShaderCstChildren, + _ruleSubShaderPassPropertyAssignmentCstChildren, + _ruleTagAssignmentCstChildren, + _ruleTagCstChildren, + _ruleTupleFloat4CstChildren, + _ruleTupleInt4CstChildren, + _ruleVariableTypeCstChildren +} from "./types"; + +export const parser = new ShaderParser(); + +const ShaderVisitorConstructor = parser.getBaseCstVisitorConstructorWithDefaults(); + +export class ShaderVisitor extends ShaderVisitorConstructor implements Partial> { + constructor() { + super(); + this.validateVisitor(); + } + + _ruleShader(ctx: _ruleShaderCstChildren, param?: any) { + const editorProperties = ctx._ruleProperty ? this.visit(ctx._ruleProperty) : undefined; + + const subShader = ctx._ruleSubShader?.map((item) => this.visit(item)); + + const position: IPositionRange = { + start: AstNodeUtils.getTokenPosition(ctx.Shader[0]).start, + end: AstNodeUtils.getTokenPosition(ctx.RCurly[0]).end + }; + const ast = { + position, + content: { + name: ctx.ValueString[0].image.replace(/"(.*)"/, "$1"), + editorProperties, + subShader + } + }; + return new AstNode(ast); + } + + _ruleSubShader(ctx: _ruleSubShaderCstChildren, param?: any) { + const tags = ctx._ruleTag ? (this.visit(ctx._ruleTag) as TagAstNode) : undefined; + + const pass = ctx._ruleShaderPass?.map((item) => this.visit(item)); + + const position: IPositionRange = { + start: AstNodeUtils.getTokenPosition(ctx.SubShader[0]).start, + end: AstNodeUtils.getTokenPosition(ctx.RCurly[0]).end + }; + + const content = { + tags, + pass + }; + + return new AstNode({ position, content }); + } + + _ruleShaderPass(ctx: _ruleShaderPassCstChildren) { + const tags = ctx._ruleTag ? (this.visit(ctx._ruleTag) as TagAstNode) : undefined; + const properties = ctx._ruleSubShaderPassPropertyAssignment?.map((item) => this.visit(item)); + const structs = ctx._ruleStruct?.map((item) => { + const ret = this.visit(item); + return ret; + }); + const variables: any = ctx._ruleFnVariableDeclaration?.map((item) => { + const ret = this.visit(item); + return ret; + }); + const renderStates = ctx._ruleRenderStateDeclaration?.map((item) => this.visit(item)); + const functions = ctx._ruleFn?.map((item) => { + const ret = this.visit(item); + return ret; + }); + + const defines = ctx._ruleFnMacroDefine?.map((item) => this.visit(item)); + + const content = { + name: ctx.ValueString[0].image.replace(/"(.*)"/, "$1"), + tags, + properties, + structs, + variables, + defines, + renderStates, + functions + }; + + const position: IPositionRange = { + start: AstNodeUtils.getTokenPosition(ctx.Pass[0]).start, + end: AstNodeUtils.getTokenPosition(ctx.RCurly[0]).end + }; + + return new AstNode({ content, position }); + } + + _ruleFnReturnType(children: _ruleFnReturnTypeCstChildren, param?: any) { + const position = AstNodeUtils.getOrTypeCstNodePosition({ children }); + return new ReturnTypeAstNode({ + position, + content: { + text: AstNodeUtils.extractCstToken(children), + isCustom: !!children._ruleVariableType?.[0].children.Identifier + } + }); + } + + _ruleFn(ctx: _ruleFnCstChildren) { + const args = ctx._ruleFnArg?.map((item) => this.visit(item)); + const body = this.visit(ctx._ruleFnBody); + + const returnType = this.visit(ctx._ruleFnReturnType); + const position: IPositionRange = { + start: returnType.position.start, + end: AstNodeUtils.getTokenPosition(ctx.RCurly[0]).end + }; + + return new FnAstNode({ + position, + content: { + returnType, + name: ctx.Identifier[0].image, + args, + body + } + }); + } + + _ruleFnBody(ctx: _ruleFnBodyCstChildren) { + let start: IPosition = { line: Number.MAX_SAFE_INTEGER, offset: -1 }, + end: IPosition = { line: 0, offset: -1 }; + + const iterate = (item: CstNode) => { + const astInfo = this.visit(item); + if (astInfo.position.start.line < start.line) { + start = astInfo.position.start; + } + if (astInfo.position.end.line > end.line) { + end = astInfo.position.end; + } + return astInfo; + }; + + const statements = ctx._ruleFnStatement?.map(iterate); + const macros = ctx._ruleFnMacro?.map(iterate); + + return new FnBodyAstNode({ + content: { statements, macros }, + position: { start, end } + }); + } + + _ruleFnMacro(children: _ruleFnMacroCstChildren, param?: any) { + return AstNodeUtils.defaultVisit.bind(this)(children); + } + + _ruleFnMacroDefine(children: _ruleFnMacroDefineCstChildren, param?: any) { + const value = children._ruleAssignableValue ? this.visit(children._ruleAssignableValue) : undefined; + + const position: IPositionRange = { + start: AstNodeUtils.getTokenPosition(children.m_define[0]).start, + end: value ? value.position.end : AstNodeUtils.getTokenPosition(children.Identifier[0]).end + }; + + return new FnMacroDefineAstNode({ + position, + content: { + variable: children.Identifier[0].image, + value + } + }); + } + + _ruleFnMacroInclude(children: _ruleFnMacroIncludeCstChildren, param?: any) { + const position: IPositionRange = { + start: AstNodeUtils.getTokenPosition(children.m_include[0]).start, + end: AstNodeUtils.getTokenPosition(children.ValueString[0]).end + }; + + return new FnMacroIncludeAstNode({ + position, + content: { + name: children.ValueString[0].image.replace(/"(.*)"/, "$1") + } + }); + } + + _ruleFnMacroCondition(children: _ruleFnMacroConditionCstChildren, param?: any) { + const position: IPositionRange = { + start: AstNodeUtils.getOrTypeCstNodePosition(children._ruleFnMacroConditionDeclare[0]).start, + end: AstNodeUtils.getTokenPosition(children.m_endif[0]).end + }; + + const branch = children._ruleFnMacroConditionBranch && this.visit(children._ruleFnMacroConditionBranch); + + return new FnMacroConditionAstNode({ + position, + content: { + command: AstNodeUtils.extractCstToken(children._ruleFnMacroConditionDeclare[0]), + identifier: children.Identifier[0].image, + body: this.visit(children._ruleFnBody), + branch + } + }); + } + + _ruleFnMacroConditionBranch(children: _ruleFnMacroConditionBranchCstChildren, param?: any) { + const body = this.visit(children._ruleFnBody); + + const position: IPositionRange = { + start: AstNodeUtils.getOrTypeCstNodePosition(children._ruleFnMacroConditionBranchDeclare[0]).start, + end: body.position.end + }; + + return new FnMacroConditionBranchAstNode({ + position, + content: { + declare: AstNodeUtils.extractCstToken(children._ruleFnMacroConditionBranchDeclare[0]), + body + } + }); + } + + _ruleFnStatement(ctx: _ruleFnStatementCstChildren) { + return AstNodeUtils.defaultVisit.bind(this)(ctx); + } + + _ruleFnCall(ctx: _ruleFnCallCstChildren) { + const isCustom = !!ctx._ruleFnCallVariable[0].children.Identifier; + const args = ctx._ruleAssignableValue?.map((item) => { + return this.visit(item); + }); + + const position: IPositionRange = { + start: AstNodeUtils.getOrTypeCstNodePosition(ctx._ruleFnCallVariable[0]).start, + end: AstNodeUtils.getTokenPosition(ctx.RBracket[0]).end + }; + + const content = { + function: AstNodeUtils.extractCstToken(ctx._ruleFnCallVariable[0]), + args, + isCustom + }; + + return new FnCallAstNode({ position, content }); + } + + _ruleFnConditionStatement(ctx: _ruleFnConditionStatementCstChildren) { + const blocks = ctx._ruleFnBlockStatement + .map((item) => this.visit(item)) + .sort((a, b) => a.position.start.line - b.position.start.line); + const [body, elseBranch] = blocks; + const elseIfBranches = ctx._ruleFnConditionStatement + ?.map((item) => this.visit(item)) + .sort((a, b) => a.position.start.line - b.position.start.line); + + let end: IPosition = elseIfBranches[elseIfBranches.length - 1]?.position.end; + const blockEnd = blocks[blocks.length - 1].position.end; + + end = end && end.line > blockEnd.line ? end : blockEnd; + + const position: IPositionRange = { + start: AstNodeUtils.getTokenPosition(ctx.if[0]).start, + end + }; + + return new FnConditionStatementAstNode({ + position, + content: { + relation: this.visit(ctx._ruleFnRelationExpr), + body, + elseBranch, + elseIfBranches + } + }); + } + + _ruleFnRelationExpr(ctx: _ruleFnRelationExprCstChildren) { + const operands = ctx._ruleFnAddExpr.map((item) => this.visit(item)); + const position: IPositionRange = { + start: operands[0].position.start, + end: operands[1].position.end + }; + + return new RelationExprAstNode({ + position, + content: { + operator: this.visit(ctx._ruleRelationOperator), + operands + } + }); + } + + _ruleRelationOperator(children: _ruleRelationOperatorCstChildren, param?: any) { + const position = AstNodeUtils.getOrTypeCstNodePosition({ children }); + return new RelationOperatorAstNode({ position, content: AstNodeUtils.extractCstToken(children) }); + } + + _ruleFnBlockStatement(ctx: _ruleFnBlockStatementCstChildren) { + const position: IPositionRange = { + start: AstNodeUtils.getTokenPosition(ctx.LCurly[0]).start, + end: AstNodeUtils.getTokenPosition(ctx.RCurly[0]).end + }; + + return new FnBlockStatementAstNode({ position, content: this.visit(ctx._ruleFnBody) }); + } + + _ruleFnAssignStatement(ctx: _ruleFnAssignStatementCstChildren) { + const assignee = this.visit(ctx._ruleFnAssignLO); + + const position: IPositionRange = { + start: assignee.position.start, + end: AstNodeUtils.getTokenPosition(ctx.Semicolon[0]).end + }; + + return new FnAssignStatementAstNode({ + position, + content: { + operator: AstNodeUtils.extractCstToken(ctx._ruleFnAssignmentOperator[0]), + assignee, + value: this.visit(ctx._ruleFnExpression) + } + }); + } + + _ruleFnExpression(ctx: _ruleFnExpressionCstChildren) { + return this.visit(ctx._ruleFnAddExpr); + } + + _ruleAddOperator(children: _ruleAddOperatorCstChildren, param?: any) { + const position = AstNodeUtils.getOrTypeCstNodePosition({ children }); + return new AddOperatorAstNode({ + content: AstNodeUtils.extractCstToken(children), + position + }); + } + + _ruleFnAddExpr(ctx: _ruleFnAddExprCstChildren) { + if (ctx._ruleAddOperator) { + const operands = ctx._ruleFnMultiplicationExpr?.map((item) => this.visit(item)); + + const position: IPositionRange = { + start: operands[0].position.start, + end: operands[1].position.end + }; + + return new AddExprAstNode({ + content: { + operators: ctx._ruleAddOperator.map((item) => this.visit(item)), + operands + }, + position + }); + } + + return this.visit(ctx._ruleFnMultiplicationExpr); + } + + _ruleMultiplicationOperator(children: _ruleMultiplicationOperatorCstChildren, param?: any) { + return new MultiplicationOperatorAstNode({ + content: AstNodeUtils.extractCstToken(children), + position: AstNodeUtils.getOrTypeCstNodePosition({ children }) + }); + } + + _ruleFnMultiplicationExpr(ctx: _ruleFnMultiplicationExprCstChildren) { + if (ctx._ruleMultiplicationOperator) { + const operands = ctx._ruleFnAtomicExpr?.map((item) => this.visit(item)); + + const position: IPositionRange = { + start: operands[0].position.start, + end: operands[1].position.end + }; + + return new MultiplicationExprAstNode({ + content: { + operators: ctx._ruleMultiplicationOperator.map((item) => this.visit(item)), + operands + }, + position + }); + } + return this.visit(ctx._ruleFnAtomicExpr); + } + + _ruleFnAtomicExpr(ctx: _ruleFnAtomicExprCstChildren) { + const exprAst: ObjectAstNode = AstNodeUtils.defaultVisit.bind(this)(ctx); + const position = exprAst.position; + let sign: AddOperatorAstNode | undefined; + + if (ctx._ruleAddOperator) { + sign = this.visit(ctx._ruleAddOperator); + position.start = sign.position.start; + delete exprAst.content._ruleAddOperator; + } + + return new FnAtomicExprAstNode({ + content: { sign, RuleFnAtomicExpr: exprAst }, + position + }); + } + + _ruleFnParenthesisExpr(ctx: _ruleFnParenthesisExprCstChildren) { + return this.visit(ctx._ruleFnAddExpr); + } + + _ruleNumber(children: _ruleNumberCstChildren) { + return new NumberAstNode({ + content: AstNodeUtils.extractCstToken(children), + position: AstNodeUtils.getOrTypeCstNodePosition({ children }) + }); + } + + _ruleBoolean(children: _ruleBooleanCstChildren, param?: any) { + const position = AstNodeUtils.getOrTypeCstNodePosition({ children }); + return new BooleanAstNode({ + content: AstNodeUtils.extractCstToken(children), + position + }); + } + + _ruleFnAssignLO(ctx: _ruleFnAssignLOCstChildren) { + if (ctx._ruleFnVariable) { + return this.visit(ctx._ruleFnVariable); + } + + const token = ctx.gl_FragColor ?? ctx.gl_Position; + return new AssignLoAstNode({ + content: token?.[0].image, + position: AstNodeUtils.getOrTypeCstNodePosition({ children: ctx }) + }); + } + + _ruleFnVariable(ctx: _ruleFnVariableCstChildren) { + const position: IPositionRange = { + start: AstNodeUtils.getTokenPosition(ctx.Identifier[0]).start, + end: AstNodeUtils.getTokenPosition(ctx.Identifier[ctx.Identifier.length - 1]).end + }; + return new FnVariableAstNode({ + content: ctx.Identifier.map((item) => item.image), + position + }); + } + + _ruleFnReturnStatement(ctx: _ruleFnReturnStatementCstChildren) { + const position: IPositionRange = { + start: AstNodeUtils.getTokenPosition(ctx.return[0]).start, + end: AstNodeUtils.getTokenPosition(ctx.Semicolon[0]).end + }; + + return new FnReturnStatementAstNode({ position, content: AstNodeUtils.defaultVisit.bind(this)(ctx) }); + } + + _ruleFnArg(ctx: _ruleFnArgCstChildren) { + const position: IPositionRange = { + start: AstNodeUtils.getOrTypeCstNodePosition(ctx._ruleVariableType[0]).start, + end: AstNodeUtils.getTokenPosition(ctx.Identifier[0]).end + }; + + return new FnArgAstNode({ + position, + content: { + name: ctx.Identifier[0].image, + type: { + isCustom: !!ctx._ruleVariableType[0].children.Identifier, + text: AstNodeUtils.extractCstToken(ctx._ruleVariableType[0]) + } + } + }); + } + + _ruleRenderStateDeclaration(ctx: _ruleRenderStateDeclarationCstChildren) { + const properties = ctx._ruleStatePropertyAssign?.map((item) => this.visit(item)); + + const position: IPositionRange = { + start: AstNodeUtils.getOrTypeCstNodePosition(ctx._ruleRenderStateType[0]).start, + end: AstNodeUtils.getTokenPosition(ctx.RCurly[0]).end + }; + + return new RenderStateDeclarationAstNode({ + position, + content: { + name: ctx.Identifier[0].image, + type: AstNodeUtils.extractCstToken(ctx._ruleRenderStateType[0]), + properties + } + }); + } + + _ruleAssignableValue(children: _ruleAssignableValueCstChildren, param?: any) { + if (children._ruleFnAddExpr) { + return this.visit(children._ruleFnAddExpr); + } + + const position = AstNodeUtils.getOrTypeCstNodePosition({ children }); + return new AssignableValueAstNode({ position, content: AstNodeUtils.extractCstToken(children) }); + } + + _ruleStatePropertyAssign(ctx: _ruleStatePropertyAssignCstChildren) { + const position: IPositionRange = { + start: AstNodeUtils.getOrTypeCstNodePosition(ctx._ruleStateProperty[0]).start, + end: AstNodeUtils.getOrTypeCstNodePosition(ctx._ruleAssignableValue[0]).end + }; + + return new StatePropertyAssignAstNode({ + position, + content: { + name: AstNodeUtils.extractCstToken(ctx._ruleStateProperty[0]), + value: this.visit(ctx._ruleAssignableValue) + } + }); + } + + _ruleFnVariableDeclaration(ctx: _ruleFnVariableDeclarationCstChildren) { + const position: IPositionRange = { + start: AstNodeUtils.getOrTypeCstNodePosition(ctx._ruleVariableType[0]).start, + end: AstNodeUtils.getTokenPosition(ctx.Semicolon[0]).end + }; + + return new VariableDeclarationAstNode({ + position, + content: { + type: this.visit(ctx._ruleVariableType), + variable: ctx.Identifier[0].image, + default: ctx._ruleFnExpression ? this.visit(ctx._ruleFnExpression) : undefined + } + }); + } + + _ruleStruct(ctx: _ruleStructCstChildren) { + const variables = ctx._ruleDeclaration?.map((item) => this.visit(item)); + + const position: IPositionRange = { + start: AstNodeUtils.getTokenPosition(ctx.struct[0]).start, + end: AstNodeUtils.getTokenPosition(ctx.RCurly[0]).end + }; + + return new StructAstNode({ + position, + content: { + name: ctx.Identifier[0].image, + variables + } + }); + } + + _ruleVariableType(children: _ruleVariableTypeCstChildren, param?: any) { + const position: IPositionRange = AstNodeUtils.getOrTypeCstNodePosition({ children }); + return new VariableTypeAstNode({ + position, + content: { + text: AstNodeUtils.extractCstToken(children), + isCustom: !!children.Identifier + } + }); + } + + _ruleDeclaration(ctx: _ruleDeclarationCstChildren) { + const type = this.visit(ctx._ruleVariableType); + + const position: IPositionRange = { + start: type.position.start, + end: AstNodeUtils.getTokenPosition(ctx.Identifier[0]).end + }; + + return new DeclarationAstNode({ + position, + content: { + type, + variable: ctx.Identifier[0].image + } + }); + } + + _ruleSubShaderPassPropertyAssignment(ctx: _ruleSubShaderPassPropertyAssignmentCstChildren) { + const position: IPositionRange = { + start: AstNodeUtils.getOrTypeCstNodePosition(ctx._ruleShaderPassPropertyType[0]).start, + end: AstNodeUtils.getTokenPosition(ctx.Semicolon[0]).end + }; + + return new PassPropertyAssignmentAstNode({ + position, + content: { + type: AstNodeUtils.extractCstToken(ctx._ruleShaderPassPropertyType[0]), + value: ctx.Identifier[0].image + } + }); + } + + _ruleTag(ctx: _ruleTagCstChildren) { + const position: IPositionRange = { + start: AstNodeUtils.getTokenPosition(ctx.Tags[0]).start, + end: AstNodeUtils.getTokenPosition(ctx.RCurly[0]).end + }; + + return new TagAstNode({ + content: ctx._ruleTagAssignment?.map((item) => this.visit(item)), + position + }); + } + + _ruleTagAssignment(ctx: _ruleTagAssignmentCstChildren) { + const position: IPositionRange = { + start: AstNodeUtils.getOrTypeCstNodePosition(ctx._ruleTagType[0]).start, + end: AstNodeUtils.getTokenPosition(ctx.ValueString[0]).end + }; + + return new TagAssignmentAstNode({ + position, + content: { + tag: AstNodeUtils.extractCstToken(ctx._ruleTagType[0]), + value: ctx.ValueString[0].image + } + }); + } + + _ruleProperty(ctx: _rulePropertyCstChildren) { + const position: IPositionRange = { + start: AstNodeUtils.getTokenPosition(ctx.EditorProperties[0]).start, + end: AstNodeUtils.getTokenPosition(ctx.RCurly[0]).end + }; + + return new PropertyAstNode({ + content: ctx._rulePropertyItem?.map((item) => this.visit(item)), + position + }); + } + + _rulePropertyItem(ctx: _rulePropertyItemCstChildren, param?: any) { + const position: IPositionRange = { + start: AstNodeUtils.getTokenPosition(ctx.Identifier[0]).start, + end: AstNodeUtils.getTokenPosition(ctx.Semicolon[0]).end + }; + + return new PropertyItemAstNode({ + position, + content: { + name: ctx.Identifier[0].image, + desc: ctx.ValueString[0].image, + type: AstNodeUtils.extractCstToken(ctx._rulePropertyItemType[0]), + default: this.visit(ctx._rulePropertyItemValue) + } + }); + } + + _rulePropertyItemValue(ctx: _rulePropertyItemValueCstChildren) { + return AstNodeUtils.defaultVisit.bind(this)(ctx); + } + + _ruleTupleFloat4(ctx: _ruleTupleFloat4CstChildren) { + const position: IPositionRange = { + start: AstNodeUtils.getTokenPosition(ctx.LBracket[0]).start, + end: AstNodeUtils.getTokenPosition(ctx.RBracket[0]).end + }; + return new TupleNumber4AstNode({ position, content: ctx.ValueFloat.map((n) => Number(n)) as any }); + } + + _ruleTupleInt4(ctx: _ruleTupleInt4CstChildren) { + const position: IPositionRange = { + start: AstNodeUtils.getTokenPosition(ctx.LBracket[0]).start, + end: AstNodeUtils.getTokenPosition(ctx.RBracket[0]).end + }; + + const astInfo = { position, content: ctx.ValueInt.map((n) => Number(n.image)) }; + return new TupleNumber4AstNode(astInfo as any); + } + + _ruleRange(ctx: _ruleRangeCstChildren) { + const position: IPositionRange = { + start: AstNodeUtils.getTokenPosition(ctx.Range[0]).start, + end: AstNodeUtils.getTokenPosition(ctx.RBracket[0]).end + }; + return new RangeAstNode({ position, content: ctx.ValueInt.map((int) => Number(int.image)) as any }); + } +} diff --git a/packages/shader-lab/src/ast-node/AstNode.ts b/packages/shader-lab/src/ast-node/AstNode.ts new file mode 100644 index 0000000000..0e641d5d95 --- /dev/null +++ b/packages/shader-lab/src/ast-node/AstNode.ts @@ -0,0 +1,424 @@ +import { AstNodeUtils } from "../AstNodeUtils"; +import { DiagnosticSeverity } from "../Constants"; +import RuntimeContext from "../RuntimeContext"; +import { + IAddOperatorAstContent, + IAssignableValueAstContent, + IBooleanAstContent, + IDeclarationAstContent, + IFnAddExprAstContent, + IFnArgAstContent, + IFnAssignLOAstContent, + IFnAssignStatementAstContent, + IFnAstContent, + IFnAtomicExprAstContent, + IFnBlockStatementAstContent, + IFnBodyAstContent, + IFnCallAstContent, + IFnConditionStatementAstContent, + IFnMacroConditionAstContent, + IFnMacroConditionBranchAstContent, + IFnMacroDefineAstContent, + IFnMacroIncludeAstContent, + IFnMultiplicationExprAstContent, + IFnRelationExprAstContent, + IFnReturnStatementAstContent, + IFnReturnTypeAstContent, + IFnVariableAstContent, + IFnVariableDeclarationAstContent, + IMultiplicationOperatorAstContent, + INumberAstContent, + IPassPropertyAssignmentAstContent, + IPropertyAstContent, + IPropertyItemAstContent, + IRelationOperatorAstContent, + IRenderStateDeclarationAstContent, + IStatePropertyAssignAstContent, + IStructAstContent, + ITagAssignmentAstContent, + ITagAstContent, + ITupleNumber2, + ITupleNumber3, + ITupleNumber4, + IVariableTypeAstContent +} from "./AstNodeContent"; + +export interface IPosition { + line: number; + offset: number; +} + +export interface IPositionRange { + start: IPosition; + end: IPosition; +} + +export interface IAstInfo { + position: IPositionRange; + content: T; +} + +export class AstNode implements IAstInfo { + position: IPositionRange; + content: T; + + /** @internal */ + private _isAstNode = true; + + constructor(ast: IAstInfo) { + this.position = ast.position; + this.content = ast.content; + } + + /** @internal */ + _doSerialization(context: RuntimeContext, args?: any): string { + throw { message: "NOT IMPLEMENTED", astNode: this, ...this.position }; + } + + /** @internal */ + _beforeSerialization(context: RuntimeContext, args?: any) { + context.serializingAstNode = this; + } + + serialize(context: RuntimeContext, args?: any): string { + this._beforeSerialization(context, args); + return this._doSerialization(context, args); + } + + private _jsonifyObject(obj: any, includePos: boolean, withClass = false) { + if (typeof obj !== "object") return obj; + const ret = {} as any; + if (obj._isAstNode) { + return obj.toJson(includePos, withClass); + } + for (const k in obj) { + let v = obj[k]; + if (v === null || v === undefined) continue; + if (v._isAstNode) { + v = v.toJson(includePos, withClass); + } else if (Array.isArray(v)) { + v = v.map((i) => this._jsonifyObject(i, includePos, withClass)); + } else if (typeof v === "object") { + v = this._jsonifyObject(v, includePos, withClass); + } + ret[k] = v; + } + + return ret; + } + + toJson(includePos = false, withClass = false) { + let res: any; + if (Array.isArray(this.content)) { + res = this.content.map((item) => this._jsonifyObject(item, includePos, withClass)); + } else if (typeof this.content === "object") { + res = this._jsonifyObject(this.content, includePos, withClass); + } else { + res = this.content; + } + let ret: any = { content: res }; + if (includePos) { + ret.position = this.position; + } + if (withClass) { + ret.Class = this.constructor.name; + } + return ret; + } +} + +export class ReturnTypeAstNode extends AstNode { + override _doSerialization(context: RuntimeContext): string { + if (this.content.isCustom) { + context.findGlobal(this.content.text); + } + return this.content.text; + } +} + +export class ObjectAstNode extends AstNode>> { + override _doSerialization(context: RuntimeContext): string { + const astList = Object.values(this.content) + .sort(AstNodeUtils.astSortAsc) + .filter((item) => item.constructor.name !== "AstNode"); + return astList.map((ast) => ast.serialize(context)).join("\n"); + } +} + +export class FnAstNode extends AstNode { + override _doSerialization(context: RuntimeContext): string { + context.functionAstStack.push({ fnAst: this, localDeclaration: [] }); + + let returnType: string; + let args: string; + let fnName: string; + + if (context.currentMainFnAst === this) { + returnType = "void"; + args = ""; + fnName = "main"; + } else { + returnType = this.content.returnType.serialize(context); + args = this.content.args.map((arg) => arg.serialize(context)).join(", "); + fnName = this.content.name; + } + const body = this.content.body.serialize(context); + + context.functionAstStack.pop(); + return `${returnType} ${fnName} (${args}) {\n${body}\n}`; + } +} + +export class FnBodyAstNode extends AstNode { + override _doSerialization(context: RuntimeContext): string { + const statements = [...(this.content.macros ?? []), ...(this.content.statements ?? [])].sort( + (a, b) => a.position.start.line - b.position.start.line + ); + return statements.map((s) => s.serialize(context)).join("\n"); + } +} + +export class FnMacroDefineAstNode extends AstNode {} + +export class FnMacroIncludeAstNode extends AstNode {} + +export class FnMacroConditionAstNode extends AstNode { + override _doSerialization(context: RuntimeContext): string { + const body = this.content.body.serialize(context); + const branch = this.content.branch?.serialize(context) ?? ""; + return `${this.content.command} ${this.content.identifier}\n ${body}\n${branch}\n#endif`; + } +} + +export class FnMacroConditionBranchAstNode extends AstNode {} + +export class FnCallAstNode extends AstNode { + override _doSerialization(context: RuntimeContext): string { + if (this.content.isCustom) { + if (!context.referenceGlobal(this.content.function)) { + context.diagnostics.push({ + severity: DiagnosticSeverity.Warning, + message: `Not found function definition: ${this.content.function}`, + token: this.position + }); + } + } + const args = this.content.args.map((item) => item.serialize(context)).join(", "); + return `${this.content.function}(${args})`; + } +} + +export class FnConditionStatementAstNode extends AstNode {} + +export class FnBlockStatementAstNode extends AstNode {} + +export class RelationOperatorAstNode extends AstNode { + override _doSerialization(context: RuntimeContext): string { + return this.content.text; + } +} + +export class RelationExprAstNode extends AstNode {} + +export class FnAssignStatementAstNode extends AstNode { + override _doSerialization(context: RuntimeContext): string { + const { value } = this.content; + const valueStr = value.serialize(context); + return `${this.content.assignee.serialize(context)} ${this.content.operator} ${valueStr};`; + } +} + +export class AddOperatorAstNode extends AstNode { + override _doSerialization(context: RuntimeContext): string { + return this.content; + } +} + +export class MultiplicationOperatorAstNode extends AstNode { + override _doSerialization(context: RuntimeContext): string { + return this.content; + } +} + +export class AddExprAstNode extends AstNode { + override _doSerialization(context: RuntimeContext): string { + const orderItemList = [...this.content.operands, ...this.content.operators].sort(AstNodeUtils.astSortAsc); + return orderItemList.map((item) => item.serialize(context)).join(" "); + } +} + +export class MultiplicationExprAstNode extends AstNode { + override _doSerialization(context: RuntimeContext): string { + const orderItemList = [...this.content.operands, ...this.content.operators].sort(AstNodeUtils.astSortAsc); + return orderItemList.map((item) => item.serialize(context)).join(" "); + } +} + +export class FnAtomicExprAstNode extends AstNode { + override _doSerialization(context: RuntimeContext): string { + const signStr = this.content.sign?.serialize(context) ?? ""; + return signStr + this.content.RuleFnAtomicExpr.serialize(context); + } +} + +export class NumberAstNode extends AstNode { + override _doSerialization(context: RuntimeContext): string { + return this.content; + } +} + +export class BooleanAstNode extends AstNode { + override _doSerialization(context: RuntimeContext): string { + return this.content; + } +} + +export class AssignLoAstNode extends AstNode { + override _doSerialization(context: RuntimeContext): string { + return this.content; + } +} + +export class FnVariableAstNode extends AstNode { + override _doSerialization(context: RuntimeContext): string { + const objName = this.content[0]; + const propName = this.content[1]; + if (propName) { + if (objName === context.varyingStructInfo.objectName) { + const ref = context.varyingStructInfo.reference.find((ref) => ref.property.content.variable === propName); + ref && (ref.referenced = true); + return this.content.slice(1).join("."); + } else { + const attribStruct = context.attributeStructListInfo.find((struct) => struct.objectName === objName); + if (attribStruct) { + const ref = attribStruct.reference.find((ref) => ref.property.content.variable === propName); + ref && (ref.referenced = true); + return this.content.slice(1).join("."); + } + } + } + + if (!context.findLocal(objName)) { + if (!context.referenceGlobal(objName)) { + context.diagnostics.push({ + severity: DiagnosticSeverity.Error, + message: `Not found variable definition: ${objName}`, + token: this.position + }); + } + } + + return this.content.join("."); + } +} + +export class FnReturnStatementAstNode extends AstNode { + override _doSerialization(context: RuntimeContext): string { + if (context.currentFunctionInfo.fnAst === context.currentMainFnAst) { + return ""; + } + return `return ${this.content.serialize(context)};`; + } +} + +export class FnArgAstNode extends AstNode { + override _doSerialization(context: RuntimeContext, args?: any): string { + context.currentFunctionInfo.localDeclaration.push( + new DeclarationAstNode({ + position: this.position, + content: { + variable: this.content.name, + type: new VariableTypeAstNode({ position: this.position, content: this.content.type }) + } + }) + ); + return `${this.content.type.text} ${this.content.name}`; + } +} + +export class RenderStateDeclarationAstNode extends AstNode {} + +export class StatePropertyAssignAstNode extends AstNode {} + +export class AssignableValueAstNode extends AstNode { + override _doSerialization(context: RuntimeContext): string { + return this.content; + } +} +export class VariableTypeAstNode extends AstNode { + override _doSerialization(context: RuntimeContext): string { + return this.content.text; + } +} + +export class VariableDeclarationAstNode extends AstNode { + override _doSerialization(context: RuntimeContext, opts?: { global: boolean }): string { + if (context.currentFunctionInfo) { + context.currentFunctionInfo.localDeclaration.push(this); + } + const typeNode = this.content.type; + if (typeNode.content.text === context.varyingTypeAstNode.content.text) { + if (this.content.default) { + context.diagnostics.push({ + severity: DiagnosticSeverity.Error, + message: "不应该给 varying 对象赋值", + token: this.content.default.position + }); + } + context.varyingStructInfo.objectName = this.content.variable; + + return ""; + } + if (typeNode.content.isCustom) { + if (!context.referenceGlobal(typeNode.content.text)) { + context.diagnostics.push({ + severity: DiagnosticSeverity.Error, + message: `Undefined type ${typeNode.content.text}`, + token: this.position + }); + } + } + let ret = `${typeNode.content.text} ${this.content.variable}`; + if (opts?.global) { + ret = "uniform " + ret; + } + if (this.content.default) { + ret += " = " + this.content.default.serialize(context); + } + return ret + ";"; + } +} + +export class DeclarationAstNode extends AstNode {} + +export class StructAstNode extends AstNode {} + +export class PassPropertyAssignmentAstNode extends AstNode {} + +export class TagAssignmentAstNode extends AstNode { + override _doSerialization(context: RuntimeContext): string { + return `${this.content.tag} = ${this.content.value}`; + } +} + +export class TagAstNode extends AstNode { + toObj(): Record { + const ret = {} as any; + for (const t of this.content) { + ret[t.content.tag] = t.content.value.replace(/"(.*)"/, "$1"); + } + return ret; + } +} + +export class PropertyItemAstNode extends AstNode {} + +export class PropertyAstNode extends AstNode {} + +export class TupleNumber4AstNode extends AstNode {} + +export class TupleNumber3AstNode extends AstNode {} + +export class TupleNumber2AstNode extends AstNode {} + +export class RangeAstNode extends AstNode {} diff --git a/packages/shader-lab/src/ast-node/AstNodeContent.ts b/packages/shader-lab/src/ast-node/AstNodeContent.ts new file mode 100644 index 0000000000..2d15310fbf --- /dev/null +++ b/packages/shader-lab/src/ast-node/AstNodeContent.ts @@ -0,0 +1,218 @@ +import { + AstNode, + PropertyItemAstNode, + TagAstNode, + PassPropertyAssignmentAstNode, + StructAstNode, + VariableDeclarationAstNode, + FnAstNode, + ReturnTypeAstNode, + FnArgAstNode, + AssignLoAstNode, + FnVariableAstNode, + AddOperatorAstNode, + MultiplicationExprAstNode, + MultiplicationOperatorAstNode, + FnAtomicExprAstNode, + ObjectAstNode, + StatePropertyAssignAstNode, + AssignableValueAstNode, + VariableTypeAstNode, + DeclarationAstNode, + TagAssignmentAstNode +} from "./AstNode"; + +export interface IShaderAstContent { + name: string; + editorProperties?: AstNode>; + subShader: Array>; +} + +export interface IPropertyItemAstContent { + name: string; + desc: string; + type: string; + default: Record; +} + +export interface ISubShaderAstContent { + tags?: TagAstNode; + pass: Array>; +} + +export interface IFunctionAstContent { + returnType: AstNode; + name: string; + args: Array; + body: AstNode; +} + +export interface IPassAstContent { + name: string; + tags: TagAstNode; + properties: Array; + structs: Array; + variables: Array; + functions: Array; +} + +export interface ITypeAstContent { + text: string; + isCustom: boolean; +} + +export interface IFnReturnTypeAstContent { + text: string; + isCustom: boolean; +} + +export interface IFnAstContent { + returnType: ReturnTypeAstNode; + name: string; + args: Array; + body: AstNode; +} + +export interface IFnBodyAstContent { + statements: Array; + macros: Array; +} + +export interface IFnMacroDefineAstContent { + variable: string; + value?: AstNode; +} + +export interface IFnMacroIncludeAstContent { + name: string; +} + +export interface IFnMacroConditionAstContent { + command: string; + identifier: string; + body: AstNode; + branch?: AstNode; +} + +export interface IFnMacroConditionBranchAstContent { + declare: string; + body: AstNode; +} + +export interface IFnCallAstContent { + function: string; + args: Array; + isCustom: boolean; +} + +export interface IFnConditionStatementAstContent { + relation: AstNode; + body: AstNode; + elseBranch: AstNode; + elseIfBranches: Array>; +} + +export interface IFnRelationExprAstContent { + operands: Array; + operator: AstNode; +} + +export type IFnBlockStatementAstContent = AstNode; + +export interface IRelationOperatorAstContent { + text: string; +} + +export interface IFnAssignStatementAstContent { + assignee: AssignLoAstNode | FnVariableAstNode; + value: AstNode; + operator: string; +} + +export type IFnExpressionAstContent = AstNode; + +export type IFnAddExprAstContent = { + operators: Array; + operands: Array; +}; + +export interface IFnMultiplicationExprAstContent { + operators: Array; + operands: Array; +} + +export type IMultiplicationOperatorAstContent = string; + +export type IAddOperatorAstContent = string; + +export interface IFnAtomicExprAstContent { + sign?: AddOperatorAstNode; + RuleFnAtomicExpr: AstNode; +} + +export type INumberAstContent = string; +export type IBooleanAstContent = string; +export type IFnAssignLOAstContent = string; + +export type IFnVariableAstContent = Array; +export type IFnReturnStatementAstContent = ObjectAstNode; + +export interface IFnArgAstContent { + name: string; + type: { + isCustom: boolean; + text: string; + }; +} + +export interface IRenderStateDeclarationAstContent { + name: string; + type: string; + properties: Array; +} + +export interface IStatePropertyAssignAstContent { + name: string; + value: AssignableValueAstNode; +} + +export type IAssignableValueAstContent = string; + +export interface IVariableTypeAstContent { + text: string; + isCustom: boolean; +} + +export interface IFnVariableDeclarationAstContent { + type: VariableTypeAstNode; + variable: string; + default?: AstNode; +} + +export interface IDeclarationAstContent { + type: VariableTypeAstNode; + variable: string; +} + +export interface IStructAstContent { + name: string; + variables: Array; +} + +export interface IPassPropertyAssignmentAstContent { + type: string; + value: string; +} + +export interface ITagAssignmentAstContent { + tag: string; + value: string; +} + +export type ITagAstContent = Array; + +export type IPropertyAstContent = Array; + +export type ITupleNumber4 = [number, number, number, number]; +export type ITupleNumber3 = [number, number, number]; +export type ITupleNumber2 = [number, number]; diff --git a/packages/shader-lab/src/ast-node/index.ts b/packages/shader-lab/src/ast-node/index.ts new file mode 100644 index 0000000000..947d94e170 --- /dev/null +++ b/packages/shader-lab/src/ast-node/index.ts @@ -0,0 +1,2 @@ +export * from "./AstNode"; +export * from "./AstNodeContent"; diff --git a/packages/shader-lab/src/index.ts b/packages/shader-lab/src/index.ts new file mode 100644 index 0000000000..e9332e4a76 --- /dev/null +++ b/packages/shader-lab/src/index.ts @@ -0,0 +1,5 @@ +import { AstNodeUtils } from "./AstNodeUtils"; +export { ShaderLab } from "./ShaderLab"; +export { ShaderVisitor, parser } from "./ShaderVisitor"; +export { ShaderParser } from "./parser/ShaderParser"; +export const parseShader = AstNodeUtils.parseShader; diff --git a/packages/shader-lab/src/parser/ShaderParser.ts b/packages/shader-lab/src/parser/ShaderParser.ts new file mode 100644 index 0000000000..53e10ca368 --- /dev/null +++ b/packages/shader-lab/src/parser/ShaderParser.ts @@ -0,0 +1,528 @@ +import { CstParser, Lexer, TokenType } from "chevrotain"; +import { Others, Symbols, Types, EditorTypes, Keywords, Values, GLKeywords } from "./tokens"; +import { ValueFalse, ValueFloat, ValueInt, ValueTrue } from "./tokens/value"; + +const allTokens = [ + Others.WhiteSpace, + Others.CommentLine, + Others.CommentMultiLine, + ...Symbols.tokenList, + ...Keywords.tokenList, + ...GLKeywords.variableTokenList, + ...GLKeywords.funcTokenList, + ...GLKeywords.macroTokenList, + ...GLKeywords.otherTokenList, + ...Keywords.tagTokenList, + ...Values.tokenList, + ...Types.tokenList, + ...EditorTypes.tokenList, + Others.Identifier +]; + +export class ShaderParser extends CstParser { + lexer: Lexer; + + constructor() { + super(allTokens, { maxLookahead: 8 }); + this.lexer = new Lexer(allTokens); + + this.performSelfAnalysis(); + } + + parse(text: string) { + // TODO: replace include + + const lexingResult = this.lexer.tokenize(text); + this.input = lexingResult.tokens; + } + + public ruleShader = this.RULE("_ruleShader", () => { + this.CONSUME(Keywords.Shader); + this.CONSUME(Values.ValueString); + this.CONSUME(Symbols.LCurly); + this.MANY(() => { + this.OR([{ ALT: () => this.SUBRULE(this._ruleProperty) }, { ALT: () => this.SUBRULE(this._ruleSubShader) }]); + }); + this.CONSUME(Symbols.RCurly); + }); + + private _ruleSubShader = this.RULE("_ruleSubShader", () => { + this.CONSUME(Keywords.SubShader); + this.CONSUME(Symbols.LCurly); + this.MANY(() => { + this.OR([ + { ALT: () => this.SUBRULE(this._ruleShaderPass) }, + { ALT: () => this.SUBRULE(this._ruleTag) }, + { ALT: () => this.SUBRULE(this._ruleRenderStateDeclaration) } + ]); + }); + this.CONSUME(Symbols.RCurly); + }); + + private _ruleShaderPass = this.RULE("_ruleShaderPass", () => { + this.CONSUME(Keywords.Pass); + this.CONSUME(Values.ValueString); + this.CONSUME(Symbols.LCurly); + this.MANY(() => { + this.OR([ + { ALT: () => this.SUBRULE(this._ruleTag) }, + { ALT: () => this.SUBRULE(this._ruleStruct) }, + { ALT: () => this.SUBRULE(this._ruleFn) }, + { ALT: () => this.SUBRULE(this._ruleFnVariableDeclaration) }, + { ALT: () => this.SUBRULE(this._ruleSubShaderPassPropertyAssignment) }, + { ALT: () => this.SUBRULE(this._ruleRenderStateDeclaration) }, + { ALT: () => this.SUBRULE(this._ruleFnMacroInclude) }, + { ALT: () => this.SUBRULE(this._ruleFnMacroDefine) } + ]); + }); + this.CONSUME(Symbols.RCurly); + }); + + private _ruleStruct = this.RULE("_ruleStruct", () => { + this.CONSUME(GLKeywords.Struct); + this.CONSUME(Others.Identifier); + this.CONSUME(Symbols.LCurly); + this.MANY(() => { + this.SUBRULE(this._ruleDeclaration); + this.CONSUME(Symbols.Semicolon); + }); + this.CONSUME(Symbols.RCurly); + }); + + private _ruleDeclaration = this.RULE("_ruleDeclaration", () => { + this.SUBRULE(this._ruleVariableType); + this.CONSUME(Others.Identifier); + }); + + private _ruleVariableType = this.RULE("_ruleVariableType", () => { + const types = Types.tokenList.map((item) => ({ + ALT: () => this.CONSUME(item) + })); + + this.OR([...types, { ALT: () => this.CONSUME(Others.Identifier) }]); + }); + + private _ruleTag = this.RULE("_ruleTag", () => { + this.CONSUME(Keywords.Tags); + this.CONSUME(Symbols.LCurly); + this.MANY_SEP({ + DEF: () => { + this.SUBRULE(this._ruleTagAssignment); + }, + SEP: Symbols.Comma + }); + this.CONSUME(Symbols.RCurly); + }); + + private _ruleTagAssignment = this.RULE("_ruleTagAssignment", () => { + this.SUBRULE(this._ruleTagType); + this.CONSUME(Symbols.Equal); + this.CONSUME(Values.ValueString); + }); + + private _ruleTagType = this.RULE("_ruleTagType", () => { + this.OR( + Keywords.tagTokenList.map((kw) => ({ + ALT: () => this.CONSUME(kw) + })) + ); + }); + + private _ruleFn = this.RULE("_ruleFn", () => { + this.SUBRULE(this._ruleFnReturnType); + this.CONSUME1(Others.Identifier); + this.CONSUME1(Symbols.LBracket); + this.MANY_SEP({ + SEP: Symbols.Comma, + DEF: () => this.SUBRULE(this._ruleFnArg) + }); + this.CONSUME(Symbols.RBracket); + this.CONSUME(Symbols.LCurly); + this.SUBRULE(this._ruleFnBody); + this.CONSUME(Symbols.RCurly); + }); + + private _ruleFnReturnType = this.RULE("_ruleFnReturnType", () => { + this.OR([{ ALT: () => this.SUBRULE(this._ruleVariableType) }, { ALT: () => this.CONSUME(GLKeywords.Void) }]); + }); + + private _ruleFnArg = this.RULE("_ruleFnArg", () => { + this.SUBRULE(this._ruleVariableType); + this.CONSUME2(Others.Identifier); + }); + + private _ruleFnBody = this.RULE("_ruleFnBody", () => { + this.MANY(() => { + this.OR([{ ALT: () => this.SUBRULE(this._ruleFnMacro) }, { ALT: () => this.SUBRULE(this._ruleFnStatement) }]); + }); + }); + + private _ruleFnMacro = this.RULE("_ruleFnMacro", () => { + this.OR([ + { ALT: () => this.SUBRULE(this._ruleFnMacroDefine) }, + { ALT: () => this.SUBRULE(this._ruleFnMacroInclude) }, + { ALT: () => this.SUBRULE(this._ruleFnMacroCondition) } + ]); + }); + + private _ruleFnMacroCondition = this.RULE("_ruleFnMacroCondition", () => { + this.SUBRULE(this._ruleFnMacroConditionDeclare); + this.CONSUME(Others.Identifier); + this.SUBRULE(this._ruleFnBody); + this.OPTION(() => { + this.SUBRULE(this._ruleFnMacroConditionBranch); + }); + this.OPTION1(() => { + this.SUBRULE1(this._ruleFnBody); + }); + this.CONSUME(GLKeywords.M_ENDIF); + }); + + private _ruleFnMacroConditionDeclare = this.RULE("_ruleFnMacroConditionDeclare", () => { + this.OR([{ ALT: () => this.CONSUME(GLKeywords.M_IFDEF) }, { ALT: () => this.CONSUME(GLKeywords.M_IFNDEF) }]); + }); + + private _ruleFnMacroConditionBranch = this.RULE("_ruleFnMacroConditionBranch", () => { + this.SUBRULE(this._ruleFnMacroConditionBranchDeclare); + this.SUBRULE(this._ruleFnBody); + }); + + private _ruleFnMacroConditionBranchDeclare = this.RULE("_ruleFnMacroConditionBranchDeclare", () => { + this.OR([{ ALT: () => this.CONSUME(GLKeywords.M_ELSE) }]); + }); + + private _ruleFnMacroDefine = this.RULE("_ruleFnMacroDefine", () => { + this.CONSUME(GLKeywords.M_DEFINE); + this.CONSUME(Others.Identifier); + this.OPTION(() => { + this.SUBRULE(this._ruleAssignableValue); + }); + }); + + private _ruleAssignableValue = this.RULE("_ruleAssignableValue", () => { + this.OR([ + { ALT: () => this.CONSUME(Values.ValueTrue) }, + { ALT: () => this.CONSUME(Values.ValueFalse) }, + { ALT: () => this.CONSUME(Values.ValueString) }, + { ALT: () => this.SUBRULE(this._ruleFnAddExpr) }, + { ALT: () => this.CONSUME(GLKeywords.GLFragColor) }, + { ALT: () => this.CONSUME(GLKeywords.GLPosition) } + ]); + }); + + private _ruleFnAddExpr = this.RULE("_ruleFnAddExpr", () => { + this.SUBRULE(this._ruleFnMultiplicationExpr); + this.MANY(() => { + this.SUBRULE(this._ruleAddOperator); + this.SUBRULE2(this._ruleFnMultiplicationExpr); + }); + }); + + private _ruleFnMultiplicationExpr = this.RULE("_ruleFnMultiplicationExpr", () => { + this.SUBRULE(this._ruleFnAtomicExpr); + this.MANY(() => { + this.SUBRULE(this._ruleMultiplicationOperator); + this.SUBRULE2(this._ruleFnAtomicExpr); + }); + }); + + private _ruleFnAtomicExpr = this.RULE("_ruleFnAtomicExpr", () => { + this.OPTION(() => this.SUBRULE(this._ruleAddOperator)); + this.OR([ + { ALT: () => this.SUBRULE(this._ruleFnParenthesisExpr) }, + { ALT: () => this.SUBRULE(this._ruleNumber) }, + { ALT: () => this.SUBRULE(this._ruleFnCall) }, + { ALT: () => this.SUBRULE(this._ruleFnVariable) } + ]); + }); + + private _ruleAddOperator = this.RULE("_ruleAddOperator", () => { + this.OR([{ ALT: () => this.CONSUME(Symbols.Add) }, { ALT: () => this.CONSUME(Symbols.Minus) }]); + }); + + private _ruleFnParenthesisExpr = this.RULE("_ruleFnParenthesisExpr", () => { + this.CONSUME1(Symbols.LBracket); + this.SUBRULE(this._ruleFnAddExpr); + this.CONSUME(Symbols.RBracket); + }); + + private _ruleNumber = this.RULE("_ruleNumber", () => { + this.OR([{ ALT: () => this.CONSUME1(ValueInt) }, { ALT: () => this.CONSUME(ValueFloat) }]); + }); + + private _ruleFnCall = this.RULE("_ruleFnCall", () => { + this.SUBRULE(this._ruleFnCallVariable); + this.CONSUME1(Symbols.LBracket); + this.MANY_SEP({ + SEP: Symbols.Comma, + DEF: () => { + this.SUBRULE(this._ruleAssignableValue); + } + }); + this.CONSUME(Symbols.RBracket); + }); + + private _ruleFnCallVariable = this.RULE("_ruleFnCallVariable", () => { + this.OR([ + ...Types.tokenList.map((item) => ({ ALT: () => this.CONSUME(item) })), + { ALT: () => this.CONSUME(GLKeywords.Pow) }, + { ALT: () => this.CONSUME(GLKeywords.Texture2D) }, + { ALT: () => this.CONSUME(Others.Identifier) } + ]); + }); + + private _ruleFnVariable = this.RULE("_ruleFnVariable", () => { + this.CONSUME(Others.Identifier); + this.MANY(() => { + this.CONSUME(Symbols.Dot); + this.CONSUME1(Others.Identifier); + }); + }); + + private _ruleMultiplicationOperator = this.RULE("_ruleMultiplicationOperator", () => { + this.OR([{ ALT: () => this.CONSUME(Symbols.Multiply) }, { ALT: () => this.CONSUME(Symbols.Divide) }]); + }); + + private _ruleFnMacroInclude = this.RULE("_ruleFnMacroInclude", () => { + this.CONSUME(GLKeywords.M_INCLUDE); + this.CONSUME(Values.ValueString); + }); + + private _ruleFnStatement = this.RULE("_ruleFnStatement", () => { + this.OR([ + { ALT: () => this.SUBRULE(this._ruleFnCall) }, + { ALT: () => this.SUBRULE(this._ruleFnReturnStatement) }, + { ALT: () => this.SUBRULE(this._ruleFnVariableDeclaration) }, + { ALT: () => this.SUBRULE(this._ruleFnConditionStatement) }, + { ALT: () => this.SUBRULE(this._ruleFnAssignStatement) }, + { + ALT: () => { + this.CONSUME(GLKeywords.Discard); + this.CONSUME(Symbols.Semicolon); + } + } + ]); + }); + + private _ruleFnReturnStatement = this.RULE("_ruleFnReturnStatement", () => { + this.CONSUME(GLKeywords.Return); + this.OR([ + { ALT: () => this.SUBRULE(this._ruleFnExpression) }, + { ALT: () => this.SUBRULE(this._ruleBoolean) }, + { ALT: () => this.CONSUME(Values.ValueString) } + ]); + + this.CONSUME(Symbols.Semicolon); + }); + + private _ruleFnExpression = this.RULE("_ruleFnExpression", () => { + this.SUBRULE(this._ruleFnAddExpr); + }); + + private _ruleBoolean = this.RULE("_ruleBoolean", () => { + this.OR([{ ALT: () => this.CONSUME(ValueTrue) }, { ALT: () => this.CONSUME(ValueFalse) }]); + }); + + private _ruleFnVariableDeclaration = this.RULE("_ruleFnVariableDeclaration", () => { + this.SUBRULE(this._ruleVariableType); + this.CONSUME(Others.Identifier); + this.OPTION1(() => { + this.CONSUME(Symbols.Equal); + this.SUBRULE(this._ruleFnExpression); + }); + this.CONSUME(Symbols.Semicolon); + }); + + private _ruleFnConditionStatement = this.RULE("_ruleFnConditionStatement", () => { + this.CONSUME(GLKeywords.If); + this.CONSUME1(Symbols.LBracket); + this.SUBRULE(this._ruleFnRelationExpr); + this.CONSUME(Symbols.RBracket); + this.SUBRULE(this._ruleFnBlockStatement); + this.MANY(() => { + this.CONSUME(GLKeywords.Else); + this.SUBRULE(this._ruleFnConditionStatement); + }); + this.OPTION(() => { + this.CONSUME1(GLKeywords.Else); + this.SUBRULE1(this._ruleFnBlockStatement); + }); + }); + + private _ruleFnRelationExpr = this.RULE("_ruleFnRelationExpr", () => { + this.SUBRULE(this._ruleFnAddExpr); + this.SUBRULE(this._ruleRelationOperator); + this.SUBRULE1(this._ruleFnAddExpr); + }); + + private _ruleRelationOperator = this.RULE("_ruleRelationOperator", () => { + this.OR([{ ALT: () => this.CONSUME(Symbols.GreaterThan) }, { ALT: () => this.CONSUME(Symbols.LessThan) }]); + }); + + private _ruleFnBlockStatement = this.RULE("_ruleFnBlockStatement", () => { + this.CONSUME(Symbols.LCurly); + this.SUBRULE(this._ruleFnBody); + this.CONSUME(Symbols.RCurly); + }); + + private _ruleFnAssignStatement = this.RULE("_ruleFnAssignStatement", () => { + this.SUBRULE(this._ruleFnAssignLO); + this.SUBRULE(this._ruleFnAssignmentOperator); + this.SUBRULE(this._ruleFnExpression); + this.CONSUME(Symbols.Semicolon); + }); + + private _ruleFnAssignLO = this.RULE("_ruleFnAssignLO", () => { + this.OR([ + { ALT: () => this.CONSUME(GLKeywords.GLFragColor) }, + { ALT: () => this.CONSUME(GLKeywords.GLPosition) }, + { ALT: () => this.SUBRULE(this._ruleFnVariable) } + ]); + }); + + private _ruleFnAssignmentOperator = this.RULE("_ruleFnAssignmentOperator", () => { + this.OR([ + { ALT: () => this.CONSUME(Symbols.Equal) }, + { ALT: () => this.CONSUME(Symbols.MultiEqual) }, + { ALT: () => this.CONSUME(Symbols.DivideEqual) }, + { ALT: () => this.CONSUME(Symbols.AddEqual) }, + { ALT: () => this.CONSUME(Symbols.MinusEqual) } + ]); + }); + + private _ruleSubShaderPassPropertyAssignment = this.RULE("_ruleSubShaderPassPropertyAssignment", () => { + this.SUBRULE(this._ruleShaderPassPropertyType); + this.CONSUME(Symbols.Equal); + this.CONSUME(Others.Identifier); + this.CONSUME(Symbols.Semicolon); + }); + + private _ruleShaderPassPropertyType = this.RULE("_ruleShaderPassPropertyType", () => { + this.OR([ + { ALT: () => this.SUBRULE(this._ruleRenderStateType) }, + { ALT: () => this.CONSUME(Keywords.VertexShader) }, + { ALT: () => this.CONSUME(Keywords.FragmentShader) } + ]); + }); + + private _ruleRenderStateType = this.RULE("_ruleRenderStateType", () => { + this.OR([ + { ALT: () => this.CONSUME(Keywords.BlendState) }, + { ALT: () => this.CONSUME(Keywords.DepthState) }, + { ALT: () => this.CONSUME(Keywords.RasterState) }, + { ALT: () => this.CONSUME(Keywords.StencilState) } + ]); + }); + + private _ruleRenderStateDeclaration = this.RULE("_ruleRenderStateDeclaration", () => { + this.SUBRULE(this._ruleRenderStateType); + this.CONSUME(Others.Identifier); + this.CONSUME(Symbols.LCurly); + this.MANY(() => { + this.SUBRULE(this._ruleStatePropertyAssign); + this.CONSUME(Symbols.Semicolon); + }); + this.CONSUME(Symbols.RCurly); + }); + + private _ruleStatePropertyAssign = this.RULE("_ruleStatePropertyAssign", () => { + this.SUBRULE(this._ruleStateProperty); + this.CONSUME(Symbols.Equal); + this.SUBRULE(this._ruleAssignableValue); + }); + + private _ruleStateProperty = this.RULE("_ruleStateProperty", () => { + this.OR([ + { ALT: () => this.CONSUME(Keywords.Enabled) }, + { ALT: () => this.CONSUME(Keywords.DestColorBlendFactor) }, + { ALT: () => this.CONSUME(Keywords.SrcColorBlendFactor) } + ]); + }); + + private _ruleProperty = this.RULE("_ruleProperty", () => { + this.CONSUME(Keywords.EditorProperties); + this.CONSUME(Symbols.LCurly); + this.MANY(() => { + this.SUBRULE(this._rulePropertyItem); + }); + this.CONSUME(Symbols.RCurly); + }); + + private _rulePropertyItem = this.RULE("_rulePropertyItem", () => { + this.CONSUME(Others.Identifier); + this.CONSUME9(Symbols.LBracket); + this.CONSUME(Values.ValueString); + this.CONSUME(Symbols.Comma); + this.SUBRULE(this._rulePropertyItemType); + this.CONSUME(Symbols.RBracket); + this.CONSUME(Symbols.Equal); + this.SUBRULE(this._rulePropertyItemValue); + this.CONSUME(Symbols.Semicolon); + }); + + private _rulePropertyItemType = this.RULE("_rulePropertyItemType", () => { + this.OR([ + ...EditorTypes.tokenList + .filter((item) => item.name !== "Range") + .map((item) => ({ + ALT: () => this.CONSUME(item) + })), + { ALT: () => this.SUBRULE(this._ruleVariableType) }, + { ALT: () => this.SUBRULE(this._ruleRange) } + ]); + }); + + private _ruleRange = this.RULE("_ruleRange", () => { + this.CONSUME(EditorTypes.TypeRange); + this.CONSUME2(Symbols.LBracket); + this.CONSUME(Values.ValueInt); + this.CONSUME(Symbols.Comma); + this.CONSUME1(Values.ValueInt); + this.CONSUME(Symbols.RBracket); + }); + + private _rulePropertyItemValue = this.RULE("_rulePropertyItemValue", () => { + this.OR([ + { ALT: () => this.SUBRULE(this._ruleTupleFloat4) }, + { ALT: () => this.SUBRULE(this._ruleTupleFloat3) }, + { ALT: () => this.SUBRULE(this._ruleTupleFloat2) }, + { ALT: () => this.SUBRULE(this._ruleTupleInt4) }, + { ALT: () => this.SUBRULE(this._ruleTupleInt3) }, + { ALT: () => this.SUBRULE(this._ruleTupleInt2) }, + { ALT: () => this.CONSUME(Values.ValueTrue) }, + { ALT: () => this.CONSUME(Values.ValueFalse) }, + { ALT: () => this.CONSUME1(Values.ValueInt) }, + { ALT: () => this.CONSUME(Values.ValueString) }, + { ALT: () => this.CONSUME(Values.ValueFloat) } + ]); + }); + + private _consume(idx: number, tokType: TokenType) { + if (idx === 0) return this.CONSUME1(tokType); + else if (idx === 1) return this.CONSUME2(tokType); + else if (idx === 2) return this.CONSUME3(tokType); + else if (idx === 3) return this.CONSUME4(tokType); + else if (idx === 4) return this.CONSUME5(tokType); + else if (idx === 5) return this.CONSUME6(tokType); + return this.CONSUME7(tokType); + } + + private _ruleTuple(type: "int" | "float", num: number) { + const valueToken = type === "int" ? Values.ValueInt : Values.ValueFloat; + this.CONSUME2(Symbols.LBracket); + for (let i = 0; i < num - 1; i++) { + this._consume(i, valueToken); + this._consume(i, Symbols.Comma); + } + this.CONSUME(valueToken); + this.CONSUME(Symbols.RBracket); + } + + private _ruleTupleFloat4 = this.RULE("_ruleTupleFloat4", () => this._ruleTuple("float", 4)); + private _ruleTupleFloat3 = this.RULE("_ruleTupleFloat3", () => this._ruleTuple("float", 3)); + private _ruleTupleFloat2 = this.RULE("_ruleTupleFloat2", () => this._ruleTuple("float", 2)); + + private _ruleTupleInt4 = this.RULE("_ruleTupleInt4", () => this._ruleTuple("int", 4)); + private _ruleTupleInt3 = this.RULE("_ruleTupleInt3", () => this._ruleTuple("int", 3)); + private _ruleTupleInt2 = this.RULE("_ruleTupleInt2", () => this._ruleTuple("int", 2)); +} diff --git a/packages/shader-lab/src/parser/tokens/EditorTypes.ts b/packages/shader-lab/src/parser/tokens/EditorTypes.ts new file mode 100644 index 0000000000..837a646a86 --- /dev/null +++ b/packages/shader-lab/src/parser/tokens/EditorTypes.ts @@ -0,0 +1,17 @@ +import { createToken } from "chevrotain"; + +export const TypeInteger = createToken({ + name: "TypeInteger", + pattern: /Integer/ +}); +export const TypeString = createToken({ + name: "TypeString", + pattern: /String/ +}); +export const TypeFloat = createToken({ name: "TypeFloat", pattern: /Float/ }); +export const TypeRange = createToken({ + name: "Range", + pattern: /Range/ +}); + +export const tokenList = [TypeInteger, TypeString, TypeFloat, TypeRange]; diff --git a/packages/shader-lab/src/parser/tokens/GlslKeywords.ts b/packages/shader-lab/src/parser/tokens/GlslKeywords.ts new file mode 100644 index 0000000000..f4d298258a --- /dev/null +++ b/packages/shader-lab/src/parser/tokens/GlslKeywords.ts @@ -0,0 +1,31 @@ +import { createKeywordToken } from "./utils"; + +// built-in variable +export const GLPosition = createKeywordToken("gl_Position"); +export const GLFragColor = createKeywordToken("gl_FragColor"); + +// function +export const Pow = createKeywordToken("pow"); +export const Texture2D = createKeywordToken("texture2D"); + +// macro +export const M_DEFINE = createKeywordToken("#define", { name: "m_define" }); +export const M_IFDEF = createKeywordToken("#ifdef", { name: "m_ifdef" }); +export const M_IFNDEF = createKeywordToken("#ifndef", { name: "m_ifndef" }); +export const M_ELSE = createKeywordToken("#else", { name: "m_else" }); +export const M_ELIF = createKeywordToken("#elif", { name: "m_elif" }); +export const M_ENDIF = createKeywordToken("#endif", { name: "m_endif" }); +export const M_INCLUDE = createKeywordToken("#include", { name: "m_include" }); + +// other +export const Struct = createKeywordToken("struct"); +export const If = createKeywordToken("if"); +export const Else = createKeywordToken("else"); +export const Discard = createKeywordToken("discard"); +export const Void = createKeywordToken("void"); +export const Return = createKeywordToken("return"); + +export const variableTokenList = [GLPosition, GLFragColor]; +export const funcTokenList = [Texture2D, Pow]; +export const macroTokenList = [M_DEFINE, M_IFDEF, M_IFNDEF, M_ELSE, M_ELIF, M_ENDIF, M_INCLUDE]; +export const otherTokenList = [Struct, If, Else, Discard, Void, Return]; diff --git a/packages/shader-lab/src/parser/tokens/GlslTypes.ts b/packages/shader-lab/src/parser/tokens/GlslTypes.ts new file mode 100644 index 0000000000..fe0c528d33 --- /dev/null +++ b/packages/shader-lab/src/parser/tokens/GlslTypes.ts @@ -0,0 +1,36 @@ +import { createToken } from "chevrotain"; + +export const glsl_mat2 = createToken({ name: "glsl_mat2", pattern: /mat2/ }); +export const glsl_mat3 = createToken({ name: "glsl_mat3", pattern: /mat3/ }); +export const glsl_mat4 = createToken({ name: "glsl_mat4", pattern: /mat4/ }); + +export const glsl_vec2 = createToken({ name: "glsl_vec2", pattern: /vec2/ }); +export const glsl_vec3 = createToken({ name: "glsl_vec3", pattern: /vec3/ }); +export const glsl_vec4 = createToken({ name: "glsl_vec4", pattern: /vec4/ }); + +export const glsl_ivec2 = createToken({ name: "glsl_ivec2", pattern: /ivec2/ }); +export const glsl_ivec3 = createToken({ name: "glsl_ivec3", pattern: /ivec3/ }); +export const glsl_ivec4 = createToken({ name: "glsl_ivec4", pattern: /ivec4/ }); + +export const glsl_float = createToken({ name: "glsl_float", pattern: /float/ }); +export const glsl_int = createToken({ name: "glsl_int", pattern: /int/ }); + +export const glsl_sampler2D = createToken({ + name: "glsl_sampler2D", + pattern: /sampler2D/ +}); + +export const tokenList = [ + glsl_ivec2, + glsl_ivec3, + glsl_ivec4, + glsl_mat2, + glsl_mat3, + glsl_mat4, + glsl_vec2, + glsl_vec3, + glsl_vec4, + glsl_float, + glsl_int, + glsl_sampler2D +]; diff --git a/packages/shader-lab/src/parser/tokens/index.ts b/packages/shader-lab/src/parser/tokens/index.ts new file mode 100644 index 0000000000..22f4d30d56 --- /dev/null +++ b/packages/shader-lab/src/parser/tokens/index.ts @@ -0,0 +1,7 @@ +export * as Keywords from "./keyword"; +export * as Symbols from "./symbol"; +export * as EditorTypes from "./EditorTypes"; +export * as Types from "./GlslTypes"; +export * as GLKeywords from "./GlslKeywords"; +export * as Others from "./other"; +export * as Values from "./value"; diff --git a/packages/shader-lab/src/parser/tokens/keyword.ts b/packages/shader-lab/src/parser/tokens/keyword.ts new file mode 100644 index 0000000000..4addeb0d25 --- /dev/null +++ b/packages/shader-lab/src/parser/tokens/keyword.ts @@ -0,0 +1,43 @@ +import { ValueString } from "./value"; +import { createKeywordToken } from "./utils"; + +export const Shader = createKeywordToken("Shader", { longer_alt: ValueString }); +export const EditorProperties = createKeywordToken("EditorProperties"); +export const SubShader = createKeywordToken("SubShader"); +export const Pass = createKeywordToken("Pass"); +export const Tags = createKeywordToken("Tags"); + +export const BlendState = createKeywordToken("BlendState"); +export const DepthState = createKeywordToken("DepthState"); +export const StencilState = createKeywordToken("StencilState"); +export const RasterState = createKeywordToken("RasterState"); + +export const Enabled = createKeywordToken("Enabled"); +export const SrcColorBlendFactor = createKeywordToken("SrcColorBlendFactor"); +export const DestColorBlendFactor = createKeywordToken("DestColorBlendFactor"); + +// tags +export const ReplacementTag = createKeywordToken("ReplacementTag"); +export const PipelineStage = createKeywordToken("PipelineStage"); + +export const VertexShader = createKeywordToken("VertexShader"); +export const FragmentShader = createKeywordToken("FragmentShader"); + +export const tagTokenList = [ReplacementTag, PipelineStage]; + +export const tokenList = [ + Shader, + EditorProperties, + SubShader, + Pass, + Tags, + BlendState, + DepthState, + StencilState, + RasterState, + Enabled, + DestColorBlendFactor, + SrcColorBlendFactor, + VertexShader, + FragmentShader +]; diff --git a/packages/shader-lab/src/parser/tokens/other.ts b/packages/shader-lab/src/parser/tokens/other.ts new file mode 100644 index 0000000000..67dd802f4e --- /dev/null +++ b/packages/shader-lab/src/parser/tokens/other.ts @@ -0,0 +1,21 @@ +import { Lexer, createToken } from "chevrotain"; + +export const Identifier = createToken({ name: "Identifier", pattern: /[a-zA-z]\w*/ }); + +export const WhiteSpace = createToken({ + name: "WhiteSpace", + pattern: /(\s|\n)+/, + group: Lexer.SKIPPED +}); + +export const CommentLine = createToken({ + name: "CommentLine", + pattern: /\/\/.*\n/, + group: Lexer.SKIPPED +}); + +export const CommentMultiLine = createToken({ + name: "CommentMultiLine", + pattern: /\/\*.*?\*\//, + group: Lexer.SKIPPED +}); diff --git a/packages/shader-lab/src/parser/tokens/symbol.ts b/packages/shader-lab/src/parser/tokens/symbol.ts new file mode 100644 index 0000000000..aa68c70a5d --- /dev/null +++ b/packages/shader-lab/src/parser/tokens/symbol.ts @@ -0,0 +1,78 @@ +import { createToken } from "chevrotain"; + +/** { */ +export const LCurly = createToken({ name: "LCurly", pattern: /\{/, label: "{" }); +/** } */ +export const RCurly = createToken({ name: "RCurly", pattern: /\}/, label: "}" }); +/** ( */ +export const LBracket = createToken({ name: "LBracket", pattern: /\(/, label: "(" }); +/** ) */ +export const RBracket = createToken({ name: "RBracket", pattern: /\)/, label: ")" }); +/** , */ +export const Comma = createToken({ name: "Comma", pattern: /,/, label: "," }); +/** : */ +export const Colon = createToken({ name: "Colon", pattern: /:/, label: ":" }); +/** = */ +export const Equal = createToken({ name: "Equal", pattern: /=/, label: "=" }); +/** ; */ +export const Semicolon = createToken({ name: "Semicolon", pattern: /;/, label: ";" }); +/** . */ +export const Dot = createToken({ name: "Dot", pattern: /\./, label: "." }); +export const Add = createToken({ name: "Add", pattern: /\+/, label: "+" }); +export const Minus = createToken({ name: "Minus", pattern: /\-/, label: "-" }); +export const MultiEqual = createToken({ + name: "MultiEqual", + pattern: /\*=/, + label: "*=" +}); +export const DivideEqual = createToken({ + name: "DivideEqual", + pattern: /\/=/, + label: "/=" +}); +export const AddEqual = createToken({ + name: "AddEqual", + pattern: /\+=/, + label: "+=" +}); +export const MinusEqual = createToken({ + name: "MinusEqual", + pattern: /\-=/, + label: "-=" +}); +export const Multiply = createToken({ name: "Multiply", pattern: /\*/, label: "*" }); +export const Divide = createToken({ name: "Divide", pattern: /\//, label: "/" }); +export const GreaterThan = createToken({ + name: "GreaterThan", + pattern: /\>/, + label: ">" +}); +export const LessThan = createToken({ + name: "LessThan", + pattern: /\) { + return createToken({ label: k, name: k, pattern: new RegExp(k), ...opts }); +} diff --git a/packages/shader-lab/src/parser/tokens/value.ts b/packages/shader-lab/src/parser/tokens/value.ts new file mode 100644 index 0000000000..c5ff60b248 --- /dev/null +++ b/packages/shader-lab/src/parser/tokens/value.ts @@ -0,0 +1,12 @@ +import { createToken } from "chevrotain"; + +export const ValueInt = createToken({ name: "ValueInt", pattern: /-?\d+/ }); +export const ValueFloat = createToken({ name: "ValueFloat", pattern: /-?\d+\.\d+/ }); +export const ValueString = createToken({ + name: "ValueString", + pattern: /"[\w\s\.]*"/ +}); +export const ValueTrue = createToken({ name: "ValueTrue", pattern: /true/ }); +export const ValueFalse = createToken({ name: "ValueFalse", pattern: /false/ }); + +export const tokenList = [ValueFloat, ValueInt, ValueString, ValueTrue, ValueFalse]; diff --git a/packages/shader-lab/src/types.ts b/packages/shader-lab/src/types.ts new file mode 100644 index 0000000000..aa43e1c866 --- /dev/null +++ b/packages/shader-lab/src/types.ts @@ -0,0 +1,805 @@ +import type { CstNode, ICstVisitor, IToken } from "chevrotain"; + +export interface _ruleShaderCstNode extends CstNode { + name: "_ruleShader"; + children: _ruleShaderCstChildren; +} + +export type _ruleShaderCstChildren = { + Shader: IToken[]; + ValueString: IToken[]; + LCurly: IToken[]; + _ruleProperty?: _rulePropertyCstNode[]; + _ruleSubShader?: _ruleSubShaderCstNode[]; + RCurly: IToken[]; +}; + +export interface _ruleSubShaderCstNode extends CstNode { + name: "_ruleSubShader"; + children: _ruleSubShaderCstChildren; +} + +export type _ruleSubShaderCstChildren = { + SubShader: IToken[]; + LCurly: IToken[]; + _ruleShaderPass?: _ruleShaderPassCstNode[]; + _ruleTag?: _ruleTagCstNode[]; + _ruleRenderStateDeclaration?: _ruleRenderStateDeclarationCstNode[]; + RCurly: IToken[]; +}; + +export interface _ruleShaderPassCstNode extends CstNode { + name: "_ruleShaderPass"; + children: _ruleShaderPassCstChildren; +} + +export type _ruleShaderPassCstChildren = { + Pass: IToken[]; + ValueString: IToken[]; + LCurly: IToken[]; + _ruleTag?: _ruleTagCstNode[]; + _ruleStruct?: _ruleStructCstNode[]; + _ruleFn?: _ruleFnCstNode[]; + _ruleFnVariableDeclaration?: _ruleFnVariableDeclarationCstNode[]; + _ruleSubShaderPassPropertyAssignment?: _ruleSubShaderPassPropertyAssignmentCstNode[]; + _ruleRenderStateDeclaration?: _ruleRenderStateDeclarationCstNode[]; + _ruleFnMacroInclude?: _ruleFnMacroIncludeCstNode[]; + _ruleFnMacroDefine?: _ruleFnMacroDefineCstNode[]; + RCurly: IToken[]; +}; + +export interface _ruleStructCstNode extends CstNode { + name: "_ruleStruct"; + children: _ruleStructCstChildren; +} + +export type _ruleStructCstChildren = { + struct: IToken[]; + Identifier: IToken[]; + LCurly: IToken[]; + _ruleDeclaration?: _ruleDeclarationCstNode[]; + Semicolon?: IToken[]; + RCurly: IToken[]; +}; + +export interface _ruleDeclarationCstNode extends CstNode { + name: "_ruleDeclaration"; + children: _ruleDeclarationCstChildren; +} + +export type _ruleDeclarationCstChildren = { + _ruleVariableType: _ruleVariableTypeCstNode[]; + Identifier: IToken[]; +}; + +export interface _ruleVariableTypeCstNode extends CstNode { + name: "_ruleVariableType"; + children: _ruleVariableTypeCstChildren; +} + +export type _ruleVariableTypeCstChildren = { + glsl_ivec2?: IToken[]; + glsl_ivec3?: IToken[]; + glsl_ivec4?: IToken[]; + glsl_mat2?: IToken[]; + glsl_mat3?: IToken[]; + glsl_mat4?: IToken[]; + glsl_vec2?: IToken[]; + glsl_vec3?: IToken[]; + glsl_vec4?: IToken[]; + glsl_float?: IToken[]; + glsl_int?: IToken[]; + glsl_sampler2D?: IToken[]; + Identifier?: IToken[]; +}; + +export interface _ruleTagCstNode extends CstNode { + name: "_ruleTag"; + children: _ruleTagCstChildren; +} + +export type _ruleTagCstChildren = { + Tags: IToken[]; + LCurly: IToken[]; + _ruleTagAssignment?: _ruleTagAssignmentCstNode[]; + Comma?: IToken[]; + RCurly: IToken[]; +}; + +export interface _ruleTagAssignmentCstNode extends CstNode { + name: "_ruleTagAssignment"; + children: _ruleTagAssignmentCstChildren; +} + +export type _ruleTagAssignmentCstChildren = { + _ruleTagType: _ruleTagTypeCstNode[]; + Equal: IToken[]; + ValueString: IToken[]; +}; + +export interface _ruleTagTypeCstNode extends CstNode { + name: "_ruleTagType"; + children: _ruleTagTypeCstChildren; +} + +export type _ruleTagTypeCstChildren = { + ReplacementTag?: IToken[]; + PipelineStage?: IToken[]; +}; + +export interface _ruleFnCstNode extends CstNode { + name: "_ruleFn"; + children: _ruleFnCstChildren; +} + +export type _ruleFnCstChildren = { + _ruleFnReturnType: _ruleFnReturnTypeCstNode[]; + Identifier: IToken[]; + LBracket: IToken[]; + _ruleFnArg?: _ruleFnArgCstNode[]; + Comma?: IToken[]; + RBracket: IToken[]; + LCurly: IToken[]; + _ruleFnBody: _ruleFnBodyCstNode[]; + RCurly: IToken[]; +}; + +export interface _ruleFnReturnTypeCstNode extends CstNode { + name: "_ruleFnReturnType"; + children: _ruleFnReturnTypeCstChildren; +} + +export type _ruleFnReturnTypeCstChildren = { + _ruleVariableType?: _ruleVariableTypeCstNode[]; + void?: IToken[]; +}; + +export interface _ruleFnArgCstNode extends CstNode { + name: "_ruleFnArg"; + children: _ruleFnArgCstChildren; +} + +export type _ruleFnArgCstChildren = { + _ruleVariableType: _ruleVariableTypeCstNode[]; + Identifier: IToken[]; +}; + +export interface _ruleFnBodyCstNode extends CstNode { + name: "_ruleFnBody"; + children: _ruleFnBodyCstChildren; +} + +export type _ruleFnBodyCstChildren = { + _ruleFnMacro?: _ruleFnMacroCstNode[]; + _ruleFnStatement?: _ruleFnStatementCstNode[]; +}; + +export interface _ruleFnMacroCstNode extends CstNode { + name: "_ruleFnMacro"; + children: _ruleFnMacroCstChildren; +} + +export type _ruleFnMacroCstChildren = { + _ruleFnMacroDefine?: _ruleFnMacroDefineCstNode[]; + _ruleFnMacroInclude?: _ruleFnMacroIncludeCstNode[]; + _ruleFnMacroCondition?: _ruleFnMacroConditionCstNode[]; +}; + +export interface _ruleFnMacroConditionCstNode extends CstNode { + name: "_ruleFnMacroCondition"; + children: _ruleFnMacroConditionCstChildren; +} + +export type _ruleFnMacroConditionCstChildren = { + _ruleFnMacroConditionDeclare: _ruleFnMacroConditionDeclareCstNode[]; + Identifier: IToken[]; + _ruleFnBody: _ruleFnBodyCstNode[]; + _ruleFnMacroConditionBranch?: _ruleFnMacroConditionBranchCstNode[]; + m_endif: IToken[]; +}; + +export interface _ruleFnMacroConditionDeclareCstNode extends CstNode { + name: "_ruleFnMacroConditionDeclare"; + children: _ruleFnMacroConditionDeclareCstChildren; +} + +export type _ruleFnMacroConditionDeclareCstChildren = { + m_ifdef?: IToken[]; + m_ifndef?: IToken[]; +}; + +export interface _ruleFnMacroConditionBranchCstNode extends CstNode { + name: "_ruleFnMacroConditionBranch"; + children: _ruleFnMacroConditionBranchCstChildren; +} + +export type _ruleFnMacroConditionBranchCstChildren = { + _ruleFnMacroConditionBranchDeclare: _ruleFnMacroConditionBranchDeclareCstNode[]; + _ruleFnBody: _ruleFnBodyCstNode[]; +}; + +export interface _ruleFnMacroConditionBranchDeclareCstNode extends CstNode { + name: "_ruleFnMacroConditionBranchDeclare"; + children: _ruleFnMacroConditionBranchDeclareCstChildren; +} + +export type _ruleFnMacroConditionBranchDeclareCstChildren = { + m_else?: IToken[]; +}; + +export interface _ruleFnMacroDefineCstNode extends CstNode { + name: "_ruleFnMacroDefine"; + children: _ruleFnMacroDefineCstChildren; +} + +export type _ruleFnMacroDefineCstChildren = { + m_define: IToken[]; + Identifier: IToken[]; + _ruleAssignableValue?: _ruleAssignableValueCstNode[]; +}; + +export interface _ruleAssignableValueCstNode extends CstNode { + name: "_ruleAssignableValue"; + children: _ruleAssignableValueCstChildren; +} + +export type _ruleAssignableValueCstChildren = { + ValueTrue?: IToken[]; + ValueFalse?: IToken[]; + ValueString?: IToken[]; + _ruleFnAddExpr?: _ruleFnAddExprCstNode[]; + gl_FragColor?: IToken[]; + gl_Position?: IToken[]; +}; + +export interface _ruleFnAddExprCstNode extends CstNode { + name: "_ruleFnAddExpr"; + children: _ruleFnAddExprCstChildren; +} + +export type _ruleFnAddExprCstChildren = { + _ruleFnMultiplicationExpr: _ruleFnMultiplicationExprCstNode[]; + _ruleAddOperator?: _ruleAddOperatorCstNode[]; +}; + +export interface _ruleFnMultiplicationExprCstNode extends CstNode { + name: "_ruleFnMultiplicationExpr"; + children: _ruleFnMultiplicationExprCstChildren; +} + +export type _ruleFnMultiplicationExprCstChildren = { + _ruleFnAtomicExpr: _ruleFnAtomicExprCstNode[]; + _ruleMultiplicationOperator?: _ruleMultiplicationOperatorCstNode[]; +}; + +export interface _ruleFnAtomicExprCstNode extends CstNode { + name: "_ruleFnAtomicExpr"; + children: _ruleFnAtomicExprCstChildren; +} + +export type _ruleFnAtomicExprCstChildren = { + _ruleAddOperator?: _ruleAddOperatorCstNode[]; + _ruleFnParenthesisExpr?: _ruleFnParenthesisExprCstNode[]; + _ruleNumber?: _ruleNumberCstNode[]; + _ruleFnCall?: _ruleFnCallCstNode[]; + _ruleFnVariable?: _ruleFnVariableCstNode[]; +}; + +export interface _ruleAddOperatorCstNode extends CstNode { + name: "_ruleAddOperator"; + children: _ruleAddOperatorCstChildren; +} + +export type _ruleAddOperatorCstChildren = { + Add?: IToken[]; + Minus?: IToken[]; +}; + +export interface _ruleFnParenthesisExprCstNode extends CstNode { + name: "_ruleFnParenthesisExpr"; + children: _ruleFnParenthesisExprCstChildren; +} + +export type _ruleFnParenthesisExprCstChildren = { + LBracket: IToken[]; + _ruleFnAddExpr: _ruleFnAddExprCstNode[]; + RBracket: IToken[]; +}; + +export interface _ruleNumberCstNode extends CstNode { + name: "_ruleNumber"; + children: _ruleNumberCstChildren; +} + +export type _ruleNumberCstChildren = { + ValueInt?: IToken[]; + ValueFloat?: IToken[]; +}; + +export interface _ruleFnCallCstNode extends CstNode { + name: "_ruleFnCall"; + children: _ruleFnCallCstChildren; +} + +export type _ruleFnCallCstChildren = { + _ruleFnCallVariable: _ruleFnCallVariableCstNode[]; + LBracket: IToken[]; + _ruleAssignableValue?: _ruleAssignableValueCstNode[]; + Comma?: IToken[]; + RBracket: IToken[]; +}; + +export interface _ruleFnCallVariableCstNode extends CstNode { + name: "_ruleFnCallVariable"; + children: _ruleFnCallVariableCstChildren; +} + +export type _ruleFnCallVariableCstChildren = { + glsl_ivec2?: IToken[]; + glsl_ivec3?: IToken[]; + glsl_ivec4?: IToken[]; + glsl_mat2?: IToken[]; + glsl_mat3?: IToken[]; + glsl_mat4?: IToken[]; + glsl_vec2?: IToken[]; + glsl_vec3?: IToken[]; + glsl_vec4?: IToken[]; + glsl_float?: IToken[]; + glsl_int?: IToken[]; + glsl_sampler2D?: IToken[]; + pow?: IToken[]; + texture2D?: IToken[]; + Identifier?: IToken[]; +}; + +export interface _ruleFnVariableCstNode extends CstNode { + name: "_ruleFnVariable"; + children: _ruleFnVariableCstChildren; +} + +export type _ruleFnVariableCstChildren = { + Identifier: IToken[]; + Dot?: IToken[]; +}; + +export interface _ruleMultiplicationOperatorCstNode extends CstNode { + name: "_ruleMultiplicationOperator"; + children: _ruleMultiplicationOperatorCstChildren; +} + +export type _ruleMultiplicationOperatorCstChildren = { + Multiply?: IToken[]; + Divide?: IToken[]; +}; + +export interface _ruleFnMacroIncludeCstNode extends CstNode { + name: "_ruleFnMacroInclude"; + children: _ruleFnMacroIncludeCstChildren; +} + +export type _ruleFnMacroIncludeCstChildren = { + m_include: IToken[]; + ValueString: IToken[]; +}; + +export interface _ruleFnStatementCstNode extends CstNode { + name: "_ruleFnStatement"; + children: _ruleFnStatementCstChildren; +} + +export type _ruleFnStatementCstChildren = { + _ruleFnCall?: _ruleFnCallCstNode[]; + _ruleFnReturnStatement?: _ruleFnReturnStatementCstNode[]; + _ruleFnVariableDeclaration?: _ruleFnVariableDeclarationCstNode[]; + _ruleFnConditionStatement?: _ruleFnConditionStatementCstNode[]; + _ruleFnAssignStatement?: _ruleFnAssignStatementCstNode[]; + discard?: IToken[]; + Semicolon?: IToken[]; +}; + +export interface _ruleFnReturnStatementCstNode extends CstNode { + name: "_ruleFnReturnStatement"; + children: _ruleFnReturnStatementCstChildren; +} + +export type _ruleFnReturnStatementCstChildren = { + return: IToken[]; + _ruleFnExpression?: _ruleFnExpressionCstNode[]; + _ruleBoolean?: _ruleBooleanCstNode[]; + ValueString?: IToken[]; + Semicolon: IToken[]; +}; + +export interface _ruleFnExpressionCstNode extends CstNode { + name: "_ruleFnExpression"; + children: _ruleFnExpressionCstChildren; +} + +export type _ruleFnExpressionCstChildren = { + _ruleFnAddExpr: _ruleFnAddExprCstNode[]; +}; + +export interface _ruleBooleanCstNode extends CstNode { + name: "_ruleBoolean"; + children: _ruleBooleanCstChildren; +} + +export type _ruleBooleanCstChildren = { + ValueTrue?: IToken[]; + ValueFalse?: IToken[]; +}; + +export interface _ruleFnVariableDeclarationCstNode extends CstNode { + name: "_ruleFnVariableDeclaration"; + children: _ruleFnVariableDeclarationCstChildren; +} + +export type _ruleFnVariableDeclarationCstChildren = { + _ruleVariableType: _ruleVariableTypeCstNode[]; + Identifier: IToken[]; + Equal?: IToken[]; + _ruleFnExpression?: _ruleFnExpressionCstNode[]; + Semicolon: IToken[]; +}; + +export interface _ruleFnConditionStatementCstNode extends CstNode { + name: "_ruleFnConditionStatement"; + children: _ruleFnConditionStatementCstChildren; +} + +export type _ruleFnConditionStatementCstChildren = { + if: IToken[]; + LBracket: IToken[]; + _ruleFnRelationExpr: _ruleFnRelationExprCstNode[]; + RBracket: IToken[]; + _ruleFnBlockStatement: _ruleFnBlockStatementCstNode[]; + else?: IToken[]; + _ruleFnConditionStatement?: _ruleFnConditionStatementCstNode[]; +}; + +export interface _ruleFnRelationExprCstNode extends CstNode { + name: "_ruleFnRelationExpr"; + children: _ruleFnRelationExprCstChildren; +} + +export type _ruleFnRelationExprCstChildren = { + _ruleFnAddExpr: _ruleFnAddExprCstNode[]; + _ruleRelationOperator: _ruleRelationOperatorCstNode[]; +}; + +export interface _ruleRelationOperatorCstNode extends CstNode { + name: "_ruleRelationOperator"; + children: _ruleRelationOperatorCstChildren; +} + +export type _ruleRelationOperatorCstChildren = { + GreaterThan?: IToken[]; + LessThan?: IToken[]; +}; + +export interface _ruleFnBlockStatementCstNode extends CstNode { + name: "_ruleFnBlockStatement"; + children: _ruleFnBlockStatementCstChildren; +} + +export type _ruleFnBlockStatementCstChildren = { + LCurly: IToken[]; + _ruleFnBody: _ruleFnBodyCstNode[]; + RCurly: IToken[]; +}; + +export interface _ruleFnAssignStatementCstNode extends CstNode { + name: "_ruleFnAssignStatement"; + children: _ruleFnAssignStatementCstChildren; +} + +export type _ruleFnAssignStatementCstChildren = { + _ruleFnAssignLO: _ruleFnAssignLOCstNode[]; + _ruleFnAssignmentOperator: _ruleFnAssignmentOperatorCstNode[]; + _ruleFnExpression: _ruleFnExpressionCstNode[]; + Semicolon: IToken[]; +}; + +export interface _ruleFnAssignLOCstNode extends CstNode { + name: "_ruleFnAssignLO"; + children: _ruleFnAssignLOCstChildren; +} + +export type _ruleFnAssignLOCstChildren = { + gl_FragColor?: IToken[]; + gl_Position?: IToken[]; + _ruleFnVariable?: _ruleFnVariableCstNode[]; +}; + +export interface _ruleFnAssignmentOperatorCstNode extends CstNode { + name: "_ruleFnAssignmentOperator"; + children: _ruleFnAssignmentOperatorCstChildren; +} + +export type _ruleFnAssignmentOperatorCstChildren = { + Equal?: IToken[]; + MultiEqual?: IToken[]; + DivideEqual?: IToken[]; + AddEqual?: IToken[]; + MinusEqual?: IToken[]; +}; + +export interface _ruleSubShaderPassPropertyAssignmentCstNode extends CstNode { + name: "_ruleSubShaderPassPropertyAssignment"; + children: _ruleSubShaderPassPropertyAssignmentCstChildren; +} + +export type _ruleSubShaderPassPropertyAssignmentCstChildren = { + _ruleShaderPassPropertyType: _ruleShaderPassPropertyTypeCstNode[]; + Equal: IToken[]; + Identifier: IToken[]; + Semicolon: IToken[]; +}; + +export interface _ruleShaderPassPropertyTypeCstNode extends CstNode { + name: "_ruleShaderPassPropertyType"; + children: _ruleShaderPassPropertyTypeCstChildren; +} + +export type _ruleShaderPassPropertyTypeCstChildren = { + _ruleRenderStateType?: _ruleRenderStateTypeCstNode[]; + VertexShader?: IToken[]; + FragmentShader?: IToken[]; +}; + +export interface _ruleRenderStateTypeCstNode extends CstNode { + name: "_ruleRenderStateType"; + children: _ruleRenderStateTypeCstChildren; +} + +export type _ruleRenderStateTypeCstChildren = { + BlendState?: IToken[]; + DepthState?: IToken[]; + RasterState?: IToken[]; + StencilState?: IToken[]; +}; + +export interface _ruleRenderStateDeclarationCstNode extends CstNode { + name: "_ruleRenderStateDeclaration"; + children: _ruleRenderStateDeclarationCstChildren; +} + +export type _ruleRenderStateDeclarationCstChildren = { + _ruleRenderStateType: _ruleRenderStateTypeCstNode[]; + Identifier: IToken[]; + LCurly: IToken[]; + _ruleStatePropertyAssign?: _ruleStatePropertyAssignCstNode[]; + Semicolon?: IToken[]; + RCurly: IToken[]; +}; + +export interface _ruleStatePropertyAssignCstNode extends CstNode { + name: "_ruleStatePropertyAssign"; + children: _ruleStatePropertyAssignCstChildren; +} + +export type _ruleStatePropertyAssignCstChildren = { + _ruleStateProperty: _ruleStatePropertyCstNode[]; + Equal: IToken[]; + _ruleAssignableValue: _ruleAssignableValueCstNode[]; +}; + +export interface _ruleStatePropertyCstNode extends CstNode { + name: "_ruleStateProperty"; + children: _ruleStatePropertyCstChildren; +} + +export type _ruleStatePropertyCstChildren = { + Enabled?: IToken[]; + DestColorBlendFactor?: IToken[]; + SrcColorBlendFactor?: IToken[]; +}; + +export interface _rulePropertyCstNode extends CstNode { + name: "_ruleProperty"; + children: _rulePropertyCstChildren; +} + +export type _rulePropertyCstChildren = { + EditorProperties: IToken[]; + LCurly: IToken[]; + _rulePropertyItem?: _rulePropertyItemCstNode[]; + RCurly: IToken[]; +}; + +export interface _rulePropertyItemCstNode extends CstNode { + name: "_rulePropertyItem"; + children: _rulePropertyItemCstChildren; +} + +export type _rulePropertyItemCstChildren = { + Identifier: IToken[]; + LBracket: IToken[]; + ValueString: IToken[]; + Comma: IToken[]; + _rulePropertyItemType: _rulePropertyItemTypeCstNode[]; + RBracket: IToken[]; + Equal: IToken[]; + _rulePropertyItemValue: _rulePropertyItemValueCstNode[]; + Semicolon: IToken[]; +}; + +export interface _rulePropertyItemTypeCstNode extends CstNode { + name: "_rulePropertyItemType"; + children: _rulePropertyItemTypeCstChildren; +} + +export type _rulePropertyItemTypeCstChildren = { + TypeInteger?: IToken[]; + TypeString?: IToken[]; + TypeFloat?: IToken[]; + _ruleVariableType?: _ruleVariableTypeCstNode[]; + _ruleRange?: _ruleRangeCstNode[]; +}; + +export interface _ruleRangeCstNode extends CstNode { + name: "_ruleRange"; + children: _ruleRangeCstChildren; +} + +export type _ruleRangeCstChildren = { + Range: IToken[]; + LBracket: IToken[]; + ValueInt: IToken[]; + Comma: IToken[]; + RBracket: IToken[]; +}; + +export interface _rulePropertyItemValueCstNode extends CstNode { + name: "_rulePropertyItemValue"; + children: _rulePropertyItemValueCstChildren; +} + +export type _rulePropertyItemValueCstChildren = { + _ruleTupleFloat4?: _ruleTupleFloat4CstNode[]; + _ruleTupleFloat3?: _ruleTupleFloat3CstNode[]; + _ruleTupleFloat2?: _ruleTupleFloat2CstNode[]; + _ruleTupleInt4?: _ruleTupleInt4CstNode[]; + _ruleTupleInt3?: _ruleTupleInt3CstNode[]; + _ruleTupleInt2?: _ruleTupleInt2CstNode[]; + ValueTrue?: IToken[]; + ValueFalse?: IToken[]; + ValueInt?: IToken[]; + ValueString?: IToken[]; + ValueFloat?: IToken[]; +}; + +export interface _ruleTupleFloat4CstNode extends CstNode { + name: "_ruleTupleFloat4"; + children: _ruleTupleFloat4CstChildren; +} + +export type _ruleTupleFloat4CstChildren = { + LBracket: IToken[]; + ValueFloat: IToken[]; + Comma: IToken[]; + RBracket: IToken[]; +}; + +export interface _ruleTupleFloat3CstNode extends CstNode { + name: "_ruleTupleFloat3"; + children: _ruleTupleFloat3CstChildren; +} + +export type _ruleTupleFloat3CstChildren = { + LBracket: IToken[]; + ValueFloat: IToken[]; + Comma: IToken[]; + RBracket: IToken[]; +}; + +export interface _ruleTupleFloat2CstNode extends CstNode { + name: "_ruleTupleFloat2"; + children: _ruleTupleFloat2CstChildren; +} + +export type _ruleTupleFloat2CstChildren = { + LBracket: IToken[]; + ValueFloat: IToken[]; + Comma: IToken[]; + RBracket: IToken[]; +}; + +export interface _ruleTupleInt4CstNode extends CstNode { + name: "_ruleTupleInt4"; + children: _ruleTupleInt4CstChildren; +} + +export type _ruleTupleInt4CstChildren = { + LBracket: IToken[]; + ValueInt: IToken[]; + Comma: IToken[]; + RBracket: IToken[]; +}; + +export interface _ruleTupleInt3CstNode extends CstNode { + name: "_ruleTupleInt3"; + children: _ruleTupleInt3CstChildren; +} + +export type _ruleTupleInt3CstChildren = { + LBracket: IToken[]; + ValueInt: IToken[]; + Comma: IToken[]; + RBracket: IToken[]; +}; + +export interface _ruleTupleInt2CstNode extends CstNode { + name: "_ruleTupleInt2"; + children: _ruleTupleInt2CstChildren; +} + +export type _ruleTupleInt2CstChildren = { + LBracket: IToken[]; + ValueInt: IToken[]; + Comma: IToken[]; + RBracket: IToken[]; +}; + +export interface ICstNodeVisitor extends ICstVisitor { + _ruleShader(children: _ruleShaderCstChildren, param?: IN): OUT; + _ruleSubShader(children: _ruleSubShaderCstChildren, param?: IN): OUT; + _ruleShaderPass(children: _ruleShaderPassCstChildren, param?: IN): OUT; + _ruleStruct(children: _ruleStructCstChildren, param?: IN): OUT; + _ruleDeclaration(children: _ruleDeclarationCstChildren, param?: IN): OUT; + _ruleVariableType(children: _ruleVariableTypeCstChildren, param?: IN): OUT; + _ruleTag(children: _ruleTagCstChildren, param?: IN): OUT; + _ruleTagAssignment(children: _ruleTagAssignmentCstChildren, param?: IN): OUT; + _ruleTagType(children: _ruleTagTypeCstChildren, param?: IN): OUT; + _ruleFn(children: _ruleFnCstChildren, param?: IN): OUT; + _ruleFnReturnType(children: _ruleFnReturnTypeCstChildren, param?: IN): OUT; + _ruleFnArg(children: _ruleFnArgCstChildren, param?: IN): OUT; + _ruleFnBody(children: _ruleFnBodyCstChildren, param?: IN): OUT; + _ruleFnMacro(children: _ruleFnMacroCstChildren, param?: IN): OUT; + _ruleFnMacroCondition(children: _ruleFnMacroConditionCstChildren, param?: IN): OUT; + _ruleFnMacroConditionDeclare(children: _ruleFnMacroConditionDeclareCstChildren, param?: IN): OUT; + _ruleFnMacroConditionBranch(children: _ruleFnMacroConditionBranchCstChildren, param?: IN): OUT; + _ruleFnMacroConditionBranchDeclare(children: _ruleFnMacroConditionBranchDeclareCstChildren, param?: IN): OUT; + _ruleFnMacroDefine(children: _ruleFnMacroDefineCstChildren, param?: IN): OUT; + _ruleAssignableValue(children: _ruleAssignableValueCstChildren, param?: IN): OUT; + _ruleFnAddExpr(children: _ruleFnAddExprCstChildren, param?: IN): OUT; + _ruleFnMultiplicationExpr(children: _ruleFnMultiplicationExprCstChildren, param?: IN): OUT; + _ruleFnAtomicExpr(children: _ruleFnAtomicExprCstChildren, param?: IN): OUT; + _ruleAddOperator(children: _ruleAddOperatorCstChildren, param?: IN): OUT; + _ruleFnParenthesisExpr(children: _ruleFnParenthesisExprCstChildren, param?: IN): OUT; + _ruleNumber(children: _ruleNumberCstChildren, param?: IN): OUT; + _ruleFnCall(children: _ruleFnCallCstChildren, param?: IN): OUT; + _ruleFnCallVariable(children: _ruleFnCallVariableCstChildren, param?: IN): OUT; + _ruleFnVariable(children: _ruleFnVariableCstChildren, param?: IN): OUT; + _ruleMultiplicationOperator(children: _ruleMultiplicationOperatorCstChildren, param?: IN): OUT; + _ruleFnMacroInclude(children: _ruleFnMacroIncludeCstChildren, param?: IN): OUT; + _ruleFnStatement(children: _ruleFnStatementCstChildren, param?: IN): OUT; + _ruleFnReturnStatement(children: _ruleFnReturnStatementCstChildren, param?: IN): OUT; + _ruleFnExpression(children: _ruleFnExpressionCstChildren, param?: IN): OUT; + _ruleBoolean(children: _ruleBooleanCstChildren, param?: IN): OUT; + _ruleFnVariableDeclaration(children: _ruleFnVariableDeclarationCstChildren, param?: IN): OUT; + _ruleFnConditionStatement(children: _ruleFnConditionStatementCstChildren, param?: IN): OUT; + _ruleFnRelationExpr(children: _ruleFnRelationExprCstChildren, param?: IN): OUT; + _ruleRelationOperator(children: _ruleRelationOperatorCstChildren, param?: IN): OUT; + _ruleFnBlockStatement(children: _ruleFnBlockStatementCstChildren, param?: IN): OUT; + _ruleFnAssignStatement(children: _ruleFnAssignStatementCstChildren, param?: IN): OUT; + _ruleFnAssignLO(children: _ruleFnAssignLOCstChildren, param?: IN): OUT; + _ruleFnAssignmentOperator(children: _ruleFnAssignmentOperatorCstChildren, param?: IN): OUT; + _ruleSubShaderPassPropertyAssignment(children: _ruleSubShaderPassPropertyAssignmentCstChildren, param?: IN): OUT; + _ruleShaderPassPropertyType(children: _ruleShaderPassPropertyTypeCstChildren, param?: IN): OUT; + _ruleRenderStateType(children: _ruleRenderStateTypeCstChildren, param?: IN): OUT; + _ruleRenderStateDeclaration(children: _ruleRenderStateDeclarationCstChildren, param?: IN): OUT; + _ruleStatePropertyAssign(children: _ruleStatePropertyAssignCstChildren, param?: IN): OUT; + _ruleStateProperty(children: _ruleStatePropertyCstChildren, param?: IN): OUT; + _ruleProperty(children: _rulePropertyCstChildren, param?: IN): OUT; + _rulePropertyItem(children: _rulePropertyItemCstChildren, param?: IN): OUT; + _rulePropertyItemType(children: _rulePropertyItemTypeCstChildren, param?: IN): OUT; + _ruleRange(children: _ruleRangeCstChildren, param?: IN): OUT; + _rulePropertyItemValue(children: _rulePropertyItemValueCstChildren, param?: IN): OUT; + _ruleTupleFloat4(children: _ruleTupleFloat4CstChildren, param?: IN): OUT; + _ruleTupleFloat3(children: _ruleTupleFloat3CstChildren, param?: IN): OUT; + _ruleTupleFloat2(children: _ruleTupleFloat2CstChildren, param?: IN): OUT; + _ruleTupleInt4(children: _ruleTupleInt4CstChildren, param?: IN): OUT; + _ruleTupleInt3(children: _ruleTupleInt3CstChildren, param?: IN): OUT; + _ruleTupleInt2(children: _ruleTupleInt2CstChildren, param?: IN): OUT; +} diff --git a/packages/shader-lab/tsconfig.json b/packages/shader-lab/tsconfig.json new file mode 100644 index 0000000000..f959fa90c9 --- /dev/null +++ b/packages/shader-lab/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "module": "esnext", + "target": "esnext", + "declaration": true, + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "experimentalDecorators": true, + "declarationDir": "types", + "emitDeclarationOnly": true, + "noImplicitOverride": true, + "sourceMap": true, + "incremental": false, + "skipLibCheck": true, + "stripInternal": true + }, + "include": ["src/**/*"], + "ts-node": { + "compilerOptions": { + "module": "commonjs", + "esModuleInterop": true + }, + "files": true + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a2f32ef969..a43adbfedd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,9 +1,5 @@ lockfileVersion: '6.0' -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - importers: .: @@ -193,6 +189,16 @@ importers: specifier: workspace:* version: link:../design + packages/shader-lab: + dependencies: + chevrotain: + specifier: ^10.5.0 + version: 10.5.0 + devDependencies: + '@galacean/engine-design': + specifier: workspace:* + version: link:../design + tests: dependencies: '@galacean/engine-core': @@ -213,6 +219,9 @@ importers: '@galacean/engine-rhi-webgl': specifier: workspace:* version: link:../packages/rhi-webgl + '@galacean/engine-shader-lab': + specifier: workspace:* + version: link:../packages/shader-lab packages: @@ -429,6 +438,29 @@ packages: to-fast-properties: 2.0.0 dev: true + /@chevrotain/cst-dts-gen@10.5.0: + resolution: {integrity: sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw==} + dependencies: + '@chevrotain/gast': 10.5.0 + '@chevrotain/types': 10.5.0 + lodash: 4.17.21 + dev: false + + /@chevrotain/gast@10.5.0: + resolution: {integrity: sha512-pXdMJ9XeDAbgOWKuD1Fldz4ieCs6+nLNmyVhe2gZVqoO7v8HXuHYs5OV2EzUtbuai37TlOAQHrTDvxMnvMJz3A==} + dependencies: + '@chevrotain/types': 10.5.0 + lodash: 4.17.21 + dev: false + + /@chevrotain/types@10.5.0: + resolution: {integrity: sha512-f1MAia0x/pAVPWH/T73BJVyO2XU5tI4/iE7cnxb7tqdNTNhQI3Uq3XkqcoteTmD4t1aM0LbHCJOhgIDn07kl2A==} + dev: false + + /@chevrotain/utils@10.5.0: + resolution: {integrity: sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ==} + dev: false + /@choojs/findup@0.2.1: resolution: {integrity: sha512-YstAqNb0MCN8PjdLCDfRsBcGVRN41f3vgLvaI0IrIcBp4AqILRSS0DeWNGkicC+f/zRIPJLc+9RURVSepwvfBw==} hasBin: true @@ -1362,8 +1394,8 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001516 - electron-to-chromium: 1.4.463 + caniuse-lite: 1.0.30001517 + electron-to-chromium: 1.4.466 node-releases: 2.0.13 update-browserslist-db: 1.0.11(browserslist@4.21.9) dev: true @@ -1435,8 +1467,8 @@ packages: engines: {node: '>=10'} dev: true - /caniuse-lite@1.0.30001516: - resolution: {integrity: sha512-Wmec9pCBY8CWbmI4HsjBeQLqDTqV91nFVR83DnZpYyRnPI1wePDsTg0bGLPC5VU/3OIZV1fmxEea1b+tFKe86g==} + /caniuse-lite@1.0.30001517: + resolution: {integrity: sha512-Vdhm5S11DaFVLlyiKu4hiUTkpZu+y1KA/rZZqVQfOD5YdDT/eQKlkt7NaE0WGOFgX32diqt9MiP9CAiFeRklaA==} dev: true /chai-spies@1.0.0(chai@4.3.6): @@ -1490,6 +1522,17 @@ packages: resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==} dev: true + /chevrotain@10.5.0: + resolution: {integrity: sha512-Pkv5rBY3+CsHOYfV5g/Vs5JY9WTHHDEKOlohI2XeygaZhUeqhAlldZ8Hz9cRmxu709bvS08YzxHdTPHhffc13A==} + dependencies: + '@chevrotain/cst-dts-gen': 10.5.0 + '@chevrotain/gast': 10.5.0 + '@chevrotain/types': 10.5.0 + '@chevrotain/utils': 10.5.0 + lodash: 4.17.21 + regexp-to-ast: 0.5.0 + dev: false + /chokidar@3.5.1: resolution: {integrity: sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==} engines: {node: '>= 8.10.0'} @@ -1892,8 +1935,8 @@ packages: stream-shift: 1.0.1 dev: true - /electron-to-chromium@1.4.463: - resolution: {integrity: sha512-fT3hvdUWLjDbaTGzyOjng/CQhQJSQP8ThO3XZAoaxHvHo2kUXiRQVMj9M235l8uDFiNPsPa6KHT1p3RaR6ugRw==} + /electron-to-chromium@1.4.466: + resolution: {integrity: sha512-TSkRvbXRXD8BwhcGlZXDsbI2lRoP8dvqR7LQnqQNk9KxXBc4tG8O+rTuXgTyIpEdiqSGKEBSqrxdqEntnjNncA==} dev: true /electron@13.0.0: @@ -3237,7 +3280,6 @@ packages: /lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - dev: true /log-symbols@4.0.0: resolution: {integrity: sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==} @@ -3658,7 +3700,7 @@ packages: levn: 0.3.0 prelude-ls: 1.1.2 type-check: 0.3.2 - word-wrap: 1.2.3 + word-wrap: 1.2.4 dev: true /optionator@0.9.3: @@ -3985,6 +4027,10 @@ packages: resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} dev: true + /regexp-to-ast@0.5.0: + resolution: {integrity: sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==} + dev: false + /release-zalgo@1.0.0: resolution: {integrity: sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==} engines: {node: '>=4'} @@ -4740,8 +4786,8 @@ packages: string-width: 2.1.1 dev: true - /word-wrap@1.2.3: - resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} + /word-wrap@1.2.4: + resolution: {integrity: sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==} engines: {node: '>=0.10.0'} dev: true diff --git a/rollup.config.js b/rollup.config.js index 0e004917d2..603a8b9b19 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -104,7 +104,7 @@ function config({ location, pkgJson }) { sourcemap: false } ], - external: Object.keys(pkgJson.dependencies || {}) + external: external .concat("@galacean/engine-miniprogram-adapter") .map((name) => `${name}/dist/miniprogram`), plugins diff --git a/tests/index.ts b/tests/index.ts index 877ef80821..2a063a62d6 100644 --- a/tests/index.ts +++ b/tests/index.ts @@ -15,7 +15,7 @@ function searchTests(root: string) { } else if (stat.isDirectory()) { describe(file, () => { searchTests(filePath); - }); + }).timeout(5000); } }); } diff --git a/tests/package.json b/tests/package.json index 4b2d78a595..5b65592eea 100644 --- a/tests/package.json +++ b/tests/package.json @@ -20,7 +20,7 @@ "@galacean/engine-design": "workspace:*", "@galacean/engine-math": "workspace:*", "@galacean/engine-rhi-webgl": "workspace:*", - "@galacean/engine-physics-lite": "workspace:*" - + "@galacean/engine-physics-lite": "workspace:*", + "@galacean/engine-shader-lab": "workspace:*" } -} \ No newline at end of file +} diff --git a/tests/src/core/2d/text/Font.test.ts b/tests/src/core/2d/text/Font.test.ts new file mode 100644 index 0000000000..e9e5911f41 --- /dev/null +++ b/tests/src/core/2d/text/Font.test.ts @@ -0,0 +1,62 @@ +import { WebGLEngine } from "@galacean/engine-rhi-webgl"; +import { Camera, Font, TextRenderer, Entity } from "@galacean/engine-core"; +import { Vector3 } from "@galacean/engine-math"; +import { expect } from "chai"; + +describe("Font", function () { + let engine: WebGLEngine; + let textRendererEntity: Entity; + + before(async () => { + engine = await WebGLEngine.create({ canvas: document.createElement("canvas") }); + engine.canvas.resizeByClientSize(); + + const rootEntity = engine.sceneManager.activeScene.createRootEntity("root"); + const camera = rootEntity.addComponent(Camera); + rootEntity.transform.setPosition(0, 0, 10); + rootEntity.transform.lookAt(new Vector3(0, 0, 0)); + + textRendererEntity = rootEntity.createChild("TextRenderer"); + textRendererEntity.addComponent(TextRenderer); + + engine.run(); + }); + + it("Font constructor", () => { + // Test that Font constructor works correctly. + expect(() => { + new Font(engine, "TestFont"); + new Font(engine); + new Font(engine, undefined); + }).not.to.throw(); + }); + + it("Font createFromOS", () => { + // Test that createFromOS returns null, while the name is empty string or undefined. + expect(Font.createFromOS(engine, "")).to.be.null; + expect(Font.createFromOS(engine, undefined)).to.be.null; + + const sysFont = Font.createFromOS(engine, "Arial"); + const sysFont2 = Font.createFromOS(engine, "Arial Black"); + const sysFont3 = Font.createFromOS(engine, "Rockwell"); + + // Test that fonts are same object, while call createFromOS with same parameter. + expect(Font.createFromOS(engine, "Arial")).to.be.eq(sysFont); + expect(Font.createFromOS(engine, "Arial Black")).to.be.eq(sysFont2); + expect(Font.createFromOS(engine, "Rockwell")).to.be.eq(sysFont3); + + // Test font name is right. + expect(sysFont.name).to.eq("Arial"); + expect(sysFont2.name).to.eq("Arial Black"); + expect(sysFont3.name).to.eq("Rockwell"); + }); + + it("Destroy font", () => { + // Test that destroy a font works correctly. + expect(textRendererEntity.destroy()).not.to.throw; + }); + + after(() => { + engine.destroy(); + }); +}); diff --git a/tests/src/core/Animator.test.ts b/tests/src/core/Animator.test.ts new file mode 100644 index 0000000000..e4c38b54d5 --- /dev/null +++ b/tests/src/core/Animator.test.ts @@ -0,0 +1,208 @@ +import { WebGLEngine } from "@galacean/engine-rhi-webgl"; +import { Animator, Camera } from "@galacean/engine-core"; +import { Quaternion } from "@galacean/engine-math"; +import { GLTFResource } from "@galacean/engine-loader"; +import chai, { expect } from "chai"; +import spies from "chai-spies"; +import { glbResource } from "./model"; + +chai.use(spies); + +const canvasDOM = document.createElement("canvas"); +canvasDOM.width = 1024; +canvasDOM.height = 1024; + +describe("Animator test", function () { + let animator: Animator; + let resource: GLTFResource; + let engine: WebGLEngine; + + before(async () => { + engine = await WebGLEngine.create({ canvas: canvasDOM }); + const scene = engine.sceneManager.activeScene; + const rootEntity = scene.createRootEntity(); + rootEntity.addComponent(Camera); + + resource = await engine.resourceManager.load(glbResource); + const defaultSceneRoot = resource.defaultSceneRoot; + rootEntity.addChild(defaultSceneRoot); + animator = defaultSceneRoot.getComponent(Animator); + + engine.run(); + }); + + after(function () { + animator.destroy(); + engine.destroy(); + }); + + afterEach(function () { + animator.speed = 1; + // @ts-ignore + animator._reset(); + }); + it("constructor", () => { + // Test default values + expect(animator).not.to.be.undefined; + expect(animator.cullingMode).to.eq(0); + expect(animator["_awoken"]).to.eq(true); + expect(animator["_enabled"]).to.eq(true); + expect(animator["_onUpdateIndex"]).to.eq(0); + expect(animator["_phasedActive"]).to.eq(true); + + // Test _tempAnimatorStateInfo default layerIndex values + expect(animator["_tempAnimatorStateInfo"].layerIndex).to.eq(-1); + }); + + it("animator speed value", () => { + // Test animator speed. + animator.play("Run"); + + let animatorLayerData = animator["_animatorLayersData"]; + const srcPlayData = animatorLayerData[0]?.srcPlayData; + + const speed = 1; + let expectedSpeed = speed * 0.5; + animator.speed = expectedSpeed; + let lastFrameTime = srcPlayData.frameTime; + animator.update(5); + expect(animator.speed).to.eq(expectedSpeed); + expect(srcPlayData.frameTime).to.eq(lastFrameTime + 5 * expectedSpeed); + expectedSpeed = speed * 2; + animator.speed = expectedSpeed; + lastFrameTime = srcPlayData.frameTime; + animator.update(10); + expect(animator.speed).to.eq(expectedSpeed); + expect(srcPlayData.frameTime).to.eq(lastFrameTime + 10 * expectedSpeed); + expectedSpeed = speed * 0; + animator.speed = expectedSpeed; + lastFrameTime = srcPlayData.frameTime; + animator.update(15); + expect(animator.speed).to.eq(expectedSpeed); + expect(srcPlayData.frameTime).to.eq(lastFrameTime + 15 * expectedSpeed); + }); + + it("play animation", () => { + // Test animator play. + const layerIndex = 0; + const normalizedTimeOffset = 0.5; + animator.play("Run"); + expect(animator["_tempAnimatorStateInfo"].layerIndex).to.eq(layerIndex); + + let animatorState = animator.getCurrentAnimatorState(layerIndex); + expect(animatorState.name).to.eq("Run"); + expect(animatorState.speed).to.eq(1); + expect(animatorState.wrapMode).to.eq(1); + + // Test animator change play state. + animator.play("Walk", layerIndex, normalizedTimeOffset); + animatorState = animator.getCurrentAnimatorState(layerIndex); + expect(animatorState.name).to.eq("Walk"); + }); + + it("animator cullingMode", () => { + // Test animator cullingMode. + //@ts-ignore + animator._controlledRenderers.forEach((renderer) => { + // mock entity is culled + renderer._renderFrameCount = Infinity; + }); + + animator.play("Run"); + + let animatorLayerData = animator["_animatorLayersData"]; + const srcPlayData = animatorLayerData[0]?.srcPlayData; + + animator.cullingMode = 1; + expect(animator.cullingMode).to.eq(1); + animator.update(5); + const curveOwner = srcPlayData.stateData.curveLayerOwner[0].curveOwner; + const initValue = curveOwner.defaultValue; + const currentValue = curveOwner.referenceTargetValue; + + expect(Quaternion.equals(initValue, currentValue)).to.eq(true); + + animator.cullingMode = 0; + expect(animator.cullingMode).to.eq(0); + animator.update(5); + expect(Quaternion.equals(initValue, currentValue)).to.eq(false); + }); + + it("animation enabled", () => { + // Test animator play. + animator.play("Survey"); + const onDisableSpy = chai.spy.on(animator, "_onDisable"); + const onEnableSpy = chai.spy.on(animator, "_onEnable"); + const onUpdateSpy = chai.spy.on(animator, "update"); + + animator.enabled = false; + expect(animator["_enabled"]).to.eq(false); + expect(onDisableSpy).to.have.been.called.exactly(1); + engine.update(); + expect(onUpdateSpy).to.have.been.called.exactly(0); + + animator.enabled = true; + expect(animator["_enabled"]).to.eq(true); + expect(onEnableSpy).to.have.been.called.exactly(1); + engine.update(); + expect(onUpdateSpy).to.have.been.called.exactly(1); + }); + + it("find animator state", () => { + const stateName = "Survey"; + const expectedStateName = "Run"; + const layerIndex = animator["_tempAnimatorStateInfo"].layerIndex; + + animator.play(stateName); + const currentAnimatorState = animator.getCurrentAnimatorState(layerIndex); + let animatorState = animator.findAnimatorState(stateName, layerIndex); + expect(animatorState).to.eq(currentAnimatorState); + + animator.play(expectedStateName); + animatorState = animator.findAnimatorState(expectedStateName, layerIndex); + expect(animatorState).not.to.eq(currentAnimatorState); + expect(animatorState.name).to.eq(expectedStateName); + }); + + it("animation getCurrentAnimatorState", () => { + //get random animation element from gltf resource + const min = 0; + const max = resource.animations.length - 1; + const index = Math.floor(Math.random() * (max - min + 1)) + min; + + //play animation and get current animator state + const expectedStateName = resource.animations[index].name; + animator.play(expectedStateName); + const layerIndex = animator["_tempAnimatorStateInfo"].layerIndex; + const currentAnimatorState = animator.getCurrentAnimatorState(layerIndex); + expect(currentAnimatorState.name).to.eq(expectedStateName); + }); + + it("animation cross fade", () => { + animator.play("Walk"); + animator.crossFade("Run", 0.5); + animator.update(1); + + const layerIndex = animator["_tempAnimatorStateInfo"].layerIndex; + const animatorLayerData = animator["_animatorLayersData"]; + const layerState = animatorLayerData[layerIndex].layerState; + + // current animator layerState should be CrossFading(2) + expect(layerState).to.eq(2); + }); + + it("animation fix cross fade", () => { + animator.play("Walk"); + animator.update(1); + animator.crossFade("Survey", 5); + animator.crossFade("Run", 0.5); + animator.update(10); + + const layerIndex = animator["_tempAnimatorStateInfo"].layerIndex; + const animatorLayerData = animator["_animatorLayersData"]; + const layerState = animatorLayerData[layerIndex].layerState; + + // current animator layerState should be FixedCrossFading(3) + expect(layerState).to.eq(3); + }); +}); diff --git a/tests/src/core/Sprite.test.ts b/tests/src/core/Sprite.test.ts index a4a7638c1a..cb3f941cb4 100644 --- a/tests/src/core/Sprite.test.ts +++ b/tests/src/core/Sprite.test.ts @@ -18,9 +18,9 @@ describe("Sprite", async () => { const sprite = new Sprite(engine); expect(sprite.texture).to.eq(null); - expect(sprite.region).to.deep.eq(new Rect(0, 0, 1, 1)); - expect(sprite.pivot).to.deep.eq(new Vector2(0.5, 0.5)); - expect(sprite.border).to.deep.eq(new Vector4(0, 0, 0, 0)); + expect(sprite.region).to.deep.include({ x: 0, y: 0, width: 1, height: 1 }); + expect(sprite.pivot).to.deep.include({ x: 0.5, y: 0.5 }); + expect(sprite.border).to.deep.include({ x: 0, y: 0, z: 0, w: 0 }); }); it("get set texture", () => { @@ -36,23 +36,23 @@ describe("Sprite", async () => { const rect = new Rect(0.1, 0.1, 0.7, 1.0); sprite.region = rect; - expect(sprite.region).to.deep.eq(new Rect(0.1, 0.1, 0.7, 0.9)); + expect(sprite.region).to.deep.include({ x: 0.1, y: 0.1, width: 0.7, height: 0.9 }); }); it("get set pivot", () => { const sprite = new Sprite(engine); const pivot = new Vector2(0.1, 0.1); sprite.pivot = pivot; - expect(sprite.pivot).to.deep.eq(pivot); + expect(sprite.pivot).to.deep.include({ x: 0.1, y: 0.1 }); sprite.pivot = sprite.pivot; - expect(sprite.pivot).to.deep.eq(pivot); + expect(sprite.pivot).to.deep.include({ x: 0.1, y: 0.1 }); }); it("get set border", () => { const sprite = new Sprite(engine); const border = new Vector4(0.1, 0.1, 0.8, 0.8); sprite.border = border; - expect(sprite.border).to.deep.eq(border); + expect(sprite.border).to.deep.include({ x: 0.1, y: 0.1, z: 0.8, w: 0.8 }); }); it("get set atlasRotated", () => { @@ -189,9 +189,6 @@ describe("Sprite", async () => { const sprite1 = new Sprite(engine, new Texture2D(engine, 1000, 2000)); const sprite2 = sprite1.clone(); expect(sprite1.texture).to.deep.eq(sprite2.texture); - expect(sprite1.region).to.deep.eq(sprite2.region); - expect(sprite1.pivot).to.deep.eq(sprite2.pivot); - expect(sprite1.border).to.deep.eq(sprite2.border); expect(sprite1.atlasRotated).to.eq(sprite2.atlasRotated); expect(sprite1.atlasRegion).to.deep.eq(sprite2.atlasRegion); expect(sprite1.atlasRegionOffset).to.deep.eq(sprite2.atlasRegionOffset); diff --git a/tests/src/core/base/EventDispatcher.test.ts b/tests/src/core/base/EventDispatcher.test.ts index b33f3ea394..55ba8eb4f6 100644 --- a/tests/src/core/base/EventDispatcher.test.ts +++ b/tests/src/core/base/EventDispatcher.test.ts @@ -72,4 +72,19 @@ describe("EventDispatcher test", function () { expect(eventOn).to.have.been.called.exactly(1); expect(eventDispatcher.listenerCount("test-event")).to.eql(1); }); + + it("call event in a callback", () => { + const eventDispatcher = new EventDispatcher(); + const event1On = chai.spy(() => { + eventDispatcher.dispatch("event2"); + }); + const event2On = chai.spy(() => {}); + eventDispatcher.on("event1", event1On); + eventDispatcher.on("event1", event1On); + eventDispatcher.on("event2", event2On); + eventDispatcher.on("event2", event2On); + eventDispatcher.dispatch("event1"); + expect(event1On).to.have.been.called.exactly(2); + expect(event2On).to.have.been.called.exactly(4); + }); }); diff --git a/tests/src/core/model/index.ts b/tests/src/core/model/index.ts new file mode 100644 index 0000000000..f1c5745a73 --- /dev/null +++ b/tests/src/core/model/index.ts @@ -0,0 +1,2 @@ +export const glbResource = + "data:application/octet-stream;base64,Z2xURgIAAAAofAIAID8AAEpTT057ImFzc2V0Ijp7ImNvcHlyaWdodCI6IkNDLUJZIDQuMCBNb2RlbCBieSBQaXhlbE1hbm5lbiBodHRwczovL29wZW5nYW1lYXJ0Lm9yZy9jb250ZW50L2ZveC1hbmQtc2hpYmEgYW5kIEB0b21rcmFuaXMgaHR0cHM6Ly9za2V0Y2hmYWIuY29tLzNkLW1vZGVscy9sb3ctcG9seS1mb3gtYnktcGl4ZWxtYW5uZW4tYW5pbWF0ZWQtMzcxZGVhODhkN2UwNGE3NmFmNTc2M2YyYTM2ODY2YmMgYW5kIEBBc29ib1N0dWRpbyB3aXRoIEBzY3VyZXN0IGh0dHBzOi8vZ2l0aHViLmNvbS9LaHJvbm9zR3JvdXAvZ2xURi1TYW1wbGUtTW9kZWxzL3B1bGwvMTUwI2lzc3VlY29tbWVudC00MDYzMDAxMTgiLCJ2ZXJzaW9uIjoiMi4wIn0sImFjY2Vzc29ycyI6W3siYnVmZmVyVmlldyI6MCwiY29tcG9uZW50VHlwZSI6NTEyNiwiY291bnQiOjE3MjgsInR5cGUiOiJWRUMzIiwiYnl0ZU9mZnNldCI6MCwibWluIjpbLTEyLjU5MjcxODEyNDM4OTY0OCwtMC4xMjE3NDQ3NjY4MzEzOTgwMSwtODguMDk1MDAxMjIwNzAzMTJdLCJtYXgiOlsxMi41OTI3MTgxMjQzODk2NDgsNzguOTA3MTg4NDE1NTI3MzQsNjYuNjI0ODYyNjcwODk4NDRdfSx7ImJ1ZmZlclZpZXciOjEsImNvbXBvbmVudFR5cGUiOjUxMjYsImNvdW50IjoxNzI4LCJ0eXBlIjoiVkVDMiIsImJ5dGVPZmZzZXQiOjB9LHsiYnVmZmVyVmlldyI6MSwiY29tcG9uZW50VHlwZSI6NTEyMywiY291bnQiOjE3MjgsInR5cGUiOiJWRUM0IiwiYnl0ZU9mZnNldCI6MTM4MjR9LHsiYnVmZmVyVmlldyI6MiwiYnl0ZU9mZnNldCI6MCwiY29tcG9uZW50VHlwZSI6NTEyNiwiY291bnQiOjE3MjgsInR5cGUiOiJWRUM0In0seyJidWZmZXJWaWV3IjozLCJieXRlT2Zmc2V0IjowLCJjb21wb25lbnRUeXBlIjo1MTI2LCJjb3VudCI6MjQsInR5cGUiOiJNQVQ0In0seyJidWZmZXJWaWV3Ijo0LCJieXRlT2Zmc2V0IjowLCJjb21wb25lbnRUeXBlIjo1MTI2LCJjb3VudCI6ODMsInR5cGUiOiJTQ0FMQVIiLCJtaW4iOlswXSwibWF4IjpbMy40MTY2NjY3NDYxMzk1MjY0XX0seyJidWZmZXJWaWV3Ijo1LCJieXRlT2Zmc2V0IjowLCJjb21wb25lbnRUeXBlIjo1MTI2LCJjb3VudCI6ODMsInR5cGUiOiJWRUM0In0seyJidWZmZXJWaWV3Ijo1LCJieXRlT2Zmc2V0IjoxMzI4LCJjb21wb25lbnRUeXBlIjo1MTI2LCJjb3VudCI6ODMsInR5cGUiOiJWRUM0In0seyJidWZmZXJWaWV3Ijo1LCJieXRlT2Zmc2V0IjoyNjU2LCJjb21wb25lbnRUeXBlIjo1MTI2LCJjb3VudCI6ODMsInR5cGUiOiJWRUM0In0seyJidWZmZXJWaWV3Ijo1LCJieXRlT2Zmc2V0IjozOTg0LCJjb21wb25lbnRUeXBlIjo1MTI2LCJjb3VudCI6ODMsInR5cGUiOiJWRUM0In0seyJidWZmZXJWaWV3Ijo1LCJieXRlT2Zmc2V0Ijo1MzEyLCJjb21wb25lbnRUeXBlIjo1MTI2LCJjb3VudCI6ODMsInR5cGUiOiJWRUM0In0seyJidWZmZXJWaWV3Ijo1LCJieXRlT2Zmc2V0Ijo2NjQwLCJjb21wb25lbnRUeXBlIjo1MTI2LCJjb3VudCI6ODMsInR5cGUiOiJWRUM0In0seyJidWZmZXJWaWV3Ijo1LCJieXRlT2Zmc2V0Ijo3OTY4LCJjb21wb25lbnRUeXBlIjo1MTI2LCJjb3VudCI6ODMsInR5cGUiOiJWRUM0In0seyJidWZmZXJWaWV3Ijo1LCJieXRlT2Zmc2V0Ijo5Mjk2LCJjb21wb25lbnRUeXBlIjo1MTI2LCJjb3VudCI6ODMsInR5cGUiOiJWRUM0In0seyJidWZmZXJWaWV3Ijo1LCJieXRlT2Zmc2V0IjoxMDYyNCwiY29tcG9uZW50VHlwZSI6NTEyNiwiY291bnQiOjgzLCJ0eXBlIjoiVkVDNCJ9LHsiYnVmZmVyVmlldyI6NSwiYnl0ZU9mZnNldCI6MTE5NTIsImNvbXBvbmVudFR5cGUiOjUxMjYsImNvdW50Ijo4MywidHlwZSI6IlZFQzQifSx7ImJ1ZmZlclZpZXciOjUsImJ5dGVPZmZzZXQiOjEzMjgwLCJjb21wb25lbnRUeXBlIjo1MTI2LCJjb3VudCI6ODMsInR5cGUiOiJWRUM0In0seyJidWZmZXJWaWV3Ijo1LCJieXRlT2Zmc2V0IjoxNDYwOCwiY29tcG9uZW50VHlwZSI6NTEyNiwiY291bnQiOjgzLCJ0eXBlIjoiVkVDNCJ9LHsiYnVmZmVyVmlldyI6NSwiYnl0ZU9mZnNldCI6MTU5MzYsImNvbXBvbmVudFR5cGUiOjUxMjYsImNvdW50Ijo4MywidHlwZSI6IlZFQzQifSx7ImJ1ZmZlclZpZXciOjUsImJ5dGVPZmZzZXQiOjE3MjY0LCJjb21wb25lbnRUeXBlIjo1MTI2LCJjb3VudCI6ODMsInR5cGUiOiJWRUM0In0seyJidWZmZXJWaWV3Ijo1LCJieXRlT2Zmc2V0IjoxODU5MiwiY29tcG9uZW50VHlwZSI6NTEyNiwiY291bnQiOjgzLCJ0eXBlIjoiVkVDNCJ9LHsiYnVmZmVyVmlldyI6NSwiYnl0ZU9mZnNldCI6MTk5MjAsImNvbXBvbmVudFR5cGUiOjUxMjYsImNvdW50Ijo4MywidHlwZSI6IlZFQzQifSx7ImJ1ZmZlclZpZXciOjUsImJ5dGVPZmZzZXQiOjIxMjQ4LCJjb21wb25lbnRUeXBlIjo1MTI2LCJjb3VudCI6ODMsInR5cGUiOiJWRUM0In0seyJidWZmZXJWaWV3Ijo1LCJieXRlT2Zmc2V0IjoyMjU3NiwiY29tcG9uZW50VHlwZSI6NTEyNiwiY291bnQiOjgzLCJ0eXBlIjoiVkVDNCJ9LHsiYnVmZmVyVmlldyI6NSwiYnl0ZU9mZnNldCI6MjM5MDQsImNvbXBvbmVudFR5cGUiOjUxMjYsImNvdW50Ijo4MywidHlwZSI6IlZFQzQifSx7ImJ1ZmZlclZpZXciOjYsImJ5dGVPZmZzZXQiOjAsImNvbXBvbmVudFR5cGUiOjUxMjYsImNvdW50Ijo4MywidHlwZSI6IlZFQzMifSx7ImJ1ZmZlclZpZXciOjUsImJ5dGVPZmZzZXQiOjI1MjMyLCJjb21wb25lbnRUeXBlIjo1MTI2LCJjb3VudCI6ODMsInR5cGUiOiJWRUM0In0seyJidWZmZXJWaWV3Ijo0LCJieXRlT2Zmc2V0IjozMzIsImNvbXBvbmVudFR5cGUiOjUxMjYsImNvdW50IjoxOCwidHlwZSI6IlNDQUxBUiIsIm1pbiI6WzBdLCJtYXgiOlswLjcwODMzMzMxMzQ2NTExODRdfSx7ImJ1ZmZlclZpZXciOjUsImJ5dGVPZmZzZXQiOjI2NTYwLCJjb21wb25lbnRUeXBlIjo1MTI2LCJjb3VudCI6MTgsInR5cGUiOiJWRUM0In0seyJidWZmZXJWaWV3Ijo1LCJieXRlT2Zmc2V0IjoyNjg0OCwiY29tcG9uZW50VHlwZSI6NTEyNiwiY291bnQiOjE4LCJ0eXBlIjoiVkVDNCJ9LHsiYnVmZmVyVmlldyI6NSwiYnl0ZU9mZnNldCI6MjcxMzYsImNvbXBvbmVudFR5cGUiOjUxMjYsImNvdW50IjoxOCwidHlwZSI6IlZFQzQifSx7ImJ1ZmZlclZpZXciOjUsImJ5dGVPZmZzZXQiOjI3NDI0LCJjb21wb25lbnRUeXBlIjo1MTI2LCJjb3VudCI6MTgsInR5cGUiOiJWRUM0In0seyJidWZmZXJWaWV3Ijo1LCJieXRlT2Zmc2V0IjoyNzcxMiwiY29tcG9uZW50VHlwZSI6NTEyNiwiY291bnQiOjE4LCJ0eXBlIjoiVkVDNCJ9LHsiYnVmZmVyVmlldyI6NSwiYnl0ZU9mZnNldCI6MjgwMDAsImNvbXBvbmVudFR5cGUiOjUxMjYsImNvdW50IjoxOCwidHlwZSI6IlZFQzQifSx7ImJ1ZmZlclZpZXciOjUsImJ5dGVPZmZzZXQiOjI4Mjg4LCJjb21wb25lbnRUeXBlIjo1MTI2LCJjb3VudCI6MTgsInR5cGUiOiJWRUM0In0seyJidWZmZXJWaWV3Ijo1LCJieXRlT2Zmc2V0IjoyODU3NiwiY29tcG9uZW50VHlwZSI6NTEyNiwiY291bnQiOjE4LCJ0eXBlIjoiVkVDNCJ9LHsiYnVmZmVyVmlldyI6NSwiYnl0ZU9mZnNldCI6Mjg4NjQsImNvbXBvbmVudFR5cGUiOjUxMjYsImNvdW50IjoxOCwidHlwZSI6IlZFQzQifSx7ImJ1ZmZlclZpZXciOjUsImJ5dGVPZmZzZXQiOjI5MTUyLCJjb21wb25lbnRUeXBlIjo1MTI2LCJjb3VudCI6MTgsInR5cGUiOiJWRUM0In0seyJidWZmZXJWaWV3Ijo1LCJieXRlT2Zmc2V0IjoyOTQ0MCwiY29tcG9uZW50VHlwZSI6NTEyNiwiY291bnQiOjE4LCJ0eXBlIjoiVkVDNCJ9LHsiYnVmZmVyVmlldyI6NSwiYnl0ZU9mZnNldCI6Mjk3MjgsImNvbXBvbmVudFR5cGUiOjUxMjYsImNvdW50IjoxOCwidHlwZSI6IlZFQzQifSx7ImJ1ZmZlclZpZXciOjUsImJ5dGVPZmZzZXQiOjMwMDE2LCJjb21wb25lbnRUeXBlIjo1MTI2LCJjb3VudCI6MTgsInR5cGUiOiJWRUM0In0seyJidWZmZXJWaWV3Ijo1LCJieXRlT2Zmc2V0IjozMDMwNCwiY29tcG9uZW50VHlwZSI6NTEyNiwiY291bnQiOjE4LCJ0eXBlIjoiVkVDNCJ9LHsiYnVmZmVyVmlldyI6NSwiYnl0ZU9mZnNldCI6MzA1OTIsImNvbXBvbmVudFR5cGUiOjUxMjYsImNvdW50IjoxOCwidHlwZSI6IlZFQzQifSx7ImJ1ZmZlclZpZXciOjUsImJ5dGVPZmZzZXQiOjMwODgwLCJjb21wb25lbnRUeXBlIjo1MTI2LCJjb3VudCI6MTgsInR5cGUiOiJWRUM0In0seyJidWZmZXJWaWV3Ijo1LCJieXRlT2Zmc2V0IjozMTE2OCwiY29tcG9uZW50VHlwZSI6NTEyNiwiY291bnQiOjE4LCJ0eXBlIjoiVkVDNCJ9LHsiYnVmZmVyVmlldyI6NSwiYnl0ZU9mZnNldCI6MzE0NTYsImNvbXBvbmVudFR5cGUiOjUxMjYsImNvdW50IjoxOCwidHlwZSI6IlZFQzQifSx7ImJ1ZmZlclZpZXciOjUsImJ5dGVPZmZzZXQiOjMxNzQ0LCJjb21wb25lbnRUeXBlIjo1MTI2LCJjb3VudCI6MTgsInR5cGUiOiJWRUM0In0seyJidWZmZXJWaWV3Ijo2LCJieXRlT2Zmc2V0Ijo5OTYsImNvbXBvbmVudFR5cGUiOjUxMjYsImNvdW50IjoxOCwidHlwZSI6IlZFQzMifSx7ImJ1ZmZlclZpZXciOjUsImJ5dGVPZmZzZXQiOjMyMDMyLCJjb21wb25lbnRUeXBlIjo1MTI2LCJjb3VudCI6MTgsInR5cGUiOiJWRUM0In0seyJidWZmZXJWaWV3Ijo0LCJieXRlT2Zmc2V0Ijo0MDQsImNvbXBvbmVudFR5cGUiOjUxMjYsImNvdW50IjoyNSwidHlwZSI6IlNDQUxBUiIsIm1pbiI6WzBdLCJtYXgiOlsxLjE1ODMzMzMwMTU0NDE4OTVdfSx7ImJ1ZmZlclZpZXciOjUsImJ5dGVPZmZzZXQiOjMyMzIwLCJjb21wb25lbnRUeXBlIjo1MTI2LCJjb3VudCI6MjUsInR5cGUiOiJWRUM0In0seyJidWZmZXJWaWV3Ijo1LCJieXRlT2Zmc2V0IjozMjcyMCwiY29tcG9uZW50VHlwZSI6NTEyNiwiY291bnQiOjI1LCJ0eXBlIjoiVkVDNCJ9LHsiYnVmZmVyVmlldyI6NSwiYnl0ZU9mZnNldCI6MzMxMjAsImNvbXBvbmVudFR5cGUiOjUxMjYsImNvdW50IjoyNSwidHlwZSI6IlZFQzQifSx7ImJ1ZmZlclZpZXciOjUsImJ5dGVPZmZzZXQiOjMzNTIwLCJjb21wb25lbnRUeXBlIjo1MTI2LCJjb3VudCI6MjUsInR5cGUiOiJWRUM0In0seyJidWZmZXJWaWV3Ijo1LCJieXRlT2Zmc2V0IjozMzkyMCwiY29tcG9uZW50VHlwZSI6NTEyNiwiY291bnQiOjI1LCJ0eXBlIjoiVkVDNCJ9LHsiYnVmZmVyVmlldyI6NSwiYnl0ZU9mZnNldCI6MzQzMjAsImNvbXBvbmVudFR5cGUiOjUxMjYsImNvdW50IjoyNSwidHlwZSI6IlZFQzQifSx7ImJ1ZmZlclZpZXciOjUsImJ5dGVPZmZzZXQiOjM0NzIwLCJjb21wb25lbnRUeXBlIjo1MTI2LCJjb3VudCI6MjUsInR5cGUiOiJWRUM0In0seyJidWZmZXJWaWV3Ijo1LCJieXRlT2Zmc2V0IjozNTEyMCwiY29tcG9uZW50VHlwZSI6NTEyNiwiY291bnQiOjI1LCJ0eXBlIjoiVkVDNCJ9LHsiYnVmZmVyVmlldyI6NSwiYnl0ZU9mZnNldCI6MzU1MjAsImNvbXBvbmVudFR5cGUiOjUxMjYsImNvdW50IjoyNSwidHlwZSI6IlZFQzQifSx7ImJ1ZmZlclZpZXciOjUsImJ5dGVPZmZzZXQiOjM1OTIwLCJjb21wb25lbnRUeXBlIjo1MTI2LCJjb3VudCI6MjUsInR5cGUiOiJWRUM0In0seyJidWZmZXJWaWV3Ijo1LCJieXRlT2Zmc2V0IjozNjMyMCwiY29tcG9uZW50VHlwZSI6NTEyNiwiY291bnQiOjI1LCJ0eXBlIjoiVkVDNCJ9LHsiYnVmZmVyVmlldyI6NSwiYnl0ZU9mZnNldCI6MzY3MjAsImNvbXBvbmVudFR5cGUiOjUxMjYsImNvdW50IjoyNSwidHlwZSI6IlZFQzQifSx7ImJ1ZmZlclZpZXciOjUsImJ5dGVPZmZzZXQiOjM3MTIwLCJjb21wb25lbnRUeXBlIjo1MTI2LCJjb3VudCI6MjUsInR5cGUiOiJWRUM0In0seyJidWZmZXJWaWV3Ijo1LCJieXRlT2Zmc2V0IjozNzUyMCwiY29tcG9uZW50VHlwZSI6NTEyNiwiY291bnQiOjI1LCJ0eXBlIjoiVkVDNCJ9LHsiYnVmZmVyVmlldyI6NSwiYnl0ZU9mZnNldCI6Mzc5MjAsImNvbXBvbmVudFR5cGUiOjUxMjYsImNvdW50IjoyNSwidHlwZSI6IlZFQzQifSx7ImJ1ZmZlclZpZXciOjUsImJ5dGVPZmZzZXQiOjM4MzIwLCJjb21wb25lbnRUeXBlIjo1MTI2LCJjb3VudCI6MjUsInR5cGUiOiJWRUM0In0seyJidWZmZXJWaWV3Ijo1LCJieXRlT2Zmc2V0IjozODcyMCwiY29tcG9uZW50VHlwZSI6NTEyNiwiY291bnQiOjI1LCJ0eXBlIjoiVkVDNCJ9LHsiYnVmZmVyVmlldyI6NSwiYnl0ZU9mZnNldCI6MzkxMjAsImNvbXBvbmVudFR5cGUiOjUxMjYsImNvdW50IjoyNSwidHlwZSI6IlZFQzQifSx7ImJ1ZmZlclZpZXciOjUsImJ5dGVPZmZzZXQiOjM5NTIwLCJjb21wb25lbnRUeXBlIjo1MTI2LCJjb3VudCI6MjUsInR5cGUiOiJWRUM0In0seyJidWZmZXJWaWV3Ijo2LCJieXRlT2Zmc2V0IjoxMjEyLCJjb21wb25lbnRUeXBlIjo1MTI2LCJjb3VudCI6MjUsInR5cGUiOiJWRUMzIn0seyJidWZmZXJWaWV3Ijo1LCJieXRlT2Zmc2V0IjozOTkyMCwiY29tcG9uZW50VHlwZSI6NTEyNiwiY291bnQiOjI1LCJ0eXBlIjoiVkVDNCJ9XSwiYW5pbWF0aW9ucyI6W3siY2hhbm5lbHMiOlt7InNhbXBsZXIiOjAsInRhcmdldCI6eyJub2RlIjo4LCJwYXRoIjoicm90YXRpb24ifX0seyJzYW1wbGVyIjoxLCJ0YXJnZXQiOnsibm9kZSI6NywicGF0aCI6InJvdGF0aW9uIn19LHsic2FtcGxlciI6MiwidGFyZ2V0Ijp7Im5vZGUiOjExLCJwYXRoIjoicm90YXRpb24ifX0seyJzYW1wbGVyIjozLCJ0YXJnZXQiOnsibm9kZSI6MTAsInBhdGgiOiJyb3RhdGlvbiJ9fSx7InNhbXBsZXIiOjQsInRhcmdldCI6eyJub2RlIjo5LCJwYXRoIjoicm90YXRpb24ifX0seyJzYW1wbGVyIjo1LCJ0YXJnZXQiOnsibm9kZSI6MTQsInBhdGgiOiJyb3RhdGlvbiJ9fSx7InNhbXBsZXIiOjYsInRhcmdldCI6eyJub2RlIjoxMywicGF0aCI6InJvdGF0aW9uIn19LHsic2FtcGxlciI6NywidGFyZ2V0Ijp7Im5vZGUiOjEyLCJwYXRoIjoicm90YXRpb24ifX0seyJzYW1wbGVyIjo4LCJ0YXJnZXQiOnsibm9kZSI6NiwicGF0aCI6InJvdGF0aW9uIn19LHsic2FtcGxlciI6OSwidGFyZ2V0Ijp7Im5vZGUiOjUsInBhdGgiOiJyb3RhdGlvbiJ9fSx7InNhbXBsZXIiOjEwLCJ0YXJnZXQiOnsibm9kZSI6MTcsInBhdGgiOiJyb3RhdGlvbiJ9fSx7InNhbXBsZXIiOjExLCJ0YXJnZXQiOnsibm9kZSI6MTYsInBhdGgiOiJyb3RhdGlvbiJ9fSx7InNhbXBsZXIiOjEyLCJ0YXJnZXQiOnsibm9kZSI6MTUsInBhdGgiOiJyb3RhdGlvbiJ9fSx7InNhbXBsZXIiOjEzLCJ0YXJnZXQiOnsibm9kZSI6MjAsInBhdGgiOiJyb3RhdGlvbiJ9fSx7InNhbXBsZXIiOjE0LCJ0YXJnZXQiOnsibm9kZSI6MTksInBhdGgiOiJyb3RhdGlvbiJ9fSx7InNhbXBsZXIiOjE1LCJ0YXJnZXQiOnsibm9kZSI6MTgsInBhdGgiOiJyb3RhdGlvbiJ9fSx7InNhbXBsZXIiOjE2LCJ0YXJnZXQiOnsibm9kZSI6MjQsInBhdGgiOiJyb3RhdGlvbiJ9fSx7InNhbXBsZXIiOjE3LCJ0YXJnZXQiOnsibm9kZSI6MjMsInBhdGgiOiJyb3RhdGlvbiJ9fSx7InNhbXBsZXIiOjE4LCJ0YXJnZXQiOnsibm9kZSI6MjIsInBhdGgiOiJyb3RhdGlvbiJ9fSx7InNhbXBsZXIiOjE5LCJ0YXJnZXQiOnsibm9kZSI6NCwicGF0aCI6InRyYW5zbGF0aW9uIn19LHsic2FtcGxlciI6MjAsInRhcmdldCI6eyJub2RlIjo0LCJwYXRoIjoicm90YXRpb24ifX1dLCJzYW1wbGVycyI6W3siaW5wdXQiOjUsIm91dHB1dCI6Nn0seyJpbnB1dCI6NSwib3V0cHV0Ijo3fSx7ImlucHV0Ijo1LCJvdXRwdXQiOjh9LHsiaW5wdXQiOjUsIm91dHB1dCI6OX0seyJpbnB1dCI6NSwib3V0cHV0IjoxMH0seyJpbnB1dCI6NSwib3V0cHV0IjoxMX0seyJpbnB1dCI6NSwib3V0cHV0IjoxMn0seyJpbnB1dCI6NSwib3V0cHV0IjoxM30seyJpbnB1dCI6NSwib3V0cHV0IjoxNH0seyJpbnB1dCI6NSwib3V0cHV0IjoxNX0seyJpbnB1dCI6NSwib3V0cHV0IjoxNn0seyJpbnB1dCI6NSwib3V0cHV0IjoxN30seyJpbnB1dCI6NSwib3V0cHV0IjoxOH0seyJpbnB1dCI6NSwib3V0cHV0IjoxOX0seyJpbnB1dCI6NSwib3V0cHV0IjoyMH0seyJpbnB1dCI6NSwib3V0cHV0IjoyMX0seyJpbnB1dCI6NSwib3V0cHV0IjoyMn0seyJpbnB1dCI6NSwib3V0cHV0IjoyM30seyJpbnB1dCI6NSwib3V0cHV0IjoyNH0seyJpbnB1dCI6NSwib3V0cHV0IjoyNX0seyJpbnB1dCI6NSwib3V0cHV0IjoyNn1dLCJuYW1lIjoiU3VydmV5In0seyJjaGFubmVscyI6W3sic2FtcGxlciI6MCwidGFyZ2V0Ijp7Im5vZGUiOjgsInBhdGgiOiJyb3RhdGlvbiJ9fSx7InNhbXBsZXIiOjEsInRhcmdldCI6eyJub2RlIjo3LCJwYXRoIjoicm90YXRpb24ifX0seyJzYW1wbGVyIjoyLCJ0YXJnZXQiOnsibm9kZSI6MTEsInBhdGgiOiJyb3RhdGlvbiJ9fSx7InNhbXBsZXIiOjMsInRhcmdldCI6eyJub2RlIjoxMCwicGF0aCI6InJvdGF0aW9uIn19LHsic2FtcGxlciI6NCwidGFyZ2V0Ijp7Im5vZGUiOjksInBhdGgiOiJyb3RhdGlvbiJ9fSx7InNhbXBsZXIiOjUsInRhcmdldCI6eyJub2RlIjoxNCwicGF0aCI6InJvdGF0aW9uIn19LHsic2FtcGxlciI6NiwidGFyZ2V0Ijp7Im5vZGUiOjEzLCJwYXRoIjoicm90YXRpb24ifX0seyJzYW1wbGVyIjo3LCJ0YXJnZXQiOnsibm9kZSI6MTIsInBhdGgiOiJyb3RhdGlvbiJ9fSx7InNhbXBsZXIiOjgsInRhcmdldCI6eyJub2RlIjo2LCJwYXRoIjoicm90YXRpb24ifX0seyJzYW1wbGVyIjo5LCJ0YXJnZXQiOnsibm9kZSI6NSwicGF0aCI6InJvdGF0aW9uIn19LHsic2FtcGxlciI6MTAsInRhcmdldCI6eyJub2RlIjoxNywicGF0aCI6InJvdGF0aW9uIn19LHsic2FtcGxlciI6MTEsInRhcmdldCI6eyJub2RlIjoxNiwicGF0aCI6InJvdGF0aW9uIn19LHsic2FtcGxlciI6MTIsInRhcmdldCI6eyJub2RlIjoxNSwicGF0aCI6InJvdGF0aW9uIn19LHsic2FtcGxlciI6MTMsInRhcmdldCI6eyJub2RlIjoyMCwicGF0aCI6InJvdGF0aW9uIn19LHsic2FtcGxlciI6MTQsInRhcmdldCI6eyJub2RlIjoxOSwicGF0aCI6InJvdGF0aW9uIn19LHsic2FtcGxlciI6MTUsInRhcmdldCI6eyJub2RlIjoxOCwicGF0aCI6InJvdGF0aW9uIn19LHsic2FtcGxlciI6MTYsInRhcmdldCI6eyJub2RlIjoyNCwicGF0aCI6InJvdGF0aW9uIn19LHsic2FtcGxlciI6MTcsInRhcmdldCI6eyJub2RlIjoyMywicGF0aCI6InJvdGF0aW9uIn19LHsic2FtcGxlciI6MTgsInRhcmdldCI6eyJub2RlIjoyMiwicGF0aCI6InJvdGF0aW9uIn19LHsic2FtcGxlciI6MTksInRhcmdldCI6eyJub2RlIjo0LCJwYXRoIjoidHJhbnNsYXRpb24ifX0seyJzYW1wbGVyIjoyMCwidGFyZ2V0Ijp7Im5vZGUiOjQsInBhdGgiOiJyb3RhdGlvbiJ9fV0sInNhbXBsZXJzIjpbeyJpbnB1dCI6MjcsIm91dHB1dCI6Mjh9LHsiaW5wdXQiOjI3LCJvdXRwdXQiOjI5fSx7ImlucHV0IjoyNywib3V0cHV0IjozMH0seyJpbnB1dCI6MjcsIm91dHB1dCI6MzF9LHsiaW5wdXQiOjI3LCJvdXRwdXQiOjMyfSx7ImlucHV0IjoyNywib3V0cHV0IjozM30seyJpbnB1dCI6MjcsIm91dHB1dCI6MzR9LHsiaW5wdXQiOjI3LCJvdXRwdXQiOjM1fSx7ImlucHV0IjoyNywib3V0cHV0IjozNn0seyJpbnB1dCI6MjcsIm91dHB1dCI6Mzd9LHsiaW5wdXQiOjI3LCJvdXRwdXQiOjM4fSx7ImlucHV0IjoyNywib3V0cHV0IjozOX0seyJpbnB1dCI6MjcsIm91dHB1dCI6NDB9LHsiaW5wdXQiOjI3LCJvdXRwdXQiOjQxfSx7ImlucHV0IjoyNywib3V0cHV0Ijo0Mn0seyJpbnB1dCI6MjcsIm91dHB1dCI6NDN9LHsiaW5wdXQiOjI3LCJvdXRwdXQiOjQ0fSx7ImlucHV0IjoyNywib3V0cHV0Ijo0NX0seyJpbnB1dCI6MjcsIm91dHB1dCI6NDZ9LHsiaW5wdXQiOjI3LCJvdXRwdXQiOjQ3fSx7ImlucHV0IjoyNywib3V0cHV0Ijo0OH1dLCJuYW1lIjoiV2FsayJ9LHsiY2hhbm5lbHMiOlt7InNhbXBsZXIiOjAsInRhcmdldCI6eyJub2RlIjo4LCJwYXRoIjoicm90YXRpb24ifX0seyJzYW1wbGVyIjoxLCJ0YXJnZXQiOnsibm9kZSI6NywicGF0aCI6InJvdGF0aW9uIn19LHsic2FtcGxlciI6MiwidGFyZ2V0Ijp7Im5vZGUiOjExLCJwYXRoIjoicm90YXRpb24ifX0seyJzYW1wbGVyIjozLCJ0YXJnZXQiOnsibm9kZSI6MTAsInBhdGgiOiJyb3RhdGlvbiJ9fSx7InNhbXBsZXIiOjQsInRhcmdldCI6eyJub2RlIjo5LCJwYXRoIjoicm90YXRpb24ifX0seyJzYW1wbGVyIjo1LCJ0YXJnZXQiOnsibm9kZSI6MTQsInBhdGgiOiJyb3RhdGlvbiJ9fSx7InNhbXBsZXIiOjYsInRhcmdldCI6eyJub2RlIjoxMywicGF0aCI6InJvdGF0aW9uIn19LHsic2FtcGxlciI6NywidGFyZ2V0Ijp7Im5vZGUiOjEyLCJwYXRoIjoicm90YXRpb24ifX0seyJzYW1wbGVyIjo4LCJ0YXJnZXQiOnsibm9kZSI6NiwicGF0aCI6InJvdGF0aW9uIn19LHsic2FtcGxlciI6OSwidGFyZ2V0Ijp7Im5vZGUiOjUsInBhdGgiOiJyb3RhdGlvbiJ9fSx7InNhbXBsZXIiOjEwLCJ0YXJnZXQiOnsibm9kZSI6MTcsInBhdGgiOiJyb3RhdGlvbiJ9fSx7InNhbXBsZXIiOjExLCJ0YXJnZXQiOnsibm9kZSI6MTYsInBhdGgiOiJyb3RhdGlvbiJ9fSx7InNhbXBsZXIiOjEyLCJ0YXJnZXQiOnsibm9kZSI6MTUsInBhdGgiOiJyb3RhdGlvbiJ9fSx7InNhbXBsZXIiOjEzLCJ0YXJnZXQiOnsibm9kZSI6MjAsInBhdGgiOiJyb3RhdGlvbiJ9fSx7InNhbXBsZXIiOjE0LCJ0YXJnZXQiOnsibm9kZSI6MTksInBhdGgiOiJyb3RhdGlvbiJ9fSx7InNhbXBsZXIiOjE1LCJ0YXJnZXQiOnsibm9kZSI6MTgsInBhdGgiOiJyb3RhdGlvbiJ9fSx7InNhbXBsZXIiOjE2LCJ0YXJnZXQiOnsibm9kZSI6MjQsInBhdGgiOiJyb3RhdGlvbiJ9fSx7InNhbXBsZXIiOjE3LCJ0YXJnZXQiOnsibm9kZSI6MjMsInBhdGgiOiJyb3RhdGlvbiJ9fSx7InNhbXBsZXIiOjE4LCJ0YXJnZXQiOnsibm9kZSI6MjIsInBhdGgiOiJyb3RhdGlvbiJ9fSx7InNhbXBsZXIiOjE5LCJ0YXJnZXQiOnsibm9kZSI6NCwicGF0aCI6InRyYW5zbGF0aW9uIn19LHsic2FtcGxlciI6MjAsInRhcmdldCI6eyJub2RlIjo0LCJwYXRoIjoicm90YXRpb24ifX1dLCJzYW1wbGVycyI6W3siaW5wdXQiOjQ5LCJvdXRwdXQiOjUwfSx7ImlucHV0Ijo0OSwib3V0cHV0Ijo1MX0seyJpbnB1dCI6NDksIm91dHB1dCI6NTJ9LHsiaW5wdXQiOjQ5LCJvdXRwdXQiOjUzfSx7ImlucHV0Ijo0OSwib3V0cHV0Ijo1NH0seyJpbnB1dCI6NDksIm91dHB1dCI6NTV9LHsiaW5wdXQiOjQ5LCJvdXRwdXQiOjU2fSx7ImlucHV0Ijo0OSwib3V0cHV0Ijo1N30seyJpbnB1dCI6NDksIm91dHB1dCI6NTh9LHsiaW5wdXQiOjQ5LCJvdXRwdXQiOjU5fSx7ImlucHV0Ijo0OSwib3V0cHV0Ijo2MH0seyJpbnB1dCI6NDksIm91dHB1dCI6NjF9LHsiaW5wdXQiOjQ5LCJvdXRwdXQiOjYyfSx7ImlucHV0Ijo0OSwib3V0cHV0Ijo2M30seyJpbnB1dCI6NDksIm91dHB1dCI6NjR9LHsiaW5wdXQiOjQ5LCJvdXRwdXQiOjY1fSx7ImlucHV0Ijo0OSwib3V0cHV0Ijo2Nn0seyJpbnB1dCI6NDksIm91dHB1dCI6Njd9LHsiaW5wdXQiOjQ5LCJvdXRwdXQiOjY4fSx7ImlucHV0Ijo0OSwib3V0cHV0Ijo2OX0seyJpbnB1dCI6NDksIm91dHB1dCI6NzB9XSwibmFtZSI6IlJ1biJ9XSwiYnVmZmVyVmlld3MiOlt7ImJ1ZmZlciI6MCwiYnl0ZU9mZnNldCI6MCwiYnl0ZUxlbmd0aCI6MjA3MzYsImJ5dGVTdHJpZGUiOjEyfSx7ImJ1ZmZlciI6MCwiYnl0ZU9mZnNldCI6MjA3MzYsImJ5dGVMZW5ndGgiOjI3NjQ4LCJieXRlU3RyaWRlIjo4fSx7ImJ1ZmZlciI6MCwiYnl0ZU9mZnNldCI6NDgzODQsImJ5dGVMZW5ndGgiOjI3NjQ4LCJieXRlU3RyaWRlIjoxNn0seyJidWZmZXIiOjAsImJ5dGVPZmZzZXQiOjc2MDMyLCJieXRlTGVuZ3RoIjoxNTM2fSx7ImJ1ZmZlciI6MCwiYnl0ZU9mZnNldCI6Nzc1NjgsImJ5dGVMZW5ndGgiOjUwNCwiYnl0ZVN0cmlkZSI6NH0seyJidWZmZXIiOjAsImJ5dGVPZmZzZXQiOjc4MDcyLCJieXRlTGVuZ3RoIjo0MDMyMCwiYnl0ZVN0cmlkZSI6MTZ9LHsiYnVmZmVyIjowLCJieXRlT2Zmc2V0IjoxMTgzOTIsImJ5dGVMZW5ndGgiOjE1MTIsImJ5dGVTdHJpZGUiOjEyfSx7ImJ1ZmZlciI6MCwiYnl0ZU9mZnNldCI6MTE5OTA0LCJieXRlTGVuZ3RoIjoyNjc2NH1dLCJidWZmZXJzIjpbeyJieXRlTGVuZ3RoIjoxNDY2Njh9XSwiaW1hZ2VzIjpbeyJtaW1lVHlwZSI6ImltYWdlL3BuZyIsImJ1ZmZlclZpZXciOjd9XSwibWF0ZXJpYWxzIjpbeyJuYW1lIjoiZm94X21hdGVyaWFsIiwicGJyTWV0YWxsaWNSb3VnaG5lc3MiOnsiYmFzZUNvbG9yVGV4dHVyZSI6eyJpbmRleCI6MH0sIm1ldGFsbGljRmFjdG9yIjowLCJyb3VnaG5lc3NGYWN0b3IiOjAuNTh9fV0sIm1lc2hlcyI6W3sibmFtZSI6ImZveDEiLCJwcmltaXRpdmVzIjpbeyJhdHRyaWJ1dGVzIjp7IlBPU0lUSU9OIjowLCJURVhDT09SRF8wIjoxLCJKT0lOVFNfMCI6MiwiV0VJR0hUU18wIjozfSwibWF0ZXJpYWwiOjB9XX1dLCJub2RlcyI6W3siY2hpbGRyZW4iOlsxLDJdLCJuYW1lIjoicm9vdCJ9LHsibmFtZSI6ImZveCIsIm1lc2giOjAsInNraW4iOjB9LHsiY2hpbGRyZW4iOlszXSwibmFtZSI6Il9yb290Sm9pbnQifSx7ImNoaWxkcmVuIjpbNF0sIm5hbWUiOiJiX1Jvb3RfMDAiLCJyb3RhdGlvbiI6Wy0wLjcwNzEwODA5MjQ4NzUzOTEsMCwwLDAuNzA3MTA1NDY5ODgzMTI0Ml19LHsiY2hpbGRyZW4iOls1LDE1LDE4LDIyXSwibmFtZSI6ImJfSGlwXzAxIiwicm90YXRpb24iOlswLjEyNzY5MDk0MTc2MTc1NTQ3LC0wLjY5NTQ4MjAxOTIzOTM3NjIsLTAuMTI3NjkwMjI2NTA2MDE0NDQsMC42OTU0ODE4NDA0MjU0NDFdLCJ0cmFuc2xhdGlvbiI6WzAsMjYuNzQ4NDAzNTQ5MTk0MzM2LDQyLjkzODE3MTM4NjcxODc1XX0seyJjaGlsZHJlbiI6WzZdLCJuYW1lIjoiYl9TcGluZTAxXzAyIiwicm90YXRpb24iOlswLDAsLTAuNTkwNDE1NzYzODIzODMxNywwLjgwNzA5OTI2NjQwMzAzNzZdLCJ0cmFuc2xhdGlvbiI6WzEyLjg1MDYwMTE5NjI4OTA2MiwwLDBdfSx7ImNoaWxkcmVuIjpbNyw5LDEyXSwibmFtZSI6ImJfU3BpbmUwMl8wMyIsInJvdGF0aW9uIjpbMCwwLDAuMDE3NDExOTUyNDA0MjgxMDgyLDAuOTk5ODQ4NDAwNDY1NTI2MV0sInRyYW5zbGF0aW9uIjpbMjEuNjU1NzU0MDg5MzU1NDcsLTAuMDAwMTE4MjU1NjE1MjM0Mzc1LDBdfSx7ImNoaWxkcmVuIjpbOF0sIm5hbWUiOiJiX05lY2tfMDQiLCJyb3RhdGlvbiI6WzAsMCwwLjMwMzM3OTE0MDI4MjY0MzQ2LDAuOTUyODY5OTI2NzE2ODQ0M10sInRyYW5zbGF0aW9uIjpbMjUuNjQ5MTQzMjE4OTk0MTQsMCwwXX0seyJuYW1lIjoiYl9IZWFkXzA1Iiwicm90YXRpb24iOlswLDAsLTAuNDAwMjg1NDE1MTQ4NzM0OSwwLjkxNjM5MDUyMDY5NDc1NTVdLCJ0cmFuc2xhdGlvbiI6WzEzLjM3Njk2MDc1NDM5NDUzMSwwLDBdfSx7ImNoaWxkcmVuIjpbMTBdLCJuYW1lIjoiYl9SaWdodFVwcGVyQXJtXzA2Iiwicm90YXRpb24iOlswLjAwMDQ2NzMyNzMyNjIwMTE1NjIsLTAuMDAwNDQ2MTQ4NDY5MjI1NTkyOCwtMC43MTIxNzkyODgxMTEwNjkxLDAuNzAxOTk3MzI0ODgyNTk4NV0sInRyYW5zbGF0aW9uIjpbMTguNjc3OTEzNjY1NzcxNDg0LC00LjI5NzM0MDM5MzA2NjQwNiw2Ljk2NzU3NTA3MzI0MjE4NzVdfSx7ImNoaWxkcmVuIjpbMTFdLCJuYW1lIjoiYl9SaWdodEZvcmVBcm1fMDciLCJyb3RhdGlvbiI6WzAsMCwwLjAzNzEyNTg5OTc3MzQ4NzQ0LDAuOTk5MzEwNTk2MTQ0MTY2M10sInRyYW5zbGF0aW9uIjpbMjMuMDQ1MTI1OTYxMzAzNzEsMCwwXX0seyJuYW1lIjoiYl9SaWdodEhhbmRfMDgiLCJyb3RhdGlvbiI6Wy0wLjAxMjAzNzQwNjkxNDc5NzAxOCwtMC4wMDc4MjIyMTAxMjQ2NTI3NiwwLjQ2MDU2MjMyNzcxODUxNDgsMC44ODc1MTEyNzA5OTg4NzQxXSwidHJhbnNsYXRpb24iOlsxOS4zNTAwNTU2OTQ1ODAwNzgsLTAuMTQ1OTg2NTU3MDA2ODM1OTQsMF19LHsiY2hpbGRyZW4iOlsxM10sIm5hbWUiOiJiX0xlZnRVcHBlckFybV8wOSIsInJvdGF0aW9uIjpbMC4wMDA0OTcyNjE5MjIwOTQwMTc0LC0wLjAwMDg4MjE5MjMxNjY0NDI4NzUsLTAuNzEyMDg3NDkyOTkxNDY2MywwLjcwMjA5MDAwNjE5MDM5MjddLCJ0cmFuc2xhdGlvbiI6WzE4LjY3NzkxNzQ4MDQ2ODc1LC00LjI5NzM0NDIwNzc2MzY3MiwtNi45Njc5ODcwNjA1NDY4NzVdfSx7ImNoaWxkcmVuIjpbMTRdLCJuYW1lIjoiYl9MZWZ0Rm9yZUFybV8wMTAiLCJyb3RhdGlvbiI6WzAsMCwwLjAzNzEyNTg5OTc3MzQ4NzQ0LDAuOTk5MzEwNTk2MTQ0MTY2M10sInRyYW5zbGF0aW9uIjpbMjMuMDQ1MTI0MDUzOTU1MDc4LDAsMF19LHsibmFtZSI6ImJfTGVmdEhhbmRfMDExIiwicm90YXRpb24iOlswLjAxNjUxNzkxNDQwNzIxNTA3LDAuMDE0MDEzNzM5ODczOTk3NzgxLDAuNDYwMDc1NTc1MjEyNzEsMC44ODc2MTU0NzkwNzM2MDk5XSwidHJhbnNsYXRpb24iOlsxOS4zNTAwNTE4Nzk4ODI4MTIsLTAuMTQ1OTkwMzcxNzA0MTAxNTYsMF19LHsiY2hpbGRyZW4iOlsxNl0sIm5hbWUiOiJiX1RhaWwwMV8wMTIiLCJyb3RhdGlvbiI6WzAsMCwwLjk4MTg5Mjg5NDA2NTYyOTUsMC4xODk0MzY5MTQ1MjE0OTA0XSwidHJhbnNsYXRpb24iOls0LjI2MDM3NTk3NjU2MjUsMTUuOTU4NzcwNzUxOTUzMTI1LDBdfSx7ImNoaWxkcmVuIjpbMTddLCJuYW1lIjoiYl9UYWlsMDJfMDEzIiwicm90YXRpb24iOlswLDAsLTAuMDY5NjE3MTY2MzM4NzQ2NiwwLjk5NzU3Mzc4MTgwODEyNDRdLCJ0cmFuc2xhdGlvbiI6WzEyLjQxMTkxODY0MDEzNjcxOSwwLDBdfSx7Im5hbWUiOiJiX1RhaWwwM18wMTQiLCJyb3RhdGlvbiI6WzAsMCwtMC4wNTM4MzI3NDQ4NDIwNzY4NCwwLjk5ODU0OTk2NjQ5Mjc5NzldLCJ0cmFuc2xhdGlvbiI6WzI0LjI0MDMyMjExMzAzNzExLDAsMF19LHsiY2hpbGRyZW4iOlsxOV0sIm5hbWUiOiJiX0xlZnRMZWcwMV8wMTUiLCJyb3RhdGlvbiI6WzAsLTAuMDAwMTcxNzUyMjUzNjU1OTkzNiwwLjk3MDAxNTg4MzQwMjA2ODEsLTAuMjQzMDQxNDcwNjM1OTE2MV0sInRyYW5zbGF0aW9uIjpbNC44MTM3NzAyOTQxODk0NTMsNS4xNTQwMTg0MDIwOTk2MDksLTYuOTY4MDA2MTM0MDMzMjAzXX0seyJjaGlsZHJlbiI6WzIwXSwibmFtZSI6ImJfTGVmdExlZzAyXzAxNiIsInJvdGF0aW9uIjpbMCwwLC0wLjM2ODA0Mzc4ODU1NTExNjU1LDAuOTI5ODA4NDU4NjExNzcwNl0sInRyYW5zbGF0aW9uIjpbMTguOTQ0MTc1NzIwMjE0ODQ0LDAsMF19LHsiY2hpbGRyZW4iOlsyMV0sIm5hbWUiOiJiX0xlZnRGb290MDFfMDE3Iiwicm90YXRpb24iOlswLjAwMDI0ODQxMDU5Mjk2NjQ2NjYsMCwwLjQ1ODQ4NDExMjI1ODUwOTksMC44ODg3MDI1Njk1MzUzMzNdLCJ0cmFuc2xhdGlvbiI6WzE3Ljk0MjgxMTk2NTk0MjM4MywwLDBdfSx7Im5hbWUiOiJiX0xlZnRGb290MDJfMDE4Iiwicm90YXRpb24iOlswLDAsMC41NDcyODgyOTQ5MDkwMjQzLDAuODM2OTQ0MTU3MTkwNjUzM10sInRyYW5zbGF0aW9uIjpbMTUuNzc5OTM4Njk3ODE0OTQxLDAsMF19LHsiY2hpbGRyZW4iOlsyM10sIm5hbWUiOiJiX1JpZ2h0TGVnMDFfMDE5Iiwicm90YXRpb24iOlswLDAsMC45Njk5NTg1OTQyMDU0NTM1LC0wLjI0MzI3MDA2NzA1OTE4NTMzXSwidHJhbnNsYXRpb24iOls0LjgxMzc3NzkyMzU4Mzk4NCw1LjE1NDAyNjAzMTQ5NDE0MSw2Ljk2NzU2MzYyOTE1MDM5MV19LHsiY2hpbGRyZW4iOlsyNF0sIm5hbWUiOiJiX1JpZ2h0TGVnMDJfMDIwIiwicm90YXRpb24iOlswLDAsLTAuMzY4MDQzODE0MzIwNTI4ODUsMC45Mjk4MDg0NDg0MTMxMTA2XSwidHJhbnNsYXRpb24iOlsxOC45NDQxODMzNDk2MDkzNzUsMCwwXX0seyJjaGlsZHJlbiI6WzI1XSwibmFtZSI6ImJfUmlnaHRGb290MDFfMDIxIiwicm90YXRpb24iOlstMC4wMDAxNTM0NTQ1NTg3NjgwMzE2MywwLDAuNDU3OTA5Mzc0NjE2ODM0NiwwLjg4ODk5ODg2NDUwNDE3OF0sInRyYW5zbGF0aW9uIjpbMTcuOTQyODEwMDU4NTkzNzUsMCwwXX0seyJuYW1lIjoiYl9SaWdodEZvb3QwMl8wMjIiLCJyb3RhdGlvbiI6WzAsMCwwLjU0NzI4ODI5NDkwOTAyNDMsMC44MzY5NDQxNTcxOTA2NTMzXSwidHJhbnNsYXRpb24iOlsxNS43Nzk5MzU4MzY3OTE5OTIsMCwwXX1dLCJzYW1wbGVycyI6W3sibWFnRmlsdGVyIjo5NzI5LCJtaW5GaWx0ZXIiOjk5ODd9XSwic2NlbmUiOjAsInNjZW5lcyI6W3sibm9kZXMiOlswXX1dLCJza2lucyI6W3siaW52ZXJzZUJpbmRNYXRyaWNlcyI6NCwiam9pbnRzIjpbMiwzLDQsNSw2LDcsOCw5LDEwLDExLDEyLDEzLDE0LDE1LDE2LDE3LDE4LDE5LDIwLDIxLDIyLDIzLDI0LDI1XSwic2tlbGV0b24iOjJ9XSwidGV4dHVyZXMiOlt7InNhbXBsZXIiOjAsInNvdXJjZSI6MH1dfSAgIOw8AgBCSU4AnZsDQJHbDEJnXLjB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AgAQAAIAAgACABAAAgACAAIADQACAAIABAAFAAcACgAKAAQAAgACAAoAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABQAEAAIAAgAKAAIAAgACAAUABAACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIAAgAQAAIAAgADAAIAAgACAAIAEAARAAIABAAFAAcACgAEAAoABwACAAoABAACAAIAAgANAAIAAgACABAAAgACAAIADQACAAIAAwACAAIAAgADAAIAAgACAAIAEAACAAIAAgAQABEAAgADAAIAAgACAAIAAgACAAIACgACAAIAAgAKAAQAAgACAAoAAgACAAIADwACAAIAAgAPAA4AAgACAA8AAgACAAIADwACAAIAAgAPAAIAAgACAA8AAgACAAIADwACAAIAAgAPAAIAAgACAA8AAgACAAIADwACAAIAAgAPAAIAAgACAA8AAgACAAIADwACAAIAAgAPAAIAAgACAA8AAgACAAIAAgAUAAIAAgACAA0AAgACAAIAEAAUAAIACgACAAIAAgAFAAQAAgACAAUABAACAAIABQAEAAIAAgAEAAUABwAKAAoAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABwACAAIAAgAHAAgAAgACAAcAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIAAgAUAAIAAgACAA0AAgACAAIAFAACAAIAAwACAAIAAgADAAIAAgACAAMAAgACAAIAAwACAAIAAgACABQAAgACAAMAAgACAAIAAgAQABQAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACABQAAgACAAIAEAAUAAIAAgAUAAIAAgAVABQAAgACAAIAFAACAAIABwACAAIAAgAIAAcAAgACAAcACAACAAIADwACAAIAAgAPAAIAAgACAA8AAgACAAIADwACAAIAAgAPAAIAAgACAA8AAgACAAIADwAOAAIAAgAPAAIAAgACAA8AAgACAAIADwACAAIAAgAPAAIAAgACAA8AAgACAAIAAwACAAIAAgAHAAQAAgACAAMAFAACAAIAAgAUAAIAAgACAA0AAgACAAIAFAACAAIABAAFAAcACgAHAAIAAgACAAcABAACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABQAEAAIAAgAFAAQAAgACAAcAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIAAgAUAAIAAgACABQAFQACAAMAAgACAAIABAAFAAcACgAHAAQAAgACAAQACgAHAAIAAgANAAIAAgACAA0AAgACAAIAFAACAAIAAwACAAIAAgACABQAAgACAAMAAgACAAIAAgAUABUAAgACABQAAgACAAIAAgACAAIABwACAAIAAgAHAAIAAgACAAcABAACAAIADwACAAIAAgAPAAIAAgACAA8ADgACAAIADwACAAIAAgAPAAIAAgACAA8AAgACAAIADwACAAIAAgAPAAIAAgACAA8AAgACAAIADwACAAIAAgAPAAIAAgACAA8AAgACAAIADwACAAIAAgAPAAIAAgACAA8AAgACAAIABwACAAIAAgAEAAUABwAKAAUABAACAAIABQAEAAIAAgAFAAQAAgACAAcAAgACAAIACgACAAIAAgAKAAIAAgACAAoAAgACAAIACgACAAIAAgAKAAQAAgACAAoAAgACAAIACgACAAIAAgALAAoAAgACAAoACwACAAIACgALAAIAAgAKAAIAAgACAAoAAgACAAIABwACAAIAAgAHAAQAAgACAAcAAgACAAIABwACAAIAAgAHAAIAAgACAAcAAgACAAIACgALAAIAAgAKAAIAAgACAAoABAACAAIACgAEAAIAAgAEAAoABwACAAoACwACAAIABwAIAAIAAgAEAAoABwACAAcABAACAAIABwAEAAIAAgAHAAIAAgACAAcACAACAAIABwACAAIAAgAHAAIAAgACAAcACAACAAIABwAIAAIAAgAIAAcAAgACAAcAAgACAAIAAwAUAAIAAgACABQAAgACAAIAFAACAAIAAgAUAAIAAgADAAIAAgACAAMAFAACAAIABAAKAAcAAgADAAIAAgACAAMAAgACAAIAAwACAAIAAgAEAAoABwACAAcABAACAAIABwAEAAIAAgADAAIAAgACAAMAAgACAAIAAwAQAAIAAgADAAIAAgACAAIAEAACAAIAAgAQAAIAAgACABAAAgACAAMAEAACAAIABAAKAAcAAgADAAIAAgACAAMAAgACAAIAAwACAAIAAgADAAIAAgACAAoABAACAAIACgAEAAIAAgAEAAoABwACAAMAAgACAAIAAwACAAIAAgACAAIAAgACAAIAAgACAAIAAwACAAIAAgACAAIAAgACAAIAAgACAAIAAwACAAIAAgACABQAFQACAAIAAgACAAIAAgAQAAIAAgACABAAEQACAAIAAgACAAIABQAEAAIAAgAFAAQAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAUABAACAAIABQAEAAIAAgAFAAQAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAUABAACAAIABQAEAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAFAAQAAgACAAUABAACAAIABQAEAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAFAAQAAgACAAUABAACAAIAAgANAAIAAgACAA0AAgACAA0ADgACAAIADQAOAAIAAgACAA0AAgACAA0ADgACAAIACgAEAAIAAgADABAAAgACAAMAEAACAAIAAwAQAAIAAgAKAAQAAgACAAoABAACAAIAAwACAAIAAgADAAIAAgACAAMAAgACAAIAAwACAAIAAgACAAIAAgACAAMAAgACAAIAAgANAAIAAgACABAAAgACAAIADQACAAIAAgACAAIAAgACABAAAgACAAIAEAACAAIAAgAQAAIAAgACABAAAgACAAIADQACAAIACgAEAAIAAgAKAAQAAgACAAoACwACAAIABAACAAIAAgAEAAIAAgACAAUABAACAAIABAACAAIAAgAEAAIAAgACAAUABAACAAIABgACAAIAAgAFAAQAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABQAEAAIAAgAFAAQAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABQAEAAIAAgAFAAQAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIAAgANAAIAAgACAA0AAgACAA0ADgACAAIAAgAQAAIAAgACABAAAgACABEAEAACAAIADQAOAAIAAgANAA4AAgACAA8ADgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABAACAAIAAgAEAAIAAgACAAQAAgACAAIAAgACAAIAAgACAAIAAgACAAIADQACAAIAAgANAAIAAgACAA0AAgACAAIADQACAAIAAgAQAAIAAgACABAAAgACABAAEQACAAIAAgAQAAIAAgACABAAAgACABEAEAACAAIAAgAQAAIAAgACABAAAgACABEAEAACAAIAAgAQAAIAAgACABAAAgACABEAEAACAAIAEQASAAIAAgARABIAAgACABEAEgACAAIAEQASAAIAAgARABIAAgACABEAEgACAAIAEQAQAAIAAgACABAAEQACABEAEgACAAIAEQASAAIAAgARABIAAgACABEAEgACAAIAEQASAAIAAgARABIAAgACABEAEgACAAIAEQAQAAIAAgARABAAAgACABEAAgACAAIAEAARAAIAAgARABAAAgACABEAAgACAAIAEQASAAIAAgARABIAAgACABEAEgACAAIACgALAAIAAgAKAAsAAgACAAsAAgACAAIACwAKAAIAAgAKAAsAAgACAAsAAgACAAIABwACAAIAAgAHAAIAAgACAAcAAgACAAIABwACAAIAAgAHAAIAAgACAAcAAgACAAIABAAKAAcAAgAKAAQAAgACAAoACwACAAIACgALAAIAAgAKAAsAAgACAAsAAgACAAIADQAOAAIAAgACAA0AAgACAA0ADgACAAIADQAOAAIAAgAPAA4AAgACAA0ADgACAAIAAgANAAIAAgACAA0AAgACAA0ADgACAAIADQAOAAIAAgANAA4AAgACAAIADQACAAIADwAOAAIAAgAPAA4AAgACAA8AAgACAAIADwAOAAIAAgAPAA4AAgACAA8AAgACAAIADwAOAAIAAgAPAA4AAgACAA8AAgACAAIADwAOAAIAAgAPAA4AAgACAA8AAgACAAIADwAOAAIAAgAPAA4AAgACAA8AAgACAAIAEQAQAAIAAgACABAAAgACAAIAEAARAAIAAwACAAIAAgACAAIAAgACAAIAAgACAAIAAgANAAIAAgACAA0AAgACAAIADQACAAIAAgANAAIAAgACAA0AAgACAAIADQACAAIAAgANAAIAAgACAAIAAgACAAIAEAACAAIABAACAAIAAgAEAAIAAgACAAQAAgACAAIAAgANAAIAAgACABAAAgACAAIADQACAAIABQAEAAIAAgAEAAIAAgACAAUABAACAAIABQAEAAIAAgAEAAIAAgACAAUABAACAAIACgALAAIAAgAKAAQAAgACAAoACwACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAFAAQAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIACgALAAIAAgAKAAQAAgACAAoACwACAAIACgACAAIAAgAKAAIAAgACAAoAAgACAAIAAwAQAAIAAgADAAIAAgACAAoABAACAAIADQAOAAIAAgACAA0AAgACAAIADQACAAIAEAARAAIAAgACABAAAgACABEAEAACAAIADwAOAAIAAgANAA4AAgACAA8ADgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABAACAAIAAgAEAAIAAgACAAQAAgACAAIAAgANAAIAAgACAAIAAgACAAIADQACAAIAEQAQAAIAAgACABAAAgACABAAEQACAAIAAwACAAIAAgADAAIAAgACAAIAAgACAAIAEQAQAAIAAgACABAAAgACABEAEAACAAIAEQAQAAIAAgACABAAAgACABEAEAACAAIAEQAQAAIAAgACABAAAgACABEAEAACAAIAEQASAAIAAgACABAAEQACABEAEgACAAIACgALAAIAAgAEAAoABwACAAoACwACAAIACwACAAIAAgAKAAsAAgACAAsACgACAAIADwAOAAIAAgANAA4AAgACAA8ADgACAAIADwAOAAIAAgANAA4AAgACAA8ADgACAAIADwAOAAIAAgANAA4AAgACAA8ADgACAAIADwAOAAIAAgANAA4AAgACAA0ADgACAAIADwACAAIAAgAPAA4AAgACAA8AAgACAAIADwACAAIAAgAPAA4AAgACAA8AAgACAAIADwACAAIAAgAPAA4AAgACAA8AAgACAAIAAgANAAIAAgANAA4AAgACAAIADQACAAIADQAOAAIAAgANAA4AAgACAAIADQACAAIABwAEAAIAAgADABQAAgACAAMAFAACAAIAAwAUAAIAAgAHAAQAAgACAAcABAACAAIAAgACAAIAAgADAAIAAgACAAMAAgACAAIAAwACAAIAAgADAAIAAgACAAIAAgACAAIABAACAAIAAgAHAAIAAgACAAcAAgACAAIABwACAAIAAgAEAAIAAgACAAQAAgACAAIAAgANAAIAAgACAA0AAgACAAIAFAACAAIAAgACAAIAAgACABQAAgACAAIAFAACAAIAAgAUAAIAAgACAA0AAgACAAIAFAACAAIABwAEAAIAAgAHAAgAAgACAAcABAACAAIABAACAAIAAgAFAAQAAgACAAQAAgACAAIABAACAAIAAgAFAAQAAgACAAQAAgACAAIABgACAAIAAgAGAAIAAgACAAUABAACAAIABAACAAIAAgAFAAQAAgACAAUABAACAAIABQAEAAIAAgAKAAIAAgACAAQAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABQAEAAIAAgAGAAIAAgACAAUABAACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABQAEAAIAAgAGAAIAAgACAAUABAACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIAAgANAAIAAgANAA4AAgACAAIADQACAAIAAgAUAAIAAgAVABQAAgACAAIAFAACAAIADQAOAAIAAgAPAA4AAgACAA0ADgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABAACAAIAAgAEAAIAAgACAAQAAgACAAIAAgACAAIAAgACAA0AAgACAAIAAgACAAIAAgANAAIAAgACAA0AAgACAAIADQACAAIAAgAUAAIAAgAUABUAAgACAAIAFAACAAIAAgAUAAIAAgAVABQAAgACAAIAFAACAAIAAgAUAAIAAgAVABQAAgACAAIAFAACAAIAAgAUAAIAAgAVABQAAgACAAIAFAACAAIAFQAWAAIAAgAVABYAAgACABUAFgACAAIAFQAUAAIAAgAVABYAAgACAAIAFAAVAAIAFQAWAAIAAgAVABYAAgACABUAFgACAAIAFQAWAAIAAgAVABYAAgACABUAFgACAAIAFQAUAAIAAgAVAAIAAgACABUAFAACAAIAFAAVAAIAAgAVAAIAAgACABUAFAACAAIAFQAWAAIAAgAVABYAAgACABUAFgACAAIABwAIAAIAAgAIAAIAAgACAAcACAACAAIACAAHAAIAAgAIAAIAAgACAAcACAACAAIABAAKAAcAAgAHAAgAAgACAAcABAACAAIABwAIAAIAAgAIAAIAAgACAAcACAACAAIADQAOAAIAAgAPAA4AAgACAA0ADgACAAIADQAOAAIAAgAPAA4AAgACAA0ADgACAAIADQAOAAIAAgAPAA4AAgACAA8ADgACAAIADwAOAAIAAgANAA4AAgACAA0ADgACAAIADwAOAAIAAgAPAAIAAgACAA8ADgACAAIADwAOAAIAAgAPAAIAAgACAA8ADgACAAIADwAOAAIAAgAPAAIAAgACAA8ADgACAAIADwAOAAIAAgAPAAIAAgACAA8ADgACAAIADwAOAAIAAgAPAAIAAgACAA8ADgACAAIAFQAUAAIAAgACABQAFQACAAIAFAACAAIAAwACAAIAAgACAAIAAgACAAIAAgACAAIAAgANAAIAAgACAA0AAgACAAIADQACAAIAAgANAAIAAgACAA0AAgACAAIADQACAAIAAgANAAIAAgACABQAAgACAAIAAgACAAIABAACAAIAAgAEAAIAAgACAAQAAgACAAIAAgANAAIAAgACAA0AAgACAAIAFAACAAIABQAEAAIAAgAFAAQAAgACAAQAAgACAAIABQAEAAIAAgAEAAIAAgACAAUABAACAAIABwAIAAIAAgAHAAgAAgACAAcABAACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAUABAACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABwAIAAIAAgAHAAgAAgACAAcABAACAAIABwACAAIAAgAHAAIAAgACAAcAAgACAAIAAwAUAAIAAgAHAAQAAgACAAMAAgACAAIADQAOAAIAAgACAA0AAgACAAIADQACAAIAFAAVAAIAAgAVABQAAgACAAIAFAACAAIADwAOAAIAAgAPAA4AAgACAA0ADgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABAACAAIAAgAEAAIAAgACAAQAAgACAAIAAgANAAIAAgACAA0AAgACAAIAAgACAAIAFQAUAAIAAgAUABUAAgACAAIAFAACAAIAAwACAAIAAgACAAIAAgACAAMAAgACAAIAFQAUAAIAAgAVABQAAgACAAIAFAACAAIAFQAUAAIAAgAVABQAAgACAAIAFAACAAIAFQAUAAIAAgAVABQAAgACAAIAFAACAAIAFQAWAAIAAgAVABYAAgACABUAFgACAAIABwAIAAIAAgAEAAoABwACAAcACAACAAIACAACAAIAAgAIAAcAAgACAAcACAACAAIADwAOAAIAAgAPAA4AAgACAA0ADgACAAIADwAOAAIAAgAPAA4AAgACAA0ADgACAAIADwAOAAIAAgAPAA4AAgACAA0ADgACAAIADQAOAAIAAgANAA4AAgACAA8ADgACAAIADwAOAAIAAgAPAA4AAgACAA0ADgACAAIADwACAAIAAgAPAAIAAgACAA8ADgACAAIADwACAAIAAgAPAAIAAgACAA8ADgACAAIADwACAAIAAgAPAAIAAgACAA8ADgACAAIAEQASAAIAAgARABIAAgACAAIAEAARAAIAAgAQABEAAgACABAAAgACABEAEgACAAIABAACAAIAAgADAAIAAgACAAMAAgACAAIAAwACAAIAAgAEAAIAAgACAAQAAgACAAIACgAEAAIAAgADAAIAAgACAAMAAgACAAIAAwACAAIAAgAEAAIAAgACAAoABAACAAIABwAEAAIAAgAHAAgAAgACAAcAAgACAAIABAACAAIAAgADAAIAAgACAAMAAgACAAIAAwACAAIAAgAHAAQAAgACAAQAAgACAAIABAACAAIAAgAEAAIAAgACAAMAAgACAAIAAwACAAIAAgADAAIAAgACAAQAAgACAAIACgACAAIAAgAKAAIAAgACAAoAAgACAAIACgACAAIAAgAKAAIAAgACAAoAAgACAAIABwAIAAIAAgAIAAcAAgACAAcAAgACAAIABwACAAIAAgAHAAIAAgACAAcACAACAAIABwAIAAIAAgAHAAgAAgACAAcAAgACAAIACgACAAIAAgAKAAsAAgACAAoACwACAAIACgALAAIAAgAKAAIAAgACAAoAAgACAAIACgACAAIAAgAKAAsAAgACAAsACgACAAIACwAKAAIAAgAKAAIAAgACAAoAAgACAAIABwACAAIAAgAHAAIAAgACAAgABwACAAIACAAHAAIAAgAHAAgAAgACAAcAAgACAAIACwAKAAIAAgAKAAsAAgACAAoACwACAAIACAAHAAIAAgAHAAgAAgACAAcACAACAAIACgALAAIAAgAKAAIAAgACAAsACgACAAIACgACAAIAAgAKAAsAAgACAAoAAgACAAIACgACAAIAAgAKAAIAAgACAAoAAgACAAIABAACAAIAAgAEAAIAAgACAAoAAgACAAIACgACAAIAAgAKAAIAAgACAAQAAgACAAIACgACAAIAAgAKAAQAAgACAAQAAgACAAIABAACAAIAAgAEAAIAAgACAAoAAgACAAIABAACAAIAAgAEAAIAAgACAAQAAgACAAIABAACAAIAAgAEAAIAAgACAAQAAgACAAIABAACAAIAAgAEAAIAAgACAAQAAgACAAIABAACAAIAAgAEAAIAAgACAAQAAgACAAIABwACAAIAAgAHAAIAAgACAAcACAACAAIABwAIAAIAAgAHAAIAAgACAAcAAgACAAIACgAEAAIAAgAKAAIAAgACAAoACwACAAIABAACAAIAAgAEAAIAAgACAAQAAgACAAIABAACAAIAAgAEAAIAAgACAAQAAgACAAIABAACAAIAAgAEAAIAAgACAAQAAgACAAIABAACAAIAAgAEAAIAAgACAAQAAgACAAIABAACAAIAAgAEAAIAAgACAAQAAgACAAIABAACAAIAAgAEAAIAAgACAAQAAgACAAIABAACAAIAAgAEAAIAAgACAAcABAACAAIABwAEAAIAAgAHAAIAAgACAAQAAgACAAIAEQASAAIAAgARABIAAgACABEAEAACAAIAEQAQAAIAAgARABAAAgACABEAEgACAAIAEQASAAIAAgARABIAAgACAAIAEAACAAIAAgAQAAIAAgARABAAAgACABEAEgACAAIAEQASAAIAAgARABIAAgACABEAEAACAAIAEQAQAAIAAgARABAAAgACABEAEgACAAIAFQAWAAIAAgAVABQAAgACABUAFAACAAIAFQAUAAIAAgAVABYAAgACABUAFgACAAIAFQAWAAIAAgAVABQAAgACABUAFAACAAIAFQAUAAIAAgAVABYAAgACABUAFgACAAIAFQAUAAIAAgAVABYAAgACABQAFQACAAIAEQAQAAIAAgAQABEAAgACABEAEgACAAIAFQAWAAIAAgACABQAAgACAAIAFAAVAAIAAgAUABUAAgAVABYAAgACABUAFgACAAIAFQAWAAIAAgAVABQAAgACAAIAFAACAAIAAgAUAAIAAgAVABYAAgACABUAFgACAAIAAgAUABUAAgAVABYAAgACABUAFgACAAIAAgANAAIAAgANAA4AAgACAA0ADgACAAIADQAOAAIAAgACAA0AAgACAAIADQACAAIAAgANAAIAAgANAA4AAgACAA0ADgACAAIAAgANAAIAAgANAA4AAgACAA0ADgACAAIAAgANAAIAAgANAA4AAgACAA0ADgACAAIABAACAAIAAgADAAIAAgACAAMAAgACAAIABAACAAIAAgADAAIAAgACAAMAAgACAAIAAwACAAIAAgADAAIAAgACAAQAAgACAAIABAACAAIAAgAEAAIAAgACAAMAAgACAAIAAwACAAIAAgADAAIAAgACAAIAEAACAAIAAgAQAAIAAgACAAIAAgACAAMAAgACAAIAAwACAAIAAgADABAAAgACAAIAEAACAAIAAgAQAAIAAgACABAAAgACAAMAAgACAAIAAgAQAAIAAgADAAIAAgACAAMAAgACAAIAAgAQAAIAAgADABAAAgACAAMAEAACAAIAAgAUAAIAAgADAAIAAgACAAMAAgACAAIAAwACAAIAAgAEAAIAAgACAAQAAgACAAIABAACAAIAAgADAAIAAgACAAMAAgACAAIAAwACAAIAAgACAAIAAgACAAIAFAACAAIAAgAUAAIAAgADAAIAAgACAAMAAgACAAIAAgAUAAIAAgADABQAAgACAAMAFAACAAIAAwACAAIAAgACABQAAgACAAIAFAACAAIAAgAUAAIAAgADABQAAgACAAMAAgACAAIABAACAAIAAgAHAAIAAgACAAUABAACAAIABQAEAAIAAgAFAAQAAgACAAQAAgACAAIABQAEAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAFAAQAAgACAAUABAACAAIABQAEAAIAAgAFAAQAAgACAAoAAgACAAIACgACAAIAAgAKAAIAAgACAAUABAACAAIABQAEAAIAAgAHAAIAAgACAAcAAgACAAIABwACAAIAAgAFAAQAAgACAAUABAACAAIABQAEAAIAAgAFAAQAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAUABAACAAIABgACAAIAAgAFAAQAAgACAAUABAACAAIABgACAAIAAgAFAAQAAgACAAUABAACAAIAEQASAAIAAgARAAIAAgACABIAEQACAAIAEQASAAIAAgARABIAAgACABMAEgACAAIAEQASAAIAAgASABEAAgACABIAEwACAAIAEwASAAIAAgASABMAAgACABMAAgACAAIAEQASAAIAAgARABIAAgACABMAEgACAAIAEgARAAIAAgARABIAAgACABIAEwACAAIAEQASAAIAAgARABIAAgACABIAEwACAAIAEQASAAIAAgARABIAAgACABIAEwACAAIAEwACAAIAAgATABIAAgACABMAAgACAAIAEwACAAIAAgATAAIAAgACABMAAgACAAIAEwASAAIAAgATAAIAAgACABMAAgACAAIAEwACAAIAAgASABMAAgACABMAAgACAAIAEgATAAIAAgASABMAAgACABMAAgACAAIAEgATAAIAAgASABMAAgACABMAAgACAAIACwACAAIAAgALAAIAAgACAAwACwACAAIACwACAAIAAgAMAAsAAgACAAsAAgACAAIACwACAAIAAgALAAIAAgACAAwACwACAAIACwACAAIAAgALAAIAAgACAAwACwACAAIACwACAAIAAgALAAIAAgACAAwACwACAAIACwACAAIAAgALAAoAAgACAAsAAgACAAIADAACAAIAAgAMAAsAAgACAAwAAgACAAIADAACAAIAAgAMAAIAAgACAAwAAgACAAIADAALAAIAAgAMAAsAAgACAAwAAgACAAIADAALAAIAAgAMAAsAAgACAAwAAgACAAIADAALAAIAAgAMAAsAAgACAAwAAgACAAIADAALAAIAAgAMAAsAAgACAAwAAgACAAIADAALAAIAAgAMAAsAAgACAAwAAgACAAIAEQASAAIAAgARABAAAgACABEAEgACAAIAEQACAAIAAgARABAAAgACABEAEgACAAIAEQASAAIAAgARABAAAgACABEAEgACAAIAEgARAAIAAgARAAIAAgACABEAEgACAAIAEQASAAIAAgAQABEAAgACABEAAgACAAIAEgATAAIAAgARABIAAgACABMAEgACAAIAEgATAAIAAgASABEAAgACABIAEwACAAIAEwASAAIAAgARABIAAgACABMAEgACAAIAEgATAAIAAgASABEAAgACABIAEwACAAIAEwASAAIAAgARABIAAgACABIAEwACAAIAEwACAAIAAgATABIAAgACABMAEgACAAIAEwACAAIAAgATAAIAAgACABMAAgACAAIAEwASAAIAAgATAAIAAgACABMAAgACAAIAEwACAAIAAgATAAIAAgACABMAAgACAAIAEwACAAIAAgASABMAAgACABMAEgACAAIAEwACAAIAAgASABMAAgACABMAAgACAAIACwACAAIAAgAKAAsAAgACAAsAAgACAAIADAALAAIAAgALAAIAAgACAAwACwACAAIACwACAAIAAgALAAoAAgACAAsAAgACAAIACwACAAIAAgALAAoAAgACAAsAAgACAAIACwACAAIAAgAKAAsAAgACAAsAAgACAAIADAALAAIAAgALAAIAAgACAAwACwACAAIADAALAAIAAgALAAIAAgACAAwACwACAAIADAACAAIAAgAMAAsAAgACAAwAAgACAAIADAALAAIAAgAMAAsAAgACAAwACwACAAIADAACAAIAAgAMAAIAAgACAAwAAgACAAIADAACAAIAAgAMAAIAAgACAAwAAgACAAIADAACAAIAAgAMAAsAAgACAAwAAgACAAIADAACAAIAAgAMAAsAAgACAAwAAgACAAIADAACAAIAAgAMAAsAAgACAAwAAgACAAIAFQAWAAIAAgAWABUAAgACABUAAgACAAIAFQAWAAIAAgAXABYAAgACABUAFgACAAIAFQAWAAIAAgAWABcAAgACABYAFQACAAIAFwAWAAIAAgAXAAIAAgACABYAFwACAAIAFQAWAAIAAgAXABYAAgACABUAFgACAAIAFgAVAAIAAgAWABcAAgACABUAFgACAAIAFQAWAAIAAgAWABcAAgACABUAFgACAAIAFQAWAAIAAgAWABcAAgACABUAFgACAAIAFwACAAIAAgAXAAIAAgACABcAFgACAAIAFwACAAIAAgAXAAIAAgACABcAAgACAAIAFwAWAAIAAgAXAAIAAgACABcAAgACAAIAFwACAAIAAgAXAAIAAgACABYAFwACAAIAFgAXAAIAAgAXAAIAAgACABYAFwACAAIAFgAXAAIAAgAXAAIAAgACABYAFwACAAIACAACAAIAAgAJAAgAAgACAAgAAgACAAIACAACAAIAAgAJAAgAAgACAAgAAgACAAIACAACAAIAAgAJAAgAAgACAAgAAgACAAIACAACAAIAAgAJAAgAAgACAAgAAgACAAIACAACAAIAAgAJAAgAAgACAAgAAgACAAIACAACAAIAAgAJAAgAAgACAAgAAgACAAIACQACAAIAAgAJAAIAAgACAAkACAACAAIACQACAAIAAgAJAAIAAgACAAkAAgACAAIACQAIAAIAAgAJAAIAAgACAAkACAACAAIACQAIAAIAAgAJAAIAAgACAAkACAACAAIACQAIAAIAAgAJAAIAAgACAAkACAACAAIACQAIAAIAAgAJAAIAAgACAAkACAACAAIACQAIAAIAAgAJAAIAAgACAAkACAACAAIAFQAWAAIAAgAVABYAAgACABUAFAACAAIAFQACAAIAAgAVABYAAgACABUAFAACAAIAFQAWAAIAAgAVABYAAgACABUAFAACAAIAFgAVAAIAAgAVABYAAgACABUAAgACAAIAFQAWAAIAAgAVAAIAAgACABQAFQACAAIAFgAXAAIAAgAXABYAAgACABUAFgACAAIAFgAXAAIAAgAWABcAAgACABYAFQACAAIAFwAWAAIAAgAXABYAAgACABUAFgACAAIAFgAXAAIAAgAWABcAAgACABYAFQACAAIAFwAWAAIAAgAWABcAAgACABUAFgACAAIAFwACAAIAAgAXABYAAgACABcAFgACAAIAFwACAAIAAgAXAAIAAgACABcAAgACAAIAFwAWAAIAAgAXAAIAAgACABcAAgACAAIAFwACAAIAAgAXAAIAAgACABcAAgACAAIAFwACAAIAAgAXABYAAgACABYAFwACAAIAFwACAAIAAgAXAAIAAgACABYAFwACAAIACAACAAIAAgAIAAIAAgACAAcACAACAAIACQAIAAIAAgAJAAgAAgACAAgAAgACAAIACAACAAIAAgAIAAIAAgACAAgABwACAAIACAACAAIAAgAIAAIAAgACAAgABwACAAIACAACAAIAAgAIAAIAAgACAAcACAACAAIACQAIAAIAAgAJAAgAAgACAAgAAgACAAIACQAIAAIAAgAJAAgAAgACAAgAAgACAAIACQACAAIAAgAJAAIAAgACAAkACAACAAIACQAIAAIAAgAJAAgAAgACAAkACAACAAIACQACAAIAAgAJAAIAAgACAAkAAgACAAIACQACAAIAAgAJAAIAAgACAAkAAgACAAIACQACAAIAAgAJAAIAAgACAAkACAACAAIACQACAAIAAgAJAAIAAgACAAkACAACAAIACQACAAIAAgAJAAIAAgACAAkACAACAAIACwACAAIAAgAMAAsAAgACAAwACwACAAIADAALAAIAAgALAAIAAgACAAsAAgACAAIACAACAAIAAgAIAAIAAgACAAkACAACAAIACQAIAAIAAgAJAAgAAgACAAgAAgACAAIACAACAAIAAgAIAAIAAgACAAkACAACAAIACQAIAAIAAgAJAAgAAgACAAgAAgACAAIACwACAAIAAgALAAIAAgACAAsACgACAAIACwAKAAIAAgAKAAsAAgACAAsAAgACAAIACwACAAIAAgAMAAsAAgACAAwACwACAAIADAALAAIAAgALAAIAAgACAAsAAgACAAIACwACAAIAAgAMAAsAAgACAAwACwACAAIADAALAAIAAgALAAIAAgACAAsAAgACAAIACAACAAIAAgAHAAgAAgACAAcACAACAAIABwAIAAIAAgAIAAIAAgACAAgAAgACAAIACAACAAIAAgAIAAcAAgACAAcACAACAAIABwAIAAIAAgAIAAIAAgACAAgAAgACAAIACwACAAIAAgALAAIAAgACAAoACwACAAIACgALAAIAAgAKAAsAAgACAAsAAgACAAIACwACAAIAAgALAAIAAgACAAoACwACAAIACgALAAIAAgALAAoAAgACAAsAAgACAAIACAACAAIAAgAHAAgAAgACAAgABwACAAIACAAHAAIAAgAIAAIAAgACAAgAAgACAAIACAACAAIAAgAIAAIAAgACAAkACAACAAIACQAIAAIAAgAJAAgAAgACAAgAAgACAAIADAALAAIAAgALAAIAAgACAAsAAgACAAIACwAKAAIAAgALAAIAAgACAAsAAgACAAIACAAHAAIAAgAIAAIAAgACAAgAAgACAAIACAAHAAIAAgAIAAIAAgACAAgAAgACAAIAFQAWAAIAAgAVABYAAgACABUAFgACAAIAFQAWAAIAAgAVABYAAgACABUAFgACAAIAEQASAAIAAgARABIAAgACABEAEgACAAIAEQASAAIAAgARABIAAgACABEAEgACAAIAEQASAAIAAgARABIAAgACABEAEgACAAIAEQASAAIAAgARABIAAgACABEAEgACAAIAFQAWAAIAAgAVABYAAgACABUAFgACAAIAFQAWAAIAAgAVABYAAgACABUAFgACAAIAFQAWAAIAAgAVABYAAgACABUAFAACAAIAFQAWAAIAAgAVABYAAgACABUAFgACAAIAEQASAAIAAgARABAAAgACABEAEgACAAIAEQASAAIAAgARABIAAgACABEAEgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIAmpkZP83MzD4AAAAAAAAAANo/Mz+amRk+AmcZPgAAAAAK12M/rkfhPQAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/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AACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAzM3M/zcxMPQAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAzM3M/zcxMPQAAAAAAAAAAAACAPwAAAAAAAAAAAAAAADMzcz/NzEw9AAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAMzNzP83MTD0AAAAAAAAAADMzcz/NzEw9AAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAMzNzP83MTD0AAAAAAAAAADMzcz/NzEw9AAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAACtdjP65H4T0AAAAAAAAAAArXYz+uR+E9AAAAAAAAAAAAAAA/AAAAPwAAAAAAAAAAAABAPwAAgD4AAAAAAAAAAAAAAD8AAAA/AAAAAAAAAACZmRk/zszMPgAAAAAAAAAAAAAAPwAAAD8AAAAAAAAAAAAAAD8AAAA/AAAAAAAAAAAAAAA/AAAAPwAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/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AAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAAAAPwAAAD8AAAAAAAAAAGZmZj/NzMw9AAAAAAAAAABmZmY/zczMPQAAAAAAAAAAAABAPwAAgD4AAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAwvUoP3sUrj4AAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAFyPQj+PwnU+AAAAAAAAAADC9Sg/exSuPgAAAAAAAAAAwvUoP3sUrj4AAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAEA/AACAPgAAAAAAAAAAAABAPwAAgD4AAAAAAAAAAAAAgD8AAAAAAAAAAAAAAADC9Sg/exSuPgAAAAAAAAAAAAAAPwAAAD8AAAAAAAAAAGZmZj/NzMw9AAAAAAAAAABmZmY/zczMPQAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAQD8AAIA+AAAAAAAAAAAAAAA/AAAAPwAAAAAAAAAAAAAAPwAAAD8AAAAAAAAAAGZmZj/NzMw9AAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAzM3M/zcxMPQAAAAAAAAAAMzNzP83MTD0AAAAAAAAAADMzcz/NzEw9AAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAMzNzP83MTD0AAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAADMzcz/NzEw9AAAAAAAAAAAzM3M/zcxMPQAAAAAAAAAAMzNzP83MTD0AAAAAAAAAADMzcz/NzEw9AAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAzM3M/zcxMPQAAAAAAAAAAMzNzP83MTD0AAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAADMzcz/NzEw9AAAAAAAAAAAzM3M/zcxMPQAAAAAAAAAAMzNzP83MTD0AAAAAAAAAADMzcz/NzEw9AAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAA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Z8ECPhwLMr87wQK+GQsyP2nBAj4cCzK/PcECvhkLMj9pwQI+HAsyvz3BAr4ZCzI/acECPhwLMr89wQK+GQsyP2nBAj4cCzK/PcECvhkLMj9mwQI+HAsyvzrBAr4ZCzI/ZsECPhwLMr86wQK+GQsyP2bBAj4cCzK/OsECvhkLMj9pwQI+HAsyvz3BAr4ZCzI/Z8ECPhwLMr87wQK+GQsyP2bBAj4cCzK/OsECvhkLMj9pwQI+HAsyvz3BAr4ZCzI/aMECPhwLMr88wQK+GQsyP2bBAj4cCzK/OsECvhkLMj9owQI+HAsyvzzBAr4ZCzI/acECPhwLMr89wQK+GQsyP2nBAj4cCzK/PcECvhkLMj9pwQI+HAsyvz3BAr4ZCzI/acECPhwLMr89wQK+GQsyP2nBAj4cCzK/PcECvhkLMj9pwQI+HAsyvz3BAr4ZCzI/Z8ECPhwLMr87wQK+GQsyP2nBAj4cCzK/PcECvhkLMj9pwQI+HAsyvz3BAr4ZCzI/acECPhwLMr89wQK+GQsyP2nBAj4cCzK/PcECvhkLMj9pwQI+HAsyvz3BAr4ZCzI/acECPhwLMr89wQK+GQsyP2nBAj4cCzK/PcECvhkLMj9pwQI+HAsyvz3BAr4ZCzI/acECPhwLMr89wQK+GQsyP2bBAj4cCzK/OsECvhkLMj9pwQI+HAsyvz3BAr4ZCzI/acECPhwLMr89wQK+GQsyP2nBAj4cCzK/PcECvhkLMj9mwQI+HAsyvzrBAr4ZCzI/acECPhwLMr89wQK+GQsyP2bBAj4cCzK/OsECvhkLMj9nwQI+HAsyvzvBAr4ZCzI/ZsECPhwLMr86wQK+GQsyP2bBAj4cCzK/OsECvhkLMj9pwQI+HAsyvz3BAr4ZCzI/acECPhwLMr89wQK+GQsyP2bBAj4cCzK/OsECvhkLMj9owQI+HAsyvzzBAr4ZCzI/ZsECPhwLMr86wQK+GQsyP2bBAj4cCzK/OsECvhkLMj9mwQI+HAsyvzrBAr4ZCzI/ZsECPhwLMr86wQK+GQsyP2nBAj4cCzK/PcECvhkLMj9pwQI+HAsyvz3BAr4ZCzI/acECPhwLMr89wQK+GQsyP2nBAj4cCzK/PcECvhkLMj9nwQI+HAsyvzvBAr4ZCzI/Z8ECPhwLMr87wQK+GQsyP2nBAj4cCzK/PcECvhkLMj9nwQI+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#.glb"; diff --git a/tests/src/loader/GLTFLoader.test.ts b/tests/src/loader/GLTFLoader.test.ts index cbcfd3b67c..7886151128 100644 --- a/tests/src/loader/GLTFLoader.test.ts +++ b/tests/src/loader/GLTFLoader.test.ts @@ -306,7 +306,7 @@ class GLTFCustomBufferParser extends GLTFParser { 0, 0, 0, 0, 7, 9, 255, 196, 0, 20, 17, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 218, 0, 12, 3, 1, 0, 2, 17, 3, 17, 0, 63, 0, 157, 0, 6, 42, 155, 255, 217 ]); - context.buffers = [buffer]; + context._buffers = [buffer]; } } @@ -420,10 +420,5 @@ describe("glTF Loader test", function () { const renderer = entities[1].getComponent(SkinnedMeshRenderer); expect(renderer).to.exist; expect(renderer.blendShapeWeights).to.deep.include([1, 1]); - - glTFResource.destroy(); - expect(glTFResource.materials).to.be.null; - expect(glTFResource.textures).to.be.null; - expect(glTFResource.entities).to.be.null; }); }); diff --git a/tests/src/loader/KTX2Loader.test.ts b/tests/src/loader/KTX2Loader.test.ts index 0e5b1a375d..41a9eb1880 100644 --- a/tests/src/loader/KTX2Loader.test.ts +++ b/tests/src/loader/KTX2Loader.test.ts @@ -100,7 +100,7 @@ describe("ktx2 Loader test", function () { expect(texture2d.width).to.be.equal(32); expect(texture2d.height).to.be.equal(32); expect(texture2d.mipmapCount).to.be.equal(6); - expect(texture2d.format).to.be.equal(TextureFormat.DXT1); + expect(texture2d.format).to.be.equal(TextureFormat.BC1); }); }); diff --git a/tests/src/shader-lab/ShaderLab.test.ts b/tests/src/shader-lab/ShaderLab.test.ts new file mode 100644 index 0000000000..72eca4149e --- /dev/null +++ b/tests/src/shader-lab/ShaderLab.test.ts @@ -0,0 +1,45 @@ +import { ShaderLab } from "@galacean/engine-shader-lab"; +import { Shader } from "@galacean/engine-core"; +import { WebGLEngine } from "@galacean/engine-rhi-webgl"; + +import fs from "fs"; +import path from "path"; +import chai, { expect } from "chai"; +import spies from "chai-spies"; + +chai.use(spies); +const demoShader = fs.readFileSync(path.join(__dirname, "demo.shader")).toString(); + +const shaderLab = new ShaderLab(); +const canvas = document.createElement("canvas"); + +describe("ShaderLab", () => { + it("create shaderlab", async () => { + expect(shaderLab).not.be.null; + }); + + it("shader parse result", () => { + const shader = shaderLab.parseShader(demoShader); + expect(shader.name).to.equal("Water"); + const subShader = shader.subShaders[0]; + const pass = subShader.passes[0]; + expect(pass.name).equal("default"); + }); +}); + +describe("engine shader", () => { + let engine: WebGLEngine; + + before(async () => { + engine = await WebGLEngine.create({ canvas, shaderLab }); + }); + + it("engine init", () => { + expect(engine).not.be.null; + }); + + it("shader create", () => { + const shader = Shader.create(demoShader); + expect(shader).not.be.null; + }); +}); diff --git a/tests/src/shader-lab/demo.shader b/tests/src/shader-lab/demo.shader new file mode 100644 index 0000000000..22ef1ceea0 --- /dev/null +++ b/tests/src/shader-lab/demo.shader @@ -0,0 +1,54 @@ +Shader "Water" { + SubShader { + + Pass "default" { + + struct a2v { + vec4 POSITION; + vec2 TEXCOORD_0; + } + + struct v2f { + vec2 v_uv; + vec3 v_position; + } + + mat4 renderer_MVPMat; + mat4 renderer_MVMat; + + sampler2D material_BaseTexture; + vec4 u_color; + vec4 u_fogColor; + float u_fogDensity; + + VertexShader = vert; + FragmentShader = frag; + + vec4 linearToGamma(vec4 linearIn) { + return vec4(pow(linearIn.rgb, vec3(1.0 / 2.2)), linearIn.a); + } + + v2f vert(a2v v) { + v2f o; + + o.v_uv = v.TEXCOORD_0; + vec4 tmp = renderer_MVMat * POSITION; + o.v_position = tmp.xyz; + gl_Position = renderer_MVPMat * v.POSITION; + return o; + } + + void frag(v2f i) { + vec4 color = texture2D(material_BaseTexture, i.v_uv) * u_color; + float fogDistance = length(i.v_position); + float fogAmount = 1.0 - exp2(-u_fogDensity * u_fogDensity * fogDistance * fogDistance * 1.442695); + fogAmount = clamp(fogAmount, 0.0, 1.0); + gl_FragColor = mix(color, u_fogColor, fogAmount); + + #ifndef ENGINE_IS_COLORSPACE_GAMMA + gl_FragColor = linearToGamma(gl_FragColor); + #endif + } + } + } +} \ No newline at end of file