angle追加折腾笔记
之前在angle折腾笔记里提到恢复angle作为ES3模拟器,方便PC上查BUG。当时为了模拟EXT_copy_image
使用了Vulkan Backend,别的用起来都挺好,但是很蛋疼的一点是RenderDoc抓帧的API其实都是vlk的… 这礼拜查别的BUG的时候,索性痛定思痛尝试使用GLES Backend。
这里记录下补全功能以及修复了若干Angle/RenderDoc相关问题的笔记…
depot_tools更新
无脑先拉了最新的Angle代码,结果老方法遭遇开幕雷击orz…看了下log是因为新版本的depot_tools不在自带ninja二进制文件了,所以编译一堆失败。
这个很好处理,去ninja拉一份最新的exe,然后设置进系统路径就行。
Angle部分修改
按照之前经验,编译出libEGL.dll和libGLESv2.dll两个文件,测了下打开GLES Backend可以直接运行起来,但也发现了效果错误+闪退问题。
glCopySubImage
补全
之前在angle折腾笔记里也提到,暂时只有Vulkan这个Backend支持了跨格式拷贝贴图的功能(虽然这块其实也有点小瑕疵,见CopyImageSubData in Vulkan Backend,但本地改改、糊弄一个可以用的版本很快)。
调试了下ES Backend下的调用堆栈(pdb在手真香),确实是没有实现对应逻辑:
angle::Result TextureImpl::copyTextureSubData(const gl::Context *context, |
这时候先用OpenGL hardware capability viewer确认了下这个扩展其实是存在的。
这样就好办了——索性自己实现一发TextureGL::copyTextureSubData
,对应调用的函数塞在BlitGL::copyImageSubData
里面即可,加起来差不多也就十多行。
Extensions
补完逻辑之后发现游戏运行起来,还是自动禁用了这块功能。继续调试下,发现原因是GLES 3.1 Context下的Extension列表中压根没有GL_EXT_copy_image
,所以引擎认为当前RHI下这个功能是不可用的。
翻了翻ANGLE生成扩展列表相关逻辑,它其实是通过检查当前Backend Device情况,来决定可以支持多少GLES Extensions的。所以在renderergl_utils.cpp里补一行:
extensions->copyImageEXT = functions->hasGLESExtension("GL_EXT_copy_image"); |
从而告诉ANGLE此时是可以支持copyImageEXT
,自此引擎对应功能就正常了。
EXT or not?
这个也是之前用Vulkan Backend就发现的问题: 打开RenderDoc后会优先使用非EXT版本的API,导致glCopyImageSubDataEXT
调用被劫持成glCopyImageSubData
。
我之前给RenderDoc提过split code path for glCopyImageSubDataEXT&glCopyImageSubData,但是作者的回复还是比较有道理的——在GLES 3.1 Context的情况下,ANGLE其实就不应该返回glCopyImageSubData
这个接口(虽然里面是空实现)。
临时解决方案是ANGLE里后面会提到更加釜底抽薪的解决方案。ValidateCopyImageSubData
里的ES 3.2限制降到3.1。
glProgramBinary
闪退
关掉RenderDoc之后发现启动时有一定概率闪退,看了下堆栈是崩在了glProgramBinary
中(其实开了RenderDoc就没问题也很有意思,回头有机会展开)
libGLESv2.dll!rx::ProgramGL::uniLoc(int glLocation) Line 148 C++ |
看了下这块代码,然后在ANGLE的test case里用现有的ProgramBinaryUniformResetCase
改了改,发现能稳定复现了这个问题: 加载shader binary cache时,压根没考虑es 3.1下的uniform sampler binding point…
给官方提了glProgramBinary crash with unifor binding point,回复的非常快但是吐槽无力…两年前就有人提过这个事情还没修(难怪我搜glProgramBinary
没找到这个issue…)
本来想直接禁用shader binary cache绕开,后来发现glLinkProgram
的时候ANGLE内部也会尝试复用Program,导致一样的闪退。
Fix Attemp 1
一开始非常临时的方案糊弄了下:在setUniformValuesFromBindingQualifiers
调用时候检查mUniformRealLocationMap
为空就跳过,至少不会闪退了。
但是跑测时候发现有概率导致加载的shader的uniform sampler信息丢失(毕竟被跳过了orz) ,渲染效果出错。
Fix Attemp 2
索性重新梳理了下这块逻辑,特别是对比了下正常创建Shader的行为: 本质问题其实是postResolveLink
必须保证在link
之后调用。
定位到这点之后就好改了,索性把postResolveLink
从deserialize
里挪出,放到后面合适的位置调用即可。
更新: 官方repo上也有人处理掉了Do not call postResolveLink in Program::deserialize.,倒是和我前面的方案一致。
RenderDoc Replay
Runtime部分到目前已经比较稳定了,接下来要处理的是配合RenderDoc使用时发现的两个问题。
贴图错乱
使用RenderDoc Capture&Replay会发现,有些贴图的数据完全错乱了,然而在运行时是完全正常的。临时用PVR对比了下(右)倒是一切正常的。
具体分析了下,发现内容错误的贴图都是非压缩格式,而压缩贴图则全员良好,于是构造了一个极简例子来分析。
Debug Attemp 1
首先需要确认下是Capture过程中出的问题还是Replay的问题。
一开始我想的突破口是找到Replay阶段是如何从rdc中读取数据并创建贴图的。翻了下gl_texture_funcs.cpp里的WrappedOpenGL::Serialise_glTextureImage2DEXT
实现,打断点确实能命中同时包括width/height/format等参数都对的上。
于是我尝试把这块数据直接dump下来看看内容
if (width == 1024 && height == 128) |
发现不管是pvr还是angle的rdc,对应的数据居然是分毫不差的…
为了从别的角度证明或者证伪,我还掏出apitrace直接去抓RenderDoc的Replay过程中GL API,发现调用的参数和二进制数据确实分毫不差…
场面非常尴尬: 明明这里调用的参数和数据一模一样,为什么结果会不一样?
ps. 当时为了验证没找错地方(这里要加粗的原因是当时的我有多么自信,后来就有多么哭笑不得orz)
- 断点确实命中且只命中一次,因为构造的test case里只有一张未压缩纹理
- 如果注释掉这行、确实Replay里看不到对应这张纹理了。
Debug Attemp 2s
陷入僵局之后,果断找朋友进行小黄鸭调试…在此感谢阿兰和群主的各种协助和讨论:
- 确认
glBindTexture
和glActiveTexture
- 确认
GL_PIXEL_UNPACK_BUFFER=0
及GL_UNPACK_ALIGNMENT=1
- 确认slot是对的情况下进行的各种设置
而且Replay过程已经和Angle/PVR无关了,是RenderDoc直接调用的Desktop OpenGL。
这时候突然阿兰提到: 如果故意在前面的GL.glTextureImage2DEXT
中传入空指针作为数据会怎么样? 试了下发现居然对应纹理的内容没变。
这就得到一个结论: 我前面找到的那个接口不是真正起决定性的地方,肯定还有别的接口在填充数据orz。
Debug Attemp 3
确认自己前面走进误区之后反而好办,重新定位了下相关函数,找到了真·填充数据的口子在gl_initstate.cpp的GLResourceManager::Apply_InitialState
里。
老样子在GL.glTextureSubImage2DEXT
调用前把scratchBuf
保存到文件,对比确实数据不一致——而且用IrfanView直接打开RAW,确实就是那张错误贴图。
至此明确了Replay部分其实是没问题的,问题转化到Capture时是如何获取scratchBuf
数据的。
在Capture过程挂断点跑进GL.glGetTexImage(targets[trg], i, fmt, type, scratchBuf);
,立刻发现问题所在: gl_emulated.cpp的_glGetTexImage
实现需要获取贴图的大小
GLint width = 0, height = 0, depth = 0; |
但是Angle认为这个调用是不合法的,所以导致后面RenderDoc压根没有真正去glReadPixels
获取数据,scratchBuf
就是纯纯脏数据。
Fix
定位到这里之后可以说是老眼熟了…不就和前面提到的ValidateCopyImageSubData
一回事么…
这次想着全局禁用检查得了,看了下Context.cpp实现,应该只需要创建的时候带上EGL_CONTEXT_OPENGL_NO_ERROR_KHR
属性。
然而试了下是无效的…怒而下断点,结果发现这次是egl_hooks.cpp在使坏
if(name == EGL_CONTEXT_OPENGL_NO_ERROR_KHR) |
这个注释好有道理导致我无言以对emmm
算了算了,那我就在Angle代码里mSkipValidation(true)
写死,反正也没法直接用官方版本dll。
glCopyImageSubData
效果错误
这个问题我其实当年提交supporting full glCopyImageSubData的时候就已知,只不过后面一直没空去填坑…这次准备一起搞一下orz
问题本质是分帧、分块调用glCopyImageSubDataEXT
时,RenderDoc需要一个机制来获取对应的数据。如果数据来源是某张压缩纹理,那么RD本身就其保存在了compressedData
里面,直接读取即可;对于非压缩的情况,就暂时没有机制解决(这个对应的就是RGBA32UI->ETC/BC3这种GPU Compress的情况)。
后来抽空把这块补全了下,结果提交complete glCopyImageSubData
时候反而断断续续根据review修改花了一周,总算也填了一个两年前的坑orz