diff --git a/OpenRA.Game/Graphics/PlatformInterfaces.cs b/OpenRA.Game/Graphics/PlatformInterfaces.cs index 40efb14167..5821d9ba92 100644 --- a/OpenRA.Game/Graphics/PlatformInterfaces.cs +++ b/OpenRA.Game/Graphics/PlatformInterfaces.cs @@ -23,7 +23,7 @@ namespace OpenRA public interface IPlatform { - IPlatformWindow CreateWindow(Size size, WindowMode windowMode, float scaleModifier, int batchSize, int videoDisplay); + IPlatformWindow CreateWindow(Size size, WindowMode windowMode, float scaleModifier, int batchSize, int videoDisplay, GLProfile profile); ISoundEngine CreateSound(string device); IFont CreateFont(byte[] data); } @@ -70,6 +70,10 @@ namespace OpenRA void SetHardwareCursor(IHardwareCursor cursor); void SetRelativeMouseMode(bool mode); void SetScaleModifier(float scale); + + GLProfile GLProfile { get; } + + GLProfile[] SupportedGLProfiles { get; } } public interface IGraphicsContext : IDisposable diff --git a/OpenRA.Game/Renderer.cs b/OpenRA.Game/Renderer.cs index c7c93fe299..9fec7df685 100644 --- a/OpenRA.Game/Renderer.cs +++ b/OpenRA.Game/Renderer.cs @@ -75,7 +75,10 @@ namespace OpenRA this.platform = platform; var resolution = GetResolution(graphicSettings); - Window = platform.CreateWindow(new Size(resolution.Width, resolution.Height), graphicSettings.Mode, graphicSettings.UIScale, graphicSettings.BatchSize, graphicSettings.VideoDisplay); + Window = platform.CreateWindow(new Size(resolution.Width, resolution.Height), + graphicSettings.Mode, graphicSettings.UIScale, graphicSettings.BatchSize, + graphicSettings.VideoDisplay, graphicSettings.GLProfile); + Context = Window.Context; TempBufferSize = graphicSettings.BatchSize; @@ -310,6 +313,8 @@ namespace OpenRA public Size NativeResolution { get { return Window.NativeWindowSize; } } public float WindowScale { get { return Window.EffectiveWindowScale; } } public float NativeWindowScale { get { return Window.NativeWindowScale; } } + public GLProfile GLProfile { get { return Window.GLProfile; } } + public GLProfile[] SupportedGLProfiles { get { return Window.SupportedGLProfiles; } } public interface IBatchRenderer { void Flush(); } diff --git a/OpenRA.Game/Settings.cs b/OpenRA.Game/Settings.cs index 32f4bcca1a..0f3401d8f4 100644 --- a/OpenRA.Game/Settings.cs +++ b/OpenRA.Game/Settings.cs @@ -172,12 +172,14 @@ namespace OpenRA [Desc("Disable operating-system provided cursor rendering.")] public bool DisableHardwareCursors = false; - [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; + [Desc("Preferred OpenGL profile to use.", + "Modern: OpenGL Core Profile 3.2 or greater.", + "Embedded: OpenGL ES 3.0 or greater.")] + public GLProfile GLProfile = GLProfile.Modern; + 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 7bdb8eecd7..efaf2a9dee 100644 --- a/OpenRA.Mods.Common/LoadScreens/BlankLoadScreen.cs +++ b/OpenRA.Mods.Common/LoadScreens/BlankLoadScreen.cs @@ -122,6 +122,7 @@ namespace OpenRA.Mods.Common.LoadScreens } // Saved settings may have been invalidated by a hardware change + Game.Settings.Graphics.GLProfile = Game.Renderer.GLProfile; Game.Settings.Graphics.VideoDisplay = Game.Renderer.CurrentDisplay; // If a ModContent section is defined then we need to make sure that the diff --git a/OpenRA.Platforms.Default/DefaultPlatform.cs b/OpenRA.Platforms.Default/DefaultPlatform.cs index 470e040cd8..419c4c8433 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, int videoDisplay) + public IPlatformWindow CreateWindow(Size size, WindowMode windowMode, float scaleModifier, int batchSize, int videoDisplay, GLProfile profile) { - return new Sdl2PlatformWindow(size, windowMode, scaleModifier, batchSize, videoDisplay); + return new Sdl2PlatformWindow(size, windowMode, scaleModifier, batchSize, videoDisplay, profile); } public ISoundEngine CreateSound(string device) diff --git a/OpenRA.Platforms.Default/Sdl2PlatformWindow.cs b/OpenRA.Platforms.Default/Sdl2PlatformWindow.cs index c168fed622..9384d0d362 100644 --- a/OpenRA.Platforms.Default/Sdl2PlatformWindow.cs +++ b/OpenRA.Platforms.Default/Sdl2PlatformWindow.cs @@ -12,6 +12,7 @@ using System; using System.Diagnostics; using System.Globalization; +using System.Linq; using System.Runtime.InteropServices; using OpenRA.Primitives; using SDL2; @@ -34,6 +35,8 @@ namespace OpenRA.Platforms.Default float windowScale = 1f; int2? lockedMousePosition; float scaleModifier; + readonly GLProfile profile; + readonly GLProfile[] supportedProfiles; internal IntPtr Window { @@ -107,12 +110,31 @@ namespace OpenRA.Platforms.Default public bool HasInputFocus { get; internal set; } + public GLProfile GLProfile + { + get + { + lock (syncObject) + return profile; + } + } + + public GLProfile[] SupportedGLProfiles + { + get + { + lock (syncObject) + return supportedProfiles; + } + } + 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, int videoDisplay) + public Sdl2PlatformWindow(Size requestEffectiveWindowSize, WindowMode windowMode, + float scaleModifier, int batchSize, int videoDisplay, GLProfile requestProfile) { // Lock the Window/Surface properties until initialization is complete lock (syncObject) @@ -124,31 +146,24 @@ namespace OpenRA.Platforms.Default SetProcessDPIAware(); SDL.SDL_Init(SDL.SDL_INIT_NOPARACHUTE | SDL.SDL_INIT_VIDEO); - SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_DOUBLEBUFFER, 1); - SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_RED_SIZE, 8); - SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_GREEN_SIZE, 8); - SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_BLUE_SIZE, 8); - SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_ALPHA_SIZE, 0); - // Decide between OpenGL and OpenGL ES rendering - // Test whether we can use the preferred renderer and fall back to the other if that fails - // If neither works we will throw a graphics error later when trying to create the real window - bool useGLES; - if (Game.Settings.Graphics.PreferGLES) - useGLES = CanCreateGLWindow(3, 0, SDL.SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_ES); + // Decide which OpenGL profile to use. + // We first need to query the available profiles on Windows/Linux. + // On macOS, known/consistent OpenGL support is provided by the OS. + if (Platform.CurrentPlatform == PlatformType.OSX) + supportedProfiles = new[] { GLProfile.Modern }; else - useGLES = !CanCreateGLWindow(3, 2, SDL.SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_CORE); + supportedProfiles = new[] { GLProfile.Modern, GLProfile.Embedded } + .Where(CanCreateGLWindow) + .ToArray(); - var glMajor = 3; - var glMinor = useGLES ? 0 : 2; - var glProfile = useGLES ? SDL.SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_ES : SDL.SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_CORE; + if (!supportedProfiles.Any()) + throw new InvalidOperationException("No supported OpenGL profiles were found."); - SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_MAJOR_VERSION, glMajor); - SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_MINOR_VERSION, glMinor); - SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_PROFILE_MASK, (int)glProfile); - - Console.WriteLine("Using SDL 2 with OpenGL{0} renderer", useGLES ? " ES" : ""); + profile = supportedProfiles.Contains(requestProfile) ? requestProfile : supportedProfiles.First(); + SetSDLAttributes(profile); + Console.WriteLine("Using SDL 2 with OpenGL ({0}) renderer", profile); if (videoDisplay < 0 || videoDisplay >= DisplayCount) videoDisplay = 0; @@ -451,12 +466,34 @@ namespace OpenRA.Platforms.Default return input.SetClipboardText(text); } - static bool CanCreateGLWindow(int major, int minor, SDL.SDL_GLprofile profile) + static void SetSDLAttributes(GLProfile profile) + { + SDL.SDL_GL_ResetAttributes(); + SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_DOUBLEBUFFER, 1); + SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_RED_SIZE, 8); + SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_GREEN_SIZE, 8); + SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_BLUE_SIZE, 8); + SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_ALPHA_SIZE, 0); + + switch (profile) + { + case GLProfile.Modern: + SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_MINOR_VERSION, 2); + SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_PROFILE_MASK, (int)SDL.SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_CORE); + break; + case GLProfile.Embedded: + SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_MINOR_VERSION, 0); + SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_PROFILE_MASK, (int)SDL.SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_ES); + break; + } + } + + static bool CanCreateGLWindow(GLProfile profile) { // Implementation inspired by TestIndividualGLVersion from Veldrid - SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_MAJOR_VERSION, major); - SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_MINOR_VERSION, minor); - SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_PROFILE_MASK, (int)profile); + SetSDLAttributes(profile); var flags = SDL.SDL_WindowFlags.SDL_WINDOW_HIDDEN | SDL.SDL_WindowFlags.SDL_WINDOW_OPENGL; var window = SDL.SDL_CreateWindow("", 0, 0, 1, 1, flags);