From a4d2a31386b701436fdd9b2806ed8e9c7dd6d1c4 Mon Sep 17 00:00:00 2001 From: thefiddler Date: Thu, 9 Jan 2014 23:36:28 +0100 Subject: [PATCH 01/15] [Mac] OpenGL 3.x/4.x require core profile flag SDL will fail to construct an OpenGL 3.x/4.x context on Mac OS X, unless ContextProfileFlags.CORE is specified. Fixes issue #44 Upstream enhancement request at https://bugzilla.libsdl.org/show_bug.cgi?id=2342 --- Source/OpenTK/Platform/SDL2/Sdl2GraphicsContext.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Source/OpenTK/Platform/SDL2/Sdl2GraphicsContext.cs b/Source/OpenTK/Platform/SDL2/Sdl2GraphicsContext.cs index a34019f9..c7714b58 100644 --- a/Source/OpenTK/Platform/SDL2/Sdl2GraphicsContext.cs +++ b/Source/OpenTK/Platform/SDL2/Sdl2GraphicsContext.cs @@ -157,6 +157,8 @@ namespace OpenTK.Platform.SDL2 int major, int minor, GraphicsContextFlags flags) { + ContextProfileFlags cpflags = 0; + if (mode.AccumulatorFormat.BitsPerPixel > 0) { SDL.GL.SetAttribute(ContextAttribute.ACCUM_ALPHA_SIZE, mode.AccumulatorFormat.Alpha); @@ -203,6 +205,15 @@ namespace OpenTK.Platform.SDL2 { SDL.GL.SetAttribute(ContextAttribute.CONTEXT_MAJOR_VERSION, major); SDL.GL.SetAttribute(ContextAttribute.CONTEXT_MINOR_VERSION, minor); + + // Workaround for https://github.com/opentk/opentk/issues/44 + // Mac OS X desktop OpenGL 3.x/4.x contexts require require + // ContextProfileFlags.Core, otherwise they will fail to construct. + if (Configuration.RunningOnMacOS && major >= 3 && + (flags & GraphicsContextFlags.Embedded) == 0) + { + cpflags |= ContextProfileFlags.CORE; + } } if ((flags & GraphicsContextFlags.Debug) != 0) @@ -223,7 +234,6 @@ namespace OpenTK.Platform.SDL2 */ { - ContextProfileFlags cpflags = 0; if ((flags & GraphicsContextFlags.Embedded) != 0) { cpflags |= ContextProfileFlags.ES; From ef5aedba6f751874e90c7a16de9ff2ec2efe0243 Mon Sep 17 00:00:00 2001 From: "Stefanos A." Date: Fri, 10 Jan 2014 09:24:59 +0100 Subject: [PATCH 02/15] [Win] More robust WGL extension detection Affects issue #42 and issue #45 --- Source/OpenTK/Platform/Windows/WglHelper.cs | 87 ++++++++++--------- .../OpenTK/Platform/Windows/WinGLContext.cs | 12 ++- 2 files changed, 55 insertions(+), 44 deletions(-) diff --git a/Source/OpenTK/Platform/Windows/WglHelper.cs b/Source/OpenTK/Platform/Windows/WglHelper.cs index fd1c3dee..0e863213 100644 --- a/Source/OpenTK/Platform/Windows/WglHelper.cs +++ b/Source/OpenTK/Platform/Windows/WglHelper.cs @@ -8,6 +8,7 @@ #endregion using System; +using System.Collections.Generic; using System.Runtime.InteropServices; using System.Reflection; @@ -34,6 +35,9 @@ namespace OpenTK.Platform.Windows internal const string Library = "OPENGL32.DLL"; + readonly static Dictionary extensions = + new Dictionary(); + private static Assembly assembly; private static Type wglClass; private static Type delegatesClass; @@ -121,43 +125,61 @@ namespace OpenTK.Platform.Windows #endregion - #region public static partial class Arb - - /// Contains ARB extensions for WGL. - public static partial class Arb + public static bool SupportsExtension(string name) { - /// - /// Checks if a Wgl extension is supported by the given context. - /// - /// The device context. - /// The extension to check. - /// True if the extension is supported by the given context, false otherwise - public static bool SupportsExtension(WinGLContext context, string ext) + return SupportsExtension(Wgl.GetCurrentDC(), name); + } + + /// + /// Checks if a Wgl extension is supported by the given context. + /// + /// The device context. + /// The extension to check. + /// True if the extension is supported by the given context, false otherwise + public static bool SupportsExtension(IntPtr dc, string name) + { + if (extensions.Count == 0) { // We cache this locally, as another thread might create a context which doesn't support this method. // The design is far from ideal, but there's no good solution to this issue as long as we are using // static WGL/GL classes. Fortunately, this issue is extremely unlikely to arise in practice, as you'd // have to create one accelerated and one non-accelerated context in the same application, with the // non-accelerated context coming second. - Wgl.Delegates.GetExtensionsStringARB get = Wgl.Delegates.wglGetExtensionsStringARB; + Wgl.Delegates.GetExtensionsStringARB get_arb = Wgl.Delegates.wglGetExtensionsStringARB; + Wgl.Delegates.GetExtensionsStringEXT get_ext = Wgl.Delegates.wglGetExtensionsStringEXT; + IntPtr str_ptr = + get_arb != null ? get_arb(dc) : + get_ext != null ? get_ext() : + IntPtr.Zero; - if (get != null) + if (str_ptr != IntPtr.Zero) { - string[] extensions = null; + string str; + unsafe { - extensions = new string((sbyte*)get(context.DeviceContext)) - .Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); + str = new string((sbyte*)str_ptr); } - if (extensions == null || extensions.Length == 0) - return false; - foreach (string s in extensions) - if (s == ext) - return true; + foreach (string ext in str.Split(' ')) + { + extensions.Add(ext, true); + } } - return false; } + + if (extensions.Count > 0) + { + return extensions.ContainsKey(name); + } + return false; + } + + #region public static partial class Arb + + /// Contains ARB extensions for WGL. + public static partial class Arb + { } #endregion @@ -167,27 +189,6 @@ namespace OpenTK.Platform.Windows /// Contains EXT extensions for WGL. public static partial class Ext { - private static string[] extensions; - /// - /// Checks if a Wgl extension is supported by the given context. - /// - /// The extension to check. - /// True if the extension is supported by the given context, false otherwise - public static bool SupportsExtension(string ext) - { - if (Wgl.Delegates.wglGetExtensionsStringEXT != null) - { - if (extensions == null || rebuildExtensionList) - { - extensions = Wgl.Ext.GetExtensionsString().Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); - Array.Sort(extensions); - rebuildExtensionList = false; - } - - return Array.BinarySearch(extensions, ext) != -1; - } - return false; - } } #endregion diff --git a/Source/OpenTK/Platform/Windows/WinGLContext.cs b/Source/OpenTK/Platform/Windows/WinGLContext.cs index 837c3e76..af55d3e5 100644 --- a/Source/OpenTK/Platform/Windows/WinGLContext.cs +++ b/Source/OpenTK/Platform/Windows/WinGLContext.cs @@ -31,6 +31,7 @@ namespace OpenTK.Platform.Windows IntPtr device_context; bool vsync_supported; + bool vsync_tear_supported; readonly WinGraphicsMode ModeSelector; @@ -325,7 +326,13 @@ namespace OpenTK.Platform.Windows lock (LoadLock) { if (vsync_supported) + { + if (value < 0 && !vsync_tear_supported) + { + value = 1; + } Wgl.Ext.SwapInterval(value); + } } } } @@ -339,8 +346,11 @@ namespace OpenTK.Platform.Windows lock (LoadLock) { Wgl.LoadAll(); - vsync_supported = Wgl.Arb.SupportsExtension(this, "WGL_EXT_swap_control") && + vsync_supported = + Wgl.SupportsExtension(DeviceContext, "WGL_EXT_swap_control") && Wgl.Load("wglGetSwapIntervalEXT") && Wgl.Load("wglSwapIntervalEXT"); + vsync_tear_supported = + Wgl.SupportsExtension(DeviceContext, "WGL_EXT_swap_tear"); } base.LoadAll(); From bdfcf43e0b6b18d6bf21b371f5b5c6618c3b757a Mon Sep 17 00:00:00 2001 From: "Stefanos A." Date: Fri, 10 Jan 2014 15:41:57 +0100 Subject: [PATCH 03/15] [Win] More robust pixel format selection This patch adds more robust checks for WGL_ARB_pixel_format and WGL_ARB_multisample before using the relevant extensions, and adds checks whether Wgl.Arb.ChoosePixelFormat() returns a valid pixel format before trying to use it (thanks to Repetier for catching this edge case.) Additionally, the ChoosePixelFormatPFD code-path now heavily penalizes single-buffered modes when the user requests a double-buffered mode. Affects issues #42 and #45 --- .../Platform/Windows/WinGraphicsMode.cs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/Source/OpenTK/Platform/Windows/WinGraphicsMode.cs b/Source/OpenTK/Platform/Windows/WinGraphicsMode.cs index a5886f98..1fc3954d 100644 --- a/Source/OpenTK/Platform/Windows/WinGraphicsMode.cs +++ b/Source/OpenTK/Platform/Windows/WinGraphicsMode.cs @@ -99,7 +99,8 @@ namespace OpenTK.Platform.Windows GraphicsMode ChoosePixelFormatARB(IntPtr device, GraphicsMode mode) { GraphicsMode created_mode = null; - if (Wgl.Delegates.wglChoosePixelFormatARB != null) + if (Wgl.SupportsExtension("WGL_ARB_pixel_format") && + Wgl.Delegates.wglChoosePixelFormatARB != null) { List attributes = new List(); attributes.Add((int)WGL_ARB_pixel_format.AccelerationArb); @@ -168,7 +169,8 @@ namespace OpenTK.Platform.Windows attributes.Add(mode.AccumulatorFormat.Alpha); } - if (mode.Samples > 0) + if (mode.Samples > 0 && + Wgl.SupportsExtension("WGL_ARB_multisample")) { attributes.Add((int)WGL_ARB_multisample.SampleBuffersArb); attributes.Add(1); @@ -193,7 +195,8 @@ namespace OpenTK.Platform.Windows int[] format = new int[1]; int count; - if (Wgl.Arb.ChoosePixelFormat(device, attributes.ToArray(), null, format.Length, format, out count)) + if (Wgl.Arb.ChoosePixelFormat(device, attributes.ToArray(), null, format.Length, format, out count) + && count > 0) { created_mode = DescribePixelFormatARB(device, format[0]); } @@ -271,13 +274,6 @@ namespace OpenTK.Platform.Windows { 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) @@ -304,6 +300,9 @@ namespace OpenTK.Platform.Windows valid &= GetAccelerationType(ref pfd) == requested_acceleration_type; valid &= (pfd.Flags & flags) == flags; valid &= pfd.PixelType == PixelType.RGBA; // indexed modes not currently supported + // heavily penalize single-buffered modes when the user requests double buffering + if ((pfd.Flags & PixelFormatDescriptorFlags.DOUBLEBUFFER) == 0 && mode.Buffers > 1) + dist += 1000; 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); From aff97198726d225e31122807ecd154a4c7045e54 Mon Sep 17 00:00:00 2001 From: "Stefanos A." Date: Fri, 10 Jan 2014 16:55:20 +0100 Subject: [PATCH 04/15] [SDL2] Implemented GetWindowWMInfo --- Source/OpenTK/Platform/SDL2/Sdl2.cs | 99 ++++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 1 deletion(-) diff --git a/Source/OpenTK/Platform/SDL2/Sdl2.cs b/Source/OpenTK/Platform/SDL2/Sdl2.cs index 5dc31dd9..d38039be 100644 --- a/Source/OpenTK/Platform/SDL2/Sdl2.cs +++ b/Source/OpenTK/Platform/SDL2/Sdl2.cs @@ -94,7 +94,6 @@ namespace OpenTK.Platform.SDL2 [SuppressUnmanagedCodeSecurity] [DllImport(lib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SDL_CreateWindow", ExactSpelling = true)] public static extern IntPtr CreateWindow(string title, int x, int y, int w, int h, WindowFlags flags); - //public static extern IntPtr SDL_CreateWindow(string title, int x, int y, int w, int h, WindowFlags flags); [SuppressUnmanagedCodeSecurity] [DllImport(lib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SDL_CreateWindowFrom", ExactSpelling = true)] @@ -420,6 +419,34 @@ namespace OpenTK.Platform.SDL2 [DllImport(lib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SDL_WarpMouseInWindow", ExactSpelling = true)] public static extern void WarpMouseInWindow(IntPtr window, int x, int y); + #region SysWM + + /// + /// Retrieves driver-dependent window information. + /// + /// + /// The window about which information is being requested. + /// + /// + /// Returns driver-dependent information about the specified window. + /// + /// + /// True, if the function is implemented and the version number of the info struct is valid; + /// false, otherwise. + /// + public static bool GetWindowWMInfo(IntPtr window, out SysWMInfo info) + { + info = new SysWMInfo(); + info.Version = GetVersion(); + return GetWindowWMInfoInternal(window, ref info); + } + + [SuppressUnmanagedCodeSecurity] + [DllImport(lib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SDL_GetWindowWMInfo", ExactSpelling = true)] + static extern bool GetWindowWMInfoInternal(IntPtr window, ref SysWMInfo info); + + #endregion + public partial class GL { [SuppressUnmanagedCodeSecurity] @@ -1200,6 +1227,17 @@ namespace OpenTK.Platform.SDL2 JOYSTICK | HAPTIC | GAMECONTROLLER } + enum SysWMType + { + Unknown = 0, + Windows, + X11, + Wayland, + DirectFB, + Cocoa, + UIKit, + } + enum WindowEventID : byte { NONE, @@ -1517,6 +1555,65 @@ namespace OpenTK.Platform.SDL2 public int Height; } + struct SysWMInfo + { + public Version Version; + public SysWMType Subsystem; + public SysInfo Info; + + [StructLayout(LayoutKind.Explicit)] + public struct SysInfo + { + [FieldOffset(0)] + public WindowsInfo Windows; + [FieldOffset(0)] + public X11Info X11; + [FieldOffset(0)] + public WaylandInfo Wayland; + [FieldOffset(0)] + public DirectFBInfo DirectFB; + [FieldOffset(0)] + public CocoaInfo Cocoa; + [FieldOffset(0)] + public UIKitInfo UIKit; + + public struct WindowsInfo + { + public IntPtr Window; + } + + public struct X11Info + { + public IntPtr Display; + public IntPtr Window; + } + + public struct WaylandInfo + { + public IntPtr Display; + public IntPtr Surface; + public IntPtr ShellSurface; + } + + public struct DirectFBInfo + { + public IntPtr Dfb; + public IntPtr Window; + public IntPtr Surface; + } + + public struct CocoaInfo + { + public IntPtr Window; + } + + public struct UIKitInfo + { + public IntPtr Window; + } + } + } + struct TextEditingEvent { public const int TextSize = 32; From 19eb72b3a9afe06536f346a822aac4e57841e5d4 Mon Sep 17 00:00:00 2001 From: "Stefanos A." Date: Fri, 10 Jan 2014 16:56:26 +0100 Subject: [PATCH 05/15] [OpenTK] Fixed Utilities.CreateSdl2WindowInfo Utilities.CreateSdl2WindowInfo should store the specified windowHandle directly instead of trying to call SDL.SDL.CreateWindowFrom. --- Source/OpenTK/Platform/Utilities.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/OpenTK/Platform/Utilities.cs b/Source/OpenTK/Platform/Utilities.cs index 67c92a78..cbf0e811 100644 --- a/Source/OpenTK/Platform/Utilities.cs +++ b/Source/OpenTK/Platform/Utilities.cs @@ -301,7 +301,7 @@ namespace OpenTK.Platform public static IWindowInfo CreateSdl2WindowInfo(IntPtr windowHandle) { return new OpenTK.Platform.SDL2.Sdl2WindowInfo( - SDL2.SDL.CreateWindowFrom(windowHandle), null); + windowHandle, null); } #endregion From c31f64f7e1508d7aad08a043692aa123a9cebdbf Mon Sep 17 00:00:00 2001 From: "Stefanos A." Date: Sat, 11 Jan 2014 01:46:38 +0100 Subject: [PATCH 06/15] [OpenTK] Frameskip needs TargetUpdateFrequency!=0 --- Source/OpenTK/GameWindow.cs | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/Source/OpenTK/GameWindow.cs b/Source/OpenTK/GameWindow.cs index 8012eba3..a4a61314 100644 --- a/Source/OpenTK/GameWindow.cs +++ b/Source/OpenTK/GameWindow.cs @@ -441,20 +441,23 @@ namespace OpenTK { // Raise UpdateFrame events until we catch up with our target update rate. double update_elapsed = MathHelper.Clamp(timestamp - update_timestamp, 0.0, 1.0); - if (RaiseUpdateFrame(update_elapsed)) + if (update_elapsed > 0) { - update_period = update_elapsed; - update_timestamp = timestamp; - timestamp = watch.Elapsed.TotalSeconds; - update_time = timestamp - update_timestamp; + if (RaiseUpdateFrame(update_elapsed)) + { + update_period = update_elapsed; + update_timestamp = timestamp; + timestamp = watch.Elapsed.TotalSeconds; + update_time = timestamp - update_timestamp; + } + else + { + // We have executed enough UpdateFrame events to catch up. + // Break and issue a RenderFrame event. + break; + } } - else - { - // We have executed enough UpdateFrame events to catch up. - // Break and issue a RenderFrame event. - break; - } - } while (++frameskip < max_frameskip); + } while (TargetRenderFrequency > 0 && ++frameskip < max_frameskip); timestamp = watch.Elapsed.TotalSeconds; double render_elapsed = MathHelper.Clamp(timestamp - render_timestamp, 0.0, 1.0); From fca9f930e466a48448ccc159352281a7d68153d8 Mon Sep 17 00:00:00 2001 From: "Stefanos A." Date: Sun, 12 Jan 2014 21:37:18 +0100 Subject: [PATCH 07/15] [OpenTK] Fix UpdateFrame loop condition Multiple UpdateFrame events should be raised to match the desired TargetUpdateFrequency, when TargetUpdateFrequency > 0. The loop would incorrectly check for TargetRenderFrequency instead. Affects issue #43 --- Source/OpenTK/GameWindow.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/OpenTK/GameWindow.cs b/Source/OpenTK/GameWindow.cs index a4a61314..ad8ed413 100644 --- a/Source/OpenTK/GameWindow.cs +++ b/Source/OpenTK/GameWindow.cs @@ -457,7 +457,7 @@ namespace OpenTK break; } } - } while (TargetRenderFrequency > 0 && ++frameskip < max_frameskip); + } while (TargetUpdateFrequency > 0 && ++frameskip < max_frameskip); timestamp = watch.Elapsed.TotalSeconds; double render_elapsed = MathHelper.Clamp(timestamp - render_timestamp, 0.0, 1.0); From 7afe48c9798aa3e5eb5124ba0a978fdd81985d79 Mon Sep 17 00:00:00 2001 From: thefiddler Date: Sun, 12 Jan 2014 22:05:15 +0100 Subject: [PATCH 08/15] [Audio] Don't crash when Alc.GetString() returns null MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Alc.GetString() could crash if the unmanaged code returned null due to any kind of failure. This is now fixed and better documented. Additionally, the array overload for Alc.GetString() will now correctly forward the ‘device’ parameter to unmanaged code. --- Source/OpenTK/Audio/OpenAL/Alc/Alc.cs | 75 ++++++++++++++++++++------- 1 file changed, 55 insertions(+), 20 deletions(-) diff --git a/Source/OpenTK/Audio/OpenAL/Alc/Alc.cs b/Source/OpenTK/Audio/OpenAL/Alc/Alc.cs index 38767a15..371bd81f 100644 --- a/Source/OpenTK/Audio/OpenAL/Alc/Alc.cs +++ b/Source/OpenTK/Audio/OpenAL/Alc/Alc.cs @@ -9,6 +9,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Runtime.InteropServices; using System.Security; @@ -262,7 +263,13 @@ namespace OpenTK.Audio.OpenAL /// A string containing the name of the Device. public static string GetString(IntPtr device, AlcGetString param) { - return Marshal.PtrToStringAnsi(GetStringPrivate(device, param)); + IntPtr pstr = GetStringPrivate(device, param); + string str = String.Empty; + if (pstr != IntPtr.Zero) + { + str = Marshal.PtrToStringAnsi(pstr); + } + return str; } /// This function returns a List of strings related to the context. @@ -277,26 +284,54 @@ namespace OpenTK.Audio.OpenAL public static IList GetString(IntPtr device, AlcGetStringList param) { List result = new List(); - IntPtr t = GetStringPrivate(IntPtr.Zero, (AlcGetString)param); - System.Text.StringBuilder sb = new System.Text.StringBuilder(); - byte b; - int offset = 0; - do - { - b = Marshal.ReadByte(t, offset++); - if (b != 0) - sb.Append((char)b); - if (b == 0) - { - result.Add(sb.ToString()); - if (Marshal.ReadByte(t, offset) == 0) // offset already properly increased through ++ - break; // 2x null - else - sb.Remove(0, sb.Length); // 1x null - } - } while (true); - return (IList)result; + // We cannot use Marshal.PtrToStringAnsi(), + // because alcGetString is defined to return either a nul-terminated string, + // or an array of nul-terminated strings terminated by an extra nul. + // Marshal.PtrToStringAnsi() will fail in the latter case (it will only + // return the very first string in the array.) + // We'll have to marshal this ourselves. + IntPtr t = GetStringPrivate(device, (AlcGetString)param); + if (t != IntPtr.Zero) + { + System.Text.StringBuilder sb = new System.Text.StringBuilder(); + byte b; + int offset = 0; + do + { + b = Marshal.ReadByte(t, offset++); + if (b != 0) + { + sb.Append((char)b); + } + else + { + // One string from the array is complete + result.Add(sb.ToString()); + + // Check whether the array has finished + // Note: offset already been increased through offset++ above + if (Marshal.ReadByte(t, offset) == 0) + { + // 2x consecutive nuls, we've read the whole array + break; + } + else + { + // Another string is starting, clear the StringBuilder + sb.Remove(0, sb.Length); + } + } + } + while (true); + } + else + { + Debug.Print("[Audio] Alc.GetString({0}, {1}) returned null.", + device, param); + } + + return result; } [DllImport(Alc.Lib, EntryPoint = "alcGetIntegerv", ExactSpelling = true, CallingConvention = Alc.Style, CharSet = CharSet.Ansi), SuppressUnmanagedCodeSecurity()] From b3554bb74c1c41a9ccb91eae0020a2b1e0e8c89d Mon Sep 17 00:00:00 2001 From: "Stefanos A." Date: Mon, 13 Jan 2014 11:22:33 +0100 Subject: [PATCH 09/15] [OpenTK] Simplified update and render loops --- Source/OpenTK/GameWindow.cs | 93 +++++++++++++++++++------------------ 1 file changed, 47 insertions(+), 46 deletions(-) diff --git a/Source/OpenTK/GameWindow.cs b/Source/OpenTK/GameWindow.cs index ad8ed413..4a9cb092 100644 --- a/Source/OpenTK/GameWindow.cs +++ b/Source/OpenTK/GameWindow.cs @@ -433,64 +433,65 @@ namespace OpenTK void DispatchUpdateAndRenderFrame(object sender, EventArgs e) { - const int max_frameskip = 10; - int frameskip = 0; double timestamp = watch.Elapsed.TotalSeconds; + double elapsed = MathHelper.Clamp(timestamp - update_timestamp, 0.0, 1.0); - do + // Calculate how many update events we need to execute in order to reach + // our desired TargetUpdateFrequency + int update_count = TargetUpdatePeriod != 0 ? + (int)(elapsed / TargetUpdatePeriod) : + 1; + + while (update_count > 0) { // Raise UpdateFrame events until we catch up with our target update rate. - double update_elapsed = MathHelper.Clamp(timestamp - update_timestamp, 0.0, 1.0); - if (update_elapsed > 0) + if (elapsed > 0) { - if (RaiseUpdateFrame(update_elapsed)) - { - update_period = update_elapsed; - update_timestamp = timestamp; - timestamp = watch.Elapsed.TotalSeconds; - update_time = timestamp - update_timestamp; - } - else - { - // We have executed enough UpdateFrame events to catch up. - // Break and issue a RenderFrame event. - break; - } + RaiseUpdateFrame(elapsed, ref timestamp); + --update_count; } - } while (TargetUpdateFrequency > 0 && ++frameskip < max_frameskip); + elapsed = MathHelper.Clamp(timestamp - update_timestamp, 0.0, 1.0); + } + //timestamp = watch.Elapsed.TotalSeconds; + elapsed = MathHelper.Clamp(timestamp - render_timestamp, 0.0, 1.0); + if (elapsed > 0 && elapsed >= TargetRenderPeriod) + { + RaiseRenderFrame(elapsed, ref timestamp); + } + + Thread.Sleep(1); + } + + void RaiseUpdateFrame(double elapsed, ref double timestamp) + { + // Raise UpdateFrame event + update_args.Time = elapsed; + OnUpdateFrameInternal(update_args); + + // Update UpdatePeriod/UpdateFrequency properties + update_period = elapsed; + + // Update UpdateTime property + update_timestamp = timestamp; timestamp = watch.Elapsed.TotalSeconds; - double render_elapsed = MathHelper.Clamp(timestamp - render_timestamp, 0.0, 1.0); - if (RaiseRenderFrame(render_elapsed)) - { - render_period = render_elapsed; - render_timestamp = timestamp; - timestamp = watch.Elapsed.TotalSeconds; - render_time = timestamp - render_timestamp; - } - } - - bool RaiseUpdateFrame(double time) - { - if (time > 0 && time >= TargetUpdatePeriod) - { - update_args.Time = time; - OnUpdateFrameInternal(update_args); - return true; - } - return false; + update_time = timestamp - update_timestamp; } - bool RaiseRenderFrame(double time) + void RaiseRenderFrame(double elapsed, ref double timestamp) { - if (time > 0 && time >= TargetRenderPeriod) - { - render_args.Time = time; - OnRenderFrameInternal(render_args); - return true; - } - return false; + // Raise RenderFrame event + render_args.Time = elapsed; + OnRenderFrameInternal(render_args); + + // Update RenderPeriod/UpdateFrequency properties + render_period = elapsed; + + // Update RenderTime property + render_timestamp = timestamp; + timestamp = watch.Elapsed.TotalSeconds; + render_time = timestamp - render_timestamp; } #endregion From 3eccb898212ba0c254b8c7fcd8420f89e09a8d2b Mon Sep 17 00:00:00 2001 From: "Stefanos A." Date: Mon, 13 Jan 2014 11:36:56 +0100 Subject: [PATCH 10/15] [OpenTK] Remove Thread.Sleep() from loop timing --- Source/OpenTK/GameWindow.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Source/OpenTK/GameWindow.cs b/Source/OpenTK/GameWindow.cs index 4a9cb092..cc1e3e7c 100644 --- a/Source/OpenTK/GameWindow.cs +++ b/Source/OpenTK/GameWindow.cs @@ -431,10 +431,15 @@ namespace OpenTK } } + double ClampElapsed(double elapsed) + { + return MathHelper.Clamp(elapsed, 0.0, 1.0); + } + void DispatchUpdateAndRenderFrame(object sender, EventArgs e) { double timestamp = watch.Elapsed.TotalSeconds; - double elapsed = MathHelper.Clamp(timestamp - update_timestamp, 0.0, 1.0); + double elapsed = ClampElapsed(timestamp - update_timestamp); // Calculate how many update events we need to execute in order to reach // our desired TargetUpdateFrequency @@ -450,17 +455,15 @@ namespace OpenTK RaiseUpdateFrame(elapsed, ref timestamp); --update_count; } - elapsed = MathHelper.Clamp(timestamp - update_timestamp, 0.0, 1.0); + elapsed = ClampElapsed(timestamp - update_timestamp); } //timestamp = watch.Elapsed.TotalSeconds; - elapsed = MathHelper.Clamp(timestamp - render_timestamp, 0.0, 1.0); + elapsed = ClampElapsed(timestamp - render_timestamp); if (elapsed > 0 && elapsed >= TargetRenderPeriod) { RaiseRenderFrame(elapsed, ref timestamp); } - - Thread.Sleep(1); } void RaiseUpdateFrame(double elapsed, ref double timestamp) From 97e49b76b2753bd5f7734193637767ddf91d9ae4 Mon Sep 17 00:00:00 2001 From: "Stefanos A." Date: Tue, 14 Jan 2014 13:04:30 +0100 Subject: [PATCH 11/15] [OpenTK] Fix UpdateFrame quantization error The UpdateFrame event rate will now match TargetUpdatePeriod even if vsync is enabled. Previously, it would be quantized to a multiple or integer fraction of of the vsync rate. --- Source/OpenTK/GameWindow.cs | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/Source/OpenTK/GameWindow.cs b/Source/OpenTK/GameWindow.cs index cc1e3e7c..af55fe04 100644 --- a/Source/OpenTK/GameWindow.cs +++ b/Source/OpenTK/GameWindow.cs @@ -90,6 +90,8 @@ namespace OpenTK double update_timestamp; // timestamp of last UpdateFrame event double render_timestamp; // timestamp of last RenderFrame event + double update_epsilon; // quantization error for UpdateFrame events + VSyncMode vsync; FrameEventArgs update_args = new FrameEventArgs(); @@ -439,26 +441,22 @@ namespace OpenTK void DispatchUpdateAndRenderFrame(object sender, EventArgs e) { double timestamp = watch.Elapsed.TotalSeconds; - double elapsed = ClampElapsed(timestamp - update_timestamp); + double elapsed = 0; - // Calculate how many update events we need to execute in order to reach - // our desired TargetUpdateFrequency - int update_count = TargetUpdatePeriod != 0 ? - (int)(elapsed / TargetUpdatePeriod) : - 1; - - while (update_count > 0) + elapsed = ClampElapsed(timestamp - update_timestamp); + while (elapsed > 0 && elapsed + update_epsilon >= TargetUpdatePeriod) { - // Raise UpdateFrame events until we catch up with our target update rate. - if (elapsed > 0) - { - RaiseUpdateFrame(elapsed, ref timestamp); - --update_count; - } + RaiseUpdateFrame(elapsed, ref timestamp); + + // Calculate difference (positive or negative) between + // actual elapsed time and target elapsed time. We must + // compensate for this difference. + update_epsilon += elapsed - TargetUpdatePeriod; + + // Prepare for next loop elapsed = ClampElapsed(timestamp - update_timestamp); } - //timestamp = watch.Elapsed.TotalSeconds; elapsed = ClampElapsed(timestamp - render_timestamp); if (elapsed > 0 && elapsed >= TargetRenderPeriod) { From 7b98255626876870cc921693fde5c0ef384a604a Mon Sep 17 00:00:00 2001 From: "Stefanos A." Date: Tue, 14 Jan 2014 13:27:09 +0100 Subject: [PATCH 12/15] [Examples] Improved timing display GameWindowStates will now display the average fps and draw three moving boxes based on different timing methods. If the timing implementation in OpenTK is working correctly, all three boxes should be moving at the same speed. --- .../Examples/OpenTK/Test/GameWindowStates.cs | 145 +++++++++++++++--- 1 file changed, 122 insertions(+), 23 deletions(-) diff --git a/Source/Examples/OpenTK/Test/GameWindowStates.cs b/Source/Examples/OpenTK/Test/GameWindowStates.cs index 10ae3141..f5cbcfea 100644 --- a/Source/Examples/OpenTK/Test/GameWindowStates.cs +++ b/Source/Examples/OpenTK/Test/GameWindowStates.cs @@ -25,9 +25,23 @@ namespace Examples.Tests int texture; bool mouse_in_window = false; bool viewport_changed = true; + + // time drift Stopwatch watch = new Stopwatch(); double update_time, render_time; + // timing information + double timestamp; + int update_count; + int update_fps; + int render_count; + int render_fps; + + // position of moving objects on screen + double variable_update_timestep_pos = -1; + double variable_refresh_timestep_pos = -1; + double fixed_update_timestep_pos = -1; + public GameWindowStates() : base(800, 600, GraphicsMode.Default) { @@ -35,10 +49,10 @@ namespace Examples.Tests Keyboard.KeyRepeat = true; KeyDown += KeyDownHandler; KeyPress += KeyPressHandler; - + MouseEnter += delegate { mouse_in_window = true; }; MouseLeave += delegate { mouse_in_window = false; }; - + Mouse.Move += MouseMoveHandler; Mouse.ButtonDown += MouseButtonHandler; Mouse.ButtonUp += MouseButtonHandler; @@ -110,7 +124,7 @@ namespace Examples.Tests { return val > max ? max : val < min ? min : val; } - + static float DrawString(Graphics gfx, string str, int line) { return DrawString(gfx, str, line, 0); @@ -217,6 +231,8 @@ namespace Examples.Tests { double clock_time = watch.Elapsed.TotalSeconds; update_time += e.Time; + timestamp += e.Time; + update_count++; using (Graphics gfx = Graphics.FromImage(TextBitmap)) { @@ -247,22 +263,47 @@ namespace Examples.Tests // Timing information line++; DrawString(gfx, "Timing:", line++); - DrawString(gfx, String.Format("Frequency: update ({0:f2}/{1:f2}); render ({2:f2}/{3:f2})", - UpdateFrequency, TargetUpdateFrequency, RenderFrequency, TargetRenderFrequency), line++); - DrawString(gfx, String.Format("Period: update ({0:f4}/{1:f4}); render ({2:f4}/{3:f4})", - UpdatePeriod, TargetUpdatePeriod, RenderPeriod, TargetRenderPeriod), line++); + DrawString(gfx, + String.Format("Frequency: update {4} ({0:f2}/{1:f2}); render {5} ({2:f2}/{3:f2})", + UpdateFrequency, TargetUpdateFrequency, + RenderFrequency, TargetRenderFrequency, + update_fps, render_fps), + line++); + DrawString(gfx, + String.Format("Period: update {4:N4} ({0:f4}/{1:f4}); render {5:N4} ({2:f4}/{3:f4})", + UpdatePeriod, TargetUpdatePeriod, + RenderPeriod, TargetRenderPeriod, + 1.0 / update_fps, 1.0 / render_fps), + line++); DrawString(gfx, String.Format("Time: update {0:f4}; render {1:f4}", - UpdateTime, RenderTime), line++); + UpdateTime, RenderTime), line++); DrawString(gfx, String.Format("Drift: clock {0:f4}; update {1:f4}; render {2:f4}", clock_time, clock_time - update_time, clock_time - render_time), line++); DrawString(gfx, String.Format("Text: {0}", TypedText.ToString()), line++); + if (timestamp >= 1) + { + timestamp -= 1; + update_fps = update_count; + render_fps = render_count; + update_count = 0; + render_count = 0; + + } + // Input information line = DrawKeyboards(gfx, line); line = DrawMice(gfx, line); line = DrawJoysticks(gfx, line); line = DrawLegacyJoysticks(gfx, Joysticks, line); } + + fixed_update_timestep_pos += TargetUpdatePeriod; + variable_update_timestep_pos += e.Time; + if (fixed_update_timestep_pos >= 1) + fixed_update_timestep_pos -= 2; + if (variable_update_timestep_pos >= 1) + variable_update_timestep_pos -= 2; } int DrawJoysticks(Graphics gfx, int line) @@ -323,7 +364,31 @@ namespace Examples.Tests protected override void OnRenderFrame(FrameEventArgs e) { render_time += e.Time; + render_count++; + GL.Clear(ClearBufferMask.ColorBufferBit); + + if (viewport_changed) + { + viewport_changed = false; + GL.Viewport(0, 0, Width, Height); + } + + DrawText(); + + DrawMovingObjects(); + + variable_refresh_timestep_pos += e.Time; + if (variable_refresh_timestep_pos >= 1) + variable_refresh_timestep_pos -= 2; + + SwapBuffers(); + } + + // Uploads our text Bitmap to an OpenGL texture + // and displays is to screen. + private void DrawText() + { System.Drawing.Imaging.BitmapData data = TextBitmap.LockBits( new System.Drawing.Rectangle(0, 0, TextBitmap.Width, TextBitmap.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); @@ -331,29 +396,63 @@ namespace Examples.Tests PixelType.UnsignedByte, data.Scan0); TextBitmap.UnlockBits(data); - if (viewport_changed) - { - viewport_changed = false; - - GL.Viewport(0, 0, Width, Height); - - Matrix4 ortho_projection = Matrix4.CreateOrthographicOffCenter(0, Width, Height, 0, -1, 1); - GL.MatrixMode(MatrixMode.Projection); - GL.LoadMatrix(ref ortho_projection); - } - - GL.Clear(ClearBufferMask.ColorBufferBit); + Matrix4 text_projection = Matrix4.CreateOrthographicOffCenter(0, Width, Height, 0, -1, 1); + GL.MatrixMode(MatrixMode.Projection); + GL.LoadMatrix(ref text_projection); + GL.MatrixMode(MatrixMode.Modelview); + GL.LoadIdentity(); + GL.Color4(Color4.White); + GL.Enable(EnableCap.Texture2D); GL.Begin(PrimitiveType.Quads); - GL.TexCoord2(0, 0); GL.Vertex2(0, 0); GL.TexCoord2(1, 0); GL.Vertex2(TextBitmap.Width, 0); GL.TexCoord2(1, 1); GL.Vertex2(TextBitmap.Width, TextBitmap.Height); GL.TexCoord2(0, 1); GL.Vertex2(0, TextBitmap.Height); - GL.End(); + GL.Disable(EnableCap.Texture2D); + } - SwapBuffers(); + // Draws three moving objects, using three different timing methods: + // 1. fixed framerate based on TargetUpdatePeriod + // 2. variable framerate based on UpdateFrame e.Time + // 3. variable framerate based on RenderFrame e.Time + // If the timing implementation is correct, all three objects + // should be moving at the same speed, regardless of the current + // UpdatePeriod and RenderPeriod. + void DrawMovingObjects() + { + Matrix4 thing_projection = Matrix4.CreateOrthographic(2, 2, -1, 1); + GL.MatrixMode(MatrixMode.Projection); + GL.LoadMatrix(ref thing_projection); + + GL.MatrixMode(MatrixMode.Modelview); + GL.LoadIdentity(); + GL.Translate(fixed_update_timestep_pos, -0.2, 0); + GL.Color4(Color4.Red); + DrawRectangle(); + + GL.MatrixMode(MatrixMode.Modelview); + GL.LoadIdentity(); + GL.Translate(variable_update_timestep_pos, -0.4, 0); + GL.Color4(Color4.DarkGoldenrod); + DrawRectangle(); + + GL.MatrixMode(MatrixMode.Modelview); + GL.LoadIdentity(); + GL.Translate(variable_refresh_timestep_pos, -0.8, 0); + GL.Color4(Color4.DarkGreen); + DrawRectangle(); + } + + private void DrawRectangle() + { + GL.Begin(PrimitiveType.Quads); + GL.Vertex2(-0.05, -0.05); + GL.Vertex2(+0.05, -0.05); + GL.Vertex2(+0.05, +0.05); + GL.Vertex2(-0.05, +0.05); + GL.End(); } public static void Main() From 95d71bc0ccc1b1ad25a9afda81b56efd9f14932c Mon Sep 17 00:00:00 2001 From: "Stefanos A." Date: Tue, 14 Jan 2014 13:33:41 +0100 Subject: [PATCH 13/15] [OpenTK] Respect a TargetUpdatePeriod of zero --- Source/OpenTK/GameWindow.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Source/OpenTK/GameWindow.cs b/Source/OpenTK/GameWindow.cs index af55fe04..92002162 100644 --- a/Source/OpenTK/GameWindow.cs +++ b/Source/OpenTK/GameWindow.cs @@ -455,6 +455,15 @@ namespace OpenTK // Prepare for next loop elapsed = ClampElapsed(timestamp - update_timestamp); + + if (TargetUpdatePeriod <= Double.Epsilon) + { + // According to the TargetUpdatePeriod documentation, + // a TargetUpdatePeriod of zero means we will raise + // UpdateFrame events as fast as possible (one event + // per ProcessEvents() call) + break; + } } elapsed = ClampElapsed(timestamp - render_timestamp); From 1f44cf27a14a77c544f0f8bef96cf8c62e752a30 Mon Sep 17 00:00:00 2001 From: "Stefanos A." Date: Tue, 14 Jan 2014 13:55:24 +0100 Subject: [PATCH 14/15] [OpenTK] Do not hang when update rate too high OpenTK will now detect when an UpdateFrame handler is consistently taking too long to finish, and stop raising UpdateFrame events. This gives ProcessEvents() a chance to execute and will protect the application from hanging up. --- Source/OpenTK/GameWindow.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Source/OpenTK/GameWindow.cs b/Source/OpenTK/GameWindow.cs index 92002162..5cd8b81a 100644 --- a/Source/OpenTK/GameWindow.cs +++ b/Source/OpenTK/GameWindow.cs @@ -92,6 +92,8 @@ namespace OpenTK double update_epsilon; // quantization error for UpdateFrame events + bool is_running_slowly; // true, when UpdatePeriod cannot reach TargetUpdatePeriod + VSyncMode vsync; FrameEventArgs update_args = new FrameEventArgs(); @@ -440,6 +442,7 @@ namespace OpenTK void DispatchUpdateAndRenderFrame(object sender, EventArgs e) { + int is_running_slowly_retries = 4; double timestamp = watch.Elapsed.TotalSeconds; double elapsed = 0; @@ -464,6 +467,14 @@ namespace OpenTK // per ProcessEvents() call) break; } + + is_running_slowly = update_epsilon >= TargetUpdatePeriod; + if (is_running_slowly && --is_running_slowly_retries == 0) + { + // If UpdateFrame consistently takes longer than TargetUpdateFrame + // stop raising events to avoid hanging inside the UpdateFrame loop. + break; + } } elapsed = ClampElapsed(timestamp - render_timestamp); From 0c9b612bff7d88af8416c18147f9bc7d537a18ce Mon Sep 17 00:00:00 2001 From: "Stefanos A." Date: Tue, 14 Jan 2014 14:20:38 +0100 Subject: [PATCH 15/15] [OpenTK] Increase max Update/RenderFrame rates Given the new 144Hz monitors on the market today, it makes sense to increase the Update/RenderFrame limit from 200Hz to 500Hz. --- Source/OpenTK/GameWindow.cs | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/Source/OpenTK/GameWindow.cs b/Source/OpenTK/GameWindow.cs index 5cd8b81a..559c90b1 100644 --- a/Source/OpenTK/GameWindow.cs +++ b/Source/OpenTK/GameWindow.cs @@ -75,6 +75,8 @@ namespace OpenTK { #region --- Fields --- + const double MaxFrequency = 500.0; // Frequency cap for Update/RenderFrame events + readonly Stopwatch watch = new Stopwatch(); IGraphicsContext glContext; @@ -681,7 +683,7 @@ namespace OpenTK /// /// /// A value of 0.0 indicates that RenderFrame events are generated at the maximum possible frequency (i.e. only limited by the hardware's capabilities). - /// Values lower than 1.0Hz are clamped to 1.0Hz. Values higher than 200.0Hz are clamped to 200.0Hz. + /// Values lower than 1.0Hz are clamped to 0.0. Values higher than 500.0Hz are clamped to 200.0Hz. /// public double TargetRenderFrequency { @@ -699,11 +701,11 @@ namespace OpenTK { TargetRenderPeriod = 0.0; } - else if (value <= 200.0) + else if (value <= MaxFrequency) { TargetRenderPeriod = 1.0 / value; } - else Debug.Print("Target render frequency clamped to 200.0Hz."); // TODO: Where is it actually performed? + else Debug.Print("Target render frequency clamped to {0}Hz.", MaxFrequency); } } @@ -716,7 +718,7 @@ namespace OpenTK /// /// /// A value of 0.0 indicates that RenderFrame events are generated at the maximum possible frequency (i.e. only limited by the hardware's capabilities). - /// Values lower than 0.005 seconds (200Hz) are clamped to 0.0. Values higher than 1.0 seconds (1Hz) are clamped to 1.0. + /// Values lower than 0.002 seconds (500Hz) are clamped to 0.0. Values higher than 1.0 seconds (1Hz) are clamped to 1.0. /// public double TargetRenderPeriod { @@ -728,7 +730,7 @@ namespace OpenTK set { EnsureUndisposed(); - if (value <= 0.005) + if (value <= 1 / MaxFrequency) { target_render_period = 0.0; } @@ -749,7 +751,7 @@ namespace OpenTK /// /// /// A value of 0.0 indicates that UpdateFrame events are generated at the maximum possible frequency (i.e. only limited by the hardware's capabilities). - /// Values lower than 1.0Hz are clamped to 1.0Hz. Values higher than 200.0Hz are clamped to 200.0Hz. + /// Values lower than 1.0Hz are clamped to 0.0. Values higher than 500.0Hz are clamped to 500.0Hz. /// public double TargetUpdateFrequency { @@ -767,11 +769,11 @@ namespace OpenTK { TargetUpdatePeriod = 0.0; } - else if (value <= 200.0) + else if (value <= MaxFrequency) { TargetUpdatePeriod = 1.0 / value; } - else Debug.Print("Target update frequency clamped to 200.0Hz."); // TODO: Where is it actually performed? + else Debug.Print("Target render frequency clamped to {0}Hz.", MaxFrequency); } } @@ -784,7 +786,7 @@ namespace OpenTK /// /// /// A value of 0.0 indicates that UpdateFrame events are generated at the maximum possible frequency (i.e. only limited by the hardware's capabilities). - /// Values lower than 0.005 seconds (200Hz) are clamped to 0.0. Values higher than 1.0 seconds (1Hz) are clamped to 1.0. + /// Values lower than 0.002 seconds (500Hz) are clamped to 0.0. Values higher than 1.0 seconds (1Hz) are clamped to 1.0. /// public double TargetUpdatePeriod { @@ -796,7 +798,7 @@ namespace OpenTK set { EnsureUndisposed(); - if (value <= 0.005) + if (value <= 1 / MaxFrequency) { target_update_period = 0.0; } @@ -804,7 +806,7 @@ namespace OpenTK { target_update_period = value; } - else Debug.Print("Target update period clamped to 1.0 seconds."); // TODO: Where is it actually performed? + else Debug.Print("Target update period clamped to 1.0 seconds."); } }