GPU有一个命令队列,CPU通过Direct3D API将命令提交到队列里来使用命令列表(command lists),如下图。当一套命令(a set of commands)已经被提交到命令队列,他们不会被GPU立刻执行,理解这一点非常重要。由于GPU很可能忙着处理之前插入的命令,所以它们会待在队列里直到GPU准备好处理它们。

如果命令队列空了,没有任何工作可做,GPU就会处于空闲状态;另一方面,如果命令队列太满,CPU在某个时刻必须停下来等着GPU追上来。这两种情况都不是我们希望看到的;对于高性能要求的应用,比如游戏,目标是同时保持CPU和GPU的处于繁忙状态以使得能够充分利用硬件资源的优势。

在Direct3D12中,命令队列由接口ID3D12CommandQueue来表示。它是通过填充D3D12_COMMAND_QUEUE_DESC结构来描述队列,然后调用ID3D12Device::CreateCommandQueue来创建的。在本书中,我们通过以下方式来创建我们的命令队列:

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)));

其中IID_PPV_ARGS这个帮助宏(helper macro)的定义如下

#define IID_PPV_ARGS(ppType) __uuidof(**(ppType)), IID_PPV_ARGS_Helper(ppType)

其中__uuidof(**(ppType))求值为(**(ppType))的COM接口ID,在上面的代码中是ID3D12CommandQueue。IID_PPV_ARGS_Helper函数实质上将ppType强制转换为void **。 我们在本书中使用了这个宏,这是因为许多Direct3D 12 API调用都要求有一个参数,即我们正在创建的接口的COM ID,并使用void **类型。

这个接口中的一个主要函数是ExecuteCommandLists方法,它将命令列表(command lists)中的命令(commands)添加到到命令队列(command queue):

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);

命令列表(command lists)将会从ppCommandLists的第一个数组元素开始顺序执行

正如上面的方法声明所暗示的,图形的命令列表(a command list for graphics)由ID3D12GraphicsCommandList接口表示,该接口继承自ID3D12CommandList接口。 ID3D12GraphicsCommandList接口有许多方法可以将命令添加到命令列表中。 例如,以下代码添加了设置视口(set the viewport),清除渲染目标视图(clear the render target view)和发出绘制调用(issue a draw call)的命令:

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

这些方法的名称暗示命令是立即执行的,但实际并不是。上面的代码只是将命令添加到命令列表中。 ExecuteCommandLists方法将命令添加到命令队列,GPU处理来自队列的命令。 在我们阅读本书的过程中,我们将了解ID3D12GraphicsCommandList支持的各种命令。 当我们完成向命令列表添加命令时,我们必须通过调用ID3D12GraphicsCommandList :: Close方法来表明我们已完成命令录制(finished recording commands)。

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

命令列表在被传递给ID3D12CommandQueue :: ExecuteCommandLists之前,必须先被关闭。

与命令列表相关联的是一个名为ID3D12CommandAllocator的内存支持类。 当命令被记录到命令列表中时,它们实际上将存储在相关的命令分配器(command allocator)中。 当通过ID3D12CommandQueue :: ExecuteCommandLists执行命令列表时,命令队列将引用分配器中的命令(commands)。 从ID3D12Device创建命令分配器的代码如下:

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

参数解释如下

1.type:可与此分配器关联的命令列表类型。我们在本书中使用的两种常见类型是:
  第一种:D3D12_COMMAND_LIST_TYPE_DIRECT:存储会被GPU直接执行的命令列表(到目前为止我们已经描述的命令列表的类型)。
  第二种:D3D12_COMMAND_LIST_TYPE_BUNDLE:指定命令列表的捆绑包。构建(building)命令列表时会产生一些CPU开销,因此Direct3D 12提供了一种优化,允许我们将一系列命令记录到所谓的bundle中。记录捆绑后,驱动程序将预处理命令以优化其在渲染过程中的执行。因此,应在初始化时记录捆绑。如果分析显示构建特定命令列表需要花费大量时间,则应将bundle的使用视为必要的优化。 Direct3D 12绘图API已经非常高效,因此您不需要经常使用捆绑包,只有在您可以通过它们取得立竿见影的性能时才应该使用它们;也就是说,默认情况下不要使用它们。我们在本书中不使用bundle;有关更多详细信息,请参阅DirectX 12文档。

2.riid:我们要创建的ID3D12CommandAllocator接口的COM ID。

3.ppCommandAllocator:输出的指向被创建的命令分配器的指针。

命令列表也是用ID3D12Device中的方法来创建的

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

参数解释如下

1.nodeMask:单GPU系统设置为0。 否则,nodeMask 标识与该命令列表相关联的物理GPU。 在本书中,我们假设单GPU系统。

2.命令列表的类型:_COMMAND_LIST_TYPE_DIRECT或D3D12_COMMAND_LIST_TYPE_BUNDLE。

3.pCommandAllocator:与创建的命令列表关联的分配器。 命令分配器类型必须与命令列表类型匹配。

4.pInitialState:指定命令列表的初始管道状态(pipeline state)。 对于bundle而言,这可以为null,并且在特殊情况下,执行命令列表以进行初始化并且不包含任何绘制命令。 我们将在第6章讨论ID3D12PipelineState。

5.riid:我们想要创建的ID3D12CommandList接口的COM ID。

6.ppCommandList:输出指向被创建的命令列表的指针。

你可以创建多个命令列表,并关联到同一个分配器上,但不能同时为这些命令列表录制命令。 也就是说,除了我们将要录制命令的列表之外,其他命令列表必须被关闭。 因此,来自给定命令列表的所有命令将连续地添加到分配器。 请注意,创建或重置命令列表时,它处于“打开”(open)状态。 因此,如果我们尝试使用相同的分配器在一行(a row)中创建两个命令列表,我们将收到错误:

D3D12 ERROR: ID3D12CommandList::{Create,Reset}CommandList: The command allocator is currently in-use by another command list.

在我们调用了ID3D12CommandQueue :: ExecuteCommandList(C)之后,通过调用ID3D12CommandList :: Reset方法重用C的内部存储器来记录一组新命令是安全的。 此方法的参数与ID3D12Device :: CreateCommandList中的相应参数相同:

HRESULT ID3D12CommandList::Reset(
ID3D12CommandAllocator *pAllocator,
ID3D12PipelineState *pInitialState);

此方法将命令列表设置为和刚刚创建时相同的状态,但允许我们重用内部内存并避免取消分配旧命令列表并分配新命令列表。 请注意,重置命令列表不会影响命令队列中的命令,因为关联的命令分配器仍然具有命令队列引用的内存中的命令。

在我们将完整帧的渲染命令提交给GPU之后,我们希望重用命令分配器中的内存来录制下一帧的渲染命令。 ID3D12CommandAllocator :: Reset方法可用于此目的:

HRESULT ID3D12CommandAllocator::Reset(void);

这个想法类似于调用std :: vector :: clear,它将向量的大小调整为零,但保持当前容量相同。 但是,因为命令队列可能在分配器中引用数据,所以在我们确定GPU已完成执行分配器中的所有命令之前,不得重置命令分配器。 如何执行此操作将在下一节中介绍。

【D3D12学习手记】The Command Queue and Command Lists的更多相关文章

  1. 【D3D12学习手记】CPU/GPU Synchronization

    由于有两个并行运行的处理器(CPU和GPU),会出现许多同步问题.假设我们有一些资源R存储了我们希望绘制的某些几何体的位置. 此外,假设CPU更新R的数据以存储位置p1,然后将引用R的绘图命令C添加到 ...

  2. 【D3D12学习手记】4.3.8 Create the Depth/Stencil Buffer and View

    我们现在需要创建深度/模板缓冲区. 如§4.1.5所述,深度缓冲区只是一个2D纹理,用于存储最近的可见对象的深度信息(如果使用模板(stencil),则也会存储模板信息). 纹理是一种GPU资源,因此 ...

  3. 【D3D12学习手记】4.1.6 Resources and Descriptors

    在渲染过程中,GPU将写资源(resources)(例如,后缓冲区,深度/模板缓冲区),读资源(例如,描述表面外观的纹理,存储场景中几何体3D位置的缓冲区).在我们发出绘图命令之前,我们需要将资源绑定 ...

  4. 【D3D12学习手记】The Swap Chain and Page Flipping

    为了避免动画中的闪烁,最好将整个动画帧绘制到称为后台缓冲区的屏幕外纹理(off-screen texture)中.一旦整个场景被绘制到给定动画帧的后缓冲区,它就作为一个完整的帧呈现给屏幕;以这种方式, ...

  5. Linux.NET学习手记(7)

    前一篇中,我们简单的讲述了下如何在Linux.NET中部署第一个ASP.NET MVC 5.0的程序.而目前微软已经提出OWIN并致力于发展VNext,接下来系列中,我们将会向OWIN方向转战. 早在 ...

  6. Linux.NET学习手记(8)

    上一回合中,我们讲解了Linux.NET面对OWIN需要做出的准备,以及介绍了如何将两个支持OWIN协议的框架:SignalR以及NancyFX以OwinHost的方式部署到Linux.NET当中.这 ...

  7. 关于《Linux.NET学习手记(8)》的补充说明

    早前的一两天<Linux.NET学习手记(8)>发布了,这一篇主要是讲述OWIN框架与OwinHost之间如何根据OWIN协议进行通信构成一套完整的系统.文中我们还直接学习如何直接操作OW ...

  8. EF框架学习手记

    转载: [ASP.NET MVC]: - EF框架学习手记 1.EF(Entity Framework)实体框架EF是ADO.NET中的一组支持开发面向数据的软件应用程序的技术,是微软的一个ORM框架 ...

  9. ExtJS MVC 学习手记3

    在演示应用中,我们已经创建好了viewport,并为之添加了一个菜单树.但也仅仅是这样,点击树或应用的其他地方获得不到任何响应.这个演示应用还是一个死的应用. 接下来,我们让这个应用活起来. 首先,给 ...

随机推荐

  1. IntelliJ IDEA 如何设置代码提示和代码模板

    在编写java代码时如何设置不分大小写提示和设置快捷输出模板代码 首先设置不分大小写,settings-Editor-General-CodeCompletion 将红框的Match case取消打勾 ...

  2. 陌上花开 HYSBZ - 3262 (CDQ分治)

    陌上花开 HYSBZ - 3262 有n朵花,每朵花有三个属性:花形(s).颜色(c).气味(m),用三个整数表示. 现在要对每朵花评级,一朵花的级别是它拥有的美丽能超过的花的数量. 定义一朵花A比另 ...

  3. Linux之apt-get软件管理

    apt-get 用Linux apt-get命令的第一步就是引入必需的软件库,Debian的软件库也就是所有Debian软件包的集合,它们存在互联网上的一些公共站点上.把它们的地址加入,apt-get ...

  4. CSS基础学习-2.CSS选择器(上)

    元素选择符 关系选择符 属性选择符 伪类选择符 伪对象选择符 一.元素选择符 1.通配符:*{ } 2.类选择符:.类名称{ } 3.id选择符::#id名称{ } 4.类型选择符(标签选择符):标签 ...

  5. Nginx location模块整理

    location模块 Nginx location location 指令的作用是根据用户请求的URI来执行不同的应用,URI就是根据用户请求到的网址URL进行匹配,匹配成功了进行相关的操作. loc ...

  6. Struct2的简单的CRUD配置和使用

    1. 首先是Struct2使用的jar包,可以在官网下载https://struts.apache.org/   ,其中包只要下面这些就够用了. 或者点击下面链接下载 链接:https://pan.b ...

  7. 01- ES6、jquery源码、node、webpack

    1.课程介绍 小马哥blog:https://www.cnblogs.com/majj/ 前端学习路径:https://www.processon.com/view/link/5d3a5947e4b0 ...

  8. 配置并访问NFS共享

    NFS服务器 192.168.2.5 NFS客户机 192.168.2.100 软件包nfs-utils用来提供NFS共享服务及相关工具,而软件包rpcbind用来提供RPC协议的支持 服务器 修改/ ...

  9. 初识 ZeroMQ

    由于网上和官方的ZeroMQ主要是讲解和说明大都是基于C.PHP.Java偏偏.Net的很少,可能你看完80多页的官方文档仍被C代码搞的晕晕乎乎的,我这里就将资料收集整理成几篇博文同时用c#重新实现D ...

  10. MessagePack Java 0.6.X 多种类型变量的序列化和反序列化(serialization/deserialization)

    类 Packer/Unpacker 允许序列化和反序列化多种类型的变量,如后续程序所示.这个类启用序列化和反序列化多种类型的变量和序列化主要类型变量以及包装类,String 对象,byte[] 对象, ...