COSC-3P93-Project/Step 3/src/graphics/gl/gl.cpp

314 lines
12 KiB
C++

/*
* Created by Brett Terpstra 6920201 on 26/10/22.
* Copyright (c) 2022 Brett Terpstra. All Rights Reserved.
*/
#include <graphics/gl/gl.h>
#include <GL/glx.h>
#include "engine/image/stb_image.h"
#ifndef USE_GLFW
PFNGLCREATEVERTEXARRAYSPROC glCreateVertexArrays;
PFNGLCREATEBUFFERSPROC glCreateBuffers;
PFNGLNAMEDBUFFERDATAPROC glNamedBufferData;
PFNGLNAMEDBUFFERSUBDATAPROC glNamedBufferSubData;
PFNGLENABLEVERTEXARRAYATTRIBPROC glEnableVertexArrayAttrib;
PFNGLVERTEXARRAYATTRIBBINDINGPROC glVertexArrayAttribBinding;
PFNGLVERTEXARRAYATTRIBFORMATPROC glVertexArrayAttribFormat;
void assignGLFunctionPointers() {
glCreateVertexArrays = (PFNGLCREATEVERTEXARRAYSPROC) glXGetProcAddress((unsigned char*) "glCreateVertexArrays");
glCreateBuffers = (PFNGLCREATEBUFFERSPROC) glXGetProcAddress((unsigned char*) "glCreateBuffers");
glNamedBufferData = (PFNGLNAMEDBUFFERDATAPROC) glXGetProcAddress((unsigned char*) "glNamedBufferData");
glNamedBufferSubData = (PFNGLNAMEDBUFFERSUBDATAPROC) glXGetProcAddress((unsigned char*) "glNamedBufferSubData");
glEnableVertexArrayAttrib = (PFNGLENABLEVERTEXARRAYATTRIBPROC) glXGetProcAddress((unsigned char*) "glEnableVertexArrayAttrib");
glVertexArrayAttribBinding = (PFNGLVERTEXARRAYATTRIBBINDINGPROC) glXGetProcAddress((unsigned char*) "glVertexArrayAttribBinding");
glVertexArrayAttribFormat = (PFNGLVERTEXARRAYATTRIBFORMATPROC) glXGetProcAddress((unsigned char*) "glVertexArrayAttribFormat");
}
#endif
unsigned int createVAO() {
unsigned int vaoID;
glGenVertexArrays(1, &vaoID);
glBindVertexArray(vaoID);
return vaoID;
}
unsigned int VAO::storeData(int attrNumber, int coordSize, int stride, long offset, int length, const float* data) {
unsigned int vboID;
glGenBuffers(1, &vboID);
VBOs.push_back(vboID);
glBindBuffer(GL_ARRAY_BUFFER, vboID);
// 4 bytes / float
glBufferData(GL_ARRAY_BUFFER, length << 2, data, GL_STATIC_DRAW);
glVertexAttribPointer(attrNumber,coordSize,GL_FLOAT,false,stride, (void *) offset);
glBindBuffer(GL_ARRAY_BUFFER, 0);
return vboID;
}
unsigned int VAO::storeData(int length, const unsigned int *data) {
unsigned int eboID;
glGenBuffers(1, &eboID);
VBOs.push_back(eboID);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eboID);
// 4 bytes / int
glBufferData(GL_ELEMENT_ARRAY_BUFFER, length << 2, data, GL_STATIC_DRAW);
return eboID;
}
unsigned int VAO::createInstanceVBO(int count, int bytePerInstance) {
if (currentTransforms < 0) {
unsigned int vboID;
glGenBuffers(1, &vboID);
instanceVBO = vboID;
currentTransforms = count;
glBindBuffer(GL_ARRAY_BUFFER, vboID);
glBufferData(GL_ARRAY_BUFFER, (long) count * bytePerInstance, NULL, GL_STREAM_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
return vboID;
}
if (currentTransforms < count){
// double as to be more efficient. (prevents constant allocations when adding data per frame)
// n -> log n
currentTransforms = count * 2;
// update the vertex buffer on the GPU if our count is out of sync.
glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
glBufferData(GL_ARRAY_BUFFER, (long) currentTransforms * bytePerInstance, NULL, GL_STREAM_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
// otherwise do nothing.
return instanceVBO;
}
void VAO::addInstancedAttribute(int attribute, int dataSize, int dataLengthBytes, int offset) const {
// bind buffer like normal
glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
glBindVertexArray(VaoID);
glEnableVertexAttribArray(attribute);
// tell opengl how large this attribute is expected to be
glVertexAttribPointer(attribute, dataSize, GL_FLOAT, GL_FALSE, dataLengthBytes, (void *) (long)offset);
// how many per instance. 1 mean every triangle gets one copy of the data specified by ^
glVertexAttribDivisor(attribute, 1);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
VAO::VAO(const std::vector<float>& verts, const std::vector<float>& uvs, const std::vector<unsigned int>& indices): VaoID(createVAO()) {
this->drawCount = (int)indices.size();
glBindVertexArray(VaoID);
// enable the attributes, prevents us from having to do this later.
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
// store index data
storeData((int)indices.size(), indices.data());
// store vertex data
storeData(0, 3, 3 * sizeof(float), 0, (int)verts.size(), verts.data());
// store texture UV data
storeData(1, 2, 2 * sizeof(float), 0, (int)uvs.size(), uvs.data());
unbind();
}
VAO::VAO(const std::vector<Raytracing::Triangle>& triangles): VaoID(createVAO()) {
this->drawCount = (int)triangles.size() * 3;
glBindVertexArray(VaoID);
// enable the attributes, prevents us from having to do this later.
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
// convert vertex data
std::vector<float> verts;
std::vector<float> uvs;
std::vector<float> normals;
for (const Raytracing::Triangle& t : triangles){
verts.push_back(float(t.vertex1.x()));
verts.push_back(float(t.vertex1.y()));
verts.push_back(float(t.vertex1.z()));
uvs.push_back(float(t.uv1.x()));
uvs.push_back(float(t.uv1.y()));
normals.push_back(float(t.normal1.x()));
normals.push_back(float(t.normal1.y()));
normals.push_back(float(t.normal1.z()));
verts.push_back(float(t.vertex2.x()));
verts.push_back(float(t.vertex2.y()));
verts.push_back(float(t.vertex2.z()));
uvs.push_back(float(t.uv2.x()));
uvs.push_back(float(t.uv2.y()));
normals.push_back(float(t.normal2.x()));
normals.push_back(float(t.normal2.y()));
normals.push_back(float(t.normal2.z()));
verts.push_back(float(t.vertex3.x()));
verts.push_back(float(t.vertex3.y()));
verts.push_back(float(t.vertex3.z()));
uvs.push_back(float(t.uv3.x()));
uvs.push_back(float(t.uv3.y()));
normals.push_back(float(t.normal3.x()));
normals.push_back(float(t.normal3.y()));
normals.push_back(float(t.normal3.z()));
}
// store vertex data
storeData(0, 3, 3 * sizeof(float), 0, (int)verts.size(), verts.data());
// store texture UV data
storeData(1, 2, 2 * sizeof(float), 0, (int)uvs.size(), uvs.data());
// store normal data
storeData(2, 3, 3 * sizeof(float), 0, (int)normals.size(), normals.data());
// create an instance buffer with a large number of positions (stored in matrices)
// this way we don't have to expand it later, since I don't think I'll use 1k
// createInstanceVBO(1000, sizeof(Raytracing::Mat4x4));
// // send the positions as attributes
// addInstancedAttribute(3, 4, sizeof(Raytracing::Mat4x4), 0);
// addInstancedAttribute(4, 4, sizeof(Raytracing::Mat4x4), 16);
// addInstancedAttribute(5, 4, sizeof(Raytracing::Mat4x4), 32);
// addInstancedAttribute(6, 4, sizeof(Raytracing::Mat4x4), 48);
unbind();
}
void VAO::bind() const {
glBindVertexArray(VaoID);
}
void VAO::unbind() {
glBindVertexArray(0);
VaoID;
}
void VAO::draw(Raytracing::Shader& shader, const std::vector<Raytracing::Vec4>& positions) {
// this should only update if we are drawing with more positions than we already have allocated.
// createInstanceVBO((int)positions.size(), sizeof(Raytracing::Mat4x4));
// std::vector<Raytracing::Mat4x4> data;
// for (const Raytracing::Vec4& v : positions){
// // identity matrix
// Raytracing::Mat4x4 mat4X4 {};
// // translate to position
// mat4X4 = mat4X4.translate(v);
// data.push_back(mat4X4);
// }
// send translated matrices as data to the GPU, TODO: since this will likely be used on static geometry, try making this more static!
// glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
// //glBufferData(GL_ARRAY_BUFFER, datas.size() * sizeof(LightData), NULL, GL_STREAM_DRAW);
// glBufferSubData(GL_ARRAY_BUFFER, 0, (long)(data.size() * sizeof(Raytracing::Mat4x4)), &data[0]);
// glBindBuffer(GL_ARRAY_BUFFER, 0);
// // then finally draw
//glDrawArraysInstanced(GL_TRIANGLES, 0, drawCount * 3, (int)positions.size());
for (const auto& v : positions) {
Raytracing::Mat4x4 transform {};
transform.translate(v);
shader.setMatrix("transform", transform);
glDrawArrays(GL_TRIANGLES, 0, drawCount);
}
}
void VAO::draw() const {
if (drawCount < 0)
return;
else
glDrawElements(GL_TRIANGLES, drawCount, GL_UNSIGNED_INT, 0);
}
VAO::~VAO() {
const unsigned int vao = VaoID;
glDeleteVertexArrays(1, &vao);
for (const unsigned int vbo : VBOs){
glDeleteBuffers(1, &vbo);
}
glDeleteBuffers(1, &instanceVBO);
}
Texture::Texture(){
}
Texture::Texture(const std::string& path) {
data = loadTexture(path);
if (data == nullptr){
flog << "There was an error loading the image file " << path;
throw std::runtime_error("Error loading image from file!");
}
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
int GL_RGB_SETTING = channels == 3 ? GL_RGB : GL_RGBA;
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB_SETTING, width, height, 0, GL_RGB_SETTING, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
stbi_image_free(data);
data = nullptr;
}
Texture::Texture(Raytracing::Image* image): _image(image), width(image->getWidth()), height(image->getHeight()), channels(3) {
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGB8, width, height);
//glGenerateMipmap(GL_TEXTURE_2D);
// no sense sending data now since this texture is likely all black.
}
unsigned char *Texture::loadTexture(const std::string& path) {
// TODO: add more image processing options
stbi_set_flip_vertically_on_load(true);
unsigned char *dta = stbi_load(path.c_str(), &width, &height, &channels, 0);
//if (stbi__g_failure_reason) {
// flog << "STB Error Reason: ";
// flog << stbi__g_failure_reason;
//}
return dta;
}
Texture::~Texture() {
tlog << "Deleting Texture {" << textureID << "}\n";
glDeleteTextures(1, &textureID);
data = nullptr;
}
void Texture::bind() const {
glBindTexture(GL_TEXTURE_2D, textureID);
}
void Texture::unbind() {
glBindTexture(GL_TEXTURE_2D, 0);
textureID;
}
void Texture::enableGlTextures(int textureCount) {
for (int i = 0; i < textureCount; i++)
glActiveTexture(GL_TEXTURE0 + i);
textureID;
}
void Texture::updateImage() {
glBindTexture(GL_TEXTURE_2D, textureID);
// unfortunately we do have to put the data into a format that OpenGL can read. This is a TODO:?
data = new unsigned char[(unsigned long)(width) * (unsigned long)height * 3];
int pixelIndex = 0;
// slightly different order from STBi
for (int j = 0; j < height; j++) {
for (int i = 0; i < width; i++) {
data[pixelIndex++] = _image->getPixelR(i, j);
data[pixelIndex++] = _image->getPixelG(i, j);
data[pixelIndex++] = _image->getPixelB(i, j);
}
}
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, data);
delete[](data);
data = nullptr;
}