diff --git a/.gitignore b/.gitignore index 990936c..9e92679 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .idea/ cmake-build-debug/ +/build diff --git a/CMakeLists.txt b/CMakeLists.txt index 9053b9d..dd6e9f5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,9 @@ option(GL_VALIDATION_LAYER_BUILD_TESTS "Build tests for the OpenGL Validation La add_library(gl_validation_layer src/context.cpp src/shader.cpp + include/gl_layer/context.h + include/gl_layer/private/context.h + include/gl_layer/private/types.h ) target_include_directories(gl_validation_layer PUBLIC include/) diff --git a/include/gl_layer/context.h b/include/gl_layer/context.h index 949974e..f6722e5 100644 --- a/include/gl_layer/context.h +++ b/include/gl_layer/context.h @@ -5,13 +5,22 @@ extern "C" { #endif +typedef struct ContextGLFunctions +{ + // TODO: replace the types in the parameters with proper platform-detected ones + // TODO: evaluate whether * should be replaced with something like APIENTRYP in glad + void (*GetActiveUniform)(unsigned, unsigned, int, int*, int*, unsigned*, char*); + int (*GetUniformLocation)(unsigned, const char*); + void (*GetProgramiv)(unsigned, unsigned, int*); +}ContextGLFunctions; + /** * @brief Initialize the OpenGL Validation Layer * @param gl_version_major OpenGL context major version. * @param gl_version_minor OpenGL context minor version. * @return 0 on success, any other value on error. */ -int gl_layer_init(unsigned int gl_version_major, unsigned int gl_version_minor); +int gl_layer_init(unsigned int gl_version_major, unsigned int gl_version_minor, const ContextGLFunctions* gl_functions); /** * @brief Terminate the OpenGL Validation Layer. diff --git a/include/gl_layer/private/context.h b/include/gl_layer/private/context.h index b214856..b7dca0a 100644 --- a/include/gl_layer/private/context.h +++ b/include/gl_layer/private/context.h @@ -11,7 +11,7 @@ namespace gl_layer { class Context { public: - explicit Context(Version version); + explicit Context(Version version, const ContextGLFunctions* gl_functions); void set_output_callback(GLLayerOutputFun callback, void* user_data); @@ -20,13 +20,22 @@ class Context { void glAttachShader(unsigned int program, unsigned int shader); void glGetProgramiv(unsigned int handle, GLenum param, int* params); + void glLinkProgram(GLuint program); void glUseProgram(unsigned int handle); + void glDeleteProgram(GLuint program); + + void validate_program_bound(std::string_view func_name); + bool validate_program_status(GLuint program); + void validate_uniform_location_count(GLint location, GLsizei count); + void validate_uniform_type(GLint location, std::string_view uniform_func_name); private: Version gl_version; GLLayerOutputFun output_fun = nullptr; void* output_user_data = nullptr; + ContextGLFunctions gl; + GLuint bound_program = 0; std::unordered_map shaders{}; std::unordered_map programs{}; diff --git a/include/gl_layer/private/types.h b/include/gl_layer/private/types.h index 67b9956..86091be 100644 --- a/include/gl_layer/private/types.h +++ b/include/gl_layer/private/types.h @@ -2,10 +2,18 @@ #define GL_VALIDATION_LAYER_TYPES_H_ #include +#include +#include namespace gl_layer { -using GLenum = unsigned int; +using GLenum = std::uint32_t; +using GLuint = std::uint32_t; +using GLint = std::int32_t; +using GLsizei = std::int32_t; +using GLchar = char; +using GLfloat = float; +using GLboolean = bool; enum GLShaderInfoParam { GL_SHADER_TYPE = 0x8B4F, @@ -13,7 +21,21 @@ enum GLShaderInfoParam { GL_COMPILE_STATUS = 0x8B81, GL_LINK_STATUS = 0x8B82, GL_INFO_LOG_LENGTH = 0x8B84, - GL_SHADER_SOURCE_LENGTH = 0x8B88 + GL_SHADER_SOURCE_LENGTH = 0x8B88, + GL_ACTIVE_UNIFORM_MAX_LENGTH = 0x8B87, + GL_ACTIVE_UNIFORMS = 0x8B86 +}; + +enum class CompileStatus { + UNCHECKED = -1, + FAILED = 0, + OK = 1, +}; + +enum class LinkStatus { + UNCHECKED = -1, + FAILED = 0, + OK = 1, }; const char* enum_str(GLenum v); @@ -23,20 +45,31 @@ struct Version { unsigned int minor = 0; }; +struct UniformInfo { + GLint array_size; + GLenum type; + // std::string name // the name is probably not useful for us + + bool operator==(const UniformInfo& other) const { + return array_size == other.array_size && type == other.type; + } +}; + // Represents a shader returned by glCreateShader struct Shader { unsigned int handle {}; // If this is -1, this means the compile status was never checked by the host application. // This is an error that should be reported. - int compile_status = -1; + CompileStatus compile_status = CompileStatus::UNCHECKED; }; // Represents a shader program returned by glCreateProgram struct Program { unsigned int handle {}; std::vector shaders {}; + std::unordered_map uniforms {}; // If this is -1, this means the status was never checked by the host application. - int link_status = -1; + LinkStatus link_status = LinkStatus::UNCHECKED; }; } diff --git a/src/context.cpp b/src/context.cpp index 8db7747..c186b62 100644 --- a/src/context.cpp +++ b/src/context.cpp @@ -26,7 +26,8 @@ static void default_output_func(const char* text, void* = nullptr) { printf("%s\n", text); } -Context::Context(Version version) : gl_version(version) { +Context::Context(Version version, const ContextGLFunctions* gl_functions) + : gl_version(version), gl(*gl_functions) { output_fun = &default_output_func; } @@ -42,12 +43,16 @@ Context* g_context = nullptr; } // namespace gl_layer -static bool is_func(const char* name, std::string_view func) { +static bool is_func(std::string_view name, std::string_view func) { return func == name; } -int gl_layer_init(unsigned int gl_version_major, unsigned int gl_version_minor) { - gl_layer::g_context = new gl_layer::Context(gl_layer::Version{ gl_version_major, gl_version_minor }); +static bool func_has(std::string_view name, std::string_view substr) { + return name.find(substr) != std::string_view::npos; +} + +int gl_layer_init(unsigned int gl_version_major, unsigned int gl_version_minor, const ContextGLFunctions* gl_functions) { + gl_layer::g_context = new gl_layer::Context(gl_layer::Version{ gl_version_major, gl_version_minor }, gl_functions); if (gl_layer::g_context) return 0; return -1; @@ -57,12 +62,16 @@ void gl_layer_terminate() { delete gl_layer::g_context; } -void gl_layer_callback(const char* name, void* func_ptr, int num_args, ...) { +void gl_layer_callback(const char* name_c, void* func_ptr, int num_args, ...) { if (!gl_layer::g_context) { // Report error: context not initialized. return; } + using namespace gl_layer; // GL types + + std::string_view name = name_c; + va_list args; va_start(args, num_args); @@ -86,6 +95,25 @@ void gl_layer_callback(const char* name, void* func_ptr, int num_args, ...) { } else if (is_func(name, "glUseProgram")) { unsigned int program = va_arg(args, unsigned int); gl_layer::g_context->glUseProgram(program); + } else if (is_func(name, "glLinkProgram")) { + unsigned int program = va_arg(args, unsigned int); + gl_layer::g_context->glLinkProgram(program); + } else if (func_has(name, "glUniform")) { + auto location = va_arg(args, GLint); + GLsizei count = 1; + + g_context->validate_program_bound(name); + + // array version of a function + if (func_has(name, "v")) { + count = va_arg(args, GLsizei); + } + + g_context->validate_uniform_location_count(location, count); + + for (GLsizei i = 0; i < count; i++) { + g_context->validate_uniform_type(location + i, name); + } } va_end(args); diff --git a/src/shader.cpp b/src/shader.cpp index 7bd6770..212a332 100644 --- a/src/shader.cpp +++ b/src/shader.cpp @@ -1,6 +1,7 @@ #include #include - +#include +#include namespace gl_layer { @@ -14,7 +15,7 @@ void Context::glCompileShader(unsigned int handle) { return; } - shaders.insert({ handle, Shader { handle } }); + shaders.emplace(handle, Shader{ handle }); } void Context::glGetShaderiv(unsigned int handle, GLenum param, int* params) { @@ -27,7 +28,7 @@ void Context::glGetShaderiv(unsigned int handle, GLenum param, int* params) { return; } - it->second.compile_status = *params; + it->second.compile_status = static_cast(*params); } } @@ -39,9 +40,9 @@ void Context::glAttachShader(unsigned int program, unsigned int shader) { return; } - if (it->second.compile_status == -1) { + if (it->second.compile_status == CompileStatus::UNCHECKED) { output_fmt("glAttachShader(program = %u, shader = %u): Always check shader compilation status before trying to use the object.", program, shader); - } else if (it->second.compile_status == false) { + } else if (it->second.compile_status == CompileStatus::FAILED) { output_fmt("glAttachShader(program = %u, shader = %u): Attached shader has a compilation error.", program, shader); } @@ -67,26 +68,135 @@ void Context::glGetProgramiv(unsigned int handle, GLenum param, int* params) { return; } - it->second.link_status = *params; + it->second.link_status = static_cast(*params); + } +} + +void Context::glLinkProgram(GLuint program) +{ + auto program_it = programs.find(program); + if (program_it == programs.end()) { + output_fmt("glLinkProgram(program = %u): Invalid program handle.", program); + return; + } + + auto& programInfo = program_it->second; + + // Store all active uniforms in the program (location, array size, and type) + GLint uniform_count{}; + gl.GetProgramiv(program, GL_ACTIVE_UNIFORMS, &uniform_count); + if (uniform_count > 0) + { + GLint max_name_len{}; + gl.GetProgramiv(program, GL_ACTIVE_UNIFORM_MAX_LENGTH, &max_name_len); + + auto uniform_name = std::make_unique(max_name_len); + + for (int i = 0; i < uniform_count; i++) + { + GLsizei uniform_name_length{}; + UniformInfo uniform_info{}; + gl.GetActiveUniform( + program, + i, + max_name_len, + &uniform_name_length, + &uniform_info.array_size, + &uniform_info.type, + uniform_name.get()); + + auto loc = gl.GetUniformLocation(program, uniform_name.get()); + + programInfo.uniforms.emplace(loc, uniform_info); + } } } void Context::glUseProgram(unsigned int handle) { - auto it = programs.find(handle); + if (handle == 0) { + bound_program = 0; + return; + } + + if (!validate_program_status(handle)) { + return; + } + + // TODO: add optional performance warning for rebinding the same program + //if (handle == bound_program) { + // output_fmt("glUseProgram(program = %u): Program is already bound.", handle); + //} + + bound_program = handle; +} + +void Context::glDeleteProgram(GLuint program) +{ + auto it = programs.find(program); if (it == programs.end()) { - output_fmt("glUseProgram(program = %u): Invalid program handle.", handle); + output_fmt("glUseProgram(program = %u): Invalid program handle.", program); return; } - if (it->second.link_status == -1) { - output_fmt("glUseProgram(program = %u): Always check program link status before trying to use the object.", handle); + // note: this does not change which program is bound, even if it becomes invalid here + + programs.erase(it); +} + +void Context::validate_program_bound(std::string_view func_name) +{ + if (bound_program == 0) { + output_fmt("%s: No program bound.", func_name.data()); return; } +} + +bool Context::validate_program_status(GLuint program) +{ + auto it = programs.find(program); + if (it == programs.end()) { + output_fmt("glUseProgram(program = %u): Invalid program handle.", program); + return false; + } - if (it->second.link_status == false) { - output_fmt("glUseProgram(program = %u): Program has a linker error.", handle); + if (it->second.link_status == LinkStatus::UNCHECKED) { + output_fmt("glUseProgram(program = %u): Always check program link status before trying to use the object.", program); + return false; + } + + if (it->second.link_status == LinkStatus::FAILED) { + output_fmt("glUseProgram(program = %u): Program has a linker error.", program); + return false; + } + + return true; +} + +void Context::validate_uniform_location_count(GLint location, GLsizei count) +{ + auto program_it = programs.find(bound_program); + if (program_it != programs.end()) { + return; + } + + auto& [_, program] = *program_it; + + // TODO: this doesn't work with uniform arrays because only the base uniform of an array is registered in the map + validate_program_status(bound_program); + auto uniform_it = program.uniforms.find(location); + if (uniform_it == program.uniforms.end()) { + output_fmt("glUniform(location = %d, count = %d): Uniform does not exist.", location, count); return; } + + if (count > uniform_it->second.array_size) { + output_fmt("glUniform(location = %d, count = %d): count exceeds uniform array length.", location, count); + } } +// validates that the glUniform* function being called matches the type of the uniform being set +void Context::validate_uniform_type(GLint location, std::string_view uniform_func_name) +{ + // TODO: implement +} } \ No newline at end of file diff --git a/tests/main.cpp b/tests/main.cpp index 5d35792..11df711 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include #include @@ -18,32 +20,22 @@ void gl_error_callback([[maybe_unused]] GLenum source, std::cout << "OpenGL Error (default validation): " << type << " message: " << message << std::endl; } -unsigned int create_shader(const char* vtx_path, const char* frag_path) { - using namespace std::literals::string_literals; - - std::fstream file(vtx_path); - - if (!file.good()) { - return 0; - } - - std::stringstream buf; - buf << file.rdbuf(); - - std::string vtx_source(buf.str()); +std::optional load_file(std::string_view path) +{ + std::fstream file(path.data()); - file.close(); - buf = std::stringstream{}; // reset buffer - file.open(frag_path); + if (!file.good()) { + return std::nullopt; + } - if (!file.good()) { - return 0; - } + std::stringstream buf; + buf << file.rdbuf(); - buf << file.rdbuf(); + return std::string(buf.str()); +} - std::string frag_source(buf.str()); - buf = std::stringstream{}; +unsigned int create_shader(std::string_view vtx_source, std::string_view frag_source) { + using namespace std::literals::string_literals; unsigned int vtx_shader = 0, frag_shader = 0; vtx_shader = glCreateShader(GL_VERTEX_SHADER); @@ -51,8 +43,8 @@ unsigned int create_shader(const char* vtx_path, const char* frag_path) { // This is wrapped inside a lambda to limit the scope of vtx_carr and // frag_carr [&vtx_source, &frag_source, &vtx_shader, &frag_shader]() { - const char* vtx_carr = vtx_source.c_str(); - const char* frag_carr = frag_source.c_str(); + const char* vtx_carr = vtx_source.data(); + const char* frag_carr = frag_source.data(); glShaderSource(vtx_shader, 1, &vtx_carr, nullptr); glShaderSource(frag_shader, 1, &frag_carr, nullptr); }(); @@ -102,6 +94,17 @@ unsigned int create_shader(const char* vtx_path, const char* frag_path) { return shaderProgram; } +unsigned create_shader_from_files(std::string_view vtx_path, std::string_view frag_path) +{ + auto vtx_source = load_file(vtx_path); + if (!vtx_source) return 0; + + auto frag_source = load_file(frag_path); + if (!frag_source) return 0; + + return create_shader(*vtx_source, *frag_source); +} + int main() { // Initialize GLFW. @@ -111,14 +114,18 @@ int main() { glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE); glfwWindowHint(GLFW_SRGB_CAPABLE, GL_TRUE); - + GLFWwindow* window = glfwCreateWindow(800, 600, "OpenGL Validation Layer - tests", nullptr, nullptr); glfwMakeContextCurrent(window); // Load GLAD. gladLoadGLLoader((GLADloadproc)glfwGetProcAddress); // Initialize our library. - int error = gl_layer_init(3, 3); + ContextGLFunctions glFuncs; + glFuncs.GetActiveUniform = glad_glGetActiveUniform; + glFuncs.GetUniformLocation = glad_glGetUniformLocation; + glFuncs.GetProgramiv = glad_glGetProgramiv; + int error = gl_layer_init(3, 3, &glFuncs); if (error) { std::cerr << "Could not initialize OpenGL Validation Layer\n"; return -1; @@ -128,15 +135,41 @@ int main() { // glDebugMessageCallback(&gl_error_callback, nullptr); glad_set_post_callback(&gl_layer_callback); - unsigned int program = create_shader("shaders/vert.glsl", "shaders/frag.glsl"); + const char* vtx_source2 = R"( +#version 330 core + +layout(location = 0) in vec3 iPos; + +void main() { + gl_Position = vec4(iPos, 1); +} +)"; + + const char* frag_source2 = R"( +#version 330 core + +uniform vec3 u_color; +uniform float u_alpha; + +out vec4 o_color; + +void main() { + o_color = vec4(u_color, u_alpha); +} +)"; + + unsigned int program = create_shader_from_files("shaders/vert.glsl", "shaders/frag.glsl"); + unsigned int program2 = create_shader(vtx_source2, frag_source2); // Main application loop while(!glfwWindowShouldClose(window)) { glfwPollEvents(); glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glUseProgram(program); + glUseProgram(program2); + glUniform1f(glGetUniformLocation(program2, "u_alpha"), 0); // ok + glUniform1f(-1, 0); // bad glfwSwapBuffers(window); }