move to libuv for the rendering loop

This commit is contained in:
Konstantin Käfer
2014-04-30 12:01:26 +02:00
parent ddc359f006
commit 1216a061d3
32 changed files with 1312 additions and 911 deletions

View File

@@ -88,10 +88,9 @@ static std::queue<CURL *> curl_handle_cache;
class CURLRequest : public llmr::platform::Request {
public:
CURLRequest(const std::string &url,
std::function<void(llmr::platform::Response *)> background_function,
std::function<void()> foreground_callback,
std::function<void(llmr::platform::Response *)> callback,
uv_loop_t *loop)
: Request(url, background_function, foreground_callback, loop) {}
: Request(url, callback, loop) {}
CURL *curl = nullptr;
};
@@ -169,16 +168,7 @@ void curl_perform(uv_poll_t *req, int /*status*/, int events) {
// We're currently in the CURL request thread. We're going to schedule a uv_work request
// that executes the background function in a threadpool, and tell it to call the
// after callback back in the main uv loop.
uv_work_t *work = new uv_work_t();
// We're passing on the pointer we created to the work structure.
// It is going to be deleted in the after_work_cb;
work->data = req;
// Executes the background_function in a libuv thread pool, and the after_work_cb back
// in the *main* event loop.
uv_queue_work((*req)->loop, work, Request::work_callback, Request::after_work_callback);
(*req)->complete();
CURL *handle = message->easy_handle;
remove_curl_handle(handle);
@@ -187,6 +177,8 @@ void curl_perform(uv_poll_t *req, int /*status*/, int events) {
// be cancelled.
((CURLRequest *)req->get())->curl = nullptr;
// Delete the shared_ptr pointer we created earlier.
delete req;
break;
}
@@ -312,8 +304,6 @@ void async_add_cb(uv_async_t * /*async*/) {
continue;
}
(*req)->res = std::make_unique<Response>();
// Obtain a curl handle (and try to reuse existing handles before creating new ones).
CURL *handle = nullptr;
if (!curl_handle_cache.empty()) {
@@ -367,12 +357,11 @@ void thread_init_cb() {
std::shared_ptr<platform::Request>
platform::request_http(const std::string &url,
std::function<void(Response *)> background_function,
std::function<void()> foreground_callback,
std::function<void(Response *)> callback,
uv_loop_t *loop) {
using namespace request;
init_thread_once(thread_init_cb);
std::shared_ptr<CURLRequest> req = std::make_shared<CURLRequest>(url, background_function, foreground_callback, loop);
std::shared_ptr<CURLRequest> req = std::make_shared<CURLRequest>(url, callback, loop);
// Note that we are creating a new shared_ptr pointer(!) because the lockless queue can't store
// objects with nontrivial destructors. We have to make absolutely sure that we manually delete

View File

@@ -40,10 +40,9 @@ void request_initialize_cb() {
class FoundationRequest : public llmr::platform::Request {
public:
FoundationRequest(const std::string &url,
std::function<void(llmr::platform::Response *)> background_function,
std::function<void()> foreground_callback,
std::function<void(llmr::platform::Response *)> callback,
uv_loop_t *loop)
: Request(url, background_function, foreground_callback, loop) {
: Request(url, callback, loop) {
#if TARGET_OS_IPHONE
active_tasks++;
dispatch_async(dispatch_get_main_queue(), ^(void) {
@@ -68,13 +67,12 @@ public:
std::shared_ptr<llmr::platform::Request>
llmr::platform::request_http(const std::string &url,
std::function<void(Response *)> background_function,
std::function<void()> foreground_callback,
std::function<void(Response *)> callback,
uv_loop_t *loop) {
uv_once(&request_initialize, request_initialize_cb);
std::shared_ptr<FoundationRequest> req =
std::make_shared<FoundationRequest>(url, background_function, foreground_callback, loop);
std::make_shared<FoundationRequest>(url, callback, loop);
// Note that we are creating a new shared_ptr pointer(!) to make sure there is at least one
// shared_ptr in existence while the NSURLSession is loading our data. We are making sure in the
@@ -93,8 +91,6 @@ llmr::platform::request_http(const std::string &url,
return;
}
(*req_ptr)->res = std::make_unique<Response>();
if (!error && [response isKindOfClass:[NSHTTPURLResponse class]]) {
(*req_ptr)->res->code = [(NSHTTPURLResponse *)response statusCode];
(*req_ptr)->res->body = {(const char *)[data bytes], [data length]};
@@ -102,19 +98,9 @@ llmr::platform::request_http(const std::string &url,
(*req_ptr)->res->error_message = [[error localizedDescription] UTF8String];
}
// We're currently in the request thread. We're going to schedule a uv_work request that
// executes the background function in a threadpool, and tell it to call the after callback
// back in the main uv loop.
uv_work_t *work = new uv_work_t();
// We're passing on the pointer we created to the work structure. It is going to be deleted
// in the after_work_cb;
work->data = req_ptr;
// Executes the background_function in a libuv thread pool, and the after_work_cb back in
// the *main* event loop.
uv_queue_work((*req_ptr)->loop, work, Request::work_callback,
Request::after_work_callback);
(*req_ptr)->complete();
delete req_ptr;
}];
req->task = task;

View File

@@ -1,39 +1,7 @@
#include "glfw_view.hpp"
#ifndef HAVE_KQUEUE
# if defined(__APPLE__) || \
defined(__DragonFly__) || \
defined(__FreeBSD__) || \
defined(__OpenBSD__) || \
defined(__NetBSD__)
# define HAVE_KQUEUE 1
# endif
#endif
#ifndef HAVE_EPOLL
# if defined(__linux__)
# define HAVE_EPOLL 1
# endif
#endif
#if defined(HAVE_KQUEUE) || defined(HAVE_EPOLL)
#if defined(HAVE_KQUEUE)
# include <sys/types.h>
# include <sys/event.h>
# include <sys/time.h>
#endif
#if defined(HAVE_EPOLL)
# include <sys/epoll.h>
#endif
#endif
MapView::MapView(llmr::Settings &settings, bool fullscreen)
: fullscreen(fullscreen), settings(settings), map(settings) {}
: fullscreen(fullscreen), settings(settings), map(*this) {}
MapView::~MapView() { glfwTerminate(); }
@@ -77,20 +45,17 @@ void MapView::init() {
glfwGetFramebufferSize(window, &fb_width, &fb_height);
settings.load();
map.setup();
resize(window, 0, 0);
glfwSwapInterval(1);
map.loadSettings();
glfwSetCursorPosCallback(window, mousemove);
glfwSetMouseButtonCallback(window, mouseclick);
glfwSetWindowSizeCallback(window, resize);
glfwSetFramebufferSizeCallback(window, resize);
glfwSetScrollCallback(window, scroll);
glfwSetKeyCallback(window, key);
glfwMakeContextCurrent(nullptr);
}
void MapView::key(GLFWwindow *window, int key, int /*scancode*/, int action, int mods) {
@@ -152,7 +117,6 @@ void MapView::resize(GLFWwindow *window, int, int) {
glfwGetFramebufferSize(window, &fb_width, &fb_height);
mapView->map.resize(width, height, (float)fb_width / (float)width, fb_width, fb_height);
mapView->map.update();
}
void MapView::mouseclick(GLFWwindow *window, int button, int action, int modifiers) {
@@ -199,17 +163,11 @@ int MapView::run() {
map.start();
while (!glfwWindowShouldClose(window)) {
if (map.clean.test_and_set() == false) {
// This branch is executed when the previous value of the "clean"
// flag is false (i.e. it is unclean == dirty).
try {
map.render();
}
catch (std::exception &ex) {
fprintf(stderr, "exception: %s\n", ex.what());
}
if (map.swapped.test_and_set() == false) {
glfwSwapBuffers(window);
map.rendered.clear();
fps();
map.rerender();
}
glfwWaitEvents();
@@ -220,6 +178,14 @@ int MapView::run() {
return 0;
}
void MapView::make_active() {
glfwMakeContextCurrent(window);
}
void MapView::swap() {
glfwPostEmptyEvent();
}
void MapView::fps() {
static int frames = 0;
static double time_elapsed = 0;
@@ -267,9 +233,5 @@ void show_debug_image(std::string name, const char *data, size_t width, size_t h
glfwMakeContextCurrent(current_window);
}
void restart() {
glfwPostEmptyEvent();
}
}
}

View File

@@ -5,13 +5,16 @@
#include <GLFW/glfw3.h>
#include <uv.h>
class MapView {
class MapView : public llmr::View {
public:
MapView(llmr::Settings &settings, bool fullscreen = false);
~MapView();
void init();
void swap();
void make_active();
static void key(GLFWwindow *window, int key, int scancode, int action, int mods);
static void scroll(GLFWwindow *window, double xoffset, double yoffset);
static void resize(GLFWwindow *window, int, int);

View File

@@ -2,6 +2,7 @@
#define LLMR_MAIN
#include "map/map.hpp"
#include "map/view.hpp"
#include "map/settings.hpp"
#endif

View File

@@ -3,8 +3,9 @@
#include <uv.h>
#include <llmr/map/tile.hpp>
#include <llmr/map/tile_data.hpp>
#include <llmr/map/view.hpp>
// #include <llmr/map/tile.hpp>
// #include <llmr/map/tile_data.hpp>
#include <llmr/map/transform.hpp>
#include <llmr/style/style.hpp>
#include <llmr/geometry/glyph_atlas.hpp>
@@ -18,31 +19,34 @@
namespace llmr {
class Settings;
class Source;
class Map : private util::noncopyable {
public:
explicit Map(Settings& settings);
explicit Map(View &view);
~Map();
// Start/stop the map render thread
void start();
void run();
void stop();
/* setup */
void setup();
void loadStyle(const uint8_t *const data, uint32_t bytes);
void loadSettings();
// Runs the map event loop.
void run();
// Triggers a lazy rerender: only performs a render when the map is not clean.
void rerender();
// Forces a map update: always triggers a rerender.
void update();
// Size
void resize(uint16_t width, uint16_t height, float ratio = 1);
void resize(uint16_t width, uint16_t height, float ratio, uint16_t fb_width, uint16_t fb_height);
/* callback */
void update();
void render();
// Animations
void cancelAnimations();
/* position */
// Position
void moveBy(double dx, double dy, double duration = 0);
void setLonLat(double lon, double lat, double duration = 0);
void getLonLat(double &lon, double &lat) const;
@@ -50,7 +54,7 @@ public:
void stopPanning();
void resetPosition();
/* scale */
// Scale
void scaleBy(double ds, double cx = -1, double cy = -1, double duration = 0);
void setScale(double scale, double cx = -1, double cy = -1, double duration = 0);
double getScale() const;
@@ -62,39 +66,63 @@ public:
void startScaling();
void stopScaling();
/* rotation */
// Rotation
void rotateBy(double sx, double sy, double ex, double ey, double duration = 0);
void setAngle(double angle, double cx = -1, double cy = -1, double duration = 0);
void setAngle(double angle, double cx = -1, double cy = -1);
double getAngle() const;
void resetNorth();
void startRotating();
void stopRotating();
void redraw();
// Toggles
void setDebug(bool value);
void toggleDebug();
bool getDebug() const;
void toggleRaster();
box cornersToBox(uint32_t z) const;
public: // Getters
float getPixelRatio() const;
Style& getStyle();
GlyphAtlas& getGlyphAtlas();
uv_loop_t *getLoop();
public:
inline const TransformState &getState() const { return state; }
inline const Style &getStyle() const { return style; }
inline GlyphAtlas &getGlyphAtlas() { return glyphAtlas; }
inline uv_loop_t *getLoop() { return loop; }
private:
bool updateTiles();
static void eventloop(void *arg);
// uv async callbacks
static void render(uv_async_t *async);
static void terminate(uv_async_t *async);
static void delete_async(uv_handle_t *handle);
// Setup
void setup();
void loadStyle(const uint8_t *const data, uint32_t bytes);
void updateTiles();
// Prepares a map render by updating the tiles we need for the current view, as well as updating
// the stylesheet.
void prepare();
// Unconditionally performs a render with the current map state.
void render();
public:
// If cleared, the next time the render thread attempts to render the map, it will *actually*
// render the map.
std::atomic_flag clean = ATOMIC_FLAG_INIT;
// If this flag is cleared, the current back buffer is ready for being swapped with the front
// buffer (i.e. it has rendered data).
std::atomic_flag swapped = ATOMIC_FLAG_INIT;
// This is cleared once the current front buffer has been presented and the back buffer is
// ready for rendering.
std::atomic_flag rendered = ATOMIC_FLAG_INIT;
private:
Settings& settings;
View &view;
Transform transform;
TransformState state;
Texturepool texturepool;
Style style;
GlyphAtlas glyphAtlas;
@@ -102,10 +130,15 @@ private:
std::map<std::string, const std::unique_ptr<Source>> sources;
bool debug = false;
double animationTime = 0;
private:
bool async = false;
uv_loop_t *loop = nullptr;
uv_thread_t thread;
uv_async_t async_terminate;
uv_async_t *async_terminate = nullptr;
uv_async_t *async_render = nullptr;
};
}

View File

@@ -13,7 +13,7 @@
namespace llmr {
class Map;
class Transform;
class TransformState;
class Painter;
class Texturepool;
@@ -25,15 +25,18 @@ public:
};
public:
Source(Map &map, Transform &transform, Painter &painter, Texturepool &texturepool,
Source(Map &map, Painter &painter, Texturepool &texturepool,
const char *url = "", Type type = Type::vector, std::vector<uint32_t> zooms = {0},
uint32_t tile_size = 512, uint32_t min_zoom = 0, uint32_t max_zoom = 14,
bool enabled = true);
bool update();
void prepare_render(bool is_baselayer = false);
void prepare_render(const TransformState &transform, bool is_baselayer = false);
void render(double animationTime);
public:
bool enabled;
@@ -51,7 +54,6 @@ private:
private:
Map& map;
Transform& transform;
Painter& painter;
Texturepool& texturepool;

View File

@@ -59,7 +59,8 @@ public:
~TileData();
void request();
bool parse();
void scheduleParsing();
void parse();
void cancel();
const std::string toString() const;

View File

@@ -1,11 +1,11 @@
#ifndef LLMR_MAP_TRANSFORM
#define LLMR_MAP_TRANSFORM
#include <llmr/util/vec.hpp>
#include <llmr/util/mat4.hpp>
#include <llmr/util/animation.hpp>
#include <llmr/util/noncopyable.hpp>
#include <llmr/map/tile.hpp>
#include <llmr/util/uv.hpp>
#include "transform_state.hpp"
#include <forward_list>
@@ -17,82 +17,72 @@ struct box;
public:
Transform();
// Map view
// Note: width * ratio does not necessarily equal fb_width
bool resize(uint16_t width, uint16_t height, float ratio,
uint16_t fb_width, uint16_t fb_height);
// Position
void moveBy(double dx, double dy, double duration = 0);
void setLonLat(double lon, double lat, double duration = 0);
void setLonLatZoom(double lon, double lat, double zoom, double duration = 0);
void getLonLat(double& lon, double& lat) const;
void getLonLatZoom(double& lon, double& lat, double& zoom) const;
void startPanning();
void stopPanning();
// Zoom
void scaleBy(double ds, double cx = -1, double cy = -1, double duration = 0);
void setScale(double scale, double cx = -1, double cy = -1, double duration = 0);
void setZoom(double zoom, double duration = 0);
double getZoom() const;
double getScale() const;
void startRotating();
void stopRotating();
// Angle
void rotateBy(double sx, double sy, double ex, double ey, double duration = 0);
void setAngle(double angle, double duration = 0);
void setAngle(double angle, double cx, double cy);
double getAngle() const;
void startScaling();
void stopScaling();
// Animations
bool needsAnimation() const;
void updateAnimations(double time);
void cancelAnimations();
// Map view changes.
// Note: width * ratio does not necessarily equal fb_width
void resize(uint16_t width, uint16_t height, float ratio, uint16_t fb_width, uint16_t fb_height);
// Relative changes
void moveBy(double dx, double dy, double duration = 0);
void scaleBy(double ds, double cx = -1, double cy = -1, double duration = 0);
void rotateBy(double sx, double sy, double ex, double ey, double duration = 0);
// Absolute changes
void setScale(double scale, double cx = -1, double cy = -1, double duration = 0);
void setAngle(double angle, double duration = 0);
void setZoom(double zoom, double duration = 0);
void setLonLat(double lon, double lat, double duration = 0);
void setLonLatZoom(double lon, double lat, double zoom, double duration = 0);
// Getters
void matrixFor(mat4& matrix, const Tile::ID& id) const;
float getZoom() const;
float getNormalizedZoom() const;
int32_t getIntegerZoom() const;
double getScale() const;
double getAngle() const;
void getLonLat(double& lon, double& lat) const;
void getLonLatZoom(double& lon, double& lat, double& zoom) const;
// Animations
void startPanning();
void stopPanning();
void startRotating();
void stopRotating();
void startScaling();
void stopScaling();
box cornersToBox(uint32_t z) const;
// More getters
inline uint16_t getWidth() const { return width; }
inline uint16_t getHeight() const { return height; }
inline uint16_t getFramebufferWidth() const { return fb_width; }
inline uint16_t getFramebufferHeight() const { return fb_height; }
inline float getPixelRatio() const { return pixelRatio; }
// Transform state
const TransformState currentState() const;
const TransformState finalState() const;
private:
void setScaleXY(double new_scale, double xn, double yn, double duration = 0);
double pixel_x() const;
double pixel_y() const;
// Functions prefixed with underscores will *not* perform any locks. It is the caller's
// responsibility to lock this object.
void _moveBy(double dx, double dy, double duration = 0);
void _setScale(double scale, double cx, double cy, double duration = 0);
void _setScaleXY(double new_scale, double xn, double yn, double duration = 0);
void _setAngle(double angle, double duration = 0);
void _clearPanning();
void _clearRotating();
void _clearScaling();
private:
// logical dimensions
uint16_t width = 0;
uint16_t height = 0;
mutable uv::rwlock mtx;
// physical (framebuffer) dimensions
uint16_t fb_width = 0;
uint16_t fb_height = 0;
// This reflects the current state of the transform, representing the actual position of the
// map. After calling a transform function with a timer, this will likely remain the same until
// you render a new frame.
TransformState current;
float pixelRatio = 1;
// This reflects the final position of the transform, after all possible animations took place.
TransformState final;
public:
bool rotating = false;
bool scaling = false;
bool panning = false;
private:
double x = 0, y = 0; // pixel values of the map center in the current scale
double angle = 0;
double scale = 1;
double min_scale = pow(2, 0);
double max_scale = pow(2, 20);
// Limit the amount of zooming possible on the map.
// TODO: make these modifiable from outside.
const double min_scale = pow(2, 0);
const double max_scale = pow(2, 20);
// cache values for spherical mercator math
double zc, Bc, Cc;

View File

@@ -0,0 +1,68 @@
#ifndef LLMR_MAP_TRANSFORM_STATE
#define LLMR_MAP_TRANSFORM_STATE
#include <llmr/util/mat4.hpp>
#include <llmr/map/tile.hpp>
#include <cstdint>
namespace llmr {
class Transform;
class TransformState {
friend class Transform;
public:
// Matrix
void matrixFor(mat4& matrix, const Tile::ID& id) const;
box cornersToBox(uint32_t z) const;
// Dimensions
uint16_t getWidth() const;
uint16_t getHeight() const;
uint16_t getFramebufferWidth() const;
uint16_t getFramebufferHeight() const;
const std::array<uint16_t, 2> getFramebufferDimensions() const;
float getPixelRatio() const;
// Zoom
float getNormalizedZoom() const;
int32_t getIntegerZoom() const;
double getZoom() const;
double getScale() const;
// Rotation
float getAngle() const;
// Changing
bool isChanging() const;
private:
double pixel_x() const;
double pixel_y() const;
private:
// logical dimensions
uint16_t width = 0, height = 0;
// physical (framebuffer) dimensions
std::array<uint16_t, 2> framebuffer = {{ 0, 0 }};
// map scale factor
float pixelRatio = 0;
// animation state
bool rotating = false;
bool scaling = false;
bool panning = false;
// map position
double x = 0, y = 0;
double angle = 0;
double scale = 1;
};
}
#endif

19
include/llmr/map/view.hpp Normal file
View File

@@ -0,0 +1,19 @@
#ifndef LLMR_MAP_VIEW
#define LLMR_MAP_VIEW
namespace llmr {
class View {
public:
// Called from the render (=GL) thread. Signals that the context should
// swap the front and the back buffer.
virtual void swap() = 0;
// Called from the render thread. Makes the GL context active in the current
// thread. This is typically just called once at the beginning of the
// renderer setup since the render thread doesn't switch the contexts.
virtual void make_active() = 0;
};
}
#endif

View File

@@ -13,34 +13,27 @@ extern const char *kSpriteURL;
namespace platform {
// Restarts painting. This could for example trigger the event loop of the controlling application.
void restart();
class Request;
struct Response {
Response(std::function<void(Response *)> callback) : callback(callback) {}
int16_t code = -1;
std::string body;
std::string error_message;
std::function<void(Response *)> callback;
};
// Makes an HTTP request of a URL on a background thread, calls a function with the results in
// another thread, then calls the foreground callback in the original main thread.
// Returns a cancellable request.
std::shared_ptr<Request> request_http(const std::string &url,
std::function<void(Response *)> background_function,
std::function<void()> foreground_callback,
std::function<void(Response *)> callback,
uv_loop_t *loop);
// Cancels an HTTP request.
void cancel_request_http(const std::shared_ptr<Request> &req);
// Returns a relative timestamp in seconds. This value must be monotonic.
double elapsed();
// Shows an RGBA image with the specified dimensions in a named window.
void show_debug_image(std::string name, const char *data, size_t width, size_t height);

View File

@@ -4,13 +4,13 @@
#include <string>
#include <functional>
#include <memory>
#include <atomic>
#include <llmr/util/noncopyable.hpp>
// Forward definition.
typedef struct uv_work_s uv_work_t;
typedef struct uv_check_s uv_check_t;
typedef struct uv_loop_s uv_loop_t;
typedef struct uv_async_s uv_async_t;
namespace llmr {
namespace platform {
@@ -20,24 +20,22 @@ struct Response;
class Request : public std::enable_shared_from_this<Request>, private util::noncopyable {
public:
Request(const std::string &url,
std::function<void(Response *)> background_function,
std::function<void()> foreground_callback,
std::function<void(Response *)> callback,
uv_loop_t *loop);
~Request();
public:
static void work_callback(uv_work_t *work);
static void after_work_callback(uv_work_t *work, int status);
void complete();
private:
static void complete(uv_async_t *async);
public:
const std::string url;
const std::function<void(Response *)> background_function;
const std::function<void()> foreground_callback;
std::unique_ptr<Response> res;
bool cancelled = false;
std::atomic<bool> cancelled;
public:
uv_check_t *check = nullptr;
uv_async_t *async = nullptr;
uv_loop_t *loop = nullptr;
};
}

View File

@@ -17,6 +17,8 @@
#include <llmr/shader/raster_shader.hpp>
#include <llmr/shader/text_shader.hpp>
#include <llmr/map/transform_state.hpp>
namespace llmr {
class Transform;
@@ -32,7 +34,8 @@ class RasterBucket;
class Painter : private util::noncopyable {
public:
Painter(Transform& transform, Style& style, GlyphAtlas& glyphAtlas);
Painter(Map &map);
void setup();
void clear();
@@ -72,9 +75,7 @@ public:
mat4 extrudeMatrix;
private:
Transform& transform;
Style& style;
GlyphAtlas& glyphAtlas;
Map& map;
FrameHistory frameHistory;
@@ -83,6 +84,7 @@ private:
uint32_t gl_program = 0;
float gl_lineWidth = 0;
bool gl_depthMask = true;
std::array<uint16_t, 2> gl_viewport = {{ 0, 0 }};
float strata = 0;
const float strata_epsilon = 1.0f / (1 << 16);
enum { Opaque, Translucent } pass = Opaque;

View File

@@ -41,18 +41,22 @@ public:
ImagePosition getPosition(const std::string& name, bool repeating = false);
operator bool() const;
bool isLoaded() const;
public:
const float pixelRatio;
std::shared_ptr<Raster> raster;
private:
void parseJSON(const std::string& data);
void asyncParseJSON();
static void parseJSON(std::shared_ptr<Sprite> &sprite);
static void complete(std::shared_ptr<Sprite> &sprite);
private:
Map &map;
mutable std::mutex mtx;
std::string body;
bool loaded = false;
std::map<std::string, SpritePosition> pos;
};

View File

@@ -0,0 +1,42 @@
#ifndef LLMR_UTIL_TIME
#define LLMR_UTIL_TIME
#include <uv.h>
namespace llmr {
typedef uint64_t time;
namespace util {
// Returns time in nanoseconds since an arbitrary point in the past. This has
// no relation to the current time, but is continuously increasing, so
// comparisons between different timestamps produce meaningful values.
inline time now() {
return uv_hrtime();
}
}
// used for time conversions
constexpr time operator"" _nanoseconds (long double ns) { return ns; }
constexpr time operator"" _nanosecond (long double ns) { return ns; }
constexpr time operator"" _microseconds (long double us) { return us * 1e3; }
constexpr time operator"" _microsecond (long double us) { return us * 1e3; }
constexpr time operator"" _milliseconds (long double ms) { return ms * 1e6; }
constexpr time operator"" _millisecond (long double ms) { return ms * 1e6; }
constexpr time operator"" _seconds (long double s) { return s * 1e9; }
constexpr time operator"" _second (long double s) { return s * 1e9; }
constexpr time operator"" _nanoseconds (unsigned long long ns) { return ns; }
constexpr time operator"" _nanosecond (unsigned long long ns) { return ns; }
constexpr time operator"" _microseconds (unsigned long long us) { return us * 1e3; }
constexpr time operator"" _microsecond (unsigned long long us) { return us * 1e3; }
constexpr time operator"" _milliseconds (unsigned long long ms) { return ms * 1e6; }
constexpr time operator"" _millisecond (unsigned long long ms) { return ms * 1e6; }
constexpr time operator"" _seconds (unsigned long long s) { return s * 1e9; }
constexpr time operator"" _second (unsigned long long s) { return s * 1e9; }
}
#endif

View File

@@ -8,7 +8,6 @@
namespace uv {
typedef void (*callback)();
class mutex {
public:
@@ -16,7 +15,6 @@ public:
inline ~mutex() { uv_mutex_destroy(&mtx); }
inline void lock() { uv_mutex_lock(&mtx); }
inline void unlock() { uv_mutex_unlock(&mtx); }
inline bool trylock() { return uv_mutex_trylock(&mtx); }
private:
uv_mutex_t mtx;
@@ -31,8 +29,40 @@ private:
mutex &mtx;
};
class rwlock {
public:
inline rwlock() { assert(uv_rwlock_init(&mtx) == 0); }
inline ~rwlock() { uv_rwlock_destroy(&mtx); }
inline void rdlock() { uv_rwlock_rdlock(&mtx); }
inline void wrlock() { uv_rwlock_wrlock(&mtx); }
inline void rdunlock() { uv_rwlock_rdunlock(&mtx); }
inline void wrunlock() { uv_rwlock_wrunlock(&mtx); }
private:
uv_rwlock_t mtx;
};
class readlock {
public:
inline readlock(rwlock &mtx) : mtx(mtx) { mtx.rdlock(); }
inline ~readlock() { mtx.rdunlock(); }
private:
rwlock &mtx;
};
class writelock {
public:
inline writelock(rwlock &mtx) : mtx(mtx) { mtx.wrlock(); }
inline ~writelock() { mtx.wrunlock(); }
private:
rwlock &mtx;
};
class once {
public:
typedef void (*callback)();
void operator()(void (*callback)(void)) {
uv_once(&o, callback);
}
@@ -41,6 +71,40 @@ private:
uv_once_t o = UV_ONCE_INIT;
};
template <typename T>
class work {
public:
typedef void (*work_callback)(T &object);
typedef void (*after_work_callback)(T &object);
template<typename... Args>
work(uv_loop_t *loop, work_callback work_cb, after_work_callback after_work_cb, Args&&... args)
: data(std::forward<Args>(args)...),
work_cb(work_cb),
after_work_cb(after_work_cb) {
req.data = this;
uv_queue_work(loop, &req, do_work, after_work);
}
private:
static void do_work(uv_work_t *req) {
work<T> *w = static_cast<work<T> *>(req->data);
w->work_cb(w->data);
}
static void after_work(uv_work_t *req, int) {
work<T> *w = static_cast<work<T> *>(req->data);
w->after_work_cb(w->data);
delete w;
}
private:
uv_work_t req;
T data;
work_callback work_cb;
after_work_callback after_work_cb;
};
}
#endif

View File

@@ -1,6 +1,5 @@
#import <UIKit/UIKit.h>
#import <GLKit/GLKit.h>
@interface MBXViewController : GLKViewController
@interface MBXViewController : UIViewController
@end

View File

@@ -6,6 +6,7 @@
#import <OpenGLES/EAGL.h>
#import <QuartzCore/QuartzCore.h>
#import <GLKit/GLKit.h>
#include <llmr/llmr.hpp>
#include <llmr/platform/platform.hpp>
@@ -25,19 +26,18 @@
@implementation MBXViewController
class View;
llmr::Map *map = nullptr;
View *viewObj = nullptr;
llmr::Settings_iOS *settings = nullptr;
MBXViewController *viewController = nullptr;
#pragma mark - Setup
- (void)viewDidLoad
- (void)loadView
{
[super viewDidLoad];
viewController = self;
self.preferredFramesPerSecond = 60;
[super loadView];
self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
@@ -46,29 +46,34 @@ MBXViewController *viewController = nullptr;
NSLog(@"Failed to create OpenGL ES context");
}
GLKView *view = (GLKView *)self.view;
view.context = self.context;
GLKView *view = [[GLKView alloc] initWithFrame:[[UIScreen mainScreen] bounds] context:self.context];
view.enableSetNeedsDisplay = NO;
view.drawableStencilFormat = GLKViewDrawableStencilFormat8;
view.drawableDepthFormat = GLKViewDrawableDepthFormat16;
[view bindDrawable];
self.view = view;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self setupMap];
[self setupInteraction];
[self setupDebugUI];
map->start();
}
- (void)setupMap
{
[EAGLContext setCurrentContext:self.context];
settings = new llmr::Settings_iOS();
settings->load();
map = new llmr::Map(*settings);
map->setup();
map->loadSettings();
viewObj = new View(self);
map = new llmr::Map(*viewObj);
}
@@ -146,26 +151,23 @@ MBXViewController *viewController = nullptr;
#pragma mark - Rendering delegates
- (void)viewWillLayoutSubviews
- (void)swap
{
// This callback is called whenever the view dimensions change. We trigger an update to have a fresh render to display
// while the view change is transitioned.
[super viewWillLayoutSubviews];
// TODO: Pause all current animations during animation. We don't want other events to fire while the view is rotated.
self.paused = NO;
if (map->swapped.test_and_set() == false)
{
[(GLKView *)self.view display];
map->rendered.clear();
map->rerender();
}
}
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
- (void)viewWillLayoutSubviews
{
// Render the map
map->resize(rect.size.width, rect.size.height, view.contentScaleFactor, view.drawableWidth, view.drawableHeight);
[super viewWillLayoutSubviews];
if (map->render() == false)
{
self.paused = YES;
settings->sync();
}
GLKView *view = (GLKView *)self.view;
CGRect rect = [view bounds];
map->resize(rect.size.width, rect.size.height, view.contentScaleFactor, view.drawableWidth, view.drawableHeight);
}
#pragma mark - Debugging UI
@@ -211,7 +213,7 @@ MBXViewController *viewController = nullptr;
- (void)toggleRaster
{
map->toggleRaster();
map->toggleRaster();
}
#pragma mark - Destruction
@@ -224,6 +226,12 @@ MBXViewController *viewController = nullptr;
map = nullptr;
}
if (viewObj)
{
delete viewObj;
viewObj = nullptr;
}
if (settings)
{
settings->sync();
@@ -270,8 +278,6 @@ MBXViewController *viewController = nullptr;
map->moveBy(finalCenter.x - self.center.x, finalCenter.y - self.center.y, duration);
}
self.paused = NO;
}
- (void)handlePinchGesture:(UIPinchGestureRecognizer *)pinch
@@ -324,8 +330,6 @@ MBXViewController *viewController = nullptr;
{
map->stopScaling();
}
self.paused = NO;
}
- (void)handleRotateGesture:(UIRotationGestureRecognizer *)rotate
@@ -346,8 +350,6 @@ MBXViewController *viewController = nullptr;
{
map->stopRotating();
}
self.paused = NO;
}
- (void)handleSingleTapGesture:(UITapGestureRecognizer *)singleTap
@@ -366,8 +368,6 @@ MBXViewController *viewController = nullptr;
{
map->scaleBy(2, [doubleTap locationInView:doubleTap.view].x, [doubleTap locationInView:doubleTap.view].y, 0.5);
}
self.paused = NO;
}
- (void)handleTwoFingerTapGesture:(UITapGestureRecognizer *)twoFingerTap
@@ -378,8 +378,6 @@ MBXViewController *viewController = nullptr;
{
map->scaleBy(0.5, [twoFingerTap locationInView:twoFingerTap.view].x, [twoFingerTap locationInView:twoFingerTap.view].y, 0.5);
}
self.paused = NO;
}
- (void)handleQuickZoomGesture:(UILongPressGestureRecognizer *)quickZoom
@@ -400,8 +398,6 @@ MBXViewController *viewController = nullptr;
map->scaleBy(powf(2, newZoom) / map->getScale(), self.view.bounds.size.width / 2, self.view.bounds.size.height / 2);
}
self.paused = NO;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
@@ -411,40 +407,21 @@ MBXViewController *viewController = nullptr;
return ([validSimultaneousGestures containsObject:[gestureRecognizer class]] && [validSimultaneousGestures containsObject:[otherGestureRecognizer class]]);
}
@end
class View : public llmr::View {
public:
View(MBXViewController *controller) : controller(controller) {}
virtual ~View() {}
namespace llmr
{
namespace platform
{
class Request
{
public:
int16_t identifier;
std::string original_url;
};
void restart()
{
if ([NSThread isMainThread])
{
viewController.paused = NO;
}
else
{
[viewController performSelectorOnMainThread:@selector(setPaused:) withObject:@(NO) waitUntilDone:NO];
}
}
double time()
{
return [viewController timeSinceFirstResume];
}
double elapsed()
{
return CACurrentMediaTime();
}
void make_active() {
[EAGLContext setCurrentContext:controller.context];
}
}
void swap() {
[controller performSelectorOnMainThread:@selector(swap) withObject:nil waitUntilDone:NO];
}
private:
MBXViewController *controller = nullptr;
};
@end

View File

@@ -9,11 +9,11 @@
MapView *mapView = nullptr;
void quit_handler(int s) {
void quit_handler(int) {
if (mapView) {
fprintf(stderr, "waiting for quit...\n");
glfwSetWindowShouldClose(mapView->window, true);
llmr::platform::restart();
glfwPostEmptyEvent();
} else {
exit(0);
}

View File

@@ -1,65 +1,130 @@
#include <llmr/map/map.hpp>
#include <llmr/map/settings.hpp>
#include <llmr/map/source.hpp>
#include <llmr/platform/platform.hpp>
#include <llmr/style/resources.hpp>
#include <llmr/style/sprite.hpp>
#include <llmr/util/animation.hpp>
#include <llmr/util/time.hpp>
#include <algorithm>
#include <memory>
using namespace llmr;
Map::Map(Settings& settings)
: settings(settings),
Map::Map(View& view)
: view(view),
transform(),
texturepool(),
style(),
glyphAtlas(1024, 1024),
painter(transform, style, glyphAtlas),
painter(*this),
loop(uv_loop_new()) {
// Make sure that we're doing an initial drawing in all cases.
clean.clear();
rendered.clear();
swapped.test_and_set();
}
Map::~Map() {
settings.sync();
}
void Map::start() {
uv_async_init(loop, &async_terminate, [](uv_async_t *async) {
uv_stop(static_cast<uv_loop_t *>(async->data));
});
async_terminate.data = loop;
uv_thread_create(&thread, eventloop, this);
}
void Map::stop() {
uv_async_send(&async_terminate);
uv_thread_join(&thread);
uv_loop_delete(loop);
loop = nullptr;
}
void Map::eventloop(void *arg) {
Map *map = static_cast<Map *>(arg);
map->run();
void Map::start() {
// When starting map rendering in another thread, we perform async/continuously
// updated rendering. Only in these cases, we attach the async handlers.
async = true;
// Setup async notifications
async_terminate = new uv_async_t();
uv_async_init(loop, async_terminate, terminate);
async_terminate->data = loop;
async_render = new uv_async_t();
uv_async_init(loop, async_render, render);
async_render->data = this;
uv_thread_create(&thread, [](void *arg) {
Map *map = static_cast<Map *>(arg);
map->run();
}, this);
}
void Map::stop() {
uv_async_send(async_terminate);
uv_thread_join(&thread);
uv_close((uv_handle_t *)async_terminate, delete_async);
async_terminate = nullptr;
uv_close((uv_handle_t *)async_render, delete_async);
async_render = nullptr;
// Run the event loop once to make sure our async delete handlers are called.
uv_run(loop, UV_RUN_ONCE);
async = false;
}
void Map::delete_async(uv_handle_t *handle) {
delete (uv_async_t *)handle;
}
void Map::run() {
setup();
prepareRender();
uv_run(loop, UV_RUN_DEFAULT);
// If the map rendering wasn't started asynchronously, we perform one render
// *after* all events have been processed.
if (!async) {
render();
}
}
void Map::setup() {
painter.setup();
void Map::rerender() {
// We only send render events if we want to continuously update the map
// (== async rendering).
if (async) {
uv_async_send(async_render);
}
}
style.loadJSON(resources::style, resources::style_size);
void Map::update() {
clean.clear();
rerender();
}
void Map::render(uv_async_t *async) {
Map *map = static_cast<Map *>(async->data);
map->prepare();
if (map->rendered.test_and_set() == false) {
if (map->clean.test_and_set() == false) {
map->swapped.clear();
map->view.swap();
} else {
// We set the rendered flag in the test above, so we have to reset it
// now that we're not actually rendering because the map is clean.
map->rendered.clear();
}
}
}
void Map::terminate(uv_async_t *async) {
uv_stop(static_cast<uv_loop_t *>(async->data));
}
#pragma mark - Setup
void Map::setup() {
view.make_active();
painter.setup();
sources.emplace("mapbox streets",
std::unique_ptr<Source>(new Source(*this,
transform,
painter,
texturepool,
"http://a.gl-api-us-east-1.tilestream.net/v3/mapbox.mapbox-streets-v4/%d/%d/%d.gl.pbf",
@@ -72,7 +137,6 @@ void Map::setup() {
sources.emplace("satellite",
std::unique_ptr<Source>(new Source(*this,
transform,
painter,
texturepool,
"https://a.tiles.mapbox.com/v3/justin.hh0gkdfm/%d/%d/%d%s.png256",
@@ -82,6 +146,8 @@ void Map::setup() {
0,
21,
false)));
loadStyle(resources::style, resources::style_size);
}
void Map::loadStyle(const uint8_t *const data, uint32_t bytes) {
@@ -89,176 +155,120 @@ void Map::loadStyle(const uint8_t *const data, uint32_t bytes) {
update();
}
void Map::loadSettings() {
transform.setAngle(settings.angle);
transform.setScale(settings.scale);
transform.setLonLat(settings.longitude, settings.latitude);
painter.setDebug(settings.debug);
update();
}
#pragma mark - Size
void Map::resize(uint16_t width, uint16_t height, float ratio) {
resize(width, height, ratio, width * ratio, height * ratio);
}
void Map::resize(uint16_t width, uint16_t height, float ratio, uint16_t fb_width, uint16_t fb_height) {
transform.resize(width, height, ratio, fb_width, fb_height);
painter.resize();
if (!style.sprite || style.sprite->pixelRatio != transform.getPixelRatio()) {
style.sprite = std::make_shared<Sprite>(*this, transform.getPixelRatio());
style.sprite->load(kSpriteURL);
if (transform.resize(width, height, ratio, fb_width, fb_height)) {
update();
}
}
box Map::cornersToBox(uint32_t z) const {
return transform.cornersToBox(z);
#pragma mark - Animations
void Map::cancelAnimations() {
transform.cancelAnimations();
update();
}
float Map::getPixelRatio() const {
return transform.getPixelRatio();
}
Style& Map::getStyle() {
return style;
}
GlyphAtlas& Map::getGlyphAtlas() {
return glyphAtlas;
}
uv_loop_t *Map::getLoop() {
return loop;
}
#pragma mark - Position
void Map::moveBy(double dx, double dy, double duration) {
transform.moveBy(dx, dy, duration);
update();
transform.getLonLat(settings.longitude, settings.latitude);
settings.persist();
}
void Map::startPanning() {
transform.startPanning();
redraw();
}
void Map::stopPanning() {
transform.stopPanning();
redraw();
}
void Map::scaleBy(double ds, double cx, double cy, double duration) {
transform.scaleBy(ds, cx, cy, duration);
update();
transform.getLonLat(settings.longitude, settings.latitude);
settings.scale = transform.getScale();
settings.persist();
}
void Map::startScaling() {
transform.startScaling();
redraw();
}
void Map::stopScaling() {
transform.stopScaling();
redraw();
}
void Map::rotateBy(double sx, double sy, double ex, double ey, double duration) {
transform.rotateBy(sx, sy, ex, ey, duration);
update();
settings.angle = transform.getAngle();
settings.persist();
}
void Map::startRotating() {
transform.startRotating();
redraw();
}
void Map::stopRotating() {
transform.stopRotating();
redraw();
}
void Map::setLonLat(double lon, double lat, double duration) {
transform.setLonLat(lon, lat, duration);
update();
transform.getLonLat(settings.longitude, settings.latitude);
settings.persist();
}
void Map::getLonLat(double& lon, double& lat) const {
transform.getLonLat(lon, lat);
}
void Map::setLonLatZoom(double lon, double lat, double zoom, double duration) {
transform.setLonLatZoom(lon, lat, zoom, duration);
void Map::startPanning() {
transform.startPanning();
update();
transform.getLonLat(settings.longitude, settings.latitude);
settings.scale = transform.getScale();
settings.persist();
}
void Map::getLonLatZoom(double& lon, double& lat, double& zoom) const {
transform.getLonLatZoom(lon, lat, zoom);
void Map::stopPanning() {
transform.stopPanning();
update();
}
void Map::resetPosition() {
transform.setAngle(0);
transform.setLonLat(0, 0);
transform.setZoom(0);
update();
}
#pragma mark - Scale
void Map::scaleBy(double ds, double cx, double cy, double duration) {
transform.scaleBy(ds, cx, cy, duration);
update();
}
void Map::setScale(double scale, double cx, double cy, double duration) {
transform.setScale(scale, cx, cy, duration);
update();
}
transform.getLonLat(settings.longitude, settings.latitude);
settings.scale = transform.getScale();
settings.persist();
double Map::getScale() const {
return transform.getScale();
}
void Map::setZoom(double zoom, double duration) {
transform.setZoom(zoom, duration);
update();
transform.getLonLat(settings.longitude, settings.latitude);
settings.scale = transform.getScale();
settings.persist();
}
double Map::getZoom() const {
return transform.getZoom();
}
void Map::setAngle(double angle, double x, double y, double duration) {
double dx = 0, dy = 0;
if (x >= 0 && y >= 0) {
dx = (transform.getWidth() / 2) - x;
dy = (transform.getWidth() / 2) - y;
transform.moveBy(dx, dy, duration);
}
transform.setAngle(angle, duration);
if (x >= 0 && y >= 0) {
transform.moveBy(-dx, -dy, duration);
}
void Map::setLonLatZoom(double lon, double lat, double zoom, double duration) {
transform.setLonLatZoom(lon, lat, zoom, duration);
update();
transform.getLonLat(settings.longitude, settings.latitude);
settings.angle = transform.getAngle();
settings.persist();
}
double Map::getScale() const {
return transform.getScale();
void Map::getLonLatZoom(double& lon, double& lat, double& zoom) const {
transform.getLonLatZoom(lon, lat, zoom);
}
void Map::resetZoom() {
setZoom(0);
}
void Map::startScaling() {
transform.startScaling();
update();
}
void Map::stopScaling() {
transform.stopScaling();
update();
}
#pragma mark - Rotation
void Map::rotateBy(double sx, double sy, double ex, double ey, double duration) {
transform.rotateBy(sx, sy, ex, ey, duration);
update();
}
void Map::setAngle(double angle, double cx, double cy) {
transform.setAngle(angle, cx, cy);
update();
}
double Map::getAngle() const {
@@ -268,33 +278,33 @@ double Map::getAngle() const {
void Map::resetNorth() {
transform.setAngle(0, 0.5);
update();
settings.angle = transform.getAngle();
settings.persist();
}
void Map::resetPosition() {
transform.setAngle(0);
transform.setLonLat(0, 0);
transform.setZoom(0);
void Map::startRotating() {
transform.startRotating();
update();
transform.getLonLat(settings.longitude, settings.latitude);
settings.scale = transform.getScale();
settings.angle = transform.getAngle();
settings.persist();
}
void Map::resetZoom() {
setZoom(0);
void Map::stopRotating() {
transform.stopRotating();
update();
}
#pragma mark - Toggles
void Map::setDebug(bool value) {
debug = value;
painter.setDebug(debug);
update();
}
void Map::toggleDebug() {
settings.debug = !settings.debug;
painter.setDebug(settings.debug);
update();
setDebug(!debug);
}
settings.persist();
bool Map::getDebug() const {
return debug;
}
void Map::toggleRaster() {
@@ -309,64 +319,45 @@ void Map::toggleRaster() {
satellite_source.enabled = true;
style.appliedClasses.insert("satellite");
}
style.cascade(transform.getNormalizedZoom());
update();
}
update();
}
void Map::cancelAnimations() {
transform.cancelAnimations();
}
void Map::update() {
// Loads new tiles and removes old ones.
updateTiles();
// Updates the stylesheet to the new zoom level
style.cascade(transform.getNormalizedZoom());
// Schedules a repaint
redraw();
}
void Map::redraw() {
clean.clear();
platform::restart();
}
bool Map::updateTiles() {
bool changed = false;
void Map::updateTiles() {
for (auto &pair : sources) {
Source &source = *pair.second;
if (source.enabled) {
changed = source.update() || changed;
const std::unique_ptr<Source> &source = pair.second;
if (source->enabled) {
source->update();
}
}
}
return changed;
void Map::prepare() {
view.make_active();
// Update animations
animationTime = (double)util::now() / 1e9;
bool animating = transform.needsAnimation();
if (animating) {
transform.updateAnimations(animationTime);
}
state = transform.currentState();
style.cascade(state.getNormalizedZoom());
if (!style.sprite || style.sprite->pixelRatio != state.getPixelRatio()) {
style.sprite = std::make_shared<Sprite>(*this, state.getPixelRatio());
style.sprite->load(kSpriteURL);
}
updateTiles();
}
void Map::render() {
double animationTime = platform::elapsed();
if (transform.needsAnimation()) {
transform.updateAnimations(animationTime);
// Animations change the transform which affects the stylesheet.
style.cascade(transform.getNormalizedZoom());
}
if (*style.sprite->raster && !style.sprite->raster->textured) {
style.sprite->raster->setTexturepool(&texturepool);
}
bool changed = updateTiles();
painter.clear();
painter.resize();
painter.changeMatrix();
// First, update the sources' tile matrices with the new
@@ -379,7 +370,7 @@ void Map::render() {
for (auto &pair : sources) {
Source &source = *pair.second;
source.prepare_render(is_baselayer);
source.prepare_render(state, is_baselayer);
is_baselayer = source.enabled ? false : (true && is_baselayer);
}
@@ -395,11 +386,8 @@ void Map::render() {
painter.renderMatte();
bool transition = transform.needsAnimation();
bool animation = painter.needsAnimation();
if (changed || transition || animation) {
redraw();
// Schedule another rerender when we definitely need a next frame.
if (transform.needsAnimation()) {
update();
}
}

View File

@@ -11,12 +11,11 @@
using namespace llmr;
Source::Source(Map &map, Transform &transform, Painter &painter, Texturepool &texturepool,
Source::Source(Map &map, Painter &painter, Texturepool &texturepool,
const char *url, Source::Type type, std::vector<uint32_t> zooms, uint32_t tile_size,
uint32_t min_zoom, uint32_t max_zoom, bool enabled)
: enabled(enabled),
map(map),
transform(transform),
painter(painter),
texturepool(texturepool),
type(type),
@@ -30,7 +29,7 @@ bool Source::update() {
return updateTiles();
}
void Source::prepare_render(bool is_baselayer) {
void Source::prepare_render(const TransformState &transform, bool is_baselayer) {
if (!enabled) return;
uint8_t i = 1;
@@ -113,7 +112,7 @@ TileData::State Source::addTile(const Tile::ID& id) {
if (type == Source::Type::vector) {
formed_url = util::sprintf<256>(url, normalized_id.z, normalized_id.x, normalized_id.y);
} else {
formed_url = util::sprintf<256>(url, normalized_id.z, normalized_id.x, normalized_id.y, (map.getPixelRatio() > 1.0 ? "@2x" : ""));
formed_url = util::sprintf<256>(url, normalized_id.z, normalized_id.x, normalized_id.y, (map.getState().getPixelRatio() > 1.0 ? "@2x" : ""));
}
new_tile.data = std::make_shared<TileData>(normalized_id, map, formed_url, (type == Source::Type::raster));
@@ -179,7 +178,7 @@ bool Source::updateTiles() {
bool changed = false;
// Figure out what tiles we need to load
int32_t zoom = map.getZoom();
int32_t zoom = map.getState().getIntegerZoom();
if (zoom > max_zoom) zoom = max_zoom;
if (zoom < min_zoom) zoom = min_zoom;
@@ -190,7 +189,7 @@ bool Source::updateTiles() {
if (min_covering_zoom < min_zoom) min_covering_zoom = min_zoom;
// Map four viewport corners to pixel coordinates
box box = map.cornersToBox(zoom);
box box = map.getState().cornersToBox(zoom);
// Performs a scanline algorithm search that covers the rectangle of the box
// and sorts them by proximity to the center.
@@ -263,7 +262,7 @@ bool Source::updateTiles() {
return false;
}
});
return changed;
}
@@ -331,8 +330,8 @@ void _scanTriangle(const llmr::vec2<double> a, const llmr::vec2<double> b, const
double Source::getZoom() const {
double offset = log(util::tileSize / tile_size) / log(2);
offset += (map.getPixelRatio() > 1.0 ? 1 :0);
return map.getZoom() + offset;
offset += (map.getState().getPixelRatio() > 1.0 ? 1 :0);
return map.getState().getZoom() + offset;
}
std::forward_list<llmr::Tile::ID> Source::covering_tiles(int32_t zoom, const box& points) {

View File

@@ -10,7 +10,7 @@
#include <llmr/util/raster.hpp>
#include <llmr/util/string.hpp>
#include <uv.h>
#include <llmr/util/uv.hpp>
using namespace llmr;
@@ -47,16 +47,14 @@ void TileData::request() {
// to drop to zero for destruction.
} else if (res->code == 200) {
tile->state = State::loaded;
tile->data.swap(res->body);
tile->parse();
// Schedule tile parsing in another thread
tile->scheduleParsing();
} else {
fprintf(stderr, "[%s] tile loading failed: %d, %s\n", tile->url.c_str(), res->code, res->error_message.c_str());
}
}, [weak_tile]() {
std::shared_ptr<TileData> tile = weak_tile.lock();
if (tile) {
tile->map.redraw();
}
}, map.getLoop());
}
@@ -67,9 +65,19 @@ void TileData::cancel() {
}
}
bool TileData::parse() {
void TileData::scheduleParsing() {
// We're creating a new work request. The work request deletes itself after it executed
// the after work handler
new uv::work<std::shared_ptr<TileData>>(
map.getLoop(),
[](std::shared_ptr<TileData> &tile) { tile->parse(); },
[](std::shared_ptr<TileData> &tile) { tile->map.update(); },
shared_from_this());
}
void TileData::parse() {
if (state != State::loaded) {
return false;
return;
}
try {
@@ -80,14 +88,10 @@ bool TileData::parse() {
} catch (const std::exception& ex) {
fprintf(stderr, "[%p] exception [%d/%d/%d]... failed: %s\n", this, id.z, id.x, id.y, ex.what());
cancel();
return false;
return;
}
if (state == State::obsolete) {
return false;
} else {
if (state != State::obsolete) {
state = State::parsed;
}
return true;
}

View File

@@ -3,113 +3,280 @@
#include <llmr/util/mat4.hpp>
#include <llmr/util/std.hpp>
#include <llmr/util/math.hpp>
#include <llmr/util/time.hpp>
#include <llmr/platform/platform.hpp>
#include <cstdio>
using namespace llmr;
const double D2R = M_PI / 180.0;
const double R2D = 180.0 / M_PI;
const double M2PI = 2 * M_PI;
Transform::Transform() {
setScale(scale);
setAngle(angle);
setScale(current.scale);
setAngle(current.angle);
}
bool Transform::needsAnimation() const {
return !animations.empty();
}
#pragma mark - Map View
void Transform::updateAnimations(double time) {
animations.remove_if([time](const std::shared_ptr<util::animation>& animation) {
return animation->update(time) == util::animation::complete;
});
}
bool Transform::resize(const uint16_t w, const uint16_t h, const float ratio,
const uint16_t fb_w, const uint16_t fb_h) {
uv::writelock lock(mtx);
void Transform::cancelAnimations() {
animations.clear();
}
if (final.width != w || final.height != h || final.pixelRatio != ratio ||
final.framebuffer[0] != fb_w || final.framebuffer[1] != fb_h) {
void Transform::resize(uint16_t w, uint16_t h, float ratio, uint16_t fb_w, uint16_t fb_h) {
width = w;
height = h;
pixelRatio = ratio;
fb_width = fb_w;
fb_height = fb_h;
}
void Transform::moveBy(double dx, double dy, double duration) {
double xn = x + cos(angle) * dx + sin(angle) * dy;
double yn = y + cos(angle) * dy + sin(-angle) * dx;
if (duration == 0) {
x = xn;
y = yn;
current.width = final.width = w;
current.height = final.height = h;
current.pixelRatio = final.pixelRatio = ratio;
current.framebuffer[0] = final.framebuffer[0] = fb_w;
current.framebuffer[1] = final.framebuffer[1] = fb_h;
return true;
} else {
// Use a common start time for all of the animations to avoid divergent animations.
double start = platform::elapsed();
animations.emplace_front(std::make_shared<util::ease_animation>(x, xn, x, start, duration));
animations.emplace_front(std::make_shared<util::ease_animation>(y, yn, y, start, duration));
return false;
}
}
#pragma mark - Position
void Transform::moveBy(const double dx, const double dy, const double duration) {
uv::writelock lock(mtx);
_moveBy(dx, dy, duration);
}
void Transform::_moveBy(const double dx, const double dy, const double duration) {
// This is only called internally, so we don't need a lock here.
final.x = current.x + cos(current.angle) * dx + sin(current.angle) * dy;
final.y = current.y + cos(current.angle) * dy + sin(-current.angle) * dx;
if (duration == 0) {
current.x = final.x;
current.y = final.y;
} else {
// Use a common start time for all of the animations to avoid divergent animations.
double start = (double)util::now() / 1_second;
animations.emplace_front(
std::make_shared<util::ease_animation>(current.x, final.x, current.x, start, duration));
animations.emplace_front(
std::make_shared<util::ease_animation>(current.y, final.y, current.y, start, duration));
}
}
void Transform::setLonLat(const double lon, const double lat, const double duration) {
uv::writelock lock(mtx);
const double f = fmin(fmax(sin(D2R * lat), -0.9999), 0.9999);
double xn = -round(lon * Bc);
double yn = round(0.5 * Cc * log((1 + f) / (1 - f)));
_setScaleXY(current.scale, xn, yn, duration);
}
void Transform::setLonLatZoom(const double lon, const double lat, const double zoom,
const double duration) {
uv::writelock lock(mtx);
double new_scale = pow(2.0, zoom);
const double s = new_scale * util::tileSize;
zc = s / 2;
Bc = s / 360;
Cc = s / (2 * M_PI);
const double f = fmin(fmax(sin(D2R * lat), -0.9999), 0.9999);
double xn = -round(lon * Bc);
double yn = round(0.5 * Cc * log((1 + f) / (1 - f)));
_setScaleXY(new_scale, xn, yn, duration);
}
void Transform::getLonLat(double &lon, double &lat) const {
uv::readlock lock(mtx);
lon = -final.x / Bc;
lat = R2D * (2 * atan(exp(final.y / Cc)) - 0.5 * M_PI);
}
void Transform::getLonLatZoom(double &lon, double &lat, double &zoom) const {
uv::readlock lock(mtx);
getLonLat(lon, lat);
zoom = getZoom();
}
void Transform::startPanning() {
stopPanning();
uv::writelock lock(mtx);
_clearPanning();
// Add a 200ms timeout for resetting this to false
panning = true;
double start = platform::elapsed();
pan_timeout = std::make_shared<util::timeout<bool>>(false, panning, start, 0.2);
current.panning = true;
double start = (double)util::now() / 1_second;
pan_timeout = std::make_shared<util::timeout<bool>>(false, current.panning, start, 0.2);
animations.emplace_front(pan_timeout);
}
void Transform::stopPanning() {
panning = false;
uv::writelock lock(mtx);
_clearPanning();
}
void Transform::_clearPanning() {
current.panning = false;
if (pan_timeout) {
animations.remove(pan_timeout);
pan_timeout.reset();
}
}
void Transform::scaleBy(double ds, double cx, double cy, double duration) {
#pragma mark - Zoom
void Transform::scaleBy(const double ds, const double cx, const double cy, const double duration) {
uv::writelock lock(mtx);
// clamp scale to min/max values
double new_scale = scale * ds;
double new_scale = current.scale * ds;
if (new_scale < min_scale) {
new_scale = min_scale;
} else if (new_scale > max_scale) {
new_scale = max_scale;
}
setScale(new_scale, cx, cy, duration);
_setScale(new_scale, cx, cy, duration);
}
void Transform::setScale(const double scale, const double cx, const double cy,
const double duration) {
uv::writelock lock(mtx);
_setScale(scale, cx, cy, duration);
}
void Transform::setZoom(const double zoom, const double duration) {
uv::writelock lock(mtx);
_setScale(pow(2.0, zoom), -1, -1, duration);
}
double Transform::getZoom() const {
uv::readlock lock(mtx);
return log(final.scale) / M_LN2;
}
double Transform::getScale() const {
uv::readlock lock(mtx);
return final.scale;
}
void Transform::startScaling() {
stopScaling();
uv::writelock lock(mtx);
_clearScaling();
// Add a 200ms timeout for resetting this to false
scaling = true;
double start = platform::elapsed();
scale_timeout = std::make_shared<util::timeout<bool>>(false, scaling, start, 0.2);
current.scaling = true;
double start = (double)util::now() / 1_second;
scale_timeout = std::make_shared<util::timeout<bool>>(false, current.scaling, start, 0.2);
animations.emplace_front(scale_timeout);
}
void Transform::stopScaling() {
scaling = false;
uv::writelock lock(mtx);
_clearScaling();
}
void Transform::_clearScaling() {
// This is only called internally, so we don't need a lock here.
current.scaling = false;
if (scale_timeout) {
animations.remove(scale_timeout);
scale_timeout.reset();
}
}
void Transform::rotateBy(double start_x, double start_y, double end_x, double end_y, double duration) {
double center_x = width / 2, center_y = height / 2;
void Transform::_setScale(double new_scale, double cx, double cy, const double duration) {
// This is only called internally, so we don't need a lock here.
// Ensure that we don't zoom in further than the maximum allowed.
if (new_scale < min_scale) {
new_scale = min_scale;
} else if (new_scale > max_scale) {
new_scale = max_scale;
}
// Zoom in on the center if we don't have click or gesture anchor coordinates.
if (cx < 0 || cy < 0) {
cx = current.width / 2;
cy = current.height / 2;
}
// Account for the x/y offset from the center (= where the user clicked or pinched)
const double factor = new_scale / current.scale;
const double dx = (cx - current.width / 2) * (1.0 - factor);
const double dy = (cy - current.height / 2) * (1.0 - factor);
// Account for angle
const double angle_sin = sin(-current.angle);
const double angle_cos = cos(-current.angle);
const double ax = angle_cos * dx - angle_sin * dy;
const double ay = angle_sin * dx + angle_cos * dy;
const double xn = current.x * factor + ax;
const double yn = current.y * factor + ay;
_setScaleXY(new_scale, xn, yn, duration);
}
void Transform::_setScaleXY(const double new_scale, const double xn, const double yn,
const double duration) {
// This is only called internally, so we don't need a lock here.
final.scale = new_scale;
final.x = xn;
final.y = yn;
if (duration == 0) {
current.scale = final.scale;
current.x = final.x;
current.y = final.y;
} else {
// Use a common start time for all of the animations to avoid divergent animations.
double start = (double)util::now() / 1_second;
animations.emplace_front(std::make_shared<util::ease_animation>(
current.scale, final.scale, current.scale, start, duration));
animations.emplace_front(
std::make_shared<util::ease_animation>(current.x, final.x, current.x, start, duration));
animations.emplace_front(
std::make_shared<util::ease_animation>(current.y, final.y, current.y, start, duration));
}
const double s = final.scale * util::tileSize;
zc = s / 2;
Bc = s / 360;
Cc = s / (2 * M_PI);
}
#pragma mark - Angle
void Transform::rotateBy(const double start_x, const double start_y, const double end_x,
const double end_y, const double duration) {
uv::writelock lock(mtx);
double center_x = current.width / 2, center_y = current.height / 2;
const double begin_center_x = start_x - center_x;
const double begin_center_y = start_y - center_y;
const double beginning_center_dist = sqrt(begin_center_x * begin_center_x + begin_center_y * begin_center_y);
const double beginning_center_dist =
sqrt(begin_center_x * begin_center_x + begin_center_y * begin_center_y);
// If the first click was too close to the center, move the center of rotation by 200 pixels
// in the direction of the click.
@@ -125,206 +292,120 @@ void Transform::rotateBy(double start_x, double start_y, double end_x, double en
const double first_x = start_x - center_x, first_y = start_y - center_y;
const double second_x = end_x - center_x, second_y = end_y - center_y;
const double ang = angle + util::angle_between(first_x, first_y, second_x, second_y);
const double ang = current.angle + util::angle_between(first_x, first_y, second_x, second_y);
setAngle(ang, duration);
_setAngle(ang, duration);
}
void Transform::setAngle(double new_angle, double duration) {
while (new_angle > M_PI) new_angle -= M2PI;
while (new_angle <= -M_PI) new_angle += M2PI;
void Transform::setAngle(const double new_angle, const double duration) {
uv::writelock lock(mtx);
if (duration == 0) {
angle = new_angle;
} else {
double start = platform::elapsed();
animations.emplace_front(std::make_shared<util::ease_animation>(angle, new_angle, angle, start, duration));
_setAngle(new_angle, duration);
}
void Transform::setAngle(const double new_angle, const double cx, const double cy) {
uv::writelock lock(mtx);
double dx = 0, dy = 0;
if (cx >= 0 && cy >= 0) {
dx = (final.width / 2) - cx;
dy = (final.height / 2) - cy;
_moveBy(dx, dy, 0);
}
_setAngle(new_angle, 0);
if (cx >= 0 && cy >= 0) {
_moveBy(-dx, -dy, 0);
}
}
void Transform::_setAngle(double new_angle, const double duration) {
// This is only called internally, so we don't need a lock here.
while (new_angle > M_PI)
new_angle -= M2PI;
while (new_angle <= -M_PI)
new_angle += M2PI;
final.angle = new_angle;
if (duration == 0) {
current.angle = final.angle;
} else {
double start = (double)util::now() / 1_second;
animations.emplace_front(std::make_shared<util::ease_animation>(
current.angle, final.angle, current.angle, start, duration));
}
}
double Transform::getAngle() const {
uv::readlock lock(mtx);
return final.angle;
}
void Transform::startRotating() {
stopRotating();
uv::writelock lock(mtx);
_clearRotating();
// Add a 200ms timeout for resetting this to false
rotating = true;
double start = platform::elapsed();
rotate_timeout = std::make_shared<util::timeout<bool>>(false, rotating, start, 0.2);
current.rotating = true;
double start = (double)util::now() / 1_second;
rotate_timeout = std::make_shared<util::timeout<bool>>(false, current.rotating, start, 0.2);
animations.emplace_front(rotate_timeout);
}
void Transform::stopRotating() {
rotating = false;
uv::writelock lock(mtx);
_clearRotating();
}
void Transform::_clearRotating() {
// This is only called internally, so we don't need a lock here.
current.rotating = false;
if (rotate_timeout) {
animations.remove(rotate_timeout);
rotate_timeout.reset();
}
}
void Transform::setScaleXY(double new_scale, double xn, double yn, double duration) {
if (duration == 0) {
scale = new_scale;
x = xn;
y = yn;
} else {
// Use a common start time for all of the animations to avoid divergent animations.
double start = platform::elapsed();
animations.emplace_front(std::make_shared<util::ease_animation>(scale, new_scale, scale, start, duration));
animations.emplace_front(std::make_shared<util::ease_animation>(x, xn, x, start, duration));
animations.emplace_front(std::make_shared<util::ease_animation>(y, yn, y, start, duration));
}
#pragma mark - Animation
const double s = scale * util::tileSize;
zc = s / 2;
Bc = s / 360;
Cc = s / (2 * M_PI);
bool Transform::needsAnimation() const {
uv::readlock lock(mtx);
return !animations.empty();
}
void Transform::setScale(double new_scale, double cx, double cy, double duration) {
// Ensure that we don't zoom in further than the maximum allowed.
if (new_scale < min_scale) {
new_scale = min_scale;
} else if (new_scale > max_scale) {
new_scale = max_scale;
}
void Transform::updateAnimations(const double time) {
uv::writelock lock(mtx);
// Zoom in on the center if we don't have click or gesture anchor coordinates.
if (cx < 0 || cy < 0) {
cx = width / 2;
cy = height / 2;
}
// Account for the x/y offset from the center (= where the user clicked or pinched)
const double factor = new_scale / scale;
const double dx = (cx - width / 2) * (1.0 - factor);
const double dy = (cy - height / 2) * (1.0 - factor);
// Account for angle
const double angle_sin = sin(-angle);
const double angle_cos = cos(-angle);
const double ax = angle_cos * dx - angle_sin * dy;
const double ay = angle_sin * dx + angle_cos * dy;
const double xn = x * factor + ax;
const double yn = y * factor + ay;
setScaleXY(new_scale, xn, yn, duration);
animations.remove_if([time](const std::shared_ptr<util::animation> &animation) {
return animation->update(time) == util::animation::complete;
});
}
void Transform::setZoom(double zoom, double duration) {
setScale(pow(2.0, zoom), -1, -1, duration);
void Transform::cancelAnimations() {
uv::writelock lock(mtx);
animations.clear();
}
void Transform::setLonLat(double lon, double lat, double duration) {
const double f = fmin(fmax(sin(D2R * lat), -0.9999), 0.9999);
double xn = -round(lon * Bc);
double yn = round(0.5 * Cc * log((1 + f) / (1 - f)));
#pragma mark - Transform state
setScaleXY(scale, xn, yn, duration);
const TransformState Transform::currentState() const {
uv::readlock lock(mtx);
return current;
}
void Transform::setLonLatZoom(double lon, double lat, double zoom, double duration) {
double new_scale = pow(2.0, zoom);
const TransformState Transform::finalState() const {
uv::readlock lock(mtx);
const double s = new_scale * util::tileSize;
zc = s / 2;
Bc = s / 360;
Cc = s / (2 * M_PI);
const double f = fmin(fmax(sin(D2R * lat), -0.9999), 0.9999);
double xn = -round(lon * Bc);
double yn = round(0.5 * Cc * log((1 + f) / (1 - f)));
setScaleXY(new_scale, xn, yn, duration);
return final;
}
void Transform::getLonLat(double &lon, double &lat) const {
lon = -x / Bc;
lat = R2D * (2 * atan(exp(y / Cc)) - 0.5 * M_PI);
}
void Transform::getLonLatZoom(double& lon, double& lat, double& zoom) const {
getLonLat(lon, lat);
zoom = getZoom();
}
double Transform::pixel_x() const {
const double center = (width - scale * util::tileSize) / 2;
return center + x;
}
double Transform::pixel_y() const {
const double center = (height - scale * util::tileSize) / 2;
return center + y;
}
void Transform::matrixFor(mat4& matrix, const Tile::ID& id) const {
const double tile_scale = pow(2, id.z);
const double tile_size = scale * util::tileSize / tile_scale;
matrix::identity(matrix);
matrix::translate(matrix, matrix, 0.5f * (float)width, 0.5f * (float)height, 0);
matrix::rotate_z(matrix, matrix, angle);
matrix::translate(matrix, matrix, -0.5f * (float)width, -0.5f * (float)height, 0);
matrix::translate(matrix, matrix, pixel_x() + id.x * tile_size, pixel_y() + id.y * tile_size, 0);
// TODO: Get rid of the 8 (scaling from 4096 to tile size);
float factor = scale / tile_scale / (4096.0f / util::tileSize);
matrix::scale(matrix, matrix, factor, factor, 1);
}
float Transform::getZoom() const {
return log(scale) / M_LN2;
}
float Transform::getNormalizedZoom() const {
return log(scale * util::tileSize / 256.0f) / M_LN2;
}
int32_t Transform::getIntegerZoom() const {
return floor(log(scale) / M_LN2);
}
double Transform::getScale() const {
return scale;
}
double Transform::getAngle() const {
return angle;
}
box Transform::cornersToBox(uint32_t z) const {
const double ref_scale = pow(2, z);
const double angle_sin = sin(-angle);
const double angle_cos = cos(-angle);
const double w_2 = width / 2;
const double h_2 = height / 2;
const double ss_0 = scale * util::tileSize;
const double ss_1 = ref_scale / ss_0;
const double ss_2 = ss_0 / 2.0;
// Calculate the corners of the map view. The resulting coordinates will be
// in fractional tile coordinates.
box b;
b.tl.x = ((-w_2) * angle_cos - (-h_2) * angle_sin + ss_2 - x) * ss_1;
b.tl.y = ((-w_2) * angle_sin + (-h_2) * angle_cos + ss_2 - y) * ss_1;
b.tr.x = ((+w_2) * angle_cos - (-h_2) * angle_sin + ss_2 - x) * ss_1;
b.tr.y = ((+w_2) * angle_sin + (-h_2) * angle_cos + ss_2 - y) * ss_1;
b.bl.x = ((-w_2) * angle_cos - (+h_2) * angle_sin + ss_2 - x) * ss_1;
b.bl.y = ((-w_2) * angle_sin + (+h_2) * angle_cos + ss_2 - y) * ss_1;
b.br.x = ((+w_2) * angle_cos - (+h_2) * angle_sin + ss_2 - x) * ss_1;
b.br.y = ((+w_2) * angle_sin + (+h_2) * angle_cos + ss_2 - y) * ss_1;
b.center.x = (ss_2 - x) * ss_1;
b.center.y = (ss_2 - y) * ss_1;
return b;
}

132
src/map/transform_state.cpp Normal file
View File

@@ -0,0 +1,132 @@
#include <llmr/map/transform_state.hpp>
#include <llmr/util/constants.hpp>
using namespace llmr;
#pragma mark - Matrix
void TransformState::matrixFor(mat4& matrix, const Tile::ID& id) const {
const double tile_scale = pow(2, id.z);
const double tile_size = scale * util::tileSize / tile_scale;
matrix::identity(matrix);
matrix::translate(matrix, matrix, 0.5f * (float)width, 0.5f * (float)height, 0);
matrix::rotate_z(matrix, matrix, angle);
matrix::translate(matrix, matrix, -0.5f * (float)width, -0.5f * (float)height, 0);
matrix::translate(matrix, matrix, pixel_x() + id.x * tile_size, pixel_y() + id.y * tile_size, 0);
// TODO: Get rid of the 8 (scaling from 4096 to tile size);
float factor = scale / tile_scale / (4096.0f / util::tileSize);
matrix::scale(matrix, matrix, factor, factor, 1);
}
box TransformState::cornersToBox(uint32_t z) const {
const double ref_scale = pow(2, z);
const double angle_sin = sin(-angle);
const double angle_cos = cos(-angle);
const double w_2 = width / 2;
const double h_2 = height / 2;
const double ss_0 = scale * util::tileSize;
const double ss_1 = ref_scale / ss_0;
const double ss_2 = ss_0 / 2.0;
// Calculate the corners of the map view. The resulting coordinates will be
// in fractional tile coordinates.
box b;
b.tl.x = ((-w_2) * angle_cos - (-h_2) * angle_sin + ss_2 - x) * ss_1;
b.tl.y = ((-w_2) * angle_sin + (-h_2) * angle_cos + ss_2 - y) * ss_1;
b.tr.x = ((+w_2) * angle_cos - (-h_2) * angle_sin + ss_2 - x) * ss_1;
b.tr.y = ((+w_2) * angle_sin + (-h_2) * angle_cos + ss_2 - y) * ss_1;
b.bl.x = ((-w_2) * angle_cos - (+h_2) * angle_sin + ss_2 - x) * ss_1;
b.bl.y = ((-w_2) * angle_sin + (+h_2) * angle_cos + ss_2 - y) * ss_1;
b.br.x = ((+w_2) * angle_cos - (+h_2) * angle_sin + ss_2 - x) * ss_1;
b.br.y = ((+w_2) * angle_sin + (+h_2) * angle_cos + ss_2 - y) * ss_1;
b.center.x = (ss_2 - x) * ss_1;
b.center.y = (ss_2 - y) * ss_1;
return b;
}
#pragma mark - Dimensions
uint16_t TransformState::getWidth() const {
return width;
}
uint16_t TransformState::getHeight() const {
return height;
}
uint16_t TransformState::getFramebufferWidth() const {
return framebuffer[0];
}
uint16_t TransformState::getFramebufferHeight() const {
return framebuffer[1];
}
const std::array<uint16_t, 2> TransformState::getFramebufferDimensions() const {
return framebuffer;
}
float TransformState::getPixelRatio() const {
return pixelRatio;
}
#pragma mark - Zoom
float TransformState::getNormalizedZoom() const {
return log(scale * util::tileSize / 256.0f) / M_LN2;
}
int32_t TransformState::getIntegerZoom() const {
return floor(getZoom());
}
double TransformState::getZoom() const {
return log(scale) / M_LN2;
}
double TransformState::getScale() const {
return scale;
}
#pragma mark - Rotation
float TransformState::getAngle() const {
return angle;
}
#pragma mark - Rotation
bool TransformState::isChanging() const {
return rotating || scaling || panning;
}
#pragma mark - (private helper functions)
double TransformState::pixel_x() const {
const double center = (width - scale * util::tileSize) / 2;
return center + x;
}
double TransformState::pixel_y() const {
const double center = (height - scale * util::tileSize) / 2;
return center + y;
}

View File

@@ -1,46 +1,46 @@
#include <llmr/platform/request.hpp>
#include <llmr/platform/platform.hpp>
#include <llmr/util/std.hpp>
#include <uv.h>
using namespace llmr::platform;
Request::Request(const std::string &url,
std::function<void(Response *)> background_function,
std::function<void()> foreground_callback,
std::function<void(Response *)> callback,
uv_loop_t *loop)
: url(url),
background_function(background_function),
foreground_callback(foreground_callback),
res(std::make_unique<Response>(callback)),
cancelled(false),
loop(loop) {
// Add a check handle without a callback to keep the default loop running.
// We don't have a real handler attached to the default loop right from the
// beginning, because we're using asynchronous messaging to perform the actual
// request in the request thread. Only after the request is complete, we
// create an actual work request that is attached to the default loop.
check = new uv_check_t();
uv_check_init(loop, check);
uv_check_start(check, [](uv_check_t *) {});
async = new uv_async_t();
async->data = new std::unique_ptr<Response>();
uv_async_init(loop, async, complete);
}
Request::~Request() {
// We need to remove our no-op handle again to allow the main event loop to exit.
uv_check_stop(check);
}
void Request::work_callback(uv_work_t *work) {
std::shared_ptr<Request> *req = static_cast<std::shared_ptr<Request> *>(work->data);
(*req)->background_function((*req)->res.get());
void Request::complete() {
// We're scheduling the response callback to be invoked in the event loop.
// Since the Request object will be deleted before the callback is invoked,
// we move over the Response object to be owned by the async handle.
((std::unique_ptr<Response> *)async->data)->swap(res);
uv_async_send(async);
}
// This callback is executed in the main loop.
void Request::after_work_callback(uv_work_t *work, int /*status*/) {
std::shared_ptr<Request> *req = static_cast<std::shared_ptr<Request> *>(work->data);
void Request::complete(uv_async_t *async) {
Response *res = static_cast<std::unique_ptr<Response> *>(async->data)->get();
(*req)->foreground_callback();
res->callback(res);
// This finally deletes the *pointer* to the shared pointer we've been holding on since we
// pushed it on the add_queue on request creation.
delete req;
delete work;
// We need to remove our async handle again to allow the main event loop to exit.
uv_close((uv_handle_t *)async, [](uv_handle_t *handle) {
delete static_cast<std::unique_ptr<Response> *>(handle->data);
delete (uv_async_t *)handle;
});
}

View File

@@ -1,4 +1,5 @@
#include <llmr/renderer/frame_history.hpp>
#include <llmr/util/time.hpp>
using namespace llmr;
@@ -11,7 +12,7 @@ void FrameHistory::record(float zoom) {
}
if (history.size() > 0 || history.back().z != zoom) {
history.emplace_back(FrameSnapshot{static_cast<float>(platform::elapsed() * 1000), zoom});
history.emplace_back(FrameSnapshot{static_cast<float>((double) util::now() / 1_millisecond), zoom});
}
}

View File

@@ -11,6 +11,7 @@
#include <llmr/renderer/text_bucket.hpp>
#include <llmr/renderer/raster_bucket.hpp>
#include <llmr/map/map.hpp>
#include <llmr/map/transform.hpp>
#include <llmr/geometry/debug_font_buffer.hpp>
#include <llmr/geometry/glyph_atlas.hpp>
@@ -20,15 +21,14 @@
#include <llmr/util/raster.hpp>
#include <llmr/util/string.hpp>
#include <llmr/util/timer.hpp>
#include <llmr/util/time.hpp>
using namespace llmr;
#define BUFFER_OFFSET(i) ((char *)nullptr + (i))
Painter::Painter(Transform& transform, Style& style, GlyphAtlas& glyphAtlas)
: transform(transform),
style(style),
glyphAtlas(glyphAtlas) {
Painter::Painter(Map &map)
: map(map) {
}
bool Painter::needsAnimation() const {
@@ -79,8 +79,12 @@ void Painter::setupShaders() {
}
void Painter::resize() {
assert(transform.getFramebufferWidth() > 0 && transform.getFramebufferHeight() > 0);
glViewport(0, 0, transform.getFramebufferWidth(), transform.getFramebufferHeight());
const TransformState &state = map.getState();
if (gl_viewport != state.getFramebufferDimensions()) {
gl_viewport = state.getFramebufferDimensions();
assert(gl_viewport[0] > 0 && gl_viewport[1] > 0);
glViewport(0, 0, gl_viewport[0], gl_viewport[1]);
}
}
void Painter::setDebug(bool enabled) {
@@ -110,12 +114,12 @@ void Painter::depthMask(bool value) {
void Painter::changeMatrix() {
// Initialize projection matrix
matrix::ortho(projMatrix, 0, transform.getWidth(), transform.getHeight(), 0, 0, 1);
matrix::ortho(projMatrix, 0, map.getState().getWidth(), map.getState().getHeight(), 0, 0, 1);
// The extrusion matrix.
matrix::identity(extrudeMatrix);
matrix::multiply(extrudeMatrix, projMatrix, extrudeMatrix);
matrix::rotate_z(extrudeMatrix, extrudeMatrix, transform.getAngle());
matrix::rotate_z(extrudeMatrix, extrudeMatrix, map.getState().getAngle());
// The native matrix is a 1:1 matrix that paints the coordinates at the
// same screen position as the vertex specifies.
@@ -137,11 +141,11 @@ void Painter::drawClippingMask(const mat4& matrix, uint8_t clip_id) {
gl::group group("mask");
plainShader->setMatrix(matrix);
Color background = style.computed.background.color;
background[0] *= style.computed.background.opacity;
background[1] *= style.computed.background.opacity;
background[2] *= style.computed.background.opacity;
background[3] *= style.computed.background.opacity;
Color background = map.getStyle().computed.background.color;
background[0] *= map.getStyle().computed.background.opacity;
background[1] *= map.getStyle().computed.background.opacity;
background[2] *= map.getStyle().computed.background.opacity;
background[3] *= map.getStyle().computed.background.opacity;
plainShader->setColor(background);
@@ -177,12 +181,12 @@ void Painter::render(const Tile& tile) {
return;
}
frameHistory.record(transform.getNormalizedZoom());
frameHistory.record(map.getState().getNormalizedZoom());
matrix = tile.matrix;
glStencilFunc(GL_EQUAL, tile.clip_id, 0xFF);
renderLayers(tile.data, style.layers);
renderLayers(tile.data, map.getStyle().layers);
if (debug) {
renderDebug(tile.data);
@@ -234,15 +238,18 @@ void Painter::renderLayer(const std::shared_ptr<TileData>& tile_data, const Laye
} else {
// This is a singular layer. Try to find the bucket associated with
// this layer and render it.
if (style.buckets[layer_desc.bucket_name].type == BucketType::Raster) {
if (tile_data && tile_data->raster) {
renderRaster(layer_desc.name, tile_data);
}
} else {
auto bucket_it = tile_data->buckets.find(layer_desc.bucket_name);
if (bucket_it != tile_data->buckets.end()) {
assert(bucket_it->second);
bucket_it->second->render(*this, layer_desc.name, tile_data->id);
auto bucket_it = map.getStyle().buckets.find(layer_desc.bucket_name);
if (bucket_it != map.getStyle().buckets.end()) {
if (bucket_it->second.type == BucketType::Raster) {
if (tile_data && tile_data->raster) {
renderRaster(layer_desc.name, tile_data);
}
} else {
auto databucket_it = tile_data->buckets.find(layer_desc.bucket_name);
if (databucket_it != tile_data->buckets.end()) {
assert(databucket_it->second);
databucket_it->second->render(*this, layer_desc.name, tile_data->id);
}
}
}
}
@@ -251,7 +258,11 @@ void Painter::renderLayer(const std::shared_ptr<TileData>& tile_data, const Laye
void Painter::renderRaster(const std::string& layer_name, const std::shared_ptr<TileData>& tile_data) {
if (pass == Opaque) return;
const RasterProperties& properties = style.computed.rasters[layer_name];
auto raster_properties = map.getStyle().computed.rasters;
auto raster_properties_it = raster_properties.find(layer_name);
if (raster_properties_it == raster_properties.end()) return;
const RasterProperties& properties = raster_properties_it->second;
if (!properties.enabled) return;
gl::group group(layer_name + " (raster)");
@@ -271,7 +282,11 @@ void Painter::renderFill(FillBucket& bucket, const std::string& layer_name, cons
// Abort early.
if (bucket.empty()) return;
const FillProperties& properties = style.computed.fills[layer_name];
auto fill_properties = map.getStyle().computed.fills;
auto fill_properties_it = fill_properties.find(layer_name);
if (fill_properties_it == fill_properties.end()) return;
const FillProperties& properties = fill_properties_it->second;
if (!properties.enabled) return;
Color fill_color = properties.fill_color;
@@ -305,8 +320,8 @@ void Painter::renderFill(FillBucket& bucket, const std::string& layer_name, cons
// Draw the entire line
outlineShader->setWorld({{
static_cast<float>(transform.getFramebufferWidth()),
static_cast<float>(transform.getFramebufferHeight())
static_cast<float>(map.getState().getFramebufferWidth()),
static_cast<float>(map.getState().getFramebufferHeight())
}});
glDepthRange(strata, 1.0f);
bucket.drawVertices(*outlineShader);
@@ -329,12 +344,13 @@ void Painter::renderFill(FillBucket& bucket, const std::string& layer_name, cons
}
if ((fill_color[3] >= 1.0f) == (pass == Opaque)) {
if (properties.image.size() && *style.sprite) {
auto &sprite = map.getStyle().sprite;
if (properties.image.size() && sprite && sprite->isLoaded()) {
// Draw texture fill
ImagePosition imagePos = style.sprite->getPosition(properties.image, true);
ImagePosition imagePos = sprite->getPosition(properties.image, true);
float factor = 8.0 / pow(2, transform.getIntegerZoom() - id.z);
float mix = fmod(transform.getZoom(), 1.0);
float factor = 8.0 / pow(2, map.getState().getIntegerZoom() - id.z);
float mix = fmod(map.getState().getZoom(), 1.0);
std::array<float, 2> imageSize = {{
imagePos.size.x * factor,
@@ -356,7 +372,7 @@ void Painter::renderFill(FillBucket& bucket, const std::string& layer_name, cons
patternShader->setPatternBottomRight({{ imagePos.br.x, imagePos.br.y }});
patternShader->setColor(fill_color);
patternShader->setMix(mix);
style.sprite->raster->bind(true);
sprite->raster->bind(true);
// Draw the actual triangles into the color & stencil buffer.
glDepthRange(strata + strata_epsilon, 1.0f);
@@ -387,8 +403,8 @@ void Painter::renderFill(FillBucket& bucket, const std::string& layer_name, cons
// Draw the entire line
outlineShader->setWorld({{
static_cast<float>(transform.getFramebufferWidth()),
static_cast<float>(transform.getFramebufferHeight())
static_cast<float>(map.getState().getFramebufferWidth()),
static_cast<float>(map.getState().getFramebufferHeight())
}});
glDepthRange(strata + strata_epsilon, 1.0f);
@@ -401,7 +417,11 @@ void Painter::renderLine(LineBucket& bucket, const std::string& layer_name, cons
if (pass == Opaque) return;
if (bucket.empty()) return;
const LineProperties& properties = style.computed.lines[layer_name];
auto line_properties = map.getStyle().computed.lines;
auto line_properties_it = line_properties.find(layer_name);
if (line_properties_it == line_properties.end()) return;
const LineProperties& properties = line_properties_it->second;
if (!properties.enabled) return;
float width = properties.width;
@@ -428,17 +448,17 @@ void Painter::renderLine(LineBucket& bucket, const std::string& layer_name, cons
linejoinShader->setMatrix(matrix);
linejoinShader->setColor(color);
linejoinShader->setWorld({{
transform.getFramebufferWidth() * 0.5f,
transform.getFramebufferHeight() * 0.5f
map.getState().getFramebufferWidth() * 0.5f,
map.getState().getFramebufferHeight() * 0.5f
}
});
linejoinShader->setLineWidth({{
((outset - 0.25f) * transform.getPixelRatio()),
((inset - 0.25f) * transform.getPixelRatio())
((outset - 0.25f) * map.getState().getPixelRatio()),
((inset - 0.25f) * map.getState().getPixelRatio())
}
});
float pointSize = ceil(transform.getPixelRatio() * outset * 2.0);
float pointSize = ceil(map.getState().getPixelRatio() * outset * 2.0);
#if defined(GL_ES_VERSION_2_0)
linejoinShader->setSize(pointSize);
#else
@@ -468,7 +488,7 @@ void Painter::renderLine(LineBucket& bucket, const std::string& layer_name, cons
lineShader->setExtrudeMatrix(extrudeMatrix);
// TODO: Move this to transform?
const float tilePixelRatio = transform.getScale() / (1 << transform.getIntegerZoom()) / 8;
const float tilePixelRatio = map.getState().getScale() / (1 << map.getState().getIntegerZoom()) / 8;
lineShader->setDashArray({{ 1, -1 }});
lineShader->setLineWidth({{ outset, inset }});
@@ -483,9 +503,16 @@ void Painter::renderPoint(PointBucket& bucket, const std::string& layer_name, co
if (!bucket.hasPoints()) return;
if (pass == Opaque) return;
const PointProperties& properties = style.computed.points[layer_name];
auto point_properties = map.getStyle().computed.points;
auto point_properties_it = point_properties.find(layer_name);
if (point_properties_it == point_properties.end()) return;
const PointProperties& properties = point_properties_it->second;
if (!properties.enabled) return;
auto &sprite = map.getStyle().sprite;
if (!sprite || !sprite->isLoaded()) return;
gl::group group(layer_name + " (point)");
Color color = properties.color;
@@ -498,7 +525,7 @@ void Painter::renderPoint(PointBucket& bucket, const std::string& layer_name, co
sized_image.append("-");
sized_image.append(std::to_string(static_cast<int>(std::round(properties.size))));
ImagePosition imagePos = style.sprite->getPosition(sized_image, false);
ImagePosition imagePos = sprite->getPosition(sized_image, false);
// fprintf(stderr, "%f/%f => %f/%f\n", imagePos.tl.x, imagePos.tl.y, imagePos.br.x, imagePos.br.y);
@@ -506,7 +533,7 @@ void Painter::renderPoint(PointBucket& bucket, const std::string& layer_name, co
pointShader->setMatrix(matrix);
pointShader->setImage(0);
pointShader->setColor(color);
const float pointSize = properties.size * 1.4142135623730951 * transform.getPixelRatio();
const float pointSize = properties.size * 1.4142135623730951 * map.getState().getPixelRatio();
#if defined(GL_ES_VERSION_2_0)
pointShader->setSize(pointSize);
#else
@@ -515,8 +542,8 @@ void Painter::renderPoint(PointBucket& bucket, const std::string& layer_name, co
#endif
pointShader->setPointTopLeft({{ imagePos.tl.x, imagePos.tl.y }});
pointShader->setPointBottomRight({{ imagePos.br.x, imagePos.br.y }});
if (*style.sprite->raster) {
style.sprite->raster->bind(transform.rotating || transform.scaling || transform.panning);
if (*sprite->raster) {
sprite->raster->bind(map.getState().isChanging());
}
glDepthRange(strata, 1.0f);
bucket.drawPoints(*pointShader);
@@ -527,7 +554,11 @@ void Painter::renderText(TextBucket& bucket, const std::string& layer_name, cons
if (pass == Opaque) return;
if (bucket.empty()) return;
const TextProperties& properties = style.computed.texts[layer_name];
auto text_properties = map.getStyle().computed.texts;
auto text_properties_it = text_properties.find(layer_name);
if (text_properties_it == text_properties.end()) return;
const TextProperties& properties = text_properties_it->second;
if (!properties.enabled) return;
gl::group group(layer_name + " (text)");
@@ -535,7 +566,7 @@ void Painter::renderText(TextBucket& bucket, const std::string& layer_name, cons
mat4 exMatrix;
matrix::copy(exMatrix, projMatrix);
if (bucket.geom_desc.path == TextPathType::Curve) {
matrix::rotate_z(exMatrix, exMatrix, transform.getAngle());
matrix::rotate_z(exMatrix, exMatrix, map.getState().getAngle());
}
const float rotate = properties.rotate;
@@ -551,24 +582,25 @@ void Painter::renderText(TextBucket& bucket, const std::string& layer_name, cons
textShader->setMatrix(matrix);
textShader->setExtrudeMatrix(exMatrix);
glyphAtlas.bind();
textShader->setTextureSize({{ static_cast<float>(glyphAtlas.width), static_cast<float>(glyphAtlas.height) }});
map.getGlyphAtlas().bind();
textShader->setTextureSize({{static_cast<float>(map.getGlyphAtlas().width),
static_cast<float>(map.getGlyphAtlas().height)}});
textShader->setGamma(2.5f / fontSize / transform.getPixelRatio());
textShader->setGamma(2.5f / fontSize / map.getState().getPixelRatio());
// Convert the -pi..pi to an int8 range.
float angle = round((transform.getAngle() + rotate) / M_PI * 128);
float angle = round((map.getState().getAngle() + rotate) / M_PI * 128);
// adjust min/max zooms for variable font sies
float zoomAdjust = log(fontSize / bucket.geom_desc.font_size) / log(2);
textShader->setAngle((int32_t)(angle + 256) % 256);
textShader->setFlip(bucket.geom_desc.path == TextPathType::Curve ? 1 : 0);
textShader->setZoom((transform.getNormalizedZoom() - zoomAdjust) * 10); // current zoom level
textShader->setZoom((map.getState().getNormalizedZoom() - zoomAdjust) * 10); // current zoom level
// Label fading
const float duration = 300.0f;
const float currentTime = platform::elapsed() * 1000;
const float currentTime = util::now() / 1_millisecond;
std::deque<FrameSnapshot> &history = frameHistory.history;
@@ -606,7 +638,7 @@ void Painter::renderText(TextBucket& bucket, const std::string& layer_name, cons
textShader->setFadeDist(fadedist * 10);
textShader->setMinFadeZoom(floor(lowZ * 10));
textShader->setMaxFadeZoom(floor(highZ * 10));
textShader->setFadeZoom((transform.getZoom() + bump) * 10);
textShader->setFadeZoom((map.getState().getZoom() + bump) * 10);
// We're drawing in the translucent pass which is bottom-to-top, so we need
// to draw the halo first.
@@ -638,16 +670,16 @@ void Painter::renderDebug(const TileData::Ptr& tile_data) {
// draw tile outline
tileBorderArray.bind(*plainShader, tileBorderBuffer, BUFFER_OFFSET(0));
plainShader->setColor(1.0f, 0.0f, 0.0f, 1.0f);
lineWidth(4.0f * transform.getPixelRatio());
lineWidth(4.0f * map.getState().getPixelRatio());
glDrawArrays(GL_LINE_STRIP, 0, (GLsizei)tileBorderBuffer.index());
// draw debug info
tile_data->debugFontArray.bind(*plainShader, tile_data->debugFontBuffer, BUFFER_OFFSET(0));
plainShader->setColor(1.0f, 1.0f, 1.0f, 1.0f);
lineWidth(4.0f * transform.getPixelRatio());
lineWidth(4.0f * map.getState().getPixelRatio());
glDrawArrays(GL_LINES, 0, (GLsizei)tile_data->debugFontBuffer.index());
plainShader->setColor(0.0f, 0.0f, 0.0f, 1.0f);
lineWidth(2.0f * transform.getPixelRatio());
lineWidth(2.0f * map.getState().getPixelRatio());
glDrawArrays(GL_LINES, 0, (GLsizei)tile_data->debugFontBuffer.index());
glEnable(GL_DEPTH_TEST);

View File

@@ -4,6 +4,7 @@
#include <string>
#include <llmr/platform/platform.hpp>
#include <llmr/util/uv.hpp>
#include <rapidjson/document.h>
@@ -24,46 +25,56 @@ ImagePosition::ImagePosition(const vec2<uint16_t>& size, vec2<float> tl, vec2<fl
Sprite::Sprite(Map &map, float pixelRatio) : pixelRatio(pixelRatio), raster(std::make_shared<Raster>()), map(map) {}
void Sprite::load(const std::string& base_url) {
std::shared_ptr<Sprite> sprite = shared_from_this();
std::shared_ptr<Sprite> sprite = shared_from_this();
auto complete = [sprite]() {
std::lock_guard<std::mutex> lock(sprite->mtx);
if (*sprite->raster && sprite->pos.size()) {
sprite->loaded = true;
sprite->map.redraw();
fprintf(stderr, "sprite loaded\n");
}
};
std::string suffix = (pixelRatio > 1 ? "@2x" : "");
std::string suffix = (pixelRatio > 1 ? "@2x" : "");
platform::request_http(base_url + suffix + ".json", [sprite](platform::Response *res) {
if (res->code == 200) {
sprite->body.swap(res->body);
sprite->asyncParseJSON();
} else {
fprintf(stderr, "failed to load sprite\n");
}
}, map.getLoop());
platform::request_http(base_url + suffix + ".json", [sprite](const platform::Response *res) {
if (res->code == 200) {
sprite->parseJSON(res->body);
} else {
fprintf(stderr, "failed to load sprite\n");
}
}, complete, map.getLoop());
platform::request_http(base_url + suffix + ".png", [sprite](const platform::Response *res) {
if (res->code == 200) {
platform::request_http(base_url + suffix + ".png", [sprite](platform::Response *res) {
if (res->code == 200) {
// TODO: move raster loading to other thread
// TODO: call complete handler
sprite->raster->load(res->body);
} else {
fprintf(stderr, "failed to load sprite image\n");
}
}, complete, map.getLoop());
} else {
fprintf(stderr, "failed to load sprite image\n");
}
}, map.getLoop());
}
Sprite::operator bool() const {
void Sprite::complete(std::shared_ptr<Sprite> &sprite) {
std::lock_guard<std::mutex> lock(sprite->mtx);
if (*sprite->raster && sprite->pos.size()) {
sprite->loaded = true;
sprite->map.update();
fprintf(stderr, "sprite loaded\n");
}
}
bool Sprite::isLoaded() const {
std::lock_guard<std::mutex> lock(mtx);
return loaded;
}
void Sprite::parseJSON(const std::string& data) {
std::lock_guard<std::mutex> lock(mtx);
void Sprite::asyncParseJSON() {
new uv::work<std::shared_ptr<Sprite>>(map.getLoop(),
parseJSON,
complete,
shared_from_this());
}
void Sprite::parseJSON(std::shared_ptr<Sprite> &sprite) {
std::lock_guard<std::mutex> lock(sprite->mtx);
rapidjson::Document d;
d.Parse<0>(data.c_str());
d.Parse<0>(sprite->body.c_str());
if (d.IsObject()) {
for (rapidjson::Value::ConstMemberIterator itr = d.MemberBegin(); itr != d.MemberEnd(); ++itr) {
@@ -83,7 +94,7 @@ void Sprite::parseJSON(const std::string& data) {
if (value.HasMember("height")) height = value["height"].GetInt();
if (value.HasMember("pixelRatio")) pixelRatio = value["height"].GetInt();
pos.insert({ name, { x, y, width, height, pixelRatio } });
sprite->pos.insert({ name, { x, y, width, height, pixelRatio } });
}
}
}

View File

@@ -6,6 +6,7 @@
#include <llmr/platform/platform.hpp>
#include <llmr/platform/gl.hpp>
#include <llmr/util/time.hpp>
#include <png.h>
@@ -182,7 +183,7 @@ void Raster::bind(bool linear) {
}
void Raster::beginFadeInAnimation() {
double start = platform::elapsed();
double start = (double)util::now() / 1_second;
fade_animation = std::make_shared<util::ease_animation>(opacity, 1.0, opacity, start, 0.25);
}

35
src/util/timer.cpp Normal file
View File

@@ -0,0 +1,35 @@
#ifndef DISABLE_TIMER
#include <llmr/util/timer.hpp>
#include <llmr/util/time.hpp>
#include <iostream>
#include <atomic>
using namespace llmr::util;
std::atomic<int> indent;
timer::timer() : start(now()) {
indent++;
}
timer::timer(const std::string &name) : name(name), start(now()) {
indent++;
}
void timer::report(const std::string &name) {
time duration = now() - start;
std::cerr << std::string((indent - 1) * 4, ' ')
<< name << ": "
<< (double)(duration) / 1_millisecond
<< "ms" << std::endl;
start += duration;
}
timer::~timer() {
if (name.size()) {
report(name);
}
indent--;
}
#endif

View File

@@ -11,23 +11,24 @@
#include <uv.h>
double llmr::platform::elapsed() {
return 0;
}
class View : public llmr::View {
public:
void make_active() {
CGLError error = CGLSetCurrentContext(gl_context);
if (error) {
fprintf(stderr, "Switching OpenGL context failed\n");
}
}
void swap() {}
void llmr::platform::restart() {
// noop
}
CGLContextObj gl_context;
};
TEST(Headless, initialize) {
llmr::Settings settings;
llmr::Map map(settings);
llmr::util::timer timer;
// Setup OpenGL
CGLContextObj gl_context;
View view;
// TODO: test if OpenGL 4.1 with GL_ARB_ES2_compatibility is supported
// If it is, use kCGLOGLPVersion_3_2_Core and enable that extension.
@@ -46,18 +47,14 @@ TEST(Headless, initialize) {
return;
}
error = CGLCreateContext(pixelFormat, NULL, &gl_context);
error = CGLCreateContext(pixelFormat, NULL, &view.gl_context);
CGLDestroyPixelFormat(pixelFormat);
if (error) {
fprintf(stderr, "Error creating GL context object\n");
return;
}
error = CGLSetCurrentContext(gl_context);
if (error) {
fprintf(stderr, "Switching OpenGL context failed\n");
return;
}
view.make_active();
timer.report("gl setup");
@@ -102,31 +99,18 @@ TEST(Headless, initialize) {
timer.report("gl framebuffer");
map.setup();
timer.report("map setup");
llmr::Map map(view);
map.resize(width, height);
timer.report("map resize");
map.loadSettings();
timer.report("map settings");
map.update();
timer.report("map update");
// Run the loop. It will terminate when we don't have any further listeners.
map.run();
timer.report("map loop");
map.render();
timer.report("map render");
uint32_t *pixels = new uint32_t[width * height];
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
@@ -149,7 +133,7 @@ TEST(Headless, initialize) {
glDeleteTextures(1, &fbo_color);
glDeleteRenderbuffersEXT(1, &fbo_depth_stencil);
CGLDestroyContext(gl_context);
CGLDestroyContext(view.gl_context);
timer.report("destruct");
}