【DirectX12从零渲染03】DirectX12 绘制流程

本文详细介绍了DirectX12中的绘制流程,包括重置上传资源、提交命令、设置视口和裁剪区域、清空缓存、合并输出、页面翻转以及同步等待,通过代码示例展示了如何在Dx12渲染中实现基础的图形绘制操作。
摘要由CSDN通过智能技术生成

一、Dx12绘制流程

上一章说完了Dx12的初始化,接下来就是核心了,开始绘制画面。
这一阶段,就是CPU向GPU送任务,处理,如此往复即可,具体可以理解为如下几个步骤:
1、重置上传;
2、提交命令;
3、页面翻转;
4、等待同步;

一、重置上传

在每次渲染绘制之前,都要把上传相关的进行重置,当初用快递举例,但快递是接收到,快递壳子就丢掉了,但这里不是,只是重置而已,其目的就是为了避免删除后再创建这样的开销;

void cDx12Rendering::ResetCMDListAlloctor()
{
	//重置上传
	m_spCommandAllocator->Reset();
	m_spGraphicsCommandList->Reset(m_spCommandAllocator.Get(), NULL);
}

二、提交命令

提交绘制任务是绘制的绝对核心,也是开放了很复杂的参数,用来“本地化”Dx12的通用方案,以期达到想要的渲染效果。
也因为任务的可定制化极高,这里将会涉及到动辄上千的代码,但暂时先采用最简单的绘制让我们有一个总体的印象,细节将会在后面的章节不断补充。
先,来绘制一个纯色背景吧。

void cDx12Rendering::SubmitDrawingTask()
{
	//把后缓冲区的资源状态切换成Render Target
	CD3DX12_RESOURCE_BARRIER ResourceBarrierPresent = CD3DX12_RESOURCE_BARRIER::Transition(
		vecRTVBuffer[m_iCurrentSwapBuffIndex].Get(),
		D3D12_RESOURCE_STATE_PRESENT, 
		D3D12_RESOURCE_STATE_RENDER_TARGET);

	m_spGraphicsCommandList->ResourceBarrier(1, &ResourceBarrierPresent);

	//设置视口和裁剪区域
	m_spGraphicsCommandList->RSSetViewports(1, &m_dvViewprotInfo);
	m_spGraphicsCommandList->RSSetScissorRects(1, &m_drViewprotRect);

	//清空后缓存和深度缓存
	m_spGraphicsCommandList->ClearRenderTargetView(
		CD3DX12_CPU_DESCRIPTOR_HANDLE(
			m_spRTVHeap->GetCPUDescriptorHandleForHeapStart(),
			m_iCurrentSwapBuffIndex,
			m_iRTVDescriptorSize),
		DirectX::Colors::Blue,
		0, nullptr);

	m_spGraphicsCommandList->ClearDepthStencilView(
		m_spDSVHeap->GetCPUDescriptorHandleForHeapStart(),
		D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL,
		1.f, 0, 0, NULL);

	//输出的合并阶段
	D3D12_CPU_DESCRIPTOR_HANDLE SwapBufferView = CD3DX12_CPU_DESCRIPTOR_HANDLE(
		m_spRTVHeap->GetCPUDescriptorHandleForHeapStart(),
		m_iCurrentSwapBuffIndex,
		m_iRTVDescriptorSize);

	D3D12_CPU_DESCRIPTOR_HANDLE DepthStencilView = m_spDSVHeap->GetCPUDescriptorHandleForHeapStart();

	m_spGraphicsCommandList->OMSetRenderTargets(
		1,
		&SwapBufferView,
		true,
		&DepthStencilView);

	//把后缓冲区切换成PRESENT状态
	CD3DX12_RESOURCE_BARRIER ResourceBarrierPresentRenderTarget = CD3DX12_RESOURCE_BARRIER::Transition(
		vecRTVBuffer[m_iCurrentSwapBuffIndex].Get(),
		D3D12_RESOURCE_STATE_RENDER_TARGET,
		D3D12_RESOURCE_STATE_PRESENT);

	m_spGraphicsCommandList->ResourceBarrier(1, &ResourceBarrierPresentRenderTarget);

	//录入完成,提交命令
	m_spGraphicsCommandList->Close();
	ID3D12CommandList* pCommandList[] = { m_spGraphicsCommandList.Get() };
	m_spCommandQueue->ExecuteCommandLists(_countof(pCommandList), pCommandList);
}

1、前言

在这一部分,主要就是CPU向GPU送任务的核心操作,包括了六个步骤,逐步的打包好必要资源,然后贴上任务列表,提交给GPU。
在此要注意,打包的必要资源中,绘制模型资源的步骤这里没有提及,毕竟这是最基础的任务提交,在之后,我们会把这个步骤展开详细的进行解释,包括上传资源堆放在GPU哪里,怎么控制GPU渲染的效果等等。

2、把后缓冲区的资源状态切换成Render Target

此步骤就是堆资源的状态进行转换,将资源从呈现状态转换为渲染目标状态(Render Target)。
我们可以将此转换看作是一条告知GPU某资源状态正在进行转换的命令,让它在执行后续的命令时防范一下读写的资源冒险,而这种防范是dx12设定好的,只需要用好资源屏障就可以。

CD3DX12_RESOURCE_BARRIER ResourceBarrierPresent = CD3DX12_RESOURCE_BARRIER::Transition(
		vecRTVBuffer[m_iCurrentSwapBuffIndex].Get(),
		D3D12_RESOURCE_STATE_PRESENT, 
		D3D12_RESOURCE_STATE_RENDER_TARGET);

主要用到的 API 为 ResourceBarrier:

void ResourceBarrier(
  [in] UINT                         NumBarriers,
  [in] const D3D12_RESOURCE_BARRIER *pBarriers
);

a、NumBarriers:UINT类型,提交的屏障说明数。
b、pBarriers:指向屏障说明数组的指针。

3、设置视口和裁剪区域

明确两个概念,即视口和裁剪区域。
视口:是窗口还可以分为若干个区域,称为视口,窗口中用来绘图的区域。一般设置视口大小等于窗口大小。
裁剪区域:是在视口中让你看到的图形,即显示出来的那部分。

m_spGraphicsCommandList->RSSetViewports(1, &m_dvViewprotInfo);
m_spGraphicsCommandList->RSSetScissorRects(1, &m_drViewprotRect);

这里主要的API是 RSSetViewports 和 RSSetScissorRects:

void RSSetViewports(
  [in]           UINT                 NumViewports,
  [in, optional] const D3D11_VIEWPORT *pViewports
);

a、NumBarriers:UINT类型,要绑定的视区数。
b、pViewports:const D3D11_VIEWPORT*类型,指向要绑定到设备的 D3D11_VIEWPORT 结构的数组的指针。

void RSSetScissorRects(
  [in] UINT             NumRects,
  [in] const D3D10_RECT *pRects
);

a、NumBarriers:UINT类型,要绑定的剪刀矩形数。
b、pRects :const D3D10_RECT*类型,指向剪刀矩形数组的数组的指针。

4、清空后缓存和深度缓存

在此步骤,应该是为GPU处理做前置的,先把原资源进行初始化,主要关注两个堆,即RTV堆 和 DSV堆 。

m_spGraphicsCommandList->ClearRenderTargetView(
		CD3DX12_CPU_DESCRIPTOR_HANDLE(
			m_spRTVHeap->GetCPUDescriptorHandleForHeapStart(),
			m_iCurrentSwapBuffIndex,
			m_iRTVDescriptorSize),
		DirectX::Colors::Blue,
		0, nullptr);

m_spGraphicsCommandList->ClearDepthStencilView(
		m_spDSVHeap->GetCPUDescriptorHandleForHeapStart(),
		D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL,
		1.f, 0, 0, NULL);

这里主要的API是 ClearRenderTargetView和 ClearDepthStencilView:

void ClearRenderTargetView(
  [in] D3D12_CPU_DESCRIPTOR_HANDLE RenderTargetView,
  [in] const FLOAT [4]             ColorRGBA,
  [in] UINT                        NumRects,
  [in] const D3D12_RECT            *pRects
);

a、RenderTargetView: D3D12_CPU_DESCRIPTOR_HANDLE类型,指定D3D12_CPU_DESCRIPTOR_HANDLE结构,该结构描述 CPU 描述符句柄,该句柄表示要清除的呈现器目标的堆的开头。
b、ColorRGBA:一个由 4 分量构成的数组,表示要填充呈现器目标时使用的颜色。
c、NumRects:UINT类型,参数指定的数组中的矩形数。
d、pRects:D3D12_RECT类型,要清除的资源视图中矩形的 D3D12_RECT 结构的数组。 如果 为NULL,ClearRenderTargetView 将清除整个资源视图。

void ClearDepthStencilView(
  [in] ID3D11DepthStencilView *pDepthStencilView,
  [in] UINT                   ClearFlags,
  [in] FLOAT                  Depth,
  [in] UINT8                  Stencil
);

a、pDepthStencilView:ID3D11DepthStencilView类型,指向要清除的深度模具的指针。
b、ClearFlags:const D3D10_RECT
类型,确定要清除的数据类型 。
c、Depth:FLOAT类型,使用此值清除深度缓冲区。 此值将固定在 0 和 1 之间。
d、Stencil:UINT8类型,使用此值清除模具缓冲区。

5、输出的合并阶段

在此步骤,应该是为GPU处理的后续,在开始提过,GPU处理是没有做的,将在后续文章中补充,但假定GPU处理已经设定好了,这些改好的数据就需要合并到对应结果中,方便后续CPU拿走结果送给显示器进行显示。

D3D12_CPU_DESCRIPTOR_HANDLE SwapBufferView = CD3DX12_CPU_DESCRIPTOR_HANDLE(
		m_spRTVHeap->GetCPUDescriptorHandleForHeapStart(),
		m_iCurrentSwapBuffIndex,
		m_iRTVDescriptorSize);

D3D12_CPU_DESCRIPTOR_HANDLE DepthStencilView = m_spDSVHeap->GetCPUDescriptorHandleForHeapStart();

m_spGraphicsCommandList->OMSetRenderTargets(
		1,
		&SwapBufferView,
		true,
		&DepthStencilView);

主要用到的 API 为 OMSetRenderTargets:

void OMSetRenderTargets(
  [in]           UINT                              NumRenderTargetDescriptors,
  [in, optional] const D3D12_CPU_DESCRIPTOR_HANDLE *pRenderTargetDescriptors,
  [in]           BOOL                              RTsSingleHandleToDescriptorRange,
  [in, optional] const D3D12_CPU_DESCRIPTOR_HANDLE *pDepthStencilDescriptor
);

a、NumRenderTargetDescriptors:UINT类型,pRenderTargetDescriptors 阵列中的项目数, (介于 0 到D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT) 之间。 如果此参数为非零,pRenderTargetDescriptors 指向的数字数组中的项目数必须等于此参数中的数字。

b、pRenderTargetDescriptors:D3D12_CPU_DESCRIPTOR_HANDLE*类型,指定描述 CPU 描述项句柄 的D3D12_CPU_DESCRIPTOR_HANDLE 结构阵列,此句柄代表转译目标描述项堆积的开头。 如果此参数为 NULL 且 NumRenderTargetDescriptors 为 0,则不会绑定转译目标。

c、RTsSingleHandleToDescriptorRange:BOOL类型,True 表示传入的句柄是 连续范围 NumRenderTargetDescriptors 描述元的指标,False 表示句柄是 NumRenderTargetDescriptors 句柄的第一个阵列。

d、pDepthStencilDescriptor:D3D12_CPU_DESCRIPTOR_HANDLE*类型,D3D12_CPU_DESCRIPTOR_HANDLE结构的指标,描述CPU描述项句柄,代表保存深度样板描述项之堆积的开头。 如果此参数为NULL,则不会系结深度样板描述项。。

6、把后缓冲区切换成PRESENT状态

此步骤与第一个阶段基本相同,只是需要把 RENDER_TARGET状态 转换(Transition)为 PRESENT状态。

CD3DX12_RESOURCE_BARRIER ResourceBarrierPresentRenderTarget = CD3DX12_RESOURCE_BARRIER::Transition(
		vecRTVBuffer[m_iCurrentSwapBuffIndex].Get(),
		D3D12_RESOURCE_STATE_RENDER_TARGET,
		D3D12_RESOURCE_STATE_PRESENT);

m_spGraphicsCommandList->ResourceBarrier(1, &ResourceBarrierPresentRenderTarget);

7、录入完成,提交命令

最后一个步骤,当然就是炮弹入膛后关闭膛们和点火了,也就是关闭提交列表,把让队列执行任务列表。

m_spGraphicsCommandList->Close();
ID3D12CommandList* pCommandList[] = { m_spGraphicsCommandList.Get() };
m_spCommandQueue->ExecuteCommandLists(_countof(pCommandList), pCommandList);
void ExecuteCommandLists(
  [in] UINT              NumCommandLists,
  [in] ID3D12CommandList * const *ppCommandLists
);

a、NumCommandLists:UINT类型,要执行的命令清单数目。

b、ppCommandLists:ID3D12CommandList* 类型,指定要执行的 ID3D12CommandList 命令列表阵列。

三、页面翻转

上次提到的交换链,我们是写入后台,显示前台的,因此在写入后台后,不要忘了告诉GPU,后台转前台。

//交换两个buff缓冲区
void cDx12Rendering::PageTurn()
{
	//交换两个buff缓冲区
	m_spSwapChain->Present(0, 0);
	m_iCurrentSwapBuffIndex = !(bool)m_iCurrentSwapBuffIndex;
}

四、执行同步

任务提交了,也通知GPU页面反转了,最后就是等待处理结果,这里就是调用初始化的时候准备的同步函数WaitGPUComplete(),因在初始化已经解释过了,这里就不多说了。

void cDx12Rendering::WaitGPUComplete()
{
	m_iCurFenceIndex++;

	//向GUP设置新的隔离点 等待GPU处理玩信号
	m_spCommandQueue->Signal(m_spFence.Get(), m_iCurFenceIndex);

	if (m_spFence->GetCompletedValue() < m_iCurFenceIndex)
	{
		HANDLE hEventEX = CreateEventEx(NULL, NULL, 0, EVENT_ALL_ACCESS);

		//GPU完成后会通知我们的Handle
		m_spFence->SetEventOnCompletion(m_iCurFenceIndex, hEventEX);

		//等待GPU,阻塞主线程
		WaitForSingleObject(hEventEX, INFINITE);
		CloseHandle(hEventEX);
	}
}

五、全流程代码

在Dx12TestMian.cpp的主函数中添加:

while (true)
{
	cDx12Render.Draw(0.03);
}

在Dx12Render.h中添加如下成员函数:

public:
	void Draw(float DeltaTime);

private:
	void ResetCMDListAlloctor();
	void SubmitDrawingTask();
	void PageTurn();
	void WaitGPUComplete();		//初始化阶段已经做好了,这里只是搬到这里,方便理解

在Dx12Render.cpp中添加如下代码:

void cDx12Rendering::Draw(float DeltaTime)
{
	ResetCMDListAlloctor();

	//核心点
	SubmitDrawingTask();

	PageTurn();

	//CPU等GPU
	WaitGPUComplete();
}
void cDx12Rendering::ResetCMDListAlloctor()
{
	//重置录制相关的内存,为下一帧做准备
	m_spCommandAllocator->Reset();

	m_spGraphicsCommandList->Reset(m_spCommandAllocator.Get(), NULL);
}

void cDx12Rendering::SubmitDrawingTask()
{
	//把后缓冲区的资源状态切换成Render Target
	CD3DX12_RESOURCE_BARRIER ResourceBarrierPresent = CD3DX12_RESOURCE_BARRIER::Transition(
		vecRTVBuffer[m_iCurrentSwapBuffIndex].Get(),
		D3D12_RESOURCE_STATE_PRESENT, 
		D3D12_RESOURCE_STATE_RENDER_TARGET);

	m_spGraphicsCommandList->ResourceBarrier(1, &ResourceBarrierPresent);

	//设置视口和裁剪区域
	m_spGraphicsCommandList->RSSetViewports(1, &m_dvViewprotInfo);
	m_spGraphicsCommandList->RSSetScissorRects(1, &m_drViewprotRect);

	//清空后缓存和深度缓存
	m_spGraphicsCommandList->ClearRenderTargetView(
		CD3DX12_CPU_DESCRIPTOR_HANDLE(
			m_spRTVHeap->GetCPUDescriptorHandleForHeapStart(),
			m_iCurrentSwapBuffIndex,
			m_iRTVDescriptorSize),
		DirectX::Colors::Blue,
		0, nullptr);

	m_spGraphicsCommandList->ClearDepthStencilView(
		m_spDSVHeap->GetCPUDescriptorHandleForHeapStart(),
		D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL,
		1.f, 0, 0, NULL);

	//输出的合并阶段
	D3D12_CPU_DESCRIPTOR_HANDLE SwapBufferView = CD3DX12_CPU_DESCRIPTOR_HANDLE(
		m_spRTVHeap->GetCPUDescriptorHandleForHeapStart(),
		m_iCurrentSwapBuffIndex,
		m_iRTVDescriptorSize);

	D3D12_CPU_DESCRIPTOR_HANDLE DepthStencilView = m_spDSVHeap->GetCPUDescriptorHandleForHeapStart();

	m_spGraphicsCommandList->OMSetRenderTargets(
		1,
		&SwapBufferView,
		true,
		&DepthStencilView);

	//把后缓冲区切换成PRESENT状态
	CD3DX12_RESOURCE_BARRIER ResourceBarrierPresentRenderTarget = CD3DX12_RESOURCE_BARRIER::Transition(
		vecRTVBuffer[m_iCurrentSwapBuffIndex].Get(),
		D3D12_RESOURCE_STATE_RENDER_TARGET,
		D3D12_RESOURCE_STATE_PRESENT);

	m_spGraphicsCommandList->ResourceBarrier(1, &ResourceBarrierPresentRenderTarget);

	//录入完成,提交命令
	m_spGraphicsCommandList->Close();
	ID3D12CommandList* pCommandList[] = { m_spGraphicsCommandList.Get() };
	m_spCommandQueue->ExecuteCommandLists(_countof(pCommandList), pCommandList);
}

void cDx12Rendering::PageTurn()
{
	//交换两个buff缓冲区
	m_spSwapChain->Present(0, 0);
	m_iCurrentSwapBuffIndex = !(bool)m_iCurrentSwapBuffIndex;
}

void cDx12Rendering::WaitGPUComplete()
{
	m_iCurFenceIndex++;

	//向GUP设置新的隔离点 等待GPU处理玩信号
	m_spCommandQueue->Signal(m_spFence.Get(), m_iCurFenceIndex);

	if (m_spFence->GetCompletedValue() < m_iCurFenceIndex)
	{
		HANDLE hEventEX = CreateEventEx(NULL, NULL, 0, EVENT_ALL_ACCESS);

		//GPU完成后会通知我们的Handle
		m_spFence->SetEventOnCompletion(m_iCurFenceIndex, hEventEX);

		//等待GPU,阻塞主线程
		WaitForSingleObject(hEventEX, INFINITE);
		CloseHandle(hEventEX);
	}
}

六、效果展示

在这里插入图片描述

  • 33
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值