dwmkerr / sharpgl

Use OpenGL in .NET applications. SharpGL wraps all modern OpenGL features and offers a powerful scene graph to aid development.
MIT License
755 stars 299 forks source link

Shared OpenGL Rendercontext #97

Open ThVoss opened 9 years ago

ThVoss commented 9 years ago

Hi,

I'm currently working on a WPF based UI which must be able to use the same render data in multiple control instance. To be able to support this I had to tweak the OpenGl Control and the CreateRenderContext methods.

Basically I check on control creation if the control is the first one and if not I provide the main render context to enable context sharing.

Although my solution works it isn't perfect, as currently all control instances always share their data. But maybe anyone is interested in what I've done and has ideas how to optimize it.

Even better would be if sharing between rendercontexts could be implemented into SharpGL.

Cheers Thomas

dwmkerr commented 9 years ago

Hi @ThVoss I'd be interested in seeing the solution. I'm in the process of refactoring the Render Contexts for SharpGL 3. There will be extensive changes which de-couple the context from the OpenGL object. The idea is that the controls will use the best effort OpenGL Context they can create, but it will be trivial to swap it out for another SharpGL render context or to use your own. This changes are fundamental to 3.0. I'd be interested to see whether your changes and my own are compatible, and perhaps add your shared render context to the suite of pre-built render contexts available.

ThVoss commented 9 years ago

Hi Dave,

the changes to the OpenGL.Create and RenderContext.Create methods are fairly simple (Step1). The rest see "Step 2".

Step 1

I overloaded the methods and added a sharing render context as additional parameter. Here the method definitions: OpenGL public virtual bool Create(OpenGLVersion openGLVersion, RenderContextType renderContextType, IntPtr sharingRenderContextHandle, int width, int height, int bitDepth, object parameter) IRenderContextProvider bool Create(OpenGLVersion openGLVersion, OpenGL gl, IntPtr sharingRenderContextHandle, int width, int height, int bitDepth, object parameter); FBORenderContextProvider public override bool Create(OpenGLVersion openGLVersion, OpenGL gl, IntPtr sharingRenderContextHandle, int width, int height, int bitDepth, object parameter) HiddenWindowRenderContextProvider public override bool Create(OpenGLVersion openGLVersion, OpenGL gl, IntPtr sharingRenderContextHandle, int width, int height, int bitDepth, object parameter) I store the sharingRenderContextHandle as a private field in the RenderContextProvider class /// /// The sharing render context handle. /// protected IntPtr sharingRenderContextHandle = IntPtr.Zero; and changed the call to gl.CreateContextAttribsARB in protected void UpdateContextVersion(OpenGL gl) method to: var hrc = gl.CreateContextAttribsARB(this.sharingRenderContextHandle, attributes); To get things running you can now call the Create method with a sharing context IntPtr. For the first instance you can either call the method with IntPtr.Null or use the old method (which implementation I changed to call the new function with IntPtr.Null for the sharing context).

Step2

The harder thing to find out is: Which is my sharing Context? To solve this problem I implemented a dependency property "SharingName" where all contexts with the same "SharingName" share their data. If the "SharingName" is string.Empty it will be set to the IDGuid.ToString() (see below) Each Control has it's identifying Guid (IDGuid). I have a static Dictionary where I store all IDGuids in a List<Guid> under the "SharingName" Key. In OnApplyTemplate I search for the first IDGuid for the "SharingName" and search in a second dictionary (Dictionary<IDGuid, OpenGL>) for the right OpenGL instance. Additionally we need to know two things: 1.: Which instance is the first instance (for data initialization / data updating)? => Dictionary[SharingName][0] == this.IDGuid 2.:Am I the Last instance (for unloading purpose)? => Dictionary[SharingName].Count == 1 && IsFirstInstance Last but not least we need a "lock" possibility that denies access to the shared data when it is initialized / updated / deleted. Otherwise you might end up with data being deleted / updated at the same time as another context wants to draw it. I currently implemented it with a real lock which is not perfect, as it denies parallel drawing although no updates to the data take place. I think I will implement my own "lock" mechanism which only denies access to the draw method if the first instance wants to modify the data. Sorry for this long comment. It's my first time to work with github and I don't know how to upload all the changes I made. If you want, I can send you the bunch of projects as a zip file or you tell me how to create a feature branch and I implement it and send a pull request. Cheers Thomas

dwmkerr commented 9 years ago

@ThVoss thanks for this it's great, I don't think you need to send me anything else, this provides enough context for me to ensure that the new OpenGL Contexts in SharpGL 3 will support this facility. Thanks again for sharing!

tgajera1016 commented 9 years ago

Hi ThVoss,

I tried the way you mentioned to create shared rendering context, but did not get success. Would you please share source code with me to create shared rendering context?

ThVoss commented 9 years ago

Hi,

no Problem. I will copy it into the comment as others might want to have it as well and I don't have any permanent download site.

So let's start (I will only fill in methods I changed): SharpGL.WPF.OpenGLControl:

        public OpenGLControl()
        {
            this.Content = image;
            if (ShareingContextGuids == null)
            {
                ShareingContextGuids = new Dictionary<string, List<Guid>>();
            }

            if (ShareingContexts == null)
            {
                ShareingContexts = new Dictionary<Guid, OpenGL>();
            }

            if (this.OpenGLGuid == Guid.Empty)
            {
                this.OpenGLGuid = Guid.NewGuid();
            }

            SizeChanged += new SizeChangedEventHandler(OpenGLControl_SizeChanged);
        }
        public override void OnApplyTemplate()
        {
            //  Call the base.
            base.OnApplyTemplate();

            object parameter = null;

            if (RenderContextType == RenderContextType.NativeWindow)
            {
                HwndSource source = (HwndSource)HwndSource.FromVisual(this);
                parameter = source.Handle;
            }

            //  Lock on OpenGL.
            lock (OpenGL.MultiGlLock)
            {
                gl.MakeCurrent();
                if (!ShareingContexts.ContainsKey(this.OpenGLGuid))
                {
                    if (this.SharingName == string.Empty)
                    {
                        this.SharingName = this.OpenGLGuid.ToString();
                    }

                    List<Guid> sharingGuids = null;
                    if (!ShareingContextGuids.TryGetValue(this.SharingName, out sharingGuids))
                    {
                        sharingGuids = new List<Guid>();
                        ShareingContextGuids.Add(this.SharingName, sharingGuids);
                    }

                    sharingGuids.Add(this.OpenGLGuid);

                    ShareingContexts.Add(this.OpenGLGuid, gl);
                }

                if (this.IsFirstInstance)
                {
                    //  Create OpenGL.
                    gl.Create(OpenGLVersion, RenderContextType, 1, 1, 32, parameter);
                }
                else
                {
                    //  Create OpenGL.
                    gl.Create(OpenGLVersion, RenderContextType, ShareingContexts[ShareingContextGuids[this.SharingName][0]].RenderContextProvider.RenderContextHandle, 1, 1, 32, parameter);
                }

                //  Create our fast event args.
                eventArgsFast = new OpenGLEventArgs(gl);

                //  Set the most basic OpenGL styles.
                gl.ShadeModel(OpenGL.GL_SMOOTH);
                gl.ClearColor(SystemColors.ControlColor.R / 255f, SystemColors.ControlColor.G / 255f, SystemColors.ControlColor.B / 255f, SystemColors.ControlColor.A / 255f);
                gl.ClearDepth(1.0f);
                gl.Enable(OpenGL.GL_DEPTH_TEST);
                gl.DepthFunc(OpenGL.GL_LEQUAL);
                gl.Hint(OpenGL.GL_PERSPECTIVE_CORRECTION_HINT, OpenGL.GL_NICEST);

                //  Fire the OpenGL initialised event.
                var handler = OpenGLInitialized;
                if (handler != null)
                    handler(this, eventArgsFast);
            }

            //  DispatcherTimer setup
            timer = new DispatcherTimer(DispatcherPriority.Send);
            timer.Tick += new EventHandler(timer_Tick);
            timer.Interval = new TimeSpan(0, 0, 0, 0, (int)(1000.0 / FrameRate));
            timer.Start();
        }
        void timer_Tick(object sender, EventArgs e)
        {
            // If you use shared context it is likely that one or more of the controls is not visible so save some performance.
            if (!IsVisible)
            {
                return;
            }
       ....
        /// <summary>
        /// The sharing name property.
        /// </summary>
        private static readonly DependencyProperty SharingNameProperty =
          DependencyProperty.Register("SharingName", typeof(string), typeof(OpenGLControl),
          new PropertyMetadata(string.Empty, null));

        /// <summary>
        /// Gets or sets the name of the sharing.
        /// </summary>
        /// <value>
        /// The name of the sharing.
        /// </value>
        public string SharingName
        {
            get { return (string)GetValue(SharingNameProperty); }
            set { SetValue(SharingNameProperty, value); }
        }

        /// <summary>
        /// Gets or sets the shareing contexts guids. Dictionary&lt;Sharing name, List&lt;Sharing context Guids&gt;&gt;
        /// </summary>
        /// <value>
        /// The shareing contexts guids dictionary.
        /// </value>
        public static Dictionary<string, List<Guid>> ShareingContextGuids { get; set; }

        /// <summary>
        /// Gets or sets the shareing contexts.
        /// </summary>
        /// <value>
        /// The shareing contexts.
        /// </value>
        public static Dictionary<Guid, OpenGL> ShareingContexts { get; set; }

        /// <summary>
        /// Gets or sets the open gl unique identifier.
        /// </summary>
        /// <value>
        /// The open gl unique identifier.
        /// </value>
        public Guid OpenGLGuid { get; protected set; }

        /// <summary>
        /// Gets or sets a value indicating whether this instance is first instance.
        /// </summary>
        /// <value>
        /// <c>true</c> if this instance is first instance; otherwise, <c>false</c>.
        /// </value>
        public bool IsFirstInstance
        {
            get
            {
                return OpenGLControl.ShareingContextGuids.ContainsKey(this.SharingName) &&
                       OpenGLControl.ShareingContextGuids[this.SharingName] != null &&
                       OpenGLControl.ShareingContextGuids[this.SharingName].Any() &&
                       OpenGLControl.ShareingContextGuids[this.SharingName][0] == this.OpenGLGuid;
            }
        }

        /// <summary>
        /// Gets a value indicating whether this instance is last instance.
        /// </summary>
        /// <value>
        /// <c>true</c> if this instance is last instance; otherwise, <c>false</c>.
        /// </value>
        public bool IsLastInstance
        {
            get
            {
                return OpenGLControl.ShareingContextGuids.ContainsKey(this.SharingName) &&
                       OpenGLControl.ShareingContextGuids[this.SharingName] != null &&
                       OpenGLControl.ShareingContextGuids[this.SharingName].Any() &&
                       OpenGLControl.ShareingContextGuids[this.SharingName].Count <= 1;
            }
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            this.timer.Stop();
            this.timer.Tick -= timer_Tick;

            if (OpenGLControl.ShareingContextGuids.ContainsKey(this.SharingName))
            {
                if (this.IsLastInstance)
                {
                    OpenGLControl.ShareingContextGuids.Remove(this.SharingName);

                }
                else if (OpenGLControl.ShareingContextGuids[this.SharingName] != null &&
                         OpenGLControl.ShareingContextGuids[this.SharingName].Contains(this.OpenGLGuid))
                {
                    ShareingContextGuids[this.SharingName].Remove(this.OpenGLGuid);
                }

                this.OpenGL.RenderContextProvider.Destroy();

                ShareingContexts.Remove(this.OpenGLGuid);
            }
        }

please see next comment for further required changes.

Thomas

ThVoss commented 9 years ago

This is the continued commend from my last post:

SharpGL.OpenGL.cs:

        /// <summary>
        /// Creates the OpenGL instance.
        /// </summary>
        /// <param name="openGLVersion">The OpenGL version requested.</param>
        /// <param name="renderContextType">Type of the render context.</param>
        /// <param name="width">The drawing context width.</param>
        /// <param name="height">The drawing context height.</param>
        /// <param name="bitDepth">The bit depth.</param>
        /// <param name="parameter">The parameter.</param>
        /// <returns></returns>
        public virtual bool Create(OpenGLVersion openGLVersion, RenderContextType renderContextType, int width, int height, int bitDepth, object parameter)
        {

            return Create(openGLVersion, renderContextType, IntPtr.Zero, width, height, bitDepth, parameter);
        }

        /// <summary>
        /// Creates the OpenGL instance.
        /// </summary>
        /// <param name="openGLVersion">The OpenGL version requested.</param>
        /// <param name="renderContextType">Type of the render context.</param>
        /// <param name="width">The drawing context width.</param>
        /// <param name="height">The drawing context height.</param>
        /// <param name="bitDepth">The bit depth.</param>
        /// <param name="parameter">The parameter.</param>
        /// <returns></returns>
        public virtual bool Create(OpenGLVersion openGLVersion, RenderContextType renderContextType, IntPtr sharingRenderContextHandle, int width, int height, int bitDepth, object parameter)
        {
            //  Return if we don't have a sensible width or height.
            if (width == 0 || height == 0 || bitDepth == 0)
                return false;

            //  Create an instance of the render context provider.
            switch (renderContextType)
            {
                case RenderContextType.DIBSection:
                    renderContextProvider = new DIBSectionRenderContextProvider();
                    break;
                case RenderContextType.NativeWindow:
                    renderContextProvider = new NativeWindowRenderContextProvider();
                    break;
                case RenderContextType.HiddenWindow:
                    renderContextProvider = new HiddenWindowRenderContextProvider();
                    break;
                case RenderContextType.FBO:
                    renderContextProvider = new FBORenderContextProvider();
                    break;
            }

            //  Create the render context.
            renderContextProvider.Create(openGLVersion, this, sharingRenderContextHandle, width, height, bitDepth, parameter);

            return true;
        }
        /// <summary>
        /// Gets or sets the multi gl lock. This lock is used to protect the data if you draw from changes.
        /// When you have to Change data (VBOs, Shader programs, etc) lock first so that all contexts
        /// stop drawing. This is not optimized for multiple shareing names.
        /// </summary>
        /// <value>
        /// The multi gl lock.
        /// </value>
        public static object MultiGlLock { get; set; }
ThVoss commented 9 years ago

SharpGL.RenderContextProviders.IRenderContextProvider:

    /// <summary>
    /// Defines the contract for a type that can provide an OpenGL render context.
    /// </summary>
    public interface IRenderContextProvider : IDisposable
    {
        /// <summary>
        /// Creates the render context provider. Must also create the OpenGL extensions.
        /// </summary>
        /// <param name="openGLVersion">The desired OpenGL version.</param>
        /// <param name="gl">The OpenGL context.</param>
        /// <param name="width">The width.</param>
        /// <param name="height">The height.</param>
        /// <param name="bitDepth">The bit depth.</param>
        /// <param name="parameter">The extra parameter.</param>
        /// <returns></returns>
        bool Create(OpenGLVersion openGLVersion, OpenGL gl, int width, int height, int bitDepth, object parameter);

        /// <summary>
        /// Creates the render context provider. Must also create the OpenGL extensions.
        /// </summary>
        /// <param name="openGLVersion">The open gl version.</param>
        /// <param name="gl">The gl.</param>
        /// <param name="sharingRenderContext">The sharing render context.</param>
        /// <param name="width">The width.</param>
        /// <param name="height">The height.</param>
        /// <param name="bitDepth">The bit depth.</param>
        /// <param name="parameter">The parameter.</param>
        /// <returns></returns>
        bool Create(OpenGLVersion openGLVersion, OpenGL gl, IntPtr sharingRenderContextHandle, int width, int height, int bitDepth, object parameter);
ThVoss commented 9 years ago

And as an example for the render context Providers: FBORenderContextProvider:

        /// <summary>
        /// Creates the render context provider. Must also create the OpenGL extensions.
        /// </summary>
        /// <param name="openGLVersion">The desired OpenGL version.</param>
        /// <param name="gl">The OpenGL context.</param>
        /// <param name="width">The width.</param>
        /// <param name="height">The height.</param>
        /// <param name="bitDepth">The bit depth.</param>
        /// <param name="parameter">The parameter</param>
        /// <returns></returns>
        public override bool Create(OpenGLVersion openGLVersion, OpenGL gl, int width, int height, int bitDepth, object parameter)
        {
            return this.Create(openGLVersion, gl, IntPtr.Zero, width, height, bitDepth, parameter);
        }

        /// <summary>
        /// Creates the render context provider. Must also create the OpenGL extensions.
        /// </summary>
        /// <param name="openGLVersion">The desired OpenGL version.</param>
        /// <param name="gl">The OpenGL context.</param>
        /// <param name="width">The width.</param>
        /// <param name="height">The height.</param>
        /// <param name="bitDepth">The bit depth.</param>
        /// <param name="parameter">The parameter</param>
        /// <returns></returns>
        public override bool Create(OpenGLVersion openGLVersion, OpenGL gl, IntPtr sharingRenderContextHandle, int width, int height, int bitDepth, object parameter)
        {
            this.gl = gl;

            //  Call the base class.            
            base.Create(openGLVersion, gl, sharingRenderContextHandle, width, height, bitDepth, parameter);
         ...
ThVoss commented 9 years ago

And the create method is handed even further down the inheritance: HiddenWindowRenderContextProvider:

        /// <summary>
        /// Creates the render context provider. Must also create the OpenGL extensions.
        /// </summary>
        /// <param name="openGLVersion">The desired OpenGL version.</param>
        /// <param name="gl">The OpenGL context.</param>
        /// <param name="width">The width.</param>
        /// <param name="height">The height.</param>
        /// <param name="bitDepth">The bit depth.</param>
        /// <param name="parameter">The extra parameter.</param>
        /// <returns></returns>
        public override bool Create(OpenGLVersion openGLVersion, OpenGL gl, int width, int height, int bitDepth, object parameter)
        {
            return this.Create(openGLVersion, gl, IntPtr.Zero, width, height, bitDepth, parameter);
        }

        /// <summary>
        /// Creates the render context provider. Must also create the OpenGL extensions.
        /// </summary>
        /// <param name="openGLVersion">The desired OpenGL version.</param>
        /// <param name="gl">The OpenGL context.</param>
        /// <param name="width">The width.</param>
        /// <param name="height">The height.</param>
        /// <param name="bitDepth">The bit depth.</param>
        /// <param name="parameter">The parameter</param>
        /// <returns></returns>
        public override bool Create(OpenGLVersion openGLVersion, OpenGL gl, IntPtr sharingRenderContextHandle, int width, int height, int bitDepth, object parameter)
        {
            //  Call the base.
            base.Create(openGLVersion, gl, sharingRenderContextHandle, width, height, bitDepth, parameter);
           ...
ThVoss commented 9 years ago

And last but not least: RenderContextProvider:


        /// <summary>
        /// Creates the render context provider. Must also create the OpenGL extensions.
        /// </summary>
        /// <param name="openGLVersion">The desired OpenGL version.</param>
        /// <param name="gl">The OpenGL context.</param>
        /// <param name="width">The width.</param>
        /// <param name="height">The height.</param>
        /// <param name="bitDepth">The bit depth.</param>
        /// <param name="parameter">The extra parameter.</param>
        /// <returns></returns>
        public virtual bool Create(OpenGLVersion openGLVersion, OpenGL gl, int width, int height, int bitDepth, object parameter)
        {
            this.Initialize(openGLVersion, width, height, bitDepth, parameter);
            return true;
        }

        public virtual bool Create(OpenGLVersion openGLVersion, OpenGL gl, IntPtr sharingRenderContextHandle, int width, int height, int bitDepth, object parameter)
        {
            this.sharingRenderContextHandle = sharingRenderContextHandle;
            this.Initialize(openGLVersion, width, height, bitDepth, parameter);
            return true;
        }

        /// <summary>
        /// The sharing render context handle.
        /// </summary>
        protected IntPtr sharingRenderContextHandle = IntPtr.Zero;

        /// <summary>
        /// Only valid to be called after the render context is created, this function attempts to
        /// move the render context to the OpenGL version originally requested. If this is &gt; 2.1, this
        /// means building a new context. If this fails, we'll have to make do with 2.1.
        /// </summary>
        /// <param name="gl">The OpenGL instance.</param>
        protected void UpdateContextVersion(OpenGL gl)
        {
            //  If the request version number is anything up to and including 2.1, standard render contexts
            //  will provide what we need (as long as the graphics card drivers are up to date).
            var requestedVersionNumber = VersionAttribute.GetVersionAttribute(requestedOpenGLVersion);
            if (requestedVersionNumber.IsAtLeastVersion(3, 0) == false)
            {
                createdOpenGLVersion = requestedOpenGLVersion;
                return;
            }

            //  Now the none-trivial case. We must use the WGL_ARB_create_context extension to 
            //  attempt to create a 3.0+ context.
            try
            {
                int[] attributes = 
                {
                    OpenGL.WGL_CONTEXT_MAJOR_VERSION_ARB, requestedVersionNumber.Major,  
                    OpenGL.WGL_CONTEXT_MINOR_VERSION_ARB, requestedVersionNumber.Minor,
                    OpenGL.WGL_CONTEXT_FLAGS_ARB, 0,//OpenGL.WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
                    OpenGL.WGL_CONTEXT_PROFILE_MASK_ARB, OpenGL.WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB,
                    0
                };

                var hrc = gl.CreateContextAttribsARB(this.sharingRenderContextHandle, attributes);
                Win32.wglMakeCurrent(IntPtr.Zero, IntPtr.Zero);
                Win32.wglDeleteContext(renderContextHandle);
                Win32.wglMakeCurrent(deviceContextHandle, hrc);
                renderContextHandle = hrc;
            }
            catch (Exception)
            {
                //  TODO: can we actually get the real version?
                createdOpenGLVersion = OpenGLVersion.OpenGL2_1;
            }
        }

Hope I don't annoy anybody by posting that much code.

Anyway I guess this will get you closer to your solution.

If I forgot something drop me a lign. I will try to help then.

Cheers Thomas