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. Python——列表赋值的若干用例

    原创声明:本文系博主原创文章,转载或引用请注明出处. 1. 直接赋值 >>> a = [1,2,3,4,5] >>> b = a >>> id(a ...

  2. sql 184. 部门工资最高的员工

    Employee 表包含所有员工信息,每个员工有其对应的 Id, salary 和 department Id. +----+-------+--------+--------------+| Id ...

  3. MyBatis 分页插件PageHelper 后台报错

    今天遇到一个问题,使用MyBatis 分页插件PageHelper 进行排序分页后,能正常返回正确的结果,但后台却一直在报错 net.sf.jsqlparser.parser.ParseExcepti ...

  4. [uboot] (番外篇)uboot串口&console&stdio设备工作流程 (转)

    [uboot] uboot流程系列:[project X] tiny210(s5pv210)上电启动流程(BL0-BL2)[project X] tiny210(s5pv210)从存储设备加载代码到D ...

  5. React组件:Dragact 0.1.4发布

    Dragact 是一款React组件,他能够使你简单.快速的构建出一款强大的 拖拽式网格(grid)布局. 仓库地址:Dragact 经过几天的迭代时间Dragact已经能够支持自由缩放功能了(res ...

  6. BZOJ 2219 数论之神 (CRT推论+BSGS+原根指标)

    看了Po神的题解一下子就懂了A了! 不过Po神的代码出锅了-solve中"d-temp"并没有什么用QwQQwQQwQ-应该把模数除以p^temp次方才行. 来自BZOJ讨论板的h ...

  7. 慎用javascript自动类型转换

    1.如果把非空对象用在逻辑运算环境中,则对象被转换为true.此时的对象包括所有类型的对象,即使是值为false的包装对象也被转换为true. 2.如果把对象用在数值运算环境中,则对象会被自动转换为数 ...

  8. App自动化测试介绍

  9. JQuery调用绑定click事件的3种写法

    第一种方式: $(document).ready(function(){ $("#clickme").click(function(){ alert("Hello Wor ...

  10. 使用Python画一朵玫瑰花

    # -*- coding: utf-8 -*- # @Time : 18-9-14 下午12:47 # @Author : Felix Wang from turtle import * import ...