/// Includes #include #include #include #include #include #include #include #include #include // NOLINTNEXTLINE #define STR_EXCEPTION GLBase::Exception #include /// 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((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> 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; }