mirror of
https://github.com/Nexoniarz/Fry3DX.git
synced 2026-11-03 04:24:26 +00:00
macOS (Apple silicon and Intel): Fry3DX.app with its content in Resources and MoltenVK in Frameworks, OpenGL 4.1 when there's no Vulkan; the program's own path, memory, OS and CPU names on a Mac; files dropped on the window (and opened from the Finder) open; presets macos-arm64/macos-x64, a CI job, tools/package.py macos makes one universal app
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
5c28e3da4e
commit
e22cd2e3e6
15 changed files with 273 additions and 40 deletions
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <thread>
|
||||
|
||||
|
|
@ -15,6 +16,11 @@
|
|||
#include <sys/utsname.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#if defined(__APPLE__)
|
||||
#include <mach-o/dyld.h>
|
||||
#include <mach/mach.h>
|
||||
#include <sys/sysctl.h>
|
||||
#endif
|
||||
#if defined(_MSC_VER)
|
||||
#include <intrin.h>
|
||||
#elif defined(__x86_64__) || defined(__i386__)
|
||||
|
|
@ -30,6 +36,14 @@ std::string trim(std::string s) {
|
|||
return s.substr(i);
|
||||
}
|
||||
|
||||
#if defined(__APPLE__)
|
||||
std::string sysctlText(const char* name) {
|
||||
char buf[256] = {};
|
||||
size_t size = sizeof buf - 1;
|
||||
return sysctlbyname(name, buf, &size, nullptr, 0) == 0 ? trim(buf) : std::string();
|
||||
}
|
||||
#endif
|
||||
|
||||
// The CPU's own name for itself (x86: cpuid's brand string; elsewhere /proc/cpuinfo).
|
||||
std::string cpuName() {
|
||||
#if defined(_MSC_VER) || defined(__x86_64__) || defined(__i386__)
|
||||
|
|
@ -46,6 +60,9 @@ std::string cpuName() {
|
|||
char brand[49] = {};
|
||||
std::memcpy(brand, regs, 48);
|
||||
if (brand[0]) return trim(brand);
|
||||
#endif
|
||||
#if defined(__APPLE__)
|
||||
if (std::string s = sysctlText("machdep.cpu.brand_string"); !s.empty()) return s; // ("Apple M2 Pro" too)
|
||||
#endif
|
||||
std::ifstream in("/proc/cpuinfo");
|
||||
for (std::string line; std::getline(in, line);)
|
||||
|
|
@ -64,6 +81,11 @@ std::string osName() {
|
|||
fn(&v);
|
||||
const char* name = v.dwMajorVersion == 10 ? (v.dwBuildNumber >= 22000 ? "Windows 11" : "Windows 10") : "Windows";
|
||||
return std::string(name) + " (build " + std::to_string(v.dwBuildNumber) + ")";
|
||||
#elif defined(__APPLE__)
|
||||
utsname u{};
|
||||
uname(&u);
|
||||
const std::string v = sysctlText("kern.osproductversion");
|
||||
return "macOS " + (v.empty() ? std::string("?") : v) + " (" + u.machine + ")";
|
||||
#else
|
||||
utsname u{};
|
||||
uname(&u);
|
||||
|
|
@ -95,6 +117,19 @@ Memory memory() {
|
|||
if (GlobalMemoryStatusEx(&s)) m.total = s.ullTotalPhys, m.used = s.ullTotalPhys - s.ullAvailPhys;
|
||||
PROCESS_MEMORY_COUNTERS pmc{sizeof pmc};
|
||||
if (K32GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof pmc)) m.ours = pmc.WorkingSetSize;
|
||||
#elif defined(__APPLE__)
|
||||
uint64_t total = 0;
|
||||
size_t size = sizeof total;
|
||||
if (sysctlbyname("hw.memsize", &total, &size, nullptr, 0) == 0) m.total = total;
|
||||
vm_statistics64_data_t vm{};
|
||||
mach_msg_type_number_t count = HOST_VM_INFO64_COUNT;
|
||||
if (host_statistics64(mach_host_self(), HOST_VM_INFO64, reinterpret_cast<host_info64_t>(&vm), &count) == KERN_SUCCESS) {
|
||||
const uint64_t page = uint64_t(sysconf(_SC_PAGESIZE)); // (used as Activity Monitor counts it: in use by apps, wired, compressed)
|
||||
m.used = (uint64_t(vm.internal_page_count) - vm.purgeable_count + vm.wire_count + vm.compressor_page_count) * page;
|
||||
}
|
||||
mach_task_basic_info_data_t task{};
|
||||
mach_msg_type_number_t taskCount = MACH_TASK_BASIC_INFO_COUNT;
|
||||
if (task_info(mach_task_self(), MACH_TASK_BASIC_INFO, reinterpret_cast<task_info_t>(&task), &taskCount) == KERN_SUCCESS) m.ours = task.resident_size;
|
||||
#else
|
||||
uint64_t available = 0;
|
||||
std::ifstream in("/proc/meminfo");
|
||||
|
|
@ -114,4 +149,39 @@ Memory memory() {
|
|||
return m;
|
||||
}
|
||||
|
||||
std::string executablePath() {
|
||||
#if defined(_WIN32)
|
||||
char exe[1024] = {};
|
||||
GetModuleFileNameA(nullptr, exe, sizeof exe);
|
||||
return exe;
|
||||
#elif defined(__APPLE__)
|
||||
char exe[4096] = {};
|
||||
uint32_t size = sizeof exe;
|
||||
if (_NSGetExecutablePath(exe, &size) != 0) return {};
|
||||
std::error_code ec;
|
||||
const auto real = std::filesystem::canonical(exe, ec);
|
||||
return ec ? std::string(exe) : real.string();
|
||||
#else
|
||||
std::error_code ec;
|
||||
const auto p = std::filesystem::read_symlink("/proc/self/exe", ec);
|
||||
return ec ? std::string() : p.string();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool debuggerAttached() {
|
||||
#if defined(_WIN32)
|
||||
return IsDebuggerPresent();
|
||||
#elif defined(__APPLE__)
|
||||
kinfo_proc info{};
|
||||
size_t size = sizeof info;
|
||||
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()};
|
||||
return sysctl(mib, 4, &info, &size, nullptr, 0) == 0 && (info.kp_proc.p_flag & P_TRACED) != 0;
|
||||
#else
|
||||
std::ifstream f("/proc/self/status");
|
||||
for (std::string line; std::getline(f, line);)
|
||||
if (line.rfind("TracerPid:", 0) == 0) return std::atoi(line.c_str() + 10) != 0;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace keel::sys
|
||||
|
|
|
|||
|
|
@ -18,4 +18,9 @@ struct Memory {
|
|||
};
|
||||
Memory memory();
|
||||
|
||||
// This program's file (for starting it again: crash reports, restarts). Empty if unknown.
|
||||
std::string executablePath();
|
||||
// Is a debugger watching (the freeze watchdog waits then)?
|
||||
bool debuggerAttached();
|
||||
|
||||
} // namespace keel::sys
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include "crash.h"
|
||||
|
||||
#include <core/log.h>
|
||||
#include <core/sysinfo.h>
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
|
|
@ -122,17 +123,6 @@ void onSignal(int sig) {
|
|||
}
|
||||
#endif
|
||||
|
||||
bool debuggerAttached() {
|
||||
#ifdef _WIN32
|
||||
return IsDebuggerPresent();
|
||||
#else
|
||||
std::ifstream f("/proc/self/status");
|
||||
for (std::string line; std::getline(f, line);)
|
||||
if (line.rfind("TracerPid:", 0) == 0) return std::atoi(line.c_str() + 10) != 0;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void watchdog() {
|
||||
constexpr double kTimeout = 45.0;
|
||||
for (;;) {
|
||||
|
|
@ -141,7 +131,7 @@ void watchdog() {
|
|||
g_beat = seconds();
|
||||
continue;
|
||||
}
|
||||
if (seconds() - g_beat < kTimeout || debuggerAttached()) continue;
|
||||
if (seconds() - g_beat < kTimeout || sys::debuggerAttached()) continue;
|
||||
#ifndef _WIN32
|
||||
pthread_kill(g_main, SIGUSR2); // its handler reports from the stuck thread (its backtrace)
|
||||
std::this_thread::sleep_for(std::chrono::seconds(3));
|
||||
|
|
@ -153,12 +143,8 @@ void watchdog() {
|
|||
|
||||
void install(const std::string& prefPath, const std::vector<std::string>& args, bool dog) {
|
||||
g_report = prefPath + "crash.txt";
|
||||
#ifdef _WIN32
|
||||
char exe[1024] = {};
|
||||
GetModuleFileNameA(nullptr, exe, sizeof exe);
|
||||
g_exe = exe;
|
||||
#else
|
||||
g_exe = std::filesystem::read_symlink("/proc/self/exe").string();
|
||||
g_exe = sys::executablePath();
|
||||
#ifndef _WIN32
|
||||
g_main = pthread_self();
|
||||
#endif
|
||||
g_args = {g_exe, "-crashreport", g_report, "--"};
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include "core/console.h"
|
||||
#include "core/cvar.h"
|
||||
#include "core/log.h"
|
||||
#include "core/sysinfo.h"
|
||||
#include "core/time.h"
|
||||
#include "fs/vfs.h"
|
||||
#include "platform/input.h"
|
||||
|
|
@ -289,6 +290,10 @@ int runClient(Game& game) {
|
|||
#if defined(__ANDROID__)
|
||||
if (first != RenderApi::Vulkan) first = RenderApi::GLES30;
|
||||
const std::initializer_list<RenderApi> all{RenderApi::Vulkan, RenderApi::GLES30};
|
||||
#elif defined(__APPLE__)
|
||||
// (A Mac: Vulkan through MoltenVK, else OpenGL as far as macOS has it: 4.1.)
|
||||
if (first != RenderApi::Vulkan) first = RenderApi::OpenGL40;
|
||||
const std::initializer_list<RenderApi> all{RenderApi::Vulkan, RenderApi::OpenGL40};
|
||||
#else
|
||||
const std::initializer_list<RenderApi> all{RenderApi::Vulkan, RenderApi::OpenGL, RenderApi::OpenGL40, RenderApi::OpenGL31, RenderApi::GLES30};
|
||||
#endif
|
||||
|
|
@ -356,6 +361,10 @@ int runClient(Game& game) {
|
|||
else game.onEscape();
|
||||
continue;
|
||||
}
|
||||
if (ev.type == SDL_EVENT_DROP_FILE && ev.drop.data) {
|
||||
game.onFileDropped(ev.drop.data);
|
||||
continue;
|
||||
}
|
||||
// Don't let the toggle key's character land in the console input.
|
||||
if (ev.type == SDL_EVENT_TEXT_INPUT && ev.text.text[0] == '`') continue;
|
||||
input::handleEvent(ev);
|
||||
|
|
@ -443,20 +452,17 @@ const char* buildVersion() { return KEEL_COMMIT ", " KEEL_COMMIT_DATE; }
|
|||
// Replaces this process with a fresh one (Windows: starts it, then this one exits).
|
||||
void relaunch(const std::vector<std::string>& args) {
|
||||
std::vector<std::string> a = args;
|
||||
const std::string exe = sys::executablePath();
|
||||
#ifdef _WIN32
|
||||
char exe[1024] = {};
|
||||
GetModuleFileNameA(nullptr, exe, sizeof exe);
|
||||
for (std::string& x : a)
|
||||
if (x.find(' ') != std::string::npos) x = "\"" + x + "\"";
|
||||
#else
|
||||
const std::string exe = std::filesystem::read_symlink("/proc/self/exe").string();
|
||||
#endif
|
||||
std::vector<char*> argv;
|
||||
for (std::string& x : a) argv.push_back(x.data());
|
||||
argv.push_back(nullptr);
|
||||
log::info("Restarting");
|
||||
#ifdef _WIN32
|
||||
_spawnv(_P_NOWAIT, exe, argv.data());
|
||||
_spawnv(_P_NOWAIT, exe.c_str(), argv.data());
|
||||
#else
|
||||
execv(exe.c_str(), argv.data());
|
||||
log::error("Restart failed");
|
||||
|
|
|
|||
|
|
@ -33,6 +33,9 @@ public:
|
|||
// The window's close button (or the OS asking it to close): false keeps it open (to ask
|
||||
// about saving first; the game quits later itself). The quit command always quits.
|
||||
virtual bool allowQuit() { return true; }
|
||||
// A file dropped on the window, or opened with the program from the desktop (a Mac's Finder
|
||||
// hands it over this way, not on the command line).
|
||||
virtual void onFileDropped(const std::string& path) { (void)path; }
|
||||
// Hot reload: called twice a second (hotreload 1) after the engine reloaded what changed
|
||||
// (`changed`: textures, models, sounds); the game reloads its own (scripts, the map).
|
||||
virtual void hotReload(const std::vector<std::string>& changed) { (void)changed; }
|
||||
|
|
|
|||
|
|
@ -21,7 +21,14 @@ namespace {
|
|||
// one) unless asked. Vulkan picks the discrete GPU itself; this makes OpenGL do the same. Set
|
||||
// before SDL loads the GL library; anything the user already set wins. KEEL_IGPU=1 opts out.
|
||||
void preferDedicatedGpu() {
|
||||
#ifndef _WIN32
|
||||
#if defined(__APPLE__)
|
||||
// macOS has no Vulkan of its own: the app carries MoltenVK (Vulkan on Metal) in
|
||||
// Contents/Frameworks. (Unless the user pointed SDL somewhere else.)
|
||||
if (const char* base = SDL_GetBasePath(); base && !SDL_GetHint(SDL_HINT_VULKAN_LIBRARY)) {
|
||||
const std::filesystem::path mvk = std::filesystem::path(base) / ".." / "Frameworks" / "libMoltenVK.dylib";
|
||||
if (std::filesystem::exists(mvk)) SDL_SetHint(SDL_HINT_VULKAN_LIBRARY, mvk.lexically_normal().string().c_str());
|
||||
}
|
||||
#elif !defined(_WIN32)
|
||||
if (std::getenv("KEEL_IGPU")) return;
|
||||
if (std::filesystem::exists("/proc/driver/nvidia")) { // NVIDIA's own driver: PRIME render offload
|
||||
setenv("__NV_PRIME_RENDER_OFFLOAD", "1", 0);
|
||||
|
|
|
|||
|
|
@ -46,6 +46,11 @@ static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityF
|
|||
|
||||
bool Device::init(const Window& window, bool validation) {
|
||||
window_ = &window;
|
||||
#if defined(__APPLE__)
|
||||
// The library SDL loaded for the window (the app's MoltenVK, or a Vulkan SDK's loader).
|
||||
if (auto gipa = reinterpret_cast<PFN_vkGetInstanceProcAddr>(SDL_Vulkan_GetVkGetInstanceProcAddr())) volkInitializeCustom(gipa);
|
||||
else
|
||||
#endif
|
||||
if (volkInitialize() != VK_SUCCESS) {
|
||||
log::error("No Vulkan loader found - is a Vulkan driver installed?");
|
||||
return false;
|
||||
|
|
|
|||
Loading…
Reference in a new issue