summaryrefslogtreecommitdiff
path: root/platform/glfw
diff options
context:
space:
mode:
authorKonstantin Käfer <mail@kkaefer.com>2016-11-22 12:29:50 +0100
committerKonstantin Käfer <mail@kkaefer.com>2016-11-22 20:58:51 +0100
commit67ed3ae3eb25e5bb0ec2d0369d042438bd9b2c7f (patch)
treea71ef8fdf2ec4d080ceef8e4106e8db9ef5a26ff /platform/glfw
parent9353fcbf4cde1f8ca2e0d30cabeb64aa3528682e (diff)
downloadqtlocation-mapboxgl-67ed3ae3eb25e5bb0ec2d0369d042438bd9b2c7f.tar.gz
[build] move GLFW-related files to platform/glfw
Diffstat (limited to 'platform/glfw')
-rw-r--r--platform/glfw/glfw_view.cpp630
-rw-r--r--platform/glfw/glfw_view.hpp111
-rw-r--r--platform/glfw/main.cpp190
-rw-r--r--platform/glfw/settings_json.cpp41
-rw-r--r--platform/glfw/settings_json.hpp24
5 files changed, 996 insertions, 0 deletions
diff --git a/platform/glfw/glfw_view.cpp b/platform/glfw/glfw_view.cpp
new file mode 100644
index 0000000000..ecc5e73da1
--- /dev/null
+++ b/platform/glfw/glfw_view.cpp
@@ -0,0 +1,630 @@
+#include "glfw_view.hpp"
+
+#include <mbgl/annotation/annotation.hpp>
+#include <mbgl/sprite/sprite_image.hpp>
+#include <mbgl/style/transition_options.hpp>
+#include <mbgl/gl/gl.hpp>
+#include <mbgl/gl/extension.hpp>
+#include <mbgl/gl/context.hpp>
+#include <mbgl/platform/log.hpp>
+#include <mbgl/platform/platform.hpp>
+#include <mbgl/util/string.hpp>
+#include <mbgl/util/chrono.hpp>
+#include <mbgl/map/camera.hpp>
+
+#include <mbgl/gl/state.hpp>
+#include <mbgl/gl/value.hpp>
+
+#include <cassert>
+#include <cstdlib>
+
+void glfwError(int error, const char *description) {
+ mbgl::Log::Error(mbgl::Event::OpenGL, "GLFW error (%i): %s", error, description);
+ assert(false);
+}
+
+GLFWView::GLFWView(bool fullscreen_, bool benchmark_)
+ : fullscreen(fullscreen_), benchmark(benchmark_) {
+ glfwSetErrorCallback(glfwError);
+
+ std::srand(std::time(nullptr));
+
+ if (!glfwInit()) {
+ mbgl::Log::Error(mbgl::Event::OpenGL, "failed to initialize glfw");
+ exit(1);
+ }
+
+ GLFWmonitor *monitor = nullptr;
+ if (fullscreen) {
+ monitor = glfwGetPrimaryMonitor();
+ auto videoMode = glfwGetVideoMode(monitor);
+ width = videoMode->width;
+ height = videoMode->height;
+ }
+
+#ifdef DEBUG
+ glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE);
+#endif
+
+#ifdef GL_ES_VERSION_2_0
+ glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
+#endif
+
+ glfwWindowHint(GLFW_RED_BITS, 8);
+ glfwWindowHint(GLFW_GREEN_BITS, 8);
+ glfwWindowHint(GLFW_BLUE_BITS, 8);
+ glfwWindowHint(GLFW_ALPHA_BITS, 8);
+ glfwWindowHint(GLFW_STENCIL_BITS, 8);
+ glfwWindowHint(GLFW_DEPTH_BITS, 16);
+
+ window = glfwCreateWindow(width, height, "Mapbox GL", monitor, nullptr);
+ if (!window) {
+ glfwTerminate();
+ mbgl::Log::Error(mbgl::Event::OpenGL, "failed to initialize window");
+ exit(1);
+ }
+
+ glfwSetWindowUserPointer(window, this);
+ glfwMakeContextCurrent(window);
+ if (benchmark) {
+ // Disables vsync on platforms that support it.
+ glfwSwapInterval(0);
+ } else {
+ glfwSwapInterval(1);
+ }
+
+
+ glfwSetCursorPosCallback(window, onMouseMove);
+ glfwSetMouseButtonCallback(window, onMouseClick);
+ glfwSetWindowSizeCallback(window, onWindowResize);
+ glfwSetFramebufferSizeCallback(window, onFramebufferResize);
+ glfwSetScrollCallback(window, onScroll);
+ glfwSetKeyCallback(window, onKey);
+
+ mbgl::gl::InitializeExtensions(glfwGetProcAddress);
+
+ glfwGetWindowSize(window, &width, &height);
+ glfwGetFramebufferSize(window, &fbWidth, &fbHeight);
+ pixelRatio = static_cast<float>(fbWidth) / width;
+
+ glfwMakeContextCurrent(nullptr);
+
+ printf("\n");
+ printf("================================================================================\n");
+ printf("\n");
+ printf("- Press `S` to cycle through bundled styles\n");
+ printf("- Press `X` to reset the transform\n");
+ printf("- Press `N` to reset north\n");
+ printf("- Press `R` to toggle any available `night` style class\n");
+ printf("- Press `Z` to cycle through north orientations\n");
+ printf("- Prezz `X` to cycle through the viewport modes\n");
+ printf("- Press `A` to cycle through Mapbox offices in the world + dateline monument\n");
+ printf("- Press `B` to cycle through the color, stencil, and depth buffer\n");
+ printf("\n");
+ printf("- Press `1` through `6` to add increasing numbers of point annotations for testing\n");
+ printf("- Press `7` through `0` to add increasing numbers of shape annotations for testing\n");
+ printf("\n");
+ printf("- Press `Q` to remove annotations\n");
+ printf("- Press `P` to add a random custom runtime imagery annotation\n");
+ printf("- Press `L` to add a random line annotation\n");
+ printf("- Press `W` to pop the last-added annotation off\n");
+ printf("\n");
+ printf("- `Control` + mouse drag to rotate\n");
+ printf("- `Shift` + mouse drag to tilt\n");
+ printf("\n");
+ printf("- Press `Tab` to cycle through the map debug options\n");
+ printf("- Press `Esc` to quit\n");
+ printf("\n");
+ printf("================================================================================\n");
+ printf("\n");
+}
+
+GLFWView::~GLFWView() {
+ glfwDestroyWindow(window);
+ glfwTerminate();
+}
+
+void GLFWView::setMap(mbgl::Map *map_) {
+ map = map_;
+ map->addAnnotationIcon("default_marker", makeSpriteImage(22, 22, 1));
+}
+
+void GLFWView::updateViewBinding() {
+ getContext().bindFramebuffer.setCurrentValue(0);
+ assert(mbgl::gl::value::BindFramebuffer::Get() == getContext().bindFramebuffer.getCurrentValue());
+ getContext().viewport.setCurrentValue({ 0, 0, getFramebufferSize() });
+ assert(mbgl::gl::value::Viewport::Get() == getContext().viewport.getCurrentValue());
+}
+
+void GLFWView::bind() {
+ getContext().bindFramebuffer = 0;
+ getContext().viewport = { 0, 0, getFramebufferSize() };
+}
+
+void GLFWView::onKey(GLFWwindow *window, int key, int /*scancode*/, int action, int mods) {
+ GLFWView *view = reinterpret_cast<GLFWView *>(glfwGetWindowUserPointer(window));
+
+ if (action == GLFW_RELEASE) {
+ switch (key) {
+ case GLFW_KEY_ESCAPE:
+ glfwSetWindowShouldClose(window, true);
+ break;
+ case GLFW_KEY_TAB:
+ view->map->cycleDebugOptions();
+ break;
+ case GLFW_KEY_X:
+ if (!mods)
+ view->map->resetPosition();
+ break;
+ case GLFW_KEY_S:
+ if (view->changeStyleCallback)
+ view->changeStyleCallback();
+ break;
+ case GLFW_KEY_R:
+ if (!mods) {
+ static const mbgl::style::TransitionOptions transition { { mbgl::Milliseconds(300) } };
+ view->map->setTransitionOptions(transition);
+ if (view->map->hasClass("night")) {
+ view->map->removeClass("night");
+ } else {
+ view->map->addClass("night");
+ }
+ }
+ break;
+#if not MBGL_USE_GLES2
+ case GLFW_KEY_B: {
+ auto debug = view->map->getDebug();
+ if (debug & mbgl::MapDebugOptions::StencilClip) {
+ debug &= ~mbgl::MapDebugOptions::StencilClip;
+ debug |= mbgl::MapDebugOptions::DepthBuffer;
+ } else if (debug & mbgl::MapDebugOptions::DepthBuffer) {
+ debug &= ~mbgl::MapDebugOptions::DepthBuffer;
+ } else {
+ debug |= mbgl::MapDebugOptions::StencilClip;
+ }
+ view->map->setDebug(debug);
+ } break;
+#endif // MBGL_USE_GLES2
+ case GLFW_KEY_N:
+ if (!mods)
+ view->map->resetNorth();
+ break;
+ case GLFW_KEY_Z:
+ view->nextOrientation();
+ break;
+ case GLFW_KEY_Q: {
+ auto result = view->map->queryPointAnnotations({ {}, { double(view->getSize().width), double(view->getSize().height) } });
+ printf("visible point annotations: %lu\n", result.size());
+ } break;
+ case GLFW_KEY_C:
+ view->clearAnnotations();
+ break;
+ case GLFW_KEY_P:
+ view->addRandomCustomPointAnnotations(1);
+ break;
+ case GLFW_KEY_L:
+ view->addRandomLineAnnotations(1);
+ break;
+ case GLFW_KEY_A: {
+ // XXX Fix precision loss in flyTo:
+ // https://github.com/mapbox/mapbox-gl-native/issues/4298
+ static const std::vector<mbgl::LatLng> places = {
+ mbgl::LatLng { -16.796665, -179.999983 }, // Dateline monument
+ mbgl::LatLng { 12.9810542, 77.6345551 }, // Mapbox Bengaluru, India
+ mbgl::LatLng { -13.15607,-74.21773 }, // Mapbox Peru
+ mbgl::LatLng { 37.77572, -122.4158818 }, // Mapbox SF, USA
+ mbgl::LatLng { 38.91318,-77.03255 }, // Mapbox DC, USA
+ };
+ static size_t nextPlace = 0;
+ mbgl::CameraOptions cameraOptions;
+ cameraOptions.center = places[nextPlace++];
+ cameraOptions.zoom = 20;
+ cameraOptions.pitch = 30;
+
+ mbgl::AnimationOptions animationOptions(mbgl::Seconds(10));
+ view->map->flyTo(cameraOptions, animationOptions);
+ nextPlace = nextPlace % places.size();
+ } break;
+ }
+ }
+
+ if (action == GLFW_RELEASE || action == GLFW_REPEAT) {
+ switch (key) {
+ case GLFW_KEY_W: view->popAnnotation(); break;
+ case GLFW_KEY_1: view->addRandomPointAnnotations(1); break;
+ case GLFW_KEY_2: view->addRandomPointAnnotations(10); break;
+ case GLFW_KEY_3: view->addRandomPointAnnotations(100); break;
+ case GLFW_KEY_4: view->addRandomPointAnnotations(1000); break;
+ case GLFW_KEY_5: view->addRandomPointAnnotations(10000); break;
+ case GLFW_KEY_6: view->addRandomPointAnnotations(100000); break;
+ case GLFW_KEY_7: view->addRandomShapeAnnotations(1); break;
+ case GLFW_KEY_8: view->addRandomShapeAnnotations(10); break;
+ case GLFW_KEY_9: view->addRandomShapeAnnotations(100); break;
+ case GLFW_KEY_0: view->addRandomShapeAnnotations(1000); break;
+ }
+ }
+}
+
+mbgl::Color GLFWView::makeRandomColor() const {
+ const float r = 1.0f * (float(std::rand()) / RAND_MAX);
+ const float g = 1.0f * (float(std::rand()) / RAND_MAX);
+ const float b = 1.0f * (float(std::rand()) / RAND_MAX);
+ return { r, g, b, 1.0f };
+}
+
+mbgl::Point<double> GLFWView::makeRandomPoint() const {
+ const double x = width * double(std::rand()) / RAND_MAX;
+ const double y = height * double(std::rand()) / RAND_MAX;
+ mbgl::LatLng latLng = map->latLngForPixel({ x, y });
+ return { latLng.longitude, latLng.latitude };
+}
+
+std::shared_ptr<const mbgl::SpriteImage>
+GLFWView::makeSpriteImage(int width, int height, float pixelRatio) {
+ const int r = 255 * (double(std::rand()) / RAND_MAX);
+ const int g = 255 * (double(std::rand()) / RAND_MAX);
+ const int b = 255 * (double(std::rand()) / RAND_MAX);
+
+ const int w = std::ceil(pixelRatio * width);
+ const int h = std::ceil(pixelRatio * height);
+
+ mbgl::PremultipliedImage image({ static_cast<uint32_t>(w), static_cast<uint32_t>(h) });
+ auto data = reinterpret_cast<uint32_t*>(image.data.get());
+ const int dist = (w / 2) * (w / 2);
+ for (int y = 0; y < h; y++) {
+ for (int x = 0; x < w; x++) {
+ const int dx = x - w / 2;
+ const int dy = y - h / 2;
+ const int diff = dist - (dx * dx + dy * dy);
+ if (diff > 0) {
+ const int a = std::min(0xFF, diff) * 0xFF / dist;
+ // Premultiply the rgb values with alpha
+ data[w * y + x] =
+ (a << 24) | ((a * r / 0xFF) << 16) | ((a * g / 0xFF) << 8) | (a * b / 0xFF);
+ }
+ }
+ }
+
+ return std::make_shared<mbgl::SpriteImage>(std::move(image), pixelRatio);
+}
+
+void GLFWView::nextOrientation() {
+ using NO = mbgl::NorthOrientation;
+ switch (map->getNorthOrientation()) {
+ case NO::Upwards: map->setNorthOrientation(NO::Rightwards); break;
+ case NO::Rightwards: map->setNorthOrientation(NO::Downwards); break;
+ case NO::Downwards: map->setNorthOrientation(NO::Leftwards); break;
+ default: map->setNorthOrientation(NO::Upwards); break;
+ }
+}
+
+void GLFWView::addRandomCustomPointAnnotations(int count) {
+ for (int i = 0; i < count; i++) {
+ static int spriteID = 1;
+ const auto name = std::string{ "marker-" } + mbgl::util::toString(spriteID++);
+ map->addAnnotationIcon(name, makeSpriteImage(22, 22, 1));
+ spriteIDs.push_back(name);
+ annotationIDs.push_back(map->addAnnotation(mbgl::SymbolAnnotation { makeRandomPoint(), name }));
+ }
+}
+
+void GLFWView::addRandomPointAnnotations(int count) {
+ for (int i = 0; i < count; ++i) {
+ annotationIDs.push_back(map->addAnnotation(mbgl::SymbolAnnotation { makeRandomPoint(), "default_marker" }));
+ }
+}
+
+void GLFWView::addRandomLineAnnotations(int count) {
+ for (int i = 0; i < count; ++i) {
+ mbgl::LineString<double> lineString;
+ for (int j = 0; j < 3; ++j) {
+ lineString.push_back(makeRandomPoint());
+ }
+ annotationIDs.push_back(map->addAnnotation(mbgl::LineAnnotation { lineString, 1.0f, 2.0f, { makeRandomColor() } }));
+ }
+}
+
+void GLFWView::addRandomShapeAnnotations(int count) {
+ for (int i = 0; i < count; ++i) {
+ mbgl::Polygon<double> triangle;
+ triangle.push_back({ makeRandomPoint(), makeRandomPoint(), makeRandomPoint() });
+ annotationIDs.push_back(map->addAnnotation(mbgl::FillAnnotation { triangle, 0.5f, { makeRandomColor() }, { makeRandomColor() } }));
+ }
+}
+
+void GLFWView::clearAnnotations() {
+ for (const auto& id : annotationIDs) {
+ map->removeAnnotation(id);
+ }
+
+ annotationIDs.clear();
+}
+
+void GLFWView::popAnnotation() {
+ if (annotationIDs.empty()) {
+ return;
+ }
+
+ map->removeAnnotation(annotationIDs.back());
+ annotationIDs.pop_back();
+}
+
+void GLFWView::onScroll(GLFWwindow *window, double /*xOffset*/, double yOffset) {
+ GLFWView *view = reinterpret_cast<GLFWView *>(glfwGetWindowUserPointer(window));
+ double delta = yOffset * 40;
+
+ bool isWheel = delta != 0 && std::fmod(delta, 4.000244140625) == 0;
+
+ double absDelta = delta < 0 ? -delta : delta;
+ double scale = 2.0 / (1.0 + std::exp(-absDelta / 100.0));
+
+ // Make the scroll wheel a bit slower.
+ if (!isWheel) {
+ scale = (scale - 1.0) / 2.0 + 1.0;
+ }
+
+ // Zooming out.
+ if (delta < 0 && scale != 0) {
+ scale = 1.0 / scale;
+ }
+
+ view->map->scaleBy(scale, mbgl::ScreenCoordinate { view->lastX, view->lastY });
+}
+
+void GLFWView::onWindowResize(GLFWwindow *window, int width, int height) {
+ GLFWView *view = reinterpret_cast<GLFWView *>(glfwGetWindowUserPointer(window));
+ view->width = width;
+ view->height = height;
+ view->map->setSize({ static_cast<uint32_t>(view->width), static_cast<uint32_t>(view->height) });
+}
+
+void GLFWView::onFramebufferResize(GLFWwindow *window, int width, int height) {
+ GLFWView *view = reinterpret_cast<GLFWView *>(glfwGetWindowUserPointer(window));
+ view->fbWidth = width;
+ view->fbHeight = height;
+
+ // This is only triggered when the framebuffer is resized, but not the window. It can
+ // happen when you move the window between screens with a different pixel ratio.
+ // We are forcing a repaint my invalidating the view, which triggers a rerender with the
+ // new framebuffer dimensions.
+ view->invalidate();
+}
+
+void GLFWView::onMouseClick(GLFWwindow *window, int button, int action, int modifiers) {
+ GLFWView *view = reinterpret_cast<GLFWView *>(glfwGetWindowUserPointer(window));
+
+ if (button == GLFW_MOUSE_BUTTON_RIGHT ||
+ (button == GLFW_MOUSE_BUTTON_LEFT && modifiers & GLFW_MOD_CONTROL)) {
+ view->rotating = action == GLFW_PRESS;
+ view->map->setGestureInProgress(view->rotating);
+ } else if (button == GLFW_MOUSE_BUTTON_LEFT && (modifiers & GLFW_MOD_SHIFT)) {
+ view->pitching = action == GLFW_PRESS;
+ view->map->setGestureInProgress(view->pitching);
+ } else if (button == GLFW_MOUSE_BUTTON_LEFT) {
+ view->tracking = action == GLFW_PRESS;
+ view->map->setGestureInProgress(view->tracking);
+
+ if (action == GLFW_RELEASE) {
+ double now = glfwGetTime();
+ if (now - view->lastClick < 0.4 /* ms */) {
+ if (modifiers & GLFW_MOD_SHIFT) {
+ view->map->scaleBy(0.5, mbgl::ScreenCoordinate { view->lastX, view->lastY }, mbgl::Milliseconds(500));
+ } else {
+ view->map->scaleBy(2.0, mbgl::ScreenCoordinate { view->lastX, view->lastY }, mbgl::Milliseconds(500));
+ }
+ }
+ view->lastClick = now;
+ }
+ }
+}
+
+void GLFWView::onMouseMove(GLFWwindow *window, double x, double y) {
+ GLFWView *view = reinterpret_cast<GLFWView *>(glfwGetWindowUserPointer(window));
+ if (view->tracking) {
+ double dx = x - view->lastX;
+ double dy = y - view->lastY;
+ if (dx || dy) {
+ view->map->setLatLng(
+ view->map->latLngForPixel(mbgl::ScreenCoordinate(x - dx, y - dy)),
+ mbgl::ScreenCoordinate(x, y));
+ }
+ } else if (view->rotating) {
+ view->map->rotateBy({ view->lastX, view->lastY }, { x, y });
+ } else if (view->pitching) {
+ const double dy = y - view->lastY;
+ if (dy) {
+ view->map->setPitch(view->map->getPitch() - dy / 2);
+ }
+ }
+ view->lastX = x;
+ view->lastY = y;
+}
+
+void GLFWView::run() {
+ auto callback = [&] {
+ if (glfwWindowShouldClose(window)) {
+ runLoop.stop();
+ return;
+ }
+
+ glfwPollEvents();
+
+ if (dirty) {
+ const double started = glfwGetTime();
+
+ glfwMakeContextCurrent(window);
+
+ updateViewBinding();
+ map->render(*this);
+
+ glfwSwapBuffers(window);
+
+ report(1000 * (glfwGetTime() - started));
+ if (benchmark) {
+ invalidate();
+ }
+
+ dirty = false;
+ }
+ };
+
+ frameTick.start(mbgl::Duration::zero(), mbgl::Milliseconds(1000 / 60), callback);
+#if defined(__APPLE__)
+ while (!glfwWindowShouldClose(window)) runLoop.run();
+#else
+ runLoop.run();
+#endif
+}
+
+float GLFWView::getPixelRatio() const {
+ return pixelRatio;
+}
+
+mbgl::Size GLFWView::getSize() const {
+ return { static_cast<uint32_t>(width), static_cast<uint32_t>(height) };
+}
+
+mbgl::Size GLFWView::getFramebufferSize() const {
+ return { static_cast<uint32_t>(fbWidth), static_cast<uint32_t>(fbHeight) };
+}
+
+void GLFWView::activate() {
+ glfwMakeContextCurrent(window);
+}
+
+void GLFWView::deactivate() {
+ glfwMakeContextCurrent(nullptr);
+}
+
+void GLFWView::invalidate() {
+ dirty = true;
+ glfwPostEmptyEvent();
+}
+
+void GLFWView::report(float duration) {
+ frames++;
+ frameTime += duration;
+
+ const double currentTime = glfwGetTime();
+ if (currentTime - lastReported >= 1) {
+ frameTime /= frames;
+ mbgl::Log::Info(mbgl::Event::OpenGL, "Frame time: %6.2fms (%6.2f fps)", frameTime,
+ 1000 / frameTime);
+ frames = 0;
+ frameTime = 0;
+ lastReported = currentTime;
+ }
+}
+
+void GLFWView::setChangeStyleCallback(std::function<void()> callback) {
+ changeStyleCallback = callback;
+}
+
+void GLFWView::setShouldClose() {
+ glfwSetWindowShouldClose(window, true);
+ glfwPostEmptyEvent();
+}
+
+void GLFWView::setWindowTitle(const std::string& title) {
+ glfwSetWindowTitle(window, (std::string { "Mapbox GL: " } + title).c_str());
+}
+
+void GLFWView::setMapChangeCallback(std::function<void(mbgl::MapChange)> callback) {
+ this->mapChangeCallback = callback;
+}
+
+void GLFWView::notifyMapChange(mbgl::MapChange change) {
+ if (mapChangeCallback) {
+ mapChangeCallback(change);
+ }
+}
+
+namespace mbgl {
+namespace platform {
+
+#ifndef GL_ES_VERSION_2_0
+void showDebugImage(std::string name, const char *data, size_t width, size_t height) {
+ glfwInit();
+
+ static GLFWwindow *debugWindow = nullptr;
+ if (!debugWindow) {
+ debugWindow = glfwCreateWindow(width, height, name.c_str(), nullptr, nullptr);
+ if (!debugWindow) {
+ glfwTerminate();
+ fprintf(stderr, "Failed to initialize window\n");
+ exit(1);
+ }
+ }
+
+ GLFWwindow *currentWindow = glfwGetCurrentContext();
+
+ glfwSetWindowSize(debugWindow, width, height);
+ glfwMakeContextCurrent(debugWindow);
+
+ int fbWidth, fbHeight;
+ glfwGetFramebufferSize(debugWindow, &fbWidth, &fbHeight);
+ float scale = static_cast<float>(fbWidth) / static_cast<float>(width);
+
+ {
+ gl::PreserveState<gl::value::PixelZoom> pixelZoom;
+ gl::PreserveState<gl::value::RasterPos> rasterPos;
+
+ MBGL_CHECK_ERROR(glPixelZoom(scale, -scale));
+ MBGL_CHECK_ERROR(glRasterPos2f(-1.0f, 1.0f));
+ MBGL_CHECK_ERROR(glDrawPixels(width, height, GL_LUMINANCE, GL_UNSIGNED_BYTE, data));
+ }
+
+ glfwSwapBuffers(debugWindow);
+
+ glfwMakeContextCurrent(currentWindow);
+}
+
+void showColorDebugImage(std::string name, const char *data, size_t logicalWidth, size_t logicalHeight, size_t width, size_t height) {
+ glfwInit();
+
+ static GLFWwindow *debugWindow = nullptr;
+ if (!debugWindow) {
+ debugWindow = glfwCreateWindow(logicalWidth, logicalHeight, name.c_str(), nullptr, nullptr);
+ if (!debugWindow) {
+ glfwTerminate();
+ fprintf(stderr, "Failed to initialize window\n");
+ exit(1);
+ }
+ }
+
+ GLFWwindow *currentWindow = glfwGetCurrentContext();
+
+ glfwSetWindowSize(debugWindow, logicalWidth, logicalHeight);
+ glfwMakeContextCurrent(debugWindow);
+
+ int fbWidth, fbHeight;
+ glfwGetFramebufferSize(debugWindow, &fbWidth, &fbHeight);
+ float xScale = static_cast<float>(fbWidth) / static_cast<float>(width);
+ float yScale = static_cast<float>(fbHeight) / static_cast<float>(height);
+
+ {
+ gl::PreserveState<gl::value::ClearColor> clearColor;
+ gl::PreserveState<gl::value::Blend> blend;
+ gl::PreserveState<gl::value::BlendFunc> blendFunc;
+ gl::PreserveState<gl::value::PixelZoom> pixelZoom;
+ gl::PreserveState<gl::value::RasterPos> rasterPos;
+
+ MBGL_CHECK_ERROR(glClearColor(0.8, 0.8, 0.8, 1));
+ MBGL_CHECK_ERROR(glClear(GL_COLOR_BUFFER_BIT));
+ MBGL_CHECK_ERROR(glEnable(GL_BLEND));
+ MBGL_CHECK_ERROR(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
+ MBGL_CHECK_ERROR(glPixelZoom(xScale, -yScale));
+ MBGL_CHECK_ERROR(glRasterPos2f(-1.0f, 1.0f));
+ MBGL_CHECK_ERROR(glDrawPixels(width, height, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, data));
+ }
+
+ glfwSwapBuffers(debugWindow);
+
+ glfwMakeContextCurrent(currentWindow);
+}
+#endif
+
+} // namespace platform
+} // namespace mbgl
diff --git a/platform/glfw/glfw_view.hpp b/platform/glfw/glfw_view.hpp
new file mode 100644
index 0000000000..c640f188f9
--- /dev/null
+++ b/platform/glfw/glfw_view.hpp
@@ -0,0 +1,111 @@
+#pragma once
+
+#include <mbgl/mbgl.hpp>
+#include <mbgl/map/backend.hpp>
+#include <mbgl/util/run_loop.hpp>
+#include <mbgl/util/timer.hpp>
+#include <mbgl/util/geometry.hpp>
+
+#if MBGL_USE_GLES2
+#define GLFW_INCLUDE_ES2
+#endif
+#define GL_GLEXT_PROTOTYPES
+#include <GLFW/glfw3.h>
+
+class GLFWView : public mbgl::View, public mbgl::Backend {
+public:
+ GLFWView(bool fullscreen = false, bool benchmark = false);
+ ~GLFWView() override;
+
+ float getPixelRatio() const;
+
+ void setMap(mbgl::Map*);
+
+ // Callback called when the user presses the key mapped to style change.
+ // The expected action is to set a new style, different to the current one.
+ void setChangeStyleCallback(std::function<void()> callback);
+
+ void setShouldClose();
+
+ void setWindowTitle(const std::string&);
+
+ void run();
+
+ // mbgl::View implementation
+ void updateViewBinding();
+ void bind() override;
+ mbgl::Size getSize() const;
+ mbgl::Size getFramebufferSize() const;
+
+ // mbgl::Backend implementation
+ void activate() override;
+ void deactivate() override;
+ void invalidate() override;
+
+private:
+ // Window callbacks
+ static void onKey(GLFWwindow *window, int key, int scancode, int action, int mods);
+ static void onScroll(GLFWwindow *window, double xoffset, double yoffset);
+ static void onWindowResize(GLFWwindow *window, int width, int height);
+ static void onFramebufferResize(GLFWwindow *window, int width, int height);
+ static void onMouseClick(GLFWwindow *window, int button, int action, int modifiers);
+ static void onMouseMove(GLFWwindow *window, double x, double y);
+
+ // Internal
+ void report(float duration);
+
+ void setMapChangeCallback(std::function<void(mbgl::MapChange)> callback);
+ void notifyMapChange(mbgl::MapChange change) override;
+
+ mbgl::Color makeRandomColor() const;
+ mbgl::Point<double> makeRandomPoint() const;
+ static std::shared_ptr<const mbgl::SpriteImage>
+ makeSpriteImage(int width, int height, float pixelRatio);
+
+ void nextOrientation();
+
+ void addRandomPointAnnotations(int count);
+ void addRandomLineAnnotations(int count);
+ void addRandomShapeAnnotations(int count);
+ void addRandomCustomPointAnnotations(int count);
+
+ void clearAnnotations();
+ void popAnnotation();
+
+ mbgl::AnnotationIDs annotationIDs;
+ std::vector<std::string> spriteIDs;
+
+ std::function<void(mbgl::MapChange)> mapChangeCallback;
+
+private:
+ mbgl::Map* map = nullptr;
+
+ bool fullscreen = false;
+ const bool benchmark = false;
+ bool tracking = false;
+ bool rotating = false;
+ bool pitching = false;
+
+ // Frame timer
+ int frames = 0;
+ float frameTime = 0;
+ double lastReported = 0;
+
+ int width = 1024;
+ int height = 768;
+ int fbWidth;
+ int fbHeight;
+ float pixelRatio;
+
+ double lastX = 0, lastY = 0;
+
+ double lastClick = -1;
+
+ std::function<void()> changeStyleCallback;
+
+ mbgl::util::RunLoop runLoop;
+ mbgl::util::Timer frameTick;
+
+ GLFWwindow *window = nullptr;
+ bool dirty = false;
+};
diff --git a/platform/glfw/main.cpp b/platform/glfw/main.cpp
new file mode 100644
index 0000000000..6e4b324c43
--- /dev/null
+++ b/platform/glfw/main.cpp
@@ -0,0 +1,190 @@
+#include "glfw_view.hpp"
+#include "settings_json.hpp"
+
+#include <mbgl/util/default_styles.hpp>
+#include <mbgl/platform/log.hpp>
+#include <mbgl/platform/platform.hpp>
+#include <mbgl/platform/default/thread_pool.hpp>
+#include <mbgl/storage/default_file_source.hpp>
+
+#include <signal.h>
+#include <getopt.h>
+#include <fstream>
+#include <sstream>
+#include <cstdlib>
+#include <cstdio>
+#include <array>
+
+namespace {
+
+GLFWView* view = nullptr;
+
+}
+
+void quit_handler(int) {
+ if (view) {
+ mbgl::Log::Info(mbgl::Event::Setup, "waiting for quit...");
+ view->setShouldClose();
+ } else {
+ exit(0);
+ }
+}
+
+int main(int argc, char *argv[]) {
+ bool fullscreen = false;
+ bool benchmark = false;
+ std::string style;
+ double latitude = 0, longitude = 0;
+ double bearing = 0, zoom = 1, pitch = 0;
+ bool skipConfig = false;
+
+ const struct option long_options[] = {
+ {"fullscreen", no_argument, 0, 'f'},
+ {"benchmark", no_argument, 0, 'b'},
+ {"style", required_argument, 0, 's'},
+ {"lon", required_argument, 0, 'x'},
+ {"lat", required_argument, 0, 'y'},
+ {"zoom", required_argument, 0, 'z'},
+ {"bearing", required_argument, 0, 'r'},
+ {"pitch", required_argument, 0, 'p'},
+ {0, 0, 0, 0}
+ };
+
+ while (true) {
+ int option_index = 0;
+ int opt = getopt_long(argc, argv, "fbs:", long_options, &option_index);
+ if (opt == -1) break;
+ switch (opt)
+ {
+ case 0:
+ if (long_options[option_index].flag != 0)
+ break;
+ case 'f':
+ fullscreen = true;
+ break;
+ case 'b':
+ benchmark = true;
+ break;
+ case 's':
+ style = std::string("asset://") + std::string(optarg);
+ break;
+ case 'x':
+ longitude = atof(optarg);
+ skipConfig = true;
+ break;
+ case 'y':
+ latitude = atof(optarg);
+ skipConfig = true;
+ break;
+ case 'z':
+ zoom = atof(optarg);
+ skipConfig = true;
+ break;
+ case 'r':
+ bearing = atof(optarg);
+ skipConfig = true;
+ break;
+ case 'p':
+ pitch = atof(optarg);
+ skipConfig = true;
+ break;
+ default:
+ break;
+ }
+
+ }
+
+ // sigint handling
+ struct sigaction sigIntHandler;
+ sigIntHandler.sa_handler = quit_handler;
+ sigemptyset(&sigIntHandler.sa_mask);
+ sigIntHandler.sa_flags = 0;
+ sigaction(SIGINT, &sigIntHandler, NULL);
+
+ if (benchmark) {
+ mbgl::Log::Info(mbgl::Event::General, "BENCHMARK MODE: Some optimizations are disabled.");
+ }
+
+ GLFWView backend(fullscreen, benchmark);
+ view = &backend;
+
+ mbgl::DefaultFileSource fileSource("/tmp/mbgl-cache.db", ".");
+
+ // Set access token if present
+ const char *token = getenv("MAPBOX_ACCESS_TOKEN");
+ if (token == nullptr) {
+ mbgl::Log::Warning(mbgl::Event::Setup, "no access token set. mapbox.com tiles won't work.");
+ } else {
+ fileSource.setAccessToken(std::string(token));
+ }
+
+ mbgl::ThreadPool threadPool(4);
+
+ mbgl::Map map(backend, view->getSize(), view->getPixelRatio(), fileSource, threadPool);
+
+ backend.setMap(&map);
+
+ // Load settings
+ mbgl::Settings_JSON settings;
+
+ if (skipConfig) {
+ map.setLatLngZoom(mbgl::LatLng(latitude, longitude), zoom);
+ map.setBearing(bearing);
+ map.setPitch(pitch);
+ mbgl::Log::Info(mbgl::Event::General, "Location: %f/%f (z%.2f, %.2f deg)", latitude, longitude, zoom, bearing);
+ } else {
+ map.setLatLngZoom(mbgl::LatLng(settings.latitude, settings.longitude), settings.zoom);
+ map.setBearing(settings.bearing);
+ map.setPitch(settings.pitch);
+ map.setDebug(mbgl::MapDebugOptions(settings.debug));
+ }
+
+ view->setChangeStyleCallback([&map] () {
+ static uint8_t currentStyleIndex;
+
+ if (++currentStyleIndex == mbgl::util::default_styles::numOrderedStyles) {
+ currentStyleIndex = 0;
+ }
+
+ mbgl::util::default_styles::DefaultStyle newStyle = mbgl::util::default_styles::orderedStyles[currentStyleIndex];
+ map.setStyleURL(newStyle.url);
+ view->setWindowTitle(newStyle.name);
+
+ mbgl::Log::Info(mbgl::Event::Setup, "Changed style to: %s", newStyle.name);
+ });
+
+ // Load style
+ if (style.empty()) {
+ const char *url = getenv("MAPBOX_STYLE_URL");
+ if (url == nullptr) {
+ mbgl::util::default_styles::DefaultStyle newStyle = mbgl::util::default_styles::orderedStyles[0];
+ style = newStyle.url;
+ view->setWindowTitle(newStyle.name);
+ } else {
+ style = url;
+ view->setWindowTitle(url);
+ }
+ }
+
+ map.setStyleURL(style);
+
+ view->run();
+
+ // Save settings
+ mbgl::LatLng latLng = map.getLatLng();
+ settings.latitude = latLng.latitude;
+ settings.longitude = latLng.longitude;
+ settings.zoom = map.getZoom();
+ settings.bearing = map.getBearing();
+ settings.pitch = map.getPitch();
+ settings.debug = mbgl::EnumType(map.getDebug());
+ if (!skipConfig) {
+ settings.save();
+ }
+ mbgl::Log::Info(mbgl::Event::General,
+ "Exit location: --lat=\"%f\" --lon=\"%f\" --zoom=\"%f\" --bearing \"%f\"",
+ settings.latitude, settings.longitude, settings.zoom, settings.bearing);
+
+ view = nullptr;
+ return 0;
+}
diff --git a/platform/glfw/settings_json.cpp b/platform/glfw/settings_json.cpp
new file mode 100644
index 0000000000..2ba1038dc7
--- /dev/null
+++ b/platform/glfw/settings_json.cpp
@@ -0,0 +1,41 @@
+#include "settings_json.hpp"
+#include <fstream>
+
+namespace mbgl {
+
+Settings_JSON::Settings_JSON() { load(); }
+
+void Settings_JSON::load() {
+ std::ifstream file("/tmp/mbgl-native.cfg");
+ if (file) {
+ file >> longitude;
+ file >> latitude;
+ file >> zoom;
+ file >> bearing;
+ file >> pitch;
+ file >> debug;
+ }
+}
+
+void Settings_JSON::save() {
+ std::ofstream file("/tmp/mbgl-native.cfg");
+ if (file) {
+ file << longitude << std::endl;
+ file << latitude << std::endl;
+ file << zoom << std::endl;
+ file << bearing << std::endl;
+ file << pitch << std::endl;
+ file << debug << std::endl;
+ }
+}
+
+void Settings_JSON::clear() {
+ longitude = 0;
+ latitude = 0;
+ zoom = 0;
+ bearing = 0;
+ pitch = 0;
+ debug = 0;
+}
+
+} // namespace mbgl
diff --git a/platform/glfw/settings_json.hpp b/platform/glfw/settings_json.hpp
new file mode 100644
index 0000000000..eb23b28bc8
--- /dev/null
+++ b/platform/glfw/settings_json.hpp
@@ -0,0 +1,24 @@
+#pragma once
+
+#include <mbgl/map/mode.hpp>
+
+namespace mbgl {
+
+class Settings_JSON {
+public:
+ Settings_JSON();
+ void load();
+ void save();
+ void clear();
+
+public:
+ double longitude = 0;
+ double latitude = 0;
+ double zoom = 0;
+ double bearing = 0;
+ double pitch = 0;
+
+ EnumType debug = 0;
+};
+
+} // namespace mbgl