Merge branch 'master' of github.com:mapbox/mapbox-gl-native
This commit is contained in:
@@ -283,13 +283,6 @@ void JNICALL nativeResume(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) {
|
||||
nativeMapView->resume();
|
||||
}
|
||||
|
||||
void JNICALL nativeRun(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) {
|
||||
mbgl::Log::Debug(mbgl::Event::JNI, "nativeRun");
|
||||
assert(nativeMapViewPtr != 0);
|
||||
NativeMapView *nativeMapView = reinterpret_cast<NativeMapView *>(nativeMapViewPtr);
|
||||
nativeMapView->getMap().run();
|
||||
}
|
||||
|
||||
void JNICALL nativeUpdate(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) {
|
||||
mbgl::Log::Debug(mbgl::Event::JNI, "nativeUpdate");
|
||||
assert(nativeMapViewPtr != 0);
|
||||
@@ -936,7 +929,7 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
|
||||
|
||||
// NOTE: if you get java.lang.UnsatisfiedLinkError you likely forgot to set the size of the
|
||||
// array correctly (too large)
|
||||
std::array<JNINativeMethod, 63> methods = {{ // Can remove the extra brace in C++14
|
||||
std::array<JNINativeMethod, 62> methods = {{ // Can remove the extra brace in C++14
|
||||
{"nativeCreate", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)J",
|
||||
reinterpret_cast<void *>(&nativeCreate)},
|
||||
{"nativeDestroy", "(J)V", reinterpret_cast<void *>(&nativeDestroy)},
|
||||
@@ -951,7 +944,6 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
|
||||
{"nativeStop", "(J)V", reinterpret_cast<void *>(&nativeStop)},
|
||||
{"nativePause", "(J)V", reinterpret_cast<void *>(&nativePause)},
|
||||
{"nativeResume", "(J)V", reinterpret_cast<void *>(&nativeResume)},
|
||||
{"nativeRun", "(J)V", reinterpret_cast<void *>(&nativeRun)},
|
||||
{"nativeUpdate", "(J)V", reinterpret_cast<void *>(&nativeUpdate)},
|
||||
{"nativeTerminate", "(J)V", reinterpret_cast<void *>(&nativeTerminate)},
|
||||
{"nativeResize", "(JIIFII)V",
|
||||
|
||||
@@ -89,10 +89,6 @@ class NativeMapView {
|
||||
nativeResume(mNativeMapViewPtr);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
nativeRun(mNativeMapViewPtr);
|
||||
}
|
||||
|
||||
public void update() {
|
||||
nativeUpdate(mNativeMapViewPtr);
|
||||
}
|
||||
@@ -398,8 +394,6 @@ class NativeMapView {
|
||||
|
||||
private native void nativeResume(long nativeMapViewPtr);
|
||||
|
||||
private native void nativeRun(long nativeMapViewPtr);
|
||||
|
||||
private native void nativeUpdate(long nativeMapViewPtr);
|
||||
|
||||
private native void nativeTerminate(long nativeMapViewPtr);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <mbgl/map/map.hpp>
|
||||
#include <mbgl/map/still_image.hpp>
|
||||
#include <mbgl/util/image.hpp>
|
||||
#include <mbgl/util/std.hpp>
|
||||
#include <mbgl/util/io.hpp>
|
||||
@@ -19,12 +20,13 @@
|
||||
|
||||
namespace po = boost::program_options;
|
||||
|
||||
#include <uv.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
|
||||
std::string style_path;
|
||||
double lat = 0, lon = 0;
|
||||
double zoom = 0;
|
||||
@@ -33,7 +35,7 @@ int main(int argc, char *argv[]) {
|
||||
int width = 512;
|
||||
int height = 512;
|
||||
double pixelRatio = 1.0;
|
||||
std::string output = "out.png";
|
||||
static std::string output = "out.png";
|
||||
std::string cache_file = "cache.sqlite";
|
||||
std::vector<std::string> classes;
|
||||
std::string token;
|
||||
@@ -81,6 +83,8 @@ int main(int argc, char *argv[]) {
|
||||
HeadlessView view;
|
||||
Map map(view, fileSource);
|
||||
|
||||
map.start(Map::Mode::Static);
|
||||
|
||||
// Set access token if present
|
||||
if (token.size()) {
|
||||
map.setAccessToken(std::string(token));
|
||||
@@ -93,14 +97,24 @@ int main(int argc, char *argv[]) {
|
||||
map.setLatLngZoom({ lat, lon }, zoom);
|
||||
map.setBearing(bearing);
|
||||
|
||||
// Run the loop. It will terminate when we don't have any further listeners.
|
||||
map.run();
|
||||
uv_async_t *async = new uv_async_t;
|
||||
uv_async_init(uv_default_loop(), async, [](uv_async_t *as, int) {
|
||||
std::unique_ptr<const StillImage> image(reinterpret_cast<const StillImage *>(as->data));
|
||||
uv_close(reinterpret_cast<uv_handle_t *>(as), [](uv_handle_t *handle) {
|
||||
delete reinterpret_cast<uv_async_t *>(handle);
|
||||
});
|
||||
|
||||
// Get the data from the GPU.
|
||||
auto pixels = view.readPixels();
|
||||
const std::string png = util::compress_png(image->width, image->height, image->pixels.get());
|
||||
util::write_file(output, png);
|
||||
});
|
||||
|
||||
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);
|
||||
map.renderStill([async](std::unique_ptr<const StillImage> image) {
|
||||
async->data = const_cast<StillImage *>(image.release());
|
||||
uv_async_send(async);
|
||||
});
|
||||
|
||||
// This loop will terminate once the async was fired.
|
||||
uv_run(uv_default_loop(), UV_RUN_DEFAULT);
|
||||
|
||||
map.stop();
|
||||
}
|
||||
|
||||
@@ -28,15 +28,18 @@
|
||||
'variables' : {
|
||||
'cflags_cc': [
|
||||
'<@(glfw3_cflags)',
|
||||
'<@(uv_cflags)',
|
||||
'<@(boost_cflags)',
|
||||
],
|
||||
'ldflags': [
|
||||
'<@(glfw3_ldflags)',
|
||||
'<@(uv_ldflags)',
|
||||
'<@(boost_ldflags)',
|
||||
'-lboost_program_options'
|
||||
],
|
||||
'libraries': [
|
||||
'<@(glfw3_static_libs)',
|
||||
'<@(uv_static_libs)',
|
||||
],
|
||||
},
|
||||
|
||||
|
||||
@@ -42,16 +42,24 @@ class EnvironmentScope;
|
||||
class AnnotationManager;
|
||||
class MapData;
|
||||
class Worker;
|
||||
class StillImage;
|
||||
|
||||
class Map : private util::noncopyable {
|
||||
friend class View;
|
||||
|
||||
public:
|
||||
enum class Mode : uint8_t {
|
||||
None, // we're not doing any processing
|
||||
Continuous, // continually updating map
|
||||
Static, // a once-off static image.
|
||||
};
|
||||
|
||||
explicit Map(View&, FileSource&);
|
||||
~Map();
|
||||
|
||||
// Start the map render thread. It is asynchronous.
|
||||
void start(bool startPaused = false);
|
||||
void start(bool startPaused = false, Mode mode = Mode::Continuous);
|
||||
inline void start(Mode renderMode) { start(false, renderMode); }
|
||||
|
||||
// Stop the map render thread. This call will block until the map rendering thread stopped.
|
||||
// The optional callback function will be invoked repeatedly until the map thread is stopped.
|
||||
@@ -65,10 +73,8 @@ public:
|
||||
// Resumes a paused render thread
|
||||
void resume();
|
||||
|
||||
// Runs the map event loop. ONLY run this function when you want to get render a single frame
|
||||
// with this map object. It will *not* spawn a separate thread and instead block until the
|
||||
// frame is completely rendered.
|
||||
void run();
|
||||
using StillImageCallback = std::function<void(std::unique_ptr<const StillImage>)>;
|
||||
void renderStill(StillImageCallback callback);
|
||||
|
||||
// Triggers a synchronous or asynchronous render.
|
||||
void renderSync();
|
||||
@@ -167,6 +173,11 @@ public:
|
||||
inline AnnotationManager& getAnnotationManager() const { return *annotationManager; }
|
||||
|
||||
private:
|
||||
// Runs the map event loop. ONLY run this function when you want to get render a single frame
|
||||
// with this map object. It will *not* spawn a separate thread and instead block until the
|
||||
// frame is completely rendered.
|
||||
void run();
|
||||
|
||||
// This may only be called by the View object.
|
||||
void resize(uint16_t width, uint16_t height, float ratio = 1);
|
||||
void resize(uint16_t width, uint16_t height, float ratio, uint16_t fbWidth, uint16_t fbHeight);
|
||||
@@ -203,12 +214,6 @@ private:
|
||||
|
||||
size_t sourceCacheSize;
|
||||
|
||||
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;
|
||||
|
||||
const std::unique_ptr<Environment> env;
|
||||
@@ -260,6 +265,7 @@ private:
|
||||
|
||||
std::mutex mutexTask;
|
||||
std::queue<std::function<void()>> tasks;
|
||||
StillImageCallback callback;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
21
include/mbgl/map/still_image.hpp
Normal file
21
include/mbgl/map/still_image.hpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#ifndef MBGL_MAP_STILL_IMAGE
|
||||
#define MBGL_MAP_STILL_IMAGE
|
||||
|
||||
#include <mbgl/util/noncopyable.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
class StillImage : util::noncopyable {
|
||||
public:
|
||||
uint16_t width = 0;
|
||||
uint16_t height = 0;
|
||||
using Pixel = uint32_t;
|
||||
std::unique_ptr<Pixel[]> pixels;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,6 +1,8 @@
|
||||
#ifndef MBGL_MAP_UPDATE
|
||||
#define MBGL_MAP_UPDATE
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
using UpdateType = uint32_t;
|
||||
@@ -12,6 +14,7 @@ enum class Update : UpdateType {
|
||||
DefaultTransitionDuration = 1 << 2,
|
||||
Classes = 1 << 3,
|
||||
Zoom = 1 << 4,
|
||||
RenderStill = 1 << 5,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -3,9 +3,12 @@
|
||||
|
||||
#include <mbgl/util/chrono.hpp>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
class Map;
|
||||
class StillImage;
|
||||
|
||||
enum MapChange : uint8_t {
|
||||
MapChangeRegionWillChange = 0,
|
||||
@@ -40,6 +43,14 @@ public:
|
||||
// (i.e. map->renderSync() or map->renderAsync() must be called as a result of this)
|
||||
virtual void invalidate() = 0;
|
||||
|
||||
// Called from the render (=GL) thread. Signals that the contents of the contents
|
||||
// may be discarded. The default is a no-op.
|
||||
virtual void discard();
|
||||
|
||||
// Reads the pixel data from the current framebuffer. If your View implementation
|
||||
// doesn't support reading from the framebuffer, return a null pointer.
|
||||
virtual std::unique_ptr<StillImage> readStillImage();
|
||||
|
||||
// Notifies a watcher of map x/y/scale/rotation changes.
|
||||
// Must only be called from the same thread that caused the change.
|
||||
// Must not be called from the render thread.
|
||||
|
||||
@@ -24,32 +24,47 @@ class HeadlessDisplay;
|
||||
|
||||
class HeadlessView : public View {
|
||||
public:
|
||||
HeadlessView();
|
||||
HeadlessView(std::shared_ptr<HeadlessDisplay> display);
|
||||
HeadlessView(uint16_t width = 256, uint16_t height = 256, float pixelRatio = 1);
|
||||
HeadlessView(std::shared_ptr<HeadlessDisplay> display, uint16_t width = 256, uint16_t height = 256, float pixelRatio = 1);
|
||||
~HeadlessView();
|
||||
|
||||
void createContext();
|
||||
void loadExtensions();
|
||||
|
||||
void resize(uint16_t width, uint16_t height, float pixelRatio);
|
||||
std::unique_ptr<uint32_t[]> readPixels();
|
||||
|
||||
void activate() override;
|
||||
void deactivate() override;
|
||||
void notify() override;
|
||||
void invalidate() override;
|
||||
void discard() override;
|
||||
std::unique_ptr<StillImage> readStillImage() override;
|
||||
|
||||
private:
|
||||
void createContext();
|
||||
void loadExtensions();
|
||||
void clearBuffers();
|
||||
bool isActive();
|
||||
|
||||
private:
|
||||
std::shared_ptr<HeadlessDisplay> display_;
|
||||
uint16_t width_;
|
||||
uint16_t height_;
|
||||
float pixelRatio_;
|
||||
std::shared_ptr<HeadlessDisplay> display;
|
||||
|
||||
struct Dimensions {
|
||||
inline Dimensions(uint16_t width = 0, uint16_t height = 0, float pixelRatio = 0);
|
||||
inline uint16_t pixelWidth() const { return width * pixelRatio; }
|
||||
inline uint16_t pixelHeight() const { return height * pixelRatio; }
|
||||
|
||||
uint16_t width = 0;
|
||||
uint16_t height = 0;
|
||||
float pixelRatio = 0;
|
||||
};
|
||||
|
||||
// These are the values that represent the state of the current framebuffer.
|
||||
Dimensions current;
|
||||
|
||||
// These are the values that will be used after the next discard() event.
|
||||
std::mutex prospectiveMutex;
|
||||
Dimensions prospective;
|
||||
|
||||
#if MBGL_USE_CGL
|
||||
CGLContextObj glContext;
|
||||
CGLContextObj glContext = nullptr;
|
||||
#endif
|
||||
|
||||
#if MBGL_USE_GLX
|
||||
@@ -64,6 +79,8 @@ private:
|
||||
GLuint fbo = 0;
|
||||
GLuint fboDepthStencil = 0;
|
||||
GLuint fboColor = 0;
|
||||
|
||||
std::thread::id thread;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
|
||||
#include <mbgl/platform/default/headless_view.hpp>
|
||||
#include <mbgl/platform/default/headless_display.hpp>
|
||||
#include <mbgl/platform/log.hpp>
|
||||
|
||||
#include <mbgl/map/still_image.hpp>
|
||||
|
||||
#include <mbgl/util/std.hpp>
|
||||
|
||||
#include <stdexcept>
|
||||
@@ -36,17 +39,17 @@ CGLProc CGLGetProcAddress(const char *proc) {
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
|
||||
HeadlessView::HeadlessView()
|
||||
: display_(std::make_shared<HeadlessDisplay>()) {
|
||||
createContext();
|
||||
loadExtensions();
|
||||
HeadlessView::HeadlessView(uint16_t width, uint16_t height, float pixelRatio)
|
||||
: display(std::make_shared<HeadlessDisplay>()) {
|
||||
resize(width, height, pixelRatio);
|
||||
}
|
||||
|
||||
HeadlessView::HeadlessView(std::shared_ptr<HeadlessDisplay> display)
|
||||
: display_(display) {
|
||||
createContext();
|
||||
loadExtensions();
|
||||
HeadlessView::HeadlessView(std::shared_ptr<HeadlessDisplay> display_,
|
||||
uint16_t width,
|
||||
uint16_t height,
|
||||
float pixelRatio)
|
||||
: display(display_) {
|
||||
resize(width, height, pixelRatio);
|
||||
}
|
||||
|
||||
void HeadlessView::loadExtensions() {
|
||||
@@ -54,8 +57,6 @@ void HeadlessView::loadExtensions() {
|
||||
return;
|
||||
}
|
||||
|
||||
activate();
|
||||
|
||||
pthread_once(&loadGLExtensions, [] {
|
||||
const char *extensionPtr = reinterpret_cast<const char *>(MBGL_CHECK_ERROR(glGetString(GL_EXTENSIONS)));
|
||||
|
||||
@@ -95,17 +96,15 @@ void HeadlessView::loadExtensions() {
|
||||
gl::isDepth24Supported = true;
|
||||
|
||||
extensionsLoaded = true;
|
||||
|
||||
deactivate();
|
||||
}
|
||||
|
||||
void HeadlessView::createContext() {
|
||||
if (!display_) {
|
||||
if (!display) {
|
||||
throw std::runtime_error("Display is not set");
|
||||
}
|
||||
|
||||
#if MBGL_USE_CGL
|
||||
CGLError error = CGLCreateContext(display_->pixelFormat, NULL, &glContext);
|
||||
CGLError error = CGLCreateContext(display->pixelFormat, NULL, &glContext);
|
||||
if (error != kCGLNoError) {
|
||||
throw std::runtime_error(std::string("Error creating GL context object:") + CGLErrorString(error) + "\n");
|
||||
}
|
||||
@@ -117,8 +116,8 @@ void HeadlessView::createContext() {
|
||||
#endif
|
||||
|
||||
#if MBGL_USE_GLX
|
||||
xDisplay = display_->xDisplay;
|
||||
fbConfigs = display_->fbConfigs;
|
||||
xDisplay = display->xDisplay;
|
||||
fbConfigs = display->fbConfigs;
|
||||
|
||||
if (!glContext) {
|
||||
// Try to create a legacy context
|
||||
@@ -147,17 +146,34 @@ void HeadlessView::createContext() {
|
||||
#endif
|
||||
}
|
||||
|
||||
bool HeadlessView::isActive() {
|
||||
return std::this_thread::get_id() == thread;
|
||||
}
|
||||
|
||||
void HeadlessView::resize(const uint16_t width, const uint16_t height, const float pixelRatio) {
|
||||
std::lock_guard<std::mutex> lock(prospectiveMutex);
|
||||
prospective = { width, height, pixelRatio };
|
||||
}
|
||||
|
||||
HeadlessView::Dimensions::Dimensions(uint16_t width_, uint16_t height_, float pixelRatio_)
|
||||
: width(width_), height(height_), pixelRatio(pixelRatio_) {
|
||||
}
|
||||
|
||||
void HeadlessView::discard() {
|
||||
assert(isActive());
|
||||
|
||||
{ // Obtain the new values.
|
||||
std::lock_guard<std::mutex> lock(prospectiveMutex);
|
||||
if (current.pixelWidth() == prospective.pixelWidth() && current.pixelHeight() == prospective.pixelHeight()) {
|
||||
return;
|
||||
}
|
||||
current = prospective;
|
||||
}
|
||||
|
||||
clearBuffers();
|
||||
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
pixelRatio_ = pixelRatio;
|
||||
|
||||
const unsigned int w = width_ * pixelRatio_;
|
||||
const unsigned int h = height_ * pixelRatio_;
|
||||
|
||||
activate();
|
||||
const unsigned int w = current.width * current.pixelRatio;
|
||||
const unsigned int h = current.height * current.pixelRatio;
|
||||
|
||||
// Create depth/stencil buffer
|
||||
MBGL_CHECK_ERROR(glGenRenderbuffersEXT(1, &fboDepthStencil));
|
||||
@@ -193,35 +209,36 @@ void HeadlessView::resize(const uint16_t width, const uint16_t height, const flo
|
||||
throw std::runtime_error(error.str());
|
||||
}
|
||||
|
||||
View::resize(width, height, pixelRatio, w, h);
|
||||
|
||||
deactivate();
|
||||
View::resize(current.width, current.height, current.pixelRatio, w, h);
|
||||
}
|
||||
|
||||
std::unique_ptr<uint32_t[]> HeadlessView::readPixels() {
|
||||
const unsigned int w = width_ * pixelRatio_;
|
||||
const unsigned int h = height_ * pixelRatio_;
|
||||
std::unique_ptr<StillImage> HeadlessView::readStillImage() {
|
||||
assert(isActive());
|
||||
|
||||
auto pixels = util::make_unique<uint32_t[]>(w * h);
|
||||
const unsigned int w = current.pixelWidth();
|
||||
const unsigned int h = current.pixelHeight();
|
||||
|
||||
activate();
|
||||
MBGL_CHECK_ERROR(glReadPixels(0, 0, width_, height_, GL_RGBA, GL_UNSIGNED_BYTE, pixels.get()));
|
||||
deactivate();
|
||||
auto image = util::make_unique<StillImage>();
|
||||
image->width = w;
|
||||
image->height = h;
|
||||
image->pixels = util::make_unique<uint32_t[]>(w * h);
|
||||
|
||||
MBGL_CHECK_ERROR(glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, image->pixels.get()));
|
||||
|
||||
const int stride = w * 4;
|
||||
auto tmp = util::make_unique<char[]>(stride);
|
||||
char *rgba = reinterpret_cast<char *>(pixels.get());
|
||||
for (int i = 0, j = height_ - 1; i < j; i++, j--) {
|
||||
char *rgba = reinterpret_cast<char *>(image->pixels.get());
|
||||
for (int i = 0, j = h - 1; i < j; i++, j--) {
|
||||
std::memcpy(tmp.get(), rgba + i * stride, stride);
|
||||
std::memcpy(rgba + i * stride, rgba + j * stride, stride);
|
||||
std::memcpy(rgba + j * stride, tmp.get(), stride);
|
||||
}
|
||||
|
||||
return pixels;
|
||||
return image;
|
||||
}
|
||||
|
||||
void HeadlessView::clearBuffers() {
|
||||
activate();
|
||||
assert(isActive());
|
||||
|
||||
MBGL_CHECK_ERROR(glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0));
|
||||
|
||||
@@ -239,12 +256,12 @@ void HeadlessView::clearBuffers() {
|
||||
MBGL_CHECK_ERROR(glDeleteRenderbuffersEXT(1, &fboDepthStencil));
|
||||
fboDepthStencil = 0;
|
||||
}
|
||||
|
||||
deactivate();
|
||||
}
|
||||
|
||||
HeadlessView::~HeadlessView() {
|
||||
activate();
|
||||
clearBuffers();
|
||||
deactivate();
|
||||
|
||||
#if MBGL_USE_CGL
|
||||
CGLDestroyContext(glContext);
|
||||
@@ -265,6 +282,15 @@ void HeadlessView::notify() {
|
||||
}
|
||||
|
||||
void HeadlessView::activate() {
|
||||
if (thread != std::thread::id()) {
|
||||
throw std::runtime_error("OpenGL context was already current");
|
||||
}
|
||||
thread = std::this_thread::get_id();
|
||||
|
||||
if (!glContext) {
|
||||
createContext();
|
||||
}
|
||||
|
||||
#if MBGL_USE_CGL
|
||||
CGLError error = CGLSetCurrentContext(glContext);
|
||||
if (error != kCGLNoError) {
|
||||
@@ -277,9 +303,17 @@ void HeadlessView::activate() {
|
||||
throw std::runtime_error("Switching OpenGL context failed.\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
loadExtensions();
|
||||
discard();
|
||||
}
|
||||
|
||||
void HeadlessView::deactivate() {
|
||||
if (thread == std::thread::id()) {
|
||||
throw std::runtime_error("OpenGL context was not current");
|
||||
}
|
||||
thread = std::thread::id();
|
||||
|
||||
#if MBGL_USE_CGL
|
||||
CGLError error = CGLSetCurrentContext(nullptr);
|
||||
if (error != kCGLNoError) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <mbgl/map/environment.hpp>
|
||||
#include <mbgl/map/view.hpp>
|
||||
#include <mbgl/map/map_data.hpp>
|
||||
#include <mbgl/map/still_image.hpp>
|
||||
#include <mbgl/platform/platform.hpp>
|
||||
#include <mbgl/map/source.hpp>
|
||||
#include <mbgl/renderer/painter.hpp>
|
||||
@@ -79,7 +80,7 @@ Map::Map(View& view_, FileSource& fileSource_)
|
||||
}
|
||||
|
||||
Map::~Map() {
|
||||
if (mode == Mode::Continuous) {
|
||||
if (mode != Mode::None) {
|
||||
stop();
|
||||
}
|
||||
|
||||
@@ -111,13 +112,13 @@ Worker& Map::getWorker() {
|
||||
return *workers;
|
||||
}
|
||||
|
||||
void Map::start(bool startPaused) {
|
||||
void Map::start(bool startPaused, Mode renderMode) {
|
||||
assert(Environment::currentlyOn(ThreadType::Main));
|
||||
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.
|
||||
mode = Mode::Continuous;
|
||||
mode = renderMode;
|
||||
|
||||
// Reset the flag.
|
||||
isStopped = false;
|
||||
@@ -143,6 +144,10 @@ void Map::start(bool startPaused) {
|
||||
});
|
||||
|
||||
asyncUpdate = util::make_unique<uv::async>(env->loop, [this] {
|
||||
// Whenever we call triggerUpdate(), we ref() the asyncUpdate handle to make sure that all
|
||||
// of the calls actually get triggered.
|
||||
asyncUpdate->unref();
|
||||
|
||||
update();
|
||||
});
|
||||
|
||||
@@ -184,15 +189,15 @@ void Map::start(bool startPaused) {
|
||||
triggerUpdate();
|
||||
}
|
||||
|
||||
void Map::stop(std::function<void ()> callback) {
|
||||
void Map::stop(std::function<void ()> cb) {
|
||||
assert(Environment::currentlyOn(ThreadType::Main));
|
||||
assert(mode == Mode::Continuous);
|
||||
assert(mode != Mode::None);
|
||||
|
||||
asyncTerminate->send();
|
||||
|
||||
resume();
|
||||
|
||||
if (callback) {
|
||||
if (cb) {
|
||||
// Wait until the render thread stopped. We are using this construct instead of plainly
|
||||
// relying on the thread_join because the system might need to run things in the current
|
||||
// thread that is required for the render thread to terminate correctly. This is for example
|
||||
@@ -200,7 +205,7 @@ void Map::stop(std::function<void ()> callback) {
|
||||
// thread (== main thread) is blocked. The callback function should use an efficient waiting
|
||||
// function to avoid a busy waiting loop.
|
||||
while (!isStopped) {
|
||||
callback();
|
||||
cb();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,7 +236,7 @@ void Map::pause(bool waitForPause) {
|
||||
|
||||
void Map::resume() {
|
||||
assert(Environment::currentlyOn(ThreadType::Main));
|
||||
assert(mode == Mode::Continuous);
|
||||
assert(mode != Mode::None);
|
||||
|
||||
mutexRun.lock();
|
||||
pausing = false;
|
||||
@@ -239,21 +244,28 @@ void Map::resume() {
|
||||
mutexRun.unlock();
|
||||
}
|
||||
|
||||
void Map::run() {
|
||||
ThreadType threadType = ThreadType::Map;
|
||||
std::string threadName("Map");
|
||||
void Map::renderStill(StillImageCallback fn) {
|
||||
assert(Environment::currentlyOn(ThreadType::Main));
|
||||
|
||||
if (mode == Mode::None) {
|
||||
mode = Mode::Static;
|
||||
|
||||
// FIXME: Threads should have only one purpose. When running on Static mode,
|
||||
// we are currently not spawning a Map thread and running the code on the
|
||||
// Main thread, thus, the Main thread in this case is both Main and Map thread.
|
||||
threadType = static_cast<ThreadType>(static_cast<uint8_t>(threadType) | static_cast<uint8_t>(ThreadType::Main));
|
||||
threadName += "andMain";
|
||||
if (mode != Mode::Static) {
|
||||
throw util::Exception("Map is not in static render mode");
|
||||
}
|
||||
|
||||
EnvironmentScope mapScope(*env, threadType, threadName);
|
||||
if (callback) {
|
||||
throw util::Exception("Map is currently rendering an image");
|
||||
}
|
||||
|
||||
assert(mode == Mode::Static);
|
||||
|
||||
callback = std::move(fn);
|
||||
|
||||
triggerUpdate(Update::RenderStill);
|
||||
}
|
||||
|
||||
void Map::run() {
|
||||
EnvironmentScope mapScope(*env, ThreadType::Map, "Map");
|
||||
assert(Environment::currentlyOn(ThreadType::Map));
|
||||
assert(mode != Mode::None);
|
||||
|
||||
if (mode == Mode::Continuous) {
|
||||
checkForPause();
|
||||
@@ -261,11 +273,8 @@ void Map::run() {
|
||||
|
||||
auto styleInfo = data->getStyleInfo();
|
||||
|
||||
if (mode == Mode::Static && !style && (styleInfo.url.empty() && styleInfo.json.empty())) {
|
||||
throw util::Exception("Style is not set");
|
||||
}
|
||||
|
||||
view.activate();
|
||||
view.discard();
|
||||
|
||||
workers = util::make_unique<Worker>(env->loop, 4);
|
||||
|
||||
@@ -274,24 +283,47 @@ void Map::run() {
|
||||
|
||||
if (mode == Mode::Continuous) {
|
||||
terminating = false;
|
||||
while(!terminating) {
|
||||
while (!terminating) {
|
||||
uv_run(env->loop, UV_RUN_DEFAULT);
|
||||
checkForPause();
|
||||
}
|
||||
} else if (mode == Mode::Static) {
|
||||
terminating = false;
|
||||
while (!terminating) {
|
||||
uv_run(env->loop, UV_RUN_DEFAULT);
|
||||
|
||||
// After the loop terminated, these async handles may have been deleted if the terminate()
|
||||
// callback was fired. In this case, we are exiting the loop.
|
||||
if (asyncTerminate && asyncUpdate) {
|
||||
// Otherwise, loop termination means that we have acquired and parsed all resources
|
||||
// required for this map image and we can now proceed to rendering.
|
||||
render();
|
||||
auto image = view.readStillImage();
|
||||
|
||||
// We are moving the callback out of the way and empty it in case the callback function
|
||||
// starts the next map image render.
|
||||
assert(callback);
|
||||
StillImageCallback cb;
|
||||
std::swap(cb, callback);
|
||||
|
||||
// Now we can finally invoke the callback function with the map image we rendered.
|
||||
cb(std::move(image));
|
||||
|
||||
// To prepare for the next event loop run, we have to make sure the async handles keep
|
||||
// the loop alive.
|
||||
asyncTerminate->ref();
|
||||
asyncUpdate->ref();
|
||||
asyncInvoke->ref();
|
||||
asyncRender->ref();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
uv_run(env->loop, UV_RUN_DEFAULT);
|
||||
abort();
|
||||
}
|
||||
|
||||
// Run the event loop once more to make sure our async delete handlers are called.
|
||||
uv_run(env->loop, UV_RUN_ONCE);
|
||||
|
||||
// If the map rendering wasn't started asynchronously, we perform one render
|
||||
// *after* all events have been processed.
|
||||
if (mode == Mode::Static) {
|
||||
render();
|
||||
mode = Mode::None;
|
||||
}
|
||||
|
||||
view.deactivate();
|
||||
}
|
||||
|
||||
@@ -309,14 +341,14 @@ void Map::renderSync() {
|
||||
void Map::triggerUpdate(const Update u) {
|
||||
updated |= static_cast<UpdateType>(u);
|
||||
|
||||
if (mode == Mode::Static) {
|
||||
prepare();
|
||||
} else if (asyncUpdate) {
|
||||
if (asyncUpdate) {
|
||||
asyncUpdate->ref();
|
||||
asyncUpdate->send();
|
||||
}
|
||||
}
|
||||
|
||||
void Map::triggerRender() {
|
||||
assert(mode == Mode::Continuous);
|
||||
assert(asyncRender);
|
||||
asyncRender->send();
|
||||
}
|
||||
@@ -328,12 +360,8 @@ void Map::invokeTask(std::function<void()>&& fn) {
|
||||
tasks.emplace(::std::forward<std::function<void()>>(fn));
|
||||
}
|
||||
|
||||
// TODO: Once we have aligned static and continuous rendering, this should always dispatch
|
||||
// to the async queue.
|
||||
if (asyncInvoke) {
|
||||
asyncInvoke->send();
|
||||
} else {
|
||||
processTasks();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -775,6 +803,19 @@ void Map::prepare() {
|
||||
painter->setDebug(data->getDebug());
|
||||
}
|
||||
|
||||
if (u & static_cast<UpdateType>(Update::RenderStill)) {
|
||||
// Triggers a view resize.
|
||||
view.discard();
|
||||
|
||||
// Whenever we trigger an image render, we are unrefing all async handles so the loop will
|
||||
// eventually terminate. However, it'll stay alive as long as there are pending requests
|
||||
// (like work requests or HTTP requests).
|
||||
asyncTerminate->unref();
|
||||
asyncUpdate->unref();
|
||||
asyncInvoke->unref();
|
||||
asyncRender->unref();
|
||||
}
|
||||
|
||||
if (style) {
|
||||
if (u & static_cast<UpdateType>(Update::DefaultTransitionDuration)) {
|
||||
style->setDefaultTransitionDuration(data->getDefaultTransitionDuration());
|
||||
@@ -805,6 +846,8 @@ void Map::prepare() {
|
||||
void Map::render() {
|
||||
assert(Environment::currentlyOn(ThreadType::Map));
|
||||
|
||||
view.discard();
|
||||
|
||||
// Cleanup OpenGL objects that we abandoned since the last render call.
|
||||
env->performCleanup();
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <mbgl/map/view.hpp>
|
||||
#include <mbgl/map/map.hpp>
|
||||
#include <mbgl/map/still_image.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
@@ -8,6 +9,14 @@ void View::initialize(Map *map_) {
|
||||
map = map_;
|
||||
}
|
||||
|
||||
void View::discard() {
|
||||
// no-op
|
||||
}
|
||||
|
||||
std::unique_ptr<StillImage> View::readStillImage() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void View::resize(uint16_t width, uint16_t height, float ratio, uint16_t fbWidth, uint16_t fbHeight) {
|
||||
assert(map);
|
||||
map->resize(width, height, ratio, fbWidth, fbHeight);
|
||||
|
||||
@@ -42,7 +42,8 @@ void Log::record(EventSeverity severity, Event event, int64_t code) {
|
||||
}
|
||||
|
||||
void Log::record(EventSeverity severity, Event event, int64_t code, const std::string &msg) {
|
||||
if (currentObserver && currentObserver->onRecord(severity, event, code, msg)) {
|
||||
if (currentObserver && severity != EventSeverity::Debug &&
|
||||
currentObserver->onRecord(severity, event, code, msg)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include <mbgl/style/style_parser.hpp>
|
||||
#include <mbgl/style/style_bucket.hpp>
|
||||
#include <mbgl/util/constants.hpp>
|
||||
#include <mbgl/util/error.hpp>
|
||||
#include <mbgl/util/std.hpp>
|
||||
#include <mbgl/util/uv_detail.hpp>
|
||||
#include <mbgl/platform/log.hpp>
|
||||
@@ -75,7 +74,7 @@ void Style::loadJSON(const uint8_t *const data) {
|
||||
doc.Parse<0>((const char *const)data);
|
||||
if (doc.HasParseError()) {
|
||||
Log::Error(Event::ParseStyle, "Error parsing style JSON at %i: %s", doc.GetErrorOffset(), doc.GetParseError());
|
||||
throw error::style_parse(doc.GetErrorOffset(), doc.GetParseError());
|
||||
return;
|
||||
}
|
||||
|
||||
StyleParser parser;
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
#ifndef MBGL_UTIL_ERROR
|
||||
#define MBGL_UTIL_ERROR
|
||||
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace mbgl {
|
||||
namespace error {
|
||||
|
||||
struct style_parse : std::exception {
|
||||
inline style_parse(size_t offset_, const char *msg_) : offset(offset_), msg(msg_) {}
|
||||
inline const char* what() const noexcept { return msg.c_str(); }
|
||||
const size_t offset;
|
||||
const std::string msg;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
63
test/api/repeated_render.cpp
Normal file
63
test/api/repeated_render.cpp
Normal file
@@ -0,0 +1,63 @@
|
||||
#include "../fixtures/util.hpp"
|
||||
#include "../fixtures/fixture_log_observer.hpp"
|
||||
|
||||
#include <mbgl/map/map.hpp>
|
||||
#include <mbgl/map/still_image.hpp>
|
||||
#include <mbgl/platform/default/headless_view.hpp>
|
||||
#include <mbgl/platform/default/headless_display.hpp>
|
||||
#include <mbgl/storage/default_file_source.hpp>
|
||||
#include <mbgl/util/image.hpp>
|
||||
#include <mbgl/util/io.hpp>
|
||||
|
||||
#include <future>
|
||||
|
||||
TEST(API, RepeatedRender) {
|
||||
using namespace mbgl;
|
||||
|
||||
const auto style = util::read_file("test/fixtures/api/water.json");
|
||||
|
||||
auto display = std::make_shared<mbgl::HeadlessDisplay>();
|
||||
HeadlessView view(display);
|
||||
DefaultFileSource fileSource(nullptr);
|
||||
|
||||
Log::setObserver(util::make_unique<FixtureLogObserver>());
|
||||
|
||||
Map map(view, fileSource);
|
||||
|
||||
map.start(Map::Mode::Static);
|
||||
|
||||
{
|
||||
view.resize(128, 512, 1);
|
||||
map.setStyleJSON(style, "test/suite");
|
||||
std::promise<std::unique_ptr<const StillImage>> promise;
|
||||
map.renderStill([&promise](std::unique_ptr<const StillImage> image) {
|
||||
promise.set_value(std::move(image));
|
||||
});
|
||||
auto result = promise.get_future().get();
|
||||
ASSERT_EQ(128, result->width);
|
||||
ASSERT_EQ(512, result->height);
|
||||
const std::string png = util::compress_png(result->width, result->height, result->pixels.get());
|
||||
util::write_file("test/fixtures/api/1.png", png);
|
||||
}
|
||||
|
||||
{
|
||||
view.resize(512, 512, 2);
|
||||
map.setStyleJSON(style, "TEST_DATA/suite");
|
||||
std::promise<std::unique_ptr<const StillImage>> promise;
|
||||
map.renderStill([&promise](std::unique_ptr<const StillImage> image) {
|
||||
promise.set_value(std::move(image));
|
||||
});
|
||||
auto result = promise.get_future().get();
|
||||
ASSERT_EQ(1024, result->width);
|
||||
ASSERT_EQ(1024, result->height);
|
||||
const std::string png = util::compress_png(result->width, result->height, result->pixels.get());
|
||||
util::write_file("test/fixtures/api/2.png", png);
|
||||
}
|
||||
|
||||
map.stop();
|
||||
|
||||
auto observer = Log::removeObserver();
|
||||
auto flo = dynamic_cast<FixtureLogObserver*>(observer.get());
|
||||
auto unchecked = flo->unchecked();
|
||||
EXPECT_TRUE(unchecked.empty()) << unchecked;
|
||||
}
|
||||
33
test/api/set_style.cpp
Normal file
33
test/api/set_style.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#include "../fixtures/util.hpp"
|
||||
#include "../fixtures/fixture_log_observer.hpp"
|
||||
|
||||
#include <mbgl/map/map.hpp>
|
||||
#include <mbgl/platform/default/headless_view.hpp>
|
||||
#include <mbgl/platform/default/headless_display.hpp>
|
||||
#include <mbgl/storage/default_file_source.hpp>
|
||||
|
||||
|
||||
TEST(API, SetStyle) {
|
||||
using namespace mbgl;
|
||||
|
||||
auto display = std::make_shared<mbgl::HeadlessDisplay>();
|
||||
HeadlessView view(display);
|
||||
DefaultFileSource fileSource(nullptr);
|
||||
|
||||
Log::setObserver(util::make_unique<FixtureLogObserver>());
|
||||
|
||||
Map map(view, fileSource);
|
||||
|
||||
map.start(Map::Mode::Static);
|
||||
|
||||
map.setStyleJSON("invalid", "test/suite");
|
||||
|
||||
map.stop();
|
||||
|
||||
auto observer = Log::removeObserver();
|
||||
auto flo = dynamic_cast<FixtureLogObserver*>(observer.get());
|
||||
EXPECT_EQ(1ul, flo->count({ EventSeverity::Error, Event::ParseStyle, -1,
|
||||
"Error parsing style JSON at 0: Expect either an object or array at root" }));
|
||||
auto unchecked = flo->unchecked();
|
||||
EXPECT_TRUE(unchecked.empty()) << unchecked;
|
||||
}
|
||||
BIN
test/fixtures/api/1.png
vendored
Normal file
BIN
test/fixtures/api/1.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
test/fixtures/api/2.png
vendored
Normal file
BIN
test/fixtures/api/2.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 112 KiB |
25
test/fixtures/api/water.json
vendored
Normal file
25
test/fixtures/api/water.json
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"version": 7,
|
||||
"name": "Water",
|
||||
"sources": {
|
||||
"mapbox": {
|
||||
"type": "vector",
|
||||
"url": "asset://TEST_DATA/fixtures/tiles/streets.json"
|
||||
}
|
||||
},
|
||||
"layers": [{
|
||||
"id": "background",
|
||||
"type": "background",
|
||||
"paint": {
|
||||
"background-color": "red"
|
||||
}
|
||||
}, {
|
||||
"id": "water",
|
||||
"type": "fill",
|
||||
"source": "mapbox",
|
||||
"source-layer": "water",
|
||||
"paint": {
|
||||
"fill-color": "blue"
|
||||
}
|
||||
}]
|
||||
}
|
||||
14
test/fixtures/tiles/streets.json
vendored
Normal file
14
test/fixtures/tiles/streets.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"bounds": [ -180, -85.0511, 180, 85.0511 ],
|
||||
"center": [ 0, 0, 0 ],
|
||||
"description": "",
|
||||
"format": "pbf",
|
||||
"id": "streets",
|
||||
"maskLevel": 8,
|
||||
"maxzoom": 15,
|
||||
"minzoom": 0,
|
||||
"name": "Streets",
|
||||
"scheme": "xyz",
|
||||
"tilejson": "2.0.0",
|
||||
"tiles": [ "asset://TEST_DATA/fixtures/tiles/streets/{z}-{x}-{y}.vector.pbf" ]
|
||||
}
|
||||
BIN
test/fixtures/tiles/streets/0-0-0.vector.pbf
vendored
Normal file
BIN
test/fixtures/tiles/streets/0-0-0.vector.pbf
vendored
Normal file
Binary file not shown.
@@ -2,6 +2,7 @@
|
||||
#include "../fixtures/fixture_log_observer.hpp"
|
||||
|
||||
#include <mbgl/map/map.hpp>
|
||||
#include <mbgl/map/still_image.hpp>
|
||||
#include <mbgl/util/image.hpp>
|
||||
#include <mbgl/util/std.hpp>
|
||||
|
||||
@@ -15,6 +16,8 @@
|
||||
#include <mbgl/platform/default/headless_display.hpp>
|
||||
#include <mbgl/storage/default_file_source.hpp>
|
||||
|
||||
#include <uv.h>
|
||||
|
||||
#include <dirent.h>
|
||||
|
||||
void rewriteLocalScheme(rapidjson::Value &value, rapidjson::Document::AllocatorType &allocator) {
|
||||
@@ -142,6 +145,8 @@ TEST_P(HeadlessTest, render) {
|
||||
DefaultFileSource fileSource(nullptr);
|
||||
Map map(view, fileSource);
|
||||
|
||||
map.start(Map::Mode::Static);
|
||||
|
||||
map.setClasses(classes);
|
||||
map.setStyleJSON(style, "test/suite");
|
||||
|
||||
@@ -149,16 +154,34 @@ TEST_P(HeadlessTest, render) {
|
||||
map.setLatLngZoom(mbgl::LatLng(latitude, longitude), zoom);
|
||||
map.setBearing(bearing);
|
||||
|
||||
// Run the loop. It will terminate when we don't have any further listeners.
|
||||
map.run();
|
||||
struct Data {
|
||||
std::string path;
|
||||
std::unique_ptr<const StillImage> image;
|
||||
};
|
||||
|
||||
const unsigned int w = width * pixelRatio;
|
||||
const unsigned int h = height * pixelRatio;
|
||||
uv_async_t *async = new uv_async_t;
|
||||
async->data = new Data { "test/suite/tests/" + base + "/" + name + "/actual.png", nullptr };
|
||||
uv_async_init(uv_default_loop(), async, [](uv_async_t *as, int) {
|
||||
auto data = std::unique_ptr<Data>(reinterpret_cast<Data *>(as->data));
|
||||
as->data = nullptr;
|
||||
uv_close(reinterpret_cast<uv_handle_t *>(as), [](uv_handle_t *handle) {
|
||||
delete reinterpret_cast<uv_async_t *>(handle);
|
||||
});
|
||||
|
||||
auto pixels = view.readPixels();
|
||||
assert(data);
|
||||
const std::string png = util::compress_png(data->image->width, data->image->height, data->image->pixels.get());
|
||||
util::write_file(data->path, png);
|
||||
});
|
||||
|
||||
const std::string image = util::compress_png(w, h, pixels.get());
|
||||
util::write_file(actual_image, image);
|
||||
map.renderStill([async](std::unique_ptr<const StillImage> image) {
|
||||
reinterpret_cast<Data *>(async->data)->image = std::move(image);
|
||||
uv_async_send(async);
|
||||
});
|
||||
|
||||
// This loop will terminate once the async was fired.
|
||||
uv_run(uv_default_loop(), UV_RUN_DEFAULT);
|
||||
|
||||
map.stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,9 @@
|
||||
'fixtures/fixture_log_observer.hpp',
|
||||
'fixtures/fixture_log_observer.cpp',
|
||||
|
||||
'api/set_style.cpp',
|
||||
'api/repeated_render.cpp',
|
||||
|
||||
'headless/headless.cpp',
|
||||
|
||||
'miscellaneous/clip_ids.cpp',
|
||||
|
||||
Reference in New Issue
Block a user