Crossous's DX12 FrameWork
Crossous的DirectX12 框架,对DX12做了一些封装
当前处于开发参考阶段,部分本人没遇到的功能还未具有通用性
C++ 17及以上
magic_enum 用于显示错误时的枚举值。
D3D12MemAlloc 用于简化显存分配,无需考虑显存是Placed还是Committed Resource。
DirectX Tool Kit For DirectX 12 简化DDS纹理读取、键盘鼠标输入
lodepng 读取PNG图片文件
renderdoc_app 内置可开启renderdoc截取分析
基于**Introduction to 3D Game Programming with DirectX 12 **的框架编写
DX12的HLSL Shader关联到DX12的顶点布局(Input Layout)、寄存器布局(Root Signature),在《Intriduction……DX12》(后称为龙书)中,作者用了一个Uber Shader完成大部分功能,全局几乎只用了一个寄存器布局,实际编写程序中,不同Shader拥有不同的布局。
并且龙书中,寄存器布局、视图在堆中的位置,是用大脑来记录,属于硬编码在程序中,而此框架的Shader封装尽量让Shader自己记录寄存器布局。
本人用了DX12反射功能D3D12Reflection对HLSL信息进行读取,来初始化Shader的Root Signature,并记录布局。
Input Layout信息也可以通过反射读取,包括类型、Semantic Name等,但HLSL的Input Layout通常只表示自己需要什么数据,而步长等其他数据,还要由网格数据决定,因此Shader签名暂时留下默认Input Layout读取方式,也可以在构造函数中,由外界传入。
使用方法案例:
// construct:
const D3D_SHADER_MACRO defines[] = // defines // options
{
"FOG", "1",
NULL, NULL
};
Soco::ShaderStage ss;//entry point
ss.vs = "VS";
ss.ps = "PS";
mShaders["opaque"] = std::make_unique<Soco::Shader>(L"Shaders\\Default.hlsl", defines, ss);
Shader还能帮你填写PSO描述符所需要的信息,以及设置常量缓冲区、纹理,这些通常不需要你自己来做,而是由我的Material封装来处理,但接口依旧保留。
用于初始化PSO的几个方法:
//Initialize Graphics PSO Desc
void SetPSODescShader(D3D12_GRAPHICS_PIPELINE_STATE_DESC* desc);//将hlsl二进制程序填写到pso中
void SetPSODescTopology(D3D12_GRAPHICS_PIPELINE_STATE_DESC* desc);//填写拓扑类型 point\line\triangle\patch
void SetPSODescRootSignature(D3D12_GRAPHICS_PIPELINE_STATE_DESC* desc);//填写Root Signature
void SetPSODescInputLayout(D3D12_GRAPHICS_PIPELINE_STATE_DESC* desc);//填写InputLayout
用于SetPass的几个方法:
//设置根签名到当前CommandList
void SetGraphicsRootSignature(ID3D12GraphicsCommandList* cmdList);
//设置图元类型
void SetIASetPrimitiveTopology(ID3D12GraphicsCommandList* cmdList);
设置常量缓冲区、纹理的方法:
void SetConstantBufferView(ID3D12GraphicsCommandList* cmdList, const std::string& variableName, D3D12_GPU_VIRTUAL_ADDRESS BufferLocation);
void SetTexture(ID3D12GraphicsCommandList* cmdList, const std::string& textureName, D3D12_GPU_DESCRIPTOR_HANDLE BaseDescriptor);
支持Compute Shader
void SetComputeRootSignature(ID3D12GraphicsCommandList* cmdList)
void SetComputePipelineState(ID3D12GraphicsCommandList* cmdList)
支持曲面细分,当你传入的ShaderStage包括DomainShader和HullShader的EntryPoint时,会将你的图元拓扑类型改成相应枚举值。
和Unity一样,Texture只是一个抽象基类,指定子类必须实现能提供创建着色器资源视图的方法GetSRV,基于这个基类,我实现了Texture2D、TextureCube、RenderTexture。
和前两个不同,RenderTexture不但可以作为着色器资源,还可以作为渲染目标(RTV)、深度模版缓冲(DSV),并且有时能支持无序写入、访问(UAV)。
因此RenderTexture构建时需要传入一个格式:
struct RenderTextureFormat
{
DXGI_FORMAT ResourceFormat = DXGI_FORMAT_UNKNOWN;
DXGI_FORMAT SrvFormat = DXGI_FORMAT_UNKNOWN;
DXGI_FORMAT UavFormat = DXGI_FORMAT_UNKNOWN;
DXGI_FORMAT RtvFormat = DXGI_FORMAT_UNKNOWN;
DXGI_FORMAT DsvFormat = DXGI_FORMAT_UNKNOWN;
};
其中ResourceFormat是纹理资源格式,后面4个格式是分别作为其他用途时的格式;例如龙书的ShadowMap章节中,ShadowMap纹理资源格式本身被指定为DXGI_FORMAT_R24G8_TYPELESS,作为着色器资源时,格式为DXGI_FORMAT_R24_UNORM_X8_TYPELESS,作为深度模版缓冲时,为:DXGI_FORMAT_D24_UNORM_S8_UINT;并非是所有格式都可以同时绑定,根据Microsoft文档,RT和DS、UA和DS的视图不能同时被创建。
使用方法:
//Texture2D
mTextures["EarthDay"] = std::make_unique<Soco::Texture2D>("EarthDay", L"../Textures/Resources/1/EarthDay.dds");
mTextures["EarthNight"] = std::make_unique<Soco::Texture2D>("EarthNight", L"../Textures/Resources/1/EarthNight.dds");
mTextures["EarthCloud"] = std::make_unique<Soco::Texture2D>("EarthCloud", L"../Textures/Resources/1/EarthClouds.dds");
//TextureCube
mCubeMap = std::make_unique<Soco::TextureCube>("GrassCubeMap", L"../Textures/grasscube1024.dds");
//RenderTexture
Soco::RenderTextureFormat rtFormat;
rtFormat.ResourceFormat = mBackBufferFormat;
rtFormat.SrvFormat = mBackBufferFormat;
rtFormat.UavFormat = mBackBufferFormat;
D3D12_RESOURCE_DESC backbufferDesc = mSwapChainBuffer[0]->GetDesc();
mRenderTexture = std::make_unique<Soco::RenderTexture>(backbufferDesc.Width, backbufferDesc.Height, rtFormat);
Material需要传入使用的Shader、[光栅化、深度模版缓冲状态、默认PSO描述符、Material数据常量缓冲区名称]:
Material(Shader* shader, MaterialState* drawState = nullptr, D3D12_GRAPHICS_PIPELINE_STATE_DESC* psoDesc = nullptr, const std::string& matCBName = "");
初始化事例:
Soco::MaterialState earthMS;
earthMS.blendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
earthMS.rasterizeState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
earthMS.depthStencilState = CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT);
mMaterials["Earth"] = std::make_unique<Soco::Material>(mShaders["Earth"].get(), &earthMS, nullptr, "cbMaterial");
mMaterials["Earth"]->SetTexture("EarthDayMap", mTextures["EarthDay"].get());
mMaterials["Earth"]->SetTexture("EarthNightMap", mTextures["EarthNight"].get());
mMaterials["Earth"]->SetTexture("EarthCloudMap", mTextures["EarthCloud"].get());
Material可以帮助管理常量缓冲区数据(当前常量缓冲区使用方式并不好,申请、使用方式过于零散,与SRP Batch的方法背道而驰,还需要整改,但当前拥有脏数据功能,不修改数据的情况下不会上传数据给GPU)。
当前可以设置和获取MaterialObject:
SetMaterialData
GetMaterialData
之所以未放函数签名,因为使用了模版元的SFINEA,能察觉到使用的是引用还是指针,或者直接值传递。
由于上述的设计问题,Material暂时暴露出Update接口,当引擎Update时,需要调用所有Material的Update方法,用来更新常量缓冲区数据:
for (auto& [name, material] : mMaterials)
{
material->Update(mCurrFrameResourceIndex);
}
Material还支持设置PSO和SetPass功能:
//设置PSO
void SetPipelineState(ID3D12GraphicsCommandList* cmdList);
//设置常量缓冲区到对应slot中
void Setup(ID3D12GraphicsCommandList* cmdList, int currentFrame)
和前面部分Shader接口一样,一般不直接调用,而是交给我封装的Renderer来调用。
和Unity一样,Renderer只是一个抽象基类,管理常量缓冲区数据和更新,SetPass、DrawCall等。
基于Renderer实现了MeshRenderer、SkyboxRenderer、TextureRenderer、TerrainRenderer。
基于XTK实现的输入模块,提供了以下几个函数:
bool D3DApp::GetKey(Key);
bool D3DApp::GetKeyDown(Key);
bool D3DApp::GetKeyUp(Key);
bool D3DApp::GetKey(MouseKey);
bool D3DApp::GetKeyDown(MouseKey);
bool D3DApp::GetKeyUp(MouseKey);
POINT D3DApp::GetMouseMove();
POINT D3DApp::GetMousePosition();
使用方式:
if (GetKeyDown(Key::C))
{
rdoc_api->TriggerCapture();
if(!rdoc_api->IsTargetControlConnected())
rdoc_api->LaunchReplayUI(1, nullptr);
}