virabhadra's picture

Performance problem in the fullscreen mode

Hello together!

My program makes a scene with flying particles based on one idea from one example. I already don't remember where it goes from.
I use OpenTK.GameWindow

And now I have a strange (at least for me) performance problem.

1 case:
The program starts in full screen mode, it works smooth and nice, but after some seconds the CPU load increases and uses permanently one core for 100%. (see picture 1)

2 case:
If I start the program and immediately switch to another window (f.e. task manager) putting the GL-window to work on the background, but still visible behind, it doesn't load CPU, only some percents. (see pictire 2).
I tested it and during many minutes it works constantly with very low CPU load.

The behavior doesn't depend on a number of objects: same for 50 and 5000 particles.

P.S.
I cannot load the code example. It writes me that the spam filter is triggered.
I will try to upload it to a comment.

Inline Images
picture 1
picture 2

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
virabhadra's picture

Here is complete code to compile:

using System;
using System.Drawing;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using OpenTK.Input;
 
 
class Program
{
	static void Main(string[] args)
	{
		GL_Window N = new GL_Window();
		N.Run();
	}
}
 
 
public class GL_Window : GameWindow
{
	bool loaded = false;
	double fov = Math.PI / 4;
	uint texture = 1;
 
	int list = 1;
	Engine engine;
 
 
	public GL_Window()
	{
		Load += GL_Window_Load;
		RenderFrame += GL_Window_RenderFrame;
		KeyPress += GL_Window_KeyPress;
		Mouse.Move += Mouse_Move;
 
		CursorVisible = false;
	}
 
 
	void GL_Window_Load(object sender, EventArgs e)
	{
		WindowBorder = WindowBorder.Hidden;
		WindowState = WindowState.Fullscreen;
		VSync = VSyncMode.On;
 
		setup_viewport();
		init();
		create_model();
 
		load_textures(opentk_particles.Properties.Resources.particle);
	}
 
 
	void setup_viewport()
	{
		int w = Width;
		int h = Height;
		float ratio = (float) w / (float) h;
		GL.Viewport(0, 0, w, h);
 
		GL.MatrixMode(MatrixMode.Projection);
		GL.LoadIdentity();
		Matrix4 m = Matrix4.CreatePerspectiveOffCenter(-ratio / 2, ratio / 2, -0.5f, 0.5f, (float) (0.5 / Math.Tan(fov / 2)), 1000);
		GL.LoadMatrix(ref m);
	}
 
 
	void init()
	{
		GL.ClearColor(Color4.Black);
 
		GL.Enable(EnableCap.DepthTest);
		GL.DepthFunc(DepthFunction.Less);
		GL.Hint(HintTarget.PerspectiveCorrectionHint, HintMode.Nicest);
 
		GL.Enable(EnableCap.Blend);
		GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.One);
	}
 
 
	void load_textures(Bitmap image)
	{
		System.Drawing.Imaging.BitmapData bitmapdata = new System.Drawing.Imaging.BitmapData();
		Rectangle rect = new Rectangle(new Point(0, 0), image.Size);
 
		bitmapdata = image.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
 
		GL.Enable(EnableCap.Texture2D);
		GL.GenTextures(1, out texture);
		GL.BindTexture(TextureTarget.Texture2D, texture);
		GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int) All.Linear);
		GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int) All.Linear);
		GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgb, image.Width, image.Height, 0, PixelFormat.Bgr, PixelType.UnsignedByte, bitmapdata.Scan0);
		GL.Disable(EnableCap.Texture2D);
 
		image.UnlockBits(bitmapdata);
		image.Dispose();
	}
 
 
	void create_model()
	{
		GL.NewList(list, ListMode.Compile);
		{
			GL.Begin(PrimitiveType.TriangleStrip);
			float size = .3f;
			GL.TexCoord2(1, 0); GL.Vertex3(size / 2, -size / 2, 0);
			GL.TexCoord2(0, 0); GL.Vertex3(-size / 2, -size / 2, 0);
			GL.TexCoord2(1, 1); GL.Vertex3(size / 2, size / 2, 0);
			GL.TexCoord2(0, 1); GL.Vertex3(-size / 2, size / 2, 0);
			GL.End();
		}
		GL.EndList();
 
		engine = new Engine(5000, new Vector3(0, 0, -3), 5, list);
	}
 
 
	void GL_Window_RenderFrame(object sender, FrameEventArgs e)
	{
		System.Threading.Thread.Sleep(1);
 
		GL.Clear(ClearBufferMask.DepthBufferBit | ClearBufferMask.ColorBufferBit);
 
		GL.MatrixMode(MatrixMode.Modelview);
		GL.LoadIdentity();
 
		GL.Enable(EnableCap.Texture2D);
		GL.BindTexture(TextureTarget.Texture2D, texture);
		GL.DepthMask(false);
 
		engine.Render();
 
		engine.Update();
 
		GL.DepthMask(true);
		GL.Disable(EnableCap.Texture2D);
 
		SwapBuffers();
 
		loaded = true;
	}
 
 
	void GL_Window_KeyPress(object sender, KeyPressEventArgs e)
	{
		Exit();
	}
 
 
	void Mouse_Move(object sender, MouseMoveEventArgs e)
	{
		if(!loaded) return;
		//if(e.XDelta > 0 || e.YDelta > 0) Exit();
	}
}
 
 
class Engine
{
	Vector3 origin;
	Particle[] particles;
	int numParticles;
 
	int list;
	float depth;
	Random rand = new Random();
 
 
	public Engine(int num, Vector3 origin, float depth, int list)
	{
		this.numParticles = num;
		this.origin = origin;
		this.depth = depth;
		this.list = list;
 
		particles = new Particle[num];
		Reset();
	}
 
 
	void Reset()
	{
		for(int i = 0; i < numParticles; i++)
		{
			particles[i].color = new Color4((float) rand.NextDouble(), (float) rand.NextDouble(), (float) rand.NextDouble(), 1);
			ResetParticle(i);
		}
	}
 
 
	public void ResetParticle(int i)
	{
		particles[i].pos = origin;
 
		particles[i].vel = new Vector3(
			(float) (rand.NextDouble() - .5) / 200,
			(float) (rand.NextDouble() - .5) / 200,
			(float) (rand.NextDouble() - .5) / 200);
 
		particles[i].life = Environment.TickCount;
	}
 
 
	public void Update()
	{
		origin.X = (float) Math.Sin(Environment.TickCount / (double) 1000);
		origin.Y = (float) Math.Cos(Environment.TickCount / (double) 1000);
		origin.Z = -3;
 
		float t = 2;
		for(int i = 0; i < numParticles; i++)
		{
			//particles[i].vel = particles[i].vel + particles[i].acc * t;
			particles[i].pos = particles[i].pos + particles[i].vel * t;
 
			if(particles[i].pos.X > depth || particles[i].pos.X < -depth ||
				particles[i].pos.Y > depth || particles[i].pos.Y < -depth ||
				particles[i].pos.Z > depth || particles[i].pos.Z < -depth)
				ResetParticle(i);
		}
	}
 
 
	public void Render()
	{
		foreach(Particle i in particles)
		{
			GL.LoadIdentity();
			GL.Translate(i.pos);
			GL.Color4(i.color);
			GL.CallList(list);
		}
	}
}
 
 
struct Particle
{
	public Vector3 pos;
	public Vector3 vel;
	public Color4 color;
	public long life;
}
virabhadra's picture

I played with both tricks:
GraphicsContext.CurrentContext.VSync = true
and
System.Threading.Thread.Sleep(1)

There is no effect.

the Fiddler's picture

Which version of the library are you using? Is this on Windows?

Does the SDL backend also suffer from this issue? (Copy SDL2.dll from opentk/Dependencies/ to your application directory)

virabhadra's picture

I'm using Windows 8.1 and actual version of OpenTK (downloaded yesterday).

I tried to copy SDL2.dll to the folder. No effect.

The last example behaves that way. When the full screen mode it makes one core working to the maximum and the hole processor frequency is growing to the maximum.
When I switch task manager to the top, it works on background (like on pictures what I uploaded) and the process take only 0,5% of the core. When I switch back it takes the full core again.
I can switch there and back and the behavior is immediately changing.

The same behavior when I use WindowForms + glControl.

(to compile the last example need only picture of particle 128x128)

virabhadra's picture

When I start something powerful on my pc (f.e. Need For Speed Most Wanted) the processor behaves more quiet.

the Fiddler's picture

Just to make sure I understand correctly, running in a window or in the background uses a normal amount of CPU, but running fullscreen maximizes CPU use. OpenTK doesn't actually do anything different when runningin the background/foreground or windowed/fullscreen (fullscreen is just a maximized, WS_POPUP window), but the OS/drivers might be treating fullscreen windows differently (e.g. bypassing the DWM).

If you are certain that this affects both the native and SDL2 backends (check with OpenTK.Configuration.RunningOnSdl2), then the best approach would be to use a profiler and see where the extra CPU time is consumed. That might be give us an idea of what the problem might be and how to fix it.

(The Need for Speed test is not necessarily representative as it is using Direct3d, not OpenGL.)

virabhadra's picture

"OpenTK.Configuration.RunningOnSdl2" shows "false".

The program based on GameWindow is always running in the full screen mode as it is in code:

WindowBorder = WindowBorder.Hidden;
WindowState = WindowState.Fullscreen;

When on the screen is only the GL window (normal game mode) and nothing else is shown, one core is used for maximum.
When you use "Alt + Tab" and switch f.e. Task Manager before the GL Window (as on the picture) it works with minimal CPU use.

I will try to use the profiler to investigate.

the Fiddler's picture

When running with the SDL2.dll, Configuration.RunningOnSdl2 should return true. The reason why I'm suggesting to try both with and without SDL2, is to help isolate which part of the library is causing the problem: if the issue disappears with SDL2, then we automatically know it is caused by something within OpenTK.Platform.Windows. If the issue is visible with and without SDL2, then it is caused by something in the common code (e.g. OpenTK.GameWindow) or possibly by the GPU drivers.

A profiler would also help isolate the exact code in question.

virabhadra's picture

Results from the profiler, relative percentage of library using:

For the "full screen" case (processor usage 16%):
wow64win.dll - 54.53%
nvogl32v.dll - 25.27%
wow64cpu.dll - 8.04%
ntdll.dll - 5.67%
gdi32.dll - 3.24%

For the "back" case (processor usage 1%):
nvoglv32.dll - 56.36%
wow64win.dll - 28.28%
wow64cpu.dll - 2.45%
ntdll.dll - 2.26%

virabhadra's picture

Absolute values of samples of using OpenTK is comparable, but using of wow64, wow64win, wow64cpu, ntdll, nvoglv32, gdi32 and kernel32 is 50 times higher.