(转载请注明出处)
请叫我挖坑狂魔_(:3」∠)_
微软前几天发布了Win10的开发工具,希望使用的童鞋可以加入windows insider计划 进行下载.
下面是我的环境:
- Windows 10 Technical Preview Build 10041
- Visual Studio 2015 CTP 6
- Visual Studio Tools for Windows 10
当然,使用的是虚拟机.
D3D12文档可以在官方文档里面进行查看,里面有编程向导与API文档. 不过, 这个文档也是初步的, 可能链接会失效.
初始化:
初始化COM组件和创建窗口就不再累述,直接杀入主题:
和D3D11类似, 使用D3D12CreateDevice创建D3D12设备,目前,函数声明如下:
HRESULT WINAPI D3D12CreateDevice(
IDXGIAdapter* pAdapter,
D3D_DRIVER_TYPE DriverType,
D3D12_CREATE_DEVICE_FLAG Flags,
D3D_FEATURE_LEVEL MinimumFeatureLevel,
UINT SDKVersion,
REFIID riid,
void** ppDevice );
第一个是显卡适配器, 可以枚举, 可以为nullptr,
第二个是驱动类型, 我这里是虚拟机, 所以选择WARP
第三个是创建flag(没有RGBA支持,也就是说不能链接D2D?)
第四个是特性等级, 现在还没有12的等级,所以选择11.1
第五个是SDK版本, 使用宏D3D12_SDK_VERSION即可
第五个第六个也就很熟悉了, 假如只是用MSC编译, 可以使用宏
IID_PPV_ARGS,不过对于GCC等编译器还是手写吧:
D3D12_CREATE_DEVICE_FLAG flags = D3D12_CREATE_DEVICE_NONE;
#ifdef _DEBUG
flags |= D3D12_CREATE_DEVICE_DEBUG;
#endif
hr = ::D3D12CreateDevice(
nullptr,
D3D_DRIVER_TYPE_WARP,
flags,
D3D_FEATURE_LEVEL_11_1,
D3D12_SDK_VERSION,
IID_ID3D12Device,
reinterpret_cast<void**>(&m_pd3dDevice)
);
不知道是不是bug还是没有实现还是什么原因,不能像D3D11那样利用d3d设备获取dxgi设备, 再balabala创建交换链。 所以这里利用CreateDXGIFactory2创建Dxgi工厂
hr = ::CreateDXGIFactory2(
0,
IID_IDXGIFactory2,
reinterpret_cast<void**>(&m_pDxgiFactory)
);
再使用IDXGIFactory2::CreateSwapChainForHwnd为窗口创建交换链, 需要注意的是第一个参数,用过D3D11的童鞋可能习惯性地传个D3D12设备指针,不过这是错误的,调用可能没问题, 但是呈现时会出错,第一个参数应该传一个ID3D12CommandQueue指针,所以我们还应该创建一个D3D12的命令队列, 官方给的向导里面可以获取一个默认的队列,但是发现现在这个接口被移除了,只能直接创建了:
// 创建命令队列
if (SUCCEEDED(hr)) {
D3D12_COMMAND_QUEUE_DESC desc = {
D3D12_COMMAND_LIST_TYPE_DIRECT,
0,
D3D12_COMMAND_QUEUE_NONE,
0
};
hr = m_pd3dDevice->CreateCommandQueue(
&desc,
IID_ID3D12CommandQueue,
reinterpret_cast<void**>(&m_pCmdQueue)
);
}
// 创建交换链
if (SUCCEEDED(hr)) {
RECT rect = { 0 }; ::GetClientRect(m_hwnd, &rect);
// 交换链信息
DXGI_SWAP_CHAIN_DESC1 swapChainDesc = { 0 };
m_uBufferWidth = swapChainDesc.Width = rect.right - rect.left;
m_uBufferHeight = swapChainDesc.Height = rect.bottom - rect.top;
swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
swapChainDesc.Stereo = FALSE;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.SampleDesc.Quality = 0;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.BufferCount = 2;
swapChainDesc.Scaling = DXGI_SCALING_STRETCH;
swapChainDesc.Flags = 0;
// 一般桌面应用程序
swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
// 利用窗口句柄创建交换链
hr = m_pDxgiFactory->CreateSwapChainForHwnd(
m_pCmdQueue,
m_hwnd,
&swapChainDesc,
nullptr,
nullptr,
&m_pSwapChain
);
}
D3D12中中显然的就是命令列表ID3D12CommandList, 其中一个实现是ID3D12GraphicsCommandList, 和Direct2D中的命令列表类似,不过可以重置.
这个图像命令列表的特点就是用来记录DrawCall, 完了就关闭, 然后高效重现.
为了创建图像命令列表, 我们需要创建一个命令分配器, 因为可以指定分配器的类型, 不然可以向D2D那样由设备上下文直接创建命令列表, 使用ID3D12Device::CreateCommandQueue:
// 创建命令队列
if (SUCCEEDED(hr)) {
D3D12_COMMAND_QUEUE_DESC desc = {
D3D12_COMMAND_LIST_TYPE_DIRECT,
0,
D3D12_COMMAND_QUEUE_NONE,
0
};
hr = m_pd3dDevice->CreateCommandQueue(
&desc,
IID_ID3D12CommandQueue,
reinterpret_cast<void**>(&m_pCmdQueue)
);
}
这样创建一个D3D12_COMMAND_LIST_TYPE_DIRECT类型的分配器,这个是可以创建GPU可执行的命令列表.
现在终于可以创建一个图像命令列表ID3D12Device::CreateCommandList了:
// 创建图像命令列表
if (SUCCEEDED(hr)) {
hr = m_pd3dDevice->CreateCommandList(
0, D3D12_COMMAND_LIST_TYPE_DIRECT,
m_pCmdAllocator,
nullptr,
IID_ID3D12GraphicsCommandList,
reinterpret_cast<void**>(&m_pGfxCmdList)
);
}
这个图像命令列表就和D3D11的设备上下文一样可以执(ji)行(lu)具体渲染命令:
D3D11中, 我们可以从交换链中获取一个2D纹理, 但是D3D12中就没有2D纹理, 取代的是可以代表资源的ID3D12Resource接口, 直接获取就行了:
// 获取缓冲区
if (SUCCEEDED(hr)) {
hr = m_pSwapChain->GetBuffer(
0, IID_ID3D12Resource,
reinterpret_cast<void**>(&m_pTargetBuffer)
);
}
同样地, D3D11中, 可以使用设备创建RTV
(ID3D11Device::CreateRenderTargetView),
在D3D12中, 所有的资源都被绑定到”descriptor”标识符上面,还有descriptor tables, descriptor heaps, root signature什么的,详见资源绑定. 这里主要是descriptor 和 descriptor heap,主要区别, 前者是单个后者是连续.
因为(目前)没有ID3D12Device::CreateDescriptor, 所以使用ID3D12Device::CreateDescriptorHeap代替, 创建一个就好了, 以后有相同资源需要绑定, 可以创建一个descriptor heap一起使用.
// 创建RTV描述符
if (SUCCEEDED(hr)) {
D3D12_DESCRIPTOR_HEAP_DESC desc = {
D3D12_RTV_DESCRIPTOR_HEAP,
1,
D3D12_DESCRIPTOR_HEAP_NONE,
0
};
hr = m_pd3dDevice->CreateDescriptorHeap(
&desc, IID_ID3D12DescriptorHeap,
reinterpret_cast<void**>(&m_pRTVDescriptor)
);
}
// 创建RTV
if (SUCCEEDED(hr)) {
m_pd3dDevice->CreateRenderTargetView(
m_pTargetBuffer,
nullptr,
m_pRTVDescriptor->GetCPUDescriptorHandleForHeapStart()
);
}
D3D11类似, 创建RTV, 不过没有专用的接口了, 用这个描述符就好了, 从这里和上面可以看出, D3D12没有了一大堆接口, 泛化了.
D3D11 中, 清屏很简单:
- 为当前设备上下文在OM阶段设置RTV
- 清理RTV
D3D12中:
- 为某图像命令列表在RS阶段设置RTV
- 为该命令列表对于资源(呈现缓存)设置Barrier表明从”用于呈现”变为”用于渲染对象”
- 清除RTV
- 设置Barrier, 变回来
- 关闭本命令列表
- 加入命令队列并执行
可以看出多了几步, 其实就多了两步: 设置资源Barrier过去, 设置Barrier回来. (因为D3D12对多线程渲染做了很多?) 资源Barrier就是为了处理资源的多个访问.
对于命令列表, 如果不再使用可以重置.
这部分代码就不放上来了, 可以看所附带的实例代码: 下载地址
下面就是成果图
已知问题
从输出窗口可以看出错误:
这个错误有机会再说吧_(:3」∠)_
代码下载地址在上面, 不要漏掉了