diff options
-rw-r--r-- | Makefile | 15 | ||||
-rw-r--r-- | bin/render.cpp | 115 | ||||
-rw-r--r-- | bin/render.gyp | 53 | ||||
-rw-r--r-- | include/mbgl/map/map.hpp | 9 | ||||
-rw-r--r-- | platform/default/headless_view.cpp | 120 | ||||
-rw-r--r-- | src/mbgl/map/map.cpp | 29 |
6 files changed, 236 insertions, 105 deletions
@@ -53,6 +53,10 @@ build/linux/Makefile: linux/mapboxgl-app.gyp config.gypi build/macosx/Makefile: macosx/mapboxgl-app.gyp config.gypi deps/run_gyp macosx/mapboxgl-app.gyp -Iconfig.gypi -Dplatform=osx --depth=. -Goutput_dir=.. --generator-output=./build/macosx -f make +.PHONY: build/render/Makefile +build/render/Makefile: bin/render.gyp config.gypi + deps/run_gyp bin/render.gyp -Iconfig.gypi -Dplatform=$(PLATFORM) --depth=. -Goutput_dir=.. --generator-output=./build/render -f make + .PHONY: build/test/test.xcodeproj build/test/test.xcodeproj: test/test.gyp config.gypi deps/run_gyp test/test.gyp -Iconfig.gypi -Dplatform=$(PLATFORM) --depth=. -Goutput_dir=.. --generator-output=./build -f xcode @@ -69,6 +73,10 @@ build/ios/mapbox-gl-cocoa/app/mapboxgl-app.xcodeproj: ios/mapbox-gl-cocoa/app/ma build/linux/mapboxgl-app.xcodeproj: linux/mapboxgl-app.gyp config.gypi deps/run_gyp linux/mapboxgl-app.gyp -Iconfig.gypi -Dplatform=linux --depth=. --generator-output=./build -f xcode +.PHONY: build/bin/render.xcodeproj +build/bin/render.xcodeproj: bin/render.gyp config.gypi + deps/run_gyp bin/render.gyp -Iconfig.gypi -Dplatform=$(PLATFORM) --depth=. --generator-output=./build -f xcode + ##### Test cases ############################################################### test: build/test/Makefile @@ -101,6 +109,10 @@ osx: build/macosx/Makefile run-osx: osx build/$(BUILDTYPE)/Mapbox\ GL.app/Contents/MacOS/MAPBOX\ GL +# Builds the CLI render app +render: build/render/Makefile + $(MAKE) -C build/render BUILDTYPE=$(BUILDTYPE) V=$(V) mbgl-render + ##### Xcode projects ########################################################### clear_xcode_cache: @@ -120,6 +132,9 @@ xproj: build/macosx/mapboxgl-app.xcodeproj iproj: build/ios/mapbox-gl-cocoa/app/mapboxgl-app.xcodeproj open ./build/ios/mapbox-gl-cocoa/app/mapboxgl-app.xcodeproj +rproj: build/bin/render.xcodeproj + open ./build/bin/render.xcodeproj + # build Linux project for Xcode (Runs on Mac OS X too, but without platform-specific code) lproj: build/linux/mapboxgl-app.xcodeproj open ./build/linux/mapboxgl-app.xcodeproj diff --git a/bin/render.cpp b/bin/render.cpp new file mode 100644 index 0000000000..feeb4fe3f4 --- /dev/null +++ b/bin/render.cpp @@ -0,0 +1,115 @@ +#include <mbgl/map/map.hpp> +#include <mbgl/util/image.hpp> +#include <mbgl/util/std.hpp> +#include <mbgl/util/io.hpp> + +#include <rapidjson/document.h> +#include <rapidjson/writer.h> +#include <rapidjson/stringbuffer.h> + +#include <mbgl/platform/default/headless_view.hpp> +#include <mbgl/platform/default/headless_display.hpp> +#include <mbgl/storage/caching_http_file_source.hpp> + +#if __APPLE__ +#include <mbgl/platform/darwin/log_nslog.hpp> +#else +#include <mbgl/platform/default/log_stderr.hpp> +#endif + +#include <boost/program_options.hpp> +namespace po = boost::program_options; + +#include <cassert> +#include <cstdlib> +#include <iostream> + +int main(int argc, char *argv[]) { + + std::string style_path; + double lat = 0, lon = 0; + double zoom = 0; + double bearing = 0; + + int width = 256; + int height = 256; + double pixelRatio = 1.0; + std::string output = "out.png"; + std::string cache = "cache.sqlite"; + std::vector<std::string> classes; + std::string token; + + po::options_description desc("Allowed options"); + desc.add_options() + ("style,s", po::value(&style_path)->required()->value_name("json"), "Map stylesheet") + ("lon,x", po::value(&lon)->value_name("degrees")->default_value(lon), "Longitude") + ("lat,y", po::value(&lat)->value_name("degrees")->default_value(lat), "Latitude in degrees") + ("zoom,z", po::value(&zoom)->value_name("number")->default_value(zoom), "Zoom level") + ("bearing,b", po::value(&bearing)->value_name("degrees")->default_value(bearing), "Bearing") + ("width,w", po::value(&width)->value_name("pixels")->default_value(width), "Image width") + ("height,h", po::value(&height)->value_name("pixels")->default_value(height), "Image height") + ("class,c", po::value(&classes)->value_name("name"), "Class name") + ("token,t", po::value(&token)->value_name("key")->default_value(token), "Mapbox access token") + ("output,o", po::value(&output)->value_name("file")->default_value(output), "Output file name") + ("cache,d", po::value(&cache)->value_name("file")->default_value(cache), "Cache database file name") + ; + + try { + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + } catch(std::exception& e) { + std::cout << "Error: " << e.what() << std::endl << desc; + exit(1); + } + + std::string style = mbgl::util::read_file(style_path); + + using namespace mbgl; + + +#if __APPLE__ + Log::Set<NSLogBackend>(); +#else + Log::Set<StderrLogBackend>(); +#endif + + CachingHTTPFileSource fileSource(cache); + + // Try to load the token from the environment. + if (!token.size()) { + const char *token_ptr = getenv("MAPBOX_ACCESS_TOKEN"); + if (token_ptr) { + token = token_ptr; + } + } + + // Set access token if present + if (token.size()) { + fileSource.setAccessToken(std::string(token)); + } + + HeadlessView view; + Map map(view, fileSource); + + map.setStyleJSON(style, "."); + map.setAppliedClasses(classes); + + view.resize(width, height, pixelRatio); + map.resize(width, height, pixelRatio); + map.setLonLatZoom(lon, lat, zoom); + map.setBearing(bearing); + + std::unique_ptr<uint32_t[]> pixels; + + // Run the loop. It will terminate when we don't have any further listeners. + map.run(); + + // Get the data from the GPU. + pixels = view.readPixels(); + + const unsigned int w = width * pixelRatio; + const unsigned int h = height * pixelRatio; + const std::string image = util::compress_png(w, h, pixels.get()); + util::write_file(output, image); +} diff --git a/bin/render.gyp b/bin/render.gyp new file mode 100644 index 0000000000..e9a2594236 --- /dev/null +++ b/bin/render.gyp @@ -0,0 +1,53 @@ +{ + 'includes': [ + '../gyp/common.gypi', + ], + 'targets': [ + { + 'target_name': 'mbgl-render', + 'product_name': 'mbgl-render', + 'type': 'executable', + 'sources': [ + './render.cpp', + ], + 'variables' : { + 'cflags': [ + '<@(uv_cflags)', + '<@(png_cflags)', + '-I<(boost_root)/include', + ], + 'ldflags': [ + '<@(glfw3_ldflags)', + '<@(uv_ldflags)', + '<@(sqlite3_ldflags)', + '<@(curl_ldflags)', + '<@(png_ldflags)', + '<@(uv_static_libs)', + '-L<(boost_root)/lib', + '-lboost_program_options' + ], + }, + 'conditions': [ + # add libuv include path and OpenGL libs + ['OS == "mac"', + { + 'xcode_settings': { + 'OTHER_CPLUSPLUSFLAGS': ['<@(cflags)'], + 'OTHER_LDFLAGS': ['<@(ldflags)'], + }, + }, + { + 'cflags': ['<@(cflags)'], + 'libraries': ['<@(ldflags)'], + }], + ], + 'include_dirs': [ '../src' ], + 'dependencies': [ + '../mapboxgl.gyp:mbgl-standalone', + '../mapboxgl.gyp:mbgl-headless', + '../mapboxgl.gyp:mbgl-<(platform)', + '../mapboxgl.gyp:copy_certificate_bundle', + ], + }, + ], +} diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp index 245aaf9ea7..45846170c2 100644 --- a/include/mbgl/map/map.hpp +++ b/include/mbgl/map/map.hpp @@ -136,7 +136,14 @@ private: // Unconditionally performs a render with the current map state. void render(); - bool async = false; + enum class Mode : uint8_t { + None, // we're not doing any processing + Continuous, // continually updating map + Static, // a once-off static image. + }; + + Mode mode = Mode::None; + std::unique_ptr<uv::loop> loop; std::unique_ptr<uv::worker> workers; std::thread thread; diff --git a/platform/default/headless_view.cpp b/platform/default/headless_view.cpp index b0fa60fc12..4053af226d 100644 --- a/platform/default/headless_view.cpp +++ b/platform/default/headless_view.cpp @@ -1,5 +1,6 @@ #include <mbgl/platform/default/headless_view.hpp> #include <mbgl/platform/default/headless_display.hpp> +#include <mbgl/platform/log.hpp> #include <mbgl/util/std.hpp> @@ -9,12 +10,6 @@ #include <cstring> #include <cassert> -#if MBGL_USE_GLX -#ifdef GLX_ARB_create_context -static PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB = nullptr; -#endif -#endif - #ifdef MBGL_USE_CGL #include <CoreFoundation/CoreFoundation.h> @@ -52,88 +47,39 @@ HeadlessView::HeadlessView(std::shared_ptr<HeadlessDisplay> display) void HeadlessView::loadExtensions() { make_active(); - const std::string extensions = (char *)MBGL_CHECK_ERROR(glGetString(GL_EXTENSIONS)); + const char *extension_ptr = (char *)MBGL_CHECK_ERROR(glGetString(GL_EXTENSIONS)); + + if (extension_ptr) { + const std::string extensions = extension_ptr; #ifdef MBGL_USE_CGL - if (extensions.find("GL_APPLE_vertex_array_object") != std::string::npos) { - gl::BindVertexArray = (gl::PFNGLBINDVERTEXARRAYPROC)CGLGetProcAddress("glBindVertexArrayAPPLE"); - gl::DeleteVertexArrays = (gl::PFNGLDELETEVERTEXARRAYSPROC)CGLGetProcAddress("glDeleteVertexArraysAPPLE"); - gl::GenVertexArrays = (gl::PFNGLGENVERTEXARRAYSPROC)CGLGetProcAddress("glGenVertexArraysAPPLE"); - gl::IsVertexArray = (gl::PFNGLISVERTEXARRAYPROC)CGLGetProcAddress("glIsVertexArrayAPPLE"); - assert(gl::BindVertexArray != nullptr); - assert(gl::DeleteVertexArrays != nullptr); - assert(gl::GenVertexArrays != nullptr); - assert(gl::IsVertexArray != nullptr); - } + if (extensions.find("GL_APPLE_vertex_array_object") != std::string::npos) { + gl::BindVertexArray = (gl::PFNGLBINDVERTEXARRAYPROC)CGLGetProcAddress("glBindVertexArrayAPPLE"); + gl::DeleteVertexArrays = (gl::PFNGLDELETEVERTEXARRAYSPROC)CGLGetProcAddress("glDeleteVertexArraysAPPLE"); + gl::GenVertexArrays = (gl::PFNGLGENVERTEXARRAYSPROC)CGLGetProcAddress("glGenVertexArraysAPPLE"); + gl::IsVertexArray = (gl::PFNGLISVERTEXARRAYPROC)CGLGetProcAddress("glIsVertexArrayAPPLE"); + assert(gl::BindVertexArray != nullptr); + assert(gl::DeleteVertexArrays != nullptr); + assert(gl::GenVertexArrays != nullptr); + assert(gl::IsVertexArray != nullptr); + } #endif #ifdef MBGL_USE_GLX - if (extensions.find("GL_ARB_vertex_array_object") != std::string::npos) { - gl::BindVertexArray = (gl::PFNGLBINDVERTEXARRAYPROC)glXGetProcAddress((const GLubyte *)"glBindVertexArray"); - gl::DeleteVertexArrays = (gl::PFNGLDELETEVERTEXARRAYSPROC)glXGetProcAddress((const GLubyte *)"glDeleteVertexArrays"); - gl::GenVertexArrays = (gl::PFNGLGENVERTEXARRAYSPROC)glXGetProcAddress((const GLubyte *)"glGenVertexArrays"); - gl::IsVertexArray = (gl::PFNGLISVERTEXARRAYPROC)glXGetProcAddress((const GLubyte *)"glIsVertexArray"); - assert(gl::BindVertexArray != nullptr); - assert(gl::DeleteVertexArrays != nullptr); - assert(gl::GenVertexArrays != nullptr); - assert(gl::IsVertexArray != nullptr); - } -#endif - - make_inactive(); -} - - -#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 coreProfileVersions[] = { - {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 contextCreationFailed = 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 (*previousErrorHandler)(Display *, XErrorEvent *) = XSetErrorHandler([](Display *, XErrorEvent *) { - contextCreationFailed = true; - return 0; - }); - - // Try to create core profiles from the highest known version on down. - for (int i = 0; !ctx && coreProfileVersions[i].major; i++) { - contextCreationFailed = false; - const int contextFlags[] = { - GLX_CONTEXT_MAJOR_VERSION_ARB, coreProfileVersions[i].major, - GLX_CONTEXT_MINOR_VERSION_ARB, coreProfileVersions[i].minor, - 0 - }; - ctx = glXCreateContextAttribsARB(dpy, fbconfig, 0, True, contextFlags); - if (contextCreationFailed) { - ctx = 0; + if (extensions.find("GL_ARB_vertex_array_object") != std::string::npos) { + gl::BindVertexArray = (gl::PFNGLBINDVERTEXARRAYPROC)glXGetProcAddress((const GLubyte *)"glBindVertexArray"); + gl::DeleteVertexArrays = (gl::PFNGLDELETEVERTEXARRAYSPROC)glXGetProcAddress((const GLubyte *)"glDeleteVertexArrays"); + gl::GenVertexArrays = (gl::PFNGLGENVERTEXARRAYSPROC)glXGetProcAddress((const GLubyte *)"glGenVertexArrays"); + gl::IsVertexArray = (gl::PFNGLISVERTEXARRAYPROC)glXGetProcAddress((const GLubyte *)"glIsVertexArray"); + assert(gl::BindVertexArray != nullptr); + assert(gl::DeleteVertexArrays != nullptr); + assert(gl::GenVertexArrays != nullptr); + assert(gl::IsVertexArray != nullptr); } +#endif } - // Restore the old error handler. - XSetErrorHandler(previousErrorHandler); - return ctx; + make_inactive(); } -#endif -#endif void HeadlessView::createContext() { #if MBGL_USE_CGL @@ -149,27 +95,15 @@ void HeadlessView::createContext() { #endif #if MBGL_USE_GLX -#ifdef GLX_ARB_create_context - if (glXCreateContextAttribsARB == nullptr) { - glXCreateContextAttribsARB = (PFNGLXCREATECONTEXTATTRIBSARBPROC)glXGetProcAddressARB((const GLubyte *)"glXCreateContextAttribsARB"); - } -#endif - xDisplay = display_->xDisplay; fbConfigs = display_->fbConfigs; -#ifdef GLX_ARB_create_context - if (glXCreateContextAttribsARB) { - // Try to create a core profile context. - glContext = createCoreProfile(xDisplay, fbConfigs[0]); - } -#endif - if (!glContext) { // Try to create a legacy context glContext = glXCreateNewContext(xDisplay, fbConfigs[0], GLX_RGBA_TYPE, 0, True); if (glContext) { if (!glXIsDirect(xDisplay, glContext)) { + mbgl::Log::Error(mbgl::Event::OpenGL, "Failed to create direct OpenGL Legacy context"); glXDestroyContext(xDisplay, glContext); glContext = 0; } diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp index f46e0f558b..ea4115f05c 100644 --- a/src/mbgl/map/map.cpp +++ b/src/mbgl/map/map.cpp @@ -91,6 +91,7 @@ Map::Map(View& view_, FileSource& fileSource_) view(view_), #ifndef NDEBUG mainThread(std::this_thread::get_id()), + mapThread(mainThread), #endif transform(view_), fileSource(fileSource_), @@ -108,7 +109,7 @@ Map::Map(View& view_, FileSource& fileSource_) } Map::~Map() { - if (async) { + if (mode == Mode::Continuous) { stop(); } @@ -132,11 +133,11 @@ uv::worker &Map::getWorker() { void Map::start() { assert(std::this_thread::get_id() == mainThread); - assert(!async); + assert(mode == Mode::None); // When starting map rendering in another thread, we perform async/continuously // updated rendering. Only in these cases, we attach the async handlers. - async = true; + mode = Mode::Continuous; // Reset the flag. isStopped = false; @@ -200,7 +201,7 @@ void Map::start() { void Map::stop(std::function<void ()> callback) { assert(std::this_thread::get_id() == mainThread); assert(mainThread != mapThread); - assert(async); + assert(mode == Mode::Continuous); asyncTerminate->send(); @@ -220,15 +221,16 @@ void Map::stop(std::function<void ()> callback) { // already finished executing. thread.join(); - async = false; + mode = Mode::None; } void Map::run() { + if (mode == Mode::None) { #ifndef NDEBUG - if (!async) { mapThread = mainThread; - } #endif + mode = Mode::Static; + } assert(std::this_thread::get_id() == mapThread); setup(); @@ -240,18 +242,23 @@ void Map::run() { // If the map rendering wasn't started asynchronously, we perform one render // *after* all events have been processed. - if (!async) { + if (mode == Mode::Static) { render(); #ifndef NDEBUG mapThread = std::thread::id(); #endif + mode = Mode::None; + fileSource.clearLoop(); } } void Map::rerender() { - // We only send render events if we want to continuously update the map - // (== async rendering). - if (async) { + if (mode == Mode::Static) { + prepare(); + } else if (mode == Mode::Continuous) { + // We only send render events if we want to continuously update the map + // (== async rendering). + assert(asyncRender); asyncRender->send(); } } |