euphonictechnologies’s diary

Haskell超初心者の日記です。OCamlが好きです。

follow us in feedly

Haskell - GHC for iOS : OpenGLで3Dグラフィックスを表示してみる、の準備

動機

前回はHaskellで書いたコードをghc-iosでコンパイルして、そのバイナリをObjective-Cで書かれたレイヤから呼び出すことができました。

Haskell - GHC for iOS : iOSアプリをHaskellで開発する - euphonictechnologies’s diary

ghc-iosのGithubにはOpenGLを使ったテストアプリケーションがある。というわけで、OpenGLで遊ぶのはできそうです。OpenGLを使った簡単なデモアプリケーションを動かしてみようと思います。

それによってiOS上でSwiftの支配から真に解放された、Haskellerが唯一自由を感じる事の出来る場所を作ることができます。Swift sucks!

まずはXcodeのワークスペースを作って設定する

前回同様、Xcode上にワークスペースを作ります。

今回はOpenGLを使うのでXcode6の場合、iPhone->Gameを選んで、OpenGL ESの開発テンプレートを選択します。

f:id:euphonictechnologies:20150301180504p:plain

で、こんな感じでプロジェクトの設定をしてください。

f:id:euphonictechnologies:20150301180525p:plain

出来上がったプロジェクトにHaskellが使える設定をしていきます。

  • HaskelliOS.xcconfigをプロジェクトに追加する f:id:euphonictechnologies:20150301180808p:plain
  • PROJECT -> Build Settings -> Architectures -> Build Active Architecture OnlyをすべてNOに f:id:euphonictechnologies:20150126205617p:plain
  • Info -> Configurations を両方HaskelliOSに変更 f:id:euphonictechnologies:20150126205627p:plain

一回全体をビルドして設定に問題がないことを確認しましょう。

一度ビルドしてシミュレータ、もしくは実機上で動作を確認して下さい。 この動作しているアプリケーションが今回の目標です。

Objective-Cのコードを見渡して移植の方針を立てる

つまり、今回の目標はこのテンプレートで作られているObjective-CのコードをHaskell上に移植することです。当然全てをHaskellに移すことはできないので、描画部分やシェーダのコンパイル、設定部分等です。View自体はObjective-Cのコードが必要です。

GameViewController.mに今回作りたいものがほぼ全て入っているはずです。この中身は大きく分けて

  • 初期化処理
    • その中にシェーダのコンパイル、リンク、アタッチ処理を含む
  • 描画処理 - 1フレームごとに呼ばれてアップデートする部分

の2種類のコードが有ります。

手っ取り早く、今回の目標としているGameViewController.mを示しましょう。

//
//  GameViewController.m
//  OGLTest
//
//  Created by Kodama Yoshinori on 2/11/15.
//  Copyright (c) 2015 EuphonicTeck. All rights reserved.
//

#import "GameViewController.h"
#import <OpenGLES/ES2/glext.h>
#import "Main_stub.h"

@interface GameViewController () {
}
@property (strong, nonatomic) EAGLContext *context;

- (void)setupGL;
- (void)tearDownGL;
@end

@implementation GameViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];

    if (!self.context) {
        NSLog(@"Failed to create ES context");
    }
    
    GLKView *view = (GLKView *)self.view;
    view.context = self.context;
    view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
    
    [self setupGL];
}

- (void)dealloc
{    
    [self tearDownGL];
    
    if ([EAGLContext currentContext] == self.context) {
        [EAGLContext setCurrentContext:nil];
    }
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];

    if ([self isViewLoaded] && ([[self view] window] == nil)) {
        self.view = nil;
        
        [self tearDownGL];
        
        if ([EAGLContext currentContext] == self.context) {
            [EAGLContext setCurrentContext:nil];
        }
        self.context = nil;
    }

    // Dispose of any resources that can be recreated.
}

- (BOOL)prefersStatusBarHidden {
    return YES;
}

- (void)setupGL
{
    [EAGLContext setCurrentContext:self.context];

    loadShaders();

    glEnable(GL_DEPTH_TEST);
}

- (void)tearDownGL
{
    [EAGLContext setCurrentContext:self.context];
}

#pragma mark - GLKView and GLKViewController delegate methods

- (void)update
{
}

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
    extern void (*drawFrame)(double, double, double);
    drawFrame(self.timeSinceLastUpdate, self.view.bounds.size.width, self.view.bounds.size.height);
}

@end

ずいぶんと今ワークスペースにあるものよりスッキリしているのがわかると思います。ご覧のとおりView自体の初期化はObjective-Cのレイヤに残っています。ただ、シェーダの初期化を行うloadShadersという関数と、描画を行うdrawFrameによってその他大部分のコードが置き換えられています。今回の目標はこのコードを動くようにすることです。

Haskell開発の準備

今度はIntelliJ上でHaskellの開発をしていきます。

まずは普通にNew Project -> Haskellを選んで f:id:euphonictechnologies:20150301182622p:plain

次に場所ですが、すべてを一つのリポジトリにまとめたいので、OGLWHaskellTestのXcodeのプロジェクトルートの直下にIntelliJのワークスペースのフォルダが有るようにしたいです。フォルダを新しく作成して、その中を選択してください。名前はどうでもいいですが、私は末尾にHaskellとつけて区別するようにしています。 f:id:euphonictechnologies:20150301182630p:plain

そうするとワークスペースが作られます。IntelliJがXcodeで作ったgitリポジトリを検知してそこに加えてくれますので、素直にAdd rootしておきましょう。 f:id:euphonictechnologies:20150301182706p:plain

ここにコードをガシガシ書いていきます、が、もう少しだけ下ごしらえをします。

cabal sandboxとcabal init

当然コードはghc-iosでコンパイルするのでこの作業フォルダ上のcabal sandboxは使われません。しかし、テストコードなど別に実機じゃなくてもいい部分を走らせるのにcabal testすることがあるので、cabal sandbox initとcabal configureしておきましょう。

$ cabal sandbox init
Writing a default package environment file to
~/<path to your workspace>/OGLWHaskellTest/OGLWHaskellTestHaskell/cabal.sandbox.config
Creating a new sandbox at
~/<path to your workspace>/OGLWHaskellTest/OGLWHaskellTestHaskell/.cabal-sandbox
$ cabal configure
Resolving dependencies...
Configuring OGLWHaskellTestHaskell-1.0...

さらに後ほど使うパッケージを予めインストールしておきましょう。

$ cabal install HUnit matrix test-framework test-framework-hunit vector

コンパイルのためにユーティリティを作っておく

ビルドを私は今のところコンソールでやっています。なので、ビルド用の簡単なコマンドラインをshファイルに入れておくと便利です。

ghc-ios src/*.hs && cp -v ./*.a ./Main*.h ../OGLWHaskellTest

としておくとビルドと成功時にはコピーまでやってくれます。これをbuild.shと名づけてIntelliJのワークスペースのルート直下に保存していてください。

下ごしらえ後はこんな感じ

f:id:euphonictechnologies:20150301183645p:plain

こんな感じです。

早速コードを入れて動かしてみる。

この記事はすべての作業後に書いているので、すでにすべてのコードが手元にあります。なので、早速それを動かしてみます。後ほどそのコードの内容を追いかけてみたいと思います。

f:id:euphonictechnologies:20150301192901g:plain

こんな感じで青い立方体がくるくるする実用的なアプリケーションになっています。

コードはGithubにあります。

ysnrkdm/OGLWHaskellTest · GitHub

ビルドの仕方

まずはビルドの仕方ですが、IntelliJのワークスペースルート直下にbuild.shがあるので、実行するとsrc/以下の.hsファイルをすべてコンパイルしてバイナリとMain_stub.hを作ってくれます。バイナリのファイル名は最後に指定した.hsファイルの名前が使われるので、おそらくMatrixHelper.aという実態とはかけ離れた名前がつくはずです。もしかすると z_Haskell.hsなるからのソースコードを作ってコンパイルさせると名前をいじることができるかもしれません。

$ ./build.sh
[1 of 3] Compiling MatrixHelper     ( src/MatrixHelper.hs, build/arm/MatrixHelper.o )
[2 of 3] Compiling LoadShaders      ( src/LoadShaders.hs, build/arm/LoadShaders.o )
[3 of 3] Compiling Main             ( src/Main.hs, build/arm/Main.o )
Linking build/arm/MatrixHelper.a ...
[1 of 3] Compiling MatrixHelper     ( src/MatrixHelper.hs, build/i386/MatrixHelper.o )
[2 of 3] Compiling LoadShaders      ( src/LoadShaders.hs, build/i386/LoadShaders.o )
[3 of 3] Compiling Main             ( src/Main.hs, build/i386/Main.o )
Linking build/i386/MatrixHelper.a ...
./MatrixHelper.a -> ../OGLWHaskellTest/MatrixHelper.a
./Main_stub.h -> ../OGLWHaskellTest/Main_stub.h

ご覧のとおり、Haskellサイドは3つのファイルが有ります。それぞれMain.hsと行列計算用ヘルパMatrixHelper.hsとシェーダーをロードするためのLoadShaders.hsです。

今回はここまで

少し記事が長くなってきましたので、今回はここまでで、次のエントリでそれぞれのソースコードの内容を追いかけてみたいと思います。コードは非常に小規模で比較的見通しよく書いたつもりなので、興味のある方は読み進まれれば私のやったことなどまるっとお見通しだと思います。私はルンバの修理などで忙しいので、エントリをわけます。すみません。エア切腹でわびます。