目录
1、前言及本章内容提要
2、初识DirectXMath库
3、使用独立堆创建常量缓冲
4、理解管线状态对象
5、理解围栏同步
6、完整代码
经过了之前3篇教程的跨度有点大的学习,我想大家现在应该对D3D12编程有了初步掌握。本篇教程算一个小小的过渡:一是巩固一下之前所学的知识;二是将还剩下的两个知识点讲解清楚;从而为后面进一步学习相对高(fu)级(za)的内容进一步扫清障碍。
本章示例将在上一章基础上实现一个旋转的带纹理的正方体的绘制,并实现了简单的摄像机控制以及旋转速度控制。可以说这章起我们才算是真正的绘制了3D的图形。
最终本章例子运行效果如下:
DirectXMath是一个用于游戏和图形应用程序的全内联SIMD c++线性代数库,它提供了单精度3D向量(2-4维)、矩阵(3X3、4X4)、欧拉角、四元数等的基本计算支持,底层利用了现代x86架构中的sse1-sse4等高级SIMD指令,得益于这些指令的高效及流水线并行优化的特性,所以它是目前在x86 CPU上执行效率最高的3D数学库,非常合适用来封装引擎或游戏等3D应用。
一般它伴随Windows SDK一起发布,但微软现在也将它放在了GitHub上并在不定期的更新和发布,网址在https://github.com/Microsoft/DirectXMath。因为它是放在SDK中,所以当下载了最新版之后,最好是全部解压之后复制进SDK的目录中,覆盖原来的文件。否则单独放目录编译的话会比较麻烦。
其实DirectXMath库是是历史上D3Dx@@Math库的自然演化物,只是说D3DXMath库依赖于其dll,版本久远,而现在的DirectXMath库则全部采用内联代码方式,在使用、调试、发布上更有优势。所以现在我们就应该使用最新的DirectXMath库函数来实现3D应用中的数学部分。
要使用这个库,首先就包含它的头文件#include
因为DirectXMath库中默认使用了处理器上的SIMD指令,这就与GPU使用数据类似,都会要求数据边界对齐,当然对于CPU上的SIMD指令来说只需要简单的16字节边界对齐即可。因此在使用动态内存分配的时候,考虑使用边界对齐的分配和释放方法:_aligned_malloc 、_aligned_free。
变量边界对齐的要求也给我们带来了一定的方便性,当我们需要将变换矩阵等数据常量上传到GPU的资源中时,使用Map方法返回的指针天然就是边界对齐的,并且它对齐与64k边界,是16字节的倍数,所以我们可以直接向这个指针的内存中复制DirectXMath定义的变量。
也因为边界对齐的要求,总体上来说,DirectXMath库定义了两套结构体系,均以XM开头。第一套是以XMFLOAT、XMINT、XMUINT等成员类型名开头,它是普通结构体,用于我们在代码中任意地方定义需要的向量、矩阵等变量,另一组则是以XMVECTOR、XMMATRIX等开头用以定义和指向边界对齐的等价向量和矩阵值,并且所有的XM开头的库函数都只操作边界对齐的这组结构体定义的变量。那么在实际使用时,就需要首先使用XMLoad*****函数族,将我们定义的XMFLOAT***、XMINT****等值加载成边界对齐的等价XMVECTOR***、XMMATRIX**等类型的变量,运算完成后就使用XMStore*****函数族将边界对齐的变量值存回任意的XMFLOAT***、XMINT****变量中。因此我们经常会看到如下框架性的代码段:
XMMATRIX matWorld = XMMatrixRotationQuaternion( XMLoadFloat4( &obb.Orientation ) );
XMMATRIX matScale = XMMatrixScaling( obb.Extents.x, obb.Extents.y, obb.Extents.z );
matWorld = XMMatrixMultiply( matScale, matWorld );
XMVECTOR position = XMLoadFloat3( &obb.Center );
需要注意的是,现代的64位VC++编译器已经能够正确的自动对齐类似XMVECTOR、XMMATRIX等的自动变量,所以现在我们更多的是直接定义自动变量来使用DirectXMath库。
最终要完全用好DirectXMath库,就需要我们对3D数学有深刻的理解和掌握,这将在我后续的其他教程中进一步讲解,目前我们任然聚焦于D3D12自身的编程学习方面。
在本章例子中,我们主要在立方体的旋转方面用到了这个库的能力,代码如下:
n64tmCurrent = ::GetTickCount();
//计算旋转的角度:旋转角度(弧度) = 时间(秒) * 角速度(弧度/秒)
//下面这句代码相当于经典游戏消息循环中的OnUpdate函数中需要做的事情
dModelRotationYAngle += (( n64tmCurrent - n64tmFrameStart ) / 1000.0f) * fPalstance;
n64tmFrameStart = n64tmCurrent;
//旋转角度是2PI周期的倍数,去掉周期数,只留下相对0弧度开始的小于2PI的弧度即可
if ( dModelRotationYAngle > XM_2PI )
{
dModelRotationYAngle = fmod( dModelRotationYAngle, XM_2PI );
}
//模型矩阵 model
XMMATRIX xmRot = XMMatrixRotationY(static_cast(dModelRotationYAngle));
//计算 模型矩阵 model * 视矩阵 view
XMMATRIX xmMVP = XMMatrixMultiply(xmRot, XMMatrixLookAtLH(Eye, At, Up));
//投影矩阵 projection
xmMVP = XMMatrixMultiply(xmMVP, (XMMatrixPerspectiveFovLH(XM_PIDIV4, (FLOAT)iWidth / (FLOAT)iHeight, 0.1f, 1000.0f)));
XMStoreFloat4x4(&pMVPBuffer->m_MVP, xmMVP);
上述代码中我们用到了最简单的物理公式:旋转角度=角速度X时间,这是高中的内容,剩下的就是实现经典的MVP矩阵。M矩阵就是绕Y轴旋转的矩阵,因为在D3D中默认坐标系Y轴垂直向上,而V矩阵就是根据代码中简单的摄像机计算得到的,最后P矩阵在代码中已经显示很清楚了。注意我们使用的是左手坐标系,所以函数是以LH结尾的,对应的有可以用于OpenGL的RH结尾的函数版本。最后我们虽然调用了一次XMStoreFloat4x4,但其实这里可以直接使用memcpy的,因为pMVPBuffer指针来自于我们对常量缓冲的Map操作。我这样做只是一个习惯问题,没有特别的含义,大家不要被迷惑。
这里需要注意的一个问题就是在DirectXMath中矩阵是行主序存储的,而在我们使用的HLSL中,使用的是SM5.0语法,它里面的矩阵是列主序存储的,因此在最终将MVP矩阵传入Shader的常量缓冲中时可能需要一次转置操作。在我们的示例中,我们是使用编译Shader时指定行主序编译标志,省去了这个转置操作,代码如下:
//编译为行矩阵形式
compileFlags |= D3DCOMPILE_PACK_MATRIX_ROW_MAJOR;
TCHAR pszShaderFileName[] = _T("D:\\Projects_2018_08\\D3D12 Tutorials\\3-D3D12TextureCube\\Shader\\TextureCube.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));
这样我们就可以直接将DirectXMath的矩阵传入Shader中使用了。
在后面我们在简单的摄像机控制中,也使用了简单的DirectXMath库函数,用于变换摄像机的位置,代码如下:
//初始的默认摄像机的位置
XMVECTOR Eye = XMVectorSet(0.0f, 0.0f, -10.0f, 0.0f); //眼睛位置
XMVECTOR At = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f); //眼睛所盯的位置
XMVECTOR Up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f); //头部正上方位置
......
......
if (VK_UP == n16KeyCode || 'w' == n16KeyCode || 'W' == n16KeyCode)
{
Eye = XMVectorAdd(Eye, XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f));
}
if (VK_DOWN == n16KeyCode || 's' == n16KeyCode || 'S' == n16KeyCode)
{
Eye = XMVectorAdd(Eye, XMVectorSet(0.0f, -1.0f, 0.0f, 0.0f));
}
if (VK_RIGHT == n16KeyCode || 'd' == n16KeyCode || 'D' == n16KeyCode)
{
Eye = XMVectorAdd(Eye, XMVectorSet(1.0f, 0.0f, 0.0f, 0.0f));
}
if (VK_LEFT == n16KeyCode || 'a' == n16KeyCode || 'A' == n16KeyCode)
{
Eye = XMVectorAdd(Eye, XMVectorSet(-1.0f, 0.0f, 0.0f, 0.0f));
}
这段代码很简单,就是简单的上下左右,变换眼睛的位置而已,我想函数的名字已经很清楚的标明的了代码在干什么,我就不多解释了。当然这个变换因为简单,但不是很正确,后续的其他教程中我会讲解更准确的方法。因为那是比较烧脑的纯数学的问题,所以就不放在这个系列的教程中去啰嗦了。
在之前的例子中,因为我们渲染的基本是平面的图形,那么就没有用到什么常量缓冲。这章中我们需要渲染的是一个立方体,为了体现3D的效果,我们特意让它旋转起来,显示我们其实是在做3D的渲染。
为了旋转我们就需要用到经典的MVP矩阵,并且要让它能够传递到Shader中,做逐个顶点的旋转计算。当然为了看3D的效果,我们必须为哪怕最简单的场景都指定View矩阵、和Projection矩阵,也就是我们通常说的摄像机矩阵和裁剪矩阵,关于这些矩阵的推导和理解我将放在其他的教程。
这里需要理解的其他几个地方就是,首先对于任何3D场景来说,一帧画面在渲染(3D->2D)的过程中,它的摄像机位置基本是不会变化的,否则最终的画面将不符合人类的视觉习惯了,也就成了无效的渲染;其次对于GPU渲染来说摄像机相关的矩阵数据其实都是只读的,因此为了进一步提高性能,我们就干脆把它放在GPU可以访问的只读常量缓冲中;最后我们要注意的就是对于不同的帧来说,摄像机的位置、方位等参数就可能是不断变化的了,因此我们就需要在每帧画面渲染开始的时候就更新他们的值;另外对于我们所熟知的骨骼动画的矩阵调色板来说也是如此,而这些矩阵的计算其实最终是CPU完成的,根据现代渲染管线中CPU和GPU的职责分工来说,这些矩阵有一个典型的特征就是1对多,直白的说,就是比如一个摄像机的VP矩阵就可以用于变换整个场景所有物体的所有顶点,而Model矩阵(有些教程中还要在其后追加一个World矩阵,用于变换整个场景的方位)则是用来变换一个物体,骨骼动画的矩阵调试版也是按照索引和权重变换一个具体的物体。所以这些矩阵的量本质上来说数量是很少的,少则0~1个,多则几十个而已,用CPU来计算的话是比较合理和划算的。最终就形成了CPU在每帧渲染开始前不断的计算这些矩阵,然后上传到GPU的缓冲中的实际运行场景。
综合这些情况,以及我们之前几章学习的显存管理的知识来看,我们可以这样考虑数据缓冲存储,第一就是我们考虑将他们放在CPU和GPU都能访问的共享内存中,就我们目前可以控制的存储堆类型来说这对应着上传堆;第二考虑到这些数据每帧都要变化和重新传输,因此我们只做第一次Copy,即从内存中将数据复制到上传堆缓冲中即可,而不再额外的做第二次GPU复制引擎的Copy动作了(考虑虽然GPU访问上传堆中的数据有性能损失,但是比起浪费GPU的复制引擎周期来复制数据,则完全少了复制的开销,因为复制引擎访问这个数据也要付出同样的性能损失的代价);第三也因为这些数据需要反复计算和传输,同时我们现在已经完全控制了堆的生命周期,所以我们直接一次性Map之后,就反复memcpy,直到程序结束前才调用Unmap。所有这些考虑其实只有一个目的就是——性能!
最终综合上面这些陈述我们就可以编写如下的代码:
n64BufferOffset = GRS_UPPER(n64BufferOffset + nszIndexBuffer, D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT);
// 创建常量缓冲 注意缓冲尺寸设置为256边界对齐大小
GRS_THROW_IF_FAILED(pID3DDevice->CreatePlacedResource(
pIUploadHeap.Get()
, n64BufferOffset
, &CD3DX12_RESOURCE_DESC::Buffer(szMVPBuffer)
, D3D12_RESOURCE_STATE_GENERIC_READ
, nullptr
, IID_PPV_ARGS(&pICBVUpload)));
// Map 之后就不再Unmap了 直接复制数据进去 这样每帧都不用map-copy-unmap浪费时间了
GRS_THROW_IF_FAILED(pICBVUpload->Map(0, nullptr, reinterpret_cast(&pMVPBuffer)));
在渲染前我们就像下面这样将数据复制进常量缓冲中:
XMStoreFloat4x4(&pMVPBuffer->m_MVP, xmMVP);
根据前面我们对DirectXMath库基本用法的描述,这里的XMStoreFloat4x4其实就相当于一个memcpy操作了。综合前面各章的描述大家一定要注意区分我每次说memcpy时,其实是泛指CPU的memory copy操作,而不是简单只是说memcpy函数而已,希望你明白这个意思。
当然按照之前我们对根签名的学习,这里其实我们只是说准备好了一个常量缓冲而已,接下来其实我们还需要准备两级对应的“指针”:一个就是根签名中根参数,一个是描述符堆上的常量缓冲视图(即常量缓冲描述符),综合起来他们的代码如下:
......
CD3DX12_DESCRIPTOR_RANGE1 stDSPRanges[3];
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_CBV, 1, 0);
stDSPRanges[2].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER, 1, 0);
CD3DX12_ROOT_PARAMETER1 stRootParameters[3];
stRootParameters[0].InitAsDescriptorTable(1, &stDSPRanges[0], D3D12_SHADER_VISIBILITY_PIXEL);//SRV仅PS可见
stRootParameters[1].InitAsDescriptorTable(1, &stDSPRanges[1], D3D12_SHADER_VISIBILITY_ALL); //CBV是所有Shader可见
stRootParameters[2].InitAsDescriptorTable(1, &stDSPRanges[2], D3D12_SHADER_VISIBILITY_PIXEL);//SAMPLE仅PS可见
......
//我们将纹理视图描述符和CBV描述符放在一个描述符堆上
D3D12_DESCRIPTOR_HEAP_DESC stSRVHeapDesc = {};
stSRVHeapDesc.NumDescriptors = 2; //1 SRV + 1 CBV
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)));
......
D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc = {};
cbvDesc.BufferLocation = pICBVUpload->GetGPUVirtualAddress();
cbvDesc.SizeInBytes = static_cast(szMVPBuffer);
CD3DX12_CPU_DESCRIPTOR_HANDLE cbvSrvHandle(pISRVHeap->GetCPUDescriptorHandleForHeapStart()
, 1
, pID3DDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV));
pID3DDevice->CreateConstantBufferView(&cbvDesc, cbvSrvHandle);
上面的代码与我们之前例子中的同类代码很类似,就不多啰嗦讲解的了。这里只是再强调一下两级指针的含义,第一级指针是常量缓冲描述符指向常量缓冲,标志是语句:cbvDesc.BufferLocation = pICBVUpload->GetGPUVirtualAddress(); 从其函数名我们应该立即明白这就是获取指针,而第二级指针的设定就在渲染循环中的:
CD3DX12_GPU_DESCRIPTOR_HANDLE stGPUCBVHandle(pISRVHeap->GetGPUDescriptorHandleForHeapStart()
, 1
, pID3DDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV));
//设置CBV
pICommandList->SetGraphicsRootDescriptorTable(1, stGPUCBVHandle);
我们仔细观察其中的pISRVHeap->GetGPUDescriptorHandleForHeapStart(),这个语句虽然它名字中没有明显出现Address字眼,但其实Handle、Heap Start等字眼我想对于一个搞C/C++的老程序员来说,这只不过都是障眼法而已,我们需要的是用火眼金睛,快速的看出他的本质——还是指针!
这样最终就形成了从描述符堆(描述符数组)中对应元素首地址指向对应描述符、再从描述符指向缓冲开始地址的二级指针的结构。其实对于加入了显存管理的D3D12编程来说,加入这样的二级指针框架来具体关联存储区域的做法,我想对于C/C++程序来说应该很好理解的。当然这里再次重复这个概念的目的就是为了让你深刻的巩固过去的知识并进一步加深理解。
那么对于我们C/C++程序员来说这个“二级指针”框架一旦认识了它的本质就没什么好稀奇的了,而我们的职责就是在封装引擎或构建3D应用时,将它彻底的封装埋藏起来,并且让最终的脚本编写人员或引擎调用人员不再需要去感知和理解它。当然这是题外话了,我只是提供些方向性的建议。
在一开始的基础教程中,我只是说了在D3D12中渲染管线状态对象被独立了出来,形成了渲染管线状态对象。这极大的方便了对渲染管线状态的设置和管理工作。
在D3D12中整个渲染管线的状态以及对应的Shader程序都被放置在一个统一的结构体中,然后通过调用ID3D12Device::CreateGraphicsPipelineState创建成一个渲染管线状态的对象。这里需要注意的是这个渲染管线状态只是一个静态的状态,如果如前面的教程中所讲将根签名看做渲染管线的参数描述的话,这个渲染管线状态对象就是一个函数体的定义了。这样继续理解的话,那么就是说一个渲染管线状态可以配以不同的根签名描述,当然需要的就是参数个数种类必须一致,顺序可以不一致。而反过来不同的根签名就可以用以描述不同的渲染管线状态。
在运行时,或者说渲染时,渲染管线状态对象就要绑定到具体的命令列表上去,然后我们通过命令列表来设置渲染管线需要的各种参数,最终在命令队列中排队等待图形引擎的执行。
这样我们就基本上完整的明白了D3D12中整个渲染部分的全部框架了,那就是渲染管线状态对象决定了渲染的静态配置和渲染的程序(各种Shader),而命令列表和命令队列决定了渲染的动态运行状态。而整个渲染管线的编程就是围绕它们展开的,它们最终决定了我们编程的基本框架。
更进一步建议各位去看看D3D12_GRAPHICS_PIPELINE_STATE_DESC这个结构体的定义,从而加深理解,它的定义我就不粘贴在这里了。其成员我也不做多的解释了,因为它的名字已经描述的很清楚了。
在之前的例子中,我们以及接触了很多次围栏(Fence)对象了,我们反复的说它是用来同步的,我相信很多人在看到这个东西时是有点晕的,因为我们一直都只是一个单线程的程序,需要同步什么呢?其实本质上说虽然直到目前我们一直都使用的单线程(其实就是程序的默认主线程)程序,但实质上我们同时操作的是CPU和GPU。根据我们前面的描述,在D3D12中,其实CPU和GPU已经是完全的并行执行了,即CPU不论发送给GPU何种命令,以至于最终的ExecuteCommandLists函数都是立即返回,此时GPU和CPU就是真正的并行执行了,GPU上就是运行一个完整的渲染过程了,而CPU就是按照我们之后的C/C++代码继续运行。
最终我们实际需要知道的就是GPU运行到哪里了?那就需要围栏来帮忙了。
从原理上来说,围栏本身关联与一个值,也叫作围栏值,我们通过下面的语句来关联他们:
const UINT64 fence = n64FenceValue;
GRS_THROW_IF_FAILED(pICommandQueue->Signal(pIFence.Get(), fence));
n64FenceValue++;
围栏值其实是一个计数值。而Signal函数是命令队列本身为数不多的几个命令函数之一,也就是直接在GPU对应引擎上执行的命令。所以它是我们直接排队在命令队列中的命令,如我们之前教程中所说,虽然命令列表可能可以随时记录命令甚至可以并行乱序记录,但他们排队到命令队列中之后,各引擎在执行时却是串行的,也就是先进先出的执行命令,所以代表各引擎执行命令的对象就被命名为命令队列。因此当我们按顺序排队完需要的命令或命令列表之后,我们放置一个Signal命令,那么含义就是说前面的命令顺序执行完之后,就执行Signal,而当Signal执行后,对应的围栏对象的围栏值就被GPU变成了我们调用它排队进命令队列中的值。此时如果我们用CPU检测这个围栏值变成我们指定的值时,就说明之前的命令已经全部被GPU执行完了,之后CPU就可以重新组织和编排新的命令或命令列表去执行了。
当然我们最好不要通过一个死循环不停的在CPU端取这个围栏值来看GPU到底执行完之前的命令没有,因为这样完全失去了CPU和GPU并行执行的意义了,通常我们是为这个围栏对象的围栏值绑定一个CPU侧的同步对象,在我们例子中我们绑定的都是Event对象(通过调用ID3D12Fence::SetEventOnCompletion),当GPU命令执行到指定的围栏值时,这个Event对象就被设置为有信号状态,此时在其上等待的Wait函数族就会退出等待信号状态而返回。这样CPU通过Wait函数的返回就知道了围栏值已经变成了我们设定的那个值,从而进一步知道它之前的命令都已经被GPU执行完了。
当然我们例子代码以及大多数D3D12例子代码中,都是通过同步的Wait函数“死等”这个信号的,但其实建议在真实的引擎或3D应用渲染过程中,最好不要指定INFINITE(无限)等待的调用,而是指定一个0参数的Wait函数进行轮询,正如我们本章例子中对MsgWait函数进行的修改一样,其实这就是一个提示,我建议有能力的看官,将例子中的Wait整合进MsgWait函数中去,从而最终明白我一直都坚持使用MsgWait函数的“良苦用心”。
最后关于围栏的这段代码我就不再粘贴了,最后请查看完整的代码清单。
#include
#define WIN32_LEAN_AND_MEAN // 从 Windows 头中排除极少使用的资料
#include
#include
//添加WTL支持 方便使用COM
#include
using namespace Microsoft;
using namespace Microsoft::WRL;
#include
#include
using namespace DirectX;
//for d3d12
#include
#include
//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
#endif
//for WIC
#include
#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 ST_GRS_VERTEX
{//这次我们额外加入了每个顶点的法线,但Shader中还暂时没有用
XMFLOAT3 m_vPos; //Position
XMFLOAT2 m_vTex; //Texcoord
XMFLOAT3 m_vNor; //Normal
};
struct ST_GRS_FRAME_MVP_BUFFER
{
XMFLOAT4X4 m_MVP; //经典的Model-view-projection(MVP)矩阵.
};
UINT nCurrentSamplerNO = 0; //当前使用的采样器索引
UINT nSampleMaxCnt = 5; //创建五个典型的采样器
//初始的默认摄像机的位置
XMVECTOR Eye = XMVectorSet(0.0f, 0.0f, -10.0f, 0.0f); //眼睛位置
XMVECTOR At = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f); //眼睛所盯的位置
XMVECTOR Up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f); //头部正上方位置
double fPalstance = 10.0f * XM_PI / 180.0f; //物体旋转的角速度,单位:弧度/秒
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 fBoxSize = 3.0f;
float fTCMax = 3.0f;
ST_GRS_FRAME_MVP_BUFFER* pMVPBuffer = nullptr;
SIZE_T szMVPBuffer = GRS_UPPER_DIV(sizeof(ST_GRS_FRAME_MVP_BUFFER), 256) * 256;
D3D12_VERTEX_BUFFER_VIEW stVertexBufferView = {};
D3D12_INDEX_BUFFER_VIEW stIndexBufferView = {};
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(iWidth), static_cast(iHeight));
CD3DX12_RECT stScissorRect(0, 0, static_cast(iWidth), static_cast(iHeight));
ComPtr pIDXGIFactory5;
ComPtr pIAdapter;
ComPtr pID3DDevice;
ComPtr pICommandQueue;
ComPtr pICommandAllocator;
ComPtr pICommandList;
ComPtr pISwapChain1;
ComPtr pISwapChain3;
ComPtr pIARenderTargets[nFrameBackBufCount];
ComPtr pIRTVHeap;
ComPtr pITextureHeap;
ComPtr pIUploadHeap;
ComPtr pITexture;
ComPtr pITextureUpload;
ComPtr pICBVUpload;
ComPtr pIVertexBuffer;
ComPtr pIIndexBuffer;
ComPtr pISRVHeap;
ComPtr pISamplerDescriptorHeap;
ComPtr pIFence;
ComPtr pIBlobVertexShader;
ComPtr pIBlobPixelShader;
ComPtr pIRootSignature;
ComPtr pIPipelineState;
ComPtr pIWICFactory;
ComPtr pIWICDecoder;
ComPtr pIWICFrame;
ComPtr 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\\3-D3D12TextureCube\\Texture\\timg.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 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 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 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 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、枚举适配器创建设备
{//选择NUMA架构的独显来创建3D设备对象,暂时先不支持集显了,当然你可以修改这些行为
DXGI_ADAPTER_DESC1 desc = {};
D3D12_FEATURE_DATA_ARCHITECTURE stArchitecture = {};
for (UINT adapterIndex = 0; DXGI_ERROR_NOT_FOUND != pIDXGIFactory5->EnumAdapters1(adapterIndex, &pIAdapter); ++adapterIndex)
{
DXGI_ADAPTER_DESC1 desc = {};
pIAdapter->GetDesc1(&desc);
if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)
{//跳过软件虚拟适配器设备
continue;
}
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 )
{
break;
}
pID3DDevice.Reset();
}
//---------------------------------------------------------------------------------------------
if (nullptr == pID3DDevice.Get())
{// 可怜的机器上居然没有独显 还是先退出了事
throw CGRSCOMException(E_FAIL);
}
}
//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图形引擎、计算引擎、复制引擎等)
//注意初始时并没有使用PSO对象,此时其实这个命令列表依然可以记录命令
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
));
//注意此处使用了高版本的SwapChain接口的函数
GRS_THROW_IF_FAILED(pISwapChain1.As(&pISwapChain3));
nFrameIndex = pISwapChain3->GetCurrentBackBufferIndex();
//创建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);
}
}
//9、创建 SRV CBV Sample堆
{
//我们将纹理视图描述符和CBV描述符放在一个描述符堆上
D3D12_DESCRIPTOR_HEAP_DESC stSRVHeapDesc = {};
stSRVHeapDesc.NumDescriptors = 2; //1 SRV + 1 CBV
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)));
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);
}
//10、创建根签名
{
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[3];
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_CBV, 1, 0);
stDSPRanges[2].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER, 1, 0);
CD3DX12_ROOT_PARAMETER1 stRootParameters[3];
stRootParameters[0].InitAsDescriptorTable(1, &stDSPRanges[0], D3D12_SHADER_VISIBILITY_PIXEL);//SRV仅PS可见
stRootParameters[1].InitAsDescriptorTable(1, &stDSPRanges[1], D3D12_SHADER_VISIBILITY_ALL); //CBV是所有Shader可见
stRootParameters[2].InitAsDescriptorTable(1, &stDSPRanges[2], D3D12_SHADER_VISIBILITY_PIXEL);//SAMPLE仅PS可见
CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC stRootSignatureDesc;
stRootSignatureDesc.Init_1_1(_countof(stRootParameters), stRootParameters
, 0, nullptr
, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);
ComPtr pISignatureBlob;
ComPtr 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)));
}
//11、编译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
//编译为行矩阵形式
compileFlags |= D3DCOMPILE_PACK_MATRIX_ROW_MAJOR;
TCHAR pszShaderFileName[] = _T("D:\\Projects_2018_08\\D3D12 Tutorials\\3-D3D12TextureCube\\Shader\\TextureCube.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));
// 我们多添加了一个法线的定义,但目前Shader中我们并没有使用
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 },
{ "NORMAL", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 20, 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)));
}
//12、创建纹理的默认堆
{
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)));
}
//13、创建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;
//-----------------------------------------------------------------------------------------------------------
//使用“定位方式”来创建纹理,注意下面这个调用内部实际已经没有存储分配和释放的实际操作了,所以性能很高
//同时可以在这个堆上反复调用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);
}
//14、创建上传堆
{
//-----------------------------------------------------------------------------------------------------------
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)));
//-----------------------------------------------------------------------------------------------------------
}
//15、使用“定位方式”创建用于上传纹理数据的缓冲资源
{
GRS_THROW_IF_FAILED(pID3DDevice->CreatePlacedResource(pIUploadHeap.Get()
, 0
, &CD3DX12_RESOURCE_DESC::Buffer(n64UploadBufferSize)
, D3D12_RESOURCE_STATE_GENERIC_READ
, nullptr
, IID_PPV_ARGS(&pITextureUpload)));
}
//16、加载图片数据至上传堆,即完成第一个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(nPicRowPitch * nTextureH) //注意这里才是图片数据真实的大小,这个值通常小于缓冲的大小
, reinterpret_cast(pbPicData)));
//{//下面这段代码来自DX12的示例,直接通过填充缓冲绘制了一个黑白方格的纹理
// //还原这段代码,然后注释上面的CopyPixels调用可以看到黑白方格纹理的效果
// const UINT rowPitch = nPicRowPitch; //nTextureW * 4; //static_cast(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(n64UploadBufferSize);
// UINT nTexturePixelSize = static_cast(n64UploadBufferSize / nTextureH / nTextureW);
// UINT8* pData = reinterpret_cast(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(&pData)));
BYTE* pDestSlice = reinterpret_cast(pData) + stTxtLayouts.Offset;
const BYTE* pSrcSlice = reinterpret_cast(pbPicData);
for (UINT y = 0; y < nTextureRowNum; ++y)
{
memcpy(pDestSlice + static_cast(stTxtLayouts.Footprint.RowPitch) * y
, pSrcSlice + static_cast(nPicRowPitch) * y
, nPicRowPitch);
}
//取消映射 对于易变的数据如每帧的变换矩阵等数据,可以撒懒不用Unmap了,
//让它常驻内存,以提高整体性能,因为每次Map和Unmap是很耗时的操作
//因为现在起码都是64位系统和应用了,地址空间是足够的,被长期占用不会影响什么
pITextureUpload->Unmap(0, NULL);
//释放图片数据,做一个干净的程序员
::HeapFree(::GetProcessHeap(), 0, pbPicData);
}
//17、向直接命令列表发出从上传堆复制纹理数据到默认堆的命令,执行并同步等待,即完成第二个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()));
//---------------------------------------------------------------------------------------------
}
//18、定义正方形的3D数据结构,注意此处的纹理坐标我故意设置为大于1
ST_GRS_VERTEX stTriangleVertices[] = {
{ {-1.0f * fBoxSize, 1.0f * fBoxSize, -1.0f * fBoxSize}, {0.0f * fTCMax, 0.0f* fTCMax}, {0.0f, 0.0f, -1.0f} },
{ {1.0f * fBoxSize, 1.0f * fBoxSize, -1.0f * fBoxSize}, {1.0f* fTCMax, 0.0f* fTCMax}, {0.0f, 0.0f, -1.0f} },
{ {-1.0f * fBoxSize, -1.0f * fBoxSize, -1.0f * fBoxSize}, {0.0f* fTCMax, 1.0f* fTCMax}, {0.0f, 0.0f, -1.0f} },
{ {-1.0f * fBoxSize, -1.0f * fBoxSize, -1.0f * fBoxSize}, {0.0f * fTCMax, 1.0f * fTCMax}, {0.0f, 0.0f, -1.0f} },
{ {1.0f * fBoxSize, 1.0f * fBoxSize, -1.0f * fBoxSize}, {1.0f * fTCMax, 0.0f * fTCMax}, {0.0f, 0.0f, -1.0f} },
{ {1.0f * fBoxSize, -1.0f * fBoxSize, -1.0f * fBoxSize}, {1.0f * fTCMax, 1.0f * fTCMax}, {0.0f, 0.0f, -1.0f} },
{ {1.0f * fBoxSize, 1.0f * fBoxSize, -1.0f * fBoxSize}, {0.0f * fTCMax, 0.0f * fTCMax}, {1.0f, 0.0f, 0.0f} },
{ {1.0f * fBoxSize, 1.0f * fBoxSize, 1.0f * fBoxSize}, {1.0f * fTCMax, 0.0f * fTCMax}, {1.0f, 0.0f, 0.0f} },
{ {1.0f * fBoxSize, -1.0f * fBoxSize, -1.0f * fBoxSize}, {0.0f * fTCMax, 1.0f * fTCMax}, {1.0f, 0.0f, 0.0f} },
{ {1.0f * fBoxSize, -1.0f * fBoxSize, -1.0f * fBoxSize}, {0.0f * fTCMax, 1.0f * fTCMax}, {1.0f, 0.0f, 0.0f} },
{ {1.0f * fBoxSize, 1.0f * fBoxSize, 1.0f * fBoxSize}, {1.0f * fTCMax, 0.0f * fTCMax}, {1.0f, 0.0f, 0.0f} },
{ {1.0f * fBoxSize, -1.0f * fBoxSize, 1.0f * fBoxSize}, {1.0f * fTCMax, 1.0f * fTCMax}, {1.0f, 0.0f, 0.0f} },
{ {1.0f * fBoxSize, 1.0f * fBoxSize, 1.0f * fBoxSize}, {0.0f * fTCMax, 0.0f * fTCMax}, {0.0f, 0.0f, 1.0f} },
{ {-1.0f * fBoxSize, 1.0f * fBoxSize, 1.0f * fBoxSize}, {1.0f * fTCMax, 0.0f * fTCMax}, {0.0f, 0.0f, 1.0f} },
{ {1.0f * fBoxSize, -1.0f * fBoxSize, 1.0f * fBoxSize}, {0.0f * fTCMax, 1.0f * fTCMax}, {0.0f, 0.0f, 1.0f} },
{ {1.0f * fBoxSize, -1.0f * fBoxSize, 1.0f * fBoxSize}, {0.0f * fTCMax, 1.0f * fTCMax}, {0.0f, 0.0f, 1.0f} },
{ {-1.0f * fBoxSize, 1.0f * fBoxSize, 1.0f * fBoxSize}, {1.0f * fTCMax, 0.0f * fTCMax}, {0.0f, 0.0f, 1.0f} },
{ {-1.0f * fBoxSize, -1.0f * fBoxSize, 1.0f * fBoxSize}, {1.0f * fTCMax, 1.0f * fTCMax}, {0.0f, 0.0f, 1.0f} },
{ {-1.0f * fBoxSize, 1.0f * fBoxSize, 1.0f * fBoxSize}, {0.0f * fTCMax, 0.0f * fTCMax}, {-1.0f, 0.0f, 0.0f} },
{ {-1.0f * fBoxSize, 1.0f * fBoxSize, -1.0f * fBoxSize}, {1.0f * fTCMax, 0.0f * fTCMax}, {-1.0f, 0.0f, 0.0f} },
{ {-1.0f * fBoxSize, -1.0f * fBoxSize, 1.0f * fBoxSize}, {0.0f * fTCMax, 1.0f * fTCMax}, {-1.0f, 0.0f, 0.0f} },
{ {-1.0f * fBoxSize, -1.0f * fBoxSize, 1.0f * fBoxSize}, {0.0f * fTCMax, 1.0f * fTCMax}, {-1.0f, 0.0f, 0.0f} },
{ {-1.0f * fBoxSize, 1.0f * fBoxSize, -1.0f * fBoxSize}, {1.0f * fTCMax, 0.0f * fTCMax}, {-1.0f, 0.0f, 0.0f} },
{ {-1.0f * fBoxSize, -1.0f * fBoxSize, -1.0f * fBoxSize}, {1.0f * fTCMax, 1.0f * fTCMax}, {-1.0f, 0.0f, 0.0f} },
{ {-1.0f * fBoxSize, 1.0f * fBoxSize, 1.0f * fBoxSize}, {0.0f * fTCMax, 0.0f * fTCMax}, {0.0f, 1.0f, 0.0f} },
{ {1.0f * fBoxSize, 1.0f * fBoxSize, 1.0f * fBoxSize}, {1.0f * fTCMax, 0.0f * fTCMax}, {0.0f, 1.0f, 0.0f} },
{ {-1.0f * fBoxSize, 1.0f * fBoxSize, -1.0f * fBoxSize}, {0.0f * fTCMax, 1.0f * fTCMax}, {0.0f, 1.0f, 0.0f} },
{ {-1.0f * fBoxSize, 1.0f * fBoxSize, -1.0f * fBoxSize}, {0.0f * fTCMax, 1.0f * fTCMax}, {0.0f, 1.0f, 0.0f} },
{ {1.0f * fBoxSize, 1.0f * fBoxSize, 1.0f * fBoxSize}, {1.0f * fTCMax, 0.0f * fTCMax}, {0.0f, 1.0f, 0.0f} },
{ {1.0f * fBoxSize, 1.0f * fBoxSize, -1.0f * fBoxSize}, {1.0f * fTCMax, 1.0f * fTCMax}, {0.0f, 1.0f, 0.0f }},
{ {-1.0f * fBoxSize, -1.0f * fBoxSize, -1.0f * fBoxSize}, {0.0f * fTCMax, 0.0f * fTCMax}, {0.0f, -1.0f, 0.0f} },
{ {1.0f * fBoxSize, -1.0f * fBoxSize, -1.0f * fBoxSize}, {1.0f * fTCMax, 0.0f * fTCMax}, {0.0f, -1.0f, 0.0f} },
{ {-1.0f * fBoxSize, -1.0f * fBoxSize, 1.0f * fBoxSize}, {0.0f * fTCMax, 1.0f * fTCMax}, {0.0f, -1.0f, 0.0f} },
{ {-1.0f * fBoxSize, -1.0f * fBoxSize, 1.0f * fBoxSize}, {0.0f * fTCMax, 1.0f * fTCMax}, {0.0f, -1.0f, 0.0f} },
{ {1.0f * fBoxSize, -1.0f * fBoxSize, -1.0f * fBoxSize}, {1.0f * fTCMax, 0.0f * fTCMax}, {0.0f, -1.0f, 0.0f} },
{ {1.0f * fBoxSize, -1.0f * fBoxSize, 1.0f * fBoxSize}, {1.0f * fTCMax, 1.0f * fTCMax}, {0.0f, -1.0f, 0.0f} },
};
const UINT nVertexBufferSize = sizeof(stTriangleVertices);
UINT32 pBoxIndices[] //立方体索引数组
= {
0,1,2,
3,4,5,
6,7,8,
9,10,11,
12,13,14,
15,16,17,
18,19,20,
21,22,23,
24,25,26,
27,28,29,
30,31,32,
33,34,35,
};
const UINT nszIndexBuffer = sizeof(pBoxIndices);
UINT64 n64BufferOffset = GRS_UPPER(n64UploadBufferSize, D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT);
//19、使用“定位方式”创建顶点缓冲和索引缓冲,使用与上传纹理数据缓冲相同的一个上传堆
{
//---------------------------------------------------------------------------------------------
//使用定位方式在相同的上传堆上以“定位方式”创建顶点缓冲,注意第二个参数指出了堆中的偏移位置
//按照堆边界对齐的要求,我们主动将偏移位置对齐到了64k的边界上
GRS_THROW_IF_FAILED(pID3DDevice->CreatePlacedResource(
pIUploadHeap.Get()
, n64BufferOffset
, &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(&pVertexDataBegin)));
memcpy(pVertexDataBegin, stTriangleVertices, sizeof(stTriangleVertices));
pIVertexBuffer->Unmap(0, nullptr);
//创建资源视图,实际可以简单理解为指向顶点缓冲的显存指针
stVertexBufferView.BufferLocation = pIVertexBuffer->GetGPUVirtualAddress();
stVertexBufferView.StrideInBytes = sizeof(ST_GRS_VERTEX);
stVertexBufferView.SizeInBytes = nVertexBufferSize;
//计算边界对齐的正确的偏移位置
n64BufferOffset = GRS_UPPER(n64BufferOffset + nVertexBufferSize, D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT);
GRS_THROW_IF_FAILED(pID3DDevice->CreatePlacedResource(
pIUploadHeap.Get()
, n64BufferOffset
, &CD3DX12_RESOURCE_DESC::Buffer(nszIndexBuffer)
, D3D12_RESOURCE_STATE_GENERIC_READ
, nullptr
, IID_PPV_ARGS(&pIIndexBuffer)));
UINT8* pIndexDataBegin = nullptr;
GRS_THROW_IF_FAILED(pIIndexBuffer->Map(0, &stReadRange, reinterpret_cast(&pIndexDataBegin)));
memcpy(pIndexDataBegin, pBoxIndices, nszIndexBuffer);
pIIndexBuffer->Unmap(0, nullptr);
stIndexBufferView.BufferLocation = pIIndexBuffer->GetGPUVirtualAddress();
stIndexBufferView.Format = DXGI_FORMAT_R32_UINT;
stIndexBufferView.SizeInBytes = nszIndexBuffer;
}
//20、在上传堆上以“定位方式”创建常量缓冲
{
n64BufferOffset = GRS_UPPER(n64BufferOffset + nszIndexBuffer, D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT);
// 创建常量缓冲 注意缓冲尺寸设置为256边界对齐大小
GRS_THROW_IF_FAILED(pID3DDevice->CreatePlacedResource(
pIUploadHeap.Get()
, n64BufferOffset
, &CD3DX12_RESOURCE_DESC::Buffer(szMVPBuffer)
, D3D12_RESOURCE_STATE_GENERIC_READ
, nullptr
, IID_PPV_ARGS(&pICBVUpload)));
// Map 之后就不再Unmap了 直接复制数据进去 这样每帧都不用map-copy-unmap浪费时间了
GRS_THROW_IF_FAILED(pICBVUpload->Map(0, nullptr, reinterpret_cast(&pMVPBuffer)));
}
//21、创建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());
}
//22、创建CBV描述符
{
D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc = {};
cbvDesc.BufferLocation = pICBVUpload->GetGPUVirtualAddress();
cbvDesc.SizeInBytes = static_cast(szMVPBuffer);
CD3DX12_CPU_DESCRIPTOR_HANDLE cbvSrvHandle(pISRVHeap->GetCPUDescriptorHandleForHeapStart()
, 1
, pID3DDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV));
pID3DDevice->CreateConstantBufferView(&cbvDesc, cbvSrvHandle);
}
//23、创建各种采样器
{
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);
}
//---------------------------------------------------------------------------------------------
//24、创建定时器对象,以便于创建高效的消息循环
HANDLE phWait = CreateWaitableTimer(NULL, FALSE, NULL);
LARGE_INTEGER liDueTime = {};
liDueTime.QuadPart = -1i64;//1秒后开始计时
SetWaitableTimer(phWait, &liDueTime, 1, NULL, NULL, 0);//40ms的周期
//25、记录帧开始时间,和当前时间,以循环结束为界
ULONGLONG n64tmFrameStart = ::GetTickCount64();
ULONGLONG n64tmCurrent = n64tmFrameStart;
//计算旋转角度需要的变量
double dModelRotationYAngle = 0.0f;
//---------------------------------------------------------------------------------------------
//26、开始消息循环,并在其中不断渲染
DWORD dwRet = 0;
BOOL bExit = FALSE;
while (!bExit)
{//注意这里我们调整了消息循环,将等待时间设置为0,同时将定时性的渲染,改成了每次循环都渲染
//但这不表示说MsgWait函数就没啥用了,坚持使用它是因为后面例子如果想加入多线程控制就非常简单了
dwRet = ::MsgWaitForMultipleObjects(1, &phWait, FALSE, 0, QS_ALLINPUT);
switch (dwRet - WAIT_OBJECT_0)
{
case 0:
case WAIT_TIMEOUT:
{//计时器时间到
}
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;
}
//GRS_TRACE(_T("开始第%u帧渲染{Frame Index = %u}:\n"),nFrame,nFrameIndex);
//开始记录命令
//---------------------------------------------------------------------------------------------
// 准备一个简单的旋转MVP矩阵 让方块转起来
{
n64tmCurrent = ::GetTickCount();
//计算旋转的角度:旋转角度(弧度) = 时间(秒) * 角速度(弧度/秒)
//下面这句代码相当于经典游戏消息循环中的OnUpdate函数中需要做的事情
dModelRotationYAngle += (( n64tmCurrent - n64tmFrameStart ) / 1000.0f) * fPalstance;
n64tmFrameStart = n64tmCurrent;
//旋转角度是2PI周期的倍数,去掉周期数,只留下相对0弧度开始的小于2PI的弧度即可
if ( dModelRotationYAngle > XM_2PI )
{
dModelRotationYAngle = fmod( dModelRotationYAngle, XM_2PI );
}
//模型矩阵 model
XMMATRIX xmRot = XMMatrixRotationY(static_cast(dModelRotationYAngle));
//计算 模型矩阵 model * 视矩阵 view
XMMATRIX xmMVP = XMMatrixMultiply(xmRot, XMMatrixLookAtLH(Eye, At, Up));
//投影矩阵 projection
xmMVP = XMMatrixMultiply(xmMVP, (XMMatrixPerspectiveFovLH(XM_PIDIV4, (FLOAT)iWidth / (FLOAT)iHeight, 0.1f, 1000.0f)));
XMStoreFloat4x4(&pMVPBuffer->m_MVP, xmMVP);
}
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
pICommandList->SetGraphicsRootSignature(pIRootSignature.Get());
ID3D12DescriptorHeap* ppHeaps[] = { pISRVHeap.Get(),pISamplerDescriptorHeap.Get() };
pICommandList->SetDescriptorHeaps(_countof(ppHeaps), ppHeaps);
//设置SRV
pICommandList->SetGraphicsRootDescriptorTable(0, pISRVHeap->GetGPUDescriptorHandleForHeapStart());
CD3DX12_GPU_DESCRIPTOR_HANDLE stGPUCBVHandle(pISRVHeap->GetGPUDescriptorHandleForHeapStart()
, 1
, pID3DDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV));
//设置CBV
pICommandList->SetGraphicsRootDescriptorTable(1, stGPUCBVHandle);
CD3DX12_GPU_DESCRIPTOR_HANDLE hGPUSampler(pISamplerDescriptorHeap->GetGPUDescriptorHandleForHeapStart()
, nCurrentSamplerNO
, nSamplerDescriptorSize);
//设置Sample
pICommandList->SetGraphicsRootDescriptorTable(2, 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);
//注意我们使用的渲染手法是三角形列表,也就是通常的Mesh网格
pICommandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
pICommandList->IASetVertexBuffers(0, 1, &stVertexBufferView);
pICommandList->IASetIndexBuffer(&stIndexBufferView);
//---------------------------------------------------------------------------------------------
//Draw Call!!!
pICommandList->DrawIndexedInstanced(_countof(pBoxIndices), 1, 0, 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++);
}
//::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_KEYDOWN:
{
USHORT n16KeyCode = (wParam & 0xFF);
if (VK_SPACE == n16KeyCode)
{//按空格键切换不同的采样器看效果,以明白每种采样器具体的含义
//UINT nCurrentSamplerNO = 0; //当前使用的采样器索引
//UINT nSampleMaxCnt = 5; //创建五个典型的采样器
++nCurrentSamplerNO;
nCurrentSamplerNO %= nSampleMaxCnt;
}
//根据用户输入变换
//XMVECTOR Eye = XMVectorSet(0.0f, 5.0f, -10.0f, 0.0f); //眼睛位置
//XMVECTOR At = XMVectorSet(0.0f, 0.0f, 1.0f, 0.0f); //眼睛所盯的位置
//XMVECTOR Up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f); //头部正上方位置
if (VK_UP == n16KeyCode || 'w' == n16KeyCode || 'W' == n16KeyCode)
{
Eye = XMVectorAdd(Eye, XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f));
}
if (VK_DOWN == n16KeyCode || 's' == n16KeyCode || 'S' == n16KeyCode)
{
Eye = XMVectorAdd(Eye, XMVectorSet(0.0f, -1.0f, 0.0f, 0.0f));
}
if (VK_RIGHT == n16KeyCode || 'd' == n16KeyCode || 'D' == n16KeyCode)
{
Eye = XMVectorAdd(Eye, XMVectorSet(1.0f, 0.0f, 0.0f, 0.0f));
}
if (VK_LEFT == n16KeyCode || 'a' == n16KeyCode || 'A' == n16KeyCode)
{
Eye = XMVectorAdd(Eye, XMVectorSet(-1.0f, 0.0f, 0.0f, 0.0f));
}
if ( VK_ADD == n16KeyCode || VK_OEM_PLUS == n16KeyCode )
{
//double fPalstance = 10.0f * XM_PI / 180.0f; //物体旋转的角速度,单位:弧度/秒
fPalstance += 10 * XM_PI / 180.0f;
if (fPalstance > XM_PI)
{
fPalstance = XM_PI;
}
}
if ( VK_SUBTRACT == n16KeyCode || VK_OEM_MINUS == n16KeyCode )
{
fPalstance -= 10 * XM_PI / 180.0f;
if ( fPalstance < 0.0f )
{
fPalstance = XM_PI / 180.0f;
}
}
}
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
TextureCube.hlsl
struct PSInput
{
float4 position : SV_POSITION;
float2 uv : TEXCOORD;
};
cbuffer MVPBuffer : register(b0)
{
float4x4 m_MVP;
};
Texture2D g_texture : register(t0);
SamplerState g_sampler : register(s0);
PSInput VSMain(float4 position : POSITION, float2 uv : TEXCOORD)
{
PSInput result;
result.position = mul(position, m_MVP);
result.uv = uv;
return result;
}
float4 PSMain(PSInput input) : SV_TARGET
{
return g_texture.Sample(g_sampler, input.uv);
}