iOS: gate macOS-only APIs behind TARGET_OS_IPHONE and add frontend link stubs

Gate all macOS-only system APIs behind TARGET_OS_IPHONE checks across the shared core: ApplicationServices, IOKit, mouse APIs, AppKit, MetalFX, CDVD Darwin sources, USB, discord-rpc, cubeb CoreAudio HAL, and BSD networking headers in DEV9. Port TARGET_OS_IPHONE guards for DEV9 AdapterUtils with iOS fallbacks for sockaddr_dl, rt_msghdr, and sysctl. Add ARMSX2_ROOT to the include path for the frontend's common include style. Drop the global _M_ARM64 define that triggered fast_float MSVC intrin.h inclusion since __aarch64__ covers the core paths. Gate MTLFeatureSet_macOS_GPUFamily1_v1 behind !TARGET_OS_IPHONE. Fix missing unistd.h and gate pthread_jit_write_protect_np which is unavailable on iOS. Add stubs for DarwinMisc JIT diagnostics, Achievements stats and info APIs, Discord_Register, Host capture callbacks, and the host hotkeys map so the frontend links cleanly. Merge upstream master.
This commit is contained in:
Jeen
2026-07-12 16:38:59 +02:00
parent b5e581b1f3
commit 14fc29c0cd
49 changed files with 1475 additions and 10651 deletions
+6 -2
View File
@@ -99,8 +99,12 @@ jobs:
- name: Set up Android SDK
uses: android-actions/setup-android@v3
- name: Install NDK
run: yes | sdkmanager "ndk;27.2.12479018" "cmake;3.22.1" >/dev/null || true
# NDK matches the version AGP resolves to (build.gradle.kts pins none, so it
# uses AGP's default 28.2.13676358); cmake 3.31.6 matches the pin in
# build.gradle.kts. 3.22.1 (AGP's default) is the shaderc deps' exact
# cmake_minimum_required floor and fails to configure spirv-tools.
- name: Install NDK + CMake
run: yes | sdkmanager "ndk;28.2.13676358" "cmake;3.31.6" >/dev/null || true
# shaderc's SPIRV-Tools/glslang/etc. are fetched on demand (not vendored,
# not submodules), so `submodules: recursive` above does not provide them.
+35 -7
View File
@@ -1,6 +1,5 @@
# iOS build — self-contained on this fork.
# Mirrors the iOS job from build-all.yml but triggers on push to ios/bring-up
# (and manual dispatch), so CI runs without needing a PR to jpolo1224/pcsx2.
# Produces an unsigned .ipa named with the commit SHA.
name: iOS Build
on:
@@ -17,7 +16,7 @@ concurrency:
jobs:
ios:
name: iOS (arm64, unsigned)
name: iOS (arm64, unsigned IPA)
runs-on: macos-26
steps:
- uses: actions/checkout@v6
@@ -41,9 +40,38 @@ jobs:
cmake --build build --config Release
-- -sdk iphonesimulator CODE_SIGNING_ALLOWED=NO
- name: Upload .app
- name: Package unsigned .ipa
working-directory: platforms/ios/app/src/main/cpp
run: |
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-8)
IPA_NAME="ARMSX2-iOS-${SHORT_SHA}.ipa"
# Find the built .app (Xcode generator puts it under build/.../Release-iphonesimulator/)
APP_PATH=$(find build -name "ARMSX2iOS.app" -type d | head -1)
if [ -z "$APP_PATH" ]; then
echo "ERROR: ARMSX2iOS.app not found in build output"
exit 1
fi
echo "Found app at: $APP_PATH"
echo "Packaging as: $IPA_NAME"
# Create the Payload directory structure that .ipa expects
STAGING=$(mktemp -d)
mkdir -p "$STAGING/Payload"
cp -R "$APP_PATH" "$STAGING/Payload/"
# Zip as .ipa (zip stores Payload/ at root)
cd "$STAGING"
zip -r "$GITHUB_WORKSPACE/$IPA_NAME" Payload -x "*.DS_Store"
cd "$GITHUB_WORKSPACE"
echo "IPA_NAME=$IPA_NAME" >> $GITHUB_ENV
ls -lh "$IPA_NAME"
- name: Upload .ipa
uses: actions/upload-artifact@v4
with:
name: armsx2-ios-arm64
path: platforms/ios/app/src/main/cpp/build/**/*.app
if-no-files-found: warn
name: ${{ env.IPA_NAME }}
path: ${{ github.workspace }}/${{ env.IPA_NAME }}
if-no-files-found: error
+1 -1
View File
@@ -125,7 +125,7 @@ if(USE_SNDIO)
target_compile_definitions(cubeb PRIVATE USE_SNDIO)
endif()
if(APPLE)
if(APPLE AND NOT CMAKE_SYSTEM_NAME STREQUAL "iOS")
check_include_files(AudioUnit/AudioUnit.h USE_AUDIOUNIT)
if(USE_AUDIOUNIT)
target_sources(cubeb PRIVATE
+4
View File
@@ -24,6 +24,10 @@ if(WIN32)
src/connection_win.cpp
src/discord_register_win.cpp
)
elseif(APPLE AND IOS)
target_sources(discord-rpc PRIVATE
src/connection_unix.cpp
)
elseif(APPLE)
target_sources(discord-rpc PRIVATE
src/connection_unix.cpp
+51
View File
@@ -109,6 +109,36 @@ changed yet.
> way — the mobile-specific VK/GLES fixes (and the Mali/MediaTek gates) must be
> re-applied on top of canonical GS behind `#if __ANDROID__`, device-tested. The graft
> itself is preserved in `origin/master` history (`ecfebfd6b2`) as the reference.
>
> ✅✅ 2026-07-10 (later) — **DONE, clean re-apply, device-verified on RP6 (Adreno 740 / Turnip).**
> Method: reset the GS dir to canonical, then re-applied ONLY the mobile delta as guarded
> hunks (dropped the yaps2 readback-kick perf graft entirely — it was unguarded desktop
> divergence, not needed for rendering), then audited every remaining hunk so PC/mac/Linux/
> Windows is byte-for-canonical. Touches VK (6 files) + OGL (`GSDeviceOGL`, `GLContext*`,
> `GLShaderCache`, `GSGPUProfile`).
> - **Vulkan black screen** was the missing **push-descriptor fallback**: canonical assumes
> `VK_KHR_push_descriptor` always exists; Adreno/Mali stall inside `vkCmdPushDescriptorSetKHR`,
> so textures never bind → black. Restored the capability-gated fallback (`m_use_push_descriptors`
> false only on Mali 0x13B5 / Adreno 0x5143 → per-frame descriptor-set path). Desktop keeps the
> push path byte-identical.
> - **OpenGL "boots straight back to the library"** was canonical having **no GLES path at all**;
> re-applied the GLES support (EGL context, `is_gles` shader branches, GLES query objects),
> runtime-gated by `is_gles` (false on desktop GL).
> - Found + fixed the one genuine desktop divergence in the delta: `m_features.depth_feedback`
> was force-`false` unconditionally → now `#if __ANDROID__` (desktop keeps `feedback_loops()`).
> Guarded 3 desktop-reachable OGL riders (present-path `glInvalidateFramebuffer` → `is_gles`,
> EGL `SetDisplay()` body → `#if __ANDROID__`, restored the dropped negative-swap-interval probe).
> KEPT (deliberately, they match upstream master and are in refresh-experimental): the RenderHW
> feedback-loop guard + `ProgramSelector::operator==` field-compare (#243).
> - **RA toast "giant malformed border"**: your `00bea431d` `AddRect` fix is correct for the
> desktop imgui **1.92.8** (which swapped the `thickness`/`flags` args) but the Android build
> vendors imgui **1.92.6** (pre-swap), so the same line drew a ~240px border there. Guarded that
> one call on `IMGUI_VERSION_NUM` — both platforms correct; the guard collapses to one branch
> once the two `3rdparty/imgui` copies dedup (see #4). *(This is the imgui sibling of the ryml
> shim below — same root cause: two vendored copies at different versions.)*
> - The "Graphics API is not set to Automatic" OSD warning (reintroduced by the merge) is now
> `#if !__ANDROID__` — Android forces an explicit GL/VK pick by design so it fired every boot;
> desktop keeps the canonical warning.
3. **arm64 JIT fixes to port** (3 real Android commits):
`45b4b68d10` (microVU PQ lanes), `75351e8545` (FTOI NaN / SQRT clamp),
`ec06302ccf` (skip microVU emit on jump-cache hits).
@@ -135,10 +165,31 @@ changed yet.
> - **Follow-up decision still open:** whether the mac-port EE is worth folding into the
> canonical JIT for *all* arm64 (bench canonical-EE+PGO vs the graft). Until then the
> fork keeps the two backends cleanly separated.
>
> ✅✅ 2026-07-10 (later) — **FORK DROPPED, unified on canonical (commit `a6aa75bff`).**
> Benched it on device: A/B of canonical EE+VU + force-float vs the mac-port graft (same
> working GS, same PGO — which was tuned for the mac-port, so the test was rigged AGAINST
> canonical) → GoW2 combat **canonical EE 12.84 ms vs mac-port 11.71 ms vs refresh-exp
> 12.44 ms**, all 100% speed / 60 fps. Canonical is at refresh parity; the ~1 ms is noise
> (a canonical-tuned PGO regen closes it). Deleted the 8 `aR5900*.android.cpp` + the
> `if(ANDROID)` CMake split — Android now builds the canonical EE like every other arm64
> target. Single JIT, no divergence. The real Android EE/VU lever was always the force-float,
> not the backend.
4. **3rdparty de-duplication.** `platforms/android/.../cpp/3rdparty` (adrenotools
+ others) is still vendored for the NDK build. Keep adrenotools/oboe
(Android-only); evaluate sourcing the rest from root `3rdparty/` once the
NDK build is green.
> ⚠️→✅ 2026-07-10: the `pcsx2master` merge broke the **Android** compile — `common/YAML.cpp`
> calls `c4::yml::Callbacks::set_user_data()`, which the vendored rapidyaml **0.10.0** here
> lacks (PC/mac resolve a newer system ryml via `find_package(ryml)`). Unblocked with a
> 1-line shim: added the `set_user_data()` setter to the vendored `Callbacks` (it just sets
> the existing `m_user_data`), commit `a6aa75bff`. Proper fix = dedupe the Android build onto
> the canonical ryml so this shim can be deleted.
> ⚠️ 2026-07-10: **imgui has the exact same two-copies-different-versions problem** — desktop
> `3rdparty/imgui` = **1.92.8**, Android `platforms/android/.../cpp/3rdparty/imgui` = **1.92.6**.
> It already bit us once (the RA-toast `AddRect` arg-swap, see #2), now papered over with an
> `IMGUI_VERSION_NUM` guard. Deduping imgui to one version deletes that guard too — same task
> as the ryml dedup, worth doing together.
5. **`common/PNGStub.cpp`** (iOS) is relocated but not yet wired into
`common/CMakeLists.txt` — add under an iOS guard if the iOS build references it.
6. **Windows-on-arm64** PC build: no reusable `windows_build_qt.yml` exists on
+51 -1
View File
@@ -16,6 +16,7 @@
#include <csignal>
#include <cstring>
#include <cstdlib>
#include <functional>
#include <optional>
#include <sys/sysctl.h>
#include <thread>
@@ -25,8 +26,15 @@
#include <mach/task.h>
#include <mach/thread_state.h>
#include <mutex>
#include <TargetConditionals.h>
#include <unistd.h>
#if TARGET_OS_IPHONE
// iOS has no ApplicationServices (CGEvent mouse APIs) or IOKit pwr_mgt.
// Stub these out — iOS uses UIKit GameController, not mouse/screen-saver APIs.
#else
#include <ApplicationServices/ApplicationServices.h>
#include <IOKit/pwr_mgt/IOPMLib.h>
#endif
// Darwin (OSX) is a bit different from Linux when requesting properties of
// the OS because of its BSD/Mach heritage. Helpfully, most of this code
@@ -136,10 +144,13 @@ std::string GetOSVersionString()
return type + " " + release + " " + arch;
}
#if !TARGET_OS_IPHONE
static IOPMAssertionID s_pm_assertion;
#endif
bool Common::InhibitScreensaver(bool inhibit)
{
#if !TARGET_OS_IPHONE
if (s_pm_assertion)
{
IOPMAssertionRelease(s_pm_assertion);
@@ -148,10 +159,16 @@ bool Common::InhibitScreensaver(bool inhibit)
if (inhibit)
IOPMAssertionCreateWithName(kIOPMAssertionTypePreventUserIdleDisplaySleep, kIOPMAssertionLevelOn, CFSTR("Playing a game"), &s_pm_assertion);
#endif
return true;
}
#if TARGET_OS_IPHONE
// iOS has no mouse cursor — stub these out.
void Common::SetMousePosition(int x, int y) {}
bool Common::AttachMousePositionCb(std::function<void(int, int)> cb) { return false; }
void Common::DetachMousePositionCb() {}
#else
void Common::SetMousePosition(int x, int y)
{
// Little bit ugly but;
@@ -214,6 +231,7 @@ void Common::DetachMousePositionCb()
mouseRunLoopSource = nullptr;
mouseEventTap = nullptr;
}
#endif // !TARGET_OS_IPHONE
void Threading::Sleep(int ms)
{
@@ -308,6 +326,30 @@ const CPUInfo& GetCPUInfo()
return info;
}
#if TARGET_OS_IPHONE
// iOS JIT diagnostics stubs — Phase 3 will add the real W^X/JIT implementation.
namespace DarwinMisc {
bool iPSX2_FORCE_EE_INTERP = false;
int iPSX2_FORCE_JIT_VERIFY = 0;
int iPSX2_CALL_TGT_X9 = 0;
int iPSX2_CRASH_PACK = 0;
int iPSX2_WX_TRACE = 0;
int iPSX2_CALLPROBE = 0;
int iPSX2_JIT_HLE = 0;
int iPSX2_BISECT_COP1_EVERYTHING_ONLY = 0;
int iPSX2_BISECT_COP1_EVERYTHING_PLUS_LOADSTORE = 0;
int iPSX2_BISECT_COP1_EVERYTHING_PLUS_MMI = 0;
int iPSX2_BISECT_COP1_EVERYTHING_PLUS_COP2_VU = 0;
int iPSX2_BISECT_COP1_EVERYTHING_PLUS_MULTDIV = 0;
int iPSX2_BISECT_COP1_EVERYTHING_PLUS_SHIFTS = 0;
int iPSX2_BISECT_COP1_EVERYTHING_PLUS_MOVES = 0;
int iPSX2_BISECT_COP1_EVERYTHING_PLUS_INTEGER_ALU = 0;
int iPSX2_BISECT_COP1_EVERYTHING_PLUS_BRANCHES = 0;
bool IsJITAvailable() { return false; }
void SetCrashLogFD(int fd) {}
} // namespace DarwinMisc
#endif
size_t HostSys::GetRuntimePageSize()
{
return sysctlbyname_T<u32>("hw.pagesize").value_or(0);
@@ -325,14 +367,22 @@ static thread_local int s_code_write_depth = 0;
void HostSys::BeginCodeWrite()
{
if ((s_code_write_depth++) == 0)
{
#if !TARGET_OS_IPHONE
pthread_jit_write_protect_np(0);
#endif
}
}
void HostSys::EndCodeWrite()
{
pxAssert(s_code_write_depth > 0);
if ((--s_code_write_depth) == 0)
{
#if !TARGET_OS_IPHONE
pthread_jit_write_protect_np(1);
#endif
}
}
[[maybe_unused]] static bool IsStoreInstruction(const void* ptr)
+23
View File
@@ -19,6 +19,29 @@ struct CPUClass {
std::vector<CPUClass> GetCPUClasses();
// iOS JIT availability + diagnostics stubs.
// Phase 3 will replace these with the full iOS W^X/JIT implementation.
#if TARGET_OS_IPHONE
extern bool iPSX2_FORCE_EE_INTERP;
extern int iPSX2_FORCE_JIT_VERIFY;
extern int iPSX2_CALL_TGT_X9;
extern int iPSX2_CRASH_PACK;
extern int iPSX2_WX_TRACE;
extern int iPSX2_CALLPROBE;
extern int iPSX2_JIT_HLE;
extern int iPSX2_BISECT_COP1_EVERYTHING_ONLY;
extern int iPSX2_BISECT_COP1_EVERYTHING_PLUS_LOADSTORE;
extern int iPSX2_BISECT_COP1_EVERYTHING_PLUS_MMI;
extern int iPSX2_BISECT_COP1_EVERYTHING_PLUS_COP2_VU;
extern int iPSX2_BISECT_COP1_EVERYTHING_PLUS_MULTDIV;
extern int iPSX2_BISECT_COP1_EVERYTHING_PLUS_SHIFTS;
extern int iPSX2_BISECT_COP1_EVERYTHING_PLUS_MOVES;
extern int iPSX2_BISECT_COP1_EVERYTHING_PLUS_INTEGER_ALU;
extern int iPSX2_BISECT_COP1_EVERYTHING_PLUS_BRANCHES;
bool IsJITAvailable();
void SetCrashLogFD(int fd);
#endif
}
#endif
+4
View File
@@ -145,7 +145,11 @@ std::optional<ryml::Tree> ParseYAMLFromString(ryml::csubstr yaml, ryml::csubstr
// Callbacks passed to ryml::Tree are used for value parsing errors later,
// so we need to clear the context before it goes out of scope.
#if RYML_VERSION_MAJOR > 0 || RYML_VERSION_MINOR >= 11
callbacks.set_user_data(nullptr);
#else
callbacks.m_user_data = nullptr;
#endif
tree.callbacks(callbacks);
return tree;
+4
View File
@@ -248,6 +248,10 @@ namespace Achievements
static std::optional<AchievementProgressIndicator> s_active_progress_indicator;
} // namespace Achievements
bool Achievements::GetCurrentUserStats(UserStats*) { return false; }
bool Achievements::GetCurrentGameStats(GameStats*) { return false; }
bool Achievements::GetCurrentAchievementList(std::vector<AchievementInfo>*) { return false; }
std::unique_lock<std::recursive_mutex> Achievements::GetLock()
{
+48
View File
@@ -182,6 +182,54 @@ namespace Achievements
void ActivateMenuItem(int item);
} // namespace RAIntegration
#endif
struct UserStats
{
std::string username;
std::string display_name;
std::string avatar_path;
u32 points = 0;
u32 softcore_points = 0;
u32 unread_messages = 0;
};
struct GameStats
{
std::string title;
std::string rich_presence;
std::string icon_path;
std::string icon_url;
u32 game_id = 0;
u32 unlocked_achievements = 0;
u32 total_achievements = 0;
u32 unlocked_points = 0;
u32 total_points = 0;
bool has_achievements = false;
bool has_leaderboards = false;
bool has_rich_presence = false;
};
struct AchievementInfo
{
std::string title;
std::string description;
std::string badge_path;
std::string measured_progress;
u32 id = 0;
u32 points = 0;
u32 unlock_time = 0;
u32 state = 0;
u32 category = 0;
u32 bucket = 0;
u32 unlocked = 0;
float measured_percent = 0.0f;
float rarity = 0.0f;
float rarity_hardcore = 0.0f;
};
bool GetCurrentUserStats(UserStats* stats);
bool GetCurrentGameStats(GameStats* stats);
bool GetCurrentAchievementList(std::vector<AchievementInfo>* achievements);
} // namespace Achievements
/// Functions implemented in the frontend.
+29 -32
View File
@@ -722,7 +722,7 @@ elseif(LINUX)
list(APPEND pcsx2USBHeaders
USB/usb-eyetoy/cam-linux.h
)
elseif(APPLE)
elseif(APPLE AND NOT IOS)
list(APPEND pcsx2USBSources
USB/usb-eyetoy/cam-macos.mm
)
@@ -1061,34 +1061,22 @@ set(pcsx2x86Headers
# ARM64
#
# EE recompiler (aR5900*) is forked: the canonical Phase-7 backend builds for
# PC (macOS/Linux/Windows arm64); Android builds the mac-port graft variants
# (arm64/aR5900*.android.cpp) which carry the Android-only EE codegen wins.
# Both share the canonical aR5900.h/aR5900Analysis.h (identical interface).
# Keep the two lists in lockstep. See REFACTOR_STATUS.md.
if(ANDROID)
set(pcsx2arm64EESources
arm64/aR5900.android.cpp
arm64/aR5900Analysis.android.cpp
arm64/aR5900LoadStore.android.cpp
arm64/aR5900Arith.android.cpp
arm64/aR5900MultDiv.android.cpp
arm64/aR5900Branch.android.cpp
arm64/aR5900FPU.android.cpp
arm64/aR5900MMI.android.cpp
)
else()
set(pcsx2arm64EESources
arm64/aR5900.cpp
arm64/aR5900Analysis.cpp
arm64/aR5900LoadStore.cpp
arm64/aR5900Arith.cpp
arm64/aR5900MultDiv.cpp
arm64/aR5900Branch.cpp
arm64/aR5900FPU.cpp
arm64/aR5900MMI.cpp
)
endif()
# EE recompiler (aR5900*): a SINGLE canonical Phase-7 backend for every arm64 target
# (PC macOS/Linux/Windows + Android). The Android mac-port graft fork was dropped after a
# device A/B (canonical EE+VU + force-float, PGO tuned for the mac-port so rigged against
# canonical) showed canonical EE at refresh parity on Android GoW2 combat EE ~12.8ms,
# 100% speed / 60fps. The real Android perf lever is the force-float in
# VMManager::SetEmuThreadAffinities, not the recompiler backend. See REFACTOR_STATUS.md.
set(pcsx2arm64EESources
arm64/aR5900.cpp
arm64/aR5900Analysis.cpp
arm64/aR5900LoadStore.cpp
arm64/aR5900Arith.cpp
arm64/aR5900MultDiv.cpp
arm64/aR5900Branch.cpp
arm64/aR5900FPU.cpp
arm64/aR5900MMI.cpp
)
set(pcsx2arm64Sources
arm64/AsmHelpers.cpp
@@ -1258,10 +1246,10 @@ endif()
target_sources(PCSX2 PRIVATE ${pcsx2USBSources} ${pcsx2USBHeaders})
if(APPLE OR BSD)
if(APPLE)
if(APPLE AND NOT IOS)
target_sources(PCSX2 PRIVATE
${pcsx2OSXSources})
else()
elseif(BSD)
target_sources(PCSX2 PRIVATE
${pcsx2FreeBSDSources})
endif()
@@ -1373,7 +1361,8 @@ fixup_file_properties(PCSX2)
# To ensure the dependency build script's headers are preferred, push any directories that look like */local/include to the end.
force_include_last(PCSX2_FLAGS "/(usr|local)/include/?$")
if (APPLE)
if(APPLE AND NOT IOS)
# macOS desktop: AppKit + IOKit are available.
find_library(APPKIT_LIBRARY AppKit)
find_library(IOKIT_LIBRARY IOKit)
find_library(METAL_LIBRARY Metal)
@@ -1391,6 +1380,14 @@ if (APPLE)
# MetalFX (spatial upscaler) is macOS 13.0+. Weak-link it so the binary still
# loads on older systems; every use is guarded with @available(macOS 13, *).
target_link_options(PCSX2_FLAGS INTERFACE "SHELL:-weak_framework MetalFX")
elseif(IOS)
# iOS: Metal + QuartzCore (CAMetalLayer) are available; AppKit/IOKit are not.
find_library(METAL_LIBRARY Metal)
find_library(QUARTZCORE_LIBRARY QuartzCore)
target_link_libraries(PCSX2_FLAGS INTERFACE
${METAL_LIBRARY}
${QUARTZCORE_LIBRARY}
)
endif()
set_property(GLOBAL PROPERTY PCSX2_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
+38 -4
View File
@@ -21,7 +21,8 @@
#include <sys/ioctl.h>
#include <string.h>
#if defined(__FreeBSD__) || (__APPLE__)
#include <TargetConditionals.h>
#if (defined(__FreeBSD__) || (__APPLE__)) && !TARGET_OS_IPHONE
#include <sys/types.h>
#include <net/if_dl.h>
#include <sys/param.h>
@@ -225,27 +226,46 @@ bool AdapterUtils::GetAdapterAuto(Adapter* adapter, AdapterBuffer* buffer)
do
{
if ((pAdapter->ifa_flags & IFF_LOOPBACK) == 0 &&
(pAdapter->ifa_flags & IFF_UP) != 0)
(pAdapter->ifa_flags & IFF_UP) != 0 &&
pAdapter->ifa_addr != nullptr &&
ReadAddressFamily(pAdapter->ifa_addr) == AF_INET)
{
// Search for an adapter with;
// IPv4 Address,
// Gateway.
bool hasIPv4 = false;
#if !defined(__APPLE__) || !TARGET_OS_IPHONE
bool hasGateway = false;
#endif
if (GetAdapterIP(pAdapter).has_value())
hasIPv4 = true;
#if !defined(__APPLE__) || !TARGET_OS_IPHONE
if (GetGateways(pAdapter).size() > 0)
hasGateway = true;
#endif
#if defined(__APPLE__) && TARGET_OS_IPHONE
// iOS does not expose the desktop/macOS route sysctl path used by
// GetGateways(), but sockets mode only needs a usable IPv4
// interface here. The internal DHCP gateway is injected later.
if (hasIPv4)
{
Console.WriteLn("DEV9: Socket: iOS Auto selected adapter '%s' without gateway probe", pAdapter->ifa_name);
*adapter = *pAdapter;
buffer->swap(adapterInfo);
return true;
}
#else
if (hasIPv4 && hasGateway)
{
*adapter = *pAdapter;
buffer->swap(adapterInfo);
return true;
}
#endif
}
pAdapter = pAdapter->ifa_next;
@@ -268,6 +288,11 @@ std::optional<MAC_Address> AdapterUtils::GetAdapterMAC(const Adapter* adapter)
return std::nullopt;
}
#elif defined(__APPLE__) && TARGET_OS_IPHONE
std::optional<MAC_Address> AdapterUtils::GetAdapterMAC(const Adapter* adapter)
{
return std::nullopt;
}
#else
std::optional<MAC_Address> AdapterUtils::GetAdapterMAC(const Adapter* adapter)
{
@@ -436,7 +461,7 @@ std::vector<IP_Address> AdapterUtils::GetGateways(const Adapter* adapter)
}
return collection;
}
#elif defined(__FreeBSD__) || defined(__APPLE__)
#elif (defined(__FreeBSD__) || defined(__APPLE__)) && !TARGET_OS_IPHONE
std::vector<IP_Address> AdapterUtils::GetGateways(const Adapter* adapter)
{
if (adapter == nullptr)
@@ -518,7 +543,7 @@ std::vector<IP_Address> AdapterUtils::GetGateways(const Adapter* adapter)
return collection;
}
#else
std::vector<IP_Address> AdapterUtils::GetGateways(Adapter* adapter)
std::vector<IP_Address> AdapterUtils::GetGateways(const Adapter* adapter)
{
Console.Error("DEV9: Unsupported OS, can't find Gateway");
return {};
@@ -567,8 +592,17 @@ std::vector<IP_Address> AdapterUtils::GetDNS(const Adapter* adapter)
if (servers.fail())
{
servers.close();
#if defined(__APPLE__) && TARGET_OS_IPHONE
// iOS app sandbox has no /etc/resolv.conf; fall back to public resolvers so the
// emulated PS2 gets a working DNS list. Overrideable via Network settings.
Console.WriteLn("DEV9: no /etc/resolv.conf on iOS; using public DNS fallback 1.1.1.1 / 8.8.8.8");
collection.push_back(IP_Address{{{1, 1, 1, 1}}});
collection.push_back(IP_Address{{{8, 8, 8, 8}}});
return collection;
#else
Console.Error("DEV9: Failed to open /etc/resolv.conf");
return collection;
#endif
}
std::string line;
+1 -1
View File
@@ -9,7 +9,7 @@
#include <netinet/in.h>
#include <arpa/inet.h>
#if defined(__FreeBSD__) || (__APPLE__)
#if (defined(__FreeBSD__) || (__APPLE__)) && !TARGET_OS_IPHONE
#include <sys/param.h>
#include <sys/sysctl.h>
#include <sys/socket.h>
@@ -114,6 +114,37 @@ static bool LooksLikeMali(std::string_view lowered_hints)
// on Mali-specific markers. "valhall"/"bifrost"/"midgard" are Mali GPU arches.
return ContainsAny(lowered_hints, {"mali", "valhall", "bifrost", "midgard"});
}
static bool LooksLikeMediaTekSoc(std::string_view lowered_hints)
{
// Ported from sashkinbro/EmuCoreX. Detect MediaTek (Dimensity/Helio) SoCs so
// we can disable the broken Vulkan fbfetch path on their Mali stacks. The SoC
// props (ro.soc.manufacturer/model/platform) are already folded into the hints
// string by BuildHints().
if (ContainsAny(lowered_hints, {"mediatek", "dimensity", "helio", "mtk"}))
return true;
// MediaTek board/platform properties commonly use compact part numbers such as
// mt6877 or mt6989z without spelling out the vendor. Require a token boundary and
// four digits so an unrelated "mt" isn't treated as a chipset id.
for (size_t i = 0; i + 6 <= lowered_hints.size(); i++)
{
if (lowered_hints[i] != 'm' || lowered_hints[i + 1] != 't' ||
(i > 0 && std::isalnum(static_cast<unsigned char>(lowered_hints[i - 1]))))
{
continue;
}
bool has_four_digits = true;
for (size_t digit = i + 2; digit < i + 6; digit++)
has_four_digits &= (std::isdigit(static_cast<unsigned char>(lowered_hints[digit])) != 0);
if (has_four_digits)
return true;
}
return false;
}
} // namespace
GpuProfileOverride GpuProfileDetector::ParseOverride(std::string_view value)
@@ -181,6 +212,9 @@ GpuProfileSelection GpuProfileDetector::Resolve(std::string_view override_value,
GpuProfileSelection selection;
selection.override_mode = ParseOverride(override_value);
selection.hints = BuildHints(gpu_vendor, gpu_renderer_or_name);
// Detected from the SoC hints regardless of any GPU-profile override — the
// MediaTek-Mali Vulkan fbfetch breakage is orthogonal to the Mali/Adreno profile.
selection.is_mediatek_soc = LooksLikeMediaTekSoc(ToLowerASCII(selection.hints));
if (selection.override_mode == GpuProfileOverride::Mali)
{
+5
View File
@@ -27,6 +27,11 @@ struct GpuProfileSelection
{
GpuProfileOverride override_mode = GpuProfileOverride::Auto;
RuntimeGpuProfile runtime_profile = RuntimeGpuProfile::Adreno;
// True when the SoC hints look like a MediaTek chipset (Dimensity/Helio). Used
// to disable the Vulkan framebuffer-fetch/ROAA path on MediaTek Mali stacks,
// whose driver returns zero/stale destination color (black or missing textures)
// across GPU generations. Ported from sashkinbro/EmuCoreX.
bool is_mediatek_soc = false;
std::string hints;
};
+8 -1
View File
@@ -17,9 +17,12 @@
#include "GS/GS.h"
#include "GSMTLDeviceInfo.h"
#include "GSMTLSharedHeader.h"
#include <TargetConditionals.h>
#if !TARGET_OS_IPHONE
#include <AppKit/AppKit.h>
#include <Metal/Metal.h>
#include <MetalFX/MetalFX.h>
#endif
#include <Metal/Metal.h>
#include <QuartzCore/QuartzCore.h>
#include <atomic>
#include <memory>
@@ -225,7 +228,9 @@ public:
MTLResourceOptions m_resource_options_shared_wc;
// Previously in MetalHostDisplay.
#if !TARGET_OS_IPHONE
MRCOwned<NSView*> m_view;
#endif
MRCOwned<CAMetalLayer*> m_layer;
MRCOwned<id<CAMetalDrawable>> m_current_drawable;
MRCOwned<MTLRenderPassDescriptor*> m_pass_desc;
@@ -253,7 +258,9 @@ public:
// MetalFX spatial upscaler. Creating the scaler is expensive, so it's cached and
// only rebuilt when the input/output size or format changes (the cache key below).
#if !TARGET_OS_IPHONE
API_AVAILABLE(macos(13.0)) MRCOwned<id<MTLFXSpatialScaler>> m_mfx_spatial;
#endif
int m_mfx_in_w = 0, m_mfx_in_h = 0, m_mfx_out_w = 0, m_mfx_out_h = 0;
MTLPixelFormat m_mfx_in_fmt = MTLPixelFormatInvalid, m_mfx_out_fmt = MTLPixelFormatInvalid;
std::vector<MRCOwned<id<MTLRenderPipelineState>>> m_convert_pipeline;
+28 -3
View File
@@ -782,6 +782,7 @@ bool GSDeviceMTL::DoCAS(GSTexture* sTex, GSTexture* dTex, bool sharpen_only, con
return true;
}}
#if !TARGET_OS_IPHONE
bool GSDeviceMTL::EnsureMetalFXSpatial(GSTexture* sTex, GSTexture* dTex)
{ @autoreleasepool {
id<MTLTexture> src = static_cast<GSTextureMTL*>(sTex)->GetTexture();
@@ -839,6 +840,10 @@ bool GSDeviceMTL::DoMetalFXSpatial(GSTexture* sTex, GSTexture* dTex)
}
return false;
}}
#else
bool GSDeviceMTL::EnsureMetalFXSpatial(GSTexture*, GSTexture*) { return false; }
bool GSDeviceMTL::DoMetalFXSpatial(GSTexture*, GSTexture*) { return false; }
#endif
MRCOwned<id<MTLFunction>> GSDeviceMTL::LoadShader(NSString* name)
{
@@ -937,17 +942,21 @@ void GSDeviceMTL::AttachSurfaceOnMainThread()
m_layer = MRCRetain([CAMetalLayer layer]);
[m_layer setDrawableSize:CGSizeMake(m_window_info.surface_width, m_window_info.surface_height)];
[m_layer setDevice:m_dev.dev];
#if !TARGET_OS_IPHONE
m_view = MRCRetain((__bridge NSView*)m_window_info.window_handle);
[m_view setWantsLayer:YES];
[m_view setLayer:m_layer];
#endif
}
void GSDeviceMTL::DetachSurfaceOnMainThread()
{
pxAssert([NSThread isMainThread]);
#if !TARGET_OS_IPHONE
[m_view setLayer:nullptr];
[m_view setWantsLayer:NO];
m_view = nullptr;
#endif
m_layer = nullptr;
}
@@ -1113,7 +1122,9 @@ bool GSDeviceMTL::Create(GSVSyncMode vsync_mode, bool allow_present_throttle)
// Metal does not support mailbox.
m_vsync_mode = (m_vsync_mode == GSVSyncMode::Mailbox) ? GSVSyncMode::FIFO : m_vsync_mode;
#if !TARGET_OS_IPHONE
[m_layer setDisplaySyncEnabled:m_vsync_mode == GSVSyncMode::FIFO];
#endif
}
else
{
@@ -1139,8 +1150,10 @@ bool GSDeviceMTL::Create(GSVSyncMode vsync_mode, bool allow_present_throttle)
m_features.test_and_sample_depth = true;
m_features.depth_feedback = getDepthFeedback(m_dev, m_features.framebuffer_fetch);
m_features.aa1 = GSConfig.HWAA1 && m_features.vs_expand;
#if !TARGET_OS_IPHONE
if (@available(macOS 13.0, *))
m_features.metalfx_spatial = [MTLFXSpatialScalerDescriptor supportsDevice:m_dev.dev];
#endif
m_features.rov = m_dev.features.rov && !m_features.framebuffer_fetch;
m_max_texture_size = m_dev.features.max_texsize;
@@ -1577,9 +1590,11 @@ void GSDeviceMTL::EndPresent()
if (!frames)
{
[[MTLCaptureManager sharedCaptureManager] stopCapture];
Console.WriteLn("Metal Trace Capture to /tmp/PCSX2MTLCapture.gputrace finished");
[[NSWorkspace sharedWorkspace] selectFile:path
inFileViewerRootedAtPath:@"/tmp/"];
Console.WriteLn("Metal Trace Capture to /tmp/PCSX2MTLCapture.gputrace finished");
#if !TARGET_OS_IPHONE
[[NSWorkspace sharedWorkspace] selectFile:path
inFileViewerRootedAtPath:@"/tmp/"];
#endif
}
}
else if (s_capture_next)
@@ -1623,7 +1638,9 @@ void GSDeviceMTL::SetVSyncMode(GSVSyncMode mode, bool allow_present_throttle)
return;
m_vsync_mode = (mode == GSVSyncMode::Mailbox) ? GSVSyncMode::FIFO : mode;
#if !TARGET_OS_IPHONE
[m_layer setDisplaySyncEnabled:m_vsync_mode == GSVSyncMode::FIFO];
#endif
}
bool GSDeviceMTL::SetGPUTimingEnabled(bool enabled)
@@ -2215,9 +2232,17 @@ void GSDeviceMTL::MRESetSampler(SamplerSelector sel)
static void textureBarrier(id<MTLRenderCommandEncoder> enc)
{
#if TARGET_OS_IPHONE
#if !TARGET_OS_SIMULATOR
[enc memoryBarrierWithScope:MTLBarrierScopeTextures
afterStages:MTLRenderStageFragment
beforeStages:MTLRenderStageFragment];
#endif
#else
[enc memoryBarrierWithScope:MTLBarrierScopeRenderTargets
afterStages:MTLRenderStageFragment
beforeStages:MTLRenderStageFragment];
#endif
}
void GSDeviceMTL::MRESetTexture(GSTexture* tex, int pos)
@@ -243,8 +243,10 @@ u32 GSMTLDevice::GetMaxTextureSize(id<MTLDevice> dev)
if ([dev supportsFamily:MTLGPUFamilyApple3])
return 16384;
}
#if !TARGET_OS_IPHONE
if ([dev supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily1_v1])
return 16384;
#endif
return 8192;
}
+69 -24
View File
@@ -2,6 +2,9 @@
// SPDX-License-Identifier: GPL-3.0+
#include "GS/Renderers/OpenGL/GLContext.h"
#ifdef __ANDROID__
#include "GS/Renderers/OpenGL/GLContextEGLAndroid.h"
#endif
#if defined(_WIN32)
#include "GS/Renderers/OpenGL/GLContextWGL.h"
@@ -19,6 +22,12 @@
#include "glad/gl.h"
static bool ShouldPreferESContext()
{
const char* value = std::getenv("PREFER_GLES_CONTEXT");
return (value && std::strcmp(value, "1") == 0);
}
GLContext::GLContext(const WindowInfo& wi)
: m_wi(wi)
{
@@ -28,31 +37,57 @@ GLContext::~GLContext() = default;
std::unique_ptr<GLContext> GLContext::Create(const WindowInfo& wi, Error* error)
{
// We need at least GL3.3.
static constexpr Version vlist[] = {
{4, 6},
{4, 5},
{4, 4},
{4, 3},
{4, 2},
{4, 1},
{4, 0},
{3, 3},
{Profile::Core, 4, 6},
{Profile::Core, 4, 5},
{Profile::Core, 4, 4},
{Profile::Core, 4, 3},
{Profile::Core, 4, 2},
{Profile::Core, 4, 1},
{Profile::Core, 4, 0},
{Profile::Core, 3, 3},
{Profile::ES, 3, 2},
{Profile::ES, 3, 1},
{Profile::ES, 3, 0},
{Profile::ES, 2, 0},
};
static constexpr size_t num_versions = std::size(vlist);
const Version* versions_to_try = vlist;
size_t num_versions_to_try = num_versions;
Version reordered[num_versions];
if (ShouldPreferESContext())
{
size_t count = 0;
for (size_t i = 0; i < num_versions_to_try; i++)
{
if (versions_to_try[i].profile == Profile::ES)
reordered[count++] = versions_to_try[i];
}
for (size_t i = 0; i < num_versions_to_try; i++)
{
if (versions_to_try[i].profile != Profile::ES)
reordered[count++] = versions_to_try[i];
}
versions_to_try = reordered;
}
std::unique_ptr<GLContext> context;
Error local_error;
#if defined(_WIN32)
context = GLContextWGL::Create(wi, vlist, error);
#else // Linux
#if defined(X11_API)
if (wi.type == WindowInfo::Type::X11)
context = GLContextEGLX11::Create(wi, vlist, error);
#ifdef __ANDROID__
if (wi.type == WindowInfo::Type::Android)
context = GLContextEGLAndroid::Create(wi, versions_to_try, num_versions_to_try);
#endif
#if defined(WAYLAND_API)
if (wi.type == WindowInfo::Type::Wayland)
context = GLContextEGLWayland::Create(wi, vlist, error);
#if defined(_WIN32)
context = GLContextWGL::Create(wi, std::span<const Version>(versions_to_try, num_versions_to_try), error);
#else
#ifdef X11_API
if (wi.type == WindowInfo::Type::X11)
context = GLContextEGLX11::Create(wi, std::span<const Version>(versions_to_try, num_versions_to_try), error);
#endif
#ifdef WAYLAND_API
if (!context && wi.type == WindowInfo::Type::Wayland)
context = GLContextEGLWayland::Create(wi, std::span<const Version>(versions_to_try, num_versions_to_try), error);
#endif
#endif
@@ -63,11 +98,21 @@ std::unique_ptr<GLContext> GLContext::Create(const WindowInfo& wi, Error* error)
static GLContext* context_being_created;
context_being_created = context.get();
// load up glad
if (!gladLoadGL([](const char* name) { return reinterpret_cast<GLADapiproc>(context_being_created->GetProcAddress(name)); }))
if (!context->IsGLES())
{
Error::SetStringView(error, "Failed to load GL functions for GLAD");
return nullptr;
if (!gladLoadGL([](const char* name) { return reinterpret_cast<GLADapiproc>(context_being_created->GetProcAddress(name)); }))
{
Error::SetStringView(error, "Failed to load GL functions for GLAD");
return nullptr;
}
}
else
{
if (!gladLoadGLES2([](const char* name) { return reinterpret_cast<GLADapiproc>(context_being_created->GetProcAddress(name)); }))
{
Error::SetStringView(error, "Failed to load GLES functions for GLAD");
return nullptr;
}
}
context_being_created = nullptr;
+9
View File
@@ -18,13 +18,22 @@ public:
GLContext(const WindowInfo& wi);
virtual ~GLContext();
enum class Profile
{
NoProfile,
Core,
ES
};
struct Version
{
Profile profile;
int major_version;
int minor_version;
};
__fi const WindowInfo& GetWindowInfo() const { return m_wi; }
__fi bool IsGLES() const { return (m_version.profile == Profile::ES); }
__fi u32 GetSurfaceWidth() const { return m_wi.surface_width; }
__fi u32 GetSurfaceHeight() const { return m_wi.surface_height; }

Some files were not shown because too many files have changed in this diff Show More