objarni's picture

My little using()-trick to ease the burden on the programmer..

My GL-drawing code got completely scrambled today because I forgot to put an GL.End() after my GL.Vertex2f-code.

So, I thought, can't I do something to relieve me of the burden of matching GL.Begin-End pairs all the time..?

This is what I came up with, thought I'd share it for anyone else to use in case you also forget those evil GL.End()-lines every now and then!

Update: After Kamujins complaint about allocating objects every frame, I changed the class to this which allocates only one object per application run:

     class GLDraw : IDisposable
    {
      private GLDraw() { }
      private static GLDraw drawer = new GLDraw();
      public static GLDraw Begin(BeginMode mode)
      {
        GL.Begin(mode);
        return drawer;
      }
      public void Dispose()
      {
        GL.End();
      }
    }
 
    ....
    using (GLDraw.Begin(BeginMode.Quads))
    {
      GL.Vertex2(0, 0);
      GL.Vertex2(size, 0);
      GL.Vertex2(size, size);
      GL.Vertex2(0, size);
    }

Happy coding,


Comments

Comment viewing options

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

Not a bad trick, but the "new GLDraw(...)" allocation will abuse the heap and could negatively impact performance.

objarni's picture

I'm not so worried about the heap when using C# ... If it was c/c++ I would think twice using it extensively in my inner loops.

.NET is highly optimized for extensive allocation-deallocation, lots of resources and research has been put into the garbage colector.

Kamujin's picture

The .NET allocator is optimized for frequent allocations, but they still need to be GC'd. Luckily, these will be gen0 collections.

The allocator returns a block of memory to each allocating thread from which to satisfy future requests. In a vacuum this technique doesn't pull enough bytes to overwhelm it, but as a general technique, you will eventually bottleneck on the heap. Since other aspects of managed code require heap allocations, its best not to assume the GC optimizations will always be maximally effective.

objarni's picture

OK I think I've fixed your complaint Kamujin ;)

Only one allocation done for the entire run of the app:

     class GLDraw : IDisposable
    {
      private GLDraw() { }
      private static GLDraw drawer = new GLDraw();
      public static GLDraw Begin(BeginMode mode)
      {
        GL.Begin(mode);
        return drawer;
      }
      public void Dispose()
      {
        GL.End();
      }
    }
 
    ....
    using (GLDraw.Begin(BeginMode.Quads))
    {
      GL.Vertex2(0, 0);
      GL.Vertex2(size, 0);
      GL.Vertex2(size, size);
      GL.Vertex2(0, size);
    }
Kamujin's picture

I was going to suggest the following as the allocation would be on the stack...

    struct GLDraw : IDisposable
    {
      public GLDraw(BeginMode mode)
      {
        GL.Begin(mode);
      }
      public void Dispose()
      {
        GL.End();
      }
    }

...but I think that implementing the interface causes it to box right?

the Fiddler's picture

Yep, it will be boxed so it doesn't really help. This trick is great, but isn't really suited to code that runs per frame. It works better with vertex buffers (to ensure Map/UnmapBuffer or BufferData is called), since you usually upload data once but draw many times.

objarni's picture

Does my version 2, using a statically allocated object, work Fiddler? I'm using it in my program now, without any noticeable problem ...

the Fiddler's picture

Sorry, missed your post. This should work fine, as long as you don't use threading.

objarni's picture

.. even if I used threading, all GL-draw code must be on the context-creating thread, so I guess even then?

Unless there is multiple threading AND multiple contexts.

the Fiddler's picture

Of course. You can make it work even then, but it starts to become too much trouble for a simple Begin-End section.