RenderDoc 源码分析:OpenGL 资源监控
做渲染相关的开发,离不开图形调试工具。Android Studio 的 AGI 和 Xcode 的 Instruments 都提供了各自平台的相关能力。
若是做跨平台渲染,或追求更高阶的调试能力,需要用到 RenderDoc。
支持几乎所有系统平台(Windows/Linux/Android/NintendoSwitch);
支持几乎所有图形后端(OpenGL/Direct3D/Vulkan);
支持主流游戏引擎(Unity/Unreal),有插件提供;
不仅提供了基本的资源监控、断点调试、耗时分析等功能,还支持 Replay、查看 Pipeline 状态等。
虽然官方未明确说支持 iOS/MacOS 及 Metal,但能找到相关代码,且近一个月内还有提交,很可能正处于开发中。
既然 RenderDoc 是开源的,对于一个有追求的程序员,必须得“知其所以然”。
本篇先聊聊基本的资源监控,以 Apple 平台的 OpenGL 为例。
抓手在哪?
如果你做过内存监控相关的 APM 工作,可能对 Hook 有所了解:
dlsym 将内存分配/释放函数指向新的地址;
新的函数实现里插入自己的监控逻辑。
RenderDoc 也是类似的操作。
apple_hook.cpp 实现 Apple 平台通用的 Hook:
void LibraryHooks::EndHookRegistration() {
for (auto it = libraryCallbacks.begin(); it != libraryCallbacks.end(); ++it) {
//...
void *handle = dlopen(libName.c_str(), RTLD_NOW | RTLD_GLOBAL);
//...
}
for (FunctionHook &hook : functionHooks) {
//...
*hook.orig = dlsym(RTLD_NEXT, hook.function.c_str());
//...
}
}
gl_hooks.cpp 实现对 OpenGL 库的 Hook:
#if ENABLED(RDOC_WIN32)
const char *libraryName = "opengl32.dll";
#elif ENABLED(RDOC_ANDROID)
const char *libraryName = "libEGL.so";
#elif ENABLED(RDOC_APPLE)
const char *libraryName = "/System/Library/Frameworks/OpenGL.framework/Libraries/libGL.dylib";
#else
const char *libraryName = "libGL.so.1";
#endif
LibraryHooks::RegisterLibraryHook(libraryName, &GLHooked);
//...
void SetDriverForHooks(WrappedOpenGL *driver) {
glhook.driver = driver;
}
从 SetDriverForHooks()
方法能看到,真正的实现是 WrappedOpenGL
,位于 gl_driver.h。
apple_gl_hook_defs.h 定义了 Apple 平台需要 Hook 的 OpenGL 方法:
#define ForEachAppleSupported() \
APPLE_FUNC(glActiveShaderProgram); \
APPLE_FUNC(glActiveTexture); \
APPLE_FUNC(glAttachShader); \
gl_driver.h 中还声明了上面这些 OpenGL 方法的实现:
IMPLEMENT_FUNCTION_SERIALISED(void, glActiveShaderProgram, GLuint pipeline, GLuint program);
IMPLEMENT_FUNCTION_SERIALISED(void, glActiveTexture, GLenum texture);
IMPLEMENT_FUNCTION_SERIALISED(void, glAttachShader, GLuint program, GLuint shader);
由于 Hook 本身不是本文讨论的重点,这里不做详细展开。
资源状态机
resource_manager.h 定义了资源的状态流转状态机:
// +------------------ NONE -----------------------------+
// | | |
// read partialWrite completeWrite
// | | |
// V V V
// READ PARTIAL_WRITE --completeWrite--> COMPLETE_WRITE
// | |
// | read
// write |
// | V
// | WRITE_BEFORE_READ
// V |
// READ_BEFORE_WRITE <--write--+
其中:
大写字母表示状态,如
READ_BEFORE_WRITE
表示先读后写;小写字母表示资源操作;
初始状态为
NONE
;
对应的枚举为 FrameRefType
,这个状态在资源管理很多地方都需要用到。
例如清理资源引用时,就需要知道是否是“脏的”(包含写):
template <typename Configuration>
void ResourceManager<Configuration>::ClearReferencedResources() {
//...
for(auto it = m_FrameReferencedResources.begin(); it != m_FrameReferencedResources.end(); ++it) {
RecordType *record = GetResourceRecord(it->first);
if (record) {
if (IncludesWrite(it->second))
MarkDirtyResource(it->first);
record->Delete(this);
}
}
m_FrameReferencedResources.clear();
}
而资源是否是“脏的”,又要细分多种状态去判断:
bool IncludesWrite(FrameRefType refType) {
switch(refType) {
case eFrameRef_PartialWrite:
case eFrameRef_CompleteWrite:
case eFrameRef_CompleteWriteAndDiscard:
case eFrameRef_WriteBeforeRead:
case eFrameRef_ReadBeforeWrite: return true;
default: return false;
}
}
//...
bool IsDirtyFrameRef(FrameRefType refType) {
return (refType != eFrameRef_None && refType != eFrameRef_Read);
}
资源引用计数
resource_manager.h 还包含资源操作记录 ResourceRecord
的定义:
struct ResourceRecord {
ResourceRecord(ResourceId id, bool lock)
: RefCount(1),
ResID(id),
UpdateCount(0),
DataInSerialiser(false),
DataPtr(NULL),
DataOffset(0),
Length(0),
DataWritten(false),
InternalResource(false) {
//...
}
//...
void AddRef() { Atomic::Inc32(&RefCount); }
int GetRefCount() const { return RefCount; }
//...
protected:
int32_t RefCount;
//...
ResourceId ResID;
//...
std::unordered_map<ResourceId, FrameRefType> m_FrameRefs;
}
可以看到,里面通过 unordered_map
记录了所有资源的状态;更重要的,是引用计数逻辑:
RefCount
记录引用数;AddRef()
原子化操作,增加引用计数;
而资源引用数的减少则位于 resource_manager.cpp 的 delete()
方法:
void ResourceRecord::Delete(ResourceRecordHandler *mgr) {
int32_t ref = Atomic::Dec32(&RefCount);
//...
if (ref <= 0) {
for (auto it = Parents.begin(); it != Parents.end(); ++it)
(*it)->Delete(mgr);
if(ResID != ResourceId())
mgr->RemoveResourceRecord(ResID);
mgr->DestroyResourceRecord(this);
}
}
不仅更新了引用计数,而且在引用数为 0 时释放资源。
OpenGL 资源定义
gl_resources.h 包含资源数据结构 GLResource
的定义:
struct GLResource {
void *ContextShareGroup;
GLNamespace Namespace;
GLuint name;
}
同时提供了 Texture、Buffer、Shader 等各种资源的初始化方法:
inline GLResource TextureRes(const ContextPair &c, GLuint i) {
return GLResource(c.shareGroup, eResTexture, i);
}
inline GLResource BufferRes(const ContextPair &c, GLuint i) {
return GLResource(c.shareGroup, eResBuffer, i);
}
inline GLResource ShaderRes(const ContextPair &c, GLuint i) {
return GLResource(c.shareGroup, eResShader, i);
}
OpenGL 资源监控
对于 OpenGL,资源的分配和释放是成对的,如 glGenerateXXX()
、glDeleteXXX()
;因此,Hook 这些方法就能跟踪资源的访问。
下面以 Buffer 资源为例,实现位于 gl_buffer_funcs.cpp。
- Hook
glGenBuffers()
,注册资源、添加资源记录:
void WrappedOpenGL::glGenBuffers(GLsizei n, GLuint *buffers) {
SERIALISE_TIME_CALL(GL.glGenBuffers(n, buffers));
//...
for (GLsizei i = 0; i < n; i++) {
GLResource res = BufferRes(GetCtx(), buffers[i]);
ResourceId id = GetResourceManager()->RegisterResource(res);
//...
GLResourceRecord *record = GetResourceManager()->AddResourceRecord(id);
}
//...
}
- Hook
glBindBuffer()
,增加引用计数:
void WrappedOpenGL::glBindBuffer(GLenum target, GLuint buffer) {
SERIALISE_TIME_CALL(GL.glBindBuffer(target, buffer));
//...
GetResourceManager()->MarkResourceFrameReferenced(cd.m_BufferRecord[idx]->GetResourceID(), refType);
//...
}
MarkResourceFrameReferenced()
的实现位于 resource_manager.h:
void ResourceManager<Configuration>::MarkResourceFrameReferenced(ResourceId id, FrameRefType refType, Compose comp) {
//...
bool newRef = MarkReferenced(m_FrameReferencedResources, id, refType, comp);
if (newRef) {
RecordType *record = GetResourceRecord(id);
if (record)
record->AddRef();
}
}
- Hook
glDeleteBuffers()
,删除与资源和记录:
void WrappedOpenGL::glDeleteBuffers(GLsizei n, const GLuint *buffers) {
for (GLsizei i = 0; i < n; i++) {
GLResource res = BufferRes(GetCtx(), buffers[i]);
if (GetResourceManager()->HasCurrentResource(res)) {
GLResourceRecord *record = GetResourceManager()->GetResourceRecord(res);
//...
if (GetResourceManager()->HasResourceRecord(res))
GetResourceManager()->GetResourceRecord(res)->Delete(GetResourceManager());
GetResourceManager()->UnregisterResource(res);
}
}
GL.glDeleteBuffers(n, buffers);
}
类似的,gl_texture_funcs.cpp 实现了对 Texture 资源的监控,gl_shader_funcs.cpp 实现了对 Shader 资源的监控,限于篇幅,不再赘述。