You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
453 lines
14 KiB
453 lines
14 KiB
//*//
|
|
import { cclegacy, Color, Director, director, Game, game, gfx, Material, MeshBuffer, Node, renderer, resources, sp, Texture2D, UIRenderer } from 'cc';
|
|
import { EDITOR, JSB } from 'cc/env';
|
|
|
|
|
|
let _uid = 0;
|
|
let _texid = 0;
|
|
let _hash = -1;
|
|
let _texCount = 0;
|
|
let _spineCache = [];
|
|
|
|
//是否使用,shader传参变色效果
|
|
//如不需要,请设置false , 节省shader计算
|
|
const useShaderVar = true;
|
|
declare module "cc" {
|
|
interface Component {
|
|
shaderVar: number;
|
|
// 类型,0 不变,1 置灰,2闪白?,3待定?,4待定?
|
|
// 参数整数部分为类型,小数部分为插值,
|
|
// 例如 1.999,代表是 ,近似置灰100%
|
|
// 例如 2.5 代表是,闪白50%插值
|
|
// 默认原色 <=1
|
|
}
|
|
}
|
|
|
|
let _spineTexture: Texture2D = null;
|
|
let _spineBuffers: Float32Array = null;
|
|
let _texMaps: Map<number, number> = new Map();
|
|
let _materialMaps: Map<number, any> = new Map();
|
|
|
|
|
|
const _region = new gfx.BufferTextureCopy();
|
|
_region.texExtent.width = 128;
|
|
_region.texExtent.height = 128;
|
|
_region.texOffset.x = 0;
|
|
_region.texOffset.y = 0;
|
|
|
|
//@ts-ignore
|
|
Material.prototype.spineCache = [];
|
|
export const SpineBatch: any = {
|
|
enable: false,
|
|
parent: null,
|
|
next: function () {
|
|
_texMaps.clear();
|
|
_texCount = 0;
|
|
_texid = 0;
|
|
_hash = -1;
|
|
},
|
|
reset: function () {
|
|
this.next();
|
|
_uid = 0;
|
|
},
|
|
active: function () {
|
|
return this.enable && this.parent;
|
|
},
|
|
|
|
update: function () {
|
|
|
|
if (!this.active()) return;
|
|
// _spineTexture?.uploadData(_spineBuffers);
|
|
for (let value of _materialMaps.values()) {
|
|
if (value) {
|
|
value.n = 0;
|
|
}
|
|
}
|
|
|
|
if (_uid > 0 && _spineTexture) {
|
|
//@ts-ignore
|
|
const gfxDevice = _spineTexture._getGFXDevice();
|
|
const gfxTexture = _spineTexture.getGFXTexture();
|
|
|
|
const hight = (~~((_uid * 4 - 1) / 128)) + 1;
|
|
_region.texExtent.height = hight > 128 ? 128 : hight;
|
|
gfxDevice.copyBuffersToTexture([_spineBuffers], gfxTexture, [_region]);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
|
|
const _getMaterialForBlendAndTint = function (comp: any, mat: any) {
|
|
|
|
// _mat = mat; //保存当前
|
|
let key = _hash = mat.hash;
|
|
let caches = _materialMaps.get(key);
|
|
if (caches === undefined) {
|
|
caches = { mats: [], n: 0 };
|
|
_materialMaps.set(key, caches);
|
|
}
|
|
|
|
let inc = caches.n++;
|
|
let materialCache = caches.mats;
|
|
let inst: any = materialCache[inc];
|
|
if (inst !== undefined) {
|
|
_spineCache = inst.spineCache;
|
|
return inst;
|
|
}
|
|
|
|
let type = key & 0xf;
|
|
let src = (key >> 4) & 0xf;
|
|
let dst = (key >> 8) & 0xf;
|
|
|
|
const material = SpineBatch.parent;
|
|
|
|
inst = new renderer.MaterialInstance({ parent: material });
|
|
|
|
materialCache[inc] = inst;
|
|
|
|
inst.overridePipelineStates({
|
|
blendState: {
|
|
blendColor: Color.WHITE,
|
|
targets: [{
|
|
blendEq: gfx.BlendOp.ADD,
|
|
blendAlphaEq: gfx.BlendOp.ADD,
|
|
blendSrc: src,
|
|
blendDst: dst,
|
|
blendSrcAlpha: src,
|
|
blendDstAlpha: dst,
|
|
}],
|
|
},
|
|
});
|
|
|
|
inst.recompileShaders({ TWO_COLORED: (type === 1) ? true : false, USE_LOCAL: false, USE_SHADER_VAR: useShaderVar });
|
|
|
|
inst.setProperty('spineData', _spineTexture);
|
|
|
|
_spineCache = inst['spineCache'] = [];
|
|
|
|
inst.addRef();
|
|
|
|
return inst;
|
|
}
|
|
|
|
|
|
//预加载多纹理材质
|
|
const loadMaterial = function () {
|
|
if (SpineBatch.parent) return;
|
|
resources.load("multSpines/Mult-spine", Material, (err, material) => {
|
|
if (!err) {
|
|
//material.passes[0].tryCompile();
|
|
SpineBatch.parent = material;
|
|
material
|
|
material.addRef();
|
|
}
|
|
});
|
|
}
|
|
|
|
const initSpineDatas = function () {
|
|
|
|
SpineBatch.enable = false;
|
|
|
|
const size = 128;
|
|
const gfxDevice = director.root!.device;
|
|
const hasFeatureFloatTexture = gfxDevice.getFormatFeatures(gfx.Format.RGBA32F) & gfx.FormatFeatureBit.SAMPLED_TEXTURE;
|
|
|
|
if (hasFeatureFloatTexture) {
|
|
|
|
SpineBatch.enable = true;
|
|
|
|
_spineTexture = new Texture2D();
|
|
_spineBuffers = new Float32Array(size * size * 4);
|
|
_spineTexture.setFilters(Texture2D.Filter.NEAREST, Texture2D.Filter.NEAREST);
|
|
_spineTexture.reset({ width: size, height: size, format: Texture2D.PixelFormat.RGBA32F, mipmapLevel: 0 });
|
|
_spineTexture.uploadData(_spineBuffers);
|
|
|
|
}
|
|
}
|
|
|
|
const injectSpine = function () {
|
|
|
|
const Skeleton: any = sp.Skeleton.prototype;
|
|
Skeleton.shaderVar = 0; //shader自定义参数
|
|
Skeleton.isMult = false; //是否多纹理合批
|
|
|
|
//强制关闭合批
|
|
Object.defineProperty(Skeleton, "enableBatch", {
|
|
get: function () {
|
|
if (this.customMaterial)
|
|
return this._enableBatch;
|
|
return false;
|
|
},
|
|
set: function (value: boolean) {
|
|
if (this.customMaterial) {
|
|
if (value !== this._enableBatch) {
|
|
this._enableBatch = value;
|
|
this._updateBatch();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
|
|
//获取共享材质
|
|
const getMaterialForBlendAndTint = Skeleton.getMaterialForBlendAndTint;
|
|
Skeleton.getMaterialForBlendAndTint = function (src: any, dst: any, type: any) {
|
|
|
|
if (!SpineBatch.active() || this.customMaterial) {
|
|
this.isMult = false;
|
|
return getMaterialForBlendAndTint.call(this, src, dst, type);
|
|
}
|
|
|
|
let hash = type | (src << 4) | (dst << 8);
|
|
|
|
this.isMult = true;
|
|
|
|
return { hash: hash };
|
|
}
|
|
|
|
//绘画数据处理
|
|
const render = Skeleton._render;
|
|
Skeleton._render = function (batcher: any): void {
|
|
|
|
if (!this.isMult) return render.call(this, batcher);
|
|
|
|
|
|
let indicesCount = 0;
|
|
if (this.renderData && this._drawList.length > 0) {
|
|
const rd = this.renderData;
|
|
const chunk = rd.chunk;
|
|
const accessor = chunk.vertexAccessor;
|
|
const meshBuffer = rd.getMeshBuffer()!;
|
|
const origin = meshBuffer.indexOffset;
|
|
|
|
|
|
//@ts-ignore
|
|
let floatStride = meshBuffer.vertexFormatBytes >> 2;
|
|
let isMultDraw = this._drawList.length > 1;
|
|
let offset = chunk.vertexOffset;
|
|
let length = rd.vertexCount;
|
|
let vbuff = rd.chunk.vb;
|
|
let ibuff = rd.indices;
|
|
let isCommit = false;
|
|
|
|
// Fill index buffer
|
|
for (let i = 0; i < this._drawList.length; i++) {
|
|
const dc = this._drawList.data[i];
|
|
if (dc.texture) {
|
|
batcher.commitMiddleware(this, meshBuffer, origin + dc.indexOffset, dc.indexCount, dc.texture, dc.material, this._enableBatch);
|
|
isCommit = true;
|
|
|
|
if (isMultDraw) {
|
|
let j = dc.indexOffset;
|
|
length = dc.indexCount;
|
|
let id = _uid * 10 + _texid;
|
|
while (length--) {
|
|
vbuff[(ibuff[j++] - offset) * floatStride + 2] = id; //-offset
|
|
}
|
|
}
|
|
}
|
|
indicesCount += dc.indexCount;
|
|
}
|
|
|
|
if (!isMultDraw && isCommit) {
|
|
let id = _uid * 10 + _texid;
|
|
for (let i = 0, j = 0; i < length; i++) {
|
|
vbuff[j + 2] = id;
|
|
j += floatStride;
|
|
}
|
|
}
|
|
|
|
|
|
if (isCommit) {
|
|
let i = 16 * _uid++;
|
|
let datas = _spineBuffers;
|
|
let m = this.node.worldMatrix;
|
|
datas[i] = m.m00;
|
|
datas[i + 1] = m.m01;
|
|
datas[i + 2] = m.m02;
|
|
datas[i + 3] = this.shaderVar; //m.m03;
|
|
datas[i + 4] = m.m04;
|
|
datas[i + 5] = m.m05;
|
|
datas[i + 6] = m.m06;
|
|
datas[i + 7] = m.m07;
|
|
datas[i + 8] = m.m08;
|
|
datas[i + 9] = m.m09;
|
|
datas[i + 10] = m.m10;
|
|
datas[i + 11] = m.m11;
|
|
datas[i + 12] = m.m12;
|
|
datas[i + 13] = m.m13;
|
|
datas[i + 14] = m.m14;
|
|
datas[i + 15] = m.m15;
|
|
}
|
|
const subIndices = rd.indices!.subarray(0, indicesCount);
|
|
accessor.appendIndices(chunk.bufferId, subIndices);
|
|
accessor.getMeshBuffer(chunk.bufferId).setDirty();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//多纹理合批流程
|
|
const injectBatch = function () {
|
|
|
|
const Batcher2D: any = cclegacy.internal.Batcher2D.prototype;
|
|
const commitMiddleware = Batcher2D.commitMiddleware;
|
|
Batcher2D.commitMiddleware = function (
|
|
comp: UIRenderer,
|
|
meshBuffer: MeshBuffer,
|
|
indexOffset: number,
|
|
indexCount: number,
|
|
tex: any,
|
|
mat: any,
|
|
enableBatch: boolean,
|
|
) {
|
|
|
|
//@ts-ignore
|
|
if (!comp.isMult) {
|
|
return commitMiddleware.call(this, comp, meshBuffer, indexOffset, indexCount, tex, mat, enableBatch);
|
|
}
|
|
|
|
// check if need merge draw batch
|
|
const texture = tex.getGFXTexture();
|
|
|
|
let tid = texture.objectID;
|
|
_texid = _texMaps.get(tid);
|
|
let isFlush = (_texid === undefined && _texCount >= 8);
|
|
|
|
|
|
if (!isFlush && this._middlewareBuffer === meshBuffer && _hash === mat.hash
|
|
&& this._middlewareIndexStart + this._middlewareIndexCount === indexOffset
|
|
&& this._currLayer === comp.node.layer) {
|
|
this._middlewareIndexCount += indexCount;
|
|
} else {
|
|
this.autoMergeBatches(this._currComponent!);
|
|
this.resetRenderStates();
|
|
|
|
this._currComponent = comp;
|
|
this._currTexture = texture;
|
|
this._currSampler = tex.getGFXSampler();
|
|
this._currTextureHash = tex.getHash();
|
|
this._currLayer = comp.node.layer;
|
|
this._currSamplerHash = this._currSampler.hash;
|
|
this._currHash = 0;
|
|
this._currTransform = null;//enableBatch ? null : comp.node;
|
|
|
|
this._middlewareEnableBatch = false;//enableBatch;
|
|
this._middlewareBuffer = meshBuffer;
|
|
this._middlewareIndexStart = indexOffset;
|
|
this._middlewareIndexCount = indexCount;
|
|
|
|
this._currMaterial = _getMaterialForBlendAndTint(comp, mat);
|
|
|
|
}
|
|
|
|
this._currIsMiddleware = true;
|
|
if (_texCount === 0 || _texid === undefined) {
|
|
_texid = _texCount++;
|
|
_texMaps.set(tid, _texid);
|
|
}
|
|
|
|
|
|
if (_spineCache[_texid] !== tid) {
|
|
_spineCache[_texid] = tid;
|
|
this._currMaterial.setProperty("texture" + _texid, tex);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
const mergeBatchesForMiddleware = Batcher2D.mergeBatchesForMiddleware;
|
|
Batcher2D.mergeBatchesForMiddleware = function (renderComp: UIRenderer) {
|
|
if (this._currIsMiddleware) SpineBatch.next();
|
|
mergeBatchesForMiddleware.call(this, renderComp);
|
|
}
|
|
}
|
|
|
|
|
|
//游戏启动前,务必加载多纹理材质
|
|
game.once(Game.EVENT_GAME_INITED, () => {
|
|
if (EDITOR || JSB || !sp.Skeleton) return;
|
|
initSpineDatas();
|
|
if (SpineBatch.enable) {
|
|
loadMaterial();
|
|
injectSpine();
|
|
injectBatch();
|
|
}
|
|
});
|
|
|
|
game.on(Game.EVENT_RESTART, () => {
|
|
if (EDITOR || JSB || !sp.Skeleton) return;
|
|
if (SpineBatch.enable) {
|
|
SpineBatch.parent = null;
|
|
loadMaterial();
|
|
}
|
|
});
|
|
|
|
director.on(Director.EVENT_AFTER_DRAW, (dt) => { SpineBatch.reset(); });
|
|
director.on(Director.EVENT_BEFORE_COMMIT, (dt) => { SpineBatch.update(); });
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//以下代码,仅供测试demo使用,请不要在项目中使用
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
const changeBatch = function (node: Node, batch: boolean) {
|
|
if (!node) return;
|
|
let skeleton = node.getComponent(sp.Skeleton);
|
|
if (skeleton) {
|
|
skeleton.enableBatch = batch;
|
|
skeleton.material = null;
|
|
//@ts-ignore
|
|
skeleton._updateBatch();
|
|
}
|
|
|
|
let childs = node.children;
|
|
for (let i = 0; i < childs.length; i++) {
|
|
changeBatch(childs[i], batch);
|
|
}
|
|
}
|
|
|
|
export const switchMultSpine = function (useMult: boolean, useBatch: boolean) {
|
|
|
|
SpineBatch.enable = useMult;
|
|
const Skeleton: any = sp.Skeleton.prototype;
|
|
|
|
if (!useMult) {
|
|
Object.defineProperty(Skeleton, "enableBatch", {
|
|
get: function () {
|
|
return this._enableBatch;
|
|
},
|
|
set: function (value: boolean) {
|
|
if (value !== this._enableBatch) {
|
|
this._enableBatch = value;
|
|
this._updateBatch();
|
|
}
|
|
}
|
|
});
|
|
} else {
|
|
Object.defineProperty(Skeleton, "enableBatch", {
|
|
get: function () {
|
|
if (this.customMaterial)
|
|
return this._enableBatch;
|
|
return false;
|
|
},
|
|
set: function (value: boolean) {
|
|
if (this.customMaterial) {
|
|
if (value !== this._enableBatch) {
|
|
this._enableBatch = value;
|
|
this._updateBatch();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
let scene = director.getScene() as Node;
|
|
if (scene) {
|
|
changeBatch(scene, useBatch);
|
|
}
|
|
|
|
}
|
|
|
|
// */
|
|
|