aptpod Advent Calendar 2019 18æ¥ç®ãæ
åœãããŠããã ããŸã äžé ãšç³ããŸãã æšå¹Žã ARKit2.0ãåããããªãã®èŠãŠããæ¹åãèšé²ãå¯èŠåããã㢠ãšããèšäºã§åå ãããŠããã ããŸããŠã ä»å¹ŽãiOSç³»ã§èšäºãæžãããŠããã ãããšæããŸããiOSã¢ããªãšã³ãžãã¢ã®ã¿ãªãããããããé¡ãããŸãã ããŠãä»åã®ãã©ãŒã«ã¹ããå
容ã§ããããã çããããMetalã£ãŠäœ¿ã£ãŠãããããïŒ æšå¹Žã®èšäºã§ã¯ ARKit ã SceneKit ãšãã£ããã¬ãŒã ã¯ãŒã¯ã䜿çšããŠããŸãããããããã¡ãã ã UIKit ãªã©ã«å«ãŸãããã¥ãŒã³ã³ãã³ããã¢ãã¡ãŒã·ã§ã³ãã€ã¡ãŒãžãªã©ã®ã»ãšãã©ã¯ Metal ãã³ã¢ã«äœãããŠããŸãã Metalã¯ã Apple補åã«æèŒãããGPUãžã¢ã¯ã»ã¹ãæäŸããAPI ã§åºæ¬çã«UIã«é¢ããéšåã»ãšãã©ã«äœ¿ãããŠããããã§ãã ä»åã¯ãã®Metalã«ãã©ãŒã«ã¹ããMetalã®å®è£
ã³ã¹ããäžãã MetalKit ãå©çšããŠãã¢ã¢ããªãäœããããšæããŸãã â»Metalã®äºåç¥èãæ¬²ãããšèšãæ¹ã¯ãããŸãšããããèšäºããããŸããã®ã§ ãã¡ã ãã芧ãã ããã ä»åäœãç© å®éã ãããMetal(ã ã)ã§äœãäœã£ãŠã¿ããããã ããšãªã£ãå Žåã«çããã¯ãããšäœãæãã€ãã§ããããïŒ è©Šãã«Appleã§ ãµã³ãã« ãèŠãŠã¿ãŸãããã Creating and Sampling Textures Using a Render Pipeline to Render Primitives Reflections with Layer Selection åçŽã«ç»åã MetalView äžã«æç»ããç©ã ã£ããã ã°ã©ãã£ã¯ã¹APIéçºã®å
¥éã§ã¯ãããã¡ã§ããåçŽã«äžè§åœ¢ã®æç»ã®ãµã³ãã«ã äœã åã3Dã®ã¢ãã«ãæç»ããæ¹æ³ã ã£ããçãããŸãããã€ãã€ãäœãäœãããããããšç§ã¯ãªããŸããã ãããã調ã¹ãŠããäžã§ãMetalKit ã§ã¯å®è£
æ¹æ³ããç¥ã£ãŠããã° Textureäžã«ãã¯ã»ã«åäœã§ç°¡åã«è²ãå¡ãããšãã§ãããšåãããŸããã®ã§ã ã¿ã€ãã«ã«ããããã«ç°¡æç㪠ãã€ã³ãããŒã« ãäœãããšæããŸãïŒä»å㯠MTKView( MetalKitã«å«ãŸããView ) ã Canvas ãšãªããŸãã æç»ãŸã§ã®æµã MTKViewã®ãã¬ãŒã ãµã€ãºã«å¿ããŠè§£å床(瞊暪ã®ãã¯ã»ã«æ°)ãæ±ºããã ãã¯ã»ã«æ°ã«å¿ããé
å(ãã¯ã¹ãã£ãžæž¡ãçšã®ãããã¡)ãäœæã ç»é¢ãã¿ãããããããã®åº§æšãšåçã®é
åã®ã€ã³ããã¯ã¹ã«éžæãããŠããè²æ
å ±ãæ¿å
¥ã 宿çã« MetalKit ãã draw ã®ãªã¯ãšã¹ã( MTKViewDelegate ã®ã³ãŒã«ããã¯)ãåŒã°ããã®ã§ããã®ã¿ã€ãã³ã°ã§çšæãããããã¡ãšæžã蟌ã察象ã®ãã¯ã¹ãã£ãæž¡ãã ã·ã§ãŒãã«ãŠæç»ããè²æ
å ±ãé©åã«ãã¯ã¹ãã£ã«æž¡ãã ãšãã£ãæµããšãªããŸãã å®è£
éå§ Metalãã»ããã¢ãã ã§ã¯ãã€ãéãã®æãã§ãããžã§ã¯ããäœããŸãã MetalKit Viewãèšçœ®ããŸãããã ViewController.swift import UIKit import MetalKit class ViewController : UIViewController , MTKViewDelegate { override func viewDidLoad () { super .viewDidLoad() // Do any additional setup after loading the view. self .setupMetalView() } // MARK:- Metal View @IBOutlet weak var metalView : MTKView ! var mDevice = MTLCreateSystemDefaultDevice() var mCommandQueue : MTLCommandQueue ! var mComputePiplineState : MTLComputePipelineState ! var metalViewDrawableSize : CGSize ? = nil var targetMetalTextureSize : CGSize = CGSize.zero var bufferWidth : Int = - 1 var mTextureBuffer : MTLBuffer ? func setupMetalView () { guard let library = self .mDevice?.makeDefaultLibrary() else { return } // Register Texture Shader guard let kernel = library.makeFunction(name : "computeTexture2d" ) else { return } guard let computePipeline = try? self .mDevice?.makeComputePipelineState(function : kernel ) else { return } self .mComputePiplineState = computePipeline self .metalView.device = self .mDevice self .metalView.delegate = self self .metalView.framebufferOnly = false // â ããããªããšXcode11以éã§ã¯èœã¡ãŸã self .mCommandQueue = self .mDevice?.makeCommandQueue() } //MARK:- MTKViewDelegate func mtkView (_ view : MTKView , drawableSizeWillChange size : CGSize ) {} func draw ( in view : MTKView ) {} } äžæŠãããªæãã§ViewControllerãæžããŠã¿ãŸããã IBOutlet ã§å®çŸ©ããŠãã metalView ã¯ViewControllerã«èšçœ®ãããã®ã§ããã®èŸºãã§åºãŠããŠãã mDevice ã ã£ããã libary ã computePipeline ãããã¯ãäœæ³ã®ãããªãã®ãªã®ã§ å
ã»ã©ã®ãªã³ã¯ ã ã£ãã ãã¡ã ããåç
§ãã ããã â»ããªãåæ©çãªæã¯æ·±ãã¯èªããŸããã ããã§èšå®ããã·ã§ãŒãã®èšå® computeTexture2d ã¯åŸã»ã©è§£èª¬ããŸãã åºæ¬çã«ã¯ MTKViewDelegateã«å«ãŸãã draw ã®ã³ãŒã«ããã¯ã«ãŠ GPUãžå¿
èŠãªãã¯ã¹ãã£ããããã¡æ
å ±ãæž¡ãäºã«ãªããŸãã ããã§ã¯ãããããã¯å
ã»ã©ç€ºããæç»ãŸã§ã®æµãã«ãã£ãŠé²ããŠãããŸãã 1.MTKViewã®ãã¬ãŒã ãµã€ãºã«å¿ããŠè§£å床(瞊暪ã®ãã¯ã»ã«æ°)ãæ±ºãã ViewController.swift // MARK:- viewDidAppear override func viewDidAppear (_ animated : Bool ) { self .updateMetalViewDrawableSize() } // MARK:- viewWillTransition override func viewWillTransition (to size : CGSize , with coordinator : UIViewControllerTransitionCoordinator ) { coordinator.animate(alongsideTransition : nil ) { (_) in self .updateMetalViewDrawableSize() } } ãªãŒãã¬ã€ã¢ãŠãã§æå®ããViewã®ãã¬ãŒã ãµã€ãºã決ãŸã(å€ãã)ã¿ã€ãã³ã°ã£ãŠå€§äœãã®2ã€ã§ãããã ãã®ã¿ã€ãã³ã°ã§èšçœ®ãã MTKView ã®å®éã®ãã¬ãŒã ãµã€ãºãããã¯ã»ã«æ°ã決ããŸãããã ViewController.swift let kMetalTextureHeightDotSize : Int = 512 ... func updateMetalViewDrawableSize () { // Viewã®å®éã®ãµã€ãºãã瞊暪æ¯ãåèã«é«ãã®ããããµã€ãºããå¹
ã®ããããµã€ãºãæ±ãã var width = Int(ceil(( self .metalView.frame.width /self .metalView.frame.height) * CGFloat(kMetalTextureHeightDotSize))) // // ããã«ã¯åŸã»ã©æžãæãããããŸã(1) // self .targetMetalTextureSize = CGSize(width : width , height : kMetalTextureHeightDotSize ) self .log( "drawableSize: \(self.metalView.drawableSize) , frame: \(self.metalView.frame) => targetMetalTextureSize: \(self.targetMetalTextureSize) - MetalView" ) self .metalView.drawableSize = self .targetMetalTextureSize } ä»åã¯é«ãã®ãã¯ã»ã«æ°ã 512 ã«åºå®ã«ãããããã瞊暪æ¯ã§æ±ããäºã«ããŸããã ãã¯ã»ã«æšª = ãã¬ãŒã å¹
/ ãã¬ãŒã é«ã * ãã¯ã»ã«çžŠ ãããŠãæ±ããè§£å床ã MTKView ã® drawableSize ã« æç»ãããµã€ãºãšããŠæå®ããŠãããŸãããã ã§ãããã®ãŸãŸã§ã¯äžååã§ãåŸã»ã© (1) ã®å
容ã説æããŸãã 2. ãã¯ã»ã«æ°ã«å¿ããé
å(ãã¯ã¹ãã£ãžæž¡ãçšã®ãããã¡)ãäœæ ViewController.swift //MARK:- MTKViewDelegate func mtkView (_ view : MTKView , drawableSizeWillChange size : CGSize ) { self .log( "drawableSizeWillChange \(view.drawableSize) => size: \(size) , frame: \(self.metalView.frame) , targetSize: \(self.targetMetalTextureSize) - MTKViewDelegate" ) guard !self .targetMetalTextureSize.equalTo(CGSize.zero) else { return } if self .metalViewDrawableSize != nil { guard !self .metalViewDrawableSize ! .equalTo( self .targetMetalTextureSize) else { // ååãšåãå€ã ã£ãå Žåã¯æŽæ°ããªã return } } self .metalViewDrawableSize = self .targetMetalTextureSize self .bufferWidth = Int( self .metalViewDrawableSize ! .width) self .setupMetalBuffer() } å
ã»ã© MTKView ã«è§£å床ãæå®ããŸããããã¡ããã»ãããããš MTKViewDelegate å
ã® drawableSizeWillChange ãã³ãŒã«ãããŸãã ãã®ã¿ã€ãã³ã°ã§ãæå®ããè§£å床ã MTKView ã«åæ ãããŸãã®ã§ããããã¡ã®äœæãè¡ããŸãããã ViewController.swift let kMetalTextureClearColor : simd_float4 = [ 255 / 255.0 , 255 / 255.0 , 255 / 255.0 , 1.0 ] ... func setupMetalBuffer () { guard let device = self .mDevice else { return } let colors = [simd_float4]. init (repeating : kMetalTextureClearColor , count : self.bufferWidth * kMetalTextureHeightDotSize) let bufferLength = colors.count * MemoryLayout < simd_float4 > .stride // <- ããèŠãã§ã㯠self .mTextureBuffer = device.makeBuffer(bytes : UnsafeRawPointer (colors), length : bufferLength , options : .cpuCacheModeWriteCombined) } ããèŠãã§ã㯠ãšã³ã¡ã³ãããŠããç®æã«æ³šç®ããŠãã ãããé
åã®æ°ã§ãããã¡ãµã€ãºãæ±ããŠããåŠçã§ãä»åã¯ããŸãé¢ä¿ãªãã§ãããMetalã®ãããã¡ç®¡çã§ã¯ ããªãéèŠ ã§ãã ãã¡ã ã§è©³ãã解説ãããŠããŸããMeltaåŽã®ãããã¡ãµã€ãºãšCPUåŽã®ãããã¡ãµã€ãºãéã£ãŠããŸãèŠå ã«ãªããŸãã®ã§çè§£ã¯å¿
é ã§ãã åæå€(äœãæžãããŠããªãè²æ
å ±(çœ))ãšçžŠæšªã®é·ãã§é
åã宣èšã MTLDevice ã§ãããã¡ãäœæããŸãã 3. ç»é¢ãã¿ãããããããã®åº§æšãšåçã®é
åã®ã€ã³ããã¯ã¹ã«éžæãããŠããè²æ
å ±ãæ¿å
¥ ViewController.swift // ã¿ããã€ãã³ããéå§ããã override func touchesBegan (_ touches : Set <UITouch> , with event : UIEvent ?) { guard let touch = event?.touches( for : self.metalView )?.first else { return } let point = touch.location( in : self.metalView ) self .lastPoint = nil self .drawCanvas(point : point ) } // ã¿ããäœçœ®ãç§»åãã override func touchesMoved (_ touches : Set <UITouch> , with event : UIEvent ?) { guard let touch = event?.touches( for : self.metalView )?.first else { return } let point = touch.location( in : self.metalView ) self .drawCanvas(point : point ) } // ã¿ãããçµäºãã override func touchesEnded (_ touches : Set <UITouch> , with event : UIEvent ?) { guard let touch = event?.touches( for : self.metalView )?.first else { return } let point = touch.location( in : self.metalView ) self .drawCanvas(point : point ) } // ã¿ããããã£ã³ã»ã«ããã override func touchesCancelled (_ touches : Set <UITouch> , with event : UIEvent ?) {} ã¿ããã€ãã³ããååŸããŸãã UIViewController ã¯æšæºã§ override ã§ã¿ããã€ãã³ããååŸã§ããŠç°¡åã§ããã ã¿ãããããäœçœ®ã MetalView ã«å¯Ÿããã¿ããã€ãã³ãã®ã¿åŠçããŠããŸãã ViewController.swift func drawCanvas (point : CGPoint ) { let x : Int = Int(ceil((point.x /self .metalView.frame.width) * CGFloat( self .bufferWidth))) let y : Int = Int(ceil((point.y /self .metalView.frame.height) * CGFloat(kMetalTextureHeightDotSize))) var color : simd_float4 = [ self .isRed ? 1.0 : 0.0 , self .isGreen ? 1.0 : 0.0 , self .isBlue ? 1.0 : 0.0 , 1.0 ] let dataSize = MemoryLayout < simd_float4 > .stride if let ptr = self .mTextureBuffer?.contents() { if 0 <= x, x < self .bufferWidth, 0 <= y, y < kMetalTextureHeightDotSize { let index = (x * kMetalTextureHeightDotSize) + y memcpy(ptr.advanced(by : index * dataSize), & color, dataSize) } } } ã¿ããããã座æšãå
ã«ãçšæãããããã¡ã«è²æ
å ±ãå
¥ããŠãããŸãã isRed ã isGreen ã isBlue ãšããé
ç®ãåºãŠããŸãããè²æ
å ±ã倿Žã§ããæ§ã«ãã©ã°ã宣èšããŠããŸãã å
ã»ã©äœæããMetalçšã®ãããã¡ã® contents() ãšèšããã¡ã³ã¯ã·ã§ã³ã§ãããã¡ã®å
é ã®ãã€ã³ã¿ãã¢ãã¬ã¹æ
å ±ãååŸããŸãããã®åŠçã¯Cèšèªã£ãœãã§ãã memcpy ã§è²æ
å ±ãã¢ãã¬ã¹ãæå®ããŠãããã¡ã«ã³ããŒããŸãããã ããã§ãããã¡ã®æºåã¯å®äºã§ãã 4. MetalKit ãã宿çã«åŒã°ããdrawã®ãªã¯ãšã¹ã(MTKViewDelegateã®ã³ãŒã«ããã¯)ã®ã¿ã€ãã³ã°ã§ãçšæãããããã¡ãšæžã蟌ã察象ã®ãã¯ã¹ãã£ãæž¡ã ViewController.swift let kMetalThreadGroupCount : Int = 16 ... func draw ( in view : MTKView ) { guard let drawable = view.currentDrawable else { return } guard let commandBuffer = self .mCommandQueue.makeCommandBuffer() else { return } guard let textureBuffer = self .mTextureBuffer else { return } let computeEncoder = commandBuffer.makeComputeCommandEncoder() computeEncoder?.setComputePipelineState( self .mComputePiplineState) let texture = drawable.texture // æžã蟌ã察象ã®ãã¯ã¹ãã£ãã»ãã computeEncoder?.setTexture(texture, index : 0 ) // GPUã«æž¡ããã¯ã¹ãã£çšãããã¡ãã»ãã computeEncoder?.setBuffer(textureBuffer, offset : 0 , index : 1 ) let threadGroupCount = MTLSizeMake(kMetalThreadGroupCount, kMetalThreadGroupCount, 1 ) let threadGroups = MTLSizeMake(Int( self .targetMetalTextureSize.width) / threadGroupCount.width, Int( self .targetMetalTextureSize.height) / threadGroupCount.height, 1 ) computeEncoder?.dispatchThreadgroups(threadGroups, threadsPerThreadgroup : threadGroupCount ) computeEncoder?.endEncoding() commandBuffer.present(drawable) commandBuffer.commit() commandBuffer.waitUntilCompleted() } ä»åã®èãšãªãåŠçã§ãããå
ã»ã©çšæãããããã¡ãMetalåŽã«æž¡ããŸãã Metalã«éãã GPUããã°ã©ãã³ã° ã§ã¯äžè¬çãªåŠçãããŒã1ã€èª¬æããŸãã åŒçšå
(â»NVIDIAããã°) GPU 㯠CPU ãšã¯éãåºæ¬çã«ã¯ 䞊å ã§åŠçãå®è¡ãããŸãã äžã®å³ã®äŸã§èšããšãåŠçããã颿° saxpy ãåèµ·çã«åŒã³åºããåãã¹ã¬ããå
ã§é£ç¶ããŠåŠçããŠããã®ã«å¯Ÿã Cuda ã® saxpy ã¯éåæã«åŠçãã cuda å
ã®ã¡ã¢ãªã«èšç®çµæãåæ ããŠããŸãã ä»å kMetalThreadGroupCount ã§æå®ããŠããæ°å€ã䞊ååŠçãè¡ãããã¹ã¬ããã®æ°ã«ãªããŸãã commandBuffer( MTLCommandBuffer ) ãš computeEncoder( MTLComputeCommandEncoder ) ã䜿ã£ãŠã ãã¯ã¹ãã£ãšãããã¡ãMetal( GPUåŽ )ã«æž¡ããŠãããŸãããã 5. ã·ã§ãŒãã«ãŠæç»ããè²æ
å ±ãé©åã«ãã¯ã¹ãã£ã«æž¡ã ã·ã§ãŒãã®ã»ããã¢ãã ã·ã§ãŒã㯠å¥ãã¡ã€ã« ãšãªããŸããæ¡åŒµå㯠.metal ã§èšèªã¯ C++ ã§ããã PaintShader.metal #include <metal_stdlib> using namespace metal; /* * |--|--|--|--| * | 0| 4| 8|12| * | 1| 5| 9|13| * | 2| 6|10|14| * | 3| 7|11|15| * |--|--|--|--| */ kernel void computeTexture2d(texture2d<half, access::write> output [[texture( 0 )]], device float4 *color_buffer [[buffer( 1 )]], uint2 gid [[thread_position_in_grid]]) { int h = output.get_height(); int index = (gid.x * h) + gid.y; half r = color_buffer[index].x; half g = color_buffer[index].y; half b = color_buffer[index].z; half a = color_buffer[index].w; output.write(half4(r, g, b, a), gid); } ã¯ããããã§Metalã®ã»ããã¢ããã§åºãŠãã computeTexture2d ãåºãŠããŸãããã åŒæ°ã® [[texture(0)]] ã [[buffer(1)]] ã®æ°å€ã¯å
ã»ã© computeEncoder ã§ã»ããããéã«æå®ãã ã€ã³ããã¯ã¹ ã§ãã gid [[thread_position_in_grid]] ã¯æå®ããã¹ã¬ããã®æ°ã«ãã£ãŠäžŠååããéã®çŸåšã® ã€ã³ããã¯ã¹(é
åã®ã¢ãã¬ã¹ã®ãããªç©)æ
å ± ãèšå®ãããŠããŸãã ãã® gid ã®äœçœ®ãæžã蟌ããã£ã³ãã¹ã®ãã¯ã»ã«äœçœ®æ
å ±ãšãªããŸãã X座æšãšYåº§æšæ
å ±ã§ã€ã³ããã¯ã¹æ
å ±ãç®åºããæž¡ãããããã¡ããè²æ
å ±ãæœåºããŸãã ãããŠãå
ã»ã©ã®ãã¯ã¹ãã£ã«è²æ
å ±ãæž¡ããŠãããã° MTKViewäžã«çµµãæç»ãããŸãïŒ(ãããã) ãïŒ è¬ã®ééãããïŒãšæã£ãæ¹éãã§ããã ãã®ãŸãŸã§ã¯ãã£ã³ãã¹å
šäœã«å¯ŸããŠè²ãå¡ãã€ã¶ãããšãã§ããªãå ŽåããããŸãã(â»ã§ããå Žåããã) ViewController.swift func updateMetalViewDrawableSize () { // Viewã®å®éã®ãã¬ãŒã ãµã€ãºãã瞊暪æ¯ãåèã«é«ãã®ããããµã€ãºããå¹
ã®ããããµã€ãºãæ±ãã var width = Int(ceil(( self .metalView.frame.width /self .metalView.frame.height) * CGFloat(kMetalTextureHeightDotSize))) // æžãæããå
容(1) ////// // æå®ããThreadGroupCountã§å²ãåããªããã°ãªããªãçºèª¿æŽããã let v : Int = width % kMetalThreadGroupCount if v > 0 { width -= v } // æžãæããå
容(1) /////// self .targetMetalTextureSize = CGSize(width : width , height : kMetalTextureHeightDotSize ) self .log( "drawableSize: \(self.metalView.drawableSize) , frame: \(self.metalView.frame) => targetMetalTextureSize: \(self.targetMetalTextureSize) - MetalView" ) self .metalView.drawableSize = self .targetMetalTextureSize ããã§å
ã»ã© 1. ã§ åŸã»ã©æžãæãããããŸã(1) ãšèšè¿°ããå
容ã«ãªããŸãã kMetalThreadGroupCount ã§å²ã£ãå Žåã®äœããå¹
ããåŒãããšã§æ¹åã§ããŸãã ã€ãŸãã GPUåŽã®ã¹ã¬ããæ°ã§å²ãåããªãç©ã¯æç»ããããªã ãšããæ³šæãã¹ããã€ã³ãããããŸãã Appleã®ããã¥ã¡ã³ã ã«ãŠ ThreadGroup ãš GirdSize ã«ã€ããŠã®èšè¿°ã¯ãããŸãããä»åã¯ãªãã¹ããããã¡ã¯äœåã«åããããŸããåºããªãããã«ããæ¹ã管çãæ¥œã ã£ãã®ã§ãã®æ¹æ³ãåã£ãŠããŸãã +αãã®1ãããã§ã¯ç·ãåŒããªãã®ã§åŒããæ§ã«ãã ãã®ãŸãŸã§ã¯ã¿ããã€ãã³ããçºçããã¿ã€ãã³ã°ã§ãã£ã³ãã¹ã«è²ãïŒç¹å¡ãã ã䌌åã£ãŠããŸãã®ã§ç·ãåŒããããã«å¯Ÿå¿ããŸãã ViewController.swift // ïŒç¹éã®ç·ãåŒãçºã®ãã€ã³ãäžèЧãååŸãã // åè(ãã¬ãŒã³ãã ã®ã¢ã«ãŽãªãºã ): https://ja.wikipedia.org/wiki/%E3%83%96%E3%83%AC%E3%82%BC%E3%83%B3%E3%83%8F%E3%83%A0%E3%81%AE%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0 func getLinePoints (p0 : CGPoint , p1 : CGPoint ) -> [CGPoint] { var points = [CGPoint]() var x0 : Int = Int(p0.x) var y0 : Int = Int(p0.y) let x1 : Int = Int(p1.x) let y1 : Int = Int(p1.y) let dx : Int = Int(abs(p1.x - p0.x)) // DeltaX let dy : Int = Int(abs(p1.y - p0.y)) // DeltaY let sx : Int = (p1.x > p0.x) ? 1 : - 1 // StepX let sy : Int = (p1.y > p0.y) ? 1 : - 1 // StepT var err = dx - dy while true { if x0 >= 0 , y0 >= 0 { points.append(CGPoint(x : x0 , y : y0 )) } if x0 == x1, y0 == y1 { break } let e2 = 2 * err if e2 > - dy { err -= dy x0 += sx } if e2 < dx { err += dx y0 += sy } } return points } ïŒç¹éã®ç·ãåŒãã¢ã«ãŽãªãºã 㯠ãã¬ãŒã³ãã ã®ã¢ã«ãŽãªãºã ãæåã§ããã ãŠã£ãããã£ã¢ ã«ãã£ãå
容ãå©çšãããŠãã ã ããŸããã ãã¬ãŒã³ãã ã®ã¢ã«ãŽãªãºã (Bresenham's line algorithm)ã¯ã äžããããå§ç¹ãšçµç¹ã®éã«é£ç¶ããç¹ã眮ããè¿äŒŒçãªçŽç·ãåŒãããã®ã¢ã«ãŽãªãºã ã ãã¬ãŒã³ãã ã®ç·åæç»ã¢ã«ãŽãªãºã ããã¬ãŒã³ãã ã¢ã«ãŽãªãºã ãšãã ã³ã³ãã¥ãŒã¿ã®ãã£ã¹ãã¬ã€ã«çŽç·ãæç»ããã®ã«ãã䜿ããã æŽæ°ã®å æžç®ãšãããã·ããã®ã¿ã§å®è£
ã§ããã®ã§å€ãã®ã³ã³ãã¥ãŒã¿ã§äœ¿çšå¯èœã§ããã ã³ã³ãã¥ãŒã¿ã°ã©ãã£ãã¯ã¹ã®åéã®æåæã®ã¢ã«ãŽãªãºã ã®1ã€ã§ããã ãããè¥å¹²æ¡åŒµãããšãåãæãããšãã§ãã ããã§ç°¡åãªã¡ã¢æžããããã«ã¯äœ¿ãããã§ããã +αãã®2ãæããç·ã®å€ªããå€ããããããã«ãã ãã€ã³ãããŒã«ãšããã°ç·ã®å€ªããå€ãããŸãããããªã®ã§ãã¡ããåçŸããããšæããŸãã å
ã»ã©ã®ãŠã£ãããã£ã¢ã®èšè¿°ã«ãã£ãããã« ãã¬ãŒã³ãã ã®ã¢ã«ãŽãªãºã ãå©çšããã°åãæãããšãã§ããŸãã ãã¡ã ã«ãã£ãããã°ã©ã ãåèã«ãããŠããã ããŸããã åèã«ãããã®ããåã®äžãå¡ãã€ã¶ããããã«æ¹è¯ããŸãããç¹ã«ããã©ãŒãã³ã¹ã¯æèããŠããªãã®ã§ãã¡ãã¯ã容赊ãã ViewController.swift // äžå¿ç¹ãšååŸããçžãæãçºã®ãã€ã³ãäžèЧãååŸãã // åè(ãã¬ãŒã³ãã åæç»ã®ã¢ã«ãŽãªãºã ): http://dencha.ojaru.jp/programs_07/pg_graphic_09a1.html func getCircleFillPoints (center : CGPoint , radius : Int ) -> [CGPoint] { var points = [CGPoint]() let centerX : Int = Int(center.x) let centerY : Int = Int(center.y) var cx : Int = 0 var cy : Int = radius var d : Int = 2 - 2 * radius // Left Top var ltx : Int = 0 var lty : Int = 0 // Right Top var rtx : Int = 0 var rty : Int = 0 // Left Bottom var lbx : Int = 0 var lby : Int = 0 // Right Bottom var rbx : Int = 0 var rby : Int = 0 // Top(0, R) var vx : Int = cx + centerX var vy : Int = cy + centerY if vx >= 0 , vy >= 0 { points.append(CGPoint(x : vx , y : vy )) } // Bottom(0, -R) vx = cx + centerX vy = - cy + centerY if vx >= 0 , vy >= 0 { points.append(CGPoint(x : vx , y : vy )) } // Right(R, 0) vx = cy + centerX vy = cx + centerY if vx >= 0 , vy >= 0 { points.append(CGPoint(x : vx , y : vy )) } // Left(-R, 0) vx = - cy + centerX vy = cx + centerY if vx >= 0 , vy >= 0 { points.append(CGPoint(x : vx , y : vy )) } while true { if d > - cy { cy -= 1 d += 1 - 2 * cy } if d <= cx { cx += 1 d += 1 + 2 * cx } guard cy > 0 else { break } // Right Bottom (Bottom To Right) rbx = cx + centerX rby = cy + centerY if rbx >= 0 , rby >= 0 { points.append(CGPoint(x : rbx , y : rby )) // 0 ~ 90 } // Left Bottom (Bottom To Left) lbx = - cx + centerX lby = cy + centerY if lbx >= 0 , lby >= 0 { points.append(CGPoint(x : lbx , y : lby )) // 90 ~ 180 } // Left Top (Top To Left) ltx = - cx + centerX lty = - cy + centerY if ltx >= 0 , lty >= 0 { points.append(CGPoint(x : ltx , y : lty )) // 180 ~ 270 } // Right Top (Top To Right) rtx = cx + centerX rty = - cy + centerY if rtx >= 0 , rty >= 0 { points.append(CGPoint(x : rtx , y : rty )) // 270 ~ 360 } // äžååã¯äžéšåããå·Šå³ã«ãäžååã¯ããéšåããå·Šå³ã«äŒžã³ãŠãã //print("[\(ltx), \(lty)], [\(rtx), \(rty)], [\(lbx), \(lby)], [\(rbx), \(rby)]") // Y軞ã¯å·Šå³åãå°ç¹ãæããŠããäºããäžçåãäžååã§X軞ã®å·Šç«¯ããå³ç«¯ã«ãã€ã³ãã远å ããäºã§åãå¡ã朰ããŸãã for i in lbx ... rbx { if i >= 0 , rby >= 0 { points.append(CGPoint(x : i , y : rby )) } } for i in ltx ... rtx { if i >= 0 , lty >= 0 { points.append(CGPoint(x : i , y : lty )) } } } // äžå¿ç· for i in centerX - radius ... centerX + radius { if i >= 0 , centerY >= 0 { points.append(CGPoint(x : i , y : centerY )) } } return points } åã®æç»ã¯å·Šå³ã®ç«¯ããäžãããã¯äžæ¹åã«ã«ãŒãã§æç»ããŠããã®ã§ã巊端ããå³ç«¯ã«ã«ãŒãã䌞ã°ããŠäžã®è²æ
å ±ãæ¿å
¥ããŠããŸãã ãããã¡ã«è²æ
å ±ãå
¥ãã颿°ã®å
šå®¹ ViewController.swift func drawCanvas (point : CGPoint ) { let x : Int = Int(ceil((point.x /self .metalView.frame.width) * CGFloat( self .bufferWidth))) let y : Int = Int(ceil((point.y /self .metalView.frame.height) * CGFloat(kMetalTextureHeightDotSize))) var color : simd_float4 = [ self .isRed ? 1.0 : 0.0 , self .isGreen ? 1.0 : 0.0 , self .isBlue ? 1.0 : 0.0 , 1.0 ] let dataSize = MemoryLayout < simd_float4 > .stride if let ptr = self .mTextureBuffer?.contents() { if 0 <= x, x < self .bufferWidth, 0 <= y, y < kMetalTextureHeightDotSize { let index = (x * kMetalTextureHeightDotSize) + y self .log( "draw color[[ \(x) , \(y) ] => \(index) ]: \(color) " ) memcpy(ptr.advanced(by : index * dataSize), & color, dataSize) } } // çŸåšå°ç¹ãšååå°ç¹ã®éã«ç·ãå
¥ããŸã var linePoints : [CGPoint] = [CGPoint]() let newPoint = CGPoint. init (x : x , y : y ) defer { self .lastPoint = newPoint } // ååå€ãä¿æããŠãã if let last = self .lastPoint, ! last.equalTo(newPoint) { // ïŒç¹éã®ç·ãåŒãçºã®ãã€ã³ãäžèЧãååŸã远å ãã linePoints.append(contentsOf : self.getLinePoints (p0 : last , p1 : newPoint )) } else { // â»åãç¹ãæç»ããäºã«ãªãã®ã§ããŸãåŠçå¹çã¯è¯ãç¡ãã§ãã説æäžæç»ããå€ãå
¥ããŸãã linePoints.append(newPoint) } if self .pointSize > 1 { // åæç» var cirlePoints : [CGPoint] = [CGPoint]() for p in linePoints { cirlePoints.append(contentsOf : self.getCircleFillPoints (center : p , radius : self.pointSize / 2 )) } linePoints.append(contentsOf : cirlePoints ) } if linePoints.count > 0 , let ptr = self .mTextureBuffer?.contents() { for p in linePoints { let x = Int(p.x) let y = Int(p.y) guard x < self .bufferWidth, y < kMetalTextureHeightDotSize else { continue } let index = (x * kMetalTextureHeightDotSize) + y memcpy(ptr.advanced(by : index * dataSize), & color, dataSize) } } } ã§ãããã® Metalã§ç°¡åãªãã€ã³ãããŒã«ãäœã£ãŠã¿ãŸãã #iOS #Metal #MetalKit #Texture #2D #Demo pic.twitter.com/yGmzw647Up — aptueno (@aptueno) December 16, 2019 ãŸãšã MetalKitããµãã ãã«äœ¿ã£ãã¢ããªã±ãŒã·ã§ã³ã£ãŠçµæ§å®è£
ã³ã¹ããšåŠç¿ã³ã¹ããçžãŸã£ãŠãªããªã䜿ããªãããšãå€ãæ°ãããŸãã ãã®èšäºãã¿ãŠMetalKitã䜿ã£ãã¢ããªã±ãŒã·ã§ã³ãæ©èœã®æ¡ãæµ®ããã æ¹ãã©ãããŒã§ãããã æ£çŽç§ã¯ãã€ããã©ãŒãã³ã¹ã§çµµãæããã°ãªãã§ãã§ããæ°ãããŠããã®ã§å°ãã§ãåèã«ãªã£ããå¬ããã§ãã ä»åã®ãã¢ã¯åååæ§githubã§å
¬éããŸãã®ã§ããã£ããåãããããã芧ãã ããã å°ãããªã¥ãŒããŒãªèšäºã«ãªã£ãããšæããŸããã芧ããã ãããããšãããããŸããã ã»iOS-MetalPaintDemo