From 6816546edb39321714e08ad5332995dfabdc6f03 Mon Sep 17 00:00:00 2001 From: SmallMain Date: Tue, 21 Jun 2022 10:31:13 +0800 Subject: [PATCH 04/15] =?UTF-8?q?sp=E3=80=81=E5=A4=9A=E7=BA=B9=E7=90=86?= =?UTF-8?q?=E6=B8=B2=E6=9F=93=20-=20=E5=9F=BA=E7=A1=80=E6=A0=B8=E5=BF=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- engine/cocos2d/core/asset-manager/builtins.js | 44 +++++- engine/cocos2d/core/assets/CCTexture2D.js | 43 +++++- .../core/assets/material/CCMaterial.js | 36 +++++ engine/cocos2d/core/index.js | 2 + engine/cocos2d/core/sp/index.js | 3 + engine/cocos2d/core/sp/multi-batcher.ts | 74 +++++++++ engine/cocos2d/core/sp/multi-handler.ts | 146 ++++++++++++++++++ engine/cocos2d/core/sp/sp.js | 86 +++++++++++ 8 files changed, 432 insertions(+), 2 deletions(-) create mode 100644 engine/cocos2d/core/sp/index.js create mode 100644 engine/cocos2d/core/sp/multi-batcher.ts create mode 100644 engine/cocos2d/core/sp/multi-handler.ts create mode 100644 engine/cocos2d/core/sp/sp.js diff --git a/engine/cocos2d/core/asset-manager/builtins.js b/engine/cocos2d/core/asset-manager/builtins.js index ae8cca7..f794369 100644 --- a/engine/cocos2d/core/asset-manager/builtins.js +++ b/engine/cocos2d/core/asset-manager/builtins.js @@ -80,10 +80,52 @@ var builtins = { } this._loadBuiltins('effect', () => { - this._loadBuiltins('material', cb); + this._loadBuiltins('material', () => { + this._loadBuiltinsSP(cb); + }); + }); + }, + + _loadBuiltinsSP(cb) { + cc.sp.MAX_MULTITEXTURE_NUM = 8; + // cc.renderer.device.caps.maxTextureUnits + + this._loadMultiEffect('multi-2d-sprite', (effect) => { + cc.sp.multi2dSpriteEffectAsset = effect; + effect.addRef(); + cc.sp.inited = true; + cc.sp.multiBatcher.init(); + + cb(); }); }, + _loadMultiEffect(name, cb) { + if (CC_EDITOR) { + cc.assetManager.loadAny(Editor.assetdb.remote.urlToUuid('db://service-pack-resources/sp/effects/' + name + '.effect'), function (err, effect) { + if (err) { + return Editor.error(err); + } else { + cb(effect); + } + }); + } else { + cc.assetManager.loadBundle('sp', (err, bundle) => { + if (err) { + cc.error(err); + } else { + bundle.load('effects/' + name, cc.EffectAsset, (err, effect) => { + if (err) { + cc.error(err); + } else { + cb(effect); + } + }); + } + }); + } + }, + /** * !#en * Get the built-in asset using specific type and name. diff --git a/engine/cocos2d/core/assets/CCTexture2D.js b/engine/cocos2d/core/assets/CCTexture2D.js index 3863538..e823c45 100644 --- a/engine/cocos2d/core/assets/CCTexture2D.js +++ b/engine/cocos2d/core/assets/CCTexture2D.js @@ -470,6 +470,9 @@ var Texture2D = cc.Class({ if (CC_EDITOR) { this._exportedExts = null; } + + // multi batcher + this._multiMaterial = null; }, /** @@ -688,6 +691,8 @@ var Texture2D = cc.Class({ } this._packable && cc.dynamicAtlasManager && cc.dynamicAtlasManager.deleteAtlasTexture(this); + this.unlinkMaterial(); + this._image = null; this._texture && this._texture.destroy(); this._super(); @@ -1050,7 +1055,43 @@ var Texture2D = cc.Class({ else { cb(); } - } + }, + + linkMaterial(material, index) { + const handler = material.getMultiHandler(); + if (handler) { + if (index == null) { + if (handler.autoSetTexture(this) === -1) { + return false; + } + } else { + handler.setTexture(index, this); + } + this.unlinkMaterial(); + this._multiMaterial = material; + return true; + } else { + return false; + } + }, + + unlinkMaterial() { + if (this._multiMaterial) { + const handler = this._multiMaterial.getMultiHandler(); + const _texture = this.getImpl(); + handler.removeTexture(_texture); + this._multiMaterial = null; + } + }, + + getLinkedMaterial() { + return this._multiMaterial; + }, + + hasLinkedMaterial() { + return !!this._multiMaterial; + }, + }); /** diff --git a/engine/cocos2d/core/assets/material/CCMaterial.js b/engine/cocos2d/core/assets/material/CCMaterial.js index e0249b3..2251347 100644 --- a/engine/cocos2d/core/assets/material/CCMaterial.js +++ b/engine/cocos2d/core/assets/material/CCMaterial.js @@ -72,6 +72,7 @@ let Material = cc.Class({ this._manualHash = false; this._dirty = true; this._effect = null; + this._multiHandler = null; }, properties: { @@ -124,6 +125,8 @@ let Material = cc.Class({ } this._effect = this._effectAsset.getInstantiatedEffect(); + + this.updateMultiSupport(); } }, @@ -140,6 +143,7 @@ let Material = cc.Class({ set (v) { this._techniqueIndex = v; this._effect.switchTechnique(v); + this.updateMultiSupport(); } } }, @@ -401,7 +405,39 @@ let Material = cc.Class({ } } + this.updateMultiSupport(); + if (this._multiHandler) this._multiHandler.syncTextures(); + }, + + updateMultiSupport() { + const passes = this._effect.technique.passes; + if (passes.length > 0 && passes[0].getDefine("USE_MULTI_TEXTURE")) { + this.setMultiSupport(true); + } else { + this.setMultiSupport(false); + } }, + + isMultiSupport() { + return !!this._multiHandler; + }, + + setMultiSupport(bool) { + if (bool) { + if (this._multiHandler) { + this._multiHandler.syncTextures(); + } else { + this._multiHandler = new cc.sp.MultiHandler(this); + } + } else if (!bool) { + this._multiHandler = null; + } + }, + + getMultiHandler() { + return this._multiHandler; + }, + }); export default Material; diff --git a/engine/cocos2d/core/index.js b/engine/cocos2d/core/index.js index d7e79d1..a0eda10 100644 --- a/engine/cocos2d/core/index.js +++ b/engine/cocos2d/core/index.js @@ -40,6 +40,8 @@ if (!CC_EDITOR || !Editor.isMainProcess) { require('./physics'); require('./camera/CCCamera'); require('./geom-utils'); + + require('./sp'); } require('./mesh'); diff --git a/engine/cocos2d/core/sp/index.js b/engine/cocos2d/core/sp/index.js new file mode 100644 index 0000000..fa17f6f --- /dev/null +++ b/engine/cocos2d/core/sp/index.js @@ -0,0 +1,3 @@ +require('./sp'); +require('./multi-handler'); +require('./multi-batcher'); diff --git a/engine/cocos2d/core/sp/multi-batcher.ts b/engine/cocos2d/core/sp/multi-batcher.ts new file mode 100644 index 0000000..dd18ab2 --- /dev/null +++ b/engine/cocos2d/core/sp/multi-batcher.ts @@ -0,0 +1,74 @@ +import { MultiHandler } from "./multi-handler"; + + +/** + * 多纹理合批器 + */ +export class MultiBatcher { + + /** + * 多纹理材质管理器数组 + */ + handlers: MultiHandler[] = []; + + /** + * 有空槽的材质 + */ + nextHandler!: MultiHandler; + + + /** + * 初始化 + */ + init() { + const handler = new MultiHandler(); + this.handlers.push(handler); + this.nextHandler = handler; + } + + + /** + * 传入 cc.Texture2D,会关联并返回一个多纹理材质,如果已经有关联的材质则会返回已关联的材质 + */ + requsetMaterial(texture: any) { + if (!texture._multiMaterial) { + let handler = this.nextHandler; + let index = handler.getEmptyIndex(); + if (index === -1) { + // 没有空位,尝试在已有 handlers 里查找 + for (const _handler of this.handlers) { + index = _handler.getEmptyIndex(); + if (index !== -1) { + handler = _handler; + this.nextHandler = handler; + break; + } + } + + // 已有的没有空位,创建新材质 + if (index === -1) { + handler = new MultiHandler(); + this.handlers.push(handler); + this.nextHandler = handler; + index = 0; + } + } + + texture.linkMaterial(handler.material, index); + } + return texture._multiMaterial; + } + + + /** + * 重置多纹理材质数组,再次使用请先初始化 + */ + reset() { + this.handlers.length = 0; + } + +} + + +cc.sp.multiBatcher = new MultiBatcher(); +cc.sp.MultiBatcher = MultiBatcher; diff --git a/engine/cocos2d/core/sp/multi-handler.ts b/engine/cocos2d/core/sp/multi-handler.ts new file mode 100644 index 0000000..664876e --- /dev/null +++ b/engine/cocos2d/core/sp/multi-handler.ts @@ -0,0 +1,146 @@ +/** + * 多纹理 Material 管理类 + */ +export class MultiHandler { + + /** + * 材质 + */ + material: any; + + /** + * Texture 数组 + * + * 注意:不是 cc.Texture2D + */ + protected textures: any[] = []; + + /** + * 有空槽(缓存值,并不是完全正确,只是为了降低当材质没有空槽时避免数组遍历的性能消耗) + */ + protected hasEmptySlot: boolean = false; + + + constructor(material?) { + if (material) { + this.material = material; + } else { + this.material = (cc.Material as any).create(cc.sp.multi2dSpriteEffectAsset); + this.material.name = "multi-2d-sprite"; + this.material.define('USE_TEXTURE', true); + this.material.define('USE_MULTI_TEXTURE', true); + } + this.material._multiHandler = this; + this.syncTextures(); + } + + + /** + * 同步 Material 的纹理插槽数据 + * + * 当自行设置插槽可调用此函数同步数组 + */ + syncTextures() { + const effect = this.material['effect']; + const properties = effect.passes[0]._properties; + + this.textures[0] = properties.texture.value; + this.textures[1] = properties.texture2.value; + this.textures[2] = properties.texture3.value; + this.textures[3] = properties.texture4.value; + this.textures[4] = properties.texture5.value; + this.textures[5] = properties.texture6.value; + this.textures[6] = properties.texture7.value; + this.textures[7] = properties.texture8.value; + + // refresh has empty slot state + this.hasEmptySlot = true; + this.getEmptyIndex(); + } + + + /** + * 设置纹理插槽(提供 cc.Texture2D) + */ + setTexture(index: number, texture: any) { + this.textures[index] = texture ? texture.getImpl() : null; + this.material.setProperty(cc.sp.propertyIndex2Name(index), texture); + if (texture == null) this.hasEmptySlot = true; + } + + + /** + * 移除指定纹理 + * + * 注意:不是 cc.Texture2D + */ + removeTexture(texture: any) { + const index = this.getIndex(texture); + if (index !== -1) { + this.setTexture(index, null); + } + } + + + /** + * 纹理是否在插槽中 + * + * 注意:不是 cc.Texture2D + */ + hasTexture(texture: any) { + return this.textures.indexOf(texture) !== -1; + } + + + /** + * 获取纹理在插槽中的 Index,没有返回 -1 + * + * 注意:不是 cc.Texture2D + */ + getIndex(texture: any) { + return this.textures.indexOf(texture); + } + + + /** + * 获取指定 index 中的纹理 + * + * 注意:不是 cc.Texture2D + */ + getTexture(index: number) { + return this.textures[index]; + } + + + /** + * 获取空插槽 Index,没有返回 -1 + */ + getEmptyIndex() { + if (!this.hasEmptySlot) return -1; + const index = this.textures.indexOf(null); + if (index !== -1) { + return index; + } else { + this.hasEmptySlot = false; + return -1; + } + } + + + /** + * 自动设置纹理到空插槽,返回插槽下标,失败返回 -1(提供 cc.Texture2D) + */ + autoSetTexture(texture: any) { + const index = this.getEmptyIndex(); + if (index === -1) { + return -1; + } + + this.setTexture(index, texture); + return index; + } + +} + + +cc.sp.MultiHandler = MultiHandler; diff --git a/engine/cocos2d/core/sp/sp.js b/engine/cocos2d/core/sp/sp.js new file mode 100644 index 0000000..956a003 --- /dev/null +++ b/engine/cocos2d/core/sp/sp.js @@ -0,0 +1,86 @@ +cc.sp = { + + /** + * 是否初始化完成 + */ + inited: false, + + /** + * 版本号 + */ + version: "1.0.0", + + /** + * 最大纹理插槽数量 + * + * 固定为 8 + */ + MAX_MULTITEXTURE_NUM: -1, + + /** + * 渲染组件是否默认自动切换至贴图关联的材质 + */ + autoSwitchMaterial: true, + + /** + * 渲染组件是否默认参与动态合图 + */ + allowDynamicAtlas: true, + + /** + * Label 组件是否默认启用渲染时进行缩放以适配高 DPI 屏幕 + */ + enableLabelRetina: true, + + /** + * Label 组件渲染时进行缩放的缩放比例 + */ + labelRetinaScale: 1, + + /** + * Char 图集会进行自动多纹理合批的数量 + */ + charAtlasAutoBatchCount: 1, + + /** + * Char 图集是否在场景切换时清空 + */ + charAtlasAutoResetBeforeSceneLoad: true, + + /** + * 内置的多纹理合批 Effect Asset + */ + multi2dSpriteEffectAsset: null, + + /** + * property index to name map + */ + i2nMap: ['texture'], + + /** + * property name to index map + */ + n2iMap: { texture: 0 }, + + /** + * property index to name + */ + propertyIndex2Name(index) { + return this.i2nMap[index]; + }, + + /** + * property name to index + */ + propertyName2Index(name) { + return this.n2iMap[name]; + }, + +}; + +// 初始化 +for (let i = 1; i < 8; i++) { + const name = "texture" + (i + 1); + cc.sp.i2nMap[i] = name; + cc.sp.n2iMap[name] = i; +} -- 2.32.0 (Apple Git-132)