From 5df7608fa8ace122cbb0df37367a09ed75e3239f Mon Sep 17 00:00:00 2001 From: "C. J. Howard" Date: Thu, 29 Apr 2021 16:14:50 +0800 Subject: [PATCH] Extend functionality of the shader_program class and improve its error handling --- src/gl/shader-object.cpp | 10 +- src/gl/shader-object.hpp | 7 +- src/gl/shader-program.cpp | 156 +++++++++++++++++++----- src/gl/shader-program.hpp | 98 +++++++++++++-- src/resources/shader-program-loader.cpp | 23 +++- 5 files changed, 240 insertions(+), 54 deletions(-) diff --git a/src/gl/shader-object.cpp b/src/gl/shader-object.cpp index 8733a8b..584ae09 100644 --- a/src/gl/shader-object.cpp +++ b/src/gl/shader-object.cpp @@ -50,7 +50,7 @@ shader_object::shader_object(shader_stage stage): shader_object::~shader_object() { - // Flags the OpenGL shader object for deletion + // Flag the OpenGL shader object for deletion glDeleteShader(gl_shader_id); } @@ -64,11 +64,11 @@ void shader_object::source(const char* buffer, std::size_t size) switch (glGetError()) { case GL_INVALID_VALUE: - throw std::runtime_error("Shader object handle is not a value generated by OpenGL."); + throw std::runtime_error("OpenGL shader object handle is not a value generated by OpenGL."); break; case GL_INVALID_OPERATION: - throw std::runtime_error("Shader object handle is not a shader object."); + throw std::runtime_error("OpenGL shader object handle is not a shader object."); break; } } @@ -82,11 +82,11 @@ bool shader_object::compile() switch (glGetError()) { case GL_INVALID_VALUE: - throw std::runtime_error("Shader object handle is not a value generated by OpenGL."); + throw std::runtime_error("OpenGL shader object handle is not a value generated by OpenGL."); break; case GL_INVALID_OPERATION: - throw std::runtime_error("Shader object handle is not a shader object."); + throw std::runtime_error("OpenGL shader object handle is not a shader object."); break; } diff --git a/src/gl/shader-object.hpp b/src/gl/shader-object.hpp index ff086e2..7d8f70a 100644 --- a/src/gl/shader-object.hpp +++ b/src/gl/shader-object.hpp @@ -77,7 +77,7 @@ public: /// Returns the shader stage of this shader object. shader_stage get_stage() const; - /// Returns the shader object info log, which is updated when the shader is compiled. + /// Returns the shader object info log, which is updated when the shader object is compiled. const std::string& get_info_log() const; /// Returns `true` if the shader object has been successfully compiled, `false` otherwise. @@ -88,12 +88,11 @@ public: private: friend class shader_program; - - + unsigned int gl_shader_id; shader_stage stage; - bool compiled; std::string info_log; + bool compiled; }; inline shader_stage shader_object::get_stage() const diff --git a/src/gl/shader-program.cpp b/src/gl/shader-program.cpp index 1e3a943..2bbad68 100644 --- a/src/gl/shader-program.cpp +++ b/src/gl/shader-program.cpp @@ -26,42 +26,145 @@ namespace gl { -shader_program::shader_program(const std::list& shaders): - gl_program_id(0) +shader_program::shader_program(): + gl_program_id(0), + linked(false) { + // Create an OpenGL shader program gl_program_id = glCreateProgram(); - - for (shader_object* shader: shaders) + + // Handle OpenGL errors + if (!gl_program_id) { - glAttachShader(gl_program_id, shader->gl_shader_id); + throw std::runtime_error("An error occurred while creating an OpenGL shader program."); } +} - glLinkProgram(gl_program_id); +shader_program::~shader_program() +{ + // Delete shader inputs + free_inputs(); - GLint status; - glGetProgramiv(gl_program_id, GL_LINK_STATUS, &status); - if (status == GL_FALSE) + // Detach all shader objects + detach_all(); + + // Delete the OpenGL shader program + glDeleteProgram(gl_program_id); +} + +void shader_program::attach(const shader_object* object) +{ + if (attached_objects.find(object) != attached_objects.end()) { - throw std::runtime_error(get_info_log().c_str()); + throw std::runtime_error("Shader object is already attached to the shader program."); } - - for (shader_object* shader: shaders) + else { - glDetachShader(gl_program_id, shader->gl_shader_id); + // Check that both the OpenGL shader program and OpenGL shader object are valid + if (glIsProgram(gl_program_id) != GL_TRUE) + throw std::runtime_error("OpenGL shader program is not a valid program object."); + if (glIsShader(object->gl_shader_id) != GL_TRUE) + throw std::runtime_error("OpenGL shader object is not a valid shader object."); + + // Attach the OpenGL shader object to the OpenGL shader program + glAttachShader(gl_program_id, object->gl_shader_id); + + // Handle OpenGL errors + switch (glGetError()) + { + case GL_INVALID_OPERATION: + throw std::runtime_error("OpenGL shader object is already attached to the shader program."); + break; + } + + // Add shader object to set of attached objects + attached_objects.insert(object); } +} - // Find shader inputs - find_inputs(); +void shader_program::detach(const shader_object* object) +{ + if (attached_objects.find(object) == attached_objects.end()) + { + throw std::runtime_error("Shader object is not attached to the shader program."); + } + else + { + // Check that both the OpenGL shader program and OpenGL shader object are valid + if (glIsProgram(gl_program_id) != GL_TRUE) + throw std::runtime_error("OpenGL shader program is not a valid program object."); + if (glIsShader(object->gl_shader_id) != GL_TRUE) + throw std::runtime_error("OpenGL shader object is not a valid shader object."); + + // Detach the OpenGL shader object from the OpenGL shader program + glDetachShader(gl_program_id, object->gl_shader_id); + + // Handle OpenGL errors + switch (glGetError()) + { + case GL_INVALID_OPERATION: + throw std::runtime_error("OpenGL shader object is not attached to the shader program."); + break; + } + + // Remove shader object from set of attached objects + attached_objects.erase(object); + } } -shader_program::~shader_program() +void shader_program::detach_all() { - glDeleteProgram(gl_program_id); + while (!attached_objects.empty()) + detach(*attached_objects.begin()); +} - for (shader_input* input: inputs) +bool shader_program::link() +{ + // Check that the OpenGL shader program is valid + if (glIsProgram(gl_program_id) != GL_TRUE) + throw std::runtime_error("OpenGL shader program is not a valid program object."); + + // Link OpenGL shader program + glLinkProgram(gl_program_id); + + // Handle OpenGL errors + switch (glGetError()) { - delete input; + case GL_INVALID_OPERATION: + throw std::runtime_error("OpenGL shader program is the currently active program object and transform feedback mode is active."); + break; } + + // Get OpenGL shader program linking status + GLint gl_link_status; + glGetProgramiv(gl_program_id, GL_LINK_STATUS, &gl_link_status); + linked = (gl_link_status == GL_TRUE); + + // Get OpenGL shader program info log length + GLint gl_info_log_length; + glGetProgramiv(gl_program_id, GL_INFO_LOG_LENGTH, &gl_info_log_length); + + if (gl_info_log_length > 0) + { + // Resize string to accommodate OpenGL shader program info log + info_log.resize(gl_info_log_length); + + // Read OpenGL shader program info log into string + glGetProgramInfoLog(gl_program_id, gl_info_log_length, &gl_info_log_length, info_log.data()); + + // Remove redundant null terminator from string + info_log.pop_back(); + } + else + { + // Empty info log + info_log.clear(); + } + + // Find shader inputs + find_inputs(); + + return linked; } void shader_program::find_inputs() @@ -210,19 +313,14 @@ void shader_program::find_inputs() delete[] uniform_name; } -std::string shader_program::get_info_log() const +void shader_program::free_inputs() { - GLint length; - glGetProgramiv(gl_program_id, GL_INFO_LOG_LENGTH, &length); - - if (length > 0) + for (shader_input* input: inputs) { - std::string log(length, '\0'); - glGetProgramInfoLog(gl_program_id, length, &length, &log[0]); - return log; + delete input; } - - return std::string(); + + inputs.clear(); } } // namespace gl diff --git a/src/gl/shader-program.hpp b/src/gl/shader-program.hpp index 672628a..d2d0cc0 100644 --- a/src/gl/shader-program.hpp +++ b/src/gl/shader-program.hpp @@ -20,10 +20,10 @@ #ifndef ANTKEEPER_GL_SHADER_PROGRAM_HPP #define ANTKEEPER_GL_SHADER_PROGRAM_HPP -#include #include -#include #include +#include +#include namespace gl { @@ -31,16 +31,80 @@ class shader_object; class rasterizer; class shader_input; +/** + * Shader program which can be linked to shader objects and executed. + * + * @see gl::shader_object + */ class shader_program { public: /** - * Creates a shader program. + * Creates an empty shader program. * - * @param shaders List of shaders to be linked to the program. Note that after the shader program has been created, these shaders can be deleted if desired with no effect on the shader program. + * @exception std::runtime_error An error occurred while creating an OpenGL shader program. + */ + shader_program(); + + /** + * Destroys a shader program. */ - explicit shader_program(const std::list& shaders); ~shader_program(); + + /** + * Attaches a shader object to the shader program. Attaching a shader object has no effect on a shader program until shader_program::link() is called. + * + * @param object Shader object to attach. + * + * @exception std::runtime_error Shader object is already attached to the shader program. + * @exception std::runtime_error OpenGL shader program is not a valid program object. + * @exception std::runtime_error OpenGL shader object is not a valid shader object. + * @exception std::runtime_error OpenGL shader object is already attached to the shader program. + * + * @see shader_program::link() + */ + void attach(const shader_object* object); + + /** + * Detaches a shader object from the shader program. Detaching a shader object has no effect on a shader program until shader_program::link() is called. + * + * @param object Shader object to detach. + * + * @exception std::runtime_error Shader object is not attached to the shader program. + * @exception std::runtime_error OpenGL shader program is not a valid program object. + * @exception std::runtime_error OpenGL shader object is not a valid shader object. + * @exception std::runtime_error OpenGL shader object is not attached to the shader program. + * + * @see shader_program::link() + */ + void detach(const shader_object* object); + + /** + * Detaches all shader objects from the shader program. + * + * @exception std::runtime_error Shader object is not attached to the shader program. + * @exception std::runtime_error OpenGL shader program is not a valid program object. + * @exception std::runtime_error OpenGL shader object is not a valid shader object. + * @exception std::runtime_error OpenGL shader object is not attached to the shader program. + * + * @see shader_program::detach(const shader_object*) + */ + void detach_all(); + + /** + * Links all attached shader objects to create an executable shader program. + * + * @warning All existing pointers to a shader program's shader inputs will be invalidated if the program is re-linked. + * + * @return `true` if the attached shader objects were successfully linked into the shader program, `false` otherwise. If linking fails, check the info log via shader_program::get_info_log() for more information. + */ + bool link(); + + /// Returns the shader program info log, which is updated when the shader program is linked. + const std::string& get_info_log() const; + + /// Returns `true` if the shader program has been successfully linked, `false` otherwise. + bool was_linked() const; shader_program(const shader_program&) = delete; shader_program& operator=(const shader_program&) = delete; @@ -50,15 +114,30 @@ public: private: friend class rasterizer; + + unsigned int gl_program_id; + std::string info_log; + bool linked; + std::unordered_set attached_objects; void find_inputs(); - std::string get_info_log() const; - - unsigned int gl_program_id; + void free_inputs(); + std::list inputs; - std::map input_map; + std::unordered_map input_map; + }; +inline const std::string& shader_program::get_info_log() const +{ + return info_log; +} + +inline bool shader_program::was_linked() const +{ + return linked; +} + inline const std::list* shader_program::get_inputs() const { return &inputs; @@ -78,4 +157,3 @@ inline const shader_input* shader_program::get_input(const std::string& name) co } // namespace gl #endif // ANTKEEPER_GL_SHADER_PROGRAM_HPP - diff --git a/src/resources/shader-program-loader.cpp b/src/resources/shader-program-loader.cpp index e3c84d0..461dd6e 100644 --- a/src/resources/shader-program-loader.cpp +++ b/src/resources/shader-program-loader.cpp @@ -188,7 +188,7 @@ gl::shader_program* resource_loader::load(resource_manager* std::unordered_set shade_stages = detect_shader_stages(source); // Load detected shaders - std::list shaders; + std::list objects; for (gl::shader_stage stage: shade_stages) { text_file stage_source = *source; @@ -199,16 +199,27 @@ gl::shader_program* resource_loader::load(resource_manager* object->source(source_buffer.c_str(), source_buffer.length()); object->compile(); - shaders.push_back(object); + objects.push_back(object); } // Create shader program - gl::shader_program* program = new gl::shader_program(shaders); + gl::shader_program* program = new gl::shader_program(); + + // Attach shader objects + for (gl::shader_object* object: objects) + program->attach(object); + + // Link shader program + if (!program->link()) + { + throw std::runtime_error("Shader program linking failed: " + program->get_info_log()); + } - // Delete shaders objects - for (gl::shader_object* shader: shaders) + // Detach and delete shader objects + for (gl::shader_object* object: objects) { - delete shader; + program->detach(object); + delete object; } return program;