From 869552f5c6704148aaba4d60d8142cce34737c09 Mon Sep 17 00:00:00 2001 From: singlecoder Date: Mon, 6 Jun 2022 17:52:38 +0800 Subject: [PATCH 01/55] feat(text): add FontAtlas --- packages/core/src/2d/atlas/FontAtlas.ts | 57 +++++++++++++++++++++++++ packages/core/src/2d/index.ts | 1 + 2 files changed, 58 insertions(+) create mode 100644 packages/core/src/2d/atlas/FontAtlas.ts diff --git a/packages/core/src/2d/atlas/FontAtlas.ts b/packages/core/src/2d/atlas/FontAtlas.ts new file mode 100644 index 0000000000..e01cf9cf1b --- /dev/null +++ b/packages/core/src/2d/atlas/FontAtlas.ts @@ -0,0 +1,57 @@ +import { RefObject } from "../../asset/RefObject"; +import { Engine } from "../../Engine"; +import { Texture2D } from "../../texture/Texture2D"; + +export interface CharDef { + x: number, + y: number, + w: number, + h: number, + offsetX: number, + offsetY: number, + xAdvance: number +} + +export interface CharDefDict { + [key: string]: CharDef; +} + +/** + * Font Atlas. + */ +export class FontAtlas extends RefObject { + private _charDefDict: CharDefDict; + private _texture: Texture2D; + + /** + * Constructor a FontAtlas. + * @param engine - Engine to which the FontAtlas belongs + */ + constructor(engine: Engine, texture: Texture2D) { + super(engine); + this._charDefDict = {}; + this._texture = texture; + } + + /** + * @override + */ + _onDestroy(): void { + this._texture.destroy(); + this._texture = null; + this._charDefDict = {}; + } + + addChar(key: string, def: CharDef) { + this._charDefDict[key] = def; + } + + getChar(key: string): CharDef | null { + return this._charDefDict[key]; + } + + getTexture(): Texture2D { + return this._texture; + } +} + diff --git a/packages/core/src/2d/index.ts b/packages/core/src/2d/index.ts index f58f66ada7..b75830a362 100644 --- a/packages/core/src/2d/index.ts +++ b/packages/core/src/2d/index.ts @@ -4,5 +4,6 @@ export { TextHorizontalAlignment, TextVerticalAlignment } from "./enums/TextAlig export { OverflowMode } from "./enums/TextOverflow"; export { FontStyle } from "./enums/FontStyle"; export { SpriteAtlas } from "./atlas/SpriteAtlas"; +export { FontAtlas } from "./atlas/FontAtlas"; export * from "./sprite/index"; export * from "./text/index"; From d4c3b289248e1207dec55cd24fb153aa186d8a3f Mon Sep 17 00:00:00 2001 From: singlecoder Date: Mon, 6 Jun 2022 17:54:53 +0800 Subject: [PATCH 02/55] feat(text): opt api name --- packages/core/src/2d/atlas/FontAtlas.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/2d/atlas/FontAtlas.ts b/packages/core/src/2d/atlas/FontAtlas.ts index e01cf9cf1b..707cd46406 100644 --- a/packages/core/src/2d/atlas/FontAtlas.ts +++ b/packages/core/src/2d/atlas/FontAtlas.ts @@ -42,11 +42,11 @@ export class FontAtlas extends RefObject { this._charDefDict = {}; } - addChar(key: string, def: CharDef) { + addCharDef(key: string, def: CharDef) { this._charDefDict[key] = def; } - getChar(key: string): CharDef | null { + getCharDef(key: string): CharDef | null { return this._charDefDict[key]; } From 05f38038c7880793aaf73ca971af611696b08aaa Mon Sep 17 00:00:00 2001 From: singlecoder Date: Tue, 7 Jun 2022 15:17:31 +0800 Subject: [PATCH 03/55] feat(text): add assembler for 2d renderer --- packages/core/src/2d/assembler/IAssembler.ts | 6 + .../2d/assembler/StaticInterfaceImplement.ts | 9 + .../core/src/2d/assembler/TextAssembler.ts | 84 +++++++++ packages/core/src/2d/assembler/base.ts | 3 + packages/core/src/2d/data/RenderData2D.ts | 10 ++ packages/core/src/2d/text/TextRenderer.ts | 169 ++++++++---------- 6 files changed, 185 insertions(+), 96 deletions(-) create mode 100644 packages/core/src/2d/assembler/IAssembler.ts create mode 100644 packages/core/src/2d/assembler/StaticInterfaceImplement.ts create mode 100644 packages/core/src/2d/assembler/TextAssembler.ts create mode 100644 packages/core/src/2d/assembler/base.ts create mode 100644 packages/core/src/2d/data/RenderData2D.ts diff --git a/packages/core/src/2d/assembler/IAssembler.ts b/packages/core/src/2d/assembler/IAssembler.ts new file mode 100644 index 0000000000..82ee985bd6 --- /dev/null +++ b/packages/core/src/2d/assembler/IAssembler.ts @@ -0,0 +1,6 @@ +import { Renderer } from "../../Renderer"; + +export interface IAssembler { + resetData(renderer: Renderer): void; + updateData(renderer: Renderer): void; +} diff --git a/packages/core/src/2d/assembler/StaticInterfaceImplement.ts b/packages/core/src/2d/assembler/StaticInterfaceImplement.ts new file mode 100644 index 0000000000..183cfb5af4 --- /dev/null +++ b/packages/core/src/2d/assembler/StaticInterfaceImplement.ts @@ -0,0 +1,9 @@ +/** + * Static interface implement decorator. + * https://stackoverflow.com/questions/13955157/how-to-define-static-property-in-typescript-interface + */ +export function StaticInterfaceImplement() { + return (constructor: U) => { + constructor; + }; +} diff --git a/packages/core/src/2d/assembler/TextAssembler.ts b/packages/core/src/2d/assembler/TextAssembler.ts new file mode 100644 index 0000000000..00026a8a20 --- /dev/null +++ b/packages/core/src/2d/assembler/TextAssembler.ts @@ -0,0 +1,84 @@ +import { Vector3 } from "@oasis-engine/math"; +import { Texture2D } from "../../texture"; +import { TextRenderer, DirtyFlag } from "../text/TextRenderer"; +import { TextUtils } from "../text/TextUtils"; +import { IAssembler } from "./IAssembler"; +import { StaticInterfaceImplement } from "./StaticInterfaceImplement"; + +@StaticInterfaceImplement() +export class TextAssembler { + private static _tempVec3: Vector3 = new Vector3(); + + public static resetData(renderer: TextRenderer) { + const { _positions, _triangles } = renderer._renderData; + + _positions[0] = new Vector3(); + _positions[1] = new Vector3(); + _positions[2] = new Vector3(); + _positions[3] = new Vector3(); + + _triangles[0] = 0, _triangles[1] = 2, _triangles[2] = 1; + _triangles[3] = 2, _triangles[4] = 0, _triangles[5] = 3; + } + + static updateData(renderer: TextRenderer): void { + const isTextureDirty = renderer._isContainDirtyFlag(DirtyFlag.Property); + if (isTextureDirty) { + TextAssembler._updateText(renderer); + renderer._setDirtyFlagFalse(DirtyFlag.Property); + } + + if (renderer._isWorldMatrixDirty.flag || isTextureDirty) { + TextAssembler._updatePosition(renderer); + renderer._isWorldMatrixDirty.flag = false; + } + } + + private static _updatePosition(renderer: TextRenderer): void { + const localPositions = renderer._sprite._positions; + const localVertexPos = TextAssembler._tempVec3; + const worldMatrix = renderer.entity.transform.worldMatrix; + + const { _positions } = renderer._renderData; + for (let i = 0, n = _positions.length; i < n; i++) { + const curVertexPos = localPositions[i]; + localVertexPos.setValue(curVertexPos.x, curVertexPos.y, 0); + Vector3.transformToVec3(localVertexPos, worldMatrix, _positions[i]); + } + } + + private static _updateText(renderer: TextRenderer): void { + const { width: originWidth, height: originHeight, enableWrapping, overflowMode } = renderer; + const fontStr = TextUtils.getNativeFontString(renderer.font.name, renderer.fontSize, renderer.fontStyle); + const textMetrics = TextUtils.measureText( + renderer.text, + originWidth, + originHeight, + renderer.lineSpacing, + enableWrapping, + overflowMode, + fontStr + ); + TextUtils.updateText(textMetrics, fontStr, renderer.horizontalAlignment, renderer.verticalAlignment); + TextAssembler._updateTexture(renderer); + } + + private static _updateTexture(renderer: TextRenderer): void { + const trimData = TextUtils.trimCanvas(); + const { width, height } = trimData; + const canvas = TextUtils.updateCanvas(width, height, trimData.data); + renderer._clearTexture(); + const sprite = renderer._sprite; + // If add fail, set texture for sprite. + if (!renderer.engine._dynamicTextAtlasManager.addSprite(sprite, canvas)) { + const texture = new Texture2D(renderer.engine, width, height); + texture.setImageSource(canvas); + texture.generateMipmaps(); + sprite.texture = texture; + } + // Update sprite data. + sprite._updateMesh(); + renderer._renderData._uv = sprite._uv; + debugger; + } +} diff --git a/packages/core/src/2d/assembler/base.ts b/packages/core/src/2d/assembler/base.ts new file mode 100644 index 0000000000..0b6f10a812 --- /dev/null +++ b/packages/core/src/2d/assembler/base.ts @@ -0,0 +1,3 @@ +export interface assembler { + +} diff --git a/packages/core/src/2d/data/RenderData2D.ts b/packages/core/src/2d/data/RenderData2D.ts new file mode 100644 index 0000000000..0c08acc3f5 --- /dev/null +++ b/packages/core/src/2d/data/RenderData2D.ts @@ -0,0 +1,10 @@ +import { Vector2, Vector3 } from "@oasis-engine/math"; + +export class RenderData2D { + /** @internal */ + _positions: Vector3[] = new Array(); + /** @internal */ + _uv: Vector2[] = new Array(); + /** @internal */ + _triangles: number[] = new Array(); +} diff --git a/packages/core/src/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts index d54b446855..3407cac2ce 100644 --- a/packages/core/src/2d/text/TextRenderer.ts +++ b/packages/core/src/2d/text/TextRenderer.ts @@ -1,23 +1,21 @@ -import { BoundingBox, Color, Vector3 } from "@oasis-engine/math"; +import { BoundingBox, Color } from "@oasis-engine/math"; import { Sprite, SpriteMaskInteraction, SpriteMaskLayer, SpriteRenderer } from ".."; -import { CompareFunction, Renderer, UpdateFlag } from "../.."; +import { CompareFunction, Renderer } from "../.."; import { BoolUpdateFlag } from "../../BoolUpdateFlag"; import { Camera } from "../../Camera"; import { assignmentClone, deepClone, ignoreClone } from "../../clone/CloneManager"; import { Entity } from "../../Entity"; -import { Texture2D } from "../../texture"; +import { TextAssembler } from "../assembler/textAssembler"; +import { RenderData2D } from "../data/RenderData2D"; import { FontStyle } from "../enums/FontStyle"; import { TextHorizontalAlignment, TextVerticalAlignment } from "../enums/TextAlignment"; import { OverflowMode } from "../enums/TextOverflow"; import { Font } from "./Font"; -import { TextUtils } from "./TextUtils"; /** * Renders a text for 2D graphics. */ export class TextRenderer extends Renderer { - private static _tempVec3: Vector3 = new Vector3(); - /** @internal temp solution. */ @ignoreClone _customLocalBounds: BoundingBox = null; @@ -25,10 +23,19 @@ export class TextRenderer extends Renderer { @ignoreClone _customRootEntity: Entity = null; + /** @internal */ @ignoreClone - private _sprite: Sprite = null; - @deepClone - private _positions: Vector3[] = [new Vector3(), new Vector3(), new Vector3(), new Vector3()]; + _sprite: Sprite = null; + /** @internal */ + @ignoreClone + _renderData: RenderData2D = new RenderData2D(); + /** @internal */ + @ignoreClone + _dirtyFlag: number = DirtyFlag.Property; + /** @internal */ + @ignoreClone + _isWorldMatrixDirty: BoolUpdateFlag; + @deepClone private _color: Color = new Color(1, 1, 1, 1); @assignmentClone @@ -46,6 +53,8 @@ export class TextRenderer extends Renderer { @assignmentClone private _lineSpacing: number = 0; @assignmentClone + private _useCharCache: boolean = false; + @assignmentClone private _horizontalAlignment: TextHorizontalAlignment = TextHorizontalAlignment.Center; @assignmentClone private _verticalAlignment: TextVerticalAlignment = TextVerticalAlignment.Center; @@ -53,10 +62,6 @@ export class TextRenderer extends Renderer { private _enableWrapping: boolean = false; @assignmentClone private _overflowMode: OverflowMode = OverflowMode.Overflow; - @ignoreClone - private _dirtyFlag: number = DirtyFlag.Property; - @ignoreClone - private _isWorldMatrixDirty: BoolUpdateFlag; @assignmentClone private _maskInteraction: SpriteMaskInteraction = SpriteMaskInteraction.None; @assignmentClone @@ -174,6 +179,19 @@ export class TextRenderer extends Renderer { } } + /** + * Whether cache each character individually. + */ + get useCharCache(): boolean { + return this._useCharCache; + } + + set useCharCache(value: boolean) { + if (this._useCharCache !== value) { + this._useCharCache = value; + } + } + /** * The horizontal alignment. */ @@ -260,6 +278,7 @@ export class TextRenderer extends Renderer { const { engine } = this; this._isWorldMatrixDirty = entity.transform.registerWorldChangeFlag(); this._sprite = new Sprite(engine); + TextAssembler.resetData(this); this.font = Font.createFromOS(engine); this.setMaterial(engine._spriteDefaultMaterial); } @@ -277,31 +296,22 @@ export class TextRenderer extends Renderer { return; } - const { _sprite: sprite } = this; - const isTextureDirty = this._isContainDirtyFlag(DirtyFlag.Property); - if (isTextureDirty) { - this._updateText(); - this._setDirtyFlagFalse(DirtyFlag.Property); - } - - if (this._isWorldMatrixDirty.flag || isTextureDirty) { - this._updatePosition(); - this._isWorldMatrixDirty.flag = false; - } + TextAssembler.updateData(this); if (this._isContainDirtyFlag(DirtyFlag.MaskInteraction)) { this._updateStencilState(); this._setDirtyFlagFalse(DirtyFlag.MaskInteraction); } - this.shaderData.setTexture(SpriteRenderer._textureProperty, sprite.texture); + this.shaderData.setTexture(SpriteRenderer._textureProperty, this._sprite.texture); const spriteElementPool = this._engine._spriteElementPool; const spriteElement = spriteElementPool.getFromPool(); + const { _positions, _triangles, _uv } = this._renderData; spriteElement.setValue( this, - this._positions, - sprite._uv, - sprite._triangles, + _positions, + _uv, + _triangles, this.color, this.getMaterial(), camera @@ -325,6 +335,38 @@ export class TextRenderer extends Renderer { target.font = this._font; } + /** + * @internal + */ + _isContainDirtyFlag(type: number): boolean { + return (this._dirtyFlag & type) != 0; + } + + /** + * @internal + */ + _setDirtyFlagTrue(type: number): void { + this._dirtyFlag |= type; + } + + /** + * @internal + */ + _setDirtyFlagFalse(type: number): void { + this._dirtyFlag &= ~type; + } + + /** + * @internal + */ + _clearTexture(): void { + const { _sprite } = this; + // Remove sprite from dynamic atlas. + this.engine._dynamicTextAtlasManager.removeSprite(_sprite); + this.shaderData.setTexture(SpriteRenderer._textureProperty, null); + _sprite.atlasRegion = _sprite.region; + } + /** * @override */ @@ -345,51 +387,6 @@ export class TextRenderer extends Renderer { } } - private _isContainDirtyFlag(type: number): boolean { - return (this._dirtyFlag & type) != 0; - } - - private _setDirtyFlagTrue(type: number): void { - this._dirtyFlag |= type; - } - - private _setDirtyFlagFalse(type: number): void { - this._dirtyFlag &= ~type; - } - - private _updateText(): void { - const { width: originWidth, height: originHeight, enableWrapping, overflowMode } = this; - const fontStr = TextUtils.getNativeFontString(this._font.name, this._fontSize, this._fontStyle); - const textMetrics = TextUtils.measureText( - this.text, - originWidth, - originHeight, - this.lineSpacing, - enableWrapping, - overflowMode, - fontStr - ); - TextUtils.updateText(textMetrics, fontStr, this.horizontalAlignment, this.verticalAlignment); - this._updateTexture(); - } - - private _updateTexture(): void { - const trimData = TextUtils.trimCanvas(); - const { width, height } = trimData; - const canvas = TextUtils.updateCanvas(width, height, trimData.data); - this._clearTexture(); - const { _sprite: sprite } = this; - // If add fail, set texture for sprite. - if (!this.engine._dynamicTextAtlasManager.addSprite(sprite, canvas)) { - const texture = new Texture2D(this.engine, width, height); - texture.setImageSource(canvas); - texture.generateMipmaps(); - sprite.texture = texture; - } - // Update sprite data. - sprite._updateMesh(); - } - private _updateStencilState(): void { // Update stencil. const material = this.getInstanceMaterial(); @@ -413,31 +410,11 @@ export class TextRenderer extends Renderer { stencilState.compareFunctionBack = compare; } } - - private _updatePosition(): void { - const localPositions = this._sprite._positions; - const localVertexPos = TextRenderer._tempVec3; - const worldMatrix = this.entity.transform.worldMatrix; - - const { _positions } = this; - for (let i = 0, n = _positions.length; i < n; i++) { - const curVertexPos = localPositions[i]; - localVertexPos.setValue(curVertexPos.x, curVertexPos.y, 0); - Vector3.transformToVec3(localVertexPos, worldMatrix, _positions[i]); - } - } - - private _clearTexture(): void { - const { _sprite } = this; - // Remove sprite from dynamic atlas. - this.engine._dynamicTextAtlasManager.removeSprite(_sprite); - this.shaderData.setTexture(SpriteRenderer._textureProperty, null); - _sprite.atlasRegion = _sprite.region; - } } -enum DirtyFlag { +export enum DirtyFlag { Property = 0x1, MaskInteraction = 0x2, - All = 0x3 + CharCache = 0x4, + All = 0x7 } From ef915d3a3e404c0557b996460ef7ff9f5a76186e Mon Sep 17 00:00:00 2001 From: singlecoder Date: Thu, 9 Jun 2022 20:39:19 +0800 Subject: [PATCH 04/55] feat(text): temp version --- .../core/src/2d/assembler/CharAssembler.ts | 252 ++++++++++++++++++ .../core/src/2d/assembler/CharRenderData.ts | 12 + .../src/2d/assembler/CharRenderDataPool.ts | 64 +++++ packages/core/src/2d/assembler/CharUtils.ts | 113 ++++++++ .../core/src/2d/assembler/TextAssembler.ts | 32 ++- packages/core/src/2d/assembler/base.ts | 3 - packages/core/src/2d/atlas/FontAtlas.ts | 4 +- packages/core/src/2d/data/RenderData2D.ts | 12 +- packages/core/src/2d/text/TextRenderer.ts | 74 ++--- packages/core/src/2d/text/TextUtils.ts | 152 +++++++++-- 10 files changed, 643 insertions(+), 75 deletions(-) create mode 100644 packages/core/src/2d/assembler/CharAssembler.ts create mode 100644 packages/core/src/2d/assembler/CharRenderData.ts create mode 100644 packages/core/src/2d/assembler/CharRenderDataPool.ts create mode 100644 packages/core/src/2d/assembler/CharUtils.ts delete mode 100644 packages/core/src/2d/assembler/base.ts diff --git a/packages/core/src/2d/assembler/CharAssembler.ts b/packages/core/src/2d/assembler/CharAssembler.ts new file mode 100644 index 0000000000..df15e4b347 --- /dev/null +++ b/packages/core/src/2d/assembler/CharAssembler.ts @@ -0,0 +1,252 @@ +import { Vector3 } from "@oasis-engine/math"; +import { TextHorizontalAlignment, TextVerticalAlignment } from "../enums/TextAlignment"; +import { OverflowMode } from "../enums/TextOverflow"; +import { TextRenderer, DirtyFlag } from "../text/TextRenderer"; +import { TextUtils, TextMetrics } from "../text/TextUtils"; +import { CharRenderDataPool } from "./CharRenderDataPool"; +import { CharDefWithTexture, CharUtils } from "./CharUtils"; +import { IAssembler } from "./IAssembler"; +import { StaticInterfaceImplement } from "./StaticInterfaceImplement"; + +@StaticInterfaceImplement() +export class CharAssembler { + private static _charUtils: CharUtils; + private static _charRenderDataPool: CharRenderDataPool; + + static resetData(renderer: TextRenderer): void { + if (!CharAssembler._charUtils) { + CharAssembler._charUtils = new CharUtils(renderer.engine); + CharAssembler._charRenderDataPool = new CharRenderDataPool(); + } + const { _charRenderDatas } = renderer; + for (let i = 0, l = _charRenderDatas.length; i < l; ++i) { + CharAssembler._charRenderDataPool.putData(_charRenderDatas[i]); + } + _charRenderDatas.length = 0; + } + + static updateData(renderer: TextRenderer): void { + const isTextureDirty = renderer._isContainDirtyFlag(DirtyFlag.Property); + if (isTextureDirty) { + CharAssembler._updateText(renderer); + renderer._setDirtyFlagFalse(DirtyFlag.Property); + } + + if (renderer._isWorldMatrixDirty.flag || isTextureDirty) { + CharAssembler._updatePosition(renderer); + renderer._isWorldMatrixDirty.flag = false; + } + } + + static clear(): void { + if (CharAssembler._charUtils) { + CharAssembler._charUtils.clear(); + CharAssembler._charUtils = null; + } + } + + private static _updatePosition(renderer: TextRenderer): void { + const worldMatrix = renderer.entity.transform.worldMatrix; + const { _charRenderDatas } = renderer; + for (let i = 0, l = _charRenderDatas.length; i < l; ++i) { + const { localPositions, renderData } = _charRenderDatas[i]; + for (let j = 0; j < 4; ++j) { + Vector3.transformToVec3(localPositions[j], worldMatrix, renderData.positions[j]); + } + } + } + + private static _updateText(renderer: TextRenderer): void { + const { color, fontSize, fontStyle, horizontalAlignment, verticalAlignment, _charRenderDatas } = renderer; + const { name } = renderer.font; + const { _pixelsPerUnit } = TextUtils; + const fontString = TextUtils.getNativeFontString(name, fontSize, fontStyle); + const fontSizeInfo = TextUtils.measureFont(fontString); + const fontHash = TextUtils.getNativeFontHash(name, fontSize, fontStyle); + const widthInPixel = renderer.width * _pixelsPerUnit; + const heightInPixel = renderer.height * _pixelsPerUnit; + + const textMetrics = renderer.enableWrapping ? CharAssembler._measureTextWithWrap(renderer) : CharAssembler._measureTextWithoutWrap(renderer); + const { width, height, lines, lineWidths, lineHeight } = textMetrics; + + let startY = 0; + switch (verticalAlignment) { + case TextVerticalAlignment.Top: + startY = heightInPixel * 0.5; + break; + case TextVerticalAlignment.Center: + startY = height * 0.5; + break; + case TextVerticalAlignment.Bottom: + startY = height - heightInPixel * 0.5; + break; + } + + for (let i = 0, n = lines.length; i < n; ++i) { + const line = lines[i]; + const lineWidth = lineWidths[i]; + + let startX = 0; + switch (horizontalAlignment) { + case TextHorizontalAlignment.Left: + startX = -widthInPixel * 0.5; + break; + case TextHorizontalAlignment.Center: + startX = -lineWidth * 0.5; + break; + case TextHorizontalAlignment.Right: + startX = widthInPixel * 0.5 - lineWidth; + break; + } + startY -= (i + 0/.5) * lineHeight; + + for (let j = 0, m = line.length; j < m; ++j) { + const char = line[j]; + const key = `${fontHash}${char.charCodeAt(0)}`; + const charDefWithTexture = CharAssembler._charUtils.getCharDef(key); + const { charDef } = charDefWithTexture; + const charRenderData = CharAssembler._charRenderDataPool.getData(); + const { renderData, localPositions } = charRenderData; + charRenderData.texture = charDefWithTexture.texture; + renderData.color = color; + const textureSize = CharAssembler._charUtils.getTextureSize(); + + const { uvs } = renderData; + const { x, y, w, h} = charDef; + const left = startX / _pixelsPerUnit; + const right = (startX + charDef.w) / _pixelsPerUnit; + const top = (startY + charDef.offsetY + h * 0.5) / _pixelsPerUnit; + const bottom = (top - h) / _pixelsPerUnit; + // Top-left. + localPositions[0].setValue(left, top, 0); + uvs[0].setValue(x / textureSize, y / textureSize); + // Top-right. + localPositions[1].setValue(right, top, 0); + uvs[1].setValue((x + w) / textureSize, h / textureSize); + // Bottom-right. + localPositions[2].setValue(right, bottom, 0); + uvs[2].setValue((x + w) / textureSize, (y + h) / textureSize); + // Bottom-left. + localPositions[3].setValue(left, bottom, 0); + uvs[3].setValue(x / textureSize, (y + h) / textureSize); + + _charRenderDatas.push(charRenderData); + startX += charDef.xAdvance; + } + } + } + + private static _measureTextWithWrap(renderer: TextRenderer): TextMetrics { + const { fontSize, fontStyle } = renderer; + const { name } = renderer.font; + const fontString = TextUtils.getNativeFontString(name, fontSize, fontStyle); + const fontHash = TextUtils.getNativeFontHash(name, fontSize, fontStyle); + const fontSizeInfo = TextUtils.measureFont(fontString); + const subTexts = renderer.text.split(/(?:\r\n|\r|\n)/); + const lines = new Array(); + const lineWidths = new Array(); + const { _pixelsPerUnit } = TextUtils; + const lineHeight = fontSizeInfo.size + renderer.lineSpacing * _pixelsPerUnit; + const wrapWidth = renderer.width * _pixelsPerUnit; + let width = 0; + + for (let i = 0, n = subTexts.length; i < n; ++i) { + const subText = subTexts[i]; + let chars = ""; + let charsWidth = 0; + + for (let j = 0, m = subText.length; j < m; ++j) { + const char = subText[j]; + const charDefWithTexture = CharAssembler._getCharDefWithTexture(char, fontString, fontHash); + const { charDef } = charDefWithTexture; + if (charsWidth + charDef.w > wrapWidth) { + if (charsWidth === 0) { + lines.push(char); + lineWidths.push(charDef.w); + } else { + lines.push(chars); + lineWidths.push(charsWidth); + chars = char; + charsWidth = charDef.xAdvance; + } + } else { + chars += char; + charsWidth += charDef.xAdvance; + } + } + + if (charsWidth > 0) { + lines.push(chars); + lineWidths.push(charsWidth); + } + } + + let height = renderer.height * _pixelsPerUnit; + if (renderer.overflowMode === OverflowMode.Overflow) { + height = lineHeight * lines.length; + } + + return { + width, + height, + lines, + lineWidths, + lineHeight + }; + } + + private static _measureTextWithoutWrap(renderer: TextRenderer): TextMetrics { + const { fontSize, fontStyle } = renderer; + const { name } = renderer.font; + const fontString = TextUtils.getNativeFontString(name, fontSize, fontStyle); + const fontHash = TextUtils.getNativeFontHash(name, fontSize, fontStyle); + const fontSizeInfo = TextUtils.measureFont(fontString); + const lines = renderer.text.split(/(?:\r\n|\r|\n)/); + const lineCount = lines.length; + const lineWidths = new Array(); + const { _pixelsPerUnit } = TextUtils; + const lineHeight = fontSizeInfo.size + renderer.lineSpacing * _pixelsPerUnit; + let width = 0; + let height = renderer.height * _pixelsPerUnit; + if (renderer.overflowMode === OverflowMode.Overflow) { + height = lineHeight * lineCount; + } + + for (let i = 0; i < lineCount; ++i) { + const line = lines[i]; + let curWidth = 0; + + for (let j = 0, m = line.length; j < m; ++j) { + const charDefWithTexture = CharAssembler._getCharDefWithTexture(line[j], fontString, fontHash); + curWidth += charDefWithTexture.charDef.xAdvance; + } + lineWidths[i] = curWidth; + if (curWidth > width) { + width = curWidth; + } + } + + return { + width, + height, + lines, + lineWidths, + lineHeight + }; + } + + private static _getCharDefWithTexture(char: string, fontString: string, fontHash: string): CharDefWithTexture { + const { _charUtils } = CharAssembler; + const key = `${fontHash}${char.charCodeAt(0)}`; + let charDefWithTexture = _charUtils.getCharDef(key); + if (!charDefWithTexture) { + const charMetrics = TextUtils.measureChar(char, fontString); + const { sizeInfo } = charMetrics; + const { ascent, descent } = sizeInfo; + const offsetY = (ascent - descent) * 0.5; + charDefWithTexture = _charUtils.addCharDef(key, TextUtils.textContext().canvas, charMetrics.width, sizeInfo.size, 0, offsetY); + } + + return charDefWithTexture; + } +} diff --git a/packages/core/src/2d/assembler/CharRenderData.ts b/packages/core/src/2d/assembler/CharRenderData.ts new file mode 100644 index 0000000000..a68e4fd492 --- /dev/null +++ b/packages/core/src/2d/assembler/CharRenderData.ts @@ -0,0 +1,12 @@ +import { Vector3 } from "@oasis-engine/math"; +import { Texture2D } from "../../texture"; +import { RenderData2D } from "../data/RenderData2D"; + +export interface CharRenderData { + /** @internal */ + texture: Texture2D; + /** @internal */ + renderData: RenderData2D; + /** @intarnal */ + localPositions: Vector3[]; +} diff --git a/packages/core/src/2d/assembler/CharRenderDataPool.ts b/packages/core/src/2d/assembler/CharRenderDataPool.ts new file mode 100644 index 0000000000..5edf49ca10 --- /dev/null +++ b/packages/core/src/2d/assembler/CharRenderDataPool.ts @@ -0,0 +1,64 @@ +import { Color, Vector2, Vector3 } from "@oasis-engine/math"; +import { Texture2D } from "../../texture"; +import { RenderData2D } from "../data/RenderData2D"; +import { CharRenderData } from "./CharRenderData"; + +export class CharRenderDataPool { + private _pools: Array = []; + private _poolIndex: number = -1; + + /** + * @internal + */ + constructor() { + const length = 50; + for (let i = 0; i < length; ++i) { + this._pools[i] = this._createData(); + } + this._poolIndex = length - 1; + } + + getData(): CharRenderData { + if (this._poolIndex !== -1) { + this._poolIndex--; + return this._pools.pop(); + } + + return this._createData(); + } + + putData(data: CharRenderData): void { + this._pools.push(data); + this._poolIndex++; + } + + private _createData(): CharRenderData { + const positions: Array = []; + const uvs: Array = []; + const triangles: Array = []; + const color: Color = null; + const texture: Texture2D = null; + const localPositions: Array = []; + + positions[0] = new Vector3(); + positions[1] = new Vector3(); + positions[2] = new Vector3(); + positions[3] = new Vector3(); + localPositions[0] = new Vector3(); + localPositions[1] = new Vector3(); + localPositions[2] = new Vector3(); + localPositions[3] = new Vector3(); + + uvs[0] = new Vector2(); + uvs[1] = new Vector2(); + uvs[2] = new Vector2(); + uvs[3] = new Vector2(); + + triangles[0] = 0, triangles[1] = 2, triangles[2] = 1; + triangles[3] = 2, triangles[4] = 0, triangles[5] = 3; + + const renderData: RenderData2D = { positions, uvs, triangles, color }; + + return { texture, renderData, localPositions }; + } +} diff --git a/packages/core/src/2d/assembler/CharUtils.ts b/packages/core/src/2d/assembler/CharUtils.ts new file mode 100644 index 0000000000..ad3b67d5fa --- /dev/null +++ b/packages/core/src/2d/assembler/CharUtils.ts @@ -0,0 +1,113 @@ +import { Engine } from "../../Engine"; +import { Texture2D } from "../../texture"; +import { CharDef, FontAtlas } from "../atlas/FontAtlas"; + +export interface CharDefWithTexture { + texture: Texture2D; + charDef: CharDef; +} + +export class CharUtils { + private _fontAtlasList: Array = []; + private _curFontAtlas: FontAtlas = null; + private _textureSize: number = 1024; + private _space: number = 1; + private _curX: number = 1; + private _curY: number = 1; + private _nextY: number = 1; + + /** + * @internal + */ + constructor(public readonly engine: Engine) { + this._createFontAtlas(); + } + + addCharDef( + key: string, + imageSource: TexImageSource | OffscreenCanvas, + width: number, + height: number, + offsetX: number, + offsetY: number, + ): CharDefWithTexture { + const { _space: space, _textureSize: textureSize } = this; + + const endX = this._curX + width + space; + if (endX >= textureSize) { + this._curX = space; + this._curY = this._nextY + space; + } + const endY = this._curY + height + space; + + if (endY >= textureSize) { + this._createFontAtlas(); + if (this._curX + width + space >= textureSize || this._curY + height + space >= textureSize) { + throw Error("The char fontSize is too large."); + } + } + + const curTexture = this._curFontAtlas.getTexture(); + if (width > 0 && height > 0) { + curTexture.setImageSource(imageSource, 0, false, false, this._curX, this._curY); + curTexture.generateMipmaps(); + } + + const charDef = { + x: this._curX, + y: this._curY, + w: width, + h: height, + offsetX, + offsetY, + xAdvance: width + space + }; + this._curFontAtlas.addCharDef(key, charDef); + this._curX = endX + space; + + return { + texture: curTexture, + charDef + }; + } + + getCharDef(key: string): CharDefWithTexture { + const { _fontAtlasList } = this; + for (let i = 0, l = _fontAtlasList.length; i < l; ++i) { + const fontAtlas = _fontAtlasList[i]; + const charDef = fontAtlas.getCharDef(key); + if (charDef) { + return { + texture: fontAtlas.getTexture(), + charDef: charDef + }; + } + } + return null; + } + + clear(): void { + this._curFontAtlas = null; + const { _fontAtlasList } = this; + for (let i = 0, l = _fontAtlasList.length; i < l; ++i) { + const fontAtlas = _fontAtlasList[i]; + fontAtlas.destroy(); + } + _fontAtlasList.length = 0; + } + + getTextureSize(): number { + return this._textureSize; + } + + private _createFontAtlas(): void { + const { engine, _textureSize } = this; + const tex = new Texture2D(engine, _textureSize, _textureSize); + tex._addRefCount(1); + this._curFontAtlas = new FontAtlas(engine, tex); + this._fontAtlasList.push(this._curFontAtlas); + this._curX = 1; + this._curY = 1; + this._nextY = 1; + } +} diff --git a/packages/core/src/2d/assembler/TextAssembler.ts b/packages/core/src/2d/assembler/TextAssembler.ts index a17b81bdee..000acc80b0 100644 --- a/packages/core/src/2d/assembler/TextAssembler.ts +++ b/packages/core/src/2d/assembler/TextAssembler.ts @@ -1,4 +1,4 @@ -import { Vector3 } from "@oasis-engine/math"; +import { Color, Vector2, Vector3 } from "@oasis-engine/math"; import { Texture2D } from "../../texture"; import { TextHorizontalAlignment, TextVerticalAlignment } from "../enums/TextAlignment"; import { TextRenderer, DirtyFlag } from "../text/TextRenderer"; @@ -11,15 +11,19 @@ export class TextAssembler { private static _tempVec3: Vector3 = new Vector3(); public static resetData(renderer: TextRenderer) { - const { _positions, _triangles } = renderer._renderData; + const positions: Array = []; + const uvs: Array = []; + const triangles: Array = []; + const color: Color = null; - _positions[0] = new Vector3(); - _positions[1] = new Vector3(); - _positions[2] = new Vector3(); - _positions[3] = new Vector3(); + positions[0] = new Vector3(); + positions[1] = new Vector3(); + positions[2] = new Vector3(); + positions[3] = new Vector3(); + triangles[0] = 0, triangles[1] = 2, triangles[2] = 1; + triangles[3] = 2, triangles[4] = 0, triangles[5] = 3; - _triangles[0] = 0, _triangles[1] = 2, _triangles[2] = 1; - _triangles[3] = 2, _triangles[4] = 0, _triangles[5] = 3; + renderer._renderData = { positions, uvs, triangles, color}; } static updateData(renderer: TextRenderer): void { @@ -33,6 +37,8 @@ export class TextAssembler { TextAssembler._updatePosition(renderer); renderer._isWorldMatrixDirty.flag = false; } + + renderer._renderData.color = renderer.color; } private static _updatePosition(renderer: TextRenderer): void { @@ -40,7 +46,7 @@ export class TextAssembler { const localVertexPos = TextAssembler._tempVec3; const worldMatrix = renderer.entity.transform.worldMatrix; - const { _positions } = renderer._renderData; + const { positions: _positions } = renderer._renderData; for (let i = 0, n = _positions.length; i < n; i++) { const curVertexPos = localPositions[i]; localVertexPos.setValue(curVertexPos.x, curVertexPos.y, 0); @@ -50,7 +56,7 @@ export class TextAssembler { private static _updateText(renderer: TextRenderer): void { const { width: originWidth, height: originHeight, enableWrapping, overflowMode } = renderer; - const fontStr = TextUtils.getNativeFontString(renderer.font.name, renderer.fontSize, renderer.fontStyle); + const fontString = TextUtils.getNativeFontString(renderer.font.name, renderer.fontSize, renderer.fontStyle); const textMetrics = TextUtils.measureText( renderer.text, originWidth, @@ -58,9 +64,9 @@ export class TextAssembler { renderer.lineSpacing, enableWrapping, overflowMode, - fontStr + fontString ); - TextUtils.updateText(textMetrics, fontStr, renderer.horizontalAlignment, renderer.verticalAlignment); + TextUtils.updateText(textMetrics, fontString, renderer.horizontalAlignment, renderer.verticalAlignment); TextAssembler._updateTexture(renderer); } @@ -107,6 +113,6 @@ export class TextAssembler { } // Update sprite data. sprite._updateMesh(); - renderer._renderData._uv = sprite._uv; + renderer._renderData.uvs = sprite._uv; } } diff --git a/packages/core/src/2d/assembler/base.ts b/packages/core/src/2d/assembler/base.ts deleted file mode 100644 index 0b6f10a812..0000000000 --- a/packages/core/src/2d/assembler/base.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface assembler { - -} diff --git a/packages/core/src/2d/atlas/FontAtlas.ts b/packages/core/src/2d/atlas/FontAtlas.ts index 707cd46406..3aca2b037f 100644 --- a/packages/core/src/2d/atlas/FontAtlas.ts +++ b/packages/core/src/2d/atlas/FontAtlas.ts @@ -42,11 +42,11 @@ export class FontAtlas extends RefObject { this._charDefDict = {}; } - addCharDef(key: string, def: CharDef) { + addCharDef(key: string, def: CharDef): void { this._charDefDict[key] = def; } - getCharDef(key: string): CharDef | null { + getCharDef(key: string): CharDef { return this._charDefDict[key]; } diff --git a/packages/core/src/2d/data/RenderData2D.ts b/packages/core/src/2d/data/RenderData2D.ts index 0c08acc3f5..a77e2630ec 100644 --- a/packages/core/src/2d/data/RenderData2D.ts +++ b/packages/core/src/2d/data/RenderData2D.ts @@ -1,10 +1,12 @@ -import { Vector2, Vector3 } from "@oasis-engine/math"; +import { Color, Vector2, Vector3 } from "@oasis-engine/math"; -export class RenderData2D { +export interface RenderData2D { /** @internal */ - _positions: Vector3[] = new Array(); + positions: Vector3[]; /** @internal */ - _uv: Vector2[] = new Array(); + uvs: Vector2[]; /** @internal */ - _triangles: number[] = new Array(); + triangles: number[]; + /** @internal */ + color: Color; } diff --git a/packages/core/src/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts index 12656e55c4..2c15db9e37 100644 --- a/packages/core/src/2d/text/TextRenderer.ts +++ b/packages/core/src/2d/text/TextRenderer.ts @@ -5,8 +5,11 @@ import { BoolUpdateFlag } from "../../BoolUpdateFlag"; import { Camera } from "../../Camera"; import { assignmentClone, deepClone, ignoreClone } from "../../clone/CloneManager"; import { Entity } from "../../Entity"; +import { Texture2D } from "../../texture"; +import { CharAssembler } from "../assembler/CharAssembler"; import { TextAssembler } from "../assembler/textAssembler"; import { RenderData2D } from "../data/RenderData2D"; +import { CharRenderData } from "../assembler/CharRenderData"; import { FontStyle } from "../enums/FontStyle"; import { TextHorizontalAlignment, TextVerticalAlignment } from "../enums/TextAlignment"; import { OverflowMode } from "../enums/TextOverflow"; @@ -16,20 +19,16 @@ import { Font } from "./Font"; * Renders a text for 2D graphics. */ export class TextRenderer extends Renderer { - /** @internal temp solution. */ - @ignoreClone - _customLocalBounds: BoundingBox = null; - /** @internal temp solution. */ - @ignoreClone - _customRootEntity: Entity = null; - /** @internal */ @ignoreClone _sprite: Sprite = null; /** @internal */ @ignoreClone - _renderData: RenderData2D = new RenderData2D(); + _renderData: RenderData2D; /** @internal */ + @ignoreClone + _charRenderDatas: Array = []; + @ignoreClone _dirtyFlag: number = DirtyFlag.Property; /** @internal */ @@ -189,6 +188,7 @@ export class TextRenderer extends Renderer { set useCharCache(value: boolean) { if (this._useCharCache !== value) { this._useCharCache = value; + this._setDirtyFlagTrue(DirtyFlag.Property); } } @@ -279,6 +279,7 @@ export class TextRenderer extends Renderer { this._isWorldMatrixDirty = entity.transform.registerWorldChangeFlag(); this._sprite = new Sprite(engine); TextAssembler.resetData(this); + CharAssembler.resetData(this); this.font = Font.createFromOS(engine); this.setMaterial(engine._spriteDefaultMaterial); } @@ -296,27 +297,23 @@ export class TextRenderer extends Renderer { return; } - TextAssembler.updateData(this); - if (this._isContainDirtyFlag(DirtyFlag.MaskInteraction)) { this._updateStencilState(); this._setDirtyFlagFalse(DirtyFlag.MaskInteraction); } - this.shaderData.setTexture(SpriteRenderer._textureProperty, this._sprite.texture); - const spriteElementPool = this._engine._spriteElementPool; - const spriteElement = spriteElementPool.getFromPool(); - const { _positions, _triangles, _uv } = this._renderData; - spriteElement.setValue( - this, - _positions, - _uv, - _triangles, - this.color, - this.getMaterial(), - camera - ); - camera._renderPipeline.pushPrimitive(spriteElement); + if (this._useCharCache) { + CharAssembler.updateData(this); + + const { _charRenderDatas } = this; + for (let i = 0, l = _charRenderDatas.length; i < l; ++i) { + const charRenderData = _charRenderDatas[i]; + this._drawPrimitive(camera, charRenderData.renderData, charRenderData.texture); + } + } else { + TextAssembler.updateData(this); + this._drawPrimitive(camera, this._renderData, this._sprite.texture); + } } /** @@ -371,13 +368,8 @@ export class TextRenderer extends Renderer { * @override */ protected _updateBounds(worldBounds: BoundingBox): void { - if (this._customLocalBounds && this._customRootEntity) { - const worldMatrix = this._customRootEntity.transform.worldMatrix; - BoundingBox.transform(this._customLocalBounds, worldMatrix, worldBounds); - } else { - const worldMatrix = this._entity.transform.worldMatrix; - BoundingBox.transform(this._sprite.bounds, worldMatrix, worldBounds); - } + const worldMatrix = this._entity.transform.worldMatrix; + BoundingBox.transform(this._sprite.bounds, worldMatrix, worldBounds); } private _updateStencilState(): void { @@ -403,11 +395,27 @@ export class TextRenderer extends Renderer { stencilState.compareFunctionBack = compare; } } + + private _drawPrimitive(camera: Camera, renderData: RenderData2D, texture: Texture2D): void { + this.shaderData.setTexture(SpriteRenderer._textureProperty, texture); + const spriteElementPool = this._engine._spriteElementPool; + const spriteElement = spriteElementPool.getFromPool(); + const { positions, triangles, uvs, color } = renderData; + spriteElement.setValue( + this, + positions, + uvs, + triangles, + color, + this.getMaterial(), + camera + ); + camera._renderPipeline.pushPrimitive(spriteElement); + } } export enum DirtyFlag { Property = 0x1, MaskInteraction = 0x2, - CharCache = 0x4, - All = 0x7 + All = 0x3 } diff --git a/packages/core/src/2d/text/TextUtils.ts b/packages/core/src/2d/text/TextUtils.ts index a3592aea97..5f1a1503d4 100644 --- a/packages/core/src/2d/text/TextUtils.ts +++ b/packages/core/src/2d/text/TextUtils.ts @@ -12,6 +12,16 @@ export interface TextContext { context: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D; } +/** + * @internal + * FontSizeInfo. + */ +export interface FontSizeInfo { + ascent: number; + descent: number; + size: number; +} + /** * @internal * TextMetrics. @@ -24,11 +34,21 @@ export interface TextMetrics { lineHeight: number; } +/** + * @internal + * CharMetrics. + */ +export interface CharMetrics { + width: number; + sizeInfo: FontSizeInfo; +} + /** * @internal * TextUtils includes some helper function for text. */ export class TextUtils { + /** @internal */ static _genericFontFamilies: Array = [ "serif", "sans-serif", @@ -41,14 +61,15 @@ export class TextUtils { "fangsong" ]; /** These characters are all tall to help calculate the height required for text. */ + /** @internal */ + static _pixelsPerUnit: number = 128; private static _measureString: string = "|ÉqÅ"; private static _measureBaseline: string = "M"; private static _heightMultiplier: number = 2; private static _baselineMultiplier: number = 1.4; private static _maxWidth: number = 2048; private static _maxHeight: number = 2048; - private static _pixelsPerUnit: number = 128; - private static _fontSizeCache: Record = {}; + private static _fontSizeInfoCache: Record = {}; private static _textContext: TextContext = null; private static _tempVec2: Vector2 = new Vector2(); @@ -74,18 +95,18 @@ export class TextUtils { /** * Measure the font. - * @param font - the string of the font - * @returns the font size + * @param fontString - the string of the font + * @returns the font size info */ - public static measureFont(font: string): number { - const { _fontSizeCache: fontSizeCache } = TextUtils; - let fontSize = fontSizeCache[font]; - if (fontSize) { - return fontSize; + public static measureFont(fontString: string): FontSizeInfo { + const { _fontSizeInfoCache: fontSizeInfoCache } = TextUtils; + let info = fontSizeInfoCache[fontString]; + if (info) { + return info; } const { canvas, context } = TextUtils.textContext(); - context.font = font; + context.font = fontString; const measureString = TextUtils._measureString; const width = Math.ceil(context.measureText(measureString).width); let baseline = Math.ceil(context.measureText(TextUtils._measureBaseline).width); @@ -95,10 +116,10 @@ export class TextUtils { canvas.width = width; canvas.height = height; - context.font = font; + context.font = fontString; context.fillStyle = "#000"; context.clearRect(0, 0, width, height); - context.textBaseline = "alphabetic"; + context.textBaseline = "middle"; context.fillStyle = "#f00"; context.fillText(measureString, 0, baseline); @@ -138,9 +159,12 @@ export class TextUtils { } const descent = i - baseline + 1; - fontSize = ascent + descent; - fontSizeCache[font] = fontSize; - return fontSize; + fontSizeInfoCache[fontString] = info = { + ascent, + descent, + size: ascent + descent + }; + return info; } /** @@ -164,7 +188,7 @@ export class TextUtils { fontString: string ): TextMetrics { const { _pixelsPerUnit } = TextUtils; - const fontSize = TextUtils.measureFont(fontString); + const fontSize = TextUtils.measureFont(fontString).size; const context = TextUtils.textContext().context; const lines = TextUtils._wordWrap(text, originWidth, enableWrapping, fontString); const lineCount = lines.length; @@ -196,6 +220,78 @@ export class TextUtils { }; } + public static measureChar(char: string, fontString: string): CharMetrics { + const { canvas, context } = TextUtils.textContext(); + const width = Math.ceil(context.measureText(char).width); + let baseline = Math.ceil(context.measureText(TextUtils._measureBaseline).width); + const height = baseline * TextUtils._heightMultiplier; + baseline = (TextUtils._baselineMultiplier * baseline) | 0; + + canvas.width = width; + canvas.height = height; + + context.font = fontString; + context.fillStyle = "#000"; + context.clearRect(0, 0, width, height); + context.textBaseline = "middle"; + context.fillStyle = "#fff"; + context.fillText(char, 0, baseline); + + const imgData = context.getImageData(0, 0, width, height).data; + const lineDataCount = width * 4; + let stop = false; + let i = 0; + let offset = 0; + let top = 0; + + for (i = 0; i < baseline; ++i) { + offset = i * lineDataCount; + for (let j = 0; j < lineDataCount; j += 4) { + if (imgData[offset + j] !== 0) { + stop = true; + break; + } + } + if (stop) { + break; + } + } + + const ascent = baseline - i; + top = i; + stop = false; + + for (i = height - 1; i >= baseline; --i) { + offset = i * lineDataCount; + for (let j = 0; j < lineDataCount; j += 4) { + if (imgData[offset + j] !== 0) { + stop = true; + break; + } + } + if (stop) { + break; + } + } + + const descent = i - baseline + 1; + const size = ascent + descent; + const sizeInfo = { + ascent, + descent, + size + }; + if (size > 0) { + const data = context.getImageData(0, top, width, size); + TextUtils.updateCanvas(width, size, data); + } + + return { + width, + sizeInfo + }; + } + /** * Trim canvas. * @returns the width and height after trim, and the image data @@ -276,16 +372,34 @@ export class TextUtils { return str; } + /** + * Get native font hash. + * @param fontName - The font name + * @param fontSize - The font size + * @param style - The font style + * @returns The native font hash + */ + public static getNativeFontHash(fontName: string, fontSize: number, style: FontStyle): string { + let str = style & FontStyle.Bold ? "bold" : ""; + style & FontStyle.Italic && (str += "italic"); + // Check if font already contains strings + if (!/([\"\'])[^\'\"]+\1/.test(fontName) && TextUtils._genericFontFamilies.indexOf(fontName) == -1) { + fontName = `${fontName}`; + } + str += `${fontSize}px${fontName}`; + return str; + } + /** * Update text. * @param textMetrics - the text metrics object - * @param fontStr - the font string + * @param fontString - the font string * @param horizontalAlignment - the horizontal alignment * @param verticalAlignment - the vertical alignment */ public static updateText( textMetrics: TextMetrics, - fontStr: string, + fontString: string, horizontalAlignment: TextHorizontalAlignment, verticalAlignment: TextVerticalAlignment ): void { @@ -295,7 +409,7 @@ export class TextUtils { canvas.width = width; canvas.height = height; // clear canvas. - context.font = fontStr; + context.font = fontString; context.clearRect(0, 0, width, height); // set canvas font info. context.textBaseline = "middle"; From 2b266d5870bca8270383e9a520bad0d7f477fa1b Mon Sep 17 00:00:00 2001 From: singlecoder Date: Fri, 10 Jun 2022 15:48:00 +0800 Subject: [PATCH 05/55] feat(text): preliminary version for render text use char cache --- packages/core/src/2d/assembler/CharAssembler.ts | 13 ++++++------- packages/core/src/2d/text/TextUtils.ts | 1 + 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/core/src/2d/assembler/CharAssembler.ts b/packages/core/src/2d/assembler/CharAssembler.ts index df15e4b347..aa80130bfd 100644 --- a/packages/core/src/2d/assembler/CharAssembler.ts +++ b/packages/core/src/2d/assembler/CharAssembler.ts @@ -60,14 +60,13 @@ export class CharAssembler { const { color, fontSize, fontStyle, horizontalAlignment, verticalAlignment, _charRenderDatas } = renderer; const { name } = renderer.font; const { _pixelsPerUnit } = TextUtils; - const fontString = TextUtils.getNativeFontString(name, fontSize, fontStyle); - const fontSizeInfo = TextUtils.measureFont(fontString); const fontHash = TextUtils.getNativeFontHash(name, fontSize, fontStyle); const widthInPixel = renderer.width * _pixelsPerUnit; const heightInPixel = renderer.height * _pixelsPerUnit; const textMetrics = renderer.enableWrapping ? CharAssembler._measureTextWithWrap(renderer) : CharAssembler._measureTextWithoutWrap(renderer); - const { width, height, lines, lineWidths, lineHeight } = textMetrics; + const { height, lines, lineWidths, lineHeight } = textMetrics; + const textureSize = CharAssembler._charUtils.getTextureSize(); let startY = 0; switch (verticalAlignment) { @@ -98,7 +97,6 @@ export class CharAssembler { startX = widthInPixel * 0.5 - lineWidth; break; } - startY -= (i + 0/.5) * lineHeight; for (let j = 0, m = line.length; j < m; ++j) { const char = line[j]; @@ -109,20 +107,19 @@ export class CharAssembler { const { renderData, localPositions } = charRenderData; charRenderData.texture = charDefWithTexture.texture; renderData.color = color; - const textureSize = CharAssembler._charUtils.getTextureSize(); const { uvs } = renderData; const { x, y, w, h} = charDef; const left = startX / _pixelsPerUnit; const right = (startX + charDef.w) / _pixelsPerUnit; const top = (startY + charDef.offsetY + h * 0.5) / _pixelsPerUnit; - const bottom = (top - h) / _pixelsPerUnit; + const bottom = top - h / _pixelsPerUnit; // Top-left. localPositions[0].setValue(left, top, 0); uvs[0].setValue(x / textureSize, y / textureSize); // Top-right. localPositions[1].setValue(right, top, 0); - uvs[1].setValue((x + w) / textureSize, h / textureSize); + uvs[1].setValue((x + w) / textureSize, y / textureSize); // Bottom-right. localPositions[2].setValue(right, bottom, 0); uvs[2].setValue((x + w) / textureSize, (y + h) / textureSize); @@ -133,6 +130,8 @@ export class CharAssembler { _charRenderDatas.push(charRenderData); startX += charDef.xAdvance; } + + startY -= lineHeight; } } diff --git a/packages/core/src/2d/text/TextUtils.ts b/packages/core/src/2d/text/TextUtils.ts index 5f1a1503d4..e4bd2be5bb 100644 --- a/packages/core/src/2d/text/TextUtils.ts +++ b/packages/core/src/2d/text/TextUtils.ts @@ -222,6 +222,7 @@ export class TextUtils { public static measureChar(char: string, fontString: string): CharMetrics { const { canvas, context } = TextUtils.textContext(); + context.font = fontString; const width = Math.ceil(context.measureText(char).width); let baseline = Math.ceil(context.measureText(TextUtils._measureBaseline).width); const height = baseline * TextUtils._heightMultiplier; From 40473e163aa191a42e99ee68bcf6675bd85cc6eb Mon Sep 17 00:00:00 2001 From: singlecoder Date: Tue, 14 Jun 2022 18:11:11 +0800 Subject: [PATCH 06/55] feat(text): opt code --- .../core/src/2d/assembler/CharAssembler.ts | 144 ++++++++++++------ .../src/2d/assembler/CharRenderDataPool.ts | 2 +- packages/core/src/2d/assembler/CharUtils.ts | 24 ++- packages/core/src/2d/data/RenderData2D.ts | 2 + packages/core/src/2d/text/TextUtils.ts | 1 + 5 files changed, 123 insertions(+), 50 deletions(-) diff --git a/packages/core/src/2d/assembler/CharAssembler.ts b/packages/core/src/2d/assembler/CharAssembler.ts index aa80130bfd..ac185f1337 100644 --- a/packages/core/src/2d/assembler/CharAssembler.ts +++ b/packages/core/src/2d/assembler/CharAssembler.ts @@ -2,7 +2,7 @@ import { Vector3 } from "@oasis-engine/math"; import { TextHorizontalAlignment, TextVerticalAlignment } from "../enums/TextAlignment"; import { OverflowMode } from "../enums/TextOverflow"; import { TextRenderer, DirtyFlag } from "../text/TextRenderer"; -import { TextUtils, TextMetrics } from "../text/TextUtils"; +import { TextUtils, TextMetrics, FontSizeInfo } from "../text/TextUtils"; import { CharRenderDataPool } from "./CharRenderDataPool"; import { CharDefWithTexture, CharUtils } from "./CharUtils"; import { IAssembler } from "./IAssembler"; @@ -60,78 +60,86 @@ export class CharAssembler { const { color, fontSize, fontStyle, horizontalAlignment, verticalAlignment, _charRenderDatas } = renderer; const { name } = renderer.font; const { _pixelsPerUnit } = TextUtils; + const pixelsPerUnitReciprocal = 1.0 / _pixelsPerUnit; const fontHash = TextUtils.getNativeFontHash(name, fontSize, fontStyle); - const widthInPixel = renderer.width * _pixelsPerUnit; - const heightInPixel = renderer.height * _pixelsPerUnit; - - const textMetrics = renderer.enableWrapping ? CharAssembler._measureTextWithWrap(renderer) : CharAssembler._measureTextWithoutWrap(renderer); - const { height, lines, lineWidths, lineHeight } = textMetrics; - const textureSize = CharAssembler._charUtils.getTextureSize(); - - let startY = 0; - switch (verticalAlignment) { - case TextVerticalAlignment.Top: - startY = heightInPixel * 0.5; - break; - case TextVerticalAlignment.Center: - startY = height * 0.5; - break; - case TextVerticalAlignment.Bottom: - startY = height - heightInPixel * 0.5; - break; - } + const rendererWidth = renderer.width * _pixelsPerUnit; + const rendererHeight = renderer.height * _pixelsPerUnit; + + const textMetrics = renderer.enableWrapping + ? CharAssembler._measureTextWithWrap(renderer) + : CharAssembler._measureTextWithoutWrap(renderer); + const { height, lines, lineWidths, lineHeight, lineMaxSizes } = textMetrics; + const { _charUtils, _charRenderDataPool } = CharAssembler; + const textureSizeReciprocal = 1.0 / _charUtils.getTextureSize(); for (let i = 0, n = lines.length; i < n; ++i) { const line = lines[i]; const lineWidth = lineWidths[i]; + const sizeInfo = lineMaxSizes[i]; + const { ascent: maxAscent } = sizeInfo; + const halfLineHeightDiff = (lineHeight - sizeInfo.size) * 0.5; let startX = 0; switch (horizontalAlignment) { case TextHorizontalAlignment.Left: - startX = -widthInPixel * 0.5; + startX = -rendererWidth * 0.5; break; case TextHorizontalAlignment.Center: startX = -lineWidth * 0.5; break; case TextHorizontalAlignment.Right: - startX = widthInPixel * 0.5 - lineWidth; + startX = rendererWidth * 0.5 - lineWidth; + break; + } + let startY = 0; + switch (verticalAlignment) { + case TextVerticalAlignment.Top: + startY = rendererHeight * 0.5 + halfLineHeightDiff - i * lineHeight; + break; + case TextVerticalAlignment.Center: + startY = height * 0.5 - i * lineHeight; + break; + case TextVerticalAlignment.Bottom: + startY = height - rendererHeight * 0.5 - halfLineHeightDiff - i * lineHeight; break; } for (let j = 0, m = line.length; j < m; ++j) { const char = line[j]; const key = `${fontHash}${char.charCodeAt(0)}`; - const charDefWithTexture = CharAssembler._charUtils.getCharDef(key); + const charDefWithTexture = _charUtils.getCharDef(key); const { charDef } = charDefWithTexture; - const charRenderData = CharAssembler._charRenderDataPool.getData(); + const charRenderData = _charRenderDataPool.getData(); const { renderData, localPositions } = charRenderData; charRenderData.texture = charDefWithTexture.texture; renderData.color = color; const { uvs } = renderData; - const { x, y, w, h} = charDef; - const left = startX / _pixelsPerUnit; - const right = (startX + charDef.w) / _pixelsPerUnit; - const top = (startY + charDef.offsetY + h * 0.5) / _pixelsPerUnit; - const bottom = top - h / _pixelsPerUnit; + const { x, y, w, h } = charDef; + const left = startX * pixelsPerUnitReciprocal; + const right = (startX + w) * pixelsPerUnitReciprocal; + const top = (startY - halfLineHeightDiff - maxAscent + h * 0.5 + charDef.offsetY) * pixelsPerUnitReciprocal; + const bottom = top - h * pixelsPerUnitReciprocal; + const u0 = x * textureSizeReciprocal; + const u1 = (x + w) * textureSizeReciprocal; + const v0 = y * textureSizeReciprocal; + const v1 = (y + h) * textureSizeReciprocal; // Top-left. localPositions[0].setValue(left, top, 0); - uvs[0].setValue(x / textureSize, y / textureSize); + uvs[0].setValue(u0, v0); // Top-right. localPositions[1].setValue(right, top, 0); - uvs[1].setValue((x + w) / textureSize, y / textureSize); + uvs[1].setValue(u1, v0); // Bottom-right. localPositions[2].setValue(right, bottom, 0); - uvs[2].setValue((x + w) / textureSize, (y + h) / textureSize); + uvs[2].setValue(u1, v1); // Bottom-left. localPositions[3].setValue(left, bottom, 0); - uvs[3].setValue(x / textureSize, (y + h) / textureSize); + uvs[3].setValue(u0, v1); _charRenderDatas.push(charRenderData); startX += charDef.xAdvance; } - - startY -= lineHeight; } } @@ -144,6 +152,7 @@ export class CharAssembler { const subTexts = renderer.text.split(/(?:\r\n|\r|\n)/); const lines = new Array(); const lineWidths = new Array(); + const lineMaxSizes = new Array(); const { _pixelsPerUnit } = TextUtils; const lineHeight = fontSizeInfo.size + renderer.lineSpacing * _pixelsPerUnit; const wrapWidth = renderer.width * _pixelsPerUnit; @@ -153,30 +162,55 @@ export class CharAssembler { const subText = subTexts[i]; let chars = ""; let charsWidth = 0; - + let maxAscent = -1; + let maxDescent = -1; + for (let j = 0, m = subText.length; j < m; ++j) { const char = subText[j]; const charDefWithTexture = CharAssembler._getCharDefWithTexture(char, fontString, fontHash); const { charDef } = charDefWithTexture; - if (charsWidth + charDef.w > wrapWidth) { + const { w, offsetY } = charDef; + const halfH = charDef.h * 0.5; + const ascent = halfH + offsetY; + const descent = halfH - offsetY; + if (charsWidth + w > wrapWidth) { if (charsWidth === 0) { lines.push(char); - lineWidths.push(charDef.w); + lineWidths.push(w); + lineMaxSizes.push({ + ascent, + descent, + size: ascent + descent + }); } else { lines.push(chars); lineWidths.push(charsWidth); + lineMaxSizes.push({ + ascent: maxAscent, + descent: maxDescent, + size: maxAscent + maxDescent + }); chars = char; charsWidth = charDef.xAdvance; + maxAscent = ascent; + maxDescent = descent; } } else { chars += char; charsWidth += charDef.xAdvance; + maxAscent < ascent && (maxAscent = ascent); + maxDescent < descent && (maxDescent = descent); } } if (charsWidth > 0) { lines.push(chars); lineWidths.push(charsWidth); + lineMaxSizes.push({ + ascent: maxAscent, + descent: maxDescent, + size: maxAscent + maxDescent + }); } } @@ -190,7 +224,8 @@ export class CharAssembler { height, lines, lineWidths, - lineHeight + lineHeight, + lineMaxSizes }; } @@ -203,6 +238,7 @@ export class CharAssembler { const lines = renderer.text.split(/(?:\r\n|\r|\n)/); const lineCount = lines.length; const lineWidths = new Array(); + const lineMaxSizes = new Array(); const { _pixelsPerUnit } = TextUtils; const lineHeight = fontSizeInfo.size + renderer.lineSpacing * _pixelsPerUnit; let width = 0; @@ -214,12 +250,26 @@ export class CharAssembler { for (let i = 0; i < lineCount; ++i) { const line = lines[i]; let curWidth = 0; - + let maxAscent = -1; + let maxDescent = -1; + for (let j = 0, m = line.length; j < m; ++j) { const charDefWithTexture = CharAssembler._getCharDefWithTexture(line[j], fontString, fontHash); - curWidth += charDefWithTexture.charDef.xAdvance; + const { charDef } = charDefWithTexture; + curWidth += charDef.xAdvance; + const { offsetY } = charDef; + const halfH = charDef.h * 0.5; + const ascent = halfH + offsetY; + const descent = halfH - offsetY; + 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; } @@ -230,7 +280,8 @@ export class CharAssembler { height, lines, lineWidths, - lineHeight + lineHeight, + lineMaxSizes }; } @@ -243,7 +294,14 @@ export class CharAssembler { const { sizeInfo } = charMetrics; const { ascent, descent } = sizeInfo; const offsetY = (ascent - descent) * 0.5; - charDefWithTexture = _charUtils.addCharDef(key, TextUtils.textContext().canvas, charMetrics.width, sizeInfo.size, 0, offsetY); + charDefWithTexture = _charUtils.addCharDef( + key, + TextUtils.textContext().canvas, + charMetrics.width, + sizeInfo.size, + 0, + offsetY + ); } return charDefWithTexture; diff --git a/packages/core/src/2d/assembler/CharRenderDataPool.ts b/packages/core/src/2d/assembler/CharRenderDataPool.ts index 5edf49ca10..3c384d4a98 100644 --- a/packages/core/src/2d/assembler/CharRenderDataPool.ts +++ b/packages/core/src/2d/assembler/CharRenderDataPool.ts @@ -57,7 +57,7 @@ export class CharRenderDataPool { triangles[0] = 0, triangles[1] = 2, triangles[2] = 1; triangles[3] = 2, triangles[4] = 0, triangles[5] = 3; - const renderData: RenderData2D = { positions, uvs, triangles, color }; + const renderData: RenderData2D = { positions, uvs, triangles, color, vertexCount: 4 }; return { texture, renderData, localPositions }; } diff --git a/packages/core/src/2d/assembler/CharUtils.ts b/packages/core/src/2d/assembler/CharUtils.ts index ad3b67d5fa..b44ca60aa9 100644 --- a/packages/core/src/2d/assembler/CharUtils.ts +++ b/packages/core/src/2d/assembler/CharUtils.ts @@ -1,6 +1,7 @@ import { Engine } from "../../Engine"; import { Texture2D } from "../../texture"; import { CharDef, FontAtlas } from "../atlas/FontAtlas"; +// import { Sprite, SpriteRenderer } from "../sprite"; export interface CharDefWithTexture { texture: Texture2D; @@ -10,8 +11,8 @@ export interface CharDefWithTexture { export class CharUtils { private _fontAtlasList: Array = []; private _curFontAtlas: FontAtlas = null; - private _textureSize: number = 1024; - private _space: number = 1; + private _textureSize: number = 512; + private _space: number = 0; private _curX: number = 1; private _curY: number = 1; private _nextY: number = 1; @@ -29,7 +30,7 @@ export class CharUtils { width: number, height: number, offsetX: number, - offsetY: number, + offsetY: number ): CharDefWithTexture { const { _space: space, _textureSize: textureSize } = this; @@ -39,9 +40,15 @@ export class CharUtils { this._curY = this._nextY + space; } const endY = this._curY + height + space; + if (endY > this._nextY) { + this._nextY = endY; + } if (endY >= textureSize) { this._createFontAtlas(); + this._curX = 1; + this._curY = 1; + this._nextY = 1; if (this._curX + width + space >= textureSize || this._curY + height + space >= textureSize) { throw Error("The char fontSize is too large."); } @@ -106,8 +113,13 @@ export class CharUtils { tex._addRefCount(1); this._curFontAtlas = new FontAtlas(engine, tex); this._fontAtlasList.push(this._curFontAtlas); - this._curX = 1; - this._curY = 1; - this._nextY = 1; + + // // 测试代码 + // const scene = engine.sceneManager.activeScene; + // const rootEntity = scene.getRootEntity(); + // const testEntity = rootEntity.createChild("test"); + // testEntity.transform.setPosition(0, 0, -10); + // const sprite = new Sprite(engine, tex); + // testEntity.addComponent(SpriteRenderer).sprite = sprite; } } diff --git a/packages/core/src/2d/data/RenderData2D.ts b/packages/core/src/2d/data/RenderData2D.ts index a77e2630ec..f520e84419 100644 --- a/packages/core/src/2d/data/RenderData2D.ts +++ b/packages/core/src/2d/data/RenderData2D.ts @@ -1,6 +1,8 @@ import { Color, Vector2, Vector3 } from "@oasis-engine/math"; export interface RenderData2D { + /** @internal */ + vertexCount: number; /** @internal */ positions: Vector3[]; /** @internal */ diff --git a/packages/core/src/2d/text/TextUtils.ts b/packages/core/src/2d/text/TextUtils.ts index e4bd2be5bb..51f4f28fc2 100644 --- a/packages/core/src/2d/text/TextUtils.ts +++ b/packages/core/src/2d/text/TextUtils.ts @@ -32,6 +32,7 @@ export interface TextMetrics { lines: Array; lineWidths: Array; lineHeight: number; + lineMaxSizes?: Array; } /** From 3bdd77374b151f822cd2e00c0dba74ef01427be5 Mon Sep 17 00:00:00 2001 From: singlecoder Date: Tue, 14 Jun 2022 18:16:54 +0800 Subject: [PATCH 07/55] feat(text): opt code --- packages/core/src/2d/assembler/TextAssembler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/2d/assembler/TextAssembler.ts b/packages/core/src/2d/assembler/TextAssembler.ts index 000acc80b0..3f7f203f96 100644 --- a/packages/core/src/2d/assembler/TextAssembler.ts +++ b/packages/core/src/2d/assembler/TextAssembler.ts @@ -23,7 +23,7 @@ export class TextAssembler { triangles[0] = 0, triangles[1] = 2, triangles[2] = 1; triangles[3] = 2, triangles[4] = 0, triangles[5] = 3; - renderer._renderData = { positions, uvs, triangles, color}; + renderer._renderData = { positions, uvs, triangles, color, vertexCount: 4 }; } static updateData(renderer: TextRenderer): void { From 1a4a6978252b880a4436ad0260d6c75b91f2496a Mon Sep 17 00:00:00 2001 From: singlecoder Date: Wed, 15 Jun 2022 14:11:18 +0800 Subject: [PATCH 08/55] feat(text): opt code and fix bounds --- .../core/src/2d/assembler/CharAssembler.ts | 71 ++++++++++--------- .../core/src/2d/assembler/TextAssembler.ts | 8 +++ packages/core/src/2d/text/TextRenderer.ts | 49 ++++++++++++- 3 files changed, 94 insertions(+), 34 deletions(-) diff --git a/packages/core/src/2d/assembler/CharAssembler.ts b/packages/core/src/2d/assembler/CharAssembler.ts index ac185f1337..1167525f4c 100644 --- a/packages/core/src/2d/assembler/CharAssembler.ts +++ b/packages/core/src/2d/assembler/CharAssembler.ts @@ -18,16 +18,12 @@ export class CharAssembler { CharAssembler._charUtils = new CharUtils(renderer.engine); CharAssembler._charRenderDataPool = new CharRenderDataPool(); } - const { _charRenderDatas } = renderer; - for (let i = 0, l = _charRenderDatas.length; i < l; ++i) { - CharAssembler._charRenderDataPool.putData(_charRenderDatas[i]); - } - _charRenderDatas.length = 0; } static updateData(renderer: TextRenderer): void { const isTextureDirty = renderer._isContainDirtyFlag(DirtyFlag.Property); if (isTextureDirty) { + CharAssembler.clearData(renderer); CharAssembler._updateText(renderer); renderer._setDirtyFlagFalse(DirtyFlag.Property); } @@ -45,6 +41,14 @@ export class CharAssembler { } } + static clearData(renderer: TextRenderer): void { + const { _charRenderDatas } = renderer; + for (let i = 0, l = _charRenderDatas.length; i < l; ++i) { + CharAssembler._charRenderDataPool.putData(_charRenderDatas[i]); + } + _charRenderDatas.length = 0; + } + private static _updatePosition(renderer: TextRenderer): void { const worldMatrix = renderer.entity.transform.worldMatrix; const { _charRenderDatas } = renderer; @@ -109,35 +113,38 @@ export class CharAssembler { const key = `${fontHash}${char.charCodeAt(0)}`; const charDefWithTexture = _charUtils.getCharDef(key); const { charDef } = charDefWithTexture; - const charRenderData = _charRenderDataPool.getData(); - const { renderData, localPositions } = charRenderData; - charRenderData.texture = charDefWithTexture.texture; - renderData.color = color; - const { uvs } = renderData; - const { x, y, w, h } = charDef; - const left = startX * pixelsPerUnitReciprocal; - const right = (startX + w) * pixelsPerUnitReciprocal; - const top = (startY - halfLineHeightDiff - maxAscent + h * 0.5 + charDef.offsetY) * pixelsPerUnitReciprocal; - const bottom = top - h * pixelsPerUnitReciprocal; - const u0 = x * textureSizeReciprocal; - const u1 = (x + w) * textureSizeReciprocal; - const v0 = y * textureSizeReciprocal; - const v1 = (y + h) * textureSizeReciprocal; - // Top-left. - localPositions[0].setValue(left, top, 0); - uvs[0].setValue(u0, v0); - // Top-right. - localPositions[1].setValue(right, top, 0); - uvs[1].setValue(u1, v0); - // Bottom-right. - localPositions[2].setValue(right, bottom, 0); - uvs[2].setValue(u1, v1); - // Bottom-left. - localPositions[3].setValue(left, bottom, 0); - uvs[3].setValue(u0, v1); + if (charDef.h > 0) { + const charRenderData = _charRenderDataPool.getData(); + const { renderData, localPositions } = charRenderData; + charRenderData.texture = charDefWithTexture.texture; + renderData.color = color; + + const { uvs } = renderData; + const { x, y, w, h } = charDef; + const left = startX * pixelsPerUnitReciprocal; + const right = (startX + w) * pixelsPerUnitReciprocal; + const top = (startY - halfLineHeightDiff - maxAscent + h * 0.5 + charDef.offsetY) * pixelsPerUnitReciprocal; + const bottom = top - h * pixelsPerUnitReciprocal; + const u0 = x * textureSizeReciprocal; + const u1 = (x + w) * textureSizeReciprocal; + const v0 = y * textureSizeReciprocal; + const v1 = (y + h) * textureSizeReciprocal; + // Top-left. + localPositions[0].setValue(left, top, 0); + uvs[0].setValue(u0, v0); + // Top-right. + localPositions[1].setValue(right, top, 0); + uvs[1].setValue(u1, v0); + // Bottom-right. + localPositions[2].setValue(right, bottom, 0); + uvs[2].setValue(u1, v1); + // Bottom-left. + localPositions[3].setValue(left, bottom, 0); + uvs[3].setValue(u0, v1); - _charRenderDatas.push(charRenderData); + _charRenderDatas.push(charRenderData); + } startX += charDef.xAdvance; } } diff --git a/packages/core/src/2d/assembler/TextAssembler.ts b/packages/core/src/2d/assembler/TextAssembler.ts index 3f7f203f96..e0c1448209 100644 --- a/packages/core/src/2d/assembler/TextAssembler.ts +++ b/packages/core/src/2d/assembler/TextAssembler.ts @@ -41,6 +41,14 @@ export class TextAssembler { renderer._renderData.color = renderer.color; } + static clearData(renderer: TextRenderer): void { + const { positions, uvs, triangles } = renderer._renderData; + positions.length = 0; + uvs.length = 0; + triangles.length = 0; + renderer._renderData = null; + } + private static _updatePosition(renderer: TextRenderer): void { const localPositions = renderer._sprite._positions; const localVertexPos = TextAssembler._tempVec3; diff --git a/packages/core/src/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts index 2c15db9e37..c92b35c0c8 100644 --- a/packages/core/src/2d/text/TextRenderer.ts +++ b/packages/core/src/2d/text/TextRenderer.ts @@ -19,6 +19,8 @@ import { Font } from "./Font"; * Renders a text for 2D graphics. */ export class TextRenderer extends Renderer { + private static _tempBounds: BoundingBox = new BoundingBox(); + /** @internal */ @ignoreClone _sprite: Sprite = null; @@ -189,6 +191,18 @@ export class TextRenderer extends Renderer { if (this._useCharCache !== value) { this._useCharCache = value; this._setDirtyFlagTrue(DirtyFlag.Property); + + if (value) { + this._clearTexture(); + this._sprite.destroy(); + this._sprite = null; + TextAssembler.clearData(this); + CharAssembler.resetData(this); + } else { + CharAssembler.clearData(this); + this._sprite = new Sprite(this.engine); + TextAssembler.resetData(this); + } } } @@ -279,7 +293,6 @@ export class TextRenderer extends Renderer { this._isWorldMatrixDirty = entity.transform.registerWorldChangeFlag(); this._sprite = new Sprite(engine); TextAssembler.resetData(this); - CharAssembler.resetData(this); this.font = Font.createFromOS(engine); this.setMaterial(engine._spriteDefaultMaterial); } @@ -369,7 +382,39 @@ export class TextRenderer extends Renderer { */ protected _updateBounds(worldBounds: BoundingBox): void { const worldMatrix = this._entity.transform.worldMatrix; - BoundingBox.transform(this._sprite.bounds, worldMatrix, worldBounds); + if (this._useCharCache) { + const bounds = TextRenderer._tempBounds; + const { min, max } = bounds; + min.setValue(0, 0, 0); + max.setValue(0, 0, 0); + const { _charRenderDatas } = this; + const dataLen = _charRenderDatas.length; + if (dataLen > 0) { + const charRenderData = _charRenderDatas[0]; + const { localPositions } = charRenderData; + let minPos = localPositions[3]; + let maxPos = localPositions[1]; + let minX = minPos.x; + let minY = minPos.y; + let maxX = maxPos.x; + let maxY = maxPos.y; + + for (let i = 1; i < dataLen; ++i) { + const { localPositions } = _charRenderDatas[i]; + let minPos = localPositions[3]; + let maxPos = localPositions[1]; + minX > minPos.x && (minX = minPos.x); + minY > minPos.y && (minY = minPos.y); + maxX < maxPos.x && (maxX = maxPos.x); + maxY < maxPos.y && (maxY = maxPos.y); + } + min.setValue(minX, minY, 0); + max.setValue(maxX, maxY, 0); + } + BoundingBox.transform(bounds, worldMatrix, worldBounds); + } else { + BoundingBox.transform(this._sprite.bounds, worldMatrix, worldBounds); + } } private _updateStencilState(): void { From fe6029b294318d5929642d166292e7e99e4d54fc Mon Sep 17 00:00:00 2001 From: singlecoder Date: Thu, 16 Jun 2022 20:15:29 +0800 Subject: [PATCH 09/55] feat(text): render text for char cache --- .../core/src/2d/assembler/CharAssembler.ts | 53 ++- packages/core/src/2d/assembler/CharUtils.ts | 26 +- .../core/src/2d/assembler/TextAssembler.ts | 184 +++++++- packages/core/src/2d/data/RenderData2D.ts | 3 + .../src/2d/dynamic-atlas/DynamicTextAtlas.ts | 5 +- packages/core/src/2d/sprite/SpriteRenderer.ts | 4 +- packages/core/src/2d/text/TextRenderer.ts | 12 +- packages/core/src/2d/text/TextUtils.ts | 403 +++--------------- .../core/src/RenderPipeline/SpriteBatcher.ts | 7 +- .../core/src/RenderPipeline/SpriteElement.ts | 4 + 10 files changed, 311 insertions(+), 390 deletions(-) diff --git a/packages/core/src/2d/assembler/CharAssembler.ts b/packages/core/src/2d/assembler/CharAssembler.ts index 1167525f4c..26799c9cb1 100644 --- a/packages/core/src/2d/assembler/CharAssembler.ts +++ b/packages/core/src/2d/assembler/CharAssembler.ts @@ -74,14 +74,28 @@ export class CharAssembler { : CharAssembler._measureTextWithoutWrap(renderer); const { height, lines, lineWidths, lineHeight, lineMaxSizes } = textMetrics; const { _charUtils, _charRenderDataPool } = CharAssembler; + const halfLineHeight = lineHeight * 0.5; const textureSizeReciprocal = 1.0 / _charUtils.getTextureSize(); + const linesLen = lines.length; - for (let i = 0, n = lines.length; i < n; ++i) { + 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; + } + + for (let i = 0; i < linesLen; ++i) { const line = lines[i]; const lineWidth = lineWidths[i]; - const sizeInfo = lineMaxSizes[i]; - const { ascent: maxAscent } = sizeInfo; - const halfLineHeightDiff = (lineHeight - sizeInfo.size) * 0.5; let startX = 0; switch (horizontalAlignment) { @@ -95,18 +109,6 @@ export class CharAssembler { startX = rendererWidth * 0.5 - lineWidth; break; } - let startY = 0; - switch (verticalAlignment) { - case TextVerticalAlignment.Top: - startY = rendererHeight * 0.5 + halfLineHeightDiff - i * lineHeight; - break; - case TextVerticalAlignment.Center: - startY = height * 0.5 - i * lineHeight; - break; - case TextVerticalAlignment.Bottom: - startY = height - rendererHeight * 0.5 - halfLineHeightDiff - i * lineHeight; - break; - } for (let j = 0, m = line.length; j < m; ++j) { const char = line[j]; @@ -122,10 +124,16 @@ export class CharAssembler { const { uvs } = renderData; const { x, y, w, h } = charDef; + + const { offsetY } = charDef; + const halfH = charDef.h * 0.5; + const ascent = halfH + offsetY; + const descent = halfH - offsetY; + const left = startX * pixelsPerUnitReciprocal; const right = (startX + w) * pixelsPerUnitReciprocal; - const top = (startY - halfLineHeightDiff - maxAscent + h * 0.5 + charDef.offsetY) * pixelsPerUnitReciprocal; - const bottom = top - h * pixelsPerUnitReciprocal; + const top = (startY + ascent) * pixelsPerUnitReciprocal; + const bottom = (startY - descent + 1) * pixelsPerUnitReciprocal; const u0 = x * textureSizeReciprocal; const u1 = (x + w) * textureSizeReciprocal; const v0 = y * textureSizeReciprocal; @@ -147,6 +155,8 @@ export class CharAssembler { } startX += charDef.xAdvance; } + + startY -= lineHeight; } } @@ -298,16 +308,17 @@ export class CharAssembler { let charDefWithTexture = _charUtils.getCharDef(key); if (!charDefWithTexture) { const charMetrics = TextUtils.measureChar(char, fontString); - const { sizeInfo } = charMetrics; + const { width, sizeInfo } = charMetrics; const { ascent, descent } = sizeInfo; const offsetY = (ascent - descent) * 0.5; charDefWithTexture = _charUtils.addCharDef( key, TextUtils.textContext().canvas, - charMetrics.width, + width, sizeInfo.size, 0, - offsetY + offsetY, + width ); } diff --git a/packages/core/src/2d/assembler/CharUtils.ts b/packages/core/src/2d/assembler/CharUtils.ts index b44ca60aa9..0451a1491d 100644 --- a/packages/core/src/2d/assembler/CharUtils.ts +++ b/packages/core/src/2d/assembler/CharUtils.ts @@ -1,7 +1,6 @@ import { Engine } from "../../Engine"; import { Texture2D } from "../../texture"; import { CharDef, FontAtlas } from "../atlas/FontAtlas"; -// import { Sprite, SpriteRenderer } from "../sprite"; export interface CharDefWithTexture { texture: Texture2D; @@ -12,7 +11,7 @@ export class CharUtils { private _fontAtlasList: Array = []; private _curFontAtlas: FontAtlas = null; private _textureSize: number = 512; - private _space: number = 0; + private _space: number = 1; private _curX: number = 1; private _curY: number = 1; private _nextY: number = 1; @@ -30,16 +29,19 @@ export class CharUtils { width: number, height: number, offsetX: number, - offsetY: number + offsetY: number, + xAdvance: number, ): CharDefWithTexture { const { _space: space, _textureSize: textureSize } = this; - const endX = this._curX + width + space; + const offsetWidth = width + space; + const endX = this._curX + offsetWidth; if (endX >= textureSize) { this._curX = space; this._curY = this._nextY + space; } - const endY = this._curY + height + space; + const offsetHeight = height + space; + const endY = this._curY + offsetHeight; if (endY > this._nextY) { this._nextY = endY; } @@ -49,7 +51,7 @@ export class CharUtils { this._curX = 1; this._curY = 1; this._nextY = 1; - if (this._curX + width + space >= textureSize || this._curY + height + space >= textureSize) { + if (this._curX + offsetWidth >= textureSize || this._curY + offsetHeight >= textureSize) { throw Error("The char fontSize is too large."); } } @@ -67,10 +69,10 @@ export class CharUtils { h: height, offsetX, offsetY, - xAdvance: width + space + xAdvance }; this._curFontAtlas.addCharDef(key, charDef); - this._curX = endX + space; + this._curX += offsetWidth + space; return { texture: curTexture, @@ -113,13 +115,5 @@ export class CharUtils { tex._addRefCount(1); this._curFontAtlas = new FontAtlas(engine, tex); this._fontAtlasList.push(this._curFontAtlas); - - // // 测试代码 - // const scene = engine.sceneManager.activeScene; - // const rootEntity = scene.getRootEntity(); - // const testEntity = rootEntity.createChild("test"); - // testEntity.transform.setPosition(0, 0, -10); - // const sprite = new Sprite(engine, tex); - // testEntity.addComponent(SpriteRenderer).sprite = sprite; } } diff --git a/packages/core/src/2d/assembler/TextAssembler.ts b/packages/core/src/2d/assembler/TextAssembler.ts index e0c1448209..6e43fb949e 100644 --- a/packages/core/src/2d/assembler/TextAssembler.ts +++ b/packages/core/src/2d/assembler/TextAssembler.ts @@ -1,16 +1,20 @@ import { Color, Vector2, Vector3 } from "@oasis-engine/math"; import { Texture2D } from "../../texture"; import { TextHorizontalAlignment, TextVerticalAlignment } from "../enums/TextAlignment"; +import { OverflowMode } from "../enums/TextOverflow"; import { TextRenderer, DirtyFlag } from "../text/TextRenderer"; -import { TextUtils } from "../text/TextUtils"; +import { TextUtils, TextMetrics } from "../text/TextUtils"; import { IAssembler } from "./IAssembler"; import { StaticInterfaceImplement } from "./StaticInterfaceImplement"; @StaticInterfaceImplement() export class TextAssembler { + private static _tempVec2: Vector2 = new Vector2(); private static _tempVec3: Vector3 = new Vector3(); + private static _maxWidth: number = 1024; + private static _maxHeight: number = 1024; - public static resetData(renderer: TextRenderer) { + static resetData(renderer: TextRenderer) { const positions: Array = []; const uvs: Array = []; const triangles: Array = []; @@ -23,7 +27,7 @@ export class TextAssembler { triangles[0] = 0, triangles[1] = 2, triangles[2] = 1; triangles[3] = 2, triangles[4] = 0, triangles[5] = 3; - renderer._renderData = { positions, uvs, triangles, color, vertexCount: 4 }; + renderer._renderData = { positions, uvs, triangles, color, vertexCount: 4, texture: null }; } static updateData(renderer: TextRenderer): void { @@ -65,7 +69,7 @@ export class TextAssembler { private static _updateText(renderer: TextRenderer): void { const { width: originWidth, height: originHeight, enableWrapping, overflowMode } = renderer; const fontString = TextUtils.getNativeFontString(renderer.font.name, renderer.fontSize, renderer.fontStyle); - const textMetrics = TextUtils.measureText( + const textMetrics = TextAssembler._measureText( renderer.text, originWidth, originHeight, @@ -74,10 +78,94 @@ export class TextAssembler { overflowMode, fontString ); - TextUtils.updateText(textMetrics, fontString, renderer.horizontalAlignment, renderer.verticalAlignment); + TextAssembler._fillText(textMetrics, fontString, renderer.horizontalAlignment, renderer.verticalAlignment); TextAssembler._updateTexture(renderer); } + private static _measureText( + text: string, + originWidth: number, + originHeight: number, + lineSpacing: number, + enableWrapping: boolean, + overflowMode: OverflowMode, + fontString: string + ): TextMetrics { + const { _pixelsPerUnit } = TextUtils; + const fontSize = TextUtils.measureFont(fontString).size; + const context = TextUtils.textContext().context; + const lines = TextAssembler._wordWrap(text, originWidth, enableWrapping, fontString); + const lineCount = lines.length; + const lineWidths = new Array(); + const lineHeight = fontSize + lineSpacing * _pixelsPerUnit; + context.font = fontString; + // Calculate max width of all lines. + let width = 0; + for (let i = 0; i < lineCount; ++i) { + const lineWidth = context.measureText(lines[i]).width; + if (lineWidth > width) { + width = lineWidth; + } + lineWidths.push(lineWidth); + } + + // Reset width and height. + let height = originHeight * _pixelsPerUnit; + if (overflowMode === OverflowMode.Overflow) { + height = Math.min(lineHeight * lineCount, TextAssembler._maxHeight); + } + + return { + width, + height, + lines, + lineWidths, + lineHeight + }; + } + + private static _fillText( + textMetrics: TextMetrics, + fontString: string, + horizontalAlignment: TextHorizontalAlignment, + verticalAlignment: TextVerticalAlignment + ): void { + const { canvas, context } = TextUtils.textContext(); + const { width, height } = textMetrics; + // reset canvas's width and height. + canvas.width = width; + canvas.height = height; + // clear canvas. + context.font = fontString; + context.clearRect(0, 0, width, height); + // set canvas font info. + context.textBaseline = "middle"; + context.fillStyle = "#fff"; + + // draw lines. + const { lines, lineHeight, lineWidths } = textMetrics; + const halfLineHeight = lineHeight * 0.5; + for (let i = 0, l = lines.length; i < l; ++i) { + const lineWidth = lineWidths[i]; + const pos = TextAssembler._tempVec2; + TextAssembler._calculateLinePosition( + width, + height, + lineWidth, + lineHeight, + i, + l, + horizontalAlignment, + verticalAlignment, + pos + ); + const { x, y } = pos; + if (y + lineHeight >= 0 && y < height) { + context.fillText(lines[i], x, y + halfLineHeight); + } + } + } + private static _updateTexture(renderer: TextRenderer): void { const trimData = TextUtils.trimCanvas(); const { width, height } = trimData; @@ -123,4 +211,90 @@ export class TextAssembler { sprite._updateMesh(); renderer._renderData.uvs = sprite._uv; } + + private static _wordWrap(text: string, width: number, enableWrapping: boolean, fontString: string): Array { + const { context } = TextUtils.textContext(); + const { _maxWidth: maxWidth } = TextAssembler; + const widthInPixel = width * TextUtils._pixelsPerUnit; + const wrapWidth = Math.min(widthInPixel, maxWidth); + const wrappedSubTexts = new Array(); + const subTexts = text.split(/(?:\r\n|\r|\n)/); + context.font = fontString; + + for (let i = 0, n = subTexts.length; i < n; ++i) { + const subText = subTexts[i]; + const subWidth = context.measureText(subText).width; + const needWrap = enableWrapping || subWidth > maxWidth; + + if (needWrap) { + const autoWrapWidth = enableWrapping ? wrapWidth : maxWidth; + if (subWidth <= autoWrapWidth) { + wrappedSubTexts.push(subText); + } else { + let chars = ""; + let charsWidth = 0; + for (let j = 0, m = subText.length; j < m; ++j) { + const char = subText[j]; + const charWidth = context.measureText(char).width; + if (charsWidth + charWidth > autoWrapWidth) { + // The width of text renderer is shorter than current char. + if (charsWidth === 0) { + wrappedSubTexts.push(char); + } else { + wrappedSubTexts.push(chars); + chars = char; + charsWidth = charWidth; + } + } else { + chars += char; + charsWidth += charWidth; + } + } + if (charsWidth > 0) { + wrappedSubTexts.push(chars); + } + } + } else { + wrappedSubTexts.push(subText); + } + } + + return wrappedSubTexts; + } + + private static _calculateLinePosition( + width: number, + height: number, + lineWidth: number, + lineHeight: number, + index: number, + length: number, + horizontalAlignment: TextHorizontalAlignment, + verticalAlignment: TextVerticalAlignment, + out: Vector2 + ): void { + switch (verticalAlignment) { + case TextVerticalAlignment.Top: + out.y = index * lineHeight; + break; + case TextVerticalAlignment.Bottom: + out.y = height - (length - index) * lineHeight; + break; + default: + out.y = 0.5 * height - 0.5 * length * lineHeight + index * lineHeight; + break; + } + + switch (horizontalAlignment) { + case TextHorizontalAlignment.Left: + out.x = 0; + break; + case TextHorizontalAlignment.Right: + out.x = width - lineWidth; + break; + default: + out.x = (width - lineWidth) * 0.5; + break; + } + } } diff --git a/packages/core/src/2d/data/RenderData2D.ts b/packages/core/src/2d/data/RenderData2D.ts index f520e84419..f7c29702c3 100644 --- a/packages/core/src/2d/data/RenderData2D.ts +++ b/packages/core/src/2d/data/RenderData2D.ts @@ -1,4 +1,5 @@ import { Color, Vector2, Vector3 } from "@oasis-engine/math"; +import { Texture2D } from "../../texture"; export interface RenderData2D { /** @internal */ @@ -11,4 +12,6 @@ export interface RenderData2D { triangles: number[]; /** @internal */ color: Color; + /** @internal */ + texture: Texture2D; } diff --git a/packages/core/src/2d/dynamic-atlas/DynamicTextAtlas.ts b/packages/core/src/2d/dynamic-atlas/DynamicTextAtlas.ts index 2c89a13c37..7fda3c542f 100644 --- a/packages/core/src/2d/dynamic-atlas/DynamicTextAtlas.ts +++ b/packages/core/src/2d/dynamic-atlas/DynamicTextAtlas.ts @@ -45,7 +45,8 @@ export class DynamicTextAtlas { const { _space: space, _texture: texture } = this; const { width, height } = imageSource; - const endX = this._curX + width + space; + const offsetWidth = width + space; + const endX = this._curX + offsetWidth; if (endX >= this._width) { this._curX = space; this._curY = this._nextY + space; @@ -72,7 +73,7 @@ export class DynamicTextAtlas { // Update atlas texture. sprite.atlasRegion = region; sprite.texture = texture; - this._curX = endX + space; + this._curX += offsetWidth + space; return true; } diff --git a/packages/core/src/2d/sprite/SpriteRenderer.ts b/packages/core/src/2d/sprite/SpriteRenderer.ts index f916ac5993..fd16b4324d 100644 --- a/packages/core/src/2d/sprite/SpriteRenderer.ts +++ b/packages/core/src/2d/sprite/SpriteRenderer.ts @@ -212,12 +212,10 @@ export class SpriteRenderer extends Renderer implements ICustomClone { this._setDirtyFlagFalse(DirtyFlag.MaskInteraction); } - this.shaderData.setTexture(SpriteRenderer._textureProperty, texture); const material = this.getMaterial(); - const spriteElementPool = this._engine._spriteElementPool; const spriteElement = spriteElementPool.getFromPool(); - spriteElement.setValue(this, _positions, sprite._uv, sprite._triangles, this.color, material, camera); + spriteElement.setValue(this, _positions, sprite._uv, sprite._triangles, this.color, texture, material, camera); camera._renderPipeline.pushPrimitive(spriteElement); } diff --git a/packages/core/src/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts index c92b35c0c8..31d18aa759 100644 --- a/packages/core/src/2d/text/TextRenderer.ts +++ b/packages/core/src/2d/text/TextRenderer.ts @@ -371,10 +371,12 @@ export class TextRenderer extends Renderer { */ _clearTexture(): void { const { _sprite } = this; - // Remove sprite from dynamic atlas. - this.engine._dynamicTextAtlasManager.removeSprite(_sprite); - this.shaderData.setTexture(SpriteRenderer._textureProperty, null); - _sprite.atlasRegion = _sprite.region; + if (_sprite) { + // Remove sprite from dynamic atlas. + this.engine._dynamicTextAtlasManager.removeSprite(_sprite); + this.shaderData.setTexture(SpriteRenderer._textureProperty, null); + _sprite.atlasRegion = _sprite.region; + } } /** @@ -442,7 +444,6 @@ export class TextRenderer extends Renderer { } private _drawPrimitive(camera: Camera, renderData: RenderData2D, texture: Texture2D): void { - this.shaderData.setTexture(SpriteRenderer._textureProperty, texture); const spriteElementPool = this._engine._spriteElementPool; const spriteElement = spriteElementPool.getFromPool(); const { positions, triangles, uvs, color } = renderData; @@ -452,6 +453,7 @@ export class TextRenderer extends Renderer { uvs, triangles, color, + texture, this.getMaterial(), camera ); diff --git a/packages/core/src/2d/text/TextUtils.ts b/packages/core/src/2d/text/TextUtils.ts index 51f4f28fc2..52c14309fa 100644 --- a/packages/core/src/2d/text/TextUtils.ts +++ b/packages/core/src/2d/text/TextUtils.ts @@ -1,7 +1,4 @@ -import { Vector2 } from "@oasis-engine/math"; import { FontStyle } from "../enums/FontStyle"; -import { TextHorizontalAlignment, TextVerticalAlignment } from "../enums/TextAlignment"; -import { OverflowMode } from "../enums/TextOverflow"; /** * @internal @@ -68,17 +65,14 @@ export class TextUtils { private static _measureBaseline: string = "M"; private static _heightMultiplier: number = 2; private static _baselineMultiplier: number = 1.4; - private static _maxWidth: number = 2048; - private static _maxHeight: number = 2048; private static _fontSizeInfoCache: Record = {}; private static _textContext: TextContext = null; - private static _tempVec2: Vector2 = new Vector2(); /** * The instance function to get an object includes 2d context and canvas. * @returns the TextContext object */ - public static textContext(): TextContext { + static textContext(): TextContext { let { _textContext: textContext } = TextUtils; if (!textContext) { let canvas: HTMLCanvasElement | OffscreenCanvas; @@ -99,206 +93,27 @@ export class TextUtils { * @param fontString - the string of the font * @returns the font size info */ - public static measureFont(fontString: string): FontSizeInfo { + static measureFont(fontString: string): FontSizeInfo { const { _fontSizeInfoCache: fontSizeInfoCache } = TextUtils; let info = fontSizeInfoCache[fontString]; if (info) { return info; } - const { canvas, context } = TextUtils.textContext(); - context.font = fontString; - const measureString = TextUtils._measureString; - const width = Math.ceil(context.measureText(measureString).width); - let baseline = Math.ceil(context.measureText(TextUtils._measureBaseline).width); - const height = baseline * TextUtils._heightMultiplier; - baseline = (TextUtils._baselineMultiplier * baseline) | 0; - - canvas.width = width; - canvas.height = height; - - context.font = fontString; - context.fillStyle = "#000"; - context.clearRect(0, 0, width, height); - context.textBaseline = "middle"; - context.fillStyle = "#f00"; - context.fillText(measureString, 0, baseline); - - const imgData = context.getImageData(0, 0, width, height).data; - const lineDataCount = width * 4; - let stop = false; - let i = 0; - let offset = 0; - - for (i = 0; i < baseline; ++i) { - offset = i * lineDataCount; - for (let j = 0; j < lineDataCount; j += 4) { - if (imgData[offset + j] !== 0) { - stop = true; - break; - } - } - if (stop) { - break; - } - } - - const ascent = baseline - i; - stop = false; - - for (i = height - 1; i >= baseline; --i) { - offset = i * lineDataCount; - for (let j = 0; j < lineDataCount; j += 4) { - if (imgData[offset + j] !== 0) { - stop = true; - break; - } - } - if (stop) { - break; - } - } - - const descent = i - baseline + 1; - fontSizeInfoCache[fontString] = info = { - ascent, - descent, - size: ascent + descent - }; + info = TextUtils._measureFontOrChar(fontString); + fontSizeInfoCache[fontString] = info; return info; } - /** - * Measure the text. - * @param text - rendering string - * @param originWidth - the width of the TextRenderer - * @param originHeight - the height of the TextRenderer - * @param lineSpacing - the space between two lines - * @param enableWrapping - whether wrap text to next line when exceeds the width of the container - * @param overflowMode - the overflow mode - * @param fontString - the font string - * @returns the TextMetrics object - */ - public static measureText( - text: string, - originWidth: number, - originHeight: number, - lineSpacing: number, - enableWrapping: boolean, - overflowMode: OverflowMode, - fontString: string - ): TextMetrics { - const { _pixelsPerUnit } = TextUtils; - const fontSize = TextUtils.measureFont(fontString).size; - const context = TextUtils.textContext().context; - const lines = TextUtils._wordWrap(text, originWidth, enableWrapping, fontString); - const lineCount = lines.length; - const lineWidths = new Array(); - const lineHeight = fontSize + lineSpacing * _pixelsPerUnit; - context.font = fontString; - // Calculate max width of all lines. - let width = 0; - for (let i = 0; i < lineCount; ++i) { - const lineWidth = Math.ceil(context.measureText(lines[i]).width); - if (lineWidth > width) { - width = lineWidth; - } - lineWidths.push(lineWidth); - } - - // Reset width and height. - let height = originHeight * _pixelsPerUnit; - if (overflowMode === OverflowMode.Overflow) { - height = Math.min(lineHeight * lineCount, TextUtils._maxHeight); - } - - return { - width, - height, - lines, - lineWidths, - lineHeight - }; - } - - public static measureChar(char: string, fontString: string): CharMetrics { - const { canvas, context } = TextUtils.textContext(); - context.font = fontString; - const width = Math.ceil(context.measureText(char).width); - let baseline = Math.ceil(context.measureText(TextUtils._measureBaseline).width); - const height = baseline * TextUtils._heightMultiplier; - baseline = (TextUtils._baselineMultiplier * baseline) | 0; - - canvas.width = width; - canvas.height = height; - - context.font = fontString; - context.fillStyle = "#000"; - context.clearRect(0, 0, width, height); - context.textBaseline = "middle"; - context.fillStyle = "#fff"; - context.fillText(char, 0, baseline); - - const imgData = context.getImageData(0, 0, width, height).data; - const lineDataCount = width * 4; - let stop = false; - let i = 0; - let offset = 0; - let top = 0; - - for (i = 0; i < baseline; ++i) { - offset = i * lineDataCount; - for (let j = 0; j < lineDataCount; j += 4) { - if (imgData[offset + j] !== 0) { - stop = true; - break; - } - } - if (stop) { - break; - } - } - - const ascent = baseline - i; - top = i; - stop = false; - - for (i = height - 1; i >= baseline; --i) { - offset = i * lineDataCount; - for (let j = 0; j < lineDataCount; j += 4) { - if (imgData[offset + j] !== 0) { - stop = true; - break; - } - } - if (stop) { - break; - } - } - - const descent = i - baseline + 1; - const size = ascent + descent; - const sizeInfo = { - ascent, - descent, - size - }; - if (size > 0) { - const data = context.getImageData(0, top, width, size); - TextUtils.updateCanvas(width, size, data); - } - - return { - width, - sizeInfo - }; + static measureChar(char: string, fontString: string): CharMetrics { + return TextUtils._measureFontOrChar(fontString, char); } /** * Trim canvas. * @returns the width and height after trim, and the image data */ - public static trimCanvas(): { width: number; height: number; data?: ImageData } { + static trimCanvas(): { width: number; height: number; data?: ImageData } { // https://gist.github.com/remy/784508 const { canvas, context } = TextUtils.textContext(); @@ -309,30 +124,18 @@ export class TextUtils { let top = -1; let bottom = -1; - let left = width; - let right = -1; let data = null; - let x; let y; for (let i = 0; i < len; i += 4) { if (imageData[i + 3] !== 0) { const idx = i / 4; - x = idx % width; y = ~~(idx / width); if (top === -1) { top = y; } - if (x < left) { - left = x; - } - - if (x > right) { - right = x; - } - if (y > bottom) { bottom = y; } @@ -340,13 +143,10 @@ export class TextUtils { } if (top !== -1) { - top = Math.max(0, top - 1); - bottom = Math.min(height - 1, bottom + 1); - left = Math.max(0, left - 1); - right = Math.min(width - 1, right + 1); - width = right - left + 1; + top = Math.max(0, top); + bottom = Math.min(height - 1, bottom); height = bottom - top + 1; - data = context.getImageData(left, top, width, height); + data = context.getImageData(0, top, width, height); } return { @@ -363,7 +163,7 @@ export class TextUtils { * @param style - The font style * @returns The native font string */ - public static getNativeFontString(fontName: string, fontSize: number, style: FontStyle): string { + static getNativeFontString(fontName: string, fontSize: number, style: FontStyle): string { let str = style & FontStyle.Bold ? "bold " : ""; style & FontStyle.Italic && (str += "italic "); // Check if font already contains strings @@ -381,7 +181,7 @@ export class TextUtils { * @param style - The font style * @returns The native font hash */ - public static getNativeFontHash(fontName: string, fontSize: number, style: FontStyle): string { + static getNativeFontHash(fontName: string, fontSize: number, style: FontStyle): string { let str = style & FontStyle.Bold ? "bold" : ""; style & FontStyle.Italic && (str += "italic"); // Check if font already contains strings @@ -392,55 +192,6 @@ export class TextUtils { return str; } - /** - * Update text. - * @param textMetrics - the text metrics object - * @param fontString - the font string - * @param horizontalAlignment - the horizontal alignment - * @param verticalAlignment - the vertical alignment - */ - public static updateText( - textMetrics: TextMetrics, - fontString: string, - horizontalAlignment: TextHorizontalAlignment, - verticalAlignment: TextVerticalAlignment - ): void { - const { canvas, context } = TextUtils.textContext(); - const { width, height } = textMetrics; - // reset canvas's width and height. - canvas.width = width; - canvas.height = height; - // clear canvas. - context.font = fontString; - context.clearRect(0, 0, width, height); - // set canvas font info. - context.textBaseline = "middle"; - context.fillStyle = "#fff"; - - // draw lines. - const { lines, lineHeight, lineWidths } = textMetrics; - const halfLineHeight = lineHeight * 0.5; - for (let i = 0, l = lines.length; i < l; ++i) { - const lineWidth = lineWidths[i]; - const pos = TextUtils._tempVec2; - TextUtils._calculateLinePosition( - width, - height, - lineWidth, - lineHeight, - i, - l, - horizontalAlignment, - verticalAlignment, - pos - ); - const { x, y } = pos; - if (y + lineHeight >= 0 && y < height) { - context.fillText(lines[i], x, y + halfLineHeight); - } - } - } - /** * Update canvas with the data. * @param width - the new width of canvas @@ -448,7 +199,7 @@ export class TextUtils { * @param data - the new data of canvas * @returns the canvas after update */ - public static updateCanvas(width: number, height: number, data: ImageData): HTMLCanvasElement | OffscreenCanvas { + static updateCanvas(width: number, height: number, data: ImageData): HTMLCanvasElement | OffscreenCanvas { const { canvas, context } = TextUtils.textContext(); canvas.width = width; canvas.height = height; @@ -456,87 +207,69 @@ export class TextUtils { return canvas; } - private static _wordWrap(text: string, width: number, enableWrapping: boolean, fontString: string): Array { - const { context } = TextUtils.textContext(); - const { _maxWidth: maxWidth } = TextUtils; - const widthInPixel = width * TextUtils._pixelsPerUnit; - const wrapWidth = Math.min(widthInPixel, maxWidth); - const wrappedSubTexts = new Array(); - const subTexts = text.split(/(?:\r\n|\r|\n)/); + private static _measureFontOrChar(fontString: string, char: string = ""): FontSizeInfo | CharMetrics { + const { canvas, context } = TextUtils.textContext(); context.font = fontString; + const measureString = char || TextUtils._measureString; + const width = context.measureText(measureString).width; + let baseline = Math.ceil(context.measureText(TextUtils._measureBaseline).width); + const height = baseline * TextUtils._heightMultiplier; + baseline = (TextUtils._baselineMultiplier * baseline) | 0; - for (let i = 0, n = subTexts.length; i < n; ++i) { - const subText = subTexts[i]; - const subWidth = Math.ceil(context.measureText(subText).width); - const needWrap = enableWrapping || subWidth > maxWidth; - if (needWrap) { - if (subWidth <= wrapWidth) { - wrappedSubTexts.push(subText); - } else { - let chars = ""; - let charsWidth = 0; - for (let j = 0, m = subText.length; j < m; ++j) { - const char = subText[j]; - const charWidth = Math.ceil(context.measureText(char).width); - if (charsWidth + charWidth > wrapWidth) { - // The width of text renderer is shorter than current char. - if (charsWidth === 0) { - wrappedSubTexts.push(char); - } else { - wrappedSubTexts.push(chars); - chars = char; - charsWidth = charWidth; - } - } else { - chars += char; - charsWidth += charWidth; - } - } - if (charsWidth > 0) { - wrappedSubTexts.push(chars); - } + canvas.width = width; + canvas.height = height; + + context.font = fontString; + context.fillStyle = "#000"; + context.clearRect(0, 0, width, height); + context.textBaseline = "middle"; + context.fillStyle = "#fff"; + context.fillText(measureString, 0, baseline); + + const imageData = context.getImageData(0, 0, width, height).data; + const len = imageData.length; + + let top = -1; + let bottom = -1; + let y; + let ascent = 0; + let descent = 0; + let size = 0; + + const integerW = canvas.width; + for (let i = 0; i < len; i += 4) { + if (imageData[i + 3] !== 0) { + const idx = i / 4; + y = ~~(idx / integerW); + + if (top === -1) { + top = y; + } + + if (y > bottom) { + bottom = y; } - } else { - wrappedSubTexts.push(subText); } } - return wrappedSubTexts; - } - - private static _calculateLinePosition( - width: number, - height: number, - lineWidth: number, - lineHeight: number, - index: number, - length: number, - horizontalAlignment: TextHorizontalAlignment, - verticalAlignment: TextVerticalAlignment, - out: Vector2 - ): void { - switch (verticalAlignment) { - case TextVerticalAlignment.Top: - out.y = index * lineHeight; - break; - case TextVerticalAlignment.Bottom: - out.y = height - (length - index) * lineHeight; - break; - default: - out.y = 0.5 * height - 0.5 * length * lineHeight + index * lineHeight; - break; + if (top !== -1 && bottom !== -1) { + ascent = baseline - top; + descent = bottom - baseline + 1; + size = ascent + descent; } + const sizeInfo = { ascent, descent, size }; - switch (horizontalAlignment) { - case TextHorizontalAlignment.Left: - out.x = 0; - break; - case TextHorizontalAlignment.Right: - out.x = width - lineWidth; - break; - default: - out.x = (width - lineWidth) * 0.5; - break; + if (char) { + if (size > 0) { + const data = context.getImageData(0, top, width, size); + TextUtils.updateCanvas(width, size, data); + } + return { + width, + sizeInfo + } + } else { + return sizeInfo; } } } diff --git a/packages/core/src/RenderPipeline/SpriteBatcher.ts b/packages/core/src/RenderPipeline/SpriteBatcher.ts index c382bfbca0..7d8967f8fa 100644 --- a/packages/core/src/RenderPipeline/SpriteBatcher.ts +++ b/packages/core/src/RenderPipeline/SpriteBatcher.ts @@ -31,9 +31,8 @@ export class SpriteBatcher extends Basic2DBatcher { return false; } - // Compare renderer property - const textureProperty = SpriteBatcher._textureProperty; - if (preRenderer.shaderData.getTexture(textureProperty) !== curRenderer.shaderData.getTexture(textureProperty)) { + // Compare texture + if (preElement.texture !== curElement.texture) { return false; } @@ -106,6 +105,8 @@ export class SpriteBatcher extends Basic2DBatcher { return; } + renderer.shaderData.setTexture(SpriteBatcher._textureProperty, spriteElement.texture); + program.bind(); program.groupingOtherUniformBlock(); program.uploadAll(program.sceneUniformBlock, camera.scene.shaderData); diff --git a/packages/core/src/RenderPipeline/SpriteElement.ts b/packages/core/src/RenderPipeline/SpriteElement.ts index 133770318b..4f625b11c1 100644 --- a/packages/core/src/RenderPipeline/SpriteElement.ts +++ b/packages/core/src/RenderPipeline/SpriteElement.ts @@ -2,6 +2,7 @@ import { Color, Vector2, Vector3 } from "@oasis-engine/math"; import { Camera } from "../Camera"; import { Material } from "../material/Material"; import { Renderer } from "../Renderer"; +import { Texture2D } from "../texture"; export class SpriteElement { component: Renderer; @@ -9,6 +10,7 @@ export class SpriteElement { uv: Vector2[]; triangles: number[]; color: Color; + texture: Texture2D; material: Material; camera: Camera; @@ -18,6 +20,7 @@ export class SpriteElement { uv: Vector2[], triangles: number[], color: Color, + texture: Texture2D, material: Material, camera: Camera ): void { @@ -26,6 +29,7 @@ export class SpriteElement { this.uv = uv; this.triangles = triangles; this.color = color; + this.texture = texture; this.material = material; this.camera = camera; } From 47f2fc40509f1435f4cc9d6fe5f2932d7b13a754 Mon Sep 17 00:00:00 2001 From: singlecoder Date: Mon, 4 Jul 2022 14:37:50 +0800 Subject: [PATCH 10/55] feat(text): opt code for pixelsPerUnit --- packages/core/src/2d/text/TextUtils.ts | 2 -- packages/core/src/Engine.ts | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/core/src/2d/text/TextUtils.ts b/packages/core/src/2d/text/TextUtils.ts index 52c14309fa..7f40b61b7b 100644 --- a/packages/core/src/2d/text/TextUtils.ts +++ b/packages/core/src/2d/text/TextUtils.ts @@ -59,8 +59,6 @@ export class TextUtils { "fangsong" ]; /** These characters are all tall to help calculate the height required for text. */ - /** @internal */ - static _pixelsPerUnit: number = 128; private static _measureString: string = "|ÉqÅ"; private static _measureBaseline: string = "M"; private static _heightMultiplier: number = 2; diff --git a/packages/core/src/Engine.ts b/packages/core/src/Engine.ts index 9ec72b2f1f..eac0af954a 100644 --- a/packages/core/src/Engine.ts +++ b/packages/core/src/Engine.ts @@ -44,7 +44,7 @@ ShaderPool.init(); export class Engine extends EventDispatcher { /** @internal */ static _gammaMacro: ShaderMacro = Shader.getMacroByName("OASIS_COLORSPACE_GAMMA"); - /** @internal Conversion of space units to pixel units. */ + /** @internal Conversion of space units to pixel units for 2D. */ static _pixelsPerUnit: number = 128; /** Physics manager of Engine. */ From 9441d165e453437e6fb277a75503e1e90311c78a Mon Sep 17 00:00:00 2001 From: singlecoder Date: Mon, 4 Jul 2022 14:40:59 +0800 Subject: [PATCH 11/55] feat(text): opt code --- packages/core/src/2d/assembler/TextAssembler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/2d/assembler/TextAssembler.ts b/packages/core/src/2d/assembler/TextAssembler.ts index e2c08a3b45..6c73bbe054 100644 --- a/packages/core/src/2d/assembler/TextAssembler.ts +++ b/packages/core/src/2d/assembler/TextAssembler.ts @@ -226,7 +226,7 @@ export class TextAssembler { private static _wordWrap(text: string, width: number, enableWrapping: boolean, fontString: string): Array { const { context } = TextUtils.textContext(); const { _maxWidth: maxWidth } = TextAssembler; - const widthInPixel = width * TextUtils._pixelsPerUnit; + const widthInPixel = width * Engine._pixelsPerUnit; const wrapWidth = Math.min(widthInPixel, maxWidth); const wrappedSubTexts = new Array(); const subTexts = text.split(/(?:\r\n|\r|\n)/); From 67e86b889a1ae08f5ed42bf9140f486ab8d48e1d Mon Sep 17 00:00:00 2001 From: singlecoder Date: Mon, 4 Jul 2022 14:44:12 +0800 Subject: [PATCH 12/55] feat(text): change pixelsPerUnit value --- packages/core/src/Engine.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/Engine.ts b/packages/core/src/Engine.ts index eac0af954a..e4cf06045a 100644 --- a/packages/core/src/Engine.ts +++ b/packages/core/src/Engine.ts @@ -45,7 +45,7 @@ export class Engine extends EventDispatcher { /** @internal */ static _gammaMacro: ShaderMacro = Shader.getMacroByName("OASIS_COLORSPACE_GAMMA"); /** @internal Conversion of space units to pixel units for 2D. */ - static _pixelsPerUnit: number = 128; + static _pixelsPerUnit: number = 100; /** Physics manager of Engine. */ readonly physicsManager: PhysicsManager; From 833e4afb7b0aa30b12c568d88a61642f13b86451 Mon Sep 17 00:00:00 2001 From: singlecoder Date: Mon, 4 Jul 2022 14:50:13 +0800 Subject: [PATCH 13/55] feat(text): add internal --- packages/core/src/2d/assembler/CharAssembler.ts | 3 +++ packages/core/src/2d/assembler/CharRenderDataPool.ts | 3 +++ packages/core/src/2d/assembler/CharUtils.ts | 6 ++++++ packages/core/src/2d/assembler/IAssembler.ts | 3 +++ packages/core/src/2d/assembler/SimpleSpriteAssembler.ts | 2 +- packages/core/src/2d/assembler/SlicedSpriteAssembler.ts | 2 +- packages/core/src/2d/assembler/TextAssembler.ts | 3 +++ 7 files changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/core/src/2d/assembler/CharAssembler.ts b/packages/core/src/2d/assembler/CharAssembler.ts index 277f5d6286..7082dc21bf 100644 --- a/packages/core/src/2d/assembler/CharAssembler.ts +++ b/packages/core/src/2d/assembler/CharAssembler.ts @@ -9,6 +9,9 @@ import { CharDefWithTexture, CharUtils } from "./CharUtils"; import { IAssembler } from "./IAssembler"; import { StaticInterfaceImplement } from "./StaticInterfaceImplement"; +/** + * @internal + */ @StaticInterfaceImplement() export class CharAssembler { private static _charUtils: CharUtils; diff --git a/packages/core/src/2d/assembler/CharRenderDataPool.ts b/packages/core/src/2d/assembler/CharRenderDataPool.ts index 3c384d4a98..d63222af2b 100644 --- a/packages/core/src/2d/assembler/CharRenderDataPool.ts +++ b/packages/core/src/2d/assembler/CharRenderDataPool.ts @@ -3,6 +3,9 @@ import { Texture2D } from "../../texture"; import { RenderData2D } from "../data/RenderData2D"; import { CharRenderData } from "./CharRenderData"; +/** + * @internal + */ export class CharRenderDataPool { private _pools: Array = []; private _poolIndex: number = -1; diff --git a/packages/core/src/2d/assembler/CharUtils.ts b/packages/core/src/2d/assembler/CharUtils.ts index 0451a1491d..b1bcb3b85f 100644 --- a/packages/core/src/2d/assembler/CharUtils.ts +++ b/packages/core/src/2d/assembler/CharUtils.ts @@ -2,11 +2,17 @@ import { Engine } from "../../Engine"; import { Texture2D } from "../../texture"; import { CharDef, FontAtlas } from "../atlas/FontAtlas"; +/** + * @internal + */ export interface CharDefWithTexture { texture: Texture2D; charDef: CharDef; } +/** + * @internal + */ export class CharUtils { private _fontAtlasList: Array = []; private _curFontAtlas: FontAtlas = null; diff --git a/packages/core/src/2d/assembler/IAssembler.ts b/packages/core/src/2d/assembler/IAssembler.ts index c91d5ed187..22a571fc1d 100644 --- a/packages/core/src/2d/assembler/IAssembler.ts +++ b/packages/core/src/2d/assembler/IAssembler.ts @@ -1,5 +1,8 @@ import { Renderer } from "../../Renderer"; +/** + * @internal + */ export interface IAssembler { resetData(renderer: Renderer): void; updateData(renderer: Renderer): void; diff --git a/packages/core/src/2d/assembler/SimpleSpriteAssembler.ts b/packages/core/src/2d/assembler/SimpleSpriteAssembler.ts index aa7cf342e6..740e14cd1e 100644 --- a/packages/core/src/2d/assembler/SimpleSpriteAssembler.ts +++ b/packages/core/src/2d/assembler/SimpleSpriteAssembler.ts @@ -4,10 +4,10 @@ import { SpriteRenderer } from "../sprite/SpriteRenderer"; import { IAssembler } from "./IAssembler"; import { StaticInterfaceImplement } from "./StaticInterfaceImplement"; -@StaticInterfaceImplement() /** * @internal */ +@StaticInterfaceImplement() export class SimpleSpriteAssembler { static _rectangleTriangles: number[] = [0, 1, 2, 2, 1, 3]; static _worldMatrix: Matrix = new Matrix(); diff --git a/packages/core/src/2d/assembler/SlicedSpriteAssembler.ts b/packages/core/src/2d/assembler/SlicedSpriteAssembler.ts index 7d6b95d0ad..2b8e45ac9a 100644 --- a/packages/core/src/2d/assembler/SlicedSpriteAssembler.ts +++ b/packages/core/src/2d/assembler/SlicedSpriteAssembler.ts @@ -4,10 +4,10 @@ import { SpriteRenderer } from "../sprite/SpriteRenderer"; import { IAssembler } from "./IAssembler"; import { StaticInterfaceImplement } from "./StaticInterfaceImplement"; -@StaticInterfaceImplement() /** * @internal */ +@StaticInterfaceImplement() export class SlicedSpriteAssembler { static _worldMatrix: Matrix = new Matrix(); static resetData(renderer: SpriteRenderer | SpriteMask): void { diff --git a/packages/core/src/2d/assembler/TextAssembler.ts b/packages/core/src/2d/assembler/TextAssembler.ts index 6c73bbe054..b4d71190b5 100644 --- a/packages/core/src/2d/assembler/TextAssembler.ts +++ b/packages/core/src/2d/assembler/TextAssembler.ts @@ -9,6 +9,9 @@ import { TextUtils, TextMetrics } from "../text/TextUtils"; import { IAssembler } from "./IAssembler"; import { StaticInterfaceImplement } from "./StaticInterfaceImplement"; +/** + * @internal + */ @StaticInterfaceImplement() export class TextAssembler { private static _tempVec2: Vector2 = new Vector2(); From 1a096e72e5148c1a66e599b7647647db19857052 Mon Sep 17 00:00:00 2001 From: singlecoder Date: Mon, 4 Jul 2022 14:52:53 +0800 Subject: [PATCH 14/55] feat(text): opt code --- packages/core/src/2d/atlas/FontAtlas.ts | 7 +++++++ packages/core/src/2d/index.ts | 1 - 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/core/src/2d/atlas/FontAtlas.ts b/packages/core/src/2d/atlas/FontAtlas.ts index 3aca2b037f..499d48816e 100644 --- a/packages/core/src/2d/atlas/FontAtlas.ts +++ b/packages/core/src/2d/atlas/FontAtlas.ts @@ -2,6 +2,9 @@ import { RefObject } from "../../asset/RefObject"; import { Engine } from "../../Engine"; import { Texture2D } from "../../texture/Texture2D"; +/** + * @internal + */ export interface CharDef { x: number, y: number, @@ -12,11 +15,15 @@ export interface CharDef { xAdvance: number } +/** + * @internal + */ export interface CharDefDict { [key: string]: CharDef; } /** + * @internal * Font Atlas. */ export class FontAtlas extends RefObject { diff --git a/packages/core/src/2d/index.ts b/packages/core/src/2d/index.ts index 6fd08f2358..c7bb91fa4e 100644 --- a/packages/core/src/2d/index.ts +++ b/packages/core/src/2d/index.ts @@ -3,7 +3,6 @@ export { SpriteMaskLayer } from "./enums/SpriteMaskLayer"; export { TextHorizontalAlignment, TextVerticalAlignment } from "./enums/TextAlignment"; export { OverflowMode } from "./enums/TextOverflow"; export { FontStyle } from "./enums/FontStyle"; -export { FontAtlas } from "./atlas/FontAtlas"; export { SpriteAtlas } from "./atlas/SpriteAtlas"; export { SpriteDrawMode } from "./enums/SpriteDrawMode"; export * from "./sprite/index"; From 884f82153b33c93efe4d956e87db6ac4c1e0520b Mon Sep 17 00:00:00 2001 From: singlecoder Date: Mon, 4 Jul 2022 15:03:41 +0800 Subject: [PATCH 15/55] feat(text): useCharCache default value is true --- packages/core/src/2d/assembler/TextAssembler.ts | 12 +++++++----- packages/core/src/2d/text/TextRenderer.ts | 13 +++++++------ 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/packages/core/src/2d/assembler/TextAssembler.ts b/packages/core/src/2d/assembler/TextAssembler.ts index b4d71190b5..d088b6677c 100644 --- a/packages/core/src/2d/assembler/TextAssembler.ts +++ b/packages/core/src/2d/assembler/TextAssembler.ts @@ -56,11 +56,13 @@ export class TextAssembler { } static clearData(renderer: TextRenderer): void { - const { positions, uvs, triangles } = renderer._renderData; - positions.length = 0; - uvs.length = 0; - triangles.length = 0; - renderer._renderData = null; + if (renderer._renderData) { + const { positions, uvs, triangles } = renderer._renderData; + positions.length = 0; + uvs.length = 0; + triangles.length = 0; + renderer._renderData = null; + } } private static _updatePosition(renderer: TextRenderer): void { diff --git a/packages/core/src/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts index a146b8c12e..21ac4e51cc 100644 --- a/packages/core/src/2d/text/TextRenderer.ts +++ b/packages/core/src/2d/text/TextRenderer.ts @@ -58,7 +58,7 @@ export class TextRenderer extends Renderer { @assignmentClone private _lineSpacing: number = 0; @assignmentClone - private _useCharCache: boolean = false; + private _useCharCache: boolean = true; @assignmentClone private _horizontalAlignment: TextHorizontalAlignment = TextHorizontalAlignment.Center; @assignmentClone @@ -197,9 +197,11 @@ export class TextRenderer extends Renderer { this._setDirtyFlagTrue(DirtyFlag.Property); if (value) { - this._clearTexture(); - this._sprite.destroy(); - this._sprite = null; + if (this._sprite) { + this._clearTexture(); + this._sprite.destroy(); + this._sprite = null; + } TextAssembler.clearData(this); CharAssembler.resetData(this); } else { @@ -295,8 +297,7 @@ export class TextRenderer extends Renderer { super(entity); const { engine } = this; this._isWorldMatrixDirty = entity.transform.registerWorldChangeFlag(); - this._sprite = new Sprite(engine); - TextAssembler.resetData(this); + CharAssembler.resetData(this); this.font = Font.createFromOS(engine); this.setMaterial(engine._spriteDefaultMaterial); } From 304f7bebfc3f943a0b760861ca35943bb4f7f7b4 Mon Sep 17 00:00:00 2001 From: singlecoder Date: Mon, 4 Jul 2022 15:27:27 +0800 Subject: [PATCH 16/55] feat(text): delete debugger --- packages/core/src/2d/assembler/TextAssembler.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core/src/2d/assembler/TextAssembler.ts b/packages/core/src/2d/assembler/TextAssembler.ts index d088b6677c..7197d07884 100644 --- a/packages/core/src/2d/assembler/TextAssembler.ts +++ b/packages/core/src/2d/assembler/TextAssembler.ts @@ -46,7 +46,6 @@ export class TextAssembler { renderer._setDirtyFlagFalse(DirtyFlag.Property); } - debugger; if (renderer._isWorldMatrixDirty.flag || isTextureDirty) { TextAssembler._updatePosition(renderer); renderer._isWorldMatrixDirty.flag = false; From c1427443f5574964ba9928d7b3ece5fe530bfba7 Mon Sep 17 00:00:00 2001 From: singlecoder Date: Mon, 4 Jul 2022 16:31:08 +0800 Subject: [PATCH 17/55] feat(text): fix useCharCache clone bug --- packages/core/src/2d/text/TextRenderer.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/core/src/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts index 21ac4e51cc..f39a049cf8 100644 --- a/packages/core/src/2d/text/TextRenderer.ts +++ b/packages/core/src/2d/text/TextRenderer.ts @@ -18,11 +18,12 @@ import { SpriteMaskInteraction } from "../enums/SpriteMaskInteraction"; import { SpriteMaskLayer } from "../enums/SpriteMaskLayer"; import { SpriteRenderer } from "../sprite/SpriteRenderer"; import { CompareFunction } from "../../shader/enums/CompareFunction"; +import { ICustomClone } from "../../clone/ComponentCloner"; /** * Renders a text for 2D graphics. */ -export class TextRenderer extends Renderer { +export class TextRenderer extends Renderer implements ICustomClone { private static _tempBounds: BoundingBox = new BoundingBox(); /** @internal */ @@ -57,7 +58,7 @@ export class TextRenderer extends Renderer { private _fontStyle: FontStyle = FontStyle.None; @assignmentClone private _lineSpacing: number = 0; - @assignmentClone + @ignoreClone private _useCharCache: boolean = true; @assignmentClone private _horizontalAlignment: TextHorizontalAlignment = TextHorizontalAlignment.Center; @@ -348,6 +349,7 @@ export class TextRenderer extends Renderer { */ _cloneTo(target: TextRenderer): void { target.font = this._font; + target.useCharCache = this._useCharCache; } /** @@ -389,11 +391,11 @@ export class TextRenderer extends Renderer { */ protected _updateBounds(worldBounds: BoundingBox): void { const worldMatrix = this._entity.transform.worldMatrix; + let bounds = TextRenderer._tempBounds; + const { min, max } = bounds; + min.set(0, 0, 0); + max.set(0, 0, 0); if (this._useCharCache) { - const bounds = TextRenderer._tempBounds; - const { min, max } = bounds; - min.set(0, 0, 0); - max.set(0, 0, 0); const { _charRenderDatas } = this; const dataLen = _charRenderDatas.length; if (dataLen > 0) { @@ -420,7 +422,10 @@ export class TextRenderer extends Renderer { } BoundingBox.transform(bounds, worldMatrix, worldBounds); } else { - BoundingBox.transform(this._sprite._getBounds(), worldMatrix, worldBounds); + if (this._sprite) { + bounds = this._sprite._getBounds(); + } + BoundingBox.transform(bounds, worldMatrix, worldBounds); } } From 91ec41504d38b5faf7d7fcd3bbdce832296fa3fa Mon Sep 17 00:00:00 2001 From: singlecoder Date: Mon, 4 Jul 2022 16:47:07 +0800 Subject: [PATCH 18/55] feat(text): opt code --- .../core/src/2d/assembler/CharAssembler.ts | 8 +----- packages/core/src/2d/assembler/CharUtils.ts | 25 ++++++++----------- packages/core/src/2d/atlas/FontAtlas.ts | 6 ++++- 3 files changed, 17 insertions(+), 22 deletions(-) diff --git a/packages/core/src/2d/assembler/CharAssembler.ts b/packages/core/src/2d/assembler/CharAssembler.ts index 7082dc21bf..ac5875ac4c 100644 --- a/packages/core/src/2d/assembler/CharAssembler.ts +++ b/packages/core/src/2d/assembler/CharAssembler.ts @@ -79,7 +79,6 @@ export class CharAssembler { const { height, lines, lineWidths, lineHeight, lineMaxSizes } = textMetrics; const { _charUtils, _charRenderDataPool } = CharAssembler; const halfLineHeight = lineHeight * 0.5; - const textureSizeReciprocal = 1.0 / _charUtils.getTextureSize(); const linesLen = lines.length; let startY = 0; @@ -127,8 +126,7 @@ export class CharAssembler { renderData.color = color; const { uvs } = renderData; - const { x, y, w, h } = charDef; - + const { w, u0, v0, u1, v1 } = charDef; const { offsetY } = charDef; const halfH = charDef.h * 0.5; const ascent = halfH + offsetY; @@ -138,10 +136,6 @@ export class CharAssembler { const right = (startX + w) * pixelsPerUnitReciprocal; const top = (startY + ascent) * pixelsPerUnitReciprocal; const bottom = (startY - descent + 1) * pixelsPerUnitReciprocal; - const u0 = x * textureSizeReciprocal; - const u1 = (x + w) * textureSizeReciprocal; - const v0 = y * textureSizeReciprocal; - const v1 = (y + h) * textureSizeReciprocal; // Top-left. localPositions[0].set(left, top, 0); uvs[0].set(u0, v0); diff --git a/packages/core/src/2d/assembler/CharUtils.ts b/packages/core/src/2d/assembler/CharUtils.ts index b1bcb3b85f..67a9595edf 100644 --- a/packages/core/src/2d/assembler/CharUtils.ts +++ b/packages/core/src/2d/assembler/CharUtils.ts @@ -67,16 +67,17 @@ export class CharUtils { curTexture.setImageSource(imageSource, 0, false, false, this._curX, this._curY); curTexture.generateMipmaps(); } - - const charDef = { - x: this._curX, - y: this._curY, - w: width, - h: height, - offsetX, - offsetY, - xAdvance - }; + + const textureSizeReciprocal = 1.0 / curTexture.width; + const x = this._curX; + const y = this._curY; + const w = width; + const h = height; + const u0 = x * textureSizeReciprocal; + const u1 = (x + w) * textureSizeReciprocal; + const v0 = y * textureSizeReciprocal; + const v1 = (y + h) * textureSizeReciprocal; + const charDef = { x, y, w, h, offsetX, offsetY, xAdvance, u0, v0, u1, v1 }; this._curFontAtlas.addCharDef(key, charDef); this._curX += offsetWidth + space; @@ -111,10 +112,6 @@ export class CharUtils { _fontAtlasList.length = 0; } - getTextureSize(): number { - return this._textureSize; - } - private _createFontAtlas(): void { const { engine, _textureSize } = this; const tex = new Texture2D(engine, _textureSize, _textureSize); diff --git a/packages/core/src/2d/atlas/FontAtlas.ts b/packages/core/src/2d/atlas/FontAtlas.ts index 499d48816e..e1a00a27bc 100644 --- a/packages/core/src/2d/atlas/FontAtlas.ts +++ b/packages/core/src/2d/atlas/FontAtlas.ts @@ -12,7 +12,11 @@ export interface CharDef { h: number, offsetX: number, offsetY: number, - xAdvance: number + xAdvance: number, + u0: number, + v0: number, + u1: number, + v1: number } /** From 2ac80dafb631d9a62e0b2f335c781c99ace413e5 Mon Sep 17 00:00:00 2001 From: singlecoder Date: Tue, 5 Jul 2022 14:04:00 +0800 Subject: [PATCH 19/55] feat(text): opt code --- .../core/src/2d/assembler/CharAssembler.ts | 20 ++- packages/core/src/2d/assembler/CharUtils.ts | 122 ++++++------------ .../core/src/2d/atlas/DynamicFontAtlas.ts | 73 +++++++++++ packages/core/src/2d/atlas/FontAtlas.ts | 60 ++++----- 4 files changed, 151 insertions(+), 124 deletions(-) create mode 100644 packages/core/src/2d/atlas/DynamicFontAtlas.ts diff --git a/packages/core/src/2d/assembler/CharAssembler.ts b/packages/core/src/2d/assembler/CharAssembler.ts index ac5875ac4c..47790f72bc 100644 --- a/packages/core/src/2d/assembler/CharAssembler.ts +++ b/packages/core/src/2d/assembler/CharAssembler.ts @@ -115,8 +115,7 @@ export class CharAssembler { for (let j = 0, m = line.length; j < m; ++j) { const char = line[j]; - const key = `${fontHash}${char.charCodeAt(0)}`; - const charDefWithTexture = _charUtils.getCharDef(key); + const charDefWithTexture = _charUtils.getCharDef(fontHash, char.charCodeAt(0)); const { charDef } = charDefWithTexture; if (charDef.h > 0) { @@ -126,11 +125,7 @@ export class CharAssembler { renderData.color = color; const { uvs } = renderData; - const { w, u0, v0, u1, v1 } = charDef; - const { offsetY } = charDef; - const halfH = charDef.h * 0.5; - const ascent = halfH + offsetY; - const descent = halfH - offsetY; + const { w, u0, v0, u1, v1, ascent, descent } = charDef; const left = startX * pixelsPerUnitReciprocal; const right = (startX + w) * pixelsPerUnitReciprocal; @@ -302,21 +297,24 @@ export class CharAssembler { private static _getCharDefWithTexture(char: string, fontString: string, fontHash: string): CharDefWithTexture { const { _charUtils } = CharAssembler; - const key = `${fontHash}${char.charCodeAt(0)}`; - let charDefWithTexture = _charUtils.getCharDef(key); + const id = char.charCodeAt(0); + let charDefWithTexture = _charUtils.getCharDef(fontHash, id); if (!charDefWithTexture) { const charMetrics = TextUtils.measureChar(char, fontString); const { width, sizeInfo } = charMetrics; const { ascent, descent } = sizeInfo; const offsetY = (ascent - descent) * 0.5; charDefWithTexture = _charUtils.addCharDef( - key, + fontHash, + id, TextUtils.textContext().canvas, width, sizeInfo.size, 0, offsetY, - width + width, + ascent, + descent ); } diff --git a/packages/core/src/2d/assembler/CharUtils.ts b/packages/core/src/2d/assembler/CharUtils.ts index 67a9595edf..ddd672c32f 100644 --- a/packages/core/src/2d/assembler/CharUtils.ts +++ b/packages/core/src/2d/assembler/CharUtils.ts @@ -1,6 +1,7 @@ import { Engine } from "../../Engine"; import { Texture2D } from "../../texture"; -import { CharDef, FontAtlas } from "../atlas/FontAtlas"; +import { DynamicFontAtlas } from "../atlas/DynamicFontAtlas"; +import { CharDef } from "../atlas/FontAtlas"; /** * @internal @@ -14,109 +15,68 @@ export interface CharDefWithTexture { * @internal */ export class CharUtils { - private _fontAtlasList: Array = []; - private _curFontAtlas: FontAtlas = null; - private _textureSize: number = 512; - private _space: number = 1; - private _curX: number = 1; - private _curY: number = 1; - private _nextY: number = 1; + private _fontAtlasListMap: Record> = {}; /** * @internal */ - constructor(public readonly engine: Engine) { - this._createFontAtlas(); - } + constructor(public readonly engine: Engine) {} addCharDef( - key: string, + fontHash: string, + id: number, imageSource: TexImageSource | OffscreenCanvas, width: number, height: number, offsetX: number, offsetY: number, xAdvance: number, + ascent: number, + descent: number, ): CharDefWithTexture { - const { _space: space, _textureSize: textureSize } = this; - - const offsetWidth = width + space; - const endX = this._curX + offsetWidth; - if (endX >= textureSize) { - this._curX = space; - this._curY = this._nextY + space; - } - const offsetHeight = height + space; - const endY = this._curY + offsetHeight; - if (endY > this._nextY) { - this._nextY = endY; - } - - if (endY >= textureSize) { - this._createFontAtlas(); - this._curX = 1; - this._curY = 1; - this._nextY = 1; - if (this._curX + offsetWidth >= textureSize || this._curY + offsetHeight >= textureSize) { - throw Error("The char fontSize is too large."); - } + const { _fontAtlasListMap } = this; + let fontAtlasList = _fontAtlasListMap[fontHash]; + if (!fontAtlasList) { + fontAtlasList = _fontAtlasListMap[fontHash] = [new DynamicFontAtlas(this.engine)]; } - const curTexture = this._curFontAtlas.getTexture(); - if (width > 0 && height > 0) { - curTexture.setImageSource(imageSource, 0, false, false, this._curX, this._curY); - curTexture.generateMipmaps(); + const lastIndex = fontAtlasList.length - 1; + let lastFontAtlas = fontAtlasList[lastIndex]; + let charDef = lastFontAtlas.addCharDefDynamic(id, imageSource, width, height, offsetX, offsetY, xAdvance, ascent, descent); + if (!charDef) { + lastFontAtlas = new DynamicFontAtlas(this.engine); + fontAtlasList.push(lastFontAtlas); + charDef = lastFontAtlas.addCharDefDynamic(id, imageSource, width, height, offsetX, offsetY, xAdvance, ascent, descent); } - - const textureSizeReciprocal = 1.0 / curTexture.width; - const x = this._curX; - const y = this._curY; - const w = width; - const h = height; - const u0 = x * textureSizeReciprocal; - const u1 = (x + w) * textureSizeReciprocal; - const v0 = y * textureSizeReciprocal; - const v1 = (y + h) * textureSizeReciprocal; - const charDef = { x, y, w, h, offsetX, offsetY, xAdvance, u0, v0, u1, v1 }; - this._curFontAtlas.addCharDef(key, charDef); - this._curX += offsetWidth + space; - - return { - texture: curTexture, - charDef - }; + return charDef ? { charDef, texture: lastFontAtlas.texture } : null; } - getCharDef(key: string): CharDefWithTexture { - const { _fontAtlasList } = this; - for (let i = 0, l = _fontAtlasList.length; i < l; ++i) { - const fontAtlas = _fontAtlasList[i]; - const charDef = fontAtlas.getCharDef(key); - if (charDef) { - return { - texture: fontAtlas.getTexture(), - charDef: charDef - }; + getCharDef(fontHash: string, id: number): CharDefWithTexture { + let fontAtlasList = this._fontAtlasListMap[fontHash]; + if (fontAtlasList) { + for (let i = 0, l = fontAtlasList.length; i < l; ++i) { + const fontAtlas = fontAtlasList[i]; + const charDef = fontAtlas.getCharDef(id); + if (charDef) { + return { + charDef, + texture: fontAtlas.texture + } + } } } return null; } clear(): void { - this._curFontAtlas = null; - const { _fontAtlasList } = this; - for (let i = 0, l = _fontAtlasList.length; i < l; ++i) { - const fontAtlas = _fontAtlasList[i]; - fontAtlas.destroy(); - } - _fontAtlasList.length = 0; - } - - private _createFontAtlas(): void { - const { engine, _textureSize } = this; - const tex = new Texture2D(engine, _textureSize, _textureSize); - tex._addRefCount(1); - this._curFontAtlas = new FontAtlas(engine, tex); - this._fontAtlasList.push(this._curFontAtlas); + const { _fontAtlasListMap } = this; + Object.keys(_fontAtlasListMap).forEach((key) => { + const fontAtlasList = _fontAtlasListMap[key]; + for (let i = 0, l = fontAtlasList.length; i < l; ++i) { + fontAtlasList[i].destroy(); + } + fontAtlasList.length = 0; + delete _fontAtlasListMap[key]; + }); } } diff --git a/packages/core/src/2d/atlas/DynamicFontAtlas.ts b/packages/core/src/2d/atlas/DynamicFontAtlas.ts new file mode 100644 index 0000000000..7184cc0a70 --- /dev/null +++ b/packages/core/src/2d/atlas/DynamicFontAtlas.ts @@ -0,0 +1,73 @@ +import { Engine } from "../../Engine"; +import { Texture2D } from "../../texture"; +import { CharDef, FontAtlas } from "./FontAtlas"; + +/** + * @internal + */ +export class DynamicFontAtlas extends FontAtlas { + private _textureSize: number = 512; + private _space: number = 1; + private _curX: number = 1; + private _curY: number = 1; + private _nextY: number = 1; + + constructor(engine: Engine) { + super(engine); + const { _textureSize } = this; + this.texture = new Texture2D(engine, _textureSize, _textureSize); + } + + addCharDefDynamic( + id: number, + imageSource: TexImageSource | OffscreenCanvas, + width: number, + height: number, + offsetX: number, + offsetY: number, + xAdvance: number, + ascent: number, + descent: number, + ): CharDef { + const { _space: space, _textureSize: textureSize } = this; + const offsetWidth = width + space; + const offsetHeight = height + space; + if ((1 + offsetWidth) >= textureSize || (1 + offsetHeight) >= textureSize) { + throw Error("The char fontSize is too large."); + } + + const endX = this._curX + offsetWidth; + if (endX >= textureSize) { + this._curX = space; + this._curY = this._nextY + space; + } + const endY = this._curY + offsetHeight; + if (endY > this._nextY) { + this._nextY = endY; + } + if (endY >= textureSize) { + return null; + } + + if (width > 0 && height > 0) { + const { texture } = this; + texture.setImageSource(imageSource, 0, false, false, this._curX, this._curY); + texture.generateMipmaps(); + } + + const textureSizeReciprocal = 1.0 / textureSize; + const x = this._curX; + const y = this._curY; + const w = width; + const h = height; + const u0 = x * textureSizeReciprocal; + const u1 = (x + w) * textureSizeReciprocal; + const v0 = y * textureSizeReciprocal; + const v1 = (y + h) * textureSizeReciprocal; + const charDef = { x, y, w, h, offsetX, offsetY, xAdvance, u0, v0, u1, v1, ascent, descent }; + this.addCharDef(id, charDef); + this._curX += offsetWidth + space; + return charDef; + } +} + diff --git a/packages/core/src/2d/atlas/FontAtlas.ts b/packages/core/src/2d/atlas/FontAtlas.ts index e1a00a27bc..4b0b35c598 100644 --- a/packages/core/src/2d/atlas/FontAtlas.ts +++ b/packages/core/src/2d/atlas/FontAtlas.ts @@ -6,24 +6,19 @@ import { Texture2D } from "../../texture/Texture2D"; * @internal */ export interface CharDef { - x: number, - y: number, - w: number, - h: number, - offsetX: number, - offsetY: number, - xAdvance: number, - u0: number, - v0: number, - u1: number, - v1: number -} - -/** - * @internal - */ -export interface CharDefDict { - [key: string]: CharDef; + x: number; + y: number; + w: number; + h: number; + offsetX: number; + offsetY: number; + xAdvance: number; + u0: number; + v0: number; + u1: number; + v1: number; + ascent: number; + descent: number; } /** @@ -31,17 +26,23 @@ export interface CharDefDict { * Font Atlas. */ export class FontAtlas extends RefObject { - private _charDefDict: CharDefDict; + private _charDefMap: Record = {}; private _texture: Texture2D; + get texture(): Texture2D { + return this._texture; + } + + set texture(value: Texture2D) { + this._texture = value; + } + /** * Constructor a FontAtlas. * @param engine - Engine to which the FontAtlas belongs */ - constructor(engine: Engine, texture: Texture2D) { + constructor(engine: Engine) { super(engine); - this._charDefDict = {}; - this._texture = texture; } /** @@ -50,19 +51,14 @@ export class FontAtlas extends RefObject { _onDestroy(): void { this._texture.destroy(); this._texture = null; - this._charDefDict = {}; + this._charDefMap = {}; } - addCharDef(key: string, def: CharDef): void { - this._charDefDict[key] = def; + addCharDef(id: number, def: CharDef): void { + this._charDefMap[id] = def; } - getCharDef(key: string): CharDef { - return this._charDefDict[key]; - } - - getTexture(): Texture2D { - return this._texture; + getCharDef(id: number): CharDef { + return this._charDefMap[id]; } } - From 36f4def45a0915924bc5c0bfc4b29ced776e60ae Mon Sep 17 00:00:00 2001 From: singlecoder Date: Wed, 6 Jul 2022 10:52:40 +0800 Subject: [PATCH 20/55] feat(text): change CharDef to CharInfo --- .../core/src/2d/assembler/CharAssembler.ts | 46 +++++++++---------- packages/core/src/2d/assembler/CharInfo.ts | 18 ++++++++ packages/core/src/2d/assembler/CharUtils.ts | 26 +++++------ .../core/src/2d/atlas/DynamicFontAtlas.ts | 13 +++--- packages/core/src/2d/atlas/FontAtlas.ts | 32 +++---------- packages/core/src/2d/text/Font.ts | 30 ++++++++---- 6 files changed, 90 insertions(+), 75 deletions(-) create mode 100644 packages/core/src/2d/assembler/CharInfo.ts diff --git a/packages/core/src/2d/assembler/CharAssembler.ts b/packages/core/src/2d/assembler/CharAssembler.ts index 47790f72bc..ac8991b33b 100644 --- a/packages/core/src/2d/assembler/CharAssembler.ts +++ b/packages/core/src/2d/assembler/CharAssembler.ts @@ -5,7 +5,7 @@ import { OverflowMode } from "../enums/TextOverflow"; import { TextRenderer, DirtyFlag } from "../text/TextRenderer"; import { TextUtils, TextMetrics, FontSizeInfo } from "../text/TextUtils"; import { CharRenderDataPool } from "./CharRenderDataPool"; -import { CharDefWithTexture, CharUtils } from "./CharUtils"; +import { CharInfoWithTexture, CharUtils } from "./CharUtils"; import { IAssembler } from "./IAssembler"; import { StaticInterfaceImplement } from "./StaticInterfaceImplement"; @@ -115,17 +115,17 @@ export class CharAssembler { for (let j = 0, m = line.length; j < m; ++j) { const char = line[j]; - const charDefWithTexture = _charUtils.getCharDef(fontHash, char.charCodeAt(0)); - const { charDef } = charDefWithTexture; + const charInfoWithTexture = _charUtils.getCharInfo(fontHash, char.charCodeAt(0)); + const { charInfo } = charInfoWithTexture; - if (charDef.h > 0) { + if (charInfo.h > 0) { const charRenderData = _charRenderDataPool.getData(); const { renderData, localPositions } = charRenderData; - charRenderData.texture = charDefWithTexture.texture; + charRenderData.texture = charInfoWithTexture.texture; renderData.color = color; const { uvs } = renderData; - const { w, u0, v0, u1, v1, ascent, descent } = charDef; + const { w, u0, v0, u1, v1, ascent, descent } = charInfo; const left = startX * pixelsPerUnitReciprocal; const right = (startX + w) * pixelsPerUnitReciprocal; @@ -146,7 +146,7 @@ export class CharAssembler { _charRenderDatas.push(charRenderData); } - startX += charDef.xAdvance; + startX += charInfo.xAdvance; } startY -= lineHeight; @@ -177,10 +177,10 @@ export class CharAssembler { for (let j = 0, m = subText.length; j < m; ++j) { const char = subText[j]; - const charDefWithTexture = CharAssembler._getCharDefWithTexture(char, fontString, fontHash); - const { charDef } = charDefWithTexture; - const { w, offsetY } = charDef; - const halfH = charDef.h * 0.5; + const charInfoWithTexture = CharAssembler._getCharInfoWithTexture(char, fontString, fontHash); + const { charInfo } = charInfoWithTexture; + const { w, offsetY } = charInfo; + const halfH = charInfo.h * 0.5; const ascent = halfH + offsetY; const descent = halfH - offsetY; if (charsWidth + w > wrapWidth) { @@ -201,13 +201,13 @@ export class CharAssembler { size: maxAscent + maxDescent }); chars = char; - charsWidth = charDef.xAdvance; + charsWidth = charInfo.xAdvance; maxAscent = ascent; maxDescent = descent; } } else { chars += char; - charsWidth += charDef.xAdvance; + charsWidth += charInfo.xAdvance; maxAscent < ascent && (maxAscent = ascent); maxDescent < descent && (maxDescent = descent); } @@ -264,11 +264,11 @@ export class CharAssembler { let maxDescent = -1; for (let j = 0, m = line.length; j < m; ++j) { - const charDefWithTexture = CharAssembler._getCharDefWithTexture(line[j], fontString, fontHash); - const { charDef } = charDefWithTexture; - curWidth += charDef.xAdvance; - const { offsetY } = charDef; - const halfH = charDef.h * 0.5; + const charInfoWithTexture = CharAssembler._getCharInfoWithTexture(line[j], fontString, fontHash); + const { charInfo } = charInfoWithTexture; + curWidth += charInfo.xAdvance; + const { offsetY } = charInfo; + const halfH = charInfo.h * 0.5; const ascent = halfH + offsetY; const descent = halfH - offsetY; maxAscent < ascent && (maxAscent = ascent); @@ -295,16 +295,16 @@ export class CharAssembler { }; } - private static _getCharDefWithTexture(char: string, fontString: string, fontHash: string): CharDefWithTexture { + private static _getCharInfoWithTexture(char: string, fontString: string, fontHash: string): CharInfoWithTexture { const { _charUtils } = CharAssembler; const id = char.charCodeAt(0); - let charDefWithTexture = _charUtils.getCharDef(fontHash, id); - if (!charDefWithTexture) { + let charInfoWithTexture = _charUtils.getCharInfo(fontHash, id); + if (!charInfoWithTexture) { const charMetrics = TextUtils.measureChar(char, fontString); const { width, sizeInfo } = charMetrics; const { ascent, descent } = sizeInfo; const offsetY = (ascent - descent) * 0.5; - charDefWithTexture = _charUtils.addCharDef( + charInfoWithTexture = _charUtils.addCharInfo( fontHash, id, TextUtils.textContext().canvas, @@ -318,6 +318,6 @@ export class CharAssembler { ); } - return charDefWithTexture; + return charInfoWithTexture; } } diff --git a/packages/core/src/2d/assembler/CharInfo.ts b/packages/core/src/2d/assembler/CharInfo.ts new file mode 100644 index 0000000000..b8c14fb1af --- /dev/null +++ b/packages/core/src/2d/assembler/CharInfo.ts @@ -0,0 +1,18 @@ +/** + * @internal + */ +export interface CharInfo { + x: number; + y: number; + w: number; + h: number; + offsetX: number; + offsetY: number; + xAdvance: number; + u0: number; + v0: number; + u1: number; + v1: number; + ascent: number; + descent: number; +} diff --git a/packages/core/src/2d/assembler/CharUtils.ts b/packages/core/src/2d/assembler/CharUtils.ts index ddd672c32f..f401a93cab 100644 --- a/packages/core/src/2d/assembler/CharUtils.ts +++ b/packages/core/src/2d/assembler/CharUtils.ts @@ -1,14 +1,14 @@ import { Engine } from "../../Engine"; import { Texture2D } from "../../texture"; import { DynamicFontAtlas } from "../atlas/DynamicFontAtlas"; -import { CharDef } from "../atlas/FontAtlas"; +import { CharInfo } from "./CharInfo"; /** * @internal */ -export interface CharDefWithTexture { +export interface CharInfoWithTexture { texture: Texture2D; - charDef: CharDef; + charInfo: CharInfo; } /** @@ -22,7 +22,7 @@ export class CharUtils { */ constructor(public readonly engine: Engine) {} - addCharDef( + addCharInfo( fontHash: string, id: number, imageSource: TexImageSource | OffscreenCanvas, @@ -33,7 +33,7 @@ export class CharUtils { xAdvance: number, ascent: number, descent: number, - ): CharDefWithTexture { + ): CharInfoWithTexture { const { _fontAtlasListMap } = this; let fontAtlasList = _fontAtlasListMap[fontHash]; if (!fontAtlasList) { @@ -42,24 +42,24 @@ export class CharUtils { const lastIndex = fontAtlasList.length - 1; let lastFontAtlas = fontAtlasList[lastIndex]; - let charDef = lastFontAtlas.addCharDefDynamic(id, imageSource, width, height, offsetX, offsetY, xAdvance, ascent, descent); - if (!charDef) { + let charInfo = lastFontAtlas.addCharInfoDynamic(id, imageSource, width, height, offsetX, offsetY, xAdvance, ascent, descent); + if (!charInfo) { lastFontAtlas = new DynamicFontAtlas(this.engine); fontAtlasList.push(lastFontAtlas); - charDef = lastFontAtlas.addCharDefDynamic(id, imageSource, width, height, offsetX, offsetY, xAdvance, ascent, descent); + charInfo = lastFontAtlas.addCharInfoDynamic(id, imageSource, width, height, offsetX, offsetY, xAdvance, ascent, descent); } - return charDef ? { charDef, texture: lastFontAtlas.texture } : null; + return charInfo ? { charInfo: charInfo, texture: lastFontAtlas.texture } : null; } - getCharDef(fontHash: string, id: number): CharDefWithTexture { + getCharInfo(fontHash: string, id: number): CharInfoWithTexture { let fontAtlasList = this._fontAtlasListMap[fontHash]; if (fontAtlasList) { for (let i = 0, l = fontAtlasList.length; i < l; ++i) { const fontAtlas = fontAtlasList[i]; - const charDef = fontAtlas.getCharDef(id); - if (charDef) { + const charInfo = fontAtlas.getCharInfo(id); + if (charInfo) { return { - charDef, + charInfo: charInfo, texture: fontAtlas.texture } } diff --git a/packages/core/src/2d/atlas/DynamicFontAtlas.ts b/packages/core/src/2d/atlas/DynamicFontAtlas.ts index 7184cc0a70..e87af7db1b 100644 --- a/packages/core/src/2d/atlas/DynamicFontAtlas.ts +++ b/packages/core/src/2d/atlas/DynamicFontAtlas.ts @@ -1,6 +1,7 @@ import { Engine } from "../../Engine"; import { Texture2D } from "../../texture"; -import { CharDef, FontAtlas } from "./FontAtlas"; +import { CharInfo } from "../assembler/CharInfo"; +import { FontAtlas } from "./FontAtlas"; /** * @internal @@ -18,7 +19,7 @@ export class DynamicFontAtlas extends FontAtlas { this.texture = new Texture2D(engine, _textureSize, _textureSize); } - addCharDefDynamic( + addCharInfoDynamic( id: number, imageSource: TexImageSource | OffscreenCanvas, width: number, @@ -28,7 +29,7 @@ export class DynamicFontAtlas extends FontAtlas { xAdvance: number, ascent: number, descent: number, - ): CharDef { + ): CharInfo { const { _space: space, _textureSize: textureSize } = this; const offsetWidth = width + space; const offsetHeight = height + space; @@ -64,10 +65,10 @@ export class DynamicFontAtlas extends FontAtlas { const u1 = (x + w) * textureSizeReciprocal; const v0 = y * textureSizeReciprocal; const v1 = (y + h) * textureSizeReciprocal; - const charDef = { x, y, w, h, offsetX, offsetY, xAdvance, u0, v0, u1, v1, ascent, descent }; - this.addCharDef(id, charDef); + const charInfo = { x, y, w, h, offsetX, offsetY, xAdvance, u0, v0, u1, v1, ascent, descent }; + this.addCharInfo(id, charInfo); this._curX += offsetWidth + space; - return charDef; + return charInfo; } } diff --git a/packages/core/src/2d/atlas/FontAtlas.ts b/packages/core/src/2d/atlas/FontAtlas.ts index 4b0b35c598..b81ffd05c9 100644 --- a/packages/core/src/2d/atlas/FontAtlas.ts +++ b/packages/core/src/2d/atlas/FontAtlas.ts @@ -1,32 +1,14 @@ import { RefObject } from "../../asset/RefObject"; import { Engine } from "../../Engine"; import { Texture2D } from "../../texture/Texture2D"; - -/** - * @internal - */ -export interface CharDef { - x: number; - y: number; - w: number; - h: number; - offsetX: number; - offsetY: number; - xAdvance: number; - u0: number; - v0: number; - u1: number; - v1: number; - ascent: number; - descent: number; -} +import { CharInfo } from "../assembler/CharInfo"; /** * @internal * Font Atlas. */ export class FontAtlas extends RefObject { - private _charDefMap: Record = {}; + private _charInfoMap: Record = {}; private _texture: Texture2D; get texture(): Texture2D { @@ -51,14 +33,14 @@ export class FontAtlas extends RefObject { _onDestroy(): void { this._texture.destroy(); this._texture = null; - this._charDefMap = {}; + this._charInfoMap = {}; } - addCharDef(id: number, def: CharDef): void { - this._charDefMap[id] = def; + addCharInfo(id: number, def: CharInfo): void { + this._charInfoMap[id] = def; } - getCharDef(id: number): CharDef { - return this._charDefMap[id]; + getCharInfo(id: number): CharInfo { + return this._charInfoMap[id]; } } diff --git a/packages/core/src/2d/text/Font.ts b/packages/core/src/2d/text/Font.ts index f56e36b27a..7bc14d17d1 100644 --- a/packages/core/src/2d/text/Font.ts +++ b/packages/core/src/2d/text/Font.ts @@ -1,5 +1,6 @@ import { RefObject } from "../../asset/RefObject"; import { Engine } from "../../Engine"; +import { FontAtlas } from "../atlas/FontAtlas"; /** * Font. @@ -10,21 +11,23 @@ export class Font extends RefObject { /** * Create a font from OS. * @param engine - Engine to which the font belongs - * @param fontName - The name of font + * @param name - The name of font * @returns The font object has been create */ - static createFromOS(engine: Engine, fontName: string = ""): Font { + static createFromOS(engine: Engine, name: string = ""): Font { const fontMap = Font._fontMap; - let font = fontMap[fontName]; + let font = fontMap[name]; if (font) { return font; } - font = new Font(engine, fontName); - fontMap[fontName] = font; + font = new Font(engine, name); + fontMap[name] = font; return font; } private _name: string = ""; + private _fontName: string = ""; + private _fontAtlasArray: Array = []; /** * The name of the font object. @@ -33,13 +36,24 @@ export class Font extends RefObject { return this._name; } - private constructor(engine: Engine, name: string = "") { - super(engine); - this._name = name; + /** + * The font name for Canvas. + */ + get fontName(): string { + return this._fontName; + } + + set fontName(value: string) { + this._fontName = value; } /** * @override */ protected _onDestroy(): void {} + + private constructor(engine: Engine, name: string = "") { + super(engine); + this._name = name; + } } From 3bce13237a391798d1ca15a1d644ddeacdb95f13 Mon Sep 17 00:00:00 2001 From: singlecoder Date: Wed, 6 Jul 2022 14:07:27 +0800 Subject: [PATCH 21/55] feat(text): opt code --- .../core/src/2d/assembler/CharAssembler.ts | 34 ++++---- packages/core/src/2d/assembler/CharInfo.ts | 10 +++ packages/core/src/2d/assembler/CharUtils.ts | 82 ------------------- .../core/src/2d/atlas/DynamicFontAtlas.ts | 74 ----------------- packages/core/src/2d/atlas/FontAtlas.ts | 56 ++++++++++++- packages/core/src/2d/text/Font.ts | 70 ++++++++++++++-- packages/core/src/2d/text/TextRenderer.ts | 3 + 7 files changed, 145 insertions(+), 184 deletions(-) delete mode 100644 packages/core/src/2d/assembler/CharUtils.ts delete mode 100644 packages/core/src/2d/atlas/DynamicFontAtlas.ts diff --git a/packages/core/src/2d/assembler/CharAssembler.ts b/packages/core/src/2d/assembler/CharAssembler.ts index ac8991b33b..0207543dbd 100644 --- a/packages/core/src/2d/assembler/CharAssembler.ts +++ b/packages/core/src/2d/assembler/CharAssembler.ts @@ -2,10 +2,10 @@ import { Vector3 } from "@oasis-engine/math"; import { Engine } from "../../Engine"; import { TextHorizontalAlignment, TextVerticalAlignment } from "../enums/TextAlignment"; import { OverflowMode } from "../enums/TextOverflow"; +import { Font } from "../text"; import { TextRenderer, DirtyFlag } from "../text/TextRenderer"; import { TextUtils, TextMetrics, FontSizeInfo } from "../text/TextUtils"; import { CharRenderDataPool } from "./CharRenderDataPool"; -import { CharInfoWithTexture, CharUtils } from "./CharUtils"; import { IAssembler } from "./IAssembler"; import { StaticInterfaceImplement } from "./StaticInterfaceImplement"; @@ -14,12 +14,10 @@ import { StaticInterfaceImplement } from "./StaticInterfaceImplement"; */ @StaticInterfaceImplement() export class CharAssembler { - private static _charUtils: CharUtils; private static _charRenderDataPool: CharRenderDataPool; static resetData(renderer: TextRenderer): void { - if (!CharAssembler._charUtils) { - CharAssembler._charUtils = new CharUtils(renderer.engine); + if (!CharAssembler._charRenderDataPool) { CharAssembler._charRenderDataPool = new CharRenderDataPool(); } } @@ -27,6 +25,7 @@ export class CharAssembler { static updateData(renderer: TextRenderer): void { const isTextureDirty = renderer._isContainDirtyFlag(DirtyFlag.Property); if (isTextureDirty) { + renderer._charFont = Font.createFromOS(renderer.engine, TextUtils.getNativeFontHash(renderer.font.name, renderer.fontSize, renderer.fontStyle)); CharAssembler.clearData(renderer); CharAssembler._updateText(renderer); renderer._setDirtyFlagFalse(DirtyFlag.Property); @@ -39,10 +38,7 @@ export class CharAssembler { } static clear(): void { - if (CharAssembler._charUtils) { - CharAssembler._charUtils.clear(); - CharAssembler._charUtils = null; - } + } static clearData(renderer: TextRenderer): void { @@ -69,7 +65,7 @@ export class CharAssembler { const { name } = renderer.font; const { _pixelsPerUnit } = Engine; const pixelsPerUnitReciprocal = 1.0 / _pixelsPerUnit; - const fontHash = TextUtils.getNativeFontHash(name, fontSize, fontStyle); + const charFont = renderer._charFont; const rendererWidth = renderer.width * _pixelsPerUnit; const rendererHeight = renderer.height * _pixelsPerUnit; @@ -77,7 +73,7 @@ export class CharAssembler { ? CharAssembler._measureTextWithWrap(renderer) : CharAssembler._measureTextWithoutWrap(renderer); const { height, lines, lineWidths, lineHeight, lineMaxSizes } = textMetrics; - const { _charUtils, _charRenderDataPool } = CharAssembler; + const { _charRenderDataPool } = CharAssembler; const halfLineHeight = lineHeight * 0.5; const linesLen = lines.length; @@ -115,7 +111,7 @@ export class CharAssembler { for (let j = 0, m = line.length; j < m; ++j) { const char = line[j]; - const charInfoWithTexture = _charUtils.getCharInfo(fontHash, char.charCodeAt(0)); + const charInfoWithTexture = charFont.getCharInfo(char.charCodeAt(0)); const { charInfo } = charInfoWithTexture; if (charInfo.h > 0) { @@ -157,7 +153,7 @@ export class CharAssembler { const { fontSize, fontStyle } = renderer; const { name } = renderer.font; const fontString = TextUtils.getNativeFontString(name, fontSize, fontStyle); - const fontHash = TextUtils.getNativeFontHash(name, fontSize, fontStyle); + const charFont = renderer._charFont; const fontSizeInfo = TextUtils.measureFont(fontString); const subTexts = renderer.text.split(/(?:\r\n|\r|\n)/); const lines = new Array(); @@ -177,7 +173,7 @@ export class CharAssembler { for (let j = 0, m = subText.length; j < m; ++j) { const char = subText[j]; - const charInfoWithTexture = CharAssembler._getCharInfoWithTexture(char, fontString, fontHash); + const charInfoWithTexture = CharAssembler._getCharInfoWithTexture(char, fontString, charFont); const { charInfo } = charInfoWithTexture; const { w, offsetY } = charInfo; const halfH = charInfo.h * 0.5; @@ -243,7 +239,7 @@ export class CharAssembler { const { fontSize, fontStyle } = renderer; const { name } = renderer.font; const fontString = TextUtils.getNativeFontString(name, fontSize, fontStyle); - const fontHash = TextUtils.getNativeFontHash(name, fontSize, fontStyle); + const charFont = renderer._charFont; const fontSizeInfo = TextUtils.measureFont(fontString); const lines = renderer.text.split(/(?:\r\n|\r|\n)/); const lineCount = lines.length; @@ -264,7 +260,7 @@ export class CharAssembler { let maxDescent = -1; for (let j = 0, m = line.length; j < m; ++j) { - const charInfoWithTexture = CharAssembler._getCharInfoWithTexture(line[j], fontString, fontHash); + const charInfoWithTexture = CharAssembler._getCharInfoWithTexture(line[j], fontString, charFont); const { charInfo } = charInfoWithTexture; curWidth += charInfo.xAdvance; const { offsetY } = charInfo; @@ -295,17 +291,15 @@ export class CharAssembler { }; } - private static _getCharInfoWithTexture(char: string, fontString: string, fontHash: string): CharInfoWithTexture { - const { _charUtils } = CharAssembler; + private static _getCharInfoWithTexture(char: string, fontString: string, font: Font): CharInfoWithTexture { const id = char.charCodeAt(0); - let charInfoWithTexture = _charUtils.getCharInfo(fontHash, id); + let charInfoWithTexture = font.getCharInfo(id); if (!charInfoWithTexture) { const charMetrics = TextUtils.measureChar(char, fontString); const { width, sizeInfo } = charMetrics; const { ascent, descent } = sizeInfo; const offsetY = (ascent - descent) * 0.5; - charInfoWithTexture = _charUtils.addCharInfo( - fontHash, + charInfoWithTexture = font.addCharInfo( id, TextUtils.textContext().canvas, width, diff --git a/packages/core/src/2d/assembler/CharInfo.ts b/packages/core/src/2d/assembler/CharInfo.ts index b8c14fb1af..b527490c95 100644 --- a/packages/core/src/2d/assembler/CharInfo.ts +++ b/packages/core/src/2d/assembler/CharInfo.ts @@ -1,3 +1,5 @@ +import { Texture2D } from "../../texture"; + /** * @internal */ @@ -16,3 +18,11 @@ export interface CharInfo { ascent: number; descent: number; } + +/** + * @internal + */ +export interface CharInfoWithTexture { + texture: Texture2D; + charInfo: CharInfo; +} diff --git a/packages/core/src/2d/assembler/CharUtils.ts b/packages/core/src/2d/assembler/CharUtils.ts deleted file mode 100644 index f401a93cab..0000000000 --- a/packages/core/src/2d/assembler/CharUtils.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { Engine } from "../../Engine"; -import { Texture2D } from "../../texture"; -import { DynamicFontAtlas } from "../atlas/DynamicFontAtlas"; -import { CharInfo } from "./CharInfo"; - -/** - * @internal - */ -export interface CharInfoWithTexture { - texture: Texture2D; - charInfo: CharInfo; -} - -/** - * @internal - */ -export class CharUtils { - private _fontAtlasListMap: Record> = {}; - - /** - * @internal - */ - constructor(public readonly engine: Engine) {} - - addCharInfo( - fontHash: string, - id: number, - imageSource: TexImageSource | OffscreenCanvas, - width: number, - height: number, - offsetX: number, - offsetY: number, - xAdvance: number, - ascent: number, - descent: number, - ): CharInfoWithTexture { - const { _fontAtlasListMap } = this; - let fontAtlasList = _fontAtlasListMap[fontHash]; - if (!fontAtlasList) { - fontAtlasList = _fontAtlasListMap[fontHash] = [new DynamicFontAtlas(this.engine)]; - } - - const lastIndex = fontAtlasList.length - 1; - let lastFontAtlas = fontAtlasList[lastIndex]; - let charInfo = lastFontAtlas.addCharInfoDynamic(id, imageSource, width, height, offsetX, offsetY, xAdvance, ascent, descent); - if (!charInfo) { - lastFontAtlas = new DynamicFontAtlas(this.engine); - fontAtlasList.push(lastFontAtlas); - charInfo = lastFontAtlas.addCharInfoDynamic(id, imageSource, width, height, offsetX, offsetY, xAdvance, ascent, descent); - } - return charInfo ? { charInfo: charInfo, texture: lastFontAtlas.texture } : null; - } - - getCharInfo(fontHash: string, id: number): CharInfoWithTexture { - let fontAtlasList = this._fontAtlasListMap[fontHash]; - if (fontAtlasList) { - for (let i = 0, l = fontAtlasList.length; i < l; ++i) { - const fontAtlas = fontAtlasList[i]; - const charInfo = fontAtlas.getCharInfo(id); - if (charInfo) { - return { - charInfo: charInfo, - texture: fontAtlas.texture - } - } - } - } - return null; - } - - clear(): void { - const { _fontAtlasListMap } = this; - Object.keys(_fontAtlasListMap).forEach((key) => { - const fontAtlasList = _fontAtlasListMap[key]; - for (let i = 0, l = fontAtlasList.length; i < l; ++i) { - fontAtlasList[i].destroy(); - } - fontAtlasList.length = 0; - delete _fontAtlasListMap[key]; - }); - } -} diff --git a/packages/core/src/2d/atlas/DynamicFontAtlas.ts b/packages/core/src/2d/atlas/DynamicFontAtlas.ts deleted file mode 100644 index e87af7db1b..0000000000 --- a/packages/core/src/2d/atlas/DynamicFontAtlas.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { Engine } from "../../Engine"; -import { Texture2D } from "../../texture"; -import { CharInfo } from "../assembler/CharInfo"; -import { FontAtlas } from "./FontAtlas"; - -/** - * @internal - */ -export class DynamicFontAtlas extends FontAtlas { - private _textureSize: number = 512; - private _space: number = 1; - private _curX: number = 1; - private _curY: number = 1; - private _nextY: number = 1; - - constructor(engine: Engine) { - super(engine); - const { _textureSize } = this; - this.texture = new Texture2D(engine, _textureSize, _textureSize); - } - - addCharInfoDynamic( - id: number, - imageSource: TexImageSource | OffscreenCanvas, - width: number, - height: number, - offsetX: number, - offsetY: number, - xAdvance: number, - ascent: number, - descent: number, - ): CharInfo { - const { _space: space, _textureSize: textureSize } = this; - const offsetWidth = width + space; - const offsetHeight = height + space; - if ((1 + offsetWidth) >= textureSize || (1 + offsetHeight) >= textureSize) { - throw Error("The char fontSize is too large."); - } - - const endX = this._curX + offsetWidth; - if (endX >= textureSize) { - this._curX = space; - this._curY = this._nextY + space; - } - const endY = this._curY + offsetHeight; - if (endY > this._nextY) { - this._nextY = endY; - } - if (endY >= textureSize) { - return null; - } - - if (width > 0 && height > 0) { - const { texture } = this; - texture.setImageSource(imageSource, 0, false, false, this._curX, this._curY); - texture.generateMipmaps(); - } - - const textureSizeReciprocal = 1.0 / textureSize; - const x = this._curX; - const y = this._curY; - const w = width; - const h = height; - const u0 = x * textureSizeReciprocal; - const u1 = (x + w) * textureSizeReciprocal; - const v0 = y * textureSizeReciprocal; - const v1 = (y + h) * textureSizeReciprocal; - const charInfo = { x, y, w, h, offsetX, offsetY, xAdvance, u0, v0, u1, v1, ascent, descent }; - this.addCharInfo(id, charInfo); - this._curX += offsetWidth + space; - return charInfo; - } -} - diff --git a/packages/core/src/2d/atlas/FontAtlas.ts b/packages/core/src/2d/atlas/FontAtlas.ts index b81ffd05c9..e25a6e34c2 100644 --- a/packages/core/src/2d/atlas/FontAtlas.ts +++ b/packages/core/src/2d/atlas/FontAtlas.ts @@ -10,6 +10,10 @@ import { CharInfo } from "../assembler/CharInfo"; export class FontAtlas extends RefObject { private _charInfoMap: Record = {}; private _texture: Texture2D; + private _space: number = 1; + private _curX: number = 1; + private _curY: number = 1; + private _nextY: number = 1; get texture(): Texture2D { return this._texture; @@ -36,8 +40,56 @@ export class FontAtlas extends RefObject { this._charInfoMap = {}; } - addCharInfo(id: number, def: CharInfo): void { - this._charInfoMap[id] = def; + addCharInfo( + id: number, + imageSource: TexImageSource | OffscreenCanvas, + width: number, + height: number, + offsetX: number, + offsetY: number, + xAdvance: number, + ascent: number, + descent: number, + ): CharInfo { + const { _space: space, texture } = this; + const textureSize = texture.width; + const offsetWidth = width + space; + const offsetHeight = height + space; + if ((1 + offsetWidth) >= textureSize || (1 + offsetHeight) >= textureSize) { + throw Error("The char fontSize is too large."); + } + + const endX = this._curX + offsetWidth; + if (endX >= textureSize) { + this._curX = space; + this._curY = this._nextY + space; + } + const endY = this._curY + offsetHeight; + if (endY > this._nextY) { + this._nextY = endY; + } + if (endY >= textureSize) { + return null; + } + + if (width > 0 && height > 0) { + texture.setImageSource(imageSource, 0, false, false, this._curX, this._curY); + texture.generateMipmaps(); + } + + const textureSizeReciprocal = 1.0 / textureSize; + const x = this._curX; + const y = this._curY; + const w = width; + const h = height; + const u0 = x * textureSizeReciprocal; + const u1 = (x + w) * textureSizeReciprocal; + const v0 = y * textureSizeReciprocal; + const v1 = (y + h) * textureSizeReciprocal; + const charInfo = { x, y, w, h, offsetX, offsetY, xAdvance, u0, v0, u1, v1, ascent, descent }; + this._charInfoMap[id] = charInfo; + this._curX += offsetWidth + space; + return charInfo; } getCharInfo(id: number): CharInfo { diff --git a/packages/core/src/2d/text/Font.ts b/packages/core/src/2d/text/Font.ts index 7bc14d17d1..8a83c0bc2c 100644 --- a/packages/core/src/2d/text/Font.ts +++ b/packages/core/src/2d/text/Font.ts @@ -1,5 +1,7 @@ import { RefObject } from "../../asset/RefObject"; import { Engine } from "../../Engine"; +import { Texture2D } from "../../texture"; +import { CharInfoWithTexture } from "../assembler/CharInfo"; import { FontAtlas } from "../atlas/FontAtlas"; /** @@ -26,7 +28,6 @@ export class Font extends RefObject { } private _name: string = ""; - private _fontName: string = ""; private _fontAtlasArray: Array = []; /** @@ -37,14 +38,62 @@ export class Font extends RefObject { } /** - * The font name for Canvas. + * Add char into font atlas. + * @param id - The unique id for char + * @param imageSource - The source of texture + * @param width - The width of char + * @param height - The height of char + * @param offsetX - The char offset in X axis + * @param offsetY - The char offset in Y axis + * @param xAdvance - The next char start position in X axis + * @param ascent - The ascent of char + * @param descent - The descent of char + * @returns - The char's char info and texture */ - get fontName(): string { - return this._fontName; + addCharInfo( + id: number, + imageSource: TexImageSource | OffscreenCanvas, + width: number, + height: number, + offsetX: number, + offsetY: number, + xAdvance: number, + ascent: number, + descent: number, + ): CharInfoWithTexture { + const { _fontAtlasArray: fontAtlasArray } = this; + if (fontAtlasArray.length === 0) { + this._createFontAtlas(); + } + + const lastIndex = fontAtlasArray.length - 1; + let lastFontAtlas = fontAtlasArray[lastIndex]; + let charInfo = lastFontAtlas.addCharInfo(id, imageSource, width, height, offsetX, offsetY, xAdvance, ascent, descent); + if (!charInfo) { + lastFontAtlas = this._createFontAtlas(); + charInfo = lastFontAtlas.addCharInfo(id, imageSource, width, height, offsetX, offsetY, xAdvance, ascent, descent); + } + return charInfo ? { charInfo: charInfo, texture: lastFontAtlas.texture } : null; } - set fontName(value: string) { - this._fontName = value; + /** + * Get char info. + * @param id - The unique id for char + * @returns - The char's char info and texture + */ + getCharInfo(id: number): CharInfoWithTexture { + const { _fontAtlasArray: fontAtlasArray } = this; + for (let i = 0, l = fontAtlasArray.length; i < l; ++i) { + const fontAtlas = fontAtlasArray[i]; + const charInfo = fontAtlas.getCharInfo(id); + if (charInfo) { + return { + charInfo: charInfo, + texture: fontAtlas.texture + } + } + } + return null; } /** @@ -56,4 +105,13 @@ export class Font extends RefObject { super(engine); this._name = name; } + + private _createFontAtlas(): FontAtlas { + const { engine } = this; + const fontAtlas = new FontAtlas(engine); + const texture = new Texture2D(engine, 512, 512); + fontAtlas.texture = texture; + this._fontAtlasArray.push(fontAtlas); + return fontAtlas; + } } diff --git a/packages/core/src/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts index f39a049cf8..adcc656010 100644 --- a/packages/core/src/2d/text/TextRenderer.ts +++ b/packages/core/src/2d/text/TextRenderer.ts @@ -33,6 +33,9 @@ export class TextRenderer extends Renderer implements ICustomClone { @ignoreClone _renderData: RenderData2D; /** @internal */ + @assignmentClone + _charFont: Font = null; + /** @internal */ @ignoreClone _charRenderDatas: Array = []; From ecf9d7aacac8b4f298e1885feacf836df12b34ed Mon Sep 17 00:00:00 2001 From: singlecoder Date: Wed, 6 Jul 2022 14:08:43 +0800 Subject: [PATCH 22/55] feat(text): opt code --- packages/core/src/2d/assembler/CharAssembler.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/src/2d/assembler/CharAssembler.ts b/packages/core/src/2d/assembler/CharAssembler.ts index 0207543dbd..85cedc97a4 100644 --- a/packages/core/src/2d/assembler/CharAssembler.ts +++ b/packages/core/src/2d/assembler/CharAssembler.ts @@ -5,6 +5,7 @@ import { OverflowMode } from "../enums/TextOverflow"; import { Font } from "../text"; import { TextRenderer, DirtyFlag } from "../text/TextRenderer"; import { TextUtils, TextMetrics, FontSizeInfo } from "../text/TextUtils"; +import { CharInfoWithTexture } from "./CharInfo"; import { CharRenderDataPool } from "./CharRenderDataPool"; import { IAssembler } from "./IAssembler"; import { StaticInterfaceImplement } from "./StaticInterfaceImplement"; From 944946eafd4374875c1305685488810b1fb1b6ad Mon Sep 17 00:00:00 2001 From: singlecoder Date: Wed, 6 Jul 2022 15:15:48 +0800 Subject: [PATCH 23/55] feat(text): opt code --- .../core/src/2d/assembler/CharAssembler.ts | 26 +++++++---------- packages/core/src/2d/assembler/CharInfo.ts | 11 +------ packages/core/src/2d/atlas/FontAtlas.ts | 3 +- packages/core/src/2d/text/Font.ts | 29 ++++++++++++------- 4 files changed, 32 insertions(+), 37 deletions(-) diff --git a/packages/core/src/2d/assembler/CharAssembler.ts b/packages/core/src/2d/assembler/CharAssembler.ts index 85cedc97a4..b5538f5f29 100644 --- a/packages/core/src/2d/assembler/CharAssembler.ts +++ b/packages/core/src/2d/assembler/CharAssembler.ts @@ -5,7 +5,7 @@ import { OverflowMode } from "../enums/TextOverflow"; import { Font } from "../text"; import { TextRenderer, DirtyFlag } from "../text/TextRenderer"; import { TextUtils, TextMetrics, FontSizeInfo } from "../text/TextUtils"; -import { CharInfoWithTexture } from "./CharInfo"; +import { CharInfo } from "./CharInfo"; import { CharRenderDataPool } from "./CharRenderDataPool"; import { IAssembler } from "./IAssembler"; import { StaticInterfaceImplement } from "./StaticInterfaceImplement"; @@ -62,8 +62,7 @@ export class CharAssembler { } private static _updateText(renderer: TextRenderer): void { - const { color, fontSize, fontStyle, horizontalAlignment, verticalAlignment, _charRenderDatas } = renderer; - const { name } = renderer.font; + const { color, horizontalAlignment, verticalAlignment, _charRenderDatas } = renderer; const { _pixelsPerUnit } = Engine; const pixelsPerUnitReciprocal = 1.0 / _pixelsPerUnit; const charFont = renderer._charFont; @@ -112,13 +111,12 @@ export class CharAssembler { for (let j = 0, m = line.length; j < m; ++j) { const char = line[j]; - const charInfoWithTexture = charFont.getCharInfo(char.charCodeAt(0)); - const { charInfo } = charInfoWithTexture; + const charInfo = charFont.getCharInfo(char.charCodeAt(0)); if (charInfo.h > 0) { const charRenderData = _charRenderDataPool.getData(); const { renderData, localPositions } = charRenderData; - charRenderData.texture = charInfoWithTexture.texture; + charRenderData.texture = charFont.getTextureByIndex(charInfo.index); renderData.color = color; const { uvs } = renderData; @@ -174,8 +172,7 @@ export class CharAssembler { for (let j = 0, m = subText.length; j < m; ++j) { const char = subText[j]; - const charInfoWithTexture = CharAssembler._getCharInfoWithTexture(char, fontString, charFont); - const { charInfo } = charInfoWithTexture; + const charInfo = CharAssembler._getCharInfo(char, fontString, charFont); const { w, offsetY } = charInfo; const halfH = charInfo.h * 0.5; const ascent = halfH + offsetY; @@ -261,8 +258,7 @@ export class CharAssembler { let maxDescent = -1; for (let j = 0, m = line.length; j < m; ++j) { - const charInfoWithTexture = CharAssembler._getCharInfoWithTexture(line[j], fontString, charFont); - const { charInfo } = charInfoWithTexture; + const charInfo = CharAssembler._getCharInfo(line[j], fontString, charFont); curWidth += charInfo.xAdvance; const { offsetY } = charInfo; const halfH = charInfo.h * 0.5; @@ -292,15 +288,15 @@ export class CharAssembler { }; } - private static _getCharInfoWithTexture(char: string, fontString: string, font: Font): CharInfoWithTexture { + private static _getCharInfo(char: string, fontString: string, font: Font): CharInfo { const id = char.charCodeAt(0); - let charInfoWithTexture = font.getCharInfo(id); - if (!charInfoWithTexture) { + let charInfo = font.getCharInfo(id); + if (!charInfo) { const charMetrics = TextUtils.measureChar(char, fontString); const { width, sizeInfo } = charMetrics; const { ascent, descent } = sizeInfo; const offsetY = (ascent - descent) * 0.5; - charInfoWithTexture = font.addCharInfo( + charInfo = font.addCharInfo( id, TextUtils.textContext().canvas, width, @@ -313,6 +309,6 @@ export class CharAssembler { ); } - return charInfoWithTexture; + return charInfo; } } diff --git a/packages/core/src/2d/assembler/CharInfo.ts b/packages/core/src/2d/assembler/CharInfo.ts index b527490c95..a4696fd6c2 100644 --- a/packages/core/src/2d/assembler/CharInfo.ts +++ b/packages/core/src/2d/assembler/CharInfo.ts @@ -1,5 +1,3 @@ -import { Texture2D } from "../../texture"; - /** * @internal */ @@ -17,12 +15,5 @@ export interface CharInfo { v1: number; ascent: number; descent: number; -} - -/** - * @internal - */ -export interface CharInfoWithTexture { - texture: Texture2D; - charInfo: CharInfo; + index: number; } diff --git a/packages/core/src/2d/atlas/FontAtlas.ts b/packages/core/src/2d/atlas/FontAtlas.ts index e25a6e34c2..49f7a72948 100644 --- a/packages/core/src/2d/atlas/FontAtlas.ts +++ b/packages/core/src/2d/atlas/FontAtlas.ts @@ -50,6 +50,7 @@ export class FontAtlas extends RefObject { xAdvance: number, ascent: number, descent: number, + index: number = 0, ): CharInfo { const { _space: space, texture } = this; const textureSize = texture.width; @@ -86,7 +87,7 @@ export class FontAtlas extends RefObject { const u1 = (x + w) * textureSizeReciprocal; const v0 = y * textureSizeReciprocal; const v1 = (y + h) * textureSizeReciprocal; - const charInfo = { x, y, w, h, offsetX, offsetY, xAdvance, u0, v0, u1, v1, ascent, descent }; + const charInfo = { x, y, w, h, offsetX, offsetY, xAdvance, u0, v0, u1, v1, ascent, descent, index }; this._charInfoMap[id] = charInfo; this._curX += offsetWidth + space; return charInfo; diff --git a/packages/core/src/2d/text/Font.ts b/packages/core/src/2d/text/Font.ts index 8a83c0bc2c..386764f173 100644 --- a/packages/core/src/2d/text/Font.ts +++ b/packages/core/src/2d/text/Font.ts @@ -1,7 +1,7 @@ import { RefObject } from "../../asset/RefObject"; import { Engine } from "../../Engine"; import { Texture2D } from "../../texture"; -import { CharInfoWithTexture } from "../assembler/CharInfo"; +import { CharInfo } from "../assembler/CharInfo"; import { FontAtlas } from "../atlas/FontAtlas"; /** @@ -60,7 +60,7 @@ export class Font extends RefObject { xAdvance: number, ascent: number, descent: number, - ): CharInfoWithTexture { + ): CharInfo { const { _fontAtlasArray: fontAtlasArray } = this; if (fontAtlasArray.length === 0) { this._createFontAtlas(); @@ -68,12 +68,12 @@ export class Font extends RefObject { const lastIndex = fontAtlasArray.length - 1; let lastFontAtlas = fontAtlasArray[lastIndex]; - let charInfo = lastFontAtlas.addCharInfo(id, imageSource, width, height, offsetX, offsetY, xAdvance, ascent, descent); + let charInfo = lastFontAtlas.addCharInfo(id, imageSource, width, height, offsetX, offsetY, xAdvance, ascent, descent, lastIndex); if (!charInfo) { lastFontAtlas = this._createFontAtlas(); - charInfo = lastFontAtlas.addCharInfo(id, imageSource, width, height, offsetX, offsetY, xAdvance, ascent, descent); + charInfo = lastFontAtlas.addCharInfo(id, imageSource, width, height, offsetX, offsetY, xAdvance, ascent, descent, lastIndex + 1); } - return charInfo ? { charInfo: charInfo, texture: lastFontAtlas.texture } : null; + return charInfo; } /** @@ -81,25 +81,32 @@ export class Font extends RefObject { * @param id - The unique id for char * @returns - The char's char info and texture */ - getCharInfo(id: number): CharInfoWithTexture { + getCharInfo(id: number): CharInfo { const { _fontAtlasArray: fontAtlasArray } = this; for (let i = 0, l = fontAtlasArray.length; i < l; ++i) { const fontAtlas = fontAtlasArray[i]; const charInfo = fontAtlas.getCharInfo(id); if (charInfo) { - return { - charInfo: charInfo, - texture: fontAtlas.texture - } + return charInfo; } } return null; } + getTextureByIndex(index: number): Texture2D { + const fontAtlas = this._fontAtlasArray[index]; + if (fontAtlas) { + return fontAtlas.texture; + } + return null; + } + /** * @override */ - protected _onDestroy(): void {} + _onDestroy(): void { + + } private constructor(engine: Engine, name: string = "") { super(engine); From 01a81c9912c8e50fc86394227d010743da4abefa Mon Sep 17 00:00:00 2001 From: singlecoder Date: Wed, 6 Jul 2022 16:14:47 +0800 Subject: [PATCH 24/55] feat(text): add resource destroy --- packages/core/src/2d/assembler/CharAssembler.ts | 14 +++++++++----- packages/core/src/2d/text/Font.ts | 7 ++++++- packages/core/src/2d/text/TextRenderer.ts | 5 +++++ 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/packages/core/src/2d/assembler/CharAssembler.ts b/packages/core/src/2d/assembler/CharAssembler.ts index b5538f5f29..ca102f08a3 100644 --- a/packages/core/src/2d/assembler/CharAssembler.ts +++ b/packages/core/src/2d/assembler/CharAssembler.ts @@ -26,8 +26,9 @@ export class CharAssembler { static updateData(renderer: TextRenderer): void { const isTextureDirty = renderer._isContainDirtyFlag(DirtyFlag.Property); if (isTextureDirty) { - renderer._charFont = Font.createFromOS(renderer.engine, TextUtils.getNativeFontHash(renderer.font.name, renderer.fontSize, renderer.fontStyle)); CharAssembler.clearData(renderer); + renderer._charFont = Font.createFromOS(renderer.engine, TextUtils.getNativeFontHash(renderer.font.name, renderer.fontSize, renderer.fontStyle)); + renderer._charFont._addRefCount(1); CharAssembler._updateText(renderer); renderer._setDirtyFlagFalse(DirtyFlag.Property); } @@ -38,16 +39,19 @@ export class CharAssembler { } } - static clear(): void { - - } - static clearData(renderer: TextRenderer): void { const { _charRenderDatas } = renderer; for (let i = 0, l = _charRenderDatas.length; i < l; ++i) { CharAssembler._charRenderDataPool.putData(_charRenderDatas[i]); } _charRenderDatas.length = 0; + + const { _charFont } = renderer; + if (_charFont) { + _charFont._addRefCount(-1); + _charFont.destroy(); + renderer._charFont = null; + } } private static _updatePosition(renderer: TextRenderer): void { diff --git a/packages/core/src/2d/text/Font.ts b/packages/core/src/2d/text/Font.ts index 386764f173..256f8eef1b 100644 --- a/packages/core/src/2d/text/Font.ts +++ b/packages/core/src/2d/text/Font.ts @@ -105,7 +105,12 @@ export class Font extends RefObject { * @override */ _onDestroy(): void { - + const { _fontAtlasArray } = this; + for (let i = 0, l = _fontAtlasArray.length; i < l; ++i) { + _fontAtlasArray[i].destroy(true); + } + _fontAtlasArray.length = 0; + delete Font._fontMap[this._name]; } private constructor(engine: Engine, name: string = "") { diff --git a/packages/core/src/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts index adcc656010..9f1a35e207 100644 --- a/packages/core/src/2d/text/TextRenderer.ts +++ b/packages/core/src/2d/text/TextRenderer.ts @@ -342,6 +342,11 @@ export class TextRenderer extends Renderer implements ICustomClone { * @internal */ _onDestroy(): void { + if (this._useCharCache) { + CharAssembler.clearData(this); + } else { + TextAssembler.clearData(this); + } this.engine._dynamicTextAtlasManager.removeSprite(this._sprite); this._isWorldMatrixDirty.destroy(); super._onDestroy(); From 26ea7c496df35d29c80befdd9d1d83c42865301d Mon Sep 17 00:00:00 2001 From: singlecoder Date: Wed, 6 Jul 2022 16:42:03 +0800 Subject: [PATCH 25/55] feat(text): opt code --- packages/core/src/2d/assembler/CharRenderData.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/src/2d/assembler/CharRenderData.ts b/packages/core/src/2d/assembler/CharRenderData.ts index a68e4fd492..9b1b4ecbbd 100644 --- a/packages/core/src/2d/assembler/CharRenderData.ts +++ b/packages/core/src/2d/assembler/CharRenderData.ts @@ -2,11 +2,11 @@ import { Vector3 } from "@oasis-engine/math"; import { Texture2D } from "../../texture"; import { RenderData2D } from "../data/RenderData2D"; +/** + * @internal + */ export interface CharRenderData { - /** @internal */ texture: Texture2D; - /** @internal */ renderData: RenderData2D; - /** @intarnal */ localPositions: Vector3[]; } From 5a7943ecf5a04a022ae1715dc1d47ee3f13dfe36 Mon Sep 17 00:00:00 2001 From: singlecoder Date: Wed, 6 Jul 2022 16:55:35 +0800 Subject: [PATCH 26/55] feat(text): opt code --- packages/core/src/2d/text/Font.ts | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/packages/core/src/2d/text/Font.ts b/packages/core/src/2d/text/Font.ts index 256f8eef1b..88809236f9 100644 --- a/packages/core/src/2d/text/Font.ts +++ b/packages/core/src/2d/text/Font.ts @@ -38,17 +38,7 @@ export class Font extends RefObject { } /** - * Add char into font atlas. - * @param id - The unique id for char - * @param imageSource - The source of texture - * @param width - The width of char - * @param height - The height of char - * @param offsetX - The char offset in X axis - * @param offsetY - The char offset in Y axis - * @param xAdvance - The next char start position in X axis - * @param ascent - The ascent of char - * @param descent - The descent of char - * @returns - The char's char info and texture + * @internal */ addCharInfo( id: number, @@ -77,9 +67,7 @@ export class Font extends RefObject { } /** - * Get char info. - * @param id - The unique id for char - * @returns - The char's char info and texture + * @internal */ getCharInfo(id: number): CharInfo { const { _fontAtlasArray: fontAtlasArray } = this; @@ -93,6 +81,9 @@ export class Font extends RefObject { return null; } + /** + * @internal + */ getTextureByIndex(index: number): Texture2D { const fontAtlas = this._fontAtlasArray[index]; if (fontAtlas) { From c58e2b70ed4b9c7841df5f58ec9f8a6213eb8823 Mon Sep 17 00:00:00 2001 From: singlecoder Date: Wed, 6 Jul 2022 16:58:38 +0800 Subject: [PATCH 27/55] feat(text): opt code --- packages/core/src/2d/assembler/CharAssembler.ts | 8 ++++---- packages/core/src/2d/text/Font.ts | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/core/src/2d/assembler/CharAssembler.ts b/packages/core/src/2d/assembler/CharAssembler.ts index ca102f08a3..15c4958de6 100644 --- a/packages/core/src/2d/assembler/CharAssembler.ts +++ b/packages/core/src/2d/assembler/CharAssembler.ts @@ -115,12 +115,12 @@ export class CharAssembler { for (let j = 0, m = line.length; j < m; ++j) { const char = line[j]; - const charInfo = charFont.getCharInfo(char.charCodeAt(0)); + const charInfo = charFont._getCharInfo(char.charCodeAt(0)); if (charInfo.h > 0) { const charRenderData = _charRenderDataPool.getData(); const { renderData, localPositions } = charRenderData; - charRenderData.texture = charFont.getTextureByIndex(charInfo.index); + charRenderData.texture = charFont._getTextureByIndex(charInfo.index); renderData.color = color; const { uvs } = renderData; @@ -294,13 +294,13 @@ export class CharAssembler { private static _getCharInfo(char: string, fontString: string, font: Font): CharInfo { const id = char.charCodeAt(0); - let charInfo = font.getCharInfo(id); + let charInfo = font._getCharInfo(id); if (!charInfo) { const charMetrics = TextUtils.measureChar(char, fontString); const { width, sizeInfo } = charMetrics; const { ascent, descent } = sizeInfo; const offsetY = (ascent - descent) * 0.5; - charInfo = font.addCharInfo( + charInfo = font._addCharInfo( id, TextUtils.textContext().canvas, width, diff --git a/packages/core/src/2d/text/Font.ts b/packages/core/src/2d/text/Font.ts index 88809236f9..954071f451 100644 --- a/packages/core/src/2d/text/Font.ts +++ b/packages/core/src/2d/text/Font.ts @@ -40,7 +40,7 @@ export class Font extends RefObject { /** * @internal */ - addCharInfo( + _addCharInfo( id: number, imageSource: TexImageSource | OffscreenCanvas, width: number, @@ -69,7 +69,7 @@ export class Font extends RefObject { /** * @internal */ - getCharInfo(id: number): CharInfo { + _getCharInfo(id: number): CharInfo { const { _fontAtlasArray: fontAtlasArray } = this; for (let i = 0, l = fontAtlasArray.length; i < l; ++i) { const fontAtlas = fontAtlasArray[i]; @@ -84,7 +84,7 @@ export class Font extends RefObject { /** * @internal */ - getTextureByIndex(index: number): Texture2D { + _getTextureByIndex(index: number): Texture2D { const fontAtlas = this._fontAtlasArray[index]; if (fontAtlas) { return fontAtlas.texture; From 18685d42eee4a1260acaa6630ac1b6648ec639b6 Mon Sep 17 00:00:00 2001 From: singlecoder Date: Wed, 6 Jul 2022 17:10:40 +0800 Subject: [PATCH 28/55] feat(text): opt code --- packages/core/src/2d/text/Font.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/core/src/2d/text/Font.ts b/packages/core/src/2d/text/Font.ts index 954071f451..3583fbdfdc 100644 --- a/packages/core/src/2d/text/Font.ts +++ b/packages/core/src/2d/text/Font.ts @@ -37,6 +37,11 @@ export class Font extends RefObject { return this._name; } + private constructor(engine: Engine, name: string = "") { + super(engine); + this._name = name; + } + /** * @internal */ @@ -104,11 +109,6 @@ export class Font extends RefObject { delete Font._fontMap[this._name]; } - private constructor(engine: Engine, name: string = "") { - super(engine); - this._name = name; - } - private _createFontAtlas(): FontAtlas { const { engine } = this; const fontAtlas = new FontAtlas(engine); From 22749c57a6f7d7937f2a2f3e2a176eae28cb2c7e Mon Sep 17 00:00:00 2001 From: singlecoder Date: Wed, 6 Jul 2022 18:42:50 +0800 Subject: [PATCH 29/55] feat(text): opt code --- .../core/src/2d/assembler/CharAssembler.ts | 23 ++------ packages/core/src/2d/atlas/FontAtlas.ts | 38 ++++++------- packages/core/src/2d/text/Font.ts | 54 +++++++++---------- packages/core/src/2d/text/TextUtils.ts | 21 ++++++-- 4 files changed, 64 insertions(+), 72 deletions(-) diff --git a/packages/core/src/2d/assembler/CharAssembler.ts b/packages/core/src/2d/assembler/CharAssembler.ts index 15c4958de6..1190e1a9ab 100644 --- a/packages/core/src/2d/assembler/CharAssembler.ts +++ b/packages/core/src/2d/assembler/CharAssembler.ts @@ -115,7 +115,7 @@ export class CharAssembler { for (let j = 0, m = line.length; j < m; ++j) { const char = line[j]; - const charInfo = charFont._getCharInfo(char.charCodeAt(0)); + const charInfo = charFont._getCharInfo(char); if (charInfo.h > 0) { const charRenderData = _charRenderDataPool.getData(); @@ -293,24 +293,11 @@ export class CharAssembler { } private static _getCharInfo(char: string, fontString: string, font: Font): CharInfo { - const id = char.charCodeAt(0); - let charInfo = font._getCharInfo(id); + let charInfo = font._getCharInfo(char); if (!charInfo) { - const charMetrics = TextUtils.measureChar(char, fontString); - const { width, sizeInfo } = charMetrics; - const { ascent, descent } = sizeInfo; - const offsetY = (ascent - descent) * 0.5; - charInfo = font._addCharInfo( - id, - TextUtils.textContext().canvas, - width, - sizeInfo.size, - 0, - offsetY, - width, - ascent, - descent - ); + charInfo = TextUtils.measureChar(char, fontString); + font._uploadCharTexture(charInfo, TextUtils.textContext().canvas); + font._addCharInfo(char, charInfo); } return charInfo; diff --git a/packages/core/src/2d/atlas/FontAtlas.ts b/packages/core/src/2d/atlas/FontAtlas.ts index 49f7a72948..faea9520bc 100644 --- a/packages/core/src/2d/atlas/FontAtlas.ts +++ b/packages/core/src/2d/atlas/FontAtlas.ts @@ -40,18 +40,8 @@ export class FontAtlas extends RefObject { this._charInfoMap = {}; } - addCharInfo( - id: number, - imageSource: TexImageSource | OffscreenCanvas, - width: number, - height: number, - offsetX: number, - offsetY: number, - xAdvance: number, - ascent: number, - descent: number, - index: number = 0, - ): CharInfo { + uploadCharTexture(charInfo: CharInfo, imageSource: TexImageSource | OffscreenCanvas): boolean { + const { w: width, h: height } = charInfo; const { _space: space, texture } = this; const textureSize = texture.width; const offsetWidth = width + space; @@ -70,7 +60,7 @@ export class FontAtlas extends RefObject { this._nextY = endY; } if (endY >= textureSize) { - return null; + return false; } if (width > 0 && height > 0) { @@ -83,17 +73,21 @@ export class FontAtlas extends RefObject { const y = this._curY; const w = width; const h = height; - const u0 = x * textureSizeReciprocal; - const u1 = (x + w) * textureSizeReciprocal; - const v0 = y * textureSizeReciprocal; - const v1 = (y + h) * textureSizeReciprocal; - const charInfo = { x, y, w, h, offsetX, offsetY, xAdvance, u0, v0, u1, v1, ascent, descent, index }; - this._charInfoMap[id] = charInfo; + charInfo.x = x; + charInfo.y = y; + charInfo.u0 = x * textureSizeReciprocal; + charInfo.u1 = (x + w) * textureSizeReciprocal; + charInfo.v0 = y * textureSizeReciprocal; + charInfo.v1 = (y + h) * textureSizeReciprocal; this._curX += offsetWidth + space; - return charInfo; + return true; } - getCharInfo(id: number): CharInfo { - return this._charInfoMap[id]; + addCharInfo(char: string, charInfo: CharInfo) { + this._charInfoMap[char.charCodeAt(0)] = charInfo; + } + + getCharInfo(char: string): CharInfo { + return this._charInfoMap[char.charCodeAt(0)]; } } diff --git a/packages/core/src/2d/text/Font.ts b/packages/core/src/2d/text/Font.ts index 3583fbdfdc..cea92ab225 100644 --- a/packages/core/src/2d/text/Font.ts +++ b/packages/core/src/2d/text/Font.ts @@ -28,7 +28,8 @@ export class Font extends RefObject { } private _name: string = ""; - private _fontAtlasArray: Array = []; + private _fontAtlases: Array = []; + private _lastIndex: number = -1; /** * The name of the font object. @@ -45,40 +46,37 @@ export class Font extends RefObject { /** * @internal */ - _addCharInfo( - id: number, - imageSource: TexImageSource | OffscreenCanvas, - width: number, - height: number, - offsetX: number, - offsetY: number, - xAdvance: number, - ascent: number, - descent: number, - ): CharInfo { - const { _fontAtlasArray: fontAtlasArray } = this; - if (fontAtlasArray.length === 0) { + _uploadCharTexture(charInfo: CharInfo, imageSource: TexImageSource | OffscreenCanvas): void { + const { _fontAtlases: fontAtlasArray } = this; + if (this._lastIndex === -1) { this._createFontAtlas(); } - - const lastIndex = fontAtlasArray.length - 1; - let lastFontAtlas = fontAtlasArray[lastIndex]; - let charInfo = lastFontAtlas.addCharInfo(id, imageSource, width, height, offsetX, offsetY, xAdvance, ascent, descent, lastIndex); - if (!charInfo) { - lastFontAtlas = this._createFontAtlas(); - charInfo = lastFontAtlas.addCharInfo(id, imageSource, width, height, offsetX, offsetY, xAdvance, ascent, descent, lastIndex + 1); + this._lastIndex = fontAtlasArray.length - 1; + let fontAtlas = fontAtlasArray[this._lastIndex]; + if (!fontAtlas.uploadCharTexture(charInfo, imageSource)) { + fontAtlas = this._createFontAtlas(); + fontAtlas.uploadCharTexture(charInfo, imageSource); + this._lastIndex++; } - return charInfo; } /** * @internal */ - _getCharInfo(id: number): CharInfo { - const { _fontAtlasArray: fontAtlasArray } = this; + _addCharInfo(char: string, charInfo: CharInfo) { + const { _lastIndex } = this; + charInfo.index = _lastIndex; + this._fontAtlases[_lastIndex].addCharInfo(char, charInfo); + } + + /** + * @internal + */ + _getCharInfo(char: string): CharInfo { + const { _fontAtlases: fontAtlasArray } = this; for (let i = 0, l = fontAtlasArray.length; i < l; ++i) { const fontAtlas = fontAtlasArray[i]; - const charInfo = fontAtlas.getCharInfo(id); + const charInfo = fontAtlas.getCharInfo(char); if (charInfo) { return charInfo; } @@ -90,7 +88,7 @@ export class Font extends RefObject { * @internal */ _getTextureByIndex(index: number): Texture2D { - const fontAtlas = this._fontAtlasArray[index]; + const fontAtlas = this._fontAtlases[index]; if (fontAtlas) { return fontAtlas.texture; } @@ -101,7 +99,7 @@ export class Font extends RefObject { * @override */ _onDestroy(): void { - const { _fontAtlasArray } = this; + const { _fontAtlases: _fontAtlasArray } = this; for (let i = 0, l = _fontAtlasArray.length; i < l; ++i) { _fontAtlasArray[i].destroy(true); } @@ -114,7 +112,7 @@ export class Font extends RefObject { const fontAtlas = new FontAtlas(engine); const texture = new Texture2D(engine, 512, 512); fontAtlas.texture = texture; - this._fontAtlasArray.push(fontAtlas); + this._fontAtlases.push(fontAtlas); return fontAtlas; } } diff --git a/packages/core/src/2d/text/TextUtils.ts b/packages/core/src/2d/text/TextUtils.ts index 7f40b61b7b..b021b51d46 100644 --- a/packages/core/src/2d/text/TextUtils.ts +++ b/packages/core/src/2d/text/TextUtils.ts @@ -1,3 +1,4 @@ +import { CharInfo } from "../assembler/CharInfo"; import { FontStyle } from "../enums/FontStyle"; /** @@ -103,7 +104,7 @@ export class TextUtils { return info; } - static measureChar(char: string, fontString: string): CharMetrics { + static measureChar(char: string, fontString: string): CharInfo { return TextUtils._measureFontOrChar(fontString, char); } @@ -205,7 +206,7 @@ export class TextUtils { return canvas; } - private static _measureFontOrChar(fontString: string, char: string = ""): FontSizeInfo | CharMetrics { + private static _measureFontOrChar(fontString: string, char: string = ""): FontSizeInfo | CharInfo { const { canvas, context } = TextUtils.textContext(); context.font = fontString; const measureString = char || TextUtils._measureString; @@ -263,8 +264,20 @@ export class TextUtils { TextUtils.updateCanvas(width, size, data); } return { - width, - sizeInfo + x: 0, + y: 0, + w: width, + h: size, + offsetX: 0, + offsetY: (ascent - descent) * 0.5, + xAdvance: width, + u0: 0, + v0: 0, + u1: 0, + v1: 0, + ascent, + descent, + index: 0 } } else { return sizeInfo; From ae8a29642f47ee78379b1386d0cd07d0ab97ab68 Mon Sep 17 00:00:00 2001 From: singlecoder Date: Wed, 6 Jul 2022 18:45:17 +0800 Subject: [PATCH 30/55] feat(text): opt code --- packages/core/src/2d/text/TextUtils.ts | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/packages/core/src/2d/text/TextUtils.ts b/packages/core/src/2d/text/TextUtils.ts index b021b51d46..d63db3c216 100644 --- a/packages/core/src/2d/text/TextUtils.ts +++ b/packages/core/src/2d/text/TextUtils.ts @@ -33,15 +33,6 @@ export interface TextMetrics { lineMaxSizes?: Array; } -/** - * @internal - * CharMetrics. - */ -export interface CharMetrics { - width: number; - sizeInfo: FontSizeInfo; -} - /** * @internal * TextUtils includes some helper function for text. @@ -105,7 +96,7 @@ export class TextUtils { } static measureChar(char: string, fontString: string): CharInfo { - return TextUtils._measureFontOrChar(fontString, char); + return TextUtils._measureFontOrChar(fontString, char); } /** From 15afe31120ccd565f5408b52be3d4dfbb09a99c2 Mon Sep 17 00:00:00 2001 From: singlecoder Date: Wed, 6 Jul 2022 18:51:24 +0800 Subject: [PATCH 31/55] feat(text): opt code --- packages/core/src/2d/text/Font.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/core/src/2d/text/Font.ts b/packages/core/src/2d/text/Font.ts index cea92ab225..455bc5eb20 100644 --- a/packages/core/src/2d/text/Font.ts +++ b/packages/core/src/2d/text/Font.ts @@ -48,16 +48,18 @@ export class Font extends RefObject { */ _uploadCharTexture(charInfo: CharInfo, imageSource: TexImageSource | OffscreenCanvas): void { const { _fontAtlases: fontAtlasArray } = this; - if (this._lastIndex === -1) { + let lastIndex = this._lastIndex; + if (lastIndex === -1) { this._createFontAtlas(); + lastIndex++ } - this._lastIndex = fontAtlasArray.length - 1; - let fontAtlas = fontAtlasArray[this._lastIndex]; + let fontAtlas = fontAtlasArray[lastIndex]; if (!fontAtlas.uploadCharTexture(charInfo, imageSource)) { fontAtlas = this._createFontAtlas(); fontAtlas.uploadCharTexture(charInfo, imageSource); - this._lastIndex++; + lastIndex++; } + this._lastIndex = lastIndex; } /** From 85bab5d6c7c1d7895a37cd4d0f7c15280eac1889 Mon Sep 17 00:00:00 2001 From: singlecoder Date: Wed, 6 Jul 2022 21:26:27 +0800 Subject: [PATCH 32/55] feat(text): opt code --- .../core/src/2d/assembler/CharAssembler.ts | 10 +++++++- packages/core/src/2d/text/Font.ts | 25 ++++++++++++------- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/packages/core/src/2d/assembler/CharAssembler.ts b/packages/core/src/2d/assembler/CharAssembler.ts index 1190e1a9ab..c4b0b4ede4 100644 --- a/packages/core/src/2d/assembler/CharAssembler.ts +++ b/packages/core/src/2d/assembler/CharAssembler.ts @@ -27,7 +27,10 @@ export class CharAssembler { const isTextureDirty = renderer._isContainDirtyFlag(DirtyFlag.Property); if (isTextureDirty) { CharAssembler.clearData(renderer); - renderer._charFont = Font.createFromOS(renderer.engine, TextUtils.getNativeFontHash(renderer.font.name, renderer.fontSize, renderer.fontStyle)); + renderer._charFont = Font.createFromOS( + renderer.engine, + TextUtils.getNativeFontHash(renderer.font.name, renderer.fontSize, renderer.fontStyle) + ); renderer._charFont._addRefCount(1); CharAssembler._updateText(renderer); renderer._setDirtyFlagFalse(DirtyFlag.Property); @@ -150,6 +153,11 @@ export class CharAssembler { startY -= lineHeight; } + + charFont._getLastIndex() > 0 && + _charRenderDatas.sort((a, b) => { + return a.texture.instanceId - b.texture.instanceId; + }); } private static _measureTextWithWrap(renderer: TextRenderer): TextMetrics { diff --git a/packages/core/src/2d/text/Font.ts b/packages/core/src/2d/text/Font.ts index 455bc5eb20..848591b139 100644 --- a/packages/core/src/2d/text/Font.ts +++ b/packages/core/src/2d/text/Font.ts @@ -47,13 +47,13 @@ export class Font extends RefObject { * @internal */ _uploadCharTexture(charInfo: CharInfo, imageSource: TexImageSource | OffscreenCanvas): void { - const { _fontAtlases: fontAtlasArray } = this; + const { _fontAtlases: fontAtlases } = this; let lastIndex = this._lastIndex; if (lastIndex === -1) { this._createFontAtlas(); lastIndex++ } - let fontAtlas = fontAtlasArray[lastIndex]; + let fontAtlas = fontAtlases[lastIndex]; if (!fontAtlas.uploadCharTexture(charInfo, imageSource)) { fontAtlas = this._createFontAtlas(); fontAtlas.uploadCharTexture(charInfo, imageSource); @@ -75,9 +75,9 @@ export class Font extends RefObject { * @internal */ _getCharInfo(char: string): CharInfo { - const { _fontAtlases: fontAtlasArray } = this; - for (let i = 0, l = fontAtlasArray.length; i < l; ++i) { - const fontAtlas = fontAtlasArray[i]; + const { _fontAtlases: fontAtlases } = this; + for (let i = 0, l = fontAtlases.length; i < l; ++i) { + const fontAtlas = fontAtlases[i]; const charInfo = fontAtlas.getCharInfo(char); if (charInfo) { return charInfo; @@ -97,15 +97,22 @@ export class Font extends RefObject { return null; } + /** + * @internal + */ + _getLastIndex(): number { + return this._lastIndex; + } + /** * @override */ _onDestroy(): void { - const { _fontAtlases: _fontAtlasArray } = this; - for (let i = 0, l = _fontAtlasArray.length; i < l; ++i) { - _fontAtlasArray[i].destroy(true); + const { _fontAtlases } = this; + for (let i = 0, l = _fontAtlases.length; i < l; ++i) { + _fontAtlases[i].destroy(true); } - _fontAtlasArray.length = 0; + _fontAtlases.length = 0; delete Font._fontMap[this._name]; } From 90ca73d4af6ce7e29a8973683efa5ae21e6359d4 Mon Sep 17 00:00:00 2001 From: singlecoder Date: Thu, 7 Jul 2022 10:49:12 +0800 Subject: [PATCH 33/55] feat(text): fix text assembler position error --- packages/core/src/2d/assembler/TextAssembler.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/src/2d/assembler/TextAssembler.ts b/packages/core/src/2d/assembler/TextAssembler.ts index 7197d07884..9c71043b30 100644 --- a/packages/core/src/2d/assembler/TextAssembler.ts +++ b/packages/core/src/2d/assembler/TextAssembler.ts @@ -74,9 +74,9 @@ export class TextAssembler { const { positions: _positions } = renderer._renderData; for (let i = 0, n = _positions.length; i < n; i++) { const curVertexPos = localPositions[i]; - curVertexPos.x = (curVertexPos.x - pivot.x) * width; - curVertexPos.y = (curVertexPos.y - pivot.y) * height; - localVertexPos.set(curVertexPos.x, curVertexPos.y, 0); + localVertexPos.x = (curVertexPos.x - pivot.x) * width; + localVertexPos.y = (curVertexPos.y - pivot.y) * height; + localVertexPos.z = 0; Vector3.transformToVec3(localVertexPos, worldMatrix, _positions[i]); } } From ed69c4e3e36923cb09955b227d54a1ed9ef08659 Mon Sep 17 00:00:00 2001 From: singlecoder Date: Thu, 7 Jul 2022 14:05:28 +0800 Subject: [PATCH 34/55] feat(text): opt code --- packages/core/src/2d/text/TextUtils.ts | 144 +++++++++++++------------ 1 file changed, 74 insertions(+), 70 deletions(-) diff --git a/packages/core/src/2d/text/TextUtils.ts b/packages/core/src/2d/text/TextUtils.ts index d63db3c216..c01a905837 100644 --- a/packages/core/src/2d/text/TextUtils.ts +++ b/packages/core/src/2d/text/TextUtils.ts @@ -1,38 +1,6 @@ import { CharInfo } from "../assembler/CharInfo"; import { FontStyle } from "../enums/FontStyle"; -/** - * @internal - * TextContext. - */ -export interface TextContext { - canvas: HTMLCanvasElement | OffscreenCanvas; - context: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D; -} - -/** - * @internal - * FontSizeInfo. - */ -export interface FontSizeInfo { - ascent: number; - descent: number; - size: number; -} - -/** - * @internal - * TextMetrics. - */ -export interface TextMetrics { - width: number; - height: number; - lines: Array; - lineWidths: Array; - lineHeight: number; - lineMaxSizes?: Array; -} - /** * @internal * TextUtils includes some helper function for text. @@ -95,15 +63,53 @@ export class TextUtils { return info; } + /** + * Get native font string. + * @param fontName - The font name + * @param fontSize - The font size + * @param style - The font style + * @returns The native font string + */ + static getNativeFontString(fontName: string, fontSize: number, style: FontStyle): string { + let str = style & FontStyle.Bold ? "bold " : ""; + style & FontStyle.Italic && (str += "italic "); + // Check if font already contains strings + if (!/([\"\'])[^\'\"]+\1/.test(fontName) && TextUtils._genericFontFamilies.indexOf(fontName) == -1) { + fontName = `"${fontName}"`; + } + str += `${fontSize}px ${fontName}`; + return str; + } + static measureChar(char: string, fontString: string): CharInfo { return TextUtils._measureFontOrChar(fontString, char); } + /** + * Get native font hash. + * @param fontName - The font name + * @param fontSize - The font size + * @param style - The font style + * @returns The native font hash + */ + static getNativeFontHash(fontName: string, fontSize: number, style: FontStyle): string { + let str = style & FontStyle.Bold ? "bold" : ""; + style & FontStyle.Italic && (str += "italic"); + // Check if font already contains strings + if (!/([\"\'])[^\'\"]+\1/.test(fontName) && TextUtils._genericFontFamilies.indexOf(fontName) == -1) { + fontName = `${fontName}`; + } + str += `${fontSize}px${fontName}`; + return str; + } + + //--------------- only for text mode --------------- + /** * Trim canvas. * @returns the width and height after trim, and the image data */ - static trimCanvas(): { width: number; height: number; data?: ImageData } { + static trimCanvas(): { width: number; height: number; data?: ImageData } { // https://gist.github.com/remy/784508 const { canvas, context } = TextUtils.textContext(); @@ -146,42 +152,6 @@ export class TextUtils { }; } - /** - * Get native font string. - * @param fontName - The font name - * @param fontSize - The font size - * @param style - The font style - * @returns The native font string - */ - static getNativeFontString(fontName: string, fontSize: number, style: FontStyle): string { - let str = style & FontStyle.Bold ? "bold " : ""; - style & FontStyle.Italic && (str += "italic "); - // Check if font already contains strings - if (!/([\"\'])[^\'\"]+\1/.test(fontName) && TextUtils._genericFontFamilies.indexOf(fontName) == -1) { - fontName = `"${fontName}"`; - } - str += `${fontSize}px ${fontName}`; - return str; - } - - /** - * Get native font hash. - * @param fontName - The font name - * @param fontSize - The font size - * @param style - The font style - * @returns The native font hash - */ - static getNativeFontHash(fontName: string, fontSize: number, style: FontStyle): string { - let str = style & FontStyle.Bold ? "bold" : ""; - style & FontStyle.Italic && (str += "italic"); - // Check if font already contains strings - if (!/([\"\'])[^\'\"]+\1/.test(fontName) && TextUtils._genericFontFamilies.indexOf(fontName) == -1) { - fontName = `${fontName}`; - } - str += `${fontSize}px${fontName}`; - return str; - } - /** * Update canvas with the data. * @param width - the new width of canvas @@ -189,7 +159,7 @@ export class TextUtils { * @param data - the new data of canvas * @returns the canvas after update */ - static updateCanvas(width: number, height: number, data: ImageData): HTMLCanvasElement | OffscreenCanvas { + static updateCanvas(width: number, height: number, data: ImageData): HTMLCanvasElement | OffscreenCanvas { const { canvas, context } = TextUtils.textContext(); canvas.width = width; canvas.height = height; @@ -197,6 +167,8 @@ export class TextUtils { return canvas; } + //-------------------------------------------------- + private static _measureFontOrChar(fontString: string, char: string = ""): FontSizeInfo | CharInfo { const { canvas, context } = TextUtils.textContext(); context.font = fontString; @@ -275,3 +247,35 @@ export class TextUtils { } } } + +/** + * @internal + * TextContext. + */ + export interface TextContext { + canvas: HTMLCanvasElement | OffscreenCanvas; + context: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D; +} + +/** + * @internal + * FontSizeInfo. + */ +export interface FontSizeInfo { + ascent: number; + descent: number; + size: number; +} + +/** + * @internal + * TextMetrics. + */ +export interface TextMetrics { + width: number; + height: number; + lines: Array; + lineWidths: Array; + lineHeight: number; + lineMaxSizes?: Array; +} From fa8cea14399f64278a6d29cc934e34f430e85173 Mon Sep 17 00:00:00 2001 From: singlecoder Date: Thu, 7 Jul 2022 15:30:20 +0800 Subject: [PATCH 35/55] feat(text): opt code --- .../core/src/2d/assembler/CharAssembler.ts | 50 ++++---------- .../core/src/2d/assembler/TextAssembler.ts | 9 +-- packages/core/src/2d/text/TextRenderer.ts | 69 ++++++++++++++----- 3 files changed, 69 insertions(+), 59 deletions(-) diff --git a/packages/core/src/2d/assembler/CharAssembler.ts b/packages/core/src/2d/assembler/CharAssembler.ts index c4b0b4ede4..93646cc571 100644 --- a/packages/core/src/2d/assembler/CharAssembler.ts +++ b/packages/core/src/2d/assembler/CharAssembler.ts @@ -23,52 +23,15 @@ export class CharAssembler { } } - static updateData(renderer: TextRenderer): void { - const isTextureDirty = renderer._isContainDirtyFlag(DirtyFlag.Property); - if (isTextureDirty) { - CharAssembler.clearData(renderer); - renderer._charFont = Font.createFromOS( - renderer.engine, - TextUtils.getNativeFontHash(renderer.font.name, renderer.fontSize, renderer.fontStyle) - ); - renderer._charFont._addRefCount(1); - CharAssembler._updateText(renderer); - renderer._setDirtyFlagFalse(DirtyFlag.Property); - } - - if (renderer._isWorldMatrixDirty.flag || isTextureDirty) { - CharAssembler._updatePosition(renderer); - renderer._isWorldMatrixDirty.flag = false; - } - } - static clearData(renderer: TextRenderer): void { const { _charRenderDatas } = renderer; for (let i = 0, l = _charRenderDatas.length; i < l; ++i) { CharAssembler._charRenderDataPool.putData(_charRenderDatas[i]); } _charRenderDatas.length = 0; - - const { _charFont } = renderer; - if (_charFont) { - _charFont._addRefCount(-1); - _charFont.destroy(); - renderer._charFont = null; - } } - private static _updatePosition(renderer: TextRenderer): void { - const worldMatrix = renderer.entity.transform.worldMatrix; - const { _charRenderDatas } = renderer; - for (let i = 0, l = _charRenderDatas.length; i < l; ++i) { - const { localPositions, renderData } = _charRenderDatas[i]; - for (let j = 0; j < 4; ++j) { - Vector3.transformToVec3(localPositions[j], worldMatrix, renderData.positions[j]); - } - } - } - - private static _updateText(renderer: TextRenderer): void { + static updateData(renderer: TextRenderer): void { const { color, horizontalAlignment, verticalAlignment, _charRenderDatas } = renderer; const { _pixelsPerUnit } = Engine; const pixelsPerUnitReciprocal = 1.0 / _pixelsPerUnit; @@ -160,6 +123,17 @@ export class CharAssembler { }); } + static updatePosition(renderer: TextRenderer): void { + const worldMatrix = renderer.entity.transform.worldMatrix; + const { _charRenderDatas } = renderer; + for (let i = 0, l = _charRenderDatas.length; i < l; ++i) { + const { localPositions, renderData } = _charRenderDatas[i]; + for (let j = 0; j < 4; ++j) { + Vector3.transformToVec3(localPositions[j], worldMatrix, renderData.positions[j]); + } + } + } + private static _measureTextWithWrap(renderer: TextRenderer): TextMetrics { const { fontSize, fontStyle } = renderer; const { name } = renderer.font; diff --git a/packages/core/src/2d/assembler/TextAssembler.ts b/packages/core/src/2d/assembler/TextAssembler.ts index 9c71043b30..2638896519 100644 --- a/packages/core/src/2d/assembler/TextAssembler.ts +++ b/packages/core/src/2d/assembler/TextAssembler.ts @@ -40,13 +40,14 @@ export class TextAssembler { } static updateData(renderer: TextRenderer): void { - const isTextureDirty = renderer._isContainDirtyFlag(DirtyFlag.Property); - if (isTextureDirty) { + const isRenderDirty = renderer._isContainDirtyFlag(DirtyFlag.RenderDirty); + if (isRenderDirty || renderer._isContainDirtyFlag(DirtyFlag.FontDirty)) { TextAssembler._updateText(renderer); - renderer._setDirtyFlagFalse(DirtyFlag.Property); + renderer._setDirtyFlagFalse(DirtyFlag.RenderDirty); + renderer._setDirtyFlagFalse(DirtyFlag.FontDirty); } - if (renderer._isWorldMatrixDirty.flag || isTextureDirty) { + if (renderer._isWorldMatrixDirty.flag || isRenderDirty) { TextAssembler._updatePosition(renderer); renderer._isWorldMatrixDirty.flag = false; } diff --git a/packages/core/src/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts index 9f1a35e207..26e5f039be 100644 --- a/packages/core/src/2d/text/TextRenderer.ts +++ b/packages/core/src/2d/text/TextRenderer.ts @@ -19,6 +19,7 @@ import { SpriteMaskLayer } from "../enums/SpriteMaskLayer"; import { SpriteRenderer } from "../sprite/SpriteRenderer"; import { CompareFunction } from "../../shader/enums/CompareFunction"; import { ICustomClone } from "../../clone/ComponentCloner"; +import { TextUtils } from "./TextUtils"; /** * Renders a text for 2D graphics. @@ -40,7 +41,7 @@ export class TextRenderer extends Renderer implements ICustomClone { _charRenderDatas: Array = []; @ignoreClone - _dirtyFlag: number = DirtyFlag.Property; + _dirtyFlag: number = DirtyFlag.RenderDirty | DirtyFlag.FontDirty; /** @internal */ @ignoreClone _isWorldMatrixDirty: BoolUpdateFlag; @@ -100,7 +101,7 @@ export class TextRenderer extends Renderer implements ICustomClone { value = value || ""; if (this._text !== value) { this._text = value; - this._setDirtyFlagTrue(DirtyFlag.Property); + this._setDirtyFlagTrue(DirtyFlag.RenderDirty); } } @@ -114,7 +115,7 @@ export class TextRenderer extends Renderer implements ICustomClone { set width(value: number) { if (this._width !== value) { this._width = value; - this._setDirtyFlagTrue(DirtyFlag.Property); + this._setDirtyFlagTrue(DirtyFlag.RenderDirty); } } @@ -128,7 +129,7 @@ export class TextRenderer extends Renderer implements ICustomClone { set height(value: number) { if (this._height !== value) { this._height = value; - this._setDirtyFlagTrue(DirtyFlag.Property); + this._setDirtyFlagTrue(DirtyFlag.RenderDirty); } } @@ -142,7 +143,7 @@ export class TextRenderer extends Renderer implements ICustomClone { set font(value: Font) { if (this._font !== value) { this._font = value; - this._setDirtyFlagTrue(DirtyFlag.Property); + this._setDirtyFlagTrue(DirtyFlag.FontDirty); } } @@ -156,7 +157,7 @@ export class TextRenderer extends Renderer implements ICustomClone { set fontSize(value: number) { if (this._fontSize !== value) { this._fontSize = value; - this._setDirtyFlagTrue(DirtyFlag.Property); + this._setDirtyFlagTrue(DirtyFlag.FontDirty); } } @@ -170,7 +171,7 @@ export class TextRenderer extends Renderer implements ICustomClone { set fontStyle(value: FontStyle) { if (this.fontStyle !== value) { this._fontStyle = value; - this._setDirtyFlagTrue(DirtyFlag.Property); + this._setDirtyFlagTrue(DirtyFlag.FontDirty); } } @@ -184,7 +185,7 @@ export class TextRenderer extends Renderer implements ICustomClone { set lineSpacing(value: number) { if (this._lineSpacing !== value) { this._lineSpacing = value; - this._setDirtyFlagTrue(DirtyFlag.Property); + this._setDirtyFlagTrue(DirtyFlag.RenderDirty); } } @@ -198,7 +199,7 @@ export class TextRenderer extends Renderer implements ICustomClone { set useCharCache(value: boolean) { if (this._useCharCache !== value) { this._useCharCache = value; - this._setDirtyFlagTrue(DirtyFlag.Property); + this._setDirtyFlagTrue(DirtyFlag.FontDirty); if (value) { if (this._sprite) { @@ -226,7 +227,7 @@ export class TextRenderer extends Renderer implements ICustomClone { set horizontalAlignment(value: TextHorizontalAlignment) { if (this._horizontalAlignment !== value) { this._horizontalAlignment = value; - this._setDirtyFlagTrue(DirtyFlag.Property); + this._setDirtyFlagTrue(DirtyFlag.RenderDirty); } } @@ -240,7 +241,7 @@ export class TextRenderer extends Renderer implements ICustomClone { set verticalAlignment(value: TextVerticalAlignment) { if (this._verticalAlignment !== value) { this._verticalAlignment = value; - this._setDirtyFlagTrue(DirtyFlag.Property); + this._setDirtyFlagTrue(DirtyFlag.RenderDirty); } } @@ -254,7 +255,7 @@ export class TextRenderer extends Renderer implements ICustomClone { set enableWrapping(value: boolean) { if (this._enableWrapping !== value) { this._enableWrapping = value; - this._setDirtyFlagTrue(DirtyFlag.Property); + this._setDirtyFlagTrue(DirtyFlag.RenderDirty); } } @@ -268,7 +269,7 @@ export class TextRenderer extends Renderer implements ICustomClone { set overflowMode(value: OverflowMode) { if (this._overflowMode !== value) { this._overflowMode = value; - this._setDirtyFlagTrue(DirtyFlag.Property); + this._setDirtyFlagTrue(DirtyFlag.RenderDirty); } } @@ -325,7 +326,23 @@ export class TextRenderer extends Renderer implements ICustomClone { } if (this._useCharCache) { - CharAssembler.updateData(this); + const isFontDirty = this._isContainDirtyFlag(DirtyFlag.FontDirty); + if (isFontDirty) { + this._resetCharFont(); + this._setDirtyFlagFalse(DirtyFlag.FontDirty); + } + + const isRenderDirty = this._isContainDirtyFlag(DirtyFlag.RenderDirty); + if (isRenderDirty || isFontDirty) { + CharAssembler.clearData(this); + CharAssembler.updateData(this); + this._setDirtyFlagFalse(DirtyFlag.RenderDirty); + } + + if (this._isWorldMatrixDirty.flag || isRenderDirty) { + CharAssembler.updatePosition(this); + this._isWorldMatrixDirty.flag = false; + } const { _charRenderDatas } = this; for (let i = 0, l = _charRenderDatas.length; i < l; ++i) { @@ -381,6 +398,8 @@ export class TextRenderer extends Renderer implements ICustomClone { this._dirtyFlag &= ~type; } + //--------------- only for text mode --------------- + /** * @internal */ @@ -394,6 +413,8 @@ export class TextRenderer extends Renderer implements ICustomClone { } } + //-------------------------------------------------- + /** * @override */ @@ -467,10 +488,24 @@ export class TextRenderer extends Renderer implements ICustomClone { spriteElement.setValue(this, renderData, this.getMaterial(), texture); camera._renderPipeline.pushPrimitive(spriteElement); } + + private _resetCharFont(): void { + const lastCharFont = this._charFont; + if (lastCharFont) { + lastCharFont._addRefCount(-1); + lastCharFont.destroy(); + } + this._charFont = Font.createFromOS( + this.engine, + TextUtils.getNativeFontHash(this.font.name, this.fontSize, this.fontStyle) + ); + this._charFont._addRefCount(1); + } } export enum DirtyFlag { - Property = 0x1, - MaskInteraction = 0x2, - All = 0x3 + RenderDirty = 0x1, + FontDirty = 0x2, + MaskInteraction = 0x4, + All = 0x7 } From 1e0f0df0c5bcf313dc3ce63c1470aeba3a9316e4 Mon Sep 17 00:00:00 2001 From: singlecoder Date: Thu, 7 Jul 2022 15:34:08 +0800 Subject: [PATCH 36/55] feat(text): opt code --- packages/core/src/2d/assembler/CharAssembler.ts | 11 ----------- packages/core/src/2d/text/TextRenderer.ts | 15 +++++++++++++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/core/src/2d/assembler/CharAssembler.ts b/packages/core/src/2d/assembler/CharAssembler.ts index 93646cc571..409e400e8c 100644 --- a/packages/core/src/2d/assembler/CharAssembler.ts +++ b/packages/core/src/2d/assembler/CharAssembler.ts @@ -123,17 +123,6 @@ export class CharAssembler { }); } - static updatePosition(renderer: TextRenderer): void { - const worldMatrix = renderer.entity.transform.worldMatrix; - const { _charRenderDatas } = renderer; - for (let i = 0, l = _charRenderDatas.length; i < l; ++i) { - const { localPositions, renderData } = _charRenderDatas[i]; - for (let j = 0; j < 4; ++j) { - Vector3.transformToVec3(localPositions[j], worldMatrix, renderData.positions[j]); - } - } - } - private static _measureTextWithWrap(renderer: TextRenderer): TextMetrics { const { fontSize, fontStyle } = renderer; const { name } = renderer.font; diff --git a/packages/core/src/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts index 26e5f039be..6af19a468c 100644 --- a/packages/core/src/2d/text/TextRenderer.ts +++ b/packages/core/src/2d/text/TextRenderer.ts @@ -1,4 +1,4 @@ -import { BoundingBox, Color } from "@oasis-engine/math"; +import { BoundingBox, Color, Vector3 } from "@oasis-engine/math"; import { BoolUpdateFlag } from "../../BoolUpdateFlag"; import { Camera } from "../../Camera"; import { assignmentClone, deepClone, ignoreClone } from "../../clone/CloneManager"; @@ -340,7 +340,7 @@ export class TextRenderer extends Renderer implements ICustomClone { } if (this._isWorldMatrixDirty.flag || isRenderDirty) { - CharAssembler.updatePosition(this); + this._updatePosition(); this._isWorldMatrixDirty.flag = false; } @@ -501,6 +501,17 @@ export class TextRenderer extends Renderer implements ICustomClone { ); this._charFont._addRefCount(1); } + + private _updatePosition() { + const worldMatrix = this.entity.transform.worldMatrix; + const { _charRenderDatas } = this; + for (let i = 0, l = _charRenderDatas.length; i < l; ++i) { + const { localPositions, renderData } = _charRenderDatas[i]; + for (let j = 0; j < 4; ++j) { + Vector3.transformToVec3(localPositions[j], worldMatrix, renderData.positions[j]); + } + } + } } export enum DirtyFlag { From 5d721b59fd16645c47415849bc2524781d27713c Mon Sep 17 00:00:00 2001 From: singlecoder Date: Thu, 7 Jul 2022 16:09:45 +0800 Subject: [PATCH 37/55] feat(text): delete text assembler --- .../core/src/2d/assembler/TextAssembler.ts | 327 ------------------ packages/core/src/2d/text/TextRenderer.ts | 154 +++------ 2 files changed, 41 insertions(+), 440 deletions(-) delete mode 100644 packages/core/src/2d/assembler/TextAssembler.ts diff --git a/packages/core/src/2d/assembler/TextAssembler.ts b/packages/core/src/2d/assembler/TextAssembler.ts deleted file mode 100644 index 2638896519..0000000000 --- a/packages/core/src/2d/assembler/TextAssembler.ts +++ /dev/null @@ -1,327 +0,0 @@ -import { Color, Vector2, Vector3 } from "@oasis-engine/math"; -import { Engine } from "../../Engine"; -import { Texture2D } from "../../texture"; -import { RenderData2D } from "../data/RenderData2D"; -import { TextHorizontalAlignment, TextVerticalAlignment } from "../enums/TextAlignment"; -import { OverflowMode } from "../enums/TextOverflow"; -import { TextRenderer, DirtyFlag } from "../text/TextRenderer"; -import { TextUtils, TextMetrics } from "../text/TextUtils"; -import { IAssembler } from "./IAssembler"; -import { StaticInterfaceImplement } from "./StaticInterfaceImplement"; - -/** - * @internal - */ -@StaticInterfaceImplement() -export class TextAssembler { - private static _tempVec2: Vector2 = new Vector2(); - private static _tempVec3: Vector3 = new Vector3(); - private static _maxWidth: number = 1024; - private static _maxHeight: number = 1024; - - static resetData(renderer: TextRenderer) { - const positions: Array = []; - const uvs: Array = []; - const triangles: Array = []; - const color: Color = null; - - positions[0] = new Vector3(); - positions[1] = new Vector3(); - positions[2] = new Vector3(); - positions[3] = new Vector3(); - uvs[0] = new Vector2(); - uvs[1] = new Vector2(); - uvs[2] = new Vector2(); - uvs[3] = new Vector2(); - triangles[0] = 0, triangles[1] = 1, triangles[2] = 2; - triangles[3] = 2, triangles[4] = 1, triangles[5] = 3; - - renderer._renderData = new RenderData2D(4, positions, uvs, triangles, color); - } - - static updateData(renderer: TextRenderer): void { - const isRenderDirty = renderer._isContainDirtyFlag(DirtyFlag.RenderDirty); - if (isRenderDirty || renderer._isContainDirtyFlag(DirtyFlag.FontDirty)) { - TextAssembler._updateText(renderer); - renderer._setDirtyFlagFalse(DirtyFlag.RenderDirty); - renderer._setDirtyFlagFalse(DirtyFlag.FontDirty); - } - - if (renderer._isWorldMatrixDirty.flag || isRenderDirty) { - TextAssembler._updatePosition(renderer); - renderer._isWorldMatrixDirty.flag = false; - } - - renderer._renderData.color = renderer.color; - } - - static clearData(renderer: TextRenderer): void { - if (renderer._renderData) { - const { positions, uvs, triangles } = renderer._renderData; - positions.length = 0; - uvs.length = 0; - triangles.length = 0; - renderer._renderData = null; - } - } - - private static _updatePosition(renderer: TextRenderer): void { - const { _sprite } = renderer; - const { width, height, pivot } = _sprite; - const localPositions = _sprite._getPositions(); - const localVertexPos = TextAssembler._tempVec3; - const worldMatrix = renderer.entity.transform.worldMatrix; - - const { positions: _positions } = renderer._renderData; - for (let i = 0, n = _positions.length; i < n; i++) { - const curVertexPos = localPositions[i]; - localVertexPos.x = (curVertexPos.x - pivot.x) * width; - localVertexPos.y = (curVertexPos.y - pivot.y) * height; - localVertexPos.z = 0; - Vector3.transformToVec3(localVertexPos, worldMatrix, _positions[i]); - } - } - - private static _updateText(renderer: TextRenderer): void { - const { width: originWidth, height: originHeight, enableWrapping, overflowMode } = renderer; - const fontString = TextUtils.getNativeFontString(renderer.font.name, renderer.fontSize, renderer.fontStyle); - const textMetrics = TextAssembler._measureText( - renderer.text, - originWidth, - originHeight, - renderer.lineSpacing, - enableWrapping, - overflowMode, - fontString - ); - TextAssembler._fillText(textMetrics, fontString, renderer.horizontalAlignment, renderer.verticalAlignment); - TextAssembler._updateTexture(renderer); - } - - private static _measureText( - text: string, - originWidth: number, - originHeight: number, - lineSpacing: number, - enableWrapping: boolean, - overflowMode: OverflowMode, - fontString: string - ): TextMetrics { - const { _pixelsPerUnit } = Engine; - const fontSize = TextUtils.measureFont(fontString).size; - const context = TextUtils.textContext().context; - const lines = TextAssembler._wordWrap(text, originWidth, enableWrapping, fontString); - const lineCount = lines.length; - const lineWidths = new Array(); - const lineHeight = fontSize + lineSpacing * _pixelsPerUnit; - context.font = fontString; - // Calculate max width of all lines. - let width = 0; - for (let i = 0; i < lineCount; ++i) { - const lineWidth = context.measureText(lines[i]).width; - if (lineWidth > width) { - width = lineWidth; - } - lineWidths.push(lineWidth); - } - - // Reset width and height. - let height = originHeight * _pixelsPerUnit; - if (overflowMode === OverflowMode.Overflow) { - height = Math.min(lineHeight * lineCount, TextAssembler._maxHeight); - } - - return { - width, - height, - lines, - lineWidths, - lineHeight - }; - } - - private static _fillText( - textMetrics: TextMetrics, - fontString: string, - horizontalAlignment: TextHorizontalAlignment, - verticalAlignment: TextVerticalAlignment - ): void { - const { canvas, context } = TextUtils.textContext(); - const { width, height } = textMetrics; - // reset canvas's width and height. - canvas.width = width; - canvas.height = height; - // clear canvas. - context.font = fontString; - context.clearRect(0, 0, width, height); - // set canvas font info. - context.textBaseline = "middle"; - context.fillStyle = "#fff"; - - // draw lines. - const { lines, lineHeight, lineWidths } = textMetrics; - const halfLineHeight = lineHeight * 0.5; - for (let i = 0, l = lines.length; i < l; ++i) { - const lineWidth = lineWidths[i]; - const pos = TextAssembler._tempVec2; - TextAssembler._calculateLinePosition( - width, - height, - lineWidth, - lineHeight, - i, - l, - horizontalAlignment, - verticalAlignment, - pos - ); - const { x, y } = pos; - if (y + lineHeight >= 0 && y < height) { - context.fillText(lines[i], x, y + halfLineHeight); - } - } - } - - private static _updateTexture(renderer: TextRenderer): void { - const trimData = TextUtils.trimCanvas(); - const { width, height } = trimData; - const canvas = TextUtils.updateCanvas(width, height, trimData.data); - renderer._clearTexture(); - - const { _sprite: sprite, horizontalAlignment, verticalAlignment } = renderer; - - // Handle the case that width or height of text is larger than real width or height. - const { _pixelsPerUnit: pixelsPerUnit } = Engine; - const { pivot } = sprite; - switch (horizontalAlignment) { - case TextHorizontalAlignment.Left: - pivot.x = (renderer.width * pixelsPerUnit) / width * 0.5; - break; - case TextHorizontalAlignment.Right: - pivot.x = 1 - (renderer.width * pixelsPerUnit) / width * 0.5; - break; - case TextHorizontalAlignment.Center: - pivot.x = 0.5; - break; - } - switch (verticalAlignment) { - case TextVerticalAlignment.Top: - pivot.y = 1 - (renderer.height * pixelsPerUnit) / height * 0.5; - break; - case TextVerticalAlignment.Bottom: - pivot.y = (renderer.height * pixelsPerUnit) / height * 0.5; - break; - case TextVerticalAlignment.Center: - pivot.y = 0.5; - break; - } - sprite.pivot = pivot; - - // If add fail, set texture for sprite. - if (!renderer.engine._dynamicTextAtlasManager.addSprite(sprite, canvas)) { - const texture = new Texture2D(renderer.engine, width, height); - texture.setImageSource(canvas); - texture.generateMipmaps(); - sprite.texture = texture; - } - // Update sprite data. - TextAssembler._updateUVs(renderer); - } - - private static _wordWrap(text: string, width: number, enableWrapping: boolean, fontString: string): Array { - const { context } = TextUtils.textContext(); - const { _maxWidth: maxWidth } = TextAssembler; - const widthInPixel = width * Engine._pixelsPerUnit; - const wrapWidth = Math.min(widthInPixel, maxWidth); - const wrappedSubTexts = new Array(); - const subTexts = text.split(/(?:\r\n|\r|\n)/); - context.font = fontString; - - for (let i = 0, n = subTexts.length; i < n; ++i) { - const subText = subTexts[i]; - const subWidth = context.measureText(subText).width; - const needWrap = enableWrapping || subWidth > maxWidth; - - if (needWrap) { - const autoWrapWidth = enableWrapping ? wrapWidth : maxWidth; - if (subWidth <= autoWrapWidth) { - wrappedSubTexts.push(subText); - } else { - let chars = ""; - let charsWidth = 0; - for (let j = 0, m = subText.length; j < m; ++j) { - const char = subText[j]; - const charWidth = context.measureText(char).width; - if (charsWidth + charWidth > autoWrapWidth) { - // The width of text renderer is shorter than current char. - if (charsWidth === 0) { - wrappedSubTexts.push(char); - } else { - wrappedSubTexts.push(chars); - chars = char; - charsWidth = charWidth; - } - } else { - chars += char; - charsWidth += charWidth; - } - } - if (charsWidth > 0) { - wrappedSubTexts.push(chars); - } - } - } else { - wrappedSubTexts.push(subText); - } - } - - return wrappedSubTexts; - } - - private static _calculateLinePosition( - width: number, - height: number, - lineWidth: number, - lineHeight: number, - index: number, - length: number, - horizontalAlignment: TextHorizontalAlignment, - verticalAlignment: TextVerticalAlignment, - out: Vector2 - ): void { - switch (verticalAlignment) { - case TextVerticalAlignment.Top: - out.y = index * lineHeight; - break; - case TextVerticalAlignment.Bottom: - out.y = height - (length - index) * lineHeight; - break; - default: - out.y = 0.5 * height - 0.5 * length * lineHeight + index * lineHeight; - break; - } - - switch (horizontalAlignment) { - case TextHorizontalAlignment.Left: - out.x = 0; - break; - case TextHorizontalAlignment.Right: - out.x = width - lineWidth; - break; - default: - out.x = (width - lineWidth) * 0.5; - break; - } - } - - private static _updateUVs(renderer: TextRenderer): void { - const spriteUVs = renderer._sprite._getUVs(); - const renderUVs = renderer._renderData.uvs; - const { x: left, y: bottom } = spriteUVs[0]; - const { x: right, y: top } = spriteUVs[3]; - renderUVs[0].set(left, bottom); - renderUVs[1].set(right, bottom); - renderUVs[2].set(left, top); - renderUVs[3].set(right, top); - } -} diff --git a/packages/core/src/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts index 6af19a468c..d64d1eb49d 100644 --- a/packages/core/src/2d/text/TextRenderer.ts +++ b/packages/core/src/2d/text/TextRenderer.ts @@ -5,7 +5,6 @@ import { assignmentClone, deepClone, ignoreClone } from "../../clone/CloneManage import { Entity } from "../../Entity"; import { Texture2D } from "../../texture"; import { CharAssembler } from "../assembler/CharAssembler"; -import { TextAssembler } from "../assembler/textAssembler"; import { RenderData2D } from "../data/RenderData2D"; import { CharRenderData } from "../assembler/CharRenderData"; import { FontStyle } from "../enums/FontStyle"; @@ -13,10 +12,8 @@ import { TextHorizontalAlignment, TextVerticalAlignment } from "../enums/TextAli import { OverflowMode } from "../enums/TextOverflow"; import { Font } from "./Font"; import { Renderer } from "../../Renderer"; -import { Sprite } from "../sprite/Sprite"; import { SpriteMaskInteraction } from "../enums/SpriteMaskInteraction"; import { SpriteMaskLayer } from "../enums/SpriteMaskLayer"; -import { SpriteRenderer } from "../sprite/SpriteRenderer"; import { CompareFunction } from "../../shader/enums/CompareFunction"; import { ICustomClone } from "../../clone/ComponentCloner"; import { TextUtils } from "./TextUtils"; @@ -27,9 +24,6 @@ import { TextUtils } from "./TextUtils"; export class TextRenderer extends Renderer implements ICustomClone { private static _tempBounds: BoundingBox = new BoundingBox(); - /** @internal */ - @ignoreClone - _sprite: Sprite = null; /** @internal */ @ignoreClone _renderData: RenderData2D; @@ -62,8 +56,6 @@ export class TextRenderer extends Renderer implements ICustomClone { private _fontStyle: FontStyle = FontStyle.None; @assignmentClone private _lineSpacing: number = 0; - @ignoreClone - private _useCharCache: boolean = true; @assignmentClone private _horizontalAlignment: TextHorizontalAlignment = TextHorizontalAlignment.Center; @assignmentClone @@ -189,34 +181,6 @@ export class TextRenderer extends Renderer implements ICustomClone { } } - /** - * Whether cache each character individually. - */ - get useCharCache(): boolean { - return this._useCharCache; - } - - set useCharCache(value: boolean) { - if (this._useCharCache !== value) { - this._useCharCache = value; - this._setDirtyFlagTrue(DirtyFlag.FontDirty); - - if (value) { - if (this._sprite) { - this._clearTexture(); - this._sprite.destroy(); - this._sprite = null; - } - TextAssembler.clearData(this); - CharAssembler.resetData(this); - } else { - CharAssembler.clearData(this); - this._sprite = new Sprite(this.engine); - TextAssembler.resetData(this); - } - } - } - /** * The horizontal alignment. */ @@ -316,7 +280,6 @@ export class TextRenderer extends Renderer implements ICustomClone { (this.enableWrapping && this.width <= 0) || (this.overflowMode === OverflowMode.Truncate && this.height <= 0) ) { - this._clearTexture(); return; } @@ -325,33 +288,28 @@ export class TextRenderer extends Renderer implements ICustomClone { this._setDirtyFlagFalse(DirtyFlag.MaskInteraction); } - if (this._useCharCache) { - const isFontDirty = this._isContainDirtyFlag(DirtyFlag.FontDirty); - if (isFontDirty) { - this._resetCharFont(); - this._setDirtyFlagFalse(DirtyFlag.FontDirty); - } + const isFontDirty = this._isContainDirtyFlag(DirtyFlag.FontDirty); + if (isFontDirty) { + this._resetCharFont(); + this._setDirtyFlagFalse(DirtyFlag.FontDirty); + } - const isRenderDirty = this._isContainDirtyFlag(DirtyFlag.RenderDirty); - if (isRenderDirty || isFontDirty) { - CharAssembler.clearData(this); - CharAssembler.updateData(this); - this._setDirtyFlagFalse(DirtyFlag.RenderDirty); - } + const isRenderDirty = this._isContainDirtyFlag(DirtyFlag.RenderDirty); + if (isRenderDirty || isFontDirty) { + CharAssembler.clearData(this); + CharAssembler.updateData(this); + this._setDirtyFlagFalse(DirtyFlag.RenderDirty); + } - if (this._isWorldMatrixDirty.flag || isRenderDirty) { - this._updatePosition(); - this._isWorldMatrixDirty.flag = false; - } + if (this._isWorldMatrixDirty.flag || isRenderDirty) { + this._updatePosition(); + this._isWorldMatrixDirty.flag = false; + } - const { _charRenderDatas } = this; - for (let i = 0, l = _charRenderDatas.length; i < l; ++i) { - const charRenderData = _charRenderDatas[i]; - this._drawPrimitive(camera, charRenderData.renderData, charRenderData.texture); - } - } else { - TextAssembler.updateData(this); - this._drawPrimitive(camera, this._renderData, this._sprite.texture); + const { _charRenderDatas } = this; + for (let i = 0, l = _charRenderDatas.length; i < l; ++i) { + const charRenderData = _charRenderDatas[i]; + this._drawPrimitive(camera, charRenderData.renderData, charRenderData.texture); } } @@ -359,12 +317,7 @@ export class TextRenderer extends Renderer implements ICustomClone { * @internal */ _onDestroy(): void { - if (this._useCharCache) { - CharAssembler.clearData(this); - } else { - TextAssembler.clearData(this); - } - this.engine._dynamicTextAtlasManager.removeSprite(this._sprite); + CharAssembler.clearData(this); this._isWorldMatrixDirty.destroy(); super._onDestroy(); } @@ -374,7 +327,6 @@ export class TextRenderer extends Renderer implements ICustomClone { */ _cloneTo(target: TextRenderer): void { target.font = this._font; - target.useCharCache = this._useCharCache; } /** @@ -398,23 +350,6 @@ export class TextRenderer extends Renderer implements ICustomClone { this._dirtyFlag &= ~type; } - //--------------- only for text mode --------------- - - /** - * @internal - */ - _clearTexture(): void { - const { _sprite } = this; - if (_sprite) { - // Remove sprite from dynamic atlas. - this.engine._dynamicTextAtlasManager.removeSprite(_sprite); - this.shaderData.setTexture(SpriteRenderer._textureProperty, null); - _sprite.atlasRegion = _sprite.region; - } - } - - //-------------------------------------------------- - /** * @override */ @@ -424,38 +359,31 @@ export class TextRenderer extends Renderer implements ICustomClone { const { min, max } = bounds; min.set(0, 0, 0); max.set(0, 0, 0); - if (this._useCharCache) { - const { _charRenderDatas } = this; - const dataLen = _charRenderDatas.length; - if (dataLen > 0) { - const charRenderData = _charRenderDatas[0]; - const { localPositions } = charRenderData; + const { _charRenderDatas } = this; + const dataLen = _charRenderDatas.length; + if (dataLen > 0) { + const charRenderData = _charRenderDatas[0]; + const { localPositions } = charRenderData; + let minPos = localPositions[3]; + let maxPos = localPositions[1]; + let minX = minPos.x; + let minY = minPos.y; + let maxX = maxPos.x; + let maxY = maxPos.y; + + for (let i = 1; i < dataLen; ++i) { + const { localPositions } = _charRenderDatas[i]; let minPos = localPositions[3]; let maxPos = localPositions[1]; - let minX = minPos.x; - let minY = minPos.y; - let maxX = maxPos.x; - let maxY = maxPos.y; - - for (let i = 1; i < dataLen; ++i) { - const { localPositions } = _charRenderDatas[i]; - let minPos = localPositions[3]; - let maxPos = localPositions[1]; - minX > minPos.x && (minX = minPos.x); - minY > minPos.y && (minY = minPos.y); - maxX < maxPos.x && (maxX = maxPos.x); - maxY < maxPos.y && (maxY = maxPos.y); - } - min.set(minX, minY, 0); - max.set(maxX, maxY, 0); - } - BoundingBox.transform(bounds, worldMatrix, worldBounds); - } else { - if (this._sprite) { - bounds = this._sprite._getBounds(); + minX > minPos.x && (minX = minPos.x); + minY > minPos.y && (minY = minPos.y); + maxX < maxPos.x && (maxX = maxPos.x); + maxY < maxPos.y && (maxY = maxPos.y); } - BoundingBox.transform(bounds, worldMatrix, worldBounds); + min.set(minX, minY, 0); + max.set(maxX, maxY, 0); } + BoundingBox.transform(bounds, worldMatrix, worldBounds); } private _updateStencilState(): void { From 1ed8a8a0e6ad4a7b0e32e809e4f370858c547060 Mon Sep 17 00:00:00 2001 From: singlecoder Date: Thu, 7 Jul 2022 16:15:27 +0800 Subject: [PATCH 38/55] feat(text): delete text char mode --- .../src/2d/dynamic-atlas/DynamicTextAtlas.ts | 96 ------------- .../dynamic-atlas/DynamicTextAtlasManager.ts | 126 ------------------ packages/core/src/Engine.ts | 3 - 3 files changed, 225 deletions(-) delete mode 100644 packages/core/src/2d/dynamic-atlas/DynamicTextAtlas.ts delete mode 100644 packages/core/src/2d/dynamic-atlas/DynamicTextAtlasManager.ts diff --git a/packages/core/src/2d/dynamic-atlas/DynamicTextAtlas.ts b/packages/core/src/2d/dynamic-atlas/DynamicTextAtlas.ts deleted file mode 100644 index 819ce9761c..0000000000 --- a/packages/core/src/2d/dynamic-atlas/DynamicTextAtlas.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { Rect } from "@oasis-engine/math"; -import { Engine } from "../../Engine"; -import { Texture2D } from "../../texture/Texture2D"; -import { Sprite } from "../sprite/Sprite"; - -/** - * Dynamic atlas for text. - */ -export class DynamicTextAtlas { - private static _region: Rect = new Rect(); - - private _texture: Texture2D; - private _width: number; - private _height: number; - - private _space: number = 1; - private _curX: number = 1; - private _curY: number = 1; - private _nextY: number = 1; - - private _sprites: Record = {}; - - constructor(engine: Engine, width: number, height: number) { - this._width = width; - this._height = height; - this._texture = new Texture2D(engine, width, height); - this._texture._addRefCount(1); - } - - /** - * Destroy atlas, it will release the texture. - */ - public destroy() { - this._sprites = {}; - this._texture.destroy(true); - } - - /** - * Add a sprite. - * @param sprite - the sprite to add - * @param imageSource - The source of texture - * @returns true if add sprite success, otherwise false - */ - public addSprite(sprite: Sprite, imageSource: TexImageSource | OffscreenCanvas): boolean { - const { _space: space, _texture: texture } = this; - const { width, height } = imageSource; - - const offsetWidth = width + space; - const endX = this._curX + offsetWidth; - if (endX >= this._width) { - this._curX = space; - this._curY = this._nextY + space; - } - - const endY = this._curY + height + space; - if (endY > this._nextY) { - this._nextY = endY; - } - - if (this._nextY >= this._height) { - return false; - } - - texture.setImageSource(imageSource, 0, false, false, this._curX, this._curY); - texture.generateMipmaps(); - - const { _width, _height } = this; - const region = DynamicTextAtlas._region; - region.set(this._curX / _width, this._curY / _height, width / _width, height / _height); - - // destroy origin texture. - sprite.texture && sprite.texture.destroy(); - // Update atlas texture. - sprite.atlasRegion = region; - sprite.texture = texture; - this._curX += offsetWidth + space; - - return true; - } - - /** - * Remove a sprite. - * @param sprite - the sprite to remove - * @returns true if remove sprite success, otherwise false - */ - public removeSprite(sprite: Sprite): boolean { - const id = sprite.instanceId; - const { _sprites } = this; - if (_sprites[id]) { - delete _sprites[id]; - return true; - } - return false; - } -} - diff --git a/packages/core/src/2d/dynamic-atlas/DynamicTextAtlasManager.ts b/packages/core/src/2d/dynamic-atlas/DynamicTextAtlasManager.ts deleted file mode 100644 index 0032ac87c1..0000000000 --- a/packages/core/src/2d/dynamic-atlas/DynamicTextAtlasManager.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { Sprite } from "../sprite/Sprite"; -import { Engine } from "../../Engine"; -import { DynamicTextAtlas } from "./DynamicTextAtlas"; - -/** - * Dynamic atlas manager for text. - */ -export class DynamicTextAtlasManager { - private _maxAtlasCount: number = 2; - private _textureSize: number = 1024; - private _atlases: Array = []; - private _atlasIndex: number = -1; - private _spritesInAtlasIndex: Record = {}; - - /** - * Indicates how many atlases should be created. - */ - get maxAtlasCount(): number { - return this._maxAtlasCount; - } - - set maxAtlasCount(val: number) { - this._maxAtlasCount = val; - } - - /** - * Indicates the size of the texture. - */ - get textureSize(): number { - return this._textureSize; - } - - set textureSize(val: number) { - this._textureSize = Math.min(val, 2048); - } - - /** - * @internal - */ - constructor(public readonly engine: Engine) {} - - /** - * Add a sprite to atlas. - * @param sprite - the sprite to add - * @param imageSource - The source of texture - * @returns true if add sprite success, otherwise false - */ - public addSprite(sprite: Sprite, imageSource: TexImageSource | OffscreenCanvas): boolean { - // Remove sprite if the sprite has been add. - const { _spritesInAtlasIndex, _atlases } = this; - const id = sprite.instanceId; - const atlasIndex = _spritesInAtlasIndex[id]; - if (atlasIndex) { - _atlases[atlasIndex].removeSprite(sprite); - delete _spritesInAtlasIndex[id]; - } - - if (this._atlasIndex >= this._maxAtlasCount) { - return false; - } - - let atlas = _atlases[this._atlasIndex]; - if (!atlas) { - atlas = this._createAtlas(); - } - - if (atlas.addSprite(sprite, imageSource)) { - _spritesInAtlasIndex[id] = this._atlasIndex; - return true; - } - - if (this._atlasIndex + 1 >= this._maxAtlasCount) { - this._atlasIndex = this._maxAtlasCount; - return false; - } - - atlas = this._createAtlas(); - if (atlas.addSprite(sprite, imageSource)) { - _spritesInAtlasIndex[id] = this._atlasIndex; - return true; - } - return false; - } - - /** - * Remove a sprite from atlas. - * @param sprite - the sprite to remove - * @returns true if remove sprite success, otherwise false - */ - public removeSprite(sprite: Sprite): boolean { - if (!sprite) return false; - - const { _atlases } = this; - for (let i = _atlases.length - 1; i >= 0; --i) { - const atlas = _atlases[i]; - if(atlas.removeSprite(sprite)) { - delete this._spritesInAtlasIndex[i]; - return true; - } - } - - return false; - } - - /** - * Reset all atlases. - */ - public reset() { - const { _atlases } = this; - for (let i = 0, l = _atlases.length; i < l; ++i) { - _atlases[i].destroy(); - } - - _atlases.length = 0; - this._atlasIndex = -1; - this._spritesInAtlasIndex = {}; - } - - private _createAtlas(): DynamicTextAtlas { - this._atlasIndex++; - const { _textureSize } = this; - const atlas = new DynamicTextAtlas(this.engine, _textureSize, _textureSize); - this._atlases.push(atlas); - return atlas; - } -} diff --git a/packages/core/src/Engine.ts b/packages/core/src/Engine.ts index e4cf06045a..c358ff6aa7 100644 --- a/packages/core/src/Engine.ts +++ b/packages/core/src/Engine.ts @@ -1,5 +1,4 @@ import { ColorSpace } from "."; -import { DynamicTextAtlasManager } from "./2d/dynamic-atlas/DynamicTextAtlasManager"; import { ResourceManager } from "./asset/ResourceManager"; import { Event, EventDispatcher, Logger, Time } from "./base"; import { Canvas } from "./Canvas"; @@ -77,8 +76,6 @@ export class Engine extends EventDispatcher { _spriteMaskManager: SpriteMaskManager; /** @internal */ _macroCollection: ShaderMacroCollection = new ShaderMacroCollection(); - /** @internal */ - _dynamicTextAtlasManager: DynamicTextAtlasManager = new DynamicTextAtlasManager(this); protected _canvas: Canvas; From 44f9b1dd9eb0c6f794508cab628a96d45d590f99 Mon Sep 17 00:00:00 2001 From: singlecoder Date: Thu, 7 Jul 2022 16:21:21 +0800 Subject: [PATCH 39/55] feat(text): opt code --- packages/core/src/2d/text/TextUtils.ts | 51 -------------------------- 1 file changed, 51 deletions(-) diff --git a/packages/core/src/2d/text/TextUtils.ts b/packages/core/src/2d/text/TextUtils.ts index c01a905837..158c2ca5b8 100644 --- a/packages/core/src/2d/text/TextUtils.ts +++ b/packages/core/src/2d/text/TextUtils.ts @@ -103,55 +103,6 @@ export class TextUtils { return str; } - //--------------- only for text mode --------------- - - /** - * Trim canvas. - * @returns the width and height after trim, and the image data - */ - static trimCanvas(): { width: number; height: number; data?: ImageData } { - // https://gist.github.com/remy/784508 - - const { canvas, context } = TextUtils.textContext(); - let { width, height } = canvas; - - const imageData = context.getImageData(0, 0, width, height).data; - const len = imageData.length; - - let top = -1; - let bottom = -1; - let data = null; - let y; - - for (let i = 0; i < len; i += 4) { - if (imageData[i + 3] !== 0) { - const idx = i / 4; - y = ~~(idx / width); - - if (top === -1) { - top = y; - } - - if (y > bottom) { - bottom = y; - } - } - } - - if (top !== -1) { - top = Math.max(0, top); - bottom = Math.min(height - 1, bottom); - height = bottom - top + 1; - data = context.getImageData(0, top, width, height); - } - - return { - width, - height, - data - }; - } - /** * Update canvas with the data. * @param width - the new width of canvas @@ -167,8 +118,6 @@ export class TextUtils { return canvas; } - //-------------------------------------------------- - private static _measureFontOrChar(fontString: string, char: string = ""): FontSizeInfo | CharInfo { const { canvas, context } = TextUtils.textContext(); context.font = fontString; From e96831555aba9ce33f062de9f16ac77a71763f75 Mon Sep 17 00:00:00 2001 From: singlecoder Date: Thu, 7 Jul 2022 16:28:12 +0800 Subject: [PATCH 40/55] feat(text): opt code --- packages/core/src/2d/text/TextUtils.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/core/src/2d/text/TextUtils.ts b/packages/core/src/2d/text/TextUtils.ts index 158c2ca5b8..8cdcbf9e70 100644 --- a/packages/core/src/2d/text/TextUtils.ts +++ b/packages/core/src/2d/text/TextUtils.ts @@ -92,7 +92,7 @@ export class TextUtils { * @param style - The font style * @returns The native font hash */ - static getNativeFontHash(fontName: string, fontSize: number, style: FontStyle): string { + static getNativeFontHash(fontName: string, fontSize: number, style: FontStyle): string { let str = style & FontStyle.Bold ? "bold" : ""; style & FontStyle.Italic && (str += "italic"); // Check if font already contains strings @@ -110,7 +110,7 @@ export class TextUtils { * @param data - the new data of canvas * @returns the canvas after update */ - static updateCanvas(width: number, height: number, data: ImageData): HTMLCanvasElement | OffscreenCanvas { + static updateCanvas(width: number, height: number, data: ImageData): HTMLCanvasElement | OffscreenCanvas { const { canvas, context } = TextUtils.textContext(); canvas.width = width; canvas.height = height; @@ -190,7 +190,7 @@ export class TextUtils { ascent, descent, index: 0 - } + }; } else { return sizeInfo; } @@ -201,7 +201,7 @@ export class TextUtils { * @internal * TextContext. */ - export interface TextContext { +export interface TextContext { canvas: HTMLCanvasElement | OffscreenCanvas; context: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D; } From 8ef62129bf1e90a24f5f5f17cad44c0caa607dcd Mon Sep 17 00:00:00 2001 From: singlecoder Date: Thu, 7 Jul 2022 17:07:19 +0800 Subject: [PATCH 41/55] feat(text): opt code --- .../core/src/2d/assembler/CharAssembler.ts | 23 ++++++++++++------- packages/core/src/2d/text/TextRenderer.ts | 6 ++--- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/packages/core/src/2d/assembler/CharAssembler.ts b/packages/core/src/2d/assembler/CharAssembler.ts index 409e400e8c..47b64957a9 100644 --- a/packages/core/src/2d/assembler/CharAssembler.ts +++ b/packages/core/src/2d/assembler/CharAssembler.ts @@ -15,13 +15,9 @@ import { StaticInterfaceImplement } from "./StaticInterfaceImplement"; */ @StaticInterfaceImplement() export class CharAssembler { - private static _charRenderDataPool: CharRenderDataPool; + private static _charRenderDataPool: CharRenderDataPool = new CharRenderDataPool(); - static resetData(renderer: TextRenderer): void { - if (!CharAssembler._charRenderDataPool) { - CharAssembler._charRenderDataPool = new CharRenderDataPool(); - } - } + static resetData(renderer: TextRenderer): void {} static clearData(renderer: TextRenderer): void { const { _charRenderDatas } = renderer; @@ -62,6 +58,7 @@ export class CharAssembler { break; } + let renderDataCount = 0; for (let i = 0; i < linesLen; ++i) { const line = lines[i]; const lineWidth = lineWidths[i]; @@ -84,7 +81,7 @@ export class CharAssembler { const charInfo = charFont._getCharInfo(char); if (charInfo.h > 0) { - const charRenderData = _charRenderDataPool.getData(); + const charRenderData = _charRenderDatas[renderDataCount] || _charRenderDataPool.getData(); const { renderData, localPositions } = charRenderData; charRenderData.texture = charFont._getTextureByIndex(charInfo.index); renderData.color = color; @@ -109,7 +106,8 @@ export class CharAssembler { localPositions[3].set(left, bottom, 0); uvs[3].set(u0, v1); - _charRenderDatas.push(charRenderData); + _charRenderDatas[renderDataCount] = charRenderData; + renderDataCount++; } startX += charInfo.xAdvance; } @@ -117,6 +115,15 @@ export class CharAssembler { startY -= lineHeight; } + // Revert excess render data to pool. + const lastRenderDataCount = _charRenderDatas.length; + if (lastRenderDataCount > renderDataCount) { + for (let i = renderDataCount; i < lastRenderDataCount; ++i) { + CharAssembler._charRenderDataPool.putData(_charRenderDatas[i]); + } + _charRenderDatas.length = renderDataCount; + } + charFont._getLastIndex() > 0 && _charRenderDatas.sort((a, b) => { return a.texture.instanceId - b.texture.instanceId; diff --git a/packages/core/src/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts index d64d1eb49d..e1de9e47d7 100644 --- a/packages/core/src/2d/text/TextRenderer.ts +++ b/packages/core/src/2d/text/TextRenderer.ts @@ -266,7 +266,6 @@ export class TextRenderer extends Renderer implements ICustomClone { super(entity); const { engine } = this; this._isWorldMatrixDirty = entity.transform.registerWorldChangeFlag(); - CharAssembler.resetData(this); this.font = Font.createFromOS(engine); this.setMaterial(engine._spriteDefaultMaterial); } @@ -294,9 +293,8 @@ export class TextRenderer extends Renderer implements ICustomClone { this._setDirtyFlagFalse(DirtyFlag.FontDirty); } - const isRenderDirty = this._isContainDirtyFlag(DirtyFlag.RenderDirty); - if (isRenderDirty || isFontDirty) { - CharAssembler.clearData(this); + const isRenderDirty = this._isContainDirtyFlag(DirtyFlag.RenderDirty) || isFontDirty; + if (isRenderDirty) { CharAssembler.updateData(this); this._setDirtyFlagFalse(DirtyFlag.RenderDirty); } From 12c1b17713d2902eb97bec78d73adaa5731bd649 Mon Sep 17 00:00:00 2001 From: singlecoder Date: Thu, 7 Jul 2022 17:14:40 +0800 Subject: [PATCH 42/55] feat(text): opt code --- packages/core/src/2d/text/TextRenderer.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/core/src/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts index e1de9e47d7..fbd70e299e 100644 --- a/packages/core/src/2d/text/TextRenderer.ts +++ b/packages/core/src/2d/text/TextRenderer.ts @@ -24,9 +24,6 @@ import { TextUtils } from "./TextUtils"; export class TextRenderer extends Renderer implements ICustomClone { private static _tempBounds: BoundingBox = new BoundingBox(); - /** @internal */ - @ignoreClone - _renderData: RenderData2D; /** @internal */ @assignmentClone _charFont: Font = null; From c4f2293286dd1b8227f7d72076da995a87e01d2e Mon Sep 17 00:00:00 2001 From: singlecoder Date: Thu, 7 Jul 2022 17:41:30 +0800 Subject: [PATCH 43/55] feat(text): opt code --- packages/core/src/2d/text/TextRenderer.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/packages/core/src/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts index fbd70e299e..ab0bfb4b28 100644 --- a/packages/core/src/2d/text/TextRenderer.ts +++ b/packages/core/src/2d/text/TextRenderer.ts @@ -304,7 +304,9 @@ export class TextRenderer extends Renderer implements ICustomClone { const { _charRenderDatas } = this; for (let i = 0, l = _charRenderDatas.length; i < l; ++i) { const charRenderData = _charRenderDatas[i]; - this._drawPrimitive(camera, charRenderData.renderData, charRenderData.texture); + const spriteElement = this._engine._spriteElementPool.getFromPool(); + spriteElement.setValue(this, charRenderData.renderData, this.getMaterial(), charRenderData.texture); + camera._renderPipeline.pushPrimitive(spriteElement); } } @@ -405,13 +407,6 @@ export class TextRenderer extends Renderer implements ICustomClone { } } - private _drawPrimitive(camera: Camera, renderData: RenderData2D, texture: Texture2D): void { - const spriteElementPool = this._engine._spriteElementPool; - const spriteElement = spriteElementPool.getFromPool(); - spriteElement.setValue(this, renderData, this.getMaterial(), texture); - camera._renderPipeline.pushPrimitive(spriteElement); - } - private _resetCharFont(): void { const lastCharFont = this._charFont; if (lastCharFont) { From c36c19c2167b53ee54508777d9e553ce71892d2c Mon Sep 17 00:00:00 2001 From: singlecoder Date: Thu, 7 Jul 2022 17:47:47 +0800 Subject: [PATCH 44/55] feat(text): opt code --- packages/core/src/2d/assembler/CharAssembler.ts | 5 ++--- packages/core/src/2d/text/TextRenderer.ts | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/core/src/2d/assembler/CharAssembler.ts b/packages/core/src/2d/assembler/CharAssembler.ts index 47b64957a9..c55e6ddcf1 100644 --- a/packages/core/src/2d/assembler/CharAssembler.ts +++ b/packages/core/src/2d/assembler/CharAssembler.ts @@ -1,9 +1,8 @@ -import { Vector3 } from "@oasis-engine/math"; import { Engine } from "../../Engine"; import { TextHorizontalAlignment, TextVerticalAlignment } from "../enums/TextAlignment"; import { OverflowMode } from "../enums/TextOverflow"; import { Font } from "../text"; -import { TextRenderer, DirtyFlag } from "../text/TextRenderer"; +import { TextRenderer } from "../text/TextRenderer"; import { TextUtils, TextMetrics, FontSizeInfo } from "../text/TextUtils"; import { CharInfo } from "./CharInfo"; import { CharRenderDataPool } from "./CharRenderDataPool"; @@ -21,7 +20,7 @@ export class CharAssembler { static clearData(renderer: TextRenderer): void { const { _charRenderDatas } = renderer; - for (let i = 0, l = _charRenderDatas.length; i < l; ++i) { + for (let i = 0, n = _charRenderDatas.length; i < n; ++i) { CharAssembler._charRenderDataPool.putData(_charRenderDatas[i]); } _charRenderDatas.length = 0; diff --git a/packages/core/src/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts index ab0bfb4b28..acb2fea833 100644 --- a/packages/core/src/2d/text/TextRenderer.ts +++ b/packages/core/src/2d/text/TextRenderer.ts @@ -301,9 +301,9 @@ export class TextRenderer extends Renderer implements ICustomClone { this._isWorldMatrixDirty.flag = false; } - const { _charRenderDatas } = this; - for (let i = 0, l = _charRenderDatas.length; i < l; ++i) { - const charRenderData = _charRenderDatas[i]; + const charRenderDatas = this._charRenderDatas; + for (let i = 0, n = charRenderDatas.length; i < n; ++i) { + const charRenderData = charRenderDatas[i]; const spriteElement = this._engine._spriteElementPool.getFromPool(); spriteElement.setValue(this, charRenderData.renderData, this.getMaterial(), charRenderData.texture); camera._renderPipeline.pushPrimitive(spriteElement); From a78d42cf28393e420f8f6fdc2c9b32c3505ebace Mon Sep 17 00:00:00 2001 From: singlecoder Date: Thu, 7 Jul 2022 17:56:30 +0800 Subject: [PATCH 45/55] feat(text): opt code --- packages/core/src/2d/text/TextRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts index acb2fea833..603806b482 100644 --- a/packages/core/src/2d/text/TextRenderer.ts +++ b/packages/core/src/2d/text/TextRenderer.ts @@ -423,7 +423,7 @@ export class TextRenderer extends Renderer implements ICustomClone { private _updatePosition() { const worldMatrix = this.entity.transform.worldMatrix; const { _charRenderDatas } = this; - for (let i = 0, l = _charRenderDatas.length; i < l; ++i) { + for (let i = 0, n = _charRenderDatas.length; i < n; ++i) { const { localPositions, renderData } = _charRenderDatas[i]; for (let j = 0; j < 4; ++j) { Vector3.transformToVec3(localPositions[j], worldMatrix, renderData.positions[j]); From 423972359256636ae43b7260a415511eb5a21db9 Mon Sep 17 00:00:00 2001 From: singlecoder Date: Thu, 7 Jul 2022 18:38:38 +0800 Subject: [PATCH 46/55] feat(text): opt code --- .../core/src/2d/assembler/CharAssembler.ts | 9 +-- .../core/src/2d/assembler/CharRenderData.ts | 15 ++++- .../src/2d/assembler/CharRenderDataPool.ts | 67 ++++--------------- 3 files changed, 30 insertions(+), 61 deletions(-) diff --git a/packages/core/src/2d/assembler/CharAssembler.ts b/packages/core/src/2d/assembler/CharAssembler.ts index c55e6ddcf1..5621227818 100644 --- a/packages/core/src/2d/assembler/CharAssembler.ts +++ b/packages/core/src/2d/assembler/CharAssembler.ts @@ -5,6 +5,7 @@ import { Font } from "../text"; import { TextRenderer } from "../text/TextRenderer"; import { TextUtils, TextMetrics, FontSizeInfo } from "../text/TextUtils"; import { CharInfo } from "./CharInfo"; +import { CharRenderData } from "./CharRenderData"; import { CharRenderDataPool } from "./CharRenderDataPool"; import { IAssembler } from "./IAssembler"; import { StaticInterfaceImplement } from "./StaticInterfaceImplement"; @@ -14,14 +15,14 @@ import { StaticInterfaceImplement } from "./StaticInterfaceImplement"; */ @StaticInterfaceImplement() export class CharAssembler { - private static _charRenderDataPool: CharRenderDataPool = new CharRenderDataPool(); + private static _charRenderDataPool: CharRenderDataPool = new CharRenderDataPool(CharRenderData, 50); static resetData(renderer: TextRenderer): void {} static clearData(renderer: TextRenderer): void { const { _charRenderDatas } = renderer; for (let i = 0, n = _charRenderDatas.length; i < n; ++i) { - CharAssembler._charRenderDataPool.putData(_charRenderDatas[i]); + CharAssembler._charRenderDataPool.put(_charRenderDatas[i]); } _charRenderDatas.length = 0; } @@ -80,7 +81,7 @@ export class CharAssembler { const charInfo = charFont._getCharInfo(char); if (charInfo.h > 0) { - const charRenderData = _charRenderDatas[renderDataCount] || _charRenderDataPool.getData(); + const charRenderData = _charRenderDatas[renderDataCount] || _charRenderDataPool.get(); const { renderData, localPositions } = charRenderData; charRenderData.texture = charFont._getTextureByIndex(charInfo.index); renderData.color = color; @@ -118,7 +119,7 @@ export class CharAssembler { const lastRenderDataCount = _charRenderDatas.length; if (lastRenderDataCount > renderDataCount) { for (let i = renderDataCount; i < lastRenderDataCount; ++i) { - CharAssembler._charRenderDataPool.putData(_charRenderDatas[i]); + CharAssembler._charRenderDataPool.put(_charRenderDatas[i]); } _charRenderDatas.length = renderDataCount; } diff --git a/packages/core/src/2d/assembler/CharRenderData.ts b/packages/core/src/2d/assembler/CharRenderData.ts index 9b1b4ecbbd..e02d562e36 100644 --- a/packages/core/src/2d/assembler/CharRenderData.ts +++ b/packages/core/src/2d/assembler/CharRenderData.ts @@ -1,12 +1,21 @@ -import { Vector3 } from "@oasis-engine/math"; +import { Vector2, Vector3 } from "@oasis-engine/math"; import { Texture2D } from "../../texture"; import { RenderData2D } from "../data/RenderData2D"; /** * @internal */ -export interface CharRenderData { +export class CharRenderData { + static triangles: number[] = [0, 2, 1, 2, 0, 3]; + texture: Texture2D; - renderData: RenderData2D; localPositions: Vector3[]; + renderData: RenderData2D; + + constructor() { + const positions = [new Vector3(), new Vector3(), new Vector3(), new Vector3()]; + const uvs = [new Vector2(), new Vector2(), new Vector2(), new Vector2()]; + this.localPositions = [new Vector3(), new Vector3(), new Vector3(), new Vector3()]; + this.renderData = new RenderData2D(4, positions, uvs, CharRenderData.triangles, null); + } } diff --git a/packages/core/src/2d/assembler/CharRenderDataPool.ts b/packages/core/src/2d/assembler/CharRenderDataPool.ts index d63222af2b..ac4e89a9d5 100644 --- a/packages/core/src/2d/assembler/CharRenderDataPool.ts +++ b/packages/core/src/2d/assembler/CharRenderDataPool.ts @@ -1,67 +1,26 @@ -import { Color, Vector2, Vector3 } from "@oasis-engine/math"; -import { Texture2D } from "../../texture"; -import { RenderData2D } from "../data/RenderData2D"; -import { CharRenderData } from "./CharRenderData"; - /** * @internal */ -export class CharRenderDataPool { - private _pools: Array = []; - private _poolIndex: number = -1; +export class CharRenderDataPool { + private _elements: T[] = []; + private _type: new () => T; - /** - * @internal - */ - constructor() { - const length = 50; + constructor(type: new () => T, length: number) { + this._type = type; + const elements = this._elements; for (let i = 0; i < length; ++i) { - this._pools[i] = this._createData(); + elements[i] = new type(); } - this._poolIndex = length - 1; } - getData(): CharRenderData { - if (this._poolIndex !== -1) { - this._poolIndex--; - return this._pools.pop(); + get(): T { + if (this._elements.length > 0) { + return this._elements.pop(); } - - return this._createData(); + return new this._type(); } - putData(data: CharRenderData): void { - this._pools.push(data); - this._poolIndex++; - } - - private _createData(): CharRenderData { - const positions: Array = []; - const uvs: Array = []; - const triangles: Array = []; - const color: Color = null; - const texture: Texture2D = null; - const localPositions: Array = []; - - positions[0] = new Vector3(); - positions[1] = new Vector3(); - positions[2] = new Vector3(); - positions[3] = new Vector3(); - localPositions[0] = new Vector3(); - localPositions[1] = new Vector3(); - localPositions[2] = new Vector3(); - localPositions[3] = new Vector3(); - - uvs[0] = new Vector2(); - uvs[1] = new Vector2(); - uvs[2] = new Vector2(); - uvs[3] = new Vector2(); - - triangles[0] = 0, triangles[1] = 2, triangles[2] = 1; - triangles[3] = 2, triangles[4] = 0, triangles[5] = 3; - - const renderData: RenderData2D = { positions, uvs, triangles, color, vertexCount: 4 }; - - return { texture, renderData, localPositions }; + put(data: T): void { + this._elements.push(data); } } From 403f302a65a531efc50f1dcb03b8ccedbf315b89 Mon Sep 17 00:00:00 2001 From: singlecoder Date: Thu, 7 Jul 2022 19:24:32 +0800 Subject: [PATCH 47/55] feat(text): fix bounds error --- packages/core/src/2d/text/TextRenderer.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/core/src/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts index 603806b482..b9fdf5b565 100644 --- a/packages/core/src/2d/text/TextRenderer.ts +++ b/packages/core/src/2d/text/TextRenderer.ts @@ -259,6 +259,23 @@ export class TextRenderer extends Renderer implements ICustomClone { this._maskLayer = value; } + /** + * The bounding volume of the TextRenderer. + */ + get bounds(): BoundingBox { + const isFontDirty = this._isContainDirtyFlag(DirtyFlag.FontDirty); + const isRenderDirty = this._isContainDirtyFlag(DirtyFlag.RenderDirty); + if (this._transformChangeFlag.flag || isFontDirty || isRenderDirty) { + isFontDirty && this._resetCharFont(); + isRenderDirty && CharAssembler.updateData(this); + this._updatePosition(); + this._updateBounds(this._bounds); + this._setDirtyFlagFalse(DirtyFlag.FontDirty | DirtyFlag.RenderDirty); + this._transformChangeFlag.flag = false; + } + return this._bounds; + } + constructor(entity: Entity) { super(entity); const { engine } = this; From fd0c4caf5519589214a625cd842c3f15e4c6e266 Mon Sep 17 00:00:00 2001 From: singlecoder Date: Fri, 8 Jul 2022 10:40:26 +0800 Subject: [PATCH 48/55] feat(text): delete char assembler --- .../core/src/2d/assembler/CharAssembler.ts | 283 ------------------ packages/core/src/2d/atlas/FontAtlas.ts | 2 +- .../src/2d/{assembler => text}/CharInfo.ts | 0 .../2d/{assembler => text}/CharRenderData.ts | 0 .../{assembler => text}/CharRenderDataPool.ts | 0 packages/core/src/2d/text/Font.ts | 2 +- packages/core/src/2d/text/TextRenderer.ts | 125 +++++++- packages/core/src/2d/text/TextUtils.ts | 157 +++++++++- 8 files changed, 275 insertions(+), 294 deletions(-) delete mode 100644 packages/core/src/2d/assembler/CharAssembler.ts rename packages/core/src/2d/{assembler => text}/CharInfo.ts (100%) rename packages/core/src/2d/{assembler => text}/CharRenderData.ts (100%) rename packages/core/src/2d/{assembler => text}/CharRenderDataPool.ts (100%) diff --git a/packages/core/src/2d/assembler/CharAssembler.ts b/packages/core/src/2d/assembler/CharAssembler.ts deleted file mode 100644 index 5621227818..0000000000 --- a/packages/core/src/2d/assembler/CharAssembler.ts +++ /dev/null @@ -1,283 +0,0 @@ -import { Engine } from "../../Engine"; -import { TextHorizontalAlignment, TextVerticalAlignment } from "../enums/TextAlignment"; -import { OverflowMode } from "../enums/TextOverflow"; -import { Font } from "../text"; -import { TextRenderer } from "../text/TextRenderer"; -import { TextUtils, TextMetrics, FontSizeInfo } from "../text/TextUtils"; -import { CharInfo } from "./CharInfo"; -import { CharRenderData } from "./CharRenderData"; -import { CharRenderDataPool } from "./CharRenderDataPool"; -import { IAssembler } from "./IAssembler"; -import { StaticInterfaceImplement } from "./StaticInterfaceImplement"; - -/** - * @internal - */ -@StaticInterfaceImplement() -export class CharAssembler { - private static _charRenderDataPool: CharRenderDataPool = new CharRenderDataPool(CharRenderData, 50); - - static resetData(renderer: TextRenderer): void {} - - static clearData(renderer: TextRenderer): void { - const { _charRenderDatas } = renderer; - for (let i = 0, n = _charRenderDatas.length; i < n; ++i) { - CharAssembler._charRenderDataPool.put(_charRenderDatas[i]); - } - _charRenderDatas.length = 0; - } - - static updateData(renderer: TextRenderer): void { - const { color, horizontalAlignment, verticalAlignment, _charRenderDatas } = renderer; - const { _pixelsPerUnit } = Engine; - const pixelsPerUnitReciprocal = 1.0 / _pixelsPerUnit; - const charFont = renderer._charFont; - const rendererWidth = renderer.width * _pixelsPerUnit; - const rendererHeight = renderer.height * _pixelsPerUnit; - - const textMetrics = renderer.enableWrapping - ? CharAssembler._measureTextWithWrap(renderer) - : CharAssembler._measureTextWithoutWrap(renderer); - const { height, lines, lineWidths, lineHeight, lineMaxSizes } = textMetrics; - const { _charRenderDataPool } = CharAssembler; - const halfLineHeight = lineHeight * 0.5; - const linesLen = lines.length; - - 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; - } - - let renderDataCount = 0; - for (let i = 0; i < linesLen; ++i) { - const line = lines[i]; - const lineWidth = lineWidths[i]; - - let startX = 0; - switch (horizontalAlignment) { - case TextHorizontalAlignment.Left: - startX = -rendererWidth * 0.5; - break; - case TextHorizontalAlignment.Center: - startX = -lineWidth * 0.5; - break; - case TextHorizontalAlignment.Right: - startX = rendererWidth * 0.5 - lineWidth; - break; - } - - for (let j = 0, m = line.length; j < m; ++j) { - const char = line[j]; - const charInfo = charFont._getCharInfo(char); - - if (charInfo.h > 0) { - const charRenderData = _charRenderDatas[renderDataCount] || _charRenderDataPool.get(); - const { renderData, localPositions } = charRenderData; - charRenderData.texture = charFont._getTextureByIndex(charInfo.index); - renderData.color = color; - - const { uvs } = renderData; - const { w, u0, v0, u1, v1, ascent, descent } = charInfo; - - const left = startX * pixelsPerUnitReciprocal; - const right = (startX + w) * pixelsPerUnitReciprocal; - const top = (startY + ascent) * pixelsPerUnitReciprocal; - const bottom = (startY - descent + 1) * pixelsPerUnitReciprocal; - // Top-left. - localPositions[0].set(left, top, 0); - uvs[0].set(u0, v0); - // Top-right. - localPositions[1].set(right, top, 0); - uvs[1].set(u1, v0); - // Bottom-right. - localPositions[2].set(right, bottom, 0); - uvs[2].set(u1, v1); - // Bottom-left. - localPositions[3].set(left, bottom, 0); - uvs[3].set(u0, v1); - - _charRenderDatas[renderDataCount] = charRenderData; - renderDataCount++; - } - startX += charInfo.xAdvance; - } - - startY -= lineHeight; - } - - // Revert excess render data to pool. - const lastRenderDataCount = _charRenderDatas.length; - if (lastRenderDataCount > renderDataCount) { - for (let i = renderDataCount; i < lastRenderDataCount; ++i) { - CharAssembler._charRenderDataPool.put(_charRenderDatas[i]); - } - _charRenderDatas.length = renderDataCount; - } - - charFont._getLastIndex() > 0 && - _charRenderDatas.sort((a, b) => { - return a.texture.instanceId - b.texture.instanceId; - }); - } - - private static _measureTextWithWrap(renderer: TextRenderer): TextMetrics { - const { fontSize, fontStyle } = renderer; - const { name } = renderer.font; - const fontString = TextUtils.getNativeFontString(name, fontSize, fontStyle); - const charFont = renderer._charFont; - const fontSizeInfo = TextUtils.measureFont(fontString); - const subTexts = renderer.text.split(/(?:\r\n|\r|\n)/); - const lines = new Array(); - const lineWidths = new Array(); - const lineMaxSizes = new Array(); - const { _pixelsPerUnit } = Engine; - const lineHeight = fontSizeInfo.size + renderer.lineSpacing * _pixelsPerUnit; - const wrapWidth = renderer.width * _pixelsPerUnit; - let width = 0; - - for (let i = 0, n = subTexts.length; i < n; ++i) { - const subText = subTexts[i]; - let chars = ""; - let charsWidth = 0; - let maxAscent = -1; - let maxDescent = -1; - - for (let j = 0, m = subText.length; j < m; ++j) { - const char = subText[j]; - const charInfo = CharAssembler._getCharInfo(char, fontString, charFont); - const { w, offsetY } = charInfo; - const halfH = charInfo.h * 0.5; - const ascent = halfH + offsetY; - const descent = halfH - offsetY; - if (charsWidth + w > wrapWidth) { - if (charsWidth === 0) { - lines.push(char); - lineWidths.push(w); - lineMaxSizes.push({ - ascent, - descent, - size: ascent + descent - }); - } else { - lines.push(chars); - lineWidths.push(charsWidth); - lineMaxSizes.push({ - ascent: maxAscent, - descent: maxDescent, - size: maxAscent + maxDescent - }); - chars = char; - charsWidth = charInfo.xAdvance; - maxAscent = ascent; - maxDescent = descent; - } - } else { - chars += char; - charsWidth += charInfo.xAdvance; - maxAscent < ascent && (maxAscent = ascent); - maxDescent < descent && (maxDescent = descent); - } - } - - if (charsWidth > 0) { - lines.push(chars); - lineWidths.push(charsWidth); - lineMaxSizes.push({ - ascent: maxAscent, - descent: maxDescent, - size: maxAscent + maxDescent - }); - } - } - - let height = renderer.height * _pixelsPerUnit; - if (renderer.overflowMode === OverflowMode.Overflow) { - height = lineHeight * lines.length; - } - - return { - width, - height, - lines, - lineWidths, - lineHeight, - lineMaxSizes - }; - } - - private static _measureTextWithoutWrap(renderer: TextRenderer): TextMetrics { - const { fontSize, fontStyle } = renderer; - const { name } = renderer.font; - const fontString = TextUtils.getNativeFontString(name, fontSize, fontStyle); - const charFont = renderer._charFont; - const fontSizeInfo = TextUtils.measureFont(fontString); - const lines = renderer.text.split(/(?:\r\n|\r|\n)/); - const lineCount = lines.length; - 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; - } - - for (let i = 0; i < lineCount; ++i) { - const line = lines[i]; - let curWidth = 0; - let maxAscent = -1; - let maxDescent = -1; - - for (let j = 0, m = line.length; j < m; ++j) { - const charInfo = CharAssembler._getCharInfo(line[j], fontString, charFont); - curWidth += charInfo.xAdvance; - const { offsetY } = charInfo; - const halfH = charInfo.h * 0.5; - const ascent = halfH + offsetY; - const descent = halfH - offsetY; - 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; - } - } - - return { - width, - height, - lines, - lineWidths, - lineHeight, - lineMaxSizes - }; - } - - private static _getCharInfo(char: string, fontString: string, font: Font): CharInfo { - let charInfo = font._getCharInfo(char); - if (!charInfo) { - charInfo = TextUtils.measureChar(char, fontString); - font._uploadCharTexture(charInfo, TextUtils.textContext().canvas); - font._addCharInfo(char, charInfo); - } - - return charInfo; - } -} diff --git a/packages/core/src/2d/atlas/FontAtlas.ts b/packages/core/src/2d/atlas/FontAtlas.ts index faea9520bc..5b5e37241c 100644 --- a/packages/core/src/2d/atlas/FontAtlas.ts +++ b/packages/core/src/2d/atlas/FontAtlas.ts @@ -1,7 +1,7 @@ import { RefObject } from "../../asset/RefObject"; import { Engine } from "../../Engine"; import { Texture2D } from "../../texture/Texture2D"; -import { CharInfo } from "../assembler/CharInfo"; +import { CharInfo } from "../text/CharInfo"; /** * @internal diff --git a/packages/core/src/2d/assembler/CharInfo.ts b/packages/core/src/2d/text/CharInfo.ts similarity index 100% rename from packages/core/src/2d/assembler/CharInfo.ts rename to packages/core/src/2d/text/CharInfo.ts diff --git a/packages/core/src/2d/assembler/CharRenderData.ts b/packages/core/src/2d/text/CharRenderData.ts similarity index 100% rename from packages/core/src/2d/assembler/CharRenderData.ts rename to packages/core/src/2d/text/CharRenderData.ts diff --git a/packages/core/src/2d/assembler/CharRenderDataPool.ts b/packages/core/src/2d/text/CharRenderDataPool.ts similarity index 100% rename from packages/core/src/2d/assembler/CharRenderDataPool.ts rename to packages/core/src/2d/text/CharRenderDataPool.ts diff --git a/packages/core/src/2d/text/Font.ts b/packages/core/src/2d/text/Font.ts index 848591b139..dc88e1e31a 100644 --- a/packages/core/src/2d/text/Font.ts +++ b/packages/core/src/2d/text/Font.ts @@ -1,7 +1,7 @@ import { RefObject } from "../../asset/RefObject"; import { Engine } from "../../Engine"; import { Texture2D } from "../../texture"; -import { CharInfo } from "../assembler/CharInfo"; +import { CharInfo } from "./CharInfo"; import { FontAtlas } from "../atlas/FontAtlas"; /** diff --git a/packages/core/src/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts index b9fdf5b565..b5787ed8b9 100644 --- a/packages/core/src/2d/text/TextRenderer.ts +++ b/packages/core/src/2d/text/TextRenderer.ts @@ -3,10 +3,7 @@ import { BoolUpdateFlag } from "../../BoolUpdateFlag"; import { Camera } from "../../Camera"; import { assignmentClone, deepClone, ignoreClone } from "../../clone/CloneManager"; import { Entity } from "../../Entity"; -import { Texture2D } from "../../texture"; -import { CharAssembler } from "../assembler/CharAssembler"; -import { RenderData2D } from "../data/RenderData2D"; -import { CharRenderData } from "../assembler/CharRenderData"; +import { CharRenderData } from "./CharRenderData"; import { FontStyle } from "../enums/FontStyle"; import { TextHorizontalAlignment, TextVerticalAlignment } from "../enums/TextAlignment"; import { OverflowMode } from "../enums/TextOverflow"; @@ -17,11 +14,14 @@ import { SpriteMaskLayer } from "../enums/SpriteMaskLayer"; import { CompareFunction } from "../../shader/enums/CompareFunction"; import { ICustomClone } from "../../clone/ComponentCloner"; import { TextUtils } from "./TextUtils"; +import { CharRenderDataPool } from "./CharRenderDataPool"; +import { Engine } from "../../Engine"; /** * Renders a text for 2D graphics. */ export class TextRenderer extends Renderer implements ICustomClone { + private static _charRenderDataPool: CharRenderDataPool = new CharRenderDataPool(CharRenderData, 50); private static _tempBounds: BoundingBox = new BoundingBox(); /** @internal */ @@ -267,7 +267,7 @@ export class TextRenderer extends Renderer implements ICustomClone { const isRenderDirty = this._isContainDirtyFlag(DirtyFlag.RenderDirty); if (this._transformChangeFlag.flag || isFontDirty || isRenderDirty) { isFontDirty && this._resetCharFont(); - isRenderDirty && CharAssembler.updateData(this); + isRenderDirty && this._updateData(); this._updatePosition(); this._updateBounds(this._bounds); this._setDirtyFlagFalse(DirtyFlag.FontDirty | DirtyFlag.RenderDirty); @@ -309,7 +309,7 @@ export class TextRenderer extends Renderer implements ICustomClone { const isRenderDirty = this._isContainDirtyFlag(DirtyFlag.RenderDirty) || isFontDirty; if (isRenderDirty) { - CharAssembler.updateData(this); + this._updateData(); this._setDirtyFlagFalse(DirtyFlag.RenderDirty); } @@ -331,7 +331,13 @@ export class TextRenderer extends Renderer implements ICustomClone { * @internal */ _onDestroy(): void { - CharAssembler.clearData(this); + // Clear render data. + const charRenderDatas = this._charRenderDatas; + for (let i = 0, n = charRenderDatas.length; i < n; ++i) { + TextRenderer._charRenderDataPool.put(charRenderDatas[i]); + } + charRenderDatas.length = 0; + this._isWorldMatrixDirty.destroy(); super._onDestroy(); } @@ -437,7 +443,7 @@ export class TextRenderer extends Renderer implements ICustomClone { this._charFont._addRefCount(1); } - private _updatePosition() { + private _updatePosition(): void { const worldMatrix = this.entity.transform.worldMatrix; const { _charRenderDatas } = this; for (let i = 0, n = _charRenderDatas.length; i < n; ++i) { @@ -447,6 +453,109 @@ export class TextRenderer extends Renderer implements ICustomClone { } } } + + private _updateData(): void { + const { color, horizontalAlignment, verticalAlignment, _charRenderDatas } = this; + const { _pixelsPerUnit } = Engine; + const pixelsPerUnitReciprocal = 1.0 / _pixelsPerUnit; + const charFont = this._charFont; + const rendererWidth = this.width * _pixelsPerUnit; + 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 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; + } + + let renderDataCount = 0; + for (let i = 0; i < linesLen; ++i) { + const line = lines[i]; + const lineWidth = lineWidths[i]; + + let startX = 0; + switch (horizontalAlignment) { + case TextHorizontalAlignment.Left: + startX = -rendererWidth * 0.5; + break; + case TextHorizontalAlignment.Center: + startX = -lineWidth * 0.5; + break; + case TextHorizontalAlignment.Right: + startX = rendererWidth * 0.5 - lineWidth; + break; + } + + for (let j = 0, m = line.length; j < m; ++j) { + const char = line[j]; + const charInfo = charFont._getCharInfo(char); + + if (charInfo.h > 0) { + const charRenderData = _charRenderDatas[renderDataCount] || charRenderDataPool.get(); + const { renderData, localPositions } = charRenderData; + charRenderData.texture = charFont._getTextureByIndex(charInfo.index); + renderData.color = color; + + const { uvs } = renderData; + const { w, u0, v0, u1, v1, ascent, descent } = charInfo; + + const left = startX * pixelsPerUnitReciprocal; + const right = (startX + w) * pixelsPerUnitReciprocal; + const top = (startY + ascent) * pixelsPerUnitReciprocal; + const bottom = (startY - descent + 1) * pixelsPerUnitReciprocal; + // Top-left. + localPositions[0].set(left, top, 0); + uvs[0].set(u0, v0); + // Top-right. + localPositions[1].set(right, top, 0); + uvs[1].set(u1, v0); + // Bottom-right. + localPositions[2].set(right, bottom, 0); + uvs[2].set(u1, v1); + // Bottom-left. + localPositions[3].set(left, bottom, 0); + uvs[3].set(u0, v1); + + _charRenderDatas[renderDataCount] = charRenderData; + renderDataCount++; + } + startX += charInfo.xAdvance; + } + + startY -= lineHeight; + } + + // Revert excess render data to pool. + const lastRenderDataCount = _charRenderDatas.length; + if (lastRenderDataCount > renderDataCount) { + for (let i = renderDataCount; i < lastRenderDataCount; ++i) { + charRenderDataPool.put(_charRenderDatas[i]); + } + _charRenderDatas.length = renderDataCount; + } + + charFont._getLastIndex() > 0 && + _charRenderDatas.sort((a, b) => { + return a.texture.instanceId - b.texture.instanceId; + }); + } } export enum DirtyFlag { diff --git a/packages/core/src/2d/text/TextUtils.ts b/packages/core/src/2d/text/TextUtils.ts index 8cdcbf9e70..0c2425c95e 100644 --- a/packages/core/src/2d/text/TextUtils.ts +++ b/packages/core/src/2d/text/TextUtils.ts @@ -1,5 +1,9 @@ -import { CharInfo } from "../assembler/CharInfo"; +import { Engine } from "../../Engine"; +import { CharInfo } from "./CharInfo"; import { FontStyle } from "../enums/FontStyle"; +import { OverflowMode } from "../enums/TextOverflow"; +import { Font } from "./Font"; +import { TextRenderer } from "./TextRenderer"; /** * @internal @@ -85,6 +89,146 @@ export class TextUtils { return TextUtils._measureFontOrChar(fontString, char); } + static measureTextWithWrap(renderer: TextRenderer): TextMetrics { + const { fontSize, fontStyle } = renderer; + const { name } = renderer.font; + const fontString = TextUtils.getNativeFontString(name, fontSize, fontStyle); + const charFont = renderer._charFont; + const fontSizeInfo = TextUtils.measureFont(fontString); + const subTexts = renderer.text.split(/(?:\r\n|\r|\n)/); + const lines = new Array(); + const lineWidths = new Array(); + const lineMaxSizes = new Array(); + const { _pixelsPerUnit } = Engine; + const lineHeight = fontSizeInfo.size + renderer.lineSpacing * _pixelsPerUnit; + const wrapWidth = renderer.width * _pixelsPerUnit; + let width = 0; + + for (let i = 0, n = subTexts.length; i < n; ++i) { + const subText = subTexts[i]; + let chars = ""; + let charsWidth = 0; + let maxAscent = -1; + let maxDescent = -1; + + for (let j = 0, m = subText.length; j < m; ++j) { + const char = subText[j]; + const charInfo = TextUtils._getCharInfo(char, fontString, charFont); + const { w, offsetY } = charInfo; + const halfH = charInfo.h * 0.5; + const ascent = halfH + offsetY; + const descent = halfH - offsetY; + if (charsWidth + w > wrapWidth) { + if (charsWidth === 0) { + lines.push(char); + lineWidths.push(w); + lineMaxSizes.push({ + ascent, + descent, + size: ascent + descent + }); + } else { + lines.push(chars); + lineWidths.push(charsWidth); + lineMaxSizes.push({ + ascent: maxAscent, + descent: maxDescent, + size: maxAscent + maxDescent + }); + chars = char; + charsWidth = charInfo.xAdvance; + maxAscent = ascent; + maxDescent = descent; + } + } else { + chars += char; + charsWidth += charInfo.xAdvance; + maxAscent < ascent && (maxAscent = ascent); + maxDescent < descent && (maxDescent = descent); + } + } + + if (charsWidth > 0) { + lines.push(chars); + lineWidths.push(charsWidth); + lineMaxSizes.push({ + ascent: maxAscent, + descent: maxDescent, + size: maxAscent + maxDescent + }); + } + } + + let height = renderer.height * _pixelsPerUnit; + if (renderer.overflowMode === OverflowMode.Overflow) { + height = lineHeight * lines.length; + } + + return { + width, + height, + lines, + lineWidths, + lineHeight, + lineMaxSizes + }; + } + + static measureTextWithoutWrap(renderer: TextRenderer): TextMetrics { + const { fontSize, fontStyle } = renderer; + const { name } = renderer.font; + const fontString = TextUtils.getNativeFontString(name, fontSize, fontStyle); + const charFont = renderer._charFont; + const fontSizeInfo = TextUtils.measureFont(fontString); + const lines = renderer.text.split(/(?:\r\n|\r|\n)/); + const lineCount = lines.length; + 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; + } + + for (let i = 0; i < lineCount; ++i) { + const line = lines[i]; + let curWidth = 0; + let maxAscent = -1; + let maxDescent = -1; + + for (let j = 0, m = line.length; j < m; ++j) { + const charInfo = TextUtils._getCharInfo(line[j], fontString, charFont); + curWidth += charInfo.xAdvance; + const { offsetY } = charInfo; + const halfH = charInfo.h * 0.5; + const ascent = halfH + offsetY; + const descent = halfH - offsetY; + 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; + } + } + + return { + width, + height, + lines, + lineWidths, + lineHeight, + lineMaxSizes + }; + } + /** * Get native font hash. * @param fontName - The font name @@ -195,6 +339,17 @@ export class TextUtils { return sizeInfo; } } + + private static _getCharInfo(char: string, fontString: string, font: Font): CharInfo { + let charInfo = font._getCharInfo(char); + if (!charInfo) { + charInfo = TextUtils.measureChar(char, fontString); + font._uploadCharTexture(charInfo, TextUtils.textContext().canvas); + font._addCharInfo(char, charInfo); + } + + return charInfo; + } } /** From d7e8e192c2b57e2efbb349f64150b830a877f94b Mon Sep 17 00:00:00 2001 From: singlecoder Date: Fri, 8 Jul 2022 11:45:22 +0800 Subject: [PATCH 49/55] feat(text): opt code --- packages/core/src/2d/text/Font.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/core/src/2d/text/Font.ts b/packages/core/src/2d/text/Font.ts index dc88e1e31a..fc973a465d 100644 --- a/packages/core/src/2d/text/Font.ts +++ b/packages/core/src/2d/text/Font.ts @@ -47,7 +47,7 @@ export class Font extends RefObject { * @internal */ _uploadCharTexture(charInfo: CharInfo, imageSource: TexImageSource | OffscreenCanvas): void { - const { _fontAtlases: fontAtlases } = this; + const fontAtlases = this._fontAtlases; let lastIndex = this._lastIndex; if (lastIndex === -1) { this._createFontAtlas(); @@ -66,17 +66,17 @@ export class Font extends RefObject { * @internal */ _addCharInfo(char: string, charInfo: CharInfo) { - const { _lastIndex } = this; - charInfo.index = _lastIndex; - this._fontAtlases[_lastIndex].addCharInfo(char, charInfo); + const lastIndex = this._lastIndex; + charInfo.index = lastIndex; + this._fontAtlases[lastIndex].addCharInfo(char, charInfo); } /** * @internal */ _getCharInfo(char: string): CharInfo { - const { _fontAtlases: fontAtlases } = this; - for (let i = 0, l = fontAtlases.length; i < l; ++i) { + const fontAtlases = this._fontAtlases; + for (let i = 0, n = fontAtlases.length; i < n; ++i) { const fontAtlas = fontAtlases[i]; const charInfo = fontAtlas.getCharInfo(char); if (charInfo) { @@ -108,11 +108,11 @@ export class Font extends RefObject { * @override */ _onDestroy(): void { - const { _fontAtlases } = this; - for (let i = 0, l = _fontAtlases.length; i < l; ++i) { - _fontAtlases[i].destroy(true); + const fontAtlases = this._fontAtlases; + for (let i = 0, n = fontAtlases.length; i < n; ++i) { + fontAtlases[i].destroy(true); } - _fontAtlases.length = 0; + fontAtlases.length = 0; delete Font._fontMap[this._name]; } From eaf6d76b26ceed3005c8ca2dfc5d53253bc69084 Mon Sep 17 00:00:00 2001 From: singlecoder Date: Fri, 8 Jul 2022 12:45:41 +0800 Subject: [PATCH 50/55] feat(text): opt code --- packages/core/src/2d/text/TextRenderer.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/core/src/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts index b5787ed8b9..b235a827e1 100644 --- a/packages/core/src/2d/text/TextRenderer.ts +++ b/packages/core/src/2d/text/TextRenderer.ts @@ -460,6 +460,7 @@ export class TextRenderer extends Renderer implements ICustomClone { const pixelsPerUnitReciprocal = 1.0 / _pixelsPerUnit; const charFont = this._charFont; const rendererWidth = this.width * _pixelsPerUnit; + const halfRendererWidth = rendererWidth * 0.5; const rendererHeight = this.height * _pixelsPerUnit; const textMetrics = this.enableWrapping @@ -493,13 +494,13 @@ export class TextRenderer extends Renderer implements ICustomClone { let startX = 0; switch (horizontalAlignment) { case TextHorizontalAlignment.Left: - startX = -rendererWidth * 0.5; + startX = -halfRendererWidth; break; case TextHorizontalAlignment.Center: startX = -lineWidth * 0.5; break; case TextHorizontalAlignment.Right: - startX = rendererWidth * 0.5 - lineWidth; + startX = halfRendererWidth - lineWidth; break; } From a3e2308f545433114f9fea741da5a4b7031f22d0 Mon Sep 17 00:00:00 2001 From: singlecoder Date: Fri, 8 Jul 2022 13:50:22 +0800 Subject: [PATCH 51/55] feat(text): opt code --- packages/core/src/2d/text/TextRenderer.ts | 78 +++++++++++------------ 1 file changed, 37 insertions(+), 41 deletions(-) diff --git a/packages/core/src/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts index b235a827e1..c694e3abd0 100644 --- a/packages/core/src/2d/text/TextRenderer.ts +++ b/packages/core/src/2d/text/TextRenderer.ts @@ -22,7 +22,6 @@ import { Engine } from "../../Engine"; */ export class TextRenderer extends Renderer implements ICustomClone { private static _charRenderDataPool: CharRenderDataPool = new CharRenderDataPool(CharRenderData, 50); - private static _tempBounds: BoundingBox = new BoundingBox(); /** @internal */ @assignmentClone @@ -45,6 +44,8 @@ export class TextRenderer extends Renderer implements ICustomClone { private _width: number = 0; @assignmentClone private _height: number = 0; + @ignoreClone + private _localBounds: BoundingBox = new BoundingBox(); @assignmentClone private _font: Font = null; @assignmentClone @@ -374,36 +375,7 @@ export class TextRenderer extends Renderer implements ICustomClone { * @override */ protected _updateBounds(worldBounds: BoundingBox): void { - const worldMatrix = this._entity.transform.worldMatrix; - let bounds = TextRenderer._tempBounds; - const { min, max } = bounds; - min.set(0, 0, 0); - max.set(0, 0, 0); - const { _charRenderDatas } = this; - const dataLen = _charRenderDatas.length; - if (dataLen > 0) { - const charRenderData = _charRenderDatas[0]; - const { localPositions } = charRenderData; - let minPos = localPositions[3]; - let maxPos = localPositions[1]; - let minX = minPos.x; - let minY = minPos.y; - let maxX = maxPos.x; - let maxY = maxPos.y; - - for (let i = 1; i < dataLen; ++i) { - const { localPositions } = _charRenderDatas[i]; - let minPos = localPositions[3]; - let maxPos = localPositions[1]; - minX > minPos.x && (minX = minPos.x); - minY > minPos.y && (minY = minPos.y); - maxX < maxPos.x && (maxX = maxPos.x); - maxY < maxPos.y && (maxY = maxPos.y); - } - min.set(minX, minY, 0); - max.set(maxX, maxY, 0); - } - BoundingBox.transform(bounds, worldMatrix, worldBounds); + BoundingBox.transform(this._localBounds, this._entity.transform.worldMatrix, worldBounds); } private _updateStencilState(): void { @@ -445,9 +417,9 @@ export class TextRenderer extends Renderer implements ICustomClone { private _updatePosition(): void { const worldMatrix = this.entity.transform.worldMatrix; - const { _charRenderDatas } = this; - for (let i = 0, n = _charRenderDatas.length; i < n; ++i) { - const { localPositions, renderData } = _charRenderDatas[i]; + const charRenderDatas = this._charRenderDatas; + for (let i = 0, n = charRenderDatas.length; i < n; ++i) { + const { localPositions, renderData } = charRenderDatas[i]; for (let j = 0; j < 4; ++j) { Vector3.transformToVec3(localPositions[j], worldMatrix, renderData.positions[j]); } @@ -455,7 +427,10 @@ export class TextRenderer extends Renderer implements ICustomClone { } private _updateData(): void { - const { color, horizontalAlignment, verticalAlignment, _charRenderDatas } = this; + const { color, horizontalAlignment, verticalAlignment, _charRenderDatas: charRenderDatas } = this; + const { min, max } = this._localBounds; + min.set(0, 0, 0); + max.set(0, 0, 0); const { _pixelsPerUnit } = Engine; const pixelsPerUnitReciprocal = 1.0 / _pixelsPerUnit; const charFont = this._charFont; @@ -487,6 +462,11 @@ export class TextRenderer extends Renderer implements ICustomClone { } let renderDataCount = 0; + let minX = Number.MAX_SAFE_INTEGER; + let minY = Number.MAX_SAFE_INTEGER; + let maxX = Number.MIN_SAFE_INTEGER; + let maxY = Number.MIN_SAFE_INTEGER; + let lastLineIndex = linesLen - 1; for (let i = 0; i < linesLen; ++i) { const line = lines[i]; const lineWidth = lineWidths[i]; @@ -509,7 +489,7 @@ export class TextRenderer extends Renderer implements ICustomClone { const charInfo = charFont._getCharInfo(char); if (charInfo.h > 0) { - const charRenderData = _charRenderDatas[renderDataCount] || charRenderDataPool.get(); + const charRenderData = charRenderDatas[renderDataCount] || charRenderDataPool.get(); const { renderData, localPositions } = charRenderData; charRenderData.texture = charFont._getTextureByIndex(charInfo.index); renderData.color = color; @@ -534,8 +514,21 @@ export class TextRenderer extends Renderer implements ICustomClone { localPositions[3].set(left, bottom, 0); uvs[3].set(u0, v1); - _charRenderDatas[renderDataCount] = charRenderData; + charRenderDatas[renderDataCount] = charRenderData; renderDataCount++; + + if (i === 0) { + maxY = Math.max(maxY, top); + } + if (i === lastLineIndex) { + minY = Math.min(minY, bottom); + } + if (j === 0) { + minX = Math.min(minX, left); + } + if (j === m - 1) { + maxX = Math.max(maxX, right); + } } startX += charInfo.xAdvance; } @@ -543,17 +536,20 @@ export class TextRenderer extends Renderer implements ICustomClone { startY -= lineHeight; } + min.set(minX, minY, 0); + max.set(maxX, maxY, 0); + // Revert excess render data to pool. - const lastRenderDataCount = _charRenderDatas.length; + const lastRenderDataCount = charRenderDatas.length; if (lastRenderDataCount > renderDataCount) { for (let i = renderDataCount; i < lastRenderDataCount; ++i) { - charRenderDataPool.put(_charRenderDatas[i]); + charRenderDataPool.put(charRenderDatas[i]); } - _charRenderDatas.length = renderDataCount; + charRenderDatas.length = renderDataCount; } charFont._getLastIndex() > 0 && - _charRenderDatas.sort((a, b) => { + charRenderDatas.sort((a, b) => { return a.texture.instanceId - b.texture.instanceId; }); } From 99de1ffc8b5321c8236e48039598733686f024ec Mon Sep 17 00:00:00 2001 From: singlecoder Date: Fri, 8 Jul 2022 15:39:37 +0800 Subject: [PATCH 52/55] feat(text): opt code --- packages/core/src/2d/text/TextRenderer.ts | 72 ++++++++++++----------- packages/core/src/Transform.ts | 8 +++ 2 files changed, 46 insertions(+), 34 deletions(-) diff --git a/packages/core/src/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts index c694e3abd0..867d3a0621 100644 --- a/packages/core/src/2d/text/TextRenderer.ts +++ b/packages/core/src/2d/text/TextRenderer.ts @@ -1,5 +1,4 @@ import { BoundingBox, Color, Vector3 } from "@oasis-engine/math"; -import { BoolUpdateFlag } from "../../BoolUpdateFlag"; import { Camera } from "../../Camera"; import { assignmentClone, deepClone, ignoreClone } from "../../clone/CloneManager"; import { Entity } from "../../Entity"; @@ -16,6 +15,7 @@ import { ICustomClone } from "../../clone/ComponentCloner"; import { TextUtils } from "./TextUtils"; import { CharRenderDataPool } from "./CharRenderDataPool"; import { Engine } from "../../Engine"; +import { ListenerUpdateFlag } from "../../ListenerUpdateFlag"; /** * Renders a text for 2D graphics. @@ -31,10 +31,10 @@ export class TextRenderer extends Renderer implements ICustomClone { _charRenderDatas: Array = []; @ignoreClone - _dirtyFlag: number = DirtyFlag.RenderDirty | DirtyFlag.FontDirty; + _dirtyFlag: number = DirtyFlag.Font | DirtyFlag.LocalPositionBounds | DirtyFlag.WorldPosition | DirtyFlag.WorldBounds; /** @internal */ @ignoreClone - _isWorldMatrixDirty: BoolUpdateFlag; + _isWorldMatrixDirty: ListenerUpdateFlag; @deepClone private _color: Color = new Color(1, 1, 1, 1); @@ -91,7 +91,7 @@ export class TextRenderer extends Renderer implements ICustomClone { value = value || ""; if (this._text !== value) { this._text = value; - this._setDirtyFlagTrue(DirtyFlag.RenderDirty); + this._setDirtyFlagTrue(DirtyFlag.LocalPositionBounds); } } @@ -105,7 +105,7 @@ export class TextRenderer extends Renderer implements ICustomClone { set width(value: number) { if (this._width !== value) { this._width = value; - this._setDirtyFlagTrue(DirtyFlag.RenderDirty); + this._setDirtyFlagTrue(DirtyFlag.LocalPositionBounds); } } @@ -119,7 +119,7 @@ export class TextRenderer extends Renderer implements ICustomClone { set height(value: number) { if (this._height !== value) { this._height = value; - this._setDirtyFlagTrue(DirtyFlag.RenderDirty); + this._setDirtyFlagTrue(DirtyFlag.LocalPositionBounds); } } @@ -133,7 +133,7 @@ export class TextRenderer extends Renderer implements ICustomClone { set font(value: Font) { if (this._font !== value) { this._font = value; - this._setDirtyFlagTrue(DirtyFlag.FontDirty); + this._setDirtyFlagTrue(DirtyFlag.Font); } } @@ -147,7 +147,7 @@ export class TextRenderer extends Renderer implements ICustomClone { set fontSize(value: number) { if (this._fontSize !== value) { this._fontSize = value; - this._setDirtyFlagTrue(DirtyFlag.FontDirty); + this._setDirtyFlagTrue(DirtyFlag.Font); } } @@ -161,7 +161,7 @@ export class TextRenderer extends Renderer implements ICustomClone { set fontStyle(value: FontStyle) { if (this.fontStyle !== value) { this._fontStyle = value; - this._setDirtyFlagTrue(DirtyFlag.FontDirty); + this._setDirtyFlagTrue(DirtyFlag.Font); } } @@ -175,7 +175,7 @@ export class TextRenderer extends Renderer implements ICustomClone { set lineSpacing(value: number) { if (this._lineSpacing !== value) { this._lineSpacing = value; - this._setDirtyFlagTrue(DirtyFlag.RenderDirty); + this._setDirtyFlagTrue(DirtyFlag.LocalPositionBounds); } } @@ -189,7 +189,7 @@ export class TextRenderer extends Renderer implements ICustomClone { set horizontalAlignment(value: TextHorizontalAlignment) { if (this._horizontalAlignment !== value) { this._horizontalAlignment = value; - this._setDirtyFlagTrue(DirtyFlag.RenderDirty); + this._setDirtyFlagTrue(DirtyFlag.LocalPositionBounds); } } @@ -203,7 +203,7 @@ export class TextRenderer extends Renderer implements ICustomClone { set verticalAlignment(value: TextVerticalAlignment) { if (this._verticalAlignment !== value) { this._verticalAlignment = value; - this._setDirtyFlagTrue(DirtyFlag.RenderDirty); + this._setDirtyFlagTrue(DirtyFlag.LocalPositionBounds); } } @@ -217,7 +217,7 @@ export class TextRenderer extends Renderer implements ICustomClone { set enableWrapping(value: boolean) { if (this._enableWrapping !== value) { this._enableWrapping = value; - this._setDirtyFlagTrue(DirtyFlag.RenderDirty); + this._setDirtyFlagTrue(DirtyFlag.LocalPositionBounds); } } @@ -231,7 +231,7 @@ export class TextRenderer extends Renderer implements ICustomClone { set overflowMode(value: OverflowMode) { if (this._overflowMode !== value) { this._overflowMode = value; - this._setDirtyFlagTrue(DirtyFlag.RenderDirty); + this._setDirtyFlagTrue(DirtyFlag.LocalPositionBounds); } } @@ -264,15 +264,14 @@ export class TextRenderer extends Renderer implements ICustomClone { * The bounding volume of the TextRenderer. */ get bounds(): BoundingBox { - const isFontDirty = this._isContainDirtyFlag(DirtyFlag.FontDirty); - const isRenderDirty = this._isContainDirtyFlag(DirtyFlag.RenderDirty); - if (this._transformChangeFlag.flag || isFontDirty || isRenderDirty) { + const isFontDirty = this._isContainDirtyFlag(DirtyFlag.Font); + const isLocalPositionBoundsDirty = this._isContainDirtyFlag(DirtyFlag.LocalPositionBounds); + const isWorldBoundsDirty = this._isContainDirtyFlag(DirtyFlag.WorldBounds); + if (isFontDirty || isLocalPositionBoundsDirty || isWorldBoundsDirty) { isFontDirty && this._resetCharFont(); - isRenderDirty && this._updateData(); - this._updatePosition(); - this._updateBounds(this._bounds); - this._setDirtyFlagFalse(DirtyFlag.FontDirty | DirtyFlag.RenderDirty); - this._transformChangeFlag.flag = false; + isLocalPositionBoundsDirty && this._updateData(); + isWorldBoundsDirty && this._updateBounds(this._bounds); + this._setDirtyFlagFalse(DirtyFlag.Font | DirtyFlag.LocalPositionBounds | DirtyFlag.WorldBounds); } return this._bounds; } @@ -280,7 +279,10 @@ export class TextRenderer extends Renderer implements ICustomClone { constructor(entity: Entity) { super(entity); const { engine } = this; - this._isWorldMatrixDirty = entity.transform.registerWorldChangeFlag(); + this._isWorldMatrixDirty = entity.transform._registerWorldChangeListenser(); + this._isWorldMatrixDirty.listener = () => { + this._setDirtyFlagTrue(DirtyFlag.WorldPosition | DirtyFlag.WorldBounds); + }; this.font = Font.createFromOS(engine); this.setMaterial(engine._spriteDefaultMaterial); } @@ -302,21 +304,22 @@ export class TextRenderer extends Renderer implements ICustomClone { this._setDirtyFlagFalse(DirtyFlag.MaskInteraction); } - const isFontDirty = this._isContainDirtyFlag(DirtyFlag.FontDirty); + const isFontDirty = this._isContainDirtyFlag(DirtyFlag.Font); if (isFontDirty) { this._resetCharFont(); - this._setDirtyFlagFalse(DirtyFlag.FontDirty); + this._setDirtyFlagFalse(DirtyFlag.Font); } - const isRenderDirty = this._isContainDirtyFlag(DirtyFlag.RenderDirty) || isFontDirty; - if (isRenderDirty) { + const isLocalDirty = this._isContainDirtyFlag(DirtyFlag.LocalPositionBounds) || isFontDirty; + if (isLocalDirty) { this._updateData(); - this._setDirtyFlagFalse(DirtyFlag.RenderDirty); + this._setDirtyFlagFalse(DirtyFlag.LocalPositionBounds); } - if (this._isWorldMatrixDirty.flag || isRenderDirty) { + const isWorldDirty = this._isContainDirtyFlag(DirtyFlag.WorldPosition) || isFontDirty; + if (isWorldDirty) { this._updatePosition(); - this._isWorldMatrixDirty.flag = false; + this._setDirtyFlagFalse(DirtyFlag.WorldPosition); } const charRenderDatas = this._charRenderDatas; @@ -556,8 +559,9 @@ export class TextRenderer extends Renderer implements ICustomClone { } export enum DirtyFlag { - RenderDirty = 0x1, - FontDirty = 0x2, - MaskInteraction = 0x4, - All = 0x7 + Font = 0x1, + LocalPositionBounds = 0x2, + WorldPosition = 0x4, + WorldBounds = 0x8, + MaskInteraction = 0x10 } diff --git a/packages/core/src/Transform.ts b/packages/core/src/Transform.ts index a0633b932c..154ba8fd74 100644 --- a/packages/core/src/Transform.ts +++ b/packages/core/src/Transform.ts @@ -3,6 +3,7 @@ import { BoolUpdateFlag } from "./BoolUpdateFlag"; import { deepClone, ignoreClone } from "./clone/CloneManager"; import { Component } from "./Component"; import { Entity } from "./Entity"; +import { ListenerUpdateFlag } from "./ListenerUpdateFlag"; import { UpdateFlagManager } from "./UpdateFlagManager"; /** @@ -545,6 +546,13 @@ export class Transform extends Component { return this._updateFlagManager.createFlag(BoolUpdateFlag); } + /** + * @intenral + */ + _registerWorldChangeListenser(): ListenerUpdateFlag { + return this._updateFlagManager.createFlag(ListenerUpdateFlag); + } + /** * @internal */ From b6ddd969da1d115a6d1b613691140af38dfb6169 Mon Sep 17 00:00:00 2001 From: singlecoder Date: Fri, 8 Jul 2022 15:43:36 +0800 Subject: [PATCH 53/55] feat(text): opt code --- packages/core/src/2d/text/TextRenderer.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/core/src/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts index 867d3a0621..66b511e10e 100644 --- a/packages/core/src/2d/text/TextRenderer.ts +++ b/packages/core/src/2d/text/TextRenderer.ts @@ -310,14 +310,12 @@ export class TextRenderer extends Renderer implements ICustomClone { this._setDirtyFlagFalse(DirtyFlag.Font); } - const isLocalDirty = this._isContainDirtyFlag(DirtyFlag.LocalPositionBounds) || isFontDirty; - if (isLocalDirty) { + if (this._isContainDirtyFlag(DirtyFlag.LocalPositionBounds) || isFontDirty) { this._updateData(); this._setDirtyFlagFalse(DirtyFlag.LocalPositionBounds); } - const isWorldDirty = this._isContainDirtyFlag(DirtyFlag.WorldPosition) || isFontDirty; - if (isWorldDirty) { + if (this._isContainDirtyFlag(DirtyFlag.WorldPosition) || isFontDirty) { this._updatePosition(); this._setDirtyFlagFalse(DirtyFlag.WorldPosition); } From a0114775fc726e126e11245e86f42910f4560d04 Mon Sep 17 00:00:00 2001 From: singlecoder Date: Fri, 8 Jul 2022 15:55:18 +0800 Subject: [PATCH 54/55] feat(text): opt code --- packages/core/src/2d/text/TextRenderer.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/src/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts index 66b511e10e..6157c7203d 100644 --- a/packages/core/src/2d/text/TextRenderer.ts +++ b/packages/core/src/2d/text/TextRenderer.ts @@ -269,7 +269,7 @@ export class TextRenderer extends Renderer implements ICustomClone { const isWorldBoundsDirty = this._isContainDirtyFlag(DirtyFlag.WorldBounds); if (isFontDirty || isLocalPositionBoundsDirty || isWorldBoundsDirty) { isFontDirty && this._resetCharFont(); - isLocalPositionBoundsDirty && this._updateData(); + isLocalPositionBoundsDirty && this._updateLocalData(); isWorldBoundsDirty && this._updateBounds(this._bounds); this._setDirtyFlagFalse(DirtyFlag.Font | DirtyFlag.LocalPositionBounds | DirtyFlag.WorldBounds); } @@ -311,7 +311,7 @@ export class TextRenderer extends Renderer implements ICustomClone { } if (this._isContainDirtyFlag(DirtyFlag.LocalPositionBounds) || isFontDirty) { - this._updateData(); + this._updateLocalData(); this._setDirtyFlagFalse(DirtyFlag.LocalPositionBounds); } @@ -427,7 +427,7 @@ export class TextRenderer extends Renderer implements ICustomClone { } } - private _updateData(): void { + private _updateLocalData(): void { const { color, horizontalAlignment, verticalAlignment, _charRenderDatas: charRenderDatas } = this; const { min, max } = this._localBounds; min.set(0, 0, 0); From 5f013ec6ef232f11837465bdaa0d956318a03aab Mon Sep 17 00:00:00 2001 From: singlecoder Date: Fri, 8 Jul 2022 16:04:31 +0800 Subject: [PATCH 55/55] feat(text): opt code --- packages/core/src/2d/text/TextRenderer.ts | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/packages/core/src/2d/text/TextRenderer.ts b/packages/core/src/2d/text/TextRenderer.ts index 6157c7203d..0b24807043 100644 --- a/packages/core/src/2d/text/TextRenderer.ts +++ b/packages/core/src/2d/text/TextRenderer.ts @@ -485,7 +485,7 @@ export class TextRenderer extends Renderer implements ICustomClone { break; } - for (let j = 0, m = line.length; j < m; ++j) { + for (let j = 0, m = line.length - 1; j <= m; ++j) { const char = line[j]; const charInfo = charFont._getCharInfo(char); @@ -518,18 +518,10 @@ export class TextRenderer extends Renderer implements ICustomClone { charRenderDatas[renderDataCount] = charRenderData; renderDataCount++; - if (i === 0) { - maxY = Math.max(maxY, top); - } - if (i === lastLineIndex) { - minY = Math.min(minY, bottom); - } - if (j === 0) { - minX = Math.min(minX, left); - } - if (j === m - 1) { - maxX = Math.max(maxX, right); - } + i === 0 && (maxY = Math.max(maxY, top)); + i === lastLineIndex && (minY = Math.min(minY, bottom)); + j === 0 && (minX = Math.min(minX, left)); + j === m && (maxX = Math.max(maxX, right)); } startX += charInfo.xAdvance; }