diff --git a/common/curl_request.cpp b/common/curl_request.cpp index e00e80578..0702e1e72 100644 --- a/common/curl_request.cpp +++ b/common/curl_request.cpp @@ -88,10 +88,9 @@ static std::queue curl_handle_cache; class CURLRequest : public llmr::platform::Request { public: CURLRequest(const std::string &url, - std::function background_function, - std::function foreground_callback, + std::function 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(); - // 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_http(const std::string &url, - std::function background_function, - std::function foreground_callback, + std::function callback, uv_loop_t *loop) { using namespace request; init_thread_once(thread_init_cb); - std::shared_ptr req = std::make_shared(url, background_function, foreground_callback, loop); + std::shared_ptr req = std::make_shared(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 diff --git a/common/foundation_request.mm b/common/foundation_request.mm index c0b12cc73..d18834946 100644 --- a/common/foundation_request.mm +++ b/common/foundation_request.mm @@ -40,10 +40,9 @@ void request_initialize_cb() { class FoundationRequest : public llmr::platform::Request { public: FoundationRequest(const std::string &url, - std::function background_function, - std::function foreground_callback, + std::function 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_http(const std::string &url, - std::function background_function, - std::function foreground_callback, + std::function callback, uv_loop_t *loop) { uv_once(&request_initialize, request_initialize_cb); std::shared_ptr req = - std::make_shared(url, background_function, foreground_callback, loop); + std::make_shared(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(); - 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; diff --git a/common/glfw_view.cpp b/common/glfw_view.cpp index d3034016f..cfa1156a1 100644 --- a/common/glfw_view.cpp +++ b/common/glfw_view.cpp @@ -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 -# include -# include -#endif - -#if defined(HAVE_EPOLL) -# include -#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(); -} - } } diff --git a/common/glfw_view.hpp b/common/glfw_view.hpp index 5dbb64efb..7c9f41811 100644 --- a/common/glfw_view.hpp +++ b/common/glfw_view.hpp @@ -5,13 +5,16 @@ #include #include -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); diff --git a/include/llmr/llmr.hpp b/include/llmr/llmr.hpp index 88d20d268..1bf737483 100644 --- a/include/llmr/llmr.hpp +++ b/include/llmr/llmr.hpp @@ -2,6 +2,7 @@ #define LLMR_MAIN #include "map/map.hpp" +#include "map/view.hpp" #include "map/settings.hpp" #endif diff --git a/include/llmr/map/map.hpp b/include/llmr/map/map.hpp index d7c88b128..c1cc7c370 100644 --- a/include/llmr/map/map.hpp +++ b/include/llmr/map/map.hpp @@ -3,8 +3,9 @@ #include -#include -#include +#include +// #include +// #include #include #include #include @@ -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> 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; }; } diff --git a/include/llmr/map/source.hpp b/include/llmr/map/source.hpp index 029bc7873..6a2e1362b 100644 --- a/include/llmr/map/source.hpp +++ b/include/llmr/map/source.hpp @@ -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 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; diff --git a/include/llmr/map/tile_data.hpp b/include/llmr/map/tile_data.hpp index fc03a8507..fb9eceedf 100644 --- a/include/llmr/map/tile_data.hpp +++ b/include/llmr/map/tile_data.hpp @@ -59,7 +59,8 @@ public: ~TileData(); void request(); - bool parse(); + void scheduleParsing(); + void parse(); void cancel(); const std::string toString() const; diff --git a/include/llmr/map/transform.hpp b/include/llmr/map/transform.hpp index 6cf0295ef..ec4011e12 100644 --- a/include/llmr/map/transform.hpp +++ b/include/llmr/map/transform.hpp @@ -1,11 +1,11 @@ #ifndef LLMR_MAP_TRANSFORM #define LLMR_MAP_TRANSFORM -#include -#include #include #include -#include +#include + +#include "transform_state.hpp" #include @@ -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; diff --git a/include/llmr/map/transform_state.hpp b/include/llmr/map/transform_state.hpp new file mode 100644 index 000000000..6c360b3f3 --- /dev/null +++ b/include/llmr/map/transform_state.hpp @@ -0,0 +1,68 @@ +#ifndef LLMR_MAP_TRANSFORM_STATE +#define LLMR_MAP_TRANSFORM_STATE + +#include +#include + +#include + +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 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 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 diff --git a/include/llmr/map/view.hpp b/include/llmr/map/view.hpp new file mode 100644 index 000000000..8f198e3c4 --- /dev/null +++ b/include/llmr/map/view.hpp @@ -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 diff --git a/include/llmr/platform/platform.hpp b/include/llmr/platform/platform.hpp index b8ec955c7..deeb397b4 100644 --- a/include/llmr/platform/platform.hpp +++ b/include/llmr/platform/platform.hpp @@ -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 callback) : callback(callback) {} int16_t code = -1; std::string body; - std::string error_message; + std::function 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_http(const std::string &url, - std::function background_function, - std::function foreground_callback, + std::function callback, uv_loop_t *loop); // Cancels an HTTP request. void cancel_request_http(const std::shared_ptr &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); diff --git a/include/llmr/platform/request.hpp b/include/llmr/platform/request.hpp index 0aa8fc639..974f854bf 100644 --- a/include/llmr/platform/request.hpp +++ b/include/llmr/platform/request.hpp @@ -4,13 +4,13 @@ #include #include #include +#include #include // 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, private util::noncopyable { public: Request(const std::string &url, - std::function background_function, - std::function foreground_callback, + std::function 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 background_function; - const std::function foreground_callback; std::unique_ptr res; - bool cancelled = false; + std::atomic cancelled; public: - uv_check_t *check = nullptr; + uv_async_t *async = nullptr; uv_loop_t *loop = nullptr; }; } diff --git a/include/llmr/renderer/painter.hpp b/include/llmr/renderer/painter.hpp index 4f56dbbbb..c611f56d4 100644 --- a/include/llmr/renderer/painter.hpp +++ b/include/llmr/renderer/painter.hpp @@ -17,6 +17,8 @@ #include #include +#include + 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 gl_viewport = {{ 0, 0 }}; float strata = 0; const float strata_epsilon = 1.0f / (1 << 16); enum { Opaque, Translucent } pass = Opaque; diff --git a/include/llmr/style/sprite.hpp b/include/llmr/style/sprite.hpp index 450b47aee..fa0f86ecf 100644 --- a/include/llmr/style/sprite.hpp +++ b/include/llmr/style/sprite.hpp @@ -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; private: - void parseJSON(const std::string& data); + void asyncParseJSON(); + + static void parseJSON(std::shared_ptr &sprite); + static void complete(std::shared_ptr &sprite); private: Map ↦ mutable std::mutex mtx; + std::string body; bool loaded = false; std::map pos; }; diff --git a/include/llmr/util/time.hpp b/include/llmr/util/time.hpp new file mode 100644 index 000000000..f9e3cc05c --- /dev/null +++ b/include/llmr/util/time.hpp @@ -0,0 +1,42 @@ +#ifndef LLMR_UTIL_TIME +#define LLMR_UTIL_TIME + +#include + +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 diff --git a/include/llmr/util/uv.hpp b/include/llmr/util/uv.hpp index 3a1f48980..7ad7fd6d1 100644 --- a/include/llmr/util/uv.hpp +++ b/include/llmr/util/uv.hpp @@ -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 +class work { +public: + typedef void (*work_callback)(T &object); + typedef void (*after_work_callback)(T &object); + + template + work(uv_loop_t *loop, work_callback work_cb, after_work_callback after_work_cb, Args&&... args) + : data(std::forward(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 *w = static_cast *>(req->data); + w->work_cb(w->data); + } + + static void after_work(uv_work_t *req, int) { + work *w = static_cast *>(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 diff --git a/ios/MBXViewController.h b/ios/MBXViewController.h index c66114ffa..924d3af60 100644 --- a/ios/MBXViewController.h +++ b/ios/MBXViewController.h @@ -1,6 +1,5 @@ #import -#import -@interface MBXViewController : GLKViewController +@interface MBXViewController : UIViewController @end diff --git a/ios/MBXViewController.mm b/ios/MBXViewController.mm index cc18e97ed..c6f3b1bc4 100644 --- a/ios/MBXViewController.mm +++ b/ios/MBXViewController.mm @@ -6,6 +6,7 @@ #import #import +#import #include #include @@ -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 diff --git a/linux/main.cpp b/linux/main.cpp index 49de8a2ac..32ae04cca 100644 --- a/linux/main.cpp +++ b/linux/main.cpp @@ -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); } diff --git a/src/map/map.cpp b/src/map/map.cpp index 019ba97c6..feaf14f6c 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -1,65 +1,130 @@ #include -#include #include #include #include #include #include +#include #include #include 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(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(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(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(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(async->data)); +} + +#pragma mark - Setup + +void Map::setup() { + view.make_active(); + + painter.setup(); sources.emplace("mapbox streets", std::unique_ptr(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(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(*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 = 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(*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(); } } diff --git a/src/map/source.cpp b/src/map/source.cpp index be464aaf2..0b1e2925d 100644 --- a/src/map/source.cpp +++ b/src/map/source.cpp @@ -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 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(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 a, const llmr::vec2 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 Source::covering_tiles(int32_t zoom, const box& points) { diff --git a/src/map/tile_data.cpp b/src/map/tile_data.cpp index 905f5c745..5cfada7ff 100644 --- a/src/map/tile_data.cpp +++ b/src/map/tile_data.cpp @@ -10,7 +10,7 @@ #include #include -#include +#include 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 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>( + map.getLoop(), + [](std::shared_ptr &tile) { tile->parse(); }, + [](std::shared_ptr &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; } diff --git a/src/map/transform.cpp b/src/map/transform.cpp index aed7afba9..10bb43f4d 100644 --- a/src/map/transform.cpp +++ b/src/map/transform.cpp @@ -3,113 +3,280 @@ #include #include #include +#include #include #include 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& 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(x, xn, x, start, duration)); - animations.emplace_front(std::make_shared(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(current.x, final.x, current.x, start, duration)); + animations.emplace_front( + std::make_shared(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>(false, panning, start, 0.2); + current.panning = true; + double start = (double)util::now() / 1_second; + pan_timeout = std::make_shared>(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>(false, scaling, start, 0.2); + current.scaling = true; + double start = (double)util::now() / 1_second; + scale_timeout = std::make_shared>(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( + current.scale, final.scale, current.scale, start, duration)); + animations.emplace_front( + std::make_shared(current.x, final.x, current.x, start, duration)); + animations.emplace_front( + std::make_shared(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(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( + 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>(false, rotating, start, 0.2); + current.rotating = true; + double start = (double)util::now() / 1_second; + rotate_timeout = std::make_shared>(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(scale, new_scale, scale, start, duration)); - animations.emplace_front(std::make_shared(x, xn, x, start, duration)); - animations.emplace_front(std::make_shared(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 &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; -} - diff --git a/src/map/transform_state.cpp b/src/map/transform_state.cpp new file mode 100644 index 000000000..c6f8be71b --- /dev/null +++ b/src/map/transform_state.cpp @@ -0,0 +1,132 @@ +#include +#include + +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 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; +} diff --git a/src/platform/request.cpp b/src/platform/request.cpp index 36b2dc77e..aadf83dea 100644 --- a/src/platform/request.cpp +++ b/src/platform/request.cpp @@ -1,46 +1,46 @@ #include #include +#include #include using namespace llmr::platform; Request::Request(const std::string &url, - std::function background_function, - std::function foreground_callback, + std::function callback, uv_loop_t *loop) : url(url), - background_function(background_function), - foreground_callback(foreground_callback), + res(std::make_unique(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(); + 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 *req = static_cast *>(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 *)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 *req = static_cast *>(work->data); +void Request::complete(uv_async_t *async) { + Response *res = static_cast *>(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 *>(handle->data); + delete (uv_async_t *)handle; + }); } diff --git a/src/renderer/frame_history.cpp b/src/renderer/frame_history.cpp index 2cbc6f1bc..229c15ae4 100644 --- a/src/renderer/frame_history.cpp +++ b/src/renderer/frame_history.cpp @@ -1,4 +1,5 @@ #include +#include 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(platform::elapsed() * 1000), zoom}); + history.emplace_back(FrameSnapshot{static_cast((double) util::now() / 1_millisecond), zoom}); } } diff --git a/src/renderer/painter.cpp b/src/renderer/painter.cpp index 944a5083b..73b5779cf 100644 --- a/src/renderer/painter.cpp +++ b/src/renderer/painter.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -20,15 +21,14 @@ #include #include #include +#include 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& 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& tile_data, const Laye void Painter::renderRaster(const std::string& layer_name, const std::shared_ptr& 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(transform.getFramebufferWidth()), - static_cast(transform.getFramebufferHeight()) + static_cast(map.getState().getFramebufferWidth()), + static_cast(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 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(transform.getFramebufferWidth()), - static_cast(transform.getFramebufferHeight()) + static_cast(map.getState().getFramebufferWidth()), + static_cast(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(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(glyphAtlas.width), static_cast(glyphAtlas.height) }}); + map.getGlyphAtlas().bind(); + textShader->setTextureSize({{static_cast(map.getGlyphAtlas().width), + static_cast(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 &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); diff --git a/src/style/sprite.cpp b/src/style/sprite.cpp index f46457cb9..74aa9fa73 100644 --- a/src/style/sprite.cpp +++ b/src/style/sprite.cpp @@ -4,6 +4,7 @@ #include #include +#include #include @@ -24,46 +25,56 @@ ImagePosition::ImagePosition(const vec2& size, vec2 tl, vec2()), map(map) {} void Sprite::load(const std::string& base_url) { - std::shared_ptr sprite = shared_from_this(); + std::shared_ptr sprite = shared_from_this(); - auto complete = [sprite]() { - std::lock_guard 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) { + std::lock_guard 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 lock(mtx); return loaded; } -void Sprite::parseJSON(const std::string& data) { - std::lock_guard lock(mtx); +void Sprite::asyncParseJSON() { + new uv::work>(map.getLoop(), + parseJSON, + complete, + shared_from_this()); +} + +void Sprite::parseJSON(std::shared_ptr &sprite) { + std::lock_guard 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 } }); } } } diff --git a/src/util/raster.cpp b/src/util/raster.cpp index 0de63f125..31eae5951 100644 --- a/src/util/raster.cpp +++ b/src/util/raster.cpp @@ -6,6 +6,7 @@ #include #include +#include #include @@ -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(opacity, 1.0, opacity, start, 0.25); } diff --git a/src/util/timer.cpp b/src/util/timer.cpp new file mode 100644 index 000000000..56ad4fbbe --- /dev/null +++ b/src/util/timer.cpp @@ -0,0 +1,35 @@ +#ifndef DISABLE_TIMER +#include +#include + +#include +#include + +using namespace llmr::util; + +std::atomic 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 diff --git a/test/headless.cpp b/test/headless.cpp index 0da898245..c515b36c9 100644 --- a/test/headless.cpp +++ b/test/headless.cpp @@ -11,23 +11,24 @@ #include -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"); }