Files
MapLibreParticleLab/platform/default/glfw_view.cpp
2015-12-03 15:38:36 +01:00

541 lines
17 KiB
C++

#include <mbgl/annotation/point_annotation.hpp>
#include <mbgl/annotation/shape_annotation.hpp>
#include <mbgl/sprite/sprite_image.hpp>
#include <mbgl/platform/default/glfw_view.hpp>
#include <mbgl/platform/gl.hpp>
#include <mbgl/platform/log.hpp>
#include <mbgl/util/gl_helper.hpp>
#include <mbgl/util/string.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(0));
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, NULL);
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("\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 `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::initialize(mbgl::Map *map_) {
View::initialize(map_);
map->addAnnotationIcon("default_marker", makeSpriteImage(22, 22, 1));
}
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) {
view->map->setDefaultTransitionDuration(std::chrono::milliseconds(300));
if (view->map->hasClass("night")) {
view->map->removeClass("night");
} else {
view->map->addClass("night");
}
}
break;
case GLFW_KEY_N:
if (!mods)
view->map->resetNorth();
break;
case GLFW_KEY_Q:
view->clearAnnotations();
break;
case GLFW_KEY_P: {
view->addRandomCustomPointAnnotations(1);
} 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::LatLng GLFWView::makeRandomPoint() const {
const auto sw = map->latLngForPixel({ 0, 0 });
const auto ne = map->latLngForPixel({ double(width), double(height) });
const double lon = sw.longitude + (ne.longitude - sw.longitude) * (double(std::rand()) / RAND_MAX);
const double lat = sw.latitude + (ne.latitude - sw.latitude) * (double(std::rand()) / RAND_MAX);
return { lat, lon };
}
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);
std::string pixels(w * h * 4, '\x00');
auto data = reinterpret_cast<uint32_t*>(const_cast<char*>(pixels.data()));
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>(width, height, pixelRatio, std::move(pixels));
}
void GLFWView::addRandomCustomPointAnnotations(int count) {
std::vector<mbgl::PointAnnotation> points;
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);
points.emplace_back(makeRandomPoint(), name);
}
auto newIDs = map->addPointAnnotations(points);
annotationIDs.insert(annotationIDs.end(), newIDs.begin(), newIDs.end());
}
void GLFWView::addRandomPointAnnotations(int count) {
std::vector<mbgl::PointAnnotation> points;
for (int i = 0; i < count; i++) {
points.emplace_back(makeRandomPoint(), "default_marker");
}
auto newIDs = map->addPointAnnotations(points);
annotationIDs.insert(annotationIDs.end(), newIDs.begin(), newIDs.end());
}
void GLFWView::addRandomShapeAnnotations(int count) {
std::vector<mbgl::ShapeAnnotation> shapes;
mbgl::FillAnnotationProperties properties;
properties.opacity = .1;
for (int i = 0; i < count; i++) {
mbgl::AnnotationSegment triangle;
triangle.push_back(makeRandomPoint());
triangle.push_back(makeRandomPoint());
triangle.push_back(makeRandomPoint());
mbgl::AnnotationSegments segments;
segments.push_back(triangle);
shapes.emplace_back(segments, properties);
}
auto newIDs = map->addShapeAnnotations(shapes);
annotationIDs.insert(annotationIDs.end(), newIDs.begin(), newIDs.end());
}
void GLFWView::clearAnnotations() {
if (annotationIDs.empty()) {
return;
}
map->removeAnnotations(annotationIDs);
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, { 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->update(mbgl::Update::Dimensions);
}
void GLFWView::onFramebufferResize(GLFWwindow *window, int width, int height) {
GLFWView *view = reinterpret_cast<GLFWView *>(glfwGetWindowUserPointer(window));
view->fbWidth = width;
view->fbHeight = height;
view->map->update(mbgl::Update::Repaint);
}
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, { view->lastX, view->lastY }, std::chrono::milliseconds(500));
} else {
view->map->scaleBy(2.0, { view->lastX, view->lastY }, std::chrono::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) {
double flippedY = view->height - y;
view->map->setLatLng(
view->map->latLngForPixel(mbgl::PrecisionPoint(x - dx, flippedY + dy)),
mbgl::PrecisionPoint(x, flippedY));
}
} 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() {
while (!glfwWindowShouldClose(window)) {
glfwWaitEvents();
const bool dirty = !clean.test_and_set();
if (dirty) {
const double started = glfwGetTime();
map->renderSync();
report(1000 * (glfwGetTime() - started));
if (benchmark) {
map->update(mbgl::Update::Repaint);
}
}
}
}
float GLFWView::getPixelRatio() const {
return pixelRatio;
}
std::array<uint16_t, 2> GLFWView::getSize() const {
return {{ static_cast<uint16_t>(width), static_cast<uint16_t>(height) }};
}
std::array<uint16_t, 2> GLFWView::getFramebufferSize() const {
return {{ static_cast<uint16_t>(fbWidth), static_cast<uint16_t>(fbHeight) }};
}
void GLFWView::activate() {
glfwMakeContextCurrent(window);
}
void GLFWView::deactivate() {
glfwMakeContextCurrent(nullptr);
}
void GLFWView::notify() {
glfwPostEmptyEvent();
}
void GLFWView::invalidate() {
clean.clear();
glfwPostEmptyEvent();
}
void GLFWView::beforeRender() {
// no-op
}
void GLFWView::afterRender() {
glfwSwapBuffers(window);
}
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());
}
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::PreservePixelZoom pixelZoom;
gl::PreserveRasterPos 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::PreserveClearColor clearColor;
gl::PreserveBlend blend;
gl::PreserveBlendFunc blendFunc;
gl::PreservePixelZoom pixelZoom;
gl::PreserveRasterPos 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