نقل الزلزال إلى iPod Classic


قم بتشغيل Quake على iPod Classic ( فيديو ).

TL ؛ DR : تمكنت من تشغيل Quake على مشغل MP3. تصف المقالة كيف حدث هذا.

قضيت جزءًا من الصيف الماضي على بعض الأشياء المفضلة لدي: Rockbox و Game Quake id Software. حتى أنني أتيحت لي الفرصة للجمع بين هاتين الهويتين من خلال نقل Quake إلى Rockbox! كان من المستحيل أن أتمنى المزيد!

يروي هذا المنشور قصة كيفية عملها. هذه قصة طويلة تمتد لما يقرب من عامين. بالإضافة إلى ذلك ، فهذه هي محاولتي الأولى لتوثيق عملية التطوير ، المفصلة وغير المنقاة ، على عكس الوثائق الفنية النهائية ، التي كتبتها كثيرًا في حياتي. ستحتوي المقالة أيضًا على تفاصيل فنية ، ولكن أولاً سأحاول التحدث عن عملية التفكير التي أدت إلى إنشاء الكود.

للأسف ، حان الوقت لنقول وداعًا لـ Rockbox و Quake ، على الأقل على المدى القريب. لعدة أشهر ، سيكون وقت الفراغ مصدرًا نادرًا جدًا بالنسبة لي ، لذا قبل الاندفاع إلى العمل ، أسارع إلى التعبير عن أفكاري.

Rockbox


Rockbox هو مشروع مفتوح المصدر غريب ، قضيت الكثير من الوقت في اختراقه. أفضل شيء عن ذلك مكتوب على صفحة الويب: "Rockbox هو برنامج ثابت مجاني لمشغلات الموسيقى الرقمية." هذا صحيح - لقد أنشأنا بديلاً كاملاً لبرنامج المصنع الذي جاء مع مشغلات Sandisk Sansa و Apple iPod والعديد من الأجهزة المدعومة الأخرى.

نحن لا نسعى فقط لإعادة إنشاء وظائف البرامج الثابتة الأصلية ، ولكننا نفذنا أيضًا دعمًا للامتدادات القابلة للتنزيل والتي تسمى البرامج الإضافية - البرامج الصغيرة التي تعمل على مشغل MP3. يحتوي Rockbox بالفعل على العديد من الألعاب والعروض التوضيحية الرائعة ، وأكثرها إثارة للإعجاب هي ألعاب الرماية من منظور الشخص الأول Doom و Duke Nukem 3D 1. لكني شعرت أن هناك شيئًا مفقودًا فيه.

يظهر الزلزال على المسرح


الزلزال هو مطلق النار من منظور الشخص الأول ثلاثي الأبعاد بالكامل. دعونا نرى ما يعنيه ذلك. الكلمات الرئيسية هنا هي "ثلاثية الأبعاد بالكامل" . على عكس Doom و Duke Nukem 3D ، والمشار إليهما عادةً باسم 2.5D (تخيل خريطة ثنائية الأبعاد مع مكون ارتفاع اختياري) ، يتم تنفيذ Quake بأبعاد ثلاثية كاملة. يوجد كل قمة ومضلع في مساحة ثلاثية الأبعاد. هذا يعني أن الحيل الزائفة ثلاثية الأبعاد القديمة لم تعد تعمل - كل شيء يتم في 3D كامل. ومع ذلك ، كنت مشتتًا. باختصار ، الزلزال هو شيء قوي.

والزلزال لا يغفر النكات. أظهر بحثنا أن Quake "يتطلب" معالج x86 بتردد حوالي 100 ميجاهرتز ووحدة FPU ، بالإضافة إلى حوالي 32 ميجابايت من ذاكرة الوصول العشوائي. قبل البدء في الضحك ، تذكر أن الأنظمة الأساسية المستهدفة لـ Rockbox لا يمكن مقارنتها بما ركز عليه John Carmack عند كتابة اللعبة - حتى أن Rockbox يعمل على الأجهزة ذات المعالجات بتردد 11 ميجا هرتز فقط و 2 ميجا بايت من ذاكرة الوصول العشوائي (بالطبع ، لا ينبغي أن تعمل Quake على هذه الأجهزة). مع أخذ ذلك في الاعتبار ، نظرت إلى مجموعتي المتناقصة تدريجيًا من مشغلات الصوت الرقمية واخترت أقوى الناجين: Apple iPod Classic / 6G مع معالج 216 MHz ARMv5E و 64 ميغابايت من ذاكرة الوصول العشوائي (الفهرس Eيشير إلى وجود ملحقات ARM DSP - في وقت لاحق سيكون هذا مهمًا بالنسبة لنا). مواصفات خطيرة ، ولكن هناك بالكاد ما يكفي لإدارة الزلزال.

ميناء


هناك نسخة رائعة من Quake يمكن تشغيلها على SDL . لها الاسم المنطقي SDLQuake . لحسن الحظ ، لقد قمت بالفعل بنقل مكتبة SDL إلى Rockbox (هذا موضوع لمقال آخر) ، لذلك تبين أن إعداد Quake للتجميع عملية بسيطة إلى حد ما: نسخ شجرة المصدر ؛ make؛ نقوم بتصحيح الأخطاء ؛ شطف ، الصابون ، كرر. ربما أرسم الكثير من التفاصيل المملة هنا قليلاً ، ولكن فقط تخيل إعجابي لأنني جمعت وربط ملف الزلزال القابل للتنفيذ بنجاح. كنت سعيدا.

"حسنًا ، قم بتحميله!" اعتقدت.

وتمهيده! لقد استقبلت بخلفية وقائمة وحدة تحكم Quake الجميلة. كل شيء على أكمل وجه. لكن خذ وقتك! عندما بدأت اللعبة ، كان هناك شيء خاطئ. يبدو أن مستوى "المقدمة" يتم تحميله بشكل طبيعي ، ولكن وضع اللاعب تفرغ تمامًا من الخريطة. فكرت غريب . لقد جربت حيلًا مختلفة ، وبدأت في تصحيح الأخطاء splashf، ولكن كل ذلك كان هباءً - اتضح أن الخطأ معقد جدًا بالنسبة لي ، أو بدا لي هكذا.

واستمر هذا الوضع لعدة سنوات. ربما يستحق القليل من الحديث عن التوقيت. جرت المحاولة الأولى لإطلاق Quake في سبتمبر 2017 ، وبعد ذلك استسلمت ، وكان Frankenstein من Quake و Rockbox على الرف ، وجمع الغبار ، حتى يوليو 2019. بعد أن وجدت المزيج المثالي من الملل والتحفيز ، قررت المضي قدمًا في إكمال ما بدأته.

لقد بدأت التصحيح. كانت حالتي في التدفق لدرجة أنني لا أتذكر عمليا أي تفاصيل حول ما كنت أفعله ، لكنني سأحاول إعادة مسار العمل.

لقد وجدت أن هيكل الزلزال ينقسم إلى قسمين رئيسيين: رمز المحرك في C والمنطق عالي المستوى للعبة في QuakeC ، وهي لغة مجمعة برموز بايت. لقد حاولت دائمًا الابتعاد عن QuakeC VM بسبب الخوف غير العقلاني من تصحيح رمز شخص آخر. ولكن الآن اضطررت إلى الغطس في ذلك. أتذكر بشكل غامض جلسة البث المجنون التي بحثت خلالها عن مصدر الخطأ. بعد الكثير grep، لقد وجدت الجاني: pr_cmds.c:PF_setorigin. تلقت هذه الوظيفة متجهًا ثلاثي الأبعاد يحدد إحداثيات اللاعب الجديدة عند تحميل الخريطة ، والتي كانت دائمًا متساوية لسبب ما (0, 0, 0). جلالة ...

لقد رجعت تدفق البيانات ووجدت من أين أتت: من المكالمة Q_atof()- وظيفة التحويل الكلاسيكية من السلسلة إلى العوامة. ثم بزغت البصيرة عليّ: لقد كتبت مجموعة من الوظائف المجمّعة التي أعادت تعريف Q_atof()رمز الزلزال ، atof()وربما كان عملي خاطئًا. كان من السهل جدا إصلاحه. لقد استبدلت خطأي atofبالوظيفة الصحيحة من كود الزلزال. وفويلا! مستوى الدخول الشهير بثلاثة ممرات محملة بدون أي مشاكل ، مثل "E1M1: مجمع Slipgate". لا يزال إخراج الصوت يبدو وكأنه جزازة مكسورة ، ولكننا ما زلنا ندير Quake على مشغل MP3!

أسفل فتحة الارانب


أصبح هذا المشروع أخيرًا عذرًا لما كنت أخرجه: تعلم لغة التجميع ARM 2 .

كانت المشكلة في دورة خلط الصوت الحساسة للسرعة في snd_mix.c(تذكر صوت جزازة العشب؟). تستقبل

الوظيفة SND_PaintChannelFrom8صفيفًا من عينات صوتية أحادية 8 بت وتخلطها في دفق استريو 16 بت ، يتم قياس القناتين اليسرى واليمنى بشكل منفصل بناءً على معلمتين صحيحتين. قام مجلس التعاون الخليجي بعمل رديء لتحسين حساب التشبع ، لذلك قررت أن أفعل ذلك بنفسي. النتيجة أرضتني تمامًا.

فيما يلي إصدار المجمع لما حصلت عليه (الإصدار C معروض أدناه):

SND_PaintChannelFrom8:
        ;; r0: int true_lvol
        ;; r1: int true_rvol
        ;; r2: char *sfx
        ;; r3: int count

        stmfd sp!, {r4, r5, r6, r7, r8, sl}

        ldr ip, =paintbuffer
        ldr ip, [ip]

        mov r0, r0, asl #16                 ; prescale by 2^16
        mov r1, r1, asl #16

        sub r3, r3, #1                      ; count backwards

        ldrh sl, =0xffff                    ; halfword mask

1:
        ldrsb r4, [r2, r3]                  ; load input sample
        ldr r8, [ip, r3, lsl #2]                ; load output sample pair from paintbuffer
                                ; (left:right in memory -> right:left in register)
        ;; right channel (high half)
        mul r5, r4, r1                      ; scaledright = sfx[i] * (true_rvol << 16) -- bottom half is zero
        qadd r7, r5, r8                     ; right = scaledright + right (in high half of word)
        bic r7, r7, sl                      ; zero bottom half of r7

        ;; left channel (low half)
        mul r5, r4, r0                      ; scaledleft = sfx[i] * (true_rvol << 16)
        mov r8, r8, lsl #16                 ; extract original left channel from paintbuffer
        qadd r8, r5, r8                     ; left = scaledleft + left

        orr r7, r7, r8, lsr #16                 ; combine right:left in r7
        str r7, [ip, r3, lsl #2]                ; write right:left to output buffer
        subs r3, r3, #1                         ; decrement and loop

        bgt 1b                          ; must use bgt instead of bne in case count=1

        ldmfd sp!, {r4, r5, r6, r7, r8, sl}

        bx lr

هناك عمليات اختراق صعبة هنا تستحق التوضيح. أستخدم تعليمات DSP qaddلمعالج ARM لتنفيذ إضافة منخفضة التكلفة للتشبع ، ولكنها qaddتعمل فقط مع كلمات 32 بت ، وتستخدم اللعبة عينات صوت 16 بت. الاختراق هو أنني قمت أولاً بنقل العينات إلى اليسار 16 بتًا ؛ أنا دمج عينات مع qadd؛ ثم قم بعمل التحول العكسي. لذا في تعليمة واحدة أفعل ما أخذته دول مجلس التعاون الخليجي سبعة. (نعم ، سيكون من الممكن الاستغناء عن الاختراقات على الإطلاق إذا عملت مع ARMv6 ، الذي يحتوي على حساب تشبع معبأ يشبه MMX qadd16، ولكن للأسف ، الحياة ليست بهذه البساطة. بالإضافة إلى ذلك ، تبين أن الاختراق رائع!)

لاحظ أيضًا أن قرأت عينتين ستيريو في وقت واحد (باستخدام الكلمات ldrوstr) لإنقاذ بضع دورات أخرى.

أدناه نسخة C كمرجع:

void SND_PaintChannelFrom8 (int true_lvol, int true_rvol, signed char *sfx, int count)
{
        int     data;
        int             i;

        // we have 8-bit sound in sfx[], which we want to scale to
        // 16bit and take the volume into account
        for (i=0 ; i<count ; i++)
        {
            // We could use the QADD16 instruction on ARMv6+
            // or just 32-bit QADD with pre-shifted arguments
            data = sfx[i];
            paintbuffer[2*i+0] = CLAMPADD(paintbuffer[2*i+0], data * true_lvol); // need saturation
            paintbuffer[2*i+1] = CLAMPADD(paintbuffer[2*i+1], data * true_rvol);
        }
}

حسبت أنه مقارنة بالإصدار C المحسن ، انخفض عدد التعليمات لكل عينة بنسبة 60٪. تم حفظ معظم الحلقات باستخدام qaddعمليات ذاكرة التشبع والتعبئة للحساب.

مؤامرة الأعداد الأولية


هنا خطأ آخر مثير للاهتمام وجدته في هذه العملية. في قائمة رمز التجميع ، بجوار التعليمات bgt(الفرع "إذا كان أكثر من") هناك تعليق بأنه bne(الفرع "إن لم يكن متساوٍ") لا يمكن استخدامه بسبب حالة خطية تبطئ البرنامج مع عدد العينات يساوي 1. هذا يؤدي إلى نقل دوري عدد صحيح 0xFFFFFFFFوتأخير طويل للغاية (ينتهي في النهاية).

يتم تشغيل هذه الحالة الحدودية بصوت معين ، يبلغ طوله 7325 عينة 3 . ما هو خاص جدا عن 7325؟ دعونا نحاول العثور على ما تبقى من قسمة أي قوة من اثنين:

73251(عصري2)73251(عصري4)73255(عصري8)7325ثلاثة عشر(عصريالسادس عشر)7325التاسع والعشرون(عصري32)7325التاسع والعشرون(عصري64)7325التاسع والعشرون(عصري128)7325157(عصري256)7325157(عصري512)7325157(عصري1024)73251181(عصري2048)73253229(عصري4096)


5 ، 13 ، 29 ، 157 ...

هل لاحظت أي شيء؟ وهي - بمصادفة ما - 7325 رقم "أولي" عند القسمة على أي قوة من اثنين. يؤدي هذا بطريقة ما (لا أفهم كيف) إلى حقيقة أن مصفوفة من عينة واحدة يتم نقلها إلى رمز مزج الصوت ، ويتم تشغيل حالة حدية وتوقفها.

قضيت يومًا واحدًا على الأقل في تحديد أسباب هذا الخطأ ، نتيجة لاكتشاف أن كل ذلك يرجع إلى تعليمات خاطئة واحدة . في بعض الأحيان يحدث في الحياة ، أليس كذلك؟

فراق


لقد قمت في النهاية بتعبئة هذا المنفذ على شكل رقعة ودمجه مع فرع Rockbox الرئيسي ، حيث هو اليوم. في الإصدار 3.15 من Rockbox والإصدارات الأحدث ، يأتي في مجموعات لمعظم منصات ARM المستهدفة مع 4 شاشات ملونة . إذا لم يكن لديك نظام أساسي مدعوم ، فيمكنك مشاهدة العرض التوضيحي user890104 .

من أجل توفير المساحة ، فاتني بضع نقاط مثيرة للاهتمام. على سبيل المثال ، هناك حالة سباق تحدث فقط عندما يقتحم الزومبي قطع اللحم عندما يكون معدل أخذ العينات 44.1 كيلوهرتز. (كان هذا نتيجة تدفق الصوت الذي يحاول تحميل الصوت - انفجار ، ومحمّل النموذج يحاول تحميل قطعة من نموذج اللحم. يستخدم هذان القسمان من التعليمات البرمجية وظيفة واحدة تستخدم متغيرًا عامًا واحدًا.) وهناك أيضًا الكثير من مشكلات الطلب (أحبك يا ARM! ) ومجموعة من التحسينات الدقيقة التي قمت بإنشائها لإخراج عدد قليل من الإطارات من الجهاز. ولكن سأتركهم مرة أخرى. والآن حان الوقت لتوديع الزلزال - لقد أحببت هذه التجربة.

كل التوفيق وشكرا للسمك!



ملاحظات


  1. Duke Nukem 3D , runtime Rockbox SDL, . , user890104.
  2. ARM, Tonc: Whirlwind Tour of ARM Assembly — ( GBA) . , ARM Quick Reference Card.
  3. , 100 .
  4. بصراحة ، لا أتذكر أي منصات مستهدفة محددة تدعم ولا تدعم الزلزال. إذا كنت فضوليًا ، فانتقل إلى موقع Rockbox على الويب وحاول تثبيت البنية لمنصتك . واسمحوا لي أن أعرف على البريد كما يعمل! تدعم الإصدارات الأحدث من Rockbox Utility (من 1.4.1 والإصدارات الأحدث) التثبيت التلقائي لإصدار البرامج التجريبية من Quake.

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


All Articles