openframeworks / openFrameworks

openFrameworks is a community-developed cross platform toolkit for creative coding in C++.
http://openframeworks.cc
Other
9.98k stars 2.55k forks source link

[feature request] [macOS] native fullscreen behaviour #7762

Open artificiel opened 1 year ago

artificiel commented 1 year ago

in order to pull maximum efficiency of macOS14+ game mode, "transparently" call [cocoaWindow toggleFullScreen:nil] on the window. (the proposed custom fullscreen would be exempted from this feature in order to escape from the macOS fullscreen limits).

in addition to enabling game mode, this has the advantage of maintaining 1:1 with the native fullscreen triggers, which are:

this means some care is needed to make sure the GLFW callbacks and cocao native full screen state are properly interpreted so OF "knows" it has been made fullscreen.

because GLFW is buggy only since macOS13 and game mode only available since macOS14+, and because perhaps some user apps have been tailored to specific expectations that are hard to know about, maybe not change the behaviour on previous macOS versions:

if (@available(macOS 13.0, *)) {
      // call native full screen
      //  - macOS13: to workaround GLFW sync bug
      //  - macOS14+: as above + game mode
} else {
      // call OF full screen
}

additional help function:

// \brief checks if OF is running in a native fullscreen window 
// returns true if the native fullscreen bit is set
bool ofGetNativeFullScreenState() const

because the implementation is in a critical area of OF it might be pertinent to perform and test in parallel the different "fullscreen" issues? such as https://github.com/openframeworks/openFrameworks/issues/5158


note for users:

the Mission Control / Spaces Settings determines the behaviour of windows within a multi-monitor setup.

In other words:

so if you want to span across monitors you are outside native macOS native fullscreen territory and you need to use "no distinct spaces" and ofSetWindowPosiiton/Shape with ofGLFWWindowSettings.decorated = false or a "custom rect" flavor of ofFullScreen(and until the vsync bug in GLFW is fixed or otherwise properly addressed, presumably fallback to something like ofGetExpectedPaintTimef if you need to draw time-dependent things accurately).

in all cases, as of macOS14, a single-monitor native fullscreen window plus the "game" app type in info.plist is required for game mode to kick in.

dimitre commented 1 year ago

Maybe I should open a separate issue / feature request I was wondering a way of handling new windows without instancing new ofApp for each window, because most of the time I'll be only sharing a texture in fullscreen windows. I even tried to make a new window from scratch in GLFW or try different oldschool fullscreen addons for macOS. My personal guess is ofAppGLFWWindow grew strange over time (old code for old platforms) and has some hacky solutions that maybe not needed anymore, like nFramesSinceWindowResized < 3 but it is really hard to test things in all platforms. One thing I would start testing if I had more time in previous project, was testing multiple windows as a separate functionality, like an addon or a modified OF Core to test and prototyping ignoring ofAppGLFWWindow heritage.

artificiel commented 1 year ago

I think what you describe is parallel to the native fullscreen option.

currently with your "lightweight" windows, what happens if you fullscreen them with the window bar green button? and what macOS version are you running? and are you running with "distinct spaces" enabled?

ofTheo commented 1 year ago

I was wondering a way of handling new windows without instancing new ofApp for each window

@dimitre - def a separate issue and have solution for you - we use a similar pattern 🙂

dimitre commented 1 year ago

@artificiel Im always disabling displays have separate spaces and using macOS 13.6 now. I never use green button but if I do they animate to position (I personally don't use much fullscreen in macos, I think the animations are dizzy and have flickering in menus, they feel clumsy here using a second display).

but I like very much the framerate consistency improvements you seen in your tests. Maybe it explain some difficulties I had over the years like: a consistent stroboscopic effect using ofGetFrameNum(), a consistent bpm master clock without using timing from audio buffer, and maybe even the ofGetFrameRate stuttering visible when we draw actual frameRate on screen. (much more pronounced on macOS than windows or linux)

danomatika commented 1 year ago

I am all for the overall improvements. What should not be lost, IMO, is the flexibility of the current method for dealing with fullscreen + multiple monitors.

Also, I would say there isn't really a need for a special extra function. I would suggest rather adding an enum or set of enums to change default behavior of the existing ofFullscreen(), maybe C-flag winds modification flash which can be or-ed together? Then it's easier to add addtional options in the future for platform-specific stuff.

ofTheo commented 1 year ago

@dimitre sort of off topic, but typically what we do is use ofApp for all windows and just have an extra draw function and sometimes keypress function for the render window.

ie in ofApp::setup


    // setup the render window  //
    renderWin = ofCreateWindow(rsettings);
    if (renderWin) {
        ofAddListener(renderWin->events().draw, this, &ofApp::onRenderWindowDraw);

        ofAddListener(renderWin->events().keyPressed, this, &ofApp::onRenderWindowKeyPressed);
        ofAddListener(renderWin->events().keyReleased, this, &ofApp::onRenderWindowKeyReleased);
    }

This is somewhat simplified but I think you could have an unlimited number of windows using the onRenderWindowDraw you would just want to make sure you remove the listener when the window is closed.

artificiel commented 1 year ago

@dimitre you say:

and maybe even the ofGetFrameRate stuttering visible when we draw actual frameRate on screen. (much more pronounced on macOS than windows or linux)

yes this is a definite GLFW problem since macOS13 (somehow circumvented by macOS14 game mode). and notwithstanding that specific bug, it is what I'm proposing to fix with https://github.com/openframeworks/openFrameworks/issues/7758 which is a useful cross-platform feature outside the context of the bug as it would make the calculations of phase more deterministic (whereas the regularity ofGetElapsedTimef() is subject to how update/draw gets scheduled/prioritized).

and:

I think the animations are dizzy and have flickering in menus, they feel clumsy here using a second display

there are many ways to customize the Spaces-based full screen UX (as Apple calls it "Presentation"), including providing the animation. however that gets more involved in native code, but maybe it's reasonable to design things so the transition is "instantaneous"? I personally do not care about the animation one way or the other, just the net result of enabling game mode.

@danomatika you mention:

there isn't really a need for a special extra function

which extra function are you referring to? the ofGetNativeFullScreenState() to retrieve the state of native fullscreen? it has been quite helpful in disentangling the issues between GLFW sync bug and fullscreen native vs not native behaviour.

as for:

What should not be lost, IMO, is the flexibility of the current method for dealing with fullscreen + multiple monitors.

exactly, and that is why I'm suggesting an even more flexible approach with #7759 which is unobtrusive and useful (and opens the possibilities for one to build whatever logic they need about their monitor setup geometry without burdening OF with that complexity). as given as an example in the issue:

auto monitors =  ofGetConnectedMonitorsRects();
auto r = monitors[1]; // control monitor presumably at index 0
for (size_t i = 2; i < monitors.size(); i++) r.growToInclude(monitors[i]);
ofSetFullScreen(r); // fullscreens on all but first monitor

of course that's unsafe code and needs bounds checks (will crash if you don't have 2 connected monitors) but it achieves @ofTheo's spec of having a control monitor + an OF window spanned fullscreen across N additional monitors, without knowing in advance their dimensions. this is currently not possible with ofSetFullscreen() nor ofGLFWWindowSettings::multiMonitorFullScreen. but that's cross-platform and disconnected from the issue of macOS fullscreen as it's not a scenario that the macOS fullscreen space-based presentations supports.