From adb32f5c23622fcf3adc84480a1ae8f2e84057f3 Mon Sep 17 00:00:00 2001 From: SmallMain Date: Sat, 25 Jun 2022 00:54:38 +0800 Subject: [PATCH] =?UTF-8?q?[engine]=20=E6=96=B0=20Char=20=E7=BC=93?= =?UTF-8?q?=E5=AD=98=E6=A8=A1=E5=BC=8F=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- engine/cocos2d/core/asset-manager/builtins.js | 2 +- engine/cocos2d/core/components/CCLabel.js | 4 + .../core/renderer/utils/label/bmfont.js | 7 +- .../core/renderer/utils/label/letter-font.js | 400 ++++++++++++++++-- .../webgl/assemblers/label/2d/bmfont.js | 6 +- 5 files changed, 368 insertions(+), 51 deletions(-) diff --git a/engine/cocos2d/core/asset-manager/builtins.js b/engine/cocos2d/core/asset-manager/builtins.js index ea87ebed..668ca0ff 100644 --- a/engine/cocos2d/core/asset-manager/builtins.js +++ b/engine/cocos2d/core/asset-manager/builtins.js @@ -95,7 +95,7 @@ var builtins = { effect.addRef(); cc.sp.inited = true; cc.sp.multiBatcher.init(); - if (cc.dynamicAtlasManager.maxAtlasCount === -1) cc.dynamicAtlasManager.maxAtlasCount = cc.sp.MAX_MULTITEXTURE_NUM; + if (cc.dynamicAtlasManager.maxAtlasCount === -1) cc.dynamicAtlasManager.maxAtlasCount = Math.max(0, cc.sp.MAX_MULTITEXTURE_NUM - cc.sp.charAtlasAutoBatchCount); cb(); }); }, diff --git a/engine/cocos2d/core/components/CCLabel.js b/engine/cocos2d/core/components/CCLabel.js index cc2a7fc7..cd66e724 100644 --- a/engine/cocos2d/core/components/CCLabel.js +++ b/engine/cocos2d/core/components/CCLabel.js @@ -483,6 +483,10 @@ let Label = cc.Class({ if (oldValue === CacheMode.CHAR) { this._ttfTexture = null; + const material = this._materials[0]; + if (material && material.material && cc.Label._shareAtlas.material === material.material) { + this.setMaterial(0, this._getDefaultMaterial()); + } } if (!this.enabledInHierarchy) return; diff --git a/engine/cocos2d/core/renderer/utils/label/bmfont.js b/engine/cocos2d/core/renderer/utils/label/bmfont.js index fa07c1cb..ccce272b 100644 --- a/engine/cocos2d/core/renderer/utils/label/bmfont.js +++ b/engine/cocos2d/core/renderer/utils/label/bmfont.js @@ -339,6 +339,8 @@ export default class BmfontAssembler extends Assembler2D { index += tokenLen; } //end of for loop + this._finishMultilineTextWrap(); + _linesWidth.push(letterRight); _numberOfLines = lineIndex + 1; @@ -638,7 +640,7 @@ export default class BmfontAssembler extends Assembler2D { if (_tmpRect.height > 0 && _tmpRect.width > 0) { let isRotated = this._determineRect(_tmpRect); let letterPositionX = letterInfo.x + _linesOffsetX[letterInfo.line]; - this.appendQuad(_comp, texture, _tmpRect, isRotated, letterPositionX - appx, py - appy, _bmfontScale); + this.appendQuad(_comp, texture, _tmpRect, isRotated, letterPositionX - appx, py - appy, _bmfontScale, letterDef); } } this._quadsUpdated(_comp); @@ -727,8 +729,9 @@ export default class BmfontAssembler extends Assembler2D { updateWorldVerts() {} - appendQuad (comp, texture, rect, rotated, x, y, scale) {} + appendQuad(comp, texture, rect, rotated, x, y, scale, letter) {} _quadsUpdated (comp) {} _reserveQuads () {} + _finishMultilineTextWrap() { } } diff --git a/engine/cocos2d/core/renderer/utils/label/letter-font.js b/engine/cocos2d/core/renderer/utils/label/letter-font.js index 5e46af03..a0766aee 100644 --- a/engine/cocos2d/core/renderer/utils/label/letter-font.js +++ b/engine/cocos2d/core/renderer/utils/label/letter-font.js @@ -24,6 +24,7 @@ ****************************************************************************/ import WebglBmfontAssembler from '../../webgl/assemblers/label/2d/bmfont'; +import { vfmtPosUvColorTexId } from '../../webgl/vertex-format'; const Label = require('../../../components/CCLabel'); const LabelOutline = require('../../../components/CCLabelOutline'); @@ -118,36 +119,90 @@ LetterTexture.prototype = { this._texture.handleLoadedTexture(); }, - destroy () { + destroy() { + this._texture._packable = false; this._texture.destroy(); this._texture = null; Label._canvasPool.put(this._data); }, } -function LetterAtlas (width, height) { +function LetterAtlas(atlases, width, height) { let texture = new RenderTexture(); texture.initWithSize(width, height); texture.update(); - this._fontDefDictionary = new FontAtlas(texture); - + this._atlases = atlases; + this._texture = texture; + this._id = 0; + this._tmpId = -1; + this._x = space; this._y = space; this._nexty = space; + this.frees = []; + this.waitCleans = []; + this._width = width; this._height = height; - - cc.director.on(cc.Director.EVENT_BEFORE_SCENE_LAUNCH, this.beforeSceneLoad, this); } cc.js.mixin(LetterAtlas.prototype, { - insertLetterTexture (letterTexture) { + insertLetterTexture(letterTexture) { let texture = letterTexture._texture; - let width = texture.width, height = texture.height; + let width = texture.width, height = texture.height; + + // 先寻找是否有可用的被回收的区域 + if (this.frees.length > 0) { + let score = Number.MAX_VALUE; + let areaFit = 0; + let original = null; + let originalIndex = 0; + + for (let i = 0; i < this.frees.length; i++) { + const freeLetter = this.frees[i]; + if (freeLetter._width === width && freeLetter._height === height) { + areaFit = freeLetter._width * freeLetter._height - width * height; + if (areaFit < score) { + original = freeLetter; + originalIndex = i; + score = areaFit; + } + } + } + + if (original) { + original._hash = letterTexture._hash; + original.w = letterTexture._width - bleed; + original.h = letterTexture._height - bleed; + original.xAdvance = original.w; + original.offsetY = letterTexture._offsetY; + + this._texture.drawTextureAt(texture, original.u - bleed / 2, original.v - bleed / 2); + + this._dirty = true; + + this.removeFreeLetter(originalIndex); + + this._atlases._fontDefDictionary.addLetterDefinitions(letterTexture._hash, original); + return original; + } + } + + // 有 bleed 问题,暂时不能复用不同高宽的空间 + // 矫正宽度为三档: <0.75x height <1x height >1x height + // if (width <= height * 0.75) { + // width = height * 0.75; + // } else if (width <= height) { + // width = height; + // } + + // 没有可用的被回收区域,尝试直接插入 + const oldx = this._x, oldy = this._y, oldnexty = this._nexty; if ((this._x + width + space) > this._width) { + // TODO 跳到下一行之前将这行的剩余区域切成多个正方形并放入 frees,避免浪费 this._x = space; this._y = this._nexty; } @@ -157,33 +212,69 @@ cc.js.mixin(LetterAtlas.prototype, { } if (this._nexty > this._height) { - return null; + this._x = oldx; + this._y = oldy; + this._nexty = oldnexty; + + // 回收 waitCleans + if (this.waitCleans.length > 0) { + for (const letter of this.waitCleans) { + letter._inCleans = false; + if (letter.ref === 0) { + delete this._atlases._fontDefDictionary._letterDefinitions[letter._hash]; + this.frees.push(letter); + } + } + this.waitCleans.length = 0; + return this.insertLetterTexture(letterTexture); + } else { + return null; + } } - this._fontDefDictionary._texture.drawTextureAt(texture, this._x, this._y); + this._texture.drawTextureAt(texture, this._x, this._y); this._dirty = true; let letter = new FontLetterDefinition(); - letter.u = this._x + bleed/2; - letter.v = this._y + bleed/2; - letter.texture = this._fontDefDictionary._texture; + letter.u = this._x + bleed / 2; + letter.v = this._y + bleed / 2; + letter.texture = this._texture; + letter.atlas = this; + letter.ref = 0; letter.valid = true; letter.w = letterTexture._width - bleed; letter.h = letterTexture._height - bleed; + letter._inCleans = false; + letter._hash = letterTexture._hash; + letter._width = width; + letter._height = height; letter.xAdvance = letter.w; letter.offsetY = letterTexture._offsetY; this._x += width + space; - this._fontDefDictionary.addLetterDefinitions(letterTexture._hash, letter); + this._atlases._fontDefDictionary.addLetterDefinitions(letterTexture._hash, letter); return letter }, + pushFreeLetter(letter) { + const i = this.frees.push(letter) - 1; + }, + + removeFreeLetter(index) { + const temp = this.frees[index]; + const temp2 = this.frees[this.frees.length - 1]; + // temp2.cacheIndex = index; + // temp.cacheIndex = -1; + this.frees[index] = temp2; + this.frees.pop(); + }, + update () { if (!this._dirty) return; - this._fontDefDictionary._texture.update(); + this._texture.update(); this._dirty = false; }, @@ -192,47 +283,148 @@ cc.js.mixin(LetterAtlas.prototype, { this._y = space; this._nexty = space; - let chars = this._fontDefDictionary._letterDefinitions; - for (let i = 0, l = chars.length; i < l; i++) { - let char = chars[i]; - if (!char.isValid) { - continue; + const defs = this._atlases._fontDefDictionary._letterDefinitions; + for (const key in defs) { + const def = defs[key]; + if (def.atlas === this) { + delete defs[key]; } - char.destroy(); } - this._fontDefDictionary.clear(); + this.frees.length = 0; + this.waitCleans.length = 0; }, destroy () { this.reset(); - this._fontDefDictionary._texture.destroy(); - this._fontDefDictionary._texture = null; + const handler = this._atlases.material.getMultiHandler(); + handler.removeTexture(this._texture); + this._texture.destroy(); + this._texture = null; }, - beforeSceneLoad () { - this.clearAllCache(); - }, +}); - clearAllCache () { - this.destroy(); +class LetterAtlases { - let texture = new RenderTexture(); - texture.initWithSize(this._width, this._height); - texture.update(); - - this._fontDefDictionary._texture = texture; - }, + /** + * 图集数组 + */ + atlases = []; - getLetter (key) { + /** + * Char 多纹理材质 + */ + material = null; + + /** + * Fake MaterialVariant + */ + fakeMaterial = { material: null }; + + /** + * 抽象图集 + */ + _fontDefDictionary = new FontAtlas(null); + + + constructor() { + const handler = new cc.sp.MultiHandler(); + this.material = handler.material; + this.fakeMaterial.material = this.material; + + cc.director.on(cc.Director.EVENT_BEFORE_SCENE_LAUNCH, this.beforeSceneLoad, this); + } + + + insertLetterTexture(letterTexture) { + for (const atlas of this.atlases) { + const letter = atlas.insertLetterTexture(letterTexture); + if (letter) { + return letter; + } + } + + if (this.atlases.length >= 8) { + return null; + } else { + const atlas = new LetterAtlas(this, _atlasWidth, _atlasHeight); + const len = this.atlases.push(atlas); + atlas._id = len - 1; + const handler = this.material.getMultiHandler(); + handler.setTexture(atlas._id, atlas._texture); + if (!CC_EDITOR && cc.sp.charAtlasAutoBatchCount >= len) { + cc.sp.multiBatcher.requsetMaterial(atlas._texture); + } + return atlas.insertLetterTexture(letterTexture); + } + } + + + deleteLetter(letter) { + letter.ref--; + if (letter.ref === 0 && !letter._inCleans) { + letter._inCleans = true; + letter.atlas.waitCleans.push(letter); + } + } + + + update() { + for (const atlas of this.atlases) { + atlas.update(); + } + } + + + reset() { + this._fontDefDictionary.clear(); + + for (const atlas of this.atlases) { + atlas.reset(); + } + } + + + destroy() { + this._fontDefDictionary.clear(); + + for (const atlas of this.atlases) { + atlas.destroy(); + } + + this.atlases.length = 0; + } + + + beforeSceneLoad() { + if (cc.sp.charAtlasAutoResetBeforeSceneLoad) { + this.clearAllCache(); + } + } + + + clearAllCache() { + this.reset(); + } + + + getTexture() { + if (!_emptyTexture) { + _emptyTexture = new RenderTexture(); + _emptyTexture.initWithSize(_atlasWidth, _atlasHeight); + _emptyTexture.update(); + } + return _emptyTexture; + } + + + getLetter(key) { return this._fontDefDictionary._letterDefinitions[key]; - }, + } - getTexture () { - return this._fontDefDictionary.getTexture(); - }, - getLetterDefinitionForChar: function(char, labelInfo) { + getLetterDefinitionForChar(char, labelInfo) { let hash = char.charCodeAt(0) + labelInfo.hash; let letter = this._fontDefDictionary._letterDefinitions[hash]; if (!letter) { @@ -242,12 +434,50 @@ cc.js.mixin(LetterAtlas.prototype, { temp.destroy(); } + if (letter && _firstTraverse) { + letter.ref++; + _assembler._letterRefs.push(letter); + this.checkMaterialAndUpdateTexId(letter); + } + return letter; } -}); + + + checkMaterialAndUpdateTexId(letter) { + const atlas = letter.atlas; + const comp = _assembler._renderComp; + + // 检查是否需要自动切换材质 + if (_needCheckMaterial) { + _needCheckMaterial = false; + if (_usedMaterial.material !== _shareAtlas.material) { + _assembler.checkAndSwitchMaterial(comp, atlas._texture, _usedMaterial); + _usedMaterial = comp._materials[0]; + } + } + + // 检查是否需要更新 atlas tmpId,使用内置材质则不检查 + if (_usedMaterial.material !== _shareAtlas.material && atlas._tmpId === -1) { + const handler = _usedMaterial.material.getMultiHandler(); + if (handler) { + const index = handler.getIndex(atlas._texture.getImpl()); + if (index !== -1) { + atlas._tmpId = index; + return; + } + } + + // 如果无法在材质中找到 texture,则切换至内置材质 + comp.setMaterial(0, _shareAtlas.material); + _usedMaterial = _shareAtlas.fakeMaterial; + } + } + +} function computeHash (labelInfo) { - let hashData = ''; + let hashData = '|'; let color = labelInfo.color.toHEX(); let out = ''; if (labelInfo.isOutlined && labelInfo.margin > 0) { @@ -262,11 +492,31 @@ let _shareAtlas = null; let _atlasWidth = 2048; let _atlasHeight = 2048; let _isBold = false; +let _usedMaterial = null; +let _needCheckMaterial = false; +let _firstTraverse = false; +let _assembler = null; +let _emptyTexture = null; export default class LetterFontAssembler extends WebglBmfontAssembler { + _letterRefs = []; + + initData() { + let data = this._renderData; + data.createFlexData(0, this.verticesCount, this.indicesCount, this.getVfmt()); + } + + getVfmt() { + return vfmtPosUvColorTexId; + } + + getBuffer() { + return cc.renderer._handle.getBuffer("mesh", this.getVfmt()); + } + _getAssemblerData () { if (!_shareAtlas) { - _shareAtlas = new LetterAtlas(_atlasWidth, _atlasHeight); + _shareAtlas = new LetterAtlases(); cc.Label._shareAtlas = _shareAtlas; } @@ -310,4 +560,64 @@ export default class LetterFontAssembler extends WebglBmfontAssembler { _determineRect (tempRect) { return false; } -} \ No newline at end of file + + _aftUpdateRenderData(comp) { + // 还原 tex id 与当前使用材质 + _assembler = this; + _usedMaterial = _assembler._renderComp._materials[0]; + _needCheckMaterial = true; + _firstTraverse = true; + for (const atlas of _shareAtlas.atlases) { + atlas._tmpId = -1; + } + + // 还原 letterRef + this._recycleLetterRef(); + + super._aftUpdateRenderData(comp); + + _usedMaterial = null; + _assembler = null; + } + + _finishMultilineTextWrap() { + _firstTraverse = false; + } + + _recycleLetterRef() { + for (const letter of this._letterRefs) { + _shareAtlas.deleteLetter(letter); + } + this._letterRefs.length = 0; + } + + _resetAssemblerData(assemblerData) { + if (this._letterRefs.length !== 0) { + this._recycleLetterRef(); + } + } + + appendVerts(comp, offset, l, r, b, t, letter) { + super.appendVerts(comp, offset, l, r, b, t, letter); + + // update texId + const renderData = this._renderData; + const verts = renderData.vDatas[0]; + const floatsPerVert = this.floatsPerVert; + let texIdOffset = offset + this.texIdOffset; + const id = _usedMaterial.material !== _shareAtlas.material ? letter.atlas._tmpId : letter.atlas._id; + + verts[texIdOffset] = id; + texIdOffset += floatsPerVert; + verts[texIdOffset] = id; + texIdOffset += floatsPerVert; + verts[texIdOffset] = id; + texIdOffset += floatsPerVert; + verts[texIdOffset] = id; + } + +} + +LetterFontAssembler.prototype.floatsPerVert = 6; +LetterFontAssembler.prototype.texIdOffset = 5; +LetterFontAssembler.prototype.isMulti = true; diff --git a/engine/cocos2d/core/renderer/webgl/assemblers/label/2d/bmfont.js b/engine/cocos2d/core/renderer/webgl/assemblers/label/2d/bmfont.js index be7d330f..db1904a2 100644 --- a/engine/cocos2d/core/renderer/webgl/assemblers/label/2d/bmfont.js +++ b/engine/cocos2d/core/renderer/webgl/assemblers/label/2d/bmfont.js @@ -65,7 +65,7 @@ export default class WebglBmfontAssembler extends BmfontAssembler { return comp.node._color._val; } - appendQuad (comp, texture, rect, rotated, x, y, scale) { + appendQuad(comp, texture, rect, rotated, x, y, scale, letter) { let renderData = this._renderData; let verts = renderData.vDatas[0], uintVerts = renderData.uintVDatas[0]; @@ -131,7 +131,7 @@ export default class WebglBmfontAssembler extends BmfontAssembler { b = y - rectHeight * scale; t = y; - this.appendVerts(comp, _dataOffset, l, r, b, t); + this.appendVerts(comp, _dataOffset, l, r, b, t, letter); // colors let colorOffset = _dataOffset + this.colorOffset; @@ -143,7 +143,7 @@ export default class WebglBmfontAssembler extends BmfontAssembler { _dataOffset += this.floatsPerVert * 4; } - appendVerts (comp, offset, l, r, b, t) { + appendVerts(comp, offset, l, r, b, t, letter) { let local = this._local; let floatsPerVert = this.floatsPerVert;