diff --git a/src/common/cocoa_tools.h b/src/common/cocoa_tools.h index 61d7be7a9..4440f75fd 100644 --- a/src/common/cocoa_tools.h +++ b/src/common/cocoa_tools.h @@ -1,10 +1,12 @@ // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin // SPDX-License-Identifier: PolyForm-Strict-1.0.0 +#include "types.h" + +#include #include #include #include -#include class Error; @@ -14,14 +16,19 @@ class Error; namespace CocoaTools { NSString* StringViewToNSString(std::string_view str); +void NSErrorToErrorObject(Error* errptr, std::string_view message, NSError* error); /// Converts NSError to a human-readable string. std::string NSErrorToString(NSError* error); -} +} // namespace CocoaTools #endif namespace CocoaTools { +// Converts to Mach timebase. +u64 ConvertMachTimeBaseToNanoseconds(u64 ns); +u64 ConvertNanosecondsToMachTimeBase(u64 ns); + /// Add a handler to be run when macOS changes between dark and light themes void AddThemeChangeHandler(void* ctx, void(handler)(void* ctx)); diff --git a/src/common/cocoa_tools.mm b/src/common/cocoa_tools.mm index 1e21ad016..af42400f7 100644 --- a/src/common/cocoa_tools.mm +++ b/src/common/cocoa_tools.mm @@ -2,14 +2,16 @@ // SPDX-License-Identifier: PolyForm-Strict-1.0.0 #include "cocoa_tools.h" -#include "small_string.h" +#include "assert.h" #include "error.h" +#include "small_string.h" #include "fmt/format.h" #include -#include #include +#include +#include #if __has_feature(objc_arc) #error ARC should not be enabled. @@ -21,19 +23,25 @@ NSString* CocoaTools::StringViewToNSString(std::string_view str) return nil; return [[[NSString alloc] initWithBytes:str.data() - length:static_cast(str.length()) - encoding:NSUTF8StringEncoding] autorelease]; + length:static_cast(str.length()) + encoding:NSUTF8StringEncoding] autorelease]; } -std::string CocoaTools::NSErrorToString(NSError *error) +std::string CocoaTools::NSErrorToString(NSError* error) { return fmt::format("{}: {}", static_cast(error.code), [error.description UTF8String]); } - -bool CocoaTools::MoveFile(const char *source, const char *destination, Error *error) +void CocoaTools::NSErrorToErrorObject(Error* errptr, std::string_view message, NSError* error) { - @autoreleasepool { + Error::SetStringFmt(errptr, "{}NSError Code {}: {}", message, static_cast(error.code), + [error.description UTF8String]); +} + +bool CocoaTools::MoveFile(const char* source, const char* destination, Error* error) +{ + @autoreleasepool + { NSError* nserror; const BOOL result = [[NSFileManager defaultManager] moveItemAtPath:[NSString stringWithUTF8String:source] toPath:[NSString stringWithUTF8String:destination] @@ -43,36 +51,55 @@ bool CocoaTools::MoveFile(const char *source, const char *destination, Error *er Error::SetString(error, NSErrorToString(nserror)); return false; } - + return true; } } +// Used for present timing. +static const struct mach_timebase_info s_timebase_info = []() { + struct mach_timebase_info val; + const kern_return_t res = mach_timebase_info(&val); + Assert(res == KERN_SUCCESS); + return val; +}(); + +u64 CocoaTools::ConvertMachTimeBaseToNanoseconds(u64 time) +{ + return ((time * s_timebase_info.numer) / s_timebase_info.denom); +} + +u64 CocoaTools::ConvertNanosecondsToMachTimeBase(u64 time) +{ + return ((time * s_timebase_info.denom) / s_timebase_info.numer); +} @interface CommonKVOHelper : NSObject -- (void)addCallback:(void*)ctx run:(void(*)(void*))callback; +- (void)addCallback:(void*)ctx run:(void (*)(void*))callback; - (void)removeCallback:(void*)ctx; @end @implementation CommonKVOHelper { - std::vector> _callbacks; + std::vector> _callbacks; } -- (void)addCallback:(void*)ctx run:(void(*)(void*))callback +- (void)addCallback:(void*)ctx run:(void (*)(void*))callback { _callbacks.push_back(std::make_pair(ctx, callback)); } - (void)removeCallback:(void*)ctx { - auto new_end = std::remove_if(_callbacks.begin(), _callbacks.end(), [ctx](const auto& entry){ - return ctx == entry.first; - }); + auto new_end = + std::remove_if(_callbacks.begin(), _callbacks.end(), [ctx](const auto& entry) { return ctx == entry.first; }); _callbacks.erase(new_end, _callbacks.end()); } -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context +- (void)observeValueForKeyPath:(NSString*)keyPath + ofObject:(id)object + change:(NSDictionary*)change + context:(void*)context { for (const auto& callback : _callbacks) callback.second(callback.first); @@ -89,10 +116,7 @@ void CocoaTools::AddThemeChangeHandler(void* ctx, void(handler)(void* ctx)) { s_themeChangeHandler = [[CommonKVOHelper alloc] init]; NSApplication* app = [NSApplication sharedApplication]; - [app addObserver:s_themeChangeHandler - forKeyPath:@"effectiveAppearance" - options:0 - context:nil]; + [app addObserver:s_themeChangeHandler forKeyPath:@"effectiveAppearance" options:0 context:nil]; } [s_themeChangeHandler addCallback:ctx run:handler]; } @@ -106,7 +130,8 @@ void CocoaTools::RemoveThemeChangeHandler(void* ctx) std::optional CocoaTools::GetBundlePath() { std::optional ret; - @autoreleasepool { + @autoreleasepool + { NSURL* url = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]]; if (url) ret = std::string([url fileSystemRepresentation]); @@ -124,8 +149,12 @@ std::optional CocoaTools::GetNonTranslocatedBundlePath() if (void* handle = dlopen("/System/Library/Frameworks/Security.framework/Security", RTLD_LAZY)) { - auto IsTranslocatedURL = reinterpret_cast(dlsym(handle, "SecTranslocateIsTranslocatedURL")); - auto CreateOriginalPathForURL = reinterpret_cast(dlsym(handle, "SecTranslocateCreateOriginalPathForURL")); + auto IsTranslocatedURL = + reinterpret_cast( + dlsym(handle, "SecTranslocateIsTranslocatedURL")); + auto CreateOriginalPathForURL = + reinterpret_cast( + dlsym(handle, "SecTranslocateCreateOriginalPathForURL")); bool is_translocated = false; if (IsTranslocatedURL) IsTranslocatedURL((__bridge CFURLRef)url, &is_translocated, nullptr); @@ -142,11 +171,13 @@ std::optional CocoaTools::GetNonTranslocatedBundlePath() bool CocoaTools::DelayedLaunch(std::string_view file, std::span args) { - @autoreleasepool { + @autoreleasepool + { const int pid = [[NSProcessInfo processInfo] processIdentifier]; - + // Hopefully we're not too large here... - std::string task_args = fmt::format("while /bin/ps -p {} > /dev/null; do /bin/sleep 0.1; done; exec /usr/bin/open \"{}\"", pid, file); + std::string task_args = + fmt::format("while /bin/ps -p {} > /dev/null; do /bin/sleep 0.1; done; exec /usr/bin/open \"{}\"", pid, file); if (!args.empty()) { task_args += " --args"; @@ -157,10 +188,10 @@ bool CocoaTools::DelayedLaunch(std::string_view file, std::span #if !defined(_WIN32) && !defined(__APPLE__) @@ -38,6 +41,8 @@ #endif #endif +Log_SetChannel(Threading); + #ifdef _WIN32 union FileTimeU64Union { @@ -270,6 +275,46 @@ bool Threading::ThreadHandle::SetAffinity(u64 processor_mask) const #endif } +#ifdef __APPLE__ + +bool Threading::ThreadHandle::SetTimeConstraints(bool enabled, u64 period, u64 typical_time, u64 maximum_time) +{ + const mach_port_t mach_thread_id = pthread_mach_thread_np((pthread_t)m_native_handle); + if (!enabled) + { + thread_standard_policy policy = {}; + const kern_return_t res = thread_policy_set( + mach_thread_id, THREAD_STANDARD_POLICY, reinterpret_cast(&policy), THREAD_STANDARD_POLICY_COUNT); + if (res != KERN_SUCCESS) + { + ERROR_LOG("thread_policy_set(THREAD_STANDARD_POLICY) failed: {}", static_cast(res)); + return false; + } + + return true; + } + + thread_time_constraint_policy_data_t constraints; + constraints.period = CocoaTools::ConvertNanosecondsToMachTimeBase(period); + constraints.computation = CocoaTools::ConvertNanosecondsToMachTimeBase(typical_time); + constraints.constraint = CocoaTools::ConvertNanosecondsToMachTimeBase(maximum_time); + constraints.preemptible = false; + + const kern_return_t res = + thread_policy_set(mach_thread_id, THREAD_TIME_CONSTRAINT_POLICY, reinterpret_cast(&constraints), + THREAD_TIME_CONSTRAINT_POLICY_COUNT); + if (res != KERN_SUCCESS) + { + ERROR_LOG("thread_policy_set(THREAD_TIME_CONSTRAINT_POLICY) failed: {}, args {}, {}, {}", static_cast(res), + period, typical_time, maximum_time); + return false; + } + + return true; +} + +#endif + Threading::Thread::Thread() = default; Threading::Thread::Thread(Thread&& thread) : ThreadHandle(thread), m_stack_size(thread.m_stack_size) diff --git a/src/common/threading.h b/src/common/threading.h index a57c7f160..98f80a4d8 100644 --- a/src/common/threading.h +++ b/src/common/threading.h @@ -53,6 +53,11 @@ public: /// Obviously, only works up to 64 processors. bool SetAffinity(u64 processor_mask) const; +#ifdef __APPLE__ + /// Only available on MacOS, sets a period/maximum time for the scheduler. + bool SetTimeConstraints(bool enabled, u64 period, u64 typical_time, u64 maximum_time); +#endif + protected: void* m_native_handle = nullptr; diff --git a/src/util/metal_device.mm b/src/util/metal_device.mm index 10f261280..94af7af41 100644 --- a/src/util/metal_device.mm +++ b/src/util/metal_device.mm @@ -5,6 +5,7 @@ #include "common/align.h" #include "common/assert.h" +#include "common/cocoa_tools.h" #include "common/error.h" #include "common/file_system.h" #include "common/log.h" @@ -44,14 +45,6 @@ static constexpr u32 TEXTURE_UPLOAD_ALIGNMENT = 64; // We need 32 here for AVX2, so 64 is also fine. static constexpr u32 TEXTURE_UPLOAD_PITCH_ALIGNMENT = 64; -// Used for present timing. -static const struct mach_timebase_info s_timebase_info = []() { - struct mach_timebase_info val; - const kern_return_t res = mach_timebase_info(&val); - Assert(res == KERN_SUCCESS); - return val; -}(); - static constexpr std::array(GPUTexture::Format::MaxCount)> s_pixel_format_mapping = { MTLPixelFormatInvalid, // Unknown MTLPixelFormatRGBA8Unorm, // RGBA8 @@ -80,16 +73,6 @@ static constexpr std::array(GPUTexture::Format: MTLPixelFormatBGR10A2Unorm, // RGB10A2 }; -static NSString* StringViewToNSString(std::string_view str) -{ - if (str.empty()) - return nil; - - return [[[NSString alloc] autorelease] initWithBytes:str.data() - length:static_cast(str.length()) - encoding:NSUTF8StringEncoding]; -} - static void LogNSError(NSError* error, std::string_view message) { Log::FastWrite("MetalDevice", LOGLEVEL_ERROR, message); @@ -97,12 +80,6 @@ static void LogNSError(NSError* error, std::string_view message) Log::FastWrite("MetalDevice", LOGLEVEL_ERROR, " NSError Description: {}", [error.description UTF8String]); } -static void NSErrorToErrorObject(Error* errptr, std::string_view message, NSError* error) -{ - Error::SetStringFmt(errptr, "{}NSError Code {}: {}", message, static_cast(error.code), - [error.description UTF8String]); -} - static GPUTexture::Format GetTextureFormatForMTLFormat(MTLPixelFormat fmt) { for (u32 i = 0; i < static_cast(GPUTexture::Format::MaxCount); i++) @@ -325,13 +302,13 @@ bool MetalDevice::OpenPipelineCache(const std::string& path, Error* error) @autoreleasepool { MTLBinaryArchiveDescriptor* archiveDescriptor = [[[MTLBinaryArchiveDescriptor alloc] init] autorelease]; - archiveDescriptor.url = [NSURL fileURLWithPath:StringViewToNSString(path)]; + archiveDescriptor.url = [NSURL fileURLWithPath:CocoaTools::StringViewToNSString(path)]; NSError* nserror = nil; m_pipeline_archive = [m_device newBinaryArchiveWithDescriptor:archiveDescriptor error:&nserror]; if (m_pipeline_archive == nil) { - NSErrorToErrorObject(error, "newBinaryArchiveWithDescriptor failed: ", nserror); + CocoaTools::NSErrorToErrorObject(error, "newBinaryArchiveWithDescriptor failed: ", nserror); return false; } @@ -351,7 +328,7 @@ bool MetalDevice::CreatePipelineCache(const std::string& path, Error* error) m_pipeline_archive = [m_device newBinaryArchiveWithDescriptor:archiveDescriptor error:&nserror]; if (m_pipeline_archive == nil) { - NSErrorToErrorObject(error, "newBinaryArchiveWithDescriptor failed: ", nserror); + CocoaTools::NSErrorToErrorObject(error, "newBinaryArchiveWithDescriptor failed: ", nserror); return false; } @@ -378,11 +355,11 @@ bool MetalDevice::ClosePipelineCache(const std::string& path, Error* error) @autoreleasepool { - NSURL* url = [NSURL fileURLWithPath:StringViewToNSString(path)]; + NSURL* url = [NSURL fileURLWithPath:CocoaTools::StringViewToNSString(path)]; NSError* nserror = nil; if (![m_pipeline_archive serializeToURL:url error:&nserror]) { - NSErrorToErrorObject(error, "serializeToURL failed: ", nserror); + CocoaTools::NSErrorToErrorObject(error, "serializeToURL failed: ", nserror); return false; } @@ -682,7 +659,7 @@ void MetalShader::SetDebugName(std::string_view name) { @autoreleasepool { - [m_function setLabel:StringViewToNSString(name)]; + [m_function setLabel:CocoaTools::StringViewToNSString(name)]; } } @@ -691,7 +668,7 @@ std::unique_ptr MetalDevice::CreateShaderFromMSL(GPUShaderStage stage { @autoreleasepool { - NSString* const ns_source = StringViewToNSString(source); + NSString* const ns_source = CocoaTools::StringViewToNSString(source); NSError* nserror = nil; id library = [m_device newLibraryWithSource:ns_source options:nil error:&nserror]; if (!library) @@ -705,7 +682,7 @@ std::unique_ptr MetalDevice::CreateShaderFromMSL(GPUShaderStage stage return {}; } - id function = [library newFunctionWithName:StringViewToNSString(entry_point)]; + id function = [library newFunctionWithName:CocoaTools::StringViewToNSString(entry_point)]; if (!function) { ERROR_LOG("Failed to get main function in compiled library"); @@ -989,7 +966,7 @@ std::unique_ptr MetalDevice::CreatePipeline(const GPUPipeline::Grap if (pipeline == nil) { LogNSError(nserror, "Failed to create render pipeline state"); - NSErrorToErrorObject(error, "newRenderPipelineStateWithDescriptor failed: ", nserror); + CocoaTools::NSErrorToErrorObject(error, "newRenderPipelineStateWithDescriptor failed: ", nserror); return {}; } } @@ -1164,7 +1141,7 @@ void MetalTexture::SetDebugName(std::string_view name) { @autoreleasepool { - [m_texture setLabel:StringViewToNSString(name)]; + [m_texture setLabel:CocoaTools::StringViewToNSString(name)]; } } @@ -1383,7 +1360,7 @@ void MetalDownloadTexture::SetDebugName(std::string_view name) { @autoreleasepool { - [m_buffer setLabel:StringViewToNSString(name)]; + [m_buffer setLabel:CocoaTools::StringViewToNSString(name)]; } } @@ -1806,7 +1783,7 @@ void MetalTextureBuffer::SetDebugName(std::string_view name) { @autoreleasepool { - [m_buffer.GetBuffer() setLabel:StringViewToNSString(name)]; + [m_buffer.GetBuffer() setLabel:CocoaTools::StringViewToNSString(name)]; } } @@ -2487,7 +2464,7 @@ void MetalDevice::EndPresent(bool explicit_present, u64 present_time) if (present_time != 0 && (current_time = Common::Timer::GetCurrentValue()) < present_time) { // Need to convert to mach absolute time. Time values should already be in nanoseconds. - const u64 mach_time_nanoseconds = ((mach_absolute_time() * s_timebase_info.numer) / s_timebase_info.denom); + const u64 mach_time_nanoseconds = CocoaTools::ConvertMachTimeBaseToNanoseconds(mach_absolute_time()); const double mach_present_time = static_cast(mach_time_nanoseconds + (present_time - current_time)) / 1e+9; [m_render_cmdbuf presentDrawable:m_layer_drawable atTime:mach_present_time]; }