# What is the best way to handle the actual render loop?

I've been diving into OpenGL. So far, so good, with quite a few features implemented and objects look good. Now I want to move on to more complicated scene, with many objects.I just want to know how to properly handle rendering a large amount of different types of object.

Here is my code:

void RenderTests::renderModel2(MeshInfo& meshInfo, SArray<Point3>& positions,
CameraView cameraView)
{
bool status = SDLTools::initGL(3, 3);

if(status = false)
{
dout+"OGL init failed!";
dout++;
return;
}

SArray<float>& verts = meshInfo.getVerts();

DirectionalLight directionalLight;
directionalLight.direction = Point3(-0.1f, 1.0f, 0.5f);
directionalLight.ambient = Point3(0.3f, 0.3f, 0.3f);
directionalLight.diffuse = Point3(0.2f, 0.2f, 0.2f);
directionalLight.specular = Point3(0.2f, 0.2f, 0.22f);

PointLight light;
light.position = Point3(0.0f, 0.0f, 1.5f);
light.ambient = Point3(0.2f, 0.2f, 0.2f);
light.diffuse = Point3(0.8f, 0.8f, 0.8f);
light.specular = Point3(0.8f, 0.8f, 0.85f);
light.constant = 0.5f;
light.linear = 0.09f;

Material& material = meshInfo.getMaterial();

Point3 lightAdjustment(0.0f, 0.0f, 0.0f);

SArray<glm::mat4> modelMatrices;
modelMatrices.setSize(positions.length());

for(long i=0; i<positions.length(); i++)
{
glm::mat4 model;
SDLTools::transformModel(model, positions[i], Point3(1.0f, 0.3f, 0.5f),
20.0f * (float)i, 1.0f);
modelMatrices[i] = model;
}

//glEnable(GL_DEPTH_TEST);

GLuint VBO, VAO, modelBuffer;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &modelBuffer);

glBindVertexArray(VAO);

glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, verts.getContentsSize(), verts.getContents(),
GL_STATIC_DRAW);

glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glEnableVertexAttribArray(3);
glEnableVertexAttribArray(4);
glEnableVertexAttribArray(5);
glEnableVertexAttribArray(6);

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat),
(GLvoid*)(3 * sizeof(GLfloat)));
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat),
(GLvoid*)(6 * sizeof(GLfloat)));

glBindBuffer(GL_ARRAY_BUFFER, modelBuffer);
glBufferData(GL_ARRAY_BUFFER, modelMatrices.getContentsSize(),
modelMatrices.getContents(), GL_STATIC_DRAW);

glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, 4*sizeof(glm::vec4), (GLvoid*)0);
glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, 4*sizeof(glm::vec4),
(GLvoid*)(sizeof(glm::vec4)));
glVertexAttribPointer(5, 4, GL_FLOAT, GL_FALSE, 4*sizeof(glm::vec4),
(GLvoid*)(2 * sizeof(glm::vec4)));
glVertexAttribPointer(6, 4, GL_FLOAT, GL_FALSE, 4*sizeof(glm::vec4),
(GLvoid*)(3 * sizeof(glm::vec4)));

glVertexAttribDivisor(3, 1);
glVertexAttribDivisor(4, 1);
glVertexAttribDivisor(5, 1);
glVertexAttribDivisor(6, 1);

glBindVertexArray(0); // Unbind VAO

glEnable(GL_DEPTH_TEST);

bool quit = false;
SDL_Event e;

while(!quit)
{
while(SDL_PollEvent(&e)!=0)
{
if(e.type == SDL_QUIT)
{
quit = true;
}
}

GLuint diffuseMap;
GLuint specularMap;
GLuint normalMap;

GLfloat deltaTime = SDLTools::getFrameTime();

glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, diffuseMap);

glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, specularMap);

glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, normalMap);

glm::mat4 view = SDLTools::lookAt(cameraView);
glm::mat4 projection = SDLTools::perspective(cameraView.fov, 0.1f, 100.0f);

GLint modelLoc = glGetUniformLocation(testShader.program, "model");
GLint viewLoc = glGetUniformLocation(testShader.program, "view");
GLint projLoc = glGetUniformLocation(testShader.program, "projection");
GLint lightPosLoc = glGetUniformLocation(testShader.program, "lightPos");
GLint viewPosLoc = glGetUniformLocation(testShader.program, "ViewPos");

GLint dlightPositionLoc = glGetUniformLocation(testShader.program,
"directionalLight.direction");
GLint dlightAmbientLoc = glGetUniformLocation(testShader.program,
"directionalLight.ambient");
GLint dlightDiffuseLoc = glGetUniformLocation(testShader.program,
"directionalLight.diffuse");
GLint dlightSpecularLoc = glGetUniformLocation(testShader.program,
"directionalLight.specular");

GLint lightPositionLoc = glGetUniformLocation(testShader.program,
"light.position");
GLint lightAmbientLoc = glGetUniformLocation(testShader.program,
"light.ambient");
GLint lightDiffuseLoc = glGetUniformLocation(testShader.program, "light.diffuse");
GLint lightSpecularLoc = glGetUniformLocation(testShader.program,
"light.specular");
GLint lightConstantLoc = glGetUniformLocation(testShader.program,
"light.constant");
GLint lightLinearLoc = glGetUniformLocation(testShader.program, "light.linear");

GLint materialShininessLoc

// Pass the matrices to the shader
glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(projection));
glUniform3f(viewPosLoc, cameraView.position.x,
cameraView.position.y, cameraView.position.z);
glUniform3f(lightPosLoc, light.position.x, light.position.y, light.position.z);

glUniform3f(dlightPositionLoc, directionalLight.direction.x,
directionalLight.direction.y, directionalLight.direction.z);
glUniform3f(dlightAmbientLoc, directionalLight.ambient.x,
directionalLight.ambient.y, directionalLight.ambient.z);
glUniform3f(dlightDiffuseLoc, directionalLight.diffuse.x,
directionalLight.diffuse.y, directionalLight.diffuse.z);
glUniform3f(dlightSpecularLoc, directionalLight.specular.x,
directionalLight.specular.y, directionalLight.specular.z);

glUniform3f(lightAmbientLoc, light.ambient.x, light.ambient.y, light.ambient.z);
glUniform3f(lightDiffuseLoc, light.diffuse.x, light.diffuse.y, light.diffuse.z);
glUniform3f(lightSpecularLoc, light.specular.x, light.specular.y,
light.specular.z);
glUniform1f(lightConstantLoc, light.constant);
glUniform1f(lightLinearLoc, light.linear);

glUniform1f(materialShininessLoc, material.shininess);

glBindVertexArray(VAO);
glDrawArraysInstanced(GL_TRIANGLES, 0, 36, modelMatrices.length());

glBindVertexArray(0);

// Swap the screen buffers
SDLTools::swapBuffers();
}

glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &modelBuffer);

SDLTools::cleanAll();
}


As you can see in renderModel, I just take a bunch of positions, some vertices and a material stored in the meshInfo object and use instancing. It seems to work well enough, at least, for a test. But what happens when I have dozens of objects? What I mean is, should I be rebuilding everything for each object every frame?

Obviously, I can't have the textures for say 50 objects just sitting on the graphics card, for example. What about at the bottom, where I delete all those buffers? Should that be happening every frame, assuming I have a "real" scene which is going to have lots of different geometries, and can't possibly fit? I have heard some about batching, but this would seem to work only with geometry that uses the same textures, while what I am talking about is distinct object types.

I moved the image loading stuff into the loop, and it slows it down, a lot. Should I be deleting these image buffers each frame, as well? The actual image data should be cached, it's not actually pulling it from the disk each frame. It's still quite slow, though.

Here is my code; I want to run through this whole process except, for N object types:

void SDLTools::loadAndGenerateTexture(GLuint& texture, SString fullPath)
{
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

int width, height;
unsigned char* image = TextureUtil::loadImage(width, height, fullPath);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB,
GL_UNSIGNED_BYTE, image);
glGenerateMipmap(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, 0);
}


I just want to know how to properly handle rendering a large amount of different TYPES of object.

Whatever you consider a "type of object," you can usually avoid actually having too many types of things that the renderer needs to know how to process and draw. Fundamentally, for every given draw call, the renderer needs to know:

• what geometry (vertex data) will I draw?
• what textures and shaders will I use?
• what transforms will I use?
• what other graphics state will I set (does this draw need blending, etc)?

You can generally abstract all of that into a single "render descriptor" type of object, at which point the various ways to fill out a render descriptor are how different "object types" control how they appear. It's quite easy to use such a type to draw animated 3D models as well as 2D geometry for UI or sprites, et cetera.

It's also fairly easy to then extend such a type with new fields and capabilities (maybe you add blending later, maybe you support post-processing later, et cetera) rather than create a whole new type of rendering description that duplicates a bunch of the boilerplate setup and buffer management code.

But what happens when I have dozens of objects?

You do the same thing, but dozens of times. This is why wrapping up the process of describing a draw call into a class (for related state and functions) is useful. It makes it easier to do something more than once.

What I mean is, should I be rebuilding everything for each object every frame?

Definitely not. That's really slow.

There are some things you would want to update dynamically. But for the vast majority of things you should load them once, at startup or similar. This includes loading textures from the disk, filling out buffers with static vertex data, and so on.

It is particularly useful to make your textures different objects than your render descriptor, for example. That way your descriptor can just have a pointer or reference to the textures it uses. Similarly with the geometry data. That means you don't duplicate complete sets of geometry and textures just to have another copy of an object.

(Instancing also helps here, but there are still cases where you'd want two different non-instanced sets of geometry to use some shared textures).

Obviously, I can't have the textures for say 50 objects just sitting on the graphics card for example.

You probably could. GPUs have a ton of memory and if you're not going insane with your texture sizes they'll probably fit.

I wouldn't worry about this yet. At some point it will be a problem and you'll want to implement a streaming system that can let you discard textures you're not using and know when to reload them just before you need them again. But don't get bogged down in implementing that before you implement the rest of the basics, it won't be immediately necessary. It will be easier to add after the fact, especially if you do a good job building out your texture and render descriptor abstractions.

What about at the bottom where I delete all those buffers? Should that be happening every frame

No. A good rule of thumb is "don't do it per frame." Anything you do per-frame becomes hot, and a potential bottleneck. Resource creation and destruction is usually expensive, and expensive things are not what you want to be doing in a hot inner loop.

I have heard some about batching, but this would seem to work only with geometry that uses the same textures

Yes. Batching is generally about trying to group together objects with related state so you don't have to change states often while rendering, because state changes can be expensive. Consider this recent question about draw call performance. Batching is not something you need to worry about immediately. It will naturally be a lot easier if you only have type of thing (a render descriptor) to sort or group by.

Also, I moved the image loading stuff into the loop and it slows it down a lot. Should I be deleting these image buffers each frame as well?

I'll leave that as an exercise for the reader ;) What do you think?

• The part you say not to do is the whole point of the question. Sorry but this is a terrible answer and very annoying. I ask for specific details and get absolutely nothing specific, not even in terms of terminology or resources. – Yudrist Jul 15 '16 at 21:47
• @Lunquil, this is a great answer and more specific than most of us get when we set out to build an engine. Josh is just not going to do your work for you, and nor should he, you should actually get writing code and come back when you have specific problems. – user5665 Jul 15 '16 at 21:50
• It's a terrible answer for many reasons. I ask specifically about the render loop for example, and resource management, that is kind of the point. It is partly my fault for how I phrase the question and I put in some easily answered parts. I asked this question specifically to avoid such an answer though...blah blah scene graphs blah blah batches is completely worthless answer. That is the same crap the same guy gave to ANOTHER question about the render loop. I did get some superficial stuff answered but no explanation why. The problem here is answers like this to score points discourage real – Yudrist Jul 19 '16 at 3:00
• answers to in depth questions. At any rate, I will come back to this at some point. Or else answer it myself since I have been pushing right along. But some idea how to deal with some of these issues would have been helpful. – Yudrist Jul 19 '16 at 3:04
• Part of the problem is I don't know how to ask for some details of the question. So it's not all his fault either. – Yudrist Jul 19 '16 at 3:06