using System; using System.Diagnostics; using System.Windows.Forms; using Amphora; using Blacklight.Core; using Blacklight.Core.Cameras; namespace Konis.Testbed { public partial class Testbed : Form { // Windows runs its own message loop while a window is being dragged or resized, so Application.Idle stops // firing and the next frame would otherwise get the whole drag as one step. private static readonly TimeSpan LongestFrame = TimeSpan.FromMilliseconds(100); private readonly Stopwatch clock = Stopwatch.StartNew(); private readonly ParticleDemo demo; private TimeSpan? lastFrame = null; private double speed = 1.0; public Testbed() { InitializeComponent(); demo = new ParticleDemo(viewport.DrawableSurface, viewport.Camera, new Rand((ulong)DateTime.UtcNow.Ticks)); // The left button spawns, so the camera drags with the middle one. viewport.Controller.Options.DragNavigateButton = PointerButtons.Middle; viewport.InputRouter.AddLowestPriority(viewport.Controller); viewport.InputRouter.AddHighestPriority( new ClickSpawner(demo, viewport.DrawableSurface, viewport.Camera)); } /// /// Gets or sets whether the form closes itself once it has drawn a frame. /// public bool ExitAfterFirstFrame { get; set; } = false; /// /// Whether the pump has nothing waiting, so the loop can run another frame rather than yield to it. /// private static bool IsApplicationIdle() { return !NativeMethods.PeekMessage( out NativeMethods.QueuedMessage _, window: IntPtr.Zero, // Messages from any window on this thread. filterMin: 0, filterMax: 0, // Every message. remove: NativeMethods.PM_NOREMOVE); } private void Frame() { TimeSpan now = clock.Elapsed; TimeSpan elapsed = now - (lastFrame ?? now); lastFrame = now; if (elapsed > LongestFrame) { elapsed = LongestFrame; } Duration real = Duration.FromSeconds(elapsed.TotalSeconds); demo.World.Update(new FxTime(real, real * speed, speed)); demo.Draw(); if (ExitAfterFirstFrame) { Application.Idle -= ApplicationIdle; Close(); } } // The loop runs flat out and lets the vsync inside the frame's submit pace it, so the pump only gets a turn // once something is actually waiting on it. private void ApplicationIdle(object sender, EventArgs e) { try { while (IsApplicationIdle()) { Frame(); } } catch (GraphicsDeviceFailedException exception) { // The device can't draw again, so there's nothing to resume. Application.Idle -= ApplicationIdle; MessageBox.Show(this, exception.Message + "\n\nThis is a problem with the graphics driver or hardware, not the testbed.", "Graphics driver failure", MessageBoxButtons.OK, MessageBoxIcon.Error); Close(); } } protected override void OnLoad(EventArgs e) { base.OnLoad(e); // A run that closes after one frame spawns every effect first, so that frame draws particles. if (ExitAfterFirstFrame) { for (int i = 0; i < Effects.All.Length; i++) { demo.EffectIndex = i; demo.Spawn(new Azimuth.Vector3(0, 0, 0), preWarmSeconds: 1f); } demo.EffectIndex = 0; } Application.Idle += ApplicationIdle; } protected override void OnFormClosing(FormClosingEventArgs e) { Application.Idle -= ApplicationIdle; base.OnFormClosing(e); } } }