diff options
32 files changed, 884 insertions, 132 deletions
diff --git a/.travis.yml b/.travis.yml index 058d4e8c74..bc714f77c6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,11 +19,13 @@ matrix: env: global: - - secure: "bG4YYWMfl9API0MSRgmOaJrlGLv06tRg9KJNawBieZvBJbITPpxVGJZT3/l/SEJ+Rl15e2dRex4k+MGQlmT2SqPQxTEYWv1qxNigKPPcla7IWeNmWWqW8uVvFjdglojgBOK2k/xErVQtA4zDfi3mwSXH4DKwquXWsoEKmX2SV7M=" - - secure: "Cbvap9ubVKgjPe3hUhI6JGeDZzBXHpOG9RaYKh+SdoIPhKnlJiNOYm1egomi+e4uqJInlFKuVHTw7Ng9Cun6Zm0jIxpkSchv1GpsR7hmB3UGnGed19Dw8121FwuUaktN+4YnbVlsyd+u8EHD3+h58t4eELrLrZolM4rS7DL6caA=" + - secure: "HPlPuLk531F7bCyADMcxQ+I1CfGhJlhX9A3q3auNCdLsjqaZbktaCIGuq4R1IPZz0UTtO8RfrTfVaoktyZdq2QyU3iD8oj6BeZ/Jypr4TCBJxI5IeyU3gyPMFHqUYIJuHL9k40Xu7Dhf4HT123zr8KoeVqCHauZP1/TumA0nezg=" + - secure: "R/+uOeccl7GqjJbknailAV7VtjAtGwHHM9xToVnhfPVUXp9fzJs+oNk3IWcs3Eg4sw4WsYxXRfbCQG9bA3MnPlTOsxETGRioO9CAuXap6mwcwNAzeWuq7ePFki2u01pJGQD5b6WFqBCdnAQ4o2SgHgGzhs0TJzYd9tONQxSmkCI=" - secure: "RiBIBfVhhaMjU5ksuwJO3shdvG9FpinBjdSv4co9jg9171SR8edNriedHjVKSIeBhSGNmZmX+twS3dJS/By6tl/LKh9sTynA+ZAYYljkE7jn881B/gMrlYvdAA6og5KvkhV1/0iJWlhuZrMTkhpDR200iLgg3EWBhWjltzmDW/I=" + - AWS_S3_BUCKET: 'node-mapbox-gl-native' before_install: +- source ./scripts/travis_helper.sh - source ./scripts/flags.sh - (git clone https://github.com/mapbox/mason.git ~/.mason ; sudo ln -s ~/.mason/mason /usr/local/bin/mason) - ./scripts/travis_before_install.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..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); } diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp index bea82e6b4c..ab9775a8c9 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(); diff --git a/include/mbgl/map/view.hpp b/include/mbgl/map/view.hpp index bbdcd97c79..b567cd424f 100644 --- a/include/mbgl/map/view.hpp +++ b/include/mbgl/map/view.hpp @@ -35,11 +35,9 @@ public: // renderer setup since the render thread doesn't switch the contexts. virtual void make_active() = 0; - // Returns the base framebuffer object, if any, and 0 if using the system - // provided framebuffer. - virtual unsigned int root_fbo() { - return 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; virtual void notify() = 0; 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/ios/mapbox-gl-cocoa b/ios/mapbox-gl-cocoa -Subproject bc2c5fe6974fa99ea4816e9e793757cc1706659 +Subproject dd4f4cac252e0e9af5fbf73980370fc1528079a diff --git a/scripts/flags.sh b/scripts/flags.sh index b1de8f3c71..2b4624aa1c 100755 --- a/scripts/flags.sh +++ b/scripts/flags.sh @@ -2,6 +2,7 @@ if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then export DISPLAY=:99.0 + sh -e /etc/init.d/xvfb start # use g++ that supports c++11 diff --git a/scripts/travis_before_install.sh b/scripts/travis_before_install.sh index 59bd15d3ba..d445ae8174 100755 --- a/scripts/travis_before_install.sh +++ b/scripts/travis_before_install.sh @@ -11,21 +11,34 @@ if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then sudo add-apt-repository --yes ppa:ubuntu-toolchain-r/test fi sudo add-apt-repository --yes ppa:boost-latest/ppa + + mapbox_time "apt_update" \ sudo apt-get update -y + + mapbox_time "install_gcc" \ sudo apt-get -y install gcc-4.8 g++-4.8 + + mapbox_time "install_build_tools" \ sudo apt-get -y install git build-essential zlib1g-dev automake \ libtool xutils-dev make cmake pkg-config python-pip \ libboost1.55-dev libboost-regex1.55-dev libcurl4-openssl-dev \ libpng-dev libsqlite3-dev + + apbox_time "install_opengl" \ sudo apt-get -y install libxi-dev libglu1-mesa-dev x11proto-randr-dev \ x11proto-xext-dev libxrandr-dev \ x11proto-xf86vidmode-dev libxxf86vm-dev \ libxcursor-dev libxinerama-dev + + mapbox_time "install_awscli" \ sudo pip install awscli elif [[ ${TRAVIS_OS_NAME} == "osx" ]]; then # # install OS X dependencies # + mapbox_time "install_build_tools" \ brew install autoconf automake libtool makedepend cmake pkg-config node git boost + + mapbox_time "install_awscli" \ sudo pip install awscli fi diff --git a/scripts/travis_helper.sh b/scripts/travis_helper.sh new file mode 100755 index 0000000000..040ef29443 --- /dev/null +++ b/scripts/travis_helper.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +function mapbox_time_start { + local name=$1 + mapbox_timer_name=$name + + travis_fold start $name + + mapbox_timer_id=$(printf %08x $(( RANDOM * RANDOM ))) + eval "mapbox_start_time_$mapbox_timer_id=$(travis_nanoseconds)" + echo -en "travis_time:start:$mapbox_timer_id\n" +} + +function mapbox_time_finish { + local name=${1:-$mapbox_timer_name} + local timer_id=${2:-$mapbox_timer_id} + local timer_start="mapbox_start_time_$timer_id" + eval local start_time=\${$timer_start} + local end_time=$(travis_nanoseconds) + local duration=$(($end_time-$start_time)) + echo -en "travis_time:end:$timer_id:start=$start_time,finish=$end_time,duration=$duration\n" + + travis_fold end $name +} + +function mapbox_time { + local name=$1 ; shift + mapbox_time_start $name + local timer_id=$mapbox_timer_id + echo "\$ $@" + $@ + mapbox_time_finish $name $timer_id +} + + +export ANSI_CLEAR +export -f travis_fold +export -f travis_nanoseconds +export -f mapbox_time +export -f mapbox_time_start +export -f mapbox_time_finish diff --git a/scripts/travis_install_test_suite.sh b/scripts/travis_install_test_suite.sh index eca24e719c..cb2422d70c 100755 --- a/scripts/travis_install_test_suite.sh +++ b/scripts/travis_install_test_suite.sh @@ -4,5 +4,6 @@ if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then # # we'll need the test suite on Linux # + mapbox_time "install_test_suite" \ npm install git+https://github.com/mapbox/mapbox-gl-test-suite.git fi diff --git a/scripts/travis_script.sh b/scripts/travis_script.sh index f1cad6b9d3..f306dbcb79 100755 --- a/scripts/travis_script.sh +++ b/scripts/travis_script.sh @@ -7,26 +7,46 @@ if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then # # build & test Linux # + mapbox_time "compile_program" \ make linux -j4 BUILDTYPE=${BUILDTYPE} + + mapbox_time "compile_tests" \ make test -j4 BUILDTYPE=${BUILDTYPE} + + mapbox_time "run_tests" \ ./scripts/run_tests.sh + + mapbox_time_start "compare_results" (cd ./node_modules/mapbox-gl-test-suite/ && (./bin/compare_images.js || true)) + mapbox_time_finish if [ ! -z "${AWS_ACCESS_KEY_ID}" ] && [ ! -z "${AWS_SECRET_ACCESS_KEY}" ] ; then + mapbox_time_start "deploy_results" (cd ./node_modules/mapbox-gl-test-suite/ && ./bin/deploy_results.sh) + mapbox_time_finish fi elif [[ ${TRAVIS_OS_NAME} == "osx" ]]; then # # build OS X # + mapbox_time "create_osx_project" \ make xproj-cli + + mapbox_time "build_osx" \ xcodebuild -project ./build/macosx/mapboxgl-app.xcodeproj -jobs 4 + # # build iOS # git submodule init + + mapbox_time "load_submodules" \ git submodule update + + mapbox_time "create_ios_project" \ make iproj-cli + + mapbox_time "build_ios" \ xcodebuild -project ./build/ios/mapbox-gl-cocoa/app/mapboxgl-app.xcodeproj -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO -jobs 4 fi diff --git a/src/map/map.cpp b/src/map/map.cpp index f255c3ff65..451385d3d5 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -163,7 +163,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; @@ -206,10 +205,13 @@ void Map::cleanup(uv_async_t *async) { #endif 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) { @@ -271,8 +273,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) { @@ -605,8 +607,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); @@ -651,6 +651,8 @@ void Map::prepare() { } void Map::render() { + view.make_active(); + #if defined(DEBUG) std::vector<std::string> debug; #endif @@ -685,6 +687,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/storage/file_request_baton.cpp b/src/storage/file_request_baton.cpp index 64c6c13b12..e54b743c43 100644 --- a/src/storage/file_request_baton.cpp +++ b/src/storage/file_request_baton.cpp @@ -83,6 +83,7 @@ void FileRequestBaton::file_stated(uv_fs_t *req) { if (stat->st_size > std::numeric_limits<int>::max()) { // File is too large for us to open this way because uv_buf's only support unsigned // ints as maximum size. + const uv_err_t error = {UV_EFBIG, 0}; if (ptr->request) { ptr->request->response = std::unique_ptr<Response>(new Response); ptr->request->response->code = UV_EFBIG; @@ -97,8 +98,7 @@ void FileRequestBaton::file_stated(uv_fs_t *req) { uv_fs_req_cleanup(req); uv_fs_close(req->loop, req, ptr->fd, file_closed); } else { - const unsigned int size = - (unsigned int)(stat->st_size); + const unsigned int size = (unsigned int)(stat->st_size); ptr->body.resize(size); ptr->buffer = uv_buf_init(const_cast<char *>(ptr->body.data()), size); uv_fs_req_cleanup(req); 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-messenger.c b/src/util/uv-messenger.c index 82df1cae75..bfa1565768 100644 --- a/src/util/uv-messenger.c +++ b/src/util/uv-messenger.c @@ -8,7 +8,6 @@ typedef struct { void *queue[2]; } uv__messenger_item_t; - #if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-parameter" diff --git a/src/util/uv.cpp b/src/util/uv.cpp index 03a885d308..1e51d3dbb6 100644 --- a/src/util/uv.cpp +++ b/src/util/uv.cpp @@ -15,7 +15,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 9d2971c73e..3dd8a356a8 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 3255cff062..02dd0165ec 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 1f63756e71..8bf1dffdfb 100644 --- a/test/test.gyp +++ b/test/test.gyp @@ -169,6 +169,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", diff --git a/travis/travis-resources.template b/travis/travis-resources.template index afe0478e33..e1b0cff8ca 100644 --- a/travis/travis-resources.template +++ b/travis/travis-resources.template @@ -1,7 +1,7 @@ { "AWSTemplateFormatVersion": "2010-09-09", - "Description": "mapbox-gl-native travis resources", + "Description": "node-mapbox-gl-native travis resources", "Resources": { "BuildUser": { "Type": "AWS::IAM::User", @@ -17,7 +17,7 @@ ], "Effect": "Allow", "Resource": [ - "arn:aws:s3:::mapbox-gl-testing" + "arn:aws:s3:::node-mapbox-gl-native" ] } ] @@ -37,6 +37,42 @@ ], "Effect": "Allow", "Resource": [ + "arn:aws:s3:::node-mapbox-gl-native/*" + ] + } + ] + } + }, + { + "PolicyName": "list-testing", + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:ListBucket" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:s3:::mapbox-gl-testing" + ] + } + ] + } + }, + { + "PolicyName": "build-testing", + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:DeleteObject", + "s3:GetObject", + "s3:GetObjectAcl", + "s3:PutObject", + "s3:PutObjectAcl" + ], + "Effect": "Allow", + "Resource": [ "arn:aws:s3:::mapbox-gl-testing/*" ] } |