4K (2160p) 分辨率支持 PS1、PS2、PSP 游戏





5.00/5 (12投票s)
PCSX/PCSX2/PPSSPP 模拟器在 WPF/C# 下的 Windows 10 克隆版本,带有“触摸”控制
- https://github.com/Xirexel/OmegaRed
- Facebook 链接: Omega Red
- BMC 链接: Omega Red
- 4K (2160p) 分辨率支持 PS1、PS2、PSP 游戏
- Omega Red 视频指南
- 刷新帧率
- 支持手柄
- 新的 PS1 图形渲染器
- PS1 模拟器
- 游戏直播(YouTube、Facebook、Twitch)
- GoogleDrive 和 YouTube
- Omega Red PSP 模拟器
- Omega Red PS2 模拟器 RTSP 服务器
目录
背景
该项目的主要目的是重新设计原始的 PCSX2,为用户提供更简单、更友好的用户界面,避免复杂的控件结构。在我看来,原始项目具有复杂的用户界面。它提供了强大的工具来创建游戏的“补丁”,但作为一名玩家,我并不需要所有这些——我只需要几个按钮来启动我喜欢的游戏。另一个目的是为 Windows 10 添加触摸屏控制。
简要 PCSX2 评测
Omega Red PS2 模拟器是原始 PCSX2 项目的克隆。它在图形用户界面方面进行了显著的更改,但如果没有原始项目的上下文,可能很难理解所有这些更改。
因此,原始项目是 PCSX2 团队长期工作的成果。它在 x86 CPU 上模拟 PlayStation 2 游戏机的硬件部分,适用于 Windows、Linux 和 MacOS。它包括 Emotion Engine MIPS R5900 和 I/O 处理器 MIPS R3000 的软件转换器,这些转换器将这些命令集转换为 Intel x86 命令集。然而,CPU 架构之间的差异导致定义了三个主要的执行线程:“EE Core”用于执行 MIPS R5900 和 MIPS R3000 命令集;“MTVU”用于执行 VU1 命令中的 3D 向量运算集;“MTGS”用于执行图形合成器处理器的命令。这种多线程模型可以有效地利用三个或更多核心 CPU。
在代码层面,PCSX2 基于 wxWidgets
和 pthreads4w
框架。wxWidgets
是一个用于创建易于移植的 GUI 的框架。pthreads4w
是一个用于在 Windows 操作系统中定义 Unix 线程接口的框架,以与 Unix 代码兼容。
最后一方面是应用程序的架构。PCSX2 具有一个加载插件的主要执行应用程序。因此,其架构可以表示如下:
我对问题的看法
在我看来,原始项目过于复杂。它包含了代码调试、创建“补丁”、日志打印、CPU 转换器配置、插件配置等工具。我认为一个更简洁、更轻量级的版本可以使其易于使用。另一个方面是“触摸”模式的控制——我真的很想拿起我的轻便 Surface Pro,花一些时间玩《最终幻想 X》或《最终幻想 XII》,但“Win95”风格的 GUI 和需要使用外部游戏手柄“毁了游戏”(嵌入式触摸键盘毫无用处)。
我做了什么?
那么,我做了什么?我决定用 WPF/C# GUI 重写一个 PCSX2 的克隆版本。WPF 是一个灵活的框架,可以编写代码以正确地与“触摸”屏幕进行交互。然而,WPF 框架基于托管代码,无法有效地直接用于转换器代码。这导致了下一个解决方案——编写一个新的 C DLL 库,其中包含模拟器工作所需的最小代码。这成了一个重大问题——原始 PCSX2 项目是一个混合架构的应用程序——很难将“业务”逻辑(模拟器核心)与“瘦”客户端(用户界面)分离。这个问题因为原始代码在几乎每个源文件中都使用 wxWidgets 的类和结构而变得更糟——这使得 wxWidgets 与 PCSX2 紧密绑定。移除对 wxWidgets 的依赖需要重写几乎所有的原始源文件——对于一个爱好项目来说太多了,而且会在静态链接层面失去与原始 PCSX2 项目的兼容性——任何修改原始代码都需要手动复制并修改到新模拟器中的代码。
WxWidget 存根
在我看来,只有一个合适的决定——编写一个简单的框架,它包含与 wxWidgets 相同的静态链接接口,但用 C++ STD 代码实现所需的功能。这看起来是一项巨大的工作,但事实并非如此。从 PCSX2 中移除对 wxWidgets 的依赖可以标记与项目 GUI 部分相关的代码,并将其从代码中移除。结果,“框架”只包含 12 个源文件。其中大多数是“存根”文件——它们根据 wxWidgets 的接口定义类,但不包含可执行代码。模拟器核心代码与 wxWidgets 框架是解绑的。
创建 PCSX2Lib
我之前写过,这个项目的主要部分是找到原始 PCSX2 中可以称为“核心”或“内核”的代码。经过一些研究,我找到了可以这样命名的文件——这些文件可以在没有 GUI 和文件系统特定上下文的情况下工作。
当然,这个“Core
”不能单独工作(它是 DLL),但它可以被任何支持加载和链接 C 动态库的编程语言的任何框架使用。C# 和 WPF 是实现此目的的合适框架,我面临着为 PCSX2Lib.dll 定义正确 C 接口的问题。PCSX2Lib.dll 的代码非常难理解,我认为对于大多数读者来说,更感兴趣的是查看这个库的 C 接口,并理解每个导出函数的目的。
LIBRARY "PCSX2Lib"
EXPORTS
; Init functions
DetectCpuAndUserModeFunc
AllocateCoreStuffsFunc
ApplySettingsFunc
; EE Core thread controls
SysThreadBase_ResumeFunc
SysThreadBase_SuspendFunc
SysThreadBase_ResetFunc
SysThreadBase_CancelFunc
; MTGS thread controls
MTGS_ResumeFunc
MTGS_WaitForOpenFunc
MTGS_IsSelfFunc
MTGS_SuspendFunc
MTGS_CancelFunc
MTGS_FreezeFunc
MTGS_WaitGSFunc
; MTVU thread controls
MTVU_CancelFunc
vu1Thread_WaitVUFunc
; Plugin management
openPlugin_SPU2Func
openPlugin_DEV9Func
openPlugin_USBFunc
openPlugin_FWFunc
setPluginsInitCallback
setPluginsCloseCallback
setPluginsShutdownCallback
setPluginsOpenCallback
setPluginsAreLoadedCallback
resetCallbacksFunc
setGS
setPAD
setSPU2
setCDVD
setMcd
setUSB
setFW
setDEV9
; Patches management
setUI_EnableSysActionsCallback
ForgetLoadedPatchesFunc
inifile_commandFunc
setLoadAllPatchesAndStuffCallback
setSioSetGameSerialFunc
getGameStartedFunc
getGameLoadingFunc
getElfCRCFunc
VTLB_Alloc_PpmapFinc
releaseWCHARStringFunc
getSysGetBiosDiscIDFunc
gsUpdateFrequencyCallFunc
getSysGetDiscIDFunc
; BIOS management
setLoadBIOSCallbackCallback
setCDVDNVMCallback
setCDVDGetMechaVerCallback
; Saving management
getFreezeInternalsFunc
getEmotionMemoryFunc
getIopMemoryFunc
getHwRegsFunc
getIopHwRegsFunc
getScratchpadFunc
getVU0memFunc
getVU1memFunc
getVU0progFunc
getVU1progFunc
getFreezeOutFunc
setDoFreezeCallback
; Loading management
setFreezeInFunc
setFreezeInternalsFunc
setEmotionMemoryFunc
setIopMemoryFunc
setHwRegsFunc
setIopHwRegsFunc
setScratchpadFunc
setVU0memFunc
setVU1memFunc
setVU0progFunc
setVU1progFunc
; Elf file management
PCSX2_Hle_SetElfPathFunc
Init
函数
DetectCpuAndUserModeFunc
- 检测 CPU 多媒体支持的函数AllocateCoreStuffsFunc
- 分配内存并初始化内部变量和转换器的函数ApplySettingsFunc
- 为内部变量设置新值的函数
EE Core 线程控制
SysThreadBase_ResumeFunc
- 恢复 EE Core 线程的函数(init
状态未启动,resume 命令启动此线程)SysThreadBase_SuspendFunc
- 挂起 EE Core 线程的函数SysThreadBase_ResetFunc
- 停止 EE Core 线程并释放相关内存的函数SysThreadBase_CancelFunc
- 使 EE Core 线程无效的函数
MTGS 线程控制
MTGS_ResumeFunc
- 恢复图形合成器 (GS) 线程的函数(init
状态未启动,resume 命令启动此线程)MTGS_WaitForOpenFunc
- 等待 GS 图形上下文初始化的函数MTGS_IsSelfFunc
- 检查 GS 线程以防止互斥死锁的函数MTGS_SuspendFunc
- 挂起 GS 线程的函数MTGS_CancelFunc
- 取消 GS 线程的函数MTGS_FreezeFunc
- 从 GS 线程保存图形上下文的函数MTGS_WaitGSFunc
- 等待当前图形上下文完成的函数
MTVU 线程控制
MTVU_CancelFunc
- 取消 3D 向量单元线程的函数vu1Thread_WaitVUFunc
- 等待 3D 向量单元线程开始的函数
插件管理
openPlugin_SPU2Func
- 打开AudioOutput
上下文的函数openPlugin_DEV9Func
- 打开 DEV9 上下文的函数openPlugin_USBFunc
- 打开 USB 上下文的函数openPlugin_FWFunc
- 打开 FW 上下文的函数setPluginsInitCallback
- 设置用于从 EE Core 线程初始化的init
回调 C 函数setPluginsCloseCallback
- 设置用于从 EE Core 线程关闭的回调 C 函数setPluginsShutdownCallback
- 设置用于从 EE Core 线程释放插件资源的释放回调 C 函数setPluginsOpenCallback
- 设置用于从 EE Core 线程打开的回调 C 函数setPluginsAreLoadedCallback
- 设置用于从 EE Core 线程检查状态的加载状态检查回调 C 函数resetCallbacksFunc
- 释放所有回调指针的函数setGS
- 设置指向 GS 外部模块的 C 指针的函数setPAD
- 设置指向 PAD 外部模块的 C 指针的函数setSPU2
- 设置指向AudioOutput
外部模块的 C 指针的函数setCDVD
- 设置指向 CDVD 外部模块的 C 指针的函数setMcd
- 设置指向内存卡外部模块的 C 指针的函数setUSB
- 设置指向 USB 外部模块的 C 指针的函数setFW
- 设置指向 FW 外部模块的 C 指针的函数setDEV9
- 设置指向 DEV9 外部模块的 C 指针的函数
补丁管理
setUI_EnableSysActionsCallback
- 从 EE Core 线程设置 UI 更新回调的函数ForgetLoadedPatchesFunc
- 释放 EE Core 中所有当前补丁的函数inifile_commandFunc
- 设置文本补丁到 EE Core 的函数setLoadAllPatchesAndStuffCallback
- 设置从 EE Core 线程加载补丁回调的函数setSioSetGameSerialFunc
- 设置游戏光盘序列号的函数getGameStartedFunc
- 获取 EE Core 游戏状态的函数getGameLoadingFunc
- 获取 EE Core 游戏加载状态的函数getElfCRCFunc
- 获取ElfFile
CRC 校验和的函数VTLB_Alloc_PpmapFinc
- 为补丁重新分配内存的函数releaseWCHARStringFunc
- 释放为wchar_t string
分配的内存的函数getSysGetBiosDiscIDFunc
- 获取 BIOS 光盘 ID 的函数gsUpdateFrequencyCallFunc
- 在打补丁后更新 GS 上下文的函数getSysGetDiscIDFunc
- 获取光盘 ID 的函数
BIOS 管理
setLoadBIOSCallbackCallback
- 设置从 EE Core 线程加载 BIOS 回调的函数。setCDVDNVMCallback
- 设置从 EE Core 线程保存和加载 BIOS 配置的回调函数。setCDVDGetMechaVerCallback
- 设置从 EE Core 线程加载 DVD 硬件序列号的回调函数。
保存管理
getFreezeInternalsFunc
- 以字节数组形式获取内部变量的函数getEmotionMemoryFunc
- 以字节数组形式获取 EE Core 内存的函数getIopMemoryFunc
- 以字节数组形式获取 I/O 处理器内存的函数getHwRegsFunc
- 以字节数组形式获取 EE Core 硬件寄存器的函数getIopHwRegsFunc
- 以字节数组形式获取 I/O 处理器硬件寄存器的函数getScratchpadFunc
- 以字节数组形式获取暂存器(缓冲区)内存的函数getVU0memFunc
- 以字节数组形式获取向量单元 0 内存的函数getVU1memFunc
- 以字节数组形式获取向量单元 1 内存的函数getVU0progFunc
- 以字节数组形式获取向量单元 0 程序(代码)的函数getVU1progFunc
- 以字节数组形式获取向量单元 1 程序(代码)的函数getFreezeOutFunc
- 以字节数组形式获取插件内存的函数setDoFreezeCallback
- 设置保存插件内存回调的函数
加载管理
setFreezeInFunc
- 以字节数组形式设置插件内存的函数setFreezeInternalsFunc
- 以字节数组形式设置内部变量的函数setEmotionMemoryFunc
- 以字节数组形式设置 EE Core 内存的函数setIopMemoryFunc
- 以字节数组形式设置 I/O 处理器内存的函数setHwRegsFunc
- 以字节数组形式设置 EE Core 硬件寄存器的函数setIopHwRegsFunc
- 以字节数组形式设置 I/O 处理器硬件寄存器的函数setScratchpadFunc
- 以字节数组形式设置暂存器(缓冲区)内存的函数setVU0memFunc
- 以字节数组形式设置向量单元 0 内存的函数setVU1memFunc
- 以字节数组形式设置向量单元 1 内存的函数setVU0progFunc
- 以字节数组形式设置向量单元 0 程序(代码)的函数setVU1progFunc
- 以字节数组形式设置向量单元 1 程序(代码)的函数
Elf 文件管理
PCSX2_Hle_SetElfPathFunc
- 设置 Elf 文件路径的函数(不可用,但静态链接需要)
这些函数有特定的调用顺序,外部框架必须执行它们并通过特定的算法调用回调函数中的代码。
在 C# 代码中,启动模拟器从初始化开始
PCSX2LibNative.Instance.DetectCpuAndUserModeFunc();
PCSX2LibNative.Instance.AllocateCoreStuffsFunc
(PCSX2Controller.Instance.m_Pcsx2Config.serialize());
PCSX2LibNative.Instance.PCSX2_Hle_SetElfPathFunc("");
绑定回调函数
private void Bind()
{
foreach (var l_Module in ModuleManager.Instance.Modules)
{
PCSX2LibNative.Instance.setModule(l_Module);
}
PCSX2LibNative.Instance.setPluginsInitCallback = delegate()
{
ModuleControl.Instance.init();
};
PCSX2LibNative.Instance.setPluginsCloseCallback = delegate()
{
ModuleControl.Instance.close();
};
PCSX2LibNative.Instance.setPluginsShutdownCallback = delegate()
{
ModuleControl.Instance.shutdown();
};
PCSX2LibNative.Instance.setPluginsOpenCallback = delegate()
{
ModuleControl.Instance.open();
};
PCSX2LibNative.Instance.setPluginsAreLoadedCallback = delegate()
{
return ModuleControl.Instance.areLoaded();
};
PCSX2LibNative.Instance.setUI_EnableSysActionsCallback = delegate()
{
if (!PCSX2Controller.Instance.innerCall())
LockScreenManager.Instance.hide();
};
PCSX2LibNative.Instance.setLoadAllPatchesAndStuffCallback = delegate(uint a_FirstArg)
{
PatchAndGameFixManager.Instance.LoadAllPatchesAndStuff();
};
PCSX2LibNative.Instance.setLoadBIOSCallbackCallback =
delegate(IntPtr a_FirstArg, Int32 a_SecondArg)
{
Omega_Red.Tools.BiosControl.LoadBIOS(a_FirstArg, a_SecondArg);
};
PCSX2LibNative.Instance.setCDVDNVMCallback =
delegate(IntPtr buffer, Int32 offset, Int32 bytes, Boolean read)
{
Omega_Red.Tools.BiosControl.NVMFile(buffer, offset, bytes, read);
};
PCSX2LibNative.Instance.setCDVDGetMechaVerCallback = delegate(IntPtr buffer)
{
Omega_Red.Tools.BiosControl.CDVDGetMechaVer(buffer);
};
PCSX2LibNative.Instance.setDoFreezeCallback =
delegate(IntPtr a_FirstArg, Int32 a_mode, Int32 a_ModuleCode)
{
return ModuleControl.Instance.doFreeze(a_FirstArg, a_mode, a_ModuleCode);
};
}
并恢复 EE Core 线程
PCSX2LibNative.Instance.SysThreadBase_ResumeFunc();
Omega Red 项目很复杂,审查整个代码需要一本真正的书。然而,我认为关注编程解决方案的架构很重要。
模块 - 插件
原始 PCSX2 项目定义了一组外部插件用于与模拟器交互——视频、音频、手柄、内存卡。Omega Red 项目也有类似的解决方案,称为模块——它们不存在于真实的文件中,而是作为资源包含在可执行文件中。在应用程序启动时,它们会被解包,在应用程序退出时,它们会被删除。另一个方面是定义模块接口——每个模块都有自己的一组函数供 PCSX2Lib
使用,但这对于外部框架来说是无用的——因此,在这个项目中,有一个自己的通用接口用于与模块交互。
PCSX2_EXPORT_C_(void*) getAPI()
{
return (void*)&CDVDapi_Iso;
}
PCSX2_EXPORT_C execute(const wchar_t* a_command, wchar_t** a_result)
{
g_CDVD.execute(a_command, a_result);
}
PCSX2_EXPORT_C releaseString(wchar_t* a_string)
{
if (a_string != nullptr)
delete[] a_string;
}
函数 getAPI
返回指向 PCSX2Lib
API 的指针,函数 execute
执行来自外部框架的命令,并以 XML 文档的形式返回结果,函数 releaseString
释放返回结果字符串的内存。
视频渲染器
Omega Red 有一个用于渲染的 DirectX11 模块,但有一个问题——WPF 只支持 D3DImage
中的 DirectX9 纹理。这个问题通过以下方式解决:
- 使用“
Shared
”句柄创建 DirectX 纹理——Direct3D9Ex
支持使用 DXGI 共享句柄创建 DirectX9 纹理。 - 从 DirectX9 纹理的 DXGI 共享句柄创建共享的 DirectX11 纹理
// Create shared texture
CComPtr<ID3D11Resource> l_Resource;
hr = m_dev->OpenSharedResource(sharedhandle, IID_PPV_ARGS(&l_Resource));
if (FAILED(hr)) return false;
hr = l_Resource->QueryInterface(IID_PPV_ARGS(&m_SharedTexture));
触摸板
Omega Red 集成了触摸板
它是根据 XInput
规范实现的,通过定义 C# 结构体将按钮状态和模拟摇杆轴复制到本机内存中
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct XINPUT_GAMEPAD {
public UInt16 wButtons;
public byte bLeftTrigger;
public byte bRightTrigger;
public Int16 sThumbLX;
public Int16 sThumbLY;
public Int16 sThumbRX;
public Int16 sThumbRY;
};
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct XINPUT_STATE {
public UInt32 dwPacketNumber;
public XINPUT_GAMEPAD Gamepad;
};
屏幕和视频捕捉
原始 PCSX2 具有一些用于捕获渲染的游戏图像和录制游戏视频的功能,但这些功能不易访问且未针对此目的进行优化。我决定包含更灵活的解决方案。它使用 WPF 功能通过将 DirectX 纹理从后缓冲区复制到 C# BitmapSource
并使用 JpegBitmapEncoder
进行编码,将屏幕捕获到 JPEG 格式图像文件。
public byte[] takeScreenshot()
{
byte[] l_result = null;
var l_D3D9Image = imageSource as D3D9Image;
if(l_D3D9Image != null)
{
BitmapSource l_bitmap = l_D3D9Image.getBackBuffer();
JpegBitmapEncoder l_encoder = new JpegBitmapEncoder();
l_encoder.QualityLevel = 75;
l_encoder.Frames.Add(BitmapFrame.Create(l_bitmap));
using (var outputStream = new MemoryStream())
{
l_encoder.Save(outputStream);
l_result = outputStream.ToArray();
}
}
return l_result;
}
为了捕捉游戏视频,我使用了 CaptureManager
SDK——这是一个用于从许多源捕捉视频和音频的简单 SDK。对于本项目,CaptureManager
SDK 具有以下优点:
- 简单灵活的 C#
interface
——它允许将视频捕获代码集成到任何 WPF/C# 项目中。 - 通过 XML 文档定义源——捕获 DirectX11 纹理的指针地址以文本格式写入数字。
string lPresentationDescriptor = "<?xml version='1.0' encoding='UTF-8'?>" +
"<PresentationDescriptor StreamCount='1'>" +
"<PresentationDescriptor.Attributes Title='Attributes of Presentation'>" +
"<Attribute Name='MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK'
GUID='{58F0AAD8-22BF-4F8A-BB3D-D2C4978C6E2F}' Title='The symbolic link for a
video capture driver.' Description='Contains the unique symbolic link
for a video capture driver.'>" +
"<SingleValue Value='ImageCaptureProcessor' />" +
"</Attribute>" +
"<Attribute Name='MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME'
GUID='{60D0E559-52F8-4FA2-BBCE-ACDB34A8EC01}'
Title='The display name for a device.'
Description='The display name is a human-readable string,
suitable for display in a user interface.'>" +
"<SingleValue Value='Image Capture Processor' />" +
"</Attribute>" +
"</PresentationDescriptor.Attributes>" +
"<StreamDescriptor Index='0' MajorType='MFMediaType_Video'
MajorTypeGUID='{73646976-0000-0010-8000-00AA00389B71}'>" +
"<MediaTypes TypeCount='1'>" +
"<MediaType Index='0'>" +
"<MediaTypeItem Name='MF_MT_FRAME_RATE'
GUID='{C459A2E8-3D2C-4E44-B132-FEE5156C7BB0}' Title='Frame rate.'
Description='Frame rate of a video media type, in frames per second.'>" +
"<RatioValue Value='30.0'>" +
"<Value.ValueParts>" +
"<ValuePart Title='Numerator' Value='30' />" +
"<ValuePart Title='Denominator' Value='1' />" +
"</Value.ValueParts>" +
"</RatioValue>" +
"</MediaTypeItem>" +
"<MediaTypeItem Name='CM_DirectX11_Capture_Texture'
GUID='{179B7A05-496A-4C9F-B8C6-15F04E669595}'>" +
"<SingleValue Value='{Temp_Capture_Texture}' />" +
"</MediaTypeItem>" +
"</MediaType>" +
"</MediaTypes>" +
"</StreamDescriptor>" +
"</PresentationDescriptor>";
其中 {Temp_Capture_Texture}
被替换为用于用作源的 DirectX11 渲染目标纹理的指针的文本表示。
lPresentationDescriptor = lPresentationDescriptor.Replace
("{Temp_Capture_Texture}", a_PtrDirectX11Source.ToInt32().ToString());
音频流通过“音频回放”录制——从内部系统扬声器输出。
CaptureManager
SDK 使用不同的视频和音频编码器来录制游戏视频。
依赖
Omega Red 基于 PCSX2 代码,为了在编译过程中正确链接原始代码,它被放在根文件夹中。
结果二进制可执行文件放在“bin”文件夹中。
此项目的源代码存储在 GitHub 的 Omega_Red
仓库中:https://github.com/Xirexel/OmegaRed。
最终结果
结果以 ONE 可执行文件的形式呈现——它以一个文件的形式包含所有必需的代码。因此,该程序专为“触摸”控制而设计,并具有以下功能:
- 全屏——程序只有一个窗口大小——最大化,占据整个屏幕区域。
- 最低配置——大多数游戏默认配置已足够。
- 它只允许单人游戏配置。
接下来的子部分更详细地描述了结果程序,但小 HTML 图像中的全屏图像质量较差。
用户界面
Omega Red 是一个旨在使原始 PCSX2 更友好、更易于使用的项目。这些目标反映在用户界面的设计中,该界面具有 WPF GUI 框架可提供的功能。
PCSX2 的原始用户界面为“Win95”风格
已替换为具有“触摸-瓦片”设计的新风格,以支持屏幕触摸控制,同时尽量少使用经典的“鼠标”HID。
资源管理
花费了大量时间设计了一个简单的方案,用于轻松控制和管理主要游戏资源:BIOS、ISO 游戏光盘、内存卡、手柄、存档。
BIOS 是模拟器工作过程的重要组成部分。Omega Red 支持从二进制文件或 ZIP 存档中读取数据。
ISO 光盘映像文件是在此模拟器上启动游戏的唯一方式。这些文件已在游戏光盘集合中注册并得到识别——原始 PCSX2 模拟器在启动游戏时读取和检查 ISO 文件,而这个新模拟器则在不启动模拟器的情况下检查光盘映像类型、支持的 PS2、游戏光盘区域。
加载和保存状态已大大改变。原始 PCSX2 模拟器对每个游戏限制为 10 个文件槽,而不定义保存日期和游戏进度。
Omega Red 允许在游戏过程中为每个游戏创建多达 100 个文件槽,每个文件槽保存保存日期、当前游戏会话的时长以及捕获的游戏过程图像。
此外,在停止模拟器或关闭 Omega Red 应用程序时,当前游戏进程会保存到名为“Autosave”的特殊文件中——这允许玩家在忘记保存最后一个游戏会话时继续游戏。此外,加载已保存状态的顺序也已改变——原始 PCSX2 需要加载 BIOS,加载游戏光盘,检查它,然后才需要点击所需槽,而 Omega Red 只需单击即可加载已保存的游戏状态。结果,加载已保存游戏的时间从 20 秒减少到 3-5 秒。
该项目允许更“灵活”地管理 PS2 内存卡。
可以在游戏过程中创建内存卡,并且模拟器可以在游戏过程中切换到另一张内存卡,以便在游戏中保存和加载数据。创建的内存卡文件的名称基于游戏名称和游戏光盘的唯一序列 ID——这允许仅显示“自己的”游戏内存卡文件。
手柄
Omega Red 允许在“触摸”手柄和“游戏”手柄之间切换游戏控制。
捕捉
原始 PCSX2 中捕获图像和视频的功能存在一些限制,并且需要研究配置才能找到这些命令。在这个项目中,我重新设计了它,并在显示屏顶部添加了必要的控制按钮。
捕获图像的质量是固定的(75%)。“实时”游戏视频的质量可以在 10% 到 99% 之间更改。创建的图像和视频文件的名称由游戏友好名称和当前日期生成。分离的面板允许在暂停时查看捕获的图像和视频。
通用配置
“通用配置”面板允许更改非游戏配置:“显示模式”、“控制模式”、“置顶显示”、“视频压缩质量”、“关闭宽屏格式”、“当前语言”。
“显示模式”允许在全屏和区域之间切换显示区域。
“控制模式”允许在“按钮”和“触摸”模式之间切换通用控制。
“置顶显示”将应用程序窗口置于其他应用程序之上。“视频压缩质量”允许设置视频捕获功能的质量压缩。“关闭宽屏格式”——默认情况下,Omega Red 会应用补丁来将屏幕比例更改为 16/9,但此选项允许禁用这些补丁。“当前语言:”允许更改当前语言。
“当前配色方案:”允许更改当前配色方案。
“触摸板缩放”允许更改游戏触摸板元素的比例,以适应屏幕尺寸和手指大小。
162% 的触摸板缩放
78% 的触摸板缩放
细分曲面
Omega Red 使用 DirectX 11 进行模拟游戏渲染。这项技术支持现代显卡许多技术。其中之一是 细分曲面——一种在运行时提高网格质量的特殊技术。
我通过添加特殊的 渲染模式 配置将细分曲面包含到 Omega Red 中。
结果如下:
默认
细分曲面
默认
细分曲面
FXAA
此选项 启用 FXAA 允许开启或关闭抗锯齿后处理。
快速保存
Omega Red 支持保存和加载当前游戏状态。我通过添加快速保存和加载命令进行了改进。
Omega Red 有 5 个快速保存槽,按队列顺序排列——最新的保存位于队列前面,最旧的保存位于队列尾部。
物理游戏手柄扩展了快速保存功能——通过物理游戏手柄上的特殊按钮组合进行快速保存和快速加载。
快速保存:Start + Left Shoulder
快速加载:Start + Right Shoulder
快速加载具有“30 秒规则”——这意味着如果下一次快速加载时间距离上一次不足 30 秒,Omega Red 将加载上一个快速保存槽。结果,可以通过反复单击组合键 Start + Right Shoulder 来查看所有当前的快速保存,以找到合适的保存!
音量控制
Omega Red 支持通过专用的滑块音量和静音按钮控制渲染音频的音量。
此组件仅在本地音频渲染器的上下文中控制音频音量,而**不更改**扬声器输出音量!
离屏模式
Omega Red 具有在个人电脑主桌面屏幕上渲染游戏视频和音频的功能。这是该 PS2 模拟器的正常模式。然而,经过一些研究,我决定改进模拟器的架构——添加**新功能,通过 LAN 通过 RTSP 协议将渲染的游戏远程显示到 Android TV 等远程设备上**。为了防止在使用个人电脑处理不同任务时可能出现的问题,我为 Omega Red 添加了一个特殊模式:“离屏”,可通过参数命令 /OffScreen
启用。此模式将视频和音频渲染输出从当前用户上下文重定向到 RTSP 服务器输入——结果,在本地个人电脑上,Omega Red PS2 模拟器在隐藏状态下启动,带有控制图标。PS2 游戏可以从任务栏图标启动。游戏控制由物理游戏手柄管理。
快速加载图标
Omega Red 保留了最后选择的游戏光盘的引用,并在下一个应用程序会话中选择它——很有可能在最后一个保存处继续上一个游戏——Autosaving
。显示自动保存的图标允许通过单击该图标来继续游戏。
PSP 模拟器
大约半年前,我看到了 Steam 上的“《战场女武神 4》”。我一直对“4”这个数字感到困惑——很久以前我就玩过“《战场女武神》”。但是,“《战场女武神 2》”和“《战场女武神 3》”呢?那些游戏是在游戏机上发布的。对我来说最容易访问的是 PSP 版本,通过 PPSSPP——但我并不喜欢那个模拟器的触摸控制。此外,我不想有两个不同的程序来玩游戏并在它们之间切换。所以,我有一个想法——将 PPSSPP 模拟器添加到 Omega Red 中,并创建 PS2 和 PSP 游戏平台!我试过了,并且是可行的。
GoogleDrive 和 YouTube
经过一段时间的研究,我决定添加 Google 账户“绑定”——它允许包含一些 Google 服务的功能:GoogleDrive 和 YouTube——将游戏保存存储在“云”中,并轻松将录制的视频上传到您的 YouTube 账户。
点击 Google 图标后,应用程序将在当前默认的 Internet 浏览器中打开您的 Google 账户,以便将 Omega Red 与您的 Google 账户“绑定”。
“绑定”后,所有保存的文件都会获得 GoogleDrive 图标,以标记“云”保存访问。
还可以从 GoogleDrive 下载保存到您的计算机。
与 YouTube 账户“绑定”后,Omega Red 视频播放器中会显示一个特殊的 YouTube 图标,用于直接将当前视频上传到您的账户。
游戏直播(YouTube、Facebook、Twitch)
是的,将实时视频和音频游戏直播添加到 PS2/PSP 模拟器中,这真是个疯狂的主意。
这项任务的有趣之处在于:内部捕获视频和音频。常规的流媒体应用程序通常从屏幕捕获视频,从扬声器捕获音频。然而,我决定改变概念——在模拟器级别添加流媒体功能,以将视频和音频与 Windows 操作系统系统混音器上下文隔离开来。
流媒体功能有两个部分:
- 配置视频和音频编码,以及选择用于直播的互联网流媒体服务。
- 配置混音器——将实时游戏视频和音频与附加源混合:麦克风、网络摄像头、JPG/PNG/GIF(动画)文件。
配置视频和音频编码
流媒体配置从选择捕获模式 Streaming
开始。此模式允许选择流媒体的视频和音频比特率。此外,还可以选择或输入目标流媒体服务的地址。
需要注意的是,Omega Red 支持开放和加密(SSL)连接!
Facebook 的流媒体服务发布了以下公告:
“正如去年四月宣布的那样,从 5 月 1 日起,所有直播视频上传都必须使用 RTMPS(RTMP over a TLS/SSL connection),并且开发者在此日期之前确保他们的集成符合要求非常重要。
我们致力于保护 Facebook 平台的完整性,作为这项工作的一部分,我们要求直播视频上传使用 RTMPS,因为它为视频内容提供了更高程度的安全性。”
我决定支持 RTMPS 非常有前景,并花了一些时间将 OpenSSL 添加到 RTMP 库中。它是可行的!Facebook 在加密连接上接收流媒体!
流媒体从按下图标 Record/Streaming
开始。
混音器配置在录制/流媒体开始后可用。
媒体源在 媒体 面板中可用——这个展开器菜单包含用于混合的视频和音频源。此外,还可以添加 JPG/PNG/GIF(动画)文件进行混合。
对于麦克风,可以控制音频混合的比例。麦克风的选择通过单击复选框按钮完成,混合通过音量滑块控制。
网络摄像头的选择通过单击复选按钮完成,网络摄像头视频的分辨率通过单击组合菜单选择。
混合的透明度可以通过不透明度滑块控制。
混合视频流的位置和大小可以在流媒体时更改。
我的测试流媒体
PS1 模拟器
经过一段时间的思考,我决定在我的项目中加入第一个 PlayStation 系列游戏机的模拟器。然而,我遇到了 PCSX-Reloaded 开源社区停止支持的问题——代码已经停止支持大约 15-20 年了,而且将基于 DirectDraw 的图形渲染器与 WPF 和 DirectX 11 技术集成存在问题。我决定通过将 PCSX-Reloaded 的软件渲染器捕获的视频从系统内存快速复制到视频内存(使用 DirectX 11 纹理)来绑定它。这个解决方案效率不高,但它允许获得 100% 的兼容性,并且适用于第一个版本。
此外,我还添加了两个用于与 PCSX-Reloaded 兼容的功能:
视觉震动
——PlayStation 的游戏手柄具有反馈功能,可以使玩家更深入地融入游戏。Omega Red 项目支持游戏手柄控制的反馈,但触摸板的反馈是 PlayStation 6 的一个奇妙功能!然而,在研究 PCSX-Reloaded 代码时,我发现了从游戏手柄模拟器到图形渲染器的回调函数的引用!我发现通过改变渲染图像来响应游戏手柄振动的想法很棒!结果,我在我的项目中添加了这项功能:共享内存卡
——原始 Omega Red 项目根据游戏光盘的唯一序列号为每个游戏创建内存卡,以防止一个游戏的保存覆盖另一个游戏。这种模式对 PlayStation 2 有效,因为游戏通常只使用一张 DVD,但在 PlayStation 1 游戏中,在游戏光盘之间传输游戏状态存在问题——许多 PlayStation 1 游戏需要两张或更多光盘。因此,我决定为所有 PlayStation 1 游戏添加一个共享内存卡。
此外,我还添加了三项新功能:
- 为实时游戏过程选择视频编码的恒定比特率:
选择目标录制文件的大小:
- 按组分割 PlayStation 1、PlayStation 2 和 PlayStation Portable 的 BIOS 和游戏光盘:
- 在相关部分的头部显示当前 BIOS 和游戏光盘的名称:
您可以在接下来的演示中看到这三个平台上的游戏演示。
新的 PS1 图形渲染器
经过一段时间测试 PS1 代码后,我发现软件渲染器存在局限性——在我试图将电视分辨率的结果图像扩展到高清 720p 时,我发现图像包含多边形和纹理的巨大像素伪影。合适的解决方案是用硬件渲染器替换软件渲染器。然而,我发现目前 PS1 的硬件渲染器解决方案使用 OpenGL 驱动程序——这对我的 Omega Red 项目来说是个问题,因为它基于 WPF、DirectX9 和 DirectX11。我只找到了一个解决方案——用 DirectX11 编写新的图形渲染器!
我的工作成果很好,并且非常有前景。
您可以在接下来的演示中看到这些渲染器的比较演示。
刷新帧率
该项目使用 WPF C# 框架作为模拟器渲染器和显示图形上下文之间的中间层:渲染器将场景绘制到纹理中,WPF 框架将该纹理嵌入到用户界面中。将 DirectX 纹理嵌入用户界面是 WPF 图形驱动程序的“瓶颈”——WPF 是为 Windows XP SP2 设计的,并且在底层使用 DirectX9 层,嵌入 DirectX9 纹理会消耗 CPU 资源。
结果,CPU 负载增加,CPU 延迟也增加——在模拟器的音频流中出现静默停顿。通过跳过 DirectX9 纹理嵌入可以减少 CPU 负载。纹理更新的当前频率可以通过选项“Show frames per second
”进行监控。
帧更新可以通过选项“Skip frame mode:
”进行更改。
4K (2160p) 分辨率支持 PS1、PS2、PSP 游戏
大多数模拟器都有软件解决方案,可以通过本机 API:DirectX 或 OpenGL 来渲染游戏场景。结果,模拟器具有不同功能的图形引擎。其中之一是使用目标输出窗口分辨率进行渲染。此解决方案使用特殊的目标视频纹理作为 PS1、PS2、PSP 模拟器的目标渲染目的地,并且该纹理的大小决定了虚拟摄像机的视角大小。
目标渲染纹理的大小在配置大小中定义。
720p / 2160p 渲染质量的比较可以在视频中看到。
恢复
Omega Red 的开发源于一个简单的想法,即改进原始 PCSX2 模拟器并添加一些新功能。它是一个更用户友好的解决方案,并且对 PCSX2 开发社区可能有用。
关注点
在开发项目 C# 部分时,我遇到了堆栈溢出问题——在处理 DVD 映像程序的数据时,程序会抛出堆栈溢出异常。经过一些研究,我发现原始 PCSX2 项目通过 C/C++ 宏的组合生成 MIPS 转换器的代码。这允许快速生成高效的代码,但这段代码具有递归结构,会消耗进程的 STACK。对于本机 C/C++ 来说,这不是问题,但 C# 项目的 STACK 大小有限,不足以处理 PCSX2 代码。它是如何解决的?——很简单。Visual Studio 套件中有一个名为“editbin.exe”的特殊程序,允许编辑编译后的程序——“/STACK:
”参数允许更改进程的 STACK。以下命令在“Post-build event command line”中执行:
"$(DevEnvDir)..\..\VC\bin\editbin.exe" /STACK:6000000 "$(TargetPath)"
它将 C# 程序的 STACK 扩展到 600 万字节。
历史和更新
2018/09/10:首次更新
- 添加了“当前配色方案:”选项以更改应用程序元素的颜色。
2018/11/19:第二次更新
- 将 Omega Red Visual Studio 项目从 VS2013 迁移到 VS2017,支持
VideoRenderer
项目中 GSdx 的最新更新。 - 添加了“触摸板缩放”选项,用于更改游戏触摸板元素的尺寸,以适应屏幕尺寸和手指大小。
- 添加了
MediaStream
项目,用于实现当前游戏“实时”流功能的 RTSP 服务器。
2019/02/11:第三次更新
- 添加了“细分曲面”配置。
- 添加了“音量控制”组件。
- 添加了“快速保存”组件。
- 添加了通过 RTSP 服务器 支持的远程显示。
2019/04/02:第四次更新
- 为
PCSX2
添加了“启用 FXAA”。 - 添加了“快速加载图标”。
- 添加了
PPSSPP
模拟器。
2019/06/27:第五次更新
- 添加了 GoogleDrive 和 YouTube 支持。
- 添加了游戏直播功能。
2019/11/25:第六次更新
- 添加了 PlayStation 1 的
PCSX-Reloaded
模拟器。 - 添加了“启用了视觉震动”。
- 添加了共享内存卡。
- 添加了视频编码器比特率选择。
- 添加了录制文件大小选择。
- 添加了 BIOS 和游戏光盘分组。
- 添加了在相关部分头部显示当前 BIOS 和游戏光盘的名称。
2020/01/21:第七次更新
- 将 PlayStation 1 模拟器中的软件渲染器替换为 DirectX 11 渲染器。
2020/07/13:第八次更新
- Omega Red 视频指南;
- 添加 刷新帧率;
- 支持游戏手柄。
2020/08/03:第九次更新
- 添加了渲染分辨率选择:710p、1080p、1440p、2160p。