eric_the_animal
eric_the_animal

Reputation: 432

Copying float arrays and float values to a bytebuffer - Java

I am trying copy the data from two arrays and two variables to a bytebuffer. The bytebuffer will hold this data for a uniform block structure in a fragment shader. I can copy the first one in fine but the second always generates an index out of range error.

I've tried using .asFloatBuffer, tried initializing the buffer to twice the size I needed and tried using a FloatBuffer (I need a ByteBuffer but I thought I'd just try to fix this error then work my way back)

The structure from fragment shader:

layout (binding = 0) uniform BlobSettings {
    vec4 InnerColor;
    vec4 OuterColor;
    float RadiusInner;
    float RadiusOuter;
};

This is what I have now for code (a bit of a mess but you get the idea...):

//create a buffer for the data
//blockB.get(0) contains the 'size' of the data structure I need to copy (value is 48)
//FloatBuffer blockBuffer = BufferUtil.newFloatBuffer(blockB.get(0));
ByteBuffer blockBuffer = ByteBuffer.allocateDirect(blockB.get(0) * 4);//.asFloatBuffer();

//the following data will be copied to the buffer
float outerColor[] = {0.1f,0.1f,0.1f,0.1f};
float innerColor[] = {1.0f,1.0f,0.75f,1.0f};
float innerRadius = 0.25f;
float outerRadius = 0.45f;

//copy data to buffer at appropriate offsets
//params contains the offsets (0, 16, 32, 36)

//following 4 lines using a FloatBuffer (maybe convert to ByteBuffer after loading?)
blockBuffer.put(outerColor, params.get(0), outerColor.length);
blockBuffer.put(innerColor, params.get(1), innerColor.length); //idx out of range here...
blockBuffer.put(params.get(2), innerRadius);
blockBuffer.put(params.get(3), outerRadius);

//when using ByteBuffer directly - maybe something like the following?
for (int idx=0;idx<4;idx++){
    blockBuffer.putFloat(params.get(0) + idx, outerColor[idx]) //????
}

Can anyone tell me how I can properly get that data into a ByteBuffer?

Upvotes: 1

Views: 5585

Answers (1)

Reto Koradi
Reto Koradi

Reputation: 54602

Based on the way I read the spec for uniform blocks, you will need to be careful how the definition in the shader matches up with the values in the buffer. There are several layout options for uniform blocks. The declaration you have does not specify a layout:

layout (binding = 0) uniform BlobSettings {
    vec4 InnerColor;
    vec4 OuterColor;
    float RadiusInner;
    float RadiusOuter;
};

Without a specified layout, the default is shared. This does not guarantee a well defined memory layout for the block, and you will have to query the size/offset of values with glGetActiveUniformBlockiv() and glGetActiveUniformsiv(). In the data you provided, the total size came back as 48, which most likely means that padding was added at the end to make the size a multiple of 16.

The simpler option is that you specify the std140 layout option, which guarantees a specific layout:

layout (std140, binding = 0) uniform BlobSettings {
    ...
};

This should give you a packed layout with a total size of 40 bytes, and the values in the specified order. The std140 layout is not always fully packed, e.g. a vec3 will use the same space as a vec4. You can read the specs for the details. But for vec4 and scalar values, it is packed in this case.

For filling a buffer with float values, there are a few different options. Using your data as an example (changing the order to match the uniform definition):

float innerColor[] = {1.0f, 1.0f, 0.75f, 1.0f};
float outerColor[] = {0.1f, 0.1f, 0.1f, 0.1f};
float innerRadius = 0.25f;
float outerRadius = 0.45f;

Assuming that you want to pass this to OpenGL with a call like glBufferData().

Put into array, then wrap

If you put all the values into a single float array, you can directly turn it into a buffer with FloatBuffer.wrap():

float bufferData[] = {
    1.0f, 1.0f, 0.75f, 1.0f,
    0.1f, 0.1f, 0.1f, 0.1f,
    0.25f,
    0.45f);
glBufferData(..., FloatBuffer.wrap(bufferData));

Allocate and use float buffer

For this, you allocate a FloatBuffer, fill it with the data, then use it. Note that the put() methods advance the buffer position, so you can simply add the values one by one. You do have to rewind the buffer to the start position before using it.

FloatBuffer buf = FloatBuffer.allocate(10);
buf.put(innerColor);
buf.put(outerColor);
buf.put(innerRadius);
buf.put(outerRadius);
buf.rewind();
glBufferData(..., buf);

Allocate byte buffer, and use as float buffer

All of the OpenGL Java bindings I have seen accept either FloatBuffer or Buffer arguments. So using a FloatBuffer as in the approaches above is easiest. But if you use OpenGL bindings that really require a ByteBuffer, or need a direct allocation (which at least on Android is only the case for client side vertex arrays), you can allocate a ByteBuffer first, then use it as FloatBuffer:

ByteBuffer byteBuf = ByteBuffer.allocateDirect(10 * 4);
FloatBuffer floatBuf = byteBuf.asFloatBuffer();
floatBuf.put(innerColor);
floatBuf.put(outerColor);
floatBuf.put(innerRadius);
floatBuf.put(outerRadius);
byteBuf.rewind();
glBufferData(..., byteBuf);

Use byte buffer

This seems somewhat cumbersome, but you could use a ByteBuffer all the way:

ByteBuffer buf = ByteBuffer.allocateDirect(10 * 4);
buf.putFloat(innerColor[0]);
buf.putFloat(innerColor[1]);
buf.putFloat(innerColor[2]);
buf.putFloat(innerColor[3]);
buf.putFloat(outerColor[0]);
buf.putFloat(outerColor[1]);
buf.putFloat(outerColor[2]);
buf.putFloat(outerColor[3]);
buf.putFloat(innerRadius);
buf.putFloat(outerRadius);
buf.rewind();
glBufferData(..., buf);

Upvotes: 3

Related Questions