ينقل الزلزال 3


في نظام تشغيل 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 .


يبدو شيء مثل هذا:


 /* Available video drivers */ 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 ، ولكن أولاً سنجعل التنفيذ فارغًا تقريبًا:


 /* 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 #      

نجمع الزلزال نفسه


يتضمن 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; ... } /*   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; } 

المعالج الثاني مطلوب لإخبار 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) { /*    ,   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 تعداده الخاص لرموز المفاتيح ، لذا يجب عليك تحويل رمز مفتاح نظام التشغيل إلى رمز 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 #  kvm-intel 

هذا كل شيء ، الآن يمكنك تمرير qemu -enable-kvm flag (أو -no-kvm حتى لا تستخدم تسريع الأجهزة).


الملخص



بدأت اللعبة ، يتم عرض الرسومات حسب الحاجة ، يعمل عنصر التحكم. لسوء الحظ ، يتم رسم الرسومات على وحدة المعالجة المركزية في دفق واحد ، أيضًا بدون بطاقة SIMD ، نظرًا لانخفاض معدل الإطارات في الثانية (2-3 إطارات في الثانية) ، فمن غير المناسب التحكم بها.


كانت عملية النقل مثيرة للاهتمام. ربما في المستقبل سيكون من الممكن إطلاق زلزال على منصة مع تسريع رسومات الأجهزة ، ولكن في الوقت الحالي سأركز على ما هو.

Source: https://habr.com/ru/post/ar428634/


All Articles