🎨

軽量のグラフィックスライブラリ micro{gl} を試す

に公開

概要

今回は軽量のグラフィックスライブラリの micro{gl} を試してみようと思います。

micro{gl}とは?

https://micro-gl.github.io/docs/microgl

高速、超スリム、組み込み可能、ヘッダのみの C++11 ベクターグラフィックスライブラリ。標準ライブラリは必要ありません。

GPU・FPU(浮動小数点演算器)不要、標準ライブラリ(STL)不要を掲げており、32/64bit環境で動かせる、軽量のCPUでレンダリングする“ベクターグラフィックス寄り”のライブラリという位置付けになっています。ウェアラブルデバイスやIoTデバイスなどの組み込み環境でも採用しやすいライブラリになっています。

動作環境

$ sw_vers
ProductName: macOS
ProductVersion: 15.5
BuildVersion: 24F74
$ c++ --version
Apple clang version 16.0.0 (clang-1600.0.26.4)
Target: arm64-apple-darwin24.5.0
Thread model: posix

セットアップ

まずはリポジトリをクローンして、exampleが動かせる様にしたいと思います。

examplesを試すのにcmake と SDL2 が必要なので事前にインストールしておきます。

$ brew install cmake
cmake --version
cmake version 4.0.3

CMake suite maintained and supported by Kitware (kitware.com/cmake).
$ brew install sdl2

リポジトリをcloneし、以下を実行します。

$ git clone git@github.com:micro-gl/micro-gl.git
$ cd micro-gl
$ mkdir cmake-build-release
$ cd cmake-build-release
$ cmake -DCMAKE_BUILD_TYPE=Release ..
$ cmake --build .

すると examples/bin 配下に実行ファイルができているので、好きなものを試すことができます。

例)

  • example_3d_sampler_shader_and_z_buffer
    image1.gif

  • example_blocks_rounded_rect
    image2.png

  • example_draw_path_stroke
    image3.gif

シンプルな実装を試す

まずは作業用ディレクトリを作成し最低限のシンプルな実装を試してみたいと思います。まずは単純に四角形を表示するだけのものを実装してみたいと思います。

mkdir example-microgl

コンパイル&実行確認

まずは以下を作成してコンパイル&実行までできることを確認しときます。

  • main.cpp

    #include <iostream>
    
    int main() {
        std::cout << "Hello World!" << std::endl;
        return 0;
    }
    
  • CMakeLists.txt

    cmake_minimum_required(VERSION 4.2.1)
    project(example-microgl LANGUAGES CXX)
    add_executable(example-microgl main.cpp)
    

コンパイル & 実行して「Hello World!」が表示されとけば一旦準備完了です。

$ cmake -S . -B build
$ cmake --build build
./build/example-microgl
Hello World!

CMakeLists.txtの設定

includeパスの設定

現在のディレクトリ構成が以下の様になっているとして

.
├── micro-gl # git cloneしたもの
└── example-microgl

CMakeLists.txt に target_include_directories を追加します。

target_include_directories(example-microgl PRIVATE ../micro-gl/include)

SDL2の設定

find_package(SDL2 CONFIG REQUIRED)
target_link_libraries(example-microgl PRIVATE SDL2::SDL2)

これを受けた最終的な CMakeLists.txt は以下になります。

cmake_minimum_required(VERSION 4.0.3)
project(example-microgl LANGUAGES CXX)
add_executable(example-microgl main.cpp)

target_compile_features(example-microgl PRIVATE cxx_std_11)

find_package(SDL2 CONFIG REQUIRED)
target_link_libraries(example-microgl PRIVATE SDL2::SDL2)
target_include_directories(example-microgl PRIVATE ../micro-gl/include)

main.cppの実装

#include <SDL.h>
#include <cstdint>
#include <iostream>

#include <microgl/canvas.h>
#include <microgl/bitmaps/bitmap.h>
#include <microgl/pixel_coders/RGB888_PACKED_32.h>
#include <microgl/samplers/flat_color.h>

int main() {
  using Canvas = canvas<bitmap<coder::RGB888_PACKED_32>>;

  const int W = 512;
  const int H = 512;

  if (SDL_Init(SDL_INIT_VIDEO) != 0) {
    std::cerr << "SDL_Init failed: " << SDL_GetError() << "\n";
    return 1;
  }

  SDL_Window* window = SDL_CreateWindow(
      "micro{gl} rectangle",
      SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
      W, H, 0);

  SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, 0);
  SDL_Texture* texture = SDL_CreateTexture(
      renderer,
      SDL_PIXELFORMAT_RGB888,
      SDL_TEXTUREACCESS_STATIC,
      W, H);

  Canvas canvas(W, H);

  // 四角形の塗り(単色)
  microgl::sampling::flat_color<> fill{{0, 0, 0, 255}}; // 黒

  // 1回だけ描画(以後は表示更新だけ)
  canvas.clear({255, 255, 255, 255}); // 背景:白
  canvas.drawRect<microgl::blendmode::Normal,
                  microgl::porterduff::FastSourceOverOnOpaque,
                  false,
                  float>(
      fill,
      100, 100, 400, 400); // x, y, w, h

  bool quit = false;
  SDL_Event e{};
  while (!quit) {
    while (SDL_PollEvent(&e)) {
      if (e.type == SDL_QUIT) quit = true;
      if (e.type == SDL_KEYUP && e.key.keysym.sym == SDLK_ESCAPE) quit = true;
    }

    SDL_UpdateTexture(texture, nullptr, canvas.pixels(),
                      canvas.width() * canvas.sizeofPixel());
    SDL_RenderClear(renderer);
    SDL_RenderCopy(renderer, texture, nullptr, nullptr);
    SDL_RenderPresent(renderer);
  }

  SDL_DestroyTexture(texture);
  SDL_DestroyRenderer(renderer);
  SDL_DestroyWindow(window);
  SDL_Quit();
  return 0;
}

上記を実行すると以下が表示されます。

image4.png

Canvas

今回 SDL2 の詳細は触れず micro{gl} に関する箇所を深掘りしていきたいと思います。

上の実装でまず出てくるのが Canvas になります。

https://micro-gl.github.io/docs/microgl/concepts/canvas

キャンバスは次のように説明できます。

  • 最小限の状態を持つオブジェクト
  • 描画するビットマップを保持するオブジェクト
  • サンプラーと3Dシェーダーを使用してビットマップ上に図形を描くための一連のメソッド
    ※ キャンバスの原点(0, 0)はPhotoshopのように左上にあり、下と右に広がります。

using Canvas でのCanvas型宣言時に以下のBitmapsを指定しています。

Bitmaps

https://micro-gl.github.io/docs/microgl/concepts/bitmaps

ピクセル配列とピクセルコーダーを格納するオブジェクト。

  • 主なインターフェース:
    • 位置指定でピクセルを取得/設定。
    • 位置指定で色を取得/設定。
    • 位置指定で輝度を取得/設定。
    • ピクセル値でビットマップをクリア/塗りつぶし。

Bitmaps指定時に以下のピクセルコーダーを指定しています。

Pixel Coders

https://micro-gl.github.io/docs/microgl/concepts/pixel-coders

色や輝度をピクセル表現にエンコード(符号化)し、またピクセルを色にデコード(復号)できるオブジェクト

microgl/pixel_coders には予め用意されているピクセルコーダーが定義されています。低リソース環境でもメモリを節約して使えるように様々な形式が用意されています。

サイズ ピクセルコーダー名 概要
32ビット RGB888_PACKED_32 rgb(8|8|8)チャンネルを32ビット符号なし整数に変換
RGBA8888_PACKED_32 rgba(8|8|8|8)チャネルを 32 ビットの符号なし整数に変換
RGBA8888_ARRAY rgba(8|8|8|8)チャンネルを配列に格納 (より高速な IO)
RGB888_ARRAY rgb(8|8|8)チャンネルを配列に格納(より高速なIO)
16ビット RGB555_PACKED_16 rgb(5|5|5)チャネルを 16 ビットの符号なし整数 (word) に変換
RGB565_PACKED_16 rgb(5|6|5)チャネルを16ビットの符号なし整数(word)に変換
RGBA4444_PACKED_16 rgba(4|4|4|4)チャネルを 16 ビットの符号なし整数 (word) に変換
RGBA5551_PACKED_16 rgba(5|5|5|1)チャネルを 16 ビットの符号なし整数 (word) に変換
ARGB1555_PACKED_16 argb(1|5|5|5)チャネルを 16 ビットの符号なし整数 (word) に変換
8ビット RGB332_PACKED_8 rgb(3|3|2)チャネルを 8 ビットの符号なし整数 (char) に変換

アニメーションを試す

次に上記の四角形を時間とともにサイズを変更するサンプルを実装してみたいと思います。今回は SDL_GetTicks を使用してSDK初期化後からの経過時間(ms)を取得し、その値を元に縦、横のサイズを変更します。

  // 以下を追加
  auto render = [&] {
    // 時間で縦、横のサイズを変更する
    const uint32_t t = SDL_GetTicks();          // ms
    const float phase = (t % 2000) / 2000.0f;   // 0..1

    canvas.clear({255, 255, 255, 255});
    canvas.drawRect<microgl::blendmode::Normal,
                    microgl::porterduff::FastSourceOverOnOpaque,
                    false,
                    float>(
        fill,
        100, 100, static_cast<int>(400 * phase), static_cast<int>(400 * phase));
  };
  
  while (!quit) {
    while (SDL_PollEvent(&e)) {
      if (e.type == SDL_QUIT) quit = true;
      if (e.type == SDL_KEYUP && e.key.keysym.sym == SDLK_ESCAPE) quit = true;
    }
    render(); // 追加
    SDL_UpdateTexture(texture, nullptr, canvas.pixels(),
                      canvas.width() * canvas.sizeofPixel());
    SDL_RenderClear(renderer);
    SDL_RenderCopy(renderer, texture, nullptr, nullptr);
    SDL_RenderPresent(renderer);
  }

上記を実行すると以下のよう時間と共に縦横のサイズが変更するかと思います。

image5.gif

まとめ

今回軽く触り程度ですが、micro{gl}を試してみて特にPixel Codersの低リソースでも動作できるような工夫などが見れて勉強になりました!また機会があれば深掘りしてみたいと思います。

Discussion