HiDPIな環境でWSLgを使ってDear ImGuiを表示する


はじめに

Win11では新機能WSLgが追加され、macosのようにlinux/unixのソフトウェアをWSL環境で動かして、そのGUIをWindowsのデスクトップに表示できるようになりました。と思ってDear ImGuiを使うプログラムを書いたら思わぬところで嵌ったので、問題とその解決策を忘備録も兼ねて紹介します。

環境

Windows 11 Insider Preview Build 22581
WSL 0.56.2.0
WSLg 1.0.30
Ubuntu 20.04
NVIDIA Driver 512.15

imguiのサンプルを動かす

WSLgの準備

公式のチュートリアルに従ってWSLのアップデートとGPUドライバをインストール。チュートリアルには無いですが、

sudo apt install xfce4-terminal

ターミナルを入れるとコピペが簡単で作業が捗ります。
PowershellからWSLに入ってターミナルが起動することを確認しましょう。

>wsl -d Ubuntu-20.04
xfce4-terminal &

imguiのビルド

作業用に適当なディレクトリを作ってclone

mkdir imguitest
cd imguitest
git clone https://github.com/ocornut/imgui.git

バックエンドはSDL+OpenGLが無難です。サンプルをビルドするには

cd examples/example_sdl_opengl3/
sudo apt install libsdl2-dev
make

で実行ファイルexample_sdl_opengl3ができます
CMakeを使いたい場合は

CMakeLists.txt
cmake_minimum_required(VERSION 3.0.0)
project(sample VERSION 0.0.0)

#CMP0072: prefer libOpenGL.so/libGLX.so to libGL.so
cmake_policy(SET CMP0072 NEW)

find_package(OpenGL REQUIRED)
find_package(SDL2 REQUIRED)
set(IMGUI_SOURCE_DIR ${CMAKE_SOURCE_DIR}/imgui)
set(IMGUI_BACKENDS_DIR ${IMGUI_SOURCE_DIR}/backends)
set(IMGUI_LIBS_DIR ${IMGUI_SOURCE_DIR}/examples/libs)

set(SOURCE_FILES
    ${IMGUI_SOURCE_DIR}/examples/example_sdl_opengl3/main.cpp
    ${IMGUI_SOURCE_DIR}/imgui_demo.cpp
    ${IMGUI_SOURCE_DIR}/imgui_draw.cpp
    ${IMGUI_SOURCE_DIR}/imgui_tables.cpp
    ${IMGUI_SOURCE_DIR}/imgui.cpp
    ${IMGUI_SOURCE_DIR}/imgui_widgets.cpp
    ${IMGUI_BACKENDS_DIR}/imgui_impl_opengl3.cpp
    ${IMGUI_BACKENDS_DIR}/imgui_impl_sdl.cpp
)

add_executable(sample ${SOURCE_FILES})

target_include_directories(sample PRIVATE
    ${IMGUI_SOURCE_DIR}
    ${IMGUI_BACKENDS_DIR}
    ${SDL2_INCLUDE_DIRS}
)

target_link_libraries(sample
    OpenGL::GL
    ${SDL2_LIBRARIES}
    ${CMAKE_DL_LIBS}
)

を作業用ディレクトリ直下に置いてビルドすればOK
実行するとこんな感じでウィンドウが出るはず。

4Kの悩み

問題

筆者は4KのディスプレイをWindowsの機能でUIを150%拡大して使用しています。

この環境だと先程のサンプルプログラムは2倍に拡大されて半分の解像度で画面に出力され、ベクターにはジャギが出て、TTFフォントは滲んでしまいます。原因はWin側の拡大縮小機能をWSL側にも自動で適用する仕組みが、WSL側のスクリーンの論理寸法を半分に縮小→Win側で拡大して表示というやり方で実装されているため。確かに、こうすれば拡大表示は可能ですが…
しかしそれでもgeditやxfce4-terminalはちゃんと4Kの解像度でレンダリングされ、文字もくっきり見やすく表示されます。仕組みを調べるとWSLgはリモートデスクトップとして実装されていて、WSL側からWin側にWaylandプロトコルを使ってGUIを転送しているのですが、このプロトコルには転送する際に論理寸法のn倍のフレームバッファを送る機能がある模様。先程のサンプルプログラムではWaylandプロトコルではなく古いX11のプロトコルで伝送しているため、半分の解像度で表示されてしまったのでした。

Wayland化

先程のプログラムをWaylandで動かすのは簡単で、SDLのバージョンが2.0.10以上ならばHiDPIに対応しているので環境変数を弄るだけでそのままWaylandで起動します

export SDL_VIDEODRIVER=wayland


4Kの解像度でレンダリングされるようになりました。しかし、ウィンドウタイトルと外枠が消えてしまいました、このままではウィンドウを動かすことも出来ません。

libdecor導入

SDL2.0.16からSDLにlibdecorを組み込むことでタイトルバーを付けられるようになりました。
libdecorのパッケージは2022.3.30現在、Ubuntu 20.04のスイートに入っていないのでlibdecorをビルドして最新のSDL2.0.20をビルドする必要があります。

sudo apt remove --purge libsdl2-dev
mkdir sdl
cd sdl
curl https://www.libsdl.org/release/SDL2-2.0.20.tar.gz -O
tar -xzvf SDL2-2.0.20.tar.gz

docs/README-linux.mdを参考に依存するライブラリを導入

sudo apt-get install build-essential git make cmake autoconf automake libtool pkg-config libasound2-dev libpulse-dev libaudio-dev libjack-dev libx11-dev libxext-dev libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxinerama-dev libxxf86vm-dev libxss-dev libgl1-mesa-dev libdbus-1-dev libudev-dev libgles2-mesa-dev libegl1-mesa-dev libibus-1.0-dev fcitx-libs-dev libsamplerate0-dev libsndio-dev libwayland-dev libxkbcommon-dev libdrm-dev libgbm-dev

先にlibdecorをビルドしてインストールします。

sudo apt install meson libwayland-dev wayland-protocols libpango1.0-dev libdbus-1-dev libegl-dev libopengl-dev libxkbcommon-dev
git clone https://gitlab.gnome.org/jadahl/libdecor.git
cd libdecor
meson build --buildtype release
sudo meson install -C build

この手順で正しいのか正直不明です。SDLのビルドは通ったのにタイトルバーが付かず、WSLを一度シャットダウンしたらタイトルバーが付くようになったりしました。README.mdに沿ってlibdecorのデモもビルドして動作確認するのも良いでしょう。
libdecorをインストールしたらSDLをビルドします。

cd SDL2-2.0.20
mkdir build
cd build
cmake ../
make
sudo make install

cmakeがlibdecorを検出できているどうかに注意してください。
先程のサンプルプログラムを再ビルドして実行すると

タイトルバーが付いていれば成功です。どうもライブラリのキャッシュとかがあるっぽいので上手くいかないときはWSLを再起動してみてください。

HiDPIなフォントにする

デフォルトのビットマップなフォントのままだとHiDPIの恩恵が少ないので適当なTTFに変えると良いでしょう。

    ImFont* font = io.Fonts->AddFontFromFileTTF("/mnt/c/Windows/Fonts/arial.ttf", 24.0f, NULL, io.Fonts->GetGlyphRangesJapanese());
    IM_ASSERT(font != nullptr);
    io.FontGlobalScale = 0.5f;

大きめのフォントサイズで読み込んでio.FontGlobalScale = 0.5f;で縮小すると綺麗に表示されます。(元から半分のサイズで読み込んで表示だとぼやけてしまう)

毎フレーム全部描画しなおしてるので重いし電気を食う点に目を瞑れば、.NET等と比べても遜色ないGUIが得られました。

参考にしたページなど

Dear ImGui CMake対応 | いろいろやるブログ
Using SDL2 with CMake | Trenki’s Dev Blog
Primitive rendering on High DPI/Retina · Issue #1786 · ocornut/imgui · GitHub