src/glbackend.cpp
583abb86
 /// Includes
 
 
 #include <glbackend.hpp>
 
 #include <algorithm>
 #include <cstddef>
 #include <exception>
 #include <iomanip>
 #include <regex>
 #include <sstream>
 #include <string>
 
 #include <glbase.hpp>
 
 // NOLINTNEXTLINE
 #define STR_EXCEPTION GLBase::Exception
 #include <str.hpp>
 
 
 /// Special member functions
 
 
 GLBackend::GLBackend()
 :
     callback_update_{},
     callback_render_{},
     scroll_{},
     position_{},
     move_{},
     size_{},
     callback_key_{},
     callback_button_{},
     callback_scroll_{},
     callback_position_{},
     callback_move_{},
     callback_size_{}
 {
 }
 
 
 void GLBackend::init_()
 {
     #ifdef __GLEW_H__
     if (auto error = glewInit())
         STR_THROW(
             "Failed to initialize GLEW:" << "\n" <<
             glewGetErrorString(error)
         );
     #endif
 
     if (debug() >= 1)
     {
         if (supported({4, 3}, "GL_KHR_debug"))
         {
             glEnable(GL_DEBUG_OUTPUT);
             glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
             glDebugMessageControl(
                 GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE,
                 0, nullptr,
                 GL_TRUE
             );
             glDebugMessageControl(
                 GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_NOTIFICATION,
                 0, nullptr,
                 GL_FALSE
             );
             glDebugMessageCallback(debug_gl_message_callback_, nullptr);
         }
     }
 }
 
 
 /// Render loop
 
 
 void GLBackend::run(float dt_fixed)
 {
     auto t = time(0);
     running(true);
     while (events(), running())
     {
         if (callback_update_)
         {
             auto t_next = time();
             auto dt = t_next - t;
             if (dt_fixed != 0.0F)
                 dt = dt_fixed;
             while (t + dt <= t_next)
             {
                 callback_update_(t, dt, !(t + 2 * dt <= t_next));
                 t += dt;
             }
         }
         if (callback_render_)
             callback_render_();
         swap();
     }
 }
 
 
 /// Path
 
 
 GLBASE_GLOBAL(GLBackend::prefix_, {})
 
 
 /// TGA
 
 
 void GLBackend::tga_write(
     Path const & path
 ) const
 {
     tga_().write(path_prefix_(path, prefix()));
 }
 
 
 bool GLBackend::tga_compare(
     Path const & path,
     bool         write_on_failed_read
 ) const
 {
     auto path_prefix = path_prefix_(path, prefix());
     auto tga = tga_();
     try
     {
         auto tga_read = TGA_::read(path_prefix);
         return tga_read.data() == tga.data();
     }
     catch (std::exception const & exception)
     {
         if (!write_on_failed_read)
             throw;
         if (debug() >= 1)
             debug_callback()(exception.what());
         debug_callback()(STR("Writing TGA \"" << path_prefix << "\"."));
         tga.write(path_prefix);
         return false;
     }
 }
 
 
 GLBackend::TGA_ GLBackend::tga_() const
 {
     glFlush();
     auto size = this->size();
     auto data = std::vector<GLubyte>((4 * (size_t)size[0] * (size_t)size[1]));
     glPixelStorei(GL_PACK_ALIGNMENT, 4);
     glReadPixels(
         0, 0,
         size[0], size[1],
         GL_BGRA, GL_UNSIGNED_BYTE,
         data.data()
     );
     return TGA_(size, std::move(data));
 }
 
 
 /// Debug
 
 
 std::string GLBackend::debug_info() const
 {
     auto ostream = std::ostringstream{};
 
     #ifdef __GLEW_H__
     // NOLINTNEXTLINE
     #define GLBACKEND_INFO_GLEW_(NAME) \
         {#NAME, (char const *)glewGetString(GLEW_##NAME)}
     debug_info_(ostream, "GLEW", {
         GLBACKEND_INFO_GLEW_(VERSION),
     });
     #endif
 
     // NOLINTNEXTLINE
     #define GLBACKEND_INFO_GL_(NAME) \
         {#NAME, (char const *)glGetString(GL_##NAME)}
     // NOLINTNEXTLINE
     #define GLBACKEND_INFO_GL_FLAG_(NAME) \
         { \
             #NAME, \
             (flags & (GLuint)GL_CONTEXT_FLAG_##NAME##_BIT) \
                 ? "TRUE" \
                 : "FALSE" \
         }
     // NOLINTNEXTLINE
     #define GLBACKEND_INFO_GL_INTEGER_(NAME) \
         {#NAME, std::to_string(integer(GL_##NAME)) }
     auto const flags = (GLuint)integer(GL_CONTEXT_FLAGS);
     debug_info_(ostream, "OpenGL", {
         GLBACKEND_INFO_GL_(VENDOR),
         GLBACKEND_INFO_GL_(RENDERER),
         GLBACKEND_INFO_GL_(VERSION),
         GLBACKEND_INFO_GL_(SHADING_LANGUAGE_VERSION),
         GLBACKEND_INFO_GL_FLAG_(FORWARD_COMPATIBLE),
         GLBACKEND_INFO_GL_FLAG_(DEBUG),
         GLBACKEND_INFO_GL_FLAG_(ROBUST_ACCESS),
         GLBACKEND_INFO_GL_FLAG_(NO_ERROR),
         GLBACKEND_INFO_GL_INTEGER_(SAMPLE_BUFFERS),
         GLBACKEND_INFO_GL_INTEGER_(SAMPLES),
     });
 
     return ostream.str();
 }
 
 
 void GLBackend::debug_info_(
     std::ostream                                           & ostream,
     std::string                                      const & category,
     std::vector<std::pair<std::string, std::string>> const & values,
     char                                                     fill
 )
 {
     auto length_max = int{0};
     for (auto const & value : values)
         length_max = std::max(length_max, (int)value.first.length());
     ostream << category << "\n";
     for (auto const & value : values)
         ostream
             << "  "
             << std::left << std::setw(length_max + 1 + 2) << std::setfill(fill)
             << (value.first + " ")
             << (" " + value.second)
             << "\n";
 }
 
 
 void GLAPIENTRY GLBackend::debug_gl_message_callback_(
     GLenum          source,
     GLenum          type,
     GLuint          id,
     GLenum          severity,
     GLsizei         length,
     GLchar  const * message,
     void    const * user_param
 )
 {
     (void)length;
     (void)user_param;
 
     auto ostream = std::ostringstream{};
     ostream << std::hex << std::showbase;
 
     // https://www.khronos.org/opengl/wiki/Debug_Output#Message_Components
     ostream << "GL debug message ";
     // NOLINTNEXTLINE
     #define GLBACKEND_CALLBACK_CASE_(CATEGORY, VALUE) \
         case GL_DEBUG_##CATEGORY##_##VALUE: \
             ostream << #VALUE; \
             break;
     switch(source)
     {
         GLBACKEND_CALLBACK_CASE_(SOURCE, API)
         GLBACKEND_CALLBACK_CASE_(SOURCE, WINDOW_SYSTEM)
         GLBACKEND_CALLBACK_CASE_(SOURCE, SHADER_COMPILER)
         GLBACKEND_CALLBACK_CASE_(SOURCE, THIRD_PARTY)
         GLBACKEND_CALLBACK_CASE_(SOURCE, APPLICATION)
         GLBACKEND_CALLBACK_CASE_(SOURCE, OTHER)
         default:
             ostream << source;
     }
     ostream << " ";
     switch(type)
     {
         GLBACKEND_CALLBACK_CASE_(TYPE, ERROR)
         GLBACKEND_CALLBACK_CASE_(TYPE, DEPRECATED_BEHAVIOR)
         GLBACKEND_CALLBACK_CASE_(TYPE, UNDEFINED_BEHAVIOR)
         GLBACKEND_CALLBACK_CASE_(TYPE, PORTABILITY)
         GLBACKEND_CALLBACK_CASE_(TYPE, PERFORMANCE)
         GLBACKEND_CALLBACK_CASE_(TYPE, MARKER)
         GLBACKEND_CALLBACK_CASE_(TYPE, PUSH_GROUP)
         GLBACKEND_CALLBACK_CASE_(TYPE, POP_GROUP)
         GLBACKEND_CALLBACK_CASE_(TYPE, OTHER)
         default:
             ostream << type;
     }
     ostream << " ";
     switch(severity)
     {
         GLBACKEND_CALLBACK_CASE_(SEVERITY, HIGH)
         GLBACKEND_CALLBACK_CASE_(SEVERITY, MEDIUM)
         GLBACKEND_CALLBACK_CASE_(SEVERITY, LOW)
         GLBACKEND_CALLBACK_CASE_(SEVERITY, NOTIFICATION)
         default:
             ostream << severity;
     }
     ostream << " ";
     ostream << id;
     ostream << ":\n";
 
     ostream << debug_gl_message_(message);
 
     debug_callback()(ostream.str());
 }
 
 
 std::string GLBackend::debug_gl_message_(std::string message)
 {
     message.erase(message.find_last_not_of(" \n") + 1);
 
     auto static const file_line = std::regex{R""(^"([^"]+)"(:[0-9]+.*))""};
     auto match = std::smatch{};
     if (std::regex_match(message, match, file_line))
         message = match.str(1) + match.str(2);
 
     return message;
 }