renderdoc ソースコード解析 (2) リソースマネージャー

上に書いた
: 1. この説明は、vukan ではなく、renderdoc opengle es シナリオのみを対象としていますが、実際にはほぼ同じであるはずです。必要に応じて、後で追加することも検討します。
2. この記事では、テキスト + 画像 + コードを使用して説明します。原理的なプロセスを理解したいだけの場合は、コード部分を無視してください。renderdoc

コードは非常に複雑で乱雑であるため、コード部分は主に、次のような学生を支援することを目的としています。ソースコードを読んでコードの要点を理解することに興味がある人。
3. renderdoc 関連の名詞
4. フレームをキャプチャするとき、
1. 開始タイミング: 前のフレームが swapbuffer の場合、特定のロジックが StartFrameCapture() インターフェイスで実行されます; 2.
終了タイミング: 現在のフレームが swapbuffer の場合、特定のロジックが実行されます。ロジックは EndFrameCapture() インターフェイスで実行されます。
つまり、図に示すように、前のフレームの終わりがこのフレームの始まりになります。

画像の説明を追加してください

  1. 名前が示すように、リソース マネージャーは、gl リソースを管理するために renderdoc によって使用され、opengle 上にステート マシンを構築して、 gl リソースの使用状況を記録します。
  2. gl リソースは主に、renderdoc リソース マネージャーの 3 つの構造によって記述されます。GLResource
    : リソース タイプと id を OpenGL es ステート マシンに記録します (glGenTextures によって生成されたテクスチャ値など)。ResourceId:
    リソースの ID を記録します。リソースマネージャー、
    GLResourceRecord: チャンクの形式で、リソースの操作、特に作成、バインディング、属性設定、データアップロード (glGenTextures、glGenBuffers など) に関連する gl インターフェイス呼び出しを記録します。1 つの glXXX が 1 つのチャンクに記録されます。 。
  3. gl リソース:
    以下は、基本的にすべての gl リソースをカバーする renderdoc 内のリソースとして記述できます。これは、Linux ではすべてがファイルであるという考え方に似ています (ははは)。
enum GLNamespace
{
    
    
  eResUnknown = 0,
  eResSpecial,
  eResTexture,
  eResSampler,
  eResFramebuffer,
  eResRenderbuffer,
  eResBuffer,
  eResVertexArray,
  eResShader,
  eResProgram,
  eResProgramPipe,
  eResFeedback,
  eResQuery,
  eResSync,
  eResExternalMemory,
  eResExternalSemaphore,
};

なぜ

  1. Opengles 上にステート マシンを構築しました。これは、
    ストリーム キャプチャ シナリオで次の役割を果たすことができます。
    (1) パフォーマンスとメモリの最適化: BackgroundCapturing 中に、各 gl リソースについて、そのデータ更新時間 (延期メカニズム) と現在のフレームが更新されているかどうかを記録します。使用 (フレーム参照メカニズム)、ダーティ (ダーティ メカニズム) かどうか、
    実際のキャプチャ ActiveCapturing を実行する場合、現在のフレームに保存されているリソース関連のチャンクのみをシリアル化できます。
    リプレイ シナリオ:
    (1) 新旧リソース マッピング: from OpenGL ステート マシンのリソース ID は、ストリーム キャプチャおよびシリアル化中に保存されます。
    再生中に、対応するリソースを作成する必要があります。ID は一度であることが保証されていないため、マッピングを行う必要があります。つまり、
    元の GLResource <------ > ライブ GLResource
    (2) パフォーマンスの最適化:
    フレームをキャプチャするとき、一部のリソースはフレームの開始前に GPU 側にデータをアップロードする
    ため、各リソースの前にリソースの GPU 側メモリを処理する必要があります。再生フレームが開始されます。初期化では、毎回ディスクからデータを検索し、次にディスク -> CPU -> GPU を検索する必要がある場合、パフォーマンスの消費が多すぎるため、リソース マネージャーに記録を作成できます: live resource/
    origing resource <–>initialContents の場合、
    initialContents は cpu 側に配置されますが、メモリに関しては、各リプレイの前に cpu-data->gpu を実行すれば問題ありません。

リソース マネージャー自体も、それに実装されているフレーム参照、ダーティ、永続、延期などのさまざまなメカニズムも必要ありません。これらはパフォーマンスとメモリの最適化です。ストリームのキャプチャ時にも使用できます。フック後は簡単です。
すべての gl 呼び出しとリソースを大まかに記録すると、当然のことながら、バックグラウンド キャプチャとアクティブ キャプチャの両方でさまざまな遅延と過剰なメモリ使用量が発生します。

フレーム参照機構


  1. フレーム内のリソースを使用中としてマークするもの
  2. メモリとパフォーマンスを最適化する理由
    : ストリームをキャプチャするとき、フレーム内でマークされていないリソースを保存する必要はありません。
  3. 関連するインターフェース
template <typename Configuration>
void ResourceManager<Configuration>::MarkResourceFrameReferenced(ResourceId id, FrameRefType refType)

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

ダーティメカニズム

  1. MarkDirtyResource() を使用し
    て、リソースをダーティとしてマークします。これは、リソースの GPU 側コンテンツが変更されるときに使用されます。フレーム参照
    リソースとは異なり、MarkResourceFrameReferenced() は、フレームをリソースを使用中としてマークするために使用されます。
  2. なぜ
    BackgroundCapturing シーンを使用するのかというと、アプリケーションがリソースのデータを更新するたびに、renderdoc を毎回保存する必要はありません。実際、フレームをキャプチャするときは、このフレームの開始前のリソースの最後のコンテンツだけで十分だからです。
    たとえば、バッファの場合は次のようになります。
//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() 内で、ダーティ リソースの GPU 側のコピーと CPU 側のマッピングを作成します (GPU 側のリソースのコンテンツには CPU 側のハンドルを介してアクセスする必要があるため) )
    3. フレーム キャプチャの終了時、つまり EndFrameCapture() 内で、これらのリソースがフレーム内で参照されている場合は、以前にバックアップしたリソースを GPU 側から CPU にコピーし、ダーティ リソースをシリアル化して保存します。

関連
インターフェース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インターフェースがあり、リソースはダーティとマークされます。

  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()

永続的なメカニズム

  1. 永続リソースとは
    、作成されてから常駐しているリソースです。gleの場合、作成後に更新され、3 秒後に破棄されていないテクスチャおよびバッファ リソースです。つまり、テクスチャ リソースとバッファ リソースの場合は、
    3 秒を超える場合に使用されます。秒。リソースが再び更新されると、この期間中にリソースが破棄されなかったことも示され、そのリソースは延期されたリソース、つまりフレーム内で作成されていない常駐リソースとみなされます。

  2. 永続ルールは、
    最後の更新 (書き込み) 時間から 3 秒後に動作します。

  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;

延期メカニズム

  1. なぜ
    この仕組みが必要なのでしょうか?それは必須ですか、それとも最適化ですか?
    回答: 最適化項目。PrepareInitialContents のリソースの数を減らします。
    これらのリソースはキャプチャされる現在のフレームで使用されない可能性があるためです。
    何が最適化されますか?
    答え: パフォーマンス、メモリ


  2. 永続的なリソースのリソースを延期する

// 初期リソースの準備中、永続リソースは
// RDC ファイルにシリアル化されるまで延期されます。
std::unowned_set m_PostponedResourceIDs;

  1. 延期ルール:永続リソースの場合、 StartFrameCapture ではなく
    、MarkFrameRef まで延期処理が行われ、PrepareInitialContents は行われません。

  2. シーンの StartFrameCapture をバッファおよびテクスチャ リソースに使用する
    場合、最後の更新から 3 秒以上経過している場合も、一般的な開発プロセスでは、現在のフレームは再度更新されないとみなされます。リソースがあり、
    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. パフォーマンスの最適化の理由
    、バッファー、テクスチャーなど、処理できない状況は処理されません。OpenGL には
    現在、スキップできるリソースがありません
    。vulkan のみにある eFrameRef_CompleteWriteAndDiscard シーンがないため、これは今のところ無視できます。
  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. ルールをスキップする
  2. 使用するシーン

交換機構

//TODO

  1. なぜ
  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)

上記のリソースの読み取りおよび書き込みステータス レコードは、主にフレームのキャプチャを開始するとき、特に 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 は
    、リソースを使用する関連チャンクを保存します。チャンクについては、renderdoc フロー キャプチャ プロセスを参照してください。
    コンテキストを境界として (異なるコンテキストがリソースを共有する場合があります)、リソースについては、その使用プロセスを記録する必要があります。 、
    質問: それはどのようなものですか? リソース操作インターフェースはリソース自身のレコードに記録する必要がありますか?
    回答: テクスチャの作成やバインド、CPU 側から gup 側へのデータのアップロード、GPU 側からのデータのコピー、リソース属性の設定などのリソースの初期化プロセスはすべて、チャンクを通じて ResourceRecord に保存されます。
    その目的は、リプレイの開始時にリプレイ シーン内のリソースを初期化するためのこれらの初期化インターフェイスの取得を容易にすることです。

  3. 名前が示すように、 GLResourceManager は
    リソースの管理に使用されます。この記事の主人公であるすべてのリソースの操作と管理ロジックはここにあります。ストリームのキャプチャ: 現在
    利用可能なリソースとダーティとしてマークされているリソースを記録します。
    再生: 元のリソースを管理します。 Id とライブ リソース ID のマッピングでは、
    リソースの主なマーキング操作はダーティと FrameReflence であり、
    ストリームをキャプチャする場合、主にこれら 2 種類のリソースを処理する必要があります。

  4. GLInitialContents は
    リソース初期化データを格納します

  5. InitialContentDataOrChunk

  6. ResourceRefTimes は、
    リソースが最後にフレーム参照された時間をマークします。
    フレームのキャプチャ時に最後にマークされてから 3 秒以上経過している場合、そのリソースは永続リソースとみなされます。

  7. TextureData は
    、renderdoc でテクスチャを記述するために使用されます。

  8. ResourceDescription は、
    このリソースの初期化に必要なチャンク ID を記述します。

  9. FBOCache は
    、fbo とそれに接続されているリソースの数を記録します。

  10. GLNamespace は、
    バッファ、テクスチャ、シェーダ、プログラムなどのリソース タイプをマークします。

  11. GLContextTLSData
    gl コンテキスト スレッド共有データ? まだわかりません

チップ

  1. VERBOSE_DIRTY_RESOURCES
    は、その名前が示すように、ダーティ メカニズムをデバッグするために使用されるスイッチで、オンにすると、メカニズムの関連デバッグ ログを出力できます。
  2. GLResource と GLResourceRecord の関係は、
    GLResource がリソースを記述し、
    GLResourceRecord がリソースに使用される関連する gl インターフェイス、つまりチャンクを記録することです。

よくある質問

  1. テクスチャ マーク ダーティの場合、描画呼び出し終了後、テクスチャが削除されます。renderdoc はどのように扱うのですか? ANS
    : 現時点では扱えないようです。WrappedOpenGL::glDeleteTextures でリソース レコードを削除します。
    ただし、一般的な感じです。開発コードはこのようには書かず、初期化時に一律に作成して後から破棄する
    か、フレーム内で作成して破棄するかのどちらかになります。

おすすめ

転載: blog.csdn.net/goodnight1994/article/details/129958052