本篇是笔者进行 RenderDoc drivers 层分析时一并记录下来的,是一个渲染 Cube 的简单 D3D11 程序与 API 交互的全流程的记录。
以 DirectX11-With-Windows-SDK - Rendering A Cube 为例进行说明。
DXGI (DirectX Graphics Infrastructure) 负责抽象和交换链、DAL 相关的公共部分。关于 DXGI 的资料可以参考 MSDN。
DXGI 封装了多种对象:
- 显示适配器 (IDXGIAdapter): 一般对应一块显卡,也可以对应 Reference Rasterizer,或者支持虚拟化的显卡的一个 VF 等
- 显示输出 (IDXGIOutput): 显示适配器的输出,一般对应一个显示器
- 交换链 (IDXGISwapChain): 用来暂存要显示到输出窗口/全屏幕的 1 到多个 Surface 的对象
g_pSwapChain->GetBuffer可以拿到表示 Back Buffer 的ID3D11Texture- 从
g_pd3dDevice->CreateRenderTargetView来创建一个封装该 Texture 的ID3D11RenderTargetViewg_pd3dDeviceContext->OMSetRenderTargets来设置管线的 RenderTargetg_pd3dDeviceContext->RSSetViewports来设置管线的 Viewport
GameApp::Init()
D3DApp::Init()InitMainWindow()RegisterClass(WNDCLASS *): 注册AdjustWindowRect()CreateWindow()ShowWindow()UpdateWindow()
InitDirect3D()D3D11CreateDevice(): 采用 11.1 的 Feature Level,不行则降级该函数会返回 Immediate Context (
ID3D11DeviceContext), 设备 (ID3D11Device) 和特性等级ID3D11Device::CheckMultisampleQualityLevels: 查询给定 DXGI_FORMAT 是否支持给定倍数的 MSAA将前面的
ID3D11DeviceCast 到IDXGIDeviceAn
IDXGIDeviceinterface implements a derived class for DXGI objects that produce image data.IDXGIDevice::GetAdapter拿到IDXGIAdapterThe
IDXGIAdapterinterface represents a display subsystem (including one or more GPUs, DACs and video memory).IDXGIAdapter::GetParent拿到IDXGIFactory1这里的
GetParent是IDXGIAdapter作为IDXGIObject的方法,可以获得构造它的工厂类。The
IDXGIFactory1interface implements methods for generating DXGI objects.尝试将
IDXGIFactory1Cast 到IDXGIFactory2(DXGI 1.2 新增)- 如果支持 DXGI 1.2,则用
CreateSwapChainForHwnd来创建交换链 - 否则,用
CreateSwapChain来创建交换链这两个函数都可以创建窗口 / 全屏幕交换链;DXGI 1.2 增加了新的、到其它输出目标的交换链创建功能,所以这里进行了重构。
也要注意,不同 DirectX 可以支持的交换链的交换行为类型是不同的。大体上,交换链的交换行为可以分为
- DISCARD vs SEQUENTIAL: 可以参考 StackExchange,区别就是一个驱动可以放心扔掉,另一个必须保留回读可能
- FILP vs BLIT (Bit Block Transfer): 决定是用交换指针还是数据拷贝的方法来从交换链被 Present 的 Surface 中拿取数据
- 如果支持 DXGI 1.2,则用
IDXGIFactory1::MakeWindowAssociation来取消让 DXGI 接收 Alt-Enter 的键盘消息并且切换窗口和全屏模式D3D11SetDebugObjectName()ID3D11DeviceChild::SetPrivateData(WKPDID_D3DDebugObjectName, ...)来设置资源的内部数据,这里是调试名称
DXGISetDebugObjectName()IDXGIObject::SetPrivateData(WKPDID_D3DDebugObjectName, ...)来设置资源的内部数据,这里是调试名称
OnResize()IDXGISwapChain::ResizeBuffers()IDXGISwapChain::GetBuffer()拿到ID3D11Texture2D形式的 Back BufferIDXGISwapChain::CreateRenderTargetView()创建绑定到上面 Back Buffer 的 Texture 的渲染目标视图D3D11SetDebugObjectName来设置 Back Buffer 的调试名称ID3D11Device::CreateTexture2D()来创建深度模板缓冲 (Depth Stencil Buffer),类型ID3D11Texture2D,包含大小,MipLevel,采样描述等ID3D11Device::CreateDepthStencilView()来创建前面缓冲对应的深度模板视图ID3D11DeviceContext::OMSetRenderTargets()来将渲染目标视图和深度木板视图绑定到管线ID3D11DeviceConetxt::RSSetViewports()绑定 Viewport 信息到光栅器状态D3D11SetDebugObjectName()设置调试前面各种视图对象的对象名
InitEffect()CreateShaderFromFile(): 传入 CSO (Compiled Shader Object) 和 Shader 文件,输出ID3DBlob *- 如果有缓存,则用
D3DReadFileToBlob装入,并返回 D3DCompileFromFile(): 编译并生成ID3DBlob对象- 如果指定了缓存路径,则
D3DWriteBlobToFile()进行输出分别创建了
vs_5_0和ps_5_0Shader Model 的 Shader Blob
- 如果有缓存,则用
ID3D11Device::CreateVertexShader(),根据 Shader Bytecode 创建ID3D11VertexShader对象注意这个函数支持传入 Class Linkage,这是一种在 Shader 间共享类型和变量的机制,在 Shader Model 5 被引入。更详细的用法可以参考 Dynamic Linking Class | MSDN
TODO: 研究一下
ID3D11Device::CreateInputLayout()传入输入元素描述符和 Shader,传出ID3D11InputLayout对象ID3D11Device::CreatePixelShader(),根据 Shader Bytecode 创建ID3D11PixelShader对象
InitResource()ID3D11Device::CreateBuffer()创建顶点缓冲区 (ID3D11Buffer) ,并传入初始化数据ID3D11Device::CreateBuffer()创建索引缓冲区 (ID3D11Buffer) ,并传入初始化数据ID3D11DeviceContext::IASetIndexBuffer()设置 Immediate Context 绑定索引缓冲区ID3D11Device::CreateBuffer()创建常量缓冲区 (ID3D11Buffer) ,不是用初始化数据- 此处设置
D3D11_BUFFER_DESC的CPUAccessFlags为D3D11_CPU_ACCESS_WRITE,让 CPU 可以改变其值
- 此处设置
ID3D11DeviceContext::IASetVertexBuffers()设置顶点缓冲区,stride 和 offsetID3D11DeviceContext::IASetPrimitiveTopology()设置图元类型ID3D11DeviceContext::IASetInputLayout()设置输入布局ID3D11DeviceContext::VSSetShader()绑定顶点着色器到管线ID3D11DeviceContext::VSSetConstantBuffers()设置常量缓冲区这里当然是拿着 ID3D11Buffer 去设置
ID3D11DeviceContext::PSSetShader()设置像素着色器D3D11SetDebugObjectName()将 Input Layout, Shader 和 Buffer 设置好调试用名字
GameApp::Run()
关于 Windows 消息机制的相关介绍可以参考 About messages and message queues | MSDN。
运行首先依赖 Windows 窗口程序本身的主事件循环(PeekMessage() => TranslateMessage() => DispatchMessage())。
主窗口的消息处理函数中,主要会处理:
WM_SIZE: 如果在WM_ENTERSIZEMOVE和WM_EXITSIZEMOVE中间,则忽略,否则调用OnResize()重新配置交换链并绑定到管线WM_ACTIVATE: 窗口不活跃时暂停渲染WM_DESTROY: 窗口退出消息
如果没有待处理的窗口消息,则会进入:
CalculateFrameStats(): 根据定时器计算时长并更新窗口标题UpdateScene(): 更新场景 (主要是更新常量缓冲)- 计算更新后的常量缓冲区值
ID3D11DeviceContext::Map传入 Constant Buffer 对象MSDN: Gets a pointer to the data contained in a subresource, and denies the GPU access to that subresource.
这里要指定映射类型 (CPU 可读,CPU 可写,CPU 可写且原内容可放弃);不过,这里还有一种类型,叫做
D3D11_MAP_WRITE_NO_OVERWRITE,这块 MSDN 的文档有比较详细的解释。也可以看看这篇知乎专栏作为参考。
memcpy(mappedData.pData, &cpuCBuffer, sizeof(cpuConstBuffer))将数据拷贝到D3D11_MAPPED_SUBRESOURCE::pData成员处ID3D11DeviceContext::Unmap解除内存映射
DrawScene(): 绘制场景ID3D11DeviceContext::ClearRenderTargetView(): 用给定颜色清空渲染目标视图ID3D11DeviceContext::ClearDepthStencilView(): 用给定深度和模板值清空深度模板视图ID3D11DeviceContext::DrawIndexed(): 绘制给定的立方体IDXGISwapChain::Present(SyncInterval=0, flags=0): 告知交换链已经完成绘制,可以呈现,并且要求立即呈现IDXGISwapChain::Present: Presents a rendered image to the user.