本文主要是介绍图形API学习工程(12):讨论当前工程里同步CPU与GPU的方式,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
工程GIT地址:https://gitee.com/yaksue/yaksue-graphics
简单讨论CPU和GPU间的交互
《DX12龙书》在【4.2 CPU与GPU间的交互】章节中讨论了这个问题,简单来说:
为了最佳性能,CPU和GPU这两种处理器应该尽量同时工作,少“同步”。因为“同步”意味着一种处理器以空闲状态等待另一种处理器,即它破坏了“并行”。
但有时,又不得不进行二者的同步,见《DX12龙书》的【4.2.2 CPU与GPU间的同步】所讨论的问题。
当前工程,在D3D12和Vulkan的版本上有同步CPU与GPU的操作。
(D3D12代码参考DX12官方范例的【D3D12HelloWindow范例】,
Vulkan代码参考Rendering and presentation - Vulkan Tutorial)
我想就当前工程的同步方式进行讨论。但值得注意的是:当前的同步方式是最简单的,但并不是最佳的,详见最后一部分的讨论。
D3D12的方式
目前工程里D3D12方面是使用围栏实现同步。
接口介绍
所用的函数除了D3D12本身定义的之外,还有Windows定义的函数。
CreateEvent函数 (synchapi.h)
CreateEvent 将创建一个event对象,并返回这个event对象的Handle。
WaitForSingleObject函数 (synchapi.h)
调用WaitForSingleObject将使程序等待一个event对象直到它进入一个特定信号的状态(或者直到超过指定时间)。
DWORD WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds
);
参数中hHandle
就是event对象的Handle。dwMilliseconds
是超时的时间,如果设为INFINITE
即表示一直等待。
ID3D12Fence接口 (d3d12.h)
ID3D12Fence代表一个围栏对象,用来同步CPU和GPU。
它的三个成员函数:
ID3D12Fence::GetCompletedValue 可以获取当前围栏对象的值。
ID3D12Fence::SetEventOnCompletion 可以设定当这个围栏对象到达某个值将会触发一个event对象。
HRESULT SetEventOnCompletion(UINT64 Value,HANDLE hEvent
);
参数中Value
是围栏对象要到达的值,而hEvent
是要触发的event对象的Handle。
ID3D12Fence::Signal 将设定围栏对象为指定的值。(CPU端)
ID3D12CommandQueue::Signal方法
ID3D12CommandQueue的Signal方法将在队列执行完成后,在GPU端将围栏对象设定为指定的值。
HRESULT Signal(ID3D12Fence *pFence,UINT64 Value
);
参数中pFence
是围栏对象,而Value
则是会被设定的值。
使用
首先,在初始化阶段,创建围栏对象和event对象:
//创建Fence
Device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_fence));//创建一个event对象
m_fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
WaitForPreviousFrame()
表示等待这一帧GPU执行,其中内容如下:
首先,NewFenceValue
表示应该设置的新的围栏值,它应该和从GetCompletedValue()
获得的旧的围栏值不一样,比如+1。
const UINT64 NewFenceValue = m_fence->GetCompletedValue() + 1;
然后,CommandQueue
设定:当它完成队列后,围栏将会在GPU端被设定为新的值。
CommandQueue->Signal(m_fence.Get(), NewFenceValue);
而围栏自己也设定:当它到达新的值时,将会触发m_fenceEvent
这个事件。
m_fence->SetEventOnCompletion(NewFenceValue, m_fenceEvent);
最后,让程序无限地等待m_fenceEvent
这个事件。
WaitForSingleObject(m_fenceEvent, INFINITE);
这样,程序便会一直等待直到CommandQueue
完成队列。
Vulkan的方式
目前工程里Vulkan方面相对于简单,是在VulkanInterface::Present()
中使用了一些等待 设备/队列 进入闲置状态的接口。
接口介绍
vkDeviceWaitIdle:Wait for a device to become idle
vkQueueWaitIdle:Wait for a queue to become idle
使用
vkDeviceWaitIdle(Device);
或者
vkQueueWaitIdle(PresentQueue);
当前的同步方式并不是最佳
在学习过程中所参考的资料,无一不指出:当前的同步方式不是最佳的,而更佳的方式,会在后续学习。
例如:
《DX12龙书》:
这种解决方案并不完美,因为这意味着在等待GPU处理命令的时候,CPU会处于空闲状态,但在第7章以前也只能暂时使用这个简单的方法了。
DX12官方范例的【D3D12HelloWindow范例】:
// WAITING FOR THE FRAME TO COMPLETE BEFORE CONTINUING IS NOT BEST PRACTICE.
// This is code implemented as such for simplicity. The D3D12HelloFrameBuffering
// sample illustrates how to use fences for efficient resource usage and to
// maximize GPU utilization.
Rendering and presentation - Vulkan Tutorial:
The easy way to solve this is to wait for work to finish right after submitting it, for example by using
vkQueueWaitIdle
However, we are likely not optimally using the GPU in this way, because the whole graphics pipeline is only used for one frame at a time right now. The stages that the current frame has already progressed through are idle and could already be used for a next frame. We will now extend our application to allow for multiple frames to be in-flight while still bounding the amount of work that piles up.
这篇关于图形API学习工程(12):讨论当前工程里同步CPU与GPU的方式的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!