diff options
-rw-r--r-- | .travis.yml | 1 | ||||
-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 | 69 | ||||
-rw-r--r-- | common/headless_display.hpp | 25 | ||||
-rw-r--r-- | common/headless_view.cpp | 139 | ||||
-rw-r--r-- | common/headless_view.hpp | 26 | ||||
-rw-r--r-- | include/mbgl/map/map.hpp | 11 | ||||
-rw-r--r-- | include/mbgl/map/view.hpp | 4 | ||||
-rw-r--r-- | include/mbgl/renderer/painter.hpp | 2 | ||||
-rw-r--r-- | include/mbgl/style/class_dictionary.hpp | 13 | ||||
-rw-r--r-- | include/mbgl/util/uv_detail.hpp | 13 | ||||
m--------- | ios/mapbox-gl-cocoa | 0 | ||||
-rwxr-xr-x | setup-libraries.sh | 25 | ||||
-rw-r--r-- | src/map/map.cpp | 22 | ||||
-rw-r--r-- | src/renderer/painter.cpp | 20 | ||||
-rw-r--r-- | src/style/class_dictionary.cpp | 34 | ||||
-rw-r--r-- | src/style/style_layer.cpp | 2 | ||||
-rw-r--r-- | src/style/style_parser.cpp | 2 | ||||
-rw-r--r-- | src/util/uv.cpp | 2 | ||||
-rw-r--r-- | test/fixtures/fixture_request.cpp | 1 | ||||
-rw-r--r-- | test/headless.cpp | 10 | ||||
-rw-r--r-- | test/test.gyp | 2 |
25 files changed, 698 insertions, 120 deletions
diff --git a/.travis.yml b/.travis.yml index 42cca837c6..a2bf164c62 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,6 +22,7 @@ env: - secure: "bG4YYWMfl9API0MSRgmOaJrlGLv06tRg9KJNawBieZvBJbITPpxVGJZT3/l/SEJ+Rl15e2dRex4k+MGQlmT2SqPQxTEYWv1qxNigKPPcla7IWeNmWWqW8uVvFjdglojgBOK2k/xErVQtA4zDfi3mwSXH4DKwquXWsoEKmX2SV7M=" - secure: "Cbvap9ubVKgjPe3hUhI6JGeDZzBXHpOG9RaYKh+SdoIPhKnlJiNOYm1egomi+e4uqJInlFKuVHTw7Ng9Cun6Zm0jIxpkSchv1GpsR7hmB3UGnGed19Dw8121FwuUaktN+4YnbVlsyd+u8EHD3+h58t4eELrLrZolM4rS7DL6caA=" - secure: "RiBIBfVhhaMjU5ksuwJO3shdvG9FpinBjdSv4co9jg9171SR8edNriedHjVKSIeBhSGNmZmX+twS3dJS/By6tl/LKh9sTynA+ZAYYljkE7jn881B/gMrlYvdAA6og5KvkhV1/0iJWlhuZrMTkhpDR200iLgg3EWBhWjltzmDW/I=" + - AWS_S3_BUCKET=mapbox-gl-testing before_install: - source ./scripts/flags.sh 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..bbb1c10f51 --- /dev/null +++ b/common/headless_display.cpp @@ -0,0 +1,69 @@ +#include "headless_display.hpp" + +#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(0); + + if (x_display == nullptr) { + throw std::runtime_error("Failed to open X display"); + } + + 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, + 0 + }; + + x_info = glXChooseVisual(x_display, DefaultScreen(x_display), pixelFormat); + + if (x_info == nullptr) { + throw std::runtime_error("Error pixel format"); + } +#endif +} + +HeadlessDisplay::~HeadlessDisplay() { +#if MBGL_USE_CGL + CGLDestroyPixelFormat(pixelFormat); +#endif + +#if MBGL_USE_GLX + XFree(x_info); + XCloseDisplay(x_display); +#endif +} + +} + diff --git a/common/headless_display.hpp b/common/headless_display.hpp new file mode 100644 index 0000000000..0eb41911ee --- /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; + XVisualInfo *x_info = nullptr; +#endif +}; + +} + +#endif diff --git a/common/headless_view.cpp b/common/headless_view.cpp index ace41d38c0..c2084ac90d 100644 --- a/common/headless_view.cpp +++ b/common/headless_view.cpp @@ -1,61 +1,38 @@ #include "headless_view.hpp" -#include <mbgl/util/timer.hpp> +#include "headless_display.hpp" #include <stdexcept> +#include <sstream> +#include <string> namespace mbgl { -HeadlessView::HeadlessView() { +HeadlessView::HeadlessView() + : display_(std::make_shared<HeadlessDisplay>()) { + createContext(); +} + +HeadlessView::HeadlessView(std::shared_ptr<HeadlessDisplay> display) + : display_(display) { + createContext(); +} + +void HeadlessView::createContext() { #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 - }; - - CGLPixelFormatObj pixelFormat; - GLint num; - CGLError error = CGLChoosePixelFormat(attributes, &pixelFormat, &num); + CGLError error = CGLCreateContext(display_->pixelFormat, NULL, &gl_context); if (error) { - fprintf(stderr, "Error pixel format\n"); - return; + throw std::runtime_error("Error creating GL context object\n"); } - error = CGLCreateContext(pixelFormat, NULL, &gl_context); - CGLDestroyPixelFormat(pixelFormat); - if (error) { - fprintf(stderr, "Error creating GL context object\n"); - return; + 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"); - } - - 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_info = glXChooseVisual(x_display, DefaultScreen(x_display), pixelFormat); - - if (x_info == nullptr) { - throw std::runtime_error("Error pixel format"); - } + x_display = display_->x_display; + x_info = display_->x_info; gl_context = glXCreateContext(x_display, x_info, 0, GL_TRUE); if (gl_context == nullptr) { @@ -64,12 +41,15 @@ HeadlessView::HeadlessView() { #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(); @@ -77,12 +57,12 @@ void HeadlessView::resize(uint16_t width, uint16_t height, float pixelRatio) { // 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 +74,46 @@ 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()); } + + make_inactive(); #endif #if MBGL_USE_GLX - x_pixmap = XCreatePixmap(x_display, DefaultRootWindow(x_display), width, height, 32); + x_pixmap = XCreatePixmap(x_display, DefaultRootWindow(x_display), w, h, 32); glx_pixmap = glXCreateGLXPixmap(x_display, x_info, x_pixmap); +#endif +} + +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,6 +130,8 @@ void HeadlessView::clear_buffers() { glDeleteRenderbuffersEXT(1, &fbo_depth_stencil); fbo_depth_stencil = 0; } + + make_inactive(); #endif #if MBGL_USE_GLX @@ -145,6 +144,8 @@ void HeadlessView::clear_buffers() { XFreePixmap(x_display, x_pixmap); x_pixmap = 0; } + + make_inactive(); #endif } @@ -156,10 +157,7 @@ HeadlessView::~HeadlessView() { #endif #if MBGL_USE_GLX - glXMakeCurrent(x_display, None, NULL); glXDestroyContext(x_display, gl_context); - XFree(x_info); - XCloseDisplay(x_display); #endif } @@ -175,13 +173,28 @@ 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"); + throw std::runtime_error("Switching OpenGL context failed\n"); + } +#endif +} + +void HeadlessView::make_inactive() { +#if MBGL_USE_CGL + CGLError error = CGLSetCurrentContext(nullptr); + if (error) { + throw std::runtime_error("Removing OpenGL context failed\n"); + } +#endif + +#if MBGL_USE_GLX + if (!glXMakeCurrent(x_display, 0, NULL)) { + throw std::runtime_error("Removing OpenGL context failed\n"); } #endif } diff --git a/common/headless_view.hpp b/common/headless_view.hpp index 42f9c46da2..d2fe75382a 100644 --- a/common/headless_view.hpp +++ b/common/headless_view.hpp @@ -1,37 +1,49 @@ -#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 -#include <GL/glx.h> +#include "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; @@ -40,9 +52,9 @@ private: #endif #if MBGL_USE_GLX - GLXContext gl_context = nullptr; - XVisualInfo *x_info = nullptr; Display *x_display = nullptr; + XVisualInfo *x_info = nullptr; + GLXContext gl_context = nullptr; Pixmap x_pixmap = 0; GLXPixmap glx_pixmap = 0; #endif diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp index a69943aa74..7e4687ea6f 100644 --- a/include/mbgl/map/map.hpp +++ b/include/mbgl/map/map.hpp @@ -63,6 +63,9 @@ public: // Triggers a cleanup that releases resources. void cleanup(); + // Releases resources immediately + void terminate(); + // Controls buffer swapping. bool needsSwap(); void swapped(); @@ -143,10 +146,10 @@ public: private: // uv async callbacks - static void render(uv_async_t *async); - static void terminate(uv_async_t *async); - static void cleanup(uv_async_t *async); - static void delete_async(uv_handle_t *handle); + static void render(uv_async_t *async, int status); + static void terminate(uv_async_t *async, int status); + static void cleanup(uv_async_t *async, int status); + static void delete_async(uv_handle_t *handle, int status); // Setup void setup(); diff --git a/include/mbgl/map/view.hpp b/include/mbgl/map/view.hpp index bbdcd97c79..395a05d435 100644 --- a/include/mbgl/map/view.hpp +++ b/include/mbgl/map/view.hpp @@ -35,6 +35,10 @@ public: // renderer setup since the render thread doesn't switch the contexts. virtual void make_active() = 0; + // Called from the render thread. Makes the GL context inactive in the current + // thread. This is called once just before the rendering thread terminates. + virtual void make_inactive() = 0; + // Returns the base framebuffer object, if any, and 0 if using the system // provided framebuffer. virtual unsigned int root_fbo() { diff --git a/include/mbgl/renderer/painter.hpp b/include/mbgl/renderer/painter.hpp index 13c2050bd0..0f9bd79173 100644 --- a/include/mbgl/renderer/painter.hpp +++ b/include/mbgl/renderer/painter.hpp @@ -64,6 +64,7 @@ public: // lazy initialization) in case rendering continues. void cleanup(); + void terminate(); // Renders the backdrop of the OpenGL view. This also paints in areas where we don't have any // tiles whatsoever. @@ -124,6 +125,7 @@ public: private: void setupShaders(); + void deleteShaders(); mat4 translatedMatrix(const mat4& matrix, const std::array<float, 2> &translation, const Tile::ID &id, TranslateAnchorType anchor); void prepareTile(const Tile& tile); diff --git a/include/mbgl/style/class_dictionary.hpp b/include/mbgl/style/class_dictionary.hpp index c7f9c6a284..ecf80be3e3 100644 --- a/include/mbgl/style/class_dictionary.hpp +++ b/include/mbgl/style/class_dictionary.hpp @@ -14,17 +14,22 @@ enum class ClassID : uint32_t { }; class ClassDictionary { +private: + ClassDictionary(); + public: + static ClassDictionary &Get(); + // Returns an ID for a class name. If the class name does not yet have an ID, one is // auto-generated and stored for future reference. - static ClassID Lookup(const std::string &class_name); + ClassID lookup(const std::string &class_name); // Returns either Fallback, Default or Named, depending on the type of the class id. - static ClassID Normalize(ClassID id); + ClassID normalize(ClassID id); private: - static std::unordered_map<std::string, ClassID> store; - static uint32_t offset; + std::unordered_map<std::string, ClassID> store = { { "", ClassID::Default } }; + uint32_t offset = 0; }; } diff --git a/include/mbgl/util/uv_detail.hpp b/include/mbgl/util/uv_detail.hpp index e0a57ce65d..a80423f822 100644 --- a/include/mbgl/util/uv_detail.hpp +++ b/include/mbgl/util/uv_detail.hpp @@ -23,18 +23,13 @@ private: class loop { public: - inline loop() { - if (uv_loop_init(&l) != 0) { - throw std::runtime_error("failed to initialize loop"); - } - } - - inline ~loop() { uv_loop_close(&l); } + inline loop() : l(uv_loop_new()) {} + inline ~loop() { uv_loop_delete(l); } - inline uv_loop_t *operator*() { return &l; } + inline uv_loop_t *operator*() { return l; } private: - uv_loop_t l; + uv_loop_t *l; }; class mutex { diff --git a/ios/mapbox-gl-cocoa b/ios/mapbox-gl-cocoa -Subproject bc2c5fe6974fa99ea4816e9e793757cc1706659 +Subproject 1aa3db196b210c6880df6b2aff5e0d924aa7876 diff --git a/setup-libraries.sh b/setup-libraries.sh index e752d055c1..dc1b1643fa 100755 --- a/setup-libraries.sh +++ b/setup-libraries.sh @@ -51,7 +51,7 @@ set -u NODE=$(which node) NPM=$(which npm) -MP_HASH="eb8c6d7e6bd6adb42c232ed806b889f3e6825bb5" +MP_HASH="e82e6c00ea5a0cda147e4c121a3c7751cae69ff8" DIR_HASH=$(echo `pwd` | git hash-object --stdin) if [ ! -d 'mapnik-packaging/' ]; then @@ -68,8 +68,8 @@ export CXX11=true if [ ${UNAME} = 'Darwin' ]; then if [[ $TRAVIS ]]; then - if aws s3 cp s3://mapbox-gl-testing/dependencies/build-cpp11-libcpp-osx_${MP_HASH}_${DIR_HASH}.tar.gz ./out/ ; then - if aws s3 cp s3://mapbox-gl-testing/dependencies/build-cpp11-libcpp-ios_${MP_HASH}_${DIR_HASH}.tar.gz ./out/ ; then + if aws s3 cp s3://${AWS_S3_BUCKET}/dependencies/build-cpp11-libcpp-osx_${MP_HASH}_${DIR_HASH}.tar.gz ./out/ ; then + if aws s3 cp s3://${AWS_S3_BUCKET}/dependencies/build-cpp11-libcpp-ios_${MP_HASH}_${DIR_HASH}.tar.gz ./out/ ; then rm -rf out/build-cpp11-libcpp-x86_64-macosx rm -rf out/build-cpp11-libcpp-universal tar -xzf out/build-cpp11-libcpp-osx_${MP_HASH}_${DIR_HASH}.tar.gz @@ -81,6 +81,7 @@ fi if test -z "${TRAVIS:-}" || ! test -d out/build-cpp11-libcpp-universal; then source iPhoneOS.sh +export LIBUV_VERSION=0.10.28 if [ ! -f out/build-cpp11-libcpp-armv7-iphoneos/lib/libpng.a ] ; then ./scripts/build_png.sh ; fi if [ ! -f out/build-cpp11-libcpp-armv7-iphoneos/lib/libuv.a ] ; then ./scripts/build_libuv.sh ; fi if [ ! -f out/build-cpp11-libcpp-armv7-iphoneos/lib/libcurl.a ] ; then ./scripts/build_curl.sh ; fi @@ -88,6 +89,7 @@ source iPhoneOS.sh echo ' ...done' source iPhoneOSs.sh +export LIBUV_VERSION=0.10.28 if [ ! -f out/build-cpp11-libcpp-armv7s-iphoneoss/lib/libpng.a ] ; then ./scripts/build_png.sh ; fi if [ ! -f out/build-cpp11-libcpp-armv7s-iphoneoss/lib/libuv.a ] ; then ./scripts/build_libuv.sh ; fi if [ ! -f out/build-cpp11-libcpp-armv7s-iphoneoss/lib/libcurl.a ] ; then ./scripts/build_curl.sh ; fi @@ -95,6 +97,7 @@ source iPhoneOSs.sh echo ' ...done' source iPhoneOS64.sh +export LIBUV_VERSION=0.10.28 if [ ! -f out/build-cpp11-libcpp-arm64-iphoneos64/lib/libpng.a ] ; then ./scripts/build_png.sh ; fi if [ ! -f out/build-cpp11-libcpp-arm64-iphoneos64/lib/libuv.a ] ; then ./scripts/build_libuv.sh ; fi if [ ! -f out/build-cpp11-libcpp-arm64-iphoneos64/lib/libcurl.a ] ; then ./scripts/build_curl.sh ; fi @@ -102,6 +105,7 @@ source iPhoneOS64.sh echo ' ...done' source iPhoneSimulator.sh +export LIBUV_VERSION=0.10.28 if [ ! -f out/build-cpp11-libcpp-i386-iphonesimulator/lib/libpng.a ] ; then ./scripts/build_png.sh ; fi if [ ! -f out/build-cpp11-libcpp-i386-iphonesimulator/lib/libuv.a ] ; then ./scripts/build_libuv.sh ; fi if [ ! -f out/build-cpp11-libcpp-i386-iphonesimulator/lib/libcurl.a ] ; then ./scripts/build_curl.sh ; fi @@ -109,6 +113,7 @@ source iPhoneSimulator.sh echo ' ...done' source iPhoneSimulator64.sh +export LIBUV_VERSION=0.10.28 if [ ! -f out/build-cpp11-libcpp-x86_64-iphonesimulator/lib/libpng.a ] ; then ./scripts/build_png.sh ; fi if [ ! -f out/build-cpp11-libcpp-x86_64-iphonesimulator/lib/libuv.a ] ; then ./scripts/build_libuv.sh ; fi if [ ! -f out/build-cpp11-libcpp-x86_64-iphonesimulator/lib/libcurl.a ] ; then ./scripts/build_curl.sh ; fi @@ -116,6 +121,7 @@ source iPhoneSimulator64.sh echo ' ...done' source MacOSX.sh +export LIBUV_VERSION=0.10.28 if [ ! -f out/build-cpp11-libcpp-x86_64-macosx/lib/libpng.a ] ; then ./scripts/build_png.sh ; fi if [ ! -f out/build-cpp11-libcpp-x86_64-macosx/lib/libglfw3.a ] ; then ./scripts/build_glfw.sh ; fi if [ ! -f out/build-cpp11-libcpp-x86_64-macosx/lib/libuv.a ] ; then ./scripts/build_libuv.sh ; fi @@ -132,9 +138,9 @@ echo "NOTE: One patch FAILURE is expected. The other should have been applied or if [[ $TRAVIS && $AWS_ACCESS_KEY_ID && $AWS_SECRET_ACCESS_KEY ]] ; then tar -zcf out/build-cpp11-libcpp-ios_${MP_HASH}_${DIR_HASH}.tar.gz out/build-cpp11-libcpp-universal - aws s3 cp --acl public-read out/build-cpp11-libcpp-ios_${MP_HASH}_${DIR_HASH}.tar.gz s3://mapbox-gl-testing/dependencies/ + aws s3 cp --acl public-read out/build-cpp11-libcpp-ios_${MP_HASH}_${DIR_HASH}.tar.gz s3://${AWS_S3_BUCKET}/dependencies/ tar -zcf out/build-cpp11-libcpp-osx_${MP_HASH}_${DIR_HASH}.tar.gz out/build-cpp11-libcpp-x86_64-macosx - aws s3 cp --acl public-read out/build-cpp11-libcpp-osx_${MP_HASH}_${DIR_HASH}.tar.gz s3://mapbox-gl-testing/dependencies/ + aws s3 cp --acl public-read out/build-cpp11-libcpp-osx_${MP_HASH}_${DIR_HASH}.tar.gz s3://${AWS_S3_BUCKET}/dependencies/ fi fi @@ -147,13 +153,14 @@ cd ../../ elif [ ${UNAME} = 'Linux' ]; then if [[ $TRAVIS ]]; then - if aws s3 cp s3://mapbox-gl-testing/dependencies/build-cpp11-libstdcpp-gcc-x86_64-linux.tar.gz ./out/ ; then + if aws s3 cp s3://${AWS_S3_BUCKET}/dependencies/build-cpp11-libstdcpp-gcc-x86_64-linux_${MP_HASH}_${DIR_HASH}.tar.gz ./out/ ; then rm -rf out/build-cpp11-libstdcpp-gcc-x86_64-linux - tar -xzf out/build-cpp11-libstdcpp-gcc-x86_64-linux.tar.gz + tar -xzf out/build-cpp11-libstdcpp-gcc-x86_64-linux_${MP_HASH}_${DIR_HASH}.tar.gz fi fi source Linux.sh +export LIBUV_VERSION=0.10.28 if [ ! -f out/build-cpp11-libstdcpp-gcc-x86_64-linux/lib/libglfw3.a ] ; then ./scripts/build_glfw.sh ; fi if [ ! -f out/build-cpp11-libstdcpp-gcc-x86_64-linux/lib/libpng.a ] ; then ./scripts/build_png.sh ; fi if [ ! -f out/build-cpp11-libstdcpp-gcc-x86_64-linux/lib/libuv.a ] ; then ./scripts/build_libuv.sh ; fi @@ -164,8 +171,8 @@ source Linux.sh if [[ $TRAVIS && $AWS_ACCESS_KEY_ID && $AWS_SECRET_ACCESS_KEY ]] ; then if ! tar --compare -zf out/build-cpp11-libstdcpp-gcc-x86_64-linux.tar.gz ; then - tar -zcf out/build-cpp11-libstdcpp-gcc-x86_64-linux.tar.gz out/build-cpp11-libstdcpp-gcc-x86_64-linux - aws s3 cp --acl public-read out/build-cpp11-libstdcpp-gcc-x86_64-linux.tar.gz s3://mapbox-gl-testing/dependencies/ + tar -zcf out/build-cpp11-libstdcpp-gcc-x86_64-linux_${MP_HASH}_${DIR_HASH}.tar.gz out/build-cpp11-libstdcpp-gcc-x86_64-linux + aws s3 cp --acl public-read out/build-cpp11-libstdcpp-gcc-x86_64-linux_${MP_HASH}_${DIR_HASH}.tar.gz s3://${AWS_S3_BUCKET}/dependencies/ fi fi diff --git a/src/map/map.cpp b/src/map/map.cpp index a1543dbdae..9034c6d5ac 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -145,7 +145,7 @@ void Map::stop(stop_callback cb, void *data) { async = false; } -void Map::delete_async(uv_handle_t *handle) { +void Map::delete_async(uv_handle_t *handle, int status) { delete (uv_async_t *)handle; } @@ -167,7 +167,6 @@ void Map::run() { // If the map rendering wasn't started asynchronously, we perform one render // *after* all events have been processed. if (!async) { - prepare(); render(); #ifndef NDEBUG map_thread = -1; @@ -203,13 +202,16 @@ void Map::cleanup() { } } -void Map::cleanup(uv_async_t *async) { +void Map::cleanup(uv_async_t *async, int status) { Map *map = static_cast<Map *>(async->data); - map->view.make_active(); map->painter.cleanup(); } +void Map::terminate() { + painter.terminate(); +} + void Map::setReachability(bool reachable) { // Note: This function may be called from *any* thread. if (reachable) { @@ -221,7 +223,7 @@ void Map::setReachability(bool reachable) { } } -void Map::render(uv_async_t *async) { +void Map::render(uv_async_t *async, int status) { Map *map = static_cast<Map *>(async->data); assert(uv_thread_self() == map->map_thread); @@ -241,7 +243,7 @@ void Map::render(uv_async_t *async) { } } -void Map::terminate(uv_async_t *async) { +void Map::terminate(uv_async_t *async, int status) { // Closes all open handles on the loop. This means that the loop will automatically terminate. Map *map = static_cast<Map *>(async->data); assert(uv_thread_self() == map->map_thread); @@ -263,8 +265,8 @@ void Map::terminate(uv_async_t *async) { void Map::setup() { assert(uv_thread_self() == map_thread); view.make_active(); - painter.setup(); + view.make_inactive(); } void Map::setStyleURL(const std::string &url) { @@ -597,8 +599,6 @@ void Map::updateRenderState() { } void Map::prepare() { - view.make_active(); - if (!fileSource) { fileSource = std::make_shared<FileSource>(**loop, platform::defaultCacheDatabase()); glyphStore = std::make_shared<GlyphStore>(fileSource); @@ -643,6 +643,8 @@ void Map::prepare() { } void Map::render() { + view.make_active(); + #if defined(DEBUG) std::vector<std::string> debug; #endif @@ -677,6 +679,8 @@ void Map::render() { } glFlush(); + + view.make_inactive(); } void Map::renderLayers(util::ptr<StyleLayerGroup> group) { diff --git a/src/renderer/painter.cpp b/src/renderer/painter.cpp index 9643fb1561..8988112585 100644 --- a/src/renderer/painter.cpp +++ b/src/renderer/painter.cpp @@ -82,9 +82,29 @@ void Painter::setupShaders() { if (!gaussianShader) gaussianShader = std::make_unique<GaussianShader>(); } +void Painter::deleteShaders() { + plainShader = nullptr; + outlineShader = nullptr; + lineShader = nullptr; + linejoinShader = nullptr; + linepatternShader = nullptr; + patternShader = nullptr; + iconShader = nullptr; + rasterShader = nullptr; + sdfGlyphShader = nullptr; + sdfIconShader = nullptr; + dotShader = nullptr; + gaussianShader = nullptr; +} + void Painter::cleanup() { } +void Painter::terminate() { + cleanup(); + deleteShaders(); +} + void Painter::resize() { const TransformState &state = map.getState(); if (gl_viewport != state.getFramebufferDimensions()) { diff --git a/src/style/class_dictionary.cpp b/src/style/class_dictionary.cpp index 6e1eb5a879..ba7c0d55be 100644 --- a/src/style/class_dictionary.cpp +++ b/src/style/class_dictionary.cpp @@ -1,8 +1,34 @@ #include <mbgl/style/class_dictionary.hpp> +#include <uv.h> + namespace mbgl { -ClassID ClassDictionary::Lookup(const std::string &class_name) { +ClassDictionary::ClassDictionary() {} + +ClassDictionary &ClassDictionary::Get() { + // Note: We should eventually switch to uv_key_* functions, but libuv 0.10 doesn't have these + // yet. Instead, we're using the pthread functions directly for now. + static pthread_once_t store_once = PTHREAD_ONCE_INIT; + static pthread_key_t store_key; + + // Create the key. + pthread_once(&store_once, []() { + pthread_key_create(&store_key, [](void *ptr) { + delete reinterpret_cast<ClassDictionary *>(ptr); + }); + }); + + ClassDictionary *ptr = reinterpret_cast<ClassDictionary *>(pthread_getspecific(store_key)); + if (ptr == nullptr) { + ptr = new ClassDictionary(); + pthread_setspecific(store_key, ptr); + } + + return *ptr; +} + +ClassID ClassDictionary::lookup(const std::string &class_name) { auto it = store.find(class_name); if (it == store.end()) { // Insert the class name into the store. @@ -14,7 +40,7 @@ ClassID ClassDictionary::Lookup(const std::string &class_name) { } } -ClassID ClassDictionary::Normalize(ClassID id) { +ClassID ClassDictionary::normalize(ClassID id) { if (id >= ClassID::Named) { return ClassID::Named; } else { @@ -22,8 +48,4 @@ ClassID ClassDictionary::Normalize(ClassID id) { } } - -std::unordered_map<std::string, ClassID> ClassDictionary::store = { { "", ClassID::Default } }; -uint32_t ClassDictionary::offset = 0; - } diff --git a/src/style/style_layer.cpp b/src/style/style_layer.cpp index 4f758fe723..b1b878cc8d 100644 --- a/src/style/style_layer.cpp +++ b/src/style/style_layer.cpp @@ -23,7 +23,7 @@ void StyleLayer::setClasses(const std::vector<std::string> &class_names, const t for (auto it = class_names.rbegin(); it != class_names.rend(); it++) { const std::string &class_name = *it; // From here on, we're only dealing with IDs to avoid comparing strings all the time. - const ClassID class_id = ClassDictionary::Lookup(class_name); + const ClassID class_id = ClassDictionary::Get().lookup(class_name); applyClassProperties(class_id, already_applied, now, defaultTransition); } diff --git a/src/style/style_parser.cpp b/src/style/style_parser.cpp index c2247d51b2..2a64ab38f8 100644 --- a/src/style/style_parser.cpp +++ b/src/style/style_parser.cpp @@ -554,7 +554,7 @@ void StyleParser::parseStyles(JSVal value, std::map<ClassID, ClassProperties> &s if (name == "style") { parseStyle(replaceConstant(itr->value), styles[ClassID::Default]); } else if (name.compare(0, 6, "style.") == 0 && name.length() > 6) { - const ClassID class_id = ClassDictionary::Lookup(name.substr(6)); + const ClassID class_id = ClassDictionary::Get().lookup(name.substr(6)); parseStyle(replaceConstant(itr->value), styles[class_id]); } } diff --git a/src/util/uv.cpp b/src/util/uv.cpp index b97074f054..6e15ac4537 100644 --- a/src/util/uv.cpp +++ b/src/util/uv.cpp @@ -10,7 +10,7 @@ std::string cwd() { do { max += 256; dir.resize(max); - uv_cwd(const_cast<char *>(dir.data()), &max); + uv_cwd(const_cast<char *>(dir.data()), max); } while (max == dir.size()); dir.resize(max - 1); return dir; diff --git a/test/fixtures/fixture_request.cpp b/test/fixtures/fixture_request.cpp index 3f72b890db..b37cd92bee 100644 --- a/test/fixtures/fixture_request.cpp +++ b/test/fixtures/fixture_request.cpp @@ -4,6 +4,7 @@ #include <mbgl/util/url.hpp> #include <mbgl/util/std.hpp> #include <mbgl/platform/log.hpp> +#include <iostream> #include <uv.h> diff --git a/test/headless.cpp b/test/headless.cpp index 3cc2607e47..91533bb36b 100644 --- a/test/headless.cpp +++ b/test/headless.cpp @@ -10,6 +10,7 @@ #include <rapidjson/stringbuffer.h> #include "../common/headless_view.hpp" +#include "../common/headless_display.hpp" #include "./fixtures/fixture_log.hpp" @@ -22,6 +23,8 @@ const std::string base_directory = []{ return fn + "/node_modules/mapbox-gl-test-suite/"; }(); +auto display_ = std::make_shared<mbgl::HeadlessDisplay>(); + class HeadlessTest : public ::testing::TestWithParam<std::string> {}; TEST_P(HeadlessTest, render) { @@ -78,7 +81,7 @@ TEST_P(HeadlessTest, render) { } } - HeadlessView view; + HeadlessView view(display_); Map map(view); map.setStyleJSON(style, base_directory); @@ -95,10 +98,7 @@ TEST_P(HeadlessTest, render) { const unsigned int w = width * pixelRatio; const unsigned int h = height * pixelRatio; - const std::unique_ptr<uint32_t[]> pixels(new uint32_t[w * h]); - glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, pixels.get()); - - const std::string image = util::compress_png(w, h, pixels.get(), true); + const std::string image = util::compress_png(w, h, view.readPixels().get(), true); util::write_file(actual_image, image); } } diff --git a/test/test.gyp b/test/test.gyp index fd7725bb03..1ebaf6cc11 100644 --- a/test/test.gyp +++ b/test/test.gyp @@ -201,6 +201,8 @@ "./headless.cpp", "../common/headless_view.hpp", "../common/headless_view.cpp", + "../common/headless_display.hpp", + "../common/headless_display.cpp", "../common/platform_default.cpp", "./fixtures/fixture_request.cpp", "./fixtures/fixture_log.hpp", |