新パソコンが届いていろいろ設定しているうちに寒くなってしまいました。もう師走です。

Windowsパソコンというと自分は15年くらい前にWindowsXPを使っていてそれ以来です。あとはLinuxとMacを交互に使うような感じでした。

WSLの導入

Windows10といえばWSL(Windows Subsystem for Linux)なのでさっそく導入しました。当初はLinuxとのDualブートを考えていましたがあまり推奨されていないようなので現時点では見送ってこのパソコンが古くなったら丸ごとLinuxマシンにしてしまおうかと思います。

WSLでとりあえずの目標は(1)Emacsがシェルとの連携も含めて動くこと、(2)ブログを運用する自作スクリプトが動くこと、(3)OpenGLのコードを書いて動かすことなどです。

まずGUIアプリを使うのにフリーのVcXsrvというXサーバーを使います(他の選択肢もあります)。日本語入力はfcitx-mozcを使うということにして、この辺はネットで検索すればそれほど難しくはないので、上の(1)(2)(3)をやっていきます。

(2)はまったく問題なくできました。Linuxで書いたコードがそのまま使えています。スクリプト言語ならほとんど動くだろうと思います。

(1)Emacsについてはメタキー、ハイパーキーの設定で時間を使いました。フリーソフトのChange Keyとxmodmapでなんとか不満のない形にできたというところです。シェルとの連携も問題ありません。さすがLinuxです。

(3)はOpenGLに限らずハードウェアに近い部分はまだまだ熟していないという感じです。例えば音を鳴らすmpg123は今のところ動きません。それと肝心のOpenGLですがglxinfoを叩いてみると:

$ glxinfo | grep version

server glx version string: 1.4
client glx version string: 1.4
GLX version: 1.4
OpenGL version string: 1.4 (4.6.13558 Compatibility Profile Context 26.20.11016.1)

これはマズい。Windowsネイティブならversionは4.6であります。

msys2/mingw64を導入してみることに

Windowsネイティブなバイナリが動くUNIX-likeなシステムとしてmsys2/mingw64が良いらしいので入れることにします。

pacmanコマンドで色々なパッケージを入れていきますが、一部のコマンドラインツール(git,make,cmake等)はmsys2のもの(/usr/bin/以下にインストールされる)、コンパイラやguiアプリ(gcc,g++,Emacs等)はmingw64のものを使う(/mingw64/bin/以下にインストールされる)ことにします。

msys2とmingw64で同じパッケージがあったりするので混乱します。たとえばEmacsは両方に存在します。混乱します。

一番困ったのがmingw64のEmacs上でシェルコマンドが使えない。というかパスの通し方がわからない。msys2のルートディレクトリ/はWindnowsからみるとc:/msys64/である(多分)。それをターミナル上からは/に見えるように細工してあるのだろうと思う。ところがmingw64のEmacsのルートディレクトリはc:/になるので、Emacsのパス設定と矛盾してしまうようです。その解決法がわからない。

ちなみにmsys2のターミナル上のEmacsなら普通にシェルコマンドは使えます。ただ自分はターミナル上のEmacsは全然好きではなくそれならVimのほうがいいと思う。

それでOpenGLのコードはVimで書けばいいじゃないかと割りきることにしました。普段の生活はWSLのEmacsですることにします。

それでOpenGLは結局動いたのか

動きました。

mingw64のgcc,glfw3,pkg-configはインストール済みとします。

https://www.glfw.org/docs/latest/quick.htmlにあるコードを若干修正(#include "gl.c"を追加するだけです)して試してみましょう。ちなみに最近はglewの開発が止まっているようでgladというのを使います。リンクから飛んで必要なファイルを生成します。APIはglのVersion4.6(自分の環境に合わせること)を選択、Extensionsは空のまま、Optionsはdebugにチェックを入れて、GENERATEボタンをクリックすればダウンロードできます。

ダウンロードして解凍するとincludesrcというディレクトリができます。includeディレクトリにあるgladディレクトリを/mingw64/includeへコピーします。srcにあるgl.cをホームディレクトリにとりあえず置きます。

以下で使うlinmath.hというのはhttps://github.com/glfw/glfw/blob/master/deps/linmath.hにあります。これもとりあえずホームディレクトリに置きます。

以下をホームディレクトリにsimple.cという名前で保存します。

#include <glad/gl.h>
#include <GLFW/glfw3.h>
#include "gl.c"
#include "linmath.h"
#include <stdlib.h>
#include <stdio.h>

static const struct
{
    float x, y;
    float r, g, b;
} vertices[3] =
{
    { -0.6f, -0.4f, 1.f, 0.f, 0.f },
    {  0.6f, -0.4f, 0.f, 1.f, 0.f },
    {   0.f,  0.6f, 0.f, 0.f, 1.f }
};

static const char* vertex_shader_text =
"#version 110\n"
"uniform mat4 MVP;\n"
"attribute vec3 vCol;\n"
"attribute vec2 vPos;\n"
"varying vec3 color;\n"
"void main()\n"
"{\n"
"    gl_Position = MVP * vec4(vPos, 0.0, 1.0);\n"
"    color = vCol;\n"
"}\n";
static const char* fragment_shader_text =
"#version 110\n"
"varying vec3 color;\n"
"void main()\n"
"{\n"
"    gl_FragColor = vec4(color, 1.0);\n"
"}\n";

static void error_callback(int error, const char* description)
{
    fprintf(stderr, "Error: %s\n", description);
}

static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
    if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
        glfwSetWindowShouldClose(window, GLFW_TRUE);
}

int main(void)
{
    GLFWwindow* window;
    GLuint vertex_buffer, vertex_shader, fragment_shader, program;
    GLint mvp_location, vpos_location, vcol_location;
    glfwSetErrorCallback(error_callback);
    
    if (!glfwInit())
        exit(EXIT_FAILURE);
        
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
    window = glfwCreateWindow(640, 480, "Simple example", NULL, NULL);
    
    if (!window)
    {
        glfwTerminate();
        exit(EXIT_FAILURE);
    }
    
    glfwSetKeyCallback(window, key_callback);
    glfwMakeContextCurrent(window);
    gladLoadGL(glfwGetProcAddress);
    glfwSwapInterval(1);
    
    // NOTE: OpenGL error checks have been omitted for brevity
    
    glGenBuffers(1, &vertex_buffer);
    glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    vertex_shader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertex_shader, 1, &vertex_shader_text, NULL);
    glCompileShader(vertex_shader);
    fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragment_shader, 1, &fragment_shader_text, NULL);
    glCompileShader(fragment_shader);
    program = glCreateProgram();
    glAttachShader(program, vertex_shader);
    glAttachShader(program, fragment_shader);
    glLinkProgram(program);
    mvp_location = glGetUniformLocation(program, "MVP");
    vpos_location = glGetAttribLocation(program, "vPos");
    vcol_location = glGetAttribLocation(program, "vCol");
    glEnableVertexAttribArray(vpos_location);
    glVertexAttribPointer(vpos_location, 2, GL_FLOAT, GL_FALSE,
                          sizeof(vertices[0]), (void*) 0);
    glEnableVertexAttribArray(vcol_location);
    glVertexAttribPointer(vcol_location, 3, GL_FLOAT, GL_FALSE,
                          sizeof(vertices[0]), (void*) (sizeof(float) * 2));
                          
    while (!glfwWindowShouldClose(window))
    {
        float ratio;
        int width, height;
        mat4x4 m, p, mvp;
        glfwGetFramebufferSize(window, &width, &height);
        ratio = width / (float) height;
        glViewport(0, 0, width, height);
        glClear(GL_COLOR_BUFFER_BIT);
        mat4x4_identity(m);
        mat4x4_rotate_Z(m, m, (float) glfwGetTime());
        mat4x4_ortho(p, -ratio, ratio, -1.f, 1.f, 1.f, -1.f);
        mat4x4_mul(mvp, p, m);
        glUseProgram(program);
        glUniformMatrix4fv(mvp_location, 1, GL_FALSE, (const GLfloat*) mvp);
        glDrawArrays(GL_TRIANGLES, 0, 3);
        glfwSwapBuffers(window);
        glfwPollEvents();
    }
    
    glfwDestroyWindow(window);
    glfwTerminate();
    exit(EXIT_SUCCESS);
}

ターミナルでホームディレクトリにいるとしてコンパイルします。

~$ gcc -o simple simple.c `pkg-config --static --libs glfw3`

実行:

~$ ./simple

三角形が回転していれば成功。

ちょっとファイルサイズが大きすぎる

自分の環境だとsimple.exeのファイルサイズが764Kもある。これはちょっと大きすぎると思うのでgl.cをdllにしてみる。

~$ gcc -c gl.c
~$ gcc gl.o -o libgladgl.dll -shared

上のsimple.cで#include "gl.c"の行はコメントアウト(あるいは削除)して保存します。今度は次のようにコンパイルします。

~$ gcc -o simple simple.c libgladgl.dll `pkg-config --static --libs glfw3`

これでできたsimple.exeのサイズは344Kで半分以下になりました(これでもまだ大きすぎると思う)。最適化オプションをつければもうすこしマシかもしれない。

感想

WSLは思っていたよりも悪くないという印象です。Linuxで使っていたドットファイルもほとんど修正なしで使えます。しかしWindowsとEmacsは相性がよくないようでEmacs上で全部やろうとすると大変です。あたらめてVimの強さもわかります。Vimもこれを期にぼちぼち使ってみようと思います。

Windows本体がLinuxのカーネルを使ってしまえばいいじゃないかと思うのですがそれはただのLinuxなので(それはそれで歓迎したいけれど)、WSLがどういう進化をしていくのか期待しています。