source/gpu/GlfwUtil.h (322 lines of code) (raw):

/** * Copyright 2004-present Facebook. All Rights Reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. */ #pragma once #include <map> #include <mutex> #ifdef __APPLE__ #define GLFW_INCLUDE_GLCOREARB #endif #include "Eigen/Geometry" // GlUtil.h needs to come first in this ordering #include "source/gpu/GlUtil.h" #include "source/util/MathUtil.h" #include <GLFW/glfw3.h> #include <glog/logging.h> #include <folly/Format.h> #ifdef __linux__ #define USE_EGL #endif #ifdef USE_EGL #include <EGL/egl.h> #undef None /* Avoid name colisions with folly */ #undef Bool static const EGLint configAttribs[] = { EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_BLUE_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_RED_SIZE, 8, EGL_DEPTH_SIZE, 8, EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, EGL_NONE}; static const int pbufferWidth = 9; static const int pbufferHeight = 9; static const EGLint pbufferAttribs[] = { EGL_WIDTH, pbufferWidth, EGL_HEIGHT, pbufferHeight, EGL_NONE, }; #endif //! A multi-window, OS independent opengl window /*! Abstracts the glfw window across multiple instances and factors out code common to all glfw windows into the base class. The fundamental idea behind the window abstract is that the common cases should be simple and intuitive. In particular: An instance with no arguments to the constructor creates an offscreen window. All on screen instances require a window name, width, and height. These two are the most common cases so the idea is that we make it easy to create those. All the extra constructor arguments are designed for more complicated arrangements like borderless windows or windows that have both on and offscreen buffers. */ class GlWindow { public: enum ScreenState : unsigned { ON_SCREEN = 0x1, OFF_SCREEN = 0x2, BOTH_SCREEN = 0x3 }; private: static std::mutex windowMapMutex; static std::map<GLFWwindow*, GlWindow*> windows; static void reshapeCallBack(GLFWwindow* window, const int w, const int h) { // Use this window glfwMakeContextCurrent(window); windows[window]->reshape(w, h); } static void mouseCallBack(GLFWwindow* window, const int button, const int action, const int mods) { glfwMakeContextCurrent(window); windows[window]->mouse(button, action, mods); } static void motionCallBack(GLFWwindow* window, const double x, const double y) { glfwMakeContextCurrent(window); windows[window]->motion(x, y); } static void keyCallBack(GLFWwindow* window, const int key, const int s, const int action, const int mods) { glfwMakeContextCurrent(window); windows[window]->keyPress(key, s, action, mods); } protected: const ScreenState screenState; int mouseButton; // GLFW_MOUSE_BUTTON_xxxx int mouseAction; // GLFW_MOUSE_xxx int mouseMods; Eigen::Vector2f mousePos; Eigen::Vector2i viewport; float scale; // the size of a pixel at z = -1 Eigen::Projective3f projection; Eigen::Affine3f transform = Eigen::Affine3f::Identity(); Eigen::Vector3f up; float pitch; float yaw; Eigen::Vector3f origin; bool wireframe; bool done; std::string name; int width; int height; GLFWwindow* window; GLuint fbo; #ifdef USE_EGL EGLDisplay eglDpy; #endif virtual void updateTransform() { const Eigen::Vector3f forward = { std::sin(pitch) * std::cos(yaw), std::sin(pitch) * std::sin(yaw), std::cos(pitch)}; const Eigen::Vector3f right = up.cross(-forward).normalized(); transform.linear().row(0) = right; transform.linear().row(1) = right.cross(forward); transform.linear().row(2) = -forward; transform.translation() = origin * scale; } virtual void reshape(const int w, const int h) { // Update this window's width and height state width = w; height = h; // Update the transformation matrix viewport = Eigen::Vector2i(w, h); glViewport(0, 0, w, h); // fit +/-1 into the window at z = -1 scale = w < h ? 2.0f / w : 2.0f / h; static const float kNear = 0.1f; projection = frustum( -kNear * scale * w / 2.0f, kNear * scale * w / 2.0f, -kNear * scale * h / 2.0f, kNear * scale * h / 2.0f, kNear); updateTransform(); } virtual void mouse(const int button, const int action, const int mods) { mouseButton = button; mouseAction = action; mouseMods = mods; } virtual void motion(const double x, const double y) { if (mouseAction == GLFW_PRESS) { if (mouseButton == GLFW_MOUSE_BUTTON_LEFT) { // rotate const float dPitch = (mousePos.y() - static_cast<float>(y)); const float dYaw = (mousePos.x() - static_cast<float>(x)); pitch += (dPitch / height) * (static_cast<float>(M_PI) / 2.0f); yaw -= (dYaw / width) * (static_cast<float>(M_PI) / 2.0f); } else { // pan Eigen::Vector3f move( static_cast<float>(x) - mousePos.x(), mousePos.y() - static_cast<float>(y), 0); // y is down origin += move; } updateTransform(); } mousePos = {x, y}; } virtual void keyPress(const int key, const int s, const int action, const int mods) { if (action != GLFW_PRESS) { switch (key) { case GLFW_KEY_RIGHT: case GLFW_KEY_A: yaw -= static_cast<float>(M_PI) / 90; updateTransform(); break; case GLFW_KEY_LEFT: case GLFW_KEY_D: yaw += static_cast<float>(M_PI) / 90; updateTransform(); break; case GLFW_KEY_RIGHT_BRACKET: case GLFW_KEY_DOWN: pitch += static_cast<float>(M_PI) / 90; updateTransform(); break; case GLFW_KEY_LEFT_BRACKET: case GLFW_KEY_UP: pitch -= static_cast<float>(M_PI) / 90; updateTransform(); break; case GLFW_KEY_MINUS: case GLFW_KEY_S: origin[2] -= 1; updateTransform(); break; case GLFW_KEY_EQUAL: case GLFW_KEY_W: origin[2] += 1; updateTransform(); break; case GLFW_KEY_PERIOD: wireframe = !wireframe; updateTransform(); break; case GLFW_KEY_R: resetTransformState(); break; case GLFW_KEY_ESCAPE: case GLFW_KEY_Q: done = true; break; } } } void resetTransformState() { mousePos = {0, 0}; transform = Eigen::Affine3f::Identity(); origin = {0, 0, 0}; pitch = static_cast<float>(M_PI) / 2; yaw = 0; updateTransform(); } // This must be filled in by the sub-class virtual void display() = 0; public: //! Off-screen window constructor. GlWindow() : GlWindow("offscreen", 8, 8, true, 8, OFF_SCREEN) {} //! On-screen window constructor. GlWindow( const std::string& name, /*! \param Window name that appears in the border. required*/ const int width, /*! \param Window width in pixels. required*/ const int height, /*! \param Window height in pixels. required */ const bool borderless = false, /*! \param Enable borderless windows, default=false. */ const int outputBPP = 8, /*! \param Bits per pixel, default=8. */ const ScreenState screenState = ON_SCREEN /*! \param Enable offscreen rendering or both. Default = ON_SCREEN */) : screenState(screenState), up({0, 0, 1}), wireframe(false), done(false), name(name), width(width), height(height) { #ifdef USE_EGL // Only use EGL for offscreen rendering. if (screenState & OFF_SCREEN) { // 1. Initialize EGL eglDpy = eglGetDisplay(EGL_DEFAULT_DISPLAY); EGLint major, minor; eglInitialize(eglDpy, &major, &minor); // 2. Select an appropriate configuration EGLint numConfigs; EGLConfig eglCfg; eglChooseConfig(eglDpy, configAttribs, &eglCfg, 1, &numConfigs); // 3. Create a surface EGLSurface eglSurf = eglCreatePbufferSurface(eglDpy, eglCfg, pbufferAttribs); // 4. Bind the API eglBindAPI(EGL_OPENGL_API); // 5. Create a context and make it current EGLContext eglCtx = eglCreateContext(eglDpy, eglCfg, EGL_NO_CONTEXT, NULL); eglMakeCurrent(eglDpy, eglSurf, eglSurf, eglCtx); // Create a frame buffer to render into fbo = createFramebuffer(); // Message the graphics device info LOG(INFO) << folly::format( "OpenGL off-screen renderer: {}", (char*)(glGetString(GL_RENDERER))); // Return early avoiding glfw entirely for offscreen rendering return; } #endif // Used to report glfw faulures const char* lastGlfwErrorMessage; // Init the window system stuff const int glfwInitialization = glfwInit(); glfwGetError(&lastGlfwErrorMessage); CHECK(glfwInitialization) << "failed -> " << lastGlfwErrorMessage; if (screenState & OFF_SCREEN) { glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); } glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); if (borderless) { glfwWindowHint(GLFW_DECORATED, GL_FALSE); } glfwWindowHint(GLFW_RED_BITS, outputBPP); glfwWindowHint(GLFW_GREEN_BITS, outputBPP); glfwWindowHint(GLFW_BLUE_BITS, outputBPP); #ifdef USE_EGL_ // Only use EGL for offscreen rendering. if (screenState & OFF_SCREEN) { glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API); } #endif window = glfwCreateWindow(width, height, name.c_str(), NULL, NULL); glfwGetError(&lastGlfwErrorMessage); CHECK(window) << "creation failed -> " << lastGlfwErrorMessage; // Map this window to this instance { std::lock_guard<std::mutex> windowLock(windowMapMutex); windows[window] = this; } // Make this window current glfwMakeContextCurrent(window); // Only init Glew if we're using it and not, e.g., the Oculus-SDK-specific CAPI_GLE_GL #ifdef __GLEW_H__ glewExperimental = GL_TRUE; GLint GlewInitResult = glewInit(); if (GLEW_OK != GlewInitResult) { throw std::runtime_error((char*)(glewGetErrorString(GlewInitResult))); } #endif // Message the graphics device info LOG(INFO) << folly::format("OpenGL on screen renderer: {}", (char*)(glGetString(GL_RENDERER))); // Create a place to render offscreen pixesl if (screenState & OFF_SCREEN) { fbo = createFramebuffer(); } // Initialize UX transformation state resetTransformState(); // Set callback functions if (screenState & ON_SCREEN) { glfwSetFramebufferSizeCallback(window, reshapeCallBack); glfwSetKeyCallback(window, keyCallBack); glfwSetMouseButtonCallback(window, mouseCallBack); glfwSetCursorPosCallback(window, motionCallBack); glfwSwapInterval(1); // Make sure the window and view port are the same glfwGetFramebufferSize(window, &(this->width), &(this->height)); reshape(this->width, this->height); } } virtual ~GlWindow() { { std::lock_guard<std::mutex> windowLock(windowMapMutex); windows.erase(window); } #ifdef USE_EGL if (screenState & OFF_SCREEN) { // cleanup offscreen rendering glDeleteFramebuffers(1, &fbo); eglTerminate(eglDpy); } #else if (screenState & OFF_SCREEN) { // cleanup offscreen rendering glDeleteFramebuffers(1, &fbo); } glfwDestroyWindow(window); glfwTerminate(); #endif } static void mainLoop() { // This is not re-entrant so we'll only let one in. static std::mutex once; static std::lock_guard<std::mutex> onlyOnce(once); // Loop until all the windows exit static bool windowsAvailable; // Make sure we read the windows data structure in a thread safe manner. { std::lock_guard<std::mutex> windowLock(windowMapMutex); windowsAvailable = !windows.empty(); } // Loop until we kill all the windows while (windowsAvailable) { // This lock allows thread safe window creation and destruction // to happen between window updates. std::lock_guard<std::mutex> windowLock(windowMapMutex); // Loop over all the windows giving each a chance to draw. for (std::map<GLFWwindow*, GlWindow*>::iterator it = windows.begin(); it != windows.end();) { // Check for input between window draws glfwPollEvents(); // Grab the window ptrs GLFWwindow* glfwWindow = it->first; GlWindow* glWindow = it->second; // Use this window glfwMakeContextCurrent(glfwWindow); // Call the user's display code glWindow->display(); // Show the frame buffer glfwSwapBuffers(glfwWindow); // Remove the window if it's been flagged as done it = (glWindow->done) ? windows.erase(it) : ++it; } windowsAvailable = !windows.empty(); } } };