#region License // // The Open Toolkit Library License // // Copyright (c) 2006 - 2010 the Open Toolkit library. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights to // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of // the Software, and to permit persons to whom the Software is furnished to do // so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // #endregion using System; using System.Collections.Generic; using System.Text; using System.Diagnostics; using System.Runtime.InteropServices; using OpenTK.Graphics; namespace OpenTK.Platform.Windows { class WinGraphicsMode : IGraphicsMode { enum AccelerationType { // Software acceleration None = 0, // Partial acceleration (Direct3D emulation) MCD, // Full acceleration ICD, } static readonly object SyncRoot = new object(); readonly IntPtr Device; readonly List modes = new List(); #region Constructors public WinGraphicsMode(IntPtr device) { if (device == IntPtr.Zero) throw new ArgumentException(); Device = device; } #endregion #region IGraphicsMode Members public GraphicsMode SelectGraphicsMode(ColorFormat color, int depth, int stencil, int samples, ColorFormat accum, int buffers, bool stereo) { GraphicsMode mode = new GraphicsMode(color, depth, stencil, samples,accum, buffers, stereo); GraphicsMode created_mode = ChoosePixelFormatARB(Device, mode); // If ChoosePixelFormatARB failed, iterate through all acceleration types in turn (ICD, MCD, None) // This should fix issue #2224, which causes OpenTK to fail on VMs without hardware acceleration. created_mode = created_mode ?? ChoosePixelFormatPFD(Device, mode, AccelerationType.ICD); created_mode = created_mode ?? ChoosePixelFormatPFD(Device, mode, AccelerationType.MCD); created_mode = created_mode ?? ChoosePixelFormatPFD(Device, mode, AccelerationType.None); if (created_mode == null) { throw new GraphicsModeException("The requested GraphicsMode is not supported"); } return created_mode; } #endregion #region Private Methods #region ChoosePixelFormatARB // Queries pixel formats through the WGL_ARB_pixel_format extension // This method only returns accelerated formats. If no format offers // 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 created_mode = null; if (Wgl.Delegates.wglChoosePixelFormatARB != null) { 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) { 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)) { created_mode = DescribePixelFormatARB(device, format[0]); } else { Debug.Print("[WGL] ChoosePixelFormatARB failed with {0}", Marshal.GetLastWin32Error()); } } else { Debug.WriteLine("[WGL] ChoosePixelFormatARB not supported on this context"); } return created_mode; } #endregion #region ChoosePixelFormatPFD static bool Compare(int got, int requested, ref int distance) { bool valid = true; if (got == 0 && requested != 0) { // mode does not support the requested feature. valid = false; } else if (got >= requested) { // mode supports the requested feature, // calculate the distance from an "ideal" mode // that matches this feature exactly. distance += got - requested; } else { // mode supports the requested feature, // but at a suboptimal level. For example: // - requsted AA = 8x, got 4x // - requested color = 32bpp, got 16bpp // We can still use this mode but only if // no better mode exists. const int penalty = 8; distance += penalty * Math.Abs(got - requested); } return valid; } static AccelerationType GetAccelerationType(ref PixelFormatDescriptor pfd) { AccelerationType type = AccelerationType.ICD; if ((pfd.Flags & PixelFormatDescriptorFlags.GENERIC_FORMAT) != 0) { if ((pfd.Flags & PixelFormatDescriptorFlags.GENERIC_ACCELERATED) != 0) { type = AccelerationType.MCD; } else { type = AccelerationType.None; } } return type; } GraphicsMode ChoosePixelFormatPFD(IntPtr device, GraphicsMode mode, AccelerationType requested_acceleration_type) { PixelFormatDescriptor pfd = new PixelFormatDescriptor(); PixelFormatDescriptorFlags flags = 0; flags |= PixelFormatDescriptorFlags.DRAW_TO_WINDOW; flags |= PixelFormatDescriptorFlags.SUPPORT_OPENGL; if (mode.Stereo) { flags |= PixelFormatDescriptorFlags.STEREO; } if (mode.Buffers > 1) { // On Win7 64bit + Nvidia 650M, no pixel format advertises DOUBLEBUFFER. // Adding this check here causes mode selection to fail. // Does not appear to be supported by DescribePixelFormat //flags |= PixelFormatDescriptorFlags.DOUBLEBUFFER; } if (System.Environment.OSVersion.Version.Major >= 6 && requested_acceleration_type != AccelerationType.None) { // Request a compositor-capable mode when running on // Vista+ and using hardware acceleration. Without this, // some modes will cause the compositor to turn off, // which is very annoying to the user. // Note: compositor-capable modes require hardware // acceleration. Don't set this flag when running // with software acceleration (e.g. over Remote Desktop // as described in bug https://github.com/opentk/opentk/issues/35) flags |= PixelFormatDescriptorFlags.SUPPORT_COMPOSITION; } int count = Functions.DescribePixelFormat(device, 1, API.PixelFormatDescriptorSize, ref pfd); int best = 0; int best_dist = int.MaxValue; for (int index = 1; index <= count; index++) { int dist = 0; bool valid = Functions.DescribePixelFormat(device, index, API.PixelFormatDescriptorSize, ref pfd) != 0; valid &= GetAccelerationType(ref pfd) == requested_acceleration_type; valid &= (pfd.Flags & flags) == flags; valid &= pfd.PixelType == PixelType.RGBA; // indexed modes not currently supported valid &= Compare(pfd.ColorBits, mode.ColorFormat.BitsPerPixel, ref dist); valid &= Compare(pfd.RedBits, mode.ColorFormat.Red, ref dist); valid &= Compare(pfd.GreenBits, mode.ColorFormat.Green, ref dist); valid &= Compare(pfd.BlueBits, mode.ColorFormat.Blue, ref dist); valid &= Compare(pfd.AlphaBits, mode.ColorFormat.Alpha, ref dist); valid &= Compare(pfd.AccumBits, mode.AccumulatorFormat.BitsPerPixel, ref dist); valid &= Compare(pfd.AccumRedBits, mode.AccumulatorFormat.Red, ref dist); valid &= Compare(pfd.AccumGreenBits, mode.AccumulatorFormat.Green, ref dist); valid &= Compare(pfd.AccumBlueBits, mode.AccumulatorFormat.Blue, ref dist); valid &= Compare(pfd.AccumAlphaBits, mode.AccumulatorFormat.Alpha, ref dist); valid &= Compare(pfd.DepthBits, mode.Depth, ref dist); valid &= Compare(pfd.StencilBits, mode.Stencil, ref dist); if (valid && dist < best_dist) { best = index; best_dist = dist; } } return DescribePixelFormatPFD(device, ref pfd, best); } #endregion #region DescribePixelFormatPFD static GraphicsMode DescribePixelFormatPFD(IntPtr device, ref PixelFormatDescriptor pfd, int pixelformat) { GraphicsMode created_mode = null; if (Functions.DescribePixelFormat(device, pixelformat, pfd.Size, ref pfd) > 0) { created_mode = new GraphicsMode( new IntPtr(pixelformat), new ColorFormat(pfd.RedBits, pfd.GreenBits, pfd.BlueBits, pfd.AlphaBits), pfd.DepthBits, pfd.StencilBits, 0, // MSAA not supported when using PixelFormatDescriptor new ColorFormat(pfd.AccumRedBits, pfd.AccumGreenBits, pfd.AccumBlueBits, pfd.AccumAlphaBits), (pfd.Flags & PixelFormatDescriptorFlags.DOUBLEBUFFER) != 0 ? 2 : 1, (pfd.Flags & PixelFormatDescriptorFlags.STEREO) != 0); } return created_mode; } #endregion #region DescribePixelFormatARB GraphicsMode DescribePixelFormatARB(IntPtr device, int pixelformat) { GraphicsMode created_mode = null; // See http://www.opengl.org/registry/specs/ARB/wgl_pixel_format.txt for more details if (Wgl.Delegates.wglGetPixelFormatAttribivARB != null) { // Define the list of attributes we are interested in. // The results will be stored in the 'values' array below. int[] attribs = new int[] { (int)WGL_ARB_pixel_format.AccelerationArb, (int)WGL_ARB_pixel_format.RedBitsArb, (int)WGL_ARB_pixel_format.GreenBitsArb, (int)WGL_ARB_pixel_format.BlueBitsArb, (int)WGL_ARB_pixel_format.AlphaBitsArb, (int)WGL_ARB_pixel_format.ColorBitsArb, (int)WGL_ARB_pixel_format.DepthBitsArb, (int)WGL_ARB_pixel_format.StencilBitsArb, (int)WGL_ARB_multisample.SampleBuffersArb, (int)WGL_ARB_multisample.SamplesArb, (int)WGL_ARB_pixel_format.AccumRedBitsArb, (int)WGL_ARB_pixel_format.AccumGreenBitsArb, (int)WGL_ARB_pixel_format.AccumBlueBitsArb, (int)WGL_ARB_pixel_format.AccumAlphaBitsArb, (int)WGL_ARB_pixel_format.AccumBitsArb, (int)WGL_ARB_pixel_format.DoubleBufferArb, (int)WGL_ARB_pixel_format.StereoArb, 0 }; // Allocate storage for the results of GetPixelFormatAttrib queries int[] values = new int[attribs.Length]; // Get the format attributes for this pixel format if (!Wgl.Arb.GetPixelFormatAttrib(device, pixelformat, 0, attribs.Length - 1, attribs, values)) { Debug.Print("[Warning] Failed to detect attributes for PixelFormat: {0}.", pixelformat); } // Skip formats that don't offer full hardware acceleration WGL_ARB_pixel_format acceleration = (WGL_ARB_pixel_format)values[0]; if (acceleration == WGL_ARB_pixel_format.FullAccelerationArb) { // Construct a new GraphicsMode to describe this format created_mode = new GraphicsMode(new IntPtr(pixelformat), new ColorFormat(values[1], values[2], values[3], values[4]), values[6], values[7], values[8] != 0 ? values[9] : 0, new ColorFormat(values[10], values[11], values[12], values[13]), values[15] == 1 ? 2 : 1, values[16] == 1 ? true : false); } } return created_mode; } #endregion #endregion } }