diff --git a/src/SDL/MyOpenGLView.h b/src/SDL/MyOpenGLView.h index 7d83eca61..a0091013a 100644 --- a/src/SDL/MyOpenGLView.h +++ b/src/SDL/MyOpenGLView.h @@ -226,8 +226,6 @@ extern int debug; #if OOLITE_WINDOWS - BOOL wasFullScreen; - BOOL updateContext; BOOL saveSize; BOOL atDesktopResolution; unsigned keyboardMap; // *** FLAGGED for deletion @@ -296,7 +294,7 @@ extern int debug; #if OOLITE_WINDOWS - (BOOL) getCurrentMonitorInfo:(MONITORINFOEX *)mInfo; - (MONITORINFOEX) currentMonitorInfo; -- (void) refreshDarKOrLightMode; +- (void) refreshDarkOrLightMode; - (BOOL) isDarkModeOn; - (BOOL) atDesktopResolution; - (float) hdrMaxBrightness; diff --git a/src/SDL/MyOpenGLView.m b/src/SDL/MyOpenGLView.m index afa6ff9e8..d23315764 100644 --- a/src/SDL/MyOpenGLView.m +++ b/src/SDL/MyOpenGLView.m @@ -79,7 +79,6 @@ @interface MyOpenGLView (OOPrivate) - (void) resetSDLKeyModifiers; -- (void) setWindowBorderless:(BOOL)borderless; - (void) handleStringInput: (SDL_KeyboardEvent *) kbd_event keyID:(Uint16)key_id; // DJS @end @@ -189,7 +188,7 @@ - (void) createWindowWithSize: (NSSize) size } NSString *windowCaption = [self getWindowCaption]; - Uint32 windowFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY; + Uint32 windowFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_BORDERLESS | SDL_WINDOW_HIGH_PIXEL_DENSITY; // Define modern SDL3 properties for window configuration SDL_PropertiesID props = SDL_CreateProperties(); @@ -279,7 +278,7 @@ - (void) createWindowWithSize: (NSSize) size if (SetPreferredAppMode) SetPreferredAppMode(AllowDark); FreeLibrary(hUxTheme); } - [self refreshDarKOrLightMode]; + [self refreshDarkOrLightMode]; #endif #endif //OOLITE_WINDOWS @@ -451,7 +450,6 @@ - (id) init #if OOLITE_WINDOWS ShowWindow(windowHandle,SW_SHOWMINIMIZED); - updateContext = !showSplashScreen; #endif if (!showSplashScreen) { @@ -483,6 +481,8 @@ - (id) init - (void) endSplashScreen { + SDL_SetWindowBordered(window, true); + SDL_SetWindowResizable(window, true); #if OOLITE_WINDOWS // we need to get through here even if splash screen has not // been shown - this method also prepares the main game window @@ -497,8 +497,6 @@ - (void) endSplashScreen } } - wasFullScreen = !fullScreen; - updateContext = YES; #endif // OOLITE_WINDOWS if (!showSplashScreen) return; @@ -868,7 +866,6 @@ - (void) initSplashScreen dest.x = (GetSystemMetrics(SM_CXSCREEN)- dest.w)/2; dest.y = (GetSystemMetrics(SM_CYSCREEN)-dest.h)/2; - SetWindowLong(windowHandle,GWL_STYLE,GetWindowLong(windowHandle,GWL_STYLE) & ~WS_CAPTION & ~WS_THICKFRAME); ShowWindow(windowHandle,SW_RESTORE); MoveWindow(windowHandle,dest.x,dest.y,dest.w,dest.h,TRUE); #endif @@ -1099,29 +1096,7 @@ - (void) resetSDLKeyModifiers } -- (void) setWindowBorderless:(BOOL)borderless -{ - LONG currentWindowStyle = GetWindowLong(windowHandle, GWL_STYLE); - - // window already has the desired style? - if ((!borderless && (currentWindowStyle & WS_CAPTION)) || - (borderless && !(currentWindowStyle & WS_CAPTION))) return; - - if (borderless) - { - SetWindowLong(windowHandle, GWL_STYLE, currentWindowStyle & ~WS_CAPTION & ~WS_THICKFRAME); - } - else - { - SetWindowLong(windowHandle, GWL_STYLE, currentWindowStyle | - WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX ); - [self refreshDarKOrLightMode]; - } - SetWindowPos(windowHandle, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED); -} - - -- (void) refreshDarKOrLightMode +- (void) refreshDarkOrLightMode { int shouldSetDarkMode = [self isDarkModeOn]; DwmSetWindowAttribute (windowHandle, DWMWA_USE_IMMERSIVE_DARK_MODE, &shouldSetDarkMode, sizeof(shouldSetDarkMode)); @@ -1414,12 +1389,6 @@ - (void) resetSDLKeyModifiers } -- (void) setWindowBorderless:(BOOL)borderless -{ - // do nothing on Linux -} - - - (BOOL) hdrOutput { return NO; @@ -1456,204 +1425,101 @@ - (void) initialiseGLWithSize:(NSSize) v_size - (void) initialiseGLWithSize:(NSSize) v_size useVideoMode:(BOOL) v_mode { - if (!window) - { - [self createWindowWithSize: v_size]; - } - viewSize = v_size; - OOLog(@"display.initGL", @"Requested a new surface of %d x %d, %@.", (int)viewSize.width, (int)viewSize.height,(fullScreen ? @"fullscreen" : @"windowed")); - SDL_GL_SwapWindow(window); // clear the buffer before resize - + if (!window) + { + [self createWindowWithSize: v_size]; + } + + viewSize = v_size; + OOLog(@"display.initGL", @"Requested a new surface of %d x %d, %@.", + (int)viewSize.width, (int)viewSize.height, (fullScreen ? @"fullscreen" : @"windowed")); + + // 1. Handle Fullscreen vs. Windowed Mode in pure SDL3 + if (fullScreen) + { + SDL_DisplayID displayID = SDL_GetDisplayForWindow(window); + const SDL_DisplayMode *desktopMode = (displayID != 0) ? SDL_GetDesktopDisplayMode(displayID) : NULL; + + // Check if requested resolution differs from current desktop mode + BOOL isDifferentResolution = desktopMode && + (desktopMode->w != (int)viewSize.width || desktopMode->h != (int)viewSize.height); + + // Only switch hardware display modes if explicit video mode was requested + // AND the requested resolution is actually different from the desktop native resolution. + if (v_mode && isDifferentResolution) + { + SDL_DisplayMode closestMode; + + // In SDL3: pass width, height, refresh rate (0.0f = desktop rate), high-density flag, and target struct + if (displayID != 0 && SDL_GetClosestFullscreenDisplayMode(displayID, (int)viewSize.width, (int)viewSize.height, 0.0f, true, &closestMode)) + { + SDL_SetWindowFullscreenMode(window, &closestMode); + } + else + { + // Fallback to desktop mode if no exact mode was matched + SDL_SetWindowFullscreenMode(window, NULL); + } + } + else + { + // Desktop (Borderless) Fullscreen — instant, zero display handshake delay + SDL_SetWindowFullscreenMode(window, NULL); + } + + // Toggle Fullscreen ON (SDL3 uses a boolean true/false) + SDL_SetWindowFullscreen(window, true); + } + else + { + // Toggle Fullscreen OFF + SDL_SetWindowFullscreen(window, false); + SDL_SetWindowSize(window, (int)viewSize.width, (int)viewSize.height); #if OOLITE_WINDOWS - if (!updateContext) return; - - DEVMODE settings; - settings.dmSize = sizeof(DEVMODE); - settings.dmDriverExtra = 0; - EnumDisplaySettings(0, ENUM_CURRENT_SETTINGS, &settings); - - WINDOWPLACEMENT windowPlacement; - windowPlacement.length = sizeof(WINDOWPLACEMENT); - GetWindowPlacement(windowHandle, &windowPlacement); - - static BOOL lastWindowPlacementMaximized = NO; - if (fullScreen && (windowPlacement.showCmd == SW_SHOWMAXIMIZED)) - { - if (!wasFullScreen) - { - lastWindowPlacementMaximized = YES; - } - } - - if (lastWindowPlacementMaximized) - { - windowPlacement.showCmd = SW_SHOWMAXIMIZED; - } - - // are we attempting to go to a different screen resolution? Note: this also takes care of secondary monitor situations because - // by design the only resolution available for fullscreen on a secondary display device is its native one - Nikos 20150605 - BOOL changingResolution = [self isRunningOnPrimaryDisplayDevice] && - ((fullScreen && (settings.dmPelsWidth != viewSize.width || settings.dmPelsHeight != viewSize.height)) || - (wasFullScreen && (settings.dmPelsWidth != [[[screenSizes objectAtIndex:0] objectForKey: kOODisplayWidth] intValue] - || settings.dmPelsHeight != [[[screenSizes objectAtIndex:0] objectForKey: kOODisplayHeight] intValue]))); - - RECT wDC; - - if (fullScreen) - { - /*NOTE: If we ever decide to change the default behaviour of launching - always on primary monitor to launching on the monitor the program was - started on, all that needs to be done is comment out the line below, as - well as the identical one in the else branch further down. - Nikos 20141222 - */ - [self getCurrentMonitorInfo: &monitorInfo]; - - settings.dmPelsWidth = viewSize.width; - settings.dmPelsHeight = viewSize.height; - settings.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT; - - // just before going fullscreen, save the location of the current window. It - // may be needed in case of potential attempts to move our fullscreen window - // in a maximized state (yes, in Windows this is entirely possible). - if(lastWindowPlacementMaximized) - { - CopyRect(&lastGoodRect, &windowPlacement.rcNormalPosition); - // if maximized, switch to normal placement before going full screen - windowPlacement.showCmd = SW_SHOWNORMAL; - SetWindowPlacement(windowHandle, &windowPlacement); - } - else GetWindowRect(windowHandle, &lastGoodRect); - - // ok, can go fullscreen now - SetForegroundWindow(windowHandle); - if (changingResolution) - { - if (ChangeDisplaySettingsEx(monitorInfo.szDevice, &settings, NULL, CDS_FULLSCREEN, NULL) != DISP_CHANGE_SUCCESSFUL) - { - m_glContextInitialized = YES; - OOLogERR(@"displayMode.change.error", @"Could not switch to requested display mode."); - return; - } - atDesktopResolution = settings.dmPelsWidth == [[[screenSizes objectAtIndex:0] objectForKey: kOODisplayWidth] intValue] - && settings.dmPelsHeight == [[[screenSizes objectAtIndex:0] objectForKey: kOODisplayHeight] intValue]; - } - - MoveWindow(windowHandle, monitorInfo.rcMonitor.left, monitorInfo.rcMonitor.top, (int)viewSize.width, (int)viewSize.height, TRUE); - if(!wasFullScreen) - { - [self setWindowBorderless:YES]; - } - } - - else if ( wasFullScreen ) - { - if (changingResolution) - { - // restore original desktop resolution - if (ChangeDisplaySettingsEx(NULL, NULL, NULL, 0, NULL) == DISP_CHANGE_SUCCESSFUL) - { - atDesktopResolution = YES; - } - } - - /*NOTE: If we ever decide to change the default behaviour of launching - always on primary monitor to launching on the monitor the program was - started on, we need to comment out the line below. - For now, this line is needed for correct positioning of our window in case - we return from a non-native resolution fullscreen and has to come after the - display settings have been reverted. - Nikos 20141222 - */ - [self getCurrentMonitorInfo: &monitorInfo]; - - if (lastWindowPlacementMaximized) CopyRect(&windowPlacement.rcNormalPosition, &lastGoodRect); - SetWindowPlacement(windowHandle, &windowPlacement); - if (!lastWindowPlacementMaximized) - { - MoveWindow(windowHandle, (monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left - (int)viewSize.width)/2 + - monitorInfo.rcMonitor.left, - (monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top - (int)viewSize.height)/2 + - monitorInfo.rcMonitor.top, - (int)viewSize.width, (int)viewSize.height, TRUE); - } - - [self setWindowBorderless:NO]; - - lastWindowPlacementMaximized = NO; - ShowWindow(windowHandle,SW_SHOW); - } - - // stop saveWindowSize from reacting to caption & frame if necessary - saveSize = !wasFullScreen; - - GetClientRect(windowHandle, &wDC); - - if (!fullScreen && (bounds.size.width != wDC.right - wDC.left - || bounds.size.height != wDC.bottom - wDC.top)) - { - // Resize the game window if needed. When we ask for a W x H - // window, we intend that the client area be W x H. The actual - // window itself must become big enough to accomodate an area - // of such size. - if (wasFullScreen) // this is true when switching from full screen or when starting in windowed mode - //after the splash screen has ended - { - RECT desiredClientRect; - GetWindowRect(windowHandle, &desiredClientRect); - AdjustWindowRect(&desiredClientRect, WS_CAPTION | WS_THICKFRAME, FALSE); - SetWindowPos(windowHandle, NULL, desiredClientRect.left, desiredClientRect.top, - desiredClientRect.right - desiredClientRect.left, - desiredClientRect.bottom - desiredClientRect.top, 0); - } - GetClientRect(windowHandle, &wDC); - viewSize.width = wDC.right - wDC.left; - viewSize.height = wDC.bottom - wDC.top; - } + [self refreshDarkOrLightMode]; +#endif + } - // Reset bounds and viewSize to current values - bounds.size.width = viewSize.width = wDC.right - wDC.left; - bounds.size.height = viewSize.height = wDC.bottom - wDC.top; - - if (fullScreen) // bounds on fullscreen coincide with client area, since we are borderless - { - bounds.origin.x = monitorInfo.rcMonitor.left; - bounds.origin.y = monitorInfo.rcMonitor.top; - } - wasFullScreen=fullScreen; + // 2. Query Backing Framebuffer Dimensions (HiDPI Aware in SDL3) + int pixelWidth = 0, pixelHeight = 0; + SDL_GetWindowSizeInPixels(window, &pixelWidth, &pixelHeight); -#else //OOLITE_LINUX + bounds.size.width = (CGFloat)pixelWidth; + bounds.size.height = (CGFloat)pixelHeight; + viewSize = bounds.size; - SDL_SetWindowBordered(window, v_mode); - SDL_SetWindowFullscreen(window, fullScreen); - SDL_SetWindowSize(window, viewSize.width, viewSize.height); - SDL_Surface *surface = SDL_GetWindowSurface(window); - bounds.size.width = surface->w; - bounds.size.height = surface->h; + // Query window origin position + int winX = 0, winY = 0; + SDL_GetWindowPosition(window, &winX, &winY); + bounds.origin.x = (CGFloat)winX; + bounds.origin.y = (CGFloat)winY; -#endif - OOLog(@"display.initGL", @"Created a new surface of %d x %d, %@.", (int)viewSize.width, (int)viewSize.height,(fullScreen ? @"fullscreen" : @"windowed")); + OOLog(@"display.initGL", @"Created a new surface of %d x %d, %@.", + pixelWidth, pixelHeight, (fullScreen ? @"fullscreen" : @"windowed")); - if (viewSize.width/viewSize.height > 4.0/3.0) { - display_z = 480.0 * bounds.size.width/bounds.size.height; - x_offset = 240.0 * bounds.size.width/bounds.size.height; - y_offset = 240.0; - } else { - display_z = 640.0; - x_offset = 320.0; - y_offset = 320.0 * bounds.size.height/bounds.size.width; - } + // 3. Aspect Ratio & Projection Calculations + if (bounds.size.width / bounds.size.height > 4.0 / 3.0) + { + display_z = 480.0 * bounds.size.width / bounds.size.height; + x_offset = 240.0 * bounds.size.width / bounds.size.height; + y_offset = 240.0; + } + else + { + display_z = 640.0; + x_offset = 320.0; + y_offset = 320.0 * bounds.size.height / bounds.size.width; + } - [self autoShowMouse]; + [self autoShowMouse]; - int pixelWidth, pixelHeight; - SDL_GetWindowSizeInPixels(window, &pixelWidth, &pixelHeight); - NSSize pixelSize = NSMakeSize(pixelWidth, pixelHeight); - [[self gameController] setUpBasicOpenGLStateWithSize:pixelSize]; - SDL_GL_SwapWindow(window); - squareX = 0.0f; + // 4. Initialize OpenGL State & Viewport + NSSize pixelSize = NSMakeSize(pixelWidth, pixelHeight); + [[self gameController] setUpBasicOpenGLStateWithSize:pixelSize]; + SDL_GL_SwapWindow(window); + squareX = 0.0f; - m_glContextInitialized = YES; + m_glContextInitialized = YES; } @@ -2072,12 +1938,8 @@ - (void)pollControls Uint16 key_id; SDL_Scancode scan_code; float inDelta; -#if OOLITE_WINDOWS - DWORD dwLastError = 0; -#elif OOLITE_LINUX NSSize newSize; bool resize_pending = false; -#endif while (SDL_PollEvent(&event)) { @@ -2467,58 +2329,22 @@ reset the virtual joystick (mouse) coordinates, we need to send a WarpMouse call case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: { - SDL_WindowEvent *rsevt=(SDL_WindowEvent *)&event; -#if OOLITE_WINDOWS - NSSize newSize=NSMakeSize(rsevt->data1, rsevt->data2); - if (!fullScreen && updateContext) - { - - // 1. Tell SDL to update the window dimensions - SDL_SetWindowSize(window, newSize.width, newSize.height); - - // 2. Fetch actual pixel bounds back from SDL - int pixelWidth, pixelHeight; - SDL_GetWindowSizeInPixels(window, &pixelWidth, &pixelHeight); - bounds.size = NSMakeSize(pixelWidth, pixelHeight); - viewSize = bounds.size; - - // 3. Recalculate aspect-ratio-dependent projection offsets - if (bounds.size.width / bounds.size.height > 4.0 / 3.0) { - display_z = 480.0 * bounds.size.width / bounds.size.height; - x_offset = 240.0 * bounds.size.width / bounds.size.height; - y_offset = 240.0; - } else { - display_z = 640.0; - x_offset = 320.0; - y_offset = 320.0 * bounds.size.height / bounds.size.width; - } - - // 4. Update OpenGL Viewport and Projection Matrix - float ratio = 0.5; - float aspect = bounds.size.height / bounds.size.width; - - OOGL(glViewport(0, 0, bounds.size.width, bounds.size.height)); - OOGLResetProjection(); - OOGLFrustum(-ratio, ratio, -aspect * ratio, aspect * ratio, 1.0, MAX_CLEAR_DEPTH); - - // 5. Save the updated window size - [self saveWindowSize: newSize]; - } -#else - newSize=NSMakeSize(rsevt->data1, rsevt->data2); + newSize=NSMakeSize(event.window.data1, event.window.data2); resize_pending = true; -#endif - // certain gui screens will require an immediate redraw after - // a resize event - Nikos 20140129 break; } #if OOLITE_WINDOWS + case SDL_EVENT_WINDOW_MINIMIZED: + { + if (fullScreen) + { + [self refreshDarkOrLightMode]; + } + break; + } case SDL_EVENT_WINDOW_MOVED: { - // it is important that this gets done after we've dealt with possible fullscreen movements, - // because -doGuiScreenResizeUpdates does itself an update on current monitor - if(grabMouseStatus) [self grabMouseInsideGameWindow:YES]; break; } @@ -2550,14 +2376,35 @@ reset the virtual joystick (mouse) coordinates, we need to send a WarpMouse call { _mouseWheelDelta = 0.0f; } -#if OOLITE_LINUX - if (resize_pending) + if (resize_pending) { - [self initialiseGLWithSize: newSize]; - [self saveWindowSize: newSize]; + if (!fullScreen) + { + int pixelWidth, pixelHeight; // Fetch actual pixel bounds back from SDL + SDL_GetWindowSizeInPixels(window, &pixelWidth, &pixelHeight); + bounds.size = NSMakeSize(pixelWidth, pixelHeight); + viewSize = bounds.size; + + if (bounds.size.width / bounds.size.height > 4.0 / 3.0) { // Recalculate aspect-ratio-dependent + display_z = 480.0 * bounds.size.width / bounds.size.height; // projection offsets + x_offset = 240.0 * bounds.size.width / bounds.size.height; + y_offset = 240.0; + } else { + display_z = 640.0; + x_offset = 320.0; + y_offset = 320.0 * bounds.size.height / bounds.size.width; + } + + float ratio = 0.5; // Update OpenGL Viewport and Projection Matrix + float aspect = bounds.size.height / bounds.size.width; + OOGL(glViewport(0, 0, bounds.size.width, bounds.size.height)); + OOGLResetProjection(); + OOGLFrustum(-ratio, ratio, -aspect * ratio, aspect * ratio, 1.0, MAX_CLEAR_DEPTH); + + [self saveWindowSize: newSize]; // Save the updated window size + } resize_pending = false; } -#endif }