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)