/****************************************************************************** * File - OGLGraphics.cpp * Author - Joey Pollack * Date - 2021/08/30 (y/m/d) * Mod Date - 2021/09/02 (y/m/d) * Description - An openGL implementation of the engine Graphics interface. ******************************************************************************/ #include "glGraphics.h" #include "defaultShaders.h" #include #include "../image.h" #include "../internalFont.h" #include #include #include #include namespace lunarium { //////////////////////////////////////////////////////////// // DEBUG MESSAGE CALLBACK //////////////////////////////////////////////////////////// std::map OglGraphics::mDebugMsgTypes; std::map OglGraphics::mDebugMsgSources; std::map OglGraphics::mDebugMsgSeverity; void GLAPIENTRY MessageCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) { Logger::Log(LogCategory::GRAPHICS, LogLevel::OGL_DEBUG, "%s (type: %s, source: %s, severity: %s), message: %s", (type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : ""), OglGraphics::mDebugMsgSources[source].c_str(), OglGraphics::mDebugMsgTypes[type].c_str(), OglGraphics::mDebugMsgSeverity[severity].c_str(), message); } //////////////////////////////////////////////////////////// // RESTRICTED METHODS //////////////////////////////////////////////////////////// OglGraphics::OglGraphics() { } OpRes OglGraphics::Initialize(Window* pWindow, bool enableDebugMessages) { if (!pWindow->IsInit()) { return OpRes::Fail("Can not initialize Graphics interface. The Window must be initialized first!"); } if (mbIsInit) { return OpRes::Fail("Can not initialize Graphics interface. It is already initialized"); } mpWindow = pWindow; mpFBTexture = new Image; ResizeCanvas(); // Also calls InitTextureFrameBuffer glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); if (enableDebugMessages) { InitDebugMsgSystem(); } // Initialize the 2D drawing systems InitImageSystem(); InitShapeSystem(); // Init text rendering OpRes res = mText.Initialize(); if (Failed(res)) { Logger::Log(LogCategory::GRAPHICS, LogLevel::WARNING, "Could not initialized the text renderer - %s", res.Description); } // Load the default internal font const char* font = "OpenSans-Regular.ttf"; GenerateFontFileAt(font); mDefaultFont = mText.LoadFont(font); if (mDefaultFont < 0) { Logger::Log(LogCategory::GRAPHICS, LogLevel::WARNING, "Unable to load the default font: %s", font); } else { Logger::Log(LogCategory::GRAPHICS, LogLevel::INFO, "Successfully created default font: %s", font); } return OpRes::OK(); } void OglGraphics::Shutdown() { // Nothing to clean up at this point } void OglGraphics::ResizeCanvas() { // Get viewport size from glfw window glfwGetFramebufferSize(mpWindow->GetWindow(), &mFBWidth, &mFBHeight); glViewport(0, 0, mFBWidth, mFBHeight); mProjection = glm::ortho(0.0f, (GLfloat)mFBWidth, (GLfloat)mFBHeight, 0.0f, -1.0f, 1.0f); // mProjection = glm::ortho(0.0f, (GLfloat)mFBWidth, 0.0f, (GLfloat)mFBHeight, -1.0f, 1.0f); Logger::Log(LogCategory::GRAPHICS, LogLevel::INFO_VERBOSE, "glViewport set to %d, %d", mFBWidth, mFBHeight); InitTextureFrameBuffer(); } void OglGraphics::BeginDraw(RenderTarget rt) { mRT = rt; if (mRT == RenderTarget::RT_IMAGE) { glBindFramebuffer(GL_FRAMEBUFFER, mFBO); } glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } Image* OglGraphics::EndDraw() { if (mRT == RenderTarget::RT_IMAGE) { glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindTexture(GL_TEXTURE_2D, mpFBTexture->mGLTextureID); // Buffer width and height (in pixels) is the same as the screen size // Need to multiply these by the number bytes per pixel //int bufferSize = mFBWidth * mFBHeight * 4; // NOTE: Assuming 4 channels for now //unsigned char* buffer = new unsigned char[bufferSize]; //glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, (void*)buffer); //FlipImageVertically(buffer, mFBWidth, mFBHeight, 1); //glSetTexImage() mpFBTexture->FreeRawData(); mpFBTexture->mWidth = mFBWidth; mpFBTexture->mHeight = mFBHeight; mpFBTexture->mFormat = ImageFormat::RGBA; // return a copy of the image return mpFBTexture; } else { mpWindow->SwapBuffers(); return nullptr; } } //////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////// // USER METHODS //////////////////////////////////////////////////////////// void OglGraphics::SetClearColor(Color c) { mClearColor = c; glClearColor(mClearColor.Red, mClearColor.Green, mClearColor.Blue, mClearColor.Alpha); } Color OglGraphics::GetClearColor() const { return mClearColor; } // Draw Methods void OglGraphics::DrawLine(glm::vec2 point1, glm::vec2 point2, Color color, float lineWidth) { mShapeShader.MakeActive(); mShapeShader.SetUniformMatrix("projection", 1, glm::value_ptr(mProjection)); mShapeShader.SetUniformf("shapeColor", { color.Red, color.Green, color.Blue, color.Alpha }); glBindVertexArray(mRectVAO); GLfloat lineVerts[6][4] = { { point1.x, point1.y, }, { point2.x, point2.y, }, { 0.0f, 0.0f, }, { 0.0f, 0.0f, }, { 0.0f, 0.0f, }, { 0.0f, 0.0f, } }; glBindBuffer(GL_ARRAY_BUFFER, mRectVBO); glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(lineVerts), lineVerts); glBindBuffer(GL_ARRAY_BUFFER, 0); glLineWidth(lineWidth); glDrawArrays(GL_LINES, 0, 2); glBindVertexArray(0); } void OglGraphics::DrawEllipse(glm::vec2 center, glm::vec2 radii, Color color, float thickness, int resolution) { mShapeShader.MakeActive(); mShapeShader.SetUniformMatrix("projection", 1, glm::value_ptr(mProjection)); mShapeShader.SetUniformf("shapeColor", { color.Red, color.Green, color.Blue, color.Alpha }); glBindVertexArray(mEllipseVAO); GLfloat points[360][2]; for (int i = 0; i < 360; i++) { float rad = i * 3.14159f / 180.0f; points[i][0] = center.x + (radii.x * cos(rad)); points[i][1] = center.y + (radii.y * sin(rad)); } glBindBuffer(GL_ARRAY_BUFFER, mEllipseVBO); glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(points), points); glBindBuffer(GL_ARRAY_BUFFER, 0); glLineWidth(thickness); glDrawArrays(GL_LINE_LOOP, 0, 360); glBindVertexArray(0); } void OglGraphics::DrawFilledEllipse(glm::vec2 center, glm::vec2 radii, Color color, int resolution) { mShapeShader.MakeActive(); mShapeShader.SetUniformMatrix("projection", 1, glm::value_ptr(mProjection)); mShapeShader.SetUniformf("shapeColor", { color.Red, color.Green, color.Blue, color.Alpha }); glBindVertexArray(mEllipseVAO); GLfloat points[362][2]; points[0][0] = center.x; points[0][1] = center.y; for (int i = 1; i < 362; i++) { float rad = (i - 1) * 3.14159f / 180.0f; points[i][0] = center.x + (radii.x * cos(rad)); points[i][1] = center.y + (radii.y * sin(rad)); } glBindBuffer(GL_ARRAY_BUFFER, mEllipseVBO); glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(points), points); glBindBuffer(GL_ARRAY_BUFFER, 0); glDrawArrays(GL_TRIANGLE_FAN, 0, 362); glBindVertexArray(0); } void OglGraphics::DrawFilledBox(glm::vec2 topLeft, glm::vec2 botRight, Color color) { mShapeShader.MakeActive(); mShapeShader.SetUniformMatrix("projection", 1, glm::value_ptr(mProjection)); mShapeShader.SetUniformf("shapeColor", { color.Red, color.Green, color.Blue, color.Alpha }); glBindVertexArray(mRectVAO); GLfloat vertices[6][4] = { { topLeft.x, botRight.y, }, { botRight.x, topLeft.y, }, { topLeft.x, topLeft.y, }, { topLeft.x, botRight.y, }, { botRight.x, botRight.y, }, { botRight.x, topLeft.y, } }; glBindBuffer(GL_ARRAY_BUFFER, mRectVBO); glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices); glBindBuffer(GL_ARRAY_BUFFER, 0); // Render quad glDrawArrays(GL_TRIANGLES, 0, 6); glBindVertexArray(0); } void OglGraphics::DrawBox(Rectangle dimensions, Color color, float thickness) { glm::vec2 topLeft(dimensions.Left, dimensions.Top); glm::vec2 botRight(dimensions.right(), dimensions.bottom()); // left side top to bottom DrawLine(glm::vec2(topLeft.x, topLeft.y), glm::vec2(topLeft.x, botRight.y), color, thickness); // bottom left to right DrawLine(glm::vec2(topLeft.x, botRight.y), glm::vec2(botRight.x, botRight.y), color, thickness); // right side bottom to top DrawLine(glm::vec2(botRight.x, botRight.y), glm::vec2(botRight.x, topLeft.y), color, thickness); // top right to left DrawLine(glm::vec2(botRight.x, topLeft.y), glm::vec2(topLeft.x, topLeft.y), color, thickness); } void OglGraphics::DrawImage(Image& image, glm::vec2 topLeft, Color color) { DrawImage(image, Rectangle(0.0f, 0.0f, (float)image.GetWidth(), (float)image.GetHeight()), Rectangle(topLeft.x, topLeft.y, (float)image.GetWidth(), (float)image.GetHeight()), color); } void OglGraphics::DrawImage(Image& image, Rectangle source, Rectangle destination, Color color) { glm::mat4 id = glm::mat4(1.0f); glm::vec3 pos = glm::vec3(destination.Left, destination.Top, 0.0f); glm::mat4 trans = glm::translate(id, pos); trans = glm::scale(trans, glm::vec3(destination.Width, destination.Height, 1.0f)); mImageShader.MakeActive(); // float widthDiff = image.GetWidth() - source.Width; // if (source.Width == image.GetWidth() && source.Height == image.GetHeight()) // { // mImageShader.SetUniformf("uvManip", { 1.0f, 0.0f, 1.0f, 0.0f }); // No uv Manipulation // mImageShader.SetUniformMatrix("model", 1, glm::value_ptr(trans)); // mImageShader.SetUniformMatrix("projection", 1, glm::value_ptr(mProjection)); // mImageShader.SetUniformf("spriteColor", { color.Red, color.Green, color.Blue, color.Alpha }); // } // else // { // NOTE: Pretty sure these values will work out to be correct with out the if check float xScale = source.Width / image.GetWidth(); float xOffset = source.Left / image.GetWidth(); float yScale = source.Height / image.GetHeight(); float yOffset = source.Top / image.GetHeight(); // Logger::Log(LogCategory::GRAPHICS, LogLevel::INFO_VERBOSE, "uvManip Values: %f, %f, %f, %f", xScale, xOffset, yScale, yOffset); // * -1.0f on yScale will flip the image vertically mImageShader.SetUniformf("uvManip", { xScale, xOffset, yScale /** -1.0f*/, yOffset}); mImageShader.SetUniformMatrix("model", 1, glm::value_ptr(trans)); mImageShader.SetUniformMatrix("projection", 1, glm::value_ptr(mProjection)); mImageShader.SetUniformf("spriteColor", { color.Red, color.Green, color.Blue, color.Alpha }); //} glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, image.GetGLTextureID()); glBindVertexArray(mImageVAO); glDrawArrays(GL_TRIANGLES, 0, 6); glBindVertexArray(0); } void OglGraphics::DrawString(const char* string, Rectangle boundingArea, Color color, float scale, int font) { mText.DrawString(font, string, glm::vec2(boundingArea.Left, boundingArea.Top), glm::vec2(boundingArea.right(), boundingArea.bottom()), color, scale, mProjection); } // Takes raw image data and creates and Image class instance out of it. // The raw data must be in one of the ImageFormats and it must be 1 byte per channel. Image* OglGraphics::CreateImage(const unsigned char* pData, int width, int height, ImageFormat format) { unsigned int glFormat[4] = { GL_RGB, GL_RGBA, GL_BGR, GL_BGRA }; unsigned int textureID = 0; glGenTextures(1, &textureID); glBindTexture(GL_TEXTURE_2D, textureID); glTexImage2D(GL_TEXTURE_2D, 0, glFormat[format], width, height, 0, glFormat[format], GL_UNSIGNED_BYTE, pData); glGenerateMipmap(GL_TEXTURE_2D); // TODO: Move this to a different function to allow for more user options // Or make a version of the method that takes a struct for these options 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); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); Image* image = new Image; image->mWidth = width; image->mHeight = height; image->mGLTextureID = textureID; return image; } void OglGraphics::DestroyImage(Image* i) { i->FreeRawData(); delete i; } // Fonts int OglGraphics::DefaultFont() const { return mDefaultFont; } // For weight, 400 is normal and 700 is bold int OglGraphics::CreateNewFont(const char* fontName, float size, int weight) { return mText.LoadFont(fontName, size, weight); } //////////////////////////////////////////////////////////// // HELPER INIT METHODS //////////////////////////////////////////////////////////// void OglGraphics::InitDebugMsgSystem() { // DEBUG MESSAGE TYPES mDebugMsgTypes[GL_DEBUG_TYPE_ERROR] = "ERROR"; mDebugMsgTypes[GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR] = "DEPRECATED_BEHAVIOR"; mDebugMsgTypes[GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR] = "UNDEFINED_BEHAVIOR"; mDebugMsgTypes[GL_DEBUG_TYPE_PORTABILITY] = "PORTABILITY"; mDebugMsgTypes[GL_DEBUG_TYPE_PERFORMANCE] = "PERFORMANCE"; mDebugMsgTypes[GL_DEBUG_TYPE_OTHER] = "OTHER"; mDebugMsgTypes[GL_DEBUG_TYPE_MARKER] = "MARKER"; mDebugMsgTypes[GL_DEBUG_TYPE_PUSH_GROUP] = "PUSH_GROUP"; mDebugMsgTypes[GL_DEBUG_TYPE_POP_GROUP] = "POP_GROUP"; // DEBUG MESSAGE SOURCES mDebugMsgSources[GL_DEBUG_SOURCE_API] = "API"; mDebugMsgSources[GL_DEBUG_SOURCE_WINDOW_SYSTEM] = "WINDOW_SYSTEM"; mDebugMsgSources[GL_DEBUG_SOURCE_SHADER_COMPILER] = "SHADER_COMPILER"; mDebugMsgSources[GL_DEBUG_SOURCE_THIRD_PARTY] = "THIRD_PARTY"; mDebugMsgSources[GL_DEBUG_SOURCE_APPLICATION] = "APPLICATION"; mDebugMsgSources[GL_DEBUG_SOURCE_OTHER] = "OTHER"; // DEBUG MESSAGE SEVERITY mDebugMsgSeverity[GL_DEBUG_SEVERITY_HIGH] = "HIGH"; mDebugMsgSeverity[GL_DEBUG_SEVERITY_MEDIUM] = "MEDIUM"; mDebugMsgSeverity[GL_DEBUG_SEVERITY_LOW] = "LOW"; mDebugMsgSeverity[GL_DEBUG_SEVERITY_NOTIFICATION] = "NOTIFICATION"; glEnable(GL_DEBUG_OUTPUT); glDebugMessageCallback(MessageCallback, 0); } void OglGraphics::InitImageSystem() { // Setup geometry // https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites GLuint VBO; GLfloat vertices[] = { // Pos // Tex 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f }; // GLfloat vertices[] = { // // Pos // Tex // 0.0f, 1.0f, 0.0f, 1.0f, // 1.0f, 0.0f, 1.0f, 0.0f, // 0.0f, 0.0f, 0.0f, 0.0f, // 0.0f, 1.0f, 0.0f, 1.0f, // 1.0f, 1.0f, 1.0f, 1.0f, // 1.0f, 0.0f, 1.0f, 0.0f // }; glGenVertexArrays(1, &mImageVAO); glGenBuffers(1, &VBO); glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glBindVertexArray(mImageVAO); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (GLvoid*)0); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); // Setup Shader Program mImageShader.SetSource(glShader::TYPE_VERTEX, OGLDefaultShaders.DefaultSpriteVertex, false); mImageShader.SetSource(glShader::TYPE_FRAGMENT, OGLDefaultShaders.DefaultSpriteFragment, false); if (!mImageShader.BuildProgram()) { Logger::Log(LogCategory::GRAPHICS, LogLevel::ERROR, "Unable to build default image shader program"); } } void OglGraphics::InitShapeSystem() { // Create rectangle buffers glGenVertexArrays(1, &mRectVAO); glGenBuffers(1, &mRectVBO); glBindVertexArray(mRectVAO); glBindBuffer(GL_ARRAY_BUFFER, mRectVBO); glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 6 * 4, NULL, GL_DYNAMIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), 0); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); // Create Ellipse buffers glGenVertexArrays(1, &mEllipseVAO); glGenBuffers(1, &mEllipseVBO); glBindVertexArray(mEllipseVAO); glBindBuffer(GL_ARRAY_BUFFER, mEllipseVBO); glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 362 * 2, NULL, GL_DYNAMIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), 0); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); // Load shape shaders mShapeShader.SetSource(glShader::TYPE_VERTEX, OGLDefaultShaders.DefaultShapeVertex, false); mShapeShader.SetSource(glShader::TYPE_FRAGMENT, OGLDefaultShaders.DefaultShapeFragment, false); if (!mShapeShader.BuildProgram()) { Logger::Log(LogCategory::GRAPHICS, LogLevel::WARNING, "Unable to build shape shader program."); } } void OglGraphics::InitTextureFrameBuffer() { // Frame buffer glGenFramebuffers(1, &mFBO); glBindFramebuffer(GL_FRAMEBUFFER, mFBO); // Texture glGenTextures(1, &mpFBTexture->mGLTextureID); glBindTexture(GL_TEXTURE_2D, mpFBTexture->mGLTextureID); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, mFBWidth, mFBHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glBindTexture(GL_TEXTURE_2D, 0); // Attach texture glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mpFBTexture->mGLTextureID, 0); // Render Buffer for depth/stencil testing glGenRenderbuffers(1, &mRBO); glBindRenderbuffer(GL_RENDERBUFFER, mRBO); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, mFBWidth, mFBHeight); glBindRenderbuffer(GL_RENDERBUFFER, 0); // Atach the render buffer glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, mRBO); if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { Logger::Log(LogCategory::GRAPHICS, LogLevel::WARNING, "Unable to initialize framebuffer for rendering to a texture"); } glBindFramebuffer(GL_FRAMEBUFFER, 0); } }