/// Guards #ifdef GLBACKEND_GLFW /// Includes #include <glbackend_glfw.hpp> #include <array> #include <functional> #include <limits> #include <sstream> #include <string> #include <type_traits> #include <utility> #include <glbase.hpp> #include <glbackend.hpp> #include <GLFW/glfw3.h> // NOLINTNEXTLINE #define STR_EXCEPTION GLBase::Exception #include <str.hpp> /// Special member functions GLBackendGLFW::GLBackendGLFW( std::string const & title, std::array<int, 2> size, std::array<int, 2> version, int samples, bool fullscreen, bool transparent ) : GLBackend(), window_{nullptr} { //// Backend errors glfwSetErrorCallback(debug_glfw_error_callback_); auto const glfw_get_error_description = []() { char const * description{}; glfwGetError(&description); return description; }; //// Backend init if (!glfwInit()) STR_THROW( "Failed to initialize GLFW" << ":\n" << glfw_get_error_description() ); ++init_count_; //// Window options glfwDefaultWindowHints(); glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); GLFWmonitor * monitor = nullptr; if (fullscreen) monitor = glfwGetPrimaryMonitor(); if (transparent) { glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE); glfwWindowHint(GLFW_DECORATED, GLFW_FALSE); glfwWindowHint(GLFW_FLOATING, GLFW_TRUE); } if (size[0] == 0 || size[1] == 0) { auto const * mode = glfwGetVideoMode(glfwGetPrimaryMonitor()); size[0] = mode->width; size[1] = mode->height; } size_ = size; //// Context options glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE); glfwWindowHint(GLFW_RED_BITS, 8); // NOLINT glfwWindowHint(GLFW_GREEN_BITS, 8); // NOLINT glfwWindowHint(GLFW_BLUE_BITS, 8); // NOLINT glfwWindowHint(GLFW_ALPHA_BITS, 8); // NOLINT glfwWindowHint(GLFW_DEPTH_BITS, 24); // NOLINT glfwWindowHint(GLFW_STENCIL_BITS, 8); // NOLINT if (samples) glfwWindowHint(GLFW_SAMPLES, samples); if (version[0]) glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, version[0]); if (version[1]) glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, version[1]); if ((version[0] == 3 && version[1] >= 2) || version[0] >= 4) // NOLINT { glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); } if (debug() >= 1) glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE); else if (version[0] >= 2) // NOLINT glfwWindowHint(GLFW_CONTEXT_NO_ERROR, GLFW_TRUE); try { //// Window window_ = glfwCreateWindow( size[0], size[1], title.c_str(), monitor, nullptr ); if (!window_) STR_THROW( "Failed to create GLFW window" << ":\n" << glfw_get_error_description() ); //// Context glfwMakeContextCurrent(window_); if (debug() == 0) glfwSwapInterval(1); init_(); //// Lock if (fullscreen) lock(true); } catch (...) { destroy_(); throw; } //// Callbacks // NOLINTNEXTLINE #define GLBACKEND_GLFW_SELF_(WINDOW) \ (*static_cast<GLBackendGLFW *>(glfwGetWindowUserPointer(WINDOW))) glfwSetWindowUserPointer(window_, this); glfwSetKeyCallback( window_, []( GLFWwindow * window, int key, int scancode, int action, int mods ) { (void)scancode; (void)mods; if (action != GLFW_PRESS) return; auto & self = GLBACKEND_GLFW_SELF_(window); if (self.callback_key_) { if(key == GLFW_KEY_LEFT) self.callback_key_("Left"); else if (key == GLFW_KEY_RIGHT) self.callback_key_("Right"); else if (key == GLFW_KEY_UP) self.callback_key_("Up"); else if (key == GLFW_KEY_DOWN) self.callback_key_("Down"); else if (key == GLFW_KEY_ENTER) self.callback_key_("Enter"); else if (key == GLFW_KEY_ESCAPE) self.callback_key_("Escape"); else if (key == GLFW_KEY_TAB) self.callback_key_("Tab"); else if (key == GLFW_KEY_BACKSPACE) self.callback_key_("Backspace"); else if ( key == GLFW_KEY_RIGHT_CONTROL || key == GLFW_KEY_LEFT_CONTROL ) self.callback_key_("Control"); else if ( key == GLFW_KEY_RIGHT_SHIFT || key == GLFW_KEY_LEFT_SHIFT ) self.callback_key_("Shift"); else if ( key == GLFW_KEY_RIGHT_ALT || key == GLFW_KEY_LEFT_ALT ) self.callback_key_("Alt"); else if ( 0 <= key && key <= std::numeric_limits<char>::max() ) self.callback_key_(std::string(1, (char)key)); } } ); glfwSetMouseButtonCallback( window_, []( GLFWwindow * window, int button, int action, int mods ) { (void)mods; if (action != GLFW_PRESS) return; auto & self = GLBACKEND_GLFW_SELF_(window); if (self.callback_button_) self.callback_button_(button); } ); glfwSetScrollCallback( window_, []( GLFWwindow * window, double scroll_x, double scroll_y ) { auto & self = GLBACKEND_GLFW_SELF_(window); self.scroll_ = { (float)scroll_x, (float)scroll_y, }; if (self.callback_scroll_) self.callback_scroll_(self.scroll_); } ); glfwSetCursorPosCallback( window_, []( GLFWwindow * window, double x, double y ) { auto & self = GLBACKEND_GLFW_SELF_(window); self.move_ = { (float)x - self.position_[0], (float)y - self.position_[1], }; self.position_ = { (float)x, (float)y, }; if (self.callback_position_) self.callback_position_(self.position_); if (self.callback_move_) self.callback_move_(self.move_); } ); glfwSetFramebufferSizeCallback( window_, []( GLFWwindow * window, int width, int height ) { auto & self = GLBACKEND_GLFW_SELF_(window); self.size_ = { width, height, }; if (self.callback_size_) self.callback_size_(self.size_); } ); } GLBackendGLFW::GLBackendGLFW(GLBackendGLFW && other) noexcept : GLBackend(std::move(other)), window_{other.window_} { other.window_ = nullptr; } GLBackendGLFW::~GLBackendGLFW() { destroy_(); } void GLBackendGLFW::destroy_() { if (window_) { lock(false); glfwDestroyWindow(window_); } if (--init_count_ == 0) glfwTerminate(); } GLBASE_GLOBAL(GLBackendGLFW::init_count_, {0}) /// Debug std::string GLBackendGLFW::debug_info() const { auto ostream = std::ostringstream{}; auto version_major = int{}; auto version_minor = int{}; auto version_rev = int{}; glfwGetVersion( &version_major, &version_minor, &version_rev ); debug_info_(ostream, "GLFW", { { "VERSION_COMPILED", glfwGetVersionString() }, { "VERSION_LINKED", std::to_string(version_major) + "." + std::to_string(version_minor) + "." + std::to_string(version_rev), }, }); ostream << GLBackend::debug_info(); return ostream.str(); } void GLBackendGLFW::debug_glfw_error_callback_( int error, char const * message ) { auto ostream = std::ostringstream{}; ostream << std::hex << std::showbase; // https://www.glfw.org/docs/3.3/group__errors.html ostream << "GLFW error "; // NOLINTNEXTLINE #define GLBACKEND_GLFW_CALLBACK_CASE_(VALUE) \ case GLFW_ ## VALUE: \ ostream << #VALUE; \ break; switch(error) { GLBACKEND_GLFW_CALLBACK_CASE_(NOT_INITIALIZED) GLBACKEND_GLFW_CALLBACK_CASE_(NO_CURRENT_CONTEXT) GLBACKEND_GLFW_CALLBACK_CASE_(INVALID_ENUM) GLBACKEND_GLFW_CALLBACK_CASE_(INVALID_VALUE) GLBACKEND_GLFW_CALLBACK_CASE_(OUT_OF_MEMORY) GLBACKEND_GLFW_CALLBACK_CASE_(API_UNAVAILABLE) GLBACKEND_GLFW_CALLBACK_CASE_(VERSION_UNAVAILABLE) GLBACKEND_GLFW_CALLBACK_CASE_(PLATFORM_ERROR) GLBACKEND_GLFW_CALLBACK_CASE_(FORMAT_UNAVAILABLE) GLBACKEND_GLFW_CALLBACK_CASE_(NO_WINDOW_CONTEXT) default: ostream << error; } ostream << ":\n"; ostream << message; debug_callback()(ostream.str()); } /// Guards #endif