Batch Rendering

So I was being stoic for the time and skipping out on batch rendering, but it lead me to a question…

	public static void render3DElement(Entity element, Mat4 projection, Mat4 camera, Light light) {
		GL11.glEnable(GL11.GL_CULL_FACE);
		GL11.glCullFace(GL11.GL_BACK); //ignore this gross testing artifact
		final ShaderModel shader_model = element.getShaderModel();
		final Model model = element.getModel();
		final Material[] materials = element.getMaterials();
		GL30.glBindVertexArray(model.getVao());
			GL20.glUseProgram(shader_model.getShader().getProgram());
				shader_model.bindMaterials(materials);
				shader_model.updateMatrix(projection, element.getMatrix(), camera);
				shader_model.updateVector3(light.getPosition(), light.getColor());
				GL11.glDrawElements(GL11.GL_TRIANGLES, model.getVertexCount(), GL11.GL_UNSIGNED_INT, 0);
			GL20.glUseProgram(0);
		GL30.glBindVertexArray(0);
	}

So the above code binds a bunch of things, but I need to iterate over a list of spacial positions and their differentiations inside of Entity element. Although each entity isn’t bound to different textures, they are bound to different matrices and such. So my question is…

GL20.glUseProgram(shader_model.getShader().getProgram());
shader_model.bindMaterials(materials);
shader_model.updateVector3(light.getPosition(), light.getColor());
for each entity... {
shader_model.updateMatrix(projection, next.getMatrix(), camera);
GL11.glDrawElements(GL11.GL_TRIANGLES, next.getVertexCount(), GL11.GL_UNSIGNED_INT, 0);
}
GL20.glUseProgram(0); 

or…

for each entity... {
GL20.glUseProgram(shader_model.getShader().getProgram());
shader_model.bindMaterials(materials);
shader_model.updateVector3(light.getPosition(), light.getColor());
shader_model.updateMatrix(projection, next.getMatrix(), camera);
GL11.glDrawElements(GL11.GL_TRIANGLES, next.getVertexCount(), GL11.GL_UNSIGNED_INT, 0);
GL20.glUseProgram(0); 
}

I can see the reliability and flexibility, but I know I am missing something.

Thanks for the help JGO <3

The first version. You want to minimise OpenGL state changes, esp. for:

  • render targets
  • shader programs
  • texture bindings

The GL driver may be able to cache some state and optimise on its own, but I wouldn’t count on it.

Ideally, you pre-sort your entities by shader program first, and material second. Then, you just:

for each shader {
glUseProgram(shader);
for each material {
shader.bindMaterial(material);
for each entity using (shader, material) {
entity.bindVao();
shader.updateUniforms();
glDrawElements(…);
}
}
}

Now, if entities can reference/own multiple materials like your sample code suggests, this won’t work as straightforward. In this case it may be helpful to manually cache use of materials in “bindMaterials()” and only update if there are changes. Or, use some kind of “material group” for pre-sorting entities.

I can’t really rely on what you said because in the code you bind the vao more than once. Each object that is created in my engine doesn’t need materials to be gone through as well as textures. Each entity that includes a model reference which holds vao and stuff contains spacial positions which I use to draw and the models and Mattel’s aren’t subject to change from entity to entity, because that’s the nature of that. I agree with you about minimalistic calls.

I’m just worried if binding a shader had big effects on draw time. I don’t want artifacts.

Did you profile?

The rule of thumb is to change openGL states as little as possible. I really wouldn’t implement either solution as glDrawElements() is a bottleneck.

I would keep what you have and then create a batcher class with a big vao that lets you map multiple entities to it, this way you can map materials, matrices, and vertices to a buffer and then upload it in one go. Then you can bind your buffers and draw.

Obviously you can do this a bunch of different ways, but something like this works for me: (using jogl)


	private void renderCurrentMeshes(GL4 gl, GLU glu, ArrayList<IMesh> meshes)
	{
		if(m_ibo == null) return;
		gl.glPolygonMode(m_face, m_mode);
		for(int i = 0; i < m_enable.size(); i++) { gl.glEnable(m_enable.get(i).intValue()); }
		for(int i = 0; i < m_disable.size(); i++) { gl.glDisable(m_disable.get(i).intValue()); }
		gl.glBlendFunc(GL4.GL_SRC_ALPHA, GL4.GL_ONE_MINUS_SRC_ALPHA);
		
		m_shader.enable(gl);
		bindTextures(gl);
		map(gl, meshes);

		Matrix4f projection_mat = m_camera.getProjectionMatrix();
		float[] projection_array = projection_mat.get(new float[16], 0);
		Matrix4f view_mat = new Matrix4f().identity();
		float[] view_array = view_mat.get(new float[16], 0);

		m_ubo.bind(gl, m_shader.getUniformBlockLocation("model_matrix_buff"), UniformBuffer.MODEL_INDEX, m_shader.getHandle());
		m_vao.bind(gl);
		m_ibo.bind(gl);
		m_shader.setUniformMatrix4f(gl, false, "projection_matrix", projection_array);
		m_shader.setUniformMatrix4f(gl, false, "view_matrix", view_array);
		gl.glDrawElements(GL4.GL_TRIANGLES, m_vert_count, GL4.GL_UNSIGNED_INT, 0);
		m_ibo.unbind(gl);
		m_vao.unbind(gl);
		m_ubo.unbind(gl);
		m_shader.disable(gl);
	}

there are even some optimizations I could do, like combining the projection and view matrices.