diff --git a/Source/OpenTK/Graphics/GraphicsMode.cs b/Source/OpenTK/Graphics/GraphicsMode.cs index 01262ad6..9322ab44 100644 --- a/Source/OpenTK/Graphics/GraphicsMode.cs +++ b/Source/OpenTK/Graphics/GraphicsMode.cs @@ -22,7 +22,6 @@ namespace OpenTK.Graphics IntPtr? index = null; // The id of the pixel format or visual. static GraphicsMode defaultMode; - static IGraphicsMode implementation; static readonly object SyncRoot = new object(); #region Constructors @@ -44,7 +43,7 @@ namespace OpenTK.Graphics { if (depth < 0) throw new ArgumentOutOfRangeException("depth", "Must be greater than, or equal to zero."); if (stencil < 0) throw new ArgumentOutOfRangeException("stencil", "Must be greater than, or equal to zero."); - if (buffers <= 0) throw new ArgumentOutOfRangeException("buffers", "Must be greater than zero."); + if (buffers < 0) throw new ArgumentOutOfRangeException("buffers", "Must be greater than, or equal to zero."); if (samples < 0) throw new ArgumentOutOfRangeException("samples", "Must be greater than, or equal to zero."); this.Index = index; @@ -255,7 +254,14 @@ namespace OpenTK.Graphics { return samples; } - private set { samples = value; } + private set + { + // Clamp antialiasing samples to max 64x + // This protects against a potential DOS during + // mode selection, when the user requests an + // abnormally high AA level. + samples = MathHelper.Clamp(value, 0, 64); + } } #endregion diff --git a/Source/OpenTK/Platform/MacOS/AglContext.cs b/Source/OpenTK/Platform/MacOS/AglContext.cs index d1de6664..f8e0fe0c 100644 --- a/Source/OpenTK/Platform/MacOS/AglContext.cs +++ b/Source/OpenTK/Platform/MacOS/AglContext.cs @@ -52,8 +52,6 @@ namespace OpenTK.Platform.MacOS DisplayDevice device; bool mIsFullscreen = false; - readonly MacOSGraphicsMode ModeSelector = new MacOSGraphicsMode(); - public AglContext(GraphicsMode mode, IWindowInfo window, IGraphicsContext shareContext) { Debug.Print("Context Type: {0}", shareContext); @@ -103,6 +101,7 @@ namespace OpenTK.Platform.MacOS aglAttributes.Add((int)pixelFormatAttribute); aglAttributes.Add(value); } + void CreateContext(GraphicsMode mode, CarbonWindowInfo carbonWindow, IntPtr shareContextRef, bool fullscreen) { Debug.Print("AGL pixel format attributes:"); @@ -110,51 +109,30 @@ namespace OpenTK.Platform.MacOS AGLPixelFormat myAGLPixelFormat; // Choose a pixel format with the attributes we specified. - if (fullscreen) - { - IntPtr gdevice; - IntPtr cgdevice = GetQuartzDevice(carbonWindow); + IntPtr gdevice; + IntPtr cgdevice = GetQuartzDevice(carbonWindow); - if (cgdevice == IntPtr.Zero) - cgdevice = (IntPtr)DisplayDevice.Default.Id; + if (cgdevice == IntPtr.Zero) + cgdevice = (IntPtr)DisplayDevice.Default.Id; - OSStatus status = Carbon.API.DMGetGDeviceByDisplayID(cgdevice, out gdevice, false); + OSStatus status = Carbon.API.DMGetGDeviceByDisplayID(cgdevice, out gdevice, false); - if (status != OSStatus.NoError) - throw new MacOSException(status, "DMGetGDeviceByDisplayID failed."); - - myAGLPixelFormat = ModeSelector.SelectPixelFormat( - mode.ColorFormat, mode.Depth, mode.Stencil, mode.Samples, - mode.AccumulatorFormat, mode.Buffers, mode.Stereo, - true, gdevice); - - Agl.AglError err = Agl.GetError(); - if (myAGLPixelFormat == IntPtr.Zero || err == Agl.AglError.BadPixelFormat) - { - Debug.Print("Failed to create full screen pixel format."); - Debug.Print("Trying again to create a non-fullscreen pixel format."); - - CreateContext(mode, carbonWindow, shareContextRef, false); - return; - } - } - else - { - myAGLPixelFormat = ModeSelector.SelectPixelFormat( - mode.ColorFormat, mode.Depth, mode.Stencil, mode.Samples, - mode.AccumulatorFormat, mode.Buffers, mode.Stereo, - false, IntPtr.Zero); - MyAGLReportError("aglChoosePixelFormat"); - } - + if (status != OSStatus.NoError) + throw new MacOSException(status, "DMGetGDeviceByDisplayID failed."); + + IGraphicsMode selector = new MacOSGraphicsMode(gdevice); + Mode = selector.SelectGraphicsMode( + mode.ColorFormat, mode.Depth, mode.Stencil, mode.Samples, + mode.AccumulatorFormat, mode.Buffers, mode.Stereo); + MyAGLReportError("aglChoosePixelFormat"); + Debug.Print("Creating AGL context. Sharing with {0}", shareContextRef); - + myAGLPixelFormat = Mode.Index.Value; + // create the context and share it with the share reference. Handle = new ContextHandle(Agl.aglCreateContext(myAGLPixelFormat, shareContextRef)); MyAGLReportError("aglCreateContext"); - Mode = ModeSelector.GetGraphicsModeFromPixelFormat(myAGLPixelFormat); - // Free the pixel format from memory. Agl.aglDestroyPixelFormat(myAGLPixelFormat); MyAGLReportError("aglDestroyPixelFormat"); diff --git a/Source/OpenTK/Platform/MacOS/MacOSGraphicsMode.cs b/Source/OpenTK/Platform/MacOS/MacOSGraphicsMode.cs index 604973e2..7f09155b 100644 --- a/Source/OpenTK/Platform/MacOS/MacOSGraphicsMode.cs +++ b/Source/OpenTK/Platform/MacOS/MacOSGraphicsMode.cs @@ -37,14 +37,47 @@ namespace OpenTK.Platform.MacOS class MacOSGraphicsMode : IGraphicsMode { + readonly IntPtr Device; + + public MacOSGraphicsMode(IntPtr device) + { + Device = device; + } + #region IGraphicsMode Members public GraphicsMode SelectGraphicsMode(ColorFormat color, int depth, int stencil, int samples, ColorFormat accum, int buffers, bool stereo) { - IntPtr pixelformat = SelectPixelFormat( - color, depth, stencil, samples, accum, buffers, stereo, - false, IntPtr.Zero); + IntPtr pixelformat; + do + { + pixelformat = SelectPixelFormat( + color, depth, stencil, samples, accum, buffers, stereo, + true, Device); + + Agl.AglError err = Agl.GetError(); + if (pixelformat == IntPtr.Zero || err == Agl.AglError.BadPixelFormat) + { + Debug.Print("Failed to create full screen pixel format."); + Debug.Print("Trying again to create a non-fullscreen pixel format."); + pixelformat = SelectPixelFormat( + color, depth, stencil, samples, accum, buffers, stereo, + false, IntPtr.Zero); + } + + if (pixelformat == IntPtr.Zero) + { + if (!Utilities.RelaxGraphicsMode( + ref color, ref depth, ref stencil, ref samples, ref accum, + ref buffers, ref stereo)) + { + throw new GraphicsModeException("Requested GraphicsMode not available."); + } + } + } + while (pixelformat == IntPtr.Zero); + return GetGraphicsModeFromPixelFormat(pixelformat); } @@ -52,7 +85,7 @@ namespace OpenTK.Platform.MacOS #region Internal Members - internal GraphicsMode GetGraphicsModeFromPixelFormat(IntPtr pixelformat) + GraphicsMode GetGraphicsModeFromPixelFormat(IntPtr pixelformat) { int r, g, b, a; Agl.aglDescribePixelFormat(pixelformat, Agl.PixelFormatAttribute.AGL_RED_SIZE, out r); @@ -75,7 +108,7 @@ namespace OpenTK.Platform.MacOS depth, stencil, samples, new ColorFormat(ar, ag, ab, aa), buffers + 1, stereo != 0); } - internal IntPtr SelectPixelFormat(ColorFormat color, int depth, int stencil, int samples, + IntPtr SelectPixelFormat(ColorFormat color, int depth, int stencil, int samples, ColorFormat accum, int buffers, bool stereo, bool fullscreen, IntPtr device) { List attribs = new List(); diff --git a/Source/OpenTK/Platform/SDL2/Sdl2GraphicsContext.cs b/Source/OpenTK/Platform/SDL2/Sdl2GraphicsContext.cs index c7714b58..75818496 100644 --- a/Source/OpenTK/Platform/SDL2/Sdl2GraphicsContext.cs +++ b/Source/OpenTK/Platform/SDL2/Sdl2GraphicsContext.cs @@ -63,8 +63,20 @@ namespace OpenTK.Platform.SDL2 { lock (SDL.Sync) { - SetGLAttributes(mode, shareContext, major, minor, flags); - SdlContext = new ContextHandle(SDL.GL.CreateContext(Window.Handle)); + bool retry = false; + do + { + SetGLAttributes(mode, shareContext, major, minor, flags); + SdlContext = new ContextHandle(SDL.GL.CreateContext(Window.Handle)); + + // If we failed to create a valid context, relax the GraphicsMode + // and try again. + retry = + SdlContext == ContextHandle.Zero && + Utilities.RelaxGraphicsMode(ref mode); + } + while (retry); + if (SdlContext == ContextHandle.Zero) { var error = SDL.GetError(); @@ -152,12 +164,37 @@ namespace OpenTK.Platform.SDL2 stereo != 0 ? true : false); } + static void ClearGLAttributes() + { + SDL.GL.SetAttribute(ContextAttribute.ACCUM_ALPHA_SIZE, 0); + SDL.GL.SetAttribute(ContextAttribute.ACCUM_RED_SIZE, 0); + SDL.GL.SetAttribute(ContextAttribute.ACCUM_GREEN_SIZE, 0); + SDL.GL.SetAttribute(ContextAttribute.ACCUM_BLUE_SIZE, 0); + SDL.GL.SetAttribute(ContextAttribute.DOUBLEBUFFER, 0); + SDL.GL.SetAttribute(ContextAttribute.ALPHA_SIZE, 0); + SDL.GL.SetAttribute(ContextAttribute.RED_SIZE, 0); + SDL.GL.SetAttribute(ContextAttribute.GREEN_SIZE, 0); + SDL.GL.SetAttribute(ContextAttribute.BLUE_SIZE, 0); + SDL.GL.SetAttribute(ContextAttribute.DEPTH_SIZE, 0); + SDL.GL.SetAttribute(ContextAttribute.MULTISAMPLEBUFFERS, 0); + SDL.GL.SetAttribute(ContextAttribute.MULTISAMPLESAMPLES, 0); + SDL.GL.SetAttribute(ContextAttribute.STENCIL_SIZE, 0); + SDL.GL.SetAttribute(ContextAttribute.STEREO, 0); + SDL.GL.SetAttribute(ContextAttribute.CONTEXT_MAJOR_VERSION, 1); + SDL.GL.SetAttribute(ContextAttribute.CONTEXT_MINOR_VERSION, 0); + SDL.GL.SetAttribute(ContextAttribute.CONTEXT_FLAGS, 0); + SDL.GL.SetAttribute(ContextAttribute.CONTEXT_EGL, 0); + SDL.GL.SetAttribute(ContextAttribute.CONTEXT_PROFILE_MASK, 0); + SDL.GL.SetAttribute(ContextAttribute.SHARE_WITH_CURRENT_CONTEXT, 0); + } + static void SetGLAttributes(GraphicsMode mode, IGraphicsContext shareContext, int major, int minor, GraphicsContextFlags flags) { ContextProfileFlags cpflags = 0; + ClearGLAttributes(); if (mode.AccumulatorFormat.BitsPerPixel > 0) { diff --git a/Source/OpenTK/Platform/Utilities.cs b/Source/OpenTK/Platform/Utilities.cs index cbf0e811..82fa91ab 100644 --- a/Source/OpenTK/Platform/Utilities.cs +++ b/Source/OpenTK/Platform/Utilities.cs @@ -307,5 +307,113 @@ namespace OpenTK.Platform #endregion #endregion + + #region RelaxGraphicsMode + + internal static bool RelaxGraphicsMode(ref GraphicsMode mode) + { + ColorFormat color = mode.ColorFormat; + int depth = mode.Depth; + int stencil = mode.Stencil; + int samples = mode.Samples; + ColorFormat accum = mode.AccumulatorFormat; + int buffers = mode.Buffers; + bool stereo = mode.Stereo; + + bool success = RelaxGraphicsMode( + ref color, ref depth, ref stencil, ref samples, + ref accum, ref buffers, ref stereo); + + mode = new GraphicsMode( + color, depth, stencil, samples, + accum, buffers, stereo); + + return success; + } + + /// \internal + /// + /// Relaxes graphics mode parameters. Use this function to increase compatibility + /// on systems that do not directly support a requested GraphicsMode. For example: + /// - user requested stereoscopic rendering, but GPU does not support stereo + /// - user requseted 16x antialiasing, but GPU only supports 4x + /// + /// true, if a graphics mode parameter was relaxed, false otherwise. + /// Color bits. + /// Depth bits. + /// Stencil bits. + /// Number of antialiasing samples. + /// Accumulator buffer bits. + /// Number of rendering buffers (1 for single buffering, 2+ for double buffering, 0 for don't care). + /// Stereoscopic rendering enabled/disabled. + internal static bool RelaxGraphicsMode(ref ColorFormat color, ref int depth, ref int stencil, ref int samples, ref ColorFormat accum, ref int buffers, ref bool stereo) + { + // Parameters are relaxed in order of importance. + // - Accumulator buffers are way outdated as a concept, + // so they go first. + // - Triple+ buffering is generally not supported by the + // core WGL/GLX/AGL/CGL/EGL specs, so we clamp + // to double-buffering as a second step. (If this doesn't help + // we will also fall back to undefined single/double buffering + // as a last resort). + // - AA samples are an easy way to increase compatibility + // so they go next. + // - Stereoscopic is only supported on very few GPUs + // (Quadro/FirePro series) so it goes next. + // - The rest of the parameters then follow. + + if (accum != 0) + { + accum = 0; + return true; + } + + if (buffers > 2) + { + buffers = 2; + return true; + } + + if (samples > 0) + { + samples = Math.Max(samples - 1, 0); + return true; + } + + if (stereo) + { + stereo = false; + return true; + } + + if (stencil != 0) + { + stencil = 0; + return true; + } + + if (depth != 0) + { + depth = 0; + return true; + } + + if (color != 24) + { + color = 24; + return true; + } + + if (buffers != 0) + { + buffers = 0; + return true; + } + + // no parameters left to relax, fail + return false; + } + + #endregion } } diff --git a/Source/OpenTK/Platform/Windows/WinGraphicsMode.cs b/Source/OpenTK/Platform/Windows/WinGraphicsMode.cs index 1a97fa72..8550ddf8 100644 --- a/Source/OpenTK/Platform/Windows/WinGraphicsMode.cs +++ b/Source/OpenTK/Platform/Windows/WinGraphicsMode.cs @@ -96,114 +96,123 @@ namespace OpenTK.Platform.Windows // hardware acceleration (e.g. we are running in a VM or in a remote desktop // connection), this method will return 0 formats and we will fall back to // ChoosePixelFormatPFD. - GraphicsMode ChoosePixelFormatARB(IntPtr device, GraphicsMode mode) + GraphicsMode ChoosePixelFormatARB(IntPtr device, GraphicsMode desired_mode) { GraphicsMode created_mode = null; + GraphicsMode mode = new GraphicsMode(desired_mode); if (Wgl.SupportsExtension("WGL_ARB_pixel_format") && Wgl.SupportsFunction("wglChoosePixelFormatARB")) { - List attributes = new List(); - attributes.Add((int)WGL_ARB_pixel_format.AccelerationArb); - attributes.Add((int)WGL_ARB_pixel_format.FullAccelerationArb); - - attributes.Add((int)WGL_ARB_pixel_format.DrawToWindowArb); - attributes.Add(1); - - if (mode.ColorFormat.Red > 0) - { - attributes.Add((int)WGL_ARB_pixel_format.RedBitsArb); - attributes.Add(mode.ColorFormat.Red); - } - - if (mode.ColorFormat.Green > 0) - { - attributes.Add((int)WGL_ARB_pixel_format.GreenBitsArb); - attributes.Add(mode.ColorFormat.Green); - } - - if (mode.ColorFormat.Blue > 0) - { - attributes.Add((int)WGL_ARB_pixel_format.BlueBitsArb); - attributes.Add(mode.ColorFormat.Blue); - } - - if (mode.ColorFormat.Alpha > 0) - { - attributes.Add((int)WGL_ARB_pixel_format.AlphaBitsArb); - attributes.Add(mode.ColorFormat.Alpha); - } - - if (mode.Depth > 0) - { - attributes.Add((int)WGL_ARB_pixel_format.DepthBitsArb); - attributes.Add(mode.Depth); - } - - if (mode.Stencil > 0) - { - attributes.Add((int)WGL_ARB_pixel_format.StencilBitsArb); - attributes.Add(mode.Stencil); - } - - if (mode.AccumulatorFormat.Red > 0) - { - attributes.Add((int)WGL_ARB_pixel_format.AccumRedBitsArb); - attributes.Add(mode.AccumulatorFormat.Red); - } - - if (mode.AccumulatorFormat.Green > 0) - { - attributes.Add((int)WGL_ARB_pixel_format.AccumGreenBitsArb); - attributes.Add(mode.AccumulatorFormat.Green); - } - - if (mode.AccumulatorFormat.Blue > 0) - { - attributes.Add((int)WGL_ARB_pixel_format.AccumBlueBitsArb); - attributes.Add(mode.AccumulatorFormat.Blue); - } - - if (mode.AccumulatorFormat.Alpha > 0) - { - attributes.Add((int)WGL_ARB_pixel_format.AccumAlphaBitsArb); - attributes.Add(mode.AccumulatorFormat.Alpha); - } - - if (mode.Samples > 0 && - Wgl.SupportsExtension("WGL_ARB_multisample")) - { - attributes.Add((int)WGL_ARB_multisample.SampleBuffersArb); - attributes.Add(1); - attributes.Add((int)WGL_ARB_multisample.SamplesArb); - attributes.Add(mode.Samples); - } - - if (mode.Buffers > 0) - { - attributes.Add((int)WGL_ARB_pixel_format.DoubleBufferArb); - attributes.Add(mode.Buffers > 1 ? 1 : 0); - } - - if (mode.Stereo) - { - attributes.Add((int)WGL_ARB_pixel_format.StereoArb); - attributes.Add(1); - } - - attributes.Add(0); - attributes.Add(0); - int[] format = new int[1]; int count; - if (Wgl.Arb.ChoosePixelFormat(device, attributes.ToArray(), null, format.Length, format, out count) - && count > 0) + List attributes = new List(); + bool retry = false; + + do { - created_mode = DescribePixelFormatARB(device, format[0]); - } - else - { - Debug.Print("[WGL] ChoosePixelFormatARB failed with {0}", Marshal.GetLastWin32Error()); + attributes.Clear(); + attributes.Add((int)WGL_ARB_pixel_format.AccelerationArb); + attributes.Add((int)WGL_ARB_pixel_format.FullAccelerationArb); + attributes.Add((int)WGL_ARB_pixel_format.DrawToWindowArb); + attributes.Add(1); + + if (mode.ColorFormat.Red > 0) + { + attributes.Add((int)WGL_ARB_pixel_format.RedBitsArb); + attributes.Add(mode.ColorFormat.Red); + } + + if (mode.ColorFormat.Green > 0) + { + attributes.Add((int)WGL_ARB_pixel_format.GreenBitsArb); + attributes.Add(mode.ColorFormat.Green); + } + + if (mode.ColorFormat.Blue > 0) + { + attributes.Add((int)WGL_ARB_pixel_format.BlueBitsArb); + attributes.Add(mode.ColorFormat.Blue); + } + + if (mode.ColorFormat.Alpha > 0) + { + attributes.Add((int)WGL_ARB_pixel_format.AlphaBitsArb); + attributes.Add(mode.ColorFormat.Alpha); + } + + if (mode.Depth > 0) + { + attributes.Add((int)WGL_ARB_pixel_format.DepthBitsArb); + attributes.Add(mode.Depth); + } + + if (mode.Stencil > 0) + { + attributes.Add((int)WGL_ARB_pixel_format.StencilBitsArb); + attributes.Add(mode.Stencil); + } + + if (mode.AccumulatorFormat.Red > 0) + { + attributes.Add((int)WGL_ARB_pixel_format.AccumRedBitsArb); + attributes.Add(mode.AccumulatorFormat.Red); + } + + if (mode.AccumulatorFormat.Green > 0) + { + attributes.Add((int)WGL_ARB_pixel_format.AccumGreenBitsArb); + attributes.Add(mode.AccumulatorFormat.Green); + } + + if (mode.AccumulatorFormat.Blue > 0) + { + attributes.Add((int)WGL_ARB_pixel_format.AccumBlueBitsArb); + attributes.Add(mode.AccumulatorFormat.Blue); + } + + if (mode.AccumulatorFormat.Alpha > 0) + { + attributes.Add((int)WGL_ARB_pixel_format.AccumAlphaBitsArb); + attributes.Add(mode.AccumulatorFormat.Alpha); + } + + if (mode.Samples > 0 && + Wgl.SupportsExtension("WGL_ARB_multisample")) + { + attributes.Add((int)WGL_ARB_multisample.SampleBuffersArb); + attributes.Add(1); + attributes.Add((int)WGL_ARB_multisample.SamplesArb); + attributes.Add(mode.Samples); + } + + if (mode.Buffers > 0) + { + attributes.Add((int)WGL_ARB_pixel_format.DoubleBufferArb); + attributes.Add(mode.Buffers > 1 ? 1 : 0); + } + + if (mode.Stereo) + { + attributes.Add((int)WGL_ARB_pixel_format.StereoArb); + attributes.Add(1); + } + + attributes.Add(0); + attributes.Add(0); + + if (Wgl.Arb.ChoosePixelFormat(device, attributes.ToArray(), null, format.Length, format, out count) + && count > 0) + { + created_mode = DescribePixelFormatARB(device, format[0]); + retry = false; + } + else + { + Debug.Print("[WGL] ChoosePixelFormatARB failed with {0}", Marshal.GetLastWin32Error()); + retry = Utilities.RelaxGraphicsMode(ref mode); + } } + while (retry); } else { diff --git a/Source/OpenTK/Platform/X11/X11GraphicsMode.cs b/Source/OpenTK/Platform/X11/X11GraphicsMode.cs index 1c019036..000f45ee 100644 --- a/Source/OpenTK/Platform/X11/X11GraphicsMode.cs +++ b/Source/OpenTK/Platform/X11/X11GraphicsMode.cs @@ -52,49 +52,8 @@ namespace OpenTK.Platform.X11 if (visual == IntPtr.Zero) { // Relax parameters and retry - if (stereo) - { - stereo = false; - continue; - } - - if (accum != 0) - { - accum = 0; - continue; - } - - if (samples > 0) - { - samples = Math.Max(samples - 2, 0); - continue; - } - - if (stencil != 0) - { - stencil = 0; - continue; - } - - if (depth != 0) - { - depth = 0; - continue; - } - - if (color != 24) - { - color = 24; - continue; - } - - if (buffers != 0) - { - buffers = 0; - continue; - } - - throw new GraphicsModeException("Requested GraphicsMode not available."); + if (!Utilities.RelaxGraphicsMode(ref color, ref depth, ref stencil, ref samples, ref accum, ref buffers, ref stereo)) + throw new GraphicsModeException("Requested GraphicsMode not available."); } } while (visual == IntPtr.Zero);