diff --git a/LibDmd/LibDmd.csproj b/LibDmd/LibDmd.csproj
index 401af9b4f..490fff803 100644
--- a/LibDmd/LibDmd.csproj
+++ b/LibDmd/LibDmd.csproj
@@ -314,6 +314,9 @@
AlphanumericControl.xaml
+
+ OpenGLControlExt.xaml
+
SliderValueSetting.xaml
@@ -495,6 +498,10 @@
Designer
MSBuild:Compile
+
+ Designer
+ MSBuild:Compile
+
MSBuild:Compile
Designer
diff --git a/LibDmd/Output/Virtual/Dmd/OpenGLControlExt.xaml b/LibDmd/Output/Virtual/Dmd/OpenGLControlExt.xaml
new file mode 100644
index 000000000..07f48fb07
--- /dev/null
+++ b/LibDmd/Output/Virtual/Dmd/OpenGLControlExt.xaml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/LibDmd/Output/Virtual/Dmd/OpenGLControlExt.xaml.cs b/LibDmd/Output/Virtual/Dmd/OpenGLControlExt.xaml.cs
new file mode 100644
index 000000000..9a67940a9
--- /dev/null
+++ b/LibDmd/Output/Virtual/Dmd/OpenGLControlExt.xaml.cs
@@ -0,0 +1,510 @@
+using System;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Threading;
+using SharpGL;
+using SharpGL.RenderContextProviders;
+using SharpGL.Version;
+using SharpGL.WPF;
+
+namespace LibDmd.Output.Virtual.Dmd
+{
+ ///
+ /// Interaction logic for OpenGLControlExt.xaml
+ ///
+ /// This is a slightly modified version of the one provided in SharpGL for better performance. The original SharpGL
+ /// in the method GetFormatedBitmapSource calls BitmapConversion method that perform a GC for each frame. This
+ /// implementation uses proposed by ftlPhysicsGuy in this issue: https://github.com/dwmkerr/sharpgl/issues/121
+ /// to avoid the GC.
+ ///
+ public partial class OpenGLControlExt : UserControl
+ {
+ // Fields to support the WritableBitmap method of rendering the image for display
+ byte[] m_imageBuffer;
+ WriteableBitmap m_writeableBitmap;
+ Int32Rect m_imageRect;
+ int m_imageStride;
+ double m_dpiX = 96;
+ double m_dpiY = 96;
+ PixelFormat m_format = PixelFormats.Bgra32;
+ int m_bytesPerPixel = 32 >> 3;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public OpenGLControlExt()
+ {
+ InitializeComponent();
+
+ timer = new DispatcherTimer();
+
+ Unloaded += OpenGLControlExt_Unloaded;
+ Loaded += OpenGLControlExt_Loaded;
+ }
+
+ ///
+ /// Handles the Loaded event of the OpenGLControlExt control.
+ ///
+ /// The source of the event.
+ /// The Instance containing the event data.
+ private void OpenGLControlExt_Loaded(object sender, RoutedEventArgs routedEventArgs)
+ {
+ SizeChanged += OpenGLControlExt_SizeChanged;
+
+ UpdateOpenGLControl((int)RenderSize.Width, (int)RenderSize.Height);
+
+ // DispatcherTimer setup
+ timer.Tick += timer_Tick;
+ if (RenderTrigger == RenderTrigger.TimerBased)
+ {
+ timer.Start();
+ }
+ }
+
+ ///
+ /// Handles the Unloaded event of the OpenGLControl control.
+ ///
+ /// The source of the event.
+ /// The Instance containing the event data.
+ private void OpenGLControlExt_Unloaded(object sender, RoutedEventArgs routedEventArgs)
+ {
+ SizeChanged -= OpenGLControlExt_SizeChanged;
+
+ timer.Stop();
+ timer.Tick -= timer_Tick;
+ }
+
+ ///
+ /// Handles the SizeChanged event of the OpenGLControl control.
+ ///
+ /// The source of the event.
+ /// The Instance containing the event data.
+ void OpenGLControlExt_SizeChanged(object sender, SizeChangedEventArgs e)
+ {
+ UpdateOpenGLControl((int)e.NewSize.Width, (int)e.NewSize.Height);
+ }
+
+ ///
+ /// This method is used to set the dimensions and the viewport of the opengl control.
+ ///
+ /// The width of the OpenGL drawing area.
+ /// The height of the OpenGL drawing area.
+ private void UpdateOpenGLControl(int width, int height)
+ {
+ // Lock on OpenGL.
+ lock (gl)
+ {
+ gl.SetDimensions(width, height);
+
+ // Set the viewport.
+ gl.Viewport(0, 0, width, height);
+
+ // If we have a project handler, call it...
+ if (width != -1 && height != -1)
+ {
+ RaiseEvent(new OpenGLRoutedEventArgs(ResizedEvent, gl));
+ }
+ }
+ // Force re-creation of image buffer since size has changed
+ m_imageBuffer = null;
+ }
+
+ ///
+ /// When overridden in a derived class, is invoked whenever application code or
+ /// internal processes call .
+ ///
+ public override void OnApplyTemplate()
+ {
+ // Call the base.
+ base.OnApplyTemplate();
+
+ // Lock on OpenGL.
+ lock (gl)
+ {
+ // Create OpenGL.
+ gl.Create(OpenGLVersion, RenderContextType, 1, 1, 32, null);
+ }
+
+ // Force re-set of dpi and format settings
+ m_dpiX = 0;
+
+ // Set the most basic OpenGL styles.
+ gl.ShadeModel(OpenGL.GL_SMOOTH);
+ gl.ClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+ gl.ClearDepth(1.0f);
+ gl.Enable(OpenGL.GL_DEPTH_TEST);
+ gl.DepthFunc(OpenGL.GL_LEQUAL);
+ gl.Hint(OpenGL.GL_PERSPECTIVE_CORRECTION_HINT, OpenGL.GL_NICEST);
+
+ // Fire the OpenGL initialised event.
+ RaiseEvent(new OpenGLRoutedEventArgs(OpenGLInitializedEvent, gl));
+
+ timer.Interval = new TimeSpan(0, 0, 0, 0, (int)(1000.0 / FrameRate));
+ }
+
+ ///
+ /// Handles the Tick event of the timer control.
+ ///
+ /// The source of the event.
+ /// The instance containing the event data.
+ void timer_Tick(object sender, EventArgs e)
+ {
+ DoRender();
+ if (RenderTrigger == RenderTrigger.Manual && gl == null && gl.RenderContextProvider == null)
+ {
+ timer.Stop();
+ }
+ }
+
+ ///
+ /// Executes the GL Render
+ ///
+ public void DoRender()
+ {
+ // Discard render is OpenGL is not yet initialized, schedule next render
+ if (gl == null || gl.RenderContextProvider == null)
+ {
+ // If rendering is manually triggered, schedule a new render
+ if (RenderTrigger == RenderTrigger.Manual)
+ {
+ timer.Stop();
+ timer.Start();
+ }
+ return;
+ }
+
+ // Lock on OpenGL.
+ lock (gl)
+ {
+ // Start the stopwatch so that we can time the rendering.
+ stopwatch.Restart();
+
+ // Make GL current.
+ gl.MakeCurrent();
+
+ // If there is a draw handler, then call it.
+ RaiseEvent(new OpenGLRoutedEventArgs(OpenGLDrawEvent, gl));
+
+ // Draw the FPS.
+ if (DrawFPS)
+ {
+ gl.DrawText(5, 5, 1.0f, 0.0f, 0.0f, "Courier New", 12.0f, string.Format("Draw Time: {0:0.0000} ms ~ {1:0.0} FPS", frameTime, 1000.0 / frameTime));
+ gl.Flush();
+ }
+
+ // Render.
+ gl.Blit(IntPtr.Zero);
+
+ switch (RenderContextType)
+ {
+ case RenderContextType.DIBSection:
+ {
+ var provider = gl.RenderContextProvider as DIBSectionRenderContextProvider;
+ var hBitmap = provider.DIBSection.HBitmap;
+
+ if (hBitmap != IntPtr.Zero)
+ {
+ FillImageSource(provider.DIBSection.Bits, hBitmap);
+ // var newFormatedBitmapSource = GetFormatedBitmapSource(hBitmap);
+
+ // Copy the pixels over.
+ // image.Source = newFormatedBitmapSource;
+ }
+ }
+ break;
+ case RenderContextType.NativeWindow:
+ break;
+ case RenderContextType.HiddenWindow:
+ break;
+ case RenderContextType.FBO:
+ {
+ var provider = gl.RenderContextProvider as FBORenderContextProvider;
+ var hBitmap = provider.InternalDIBSection.HBitmap;
+
+ if (hBitmap != IntPtr.Zero)
+ {
+ FillImageSource(provider.InternalDIBSection.Bits, hBitmap);
+ // var newFormatedBitmapSource = GetFormatedBitmapSource(hBitmap);
+
+ // Copy the pixels over.
+ // image.Source = newFormatedBitmapSource;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ // Stop the stopwatch.
+ stopwatch.Stop();
+
+ // Store the frame time.
+ frameTime = stopwatch.Elapsed.TotalMilliseconds;
+ }
+ }
+
+ ///
+ /// Fill the ImageSource from the provided bits IntPtr, using the provided hBitMap IntPtr
+ /// if needed to determine key data from the bitmap source.
+ ///
+ /// An IntPtr to the bits for the bitmap image. Generally provided from
+ /// DIBSectionRenderContextProvider.DIBSection.Bits or from
+ /// FBORenderContextProvider.InternalDIBSection.Bits.
+ /// An IntPtr to the HBitmap for the image. Generally provided from
+ /// DIBSectionRenderContextProvider.DIBSection.HBitmap or from
+ /// FBORenderContextProvider.InternalDIBSection.HBitmap.
+ public void FillImageSource(IntPtr bits, IntPtr hBitmap)
+ {
+ // If DPI hasn't been set, use a call to HBitmapToBitmapSource to fill the info
+ // This should happen only ONCE (near the start of the application)
+ if (m_dpiX == 0)
+ {
+ var bitmapSource = BitmapConversion.HBitmapToBitmapSource(hBitmap);
+ m_dpiX = bitmapSource.DpiX;
+ m_dpiY = bitmapSource.DpiY;
+ m_format = bitmapSource.Format;
+ m_bytesPerPixel = gl.RenderContextProvider.BitDepth >> 3;
+ // FBO render context flips the image vertically, so transform to compensate
+ if (RenderContextType == RenderContextType.FBO)
+ {
+ image.RenderTransform = new ScaleTransform(1.0, -1.0);
+ image.RenderTransformOrigin = new Point(0.0, 0.5);
+ }
+ else
+ {
+ image.RenderTransform = Transform.Identity;
+ image.RenderTransformOrigin = new Point(0.0, 0.0);
+ }
+ }
+ // If the image buffer is null, create it
+ // This should happen when the size of the image changes
+ if (m_imageBuffer == null)
+ {
+ int width = gl.RenderContextProvider.Width;
+ int height = gl.RenderContextProvider.Height;
+
+ int imageBufferSize = width * height * m_bytesPerPixel;
+ m_imageBuffer = new byte[imageBufferSize];
+ m_writeableBitmap = new WriteableBitmap(width, height, m_dpiX, m_dpiY, m_format, null);
+ m_imageRect = new Int32Rect(0, 0, width, height);
+ m_imageStride = width * m_bytesPerPixel;
+ }
+
+ // Fill the image buffer from the bits and create the writeable bitmap
+ System.Runtime.InteropServices.Marshal.Copy(bits, m_imageBuffer, 0, m_imageBuffer.Length);
+ m_writeableBitmap.WritePixels(m_imageRect, m_imageBuffer, m_imageStride, 0);
+
+ image.Source = m_writeableBitmap;
+ }
+
+ ///
+ /// This method converts the output from the OpenGL render context provider to a
+ /// FormatConvertedBitmap in order to show it in the image.
+ ///
+ /// The handle of the bitmap from the OpenGL render context.
+ /// Returns the new format converted bitmap.
+ private static FormatConvertedBitmap GetFormatedBitmapSource(IntPtr hBitmap)
+ {
+ // TODO: We have to remove the alpha channel - for some reason it comes out as 0.0
+ // meaning the drawing comes out transparent.
+
+ FormatConvertedBitmap newFormatedBitmapSource = new FormatConvertedBitmap();
+ newFormatedBitmapSource.BeginInit();
+ newFormatedBitmapSource.Source = BitmapConversion.HBitmapToBitmapSource(hBitmap);
+ newFormatedBitmapSource.DestinationFormat = PixelFormats.Rgb24;
+ newFormatedBitmapSource.EndInit();
+
+ return newFormatedBitmapSource;
+ }
+
+ ///
+ /// Called when the frame rate is changed.
+ ///
+ /// The object.
+ /// The instance containing the event data.
+ private static void OnFrameRateChanged(DependencyObject o, DependencyPropertyChangedEventArgs args)
+ {
+ // Get the control.
+ OpenGLControlExt me = o as OpenGLControlExt;
+
+ // If we have the timer, set the time.
+ if (me.timer != null)
+ {
+ // Stop the timer.
+ me.timer.Stop();
+
+ // Set the timer.
+ me.timer.Interval = new TimeSpan(0, 0, 0, 0, (int)(1000f / me.FrameRate));
+
+ // Start the timer.
+ me.timer.Start();
+ }
+ }
+
+ ///
+ /// The OpenGL instance.
+ ///
+ private OpenGL gl = new OpenGL();
+
+ ///
+ /// The dispatcher timer.
+ ///
+ DispatcherTimer timer = null;
+
+ ///
+ /// A stopwatch used for timing rendering.
+ ///
+ protected Stopwatch stopwatch = new Stopwatch();
+
+ ///
+ /// The last frame time in milliseconds.
+ ///
+ protected double frameTime = 0;
+
+ private static readonly RoutedEvent OpenGLInitializedEvent = EventManager.RegisterRoutedEvent("OpenGLInitialized",
+ RoutingStrategy.Direct, typeof(OpenGLRoutedEventHandler), typeof(OpenGLControlExt));
+
+ ///
+ /// Occurs when OpenGL should be initialised.
+ ///
+ [Description("Called when OpenGL has been initialized."), Category("SharpGL")]
+ public event OpenGLRoutedEventHandler OpenGLInitialized;
+
+ private static readonly RoutedEvent OpenGLDrawEvent = EventManager.RegisterRoutedEvent("OpenGLDraw",
+ RoutingStrategy.Direct, typeof(OpenGLRoutedEventHandler), typeof(OpenGLControlExt));
+
+ ///
+ /// Occurs when OpenGL drawing should occur.
+ ///
+ [Description("Called whenever OpenGL drawing should occur."), Category("SharpGL")]
+ public event OpenGLRoutedEventHandler OpenGLDraw;
+
+ private static readonly RoutedEvent ResizedEvent = EventManager.RegisterRoutedEvent("Resized",
+ RoutingStrategy.Direct, typeof(OpenGLRoutedEventHandler), typeof(OpenGLControlExt));
+
+ ///
+ /// Occurs when the control is resized. This can be used to perform custom projections.
+ ///
+ [Description("Called when the control is resized - you can use this to do custom viewport projections."), Category("SharpGL")]
+ public event OpenGLRoutedEventHandler Resized;
+
+ ///
+ /// The frame rate dependency property.
+ ///
+ private static readonly DependencyProperty FrameRateProperty =
+ DependencyProperty.Register("FrameRate", typeof(double), typeof(OpenGLControlExt),
+ new PropertyMetadata(28.0, new PropertyChangedCallback(OnFrameRateChanged)));
+
+ ///
+ /// Gets or sets the frame rate.
+ ///
+ /// The frame rate.
+ public double FrameRate
+ {
+ get { return (double)GetValue(FrameRateProperty); }
+ set { SetValue(FrameRateProperty, value); }
+ }
+
+ ///
+ /// The render context type property.
+ ///
+ private static readonly DependencyProperty RenderContextTypeProperty =
+ DependencyProperty.Register("RenderContextType", typeof(RenderContextType), typeof(OpenGLControl),
+ new PropertyMetadata(RenderContextType.DIBSection, new PropertyChangedCallback(OnRenderContextTypeChanged)));
+
+ ///
+ /// Gets or sets the type of the render context.
+ ///
+ /// The type of the render context.
+ public RenderContextType RenderContextType
+ {
+ get { return (RenderContextType)GetValue(RenderContextTypeProperty); }
+ set { SetValue(RenderContextTypeProperty, value); }
+ }
+
+ ///
+ /// Called when [render context type changed].
+ ///
+ /// The o.
+ /// The instance containing the event data.
+ private static void OnRenderContextTypeChanged(DependencyObject o, DependencyPropertyChangedEventArgs args)
+ {
+ OpenGLControlExt me = o as OpenGLControlExt;
+ }
+
+ ///
+ /// The OpenGL Version property.
+ ///
+ private static readonly DependencyProperty OpenGLVersionProperty =
+ DependencyProperty.Register("OpenGLVersion", typeof(OpenGLVersion), typeof(OpenGLControlExt),
+ new PropertyMetadata(OpenGLVersion.OpenGL2_1));
+
+ ///
+ /// Gets or sets the OpenGL Version requested for the control.
+ ///
+ /// The type of the render context.
+ public OpenGLVersion OpenGLVersion
+ {
+ get { return (OpenGLVersion)GetValue(OpenGLVersionProperty); }
+ set { SetValue(OpenGLVersionProperty, value); }
+ }
+
+ ///
+ /// The DrawFPS property.
+ ///
+ private static readonly DependencyProperty DrawFPSProperty =
+ DependencyProperty.Register("DrawFPS", typeof(bool), typeof(OpenGLControlExt),
+ new PropertyMetadata(false, null));
+
+ ///
+ /// Gets or sets a value indicating whether to draw FPS.
+ ///
+ ///
+ /// true if draw FPS; otherwise, false.
+ ///
+ public bool DrawFPS
+ {
+ get { return (bool)GetValue(DrawFPSProperty); }
+ set { SetValue(DrawFPSProperty, value); }
+ }
+
+ ///
+ /// The Render trigger of this control
+ ///
+ public static readonly DependencyProperty RenderTriggerProperty =
+ DependencyProperty.Register("RenderMode", typeof(RenderTrigger), typeof(OpenGLControlExt),
+ new PropertyMetadata(RenderTrigger.TimerBased));
+
+ ///
+ /// Gets or sets the Render trigger of this control
+ ///
+ public RenderTrigger RenderTrigger
+ {
+ get { return (RenderTrigger)GetValue(RenderTriggerProperty); }
+ set
+ {
+ SetValue(RenderTriggerProperty, value);
+ if (value == RenderTrigger.TimerBased)
+ {
+ timer.Start();
+ }
+ else
+ {
+ timer.Stop();
+ }
+ }
+ }
+
+ ///
+ /// Gets the OpenGL instance.
+ ///
+ public OpenGL OpenGL
+ {
+ get { return gl; }
+ }
+ }
+}
diff --git a/LibDmd/Output/Virtual/Dmd/VirtualDmdControl.xaml b/LibDmd/Output/Virtual/Dmd/VirtualDmdControl.xaml
index 61e1869b8..e50c0b9b7 100644
--- a/LibDmd/Output/Virtual/Dmd/VirtualDmdControl.xaml
+++ b/LibDmd/Output/Virtual/Dmd/VirtualDmdControl.xaml
@@ -3,12 +3,13 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
- xmlns:sharpgl="clr-namespace:SharpGL.WPF;assembly=SharpGL.WPF"
+ xmlns:dmd="clr-namespace:LibDmd.Output.Virtual.Dmd"
+ xmlns:sharpgl="clr-namespace:SharpGL.WPF;assembly=SharpGL.WPF"
mc:Ignorable="d"
Background="Transparent"
d:DesignHeight="100" d:DesignWidth="400">
-
+
diff --git a/LibDmd/Output/Virtual/Dmd/VirtualDmdControl.xaml.cs b/LibDmd/Output/Virtual/Dmd/VirtualDmdControl.xaml.cs
index dfaa79377..5bbe91f09 100644
--- a/LibDmd/Output/Virtual/Dmd/VirtualDmdControl.xaml.cs
+++ b/LibDmd/Output/Virtual/Dmd/VirtualDmdControl.xaml.cs
@@ -139,6 +139,7 @@ public void RenderBitmap(BitmapSource bmp)
_hasFrame = true;
_nextFrameType = FrameFormat.Bitmap;
_nextFrameBitmap = bmp;
+ Dispatcher.Invoke(() => Dmd.DoRender());
}
public void RenderGray2(byte[] frame)
@@ -146,6 +147,7 @@ public void RenderGray2(byte[] frame)
_hasFrame = true;
_nextFrameType = FrameFormat.Gray2;
_nextFrameData = frame;
+ Dispatcher.Invoke(() => Dmd.DoRender());
}
public void RenderGray4(byte[] frame)
@@ -153,6 +155,7 @@ public void RenderGray4(byte[] frame)
_hasFrame = true;
_nextFrameType = FrameFormat.Gray4;
_nextFrameData = frame;
+ Dispatcher.Invoke(() => Dmd.DoRender());
}
public void RenderRgb24(byte[] frame)
@@ -160,6 +163,7 @@ public void RenderRgb24(byte[] frame)
_hasFrame = true;
_nextFrameType = FrameFormat.Rgb24;
_nextFrameData = frame;
+ Dispatcher.Invoke(() => Dmd.DoRender());
}
public void RenderColoredGray2(ColoredFrame frame)
@@ -237,6 +241,13 @@ private void ogl_OpenGLInitialized(object sender, OpenGLRoutedEventArgs args)
texVBO.Bind(gl);
texVBO.SetData(gl, TexCoordAttribute, new float[] { 0f, 1f, 0f, 0f, 1f, 0f, 1f, 1f }, false, 2);
_quadVbo.Unbind(gl);
+
+ Dmd.DoRender();
+ }
+
+ private void ogl_OpenGLResized(object sender, OpenGLRoutedEventArgs args)
+ {
+ Dmd.DoRender();
}
private void ogl_OpenGLDraw(object sender, OpenGLRoutedEventArgs args)