注:在书写该文档之前已经存在一部分设计,位于./docs/TurboDesign.drawio:FrameGraph
中。由于Markdown
文件书写起来比较方便,所以使用该文档继续书写设计
注:该文档与./docs/Design/FrameGraphAdvance.md
文档配合使用,FrameGraphAdvance.md
文档是书写Turbo
引擎如何基于FrameGraph
驱动起来的设计文档
-
2022/12/30
- 创建
FrameGraph
文档 - 创建
PassNode与RenderPass
章节 - 创建
FrameGraph::Subpass
章节 - 创建
FrameGraph::RenderPass
章节
- 创建
-
2022/12/31
- 更新
FrameGraph::Subpass
章节 - 更新
FrameGraph::RenderPass
章节
- 更新
-
2023/1/1
- 将
FrameGraph::Subpass
章节重命名为FrameGraph::Builder::Subpass
,并更新FrameGraph::Builder::Subpass
章节 - 更新
FrameGraph::RenderPass
章节 - 创建
FrameGraph::Subpass
章节
- 将
-
2023/1/3
- 创建
FrameGraph::Mermaid
章节
- 创建
-
2023/1/4
- 更新
FrameGraph::Mermaid
章节
- 更新
-
2023/1/11
- 更新
PassNode与RenderPass
章节
- 更新
-
2023/1/12
- 更新
PassNode与RenderPass
章节 - 更新
FrameGraph::Builder::Subpass
章节 - 更新
FrameGraph::Subpass
章节 - 更新
FrameGraph::Mermaid
章节的示例图
- 更新
-
2023/1/18
- 更新
FrameGraph::RenderPass
章节,其中,增加如何在Execute阶段获得FrameGraph::RenderPass
章节
- 更新
在PassNode::Setup
阶段需要配置当前PassNode
的各种Subpass
,之后Turbo
引擎会根据用户的配置创建RenderPass
和FrameBuffer
//FrameGraph::PassNode::Setup
[&](TFrameGraph::TBuilder &builder, CustomPassData &data)
{
data.colorTex = builder.Create<Texture2D>("color",{512,512,Usage::Color})
data.normalTex = builder.Create<Texture2D>("normal",{512,512,Usage::Normal})
data.depthTex = builder.Create<DepthTexture2D>("depth",{512,512,Usage::Depth})
Subpass subpass0 = builder.CreateSubpass();
subpass0.Write(data.colorTex);
subpass0.Write(data.depthTex);
Subpass subpass1 = builder.CreateSubpass();
subpass1.Read(data.colorTex);
subpass1.Read(data.depthTex);
subpass1.Write(data.normalTex);
}
用户每调用一次TFrameGraph::TBuilder::CreateSubpass()
就是声明一个Subpass
,并且创建一个Subpass
并添加进PassNode
所代表的RenderPass
中,而Subpass
中有对应资源的读写配置
Subpass
对应资源测操作有:
- 读,
Subpass::Read(Resource)
- 写,
Subpass::Write(Resource)
FrameGraph::RenderPass
转Render::RenderPass
Subpass::Read(Resource)
,对应于Vulkan
底层的InputAttachment
Subpass::Write(Resource)
,对应于Vulkan
底层的ColorAttachment
或DepthStencilAttachment
,具体需要看是什么资源对于当
Subpass::Write(Resource)
资源为DepthStencil
纹理时,会有个问题,按照Vulkan
标准每个Subpass
只能绑定一个DepthTexture
,而Turbo
并不会制止用户往多个DepthTexture
中写入,这会与Vulkan
标准冲突,一种解决方案是当写入多个DepthStencil
纹理时,只有最后一个深度模板纹理有效,Turbo
输出警告信息
此处有一点要注意一下,如下:
subpass.Write(DepthStencilTexture);此时代表
DepthStencilTexture
在Vulkan
底层作为DepthStencilAttachment
进行使用subpass.Read(DepthStencilTexture);此时代表
DepthStencilTexture
在Vulkan
底层作为InputAttachment
进行使用
此时可能会有如下问题:
Loadinggraph LR; classDef Resource fill:#608ba3 classDef Pass fill:#e8924a classDef Subpass fill:#8474a0 classDef Start fill:#95ad5b,stroke:#95ad5b,stroke-width:4px classDef End fill:#a44141,stroke:#a44141,stroke-width:4px Start((" ")):::Start End((" ")):::End DepthBuffer0("Depth Buffer"):::Resource DepthBuffer1("Depth Buffer"):::Resource PassNode0:::Pass subgraph PassNode0["PassNode0"] direction TB PassNode0Subpass0("Subpass 0"):::Subpass end PassNode1:::Pass subgraph PassNode1["PassNode1"] direction TB PassNode1Subpass0("Subpass 0"):::Subpass end Start-.->PassNode0 PassNode0Subpass0-->DepthBuffer0 DepthBuffer0-.->PassNode1Subpass0 PassNode1Subpass0-->DepthBuffer1 DepthBuffer1-.->End linkStyle 1 stroke:#a44141,stroke-width:3px %% write link style linkStyle 2 stroke:#95ad5b,stroke-width:0.5px %% read link style linkStyle 3 stroke:#a44141,stroke-width:3px linkStyle 4 stroke:#a44141,stroke-width:3px此时对应的代码为:
DepthTexture2D depth_texture; PassNode pass_node0; pass_node0.Subpass0.Write(depth_texture); PassNode pass_node01; pass_node01.Subpass0.Write(depth_texture);如果此时调用
FrameGraph::Compile()
,当走到pass_node01
之后写入Depth Buffer
时,发现没有人使用该Depth Buffer
,这会导致FrameGraph
进行一系列剔除操作。此非良构。
问题的根源在于对于像
DepthStencilTexture
这样的资源,目前FrameGraph::Read(...)
可以解释成Vulkan
的InputAttachment
,但对于DepthStencilTexture
这样的资源这不是必须的,DepthStencilTexture
可以不作为InputAttachment
而被其他PassNode
使用。目前能想到的解决方案就是:
FrameGraph::Read(someResource)
唯一的作用是告诉FrameGraph
有人要使用该someResource
,请在FrameGraph::Compile()
阶段不要剔除相关节点,之后为FrameGraph::Read(someResource)
增加一个标志位
,用于表示此次读操作是否作为InputAttachment
进行解析。可能的函数声明如下://in FrameGraph FrameGraph::Read(Resource resource, bool isInput=false);或许给
Subpass
中添加Subpass::Input(Resource resource)
会更加方便//in FrameGraph FrameGraph::Input(Resource resource) { FrameGraph::Read(resource, true); }详情请预览下面的
FrameGraph::Builder::Subpass
章节和FrameGraph::Subpass
章节
位于:
namespace TFrameGraph
{
class FrameGraph
{
class Builder
{
class Subpass;//位于此处
};
};
}
Builder::Subpass
中的class Subpass
是真正的PassNode::RenderPass::Subpass
的代理(也可理解成前端),用户利用Builder::Subpass
这个前端类来完善底层的RenderPass
数据
由于原先是使用TFrameGraph::Builder::Write(...)
和TFrameGraph::Builder::Read(...)
函数,现由于资源的读写由Subpass
负责,则TFrameGraph::Builder
对于资源的读写改成私有,通过友元Subpass
调用TFrameGraph::Builder
对于资源的读写即可,所以Subpass
中需要存有TFrameGraph::Builder
引用
而对于资源的读写,同样要注册到PassNode
对应的RenderPass
中,所以
Subpass::Write(Resource)
的同时将向其中的RenderPass
下对应的Subpass
中注册资源Subpass::Read(Resource)
的同时将向其中的RenderPass
下对应的Subpass
中注册资源Subpass::Input(Resource)
的同时将向其中的RenderPass
下对应的Subpass
中注册资源
注:Subpass::Input(Resource)
与Subpass::Read(Resource)
本质上没区别,唯一的区别就是Subpass::Input(Resource)
会将对应得Resource
的input
标志位设置成true
考虑:是否将Subpass::Write(...)
和Subpass::Read(...)
设计成私有,并成为TBuilder
的友元,这样只有在PassNode::Setup
阶段可以调用Subpass::Write(...)
和Subpass::Read(...)
,如果设计成友元,其他私有成员也可以访问到了,也是个问题
//in FrameGraph::TBuilder
class Subpass
{
private:
TBuilder& builder;
RenderPass& renderPass;
uint32_t subpass;//当前subpass在RenderPass中的index
public:
TSubpass();
TSubpass(TBuilder& builder);
Resource Write(Resource);
Resource Read(Resource);
Resource Input(Resource);
}
Resource Subpass::Write(Resource resource)
{
Resource write_resource = builder.Write(resource);
renderPass.Subpasses[subpass].Write(write_resource);
return write_resource;
}
Resource Subpass::Read(Resource resource)
{
Resource read_resource = builder.Read(resource, false/*Input标志位*/);
//对于FrameGraph::Subpass来说read信息对其不重要,该信息只用于FrameGraph计算是否进行剔除,FrameGraph::Subpass保留read信息也不是一件坏事,在转成图表化信息时可以有更丰富的信息
renderPass.Subpasses[subpass].Read(read_resource);
return read_resource;
}
Resource Subpass::Input(Resource resource)
{
Resource read_resource = builder.Read(resource,true/*Input标志位*/);
renderPass.Subpasses[subpass].Input(read_resource);
return read_resource;
}
与FrameGraph::Builder::Subpass
大致差不多,为其后端,本质上用于存储资源的读写情况
//in FrameGraph
class TSubpass
{
private:
std::vector<TResource> writes;
std::vector<TResource> inputs;
public:
TSubpass() = default;
~TSubpass() = default;
void Write(TResource resource);
//void Read(TResource resource);
void Input(TResource resource);
};
void Write(TResource resource)
{
this->writes.push_back(resource);
}
// void Read(TResource resource)
// {
// this->reads.push_back(resource);
// }
void Input(TResource resource)
{
this->inputs.push_back(resource);
}
- 其中
TSubpass::writes
如果是ColorImage
,应该对应Vulkan
的ColorAttachment
。 - 其中
TSubpass::writes
如果是DepthStencil
,应该对应Vulkan
的DepthStencilAttachment
。 - 其中
TSubpass::inputs
应该对应Vulkan
的InputAttachment
。
Subpass
对于资源的读写,其实就是将对应的读写注册到RenderPass
中,而一个PassNode
代表一个RenderPass
,所以一个PassNode
中就应该存有一个RenderPass
信息。
//in FrameGraph
class PassNode
{
private:
RenderPass renderPass;
};
而一个RenderPass
下有多个Subpass
class RenderPass
{
private:
std::vector<Subpass> subpasses;
public:
RenderPass();
void AddSubpass(Subpass& subpass);
};
在Execute
阶段,回调函数参数有一个Turbo::FrameGraph::TResources
,可以通过该参数获取FrameGraph::RenderPass
//PassNode::Execute阶段回调
[=](const ColorPassData &data, const Turbo::FrameGraph::TResources &resources, void *context)
{
FrameGraph::RenderPass render_pass = resources.GetRenderPass();
}
FrameGraph
中应该提供一种接口,用于输出通用图形化图表结构,目前常见的通用图形化图表标准有:
Turbo
选择Mermaid
标准作为通用图形化图表接口。
std::string FrameGraph::ToMermaid();
该接口将会输出Mermaid
标准字符串,之后最常见的用法有两种:
- 推送到
http
服务器,展示在浏览器页面上 - 保存到本地,进而在本地打开,浏览查看
示例:
graph LR;
classDef Resource fill:#608ba3
classDef Pass fill:#e8924a
classDef Subpass fill:#8474a0
classDef Start fill:#95ad5b,stroke:#95ad5b,stroke-width:4px
classDef End fill:#a44141,stroke:#a44141,stroke-width:4px
Start((" ")):::Start
End((" ")):::End
DepthBuffer0("Depth Buffer"):::Resource
DepthBuffer1("Depth Buffer"):::Resource
GBuffer1("GBuffer 1"):::Resource
GBuffer2("GBuffer 2"):::Resource
GBuffer3("GBuffer 3"):::Resource
LightBuffer("Light Buffer"):::Resource
BackBuffer("Back Buffer"):::Resource
DepthPass:::Pass
subgraph DepthPass["Depth Pass"]
direction TB
DepthPassSubpass0("Subpass 0"):::Subpass
end
GBufferPass:::Pass
subgraph GBufferPass["GBuffer Pass"]
direction TB
GBufferPassSubpass0("Subpass 0"):::Subpass
end
LightingPass:::Pass
subgraph LightingPass["Lighting"]
direction TB
LightingPassSubpass0("Subpass 0"):::Subpass
end
PostPass:::Pass
subgraph PostPass["Post"]
direction TB
PostPassSubpass0("Subpass 0"):::Subpass
end
PresentPass:::Pass
subgraph PresentPass["Present"]
direction TB
PresentPassSubpass0("Subpass 0"):::Subpass
end
Start-.->DepthPass
DepthPassSubpass0-->DepthBuffer0
DepthBuffer0-->GBufferPassSubpass0
GBufferPassSubpass0-->DepthBuffer1
GBufferPassSubpass0-->GBuffer1
GBufferPassSubpass0-->GBuffer2
GBufferPassSubpass0-->GBuffer3
DepthBuffer1-.->LightingPassSubpass0 %% read link is dashed
GBuffer1-->LightingPassSubpass0
GBuffer2-->LightingPassSubpass0
GBuffer3-->LightingPassSubpass0
LightingPassSubpass0-->LightBuffer
LightBuffer-->PostPassSubpass0
PostPassSubpass0-->BackBuffer
BackBuffer-->PresentPassSubpass0
PresentPass-.->End
linkStyle 1 stroke:#a44141,stroke-width:3px %% write link style
linkStyle 2 stroke:#95ad5b,stroke-width:3px %% input link style
linkStyle 3 stroke:#a44141,stroke-width:3px
linkStyle 4 stroke:#a44141,stroke-width:3px
linkStyle 5 stroke:#a44141,stroke-width:3px
linkStyle 6 stroke:#a44141,stroke-width:3px
linkStyle 7 stroke:#ffd305,stroke-width:3px %% read link style
linkStyle 8 stroke:#95ad5b,stroke-width:3px
linkStyle 9 stroke:#95ad5b,stroke-width:3px
linkStyle 10 stroke:#95ad5b,stroke-width:3px
linkStyle 11 stroke:#a44141,stroke-width:3px
linkStyle 12 stroke:#95ad5b,stroke-width:3px
linkStyle 13 stroke:#a44141,stroke-width:3px
linkStyle 14 stroke:#95ad5b,stroke-width:3px
graph LR;
classDef Resource fill:#608ba3
classDef Pass fill:#e8924a
classDef Subpass fill:#8474a0
classDef Start fill:#95ad5b,stroke:#95ad5b,stroke-width:4px
classDef End fill:#a44141,stroke:#a44141,stroke-width:4px
Start((" ")):::Start
End((" ")):::End
PassNode0:::Pass
subgraph PassNode0["Color Pass"]
direction TB
PassNode0Subpass0("Subpass 0"):::Subpass
end
Color_Texture2D0("Color Texture2D"):::Resource
PassNode0Subpass0-->Color_Texture2D0
Depth_Texture2D0("Depth Texture2D"):::Resource
PassNode0Subpass0-->Depth_Texture2D0
PassNode1:::Pass
subgraph PassNode1["Post Pass"]
direction TB
PassNode1Subpass0("Subpass 0"):::Subpass
end
Color_Texture2D0("Color Texture2D"):::Resource
Color_Texture2D0-->PassNode1Subpass0
Depth_Texture2D0("Depth Texture2D"):::Resource
Depth_Texture2D0-->PassNode1Subpass0
RenderTarget_Texture2D0("RenderTarget Texture2D"):::Resource
PassNode1Subpass0-->RenderTarget_Texture2D0
PassNode2:::Pass
subgraph PassNode2["Present Pass"]
direction TB
PassNode2Subpass0("Subpass 0"):::Subpass
end
RenderTarget_Texture2D0("RenderTarget Texture2D"):::Resource
RenderTarget_Texture2D0-->PassNode2Subpass0
Start-.->PassNode0
PassNode2-.->End
linkStyle 0 stroke:#a44141,stroke-width:3px
linkStyle 1 stroke:#a44141,stroke-width:3px
linkStyle 2 stroke:#95ad5b,stroke-width:3px
linkStyle 3 stroke:#95ad5b,stroke-width:3px
linkStyle 4 stroke:#a44141,stroke-width:3px
linkStyle 5 stroke:#95ad5b,stroke-width:3px