modern OpenGL discussion

Hi,

I just reviewed some of my classes using the last LWJGL that now use GLFW instead of it’s internal Display class.

Now, i’m review some of my 2D drawing stuff that used the old OpenGL stuff using glBegin / glEnd by a more modern version using VBO, IBO, etc…

But i have some design questions and i must admit that i have some pain to go on the flexible pipeline…

For some test i done this small classe (i skip all window initialization and openGL configuration like modelview, ortho, …)

i update the code above with the result of discussions on this topic for have the code only 1 time

/** Draft
 */
public class OpenGLSupport {
	
	/** 
	 * Maximum bufferized primitives count
	 */
	private static final int     PRIMITIVES_SIZE = 512;
	
	/** FLOAT size in bytes (for stride and offset computes)
	 */
	private static final int     FLOAT_SIZE      = Float.SIZE / 8;
	
	/** INTEGER size in bytes (for stride and offset computes)
	 */
	private static final int     INTEGER_SIZE    = Integer.SIZE / 8;
	
	/** 
	 * VERTEX Position size in bytes (for stride and offset computes)
	 */
	private static final int     VERTEX_SIZE     = FLOAT_SIZE * 2;
	
	/** COLOR size in bytes (for stride and offset computes)
	 */
	private static final int     COLOR_SIZE      = FLOAT_SIZE * 4;
	
	/** 
	 * Maximum vertices buffer size
	 */
	private static final int     VERTICES_SIZE   = powerOfTwo( PRIMITIVES_SIZE * ( VERTEX_SIZE + COLOR_SIZE ) * 4 );
	
	/**
	 * Maximum indices buffer size
	 */
	private static final int     INDICES_SIZE    = powerOfTwo( PRIMITIVES_SIZE * 8  );

	/** OpenGL bind optimizer
	 */
	private OpenGLBinder         glBinder        = null;
	
	private Primitive[]			 primitives      = new Primitive[ PRIMITIVES_SIZE ];
	private FloatBuffer          verticesBuffer  = BufferUtils.createFloatBuffer( VERTICES_SIZE );
	private IntBuffer            indicesBuffer   = BufferUtils.createIntBuffer( INDICES_SIZE );
	                                             
	private int                  primitiveCount  = 0;
	                                 
	/** Our VBO and IBOI handles
	 */
	private int                  iVBO            = 0;
	private int                  iIBO            = 0;
	                             
	/** Color to use
	 */
	private Color                color           = Color.WHITE;
	
	public OpenGLSupport( OpenGLBinder binder ) {
		System.out.println("GraphicsOpenGL (primitivesSize=" + PRIMITIVES_SIZE + ", verticesSize=" + VERTICES_SIZE + ", indicesSize=" + INDICES_SIZE );
		this.glBinder = binder;
	}
	
	public void setColor( Color color ) {
		this.color = color;
	}
	
	public void drawRect(float x, float y, float width, float height) {
		// 1 primitive , 4 vertices, 8 indices
		checkBufferSize( 1 , 4 , 8 );
		
		// Get the primitive to draw
		Primitive p = getPrimitive( GL11.GL_LINES );
		
		// push vertices and colors
		p.pushVertex( x         , y          );
		p.pushVertex( x + width , y          );
		p.pushVertex( x + width , y + height );
		p.pushVertex( x         , y + height );
		
		// push indices
		indicesBuffer.put( 0 );
		indicesBuffer.put( 1 );
		indicesBuffer.put( 1 );
		indicesBuffer.put( 2 );
		indicesBuffer.put( 2 );
		indicesBuffer.put( 3 );
		indicesBuffer.put( 3 );
		indicesBuffer.put( 0 );
		p.end();
	}

	public void drawLine(float x1, float y1, float x2, float y2) {
		// 1 primitive , 2 vertices, 2 indices
		checkBufferSize( 1 , 2 , 2 );
		
		// Get the primitive to draw
		Primitive p = getPrimitive( GL11.GL_LINES );
		
		// push vertices and colors
		p.pushVertex( x1 , y1 );
		p.pushVertex( x2 , y2 ); 
		
		// push indices
		indicesBuffer.put( 0 );
		indicesBuffer.put( 1 );
		p.end();
	}

	public void fillRect(float x, float y, float width, float height) {
		// 1 primitive , 4 vertices, 6 indices
		checkBufferSize( 1 , 4 , 6 );

		// Get the primitive to draw
		Primitive p = getPrimitive( GL11.GL_TRIANGLES );
		
		// push vertices and colors
		p.pushVertex( x          , y          );
		p.pushVertex( x + width  , y          ); 
		p.pushVertex( x + width  , y + height );
		p.pushVertex( x          , y + height );
		
		// push indices
		indicesBuffer.put( 0 );
		indicesBuffer.put( 1 );
		indicesBuffer.put( 2 );
		indicesBuffer.put( 0 );
		indicesBuffer.put( 2 );
		indicesBuffer.put( 3 );
		p.end();
	}
	
	/** Peek a Primitive object in the primitives buffer and reinit it.
	 *  This methode reuse {@link Primitive} instances in order to limit instanciations under the rendering process.
	 */
	private Primitive getPrimitive( int primitiveType ) {
		Primitive p = primitives[ primitiveCount ];
		if( p == null ) {
			p = new Primitive();
			primitives[ primitiveCount ] = p;
		}
		primitiveCount++;
		
		p.begin( primitiveType );
		return p;
	}

	public void flush() {
		// flush our buffers
		verticesBuffer.flip();
		indicesBuffer.flip();
		
		// VBO binding
		boolean gen = ( iVBO == 0 );
		{
			if( gen ) {
				iVBO = GL15.glGenBuffers();
			}

			// Bind VBO (only if it's not already the case)
			glBinder.bindVBO( iVBO );
			
			// make a full buffer for be able to perform dynamic size buffer
			if( gen ) {
				FloatBuffer empty = BufferUtils.createFloatBuffer( verticesBuffer.capacity() );
				for(int i = 0 ; i < empty.capacity() ; i++ ) {
					empty.put( 0f );
				}
				empty.flip();
				GL15.glBufferData( GL15.GL_ARRAY_BUFFER, empty , GL15.GL_DYNAMIC_DRAW );
			}
			GL15.glBufferSubData( GL15.GL_ARRAY_BUFFER, 0 , verticesBuffer );
		}
		    
	    // IBO binding
	    gen =  ( iIBO == 0 );
	    {
	    	if( gen ) {
	    		iIBO = GL15.glGenBuffers();
	    	}
	    	
	    	// Bind IBO (only if it's not already the case)
	    	glBinder.bindIBO( iIBO );
		    
		    // make a full buffer for be able to perform dynamic size buffer
 			if( gen ) {
 				IntBuffer empty = BufferUtils.createIntBuffer( indicesBuffer.capacity() );
 				for(int i = 0 ; i < empty.capacity() ; i++ ) {
 					empty.put( 0 );
 				}
 				empty.flip();
 				GL15.glBufferData( GL15.GL_ELEMENT_ARRAY_BUFFER, empty , GL15.GL_DYNAMIC_DRAW );
 			}
			GL15.glBufferSubData( GL15.GL_ELEMENT_ARRAY_BUFFER, 0 , indicesBuffer );
	    }
	    
	    // Painting
	    GL11.glEnableClientState( GL11.GL_VERTEX_ARRAY );
	    
	    Primitive p = null;
	    for(int i = 0 ; i < primitiveCount ; i++ ) {
	    	p = primitives[i];
	    	
	    	if( p.glColorOffset >= 0 ) {
	    		GL11.glEnableClientState( GL11.GL_COLOR_ARRAY );
	    		GL11.glColorPointer( 4 , GL11.GL_FLOAT , p.stride , p.glColorOffset );
	    	}
	    	GL11.glVertexPointer( 2, GL11.GL_FLOAT, p.stride , p.glVertexOffset );
	    	GL11.glDrawElements( p.type , p.indicesCount , GL11.GL_UNSIGNED_INT , p.indicesOffset );
	    	
	    	if( p.glColorOffset >= 0 ) {
	    		GL11.glDisableClientState( GL11.GL_COLOR_ARRAY );	
	    	}
	    }
	    
	    //Disable vertex arrays
	    GL11.glDisableClientState( GL11.GL_VERTEX_ARRAY );
	    
	    // clear the buffer
	    verticesBuffer.clear();
	    indicesBuffer.clear();
	    primitiveCount = 0;
	}
	
	public void dispose() {
		if( iVBO > 0 ) {
			glBinder.unbindVBO( iVBO );
			
			GL15.glDeleteBuffers( iVBO );
			iVBO = 0;
		}
		if( iIBO > 0 ) {
			glBinder.unbindIBO( iIBO );
			
			GL15.glDeleteBuffers( iIBO );
			iIBO = 0;
		}
		
		verticesBuffer.clear();
		indicesBuffer.clear();
		primitiveCount = 0;
	}
	
	/** Check if the need to flush all pending statements before continuing.
	 */
	private void checkBufferSize( int primitiveCount , int verticesCount , int indicesCount ) {
		if( this.primitiveCount + primitiveCount >=  PRIMITIVES_SIZE ) {
			flush();
			return;
		}
		
		if( verticesBuffer.position() +  ( verticesCount * ( VERTEX_SIZE + COLOR_SIZE ) ) >= VERTICES_SIZE ) {
			flush();
			return;
		}
		
		if( indicesBuffer.position() + ( indicesCount * INTEGER_SIZE ) >= INDICES_SIZE ) {
			flush();
		}
	}
	
	private class Primitive {
		private int     type;
		
		private int     glVertexOffset;
		private int     glColorOffset;
		private int     stride;
		
		private int     indicesOffset;
		private int     indicesCount;
		
		private void begin( int type ) {
			// init it
			this.type            = type;
			this.glVertexOffset  = -1;
			this.glColorOffset   = -1;
			this.stride          = -1;
			
			this.indicesOffset   = indicesBuffer.position() * INTEGER_SIZE;
			this.indicesCount    = 0;
		}
		private void pushVertex( float x , float y ) {
			int strideStart = verticesBuffer.position() * FLOAT_SIZE;
			
			pushPaintContext();
			
			if( glVertexOffset < 0 ) glVertexOffset = verticesBuffer.position() * FLOAT_SIZE;
			verticesBuffer.put( x );
			verticesBuffer.put( y );
			
			if( stride < 0 ) stride =  ( verticesBuffer.position() * FLOAT_SIZE ) - strideStart;
		}
		
		private void pushPaintContext() {
			if( color != null ) {
				if( glColorOffset < 0 )  glColorOffset  = verticesBuffer.position() * FLOAT_SIZE;
				pushColor( color );
			}
			else {
				// TODO : texture impl
			}
		}
		
		private void pushColor( Color color ) {
			float[] rgb = color.getRGBComponents();
			
			for(int i = 0 ; i < 4 ; i++ ) {
				if( i < rgb.length ) {
					verticesBuffer.put( rgb[i] );
				}
				else {
					verticesBuffer.put( 1f ); // for alpha if missing
				}
			}
		}

		private void end() {
			indicesCount =  ( (indicesBuffer.position() * INTEGER_SIZE) - indicesOffset ) / INTEGER_SIZE;
		}
	}

	/**
     * Get the closest greater power of 2 to the fold number.
     */
    private static int powerOfTwo(int num) {
    	 if( num != 0 ) {
	        num--;
	        num |= (num >> 1);  // Or first 2 bits
	        num |= (num >> 2);  // Or next 2 bits
	        num |= (num >> 4);  // Or next 4 bits
	        num |= (num >> 8);  // Or next 8 bits
	        num |= (num >> 16); // Or next 16 bits
	        num++;
	    }
	    return num;
    } 	
    
}

and the binder :

public class OpenGLBinder {

	private int bindedVBO;
	private int bindedIBO;
	private int bindedTexure;
	
	
	public void bindIBO( int ibo ) {
		if( bindedIBO == ibo ) return;
		GL15.glBindBuffer( GL15.GL_ELEMENT_ARRAY_BUFFER, ibo );
		bindedIBO = ibo;
	}
	
	public void unbindIBO( int ibo ) {
		if( bindedIBO == ibo ) {
			GL15.glBindBuffer( GL15.GL_ELEMENT_ARRAY_BUFFER, 0 );
			bindedIBO = 0;
		}
	}
	
	public void bindVBO( int vbo ) {
		if( bindedVBO == vbo ) return;
		GL15.glBindBuffer( GL15.GL_ARRAY_BUFFER, vbo );
		bindedVBO = vbo;
	}

	public void unbindVBO( int vbo ) {
		if( bindedVBO == vbo ) {
			GL15.glBindBuffer( GL15.GL_ARRAY_BUFFER, 0 );
			bindedVBO = 0;
		}
	}

}

As is, if i call a fillRect + flush, this code work. (ughh ^^)

So now, i have some reflextion about how to design an efficient drawing back-end if i call by example 2 fillRect and 1 drawLine !!

What i think :

  • Fill all vertices for theses 2 rects and the line when i call fillRect and drawLine methods

when flush():

  • bind the IBO, VBO

  • send all vertices to VBO (for 2 rects and 1 line)

  • send all indices to IBO (for 2 rects and 1 line)

  • glVertex (with buffer offset)

  • drawElement for the first rect (with indice offset)

  • glVertex (with buffer offset)

  • drawElement for the first rect (with indice offset)

  • glVertex (with buffer offset)

  • drawElement for the second rect (with indice offset)

  • glVertex (with buffer offset)

  • drawElement for the line (with indice offset)

The advantage with this design, it’s i can merge relatively easily “textcoord” or “colors” in the VBO and use stride concepts for each “entity” since i make a separate drawElement for each one.

I don’t know if i can do a drawElement for all of theses ? i think no because we can have the first fillRect with a color and the second one with a texture. so i must configure the call to the drawElement.

The second things, is the size of the buffer, i could imagine to have a limited size (ie 1024k) and flush() automatically if i call a draw() methods that will overflow the buffer.

PS: i would admit, all was simpler with glBegin and glEnd ^^ but it’s really fun to discover new things.

Some advise is welcome ^^

So essentially this is what we call (in the trade) a sprite batcher. And (I will compare with what I do, which is not necessarily the best way) you are doing everything right but there are two ways you could obviously (to me) improve.

  1. You don’t have to use VBOs. You can also use what are called “Vertex Arrays” which work in almost exactly the same way except that instead of the vertex data coming from an OpenGL buffer object, they come from a buffer in CPU memory. Implementation wise, there is a glVertexAttribPointer() function in LWJGL that takes a ByteBuffer (or Float or Int) as a parameter instead of an offset. The offset into this data that OpenGL uses is the position of the ByteBuffer. This will be more efficient than what you are doing which is essentially re-creating a buffer object each time you flush.

  2. Generally I have a series of batchers each with their own unique vertex attribs. A begin() method binds textures and sets up pointers. Then the drawXXX() methods add vertex data to the (cpu side) buffers and if the buffers are overflowing then flushes them. Then an end() method flushes if anything is left in the buffers. The crucial thing here is that you set up each set of pointers as few times as possible per frame which is obviously efficient. (So yes, essentially it is back to glBegin() and glEnd())

2 - cont.) Furthering this point, I will also have my batchers only draw the same “type” of geometry. Ie only quads or only 5-sided polygons or only lines. The advantage of this is that the index buffer will be the same each and every time so you can make it entirely static in an IBO. (Grab every bit of performance you can, because why not).

Otherwise you are doing very good (as far as I know). Hope I’ve been helpful.

Whether Vertex Arrays are faster than VBOs is debatable. For multiple small batches, maybe. For medium to large batches, VBOs are most likely faster. Remember that the data still has to get to the GPU somehow.

That does not seem to be the case. glGenBuffers() should only be called once from OP’s code.

A few points for OP:

  • Don’t use GL_QUADS. Use GL_TRIANGLES and your IBO to rearrange the quad vertices into 2 triangles.
  • Only use GL_STATIC_DRAW for data that never (or very rarely) changes. Use GL_DYNAMIC_DRAW instead.
  • In addition to this, if you have data that updates frequently, you can reuse the allocated buffer by using glBufferSubData. However, you must make sure that the buffer doesn’t use more data than was allocated previously.
  • glEnableClientState and gl{Vertex,Normal,Color, etc.}Pointer methods are still using the old deprecated pipeline. Use glEnableAttribArray and glVertexAttribPointer instead.
  • If you use Vertex Array Objects (different from Vertex Arrays), you can avoid having to bind the VBO and call all the glxxxPointer methods each draw call (you still have to do it once at the start).

Obviously the above points, along with countless other optimisations, are all somewhat tricky to get working, and I recommend not trying to do everything at once.

My general advice would to be to only use more OpenGL features as you discover your game needs them.

Please don’t make a confusion between vertex arrays and vertex array objects (VAO). I can’t talk about VAO but vertex arrays are usually as fast as dynamic VBOs.

What I meant was the actual memory of the buffer was being released and reallocated every flush. And I’m sure that the allocation of the buffer (as opposed to the generation of the OpenGL object) is the longest bit. So I just minced words. You may well be right but I find it very hard to believe that VBOs (even dynamic ones using glBufferSubData()) would be faster than VAs under these conditions. Not that it really matters - I think we’re dealing with pretty minuscule differences either way - I recommend VAs for sprite batchers just because they’re simpler to use.

And as a side note, from what I hear, what is debatable is whether VAOs are really the way to go performance wise. But I don’t know anything about that because beyond figuring out how they work I don’t use them.

Also I second @gouessej, let’s not confuse VAOs and VAs. Whoever came up with these names should be shot but since that wouldn’t help us out let’s just not confuse ourselves.

If you use mapped buffers you should definitely beat vertex arrays as you’re writing directly to GPU memory (well, it may depend on implementation, but that’s what’s supposed to happen).

Thanks for all replies.

I’m dealing with this snippet code that works for now with samples cases. ( i put the new code as a basis for further discussions )

So, i don’t really understood how works glEnableVertexAttribArray and how i can replace glEnableClientState with it…

I’m not really understood your discussions about VBO vs VAs (in fact i not know what is a VA)…

Code updated on the first topic

So,

I wrote today the drawImage primitive in the same way than drawRect, fillRect and drawLine

I have few remarks or questions:

  1. When drawing the image (2 triangles with texCoords), i must do GL11.glEnable( GL11.GL_TEXTURE_2D ); each time ? i tried to put it as a global intialization but it seems to have side effects on others drawing ops. I tried the global flag since i thinked that the GL11.glEnableClientState( GL11.GL_TEXTURE_COORD_ARRAY ); would be sufficient for enabling/disabling it

  2. For drawing the image with a transparency, i must use a blend “SRC_OVER” function like it:


  GL11.glEnable(GL11.GL_BLEND);
  GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);

I must call it before drawing my image. It seems to returns onto the old fixed pipeline mechanism ?  Can i improve theses statements ?
  1. It’s a generalization of the 2) statements, if i must do a affine transformation, a blend or other similar things, i must call theses glXXXX just before the the glDrawElements() like before ?
    For the affine transformation, what is the best, letting OpenGl do it by calling glLoadMatrix or applying it by software when i push vertices on the buffer ?

  2. i read some stuff about VAs and specifically on the use of glEnableVertexAttribArray and similar stuff but it remains obscur…

I don’t updated the code with the “image” supports since it’s always a dirty code, i would clean up before posting here for dicuss about it.

Best regards,
Sébastien.

(1) Enabling GL_TEXTURE_2D and enabling GL_TEXTURE_COORD_ARRAY are two different things. GL_TEXTURE_2D should be enabled whenever you are drawing something with textures. Also see (*). GL_TEXTURE_COORD_ARRAY should be enabled whenever you are drawing something and the texture coords are coming from the glTexCoordPointer().

(2) Firstly see (*). These functions should work exactly the same as with immediate mode rendering.

(3) Yes, transforms, blend settings and uniforms can essentially be seen as OpenGL state. And the settings that get applied to the rendered primitives is the state OpenGL is in when you call the glDrawXXX() function. In terms of placement, it kind of replaces glEnd().

(4) So first of all I will explain vertex attributes (whereas VA stands for vertex arrays - you see why we were complaining about naming being confusing) but bear in mind they don’t particularly apply to what you are asking. See (*) as to why they were brought up in this topic. So in the fixed function pipeline there are a few set “channels” (for want of a better name) of data with a fixed function. Eg position, colour, tex coords, normals. But when we get to using shaders we want things to be a bit more versatile so we have “vertex attributes” which are exactly the same as position, colour, normal etc except that they can be used for whatever you want and they are referenced with an index instead of a name.

So if we were using index 0 for the position.
“glEnableClientState(GL_VERTEX_ARRAY)” -> “glEnableVertexAttribArray(0)”
“glVertexPointer(2, GL_FLOAT, stride, offset)” -> “glVertexAttribPointer(0 /index/, 2, GL_FLOAT, GL_FALSE /normalized/, stride, offset)”
“glVertex2f(x, y)” -> “glVertexAttrib2f(0 /index/, x, y)”.
So you can see that other than the index and normalized parameters, they work exactly the same as before. But you are not using shaders so this isn’t relevant to you right now.

Now Vertex Arrays. The basic idea is that instead of pointing to a position in a VBO on the gpu, you point to a location in your program’s cpu memory which in LWJGL is implemented by passing a Buffer instead of an offset. An example then.


//With VBOs
//Init
FloatBuffer fb = BufferUtils.createFloatBuffer(3 * (2 + 3)); //3 vertices with 2 position floats and 3 colour floats.
fb.put(new float[] {...}); Imagine this is the data for a triangle with XYRGB interleaving.

int bufferId = glGenBuffer();
glBindBuffer(GL_ARRAY_BUFFER, bufferId);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, fb, GL_STATIC_DRAW);

...
//Render (Assuming you've enabled all the relevant stuff)
glBindBuffer(GL_ARRAY_BUFFER, bufferId);
glVertexPointer(2, GL_FLOAT, 20 /*(2+3)*4*/, 0);
glColorPointer(2, GL_FLOAT, 20 /*(2+3)*4*/, 8 /*2 * 4*/);
glDrawElements(...); //You know this bit


//With VAs
//Init
FloatBuffer fb = BufferUtils.createFloatBuffer(3 * (2 + 3)); //3 vertices with 2 position floats and 3 colour floats.
fb.put(new float[] {...}); Imagine this is the data for a triangle with XYRGB interleaving.
//And then we DON'T create a VBO for the data.

...
//Render (Assuming you've enabled all the relevant stuff)
fb.position(0); //Move the buffer's position to the vertex offset.
glVertexPointer(2, 20 /*(2+3)*4*/, fb); //Notice that we don't pass the "type" parameter in LWJGL becuase it will infer that these are floats since it is a FloatBuffer
fb.position(2); //Then we move the buffer's position to the colour offset. 
glColorPointer(2,20 /*(2+3)*4*/, fb);
glDrawElements(...); //You know this bit

So again very simple.

(*) And a little appendix to clear things up. In your first post and subsequently you referred to these improvements as moving away from the fixed function pipeline. This is wrong but a very common misconception, not sure why. What you are doing is moving away from “immediate mode rendering” (glBegin() and glEnd()) and towards “deferred rendering” (VBOs, IBOs, VAs and pointers).

The fixed function pipeline is OpenGL’s built in transform stuff (glTranslatef(), glRotatef() …) and quite a lot more. The opposite of the fixed function pipeline is the programmable pipeline which is shaders. This is why vertex attributes were brought up - if you were using shaders (which technically you did say you were) then vertex attributes are a good idea, just because they are more flexible. And a wee bit of extra info, if you were using the programmable pipeline then you wouldn’t need to enable GL_TEXTURE_2D to draw textures.

That was a long post. Hope it helped.

Hi,

Thanks for your detailed answer.

So, ok, fixed pipeline and deferred rendering are two differents things. i deep into deferred rendering with VBO. but for dynamic pipeline it’s the use of shaders (vertex, fragment, geometry).

while reading you, you are seems to say that VBO and shaders are not suitable together and you must go to VAs if i want to use shaders (that i will soon for doing repeated atlases subregion) ?

Anothers small questions

You say than glTranslatef(), glRotatef() are fixed pipeline stuff, so that means we should avoid theses ? At this time i don’t use theses but i use some matrix mode when i need to apply an affine transform (that remains the same than glTranslatef() or glRotatef() but provide more flexibilty).

When reading your example:

//With VAs
//Init
FloatBuffer fb = BufferUtils.createFloatBuffer(3 * (2 + 3)); //3 vertices with 2 position floats and 3 colour floats.
fb.put(new float[] {...}); Imagine this is the data for a triangle with XYRGB interleaving.
//And then we DON'T create a VBO for the data.

...
//Render (Assuming you've enabled all the relevant stuff)
fb.position(0); //Move the buffer's position to the vertex offset.
glVertexPointer(2, 20 /*(2+3)*4*/, fb); //Notice that we don't pass the "type" parameter in LWJGL becuase it will infer that these are floats since it is a FloatBuffer
fb.position(2); //Then we move the buffer's position to the colour offset. 
glColorPointer(2,20 /*(2+3)*4*/, fb);
glDrawElements(...); //You know this bit

How work the buffer “fb” communication between the cpu and the gpu. You pass it to 2 calls (glVertexPointer and glColorPointer). It have 2 copy into the GPU or the OpenGL API retains that is the same buffer instance and communicate it only on time to the GPU at the end ?

I will try to adapt my code to VAs instead of VBO.

Sébastien.

Have a look at this JGO tutorial which Riven provided

In this tutorial you will see draw methods showing intermediate mode, VA, VBO (a few variations), you will also notice that no shaders are used here so shaders aren’t mandatory in this setup.

In this setup you are still using the fixed pipeline design but you are changing the way the GPU is fed the vertex data. Shaders are part of the programmable pipe line design of opengl and you can again choose which method you want to feed the data to the GPU, you can use VA and VBO (not intermediate mode) with shaders, the only difference is how you do you draw call, VA’s will pass the data to the GPU every frame and VBO’s will just pass a reference to data that is already on the GPU every frame

I said that vertex attributes (not VAs, VA stands for vertex array in my post) are more suited to shaders since they are more flexible. Using VBOs, VAs or even immediate mode has absolutely no impact on whether you want to use shaders or not.

Yes you should avoid these, but until you are using shaders, you have to use them. Shaders are the only alternative. And yes any OpenGL function to do with matrices is one to avoid. By then end, the only OpenGL function you should be using for matrices is glUniform().

My understanding is that the only thing that gets passed to OpenGL when you call glXXXPointer() (for a VBO or a VA) is an offset. If there is a buffer bound when it is called, OpenGL treats it as an offset into that buffer, otherwise it treats it as an address in cpu memory (when you call it with the function with a Buffer LWJGL does it’s magic to translate that into a memory address). It is only when you do the draw call that OpenGL starts reading data from the memory address it has. So you can see that if you are replacing all of the data each frame then there really shouldn’t be too much of a difference between VBOs and VAs.

So, i reply here for giving feedback of theses 3 past days.

I rewrote my batcher with vertex array instead of VBO.

I used IntBuffer for indices (since it’s more simple than ByteBuffer) for glDrawElements but LWJGL don’t have method facilities for use an IntBuffer and providing the number of vertices to fetch except by consumming all datas from the buffer. But my indices buffer is filled with all primitives that i had to draw by multiple calls.

I had to call the unchecked version :

   GL11.nglDrawElements( p.type , p.indicesCount , GL11.GL_UNSIGNED_INT, MemoryUtil.memAddress( indicesBuffer ) );

Now, i have perform a 2d texturing from an atlas texture. I had done all shader/program stuff in order to activate the fragment shader that will do the texturing from an atlas texture.

I have two technical approachs for handling shaders

  • only one program but i attach/detach shaders when needed
  • One program for each shaders topology.

Both of them can be achieve with the same kind of API where i just enable/disable shaders and back-end choose/generate the appropriate program or alter it (depends of the solution).

What is the best practice ?

At this time, i will have 2 shaders (one for texturing from atlas and a second for multiples stop gradiant paint)

Best regards,
Sébastien.

You know you can still use a ByteBuffer, but to make it more convenient for you to fill it, you can create an IntBuffer view on that ByteBuffer with ByteBuffer.asIntBuffer().
Using this way you can also not forget about flipping or rewinding your buffer before handing it off to LWJGL.

So I forgot to mention this in my last post but in the same way you use the buffer’s position to specify the offset, you use the buffer’s limit to specify the number of indices. Personally I have this little utility method because it speeds things up no end.


public static ByteBuffer getSlice(ByteBuffer bb, int offset, int length) {
    int oldpos = bb.position();
    int oldlim = bb.limit();
    bb.position(oldpos + offset);
    bb.limit(oldpos + offset + length);
    ByteBuffer bb2 = bb.slice();
    bb.limit(oldlim);
    bb.position(oldpos);
    return bb2;
}

Which gives you a new buffer which shares content but has independent position and limit. Which is handy. And you can just replace “ByteBuffer” with FloatBuffer of IntBuffer or any kind of buffer and it’ll work for them too.

So nglDrawElements( p.type , p.indicesCount , GL11.GL_UNSIGNED_INT, MemoryUtil.memAddress( indicesBuffer ) );
Is the same as glDrawElements(p.type, getSlice(indicesBuffer, 0, p.indicesCount));

Go for option 2. I have never seen anyone do option 1. I think the reason (what follows is a lot of guess work, I’d be very happy to be corrected) is that a shader program is not just some executable code, it is all the memory and stuff that goes with it. Essentially all the “heavy lifting” is done in the glLinkProgram() function. That will be where the compiled shader code gets put somewhere where it can process stuff and all the memory associated with the program is allocated and stuff. You don’t want to be doing that often, certainly not several times a frame. As a general rule I find, that mess around with OpenGL objects as little as possible in the game loop. You have to bind them. Do so as few times as possible. Sometimes you have to update data in Buffers. Do so as few times as possible. You get the picture.

In fact, once you have linked the shader program, you can safely delete the shader objects that are attached to it.