一、D3D12学习笔记——初始化Direct3D

这篇具有很好参考价值的文章主要介绍了一、D3D12学习笔记——初始化Direct3D。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一.创建设备

工厂类IDXGIFactory4,这个类有两个作用:

  1. 1.枚举适配器(显卡);
  2. 2.创建交换链

这个类对象的创建如下:

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和对象的指针。

二、创建围栏

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

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.");

可以查询到当前设置采样数下的最高级别,这可以用在创建交换链时设置多重采样参数。

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

三者的关系和创建方法:

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中。

GPU在使用的时候是对一整个命令数组进行执行,所以传递为数组地址与数组长度:

ID3D12CommandList* cmdsLists[] = { mCommandList.Get() };
mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);

一旦执行,命令就交给CommandAllocator来关联,此时CommandList->Reset也是可以的,但是在命令执行完之后才能对CommandAllocator->Reset()

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

注意事项:

创建时,或者使用Reset函数(命令列表和分配器)都处于打开的状态,特别要注意的是在ExecuteCommandLists之前要先关闭mCommandList。

六、交换链

为了实现流畅的页面翻转。创建交换链也是经典逻辑:结构体+创建。这里的结构体包含了对缓冲区(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;

//Important**************************Important
    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;//全屏自动适应窗口

    //命令队列是创建交换链的关键要素
    ThrowIfFailed(mdxgiFactory->CreateSwapChain(
        mCommandQueue.Get(),//这里是不是代表着切换是由命令队列来驱动的,好像还是自动的
        &sd, 
        mSwapChain.GetAddressOf()));//返回所创建的交换链接

此处相当于建立了CommandQueue和SwapChain的联系

七、描述符相关

0.基本概念:

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

描述符堆:存储描述符的数组,所以描述符堆中描述符具有一致类型。

注意:

        描述符必须存储在描述符堆上;

        一种描述符(多个)可以存储在多个描述符堆上,一个描述符堆也可以存储多个描述符(一种)

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

 

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

D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc;
    rtvHeapDesc.NumDescriptors = SwapChainBufferCount;//描述符堆Size,数组就该有长度
    rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;//RTV的堆,数组需要指定类型
    rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
    rtvHeapDesc.NodeMask = 0;//GPU,数组的存储位置空间
    ThrowIfFailed(md3dDevice->CreateDescriptorHeap(
        &rtvHeapDesc, IID_PPV_ARGS(mRtvHeap.GetAddressOf())));

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

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

2.创建描述符

2.1RTV

描述符,比如RTV,这就相当于数组的具体元素了。数组的元素存的实际是地址,对于RTV,他的地址是创建时分配好的,所以要做的工作是获得这个地址,并存入描述符堆。那么这个地址在哪里?

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

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

根据得到的资源数据构建RTV并放在描述符堆当中:

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

数组mRtvHeap这个描述符堆从首地址开始填充数据,每填充一次要进行偏移,偏移大小根据描述符类型查询的得到的Size执行:

CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHeapHandle(mRtvHeap->GetCPUDescriptorHandleForHeapStart());//从mRtvHeap堆得到他的首地址
rtvHeapHandle.Offset(index, mRtvDescriptorSize);//根据首地址和这个类型数据的大小进行偏移

这里的逻辑就是:rtvHeapHandle=首地址+index*mRtvDescriptorSize

完整过程如下:

CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHeapHandle(mRtvHeap->GetCPUDescriptorHandleForHeapStart());
	for (UINT i = 0; i < SwapChainBufferCount; i++)
	{
		ThrowIfFailed(mSwapChain->GetBuffer(i, IID_PPV_ARGS(&mSwapChainBuffer[i])));
		md3dDevice->CreateRenderTargetView(mSwapChainBuffer[i].Get(), nullptr, rtvHeapHandle);
		rtvHeapHandle.Offset(1, mRtvDescriptorSize);
	}

基本思路就是:1.获取交换链不同RTV中的存储数据;2.得到RTV堆对应索引存储位置;3.根据资源创建RTV存入描述符堆。

2.2.DSV

DSV不能像RTV那样创建,因为我们还没有给DSV开空间(RTV的空间在交换链上)。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())));

结构体包含的是一些深度信息设置,清楚信息等,最后,关键点:CreateCommittedResource,这个函数的作用

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

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

但此时数据还没存入数组(堆),那要怎么放进去呢?描述符(包含数据和解释数据)

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;
//DSBuffer资源创建DSV存储在DSV堆上
    md3dDevice->CreateDepthStencilView(mDepthStencilBuffer.Get(), &dsvDesc,//这里也可以不指定格式使用nullptr那就直接使用资源数据定义好的格式
    DepthStencilView());

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

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

注意事项:

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

因此,要让后台能往里边写东西这是不对的,需要资源屏障转换,防止读写冲突

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

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

八、视口、剪裁矩形设置

视口设置

调整在窗口上的显示区域,或者说是渲染的后台缓冲区的区域

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,根本看不到图形就可以用这个直接把这块裁减了。文章来源地址https://www.toymoban.com/news/detail-495014.html

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

mCommandList->RSSetScissorRects(1, &mScissorRect);

到了这里,关于一、D3D12学习笔记——初始化Direct3D的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • d3d12龙书阅读----数学基础 向量代数、矩阵代数、变换

    d3d12龙书阅读----数学基础 向量代数、矩阵代数、变换 directx 采用左手坐标系 点积与叉积 点积与叉积的正交化 使用点积进行正交化 使用叉积进行正交化 矩阵与矩阵乘法 转置矩阵 单位矩阵 逆矩阵 矩阵行列式 变换 旋转矩阵 坐标变换 利用DirectXMath库进行向量运算、矩阵运算以

    2024年02月19日
    浏览(48)
  • DirectX12(D3D12)基础教程(二十一)—— PBR:IBL 的数学原理(1/5)

      2023年来了,令人闹心伤身的疫情也暂告一段落了。感慨之余,其实我也挺惆怅,这个系列教程还能继续下去吗?或者我自己还能坚持多久,我不知道。因为我也天天徘徊在失业的边缘,年纪大了被人嫌弃,学历低被人嫌弃,身体稍差也被人嫌弃,忽然发现我已不是当初那

    2023年04月08日
    浏览(57)
  • DirectX12(D3D12)基础教程(二十一)—— PBR:IBL 的数学原理(5/5)镜面反射积分项2及光照合成

    3.5.4、根据 Epic 近似假设进一步拆分积分项为两部分之积   通过之前的步骤,实际上以及得到了我们想要的镜面反射项的蒙特卡洛积分重要性采样的形式,并且根据我们的假设认为视方向等于法线方向,实际上以及可以编码实现这个积分计算过程,而且依据假设我们不再需

    2023年04月08日
    浏览(50)
  • DirectX12_Windows_GameDevelop_3:Direct3D的初始化

    查看龙书时发现, 第四章介绍预备知识的代码不太利于学习 。因为它不像是LearnOpenGL那样从头开始一步一步教你敲代码,导致你没有一种整体感。 如果你把它当作某一块的代码进行学习,你跟着敲会发现,总有几个变量是没有定义的。这是因为书上的代码都是把框架里的某

    2024年02月08日
    浏览(44)
  • 国民技术N32G430开发笔记(12)- IAP升级 Settings区域数据初始化

    1、假如,有两个产品,A产品跟B产品,硬件都一样,要求一个软件里的board_name为N32G430C8L7_STB_A,另一个软件里的board_name为N32G430C8L7_STB_B。 那我们如何在不改boot程序跟App程序的基础上,快速的使两个软件看上去不同呢? 这里我们将setting区域的数据提前初始化,通过c语言的文

    2024年02月01日
    浏览(51)
  • STM32的GPIO初始化配置-学习笔记

            由于刚开始没有学懂GPIO的配置原理,导致后面学习其它外设的时候总是产生阻碍,因为其它外设要使用前,大部分都要配置GPIO的初始化,因此这几天重新学习了一遍GPIO的配置,记录如下。         首先我们要知道芯片上的引脚,并不是只有GPIO的功能,还能复用成

    2024年04月17日
    浏览(56)
  • Java开源项目mall学习笔记(1)——项目初始化

            该笔记是记录学习开源项目mall过程的文档笔记,完全原创,转载请声明。同时也对开源项目的作者表示感谢! mall: 🔥 mall项目是一套基于 SpringBoot + Vue + uni-app 实现的电商系统,包括前台商城项目及后台管理系统,采用Docker容器化部署。前台商城系统包含首页门户

    2024年02月12日
    浏览(44)
  • STM32 GPIO设置(GPIO初始化)学习笔记

    GPIO 都知道是 通用输入输出接口 的意思就不详细解释 那么我们就直接进入怎么设置GPIO接口: 这里我的编译软件是keil5,相信大家都应该知道stm32有各种的工作模式上拉、下拉、推挽、开漏等等。如果想要了解具体的工作模式原理这里我推荐大家看:推挽 开漏 高阻 这都是谁

    2024年03月28日
    浏览(59)
  • Android12 P2P 初始化

    目录  学习资料:Android Miracast 投屏 1. 开机log 2. WifiP2pService 启动

    2024年02月06日
    浏览(50)
  • ORB-SLAM2算法12之单目初始化Initializer

    ORB-SLAM2算法7详细了解了 System 主类和多线程、

    2024年02月10日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包