在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对应的资源和状态。
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
Metal中的一些objects设计的很轻量以及transient,其它的一些很贵,所以需要长期使用,甚至有可能贯穿整个app的始终
command buffer和command encode就很便宜,设计之初就是为了单次使用。创建和销毁都很便宜,所以创建的时候会返回一个autorelease object。
下列的objects就不便宜。尽量resue它们,避免重复创建
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中 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
要创建一个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
MTLCommandBuffer protocol使用下列method来简历command buffer在command queue中的执行顺序。command buffer在committed之后再执行。一旦committed了,command buffer将按照enqueued的顺序进行执行。
关于多线程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)
MTLCommandBuffer有如下method用于monitor command execution。scheduled和completed handler被invoked in execution order在一个undefined thread。handle中执行的代码should complete quickly,如果需要执行expensive或者block的work,建议将该work放到其它线程。
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
status属性对应当前command buffer所处的阶段,是一个enum值MTLCommandBufferStatus。
当成功执行完毕的时候,error属性为nil。如果执行失败,且在创建command buffer的时候设置了MTLCommandBufferErrorOptionEncoderExecutionStatus,status属性为MTLCommandBufferStatusError,error属性会通过enum值MTLCommandBufferError来指出失败的原因,userInfo中也会保存一些错误信息,可以通过MTLCommandBufferEncoderInfoErrorKey来获取到一个MTLCommandBufferEncoderInfo数组来描述这些错误
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。
由于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
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
更多关于MTLRenderCommandEncoder protocol的详细信息,参见 Graphics Rendering: Render 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
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
大部分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
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)