Why Buffers over Arrays?

Started by thinker, October 14, 2003, 17:44:05

Previous topic - Next topic

thinker

Forgive me if this has been talked about before, i did a search and could not come up with anything on point.

I'm new to LWJGL and looking at it now for the first time. While going over the NeHe ports (Great btw, please do more!) i saw that all OpenGL commands that take in a vector as an argument accept that value as some type of Buffer here in LWJGL.

Can someone explain the design decision and performance impact of this to me? At first glance it seems strange to pass in Buffers for what is more easily done as an array.

Why do these methods not simply accept an array? Don't Buffers take more time and memory to allocate for small structures like { x, y, z } ?

Must they be memory mapped Buffers?

Is there a performance gain (or degradation) in using Buffers to pass small amounts of data like used in glLightfv()?

Do you advise using Buffers for all areas of code instead of arrays in general? or should i be using arrays internally and then converting them to Buffers when i want to pass them to the OpenGL calls? This seems like a waste of time and memory to me, but I don’t know as much as you guys so please explain.

Any insight/guidance here would be great
...
thinker

elias

The explanation is slightly complex, but it involves the way java data structures are accessed from the native layer - the layer that communicates with OpenGL. The short story is that java arrays have a larger overhead than buffers. We could accept arrays, but chose not to because we want people to use the maximal performance alternative and we want the library to be simple.

The longer version begins by noting that there exists two kinds of buffer - those you create with ByteBuffer.allocateDirect (Direct Buffers), and those craeted with ByteBuffer.allocate (array backed buffers). The last kind is simply an array wrapped as a buffer to allow you to use the buffer commands on it - flip(), put(), rewind() etc. The first kind behaves like the last, but instead of an array it is backed by a memory chunk in the heap, and is in fact the only kind you can pass to LWJGL.

Now, direct buffers actually have a higher overhead, at least in terms of space - each and every buffer size is rounded to the nearest 4k on the intel platform. I'm not sure about performance, but I expect arrays to be just as fast or (more likely) faster. And as you mentioned, arrays are much easier to work with.

The bottom line is: use arrays everywhere like you're used to, and only use Direct Buffer where nescessary, that is, when you have some data to be sent to OpenGL/OpenAL.

- elias

thinker

OK, so it is faster on the native side to deal with Direct ByteBuffers instead of arrays, but with the large number of small arrays being converted to Direct ByteBuffers on the Java side, doesn't that negate any performance gain on the native side, and just end up making everything harder at the same time?

What i want to do:
GL.glLightfv( light.number, GL.GL_POSITION, light.light_pos  ); // light_pos is a float[]


What i have to do:

ByteBuffer temp = ByteBuffer.allocateDirect( 16 );
temp.order( ByteOrder.nativeOrder() );
GL.glLightfv( light.number, GL.GL_POSITION, temp.asFloatBuffer().put( light.light_pos )  );


Now with the second example there are other problems to solve. I can't create a new Direct ByteBuffer for every call to any OpenGL command that takes a vector, as doing that every frame would cause HUGE issues. So i have to either:

1 Pre-create these buffers for each of my arrays, and thus duplicating all memory. Bad.

2 Store everything as a Buffer, and thus changing code to not use simple and easy to use arrays. Bad.

3 Create a ByteBuffer pool based on allocated size, which would be slow and way to much management. Bad.

Do you have any better options? The gain you make on the native side seems to be totally wasted when we talk about the whole stack and include what it takes to do on the Java side, plus I just see this causing the Java developer too many headaches.

Hopefully i'm wrong and there is some fast way to create these ByteBuffers that i have not seen. Otherwise i don't see any of the above solutions as useable.
...
thinker

princec

All you need is a single static reusable "scratch" buffer for those methods that require buffers. Then encapsulate everything as much as possible so you don't have to worry about it. For example, derive your own GL class from org.lwjgl.opengl.GL, and overload the static glLightfv method to take an array and stuff things in your scratch buffer for you.

Cas :)

thinker

Sure, OK, i can do that. However, it seems like a needless headache if that is what most people are going to do though, why not have LWJGL support it internaly so we don't all have to reinvent the wheel here?

Why not support methods that accept an array and do the conversion either at the native level (if it is faster) or place them in your suggested scratch buffer and pass them on for us (mabye do this now and change to native conversion later if it is faster). Seems like this is something LWJGL could do for us, and you can have both versions of the methods available.
...
thinker

princec

We wanted to pare the library down to the absolute minimum to achieve its intended purpose. That's why nearly all of the *v methods have actually been completely removed as they're not actually going to help in Java at all. If you need the functionality - chances are you do in one or two places - then wrap it once in your own code and forget about it. But there's no sense in us wrapping all of the methods you might want with arrays as it's beyond what we wanted to do and we'd end up with big bloaty JOGL.

Cas :)

thinker

well hey, that is your choice. there is a line between making things thin and fast, and making things easy to use. i think on this one you have made it a little too difficult for the developer just to save a very small amount of performance without giving the developer a choice.

Not saying don't keep the versions of the functions that are there now.. just take the gl*v() functions and add a version that accepts an array value. It is nothing more then you are suggesting here (unless you do the conversion on the native side) but it will be there for everyone and i bet that most people would rather use thouse then have Fun with Buffers (tm). If at some point people belive that there is a lot to be gained from converting arrays to buffers on their own then they still have that choice.
...
thinker

cfmdobbie

I'm no dev, but I guess the array unwrapping would have to be done Java-side if arrays were used - doing it native side just wouldn't be performant, would it?  As there's no difference between it being done Java-side in your code and Java-side in LWJGL, it's extra work for the LWJGL developers and overhead for the library itself for no performance improvement.  But ease of use?  Well, yes, but Buffers really aren't that hard to get to grips with anyway.  They take a short while before you understand how to use them, but once you have they really aren't a problem.

Adding a load of array-consuming methods rather strays from the purpose of LWJGL.  As you say, do it once - write yourself a wrapper around GL and implement the methods yourself.  When it comes down to it, LWJGL is a technology-enabling API first and foremost.  If you want a cuter interface, it's not hard to build one on top.  Make it Open Source and everyone can benefit from it!
ellomynameis Charlie Dobbie.

thinker

What a headache.

After trying this out many different ways today i think overall using Buffers instead of arrays is a god awful design choice. Any way you cut it, it is object allocation hell. You either need to allocate new buffers each time you need them which is very bad, or you need to keep a size based object pool which is a pain in the rear (as several important method take buffers of an unknown size, i.e. glDrawElements). Then, even if you use a pool you have to fill it each time you want to reuse one of the objects, which is, again, much slower then just using an array in the first place. There is just no other way to slice it. You can't just store every item internally as a Direct Byte Buffer, that is not what they are for and that will kill performance in one big swoop. Arguments to the native layer should be primitive types unless completely necessary like gl*Pointer().

Show me some code that renders a scene using something like glDrawElements for meshes that have several different materials with textures for 100k+ polys per frame and is faster then if you used arrays. It is very simple and fast to do with arrays, but a pain in the ass to do with Buffers, and i bet a lot slower when it comes down to it. 9 out of 10 doctors choose primitives over objects.

It might have been easy to implement the native side using ByteBuffers, but it is making it hell on the Java side for memory use and thus performance. Not to mention that the method signatures do not match up with anything in standard OpenGL books/docs so you have to hunt and search for the right thing on the LWJGL side, that hurts the learning curve and it hurts when people try to port to LWJGL.

I could be looking at this all wrong (note first post.. i have been working on this for 12 hours stright), so please, set me straight with some *real* examples that prove Buffers are better then arrays as a whole, and not just *easier* for the native side.
...
thinker

elias

I'm mostly going to repeat princec here - you are not going to use buffers much. That's right - in a game, the GL usage is a small percentage of the overall game code and even so, the buffer using calls are even more rare. So you'll need one class to render your primitives and maybe a few to manage light state. So the problem is in fact smaller than you might think. I also see no problem in caching the Direct Buffers you use all the time.

- elias

cfmdobbie

Quote from: "thinker"Show me some code that renders a scene using something like glDrawElements for meshes that have several different materials with textures for 100k+ polys per frame and is faster then if you used arrays.
Honest, performance really isn't part of this discussion.  What you're doing would otherwise have to be done Java-side in LWJGL anyway, so what you're gaining is that you know what you're doing.

If you were only using arrays and leaving LWJGL to sort it out for itself then every single invocation LWJGL would need to copy the data into a Buffer to pass to the native layer.  That can't be a good thing.

QuoteNot to mention that the method signatures do not match up with anything in standard OpenGL books/docs so you have to hunt and search for the right thing on the LWJGL side, that hurts the learning curve and it hurts when people try to port to LWJGL.
Anything that took a pointer, takes a Buffer instead.  If something took a pointer to a load of ints, it now takes an IntBuffer.  Anything that took offsets as well as pointers in the same argument position has now been split up.  Many methods don't really apply to a Java environment and have been removed.  I'm sure you'll get used to it.

Is there a specific situation you can describe that you're having issues with?
ellomynameis Charlie Dobbie.

the2bears

Quote from: "thinker"What a headache.

...

Show me some code that renders a scene using something like glDrawElements for meshes that have several different materials with textures for 100k+ polys per frame and is faster then if you used arrays. It is very simple and fast to do with arrays, but a pain in the ass to do with Buffers, and i bet a lot slower when it comes down to it. 9 out of 10 doctors choose primitives over objects.


Sure, but an array in Java is an object is it not?  As for the rest of your performance arguments I am not qualified to comment.  But... there's always a but... you've got the source.  Might be worth you making some tests.  If you like what you see, then you've got the library and API that you want.

Bill
the2bears - the indie shmup blog

thinker

i'll take another crack at it. For all the small sized buffers there is no real problem, i can fill a scratch buffer and reuse that. It is the large buffers that have an issue, i will just need to change my data structures to hold the data as Buffers instead of arrays.. i think that will make loops slower (i believe accessing items of a buffer is slower then accessing the items in an array) but i'll try it and find out.

The only other options is duplicate the information in an array and a buffer so collision detection and other iteration intensive processes access the array.. and the buffer is used to pass the same information to OpenGL.
...
thinker

thinker

OK, so i have things working.. and it is about the same speed as my Jogl renderer. Here is the subclass with a few different methods to show what i'm doing, feedback would be great:

public class GL extends org.lwjgl.opengl.GL 
{
	private static ByteBuffer scratch;
	
	static 
	{
		scratch = ByteBuffer.allocateDirect( 1024 * 1024 ); // 1MB scratch
		scratch.order( ByteOrder.nativeOrder() );
	}
	
	private static FloatBuffer toBuffer( float[] params )
	{
		scratch.clear();
		FloatBuffer buff = scratch.asFloatBuffer();
		buff.put( params );
		buff.flip();
		return buff;
	}
	
	private static IntBuffer toBuffer( int[] params )
	{
		scratch.clear();
		IntBuffer buff = scratch.asIntBuffer();
		buff.put( params );
		buff.flip();
		return buff;
	}
	
	// *************************
	
	public static void glLightModelfv( int pname, float[] params )
	{
		glLightModel( pname, toBuffer( params ) );
	}
	
	public static void glLightfv( int light, int pname, float[] params )
	{
		glLightfv( light, pname, toBuffer( params ) );
	}
	
	public static void glVertexPointer( int size, int type, int stride, ByteBuffer buffer )
	{
		glVertexPointer( size, stride, buffer.asFloatBuffer() );
	}

	public static void glNormalPointer( int type, int stride, ByteBuffer buffer )
	{
		glNormalPointer( stride, buffer.asFloatBuffer() );
	}

	public static void glTexCoordPointer( int size, int type, int stride, ByteBuffer buffer )
	{
		glTexCoordPointer( size, stride, buffer.asFloatBuffer() );
	}
	
	public static void glMaterialfv( int face, int pname, float[] params )
	{
		glMaterial( face, pname, toBuffer( params ) );
	}
	
	public static void glDrawElements( int mode, int count, int type, int[] indices )
	{
		glDrawElements( mode, toBuffer( indices ) );
	}
	
	public static void glGenTextures( int n, int[] textures )
	{
		GL.glGenTextures( toBuffer( textures ) );
		
		for ( int i = 0; i < textures.length; i++ )
		{
			textures[i] = scratch.get( i );
		}
	}
}


If anyone has a moment to take a look and see if i'm doing anything horribly wrong here that would help. The only area that i don't like is that glDrawElements() is usually called with a very large array and doing a Buffer.put() on the scratch does take some time, less then 2% in the whole scheme of things, but still something to consider.
...
thinker

princec

I've got no idea why you want to use an array in the first place!

Cas :)