目录
1、前言
经过了第二部分教程的“折腾”之后,后面的教程我觉得应该顺畅多了。至少我现在可以一天时间就把教程示例代码调通,并且可以按照自己的想法自由的去发挥了。我很喜欢这种感觉,就像在打游戏中虐那些无脑的机器AI角色一样。
经过前面两章的学习,我相信大家对D3D12的编程复杂性应该有所认识了。那么这一章让我们放慢脚步,不再过多的学习知识点。开始缓慢爬坡,因为后面还有更吸(fu)引(za)人的内容等着我们一起去学习探讨。所以喘口气还是必要的。
通过前两章的例子,我发现虽然刻意没有使用什么封装,甚至连定义函数都没有的方式线性的灌输概念,但是由于D3D12本身在编程上的复杂和繁琐,因此代码的可读性实际上是不高的,这非常不利于学习,所以本章起,我使用中括号将每部分相关代码都放在一起,并且加上步骤序号和说明,方便大家线性的阅读和理解(使用VS的大纲折叠显示功能)。这也是为以后的例子代码做一个准备。因为说实话我在组织准备每篇教程的例子代码时,有时也是想吐的,虽然只是画一个简单的纹理,但代码量随便就超过了1000行,再往后我发现居然有点hold不住了。所以就使用了这个方法稍微组织一下。
言归正传,本篇教程中,我将重点讲解一下显卡的架构、独立堆、定位方式的资源,以及动态采样器的用法,这些是D3D12中新加入的比较新的概念性的东西,我认为这些才是真正的D3D12中比较核心的一些概念,因此掌握它们才是用好用活D3D12必须要学习的。
2、显卡架构和存储管理
在一开始的教程中,我已经提到过D3D12是一个很“低级”的接口,在我的系列文章《D3D11和D3D12多线程渲染框架的比较》中我甚至提到D3D12就是一个“显卡操作系统”。其实我这样的说的另一重目的是说,要彻底学懂D3D12,那就需要像我们学习汇编语言或C语言一样,对计算机底层的架构甚至较详细的硬件架构有所了解。否则学习这些内容就将停留在表面,很难深入的理解和掌握。
因此学习D3D12,就需要我们对现代的显卡子系统以及它与系统交互连接的方式有所了解。
首先我们在之前的教程中已经说过现代的GPU上是有很多可以并行执行命令的引擎的,如下图所示:
这个图来自MSDN,它很形象的说明了一个GPU上至少有三大类引擎,一个是复制引擎(Copy engine)、一个是计算引擎(Compute engine)、另一个是3D引擎(3D engine),实质上如果以最新的Nvidia的20xx系显卡GPU核来说,其上还有AI引擎、以及独立的专门做实时光追运算的内核,当然还有跟我们渲染没太大关系的视频处理引擎等,未来不排除D3D中会不会加入对这些独立的引擎以及对应类型的命令队列的支持。所以现在了解下GPU的架构及编程理念,至少不至于在未来因为不能理解编程框架而被淘汰掉。
同时这幅图上实际更进一步的示意说明的了CPU线程和GPU引擎之间如何交互命令,以及并行执行的原理。核心还是使用命令列表记录命令,再将命令列表在命令队列中排队,然后各引擎从命令队列中不断获取命令并执行的基本模式(回忆一下我们之前教程中提到的饭馆模型),至此我想关于这个CPU、GPU并行执行的概念大家应该是深刻理解并消化了。需要再次强调的就是虽然命令列表是分开录制的,但是它们被排队至命令队列之后,宏观上各个引擎在执行它们时仍然是串行顺序的,而在微观上针对一个个的原子数据,比如:纹理的像素、网格的顶点之类的则是并行的(SIMD)。
当然上图还是从线程角度或者说动态执行的角度来看现代CPU+GPU体系结构的视图。而另一个方面就需要我们进一步去了解CPU+GPU是如何管理和使用内存的,以便于我们深入掌握D3D12中的内存管理方法,从而为真正提高性能做好准备。我想提高性能对于一个引擎或者游戏来说意味着什么,就不需要我过分强调了。
从类型上来说现代的PC、笔记本甚至手机中CPU和GPU本质上都是独立的处理器,它们之间使用的是被称为SMP架构模型进行互联并相互协作的,SMP即共享存储型多处理机(Shared Memory mulptiProcessors),)也称为对称型多处理机(Symmetry MultiProcessors)。
或者直白的说CPU和GPU间的交互就是通过共享内存这种方式来进行的,但他们各自又有各自的内存控制器和管理器,甚至各自还有自己的片上高速缓存,因此最终要共享内存就需要一些额外的通信控制方式来进行,这也是我们使用D3D12进行存储管理编程的复杂性的根源。这里要注意的是从第一篇教程起我就特别说明是存储管理,而不是只说内存管理(CPU侧)、显存管理(GPU侧)或者共享内存(SMP中CPU和GPU共享的内存),这里大家要特别注意这个概念上的区别。因为在D3D12中我们需要管理的不仅仅是GPU上的显存,根据SMP的描述,我们还需要额外管理二者之间共享的内存。当然我想CPU的内存如何管理各位C/C++程序员应该已经轻车熟路了,就不需要我多啰嗦了。
更进一步的SMP架构又被细分为:均匀存储器存取(Uniform-Memory-Access,简称UMA)模型、非均匀存储器存取(Nonuniform-Memory-Access,简称NUMA)模型和高速缓存相关的存储器结构(cache-coherent Memory Architecture,简称CC-UMA)模型,这些模型的区别在于存储器和外围资源如何共享或分布。
UMA架构模型示意图如下:
从图中可以看出UMA架构中物理存储器被所有处理机均匀共享。所有处理机对所有存储器具有相同的存取时间,这就是为什么称它为均匀存储器存取的原因。每个处理器(CPU或GPU)可以有私有高速缓存,外围设备也以一定形式共享(GPU因为没有访问外围其他设备的能力,实质就不共享外围设备了,这里主要指多个CPU的系统共享外围设备)。实质上UMA方式是目前已经很少见的主板集成显卡的方式之一。需要注意的是这里只是一个简化的示意图,里面只示意了一个CPU和GPU的情况,实质上它是可以扩展到任意多个CPU或GPU互通互联的情况的。
NUMA架构的示意图如下:
从NUMA的示意图中可以看出,其存储器物理上是分布在所有处理器的本地存储器上。本地存储器的一般具有各自独立的地址空间,因此一般不能直接互访问各自的本地存储。而处理器(CPU或GPU)访问本地存储器是比较快的,但要访问属于另一个处理器的远程存储器则比较慢,并且需要额外的方式和手段,因此其性能也是有额外的牺牲的。其实这也就是我们现在常见的“独显”的架构。当然一般来说现代GPU访问显存的速度是非常高的,甚至远高于CPU访问内存的速度。所以在编程中经常要考虑为了性能,而将尽可能多的纯GPU计算需要的数据放在显存中,从而提高GPU运算的效率和速度。
CC-UMA架构的示意图如下:
如图所示,CC-UMA是一种只用高速缓存互联互通的多处理器系统。CC-UMA模型是NUMA机的一种特例,只是将后者中分布主存储器换成了高速缓存, 在每个处理器上没有存储器层次结构,全部高速缓冲存储器组成了全局地址空间。通常这是现代CPU中集显最容易采取的架构方式。当然高速缓存共享或直连的方式拥有最高的互访性能。但其缺点就是高速缓存因为高昂的价格,所以往往空间很小,目前的集显上还只有几兆,最多到几十兆高速缓冲的样子,所以对于现代的渲染来说这点存储量实在是少的可怜了。另外因为高速缓存是在不同的处理器(CPU或GPU)之间直接共享或互联的,因此还有一个额外的问题就是存储一致性的问题,就是说高速缓冲的内容跟实质内存中的内容是否一致,比如CPU实质是将数据先加载进内存中然后再加载进高速缓冲的,而GPU在CPU还没有完成从内存到高速缓冲的加载时,就直接访问高速缓冲中的数据就会引起错误了,反之亦然。因此就需要额外的机制来保证存储一致性,当然这就导致一些额外的性能开销。具体的关于存储一致性的内容,我就不多讲了,我们主要还是要靠独显来干活。进一步的知识大家有兴趣的可以百度一下相关资料。
具体来说,比如我的笔记本电脑上就有一个“集显”也有一个“独显”,集显跟CPU形成了CC-UMA架构,并且它独占了128M的内存当做显存,而独显则与CPU形成了NUMA架构,独显上有2G的独立显存,两个GPU都与CPU共享了8149M的内存,作为统一的共享内存。其实这也可以看出实际的系统中往往是上述架构混用的形式。
通过运行Dxdiag程序就可以看到这些信息:
上图是集显情况,下面则显示了独显的情况:
因为我的系统总共有16G的内存,所以DX子系统干脆就共享了8149M的内存作为三个处理器(CPU+2个GPU,你应该已经习惯我用处理器来指代CPU或GPU了)之间的公共内存。这样做的意义在哪里呢?除了首先想到的可以拥有巨大的可用的显存之外,其实它更重要的深层意义就是可以让这两个GPU因共享相同的公共内存,实现数据的互通互联,从而可以并行的工作,这也是D3D12重要的高级特性之一——支持多显卡渲染尤其是异构多显卡渲染。也为支持DXR中的多显卡实时光追渲染提供了基础支撑能力。
综上,实质上这些架构之间的主要区别是在各处理器访问存储的速度上,简言之就是说使用高速缓存具有最高的访问速度。其次就是访问各自独占的存储,而最慢的就是访问共享内存了,当然对于CPU来说访问共享内存与自己独占的内存在性能是基本没有差异的。这里的性能差异主要是从GPU的角度来说的。因此我们肯定愿意将一些CPU或GPU专有的数据首先考虑放在各自的独占存储中,其次需要多方来访问的数据就放在共享内存中。这也就是我们上一讲提到的D3D12中不同种类的存储堆的本质含义。
另外需要提醒的是,现代的CPU+GPU以及系统的架构都是在不断进化和变化的,目标就是更高的效率和性能,因此这里说的架构仅仅还只是概念上的模型,跟实际的系统架构可能还有出入,如果想进一步了解这类信息就请各位关注硬件类网站最新的一些CPU或GPU显卡测试类的文章,其中往往会提及一些最新的架构方面的知识。了解的目的就是为了更好的从根本上去了解软件框架为什么是这个样子,从而提高学习的效率和效果。这也是我这么多年学习的一个经验总结之一。
最后我们可以通过下面的简单程序来检测我们的GPU与系统是以什么架构互联的,同时我们可以准确的知道他们各自独占存储和共享存储的情况。代码如下(可独立运行):
#include <SDKDDKVer.h>
#define WIN32_LEAN_AND_MEAN // 从 Windows 头中排除极少使用的资料
#include <windows.h>
#include <tchar.h>
#include <strsafe.h> //for StringCchxxxx function
//添加WTL支持 方便使用COM
#include <wrl.h>
using namespace Microsoft;
using namespace Microsoft::WRL;
#include <dxgi1_6.h>
#include <d3d12.h> //for d3d12
//linker
#pragma comment(lib, "dxguid.lib")
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "d3d12.lib")
#define GRS_THROW_IF_FAILED(hr) if (FAILED(hr)){ throw CGRSCOMException(hr); }
class CGRSCOMException
{
public:
CGRSCOMException(HRESULT hr) : m_hrError(hr)
{
}
HRESULT Error() const
{
return m_hrError;
}
private:
const HRESULT m_hrError;
};
#define GRS_USEPRINTF() TCHAR pBuf[1024] = {};DWORD dwRead = 0;
#define GRS_PRINTF(...) \\
StringCchPrintf(pBuf,1024,__VA_ARGS__);\\
WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE),pBuf,lstrlen(pBuf),NULL,NULL);
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
{
UINT nDXGIFactoryFlags = 0U;
ComPtr<IDXGIFactory5> pIDXGIFactory5;
ComPtr<IDXGIAdapter1> pIAdapter;
ComPtr<ID3D12Device4> pID3DDevice;
GRS_USEPRINTF();
try
{
AllocConsole(); //打开窗口程序中的命令行窗口支持
#if defined(_DEBUG)
{//打开显示子系统的调试支持
ComPtr<ID3D12Debug> debugController;
if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController))))
{
debugController->EnableDebugLayer();
// 打开附加的调试支持
nDXGIFactoryFlags |= DXGI_CREATE_FACTORY_DEBUG;
}
}
#endif
//1、创建DXGI Factory对象
GRS_THROW_IF_FAILED(CreateDXGIFactory2(nDXGIFactoryFlags, IID_PPV_ARGS(&pIDXGIFactory5)));
//2、枚举适配器,并检测其架构及存储情况
DXGI_ADAPTER_DESC1 desc = {};
D3D12_FEATURE_DATA_ARCHITECTURE stArchitecture = {};
for (UINT adapterIndex = 0; DXGI_ERROR_NOT_FOUND != pIDXGIFactory5->EnumAdapters1(adapterIndex, &pIAdapter); ++adapterIndex)
{
pIAdapter->GetDesc1(&desc);
if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)
{//跳过软件虚拟适配器设备
continue;
}
GRS_PRINTF(_T("显卡[%d]-\\"%s\\":独占显存[%dMB]、独占内存[%dMB]、共享内存[%dMB] ")
, adapterIndex
, desc.Description
, desc.DedicatedVideoMemory / (1024*1024)
, desc.DedicatedSystemMemory / (1024*1024)
, desc.SharedSystemMemory / (1024 * 1024)
);
//创建设备,并检测架构
//檢測顯卡架構類型
//D3D12_FEATURE_DATA_ARCHITECTURE.UMA = true一般為集顯
//此時若D3D12_FEATURE_DATA_ARCHITECTURE.CacheCoherentUMA = true 則表示是CC-UMA架構 GPU和CPU通過高速緩存來交換數據
//若UMA = FALSE 一般表示為獨顯,此時CacheCoherentUMA 無意義
GRS_THROW_IF_FAILED(D3D12CreateDevice(pIAdapter.Get(), D3D_FEATURE_LEVEL_12_1, IID_PPV_ARGS(&pID3DDevice)));
GRS_THROW_IF_FAILED(pID3DDevice->CheckFeatureSupport(D3D12_FEATURE_ARCHITECTURE
, &stArchitecture, sizeof(D3D12_FEATURE_DATA_ARCHITECTURE)));
if (stArchitecture.UMA)
{//UMA
if ( stArchitecture.CacheCoherentUMA )
{
GRS_PRINTF(_T("架构为(CC-UMA)"));
}
else
{
GRS_PRINTF(_T("架构为(UMA)"));
}
}
else
{//NUMA
GRS_PRINTF(_T("架构为(NUMA)"));
}
if ( stArchitecture.TileBasedRenderer )
{
GRS_PRINTF(_T(" 支持Tile-based方式渲染"));
}
pID3DDevice.Reset();
GRS_PRINTF(_T("\\n"));
}
::system("PAUSE");
//释放命令行环境,做个干净的程序员
FreeConsole();
}
catch (CGRSCOMException& e)
{//发生了COM异常
e;
}
return 0;
}
独立建立项目并运行上述程序后,在我的笔记本电脑上运行结果如下:
这实际与运行Dxdiag工具显示的结果是相同的。当然这种在程序中使用代码检测的方式就要比使用独立的工具的方式更灵活方便,所以建议大家一定要掌握这种代码方式,主要是为了将来在封装引擎或开发游戏中可以灵活使用。
当然你也可以使用上述代码中的检测手段更准确的判定多显卡系统中究竟哪个适配器是独显,哪个适配器是集显。这也是后续异构多显卡渲染教程中将要使用的方法。建议你明白了这段代码之后,可以在所有的例子中加入判定使用独显的判断代码,并使用独显来运行本系列教程中的例子。
3、创建默认堆并在其上以“定位方式”创建2D纹理
纹理(Txture)作为3D渲染中最重要的资源之一,操作纹理也是任何3D渲染程序都不可或缺的核心功能。因此我们很有必要牢牢掌握操作纹理的能力。
当然纹理一般在整个场景渲染过程中一般是不会变化的,所以我们优先考虑将它放置到显存中,在D3D12中也就是将纹理放在默认堆上。并且之前的教程中我们已经学习了如何使用最简单的“隐式堆”的方式来创建默认堆上的纹理资源,并且我们也说过了这种方式因为我们无法控制隐式堆的生命期,所以没法通过重用缓冲的方式来提高系统性能。
说到这里我想你应该有点明白我为什么在本篇教程一开始啰嗦了一大堆显卡系统架构的原因了。其实在这里我们可以将D3D12中的默认堆理解为在GPU显存中的一块缓冲,它对GPU来说就是“默认”的存储位置,故名默认堆。因此放置其上的纹理以及资源对于GPU来说是拥有最高的访问速度,当然付出的代价就是CPU是无法直接访问GPU上的显存的(注意前述架构中显存只能由GPU访问就明白了),所以我们就需要使用一个额外的上传堆并利用复制引擎来加载纹理或其它数据。也正如你所想的上传堆就不折不扣的在共享内存中了,这样就使得CPU也能访问它,GPU也能访问它,当然最终为了从共享内存中把数据传到显存中,那么就需要GPU上的复制引擎来干活了。只是付出的代价就是我们需要协调CPU和GPU同时通知它们某段共享内存被用来当做上传堆了,这甚至比CPU单独管理内存需要额外付出一些性能代价。对于数据量不大,并且不会反复申请-释放的程序来说这没什么,有时候甚至感觉不到这种因为分配和释放存储而导致性能低下的问题。但是对于一些大型的游戏程序来说,尤其有不同关卡场景,不同物体的复杂应用来说,不可避免的就需要大量反复的申请-加载-释放资源的调用。在D3D12之前的接口上,我们要想优化它几乎是无能为力的。当然之前的教程中我们已经讲过,聪明的游戏程序员是通过“化零为整”的方法来减少分配-释放的次数从而提高性能的。当然如果你能够在自己的引擎或游戏中综合利用这两种能力,那么最终带来的性能提升一定是相当可观的。
为了能够控制这些被分配的数据缓冲(包括纹理)的生命周期,同时能够通过反复重用,减少分配和释放存储的次数从而提高性能,D3D12中就提供了独立的堆管理的接口。比如创建一个默认堆就可以像下面这样写代码:
D3D12_HEAP_DESC stTextureHeapDesc = {};
//为堆指定纹理图片至少2倍大小的空间,这里没有详细去计算了,只是指定了一个足够大的空间,够放纹理就行
//实际应用中也是要综合考虑分配堆的大小,以便可以重用堆
stTextureHeapDesc.SizeInBytes = GRS_UPPER(2 * nPicRowPitch * nTextureH, D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT);
//指定堆的对齐方式,这里使用了默认的64K边界对齐,因为我们暂时不需要MSAA支持
stTextureHeapDesc.Alignment = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT;
stTextureHeapDesc.Properties.Type = D3D12_HEAP_TYPE_DEFAULT; //默认堆类型
stTextureHeapDesc.Properties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
stTextureHeapDesc.Properties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
//拒绝渲染目标纹理、拒绝深度蜡板纹理,实际就只是用来摆放普通纹理
stTextureHeapDesc.Flags = D3D12_HEAP_FLAG_DENY_RT_DS_TEXTURES | D3D12_HEAP_FLAG_DENY_BUFFERS;
GRS_THROW_IF_FAILED(pID3DDevice->CreateHeap(&stTextureHeapDesc, IID_PPV_ARGS(&pITextureHeap)));
代码中pITextureHeap的类型为ID3D12Heap*。这里再次强调一下虽然它的名字中带有Heap,但它实际和我们使用的C/C++存储管理中的堆栈是不同的。D3D12中的堆还是静态大小的,即我们分配多大它就始终是多大,还没法完全做到像C/C++中那样利用realloc这样的方法来动态改变大小。这主要是因为,如刚才所说这个默认堆的存储虽然是在GPU的显存中,但管理它其实是靠CPU的能力来与GPU通信完成的,它具有一定的复杂性和额外的开销,所以还无法做到类似realloc这样的功能。当然我想聪明的你一定不会被这个问题难道,事实上我们可以利用复制引擎来封装一个具有realloc功能的动态堆出来,比如,我们需要将一块默认堆增大的时候,就可以先重新创建一个更大的ID3D12Heap堆,再利用复制引擎将原来堆上的数据复制到新的更大的堆上,最后释放原来的堆即可。其实这样也还是要分配和释放内存,所以功能实现了,但是性能实质上没什么改进。最终我们现在可以采取的策略就是像上面示例代码中这样事先分配一块尽可能大的存储,然后想办法重用它,而不是反复的分配和释放浪费时间。
除了大小问题,其实上面代码中还要求我们的存储区域是边界对齐的,目前对于一般的缓冲和纹理来说,D3D12中都要求64K边界对齐,对应的宏就是D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT,而对于需要MSAA(抗锯齿)采样支持的纹理数据来说,就需要4M边界对齐了,对应的宏是D3D12_DEFAULT_MSAA_RESOURCE_PLACEMENT_ALIGNMENT。如果你指定0,其默认含义一样是64k边界对齐。因此,我们对于缓冲区的大小这个参数使用了一个上对其计算,我将它定义成了宏GRS_UPPER,其定义如下:
#define GRS_UPPER(A,B) ((UINT)(((A)+((B)-1))&~(B – 1)))
这个算法与之前教程讲到的(A+B-1)/B的上取整算法类似,只是它不用去做除法了,而是直接通过与非运算最大的余数,从而使数字上对齐,这个算法也希望各位能够记住,因为对于任何与存储管理相关的内容来说,这两个算法都是最常用的。
当然你可以把它用作你的面试题,从而难住那些没有看过我这些文章的应聘者,只是不要说是我说的就行。
这个宏的参数中,A表示我们需要的堆大小,B表示需要边界对齐的大小,最终结果就是A上对齐到B边界的大小。比如我们需要分配一块100K大小的存储,64k上对齐之后实质需要分配的是128K的内存大小。最后之所以需要存储边界对齐,是因为现代GPU是一个大的SIMD处理器也是RISC架构的,访问边界对齐的存储具有巨大的性能优势,甚至它们都不支持任意位置内存访问,必须以边界对齐的格式化的方式访问存储。而现代的x86架构的CPU则基本没有这个限制,当然代价就是其指令的复杂性和一定的性能损失,而优势就是在软件编写上尤其是变量存储分配上基本没有什么限制。因此也可以这样认为,如果我们在软件编写上有什么奇怪的限制时,往往其实是因为底层硬件设计使然。这也就是需要学习一些底层硬件架构知识的原因。
结构体D3D12_HEAP_DESC中的Properties参数,则是用来说明堆的类型的,当Type字段是D3D12_HEAP_TYPE_DEFAULT(默认堆), D3D12_HEAP_TYPE_UPLOAD(上传堆),D3D12_HEAP_TYPE_READBACK(回读堆),后面两个参数CPUPageProperty,MemoryPoolPreference就像这里这样赋值即可。或者理解为这些堆类型已经是系统预制好的,后面的参数在这些情况下实质上是没有意义的。
只有当Type是D3D12_HEAP_TYPE_CUSTOM时我们才能指定后面两个参数的值。通常这时我们需要指定CPUPageProperty就是CPU对这个堆内存的访问特性,还有就是通过MemoryPoolPreference指定在那个存储中——共享内存还是显存中。对于自定义堆将放在后续的教程中酌情进行讲解,现在我们暂时只关注前三种堆的类型。
最后我们需要指定的就是D3D12_HEAP_DESC 结构体的Flags参数,它的含义就是明确描述清楚这个堆上将用来存储什么资源。在这里我们指定的是拒绝属性,即拒绝放置渲染目标纹理和深度蜡板缓冲纹理以及普通缓冲,其含义就是说我们这个默认堆只是用来放置普通纹理的(因为普通纹理没有被拒绝)。当然这个标志值有点怪异,必须要反过来思考问题,即我们把不让放置的资源类型都拒绝掉,那么允许放置的就只有没被拒绝的类型了。当然这里还有个更奇怪的约定就是代码中使用的两个拒绝(Deny)类型必须要一起设置,单独设置一个是不被允许的。我想关于这个Flags字段的这些奇怪约定和值,应该是GPU特殊要求的,才会是这样一个严重反人类的样子。
其实上述代码中真正需要我们关注的正是这个Flags标志,后续通过它我们还可以指定独立堆应该是多个GPU共享的形式,具体的一些用法我们将逐步的讲解,就不在这里一下子全部告诉大家,防止因为概念太多而不好理解和记忆。它实在是设计的太反人类了!
最后一步,我们就是使用ID3D12Device::CreateHeap方法来创建独立的默认堆。
默认堆创建好之后,我们就使用CreatePlacedResource方法来创建具体的纹理资源了,代码如下:
stTextureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
stTextureDesc.MipLevels = 1;
stTextureDesc.Format = stTextureFormat; //DXGI_FORMAT_R8G8B8A8_UNORM;
stTextureDesc.Width = nTextureW;
stTextureDesc.Height = nTextureH;
stTextureDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
stTextureDesc.DepthOrArraySize = 1;
stTextureDesc.SampleDesc.Count = 1;
stTextureDesc.SampleDesc.Quality = 0;
//-----------------------------------------------------------------------------------------------------------
创建默认堆上的资源,类型是Texture2D,GPU对默认堆资源的访问速度是最快的
因为纹理资源一般是不易变的资源,所以我们通常使用上传堆复制到默认堆中
在传统的D3D11及以前的D3D接口中,这些过程都被封装了,我们只能指定创建时的类型为默认堆
//GRS_THROW_IF_FAILED(pID3DDevice->CreateCommittedResource(
// &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT)
// , D3D12_HEAP_FLAG_NONE
// , &stTextureDesc //可以使用CD3DX12_RESOURCE_DESC::Tex2D来简化结构体的初始化
// , D3D12_RESOURCE_STATE_COPY_DEST
// , nullptr
// , IID_PPV_ARGS(&pITexture)));
//-----------------------------------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------------------------------
//使用“定位方式”来创建纹理,注意下面这个调用内部实际已经没有存储分配和释放的实际操作了,所以性能很高
//同时可以在这个堆上反复调用CreatePlacedResource来创建不同的纹理,当然前提是它们不在被使用的时候,才考虑重用堆
GRS_THROW_IF_FAILED(pID3DDevice->CreatePlacedResource(
pITextureHeap.Get()
, 0
, &stTextureDesc //可以使用CD3DX12_RESOURCE_DESC::Tex2D来简化结构体的初始化
, D3D12_RESOURCE_STATE_COPY_DEST
, nullptr
, IID_PPV_ARGS(&pITexture)));
//-----------------------------------------------------------------------------------------------------------
//获取上传堆资源缓冲的大小,这个尺寸通常大于实际图片的尺寸
n64UploadBufferSize = GetRequiredIntermediateSize(pITexture.Get(), 0, 1);
代码中的注释已经说明了这段代码的一些要点。重要的就是要注意第二个参数,不再是Flags了,而是指定从堆开始处算起的偏移量,单位是字节。主要用于在一个堆上不断的Placed(放置)不同的数据。当然它也必须要边界对齐,必须是64K或者4M的整数倍。
默认堆上的2D纹理创建完了,我们就需要创建独立的上传堆了,与创建默认堆类似,其代码如下:
D3D12_HEAP_DESC stUploadHeapDesc = { };
//尺寸依然是实际纹理数据大小的2倍并64K边界对齐大小
stUploadHeapDesc.SizeInBytes = GRS_UPPER(2 * n64UploadBufferSize, D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT);
//注意上传堆肯定是Buffer类型,可以不指定对齐方式,其默认是64k边界对齐
stUploadHeapDesc.Alignment = 0;
stUploadHeapDesc.Properties.Type = D3D12_HEAP_TYPE_UPLOAD; //上传堆类型
stUploadHeapDesc.Properties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
stUploadHeapDesc.Properties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
//上传堆就是缓冲,可以摆放任意数据
stUploadHeapDesc.Flags = D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS;
GRS_THROW_IF_FAILED(pID3DDevice->CreateHeap(&stUploadHeapDesc, IID_PPV_ARGS(&pIUploadHeap)));
这段代码与之前的类似,需要注意的就是Flags标志,这次我们又正面的指定只允许缓冲的类型D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS,堆大小我们特意设置为两倍纹理图片数据的大小,并且上边界对齐。再一次领教它的反人类设计!
接着我们就可以像下面这样首先来创建上传堆上的纹理资源缓冲了:
GRS_THROW_IF_FAILED(pID3DDevice->CreatePlacedResource(
pIUploadHeap.Get()
, 0
, &CD3DX12_RESOURCE_DESC::Buffer(n64UploadBufferSize)
, D3D12_RESOURCE_STATE_GENERIC_READ
, nullptr
, IID_PPV_ARGS(&pITextureUpload)));
从上面代码我们可以看到其实只使用了一般不到的区域用来做我们的纹理资源数据缓冲了,那么剩下的部分如果不用的话就浪费了,所以我们可以接着象下面这样,把顶点缓冲也放在上面:
GRS_THROW_IF_FAILED(pID3DDevice->CreatePlacedResource(
pIUploadHeap.Get()
, GRS_UPPER(n64UploadBufferSize, D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT)
, &CD3DX12_RESOURCE_DESC::Buffer(nVertexBufferSize)
, D3D12_RESOURCE_STATE_GENERIC_READ
, nullptr
, IID_PPV_ARGS(&pIVertexBuffer)));
那么请注意第二个参数,我们设置了偏移量为纹理资源大小上边界对齐的位置,这里的对齐要求是必须的。从内存视图上看的话,其实就是我们将顶点缓冲区放在了需要上传的纹理资源图片数据的后面。这样我们就省去了一次顶点缓冲存储的实际的申请和释放操作,提高了效率和性能。
4、动态采样器
在第二篇教程中,我们已经演示和学习了如何使用根签名的静态参数化的方式来指定一个采样器,如当时我们所说,静态的采样器是不能随便改变的,如果要改变的话我们就需要重新创建一个根签名对象,这对于采样器的管理以及渲染管线状态的管理来说都有些太啰嗦了。所以我们这次就使用纯动态的方式来创建采样器。同时为了对采样器的类型建立一些初步的概念,所以我们就一次性创建了五个采样器。
要使用动态采样器,那么首先我们需要的就是创建一个采样器的描述符堆,代码如下:
D3D12_DESCRIPTOR_HEAP_DESC stSamplerHeapDesc = {};
stSamplerHeapDesc.NumDescriptors = nSampleMaxCnt;
stSamplerHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER;
stSamplerHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
GRS_THROW_IF_FAILED(pID3DDevice->CreateDescriptorHeap(&stSamplerHeapDesc,IID_PPV_ARGS(&pISamplerDescriptorHeap)));
nSamplerDescriptorSize = pID3DDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER);
CD3DX12_CPU_DESCRIPTOR_HANDLE hSamplerHeap(pISamplerDescriptorHeap->GetCPUDescriptorHandleForHeapStart());
D3D12_SAMPLER_DESC stSamplerDesc = {};
stSamplerDesc.Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR;
stSamplerDesc.MinLOD = 0;
stSamplerDesc.MaxLOD = D3D12_FLOAT32_MAX;
stSamplerDesc.MipLODBias = 0.0f;
stSamplerDesc.MaxAnisotropy = 1;
stSamplerDesc.ComparisonFunc = D3D12_COMPARISON_FUNC_ALWAYS;
// Sampler 1
stSamplerDesc.BorderColor[0] = 1.0f;
stSamplerDesc.BorderColor[1] = 0.0f;
stSamplerDesc.BorderColor[2] = 1.0f;
stSamplerDesc.BorderColor[3] = 1.0f;
stSamplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
stSamplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
stSamplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
pID3DDevice->CreateSampler(&stSamplerDesc, hSamplerHeap);
hSamplerHeap.Offset(nSamplerDescriptorSize);
// Sampler 2
stSamplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
stSamplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
stSamplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
pID3DDevice->CreateSampler(&stSamplerDesc, hSamplerHeap);
hSamplerHeap.Offset(nSamplerDescriptorSize);
// Sampler 3
stSamplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
stSamplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
stSamplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
pID3DDevice->CreateSampler(&stSamplerDesc, hSamplerHeap);
hSamplerHeap.Offset(nSamplerDescriptorSize);
// Sampler 4
stSamplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_MIRROR;
stSamplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_MIRROR;
stSamplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_MIRROR;
pID3DDevice->CreateSampler(&stSamplerDesc, hSamplerHeap);
hSamplerHeap.Offset(nSamplerDescriptorSize);
// Sampler 5
stSamplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_MIRROR_ONCE;
stSamplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_MIRROR_ONCE;
stSamplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_MIRROR_ONCE;
pID3DDevice->CreateSampler(&stSamplerDesc, hSamplerHeap);
与静态采样器类似我们都需要填充采样器描述信息的结构体,这里主要是区别下每个纹理坐标上具体的溢出后的采样方式(本例中我们刻意将最大纹理坐标扩大到了3.0f)。
这些类型我就不再具体啰嗦了,如果你不知道可以百度一下其他教程,或者你可以直接运行和调试本章的例子程序加深理解。如果不太懂纹理,那么先建立一个感性认识也是好的。
接着我们的要做的就是在创建根签名时明确指出我们需要的是动态的采样器,代码如下:
D3D12_FEATURE_DATA_ROOT_SIGNATURE stFeatureData = {};
// 检测是否支持V1.1版本的根签名
stFeatureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_1;
if (FAILED(pID3DDevice->CheckFeatureSupport(D3D12_FEATURE_ROOT_SIGNATURE, &stFeatureData, sizeof(stFeatureData))))
{
stFeatureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_0;
}
// 在GPU上执行SetGraphicsRootDescriptorTable后,我们不修改命令列表中的SRV,因此我们可以使用默认Rang行为:
// D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC_WHILE_SET_AT_EXECUTE
CD3DX12_DESCRIPTOR_RANGE1 stDSPRanges[2];
stDSPRanges[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0, 0, D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC_WHILE_SET_AT_EXECUTE);
stDSPRanges[1].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER, 1, 0);
CD3DX12_ROOT_PARAMETER1 stRootParameters[2];
stRootParameters[0].InitAsDescriptorTable(1, &stDSPRanges[0], D3D12_SHADER_VISIBILITY_PIXEL);
stRootParameters[1].InitAsDescriptorTable(1, &stDSPRanges[1], D3D12_SHADER_VISIBILITY_PIXEL);
//---------------------------------------------------------------------------------------------
//静态的采样器不用了,我们使用动态的在堆上创建的采样器
//D3D12_STATIC_SAMPLER_DESC stSamplerDesc = {};
//stSamplerDesc.Filter = D3D12_FILTER_MIN_MAG_MIP_POINT;
//stSamplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
//stSamplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
//stSamplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
//stSamplerDesc.MipLODBias = 0;
//stSamplerDesc.MaxAnisotropy = 0;
//stSamplerDesc.ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER;
//stSamplerDesc.BorderColor = D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK;
//stSamplerDesc.MinLOD = 0.0f;
//stSamplerDesc.MaxLOD = D3D12_FLOAT32_MAX;
//stSamplerDesc.ShaderRegister = 0;
//stSamplerDesc.RegisterSpace = 0;
//stSamplerDesc.ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
//---------------------------------------------------------------------------------------------
CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC stRootSignatureDesc;
//stRootSignatureDesc.Init_1_1(_countof(stRootParameters), stRootParameters
// , 1, &stSamplerDesc
// , D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);
stRootSignatureDesc.Init_1_1(_countof(stRootParameters), stRootParameters
, 0, nullptr
, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);
ComPtr<ID3DBlob> pISignatureBlob;
ComPtr<ID3DBlob> pIErrorBlob;
GRS_THROW_IF_FAILED(D3DX12SerializeVersionedRootSignature(&stRootSignatureDesc
, stFeatureData.HighestVersion
, &pISignatureBlob
, &pIErrorBlob));
GRS_THROW_IF_FAILED(pID3DDevice->CreateRootSignature(0
, pISignatureBlob->GetBufferPointer()
, pISignatureBlob->GetBufferSize()
, IID_PPV_ARGS(&pIRootSignature)));
我们可以看到,根签名中多了两个根参数,分别指向一个SRV和一个Sample的描述符堆,这样我们在具体渲染时就需要指定具体的Sample堆了,代码如下:
ID3D12DescriptorHeap* ppHeaps[] = { pISRVHeap.Get(),pISamplerDescriptorHeap.Get()};
pICommandList->SetDescriptorHeaps(_countof(ppHeaps), ppHeaps);
pICommandList->SetGraphicsRootDescriptorTable(0, pISRVHeap->GetGPUDescriptorHandleForHeapStart());
CD3DX12_GPU_DESCRIPTOR_HANDLE hGPUSampler(pISamplerDescriptorHeap->GetGPUDescriptorHandleForHeapStart()
, nCurrentSamplerNO
, nSamplerDescriptorSize);
pICommandList->SetGraphicsRootDescriptorTable(1, hGPUSampler);
首先与之前的例子一样,我们先把所有的描述符堆的对象指针编成一个数组整体的设置到命令列表中,实质也就是指定到渲染管线上去,然后我们在按照根参数中描述的对应序号挨个设定每个参数对应的描述符堆是哪个。这里实质上设定的是GPU地址空间中的首地址,从其结构名字即可理解这一点,因为最终渲染是GPU来完成的,所以它当然需要知道的是自己的地址空间中的指针值。
另外为了动态切换每种采样器,我们实现了一个简单的功能就是按空格键来切换当前采样器的序号,然后偏移到指定序号的采样器首地址处,再将它设置到管线上,当然这里就是发出一个命令,最终执行是需要在命令队列中排队等待图形引擎顺序执行的。
这里再次强调一下,采样器描述符堆跟其他的描述符堆一样,本质上实际是个数组,不要被Heap这个名字给迷惑了。整个过程简单的理解,就是我们创建了一个有5个元素的数组,然后按数组索引号设置不同的数组元素的首地址作为当前使用的描述符堆的首地址而已。只是因为代码的复杂性掩盖了数组操作的本质而已。
最后关于使用空格键改变序号的功能和其他的一些细节就请大家查看最后的完整代码了。还有不清楚的地方请及时留意垂询。
5、完整代码
#include <SDKDDKVer.h>
#define WIN32_LEAN_AND_MEAN // 从 Windows 头中排除极少使用的资料
#include <windows.h>
#include <tchar.h>
//添加WTL支持 方便使用COM
#include <wrl.h>
using namespace Microsoft;
using namespace Microsoft::WRL;
#include <dxgi1_6.h>
#include <DirectXMath.h>
using namespace DirectX;
//for d3d12
#include <d3d12.h>
#include <d3dcompiler.h>
//linker
#pragma comment(lib, "dxguid.lib")
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "d3d12.lib")
#pragma comment(lib, "d3dcompiler.lib")
#if defined(_DEBUG)
#include <dxgidebug.h>
#endif
//for WIC
#include <wincodec.h>
#include "..\\WindowsCommons\\d3dx12.h"
#define GRS_WND_CLASS_NAME _T("Game Window Class")
#define GRS_WND_TITLE _T("DirectX12 Texture Sample")
#define GRS_THROW_IF_FAILED(hr) if (FAILED(hr)){ throw CGRSCOMException(hr); }
//新定义的宏用于上取整除法
#define GRS_UPPER_DIV(A,B) ((UINT)(((A)+((B)-1))/(B)))
//更简洁的向上边界对齐算法 内存管理中常用 请记住
#define GRS_UPPER(A,B) ((UINT)(((A)+((B)-1))&~(B - 1)))
class CGRSCOMException
{
public:
CGRSCOMException(HRESULT hr) : m_hrError(hr)
{
}
HRESULT Error() const
{
return m_hrError;
}
private:
const HRESULT m_hrError;
};
struct WICTranslate
{
GUID wic;
DXGI_FORMAT format;
};
static WICTranslate g_WICFormats[] =
{//WIC格式与DXGI像素格式的对应表,该表中的格式为被支持的格式
{ GUID_WICPixelFormat128bppRGBAFloat, DXGI_FORMAT_R32G32B32A32_FLOAT },
{ GUID_WICPixelFormat64bppRGBAHalf, DXGI_FORMAT_R16G16B16A16_FLOAT },
{ GUID_WICPixelFormat64bppRGBA, DXGI_FORMAT_R16G16B16A16_UNORM },
{ GUID_WICPixelFormat32bppRGBA, DXGI_FORMAT_R8G8B8A8_UNORM },
{ GUID_WICPixelFormat32bppBGRA, DXGI_FORMAT_B8G8R8A8_UNORM }, // DXGI 1.1
{ GUID_WICPixelFormat32bppBGR, DXGI_FORMAT_B8G8R8X8_UNORM }, // DXGI 1.1
{ GUID_WICPixelFormat32bppRGBA1010102XR, DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM }, // DXGI 1.1
{ GUID_WICPixelFormat32bppRGBA1010102, DXGI_FORMAT_R10G10B10A2_UNORM },
{ GUID_WICPixelFormat16bppBGRA5551, DXGI_FORMAT_B5G5R5A1_UNORM },
{ GUID_WICPixelFormat16bppBGR565, DXGI_FORMAT_B5G6R5_UNORM },
{ GUID_WICPixelFormat32bppGrayFloat, DXGI_FORMAT_R32_FLOAT },
{ GUID_WICPixelFormat16bppGrayHalf, DXGI_FORMAT_R16_FLOAT },
{ GUID_WICPixelFormat16bppGray, DXGI_FORMAT_R16_UNORM },
{ GUID_WICPixelFormat8bppGray, DXGI_FORMAT_R8_UNORM },
{ GUID_WICPixelFormat8bppAlpha, DXGI_FORMAT_A8_UNORM },
};
// WIC 像素格式转换表.
struct WICConvert
{
GUID source;
GUID target;
};
static WICConvert g_WICConvert[] =
{
// 目标格式一定是最接近的被支持的格式
{ GUID_WICPixelFormatBlackWhite, GUID_WICPixelFormat8bppGray }, // DXGI_FORMAT_R8_UNORM
{ GUID_WICPixelFormat1bppIndexed, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM
{ GUID_WICPixelFormat2bppIndexed, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM
{ GUID_WICPixelFormat4bppIndexed, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM
{ GUID_WICPixelFormat8bppIndexed, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM
{ GUID_WICPixelFormat2bppGray, GUID_WICPixelFormat8bppGray }, // DXGI_FORMAT_R8_UNORM
{ GUID_WICPixelFormat4bppGray, GUID_WICPixelFormat8bppGray }, // DXGI_FORMAT_R8_UNORM
{ GUID_WICPixelFormat16bppGrayFixedPoint, GUID_WICPixelFormat16bppGrayHalf }, // DXGI_FORMAT_R16_FLOAT
{ GUID_WICPixelFormat32bppGrayFixedPoint, GUID_WICPixelFormat32bppGrayFloat }, // DXGI_FORMAT_R32_FLOAT
{ GUID_WICPixelFormat16bppBGR555, GUID_WICPixelFormat16bppBGRA5551 }, // DXGI_FORMAT_B5G5R5A1_UNORM
{ GUID_WICPixelFormat32bppBGR101010, GUID_WICPixelFormat32bppRGBA1010102 }, // DXGI_FORMAT_R10G10B10A2_UNORM
{ GUID_WICPixelFormat24bppBGR, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM
{ GUID_WICPixelFormat24bppRGB, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM
{ GUID_WICPixelFormat32bppPBGRA, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM
{ GUID_WICPixelFormat32bppPRGBA, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM
{ GUID_WICPixelFormat48bppRGB, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM
{ GUID_WICPixelFormat48bppBGR, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM
{ GUID_WICPixelFormat64bppBGRA, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM
{ GUID_WICPixelFormat64bppPRGBA, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM
{ GUID_WICPixelFormat64bppPBGRA, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM
{ GUID_WICPixelFormat48bppRGBFixedPoint, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT
{ GUID_WICPixelFormat48bppBGRFixedPoint, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT
{ GUID_WICPixelFormat64bppRGBAFixedPoint, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT
{ GUID_WICPixelFormat64bppBGRAFixedPoint, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT
{ GUID_WICPixelFormat64bppRGBFixedPoint, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT
{ GUID_WICPixelFormat48bppRGBHalf, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT
{ GUID_WICPixelFormat64bppRGBHalf, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT
{ GUID_WICPixelFormat128bppPRGBAFloat, GUID_WICPixelFormat128bppRGBAFloat }, // DXGI_FORMAT_R32G32B32A32_FLOAT
{ GUID_WICPixelFormat128bppRGBFloat, GUID_WICPixelFormat128bppRGBAFloat }, // DXGI_FORMAT_R32G32B32A32_FLOAT
{ GUID_WICPixelFormat128bppRGBAFixedPoint, GUID_WICPixelFormat128bppRGBAFloat }, // DXGI_FORMAT_R32G32B32A32_FLOAT
{ GUID_WICPixelFormat128bppRGBFixedPoint, GUID_WICPixelFormat128bppRGBAFloat }, // DXGI_FORMAT_R32G32B32A32_FLOAT
{ GUID_WICPixelFormat32bppRGBE, GUID_WICPixelFormat128bppRGBAFloat }, // DXGI_FORMAT_R32G32B32A32_FLOAT
{ GUID_WICPixelFormat32bppCMYK, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM
{ GUID_WICPixelFormat64bppCMYK, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM
{ GUID_WICPixelFormat40bppCMYKAlpha, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM
{ GUID_WICPixelFormat80bppCMYKAlpha, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM
{ GUID_WICPixelFormat32bppRGB, GUID_WICPixelFormat32bppRGBA }, // DXGI_FORMAT_R8G8B8A8_UNORM
{ GUID_WICPixelFormat64bppRGB, GUID_WICPixelFormat64bppRGBA }, // DXGI_FORMAT_R16G16B16A16_UNORM
{ GUID_WICPixelFormat64bppPRGBAHalf, GUID_WICPixelFormat64bppRGBAHalf }, // DXGI_FORMAT_R16G16B16A16_FLOAT
};
bool GetTargetPixelFormat(const GUID* pSourceFormat, GUID* pTargetFormat)
{//查表确定兼容的最接近格式是哪个
*pTargetFormat = *pSourceFormat;
for (size_t i = 0; i < _countof(g_WICConvert); ++i)
{
if (InlineIsEqualGUID(g_WICConvert[i].source, *pSourceFormat))
{
*pTargetFormat = g_WICConvert[i].target;
return true;
}
}
return false;
}
DXGI_FORMAT GetDXGIFormatFromPixelFormat(const GUID* pPixelFormat)
{//查表确定最终对应的DXGI格式是哪一个
for (size_t i = 0; i < _countof(g_WICFormats); ++i)
{
if (InlineIsEqualGUID(g_WICFormats[i].wic, *pPixelFormat))
{
return g_WICFormats[i].format;
}
}
return DXGI_FORMAT_UNKNOWN;
}
struct GRS_VERTEX
{
XMFLOAT3 m_vPos; //Position
XMFLOAT2 m_vTxc; //Texcoord
};
UINT nCurrentSamplerNO = 0; //当前使用的采样器索引
UINT nSampleMaxCnt = 5; //创建五个典型的采样器
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
{
::CoInitialize(nullptr); //for WIC & COM
const UINT nFrameBackBufCount = 3u;
int iWidth = 1024;
int iHeight = 768;
UINT nFrameIndex = 0;
UINT nFrame = 0;
UINT nDXGIFactoryFlags = 0U;
UINT nRTVDescriptorSize = 0U;
HWND hWnd = nullptr;
MSG msg = {};
float fAspectRatio = 3.0f;
D3D12_VERTEX_BUFFER_VIEW stVertexBufferView = {};
UINT64 n64FenceValue = 0ui64;
HANDLE hFenceEvent = nullptr;
UINT nTextureW = 0u;
UINT nTextureH = 0u;
UINT nBPP = 0u;
UINT nPicRowPitch = 0;
UINT64 n64UploadBufferSize = 0;
DXGI_FORMAT stTextureFormat = DXGI_FORMAT_UNKNOWN;
D3D12_PLACED_SUBRESOURCE_FOOTPRINT stTxtLayouts = {};
D3D12_RESOURCE_DESC stTextureDesc = {};
D3D12_RESOURCE_DESC stDestDesc = {};
UINT nSamplerDescriptorSize = 0; //采样器大小
CD3DX12_VIEWPORT stViewPort(0.0f, 0.0f, static_cast<float>(iWidth), static_cast<float>(iHeight));
CD3DX12_RECT stScissorRect(0, 0, static_cast<LONG>(iWidth), static_cast<LONG>(iHeight));
ComPtr<IDXGIFactory5> pIDXGIFactory5;
ComPtr<IDXGIAdapter1> pIAdapter;
ComPtr<ID3D12Device4> pID3DDevice;
ComPtr<ID3D12CommandQueue> pICommandQueue;
ComPtr<ID3D12CommandAllocator> pICommandAllocator;
ComPtr<ID3D12GraphicsCommandList> pICommandList;
ComPtr<IDXGISwapChain1> pISwapChain1;
ComPtr<IDXGISwapChain3> pISwapChain3;
ComPtr<ID3D12Resource> pIARenderTargets[nFrameBackBufCount];
ComPtr<ID3D12DescriptorHeap> pIRTVHeap;
ComPtr<ID3D12Heap> pITextureHeap;
ComPtr<ID3D12Heap> pIUploadHeap;
ComPtr<ID3D12Resource> pITexture;
ComPtr<ID3D12Resource> pITextureUpload;
ComPtr<ID3D12Resource> pIVertexBuffer;
ComPtr<ID3D12DescriptorHeap> pISRVHeap;
ComPtr<ID3D12DescriptorHeap> pISamplerDescriptorHeap;
ComPtr<ID3D12Fence> pIFence;
ComPtr<ID3DBlob> pIBlobVertexShader;
ComPtr<ID3DBlob> pIBlobPixelShader;
ComPtr<ID3D12RootSignature> pIRootSignature;
ComPtr<ID3D12PipelineState> pIPipelineState;
ComPtr<IWICImagingFactory> pIWICFactory;
ComPtr<IWICBitmapDecoder> pIWICDecoder;
ComPtr<IWICBitmapFrameDecode> pIWICFrame;
ComPtr<IWICBitmapSource> pIBMP;
try
{
//1、创建窗口
{
//---------------------------------------------------------------------------------------------
WNDCLASSEX wcex = {};
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_GLOBALCLASS;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH); //防止无聊的背景重绘
wcex.lpszClassName = GRS_WND_CLASS_NAME;
RegisterClassEx(&wcex);
DWORD dwWndStyle = WS_OVERLAPPED | WS_SYSMENU;
RECT rtWnd = { 0, 0, iWidth, iHeight };
AdjustWindowRect(&rtWnd, dwWndStyle, FALSE);
hWnd = CreateWindowW(GRS_WND_CLASS_NAME
, GRS_WND_TITLE
, dwWndStyle
, CW_USEDEFAULT
, 0
, rtWnd.right - rtWnd.left
, rtWnd.bottom - rtWnd.top
, nullptr
, nullptr
, hInstance
, nullptr);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
}
//2、使用WIC创建并加载一个图片,并转换为DXGI兼容的格式
{
//---------------------------------------------------------------------------------------------
//使用纯COM方式创建WIC类厂对象,也是调用WIC第一步要做的事情
GRS_THROW_IF_FAILED(CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pIWICFactory)));
//使用WIC类厂对象接口加载纹理图片,并得到一个WIC解码器对象接口,图片信息就在这个接口代表的对象中了
WCHAR* pszTexcuteFileName = _T("D:\\\\Projects_2018_08\\\\D3D12 Tutorials\\\\2-D3D12WICTexture\\\\Texture\\\\bear.jpg");
GRS_THROW_IF_FAILED(pIWICFactory->CreateDecoderFromFilename(
pszTexcuteFileName, // 文件名
NULL, // 不指定解码器,使用默认
GENERIC_READ, // 访问权限
WICDecodeMetadataCacheOnDemand, // 若需要就缓冲数据
&pIWICDecoder // 解码器对象
));
// 获取第一帧图片(因为GIF等格式文件可能会有多帧图片,其他的格式一般只有一帧图片)
// 实际解析出来的往往是位图格式数据
GRS_THROW_IF_FAILED(pIWICDecoder->GetFrame(0, &pIWICFrame));
WICPixelFormatGUID wpf = {};
//获取WIC图片格式
GRS_THROW_IF_FAILED(pIWICFrame->GetPixelFormat(&wpf));
GUID tgFormat = {};
//通过第一道转换之后获取DXGI的等价格式
if (GetTargetPixelFormat(&wpf, &tgFormat))
{
stTextureFormat = GetDXGIFormatFromPixelFormat(&tgFormat);
}
if (DXGI_FORMAT_UNKNOWN == stTextureFormat)
{// 不支持的图片格式 目前退出了事
// 一般 在实际的引擎当中都会提供纹理格式转换工具,
// 图片都需要提前转换好,所以不会出现不支持的现象
throw CGRSCOMException(S_FALSE);
}
if (!InlineIsEqualGUID(wpf, tgFormat))
{// 这个判断很重要,如果原WIC格式不是直接能转换为DXGI格式的图片时
// 我们需要做的就是转换图片格式为能够直接对应DXGI格式的形式
//创建图片格式转换器
ComPtr<IWICFormatConverter> pIConverter;
GRS_THROW_IF_FAILED(pIWICFactory->CreateFormatConverter(&pIConverter));
//初始化一个图片转换器,实际也就是将图片数据进行了格式转换
GRS_THROW_IF_FAILED(pIConverter->Initialize(
pIWICFrame.Get(), // 输入原图片数据
tgFormat, // 指定待转换的目标格式
WICBitmapDitherTypeNone, // 指定位图是否有调色板,现代都是真彩位图,不用调色板,所以为None
NULL, // 指定调色板指针
0.f, // 指定Alpha阀值
WICBitmapPaletteTypeCustom // 调色板类型,实际没有使用,所以指定为Custom
));
// 调用QueryInterface方法获得对象的位图数据源接口
GRS_THROW_IF_FAILED(pIConverter.As(&pIBMP));
}
else
{
//图片数据格式不需要转换,直接获取其位图数据源接口
GRS_THROW_IF_FAILED(pIWICFrame.As(&pIBMP));
}
//获得图片大小(单位:像素)
GRS_THROW_IF_FAILED(pIBMP->GetSize(&nTextureW, &nTextureH));
//获取图片像素的位大小的BPP(Bits Per Pixel)信息,用以计算图片行数据的真实大小(单位:字节)
ComPtr<IWICComponentInfo> pIWICmntinfo;
GRS_THROW_IF_FAILED(pIWICFactory->CreateComponentInfo(tgFormat, pIWICmntinfo.GetAddressOf()));
WICComponentType type;
GRS_THROW_IF_FAILED(pIWICmntinfo->GetComponentType(&type));
if (type != WICPixelFormat)
{
throw CGRSCOMException(S_FALSE);
}
ComPtr<IWICPixelFormatInfo> pIWICPixelinfo;
GRS_THROW_IF_FAILED(pIWICmntinfo.As(&pIWICPixelinfo));
// 到这里终于可以得到BPP了,这也是我看的比较吐血的地方,为了BPP居然饶了这么多环节
GRS_THROW_IF_FAILED(pIWICPixelinfo->GetBitsPerPixel(&nBPP));
// 计算图片实际的行大小(单位:字节),这里使用了一个上取整除法即(A+B-1)/B ,
// 这曾经被传说是微软的面试题,希望你已经对它了如指掌
nPicRowPitch = GRS_UPPER_DIV(uint64_t(nTextureW) * uint64_t(nBPP), 8);
}
//3、打开显示子系统的调试支持
{
#if defined(_DEBUG)
ComPtr<ID3D12Debug> debugController;
if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController))))
{
debugController->EnableDebugLayer();
// 打开附加的调试支持
nDXGIFactoryFlags |= DXGI_CREATE_FACTORY_DEBUG;
}
#endif
}
//4、创建DXGI Factory对象
{
GRS_THROW_IF_FAILED(CreateDXGIFactory2(nDXGIFactoryFlags, IID_PPV_ARGS(&pIDXGIFactory5)));
// 关闭ALT+ENTER键切换全屏的功能,因为我们没有实现OnSize处理,所以先关闭
GRS_THROW_IF_FAILED(pIDXGIFactory5->MakeWindowAssociation(hWnd, DXGI_MWA_NO_ALT_ENTER));
}
//5、枚举适配器,并选择合适的适配器来创建3D设备对象
{
for (UINT adapterIndex = 1; DXGI_ERROR_NOT_FOUND != pIDXGIFactory5->EnumAdapters1(adapterIndex, &pIAdapter); ++adapterIndex)
{
DXGI_ADAPTER_DESC1 desc = {};
pIAdapter->GetDesc1(&desc);
if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)
{//跳过软件虚拟适配器设备
continue;
}
//检查适配器对D3D支持的兼容级别,这里直接要求支持12.1的能力,注意返回接口的那个参数被置为了nullptr,这样
//就不会实际创建一个设备了,也不用我们啰嗦的再调用release来释放接口。这也是一个重要的技巧,请记住!
if (SUCCEEDED(D3D12CreateDevice(pIAdapter.Get(), D3D_FEATURE_LEVEL_12_1, _uuidof(ID3D12Device), nullptr)))
{
break;
}
}
//创建D3D12.1的设备
GRS_THROW_IF_FAILED(D3D12CreateDevice(pIAdapter.Get(), D3D_FEATURE_LEVEL_12_1, IID_PPV_ARGS(&pID3DDevice)));
}
//6、创建直接命令队列
{
D3D12_COMMAND_QUEUE_DESC stQueueDesc = {};
stQueueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
GRS_THROW_IF_FAILED(pID3DDevice->CreateCommandQueue(&stQueueDesc, IID_PPV_ARGS(&pICommandQueue)));
}
//7、创建直接命令列表
{
GRS_THROW_IF_FAILED(pID3DDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT
, IID_PPV_ARGS(&pICommandAllocator)));
//创建直接命令列表,在其上可以执行几乎所有的引擎命令(3D图形引擎、计算引擎、复制引擎等)
GRS_THROW_IF_FAILED(pID3DDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT
, pICommandAllocator.Get(),nullptr, IID_PPV_ARGS(&pICommandList)));
}
//8、创建交换链
{
DXGI_SWAP_CHAIN_DESC1 stSwapChainDesc = {};
stSwapChainDesc.BufferCount = nFrameBackBufCount;
stSwapChainDesc.Width = iWidth;
stSwapChainDesc.Height = iHeight;
stSwapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
stSwapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
stSwapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
stSwapChainDesc.SampleDesc.Count = 1;
GRS_THROW_IF_FAILED(pIDXGIFactory5->CreateSwapChainForHwnd(
pICommandQueue.Get(), // Swap chain needs the queue so that it can force a flush on it.
hWnd,
&stSwapChainDesc,
nullptr,
nullptr,
&pISwapChain1
));
}
//9、得到当前后缓冲区的序号,也就是下一个将要呈送显示的缓冲区的序号
{
//注意此处使用了高版本的SwapChain接口的函数
GRS_THROW_IF_FAILED(pISwapChain1.As(&pISwapChain3));
nFrameIndex = pISwapChain3->GetCurrentBackBufferIndex();
}
//10、创建RTV的描述符
{
//创建RTV(渲染目标视图)描述符堆(这里堆的含义应当理解为数组或者固定大小元素的固定大小显存池)
D3D12_DESCRIPTOR_HEAP_DESC stRTVHeapDesc = {};
stRTVHeapDesc.NumDescriptors = nFrameBackBufCount;
stRTVHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
stRTVHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
GRS_THROW_IF_FAILED(pID3DDevice->CreateDescriptorHeap(&stRTVHeapDesc, IID_PPV_ARGS(&pIRTVHeap)));
//得到每个描述符元素的大小
nRTVDescriptorSize = pID3DDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
//---------------------------------------------------------------------------------------------
CD3DX12_CPU_DESCRIPTOR_HANDLE stRTVHandle(pIRTVHeap->GetCPUDescriptorHandleForHeapStart());
for (UINT i = 0; i < nFrameBackBufCount; i++)
{//这个循环暴漏了描述符堆实际上是个数组的本质
GRS_THROW_IF_FAILED(pISwapChain3->GetBuffer(i, IID_PPV_ARGS(&pIARenderTargets[i])));
pID3DDevice->CreateRenderTargetView(pIARenderTargets[i].Get(), nullptr, stRTVHandle);
stRTVHandle.Offset(1, nRTVDescriptorSize);
}
}
//11、创建根签名
{
D3D12_FEATURE_DATA_ROOT_SIGNATURE stFeatureData = {};
// 检测是否支持V1.1版本的根签名
stFeatureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_1;
if (FAILED(pID3DDevice->CheckFeatureSupport(D3D12_FEATURE_ROOT_SIGNATURE, &stFeatureData, sizeof(stFeatureData))))
{
stFeatureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_0;
}
// 在GPU上执行SetGraphicsRootDescriptorTable后,我们不修改命令列表中的SRV,因此我们可以使用默认Rang行为:
// D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC_WHILE_SET_AT_EXECUTE
CD3DX12_DESCRIPTOR_RANGE1 stDSPRanges[2];
stDSPRanges[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0, 0, D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC_WHILE_SET_AT_EXECUTE);
stDSPRanges[1].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER, 1, 0);
CD3DX12_ROOT_PARAMETER1 stRootParameters[2];
stRootParameters[0].InitAsDescriptorTable(1, &stDSPRanges[0], D3D12_SHADER_VISIBILITY_PIXEL);
stRootParameters[1].InitAsDescriptorTable(1, &stDSPRanges[1], D3D12_SHADER_VISIBILITY_PIXEL);
//---------------------------------------------------------------------------------------------
//静态的采样器不用了,我们使用动态的在堆上创建的采样器
//D3D12_STATIC_SAMPLER_DESC stSamplerDesc = {};
//stSamplerDesc.Filter = D3D12_FILTER_MIN_MAG_MIP_POINT;
//stSamplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
//stSamplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
//stSamplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
//stSamplerDesc.MipLODBias = 0;
//stSamplerDesc.MaxAnisotropy = 0;
//stSamplerDesc.ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER;
//stSamplerDesc.BorderColor = D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK;
//stSamplerDesc.MinLOD = 0.0f;
//stSamplerDesc.MaxLOD = D3D12_FLOAT32_MAX;
//stSamplerDesc.ShaderRegister = 0;
//stSamplerDesc.RegisterSpace = 0;
//stSamplerDesc.ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
//---------------------------------------------------------------------------------------------
CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC stRootSignatureDesc;
//stRootSignatureDesc.Init_1_1(_countof(stRootParameters), stRootParameters
// , 1, &stSamplerDesc
// , D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);
stRootSignatureDesc.Init_1_1(_countof(stRootParameters), stRootParameters
, 0, nullptr
, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);
ComPtr<ID3DBlob> pISignatureBlob;
ComPtr<ID3DBlob> pIErrorBlob;
GRS_THROW_IF_FAILED(D3DX12SerializeVersionedRootSignature(&stRootSignatureDesc
, stFeatureData.HighestVersion
, &pISignatureBlob
, &pIErrorBlob));
GRS_THROW_IF_FAILED(pID3DDevice->CreateRootSignature(0
, pISignatureBlob->GetBufferPointer()
, pISignatureBlob->GetBufferSize()
, IID_PPV_ARGS(&pIRootSignature)));
}
//12、编译Shader创建渲染管线状态对象
{
#if defined(_DEBUG)
// Enable better shader debugging with the graphics debugging tools.
UINT compileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#else
UINT compileFlags = 0;
#endif
TCHAR pszShaderFileName[] = _T("D:\\\\Projects_2018_08\\\\D3D12 Tutorials\\\\2-D3D12WICTexture\\\\Shader\\\\Texture.hlsl");
GRS_THROW_IF_FAILED(D3DCompileFromFile(pszShaderFileName, nullptr, nullptr
, "VSMain", "vs_5_0", compileFlags, 0, &pIBlobVertexShader, nullptr));
GRS_THROW_IF_FAILED(D3DCompileFromFile(pszShaderFileName, nullptr, nullptr
, "PSMain", "ps_5_0", compileFlags, 0, &pIBlobPixelShader, nullptr));
// Define the vertex input layout.
D3D12_INPUT_ELEMENT_DESC stInputElementDescs[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
};
// 创建 graphics pipeline state object (PSO)对象
D3D12_GRAPHICS_PIPELINE_STATE_DESC stPSODesc = {};
stPSODesc.InputLayout = { stInputElementDescs, _countof(stInputElementDescs) };
stPSODesc.pRootSignature = pIRootSignature.Get();
stPSODesc.VS = CD3DX12_SHADER_BYTECODE(pIBlobVertexShader.Get());
stPSODesc.PS = CD3DX12_SHADER_BYTECODE(pIBlobPixelShader.Get());
stPSODesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
stPSODesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
stPSODesc.DepthStencilState.DepthEnable = FALSE;
stPSODesc.DepthStencilState.StencilEnable = FALSE;
stPSODesc.SampleMask = UINT_MAX;
stPSODesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
stPSODesc.NumRenderTargets = 1;
stPSODesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
stPSODesc.SampleDesc.Count = 1;
GRS_THROW_IF_FAILED(pID3DDevice->CreateGraphicsPipelineState(&stPSODesc
, IID_PPV_ARGS(&pIPipelineState)));
}
//13、创建纹理的默认堆
{
D3D12_HEAP_DESC stTextureHeapDesc = {};
//为堆指定纹理图片至少2倍大小的空间,这里没有详细去计算了,只是指定了一个足够大的空间,够放纹理就行
//实际应用中也是要综合考虑分配堆的大小,以便可以重用堆
stTextureHeapDesc.SizeInBytes = GRS_UPPER(2 * nPicRowPitch * nTextureH, D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT);
//指定堆的对齐方式,这里使用了默认的64K边界对齐,因为我们暂时不需要MSAA支持
stTextureHeapDesc.Alignment = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT;
stTextureHeapDesc.Properties.Type = D3D12_HEAP_TYPE_DEFAULT; //默认堆类型
stTextureHeapDesc.Properties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
stTextureHeapDesc.Properties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
//拒绝渲染目标纹理、拒绝深度蜡板纹理,实际就只是用来摆放普通纹理
stTextureHeapDesc.Flags = D3D12_HEAP_FLAG_DENY_RT_DS_TEXTURES | D3D12_HEAP_FLAG_DENY_BUFFERS;
GRS_THROW_IF_FAILED(pID3DDevice->CreateHeap(&stTextureHeapDesc, IID_PPV_ARGS(&pITextureHeap)));
}
//14、创建2D纹理
{
stTextureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
stTextureDesc.MipLevels = 1;
stTextureDesc.Format = stTextureFormat; //DXGI_FORMAT_R8G8B8A8_UNORM;
stTextureDesc.Width = nTextureW;
stTextureDesc.Height = nTextureH;
stTextureDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
stTextureDesc.DepthOrArraySize = 1;
stTextureDesc.SampleDesc.Count = 1;
stTextureDesc.SampleDesc.Quality = 0;
//-----------------------------------------------------------------------------------------------------------
创建默认堆上的资源,类型是Texture2D,GPU对默认堆资源的访问速度是最快的
因为纹理资源一般是不易变的资源,所以我们通常使用上传堆复制到默认堆中
在传统的D3D11及以前的D3D接口中,这些过程都被封装了,我们只能指定创建时的类型为默认堆
//GRS_THROW_IF_FAILED(pID3DDevice->CreateCommittedResource(
// &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT)
// , D3D12_HEAP_FLAG_NONE
// , &stTextureDesc //可以使用CD3DX12_RESOURCE_DESC::Tex2D来简化结构体的初始化
// , D3D12_RESOURCE_STATE_COPY_DEST
// , nullptr
// , IID_PPV_ARGS(&pITexture)));
//-----------------------------------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------------------------------
//使用“定位方式”来创建纹理,注意下面这个调用内部实际已经没有存储分配和释放的实际操作了,所以性能很高
//同时可以在这个堆上反复调用CreatePlacedResource来创建不同的纹理,当然前提是它们不在被使用的时候,才考虑
//重用堆
GRS_THROW_IF_FAILED(pID3DDevice->CreatePlacedResource(
pITextureHeap.Get()
, 0
, &stTextureDesc //可以使用CD3DX12_RESOURCE_DESC::Tex2D来简化结构体的初始化
, D3D12_RESOURCE_STATE_COPY_DEST
, nullptr
, IID_PPV_ARGS(&pITexture)));
//-----------------------------------------------------------------------------------------------------------
//获取上传堆资源缓冲的大小,这个尺寸通常大于实际图片的尺寸
n64UploadBufferSize = GetRequiredIntermediateSize(pITexture.Get(), 0, 1);
}
//15、创建上传堆
{
//-----------------------------------------------------------------------------------------------------------
D3D12_HEAP_DESC stUploadHeapDesc = { };
//尺寸依然是实际纹理数据大小的2倍并64K边界对齐大小
stUploadHeapDesc.SizeInBytes = GRS_UPPER(2 * n64UploadBufferSize, D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT);
//注意上传堆肯定是Buffer类型,可以不指定对齐方式,其默认是64k边界对齐
stUploadHeapDesc.Alignment = 0;
stUploadHeapDesc.Properties.Type = D3D12_HEAP_TYPE_UPLOAD; //上传堆类型
stUploadHeapDesc.Properties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
stUploadHeapDesc.Properties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
//上传堆就是缓冲,可以摆放任意数据
stUploadHeapDesc.Flags = D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS;
GRS_THROW_IF_FAILED(pID3DDevice->CreateHeap(&stUploadHeapDesc, IID_PPV_ARGS(&pIUploadHeap)));
//-----------------------------------------------------------------------------------------------------------
}
//16、使用“定位方式”创建用于上传纹理数据的缓冲资源
{
-----------------------------------------------------------------------------------------------------------
创建用于上传纹理的资源,注意其类型是Buffer
上传堆对于GPU访问来说性能是很差的,
所以对于几乎不变的数据尤其像纹理都是
通过它来上传至GPU访问更高效的默认堆中
//GRS_THROW_IF_FAILED(pID3DDevice->CreateCommittedResource(
// &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
// D3D12_HEAP_FLAG_NONE,
// &CD3DX12_RESOURCE_DESC::Buffer(n64UploadBufferSize),
// D3D12_RESOURCE_STATE_GENERIC_READ,
// nullptr,
// IID_PPV_ARGS(&pITextureUpload)));
-----------------------------------------------------------------------------------------------------------
GRS_THROW_IF_FAILED(pID3DDevice->CreatePlacedResource(pIUploadHeap.Get()
, 0
, &CD3DX12_RESOURCE_DESC::Buffer(n64UploadBufferSize)
, D3D12_RESOURCE_STATE_GENERIC_READ
, nullptr
, IID_PPV_ARGS(&pITextureUpload)));
}
//17、加载图片数据至上传堆,即完成第一个Copy动作,从memcpy函数可知这是由CPU完成的
{
//按照资源缓冲大小来分配实际图片数据存储的内存大小
void* pbPicData = ::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, n64UploadBufferSize);
if (nullptr == pbPicData)
{
throw CGRSCOMException(HRESULT_FROM_WIN32(GetLastError()));
}
//从图片中读取出数据
GRS_THROW_IF_FAILED(pIBMP->CopyPixels(nullptr
, nPicRowPitch
, static_cast<UINT>(nPicRowPitch * nTextureH) //注意这里才是图片数据真实的大小,这个值通常小于缓冲的大小
, reinterpret_cast<BYTE*>(pbPicData)));
//{//下面这段代码来自DX12的示例,直接通过填充缓冲绘制了一个黑白方格的纹理
// //还原这段代码,然后注释上面的CopyPixels调用可以看到黑白方格纹理的效果
// const UINT rowPitch = nPicRowPitch; //nTextureW * 4; //static_cast<UINT>(n64UploadBufferSize / nTextureH);
// const UINT cellPitch = rowPitch >> 3; // The width of a cell in the checkboard texture.
// const UINT cellHeight = nTextureW >> 3; // The height of a cell in the checkerboard texture.
// const UINT textureSize = static_cast<UINT>(n64UploadBufferSize);
// UINT nTexturePixelSize = static_cast<UINT>(n64UploadBufferSize / nTextureH / nTextureW);
// UINT8* pData = reinterpret_cast<UINT8*>(pbPicData);
// for (UINT n = 0; n < textureSize; n += nTexturePixelSize)
// {
// UINT x = n % rowPitch;
// UINT y = n / rowPitch;
// UINT i = x / cellPitch;
// UINT j = y / cellHeight;
// if (i % 2 == j % 2)
// {
// pData[n] = 0x00; // R
// pData[n + 1] = 0x00; // G
// pData[n + 2] = 0x00; // B
// pData[n + 3] = 0xff; // A
// }
// else
// {
// pData[n] = 0xff; // R
// pData[n + 1] = 0xff; // G
// pData[n + 2] = 0xff; // B
// pData[n + 3] = 0xff; // A
// }
// }
//}
//获取向上传堆拷贝纹理数据的一些纹理转换尺寸信息
//对于复杂的DDS纹理这是非常必要的过程
UINT nNumSubresources = 1u; //我们只有一副图片,即子资源个数为1
UINT nTextureRowNum = 0u;
UINT64 n64TextureRowSizes = 0u;
UINT64 n64RequiredSize = 0u;
stDestDesc = pITexture->GetDesc();
pID3DDevice->GetCopyableFootprints(&stDestDesc
, 0
, nNumSubresources
, 0
, &stTxtLayouts
, &nTextureRowNum
, &n64TextureRowSizes
, &n64RequiredSize);
//因为上传堆实际就是CPU传递数据到GPU的中介
//所以我们可以使用熟悉的Map方法将它先映射到CPU内存地址中
//然后我们按行将数据复制到上传堆中
//需要注意的是之所以按行拷贝是因为GPU资源的行大小
//与实际图片的行大小是有差异的,二者的内存边界对齐要求是不一样的
BYTE* pData = nullptr;
GRS_THROW_IF_FAILED(pITextureUpload->Map(0, NULL, reinterpret_cast<void**>(&pData)));
BYTE* pDestSlice = reinterpret_cast<BYTE*>(pData) + stTxtLayouts.Offset;
const BYTE* pSrcSlice = reinterpret_cast<const BYTE*>(pbPicData);
for (UINT y = 0; y < nTextureRowNum; ++y)
{
memcpy(pDestSlice + static_cast<SIZE_T>(stTxtLayouts.Footprint.RowPitch) * y
, pSrcSlice + static_cast<SIZE_T>(nPicRowPitch) * y
, nPicRowPitch);
}
//取消映射 对于易变的数据如每帧的变换矩阵等数据,可以撒懒不用Unmap了,
//让它常驻内存,以提高整体性能,因为每次Map和Unmap是很耗时的操作
//因为现在起码都是64位系统和应用了,地址空间是足够的,被长期占用不会影响什么
pITextureUpload->Unmap(0, NULL);
//释放图片数据,做一个干净的程序员
::HeapFree(::GetProcessHeap(), 0, pbPicData);
}
//18、向直接命令列表发出从上传堆复制纹理数据到默认堆的命令,执行并同步等待,即完成第二个Copy动作,由GPU上的复制引擎完成
//注意此时直接命令列表还没有绑定PSO对象,因此它也是不能执行3D图形命令的,但是可以执行复制命令,因为复制引擎不需要什么
//额外的状态设置之类的参数
{
CD3DX12_TEXTURE_COPY_LOCATION Dst(pITexture.Get(), 0);
CD3DX12_TEXTURE_COPY_LOCATION Src(pITextureUpload.Get(), stTxtLayouts);
pICommandList->CopyTextureRegion(&Dst, 0, 0, 0, &Src, nullptr);
//设置一个资源屏障,同步并确认复制操作完成
//直接使用结构体然后调用的形式
D3D12_RESOURCE_BARRIER stResBar = {};
stResBar.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
stResBar.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
stResBar.Transition.pResource = pITexture.Get();
stResBar.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
stResBar.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
stResBar.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
pICommandList->ResourceBarrier(1, &stResBar);
//或者使用D3DX12库中的工具类调用的等价形式,下面的方式更简洁一些
//pICommandList->ResourceBarrier(1
// , &CD3DX12_RESOURCE_BARRIER::Transition(pITexture.Get()
// , D3D12_RESOURCE_STATE_COPY_DEST
// , D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE)
//);
//---------------------------------------------------------------------------------------------
// 执行命令列表并等待纹理资源上传完成,这一步是必须的
GRS_THROW_IF_FAILED(pICommandList->Close());
ID3D12CommandList* ppCommandLists[] = { pICommandList.Get() };
pICommandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);
//---------------------------------------------------------------------------------------------
// 17、创建一个同步对象——围栏,用于等待渲染完成,因为现在Draw Call是异步的了
GRS_THROW_IF_FAILED(pID3DDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&pIFence)));
n64FenceValue = 1;
//---------------------------------------------------------------------------------------------
// 18、创建一个Event同步对象,用于等待围栏事件通知
hFenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
if (hFenceEvent == nullptr)
{
GRS_THROW_IF_FAILED(HRESULT_FROM_WIN32(GetLastError()));
}
//---------------------------------------------------------------------------------------------
// 19、等待纹理资源正式复制完成先
const UINT64 fence = n64FenceValue;
GRS_THROW_IF_FAILED(pICommandQueue->Signal(pIFence.Get(), fence));
n64FenceValue++;
//---------------------------------------------------------------------------------------------
// 看命令有没有真正执行到围栏标记的这里,没有就利用事件去等待,注意使用的是命令队列对象的指针
if (pIFence->GetCompletedValue() < fence)
{
GRS_THROW_IF_FAILED(pIFence->SetEventOnCompletion(fence, hFenceEvent));
WaitForSingleObject(hFenceEvent, INFINITE);
}
//---------------------------------------------------------------------------------------------
//命令分配器先Reset一下,刚才已经执行过了一个复制纹理的命令
GRS_THROW_IF_FAILED(pICommandAllocator->Reset());
//Reset命令列表,并重新指定命令分配器和PSO对象
GRS_THROW_IF_FAILED(pICommandList->Reset(pICommandAllocator.Get(), pIPipelineState.Get()));
//---------------------------------------------------------------------------------------------
}
//19、创建SRV堆 (Shader Resource View Heap) 和SRV描述符
{
D3D12_DESCRIPTOR_HEAP_DESC stSRVHeapDesc = {};
stSRVHeapDesc.NumDescriptors = 1;
stSRVHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
stSRVHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
GRS_THROW_IF_FAILED(pID3DDevice->CreateDescriptorHeap(&stSRVHeapDesc, IID_PPV_ARGS(&pISRVHeap)));
//---------------------------------------------------------------------------------------------
// 最终创建SRV描述符
D3D12_SHADER_RESOURCE_VIEW_DESC stSRVDesc = {};
stSRVDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
stSRVDesc.Format = stTextureDesc.Format;
stSRVDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
stSRVDesc.Texture2D.MipLevels = 1;
pID3DDevice->CreateShaderResourceView(pITexture.Get(), &stSRVDesc, pISRVHeap->GetCPUDescriptorHandleForHeapStart());
}
//20、创建采样器堆 和 各种采样器
{
D3D12_DESCRIPTOR_HEAP_DESC stSamplerHeapDesc = {};
stSamplerHeapDesc.NumDescriptors = nSampleMaxCnt;
stSamplerHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER;
stSamplerHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
GRS_THROW_IF_FAILED(pID3DDevice->CreateDescriptorHeap(&stSamplerHeapDesc,IID_PPV_ARGS(&pISamplerDescriptorHeap)));
nSamplerDescriptorSize = pID3DDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER);
CD3DX12_CPU_DESCRIPTOR_HANDLE hSamplerHeap(pISamplerDescriptorHeap->GetCPUDescriptorHandleForHeapStart());
D3D12_SAMPLER_DESC stSamplerDesc = {};
stSamplerDesc.Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR;
stSamplerDesc.MinLOD = 0;
stSamplerDesc.MaxLOD = D3D12_FLOAT32_MAX;
stSamplerDesc.MipLODBias = 0.0f;
stSamplerDesc.MaxAnisotropy = 1;
stSamplerDesc.ComparisonFunc = D3D12_COMPARISON_FUNC_ALWAYS;
// Sampler 1
stSamplerDesc.BorderColor[0] = 1.0f;
stSamplerDesc.BorderColor[1] = 0.0f;
stSamplerDesc.BorderColor[2] = 1.0f;
stSamplerDesc.BorderColor[3] = 1.0f;
stSamplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
stSamplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
stSamplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
pID3DDevice->CreateSampler(&stSamplerDesc, hSamplerHeap);
hSamplerHeap.Offset(nSamplerDescriptorSize);
// Sampler 2
stSamplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
stSamplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
stSamplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP;
pID3DDevice->CreateSampler(&stSamplerDesc, hSamplerHeap);
hSamplerHeap.Offset(nSamplerDescriptorSize);
// Sampler 3
stSamplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
stSamplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
stSamplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
pID3DDevice->CreateSampler(&stSamplerDesc, hSamplerHeap);
hSamplerHeap.Offset(nSamplerDescriptorSize);
// Sampler 4
stSamplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_MIRROR;
stSamplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_MIRROR;
stSamplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_MIRROR;
pID3DDevice->CreateSampler(&stSamplerDesc, hSamplerHeap);
hSamplerHeap.Offset(nSamplerDescriptorSize);
// Sampler 5
stSamplerDesc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_MIRROR_ONCE;
stSamplerDesc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_MIRROR_ONCE;
stSamplerDesc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_MIRROR_ONCE;
pID3DDevice->CreateSampler(&stSamplerDesc, hSamplerHeap);
}
//21、定义正方形的3D数据结构,注意此处的纹理坐标我故意设置为大于1
GRS_VERTEX stTriangleVertices[] =
{
{ { -0.25f* fAspectRatio, -0.25f * fAspectRatio, 0.0f}, { 0.0f, 3.0f } }, // Bottom left.
{ { -0.25f* fAspectRatio, 0.25f * fAspectRatio, 0.0f}, { 0.0f, 0.0f } }, // Top left.
{ { 0.25f* fAspectRatio, -0.25f * fAspectRatio, 0.0f}, { 3.0f, 3.0f } }, // Bottom right.
{ { 0.25f* fAspectRatio, 0.25f * fAspectRatio, 0.0f}, { 3.0f, 0.0f } }, // Top right.
};
const UINT nVertexBufferSize = sizeof(stTriangleVertices);
//22、使用“定位方式”创建顶点缓冲,使用与上传纹理数据缓冲相同的一个上传堆
{
//---------------------------------------------------------------------------------------------
//GRS_THROW_IF_FAILED(pID3DDevice->CreateCommittedResource(
// &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
// D3D12_HEAP_FLAG_NONE,
// &CD3DX12_RESOURCE_DESC::Buffer(nVertexBufferSize),
// D3D12_RESOURCE_STATE_GENERIC_READ,
// nullptr,
// IID_PPV_ARGS(&pIVertexBuffer)));
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
//使用定位方式在相同的上传堆上以“定位方式”创建顶点缓冲,注意第二个参数指出了堆中的偏移位置
//按照堆边界对齐的要求,我们主动将偏移位置对齐到了64k的边界上
GRS_THROW_IF_FAILED(pID3DDevice->CreatePlacedResource(
pIUploadHeap.Get()
, GRS_UPPER(n64UploadBufferSize, D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT)
, &CD3DX12_RESOURCE_DESC::Buffer(nVertexBufferSize)
, D3D12_RESOURCE_STATE_GENERIC_READ
, nullptr
, IID_PPV_ARGS(&pIVertexBuffer)));
//---------------------------------------------------------------------------------------------
//使用map-memcpy-unmap大法将数据传至顶点缓冲对象
//注意顶点缓冲使用是和上传纹理数据缓冲相同的一个堆,这很神奇
UINT8* pVertexDataBegin = nullptr;
CD3DX12_RANGE stReadRange(0, 0); // We do not intend to read from this resource on the CPU.
GRS_THROW_IF_FAILED(pIVertexBuffer->Map(0, &stReadRange, reinterpret_cast<void**>(&pVertexDataBegin)));
memcpy(pVertexDataBegin, stTriangleVertices, sizeof(stTriangleVertices));
pIVertexBuffer->Unmap(0, nullptr);
//创建资源视图,实际可以简单理解为指向顶点缓冲的显存指针
stVertexBufferView.BufferLocation = pIVertexBuffer->GetGPUVirtualAddress();
stVertexBufferView.StrideInBytes = sizeof(GRS_VERTEX);
stVertexBufferView.SizeInBytes = nVertexBufferSize;
}
//---------------------------------------------------------------------------------------------
//23、创建定时器对象,以便于创建高效的消息循环
HANDLE phWait = CreateWaitableTimer(NULL, FALSE, NULL);
LARGE_INTEGER liDueTime = {};
liDueTime.QuadPart = -1i64;//1秒后开始计时
SetWaitableTimer(phWait, &liDueTime, 1, NULL, NULL, 0);//40ms的周期
//---------------------------------------------------------------------------------------------
//24、开始消息循环,并在其中不断渲染
DWORD dwRet = 0;
BOOL bExit = FALSE;
while (!bExit)
{
dwRet = ::MsgWaitForMultipleObjects(1, &phWait, FALSE, INFINITE, QS_ALLINPUT);
switch (dwRet - WAIT_OBJECT_0)
{
case 0:
case WAIT_TIMEOUT:
{//计时器时间到
//GRS_TRACE(_T("开始第%u帧渲染{Frame Index = %u}:\\n"),nFrame,nFrameIndex);
//开始记录命令
//---------------------------------------------------------------------------------------------
pICommandList->SetGraphicsRootSignature(pIRootSignature.Get());
ID3D12DescriptorHeap* ppHeaps[] = { pISRVHeap.Get(),pISamplerDescriptorHeap.Get()};
pICommandList->SetDescriptorHeaps(_countof(ppHeaps), ppHeaps);
pICommandList->SetGraphicsRootDescriptorTable(0, pISRVHeap->GetGPUDescriptorHandleForHeapStart());
CD3DX12_GPU_DESCRIPTOR_HANDLE hGPUSampler(pISamplerDescriptorHeap->GetGPUDescriptorHandleForHeapStart()
, nCurrentSamplerNO
, nSamplerDescriptorSize);
pICommandList->SetGraphicsRootDescriptorTable(1, hGPUSampler);
pICommandList->RSSetViewports(1, &stViewPort);
pICommandList->RSSetScissorRects(1, &stScissorRect);
//---------------------------------------------------------------------------------------------
// 通过资源屏障判定后缓冲已经切换完毕可以开始渲染了
pICommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(pIARenderTargets[nFrameIndex].Get(), D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET));
//偏移描述符指针到指定帧缓冲视图位置
CD3DX12_CPU_DESCRIPTOR_HANDLE stRTVHandle(pIRTVHeap->GetCPUDescriptorHandleForHeapStart(), nFrameIndex, nRTVDescriptorSize);
//设置渲染目标
pICommandList->OMSetRenderTargets(1, &stRTVHandle, FALSE, nullptr);
// 继续记录命令,并真正开始新一帧的渲染
const float clearColor[] = { 0.0f, 0.2f, 0.4f, 1.0f };
pICommandList->ClearRenderTargetView(stRTVHandle, clearColor, 0, nullptr);
//注意我们使用的渲染手法是三角形带,这是最快的绘制矩形的方式,也是很多UI库中核心使用的方法
pICommandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
pICommandList->IASetVertexBuffers(0, 1, &stVertexBufferView);
//---------------------------------------------------------------------------------------------
//Draw Call!!!
pICommandList->DrawInstanced(_countof(stTriangleVertices), 1, 0, 0);
//---------------------------------------------------------------------------------------------
//又一个资源屏障,用于确定渲染已经结束可以提交画面去显示了
pICommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(pIARenderTargets[nFrameIndex].Get(), D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT));
//关闭命令列表,可以去执行了
GRS_THROW_IF_FAILED(pICommandList->Close());
//---------------------------------------------------------------------------------------------
//执行命令列表
ID3D12CommandList* ppCommandLists[] = { pICommandList.Get() };
pICommandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);
//---------------------------------------------------------------------------------------------
//提交画面
GRS_THROW_IF_FAILED(pISwapChain3->Present(1, 0));
//---------------------------------------------------------------------------------------------
//开始同步GPU与CPU的执行,先记录围栏标记值
const UINT64 fence = n64FenceValue;
GRS_THROW_IF_FAILED(pICommandQueue->Signal(pIFence.Get(), fence));
n64FenceValue++;
//---------------------------------------------------------------------------------------------
// 看命令有没有真正执行到围栏标记的这里,没有就利用事件去等待,注意使用的是命令队列对象的指针
if (pIFence->GetCompletedValue() < fence)
{
GRS_THROW_IF_FAILED(pIFence->SetEventOnCompletion(fence, hFenceEvent));
WaitForSingleObject(hFenceEvent, INFINITE);
}
//执行到这里说明一个命令队列完整的执行完了,在这里就代表我们的一帧已经渲染完了,接着准备执行下一帧渲染
//---------------------------------------------------------------------------------------------
//获取新的后缓冲序号,因为Present真正完成时后缓冲的序号就更新了
nFrameIndex = pISwapChain3->GetCurrentBackBufferIndex();
//---------------------------------------------------------------------------------------------
//命令分配器先Reset一下
GRS_THROW_IF_FAILED(pICommandAllocator->Reset());
//Reset命令列表,并重新指定命令分配器和PSO对象
GRS_THROW_IF_FAILED(pICommandList->Reset(pICommandAllocator.Get(), pIPipelineState.Get()));
//GRS_TRACE(_T("第%u帧渲染结束.\\n"), nFrame++);
}
break;
case 1:
{//处理消息
while (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (WM_QUIT != msg.message)
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
else
{
bExit = TRUE;
}
}
}
break;
default:
break;
}
}
//::CoUninitialize();
}
catch (CGRSCOMException& e)
{//发生了COM异常
e;
}
return 0;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_KEYUP:
{
if (VK_SPACE == (wParam & 0xFF))
{//按空格键切换不同的采样器看效果,以明白每种采样器具体的含义
//UINT nCurrentSamplerNO = 0; //当前使用的采样器索引
//UINT nSampleMaxCnt = 5; //创建五个典型的采样器
++nCurrentSamplerNO;
nCurrentSamplerNO %= nSampleMaxCnt;
}
}
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
shader代码与前一篇教程中相同,略。
暂无评论内容