mirror of
https://github.com/Ryujinx/Opentk.git
synced 2024-12-26 18:55:38 +00:00
319 lines
11 KiB
C#
319 lines
11 KiB
C#
// This code is in the Public Domain. It is provided "as is"
|
|
// without express or implied warranty of any kind.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Drawing;
|
|
using System.Threading;
|
|
using OpenTK;
|
|
using OpenTK.Graphics;
|
|
using OpenTK.Graphics.OpenGL;
|
|
using OpenTK.Input;
|
|
|
|
namespace Examples.Tutorial
|
|
{
|
|
/// <summary>
|
|
/// Demonstrates how to decouple rendering from the main thread.
|
|
/// Note that all OpenGL function calls should take place at the rendering thread -
|
|
/// OpenGL will not be available on the main thread at all!
|
|
/// </summary>
|
|
[Example("GameWindow Threaded", ExampleCategory.OpenTK, "GameWindow", 3, Documentation = "GameWindowThreaded")]
|
|
public class ThreadedRendering : GameWindow
|
|
{
|
|
bool viewport_changed = true;
|
|
int viewport_width, viewport_height;
|
|
bool position_changed = true;
|
|
int position_x, position_y;
|
|
float position_dx, position_dy;
|
|
bool exit = false;
|
|
Thread rendering_thread;
|
|
object update_lock = new object();
|
|
const float GravityAccel = -9.81f;
|
|
struct Particle
|
|
{
|
|
public Vector2 Position;
|
|
public Vector2 Velocity;
|
|
public Color4 Color;
|
|
}
|
|
List<Particle> Particles = new List<Particle>();
|
|
Random rand = new Random();
|
|
|
|
public ThreadedRendering()
|
|
: base(800, 600)
|
|
{
|
|
Keyboard.KeyDown += delegate(object sender, KeyboardKeyEventArgs e)
|
|
{
|
|
if (e.Key == Key.Escape)
|
|
this.Exit();
|
|
};
|
|
|
|
Keyboard.KeyUp += delegate(object sender, KeyboardKeyEventArgs e)
|
|
{
|
|
if (e.Key == Key.F11)
|
|
if (this.WindowState == WindowState.Fullscreen)
|
|
this.WindowState = WindowState.Normal;
|
|
else
|
|
this.WindowState = WindowState.Fullscreen;
|
|
};
|
|
|
|
Resize += delegate(object sender, EventArgs e)
|
|
{
|
|
// Note that we cannot call any OpenGL methods directly. What we can do is set
|
|
// a flag and respond to it from the rendering thread.
|
|
lock (update_lock)
|
|
{
|
|
viewport_changed = true;
|
|
viewport_width = Width;
|
|
viewport_height = Height;
|
|
}
|
|
};
|
|
|
|
Move += delegate(object sender, EventArgs e)
|
|
{
|
|
// Note that we cannot call any OpenGL methods directly. What we can do is set
|
|
// a flag and respond to it from the rendering thread.
|
|
lock (update_lock)
|
|
{
|
|
position_changed = true;
|
|
position_dx = (position_x - X) / (float)Width;
|
|
position_dy = (position_y - Y) / (float)Height;
|
|
position_x = X;
|
|
position_y = Y;
|
|
}
|
|
};
|
|
|
|
// Make sure initial position are correct, otherwise we'll give a huge
|
|
// initial velocity to the balls.
|
|
position_x = X;
|
|
position_y = Y;
|
|
}
|
|
|
|
#region OnLoad
|
|
|
|
/// <summary>
|
|
/// Setup OpenGL and load resources here.
|
|
/// </summary>
|
|
/// <param name="e">Not used.</param>
|
|
protected override void OnLoad(EventArgs e)
|
|
{
|
|
Context.MakeCurrent(null); // Release the OpenGL context so it can be used on the new thread.
|
|
|
|
rendering_thread = new Thread(RenderLoop);
|
|
rendering_thread.IsBackground = true;
|
|
rendering_thread.Start();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region OnUnload
|
|
|
|
/// <summary>
|
|
/// Release resources here.
|
|
/// </summary>
|
|
/// <param name="e">Not used.</param>
|
|
protected override void OnUnload(EventArgs e)
|
|
{
|
|
exit = true; // Set a flag that the rendering thread should stop running.
|
|
rendering_thread.Join();
|
|
|
|
base.OnUnload(e);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region OnUpdateFrame
|
|
|
|
/// <summary>
|
|
/// Add your game logic here.
|
|
/// </summary>
|
|
/// <param name="e">Contains timing information.</param>
|
|
/// <remarks>There is no need to call the base implementation.</remarks>
|
|
protected override void OnUpdateFrame(FrameEventArgs e)
|
|
{
|
|
// Nothing to do!
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region OnRenderFrame
|
|
|
|
/// <summary>
|
|
/// Ignored. All rendering is performed on our own rendering function.
|
|
/// </summary>
|
|
/// <param name="e">Contains timing information.</param>
|
|
/// <remarks>There is no need to call the base implementation.</remarks>
|
|
protected override void OnRenderFrame(FrameEventArgs e)
|
|
{
|
|
// Nothing to do. Release the CPU to other threads.
|
|
Thread.Sleep(1);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region RenderLoop
|
|
|
|
void RenderLoop()
|
|
{
|
|
MakeCurrent(); // The context now belongs to this thread. No other thread may use it!
|
|
|
|
VSync = VSyncMode.On;
|
|
|
|
for (int i = 0; i < 64; i++)
|
|
{
|
|
Particle p = new Particle();
|
|
p.Position = new Vector2((float)rand.NextDouble() * 2 - 1, (float)rand.NextDouble() * 2 - 1);
|
|
p.Color.R = (float)rand.NextDouble();
|
|
p.Color.G = (float)rand.NextDouble();
|
|
p.Color.B = (float)rand.NextDouble();
|
|
Particles.Add(p);
|
|
}
|
|
|
|
// Since we don't use OpenTK's timing mechanism, we need to keep time ourselves;
|
|
Stopwatch render_watch = new Stopwatch();
|
|
Stopwatch update_watch = new Stopwatch();
|
|
update_watch.Start();
|
|
render_watch.Start();
|
|
|
|
GL.ClearColor(Color.MidnightBlue);
|
|
GL.Enable(EnableCap.DepthTest);
|
|
GL.Enable(EnableCap.PointSmooth);
|
|
GL.PointSize(16);
|
|
|
|
while (!exit)
|
|
{
|
|
Update(update_watch.Elapsed.TotalSeconds);
|
|
update_watch.Reset();
|
|
update_watch.Start();
|
|
|
|
Render(render_watch.Elapsed.TotalSeconds);
|
|
render_watch.Reset(); // Stopwatch may be inaccurate over larger intervals.
|
|
render_watch.Start(); // Plus, timekeeping is easier if we always start counting from 0.
|
|
|
|
SwapBuffers();
|
|
}
|
|
|
|
Context.MakeCurrent(null);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Update
|
|
|
|
void Update(double time)
|
|
{
|
|
lock (update_lock)
|
|
{
|
|
// When the user moves the window we make the particles react to
|
|
// this movement. The reaction is semi-random and not physically
|
|
// correct. It looks quite good, however.
|
|
if (position_changed)
|
|
{
|
|
for (int i = 0; i < Particles.Count; i++)
|
|
{
|
|
Particle p = Particles[i];
|
|
p.Velocity += new Vector2(
|
|
16 * (position_dx + 0.05f * (float)(rand.NextDouble() - 0.5)),
|
|
32 * (position_dy + 0.05f * (float)(rand.NextDouble() - 0.5)));
|
|
Particles[i] = p;
|
|
}
|
|
|
|
position_changed = false;
|
|
}
|
|
}
|
|
|
|
// For simplicity, we use simple Euler integration to simulate particle movement.
|
|
// This is not accurate, especially under varying timesteps (as is the case here).
|
|
// A better solution would have been time-corrected Verlet integration, as
|
|
// described here:
|
|
// http://www.gamedev.net/reference/programming/features/verlet/
|
|
for (int i = 0; i < Particles.Count; i++)
|
|
{
|
|
Particle p = Particles[i];
|
|
|
|
p.Velocity.X = Math.Abs(p.Position.X) >= 1 ?-p.Velocity.X * 0.92f : p.Velocity.X * 0.97f;
|
|
p.Velocity.Y = Math.Abs(p.Position.Y) >= 1 ? -p.Velocity.Y * 0.92f : p.Velocity.Y * 0.97f;
|
|
if (p.Position.Y > -0.99)
|
|
{
|
|
p.Velocity.Y += (float)(GravityAccel * time);
|
|
}
|
|
else
|
|
{
|
|
if (Math.Abs(p.Velocity.Y) < 0.02)
|
|
{
|
|
p.Velocity.Y = 0;
|
|
p.Position.Y = -1;
|
|
}
|
|
else
|
|
{
|
|
p.Velocity.Y *= 0.9f;
|
|
}
|
|
}
|
|
|
|
p.Position += p.Velocity * (float)time;
|
|
if (p.Position.Y <= -1)
|
|
p.Position.Y = -1;
|
|
|
|
Particles[i] = p;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Render
|
|
|
|
/// <summary>
|
|
/// This is our main rendering function, which executes on the rendering thread.
|
|
/// </summary>
|
|
public void Render(double time)
|
|
{
|
|
lock (update_lock)
|
|
{
|
|
if (viewport_changed)
|
|
{
|
|
GL.Viewport(0, 0, viewport_width, viewport_height);
|
|
viewport_changed = false;
|
|
}
|
|
}
|
|
|
|
Matrix4 perspective =
|
|
Matrix4.CreateOrthographic(2, 2, -1, 1);
|
|
GL.MatrixMode(MatrixMode.Projection);
|
|
GL.LoadMatrix(ref perspective);
|
|
|
|
GL.MatrixMode(MatrixMode.Modelview);
|
|
GL.LoadIdentity();
|
|
|
|
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
|
|
|
|
GL.Begin(BeginMode.Points);
|
|
foreach (Particle p in Particles)
|
|
{
|
|
GL.Color4(p.Color);
|
|
GL.Vertex2(p.Position);
|
|
}
|
|
GL.End();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region public static void Main()
|
|
|
|
/// <summary>
|
|
/// Entry point of this example.
|
|
/// </summary>
|
|
[STAThread]
|
|
public static void Main()
|
|
{
|
|
using (GameWindow example = new ThreadedRendering())
|
|
{
|
|
// Get the title and category of this example using reflection.
|
|
Utilities.SetWindowTitle(example);
|
|
example.Run();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|