
Unter dem Embox- Betriebssystem (dessen Entwickler ich bin) wurde OpenGL vor einiger Zeit unterstützt, es gab jedoch keine sinnvolle Leistungsprüfung, sondern nur das Rendern von Szenen mit mehreren grafischen Grundelementen.
Ich habe mich nie besonders für Gamedev interessiert, obwohl ich natürlich Spiele mag, und mich entschieden - dies ist eine gute Möglichkeit, Spaß zu haben, aber gleichzeitig OpenGL zu überprüfen und zu sehen, wie die Spiele mit dem Betriebssystem interagieren.
In diesem Artikel werde ich darüber sprechen, wie Quake3 auf Embox erstellt und ausgeführt wird.
Genauer gesagt werden wir nicht Quake3 selbst ausführen, sondern ioquake3 basierend darauf, das auch Open Source Code enthält. Der Einfachheit halber nennen wir ioquake3 nur Beben :)
Ich werde sofort reservieren, dass der Artikel den Quake-Quellcode und seine Architektur nicht analysiert (Sie können hier darüber lesen, es gibt Übersetzungen zum Habré ), und dieser Artikel wird sich darauf konzentrieren, wie sichergestellt werden kann, dass das Spiel auf dem neuen Betriebssystem gestartet wird.
Die im Artikel vorgestellten Codefragmente werden zum besseren Verständnis vereinfacht: Fehlerprüfungen werden weggelassen, Pseudocode wird verwendet und so weiter. Originalquellen finden Sie in unserem Repository .
Abhängigkeiten
Seltsamerweise werden nicht viele Bibliotheken benötigt, um Quake3 zu erstellen. Wir werden brauchen:
- POSIX + LibC -
malloc()
/ memcpy()
/ printf()
und so weiter - libcurl - Vernetzung
- Mesa3D - OpenGL-Unterstützung
- SDL - Unterstützung für Eingangs- und Audiogeräte
Mit dem ersten Absatz ist also alles klar - ohne diese Funktionen ist es schwierig, in C zu entwickeln, und die Verwendung dieser Aufrufe wird durchaus erwartet. Daher ist die Unterstützung für diese Schnittstellen in fast allen Betriebssystemen irgendwie verfügbar, und in diesem Fall war es praktisch nicht erforderlich, Funktionen hinzuzufügen. Ich musste mich um den Rest kümmern.
libcurl
Es war das einfachste. Libc reicht aus, um libcurl zu erstellen (einige Funktionen sind natürlich nicht verfügbar, werden aber nicht benötigt). Das Konfigurieren und Erstellen dieser Bibliothek ist statisch sehr einfach.
Normalerweise werden sowohl Anwendungen als auch Bibliotheken dynamisch verknüpft, aber weil In Embox ist der Hauptmodus das Verknüpfen in einem Bild. Wir verknüpfen alles statisch.
Abhängig vom verwendeten Build-System variieren die spezifischen Schritte, aber die Bedeutung ist ungefähr so:
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
Mesa / OpenGL
Mesa ist ein Open-Source-Framework für die Arbeit mit Grafiken. Es unterstützt eine Reihe von Schnittstellen (OpenCL, Vulkan und andere). In diesem Fall sind wir jedoch an OpenGL interessiert. Das Portieren eines so großen Frameworks ist das Thema eines separaten Artikels. Ich werde mich nur auf das beschränken, was Embox Mesa3D bereits hat :) Natürlich ist hier jede OpenGL-Implementierung geeignet.
Sdl
SDL ist ein plattformübergreifendes Framework für die Arbeit mit Eingabegeräten, Audio und Grafik.
Im Moment werden wir alles außer Grafiken hämmern und zum Zeichnen von Frames Stub-Funktionen schreiben, um zu sehen, wann sie aufgerufen werden.
Die Backends für die Arbeit mit Grafiken sind in SDL2-2.0.8/src/video/SDL_video.c
.
Es sieht ungefähr so aus:
static VideoBootStrap *bootstrap[] = { #if SDL_VIDEO_DRIVER_COCOA &COCOA_bootstrap, #endif #if SDL_VIDEO_DRIVER_X11 &X11_bootstrap, #endif ... }
VideoBootStrap
einfach Ihren VideoBootStrap
hinzu, um die "normale" Unterstützung für die neue Plattform nicht zu VideoBootStrap
Der Einfachheit halber können Sie etwas als Grundlage nehmen, zum Beispiel src/video/qnx/video.c
oder src/video/raspberry/SDL_rpivideo.c
, aber zuerst machen wir die Implementierung fast leer:
typedef struct VideoBootStrap { const char *name; const char *desc;``` int (*available) (void); SDL_VideoDevice *(*create) (int devindex); } VideoBootStrap; 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 };
Fügen Sie Ihre VideoBootStrap
zum Array hinzu:
static VideoBootStrap *bootstrap[] = { &EMBOX_bootstrap, #if SDL_VIDEO_DRIVER_COCOA &COCOA_bootstrap, #endif #if SDL_VIDEO_DRIVER_X11 &X11_bootstrap, #endif ... }
Grundsätzlich können Sie zu diesem Zeitpunkt bereits SDL kompilieren. Wie bei libcurl hängen die Kompilierungsdetails vom jeweiligen Build-System ab, aber irgendwie müssen Sie Folgendes tun:
./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
Wir sammeln Quake selbst
Quake3 beinhaltet die Verwendung dynamischer Bibliotheken, aber wir werden es wie alles andere statisch verknüpfen.
Legen Sie dazu einige Variablen im Makefile fest
CROSS_COMPILING=1 USE_OPENAL=0 USE_OPENAL_DLOPEN=0 USE_RENDERER_DLOPEN=0 SHLIBLDFLAGS=-static
Erster Start
Der Einfachheit halber werden wir auf qemu / x86 laufen. Dazu müssen Sie es installieren (hier und unten gibt es Befehle für Debian, da andere Distributionspakete möglicherweise anders aufgerufen werden).
sudo apt install qemu-system-i386
Und der Start selbst:
qemu-system-i386 -kernel build/base/bin/embox -m 1024 -vga std -serial stdio
Beim Starten von Quake wird jedoch sofort eine Fehlermeldung angezeigt
> 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
Der Fehler wird nicht vom Spiel, sondern vom Betriebssystem angezeigt. Debag zeigte, dass dieser Fehler durch unvollständige SIMD-Unterstützung für x86 in QEMU verursacht wurde: Ein Teil der Anweisungen wird nicht unterstützt und löst eine unbekannte Befehlsausnahme (ungültiger Opcode) aus. upd: Wie von WGH in den Kommentaren vorgeschlagen, bestand das eigentliche Problem darin, dass ich vergessen habe, die SSE-Unterstützung in cr0 / cr4 explizit zu aktivieren, sodass mit QEMU alles in Ordnung ist.
Dies geschieht nicht in Quake selbst, sondern in OpenLibM (dies ist die Bibliothek, mit der wir mathematische Funktionen implementieren - sin()
, expf()
und dergleichen). Patchen Sie OpenLibm so, dass __test_sse()
SSE nicht wirklich überprüft, sondern einfach glaubt, dass es keine Unterstützung gibt.
Die obigen Schritte reichen aus, um ausgeführt zu werden. Die folgende Ausgabe ist in der Konsole sichtbar:
> 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 ist bereits nicht schlecht, versucht zu starten und zeigt sogar eine Fehlermeldung an! Wie Sie sehen können, fehlen ihm die Dateien im baseq3
Verzeichnis. Es enthält Sounds, Texturen und all das. Beachten Sie, dass pak0.pk3
von einer lizenzierten CD-ROM pak0.pk3
sollte (ja, Open Source bedeutet keine freie Nutzung).
Datenträgervorbereitung
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
Jetzt können Sie das Blockgerät an qemu übertragen
qemu-system-i386 -kernel build/base/bin/embox -m 1024 -vga std -serial stdio -hda quake.img
Wenn das System gestartet wird, mounten Sie die Festplatte auf /mnt
und führen Sie quake3 in diesem Verzeichnis aus. Diesmal stürzt es später ab
> 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
Dieser Fehler tritt erneut bei SIMD in Qemu auf. upd: Wie von WGH in den Kommentaren vorgeschlagen, bestand das eigentliche Problem darin, dass ich vergessen habe, die SSE-Unterstützung in cr0 / cr4 explizit zu aktivieren, sodass mit QEMU alles in Ordnung ist. Dieses Mal werden die Anweisungen in der virtuellen Quake3 x86-Maschine verwendet. Das Problem wurde gelöst, indem die Implementierung für x86 durch eine interpretierte VM ersetzt wurde (mehr über die virtuelle Maschine Quake3 und im Prinzip über Architekturfunktionen können Sie alles im selben Artikel lesen). Danach werden unsere Funktionen für SDL aufgerufen, aber natürlich passiert nichts, weil Diese Funktionen tun noch nichts.
Grafikunterstützung hinzufügen
static SDL_VideoDevice *createDevice(int devindex) { ... device->GL_GetProcAddress = glGetProcAddress; device->GL_CreateContext = glCreateContext; ... } SDL_GLContext glCreateContext(_THIS, SDL_Window *window) { OSMesaContext ctx; sdl_init_buffers(); ctx = OSMesaCreateContextExt(OSMESA_BGRA, 16, 0, 0, NULL); OSMesaMakeCurrent(ctx, fb_base, GL_UNSIGNED_BYTE, fb_width, fb_height); return ctx; }
Der zweite Handler wird benötigt, um der SDL mitzuteilen, welche Funktionen bei der Arbeit mit OpenGL aufgerufen werden sollen.
Dazu starten wir ein Array und prüfen von Anfang bis Anfang, welche Aufrufe fehlen.
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; }
Bei einigen Neustarts wird die Liste vollständig genug, um einen Begrüßungsbildschirm und ein Menü zu zeichnen. Zum Glück hat Mesa alle notwendigen Funktionen. Das einzige ist, dass es aus irgendeinem Grund keine glGetString()
-Funktion gibt, glGetString()
musste glGetString()
verwendet _mesa_GetString()
.
Wenn die Anwendung gestartet wird, wird ein Begrüßungsbildschirm angezeigt. Prost!

Eingabegeräte hinzufügen
Fügen Sie der SDL Tastatur- und Mausunterstützung hinzu.
Um mit Ereignissen arbeiten zu können, müssen Sie einen Handler hinzufügen
static SDL_VideoDevice *createDevice(int devindex) { ... device->PumpEvents = pumpEvents; ... }
Beginnen wir mit der Tastatur. Wir legen eine Funktion auf, um das Drücken / Loslassen einer Taste zu unterbrechen. Diese Funktion sollte sich an das Ereignis erinnern (im einfachsten Fall schreiben wir einfach in eine lokale Variable, Warteschlangen können bei Bedarf verwendet werden). Der Einfachheit halber speichern wir nur das letzte Ereignis.
static struct input_event last_event; static int sdl_indev_eventhnd(struct input_dev *indev) { while (0 == input_dev_event(indev, &last_event)) { } }
Dann pumpEvents()
in pumpEvents()
das Ereignis und übergeben es an die 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); } }
Weitere Informationen zu Schlüsselcodes und SDL_ScancodeSDL verwendet eine eigene Aufzählung für Schlüsselcodes, daher müssen Sie den Betriebssystemschlüsselcode in SDL-Code konvertieren.
Eine Liste dieser Codes ist in der Datei SDL_scancode.h
definiert
Sie können den ASCII-Code beispielsweise folgendermaßen konvertieren (nicht alle ASCII-Zeichen sind hier, aber diese reichen völlig aus):
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, };
Das ist alles mit der Tastatur, der Rest wird von SDL und Quake selbst erledigt. Übrigens stellte sich hier heraus, dass Quake irgendwo bei der Verarbeitung von Tastenanschlägen Anweisungen verwendet, die von QEMU nicht unterstützt werden. Sie müssen von der virtuellen x86-Maschine zur interpretierten virtuellen Maschine wechseln. Dazu fügen wir dem Makefile BASE_CFLAGS += -DNO_VM_COMPILED
.
Danach können Sie die Bildschirmschoner feierlich „überspringen“ und sogar das Spiel starten (Fehler hacken :)). Es war angenehm überrascht, dass alles so gerendert wird, wie es sollte, wenn auch mit sehr niedrigen fps.

Jetzt können Sie die Mausunterstützung starten. Für Maus-Interrupts benötigen Sie einen weiteren Handler, und die Ereignisbehandlung muss etwas kompliziert sein. Wir beschränken uns nur auf die linke Maustaste. Es ist klar, dass Sie auf die gleiche Weise den richtigen Schlüssel, das richtige Rad usw. hinzufügen können.
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()); } } }
Danach wird es möglich, die Kamera zu steuern und zu schießen, Prost! Tatsächlich reicht das schon zum Spielen :)

Optimierung
Es ist natürlich cool, dass es Kontrolle und irgendeine Art von Grafik gibt, aber eine solche FPS ist absolut wertlos. Höchstwahrscheinlich wird die meiste Zeit für die Arbeit an OpenGL aufgewendet (bei dem es sich um Software handelt und außerdem wird SIMD nicht verwendet), und die Implementierung der Hardwareunterstützung ist zu langwierig und kompliziert.
Versuchen wir, das Spiel mit etwas Blut zu beschleunigen.
Compileroptimierung und niedrigere Auflösung
Wir bauen das Spiel, alle Bibliotheken und das Betriebssystem selbst mit -O3
(wenn plötzlich jemand an dieser Stelle liest, aber nicht weiß, was dieses Flag ist - mehr über GCC-Optimierungsflags finden Sie hier ).
Zusätzlich verwenden wir die Mindestauflösung von 320 x 240, um die Arbeit des Prozessors zu erleichtern.
Kvm
Mit KVM (Kernel-based Virtual Machine) können Sie die Hardwarevirtualisierung (Intel VT und AMD-V) verwenden, um die Leistung zu verbessern. Qemu unterstützt diesen Mechanismus. Um ihn zu verwenden, müssen Sie Folgendes tun.
Zunächst müssen Sie die Virtualisierungsunterstützung im BIOS aktivieren. Ich habe ein Gigabyte B450M DS3H-Motherboard und AMD-V wird über MIT -> Erweiterte Frequenzeinstellungen -> Erweiterte CPU-Kerneinstellungen -> SVM-Modus -> Aktiviert (Gigabyte, was ist los mit Ihnen?) Eingeschaltet.
Dann setzen wir das notwendige Paket und fügen das entsprechende Modul hinzu
sudo apt install qemu-kvm sudo modprobe kvm-amd
Das war's, jetzt können Sie qemu das -enable-kvm
(oder -no-kvm
, um keine Hardwarebeschleunigung zu verwenden).
Zusammenfassung
Das Spiel wurde gestartet, die Grafiken werden nach Bedarf angezeigt, die Steuerung funktioniert. Leider werden die Grafiken in einem Stream auf der CPU gezeichnet, auch ohne SIMD, da die Steuerung aufgrund der niedrigen fps (2-3 Bilder pro Sekunde) sehr unpraktisch ist.
Der Portierungsprozess war interessant. Vielleicht wird es in Zukunft möglich sein, Quake auf einer Plattform mit Hardware-Grafikbeschleunigung zu starten, aber jetzt werde ich mich auf das konzentrieren, was ist.