Alright, I have followed your approach and created a struct for each Quad. If it's not performant with cache and stuff I can change it in the future, but I will want to make it work with this first because this code is as clear as it can get.
Right now, everything but rendering same textures.
This is the Quad struct:
#ifndef QUAD_H
#define QUAD_H
#include "Texture.h"
struct Quad {
GLfloat vertices[100] = { 0.0f };
GLuint indices[600] = { 0, 1, 3, 1, 2, 3 };
Texture *texture;
glm::mat4 transform;
};
#endif
Don't know how hight to set the array size. And this is my SpriteBatch Header:
#ifndef SPRITEBATCH_H
#define SPRITEBATCH_H
#include <glm/glm.hpp>
#include "Texture.h"
#include <GL/glew.h>
#include "Camera.h"
#include "Shader.h"
#include <vector>
#include "Quad.h"
class SpriteBatch
{
public:
SpriteBatch(Shader& shader, Camera &camera);
~SpriteBatch();
void draw(Texture *texture, GLfloat x, GLfloat y, GLfloat width, GLfloat height);
void flush();
private:
GLuint VBO = 0, VAO = 0, EBO = 0;
GLint transformShaderLocation, viewShaderLocation, projectionShaderLocation;
Shader *shader;
Camera *camera;
std::vector<std::vector<Quad>> batchedQuads;
std::vector<Quad> combinedQuads;
std::vector<int> batchedQuadsContentQuadAmount;
glm::mat4 projection, view;
int currentIndex{ 0 };
};
#endif
And class:
#include "SpriteBatch.h"
SpriteBatch::SpriteBatch(Shader& shader, Camera &camera)
{
this->shader = &shader;
this->camera = &camera;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
}
SpriteBatch::~SpriteBatch()
{
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &EBO);
glDeleteBuffers(1, &VAO);
}
void SpriteBatch::draw(Texture *texture, GLfloat x, GLfloat y, GLfloat width, GLfloat height)
{
Quad quad;
quad.texture = texture;
quad.transform = glm::translate(quad.transform, glm::vec3(x, y, 0));
quad.vertices[0] = width/2 ;
quad.vertices[1] = height/2;
quad.vertices[2] = 0.0f;
quad.vertices[3] = 1.0f;
quad.vertices[4] = 1.0f;
quad.vertices[5] = width / 2;
quad.vertices[6] = -height / 2;
quad.vertices[7] = 0.0f;
quad.vertices[8] = 1.0f;
quad.vertices[9] = 0.0f;
quad.vertices[10] = -width / 2;
quad.vertices[11] = -height / 2;
quad.vertices[12] = 0.0f;
quad.vertices[13] = 0.0f;
quad.vertices[14] = 0.0f;
quad.vertices[15] = -width / 2;
quad.vertices[16] = height / 2;
quad.vertices[17] = 0.0f;
quad.vertices[18] = 0.0f;
quad.vertices[19] = 1.0f;
//Sort this quad into either a new list of quads which should be rendered or adds it to the last list if the texture is the same, so that they can be rendered together
if (currentIndex != 0) {
if (batchedQuads.at(currentIndex - 1).at(0).texture == texture) {
batchedQuads.at(currentIndex - 1).push_back(quad);
}
else {
batchedQuadsContentQuadAmount.push_back(batchedQuads.at(currentIndex - 1).size());
std::vector<Quad> anotherQuadVector;
anotherQuadVector.push_back(quad);
batchedQuads.push_back(anotherQuadVector);
currentIndex += 1;
}
}
else {
std::vector<Quad> firstQuadVector;
firstQuadVector.push_back(quad);
batchedQuads.push_back(firstQuadVector);
currentIndex += 1;
}
}
void SpriteBatch::flush()
{
if (currentIndex == 0) return; //Ensures that there are sprites added
for (std::vector<Quad> quadBatch : batchedQuads) {
Quad combinedQuad;
int processedQuads{ 0 };
for (Quad quad : quadBatch) {
combinedQuad.vertices[processedQuads * 20] = quad.vertices[0];
combinedQuad.vertices[processedQuads * 20 + 1] = quad.vertices[1];
combinedQuad.vertices[processedQuads * 20 + 2] = quad.vertices[2];
combinedQuad.vertices[processedQuads * 20 + 3] = quad.vertices[3];
combinedQuad.vertices[processedQuads * 20 + 4] = quad.vertices[4];
combinedQuad.vertices[processedQuads * 20 + 5] = quad.vertices[5];
combinedQuad.vertices[processedQuads * 20 + 6] = quad.vertices[6];
combinedQuad.vertices[processedQuads * 20 + 7] = quad.vertices[7];
combinedQuad.vertices[processedQuads * 20 + 8] = quad.vertices[8];
combinedQuad.vertices[processedQuads * 20 + 9] = quad.vertices[9];
combinedQuad.vertices[processedQuads * 20 + 10] = quad.vertices[10];
combinedQuad.vertices[processedQuads * 20 + 11] = quad.vertices[11];
combinedQuad.vertices[processedQuads * 20 + 12] = quad.vertices[12];
combinedQuad.vertices[processedQuads * 20 + 13] = quad.vertices[13];
combinedQuad.vertices[processedQuads * 20 + 14] = quad.vertices[14];
combinedQuad.vertices[processedQuads * 20 + 15] = quad.vertices[15];
combinedQuad.vertices[processedQuads * 20 + 16] = quad.vertices[16];
combinedQuad.vertices[processedQuads * 20 + 17] = quad.vertices[17];
combinedQuad.vertices[processedQuads * 20 + 18] = quad.vertices[18];
combinedQuad.vertices[processedQuads * 20 + 19] = quad.vertices[19];
combinedQuad.indices[processedQuads * 6] = 0 + processedQuads * 4;
combinedQuad.indices[processedQuads * 6 + 1] = 1 + processedQuads * 4;
combinedQuad.indices[processedQuads * 6 + 2] = 3 + processedQuads * 4;
combinedQuad.indices[processedQuads * 6 + 3] = 1 + processedQuads * 4;
combinedQuad.indices[processedQuads * 6 + 4] = 2 + processedQuads * 4;
combinedQuad.indices[processedQuads * 6 + 5] = 3 + processedQuads * 4;
processedQuads += 1;
}
combinedQuad.texture = quadBatch.at(0).texture;
combinedQuad.transform = quadBatch.at(0).transform;
combinedQuads.push_back(combinedQuad);
}
batchedQuadsContentQuadAmount.push_back(batchedQuads.at(batchedQuads.size()-1).size());
batchedQuads.clear();
int drawCalls{ 0 };
for (Quad combinedQuad : combinedQuads) {
shader->Use();
glBindTexture(GL_TEXTURE_2D, combinedQuad.texture->texture);
view = camera->getView();
projection = camera->getProjection();
glBindVertexArray(VAO);
//Bind vertices
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(combinedQuad.vertices), combinedQuad.vertices, GL_STATIC_DRAW);
//Bind indices
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(combinedQuad.indices), combinedQuad.indices, GL_STATIC_DRAW);
//Position
glVertexAttribPointer(0, 3 , GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
// TexCoord
glVertexAttribPointer(1, 2 , GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(1);
//VAO
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
//Shader locations
transformShaderLocation = glGetUniformLocation(shader->program, "transform");
viewShaderLocation = glGetUniformLocation(shader->program, "view");
projectionShaderLocation = glGetUniformLocation(shader->program, "projection");
// Pass them to the shaders
glUniformMatrix4fv(transformShaderLocation, 1, GL_FALSE, glm::value_ptr(combinedQuad.transform));
glUniformMatrix4fv(viewShaderLocation, 1, GL_FALSE, glm::value_ptr(view));
glUniformMatrix4fv(projectionShaderLocation, 1, GL_FALSE, glm::value_ptr(projection));
//Draw VAO
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6 * batchedQuadsContentQuadAmount.at(drawCalls), GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
drawCalls += 1;
}
//Sets index to 0 and clears combinedQuads to welcome new sprites. Batchedquads has already been cleared.
currentIndex = 0;
combinedQuads.clear();
}
For me, all is logical and does its job as intended, but same textures still don't work.
Here is the main class:
#include "main.h"
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
int main(int argc, char* argv[]) {
Main main;
return main.init();
}
int Main::init() {
display = new Display(800, 600, "OpenGL" );
shader = new Shader("Shaders/default.vert", "Shaders/default.frag");
texture1 = new Texture("Textures/libGDX.png", STBI_rgb_alpha);
texture2 = new Texture("Textures/textuuur.png", STBI_rgb);
_sprite1 = new Sprite(150.f, 150.f, 100.0f, 100.0f, *shader, texture1);
_sprite2 = new Sprite(150.f, 150.f, 100.0f, 100.0f, *shader, texture2);
if (_test3D) {
camera = new Camera(PERSPECTIVE, display);
}
else {
camera = new Camera(ORTHOGRAPHIC, display);
}
if (_test3D) {
glEnable(GL_DEPTH_TEST);
}
this->spriteBatch = new SpriteBatch(*shader, *camera);
while (!display->isClosed()) {
update();
}
delete _sprite1, _sprite2;
delete texture1, texture2;
delete shader;
delete camera;
delete display;
delete spriteBatch;
return 0;
}
void Main::update() {
draw();
display->Update();
}
void Main::draw() {
glClearColor(0.0f, 0.3f, 0.5f, 1.0f);
if (_test3D) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
else {
glClear(GL_COLOR_BUFFER_BIT);
}
//_sprite1->draw(*camera);
//_sprite2->draw(*camera);
spriteBatch->draw(texture1, _sprite1->x, _sprite1->y, _sprite1->width, _sprite1->height);
spriteBatch->draw(texture2, _sprite1->x + 600, _sprite1->y + 10, _sprite1->width, _sprite1->height);
spriteBatch->draw(texture2, _sprite1->x + 400, _sprite1->y + 10, _sprite1->width, _sprite1->height);
spriteBatch->flush();
}
The first texture draws, but the second or third doesn't. Only one of the two same textures actually is drawn on the screen, the other is not.
Now, this
Quote
thrash the poor caches when copying vertex data.
sounds pretty bad, how do I improve that. Do I have to write that without the Quad struct again?
Also, strangely with this:
Quad combinedQuad;
int processedQuads{ 0 };
combinedQuad.texture = quadBatch.at(0).texture;
combinedQuad.transform = quadBatch.at(0).transform;
for (Quad quad : quadBatch) {
combinedQuad.vertices[processedQuads * 20] = quad.vertices[0];
combinedQuad.vertices[processedQuads * 20 + 1] = quad.vertices[1];
combinedQuad.vertices[processedQuads * 20 + 2] = quad.vertices[2];
combinedQuad.vertices[processedQuads * 20 + 3] = quad.vertices[3];
combinedQuad.vertices[processedQuads * 20 + 4] = quad.vertices[4];
combinedQuad.vertices[processedQuads * 20 + 5] = quad.vertices[5];
combinedQuad.vertices[processedQuads * 20 + 6] = quad.vertices[6];
combinedQuad.vertices[processedQuads * 20 + 7] = quad.vertices[7];
combinedQuad.vertices[processedQuads * 20 + 8] = quad.vertices[8];
combinedQuad.vertices[processedQuads * 20 + 9] = quad.vertices[9];
combinedQuad.vertices[processedQuads * 20 + 10] = quad.vertices[10];
combinedQuad.vertices[processedQuads * 20 + 11] = quad.vertices[11];
combinedQuad.vertices[processedQuads * 20 + 12] = quad.vertices[12];
combinedQuad.vertices[processedQuads * 20 + 13] = quad.vertices[13];
combinedQuad.vertices[processedQuads * 20 + 14] = quad.vertices[14];
combinedQuad.vertices[processedQuads * 20 + 15] = quad.vertices[15];
combinedQuad.vertices[processedQuads * 20 + 16] = quad.vertices[16];
combinedQuad.vertices[processedQuads * 20 + 17] = quad.vertices[17];
combinedQuad.vertices[processedQuads * 20 + 18] = quad.vertices[18];
combinedQuad.vertices[processedQuads * 20 + 19] = quad.vertices[19];
combinedQuad.indices[processedQuads * 6] = 0 + processedQuads * 4;
combinedQuad.indices[processedQuads * 6 + 1] = 1 + processedQuads * 4;
combinedQuad.indices[processedQuads * 6 + 2] = 3 + processedQuads * 4;
combinedQuad.indices[processedQuads * 6 + 3] = 1 + processedQuads * 4;
combinedQuad.indices[processedQuads * 6 + 4] = 2 + processedQuads * 4;
combinedQuad.indices[processedQuads * 6 + 5] = 3 + processedQuads * 4;
processedQuads += 1;
}
combinedQuads.push_back(combinedQuad);
combinedQuad.texture will turn into a read access violation at combinedQuads.push_back(combinedQuad);
If I put
combinedQuad.texture = quadBatch.at(0).texture;
combinedQuad.transform = quadBatch.at(0).transform;
after the loop / before the push_back, it will be fine.
Why?
Sorry for the billion edits.