为了在Fullscreen中以60 FPS和稳定的帧速率显示图像,我在学习如何使用C#(OpenTK库)在OpenGL中进行操作。
我有一个运行GameWindow类的基本项目,它可以将图像作为纹理上传到gpu内存并以60 FPS显示。我的问题是,当进程完成时,永远不会释放gpu内存。只有当我关闭应用程序时,它才会被释放。
我尝试了最常见的建议,如GL.DeleteTexture并手动调用GC几次,但似乎没有任何效果。每次从Windows窗体启动GameWindow时,gpu内存使用量都会增加。
有关如何以更好的方式管理GPU内存的任何建议吗?我的应用程序对于gpu内存至关重要,因为我想在gpu内存中加载大量图像(> 1GB)并以60 FPS显示它们并具有良好的时序,其中CPU在稳定帧率方面非常不可靠。稳定的帧率对此至关重要。
这是我的示例GameWindow类:
using System;
using System.Collections.Generic;
using OpenTK.Graphics.OpenGL;
using OpenTK;
using System.Diagnostics;
using System.Threading;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Windows.Forms;
namespace OpenGLAnimation
{
class OpenGLShow : GameWindow
{
#region --- Global Variables -------------------------------------
public bool bRunning = true;
public bool bFinishedLoad = false;
public int WorkTime = 0;
private Screen Scrn;
private List<int> TextureList = new List<int>();
private Thread MainThread;
#endregion
#region --- Constructor ------------------------------------------
/// <summary>
/// Initializes the Class
/// </summary>
/// <param name="FilesPath">Directory path with all the images, that will be shown.</param>
/// <param name="_Screen">Information about the screen, on which the images will be shown.</param>
/// <param name="ScreenIndex">Index of the screen (first monitor = 0; second monitor = 1 ..</param>
public OpenGLShow (Screen _Screen, DisplayDevice disp, int ScreenIndex = 1) :
base(_Screen.WorkingArea.Width, _Screen.WorkingArea.Height, OpenTK.Graphics.GraphicsMode.Default, "FBSScreen", GameWindowFlags.Fullscreen, disp)
{
Scrn = _Screen;
bFinishedLoad = false;
}
#endregion
#region --- Event triggered functions ----------------------------
protected override void OnLoad(EventArgs EvAr)
{
WindowBorder = WindowBorder.Hidden;
WindowState = WindowState.Fullscreen;
VSync = VSyncMode.On;
GL.Viewport(0, 0, Scrn.WorkingArea.Width, Scrn.WorkingArea.Height);
GL.ClearColor(Color.MidnightBlue);
GL.Enable(EnableCap.Texture2D);
Context.MakeCurrent(null);
MainThread = new Thread(DoWork);
MainThread.Start();
}
#endregion
private void DoWork()
{
while (!bFinishedLoad) { Thread.Sleep(500); Console.WriteLine("Textures are loaded. Waiting.."); }
MakeCurrent();
int index = 0;
int MaxIndex = TextureList.Count;
Stopwatch swRender = new Stopwatch();
Stopwatch swSumTime = new Stopwatch();
swSumTime.Restart();
while (bRunning && TextureList.Count > 0)
{
swRender.Restart();
if (index >= MaxIndex) { index = 0; }
GL.ClearColor(Color.MidnightBlue);
GL.BindTexture(TextureTarget.Texture2D, TextureList[index++]);
GL.Begin(PrimitiveType.Polygon);
GL.Color4(Color.White);
GL.TexCoord2(0, 1); GL.Vertex2(-1, -1);
GL.TexCoord2(0, 0); GL.Vertex2(-1, 1);
GL.TexCoord2(1, 0); GL.Vertex2( 1, 1);
GL.TexCoord2(1, 1); GL.Vertex2( 1, -1);
GL.End();
SwapBuffers();
GL.Finish();
Title = "FPS: " + Math.Round((decimal)1000 / swRender.ElapsedMilliseconds, 2) + " | Frame: " + index + "/" + MaxIndex;
if (WorkTime > 0 && WorkTime < swSumTime.ElapsedMilliseconds) { Abort(); }
}
}
public void Abort()
{
bRunning = false;
Thread.Sleep(500);
GL.BindTexture(TextureTarget.Texture2D, 0); // unbind textures
//foreach (int i in TextureList) // doesn't free GPU memory
//{
// GL.DeleteTexture(i);
// GL.DeleteBuffer(i);
//}
TextureList.Clear();
Close();
GC.Collect();
GC.WaitForPendingFinalizers();
}
public void Start()
{
MainThread.Start();
}
private void AddTexture(string sPath)
{
int tmpTexture = 0;
Bitmap bmp = new Bitmap(sPath);
GL.GenTextures(1, out tmpTexture);
GL.BindTexture(TextureTarget.Texture2D, tmpTexture);
BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, bmpData.Width, bmpData.Height, 0, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, bmpData.Scan0);
bmp.UnlockBits(bmpData);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
TextureList.Add(tmpTexture);
}
public void LoadImages(string sPath)
{
List<string> Files = new List<string>();
TextureList.Clear();
if (File.Exists(sPath))
{
Files.Add(sPath);
}
else if (Directory.Exists(sPath))
{
Files = new List<string>(Directory.GetFiles(sPath));
}
foreach (string s in Files)
{
if (s.Contains(".png") || s.Contains(".bmp") || s.Contains(".jpg"))
{
AddTexture(s);
}
}
bFinishedLoad = true;
}
}
}
要测试班级,您可以使用NuGet下载OpenTK refs,只需使用按钮创建一个新表格,即可执行以下操作
string filesPath = @"C:\Users\Public\Pictures\Sample Pictures";
Screen[] scr = Screen.AllScreens;
OpenTK.DisplayDevice display = OpenTK.DisplayDevice.GetDisplay(OpenTK.DisplayIndex.First);
OpenGLAnimation.OpenGLShow gwForm = new OpenGLAnimation.OpenGLShow(scr[0], display, 0);
gwForm.WorkTime = 2000; // if worktime = 0 then the images will be looped forever
gwForm.LoadImages(filesPath); // load images as textures to GPU memory. Can be a png/bmp file or a directory with such files
gwForm.Run();
基本部分基本上是AddTexture(),DoWork()和Abort()函数,我将图像上传到gpu内存,在显示器上显示它们并使用Abort()来停止&amp;完成这个过程。