تصور کنید بازی God of War: Chains of Olympus را با وضوح بالا و ۶۰ فریم بر ثانیه، بدون نصب هیچ نرمافزاری و تنها با باز کردن یک تب در مرورگر اجرا کنید. این تجربه اکنون به لطف پروژه psp-web-recomp به واقعیت تبدیل شده است. این پروژه موفق شده است بازی را با چهار برابر وضوح اصلی PSP اجرا کند، بدون آنکه از شبیهسازهای سنتی استفاده کند. بازی از لحظه بوت، منوها، میانپردهها و مبارزات بهطور کامل اجرا میشود و از حالت تمامصفحه با وضوح 1440×816 پشتیبانی میکند. این سیستم در مرورگرهای کروم و فایرفاکس روی لپتاپها و همچنین روی گوشیهای هوشمند از طریق کنترلهای لمسی روی صفحه کار میکند.
به نقل از مستندات این پروژه در گیتهاب، این سیستم برخلاف شبیهسازهای قدیمی، از روش بازترکیب استاتیک (Static Recompilation) استفاده میکند. برای درک سادهتر، این روش شبیه به این است که بهجای ترجمه همزمان و کلمه به کلمه یک کتاب از انگلیسی به فارسی (که باعث مکث و کندی میشود)، کل کتاب را یکبار برای همیشه ترجمه کنیم و نسخه فارسی را چاپ کنیم تا خواننده بدون هیچ وقفهای آن را بخواند. همانطور که در تحلیلهای پیشین ما درباره بهینهسازیهای WebAssembly دیدیم، انتقال محاسبات سنگین به لایههای نزدیکتر به سختافزار، کلید دستیابی به عملکرد Native است.
در دهه گذشته، اجرای بازیهای کنسولهای قدیمی در وب نیازمند شبیهسازهای سنگینی بود که سختافزار را در لحظه شبیهسازی میکردند و در دستگاههای موبایل با لگ شدید مواجه میشدند. اکثر کاربران با کندی و سربار این سیستمها آشنا هستند. psp-web-recomp با تغییر رویکرد، کد بازی را بهجای یک مسئله شبیهسازی، به عنوان یک مسئله ترجمه در نظر میگیرد. بهجای شبیهسازی CPU مدل MIPS، کد ماشین بازی پیش از اجرا به C++ ترجمه شده، سپس به وباسمبلی (WebAssembly) کامپایل میشود و در نهایت به یک پیادهسازی مجدد از سیستمعامل و تراشه گرافیکی PSP متصل میسازد.
خط لوله بازترکیب
این سیستم ابتدا فایل اجرایی رمزگشاییشده یک بازی PSP را تحلیل میکند. سپس توابع را شناسایی کرده و برای هر ۱۶ کیلوبایت از کد مهمان، کدهای C++ در واحدهای ترجمه تولید میکند. این کدها روی یک مدل از حافظه PSP و فایل رجیسترها اجرا میشوند. توسعهدهندگان برای رسیدن به این هدف، وصلههای خاص و یک «پروفایل وب» جدید به ابزار PSPRecomp اضافه کردهاند تا هدف اجرای در مرورگر را فعال کنند.

برای عملیاتی شدن بازی، یک هسته HLE (شبیهسازی سطح بالا) در مسیر profile/host پیاده شده است که درخواستهای بازی از سیستمعامل PSP را پاسخ میدهد. این قابلیتها شامل موارد زیر است:
- مدیریت رشتههای همزمان (Cooperative threads) با استفاده از سمافورها، پرچمهای رویداد و کالبکها.
- مدیریت پارتیشنهای حافظه و دیالوگهای پیام.
- سیستم فایلی که دادههای دیسک را از طریق درخواستهای HTTP Range استریم میکند؛ به این معنی که در ابتدا فقط فایل اجرایی دانلود میشود و سایر دادهها در زمان نیاز فراخوانی میشوند.
- خروجی صوتی از طریق AudioWorklet و بازسازی سنتزکننده صدای PSP (شامل ۳۲ صدای ADPCM با کنترل گام و پاکتهای ADSR).
- رمزگشایی موسیقی و گفتار از استریمهای ATRAC3+ با استفاده از رمزگشای FFmpeg.
زمان اجرای مهمان (Guest time) بر اساس فریمها پیش میرود تا اطمینان حاصل شود که بازی فارغ از سرعت سختافزار میزبان، روی ۶۰ هرتز ثابت بماند.
گرافیک و رندرینگ
این پروژه از یک رندرکننده اختصاصی استفاده میکند که لیستهای نمایش تراشه گرافیکی PSP (GE) را به دستورات WebGL2 تبدیل میکند. فایل ge.cpp این لیستها را روی CPU رمزگشایی کرده و مواردی مثل فرمتهای ورتکس، اسکینینگ (Skinning)، نورپردازی، تولید تکسچر، کلیپینگ (Clipping) و حذف وجوه پشتی (Backface Culling) را مدیریت میکند. سپس مثلثهای دستهبندی شده و وضعیت رندر را به ge_gl.cpp تحویل میدهد.

برای حفظ عملکرد، بخش GE و بستر WebGL روی یک رشته (Thread) مجزا با استفاده از OffscreenCanvas اجرا میشوند. این ساختار دقیقاً مشابه سختافزار PSP است که در آن تراشه گرافیکی یک فریم را پردازش میکند در حالی که CPU در حال آمادهسازی فریم بعدی است. با صفبندی لیستها و انتظار تنها برای فراخوانیهای همگامسازی صریح، هزینه هر فریم توسط رشتهای که شلوغتر است تعیین میشود، نه مجموع زمان هر دو رشته.

جزئیات فنی رندرینگ شامل موارد زیر است:
- نگاشت VRAM: فریمبافرها به اهداف رندر WebGL تبدیل میشوند که بر اساس جایگاهشان در VRAM کلیدگذاری شدهاند. این امر اجازه میدهد افکتهایی که به یک تکسچر رندر شده و سپس دوباره خوانده میشوند، مستقیماً روی GPU باقی بمانند.
- شبیهسازی GPU: سیستم قابلیتهای stencil-in-alpha، مه (Fog) و انتقال بلوکها (Block Transfers) را روی GPU شبیهسازی میکند. همچنین بازتفسیر فرمت پیکسلها را مدیریت میکند؛ جایی که بازیها بافرهای ۳۲ بیتی را به عنوان تکسچرهای ۱۶ بیتی و برعکس میخوانند.
- مقیاسبندی وضوح: امکان رندر با وضوح ۱ تا ۴ برابر مقدار اصلی (۴۸۰×۲۷۲) وجود دارد. این رویکرد برای بهبود کیفیت بصری، یادآور تکنولوژیهای مدرن سونی است که قابلیت افزایش وضوح مبتنی بر هوش مصنوعی را به کنسولهای PS5 آورد تا تجربه بصری کاربر را ارتقا دهد.
رفع گلوگاههای عملکرد
طبق گزارش توسعهدهنده، اولین نسخه قابل اجرا تنها ۶ فریم بر ثانیه بود. برای رسیدن به ۶۰ فریم، باید گلوگاههای سطح مرورگر شناسایی میشدند، نه فقط بهینهسازی رندرکننده.
یکی از مهمترین اصلاحات مربوط به جابهجایی Framebuffer بود. در بازی God of War، فریمبافرها بدون منتظر ماندن برای Vertical Blank جابهجا میشدند. بدون محدودیت، بازی حدود ۸ فریم برای هر فریمی که واقعاً به صفحه میرسید، رسم میکرد. با نگه داشتن رشتهای که دو بار در یک Blank جابهجایی انجام میداد تا Blank بعدی (تکنیکی که در PPSSPP نیز استفاده شده)، حجم پردازش هر فریم نمایش داده شده ۸ برابر کاهش یافت.
سایر بهینهسازیهای کلیدی عبارت بودند از:
- دسترسی به ساعت: در وباسمبلی، خواندن ساعت از طریق
std::chronoازclock_gettimeو تبدیل BigInt در جاوااسکریپت عبور میکند. پروفایلینگ نشان داد خواندن این مقدار برای هر Primitive یکسوم زمان فریم را میگیرد؛ جایگزینی آن باperformance.now()(تنها در زمان فعال بودن پروفایلینگ) این مشکل را حل کرد. - اعتبارسنجی بافر در فایرفاکس: فایرفاکس هر آپلود بافر WebGL را به پردازش GPU خود کپی کرده و پس از هر تغییر در بافر ایندکس، کل بافر را دوباره اعتبارسنجی میکند. یک بافر حلقوی ایندکس ۴ مگابایتی باعث شد نرخ فریم فایرفاکس به ۳ برسد. اختصاص یک بافر ایندکس کوچک برای هر Draw عملکرد را بازیابی کرد.
- توقفهای GPU موبایل: توسعهدهنده متوجه شد که نوشتن دادههای ورتکس تکهتکه در یک بافر بزرگ باعث توقف (Stall) در گوشیهای موبایل میشود، زیرا درایورهای موبایل هنگام تغییر بافری که توسط GPU در حال خوانده شدن است، منتظر میمانند یا کپی میگیرند.
- بهینهسازی Stencil: PSP بافر استنسیل خود را در کانال آلفای فریمبافر نگه میدارد. بازتاب اینها با پاسهای تمامصفحه در وضوح 4x، هزینه ۶۰ میلیون پیکسل اضافی در هر فریم داشت. با ردیابی مستطیلها و بیتهای استنسیل که واقعاً تغییر کرده بودند، این مقدار به ۷ میلیون پیکسل کاهش یافت.
- بهرهوری رشتهها: اجرای GE روی رشته اختصاصی خود، هزینه یک مبارزه شلوغ در فایرفاکس را از ۱۷ میلیثانیه (بازی بهعلاوه گرافیک) به حدود ۱۰ میلیثانیه (بزرگترین مقدار بین این دو) کاهش داد.
سازگاری و DRM
در حالی که God of War بهراحتی اجرا شد، بازی Ghost of Sparta به دلیل نیاز به رمزگشایی DRM برای یک فایل ۱۷۶ بایتی در ابتدا متوقف میشد. این فایل در فرمت PGD است که از طریق موتور رمزنگاری KIRK با استفاده از AES-128 و سه کلید از خزانه کلیدهای KIRK (یک بررسی هدر مبتنی بر CMAC و حالت شمارنده برای دادهها) رمزگشایی میشود. پروژه با اضافه کردن profile/host/pgd.cpp این قابلیت را پیادهسازی کرد.

همچنین مشکل «مه سفید» در آسمان و منوهای بازی Ghost of Sparta وجود داشت. تحلیل Drawها نشان داد لایهای از ابر با نورپردازی فعال رسم میشود که شفافیت آن به آلفای نور محیطی جهانی وابسته است؛ عاملی که پیشتر در کد نورپردازی نادیده گرفته شده بود. پس از اصلاح، عملکرد عالی شد و هزینه هر فریم به ۶ تا ۸ میلیثانیه رسید.
نحوه پورت کردن بازیها
کاربران میتوانند با اسکریپت port.sh بازیهای خود را پورت کنند. این فرآیند شامل استخراج دیسک (ISO یا ZIP)، رمزگشایی PSP_GAME/SYSDIR/EBOOT.BIN با ابزارهایی مثل DecEboot یا pspdecrypt، ترجمه به C++ و ساخت صفحه وب است. این فرآیند برای بازی God of War روی یک لپتاپ ۸ هستهای حدود ۴ دقیقه زمان میبرد.
نیازمندیهای فنی:
- نرمافزار: git, CMake, Ninja, کامپایلر C++20 و پایتون ۳.
- سیستمعامل: تست شده روی لینوکس. مک برای نسخه مرورگر تست نشده است؛ اما Runner تست نیتیو به
libegl-devوlibgles-devدر دبیان/اوبونتو نیاز دارد. - فضای ذخیرهسازی: حدود ۲ گیگابایت فضای دیسک برای هر بازی جهت ذخیره دادههای استخراج شده و کدهای تولید شده.
کاربران میتوانند پرچم --native را برای ساخت یک Runner بدون رابط گرافیکی (Headless) جهت استخراج فریمها به تصویر و ضبط صدا اضافه کنند که سریعترین راه برای دیباگ بازیهای جدید است. با این حال، توسعهدهنده هشدار داده است که چون هر دو بازی تست شده از استودیو Ready at Dawn و یک موتور گرافیکی هستند، سایر بازیها ممکن است در فراخوانیهای سیستمی پیادهسازینشده (که با [hle] unimplemented ... ثبت میشوند) یا ویژگیهای پشتیبانینشده GE متوقف شوند.
میزبانی و مسائل قانونی
به دلیل استفاده از SharedArrayBuffer برای چندرشتهای شدن، صفحه باید به صورت cross-origin isolated و با هدرهای Cross-Origin-Opener-Policy: same-origin و Cross-Origin-Embedder-Policy: require-corp سرو شود. اسکریپت scripts/serve.py این هدرها و درخواستهای Range لازم را مدیریت میکند. برای تست روی موبایل، میتوان از تونل cloudflared برای انتقال این هدرها به گوشی استفاده کرد.
مرورگرهایی که قادر به رسم WebGL2 روی OffscreenCanvas نیستند، بهطور خودکار به حالت تکرشتهای باز میگردند که میتوان آن را با ?threads=0 اجبار کرد. همچنین با افزودن ?profile به URL، میتوان تفکیک زمانبندی هر فریم را مشاهده کرد.
از نظر قانونی، مخزن پروژه حاوی هیچ دادهای از بازیها نیست و تنها کدهای اصلی و کتابخانههای شخص ثالث (مانند رمزگشای ATRAC3 با مجوز LGPL 2.1) را شامل میشود. کدهای C++ و وباسمبلی تولید شده، ترجمه فایل اجرایی بازی هستند و مالکیت آنها متعلق به مالکان بازی است؛ لذا به کاربران توصیه میشود آنها را در دستگاه خود نگه دارند و از ایمیجهای دیسکی که مالکیتش را دارند استفاده کنند.
گام بعدی شما
- اگر توسعهدهنده هستید، اسکریپت
port.shرا برای تست بازیهای مورد علاقه خود امتحان کنید. - برای مشاهده تحلیل دقیق زمانبندی هر فریم، عبارت
?profileرا به انتهای URL صفحه اضافه کنید. - در صورت عدم پشتیبانی مرورگر از OffscreenCanvas، از پارامتر
?threads=0برای اجرای تکرشتهای استفاده کنید.
اما داستان سختافزاری این تحول حتی شگفتانگیزتر است — به تحلیل ما درباره تراشههای گرافیکی نسل جدید در مرورگرها مراجعه کنید.




گفتگو