تصور کنید یکی از سنگینترین بازیهای کنسول پلیاستیشن ۲ را بدون نصب هیچ نرمافزاری و تنها با باز کردن یک تب در مرورگر، با نرخ ۵۰ فریم بر ثانیه تجربه کنید. این اتفاق دیگر یک رویای فنی نیست، بلکه خروجی پروژه ps2-web-recomp است که مرزهای اجرای نرمافزارهای قدیمی در وب را جابهجا کرده است.
اجرای بازیهای با کیفیت کنسولی در مرورگر معمولاً به شبیهسازهای سنگینی نیاز دارد که بخش بزرگی از توان پردازشی CPU را میبلعند. طبق مستندات منتشر شده در GitHub، پروژه ps2-web-recomp با ترجمه پیشدستانه کدهای بازی به WebAssembly (وباسمبلی) — که شبیه به تبدیل یک کتاب از زبان سخت به زبانی است که مرورگر آن را بومی میفهمد — این مشکل را حل کرده است. به همین دلیل، عناوینی مثل Gran Turismo 4 اکنون روی GPUهای داخلی لپتاپها با سرعت بالا اجرا میشوند.
برای دههها، بازی در مرورگر به معنای اپلیکیشنهای ساده دوبعدی یا پورتهای بسیار محدود بود. صنعت بازی مدتها با سربار شبیهسازی (Emulation) دستوپنجه نرم میکرد؛ جایی که یک لایه نرمافزاری باید مدام سختافزار را در لحظه تقلید کند. همانطور که در تحلیلهای پیشین ما دربارهی بهینهسازیهای WebAssembly اشاره کردیم، انتقال بار پردازشی از زمان اجرا به مرحله پیشکامپایل، بازی را عملاً به یک اپلیکیشن بومی وب تبدیل میکند. این متد، برادرِ کوچک پروژه psp-web-recomp است و از همان معماری پیروی میکند؛ رویکردی که در اجرای بازیهای PSP از طریق بازترکیب استاتیک پیشتر به تفصیل بررسی کرده بودیم.
موتور بازترجمه
قلب این سیستم eerecomp است. این ابزار کد اجرایی بازی را میخواند و جریان کنترل آن را دنبال میکند. سپس کدهای C++ را در واحدهای ۱۶ کیلوبایتی از کد مهمان (Guest Code) تولید میکند تا ویژگیهای خاص CPU مدل R5900 در کنسول PS2 حفظ شود.
این فرآیند شامل حفظ ثباتهای ۱۲۸ بیتی و دستورات چندرسانهای است. همچنین واحد ممیز شناور (Floating-point unit) را بازسازی میکند که استانداردهای IEEE را نادیده میگیرد؛ به طور خاص، این واحد فاقد مفاهیمی مثل بینهایت (Infinities) یا اعداد بسیار کوچک (Denormals) است و گرد کردن اعداد را به شکل متفاوتی انجام میدهد. برای جلوگیری از سربار مدیریت پشته (Stack) برای هر رشته مهمان، سیستم از Tail Calls برای پرش بین واحدها استفاده میکند و در هنگام تعویض رشتهها، پشته میزبان (Host Stack) را باز میکند تا در آدرس مهمان از سر گرفته شود. این سازوکار تضمین میکند که کد تولید شده هرگز نیازی به یک پشته اختصاصی برای هر رشته مهمان نداشته باشد.
بازسازی سختافزار و سیستمعامل
به جای شبیهسازی کامل سیستمعامل، این پروژه از شبیهسازی سطح بالا برای هسته Emotion Engine استفاده میکند. این بخش مدیریت رشتهها، سمافورها و هندلرهای وقفه برای کنترلکننده DMA و V-Blank را بر عهده دارد. همچنین مدیریت آلارمها و کانال SIF که CPU اصلی را به پردازنده I/O متصل میکند، در این لایه قرار دارد.
- پردازنده I/O (IOP): پردازنده R3000 توسط یک مفسر مدیریت میشود که ماژولهای IRX بازی (شامل درایور صدا، کدهای استریم دیسک و درایورهای کنترلر و کارت حافظه) را اجرا میکند. هسته IOP و کتابخانههایی که این ماژولها فراخوانی میکنند، در زبان C++ بازنویسی شدهاند.
- استریم دیسک: سیستم از درخواستهای HTTP Range برای استریم دادهها استفاده میکند؛ یعنی در ابتدا فقط فایل اجرایی دانلود میشود و سایر دادهها در صورت نیاز از سرور فراخوانی میگردند.
- صدا و ویدیو: واحد SPU2 با ۴۸ صدای ADPCM، پوشهای ADSR و DMA استریم برای موسیقی بازسازی شده است. خروجی صدا از طریق حافظه مشترک به یک AudioWorklet ارسال میشود. ویدیوها نیز با یک پیادهسازی سفارشی از رمزگشای MPEG-2 کنسول (IPU) رمزگشایی میشوند که در آن از جداول VLC اقتباس شده از پروژه Play! استفاده شده است.

خط لوله گرافیکی WebGPU
این پروژه واحد گرافیکی PS2 (GS) را با WebGPU compute shaders جایگزین کرده است. ۴ مگابایت حافظه ویدیویی مستقیماً روی GPU و با ساختارهای بومی Swizzled قرار میگیرد.
مثلثها به کاشیهای کوچک (Tiles) تقسیم شده و با قوانین اصلی GS برای بافتها، پالتها، ترکیب آلفا (Alpha Blending)، عمق و تستهای آلفای مقصد رسترایز میشوند. چون همه چیز در حافظه GS میماند، افکتهایی که تصویر را به عنوان پالت بازخوانی میکنند یا فریمبافر را به عنوان پالت تفسیر میکنند، دقیقاً مشابه کنسول عمل میکنند. این سازوکار اجازه میدهد رزولوشن داخلی بازی یک، دو یا سه برابر رزولوشن اصلی PS2 افزایش یابد. همچنین یک رندرر نرمافزاری به عنوان مرجع برای مقایسه خروجی GPU در سیستم باقی مانده است.
پردازش واحد برداری (Vector Unit)
بیشتر کارهای سهبعدی PS2 در میکروبرنامههایی انجام میشود که روی واحد برداری ۱ (VU1) آپلود میگردند. برای دقت این بخش، سازندگان مکانیسمهای خاصی را به کار بردهاند:
- دقت ریاضی: این برنامهها با محاسبات ممیز شناور دقیقاً مطابق با سختافزار PS2 (Bit-exact) اجرا میشوند.
- مدلسازی خط لوله: سیستم تأخیرهای خط لوله را برای پرچمهای MAC، وضعیت (Status) و Clip مدل کرده است، زیرا بازیها کدها را بر اساس این تأخیرها زمانبندی میکنند.
- کامپایل: میکروبرنامههایی که در تستهای اولیه شناسایی شدهاند، پیشاپیش به SIMD C++ کامپایل میشوند. هر برنامه جدیدی که در حین بازی با آن مواجه شویم، در لحظه به یک ماژول WebAssembly تبدیل میشود که این فرآیند برای هر برنامه تنها چند میلیثانیه زمان میبرد.
بهینهسازی برای عملکرد مرورگر
تستهای اولیه تنها ۳ فریم بر ثانیه خروجی داشتند. توسعهدهندگان با انتقال GS به WebGPU و کامپایل واحدهای برداری، این عدد را به ۷۰ فریم رساندند. اما سربار مرورگر همچنان یک گلوگاه بود.
برای بهینهسازی بیشتر، تیم روی رندر GPU متمرکز شد. در نسخه اول، ۶۵۰ دستور Compute Dispatch در هر فریم ارسال میشد زیرا بارگذاری بافتها و پالتها باعث تقسیم دستههای مثلث میشد. با جابهجایی آپلودها به قبل از مثلثهای غیروابسته، این عدد به ۸۰ دستور کاهش یافت.
همچنین یک حافظه پنهان (Cache) ۱۶ مگابایتی برای بافتها روی GPU ایجاد شد که حجم دادههای ارسالی در هر فریم را ۶۰ درصد کم کرد. در بازی Gran Turismo 4، ۳ مگابایت بافت تکراری در هر فریم آپلود میشد که حالا توسط کش مدیریت میشود. اگرچه حافظه GS بین فریمها جابهجا میشود، اما این کش از عبور مداوم دادهها از WebAssembly به GPU جلوگیری میکند.
یک مشکل بحرانی دیگر، زمانبندی مرورگر بود. چون توابع setTimeout تو در تو به ۴ میلیثانیه محدود (Clamp) شدهاند، حلقه شبیهسازی اکنون از طریق MessageChannel اجرا میشود. انتقال شبیهسازی به یک Worker Thread و سپردن رشته اصلی صفحه تنها به تغذیه GPU، باعث شد نرخ فریم در Gran Turismo 4 روی عدد ثابت ۵۰ تثبیت شود، در حالی که پیش از آن بین ۴۰ و ۵۰ نوسان داشت.
محک با Gran Turismo 4 و God of War II
بازی Gran Turismo 4 به دلیل فشار زیاد به هر دو واحد برداری، نیاز به رفتار دقیق ممیز شناور برای تستهای لایسنس و تکرارها (Replays)، و ترفندهای گرافیکی پیچیده، به عنوان اولین محک انتخاب شد. این بازی از لحظه بوت و ویدیوهای ابتدایی تا منوها و مسابقات، بهطور کامل اجرا میشود و پیشرفتها در یک کارت حافظه مبتنی بر مرورگر ذخیره میگردد. در مرورگر کروم روی یک GPU داخلی، این بازی (نسخه PAL) با دو برابر رزولوشن اصلی و ۵۰ فریم بر ثانیه اجرا میشود.
اما God of War II باگهای عمیقتری را آشکار کرد که در Gran Turismo 4 هرگز رخ نداده بودند. بازی در ابتدا بدون صدا اجرا میشد چون درایور صدا به یکی از تایمرهای سختافزاری پردازنده I/O وابسته بود که هنوز پیادهسازی نشده بود و در نتیجه درایور هرگز نمونههای صوتی را میکس نمیکرد.
مشکلات دیگر شامل موارد زیر بود:
- فساد حافظه: یک باگ در بازترجمه باعث میشد وقتی یک پرش (Branch) آخرین دستور یک واحد ۱۶ کیلوبایتی بود، دستورات دو بار اجرا شوند. مسیری که پرش را انجام نمیداد، به جای ادامه بعد از پرش، از Delay Slot شروع میکرد. این موضوع باعث کرش میشد، زیرا لیست رندر روی هندلرهای V-Blank مینوشت. این باگ با دنبال کردن یک لیست لینک شده فاسد و استفاده از Hardware Watchpoints پیدا شد.
- زمانبندی واحد برداری: مشخص شد God of War II پرچمهای Clip را سه دستور بعد از عملیات CLIP میخواند، در حالی که مدل اولیه چهار دستور تأخیر داشت. در Gran Turismo 4 میکروکدها همیشه بیشتر منتظر میماندند و این خطا دیده نمیشد.
- نیازهای اضافی: این بازی به یک مسیر بوت مشابه BIOS، ماژولی که به جای دیسک از حافظه بارگذاری شود و حالت Destination Chain در DMA اسکرچپد نیاز داشت.
اکنون God of War II در صحنههای سنگین مبارزه با ۶۰ فریم بر ثانیه در رزولوشن اصلی اجرا میشود. در رزولوشن دو برابر، نرخ فریم بین ۴۸ تا ۶۰ است که توسط GPU داخلی محدود شده است.
نحوه پورت کردن بازیها
پروژه اسکریپتی به نام port.sh ارائه داده که فرآیند ترجمه را خودکار میکند. این اسکریپت برنامه بوت نامگذاری شده در فایل SYSTEM.CNF دیسک را میخواند، کد ماشین را به C++ تبدیل میکند و بازی را در حالت Headless (بدون رابط گرافیکی) اجرا میکند.
از آنجایی که تحلیل استاتیک نمیتواند تمام توابعی را که از طریق اشارهگرها (Pointers) فراخوانی میشوند شناسایی کند، اسکریپت بازی را چندین بار اجرا میکند. هر اجرا، آدرسهای گمشده را ثبت کرده و آنها را به عنوان نقاط ورود جدید ترجمه میکند تا زمانی که هیچ تابع جدیدی پیدا نشود. این اجراها همچنین میکروبرنامههای واحد برداری را برای کامپایل پیشدستانه ثبت میکنند.
برای مثال، تبدیل یک فایل ISO از God of War II به یک صفحه وب قابل بازی، روی یک لپتاپ ۸ هستهای حدود سه و نیم دقیقه زمان میبرد و خروجی نهایی حدود ۳۰۰ مگابایت فضا اشغال میکند، هرچند خود ایمیج دیسک به جای کپی شدن، لینک میشود.
برای شبیهسازی گیمپلی در این اجراها، اسکریپت هر ۵ ثانیه کلید Start و سپس Cross را فشار میدهد تا از صفحات ابتدایی عبور کند. همچنین یک کارت حافظه خالی در اسلات ۱ قرار میدهد. کاربران میتوانند با --press 900:cross ورودیهای مختلف یا با --frames مدت زمان اجرا را تنظیم کنند. فریمهای آخرین اجرا در مسیر games/mygame/shots برای تایید سریع ذخیره میشوند.
الزامات فنی و حقوقی
برای اجرای این پروژه به git، CMake، Ninja، کامپایلر C++20 (ترجیحاً Clang)، پایتون ۳ و 7-Zip برای ایمیجهای .7z نیاز است. اولین اجرا، SDK مربوط به Emscripten را در tools/emsdk نصب کرده و wgpu-native را برای رانر تست Headless دانلود میکند. تمام تستها روی لینوکس x86-64 انجام شده است.
به دلیل استفاده از SharedArrayBuffer برای رشتهها، صفحه باید با هدرهای Cross-Origin ایزوله شود (Cross-Origin-Opener-Policy: same-origin و Cross-Origin-Embedder-Policy: require-corp). اسکریپت serve.py این هدرها و درخواستهای Range مورد نیاز برای استریم دیسک را مدیریت میکند.
پشتیبانی از مرورگرها:
- Chrome/Edge: WebGPU در ویندوز و مک در دسترس است.
- لینوکس: کاربران باید پرچمهای
chrome://flags/#enable-unsafe-webgpuوchrome://flags/#enable-vulkanرا فعال کرده و مرورگر را ریاستارت کنند. - جایگزین: در صورت نبود WebGPU، یک رندرر نرمافزاری فعال میشود که برای منوها کافی است اما برای گرافیک سهبعدی بسیار کند است. در لپتاپهای دارای دو GPU، اگر کانواس GPU مجزا نمایش داده نشود، صفحه ممکن است به کپی کردن فریمها از طریق یک کانواس دوبعدی روی آورد.
کنترلها:
از کیبورد (Z برای Cross، X برای Circle، A برای Square، S برای Triangle، Q/W/1/2 برای دکمههای شانه، Enter برای Start، Backspace برای Select، کلیدهای جهتنما برای D-pad و I/J/K/L برای استیک چپ) و همچنین Gamepad API برای دستههای بازی پشتیبانی میشود. گوشیها کنترلهای روی صفحه دریافت میکنند و کلید F صفحه را تمامصفحه میکند.
ذخیرهسازی:
پیشرفت بازی در حافظه مرورگر ذخیره میشود. کاربران میتوانند کارتها را به صورت فایل .ps2 (با ساختار PCSX2) خروجی بگیرند یا وارد کنند. استفاده از ?nocard بازی را با اسلات خالی شروع میکند.
طبق اعلام مخزن گیتهاب، WebAssembly تولید شده صرفاً ترجمهای از فایل اجرایی بازی است و مالکیت آن همچنان با سازندگان بازی است. کاربران باید از ایمیجهای قانونی خود استفاده کنند و کد تولید شده را منتشر نکنند. این پروژه هیچ ارتباطی با سونی، Polyphony Digital یا Santa Monica Studio ندارد.
این رویکرد معیار جدیدی برای بازیهای تحت وب تعریف میکند. ثابت شد که بازترجمه استاتیک میتواند «مالیات شبیهساز» را حذف کند و نرمافزارهای وابسته به سختافزار را با سرعت بومی در یک محیط ایزوله اجرا کند. اگر این متد برای کنسولهای دیگر مقیاسپذیر شود، شاهد چرخی خواهیم بود که در آن نرمافزارهای قدیمی دیگر «شبیهسازی» نمیشوند، بلکه برای وب «ترنسپایل» میشوند و مرورگر به پلتفرم اصلی برای حفظ نرمافزارهای میراث تبدیل میگردد.
گام بعدی شما
- اگر توسعهدهنده هستید، مخزن GitHub پروژه را بررسی کنید تا با نحوه پیادهسازی WebGPU برای رندرهای قدیمی آشنا شوید.
- برای تجربه بازیها، از مرورگر Chrome یا Edge استفاده کنید و در لینوکس پرچمهای Vulkan را فعال نمایید.
- بررسی کنید که آیا سختافزار شما از WebGPU پشتیبانی میکند یا خیر تا از سرعت واقعی سیستم بهرهمند شوید.
اما داستان سختافزاری این تحول حتی شگفتانگیزتر است — به تحلیل ما دربارهی تراشههای Blackwell و آینده پردازش در وب مراجعه کنید.




گفتگو