JTalton's picture

VertexArrayUtility

I have been working on my base library for Vertex Arrays. I am planning on putting it out for people to use. It is very dynamic and works well. I am at the point where I am looking at the feature set that OpenGL provides and trying to determine what else it should support. The main question is about glMultiDrawElements and glMultiDrawArrays. Has anyone tried these out with OpenTK? I can see their use in certain settings. I am worried about the pinning of the memory and how to specify the arrays that go into this call. Any thoughts would be welcome.

Example of the VertexArrayUtility:

    VertexArray vertexArray;
 
    public override void OnLoad(EventArgs e)
    {
        vertexArray = new VertexArray(BeginMode.Triangles);
 
        vertexArray.Vertices = new VertexBuffer();
        vertexArray.Vertices.Add(new Vector3(-1, -1, 0));
        vertexArray.Vertices.Add(new Vector3(1, -1, 0));
        vertexArray.Vertices.Add(new Vector3(1, 1, 0));
        vertexArray.Vertices.Add(new Vector3(-1, 1, 0));
 
        vertexArray.Colors = new ColorBuffer();
        vertexArray.Colors.Add(((uint)255 << 24) | ((uint)255 << 16) | ((uint)255 << 8) | (uint)255); // WHITE
        vertexArray.Colors.Add(((uint)255 << 24) | ((uint)0 << 16) | ((uint)0 << 8) | (uint)255); // RED
        vertexArray.Colors.Add(((uint)255 << 24) | ((uint)0 << 16) | ((uint)255 << 8) | (uint)0); // GREEN
        vertexArray.Colors.Add(((uint)255 << 24) | ((uint)255 << 16) | ((uint)0 << 8) | (uint)0); // BLUE
 
        vertexArray.Elements = new ElementBuffer();
        vertexArray.Elements.Add(0);
        vertexArray.Elements.Add(1);
        vertexArray.Elements.Add(2);
        vertexArray.Elements.Add(2);
        vertexArray.Elements.Add(0);
        vertexArray.Elements.Add(3);
    }
 
    public override void OnRenderFrame(RenderFrameEventArgs e)
    {
        GL.Clear(ClearBufferMask.ColorBufferBit);
        vertexArray.Render(); // Updates any buffers and then renders
        SwapBuffers();
    }

Comments

Comment viewing options

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

Why not use Color4?

zahirtezcan's picture

Does the utility use pre 3.x style vertex arrays or forward compatible vertex attribs? If latter is the case can we set attrib names or are they fixed?

JTalton's picture

The classes in the example are derived classes of generic base classes.

public class VertexBuffer : VertexBufferObject<Vector3> {}

It is setup in such a way that you could have buffers of any type of struct.
Example:

VertexAttribBufferObject<MyAttribStruct>

Vertices, Multiple TexCoords, VertexAttribs, Normals, etc... are supported.
I use a struct for my vertex attribs that has functions for accessing the bits and it works great.

Each BufferObject is loaded into GPU memory once and any changes to the buffer are updated before the next render.

The VertexArray supports both setting up the buffers internally and also using the OpenGL VertexArrayObject functionality if available.

Instance rendering is supported.
Primitive restart support is being added.

I use this as the base for all the rendering I am doing in Golem3D. Every widget, every font character, and the model rendering all use this code. I have tried to make it as developer friendly as possible. I need to wrap up a few tweaks and I'll post it under the contributed section.

JTalton's picture

I would like to support:
glMultiDrawElements — render multiple sets of primitives by specifying indices of array data elements

void glMultiDrawElements( GLenum mode, const GLsizei* count, GLenum type, const GLvoid** indices, GLsizei primcount);

Parameters

  • mode - Specifies what kind of primitives to render.
  • count - Points to an array of the elements counts.
  • type - Specifies the type of the values in indices.
  • indices - Specifies a pointer to the location where the indices are stored.
  • primcount - Specifies the size of the count array.

So "count" is an array of counts so it needs to be pinned? Correct?
"indices", while the description above does not make it sound so, it is an array of pointers to arrays of element indices. I would need to pin all the element arrays, make an array of the IntPtrs and then pin the array of IntPtrs? Correct?

While I think I understand the issues of pinning, I have not looked deeply into the subject so I am looking for validation of my assumptions.

the Fiddler's picture

MultiDrawElements works differently depending on whether a VBO is bound or not (VBO as in ARB_vertex_buffer_object, promoted to core around OpenGL 1.4).

Quoting from the ARB_vertex_buffer_object spec.

Quote:

How does MultiDrawElements work?

The language gets a little confusing, but I believe it is quite
clearly specified in the end. The argument to
MultiDrawElements, which is of type "const void **", is an
honest-to-goodness pointer to regular old system memory, no
matter whether a buffer is bound or not. That memory in turn
consists of an array of pointers. If no buffer is
bound, each of those pointers is a regular pointer.
If a buffer is bound, each of those pointers is a
fake pointer that represents an offset in the buffer object.

  1. If a VBO is bound, indices is an array of IntPtr offsets (i.e. fake pointers), which is automatically pinned for the duration of the call. You do not need to do anything more than that.
  2. If a VBO is not bound, indices is an array of pointers-to-arrays. This is rather problematic: you need to pin each one of these arrays manually, obtain its pointer and add it to the array. OpenTK automatically pins the latter for the duration of the call - the former must be pinned manually.
  3. Count is an array of element counts that is pinned by OpenTK for the duration of the call.

In short, if you are using VBOs, there is no problem - the element buffer resides in server memory, which is safe from the GC. If you are not using VBOs, the element buffers (i.e. the pointers-to-arrays) reside in client memory which is not safe - the element buffers must remain pinned.

The question now is, "how long should the client-side element buffers remain pinned?" This depends on whether MultiDrawElements accesses those buffers immediately. If it does, it is enough to pin them for the duration of the call. If not, the buffers must remain pinned even after MultiDrawElements returns (probably for their whole lifetime).

Frankly, I have no idea how MultiDrawElements behaves in this regard. It probably accesses them immediately, but I'd still prefer to play it safe and either pin the arrays completely or allocate them in unmanaged memory from the start (via Marshal.AllocHGlobal()).

That is, I'd do that if I wanted to support legacy 1.1-level vertex arrays. What I'd actually do is use 1.4-level VBOs, if available, and fall back to immediate mode otherwise (you can do that pretty much seamlessly through the same IVertexBufferObject interface).

JTalton's picture

Thanks for the info.

Here is the latest version. Free to all. Post if you find it useful or have any suggestions. I'll make a more formal release later.

AttachmentSize
VertexArrayUtility.zip12.14 KB
the Fiddler's picture

Very thorough approach, thanks for sharing!

This is somewhat esoteric, but you should probably check that the type parameters are blittable before using them:

public abstract class BufferObject<T> : IEnumerable, IDisposable where T : struct
{
    readonly static bool typeIsSafe = BlittableValueType<T>.Check();
}

If typeIsSafe is false, T is not safe for use with OpenGL (probably best to throw an exception in the BufferObject constructor). This is the case when T contains references to managed classes or other non-blittable types. For example:

// Safe: (consists of floats (recursively), which are blittable types)
struct GoodVertex
{
   public Vector3 Position, Normal;
   public Vector2 TexCoords;
}
 
// Not safe (references are not blittable)
struct BadVertex
{
   public object Foo;
   public float[] Bar;
}

You can also use BlittableValueType.Stride to get the size of in bytes, which is useful when calling BufferData/BufferSubData.

Other than that, consider making the various "leaf" classes sealed: VertexArrayObject, TexCoordBuffer, ColorBuffer, etc (unless there is a reason to let the user inherit from them?)

zahirtezcan's picture

i once made such vertex acquisition mechanism by using c# attribute classes. its usage depended on automatic recognition of stride (Marshal.SizeOf) and offset (Marshal.OffsetOf). it was like this:

struct PositionTexCoordIndex
{
   [Vertex(3, DataType.Float)]
   public Vec3f Pos;
 
   [Vertex(2, DataType.Float)]
   public Vec2f TexCoord;
 
   [Vertex(2, DataType.Integer)]
   public Vec2i Index;
}

So, when you take Type object, you loop thorough its fields (by reflection) and you can determine offsets and strides. This way, it can contain other managed objects imho. But it is not the case most of the times.

the Fiddler's picture

You are right: you can add managed references to the vertex declaration, as long as you don't actually use them as vertex attributes. However, I can't really see any reason to do that (you generally want vertices to be as lean as possible).

I'm also using the attribute approach, albeit a little more automated. The attributes are optional (fields without attributes are considered generic and set via VertexAttribPointer) and I retrieve the actual datatype through reflection (Vector3 is DataType.Float with count = 3, etc).

struct Vertex
{
    // Generic attribute
    public Vector3 Position;
 
    // Non-generic attribute
    [VertexField(Usage = VertexUsage.Normal)]
    public Vector3 Normal;
}

In the past I used a system where the renderer sets the vertex streams to match the shader attributes automatically. This turned out to be somewhat limiting (it becomes complex if you want to use different buffers as streams), so I'm using a simpler approach now:

// The device performs state management and rendering
var device = new GraphicsDevice(context);
 
// Create a new VBO
var vbo = new VertexBuffer<Vertex>(device, ...);
var data = new Vertex[] { ... };
vbo.SetData(data);
 
// Render the VBO
device.Program = shader;
device.SetAttribute(shader.Attributes["VertexPosition"].Index, vbo, "Position"); // "Position" field of vbo
device.SetAttribute(VertexUsage.Normal, vbo, "Normal"); // "Normal" field of vbo
device.ApplyState();
 
device.DrawPrimitives(PrimitiveType.Triangles);
zahirtezcan's picture

This api looks really nice (it is like DirectX with utils). i want to comment on device class, it would be nice if we have a VertexArray class on which we call "SetAttribute" methods and use it as a state variable like the Program property:

var vao = new VertexArray();
vao.SetAttribute(VertexUsage.Normal, vbo, "Normal");
...
device.Program = shader;
device.VertexArray = vao;
device.ApplyState();
...

And another utility fantasy :): One can declare a program instance as unlinked shader collection. Then declare a new class named Material which is a specialized version of the program with some uniforms set to default values and attrib locations bound with given vertex array object. then state variable is this material object instead of the program.