opentk icon indicating copy to clipboard operation
opentk copied to clipboard

OpenTK 4.0 is not compatible with OpenTK.GlControl

Open VladislavAntonyuk opened this issue 4 years ago • 37 comments

Description

Updating OpenTK to version 4.0 causes runtime errors: Unable to find OpenTK version 3.1.0...

VladislavAntonyuk avatar Oct 12 '20 12:10 VladislavAntonyuk

GLControl is not yet updated for OpenTK 4.0. Can you please update this issue to include the runtime errors you are getting?

NogginBops avatar Oct 12 '20 15:10 NogginBops

I think the main issue here is that OpenTK 4.0 only installs when the project is a .net core project. This due to this requirement by OpenTK.compute, which halts OpenTK 4.0 from being installed in a .NET Framework application. So you can only use GLControl when you have a .net framework application but you can only install OpenTK 4.0 when you write a .NET core application

hellbender3069 avatar Oct 12 '20 21:10 hellbender3069

What is "OpenTK.compute"?

OpenTK 4.0 only runs on .net core, it doesn't run on .net framework. If you want to run OpenTK on .net framework use 3.2 as that is the supported version for .net framework (if you find any bugs in 3.2 we will still try to fix them).

NogginBops avatar Oct 12 '20 21:10 NogginBops

OpenTk.Compute is the OpenCL implementation if i am correct. And in nuget the OpenTk 4.0 package has a dependency on OpenTK.Compute. In 3.2 it seems that the GLControl can't be added to the form (see issue #1115 ).

hellbender3069 avatar Oct 12 '20 21:10 hellbender3069

Ah right! I forgot opencl made it into 4.0. OpenTK.Compute is not the only dependency on .net core, OpenTK.OpenAL and OpenTK.Windowing.Desktop also have dependencies on .net core.

I don't think anyone has looked into why you can't add glcontrol to the form from the editor, but adding it in the designer code and then opening the forms editor might work to put it in the editor.

NogginBops avatar Oct 12 '20 21:10 NogginBops

This is a known issue. If this is critical for your use case, we'd love to assist in getting a PR to update the GLControl. Conceptually this should be fairly simple to achieve.

varon avatar Oct 24 '20 07:10 varon

Awesome! It's just... Really-really-really needed!

uzername avatar Oct 24 '20 09:10 uzername

Stop by the discord and @ me! Then we can work through the steps on getting this in for you guys.

varon avatar Oct 25 '20 20:10 varon

Where can I find the GLControl code?

https://github.com/search?l=C%23&q=org%3Aopentk+GLControl&type=Code

xPaw avatar Nov 09 '20 13:11 xPaw

I'm pretty sure the GLControl source can be found here: https://github.com/opentk/opentk/tree/3.x/src/OpenTK.GLControl

NogginBops avatar Nov 09 '20 13:11 NogginBops

Also worth mentioning that we have a .Net Core compatible ultra-fast WPF Control in development.

You can find this here: https://github.com/varon/GLWpfControl

varon avatar Nov 12 '20 13:11 varon

Pre-release packages for the .Net core compatible GLWpfControl are available. https://www.nuget.org/packages/OpenTK.GLWpfControl

This is way, way better than the GLControl and highly suggested if you have a WPF application.

varon avatar Nov 14 '20 09:11 varon

Update: @Pilzinsel64 in Discord may have some plans to work on this. Come drop by if you want to help.

varon avatar Nov 25 '20 19:11 varon

Does anyone have an idea as to when the GL Control will be upgraded to work with the OpenTK 4.0 ?

yantru avatar Dec 16 '20 14:12 yantru

@yantru The simple answer is: "When someone puts in the time in to update it".

At the moment there is no one that has public plans to update the GL Control to OpenTK 4.0. The main problem here is creating a opengl context across all platforms, which we no longer have built in now that we are using GLFW. So that would have to be implemented in some way, after that blocker is overcome then we can probably get a gl control working pretty fast.

If you want to implement it yourself you are more than welcome to join the discord if you want some guidance.

NogginBops avatar Dec 16 '20 20:12 NogginBops

@yantru For what it's worth, I spent part of this weekend rewriting the GLControl mostly from scratch to be compatible with OpenTK 4.x. While it's still a prototype, you (and anyone else who wants to!) are welcome to give it a try; it works in a number of my tests now, and it might be able to work for you too. You'll have to clone/checkout the code and build it yourself, though; but it's a single project, so it's not hard.

If you're so inclined, give it a try, and see if it works for what you're doing: https://github.com/seanofw/opentk/tree/feature/new-glcontrol-4.x

seanofw avatar Dec 21 '20 02:12 seanofw

Hi NogginBops, Seanow, thank you for the replies.

For the OpenTK 4.x control prototype, I tried to check it out on your branch 'new-glcontrol-4.x' and it didn't make it through the building operation. I'm using VS 2019 community right now, was there anything special that I should have known to make it work? I mean, that was a complete solution, that should have worked at least for the building. Maybe just some settings to tweak ?

I also tried several other approaches now, but none gave me proper functional results, searching through the net always gave me non-functional results with .Net Core 5.0, but those approaches are often working with Net Framework 4.7. Often, the approaches are outdated and difficult to make it work again, at least for me.

  • GLWpfControl within an HostElement : Continuous rendering / Sizing not working. At least, I was able to instantiate the OpenGL context (ClearColor), but nothing else. That could also be a problem with the HostElement...

  • C++/Cli --> OpenGL directly : This solution did only work within Framework .Net 4.7 (not on .Net5), it seems it's getting more complicated to get that bridge working within .Net5. Many suggests also to avoid that path, but I still gave it a try. I am not an expert to C++, but as I understand, C# or C++ for doing the gl calls wouldn't have matter me. Just need to be able to pass information between the C# main application and the C++ rendering.

  • NativeWindow embeded. I could even launch window and embed them to another window with SetParent / SetWindowPos using the WindowPtr, but it doesn't seems to be compatible as this didn't work either. Even if I get the pointer, it has no effect when calling the WinApi on it as it would when doing the same thing with applications such as 'notepad.exe'.

While I understand some bits of Windows/API and graphical context and such, I am far from being an expert in game field, being more in the corporate/industrial field. In fact, what I'm trying to do is reuse graphical WinForms based controls made with OpenTK.GLControl in Framework 4.7 to an approach using the latest Net Core 5.0. The OpenGL rendering engine is used to render some part of realtime industrial machine components, and there is multiple controls that target some different part of the machine. Once I get it functionnal again, I will only need to copy the GL.x calls to enable the rendering again.

I will try again those approaches from scratch, I am sure that one of those should definitely work. I surely must have miss something.

Still thank you for the help, I will keep an eye here, let me know if anyone has more information that could help achieve this.

yantru avatar Dec 22 '20 00:12 yantru

@yantru The error you get while building the project would be a good idea to include, as that is basically the only way to know what is wrong.

NogginBops avatar Dec 22 '20 00:12 NogginBops

@NogginBops Sure, it's like links were not made between projects or things got mixed in the branch. I get somewhat around 438 errors. I switched/checked out the branch and pull everything...

1 x CS0103, The name 'ToGrayscale' does not exist in the current context 21 x CS0208 Cannot take the address of, get the size of, or declare a pointer to a managed type ('Device') 32 x CS0234 The type or namespace name 'Extensions' does not exist in the namespace 'OpenTK.Core' (are you missing an assembly reference?) (or similar) 325 x CS0246 The type or namespace name 'AdvancedDLSupport' could not be found (are you missing a using directive or an assembly reference?) (or similar) 6 x SD0535 'MultiChannelBuffers' does not implement interface member 'IMultiChannelBuffers.BufferData (uint, MultiChannelBufferFormat, void*, int, int)' (or similar) 16 x FS0039 The Type 'Registry' is not defined (or similar) 35 x FS0072 Lookup on object of indeterminate type based on information prior to this program point ... (or similar)

Some more errors and a bunchs of warnings.

yantru avatar Dec 22 '20 01:12 yantru

Some of these are known "errors". Like the AdvancedDLSupport being missing in openal extensions (these are left as a reminder that there is work to do, that project is not part of the final build and you can safely unload that project). ToGrayscale is part of the opencl test code, which also isn't a part of the final build and can be unloaded (I've not looked into why it's failing). It's a bit unintuitive.

With these unloaded you should get further in the build, and get the actual build problems if there are any. Also of note is that you should run the ./build.cmd or ./build.sh script to build (yeah I know it's weird).

Or you can build only the winforms control from source here. Which might be easier.

NogginBops avatar Dec 22 '20 01:12 NogginBops

I made a number of substantial changes to the WinForms control today in my local copy, in discussion with @varon; its API is cleaner, and it handles events much better now. At some point, we'll probably integrate this into the OpenTK repository properly. I don't think it's quite ready yet, but it's getting close.

For now, while it's still in development, @yantru, you should probably just clone my repo and build it directly. It has very minimal dependencies: The OpenTK.WinForms project in it is the only part you need to have a usable GLControl that works with OpenTK 4; and there's an included OpenTK.WinForms.TestForm project that demonstrates it in use.

I would reasonably expect my repo to build with most of the major tools out there, given how simple its projects (intentionally!) are. I do all of the dev work in VS2019 Professional Edition, but I would be really surprised if it didn't "just work" in VS2019 Community Edition as well; and building a simple standalone project is likely a lot easier than trying to build all of OpenTK.

tl;dr: Try cloning, building, and using https://github.com/seanofw/opentk.winforms for now until the OpenTK team integrates that work into their project.

seanofw avatar Dec 22 '20 03:12 seanofw

Hi @NogginBops and @seanofw ,

Thank you very much for your help.

I appreciated the explanation for the whole solution not working, I understand it is not yet wholly completed now.

I also just tried to build the standalone OpenTK.Winforms project and it compiled ok. I'll now work to reintegrate this control back.

Very fast answers from you guys, I didn't expect that much speed !

yantru avatar Dec 22 '20 13:12 yantru

Hi @seanofw,

I think it's getting closer. The test form works only if there is only one control. For example, adding a second control glControl2 and placing both paint/resize accordingly has weird effects :

first control view is getting like applied twice. (probably resized viewport is getting multiplied) and only the first control is getting drawn, the other one never getting any thing.

I tried forcing a glMakeCurrent(), without any effect.

I also remarked, and I don't know if it is related with the control, but VS seems to have difficult to follow and crashed a lot since I added the control to the solution.

I don't know if it's the best way to show code, but here is the Form1.cs code from TestForm slightly modified to explain what I mean.

using System; using System.ComponentModel; using System.Windows.Forms; using OpenTK.Graphics.OpenGL; using OpenTK.Mathematics;

namespace OpenTK.WinForms.TestForm { public partial class Form1 : Form { private Timer _timer = null!; private float _angle = 0.0f;

    public Form1()
	{
		InitializeComponent();
	}

    private void exitToolStripMenuItem_Click(object sender, EventArgs e)
    {
        Close();
    }

    private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
    {
        MessageBox.Show(this,
            "This demonstrates a simple use of the new OpenTK 4.x GLControl.",
            "GLControl Test Form",
            MessageBoxButtons.OK);
    }

    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);

        glControl.Resize += new EventHandler(glControl_Resize);
        glControl.Paint += new PaintEventHandler(glControl_Paint);

        glControl2.Resize += new EventHandler(GlControl2_Resize);
        glControl2.Paint += new PaintEventHandler(GlControl2_Paint);

        {
            // Input testing.

            glControl.GotFocus += (sender, e) =>
                System.Diagnostics.Debug.WriteLine("Focus in");
            glControl.LostFocus += (sender, e) =>
                System.Diagnostics.Debug.WriteLine("Focus out");
            glControl.MouseDown += (sender, e) =>
                System.Diagnostics.Debug.WriteLine($"Mouse down: ({e.X},{e.Y})");
            glControl.MouseUp += (sender, e) =>
                System.Diagnostics.Debug.WriteLine($"Mouse up: ({e.X},{e.Y})");
            glControl.KeyDown += (sender, e) =>
            {
                System.Diagnostics.Debug.WriteLine($"Key down: {e.KeyCode}");
                if (e.KeyCode == Keys.X)
                    glControl.EnableNativeInput();
            };
            glControl.KeyUp += (sender, e) =>
                System.Diagnostics.Debug.WriteLine($"Key up: {e.KeyCode}");

            INativeInput nativeInput = glControl.EnableNativeInput();

            nativeInput.MouseDown += (e) =>
                System.Diagnostics.Debug.WriteLine($"Native mouse down");
            nativeInput.MouseUp += (e) =>
                System.Diagnostics.Debug.WriteLine($"Native mouse up");
            nativeInput.KeyDown += (e) =>
            {
                System.Diagnostics.Debug.WriteLine($"Native key down: {e.Key}");
                if (e.Key == Windowing.GraphicsLibraryFramework.Keys.X)
                    glControl.DisableNativeInput();
            };
            nativeInput.KeyUp += (e) =>
                System.Diagnostics.Debug.WriteLine($"Native key up: {e.Key}");
            nativeInput.TextInput += (e) =>
                System.Diagnostics.Debug.WriteLine($"Native text input: {e.AsString}");
            nativeInput.JoystickConnected += (e) =>
                System.Diagnostics.Debug.WriteLine($"Joystick connected: {e.JoystickId}");
        }

        Text =
            GL.GetString(StringName.Vendor) + " " +
            GL.GetString(StringName.Renderer) + " " +
            GL.GetString(StringName.Version);

        GL.ClearColor(Color4.MidnightBlue);
        GL.Enable(EnableCap.DepthTest);

        _timer = new Timer();
        _timer.Tick += (sender, e) =>
        {
            _angle += 0.5f;
            Render();
        };
        _timer.Interval = 50;   // 1000 ms per sec / 50 ms per frame = 20 FPS
        _timer.Start();

        // Ensure that the viewport and projection matrix are set correctly.
        glControl_Resize(glControl, EventArgs.Empty);
    }

    private void GlControl2_Paint(object sender, PaintEventArgs e)
    {
        Render2();
    }

    private void GlControl2_Resize(object? sender, EventArgs e)
    {
        GLControl? c = sender as GLControl;

        if (c.ClientSize.Height == 0)
            c.ClientSize = new System.Drawing.Size(c.ClientSize.Width, 1);

        GL.Viewport(0, 0, c.ClientSize.Width, c.ClientSize.Height);

        float aspect_ratio = Width / (float)Height;
        Matrix4 perpective = Matrix4.CreatePerspectiveFieldOfView(MathHelper.PiOver4, aspect_ratio, 1, 64);
        GL.MatrixMode(MatrixMode.Projection);
        GL.LoadMatrix(ref perpective);
    }

    protected override void OnClosing(CancelEventArgs e)
    {
        base.OnClosing(e);
    }

    void glControl_Resize(object sender, EventArgs e)
    {
        GLControl? c = sender as GLControl;

        if (c.ClientSize.Height == 0)
            c.ClientSize = new System.Drawing.Size(c.ClientSize.Width, 1);

        GL.Viewport(0, 0, c.ClientSize.Width, c.ClientSize.Height);

        float aspect_ratio = Width / (float)Height;
        Matrix4 perpective = Matrix4.CreatePerspectiveFieldOfView(MathHelper.PiOver4, aspect_ratio, 1, 64);
        GL.MatrixMode(MatrixMode.Projection);
        GL.LoadMatrix(ref perpective);
    }

    void glControl_Paint(object sender, PaintEventArgs e)
    {
        Render();
    }

    private void Render()
    {
        Matrix4 lookat = Matrix4.LookAt(0, 5, 5, 0, 0, 0, 0, 1, 0);
        GL.MatrixMode(MatrixMode.Modelview);
        GL.LoadMatrix(ref lookat);

        GL.Rotate(_angle, 0.0f, 1.0f, 0.0f);

        GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

        DrawCube();

        glControl.SwapBuffers();
    }

    private void Render2()
    {
        Matrix4 lookat = Matrix4.LookAt(0, 5, 5, 0, 0, 0, 0, 1, 0);
        GL.MatrixMode(MatrixMode.Modelview);
        GL.LoadMatrix(ref lookat);

        GL.Rotate(_angle, 0.0f, 1.0f, 0.0f);

        GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

        DrawCube2();

        glControl2.SwapBuffers();
    }

    private void DrawCube()
    {
        GL.Begin(BeginMode.Quads);

        GL.Color4(Color4.Silver);
        GL.Vertex3(-1.0f, -1.0f, -1.0f);
        GL.Vertex3(-1.0f, 1.0f, -1.0f);
        GL.Vertex3(1.0f, 1.0f, -1.0f);
        GL.Vertex3(1.0f, -1.0f, -1.0f);

        GL.Color4(Color4.Honeydew);
        GL.Vertex3(-1.0f, -1.0f, -1.0f);
        GL.Vertex3(1.0f, -1.0f, -1.0f);
        GL.Vertex3(1.0f, -1.0f, 1.0f);
        GL.Vertex3(-1.0f, -1.0f, 1.0f);

        GL.Color4(Color4.Moccasin);
        GL.Vertex3(-1.0f, -1.0f, -1.0f);
        GL.Vertex3(-1.0f, -1.0f, 1.0f);
        GL.Vertex3(-1.0f, 1.0f, 1.0f);
        GL.Vertex3(-1.0f, 1.0f, -1.0f);

        GL.Color4(Color4.IndianRed);
        GL.Vertex3(-1.0f, -1.0f, 1.0f);
        GL.Vertex3(1.0f, -1.0f, 1.0f);
        GL.Vertex3(1.0f, 1.0f, 1.0f);
        GL.Vertex3(-1.0f, 1.0f, 1.0f);

        GL.Color4(Color4.PaleVioletRed);
        GL.Vertex3(-1.0f, 1.0f, -1.0f);
        GL.Vertex3(-1.0f, 1.0f, 1.0f);
        GL.Vertex3(1.0f, 1.0f, 1.0f);
        GL.Vertex3(1.0f, 1.0f, -1.0f);

        GL.Color4(Color4.ForestGreen);
        GL.Vertex3(1.0f, -1.0f, -1.0f);
        GL.Vertex3(1.0f, 1.0f, -1.0f);
        GL.Vertex3(1.0f, 1.0f, 1.0f);
        GL.Vertex3(1.0f, -1.0f, 1.0f);

        GL.End();
    }

    private void DrawCube2()
    {
        GL.Begin(BeginMode.Quads);

        GL.Color4(Color4.Pink);
        GL.Vertex3(-1.0f, -1.0f, -1.0f);
        GL.Vertex3(-1.0f, 1.0f, -1.0f);
        GL.Vertex3(1.0f, 1.0f, -1.0f);
        GL.Vertex3(1.0f, -1.0f, -1.0f);

        GL.Color4(Color4.Pink);
        GL.Vertex3(-1.0f, -1.0f, -1.0f);
        GL.Vertex3(1.0f, -1.0f, -1.0f);
        GL.Vertex3(1.0f, -1.0f, 1.0f);
        GL.Vertex3(-1.0f, -1.0f, 1.0f);

        GL.Color4(Color4.Pink);
        GL.Vertex3(-1.0f, -1.0f, -1.0f);
        GL.Vertex3(-1.0f, -1.0f, 1.0f);
        GL.Vertex3(-1.0f, 1.0f, 1.0f);
        GL.Vertex3(-1.0f, 1.0f, -1.0f);

        GL.Color4(Color4.Pink);
        GL.Vertex3(-1.0f, -1.0f, 1.0f);
        GL.Vertex3(1.0f, -1.0f, 1.0f);
        GL.Vertex3(1.0f, 1.0f, 1.0f);
        GL.Vertex3(-1.0f, 1.0f, 1.0f);

        GL.Color4(Color4.Pink);
        GL.Vertex3(-1.0f, 1.0f, -1.0f);
        GL.Vertex3(-1.0f, 1.0f, 1.0f);
        GL.Vertex3(1.0f, 1.0f, 1.0f);
        GL.Vertex3(1.0f, 1.0f, -1.0f);

        GL.Color4(Color4.Pink);
        GL.Vertex3(1.0f, -1.0f, -1.0f);
        GL.Vertex3(1.0f, 1.0f, -1.0f);
        GL.Vertex3(1.0f, 1.0f, 1.0f);
        GL.Vertex3(1.0f, -1.0f, 1.0f);

        GL.End();
    }
}

}

yantru avatar Dec 22 '20 18:12 yantru

MakeCurrent() must be issued by each control before it performs any OpenGL operations, every time. That's how OpenGL knows which control it should draw on and update: MakeCurrent() is a call that tells OpenGL, "Draw on me now." You should do that call at the start of every Paint and Resize, and at the start of any other event handler that invokes a GL.*() function. Otherwise, you're likely only drawing on whichever control was created first or last.

seanofw avatar Dec 22 '20 18:12 seanofw

I've added updated demos, and also fixed a number of small input-event-related bugs today. @yantru , you should look at the code in the new Multi GLControl demo project, since that demo does what you're trying to do. The GLControl works just fine in multiple instances if it's used correctly.

seanofw avatar Dec 22 '20 21:12 seanofw

Hi seanofw,

I got it working on multiple instances on your form, your previous comment help me better understand. I will get also your updates. It's getting on the proper way now.

Again, thank you for the support you gave me.

yantru avatar Dec 24 '20 04:12 yantru

Hi seanofw,

Do you know of any way to activate multisampling with this solution? Previously, with the GL Control I did the following :

    internal class GLComponent : OpenTK.GLControl
    {
        public GLComponent() : base(new OpenTK.Graphics.GraphicsMode(32, 24, 8, 8))

Also FYI, I remarked that when nested/inherited, the control is not getting its 'DesignTime' property well and make the designer not useable at that time. I know the property 'DesignTime' is a complete messed up from m$ because it's only returning true at the top level. On my side, I finally used a shared static property to tell to internally adds the gl control or not which bypass that problem. Even typical solutions that worked 'before' don't seems to work anymore, maybe it's due to being on the CORE net... https://stackoverflow.com/questions/34664/designmode-with-nested-controls

And finally, one small last question : Is there any reason that you go with : Timer() .... RenderControlX() instead of Timer() ... ControlX.Invalidate() and letting windows event manager do the invoking of paint which will render it?

yantru avatar Dec 28 '20 18:12 yantru

DesignMode is a mess, as you noted, and it doesn't work very well under inheritance or containment: And in .NET Core's version of WinForms, the LicenseManager trick no longer seems to work either, which is why I removed it. I've added the hierarchical test suggested by some of the answers online, along with a few other tests; but as a whole, I'm not sure that it's always a solvable issue. Clone the current code and see if it helps at all for you.

As for your other question, there's a fundamental difference between Render() and Invalidate(): Invalidate() schedules the control for future painting, but it places no guarantees on when the control will get painted: The next paint event could happen one millsecond from now — or one year from now. Invoking Render() directly causes the rendering to occur now, regardless of when Windows next repaints it. If you're running on a timer and it's important that your control get updated now because you're doing some kind of animation, you probably don't want to wait for the control to eventually be validated by the OS and thus repainted.

But it's up to you: Each of those techniques has its use case. Sometimes Invalidate() is the right answer, if it's okay to allow the OS to potentially drop some frames, and if it's okay to allow successive invalidations to potentially get batched together as a single eventual repaint. Sometimes an immediate Render() is the right answer, if your rendering must occur on-schedule to keep up an expected frame rate. I can't tell you which one is always the right answer, because both are, depending on what you're doing.

seanofw avatar Dec 28 '20 20:12 seanofw

Hi seanofw,

Thanks for the explanation of Render() vs Invalidate(), that makes sense. I also found the GLControlSettings parameter and the ability to activate the sampling within it. It's pretty obvious once you see it!

I am now on par with the previous solution!

:)

yantru avatar Dec 28 '20 22:12 yantru

Hi seanow,

With latest pull, there is chance to get an error when looking recursively for parent controls. I prefer to let you know and contribute (even it is small) instead of staying silent ;)

You may want to add a null check around it before using in the DetermineIfIsInDesignMode() method. At least in our solution there is no 'Site' at this place.

if (control.Site != null) { if (control.Site.DesignMode) return true; }

Best regards!

Yanick

yantru avatar Feb 03 '21 14:02 yantru