OpenGL interleaved VBO - how to get sizeof(Float) in Scala? - scala

I've been trying to switch from two distinct VBOs to just one with interleaved attributes. I can do it in C++, but in Scala it proves quite difficult.
Here is my implementation:
class Mesh(positions: Array[Float], textureCoordinates: Array[Float], indices: Array[Int])
{
// Create VAO, VBO and a buffer for the indices
val vao: Int = glGenVertexArrays
val vbo: Int = glGenBuffers
val ibo: Int = glGenBuffers
setup
private def setup(): Unit =
{
val interleavedBuffer: FloatBuffer = prepareFloatBuffer(positions ++ textureCoordinates)
val indicesBuffer: IntBuffer = prepareIntBuffer(indices)
// One VAO to bind them all!
glBindVertexArray(vao)
glBindBuffer(GL_ARRAY_BUFFER, vbo)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo)
// Fill buffers with data
glBufferData(GL_ARRAY_BUFFER, interleavedBuffer, GL_STATIC_DRAW)
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indicesBuffer, GL_STATIC_DRAW)
// Set vertex attribute pointers
glVertexAttribPointer(0, 3, GL_FLOAT, false, 4*5, 0) // 0 = Position = Vector3(x,y,z) -> 3 (coordinates) * 4 (byte-size of float)
glVertexAttribPointer(1, 2, GL_FLOAT, false, 4*5, 4*3) // 1 = Texture Coordinates = Vector2(x,y) -> 2 (coordinates) * 4 (byte-size of float) => stride = 3 (coordinates) + 2 (texture coordinates) = 5 * 4 (byte-size of float); offset = 3 (coordinates) * 4 (byte-size of float)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)
glBindBuffer(GL_ARRAY_BUFFER, 0)
glBindVertexArray(0)
}
private def prepareIntBuffer(data: Array[Int]): IntBuffer =
{
val buffer: IntBuffer = BufferUtils.createIntBuffer(data.length)
buffer.put(data)
buffer.flip // Make the buffer readable
buffer
}
private def prepareFloatBuffer(data: Array[Float]): FloatBuffer =
{
val buffer: FloatBuffer = BufferUtils.createFloatBuffer(data.length)
buffer.put(data)
buffer.flip // Make the buffer readable
buffer
}
def render(): Unit =
{
glBindVertexArray(vao)
glBindBuffer(GL_ARRAY_BUFFER, vbo)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo)
glEnableVertexAttribArray(0) // Vertices are in zero
glEnableVertexAttribArray(1) // Texture Coords are in one
glDrawElements(GL_TRIANGLES, this.indices.length, GL_UNSIGNED_INT, 0)
glDisableVertexAttribArray(1)
glDisableVertexAttribArray(0)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)
glBindBuffer(GL_ARRAY_BUFFER, 0)
glBindVertexArray(0)
}
}
The data (positions, textureCoordinates) is the same I used before, with two distinct VBOs for them.
now:
glVertexAttribPointer(0, 3, GL_FLOAT, false, 4*5, 0)
glVertexAttribPointer(1, 2, GL_FLOAT, false, 4*5, 4*3)
How do I calculate these strides and offsets you ask?
Well, position is a Vector3(x, y, z) so 3 floats. Texture coordinates are two floats.
3 + 2 = 5
The size of a float is... well, I thought it was 4 bytes. (according to http://wiki.lwjgl.org/wiki/The_Quad_interleaved it is in Java)
That would give 20, or 4*5
The offset for the texture coordinates would be calculated the same (3 * 4) for each coordinates of the position
Now, the outcome doesn't look too good...
can you guess what it actually should be? (Spoiler: A cube)
So, I figure that either my maths is totally broken or that a Float maybe has a different size in Scala?
In Java I could do Float.size, but Scala doesn't have anything the like it seems.
In C++ I'd define a struct and do:
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)offsetof(Vertex, textureCoordinates));

Your problem is not sizeof float, but the data layout in your buffer. The statement
val interleavedBuffer: FloatBuffer = prepareFloatBuffer(positions ++ textureCoordinates)
Creates a buffer of the layout
xyz[0],xyz[1],…,xyz[n],st[0],st[1],…,st[m]
However what you configure OpenGL to expect is
xyz[0],st[0],xyz[1],st[1],…,xyz[n],st[n]
You can either properly interleave the attributes in the buffer, your you tell OpenGL that each attribute's elements are contiguous (0 stride, or the size of exactly one element of that attribute, i.e. 3*4 for xyz and 2*4 for st) and pass offsets to where each subbuffer start.

Related

what causes the iteration of an array to conclude prematurely in metal?

What I'm trying to do
I'm testing out metals capability to work with loops. Since I can't define new constants in metal, I'm passing a uint into the buffer and use it to iterate over an array filled with integers. It looks like this in swift.
let array1: [Int] = [1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6]
The problem(s)
However when reading the result array buffer in Swift after completing the loop in metal, it seems like not every element has been allocated.
#include <metal_stdlib>
using namespace metal;
kernel void shader(constant int *arr [[ buffer(0) ]],
device int *resultArray [[ buffer(1) ]],
constant uint &iter [[ buffer(2) ]]) // value of 12
{
for (uint i = 0; i < iter; i++){
resultArray[i] = arr[i];
}
}
out
1
2
3
4
5
6
0
0
0
0
0
0
Similarly, using the iterator to set allocate each element of resultArray, yields strange results
for (uint i = 0; i < iter; i++){
resultArray[i] = i;
}
out
4294967296
12884901890
21474836484
30064771078
38654705672
47244640266
0
0
0
0
0
0
Multiplication seems to work
for (uint i = 0; i < iter; i++){
resultArray[i] = arr[i] * i;
}
out
0
4
12
24
40
60
0
0
0
0
0
0
Addition does not
for (uint i = 0; i < iter; i++){
resultArray[i] = arr[i] + i;
}
out
4294967297
12884901892
21474836487
30064771082
38654705677
47244640272
0
0
0
0
0
0
When however, I set iter to a value of for example 24 or higher, it at least iterated over the whole arrays of size 12.
for (uint i = 0; i < iter; i++){ // iter now value of 100
resultArray[i] = arr[i] * iter;
}
100
200
300
400
500
600
100
200
300
400
500
600
What is going on here?
MCVE
yes, it's a lot of code to get a simple loop running in metal, please bare with me
main.swift
import MetalKit
let array1: [Int] = [1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6]
func gpuProcess(arr1: [Int]) {
let size = arr1.count // value of 12
// GPU we want to use
let device = MTLCreateSystemDefaultDevice()
// Fifo queue for sending commands to the gpu
let commandQueue = device?.makeCommandQueue()
// The library for getting our metal functions
let gpuFunctionLibrary = device?.makeDefaultLibrary()
// Grab gpu function
let additionGPUFunction = gpuFunctionLibrary?.makeFunction(name: "shader")
var additionComputePipelineState: MTLComputePipelineState!
do {
additionComputePipelineState = try device?.makeComputePipelineState(function: additionGPUFunction!)
} catch {
print(error)
}
// Create buffers to be sent to the gpu from our array
let arr1Buff = device?.makeBuffer(bytes: arr1,
length: MemoryLayout<Int>.size * size ,
options: .storageModeShared)
let resultBuff = device?.makeBuffer(length: MemoryLayout<Int>.size * size,
options: .storageModeShared)
// Create the buffer to be sent to the command queue
let commandBuffer = commandQueue?.makeCommandBuffer()
// Create an encoder to set values on the compute function
let commandEncoder = commandBuffer?.makeComputeCommandEncoder()
commandEncoder?.setComputePipelineState(additionComputePipelineState)
// Set the parameters of our gpu function
commandEncoder?.setBuffer(arr1Buff, offset: 0, index: 0)
commandEncoder?.setBuffer(resultBuff, offset: 0, index: 1)
// Set parameters for our iterator
var count = size
commandEncoder?.setBytes(&count, length: MemoryLayout.size(ofValue: count), index: 2)
// Figure out how many threads we need to use for our operation
let threadsPerGrid = MTLSize(width: 1, height: 1, depth: 1)
let maxThreadsPerThreadgroup = additionComputePipelineState.maxTotalThreadsPerThreadgroup // 1024
let threadsPerThreadgroup = MTLSize(width: maxThreadsPerThreadgroup, height: 1, depth: 1)
commandEncoder?.dispatchThreads(threadsPerGrid,
threadsPerThreadgroup: threadsPerThreadgroup)
// Tell encoder that it is done encoding. Now we can send this off to the gpu.
commandEncoder?.endEncoding()
// Push this command to the command queue for processing
commandBuffer?.commit()
// Wait until the gpu function completes before working with any of the data
commandBuffer?.waitUntilCompleted()
// Get the pointer to the beginning of our data
var resultBufferPointer = resultBuff?.contents().bindMemory(to: Int.self,
capacity: MemoryLayout<Int>.size * size)
// Print out all of our new added together array information
for _ in 0..<size {
print("\(Int(resultBufferPointer!.pointee) as Any)")
resultBufferPointer = resultBufferPointer?.advanced(by: 1)
}
}
// Call function
gpuProcess(arr1: array1)
compute.metal
#include <metal_stdlib>
using namespace metal;
kernel void shader(constant int *arr [[ buffer(0) ]],
device int *resultArray [[ buffer(1) ]],
constant uint &iter [[ buffer(2) ]]) // value of 12
{
for (uint i = 0; i < iter; i++){
resultArray[i] = arr[i] * iter;
}
}
You are using 64 bit Int in Swift and 32 bit integers in MSL. Your GPU threads are also overlapping their work. Instead, use Int32 in Swift and make each thread process their own piece of data. Like this
import MetalKit
let array1: [Int32] = [1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6]
func gpuProcess(arr1: [Int32]) {
let size = arr1.count // value of 12
// GPU we want to use
let device = MTLCreateSystemDefaultDevice()
// Fifo queue for sending commands to the gpu
let commandQueue = device?.makeCommandQueue()
// The library for getting our metal functions
let gpuFunctionLibrary = device?.makeDefaultLibrary()
// Grab gpu function
let additionGPUFunction = gpuFunctionLibrary?.makeFunction(name: "shader")
var additionComputePipelineState: MTLComputePipelineState!
do {
additionComputePipelineState = try device?.makeComputePipelineState(function: additionGPUFunction!)
} catch {
print(error)
}
// Create buffers to be sent to the gpu from our array
let arr1Buff = device?.makeBuffer(bytes: arr1,
length: MemoryLayout<Int32>.stride * size ,
options: .storageModeShared)
let resultBuff = device?.makeBuffer(length: MemoryLayout<Int32>.stride * size,
options: .storageModeShared)
// Create the buffer to be sent to the command queue
let commandBuffer = commandQueue?.makeCommandBuffer()
// Create an encoder to set values on the compute function
let commandEncoder = commandBuffer?.makeComputeCommandEncoder()
commandEncoder?.setComputePipelineState(additionComputePipelineState)
// Set the parameters of our gpu function
commandEncoder?.setBuffer(arr1Buff, offset: 0, index: 0)
commandEncoder?.setBuffer(resultBuff, offset: 0, index: 1)
// Set parameters for our iterator
var count = size
commandEncoder?.setBytes(&count, length: MemoryLayout.size(ofValue: count), index: 2)
// Figure out how many threads we need to use for our operation
let threadsPerGrid = MTLSize(width: 1, height: 1, depth: 1)
let maxThreadsPerThreadgroup = additionComputePipelineState.maxTotalThreadsPerThreadgroup // 1024
let threadsPerThreadgroup = MTLSize(width: maxThreadsPerThreadgroup, height: 1, depth: 1)
commandEncoder?.dispatchThreads(threadsPerGrid,
threadsPerThreadgroup: threadsPerThreadgroup)
// Tell encoder that it is done encoding. Now we can send this off to the gpu.
commandEncoder?.endEncoding()
// Push this command to the command queue for processing
commandBuffer?.commit()
// Wait until the gpu function completes before working with any of the data
commandBuffer?.waitUntilCompleted()
// Get the pointer to the beginning of our data
var resultBufferPointer = resultBuff?.contents().bindMemory(to: Int32.self,
capacity: MemoryLayout<Int32>.stride * size)
// Print out all of our new added together array information
for _ in 0..<size {
print("\(Int32(resultBufferPointer!.pointee) as Any)")
resultBufferPointer = resultBufferPointer?.advanced(by: 1)
}
}
// Call function
gpuProcess(arr1: array1)
Kernel:
#include <metal_stdlib>
using namespace metal;
kernel void shader(constant int *arr [[ buffer(0) ]],
device int *resultArray [[ buffer(1) ]],
constant uint &iter [[ buffer(2) ]],
uint gid [[ thread_position_in_grid ]], // this is thread index in grid, since you have height and depth of a dispatch set to 1 in CPU code, you can use 1D `int` here.
)
{
// Early out if gid is out of array boudns
if(gid >= iter)
{
return;
}
// Each thread processes it's own data
resultArray[gid] = arr[gid] * iter;
}
For more information on how to use Metal for compute refer to developer docs and for the information about attributes such as thread_position_in_grid refer to Metal Shading Language specification.

Allocate an AudioBufferList with two buffers for stereo audio stream

I have the following code in C that allocate an AudioBufferList with the appropriate length.
UInt32 bufferSizeBytes = bufferSizeFrames * sizeof(Float32);
propertySize = offsetof(AudioBufferList, mBuffers[0]) + (sizeof(AudioBuffer) * mRecordSBD.mChannelsPerFrame);
mBufferList = (AudioBufferList *) malloc(propertySize);
mBufferList->mNumberBuffers = mRecordSBD.mChannelsPerFrame;
for(UInt32 i = 0; i < mBufferList->mNumberBuffers; ++i)
{
mBufferList->mBuffers[i].mNumberChannels = 1;
mBufferList->mBuffers[i].mDataByteSize = bufferSizeBytes;
mBufferList->mBuffers[i].mData = malloc(bufferSizeBytes);
}
Most of the time, mChannelsPerFrame is 2, so the above code creates two buffers, one for each channel. Each buffer has a reserved memory worths bufferSizeBytes.
How can I replicate the same behaviour in Swift?
Unfortunately in Swift, a C array is treated as a tuple, so AudioBufferList.mBuffers is imported a tuple with a single AudioBuffer. C lets you just access the neighboring memory by using pointer math (or array subscript in this case) in order to create a variable length struct, Swift does not.
AudioBufferList just plain doesn't translate to Swift very well. Apple have mitigated this issue with a few helper functions and types. They created a static allocate function that returns an UnsafeMutableAudioBufferListPointer which is a special type where subscript returns the audioBuffers.
let bufferSizeBytes = MemoryLayout<Float>.size * 1234
var bufferlist = AudioBufferList.allocate(maximumBuffers: 2)
bufferlist[0] = AudioBuffer(mNumberChannels: 1,
mDataByteSize: UInt32(bufferSizeBytes),
mData: malloc(bufferSizeBytes))
bufferlist[1] = AudioBuffer(mNumberChannels: 1,
mDataByteSize: UInt32(bufferSizeBytes),
mData: malloc(bufferSizeBytes))
// Free your buffers and the pointer when you're done.
for buffer in bufferlist {
free(buffer.mData)
}
free(&bufferlist)
You can create an AudioBufferList with 2 buffers for 2 interleaved streams using a Core Audio API:
import AudioUnit
import AVFoundation
var myBufferList = AudioBufferList(
mNumberBuffers: 2,
mBuffers: AudioBuffer(
mNumberChannels: UInt32(2),
mDataByteSize: myBufferSizeBytes,
mData: nil) )

Questions regarding my OpenGL - code

I have been starting to dive into OpenGL ES 2.0 the last couple days, but I still get really faulty results. One thing I do not quite understand, is how I am supposed to set up my buffers correctly.
I would like to create a shape like this: A kind of tent, if you like, without the left and right side.
3_______________________2
|\ /|
| \_ _ _ _ _ _ _ _ _ _/ |
| /4 5\ |
|/_____________________\|
0 1
So let's start with my Texture/Indices/Vertices Array:
That is what i set up :
#define RECT_TOP_R {1, 1, 0}
#define RECT_TOP_L {-1, 1, 0}
#define RECT_BOTTOM_R {1, -1, 0}
#define RECT_BOTTOM_L {-1, -1, 0}
#define BACK_RIGHT {1, 0, -1.73}
#define BACK_LEFT {-1, 0, -1.73}
const GLKVector3 Vertices[] = {
RECT_BOTTOM_L, //0
RECT_BOTTOM_R, //1
RECT_TOP_R, //2
RECT_TOP_L, //3
BACK_LEFT, //4
BACK_RIGHT //5
};
const GLKVector4 Color[] = {
{1,0,0,1},
{0,1,0,1},
{0,0,1,1},
{0,1,0,1},
{1,0,0,1},
{0,1,0,1},
{0,0,1,1},
{0,1,0,1}
};
const GLubyte Indices[] = {
0,1,3,
2,4,5,
0,1
};
const GLfloat texCoords[] = {
0,0,
1,0,
0,1,
1,1,
1,1,
0,0,
0,0,
1,0
};
Here I generate/bind the buffers.
glGenBuffers(1, &vertexArray);
glBindBuffer(GL_ARRAY_BUFFER, vertexArray);
glEnableVertexAttribArray(GLKVertexAttribPosition);
glVertexAttribPointer(GLKVertexAttribPosition,3,GL_FLOAT,GL_FALSE,sizeof(Vertices),0);
glBufferData(GL_ARRAY_BUFFER, sizeof(Vertices), Vertices, GL_STATIC_DRAW);
glGenBuffers(1, &indexBuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Indices), Indices, GL_STATIC_DRAW);
glGenBuffers(1, &colArray);
glEnableVertexAttribArray(GLKVertexAttribColor);
glVertexAttribPointer(GLKVertexAttribColor, 4, GL_FLOAT, GL_FALSE, sizeof(Color), 0);
glBufferData(GL_ARRAY_BUFFER, sizeof(Color), Color, GL_STATIC_DRAW);
glGenBuffers(1, &texArray);
glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(texCoords),0);
glBufferData(GL_ARRAY_BUFFER, sizeof(texCoords), texCoords, GL_STATIC_DRAW);
So I have a questions regarding buffers:
What is the difference between GL_ARRAY_BUFFER and GL_ELEMENT_ARRAY_BUFFER ?
Here is the gelegate method, which is called whenever it redraws:
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
self.contentScaleFactor = 2.0;
self.opaque = NO;
glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
[self.effect prepareToDraw];
glDrawElements(GL_TRIANGLE_STRIP, sizeof(Indices), GL_UNSIGNED_BYTE, 0);
}
So, the code obviously does not work accordingly. Could you please help me ? I have been trying to get it to work, but I am losing my nerves.
Ok, so I definitely did something wrong there. I reused code from a website which basically stored all the Vertex data in one struct. I, however, have changed the code, in that I have separated the individual attribute arrays (colors, texture coordinates) into individual arrays. Before, the struct was buffered on its own, so the struct was processed by the GPU as a whole with the texture array and the color array. Now - after my changes - I need to generate and bind those buffers individually.
Another problem I could partly resolve was the one with the indices and texture mapping. I do not know whether I understood that right, but if I assign the texture coordinates (x,y) to a certain index and then reuse that index - with the aim of having another texture coordinate in that exact place - then apparently I would not have reason to wonder why everything is messed up.
What I ended up doing did not exactly solve my problem, but I got a whole lot nearer to my set goal and I am quite proud of my learning curve so far as far as openGL is concerned.
This answer is intended for others who might face the same problems and I hope that I do not spread any wrong information here. Please feel free to edit/point out any mistakes.
In response to your own answer, the vertex data in a struct you mentioned is called a struct of arrays. Apple recommend you use this layout.

glDrawElements VAO/VBO crash on iOS

I am creating a batching class that uses VAOs and VBOs to manage meshes. However, when attempting to use glDrawElements, I get EXEC_BAD_ACCESS and GL_INVALID_OPERATION when binding back to my VAO. Here is the code:
glGenVertexArraysOES(1, &arrayID);
glBindVertexArrayOES(arrayID); // Bind INTO VAO, opening state
// Load shaders and textures and bind them using glUseProgram etc.
glGenBuffers(1, &vboID);
glBindBuffer(GL_ARRAY_BUFFER, vboID);
glGenBuffers(1, &indexID);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexID);
glBindVertexArrayOES(0); // Bind AWAY from VAO, saving state
Glfloat data[length];
glBindVertexArrayOES(arrayID); // Bind INTO VAO, open state
unsigned int glfloatsize = sizeof(GLfloat);
unsigned int stride = kStride * glfloatsize;
// Fill Vertex information
glBufferData(GL_ARRAY_BUFFER, vertCount * glfloatsize * kStride, NULL, GL_STATIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, vertCount * glfloatsize * kStride, data);
glEnableVertexAttribArray(kPositionLocation);
glVertexAttribPointer(kPositionLocation, 3, GL_FLOAT, GL_FALSE, stride, BUFFER_OFFSET(0));
glEnableVertexAttribArray(kNormalLocation);
glVertexAttribPointer(kNormalLocation, 3, GL_FLOAT, GL_FALSE, stride, BUFFER_OFFSET(3));
glEnableVertexAttribArray(kColorLocation);
glVertexAttribPointer(kColorLocation, 4, GL_FLOAT, GL_FALSE, stride, BUFFER_OFFSET(6));
glClientActiveTexture(GL_TEXTURE0);
glEnableVertexAttribArray(kTextureLocation);
glVertexAttribPointer(kTextureLocation, 2, GL_FLOAT, GL_FALSE, stride, BUFFER_OFFSET(10));
// Fill Index information
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexCount * sizeof(GLushort), NULL, GL_STATIC_DRAW);
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, indexCount * sizeof(GLushort), index);
glBindVertexArrayOES(0); // Bind AWAY from VAO, saving state
// DO OTHER STUFF
/** RENDER (EXPLODES WITH EXEC_BAD_ACCESS) **/
glBindVertexArrayOES(arrayID);
glDrawElements(renderMode, indexCount, GL_UNSIGNED_SHORT, 0);
glBindVertexArrayOES(0);
/** RENDER (WORKS CORRECTLY [index is a scoped array of GLushorts that are uploaded to the VBO above...]) **/
glBindVertexArrayOES(arrayID);
glDrawElements(renderMode, indexCount, GL_UNSIGNED_SHORT, index);
glBindVertexArrayOES(0);
Any idea why I am receiving EXEC_BAD_ACCESS when attempting to use a GL_ELEMENT_ARRAY_BUFFER VBO?
Are you sure that following statements are true?
all OpenGL function doesn't set error - call glGetError after EACH of glXYZ function and check the result.
kStride >= 24
length == vertCount * kStride
index array has indexCount elements with GLushort type
all elements of index array has value less than vertCount value
there are no other glEnableVertexAttribArray calls

iPhone Cheetah 3D OpenGL ES Vertex Buffer Object (VBO) Example

I'd like to use Vertex Buffer Objects (VBOs) to improved my rendering of somewhat complicated models in my Open GL ES 1.1 game for iPhone. After reading several posts on SO and this (http://playcontrol.net/ewing/jibberjabber/opengl_vertex_buffer_object.html) tutorial, I'm still having trouble understanding VBOs and how to implement them given my Cheetah 3D export model format. Could someone please give me an example of implementing a VBO and using it to draw my vertices with the given data structure and explain the syntax? I greatly appreciate any help!
#define body_vertexcount 434
#define body_polygoncount 780
// The vertex data is saved in the following format:
// u0,v0,normalx0,normaly0,normalz0,x0,y0,z0
float body_vertex[body_vertexcount][8]={
{0.03333, 0.00000, -0.68652, -0.51763, 0.51063, 0.40972, -0.25028, -1.31418},
{...},
{...}
}
GLushort body_index[body_polygoncount][3]={
{0, 1, 2},
{2, 3, 0}
}
I've written the following code with the help of Chapter 9 from Pro OpenGL ES (Appress). I'm getting EXC_BAD_ACCESS with the DrawElements command and I'm not sure why. Could someone please shed some light? Thanks -
// First thing we do is create / setup the index buffer
glGenBuffers(1, &bodyIBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bodyIBO);
// For constrast, instead of glBufferSubData and glMapBuffer,
// we can directly supply the data in one-shot
glBufferData(GL_ELEMENT_ARRAY_BUFFER, body_polygoncount*sizeof(GLubyte), body_index, GL_STATIC_DRAW);
// Define our data structure
int numXYZElements = 3;
int numNormalElements = 3;
int numTextureCoordElements = 2;
long totalXYZBytes;
long totalNormalBytes;
long totalTexCoordinateBytes;
int numBytesPerVertex;
// Allocate a new buffer
glGenBuffers(1, &bodyVBO);
// Bind the buffer object to use
glBindBuffer(GL_ARRAY_BUFFER, bodyVBO);
// Tally up the size of the data components
numBytesPerVertex = numXYZElements;
numBytesPerVertex += numNormalElements;
numBytesPerVertex += numTextureCoordElements;
numBytesPerVertex *= sizeof(GLfloat);
// Actually allocate memory on the GPU ( Data is static here )
glBufferData(GL_ARRAY_BUFFER, numBytesPerVertex * body_vertexcount, 0, GL_STATIC_DRAW);
// Upload data to the cache ( memory mapping )
GLubyte *vboBuffer = (GLubyte *)glMapBufferOES(GL_ARRAY_BUFFER, GL_WRITE_ONLY_OES);
// Caclulate the total number of bytes for each data type
totalXYZBytes = numXYZElements * body_vertexcount * sizeof(GLfloat);
totalNormalBytes = numNormalElements * body_vertexcount * sizeof(GLfloat);
totalTexCoordinateBytes = numTextureCoordElements * body_vertexcount * sizeof(GLfloat);
// Set the total bytes property for the body
self.bodyTotalBytes = totalXYZBytes + totalNormalBytes + totalTexCoordinateBytes;
// Setup the copy of the buffer(s) using memcpy()
memcpy(vboBuffer, body_vertex, self.bodyTotalBytes);
// Perform the actual copy
glUnmapBufferOES(GL_ARRAY_BUFFER);
Here are the drawing commands where I'm getting the exception:
// Activate the VBOs to draw
glBindBuffer(GL_ARRAY_BUFFER, bodyVBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bodyIBO);
// Setup drawing
glMatrixMode(GL_MODELVIEW);
glEnable(GL_TEXTURE_2D);
glClientActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D,lightGreyInt);
// Setup pointers
glVertexPointer(3, GL_FLOAT, sizeof(vertexStruct), (char *)NULL + 0 );
glTexCoordPointer(2, GL_FLOAT, sizeof(vertexStruct), (char *)NULL + 12 );
glNormalPointer(GL_FLOAT, sizeof(vertexStruct), (char *)NULL + 24 );
// Now draw the body
glDrawElements(GL_TRIANGLES, body_polygoncount,GL_UNSIGNED_SHORT, (GLvoid*)((char*)NULL));
//glDrawElements(GL_TRIANGLES, body_polygoncount, GL_UNSIGNED_SHORT, nil);
//glDrawElements(GL_TRIANGLES,body_polygoncount*3,GL_UNSIGNED_SHORT,body_index);
Well, first of all your index buffer is too small, you don't just have body_polygoncount indices but body_polygoncount * 3. You also messed up the type, since they're shorts, you need GLushort and not GLubyte, so it should be
glBufferData(GL_ELEMENT_ARRAY_BUFFER, body_polygoncount*3*sizeof(GLushort),
body_index, GL_STATIC_DRAW);
And then, you messed up the offsets of your attributes, since your data contains first the texture coords, then the normal and then the position for each vertex, it should be
glVertexPointer(3, GL_FLOAT, sizeof(vertexStruct), (char *)NULL + 20 ); //3rd, after 5*4 byte
glTexCoordPointer(2, GL_FLOAT, sizeof(vertexStruct), (char *)NULL + 0 ); //1st
glNormalPointer(GL_FLOAT, sizeof(vertexStruct), (char *)NULL + 8 ); //2nd, after 2*4 bytes
And finally, in a glDrawElements call you don't give the number of triangles, but the number of elements (indices), so it should be
glDrawElements(GL_TRIANGLES, body_polygoncount*3,
GL_UNSIGNED_SHORT, (GLvoid*)((char*)NULL));
Otherwise your code looks reasonable (of course the mapping was senseless and you could have just used glBufferData again, but I guess you did it for learning) and if you understood everything it does, there is nothing more to it.
But I wonder that all these errors would also have occurred if you had just used client side vertex arrays without VBOs and I thought OpenGL ES 1.1 doesn't have immediate mode glBegin/glEnd. So I wonder why your game worked previously without VBOs if you're not aware of these errors.