Quake3の移植



(私が開発者である) Emboxオペレーティングシステムでは、OpenGLのサポートがしばらく前に登場しましたが、実用的なパフォーマンスチェックはなく、いくつかのグラフィックプリミティブを持つシーンのみをレンダリングしました。







私はゲーム開発者に本当に興味はありませんでしたが、もちろんゲームが好きで、これは楽しいことをするのに良い方法ですが、同時にOpenGLをチェックし、ゲームがOSとどのように相互作用するかを確認します。







この記事では、EmboxでQuake3をビルドして実行する方法について説明します。







より正確には、 Quake3自体は実行しませんが、 それに基づいてioquake3を実行します。これにはオープンソースコードもあります。 簡単にするために、ioquake3を単にquakeと呼びます:)







この記事ではQuakeのソースコードとそのアーキテクチャを分析しないことをすぐに予約します(この記事についてはHabréに翻訳があります )。この記事では、新しいオペレーティングシステムでゲームを開始する方法に焦点を当てます。







この記事に記載されているコードフラグメントは、理解を深めるために簡略化されています。エラーのチェックは省略され、擬似コードが使用されています。 オリジナルのソースはリポジトリにあります







依存関係



奇妙なことに、Quake3のビルドに必要なライブラリはそれほど多くありません。 必要なもの:









最初の段落では、すべてが明確になっています。これらの関数がないと、Cで開発するときに行うのが難しく、これらの呼び出しの使用は非常に期待されます。 したがって、これらのインターフェイスのサポートは、事実上すべてのオペレーティングシステムで何らかの方法で行われ、この場合、機能を追加する必要はほとんどありませんでした。 私は残りに対処しなければなりませんでした。







libcurl



最も簡単でした。 libcurlをビルドするにはLibcで十分です(もちろん、一部の機能は利用できませんが、必要ありません)。 このライブラリの構成とコンパイルは静的に非常に簡単です。







通常、アプリケーションとライブラリは動的にリンクしますが、 Emboxでは、メインモードは1つの画像にリンクしています。すべてを静的にリンクします。







使用するビルドシステムによって、特定の手順は異なりますが、意味は次のようになります。







 wget https://curl.haxx.se/download/curl-7.61.1.tar.gz tar -xf curl-7.61.1.tar.gz cd curl-7.61.1 ./configure --enable-static --host=i386-unknown-none -disable-shared make ls ./lib/.libs/libcurl.a #      
      
      





メサ/ OpenGL



Mesaはグラフィックを操作するためのオープンソースフレームワークであり、多数のインターフェイス(OpenCL、Vulkanなど)をサポートしていますが、この場合はOpenGLに興味があります。 このような大きなフレームワークの移植は、別の記事のトピックです。 Embox Mesa3Dが既に持っているものだけに制限します:)もちろん、ここではOpenGL実装が適しています。







Sdl



SDLは、入力デバイス、オーディオ、グラフィックスを操作するためのクロスプラットフォームフレームワークです。







ここでは、グラフィックス以外のすべてをハンマーで処理し、フレームを描画するために、いつスタブ関数が呼び出されるかを確認します。







グラフィックを操作するためのバックエンドは、 SDL2-2.0.8/src/video/SDL_video.c



設定されています。







次のようになります。







 /* Available video drivers */ static VideoBootStrap *bootstrap[] = { #if SDL_VIDEO_DRIVER_COCOA &COCOA_bootstrap, #endif #if SDL_VIDEO_DRIVER_X11 &X11_bootstrap, #endif ... }
      
      





新しいプラットフォームの「通常の」サポートにVideoBootStrap



ないように、 VideoBootStrap



追加するだけVideoBootStrap









簡単にするために、たとえばsrc/video/qnx/video.c



またはsrc/video/raspberry/SDL_rpivideo.c



などのsrc/video/raspberry/SDL_rpivideo.c



を使用できますが、最初に実装をほとんど空にします。







 /* SDL_sysvideo.h */ typedef struct VideoBootStrap { const char *name; const char *desc;``` int (*available) (void); SDL_VideoDevice *(*create) (int devindex); } VideoBootStrap; /* embox_video.c */ static SDL_VideoDevice *createDevice(int devindex) { SDL_VideoDevice *device; device = (SDL_VideoDevice *)SDL_calloc(1, sizeof(SDL_VideoDevice)); if (device == NULL) { return NULL; } return device; } static int available() { return 1; } VideoBootStrap EMBOX_bootstrap = { "embox", "EMBOX Screen", available, createDevice };
      
      





VideoBootStrap



を配列に追加します。







 /* Available video drivers */ static VideoBootStrap *bootstrap[] = { &EMBOX_bootstrap, #if SDL_VIDEO_DRIVER_COCOA &COCOA_bootstrap, #endif #if SDL_VIDEO_DRIVER_X11 &X11_bootstrap, #endif ... }
      
      





基本的に、この時点ですでにSDLをコンパイルできます。 libcurlと同様に、コンパイルの詳細は特定のビルドシステムに依存しますが、どういうわけか次のようなことをする必要があります。







 ./configure --host=i386-unknown-none \ --enable-static \ --enable-audio=no \ --enable-video-directfb=no \ --enable-directfb-shared=no \ --enable-video-vulkan=no \ --enable-video-dummy=no \ --with-x=no make ls build/.libs/libSDL2.a #     
      
      





Quake自身を置く



Quake3は動的ライブラリの使用を伴いますが、他のすべてと同様に静的にリンクします。







これを行うには、Makefileでいくつかの変数を設定します







 CROSS_COMPILING=1 USE_OPENAL=0 USE_OPENAL_DLOPEN=0 USE_RENDERER_DLOPEN=0 SHLIBLDFLAGS=-static
      
      





最初の打ち上げ



簡単にするために、qemu / x86で実行します。 これを行うには、それをインストールする必要があります(ここと以下ではDebianのコマンドがあります。他のディストリビューションパッケージは異なる方法で呼び出される場合があります)。







 sudo apt install qemu-system-i386
      
      





そして、打ち上げ自体:







 qemu-system-i386 -kernel build/base/bin/embox -m 1024 -vga std -serial stdio
      
      





ただし、Quakeを起動すると、すぐにエラーが発生します







 > quake3 EXCEPTION [0x6]: error = 00000000 EAX=00000001 EBX=00d56370 ECX=80200001 EDX=0781abfd GS=00000010 FS=00000010 ES=00000010 DS=00000010 EDI=007b5740 ESI=007b5740 EBP=338968ec EIP=0081d370 CS=00000008 EFLAGS=00210202 ESP=37895d6d SS=53535353
      
      





エラーはゲームではなくオペレーティングシステムによって表示されます。 Debagは、このエラーはQEMUのx86の不完全なSIMDサポートが原因であることを示しました。命令の一部はサポートされず、不明なコマンド例外(無効なオペコード)をスローします。 upd:コメントでWGHが示唆したように、本当の問題は、cr0 / cr4でSSEサポートを明示的に有効にするのを忘れていたため、すべてがQEMUで問題ないことでした。







これはQuake自体ではなく、OpenLibMで発生します(これは数学関数expf()



を実装するために使用するライブラリです)。 __test_sse()



がSSEで実際のチェックを行わないように__test_sse()



パッチを適用しますが、単にサポートがないと考えています。







上記の手順は実行するのに十分であり、コンソールに次の出力が表示されます。







 > quake3 ioq3 1.36 linux-x86_64 Nov 1 2018 SSE instruction set not available ----- FS_Startup ----- We are looking in the current search path: //.q3a/baseq3 ./baseq3 ---------------------- 0 files in pk3 files "pak0.pk3" is missing. Please copy it from your legitimate Q3 CDROM. Point Release files are missing. Please re-install the 1.32 point release. Also check that your ioq3 executable is in the correct place and that every file in the "baseq3 " directory is present and readable ERROR: couldn't open crashlog.txt
      
      





すでに悪くはありませんが、Quake3は起動しようとしてエラーメッセージを表示します! ご覧のとおり、彼にはbaseq3



ディレクトリにファイルがありbaseq3



。 サウンド、テクスチャ、その他すべてが含まれています。 pak0.pk3



は、ライセンスされたCD-ROMから取得する必要があることに注意してください(はい、オープンソースは無料使用を意味するものではありません)。







ディスクの準備



 sudo apt install qemu-utils #  qcow2- qemu-img create -f qcow2 quake.img 1G #   nbd sudo modprobe nbd max_part=63 #  qcow2-      sudo qemu-nbd -c /dev/nbd0 quake.img sudo mkfs.ext4 /dev/nbd0 sudo mount /dev/nbd0 /mnt cp -r path/to/q3/baseq3 /mnt sync sudo umount /mnt sudo qemu-nbd -d /dev/nbd0
      
      





これで、ブロックデバイスをqemuに転送できます







 qemu-system-i386 -kernel build/base/bin/embox -m 1024 -vga std -serial stdio -hda quake.img
      
      





システムが起動したら、ディスクを/mnt



マウントし、このディレクトリでquake3を実行します。今回は後でクラッシュします







 > mount -t ext4 /dev/hda1 /mnt > cd /mnt > quake3 ioq3 1.36 linux-x86_64 Nov 1 2018 SSE instruction set not available ----- FS_Startup ----- We are looking in the current search path: //.q3a/baseq3 ./baseq3 ./baseq3/pak8.pk3 (9 files) ./baseq3/pak7.pk3 (4 files) ./baseq3/pak6.pk3 (64 files) ./baseq3/pak5.pk3 (7 files) ./baseq3/pak4.pk3 (272 files) ./baseq3/pak3.pk3 (4 files) ./baseq3/pak2.pk3 (148 files) ./baseq3/pak1.pk3 (26 files) ./baseq3/pak0.pk3 (3539 files) ---------------------- 4073 files in pk3 files execing default.cfg couldn't exec q3config.cfg couldn't exec autoexec.cfg Hunk_Clear: reset the hunk ok Com_RandomBytes: using weak randomization ----- Client Initialization ----- Couldn't read q3history. ----- Initializing Renderer ---- ------------------------------- QKEY building random string Com_RandomBytes: using weak randomization QKEY generated ----- Client Initialization Complete ----- ----- R_Init ----- tty]EXCEPTION [0xe]: error = 00000000 EAX=00000000 EBX=00d2a2d4 ECX=00000000 EDX=111011e0 GS=00000010 FS=00000010 ES=00000010 DS=00000010 EDI=0366d158 ESI=111011e0 EBP=37869918 EIP=00000000 CS=00000008 EFLAGS=00010212 ESP=006ef6ca SS=111011e0 EXCEPTION [0xe]: error = 00000000
      
      





このエラーは、QemuのSIMDでも発生します。 upd:コメントでWGHが示唆したように、本当の問題は、cr0 / cr4でSSEサポートを明示的に有効にするのを忘れていたため、すべてがQEMUで問題ないことでした。 今回は、命令がQuake3 x86仮想マシンで使用されます。 この問題は、x86の実装を解釈されたVMに置き換えることで解決しました(Quake3仮想マシンと、原則としてアーキテクチャ上の機能については、同じ記事ですべて読むことができます)。 その後、SDLの関数が呼び出され始めますが、もちろん何も起こりません。 これらの関数は今のところ何もしません。







グラフィックスのサポートを追加する



 static SDL_VideoDevice *createDevice(int devindex) { ... device->GL_GetProcAddress = glGetProcAddress; device->GL_CreateContext = glCreateContext; ... } /*   OpenGL- */ SDL_GLContext glCreateContext(_THIS, SDL_Window *window) { OSMesaContext ctx; /*   -  --    .. */ sdl_init_buffers(); /*    Mesa */ ctx = OSMesaCreateContextExt(OSMESA_BGRA, 16, 0, 0, NULL); OSMesaMakeCurrent(ctx, fb_base, GL_UNSIGNED_BYTE, fb_width, fb_height); return ctx; }
      
      





2番目のハンドラーは、OpenGLを操作するときに呼び出す関数をSDLに伝えるために必要です。







これを行うには、配列を開始し、開始から開始まで、次のような欠落している呼び出しを確認します。







 static struct { char *proc; void *fn; } embox_sdl_tbl[] = { { "glClear", glClear }, { "glClearColor", glClearColor }, { "glColor4f", glColor4f }, { "glColor4ubv", glColor4ubv }, { 0 }, }; void *glGetProcAddress(_THIS, const char *proc) { for (int i = 0; embox_sdl_tbl[i].proc != 0; i++) { if (!strcmp(embox_sdl_tbl[i].proc, proc)) { return embox_sdl_tbl[i].fn; } } printf("embox/sdl: Failed to find %s\n", proc); return 0; }
      
      





数回の再起動で、リストはスプラッシュ画面とメニューを描画するのに十分なものになります。 幸いなことに、Mesaには必要な機能がすべて備わっています。 唯一のこと-何らかの理由でglGetString()



関数がないため、 glGetString()



を使用する必要があり_mesa_GetString()









アプリケーションが起動すると、スプラッシュ画面が表示されます。











入力デバイスを追加する



SDLにキーボードとマウスのサポートを追加します。







イベントを操作するには、ハンドラーを追加する必要があります







 static SDL_VideoDevice *createDevice(int devindex) { ... device->PumpEvents = pumpEvents; ... }
      
      





キーボードから始めましょう。 キーを押したり離したりするのを中断する機能を切ります。 この関数はイベントを記憶する必要があります(最も単純なケースでは、ローカル変数に書き込むだけで、必要に応じてキューを使用できます)。簡単にするために、最後のイベントのみを保存します。







 static struct input_event last_event; static int sdl_indev_eventhnd(struct input_dev *indev) { /*    ,   last_event */ while (0 == input_dev_event(indev, &last_event)) { } }
      
      





次に、 pumpEvents()



イベントpumpEvents()



処理し、SDLに渡します。







 static void pumpEvents(_THIS) { SDL_Scancode scancode; bool pressed; scancode = scancode_from_event(&last_event); pressed = is_press(last_event); if (pressed) { SDL_SendKeyboardKey(SDL_PRESSED, scancode); } else { SDL_SendKeyboardKey(SDL_RELEASED, scancode); } }
      
      





キーコードとSDL_Scancodeの詳細

SDLはキーコードに独自の列挙型を使用するため、OSキーコードをSDLコードに変換する必要があります。







これらのコードのリストは、 SDL_scancode.h



ファイルで定義されてSDL_scancode.h



ます







たとえば、次のようにASCIIコードを変換できます(すべてのASCII文字がここにあるわけではありませんが、これらで十分です)。







 static int key_to_sdl[] = { [' '] = SDL_SCANCODE_SPACE, ['\r'] = SDL_SCANCODE_RETURN, [27] = SDL_SCANCODE_ESCAPE, ['0'] = SDL_SCANCODE_0, ['1'] = SDL_SCANCODE_1, ... ['8'] = SDL_SCANCODE_8, ['9'] = SDL_SCANCODE_9, ['a'] = SDL_SCANCODE_A, ['b'] = SDL_SCANCODE_B, ['c'] = SDL_SCANCODE_C, ... ['x'] = SDL_SCANCODE_X, ['y'] = SDL_SCANCODE_Y, ['z'] = SDL_SCANCODE_Z, };
      
      





それはすべてキーボードで、残りはSDLとQuake自体によって処理されます。 ところで、キーストロークの処理のどこかで、quakeはQEMUでサポートされていない命令を使用するため、x86仮想マシンから解釈された仮想マシンに切り替える必要があることがBASE_CFLAGS += -DNO_VM_COMPILED



しました。 BASE_CFLAGS += -DNO_VM_COMPILED



ため、MakefileにBASE_CFLAGS += -DNO_VM_COMPILED



を追加します。







その後、最後に、スクリーンセーバーを荘厳に「スキップ」し、ゲームを開始することもできます(エラーをハッキングする:))。 非常に低いfpsであるにもかかわらず、すべてが本来のとおりにレンダリングされることは嬉しい驚きでした。













これで、マウスのサポートを開始できます。 マウス割り込みの場合、別のハンドラーが必要になり、イベント処理は少し複雑になる必要があります。 左マウスボタンのみに制限します。 同様に、正しいキー、ホイールなどを追加できることは明らかです。







 static void pumpEvents(_THIS) { if (from_keyboard(&last_event)) { /*      */ ... } else { /*      */ if (is_left_click(&last_event)) { /*     */ SDL_SendMouseButton(0, 0, SDL_PRESSED, SDL_BUTTON_LEFT); } else if (is_left_release(&last_event)) { /*     */ SDL_SendMouseButton(0, 0, SDL_RELEASED, SDL_BUTTON_LEFT); } else { /*   */ SDL_SendMouseMotion(0, 0, 1, mouse_diff_x(), /*      */ mouse_diff_y()); /*      */ } } }
      
      





その後、カメラを制御して撮影することが可能になります! 実際、これで遊ぶにはすでに十分です:)













最適化



もちろん、コントロールと何らかのグラフィックスがあるのはクールですが、そのようなFPSは絶対に価値がありません。 ほとんどの場合、ほとんどの時間はOpenGLに費やされ(それはソフトウェアであり、さらにSIMDは使用されません)、ハードウェアサポートの実装が長すぎて難しいタスクです。







少し血でゲームをスピードアップしてみましょう。







コンパイラーの最適化と解像度の削減



ゲーム、すべてのライブラリ、およびOS自体を-O3



アセンブルしてい-O3



(突然、誰かがこの場所を読んでも、このフラグが何であるかわからない場合-GCC最適化フラグの詳細については、 こちらを参照してください )。







さらに、最小解像度-320x240を使用して、プロセッサの作業を容易にします。







Kvm



KVM(カーネルベースの仮想マシン)を使用すると、ハードウェア仮想化(Intel VTおよびAMD-V)を使用してパフォーマンスを改善できます。 Qemuはこのメカニズムをサポートしています。これを使用するには、以下を実行する必要があります。







まず、BIOSで仮想化サポートを有効にする必要があります。 Gigabyte B450M DS3Hマザーボードを使用しており、AMD-VはMIT-> Advanced Frequency Settings-> Advanced CPU Core Settings-> SVM Mode-> Enabled(Gigabyte、何が問題なのですか?)でオンにします。







次に、必要なパッケージを配置し、適切なモジュールを追加します







 sudo apt install qemu-kvm sudo modprobe kvm-amd #  kvm-intel
      
      





これで、qemuフラグ-enable-kvm



(またはハードウェアアクセラレーションを使用しないように-no-kvm



)を渡すことができます。







まとめ





ゲームが開始され、グラフィックが必要に応じて表示され、コントロールが機能しています。 残念ながら、グラフィックスは1つのスレッドでCPU上に描画され、SIMDもありません。これは、fpsが低いため(1秒あたり2〜3フレーム)、制御が非常に不便です。







移植プロセスは興味深いものでした。 将来的には、ハードウェアグラフィックスアクセラレーションを備えたプラットフォーム上で地震を開始することが可能になるかもしれませんが、今のところは何であるかについて説明します。








All Articles