注:在书写该文档之前已经存在一部分设计,位于./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底层的InputAttachmentSubpass::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