Skip to content

Latest commit

 

History

History
89 lines (69 loc) · 7.78 KB

MACOS_PORT.md

File metadata and controls

89 lines (69 loc) · 7.78 KB

Apple Silicon (macOS) port: fork status

This branch combines current main with the community macOS port from mugurc/AnyPS5 macos-port, which upstream is reviewing in pieces (#927, #1597, #1499, #1686). It adds the fixes needed to build and run on top of current main.

Guest code stays x86-64 and runs under Rosetta 2. The relinker writes Mach-O (--macos), the prx libraries build as x86-64 dylibs, and Vulkan runs through MoltenVK. Apple has announced that macOS 27 is the last release with full Rosetta support, so this route has a limited lifetime.

Build

git submodule update --init --recursive
cmake -S . -B build-mac -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES=x86_64 -DBUILD_TESTING=ON
ninja -C build-mac all libs

Requirements:

  • Xcode command-line tools
  • Rosetta 2
  • Ninja
  • the Vulkan SDK (universal loader and MoltenVK)
  • for the guest fixtures only: brew install llvm lld

Run

build-mac/core/relinker/relinker --macos --to-rosetta source/eboot.bin out/eboot
python3 tools/package_macos_app.py --relinked out --game source --libs build-mac/core/libs/libs --vulkan ~/VulkanSDK/<version>/macOS Title.app

Status

Measured on an M3 Pro with macOS 26.6, Vulkan SDK 1.4.363.0 and MoltenVK:

Check Result
Full build (all targets and 115 prx libraries) builds
macos_fixtures: argv, imports, TLS, C++ exceptions, C cleanup, threads, modules, TLS across modules, --to-intel 10/10 run under Rosetta
Breakout guest (video out, pad, audio out), relinked and packaged as .app runs at about 60 fps
ctest, full suite on MoltenVK 832 tests: 775 pass, 43 skip features the device lacks (64-bit atomics, Float64, mesh shaders), 14 fail
Dreaming Sarah (PPSA02929), relinked with --macos --to-rosetta, packaged as .app and opened from Finder runs the animated main menu at 60 fps (about 18 fps before the write watch)

The 14 tests that fail (macOS 26.1, MoltenVK 1.4.2):

  • Apple GPU or Metal limits (4): f32_dot2_rounding needs the Float64 capability, f32_constant_factor_tininess gets a product flushed to zero where RDNA2 keeps the smallest normal, float16_rounding gets a differently rounded v_mul_f16 product, and pixel_interlock needs fragment shader interlock, which SPIRV-Cross cannot write as MSL.
  • A product that should give -0.0 gives +0.0 (4): f32_denormal_flush, f32_output_modifier, packed_alu, sdwa_float_selectors. See TechnicalDebt.
  • Host platform (5): agc_thread_priority_refused_rt, agc_thread_priority_refused_high and agc_driver_pm4_priority_refused expect a refused priority request to throw, agc_shared_write cannot inspect mapping lifetimes, and check_skips passes from the repository root but not from the working directory ctest gives it.
  • patched_libraries: loads all patched libraries and exceeds the 300 s test timeout only when the whole suite runs in parallel after a rebuild; it passes alone.

macos_fixtures passes once on a fresh build and times out in every later run: its argv fixture, which ends with int3, stays exiting in the kernel under Rosetta until a reboot. It was left out of the run above.

Changes on top of the merged port:

  • GuestMemory.cpp and DynamicLoader.cpp compile on macOS. main had added Linux-only code to both after the port was based.
  • When the device has no robustness2.nullDescriptor (MoltenVK), the empty slots of the typed image heaps bind zeroed placeholder images. Before this, every draw and dispatch failed on macOS.
  • Shader stages without host subgroups (MoltenVK: vertex and tessellation evaluation) run each invocation as a one-lane wave instead of failing validation.
  • Guest thread destructors register through _tlv_atexit. libSystem's __cxa_thread_atexit returns void, and reading its result as an int freed a live registration twice.
  • dlsym's default scope finds the main program through dyld.
  • Guest mprotect rounds to 16 KiB pages relative to the guest image's load bias. dyld only aligns a slide to 4 KiB, so rounding the absolute address protected the wrong pages (SIGBUS in Dreaming Sarah). Guest segments get maxprot rwx, and the image's no-access padding segments are registered.
  • Without storage MSAA, multisampled image dimensions are emitted single-sample. This matches the single-sample images the driver creates; Dreaming Sarah's first shader clears an MSAA surface with image_store.
  • Merged upstream #1653: no fragment barycentric on MoltenVK. SPIRV-Cross cannot write PerVertexKHR to MSL.
  • MoltenVK has no VK_EXT_depth_range_unrestricted. A draw whose viewport depth leaves [0, 1] is drawn with [0, 1] when the depth cannot be observed (Unity's stencil clears in Kitaria Fables); see TechnicalDebt. Depth-tested draws with such a range still fail, with the range in the message.
  • sceKernelGetModuleInfoForUnwind and sceSysmoduleGetModuleInfoForUnwind report the eh_frame tables of the relinked images, so a title's bundled libc.prx can catch C++ exceptions (Kitaria Fables' managed exceptions).
  • macOS has a guest write watch. Without it the driver compared guest memory before every draw, a third of Dreaming Sarah's frame time; it write-protects watched pages and records the first write to each in a fault handler. See TechnicalDebt for its costs and limits.
  • Packaged titles start from Finder: Contents/MacOS/app0 links to the game folder, where the executable loads its guest modules, and the soft open-file limit (256 for apps Finder opens) is raised when direct memory runs out of descriptors.
  • MoltenVK's Metal argument buffers are enabled through VK_EXT_layer_settings. Together with upstream #1580 (merged here), they lift the 31 storage buffers per stage limit.
  • Test fixes for macOS:
    • case-insensitive APFS in guest_path_case
    • the portability loader in oversized_descriptor_sets
    • the fixtures' temporary directory, about 720 MiB per run, is now cleaned up
    • ProcessParametersFixture gives a test executable process parameters through its own __APS5DATA,__meta section, laid out as the relinker writes it, so dl_iterate_phdr lists them as the main image's PT_SCE_PROCPARAM
  • The AGC driver creates the GPU mutex before itself. Its destructor at exit locks that mutex, and macOS's pthread_mutex_lock fails on a destroyed one.
  • libSceJson2 converts a number that from_chars reports out of range with strtod_l in the C locale. libc++'s stream extraction fails on every ERANGE, so an underflow to a denormal or zero was rejected as an overflow.
  • When the AGC driver memory cannot be mapped at 0xF_E0000000, the libSceAgc static buffer functions throw.
  • A positioned write on an O_APPEND regular file (pwrite, pwritev, AIO writes) appends and leaves the offset unchanged, as on FreeBSD; XNU's pwrite writes at the offset. See libkernel debt.
  • sceKernelGetModuleList and the module info calls also list the .prx libraries built from this repository, reporting their Mach-O segments, as Linux lists them through dl_iterate_phdr.
  • dlerror keeps the whole message. dyld's errors list every path tried and were cut at 512 bytes, before the hint about the relinker's --module-dir option.

Feasibility probes

tools/macos/spikes holds the probes used to choose this route:

  • gs TSD reads
  • AVX2, FMA and BMI2
  • JIT code
  • fs base: unavailable under Rosetta
  • the guest arena layout
  • MoltenVK features

Run them with run.sh [libMoltenVK.dylib].