luca-piccioni / OpenGL.Net

Modern OpenGL bindings for C#.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Encounter NullReferenceException Second Time Gl.Control Window is Opened

dcb-areva opened this issue · comments

I am using OpenGL.Net to display a video stream using the OpenGL.Net.WinForms GlControl. When I open the form window the first time everything works great. However the second time I open the window I get the NullReferenceException shown in the screen capture. I'm not sure why I am encountering the exception. Is it a problem in my code (shown) or a bug in OpenGL.Net? Thanks!

openglnetnullreferenceexception

`
using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.Threading;
using System.Windows.Forms;
using System.Runtime.InteropServices;

using OpenGL;

/// <summary>
/// Class:
///     OpenGLNetWindowWriter : MediaWindow : MediaOutput : MediaAct
///
/// </summary>
/// <remarks>
/// Thread Safe: Class executes on multiple threads. Constructor executes on calling application
///              thread. A MediaIOThread is created and run from this class.
///              See individual methods. 
/// </remarks>
public class OpenGLNetWindowWriter : MediaWindow
{

    //private static double usPerTick = (double)(1000 * 1000) / (double)Stopwatch.Frequency;

    /// <summary>    
    /// Writes video frames to a window using a SimpleOpenGlControl (Tao OpenGL).
    /// </summary>
    /// <remarks>
    /// Thread Safe: Yes, designed to be called from the main application thread.
    /// </remarks>
    public OpenGLNetWindowWriter()
    {
        MediaActThread = new OpenGLNetWindowWriterMediaIOThread("OpenGL.Net Window Writer", this, ThreadPriority.AboveNormal);
    }

    /// <summary>
    /// Open the media act (parameters must be set before opening).
    /// </summary>
    /// <Param Name="timeTicks">Open time (in ticks).</Param>
    /// <returns></returns>
    /// <remarks>
    /// Thead Safe - Yes
    /// </remarks>
    internal override void ActOpen(long timeTicks, VideoMedia video, AudioMedia audio, MediaScript.ActionEnum action)
    {
        if (!ActOpened)
        {
            videoSizeRatio = (double)video.VideoSize.Width / (double)video.VideoSize.Height;
            windowSize = new Size(video.VideoSize.Width, video.VideoSize.Height);
            windowLocation = new Point(0, 0);   // #### should be passed in?

            base.ActOpen(timeTicks, video, audio, action);
        }
    }

    /// <summary>
    /// Show the window by setting window state to normal (it starts out minimized).
    /// </summary>
    /// <Param Name=""></Param>
    /// <returns></returns>
    /// <remarks>
    /// Thead Safe - Yes, designed to be called from the main application thread.
    /// </remarks>
    private delegate void showDelegate();
    public override void Show()
    {
        OpenGLNetWindowWriterMediaIOThread openGLNetWindowThread = MediaActThread as OpenGLNetWindowWriterMediaIOThread;
        if (openGLNetWindowThread.containerForm.InvokeRequired)
            openGLNetWindowThread.containerForm.BeginInvoke(new showDelegate(SetWindowState));
        else
            SetWindowState();
    }

    private void SetWindowState()
    {
        OpenGLNetWindowWriterMediaIOThread openGLNetWindowThread = MediaActThread as OpenGLNetWindowWriterMediaIOThread;
        if (openGLNetWindowThread.containerForm != null)
            openGLNetWindowThread.containerForm.WindowState = FormWindowState.Normal;
    }

    /// <summary>
    /// Set the size of the window, set the video frame size to fit in the window (scale to maintain aspect ratio).
    /// </summary>
    /// <Param Name="size"> New size of window.</Param>
    /// <returns></returns>
    /// <remarks>
    /// Thead Safe - Yes, intended to be called from the main app thread.
    /// </remarks>
    private delegate void setClientSizeDelegate(Size size);
    public override void SetSize(Size size)
    {
        OpenGLNetWindowWriterMediaIOThread openGLNetWindowThread = MediaActThread as OpenGLNetWindowWriterMediaIOThread;
        if (openGLNetWindowThread.containerForm != null)
            if (openGLNetWindowThread.containerForm.InvokeRequired)
                openGLNetWindowThread.containerForm.BeginInvoke(new setClientSizeDelegate(SetClientSize), size);
            else
                SetClientSize(size);
    }

    /// <summary>
    /// Set the size of the window, set the video frame size to fit in the window (scale to maintain aspect ratio).
    /// </summary>
    /// <Param Name="size"> New size of window.</Param>
    /// <returns></returns>
    /// <remarks>
    /// Thead Safe - No, must be called from the thread the media was opened on (i.e. MediaAct thread).
    /// </remarks>
    private void SetClientSize(Size size)
    {
        OpenGLNetWindowWriterMediaIOThread openGLNetWindowThread = MediaActThread as OpenGLNetWindowWriterMediaIOThread;

        if (openGLNetWindowThread.containerForm != null)
        {
            Size videoSize = size;
            Point videoLocation = new System.Drawing.Point(0, 0);
            windowSize = size;
            openGLNetWindowThread.containerForm.ClientSize = size;

            // keep video size perportional to captured video size
            double windowSizeRatio = (double)size.Width / (double)size.Height;
            if (windowSizeRatio > videoSizeRatio)
            {
                videoSize.Width = (int)((double)size.Height * videoSizeRatio);          // scale video size by height
                videoLocation = new Point((size.Width - videoSize.Width) / 2, 0);       // center video in window
            }
            else if (windowSizeRatio < videoSizeRatio)
            {
                videoSize.Height = (int)((double)size.Width / videoSizeRatio);          // scale video size by width
                videoLocation = new Point(0, (size.Height - videoSize.Height) / 2);       // center video in window
            }

            OpenGL.GlControl openGlControl = openGLNetWindowThread.windowControl as OpenGL.GlControl;
            openGlControl.Size = videoSize;
            openGlControl.Location = videoLocation;
        }

    }

    /// <summary>
    /// Set the screen location of the window.
    /// </summary>
    /// <Param Name="location"> New screen location of window.</Param>
    /// <returns></returns>
    /// <remarks>
    /// Thead Safe - Yes, intended to be called from the main application thread.
    /// </remarks>
    private delegate void setClientLocationDelegate(Point location);
    public override void SetLocation(Point location)
    {
        OpenGLNetWindowWriterMediaIOThread openGLNetWindowThread = MediaActThread as OpenGLNetWindowWriterMediaIOThread;

        if (openGLNetWindowThread.containerForm != null)
            if (openGLNetWindowThread.containerForm.InvokeRequired)
                openGLNetWindowThread.containerForm.BeginInvoke(new setClientLocationDelegate(SetClientLocation), location);
            else
                SetClientLocation(location);
    }

    /// <summary>
    /// Set the screen location of the window.
    /// </summary>
    /// <Param Name="location"> New screen location of window.</Param>
    /// <returns></returns>
    /// <remarks>
    /// Thead Safe - No, must be called from the thread the media was opened on (i.e. MediaAct thread).
    /// </remarks>
    private void SetClientLocation(Point location)
    {
        OpenGLNetWindowWriterMediaIOThread openGLNetWindowThread = MediaActThread as OpenGLNetWindowWriterMediaIOThread;

        if (openGLNetWindowThread.containerForm != null)
        {
            windowLocation = location;
            openGLNetWindowThread.containerForm.Location = location;
        }
    }

    /// <summary>
    /// Set window focus.
    /// </summary>
    /// <Param Name=""></Param>
    /// <returns></returns>
    /// <remarks>
    /// Thead Safe - Yes, intended to be called from the main application thread.
    /// </remarks>
    private delegate void setWindowFocusDelegate();
    public override void SetWindowFocus()
    {
        OpenGLNetWindowWriterMediaIOThread openGLNetWindowThread = MediaActThread as OpenGLNetWindowWriterMediaIOThread;

        if (openGLNetWindowThread.containerForm != null)
            if (openGLNetWindowThread.containerForm.InvokeRequired)
                openGLNetWindowThread.containerForm.BeginInvoke(new setWindowFocusDelegate(SetFocus));
            else
                SetFocus();
    }

    /// <summary>
    /// Set window focus, bring window to top of z-order.
    /// </summary>
    /// <Param Name=""></Param>
    /// <returns></returns>
    /// <remarks>
    /// Thead Safe - No, must be called from the thread the media was opened on (i.e. MediaAct thread).
    /// </remarks>
    private void SetFocus()
    {
        OpenGLNetWindowWriterMediaIOThread openGLNetWindowThread = MediaActThread as OpenGLNetWindowWriterMediaIOThread;
        //System.Diagnostics.Debug.WriteLine("2) openGLNetWindowThread.SetFocus() " + openGLNetWindowThread.containerForm.WindowState.ToString());
        if (openGLNetWindowThread.containerForm != null)
        {
            openGLNetWindowThread.containerForm.TopMost = true;
            openGLNetWindowThread.containerForm.Refresh();
            openGLNetWindowThread.containerForm.TopMost = false;
        }
    }

    /// <summary>
    ///     Class:      OpenGLNetWindowWriterMediaIOThread : MediaIOThread : MediaThread
    ///     
    ///     
    ///     
    ///     
    /// </summary>

    internal class OpenGLNetWindowWriterMediaIOThread : MediaIOThread, IDisposable
    {
        public Control windowControl;
        public Form containerForm;
        private DeviceContext deviceContext;
        private IntPtr renderContext;
        private bool disposed = false;
        private bool delayedStorageCleanup = false;

        public OpenGLNetWindowWriterMediaIOThread(string threadName, MediaAct parent, ThreadPriority priority)
            : base(threadName, parent, priority)
        {
            containerForm = null;
            windowControl = null;
            deviceContext = null;
            renderContext = IntPtr.Zero;
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposed)
                return;

            if (disposing)
            {
                // dispose managed resources
                StopProcessing();
            }
            disposed = true;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Windows message loop thread.
        /// </summary>
        /// <Param Name=""></Param>
        /// <returns>This method does not return until the Media is closed (Windows form is closed).</returns>
        /// <remarks>
        /// Thead Safe - No, designed to be called from the MediaAct thread.
        /// </remarks>
        protected void WindowsMessageLoop()
        {
            StartProcessing();
        }

        /// <summary>
        /// Open MediaAct media, create windows form, OpenGL.Net windows control, and start Windows message loop thread.
        /// </summary>
        /// <Param Name=""></Param>
        /// <returns>This method does not return until the Media is closed (Windows form is closed).</returns>
        /// <remarks>
        /// Thead Safe - No, designed to be called from the MediaAct thread.
        /// </remarks>
        protected override bool StartProcessing()
        {
            bool success = false;

            try
            {
                OpenGLNetWindowWriter OpenGLNetWindowWriter = ParentMediaAct as OpenGLNetWindowWriter;

                Application.EnableVisualStyles();
                containerForm = new Form();                     // create form as a container for the Open Gl Window

                containerForm.BackgroundImageLayout = System.Windows.Forms.ImageLayout.None;
                containerForm.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
                containerForm.ControlBox = false;
                containerForm.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
                containerForm.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide;
                containerForm.Margin = new System.Windows.Forms.Padding(0);
                containerForm.ShowIcon = false;
                containerForm.ShowInTaskbar = false;
                containerForm.ClientSize = OpenGLNetWindowWriter.windowSize;
                containerForm.Location = OpenGLNetWindowWriter.windowLocation;
                containerForm.AutoScaleMode = AutoScaleMode.None;
                containerForm.Cursor = Cursors.Default;
                containerForm.WindowState = FormWindowState.Minimized;
                containerForm.Load += new EventHandler(containerForm_Load);

                // start a windows message loop 
                Application.Run(containerForm);

                // Windows message loop does NOT exit until the form is closed

                success = true;
            }
            catch (System.Exception e)
            {
                System.Diagnostics.Debug.WriteLine("OpenGLNetWindowWriter :" + e.ToString());
            }

            return success;
        }

        /// <summary>
        /// Form load event of the MediaAct thread, used to send MediaAct initialized event.
        /// </summary>
        /// <Param Name=""></Param>
        /// <returns></returns>
        /// <remarks>
        /// Thead Safe - No, must be called from the thread the media was opened on (i.e. MediaAct thread).
        /// </remarks>
        protected void containerForm_Load(object sender, EventArgs e)
        {
            OpenGLNetWindowWriter OpenGLNetWindowWriter = ParentMediaAct as OpenGLNetWindowWriter;

            windowControl = new OpenGL.GlControl();
            OpenGL.GlControl openGlControl = windowControl as OpenGL.GlControl;
            openGlControl.BackColor = System.Drawing.Color.Black;
            openGlControl.Location = new System.Drawing.Point(0, 0);
            openGlControl.Name = "MediaWindowWriter";
            openGlControl.Size = new Size(OpenGLNetWindowWriter.windowSize.Width, OpenGLNetWindowWriter.windowSize.Height);
            openGlControl.AutoScaleMode = AutoScaleMode.None;
            openGlControl.BorderStyle = BorderStyle.None;
            openGlControl.TabStop = false;
            //openGlControl.MouseDown += new System.Windows.Forms.MouseEventHandler(openGlControl_MouseDown);
            //openGlControl.MouseMove += new System.Windows.Forms.MouseEventHandler(openGlControl_MouseMove);
            //openGlControl.MouseUp += new System.Windows.Forms.MouseEventHandler(openGlControl_MouseUp);
            //openGlControl.MouseLeave += new System.EventHandler(openGlControl_MouseLeave);
            openGlControl.MouseClick += new System.Windows.Forms.MouseEventHandler(openGlControl_MouseClick);
            openGlControl.MouseWheel += new System.Windows.Forms.MouseEventHandler(openGlControl_MouseWheel);
            openGlControl.ContextCreated += new EventHandler<GlControlEventArgs>(containerForm_ContextCreated);

            containerForm.Controls.Add(openGlControl);

            OpenGLNetWindowWriter.ActOpened = true;
            OpenGLNetWindowWriter.SendOpenedEvent();
        }

        /// <summary>
        /// OpenGL context created event of the MediaAct thread.
        /// </summary>
        /// <Param Name=""></Param>
        /// <returns></returns>
        /// <remarks>
        /// Thead Safe - No, must be called from the thread the media was opened on (i.e. MediaAct thread).
        /// </remarks>
        protected void containerForm_ContextCreated(object sender, GlControlEventArgs e)
        {
            deviceContext = e.DeviceContext;
            renderContext = e.RenderContext;
            System.Diagnostics.Debug.WriteLine("Context created");
        }

        /// <summary>
        /// OpenGL.Net control mouse click event, send event (back to main application).
        /// </summary>
        /// <Param Name=""></Param>
        /// <returns></returns>
        /// <remarks>
        /// Thead Safe - Yes
        /// </remarks>
        private void openGlControl_MouseClick(object sender, MouseEventArgs e)
        {
            //System.Diagnostics.Debug.WriteLine("openGTKControl_MouseClick()");
            OpenGLNetWindowWriter OpenGLNetWindowWriter = ParentMediaAct as OpenGLNetWindowWriter;
            OpenGLNetWindowWriter.SendWindowMouseClickEvent(e);
        }

        /// <summary>
        /// OpenGL.Net control mouse wheel event, send event (back to main application).
        /// </summary>
        /// <Param Name=""></Param>
        /// <returns></returns>
        /// <remarks>
        /// Thead Safe - No, must be called from the thread the media was opened on (i.e. MediaAct thread).
        /// </remarks>
        private void openGlControl_MouseWheel(object sender, MouseEventArgs e)
        {
            //System.Diagnostics.Debug.WriteLine("openGlControl_MouseWheel()");
            OpenGLNetWindowWriter OpenGLNetWindowWriter = ParentMediaAct as OpenGLNetWindowWriter;
            OpenGLNetWindowWriter.SendWindowMouseWheelEvent(e);
        }

        /// <summary>
        /// CloseMedia
        /// </summary>
        /// <Param Name=""></Param>
        /// <returns></returns>
        /// <remarks>
        /// Thead Safe - No, must be called from the thread the media was opened on (i.e. MediaAct thread). Use
        ///              CloseMediaDelegate() with BeginInvoke() from other threads.
        /// </remarks>
        protected delegate bool CloseMediaDelegate();
        protected override bool StopProcessing()
        {
            bool success = false;

            try
            {
                if (containerForm != null)
                {
                    containerForm.Close();
                    OpenGL.GlControl openGlControl = windowControl as OpenGL.GlControl;
                    openGlControl.Dispose();
                    containerForm = null;
                    deviceContext = null;
                    renderContext = IntPtr.Zero;
                }
                success = true;
            }
            catch (System.Exception e)
            {
                System.Diagnostics.Debug.WriteLine("OpenGLNetWindowWriter.CloseMedia(): " + e.ToString());
            }

            return success;
        }

        /// <summary>
        /// Handle MediaThread events (thread loop).
        /// </summary>
        /// <returns></returns>
        /// <remarks>
        /// Thead Safe - Yes
        /// </remarks>
        protected override void HandleEvents()
        {
            bool started = true;
            ThreadEventsList newEventIndex;
            MediaFrameEventInfo newFrameInfo = null;
            VideoFrame lastOtherFrame = null;
            OpenGLNetWindowWriter OpenGLNetWindowWriter = ParentMediaAct as OpenGLNetWindowWriter;

            // tell anyone who is listening - initialization is complete
            SendInitializedEvent();

            while (started)
            {
                try
                {
                    newEventIndex = (ThreadEventsList)WaitHandle.WaitAny(ThreadEvents);     // ##### need a timeout here ??
                    switch (newEventIndex)
                    {
                        case ThreadEventsList.StartProcessing:

                            // start windows form message loop
                            Thread windowsThread = new Thread(new ThreadStart(WindowsMessageLoop));
                            windowsThread.Name = "OpenGL.Net Window Writer MessageLoop";
                            windowsThread.Start();                                          // #### should add an error event to handle errors in this thread

                            break;

                        case ThreadEventsList.StopProcessing:
                            if (OpenGLNetWindowWriter.ActOpened)
                            {
                                if (containerForm != null)
                                    containerForm.BeginInvoke(new CloseMediaDelegate(StopProcessing));
                                OpenGLNetWindowWriter.ActOpened = false;
                                OpenGLNetWindowWriter.SendClosedEvent();
                            }
                            break;

                        case ThreadEventsList.Shutdown:
                            if (OpenGLNetWindowWriter.ActOpened)
                            {
                                if (containerForm != null)
                                    containerForm.BeginInvoke(new CloseMediaDelegate(StopProcessing));
                                OpenGLNetWindowWriter.ActOpened = false;
                                OpenGLNetWindowWriter.SendClosedEvent();
                            }
                            started = false;
                            break;

                        case ThreadEventsList.Frame:
                            int frameEventQueueCount = 0;
                            lock (FrameEventQueue.SyncRoot)
                                frameEventQueueCount = FrameEventQueue.Count;

                            while (frameEventQueueCount > 0)
                            {
                                lock (FrameEventQueue.SyncRoot)
                                {
                                    newFrameInfo = (MediaFrameEventInfo)FrameEventQueue.Dequeue();
                                    frameEventQueueCount = FrameEventQueue.Count;
                                }
                                MediaScript.ActionEnum action = OpenGLNetWindowWriter.ActAction;

                                if ((OpenGLNetWindowWriter.ActOpened) && (newFrameInfo != null) && (newFrameInfo.Frame != null))
                                {
                                    // see if the frame is a video frame
                                    VideoFrame videoFrame = newFrameInfo.Frame as VideoFrame;

                                    if (videoFrame != null)
                                    {


                                        MediaScript.ActionEnum otherDisplay = (action == MediaScript.ActionEnum.Display1) ? MediaScript.ActionEnum.Display2 : MediaScript.ActionEnum.Display1;

                                        // Frame source and display match (Source1 and Display1 OR Source2 and Display2)
                                        bool frameSourceDisplayMatch = ((videoFrame.Source == MediaScript.ActionEnum.Source1) && (action == MediaScript.ActionEnum.Display1))
                                                                                                                              ||
                                                                       ((videoFrame.Source == MediaScript.ActionEnum.Source2) && (action == MediaScript.ActionEnum.Display2));

                                        //Debug.WriteLine("WindowWriter Action " + action.ToString() + " VideoFrame " + videoFrame.Source.ToString() + " FrameNumber " +
                                        //                videoFrame.FrameNumber.ToString() + " DisplayMatch " + frameSourceDisplayMatch.ToString());

                                        // is swap displays enabled?
                                        if (videoFrame.SwapSource && videoFrame.Script.Is(action, MediaScript.ActionStateEnum.Active) &&
                                            videoFrame.Script.Is(otherDisplay, MediaScript.ActionStateEnum.Active))
                                        {
                                            if (!frameSourceDisplayMatch)
                                                lastOtherFrame = videoFrame;
                                            else if (lastOtherFrame != null)
                                                videoFrame = lastOtherFrame;
                                        }
                                        else if (!frameSourceDisplayMatch)
                                            lastOtherFrame = null;

                                        if (videoFrame.Script.Is(action, MediaScript.ActionStateEnum.Active) && frameSourceDisplayMatch)
                                        {
                                            // write overlay

                                            //string frameTime = ((double)videoFrame.FrameCaptureTimeTicks / (double)Stopwatch.Frequency).ToString("0000.000 ");
                                            //Debug.WriteLine(frameTime + action.ToString() + " " + videoFrame.Source.ToString() + " write overlay
                                            if (videoFrame.DataLock.TryEnterReadLock(videoFrame.DataLockTimeout))
                                            {
                                                // write overlay to display bitmap (only for exam display)
                                                if (videoFrame.Overlay != null)
                                                {
                                                    // draw on the frame bitmap - might want to use a copy here?
                                                    videoFrame.Overlay.Draw(videoFrame.VideoBitmap.RGBBitmap,
                                                        OpenGLNetWindowWriter.ShowSubtitles, OpenGLNetWindowWriter.ShowGraphic);
                                                    if (videoFrame.FilteredVideoBitmap != null)
                                                        videoFrame.Overlay.Draw(videoFrame.FilteredVideoBitmap.RGBBitmap,
                                                            OpenGLNetWindowWriter.ShowSubtitles, OpenGLNetWindowWriter.ShowGraphic);
                                                }
                                                videoFrame.DataLock.ExitReadLock();
                                            }

                                            WriteFrame(videoFrame, OpenGLNetWindowWriter.Video);

                                            // capture image requested ?
                                            if (Interlocked.CompareExchange(ref OpenGLNetWindowWriter.CaptureImageFlag, 0, 1) == 1)
                                                CaptureImage(videoFrame);

                                            if (videoFrame.DataLock.TryEnterWriteLock(videoFrame.DataLockTimeout))
                                            {
                                                // Return video storage to global pool.
                                                if (!delayedStorageCleanup)
                                                {
                                                    videoFrame.ReturnVideoStorage();
                                                    videoFrame.ReturnFilteredVideoStorage();
                                                }
                                                videoFrame.DataLock.ExitWriteLock();
                                            }
                                        }
                                    }

                                    // send audio frame event (i.e. used to display audio level)
                                    AudioFrame audioFrame = newFrameInfo.Frame as AudioFrame;
                                    if (audioFrame != null)
                                    {
                                        if (audioFrame.Script.Is(action, MediaScript.ActionStateEnum.Active))
                                        {
                                            OpenGLNetWindowWriter.SendAudioFrameEvent(new MediaFrameEventInfo(newFrameInfo.Frame));
                                        }
                                    }
                                }

                                if ((newFrameInfo != null) && (newFrameInfo.Frame != null))
                                    OpenGLNetWindowWriter.SendFrameEvent(new MediaFrameEventInfo(newFrameInfo.Frame));
                            }
                            break;
                    }
                }
                catch (Exception e)
                {
                    System.Diagnostics.Debug.WriteLine("OpenGLNetWindowWriterMediaIOThread.HandleEvents: " + e.ToString());
                }
            }
        }

        /// <summary>
        /// Writes video frame to the openGl window.
        /// </summary>
        /// <Param Name="frame">Video frame to be written.</Param>
        /// <Param Name="options">Video frame options for writing.</Param>
        /// <returns>true - if no error</returns>
        /// <remarks>
        /// Thead Safe - Yes, intended to be called from the MediaAct thread.
        /// </remarks>
        private bool WriteFrame(VideoFrame frame, VideoMedia options)
        {
            bool result = false;
            delayedStorageCleanup = false;

            if (windowControl != null)
            {
                try
                {
                    if (windowControl.InvokeRequired)
                    {
                        delayedStorageCleanup = true;
                        windowControl.BeginInvoke(new OpenGLNetWindowWriterMediaIOThread.RenderWindowDelegate(Render), frame, options);
                    }
                    else
                        Render(frame, options);
                    result = true;
                }
                catch (System.Exception e)
                {
                    System.Diagnostics.Debug.WriteLine("OpenGLNetWindowWriterMediaIOThread.WriteFrame: " + e.ToString());
                }
            }

            return result;
        }

        /// <summary>
        /// Renders video frame to the openGl window.
        /// </summary>
        /// <Param Name="frame">Video frame to be rendered.</Param>
        /// <Param Name="options">Video frame options for rendering.</Param>
        /// <returns></returns>
        /// <remarks>
        /// Thead Safe - No, must be called from the thread the media was opened on (i.e. MediaAct thread).
        /// </remarks>
        public delegate void RenderWindowDelegate(VideoFrame frame, VideoMedia options);
        public void Render(VideoFrame frame, VideoMedia options)
        {
            RGBStorage videoBitmap;
            BitmapData bmData;
            bool locked = false;

            if (frame != null)
            {
                try
                {
                    frame.FrameDisplayTimeTicks = DateTime.UtcNow.Ticks - frame.FrameCaptureTimeTicks;

                    OpenGL.GlControl openGlControl = windowControl as OpenGL.GlControl;
                    if (openGlControl != null && deviceContext != null)
                    {
                        deviceContext.MakeCurrent(renderContext);
                        Gl.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

                        //Debug.WriteLine("OpenGlWriter.Render TryEnterReadLock Frame");
                        if (frame.DataLock.TryEnterReadLock(frame.DataLockTimeout))
                        {
                            locked = true;

                            videoBitmap = frame.VideoBitmap;
                            //videoBitmap = videoFrame.FilteredVideoBitmap;
                            if (videoBitmap != null)
                            {
                                //System.Diagnostics.Debug.WriteLine("Rendering");
                                if (options.Stereo == VideoMedia.VideoStereoEnum.TopRightBottomLeft)
                                {
                                    Gl.PixelZoom((float)openGlControl.Width / (float)videoBitmap.Width, (float)openGlControl.Height / ((float)videoBitmap.Height) / 2F);
                                    Gl.DrawBuffer(DrawBufferMode.BackLeft);
                                    bmData = videoBitmap.RGBBitmap.LockBits(new Rectangle(0, 0, (int)videoBitmap.Width, (int)videoBitmap.Height / 2),
                                        ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
                                    Gl.DrawPixels((int)videoBitmap.Width, (int)videoBitmap.Height, OpenGL.PixelFormat.Bgr, OpenGL.PixelType.UnsignedByte, bmData.Scan0);
                                    videoBitmap.RGBBitmap.UnlockBits(bmData);

                                    Gl.DrawBuffer(DrawBufferMode.BackRight);
                                    bmData = videoBitmap.RGBBitmap.LockBits(new Rectangle(0, (int)videoBitmap.Height, (int)videoBitmap.Width, (int)videoBitmap.Height),
                                        ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
                                    Gl.DrawPixels((int)videoBitmap.Width, (int)videoBitmap.Height, OpenGL.PixelFormat.Bgr, OpenGL.PixelType.UnsignedByte, bmData.Scan0);
                                    videoBitmap.RGBBitmap.UnlockBits(bmData);
                                }
                                else if (options.Stereo == VideoMedia.VideoStereoEnum.RightLeft)
                                {
                                    Gl.PixelZoom((float)openGlControl.Width / ((float)videoBitmap.Width) / 2F, (float)openGlControl.Height / (float)videoBitmap.Height);
                                    Gl.DrawBuffer(DrawBufferMode.BackRight);
                                    bmData = videoBitmap.RGBBitmap.LockBits(new Rectangle(0, 0, (int)videoBitmap.Width / 2, (int)videoBitmap.Height),
                                        ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
                                    Gl.DrawPixels((int)videoBitmap.Width, (int)videoBitmap.Height, OpenGL.PixelFormat.Bgr, OpenGL.PixelType.UnsignedByte, bmData.Scan0);
                                    videoBitmap.RGBBitmap.UnlockBits(bmData);

                                    Gl.DrawBuffer(DrawBufferMode.BackLeft);
                                    bmData = videoBitmap.RGBBitmap.LockBits(new Rectangle((int)videoBitmap.Width / 2, 0, (int)videoBitmap.Width / 2, (int)videoBitmap.Height),
                                        ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
                                    Gl.DrawPixels((int)videoBitmap.Width, (int)videoBitmap.Height, OpenGL.PixelFormat.Bgr, OpenGL.PixelType.UnsignedByte, bmData.Scan0);
                                    videoBitmap.RGBBitmap.UnlockBits(bmData);
                                }
                                else // default to mono (no Stereo)
                                {
                                    Gl.PixelZoom((float)openGlControl.Width / (float)videoBitmap.Width, (float)openGlControl.Height / (float)videoBitmap.Height);
                                    bmData = videoBitmap.RGBBitmap.LockBits(new Rectangle(0, 0, (int)videoBitmap.Width, (int)videoBitmap.Height),
                                        ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
                                    Gl.DrawBuffer(DrawBufferMode.BackLeft);
                                    Gl.DrawPixels((int)videoBitmap.Width, (int)videoBitmap.Height, OpenGL.PixelFormat.Bgr, OpenGL.PixelType.UnsignedByte, bmData.Scan0);
                                    videoBitmap.RGBBitmap.UnlockBits(bmData);
                                }

                            }

                            // Return video storage to global pool.
                            if (delayedStorageCleanup)
                            {
                                frame.ReturnVideoStorage();
                                frame.ReturnFilteredVideoStorage();
                            }

                            frame.DataLock.ExitReadLock();
                            locked = false;

                            Gl.Flush();
                            deviceContext.SwapBuffers();
                        }
                        else
                            throw (new Exception("OpenGLNetWindowWriter.Render(): Lock Timeout"));

                        frame.FrameDisplayCompleteTimeTicks = DateTime.UtcNow.Ticks - frame.FrameCaptureTimeTicks;
                    }
                }
                catch (System.Exception e)
                {
                    System.Diagnostics.Debug.WriteLine("OpenGLNetWindowWriter.Render(): " + e.ToString());
                }
                finally
                {
                    if (locked)
                    {
                        //Debug.WriteLine("OpenGlWriter.Render TryEnterReadLock Frame ENTER finally");
                        frame.DataLock.ExitReadLock();
                    }
                }

            }
        }

        /// <summary>
        /// Capture image from video frame and send the capture image event.
        /// </summary>
        /// <Param Name="frame">Current media video frame.</Param>
        /// <returns>true - if no error</returns>
        /// <remarks>
        /// Thead Safe - Yes, intended to be called from the MediaAct thread.
        /// </remarks>
        private bool CaptureImage(VideoFrame videoFrame)
        {
            bool result = false;
            bool locked = false;
            MediaImage newImage = null;
            OpenGLNetWindowWriter openGlWindowWriter = ParentMediaAct as OpenGLNetWindowWriter;

            try
            {
                if ((videoFrame != null) && (videoFrame.DataLock.TryEnterWriteLock(videoFrame.DataLockTimeout)))
                {
                    locked = true;
                    newImage = new MediaImage();
                    newImage.ImageBitmap = (Bitmap)videoFrame.VideoBitmap.RGBBitmap.Clone();
                    if (videoFrame.RawVideoBitmap != null)
                        newImage.RawImageBitmap = (Bitmap)videoFrame.RawVideoBitmap.RGBBitmap.Clone();
                    if (videoFrame.FilteredVideoBitmap != null)
                        newImage.FilteredImageBitmap = (Bitmap)videoFrame.FilteredVideoBitmap.RGBBitmap.Clone();

                }
            }
            catch (Exception e)
            {
                newImage = null;
                System.Diagnostics.Debug.WriteLine("openGlWindowWriterMediaThreadIO.CaptureImage(): " + e.ToString());
            }
            finally
            {
                if (locked)
                    videoFrame.DataLock.ExitWriteLock();
            }

            // post image captured event to anyone who is listening
            if (newImage != null)
            {
                // images need to be flipped (display needs them one way and file needs them another)

                if (newImage.ImageBitmap != null)
                    newImage.ImageBitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);
                if (newImage.RawImageBitmap != null)
                    newImage.RawImageBitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);
                if (newImage.FilteredImageBitmap != null)
                    newImage.FilteredImageBitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);

                openGlWindowWriter.SendImageEvent(new MediaImageEventInfo(videoFrame.Source, newImage));
            }

            return result;
        }
    }
}

`

Can you clarify the meaning of "open/close" window? Do you dispose it? Or do you simply show/hide it? Are you running on different threads each time a window is displayed?

The NullReferenceException is telling me that no OpenGL procedure pointers are loaded.


Just tried to run application by opening and closing two windows. Runs flawlessly. What the H/W you running on? Maybe an Optimus system (NV+Intel)?

There's a Gl.BindAPI() method that loads function pointers based on the context current on the calling thread. Call it before creation the new window.

But it shouldn't necessary, since this is called automatically the GlControl at handle creation time. I suspect there is something wrong elsewhere.

Gl.Initialize is executed only once, and it is described in the wiki pages. Using GlControl is not necessary to call it explicitly.

To overcome this issue, try to set Wgl.CurrentExtensions.SwapControl_EXT to false (after calling Gl.Initialize explicitly). This will leave the swap interval as default (V-Sync enabled on double buffered visual configurations). In practice, it avoids to to call that specific API.

Which version of OpenGL.Net you are running on?

Sorry for the typo, but you should set Wgl.CurrentExtensions.SwapControl_EXT.

Sorry for the typo, but you should set Wgl.CurrentExtensions.SwapControl_EXT.

Good (even if it is not yet solved, this proves that the problem is elsewhere). Maybe I found the problem, since I've encountered it some days ago.

Can you check whether Wgl.GetCurrentContext() is returning a value different from IntPtr.Zero after GlControl disposition? If it is, that's the cause. Because this code:

public override bool MakeCurrent(IntPtr ctx)
{
	// Avoid actual call to wglMakeCurrent if it is not necessary
	// Efficient on simple/nominal applications
	IntPtr currentContext = Wgl.GetCurrentContext(), currentDc = Wgl.GetCurrentDC();
	if (ctx == currentContext && _DeviceContext == currentDc)
		return (true);

	// Base implementation
	return (base.MakeCurrent(ctx));
}

protected override bool MakeCurrentCore(IntPtr ctx)
{
	return (Wgl.MakeCurrent(_DeviceContext, ctx));
}

Probably that "Efficient on simple/nominal applications" becomes "Buggy on strange/complex applications" ... -.-

Another hypothesis. Windows are run on different threads?

(Probably) I found the source of the problem. Actually the library has never been tested for multi-threading, even if it is ready to support it. What you need is to load WGL procedures; to do so, you need the Wgl.BindAPI(), but actually is private.

I think you could execute it via reflection at the moment. I'll provide some pattern to support multiple threads.in the next days


Being said this, probably the problem is caused by a missing call to Wgl.BindAPI() (a private method). You must know that all procedure on KhronoApi derived classes (indeed including Wgl), have a [ThreadStatic] attribute.

Indeed, each thread using any method must load procedures dynamically. Wgl procedures are thread static too. That's responsibility of Wgl.BindAPI(); this method is called only in two places:

  • Wgl static constructors
  • After a successfull call to Wgl.MakeCurrent

Despite all the reasoning above is correct, I cannot explain why only the Wgl.SwapIntervalEXT is failing (and not the GL context creation itself). I'll try to reproduce it locally using an unit test.

By the way, for what is worth, throw away that crazy design with a single-window-at-time-with-multithreading with a safe and fast single-ui-thread design i.e. what this answer explain quite well.

Something like using System.Reflection:

MethodInfo bindApiMethod = typeof(Wgl).GetMethod("BindAPI", BindingFlags.NonPublic | BindingFlags.Static);
bindApiMethod.Invoke(null, null);

Ok, I've unit tested the multi-threading UI using WinForms control, as you can see from the commit referenced above. As I expected, it has thrown a NullReferenceException when access to wglCreateContextAttribsARB (the first WGL procedure used by GlControl [*]), and this happens on the slower thread (because the other get execution of static constructors).

Now the unit test works as expected. I hope this will fix you issue. In the other case, you can continue this issue.

[*] The first because my system does not support wglSwapIntervalEXT procedure! Eureka!

Indeed I found the bug: the commit fixes the multi-threading issues you found.

Essentially WGL function pointers doesn't must be ThreadStatic, and they requires to be initialized only at Gl initialization time. Then, WGL function pointers are shared across all threads.

This will be included in the NuGet package v0.6.0-beta3 that I'll release soon.