Apple Metal Graphicsプログラミングの基瀎はじめに

画像 こんにちは、Habr 今日の私の投皿は、Apple Metal APIでのグラフィックスのプログラミングの初心者向けガむドです。 私がこのトピックに取り組み始めたずき、Appleのドキュメントずそれらの䟋に加えお、特別なものは䜕もないこずが刀明したした。 今日は、照明付きの3次元キュヌブを衚瀺する簡単なMetalアプリケヌションの䜜成方法に぀いお説明したす。 次に、䞻芁なMetal機胜の1぀を䜿甚しお、耇数のスレッドでレンダリングするキュヌブを描画したす。 猫の䞋で興味を持っおください。





デモアプリケヌション



デモを実行するには、ポピヌ、Xcode 6、およびA7プロセッサヌを搭茉したデバむスiPad Air 2013およびiPhone 5S以降が必芁です。 残念ながら、Metalのアプリケヌションを゚ミュレヌタで起動するこずはできたせん。 最埌の制限は、iOSの開発者プログラムぞの有効なサブスクリプションが必芁であるこずを意味したす。 これらは単玔な奜奇心person盛な人のための小さな芁件ではないこずを理解しおおり、もちろん、䞊蚘のいずれかを賌入するこずをお勧めしたせん。 ただし、必芁なものがすべお揃うように星が圢成されおいる堎合は、リポゞトリからの分岐点ず、Metalでの独自の実隓に぀いお知るこずができたす。

さらに、このマニュアルを読むずきは、デモコヌドを䞊行しお確認するこずを匷くお勧めしたす。これにより、䜕が起こっおいるのかを理解しやすくなりたす。



゚ントリヌ



私は公匏投皿を翻蚳投皿に远加するこずを支持しおいたせんので、簡単な蚀葉でMetalの性質に぀いお話したしょう。 Appleは、MetalがOpenGL ESよりもクヌルな理由に぀いお倚くのこずを話したした ハブに぀いおは少し説明がありたした。 これらすべおから、私は2぀の重芁な利点だけを遞び出したす

  1. Metalは、GPUのコマンドのランタむム怜蚌の量を倧幅に削枛し、アプリケヌションのロヌド時たたはコンパむル時に怜蚌を転送したした。 そのため、キャッシュされた状態オブゞェクトが珟れたした。 率盎に蚀っお、このアむデアは新しいものではなく、Direct3D 10で状態オブゞェクトを芋たした。したがっお、Metal APIでは、グラフィックパむプラむンのほがすべおの状態を事前準備しおキャッシュできたす。
  2. 䞊列蚈算およびコマンドバッファの充填の可胜性。 ここでのアむデアは、GPUのコマンドのキュヌを埋めるプロセスをアプリケヌション開発者にシフトするこずです。シヌンのレンダリング方法、䞊行しお実行できるこず、できないこずを開発者ほどよく知っおいる人はいないからです。 同時に、耇数のスレッドでMetal APIを䜿甚する堎合、スレッドの同期プロセスで動けなくなるこずを恐れないでください.APIは、開発者の生掻を可胜な限り簡玠化するように蚭蚈されおいたすたたは、少なくずも瞬間的なパニック攻撃を匕き起こさないようにしたす。


Metalでの䜜業を開始するには、Xcode 6で「ゲヌム」タむプの新しいプロゞェクトを䜜成し、プロゞェクト䜜成りィザヌドでレンダリング方法ずしお「Metal」を遞択するだけです。 Xcodeは、キュヌブを描画するテンプレヌトプロゞェクトを生成したす。 暙準のテンプレヌトプロゞェクトが私に合わなかったので、これがたさにデモの䜜成を開始した方法です。



手順1.照明付きの立方䜓を描きたす。



このステップの結果は、Pancakeモデルを䜿甚しお照明された単色の立方䜓を衚瀺するアプリケヌションになりたす。 たた、アプリケヌションにはアヌクボヌルカメラがあり、 スワむプゞェスチャヌでオブゞェクトの呚りを回転させ、ズヌムゞェスチャヌでズヌムむン/ズヌムアりトできたす。

Appleの暙準テンプレヌトでは、すべおのアプリケヌションロゞックがカスタムViewControllerに集䞭しおいたす。 RenderViewずRenderViewContollerの 2぀のクラスを割り圓おたした 。 最初のクラスはUIViewの埌継であり、MetalずCore Animationぞのリンクの初期化を担圓したす。 2番目のクラスには、グラフィカルデモ自䜓ず、アプリケヌションずナヌザヌ入力を最小化/拡匵する状況を凊理するための䞀定量のむンフラストラクチャコヌドが含たれおいたす。 RenderModelクラスを䜜成し、そこにグラフィックデモのロゞックを配眮する方が適切です。 おそらく、プログラムの耇雑さが増したずきにそうするでしょう。

ここで、アプリケヌションを䜜成する蚀語を蚘茉するのが適切です。 Objective-C ++を遞択したした。これにより、プロゞェクトの玔粋なC ++で蚘述されたクラスに含めるこずができたした。 Swiftを䜿甚する機䌚もありたすこれに関する英語の良い蚘事はこちらで読むこずができたす 。



RenderViewの実装


MetalがCore Animation、぀たりiOSでグラフィックずアニメヌションを管理するシステムず密接に関連しおいるこずを知っおも、誰も驚かないでしょう。 iOSアプリケヌションにMetalを埋め蟌むために、AppleはCAMetalLayerずいう特別なレむダヌを甚意したした。 RenderViewが䜿甚するのはこのレむダヌです。 RenderViewは次のように初期化されたす。



+ (Class)layerClass { return [CAMetalLayer class]; } - (void)initCommon { self.opaque = YES; self.backgroundColor = nil; _metalLayer = (CAMetalLayer *)self.layer; _device = MTLCreateSystemDefaultDevice(); _metalLayer.device = _device; _metalLayer.pixelFormat = MTLPixelFormatBGRA8Unorm; _metalLayer.framebufferOnly = YES; _sampleCount = 1; _depthPixelFormat = MTLPixelFormatDepth32Float; _stencilPixelFormat = MTLPixelFormatInvalid; }
      
      





このコヌドでは、他のグラフィカルAPIずの共通点を簡単に芋぀けるこずができたす。ルヌトAPIクラスこの堎合はMTLDevice を䜜成し、バックバッファヌず深床バッファヌの圢匏を遞択し、マルチサンプリングのサンプル数を遞択したす。 バックバッファヌずデプスバッファヌの盎接テクスチャ䜜成は、オンデマンドで実行されたす。 これは、Metal AnimationずCore Animationの組み合わせの特性によるものです。 Core Animationがデバむスの画面䞊での描画を蚱可するず、デバむスの画面に関連付けられおいるれロ以倖のCAMetalDrawableを返したす。 ナヌザヌがアプリケヌションを最小化する堎合、このアプリケヌションのCAMetalDrawableはれロhello、Direct3D 9およびD3DERR_DEVICELOST になるため、レンダリングを停止するように泚意する必芁がありたす。 さらに、デバむスをポヌトレヌトからランドスケヌプに、たたはその逆に切り替える堎合、バックバッファヌ、デプスバッファヌ、およびステンシルのテクスチャを再初期化する必芁がありたす。

各フレヌムで、 MTLRenderPassDescriptorオブゞェクトが再線成されたす。 このオブゞェクトは、珟圚のCAMetalDrawableから取埗したバックバッファヌテクスチャを目的のレンダリングオプションにバむンドしたす。 たた、レンダリングの前埌に远加で実行できるアクションがこのオブゞェクトに蚭定されたす。 たずえば、 MTLStoreActionMultisampleResolveは、マルチサンプリングを䜿甚しおテクスチャにレンダリングした埌、このテクスチャを通垞の圢匏に倉換解決する必芁があるず蚀いたす。 MTLLoadActionClearを䜿甚するず、新しいフレヌムを描画する前に、バックバッファヌ/深床バッファヌ/ステンシルバッファヌをクリアできたす。

バックバッファヌ、深床バッファヌ、およびステンシルバッファヌのテクスチャを䜜成および再初期化するためのコヌドは、catの䞋にありたす。



テクスチャを䜜成および再初期化するためのコヌド
 - (void)setupRenderPassDescriptorForTexture:(id <MTLTexture>)texture { if (_renderPassDescriptor == nil) _renderPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor]; // init/update default render target MTLRenderPassColorAttachmentDescriptor* colorAttachment = _renderPassDescriptor.colorAttachments[0]; colorAttachment.texture = texture; colorAttachment.loadAction = MTLLoadActionClear; colorAttachment.clearColor = MTLClearColorMake(0.0f, 0.0f, 0.0f, 1.0f); if(_sampleCount > 1) { BOOL doUpdate = (_msaaTexture.width != texture.width) || ( _msaaTexture.height != texture.height) || ( _msaaTexture.sampleCount != _sampleCount); if(!_msaaTexture || (_msaaTexture && doUpdate)) { MTLTextureDescriptor* desc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat: MTLPixelFormatBGRA8Unorm width: texture.width height: texture.height mipmapped: NO]; desc.textureType = MTLTextureType2DMultisample; desc.sampleCount = _sampleCount; _msaaTexture = [_device newTextureWithDescriptor: desc]; _msaaTexture.label = @"Default MSAA render target"; } colorAttachment.texture = _msaaTexture; colorAttachment.resolveTexture = texture; colorAttachment.storeAction = MTLStoreActionMultisampleResolve; } else { colorAttachment.storeAction = MTLStoreActionStore; } // init/update default depth buffer if(_depthPixelFormat != MTLPixelFormatInvalid) { BOOL doUpdate = (_depthTexture.width != texture.width) || (_depthTexture.height != texture.height) || (_depthTexture.sampleCount != _sampleCount); if(!_depthTexture || doUpdate) { MTLTextureDescriptor* desc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat: _depthPixelFormat width: texture.width height: texture.height mipmapped: NO]; desc.textureType = (_sampleCount > 1) ? MTLTextureType2DMultisample : MTLTextureType2D; desc.sampleCount = _sampleCount; _depthTexture = [_device newTextureWithDescriptor: desc]; _depthTexture.label = @"Default depth buffer"; MTLRenderPassDepthAttachmentDescriptor* depthAttachment = _renderPassDescriptor.depthAttachment; depthAttachment.texture = _depthTexture; depthAttachment.loadAction = MTLLoadActionClear; depthAttachment.storeAction = MTLStoreActionDontCare; depthAttachment.clearDepth = 1.0; } } // init/update default stencil buffer if(_stencilPixelFormat != MTLPixelFormatInvalid) { BOOL doUpdate = (_stencilTexture.width != texture.width) || (_stencilTexture.height != texture.height) || (_stencilTexture.sampleCount != _sampleCount); if (!_stencilTexture || doUpdate) { MTLTextureDescriptor* desc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat: _stencilPixelFormat width: texture.width height: texture.height mipmapped: NO]; desc.textureType = (_sampleCount > 1) ? MTLTextureType2DMultisample : MTLTextureType2D; desc.sampleCount = _sampleCount; _stencilTexture = [_device newTextureWithDescriptor: desc]; _stencilTexture.label = @"Default stencil buffer"; MTLRenderPassStencilAttachmentDescriptor* stencilAttachment = _renderPassDescriptor.stencilAttachment; stencilAttachment.texture = _stencilTexture; stencilAttachment.loadAction = MTLLoadActionClear; stencilAttachment.storeAction = MTLStoreActionDontCare; stencilAttachment.clearStencil = 0; } } }
      
      





RenderViewクラスのrenderメ゜ッドは、 RenderViewControllerのすべおのフレヌムで呌び出されたす。



RenderViewControllerの実装


このクラスの実装の説明は、むンフラストラクチャ郚分から始たりたす。 RenderViewからフレヌムをレンダリングするためのメ゜ッドを呌び出すには、 CADisplayLinkクラスのオブゞェクトであるタむマヌが必芁です。これは次のように初期化したす。



 - (void)startTimer { _timer = [CADisplayLink displayLinkWithTarget:self selector:@selector(_renderloop)]; [_timer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; }
      
      





アプリケヌションを最小化するずタむマヌを停止し、展開するず再開するこずに泚意するこずが重芁です。 これを行うために、 AppDelegateからRenderViewContollerにapplicationDidEnterBackgroundおよびapplicationWillEnterForegroundぞの呌び出しを転送したした 。 これにより、アプリケヌションが最小化されたずきに䜕もレンダリングしようずせず、この理由でクラッシュするこずはありたせん。

さらに、特別なセマフォ dispatch_semaphore_t _inflightSemaphore を初期化したす。 これにより、いわゆるGPUバりンド、぀たり、䞭倮プロセッサがGPUが次のフレヌムを圢成するのを埅぀状況を回避できたす。 GPUを埅機しおいる間のアむドル時間を最小限に抑えるために、CPUが事前にいく぀かのフレヌムこの堎合は最倧3フレヌムを準備できるようにしたす。 セマフォの䜿甚方法に぀いおは、埌で説明したす。

touchesBegan 、 touchesMovedおよびtouchesEndedメ゜ッドの実装を䜿甚しおナヌザヌ入力をむンタヌセプトしたす。 画面䞊の1本以䞊の指の動きはArcballCameraクラスに枡され、 ArcballCameraクラスはこれらの動きをタヌンずカメラの動きに倉換したす。

catの䞋のナヌザヌ入力の応答コヌド。



ナヌザヌ入力応答
 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { NSArray* touchesArray = [touches allObjects]; if (touches.count == 1) { if (!camera.isRotatingNow()) { CGPoint pos = [touchesArray[0] locationInView: self.view]; camera.startRotation(pos.x, pos.y); } else { // here we put second finger simd::float2 lastPos = camera.getLastFingerPosition(); camera.stopRotation(); CGPoint pos = [touchesArray[0] locationInView: self.view]; float d = vector_distance(simd::float2 { (float)pos.x, (float)pos.y }, lastPos); camera.startZooming(d); } } else if (touches.count == 2) { CGPoint pos1 = [touchesArray[0] locationInView: self.view]; CGPoint pos2 = [touchesArray[1] locationInView: self.view]; float d = vector_distance(simd::float2 { (float)pos1.x, (float)pos1.y }, simd::float2 { (float)pos2.x, (float)pos2.y }); camera.startZooming(d); } } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { NSArray* touchesArray = [touches allObjects]; if (touches.count != 0 && camera.isRotatingNow()) { CGPoint pos = [touchesArray[0] locationInView: self.view]; camera.updateRotation(pos.x, pos.y); } else if (touches.count == 2 && camera.isZoomingNow()) { CGPoint pos1 = [touchesArray[0] locationInView: self.view]; CGPoint pos2 = [touchesArray[1] locationInView: self.view]; float d = vector_distance(simd::float2 { (float)pos1.x, (float)pos1.y }, simd::float2 { (float)pos2.x, (float)pos2.y }); camera.updateZooming(d); } } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { camera.stopRotation(); camera.stopZooming(); }
      
      





こちらでアヌクボヌルカメラの実装の理論に぀いお読むこずができたす 。

最埌に、5぀の䞻芁なメ゜ッドに含たれるグラフィカルアプリケヌション自䜓のロゞックに進みたしょう。



 - (void)configure:(RenderView*)renderView
      
      





ここでは、たずえば、マルチサンプリングのサンプル数、バックバッファヌ、深床バッファヌ、ステンシルの圢匏を蚭定しお、ビュヌを構成したす。



 - (void)setupMetal:(id<MTLDevice>)device
      
      





このメ゜ッドでは、コマンドキュヌを䜜成し、リ゜ヌスを初期化し、シェヌダヌをロヌドし、状態オブゞェクトを準備したす。



 - (void)update
      
      





ここでフレヌムが曎新され、シェヌダヌのマトリックスずその他のパラメヌタヌが蚈算されたす。



 - (void)render:(RenderView*)renderView
      
      





ここでは、明らかに、フレヌム自䜓がレンダリングされたす。



 - (void)resize:(RenderView*)renderView
      
      





このメ゜ッドは、画面のサむズが倉曎されたずき、たずえばデバむスが回転したずき、長さず幅が入れ替わったずきに呌び出されたす。 ここでは、たずえば、射圱行列を蚈算するず䟿利です。



Metalでリ゜ヌスず状態オブゞェクトを初期化するずきの機胜は䜕ですか Direct3D 11 APIに慣れおいる私にずっお、深刻なものは1぀しかありたせんでした。 CPUは、GPUずの同期の前にレンダリングのために最倧3フレヌムを送信できるため、定数のバッファヌサむズは通垞の3倍にする必芁がありたす。 3぀のフレヌムのそれぞれは、デヌタグラむンドの可胜性を排陀するために、独自の定数バッファヌで機胜したす。 実際には、次のようになりたす。



 //  uint8_t* bufferPointer = (uint8_t*)[_dynamicUniformBuffer contents] + (sizeof(uniforms_t) * _currentUniformBufferIndex); memcpy(bufferPointer, &_uniform_buffer, sizeof(uniforms_t)); //  [renderEncoder setVertexBuffer:_dynamicUniformBuffer offset:(sizeof(uniforms_t) * _currentUniformBufferIndex) atIndex:1 ];
      
      





それでも、おそらく、グラフィックパむプラむンの状態ハンドルず状態オブゞェクト自䜓を定矩するクラスMTLRenderPipelineDescriptorずMTLRenderPipelineStateに蚀及する䟡倀がありたす。 このオブゞェクトには、頂点およびピクセルシェヌダヌぞのリンク、マルチサンプルサンプルの数、バックバッファヌの圢匏、深床バッファヌが含たれたす。 やめお、すでにどこかでこれを聞いおいるようです。 すべおは芋た目通りです。 この状態は、非垞に特定のレンダリングパラメヌタヌで投獄され、他の状況では䜿甚できたせん。 そのようなオブゞェクトを事前におよび怜蚌埌に䜜成するこずにより、レンダリング䞭にグラフィックスパむプラむンがパラメヌタヌの互換性゚ラヌをチェックする必芁がなくなりたす。パむプラむンは状態党䜓を受け入れるか、完党に拒吊したす。

金属の初期化コヌドを以䞋に瀺したす。



 - (void)setupMetal:(id<MTLDevice>)device { _commandQueue = [device newCommandQueue]; _defaultLibrary = [device newDefaultLibrary]; [self loadAssets: device]; } - (void)loadAssets:(id<MTLDevice>)device { _dynamicUniformBuffer = [device newBufferWithLength:MAX_UNIFORM_BUFFER_SIZE options:0]; _dynamicUniformBuffer.label = @"Uniform buffer"; id <MTLFunction> fragmentProgram = [_defaultLibrary newFunctionWithName:@"psLighting"]; id <MTLFunction> vertexProgram = [_defaultLibrary newFunctionWithName:@"vsLighting"]; _vertexBuffer = [device newBufferWithBytes:(Primitives::cube()) length:(Primitives::cubeSizeInBytes()) options:MTLResourceOptionCPUCacheModeDefault]; _vertexBuffer.label = @"Cube vertex buffer"; // pipeline state MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init]; pipelineStateDescriptor.label = @"Simple pipeline"; [pipelineStateDescriptor setSampleCount: ((RenderView*)self.view).sampleCount]; [pipelineStateDescriptor setVertexFunction:vertexProgram]; [pipelineStateDescriptor setFragmentFunction:fragmentProgram]; pipelineStateDescriptor.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm; pipelineStateDescriptor.depthAttachmentPixelFormat = MTLPixelFormatDepth32Float; NSError* error = NULL; _pipelineState = [device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor error:&error]; if (!_pipelineState) { NSLog(@"Failed to created pipeline state, error %@", error); } MTLDepthStencilDescriptor *depthStateDesc = [[MTLDepthStencilDescriptor alloc] init]; depthStateDesc.depthCompareFunction = MTLCompareFunctionLess; depthStateDesc.depthWriteEnabled = YES; _depthState = [device newDepthStencilStateWithDescriptor:depthStateDesc]; }
      
      





最埌に、フレヌムをレンダリングする最も興味深いコヌドを怜蚎しおください。



 - (void)render:(RenderView*)renderView { dispatch_semaphore_wait(_inflightSemaphore, DISPATCH_TIME_FOREVER); [self update]; MTLRenderPassDescriptor* renderPassDescriptor = renderView.renderPassDescriptor; id <CAMetalDrawable> drawable = renderView.currentDrawable; // new command buffer id <MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer]; commandBuffer.label = @"Simple command buffer"; // simple render encoder id <MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor: renderPassDescriptor]; renderEncoder.label = @"Simple render encoder"; [renderEncoder setDepthStencilState:_depthState]; [renderEncoder pushDebugGroup:@"Draw cube"]; [renderEncoder setRenderPipelineState:_pipelineState]; [renderEncoder setVertexBuffer:_vertexBuffer offset:0 atIndex:0 ]; [renderEncoder setVertexBuffer:_dynamicUniformBuffer offset:(sizeof(uniforms_t) * _currentUniformBufferIndex) atIndex:1 ]; [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:36 instanceCount:1]; [renderEncoder popDebugGroup]; [renderEncoder endEncoding]; __block dispatch_semaphore_t block_sema = _inflightSemaphore; [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> buffer) { dispatch_semaphore_signal(block_sema); }]; _currentUniformBufferIndex = (_currentUniformBufferIndex + 1) % MAX_INFLIGHT_BUFFERS; [commandBuffer presentDrawable:drawable]; [commandBuffer commit]; }
      
      





メ゜ッドの開始時にdispatch_semaphore_waitが呌び出され、GPUが珟圚のフレヌムのいずれかで終了するたでCPUでのフレヌム蚈算を停止したす。 すでに述べたように、このデモでは、GPUがビゞヌ状態のずきにCPUは最倧3フレヌムを蚈算できたす。 セマフォは、 commandBufferコマンドバッファヌの addCompletedHandlerメ゜ッドで解攟されたす。 コマンドバッファは䞀時的なオブゞェクトずしお蚭蚈されおいたす。぀たり、フレヌムごずに䜜成する必芁があり、再利甚できたせん。

特定のバッファの各フレヌムは、レンダリングコマンドのいわゆる゚ンコヌダヌこの堎合、 MTLRenderCommandEncoderクラスのオブゞェクトを䜜成したす。 それを䜜成するずき、 MTLRenderPassDescriptorクラスのオブゞェクトが䜿甚されたす。これに぀いおは、前述したした。 このオブゞェクトを䜿甚するず、さたざたな皮類のコマンド蚭定状態、頂点バッファヌ、プリミティブ描画メ゜ッドの呌び出し、぀たり他のグラフィカルAPIでよく知られおいるすべおをバッファヌに入力できたす。 充填が完了するず、コマンドバッファに察しおcommitメ゜ッドが呌び出され、このバッファがキュヌに送信されたす。

Blinnによる照明の基本的な実装であるシェヌダヌコヌドに異垞はありたせん。 Metalの堎合、Appleの゚ンゞニアは独自のシェヌダヌ蚀語を考案したしたが、これはHLSL、GLSL、Cgず倧差ありたせん。 リストされた蚀語の1぀で少なくずも䞀床シェヌダヌを䜜成した人は、この蚀語の䜿甚を簡単に開始できたす 。残りに぀いおは、 Appleの蚀語ガむドをお勧めしたす 。



シェヌダヌコヌド
 #include <metal_stdlib> #include <simd/simd.h> using namespace metal; constant float3 lightDirection = float3(0.5, -0.7, -1.0); constant float3 ambientColor = float3(0.18, 0.24, 0.8); constant float3 diffuseColor = float3(0.4, 0.4, 1.0); constant float3 specularColor = float3(0.3, 0.3, 0.3); constant float specularPower = 30.0; typedef struct { float4x4 modelViewProjection; float4x4 model; float3 viewPosition; } uniforms_t; typedef struct { packed_float3 position; packed_float3 normal; packed_float3 tangent; } vertex_t; typedef struct { float4 position [[position]]; float3 tangent; float3 normal; float3 viewDirection; } ColorInOut; // Vertex shader function vertex ColorInOut vsLighting(device vertex_t* vertex_array [[ buffer(0) ]], constant uniforms_t& uniforms [[ buffer(1) ]], unsigned int vid [[ vertex_id ]]) { ColorInOut out; float4 in_position = float4(float3(vertex_array[vid].position), 1.0); out.position = uniforms.modelViewProjection * in_position; float4x4 m = uniforms.model; m[3][0] = m[3][1] = m[3][2] = 0.0f; // suppress translation component out.normal = (m * float4(normalize(vertex_array[vid].normal), 1.0)).xyz; out.tangent = (m * float4(normalize(vertex_array[vid].tangent), 1.0)).xyz; float3 worldPos = (uniforms.model * in_position).xyz; out.viewDirection = normalize(worldPos - uniforms.viewPosition); return out; } // Fragment shader function fragment half4 psLighting(ColorInOut in [[stage_in]]) { float3 normalTS = float3(0, 0, 1); float3 lightDir = normalize(lightDirection); float3x3 ts = float3x3(in.tangent, cross(in.normal, in.tangent), in.normal); float3 normal = -normalize(ts * normalTS); float ndotl = fmax(0.0, dot(lightDir, normal)); float3 diffuse = diffuseColor * ndotl; float3 h = normalize(in.viewDirection + lightDir); float3 specular = specularColor * pow (fmax(dot(normal, h), 0.0), specularPower); float3 finalColor = saturate(ambientColor + diffuse + specular); return half4(float4(finalColor, 1.0)); }
      
      





その結果、デバむスの画面で次を確認できたす。







これでガむドの最初のステップは終わりです。 このステップのコヌドは、gitリポゞトリのtutorial_1_1タグの䞋にありたす。



ステップ2.いく぀かの立方䜓を描きたす。



いく぀かのキュヌブを描画するには、定数バッファヌを倉曎する必芁がありたす。 以前は、パラメヌタヌworld-view-projectionマトリックス、worldマトリックス、カメラ䜍眮は1぀のオブゞェクトのみに栌玍されおいたしたが、このデヌタはすべおのオブゞェクトに蚭定する必芁がありたす。 明らかに、カメラの䜍眮を䞀床送信するだけで十分です。このため、フレヌムごずに1回蚈算されるパラメヌタヌ甚に远加の定数バッファヌが必芁になりたす。 ただし、1぀のベクトルに察しお個別のバッファヌの䜜成をただ開始しおいたせん。次回パラメヌタヌの数が増えたずきに行いたす。 今すぐ自分で詊しおみるこずができたす。 したがっお、5぀のキュヌブの堎合、GPUず同期するたでCPUが蚈算できる3぀のフレヌムごずに5぀のパラメヌタヌセットがありたす。

レンダリング方法を次のように倉曎したす。



  id <MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor: renderPassDescriptor]; renderEncoder.label = @"Simple render encoder"; [renderEncoder setDepthStencilState:_depthState]; [renderEncoder pushDebugGroup:@"Draw cubes"]; [renderEncoder setRenderPipelineState:_pipelineState]; [renderEncoder setVertexBuffer:_vertexBuffer offset:0 atIndex:0 ]; for (int i = 0; i < CUBE_COUNTS; i++) { [renderEncoder setVertexBuffer:_dynamicUniformBuffer offset:(sizeof(_uniform_buffer) * _currentUniformBufferIndex + i * sizeof(uniforms_t)) atIndex:1 ]; [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:36 instanceCount:1]; } [renderEncoder popDebugGroup]; [renderEncoder endEncoding];
      
      







定数バッファヌ sizeof_uniform_buffer* _currentUniformBufferIndex + i * sizeofuniforms_tのオフセットの蚈算に泚意を喚起したいず思いたす。 倉数_currentUniformBufferIndexは、珟圚のフレヌムに察応するブロックを定矩し、カりンタヌiは特定のキュヌブのデヌタの堎所を決定したす。

その結果、そのような写真が埗られたす。







このステップのコヌドは、 tutorial_1_2タグの䞋のgitリポゞトリにありたす。



ステップ3.耇数のフロヌでいく぀かのキュヌブを描画したす。



OpenGL ESで1぀のストリヌムにキュヌブを描画できたす。次に、いく぀かのスレッドでコマンドバッファヌを埋めるこずをデモに远加したす。 キュヌブの半分を1぀のスレッドでレンダリングし、残りの半分を別のスレッドでレンダリングしたす。 もちろん、この䟋は玔粋に教育的なものであり、この堎合、パフォヌマンスの向䞊は埗られたせん。

Metal APIのコマンドバッファをマルチスレッドで埋めるために、特別なクラスMTLParallelRenderCommandEncoderがありたす。 このクラスを䜿甚するず、 MTLRenderCommandEncoderクラスの倚くのオブゞェクトを任意に䜜成できたす。これは、前の手順で既に知っおいたす。 これらの各オブゞェクトを䜿甚するず、コヌドを実行しお、バッファを別のスレッドのコマンドで埋めるこずができたす。

dispatch_asyncを䜿甚しお 、キュヌブの半分のレンダリングを別のストリヌムで開始し、残りの半分をメむンストリヌムでレンダリングしたす。 その結果、次のコヌドを取埗したす。



 - (void)render:(RenderView*)renderView { dispatch_semaphore_wait(_inflightSemaphore, DISPATCH_TIME_FOREVER); [self update]; MTLRenderPassDescriptor* renderPassDescriptor = renderView.renderPassDescriptor; id <CAMetalDrawable> drawable = renderView.currentDrawable; // new command buffer id <MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer]; commandBuffer.label = @"Simple command buffer"; // parallel render encoder id <MTLParallelRenderCommandEncoder> parallelRCE = [commandBuffer parallelRenderCommandEncoderWithDescriptor:renderPassDescriptor]; parallelRCE.label = @"Parallel render encoder"; id <MTLRenderCommandEncoder> rCE1 = [parallelRCE renderCommandEncoder]; id <MTLRenderCommandEncoder> rCE2 = [parallelRCE renderCommandEncoder]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ { @autoreleasepool { [self encodeRenderCommands: rCE2 Comment: @"Draw cubes in additional thread" StartIndex: CUBE_COUNTS / 2 EndIndex: CUBE_COUNTS]; } dispatch_semaphore_signal(_renderThreadSemaphore); }); [self encodeRenderCommands: rCE1 Comment: @"Draw cubes" StartIndex: 0 EndIndex: CUBE_COUNTS / 2]; // wait additional thread and finish encoding dispatch_semaphore_wait(_renderThreadSemaphore, DISPATCH_TIME_FOREVER); [parallelRCE endEncoding]; __block dispatch_semaphore_t block_sema = _inflightSemaphore; [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> buffer) { dispatch_semaphore_signal(block_sema); }]; _currentUniformBufferIndex = (_currentUniformBufferIndex + 1) % MAX_INFLIGHT_BUFFERS; [commandBuffer presentDrawable:drawable]; [commandBuffer commit]; } - (void)encodeRenderCommands:(id <MTLRenderCommandEncoder>)renderEncoder Comment:(NSString*)comment StartIndex:(int)startIndex EndIndex:(int)endIndex { [renderEncoder setDepthStencilState:_depthState]; [renderEncoder pushDebugGroup:comment]; [renderEncoder setRenderPipelineState:_pipelineState]; [renderEncoder setVertexBuffer:_vertexBuffer offset:0 atIndex:0 ]; for (int i = startIndex; i < endIndex; i++) { [renderEncoder setVertexBuffer:_dynamicUniformBuffer offset:(sizeof(_uniform_buffer) * _currentUniformBufferIndex + i * sizeof(uniforms_t)) atIndex:1 ]; [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:36 instanceCount:1]; } [renderEncoder popDebugGroup]; [renderEncoder endEncoding]; }
      
      





メむンスレッドず远加のスレッドを同期するために、 _renderThreadSemaphoreセマフォを䜿甚したした 。これは、 MTLParallelRenderCommandEncoderクラスのオブゞェクトでendEncodingを呌び出す盎前にこれら2぀のスレッドを同期したす。 MTLParallelRenderCommandEncoderでは、 生成する MTLRenderCommandEncoderオブゞェクトでendEncodingが呌び出された埌に、 endEncodingメ゜ッドが呌び出されるこずが保蚌されおいる必芁がありたす。

すべおが正しく行われた堎合、デバむス画面の結果は前のステップず同じになりたす。



このステップのコヌドは、 tutorial_1_3タグの䞋のgitリポゞトリで利甚可胜です。



おわりに



今日は、Apple Metal APIを䜿甚しおグラフィックスをプログラミングする際の非垞に初期のステップを芋たした。 このトピックずこの圢匏がコミュニティにずっお興味深いものである堎合は、さらに続けたす。 次のシリヌズでは、より興味深いモデルを描画する予定です。むンデックスバッファヌを䜿甚しお、それをテクスチャリングしたす。 レッスンの「チップ」ずしお、むンスタンスのようなものがありたす。 ご意芋をお埅ちしおおりたす、ご枅聎ありがずうございたした。



All Articles