Rust で OpenGL などのグラフィックスライブラリを利用する方法

4 min read読了の目安(約3900字

はじめに

Rust で OpenGL や OpenGL ES, EGL などのグラフィックスライブラリ(本家本元はC言語のライブラリ)を利用する方法をまとめています。

Foreign Function Interface (ffi) を利用する問題点

ご存知のように、Rust から他の言語のライブラリを利用する場合には Foreign Function Interface (ffi) を利用してバインディングする必要があります。しかし、数が少なかったり、自作ライブラリであれば自分で ffi での対応も可能ですが、バイディング対象の数が多かったり、常に更新が入るようなライブラリが対象になると途端に作業が大変になります。

gl_generator とは?

OpenGL などのグラフィックスライブラリのバインディングのクレートは複数存在しますが、gl_generator というクレータが一番よく使われている様子です。また、Mozilla が開発している Firefox の レンダリングエンジンである Servo でもこのライブラリが使われています(servo / gleam)。本家本元の Mozilla が使っているライブラリということで結構信頼性は高いのではないでしょうか?

gl_generator はソースコード自動生成ツール

gl_generator はグラフィックスライブラリのバインディングソースコードというよりも、バインディングしたソースコードを自動生成するツールです。そのため、使い方としては「ビルドの過程でソースコードを自動生成してビルドする」、「gl_generatorを利用して事前にソースコードを生成し、それを利用する」の2パターンが有り得ます。今回は前者の方法を紹介します。

gl_generator の使い方

バインディング対象の独自クレートを作成

今回は OpenGL の独自クレートを作成する方法を紹介します。

独自 gl クレートの作成

対象のプロジェクトのルートディレクトリで以下を実行し、gl クレートライブラリを作成します。

$ mkdir lib
$ cd lib
$ cargo new --vcs none --lib gl

lib/gl/src/build.rs の作成

このソースコードの中に OpenGL のバインディングソースコードを自動生成するためのコードを書いていきます。以下のようなコードを書くことで、ビルド時にtargetディレクトリ以下の所定の場所にgl_bindings.rsファイルが生成されます。

extern crate gl_generator;

use gl_generator::{Api, Fallbacks, Profile, Registry};
use std::env;
use std::fs::File;
use std::path::Path;

fn main() {
    // OpenGL 3.3 bindings
    let dest = env::var("OUT_DIR").unwrap();
    let mut file_gl = File::create(&Path::new(&dest).join("gl_bindings.rs")).unwrap();
    let gl_extensions = [
        "GL_APPLE_client_storage",
        "GL_APPLE_fence",
        "GL_APPLE_texture_range",
        "GL_APPLE_vertex_array_object",
        "GL_ARB_blend_func_extended",
        "GL_ARB_buffer_storage",
        "GL_ARB_copy_image",
        "GL_ARB_get_program_binary",
        "GL_ARB_invalidate_subdata",
        "GL_ARB_texture_rectangle",
        "GL_ARB_texture_storage",
        "GL_EXT_debug_marker",
        "GL_EXT_texture_filter_anisotropic",
        "GL_KHR_debug",
        "GL_KHR_blend_equation_advanced",
        "GL_KHR_blend_equation_advanced_coherent",
        "GL_KHR_blend_equation_advanced_coherent",
        "GL_ARB_shader_storage_buffer_object",
    ];
    let gl_reg = Registry::new(
        Api::Gl,
        (3, 3), // Open GL の対象バージョン
        Profile::Compatibility,
        Fallbacks::All,
        gl_extensions,
    );
    gl_reg
        .write_bindings(gl_generator::StructGenerator, &mut file_gl)
        .unwrap();
}

lib/gl/src/lib.rs の修正

上記で生成されるソースコード(gl_bindings.rs)を利用するように指定します。

include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs"));

lib/gl/Cargo.toml の修正

gl_generator を追加し、自動生成スクリプト(build.rs)を指定します。

[package]
... (略)
build = "build.rs"

[build-dependencies]
gl_generator = "0.14.0"

プロジェクトの Cargo.toml に追加

最後に、プロジェクト直下の Cargo.toml ファイルに独自 gl クレートを追加します。

[dependencies]
gl = { path = "lib/gl" }

ビルド

$ cargo build

でソースコードが自動生成され、ビルドが通るはずです。

EGL が対象の時の注意点

EGL のソースコードを生成する場合には、目的のターゲットプラットフォーム環境に合わせていくつかの型を自分で定義してあげる必要があります。

例. lib/egl/src/lib.rs

#[allow(non_camel_case_types)]
mod egl {
    use std::os::raw::{c_long, c_void};
    use wayland_sys::client::wl_display;
    use wayland_sys::egl::wl_egl_window;

    pub type khronos_int32_t = i32;
    pub type khronos_ssize_t = c_long;
    pub type khronos_uint64_t = u64;
    pub type khronos_utime_nanoseconds_t = khronos_uint64_t;

    pub type EGLNativeDisplayType = *mut wl_display;
    pub type EGLNativePixmapType = *mut c_void;
    pub type EGLNativeWindowType = *mut wl_egl_window;
    pub type EGLint = khronos_int32_t;
    pub type NativeDisplayType = EGLNativeDisplayType;
    pub type NativePixmapType = EGLNativePixmapType;
    pub type NativeWindowType = EGLNativeWindowType;

    include!(concat!(env!("OUT_DIR"), "/egl_bindings.rs"));
}