#include "Renderer.h" #include #include #include #include #include #include "imgui/imgui.h" #include "imgui/imgui_impl_sdl_gl3.h" #include #include #include #include "Shader.h" using namespace gl; const GLfloat rectVertData[]{ 0,0,0, 1,0,0, 0,1,0, 1,1,0 }; glm::mat4 screenVPmat; double widthmult; double heightmult; GLuint LoadShader(const char* path, GLenum shadertype) { GLuint shaderId = glCreateShader(shadertype); std::string shadersource; std::ifstream shaderstream(path, std::ios::in); if (shaderstream.is_open()) { std::string line; while (getline(shaderstream, line)) { shadersource += line + "\n"; } shaderstream.close(); } else { SDL_LogError(SDL_LOG_CATEGORY_RENDER, "ERROR: Could not open shader file \"%s\".\n", path); getchar(); return 0; } GLboolean result = GL_FALSE; int infologlength = 0; SDL_Log("Compiling shader: %s\n", path); char const * source = shadersource.c_str(); glShaderSource(shaderId, 1, &source, NULL); glCompileShader(shaderId); glGetShaderiv(shaderId, GL_COMPILE_STATUS, &result); glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &infologlength); if (infologlength > 0) { std::vector errorlog(infologlength + 1); glGetShaderInfoLog(shaderId, infologlength, NULL, &errorlog[0]); SDL_LogWarn(SDL_LOG_CATEGORY_RENDER, "Errors in \"%s\":\n%s\n", path, &errorlog[0]); } SDL_LogDebug(SDL_LOG_CATEGORY_RENDER, "Done."); return shaderId; } GLuint CreateShaderProgram(GLuint vertshader, GLuint fragshader) { SDL_LogDebug(SDL_LOG_CATEGORY_RENDER, "Linking shader program...\n"); GLuint programId = glCreateProgram(); glAttachShader(programId, vertshader); glAttachShader(programId, fragshader); glLinkProgram(programId); GLboolean result = GL_FALSE; int infologlength = 0; glGetShaderiv(programId, GL_LINK_STATUS, &result); glGetShaderiv(programId, GL_INFO_LOG_LENGTH, &infologlength); if (infologlength > 0) { std::vector errorlog(infologlength + 1); glGetShaderInfoLog(programId, infologlength, NULL, &errorlog[0]); SDL_LogWarn(SDL_LOG_CATEGORY_RENDER, "Errors when creating shader program:\n%s\n", &errorlog[0]); } glDetachShader(programId, vertshader); glDetachShader(programId, fragshader); SDL_LogDebug(SDL_LOG_CATEGORY_RENDER, "Done."); return programId; } Renderer::Renderer() { window = nullptr; } Renderer::~Renderer() { ImGui_ImplSdlGL3_Shutdown(); sprites.clear(); auto it = textures.begin(); while (it != textures.end()) { glDeleteTextures(1, &it->second.id); it++; } textures.clear(); glDeleteBuffers(1, &spriteVBuf); SDL_GL_DeleteContext(context); SDL_DestroyWindow(window); window = nullptr; SDL_LogDebug(SDL_LOG_CATEGORY_RENDER, "Renderer destroyed."); } Shader mainshader; Shader wireshader; GLuint spriteVertArrayId; void MessageCallback( GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam ) { const char* sev; switch (severity) { case GL_DEBUG_SEVERITY_HIGH: sev = "HIGH"; break; case GL_DEBUG_SEVERITY_MEDIUM: sev = "MEDIUM"; break; case GL_DEBUG_SEVERITY_LOW: sev = "LOW"; break; case GL_DEBUG_SEVERITY_NOTIFICATION: sev = "NOTIFY"; break; default: sev = "UKNOWN"; } fprintf( stderr, "GL CALLBACK: %s type = 0x%x, severity = %s, message = %s\n", ( type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : "" ), type, sev, message ); } bool Renderer::Init(std::string title, int width, int height) { SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); window = SDL_CreateWindow(title.data(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); if (window == NULL) { SDL_LogCritical(SDL_LOG_CATEGORY_RENDER,"Failed to create a window!\n"); return false; } context = SDL_GL_CreateContext(window); if (context == NULL) { SDL_LogCritical(SDL_LOG_CATEGORY_RENDER,"Failed to create OpenGL context!\n"); return false; } glbinding::Binding::initialize(); glEnable(GL_DEBUG_OUTPUT); //glDebugMessageCallback((GLDEBUGPROC)MessageCallback, 0); glClearColor(0, 0, 0, 1); ImGui_ImplSdlGL3_Init(window); glGenVertexArrays(1, &spriteVertArrayId); glBindVertexArray(spriteVertArrayId); glGenBuffers(1, &spriteVBuf); glBindBuffer(GL_ARRAY_BUFFER, spriteVBuf); glBufferData(GL_ARRAY_BUFFER, sizeof(rectVertData), rectVertData, GL_STATIC_DRAW); mainshader = Shader("assets/main.vert", "assets/main.frag"); mainshader.print_values(); wireshader = Shader("assets/wireframe.vert", "assets/wireframe.frag"); set_window_size(width, height); SDL_LogDebug(SDL_LOG_CATEGORY_RENDER, "Renderer initialized.\n"); return true; } void Renderer::set_title(const char *title) { SDL_SetWindowTitle(window, title); } void Renderer::set_window_size(int width, int height) { SDL_SetWindowSize(window, width, height); // temp fix for sprites looking ugly af with an odd window resolution // but we're doing it after setting the actual window size, // to prevent the game from locking if it was just maximized // TODO: Figure this out. if (width % 2 == 1) { width++; } if (height % 2 == 1) { height++; } glViewport(0, 0, width, height); windowwidth = width; windowheight = height; widthmult = 1. / windowwidth * 2; heightmult = 1. / windowheight * 2; glm::mat4 projection = glm::ortho(-1, 1, 1, -1); glm::mat4 view = glm::scale(glm::vec3(widthmult, heightmult, 1)) * glm::translate(glm::vec3(-windowwidth / 2, -windowheight / 2, 0)); screenVPmat = projection * view; SDL_LogDebug(SDL_LOG_CATEGORY_RENDER, "Window size set to %dx%d.\n", windowwidth, windowheight); } void Renderer::set_clear_color(float r, float g, float b, float a) { glClearColor(r, g, b, a); } void Renderer::set_clear_color(Color col) { glClearColor(col.r, col.g, col.b, col.a); } void Renderer::set_vsync_enabled(bool enabled) { SDL_GL_SetSwapInterval(enabled ? 1 : 0); } bool Renderer::is_vsync_enabled() { return SDL_GL_GetSwapInterval() == 1; } void Renderer::set_wireframes_enabled(bool enabled) { wireframe = enabled; } bool Renderer::is_wireframes_enabled() { return wireframe; } int Renderer::get_renderer_width() { return windowwidth; } int Renderer::get_renderer_height() { return windowheight; } bool Renderer::ImguiProcessEvents(SDL_Event *e) { return ImGui_ImplSdlGL3_ProcessEvent(e); } void Renderer::ImguiNewFrame() { ImGui_ImplSdlGL3_NewFrame(window); } Texture * Renderer::LoadTexture(std::string path) { auto it = textures.find(path); if (it != textures.end()) return &it->second; // Texture already loaded SDL_LogVerbose(SDL_LOG_CATEGORY_RENDER, "Loading texture: %s\n", path.c_str()); // Texture not loaded, let's load it. SDL_Surface* surface = SDL_LoadBMP(path.c_str()); SDL_LogVerbose(SDL_LOG_CATEGORY_RENDER, "Loaded surface."); if (surface != NULL) { GLuint textureId; glGenTextures(1, &textureId); glBindTexture(GL_TEXTURE_2D, textureId); GLenum mode = GL_RGB; if (surface->format->BytesPerPixel == 4) { mode = GL_RGBA; } glPixelStorei(GL_UNPACK_ALIGNMENT, 4); glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); SDL_LogVerbose(SDL_LOG_CATEGORY_RENDER, "Sending %dx%d texture to GPU...", surface->w, surface->h); glTexImage2D(GL_TEXTURE_2D, 0, mode, surface->w, surface->h, 0, mode, GL_UNSIGNED_BYTE, surface->pixels); SDL_LogVerbose(SDL_LOG_CATEGORY_RENDER, "Loaded texture to GPU."); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); Texture t{}; t.id = textureId; t.w = surface->w; t.h = surface->h; SDL_FreeSurface(surface); SDL_LogVerbose(SDL_LOG_CATEGORY_RENDER, "Freed surface."); textures.insert(std::pair(path, t)); SDL_LogDebug(SDL_LOG_CATEGORY_RENDER, "Loaded texture \"%s\"\n", path.c_str()); return &textures[path]; } else { SDL_LogError(SDL_LOG_CATEGORY_RENDER, "Could not load texture \"%s\"\n\t%s\n", path.c_str(), SDL_GetError()); return nullptr; } } Sprite Renderer::CreateSprite(std::string path, int x, int y, int w, int h) { Sprite sprite; sprite.region.x = x; sprite.region.y = y; sprite.region.w = w; sprite.region.h = h; sprite.texture = LoadTexture(std::move(path)); if (sprite.texture != nullptr) { auto tw = (float)sprite.texture->w; auto th = (float)sprite.texture->h; const float uvs[] = { x / tw, y / th, (x + w) / tw, y / th, x / tw, (y + h) / th, (x + w) / tw, (y + h) / th, }; glCreateBuffers(1, &sprite.uvBuf); glBindBuffer(GL_ARRAY_BUFFER, sprite.uvBuf); glBufferData(GL_ARRAY_BUFFER, sizeof(uvs), uvs, GL_STATIC_DRAW); } return sprite; } void Renderer::draw_sprite(Sprite *sprite, Color fg, Color bg, int x, int y, float sx, float sy) { glm::mat4 model = glm::translate(glm::vec3(x, y, 0)) * glm::scale(glm::vec3(sprite->region.w * sx, sprite->region.h * sy, 1)) * glm::mat4(1); glm::mat4 mvp = screenVPmat * model; glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glEnable(GL_BLEND); glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD); glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO); mainshader.use(); mainshader.set_mvp(mvp); mainshader.set_color_tint(fg); mainshader.set_aux_uniform("colorbackground", bg); glBindVertexArray(spriteVertArrayId); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); glBindBuffer(GL_ARRAY_BUFFER, spriteVBuf); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr); glBindBuffer(GL_ARRAY_BUFFER, sprite->uvBuf); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, nullptr); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); if (wireframe) { wireshader.use(); wireshader.set_mvp(mvp); Color white = Color(1.0f,1.0f,1.0f,1.0f); wireshader.set_color_tint(white); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glBindVertexArray(spriteVertArrayId); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); glBindBuffer(GL_ARRAY_BUFFER, spriteVBuf); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr); glBindBuffer(GL_ARRAY_BUFFER, sprite->uvBuf); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, nullptr); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } glDisableVertexAttribArray(0); glDisableVertexAttribArray(1); } void Renderer::Clear() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } void Renderer::Present() { glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); ImGui::Render(); SDL_GL_SwapWindow(window); }