diff --git a/Sources/Plasma/Apps/plClient/Mac-Cocoa/main.mm b/Sources/Plasma/Apps/plClient/Mac-Cocoa/main.mm index 0101728f20..078f03d6f0 100644 --- a/Sources/Plasma/Apps/plClient/Mac-Cocoa/main.mm +++ b/Sources/Plasma/Apps/plClient/Mac-Cocoa/main.mm @@ -73,6 +73,7 @@ #include "pfGLPipeline/plGLPipeline.h" #endif #include "plInputCore/plInputDevice.h" +#include "plMacDisplayHelper.h" #ifdef PLASMA_PIPELINE_METAL #include "pfMetalPipeline/plMetalPipeline.h" #endif @@ -105,6 +106,7 @@ @interface AppDelegate : NSWindowController _displayHelper; } @property(retain) PLSKeyboardEventMonitor* eventMonitor; @@ -196,6 +198,9 @@ - (id)init window.contentView = view; [window setDelegate:self]; + _displayHelper = std::make_shared(); + _displayHelper->SetCurrentScreen([window screen]); + gClient.SetClientWindow((__bridge void *)view.layer); gClient.SetClientDisplay((hsWindowHndl)NULL); @@ -231,6 +236,7 @@ - (void)startRunLoop object:self.window queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification* _Nonnull note) { + _displayHelper->SetCurrentScreen(self.window.screen); // if we change displays, setup a new draw loop. The new display might // have a different or variable refresh rate. [self setupRunLoop]; @@ -390,6 +396,8 @@ - (void)initializeClient while (!gClient.IsInited()) { [NSRunLoop.mainRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; } + + gClient->GetPipeline()->SetDisplayHelper(_displayHelper); if (!gClient || gClient->GetDone()) { [NSApp terminate:self]; diff --git a/Sources/Plasma/Apps/plClient/Mac-Cocoa/plMacDisplayHelper.h b/Sources/Plasma/Apps/plClient/Mac-Cocoa/plMacDisplayHelper.h new file mode 100644 index 0000000000..e451adf94b --- /dev/null +++ b/Sources/Plasma/Apps/plClient/Mac-Cocoa/plMacDisplayHelper.h @@ -0,0 +1,77 @@ +/*==LICENSE==* + +CyanWorlds.com Engine - MMOG client, server and tools +Copyright (C) 2011 Cyan Worlds, Inc. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +Additional permissions under GNU GPL version 3 section 7 + +If you modify this Program, or any covered work, by linking or +combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, +NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent +JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK +(or a modified version of those libraries), +containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, +PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG +JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the +licensors of this Program grant you additional +permission to convey the resulting work. Corresponding Source for a +non-source form of such a combination shall include the source code for +the parts of OpenSSL and IJG JPEG Library used as well as that of the covered +work. + +You can contact Cyan Worlds, Inc. by email legal@cyan.com + or by snail mail at: + Cyan Worlds, Inc. + 14617 N Newport Hwy + Mead, WA 99021 + +*==LICENSE==*/ + +#ifndef plMacDisplayHelper_hpp +#define plMacDisplayHelper_hpp + +#include +// Currently requires Metal to query attached GPU capabilities +// Capability check will also work for GL - but will need something +// different for older GPUs. +#include +#include +#include + +#include "plPipeline.h" + +class plMacDisplayHelper: public plDisplayHelper +{ +public: + CGDirectDisplayID CurrentDisplay() { return fCurrentDisplay; } + // we need NSScreen to query for non rectangular screen geometry + void SetCurrentScreen(NSScreen* screen); + + plDisplayMode DefaultDisplayMode() override { return fDefaultDisplayMode; }; + void GetSupportedDisplayModes(std::vector *res, int ColorDepth = 32) override; + + MTL::Device* RenderDevice() { return fRenderDevice; } + + plMacDisplayHelper(); + ~plMacDisplayHelper(); +private: + CGDirectDisplayID fCurrentDisplay; + MTL::Device* fRenderDevice = nullptr; + plDisplayMode fDefaultDisplayMode; + std::vector fDisplayModes; +}; + +#endif /* plMacDisplayHelper_hpp */ diff --git a/Sources/Plasma/Apps/plClient/Mac-Cocoa/plMacDisplayHelper.mm b/Sources/Plasma/Apps/plClient/Mac-Cocoa/plMacDisplayHelper.mm new file mode 100644 index 0000000000..880774115d --- /dev/null +++ b/Sources/Plasma/Apps/plClient/Mac-Cocoa/plMacDisplayHelper.mm @@ -0,0 +1,149 @@ +/*==LICENSE==* + +CyanWorlds.com Engine - MMOG client, server and tools +Copyright (C) 2011 Cyan Worlds, Inc. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +Additional permissions under GNU GPL version 3 section 7 + +If you modify this Program, or any covered work, by linking or +combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, +NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent +JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK +(or a modified version of those libraries), +containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, +PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG +JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the +licensors of this Program grant you additional +permission to convey the resulting work. Corresponding Source for a +non-source form of such a combination shall include the source code for +the parts of OpenSSL and IJG JPEG Library used as well as that of the covered +work. + +You can contact Cyan Worlds, Inc. by email legal@cyan.com + or by snail mail at: + Cyan Worlds, Inc. + 14617 N Newport Hwy + Mead, WA 99021 + +*==LICENSE==*/ + +#include "plMacDisplayHelper.h" + +// CGDirectDisplayCopyCurrentMetalDevice only declared in Metal.h? +#include + +void plMacDisplayHelper::SetCurrentScreen(NSScreen* screen) +{ + fCurrentDisplay = [screen.deviceDescription[@"NSScreenNumber"] intValue]; + + // Calculate the region actually available for full screen + NSRect currentResolution = [screen frame]; + if (@available(macOS 12.0, *)) { + NSEdgeInsets currentSafeAreaInsets = [screen safeAreaInsets]; + // Sigh... Origin doesn't matter but lets do it for inspectability + currentResolution.origin.x += currentSafeAreaInsets.left; + currentResolution.origin.y += currentSafeAreaInsets.top; + currentResolution.size.width -= currentSafeAreaInsets.left + currentSafeAreaInsets.right; + currentResolution.size.height -= currentSafeAreaInsets.top + currentSafeAreaInsets.bottom; + } + + float safeAspectRatio = currentResolution.size.width / currentResolution.size.height; + + fDisplayModes.clear(); + + CFArrayRef displayModes = CGDisplayCopyAllDisplayModes(fCurrentDisplay, nullptr); + for(int i=0; i< CFArrayGetCount(displayModes); i++) + { + // Now filter out the ones that are taller than the safe area aspect ratio + // This could break in interesting ways if Apple ships displays that have unsafe + // areas along the side - but will prevent us stripping any aspect ratios that don't + // match the display entirely. + // Asked for better guidance here from Apple - FB13375033 + CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(displayModes, i); + + float modeAspectRatio = float(CGDisplayModeGetWidth(mode)) / float(CGDisplayModeGetHeight(mode)); + if(modeAspectRatio < safeAspectRatio) + { + continue; + } + + // aspect ratio is good - add the mode to the list + plDisplayMode plasmaMode; + plasmaMode.ColorDepth = 32; + plasmaMode.Width = int(CGDisplayModeGetWidth(mode)); + plasmaMode.Height = int(CGDisplayModeGetHeight(mode)); + + fDisplayModes.push_back(plasmaMode); + } + CFRelease(displayModes); + + // we're going to look for a good default mode, but + // in case we somehow don't match at least pick one. + + fDefaultDisplayMode = fDisplayModes.front(); + + fRenderDevice->release(); + fRenderDevice = (__bridge MTL::Device *)(CGDirectDisplayCopyCurrentMetalDevice(fCurrentDisplay)); + + // now inspect the GPU and figure out a good default resolution + // This code is in Metal (for now) - but it should also work + // under OpenGL/Mac as long as the GPU also supports Metal. + if(fRenderDevice->supportsFamily(MTL::GPUFamilyMetal3)) + { + // if it's a Metal 3 GPU - it should be good + // Pick the native display resolution + // (Re-picking the first one here for clarity) + fDefaultDisplayMode = fDisplayModes.front(); + } + else + { + // time to go down the rabit hole + int maxWidth = INT_MAX; + if(fRenderDevice->lowPower()) + { + // integrated - not Apple Silicon, we know it's not very good + maxWidth = 1080; + } + else if(fRenderDevice->recommendedMaxWorkingSetSize() < 4000000000) + { + // if it has less than around 4 gigs of VRAM it might still be performance + // limited + maxWidth = 1400; + } + + for (auto & mode : fDisplayModes) { + if(mode.Width <= maxWidth) { + fDefaultDisplayMode = mode; + abort(); + } + } + } +} + +void plMacDisplayHelper::GetSupportedDisplayModes(std::vector *res, int ColorDepth) +{ + *res = fDisplayModes; +} + +plMacDisplayHelper::plMacDisplayHelper() +{ + +}; + +plMacDisplayHelper::~plMacDisplayHelper() +{ + fRenderDevice->release(); +} diff --git a/Sources/Plasma/FeatureLib/pfMetalPipeline/plMetalPipeline.cpp b/Sources/Plasma/FeatureLib/pfMetalPipeline/plMetalPipeline.cpp index 85f527ede2..f2647d2498 100644 --- a/Sources/Plasma/FeatureLib/pfMetalPipeline/plMetalPipeline.cpp +++ b/Sources/Plasma/FeatureLib/pfMetalPipeline/plMetalPipeline.cpp @@ -970,35 +970,7 @@ plMipmap* plMetalPipeline::ExtractMipMap(plRenderTarget* targ) void plMetalPipeline::GetSupportedDisplayModes(std::vector* res, int ColorDepth) { - /* - There are decisions to make here. - - Modern macOS does not support "display modes." You panel runs at native resolution at all times, - and you can over-render or under-render. But you never set the display mode of the panel, or get - the display mode of the panel. Most games have a "scale slider." - - Note: There are legacy APIs for display modes for compatibility with older software. In since - we're here writing a new renderer, lets do things the right way. The display mode APIs also have - trouble with density. I.E. a 4k display might be reported as a 2k display if the window manager is - running in a higher DPI mode. - - The basic approach should be to render at whatever the resolution of our output surface is. We're - mostly doing that now (aspect ratio doesn't adjust.) - - Ideally we should support some sort of scaling/semi dynamic renderbuffer resolution thing. But don't - mess with the window servers framebuffer size. macOS has accelerated resolution scaling like consoles - do. Use that. - */ - - std::vector supported; - CA::MetalLayer* layer = fDevice.GetOutputLayer(); - CGSize drawableSize = layer->drawableSize(); - supported.emplace_back(); - supported[0].Width = drawableSize.width; - supported[0].Height = drawableSize.height; - supported[0].ColorDepth = 32; - - *res = supported; + fDisplayHelper->GetSupportedDisplayModes(res); } int plMetalPipeline::GetMaxAnisotropicSamples() diff --git a/Sources/Plasma/NucleusLib/inc/plPipeline.h b/Sources/Plasma/NucleusLib/inc/plPipeline.h index 6b6f463ee5..de35e8cf67 100644 --- a/Sources/Plasma/NucleusLib/inc/plPipeline.h +++ b/Sources/Plasma/NucleusLib/inc/plPipeline.h @@ -138,6 +138,16 @@ class plDisplayMode int ColorDepth; }; +class plDisplayHelper +{ +public: + virtual plDisplayMode DefaultDisplayMode() = 0; + virtual void GetSupportedDisplayModes(std::vector *res, int ColorDepth = 32) = 0; + + plDisplayHelper() = default; + virtual ~plDisplayHelper() = default; +}; + class plPipeline : public plCreatable { public: @@ -357,6 +367,9 @@ class plPipeline : public plCreatable float fBackingScale = 1.0f; void SetBackingScale(float scale) { fBackingScale = scale; }; + + std::shared_ptr fDisplayHelper; + void SetDisplayHelper(std::shared_ptr helper) { fDisplayHelper = helper; }; }; #endif // plPipeline_inc