Mincus's picture

OpenTK GameWindow and multi-threading.

Using a GameWindow I tried to run another thread loading textures in the background whilst the main thread processed drawing to screen and discovered this won't work due to each thread needing its own OpenGL context (at least that's my understanding of the problem).

Just wondering if there's an easy way to set up a second thread that will share the context or am I better off using a GLControl maybe?


Comments

Comment viewing options

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

Context sharing is enabled by default in SVN (you can use GraphicsContext.ShareContexts to control this).

Simply creating a GLControl in the second thread should work.

Mincus's picture

Ok, thanks. That works to a degree, however I thought the point of sharing contexts was to allow things like textures to be shared between, for instance, two windows without having to load the texture seperately for both. But when I load a texture on the GLControl, the texture handles are starting back at 1, so when I try and use this texture from the original context, which loads its own textures, I get one of the textures from the original context instead of the newly loaded one.

For example, the original context loads 3 textures, which are given the handles 1, 2 and 3.
When the new thread and context attempts to load another texture, it is also given the handle 1 instead of 4.

The contexts are definitely being shared, looking through the output:
Creating render context... done! (id: 65537)
Sharing state with context OpenTK.Graphics.GraphicsContext

shows up.

Guessing I'm doing something fundamentally wrong here. :o/

Hangar's picture

I've also been wondering about the issue of threading, but would like to add a third thread to the design, making it so that window messages aren't tied to the framerate. These are all just ideas; I'm just trying to see where you want to go with the engine. My own attempts at such detail have all failed to produce results, and I mostly did them as an academic exercise.

My thought is that a separate thread should handle window messages immediately, such as requests to go to fullscreen mode or closing the program. Things like mouse and keyboard events would be queued for synchronous processing by a main thread, which would also do the updates and rendering synchronously. When the main thread requests a texture, it would be remembered and queued for loading, using a default, perhaps invisible texture until properly loaded, or maybe there would be some sort of progress measurement going on that would tell it what percentage of textures were currently loaded and when all textures had finished loading. The loading would be in a third thread, based on a queue, and built around a lock that ensured that it would have exclusive access to a context.

In the past, working with wgl, and referring to agl and glx to see if it was compatible, I've fiddled around with recreating contexts and new windows, using switches and caches to tell me when I needed to reset all settings or when I should reload the display lists and textures.

But I was wondering if you would provide low-level cross-platform support for this kind of control, or how much of this sort of design you'd directly implement in the library. For instance, a general-purpose texture cache would be incredibly useful for most games, and the same could be said of audio and font caching.

All that's missing from the input model I described is a ProcessEvents() function that doesn't return except when indicated to do so, being told to quit by some control in another thread. It would also help to get access to a few standard events, such as loss of focus so the program can pause rendering and maybe updates if it's a singleplayer game, but also paint messages so that if out of focus, it can still redraw when the OS needs it to.

The client might want quit messages may want to hide the window immediately then let the main thread finish the current update/render before destroying the window and context, or they may want to provide a dialog to the user, asking if the user wants to quit. This could take the form of a modal dialog in the input thread, a non-modal dialog in its own thread, or an ingame-dialog rendered with OpenGL and tied to the main loop.

The most useful bit of this is the asynchronous texture loading/cache. Can you use textures while loading them in a shared context held in a different thread? Do internal locks make this inefficient? What happens if you're rendering using a texture while loading into it? Also, an improvement could be to automatically prioritize heavily-referenced textures, or maybe those that are actually used a lot.

the Fiddler's picture

For example, the original context loads 3 textures, which are given the handles 1, 2 and 3.
When the new thread and context attempts to load another texture, it is also given the handle 1 instead of 4.

One thing to check is whether both contexts have the same pixel format. The code should (but does not currently) report whether the sharing succeeds, so it can silently fail when the pixel formats are different:

Source/OpenTK/Platform/Windows/WinGLContext.cs:

    86             if (sharedContext != null)
   87             {
   88                 Debug.Print("Sharing state with context {0}", sharedContext.ToString());
   89                 Wgl.Imports.ShareLists(renderContext, (sharedContext as IGraphicsContextInternal).Context);
   90             }

Also, can you try calling System.Runtime.InteropServices.Marshal.GetLastWin32Error right after creating the GLControl? It might report something useful.

Update:
[GameWindow Threading]
On the issue of threading, I am planning to move event handling to it's own thread (with UpdateFrame and RenderFrame on a different thread). This will improve GameWindow behavior on high loads, as well as performance in multi-core systems.

I'm also debating whether to implement the ability to run UpdateFrame and RenderFrame on different threads. This would be an explicit option set by the user (i.e. GameWindow.RunAsync instead of GameWindow.Run), because it would make things a lot more complicated (e.g. no OpenGL calls in UpdateFrame) - but I'm not entirely sure if this is worth it.

Regarding higher-level functionality, like asynchronous resource loaders, these lie outside the scope of OpenTK. It's due to a lack of resources - I simply cannot commit to designing, implementing and supporting such a module at this point. :) It would certainly be valuable; I wouldn't object to adding this functionality into OpenTK.Utilities, but unless someone actually implements it (and commits to supporting it), this probably won't happen.

Also, I'd think that the actual bottleneck in resource loading is the disk - not the upload to video memory. In this case, you might not even need a different context: just fire the loader on a BackgroundWorker thread, and add the OpenGL uploading code in the RunWorkerCompleted event (which occurs in the original thread). This will likely be faster than a dedicated resource loading thread with its own context.

[GameWindow events]
I'd like to keep GameWindow as simple as possible, while still getting the necessary work done. For example, instead of focus lost events, I'd prefer to automatically suspend RenderFrame events (not UpdateFrame). Single RenderFrame would be fired on OS repaint requests.

GameWindow can already be exited asynchronously (ExitAsync - thread safe). However, you cannot intercept the "close" button yet; I'm planning to add this functionality in the future.

One more thing missing from GameWindow is a WindowState property, which would indicate whether the window is in a minimized, maximized, fulscreen or normal state.

The last missing bit, is a pair of GraphicsContext.Destroyed/Created events, which could be used to reload resources as needed. I don't know how useful that would be in practice, as the context is usually destroyed as the result of user action (e.g. change the display mode or switch to fullscreen mode). What do you think?

Mincus's picture

Both are reporting they're setting Pixel Format as 8.
Graphics Mode is reported as GraphicsMode: Index: 8, Color: 32 (8888), Depth: 24, Stencil: False, Samples: 0, Accum: 64 (16161616), Buffers: 2, Stereo: False on both, so that's clearly the same.

System.Runtime.InteropServices.Marshal.GetLastWin32Error() returns 127, which a quick google shows as The specified procedure could not be found.

Quick note on bottlenecks: Whilst looking for where the pause itself was in this (to see if I could load the image into memory in a second thread then quickly shunt it to OpenGL in the main one) I found that the bottleneck was in the call to Glu.Build2DMipmap(). I was still calling LockBits(), so I assume the bitmap was in memory. Thought it worth mentioning anyway.

Hangar's picture

Yeah, it seems like you've covered everything regarding events. Handling window messages in a separate thread is much cleaner than letting the user deal with that sort of thing manually. I don't know that I'd personally need a GraphicsContext Destroyed/Created event, just one that indicates when a context was created but wasn't able to share a previous context. But the more general events may be useful for something.

And wow, you're right about the BackgroundWorker idea. That's a much better solution.

the Fiddler's picture

Quick note on bottlenecks: Whilst looking for where the pause itself was in this (to see if I could load the image into memory in a second thread then quickly shunt it to OpenGL in the main one) I found that the bottleneck was in the call to Glu.Build2DMipmap().

The cleanest (and most "professional") solution is to generate the mipmaps beforehand and compress everything into a DDS texture. Not only will this be faster to load, but using compressed textures will bring a significant improvement in runtime performance. (0.9.1 will ship with a DDS loader, so this approach will be dead-easy to use.)

A second approach would be to use the SGIS generate mipmap extension (http://www.opengl.org/registry/specs/SGIS/generate_mipmap.txt), which might be faster than GLU.

Mincus's picture

I'm loading what you would typically identify as "user-generated" content in this instance.
The idea was to load background images and resize/etc using OpenGL and save things out again. So mostly jpegs to load.
As such I'm trying to load large (1024x768, even a few 1600x1200/1680x1050) images, hence the slow down.
I don't think OpenTK is right for the job though, moving the project into C++ using FreeImage for the manipulation.

But I'm still interested in loading things through a second thread as it's a useful bit of code to have, and one of the possible applications for this would be a level pack browser (or even just a base level browser) where you don't really want to cache an entire directory of level pack "preview" images beforehand but will want to load them on the fly instead.

However, the point on DDS is taken and I'll look into using that on the other things I'm working on. :o)

the Fiddler's picture

You could also manipulate images with GDI+ (System.Drawing.Graphics), if you want to keep using .Net. You'll probably be able achieve better quality with a more featured software solution (FreeImage, GDI+) compared to GLU, when resizing images (AFAIK GLU just applies a simple box filter in software - not really good). If the resizing/filtering is the actual bottleneck, you might be able to speed it up by performing it with pixel shaders - but that's rather more complicated.

renanyoy's picture

if there a way to do multithreading and background loading rendering with openTK, as it's basic needed feature in all projects now ??