diff --git a/packages/core/src/2d/atlas/types.ts b/packages/core/src/2d/atlas/types.ts index 16733f5418..1b346e233e 100644 --- a/packages/core/src/2d/atlas/types.ts +++ b/packages/core/src/2d/atlas/types.ts @@ -43,4 +43,6 @@ export interface AtlasSprite { pivot: { x: number; y: number }; border: { x: number; y: number; z: number; w: number }; pixelsPerUnit: number; + width: number; + height: number; } diff --git a/packages/core/src/2d/sprite/SpriteMask.ts b/packages/core/src/2d/sprite/SpriteMask.ts index ef19baec8e..9dfc034569 100644 --- a/packages/core/src/2d/sprite/SpriteMask.ts +++ b/packages/core/src/2d/sprite/SpriteMask.ts @@ -27,6 +27,7 @@ export class SpriteMask extends Renderer { _maskElement: RenderElement; /** @internal */ + @ignoreClone _verticesData: VertexData2D; @ignoreClone diff --git a/packages/core/src/2d/text/SubFont.ts b/packages/core/src/2d/text/SubFont.ts index 62f8078b93..cf6ea02628 100644 --- a/packages/core/src/2d/text/SubFont.ts +++ b/packages/core/src/2d/text/SubFont.ts @@ -94,7 +94,7 @@ export class SubFont { const { _engine: engine } = this; const fontAtlas = new FontAtlas(engine); const texture = new Texture2D(engine, 256, 256, TextureFormat.R8G8B8A8, false); - texture.filterMode = TextureFilterMode.Point; + texture.filterMode = TextureFilterMode.Bilinear; fontAtlas.texture = texture; fontAtlas.isGCIgnored = texture.isGCIgnored = true; this._fontAtlases.push(fontAtlas); diff --git a/packages/core/src/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts index 6c77c0760c..75eb073922 100644 --- a/packages/core/src/2d/text/TextRenderer.ts +++ b/packages/core/src/2d/text/TextRenderer.ts @@ -265,6 +265,16 @@ export class TextRenderer extends Renderer { * The bounding volume of the TextRenderer. */ override get bounds(): BoundingBox { + if (this._isTextNoVisible()) { + if (this._isContainDirtyFlag(DirtyFlag.WorldBounds)) { + const localBounds = this._localBounds; + localBounds.min.set(0, 0, 0); + localBounds.max.set(0, 0, 0); + this._updateBounds(this._bounds); + this._setDirtyFlagFalse(DirtyFlag.WorldBounds); + } + return this._bounds; + } this._isContainDirtyFlag(DirtyFlag.SubFont) && this._resetSubFont(); this._isContainDirtyFlag(DirtyFlag.LocalPositionBounds) && this._updateLocalData(); this._isContainDirtyFlag(DirtyFlag.WorldPosition) && this._updatePosition(); @@ -359,11 +369,7 @@ export class TextRenderer extends Renderer { * @internal */ protected override _render(context: RenderContext): void { - if ( - this._text === "" || - (this.enableWrapping && this.width <= 0) || - (this.overflowMode === OverflowMode.Truncate && this.height <= 0) - ) { + if (this._isTextNoVisible()) { return; } @@ -481,8 +487,8 @@ export class TextRenderer extends Renderer { } private _updateLocalData(): void { - const { color, _charRenderDatas: charRenderDatas, _subFont: charFont } = this; const { min, max } = this._localBounds; + const { color, _charRenderDatas: charRenderDatas, _subFont: charFont } = this; const textMetrics = this.enableWrapping ? TextUtils.measureTextWithWrap(this) : TextUtils.measureTextWithoutWrap(this); @@ -555,7 +561,7 @@ export class TextRenderer extends Renderer { const left = startX * pixelsPerUnitReciprocal; const right = (startX + w) * pixelsPerUnitReciprocal; const top = (startY + ascent) * pixelsPerUnitReciprocal; - const bottom = (startY - descent + 1) * pixelsPerUnitReciprocal; + const bottom = (startY - descent) * pixelsPerUnitReciprocal; localPositions.set(left, top, right, bottom); i === firstLine && (maxY = Math.max(maxY, top)); minY = Math.min(minY, bottom); @@ -601,6 +607,15 @@ export class TextRenderer extends Renderer { super._onTransformChanged(bit); this._setDirtyFlagTrue(DirtyFlag.WorldPosition | DirtyFlag.WorldBounds); } + + private _isTextNoVisible(): boolean { + return ( + this._text === "" || + this._fontSize === 0 || + (this.enableWrapping && this.width <= 0) || + (this.overflowMode === OverflowMode.Truncate && this.height <= 0) + ); + } } enum DirtyFlag { diff --git a/packages/core/src/2d/text/TextUtils.ts b/packages/core/src/2d/text/TextUtils.ts index 1c01b78009..8cf760d7d0 100644 --- a/packages/core/src/2d/text/TextUtils.ts +++ b/packages/core/src/2d/text/TextUtils.ts @@ -23,10 +23,14 @@ export class TextUtils { "emoji", "fangsong" ]; + + // _heightMultiplier used to measure the height of text, but in miniprogram performance is different from h5. + // so can set _heightMultiplier to adapt miniprogram, the larger the value, the worse the performance. + /** @internal */ + static _heightMultiplier: number = 2; /** These characters are all tall to help calculate the height required for text. */ private static _measureString: string = "|ÉqÅ"; private static _measureBaseline: string = "M"; - private static _heightMultiplier: number = 2; private static _baselineMultiplier: number = 1.4; private static _fontSizeInfoCache: Record = {}; private static _textContext: TextContext = null; diff --git a/packages/core/src/RenderPipeline/Basic2DBatcher.ts b/packages/core/src/RenderPipeline/Basic2DBatcher.ts index eb04bf272d..f5cdb073f4 100644 --- a/packages/core/src/RenderPipeline/Basic2DBatcher.ts +++ b/packages/core/src/RenderPipeline/Basic2DBatcher.ts @@ -1,6 +1,15 @@ import { Camera } from "../Camera"; import { Engine } from "../Engine"; -import { Buffer, BufferBindFlag, BufferUsage, IndexFormat, MeshTopology, SubMesh, VertexElement } from "../graphic"; +import { + Buffer, + BufferBindFlag, + BufferUsage, + IndexFormat, + MeshTopology, + SetDataOptions, + SubMesh, + VertexElement +} from "../graphic"; import { BufferMesh } from "../mesh"; import { ShaderTagKey } from "../shader/ShaderTagKey"; import { ClassPool } from "./ClassPool"; @@ -221,8 +230,10 @@ export abstract class Basic2DBatcher { mesh.addSubMesh(this._getSubMeshFromPool(vertexStartIndex, vertexCount)); batchedQueue[curMeshIndex] = preElement; - this._vertexBuffers[_flushId].setData(vertices, 0, 0, vertexIndex); - this._indiceBuffers[_flushId].setData(indices, 0, 0, indiceIndex); + // Set data option use Discard, or will resulted in performance slowdown when open antialias and cross-rendering of 3D and 2D elements. + // Device: iphone X(16.7.2)、iphone 15 pro max(17.1.1)、iphone XR(17.1.2) etc. + this._vertexBuffers[_flushId].setData(vertices, 0, 0, vertexIndex, SetDataOptions.Discard); + this._indiceBuffers[_flushId].setData(indices, 0, 0, indiceIndex, SetDataOptions.Discard); } private _getSubMeshFromPool(start: number, count: number): SubMesh { diff --git a/packages/core/src/RenderPipeline/RenderQueue.ts b/packages/core/src/RenderPipeline/RenderQueue.ts index bff83c357e..6e194574c3 100644 --- a/packages/core/src/RenderPipeline/RenderQueue.ts +++ b/packages/core/src/RenderPipeline/RenderQueue.ts @@ -16,22 +16,44 @@ export class RenderQueue { * @internal */ static _compareFromNearToFar(a: RenderElement, b: RenderElement): number { - return ( - a.data.component.priority - b.data.component.priority || - a.data.material._priority - b.data.material._priority || - a.data.component._distanceForSort - b.data.component._distanceForSort - ); + const dataA = a.data; + const dataB = b.data; + const componentA = dataA.component; + const componentB = dataB.component; + const priorityOrder = componentA.priority - componentB.priority; + if (priorityOrder !== 0) { + return priorityOrder; + } + // make suer from the same renderer. + if (componentA.instanceId === componentB.instanceId) { + return ( + dataA.material._priority - dataB.material._priority || componentA._distanceForSort - componentB._distanceForSort + ); + } else { + return componentA._distanceForSort - componentB._distanceForSort; + } } /** * @internal */ static _compareFromFarToNear(a: RenderElement, b: RenderElement): number { - return ( - a.data.component.priority - b.data.component.priority || - a.data.material._priority - b.data.material._priority || - b.data.component._distanceForSort - a.data.component._distanceForSort - ); + const dataA = a.data; + const dataB = b.data; + const componentA = dataA.component; + const componentB = dataB.component; + const priorityOrder = componentA.priority - componentB.priority; + if (priorityOrder !== 0) { + return priorityOrder; + } + // make suer from the same renderer. + if (componentA.instanceId === componentB.instanceId) { + return ( + dataA.material._priority - dataB.material._priority || componentB._distanceForSort - componentA._distanceForSort + ); + } else { + return componentB._distanceForSort - componentA._distanceForSort; + } } readonly elements: RenderElement[] = []; diff --git a/packages/core/src/mesh/PrimitiveMesh.ts b/packages/core/src/mesh/PrimitiveMesh.ts index ebe6c502a5..3d92a78a83 100644 --- a/packages/core/src/mesh/PrimitiveMesh.ts +++ b/packages/core/src/mesh/PrimitiveMesh.ts @@ -6,13 +6,13 @@ import { Buffer } from "../graphic/Buffer"; import { ModelMesh } from "./ModelMesh"; import { CapsuleRestoreInfo, - SubdivisionSurfaceSphereRestoreInfo, ConeRestoreInfo, CuboidRestoreInfo, CylinderRestoreInfo, PlaneRestoreInfo, PrimitiveMeshRestorer, SphereRestoreInfo, + SubdivisionSurfaceSphereRestoreInfo, TorusRestoreInfo } from "./PrimitiveMeshRestorer"; import { VertexAttribute } from "./enums/VertexAttribute"; @@ -371,18 +371,35 @@ export class PrimitiveMesh { for (let i = 0; i < cellsCount; i++) { const idx = 4 * i; - indices[offset] = cells[idx]; - indices[offset + 1] = cells[idx + 1]; - indices[offset + 2] = cells[idx + 2]; + let indexA = cells[idx]; + let indexB = cells[idx + 1]; + let indexC = cells[idx + 2]; + let indexD = cells[idx + 3]; + + // Handle seam by replacing vertex index to seam vertex index if necessary + const floatIndexA = 8 * indexA; + const floatIndexB = 8 * indexB; + const floatIndexC = 8 * indexC; + const floatIndexD = 8 * indexD; + + // If center Z is negative + if (vertices[floatIndexA + 2] + vertices[floatIndexB + 2] + vertices[floatIndexC + 2] < 0) { + vertices[floatIndexA + 6] === 0 && (indexA = seamVertices[indexA]); + vertices[floatIndexB + 6] === 0 && (indexB = seamVertices[indexB]); + vertices[floatIndexC + 6] === 0 && (indexC = seamVertices[indexC]); + vertices[floatIndexD + 6] === 0 && (indexD = seamVertices[indexD]); + } + + indices[offset] = indexA; + indices[offset + 1] = indexB; + indices[offset + 2] = indexC; - this._replaceSeamUV(indices, vertices, offset, seamVertices); this._generateAndReplacePoleUV(indices, vertices, offset, poleOffset); - indices[offset + 3] = cells[idx]; - indices[offset + 4] = cells[idx + 2]; - indices[offset + 5] = cells[idx + 3]; + indices[offset + 3] = indexA; + indices[offset + 4] = indexC; + indices[offset + 5] = indexD; - this._replaceSeamUV(indices, vertices, offset + 3, seamVertices); this._generateAndReplacePoleUV(indices, vertices, offset + 3, poleOffset); offset += 6; @@ -604,43 +621,6 @@ export class PrimitiveMesh { } } - /** - * Duplicate vertices whose uv normal is flipped and adjust their UV coordinates. - */ - private static _replaceSeamUV( - indices: Uint16Array | Uint32Array, - vertices: Float32Array, - offset: number, - seamVertices: Record - ) { - const vertexA = 8 * indices[offset]; - const vertexB = 8 * indices[offset + 1]; - const vertexC = 8 * indices[offset + 2]; - - const vertexAU = vertices[vertexA + 6]; - const vertexAV = vertices[vertexA + 7]; - - const vertexBU = vertices[vertexB + 6]; - const vertexBV = vertices[vertexB + 7]; - - const vertexCU = vertices[vertexC + 6]; - const vertexCV = vertices[vertexC + 7]; - - const z = (vertexBU - vertexAU) * (vertexCV - vertexAV) - (vertexBV - vertexAV) * (vertexCU - vertexAU); - - if (z > 0) { - if (vertexAU === 0) { - indices[offset] = seamVertices[indices[offset]]; - } - if (vertexBU === 0) { - indices[offset + 1] = seamVertices[indices[offset + 1]]; - } - if (vertexCU === 0) { - indices[offset + 2] = seamVertices[indices[offset + 2]]; - } - } - } - /** * Duplicate vertices at the poles and adjust their UV coordinates. */ diff --git a/packages/loader/src/SpriteAtlasLoader.ts b/packages/loader/src/SpriteAtlasLoader.ts index 001d9784c6..7359fbd25f 100644 --- a/packages/loader/src/SpriteAtlasLoader.ts +++ b/packages/loader/src/SpriteAtlasLoader.ts @@ -84,7 +84,7 @@ class SpriteAtlasLoader extends Loader { private _makeSprite(engine: Engine, config: AtlasSprite, texture?: Texture2D): Sprite { // Generate a SpriteAtlas object. - const { region, atlasRegionOffset, atlasRegion, pivot, border } = config; + const { region, atlasRegionOffset, atlasRegion, pivot, border, width, height } = config; const sprite = new Sprite( engine, texture, @@ -103,6 +103,8 @@ class SpriteAtlasLoader extends Loader { } config.atlasRotated && (sprite.atlasRotated = true); } + isNaN(width) || (sprite.width = width); + isNaN(height) || (sprite.height = height); return sprite; } } diff --git a/packages/loader/src/SpriteLoader.ts b/packages/loader/src/SpriteLoader.ts index 79b0488e89..f1aae8e423 100644 --- a/packages/loader/src/SpriteLoader.ts +++ b/packages/loader/src/SpriteLoader.ts @@ -41,12 +41,20 @@ class SpriteLoader extends Loader { // @ts-ignore .getResourceByRef(data.texture) .then((texture: Texture2D) => { - return new Sprite(resourceManager.engine, texture, data.region, data.pivot, data.border); + const sprite = new Sprite(resourceManager.engine, texture, data.region, data.pivot, data.border); + const { width, height } = data; + isNaN(width) || (sprite.width = width); + isNaN(height) || (sprite.height = height); + return sprite; }) ); } else { return new AssetPromise((resolve) => { - resolve(new Sprite(resourceManager.engine, null, data.region, data.pivot, data.border)); + const sprite = new Sprite(resourceManager.engine, null, data.region, data.pivot, data.border); + const { width, height } = data; + isNaN(width) || (sprite.width = width); + isNaN(height) || (sprite.height = height); + resolve(sprite); }); } } diff --git a/tests/src/core/2d/text/TextRenderer.test.ts b/tests/src/core/2d/text/TextRenderer.test.ts index 484b4a710b..ddbb1d7607 100644 --- a/tests/src/core/2d/text/TextRenderer.test.ts +++ b/tests/src/core/2d/text/TextRenderer.test.ts @@ -243,7 +243,7 @@ describe("TextRenderer", () => { textRenderer.verticalAlignment = TextVerticalAlignment.Top; textRenderer.horizontalAlignment = TextHorizontalAlignment.Left; BoundingBox.transform( - new BoundingBox(new Vector3(-1.5, 1.28, 0), new Vector3(1.39, 1.5, 0)), + new BoundingBox(new Vector3(-1.5, 1.27, 0), new Vector3(1.39, 1.5, 0)), textRendererEntity.transform.worldMatrix, box ); diff --git a/tests/src/core/2d/text/TextUtils.test.ts b/tests/src/core/2d/text/TextUtils.test.ts index ff62826bd5..1afa4f06ba 100644 --- a/tests/src/core/2d/text/TextUtils.test.ts +++ b/tests/src/core/2d/text/TextUtils.test.ts @@ -122,25 +122,9 @@ describe("TextUtils", () => { textRendererTruncate.height = 1; textRendererTruncate.enableWrapping = true; - // Test that measureTextWithWrap works correctly, while set overflow mode to truncate. - textRendererTruncate.text = ""; - let result = TextUtils.measureTextWithWrap(textRendererTruncate); - expect(result.width).to.be.equal(0); - expect(result.height).to.be.equal(100); - expect(result.lines).to.be.deep.equal([""]); - expect(result.lineWidths).to.be.deep.equal([0]); - expect(result.lineHeight).to.be.deep.equal(27); - - textRendererTruncate.text = undefined; - result = TextUtils.measureTextWithWrap(textRendererTruncate); - expect(result.width).to.be.equal(0); - expect(result.height).to.be.equal(100); - expect(result.lines).to.be.deep.equal([""]); - expect(result.lineWidths).to.be.deep.equal([0]); - expect(result.lineHeight).to.be.deep.equal(27); - textRendererTruncate.text = text1; - result = TextUtils.measureTextWithWrap(textRendererTruncate); + textRendererTruncate.bounds; + let result = TextUtils.measureTextWithWrap(textRendererTruncate); expect(result.width).to.be.equal(24); expect(result.height).to.be.equal(100); expect(result.lines).to.be.deep.equal(["趚", "今", "天", "天", "气", "很", "好", ",", "阳", "光", "明", "媚", "。", "我", "在", "公", "园", "里", "漫", "步", "。"]); @@ -171,23 +155,8 @@ describe("TextUtils", () => { textRendererOverflow.height = 1; textRendererOverflow.enableWrapping = true; - textRendererOverflow.text = ""; - result = TextUtils.measureTextWithWrap(textRendererOverflow); - expect(result.width).to.be.equal(0); - expect(result.height).to.be.equal(27); - expect(result.lines).to.be.deep.equal([""]); - expect(result.lineWidths).to.be.deep.equal([0]); - expect(result.lineHeight).to.be.deep.equal(27); - - textRendererOverflow.text = undefined; - result = TextUtils.measureTextWithWrap(textRendererOverflow); - expect(result.width).to.be.equal(0); - expect(result.height).to.be.equal(27); - expect(result.lines).to.be.deep.equal([""]); - expect(result.lineWidths).to.be.deep.equal([0]); - expect(result.lineHeight).to.be.deep.equal(27); - textRendererOverflow.text = text1; + textRendererOverflow.bounds; result = TextUtils.measureTextWithWrap(textRendererOverflow); expect(result.width).to.be.equal(24); expect(result.height).to.be.equal(567); @@ -216,11 +185,13 @@ describe("TextUtils", () => { wrap1TextRenderer.width = 5; wrap1TextRenderer.fontSize = 60; wrap1TextRenderer.text = "测试"; + wrap1TextRenderer.bounds; const text1Metrics = TextUtils.measureTextWithWrap(wrap1TextRenderer); wrap2TextRenderer.enableWrapping = true; wrap2TextRenderer.width = 5; wrap2TextRenderer.fontSize = 60; wrap2TextRenderer.text = "测试。"; + wrap2TextRenderer.bounds; const text2Metrics = TextUtils.measureTextWithWrap(wrap2TextRenderer); expect(text1Metrics.lineMaxSizes[0].size).to.be.equal(text2Metrics.lineMaxSizes[0].size); }); @@ -232,25 +203,8 @@ describe("TextUtils", () => { textRendererTruncate.height = 1; textRendererTruncate.enableWrapping = true; - // Test that measureTextWithoutWrap works correctly, while set overflow mode to truncate. - textRendererTruncate.text = ""; - let result = TextUtils.measureTextWithoutWrap(textRendererTruncate); - expect(result.width).to.be.equal(0); - expect(result.height).to.be.equal(100); - expect(result.lines).to.be.deep.equal([]); - expect(result.lineWidths).to.be.deep.equal([]); - expect(result.lineHeight).to.be.deep.equal(27); - - textRendererTruncate.text = undefined; - result = TextUtils.measureTextWithoutWrap(textRendererTruncate); - expect(result.width).to.be.equal(0); - expect(result.height).to.be.equal(100); - expect(result.lines).to.be.deep.equal([]); - expect(result.lineWidths).to.be.deep.equal([]); - expect(result.lineHeight).to.be.deep.equal(27); - textRendererTruncate.text = text1; - result = TextUtils.measureTextWithoutWrap(textRendererTruncate); + let result = TextUtils.measureTextWithoutWrap(textRendererTruncate); expect(result.width).to.be.equal(518); expect(result.height).to.be.equal(100); expect(result.lines).to.be.deep.equal(["趚今天天气很好,阳光明媚。我 在公园里 漫步。"]);