Direct3D 12 尝鲜: 基本呈现

(转载请注明出处)

请叫我挖坑狂魔_(: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 中, 清屏很简单:

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

D3D12中:

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

可以看出多了几步, 其实就多了两步: 设置资源Barrier过去, 设置Barrier回来. (因为D3D12对多线程渲染做了很多?) 资源Barrier就是为了处理资源的多个访问.

对于命令列表, 如果不再使用可以重置.

这部分代码就不放上来了, 可以看所附带的实例代码: 下载地址


下面就是成果图


已知问题

从输出窗口可以看出错误:

这个错误有机会再说吧_(:3」∠)_



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

时间: 2024-08-05 03:41:11

Direct3D 12 尝鲜: 基本呈现的相关文章

Direct3D 12 尝鲜(五): SDK 更新

(转载请注明出处) 又是一个月, 微软在Build2015中公布了不少关于Win10的新闻, 又在N卡公布的支持DX12显卡中看到了我的垃圾显卡(GT640M LE)居然在支持范围,高高兴兴地直接换物理机安装上Win10, 升级了SDK, VS2015变成了RC版. 不过失望了是显卡支持的仅仅是D3D12 level11.0, 不能用level12.0,不知道会不会更新支持. Win10SDK也更新了, 我们在意的自然是D3D12部分. 对于目前, 改变如下: 1. Helper类/函数 被分离

Direct3D 12 尝鲜(四): 旋转的彩色立方体

(转载请注明出处) 有点时间没更新了,原因是找了一个多星期的bug结果发现是少了一次加法运算.... 不过反正没人看也就没影响啦!_(:3」∠)_ 这次的目的是做一个旋转的彩色立方体: 这是自己在学D3D11的时候做过的一个例子,现在搬到D3D12上, 这次增加的内容有: 常量缓存 深度缓存 顶点/索引缓存 常量缓存, 常量缓存可以放一些GPU只读的数据, 这里就是存放的是转换矩阵,D3D12中,常量缓存是以256字节对齐的(D3D12_CONSTANT_BUFFER_DATA_PLACEMEN

Direct3D 12 尝鲜(二): Fence

(转载请注明出处) 正如上节末尾所说, 出现了运行时错误: D3D12 ERROR: ID3D12CommandAllocator::Reset: A command allocator is being reset before previous executions associated with the allocator have completed. [ EXECUTION ERROR #548: COMMAND_ALLOCATOR_SYNC] 就是说要在相关执行完成后才能重置, 在自

屌丝就爱尝鲜头——java8总结晒一晒

前两节讨论了那么多,这节就是两个议题,讨论了新增的日期的api,再说一说我的Java8的一些心得体会了. 首先,我们必须要搞清楚Java 8 为什么要增加新的日期的api,这是由于老的日期api非常的繁琐,使用起来非常不方便,Java作者奉行这变者通不变者死的原则,于是增加了这些api.下面,我们总点介绍这几个类--LocalDate类.LocalTime类.LocalDateTime类.DateTimeFormatter类,zoneDate类.一个个来看: Ⅰ.LocalDate类--返回日期

15.EVE-NG小众镜像,助力尝鲜!

文章列表(关注微信公众号EmulatedLab,及时获取文章以及下载链接) 1.EVE-NG介绍(EVE-NG最好用的模拟器,仿真环境时代来临!) 2.EVE-NG安装过程介绍 3.EVE-NG导入Dynamips和IOL 4.EVE-NG导入QEMU镜像 5.EVE-NG关联SecureCRT,VNC,Wireshark 6.EVE-NG网卡桥接,带您走进更高级的实验 7.EVE-NG硬盘扩容,存储海量镜像 8.EVE-NG定制个人镜像,脚本快速导入 9.EVE-NG容纳H3C.Huawei,

CoreOS那些事之Rkt容器尝鲜(上)

从CoreOS发布Rocket应用容器项目到现在,已经过去半年时间了.为了增加辨识度,项目更名为了Rkt.在沉寂了许久后,最近又开始在社区里出现了一些新鲜的声音. 首先是4月7日的一条新闻,Google领头投资CoreOS公司1200万美元以共同合作发展旗下的Kubernetes组件.此次合作除了促成新的商业发行版Tectonic的诞生,也使得Rkt容器与Kubernetes的关系拉近了一步:Kubernetes将提供对Rkt的友好支持,而Rkt则将沿用Kubernetes的Pods等概念来规划

【响应式】foundation栅格布局的“尝鲜”与“填坑”

提到响应式,就不得不提两个响应式框架--bootstrap和foundation.在标题上我已经说明白啦,今天给大家介绍的是foundation框架. 何为"尝鲜"?就是带大伙初步一下foundation的灵活和强大 何为"踩坑"?就是我把我使用的时候踩过的坑给标个记号,这样大伙用的时候就可以"绕道而行"啦! 没错今天我这篇文章讲的就是北方酱左手边的那个看起来温(diao)文(de)尔(yi)雅(bi)的山羊先生:foundation!: 文章主

centos7 安装尝鲜网络配置

重点是4.网络配置 很多人网络都搞不来,这里上图了 时区设置 2. 分区设置--基本没啥好说的选择XFS 尝鲜 3.密码设置基本不用说 4.网络配置这个是关键 修改虚拟机配置文件添加ethernet0.virtualDev = "e1000"这样VMware的网卡就变成了Intel的1000M网卡了 6. [[email protected] ~]# ifconfigeno16777736: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>

屌丝就爱尝鲜头——java8再判断

这节,我们来通过具体的实例来看看Java8的具体用法. 首当其冲,就是lambda用法. 这里的案例,就是用lambda来实现runnable接口,我们知道以前用匿名内部类的方式来实现runnable接口,这种方法晦涩难懂,用lambda表达式实现以后,这样的代码清爽了不少.上两种对比的代码: public static void main(String[] args) { new Runnable() { public void run() { System.out.println("这是匿名