glBufferSubData and glDrawArrays confusion

Started by Joona, February 02, 2016, 19:16:32

Previous topic - Next topic

Joona

Hello. I'm pretty new to OpenGL and 3d-programming in general. Please bear with me :)

I have some confusion about glBufferSubData and glDrawArrays. Let's say I have a mesh (uploaded via glBufferData) and a vao, I can draw that mesh multiple times by calling glDrawArrays multiple times, right? Until now I've been in the belief that I could do that same with glBufferSubData ("Batching"). However, whenever the buffer goes full and I flush the data to GPU, all previously written data seem to be overwritten.

Is this what is supposed to happen and have I just misunderstood of how things work?

My batching code:
public BatchTest()
{	
	//max instances tells how many times I can draw before the buffer going full and a forced flush
	int maxInstances	= 2;
	int triCompAmt 		= 3;
	triSizeBytes 		= triCompAmt * 3 * Float.BYTES;
	
	vao = new VertexArrayObject();
	vbo = new VertexBufferObject();
	
	vao.bind();
	vbo.bind(GL15.GL_ARRAY_BUFFER);
	
	buf = BufferUtils.createByteBuffer(triSizeBytes * maxInstances);

	GL15.glBufferData(GL15.GL_ARRAY_BUFFER, buf.capacity(), GL15.GL_DYNAMIC_DRAW);
	
	GL20.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 0, 0);
	GL20.glEnableVertexAttribArray(0);
	
	vao.unbind();
}

public void draw(Vec3f p0)
{		
	//if buffer is full, flush it
	if (buf.remaining() < triSizeBytes)
	{
		System.out.println("flush");
		render();
	}
	
	//draw a triangle to the coordinates p0
	buf.putFloat(p0.getX()	  ).putFloat(p0.getY() + 1).putFloat(p0.getZ());
	buf.putFloat(p0.getX() - 1).putFloat(p0.getY() - 1).putFloat(p0.getZ());
	buf.putFloat(p0.getX() + 1).putFloat(p0.getY() - 1).putFloat(p0.getZ());
	
	vertices += 3;
}

public void render()
{	
	if (vertices == 0)
		return;
	
	vbo.bind();
	buf.flip();
	GL15.glBufferSubData(GL15.GL_ARRAY_BUFFER, 0, buf);
	
	vao.bind();
	GL11.glDrawArrays(GL11.GL_TRIANGLES, 0, vertices);
	vao.unbind();
	
	vertices = 0;
	buf.clear();
	
	checkDrawArraysErrors();
}


"Main" class:
@Override
public void tick(float delta) throws Exception
{
	int size = 2;
	for (int i = 0; i < size; i++)
		batch.draw(new Vec3f(i, 0, 0));
		
}

@Override
public void render() throws Exception
{
	window.cls();
	
	shader.bind();

	shader.mat4fv("projection", true, Camera.getMain().getProjection());
	shader.mat4fv("view", 		true, Camera.getMain().getTransform());

	transform.setTranslation(0, 0, 0);
	shader.mat4fv("model", 		true, transform.transformModel());
	
	batch.render();

	shader.unbind();
}


In this particular code, drawing "maxInstances" number of triangles works just fine, but whenever I draw more, only the last "maxInstances" are drawn. Could you please tell me what I'm doing wrong?

Thank you :)

Cornix

These two things are completely different. Any method that has "draw" in its name (like glDrawArrays, glDrawElements, glDraw...) is used to actually render stuff. Without a call to one of the "draw" methods you wont get any kind of output. (except for deprecated functionality like immediate mode I guess)

The glBufferData and glBufferSubData methods are used to move data from client side to server side. Client side being your application and server side being the OpenGL driver and/or hardware. You use glBufferData to transfer data and then you call glDrawXXX to render something with the data you have previously transfered.

Joona

Thanks for your reply!

QuoteThe glBufferData and glBufferSubData methods are used to move data from client side to server side. --- and then you call glDrawXXX to render something with the data you have previously transfered.
Yes, this is how I've understood it. But to my knowledge that's exactly what I'm doing in the code.

Here's what's happening:
1. I call draw() n times and put the positional data to the bytebuffer
  1b. if the buffer is full, I render
2. at the end of the loop, I render. Rendering consists of flipping the bytebuffer, uploadin with glBufferSubData, drawing with glDrawArrays and clearing the buffer.

But in the case of 1b the previous data is gone, and only the latest glDrawArrays call seems to work.

Kai

OpenGL does not remember what you drew in the past after a glClear() or swapbuffers.
You have to draw everything again every frame.
So you must keep everything in one or multiple OpenGL buffer objects and draw them.
And you do realize that you overwrite all data in your ByteBuffer and eventually in the OpenGL buffer object on render(), so that you can fill new data in draw()?

Joona

QuoteOpenGL does not remember what you drew in the past after a glClear() or swapbuffers.
Derp you're absolutely right... I did part of my drawing in tick(), so of course glClear is called after that. Moreover, whenever I did drawing of course the shader hadn't been bound in tick(). Thanks :)

If I move the drawing for-loop to where I bind the shader, it works perfectly.

Kai

Glad it works now. :)

By the way, why do you actually need this kind of delaying/batching/buffering?
I mean, in any given frame, don't you know prior to rendering what you want to render?
Or is it interactive user input during runtime (mouse movement, keyboard input, joystick input) determining which vertices to render?
Otherwise your solution seems a bit too overcomplicated and if all you do is rendering a static model then you can just create an exact large OpenGL buffer object and fill it up with the model vertices.
If you think creating large OpenGL buffer objects might be an issue so that you need to split it up by buffering on the Java side, then actually no. Even old graphics cards support creating quite large OpenGL buffer objects (in the millions of vertices).

Joona

It's interactive based on the camera position. Basically I "project" the view frustum on the z-plane (for example) to determine a rectangular area that is visible to the player. Then I iterate over that area in a nested for loop and draw only those "tiles". The idea is to have varying (but simple) tile-like 3d data (viewed from above).

I realize that for example glDrawArrays isn't ideal for that, because it can render only one type of data. But if I use a renderer for each different mesh then maybe it could work.

Basically I don't know enough to make it in a better way (glMultiDrawArraysIndirect for example). At this point I just want to get it to work :)

Edit.
By the way, I once tried this same method but with instanced rendering. And it didn't work. I wasn't sure if instanced rendering somehow didn't work with batching. Of course I don't want to send all the mesh data to the GPU when I could upload it once and only update the position. Now I'm wondering if I just made the same mistake then. I have to try that again :)

If there indeed is some underlying cause preventing this it would be nice to hear a logical explanation.

Kai

So you do frustum culling.

Note that in most of the cases it is actually faster to just cull your geometry using simple geometric primitives that act as bounding volumes, such as spheres or axis-aligned boxes and doing cheap frustum/sphere or frustum/AABB tests and render the complete model if it is partly visible, instead of culling against your actual scene geometry/triangles and stream-uploading them each frame to a VBO.
Sending a built VBO off to be rendered with OpenGL is very very fast and cheap, unless of course you have a very very instruction- or bandwidth-heavy shader.

What is expensive is doing actual uploading of the data and making many OpenGL calls.
Uploading everything visible every frame is for sure the slowest way to do it.

Joona

Yeah that sounds logical. Thanks for the tip :)

I definitely need to try to implement that. Well, you'll probably understand that I want to try to get it working the way I'm doing it too :) For science (=learning)