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.cppdelete() 方法:

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 资源的监控,限于篇幅,不再赘述。