Quite high framerate when rendering 50k rectangles

Hey i’ve written a little 2d-renderer that updates every object using glMapBufferRange() and i have positions and colors in separate VBO’s but i render evertying with one call to glDrawElements.

however my framrate is at about 83 miliseconds when i render 50K rectangles.

The bottleneck is obviously that i iterate over all 50k objects in a for-loop each frame, but what if i wanted to update 50k objects of different sizes and textures but still keep the framrate low, how would one go about to get that performance?

is there someother way to render a lot more rectangles for a 2D game?

I thought updating vertices with glMapBufferRange and using one glDrawElements was supposed to be faster then using uniforms and one drawcall per transform-update.

VertexShader.glgl:

#version 330 core

layout (location = 0) in vec4 inPos;
layout (location = 3) in vec4 inCol;

out vec4 colorVS;

uniform vec2 windowDimensions;


void main()
{

	vec4 vertexPosition;
	vertexPosition.xyz = inPos.xyz;
	vertexPosition.x = (2 * vertexPosition.x / windowDimensions.x) - 1.0f; 
	vertexPosition.y =  1.0f - (2 * vertexPosition.y / windowDimensions.y);
	vertexPosition.z  = 0.0f;
	vertexPosition.w  = 1.0f;

	gl_Position = vertexPosition;

	colorVS = inCol;
}

FragmentShader.glgl:

#version 330 core

in vec4 colorVS;
out vec4 fragmentColor;

uniform vec4 iColorData;

void main()
{
	
	vec4 result_Color = colorVS;

	//result_Color.r /= iColorData.r; 
	//result_Color.g /= iColorData.g; 
	//result_Color.b /= iColorData.b; 
	//result_Color.a /= iColorData.a; 

	fragmentColor = result_Color;

}

Main.cpp:

#include "glad\glad.c"

#include "GLFW\glfw3.h"

#include "glm\glm.hpp"
#include "glm\gtc\matrix_transform.hpp"
#include "glm\gtc\type_ptr.hpp"

#include <iostream>
#include <time.h>

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

#include <CJGL_Input.h>

#define SCR_WIDTH 1920
#define SCR_HEIGHT 1080 

#define NUMOBJECTS 50000 

GLFWwindow* windowSetup(int width, int height);

char* loadShaderFile(char* filePath);

GLenum err;
bool running = true;

struct Time
{
	double startTime;
	double endTime;
	double dt_s;
	double dt_ms;

	double getMiliSeconds()
	{
		return dt_ms;
	}
	double getSeconds()
	{
		return dt_s;
	}

	void start()
	{
		dt_s = startTime - endTime;
		dt_ms = dt_s * 1000.0f;
		endTime = startTime;
		startTime = glfwGetTime();
	}
};

struct GameObject
{
	float x;
	float y;
	float width;
	float height;
	float r, g, b, a;

	float vertices[28];
	float vertex_Positions[16];
	float vertex_Colors[16];
	float vertex_TextureCoords[8];
	
	int bufferIndex;

	int positionVBO;
	int colorVBO;
	int textureVBO;

	float *pPosition;
	float *pColor;

	float getX()
	{
		return x;
	}
	float getY()
	{
		return y;
	}
	float getWidth()
	{
		return width;
	}
	float getHeight()
	{
		return height;
	}

	void setPositionVBO(unsigned int in_VBO)
	{
		positionVBO = in_VBO;
	}

	void setColorVBO(unsigned int in_VBO)
	{
		colorVBO = in_VBO;
	}

	void setTextureVBO(unsigned int in_VBO)
	{
		textureVBO = in_VBO;
	}

	void bindPositionVBO()
	{

		glBindBuffer(GL_ARRAY_BUFFER, positionVBO);
	}
	void bindColorVBO()
	{

		glBindBuffer(GL_ARRAY_BUFFER, colorVBO);
	}
	void bindTextureVBO()
	{

		glBindBuffer(GL_ARRAY_BUFFER, textureVBO);
	}

	void init(float in_x, float in_y, float in_width, float in_height)
	{

		pPosition = (float*)glMapBufferRange(GL_ARRAY_BUFFER, bufferIndex * sizeof(vertex_Positions), sizeof(vertex_Positions), GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);

		x 	= in_x;
		y 	= in_y;
		width 	= in_width;
		height 	= in_height;
		//color	= in_color;

		pPosition[0]   = x;	
		pPosition[4]   = x;	
		pPosition[8]   = x + width;	
		pPosition[12]  = x + width;	

		pPosition[1]  = y;	
		pPosition[5]  = y + height;	
		pPosition[9]  = y + height;	
		pPosition[13] = y;	
		glUnmapBuffer(GL_ARRAY_BUFFER);
	}

	void setX(float in_x)
	{
		x = in_x;

		pPosition = (float*)glMapBufferRange(GL_ARRAY_BUFFER, bufferIndex * sizeof(vertex_Positions), sizeof(vertex_Positions), GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
		pPosition[0]  = x;	
		pPosition[4]  = x;	
		pPosition[8]  = x + width;	
		pPosition[12] = x + width;	

		pPosition[1]  = y;	
		pPosition[5]  = y + height;	
		pPosition[9]  = y + height;	
		pPosition[13] = y;	
		glUnmapBuffer(GL_ARRAY_BUFFER);
	}

	void setY(float in_y)
	{
		y = in_y;

		pPosition = (float*)glMapBufferRange(GL_ARRAY_BUFFER, bufferIndex * sizeof(vertex_Positions), sizeof(vertex_Positions), GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);

		pPosition[0]   = x;	
		pPosition[4]   = x;	
		pPosition[8]   = x + width;	
		pPosition[12]  = x + width;	

		pPosition[1]  = y;	
		pPosition[5]  = y + height;	
		pPosition[9]  = y + height;	
		pPosition[13] = y;	

		glUnmapBuffer(GL_ARRAY_BUFFER);

	}

	void setWidth(float in_width)
	{
		width  = in_width;

		pPosition = (float*)glMapBufferRange(GL_ARRAY_BUFFER, bufferIndex * sizeof(vertex_Positions), sizeof(vertex_Positions), GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);

		pPosition[0]   = x;	
		pPosition[4]   = x;	
		pPosition[8]   = x + width;	
		pPosition[12]  = x + width;	

		pPosition[1]  = y;	
		pPosition[5]  = y + height;	
		pPosition[9]  = y + height;	
		pPosition[13] = y;	

		glUnmapBuffer(GL_ARRAY_BUFFER);
	}

	void setHeight(float in_height)
	{
		height = in_height;

		pPosition = (float*)glMapBufferRange(GL_ARRAY_BUFFER, bufferIndex * sizeof(vertex_Positions), sizeof(vertex_Positions), GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);

		pPosition[0]   = x;	
		pPosition[4]   = x;	
		pPosition[8]   = x + width;	
		pPosition[12]  = x + width;	

		pPosition[1]  = y;	
		pPosition[5]  = y + height;	
		pPosition[9]  = y + height;	
		pPosition[13] = y;	

		glUnmapBuffer(GL_ARRAY_BUFFER);
	}

	void setPosition( float in_x, float in_y)
	{
		x = in_x;
		y = in_y;
		pPosition[0] = in_x;	
		pPosition[4] = in_x;	
		pPosition[8] = in_x + width;	
		pPosition[12] = in_x + width;	

		pPosition[1]  = in_y;	
		pPosition[5]  = in_y;	
		pPosition[9]  = in_y + height;	
		pPosition[13] = in_y + height;	
	}

	void setDimensions(float in_width, float in_height)
	{
		width  = in_width;
		height = in_height;
		pPosition[8]   = x + in_width;	
		pPosition[12]  = x + in_width;	

		pPosition[5] = y + in_height;	
		pPosition[9] = y + in_height;	
	}

	
	 void setSolidColor(float in_r, float in_g, float in_b, float in_a)
	 {
		 r = in_r;
		 g = in_g;
		 b = in_b;
		 a = in_a;

		pColor = (float*)glMapBufferRange(GL_ARRAY_BUFFER, bufferIndex * sizeof(vertex_Colors), sizeof(vertex_Colors), GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
		pColor[0]  = r;	pColor[1]  = g;	pColor[2]  = b;	pColor[3]  = a;
		pColor[4]  = r;	pColor[5]  = g;	pColor[6]  = b;	pColor[7]  = a;
		pColor[8]  = r;	pColor[9]  = g;	pColor[10] = b;	pColor[11] = a;
		pColor[12] = r;	pColor[13] = g;	pColor[14] = b;	pColor[15] = a;
		glUnmapBuffer(GL_ARRAY_BUFFER);

	 }

	void setColorBottomLeft(float r, float g, float b, float a)
	{
		pColor = (float*)glMapBufferRange(GL_ARRAY_BUFFER, bufferIndex * sizeof(vertex_Colors), 4 * sizeof(float), GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
		pColor[0] = r;
		pColor[1] = g;
		pColor[2] = b;
		pColor[3] = a;
		glUnmapBuffer(GL_ARRAY_BUFFER);
	}

	void setColorTopLeft(float r, float g, float b, float a)
	{
		pColor = (float*)glMapBufferRange(GL_ARRAY_BUFFER, bufferIndex * sizeof(vertex_Colors) + 4 * sizeof(float), 4 * sizeof(float), GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
		pColor[0] = r;
		pColor[1] = g;
		pColor[2] = b;
		pColor[3] = a;
		glUnmapBuffer(GL_ARRAY_BUFFER);
	}

	void setColorTopRight(float r, float g, float b, float a)
	{
		pColor = (float*)glMapBufferRange(GL_ARRAY_BUFFER, bufferIndex * sizeof(vertex_Colors) + 8 * sizeof(float), 4 * sizeof(float), GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
		pColor[0] = r;
		pColor[1] = g;
		pColor[2] = b;
		pColor[3] = a;
		glUnmapBuffer(GL_ARRAY_BUFFER);
	}

	void setColorBottomRight(float r, float g, float b, float a)
	{
		pColor = (float*)glMapBufferRange(GL_ARRAY_BUFFER, bufferIndex * sizeof(vertex_Colors) +  12 * sizeof(float), 4 * sizeof(float), GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
		pColor[0] = r;
		pColor[1] = g;
		pColor[2] = b;
		pColor[3] = a;
		glUnmapBuffer(GL_ARRAY_BUFFER);
	}

};

struct GameBufferObject
{

	unsigned int positionVBO = 0;
	unsigned int colorVBO = 0;
	unsigned int textureVBO = 0;
 	unsigned int IBO = 0;
 	unsigned int VAO = 0;
 
	void genVertexArray()
	{
		glGenVertexArrays(1, &VAO);
	}

	void genBuffers()
	{
		glGenBuffers(1, &positionVBO);
		glGenBuffers(1, &colorVBO);
		glGenBuffers(1, &textureVBO);
	}

	void bindVAO()
	{
		glBindVertexArray(VAO);
	}

	void bindPositionVBO()
	{

		glBindBuffer(GL_ARRAY_BUFFER, positionVBO);
	}
	void bindColorVBO()
	{

		glBindBuffer(GL_ARRAY_BUFFER, colorVBO);
	}
	void bindTextureVBO()
	{

		glBindBuffer(GL_ARRAY_BUFFER, textureVBO);
	}

	void bindIBO()
	{

		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO);
	}
};

struct GameBuffer
{
	float vData[16]; 
};


int main()
{

	srand(time(NULL));

	GLFWwindow *window = windowSetup(SCR_WIDTH, SCR_HEIGHT);


	char *shaderTextBuffer = loadShaderFile("vertexShader.glsl");
	char *fragmentTextBuffer = loadShaderFile("fragmentShader.glsl");

	unsigned int VS = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(VS, 1, &shaderTextBuffer, 0);
	glCompileShader(VS);

	int success;
	char infoLog[1080];
	glGetShaderiv(VS, GL_COMPILE_STATUS, &success);
	if(!success)
	{
		glGetShaderInfoLog(VS, 1080, 0, infoLog);
		printf("VERTEX_SHADER: %s\n", infoLog);
	}

	unsigned int FS = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(FS, 1, &fragmentTextBuffer, 0);
	glCompileShader(FS);

	glGetShaderiv(FS, GL_COMPILE_STATUS, &success);
	if(!success)
	{
		glGetShaderInfoLog(FS, 1080, 0, infoLog);
		printf("FRAGMENT_SHADER: %s\n", infoLog);
	}


	unsigned int shaderProgram = glCreateProgram();
	glAttachShader(shaderProgram, VS);
	glAttachShader(shaderProgram, FS);
	glLinkProgram(shaderProgram);
	

	glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
	if(!success)
	{
		glGetProgramInfoLog(shaderProgram, 1080, 0, infoLog);
		printf("LINK_PROGRAM: %s\n", infoLog);
	}


	

	/*
		int indices[] =
		{
			0, 1,
			1, 2,
			2, 3,
			3, 0,

			4, 5,
			5, 6,
			6, 7,
			7, 4,

			8,  9 ,
			9,  10,
			10, 11,
			11, 8
		};
	*/
	/*
		int indices[] =
		{
			0, 1, 2, 2, 3, 0,	4, 5, 6, 6, 7, 4
		};
	*/


 	int gameObjIndices[NUMOBJECTS * 6] = {};

	for(int i = 0; i < NUMOBJECTS; i++)
	{
		gameObjIndices[0 + i * 6] = i * 4;
		gameObjIndices[1 + i * 6] = i * 4 + 1;
		gameObjIndices[2 + i * 6] = i * 4 + 2;
		gameObjIndices[3 + i * 6] = i * 4 + 2;
		gameObjIndices[4 + i * 6] = i * 4 + 3;
		gameObjIndices[5 + i * 6] = i * 4;
	}
	

	int boundingBoxIndices[8] = {};

	for(int i = 0; i < 1; i++)
	{
		boundingBoxIndices[0 + i * 8] = i * 4;
		boundingBoxIndices[1 + i * 8] = i * 4 + 1;
		boundingBoxIndices[2 + i * 8] = i * 4 + 1;
		boundingBoxIndices[3 + i * 8] = i * 4 + 2;
		boundingBoxIndices[4 + i * 8] = i * 4 + 2;
		boundingBoxIndices[5 + i * 8] = i * 4 + 3;
		boundingBoxIndices[6 + i * 8] = i * 4 + 3;
		boundingBoxIndices[7 + i * 8] = i * 4;
	}

	GameObject gameObj[NUMOBJECTS] = {};
	GameBuffer gameObjPosBuffer[NUMOBJECTS] = {};
	GameBuffer gameObjColBuffer[NUMOBJECTS] = {};
	for(int i = 0; i < NUMOBJECTS; i++)
	{
		gameObj[i].bufferIndex = i;
	}

	GameBufferObject gameObjBuffObject = {};
	glGenVertexArrays(1, &gameObjBuffObject.VAO);
	glGenBuffers(1, &gameObjBuffObject.positionVBO);
	glGenBuffers(1, &gameObjBuffObject.colorVBO);
	glGenBuffers(1, &gameObjBuffObject.IBO);

	glBindVertexArray(gameObjBuffObject.VAO);
	
	// POSITION ATTRIB
	gameObjBuffObject.bindPositionVBO();
	glBufferData(GL_ARRAY_BUFFER, sizeof(gameObjPosBuffer), gameObjPosBuffer, GL_STREAM_DRAW);
	glEnableVertexAttribArray(0);
	glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);

	// COLOR ATTRIB
	gameObjBuffObject.bindColorVBO();
	glBufferData(GL_ARRAY_BUFFER, sizeof(gameObjColBuffer), gameObjColBuffer, GL_DYNAMIC_DRAW);
	glEnableVertexAttribArray(3);
	glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
	
	// INDEX BUFFER
	gameObjBuffObject.bindIBO();
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(gameObjIndices), gameObjIndices, GL_STATIC_DRAW);

	gameObjBuffObject.bindPositionVBO();
	for(int i = 0; i < NUMOBJECTS; i++)
	{
		float randX = (rand() % 1920)  ;
		float randY = (rand() % 1080)  ;
		float randW = (rand() % 80 )  ;
		float randH = (rand() % 80 )  ;
		gameObj[i].init(randX, randY, randW, randH); 

	}

	gameObjBuffObject.bindColorVBO();
	for(int i = 0; i < NUMOBJECTS; i++)
	{
		float randC = (((float)rand() / (RAND_MAX))+  1.0f) - 0.3f;
		if(randC < 0.05f)
		{
			randC = 0.09f;
		}
		if(i % 2 == 0)
		{
			gameObj[i].setColorBottomLeft(randC, 0.0f, 0.0f, 1.0f); 
			gameObj[i].setColorTopLeft(0.0f, randC, 0.0f, 1.0f); 
			gameObj[i].setColorBottomRight(0.0f, 0.0f, randC, 1.0f); 
			gameObj[i].setColorTopRight(randC, 0.0f, 0.0f, 1.0f); 
		}
		else if(i % 3 == 0)
		{
			gameObj[i].setSolidColor(0.0f, randC, 0.0f, 1.0f); 
		}
		else if(i % 5 == 0)
		{
			gameObj[i].setSolidColor(0.0f, 0.0f, randC, 1.0f); 
		}
		else
		{
			gameObj[i].setSolidColor(0.5f, 0.0f, 0.5f, 1.0f);
		}

	}
	


	GameObject boundingBox = {};
	GameBuffer boundingBoxPositionBuffer = {};
	GameBuffer boundingBoxColorBuffer = {};
	boundingBox.bufferIndex = 0;

	GameBufferObject boundingBoxBufferObject = {};
	glGenVertexArrays(1, &boundingBoxBufferObject.VAO);
	glGenBuffers(1, &boundingBoxBufferObject.positionVBO);
	glGenBuffers(1, &boundingBoxBufferObject.colorVBO);
	glGenBuffers(1, &boundingBoxBufferObject.IBO);


	// POSITION VBO
	glBindVertexArray(boundingBoxBufferObject.VAO);
	boundingBoxBufferObject.bindPositionVBO();
	glBufferData(GL_ARRAY_BUFFER, sizeof(boundingBoxPositionBuffer), &boundingBoxPositionBuffer, GL_DYNAMIC_DRAW);

	glEnableVertexAttribArray(0);
	glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);

	// COLOR VBO
	boundingBoxBufferObject.bindColorVBO();
	glBufferData(GL_ARRAY_BUFFER, sizeof(boundingBoxColorBuffer), &boundingBoxColorBuffer, GL_STATIC_DRAW);

	glEnableVertexAttribArray(3);
	glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);

	// INDEX BUFFER
	boundingBoxBufferObject.bindIBO();
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(boundingBoxIndices), boundingBoxIndices, GL_STATIC_DRAW);


	// SETTING COLORS
	boundingBoxBufferObject.bindColorVBO();
	boundingBox.setSolidColor(1.0f, 0.3f, 0.1f, 1.0f);

	glViewport(0, 0, 800, 600);
	glUseProgram(shaderProgram);

	int    winWidth = 0;
	int    winHeight = 0;
	double mouseCurPosX = 0;
	double mouseCurPosY = 0;

	int winDimLoc = glGetUniformLocation(shaderProgram, "windowDimensions"); 
	char windowTitle[100] = {};

	
	int boundBox_X = 0;
	int boundBox_Y = 0;

	float movU = 0.0f;
	float movD = 0.0f;
	float movR = 0.0f;
	float movL = 0.0f;



	Time time = {};
	while(!glfwWindowShouldClose(window) && running) 
	{
		
		time.start();

		printf("seconds: %f\n", time.getMiliSeconds());


		glfwGetWindowSize(window, &winWidth, &winHeight);
		glUniform2f(winDimLoc, (float)winWidth, (float)winHeight);
		glViewport(0, 0, winWidth, winHeight);

		checkInput(window);

		glClearColor(0.0f, 0.05f, 0.05f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT);

		glfwGetCursorPos(window, &mouseCurPosX, &mouseCurPosY);
		sprintf(windowTitle, "CJGL Game - MouseCursor: %4.3f, %4.3f - Window (width/height): %d/%d)", mouseCurPosX, mouseCurPosY, winWidth, winHeight);
		glfwSetWindowTitle(window, windowTitle);

		boundingBoxBufferObject.bindPositionVBO();
		if(keyPressed[LEFT_MOUSE_BUTTON])
		{
			boundingBox.setX(boundBox_X);
			boundingBox.setY(boundBox_Y);
			boundingBox.setWidth(mouseCurPosX - boundBox_X);
			boundingBox.setHeight(mouseCurPosY- boundBox_Y);
		}
		else
		{
			boundingBox.setX(0.0f);
			boundingBox.setY(0.0f);
			boundingBox.setWidth(0.0f);
			boundingBox.setHeight(0.0f);

			boundBox_X = mouseCurPosX; 
			boundBox_Y = mouseCurPosY;
		}

		if(keyPressed[RIGHT_MOUSE_BUTTON])
		{
		}

		if(keyPressed[ESC])
		{
			running = false;
		}
		if(keyPressed['W'])
		{
		}
		if(keyPressed['S'])
		{
		}
		if(keyPressed['A'])
		{
		}
		if(keyPressed['D'])
		{
		}

		boundingBoxBufferObject.bindColorVBO();
		if(keyPressed['K'])
		{
		}
		if(keyPressed['I'])
		{
		}
		if(keyPressed['O'])
		{
		}
		if(keyPressed['L'])
		{
		}

		gameObjBuffObject.bindPositionVBO();
		if(keyPressed[LEFT_ARROW]) 
		{
			movL = -0.5f  * time.getMiliSeconds();
		}
		else
		{
			movL = 0.0f;
		}
		if(keyPressed[RIGHT_ARROW])
		{
			movR = +0.5f  * time.getMiliSeconds();
		}
		else
		{
			movR = 0.0f;
		}
		if(keyPressed[UP_ARROW])
		{
			movU = -0.5f  * time.getMiliSeconds();
		}
		else
		{
			movU = 0.0f;
		}
		if(keyPressed[DOWN_ARROW])
		{
			movD = +0.5f  * time.getMiliSeconds();
		}
		else
		{
			movD = 0.0f;
		}

		for(int i = 0; i < NUMOBJECTS; i++)
		{
			gameObj[i].setX(gameObj[i].getX() + movR + movL);
			gameObj[i].setY(gameObj[i].getY() + movU + movD);

		}

		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
		gameObjBuffObject.bindVAO();
		glBindBuffer(GL_ARRAY_BUFFER, gameObjBuffObject.positionVBO);
		glDrawElements(GL_TRIANGLES, NUMOBJECTS * 6, GL_UNSIGNED_INT, 0);

		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
		boundingBoxBufferObject.bindVAO();
		glBindBuffer(GL_ARRAY_BUFFER, boundingBoxBufferObject.positionVBO);
		glDrawElements(GL_LINES, 8, GL_UNSIGNED_INT, 0);


		glfwSwapBuffers(window);
		glfwPollEvents();
	}

	glDeleteShader(VS);
	glDeleteShader(FS);
	glDeleteProgram(shaderProgram);

	glDeleteBuffers(1, &boundingBoxBufferObject.positionVBO);
	glDeleteBuffers(1, &boundingBoxBufferObject.colorVBO);
	glDeleteBuffers(1, &boundingBoxBufferObject.IBO);
	glDeleteBuffers(1, &boundingBoxBufferObject.VAO);

	glDeleteBuffers(1, &gameObjBuffObject.positionVBO);
	glDeleteBuffers(1, &gameObjBuffObject.colorVBO);
	glDeleteBuffers(1, &gameObjBuffObject.IBO);
	glDeleteBuffers(1, &gameObjBuffObject.VAO);
	
	glfwDestroyWindow(window);
	glfwTerminate();


	return 0;
}

char* loadShaderFile(char* in_filePath)
{
	FILE *fileHandle = {};
	char *filePath = in_filePath;
	fileHandle = fopen(filePath, "r");


	if(!fileHandle)
	{
		printf("Couldn't open %s\n", filePath);
	}

	const int nCharsInFile = 2000;
	char fileTextBuffer[nCharsInFile] = {}; 
	
	char* shaderTextBuffer = (char*)malloc(nCharsInFile);
	strcpy(shaderTextBuffer, "");

	while(fgets(fileTextBuffer, nCharsInFile, fileHandle) != 0)
	{
		strcat(shaderTextBuffer, fileTextBuffer);
	}

	return shaderTextBuffer;

	printf("%s\n", shaderTextBuffer);

	fclose(fileHandle);
}



GLFWwindow* windowSetup(int width, int height)
{
	if(!glfwInit())
	{
		printf("FAILED TO INIT\n");
	}

	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	
	GLFWwindow *window;
	window = glfwCreateWindow(width, height, "GL GAME", 0, 0);

	glfwMakeContextCurrent(window);

	if(!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
	{
		printf("GLAD FAILED\n");
	}
	glfwSwapInterval(1);

	return window;
}

Not mapping and unmapping the buffer 100k times per frame would be a good start. Double-buffering (i.e. not modifying a buffer which is being read by pending commands) would probably help. See the relevant wiki page for more information on updating buffers efficiently.

Beyond that, you need to measure where the time is actually being spent. In particular, if a seemingly-trivial function takes a long time to complete, that suggests a pipeline stall (i.e. the command first waits for the GPU to finish executing pending commands before proceeding). Mapping a buffer for write without GL_MAP_UNSYNCHRONIZED_BIT is likely to do this (but if you map it with that flag, it’s up to you to ensure that you don’t modify regions which are used by pending commands).

1 Like

Without that and without GL_MAP_INVALIDATE_BUFFER_BIT, which equates to buffer orphaning. Again, see the relevant wiki page.

That is, mapping a buffer with one or the other of these bits (when used correctly) should generally avoid a pipeline stall caused by implicit synchronization.

1 Like

Not mapping and unmapping the buffer 100k times per frame would be a good start. Double-buffering (i.e. not modifying a buffer which is being read by pending commands) would probably help. See the relevant wiki page for more information on updating buffers efficiently.

But how would you update that vertex data without mapping it ?? If you for instance would want to have 50k+ rectangles moving around and having their positions updated at the same time.

Instead of double buffer i could for example just set the Y-value to a const value like 100.0f when i press the UP-arrow which means im not reading from the mappedBuffer but only writing to it, like so:

	if(keyPressed[UP_ARROW])
		{
			for (int i = 0; i < NUMOBJECTS; i++)
			{
				gameObjWRITE[i].setY(100.0f);


			}

		

i also use the GL_MAP_WRITE_BIT, GL_MAP_INVALIDATE_BUFFER_BIT and GL_MAP_UNSYNCHRONIZED_BIT

The problem is still there, I get 50 miliSecs per frame when i press the UP-arrow and set all 50k objects y-value to 100.0f.

You were linked to a detailed article explaining how to do that. Your problem is that you structured your setY function to be independent of anything else going on. That is, setY is (mostly) self-contained; it does everything that is needed to perform that operation. It maps the buffer, makes its one modification, then unmaps it, like a good little self-contained, OOP call.

Stop doing that. Object-oriented design, particularly in low-level circumstances like this, is often not conducive to performance. Don’t look at it in terms of “moving objects”; look at it in terms of modifying data. And structure your code accordingly.

Instead of thinking in terms of “loop over objects and update their positions”, you must think in terms of the data being updated. You are modifying your object data, so you map the data, modify it in whatever way is fastest, and then you’re done. Make whatever changes to the structure of your code is necessary to manipulate your data in that way.

And if that means ditching the idea of a GameObject entirely, so be it.

Also, persistent mapping is a thing.

You really shouldn’t be doing that. You either map for reading or map for writing; avoid any algorithm that requires doing both.

1 Like

Map the entire buffer (or the entire portion which is to be modified), modify everything, then unmap it at the end.

Also, you’re currently using GL_MAP_INVALIDATE_BUFFER_BIT, which may discard the entire contents of the buffer (presumably your implementation isn’t doing that in this case). You should only use that if you’re going to overwrite everything before unmapping the buffer. If you only want to overwrite a portion of the buffer, use GL_MAP_INVALIDATE_RANGE_BIT.

As Alfonse suggests, don’t try to force a naive OOP structure on OpenGL, as that can have a significant performance penalty. You need to work in terms of “meshes” (i.e. sets of primitives with common uniform state), not individual primitives.

1 Like

EEEY i managed to render 300K objects and update them all in one go without any framrate difference!! using only one glMapBuffer() call for the entire buffer :smiley:

I rebuilt everyting like this:

int main()
{

	srand(time(NULL));

	GLFWwindow *window = windowSetup(SCR_WIDTH, SCR_HEIGHT);



	unsigned int shaderProgram = compileShaderProgram();


	GLuint VAO, posVBO, colVBO, texVBO, IBO;

	GLfloat posBuffer[NUMOBJECTS * 16] = {};
	GLfloat colBuffer[NUMOBJECTS * 16] = {};
	GLfloat texBuffer[NUMOBJECTS * 16] = {};

	GLint indexBuffer[NUMOBJECTS * 6] = {};

	//0, 1, 2, 2, 3, 0    4, 5, 6, 6, 7, 4

	for(int i = 0; i < NUMOBJECTS; i++)
	{
		indexBuffer[0 + i * 6] = i * 4 + 0;
		indexBuffer[1 + i * 6] = i * 4 + 1;
		indexBuffer[2 + i * 6] = i * 4 + 2;
		indexBuffer[3 + i * 6] = i * 4 + 2;
		indexBuffer[4 + i * 6] = i * 4 + 3;
		indexBuffer[5 + i * 6] = i * 4 + 0;
	}

	glGenVertexArrays(1, &VAO);
	glGenBuffers(1, &posVBO);
	glGenBuffers(1, &colVBO);
	glGenBuffers(1, &texVBO);
	glGenBuffers(1, &IBO);

	glBindVertexArray(VAO);

	glBindBuffer(GL_ARRAY_BUFFER, posVBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(posBuffer), posBuffer, GL_STREAM_DRAW);

	glEnableVertexAttribArray(0);
	glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);

	glBindBuffer(GL_ARRAY_BUFFER, colVBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(colBuffer), colBuffer, GL_STREAM_DRAW);

	glEnableVertexAttribArray(3);
	glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);

	glBindBuffer(GL_ARRAY_BUFFER, texVBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(texBuffer), texBuffer, GL_STREAM_DRAW);

	glEnableVertexAttribArray(7);
	glVertexAttribPointer(7, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
	
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indexBuffer), indexBuffer, GL_STATIC_DRAW);

	glBindBuffer(GL_ARRAY_BUFFER, colVBO);
	float* pData = (float*)glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
	for (int i = 0; i < NUMOBJECTS; i++)
	{
		float randR = rand() / (RAND_MAX) + 1.0f;
		float randG = rand() / (RAND_MAX) + 1.0f;
		float randB = rand() / (RAND_MAX) + 1.0f;
		pData[0  + i * 16]  = randR ;
		pData[1  + i * 16]  = 0.f ;
		pData[2  + i * 16]  = 0.f ;
		pData[3  + i * 16]  = 1.0f ;

		pData[4  + i * 16]  = 0.f ;
		pData[5  + i * 16]  = randG ;
		pData[6  + i * 16]  = 0.f ;
		pData[7  + i * 16]  = 1.0f ;

		pData[8  + i * 16]  = 0.f ;
		pData[9  + i * 16]  = 0.f ;
		pData[10 + i * 16]  = randB ;
		pData[11 + i * 16]  = 1.0f ;

		pData[12 + i * 16]  = randR ;
		pData[13 + i * 16]  = randG ;
		pData[14 + i * 16]  = 0.f ;
		pData[15 + i * 16]  = 1.0f ;
	}

	glUnmapBuffer(GL_ARRAY_BUFFER);

	glBindBuffer(GL_ARRAY_BUFFER, posVBO);

	pData = (float*)glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
	
	float saveBuffer[NUMOBJECTS * 16] = {};
	for (int i = 0; i < NUMOBJECTS; i++)
	{
		float randX = rand() % SCR_WIDTH;
		float randY = rand() % SCR_HEIGHT;
		float randW = randX + (rand() % 150);
		float randH = randY + (rand() % 150);

		saveBuffer[0  + i * 16]  = randX ;
		saveBuffer[4  + i * 16]  = randX ;
		saveBuffer[8  + i * 16]  = randW ;
		saveBuffer[12 + i * 16]  = randW ;

		saveBuffer[1 +  i * 16]  = randY ;
		saveBuffer[5 +  i * 16]  = randH ;
		saveBuffer[9 +  i * 16]  = randH ;
		saveBuffer[13 + i * 16]  = randY ;

		pData[0  + i * 16]  = randX ;
		pData[4  + i * 16]  = randX ;
		pData[8  + i * 16]  = randW ;
		pData[12 + i * 16]  = randW ;

		pData[1 +  i * 16]  = randY ;
		pData[5 +  i * 16]  = randH ;
		pData[9 +  i * 16]  = randH ;
		pData[13 + i * 16]  = randY ;
	}


	glUnmapBuffer(GL_ARRAY_BUFFER);

	glUseProgram(shaderProgram);

	double mouseCurPosX = 0;
	double mouseCurPosY = 0;

	char windowTitle[80] = {};

	glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

	int iColorLoc = glGetUniformLocation(shaderProgram, "iColorData");
	int winDimLoc = glGetUniformLocation(shaderProgram, "windowDimensions");
	int winWidth = 0;
	int winHeight = 0;

	float moveR = 0.0f;
	float moveL = 0.0f;
	float moveU = -1.0f;
	float moveD = 0.0f;

	Time time = {};
	while(!glfwWindowShouldClose(window) && running) 
	{

		time.start();

		printf("frameRate m_s: %0.3f\n", time.getMiliSeconds());
		glfwGetWindowSize(window, &winWidth, &winHeight);

		glViewport(0, 0, winWidth, winHeight);

		glUniform2f(winDimLoc, (float)winWidth, (float)winHeight);


		glViewport(0, 0, winWidth, winHeight);
		checkInput(window);

		glClearColor(0.0f, 0.2f, 0.3f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT);

		glfwGetCursorPos(window, &mouseCurPosX, &mouseCurPosY);
		sprintf(windowTitle, "CJGL Game - MouseCursor: %4.3f, %4.3f - Window (width/height): %d/%d)", mouseCurPosX, mouseCurPosY, winWidth, winHeight);
		glfwSetWindowTitle(window, windowTitle);


		if(keyPressed[LEFT_MOUSE_BUTTON])
		{
		}
		if(keyPressed[RIGHT_MOUSE_BUTTON])
		{
		}

		if(keyPressed[ESC])
		{
			running = false;
		}
		pData = (float*)glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
		if(keyPressed['W'])
		{
			for (int i = 0; i < NUMOBJECTS; i++)
			{

				saveBuffer[0  + i * 16]  += moveU;
				saveBuffer[4  + i * 16]  += moveU;
				saveBuffer[8  + i * 16]  += moveU;
				saveBuffer[12 + i * 16]  += moveU;

				saveBuffer[1 +  i * 16]  += moveU;
				saveBuffer[5 +  i * 16]  += moveU;
				saveBuffer[9 +  i * 16]  += moveU;
				saveBuffer[13 + i * 16]  += moveU;
			}
			
	
		}
		if(keyPressed['S'])
		{
		}
		if(keyPressed['A'])
		{
		}
		if(keyPressed['D'])
		{
		}

		for (int i = 0; i < NUMOBJECTS; i++)
		{

			pData[0  + i * 16]  = saveBuffer[0  + i * 16] ;
			pData[4  + i * 16]  = saveBuffer[4  + i * 16] ;
			pData[8  + i * 16]  = saveBuffer[8  + i * 16] ;
			pData[12 + i * 16]  = saveBuffer[12 + i * 16] ;
                                                           
			pData[1 +  i * 16]  = saveBuffer[1  + i * 16] ;
			pData[5 +  i * 16]  = saveBuffer[5  + i * 16] ;
			pData[9 +  i * 16]  = saveBuffer[9  + i * 16] ;
			pData[13 + i * 16]  = saveBuffer[13 + i * 16] ;
		}


		glUnmapBuffer(GL_ARRAY_BUFFER);

		glDrawElements(GL_TRIANGLES, NUMOBJECTS * 6, GL_UNSIGNED_INT, 0);


		glfwSwapBuffers(window);
		glfwPollEvents();
	}

	glDeleteProgram(shaderProgram);



	
	glfwDestroyWindow(window);
	glfwTerminate();


	return 0;
}

now i get like 33 ms framrate when i render 300k rects with random sizes between 0 and 150 pixels in width/height. but i stays at like 16 ms!! when the width/height is randomized between 0 and 110.
so the dip seems to be at around widths/heights randomized at around 120-130 pixels

thanks for the help ! :smiley:
@GClements
@Alfonse_Reinheart
@Dark_Photon