Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display Helpers #1566

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Sources/Plasma/Apps/plClient/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ if(WIN32)
elseif(APPLE)
list(APPEND plClient_SOURCES
Mac-Cocoa/main.mm
Mac-Cocoa/plMacDisplayHelper.mm
Mac-Cocoa/NSString+StringTheory.mm
Mac-Cocoa/PLSKeyboardEventMonitor.mm
Mac-Cocoa/PLSView.mm
Expand All @@ -102,6 +103,7 @@ elseif(APPLE)
)
list(APPEND plClient_HEADERS
Mac-Cocoa/NSString+StringTheory.h
Mac-Cocoa/plMacDisplayHelper.h
Mac-Cocoa/PLSKeyboardEventMonitor.h
Mac-Cocoa/PLSView.h
Mac-Cocoa/PLSLoginWindowController.h
Expand Down
18 changes: 16 additions & 2 deletions Sources/Plasma/Apps/plClient/Mac-Cocoa/main.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -105,6 +106,7 @@ @interface AppDelegate : NSWindowController <NSApplicationDelegate,
@public
plClientLoader gClient;
dispatch_source_t _displaySource;
std::shared_ptr<plMacDisplayHelper> _displayHelper;
}

@property(retain) PLSKeyboardEventMonitor* eventMonitor;
Expand All @@ -116,6 +118,7 @@ @interface AppDelegate : NSWindowController <NSApplicationDelegate,
@property NSModalSession currentModalSession;
@property PLSPatcher* patcher;
@property PLSLoginWindowController* loginWindow;
@property NSWindow* gameWindow;

@end

Expand Down Expand Up @@ -194,10 +197,14 @@ - (id)init
PLSView* view = [[PLSView alloc] init];
self.plsView = view;
window.contentView = view;
[window setDelegate:self];
self.gameWindow = window;

_displayHelper = std::make_shared<plMacDisplayHelper>();
_displayHelper->SetCurrentScreen([window screen]);
_displayHelper->MakeCurrentDisplayHelper();

gClient.SetClientWindow((__bridge void *)view.layer);
gClient.SetClientDisplay((hsWindowHndl)NULL);
gClient.SetClientDisplay((__bridge hsWindowHndl)window.screen);

self = [super initWithWindow:window];
self.window.acceptsMouseMovedEvents = YES;
Expand Down Expand Up @@ -481,6 +488,8 @@ - (void)loginWindowControllerDidLogin:(PLSLoginWindowController*)sender
- (void)startClient
{
PF_CONSOLE_INITIALIZE(Audio)

[self.gameWindow setDelegate:self];

self.plsView.delegate = self;
// Create a window:
Expand Down Expand Up @@ -574,6 +583,11 @@ - (NSApplicationPresentationOptions)window:(NSWindow*)window
NSApplicationPresentationAutoHideMenuBar;
}

- (void)windowDidChangeScreen:(NSNotification *)notification
{
_displayHelper->SetCurrentScreen(self.window.screen);
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (context == DeviceDidChangeContext) {
Expand Down
79 changes: 79 additions & 0 deletions Sources/Plasma/Apps/plClient/Mac-Cocoa/plMacDisplayHelper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*==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 <http://www.gnu.org/licenses/>.

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 [email protected]
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021

*==LICENSE==*/

#ifndef plMacDisplayHelper_hpp
#define plMacDisplayHelper_hpp

#include <stdio.h>
// Currently requires Metal to query attached GPU capabilities
// Capability check will also work for GL - but will need something
// different for older GPUs.
#include <AppKit/AppKit.h>
#include <Metal/Metal.hpp>
#include <QuartzCore/QuartzCore.h>

#include "plPipeline/hsG3DDeviceSelector.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<plDisplayMode> *res, int ColorDepth = 32) const override;
std::vector<plDisplayMode> GetDisplayModes() const override { return fDisplayModes; }

MTL::Device* RenderDevice() { return fRenderDevice; }

plMacDisplayHelper();
plMacDisplayHelper(hsWindowHndl window);
~plMacDisplayHelper();
private:
CGDirectDisplayID fCurrentDisplay;
MTL::Device* fRenderDevice = nullptr;
plDisplayMode fDefaultDisplayMode;
std::vector<plDisplayMode> fDisplayModes;
};

#endif /* plMacDisplayHelper_hpp */
157 changes: 157 additions & 0 deletions Sources/Plasma/Apps/plClient/Mac-Cocoa/plMacDisplayHelper.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/*==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 <http://www.gnu.org/licenses/>.

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 [email protected]
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021

*==LICENSE==*/

#include "plPipeline.h"
#include "plMacDisplayHelper.h"

// CGDirectDisplayCopyCurrentMetalDevice only declared in Metal.h?
#include <Metal/Metal.h>

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);
Comment on lines +84 to +90
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably use emplace_back() to construct in place (avoids copying).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at this - we'd need to define a constructor on plDisplayMode. There is an alternative - but I'm not sure it avoids a copy.

// aspect ratio is good - add the mode to the list
        fDisplayModes.emplace_back() = {
            int(CGDisplayModeGetWidth(mode)),
            int(CGDisplayModeGetHeight(mode)),
            32
        };

FYI - C++20 emplace_back can initialize structs directly without a constructor.

}
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<plDisplayMode> *res, int ColorDepth) const
{
*res = fDisplayModes;
}

plMacDisplayHelper::plMacDisplayHelper()
{

};

plMacDisplayHelper::plMacDisplayHelper(hsWindowHndl window)
: plMacDisplayHelper()
{
NSWindow* nsWindow = (__bridge NSWindow*)(window);
SetCurrentScreen(nsWindow.screen);
}

plMacDisplayHelper::~plMacDisplayHelper()
{
fRenderDevice->release();
}
14 changes: 8 additions & 6 deletions Sources/Plasma/FeatureLib/pfMetalPipeline/plMetalEnumerate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,14 @@ void plMetalEnumerate::Enumerate(std::vector<hsG3DDeviceRecord>& records)

devRec.SetLayersAtOnce(8);

// Just make a fake mode so the device selector will let it through
hsG3DDeviceMode devMode;
devMode.SetWidth(hsG3DDeviceSelector::kDefaultWidth);
devMode.SetHeight(hsG3DDeviceSelector::kDefaultHeight);
devMode.SetColorDepth(hsG3DDeviceSelector::kDefaultDepth);
devRec.GetModes().emplace_back(devMode);
const plDisplayHelper* displayHelper = plDisplayHelper::CurrentDisplayHelper();
for (const auto& mode : displayHelper->GetDisplayModes()) {
hsG3DDeviceMode devMode;
devMode.SetWidth(mode.Width);
devMode.SetHeight(mode.Height);
devMode.SetColorDepth(mode.ColorDepth);
devRec.GetModes().emplace_back(devMode);
}

records.emplace_back(devRec);
}
Expand Down
30 changes: 1 addition & 29 deletions Sources/Plasma/FeatureLib/pfMetalPipeline/plMetalPipeline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -970,35 +970,7 @@ plMipmap* plMetalPipeline::ExtractMipMap(plRenderTarget* targ)

void plMetalPipeline::GetSupportedDisplayModes(std::vector<plDisplayMode>* 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<plDisplayMode> 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;
plDisplayHelper::CurrentDisplayHelper()->GetSupportedDisplayModes(res);
}

int plMetalPipeline::GetMaxAnisotropicSamples()
Expand Down
Loading
Loading