From 81f50cf4e937397ac9ae36f3adb23b4b4f08d26f Mon Sep 17 00:00:00 2001 From: LTDCrissOK Date: Thu, 5 Nov 2026 18:32:46 -0300 Subject: [PATCH 01/16] Add host file handles to IOP IOMAN --- ps2xIOP/src/emulator/imports/iop_ioman.h | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/ps2xIOP/src/emulator/imports/iop_ioman.h b/ps2xIOP/src/emulator/imports/iop_ioman.h index 182f786b17..aa2468f11e 100644 --- a/ps2xIOP/src/emulator/imports/iop_ioman.h +++ b/ps2xIOP/src/emulator/imports/iop_ioman.h @@ -1,6 +1,9 @@ #pragma once +#include "ps2x/iop/iop_host.h" + #include +#include #include #include @@ -13,7 +16,7 @@ namespace ps2x::iop::detail class IopIoman { public: - explicit IopIoman(IopMemory &memory) noexcept; + IopIoman(IopHost &host, IopMemory &memory) noexcept; void reset(); [[nodiscard]] bool dispatchImport(uint16_t ordinal, IopCpuState &cpu, IopGuestExecutor &executor); @@ -26,7 +29,16 @@ namespace ps2x::iop::detail std::string name; }; + struct OpenFile + { + uint64_t hostHandle = 0u; + uint64_t offset = 0u; + uint64_t size = 0u; + }; + + IopHost &m_host; IopMemory &m_memory; std::vector m_devices; + std::map m_openFiles; }; } From 7762d9a280ca44c41aa5cc11afae6951a5b4e343 Mon Sep 17 00:00:00 2001 From: LTDCrissOK Date: Thu, 5 Nov 2026 18:32:49 -0300 Subject: [PATCH 02/16] Implement read-only IOP IOMAN file operations --- ps2xIOP/src/emulator/imports/iop_ioman.cpp | 160 ++++++++++++++++++++- 1 file changed, 158 insertions(+), 2 deletions(-) diff --git a/ps2xIOP/src/emulator/imports/iop_ioman.cpp b/ps2xIOP/src/emulator/imports/iop_ioman.cpp index 3eb41190f5..8ab9cb309f 100644 --- a/ps2xIOP/src/emulator/imports/iop_ioman.cpp +++ b/ps2xIOP/src/emulator/imports/iop_ioman.cpp @@ -8,13 +8,19 @@ namespace ps2x::iop::detail { - IopIoman::IopIoman(IopMemory &memory) noexcept - : m_memory(memory) + IopIoman::IopIoman(IopHost &host, IopMemory &memory) noexcept + : m_host(host), m_memory(memory) { } void IopIoman::reset() { + for (const auto &[fd, file] : m_openFiles) + { + (void)fd; + m_host.closeHostFile(file.hostHandle); + } + m_openFiles.clear(); m_devices.clear(); } @@ -29,6 +35,156 @@ namespace ps2x::iop::detail switch (ordinal) { + case 4: // open + { + constexpr uint32_t kAccessModeMask = 0x0003u; + constexpr uint32_t kReadOnly = 0x0001u; + constexpr uint32_t kCreate = 0x0200u; + constexpr uint32_t kTruncate = 0x0400u; + constexpr uint32_t kExclusive = 0x0800u; + + const std::string guestPath = m_memory.readString(a0, 1024u); + const uint32_t mode = cpu.gpr[5]; + if (guestPath.empty() || + (mode & kAccessModeMask) != kReadOnly || + (mode & (kCreate | kTruncate | kExclusive)) != 0u) + { + setV0(0xFFFFFFFFu); + return true; + } + + const std::string hostPath = m_host.translateGuestPath(guestPath); + if (hostPath.empty()) + { + setV0(0xFFFFFFFFu); + return true; + } + + const uint64_t handle = m_host.openHostFile(hostPath); + if (handle == 0u) + { + setV0(0xFFFFFFFFu); + return true; + } + + uint64_t size = 0u; + if (!m_host.hostFileSize(handle, size)) + { + m_host.closeHostFile(handle); + setV0(0xFFFFFFFFu); + return true; + } + + int32_t fd = -1; + for (int32_t candidate = 3; candidate < 256; ++candidate) + { + if (m_openFiles.find(candidate) == m_openFiles.end()) + { + fd = candidate; + break; + } + } + if (fd < 0) + { + m_host.closeHostFile(handle); + setV0(0xFFFFFFFFu); + return true; + } + + m_openFiles.emplace(fd, OpenFile{handle, 0u, size}); + setV0(static_cast(fd)); + return true; + } + case 5: // close + { + const int32_t fd = static_cast(a0); + const auto file = m_openFiles.find(fd); + if (file == m_openFiles.end()) + { + setV0(0xFFFFFFFFu); + return true; + } + + m_host.closeHostFile(file->second.hostHandle); + m_openFiles.erase(file); + setV0(0u); + return true; + } + case 6: // read + { + const int32_t fd = static_cast(a0); + const auto file = m_openFiles.find(fd); + const uint32_t destination = cpu.gpr[5]; + const uint32_t requested = cpu.gpr[6]; + if (file == m_openFiles.end() || requested > IopMemory::RamSize) + { + setV0(0xFFFFFFFFu); + return true; + } + if (requested == 0u) + { + setV0(0u); + return true; + } + + std::vector buffer(requested); + size_t bytesRead = 0u; + if (!m_host.readHostFile(file->second.hostHandle, + file->second.offset, + buffer.data(), + buffer.size(), + bytesRead) || + bytesRead > buffer.size() || + !m_memory.writeRam(destination, buffer.data(), bytesRead)) + { + setV0(0xFFFFFFFFu); + return true; + } + + file->second.offset += bytesRead; + setV0(static_cast(bytesRead)); + return true; + } + case 8: // lseek + { + const int32_t fd = static_cast(a0); + const auto file = m_openFiles.find(fd); + if (file == m_openFiles.end()) + { + setV0(0xFFFFFFFFu); + return true; + } + + const int32_t offset = static_cast(cpu.gpr[5]); + const uint32_t whence = cpu.gpr[6]; + int64_t base = 0; + switch (whence) + { + case 0u: // SEEK_SET + base = 0; + break; + case 1u: // SEEK_CUR + base = static_cast(file->second.offset); + break; + case 2u: // SEEK_END + base = static_cast(file->second.size); + break; + default: + setV0(0xFFFFFFFFu); + return true; + } + + const int64_t newOffset = base + static_cast(offset); + if (newOffset < 0 || newOffset > 0x7FFFFFFFLL) + { + setV0(0xFFFFFFFFu); + return true; + } + + file->second.offset = static_cast(newOffset); + setV0(static_cast(newOffset)); + return true; + } case 20: // AddDrv { if (a0 == 0u || m_devices.size() >= kMaxDevices) From 34736c50d57d6a1ae2622746d4f843817519b9eb Mon Sep 17 00:00:00 2001 From: LTDCrissOK Date: Thu, 5 Nov 2026 18:32:51 -0300 Subject: [PATCH 03/16] Pass IOP host adapter into IOMAN --- ps2xIOP/src/emulator/iop_emulator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ps2xIOP/src/emulator/iop_emulator.cpp b/ps2xIOP/src/emulator/iop_emulator.cpp index e5f63a9fe2..c95981cf6f 100644 --- a/ps2xIOP/src/emulator/iop_emulator.cpp +++ b/ps2xIOP/src/emulator/iop_emulator.cpp @@ -105,7 +105,7 @@ namespace ps2x::iop::detail heaplib(memory), intrman(memory), timrman(), - ioman(memory), + ioman(host, memory), cpuCore(memory), imports(memory), loadcore(memory, imports) From aaa76b1f8516007bf843bafa18ad1abdf16fd38f Mon Sep 17 00:00:00 2001 From: LTDCrissOK Date: Thu, 5 Nov 2026 18:32:54 -0300 Subject: [PATCH 04/16] Test IOP IOMAN open read seek and close --- ps2xIOP/tests/iop_import_tests.cpp | 143 ++++++++++++++++++++++++++++- 1 file changed, 141 insertions(+), 2 deletions(-) diff --git a/ps2xIOP/tests/iop_import_tests.cpp b/ps2xIOP/tests/iop_import_tests.cpp index 3035618443..71ce04dd05 100644 --- a/ps2xIOP/tests/iop_import_tests.cpp +++ b/ps2xIOP/tests/iop_import_tests.cpp @@ -4,6 +4,7 @@ #include "emulator/imports/iop_cdvd.h" #include "emulator/imports/iop_imports.h" #include "emulator/imports/iop_loadcore.h" +#include "emulator/imports/iop_ioman.h" #include "emulator/imports/iop_timrman.h" #include "emulator/services/iop_rpc.h" #include "ps2x/iop/iop_host.h" @@ -49,6 +50,62 @@ namespace void log(LogLevel, std::string_view) override {} }; + class MemoryFileHost final : public NullHost + { + public: + MemoryFileHost() + : bytes{'A', 'B', 'C', 'D', 'E', 'F'} + { + } + + std::string translateGuestPath(std::string_view path) const override + { + return path == "cdrom0:\\AUDIO\\BANK.BIN;1" ? "virtual:/AUDIO/BANK.BIN" : std::string{}; + } + + uint64_t openHostFile(std::string_view path) override + { + if (path != "virtual:/AUDIO/BANK.BIN") + return 0u; + ++openCount; + return 1u; + } + + bool hostFileSize(uint64_t handle, uint64_t &size) const override + { + if (handle != 1u) + return false; + size = bytes.size(); + return true; + } + + bool readHostFile(uint64_t handle, + uint64_t offset, + void *destination, + size_t size, + size_t &bytesRead) override + { + bytesRead = 0u; + if (handle != 1u || (!destination && size != 0u)) + return false; + if (offset >= bytes.size() || size == 0u) + return true; + bytesRead = std::min(size, bytes.size() - static_cast(offset)); + std::memcpy(destination, bytes.data() + offset, bytesRead); + return true; + } + + void closeHostFile(uint64_t handle) override + { + if (handle == 1u) + ++closeCount; + } + + std::vector bytes; + uint32_t openCount = 0u; + uint32_t closeCount = 0u; + }; + class CdRootHost final : public NullHost { public: @@ -98,6 +155,88 @@ namespace return false; } + bool testIomanReadOnlyFileOperations() + { + MemoryFileHost host; + IopMemory memory; + IopIoman ioman(host, memory); + RecordingExecutor executor; + + constexpr uint32_t pathAddress = 0x2000u; + constexpr uint32_t firstBuffer = 0x2100u; + constexpr uint32_t secondBuffer = 0x2120u; + const char guestPath[] = "cdrom0:\\AUDIO\\BANK.BIN;1"; + if (!expect(memory.writeRam(pathAddress, guestPath, sizeof(guestPath)), + "could not place the IOP test path in memory")) + return false; + + IopCpuState cpu{}; + cpu.gpr[4] = pathAddress; + cpu.gpr[5] = 0x0001u; // FIO_O_RDONLY + if (!expect(ioman.dispatchImport(4u, cpu, executor), "ioman:4 (open) was not handled") || + !expect(cpu.gpr[2] == 3u, "ioman:4 did not return the first available file descriptor") || + !expect(host.openCount == 1u, "ioman:4 did not open the translated VFS path")) + return false; + const uint32_t fd = cpu.gpr[2]; + + cpu = {}; + cpu.gpr[4] = fd; + cpu.gpr[5] = firstBuffer; + cpu.gpr[6] = 4u; + if (!expect(ioman.dispatchImport(6u, cpu, executor), "ioman:6 (read) was not handled") || + !expect(cpu.gpr[2] == 4u, "ioman:6 returned the wrong byte count")) + return false; + char firstRead[4]{}; + if (!expect(memory.readRam(firstBuffer, firstRead, sizeof(firstRead)), + "ioman:6 did not write into IOP RAM") || + !expect(std::string(firstRead, sizeof(firstRead)) == "ABCD", + "ioman:6 read the wrong bytes")) + return false; + + cpu = {}; + cpu.gpr[4] = fd; + cpu.gpr[5] = 2u; + cpu.gpr[6] = 0u; // FIO_SEEK_SET + if (!expect(ioman.dispatchImport(8u, cpu, executor), "ioman:8 (lseek) was not handled") || + !expect(cpu.gpr[2] == 2u, "ioman:8 did not seek to the requested offset")) + return false; + + cpu = {}; + cpu.gpr[4] = fd; + cpu.gpr[5] = secondBuffer; + cpu.gpr[6] = 3u; + if (!expect(ioman.dispatchImport(6u, cpu, executor), "second ioman:6 (read) was not handled") || + !expect(cpu.gpr[2] == 3u, "second ioman:6 returned the wrong byte count")) + return false; + char secondRead[3]{}; + if (!expect(memory.readRam(secondBuffer, secondRead, sizeof(secondRead)), + "second ioman:6 did not write into IOP RAM") || + !expect(std::string(secondRead, sizeof(secondRead)) == "CDE", + "ioman:8/ioman:6 did not preserve the file position")) + return false; + + cpu = {}; + cpu.gpr[4] = fd; + if (!expect(ioman.dispatchImport(5u, cpu, executor), "ioman:5 (close) was not handled") || + !expect(cpu.gpr[2] == 0u, "ioman:5 did not return success") || + !expect(host.closeCount == 1u, "ioman:5 did not close the host file")) + return false; + + cpu = {}; + cpu.gpr[4] = fd; + cpu.gpr[5] = firstBuffer; + cpu.gpr[6] = 1u; + if (!expect(ioman.dispatchImport(6u, cpu, executor), "ioman:6 did not handle an invalid descriptor") || + !expect(cpu.gpr[2] == 0xFFFFFFFFu, "ioman:6 did not reject an invalid descriptor")) + return false; + + cpu = {}; + cpu.gpr[4] = pathAddress; + cpu.gpr[5] = 0x0002u; // FIO_O_WRONLY is not supported by the read-only host adapter. + return expect(ioman.dispatchImport(4u, cpu, executor), "ioman:4 did not handle a write-only open request") && + expect(cpu.gpr[2] == 0xFFFFFFFFu, "ioman:4 accepted a write-only open request"); + } + bool testLoadcoreRebootLibraryMode() { IopMemory memory; @@ -330,8 +469,8 @@ namespace int main() { - if (!testLoadcoreRebootLibraryMode() || !testCdvdSpecialControl() || !testCdvdSearchFile() || - !testTimrmanPeriodicCallback()) + if (!testIomanReadOnlyFileOperations() || !testLoadcoreRebootLibraryMode() || + !testCdvdSpecialControl() || !testCdvdSearchFile() || !testTimrmanPeriodicCallback()) return 1; std::cout << "ps2xIOP import tests passed\n"; return 0; From 303d3c93755567f3e455292e92fcfaae7e7e7f24 Mon Sep 17 00:00:00 2001 From: LTDCrissOK Date: Thu, 5 Nov 2026 18:33:25 -0300 Subject: [PATCH 05/16] Add explicit standard headers for IOMAN tests --- ps2xIOP/tests/iop_import_tests.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ps2xIOP/tests/iop_import_tests.cpp b/ps2xIOP/tests/iop_import_tests.cpp index 71ce04dd05..d727075413 100644 --- a/ps2xIOP/tests/iop_import_tests.cpp +++ b/ps2xIOP/tests/iop_import_tests.cpp @@ -10,6 +10,7 @@ #include "ps2x/iop/iop_host.h" #include +#include #include #include #include @@ -17,6 +18,7 @@ #include #include #include +#include namespace { From 7d0db93bbe6b26852cbc625f19e7bb1a0aebe469 Mon Sep 17 00:00:00 2001 From: LTDCrissOK Date: Thu, 5 Nov 2026 18:33:36 -0300 Subject: [PATCH 06/16] Run IOP import tests in CI --- .github/workflows/build.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index eb363c8b82..621f29d738 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,13 +30,15 @@ jobs: export CC=clang export CXX=clang++ fi - cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS=-msse4.1 -DCMAKE_CXX_FLAGS=-msse4.1 + cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS=-msse4.1 -DCMAKE_CXX_FLAGS=-msse4.1 -DPS2X_IOP_BUILD_TESTS=ON - name: Build run: cmake --build build --config Release - name: Tests - run: ./build/ps2xTest/ps2x_tests + run: | + ./build/ps2xTest/ps2x_tests + ./build/ps2xIOP/ps2_iop_import_tests windows-msvc: strategy: @@ -63,13 +65,14 @@ jobs: - name: Build shell: cmd run: | - cmake -B build + cmake -B build -DPS2X_IOP_BUILD_TESTS=ON cmake --build build --config Release - name: Tests shell: cmd run: | .\build\ps2xTest\Release\ps2x_tests.exe + .\build\ps2xIOP\Release\ps2_iop_import_tests.exe - name: Upload artifact uses: actions/upload-artifact@v6 with: From eff85c00e170bac050715aa09d9faeba2b6170e2 Mon Sep 17 00:00:00 2001 From: LTDCrissOK Date: Thu, 5 Nov 2026 18:36:21 -0300 Subject: [PATCH 07/16] Document read-only IOMAN file operation scope --- ps2xIOP/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ps2xIOP/README.md b/ps2xIOP/README.md index 2f47d9e7d4..1ef7c45803 100644 --- a/ps2xIOP/README.md +++ b/ps2xIOP/README.md @@ -55,3 +55,6 @@ ctest --test-dir out/build/iop-tests --output-on-failure The suites cover IRX execution, RPC, imports, version resolution and generic HLE compatibility. `ps2x_tests` also covers runtime SIF RPC/DMA integration. + + +The IOMAN compatibility layer currently supports read-only `open`, `close`, `read`, and `lseek` operations through the host VFS adapter. Write and directory operations are not implemented by this layer yet. From d208e1cd2c0cbdd3ee49d1c90684110e9caa4d2f Mon Sep 17 00:00:00 2001 From: LTDCrissOK Date: Thu, 5 Nov 2026 18:36:49 -0300 Subject: [PATCH 08/16] Allow manual GitHub Actions runs --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index eb363c8b82..43fbe81b69 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,6 +1,6 @@ name: build -on: [push, pull_request] +on: [push, pull_request, workflow_dispatch] jobs: linux-gcc-clang: From 71ddb7435a49b670b21607a7c33b2463737e100b Mon Sep 17 00:00:00 2001 From: LTDCrissOK Date: Thu, 5 Nov 2026 18:36:52 -0300 Subject: [PATCH 09/16] Allow manual GitHub Actions runs --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 621f29d738..ad896c516b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,6 +1,6 @@ name: build -on: [push, pull_request] +on: [push, pull_request, workflow_dispatch] jobs: linux-gcc-clang: From 3be7aa5e639ee9b99e88d738b462581120b8c725 Mon Sep 17 00:00:00 2001 From: LTDCrissOK Date: Sat, 7 Nov 2026 13:14:16 -0300 Subject: [PATCH 10/16] Add CMake script to enable OpenGL ES 3.0 for Raylib This CMake file enables OpenGL ES 3.0 for Raylib on Android by modifying the EGL context request. It includes checks for the target platform and the existence of the Raylib target and source file. --- ps2xRuntime/cmake/EnableAndroidGLES3.cmake | 52 ++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 ps2xRuntime/cmake/EnableAndroidGLES3.cmake diff --git a/ps2xRuntime/cmake/EnableAndroidGLES3.cmake b/ps2xRuntime/cmake/EnableAndroidGLES3.cmake new file mode 100644 index 0000000000..05506f898a --- /dev/null +++ b/ps2xRuntime/cmake/EnableAndroidGLES3.cmake @@ -0,0 +1,52 @@ +# EnableAndroidGLES3.cmake +# +# Include this file immediately AFTER FetchContent_MakeAvailable(raylib) +# in ps2xRuntime/CMakeLists.txt. +# +# This switches Raylib's Android renderer to OpenGL ES 3.0 and fixes the +# Android EGL context request, which Raylib 5.5 otherwise hardcodes to ES 2. +# +# Scope: presentation renderer only. The PS2 GS rasterizer remains the +# existing CPU backend. This file does NOT implement GPU GS rasterization. + +if(NOT ANDROID) + message(STATUS "Android GLES3 patch skipped: target is not Android") + return() +endif() + +if(NOT TARGET raylib) + message(FATAL_ERROR "Android GLES3 patch: Raylib target does not exist") +endif() + +set(_raylib_android_source + "${raylib_SOURCE_DIR}/src/platforms/rcore_android.c") + +if(NOT EXISTS "${_raylib_android_source}") + message(FATAL_ERROR + "Android GLES3 patch: Raylib Android source not found: " + "${_raylib_android_source}") +endif() + +file(READ "${_raylib_android_source}" _raylib_android_text) + +set(_old "EGL_CONTEXT_CLIENT_VERSION, 2,") +set(_new "EGL_CONTEXT_CLIENT_VERSION, (rlGetVersion() == RL_OPENGL_ES_30) ? 3 : 2,") + +string(FIND "${_raylib_android_text}" "${_new}" _already_patched) +if(NOT _already_patched EQUAL -1) + message(STATUS "Raylib Android EGL context already supports GLES 3") + return() +endif() + +string(FIND "${_raylib_android_text}" "${_old}" _old_found) +if(_old_found EQUAL -1) + message(FATAL_ERROR + "Android GLES3 patch: expected Raylib 5.5 EGL context line was not found. " + "No source file was modified.") +endif() + +string(REPLACE "${_old}" "${_new}" _raylib_android_text + "${_raylib_android_text}") +file(WRITE "${_raylib_android_source}" "${_raylib_android_text}") + +message(STATUS "Raylib Android EGL context patched for OpenGL ES 3.0") From 5c474add4c32dad1737630e46b4c18abcc1cfd1f Mon Sep 17 00:00:00 2001 From: LTDCrissOK Date: Sat, 7 Nov 2026 14:59:59 -0300 Subject: [PATCH 11/16] Update CMakeLists to include Android OpenGL ES support --- ps2xRuntime/CMakeLists.txt | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/ps2xRuntime/CMakeLists.txt b/ps2xRuntime/CMakeLists.txt index 589d76850a..ebd9000e12 100644 --- a/ps2xRuntime/CMakeLists.txt +++ b/ps2xRuntime/CMakeLists.txt @@ -376,6 +376,8 @@ function(ps2x_stage_ffmpeg_runtime_dlls target_name) endfunction() add_library(ps2_runtime STATIC + src/lib/gs/gs_backend.cpp + src/lib/gs/gs_opengl_backend.cpp src/lib/game_overrides.cpp src/lib/gs/ps2_gif_arbiter.cpp src/lib/ps2_audio.cpp @@ -506,12 +508,27 @@ target_include_directories(ps2_runtime PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src/lib/Kernel ) -target_link_libraries(ps2_runtime PUBLIC ps2_host_backend ffmpeg ps2_iop) -target_link_libraries(ps2EntryRunner - PRIVATE - ps2_runtime +target_link_libraries(ps2_runtime PUBLIC + ps2_host_backend + ffmpeg + ps2_iop ) +# Android OpenGL ES 3.2 backend +if(ANDROID) + find_library(ANDROID_EGL_LIBRARY EGL REQUIRED) + find_library(ANDROID_GLES3_LIBRARY GLESv3 REQUIRED) + + target_link_libraries(ps2_runtime PUBLIC + ${ANDROID_EGL_LIBRARY} + ${ANDROID_GLES3_LIBRARY} + ) + + target_compile_definitions(ps2_runtime PUBLIC + PS2X_HAS_GLES32=1 + ) +endif() + if(PS2X_ENABLE_DEBUG_UI AND NOT PS2X_IS_VITA AND NOT PS2X_IS_ANDROID) target_sources(ps2EntryRunner PRIVATE src/lib/ps2_debug_panel.cpp From b3a69991fa71aa8675719fe23943c905eb6478f8 Mon Sep 17 00:00:00 2001 From: LTDCrissOK Date: Sat, 7 Nov 2026 15:01:32 -0300 Subject: [PATCH 12/16] Update gs_backend.h --- ps2xRuntime/include/runtime/gs/gs_backend.h | 31 +++++++-------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/ps2xRuntime/include/runtime/gs/gs_backend.h b/ps2xRuntime/include/runtime/gs/gs_backend.h index 3f00d01b5f..753bd6869d 100644 --- a/ps2xRuntime/include/runtime/gs/gs_backend.h +++ b/ps2xRuntime/include/runtime/gs/gs_backend.h @@ -1,34 +1,23 @@ #pragma once -#include "runtime/gs/gs_types.h" - #include -#include +#include -class GSRasterBackend +class GSBackend { public: - virtual ~GSRasterBackend() = default; - - virtual void Initialize(uint8_t *vram, uint32_t vramSize) = 0; - virtual void Reset() = 0; + virtual ~GSBackend() = default; - virtual void Submit(const GSPrimitiveBatch &batch) = 0; - virtual void LoadClut(const GSTex0Reg &tex0, const GSTexClutReg &texclut) = 0; + virtual bool Initialize( + uint8_t* vram, + uint32_t vramSize + ) = 0; - virtual void BeginTransfer(const GSTransferCommand &command) = 0; - virtual void UploadImage(const uint8_t *data, uint32_t sizeBytes) = 0; + virtual void Reset() = 0; virtual void Flush() = 0; - virtual void TextureFlush() = 0; - virtual void Sync(GSSyncReason reason) = 0; - virtual PresentationFrame Present(const GSPresentationRequest &request) = 0; - virtual bool ClearFramebuffer(const GSContext &context, uint32_t rgba) = 0; - virtual uint32_t ConsumeLocalToHostBytes(uint8_t *dst, uint32_t maxBytes) = 0; + virtual void Sync() = 0; - virtual uint32_t ReadVram(uint32_t psm, uint32_t base, uint32_t bw, uint32_t x, uint32_t y) const = 0; - virtual void WriteVram(uint32_t psm, uint32_t base, uint32_t bw, uint32_t x, uint32_t y, uint32_t value) = 0; - virtual void SnapshotVram(std::vector &out) const = 0; - virtual GSTransferSnapshot GetTransferSnapshot() const = 0; + virtual bool IsAvailable() const = 0; }; From 5bdf7e8079129a55dd4993301e425d167dde44f4 Mon Sep 17 00:00:00 2001 From: LTDCrissOK Date: Sat, 7 Nov 2026 15:02:24 -0300 Subject: [PATCH 13/16] Add GSOpenGLBackend class for OpenGL rendering --- .../include/runtime/gs/gs_opengl_backend.h | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 ps2xRuntime/include/runtime/gs/gs_opengl_backend.h diff --git a/ps2xRuntime/include/runtime/gs/gs_opengl_backend.h b/ps2xRuntime/include/runtime/gs/gs_opengl_backend.h new file mode 100644 index 0000000000..04f1ca3d05 --- /dev/null +++ b/ps2xRuntime/include/runtime/gs/gs_opengl_backend.h @@ -0,0 +1,45 @@ +#pragma once + +#include "gs_backend.h" + +#ifdef __ANDROID__ +#include +#include +#endif + +class GSOpenGLBackend : public GSBackend +{ +public: + GSOpenGLBackend(); + + ~GSOpenGLBackend() override; + + bool Initialize( + uint8_t* vram, + uint32_t vramSize + ) override; + + void Reset() override; + + void Flush() override; + + void Sync() override; + + bool IsAvailable() const override; + +private: + bool InitializeShaders(); + bool InitializeBuffers(); + bool InitializeTextures(); + + GLuint m_program = 0; + GLuint m_vertexBuffer = 0; + GLuint m_vertexArray = 0; + GLuint m_framebuffer = 0; + GLuint m_texture = 0; + + uint8_t* m_vram = nullptr; + uint32_t m_vramSize = 0; + + bool m_initialized = false; +}; From 1d5f63832489e87e2b6d3248f400c8f468a0b022 Mon Sep 17 00:00:00 2001 From: LTDCrissOK Date: Sat, 7 Nov 2026 15:03:14 -0300 Subject: [PATCH 14/16] Implement GSOpenGLBackend class for OpenGL support --- .../include/runtime/gs/gs_opengl_backend.cpp | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 ps2xRuntime/include/runtime/gs/gs_opengl_backend.cpp diff --git a/ps2xRuntime/include/runtime/gs/gs_opengl_backend.cpp b/ps2xRuntime/include/runtime/gs/gs_opengl_backend.cpp new file mode 100644 index 0000000000..416cb061e5 --- /dev/null +++ b/ps2xRuntime/include/runtime/gs/gs_opengl_backend.cpp @@ -0,0 +1,50 @@ +#include "gs_opengl_backend.h" + +#include + +GSOpenGLBackend::GSOpenGLBackend() = default; + +GSOpenGLBackend::~GSOpenGLBackend() +{ + Reset(); +} + +bool GSOpenGLBackend::Initialize( + uint8_t* vram, + uint32_t vramSize) +{ +#ifdef __ANDROID__ + const char* version = + reinterpret_cast( + glGetString(GL_VERSION) + ); + + if (!version) + return false; + + m_vram = vram; + m_vramSize = vramSize; + + GLint major = 0; + GLint minor = 0; + + glGetIntegerv(GL_MAJOR_VERSION, &major); + glGetIntegerv(GL_MINOR_VERSION, &minor); + + if (major < 3 || (major == 3 && minor < 2)) + return false; + + m_initialized = InitializeShaders() + && InitializeBuffers() + && InitializeTextures(); + + return m_initialized; +#else + return false; +#endif +} + +bool GSOpenGLBackend::IsAvailable() const +{ + return m_initialized; +} From 8574dd05475c70d73cf859660d835ca92d2ab9c8 Mon Sep 17 00:00:00 2001 From: LTDCrissOK Date: Sat, 7 Nov 2026 15:06:18 -0300 Subject: [PATCH 15/16] Add setRasterBackend function to gs_opengl_backend --- ps2xRuntime/src/lib/gs/gs_opengl_backend.cpp | 1 + 1 file changed, 1 insertion(+) create mode 100644 ps2xRuntime/src/lib/gs/gs_opengl_backend.cpp diff --git a/ps2xRuntime/src/lib/gs/gs_opengl_backend.cpp b/ps2xRuntime/src/lib/gs/gs_opengl_backend.cpp new file mode 100644 index 0000000000..8667380f77 --- /dev/null +++ b/ps2xRuntime/src/lib/gs/gs_opengl_backend.cpp @@ -0,0 +1 @@ +void GS::setRasterBackend(std::unique_ptr backend) From 55e6bab2728efc9e3c25f297fff32696957c46a8 Mon Sep 17 00:00:00 2001 From: LTDCrissOK Date: Sat, 7 Nov 2026 15:33:31 -0300 Subject: [PATCH 16/16] Add files via upload --- .../include/runtime/gs/gs_opengl_backend.h | 58 +++++++++---------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/ps2xRuntime/include/runtime/gs/gs_opengl_backend.h b/ps2xRuntime/include/runtime/gs/gs_opengl_backend.h index 04f1ca3d05..aa543b9eba 100644 --- a/ps2xRuntime/include/runtime/gs/gs_opengl_backend.h +++ b/ps2xRuntime/include/runtime/gs/gs_opengl_backend.h @@ -1,45 +1,43 @@ #pragma once -#include "gs_backend.h" +#include "runtime/gs/gs_backend.h" +#include -#ifdef __ANDROID__ -#include -#include -#endif +class GSCpuBackend; -class GSOpenGLBackend : public GSBackend +// Fase de integración: comprueba que existe un contexto OpenGL ES 3.2 +// y conserva el comportamiento GS actual delegando las operaciones al backend CPU. +// IMPORTANTE: todavía no rasteriza las primitivas GS en la GPU. +class GSOpenGLBackend final : public GSRasterBackend { public: GSOpenGLBackend(); - ~GSOpenGLBackend() override; - bool Initialize( - uint8_t* vram, - uint32_t vramSize - ) override; + bool IsOpenGLES32Available() const; + const char* GetOpenGLVersion() const; + void Initialize(uint8_t* vram, uint32_t vramSize) override; void Reset() override; - + void Submit(const GSPrimitiveBatch& batch) override; + void LoadClut(const GSTex0Reg& tex0, const GSTexClutReg& texclut) override; + void BeginTransfer(const GSTransferCommand& command) override; + void UploadImage(const uint8_t* data, uint32_t sizeBytes) override; void Flush() override; - - void Sync() override; - - bool IsAvailable() const override; + void TextureFlush() override; + void Sync(GSSyncReason reason) override; + PresentationFrame Present(const GSPresentationRequest& request) override; + bool ClearFramebuffer(const GSContext& context, uint32_t rgba) override; + uint32_t ConsumeLocalToHostBytes(uint8_t* dst, uint32_t maxBytes) override; + uint32_t ReadVram(uint32_t psm, uint32_t base, uint32_t bw, + uint32_t x, uint32_t y) const override; + void WriteVram(uint32_t psm, uint32_t base, uint32_t bw, + uint32_t x, uint32_t y, uint32_t value) override; + void SnapshotVram(std::vector& out) const override; + GSTransferSnapshot GetTransferSnapshot() const override; private: - bool InitializeShaders(); - bool InitializeBuffers(); - bool InitializeTextures(); - - GLuint m_program = 0; - GLuint m_vertexBuffer = 0; - GLuint m_vertexArray = 0; - GLuint m_framebuffer = 0; - GLuint m_texture = 0; - - uint8_t* m_vram = nullptr; - uint32_t m_vramSize = 0; - - bool m_initialized = false; + std::unique_ptr m_cpu; + bool m_gles32Available = false; + char m_glVersion[256]{}; };