[macOS] Build infrastructure and portability fixes

Build system:
- Enable ObjC++, macOS deployment target, framework linking
- Fix AppleClang detection
- macOS platform name for output directories
- Skip Vulkan on macOS, null graphics without Vulkan dependency
- macOS SDL2 framework detection and discord-rpc macOS sources

Portability fixes:
- mach_absolute_time for macOS tick counting
- _NSGetExecutablePath for macOS
- guard MAP_FIXED_NOREPLACE, ftruncate64->ftruncate
- guard Vulkan include/usage, fix data root for all POSIX
This commit is contained in:
Herman S.
2026-03-27 02:28:34 +09:00
parent 1af96481b2
commit 57c4051eca
8 changed files with 95 additions and 16 deletions

View File

@@ -1,5 +1,11 @@
cmake_minimum_required(VERSION 3.20) cmake_minimum_required(VERSION 3.20)
project(xenia LANGUAGES C CXX) if(APPLE)
project(xenia LANGUAGES C CXX OBJCXX)
set(CMAKE_OBJCXX_STANDARD 20)
set(CMAKE_OBJCXX_STANDARD_REQUIRED ON)
else()
project(xenia LANGUAGES C CXX)
endif()
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
@@ -52,6 +58,8 @@ set(CMAKE_STATIC_LINKER_FLAGS_CHECKED "${CMAKE_STATIC_LINKER_FLAGS_DEBUG}" CACHE
# Platform name for output paths # Platform name for output paths
if(WIN32) if(WIN32)
set(XE_PLATFORM_NAME "Windows") set(XE_PLATFORM_NAME "Windows")
elseif(APPLE)
set(XE_PLATFORM_NAME "macOS")
else() else()
set(XE_PLATFORM_NAME "Linux") set(XE_PLATFORM_NAME "Linux")
endif() endif()
@@ -178,7 +186,7 @@ else()
) )
# Clang-specific C++ warnings # Clang-specific C++ warnings
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options( add_compile_options(
$<$<COMPILE_LANGUAGE:CXX>:-Wno-deprecated-register> $<$<COMPILE_LANGUAGE:CXX>:-Wno-deprecated-register>
$<$<COMPILE_LANGUAGE:CXX>:-Wno-deprecated-volatile> $<$<COMPILE_LANGUAGE:CXX>:-Wno-deprecated-volatile>
@@ -216,7 +224,23 @@ else()
$<$<COMPILE_LANGUAGE:C>:-Wno-implicit-function-declaration> $<$<COMPILE_LANGUAGE:C>:-Wno-implicit-function-declaration>
) )
if(CMAKE_SYSTEM_NAME STREQUAL "Linux") if(APPLE)
# macOS-specific settings
set(CMAKE_OSX_DEPLOYMENT_TARGET "15.0" CACHE STRING "Minimum macOS version")
add_compile_options(
$<$<COMPILE_LANGUAGE:CXX>:-Wno-shorten-64-to-32>
-Wno-deprecated-declarations
-Wno-character-conversion
)
add_link_options(-Wl,-no_warn_duplicate_libraries)
link_libraries(
"-framework CoreFoundation"
"-framework Foundation"
"-framework IOKit"
dl
pthread
)
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
# GTK3 via pkg-config # GTK3 via pkg-config
find_package(PkgConfig REQUIRED) find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK3 REQUIRED gtk+-x11-3.0) pkg_check_modules(GTK3 REQUIRED gtk+-x11-3.0)

View File

@@ -48,7 +48,9 @@
// Available graphics systems: // Available graphics systems:
#include "xenia/gpu/null/null_graphics_system.h" #include "xenia/gpu/null/null_graphics_system.h"
#if !XE_PLATFORM_MAC
#include "xenia/gpu/vulkan/vulkan_graphics_system.h" #include "xenia/gpu/vulkan/vulkan_graphics_system.h"
#endif
#if XE_PLATFORM_WIN32 #if XE_PLATFORM_WIN32
#include "xenia/gpu/d3d12/d3d12_graphics_system.h" #include "xenia/gpu/d3d12/d3d12_graphics_system.h"
#endif // XE_PLATFORM_WIN32 #endif // XE_PLATFORM_WIN32
@@ -401,7 +403,9 @@ std::unique_ptr<gpu::GraphicsSystem> EmulatorApp::CreateGraphicsSystem() {
#if XE_PLATFORM_WIN32 #if XE_PLATFORM_WIN32
factory.Add<gpu::d3d12::D3D12GraphicsSystem>("d3d12"); factory.Add<gpu::d3d12::D3D12GraphicsSystem>("d3d12");
#endif // XE_PLATFORM_WIN32 #endif // XE_PLATFORM_WIN32
#if !XE_PLATFORM_MAC
factory.Add<gpu::vulkan::VulkanGraphicsSystem>("vulkan"); factory.Add<gpu::vulkan::VulkanGraphicsSystem>("vulkan");
#endif
std::unique_ptr<gpu::GraphicsSystem> gpu_implementation = std::unique_ptr<gpu::GraphicsSystem> gpu_implementation =
factory.Create(gpu_implementation_name); factory.Create(gpu_implementation_name);
if (!gpu_implementation) { if (!gpu_implementation) {
@@ -475,12 +479,9 @@ bool EmulatorApp::OnInitialize() {
if (!cvars::portable && if (!cvars::portable &&
!std::filesystem::exists(storage_root / "portable.txt")) { !std::filesystem::exists(storage_root / "portable.txt")) {
storage_root = xe::filesystem::GetUserFolder(); storage_root = xe::filesystem::GetUserFolder();
#if defined(XE_PLATFORM_WIN32) || defined(XE_PLATFORM_LINUX) #if XE_PLATFORM_ANDROID
storage_root = storage_root / "Xenia"; // TODO(Triang3l): Point to the app's external storage "files" directory.
#else #else
// TODO(Triang3l): Point to the app's external storage "files" directory
// on Android.
#warning Unhandled platform for the data root.
storage_root = storage_root / "Xenia"; storage_root = storage_root / "Xenia";
#endif #endif
} }

View File

@@ -8,6 +8,9 @@
*/ */
#include <sys/time.h> #include <sys/time.h>
#ifdef __APPLE__
#include <mach/mach_time.h>
#endif
#include "xenia/base/assert.h" #include "xenia/base/assert.h"
#include "xenia/base/clock.h" #include "xenia/base/clock.h"
@@ -15,6 +18,12 @@
namespace xe { namespace xe {
uint64_t Clock::host_tick_frequency_platform() { uint64_t Clock::host_tick_frequency_platform() {
#ifdef __APPLE__
mach_timebase_info_data_t info;
mach_timebase_info(&info);
return (uint64_t)((1000000000ull * (uint64_t)info.denom) /
(uint64_t)info.numer);
#else
timespec res; timespec res;
int error = clock_getres(CLOCK_MONOTONIC_RAW, &res); int error = clock_getres(CLOCK_MONOTONIC_RAW, &res);
assert_zero(error); assert_zero(error);
@@ -22,14 +31,19 @@ uint64_t Clock::host_tick_frequency_platform() {
// Convert nano seconds to hertz. Resolution is 1ns on most systems. // Convert nano seconds to hertz. Resolution is 1ns on most systems.
return 1000000000ull / res.tv_nsec; return 1000000000ull / res.tv_nsec;
#endif
} }
uint64_t Clock::host_tick_count_platform() { uint64_t Clock::host_tick_count_platform() {
#ifdef __APPLE__
return mach_absolute_time();
#else
timespec tp; timespec tp;
int error = clock_gettime(CLOCK_MONOTONIC_RAW, &tp); int error = clock_gettime(CLOCK_MONOTONIC_RAW, &tp);
assert_zero(error); assert_zero(error);
return tp.tv_nsec + tp.tv_sec * 1000000000ull; return tp.tv_nsec + tp.tv_sec * 1000000000ull;
#endif
} }
uint64_t Clock::QueryHostSystemTime() { uint64_t Clock::QueryHostSystemTime() {

View File

@@ -23,6 +23,10 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
#include <unistd.h> #include <unistd.h>
#if XE_PLATFORM_MAC
#include <limits.h>
#include <mach-o/dyld.h>
#endif
namespace xe { namespace xe {
@@ -43,10 +47,26 @@ std::filesystem::path to_path(const std::u16string_view source) {
namespace filesystem { namespace filesystem {
std::filesystem::path GetExecutablePath() { std::filesystem::path GetExecutablePath() {
#if XE_PLATFORM_MAC
char path[PATH_MAX];
uint32_t size = sizeof(path);
if (_NSGetExecutablePath(path, &size) == 0) {
char real_path[PATH_MAX];
if (realpath(path, real_path)) {
return std::string(real_path);
}
return std::string(path);
}
return std::string();
#else
char buff[FILENAME_MAX] = ""; char buff[FILENAME_MAX] = "";
readlink("/proc/self/exe", buff, FILENAME_MAX); ssize_t len = readlink("/proc/self/exe", buff, sizeof(buff) - 1);
std::string s(buff); if (len != -1) {
return s; buff[len] = '\0';
return std::string(buff);
}
return std::string();
#endif
} }
std::filesystem::path GetExecutableFolder() { std::filesystem::path GetExecutableFolder() {

View File

@@ -146,7 +146,9 @@ void* AllocFixed(void* base_address, size_t length,
} }
return nullptr; return nullptr;
} }
#ifdef MAP_FIXED_NOREPLACE
flags |= MAP_FIXED_NOREPLACE; flags |= MAP_FIXED_NOREPLACE;
#endif
} }
void* result = mmap(base_address, length, prot, flags, -1, 0); void* result = mmap(base_address, length, prot, flags, -1, 0);
@@ -296,7 +298,7 @@ FileMappingHandle CreateFileMappingHandle(const std::filesystem::path& path,
if (ret < 0) { if (ret < 0) {
return kFileMappingHandleInvalid; return kFileMappingHandleInvalid;
} }
if (ftruncate64(ret, length) < 0) { if (ftruncate(ret, length) < 0) {
close(ret); close(ret);
shm_unlink(full_path.c_str()); shm_unlink(full_path.c_str());
return kFileMappingHandleInvalid; return kFileMappingHandleInvalid;
@@ -335,7 +337,9 @@ void* MapFileView(FileMappingHandle handle, void* base_address, size_t length,
int flags = MAP_SHARED; int flags = MAP_SHARED;
if (base_address != nullptr) { if (base_address != nullptr) {
#ifdef MAP_FIXED_NOREPLACE
flags |= MAP_FIXED_NOREPLACE; flags |= MAP_FIXED_NOREPLACE;
#endif
} }
void* result = mmap(base_address, length, prot, flags, handle, file_offset); void* result = mmap(base_address, length, prot, flags, handle, file_offset);

View File

@@ -1,7 +1,11 @@
add_library(xenia-gpu-null STATIC) add_library(xenia-gpu-null STATIC)
xe_platform_sources(xenia-gpu-null ${CMAKE_CURRENT_SOURCE_DIR}) xe_platform_sources(xenia-gpu-null ${CMAKE_CURRENT_SOURCE_DIR})
target_include_directories(xenia-gpu-null PRIVATE if(APPLE)
${PROJECT_SOURCE_DIR}/third_party/Vulkan-Headers/include target_link_libraries(xenia-gpu-null PUBLIC xenia-base xenia-gpu xenia-ui xxhash)
) else()
target_link_libraries(xenia-gpu-null PUBLIC xenia-base xenia-gpu xenia-ui xenia-ui-vulkan xxhash) target_include_directories(xenia-gpu-null PRIVATE
${PROJECT_SOURCE_DIR}/third_party/Vulkan-Headers/include
)
target_link_libraries(xenia-gpu-null PUBLIC xenia-base xenia-gpu xenia-ui xenia-ui-vulkan xxhash)
endif()
xe_target_defaults(xenia-gpu-null) xe_target_defaults(xenia-gpu-null)

View File

@@ -10,7 +10,9 @@
#include "xenia/gpu/null/null_graphics_system.h" #include "xenia/gpu/null/null_graphics_system.h"
#include "xenia/gpu/null//null_command_processor.h" #include "xenia/gpu/null//null_command_processor.h"
#if !XE_PLATFORM_MAC
#include "xenia/ui/vulkan/vulkan_provider.h" #include "xenia/ui/vulkan/vulkan_provider.h"
#endif
#include "xenia/xbox.h" #include "xenia/xbox.h"
namespace xe { namespace xe {
@@ -25,9 +27,13 @@ X_STATUS NullGraphicsSystem::Setup(cpu::Processor* processor,
kernel::KernelState* kernel_state, kernel::KernelState* kernel_state,
ui::WindowedAppContext* app_context, ui::WindowedAppContext* app_context,
bool with_presentation) { bool with_presentation) {
#if XE_PLATFORM_MAC
provider_ = nullptr;
#else
// This is a null graphics system, but we still setup vulkan because UI needs // This is a null graphics system, but we still setup vulkan because UI needs
// it through us :| // it through us :|
provider_ = xe::ui::vulkan::VulkanProvider::Create(false, with_presentation); provider_ = xe::ui::vulkan::VulkanProvider::Create(false, with_presentation);
#endif
return GraphicsSystem::Setup(processor, kernel_state, app_context, return GraphicsSystem::Setup(processor, kernel_state, app_context,
with_presentation); with_presentation);
} }

View File

@@ -121,6 +121,12 @@ if(WIN32)
discord-rpc/src/connection_win.cpp discord-rpc/src/connection_win.cpp
discord-rpc/src/discord_register_win.cpp discord-rpc/src/discord_register_win.cpp
) )
elseif(APPLE)
target_sources(discord-rpc PRIVATE
discord-rpc/src/connection_unix.cpp
discord-rpc/src/discord_register_osx.m
)
target_link_libraries(discord-rpc PRIVATE "-framework AppKit")
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
target_sources(discord-rpc PRIVATE target_sources(discord-rpc PRIVATE
discord-rpc/src/connection_unix.cpp discord-rpc/src/connection_unix.cpp