kanato's picture

OpenTK Mac OS X support.

Well, I thought I would let you know I've been working on writing support for Mac OS X for OpenTK. I've successfully wrapped window creation and event processing through the Carbon API. I need to work on keyboard and mouse event processions, then AGL wrapping, then I think I'll be to the point where I can integrate with OpenTK. Hopefully I can get there by early next week.


Comments

Comment viewing options

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

Awesome news! Let me know if you need help with anything.

kanato's picture

What's the rule on converting GL* types to c# types? I'm guessing GLint -> int is ok, but is GLboolean -> bool ok? Or do I need to use an int type?

Edit: nevermind, I see it's defined as unsigned char in the headers.

Do you have any tips on Marshalling? I think I understand that I need to pin arrays to a fixed pointer to marshal them, but I don't know if there are any other gotchas.

the Fiddler's picture

As a rule of thumb, just write a DllImport with the correct signature and let the runtime handle the details for you (pinning etc), unless it is performance-critical code.

For example:

// C function
#define bool char
typedef struct { ... } SomeStruct;
bool Foo(SomeStruct *data);
 
// C# - whichever of the following makes the most sense
[DllImport("library")]
bool Foo(SomeStruct[] data);
 
// or, if SomeStruct is a struct (i.e. value object):
[DllImport("library")]
bool Foo(ref SomeStruct data);
 
// or, if SomeStruct is a class (i.e. reference object):
[DllImport("library")]
bool Foo(SomeStruct data);

This should cover just about 90% of all p/invoke cases.

Void pointers can be turned into IntPtrs, and char arrays should be made into strings (in parameters) or StringBuilders (out parameters). Generally, the biggest issue is converting structs with unions or embedded arrays/strings - just make a post if anything strange shows up ;)

Another complication has to do with 64bit datatypes: do ints change bitness from 32- to 64bit compilers (i.e. 32bit on 32bit compilers and 64bit on 64bit compilers)? If so, int parameters should be defined as IntPtrs, which is a bit ugly (but necessary).

kanato's picture

Ok, here is a patch which adds preliminary MacOS X support: http://agate.sourceforge.net/download/macos.patch
This patch should be considered preliminary, and not for production use. Keyboard support is incomplete, and there are some exceptions when windows are closed, and I haven't gotten GLControl to work right yet. So I don't recommend commiting this to SVN right now.

I put everything in a MacOS folder and namespace, instead of the OSX folder / namespace. The reasoning for this is that since CarbonLib predates OSX, it should be possible, with a bit of work, to add support for Mac OS 9. I don't have a Mac which can run OS 9 however, and there is no official support from Mono for Mac OS prior to 10.3 or so. So that may not ever be a reality. Plus I think MacOS is easier to read than OSX. But if you don't like it, I can change everything to using OSX instead.

I've wrapped Agl context creation, Carbon window creation, mouse and keyboard events. Several of the examples work (Simple Window, Immediate mode, Display Lists, Fonts, Text, Texture Mapping). There are some artifacts with the text, just above and below the glyphs. I think this might be related to artifacts mentioned in other threads on the forum.

Apparently my MacMini only supports OpenGL 1.2, so VBO and GLSL don't work. The GLU Tesselate example doesn't work, and I'm not sure why. Other GLU functions like Glu.LookAt work, and extension loading for GLU seems to work.

Things that need to be resolved with GameWindow:

  • Window closing. I'm not sure how to integrate this into OpenTK. When I close the window manually, I get a "Cannot enter game loop without a render window" exception from GameWindow.OnUpdateFrameInternal. If I don't, then the window doesn't close when the close box is clicked.
  • Resizing events. I have them wired up, and I see "Firing internal resize event" in the console, but the actual rendering does not change area. I don't know if this problem is because I'm using AGL incorrectly, or I'm interfacing with OpenTK incorrectly. I think the problem is GL.Viewport is not called, but I am unsure where is an appropriate place to put a call to it.

A couple of other thoughts:

Have you thought of abstracting all the platform stuff to some sort of class factory? Rather than have lines like "if (Configuration.RunningOnOSX)" all over the place, just have a static class in Platform, which a hidden implementation object that each platform implements, and it just creates whatever objects are necessary. If you are interested, I can code this up, it won't take long.

I noticed when the GameWindow is closed, the run loop is terminated by throwing an exception. Is this really the best way to do it? I would think setting a flag to terminate the loop would be better. I also noticed if you aren't using GameWindow.Run but a custom run loop, then this exception is difficult to deal with, because the exception itself is not public, so you have to catch a base exception and either deal with, swallow, or rethrow exceptions that are of a different type.

Well, let me know what you think. If there are coding style / naming issues, let me know and I will fix them.

kanato's picture

Oh, one other thing I wanted to ask. Is there support for the type of event handling where the user clicks the close box, and the program asks if they want to save before quitting (or something like that). I already have support for this sort of thing written into the carbon wrapper, so that it can cancel a window close event if the program / user input requires it.

objarni's picture

Fantastic work kanato!

kanato's picture

I just ran the GLSL example, and it gives an error message about requiring OpenGL 2.0 and says it will abort, but then it runs anyway. Looking at the extensions supported by my card, I see things like:
gl_apple_vertex_program_evaluators
gl_arb_fragment_program
gl_arb_fragment_shader
gl_arb_shader_objects
gl_arb_shading_language_100
gl_arb_vertex_program
gl_arb_vertex_shader
gl_version_1_1
gl_version_1_2

but no other gl_versions are listed.

And my the OpenGL driver info listed is
Intel GMA 950 OpenGL Engine, Intel Inc., 1.2 APPLE-1.4.56

So shaders are supported, even though I don't have higher than OpenGL 1.2 ?

kanato's picture

Ok, just one more thing for today, then I will wait for responses :P

I have mapped the OpenAL dlls to their OSX equivalents, but I know nothing about OpenAL. Are there any example programs which use OpenTK with OpenAL that I can test out? If I run the Example launcher and run AudioContext Test four times in a row, this is the output I see:

Launching example: Examples.TestApp
Enumerating audio devices.
 
Testing AudioContext functions.
    AudioContext.CurrentContext...
AttributeSize: 12
4103 0 25189928 0 0 0 0 0 0 0 0 0 
    Launching example: Examples.TestApp
    Testing AudioContext functions.
        AudioContext.CurrentContext...
AttributeSize: 9
4103 0 40 0 0 0 0 0 0 
        Launching example: Examples.TestApp
        Testing AudioContext functions.
            AudioContext.CurrentContext...
AttributeSize: 3
4103 0 0 
            Launching example: Examples.TestApp
            Testing AudioContext functions.
                AudioContext.CurrentContext...
AttributeSize: 3
4103 0 0

I don't know if that's good or not. And it's strange that the output changes after the first two runs.

the Fiddler's picture

Great work! I won't have time to review the code for another 24 hours or so, but I can respond to your questions :)

[MacOS vs OSX]
No problem, MacOS looks better too.

[Font artifacts]
This has to do with sampling/texture coordinates, it happens on some cards/drivers but not on others. I'll loosen the packing by 1 pixel - this should take care of these artifacts.

[GLU tesselate]
This looks like a Mono problem (doesn't work on Linux either), but I haven't bothered to investigate yet. I'm looking for a managed alternative - if one exists, I'll be happy to see GLU go away.

[Window close event]
The implementation is a bit hairy. There are three ways to close the window:

  1. User calls GameWindow.Exit. This terminates the main loop. Then the Unload event is fired, the context is disposed and INativeGLWindow.DestroyWindow() is called. Finally, all pending events are processed until GameWindow.Exists becomes false.
  2. User clicks the "Close" button. INativeGLWindow.ProcessEvents detects this and raises the INativeGLWindow.Destroy event, which is handled by GameWindow.glWindow_Destroy. GameWindow.glWindow_Destroy valls GameWindow.Exit and the rest work out as above.
  3. User calls GameWindow.Dispose. This calls GraphicsContext.Dispose and INativeGLWindow.Dispose, but I haven't worked out the details yet (presumably it should call GameWindow.Exit instead).

[Window resize event]
GL.Viewport generally belongs to a GameWindow.Resize event handler. As long as OnResizeInternal is called, the resize event will be raised.

(The On*Internal methods will potentially perform some bookkeeping before raising the actual events. They also make sure that things will work correctly if the user overrides the On* methods - e.g. OnUpdate - and forgets to call base.OnUpdate).

[Class factory for platforms]
I was planning to do this, but I didn't think a MacOS port would become reality so soon. :)

I think you have such a system in place for AgateLib? I'd be very interested in such an implementation.

[GameWindowExitException]
It is the only way (I know of) to stop the main loop asynchronously. I.e:

override void OnLoad(...)
{
    if opengl version < 2.0
        Exit();
    use opengl 2.0
}

Exit() must stop control before it reaches the "use opengl 2.0" line (otherwise it will crash!). To the best of my knowledge, an exception is the only method to achieve this.

I added this exception very reluctantly, but I really cannot see a better way.

If you have a custom main loop, the solution is simple: don't call Exit() (Exit() is specific to the Run() method - should document this better). Since you have control over the main loop, you are free to use a flag (or any other method you can think of).

[Exit event with cancel support]
I'm planning to add this, so this code is useful.

[GLSL]
Intel GMA 950 OpenGL Engine, Intel Inc., 1.2 APPLE-1.4.56
OpenTK reports the version from GL.GetString(): "1.2 APPLE-1.4.56" - I don't know if there's a better way.

I'll update the app to test for GLSL support explicitly, instead of relying on gl_version_2_0 being available.

[OpenAL]
This doesn't look very good. The attributes look bogus - they should contain things like frequency (44100) etc. At least it doesn't crash :)

I'm still working on AudioContext. Should have a couple of examples ready later today, that test audio playback and audio effects.

kanato's picture

Ok, I figured out the closing and resize events issues. In AGL, when a window is resized, aglUpdateContext needs to be called on the context. It looks like inorder to do that, either IGraphicsContext or IGraphicsContextInternal needs to have an Update method. The only other way I see to do it would be to expose the implementation of GraphicsContext, at least internally.

I guess I was a bit thrown off with the close event integration because if you're running your own event loop there is no event that is raised when the native window is closed. But that can be resolved easily by making GameWindow.ExitInternal call an event. I'll submit a patch for that later.