diff --git a/azur/include/azur/azur.h b/azur/include/azur/azur.h index 44ef769..fba9338 100644 --- a/azur/include/azur/azur.h +++ b/azur/include/azur/azur.h @@ -14,8 +14,12 @@ On GINT_CG, the window size can be 396x224, 198x112 or 132x75 and this configures the rendering engine for super-resolution. Returns non-zero if - the size is not one of these. */ -int azur_init(char const *title, int window_width, int window_height); + the size is not one of these. + + On OpenGL platforms, if debug is set the OpenGL debug extension is enabled + and OpenGL messages are automatically logged to stderr. */ +int azur_init( + char const *title, int window_width, int window_height, bool debug = false); /* azur_main_loop(): Run the update/render loop. diff --git a/azur/include/azur/gl/shaders.h b/azur/include/azur/gl/shaders.h index 8171974..ef8ae3f 100644 --- a/azur/include/azur/gl/shaders.h +++ b/azur/include/azur/gl/shaders.h @@ -43,6 +43,7 @@ #pragma once #include +#include #include #include #include @@ -92,11 +93,13 @@ public: /* Compile all provided code. */ bool compile(); + /* Link compiled code (usually after binding attribute locations). */ + bool link(); - /* Check whether the program has been compiled successfully. */ + /* Check whether the program has been linked successfully. */ bool isCompiled() const { - return (m_prog != 0); + return (m_prog != 0) && m_linked; } /*** Configuration ***/ @@ -116,25 +119,26 @@ public: glVertexAttribPointer(). */ template void bindVertexAttributeFP(U VertexAttr::*x, GLint size, GLenum type, - GLboolean normalized, char const *name) { - glVertexAttribPointer(getAttribute(name), size, type, normalized, - sizeof(VertexAttr), offsetOf(x)); + GLboolean normalized, GLuint id) { + glEnableVertexAttribArray(id); + glVertexAttribPointer(id, size, type, normalized, sizeof(VertexAttr), + offsetOf(x)); } /* Same for integer attributes, using glVertexAttribIPointer(). */ template void bindVertexAttributeInt( - U VertexAttr::*x, GLint size, GLenum type, char const *name) { + U VertexAttr::*x, GLint size, GLenum type, GLuint id) { glVertexAttribIPointer( - getAttribute(name), size, type, sizeof(VertexAttr), offsetOf(x)); + id, size, type, sizeof(VertexAttr), offsetOf(x)); } /* Shortcuts for binding attributes of common types, which automatically provide the type details. */ - void bindVertexAttribute(float VertexAttr::*x, char const *name); - void bindVertexAttribute(glm::vec2 VertexAttr::*x, char const *name); - void bindVertexAttribute(glm::vec3 VertexAttr::*x, char const *name); - void bindVertexAttribute(glm::vec4 VertexAttr::*x, char const *name); + void bindVertexAttribute(float VertexAttr::*x, GLuint id); + void bindVertexAttribute(glm::vec2 VertexAttr::*x, GLuint id); + void bindVertexAttribute(glm::vec3 VertexAttr::*x, GLuint id); + void bindVertexAttribute(glm::vec4 VertexAttr::*x, GLuint id); /* Set uniforms. */ void setUniform(char const *name, float f); @@ -213,6 +217,7 @@ public: protected: /* Program ID */ GLuint m_prog = 0; + bool m_linked = false; /* Vertex Array Object and Vertex Buffer Object with parameters */ GLuint m_vao = 0, m_vbo = 0; /* Size of the VBO on the GPU */ @@ -222,6 +227,8 @@ private: /* Map from program type (vertex/etc/fragment shader) to code during the construction phase. */ std::map m_code; + /* List of shader descriptors between compiling and linking. */ + std::vector m_shaders; /* List of vertices during rendering. */ std::vector m_vertices; /* Map of attribute names to shader locations. */ @@ -260,8 +267,6 @@ bool ShaderProgram::addSourceFile( template bool ShaderProgram::compile() { - /* List of shader descriptors obtained from OpenGL. */ - std::vector m_shaders; bool success = true; for(auto const &[type, code]: m_code) { @@ -272,12 +277,70 @@ bool ShaderProgram::compile() success = false; } - // TODO: No link error detection? - m_prog = success ? link(m_shaders.data(), m_shaders.size()) : 0; + /* Generate a new program */ + if(success) { + m_prog = glCreateProgram(); + if(m_prog == 0) { + azlog(ERROR, "glCreateProgram failed\n"); + success = false; + } + } - for(auto id: m_shaders) - glDeleteShader(id); + /* Attach all shaders */ + if(success) { + for(int i = 0; i < m_shaders.size(); i++) + glAttachShader(m_prog, m_shaders[i]); + } + else { + for(auto id: m_shaders) + glDeleteShader(id); + m_shaders.clear(); + } + return success; +} +template +bool ShaderProgram::link() +{ + if(!m_shaders.size()) + return false; + + GLint rc = GL_FALSE; + GLsizei log_length = 0; + bool success = true; + + azlog(INFO, "Linking program\n"); + glLinkProgram(m_prog); + + glGetProgramiv(m_prog, GL_LINK_STATUS, &rc); + if(rc == GL_FALSE) { + azlog(ERROR, "link failed!\n"); + success = false; + } + + glGetProgramiv(m_prog, GL_INFO_LOG_LENGTH, &log_length); + if(log_length > 0) { + GLchar *log = new GLchar[log_length + 1]; + glGetProgramInfoLog(m_prog, log_length, &log_length, log); + if(log_length > 0) { + azlogc(ERROR, "%s", log); + if(log[log_length - 1] != '\n') + azlogc(ERROR, "\n"); + } + delete[] log; + } + + /* Detach all shaders */ + for(int i = 0; i < m_shaders.size(); i++) { + glDetachShader(m_prog, m_shaders[i]); + glDeleteShader(m_shaders[i]); + } + + if(!success) { + glDeleteProgram(m_prog); + m_prog = 0; + } + m_linked = success; return success; } @@ -293,27 +356,27 @@ GLuint ShaderProgram::getAttribute(char const *name) } template -void ShaderProgram::bindVertexAttribute(float T::*x, char const *name) +void ShaderProgram::bindVertexAttribute(float T::*x, GLuint id) { - bindVertexAttributeFP(x, 1, GL_FLOAT, GL_FALSE, name); + bindVertexAttributeFP(x, 1, GL_FLOAT, GL_FALSE, id); } template -void ShaderProgram::bindVertexAttribute(glm::vec2 T::*x, char const *name) +void ShaderProgram::bindVertexAttribute(glm::vec2 T::*x, GLuint id) { - bindVertexAttributeFP(x, 2, GL_FLOAT, GL_FALSE, name); + bindVertexAttributeFP(x, 2, GL_FLOAT, GL_FALSE, id); } template -void ShaderProgram::bindVertexAttribute(glm::vec3 T::*x, char const *name) +void ShaderProgram::bindVertexAttribute(glm::vec3 T::*x, GLuint id) { - bindVertexAttributeFP(x, 3, GL_FLOAT, GL_FALSE, name); + bindVertexAttributeFP(x, 3, GL_FLOAT, GL_FALSE, id); } template -void ShaderProgram::bindVertexAttribute(glm::vec4 T::*x, char const *name) +void ShaderProgram::bindVertexAttribute(glm::vec4 T::*x, GLuint id) { - bindVertexAttributeFP(x, 4, GL_FLOAT, GL_FALSE, name); + bindVertexAttributeFP(x, 4, GL_FLOAT, GL_FALSE, id); } template diff --git a/azur/src/gl/init.cpp b/azur/src/gl/init.cpp index 77b7738..017df68 100644 --- a/azur/src/gl/init.cpp +++ b/azur/src/gl/init.cpp @@ -5,6 +5,8 @@ #include #include +#include + static SDL_Window *window = NULL; static SDL_GLContext glcontext = NULL; @@ -39,7 +41,40 @@ static void enter_fullscreen(SDL_Window *window) } #endif /* AZUR_PLATFORM_EMSCRIPTEN */ -int azur_init(char const *title, int window_width, int window_height) +static void gl_debug_callback(GLenum source, GLenum type, GLuint id, + GLenum severity, GLsizei length, const GLchar *message, const GLvoid *) +{ + std::string source_str {"OtherSource"}; + std::string type_str {"other-type"}; + std::string severity_str {"?"}; + (void)id; + (void)length; + + if(source == GL_DEBUG_SOURCE_API) source_str = "API"; + if(source == GL_DEBUG_SOURCE_WINDOW_SYSTEM) source_str = "WM"; + if(source == GL_DEBUG_SOURCE_SHADER_COMPILER) source_str = "Compiler"; + if(source == GL_DEBUG_SOURCE_THIRD_PARTY) source_str = "ThirdParty"; + if(source == GL_DEBUG_SOURCE_APPLICATION) source_str = "App"; + + if(type == GL_DEBUG_TYPE_ERROR) type_str = "error"; + if(type == GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR) type_str = "deprecation"; + if(type == GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR) type_str = "ub"; + if(type == GL_DEBUG_TYPE_PORTABILITY) type_str = "non-portable"; + if(type == GL_DEBUG_TYPE_PERFORMANCE) type_str = "performance"; + if(type == GL_DEBUG_TYPE_MARKER) type_str = "marker"; + if(type == GL_DEBUG_TYPE_PUSH_GROUP) type_str = "push-group"; + if(type == GL_DEBUG_TYPE_POP_GROUP) type_str = "pop-group"; + + if(severity == GL_DEBUG_SEVERITY_HIGH) severity_str = "high"; + if(severity == GL_DEBUG_SEVERITY_MEDIUM) severity_str = "medium"; + if(severity == GL_DEBUG_SEVERITY_LOW) severity_str = "low"; + if(severity == GL_DEBUG_SEVERITY_NOTIFICATION) severity_str = "note"; + + fprintf(stderr, "[OpenGL/%s/%s/%s] %s\n", + source_str.c_str(), type_str.c_str(), severity_str.c_str(), message); +} + +int azur_init(char const *title, int window_width, int window_height, bool dbg) { int rc = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER); if(rc < 0) { @@ -79,6 +114,9 @@ int azur_init(char const *title, int window_width, int window_height) return 1; } + if(dbg) + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG); + glcontext = SDL_GL_CreateContext(window); if(!glcontext) { azlog(FATAL, "SDL_GL_CreateContext: %s\n", SDL_GetError()); @@ -98,6 +136,11 @@ int azur_init(char const *title, int window_width, int window_height) } #endif + if(dbg) { + glEnable(GL_DEBUG_OUTPUT); + glDebugMessageCallback(gl_debug_callback, nullptr); + } + glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);