Command Organization and Execution Model


在Metal架构中,MTLDevice protocol定义了一个接口,用来表示一个single GPU。MTLDevice protocol支持:查询设备属性的method,创建其他device相关object(比如buffer、texture),以及encoding、queueing 提交到GPU执行的 render和compute command

a command queue由command buffers组成,command queue组织这些command buffer的执行顺序。a command buffer包含将在特定设备执行的 encoded commands。command encoder将render、compute、blit command放入command buffer,然后这些command buffer将被eventually committed for execution on the device。

MTLCommandQueue protocol为command queue的接口,主要用于创建command buffer。MTLCommandBuffer protocol为command buffer 的接口,主要用于创建command encoder、enqueueing command buffer for execution、检查状态以及其他操作。MTLCommandBuffer protocol支持下列command encoder类型,用于encoding不同类型的GPU workloads到一个command buffer中。

在任意时间点,只能有一个command encode active,然后将command append到command buffer中。只能当一个command encoder结束后,其对应的command buffer才能创建其它command encoder。有一个例外:MTLParallelRenderCommandEncoderprotocol,详见Encoding a Single Rendering Pass Using Multiple Threads

当所有的encoding结束后,可以将MTLCommandBuffer object commit,也就代表着该command buffer已经ready for execution by GPU。MTLCommandQueue protocol决定committed MTLCommandBuffer object什么时候执行,也和command queue中已有的MTLCommandBuffer objects相关。

下图2-1展示command queue、command buffer以及command encoder obejects的关系。图中上侧的每一列(buffer、texture、sampler、depth and stencil stata、pipeline state)表示一个特定command encoder对应的资源和状态。

Metal

The Device Object Represents a GPU

MTLDevice object表示一个可以执行command 的GPU。MTLDevice protocol有创建 command queue的method,可以从内存中分配buffer,创建texture,也可以被用于查询设备的capabilities。为了获取preferred system device on the system,可以使用MTLCreateSystemDefaultDevice函数

layerClass	H:\TrunkProgram\EngineSource\Engine\Source\Runtime\ApplicationCore\Private\IOS\IOSView.cpp

Transient and Non-transient Objects in Metal

Metal中的一些objects设计的很轻量以及transient,其它的一些很贵,所以需要长期使用,甚至有可能贯穿整个app的始终

command buffer和command encode就很便宜,设计之初就是为了单次使用。创建和销毁都很便宜,所以创建的时候会返回一个autorelease object。

下列的objects就不便宜。尽量resue它们,避免重复创建

  • Command Queue
  • Data Buffer
  • Texture
  • Sampler states
  • Libraries
  • Compute states
  • Render pipeline states
  • Depth/stencil states

Command Queue

command queue accepts GPU将会执行的 an ordered list of command buffers。所有的command buffer被sent to a single queue,然后被以command buffer的顺序,按照顺序执行。总的来说,command queue是线程安全的,允许多个active command buffer同时encoded。可以通过MTLDevice的newCommandQueue或者newCommandQueueWithMaxCommandBufferCount:方法创建command queue。总的来说,command queue被希望长期存在,不应该重复创建或者销毁。

    	FMetalCommandQueue::FMetalCommandQueue(mtlpp::Device InDevice, uint32 const MaxNumCommandBuffers /* = 0 */)	H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalCommandQueue.cpp
    		FMetalDeviceContext* FMetalDeviceContext::CreateDeviceContext()	H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalContext.cpp
    			FMetalDynamicRHI::FMetalDynamicRHI(ERHIFeatureLevel::Type RequestedFeatureLevel) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalRHI.cpp
    				FMetalDynamicRHIModule::CreateRHI(ERHIFeatureLevel::Type RequestedFeatureLevel) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalDynamicRHIModule.cpp
    					PlatformCreateDynamicRHI() H:\TrunkProgram\EngineSource\Engine\Source\Runtime\RHI\Private\Apple\AppleDynamicRHI.cpp
    						RHIInit(bool bHasEditorToken) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\RHI\Private\DynamicRHI.cpp
    							FEngineLoop::PreInitPreStartupScreen(const TCHAR* CmdLine) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Launch\Private\LaunchEngineLoop.cpp
    								FEngineLoop::PreInit(const TCHAR* CmdLine) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Launch\Private\LaunchEngineLoop.cpp
    									EnginePreInit( const TCHAR* CmdLine ) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Launch\Private\Launch.cpp
    										GuardedMain( const TCHAR* CmdLine ) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Launch\Private\Launch.cpp
    

Command Buffer

command buffer中 encoded commands知道buffer被committed给GPU去执行。可以将若干个不同类型的encoder build一个single command buffer,使得其包含多个不同类型的encoded commands。在一个典型的app中,一整帧的渲染都可以被encoded到一个single command buffer中,即使该帧包含多个render passs,compute processing function或者blit operation

command buffer是transient 单次使用的object,不支持reuse。当一个command buffer被committed去执行,only valid operation是wait for the command buffer被执行或者完成(通过synchronous call或block,详见Registering Handler Blocks for Command Buffer Execution),以及check status of the command buffer execution

command buffer还represent only independently trackable unit of work by the app, and they define the coherency boundaries established by the Metal memory model, as detailed in Resource Objects: Buffers and Textures

Creating a Command Buffer

要创建一个MTLCommandBuffer object,需要调用MTLCommandQueue的commandBuffer method。MTLCommandBuffer object只能被commit到创建它的MTLCommandQueue object

通过commandBuffer创建的Command buffer会retain那些在执行中需要的data。如果是为了特殊用途,比如在其它地方retain了这些物件,可以使用MTLCommandQueue的 commandBufferWithUnretainedReferences 方法来创建command buffer。commandBufferWithUnretainedReferences method only for extremely performance-critical app在其它地方保证了crucial object的引用计数,直到command buffer的执行结束。否则,一个没有引用关系的物件可能会被过早release,导致command buffer的执行结束为undefined

    	FMetalCommandQueue::CreateCommandBuffer(void)	H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalCommandQueue.cpp
    		FMetalCommandEncoder::StartCommandBuffer(void) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalCommandEncoder.cpp
    			FMetalCommandEncoder::CommitCommandBuffer(uint32 const Flags) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalCommandEncoder.cpp
    			这里会销毁command buffer。
    				·FMetalCommandEncoder::~FMetalCommandEncoder(void) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalCommandEncoder.cpp
    				FMetalRenderPass::Submit(EMetalSubmitFlags Flags) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalRenderPass.cpp
    					~FMetalRenderPass::ConditionalSubmit() H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalRenderPass.cpp
    						FMetalRenderPass::DrawPrimitive(uint32 PrimitiveType, uint32 BaseVertexIndex, uint32 NumPrimitives, uint32 NumInstances)
    						FMetalRenderPass::DrawPrimitiveIndirect(uint32 PrimitiveType, FMetalVertexBuffer* VertexBuffer, uint32 ArgumentOffset)
    						FMetalRenderPass::DrawIndexedPrimitive(FMetalBuffer const& IndexBuffer, uint32 IndexStride, uint32 PrimitiveType, int32 BaseVertexIndex, uint32 FirstInstance,
											 uint32 NumVertices, uint32 StartIndex, uint32 NumPrimitives, uint32 NumInstances)
								FMetalContext::DrawIndexedPrimitive(FMetalBuffer const& IndexBuffer, uint32 IndexStride, mtlpp::IndexType IndexType, uint32 PrimitiveType, int32 BaseVertexIndex, uint32 FirstInstance, uint32 NumVertices, uint32 StartIndex, uint32 NumPrimitives, uint32 NumInstances) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalContext.cpp
		    						FMetalRHICommandContext::RHIDrawIndexedPrimitive(FRHIIndexBuffer* IndexBufferRHI, int32 BaseVertexIndex, uint32 FirstInstance, uint32 NumVertices, uint32 StartIndex, uint32 NumPrimitives, uint32 NumInstances) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalCommands.cpp
										DrawIndexedPrimitive(FRHIIndexBuffer* IndexBuffer, int32 BaseVertexIndex, uint32 FirstInstance, uint32 NumVertices, uint32 StartIndex, uint32 NumPrimitives, uint32 NumInstances) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\RHI\Public\RHICommandList.h
											FMeshDrawCommand::SubmitDraw( H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Renderer\Private\MeshPassProcessor.cpp
							FMetalRenderPass::DrawIndexedIndirect(FMetalIndexBuffer* IndexBuffer, uint32 PrimitiveType, FMetalStructuredBuffer* VertexBuffer, int32 DrawArgumentsIndex, uint32 NumInstances)
							FMetalRenderPass::DrawIndexedPrimitiveIndirect(uint32 PrimitiveType,FMetalIndexBuffer* IndexBuffer,FMetalVertexBuffer* VertexBuffer,uint32 ArgumentOffset)
							FMetalRenderPass::Dispatch(uint32 ThreadGroupCountX, uint32 ThreadGroupCountY, uint32 ThreadGroupCountZ)
							FMetalRenderPass::DispatchIndirect(FMetalVertexBuffer* ArgumentBuffer, uint32 ArgumentOffset)
							FMetalRenderPass::CopyFromTextureToBuffer(FMetalTexture const& Texture, uint32 sourceSlice, uint32 sourceLevel, mtlpp::Origin sourceOrigin, mtlpp::Size sourceSize, FMetalBuffer const& toBuffer, uint32 destinationOffset, uint32 destinationBytesPerRow, uint32 destinationBytesPerImage, mtlpp::BlitOption options)
							FMetalRenderPass::CopyFromBufferToTexture(FMetalBuffer const& Buffer, uint32 sourceOffset, uint32 sourceBytesPerRow, uint32 sourceBytesPerImage, mtlpp::Size sourceSize, FMetalTexture const& toTexture, uint32 destinationSlice, uint32 destinationLevel, mtlpp::Origin destinationOrigin, mtlpp::BlitOption options)
							FMetalRenderPass::CopyFromTextureToTexture(FMetalTexture const& Texture, uint32 sourceSlice, uint32 sourceLevel, mtlpp::Origin sourceOrigin, mtlpp::Size sourceSize, FMetalTexture const& toTexture, uint32 destinationSlice, uint32 destinationLevel, mtlpp::Origin destinationOrigin)
							FMetalRenderPass::CopyFromBufferToBuffer(FMetalBuffer const& SourceBuffer, NSUInteger SourceOffset, FMetalBuffer const& DestinationBuffer, NSUInteger DestinationOffset, NSUInteger Size)
							FMetalRenderPass::SynchronizeTexture(FMetalTexture const& Texture, uint32 Slice, uint32 Level)
							FMetalRenderPass::SynchroniseResource(mtlpp::Resource const& Resource)
							FMetalRenderPass::FillBuffer(FMetalBuffer const& Buffer, ns::Range Range, uint8 Value)
							FMetalRenderPass::InsertDebugEncoder()
							H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalRenderPass.cpp
    				FMetalViewport::Present(FMetalCommandQueue& CommandQueue, bool bLockToVsync) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalViewport.cpp
		    		这里真的创建commandbuffer了。
		    			FMetalDeviceContext::EndDrawingViewport(FMetalViewport* Viewport, bool bPresent, bool bLockToVsync) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalContext.cpp
		    				FMetalRHIImmediateCommandContext::RHIEndDrawingViewport(FRHIViewport* ViewportRHI,bool bPresent,bool bLockToVsync)
		    				H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalViewport.cpp
		    					FRHICommandList::EndDrawingViewport(FRHIViewport* Viewport, bool bPresent, bool bLockToVsync) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\RHI\Private\RHICommandList.cpp
		    						FViewport::EndRenderFrame(FRHICommandListImmediate& RHICmdList, bool bPresent, bool bLockToVsync) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Engine\Private\UnrealClient.cpp
		    							·FViewport::GetRawHitProxyData(FIntRect InRect) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Engine\Private\UnrealClient.cpp
		    							ViewportEndDrawing(FRHICommandListImmediate& RHICmdList, FEndDrawingCommandParams Parameters) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Engine\Private\UnrealClient.cpp
		    								FViewport::EnqueueEndRenderFrame(const bool bLockToVsync, const bool bShouldPresent)H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Engine\Private\UnrealClient.cpp
		    									·FViewport::Draw( bool bShouldPresent /*= true */) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Engine\Private\UnrealClient.cpp
		    							·FViewport::HighResScreenshot() H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Engine\Private\UnrealClient.cpp
		    							·FStreamingPauseRenderingModule::BeginStreamingPause( FViewport* GameViewport ) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\StreamingPauseRendering\Private\StreamingPauseRendering.cpp
		    						·FSlateRHIRenderer::DrawWindow_RenderThread(FRHICommandListImmediate& RHICmdList, FViewportInfo& ViewportInfo, FSlateWindowElementList& WindowElementList, const struct FSlateDrawWindowCommandParams& DrawCommandParams) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\SlateRHIRenderer\Private\SlateRHIRenderer.cpp
    			-FMetalRenderPass::ConditionalSwitchToTessellation(void) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalRenderPass.cpp
    			-FMetalRenderPass::ConditionalSwitchToSeparateTessellation(void) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalRenderPass.cpp
    			-FMetalRenderPass::ConditionalSwitchToAsyncBlit(void) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalRenderPass.cpp
    			-FMetalRenderPass::ConditionalSwitchToAsyncCompute(void) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalRenderPass.cpp
    			FMetalRenderPass::Begin(FMetalFence* Fence, bool const bParallelBegin) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalRenderPass.cpp
    				FMetalContext::InitFrame(bool const bImmediateContext, uint32 Index, uint32 Num) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalContext.cpp
    					FMetalDeviceContext::FMetalDeviceContext(mtlpp::Device MetalDevice, uint32 InDeviceIndex, FMetalCommandQueue* Queue) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalContext.cpp
    						=FMetalDeviceContext::CreateDeviceContext() H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalContext.cpp
		    				这里真的创建commandbuffer了。
    					·FMetalDeviceContext::EndFrame() H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalContext.cpp
    				-FMetalDeviceContext::SetParallelRenderPassDescriptor(FRHIRenderPassInfo const& TargetInfo) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalContext.cpp
    				-FMetalDeviceContext::EndParallelRenderCommandEncoding(void) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalContext.cpp
    			FMetalRenderPass::BeginRenderPass(mtlpp::RenderPassDescriptor RenderPass) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalRenderPass.cpp
    			由于command buffer在之前每次dc的时候会被销毁,所以这里真的创建command buffer。
    				FMetalContext::SetRenderPassInfo(const FRHIRenderPassInfo& RenderTargetsInfo, bool const bRestart) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalContext.cpp
    					FMetalRHICommandContext::SetRenderTargetsAndClear(const FRHISetRenderTargetsInfo& RenderTargetsInfo) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalCommands.cpp
    						FMetalRHICommandContext::SetRenderTargets(uint32 NumSimultaneousRenderTargets, const FRHIRenderTargetView* NewRenderTargets, const FRHIDepthRenderTargetView* NewDepthStencilTargetRHI) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalCommands.cpp
								FMetalRHIImmediateCommandContext::RHIBeginDrawingViewport(FRHIViewport* ViewportRHI, FRHITexture* RenderTargetRHI) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalViewport.cpp
									FRHICommandList::BeginDrawingViewport(FRHIViewport* Viewport, FRHITexture* RenderTargetRHI) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\RHI\Private\RHICommandList.cpp
										FViewport::BeginRenderFrame(FRHICommandListImmediate& RHICmdList) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Engine\Private\UnrealClient.cpp
											FViewport::EnqueueBeginRenderFrame(const bool bShouldPresent) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Engine\Private\UnrealClient.cpp
												-FViewport::GetRawHitProxyData(FIntRect InRect) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Engine\Private\UnrealClient.cpp
												·FViewport::Draw( bool bShouldPresent /*= true */) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Engine\Private\UnrealClient.cpp
												-FViewport::HighResScreenshot() H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Engine\Private\UnrealClient.cpp
										·FSlateRHIRenderer::DrawWindow_RenderThread(FRHICommandListImmediate& RHICmdList, FViewportInfo& ViewportInfo, FSlateWindowElementList& WindowElementList, const struct FSlateDrawWindowCommandParams& DrawCommandParams) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\SlateRHIRenderer\Private\SlateRHIRenderer.cpp
    					-FMetalDeviceContext::SetParallelRenderPassDescriptor(FRHIRenderPassInfo const& TargetInfo) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalContext.cpp
    					·FMetalContext::ResetRenderCommandEncoder() H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalContext.cpp
    					FMetalRHICommandContext::RHIBeginRenderPass(const FRHIRenderPassInfo& InInfo, const TCHAR* InName) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalRHIContext.cpp
    						~BeginRenderPass(const FRHIRenderPassInfo& InInfo, const TCHAR* Name) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\RHI\Public\RHICommandList.h
    							FMobileSceneCaptureMaskRenderer::RenderDeferred(FRHICommandListImmediate& RHICmdList, const TArrayView<.const FViewInfo*> ViewList/*, const FSortedLightSetSceneInfo& SortedLightSet*/) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Renderer\Private\MobileSceneCaptureMaskRendering.cpp
    							FMobileSceneRenderer::RenderDeferred(FRHICommandListImmediate& RHICmdList, const TArrayView<.const FViewInfo*> ViewList, const FSortedLightSetSceneInfo& SortedLightSet) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Renderer\Private\MobileShadingRenderer.cpp
    							FSceneRenderTargets::BeginRenderingPrePass(FRHICommandList& RHICmdList, bool bPerformClear, bool bStencilClear) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Renderer\Private\PostProcess\SceneRenderTargets.cpp
    							FSceneRenderer::RenderShadowDepthMaps(FRHICommandListImmediate& RHICmdList)
    							FSceneRenderer::RenderShadowDepthMapAtlases(FRHICommandListImmediate& RHICmdList)
    							H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Renderer\Private\ShadowDepthRendering.cpp
    							FMobileSceneRenderer::RenderSingleLayerWater(FRHICommandListImmediate& RHICmdList) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Renderer\Private\SingleLayerWaterRendering.cpp
    							FSystemTextures::InitializeCommonTextures(FRHICommandListImmediate& RHICmdList) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Renderer\Private\SystemTextures.cpp
    							FSlate3DRenderer::DrawWindowToTarget_RenderThread(FRHICommandListImmediate& InRHICmdList, const FRenderThreadUpdateContext& Context) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\SlateRHIRenderer\Private\Slate3DRenderer.cpp
    							FSlatePostProcessor::UpsampleRect(FRHICommandListImmediate& RHICmdList, IRendererModule& RendererModule, const FPostProcessRectParams& Params, const FIntPoint& DownsampleSize, FSamplerStateRHIRef& Sampler) 
    							FSlatePostProcessor::BlurRect(FRHICommandListImmediate& RHICmdList, IRendererModule& RendererModule, const FBlurRectParams& Params, const FPostProcessRectParams& RectParams)
    							FSlatePostProcessor::DownsampleRect(FRHICommandListImmediate& RHICmdList, IRendererModule& RendererModule, const FPostProcessRectParams& Params, const FIntPoint& DownsampleSize)
    							FSlatePostProcessor::ColorDeficiency(FRHICommandListImmediate& RHICmdList, IRendererModule& RendererModule, const FPostProcessRectParams& RectParams)
    							H:\TrunkProgram\EngineSource\Engine\Source\Runtime\SlateRHIRenderer\Private\SlatePostProcessor.cpp
    							FSlateRHIRenderer::DrawWindow_RenderThread(FRHICommandListImmediate& RHICmdList, FViewportInfo& ViewportInfo, FSlateWindowElementList& WindowElementList, const struct FSlateDrawWindowCommandParams& DrawCommandParams) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\SlateRHIRenderer\Private\SlateRHIRenderer.cpp
    							UpdateScissorRect(
    							FSlateRHIRenderingPolicy::DrawElements(
    							H:\TrunkProgram\EngineSource\Engine\Source\Runtime\SlateRHIRenderer\Private\SlateRHIRenderingPolicy.cpp
    				~FMetalContext::PrepareToDraw(uint32 PrimitiveType, EMetalIndexType IndexType) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalContext.cpp
    					=FMetalContext::DrawIndexedPrimitive(FMetalBuffer const& IndexBuffer, uint32 IndexStride, mtlpp::IndexType IndexType, uint32 PrimitiveType, int32 BaseVertexIndex, uint32 FirstInstance, uint32 NumVertices, uint32 StartIndex, uint32 NumPrimitives, uint32 NumInstances) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalContext.cpp
    					FMetalContext::DrawPrimitiveIndirect(uint32 PrimitiveType, FMetalVertexBuffer* VertexBuffer, uint32 ArgumentOffset)
    					FMetalContext::DrawIndexedIndirect(FMetalIndexBuffer* IndexBuffer, uint32 PrimitiveType, FMetalStructuredBuffer* VertexBuffer, int32 DrawArgumentsIndex, uint32 NumInstances)
    					FMetalContext::DrawIndexedPrimitiveIndirect(uint32 PrimitiveType,FMetalIndexBuffer* IndexBuffer,FMetalVertexBuffer* VertexBuffer, uint32 ArgumentOffset)
    					H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalContext.cpp
    		=FMetalViewport::Present(FMetalCommandQueue& CommandQueue, bool bLockToVsync) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalViewport.cpp
    

Executing Commands

MTLCommandBuffer protocol使用下列method来简历command buffer在command queue中的执行顺序。command buffer在committed之后再执行。一旦committed了,command buffer将按照enqueued的顺序进行执行。

  • enqueue method为command buffer在command queue中保留一个位置,然而并不会将该command buffer commit去执行。当command buffer被committed后,它将会在该command queue的之前enqueued command buffer之后进行执行
  • commit method使得command buffer尽快被执行,但是会在该command queue之前enqueued 的committed的command buffer之后。如果该command buffer还没有被enqueue,commit method会带一个隐式的enqueue。

关于多线程enqueue的例子,可以参见Multiple Threads, Command Buffers, and Command Encoders

    	FMetalCommandQueue::CommitCommandBuffer(mtlpp::CommandBuffer& CommandBuffer)
    		FMetalCommandList::Commit(mtlpp::CommandBuffer& Buffer, TArray<.ns::Object<.mtlpp::CommandBufferHandler>> CompletionHandlers, bool const bWait, bool const bIsLastCommandBuffer) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalCommandList.cpp
    			=FMetalCommandEncoder::CommitCommandBuffer(uint32 const Flags) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalCommandEncoder.cpp
    			这里会销毁command buffer。
    		FMetalCommandQueue::SubmitCommandBuffers(TArray<.mtlpp::CommandBuffer> BufferList, uint32 Index, uint32 Count) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalCommandQueue.cpp
    			FMetalCommandList::Submit(uint32 InIndex, uint32 Count)
    			H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalCommandList.cpp
    				!SubmitAndFreeContextContainer(int32 NewIndex, int32 NewNum) override final
    				H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalContext.cpp
    		=FMetalViewport::Present(FMetalCommandQueue& CommandQueue, bool bLockToVsync)
    

Registering Handler Blocks for Command Buffer Execution

MTLCommandBuffer有如下method用于monitor command execution。scheduled和completed handler被invoked in execution order在一个undefined thread。handle中执行的代码should complete quickly,如果需要执行expensive或者block的work,建议将该work放到其它线程。

  • addScheduledHandler: method注册一个block code,当command buffer scheduled的时候执行。scheduled指的是,当执行有依赖关系的其它commandbuffer或者system中其它api的时候。可以对一个command buffer注册多个schedule handler
  • waitUntilScheduled method synchronously wait,当command buffer scheduled,以及addScheduleHandle: method注册的所有handle都执行完毕的时候返回。
  • addCompletedHandler: method注册一个block code,当command buffer 被device执行完毕之后立即执行。可以对一个command buffer注册多个completed handler。
  • waitUntilCompleted method synchronously wait,当command buffer 被device执行完毕,以及 addCompletedHandler: method注册的所有handle都执行完毕的时候返回。

presentDrawable: method是一个特殊的completed handle。这个convenience method 在command buffer scheduled后present displayable resource的内容(CAMetalDrawable object)。关于presentDrawable: method的更多信息,参考Integration with Core Animation: CAMetalLayer

    	AddScheduledHandler
    		!FMetalContext::StartTiming(class FMetalEventNode* EventNode) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalContext.cpp
    		FMetalRenderPass::AddAsyncCommandBufferHandlers(mtlpp::CommandBufferHandler Scheduled, mtlpp::CommandBufferHandler Completion) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalRenderPass.cpp
    			FMetalContext::SubmitAsyncCommands(mtlpp::CommandBufferHandler ScheduledHandler, mtlpp::CommandBufferHandler CompletionHandler, bool const bWait) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalContext.cpp
    				-FMetalSurface::UpdateSurfaceAndDestroySourceBuffer(id <.MTLBuffer> SourceBuffer, uint32 MipIndex, uint32 ArrayIndex)
    				-InternalUpdateTexture2DArray(FMetalContext& Context, FRHITexture2DArray* TextureRHI, uint32 SliceIndex, uint32 MipIndex, FUpdateTextureRegion2D const& UpdateRegion, uint32 SourcePitch, FMetalBuffer Buffer)
    				-InternalUpdateTexture2D(FMetalContext& Context, FRHITexture2D* TextureRHI, uint32 MipIndex, FUpdateTextureRegion2D const& UpdateRegion, uint32 SourcePitch, FMetalBuffer Buffer)
    				-InternalUpdateTexture3D(FMetalContext& Context, FRHITexture3D* TextureRHI, uint32 MipIndex, const FUpdateTextureRegion3D& UpdateRegion, uint32 SourceRowPitch, uint32 SourceDepthPitch, FMetalBuffer Buffer)
    				-CopyMips(FMetalContext& Context, FMetalTexture2D* OldTexture, FMetalTexture2D* NewTexture, int32 NewMipCount, int32 NewSizeX, int32 NewSizeY, FThreadSafeCounter* RequestStatus)
		AddCompletedHandler
			FMetalSubBufferRing::Commit(mtlpp::CommandBuffer& CmdBuf) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalBuffer.cpp
			=FMetalCommandList::Commit(mtlpp::CommandBuffer& Buffer, TArray<.ns::Object<.mtlpp::CommandBufferHandler>> CompletionHandlers, bool const bWait, bool const bIsLastCommandBuffer) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalCommandList.cpp
				->FMetalCommandEncoder::AddCompletionHandler(mtlpp::CommandBufferHandler Handler) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalCommandEncoder.cpp
					FMetalCommandEncoder::InsertCommandBufferFence(FMetalCommandBufferFence& Fence, mtlpp::CommandBufferHandler Handler) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalCommandEncoder.cpp
						FMetalRenderPass::InsertCommandBufferFence(FMetalCommandBufferFence& Fence, mtlpp::CommandBufferHandler Handler) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalRenderPass.cpp
							FMetalContext::InsertCommandBufferFence(FMetalCommandBufferFence& Fence, mtlpp::CommandBufferHandler Handler) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalContext.cpp
								!FMetalRHICommandContext::RHIEndOcclusionQueryBatch() H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalRHIContext.cpp
									FMetalRHICommandContext::RHIEndRenderPass() H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalRHIContext.cpp
									->FMetalRHICommandContext::RHIBeginOcclusionQueryBatch(uint32 NumQueriesInBatch) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalRHIContext.cpp
										FMetalRHICommandContext::RHIBeginRenderPass(const FRHIRenderPassInfo& InInfo, const TCHAR* InName) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalRHIContext.cpp
								FMetalRHIRenderQuery::End(FMetalContext* Context) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalRHIRenderQuery.cpp
									->FMetalDynamicRHI::RHICreateRenderQuery(ERenderQueryType QueryType) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalDynamicRHI.cpp
										FMetalDynamicRHI::RHICreateRenderQuery_RenderThread(class FRHICommandListImmediate& RHICmdList, ERenderQueryType QueryType) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalDynamicRHI.cpp
											CreateRenderQuery_RenderThread(ERenderQueryType QueryType) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\RHI\Public\RHICommandList.h
												RHICreateRenderQuery(ERenderQueryType QueryType) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\RHI\Public\RHICommandList.h
													FDefaultRHIRenderQueryPool::FDefaultRHIRenderQueryPool(ERenderQueryType InQueryType, FDynamicRHI* InDynamicRHI, uint32 InNumQueries) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\RHI\Private\DynamicRHI.cpp
														RHICreateRenderQueryPool(ERenderQueryType QueryType, uint32 NumQueries = UINT32_MAX) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\RHI\Public\DynamicRHI.h
													FDefaultRHIRenderQueryPool::AllocateQuery() H:\TrunkProgram\EngineSource\Engine\Source\Runtime\RHI\Private\DynamicRHI.cpp
									FMetalDynamicRHI::RHIGetRenderQueryResult(FRHIRenderQuery* QueryRHI, uint64& OutNumPixels, bool bWait, uint32 GPUIndex) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalDynamicRHI.cpp
									!FMetalRHICommandContext::RHIBeginRenderQuery(FRHIRenderQuery* QueryRHI) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalRHIContext.cpp
										BeginRenderQuery(FRHIRenderQuery* RenderQuery) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\RHI\Public\RHICommandList.h
									!FMetalRHICommandContext::RHIEndRenderQuery(FRHIRenderQuery* QueryRHI) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalRHIContext.cpp
										EndRenderQuery(FRHIRenderQuery* RenderQuery) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\RHI\Public\RHICommandList.h
					-FMetalRenderPass::AddCompletionHandler(mtlpp::CommandBufferHandler Handler) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalRenderPass.cpp
						-FMetalSurface::UpdateSurfaceAndDestroySourceBuffer(id <.MTLBuffer> SourceBuffer, uint32 MipIndex, uint32 ArrayIndex) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalTexture.cpp
						-CopyMips(FMetalContext& Context, FMetalTexture2D* OldTexture, FMetalTexture2D* NewTexture, int32 NewMipCount, int32 NewSizeX, int32 NewSizeY, FThreadSafeCounter* RequestStatus) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalTexture.cpp
						-FMetalDeviceContext::EndFrame()
						-FMetalContext::EndTiming(class FMetalEventNode* EventNode)
						-FMetalContext::StartTiming(class FMetalEventNode* EventNode)
						H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalContext.cpp
					-FMetalRenderPass::AddAsyncCommandBufferHandlers(mtlpp::CommandBufferHandler Scheduled, mtlpp::CommandBufferHandler Completion) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalRenderPass.cpp
			!FMetalCommandBufferStats::End(mtlpp::CommandBuffer const& Buffer)
			H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalProfiler.cpp
			=FMetalViewport::Present(FMetalCommandQueue& CommandQueue, bool bLockToVsync)
		WaitUntilCompleted
			=FMetalCommandList::Commit(mtlpp::CommandBuffer& Buffer, TArray<.ns::Object<.mtlpp::CommandBufferHandler>> CompletionHandlers, bool const bWait, bool const bIsLastCommandBuffer) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalCommandList.cpp
			=!FMetalCommandQueue::CommitCommandBuffer(mtlpp::CommandBuffer& CommandBuffer) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalCommandQueue.cpp
    

Monitoring Command Buffer Execution Status

status属性对应当前command buffer所处的阶段,是一个enum值MTLCommandBufferStatus

当成功执行完毕的时候,error属性为nil。如果执行失败,且在创建command buffer的时候设置了MTLCommandBufferErrorOptionEncoderExecutionStatus,status属性为MTLCommandBufferStatusError,error属性会通过enum值MTLCommandBufferError来指出失败的原因,userInfo中也会保存一些错误信息,可以通过MTLCommandBufferEncoderInfoErrorKey来获取到一个MTLCommandBufferEncoderInfo数组来描述这些错误

Command Encoder

command encoder是一个transient object,用于将command和status用GPU能执行的format写入一个single command buffer。可以使用多个command encoder将command写入同一个command buffer。然而当一个command encoder active的时候,它拥有了独占的权力来将command写入command buffer。当结束encoding command的时候,调用endEncoding method。

Creating a Command Encoder Object

由于command encoder是将命令append到一个特定的command buffer中,所以创建的时候也是通过一个MTLCommandBuffer object创建的。使用下列method来创建command encode

    	RenderCommandEncoder
    		FMetalCommandEncoder::BeginRenderCommandEncoding(void) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalCommandEncoder.cpp
    	ComputeCommandEncoder
    		FMetalCommandEncoder::BeginComputeCommandEncoding(mtlpp::DispatchType DispatchType) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalCommandEncoder.cpp
		BlitCommandEncoder
			FMetalCommandEncoder::BeginBlitCommandEncoding(void) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalCommandEncoder.cpp
			FMetalViewport::Present(FMetalCommandQueue& CommandQueue, bool bLockToVsync) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalViewport.cpp
		ParallelRenderCommandEncoder
			FMetalCommandEncoder::BeginParallelRenderCommandEncoding(uint32 NumChildren) H:\TrunkProgram\EngineSource\Engine\Source\Runtime\Apple\MetalRHI\Private\MetalCommandEncoder.cpp
    

Render Command Encoder

Graphics rendering can be described in terms of a rendering pass. 一个MTLRenderCommandEncoder包含一个sindle render pass中对应的render state和dc。一个MTLRenderCommandEncoder对应一个MTLRenderPassDescriptor,详见 Creating a Render Pass Descriptor,包含render command对应的color、depth、stencil attachment。MTLRenderCommandEncoder包含如下method

  • 指定graphic resources,包括buffer、texture,包含了vertex、fragment、texture image data
  • 指定MTLRenderPipelineState包含编译好的render state,包含vertex、fragment shader
  • 指定fix-function state,包含viewport、triangle fill mode、scissor rectangle、depth/stencil test以及其它
  • 绘制3D primitives

更多关于MTLRenderCommandEncoder protocol的详细信息,参见 Graphics Rendering: Render Command Encoder

Compute Command Encoder

MTLComputeCommandEncoder protocol提供method去encode command buffer中的commands,来指定compute function和它的参数(比如texture、buffer、sampler state)来执行data-parallel 计算。可以通过MTLCommandBuffer的computeCommandEncoder method来创建compute command encoder。关于MTLComputeCommandEncoder的更多method和属性,参见Data-Parallel Compute Processing: Compute Command Encoder

Blit Command Encoder

MTLBlitCommandEncoder protocol提供method去append用于MTLBuffer和MTLTexture之间内存 copy 操作的command,同时也提供使用纯色fill texture以及生成mipmap的method。可以通过MTLCommandBuffer的 blitCommandEncoder method来创建 blit command encoder。关于 MTLBlitCommandEncoder 的更多method和属性,参见Buffer and Texture Operations: Blit Command Encoder

Multiple Threads, Command Buffers, and Command Encoders

大部分app的一帧只会用一个single thread去encode render comamnd到一个single command buffer。在这一帧结束的时候,commit这个command buffer,这里会schedule command以及开始执行command

如果想要将command buffer encoding并行,可以一次创建多个command buffer,然后在一个separate thread中对each one进行encode。如果事先知道command buffer的执行顺序,可以用MTLCommandBuffer enqueue method来设置command queue的执行顺序,而不需要等待command的encode和commited。否则,只有当command buffer committed的时候,才会在command queue中,之前enqueue的command buffers之后给它分配位置。

一个command buffer在一个时间点只能被一个cpu thread访问。multithread app可以使用一个command buffer一个thread的方式来创建多个并行的command buffer。

下图2-2展示了一个三线程的例子。每个线程都有自己的command buffer。在每个线程中,同一时间只有一个command encoder可以访问command buffer。2-2展示了每个command buffer从不同的command encoder获取command。当结束encoding的时候,执行command encoder的endEncoding method,然后一个新的command encoder可以开始对这个command buffer进行encodiing command

Metal

MTLParallelRenderCommandEncoder允许一个render pass拆成多个command encoder使用separate thread。更多详细信息,参考Encoding a Single Rendering Pass Using Multiple Threads.

本节教程就到此结束,希望大家继续阅读我之后的教程。

谢谢大家,再见!


原创技术文章,撰写不易,转载请注明出处:电子设备中的画家|王烁 于 2022 年 1 月 14 日发表,原文链接(http://geekfaner.com/shineengine/blog47_MetalProgrammingGuide_2.html)