CommuSoft's picture

OpenGL and the garbage collector

Perhaps a strange question, but I'm still quite new to the OpenTK framework and OpenGL. When I was building a game (still working on it, will soon be finished). Sometimes the program stopped because of an OpenGL memory access bug (most of the time minutes after it was started). After walking through the entire rendering method, I couldn't find any pointer that was null, or an integer-references that wasn't been loaded yet. After doing some try and error i changed instructions like:
GL.Light(GLLightName.Light0,GLPName.Position,new float[] {0f,1f,0f});
to:
GL.Light(GLLightName.Light0,GLPName.Position,lightP);
where lightP is a static field. The ammount of bugs dropped immidiatly. My theory is that the garbage collector cleared the array and OpenGL couldn't get access to the array anymore. But I'm not that familiar with OpenGL.

If you give a pointer with for instance the GL.Light method, does GL copy the data to some external memory or does the array needs to be pinned in the memory. Or was there some other problem that I solved.

Anyway how does OpenGL works with array-references?


Comments

Comment viewing options

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

The GC will indeed reclaim the array after the function returns. Some OpenGL functions expect you to keep such arrays around (this is called "client storage"), while others copy their parameters to internal buffers (this is called "server storage").

Client storage is not safe when a GC is in use, unless you are aware of the pitfalls. Storing the arrays in static members is not enough! The .Net GC is a so-called compacting GC, which means it may shuffle memory around to improve efficiency - you need to pin those arrays (incurring a performance penalty).

It's a good thing that OpenGL 3.x+ deprecates all functions that use client storage.

All this said, I don't know whether GL.Light uses client storage or not (the docs don't mention anything of the kind and I haven't encountered an issue before). A wild guess is that you are calling some other function with client storage, such as GL.VertexPointer without vertex buffer objects and the decreasing number of crashes is merely a side-effect of the decreasing GC pressure (less temporary arrays to reclaim). That's just a guess though.

CommuSoft's picture

Thanks for the reply! How can I pin these arrays so the performance penalty isn't that large. Are there some general instruction for that.
-----------------------------------------
my apologies for my bad english.
The box said: "Requires Windows XP or better."
So i installed Linux.

martinsm's picture

glLight uses server storage. There is no need to pin its arguments.

You can pin in two ways.
One way is with usafe code (better performance):

float[] arr = ...;
usafe
{
    fixed (float* ptr = arr)
    {
        // here pass ptr instead of arr
    }
}

Second way is with GCHandle (lower performance):

float[] arr = ...;
GCHandle handle = GCHandle.Alloc(arr, GCHandleType.Pinned);
// here pass handle.AddrOfPinnedObject().ToPointer() instead of arr
handle.Free(); // free the pinned pointer, when you don't need to operate on it

If you use pinning for vertex array drawing, then remember to keep arrays pinned up to the (and including) glDrawElements/glDrawArrays call.

And instead of doing
GL.Light(GLLightName.Light0,GLPName.Position,new float[] {0f,1f,0f});

you can write the following code that will avoid creating array and GC pressure:

GL.Light(GLLightName.Light0,GLPName.Position, new Vector4(0,1,0,1));

the Fiddler's picture

Just a clarification: OpenTK pins parameters using one of the above methods whenever necessary. However, this is only for the duration of the OpenGL function call (which is obviously not enough when using client storage).

It is generally considered a bad idea to pin objects for large amounts of time, since this decreases the efficiency of the GC. However, it should be relatively safe to allocate and pin a few large arrays on program startup and use them whenever necessary.

Of course, the best approach from a performance and safety perspective would be to switch to server storage completely.

CommuSoft's picture
the Fiddler wrote:

Of course, the best approach from a performance and safety perspective would be to switch to server storage completely.

Are there ways to do this (by using buffers or anything else)
-----------------------------------------
my apologies for my bad english.
The box said: "Requires Windows XP or better."
So i installed Linux.

martinsm's picture

Yes, you should use VBO for sending vertex data or indices to OpenGL.

hannesh's picture

VBO's are good, but you will have the vertex pointers being moved or cleared by the GC.

VAO's are the solution to all http://www.opentk.com/doc/graphics/vertex-array-objects

JTalton's picture

Don't both setting up a VBO and VAO both call GL.BufferData which takes a buffer. Is that call GC safe?

What's the difference between

// Vertex Array Object
GL.GenVertexArrays(1, out VaoHandle);
GL.GenBuffers(1, out VboHandle);
GL.BindVertexArray(VaoHandle); 
GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle);
GL.BufferData(...);
GL.EnableClientState(...);
GL.VertexPointer(...);
GL.EnableVertexAttribArray(...);
GL.BindVertexArray(0);

and

// Vertex Buffer Object
GL.GenBuffers(1, out VboHandle);
GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle);
GL.BufferData(...);
GL.EnableClientState(...);
GL.VertexPointer(...);

Is it just a matter of documentation that OpenGL 3.0 calls use server storage and VAO is only supported in 3.0, that's why it is GC safe?

martinsm's picture
hannesh wrote:

VBO's are good, but you will have the vertex pointers being moved or cleared by the GC.

For glBufferData - yes, you must be careful with GC. But once glBufferData call is finished, it has copied all memory to server side (aka GPU driver/card). Either do it with GCHandle, fixed keyword, or one of OpenTK overloads.
But after that you can release/dispose of memory that you passed to it and not worry about GC.
VAO's won't help you here with anything. It is completely different thing.

martinsm's picture
JTalton wrote:

What's the difference between

VAO is completly different thing from VBO.

VBO encapsulates buffer storage on GPU side (server side).

VAO encapsulates EnableClient and VertexPointer state. That means you can create VAO and bind and enable vertex pointers inside it at startup of application, and only bind VAO during rendering, instead of calling multiple states bind and enable functiosn for vertex pointers. Without VAO you will have a little more overhead per frame. VAO is completely not related to GC issues.

You can read here: http://www.opengl.org/registry/specs/ARB/vertex_array_object.txt what state VAO stores in it. Read section that starts with:

Quote:

A vertex array object can be made part of the current rendering state with
the command

void BindVertexArray(uint array);