C#:WinForms/WPF 渲染循环
C#: WinForms/WPF Render Loop
我想制作一个渲染循环以在 WPF Window 或 WinForm 上渲染。因此我想使用 SharpGL (https://sharpgl.codeplex.com/)。为了制作我的循环,我制作了一个线程:
public void Run()
{
IsRunning = true;
this.Initialize();
while (IsRunning)
{
Render(/* arguments here */);
// pausing and stuff
}
Dispose();
}
在渲染中,我想将绘制调用发送到 GPU。到目前为止没有问题。但是 Winforms 和 WPF 需要它们自己的线程和循环。所以我不能只创建一个 Window 并像 Java 中那样使用我之前使用的 LWJGL (https://www.lwjgl.org/) 进行绘制。我必须启动另一个线程,它在应用程序中运行表单(我删除了错误处理以使其简短):
[STAThread]
private void HostWinFormsApplication()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(true);
// get Display
Display = Engine.MainModule.Resolve<IDisplay>();
Display.Initialize();
Display.Closing += (s, e) => Stop();
Application.Run(Display as Form);
}
当我的渲染器想要访问我的窗体上的 OpenGL 控件并使用它时,会发生错误,因为 WinForms(和 WPF)不希望它们的控件被其他线程操作。所以也许 Invoke 是一个选项,但这会延迟我的绘制调用并成为瓶颈。
计时器也不是一种选择,因为它不准确且不灵活……而且我就是不喜欢它。
在 Window 代码中执行所有操作是可能的,但我希望应用程序独立于其显示,以便可以更改。它应该是一个具有 Display 的应用程序,而不是一个 Display 运行 应用程序。
在 LWJGL 中,我有可能创建和初始化一个 Display,然后简单地使用它。唯一要考虑的是更新它,一切都很顺利。
所以我只想在我的渲染线程中创建一个 Window 并绘制。如果我这样做,window 将变得不可用且呈灰色,因为它需要这个 .Net-Loop。有没有可能意识到这一点,或者有人知道另一种创建 Windows 的方法吗?我可以手动处理 window 循环吗?
欢迎任何想法。
如果有一种方法可以使用 WPF Window 做到这一点,那就太棒了。然后我可以有一个 OpenGL 控件和所有 WPF-Stuff 来制作一个编辑器!
解决方法是调用
Application.DoEvents();
在我的渲染循环中手动,正如有人告诉我的那样。所以我不必使用 Application.Run
并且能够在循环线程中使用我的表单:
public void Run()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(true);
IsRunning = true;
this.Initialize();
MyForm = new CoolRenderForm();
MyForm.Show();
while (IsRunning)
{
Render(/* arguments here */);
Application.DoEvents();
// refresh form
// pausing and stuff
}
Dispose();
}
根据我的问题:How to make a render loop in WPF?
WPF 渲染循环
最好的方法是使用静态
提供的每帧回调
CompositionTarget.Rendering event.
WinForms 渲染循环
下面是我根据this blog post:
制作的WinForms渲染循环代码class
只需使用:
WinFormsAppIdleHandler.Instance.ApplicationLoopDoWork += Instance_ApplicationLoopDoWork;
WinFormsAppIdleHandler.Instance.Enabled = true;
Class:
using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
namespace Utilities.UI
{
/// <summary>
/// WinFormsAppIdleHandler implements a WinForms Render Loop (max FPS possible).
/// Reference: http://blogs.msdn.com/b/tmiller/archive/2005/05/05/415008.aspx
/// </summary>
public sealed class WinFormsAppIdleHandler
{
private readonly object _completedEventLock = new object();
private event EventHandler _applicationLoopDoWork;
//PRIVATE Constructor
private WinFormsAppIdleHandler()
{
Enabled = false;
SleepTime = 5; //You can play/test with this value
Application.Idle += Application_Idle;
}
/// <summary>
/// Singleton from:
/// http://csharpindepth.com/Articles/General/Singleton.aspx
/// </summary>
private static readonly Lazy<WinFormsAppIdleHandler> lazy = new Lazy<WinFormsAppIdleHandler>(() => new WinFormsAppIdleHandler());
public static WinFormsAppIdleHandler Instance { get { return lazy.Value; } }
/// <summary>
/// Gets or sets if must fire ApplicationLoopDoWork event.
/// </summary>
public bool Enabled { get; set; }
/// <summary>
/// Gets or sets the minimum time betwen ApplicationLoopDoWork fires.
/// </summary>
public int SleepTime { get; set; }
/// <summary>
/// Fires while the UI is free to work. Sleeps for "SleepTime" ms.
/// </summary>
public event EventHandler ApplicationLoopDoWork
{
//Reason of using locks:
//
add
{
lock (_completedEventLock)
_applicationLoopDoWork += value;
}
remove
{
lock (_completedEventLock)
_applicationLoopDoWork -= value;
}
}
/// <summary>
/// FINALMENTE! Imagem ao vivo sem travar! Muito bom!
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Application_Idle(object sender, EventArgs e)
{
//Try to update interface
while (Enabled && IsAppStillIdle())
{
OnApplicationIdleDoWork(EventArgs.Empty);
//Give a break to the processor... :)
//8 ms -> 125 Hz
//10 ms -> 100 Hz
Thread.Sleep(SleepTime);
}
}
private void OnApplicationIdleDoWork(EventArgs e)
{
var handler = _applicationLoopDoWork;
if (handler != null)
{
handler(this, e);
}
}
/// <summary>
/// Gets if the app still idle.
/// </summary>
/// <returns></returns>
private static bool IsAppStillIdle()
{
bool stillIdle = false;
try
{
Message msg;
stillIdle = !PeekMessage(out msg, IntPtr.Zero, 0, 0, 0);
}
catch (Exception e)
{
//Should never get here... I hope...
MessageBox.Show("IsAppStillIdle() Exception. Message: " + e.Message);
}
return stillIdle;
}
#region Unmanaged Get PeekMessage
// http://blogs.msdn.com/b/tmiller/archive/2005/05/05/415008.aspx
[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern bool PeekMessage(out Message msg, IntPtr hWnd, uint messageFilterMin, uint messageFilterMax, uint flags);
#endregion
}
}
我想制作一个渲染循环以在 WPF Window 或 WinForm 上渲染。因此我想使用 SharpGL (https://sharpgl.codeplex.com/)。为了制作我的循环,我制作了一个线程:
public void Run()
{
IsRunning = true;
this.Initialize();
while (IsRunning)
{
Render(/* arguments here */);
// pausing and stuff
}
Dispose();
}
在渲染中,我想将绘制调用发送到 GPU。到目前为止没有问题。但是 Winforms 和 WPF 需要它们自己的线程和循环。所以我不能只创建一个 Window 并像 Java 中那样使用我之前使用的 LWJGL (https://www.lwjgl.org/) 进行绘制。我必须启动另一个线程,它在应用程序中运行表单(我删除了错误处理以使其简短):
[STAThread]
private void HostWinFormsApplication()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(true);
// get Display
Display = Engine.MainModule.Resolve<IDisplay>();
Display.Initialize();
Display.Closing += (s, e) => Stop();
Application.Run(Display as Form);
}
当我的渲染器想要访问我的窗体上的 OpenGL 控件并使用它时,会发生错误,因为 WinForms(和 WPF)不希望它们的控件被其他线程操作。所以也许 Invoke 是一个选项,但这会延迟我的绘制调用并成为瓶颈。 计时器也不是一种选择,因为它不准确且不灵活……而且我就是不喜欢它。 在 Window 代码中执行所有操作是可能的,但我希望应用程序独立于其显示,以便可以更改。它应该是一个具有 Display 的应用程序,而不是一个 Display 运行 应用程序。 在 LWJGL 中,我有可能创建和初始化一个 Display,然后简单地使用它。唯一要考虑的是更新它,一切都很顺利。 所以我只想在我的渲染线程中创建一个 Window 并绘制。如果我这样做,window 将变得不可用且呈灰色,因为它需要这个 .Net-Loop。有没有可能意识到这一点,或者有人知道另一种创建 Windows 的方法吗?我可以手动处理 window 循环吗?
欢迎任何想法。
如果有一种方法可以使用 WPF Window 做到这一点,那就太棒了。然后我可以有一个 OpenGL 控件和所有 WPF-Stuff 来制作一个编辑器!
解决方法是调用
Application.DoEvents();
在我的渲染循环中手动,正如有人告诉我的那样。所以我不必使用 Application.Run
并且能够在循环线程中使用我的表单:
public void Run()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(true);
IsRunning = true;
this.Initialize();
MyForm = new CoolRenderForm();
MyForm.Show();
while (IsRunning)
{
Render(/* arguments here */);
Application.DoEvents();
// refresh form
// pausing and stuff
}
Dispose();
}
根据我的问题:How to make a render loop in WPF?
WPF 渲染循环
最好的方法是使用静态
提供的每帧回调CompositionTarget.Rendering event.
WinForms 渲染循环
下面是我根据this blog post:
制作的WinForms渲染循环代码class只需使用:
WinFormsAppIdleHandler.Instance.ApplicationLoopDoWork += Instance_ApplicationLoopDoWork;
WinFormsAppIdleHandler.Instance.Enabled = true;
Class:
using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
namespace Utilities.UI
{
/// <summary>
/// WinFormsAppIdleHandler implements a WinForms Render Loop (max FPS possible).
/// Reference: http://blogs.msdn.com/b/tmiller/archive/2005/05/05/415008.aspx
/// </summary>
public sealed class WinFormsAppIdleHandler
{
private readonly object _completedEventLock = new object();
private event EventHandler _applicationLoopDoWork;
//PRIVATE Constructor
private WinFormsAppIdleHandler()
{
Enabled = false;
SleepTime = 5; //You can play/test with this value
Application.Idle += Application_Idle;
}
/// <summary>
/// Singleton from:
/// http://csharpindepth.com/Articles/General/Singleton.aspx
/// </summary>
private static readonly Lazy<WinFormsAppIdleHandler> lazy = new Lazy<WinFormsAppIdleHandler>(() => new WinFormsAppIdleHandler());
public static WinFormsAppIdleHandler Instance { get { return lazy.Value; } }
/// <summary>
/// Gets or sets if must fire ApplicationLoopDoWork event.
/// </summary>
public bool Enabled { get; set; }
/// <summary>
/// Gets or sets the minimum time betwen ApplicationLoopDoWork fires.
/// </summary>
public int SleepTime { get; set; }
/// <summary>
/// Fires while the UI is free to work. Sleeps for "SleepTime" ms.
/// </summary>
public event EventHandler ApplicationLoopDoWork
{
//Reason of using locks:
//
add
{
lock (_completedEventLock)
_applicationLoopDoWork += value;
}
remove
{
lock (_completedEventLock)
_applicationLoopDoWork -= value;
}
}
/// <summary>
/// FINALMENTE! Imagem ao vivo sem travar! Muito bom!
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Application_Idle(object sender, EventArgs e)
{
//Try to update interface
while (Enabled && IsAppStillIdle())
{
OnApplicationIdleDoWork(EventArgs.Empty);
//Give a break to the processor... :)
//8 ms -> 125 Hz
//10 ms -> 100 Hz
Thread.Sleep(SleepTime);
}
}
private void OnApplicationIdleDoWork(EventArgs e)
{
var handler = _applicationLoopDoWork;
if (handler != null)
{
handler(this, e);
}
}
/// <summary>
/// Gets if the app still idle.
/// </summary>
/// <returns></returns>
private static bool IsAppStillIdle()
{
bool stillIdle = false;
try
{
Message msg;
stillIdle = !PeekMessage(out msg, IntPtr.Zero, 0, 0, 0);
}
catch (Exception e)
{
//Should never get here... I hope...
MessageBox.Show("IsAppStillIdle() Exception. Message: " + e.Message);
}
return stillIdle;
}
#region Unmanaged Get PeekMessage
// http://blogs.msdn.com/b/tmiller/archive/2005/05/05/415008.aspx
[System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern bool PeekMessage(out Message msg, IntPtr hWnd, uint messageFilterMin, uint messageFilterMax, uint flags);
#endregion
}
}