首先声明,本人是自学DX12,有很多的理解也许不到位,不过都是自己的理解。在很长一段时间里边,我从迷茫到有一天开始能看懂,现在是第三次开始刷DX12了,于是在此表明写作的初衷:

1.有一些DX12的学习心得,希望发出来,有大佬如果愿意指教,万分感谢;

2.如果对于才入门的人来说,这可能是我的白话教程,也许会对你有所帮助,但不可尽信,因为我也不确定我对不对;

3.DX12的概念很多,也是想把这作为自己的学习笔记来做,希望对自己也有帮助,如果有一天我发现哪里错了会及时回来更正。

那么话不多说,现在开始!!!

初始化Direct3D

一.创建设备

DX12中这里讲得就有点跳跃了,个人认为的顺序应该这样:

首先,我们需要一个工厂类IDXGIFactory4,这个类有两个作用:

1.枚举适配器(显卡);2.创建交换链(PS:这不是一个扫盲教程,如果你这个也不知道,建议你还是先看看DX12的手册)

这个类对象的创建如下:

Microsoft::WRL::ComPtr<IDXGIFactory4> mdxgiFactory;

ThrowIfFailed(CreateDXGIFactory1(IID_PPV_ARGS(&mdxgiFactory)));

然后,我们需要用这个对象mdxgiFactory枚举我们可以使用的显卡等适配器(有很多检查的细节就不说了,毕竟我们不是照搬教材,而是说主线逻辑):

ComPtr<IDXGIAdapter1> pIAdapter;

mdxgiFactory->EnumAdapters1(adapterIndex, &pIAdapter);

最后,对于一个选定的适配器pIAdapter,我们拿着它去创建设备

HRESULT hardwareResult = D3D12CreateDevice(
        pIAdapter.Get(),             // 设置为nullptr为default adapter
        D3D_FEATURE_LEVEL_12_1,//DX支持1的级别类型
        IID_PPV_ARGS(&md3dDevice)//返回的设备,也就是我们创建的对象
        );

这里我们讲一次IID_PPV_ARGS这个宏实际包含了两个东西,uuid的COM ID和对象的指针,这个很常用。

二、创建围栏

冷不丁的冒一句还是很突兀的,不过就是这么突兀,并不是很多事情一下子就有联系的,也就是说一和二似乎并没有先后顺序。

围栏的主要作用:同步——通过围栏值来实现

Microsoft::WRL::ComPtr<ID3D12Fence> mFence;

ThrowIfFailed(md3dDevice->CreateFence(0, //初始值
        D3D12_FENCE_FLAG_NONE,
        IID_PPV_ARGS(&mFence)));

三、获取当前平台下各种描述符的大小

对的,这也很突兀,所以和前边也没有顺序关系。核心函数是:GetDescriptorHandleIncrementSize(类型);

因为不同的机器这些描述符可能存在差异,所以我们这一步实际是在为后边开空间做准备:mRtvDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);//RTV
    mDsvDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV);//DSV
    mCbvSrvUavDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);

四、检测4X MSAA的支持

实际上DX11之上就默认支持了,是否检测看你的编程需要,所以检测方法如下,不打算详细说:

D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS msQualityLevels;
    msQualityLevels.Format = mBackBufferFormat;
    msQualityLevels.SampleCount = 4;
    msQualityLevels.Flags = D3D12_MULTISAMPLE_QUALITY_LEVELS_FLAG_NONE;
    msQualityLevels.NumQualityLevels = 0;
    //完成属性结构体填充之后,msQualityLevels在CheckFeatureSupport被设置,修改NumQualityLevels
    ThrowIfFailed(md3dDevice->CheckFeatureSupport(
        D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,
        &msQualityLevels,
        sizeof(msQualityLevels)));

基本逻辑就是填写一个结构体,然后调用CheckFeatureSupport(检测类型,结构体指针,结构体数据大小)。重点提一下,在DX12中有很多这种逻辑的设计:结构体+对象。这里结构体里的数据是输入输出都有的,比如返回的: m4xMsaaQuality = msQualityLevels.NumQualityLevels;
    assert(m4xMsaaQuality > 0 && "Unexpected MSAA quality level.");

可以查询到当前设置采样数下的最高级别,这可以用在后边。

OK!现在是比较难的来了,个人感觉这是概念比较多也比较难记住的部分,跟紧我往下看。

五、创建命令队列,命令分配器和命令列表

我不知道DX12手册为啥不写分配器,明明三个才是一个整体。

这里解释一下三者的关系和创建方法:

1.命令队列CommandQueue是一个环形队列,是GPU获得操作指令的地方,这里有一个我不是很理解的地方,在CPU里边创建这个队列的对象,他是怎么到GPU的呢?我只是有一些猜测,如果知道的大佬,麻烦下方留言探讨一下。

他的创建方法如下:结构体描述+创建

D3D12_COMMAND_QUEUE_DESC queueDesc = {};
    queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;//注意这个格式
    queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
    //创建命令队列
    ThrowIfFailed(md3dDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&mCommandQueue)));

2.命令分配器是CPU里边存储命令的地方(至少我是这么理解的),他似乎是在命令列表的上一层,也就是说命令列表负责实际设置哪些命令,而他似乎内部实现一个类似vector的push_back的功能,把这些设置的命令存起来,看看他的创建我们可能可以看出一点端倪:ThrowIfFailed(md3dDevice->CreateCommandAllocator(
        D3D12_COMMAND_LIST_TYPE_DIRECT,//还是注意这个类型,这已经和命令队列对应上了
        IID_PPV_ARGS(mDirectCmdListAlloc.GetAddressOf())));

3.再说命令列表,我的理解就是设置命令,这是一个异步操作。先看他的创建:ThrowIfFailed(md3dDevice->CreateCommandList(
        0,//这个0实际是显卡的索引,就是你要交给谁来处理
        D3D12_COMMAND_LIST_TYPE_DIRECT,//这里,这个类型又出现了
        mDirectCmdListAlloc.Get(), // 关联命令分配器
        nullptr,                   // Initial PipelineStateObject
        IID_PPV_ARGS(mCommandList.GetAddressOf())));

看完上边这个创建,我们似乎就明白了三者的关系:命令列表作为设置命令的第一线,比如:mCommandList->RSSetViewPorts(...);将命令设置之后,自动的就放到了他关联的命令分配器mDirectCmdListAlloc中,这就是我觉得push_back的所在(不喜勿喷)。我们设置命令使用mCommandList->....  然后他就放入分配器存起来,那么这些命令如何被GPU用起来呢?ID3D12CommandList* cmdsLists[] = { mCommandList.Get() };
    mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);

看懂了吗?是命令队列的对象把命令送进GPU的,如果说这个函数里边有this指针,是不是意味着这里的mCommandQueue就是GPU的CommandQueue,那个循环取命令队列。

OK,三个的逻辑梳理了,再说一点注意事项:

创建时,或者使用Reset函数(命令列表和分配器)都处于打开的状态,特别要注意的是在ExecuteCommandLists之前要先关闭mCommandList,至于分配器,因为要进行复用时会有Reset的刷新操作,并且之后又是往里边加东西,我还不知道啥时候要关他,,,,

六、交换链

或者我们就把他叫双缓冲,为了实现流畅的页面翻转。创建交换链也是经典逻辑:结构体+创建。这里的结构体包含了对缓冲区(RT)尺寸,格式,多从采样,RT数目以及显示模式等的描述,然后就是调用函数创建:

DXGI_SWAP_CHAIN_DESC sd;
    sd.BufferDesc.Width = mClientWidth;
    sd.BufferDesc.Height = mClientHeight;
    sd.BufferDesc.RefreshRate.Numerator = 60;
    sd.BufferDesc.RefreshRate.Denominator = 1;
    sd.BufferDesc.Format = mBackBufferFormat;
    sd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
    sd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
    sd.SampleDesc.Count = m4xMsaaState ? 4 : 1;

//这不就用到了吗?就是我们做4XMSAA检测的地方那个返回来的参数
    sd.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;

//看一下这两行,我们后边要用,真的要用!!!
    sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;//将数据渲染到后台缓冲区
    sd.BufferCount = SwapChainBufferCount;//双缓冲就是2
    sd.OutputWindow = mhMainWnd;//渲染窗口的句柄,在我创建的窗口处生成
    sd.Windowed = true;//true窗口模式,false全屏模式
    sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;//使用翻页技术
    sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;//全屏自动适应窗口

// Note: Swap chain uses queue to perform flush.
    ThrowIfFailed(mdxgiFactory->CreateSwapChain(
        mCommandQueue.Get(),//这里是不是代表着切换是由命令队列来驱动的,好像还是自动的
        &sd, 
        mSwapChain.GetAddressOf()));//返回所创建的交换链接

七、描述符相关

因为这里我觉得不止是描述符堆,这东西脱离了描述符就没意思了,所以一起讲,因此:他内容比较多,并且,我觉得是和命令三剑客一样很容易记混淆的东西,我尝试说清楚,我们开始吧。

基本概念:

描述符:或者叫视图,比如RTV,DSV,CBV,SRV和UAV,不仅包含数据,还负责解释数据格式等。

描述符堆:存储描述符的地方,如果把描述符理解为带下标的元素,那这个就是数组。

我在这里写个东西:int arr[10]={};等下我们来看看有多像。

1.创建描述符堆(描述符数组)

经典的:结构体+创建

Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> mRtvHeap;

D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc;
    rtvHeapDesc.NumDescriptors = SwapChainBufferCount;//双缓冲,2
    rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;//RTV的堆
    rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
    rtvHeapDesc.NodeMask = 0;
    ThrowIfFailed(md3dDevice->CreateDescriptorHeap(
        &rtvHeapDesc, IID_PPV_ARGS(mRtvHeap.GetAddressOf())));

上边是RTV的,对应DSV的高度相似,只是数量和类型 :

dsvHeapDesc.NumDescriptors = 1;
    dsvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;

这里的类型:D3D12_DESCRIPTOR_HEAP_TYPE_RTV是不是对应了int

这里的堆:rtvHeapDesc是不是对应了arr,当然里边还有数量,dddd。

2.创建描述符(视图)

比如RTV,这就相当于arr的具体元素了。创建元素说到底在一个内存地址里边放入我们需要的数据。对于RTV,他的地址是给定了的,不是我们随意分配的,这可能就是和创建数组的一点区别,那么这个地址在哪里?我们回顾一下我们在第六步,创建交换链的时候,指定了“渲染到后台缓冲RTV”以及数量“2个”,是不是可以说我们开了空间了,对的,这里就把空间开出来了,那么怎么得到这个地址呢?

Microsoft::WRL::ComPtr<ID3D12Resource> mSwapChainBuffer[SwapChainBufferCount];

ThrowIfFailed(mSwapChain->GetBuffer(i, IID_PPV_ARGS(&mSwapChainBuffer[i])));//第一个参数表示获得地址的索引,就是双缓冲的第一个或第二个地址,把地址里边的数据存到资源mSwapChainBuffer[i]中。

然后拿着这个资源数据填到我们开出的数组mRtvHeap这个描述符堆当中,那么我们以后从这个堆就能找到我们的数据,作为哪种数据就看这个堆的类型:

CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHeapHandle(mRtvHeap->GetCPUDescriptorHandleForHeapStart());//从mRtvHeap堆得到他的首地址rtvHeapHandle.Offset(index, mRtvDescriptorSize);//根据首地址和这个类型数据的大小进行偏移,看看,这就是我们从第二步得到的RTV这种类型的存储空间大小,这里的逻辑就是:

我们希望得到的地址rtvHeapHandle=首地址+index*mRtvDescriptorSize

在我们希望到达的地址处,也就是堆中的具体位置,把我们从SwapChain中得到的数据绑定进去

md3dDevice->CreateRenderTargetView(mSwapChainBuffer[i].Get(), nullptr, rtvHeapHandle);

这就意味着以后从堆,偏移到对应地址,可以拿到RTV中的数据。

哇,好难啊,好绕啊,我也想简单一点,不过基本思路就是:1.获取交换链不同RTV中的存储数据;2.得到RTV堆对应索引存储位置;3.把这个数据与这个地址进行绑定;就实现了关联,猜测一下,这肯定不是只拷贝,肯定是指针这类引用,一个萝卜一个坑,提供一种从外界访问交换链数据的手段,因为我们可能会在输出合并阶段用到这个数据(渲染到纹理技术)。

3.创建DSV

很不幸,DSV不能像RTV那样创建,当然,意料之中,毕竟我们还没有给DSV开空间呢。DSV是GPU的访问资源,创建DSV还是满足经典格式:结构体+创建。

D3D12_RESOURCE_DESC depthStencilDesc;
    depthStencilDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
    depthStencilDesc.Alignment = 0;
    depthStencilDesc.Width = mClientWidth;
    depthStencilDesc.Height = mClientHeight;
    depthStencilDesc.DepthOrArraySize = 1;
    depthStencilDesc.MipLevels = 1;

depthStencilDesc.Format = DXGI_FORMAT_R24G8_TYPELESS;

depthStencilDesc.SampleDesc.Count = m4xMsaaState ? 4 : 1;
    depthStencilDesc.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;
    depthStencilDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
    depthStencilDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;

D3D12_CLEAR_VALUE optClear;
    optClear.Format = mDepthStencilFormat;
    optClear.DepthStencil.Depth = 1.0f;
    optClear.DepthStencil.Stencil = 0;
    ThrowIfFailed(md3dDevice->CreateCommittedResource(
        &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
        D3D12_HEAP_FLAG_NONE,
        &depthStencilDesc,
        D3D12_RESOURCE_STATE_COMMON,
        &optClear,
        IID_PPV_ARGS(mDepthStencilBuffer.GetAddressOf())));

相当的惊人,这个结构体好长(小声bb我才不会自己写,都是抄的源码)。它包含的是一些深度信息设置,清楚信息等,最后,关键点:CreateCommittedResource,这个函数的作用

1.创建资源(数据)mDepthStencilBuffer;

2.创建一个堆CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT);

结束了吗?有了数据和存数据的数组。好像结束了,看看这里:

int arr[10]={};1,2,3,4,5....数据咋存不进数组呢?因为数据不是数组的元素。

也许我理解的不对,但是还是那句话,我不会,所以你别喷,知道就来指导,谢谢。

那要怎么放进去呢?描述符(包含数据和解释数据)

D3D12_DEPTH_STENCIL_VIEW_DESC dsvDesc;
    dsvDesc.Flags = D3D12_DSV_FLAG_NONE;
    dsvDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;
    dsvDesc.Format = mDepthStencilFormat;
    dsvDesc.Texture2D.MipSlice = 0;
    md3dDevice->CreateDepthStencilView(mDepthStencilBuffer.Get(), &dsvDesc//这里也可以不指定格式使用nullptr那就直接使用资源数据定义好的格式, DepthStencilView());

OK经典操作:结构体+创建。这里有数据mDepthStencilBuffer,还使用DepthStencilView获得了DSV地址:

D3D12_CPU_DESCRIPTOR_HANDLE D3DApp::DepthStencilView()const
{
    return mDsvHeap->GetCPUDescriptorHandleForHeapStart();
}

又是一个获得数据,数据用描述符描述,存入堆(数组)的圆满解决。不过还是有注意事项的:

我们创建资源的时候他的类型是D3D12_RESOURCE_STATE_COMMON;

因此,要让后台能往里边写东西这是不对的,需要这样做:

mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(mDepthStencilBuffer.Get(),//对哪个资源
        D3D12_RESOURCE_STATE_COMMON, //之前的状态(读写权限)D3D12_RESOURCE_STATE_DEPTH_WRITE));//转换之后的状态(读写权限)

这叫做资源转换,是设置资源的读写权限的,防止读写冲突

注意一下在创建堆的时候只有一句,这里的参数是堆的类型,我们有四种类型:默认,上传,回读还有自定义,以我为数不多的学习经验,知道默认和上传就可以了,因为其他的我还没接触过,有例子的欢迎发来羞辱我一下,哈哈哈哈。

八、视口、剪裁矩形设置

能看到这里,那你很不错了,至少你攒足了劲想diss我或者你学到了一点东西,因为这之后很简单了。

视口设置——经典:结构体+创建

mScreenViewport.TopLeftX = 0;
    mScreenViewport.TopLeftY = 0;
    mScreenViewport.Width    = static_cast<float>(mClientWidth);
    mScreenViewport.Height   = static_cast<float>(mClientHeight);
    mScreenViewport.MinDepth = 0.0f;
    mScreenViewport.MaxDepth = 1.0f;

mCommandList->RSSetViewports(1, &mScreenViewport);

设置好了交给命令列表加进去,就这么简单,只是注意一下如果命令列表重置,这个也要重置。

剪裁矩形设置——为了直接排除一些像素的渲染,比如我们在界面上有一块是矩形的UI,根本看不到图形就可以用这个直接把这块裁减了,使用方法,你懂的,就是那啥。。。。

mScissorRect = { 0, 0, mClientWidth, mClientHeight }; //角点坐标,长宽

mCommandList->RSSetScissorRects(1, &mScissorRect);

注意事项也是一样的:如果命令列表重置,这个也要重置。

喜大普奔,咱们做完了,不知道我讲的你懂了吗?哈哈,希望你懂了,来指导我哪里错了。

一、D3D12学习笔记——初始化Direct3D相关推荐

  1. 二十、D3D12学习笔记——环境光遮蔽

    好多天都没有更新D3D12的学习笔记了,因为最近确实学得有点乏,所以换了个方向,总结了一下之前学习的OpenGL和一些图形渲染的高级话题.那么回到D3D的学习,今天我们要介绍一下龙书对于环境光遮蔽的讲 ...

  2. 十、D3D12学习笔记——纹理

    首先声明,本人是自学DX12,有很多的理解也许不到位,不过都是自己的理解.在很长一段时间里边,我从迷茫到有一天开始能看懂,现在是第三次开始刷DX12了,于是在此表明写作的初衷: 1.有一些DX12的学 ...

  3. JAVA学习笔记--初始化与清理

    编写程序时,常会由于变量没有初始化而产生各种错误:用完一个元素,如果不将其占用的内存资源释放,则会导致资源耗尽,这也很严重,为此,C++引入了构造器的概念,这是一个在创建对象时被自动调用的特殊方法,以 ...

  4. Introduction to 3D Game Programming with DirectX 11学习笔记 6 Direct3D中的绘制(一)

    顶点和顶点布局 在Direct3D中,顶点由空间位置和各种附加属性组成,Direct3D可以让我们灵活地建立属于我们自己的顶点格式:换句话说,它允许我们定义顶点的分量.要创建一个自定义的顶点格式,我们 ...

  5. dx12 龙书第七章学习笔记 -- 利用Direct3D绘制几何体(续)

    1.帧资源 之前,我们在处理CPU和GPU的同步问题时,采取以下方法:在每帧绘制的结尾调用D3DApp::FlushCommandQueue函数,以确保GPU在每一帧都能正确完成所有命令的执行 这样做 ...

  6. dx12 龙书第四章学习笔记 -- Direct3D的初始化

    1.预备知识: ①Direct3D 12概述: 通过Direct3D这种底层图形应用程序编程接口(Application Programming Interface, API),即可在应用程序中对图形 ...

  7. direct3D 学习笔记

    DirectX SDK 2006学习笔记1--框架 2008-01-31 13:20 友情提醒:所谓的框架是指SDK目录下/Samples/C++/Common路径下的DXUT系列函数包装.学习框架的 ...

  8. direct3D 学习笔记【转】

    DirectX SDK 2006学习笔记1--框架 2008-01-31 13:20 友情提醒:所谓的框架是指SDK目录下\Samples\C++\Common路径下的DXUT系列函数包装.学习框架的 ...

  9. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第四章:Direct 3D初始化

    学习目标 对Direct 3D编程在3D硬件中扮演的角色有基本了解: 理解COM在Direct 3D中扮演的角色: 学习基本的图形学概念,比如存储2D图像.页面切换,深度缓冲.多重纹理映射和CPU与G ...

最新文章

  1. Android_(消息提示)多种使用Toast的消息提示
  2. adf4351_在ADF实体PK属性中使用MySQL自动增量PK列
  3. 十分钟了解分布式计算:Petuum
  4. 畅享云游戏,AWS云峰会邀你零距离体验强化学习!
  5. 流畅的Python 5. 函数
  6. 生信宝典联合科学出版社在双 11推出生物信息专题书单 5 折优惠!学起来!
  7. libevent:信号、超时、回调
  8. Android仿苹果版QQ下拉刷新实现(二) ——贝塞尔曲线开发鼻涕下拉粘连效果
  9. C++进阶教程之模板
  10. 在Windows平台使用IIS部署Flask网站
  11. CF620E New Year Tree
  12. while用法小简介(涉及EOF用法)
  13. ACM_贪心(HDU2037HDU1789)
  14. SQL 事务 begin tran、commit tran、rollback tran 的用法
  15. XMind中怎么导入图标?
  16. pytorch, tensorflow, keras统计模型参数大小
  17. dabo(达泊西汀)
  18. 微信公众号、地图定位、获取地理位置
  19. 信息安全——ELGamal数字签名方案的实现
  20. SAP License:ERP仓库管理系统怎么用?

热门文章

  1. 宝塔面板Ubuntu18.04安装过程
  2. MATLAB:im2bw()原理
  3. 小废的微软大会边缘行(图)
  4. 彻底搞懂 JS 中 this 机制 1
  5. React项目搭建命令
  6. Github 创建新分支
  7. jstack命令相关参数解释(tid,nid,prio,os_prio)
  8. Old Bill(枚举)
  9. 自然几何之分形(1)
  10. Word里怎么把分数打出来(转)