PDA

View Full Version : Storing all Mesh data in one VAO



stimulate
10-21-2016, 02:35 PM
Right now, I create a new VAO for every mesh I have. In the vao i store the vertices and indices of that mesh. My textures are just IDs managed by openGL.
When i am about to load a model or texture, i am already checking wheather I have already loaded a mesh/texture with the same file path and then I reuse that ID, but wouldnt it be most efficient to pack everything into one Buffer and then draw everything with just one draw call?
What about the transformation matrices for each model? Would you put them in the buffer aswell and access them in the shader?

Does anyone know something about this?

GClements
10-21-2016, 02:55 PM
Nothing stops you from binding a single buffer to multiple targets. That doesn't necessarily mean that it's a good idea, though.

In particular, if the data will be modified at some point, then there are reasons for separating data based upon when it's modified.

Clearly, you can't draw multiple meshes with a single draw call if their vertex attributes are scattered across different buffers. But coalescing the data into a single set of buffers doesn't help much if you're going to use multiple draw calls anyhow.

In short, it's impossible to come up with a good one-size-fits-all strategy for buffer usage; it needs to be tailored to the problem.

stimulate
10-21-2016, 03:05 PM
I am working on a game engine for example. i dont expect the models to change at all (though i am not 100% sure how to deal with animation yet, i think i will be using matrices on the meshes of body parts) and since i am basically recalculating every position every frame again, i thought it was a good idea to be able to just update one single buffer and then render everything in just one draw call.

I plan my engine to work like this(per frame):

get InputEvents from User >> AI responds to GameEvents of previous Frame >> Skeletons for every entity get animated >> Hitboxes are applied to skeletons >> Physics verify and change skeletons based on hitboxes >> Graphics calculate translation Matrix for every mesh, based on the skeleton it is attached to >> update the mega-VAO with the Matrixes >> send mega-VAO to GPU

i am just noticing that my question is rather: How do i send all matrices to the shader at once?
still work in progress here.. sorry for the confusion

Dark Photon
10-21-2016, 05:25 PM
...
>> Physics verify and change skeletons based on hitboxes
>> Graphics calculate translation Matrix for every mesh, based on the skeleton it is attached to
>> update the mega-VAO with the Matrixes
>> send mega-VAO to GPU

i am just noticing that my question is rather: How do i send all matrices to the shader at once?

Pass it in through a uniform of some sort (e.g. 2D texture, texture buffer, uniform mat4 array, uniform buffer object, etc.).

Regardless of what mechanism you use, what type should you pass in for each joint transform? You could pass in mat4s (aka mat4x4 = 16 floats), but that's a complete waste of a row (the bottom row is always (0,0,0,1)). Reducing that to passing in a mat4x3 is easy cuts you down to 12 floats. If you transpose that and pass in a mat3x4 (still 12 floats), it'll save you a register over a mat4x3 when passing the data in via a uniform array.

However, you can reduce the space even more by not wasting 9 floats on the rotation. 3 or 4 floats is enough for that. Quaternion/translation (QT) gets you down to 7 floats, and dual quaternion (DQ) is more flexible and gets you down to 8 floats.


Also, you're talking about VAOs (Vertex Array Objects (https://www.opengl.org/wiki/Vertex_Specification#Vertex_Array_Object)) as if they contain big blocks of data. They don't. I think you're thinking about Buffer Objects (https://www.opengl.org/wiki/Buffer_Object).

john_connor
10-22-2016, 01:54 AM
i dont expect the models to change at all (though i am not 100% sure how to deal with animation yet, i think i will be using matrices on the meshes of body parts) and since i am basically recalculating every position every frame again, i thought it was a good idea to be able to just update one single buffer and then render everything in just one draw call.

so if you think you will have only static mesh data (for model sub-components), which share the same vertex layout like:
example vertex:


struct Vertex {
vec3 Position;
vec2 TexCoord;
vec3 Normal;
int MaterialIndex;
};

then its a good idea to put all vertices of the mesh sub-components into 1 big buffer, configured by 1 vertex array



How do i send all matrices to the shader at once?

google "instanced rendering"



struct InstancedData {
mat4 ModelMatrix;
int ObjectID;// identifier for each object / item in the scene
};

-- each frame (right before rendering) you re-build a std::vector<InstancedData>
-- put that (dynamic) data into a separate buffer
-- render instanced

example:



// INIT
glGenVertexArrays(1, &vertexarray);
glGenBuffers(1, &vertexbuffer);
glGenBuffers(1, &instancebuffer);

glBindVertexArray(vertexarray);

glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
glVertexAttribPointer(0, 3, GL_FLOAT, false, sizeof(Vertex), (void*)(sizeof(float) * 0));
glVertexAttribPointer(1, 2, GL_FLOAT, false, sizeof(Vertex), (void*)(sizeof(float) * 3));
glVertexAttribPointer(2, 3, GL_FLOAT, false, sizeof(Vertex), (void*)(sizeof(float) * 5));
glVertexAttribIPointer(3, 1, GL_INT, sizeof(Vertex), (void*)(sizeof(float) * 8));
glBindBuffer(GL_ARRAY_BUFFER, 0);

glBindBuffer(GL_ARRAY_BUFFER, instancebuffer);
glVertexAttribPointer(4, 4, GL_FLOAT, false, sizeof(InstancedData), (void*)(sizeof(float) * 0));
glVertexAttribPointer(5, 4, GL_FLOAT, false, sizeof(InstancedData), (void*)(sizeof(float) * 4));
glVertexAttribPointer(6, 4, GL_FLOAT, false, sizeof(InstancedData), (void*)(sizeof(float) * 8));
glVertexAttribPointer(7, 4, GL_FLOAT, false, sizeof(InstancedData), (void*)(sizeof(float) * 12));
glVertexAttribIPointer(8, 1, GL_INT, sizeof(InstancedData), (void*)(sizeof(float) * 16));
glBindBuffer(GL_ARRAY_BUFFER, 0);

glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glEnableVertexAttribArray(3);

glEnableVertexAttribArray(4);
glEnableVertexAttribArray(5);
glEnableVertexAttribArray(6);
glEnableVertexAttribArray(7);
glEnableVertexAttribArray(8);

// sent these attributes only once per instance to the program:
glVertexAttribDivisor(4, 1);
glVertexAttribDivisor(5, 1);
glVertexAttribDivisor(6, 1);
glVertexAttribDivisor(7, 1);
glVertexAttribDivisor(8, 1);

glBindVertexArray(0);

// HERE fill the static mesh data into "vertexbuffer" ...


// RENDER

for each model (or model-component) do this:
---------------------------------------------------------------------------------
std::vector<InstancedData> instances;
// for all models in scene: put instanced data into vector
// then fill the data in the buffer "instancebuffer"
glDrawArraysInstanced(GL_TRIANGLES, offset, count, instances.size());
---------------------------------------------------------------------------------


the vertex shader would look like this:


#version 450 core

layout (location = 0) in vec3 in_position;
layout (location = 1) in vec2 in_texcoord;
layout (location = 2) in vec3 in_normal;
layout (location = 3) in int in_materialindex;

layout (location = 4) in mat4 in_model;
//layout (location = 5) in use ...
//layout (location = 6) in use ...
//layout (location = 7) in use ...
layout (location = 8) in int in_objectID;

out VS_FS_INTERFACE {
vec3 position;
vec2 texcoord;
vec3 normal;
flat int materialindex;
flat int objectID;
} vs_out;


layout (std140, binding = 0) uniform CameraBlock
{
mat4 View;
mat4 Projection;
};


void main(void)
{
mat4 MVP = Projection * View * in_model;
gl_Position = MVP * vec4(in_position, 1);

vs_out.position = (in_model * vec4(in_position, 1)).xyz;
vs_out.texcoord = in_texcoord;
vs_out.normal = (in_model * vec4(in_normal, 0)).xyz;
vs_out.materialindex = in_materialindex;
vs_out.objectID = in_objectID;
}


attribute 4, 5, 6, 7, and 8 are only sent once per instance to the vertex shader


if you have few materials, you can put all the materials into 1 big buffer and bind that buffer to an "uniform block" permanently
if you have many different materials, you can either use a "shader storage buffer" which is not as limited in size as the uniform buffer OR you put the materials into different (uniform) buffers and bind for each model the corresponding material buffer

here is my complete example:
https://sites.google.com/site/john87connor/advanced-opengl-tutorials/tutorial-07-multiple-models

you only need to keep track of the vertex offset / count / primitive type for each model (or model-component)

stimulate
10-22-2016, 10:50 AM
Wow thanks for all this information! I will have to read some more about instancing and about the best way to do it. I am not sure how to implement it in my engine yet, so i will have to learn a little more until i feel comfortable with it and know what i am doing.

As far as I know by now, I load all my mesh data into a VBO at the beginning of my application. this vertex buffer object is static and will not be written to ever again, and it is managed by my one vertex array object. In that same VAO i have another dynamic vertex buffer object (?) containing all model matrices, identified by an ID (modelID). Every frame, this VBO will be updated with my array of matrices (i almost entirely use unordered_multimaps in my engine right now. i would also prefer these over vectors because i was told they were much faster, and its easier to find the matrix of modelID x because i can simply use the id as the map key. is this a good idea?).

Ok, but i kind of missed how you tell openGL where one model in my mesh data VB ends and how i know which one to use for which meshID. I have these two buffers now, and i want to find the correct mesh for my modelID, to then use the matrix on these vertices. But how do I find the right batch of vertices in this whole pile of vertex data?

john_connor
10-22-2016, 11:53 AM
As far as I know by now, I load all my mesh data into a VBO at the beginning of my application. this vertex buffer object is static and will not be written to ever again, and it is managed by my one vertex array object. In that same VAO i have another dynamic vertex buffer object (?) containing all model matrices, identified by an ID (modelID).

correct
the "int objectID" parameter is just for "picking" object:
you want somehow know wheather you clicked on a model (or better: object)
to distinguish all the objects in the scene (of which some can have / share the same model) i give each object a unique integer value
that integer value will be drawn into a separate "layer" in the framebuffer
so the framebuffer contains 2 layers: 1 for the color and 1 for the integer values
if i click anywhere, i use the underlaying int value to identify on which object i clicked

so that int objectID has nothing to do with any model, it's just instanced information sent to th program



Every frame, this VBO will be updated with my array of matrices (i almost entirely use unordered_multimaps in my engine right now. i would also prefer these over vectors because i was told they were much faster, and its easier to find the matrix of modelID x because i can simply use the id as the map key. is this a good idea?).

you have to have an "array" (std::vector is a dynamic sized array)
otherwise the data would be scattered around in the memory (and you couldnt upload the data with 1 glBufferSubData(...) call)



Ok, but i kind of missed how you tell openGL where one model in my mesh data VB ends and how i know which one to use for which meshID. I have these two buffers now, and i want to find the correct mesh for my modelID, to then use the matrix on these vertices. But how do I find the right batch of vertices in this whole pile of vertex data?



struct ModelInstance
{
mat4 ModelMatrix;
int ObjectID;
};


// parameters of "glDrawArrays(...)"
struct DrawCall
{
unsigned int Primitive, Offset, Count;
};


struct Model : public DrawCall
{
// model size
struct {
vec3 Min{ 0, 0, 0 }, Max{ 0, 0, 0 };
float RMax{ 0 };
} Size;

// stores temporarily all the instances, needed for the instancebuffer
std::vector<ModelInstance> Instances;

// ...
};


the "DrawCall" contains the information for 1 drawcall (which is equivalent to 1 model in my example)
the "Model" is itself just a drawcall, with additional infos about the models geometric size (in case you want to do physics / collision detection)
the vector "Instances" is only a kind of temporary storage: it will be filled, cleared and re-filled each frame with the instanced information for that model type (it has beside that nothing to do with that model)


each frame i render all the models in the scene like that:


// create instance buffer data for all models:
for (auto& object : scene.Objects)
if (object.ModelIndex < m_models.size())
m_models[object.ModelIndex].Instances.push_back({ object.ModelMatrix(), (int)object.ID() });


// for each model:
for (auto& model : m_models)
{
// determine instance count:
unsigned int instancecount = model.Instances.size();
if (instancecount >= MAX_MODEL_INSTANCES)
instancecount = MAX_MODEL_INSTANCES;

// upload instance buffer data:
glBindBuffer(GL_UNIFORM_BUFFER, m_instancebuffer);
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(ModelInstance) * instancecount, model.Instances.data());
glBindBuffer(GL_UNIFORM_BUFFER, 0);

// render objects in scene
glDrawArraysInstanced(model.Primitive, model.Offset, model.Count, instancecount);
}


// dont forget to clear the instance data for each model:
for (auto& model : m_models)
{
model.Instances.clear();
model.Instances.reserve(MAX_MODEL_INSTANCES);
}


"m_models" is the array with all my loaded "Model" types
the buffer target "GL_UNIFORM_BUFFER" is a little misleading, its not a uniform buffer but a GL_ARRAY_BUFFER

each "object" in the scene contains a "int ModelIndex":
i use that index to reference the right model in my array of models "m_models"

stimulate
10-23-2016, 04:50 PM
After overhauling half of my engine all day, i think ive almost got it. Unfortunately i am having problems with uploading the model Matrices to the shaders, at least thats my guess because my geometry is scattered all over my screen and changes with my camera. also i checked if it was anything else by uploading the matrices via uniforms as usual.
since i am using direct state access and i had to do it a little differently for my engine than you did, your code samples could not help me find the bug.



struct InstanceData{
glm::mat4 modelMatrix;
//will contain more later
}

glBindVertexArray(StaticModelData::vaoID);
glNamedBufferStorage(StaticModelData::instanceData ID, sizeof(InstanceData)*instanceDatas.size(), &instanceDatas[0], 0);
glVertexArrayVertexBuffer(StaticModelData::vaoID, 1, StaticModelData::instanceDataID, 0, 0);

glVertexArrayAttribBinding(StaticModelData::vaoID, 3, 1);
glVertexArrayAttribFormat(StaticModelData::vaoID, 3, 4, GL_FLOAT, GL_FALSE, offsetof(InstanceData, modelMatrix) + 0);

glVertexArrayAttribBinding(StaticModelData::vaoID, 4, 1);
glVertexArrayAttribFormat(StaticModelData::vaoID, 4, 4, GL_FLOAT, GL_FALSE, offsetof(InstanceData, modelMatrix) + sizeof(float)*4);

glVertexArrayAttribBinding(StaticModelData::vaoID, 5, 1);
glVertexArrayAttribFormat(StaticModelData::vaoID, 5, 4, GL_FLOAT, GL_FALSE, offsetof(InstanceData, modelMatrix) + sizeof(float) * 8);

glVertexArrayAttribBinding(StaticModelData::vaoID, 6, 1);
glVertexArrayAttribFormat(StaticModelData::vaoID, 6, 4, GL_FLOAT, GL_FALSE, offsetof(InstanceData, modelMatrix) + sizeof(float) * 12);

glVertexArrayBindingDivisor(StaticModelData::vaoID , 1, 1);

glBindTexture(GL_TEXTURE_2D, 1); //just for testing, harcoded texture

glEnableVertexArrayAttrib(StaticModelData::vaoID, 0);
glEnableVertexArrayAttrib(StaticModelData::vaoID, 1);
glEnableVertexArrayAttrib(StaticModelData::vaoID, 2);
glEnableVertexArrayAttrib(StaticModelData::vaoID, 3);
glEnableVertexArrayAttrib(StaticModelData::vaoID, 4);
glEnableVertexArrayAttrib(StaticModelData::vaoID, 5);
glEnableVertexArrayAttrib(StaticModelData::vaoID, 6);

glDrawElementsInstanced(GL_TRIANGLES, modelIndexCount, GL_UNSIGNED_INT, (void*)(sizeof(unsigned int)*indexOffset), modelInstancesCount);

at the beginning of my application i load all my models vertices into one vertex buffer and the indices into an element buffer. for every model i save the number of indices and the offset of the indices in the element buffer.

this is how i set my vertex attributes in im shader program


addAttribute("vertexPosition", 0);
addAttribute("vertexUV", 1);
addAttribute("vertexNormal", 2);
addAttribute("modelMatrix1", 3);
addAttribute("modelMatrix2", 4);
addAttribute("modelMatrix3", 5);
addAttribute("modelMatrix4", 6);

please excuse this kind of cheap request for debugging but ive been looking for hours and i would love to still see my instanced models on the screen today.
Thanks

stimulate
10-25-2016, 03:33 PM
finally ive got it working. I am now using the same mesh to draw as many instances of the same object as i want. It is still somewhat slower than i thought and i cant change the textures within the instances yet.
This is my mesh loading code:
glCreateVertexArrays(1, &StaticModelData::vaoID);
glCreateBuffers(1, &StaticModelData::vboID);
glCreateBuffers(1, &StaticModelData::iboID);
glCreateBuffers(1, &StaticModelData::instanceDataID);

glEnableVertexArrayAttrib(StaticModelData::vaoID, 0);
glEnableVertexArrayAttrib(StaticModelData::vaoID, 1);
glEnableVertexArrayAttrib(StaticModelData::vaoID, 2);
glEnableVertexArrayAttrib(StaticModelData::vaoID, 3);
glEnableVertexArrayAttrib(StaticModelData::vaoID, 4);
glEnableVertexArrayAttrib(StaticModelData::vaoID, 5);
glEnableVertexArrayAttrib(StaticModelData::vaoID, 6);

//associate vertexArray with vboID and indexArray with iboID
glNamedBufferStorage(StaticModelData::vboID, sizeof(Vertex)*StaticModelData::vertexNum, &allVert[0], 0);
glNamedBufferStorage(StaticModelData::iboID, sizeof(unsigned int)*StaticModelData::indexNum, &allInd[0], 0);
glVertexArrayElementBuffer(StaticModelData::vaoID, StaticModelData::iboID);
//vaoID vertex attrib array binding location 0 -> vboID
glVertexArrayVertexBuffer(StaticModelData::vaoID, 0, StaticModelData::vboID, 0, sizeof(Vertex));

//vao binding location 0 -> attribute 0

glVertexArrayAttribBinding(StaticModelData::vaoID, 0, 0);
glVertexArrayAttribFormat(StaticModelData::vaoID, 0, 3, GL_FLOAT, GL_FALSE, offsetof(Vertex, position));
glVertexArrayAttribBinding(StaticModelData::vaoID, 1, 0);
glVertexArrayAttribFormat(StaticModelData::vaoID, 1, 2, GL_FLOAT, GL_FALSE, offsetof(Vertex, uv));
glVertexArrayAttribBinding(StaticModelData::vaoID, 2, 0);
glVertexArrayAttribFormat(StaticModelData::vaoID, 2, 3, GL_FLOAT, GL_FALSE, offsetof(Vertex, normal));

glNamedBufferStorage(StaticModelData::instanceData ID, sizeof(InstanceData)*MAX_INSTANCES, nullptr, GL_DYNAMIC_STORAGE_BIT);

glVertexArrayAttribBinding(StaticModelData::vaoID, 3, 1);
glVertexArrayAttribFormat(StaticModelData::vaoID, 3, 4, GL_FLOAT, GL_FALSE, offsetof(InstanceData, modelMatrix));

glVertexArrayAttribBinding(StaticModelData::vaoID, 4, 1);
glVertexArrayAttribFormat(StaticModelData::vaoID, 4, 4, GL_FLOAT, GL_FALSE, offsetof(InstanceData, modelMatrix) + sizeof(float) * 4);

glVertexArrayAttribBinding(StaticModelData::vaoID, 5, 1);
glVertexArrayAttribFormat(StaticModelData::vaoID, 5, 4, GL_FLOAT, GL_FALSE, offsetof(InstanceData, modelMatrix) + sizeof(float) * 8);

glVertexArrayAttribBinding(StaticModelData::vaoID, 6, 1);
glVertexArrayAttribFormat(StaticModelData::vaoID, 6, 4, GL_FLOAT, GL_FALSE, offsetof(InstanceData, modelMatrix) + sizeof(float) * 12);

glVertexArrayAttribBinding(StaticModelData::vaoID, 7, 1);
glVertexArrayAttribFormat(StaticModelData::vaoID, 7, 1, GL_UNSIGNED_INT, GL_FALSE, offsetof(InstanceData, textureUnitIndex));


glVertexArrayVertexBuffer(StaticModelData::vaoID, 1, StaticModelData::instanceDataID, 0, sizeof(InstanceData));
glVertexArrayBindingDivisor(StaticModelData::vaoID , 1, 1);

glEnableVertexArrayAttrib(StaticModelData::vaoID, 0);
glEnableVertexArrayAttrib(StaticModelData::vaoID, 1);
glEnableVertexArrayAttrib(StaticModelData::vaoID, 2);
glEnableVertexArrayAttrib(StaticModelData::vaoID, 3);
glEnableVertexArrayAttrib(StaticModelData::vaoID, 4);
glEnableVertexArrayAttrib(StaticModelData::vaoID, 5);
glEnableVertexArrayAttrib(StaticModelData::vaoID, 6);
glEnableVertexArrayAttrib(StaticModelData::vaoID, 7);

And this is how i render:
glBindVertexArray(StaticModelData::vaoID);
updateShader();
//FOR ALL PRELOADED MODELS
for (auto it = StaticModelData::allModelDatas.begin(); it != StaticModelData::allModelDatas.end(); ++it) {
useGLSLProgram("TriangleShader");
std::vector<InstanceData> instanceDatas;
//FOR ALL INSTANCES OF THIS MODEL
for (std::vector<unsigned int>::iterator id = it->second.instances.begin(); id != it->second.instances.end(); ++id) {
//GET LOCATION OF THIS INSTANCE
std::unordered_multimap<GLuint, Location*>::iterator loc = Component::locations.find(*id);
//IF THERE IS A LOCATION FOR THIS INSTANCE
if (loc != Component::locations.end()) {
InstanceData instData;
loc->second->updateMatrix();
instData.modelMatrix = loc->second->modelMatrix;

//FIND THE TEXTURE THIS INSTANCE WANTS
std::unordered_multimap<GLuint, Texture*>::iterator tex = Component::textures.find(*id);
//IF THERE IS A TEXTURE FOR THIS INSTANCE
if (tex != Component::textures.end()) {
//FIND INSTANCE TEXTURE IN ALL TEXTURES
std::unordered_multimap<std::string, GLuint>::iterator texData = StaticModelData::allTextures.find(tex->second->texturePath);
if (texData != StaticModelData::allTextures.end()) {
//ACTIVATE THAT TEXTURE AND ADD ITS ID TO INSTANCEDATAS
int distance = std::distance(it->second.instances.begin(), id);
glActiveTexture(GL_TEXTURE0 + distance);
glBindTexture(GL_TEXTURE_2D, texData->second);
instData.textureUnitIndex = GL_TEXTURE0+distance;
}
}

instanceDatas.push_back(instData);
}
}


glNamedBufferSubData(StaticModelData::instanceData ID, 0, sizeof(InstanceData)*(instanceDatas.size()), &instanceDatas[0]);
glVertexArrayBindingDivisor(StaticModelData::vaoID , 1, 1);



glDrawElementsInstanced(GL_TRIANGLES, it->second.modelIndexNum, GL_UNSIGNED_INT, (void*)(sizeof(int)*(it->second.indexOffset)), it->second.instances.size());
glBindTexture(GL_TEXTURE_2D, 0);
glActiveTexture(0);

if (renderNormals) {
useGLSLProgram("NormalShader");

glDrawElementsInstanced(GL_TRIANGLES, it->second.modelIndexNum, GL_UNSIGNED_INT, (void*)(sizeof(unsigned int)*it->second.indexOffset), it->second.instances.size());

}

glInvalidateBufferData(StaticModelData::instanceDa taID);
glNamedBufferSubData(StaticModelData::instanceData ID, 0, sizeof(InstanceData)*(instanceDatas.size()), nullptr);
}
glBindVertexArray(0);


It seems like this is still a little unefficient. as i reach ~50 instances my framerate starts to drop and when i am at ~70 instances i get 15 FPS... any way to optimize this?
i will keep on working on this and i will try to figure out how to send an array of samplers to my shaders for multitexturing.
But you guys helped me plenty already and the main reason for this post is that didnt want to let my last post stand like that and for archive, this one might help people in the future ;)

john_connor
10-26-2016, 02:09 AM
I am now using the same mesh to draw as many instances of the same object as i want. It is still somewhat slower than i thought and i cant change the textures within the instances yet.

thats the purpose of instanced rendering
i thing it is slower because you have many gl-functions between 2 draw calls, like binding another texture etc.
you could sent an "int override_texture" as instanced info:
-- if -1, use the regular texture
-- else use that int as index in your texture array



It seems like this is still a little unefficient. as i reach ~50 instances my framerate starts to drop and when i am at ~70 instances i get 15 FPS... any way to optimize this?

removing texture binding between 2 draw calls maybe ?

useGLSLProgram("TriangleShader");
yuo only need to activate the program once, like your vertex array



i will keep on working on this and i will try to figure out how to send an array of samplers to my shaders for multitexturing.

in your program:
replace "sampler2D" with "sampler2DArray"
--> now you have to pass a "vec3" instead of a "vec2" as texture coordinates, the 3rd component is the texture index in that array
--> the texture index can be part of the "Material" struct
--> to avoid flipping between texture and no-texture mode, just mix both by multiplying the no-texture-color with the texture

vec4 resultingcolor = texture(mytex, vec3(texcoords, textureindex)) * color;

you have to make sure that:
-- if texture available, set no-texture-color to blank white vec4(1, 1, 1, 1)
-- if texture NOT available, set the texture index to a blank white texture layer in your texture array

thats how i biuld my texture array:
(you have to make sure that all your textures are of the same resolution, thats a limitation of array textures)


// setup textures
// ----------------------------------------------------------------------------------------------------------
unsigned int mipmapcount = 1;
unsigned int texturewidth = 1024;
unsigned int textureheight = 1024;
unsigned int numberoflayers = texturesdiffuse.size();

struct Pixel { unsigned char r{ 255 }, g{ 255 }, b{ 255 }, a{ 255 }; };
std::vector<Pixel> whitetexture(texturewidth * textureheight);

glBindTexture(GL_TEXTURE_2D_ARRAY, m_model.textureKd);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, /*GL_CLAMP_TO_EDGE*/GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, /*GL_CLAMP_TO_EDGE*/GL_REPEAT);
// allocate texture memory:
glTexStorage3D(GL_TEXTURE_2D_ARRAY, mipmapcount, GL_RGBA8, texturewidth, textureheight, numberoflayers);

// load all textures Kd:
// there is 1 restriction: all textures MUST be of resolution "texturewidth" x "textureheight"
for (unsigned int i = 0; i < texturesdiffuse.size(); i++)
{
// this c++ class to load images was written by Benjamin Kalytta, http://www.kalytta.com/bitmap.h
CBitmap bmp;
void* texturedata = whitetexture.data();
if (bmp.Load((texturesdiffuse[i]).c_str()))
if (bmp.GetWidth() == texturewidth && bmp.GetHeight() == textureheight)
texturedata = bmp.GetBits();

glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, i, texturewidth, textureheight, 1, GL_RGBA, GL_UNSIGNED_BYTE, texturedata);
}

glBindTexture(GL_TEXTURE_2D_ARRAY, 0);

// bind diffuse texture to texture unit 1:
glBindTextureUnit(1, m_model.textureKd);
glProgramUniform1i(m_model.program, glGetUniformLocation(m_model.program, "textureKd"), 1);
// ----------------------------------------------------------------------------------------------------------


you then never need to re-bind the texture again, bind it once, set the program sampler variable once, thats all
but if you have another textures, use another texture units for those (not "1" as i did here), an "enum" would be useful


optimization:
-- use "LOD"s for all your meshes, the easiest way is to generate separate meshes
-- https://en.wikipedia.org/wiki/Level_of_detail
-- google for "meshlab", a softawre to simplify meshes with just a few clicks

-- render those meshes which are nearest to the camera first (to use the depth-test)
-- dont render meshes which are behind the camera (or outside the view frustum)

-- use multiple mipmaps fo your textures (and call glGenerateMipMaps(...) after setting up the texture array)

-- use a framebuffer with lower resolution