论坛里有不少关于drawcall优化的方案,看到一个水友发的方案:
UI渲染合批 - Creator 3.x - Cocos中文社区
这个方案原理是给节点增加一个depth属性,然后在渲染batcher-2d.ts的walk方法中,按照depth重新对渲染排序,根据depth从小到大进行渲染。
例如原本渲染顺序如下,drawcall=4

采用depth方案后,给2个sprite设置depth为0

给2个label设置depth为1

那么渲染顺序变成如下,2个sprite为第一第二,2个label为第三第四,drawcall由原来的4变成2。

一部分代码如下:
/**
* 按指定的depth渲染
* @param a
* @param b
* @returns
*/
private sortRenderFunc(a: UIRenderer | UIMeshRenderer, b: UIRenderer | UIMeshRenderer) {
return a.depth - b.depth
}
private customFillBuffers() {
let renders = this._delayFillRenderers;
if(renders.length > 0) {
renders.sort(this.sortRenderFunc)
let render: UIRenderer
for(let i = 0, len = renders.length; i < len; i++) {
render = renders[i]
// Render assembler update logic
if (render.enabledInHierarchy) {
render.fillBuffers(this);// for rendering
}
let uiProps = render.node._uiProps
if(uiProps.colorDirty) {
if (!render.useVertexOpacity && render.renderData && render.renderData.vertexCount > 0) {
let opacity = render && render.color ? render.color.a / 255 : 1;
opacity *= uiProps.opacity;
// HARD COUPLING
updateOpacity(render.renderData, opacity);
const buffer = render.renderData.getMeshBuffer();
if (buffer) {
buffer.setDirty();
}
}
uiProps.colorDirty = false
}
}
renders.length = 0
}
}
public walk (node: Node, level = 0): void {
if (!node.activeInHierarchy) {
return;
}
const children = node.children;
const uiProps = node._uiProps;
const render = uiProps.uiComp as UIRenderer;
if(node.isBatchRoot) {
this.customFillBuffers();
this._batchRootDepth++;
}
// Save opacity
const parentOpacity = this._pOpacity;
let opacity = parentOpacity;
// TODO Always cascade ui property's local opacity before remove it
const selfOpacity = render && render.color ? render.color.a / 255 : 1;
this._pOpacity = opacity *= selfOpacity * uiProps.localOpacity;
// TODO Set opacity to ui property's opacity before remove it
uiProps.setOpacity(opacity);
if (!approx(opacity, 0, EPSILON)) {
if (uiProps.colorDirty) {
// Cascade color dirty state
this._opacityDirty++;
uiProps.colorDirty = false
}
if(render) {
if(this._batchRootDepth && !node.isBatchRoot) { //mask要马上填充
//延迟填充数据
if(this._opacityDirty) {
uiProps.colorDirty = true
}
this._delayFillRenderers.push(render)
} else {
// Render assembler update logic
if (render && render.enabledInHierarchy) {
render.fillBuffers(this);// for rendering
}
// Update cascaded opacity to vertex buffer
if (this._opacityDirty && !render.useVertexOpacity && render.renderData && render.renderData.vertexCount > 0) {
// HARD COUPLING
updateOpacity(render.renderData, opacity);
const buffer = render.renderData.getMeshBuffer();
if (buffer) {
buffer.setDirty();
}
}
}
}
if (children.length > 0 && !node._static) {
for (let i = 0; i < children.length; ++i) {
const child = children[i];
this.walk(child, level);
}
}
if (uiProps.colorDirty) {
// Reduce cascaded color dirty state
this._opacityDirty--;
// Reset color dirty
//uiProps.colorDirty = false;
}
}
if(node.isBatchRoot) {
this.customFillBuffers();
this._batchRootDepth--;
}
// Restore opacity
this._pOpacity = parentOpacity;
// Post render assembler update logic
// ATTENTION: Will also reset colorDirty inside postUpdateAssembler
if (render && render.enabledInHierarchy) {
render.postUpdateAssembler(this);
if ((render.stencilStage === Stage.ENTER_LEVEL || render.stencilStage === Stage.ENTER_LEVEL_INVERTED)
&& (StencilManager.sharedManager!.getMaskStackSize() > 0)) {
this.autoMergeBatches(this._currComponent!);
this.resetRenderStates();
StencilManager.sharedManager!.exitMask();
}
}
level += 1;
}
这个方案是侵入式的,需要修改源码,web和小游戏改ts,原生改c++。
用winmerge工具对比源码和修改后的源码,还是改了不少地方。
具体代码获取看原帖:UI渲染合批 - Creator 3.x - Cocos中文社区

实践使用中,例如一个场景或弹窗,根节点绑定一个SetBatchRoot.s,表明这个根节点下所有子节点需要按照depth排序。

背景图、来自通用大图集的图片默认depth=0就行,label都设置为depth=10,1-9预留用于灵活设置。
使用起来还是挺方便的,一个原来100+的背包界面可以优化到10以下。
浙公网安备 33010602011771号