Command Organization and Execution Model
在Metal 架构中,MTLDevice协议定义了代表单独GPU的接口。MTLDevice协议支持为了创建其他特定设备对象,如buffers 、textures,或者为了编码和排列(encoding and queueing)那些要被提交到GPU运行的渲染和计算命令,而去查询设备属性的接口.
一个command queue包含一队列的command buffers,而且还组织管理那些command buffers的执行顺序。
一个command buffer包含那些准备运行在特定设备上的压缩过的commands。
一个command encoder将那些渲染、计算、以及块传输命令追加到一个command buffer中,最终这些command buffers都会被提交到到设备上去以便执行。
MTLCommandQueue 协议定义了一个关于command queues的接口,主要支持创建command buffer对象的方法。MTLCommandBuffer协议定义了一个command buffers的接口而且提供创建command encoders、将command buffers 入队排序以便运行、检查状态、以及其他的一些操作等的方法。
MTLCommandBuffer协议支持下列命令编码类型,编码不同GPU 工作负荷格式到一个command buffer中去的接口
- MTLRenderCommandEncoder:编码为一个单独渲染过程的图形(3D)渲染命令
- MTLComputeCommandEncoder: 编码数据并行的计算工作负荷
- MTLBlitCommandEncoder: 编码在buffers 和textures之间的简单拷贝操作,以及如生成mipmap(纹理映射)这样的utility(多功能,实用)操作
在任何一个时间点,只有一个单独command encoder可以激活并将commands附加(追加)到一个command buffer.每个command encoder 必须在使用同一个command buffer的command encoder可以被创建之前结束(be ended)。
对于“每个command buffer只有一个激活的command encoder”原则,唯一的例外是MTLParalleRenderCommandEncoder协议,在Encoding a Single Rendering Pass Using Multiple Threads中有详细内容。
一旦所有的encoding 都完成了,你提交MTLCommandBuffer对象本身,这样会将command buffer 标注为(已准备好)可以被GPU操作.MTLCommandQueue协议控制在MTLCommandBuffer对象中已提交的commands何时被运行,与其他已经在该command queue中的MTLCommandBuffer对象相关。
下图展示了command queue,command buffer, 以及command encoder对象之间的关系。表的顶部每一列(buffer, texture,sampler,depath和stencil state,pipeline state)代表了特定command encoder的资源和状态
The Device Object Represents a GPU
一个MTLDevice 对象代表了一个可以执行commands 的GPU 。MTLDevice 协议有以下操作的方法,如创建新command queues、从内存中初始化buffers(allocate buffers from memory,分配空间)、创建textures、查询该设备的能力(capabilities)。为了获取首选系统设备,可以调用MTLCreateSystemDefaultDevice方法。
Transient(短暂的,临时的) and Non-transient Objects in Metal
Metal中的一些对象被设计为临时的和极轻体量的,与此同时,也有一些对象被设计为更占内存并且可以持续一段时间,甚至有可能充斥整个app生命周期。
Command buffer 和command encoder对象是临时的,而且为一次性使用。他们在创建和销毁过程中几乎不耗费什么资源,所以他们的创建方法也是返回的autoreleased objects.
下列的对象都是非临时的,需要在性能敏感的代码里复用这些对象,禁止重复创建
- Command queues
- Data buffers
- Textures
- Sampler states
- Libraries
- Compute states
- Render pipeline states
- Depth/stencil states
Command queue
一个command queue接受一列GPU将会执行的而且已经排好序的command buffers .所有被送到一个单独queue之中的command buffers默认会按照其入队的顺序来执行。一般情况下,command queues 是线程安全的,而且允许同时有多个激活的command buffer去执行被encode操作。
可以用一个MTLDevice对象调用newCommandQueue方法或者newCommandQueueWithMaxCommandBufferCount方法。一般来讲,希望command queues 可以长期存活的,所以不要反复的创建和销毁。
Command buffer
一个command buffer 一直存储那些已经编码过的commands直到该buffer 被提交给GPU运算。一个单独command buffer可以包含许多不同种类的encoded commands,这取决于用于创建它的encoder的数目和种类。在一个典型的app中,一个完整的渲染(rendering )帧会被编码进一个单独的command buffer中,即使渲染该帧包含了多个渲染过程、计算处理函数、或者blit操作。
Command buffers 是临时而且只能单次使用的对象,不支持复用。一旦一个command buffer已经被提交运行,唯一有效的操作是等待该command buffer被重新安排或者结束,通过同步请求(calls)或者在 Registering Handler Blocks for Command Buffer Execution中讨论的回调blocks,去检查该command buffer 运行的状态。
Command buffers也代表了可以被该app仅能独立跟踪的工作单元,而且它们定义了由Metal memory model 建立的一致性/相干性/内聚耦合(*注:该处的coherency应该是一致性)边界,详细内容可在Resource Objects: Buffers and Textures中查看。
Creating a command buffer
去创建一个MTLCommandBuffer对象,调用MTLCommandQueue重的commandBuffer方法。一个MTLCommandBuffer对象只能被提交到创建它的MTLCommandQueue中。
被commandBuffer方法创建的Command buffers持有将被运行的数据。对于某些情况(For certain scenarios),当你想在一个MTLCommandBuffer对象执行期间在其他地方持有该对象,你可以通过调用MTLCommandQueue的commandBufferWithUnretainedReferences方法另行创建一个command buffer来instead.
只在性能要求极其严格的apps中使用commandBufferWithUnretainedReferences方法,这样可以确保关键对象在app的其他地方一直保有引用,直到该command buffer操作完成。
另外,一个不再保有引用的对象可能会被提前释放,这时该command buffer运行的结果还没有被定义。
Executing Commands
MTLCommandBuffer协议利用下面的方法来建立在command queue中的command buffers 的执行顺序。一个command buffer 直到被提交才会执行。一旦提交,command buffers 会按照它们排序的顺序来执行。
- enqueue方法保留command buffer在command queue上的空间,但是不能提交该command buffer 执行。当该command buffer最终被提交时,它会在那些关联的command queue中提交enqueued command buffers之后执行。
- commit 方法使该command buffer 对象以最快的速度被执行,但是依然需要在同一command queue中较早排序的command buffers都被提交之后。如果该command buffer之前并未被排序(enqueued),commit命令会隐式的调用enqueue 。
想在多线程中使用enqueue,可以查看这些资料, Multiple Threads, Command Buffers, and Command Encoders.
Registering Handler Blocks for Command Buffer Execution
下面列出的 MTLCommandBuffer 方法监控命令的执行。已收进和完成的handlers被放在一个未定义的线程上按照顺序执行。被放进这些handlers的代码应该是能快速执行的,如果该handler需要耗费较大,可以单独将其安排到另一个线程推迟执行。
- addScheduledHandler: 方法注册了一个当该command buffer 为scheduled状态时执行的block。当满足系统中其他command buffer对象或其他API提交的工作之间的所有依赖关系时,认为该command buffer为已收进(可调度scheduled状态)。可以在一个command buffer上注册多个scheduled handlers
- waitUntilScheduled 方法同步等待并且当该command buffer处于scheduled状态而且所有根据addScheduledHandler: 方法注册的handlers都已完成后方才返回。
- addCompletedHandler: 和上面两个方法类似,该方法注册的block会在设备完成了该command buffer的执行后被立即调用。可以在一个command buffer上注册多个completed handlers
- waitUntilCompleted 方法在所有注册的complete handlers被执行完后同步的等待和返回
presentDrawable: 方法是一种特殊的completed handler,这个简便的方法可以在一个command buffer 处于scheduled 状态时展示一个可以展示的资源(a CAMetalDrawable object)的内容。相关详细内容可查看 Integration with Core Animation: CAMetalLayer.
Monitoring Command Buffer Execution Status
有status 和error两个属性,当命令执行成功时,error为nil.
Command Encoder
一个command encoder 对象是一个临时对象,可以用它来在一个单独command buffer中以一种GPU可以执行的格式一次性写入命令和状态。许多command encoder 对象方法在command buffer上添加commands。只要一个command encoder 是活跃的状态(active),它便具有绝对的能力来为它的command buffer添加命令。一旦想结束encoding commands,调用endEncoding方法。如果想要写更多的commands,可以重新创建一个encoder。
Creating a Command Encoder Object
command encoder 都要给command buffer添加命令,所以需要用MTLCommandBuffer对象的方法来创建合适类型的command encoder .
- renderCommandEncoderWithDescriptor: 方法生成一个MTLRenderCommandEncoder 对象,该对象根据MTLRenderPassDescriptor对象所描述的属性来进行图像渲染。
- computeCommandEncoder 方法生成一个用于data-parallel computations的 MTLComputeCommandEncoder 对象
- blitCommandEncoder方法生成一个用于内存操作的MTLBlitCommandEncoder 对象
- parallelRenderCommandEncoderWithDescriptor: 方法生成一个MTLParallelRenderCommandEncoder 对象,它可以使多个MTLRenderCommandEncoder对象在不同的线程运行同时静态渲染共享的MTLRenderPassDescriptor attachment.
Render Command Encoder
图形渲染可以以一个渲染过程来描述。一个MTLRenderCommandEncoder对象代表了渲染状态和一个单独渲染过程相应的drawing commands. 一个MTLRenderCommandEncoder对象需要有相应的 MTLRenderPassDescriptor对象,其包含了color,depth ,stencil attachment 作为rendering commands 的目标属性。
MTLRenderCommandEncoder对象有以下能力:
- 指定graphics resources ,例如buffer 和texture对象,包括顶点,片元,或则纹理图像数据(vertex, fragment, or texture image data)
- 指定一个 MTLRenderPipelineState 对象,包括编译渲染状态,包含顶点和片元shaders.
- 指定固定功能状态,包括视区、三角形填充模式、剪刀矩形、深度和模具测试以及其他值(viewport, triangle fill mode, scissor rectangle, depth and stencil tests, and other values)。
- 绘制3D 基本体(Draw 3D primitives)
想了解更详细的 MTLRenderCommandEncoder 协议内容,可以查看 Graphics Rendering: Render Command Encoder.
Compute Command Encoder
为了数据并行计算,MTLComputeCommandEncoder协议提供了一系列方法来encode commands到command buffer中,指定计算方法和它的参数(如,texture ,buffer, sampler state),而且分派计算方法的执行。
Blit Command Encoder
MTLBlitCommandEncoder 协议拥有添加命令到buffers (MTLBuffer) 和 textures(MTLTexture)之间的内存拷贝操作中。而且该协议也提供了用单色填充纹理以及生成位图的方法。
Multiple Threads, Command Buffers, and Command Encoders
绝大多数app都会用一个单独线程来encode渲染命令,但是如果想要并行处理command buffer 编码,可以同时创建多个command buffer,然后用各自不同的线程来分别进行编码处理。
如果知道了command buffer的执行时间,便可以enqueue方法来声明它在command queue中的执行顺序,而不用等相应命令的编码和提交。
一个例子: