
في نظام تشغيل Embox (الذي أنا المطور) ، ظهر دعم OpenGL منذ بعض الوقت ، ولكن لم يكن هناك فحص أداء معقول ، فقط تقديم مشاهد مع العديد من الأوليات الرسومية.
لم أكن أبدًا مهتمًا بشكل خاص بـ gamedev ، على الرغم من أنني بالطبع أحب الألعاب ، وقررت - هذه طريقة جيدة للاستمتاع ، ولكن في نفس الوقت تحقق من OpenGL وشاهد كيف تتفاعل الألعاب مع نظام التشغيل.
في هذه المقالة ، سأتحدث عن كيفية بناء وتشغيل Quake3 على Embox.
بتعبير أدق ، لن نقوم بتشغيل Quake3 بنفسه ، ولكن ioquake3 بناءً عليه ، والذي يحتوي أيضًا على كود مفتوح المصدر. من أجل البساطة ، سوف نسمي ioquake3 مجرد زلزال :)
سأحجز على الفور لأن المقالة لا تحلل شفرة مصدر الزلزال وبنيتها (يمكنك القراءة عنها هنا ، هناك ترجمات على حبري ) ، وستركز هذه المقالة على كيفية التأكد من إطلاق اللعبة على نظام التشغيل الجديد.
تم تبسيط أجزاء التعليمات البرمجية المقدمة في المقالة لفهم أفضل: يتم حذف عمليات التحقق من الأخطاء ، ويتم استخدام التعليمات البرمجية الزائفة ، وما إلى ذلك. يمكن العثور على المصادر الأصلية في مستودعنا .
التبعيات
من الغريب أن هناك حاجة إلى العديد من المكتبات لبناء Quake3. سنحتاج إلى:
- POSIX + LibC -
malloc()
/ memcpy()
/ printf()
وما إلى ذلك - libcurl - التشبيك
- Mesa3D - دعم OpenGL
- SDL - دعم الإدخال والصوت
مع الفقرة الأولى ، وبالتالي كل شيء واضح - بدون هذه الوظائف من الصعب القيام به عند التطوير في C ، ومن المتوقع استخدام هذه المكالمات. لذلك ، يتوفر دعم هذه الواجهات بطريقة أو بأخرى في جميع أنظمة التشغيل تقريبًا ، وفي هذه الحالة ، لم تكن هناك حاجة عمليًا لإضافة وظائف. اضطررت للتعامل مع الباقي.
ليبكورل
لقد كان أبسط. Libc يكفي لبناء libcurl (بالطبع ، لن تكون بعض الميزات متاحة ، ولكن لن تكون هناك حاجة إليها). تكوين هذه المكتبة وإنشائها بسيط للغاية.
عادة ، يتم ربط كل من التطبيقات والمكتبات ديناميكيًا ، ولكن بسبب في Embox ، يتم ربط الوضع الرئيسي في صورة واحدة ، وسنقوم بربط كل شيء بشكل ثابت.
اعتمادًا على نظام البناء المستخدم ، ستختلف الخطوات المحددة ، ولكن المعنى شيء مثل هذا:
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
.
يبدو شيء مثل هذا:
static VideoBootStrap *bootstrap[] = { #if SDL_VIDEO_DRIVER_COCOA &COCOA_bootstrap, #endif #if SDL_VIDEO_DRIVER_X11 &X11_bootstrap, #endif ... }
حتى لا تهتم بالدعم "العادي" للنظام الأساسي الجديد ، ما عليك سوى إضافة VideoBootStrap
من أجل البساطة ، يمكنك أن تأخذ شيئًا كأساس ، على سبيل المثال src/video/qnx/video.c
أو src/video/raspberry/SDL_rpivideo.c
، ولكن أولاً سنجعل التنفيذ فارغًا تقريبًا:
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 };
أضف VideoBootStrap
إلى الصفيف:
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
نجمع الزلزال نفسه
يتضمن 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
ومع ذلك ، عند بدء الزلزال ، نتلقى خطأ على الفور
> 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 أن هذا الخطأ ناتج عن دعم غير كامل لبطاقة SIMD لـ x86 في QEMU: جزء من التعليمات غير مدعوم ويؤدي إلى استثناء أمر غير معروف (رمز غير صالح). تحديث: كما اقترح WGH في التعليقات ، كانت المشكلة الحقيقية هي أنني نسيت تمكين دعم SSE بشكل صريح في cr0 / cr4 ، لذلك كل شيء على ما يرام مع QEMU.
لا يحدث هذا في Quake نفسه ، ولكن في OpenLibM (هذه هي المكتبة التي نستخدمها لتنفيذ الوظائف الرياضية - sin()
، expf()
وما شابه). قم بتصحيح OpenLibm بحيث لا يقوم __test_sse()
بإجراء فحص حقيقي على 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
. يحتوي على الأصوات والقوام وكل ذلك. ملاحظة ، يجب أن تؤخذ pak0.pk3
من قرص مضغوط مرخص (نعم ، المصدر المفتوح لا يعني الاستخدام المجاني).
تحضير القرص
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
هذا الخطأ مرة أخرى مع SIMD في Qemu. تحديث: كما اقترح WGH في التعليقات ، كانت المشكلة الحقيقية هي أنني نسيت تمكين دعم SSE بشكل صريح في cr0 / cr4 ، لذلك كل شيء على ما يرام مع QEMU. هذه المرة ، يتم استخدام التعليمات في الجهاز الظاهري Quake3 x86. تم حل المشكلة عن طريق استبدال تطبيق x86 بجهاز VM مترجم (المزيد عن الجهاز الظاهري Quake3 ، ومن حيث المبدأ ، الميزات المعمارية ، يمكنك قراءة كل شيء في نفس المقالة ). بعد ذلك ، يتم استدعاء وظائفنا لـ SDL ، ولكن ، بالطبع ، لا يحدث شيء ، لأن هذه الوظائف لا تفعل شيئًا بعد.
أضف دعم الرسومات
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; }
المعالج الثاني مطلوب لإخبار SDL بالوظائف التي يجب الاتصال بها عند العمل مع OpenGL.
للقيام بذلك ، نبدأ مصفوفة ومن البداية إلى البداية نتحقق من المكالمات المفقودة ، شيء من هذا القبيل:
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; }
في بعض عمليات إعادة التشغيل ، تصبح القائمة كاملة بما يكفي لرسم شاشة البداية والقائمة. لحسن الحظ ، ميسا لديه جميع الوظائف اللازمة. الشيء الوحيد هو أنه لسبب ما لا توجد وظيفة 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) { 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 تعداده الخاص لرموز المفاتيح ، لذا يجب عليك تحويل رمز مفتاح نظام التشغيل إلى رمز SDL.
يتم تعريف قائمة بهذه الرموز في ملف 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 نفسها. بالمناسبة ، اتضح هنا أنه في مكان ما في معالجة ضغطات المفاتيح ، يستخدم الزلزال تعليمات غير مدعومة من قبل QEMU ، يجب عليك التبديل إلى الجهاز الظاهري المفسر من الجهاز الظاهري x86 ، لهذا نضيف BASE_CFLAGS += -DNO_VM_COMPILED
إلى Makefile.
بعد ذلك ، أخيرًا ، يمكنك "تخطي" شاشات التوقف رسميًا وحتى بدء اللعبة (اختراق بعض الأخطاء :)). كان من المفاجئ أن يتم تقديم كل شيء كما يجب ، وإن كان بمعدل إطار منخفض جدًا.

يمكنك الآن بدء دعم الماوس. بالنسبة لمقاطعات الماوس ، تحتاج إلى معالج واحد آخر ، وستكون معالجة الأحداث معقدة بعض الشيء. نحن نقتصر على زر الماوس الأيسر فقط. من الواضح أنه بنفس الطريقة ، يمكنك إضافة المفتاح الصحيح ، والعجلة ، وما إلى ذلك.
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) ، وتنفيذ دعم الأجهزة طويل جدًا ومعقد.
دعونا نحاول تسريع اللعبة بقليل من الدم.
تحسين المترجم ودقة أقل
نحن نقوم بتجميع اللعبة ، وجميع المكتبات ، ونظام التشغيل نفسه مع -O3
(إذا ، فجأة ، قرأ شخص ما هذا المكان ، لكنه لا يعرف ما هو هذا العلم - يمكن العثور على المزيد حول أعلام التحسين في دول مجلس التعاون الخليجي هنا ).
بالإضافة إلى ذلك ، نستخدم الحد الأدنى من الدقة - 320 × 240 لتسهيل عمل المعالج.
Kvm
يسمح لك KVM (الجهاز الظاهري القائم على Kernel) باستخدام المحاكاة الافتراضية للأجهزة (Intel VT و AMD-V) لتحسين الأداء. يدعم Qemu هذه الآلية ، لاستخدامها عليك القيام بما يلي.
أولاً ، تحتاج إلى تمكين دعم المحاكاة الافتراضية في BIOS. لدي لوحة أم Gigabyte B450M DS3H ، ويتم تشغيل AMD-V عبر MIT -> إعدادات التردد المتقدمة -> إعدادات Advanced CPU Core -> وضع SVM -> ممكّن (Gigabyte ، ما خطبك؟).
ثم نضع الحزمة اللازمة ونضيف الوحدة المناسبة
sudo apt install qemu-kvm sudo modprobe kvm-amd
هذا كل شيء ، الآن يمكنك تمرير qemu -enable-kvm
flag (أو -no-kvm
حتى لا تستخدم تسريع الأجهزة).
الملخص
بدأت اللعبة ، يتم عرض الرسومات حسب الحاجة ، يعمل عنصر التحكم. لسوء الحظ ، يتم رسم الرسومات على وحدة المعالجة المركزية في دفق واحد ، أيضًا بدون بطاقة SIMD ، نظرًا لانخفاض معدل الإطارات في الثانية (2-3 إطارات في الثانية) ، فمن غير المناسب التحكم بها.
كانت عملية النقل مثيرة للاهتمام. ربما في المستقبل سيكون من الممكن إطلاق زلزال على منصة مع تسريع رسومات الأجهزة ، ولكن في الوقت الحالي سأركز على ما هو.