Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2D渲染时, 某些情况下会莫名其妙的断批 #17365

Closed
finscn opened this issue Jul 13, 2024 · 19 comments
Closed

2D渲染时, 某些情况下会莫名其妙的断批 #17365

finscn opened this issue Jul 13, 2024 · 19 comments
Assignees
Labels
Bug Needs Triage Needs to be assigned by the team

Comments

@finscn
Copy link
Contributor

finscn commented Jul 13, 2024

Cocos Creator version

3.8.3

System information

all

Issue description

现象:
两个本来是可以合批, 在一个dc里被绘制的sprite节点 莫名其妙的断批了.

首先 我在2D渲染DC优化方面有一定经验, 所以可以排除低级错误 (比如 不能合批的 被我误以为可以合批).

查看了一下dc信息, 发现断批时会出现下图中的情况

image image

这两个dc指令的参数一模一样, 且都使用的同一个 texture, 但是中间插入了一次 bindVertexArrayOES , 导致断批 (或者说是因为断批 才多执行了一次 bindVertexArrayOES , 具体cocos 3内部如何处理的我没太明白)

Relevant error log output

No response

Steps to reproduce

如上

Minimal reproduction project

No response

@finscn finscn added Bug Needs Triage Needs to be assigned by the team labels Jul 13, 2024
@finscn
Copy link
Contributor Author

finscn commented Aug 4, 2024

@finscn
Copy link
Contributor Author

finscn commented Aug 4, 2024

image

图中 橙色 和 蓝色 两个区域的 drawElement 就是理论上可以合批的.

我查看了两次drawElement 的参数.
除了顶点数据外, 唯一的差异就是 第一个 drawElement 的 BlendState里多了一个 validCommandIds 参数, 不知道这个是什么原因引起的.

image

另外有一个 uniforms 参数 的 Location 不一样.
但是值一样 (都是 value 9), 所用的贴图也是同一张.

name: cc_spriteTexture
size: 1
type: SAMPLER_2D
location: WebGLUniformLocation - ID: 130
value: 9

除此之外, 其他参数都一致.

@yoki0805
Copy link
Contributor

yoki0805 commented Aug 7, 2024

可能和 MeshBuffer 的機制有關, 2D 渲染對象會優先使用同一個 MeshBuffer 的空間
但在釋放 2D 渲染對象後, 會對 MeshBuffer 造成一定的碎片空間
當下一個 2D 渲染對象在 MeshBuffer 找不到足夠的空間使用時, 系統會再建立另一個 MeshBuffer 導致合批被斷開

這邊的管理比較複雜, 可能需要官方想辦法處理
例如發現空間不足時, 先將當前 MeshBuffer 進行整理以騰出更大的空間
若還是不足才建立新的 MeshBuffer

@finscn
Copy link
Contributor Author

finscn commented Sep 12, 2024

@minggo 这个问题你们官方有复现吗? 有解决方案或者优化建议吗?

@yoki0805
Copy link
Contributor

yoki0805 commented Nov 8, 2024

我最近也遇到這個問題, 研究了下引擎的寫法
導致 Batch 被打斷的主要原因是 UIRenderer.renderData 的 chunk 被分配到不同的 buffer 上
會發生這現象的原因有很多種, 我的情況是因為 2D 物件經常被隨機生成和複用
當 2D 物件被渲染時會申請一塊 chunk, 由於是在遊戲過程中生成的, 有可能當前 buffer 不足而使用新的 buffer
這樣就會導致連續的 2D 可合批的物件, 因 chunk 被分配到不同的 buffer 上 Batch 被打斷了

此外複用時, 放入節點池 renderData 的 chunk 並不會被釋放 (生命週期和渲染組件綁定)
當從節點池拿出 2D 物件時, 無法確保這些 2D 物件的 chunk 都在同個 buffer 上, draw calls 也會變高

目前我的解法是:

  1. 稍微加大 buffer 大小
  2. 在 2D 物件從節點池拿出時, 強制重新申請一塊 chunk 並複製舊內容到新 chunk 中
    這樣有較大的機會會分配在同一個 buffer 上

a. 需要自己寫個組件掛在節點上
WeChat 截圖_20241108151444

b. 重新申請 chunk 並複製舊內容到新 chunk 中的方式
WeChat 截圖_20241108151425

目前實測起來, draw calls 有不錯的下降程度
但目前 renderData 被標記為內部使用, 未來有可能被棄用, 風險再自行評估
@finscn

@finscn
Copy link
Contributor Author

finscn commented Nov 11, 2024

@yoki0805 万分感谢. 希望引擎组能看到.

@minggo
Copy link
Contributor

minggo commented Nov 11, 2024

这个问题我们已经注意到了,后续会优化。

@GengineJS
Copy link
Contributor

有没有复现demo

@yoki0805
Copy link
Contributor

給予 2D 物件隨機的存在時長, 用於模擬遊玩過程的生成與刪減
在大量物件的情況下, 複用時從物件池取出會無法保證這些物件的頂點資料都在相同 Buffer 中
Demo: batch-break.zip

@GengineJS

@GengineJS
Copy link
Contributor

給予 2D 物件隨機的存在時長, 用於模擬遊玩過程的生成與刪減 在大量物件的情況下, 複用時從物件池取出會無法保證這些物件的頂點資料都在相同 Buffer 中 Demo: batch-break.zip

@GengineJS

好的

@GengineJS
Copy link
Contributor

这个问题我们看了,因为我们的ui合批方案采用的是网格合批,顶点数组的默认长度是4096,当使用相同资源的ui节点超出这个范围就会新建chunk,这个时候就会新增drawcall,可以适当增加该范围,但是并不一定越大越好。demo里会突然暴增是因为使用的是NodePool,NodePool是会回收使用,而不会销毁,这会导致MeshBuffer已经分配的数据依旧存在chunk中,系统只能继续新增chunk,应该使用node的destroy方法,每帧才会重新计算chunk。
另外网格合批的方案并不适合频繁增删物体的情况,是否有必要对UI新增instancing支持,我们内部需要详细评估下。如果频繁增删的物体是简单的四边形,我建议用Quad,放在UI中,并对Quad使用gpu instancing,这时候可以绕过UI对GPU instancing的支持

@yoki0805
Copy link
Contributor

yoki0805 commented Nov 19, 2024

Demo 只是對實際情況做了簡化的模擬
以 '吸血鬼倖存者' 類型的遊戲舉例, 怪物和子彈都是帶有動畫的 2D 渲染物件,
並且會有渲染合批和物件複用的需求, GPU instancing 在處理 2D 動畫的 UV 更新上不是那麼方便

也許我們只需要一個能手動清除 chunk 數據的 API,
在放入節點池前由開發者主動呼叫, 當下次被取出並放入場景時, 再透過引擎自動分配即可?

@GengineJS
Copy link
Contributor

GengineJS commented Nov 20, 2024

image
如果一定得用NodePool,可以通过我截图得方式,在put的时候释放renderdata,这时候系统会自动利用空闲的chunk,在get的时候需要调用下_flushAssembler,以便重算renderdata。
这是这边我运行10分钟后drawcall的情况,还是保持2个drawcall,中间会少量增加,最后会回到2
image
如果把回收调用put的时长固定为1,会一直保持为2个drawcall,这是因为回收与再利用的频率大体一致

                        .delay(1).call(() => {
                            // 放入 Pool 不會釋放 RenderData, 銷毀才釋放 RenderData
                            // 若改使用 lucy.destroy() 則 DrawCalls 不會暴漲
                            // 或者在 Lucy 節點上掛載 RenderDataChunkMover 組件也可以達到相同效果
                            this.lucyPool.put(lucy);
                            // 回收后的node,需要销毁renderdata,以便系统更好的利用chunk
                            lucy.getComponent(Sprite).destroyRenderData();
                            // lucy.destroy();
                        })
                        .start();```

@yoki0805
Copy link
Contributor

嗯, 這樣就已經滿好用的
接著看未來版本如何更好地公開 _flushAssembler 方法給開發者了, 謝謝!

@finscn
Copy link
Contributor Author

finscn commented Nov 21, 2024

@GengineJS 但是我的情况和 @yoki0805 的略有不同, 我没有用到 对象池. 只是单纯的一堆东西画在屏幕上. 我这种情况无解了吗 ?

@GengineJS
Copy link
Contributor

GengineJS commented Nov 21, 2024

@GengineJS 但是我的情况和 @yoki0805 的略有不同, 我没有用到 对象池. 只是单纯的一堆东西画在屏幕上. 我这种情况无解了吗 ?

一堆东西是什么?有多少数量?是只新增不删除么?评论沟通方式成本很高,尽量一次性把问题描述清楚下

@finscn
Copy link
Contributor Author

finscn commented Nov 21, 2024

@GengineJS 但是我的情况和 @yoki0805 的略有不同, 我没有用到 对象池. 只是单纯的一堆东西画在屏幕上. 我这种情况无解了吗 ?

一堆东西是什么?有多少数量?是只新增不删除么?评论沟通方式成本很高,尽量一次性把问题描述清楚下.

  • 一堆2d sprite , 大概 3000多个, 都在同屏里.
  • 只新增 不删除, 但是有隐藏 (active = false )
  • 用了动态合图 , 所以这些精灵用到的图片 大概4张动态图集.
  • 粗略估算了一下, 理想状态 dc 应该在 50以下, 但是因为会出现莫名其妙的断批, 实际在 130以上

@GengineJS
Copy link
Contributor

GengineJS commented Nov 21, 2024

@finscn 先用我这个pr试下,#17900
还是不行的话,得排查下,先确保共用一张动态图集,看drawcall降下来得程度,如果依旧很高,适当扩容下chunk,还是不行,给个demo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug Needs Triage Needs to be assigned by the team
Projects
None yet
Development

No branches or pull requests

5 participants