Direct3D 12 尝鲜: 基本呈现

(转载请注明出处)

请叫我挖坑狂魔_(:3」∠)_


微软前几天发布了Win10的开发工具,希望使用的童鞋可以加入windows insider计划 进行下载.
Direct3D 12 尝鲜: 基本呈现_第1张图片

下面是我的环境:

  • 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没有了一大堆接口, 泛化了.
Direct3D 12 尝鲜: 基本呈现_第2张图片


D3D11 中, 清屏很简单:

  1. 为当前设备上下文在OM阶段设置RTV
  2. 清理RTV

D3D12中:

  1. 为某图像命令列表在RS阶段设置RTV
  2. 为该命令列表对于资源(呈现缓存)设置Barrier表明从”用于呈现”变为”用于渲染对象”
  3. 清除RTV
  4. 设置Barrier, 变回来
  5. 关闭本命令列表
  6. 加入命令队列并执行

可以看出多了几步, 其实就多了两步: 设置资源Barrier过去, 设置Barrier回来. (因为D3D12对多线程渲染做了很多?) 资源Barrier就是为了处理资源的多个访问.
Direct3D 12 尝鲜: 基本呈现_第3张图片

对于命令列表, 如果不再使用可以重置.
这部分代码就不放上来了, 可以看所附带的实例代码: 下载地址


下面就是成果图

Direct3D 12 尝鲜: 基本呈现_第4张图片


已知问题

从输出窗口可以看出错误:
这里写图片描述
这个错误有机会再说吧_(:3」∠)_


代码下载地址在上面, 不要漏掉了

你可能感兴趣的:(C++,Direct3D)