#include "renderer.h"
#include "constants.h"
#include <algorithm>
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb_image_write.h>
Renderer::Renderer(Shader& shaderProgram, GLuint* textureIDs, int textureCount):
shaderProgram(shaderProgram),
textureIDs(textureIDs),
textureCount(textureCount)
{
init();
}
Renderer::~Renderer() {
glDeleteVertexArrays(1, &masterVAO);
glDeleteBuffers(1, &masterVBO);
glDeleteBuffers(1, &masterEBO);
delete[] textureUniformLocations;
}
void Renderer::init() {
glGenVertexArrays(1, &masterVAO);
glGenBuffers(1, &masterVBO);
glGenBuffers(1, &masterEBO);
glBindVertexArray(masterVAO);
glBindBuffer(GL_ARRAY_BUFFER, masterVBO);
glBufferData(GL_ARRAY_BUFFER, MAX_VERTEX_BUFFER_SIZE, nullptr, GL_DYNAMIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, masterEBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, MAX_INDEX_BUFFER_SIZE, nullptr, GL_DYNAMIC_DRAW);
GLsizei stride = 9 * sizeof(GLfloat);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, stride, (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, stride, (void*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(1);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, stride, (void*)(6 * sizeof(GLfloat)));
glEnableVertexAttribArray(2);
glVertexAttribPointer(3, 1, GL_FLOAT, GL_FALSE, stride, (void*)(8 * sizeof(GLfloat)));
glEnableVertexAttribArray(3);
glBindVertexArray(0);
textureUniformLocations = new GLint[textureCount];
for (int i = 0; i < textureCount; ++i) {
std::string samplerName = "textures[" + std::to_string(i) + "]";
textureUniformLocations[i] = glGetUniformLocation(shaderProgram.ID, samplerName.c_str());
}
}
void Renderer::renderFrame() {
glBindVertexArray(masterVAO);
//std::cout << "masterVertices size: " << masterVertices.size() << std::endl;
for (int i = 0; i < textureCount; ++i) {
glActiveTexture(GL_TEXTURE0 + i);
glBindTexture(GL_TEXTURE_2D, textureIDs[i]);
}
shaderProgram.Activate();
for (int i = 0; i < textureCount; ++i) {
glUniform1i(textureUniformLocations[i], i);
}
glBindBuffer(GL_ARRAY_BUFFER, masterVBO);
glBufferSubData(GL_ARRAY_BUFFER, 0, masterVertices.size() * sizeof(GLfloat), masterVertices.data());
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, masterEBO);
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, masterIndices.size() * sizeof(GLuint), masterIndices.data());
glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(masterIndices.size()), GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
masterVertices.clear();
masterIndices.clear();
}
void Renderer::drawQuad(const QuadVertices& verts, const ColorRGB& color, int textureIndex, const float* uvs) {
GLfloat quadVertices[36];
const float defaultUVs[8] = { 0,1, 0,0, 1,0, 1,1 };
const float* uv = uvs ? uvs : defaultUVs;
const GLfloat positions[8] = { verts.x3, verts.y3, verts.x1, verts.y1, verts.x2, verts.y2, verts.x4, verts.y4 };
for (int i = 0; i < 4; ++i) {
quadVertices[i * 9 + 0] = positions[i * 2 + 0];
quadVertices[i * 9 + 1] = positions[i * 2 + 1];
quadVertices[i * 9 + 2] = 0.0f;
quadVertices[i * 9 + 3] = color.r;
quadVertices[i * 9 + 4] = color.g;
quadVertices[i * 9 + 5] = color.b;
quadVertices[i * 9 + 6] = uv[i * 2 + 0];
quadVertices[i * 9 + 7] = uv[i * 2 + 1];
quadVertices[i * 9 + 8] = static_cast<float>(textureIndex);
}
GLuint quadIndices[] = { 0,1,2,2,3,0 };
size_t baseIndex = masterVertices.size() / 9;
masterVertices.insert(masterVertices.end(), quadVertices, quadVertices + 36);
for (auto index : quadIndices) {
masterIndices.push_back(static_cast<GLuint>(baseIndex + index));
}
}
GLuint loadTexture(const char* filepath) {
GLuint textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
// Set filtering and wrapping
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// Load image data
int width, height, nrChannels;
unsigned char* data = stbi_load(filepath, &width, &height, &nrChannels, 0);
if (data) {
GLenum format;
if (nrChannels == 1) {
format = GL_RED; // Grayscale / font textures
}
else if (nrChannels == 3) {
format = GL_RGB; // Standard color
}
else if (nrChannels == 4) {
format = GL_RGBA; // Color + alpha
}
else {
std::cerr << "Warning: Unhandled channel count (" << nrChannels << "), defaulting to RGB" << std::endl;
format = GL_RGB;
}
glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
std::cout << "Loaded texture: " << filepath
<< " (" << width << "x" << height << ", channels: " << nrChannels << ")" << std::endl;
}
else {
std::cerr << "Failed to load texture: " << filepath << std::endl;
}
stbi_image_free(data);
return textureID;
}
void captureRegion(int x, int y, int w, int h, const std::string& filepath) {
std::vector<unsigned char> pixels(w * h * 4);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glReadBuffer(GL_BACK);
glReadPixels(x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
int rowBytes = w * 4;
for (int row = 0; row < h / 2; ++row) {
unsigned char* top = pixels.data() + row * rowBytes;
unsigned char* bottom = pixels.data() + (h - 1 - row) * rowBytes;
for (int col = 0; col < rowBytes; ++col) {
std::swap(top[col], bottom[col]);
}
}
if (!stbi_write_png(filepath.c_str(), w, h, 4, pixels.data(), rowBytes))
std::cerr << "Failed to write " << filepath << "\n";
else
std::cout << "Saved " << filepath << "\n";
}