OpenGL rendering control under managed C++ (WinForms)

June 1, 2012 / / 44 comments


The purpose of this article is to introduce a simple, but complete solution for OpenGL rendering under managed C++. Probably each of you were facing the same situation several times when neither intensive search gave you acceptable results. That was my case few mounts ago when I realized there is nothing like OpenGL control built into the .Net framework. There are only few partial and limited "solutions" out there, demonstrating just the basic principle. The result of my effort is a custom OpenGL Control, which can be easily used through the Visual Studio's Designer.

Main features

  • Fast, UI thread independent rendering
  • Unlimited number of OpenGL control on a single Form
  • Easy manipulation via Visual Studio's Designer
  • Set of standard events available
  • Possibility to easily and precisely limit the framerate
  • Built in actual framerate indicator
  • Asynchronous events management
  • System, Auto and Manual rendering modes
  • AverageFrameRate and RenderedFrameCount available as properties

Binding the OpenGL rendering to the WinForms custom control

There are two mostly used base controls implemented in the .Net Framework called Control and UserControl. Each of those controls has its own purpose. Generally one should use the UserControl when it is necessary to group more controls (buttons, textboxes, ...) into one control. Conversely, the Control should be used when the user needs to draw its content by overriding the paint event and thus creating a simple user control.

Although, the advice recommends inheriting from the Control, I finally voted for the UserControl. From the point of view of our requirements the difference between the two controls is very small. The only reason to use the UserControl is the OnLoad() event which has its important role at starting the rendering thread.

Each user control as buttons, labels, checkboxes and also the UserControl are wrapped to a simple window which is nothing else just a rectangular space on the display. This space has an unique identificator called Handle. Addressing the Handle, we are able to change different properties of the allocated rectangular space as an example: position, size or graphical content. It is obvious we need to somehow redirect the OpenGL output into our window and we will accomplish it using the UserControl's Handle.

As I mentioned before the Handle itself is just the identificator of the allocated window. The real connection between the window and the OpenGL output is achieved with the Device Context and the Rendering Context. We are able to obtain these contexts simply by calling system functions as it is shown in the code bellow. Last thing we need to do is activate the Rendering and Device Context for the actual thread by calling the wglMakeCurrent(hdc, hrc). Note, that the information required for the correct rendering is stored internally by the system. It means after calling the wglMakeCurrent(hdc, hrc) we don't need to address the Rendering or Device Context anymore.

Void OpenGLControl::ActivateOpenGLRendering(IntPtr WindowHandle)
{
   ...

   // handle to the actual user control
   hwnd = (HWND)WindowHandle.ToPointer(); 

   ...

   // get the Device Context associated to the hwnd
   if (!(hdc=GetDC(hwnd)))
   {
      DeactivateOpenGLRendering(WindowHandle);
      ErrorMessage = "The GL Device Context was not created!";
      return;
   }

   ...

   // we need to create the rendering context for the actual UserControl
   if (!(hrc=wglCreateContext(hdc)))
   {
      DeactivateOpenGLRendering(WindowHandle);
      ErrorMessage = "The GL Rendering Context was not created!";
      return;
   }

   // we need to make the Device and also the RenderingContext
   // current for the actual thread
   // this action is done just once, because each OpenGL component
   // has its own rendering thread
   if (!wglMakeCurrent(hdc, hrc))
   {
      DeactivateOpenGLRendering(WindowHandle);
      ErrorMessage = "The GL Rendering Context Can't be Activated!";
      return;
   }

   ...
}

We should not forget, that the UserControl's Handle is used also by the UserControl itself to paint its graphical content and thus we need to avoid painting collisions. This can be achieved easily by overriding the Refresh(), OnPaint() and the OnPaintBackground() methods.

Under special conditions, which are not well documented on the msdn, it is also possible that the Handle is recreated during runtime. If the Handle is changed the OpenGL connection gets useless and we need to recreate the Device and the Rendering Context. This rare situation is managed through the CreateHandle() and DestroyHandle() methods.

Fast rendering on a separate thread

The unwritten rule says we should not do anything on the user interface's thread if it takes more than 50ms. Rendering a frame on a fast GPU probably takes much less, but we can not count with such a precondition. We should also take into account that the user interface needs also some time to accomplish its events like button clicks, timer ticks, repaints etc. One more limitation of the user interface is the maximal number of the Timer's Tick event during a period of time. With the Forms::Timer we can achieve just about 20 frames per second while we can not expect precise timing. It is obvious we need other approach.

So, the question is, how can we achieve smooth rendering without lagging user interface? In my opinion the only solution is to do the rendering on a separate thread. This approach guarantees fast and smooth rendering, but also requires more attention. Rendering on a different thread means we need to avoid cross-thread collisions and somehow trigger the OpenGL Control's events asynchronously on this thread.

Asynchronous event management

One of the hardest tasks about the OpenGL Control was the event management. The first question, which was already answered above, is: Why we need to trigger the events on the rendering thread? The answer is hidden in the next sentence. We are able to activate only one Rendering Context per thread and the Rendering Context can be current to only one thread. Because of this behavior, it is not possible to render or even accomplish an OpenGL function call from a different thread then the one to which the activated Rendering Context belongs. We have no other options, the events have to be fired on the rendering thread.

Void OpenGLControl::ActionThread_Execute(Object^ WindowHandle)
{
   // as the first step we need to activate the OpenGL rendering
   ActivateOpenGLRendering((IntPtr)WindowHandle);

   // if the activation was not successful,
   // then the next actions are pointless
   if (!Activated)
     return;

   // first of all trigger the initialize event
   EnqueueAction(gcnew EventHandler(this, &OpenGLControl::OnOpenGLInit),
                 this, EventArgs::Empty);

   // do until the termination was not requested
   while (!Terminated->WaitOne(0))
   {
      // render if necessary
      if (RenderStyle==ERenderStyle::Auto)
      {
         EnqueueAction(
                gcnew EventHandler(this, &OpenGLControl::OnOpenGLRender),                 this, EventArgs::Empty);
      }

      DoActions();

      // wait for the new events
      if (RenderStyle!=ERenderStyle::Auto)
         WaitLock->WaitOne();
   }

   // at the end of the thread it is necessary
   // to deactivate the OpenGL rendering
   DeactivateOpenGLRendering((IntPtr)WindowHandle);
}

The next question is why and how to do it asynchronously compared to the main UI thread. The synchronous rendering is also possible, but there is a huge drawback with the synchronization. We should take into account the time (lots of time) needed for the synchronization itself, which can lead to serious problems with the UI responsiveness. I found Delegates can be very helpful in this situation. Delegate is a reference to a method which can be dynamically called through its DynamicInvoke(array<Object^>^ args) method. So, all we need to do is store a reference to a method as a delegate, store its parameters and call them later on the rendering thread. This is the way how to port a function call from one thread to another. In the code bellow you can see the OnMouseDown() event which is always called on the main UI thread. Then the function is queued with its arguments into the ActionQueue and later the queued method is called asynchronously through the Delegate->DynamicInvoke(Args) on the rendering thread.

Void OpenGLControl::OnMouseDown(MouseEventArgs^ e)
{
   if (!Activated || DesignMode)
      return;

   EnqueueAction(
      gcnew MouseEventHandler(this, &OpenGLControl::OnOpenGLMouseDown),
      this, e);
}

...

Void OpenGLControl::EnqueueAction(Delegate^ AsyncAction, ... array<Object^>^ Args)
{
   // avoid cross thread collisions
   Monitor::Enter(ActionQueueLock);
   try
   {
      Tuple<Delegate^, array<Object^>^>^ ActionItem =
         Tuple::Create(AsyncAction, Args);
      ActionQueue->Enqueue(ActionItem);
      WaitLock->Set();
   }
   finally
   {
      Monitor::Exit(ActionQueueLock);
   }
}

...

Void OpenGLControl::DoActions()
{
   // import the actions to a local structure
   // (do not block the ActionQueue for a long time)
   Queue<Tuple<Delegate^, array<Object^>^>^>^ TemporaryActionQueue;
   Monitor::Enter(ActionQueueLock);
   try
   {
      TemporaryActionQueue =
         gcnew Queue<Tuple<Delegate^, array<Object^>^>^> (ActionQueue);
      ActionQueue->Clear();
      WaitLock->Reset();
   }
   finally
   {
      Monitor::Exit(ActionQueueLock);
   }

   // perform the events
   Monitor::Enter(DoActionsLock);
   try
   {
      while (!Terminated->WaitOne(0) && TemporaryActionQueue->Count > 0)
      {
         Tuple<Delegate^, array<Object^>^>^ ActionItem =
            TemporaryActionQueue->Dequeue();
         ActionItem->Item1->DynamicInvoke(ActionItem->Item2);
      }
   }
   finally
   {
      Monitor::Exit(DoActionsLock);
   }
}

Framerate limitation

Usually the 60 frames per second is more then enough for smooth animation. Rendering more often can be waste of the available resources. Because of this fact, I implemented another useful feature for the framerate limitation.

First of all, it is obvious we need to somehow measure the rendering time of each frame. My first naive idea was to use the DateTime::Now() and compute the difference by a TimeSpan. It worked "correctly", but a bit later I realized the precision of the time returned by DateTime::Now() is not as good as it is necessary. For this kind of task, when we need to answer the "How long does it takes?" question, it is much better to use a more preciese StopWatch.

Void OpenGLControl::OnOpenGLRender(Object^ obj, EventArgs^ e)
{
   // compute the average framerate
   if (RenderingWatch->IsRunning)
   {
      // do not render more often then the MaxFrameRate allows
      Int64 ElapsedTicks = RenderingWatch->ElapsedTicks;
      if (RenderStyle==ERenderStyle::Auto && ElapsedTicks < TicksPerFrame)
      {
         // if the required time is still larger than 2 milliseconds,
         // then sleep for 1 millisecond
         if (!PreciseTiming &&
             (TicksPerFrame-ElapsedTicks) > TicksPerMilisecond*2)
         {
            Thread::Sleep(1);
         }

         return;
      }

      SaveRenderingTime(ElapsedTicks);
   }

   // restart the time measurement
   RenderingWatch->Restart();

   // the rendering itself
   OpenGLRender(this, e);

   // render also the framerate if necessary
   if (ShowFrameRate)
      OnRenderFrameRate(EventArgs::Empty);

   // swap the buffers to display the rendered image
   SwapBuffers(hdc);
   RenderedFrameCount = RenderedFrameCount + 1;
}

The limitation itself is very simple, do not render if the time reserved for one frame was not passed. It is possible that the check whether to render or not is executed many many times before one frame is rendered. This checks can easily eat up almost the complete processor time of one core. Because of this behaviour it is better to apply some kind of optimization. The Sleep(x) method can be usefull to relieve the processor core from unnecessary processing, but there is a drawback (like usually :) ).

The Sleep(x) method is not very accurate. If we call Sleep(1) it is easily possible that the function will return after 2-3 milliseconds. I was looking for some kind of accurate Sleep(x) method under the millisecond precision, but did not found anything better. So, finally I used this system call to reduce the waste processing. Using the Sleep(x) method has impact only on the framerate limitation's precision. However, if you need precise timing, please set the PrecisieTiming property of the OpenGLControl to true.

How to use the OpenGLControl in the Visual Studio's Designer

  1. Open the Visual Studio's Designer
  2. Right-click to the ToolBox, Choose Toolbox Items window will appear
  3. Select the NET. Framework Components tab
  4. Click to Browse and find the OpenGLControl.dll file
  5. Select the OpenGLControl from the list and click OK
  6. The OpenGLControl is now available in the Toolbox and it is possible to drag and drop it on a form

OpenGL rendering control Demo


Note, that the Visual Studio Designer needs the compiled assembly of the OpenGLControl for proper running. Because of this I suggest the following steps for the linked demo:
  1. Open the OpenGLControl_1.1 solution
  2. Open the Solution Explorer
  3. Right-click on to the OpenGLControl project and choose Build
  4. The OpenGLControl should be built
  5. Now you should be able to open the MainForm in the designer

History

2012/06/23 - 1.0 - First release with all features mentioned in the article
2012/10/17 - 1.1 - AutomaticSwapBuffer property implemented (deny and call the swapbuffer manually)

Downloads


44 comments:

  1. I am trying to compile "OpenGLControl_1.1_Demo.zip" with visual studio 2010 express SP1 i have downloaded Glut library and added it to the project And added OpenGLControl thru step 1-6 as you described.
    I can compile the project and run it so that i can see the project and that is fine.
    But when i try to open "MainForm.h" i only get a error message:


    "To prevent possible data loss before loading the designer, the following errors must be resolved:

    C++ CodeDOM parser error: Line: 188, Column: 19 --- Unknown type 'Zolver.OpenGLControl'. Please make sure that the assembly that contains this type is referenced. If this type is a part of your development project, make sure that the project has been successfully built."


    I have try to google it with a few suggestion to remove intelizens and some other files but that didn't do anything.

    I have no earlier experience of winform but i have a good understanding about Opengl.
    Thx for any help on this matter.

    ReplyDelete
    Replies
    1. Hello Anders,

      I think the problem is in the missing OpenGLControl reference. Please check whether your project's assembly reference list contains the OpenGLControl assembly. You can find the reference list here: [YourProject] -> Properties -> Common Properties -> Framework and References. Hope it helps. In case the problem persists feel free to ask.

      Delete
  2. Hello Zoltan, first of all many thanks for your code.
    I have the same problem as above.

    In the "common properties" there is an item "OpenGLControl" with these parameters:
    Copy Local ... = FALSE
    Reference Assembly Output = TRUE

    Link Library Dependency = TRUE
    Use Library Dependency Inputs = FALSE

    ReplyDelete
    Replies
    1. Hello Carlo, you are welcome.

      Finally, I realized what was wrong. The problem is that the Visual Studio Designer can't find the compiled OpenGLControl in the OpenGLControl_1.1_Source. The designer always needs the compiled version of your custom control. ...my fault. I forgot to mention, it is necessary to compile the OpenGLControl first and then you should be able to open the MainForm in the designer without problem.

      I suggest the following steps:
      - open the OpenGLControl_1.1 solution
      - open the Solution Explorer
      - right-click on to the OpenGLControl project and choose Build
      - the OpenGLControl should be built
      - now you should be able to open the MainForm in the designer

      I will update this information in the article. For further questions, feel free to ask.

      Delete
  3. Hi Zoltan,
    Unfortunately, I did it...

    ... nothing.

    ReplyDelete
    Replies
    1. I tried the steps above on several machines and worked without problem. Anyway, are you able to compile the project and run it? Can you provide more info?

      Delete
  4. Hi Zoltan,
    I have this issue:
    I can not use OpenGlControl.dll in visual studio2012. I compiled the project successfully but the designer reports an error and I can not use drag & drop function. Can You help me?

    ReplyDelete
    Replies
    1. Hi Predator, unfortunately I have never tried the new Visual Studio 2012, so my suggestions are bit limited. Anyway are you able to insert the OpenGL control into the form manually? I mean, inserting the code manually into the "Windows Form Designer generated code" region? Sorry for the late reply.

      Delete
  5. Hi Predator, have you checked the framework version that you compiled Zoltan's control with, and made sure that it matches your hosting application.

    I've managed to get the opengl control in a c# winForms.

    I also had to change the hosting application to target x86 platform (in project settings) to get past a BadImageFormatException as I'm also running on a 64bit OS.

    Cheers J

    ReplyDelete
  6. Hi Zoltan, I can compile the OpenGL Control Demo 1.1 and I can open the Mainform in the designer, however I can't run the program. A window will pop up for about a second then disappears straight away. Would appreciate any help you can give me.

    ReplyDelete
    Replies
    1. Hi Anroth, Do you still experience the same problem? Can you send me an example for debugging?

      Delete
    2. I have this same problem. There is no example to send since it is just the project from the source.

      Delete
    3. This comment has been removed by the author.

      Delete
    4. Hi Anroth,

      I am facing the same problem. Did u find any solution?

      Delete
  7. Hi friend. Thanks for doing this. It very nice.

    and I have a question about how can you import 3D object in OpenGL.

    ReplyDelete
    Replies
    1. You are welcome.

      OpenGL is a low level API, so you are able to create only several basic objects like triangles, cubes, spheres, teapot, ... If you want anything complicated, you will need to implement the loading and building of your objects from scratch.

      You can find several basic frameworks which can help you. This one seems it is what you are looking for: http://sourceforge.net/projects/objloader/

      Delete
  8. Salam, Peace

    brother Zoltan, can U make OpenGL control for .NET 4.0 and 4.5

    and I want UR e-mail Please .

    Thanks,

    ReplyDelete
    Replies
    1. Hello Mohammed, sorry for the late reply. The OpenGLControl is now already in Net 4.0. Porting to Net 4.5 is (I have never tried it) I think just a question of recompilation under Visual Studio 2012.

      Delete
  9. Hi Zoltan;

    I want to open open another windows form from this form.
    I want to create a small game and use windows forms as the menus.

    But when I use:
    Menu1 ^menu1 = gcnew Menu1();
    menu1->Show();

    The form shows but it freezes and just shows the loading icon when my cursor hovers over it.

    Any help?

    ReplyDelete
    Replies
    1. I assume Menu1 in your example is a form. I tried to open another form from a separate Button click event and also from the OpenGLControl click event. Both worked without problem. No freeze observed, the rendering continued as expected. I used the following code.

      Windows::Forms::Form^ menu1 = gcnew Windows::Forms::Form();
      menu1->Show();

      It is also possible, you are using a different setting combination with your OpenGLControl. Can you please post here (or upload somewhere) your code where the problem is reproducible? I'm interested mainly where you call your "ShowMenu" function and how you are setting up the OpenGLControl.

      Delete
  10. This comment has been removed by the author.

    ReplyDelete
  11. Hello Zoltan,

    Just by the name ... are you Hungarian by any chance?

    Anyhow, I can build and run your demo project in Visual Studio 2010. However, I cannot open the MainForm, nor can I place an OpenGL control on a new form. Even though I can Browse to it in Toolbox -> Choose Items ... . There is no DLL in the OpenGLControl folder, only in the !Release folder. If I choose that (or copy the DLL in the OpenGLControl folder and select it there) then the OpenGL control appears in the Toolbox, however it cannot be dropped on a new form. A box with an arrow appears but releasing the mouse cases it to instantly disappear. I also get an error message trying to open the MainForm ("Unknown type Zolver.OpenGLControl"). Weird. Could you help out.

    Thank you so much. Great work, if only I could get it to work.

    Peter

    ReplyDelete
  12. This comment has been removed by the author.

    ReplyDelete
  13. UPDATE: problem solved.

    Hello Zoltan again, sorry about this second message.

    I am not quite sure how, but I resolved the issue. Started a brand new project, set it up to work as a correct plain OpenGL project would. Then copied in the entire OpenGLControl directory + the solution and SQL Server component files that come with it (.sln and .sdf). Added OpenGLControl as a second project to the original solution, built OpenGLControl, and then did the Choose Items .. in the toolbox, browsing to the OpenGLControl.dll. This time it did get added without any error message and the control appeared in the Toolbox. So far, nothing new. But ... this time, lo! and behold, the control could be dragged and dropped onto a new form. And get resized, the works. Not sure what made the difference, maybe the .sln and .sdf files. Just a guess. But the control indeed works, at least to the point of correctly dragging and dropping. I have a strong feeling it will work OK from here on. So ....

    Thank you very much for the great work, and please ignore my previous message. More power to OpenGLControl!

    Peter

    ReplyDelete
    Replies
    1. Hello Peter,

      At first, sorry for the late reply, I'm very busy these days, lots of deadlines coming. I wish I had more time to manage this blog. It feels good to hear my small project is useful. I think the issue reported by you is a bug in Visual Studio. For some reason, under special conditions VS designer has problems to manipulate with the control if it is in the same solution. In that case if you separate the OpenGLControl from the solution where you want to use it, than you should not have problem with it. I think the best you can do is: Create your own project, then compile the OpenGLControl, add the OpenGLControl to the ToolBox by referencing the OpenGLControl.dll and use it in your project. It is not necessary to add the OpenGLControl project to your solution.

      PS: ...and yes you are right, I'm Hungarian living in Bratislava ;-)

      Delete
  14. PerspectiveView_OpenGLMouseWheel event lost when click on any from control

    ReplyDelete
    Replies
    1. This is standard behavior for the WinForms. Only the focused control can receive onWheel events. It is possible to catch the onWheel events with low level hooks.

      Delete
  15. hi zoltan.,
    would you like to share with me about tutorial with all of this?
    i'm still beginner.,.

    thanks.,

    ReplyDelete
  16. Hi,
    how can I use some 3d model instead of teapot,
    for example created as in tutorial http://msdn.microsoft.com/en-us/library/hh315738.aspx

    ReplyDelete
    Replies
    1. Hi Davlet, creating and displaying a custom 3d model is bit more complex than displaying the teapot which is included into the OpenGL framework. Back in the days I was playing with OpenGL I found the Nehe tutorials very helpful. http://nehe.gamedev.net/

      Delete
  17. I have the same problem as Anroth above. When I run the program a window will pop up for about a second then disappears straight away.

    Would appreciate any help you can give me.

    Console output:
    The thread 'Win32 Thread' (0x1b90) has exited with code 0 (0x0).
    The thread 'Win32 Thread' (0x1e5c) has exited with code 1 (0x1).
    The thread 'Win32 Thread' (0x1b00) has exited with code 1 (0x1).
    The thread 'Win32 Thread' (0xae8) has exited with code 1 (0x1).
    The thread 'Win32 Thread' (0x15f8) has exited with code 1 (0x1).
    The thread 'Win32 Thread' (0x1a84) has exited with code 1 (0x1).
    The thread 'Win32 Thread' (0x1158) has exited with code 1 (0x1).
    The thread 'Win32 Thread' (0xf1c) has exited with code 1 (0x1).
    The thread 'Win32 Thread' (0x1150) has exited with code 1 (0x1).
    The thread 'Win32 Thread' (0x144c) has exited with code 1 (0x1).
    The thread 'Win32 Thread' (0xf00) has exited with code 1 (0x1).
    The thread 'Win32 Thread' (0x1508) has exited with code 1 (0x1).
    The thread 'Win32 Thread' (0x5fc) has exited with code 1 (0x1).
    The thread 'Win32 Thread' (0xcc4) has exited with code 1 (0x1).
    The thread 'Win32 Thread' (0x1dc4) has exited with code 1 (0x1).
    The thread 'Win32 Thread' (0x12cc) has exited with code 1 (0x1).
    The thread 'Win32 Thread' (0x1b24) has exited with code 1 (0x1).
    The thread 'Win32 Thread' (0x1918) has exited with code 1 (0x1).
    The thread 'Win32 Thread' (0x1334) has exited with code 1 (0x1).
    The program '[5452] OpenGLControlDemo.exe: Managed (v4.0.30319)' has exited with code 1 (0x1).
    The program '[5452] OpenGLControlDemo.exe: Native' has exited with code 1 (0x1).

    ReplyDelete
  18. I noticed that this problem occurs only when application executes function drawSolidTeapot() .
    When i comment that function program starts but only background is displaying.
    I tried also with glutSolidCube() but also did not work, so problem occurs always while trying to execute function from glut library. Do you have any suggestions?

    ReplyDelete
    Replies
    1. Hi Kamil,

      I am facing the same problem. Did u find any solution ?

      Delete
  19. Nope.I quit using WinForms and just started using OpenGL rendering under QT.

    ReplyDelete
  20. Hi,

    This blog is a little old but it was very useful for my project.
    I have one problem. I need to do texture mapping but I have some problems.

    I tried many different methods which work fine in native C++ (MFC) including SOIL. However any of them doesn't work in C++/CLI.

    Do you have any suggestion?

    Thanks!

    ReplyDelete
  21. Hi Zoltan,

    This is exactly what I need! (nice work, looks really neat) but I cannot get it to run from Visual Studio Express 2013 C++/CLI :(. No errors, form appears briefly and then disappears. I cannot open MainForm.h Design. Error there is Unknown type Zolver.OpenGLControl. Same as others. Tried openGLControl.dll on other project, shows in toolbar but will not drop on form.
    Thank you in advance!

    ReplyDelete
  22. Hi Zoltan,I am pretty new on opengl.
    How can I draw simple line inside this controller.can u show an exaple .thank you

    ReplyDelete
  23. I can't add the OpenGLControl.dll file to the toolbox, it keeps telling me that the file does not exist.

    ReplyDelete
  24. Hi. Your tutorial is very interesting! I need to do the exact same thing that you are describing here so it is really useful. The problem is that the download links you included at the end of the article seems to be down... Is there any chance you can reupload them?

    Thanks in advance

    ReplyDelete
  25. Great tutorial ! Could you please upload your 1.1 Source Code again ? It would be very helpful ! :)
    Thank you very much

    ReplyDelete
  26. This comment has been removed by the author.

    ReplyDelete
  27. Hi Zoltan, this is a so good tutorial.
    And is it possible to reupload your sample code again?
    Thanks~~~

    ReplyDelete
  28. Your site is no longer available (zolverblog.com)!, where can I download the code? (OpenGLControl_1.1_Source.zip, OpenGLControl_1.1_Demo.zip)

    ReplyDelete
  29. Hi Zoltan, is good example, but your download link is missing,can reupload your link? Thanks a lot~

    ReplyDelete