【D3D12学习手记】The Command Queue and Command Lists
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的更多相关文章
- 【D3D12学习手记】CPU/GPU Synchronization
		由于有两个并行运行的处理器(CPU和GPU),会出现许多同步问题.假设我们有一些资源R存储了我们希望绘制的某些几何体的位置. 此外,假设CPU更新R的数据以存储位置p1,然后将引用R的绘图命令C添加到 ... 
- 【D3D12学习手记】4.3.8 Create the Depth/Stencil Buffer and View
		我们现在需要创建深度/模板缓冲区. 如§4.1.5所述,深度缓冲区只是一个2D纹理,用于存储最近的可见对象的深度信息(如果使用模板(stencil),则也会存储模板信息). 纹理是一种GPU资源,因此 ... 
- 【D3D12学习手记】4.1.6 Resources and Descriptors
		在渲染过程中,GPU将写资源(resources)(例如,后缓冲区,深度/模板缓冲区),读资源(例如,描述表面外观的纹理,存储场景中几何体3D位置的缓冲区).在我们发出绘图命令之前,我们需要将资源绑定 ... 
- 【D3D12学习手记】The Swap Chain and Page Flipping
		为了避免动画中的闪烁,最好将整个动画帧绘制到称为后台缓冲区的屏幕外纹理(off-screen texture)中.一旦整个场景被绘制到给定动画帧的后缓冲区,它就作为一个完整的帧呈现给屏幕;以这种方式, ... 
- Linux.NET学习手记(7)
		前一篇中,我们简单的讲述了下如何在Linux.NET中部署第一个ASP.NET MVC 5.0的程序.而目前微软已经提出OWIN并致力于发展VNext,接下来系列中,我们将会向OWIN方向转战. 早在 ... 
- Linux.NET学习手记(8)
		上一回合中,我们讲解了Linux.NET面对OWIN需要做出的准备,以及介绍了如何将两个支持OWIN协议的框架:SignalR以及NancyFX以OwinHost的方式部署到Linux.NET当中.这 ... 
- 关于《Linux.NET学习手记(8)》的补充说明
		早前的一两天<Linux.NET学习手记(8)>发布了,这一篇主要是讲述OWIN框架与OwinHost之间如何根据OWIN协议进行通信构成一套完整的系统.文中我们还直接学习如何直接操作OW ... 
- EF框架学习手记
		转载: [ASP.NET MVC]: - EF框架学习手记 1.EF(Entity Framework)实体框架EF是ADO.NET中的一组支持开发面向数据的软件应用程序的技术,是微软的一个ORM框架 ... 
- ExtJS MVC 学习手记3
		在演示应用中,我们已经创建好了viewport,并为之添加了一个菜单树.但也仅仅是这样,点击树或应用的其他地方获得不到任何响应.这个演示应用还是一个死的应用. 接下来,我们让这个应用活起来. 首先,给 ... 
随机推荐
- Spring——概念
			一.简介 Spring是一个开源的框架,Spring为简化企业级应用开发而生,使用Spring可以使简单的JavaBean实现以前只有EJB才能实现的功能.Spring是一个IOC和AOP容器框架. ... 
- swoole_process模拟耗时操作
			一例串行阻塞操作 <?php $start = time(); $tasklists = [ '/root/文档/longtale1.txt', '/root/文档/longtale2.txt' ... 
- HDU 6055 - Regular polygon  |  2017 Multi-University Training Contest 2
			/* HDU 6055 - Regular polygon [ 分析,枚举 ] 题意: 给出 x,y 都在 [-100, +100] 范围内的 N 个整点,问组成的正多边形的数目是多少 N <= ... 
- JavaScript相关知识点
			㈠JavaScript编写位置 ⑴可以将js代码编写到外部js文件中,然后通过script标签引入 写到外部文件中可以在不同的页面中同时使用,也可以利用到浏览器的缓存机制 推荐使用的方式 ... 
- Vue 工程化最佳实践
			目录结构 总览 api 目录用于存放 api 请求,文件名与模型名称基本一致,文件名使用小驼峰,方法名称与后端 restful 控制器一致. enums 目录存放 常量,与后端的常量目录对应 ... 
- java+大文件上传解决方案
			众所皆知,web上传大文件,一直是一个痛.上传文件大小限制,页面响应时间超时.这些都是web开发所必须直面的. 本文给出的解决方案是:前端实现数据流分片长传,后面接收完毕后合并文件的思路. 实现文件夹 ... 
- 2019icpc南京网络赛 A 主席树
			题意 给一个\(n\times n\)的螺旋矩阵,给出其中的\(m\)个点的值分别为各个点上数字的数位之和,给出\(q\)个询问,每次询问从\((x1,y1)\)到\((x2,y2)\)的子矩阵的和. ... 
- 存储映射--mmap
			存储映射 使一个磁盘文件与存储空间中的一个缓冲区相映射. 当从缓冲区中取数据,就相当于读文件中的相应字节. 将数据存入缓冲区,则相应的字节就自动写入文件. 使用这种方法,首先应通知内核,将一个指定文件 ... 
- 「HNOI2014」世界树
			题目链接 问题分析 首先观察数据范围可以知道要用虚树.但是要考虑怎么维护原树的距离信息. 如果只有两个关键点,我们可以很方便地找到中点将整棵树划分为两部分.而如果有多个关键点,看起来有效的方法就是多源 ... 
- 【知识库】-数据库_MySQL之基本数据查询:子查询、分组查询、模糊查询
			简书作者:seay 文章出处: 关系数据库SQL之基本数据查询:子查询.分组查询.模糊查询 回顾:[知识库]-数据库_MySQL常用SQL语句语法大全示例 Learn [已经过测试校验] 一.简单查询 ... 
