Merge branch 'libuv-0.10-headless-display' into mason
Conflicts: common/http_request_baton_curl.cpp include/mbgl/map/map.hpp include/mbgl/util/uv_detail.hpp scripts/travis_before_install.sh setup-libraries.sh src/map/map.cpp src/storage/file_request_baton.cpp src/storage/http_request.cpp src/util/uv-messenger.c
This commit is contained in:
@@ -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
|
||||
|
||||
383
common/curl_request.cpp
Normal file
383
common/curl_request.cpp
Normal file
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
2
common/glx.h
Normal file
2
common/glx.h
Normal file
@@ -0,0 +1,2 @@
|
||||
#include <GL/glx.h>
|
||||
#undef None
|
||||
74
common/headless_display.cpp
Normal file
74
common/headless_display.cpp
Normal file
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
25
common/headless_display.hpp
Normal file
25
common/headless_display.hpp
Normal file
@@ -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
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
gl_context = glXCreateContext(x_display, x_info, 0, GL_TRUE);
|
||||
if (gl_context == nullptr) {
|
||||
throw std::runtime_error("Error creating GL context object");
|
||||
#ifdef GLX_ARB_create_context
|
||||
if (glXCreateContextAttribsARB == nullptr) {
|
||||
glXCreateContextAttribsARB = (PFNGLXCREATECONTEXTATTRIBSARBPROC)glXGetProcAddressARB((const GLubyte *)"glXCreateContextAttribsARB");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
x_display = display_->x_display;
|
||||
fb_configs = display_->fb_configs;
|
||||
|
||||
#ifdef GLX_ARB_create_context
|
||||
if (glXCreateContextAttribsARB) {
|
||||
// Try to create a core profile context.
|
||||
gl_context = createCoreProfile(x_display, fb_configs[0]);
|
||||
}
|
||||
#endif
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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::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 (!glXMakeContextCurrent(x_display, 0, 0, nullptr)) {
|
||||
throw std::runtime_error("Removing OpenGL context failed\n");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void HeadlessView::swap() {}
|
||||
|
||||
unsigned int HeadlessView::root_fbo() {
|
||||
#if MBGL_USE_CGL
|
||||
return fbo;
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Submodule ios/mapbox-gl-cocoa updated: bc2c5fe697...dd4f4cac25
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
41
scripts/travis_helper.sh
Executable file
41
scripts/travis_helper.sh
Executable file
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
1
test/fixtures/fixture_request.cpp
vendored
1
test/fixtures/fixture_request.cpp
vendored
@@ -4,6 +4,7 @@
|
||||
#include <mbgl/util/url.hpp>
|
||||
#include <mbgl/util/std.hpp>
|
||||
#include <mbgl/platform/log.hpp>
|
||||
#include <iostream>
|
||||
|
||||
#include <uv.h>
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -25,6 +25,42 @@
|
||||
},
|
||||
{
|
||||
"PolicyName": "build",
|
||||
"PolicyDocument": {
|
||||
"Statement": [
|
||||
{
|
||||
"Action": [
|
||||
"s3:DeleteObject",
|
||||
"s3:GetObject",
|
||||
"s3:GetObjectAcl",
|
||||
"s3:PutObject",
|
||||
"s3:PutObjectAcl"
|
||||
],
|
||||
"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": [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user