#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"; }