Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OpenGL context created on wrong device #1381

Open
bazilshep opened this issue Feb 4, 2022 · 8 comments
Open

OpenGL context created on wrong device #1381

bazilshep opened this issue Feb 4, 2022 · 8 comments

Comments

@bazilshep
Copy link

bazilshep commented Feb 4, 2022

Description

I am getting an exception System.AccessViolationException at OpenTK.Graphics.OpenGL4.GL.CreateShader(OpenTK.Graphics.OpenGL4.ShaderType).
I have tracked down the root cause of this exception to the OpenGL context being created on a device which only supports OpenGL 1.1, even though the system has a GPU which supports OpenGL 4.0.

Edit:
I did request a version 4.0 using the below code, however it returned a context with version 1.1.

var graphicsmode = new OpenTK.Graphics.GraphicsMode(new OpenTK.Graphics.ColorFormat(8, 8, 8, 8), 24, 8);
glControl1 = new OpenTK.GLControl(graphicsmode,4,0, OpenTK.Graphics.GraphicsContextFlags.Default);

I have found a few references to this issue for OpenGL in general, but none related directly to OpenTk.

The below link has a suggested workaround, but I don't understand it enough to try to implement it.
https://stackoverflow.com/questions/62372029/can-i-use-different-multigpu-in-opengl

Is there any feature in OpenTK to allow targeting a specific device (GPU) when creating an OpenGL context?

Related information

Windows 7
Winforms (using GLControl)
OpenTK 3.1.0.0
.Net Framework 4.8

Workaround

The issue can be worked around by setting the primary display device to a monitor which is connected to the target GPU. The OpenGL context is created on that device, preventing the exception.

@NogginBops
Copy link
Member

OpenTK 4 uses glfw for context creation which if i recall correctly does not support choosing which GPU to create the context on.
What you can however do is set a minimum version for the context that you create. You do that by setting the Version field in NativeWindowSettings, hopefully the driver will then choose the correct device when creating the context.

@NogginBops
Copy link
Member

Oh sorry, I thought this was about OpenTK 4, in OpenTK 3 you should still be able to request a specific minimum version, which is what I would test first.
Otherwise there might be some api in OpenTK 3 that allows you to choose which GPU but if that is the case I don't know that api

@bazilshep
Copy link
Author

Thanks for your comments. I added a bit more info in the OP. I am requesting the version 4.0 via the constructor to GLControl.

@NogginBops
Copy link
Member

Just so that I can be sure, how are you checking the version of the context you are getting?

You might want to try using the GameWindow ctor that allows you to pass a DisplayDevice, it might not be a perfect solution, but it might be a workaround.

@bazilshep
Copy link
Author

I'm using the below snippet of code now to request and check the returned context OpenGL version. When I was debugging this issue I added the calls to GL.GetString(StringName.Version) to confirm I wasn't getting the requested version.

I hadn't seen the referenced GameWindow constructor. It looks like what I am looking for, thanks for pointing it out. I'll need to to check the source and try to adapt this to work for GLControl though, since I need a Control.

        public string OpenGLVersionInfo { get; private set; }
        public System.ValueTuple<int, int> OpenGLVersion { get; private set; }
        public System.ValueTuple<int, int> RequiredGLVersion { get; private set; }
        public bool OpenGLVersionOk
        {
            get
            {
                return OpenGLVersion.Item1 > RequiredGLVersion.Item1
                    || OpenGLVersion.Item1 == RequiredGLVersion.Item1
                    && OpenGLVersion.Item2 >= RequiredGLVersion.Item2;
            }
        }

        void glControl1_HandleCreated(object sender, EventArgs e)
        {
            glControl1.MakeCurrent();
            GLUtil.AssertNoGlError();
            var renderer = GL.GetString(StringName.Renderer);
            var vendor = GL.GetString(StringName.Vendor);
            var version = GL.GetString(StringName.Version);
            var glslversion = GL.GetString(StringName.ShadingLanguageVersion);

            OpenGLVersionInfo = string.Format(
                "Renderer: {0}\nVendor: {1}\nGL Version: {2}\nGLSL Version:{3}\n",
                renderer,
                vendor,
                version,
                glslversion);

            var major = GL.GetInteger(GetPName.MajorVersion);
            var minor = GL.GetInteger(GetPName.MinorVersion);
            OpenGLVersion = System.ValueTuple.Create(major, minor);
            GLUtil.AssertNoGlError();

            System.Diagnostics.Debug.WriteLine("OpenGL context created.\n" + OpenGLVersionInfo);
        }

        public SceneView()
        {
           // Constructor (inherits from UserControl)

            InitializeComponent();
            RequiredGLVersion = ValueTuple.Create(4, 0);

            var graphicsmode = new OpenTK.Graphics.GraphicsMode(new OpenTK.Graphics.ColorFormat(8, 8, 8, 8), 24, 8);

            glControl1 = new OpenTK.GLControl(
                graphicsmode, 
                RequiredGLVersion.Item1,
                RequiredGLVersion.Item2,
                OpenTK.Graphics.GraphicsContextFlags.Default);

            glControl1.BackColor = System.Drawing.Color.Black;
            glControl1.Dock = System.Windows.Forms.DockStyle.Fill;
            glControl1.Location = new System.Drawing.Point(0, 0);
            glControl1.Name = "glControl1";
            glControl1.Size = new System.Drawing.Size(150, 150);
            glControl1.TabIndex = 0;
            glControl1.VSync = false;
            this.Controls.Add(this.glControl1);
            
            glControl1.Paint += glControl1_Paint;
            glControl1.Resize += glControl1_Resize;
            glControl1.Load += glControl1_Load;
            glControl1.HandleCreated += glControl1_HandleCreated;

            glControl1.Disposed += glControl1_Disposed;

            //snipped some irrelevant code...
           }

@NogginBops
Copy link
Member

Oh I see, I missed that you where using GLControl. I don't know if that code handles that, but let me know what you find out :)

@bazilshep
Copy link
Author

I cant seem to find where the device parameter is used. I've traced the call stack I think would be used, and didn't see any uses of device anywhere except to set the x, y arguments of the NativeWindow constructor. I guess the display device of the OpenGL context is somehow set via the coordinates of the window when it is created?

If this is the case I don't think I can use this fix, since I cant control where the Form is when the GLControl is created. In my use case the user is free to move the form wherever before selecting a tab containing the GLControl.

Below is the code path I think is used on windows.

public GameWindow(int width, int height, GraphicsMode mode, string title, GameWindowFlags options, DisplayDevice device,
int major, int minor, GraphicsContextFlags flags, IGraphicsContext sharedContext, bool isSingleThreaded)
: base(width, height, title, options,
mode == null ? GraphicsMode.Default : mode,
device == null ? DisplayDevice.Default : device)

public NativeWindow(int width, int height, string title, GameWindowFlags options, GraphicsMode mode, DisplayDevice device)
: this(device != null ? device.Bounds.Left + (device.Bounds.Width - width) / 2 : 0,
device != null ? device.Bounds.Top + (device.Bounds.Height - height) / 2 : 0,
width, height, title, options, mode, device) { }

public NativeWindow(int x, int y, int width, int height, string title, GameWindowFlags options, GraphicsMode mode, DisplayDevice device)

public override INativeWindow CreateNativeWindow(int x, int y, int width, int height, string title, GraphicsMode mode, GameWindowFlags options, DisplayDevice device)

private IntPtr CreateWindow(int x, int y, int width, int height, string title, GameWindowFlags options, DisplayDevice device, IntPtr parentHandle)

@seanofw
Copy link
Contributor

seanofw commented Feb 28, 2022

The below link has a suggested workaround, but I don't understand it enough to try to implement it.
https://stackoverflow.com/questions/62372029/can-i-use-different-multigpu-in-opengl

The answer @l_belev gives at that link is interesting, and I suspect it works, but it's probably not obvious why it works; it delves pretty deep into Win32 to do what it does.

The core idea of it, though, is to force OpenGL to pick up the correct driver before OpenGL actually starts by hacking the Windows DLL-loading process. Windows won't reload a DLL that's already in your process space, and the Microsoft implementation of OpenGL, it seems, won't attempt to load a GPU driver if there's one already loaded in memory as well. So the code there forcibly creates a Device Context (HDC) that requires OpenGL on the desired display device, which causes the correct GPU's driver to be loaded if it's not already in memory. Then it immediately destroys the HDC, leaving the GPU's DLL still sitting in memory, so that when you then use OpenGL afterward, OpenGL picks up the already-loaded DLL for the desired GPU instead of loading whichever one it otherwise would. Critically, this is a one-shot operation: You get to pick which GPU your program uses exactly once, upfront, and once that driver is loaded, none of the others will be.

Clever hackery. It works, but it's not something you'd want to rely on, since anything could make an OpenGL context in your process space before your program gets a chance to.

This functionality is most definitely not built into OpenTK, or built into GLFW to my knowledge either. If you want to do it from C#, you're going to need a lot of unsafe and P/Invoke to do it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants