ãã¢ã¢ããªã±ãŒã·ã§ã³
ãã¢ãå®è¡ããã«ã¯ããããŒãXcode 6ãããã³A7ããã»ããµãŒãæèŒããããã€ã¹ïŒiPad Air 2013ããã³iPhone 5S以éïŒãå¿ èŠã§ãã æ®å¿µãªãããMetalã®ã¢ããªã±ãŒã·ã§ã³ããšãã¥ã¬ãŒã¿ã§èµ·åããããšã¯ã§ããŸããã æåŸã®å¶éã¯ãiOSã®éçºè ããã°ã©ã ãžã®æå¹ãªãµãã¹ã¯ãªãã·ã§ã³ãå¿ èŠã§ããããšãæå³ããŸãã ãããã¯åçŽãªå¥œå¥å¿personçãªäººã®ããã®å°ããªèŠä»¶ã§ã¯ãªãããšãç解ããŠããããã¡ãããäžèšã®ãããããè³Œå ¥ããããšããå§ãããŸããã ãã ããå¿ èŠãªãã®ããã¹ãŠæãããã«æã圢æãããŠããå Žåã¯ããªããžããªããã®åå²ç¹ãšãMetalã§ã®ç¬èªã®å®éšã«ã€ããŠç¥ãããšãã§ããŸãã
ããã«ããã®ããã¥ã¢ã«ãèªããšãã¯ããã¢ã³ãŒãã䞊è¡ããŠç¢ºèªããããšã匷ããå§ãããŸããããã«ãããäœãèµ·ãã£ãŠããã®ããç解ãããããªããŸãã
ãšã³ããªãŒ
ç§ã¯å ¬åŒæçš¿ã翻蚳æçš¿ã«è¿œå ããããšãæ¯æããŠããŸããã®ã§ãç°¡åãªèšèã§Metalã®æ§è³ªã«ã€ããŠè©±ããŸãããã Appleã¯ãMetalãOpenGL ESãããã¯ãŒã«ãªçç±ã«ã€ããŠå€ãã®ããšã話ããŸããïŒ ããã«ã€ããŠã¯å°ã説æããããŸããïŒã ããããã¹ãŠãããç§ã¯2ã€ã®éèŠãªå©ç¹ã ããéžã³åºããŸãïŒ
- Metalã¯ãGPUã®ã³ãã³ãã®ã©ã³ã¿ã€ã æ€èšŒã®éãå€§å¹ ã«åæžããã¢ããªã±ãŒã·ã§ã³ã®ããŒãæãŸãã¯ã³ã³ãã€ã«æã«æ€èšŒã転éããŸããã ãã®ããããã£ãã·ã¥ãããç¶æ ãªããžã§ã¯ããçŸããŸããã ççŽã«èšã£ãŠããã®ã¢ã€ãã¢ã¯æ°ãããã®ã§ã¯ãªããDirect3D 10ã§ç¶æ ãªããžã§ã¯ããèŠãŸããããããã£ãŠãMetal APIã§ã¯ãã°ã©ãã£ãã¯ãã€ãã©ã€ã³ã®ã»ãŒãã¹ãŠã®ç¶æ ãäºåæºåããŠãã£ãã·ã¥ã§ããŸãã
- 䞊åèšç®ããã³ã³ãã³ããããã¡ã®å å¡«ã®å¯èœæ§ã ããã§ã®ã¢ã€ãã¢ã¯ã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ã䜿çšããŠã°ã©ãã£ãã¯ã¹ãããã°ã©ãã³ã°ããéã®éåžžã«åæã®ã¹ããããèŠãŸããã ãã®ãããã¯ãšãã®åœ¢åŒãã³ãã¥ããã£ã«ãšã£ãŠèå³æ·±ããã®ã§ããå Žåã¯ãããã«ç¶ããŸãã 次ã®ã·ãªãŒãºã§ã¯ãããèå³æ·±ãã¢ãã«ãæç»ããäºå®ã§ããã€ã³ããã¯ã¹ãããã¡ãŒã䜿çšããŠãããããã¯ã¹ãã£ãªã³ã°ããŸãã ã¬ãã¹ã³ã®ããããããšããŠãã€ã³ã¹ã¿ã³ã¹ã®ãããªãã®ããããŸãã ãæèŠããåŸ ã¡ããŠãããŸãããæž èŽããããšãããããŸããã