diff options
author | Konstantin Käfer <mail@kkaefer.com> | 2014-10-22 15:14:21 +0200 |
---|---|---|
committer | Konstantin Käfer <mail@kkaefer.com> | 2014-10-22 15:14:21 +0200 |
commit | 905c4294be495d15591885a2438ca0b8abc8decf (patch) | |
tree | 851a94849eb7a1f3269b91de6054846cfbda722b /common | |
parent | c6e4cf2f80216c7139b6587dcd6d919080aef20d (diff) | |
parent | 8954b3971f5cb90dc4b18e61b58ea6e41c54fffe (diff) | |
download | qtlocation-mapboxgl-905c4294be495d15591885a2438ca0b8abc8decf.tar.gz |
Merge branch 'libuv-0.10-headless-display' into mason
Conflicts:
common/http_request_baton_curl.cpp
include/mbgl/map/map.hpp
include/mbgl/util/uv_detail.hpp
scripts/travis_before_install.sh
setup-libraries.sh
src/map/map.cpp
src/storage/file_request_baton.cpp
src/storage/http_request.cpp
src/util/uv-messenger.c
Diffstat (limited to 'common')
-rw-r--r-- | common/curl_request.cpp | 383 | ||||
-rw-r--r-- | common/glfw_view.cpp | 9 | ||||
-rw-r--r-- | common/glfw_view.hpp | 1 | ||||
-rw-r--r-- | common/glx.h | 2 | ||||
-rw-r--r-- | common/headless_display.cpp | 74 | ||||
-rw-r--r-- | common/headless_display.hpp | 25 | ||||
-rw-r--r-- | common/headless_view.cpp | 244 | ||||
-rw-r--r-- | common/headless_view.hpp | 36 | ||||
-rw-r--r-- | common/http_request_baton_curl.cpp | 2 |
9 files changed, 679 insertions, 97 deletions
diff --git a/common/curl_request.cpp b/common/curl_request.cpp new file mode 100644 index 0000000000..3370c0a859 --- /dev/null +++ b/common/curl_request.cpp @@ -0,0 +1,383 @@ + +#include <mbgl/platform/platform.hpp> +#include <mbgl/platform/request.hpp> +#include <mbgl/util/uv_detail.hpp> +#include <mbgl/util/std.hpp> + +#include <queue> +#include <boost/lockfree/queue.hpp> + +#include <curl/curl.h> + +// This file contains code from http://curl.haxx.se/libcurl/c/multi-uv.html: + +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2013, Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +/* Example application code using the multi socket interface to download + multiple files at once, but instead of using curl_multi_perform and + curl_multi_wait, which uses select(), we use libuv. + It supports epoll, kqueue, etc. on unixes and fast IO completion ports on + Windows, which means, it should be very fast on all platforms.. + + Written by Clemens Gruber, based on an outdated example from uvbook and + some tests from libuv. + + Requires libuv and (of course) libcurl. + + See http://nikhilm.github.com/uvbook/ for more information on libuv. +*/ + +namespace mbgl { +namespace platform { +namespace request { + +struct curl_context { + uv_poll_t poll_handle; + curl_socket_t sockfd; +}; + +// Handles the request thread + messaging to the thread. +static uv_thread_t thread; +static uv::once init_thread_once; +static uv_loop_t *loop = nullptr; +static uv_async_t async_add; +static uv_async_t async_cancel; + +// Stores pointers (!) to shared_ptrs. We use shared_ptrs so that request objects don't get +// auto-destructed while they're in progress. The TileData object retains a weak_ptr to this +// request, so we have to use a shared_ptr here to ensure that this object stays alive. +static boost::lockfree::queue<std::shared_ptr<mbgl::platform::Request> *> add_queue(8); +static boost::lockfree::queue<std::shared_ptr<mbgl::platform::Request> *> cancel_queue(8); + +// Used as the CURL timer function to periodically check for socket updates. +static uv_timer_t timeout; + +// CURL multi handle that we use to request multiple URLs at the same time, without having to block +// and spawn threads. +static CURLM *curl_multi = nullptr; + +// CURL share handles are used for sharing session state (e.g.) +static uv::mutex curl_share_mutex; +static CURLSH *curl_share = nullptr; + +// A queue that we use for storing resuable CURL easy handles to avoid creating and destroying them +// all the time. +static std::queue<CURL *> curl_handle_cache; + + +class CURLRequest : public mbgl::platform::Request { +public: + CURLRequest(const std::string &url, + std::function<void(mbgl::platform::Response *)> callback, + std::shared_ptr<uv::loop> loop) + : Request(url, callback, loop) {} + + CURL *curl = nullptr; +}; + + +// Implementation starts here. + +// Locks the CURL share handle +void curl_share_lock(CURL *, curl_lock_data, curl_lock_access, void *) { curl_share_mutex.lock(); } + +// Unlocks the CURL share handle +void curl_share_unlock(CURL *, curl_lock_data, void *) { curl_share_mutex.unlock(); } + +curl_context *create_curl_context(curl_socket_t sockfd) { + curl_context *context = new curl_context; + context->sockfd = sockfd; + + uv_poll_init_socket(loop, &context->poll_handle, sockfd); + context->poll_handle.data = context; + + return context; +} + +void curl_close_cb(uv_handle_t *handle) { + curl_context *context = (curl_context *)handle->data; + free(context); +} + +void destroy_curl_context(curl_context *context) { + uv_close((uv_handle_t *)&context->poll_handle, curl_close_cb); +} + +void remove_curl_handle(CURL *handle) { + CURLMcode error = curl_multi_remove_handle(curl_multi, handle); + if (error != CURLM_OK) { + throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(error)); + } + + curl_easy_reset(handle); + curl_handle_cache.push(handle); +} + +void curl_perform(uv_poll_t *req, int /*status*/, int events) { + int running_handles; + int flags = 0; + curl_context *context; + CURLMsg *message; + int pending; + + uv_timer_stop(&timeout); + + if (events & UV_READABLE) + flags |= CURL_CSELECT_IN; + if (events & UV_WRITABLE) + flags |= CURL_CSELECT_OUT; + + context = (curl_context *)req; + + curl_multi_socket_action(curl_multi, context->sockfd, flags, &running_handles); + + while ((message = curl_multi_info_read(curl_multi, &pending))) { + switch (message->msg) { + case CURLMSG_DONE: { + std::shared_ptr<Request> *req = nullptr; + curl_easy_getinfo(message->easy_handle, CURLINFO_PRIVATE, (char *)&req); + + // Add human-readable error code + if (message->data.result != CURLE_OK) { + (*req)->res->error_message = curl_easy_strerror(message->data.result); + (*req)->res->code = -1; + } else { + curl_easy_getinfo(message->easy_handle, CURLINFO_RESPONSE_CODE, &(*req)->res->code); + } + + // We're currently in the CURL request thread. We're going to schedule a uv_work request + // that executes the background function in a threadpool, and tell it to call the + // after callback back in the main uv loop. + (*req)->complete(); + + CURL *handle = message->easy_handle; + remove_curl_handle(handle); + + // We're setting this to NULL because there might still be shared_ptrs around that could + // be cancelled. + ((CURLRequest *)req->get())->curl = nullptr; + + // Delete the shared_ptr pointer we created earlier. + delete req; + break; + } + + default: + // This should never happen, because there are no other message types. + throw std::runtime_error("CURLMSG returned unknown message type"); + } + } +} + +int handle_socket(CURL * /*easy*/, curl_socket_t s, int action, void * /*userp*/, void *socketp) { + curl_context *context = nullptr; + + if (socketp) { + context = (curl_context *)socketp; + } else if (action != CURL_POLL_REMOVE) { + context = create_curl_context(s); + } + + if (context) { + curl_multi_assign(curl_multi, s, (void *)context); + if (action == CURL_POLL_IN || action == CURL_POLL_INOUT) { + uv_poll_start(&context->poll_handle, UV_READABLE, curl_perform); + } + if (action == CURL_POLL_OUT || action == CURL_POLL_INOUT) { + uv_poll_start(&context->poll_handle, UV_WRITABLE, curl_perform); + } + if (action == CURL_POLL_REMOVE && socketp) { + uv_poll_stop(&context->poll_handle); + destroy_curl_context(context); + curl_multi_assign(curl_multi, s, NULL); + } + } + + return 0; +} + +void on_timeout(uv_timer_t *, int status /*req*/) { + int running_handles; + CURLMcode error = + curl_multi_socket_action(curl_multi, CURL_SOCKET_TIMEOUT, 0, &running_handles); + if (error != CURLM_OK) { + throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(error)); + } +} + +void start_timeout(CURLM * /*multi*/, long timeout_ms, void * /*userp*/) { + if (timeout_ms <= 0) { + on_timeout(&timeout, -1); + } else { + uv_timer_start(&timeout, &on_timeout, timeout_ms, 0); + } +} + +// This function is the first function called in the request thread. It sets up the CURL share/multi +// handles and runs the thread loop. +void thread_init(void * /*ptr*/) { + uv_timer_init(loop, &timeout); + + CURLSHcode share_error; + curl_share = curl_share_init(); + + share_error = curl_share_setopt(curl_share, CURLSHOPT_LOCKFUNC, curl_share_lock); + if (share_error != CURLSHE_OK) { + throw std::runtime_error(std::string("CURL share error: ") + curl_share_strerror(share_error)); + } + + share_error = curl_share_setopt(curl_share, CURLSHOPT_UNLOCKFUNC, curl_share_unlock); + if (share_error != CURLSHE_OK) { + throw std::runtime_error(std::string("CURL share error: ") + curl_share_strerror(share_error)); + } + + CURLMcode multi_error; + curl_multi = curl_multi_init(); + + multi_error = curl_multi_setopt(curl_multi, CURLMOPT_SOCKETFUNCTION, handle_socket); + if (multi_error != CURLM_OK) { + throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(multi_error)); + } + multi_error = curl_multi_setopt(curl_multi, CURLMOPT_TIMERFUNCTION, start_timeout); + if (multi_error != CURLM_OK) { + throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(multi_error)); + + } + + // Main event loop. This will not return until the request loop is terminated. + uv_run(loop, UV_RUN_DEFAULT); + + curl_multi_cleanup(curl_multi); + curl_multi = nullptr; + curl_share_cleanup(curl_share); + curl_share = nullptr; + + // Clean up all the CURL easy handles that we kept around for potential future reuse. + while (!curl_handle_cache.empty()) { + curl_easy_cleanup(curl_handle_cache.front()); + curl_handle_cache.pop(); + } +} + +// This function is called when we have new data for a request. We just append it to the string +// containing the previous data. +size_t curl_write_cb(void *contents, size_t size, size_t nmemb, void *userp) { + ((std::string *)userp)->append((char *)contents, size * nmemb); + return size * nmemb; +} + +// This callback is called in the request event loop (on the request thread). +// It initializes newly queued up download requests and adds them to the CURL +// multi handle. +void async_add_cb(uv_async_t *, int status /*async*/) { + std::shared_ptr<Request> *req = nullptr; + while (add_queue.pop(req)) { + // Make sure that we're not starting requests that have been cancelled + // already by async_cancel_cb. + if ((*req)->cancelled) { + delete req; + continue; + } + + // Obtain a curl handle (and try to reuse existing handles before creating new ones). + CURL *handle = nullptr; + if (!curl_handle_cache.empty()) { + handle = curl_handle_cache.front(); + curl_handle_cache.pop(); + } else { + handle = curl_easy_init(); + } + + ((CURLRequest *)req->get())->curl = handle; + + curl_easy_setopt(handle, CURLOPT_PRIVATE, req); + curl_easy_setopt(handle, CURLOPT_CAINFO, "ca-bundle.crt"); + curl_easy_setopt(handle, CURLOPT_URL, (*req)->url.c_str()); + curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, curl_write_cb); + curl_easy_setopt(handle, CURLOPT_WRITEDATA, &(*req)->res->body); + curl_easy_setopt(handle, CURLOPT_ACCEPT_ENCODING, "gzip, deflate"); + curl_easy_setopt(handle, CURLOPT_SHARE, curl_share); + curl_multi_add_handle(curl_multi, handle); + } +} + +void async_cancel_cb(uv_async_t *, int status /*async*/) { + std::shared_ptr<Request> *req = nullptr; + while (cancel_queue.pop(req)) { + // It is possible that the request has not yet been started, but that it already has been + // added to the queue for scheduling new requests. In this case, the CURL handle is invalid + // and we manually mark the Request as cancelled. + CURL *handle = ((CURLRequest *)req->get())->curl; + if (handle && !(*req)->cancelled) { + remove_curl_handle(handle); + ((CURLRequest *)req->get())->curl = nullptr; + } + (*req)->cancelled = true; + + delete req; + req = nullptr; + } +} + +void thread_init_cb() { + curl_global_init(CURL_GLOBAL_ALL); + + loop = uv_loop_new(); + uv_async_init(loop, &async_add, &async_add_cb); + uv_async_init(loop, &async_cancel, &async_cancel_cb); + uv_thread_create(&thread, thread_init, nullptr); +} +} // end namespace request +} // end namespace platform + + +std::shared_ptr<platform::Request> +platform::request_http(const std::string &url, + std::function<void(Response *)> callback, + std::shared_ptr<uv::loop> loop) { + using namespace request; + init_thread_once(thread_init_cb); + std::shared_ptr<CURLRequest> req = std::make_shared<CURLRequest>(url, callback, loop); + + // Note that we are creating a new shared_ptr pointer(!) because the lockless queue can't store + // objects with nontrivial destructors. We have to make absolutely sure that we manually delete + // the shared_ptr when we pop it from the queue. + add_queue.push(new std::shared_ptr<Request>(req)); + uv_async_send(&async_add); + + return req; +} + +// Cancels an HTTP request. +void platform::cancel_request_http(const std::shared_ptr<Request> &req) { + if (req) { + using namespace request; + + // Note that we are creating a new shared_ptr pointer(!) because the lockless queue can't + // store objects with nontrivial destructors. We have to make absolutely shure that we + // manually delete the shared_ptr when we pop it from the queue. + cancel_queue.push(new std::shared_ptr<Request>(req)); + uv_async_send(&async_cancel); + } +} +} // end namespace mbgl diff --git a/common/glfw_view.cpp b/common/glfw_view.cpp index edfb3a6a6f..120faf4df1 100644 --- a/common/glfw_view.cpp +++ b/common/glfw_view.cpp @@ -8,7 +8,10 @@ GLFWView::GLFWView(bool fullscreen) : fullscreen(fullscreen) { #endif } -GLFWView::~GLFWView() { glfwTerminate(); } +GLFWView::~GLFWView() { + map->terminate(); + glfwTerminate(); +} void GLFWView::initialize(mbgl::Map *map) { View::initialize(map); @@ -194,6 +197,10 @@ void GLFWView::make_active() { glfwMakeContextCurrent(window); } +void GLFWView::make_inactive() { + glfwMakeContextCurrent(nullptr); +} + void GLFWView::notify() { glfwPostEmptyEvent(); } diff --git a/common/glfw_view.hpp b/common/glfw_view.hpp index 481b1598bb..6e91c1125e 100644 --- a/common/glfw_view.hpp +++ b/common/glfw_view.hpp @@ -17,6 +17,7 @@ public: void initialize(mbgl::Map *map); void swap(); void make_active(); + void make_inactive(); void notify(); void notify_map_change(mbgl::MapChange change, mbgl::timestamp delay = 0); diff --git a/common/glx.h b/common/glx.h new file mode 100644 index 0000000000..6b7d9a3df9 --- /dev/null +++ b/common/glx.h @@ -0,0 +1,2 @@ +#include <GL/glx.h> +#undef None diff --git a/common/headless_display.cpp b/common/headless_display.cpp new file mode 100644 index 0000000000..3aaf2020b9 --- /dev/null +++ b/common/headless_display.cpp @@ -0,0 +1,74 @@ +#include "headless_display.hpp" + +#include <cstring> +#include <stdexcept> + +namespace mbgl { + +HeadlessDisplay::HeadlessDisplay() { +#if MBGL_USE_CGL + // TODO: test if OpenGL 4.1 with GL_ARB_ES2_compatibility is supported + // If it is, use kCGLOGLPVersion_3_2_Core and enable that extension. + CGLPixelFormatAttribute attributes[] = { + kCGLPFAOpenGLProfile, + (CGLPixelFormatAttribute) kCGLOGLPVersion_Legacy, + kCGLPFAAccelerated, + (CGLPixelFormatAttribute) 0 + }; + + GLint num; + CGLError error = CGLChoosePixelFormat(attributes, &pixelFormat, &num); + if (error) { + fprintf(stderr, "Error pixel format: %s\n", CGLErrorString(error)); + return; + } +#endif + +#if MBGL_USE_GLX + if (!XInitThreads()) { + throw std::runtime_error("Failed to XInitThreads"); + } + + x_display = XOpenDisplay(nullptr); + if (x_display == nullptr) { + throw std::runtime_error("Failed to open X display"); + } + + const char *extensions = (char *)glXQueryServerString(x_display, DefaultScreen(x_display), GLX_EXTENSIONS); + if (!extensions) { + throw std::runtime_error("Cannot read GLX extensions"); + } + if (!strstr(extensions,"GLX_SGIX_fbconfig")) { + throw std::runtime_error("Extension GLX_SGIX_fbconfig was not found"); + } + if (!strstr(extensions, "GLX_SGIX_pbuffer")) { + throw std::runtime_error("Cannot find glXCreateContextAttribsARB"); + } + + // We're creating a dummy pbuffer anyway that we're not using. + static int pixelFormat[] = { + GLX_DRAWABLE_TYPE, GLX_PBUFFER_BIT, + None + }; + + int configs = 0; + fb_configs = glXChooseFBConfig(x_display, DefaultScreen(x_display), pixelFormat, &configs); + if (configs <= 0) { + throw std::runtime_error("No Framebuffer configurations"); + } +#endif +} + +HeadlessDisplay::~HeadlessDisplay() { +#if MBGL_USE_CGL + CGLDestroyPixelFormat(pixelFormat); +#endif + +#if MBGL_USE_GLX + XFree(fb_configs); + XCloseDisplay(x_display); +#endif +} + +} + diff --git a/common/headless_display.hpp b/common/headless_display.hpp new file mode 100644 index 0000000000..5b33fd6990 --- /dev/null +++ b/common/headless_display.hpp @@ -0,0 +1,25 @@ +#ifndef MBGL_COMMON_HEADLESS_DISPLAY +#define MBGL_COMMON_HEADLESS_DISPLAY + +#include "headless_view.hpp" + +namespace mbgl { + +class HeadlessDisplay { +public: + HeadlessDisplay(); + ~HeadlessDisplay(); + +#if MBGL_USE_CGL + CGLPixelFormatObj pixelFormat; +#endif + +#if MBGL_USE_GLX + Display *x_display = nullptr; + GLXFBConfig *fb_configs = nullptr; +#endif +}; + +} + +#endif diff --git a/common/headless_view.cpp b/common/headless_view.cpp index ace41d38c0..d1d13db5f8 100644 --- a/common/headless_view.cpp +++ b/common/headless_view.cpp @@ -1,88 +1,158 @@ #include "headless_view.hpp" -#include <mbgl/util/timer.hpp> +#include "headless_display.hpp" #include <stdexcept> +#include <sstream> +#include <string> + +#if MBGL_USE_GLX +#ifdef GLX_ARB_create_context +static PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB = nullptr; +#endif +#endif namespace mbgl { -HeadlessView::HeadlessView() { -#if MBGL_USE_CGL - // TODO: test if OpenGL 4.1 with GL_ARB_ES2_compatibility is supported - // If it is, use kCGLOGLPVersion_3_2_Core and enable that extension. - CGLPixelFormatAttribute attributes[] = { - kCGLPFAOpenGLProfile, - (CGLPixelFormatAttribute) kCGLOGLPVersion_Legacy, - kCGLPFAAccelerated, - (CGLPixelFormatAttribute) 0 - }; +HeadlessView::HeadlessView() + : display_(std::make_shared<HeadlessDisplay>()) { + createContext(); +} - CGLPixelFormatObj pixelFormat; - GLint num; - CGLError error = CGLChoosePixelFormat(attributes, &pixelFormat, &num); - if (error) { - fprintf(stderr, "Error pixel format\n"); - return; +HeadlessView::HeadlessView(std::shared_ptr<HeadlessDisplay> display) + : display_(display) { + createContext(); +} + + +#if MBGL_USE_GLX +#ifdef GLX_ARB_create_context + +// These are all of the OpenGL Core profile version that we know about. +struct core_profile_version { int major, minor; }; +static const core_profile_version core_profile_versions[] = { + {4, 5}, + {4, 4}, + {4, 3}, + {4, 2}, + {4, 1}, + {4, 0}, + {3, 3}, + {3, 2}, + {3, 1}, + {3, 0}, + {0, 0}, +}; + +GLXContext createCoreProfile(Display *dpy, GLXFBConfig fbconfig) { + static bool context_creation_failed = false; + GLXContext ctx = 0; + + // Set the Error Handler to avoid crashing the program when the context creation fails. + // It is expected that some context creation attempts fail, e.g. because the OpenGL + // implementation does not support the version we're requesting. + int (*previous_error_handler)(Display *, XErrorEvent *) = XSetErrorHandler([](Display *, XErrorEvent *) { + context_creation_failed = true; + return 0; + }); + + // Try to create core profiles from the highest known version on down. + for (int i = 0; !ctx && core_profile_versions[i].major; i++) { + context_creation_failed = false; + const int context_flags[] = { + GLX_CONTEXT_MAJOR_VERSION_ARB, core_profile_versions[i].major, + GLX_CONTEXT_MINOR_VERSION_ARB, core_profile_versions[i].minor, + None + }; + ctx = glXCreateContextAttribsARB(dpy, fbconfig, 0, True, context_flags); + if (context_creation_failed) { + ctx = 0; + } } - error = CGLCreateContext(pixelFormat, NULL, &gl_context); - CGLDestroyPixelFormat(pixelFormat); + // Restore the old error handler. + XSetErrorHandler(previous_error_handler); + return ctx; +} +#endif +#endif + +void HeadlessView::createContext() { +#if MBGL_USE_CGL + CGLError error = CGLCreateContext(display_->pixelFormat, NULL, &gl_context); if (error) { - fprintf(stderr, "Error creating GL context object\n"); - return; + throw std::runtime_error("Error creating GL context object\n"); + } + + error = CGLEnable(gl_context, kCGLCEMPEngine); + if (error != kCGLNoError ) { + throw std::runtime_error("Error enabling OpenGL multithreading\n"); } #endif #if MBGL_USE_GLX - x_display = XOpenDisplay(0); - - if (x_display == nullptr) { - throw std::runtime_error("Failed to open X display"); +#ifdef GLX_ARB_create_context + if (glXCreateContextAttribsARB == nullptr) { + glXCreateContextAttribsARB = (PFNGLXCREATECONTEXTATTRIBSARBPROC)glXGetProcAddressARB((const GLubyte *)"glXCreateContextAttribsARB"); } +#endif - static int pixelFormat[] = { - GLX_RGBA, - GLX_DOUBLEBUFFER, - GLX_RED_SIZE, 8, - GLX_GREEN_SIZE, 8, - GLX_BLUE_SIZE, 8, - GLX_ALPHA_SIZE, 8, - GLX_DEPTH_SIZE, 24, - GLX_STENCIL_SIZE, 8, - None - }; + x_display = display_->x_display; + fb_configs = display_->fb_configs; - x_info = glXChooseVisual(x_display, DefaultScreen(x_display), pixelFormat); +#ifdef GLX_ARB_create_context + if (glXCreateContextAttribsARB) { + // Try to create a core profile context. + gl_context = createCoreProfile(x_display, fb_configs[0]); + } +#endif - if (x_info == nullptr) { - throw std::runtime_error("Error pixel format"); + if (!gl_context) { + // Try to create a legacy context + gl_context = glXCreateNewContext(x_display, fb_configs[0], GLX_RGBA_TYPE, 0, True); + if (gl_context) { + if (!glXIsDirect(x_display, gl_context)) { + glXDestroyContext(x_display, gl_context); + gl_context = 0; + } + } } - gl_context = glXCreateContext(x_display, x_info, 0, GL_TRUE); - if (gl_context == nullptr) { + if (gl_context == 0) { throw std::runtime_error("Error creating GL context object"); } + + // Create a dummy pbuffer. We will render to framebuffers anyway, but we need a pbuffer to + // activate the context. + int pbuffer_attributes[] = { + GLX_PBUFFER_WIDTH, 8, + GLX_PBUFFER_HEIGHT, 8, + None + }; + glx_pbuffer = glXCreatePbuffer(x_display, fb_configs[0], pbuffer_attributes); #endif } - void HeadlessView::resize(uint16_t width, uint16_t height, float pixelRatio) { clear_buffers(); - width *= pixelRatio; - height *= pixelRatio; + width_ = width; + height_ = height; + pixelRatio_ = pixelRatio; + + const unsigned int w = width_ * pixelRatio_; + const unsigned int h = height_ * pixelRatio_; -#if MBGL_USE_CGL make_active(); // Create depth/stencil buffer glGenRenderbuffersEXT(1, &fbo_depth_stencil); glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, fbo_depth_stencil); - glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH24_STENCIL8_EXT, width, height); + glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH24_STENCIL8_EXT, w, h); glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0); glGenRenderbuffersEXT(1, &fbo_color); glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, fbo_color); - glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_RGBA8, width, height); + glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_RGBA8, w, h); glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0); glGenFramebuffersEXT(1, &fbo); @@ -94,29 +164,39 @@ void HeadlessView::resize(uint16_t width, uint16_t height, float pixelRatio) { GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); if (status != GL_FRAMEBUFFER_COMPLETE_EXT) { - fprintf(stderr, "Couldn't create framebuffer: "); + std::stringstream error("Couldn't create framebuffer: "); switch (status) { - case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT: fprintf(stderr, "incomplete attachment\n"); break; - case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT: fprintf(stderr, "incomplete missing attachment\n"); break; - case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT: fprintf(stderr, "incomplete draw buffer\n"); break; - case GL_FRAMEBUFFER_UNSUPPORTED: fprintf(stderr, "unsupported\n"); break; - default: fprintf(stderr, "other\n"); break; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT: (error << "incomplete attachment\n"); break; + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT: error << "incomplete missing attachment\n"; break; + case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT: error << "incomplete dimensions\n"; break; + case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT: error << "incomplete formats\n"; break; + case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT: error << "incomplete draw buffer\n"; break; + case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT: error << "incomplete read buffer\n"; break; + case GL_FRAMEBUFFER_UNSUPPORTED: error << "unsupported\n"; break; + default: error << "other\n"; break; } - return; + throw std::runtime_error(error.str()); } -#endif -#if MBGL_USE_GLX - x_pixmap = XCreatePixmap(x_display, DefaultRootWindow(x_display), width, height, 32); - glx_pixmap = glXCreateGLXPixmap(x_display, x_info, x_pixmap); + make_inactive(); +} + +const std::unique_ptr<uint32_t[]> HeadlessView::readPixels() { + const unsigned int w = width_ * pixelRatio_; + const unsigned int h = height_ * pixelRatio_; + + std::unique_ptr<uint32_t[]> pixels(new uint32_t[w * h]); make_active(); -#endif + glReadPixels(0, 0, width_, height_, GL_RGBA, GL_UNSIGNED_BYTE, pixels.get()); + make_inactive(); + return pixels; } void HeadlessView::clear_buffers() { -#if MBGL_USE_CGL + make_active(); + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); if (fbo) { @@ -133,19 +213,8 @@ void HeadlessView::clear_buffers() { glDeleteRenderbuffersEXT(1, &fbo_depth_stencil); fbo_depth_stencil = 0; } -#endif -#if MBGL_USE_GLX - if (glx_pixmap) { - glXDestroyGLXPixmap(x_display, glx_pixmap); - glx_pixmap = 0; - } - - if (x_pixmap) { - XFreePixmap(x_display, x_pixmap); - x_pixmap = 0; - } -#endif + make_inactive(); } HeadlessView::~HeadlessView() { @@ -156,10 +225,12 @@ HeadlessView::~HeadlessView() { #endif #if MBGL_USE_GLX - glXMakeCurrent(x_display, None, NULL); + if (glx_pbuffer) { + glXDestroyPbuffer(x_display, glx_pbuffer); + glx_pbuffer = 0; + } + glXDestroyContext(x_display, gl_context); - XFree(x_info); - XCloseDisplay(x_display); #endif } @@ -175,26 +246,33 @@ void HeadlessView::make_active() { #if MBGL_USE_CGL CGLError error = CGLSetCurrentContext(gl_context); if (error) { - fprintf(stderr, "Switching OpenGL context failed\n"); + throw std::runtime_error("Switching OpenGL context failed\n"); } #endif #if MBGL_USE_GLX - if (!glXMakeCurrent(x_display, glx_pixmap, gl_context)) { - fprintf(stderr, "Switching OpenGL context failed\n"); + if (!glXMakeContextCurrent(x_display, glx_pbuffer, glx_pbuffer, gl_context)) { + throw std::runtime_error("Switching OpenGL context failed\n"); } #endif } -void HeadlessView::swap() {} - -unsigned int HeadlessView::root_fbo() { +void HeadlessView::make_inactive() { #if MBGL_USE_CGL - return fbo; + CGLError error = CGLSetCurrentContext(nullptr); + if (error) { + throw std::runtime_error("Removing OpenGL context failed\n"); + } #endif - return 0; +#if MBGL_USE_GLX + if (!glXMakeContextCurrent(x_display, 0, 0, nullptr)) { + throw std::runtime_error("Removing OpenGL context failed\n"); + } +#endif } +void HeadlessView::swap() {} + } diff --git a/common/headless_view.hpp b/common/headless_view.hpp index 42f9c46da2..c0baddb884 100644 --- a/common/headless_view.hpp +++ b/common/headless_view.hpp @@ -1,51 +1,63 @@ -#ifndef MBGL_COMMON_HEADLESS_CGL -#define MBGL_COMMON_HEADLESS_CGL +#ifndef MBGL_COMMON_HEADLESS_VIEW +#define MBGL_COMMON_HEADLESS_VIEW #ifdef __APPLE__ #define MBGL_USE_CGL 1 #else +#define GL_GLEXT_PROTOTYPES #include <GL/glx.h> #define MBGL_USE_GLX 1 #endif #include <mbgl/map/view.hpp> #include <mbgl/platform/gl.hpp> -#include <mbgl/util/time.hpp> + +#include <memory> namespace mbgl { +class HeadlessDisplay; + class HeadlessView : public View { public: HeadlessView(); + HeadlessView(std::shared_ptr<HeadlessDisplay> display); ~HeadlessView(); + void createContext(); + void resize(uint16_t width, uint16_t height, float pixelRatio); + const std::unique_ptr<uint32_t[]> readPixels(); void notify(); void notify_map_change(MapChange change, timestamp delay = 0); void make_active(); + void make_inactive(); void swap(); - unsigned int root_fbo(); private: void clear_buffers(); - private: + std::shared_ptr<HeadlessDisplay> display_; + uint16_t width_; + uint16_t height_; + float pixelRatio_; + #if MBGL_USE_CGL CGLContextObj gl_context; - GLuint fbo = 0; - GLuint fbo_depth_stencil = 0; - GLuint fbo_color = 0; #endif #if MBGL_USE_GLX - GLXContext gl_context = nullptr; - XVisualInfo *x_info = nullptr; Display *x_display = nullptr; - Pixmap x_pixmap = 0; - GLXPixmap glx_pixmap = 0; + GLXFBConfig *fb_configs = nullptr; + GLXContext gl_context = 0; + GLXPbuffer glx_pbuffer = 0; #endif + + GLuint fbo = 0; + GLuint fbo_depth_stencil = 0; + GLuint fbo_color = 0; }; } diff --git a/common/http_request_baton_curl.cpp b/common/http_request_baton_curl.cpp index d4753266c0..b5226e20ec 100644 --- a/common/http_request_baton_curl.cpp +++ b/common/http_request_baton_curl.cpp @@ -267,7 +267,7 @@ void on_timeout(uv_timer_t *) { void start_timeout(CURLM *, long timeout_ms, void *) { if (timeout_ms <= 0) { - on_timeout(&timeout); + on_timeout(&timeout, 0); } else { uv_timer_start(&timeout, on_timeout, timeout_ms, 0); } |