// Copyright 2021 Benjamin Barenblat // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy of // the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. // Shaders and shader programs. #ifndef GLPLANET_SRC_GL_SHADER_H_ #define GLPLANET_SRC_GL_SHADER_H_ #include #include #include "src/gl/error.h" #include "third_party/abseil/absl/strings/string_view.h" #include "third_party/glew/include/GL/glew.h" namespace gl { class FragmentShader; class VertexShader; // An individual shader. // // The shader life cycle looks like this: // // Shader s; // s.SetSource(source_location); // s.Compile(); // std::cerr << s.compile_log(); // // The Compile function throws if errors occur during compilation, but warnings // are merely saved. You must retrieve the compilation log if you wish to report // warnings. class Shader { public: Shader(Shader&&) noexcept = default; Shader& operator=(Shader&&) noexcept = default; virtual ~Shader() noexcept(!(gl_internal::kThreadSafetyChecks || gl_internal::kAggressiveErrorChecking)) { gl_internal::CheckThreadSafety(); glDeleteShader(shader_); gl_internal::UnnecessaryErrorCheck(); } // Loads the shader source. This function copies the source buffer, so the // source buffer need not stay valid after this call. // // You may call this function repeatedly. Doing so invalidates the shader, and // you must recompile it with Compile before using it. void SetSource(absl::string_view source); // Compiles the shader. Throws a std::runtime_error including the compilation // log if compilation fails. If compilation succeeds but generates warnings, // this function silently returns; check the compile_log member to see what // the warnings were. // // You must call SetSource before calling this function. // // You may call this function repeatedly. void Compile(); // Fetches the shader compilation log. If this is empty, there were no // warnings during the compilation. // // You must call Compile before calling this function. // // You may call this function repeatedly. const std::string& compile_log() const; // The GL identifier for this shader. unsigned int id() const noexcept { return shader_; } protected: GLuint shader_; bool source_set_; bool compile_attempted_; std::string compile_log_; private: friend class FragmentShader; friend class VertexShader; explicit Shader(GLenum type); }; // A fragment shader. class FragmentShader final : public Shader { public: explicit FragmentShader() : Shader(GL_FRAGMENT_SHADER) {} }; // A vertex shader. class VertexShader final : public Shader { public: explicit VertexShader() : Shader(GL_VERTEX_SHADER) {} }; // A shader program consisting of multiple shaders. // // The shader program life cycle looks like this: // // ShaderProgram p; // p.attach(s); // p.SetFragmentDataLocation("output_color", framebuffer_id); // p.Link(); // std::cerr << s.link_log(); // DoSomethingWith(p.active_vertex_attribute("whatever")); // // Analogously to Shader::Compile, Link throws if errors occur during linking, // but warnings are merely saved. You must retrieve the link log if you wish to // report warnings. class ShaderProgram final { public: explicit ShaderProgram(); ShaderProgram(ShaderProgram&&) noexcept = default; ShaderProgram& operator=(ShaderProgram&&) noexcept = default; ~ShaderProgram() noexcept(!(gl_internal::kThreadSafetyChecks || gl_internal::kAggressiveErrorChecking)) { gl_internal::CheckThreadSafety(); glDeleteProgram(program_); gl_internal::UnnecessaryErrorCheck(); } // Attach shaders to the program. The shaders need not be compiled, but you // must compile them before calling Link. // // You may call these functions repeatedly, so long as you never pass the same // shader twice. In any case, calling these functions invalidates the program, // and you must relink it with Link before using it. void Attach(const VertexShader& shader) { AttachShader(shader); } void Attach(const FragmentShader& shader) { AttachShader(shader); has_fragment_shader_ = true; } // Associates a fragment shader output variable with an output framebuffer. // The variable name must not start with "gl_". The default framebuffer has ID // 0. // // You must have a fragment shader attached to this program to call this // function. // // You may call this function repeatedly. Doing so invalidates the program, // and you must relink it with Link before using it. void SetFragmentDataLocation(const char* name, int framebuffer_id); // Links the shader program. Throws a std::runtime_error including the link // log if linking fails. If linking succeeds but generates warnings, this // function silently returns; check the link_log member to see what the // warnings were. // // You may call this function repeatedly. void Link(); // Fetches the program link log. If this is empty, there were no warnings // during the link. // // You must call Link before calling this function. // // You may call this function repeatedly. const std::string& link_log() const; // The index of the specified active vertex attribute. The variable name must // not start with "gl_". // // Throws std::invalid_argument if the specified name is reserved, inactive, // or nonexistent. Throws std::logic_error if the program has not been linked. int active_vertex_attribute(const char* name) const; // The index of the specified active uniform variable. The variable name must // not start with "gl_". // // Throws std::invalid_argument if the specified name is reserved, inactive, // or nonexistent. Throws std::logic_error if the program has not been linked. int active_uniform(const char* name) const; // The GL identifier for this program. unsigned int id() const noexcept { return program_; } private: void AttachShader(const Shader& shader) { gl_internal::CheckThreadSafety(); glAttachShader(program_, shader.id()); // This error check is necessary because the user might have passed in the // same shader twice. gl_internal::ErrorCheck(); link_attempted_ = false; } GLuint program_; bool has_fragment_shader_; bool link_attempted_; std::string link_log_; }; } // namespace gl #endif // GLPLANET_SRC_GL_SHADER_H_