antonijn's picture

Pencil.Gaming - The OpenTK Alternative

So I, like many others, am in a love-hate relationship with OpenTK. I love its OpenGL support, but am fed up with three things:

  • The windowing implementation
  • The necessity to install the OpenAL redistributable package
  • A more temporal issue: the lack of updates recently

I believe that the second two points are relatively straightforward, but the first bullet deserves an explanation.
it
The OpenTK windowing API has been written entirely independent of any other windowing APIs, like FreeGLUT and GLFW. This means that it is really buggy in some places, especially on non-windows platforms, and the users of the library should not have to fix these things themselves.

That is why I decided to write my own library, which relies on a well-established native windowing library: GLFW, providing way more stability than OpenTK now does (full rationale can be found here).

It's by no means designed to be backwards-compatible with OpenTK (although it won't require a lot of work to change your application). A few notable differences:

  • It's designed to be a library used only for games
  • Half-precision floating point numbers, and their implementations in Vector2h etc., have been removed
  • Double precision matices and vectors have been removed
  • There's no need for ANY redistributable packages
  • It provides some utility classes for GLFW and OpenAL: Glfw.Utils and Al.Utils respectively

Features that are still there:

  • It's 100% cross-platform
  • It provides support for OpenGL and OpenAL
  • It has strongly typed enums
  • It has the MIT license

The most notable difference is the GLFW implementation. Have a look at the tutorials on their site to find out more about it.

The github repository, including the source code, the binaries, a list of compatible platforms and a sample program, can be found here.

This is the sample program provided by the github repo, so you don't have to click the link to see it...

using Pencil.Gaming;
using Pencil.Gaming.Audio;
using Pencil.Gaming.Graphics;
using Pencil.Gaming.MathUtils;
 
class Program {
    private static void Main(string[] args) {
        try {
            Glfw.Init();
 
            try {
                Glfw.OpenWindow(800, 600, 8, 8, 8, 8, 24, 0, WindowMode.Window);
                Glfw.SetWindowTitle("Sample application");
                Glfw.SwapInterval(false);
 
                Glfw.SetWindowSizeCallback((int width, int height) => {
                    GL.Viewport(0, 0, width, height);
 
                    GL.MatrixMode(MatrixMode.Projection);
                    GL.LoadIdentity();
                    GL.Ortho(0.0, 1.0, 1.0, 0.0, 0.0, 1.0);
                    GL.MatrixMode(MatrixMode.Modelview);
                });
 
                Glfw.SetTime(0.0);
                do {
                    float deltaTime = (float) Glfw.GetTime();
                    Glfw.SetTime(0.0);
 
                    GL.ClearColor(Color4.White);
                    GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
 
                    GL.Begin(BeginMode.Triangles);
                      GL.Color4(Color4.Red);
                      GL.Vertex2(0.1f, 0.1f);
                      GL.Color4(Color4.Green);
                      GL.Vertex2(0.1f, 0.9f);
                      GL.Color4(Color4.Blue);
                      GL.Vertex2(0.9f, 0.9f);
                    GL.End();
 
                    Glfw.SwapBuffers();
                    Glfw.PollEvents();
                } while (Glfw.GetWindowParam(WindowParam.Opened) == 1);
            } finally {
                Glfw.CloseWindow();
            }
        } finally {
            Glfw.Terminate();
        }
    }
}

NOTE: This example is for GLFW2, make sure to compile it as such

And here is another example (once again from the github repo), this time OpenAL, showing one of the nifty utilities.

uint buffer = AL.Utils.BufferFromWav("MyWaveFile.wav");
uint source;
AL.GenSources(1, out source);
 
AL.Source(source, ALSourcei.Buffer, (int) buffer);
AL.Source(source, ALSourceb.Looping, true);
 
AL.SourcePlay(source);
 
// ...
// ...
 
// When cleaning up:
AL.DeleteSources(1, ref source);
AL.DeleteBuffers(1, ref buffer);

Feedback and contributions to the git repo would be much appreciated!


Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
Inertia's picture

I don't mind if you advertise your project, but to be fair: what is your professional situation and are you engaged? No offense intended, these are valid questions if you try to get users off OpenTK to your project, because these issues changed OpenTKs development pace, and noone wants to trade "bad" for "worse". (No offense, but your project is unlikely perfect and you need to give the users an estimate of it's longevity)

migueltk's picture

Interestingly, follow the progress of your project.

I hope to see a more complex example. Your reasoning is logical, separate codebase complexity related to the application window and work up in C# code.
I think it's early to choose between "managed code OpenTK window" and "Pencil-GLFW window unmanaged code", I suppose ideally have the same application running on the two alternatives and compare.

Good job ...

regards

antonijn's picture

@Inertia First of all, thank you for your feedback! It's very much appreciated, especially this early on in development.

To answer your question(s), my professional situation at the moment is school, which leaves me with a fair amount of free time to work on the project. Also, because it's pretty much only a wrapper for unmanaged code, I only really need to add functionality when one of those libraries is updated. Another benefit of using other people's libraries is that they can worry about fixing bugs, whereas you only have to address some minor issues there might be in some of the utility classes/some of the methods not being marshalled correctly.

About the longevity, I feel as though I'm personally definitely going to keep using it, and will therefore update it whenever an update to the unmanaged libraries is released. I don't see myself abandoning this project for quite a while, which is, of course, a pretty vague timescale. That's partially because I can't know for sure. If this library won't be used by anyone, then it might die out one day (if I start using another library for example), but if a reasonable (which I would consider to be at least ten) amount of people keep using the project, I'll definitely try to keep working on it.

antonijn's picture

@migueltk I think I kind of poorly worded my first point. I don't hate OpenTK's object-oriented windowing implementation. My problem with it is, that it's not solid. It tries to implement a high-level managed windowing system built on low-level native platform-dependent libraries, making it more buggy than an unmanaged library that is already well-established.

Thanks for your feedback anyway!

AndyKorth's picture

Hi antonjin!

Nice! In my personal project, I actually dropped OpenTK in favor of GLFW. (I didn't really mention that here) I got a C# binding working in less than a day and reimplemented/moved my project very quickly. On the Mac side, GLFW doesn't use any of the Carbon API, (which is now finally officially deprecated after years of being on the out).

(I also decided to opt for just using just the OpenGL ES 2.0 spec for my project, which takes out a lot of cruft, like all the immediate mode rendering stuff)

GLFW is also absolutely tiny compared to OpenTK- if you're looking to use it in your project, it's a single header to write DLLImport calls for.

I have spent a little time with the OpenTK input libraries, trying to fix input issues, especially with the Mac port. At that time I also poked around with the windowing code OpenTK had. Rather than fixing the OpenTK code or replacing it with Cocoa API calls instead of Carbon API calls, it would be a lot easier to replace the non-working OpenTK code by just wrapping GLFW (at least for the Mac). But I figured that might not be well received. Plus I'm far from an expert on OpenTK or carbon code. (I didn't start Mac programming until like 2004 or so, so I missed the whole carbon thing).

@migueltk: Regarding more complex examples.. One plus is the simplicity of the library.. But there are a few things OP didn't touch on:

Detecting main display, setting window size, opening a window. Resizing the window. (Call gl_viewport with that viewSize, and you've got a resizable window):
http://kortham.net/temp/upshot_hs7yHU8L.png

Handling mouse event callbacks (it was surprisingly easy to work with C callbacks with C# native function interface!)
http://kortham.net/temp/upshot_T86L3e50.png

Keyboard is pretty trivial.... and that's all I've needed so far.

antonijn's picture

@AndyKorth Nice! However, I would like to clarify some things about the code samples you just provided, because that won't work in my library.

In Pencil.Gaming there is no need for unsafe code, everything is done with ref/out parameters, so the example you provided:

GLFW.GLFWvidmode mode = (new GLFW.GLFWvidmode());
unsafe {
    GLFW.glfwGetDesktopMode(&mode);
}

Would become this in Pencil.Gaming:

GlfwVidMode mode;
Glfw.GetDesktopMode(out mode);

And likewise with the input system:

int x, y;
unsafe {
    GLFW.glfwGetMousePos(&x, &y);
}

Would become:

int x, y;
Glfw.GetMousePos(out x, out y);

Also, I haven't implemented the GLFW callback system yet, but I'll probably do that this afternoon.

AndyKorth's picture

Cool antonijin! Yeah, I haven't spent much time with it, so I haven't actually wrapped any calls or anyhting. I also am completely new to making a native binding, so your suggestions will be very helpful.

Here's what I have been using: (maybe it'll save you a bit of time on your callbacks)
https://gist.github.com/andykorth/5100871

antonijn's picture

@AndyKorth Well, I could make a couple of suggestions.

Firstly, your code will only work on system of a specfific bit-ness (either 32-bit or 64-bit; mono for OS X is always 32-bit), so what I've done, is make two intermediate classes (Glfw32 and Glfw64, which import "natives32/glfw.dll" and "natives64/glfw.dll" respectively), then I have a class containing the methods that are appropriate for the specific system (GlfwDelegates), and those functions are called by the class the developer would normally use: Glfw.

You could have a look at the glfw classes in my github repository if you want.

Also, you should probably assume windows dlls, and add a .config file (here's mine) to your application, to map the dlls to a specific location.

You can also leave out most pointers in function calls (unless the function returns a pointer, in which case it's more complicated), with ref and out parameters. They will be marshalled to a pointer, making you have to worry about one less thing. If the pointer is used to represent an array (as is the case in glfwGetVideoModes), you could add a MarshalAs attribute to it, which will also convert it to a pointer automatically. Here's an example where this is used in Pencil.Gaming (Glfw64.cs):

[DllImport(Glfw64.lib)]
internal static extern int glfwGetVideoModes([MarshalAs(UnmanagedType.LPArray)] GlfwVidMode[] list, int maxcount);

You can do the same thing with strings:

 [DllImport(Glfw64.lib)]
internal static extern void glfwSetWindowTitle([MarshalAs(UnmanagedType.LPStr)] string title);

I hope this helped. Good luck with your project!

AndyKorth's picture

Thanks for the fantastic information! I'll move this conversation over to your github page to avoid cluttering the OpenTk forums.

Mailaender's picture

How did you remove the necessity to install OpenAL?

PS: I just saw that you simply added the native DLLs to the source code management system and bundle them with your releases. Probably a suitable workaround for Operating Systems without proper package management, but not what I was looking for.