DX12阅读笔记_第四章_2

4.2 GPU与CPU的交互
GPU与CPU并行工作,有时需要同步。为了获得最佳性能,我们的目标是尽可能地保持两者的忙碌,并尽量减少同步。
GPU有一个命令队列。CPU使用命令列表通过Direct3D API向队列提交命令。一旦一组命令被提交到命令队列,它们不会立即被GPU执行。它们一直在队列中,直到GPU准备好处理它们,因为GPU很可能忙于处理之前插入的命令。

创建命令队列的方法:

Microsoft::WRL::ComPtr<ID3D12CommandQueue> 	mCommandQueue;
//填写结构体 
D3D12_COMMAND_QUEUE_DESC queueDesc = {
    
    };
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; 
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; 
//创建队列
ThrowIfFailed(md3dDevice->CreateCommandQueue( 
&queueDesc, IID_PPV_ARGS(&mCommandQueue))); 

这个接口的主要方法之一是ExecuteCommandLists方法,它将命令列表中的命令添加到队列中:

void ID3D12CommandQueue::ExecuteCommandLists( 
	// Number of commands lists in the array 
	UINT Count, 
	// Pointer to the first element in an array of command lists 
	ID3D12CommandList *const *ppCommandLists); 

ID3D12GraphicsCommandList接口有许多将命令添加到命令列表的方法。如:

// mCommandList pointer to ID3D12CommandList 
mCommandList->RSSetViewports(1, &mScreenViewport); 
mCommandList->ClearRenderTargetView(mBackBufferView, 
Colors::LightSteelBlue, 0, nullptr); 
mCommandList->DrawIndexedInstanced(36, 1, 0, 0, 0); 

当我们完成向命令列表添加命令时,我们必须通过调ID3D12GraphicsCommandList::Close方法来表示我们完成了记录命令:

// Done recording commands. 
mCommandList->Close(); 

在传递给ID3D12CommandQueue::ExecuteCommandLists之前,必须关闭命令列表。
与命令列表相关联的是一个名为ID3D12CommandAllocator的内存支持类。当命令被记录到命令列表时,它们将实际存储在关联的命令分配中。当通过ID3D12CommandQueue::ExecuteCommandLists执行命令列表时,命令队列将引用分配器中的命令。创建分配器的命令如下:

HRESULT ID3D12Device::CreateCommandAllocator( 
	D3D12_COMMAND_LIST_TYPE type, 
	REFIID riid, 
	void **ppCommandAllocator); 

如果创建特定的指令列表需要耗费大量的时间时可以考虑使用Bundles。
命令列表也可以从ID3D12Device创建:

HRESULT ID3D12Device::CreateCommandList(UINT nodeMask, 
	D3D12_COMMAND_LIST_TYPE type, 
	ID3D12CommandAllocator *pCommandAllocator, 
	ID3D12PipelineState *pInitialState, 
	REFIID riid, 
	void **ppCommandList); 

4.2.2 GPU/CPU的同步
假设一种情况,CPU存储了需要绘制的几何体资源的位置p1然后提交了绘制命令之后又更新了这个资源的p1为p2,那么有可能产生GPU执行绘制命令之前CPU已经更新p1的位置,这样就和我们所需的不符合了。
解决这种情况的一个办法是迫使CPU等待,直到GPU完成对队列中所有命令的处理,直到指定的fence点。我们将此称为flushing the command queue。fence由ID3D12Fence接口表示,用于GPU和CPU的同步。fence对象可以用以下方法创建:

HRESULT ID3D12Device::CreateFence( 
	UINT64 InitialValue, 
	D3D12_FENCE_FLAGS Flags, 
	REFIID riid, 
	void **ppFence); 
// Example
ThrowIfFailed(md3dDevice->CreateFence( 
	0,
	D3D12_FENCE_FLAG_NONE, 
	IID_PPV_ARGS(&mFence))); 

以下时如何使用fence来flush命令队列。

UINT64 mCurrentFence = 0; 
void D3DApp::FlushCommandQueue() 
{
    
     
	// Advance the fence value to mark commands up to this fence point. 
	mCurrentFence++; 

	// Add an instruction to the command queue to set a new fence point. 
	// Because we are on the GPU timeline, the new fence point won’t be 
	// set until the GPU finishes processing all the commands prior to 
	// this Signal(). 
	ThrowIfFailed(mCommandQueue->Signal(mFence.Get(), mCurrentFence)); 

	// Wait until the GPU has completed commands up to this fence point. 
	if(mFence->GetCompletedValue() < mCurrentFence) 
	{
    
     
		HANDLE eventHandle = CreateEventEx(nullptr, false, 
		false, EVENT_ALL_ACCESS); 

		// Fire event when GPU hits current fence.
		ThrowIfFailed(mFence- >SetEventOnCompletion(mCurrentFence, eventHandle));
 
		// Wait until the GPU hits current fence event is fired. 
		WaitForSingleObject(eventHandle, INFINITE); 
		CloseHandle(eventHandle); 
	}
}

下面时图解:

在这里插入图片描述

图中,GPU已经处理了到xgpu的命令,CPU刚刚调用了ID3D12CommandQueue::Signal(fence, n+1)方法。这实际上是在队列末尾添加了一条指令,将fence值更改为n + 1。然而,mFence->GetCompletedValue()将继续返回n,直到GPU处理在信号(fence, n+1)指令之前添加到队列中的所有命令。

这个解决方案并不理想,因为它意味着CPU在等待GPU完成的时候是空闲的,但它提供了一个简单的解决方案,我们将使用到第7章。同时,我们可以刷新命令队列,以确保在重置命令分配器之前,所有的GPU命令都已执行,这样就解决了上一节的末尾的问题。

4.2.3 资源转换
为了解决GPU在还未结束写入资源就开始读取的问题,Direct3D将状态与资源关联起来。例如,如果我们写入资源,比如纹理,我们会设置纹理状态为渲染目标状态;当我们需要读取纹理时,我们会将其状态更改为着色器资源状态。通过通知Direct3D转换,GPU可以采取措施来避免危险,例如,在从资源读取之前等待所有的写操作完成。
通过在命令列表中设置resource barriers数组来指定资源转换。resource barriers由D3D12_RESOURCE_BARRIER_DESC结构表示。

4.24 多线程与指令
Direct3D 12是为高效多线程而设计的。命令列表设计是Direct3D利用多线程的一种方式。对于具有许多对象的大型场景,构建用于绘制整个场景的命令列表可能会占用CPU时间。所以可以并行地构建命令列表,比如可以产生四个线程,每个线程负责构建一个命令列表来绘制25%的场景对象。
以下是一些注意点:
1.命令列表不是自由线程;也就是说,多个线程可能不共享相同的命令列表并并发地调用其方法。因此,通常每个线程都会得到自己的命令列表。
2.命令分配器不是自由线程的;也就是说,多个线程可能不共享同一个命令分配器,并并发地调用它的方法。因此,通常每个线程都有自己的命令分配器。
3.命令队列是自由线程的,因此多个线程可以并发地访问命令队列并调用其方法。特别是,每个线程可以并发地将它们生成的命令列表提交给线程队列。
4.出于性能原因,应用程序必须在初始化时指定它们将同时记录的命令列表的最大数量。

总结:命令队列->命令列表->命令分配器->GPU执行(执行时必须关闭命令列表)->为了保持同步添加fence->资源转换(resource barriers指定资源状态)

本章GPU与CPU的交互说完下面开始初始化Direct3D部分

猜你喜欢

转载自blog.csdn.net/qq_35637551/article/details/108777220