#include "Window.hpp" #include #include #include #include #include #include #include "debug/Logger.hpp" #include "graphics/core/ImageData.hpp" #include "graphics/core/Texture.hpp" #include "settings.hpp" #include "util/ObjectsKeeper.hpp" #include "Events.hpp" #include "util/platform.hpp" static debug::Logger logger("window"); namespace { GLFWwindow* window = nullptr; DisplaySettings* settings = nullptr; std::stack scissorStack; glm::vec4 scissorArea; int framerate = -1; double prevSwap = 0.0; bool fullscreen = false; CursorShape cursor = CursorShape::ARROW; int posX = 0; int posY = 0; } uint Window::width = 0; uint Window::height = 0; static util::ObjectsKeeper observers_keeper; static std::unordered_set extensionsCache; static const char* gl_error_name(int error) { switch (error) { case GL_DEBUG_TYPE_ERROR: return "ERROR"; case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: return "DEPRECATED_BEHAVIOR"; case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: return "UNDEFINED_BEHAVIOR"; case GL_DEBUG_TYPE_PORTABILITY: return "PORTABILITY"; case GL_DEBUG_TYPE_PERFORMANCE: return "PERFORMANCE"; case GL_DEBUG_TYPE_OTHER: return "OTHER"; } return "UNKNOWN"; } static const char* gl_severity_name(int severity) { switch (severity) { case GL_DEBUG_SEVERITY_LOW: return "LOW"; case GL_DEBUG_SEVERITY_MEDIUM: return "MEDIUM"; case GL_DEBUG_SEVERITY_HIGH: return "HIGH"; case GL_DEBUG_SEVERITY_NOTIFICATION: return "NOTIFICATION"; } return "UNKNOWN"; } static void GLAPIENTRY gl_message_callback( GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam ) { if (severity == GL_DEBUG_SEVERITY_NOTIFICATION) { return; } if (!ENGINE_DEBUG_BUILD && severity != GL_DEBUG_SEVERITY_HIGH) { return; } std::cerr << "GL:" << gl_error_name(type) << ":" << gl_severity_name(severity) << ": " << message << std::endl; } static void cursor_position_callback(GLFWwindow*, double xpos, double ypos) { Events::setPosition(xpos, ypos); } static void mouse_button_callback(GLFWwindow*, int button, int action, int) { Events::setButton(button, action == GLFW_PRESS); } static void key_callback( GLFWwindow*, int key, int /*scancode*/, int action, int /*mode*/ ) { if (key == GLFW_KEY_UNKNOWN) return; if (action == GLFW_PRESS) { Events::setKey(key, true); Events::pressedKeys.push_back(static_cast(key)); } else if (action == GLFW_RELEASE) { Events::setKey(key, false); } else if (action == GLFW_REPEAT) { Events::pressedKeys.push_back(static_cast(key)); } } static void scroll_callback(GLFWwindow*, double xoffset, double yoffset) { Events::scroll += yoffset; } static void character_callback(GLFWwindow*, unsigned int codepoint) { Events::codepoints.push_back(codepoint); } static void window_size_callback(GLFWwindow*, int width, int height) { if (width && height) { glViewport(0, 0, width, height); Window::width = width; Window::height = height; if (!Window::isFullscreen() && !Window::isMaximized()) { Window::getSettings()->width.set(width); Window::getSettings()->height.set(height); } } Window::resetScissor(); } bool Window::isMaximized() { return glfwGetWindowAttrib(window, GLFW_MAXIMIZED); } bool Window::isIconified() { return glfwGetWindowAttrib(window, GLFW_ICONIFIED); } bool Window::isFocused() { return glfwGetWindowAttrib(window, GLFW_FOCUSED); } static const char* glfw_error_name(int error) { switch (error) { case GLFW_NO_ERROR: return "no error"; case GLFW_NOT_INITIALIZED: return "not initialized"; case GLFW_NO_CURRENT_CONTEXT: return "no current context"; case GLFW_INVALID_ENUM: return "invalid enum"; case GLFW_INVALID_VALUE: return "invalid value"; case GLFW_OUT_OF_MEMORY: return "out of memory"; case GLFW_API_UNAVAILABLE: return "api unavailable"; case GLFW_VERSION_UNAVAILABLE: return "version unavailable"; case GLFW_PLATFORM_ERROR: return "platform error"; case GLFW_FORMAT_UNAVAILABLE: return "format unavailable"; case GLFW_NO_WINDOW_CONTEXT: return "no window context"; default: return "unknown error"; } } static void glfw_error_callback(int error, const char* description) { auto logline = logger.error(); logline << "GLFW error [0x" << std::hex << error << " - " << glfw_error_name(error) << "]"; if (description) { logline << ": " << description; } } static GLFWcursor* standard_cursors[static_cast(CursorShape::LAST) + 1] = {}; class GLFWInput : public Input { public: void pollEvents() override { Events::pollEvents(); } const char* getClipboardText() const override { return glfwGetClipboardString(::window); } int getScroll() override { return Events::getScroll(); } bool pressed(keycode keycode) const override { return Events::pressed(keycode); } bool jpressed(keycode keycode) const override { return Events::jpressed(keycode); } bool clicked(mousecode mousecode) const override { return Events::clicked(mousecode); } bool jclicked(mousecode mousecode) const override { return Events::jclicked(mousecode); } CursorState getCursor() const override { return { Events::isCursorLocked(), Events::cursor, Events::delta }; } void toggleCursor() override { Events::toggleCursor(); } Bindings& getBindings() override { return Events::bindings; } const Bindings& getBindings() const override { return Events::bindings; } observer_handler addKeyCallback(keycode key, KeyCallback callback) override { return Events::addKeyCallback(key, std::move(callback)); } const std::vector& getPressedKeys() const override { return Events::pressedKeys; } const std::vector& getCodepoints() const override { return Events::codepoints; } }; static_assert(!std::is_abstract()); std::unique_ptr Window::initialize(DisplaySettings* settings) { ::settings = settings; Window::width = settings->width.get(); Window::height = settings->height.get(); std::string title = "VoxelCore v" + std::to_string(ENGINE_VERSION_MAJOR) + "." + std::to_string(ENGINE_VERSION_MINOR); if (ENGINE_DEBUG_BUILD) { title += " [debug]"; } glfwSetErrorCallback(glfw_error_callback); if (glfwInit() == GLFW_FALSE) { logger.error() << "failed to initialize GLFW"; return nullptr; } glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); #ifdef __APPLE__ glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_FALSE); #else glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_ANY_PROFILE); #endif glfwWindowHint(GLFW_RESIZABLE, GL_TRUE); glfwWindowHint(GLFW_SAMPLES, settings->samples.get()); window = glfwCreateWindow(width, height, title.c_str(), nullptr, nullptr); if (window == nullptr) { logger.error() << "failed to create GLFW window"; glfwTerminate(); return nullptr; } glfwMakeContextCurrent(window); glewExperimental = GL_TRUE; GLenum glewErr = glewInit(); if (glewErr != GLEW_OK) { if (glewErr == GLEW_ERROR_NO_GLX_DISPLAY) { // see issue #240 logger.warning() << "glewInit() returned GLEW_ERROR_NO_GLX_DISPLAY; ignored"; } else { logger.error() << "failed to initialize GLEW:\n" << glewGetErrorString(glewErr); return nullptr; } } if (isGlExtensionSupported("GL_KHR_debug")) { glEnable(GL_DEBUG_OUTPUT); glDebugMessageCallback(gl_message_callback, nullptr); } glViewport(0, 0, width, height); glClearColor(0.0f, 0.0f, 0.0f, 1); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); GLint maxTextureSize[1] {static_cast(Texture::MAX_RESOLUTION)}; glGetIntegerv(GL_MAX_TEXTURE_SIZE, maxTextureSize); if (maxTextureSize[0] > 0) { Texture::MAX_RESOLUTION = maxTextureSize[0]; logger.info() << "max texture size is " << Texture::MAX_RESOLUTION; } glfwSetKeyCallback(window, key_callback); glfwSetMouseButtonCallback(window, mouse_button_callback); glfwSetCursorPosCallback(window, cursor_position_callback); glfwSetWindowSizeCallback(window, window_size_callback); glfwSetCharCallback(window, character_callback); glfwSetScrollCallback(window, scroll_callback); observers_keeper = util::ObjectsKeeper(); observers_keeper.keepAlive(settings->fullscreen.observe( [](bool value) { if (value != isFullscreen()) { toggleFullscreen(); } }, true )); glfwSwapInterval(1); setFramerate(settings->framerate.get()); const GLubyte* vendor = glGetString(GL_VENDOR); const GLubyte* renderer = glGetString(GL_RENDERER); logger.info() << "GL Vendor: " << reinterpret_cast(vendor); logger.info() << "GL Renderer: " << reinterpret_cast(renderer); logger.info() << "GLFW: " << glfwGetVersionString(); glm::vec2 scale; glfwGetMonitorContentScale(glfwGetPrimaryMonitor(), &scale.x, &scale.y); logger.info() << "monitor content scale: " << scale.x << "x" << scale.y; input_util::initialize(); for (int i = 0; i <= static_cast(CursorShape::LAST); i++) { int cursor = GLFW_ARROW_CURSOR + i; // GLFW 3.3 does not support some cursors if (GLFW_VERSION_MAJOR <= 3 && GLFW_VERSION_MINOR <= 3 && cursor > GLFW_VRESIZE_CURSOR) { break; } standard_cursors[i] = glfwCreateStandardCursor(cursor); } return std::make_unique(); } void Window::clear() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } void Window::clearDepth() { glClear(GL_DEPTH_BUFFER_BIT); } void Window::setBgColor(glm::vec3 color) { glClearColor(color.r, color.g, color.b, 1.0f); } void Window::setBgColor(glm::vec4 color) { glClearColor(color.r, color.g, color.b, color.a); } void Window::viewport(int x, int y, int width, int height) { glViewport(x, y, width, height); } void Window::setCursorMode(int mode) { glfwSetInputMode(window, GLFW_CURSOR, mode); } void Window::resetScissor() { scissorArea = glm::vec4(0.0f, 0.0f, width, height); scissorStack = std::stack(); glDisable(GL_SCISSOR_TEST); } void Window::pushScissor(glm::vec4 area) { if (scissorStack.empty()) { glEnable(GL_SCISSOR_TEST); } scissorStack.push(scissorArea); area.z += glm::ceil(area.x); area.w += glm::ceil(area.y); area.x = glm::max(area.x, scissorArea.x); area.y = glm::max(area.y, scissorArea.y); area.z = glm::min(area.z, scissorArea.z); area.w = glm::min(area.w, scissorArea.w); if (area.z < 0.0f || area.w < 0.0f) { glScissor(0, 0, 0, 0); } else { glScissor( area.x, Window::height - area.w, std::max(0, static_cast(glm::ceil(area.z - area.x))), std::max(0, static_cast(glm::ceil(area.w - area.y))) ); } scissorArea = area; } void Window::popScissor() { if (scissorStack.empty()) { logger.warning() << "extra Window::popScissor call"; return; } glm::vec4 area = scissorStack.top(); scissorStack.pop(); if (area.z < 0.0f || area.w < 0.0f) { glScissor(0, 0, 0, 0); } else { glScissor( area.x, Window::height - area.w, std::max(0, int(area.z - area.x)), std::max(0, int(area.w - area.y)) ); } if (scissorStack.empty()) { glDisable(GL_SCISSOR_TEST); } scissorArea = area; } void Window::terminate() { observers_keeper = util::ObjectsKeeper(); for (int i = 0; i <= static_cast(CursorShape::LAST); i++) { glfwDestroyCursor(standard_cursors[i]); } glfwTerminate(); } bool Window::isShouldClose() { return glfwWindowShouldClose(window); } void Window::setShouldClose(bool flag) { glfwSetWindowShouldClose(window, flag); } void Window::setFramerate(int framerate) { if ((framerate != -1) != (::framerate != -1)) { glfwSwapInterval(framerate == -1); } ::framerate = framerate; } void Window::toggleFullscreen() { fullscreen = !fullscreen; GLFWmonitor* monitor = glfwGetPrimaryMonitor(); const GLFWvidmode* mode = glfwGetVideoMode(monitor); if (Events::isCursorLocked()){ Events::toggleCursor(); } if (fullscreen) { glfwGetWindowPos(window, &posX, &posY); glfwSetWindowMonitor( window, monitor, 0, 0, mode->width, mode->height, GLFW_DONT_CARE ); } else { glfwSetWindowMonitor( window, nullptr, posX, posY, settings->width.get(), settings->height.get(), GLFW_DONT_CARE ); } double xPos, yPos; glfwGetCursorPos(window, &xPos, &yPos); Events::setPosition(xPos, yPos); } bool Window::isFullscreen() { return fullscreen; } void Window::swapBuffers() { glfwSwapBuffers(window); Window::resetScissor(); if (framerate > 0) { auto elapsedTime = time() - prevSwap; auto frameTime = 1.0 / framerate; if (elapsedTime < frameTime) { platform::sleep( static_cast((frameTime - elapsedTime) * 1000) ); } } prevSwap = time(); } double Window::time() { return glfwGetTime(); } DisplaySettings* Window::getSettings() { return settings; } void Window::setCursor(CursorShape shape) { if (cursor == shape) { return; } cursor = shape; // NULL cursor is valid for GLFW glfwSetCursor(window, standard_cursors[static_cast(shape)]); } std::unique_ptr Window::takeScreenshot() { auto data = std::make_unique(width * height * 3); glPixelStorei(GL_PACK_ALIGNMENT, 1); glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, data.get()); return std::make_unique( ImageFormat::rgb888, width, height, data.release() ); } void Window::setClipboardText(const char* text) { glfwSetClipboardString(window, text); } void Window::setIcon(const ImageData* image) { GLFWimage icon { static_cast(image->getWidth()), static_cast(image->getHeight()), image->getData()}; glfwSetWindowIcon(window, 1, &icon); } static void initGlExtensionsCache() { if (!extensionsCache.empty()) { return; } GLint numExtensions = 0; glGetIntegerv(GL_NUM_EXTENSIONS, &numExtensions); for (GLint i = 0; i < numExtensions; ++i) { const char *ext = reinterpret_cast(glGetStringi(GL_EXTENSIONS, i)); if (ext) { extensionsCache.insert(ext); } } } bool Window::isGlExtensionSupported(const char *extension) { if (!extension || !*extension) { return false; } initGlExtensionsCache(); return extensionsCache.find(extension) != extensionsCache.end(); }