第四章-初始化Direct3D
首先我们需要一个错误检测和抛出机制
inline std::string ToString(const HRESULT& result)
{char buffer[256];sprintf_s(buffer, "error code : 0x%08X\n", result);return std::string(buffer);
}class MyException : public std::runtime_error
{
public:MyException(const HRESULT& result): std::runtime_error(ToString(result)), m_Result(result){}
private:HRESULT m_Result;
};inline std::string ThrowIfFailed(const HRESULT result)
{if (!SUCCEEDED(result)){throw MyException(result);}
}
下面是我的整体结构
#pragma once
#include <d3d12.h>
#include <dxgi1_6.h>
#include <d3dcompiler.h>
#include <wrl.h>
#include "../d3dx12.h"
#pragma comment(lib, "d3dcompiler.lib")
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "d3d12.lib")
namespace REVIEW
{using Microsoft::WRL::ComPtr;class reviewDirect{public:void init();private:void createDevice();private:ComPtr<ID3D12Device> m_Device;};
}
创建设备
我们之前所说direct3D依赖与设备,比如创建命令队列等。设备代表着一个显示适配器,显示适配器是一种3D图形硬件。但是一个系统也能用软件显示来模拟3D图形硬件的功能。
HRESULT WINAPI D3D12CreateDevice(
_In_opt_ IUnknown* pAdapter,
D3D_FEATURE_LEVEL MinimumFeatureLevel,
_In_ REFIID riid, // Expected: ID3D12Device
_COM_Outptr_opt_ void** ppDevice );
- pAdapter:指定了创建设备时所用的显示适配器,若将此参数设定为空指针,则使用主适配器。我们目前先设定主适配器
- MinimumFeatureLevel:应用程序需要硬件所支持的最低功能级别,如果适配器不支持此功能级别,则设备创建失败。目前指定为D3D_FEATURE_LEVEL_11_0(即支持Direct3D 11的特性)
void reviewDirect::createDevice(const ComPtr<IDXGIFactory7>& factory){HRESULT hardwareResult = D3D12CreateDevice(nullptr,D3D_FEATURE_LEVEL_11_0,IID_PPV_ARGS(m_Device.GetAddressOf()));//如果默认的主适配器创建失败,回退到warp设备if (FAILED(hardwareResult)){ComPtr<IDXGIAdapter4> pAdapter;UINT i = 0;factory->EnumWarpAdapter(IID_PPV_ARGS(pAdapter.GetAddressOf()));ThrowIfFailed(D3D12CreateDevice(pAdapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&m_Device)));}}void reviewDirect::initBase()
{#if defined(DEBUG || defined(_DEBUG)//启动d3d12的调式层ComPtr<ID3D12Debug> pDebug;ThrowIfFailed(D3D12GetDebugInterface(IID_PPV_ARGS(pDebug.GetAddressOf())));pDebug->EnableDebugLayer();#endifComPtr<IDXGIFactory7> pFactory;ThrowIfFailed(CreateDXGIFactory(IID_PPV_ARGS(pFactory.GetAddressOf())));//尝试创建硬件设备createDevice(pFactory);
}
- 为了进入调试模式,我们首先开启了调试层。随后,Direct3D便会开启额外的调试功能,并在错误发生时向VC++的输出窗口发送类似下面的调试信息
- 当使用主适配器创建失败以后,程序将回退到软件适配器WARP设备(window advanced Rasterization Platform)
创建围栏并获取描述符的大小
创建好设备以后,就可以为cpu和gpu的同步创建围栏fence了。另外,之前我们说过pipeline使用计算机的资源都是用描述符去引用的,我们需要知道我们的设备可以创建多少个描述符
void reviewDirect::createFence()
{ThrowIfFailed(m_Device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(m_Fence.GetAddressOf())));
}void reviewDirect::calculateNumOfDescriptors()
{m_CbvSrvUavDescriptorSize = m_Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);m_RtvDescriptorSize = m_Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);m_DsvDescriptorSize = m_Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
}
检测对4x MSAA质量级别的支持
void reviewDirect::checkMSAASupport()
{D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS msaaQualityLevels;msaaQualityLevels.Format = mBackBufferFormat;msaaQualityLevels.SampleCount = 4;msaaQualityLevels.NumQualityLevels = 0;msaaQualityLevels.Flags = D3D12_MULTISAMPLE_QUALITY_LEVELS_FLAG_NONE;ThrowIfFailed(m_Device->CheckFeatureSupport(D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,&msaaQualityLevels, sizeof(msaaQualityLevels)));m4xMssaaQuality = msaaQualityLevels.NumQualityLevels;assert(m4xMssaaQuality > 0 && "Unexpected MSAA quality level.");
}
创建命令队列和命令列表
void reviewDirect::createCommandQueueList()
{D3D12_COMMAND_QUEUE_DESC commandQueueDesc {};commandQueueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;commandQueueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;ThrowIfFailed(m_Device->CreateCommandQueue(&commandQueueDesc, IID_PPV_ARGS(m_CommandQueue.GetAddressOf())));ThrowIfFailed(m_Device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(m_CommandAllocator.GetAddressOf())));ThrowIfFailed(m_Device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, m_CommandAllocator.Get(), nullptr, IID_PPV_ARGS(m_CommandList.GetAddressOf())));//nodeMask:指定命令列表关联的物理适配器节点,在多 GPU 系统中使用。单 GPU 系统一般设为 0。
// type:D3D12_COMMAND_LIST_TYPE 枚举类型,指定命令列表的类型。常见取值如下:
// D3D12_COMMAND_LIST_TYPE_DIRECT:直接命令列表,可包含所有类型的命令,常用于渲染操作。
// D3D12_COMMAND_LIST_TYPE_COMPUTE:计算命令列表,专门用于计算着色器相关的命令。
// D3D12_COMMAND_LIST_TYPE_COPY:复制命令列表,主要用于数据的复制操作。
// pCommandAllocator:指向 ID3D12CommandAllocator 对象的指针。命令分配器负责管理命令列表所需的内存,每个命令列表都需要关联一个命令分配器。
// pInitialState:指向 ID3D12PipelineState 对象的指针,用于指定命令列表的初始管线状态。如果不需要初始状态,可以传入 nullptr。
// riid:请求的命令列表接口的 IID(接口标识符),通常使用 IID_PPV_ARGS 宏来获取,例如 IID_PPV_ARGS(&commandList)。
// ppCommandList:用于接收创建好的命令列表对象的指针。
}
目前还不会发起任何的绘制命令,所以不会用到流水线状态对象
描述创建交换链
交换链用于交换帧,首先需要填写一份DXGI_SWAP_CHAIN_DESC结构实例
typedef struct DXGI_SWAP_CHAIN_DESC{DXGI_MODE_DESC BufferDesc;DXGI_SAMPLE_DESC SampleDesc;DXGI_USAGE BufferUsage;UINT BufferCount;HWND OutputWindow;BOOL Windowed;DXGI_SWAP_EFFECT SwapEffect;UINT Flags;} DXGI_SWAP_CHAIN_DESC;
BufferDesc
- 类型:
DXGI_MODE_DESC
- 作用:描述后台缓冲区的显示模式,涵盖分辨率、刷新率、像素格式等信息。
DXGI_MODE_DESC
结构体定义
typedef struct DXGI_MODE_DESC {UINT Width;UINT Height;DXGI_RATIONAL RefreshRate;DXGI_FORMAT Format;DXGI_MODE_SCANLINE_ORDER ScanlineOrdering;DXGI_MODE_SCALING Scaling;
} DXGI_MODE_DESC;
Width
和Height
:分别表示缓冲区的宽度和高度(以像素为单位)。RefreshRate
:表示屏幕的刷新率,是一个DXGI_RATIONAL
类型,包含分子和分母,例如 60Hz 可表示为{60, 1}
。Format
:指定像素格式,如DXGI_FORMAT_R8G8B8A8_UNORM
代表 32 位的 RGBA 格式。ScanlineOrdering
:指定扫描线的排序方式。Scaling
:指定缩放模式。
SampleDesc
-
- 类型:
DXGI_SAMPLE_DESC
- 作用:描述多重采样的相关信息。
DXGI_SAMPLE_DESC
结构体定义如下:
- 类型:
typedef struct DXGI_SAMPLE_DESC {UINT Count;UINT Quality;
} DXGI_SAMPLE_DESC;
Count
:表示每个像素的采样数量。Quality
:表示采样的质量级别。
BufferUsage
-
- 类型:
DXGI_USAGE
- 作用:指定后台缓冲区的使用方式,可通过按位或操作组合多个标志。常见的标志有:
- 类型:
-
-
DXGI_USAGE_RENDER_TARGET_OUTPUT
:表示缓冲区将用作渲染目标输出。DXGI_USAGE_SHADER_INPUT
:表示缓冲区可作为着色器的输入。
-
BufferCount
-
- 类型:
UINT
- 作用:指定交换链中后台缓冲区的数量。通常为 2(双缓冲)或 3(三缓冲)。双缓冲模式下,一个缓冲区用于渲染,另一个用于显示;三缓冲模式可减少画面卡顿,提升流畅度。
- 类型:
OutputWindow
-
- 类型:
HWND
- 作用:指定交换链要呈现到的窗口句柄。这是一个 Windows 窗口句柄,用于将渲染结果显示到指定的窗口中。
- 类型:
Windowed
-
- 类型:
BOOL
- 作用:指定交换链是在窗口模式(
TRUE
)还是全屏模式(FALSE
)下运行。
- 类型:
SwapEffect
-
- 类型:
DXGI_SWAP_EFFECT
- 作用:指定交换链的呈现方式。常见的取值有:
- 类型:
-
-
DXGI_SWAP_EFFECT_DISCARD
:每次呈现后丢弃旧的缓冲区内容,适用于大多数情况。DXGI_SWAP_EFFECT_FLIP_DISCARD
:使用翻转模型进行呈现,可提高性能,是推荐的方式。
-
Flags
-
- 类型:
UINT
- 作用:指定交换链的额外标志,可通过按位或操作组合多个标志。例如
DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH
允许在窗口模式和全屏模式之间切换。
- 类型:
void reviewDirect::createSwapChain(const ComPtr<IDXGIFactory7>& factory)
{ComPtr<IDXGISwapChain> pSwapChain;DXGI_SWAP_CHAIN_DESC desc{};desc.BufferDesc.Width = m_Width;desc.BufferDesc.Height = m_Height;desc.BufferDesc.RefreshRate.Numerator = 60;desc.BufferDesc.Format = mBackBufferFormat;desc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;desc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;desc.SampleDesc.Count = m4xMssaaQuality ? 4 : 1;desc.SampleDesc.Quality = m4xMssaaQuality ? m4xMssaaQuality - 1 : 0;desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;desc.BufferCount = m_SwapChainBufferCount;desc.OutputWindow = reviewWinApp::GetHwnd();desc.Windowed = true;desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;ThrowIfFailed(factory->CreateSwapChain(m_CommandQueue.Get(), &desc, pSwapChain.GetAddressOf()));pSwapChain.As(&m_SwapChain);}
pSwapChain
是一个 ComPtr<IDXGISwapChain>
类型的智能指针,m_SwapChain
可能是另一种类型的 ComPtr
,比如 ComPtr<IDXGISwapChain3>
或者其他派生自 IDXGISwapChain
的接口类型。As
方法会尝试把 pSwapChain
所管理的 IDXGISwapChain
对象转换为 m_SwapChain
所对应的接口类型,若转换成功,m_SwapChain
就会持有转换后的接口指针。
创建描述符堆
我们的描述符需要通过创建描述符堆来存储,对此Direct3D 以ID3D12DescriptorHeap接口表示描述符堆,并用ID3D12Device::CreateDescriptorHeap方法来创建它。这里演示创建rtv的描述符堆(用来指向渲染目标资源)dsv(用来指向深度缓冲区资源)
typedef struct D3D12_DESCRIPTOR_HEAP_DESC
{
D3D12_DESCRIPTOR_HEAP_TYPE Type;
UINT NumDescriptors;
D3D12_DESCRIPTOR_HEAP_FLAGS Flags;
UINT NodeMask;
} D3D12_DESCRIPTOR_HEAP_DESC;
void reviewDirect::createDescriptorHeap()
{D3D12_DESCRIPTOR_HEAP_DESC rtvDesc{};rtvDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;rtvDesc.NumDescriptors = m_SwapChainBufferCount;rtvDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;rtvDesc.NodeMask = 0;D3D12_DESCRIPTOR_HEAP_DESC dsvDesc{};dsvDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;dsvDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;dsvDesc.NumDescriptors = 1;dsvDesc.NodeMask = 0;ThrowIfFailed(m_Device->CreateDescriptorHeap(&rtvDesc, IID_PPV_ARGS(m_RtvDescriptorHeap.GetAddressOf())));ThrowIfFailed(m_Device->CreateDescriptorHeap(&dsvDesc, IID_PPV_ARGS(m_DsvDescriptorHeap.GetAddressOf())));
}
书中用到的框架中有以下定义
UINT m_SwapChainBufferCount = 2;
int m_CurrentBackBufferIndex = 0;
其中m_CurrentBackBufferIndex是用来记录当前后台缓冲区的索引的,这样在交换渲染缓冲区和显示缓冲区的时候有用。
获取描述符
有了描述符堆以后,我们还需要通过描述符堆去访问所存的描述符,通过句柄来引用描述符,通过下面的方式来获取:
rtvhandle的获取,获取m_CurrentBackBufferIndex对应的描述符handle
D3D12_CPU_DESCRIPTOR_HANDLE reviewDirect::getCurrentBackBufferView() const
{return CD3DX12_CPU_DESCRIPTOR_HANDLE(m_RtvDescriptorHeap->GetCPUDescriptorHandleForHeapStart(),m_CurrentBackBufferIndex, m_RtvDescriptorSize);
}
dsvhandle获取
因为dsv目前也就一个描述符,所以可以直接获取
D3D12_CPU_DESCRIPTOR_HANDLE reviewDirect::getDepthStencilViewHandle() const
{return m_DsvDescriptorHeap->GetCPUDescriptorHandleForHeapStart();
}
创建渲染目标视图
前面创建了描述符堆,描述符的获取,但是有两个问题,首先我们的描述符对应的资源没有创建,其次我们描述符没有绑定对应的资源
渲染目标视图是在swapchain中,因为swapchain管理的就是渲染目标
m_SwapChain->GetBuffer()
virtual HRESULT STDMETHODCALLTYPE GetBuffer(
/* [in] */ UINT Buffer,
/* [annotation][in] */
_In_ REFIID riid,
/* [annotation][out][in] */
_COM_Outptr_ void **ppSurface) = 0;
- buffer:希望获得的特定的后台缓冲区的索引(有时后台缓冲区并不只一个,所以需要索引指明)
- riid:希望获得的id3d12Resource接口的COMID
- ppSurface:返回一个指向ID3D12Resource接口的指针,这便是希望获得的后台缓冲区
调用IDXGISwapchain::GetBuffer方法会增加相关后台缓冲区的COM引用计数,所以在每次使用后一定要将其释放。通过ComPtr可以自定的实现这一点
接下来,使用ID3D12Device::CreateRenderTargetView方法来为获取的后台缓冲区创建渲染目标视图(view)实际上也就是创建描述符handle/view 然后绑定后台缓冲区
m_Device->CreateRenderTargetView()
virtual void STDMETHODCALLTYPE CreateRenderTargetView(
_In_opt_ ID3D12Resource *pResource,
_In_opt_ const D3D12_RENDER_TARGET_VIEW_DESC *pDesc,
_In_ D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor) = 0;
通过下面的方法就能为每个swapchain对应的buffer创建view(描述符handle)然后写入descriptor heap中
void reviewDirect::createRenderTargetView()
{CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(m_RtvDescriptorHeap->GetCPUDescriptorHandleForHeapStart(),m_CurrentBackBufferIndex, m_RtvDescriptorSize);for (UINT i = 0; i < m_SwapChainBufferCount; i++){ThrowIfFailed(m_SwapChain->GetBuffer(i, IID_PPV_ARGS(m_RenderBuffer[i].GetAddressOf())));m_Device->CreateRenderTargetView(m_RenderBuffer[i].Get(), nullptr, rtvHandle);rtvHandle.Offset(1, m_RtvDescriptorSize);}
}
pDesc
参数设置为 nullptr
时,DirectX 会根据资源的属性自动推断出渲染目标视图的配置,使用资源的默认设置来创建渲染目标视图。这里我们的swapchain已经设置好了其资源格式,就不需要再次创建了
创建深度/模版缓冲区及其视图
之前的swapchain得到的buffer实际上是一种资源, 之前所说深度缓冲区其实就是一种2D纹理。纹理是一种GPU资源因此我们需要填写D3D12_RESOURCE_DESC结构性来描述纹理资源,再用ID3D12Device::CreateCommitedResource方法来创建它。D3D12_RESOURCE_DESC结构体定义如下:
typedef struct D3D12_RESOURCE_DESC
{
D3D12_RESOURCE_DIMENSION Dimension;
UINT64 Alignment;
UINT64 Width;
UINT Height;
UINT16 DepthOrArraySize;
UINT16 MipLevels;
DXGI_FORMAT Format;
DXGI_SAMPLE_DESC SampleDesc;
D3D12_TEXTURE_LAYOUT Layout;
D3D12_RESOURCE_FLAGS Flags;
} D3D12_RESOURCE_DESC;
- Dimension资源纬度
typedef
enum D3D12_RESOURCE_DIMENSION{D3D12_RESOURCE_DIMENSION_UNKNOWN = 0,D3D12_RESOURCE_DIMENSION_BUFFER = 1,D3D12_RESOURCE_DIMENSION_TEXTURE1D = 2,D3D12_RESOURCE_DIMENSION_TEXTURE2D = 3,D3D12_RESOURCE_DIMENSION_TEXTURE3D = 4} D3D12_RESOURCE_DIMENSION;
- width,以纹素为单位来表示的纹理宽度。对于缓冲区资源,此项是缓冲区占用的字节数。height,以纹素为单位来表示的纹理高度
GPU的资源都存于堆(heap)中,其本质是具有特定的属性的GPU显存块。ID3D12Device::CreateCommitedResource方法将根据我们所提供的属性创建一个资源与一个堆,并将该资源提交到这个堆中。
virtual HRESULT STDMETHODCALLTYPE CreateCommittedResource(
_In_ const D3D12_HEAP_PROPERTIES *pHeapProperties,
D3D12_HEAP_FLAGS HeapFlags,
_In_ const D3D12_RESOURCE_DESC *pDesc,
D3D12_RESOURCE_STATES InitialResourceState,
_In_opt_ const D3D12_CLEAR_VALUE *pOptimizedClearValue,
REFIID riidResource,
_COM_Outptr_opt_ void **ppvResource) = 0;typedef struct D3D12_HEAP_PROPERTIES{D3D12_HEAP_TYPE Type;D3D12_CPU_PAGE_PROPERTY CPUPageProperty;D3D12_MEMORY_POOL MemoryPoolPreference;UINT CreationNodeMask;UINT VisibleNodeMask;} D3D12_HEAP_PROPERTIES;
- pHeapProperties:(资源欲提交至的)堆所具有的属性。有一些属性是针对高级用法而设的。目前只需要关心type,而且这里可以用辅助函数来创建一个默认的。
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT)
type的类型包含
enum D3D12_HEAP_TYPE{//默认堆。向这堆里提交的资源,唯独GPU可以访问。比如GPU会读写深度模板缓冲区,但是CPU//不需要访问它,所以深度/模板缓冲区放默认堆中D3D12_HEAP_TYPE_DEFAULT = 1,//上传堆。向此堆里提交的都是经CPU上传至GPU的资源D3D12_HEAP_TYPE_UPLOAD = 2,//回读堆。向这种堆里提交的都是需要由CPU读取的资源D3D12_HEAP_TYPE_READBACK = 3,//此成员应用于高级的场景D3D12_HEAP_TYPE_CUSTOM = 4} D3D12_HEAP_TYPE;
- HeapFlags:与(资源欲提交至的)堆有关的额外选项标志。通常将它设为D3D12_HEAP_FLAG_NONE
- pDesc:指向一个D3D12_RESOURCE_DESC实例的指针,用它描述待建资源
- InitialResourceState资源的状态。不管什么时候,每个资源都会处于一种特定的使用状态。在资源创建时。需要用此参数来设置它的初始状态。对深度/缓冲区来说,通常设置为D3D12_RESOURCE_STATE_COMMON,在利用之前说的ResourceBarrier方法转换状态,比如转换为写的状态。
- pOptimizedClearValue:这个后续在渲染的时候会指定对应的值
void reviewDirect::createDepthStencilView()
{D3D12_RESOURCE_DESC desc{};desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;desc.Alignment = 0;desc.Width = m_Width;desc.Height = m_Height;desc.DepthOrArraySize = 1;desc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;desc.SampleDesc.Count = m4xMssaaQuality ? 4 : 1;desc.SampleDesc.Quality = m4xMssaaQuality ? m4xMssaaQuality - 1 : 0;desc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;D3D12_CLEAR_VALUE clearValue{};clearValue.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;clearValue.DepthStencil.Depth = 1.0f;clearValue.DepthStencil.Stencil = 0;ThrowIfFailed(m_Device->CreateCommittedResource(&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),D3D12_HEAP_FLAG_NONE,&desc,D3D12_RESOURCE_STATE_COMMON,&clearValue,IID_PPV_ARGS(m_DepthStencilBuffer.GetAddressOf())));
}
这里创建了depth的buffer资源,但是我们需要对depth资源在渲染的时候写入,需要转换资源类型,这里就可以用到临时的commandlist 来处理,首先我先创建了一个templecommandallocator,这样就可以临时的创建多个commandlist,相当于一个临时的公司
void reviewDirect::createTempleCommandAllocator()
{ThrowIfFailed(m_Device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(m_TempleCommandAllocator.GetAddressOf())));
}
加上下面这段代码将缓冲区资源转为深度可写入的状态
ComPtr<ID3D12GraphicsCommandList> templeCommandList;
ThrowIfFailed(m_Device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, m_TempleCommandAllocator.Get(),nullptr, IID_PPV_ARGS(templeCommandList.GetAddressOf())));templeCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_DepthStencilBuffer.Get(), D3D12_RESOURCE_STATE_COMMON,D3D12_RESOURCE_STATE_DEPTH_WRITE));
注意这里就必须进行一次命令的执行,因为离开了createDepthStencilView()后临时的命令列表就会释放。这里创建了堵塞cpu的fence使用
void reviewDirect::waitForFinish()
{UINT fenceValue = m_FenceValue;ThrowIfFailed(m_CommandQueue->Signal(m_Fence.Get(), fenceValue));if (m_Fence->GetCompletedValue() < fenceValue){HANDLE handEvent = CreateEventEx(nullptr, nullptr, 0, EVENT_ALL_ACCESS);ThrowIfFailed(m_Fence->SetEventOnCompletion(fenceValue, handEvent));WaitForSingleObject(handEvent, INFINITE);CloseHandle(handEvent);}
}
我们还需要加上描述符来指向这个资源,完整的代码:
void reviewDirect::createDepthStencilView()
{D3D12_RESOURCE_DESC desc{};desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;desc.Alignment = 0;desc.Width = m_Width;desc.Height = m_Height;desc.DepthOrArraySize = 1;desc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;desc.SampleDesc.Count = m4xMssaaQuality ? 4 : 1;desc.SampleDesc.Quality = m4xMssaaQuality ? m4xMssaaQuality - 1 : 0;desc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;D3D12_CLEAR_VALUE clearValue{};clearValue.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;clearValue.DepthStencil.Depth = 1.0f;clearValue.DepthStencil.Stencil = 0;ThrowIfFailed(m_Device->CreateCommittedResource(&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),D3D12_HEAP_FLAG_NONE,&desc,D3D12_RESOURCE_STATE_COMMON,&clearValue,IID_PPV_ARGS(m_DepthStencilBuffer.GetAddressOf())));CD3DX12_CPU_DESCRIPTOR_HANDLE viewHandle(m_DsvDescriptorHeap->GetCPUDescriptorHandleForHeapStart());m_Device->CreateDepthStencilView(m_DepthStencilBuffer.Get(), nullptr, viewHandle);ComPtr<ID3D12GraphicsCommandList> templeCommandList;ThrowIfFailed(m_Device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, m_TempleCommandAllocator.Get(),nullptr, IID_PPV_ARGS(templeCommandList.GetAddressOf())));templeCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_DepthStencilBuffer.Get(), D3D12_RESOURCE_STATE_COMMON,D3D12_RESOURCE_STATE_DEPTH_WRITE));waitForFinish();ID3D12CommandList* commandLists[] = { templeCommandList.Get() };m_CommandQueue->ExecuteCommandLists(_countof(commandLists), commandLists);waitForFinish();
}
设置视口
我们通常会将3D场景绘制到与整个屏幕(在全屏模式下)或整个窗口工作区大小相当的后台缓冲区。但是,有时只是希望3D场景绘制到后台缓冲区的某个矩形子区域当中,如下图:
我们把后台缓冲区中的这种矩形子区域叫做视口,并通过下列结构描述它:
typedef struct D3D12_VIEWPORT
{
FLOAT TopLeftX;
FLOAT TopLeftY;
FLOAT Width;
FLOAT Height;
FLOAT MinDepth;
FLOAT MaxDepth;
} D3D12_VIEWPORT;
FLOAT TopLeftX
-
- 该成员表示视口矩形区域左上角的
X
坐标,以像素为单位。它指定了视口在屏幕上的水平起始位置。
- 该成员表示视口矩形区域左上角的
FLOAT TopLeftY
-
- 此成员表示视口矩形区域左上角的
Y
坐标,同样以像素为单位。它指定了视口在屏幕上的垂直起始位置。
- 此成员表示视口矩形区域左上角的
FLOAT Width
-
- 表示视口矩形区域的宽度,以像素为单位。这个值决定了视口在水平方向上的大小。
FLOAT Height
-
- 表示视口矩形区域的高度,以像素为单位。它决定了视口在垂直方向上的大小。
FLOAT MinDepth
-
- 定义了视口的最小深度值,范围通常是
[0.0f, 1.0f]
。在深度测试时,只有深度值大于等于MinDepth
的像素才会被考虑。
- 定义了视口的最小深度值,范围通常是
FLOAT MaxDepth
-
- 定义了视口的最大深度值,范围同样是
[0.0f, 1.0f]
。在深度测试时,只有深度值小于等于MaxDepth
的像素才会被考虑。
- 定义了视口的最大深度值,范围同样是
void reviewDirect::createViewPort()
{m_RenderViewPort.TopLeftX = 0;m_RenderViewPort.TopLeftY = 0;m_RenderViewPort.Width = m_Width;m_RenderViewPort.Height = m_Height;m_RenderViewPort.MinDepth = 0.0f;m_RenderViewPort.MaxDepth = 1.0f;
}
后续可以设置视口,这个我在后面在写
事实上,视口可以用作分屏游戏。首先创建两个视口,一个占屏幕左半部分,一个右半部分,接下来,在左视口中以玩家1的视角来绘制3D场景,在右视口中以玩家2的视角来绘制3D场景即可。
设置裁剪矩形
我们可以相对后台缓冲区定义一个裁剪矩形,在此矩形外的像素都将被剔除(即这些图像部分将不会被光栅化)至后台缓冲区。这个方法能用于优化程序的性能。例如,假设已知有一个矩形的UI元素覆盖于屏幕的某块区域的最上层,那么我们也就无须对覆盖区域绘制
void reviewDirect::createRECT()
{m_ScissorRect.left = 0;m_ScissorRect.top = 0;m_ScissorRect.right = m_Width;m_ScissorRect.bottom = m_Height;
}