diff --git a/OpenRA.Game/Graphics/PlatformInterfaces.cs b/OpenRA.Game/Graphics/PlatformInterfaces.cs index 4898ec553894..c266b3ae7b33 100644 --- a/OpenRA.Game/Graphics/PlatformInterfaces.cs +++ b/OpenRA.Game/Graphics/PlatformInterfaces.cs @@ -17,7 +17,7 @@ namespace OpenRA { public interface IPlatform { - IPlatformWindow CreateWindow(Size size, WindowMode windowMode, float scaleModifier, int batchSize); + IPlatformWindow CreateWindow(Size size, WindowMode windowMode, float scaleModifier, int batchSize, int videoDisplay); ISoundEngine CreateSound(string device); IFont CreateFont(byte[] data); } @@ -44,6 +44,8 @@ public interface IPlatformWindow : IDisposable float NativeWindowScale { get; } float EffectiveWindowScale { get; } Size SurfaceSize { get; } + int DisplayCount { get; } + int CurrentDisplay { get; } event Action OnWindowScaleChanged; diff --git a/OpenRA.Game/Renderer.cs b/OpenRA.Game/Renderer.cs index 43722b0e1424..bf26034bc35b 100644 --- a/OpenRA.Game/Renderer.cs +++ b/OpenRA.Game/Renderer.cs @@ -66,7 +66,7 @@ public Renderer(IPlatform platform, GraphicSettings graphicSettings) this.platform = platform; var resolution = GetResolution(graphicSettings); - Window = platform.CreateWindow(new Size(resolution.Width, resolution.Height), graphicSettings.Mode, graphicSettings.UIScale, graphicSettings.BatchSize); + Window = platform.CreateWindow(new Size(resolution.Width, resolution.Height), graphicSettings.Mode, graphicSettings.UIScale, graphicSettings.BatchSize, graphicSettings.VideoDisplay); Context = Window.Context; TempBufferSize = graphicSettings.BatchSize; @@ -478,5 +478,15 @@ public IFont CreateFont(byte[] data) { return platform.CreateFont(data); } + + public int DisplayCount + { + get { return Window.DisplayCount; } + } + + public int CurrentDisplay + { + get { return Window.CurrentDisplay; } + } } } diff --git a/OpenRA.Game/Settings.cs b/OpenRA.Game/Settings.cs index 2071f0d11e42..376a981cf436 100644 --- a/OpenRA.Game/Settings.cs +++ b/OpenRA.Game/Settings.cs @@ -176,6 +176,9 @@ public class GraphicSettings [Desc("Use OpenGL ES if both ES and regular OpenGL are available.")] public bool PreferGLES = false; + [Desc("Display index to use in a multi-monitor fullscreen setup.")] + public int VideoDisplay = 0; + public int BatchSize = 8192; public int SheetSize = 2048; diff --git a/OpenRA.Mods.Common/LoadScreens/BlankLoadScreen.cs b/OpenRA.Mods.Common/LoadScreens/BlankLoadScreen.cs index 139121ad6c7c..7bdb8eecd71b 100644 --- a/OpenRA.Mods.Common/LoadScreens/BlankLoadScreen.cs +++ b/OpenRA.Mods.Common/LoadScreens/BlankLoadScreen.cs @@ -121,6 +121,9 @@ public virtual bool BeforeLoad() Game.Renderer.SetUIScale(1.0f); } + // Saved settings may have been invalidated by a hardware change + Game.Settings.Graphics.VideoDisplay = Game.Renderer.CurrentDisplay; + // If a ModContent section is defined then we need to make sure that the // required content is installed or switch to the defined content installer. if (!ModData.Manifest.Contains()) diff --git a/OpenRA.Mods.Common/Widgets/Logic/SettingsLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/SettingsLogic.cs index 825fd06e8c24..f7b26ea84048 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/SettingsLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/SettingsLogic.cs @@ -23,6 +23,7 @@ public class SettingsLogic : ChromeLogic { enum PanelType { Display, Audio, Input, Hotkeys, Advanced } + static readonly int OriginalVideoDisplay; static readonly string OriginalSoundDevice; static readonly WindowMode OriginalGraphicsMode; static readonly int2 OriginalGraphicsWindowedSize; @@ -52,6 +53,7 @@ static SettingsLogic() var original = Game.Settings; OriginalSoundDevice = original.Sound.Device; OriginalGraphicsMode = original.Graphics.Mode; + OriginalVideoDisplay = original.Graphics.VideoDisplay; OriginalGraphicsWindowedSize = original.Graphics.WindowedSize; OriginalGraphicsFullscreenSize = original.Graphics.FullscreenSize; OriginalServerDiscoverNatDevices = original.Server.DiscoverNatDevices; @@ -92,6 +94,7 @@ public SettingsLogic(Widget widget, Action onExit, ModData modData, WorldRendere Action closeAndExit = () => { Ui.CloseWindow(); onExit(); }; if (current.Sound.Device != OriginalSoundDevice || current.Graphics.Mode != OriginalGraphicsMode || + current.Graphics.VideoDisplay != OriginalVideoDisplay || current.Graphics.WindowedSize != OriginalGraphicsWindowedSize || current.Graphics.FullscreenSize != OriginalGraphicsFullscreenSize || current.Server.DiscoverNatDevices != OriginalServerDiscoverNatDevices) @@ -250,7 +253,14 @@ Action InitDisplayPanel(Widget panel) "Windowed" : ds.Mode == WindowMode.Fullscreen ? "Fullscreen (Legacy)" : "Fullscreen"; var modeChangesDesc = panel.Get("MODE_CHANGES_DESC"); - modeChangesDesc.IsVisible = () => ds.Mode != WindowMode.Windowed && ds.Mode != OriginalGraphicsMode; + modeChangesDesc.IsVisible = () => ds.Mode != WindowMode.Windowed && (ds.Mode != OriginalGraphicsMode || + ds.VideoDisplay != OriginalVideoDisplay); + + var displaySelectionDropDown = panel.Get("DISPLAY_SELECTION_DROPDOWN"); + displaySelectionDropDown.OnMouseDown = _ => ShowDisplaySelectionDropdown(displaySelectionDropDown, ds); + var displaySelectionLabel = new CachedTransform(i => "Display {0}".F(i + 1)); + displaySelectionDropDown.GetText = () => displaySelectionLabel.Update(ds.VideoDisplay); + displaySelectionDropDown.IsDisabled = () => Game.Renderer.DisplayCount < 2; var statusBarsDropDown = panel.Get("STATUS_BAR_DROPDOWN"); statusBarsDropDown.OnMouseDown = _ => ShowStatusBarsDropdown(statusBarsDropDown, gs); @@ -289,6 +299,7 @@ Action InitDisplayPanel(Widget panel) uiScaleDropdown.IsDisabled = () => disableUIScale; + panel.Get("DISPLAY_SELECTION").IsVisible = () => ds.Mode != WindowMode.Windowed; panel.Get("WINDOW_RESOLUTION").IsVisible = () => ds.Mode == WindowMode.Windowed; var windowWidth = panel.Get("WINDOW_WIDTH"); var origWidthText = windowWidth.Text = ds.WindowedSize.X.ToString(); @@ -369,6 +380,7 @@ Action ResetDisplayPanel(Widget panel) ds.MaxFramerate = dds.MaxFramerate; ds.Language = dds.Language; ds.Mode = dds.Mode; + ds.VideoDisplay = dds.VideoDisplay; ds.WindowedSize = dds.WindowedSize; ds.CursorDouble = dds.CursorDouble; ds.ViewportDistance = dds.ViewportDistance; @@ -830,6 +842,22 @@ static void ShowStatusBarsDropdown(DropDownButtonWidget dropdown, GameSettings s dropdown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 500, options.Keys, setupItem); } + static void ShowDisplaySelectionDropdown(DropDownButtonWidget dropdown, GraphicSettings s) + { + Func setupItem = (o, itemTemplate) => + { + var item = ScrollItemWidget.Setup(itemTemplate, + () => s.VideoDisplay == o, + () => s.VideoDisplay = o); + + var label = "Display {0}".F(o + 1); + item.Get("LABEL").GetText = () => label; + return item; + }; + + dropdown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 500, Enumerable.Range(0, Game.Renderer.DisplayCount), setupItem); + } + static void ShowTargetLinesDropdown(DropDownButtonWidget dropdown, GameSettings s) { var options = new Dictionary() diff --git a/OpenRA.Platforms.Default/DefaultPlatform.cs b/OpenRA.Platforms.Default/DefaultPlatform.cs index dde6cf3794aa..470e040cd8ce 100644 --- a/OpenRA.Platforms.Default/DefaultPlatform.cs +++ b/OpenRA.Platforms.Default/DefaultPlatform.cs @@ -16,9 +16,9 @@ namespace OpenRA.Platforms.Default { public class DefaultPlatform : IPlatform { - public IPlatformWindow CreateWindow(Size size, WindowMode windowMode, float scaleModifier, int batchSize) + public IPlatformWindow CreateWindow(Size size, WindowMode windowMode, float scaleModifier, int batchSize, int videoDisplay) { - return new Sdl2PlatformWindow(size, windowMode, scaleModifier, batchSize); + return new Sdl2PlatformWindow(size, windowMode, scaleModifier, batchSize, videoDisplay); } public ISoundEngine CreateSound(string device) diff --git a/OpenRA.Platforms.Default/Sdl2PlatformWindow.cs b/OpenRA.Platforms.Default/Sdl2PlatformWindow.cs index bb6c153689b7..42a353249b15 100644 --- a/OpenRA.Platforms.Default/Sdl2PlatformWindow.cs +++ b/OpenRA.Platforms.Default/Sdl2PlatformWindow.cs @@ -87,12 +87,28 @@ public Size SurfaceSize } } + public int CurrentDisplay + { + get + { + return SDL.SDL_GetWindowDisplayIndex(window); + } + } + + public int DisplayCount + { + get + { + return SDL.SDL_GetNumVideoDisplays(); + } + } + public event Action OnWindowScaleChanged = (oldNative, oldEffective, newNative, newEffective) => { }; [DllImport("user32.dll")] static extern bool SetProcessDPIAware(); - public Sdl2PlatformWindow(Size requestEffectiveWindowSize, WindowMode windowMode, float scaleModifier, int batchSize) + public Sdl2PlatformWindow(Size requestEffectiveWindowSize, WindowMode windowMode, float scaleModifier, int batchSize, int videoDisplay) { // Lock the Window/Surface properties until initialization is complete lock (syncObject) @@ -129,8 +145,11 @@ public Sdl2PlatformWindow(Size requestEffectiveWindowSize, WindowMode windowMode Console.WriteLine("Using SDL 2 with OpenGL{0} renderer", useGLES ? " ES" : ""); + if (videoDisplay < 0 || videoDisplay >= DisplayCount) + videoDisplay = 0; + SDL.SDL_DisplayMode display; - SDL.SDL_GetCurrentDisplayMode(0, out display); + SDL.SDL_GetCurrentDisplayMode(videoDisplay, out display); // Windows and Linux define window sizes in native pixel units. // Query the display/dpi scale so we can convert our requested effective size to pixels. @@ -138,7 +157,7 @@ public Sdl2PlatformWindow(Size requestEffectiveWindowSize, WindowMode windowMode if (Platform.CurrentPlatform == PlatformType.Windows) { float ddpi, hdpi, vdpi; - if (SDL.SDL_GetDisplayDPI(0, out ddpi, out hdpi, out vdpi) == 0) + if (SDL.SDL_GetDisplayDPI(videoDisplay, out ddpi, out hdpi, out vdpi) == 0) windowScale = ddpi / 96; } else if (Platform.CurrentPlatform != PlatformType.OSX) @@ -166,7 +185,7 @@ public Sdl2PlatformWindow(Size requestEffectiveWindowSize, WindowMode windowMode if (Platform.CurrentPlatform == PlatformType.OSX && windowMode == WindowMode.Fullscreen) SDL.SDL_SetHint(SDL.SDL_HINT_VIDEO_HIGHDPI_DISABLED, "1"); - window = SDL.SDL_CreateWindow("OpenRA", SDL.SDL_WINDOWPOS_CENTERED, SDL.SDL_WINDOWPOS_CENTERED, + window = SDL.SDL_CreateWindow("OpenRA", SDL.SDL_WINDOWPOS_CENTERED_DISPLAY(videoDisplay), SDL.SDL_WINDOWPOS_CENTERED_DISPLAY(videoDisplay), windowSize.Width, windowSize.Height, windowFlags); // Work around an issue in macOS's GL backend where the window remains permanently black @@ -235,11 +254,6 @@ public Sdl2PlatformWindow(Size requestEffectiveWindowSize, WindowMode windowMode } else if (windowMode == WindowMode.PseudoFullscreen) { - // Work around a visual glitch in OSX: the window is offset - // partially offscreen if the dock is at the left of the screen - if (Platform.CurrentPlatform == PlatformType.OSX) - SDL.SDL_SetWindowPosition(Window, 0, 0); - SDL.SDL_SetWindowFullscreen(Window, (uint)SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP); SDL.SDL_SetHint(SDL.SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0"); } diff --git a/mods/cnc/chrome/settings.yaml b/mods/cnc/chrome/settings.yaml index 7ad89a7385fe..aa224a6db300 100644 --- a/mods/cnc/chrome/settings.yaml +++ b/mods/cnc/chrome/settings.yaml @@ -200,13 +200,6 @@ Container@SETTINGS_PANEL: Height: 25 Font: Regular Text: Windowed - Label@MODE_CHANGES_DESC: - X: 100 - Y: 237 - Width: 200 - Height: 15 - Font: Tiny - Text: Video mode changes require restart Container@WINDOW_RESOLUTION: Y: 240 Children: @@ -242,6 +235,27 @@ Container@SETTINGS_PANEL: Height: 15 Font: Tiny Text: Video mode and window size changes require restart + Container@DISPLAY_SELECTION: + Y: 240 + Children: + Label@DISPLAY_SELECTION_LABEL: + X: 15 + Height: 25 + Width: 120 + Align: Right + Text: Select Display: + DropDownButton@DISPLAY_SELECTION_DROPDOWN: + X: 140 + Width: 160 + Height: 25 + Font: Regular + Label@MODE_CHANGES_DESC: + X: 60 + Y: 27 + Width: 200 + Height: 15 + Font: Tiny + Text: Video mode and display changes require restart Checkbox@VSYNC_CHECKBOX: X: 310 Y: 210 diff --git a/mods/common/chrome/settings.yaml b/mods/common/chrome/settings.yaml index 299abb29139e..713befc56411 100644 --- a/mods/common/chrome/settings.yaml +++ b/mods/common/chrome/settings.yaml @@ -214,13 +214,6 @@ Background@SETTINGS_PANEL: Height: 25 Font: Regular Text: Windowed - Label@MODE_CHANGES_DESC: - X: 100 - Y: 237 - Width: 200 - Height: 15 - Font: Tiny - Text: Video mode changes require restart Checkbox@VSYNC_CHECKBOX: X: 310 Y: 213 @@ -263,6 +256,28 @@ Background@SETTINGS_PANEL: Height: 15 Font: Tiny Text: Video mode and window size changes require restart + Container@DISPLAY_SELECTION: + Y: 240 + Children: + Label@DISPLAY_SELECTION_LABEL: + X: 15 + Height: 25 + Width: 120 + Align: Right + Text: Select Display: + DropDownButton@DISPLAY_SELECTION_DROPDOWN: + X: 140 + Width: 160 + Height: 25 + Font: Regular + Text: Standard + Label@MODE_CHANGES_DESC: + X: 60 + Y: 27 + Width: 200 + Height: 15 + Font: Tiny + Text: Video mode and display changes require restart Checkbox@FRAME_LIMIT_CHECKBOX: X: 310 Y: 243