
Dalam sistem operasi Embox (di mana saya adalah pengembangnya), dukungan untuk OpenGL muncul beberapa waktu lalu, tetapi tidak ada pemeriksaan kinerja yang masuk akal, hanya menyajikan adegan dengan beberapa grafis primitif.
Saya tidak pernah tertarik dengan gamedev, meskipun, tentu saja, saya suka game, dan memutuskan - ini adalah cara yang baik untuk bersenang-senang, tetapi pada saat yang sama memeriksa OpenGL dan melihat bagaimana game berinteraksi dengan OS.
Pada artikel ini, saya akan berbicara tentang cara membangun dan menjalankan Quake3 di Embox.
Lebih tepatnya, kita tidak akan menjalankan Quake3 sendiri, tetapi berdasarkan ioquake3 , yang juga memiliki kode sumber terbuka. Untuk kesederhanaan, kami akan memanggil ioquake3 just gempa :)
Saya akan segera melakukan reservasi bahwa artikel tersebut tidak menganalisis kode sumber Quake dan arsitekturnya (Anda dapat membacanya di sini , ada terjemahan di Habré ), dan artikel ini akan fokus pada cara memastikan game diluncurkan pada sistem operasi yang baru.
Fragmen kode yang disajikan dalam artikel disederhanakan untuk pemahaman yang lebih baik: memeriksa kesalahan dihilangkan, pseudo-code digunakan, dan sebagainya. Sumber asli dapat ditemukan di repositori kami .
Ketergantungan
Anehnya, tidak banyak perpustakaan diperlukan untuk membangun Quake3. Kami akan membutuhkan:
- POSIX + LibC -
malloc()
/ memcpy()
/ printf()
dan seterusnya - libcurl - jaringan
- Mesa3D - Dukungan OpenGL
- SDL - input dan dukungan perangkat audio
Dengan paragraf pertama, dan semuanya jelas - tanpa fungsi-fungsi ini sulit dilakukan ketika berkembang di C, dan penggunaan panggilan ini sangat diharapkan. Oleh karena itu, dukungan untuk antarmuka ini tersedia di hampir semua sistem operasi, dan dalam hal ini, praktis tidak perlu menambahkan fungsionalitas. Saya harus berurusan dengan yang lain.
libcurl
Itu yang paling sederhana. Libc cukup untuk membangun libcurl (tentu saja, beberapa fitur tidak akan tersedia, tetapi mereka tidak akan diperlukan). Mengkonfigurasi dan membangun perpustakaan ini secara statis sangat sederhana.
Biasanya, baik aplikasi dan perpustakaan dihubungkan secara dinamis, tetapi karena di Embox, mode utama menautkan dalam satu gambar, kami akan menghubungkan semuanya secara statis.
Bergantung pada sistem build yang digunakan, langkah-langkah spesifik akan bervariasi, tetapi artinya adalah seperti ini:
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 adalah kerangka kerja open source untuk bekerja dengan grafik, mendukung sejumlah antarmuka (OpenCL, Vulkan, dan lainnya), tetapi dalam hal ini kami tertarik dengan OpenGL. Porting kerangka besar seperti itu adalah topik dari artikel terpisah. Saya akan membatasi diri hanya untuk apa yang sudah dimiliki Embox Mesa3D :) Tentu saja, setiap implementasi OpenGL cocok di sini.
Sdl
SDL adalah kerangka kerja lintas platform untuk bekerja dengan perangkat input, audio dan grafik.
Untuk saat ini, kami akan memalu semuanya kecuali gambar, dan untuk menggambar bingkai, kami akan menulis fungsi rintisan untuk melihat kapan mereka mulai dipanggil.
Backend untuk bekerja dengan grafik diatur dalam SDL2-2.0.8/src/video/SDL_video.c
.
Itu terlihat seperti ini:
static VideoBootStrap *bootstrap[] = { #if SDL_VIDEO_DRIVER_COCOA &COCOA_bootstrap, #endif #if SDL_VIDEO_DRIVER_X11 &X11_bootstrap, #endif ... }
Agar tidak repot dengan dukungan "normal" untuk platform baru, cukup tambahkan VideoBootStrap
Anda
Untuk kesederhanaan, Anda dapat mengambil sesuatu sebagai dasar, misalnya src/video/qnx/video.c
atau src/video/raspberry/SDL_rpivideo.c
, tapi pertama-tama kami akan membuat implementasinya hampir kosong:
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 };
Tambahkan VideoBootStrap
Anda ke array:
static VideoBootStrap *bootstrap[] = { &EMBOX_bootstrap, #if SDL_VIDEO_DRIVER_COCOA &COCOA_bootstrap, #endif #if SDL_VIDEO_DRIVER_X11 &X11_bootstrap, #endif ... }
Pada dasarnya, pada titik ini Anda sudah dapat mengkompilasi SDL. Seperti halnya libcurl, detail kompilasi akan bergantung pada sistem build tertentu, tetapi Anda perlu melakukan sesuatu seperti ini:
./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
Kami mengumpulkan gempa itu sendiri
Quake3 melibatkan penggunaan perpustakaan dinamis, tetapi kami akan menautkannya secara statis, seperti yang lainnya.
Untuk melakukan ini, atur beberapa variabel di Makefile
CROSS_COMPILING=1 USE_OPENAL=0 USE_OPENAL_DLOPEN=0 USE_RENDERER_DLOPEN=0 SHLIBLDFLAGS=-static
Peluncuran pertama
Untuk mempermudah, kita akan berjalan di qemu / x86. Untuk melakukan ini, Anda perlu menginstalnya (di sini dan di bawah ini akan ada perintah untuk Debian, untuk paket distribusi lainnya dapat dipanggil secara berbeda).
sudo apt install qemu-system-i386
Dan peluncurannya sendiri:
qemu-system-i386 -kernel build/base/bin/embox -m 1024 -vga std -serial stdio
Namun, ketika memulai Gempa, kami segera mendapatkan kesalahan
> 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
Kesalahan tidak ditampilkan oleh game, tetapi oleh sistem operasi. Debag menunjukkan bahwa kesalahan ini disebabkan oleh dukungan SIMD yang tidak lengkap untuk x86 di QEMU: bagian dari instruksi tidak didukung dan melempar pengecualian perintah yang tidak dikenal (Opcode Tidak Valid). upd: Seperti yang disarankan oleh WGH dalam komentar, masalah sebenarnya adalah bahwa saya lupa untuk secara eksplisit mengaktifkan dukungan SSE di cr0 / cr4, jadi semuanya baik-baik saja dengan QEMU.
Ini terjadi bukan di Quake itu sendiri, tetapi di OpenLibM (ini adalah perpustakaan yang kami gunakan untuk mengimplementasikan fungsi matematika - sin()
, expf()
dan sejenisnya). Patch OpenLibm sehingga __test_sse()
tidak melakukan pemeriksaan nyata pada SSE, tetapi hanya percaya bahwa tidak ada dukungan.
Langkah-langkah di atas sudah cukup untuk dijalankan, output berikut terlihat di konsol:
> 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
Sudah tidak buruk, Quake3 mencoba memulai dan bahkan menampilkan pesan kesalahan! Seperti yang Anda lihat, ia tidak memiliki file di direktori baseq3
. Ini berisi suara, tekstur dan semua itu. Catatan, pak0.pk3
harus diambil dari CD-ROM berlisensi (ya, open source tidak menyiratkan penggunaan gratis).
Persiapan disk
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
Sekarang Anda dapat mentransfer perangkat blok ke qemu
qemu-system-i386 -kernel build/base/bin/embox -m 1024 -vga std -serial stdio -hda quake.img
Ketika sistem dimulai, pasang disk di /mnt
dan jalankan quake3 di direktori ini, kali ini crash kemudian
> 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
Kesalahan ini lagi dengan SIMD di Qemu. upd: Seperti yang disarankan oleh WGH dalam komentar, masalah sebenarnya adalah bahwa saya lupa untuk secara eksplisit mengaktifkan dukungan SSE di cr0 / cr4, jadi semuanya baik-baik saja dengan QEMU. Kali ini, instruksi digunakan di mesin virtual Quake3 x86. Masalahnya dipecahkan dengan mengganti implementasi untuk x86 dengan VM yang ditafsirkan (lebih lanjut tentang mesin virtual Quake3 dan, pada prinsipnya, fitur arsitektur, Anda dapat membaca semuanya di artikel yang sama ). Setelah itu, fungsi kami untuk SDL mulai dipanggil, tetapi, tentu saja, tidak ada yang terjadi, karena fungsi-fungsi ini belum melakukan apa-apa.
Tambahkan dukungan grafis
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; }
Handler kedua diperlukan untuk memberi tahu SDL yang berfungsi untuk memanggil saat bekerja dengan OpenGL.
Untuk melakukan ini, kami memulai array dan dari awal ke awal kami memeriksa panggilan mana yang hilang, kira-kira seperti ini:
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; }
Dalam beberapa restart, daftar menjadi cukup lengkap untuk menggambar layar pembuka dan menu. Untungnya, Mesa memiliki semua fungsi yang diperlukan. Satu-satunya hal adalah karena alasan tertentu tidak ada fungsi glGetString()
, glGetString()
harus digunakan _mesa_GetString()
.
Sekarang ketika aplikasi dimulai, layar splash muncul, bersorak!

Tambahkan perangkat input
Tambahkan dukungan keyboard dan mouse ke SDL.
Untuk bekerja dengan acara, Anda perlu menambahkan penangan
static SDL_VideoDevice *createDevice(int devindex) { ... device->PumpEvents = pumpEvents; ... }
Mari kita mulai dengan keyboard. Kami menutup fungsi untuk mengganggu menekan / melepaskan kunci. Fungsi ini harus mengingat peristiwa (dalam kasus paling sederhana, kita cukup menulis ke variabel lokal, antrian dapat digunakan jika diinginkan), untuk kesederhanaan kita hanya akan menyimpan acara terakhir.
static struct input_event last_event; static int sdl_indev_eventhnd(struct input_dev *indev) { while (0 == input_dev_event(indev, &last_event)) { } }
Lalu, di pumpEvents()
memproses acara dan meneruskannya ke 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); } }
Lebih lanjut tentang Kode Kunci dan SDL_ScancodeSDL menggunakan enum sendiri untuk kode kunci, jadi Anda harus mengonversi kode kunci OS ke kode SDL.
Daftar kode-kode ini didefinisikan dalam file SDL_scancode.h
Misalnya, Anda dapat mengonversi kode ASCII seperti ini (tidak semua karakter ASCII ada di sini, tetapi ini sudah cukup):
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, };
Itu saja dengan keyboard, sisanya akan ditangani oleh SDL dan Quake sendiri. Ngomong-ngomong, ternyata di sekitar sini bahwa di suatu tempat dalam pemrosesan penekanan tombol, gempa menggunakan instruksi yang tidak didukung oleh QEMU, Anda harus beralih ke mesin virtual yang ditafsirkan dari mesin virtual x86, untuk ini kami menambahkan BASE_CFLAGS += -DNO_VM_COMPILED
ke Makefile.
Setelah itu, akhirnya, Anda bisa "melewatkan" screensaver dan bahkan memulai permainan (meretas beberapa kesalahan :)). Sangat terkejut bahwa semuanya dibuat sebagaimana mestinya, meskipun dengan fps yang sangat rendah.

Sekarang Anda dapat memulai dukungan mouse. Untuk interupsi mouse, Anda perlu satu handler lagi, dan penanganan event perlu sedikit rumit. Kami membatasi diri hanya untuk tombol mouse kiri. Jelas bahwa dengan cara yang sama, Anda dapat menambahkan kunci, roda, dll.
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()); } } }
Setelah itu, menjadi mungkin untuk mengontrol kamera dan memotret, tepuk tangan! Sebenarnya, ini sudah cukup untuk dimainkan :)

Optimasi
Tentu saja keren, bahwa ada kontrol dan beberapa jenis gambar, tetapi FPS seperti itu benar-benar tidak berharga. Kemungkinan besar, sebagian besar waktu dihabiskan untuk OpenGL (dan ini adalah perangkat lunak, dan, apalagi, SIMD tidak digunakan), dan implementasi dukungan perangkat keras adalah tugas yang terlalu panjang dan sulit.
Mari kita coba percepat permainan dengan sedikit darah.
Optimalisasi kompiler dan resolusi lebih rendah
Kami sedang membangun permainan, semua perpustakaan, dan OS itu sendiri dengan -O3
(jika, tiba-tiba, seseorang membaca ke tempat ini, tetapi tidak tahu apa bendera ini - lebih banyak tentang bendera optimasi GCC dapat ditemukan di sini ).
Selain itu, kami menggunakan resolusi minimum - 320x240 untuk memudahkan kerja prosesor.
Kvm
KVM (Mesin Virtual berbasis Kernel) memungkinkan Anda untuk menggunakan virtualisasi perangkat keras (Intel VT dan AMD-V) untuk meningkatkan kinerja. Qemu mendukung mekanisme ini, untuk menggunakannya Anda perlu melakukan hal berikut.
Pertama, Anda perlu mengaktifkan dukungan virtualisasi di BIOS. Saya memiliki motherboard Gigabyte B450M DS3H, dan AMD-V dihidupkan melalui MIT -> Pengaturan Frekuensi Lanjut -> Pengaturan Inti CPU Lanjutan -> Mode SVM -> Diaktifkan (Gigabyte, ada apa dengan Anda?).
Kemudian kami meletakkan paket yang diperlukan dan menambahkan modul yang sesuai
sudo apt install qemu-kvm sudo modprobe kvm-amd
Itu saja, sekarang Anda dapat melewati qemu flag -enable-kvm
(atau -no-kvm
agar tidak menggunakan akselerasi perangkat keras).
Ringkasan
Permainan dimulai, grafik ditampilkan sesuai kebutuhan, kontrol berfungsi. Sayangnya, gambar diambil pada CPU dalam satu aliran, juga tanpa SIMD, karena fps rendah (2-3 frame per detik) sangat tidak nyaman untuk dikendalikan.
Proses porting menarik. Mungkin di masa depan akan dimungkinkan untuk meluncurkan gempa pada platform dengan akselerasi grafis perangkat keras, tetapi untuk sekarang saya akan fokus pada apa yang ada.