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- 获取- ElfFileCRC 校验和的函数
- 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。


