luca-piccioni / OpenGL.Net

Modern OpenGL bindings for C#.
MIT License
570 stars 109 forks source link

Encounter NullReferenceException Second Time Gl.Control Window is Opened #58

Closed dcb-areva closed 7 years ago

dcb-areva commented 7 years ago

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;
        }
    }
}

`

luca-piccioni commented 7 years ago

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)?

dcb-areva commented 7 years ago

Yes, I dispose the GlControl in the StopProcessing() method. The thread that calls StartProcessing() and StopProcessing() remains running in the process until I close the application. Note that I can induce the exception when first opening the window (which calls StartProcessing()) by calling Gl.Initialize() in the constructor. I suppose that I can avoid the exception by not disposing the GlControl. The problem is I will still likely encounter the exception when I open a second window (the application has that capability). Is there a way to force loading of OpenGL procedures in conjunction with Gl.Initialize()?

Disclaimer: This is inherited code. I would not have designed the form to operate in this fashion. I am moving to OpenGL.Net from OpenTK which causes random exceptions in the CLR on GC.

From: Luca Piccioni [mailto:notifications@github.com] Sent: Monday, August 14, 2017 11:19 AM To: luca-piccioni/OpenGL.Net Cc: BIGLER Don (IB); Author Subject: Re: [luca-piccioni/OpenGL.Net] Encounter NullReferenceException Second Time Gl.Control Window is Opened (#58)

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.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHubhttps://github.com/luca-piccioni/OpenGL.Net/issues/58#issuecomment-322219498, or mute the threadhttps://github.com/notifications/unsubscribe-auth/Adkl-UHlIMSUoMgganqDaJYnDACjUkvbks5sYGVZgaJpZM4O2apX.

luca-piccioni commented 7 years ago

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.

dcb-areva commented 7 years ago

I already tried calling Gl.BindApi() before creating the GlControl. I still get the same exception. In this case the first time the window is opened since I have to call Gl.Initialize() first in order to get the KhronosVersion and Extensions objects. That appears to be the key to solving the issue. I don’t understand why the OpenGL bindings are not available after calling Gl.Initialize() since Gl.Initialize() is called automatically without setting the OPENGL_NET_INIT env variable to NO.

luca-piccioni commented 7 years ago

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?

dcb-areva commented 7 years ago

I’ll try setting Wgl.CurrentExtensions.SwapControl_EXT to false.

I’m using version 0.5.2 which appears to be the latest on NuGet.

luca-piccioni commented 7 years ago

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

dcb-areva commented 7 years ago

Actually Gl.CurrentExtensions.SwapControl_EXT does not exist in version 0.5.2. At least the in the version I downloaded from NuGet. Does the NuGet package require updating?

luca-piccioni commented 7 years ago

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

dcb-areva commented 7 years ago

Unfortunately that didn’t work either. I end up getting an exception at the next OpenGL function call.

luca-piccioni commented 7 years ago

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" ... -.-

luca-piccioni commented 7 years ago

Another hypothesis. Windows are run on different threads?

dcb-areva commented 7 years ago

Wgl.GetCurrentContext()returns IntPtr.Zero after calling Dispose on the GlControl. That said, the disposition occurs in a different thread than the thread on which the GlControl was created. StartProcessing() calls Application.Run() on the form that contains the GlControl UserControl. Application.Run() spawns a new thread and calls containerForm_Load(). The GlControl is created in that method. I tried creating the GlControl directly in StartProcessing() but I get the same result.

dcb-areva commented 7 years ago

What do you mean “Windows are run on different threads”? The second time I try to open the form the attempt is made on a new thread.

luca-piccioni commented 7 years ago

(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:

luca-piccioni commented 7 years ago

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.

luca-piccioni commented 7 years ago

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.

dcb-areva commented 7 years ago

It also doesn’t explain why the form functions properly the first time it is opened but not the second. Perhaps that makes sense to you at this point.

dcb-areva commented 7 years ago

I’ll try using Wgl.MakeCurrent().

dcb-areva commented 7 years ago

Unfortunately it will not be easy to change the current design. Also the current code does use Invoke/InvokeRequired as explained in the thread.

dcb-areva commented 7 years ago

Actually I’m not certain how I can reach Wgl.BindApi() with a call to Wgl.MakeCurrent(). It requires that newContext is not IntPtr.Zero.

luca-piccioni commented 7 years ago

Something like using System.Reflection:

MethodInfo bindApiMethod = typeof(Wgl).GetMethod("BindAPI", BindingFlags.NonPublic | BindingFlags.Static);
bindApiMethod.Invoke(null, null);
luca-piccioni commented 7 years ago

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!

dcb-areva commented 7 years ago

Great! Will you be releasing a new version on nuget?

I will test the reflection solution tomorrow.

dcb-areva commented 7 years ago

I tried using the code below. I still get the same exception when opening the window the second time. As you noted, it’s strange that the exception occurs at SwapIntervalEXT instead of at Gl context creation. It’s almost as if some but not all of the OpenGL calls are initialized. I wonder if there is a static variable that becomes initialized when the window is first opened and remains initialized after disposition such that it does not allow the full initialization of all of the OpenGL functions.

luca-piccioni commented 7 years ago

Quite strange. At this point I suggest to clone the master branch, and test application against it. It is alrwady patched for your case.

luca-piccioni commented 7 years ago

Another check: did you call the Wgl.BindAPI method on the new thread, isn't it?

dcb-areva commented 7 years ago

I tested the application against the latest in Master. Your latest commit seems to have fixed the problem. I’d like to build against the NuGet package instead of building from source so I would appreciate an update at your earliest convenience. Thank you

luca-piccioni commented 7 years ago

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.