Dbank0208
facebook twitter GitHub
Dbank0208

MetalライブリーかMetalの可能性





こんにちは、フリーランスエンジニアの永田です。


本日は、Metal Advent Calendar 2017カレンダー25日目に参加させていただきました。

はじめは、やはりMetalFileだろ!と思い、自分が調べていた内容を公開しようと思いましたが、
かなり表現が細かく、呪文のような設定だったので、今回はプライベートにしました。
少しだけ公開します。こんな感じMetalFile群を解読しています。
//グリッドセルの8つの隣にあるベクトルの集合
//constantプログラムとは、Swiftのletのようなものです。プログラムで変化することのない一定の値を持つデータのことです。
//点やベクトルなどを表す型で、内積と外積を表す計算方法。
constant float2 kNeighborDirections[] =
{
    float2(-1, -1), float2(-1, 0), float2(-1, 1),
    float2( 0, -1), /*  center  */ float2( 0, 1),
    float2( 1, -1), float2( 1, 0), float2( 1, 1),
};


typedef struct {

    //float4はひとつのピクセルの色をつける4成分のベクトルです。4成分の項目順は次の通りです。 red; green; blue; alpha.の値をセットして、
    //float4と0と1の頂点配列要素を持った値がout.positionです。
    float4 position [[position]];
    float2 texCoords;
} FragmentVertex; 

//頂点設定をして、FragmentVertexをセットします。lighting_vertexはラベルです。
vertex FragmentVertex lighting_vertex(device VertexIn *vertexArray [[buffer(0)]],
                                      uint vertexIndex [[vertex_id]])
{   
    //vertexArray頂点とは頂点とプリミティブ型の動的配列を囲む非常に簡単なラッパーです。
    //[vertexIndex]の配列はプリミティブ内部の頂点番号。 0が最初の頂点です。
    
    //ここのメソッドは型を設定しています。
    FragmentVertex out;
    //vertexArray配列の要素vertexIndexにfloat4 position [[position]];<-2つの要素設定と 0, 1の要素の設定をしています。
    out.position = float4(vertexArray[vertexIndex].position, 0, 1);

    //FragmentVertexのStruct型に、vertexArray配列の要素vertexIndexにacked_float2型を代入しています。
    out.texCoords = vertexArray[vertexIndex].texCoords;
    return out;
}

自分では、MetalFileの設定方法を調べて、実装しています。


今回は、自分が作成したライブラリーのVersionUpの紹介をいたします。



MetalReFresh
こちらのMetalReFreshはAppleのobjecのモデルを完全にSwiftで再現した後に,オリジナルに改造しました。
こちらがSwift化したソースです。こちらもAppleのobjecのコードのみでSwiftに書きました。
MetalBasicTessellation
MetalGameOfLife
objecやMetalファイルはポインタ、アドレスで構成されているのでSwiftに表現する部分が少し大変だったりします。


VersionUpの内容は、上スワイプをしたら、UIがクルクルループする処理です。

TouchViewController.Swift

import UIKit import MetalReFresh class TouchViewController: UIViewController { //画像をMetalに読み込ませるライブラリー内で設定しています。 static var intCount = Int() var pull = PullToObject() var timer: Timer! var toucheSet : Set! override func viewDidLoad() { super.viewDidLoad() } //画面をタッチすると起動します。 override func touchesBegan(_ touches: Set, with event: UIEvent?) { swipeMethod() //タッチした座標を取得しています。 toucheSet = touches } @objc private func update(tm: Timer) { //時間のストップ timer.invalidate() //値の設定をしています。数値の代入。ライブラリー内の分岐に使用します。 pull.imageCount = TouchViewController.intCount //タッチした場合の座標を設定します。 pull.metalPosition(point: toucheSet.first!.location(in: self.view), view: self.view) } private func swipeMethod() { //上スワイプ設定 let directions: UISwipeGestureRecognizerDirection = .up //メソッド handleSwipeの設定 let gesture = UISwipeGestureRecognizer(target: self, action:#selector(handleSwipe(sender:))) //ジェスチャーに渡しています。 gesture.direction = directions //一本での動作 gesture.numberOfTouchesRequired = 1 //画面にadd self.view.addGestureRecognizer(gesture) } @objc func handleSwipe(sender: UISwipeGestureRecognizer) { //1秒後にself.updateを渡しています。 timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(self.update), userInfo: nil, repeats: true) } }


PullToObject.swift

//
//  PullToObject.swift
//  sampleAnmation
//
//  Created by nagatadaisuke on 2017/09/27.
//  Copyright © 2017年 永田大祐. All rights reserved.
//


import UIKit
import Foundation
import MetalKit

public class PullToObject:NSObject{
    
    //画像をMetalに読み込ませるライブラリー内で設定しています。
    open var imageCount = 0
    open var metalView: MTKView!
    
    var alphaView = MTKView()
    //AAPLRenderer()クラスの呼び出しをしています。ここは、くるくる回るUIを設定しているクラスです。
    var aAPLRenderer = AAPLRenderer()

    //AAPLTessellationPipelineクラスの呼び出しです。AAPLTessellationPipeline()クラスは背景のUIです。
    var tessellationPipeline =  AAPLTessellationPipeline()

    //時間の設定。
    var timer: Timer!
    var updateAnimation: Timer!
    
    var viewSet : UIView!
    
    //時間でメソッドを呼ぶ設定をしています。
    public func timerSet(view:UIView?)
    {
        guard view != nil else {
            
            return
            
        }
        
        self.viewSet = view
        
        timer = Timer.scheduledTimer(timeInterval: 5.0,
                                     target: self,
                                     selector: #selector(self.update),
                                     userInfo: nil, repeats: true)
        
        timer.fire()
        
        updateAnimation = Timer.scheduledTimer(timeInterval: 0.5,
                                               target: self,
                                               selector: #selector(self.updateAnimation(tm:)),
                                               userInfo: nil, repeats: true)
        
        updateAnimation.fire()
    }
    
    //時間のストップのMetalのUIを初期化<->破棄しています。
    public func invalidate()
    {
        if self.metalView != nil {
            
        self.metalView.removeFromSuperview()
        self.metalView = nil
        
        self.alphaView.removeFromSuperview()
            
        timer.invalidate()
        updateAnimation.invalidate()
            
        }
    }
    
    @objc private func update(tm: Timer)
    {
  
        if self.metalView == nil {
            //クルクル回るUIに頂点データを入れてますが、そのデータの最大値が、88です。
            //その値を設定する為に11に数値を明示しています。
            ScreenAnimation.screenAnimation = 11
            self.setupView()
           
        }else{
            //画面を削除、初期化の意図
            self.metalView.removeFromSuperview()
            self.metalView = nil
            //時間のストップ
            timer.invalidate()
            updateAnimation.invalidate()
            alphaView.alpha = 0
            
        }
        
    }
    
    @objc private func updateAnimation(tm: Timer)
    {
        
        alphaViewSetting()
        
    }
    
    private func setDesign()
    {
        //Table画面で表示される背景UIの設定
        self.alphaView.frame = CGRect(x:0,y:-UIScreen.main.bounds.size.height/4,
                                      width:UIScreen.main.bounds.size.width,
                                      height:UIScreen.main.bounds.size.height)
        
        self.alphaView.backgroundColor = UIColor.black
        self.alphaView.alpha = 0.3
        self.viewSet.addSubview(self.alphaView)
        
        /クルクル回るUI設定
        self.metalView = MTKView()
        self.viewSet.addSubview(metalView)
        self.metalView.device = MTLCreateSystemDefaultDevice()
        self.metalView.colorPixelFormat = MTLPixelFormat.bgra8Unorm
        self.metalView.clearColor =  MTLClearColorMake(0, 0, 0, 1)
        self.metalView.isUserInteractionEnabled = true
        self.metalView.frame = self.viewSet.frame
        
        self.aAPLRenderer.imageCount = imageCount
    }
    
    private func setupView()
    {
        
        if self.metalView == nil {
            
            setDesign()
            //aAPLRendererクラスのinstanceWithViewメソッドにViewAnimationクラスのanimateImageメソッドの引数にmetalViewを設定してアニメーションのViewを渡しています。
            self.aAPLRenderer.instanceWithView(view: ViewAnimation.viewAnimation.animateImage(target: self.metalView) as! MTKView)
            
        }
    }
    //上記に記載している内容と同じです。
    public func metalPosition(point:CGPoint,view:UIView)
    {
        ScreenAnimation.screenAnimation = 11
        self.viewSet = view
         setDesign()
        self.aAPLRenderer.instanceWithView(view: ViewAnimation.viewAnimation.animateSet(target: self.metalView,point: point) as! MTKView)
    }
    
    private func alphaViewSetting()
    {
        //ノードとその子ノードに関連付けられたアクションとアニメーションを実行するかどうかを決定するブール値。
        alphaView.isPaused = true
        //ビューの内容を無効にするメッセージにビューが応答するかどうかを示すブール値。
        alphaView.enableSetNeedsDisplay = true
        alphaView.sampleCount = 4
        
        tessellationPipeline = tessellationPipeline.initWithMTKView(mtkView: alphaView )
        tessellationPipeline.wireframe = false
        
        alphaView.draw()
       
    }
}



一つ問題


上スワイプの挙動の際に,,,
MetalReFresh_Example[5049:2953876] [CAMetalLayerDrawable present] should not be called after already presenting this drawable. Get a nextDrawable instead.
というメッセージがログで出ます。
Execution of the command buffer was aborted due to an error during execution. Ignored (for causing prior/excessive GPU errors) (IOAF code 4)
連続しずぎると上記のログで、少し固まります。これはパイプライン内に、頂点bufferのキューを排出する前に次のbufferがやってきている状態なんだと思います。
nextDrawableを使えば良いと思います。以前に使用してソースもGithubに展開しました。

RenderingPipeLine
RenderingPipeLineのアーキテクチャを実装すれば、固まる現象は回避できるかもしれませんが、
これは冬休みにでもTryします。
Metalプログラミングは、堤さんがわかりやすく紹介してくれてます。
なので、自分はMetalのソースコードを展開させていただきました。全部Swift4です。
MetalをTryするような凄いプログラマーに久々に会いたいと思っています。会うときは会うので、それまでまた、修行します。

今年も一年ありがとうございました。来年も宜しくお願い致します。



AppStore リリースしているアプリの紹介

進捗カメラ

進捗カメラ

こちらは個人として5月にリリースしたアプリケーションで製作期間は約1ヶ月です。
機能は二つのカメラとアルバム機能、画像を他のアプリケーションと共有機能です。
一つ目のカメラは輪郭抽出です。二つ目のカメラは底辺、高さを計測します。
アルバム機能では画面のサイズと角度を計測できます。情報を他のアプリケーションと共有できます。

今後の予定


モバイルパワーと通信パワーが上がるにつれて、3Dプログラミングが増えてくるのはないかと考えています。
iosはさらに飛躍的なアプリケーションを作成するには、フルスタックエンジニアになる必要があると思ってます。
Swiftの基盤があるので、PHPもPythonも比較的にわかりやすいので引き続き頑張りたいと思います。





今年も一年ありがとうございました。来年も宜しくお願い致します。