Reputation: 648
I know it's probably better to use VBO instead, but I have personal reasons to use Vertex Arrays (beside my curiosity)
In C++, here the vertex arrays is used:
// "vertices" is an array of Vertex Struct
const char* data = reinterpret_cast<const char*>(vertices);
glVertexPointer(2, GL_FLOAT, sizeof(Vertex), data + 0));
glTexCoordPointer(2, GL_FLOAT, sizeof(Vertex), data + 8));
glColorPointer(4, GL_FLOAT, sizeof(Vertex), data + 16));
glDrawArrays(GL_QUADS, 0, vertexCount);
the pointer passed and worked nicely in C++, however, I can't make it work in C# with OpenTK. I followed the official documentation and ended up with this codes:
Vertex[] vertices = // .. Fill some vertex here
unsafe
{
fixed (Vertex* data = vertices)
{
GL.VertexPointer(2, VertexPointerType.Float, Vertex.Stride, new IntPtr(data + 0));
GL.TexCoordPointer(2, TexCoordPointerType.Float, Vertex.Stride, new IntPtr(data + 8));
GL.ColorPointer(4, ColorPointerType.Float, Vertex.Stride, new IntPtr(data + 16));
// Draw the Vertices
GL.DrawArrays(PrimitiveType.Quads,
0,
vertices.length);
GL.Finish(); // Force OpenGL to finish rendering while the arrays are still pinned.
}
}
What I got is only blank white, nothing displayed.
I tried to use same vertices with my VBO implementation also similar vertices pointers code, it's working properly, so I think its not setup code fault and I'm sure Vertex.Stride
returning valid stride of Vertex
struct.
Any Ideas?
Upvotes: 1
Views: 3854
Reputation: 1717
The unsafe
route didn't work because new IntPtr(data + 8)
will point 8 vertices ahead instead of pointing 8 bytes ahead. You can fix this by using IntPtr.Add
method:
fixed (Vertex* data = vertices)
{
var stride = Marshal.SizeOf<Vertex>();
GL.VertexPointer(2, VertexPointerType.Float, stride,
new IntPtr.Add(new IntPtr(data), 0));
GL.TexCoordPointer(2, TexCoordPointerType.Float, stride,
new IntPtr.Add(new IntPtr(data), 8));
GL.ColorPointer(4, ColorPointerType.Float, stride,
new IntPtr.Add(new IntPtr(data), 16));
GL.DrawArrays(PrimitiveType.Quads, 0, vertices.Length);
GL.Finish();
}
Or you can make it less error prone by grabbing the relevant pointers directly:
fixed (void* posPtr = &vertices[0].x, texPtr = &vertices[0].u, colorPtr = &vertices[0].r)
{
var stride = Marshal.SizeOf<Vertex>();
GL.VertexPointer(2, VertexPointerType.Float, stride,
new IntPtr(posPtr));
GL.TexCoordPointer(2, TexCoordPointerType.Float, stride,
new IntPtr(texPtr))
GL.ColorPointer(4, ColorPointerType.Float, stride,
new IntPtr(colorPtr))
GL.DrawArrays(PrimitiveType.Quads, 0, vertices.Length);
GL.Finish();
}
I'll assume Vertex is defined as, or similar to:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct Vertex
{
public float x, y;
public float u, v;
public float r, g, b, a;
};
EDIT:
StructLayout
LayoutKind.Sequential
attribute explicitly tells the compiler NOT to rearrange the order of variables in the memory.
NB. This is not strictly necessary in this case because according to MSDN LayoutKind.Sequential
is default behavior for struct
s (value types).
Pack = 1
additionally tells the compiler to pack the variables in memory tightly, without leaving in any gaps. By default compiler tries to align memory on 4 (or 8) byte boundary.
NB. Again this is not necessary for the given Vertex
struct as all variables are perfectly aligned on 4 byte boundaries and there are no gaps in between.
The reason why I still keep the StructLayout
attribute in this case, even if it works perfectly without it, is to be crystal clear and explicit about the expected memory layout of the Vertex
structure as it is important in cases like these.
EDIT2: works with generics too
public struct Vertex
{
public Vector2<float> Pos;
public Vector2<float> Texcoord;
public Vector4<byte> Color;
}
public struct Vector2<T>
{
public T x, y;
}
public struct Vector3<T>
{
public T x, y, z;
}
public struct Vector4<T>
{
public T x, y, z, w;
}
And get the pointers:
fixed (void* posPtr = &vertices[0].Pos.x,
texPtr = &vertices[0].Texcoord.x,
colorPtr = &vertices[0].Color.x)
Upvotes: 1
Reputation: 648
Finally, I able to make it work by de-interleave the Vertex
struct (I'm not really sure if it's called "de-interleave").
However, I believe it's not best solutions for this problem. Better solutions will be highly appreciated.
Here the codes to de-interlave each attributes and upload those attributes data each frame using Vertex Arrays:
Vertex[] vertices = // Fill some vertices here..
// This is workaround that i was talking about, de-interleave attributes of Vertex Struct
// it might be better to de-interleaving those attributes when the change has been made to vertices rather than de-interleaving them each frames.
List<Vector2> positions = new List<Vector2>();
foreach (Vertex v in vertices)
positions.Add(v.position);
List<Vector2> texCoords = new List<Vector2>();
foreach (Vertex v in vertices)
texCoords.Add(v.texCoords);
List<Vector4> colors = new List<Vector4>();
foreach (Vertex v in vertices)
colors.Add(v.color);
// Use attribute stride instead of Vertex stride.
GL.VertexPointer(2, VertexPointerType.Float, Vector2.SizeInBytes, positions.ToArray());
GL.TexCoordPointer(2, TexCoordPointerType.Float, Vector2.SizeInBytes, texCoords.ToArray());
GL.ColorPointer(4, ColorPointerType.Float, Vector4.SizeInBytes, colors.ToArray());
// Draw the Vertices
GL.DrawArrays(PrimitiveType.Quads,
0,
vertices.length);
Upvotes: 1