renderdoc源码分析(二) resource manager

写在前面
1. 只针对renderdoc opengl es 场景进行说明, vukan的没有,不过其实应该也差不多啦,若有必要后面再考虑补上。
2. 本文使用 文字+图+代码 的方式叙述,若只想了解原理过程,可忽略代码部分

由于renderdoc代码还算复杂和乱,代码部分主要是辅助有兴趣阅读源码的同学去抓住代码主要逻辑。
3. renderdoc 相关名词
4. 对一帧抓流时,
1. 开始时机:前一帧swapbuffer时,具体逻辑做在StartFrameCapture()接口;
2. 结束时机:当前这一帧swapbuffer时,具体逻辑做在EndFrameCapture()接口;
即,前一帧的结束就是这一帧的开始嘛, 如图:

请添加图片描述

what

  1. resource manager, 顾名思义就是renderdoc 用来管理gl资源的,在opengles之上自己做了个状态机, 用来记录gl 资源的使用情况。
  2. gl资源在renderdoc resource manager里边主要由3个结构体来描述,
    GLResource:记录该资源 类型、在opengl es状态机里边的id,如glGenTextures生成的texture值;
    ResourceId:记录该资源在resource manager 里边的id,
    GLResourceRecord:以chunks形式,记录操作该资源的相关gl接口调用,尤其是创建、绑定、属性设置、数据upload,如glGenTextures,glGenBuffers等,一条glXXX用一块chunks记录。
  3. gl resource:
    以下这些在renderdoc都可以描述为资源,基本涵盖所有gl资源了,有点类似于 linux里一切皆文件思想哈哈哈,
enum GLNamespace
{
  eResUnknown = 0,
  eResSpecial,
  eResTexture,
  eResSampler,
  eResFramebuffer,
  eResRenderbuffer,
  eResBuffer,
  eResVertexArray,
  eResShader,
  eResProgram,
  eResProgramPipe,
  eResFeedback,
  eResQuery,
  eResSync,
  eResExternalMemory,
  eResExternalSemaphore,
};

why

  1. 在opengles 之上做了个状态机, 能起到如下作用,
    抓流场景:
    (1)性能、内存优化: BackgroundCapturing时 对于每一块gl resource,记录其数据更新时间(postpone机制)、当前帧是否使用(frame reference机制)、是否dirty(dirty机制),
    当进行真正的抓流ActiveCapturing时,就可以只序列化保存在当前帧使用的资源相关chunks;
    重放场景:
    (1)新旧资源映射: 对于来自opengl状态机的资源id, 抓流序列化时保存下来,
    重放是,需要去创建一个对应的资源,由于id不保证一次,所以需要做一个映射,
    即:origing GLResource <------> live GLResource
    (2) 性能优化:
    抓一帧时,有的资源是在该帧开始前就upload数据到gpu侧的,
    那么需要在每次重放帧开始前,先对该资源gpu侧显存进行初始化,若每次都从磁盘把数据找出来再disk–>cpu–>gpu,太耗性能,
    所以可以在resource manager里边做一个记录: live resource/origing resource <–> initialContents,
    initialContents放在cpu侧内存, 每次重放前做cpu—data—> gpu就ok了。

无论是resource manager本身,还是其里边实现的下面那些frame reference、dirty、persistent、postpone等各种机制,都不是必须的,是对性能和内存的优化,
抓流时也可以都不用,hook后简单粗暴记录下所有gl调用和资源,而随之带来的当然是无论BackgroundCapturing还是ActiveCapturing时的各种卡顿和内存占用过大。

frame reference 机制

  1. what
    标记一帧里边某个资源有被使用
  2. why
    优化内存与性能: 抓流时,该帧没被标记的资源,就不需要保存了。
  3. 相关接口
template <typename Configuration>
void ResourceManager<Configuration>::MarkResourceFrameReferenced(ResourceId id, FrameRefType refType)

//EndFrameCapture 时,序列化frame reference 资源
template <typename Configuration>
void ResourceManager<Configuration>::InsertReferencedChunks(WriteSerialiser &ser)

dirty机制

  1. what
    用 MarkDirtyResource( )标记该资源脏了,用在该资源gpu侧内容被改变时;
    区别于frame reference resource, 用MarkResourceFrameReferenced()标记该帧有使用了该资源
  2. why
    BackgroundCapturing场景,应用每次对资源更新数据时,renderdoc不需要每次都保存,因为实际上抓帧时,只要在这一帧开始前资源的最后一次内容就行了。
    例如对于一块buffer:
//BackgroundCapturing
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffName);
//frame1
glBufferData(GL_ELEMENT_ARRAY_BUFFER, data_size_in_bytes, data1, GL_STATIC_DRAW);
//frame2
glBufferData(GL_ELEMENT_ARRAY_BUFFER, data_size_in_bytes, data2, GL_STATIC_DRAW);
//framen
glBufferData(GL_ELEMENT_ARRAY_BUFFER, data_size_in_bytes, dataN, GL_STATIC_DRAW);

//ActiveCapturing
//frame capture
//对于当前要抓的这一帧,若用到了buffName,那么我们需要保存的就只是dataN的内容, 
//所以对于BackgroundCapturing时更新的数据data1、data2等,只要每次更新数据时做个dirty标记就行,
//表示这块资源dirty,可是没有保存,咱们在抓帧时把他保存下来就行
  1. 相关接口
 void MarkDirtyResource(ResourceId id);
  void MarkDirtyResource(GLResource res)//reference和dirty一起标记了
  void MarkDirtyWithWriteReference(GLResource res)
  
 void MarkVAOReferenced(GLResource res, FrameRefType ref, bool allowFake0 = false);
 void MarkFBOReferenced(GLResource res, FrameRefType ref);
 void MarkFBODirtyWithWriteReference(GLResourceRecord *record);
  1. 使用流程
    1. BackgroundCapturing时,对资源MarkDirtyResource,
    2. 真正抓帧开始前,即StartFrameCapture()里边,做dirty资源的gpu侧拷贝,cpu侧映射(因为要通过cpu侧handle去访问gpu侧资源内容嘛)
    3. 抓帧结束时,即EndFrameCapture()里边,若这些资源在该帧有引用到,从gpu侧拷贝前面备份的资源到cpu,对dirty资源做序列化保存。

相关接口
ActiveCapturing

/*****************StartFrameCapture**********************/
//遍历所有标记为dirty的资源,创建map resource, 即origin resource <----> map resource, 
//gpu侧,origin resource ---data copy---> map resource
template <typename Configuration>
void ResourceManager<Configuration>::PrepareInitialContents()


/*****************EndFrameCapture**********************/
//对每块dirty resource , origin resource ---->取出 map resource,
//map resource ---gpu data----> cpu, 
//cpu 侧对数据序列化,写入rdc文件
template <typename Configuration>
void ResourceManager<Configuration>::InsertInitialContentsChunks(WriteSerialiser &ser)

//序列化保存所有需要初始化的dirty resource,frame reference resource
template <typename Configuration>
void ResourceManager<Configuration>::Serialise_InitialContentsNeeded(WriteSerialiser &ser)

重放

//创建map resource, origin resource <-----> map resource ,
//rdc ---data--> cpu---data---> gpu, gpu侧这边为map resource
//map resourece 在cpu侧以InitContents形式与 origin resource 建立映射
template <typename SerialiserType>
bool GLResourceManager::Serialise_InitialState(SerialiserType &ser, ResourceId id,
                                               GLResourceRecord *record,
                                               const GLInitialContents *initial)
 
 //读出所有需要初始化的resource id, 
 //若此时该origin resource 有对应的live resource, 且还没有创建初始化内容,
 //则做一遍类似上面的事情:
//创建map resource, origin resource <-----> map resource ,
// gpu侧 live resource --data copy---> gpu侧map resource
//map resourece 在cpu侧以InitContents形式与 origin resource 建立映射
template <typename Configuration>
void ResourceManager<Configuration>::CreateInitialContents(ReadSerialiser &ser)
template <typename Configuration>

//每次重放开始时,执行 gpu 侧map resource ---data copy ---> gpu侧 live resource
void ResourceManager<Configuration>::ApplyInitialContents()

共用

  //获取一块 resource的初始化内容
  InitialContentData GetInitialContents(ResourceId id);
  //保存一块resource 的初始化内容
  void SetInitialContents(ResourceId id, InitialContentData contents);

关于抓流的具体过程,欢迎通过 renderdoc抓流过程 讲一步了解~

  1. 判断机制
    有数据更新的gl接口,标记该资源dirty

  2. 数据保存时机与流程
    请添加图片描述
    StartFrameCapture时做了gpu侧数据拷贝;
    EndFramecapture时从gpu拷到cpu,并序列化保存;

  3. 恢复数据时机
    那么,重放时数据的恢复,当然是上面的过程反过来啦
    请添加图片描述

  4. 时序图
    哎,本来不想画这个图的,实在太费时间,可是renderdoc代码写的实在太乱太复杂,还是画下吧方便下次通个这图就能快速理解代码流程

抓流

重放

  1. 相关调用流
    抓流
StartFrameCapture()
-->
PrepareInitialContents();
-->
Prepare_InitialState(GLResource res)
-->
//当前上下文里边做资源在gpu侧的拷贝
ContextPrepare_InitialState()
-->
创建初始化资源存到这,还没有回读与序列化 
m_InitialContents[id].data = contents;

===========================================
EndFrameCapture()
-->
InsertInitialContentsChunks()
-->
Serialise_InitialState()
-->
template <typename SerialiserType>
bool GLResourceManager::Serialise_InitialState(SerialiserType &ser, 
将数据从gpu侧回读并做序列化保存

重放

//数据加载到gpu侧,创建InitialContents
Serialise_InitialState()
-->
//若有live resource,且没有创建InitialContents
//gpu侧拷贝live resource 数据
CreateInitialContents()
-->
    //执行gpu侧数据拷贝
    Create_InitialState()
-->
//每次重放开始时,gpu侧,map resource --data copy--> live resource
ApplyInitialContents()
--> 
    //执行gpu侧数据拷贝
    Apply_InitialState()

persistent 机制

  1. persistent 资源
    创建后就一直常驻的资源,对于gles,为创建后,对其更新了内容,过了3s后还未被销毁的texture、buffer资源;
    即:对于texture、buffer资源,若超过3s,资源才再次更新,也表明资源在此期间没有被销毁,就认为是postpone资源,即非帧内创建的常驻资源;

  2. persistent 规则
    距离上次更新(写)时间操作3s。

  3. 相关接口与数据结构

//texture、buffer资源,更新速度没那么快(大于PERSISTENT_RESOURCE_AGE,即3s外),或者前面没有被引用 
bool HasPersistentAge(ResourceId id);
//texture、buffer资源
virtual bool IsResourceTrackedForPersistency(const WrappedResourceType &res) { return false; }
   
//StartFrameCapture时,对于persistent resource,不需要在prepareInitialContents()时
//先拷贝一份资源出来, 而是可以延迟到写rdc时,
//原因: 因为read only资源?反正内容不会被改变?
//场景:texture、buffer资源,更新速度没那么快(大于PERSISTENT_RESOURCE_AGE,即3s外),或者前面没有被引用 
std::unordered_set<ResourceId> m_PostponedResourceIDs;
  
//资源被引用(写)的时间,只记录最后那次
rdcarray<ResourceRefTimes>  m_ResourceRefTimes;

postpone 机制

  1. why
    为啥要有这个机制?是必须项还是优化项?
    ans: 优化项,减少PrepareInitialContents的资源数目,
    因为这些资源,在当前要抓的这一帧,不一定会用到;
    优化了啥?
    ans:性能,内存

  2. postpone 资源
    针对persistent resources 而言

// During initial resources preparation, persistent resources are
// postponed until serializing to RDC file.
std::unordered_set m_PostponedResourceIDs;

  1. postpone 规则
    若是persistent resource,则postpone处理,等到markFrameRef时才做PrepareInitialContents,
    而不是在StartFrameCapture时做,

  2. 使用场景
    StartFrameCapture时,对于buffer、texture 资源,若距离上次更新时间超过3s了,则也是认为对于一般开发流程来将,当前帧不会再去更新它了,
    认为是postpone资源,则延后到EndFrameCapture处理,
    抓帧过程会不会去处理它

  3. 相关接口与数据结构

bool IsResourcePostponed(ResourceId id);

//返回true情况:
// texture、buffer资源,更新速度没那么快(大于PERSISTENT_RESOURCE_AGE,即3s外),或者前面没有被引用 ,即 对于texture、buffer资源,若超过3s,资源还在,就认为是postpone资源
bool ShouldPostpone(ResourceId id);
template <typename Configuration>

//EndFrameCapture()时, 处理postpone 资源,前面PrepareInitialContents没有处理嘛
//所以需要留到这才处理
void Prepare_InitialStateIfPostponed(ResourceId id, bool midframe);

//gles里边没啥逻辑,应该没有skiped资源,vulkan才有,先忽略
void SkipOrPostponeOrPrepare_InitialState(ResourceId id, FrameRefType refType);

std::unordered_set<ResourceId> m_PostponedResourceIDs;
  
  1. 资源序列化
    与正常序列化流程没啥区别
EndFrameCapture()
-->
    InsertInitialContentsChunks()
--> 
        Prepare_InitialStateIfPostponed()
-->
            Prepare_InitialState()
-->
        Serialise_InitialState()

  1. 资源的恢复
    与正常恢复流程无区别
Serialise_InitialState()
-->
    //从rdc读出数据,创建副本,数据加载到gpu侧副本
    Serialise_InitialState()
-->
    //gpu侧从副本拷贝
    ApplyInitialContents();
  1. why
    性能优化,对于buffer、texture,一些可以不处理的情况,则不处理,
    opengl 目前来说应该是没有一个资源能被skip,因为没有一个eFrameRef_CompleteWriteAndDiscard场景
    vulkan才有,这个可以先不管,
  2. 相关接口
//opengl 目前来说应该是没有一个资源能被skip,因为没有一个eFrameRef_CompleteWriteAndDiscard场景
//vulkan才有,这个可以先不管
template <typename Configuration>
inline bool ResourceManager<Configuration>::ShouldSkip(ResourceId id)

template <typename Configuration>
inline bool ResourceManager<Configuration>::HasSkippableAge(ResourceId id)

//应该是一直为空
// During initial resources preparation, resources that are completely written
// over are skipped
std::unordered_set<ResourceId> m_SkippedResourceIDs;

  1. skip规则
  2. 使用场景

replacement 机制

//TODO

  1. why
  2. 规则
  3. 使用场景
  4. 相关接口

相关数据结构

  1. FrameRefType
    用来对资源的使用做标记
enum FrameRefType
{
  // Initial state, no reads or writes
  eFrameRef_None = 0,

  //标记有对资源做了写操作
  eFrameRef_PartialWrite = 1,

  //标记有对资源做了写操作, 并且后面没有读操作了
  eFrameRef_CompleteWrite = 2,

  //标记对资源做了写操作
  eFrameRef_Read = 3,
  

  //每次重放前都需要重置该资源, 
  //因为使用顺序是read-->write, 可能后面write后又回到前面了嘛
  //先对资源进行了读,又对它进行了写
  eFrameRef_ReadBeforeWrite = 4,

  //使用顺序是write --> read
  //先对资源进行了写,又对他进行了读
  eFrameRef_WriteBeforeRead = 5,

  //目前没用
  eFrameRef_CompleteWriteAndDiscard = 6,

  eFrameRef_Unknown = 1000000000,
};
//用来算新状态的,即老状态+新状态 结果
FrameRefType ComposeFrameRefs(FrameRefType first, FrameRefType second)

以上的资源read、write状态记录, 主要用在开始抓帧时,具体为StartFrameCapture()–>ClearReferencedResources(),判断是否要对被标记的资源做拷贝处理 , StartFrameCapture()细节参考renderdoc 抓流过程

template <typename Configuration>
void ResourceManager<Configuration>::ClearReferencedResources()
{
  SCOPED_LOCK_OPTIONAL(m_Lock, m_Capturing);

  for(auto it = m_FrameReferencedResources.begin(); it != m_FrameReferencedResources.end(); ++it)
  {
    RecordType *record = GetResourceRecord(it->first);

    if(record)
    {
      //这块资源在前面是有被写过的,要标记为dirty,保留
      //可能在后面的帧中有用到时,
      //需要在重放的一开始就去初始化它
      if(IncludesWrite(it->second))
        MarkDirtyResource(it->first);
      record->Delete(this);
    }
  }

  m_FrameReferencedResources.clear();
}
  1. ResourceId
    顾名思义, 标记一块资源的ID

  2. ResourceRecord 、GLResourceRecord
    存用到该资源的相关chunk,关于chunk,参考renderdoc抓流过程
    以contex为界(可能不同context会共享资源), 对于一块资源,需要记录其被使用的过程,
    ques: 啥样的资源操作接口需要记录到资源自己的record里?
    ans: 资源初始化过程,如texture的创建、绑定、cpu侧upload数据到gup侧、gpu侧数据拷贝、资源属性配置、等,这些都通过chunks存在ResourceRecord里边。
    目的是为了在重放场景,重放开始时,方便取出这些初始化接口,对资源进行初始化。

  3. GLResourceManager
    顾名思义,用来管理资源的,本文章的男主角,所有资源操作管理逻辑都在这里边;
    抓流: 记录当前有哪些资源,有哪些是标记为dirty的,
    重放:管理original resource Id 与 live resource Id的映射,
    主要对资源的标记操作为dirty、frameReflence,
    抓流时,主要需要处理的也是这2类资源

  4. GLInitialContents
    存一块resource的初始化数据

  5. InitialContentDataOrChunk

  6. ResourceRefTimes
    标记该资源最后一次被frame reference的时间,
    capture frame时若距离上次被标记超过3s,则认为是persistent 资源

  7. TextureData
    用来在renderdoc里边描述一块texture的

  8. ResourceDescription
    描述这块资源的初始化需要的chunks id

  9. FBOCache
    记录一块fbo,到底有多少资源attach到其上面

  10. GLNamespace
    标记资源类型,如buffer、texture、shader,program等

  11. GLContextTLSData
    gl context thread share data? 还不太清楚

TIPS

  1. VERBOSE_DIRTY_RESOURCES
    顾名思义,用来debug dirty 机制的开关,打开后可以打印该机制的相关debug log
  2. GLResource 和GLResourceRecord关系
    GLResource描述资源,
    GLResourceRecord记录使用到该资源的相关gl接口,即chunks

FAQ

  1. 对一块texture mark dirty,一条drawcall结束后,把texture删除了,renderdoc怎么处理?
    ANS: 目前看来是没法处理,WrappedOpenGL::glDeleteTextures 把resource record给删了
    不过感觉一般开发代码不会这么写,要么初始化时统一创建,后面统一销毁,
    要么帧内创建与销毁。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值