GPU なしで OpenGL を使う方法

Linux では事実上標準の OpenGL の実装である [mesa](http://mesa3d.org/) には OSmesa というライブラリがあり、これを使うことによってソフトウェアラスタライジング、つまり GPU なしで OpenGL を使うことができます。

同じ目的を持つ実装には他にも TransGaming 社の [SwiftShader](http://transgaming.com/swiftshader) と radgametools 社の [Pixomatic](http://www.radgametools.com/jp/pixomain.htm) があります。両方とも商用製品で DirectX をターゲットとします。ちなみに SwiftShader は Chrome/FlashPlayer のソフトウェアフォールバックとして使われており、元々 [swShader](http://sourceforge.net/projects/sw-shader/) が元です。

## OSmesa のメリットは？ 

CPU なしで OpenGL を使うことのメリットは制限された環境で OpenGL を使うことが出来るようになることです。例えば QuickLook はサンドボックス環境で実行されるため Apple のソフトウェア実装を含むネイティブの OpenGL を使うことができませんが、OSmesa を使うことでこの制約を超えて使うことが出来るようになります。

## 実際の活用例はあるの？

実装例は自分が開発した [ql4pmx](https://github.com/hkrn/MMDAI/tree/master/tools/ql4pmx) がありますので、実際に QuickLook で OpenGL を使う際の参考としていかがでしょうか。

## 使い方

まず mesa のソースコードをダウンロードし、configure または scons でビルドします。ここでは OSX のケースとして scons でビルドします。

```bash
cd $MESA_SRC_DIR
scons
```

ビルドすると build/darwin-x86(_64) に成果物が作られます。OSmesa を使うにはその中の mesa/libmesa.a と mesa/drivers/osmesa/libosmesa.dylib をリンクします。

```make
LDFLAGS := -L$MESA_SRC_DIR/darwin-x86_64/mesa/libosmesa.a -L$MESA_SRC_DIR/darwin-x86_64/mesa/drivers/osmesa -lmesa -losmesa
CFLAGS := -I$MESA_SRC_DIR/include
CXXFLAGS :=-I$MESA_SRC_DIR/include
```

簡単な使用例（C++）を以下に示します。

```c++
#include <GL/osmesa.h>
#include <iostream>
#include <vector>

int main(int argc, char *argv[])
{
  OSMesaContext context = 0;
  const int width = 640, height = 480;
  std::vector<uint8_t> buffer;
  try {
    buffer.resize(640 * 480);
    context = OSMesaCreateContextExt(GL_RGBA, 24, 0, 0, 0);
    if (context) {
      OSMesaMakeCurrent(context, &buffer[0], GL_UNSIGNED_BYTE, width, height);
      /*
       * ここの間で OpenGL の各関数が利用可能になるのでなにかをする。
       * OpenGL の拡張を利用する場合は OSMesaGetProcAddress を使うこと。
       * OSmesa は現時点 (mesa 9.0) で OpenGL 2.1 相当の実装を持ってる。
       */
    }
  } catch (std::exception &e) {
    std::cerr << e.what() << std::endl;
  }
  if (context) {
    OSMesaDestroyContext(context);
  }
  return 0;
}
```

OpenGL で描画した結果は buffer に画像として出力されます。

### 追記(2014/2/8)

OSX 上で mesa をビルド (必ず scons を使うこと) するときは以下の変更を加えないとコンパイルを通らない。

```diff
diff --git a/include/GL/glext.h b/include/GL/glext.h
index fea9e1f..197a4da 100644
--- a/include/GL/glext.h
+++ b/include/GL/glext.h
@@ -3554,11 +3554,13 @@ GLAPI void APIENTRY glMinSampleShadingARB (GLfloat value);
 
 #ifndef GL_ARB_shader_objects
 #define GL_ARB_shader_objects 1
+/*
 #ifdef __APPLE__
 typedef void *GLhandleARB;
 #else
+*/
 typedef unsigned int GLhandleARB;
-#endif
+/* #endif */
 typedef char GLcharARB;
 #define GL_PROGRAM_OBJECT_ARB             0x8B40
 #define GL_SHADER_OBJECT_ARB              0x8B48
```

これは OSX 上では ```__APPLE__``` が定義されるため GLhandleARB が ```void*``` となってしまい、64bit 上では型不一致 (```unsigned int != void*```) になるため。mesa が OSX 上でのビルドをサポートしないのはこの問題のためだろうと思われる。

上記の変更を加える事で mesa の追記時点での現行最新版である 10.0.3 のコンパイルが通ることを確認している。


### 追記2 (2014/8/10)

http://www.paraview.org/Wiki/ParaView/ParaView_And_Mesa_3D をながめていたら classic よりも高速な llvmpipe を使う OSmesa のビルド手順が出来たので追記として公開。

#### 事前準備

事前に LLVM をインストールする必要があるので、LLVM をインストールし、`llvm-config` をパスに通せるようにしておく。OSX の場合は macports か homebrew で一発インストール出来るはず。

OSX の場合は mesa3d に対して以下のパッチをあてておく。これは OSX のリンカが `-Wl,--no-undefined` を認識しないため。

```diff
diff --git a/configure.ac b/configure.ac
index 2aaba61..f2704ba 100644
--- a/configure.ac
+++ b/configure.ac
@@ -338,6 +338,8 @@ dnl
 case "$host_os" in
 openbsd*)
     LD_NO_UNDEFINED="" ;;
+darwin*)
+    LD_NO_UNDEFINED="" ;;
 *)
     LD_NO_UNDEFINED="-Wl,--no-undefined" ;;
 esac
```

#### ビルド

autogen で configure を生成し、configure から以下のオプションでビルドする。OSX の場合は `CFLAGS` と `CXXFLAGS` に `-fno-common` をつけないとリンク時にコンパイルが通らないのでつける。

ここでの `--prefix` はユーザ権限でインストール出来るように mesa3d 直下のディレクトリにインストールされる。

```bash
#!/bin/bash

./configure \
    CXXFLAGS="-O2 -DDEFAULT_SOFTWARE_DEPTH_BITS=31" \
    CFLAGS="-O2 -DDEFAULT_SOFTWARE_DEPTH_BITS=31" \
    --enable-static \
    --disable-shared \
    --disable-xvmc \
    --disable-glx \
    --disable-dri \
    --disable-llvm-shared-libs \
    --with-dri-drivers="" \
    --with-gallium-drivers="swrast" \
    --enable-texture-float \
    --disable-shared-glapi \
    --disable-egl \
    --with-egl-platforms="" \
    --enable-gallium-osmesa \
    --enable-gallium-llvm=yes \
    --prefix=`pwd`/install-root/release
```

通常通り make && make install する。

#### OSmesa をつかう

ここでは cmake の例をかくが、Makefile でも同じことが出来るだろう。

OSmesa を静的リンクするため、llvm-config からリンクするための情報を取得し、実行時リンクにその情報を追加すると OSmesa とリンクできる。

```cmake
# 以下2つを書き換えておく
set(OSMESA_INSTALL_PREFIX "/path/to/mesa3d/install-root/release")
set(LLVM_CONFIG_PATH "/path/to/llvm")

include_directories(${OSMESA_INSTALL_PREFIX}/include)
find_library(OSMESA_LIBRARY osmesa PATH ${OSMESA_INSTALL_PREFIX}/lib NO_DEFAULT_PATH)

execute_process(COMMAND ${LLVM_CONFIG_PATH}/llvm-config --libs OUTPUT_VARIABLE LLVM_CONFIG_LIBS)
string(STRIP ${LLVM_CONFIG_LIBS} LLVM_CONFIG_LIBS)
execute_process(COMMAND ${LLVM_CONFIG_PATH}/llvm-config --ldflags OUTPUT_VARIABLE LLVM_CONFIG_LDFLAGS)
string(STRIP ${LLVM_CONFIG_LDFLAGS} LLVM_CONFIG_LDFLAGS)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${LLVM_CONFIG_LDFLAGS} ${LLVM_CONFIG_LIBS}")

# ビルドする時は以下のように OSmesa をリンクする
# add_executable(${PROJECT_NAME} main.cc)
# target_link_libraries(${PROJECT_NAME} ${OSMESA_LIBRARY})
```

ちなみに OSmesa の情報を取ると OpenGL 3.0 にそれなりに多くの拡張実装をもってきてくれる。その中でもとくに OpenGL をデバッグするために非常に重要な機能拡張である `GL_ARB_debug_output` と `GL_KHR_debug` が含まれている。

```
version: 3.0 Mesa 10.2.5 (git-a53047f)
vendor: VMware, Inc.
renderer: Gallium 0.4 on llvmpipe (LLVM 3.3, 256 bits)
extensions : GL_ARB_multisample GL_EXT_abgr GL_EXT_bgra GL_EXT_blend_color GL_EXT_blend_minmax GL_EXT_blend_subtract GL_EXT_copy_texture GL_EXT_polygon_offset GL_EXT_subtexture GL_EXT_texture_object GL_EXT_vertex_array GL_EXT_compiled_vertex_array GL_EXT_texture GL_EXT_texture3D GL_IBM_rasterpos_clip GL_ARB_point_parameters GL_EXT_draw_range_elements GL_EXT_packed_pixels GL_EXT_point_parameters GL_EXT_rescale_normal GL_EXT_separate_specular_color GL_EXT_texture_edge_clamp GL_SGIS_generate_mipmap GL_SGIS_texture_border_clamp GL_SGIS_texture_edge_clamp GL_SGIS_texture_lod GL_ARB_framebuffer_sRGB GL_ARB_multitexture GL_EXT_framebuffer_sRGB GL_IBM_multimode_draw_arrays GL_IBM_texture_mirrored_repeat GL_ARB_texture_cube_map GL_ARB_texture_env_add GL_ARB_transpose_matrix GL_EXT_blend_func_separate GL_EXT_fog_coord GL_EXT_multi_draw_arrays GL_EXT_secondary_color GL_EXT_texture_env_add GL_EXT_texture_lod_bias GL_INGR_blend_func_separate GL_NV_blend_square GL_NV_light_max_exponent GL_NV_texgen_reflection GL_NV_texture_env_combine4 GL_SUN_multi_draw_arrays GL_ARB_texture_border_clamp GL_ARB_texture_compression GL_EXT_framebuffer_object GL_EXT_texture_env_combine GL_EXT_texture_env_dot3 GL_MESA_window_pos GL_NV_packed_depth_stencil GL_NV_texture_rectangle GL_ARB_depth_texture GL_ARB_occlusion_query GL_ARB_shadow GL_ARB_texture_env_combine GL_ARB_texture_env_crossbar GL_ARB_texture_env_dot3 GL_ARB_texture_mirrored_repeat GL_ARB_window_pos GL_EXT_stencil_two_side GL_EXT_texture_cube_map GL_NV_depth_clamp GL_NV_fog_distance GL_APPLE_packed_pixels GL_APPLE_vertex_array_object GL_ARB_draw_buffers GL_ARB_fragment_program GL_ARB_fragment_shader GL_ARB_shader_objects GL_ARB_vertex_program GL_ARB_vertex_shader GL_ATI_draw_buffers GL_ATI_texture_env_combine3 GL_ATI_texture_float GL_EXT_shadow_funcs GL_EXT_stencil_wrap GL_MESA_pack_invert GL_MESA_ycbcr_texture GL_NV_primitive_restart GL_ARB_depth_clamp GL_ARB_fragment_program_shadow GL_ARB_half_float_pixel GL_ARB_occlusion_query2 GL_ARB_point_sprite GL_ARB_shading_language_100 GL_ARB_sync GL_ARB_texture_non_power_of_two GL_ARB_vertex_buffer_object GL_ATI_blend_equation_separate GL_EXT_blend_equation_separate GL_OES_read_format GL_ARB_color_buffer_float GL_ARB_pixel_buffer_object GL_ARB_texture_compression_rgtc GL_ARB_texture_float GL_ARB_texture_rectangle GL_ATI_texture_compression_3dc GL_EXT_packed_float GL_EXT_pixel_buffer_object GL_EXT_texture_compression_rgtc GL_EXT_texture_mirror_clamp GL_EXT_texture_rectangle GL_EXT_texture_sRGB GL_EXT_texture_shared_exponent GL_ARB_framebuffer_object GL_EXT_framebuffer_blit GL_EXT_framebuffer_multisample GL_EXT_packed_depth_stencil GL_ARB_vertex_array_object GL_ATI_separate_stencil GL_ATI_texture_mirror_once GL_EXT_draw_buffers2 GL_EXT_draw_instanced GL_EXT_gpu_program_parameters GL_EXT_texture_array GL_EXT_texture_compression_latc GL_EXT_texture_integer GL_EXT_texture_sRGB_decode GL_EXT_timer_query GL_OES_EGL_image GL_ARB_copy_buffer GL_ARB_depth_buffer_float GL_ARB_draw_instanced GL_ARB_half_float_vertex GL_ARB_instanced_arrays GL_ARB_map_buffer_range GL_ARB_texture_rg GL_ARB_texture_swizzle GL_ARB_vertex_array_bgra GL_EXT_texture_swizzle GL_EXT_vertex_array_bgra GL_NV_conditional_render GL_AMD_conservative_depth GL_AMD_draw_buffers_blend GL_AMD_seamless_cubemap_per_texture GL_ARB_ES2_compatibility GL_ARB_blend_func_extended GL_ARB_debug_output GL_ARB_draw_buffers_blend GL_ARB_draw_elements_base_vertex GL_ARB_explicit_attrib_location GL_ARB_fragment_coord_conventions GL_ARB_provoking_vertex GL_ARB_sampler_objects GL_ARB_seamless_cube_map GL_ARB_shader_texture_lod GL_ARB_texture_multisample GL_ARB_texture_rgb10_a2ui GL_ARB_uniform_buffer_object GL_ARB_vertex_type_2_10_10_10_rev GL_EXT_provoking_vertex GL_EXT_texture_snorm GL_MESA_texture_signed_rgba GL_ARB_get_program_binary GL_ARB_robustness GL_ARB_separate_shader_objects GL_ARB_shader_bit_encoding GL_ARB_timer_query GL_ARB_transform_feedback2 GL_ARB_transform_feedback3 GL_NV_vdpau_interop GL_ARB_conservative_depth GL_ARB_internalformat_query GL_ARB_map_buffer_alignment GL_ARB_shading_language_420pack GL_ARB_shading_language_packing GL_ARB_texture_storage GL_ARB_transform_feedback_instanced GL_EXT_framebuffer_multisample_blit_scaled GL_EXT_transform_feedback GL_AMD_shader_trinary_minmax GL_ARB_clear_buffer_object GL_ARB_invalidate_subdata GL_ARB_texture_storage_multisample GL_ARB_vertex_attrib_binding GL_KHR_debug GL_ARB_multi_bind GL_ARB_texture_mirror_clamp_to_edge GL_ARB_vertex_type_10f_11f_11f_rev GL_EXT_shader_integer_mix 
```