diff options
Diffstat (limited to 'src')
92 files changed, 2424 insertions, 2252 deletions
diff --git a/src/mbgl/geometry/buffer.hpp b/src/mbgl/geometry/buffer.hpp index 4198425ecf..7e3ced4424 100644 --- a/src/mbgl/geometry/buffer.hpp +++ b/src/mbgl/geometry/buffer.hpp @@ -2,6 +2,7 @@ #define MBGL_GEOMETRY_BUFFER #include <mbgl/platform/gl.hpp> +#include <mbgl/platform/log.hpp> #include <mbgl/util/noncopyable.hpp> #include <mbgl/map/environment.hpp> @@ -38,17 +39,16 @@ public: } // Transfers this buffer to the GPU and binds the buffer to the GL context. - void bind(bool force = false) { - if (buffer == 0) { + void bind() { + if (buffer) { + MBGL_CHECK_ERROR(glBindBuffer(bufferType, buffer)); + } else { MBGL_CHECK_ERROR(glGenBuffers(1, &buffer)); - force = true; - } - MBGL_CHECK_ERROR(glBindBuffer(bufferType, buffer)); - if (force) { + MBGL_CHECK_ERROR(glBindBuffer(bufferType, buffer)); if (array == nullptr) { - throw std::runtime_error("Buffer was already deleted or doesn't contain elements"); + Log::Debug(Event::OpenGL, "Buffer doesn't contain elements"); + pos = 0; } - MBGL_CHECK_ERROR(glBufferData(bufferType, pos, array, GL_STATIC_DRAW)); if (!retainAfterUpload) { cleanup(); @@ -67,6 +67,13 @@ public: return buffer; } + // Uploads the buffer to the GPU to be available when we need it. + inline void upload() { + if (!buffer) { + bind(); + } + } + protected: // increase the buffer size by at least /required/ bytes. inline void *addElement() { diff --git a/src/mbgl/geometry/elements_buffer.cpp b/src/mbgl/geometry/elements_buffer.cpp index 79af1b7e35..3e2e2794dd 100644 --- a/src/mbgl/geometry/elements_buffer.cpp +++ b/src/mbgl/geometry/elements_buffer.cpp @@ -14,8 +14,3 @@ void LineElementsBuffer::add(element_type a, element_type b) { elements[0] = a; elements[1] = b; } - -void PointElementsBuffer::add(element_type a) { - uint16_t *data = static_cast<element_type *>(addElement()); - data[0] = a; -} diff --git a/src/mbgl/geometry/elements_buffer.hpp b/src/mbgl/geometry/elements_buffer.hpp index 5c1b421d35..24753ebafe 100644 --- a/src/mbgl/geometry/elements_buffer.hpp +++ b/src/mbgl/geometry/elements_buffer.hpp @@ -44,16 +44,6 @@ public: void add(element_type a, element_type b); }; -class PointElementsBuffer : public Buffer< - 2, // bytes per point (1 unsigned short) - GL_ELEMENT_ARRAY_BUFFER -> { -public: - typedef uint16_t element_type; - - void add(element_type a); -}; - } #endif diff --git a/src/mbgl/geometry/glyph_atlas.cpp b/src/mbgl/geometry/glyph_atlas.cpp index f690004b52..f2978de980 100644 --- a/src/mbgl/geometry/glyph_atlas.cpp +++ b/src/mbgl/geometry/glyph_atlas.cpp @@ -124,8 +124,6 @@ void GlyphAtlas::removeGlyphs(uintptr_t tileUID) { } } - dirty = true; - bin.release(rect); // Make sure to post-increment the iterator: This will return the @@ -140,6 +138,47 @@ void GlyphAtlas::removeGlyphs(uintptr_t tileUID) { } } +void GlyphAtlas::upload() { + if (dirty) { + const bool first = !texture; + bind(); + + std::lock_guard<std::mutex> lock(mtx); + + if (first) { + MBGL_CHECK_ERROR(glTexImage2D( + GL_TEXTURE_2D, // GLenum target + 0, // GLint level + GL_ALPHA, // GLint internalformat + width, // GLsizei width + height, // GLsizei height + 0, // GLint border + GL_ALPHA, // GLenum format + GL_UNSIGNED_BYTE, // GLenum type + data.get() // const GLvoid* data + )); + } else { + MBGL_CHECK_ERROR(glTexSubImage2D( + GL_TEXTURE_2D, // GLenum target + 0, // GLint level + 0, // GLint xoffset + 0, // GLint yoffset + width, // GLsizei width + height, // GLsizei height + GL_ALPHA, // GLenum format + GL_UNSIGNED_BYTE, // GLenum type + data.get() // const GLvoid* data + )); + } + + dirty = false; + +#if defined(DEBUG) + // platform::showDebugImage("Glyph Atlas", data.get(), width, height); +#endif + } +} + void GlyphAtlas::bind() { if (!texture) { MBGL_CHECK_ERROR(glGenTextures(1, &texture)); @@ -154,14 +193,4 @@ void GlyphAtlas::bind() { } else { MBGL_CHECK_ERROR(glBindTexture(GL_TEXTURE_2D, texture)); } - - if (dirty) { - std::lock_guard<std::mutex> lock(mtx); - MBGL_CHECK_ERROR(glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, width, height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, data.get())); - dirty = false; - -#if defined(DEBUG) - // platform::showDebugImage("Glyph Atlas", data, width, height); -#endif - } }; diff --git a/src/mbgl/geometry/glyph_atlas.hpp b/src/mbgl/geometry/glyph_atlas.hpp index a25c735a8e..dfa568f0fd 100644 --- a/src/mbgl/geometry/glyph_atlas.hpp +++ b/src/mbgl/geometry/glyph_atlas.hpp @@ -24,8 +24,13 @@ public: GlyphPositions&); void removeGlyphs(uintptr_t tileUID); + // Binds the atlas texture to the GPU, and uploads data if it is out of date. void bind(); + // Uploads the texture to the GPU to be available when we need it. This is a lazy operation; + // the texture is only bound when the data is out of date (=dirty). + void upload(); + const uint16_t width = 0; const uint16_t height = 0; diff --git a/src/mbgl/geometry/line_atlas.cpp b/src/mbgl/geometry/line_atlas.cpp index f64989d661..42d2380777 100644 --- a/src/mbgl/geometry/line_atlas.cpp +++ b/src/mbgl/geometry/line_atlas.cpp @@ -129,6 +129,12 @@ LinePatternPos LineAtlas::addDash(const std::vector<float> &dasharray, bool roun return position; }; +void LineAtlas::upload() { + if (dirty) { + bind(); + } +} + void LineAtlas::bind() { std::lock_guard<std::recursive_mutex> lock(mtx); @@ -147,7 +153,7 @@ void LineAtlas::bind() { if (dirty) { if (first) { - glTexImage2D( + MBGL_CHECK_ERROR(glTexImage2D( GL_TEXTURE_2D, // GLenum target 0, // GLint level GL_ALPHA, // GLint internalformat @@ -157,9 +163,9 @@ void LineAtlas::bind() { GL_ALPHA, // GLenum format GL_UNSIGNED_BYTE, // GLenum type data // const GLvoid * data - ); + )); } else { - glTexSubImage2D( + MBGL_CHECK_ERROR(glTexSubImage2D( GL_TEXTURE_2D, // GLenum target 0, // GLint level 0, // GLint xoffset @@ -169,7 +175,7 @@ void LineAtlas::bind() { GL_ALPHA, // GLenum format GL_UNSIGNED_BYTE, // GLenum type data // const GLvoid *pixels - ); + )); } diff --git a/src/mbgl/geometry/line_atlas.hpp b/src/mbgl/geometry/line_atlas.hpp index df60a2dec5..3683272f98 100644 --- a/src/mbgl/geometry/line_atlas.hpp +++ b/src/mbgl/geometry/line_atlas.hpp @@ -19,8 +19,13 @@ public: LineAtlas(uint16_t width, uint16_t height); ~LineAtlas(); + // Binds the atlas texture to the GPU, and uploads data if it is out of date. void bind(); + // Uploads the texture to the GPU to be available when we need it. This is a lazy operation; + // the texture is only bound when the data is out of date (=dirty). + void upload(); + LinePatternPos getDashPosition(const std::vector<float>&, bool); LinePatternPos addDash(const std::vector<float> &dasharray, bool round); diff --git a/src/mbgl/geometry/sprite_atlas.cpp b/src/mbgl/geometry/sprite_atlas.cpp index c2686e2f34..9d0aeac8b7 100644 --- a/src/mbgl/geometry/sprite_atlas.cpp +++ b/src/mbgl/geometry/sprite_atlas.cpp @@ -58,6 +58,7 @@ bool SpriteAtlas::resize(const float newRatio) { ::operator delete(old_data); dirty = true; + fullUploadRequired = true; // Mark all sprite images as in need of update for (const auto &pair : images) { @@ -230,8 +231,13 @@ void SpriteAtlas::setSprite(util::ptr<Sprite> sprite_) { }); } +void SpriteAtlas::upload() { + if (dirty) { + bind(); + } +} + void SpriteAtlas::bind(bool linear) { - bool first = false; if (!texture) { MBGL_CHECK_ERROR(glGenTextures(1, &texture)); MBGL_CHECK_ERROR(glBindTexture(GL_TEXTURE_2D, texture)); @@ -242,7 +248,7 @@ void SpriteAtlas::bind(bool linear) { // We use those when the pixelRatio isn't a power of two, e.g. on iPhone 6 Plus. MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); - first = true; + fullUploadRequired = true; } else { MBGL_CHECK_ERROR(glBindTexture(GL_TEXTURE_2D, texture)); } @@ -258,7 +264,7 @@ void SpriteAtlas::bind(bool linear) { std::lock_guard<std::recursive_mutex> lock(mtx); allocate(); - if (first) { + if (fullUploadRequired) { MBGL_CHECK_ERROR(glTexImage2D( GL_TEXTURE_2D, // GLenum target 0, // GLint level @@ -270,6 +276,7 @@ void SpriteAtlas::bind(bool linear) { GL_UNSIGNED_BYTE, // GLenum type data // const GLvoid * data )); + fullUploadRequired = false; } else { MBGL_CHECK_ERROR(glTexSubImage2D( GL_TEXTURE_2D, // GLenum target diff --git a/src/mbgl/geometry/sprite_atlas.hpp b/src/mbgl/geometry/sprite_atlas.hpp index 079c15cefd..6c4a381aa1 100644 --- a/src/mbgl/geometry/sprite_atlas.hpp +++ b/src/mbgl/geometry/sprite_atlas.hpp @@ -50,10 +50,13 @@ public: SpriteAtlasPosition getPosition(const std::string& name, bool repeating = false); - // Binds the image buffer of this sprite atlas to the GPU, and uploads data if it is out - // of date. + // Binds the atlas texture to the GPU, and uploads data if it is out of date. void bind(bool linear = false); + // Uploads the texture to the GPU to be available when we need it. This is a lazy operation; + // the texture is only bound when the data is out of date (=dirty). + void upload(); + inline float getWidth() const { return width; } inline float getHeight() const { return height; } inline float getTextureWidth() const { return width * pixelRatio; } @@ -76,6 +79,7 @@ private: std::set<std::string> uninitialized; uint32_t *data = nullptr; std::atomic<bool> dirty; + bool fullUploadRequired = true; uint32_t texture = 0; uint32_t filter = 0; static const int buffer = 1; diff --git a/src/mbgl/map/annotation.cpp b/src/mbgl/map/annotation.cpp index b921c08f74..7a7a0ed683 100644 --- a/src/mbgl/map/annotation.cpp +++ b/src/mbgl/map/annotation.cpp @@ -2,6 +2,8 @@ #include <mbgl/map/map.hpp> #include <mbgl/map/tile_id.hpp> #include <mbgl/map/live_tile.hpp> +#include <mbgl/map/map_data.hpp> +#include <mbgl/util/constants.hpp> #include <mbgl/util/ptr.hpp> #include <mbgl/util/std.hpp> @@ -83,8 +85,10 @@ vec2<double> AnnotationManager::projectPoint(const LatLng& point) { return { x, y }; } -std::pair<std::vector<TileID>, AnnotationIDs> AnnotationManager::addPointAnnotations( - const std::vector<LatLng>& points, const std::vector<std::string>& symbols, const Map& map) { +std::pair<std::vector<TileID>, AnnotationIDs> +AnnotationManager::addPointAnnotations(const std::vector<LatLng>& points, + const std::vector<std::string>& symbols, + const MapData& data) { std::lock_guard<std::mutex> lock(mtx); // We pre-generate tiles to contain each annotation up to the map's max zoom. @@ -109,7 +113,7 @@ std::pair<std::vector<TileID>, AnnotationIDs> AnnotationManager::addPointAnnotat util::make_unique<Annotation>(AnnotationType::Point, AnnotationSegments({ { points[i] } }))); - const uint8_t maxZoom = map.getMaxZoom(); + const uint8_t maxZoom = data.transform.getMaxZoom(); // side length of map at this zoom uint32_t z2 = 1 << maxZoom; @@ -181,13 +185,14 @@ std::pair<std::vector<TileID>, AnnotationIDs> AnnotationManager::addPointAnnotat return std::make_pair(affectedTiles, annotationIDs); } -std::vector<TileID> AnnotationManager::removeAnnotations(const AnnotationIDs& ids, const Map& map) { +std::vector<TileID> AnnotationManager::removeAnnotations(const AnnotationIDs& ids, + const MapData& data) { std::lock_guard<std::mutex> lock(mtx); std::vector<TileID> affectedTiles; std::vector<uint32_t> z2s; - uint8_t zoomCount = map.getMaxZoom() + 1; + const uint8_t zoomCount = data.transform.getMaxZoom() + 1; z2s.reserve(zoomCount); for (uint8_t z = 0; z < zoomCount; ++z) { z2s.emplace_back(1<< z); @@ -231,10 +236,10 @@ std::vector<TileID> AnnotationManager::removeAnnotations(const AnnotationIDs& id } std::vector<uint32_t> AnnotationManager::getAnnotationsInBounds(const LatLngBounds& queryBounds, - const Map& map) const { + const MapData& data) const { std::lock_guard<std::mutex> lock(mtx); - const uint8_t z = map.getMaxZoom(); + const uint8_t z = data.transform.getMaxZoom(); const uint32_t z2 = 1 << z; const vec2<double> swPoint = projectPoint(queryBounds.sw); const vec2<double> nePoint = projectPoint(queryBounds.ne); diff --git a/src/mbgl/map/annotation.hpp b/src/mbgl/map/annotation.hpp index f1596dbdff..a80b03226f 100644 --- a/src/mbgl/map/annotation.hpp +++ b/src/mbgl/map/annotation.hpp @@ -19,6 +19,7 @@ namespace mbgl { class Annotation; class Map; class LiveTile; +class MapData; using AnnotationIDs = std::vector<uint32_t>; @@ -29,9 +30,9 @@ public: void setDefaultPointAnnotationSymbol(const std::string& symbol); std::pair<std::vector<TileID>, AnnotationIDs> addPointAnnotations( - const std::vector<LatLng>&, const std::vector<std::string>& symbols, const Map&); - std::vector<TileID> removeAnnotations(const AnnotationIDs&, const Map&); - AnnotationIDs getAnnotationsInBounds(const LatLngBounds&, const Map&) const; + const std::vector<LatLng>&, const std::vector<std::string>& symbols, const MapData&); + std::vector<TileID> removeAnnotations(const AnnotationIDs&, const MapData&); + AnnotationIDs getAnnotationsInBounds(const LatLngBounds&, const MapData&) const; LatLngBounds getBoundsForAnnotations(const AnnotationIDs&) const; const LiveTile* getTile(const TileID& id); diff --git a/src/mbgl/map/environment.cpp b/src/mbgl/map/environment.cpp index 469790501c..dd6c1f1933 100644 --- a/src/mbgl/map/environment.cpp +++ b/src/mbgl/map/environment.cpp @@ -1,6 +1,7 @@ #include <mbgl/map/environment.hpp> #include <mbgl/storage/file_source.hpp> #include <mbgl/platform/gl.hpp> +#include <mbgl/util/run_loop.hpp> #include <uv.h> @@ -88,7 +89,7 @@ EnvironmentScope::~EnvironmentScope() { } Environment::Environment(FileSource& fs) - : id(makeEnvironmentID()), fileSource(fs), loop(uv_loop_new()) { + : id(makeEnvironmentID()), fileSource(fs) { } Environment::~Environment() { @@ -122,13 +123,13 @@ unsigned Environment::getID() const { void Environment::requestAsync(const Resource& resource, std::function<void(const Response&)> callback) { - fileSource.request(resource, *this, std::move(callback)); + fileSource.request(resource, std::move(callback)); } Request* Environment::request(const Resource& resource, std::function<void(const Response&)> callback) { assert(currentlyOn(ThreadType::Map)); - return fileSource.request(resource, loop, *this, std::move(callback)); + return fileSource.request(resource, util::RunLoop::current.get()->get(), std::move(callback)); } void Environment::cancelRequest(Request* req) { @@ -178,10 +179,4 @@ void Environment::performCleanup() { } } -// ############################################################################################# - -void Environment::terminate() { - fileSource.abort(*this); -} - } diff --git a/src/mbgl/map/live_tile_data.cpp b/src/mbgl/map/live_tile_data.cpp index 8b4f46deb5..1582b0190e 100644 --- a/src/mbgl/map/live_tile_data.cpp +++ b/src/mbgl/map/live_tile_data.cpp @@ -11,7 +11,7 @@ using namespace mbgl; LiveTileData::LiveTileData(const TileID& id_, AnnotationManager& annotationManager_, float mapMaxZoom, - util::ptr<Style> style_, + Style& style_, GlyphAtlas& glyphAtlas_, GlyphStore& glyphStore_, SpriteAtlas& spriteAtlas_, @@ -35,29 +35,16 @@ void LiveTileData::parse() { if (tile) { try { - if (!style) { - throw std::runtime_error("style isn't present in LiveTileData object anymore"); - } - // Parsing creates state that is encapsulated in TileParser. While parsing, // the TileParser object writes results into this objects. All other state // is going to be discarded afterwards. TileParser parser(*tile, *this, style, glyphAtlas, glyphStore, spriteAtlas, sprite); - - // Clear the style so that we don't have a cycle in the shared_ptr references. - style.reset(); - parser.parse(); } catch (const std::exception& ex) { Log::Error(Event::ParseTile, "Live-parsing [%d/%d/%d] failed: %s", id.z, id.x, id.y, ex.what()); state = State::obsolete; return; } - } else { - // Clear the style so that we don't have a cycle in the shared_ptr references. - style.reset(); - - state = State::obsolete; } if (state != State::obsolete) { diff --git a/src/mbgl/map/live_tile_data.hpp b/src/mbgl/map/live_tile_data.hpp index d40cfdfd69..e56a7bf7e1 100644 --- a/src/mbgl/map/live_tile_data.hpp +++ b/src/mbgl/map/live_tile_data.hpp @@ -12,7 +12,7 @@ public: LiveTileData(const TileID&, AnnotationManager&, float mapMaxZoom, - util::ptr<Style>, + Style&, GlyphAtlas&, GlyphStore&, SpriteAtlas&, diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp index e931d09e0d..0464ed3f1e 100644 --- a/src/mbgl/map/map.cpp +++ b/src/mbgl/map/map.cpp @@ -1,541 +1,146 @@ #include <mbgl/map/map.hpp> -#include <mbgl/map/environment.hpp> +#include <mbgl/map/map_context.hpp> #include <mbgl/map/view.hpp> #include <mbgl/map/map_data.hpp> -#include <mbgl/map/still_image.hpp> -#include <mbgl/platform/platform.hpp> -#include <mbgl/map/source.hpp> -#include <mbgl/renderer/painter.hpp> -#include <mbgl/map/annotation.hpp> -#include <mbgl/map/sprite.hpp> -#include <mbgl/util/math.hpp> -#include <mbgl/util/clip_id.hpp> -#include <mbgl/util/string.hpp> -#include <mbgl/util/constants.hpp> -#include <mbgl/util/uv_detail.hpp> + #include <mbgl/util/std.hpp> -#include <mbgl/style/style.hpp> -#include <mbgl/text/glyph_store.hpp> -#include <mbgl/geometry/glyph_atlas.hpp> -#include <mbgl/style/style_layer.hpp> -#include <mbgl/style/style_bucket.hpp> -#include <mbgl/util/texture_pool.hpp> -#include <mbgl/geometry/sprite_atlas.hpp> -#include <mbgl/geometry/line_atlas.hpp> -#include <mbgl/storage/file_source.hpp> -#include <mbgl/platform/log.hpp> -#include <mbgl/util/string.hpp> -#include <mbgl/util/uv.hpp> -#include <mbgl/util/mapbox.hpp> -#include <mbgl/util/exception.hpp> -#include <mbgl/util/worker.hpp> - -#include <algorithm> -#include <iostream> - -#define _USE_MATH_DEFINES -#include <cmath> - -#include <uv.h> - -// Check libuv library version. -const static bool uvVersionCheck = []() { - const unsigned int version = uv_version(); - const unsigned int major = (version >> 16) & 0xFF; - const unsigned int minor = (version >> 8) & 0xFF; - const unsigned int patch = version & 0xFF; - -#ifndef UV_VERSION_PATCH - // 0.10 doesn't have UV_VERSION_PATCH defined, so we "fake" it by using the library patch level. - const unsigned int UV_VERSION_PATCH = version & 0xFF; -#endif - - if (major != UV_VERSION_MAJOR || minor != UV_VERSION_MINOR || patch != UV_VERSION_PATCH) { - throw std::runtime_error(mbgl::util::sprintf<96>( - "libuv version mismatch: headers report %d.%d.%d, but library reports %d.%d.%d", UV_VERSION_MAJOR, - UV_VERSION_MINOR, UV_VERSION_PATCH, major, minor, patch)); - } - return true; -}(); - -using namespace mbgl; - -Map::Map(View& view_, FileSource& fileSource_) - : env(util::make_unique<Environment>(fileSource_)), - scope(util::make_unique<EnvironmentScope>(*env, ThreadType::Main, "Main")), - view(view_), - transform(view_), - fileSource(fileSource_), - glyphAtlas(util::make_unique<GlyphAtlas>(1024, 1024)), - glyphStore(std::make_shared<GlyphStore>(*env)), - spriteAtlas(util::make_unique<SpriteAtlas>(512, 512)), - lineAtlas(util::make_unique<LineAtlas>(512, 512)), - texturePool(std::make_shared<TexturePool>()), - painter(util::make_unique<Painter>(*spriteAtlas, *glyphAtlas, *lineAtlas)), - annotationManager(util::make_unique<AnnotationManager>()), - data(util::make_unique<MapData>()), - updated(static_cast<UpdateType>(Update::Nothing)) +#include <mbgl/util/projection.hpp> +#include <mbgl/util/thread.hpp> + +namespace mbgl { + +Map::Map(View& view, FileSource& fileSource, MapMode mode, bool startPaused) + : data(util::make_unique<MapData>(view, mode)), + context(util::make_unique<util::Thread<MapContext>>("Map", util::ThreadPriority::Regular, view, fileSource, *data, startPaused)) { view.initialize(this); } Map::~Map() { - if (mode != Mode::None) { - stop(); - } - - // Extend the scope to include both Main and Map thread types to ease cleanup. - scope.reset(); - scope = util::make_unique<EnvironmentScope>( - *env, static_cast<ThreadType>(static_cast<uint8_t>(ThreadType::Main) | - static_cast<uint8_t>(ThreadType::Map)), - "MapandMain"); - - // Explicitly reset all pointers. - sprite.reset(); - glyphStore.reset(); - style.reset(); - workers.reset(); - painter.reset(); - annotationManager.reset(); - lineAtlas.reset(); - spriteAtlas.reset(); - glyphAtlas.reset(); - - uv_run(env->loop, UV_RUN_DEFAULT); - - env->performCleanup(); -} - -Worker& Map::getWorker() { - assert(workers); - return *workers; -} - -void Map::start(bool startPaused, Mode renderMode) { - assert(Environment::currentlyOn(ThreadType::Main)); - assert(mode == Mode::None); - - // When starting map rendering in another thread, we perform async/continuously - // updated rendering. Only in these cases, we attach the async handlers. - mode = renderMode; - - // Reset the flag. - isStopped = false; - - // Setup async notifications - asyncTerminate = util::make_unique<uv::async>(env->loop, [this]() { - assert(Environment::currentlyOn(ThreadType::Map)); - - // Remove all of these to make sure they are destructed in the correct thread. - style.reset(); - - // It's now safe to destroy/join the workers since there won't be any more callbacks that - // could dispatch to the worker pool. - workers.reset(); - - terminating = true; - - // Closes all open handles on the loop. This means that the loop will automatically terminate. - asyncRender.reset(); - asyncUpdate.reset(); - asyncInvoke.reset(); - asyncTerminate.reset(); - }); - - asyncUpdate = util::make_unique<uv::async>(env->loop, [this] { - // Whenever we call triggerUpdate(), we ref() the asyncUpdate handle to make sure that all - // of the calls actually get triggered. - asyncUpdate->unref(); - - update(); - }); - - asyncInvoke = util::make_unique<uv::async>(env->loop, [this] { - processTasks(); - }); - - asyncRender = util::make_unique<uv::async>(env->loop, [this] { - // Must be called in Map thread. - assert(Environment::currentlyOn(ThreadType::Map)); - - render(); - - // Finally, notify all listeners that we have finished rendering this frame. - { - std::lock_guard<std::mutex> lk(mutexRendered); - rendered = true; - } - condRendered.notify_all(); - }); - - // Do we need to pause first? - if (startPaused) { - pause(); - } - - thread = std::thread([this]() { -#ifdef __APPLE__ - pthread_setname_np("Map"); -#endif - - run(); - - // Make sure that the stop() function knows when to stop invoking the callback function. - isStopped = true; - view.notify(); - }); - - triggerUpdate(); -} - -void Map::stop(std::function<void ()> cb) { - assert(Environment::currentlyOn(ThreadType::Main)); - assert(mode != Mode::None); - - asyncTerminate->send(); - resume(); - - if (cb) { - // Wait until the render thread stopped. We are using this construct instead of plainly - // relying on the thread_join because the system might need to run things in the current - // thread that is required for the render thread to terminate correctly. This is for example - // the case with Cocoa's NSURLRequest. Otherwise, we will eventually deadlock because this - // thread (== main thread) is blocked. The callback function should use an efficient waiting - // function to avoid a busy waiting loop. - while (!isStopped) { - cb(); - } - } - - // If a callback function was provided, this should return immediately because the thread has - // already finished executing. - thread.join(); - - mode = Mode::None; } void Map::pause(bool waitForPause) { - assert(Environment::currentlyOn(ThreadType::Main)); - assert(mode == Mode::Continuous); - mutexRun.lock(); - pausing = true; - mutexRun.unlock(); + assert(data->mode == MapMode::Continuous); - uv_stop(env->loop); - triggerUpdate(); // Needed to ensure uv_stop is seen and uv_run exits, otherwise we deadlock on wait_for_pause + std::unique_lock<std::mutex> lockPause(data->mutexPause); + context->invoke(&MapContext::pause); if (waitForPause) { - std::unique_lock<std::mutex> lockPause (mutexPause); - while (!isPaused) { - condPause.wait(lockPause); - } + data->condPaused.wait(lockPause); } } void Map::resume() { - assert(Environment::currentlyOn(ThreadType::Main)); - assert(mode != Mode::None); - - mutexRun.lock(); - pausing = false; - condRun.notify_all(); - mutexRun.unlock(); -} - -void Map::renderStill(StillImageCallback fn) { - assert(Environment::currentlyOn(ThreadType::Main)); - - if (mode != Mode::Still) { - throw util::Exception("Map is not in still image render mode"); - } - - if (callback) { - throw util::Exception("Map is currently rendering an image"); - } - - assert(mode == Mode::Still); - - callback = std::move(fn); - - triggerUpdate(Update::RenderStill); + data->condResume.notify_all(); } -void Map::run() { - EnvironmentScope mapScope(*env, ThreadType::Map, "Map"); - assert(Environment::currentlyOn(ThreadType::Map)); - assert(mode != Mode::None); - - if (mode == Mode::Continuous) { - checkForPause(); - } - - auto styleInfo = data->getStyleInfo(); - - view.activate(); - view.discard(); - - workers = util::make_unique<Worker>(env->loop, 4); - - setup(); - prepare(); - - if (mode == Mode::Continuous) { - terminating = false; - while (!terminating) { - uv_run(env->loop, UV_RUN_DEFAULT); - checkForPause(); - } - } else if (mode == Mode::Still) { - terminating = false; - while (!terminating) { - uv_run(env->loop, UV_RUN_DEFAULT); - - // After the loop terminated, these async handles may have been deleted if the terminate() - // callback was fired. In this case, we are exiting the loop. - if (asyncTerminate && asyncUpdate) { - // Otherwise, loop termination means that we have acquired and parsed all resources - // required for this map image and we can now proceed to rendering. - render(); - auto image = view.readStillImage(); - - // We are moving the callback out of the way and empty it in case the callback function - // starts the next map image render. - assert(callback); - StillImageCallback cb; - std::swap(cb, callback); - - // Now we can finally invoke the callback function with the map image we rendered. - cb(std::move(image)); - - // To prepare for the next event loop run, we have to make sure the async handles keep - // the loop alive. - asyncTerminate->ref(); - asyncUpdate->ref(); - asyncInvoke->ref(); - asyncRender->ref(); - } - } - } else { - abort(); - } - - // Run the event loop once more to make sure our async delete handlers are called. - uv_run(env->loop, UV_RUN_ONCE); - - view.deactivate(); +void Map::renderStill(StillImageCallback callback) { + context->invoke(&MapContext::renderStill, callback); } void Map::renderSync() { - // Must be called in UI thread. - assert(Environment::currentlyOn(ThreadType::Main)); - - triggerRender(); - - std::unique_lock<std::mutex> lock(mutexRendered); - condRendered.wait(lock, [this] { return rendered; }); - rendered = false; + context->invokeSync(&MapContext::render); } -void Map::triggerUpdate(const Update u) { - updated |= static_cast<UpdateType>(u); - - if (asyncUpdate) { - asyncUpdate->ref(); - asyncUpdate->send(); - } +void Map::renderAsync() { + context->invoke(&MapContext::render); } -void Map::triggerRender() { - assert(mode == Mode::Continuous); - assert(asyncRender); - asyncRender->send(); +void Map::update(Update update_) { + context->invoke(&MapContext::triggerUpdate, update_); } -// Runs the function in the map thread. -void Map::invokeTask(std::function<void()>&& fn) { - { - std::lock_guard<std::mutex> lock(mutexTask); - tasks.emplace(::std::forward<std::function<void()>>(fn)); - } - - if (asyncInvoke) { - asyncInvoke->send(); - } -} - -template <typename Fn> auto Map::invokeSyncTask(const Fn& fn) -> decltype(fn()) { - std::promise<decltype(fn())> promise; - invokeTask([&fn, &promise] { promise.set_value(fn()); }); - return promise.get_future().get(); -} - -// Processes the functions that should be run in the map thread. -void Map::processTasks() { - std::queue<std::function<void()>> queue; - { - std::lock_guard<std::mutex> lock(mutexTask); - queue.swap(tasks); - } - - while (!queue.empty()) { - queue.front()(); - queue.pop(); - } -} - -void Map::checkForPause() { - std::unique_lock<std::mutex> lockRun (mutexRun); - while (pausing) { - view.deactivate(); - - mutexPause.lock(); - isPaused = true; - condPause.notify_all(); - mutexPause.unlock(); - - condRun.wait(lockRun); - - view.activate(); - } - - mutexPause.lock(); - isPaused = false; - mutexPause.unlock(); -} - -void Map::terminate() { - assert(painter); - painter->terminate(); - view.deactivate(); -} - -#pragma mark - Setup - -void Map::setup() { - assert(Environment::currentlyOn(ThreadType::Map)); - assert(painter); - painter->setup(); -} - -std::string Map::getStyleURL() const { - return data->getStyleInfo().url; -} +#pragma mark - Style void Map::setStyleURL(const std::string &url) { - assert(Environment::currentlyOn(ThreadType::Main)); - - const std::string styleURL = mbgl::util::mapbox::normalizeStyleURL(url, getAccessToken()); - - const size_t pos = styleURL.rfind('/'); - std::string base = ""; - if (pos != std::string::npos) { - base = styleURL.substr(0, pos + 1); - } - - data->setStyleInfo({ styleURL, base, "" }); - triggerUpdate(Update::StyleInfo); + context->invoke(&MapContext::setStyleURL, url); } void Map::setStyleJSON(const std::string& json, const std::string& base) { - assert(Environment::currentlyOn(ThreadType::Main)); - - data->setStyleInfo({ "", base, json }); - triggerUpdate(Update::StyleInfo); + context->invoke(&MapContext::setStyleJSON, json, base); } -std::string Map::getStyleJSON() const { - return data->getStyleInfo().json; +std::string Map::getStyleURL() const { + return context->invokeSync<std::string>(&MapContext::getStyleURL); } -util::ptr<Sprite> Map::getSprite() { - const float pixelRatio = state.getPixelRatio(); - const std::string &sprite_url = style->getSpriteURL(); - if (!sprite || !sprite->hasPixelRatio(pixelRatio)) { - sprite = Sprite::Create(sprite_url, pixelRatio, *env); - } - - return sprite; +std::string Map::getStyleJSON() const { + return context->invokeSync<std::string>(&MapContext::getStyleJSON); } - #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 fbWidth, uint16_t fbHeight) { - if (transform.resize(width, height, ratio, fbWidth, fbHeight)) { - triggerUpdate(); + if (data->transform.resize(width, height, ratio, width * ratio, height * ratio)) { + context->invoke(&MapContext::resize, width, height, ratio); } } #pragma mark - Transitions void Map::cancelTransitions() { - transform.cancelTransitions(); - triggerUpdate(); + data->transform.cancelTransitions(); + update(); } void Map::setGestureInProgress(bool inProgress) { - transform.setGestureInProgress(inProgress); - triggerUpdate(); + data->transform.setGestureInProgress(inProgress); + update(); } #pragma mark - Position void Map::moveBy(double dx, double dy, Duration duration) { - transform.moveBy(dx, dy, duration); - triggerUpdate(); + data->transform.moveBy(dx, dy, duration); + update(); } void Map::setLatLng(LatLng latLng, Duration duration) { - transform.setLatLng(latLng, duration); - triggerUpdate(); + data->transform.setLatLng(latLng, duration); + update(); } LatLng Map::getLatLng() const { - return transform.getLatLng(); + return data->transform.getLatLng(); } void Map::resetPosition() { - transform.setAngle(0); - transform.setLatLng(LatLng(0, 0)); - transform.setZoom(0); - triggerUpdate(Update::Zoom); + data->transform.setAngle(0); + data->transform.setLatLng(LatLng(0, 0)); + data->transform.setZoom(0); + update(Update::Zoom); } #pragma mark - Scale void Map::scaleBy(double ds, double cx, double cy, Duration duration) { - transform.scaleBy(ds, cx, cy, duration); - triggerUpdate(Update::Zoom); + data->transform.scaleBy(ds, cx, cy, duration); + update(Update::Zoom); } void Map::setScale(double scale, double cx, double cy, Duration duration) { - transform.setScale(scale, cx, cy, duration); - triggerUpdate(Update::Zoom); + data->transform.setScale(scale, cx, cy, duration); + update(Update::Zoom); } double Map::getScale() const { - return transform.getScale(); + return data->transform.getScale(); } void Map::setZoom(double zoom, Duration duration) { - transform.setZoom(zoom, duration); - triggerUpdate(Update::Zoom); + data->transform.setZoom(zoom, duration); + update(Update::Zoom); } double Map::getZoom() const { - return transform.getZoom(); + return data->transform.getZoom(); } void Map::setLatLngZoom(LatLng latLng, double zoom, Duration duration) { - transform.setLatLngZoom(latLng, zoom, duration); - triggerUpdate(Update::Zoom); + data->transform.setLatLngZoom(latLng, zoom, duration); + update(Update::Zoom); } void Map::resetZoom() { @@ -543,40 +148,52 @@ void Map::resetZoom() { } double Map::getMinZoom() const { - return transform.getMinZoom(); + return data->transform.getMinZoom(); } double Map::getMaxZoom() const { - return transform.getMaxZoom(); + return data->transform.getMaxZoom(); +} + + +#pragma mark - Size + +uint16_t Map::getWidth() const { + return data->transform.currentState().getWidth(); +} + +uint16_t Map::getHeight() const { + return data->transform.currentState().getHeight(); } #pragma mark - Rotation void Map::rotateBy(double sx, double sy, double ex, double ey, Duration duration) { - transform.rotateBy(sx, sy, ex, ey, duration); - triggerUpdate(); + data->transform.rotateBy(sx, sy, ex, ey, duration); + update(); } void Map::setBearing(double degrees, Duration duration) { - transform.setAngle(-degrees * M_PI / 180, duration); - triggerUpdate(); + data->transform.setAngle(-degrees * M_PI / 180, duration); + update(); } void Map::setBearing(double degrees, double cx, double cy) { - transform.setAngle(-degrees * M_PI / 180, cx, cy); - triggerUpdate(); + data->transform.setAngle(-degrees * M_PI / 180, cx, cy); + update(); } double Map::getBearing() const { - return -transform.getAngle() / M_PI * 180; + return -data->transform.getAngle() / M_PI * 180; } void Map::resetNorth() { - transform.setAngle(0, std::chrono::milliseconds(500)); - triggerUpdate(); + data->transform.setAngle(0, std::chrono::milliseconds(500)); + update(); } + #pragma mark - Access Token void Map::setAccessToken(const std::string &token) { @@ -587,22 +204,45 @@ std::string Map::getAccessToken() const { return data->getAccessToken(); } + +#pragma mark - Projection + +void Map::getWorldBoundsMeters(ProjectedMeters& sw, ProjectedMeters& ne) const { + Projection::getWorldBoundsMeters(sw, ne); +} + +void Map::getWorldBoundsLatLng(LatLng& sw, LatLng& ne) const { + Projection::getWorldBoundsLatLng(sw, ne); +} + +double Map::getMetersPerPixelAtLatitude(const double lat, const double zoom) const { + return Projection::getMetersPerPixelAtLatitude(lat, zoom); +} + +const ProjectedMeters Map::projectedMetersForLatLng(const LatLng latLng) const { + return Projection::projectedMetersForLatLng(latLng); +} + +const LatLng Map::latLngForProjectedMeters(const ProjectedMeters projectedMeters) const { + return Projection::latLngForProjectedMeters(projectedMeters); +} + +const vec2<double> Map::pixelForLatLng(const LatLng latLng) const { + return data->transform.currentState().pixelForLatLng(latLng); +} + +const LatLng Map::latLngForPixel(const vec2<double> pixel) const { + return data->transform.currentState().latLngForPixel(pixel); +} + #pragma mark - Annotations void Map::setDefaultPointAnnotationSymbol(const std::string& symbol) { - assert(Environment::currentlyOn(ThreadType::Main)); - invokeTask([=] { - annotationManager->setDefaultPointAnnotationSymbol(symbol); - }); + data->annotationManager.setDefaultPointAnnotationSymbol(symbol); } double Map::getTopOffsetPixelsForAnnotationSymbol(const std::string& symbol) { - assert(Environment::currentlyOn(ThreadType::Main)); - return invokeSyncTask([&] { - assert(sprite); - const SpritePosition pos = sprite->getSpritePosition(symbol); - return -pos.height / pos.pixelRatio / 2; - }); + return context->invokeSync<double>(&MapContext::getTopOffsetPixelsForAnnotationSymbol, symbol); } uint32_t Map::addPointAnnotation(const LatLng& point, const std::string& symbol) { @@ -610,87 +250,60 @@ uint32_t Map::addPointAnnotation(const LatLng& point, const std::string& symbol) } std::vector<uint32_t> Map::addPointAnnotations(const std::vector<LatLng>& points, const std::vector<std::string>& symbols) { - assert(Environment::currentlyOn(ThreadType::Main)); - return invokeSyncTask([&] { - auto result = annotationManager->addPointAnnotations(points, symbols, *this); - updateAnnotationTiles(result.first); - return result.second; - }); + auto result = data->annotationManager.addPointAnnotations(points, symbols, *data); + context->invoke(&MapContext::updateAnnotationTiles, result.first); + return result.second; } void Map::removeAnnotation(uint32_t annotation) { - assert(Environment::currentlyOn(ThreadType::Main)); removeAnnotations({ annotation }); } void Map::removeAnnotations(const std::vector<uint32_t>& annotations) { - assert(Environment::currentlyOn(ThreadType::Main)); - invokeTask([=] { - auto result = annotationManager->removeAnnotations(annotations, *this); - updateAnnotationTiles(result); - }); + auto result = data->annotationManager.removeAnnotations(annotations, *data); + context->invoke(&MapContext::updateAnnotationTiles, result); } std::vector<uint32_t> Map::getAnnotationsInBounds(const LatLngBounds& bounds) { - assert(Environment::currentlyOn(ThreadType::Main)); - return invokeSyncTask([&] { - return annotationManager->getAnnotationsInBounds(bounds, *this); - }); + return data->annotationManager.getAnnotationsInBounds(bounds, *data); } LatLngBounds Map::getBoundsForAnnotations(const std::vector<uint32_t>& annotations) { - assert(Environment::currentlyOn(ThreadType::Main)); - return invokeSyncTask([&] { - return annotationManager->getBoundsForAnnotations(annotations); - }); -} - -void Map::updateAnnotationTiles(const std::vector<TileID>& ids) { - assert(Environment::currentlyOn(ThreadType::Map)); - if (!style) return; - for (const auto &source : style->sources) { - if (source->info.type == SourceType::Annotations) { - source->invalidateTiles(ids); - } - } - triggerUpdate(); + return data->annotationManager.getBoundsForAnnotations(annotations); } + #pragma mark - Toggles void Map::setDebug(bool value) { data->setDebug(value); - triggerUpdate(Update::Debug); + update(Update::Debug); } void Map::toggleDebug() { data->toggleDebug(); - triggerUpdate(Update::Debug); + update(Update::Debug); } bool Map::getDebug() const { return data->getDebug(); } -TimePoint Map::getTime() const { - return data->getAnimationTime(); -} - void Map::addClass(const std::string& klass) { if (data->addClass(klass)) { - triggerUpdate(Update::Classes); + update(Update::Classes); } } void Map::removeClass(const std::string& klass) { if (data->removeClass(klass)) { - triggerUpdate(Update::Classes); + update(Update::Classes); } } void Map::setClasses(const std::vector<std::string>& classes) { data->setClasses(classes); - triggerUpdate(Update::Classes); + update(Update::Classes); } bool Map::hasClass(const std::string& klass) const { @@ -702,185 +315,20 @@ std::vector<std::string> Map::getClasses() const { } void Map::setDefaultTransitionDuration(Duration duration) { - assert(Environment::currentlyOn(ThreadType::Main)); - data->setDefaultTransitionDuration(duration); - triggerUpdate(Update::DefaultTransitionDuration); + update(Update::DefaultTransitionDuration); } Duration Map::getDefaultTransitionDuration() { - assert(Environment::currentlyOn(ThreadType::Main)); return data->getDefaultTransitionDuration(); } -void Map::updateTiles() { - assert(Environment::currentlyOn(ThreadType::Map)); - if (!style) return; - for (const auto& source : style->sources) { - source->update(*this, getWorker(), style, *glyphAtlas, *glyphStore, - *spriteAtlas, getSprite(), *texturePool, [this]() { - assert(Environment::currentlyOn(ThreadType::Map)); - triggerUpdate(); - }); - } -} - -void Map::update() { - assert(Environment::currentlyOn(ThreadType::Map)); - - if (state.hasSize()) { - prepare(); - } -} - -void Map::reloadStyle() { - assert(Environment::currentlyOn(ThreadType::Map)); - - style = std::make_shared<Style>(); - - const auto styleInfo = data->getStyleInfo(); - - if (!styleInfo.url.empty()) { - const auto base = styleInfo.base; - // We have a style URL - env->request({ Resource::Kind::JSON, styleInfo.url }, [this, base](const Response &res) { - if (res.status == Response::Successful) { - loadStyleJSON(res.data, base); - } else { - Log::Error(Event::Setup, "loading style failed: %s", res.message.c_str()); - } - }); - } else if (!styleInfo.json.empty()) { - // We got JSON data directly. - loadStyleJSON(styleInfo.json, styleInfo.base); - } -} - -void Map::loadStyleJSON(const std::string& json, const std::string& base) { - assert(Environment::currentlyOn(ThreadType::Map)); - - sprite.reset(); - style = std::make_shared<Style>(); - style->base = base; - style->loadJSON((const uint8_t *)json.c_str()); - style->cascade(data->getClasses()); - style->setDefaultTransitionDuration(data->getDefaultTransitionDuration()); - - const std::string glyphURL = util::mapbox::normalizeGlyphsURL(style->glyph_url, getAccessToken()); - glyphStore->setURL(glyphURL); - - for (const auto& source : style->sources) { - source->load(getAccessToken(), *env, [this]() { - assert(Environment::currentlyOn(ThreadType::Map)); - triggerUpdate(); - }); - } - - triggerUpdate(Update::Zoom); -} - -void Map::prepare() { - assert(Environment::currentlyOn(ThreadType::Map)); - - const auto now = Clock::now(); - data->setAnimationTime(now); - - auto u = updated.exchange(static_cast<UpdateType>(Update::Nothing)) | - transform.updateTransitions(now); - - if (!style) { - u |= static_cast<UpdateType>(Update::StyleInfo); - } - - state = transform.currentState(); - - if (u & static_cast<UpdateType>(Update::StyleInfo)) { - reloadStyle(); - } - - if (u & static_cast<UpdateType>(Update::Debug)) { - assert(painter); - painter->setDebug(data->getDebug()); - } - - if (u & static_cast<UpdateType>(Update::RenderStill)) { - // Triggers a view resize. - view.discard(); - - // Whenever we trigger an image render, we are unrefing all async handles so the loop will - // eventually terminate. However, it'll stay alive as long as there are pending requests - // (like work requests or HTTP requests). - asyncTerminate->unref(); - asyncUpdate->unref(); - asyncInvoke->unref(); - asyncRender->unref(); - } - - if (style) { - if (u & static_cast<UpdateType>(Update::DefaultTransitionDuration)) { - style->setDefaultTransitionDuration(data->getDefaultTransitionDuration()); - } - - if (u & static_cast<UpdateType>(Update::Classes)) { - style->cascade(data->getClasses()); - } - - if (u & static_cast<UpdateType>(Update::StyleInfo) || - u & static_cast<UpdateType>(Update::Classes) || - u & static_cast<UpdateType>(Update::Zoom)) { - style->recalculate(state.getNormalizedZoom(), now); - } - - // Allow the sprite atlas to potentially pull new sprite images if needed. - spriteAtlas->resize(state.getPixelRatio()); - spriteAtlas->setSprite(getSprite()); - - updateTiles(); - } - - if (mode == Mode::Continuous) { - view.invalidate(); - } +void Map::setSourceTileCacheSize(size_t size) { + context->invoke(&MapContext::setSourceTileCacheSize, size); } -void Map::render() { - assert(Environment::currentlyOn(ThreadType::Map)); - - view.discard(); - - // Cleanup OpenGL objects that we abandoned since the last render call. - env->performCleanup(); - - assert(style); - assert(painter); - - painter->render(*style, state, data->getAnimationTime()); - - // Schedule another rerender when we definitely need a next frame. - if (transform.needsTransition() || style->hasTransitions()) { - triggerUpdate(); - } +void Map::onLowMemory() { + context->invoke(&MapContext::onLowMemory); } -void Map::setSourceTileCacheSize(size_t size) { - if (size != getSourceTileCacheSize()) { - invokeTask([=] { - sourceCacheSize = size; - if (!style) return; - for (const auto &source : style->sources) { - source->setCacheSize(sourceCacheSize); - } - env->performCleanup(); - }); - } } - -void Map::onLowMemory() { - invokeTask([=] { - if (!style) return; - for (const auto &source : style->sources) { - source->onLowMemory(); - } - env->performCleanup(); - }); -}; diff --git a/src/mbgl/map/map_context.cpp b/src/mbgl/map/map_context.cpp new file mode 100644 index 0000000000..e72a174801 --- /dev/null +++ b/src/mbgl/map/map_context.cpp @@ -0,0 +1,290 @@ +#include <mbgl/map/map_context.hpp> +#include <mbgl/map/map_data.hpp> +#include <mbgl/map/view.hpp> +#include <mbgl/map/environment.hpp> +#include <mbgl/map/source.hpp> +#include <mbgl/map/sprite.hpp> +#include <mbgl/map/still_image.hpp> + +#include <mbgl/platform/log.hpp> + +#include <mbgl/renderer/painter.hpp> + +#include <mbgl/text/glyph_store.hpp> + +#include <mbgl/geometry/glyph_atlas.hpp> +#include <mbgl/geometry/sprite_atlas.hpp> +#include <mbgl/geometry/line_atlas.hpp> + +#include <mbgl/storage/resource.hpp> +#include <mbgl/storage/response.hpp> + +#include <mbgl/style/style.hpp> + +#include <mbgl/util/std.hpp> +#include <mbgl/util/uv_detail.hpp> +#include <mbgl/util/worker.hpp> +#include <mbgl/util/texture_pool.hpp> +#include <mbgl/util/mapbox.hpp> +#include <mbgl/util/exception.hpp> + +namespace mbgl { + +MapContext::MapContext(uv_loop_t* loop, View& view_, FileSource& fileSource, MapData& data_, bool startPaused) + : view(view_), + data(data_), + env(fileSource), + envScope(env, ThreadType::Map, "Map"), + updated(static_cast<UpdateType>(Update::Nothing)), + asyncUpdate(util::make_unique<uv::async>(loop, [this] { update(); })), + glyphStore(util::make_unique<GlyphStore>(env)), + glyphAtlas(util::make_unique<GlyphAtlas>(1024, 1024)), + spriteAtlas(util::make_unique<SpriteAtlas>(512, 512)), + lineAtlas(util::make_unique<LineAtlas>(512, 512)), + texturePool(util::make_unique<TexturePool>()), + painter(util::make_unique<Painter>(*spriteAtlas, *glyphAtlas, *lineAtlas)) +{ + assert(Environment::currentlyOn(ThreadType::Map)); + + asyncUpdate->unref(); + + view.activate(); + + if (startPaused) { + pause(); + } + + painter->setup(); +} + +MapContext::~MapContext() { + view.notify(); + + // Explicit resets currently necessary because these abandon resources that need to be + // cleaned up by env.performCleanup(); + style.reset(); + sprite.reset(); + painter.reset(); + texturePool.reset(); + lineAtlas.reset(); + spriteAtlas.reset(); + glyphAtlas.reset(); + glyphStore.reset(); + + env.performCleanup(); + + view.deactivate(); +} + +void MapContext::pause() { + view.deactivate(); + + std::unique_lock<std::mutex> lockPause(data.mutexPause); + data.condPaused.notify_all(); + data.condResume.wait(lockPause); + + view.activate(); +} + +void MapContext::resize(uint16_t width, uint16_t height, float ratio) { + view.resize(width, height, ratio); + triggerUpdate(); +} + +void MapContext::triggerUpdate(const Update u) { + updated |= static_cast<UpdateType>(u); + asyncUpdate->send(); +} + +void MapContext::setStyleURL(const std::string& url) { + styleURL = mbgl::util::mapbox::normalizeStyleURL(url, data.getAccessToken()); + styleJSON.clear(); + + const size_t pos = styleURL.rfind('/'); + std::string base = ""; + if (pos != std::string::npos) { + base = styleURL.substr(0, pos + 1); + } + + env.request({ Resource::Kind::JSON, styleURL }, [this, base](const Response &res) { + if (res.status == Response::Successful) { + loadStyleJSON(res.data, base); + } else { + Log::Error(Event::Setup, "loading style failed: %s", res.message.c_str()); + } + }); +} + +void MapContext::setStyleJSON(const std::string& json, const std::string& base) { + styleURL.clear(); + styleJSON = json; + + loadStyleJSON(json, base); +} + +util::ptr<Sprite> MapContext::getSprite() { + assert(Environment::currentlyOn(ThreadType::Map)); + const float pixelRatio = transformState.getPixelRatio(); + const std::string &sprite_url = style->getSpriteURL(); + if (!sprite || !sprite->hasPixelRatio(pixelRatio)) { + sprite = std::make_shared<Sprite>(sprite_url, pixelRatio, env, [this] { + assert(Environment::currentlyOn(ThreadType::Map)); + triggerUpdate(); + }); + } + + return sprite; +} + +void MapContext::loadStyleJSON(const std::string& json, const std::string& base) { + assert(Environment::currentlyOn(ThreadType::Map)); + + sprite.reset(); + style.reset(); + + style = util::make_unique<Style>(); + style->base = base; + style->loadJSON((const uint8_t *)json.c_str()); + style->cascade(data.getClasses()); + style->setDefaultTransitionDuration(data.getDefaultTransitionDuration()); + + const std::string glyphURL = util::mapbox::normalizeGlyphsURL(style->glyph_url, data.getAccessToken()); + glyphStore->setURL(glyphURL); + + for (const auto& source : style->sources) { + source->load(data.getAccessToken(), env, [this]() { + assert(Environment::currentlyOn(ThreadType::Map)); + triggerUpdate(); + }); + } + + triggerUpdate(Update::Zoom); +} + +void MapContext::updateTiles() { + assert(Environment::currentlyOn(ThreadType::Map)); + if (!style) return; + for (const auto& source : style->sources) { + source->update(data, transformState, *style, *glyphAtlas, *glyphStore, *spriteAtlas, + getSprite(), *texturePool, [this]() { + assert(Environment::currentlyOn(ThreadType::Map)); + triggerUpdate(); + }); + } +} + +void MapContext::updateAnnotationTiles(const std::vector<TileID>& ids) { + assert(Environment::currentlyOn(ThreadType::Map)); + if (!style) return; + for (const auto &source : style->sources) { + if (source->info.type == SourceType::Annotations) { + source->invalidateTiles(ids); + } + } + triggerUpdate(); +} + +void MapContext::update() { + assert(Environment::currentlyOn(ThreadType::Map)); + + const auto now = Clock::now(); + data.setAnimationTime(now); + + updated |= data.transform.updateTransitions(now); + transformState = data.transform.currentState(); + + if (updated & static_cast<UpdateType>(Update::Debug)) { + assert(painter); + painter->setDebug(data.getDebug()); + } + + if (style) { + if (updated & static_cast<UpdateType>(Update::DefaultTransitionDuration)) { + style->setDefaultTransitionDuration(data.getDefaultTransitionDuration()); + } + + if (updated & static_cast<UpdateType>(Update::Classes)) { + style->cascade(data.getClasses()); + } + + if (updated & static_cast<UpdateType>(Update::Classes) || + updated & static_cast<UpdateType>(Update::Zoom)) { + style->recalculate(transformState.getNormalizedZoom(), now); + } + + // Allow the sprite atlas to potentially pull new sprite images if needed. + spriteAtlas->resize(transformState.getPixelRatio()); + spriteAtlas->setSprite(getSprite()); + + updateTiles(); + + view.invalidate([this] { render(); }); + } + + updated = static_cast<UpdateType>(Update::Nothing); +} + +void MapContext::renderStill(StillImageCallback fn) { + if (data.mode != MapMode::Still) { + throw util::Exception("Map is not in still image render mode"); + } + + if (callback) { + throw util::Exception("Map is currently rendering an image"); + } + + callback = fn; + triggerUpdate(Update::RenderStill); +} + +void MapContext::render() { + assert(Environment::currentlyOn(ThreadType::Map)); + + // Cleanup OpenGL objects that we abandoned since the last render call. + env.performCleanup(); + + assert(style); + assert(painter); + + painter->render(*style, transformState, data.getAnimationTime()); + + if (data.mode == MapMode::Still && callback && style->isLoaded() && getSprite()->isLoaded()) { + callback(view.readStillImage()); + callback = nullptr; + } + + // Schedule another rerender when we definitely need a next frame. + if (data.transform.needsTransition() || style->hasTransitions()) { + triggerUpdate(); + } +} + +double MapContext::getTopOffsetPixelsForAnnotationSymbol(const std::string& symbol) { + assert(Environment::currentlyOn(ThreadType::Map)); + assert(sprite); + const SpritePosition pos = sprite->getSpritePosition(symbol); + return -pos.height / pos.pixelRatio / 2; +} + +void MapContext::setSourceTileCacheSize(size_t size) { + assert(Environment::currentlyOn(ThreadType::Map)); + if (size != sourceCacheSize) { + sourceCacheSize = size; + if (!style) return; + for (const auto &source : style->sources) { + source->setCacheSize(sourceCacheSize); + } + env.performCleanup(); + } +} + +void MapContext::onLowMemory() { + assert(Environment::currentlyOn(ThreadType::Map)); + if (!style) return; + for (const auto &source : style->sources) { + source->onLowMemory(); + } + env.performCleanup(); +} + +} diff --git a/src/mbgl/map/map_context.hpp b/src/mbgl/map/map_context.hpp new file mode 100644 index 0000000000..93670fd1f2 --- /dev/null +++ b/src/mbgl/map/map_context.hpp @@ -0,0 +1,100 @@ +#ifndef MBGL_MAP_MAP_CONTEXT +#define MBGL_MAP_MAP_CONTEXT + +#include <mbgl/map/tile_id.hpp> +#include <mbgl/map/update.hpp> +#include <mbgl/map/environment.hpp> +#include <mbgl/map/transform_state.hpp> +#include <mbgl/util/ptr.hpp> + +#include <vector> + +typedef struct uv_loop_s uv_loop_t; + +namespace uv { +class async; +} + +namespace mbgl { + +class View; +class MapData; +class GlyphStore; +class GlyphAtlas; +class SpriteAtlas; +class LineAtlas; +class TexturePool; +class Painter; +class Sprite; +class Style; +class Worker; +class StillImage; +struct LatLng; +struct LatLngBounds; + +class MapContext { +public: + MapContext(uv_loop_t*, View&, FileSource&, MapData&, bool startPaused); + ~MapContext(); + + void pause(); + void render(); + + void resize(uint16_t width, uint16_t height, float ratio); + + using StillImageCallback = std::function<void(std::unique_ptr<const StillImage>)>; + void renderStill(StillImageCallback callback); + + void triggerUpdate(Update = Update::Nothing); + + void setStyleURL(const std::string&); + void setStyleJSON(const std::string& json, const std::string& base); + std::string getStyleURL() const { return styleURL; } + std::string getStyleJSON() const { return styleJSON; } + + double getTopOffsetPixelsForAnnotationSymbol(const std::string& symbol); + void updateAnnotationTiles(const std::vector<TileID>&); + + void setSourceTileCacheSize(size_t size); + void onLowMemory(); + +private: + util::ptr<Sprite> getSprite(); + void updateTiles(); + + // Update the state indicated by the accumulated Update flags, then render. + void update(); + + // Loads the actual JSON object an creates a new Style object. + void loadStyleJSON(const std::string& json, const std::string& base); + + View& view; + MapData& data; + + Environment env; + EnvironmentScope envScope; + + UpdateType updated { static_cast<UpdateType>(Update::Nothing) }; + std::unique_ptr<uv::async> asyncUpdate; + + std::unique_ptr<GlyphStore> glyphStore; + std::unique_ptr<GlyphAtlas> glyphAtlas; + std::unique_ptr<SpriteAtlas> spriteAtlas; + std::unique_ptr<LineAtlas> lineAtlas; + std::unique_ptr<TexturePool> texturePool; + std::unique_ptr<Painter> painter; + std::unique_ptr<Style> style; + + util::ptr<Sprite> sprite; + + std::string styleURL; + std::string styleJSON; + + StillImageCallback callback; + size_t sourceCacheSize; + TransformState transformState; +}; + +} + +#endif diff --git a/src/mbgl/map/map_data.hpp b/src/mbgl/map/map_data.hpp index 718c3eb0e0..86a22e123b 100644 --- a/src/mbgl/map/map_data.hpp +++ b/src/mbgl/map/map_data.hpp @@ -7,33 +7,26 @@ #include <mutex> #include <atomic> #include <vector> +#include <cassert> +#include <condition_variable> -namespace mbgl { +#include <mbgl/map/mode.hpp> +#include <mbgl/map/environment.hpp> +#include <mbgl/map/transform.hpp> +#include <mbgl/map/transform_state.hpp> +#include <mbgl/map/annotation.hpp> -struct StyleInfo { - std::string url; - std::string base; - std::string json; -}; +namespace mbgl { class MapData { using Lock = std::lock_guard<std::mutex>; public: - inline MapData() { + inline MapData(View& view, MapMode mode_) : transform(view), mode(mode_) { setAnimationTime(TimePoint::min()); setDefaultTransitionDuration(Duration::zero()); } - inline StyleInfo getStyleInfo() const { - Lock lock(mtx); - return styleInfo; - } - inline void setStyleInfo(StyleInfo&& info) { - Lock lock(mtx); - styleInfo = info; - } - inline std::string getAccessToken() const { Lock lock(mtx); return accessToken; @@ -87,15 +80,25 @@ public: defaultTransitionDuration = duration; }; +public: + Transform transform; + AnnotationManager annotationManager; + const MapMode mode; + private: mutable std::mutex mtx; - StyleInfo styleInfo; std::string accessToken; std::vector<std::string> classes; std::atomic<uint8_t> debug { false }; std::atomic<Duration> animationTime; std::atomic<Duration> defaultTransitionDuration; + +// TODO: make private +public: + std::mutex mutexPause; + std::condition_variable condPaused; + std::condition_variable condResume; }; } diff --git a/src/mbgl/map/raster_tile_data.cpp b/src/mbgl/map/raster_tile_data.cpp index 2531f5130b..0b849ca7cb 100644 --- a/src/mbgl/map/raster_tile_data.cpp +++ b/src/mbgl/map/raster_tile_data.cpp @@ -1,4 +1,3 @@ -#include <mbgl/map/map.hpp> #include <mbgl/map/raster_tile_data.hpp> #include <mbgl/style/style.hpp> @@ -24,10 +23,6 @@ void RasterTileData::parse() { } } -void RasterTileData::render(Painter &painter, const StyleLayer &layer_desc, const mat4 &matrix) { - bucket.render(painter, layer_desc, id, matrix); -} - -bool RasterTileData::hasData(StyleLayer const& /*layer_desc*/) const { - return bucket.hasData(); +Bucket* RasterTileData::getBucket(StyleLayer const&) { + return &bucket; } diff --git a/src/mbgl/map/raster_tile_data.hpp b/src/mbgl/map/raster_tile_data.hpp index dab2b7b842..45aee8f74c 100644 --- a/src/mbgl/map/raster_tile_data.hpp +++ b/src/mbgl/map/raster_tile_data.hpp @@ -20,8 +20,7 @@ public: ~RasterTileData(); void parse() override; - void render(Painter &painter, const StyleLayer &layer_desc, const mat4 &matrix) override; - bool hasData(StyleLayer const &layer_desc) const override; + Bucket* getBucket(StyleLayer const &layer_desc) override; protected: StyleLayoutRaster layout; diff --git a/src/mbgl/map/source.cpp b/src/mbgl/map/source.cpp index 9e318964b0..708ff6b27a 100644 --- a/src/mbgl/map/source.cpp +++ b/src/mbgl/map/source.cpp @@ -1,30 +1,26 @@ #include <mbgl/map/source.hpp> -#include <mbgl/map/map.hpp> +#include <mbgl/map/map_data.hpp> #include <mbgl/map/environment.hpp> #include <mbgl/map/transform.hpp> #include <mbgl/map/tile.hpp> #include <mbgl/renderer/painter.hpp> #include <mbgl/util/constants.hpp> -#include <mbgl/util/raster.hpp> -#include <mbgl/util/string.hpp> -#include <mbgl/util/texture_pool.hpp> #include <mbgl/storage/resource.hpp> #include <mbgl/storage/response.hpp> -#include <mbgl/util/vec.hpp> #include <mbgl/util/math.hpp> -#include <mbgl/util/std.hpp> #include <mbgl/util/box.hpp> #include <mbgl/util/mapbox.hpp> -#include <mbgl/geometry/glyph_atlas.hpp> #include <mbgl/style/style_layer.hpp> #include <mbgl/platform/log.hpp> #include <mbgl/util/uv_detail.hpp> #include <mbgl/util/token.hpp> +#include <mbgl/util/string.hpp> #include <mbgl/util/tile_cover.hpp> #include <mbgl/map/vector_tile_data.hpp> #include <mbgl/map/raster_tile_data.hpp> #include <mbgl/map/live_tile_data.hpp> +#include <mbgl/style/style.hpp> #include <algorithm> @@ -123,6 +119,20 @@ Source::Source() Source::~Source() {} +bool Source::isLoaded() const { + if (!loaded) { + return false; + } + + for (const auto& tile : tiles) { + if (tile.second->data->state != TileData::State::parsed) { + return false; + } + } + + return true; +} + // Note: This is a separate function that must be called exactly once after creation // The reason this isn't part of the constructor is that calling shared_from_this() in // the constructor fails. @@ -147,7 +157,7 @@ void Source::load(const std::string& accessToken, d.Parse<0>(res.data.c_str()); if (d.HasParseError()) { - Log::Warning(Event::General, "Invalid source TileJSON; Parse Error at %d: %s", d.GetErrorOffset(), d.GetParseError()); + Log::Error(Event::General, "Invalid source TileJSON; Parse Error at %d: %s", d.GetErrorOffset(), d.GetParseError()); return; } @@ -174,16 +184,6 @@ void Source::drawClippingMasks(Painter &painter) { } } -void Source::render(Painter &painter, const StyleLayer &layer_desc) { - gl::group group(std::string { "layer: " } + layer_desc.id); - for (const auto& pair : tiles) { - Tile &tile = *pair.second; - if (tile.data && tile.data->state == TileData::State::parsed) { - painter.renderTileLayer(tile, layer_desc, tile.matrix); - } - } -} - void Source::finishRender(Painter &painter) { for (const auto& pair : tiles) { Tile &tile = *pair.second; @@ -202,6 +202,9 @@ std::forward_list<Tile *> Source::getLoadedTiles() const { return ptrs; } +const std::vector<Tile*>& Source::getTiles() const { + return tilePtrs; +} TileData::State Source::hasTile(const TileID& id) { auto it = tiles.find(id); @@ -215,11 +218,16 @@ TileData::State Source::hasTile(const TileID& id) { return TileData::State::invalid; } -TileData::State Source::addTile(Map &map, Worker &worker, - util::ptr<Style> style, GlyphAtlas &glyphAtlas, - GlyphStore &glyphStore, SpriteAtlas &spriteAtlas, - util::ptr<Sprite> sprite, TexturePool &texturePool, - const TileID &id, std::function<void()> callback) { +TileData::State Source::addTile(MapData& data, + const TransformState& transformState, + Style& style, + GlyphAtlas& glyphAtlas, + GlyphStore& glyphStore, + SpriteAtlas& spriteAtlas, + util::ptr<Sprite> sprite, + TexturePool& texturePool, + const TileID& id, + std::function<void()> callback) { const TileData::State state = hasTile(id); if (state != TileData::State::invalid) { @@ -252,18 +260,17 @@ TileData::State Source::addTile(Map &map, Worker &worker, // If we don't find working tile data, we're just going to load it. if (info.type == SourceType::Vector) { new_tile.data = - std::make_shared<VectorTileData>(normalized_id, map.getMaxZoom(), style, glyphAtlas, + std::make_shared<VectorTileData>(normalized_id, data.transform.getMaxZoom(), style, glyphAtlas, glyphStore, spriteAtlas, sprite, info); - new_tile.data->request(worker, map.getState().getPixelRatio(), callback); + new_tile.data->request(style.workers, transformState.getPixelRatio(), callback); } else if (info.type == SourceType::Raster) { new_tile.data = std::make_shared<RasterTileData>(normalized_id, texturePool, info); - new_tile.data->request(worker, map.getState().getPixelRatio(), callback); + new_tile.data->request(style.workers, transformState.getPixelRatio(), callback); } else if (info.type == SourceType::Annotations) { - AnnotationManager& annotationManager = map.getAnnotationManager(); - new_tile.data = std::make_shared<LiveTileData>(normalized_id, annotationManager, - map.getMaxZoom(), style, glyphAtlas, + new_tile.data = std::make_shared<LiveTileData>(normalized_id, data.annotationManager, + data.transform.getMaxZoom(), style, glyphAtlas, glyphStore, spriteAtlas, sprite, info); - new_tile.data->reparse(worker, callback); + new_tile.data->reparse(style.workers, callback); } else { throw std::runtime_error("source type not implemented"); } @@ -352,21 +359,21 @@ bool Source::findLoadedParent(const TileID& id, int32_t minCoveringZoom, std::fo return false; } -void Source::update(Map &map, - Worker &worker, - util::ptr<Style> style, - GlyphAtlas &glyphAtlas, - GlyphStore &glyphStore, - SpriteAtlas &spriteAtlas, +void Source::update(MapData& data, + const TransformState& transformState, + Style& style, + GlyphAtlas& glyphAtlas, + GlyphStore& glyphStore, + SpriteAtlas& spriteAtlas, util::ptr<Sprite> sprite, - TexturePool &texturePool, + TexturePool& texturePool, std::function<void()> callback) { - if (!loaded || map.getTime() <= updated) { + if (!loaded || data.getAnimationTime() <= updated) { return; } - int32_t zoom = std::floor(getZoom(map.getState())); - std::forward_list<TileID> required = coveringTiles(map.getState()); + int32_t zoom = std::floor(getZoom(transformState)); + std::forward_list<TileID> required = coveringTiles(transformState); // Determine the overzooming/underzooming amounts. int32_t minCoveringZoom = util::clamp<int32_t>(zoom - 10, info.min_zoom, info.max_zoom); @@ -379,7 +386,7 @@ void Source::update(Map &map, // Add existing child/parent tiles if the actual tile is not yet loaded for (const auto& id : required) { - const TileData::State state = addTile(map, worker, style, glyphAtlas, glyphStore, + const TileData::State state = addTile(data, transformState, style, glyphAtlas, glyphStore, spriteAtlas, sprite, texturePool, id, callback); if (state != TileData::State::parsed) { @@ -399,9 +406,9 @@ void Source::update(Map &map, } if (info.type != SourceType::Raster && cache.getSize() == 0) { - size_t conservativeCacheSize = ((float)map.getState().getWidth() / util::tileSize) * - ((float)map.getState().getHeight() / util::tileSize) * - (map.getMaxZoom() - map.getMinZoom() + 1) * + size_t conservativeCacheSize = ((float)transformState.getWidth() / util::tileSize) * + ((float)transformState.getHeight() / util::tileSize) * + (data.transform.getMaxZoom() - data.transform.getMinZoom() + 1) * 0.5; cache.setSize(conservativeCacheSize); } @@ -441,7 +448,9 @@ void Source::update(Map &map, } }); - updated = map.getTime(); + updateTilePtrs(); + + updated = data.getAnimationTime(); } void Source::invalidateTiles(const std::vector<TileID>& ids) { @@ -450,6 +459,14 @@ void Source::invalidateTiles(const std::vector<TileID>& ids) { tiles.erase(id); tile_data.erase(id); } + updateTilePtrs(); +} + +void Source::updateTilePtrs() { + tilePtrs.clear(); + for (const auto& pair : tiles) { + tilePtrs.push_back(pair.second.get()); + } } void Source::setCacheSize(size_t size) { diff --git a/src/mbgl/map/source.hpp b/src/mbgl/map/source.hpp index 035e862284..b15de0e642 100644 --- a/src/mbgl/map/source.hpp +++ b/src/mbgl/map/source.hpp @@ -20,9 +20,8 @@ namespace mbgl { -class Map; +class MapData; class Environment; -class Worker; class GlyphAtlas; class GlyphStore; class SpriteAtlas; @@ -30,7 +29,6 @@ class Sprite; class TexturePool; class Style; class Painter; -class StyleLayer; class TransformState; class Tile; struct ClipID; @@ -60,18 +58,27 @@ public: void load(const std::string& accessToken, Environment&, std::function<void()> callback); - - void update(Map &, Worker &, util::ptr<Style>, GlyphAtlas &, GlyphStore &, - SpriteAtlas &, util::ptr<Sprite>, TexturePool &, std::function<void()> callback); + bool isLoaded() const; + + void load(MapData&, Environment&, std::function<void()> callback); + void update(MapData&, + const TransformState&, + Style&, + GlyphAtlas&, + GlyphStore&, + SpriteAtlas&, + util::ptr<Sprite>, + TexturePool&, + std::function<void()> callback); void invalidateTiles(const std::vector<TileID>&); void updateMatrices(const mat4 &projMatrix, const TransformState &transform); void drawClippingMasks(Painter &painter); - void render(Painter &painter, const StyleLayer &layer_desc); void finishRender(Painter &painter); std::forward_list<Tile *> getLoadedTiles() const; + const std::vector<Tile*>& getTiles() const; void setCacheSize(size_t); void onLowMemory(); @@ -85,11 +92,19 @@ private: int32_t coveringZoomLevel(const TransformState&) const; std::forward_list<TileID> coveringTiles(const TransformState&) const; - TileData::State addTile(Map &, Worker &, util::ptr<Style>, GlyphAtlas &, - GlyphStore &, SpriteAtlas &, util::ptr<Sprite>, TexturePool &, - const TileID &, std::function<void()> callback); + TileData::State addTile(MapData&, + const TransformState&, + Style&, + GlyphAtlas&, + GlyphStore&, + SpriteAtlas&, + util::ptr<Sprite>, + TexturePool&, + const TileID&, + std::function<void()> callback); TileData::State hasTile(const TileID& id); + void updateTilePtrs(); double getZoom(const TransformState &state) const; @@ -99,6 +114,7 @@ private: TimePoint updated = TimePoint::min(); std::map<TileID, std::unique_ptr<Tile>> tiles; + std::vector<Tile*> tilePtrs; std::map<TileID, std::weak_ptr<TileData>> tile_data; TileCache cache; }; diff --git a/src/mbgl/map/sprite.cpp b/src/mbgl/map/sprite.cpp index 8883ee092d..75e5f845c4 100644 --- a/src/mbgl/map/sprite.cpp +++ b/src/mbgl/map/sprite.cpp @@ -1,5 +1,4 @@ #include <mbgl/map/sprite.hpp> -#include <mbgl/map/map.hpp> #include <mbgl/util/raster.hpp> #include <mbgl/platform/log.hpp> @@ -24,42 +23,16 @@ SpritePosition::SpritePosition(uint16_t x_, uint16_t y_, uint16_t width_, uint16 sdf(sdf_) { } -util::ptr<Sprite> Sprite::Create(const std::string &base_url, float pixelRatio, Environment &env) { - util::ptr<Sprite> sprite(std::make_shared<Sprite>(Key(), base_url, pixelRatio)); - sprite->load(env); - return sprite; -} - -Sprite::Sprite(const Key &, const std::string& base_url, float pixelRatio_) - : valid(base_url.length() > 0), - pixelRatio(pixelRatio_ > 1 ? 2 : 1), - spriteURL(base_url + (pixelRatio_ > 1 ? "@2x" : "") + ".png"), - jsonURL(base_url + (pixelRatio_ > 1 ? "@2x" : "") + ".json"), +Sprite::Sprite(const std::string& baseUrl, float pixelRatio_, Environment& env_, std::function<void ()> callback_) + : pixelRatio(pixelRatio_ > 1 ? 2 : 1), raster(), loadedImage(false), loadedJSON(false), - future(promise.get_future()) { -} - -bool Sprite::hasPixelRatio(float ratio) const { - return pixelRatio == (ratio > 1 ? 2 : 1); -} - - -void Sprite::waitUntilLoaded() const { - future.wait(); -} - -Sprite::operator bool() const { - return valid && isLoaded() && !pos.empty(); -} - + future(promise.get_future()), + callback(callback_), + env(env_) { -// Note: This is a separate function that must be called exactly once after creation -// The reason this isn't part of the constructor is that calling shared_from_this() in -// the constructor fails. -void Sprite::load(Environment &env) { - if (!valid) { + if (baseUrl.empty()) { // Treat a non-existent sprite as a successfully loaded empty sprite. loadedImage = true; loadedJSON = true; @@ -67,34 +40,48 @@ void Sprite::load(Environment &env) { return; } - util::ptr<Sprite> sprite = shared_from_this(); + std::string spriteURL(baseUrl + (pixelRatio_ > 1 ? "@2x" : "") + ".png"); + std::string jsonURL(baseUrl + (pixelRatio_ > 1 ? "@2x" : "") + ".json"); - env.request({ Resource::Kind::JSON, jsonURL }, [sprite](const Response &res) { + jsonRequest = env.request({ Resource::Kind::JSON, jsonURL }, [this](const Response &res) { + jsonRequest = nullptr; if (res.status == Response::Successful) { - sprite->body = res.data; - sprite->parseJSON(); + body = res.data; + parseJSON(); } else { Log::Warning(Event::Sprite, "Failed to load sprite info: %s", res.message.c_str()); } - sprite->loadedJSON = true; - sprite->complete(); + loadedJSON = true; + complete(); }); - env.request({ Resource::Kind::Image, spriteURL }, [sprite](const Response &res) { + spriteRequest = env.request({ Resource::Kind::Image, spriteURL }, [this](const Response &res) { + spriteRequest = nullptr; if (res.status == Response::Successful) { - sprite->image = res.data; - sprite->parseImage(); + image = res.data; + parseImage(); } else { Log::Warning(Event::Sprite, "Failed to load sprite image: %s", res.message.c_str()); } - sprite->loadedImage = true; - sprite->complete(); + loadedImage = true; + complete(); }); } +Sprite::~Sprite() { + if (jsonRequest) { + env.cancelRequest(jsonRequest); + } + + if (spriteRequest) { + env.cancelRequest(spriteRequest); + } +} + void Sprite::complete() { if (loadedImage && loadedJSON) { promise.set_value(); + callback(); } } @@ -102,6 +89,14 @@ bool Sprite::isLoaded() const { return loadedImage && loadedJSON; } +void Sprite::waitUntilLoaded() const { + future.wait(); +} + +bool Sprite::hasPixelRatio(float ratio) const { + return pixelRatio == (ratio > 1 ? 2 : 1); +} + void Sprite::parseImage() { raster = util::make_unique<util::Image>(image); if (!*raster) { diff --git a/src/mbgl/map/sprite.hpp b/src/mbgl/map/sprite.hpp index 6c1b3ba8e3..bd6ae89bc6 100644 --- a/src/mbgl/map/sprite.hpp +++ b/src/mbgl/map/sprite.hpp @@ -4,6 +4,7 @@ #include <mbgl/util/image.hpp> #include <mbgl/util/noncopyable.hpp> #include <mbgl/util/ptr.hpp> +#include <mbgl/storage/request.hpp> #include <cstdint> #include <atomic> @@ -15,6 +16,7 @@ namespace mbgl { class Environment; +class Request; class SpritePosition { public: @@ -31,15 +33,10 @@ public: bool sdf = false; }; -class Sprite : public std::enable_shared_from_this<Sprite>, private util::noncopyable { -private: - struct Key {}; - void load(Environment &env); - +class Sprite : private util::noncopyable { public: - Sprite(const Key &, const std::string& base_url, float pixelRatio); - static util::ptr<Sprite> - Create(const std::string &base_url, float pixelRatio, Environment &env); + Sprite(const std::string& baseUrl, float pixelRatio, Environment&, std::function<void()> callback); + ~Sprite(); const SpritePosition &getSpritePosition(const std::string& name) const; @@ -48,15 +45,7 @@ public: void waitUntilLoaded() const; bool isLoaded() const; - operator bool() const; - -private: - const bool valid; - -public: const float pixelRatio; - const std::string spriteURL; - const std::string jsonURL; std::unique_ptr<util::Image> raster; private: @@ -64,7 +53,6 @@ private: void parseImage(); void complete(); -private: std::string body; std::string image; std::atomic<bool> loadedImage; @@ -74,7 +62,11 @@ private: std::promise<void> promise; std::future<void> future; + std::function<void ()> callback; + Environment& env; + Request* jsonRequest = nullptr; + Request* spriteRequest = nullptr; }; } diff --git a/src/mbgl/map/tile_data.cpp b/src/mbgl/map/tile_data.cpp index bb8d18d12f..80fd6346d3 100644 --- a/src/mbgl/map/tile_data.cpp +++ b/src/mbgl/map/tile_data.cpp @@ -60,13 +60,16 @@ void TileData::cancel() { void TileData::reparse(Worker& worker, std::function<void()> callback) { util::ptr<TileData> tile = shared_from_this(); worker.send( - [tile]() { - EnvironmentScope scope(tile->env, ThreadType::TileWorker, "TileWorker_" + tile->name); - tile->parse(); + [this]() { + EnvironmentScope scope(env, ThreadType::TileWorker, "TileWorker_" + name); + parse(); }, [tile, callback]() { // `tile` is bound in this lambda to ensure that if it's the last owning pointer, - // destruction happens on the map thread, not the worker thread. + // destruction happens on the map thread, not the worker thread. It is _not_ bound + // in the above lambda, because we do not want the possibility to arise that the + // after callback could execute and release the penultimate reference before the + // work callback has been destructed. callback(); }); } diff --git a/src/mbgl/map/tile_data.hpp b/src/mbgl/map/tile_data.hpp index 92d9778fd5..b85db7db78 100644 --- a/src/mbgl/map/tile_data.hpp +++ b/src/mbgl/map/tile_data.hpp @@ -47,8 +47,7 @@ public: // Override this in the child class. virtual void parse() = 0; - virtual void render(Painter &painter, const StyleLayer &layer_desc, const mat4 &matrix) = 0; - virtual bool hasData(StyleLayer const &layer_desc) const = 0; + virtual Bucket* getBucket(StyleLayer const &layer_desc) = 0; const TileID id; const std::string name; diff --git a/src/mbgl/map/tile_parser.cpp b/src/mbgl/map/tile_parser.cpp index 275b77be91..1438bcddaa 100644 --- a/src/mbgl/map/tile_parser.cpp +++ b/src/mbgl/map/tile_parser.cpp @@ -1,23 +1,15 @@ #include <mbgl/map/tile_parser.hpp> #include <mbgl/map/vector_tile_data.hpp> #include <mbgl/platform/log.hpp> -#include <mbgl/style/style.hpp> #include <mbgl/style/style_layer.hpp> #include <mbgl/map/source.hpp> #include <mbgl/renderer/fill_bucket.hpp> #include <mbgl/renderer/line_bucket.hpp> #include <mbgl/renderer/symbol_bucket.hpp> -#include <mbgl/renderer/raster_bucket.hpp> -#include <mbgl/util/raster.hpp> #include <mbgl/util/constants.hpp> -#include <mbgl/util/token.hpp> -#include <mbgl/geometry/glyph_atlas.hpp> -#include <mbgl/text/glyph_store.hpp> #include <mbgl/text/collision.hpp> -#include <mbgl/text/glyph.hpp> -#include <mbgl/map/map.hpp> #include <mbgl/util/std.hpp> -#include <mbgl/util/utf.hpp> +#include <mbgl/style/style.hpp> #include <locale> @@ -30,7 +22,7 @@ TileParser::~TileParser() = default; TileParser::TileParser(const GeometryTile& geometryTile_, VectorTileData& tile_, - const util::ptr<const Style>& style_, + const Style& style_, GlyphAtlas& glyphAtlas_, GlyphStore& glyphStore_, SpriteAtlas& spriteAtlas_, @@ -43,7 +35,6 @@ TileParser::TileParser(const GeometryTile& geometryTile_, spriteAtlas(spriteAtlas_), sprite(sprite_), collision(util::make_unique<Collision>(tile.id.z, 4096, tile.source.tile_size, tile.depth)) { - assert(style); assert(sprite); assert(collision); } @@ -51,7 +42,7 @@ TileParser::TileParser(const GeometryTile& geometryTile_, bool TileParser::obsolete() const { return tile.state == TileData::State::obsolete; } void TileParser::parse() { - for (const auto& layer_desc : style->layers) { + for (const auto& layer_desc : style.layers) { // Cancel early when parsing. if (obsolete()) { return; @@ -166,14 +157,13 @@ std::unique_ptr<Bucket> TileParser::createFillBucket(const GeometryTileLayer& la tile.triangleElementsBuffer, tile.lineElementsBuffer); addBucketGeometries(bucket, layer, bucket_desc.filter); - return std::move(bucket); + return bucket->hasData() ? std::move(bucket) : nullptr; } std::unique_ptr<Bucket> TileParser::createLineBucket(const GeometryTileLayer& layer, const StyleBucket& bucket_desc) { auto bucket = util::make_unique<LineBucket>(tile.lineVertexBuffer, - tile.triangleElementsBuffer, - tile.pointElementsBuffer); + tile.triangleElementsBuffer); const float z = tile.id.z; auto& layout = bucket->layout; @@ -184,7 +174,7 @@ std::unique_ptr<Bucket> TileParser::createLineBucket(const GeometryTileLayer& la applyLayoutProperty(PropertyKey::LineRoundLimit, bucket_desc.layout, layout.round_limit, z); addBucketGeometries(bucket, layer, bucket_desc.filter); - return std::move(bucket); + return bucket->hasData() ? std::move(bucket) : nullptr; } std::unique_ptr<Bucket> TileParser::createSymbolBucket(const GeometryTileLayer& layer, @@ -234,6 +224,6 @@ std::unique_ptr<Bucket> TileParser::createSymbolBucket(const GeometryTileLayer& bucket->addFeatures( layer, bucket_desc.filter, reinterpret_cast<uintptr_t>(&tile), spriteAtlas, *sprite, glyphAtlas, glyphStore); - return std::move(bucket); + return bucket->hasData() ? std::move(bucket) : nullptr; } } diff --git a/src/mbgl/map/tile_parser.hpp b/src/mbgl/map/tile_parser.hpp index 2c16d2a2fd..2dbb8cb17f 100644 --- a/src/mbgl/map/tile_parser.hpp +++ b/src/mbgl/map/tile_parser.hpp @@ -35,7 +35,7 @@ class TileParser : private util::noncopyable { public: TileParser(const GeometryTile& geometryTile, VectorTileData& tile, - const util::ptr<const Style>& style, + const Style& style, GlyphAtlas& glyphAtlas, GlyphStore& glyphStore, SpriteAtlas& spriteAtlas, @@ -60,7 +60,7 @@ private: VectorTileData& tile; // Cross-thread shared data. - util::ptr<const Style> style; + const Style& style; GlyphAtlas& glyphAtlas; GlyphStore& glyphStore; SpriteAtlas& spriteAtlas; diff --git a/src/mbgl/map/transform.cpp b/src/mbgl/map/transform.cpp index cb73915e36..ffe7422962 100644 --- a/src/mbgl/map/transform.cpp +++ b/src/mbgl/map/transform.cpp @@ -199,14 +199,14 @@ void Transform::_setScale(double new_scale, double cx, double cy, const Duration // 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; + cx = static_cast<double>(current.width) / 2.0; + cy = static_cast<double>(current.height) / 2.0; } // 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); + const double dx = (cx - static_cast<double>(current.width) / 2.0) * (1.0 - factor); + const double dy = (cy - static_cast<double>(current.height) / 2.0) * (1.0 - factor); // Account for angle const double angle_sin = std::sin(-current.angle); @@ -238,6 +238,9 @@ void Transform::_setScaleXY(const double new_scale, const double xn, const doubl current.scale = final.scale; current.x = final.x; current.y = final.y; + const double s = current.scale * util::tileSize; + current.Bc = s / 360; + current.Cc = s / util::M2PI; } else { const double startS = current.scale; const double startX = current.x; @@ -250,6 +253,9 @@ void Transform::_setScaleXY(const double new_scale, const double xn, const doubl current.scale = util::interpolate(startS, final.scale, t); current.x = util::interpolate(startX, final.x, t); current.y = util::interpolate(startY, final.y, t); + const double s = current.scale * util::tileSize; + current.Bc = s / 360; + current.Cc = s / util::M2PI; return Update::Zoom; }, [=] { @@ -258,10 +264,6 @@ void Transform::_setScaleXY(const double new_scale, const double xn, const doubl }, duration); } - const double s = final.scale * util::tileSize; - current.Bc = s / 360; - current.Cc = s / util::M2PI; - view.notifyMapChange(duration != Duration::zero() ? MapChangeRegionDidChangeAnimated : MapChangeRegionDidChange, @@ -287,7 +289,7 @@ void Transform::rotateBy(const double start_x, const double start_y, const doubl const double end_y, const Duration duration) { std::lock_guard<std::recursive_mutex> lock(mtx); - double center_x = current.width / 2, center_y = current.height / 2; + double center_x = static_cast<double>(current.width) / 2.0, center_y = static_cast<double>(current.height) / 2.0; const double begin_center_x = start_x - center_x; const double begin_center_y = start_y - center_y; @@ -326,8 +328,8 @@ void Transform::setAngle(const double new_angle, const double cx, const double c double dx = 0, dy = 0; if (cx >= 0 && cy >= 0) { - dx = (final.width / 2) - cx; - dy = (final.height / 2) - cy; + dx = (static_cast<double>(final.width) / 2.0) - cx; + dy = (static_cast<double>(final.height) / 2.0) - cy; _moveBy(dx, dy, Duration::zero()); } diff --git a/src/mbgl/map/transform.hpp b/src/mbgl/map/transform.hpp new file mode 100644 index 0000000000..ef89a4eefa --- /dev/null +++ b/src/mbgl/map/transform.hpp @@ -0,0 +1,101 @@ +#ifndef MBGL_MAP_TRANSFORM +#define MBGL_MAP_TRANSFORM + +#include <mbgl/map/transform_state.hpp> +#include <mbgl/util/chrono.hpp> +#include <mbgl/map/update.hpp> +#include <mbgl/util/geo.hpp> +#include <mbgl/util/noncopyable.hpp> +#include <mbgl/util/vec.hpp> + +#include <cstdint> +#include <cmath> +#include <forward_list> +#include <mutex> + +namespace mbgl { + +class View; +namespace util { class transition; } + +class Transform : private util::noncopyable { +public: + Transform(View &view); + + // 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, Duration = Duration::zero()); + void setLatLng(LatLng latLng, Duration = Duration::zero()); + void setLatLngZoom(LatLng latLng, double zoom, Duration = Duration::zero()); + inline const LatLng getLatLng() const { return current.getLatLng(); } + + // Zoom + void scaleBy(double ds, double cx = -1, double cy = -1, Duration = Duration::zero()); + void setScale(double scale, double cx = -1, double cy = -1, Duration = Duration::zero()); + void setZoom(double zoom, Duration = Duration::zero()); + double getZoom() const; + double getScale() const; + double getMinZoom() const; + double getMaxZoom() const; + + // Angle + void rotateBy(double sx, double sy, double ex, double ey, Duration = Duration::zero()); + void setAngle(double angle, Duration = Duration::zero()); + void setAngle(double angle, double cx, double cy); + double getAngle() const; + + // Transitions + bool needsTransition() const; + UpdateType updateTransitions(TimePoint now); + void cancelTransitions(); + + // Gesture + void setGestureInProgress(bool); + + // Transform state + const TransformState currentState() const; + const TransformState finalState() const; + +private: + // 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, Duration = Duration::zero()); + void _setScale(double scale, double cx, double cy, Duration = Duration::zero()); + void _setScaleXY(double new_scale, double xn, double yn, Duration = Duration::zero()); + void _setAngle(double angle, Duration = Duration::zero()); + + void constrain(double& scale, double& y) const; + + View &view; + + mutable std::recursive_mutex mtx; + + // 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; + + // This reflects the final position of the transform, after all possible transition took place. + TransformState final; + + // Limit the amount of zooming possible on the map. + const double min_scale = std::pow(2, 0); + const double max_scale = std::pow(2, 18); + + void startTransition(std::function<Update(double)> frame, + std::function<void()> finish, + Duration); + + TimePoint transitionStart; + Duration transitionDuration; + std::function<Update(TimePoint)> transitionFrameFn; + std::function<void()> transitionFinishFn; +}; + +} + +#endif diff --git a/src/mbgl/map/transform_state.cpp b/src/mbgl/map/transform_state.cpp index 507e63f67e..2a1cc4b9ea 100644 --- a/src/mbgl/map/transform_state.cpp +++ b/src/mbgl/map/transform_state.cpp @@ -31,8 +31,8 @@ box TransformState::cornersToBox(uint32_t z) const { const double angle_sin = std::sin(-angle); const double angle_cos = std::cos(-angle); - const double w_2 = width / 2; - const double h_2 = height / 2; + const double w_2 = static_cast<double>(width) / 2.0; + const double h_2 = static_cast<double>(height) / 2.0; const double ss_0 = scale * util::tileSize; const double ss_1 = ref_scale / ss_0; const double ss_2 = ss_0 / 2.0; @@ -166,8 +166,8 @@ const vec2<double> TransformState::pixelForLatLng(const LatLng latLng) const { LatLng ll = getLatLng(); double zoom = getZoom(); - const double centerX = width / 2; - const double centerY = height / 2; + const double centerX = static_cast<double>(width) / 2.0; + const double centerY = static_cast<double>(height) / 2.0; const double m = Projection::getMetersPerPixelAtLatitude(0, zoom); diff --git a/src/mbgl/map/transform_state.hpp b/src/mbgl/map/transform_state.hpp new file mode 100644 index 0000000000..f6a00a4a3d --- /dev/null +++ b/src/mbgl/map/transform_state.hpp @@ -0,0 +1,91 @@ +#ifndef MBGL_MAP_TRANSFORM_STATE +#define MBGL_MAP_TRANSFORM_STATE + +#include <mbgl/util/mat4.hpp> +#include <mbgl/util/geo.hpp> +#include <mbgl/util/vec.hpp> + +#include <cstdint> +#include <array> +#include <limits> + +namespace mbgl { + +class TileID; +struct box; + +class TransformState { + friend class Transform; + +public: + // Matrix + void matrixFor(mat4& matrix, const TileID& id) const; + box cornersToBox(uint32_t z) const; + + // Dimensions + bool hasSize() const; + uint16_t getWidth() const; + uint16_t getHeight() const; + uint16_t getFramebufferWidth() const; + uint16_t getFramebufferHeight() const; + const std::array<uint16_t, 2> getFramebufferDimensions() const; + float getPixelRatio() const; + + float worldSize() const; + float lngX(float lon) const; + float latY(float lat) const; + std::array<float, 2> locationCoordinate(float lon, float lat) const; + void getLonLat(double &lon, double &lat) const; + + // Position + const LatLng getLatLng() const; + + // Zoom + float getNormalizedZoom() const; + double getZoom() const; + int32_t getIntegerZoom() const; + double getZoomFraction() const; + double getScale() const; + + // Rotation + float getAngle() const; + + // Projection + const vec2<double> pixelForLatLng(const LatLng latLng) const; + const LatLng latLngForPixel(const vec2<double> pixel) const; + + // Changing + bool isChanging() const; + +private: + double pixel_x() const; + double pixel_y() const; + +private: + // logical dimensions + uint16_t width = 0, height = 0; + + // physical (framebuffer) dimensions + std::array<uint16_t, 2> framebuffer = {{ 0, 0 }}; + + // map scale factor + float pixelRatio = 0; + + // cache values for spherical mercator math + double Bc, Cc; + + // animation state + bool rotating = false; + bool scaling = false; + bool panning = false; + bool gestureInProgress = false; + + // map position + double x = 0, y = 0; + double angle = 0; + double scale = 1; +}; + +} + +#endif diff --git a/src/mbgl/map/vector_tile_data.cpp b/src/mbgl/map/vector_tile_data.cpp index 39c2d9fce3..4c6c1150bd 100644 --- a/src/mbgl/map/vector_tile_data.cpp +++ b/src/mbgl/map/vector_tile_data.cpp @@ -12,7 +12,7 @@ using namespace mbgl; VectorTileData::VectorTileData(const TileID& id_, float mapMaxZoom, - util::ptr<Style> style_, + Style& style_, GlyphAtlas& glyphAtlas_, GlyphStore& glyphStore_, SpriteAtlas& spriteAtlas_, @@ -37,20 +37,12 @@ void VectorTileData::parse() { } try { - if (!style) { - throw std::runtime_error("style isn't present in VectorTileData object anymore"); - } - // Parsing creates state that is encapsulated in TileParser. While parsing, // the TileParser object writes results into this objects. All other state // is going to be discarded afterwards. VectorTile vectorTile(pbf((const uint8_t *)data.data(), data.size())); const VectorTile* vt = &vectorTile; TileParser parser(*vt, *this, style, glyphAtlas, glyphStore, spriteAtlas, sprite); - - // Clear the style so that we don't have a cycle in the shared_ptr references. - style.reset(); - parser.parse(); } catch (const std::exception& ex) { Log::Error(Event::ParseTile, "Parsing [%d/%d/%d] failed: %s", id.z, id.x, id.y, ex.what()); @@ -63,23 +55,13 @@ void VectorTileData::parse() { } } -void VectorTileData::render(Painter &painter, const StyleLayer &layer_desc, const mat4 &matrix) { - if (state == State::parsed && layer_desc.bucket) { - auto databucket_it = buckets.find(layer_desc.bucket->name); - if (databucket_it != buckets.end()) { - assert(databucket_it->second); - databucket_it->second->render(painter, layer_desc, id, matrix); - } - } -} - -bool VectorTileData::hasData(const StyleLayer &layer_desc) const { - if (state == State::parsed && layer_desc.bucket) { - auto databucket_it = buckets.find(layer_desc.bucket->name); - if (databucket_it != buckets.end()) { - assert(databucket_it->second); - return databucket_it->second->hasData(); +Bucket* VectorTileData::getBucket(StyleLayer const& layer) { + if (state == State::parsed && layer.bucket) { + const auto it = buckets.find(layer.bucket->name); + if (it != buckets.end()) { + assert(it->second); + return it->second.get(); } } - return false; + return nullptr; } diff --git a/src/mbgl/map/vector_tile_data.hpp b/src/mbgl/map/vector_tile_data.hpp index 4e2f252f85..10eaf9c184 100644 --- a/src/mbgl/map/vector_tile_data.hpp +++ b/src/mbgl/map/vector_tile_data.hpp @@ -31,7 +31,7 @@ class VectorTileData : public TileData { public: VectorTileData(const TileID&, float mapMaxZoom, - util::ptr<Style>, + Style&, GlyphAtlas&, GlyphStore&, SpriteAtlas&, @@ -40,8 +40,7 @@ public: ~VectorTileData(); void parse() override; - void render(Painter &painter, const StyleLayer &layer_desc, const mat4 &matrix) override; - bool hasData(StyleLayer const& layer_desc) const override; + virtual Bucket* getBucket(StyleLayer const &layer_desc) override; protected: // Holds the actual geometries in this tile. @@ -50,7 +49,6 @@ protected: TriangleElementsBuffer triangleElementsBuffer; LineElementsBuffer lineElementsBuffer; - PointElementsBuffer pointElementsBuffer; // Holds the buckets of this tile. // They contain the location offsets in the buffers stored above @@ -60,7 +58,7 @@ protected: GlyphStore& glyphStore; SpriteAtlas& spriteAtlas; util::ptr<Sprite> sprite; - util::ptr<Style> style; + Style& style; public: const float depth; diff --git a/src/mbgl/map/view.cpp b/src/mbgl/map/view.cpp index fddcf3acdd..fb771a8c49 100644 --- a/src/mbgl/map/view.cpp +++ b/src/mbgl/map/view.cpp @@ -2,6 +2,8 @@ #include <mbgl/map/map.hpp> #include <mbgl/map/still_image.hpp> +#include <cassert> + namespace mbgl { void View::initialize(Map *map_) { @@ -9,7 +11,7 @@ void View::initialize(Map *map_) { map = map_; } -void View::discard() { +void View::resize(uint16_t, uint16_t, float) { // no-op } @@ -17,11 +19,6 @@ std::unique_ptr<StillImage> View::readStillImage() { return nullptr; } -void View::resize(uint16_t width, uint16_t height, float ratio, uint16_t fbWidth, uint16_t fbHeight) { - assert(map); - map->resize(width, height, ratio, fbWidth, fbHeight); -} - void View::notifyMapChange(MapChange, Duration) { // no-op } diff --git a/src/mbgl/renderer/bucket.hpp b/src/mbgl/renderer/bucket.hpp index a7b0f61a3b..711fc42384 100644 --- a/src/mbgl/renderer/bucket.hpp +++ b/src/mbgl/renderer/bucket.hpp @@ -1,6 +1,7 @@ #ifndef MBGL_RENDERER_BUCKET #define MBGL_RENDERER_BUCKET +#include <mbgl/renderer/render_pass.hpp> #include <mbgl/util/noncopyable.hpp> #include <mbgl/util/mat4.hpp> @@ -12,10 +13,23 @@ class TileID; class Bucket : private util::noncopyable { public: + // As long as this bucket has a Prepare render pass, this function is getting called. Typically, + // this only happens once when the bucket is being rendered for the first time. + virtual void upload() = 0; + + // Every time this bucket is getting rendered, this function is called. This happens either + // once or twice (for Opaque and Transparent render passes). virtual void render(Painter&, const StyleLayer&, const TileID&, const mat4&) = 0; - virtual bool hasData() const = 0; + virtual ~Bucket() {} + inline bool needsUpload() const { + return !uploaded; + } + +protected: + bool uploaded = false; + }; } diff --git a/src/mbgl/renderer/debug_bucket.cpp b/src/mbgl/renderer/debug_bucket.cpp index ed03458dad..a315d089e4 100644 --- a/src/mbgl/renderer/debug_bucket.cpp +++ b/src/mbgl/renderer/debug_bucket.cpp @@ -1,23 +1,29 @@ #include <mbgl/renderer/debug_bucket.hpp> #include <mbgl/renderer/painter.hpp> +#include <mbgl/shader/plain_shader.hpp> #include <mbgl/platform/gl.hpp> #include <cassert> +#ifndef BUFFER_OFFSET +#define BUFFER_OFFSET(i) ((char *)nullptr + (i)) +#endif + using namespace mbgl; DebugBucket::DebugBucket(DebugFontBuffer& fontBuffer_) : fontBuffer(fontBuffer_) { } -void DebugBucket::render(Painter &painter, const StyleLayer & /*layer_desc*/, - const TileID & /*id*/, const mat4 &matrix) { - painter.renderDebugText(*this, matrix); +void DebugBucket::upload() { + fontBuffer.upload(); + + uploaded = true; } -bool DebugBucket::hasData() const { - return fontBuffer.index() > 0; +void DebugBucket::render(Painter& painter, const StyleLayer&, const TileID&, const mat4& matrix) { + painter.renderDebugText(*this, matrix); } void DebugBucket::drawLines(PlainShader& shader) { diff --git a/src/mbgl/renderer/debug_bucket.hpp b/src/mbgl/renderer/debug_bucket.hpp index 074d1d3991..e3c01bbddb 100644 --- a/src/mbgl/renderer/debug_bucket.hpp +++ b/src/mbgl/renderer/debug_bucket.hpp @@ -7,10 +7,6 @@ #include <vector> -#ifndef BUFFER_OFFSET -#define BUFFER_OFFSET(i) ((char *)nullptr + (i)) -#endif - namespace mbgl { class PlainShader; @@ -19,9 +15,8 @@ class DebugBucket : public Bucket { public: DebugBucket(DebugFontBuffer& fontBuffer); - void render(Painter &painter, const StyleLayer &layer_desc, const TileID &id, - const mat4 &matrix) override; - bool hasData() const override; + void upload() override; + void render(Painter&, const StyleLayer&, const TileID&, const mat4&) override; void drawLines(PlainShader& shader); void drawPoints(PlainShader& shader); diff --git a/src/mbgl/renderer/fill_bucket.cpp b/src/mbgl/renderer/fill_bucket.cpp index f5726690b1..c59b0970e0 100644 --- a/src/mbgl/renderer/fill_bucket.cpp +++ b/src/mbgl/renderer/fill_bucket.cpp @@ -4,18 +4,23 @@ #include <mbgl/renderer/painter.hpp> #include <mbgl/style/style.hpp> #include <mbgl/style/style_layout.hpp> +#include <mbgl/shader/plain_shader.hpp> +#include <mbgl/shader/pattern_shader.hpp> +#include <mbgl/shader/outline_shader.hpp> #include <mbgl/util/std.hpp> #include <mbgl/platform/gl.hpp> #include <mbgl/platform/log.hpp> #include <cassert> +#ifndef BUFFER_OFFSET +#define BUFFER_OFFSET(i) ((char *)nullptr + (i)) +#endif + struct geometry_too_long_exception : std::exception {}; using namespace mbgl; - - void *FillBucket::alloc(void *, unsigned int size) { return ::malloc(size); } @@ -84,7 +89,7 @@ void FillBucket::tessellate() { hasVertices = false; std::vector<std::vector<ClipperLib::IntPoint>> polygons; - clipper.Execute(ClipperLib::ctUnion, polygons, ClipperLib::pftPositive); + clipper.Execute(ClipperLib::ctUnion, polygons, ClipperLib::pftEvenOdd, ClipperLib::pftEvenOdd); clipper.Clear(); if (polygons.size() == 0) { @@ -132,7 +137,7 @@ void FillBucket::tessellate() { lineGroup.elements_length += total_vertex_count; - if (tessTesselate(tesselator, TESS_WINDING_POSITIVE, TESS_POLYGONS, vertices_per_group, vertexSize, 0)) { + if (tessTesselate(tesselator, TESS_WINDING_ODD, TESS_POLYGONS, vertices_per_group, vertexSize, 0)) { const TESSreal *vertices = tessGetVertices(tesselator); const size_t vertex_count = tessGetVertexCount(tesselator); TESSindex *vertex_indices = const_cast<TESSindex *>(tessGetVertexIndices(tesselator)); @@ -195,8 +200,19 @@ void FillBucket::tessellate() { lineGroup.vertex_length += total_vertex_count; } -void FillBucket::render(Painter &painter, const StyleLayer &layer_desc, const TileID &id, - const mat4 &matrix) { +void FillBucket::upload() { + vertexBuffer.upload(); + triangleElementsBuffer.upload(); + lineElementsBuffer.upload(); + + // From now on, we're going to render during the opaque and translucent pass. + uploaded = true; +} + +void FillBucket::render(Painter& painter, + const StyleLayer& layer_desc, + const TileID& id, + const mat4& matrix) { painter.renderFill(*this, layer_desc, id, matrix); } diff --git a/src/mbgl/renderer/fill_bucket.hpp b/src/mbgl/renderer/fill_bucket.hpp index d28f849a2c..2ff86ba9af 100644 --- a/src/mbgl/renderer/fill_bucket.hpp +++ b/src/mbgl/renderer/fill_bucket.hpp @@ -11,10 +11,6 @@ #include <vector> #include <memory> -#ifndef BUFFER_OFFSET -#define BUFFER_OFFSET(i) ((char *)nullptr + (i)) -#endif - namespace mbgl { class FillVertexBuffer; @@ -37,9 +33,9 @@ public: LineElementsBuffer &lineElementsBuffer); ~FillBucket() override; - void render(Painter &painter, const StyleLayer &layer_desc, const TileID &id, - const mat4 &matrix) override; - bool hasData() const override; + void upload() override; + void render(Painter&, const StyleLayer&, const TileID&, const mat4&) override; + bool hasData() const; void addGeometry(const GeometryCollection&); void tessellate(); diff --git a/src/mbgl/renderer/gl_config.cpp b/src/mbgl/renderer/gl_config.cpp new file mode 100644 index 0000000000..ae648d4d33 --- /dev/null +++ b/src/mbgl/renderer/gl_config.cpp @@ -0,0 +1,19 @@ +#include "gl_config.hpp" + +namespace mbgl { +namespace gl { + +const ClearDepth::Type ClearDepth::Default = 0; +const ClearColor::Type ClearColor::Default = { 0, 0, 0, 0 }; +const ClearStencil::Type ClearStencil::Default = 0; +const StencilMask::Type StencilMask::Default = ~0u; +const DepthMask::Type DepthMask::Default = GL_TRUE; +const ColorMask::Type ColorMask::Default = { false, false, false, false }; +const StencilFunc::Type StencilFunc::Default = { GL_ALWAYS, 0, ~0u }; +const StencilTest::Type StencilTest::Default = false; +const DepthRange::Type DepthRange::Default = { 0, 1 }; +const DepthTest::Type DepthTest::Default = false; +const Blend::Type Blend::Default = false; + +} +} diff --git a/src/mbgl/renderer/gl_config.hpp b/src/mbgl/renderer/gl_config.hpp new file mode 100644 index 0000000000..59507f721f --- /dev/null +++ b/src/mbgl/renderer/gl_config.hpp @@ -0,0 +1,149 @@ +#ifndef MBGL_RENDERER_GL_CONFIG +#define MBGL_RENDERER_GL_CONFIG + +#include <cstdint> +#include <tuple> +#include <array> + +#include <mbgl/platform/gl.hpp> + +namespace mbgl { +namespace gl { + +template <typename T> +class Value { +public: + inline void operator=(const typename T::Type& value) { + if (current != value) { + current = value; + MBGL_CHECK_ERROR(T::Set(current)); + } + } + +private: + typename T::Type current = T::Default; +}; + +struct ClearDepth { + using Type = GLfloat; + static const Type Default; + inline static void Set(const Type& value) { + MBGL_CHECK_ERROR(glClearDepth(value)); + } +}; + +struct ClearColor { + struct Type { float r, g, b, a; }; + static const Type Default; + inline static void Set(const Type& value) { + MBGL_CHECK_ERROR(glClearColor(value.r, value.g, value.b, value.a)); + } +}; + +inline bool operator!=(const ClearColor::Type& a, const ClearColor::Type& b) { + return a.r != b.r || a.g != b.g || a.b != b.b || a.a != b.a; +} + +struct ClearStencil { + using Type = GLint; + static const Type Default; + inline static void Set(const Type& value) { + MBGL_CHECK_ERROR(glClearStencil(value)); + } +}; + +struct StencilMask { + using Type = GLuint; + static const Type Default; + inline static void Set(const Type& value) { + MBGL_CHECK_ERROR(glStencilMask(value)); + } +}; + +struct DepthMask { + using Type = GLboolean; + static const Type Default; + inline static void Set(const Type& value) { + MBGL_CHECK_ERROR(glDepthMask(value)); + } +}; + +struct ColorMask { + struct Type { bool r, g, b, a; }; + static const Type Default; + inline static void Set(const Type& value) { + MBGL_CHECK_ERROR(glColorMask(value.r, value.g, value.b, value.a)); + } +}; + +inline bool operator!=(const ColorMask::Type& a, const ColorMask::Type& b) { + return a.r != b.r || a.g != b.g || a.b != b.b || a.a != b.a; +} + +struct StencilFunc { + struct Type { GLenum func; GLint ref; GLuint mask; }; + static const Type Default; + inline static void Set(const Type& value) { + MBGL_CHECK_ERROR(glStencilFunc(value.func, value.ref, value.mask)); + } +}; + +inline bool operator!=(const StencilFunc::Type& a, const StencilFunc::Type& b) { + return a.func != b.func || a.ref != b.ref || a.mask != b.mask; +} + +struct StencilTest { + using Type = bool; + static const Type Default; + inline static void Set(const Type& value) { + MBGL_CHECK_ERROR(value ? glEnable(GL_STENCIL_TEST) : glDisable(GL_STENCIL_TEST)); + } +}; + +struct DepthRange { + struct Type { GLfloat near, far; }; + static const Type Default; + inline static void Set(const Type& value) { + MBGL_CHECK_ERROR(glDepthRange(value.near, value.far)); + } +}; + +inline bool operator!=(const DepthRange::Type& a, const DepthRange::Type& b) { + return a.near != b.near || a.far != b.far; +} + +struct DepthTest { + using Type = bool; + static const Type Default; + inline static void Set(const Type& value) { + MBGL_CHECK_ERROR(value ? glEnable(GL_DEPTH_TEST) : glDisable(GL_DEPTH_TEST)); + } +}; + +struct Blend { + using Type = bool; + static const Type Default; + inline static void Set(const Type& value) { + MBGL_CHECK_ERROR(value ? glEnable(GL_BLEND) : glDisable(GL_BLEND)); + } +}; + +class Config { +public: + Value<StencilFunc> stencilFunc; + Value<StencilMask> stencilMask; + Value<StencilTest> stencilTest; + Value<DepthRange> depthRange; + Value<DepthMask> depthMask; + Value<DepthTest> depthTest; + Value<Blend> blend; + Value<ColorMask> colorMask; + Value<ClearDepth> clearDepth; + Value<ClearColor> clearColor; + Value<ClearStencil> clearStencil; +}; + +} +} + +#endif diff --git a/src/mbgl/renderer/line_bucket.cpp b/src/mbgl/renderer/line_bucket.cpp index 4e09b74640..651b4986e4 100644 --- a/src/mbgl/renderer/line_bucket.cpp +++ b/src/mbgl/renderer/line_bucket.cpp @@ -4,38 +4,32 @@ #include <mbgl/renderer/painter.hpp> #include <mbgl/style/style.hpp> #include <mbgl/style/style_layout.hpp> +#include <mbgl/shader/line_shader.hpp> +#include <mbgl/shader/linesdf_shader.hpp> +#include <mbgl/shader/linepattern_shader.hpp> #include <mbgl/util/math.hpp> #include <mbgl/util/std.hpp> #include <mbgl/platform/gl.hpp> -#define BUFFER_OFFSET(i) ((char *)nullptr + (i)) +#ifndef BUFFER_OFFSET +#define BUFFER_OFFSET(i) ((char*)nullptr + (i)) +#endif #include <cassert> using namespace mbgl; -LineBucket::LineBucket(LineVertexBuffer &vertexBuffer_, - TriangleElementsBuffer &triangleElementsBuffer_, - PointElementsBuffer &pointElementsBuffer_) +LineBucket::LineBucket(LineVertexBuffer& vertexBuffer_, + TriangleElementsBuffer& triangleElementsBuffer_) : vertexBuffer(vertexBuffer_), triangleElementsBuffer(triangleElementsBuffer_), - pointElementsBuffer(pointElementsBuffer_), vertex_start(vertexBuffer_.index()), - triangle_elements_start(triangleElementsBuffer_.index()), - point_elements_start(pointElementsBuffer_.index()) { -} + triangle_elements_start(triangleElementsBuffer_.index()){}; LineBucket::~LineBucket() { // Do not remove. header file only contains forward definitions to unique pointers. } -struct TriangleElement { - TriangleElement(uint16_t a_, uint16_t b_, uint16_t c_) : a(a_), b(b_), c(c_) {} - uint16_t a, b, c; -}; - -typedef uint16_t PointElement; - void LineBucket::addGeometry(const GeometryCollection& geometryCollection) { for (auto& line : geometryCollection) { addGeometry(line); @@ -43,366 +37,374 @@ void LineBucket::addGeometry(const GeometryCollection& geometryCollection) { } void LineBucket::addGeometry(const std::vector<Coordinate>& vertices) { - // TODO: use roundLimit - // const float roundLimit = geometry.round_limit; + const auto len = [&vertices] { + auto l = vertices.size(); + // If the line has duplicate vertices at the end, adjust length to remove them. + while (l > 2 && vertices[l - 1] == vertices[l - 2]) { + l--; + } + return l; + }(); - if (vertices.size() < 2) { + if (len < 2) { // fprintf(stderr, "a line must have at least two vertices\n"); return; } - Coordinate firstVertex = vertices.front(); - Coordinate lastVertex = vertices.back(); - bool closed = firstVertex.x == lastVertex.x && firstVertex.y == lastVertex.y; + const float miterLimit = layout.join == JoinType::Bevel ? 1.05f : layout.miter_limit; + + const Coordinate firstVertex = vertices.front(); + const Coordinate lastVertex = vertices[len - 1]; + const bool closed = firstVertex == lastVertex; - if (vertices.size() == 2 && closed) { + if (len == 2 && closed) { // fprintf(stderr, "a line may not have coincident points\n"); return; } - CapType beginCap = layout.cap; - CapType endCap = closed ? CapType::Butt : layout.cap; - - JoinType currentJoin = JoinType::Miter; - - Coordinate currentVertex = Coordinate::null(), - prevVertex = Coordinate::null(), - nextVertex = Coordinate::null(); - vec2<double> prevNormal = vec2<double>::null(), - nextNormal = vec2<double>::null(); - - int32_t e1 = -1, e2 = -1, e3 = -1; + const CapType beginCap = layout.cap; + const CapType endCap = closed ? CapType::Butt : layout.cap; int8_t flip = 1; double distance = 0; + bool startOfLine = true; + Coordinate currentVertex = Coordinate::null(), prevVertex = Coordinate::null(), + nextVertex = Coordinate::null(); + vec2<double> prevNormal = vec2<double>::null(), nextNormal = vec2<double>::null(); + + // the last three vertices added + e1 = e2 = e3 = -1; if (closed) { - currentVertex = vertices[vertices.size() - 2]; - nextNormal = util::normal<double>(currentVertex, lastVertex); + currentVertex = vertices[len - 2]; + nextNormal = util::perp(util::unit(vec2<double>(firstVertex - currentVertex))); } - int32_t start_vertex = (int32_t)vertexBuffer.index(); - - std::vector<TriangleElement> triangle_store; - std::vector<PointElement> point_store; + const int32_t startVertex = (int32_t)vertexBuffer.index(); + std::vector<TriangleElement> triangleStore; - for (size_t i = 0; i < vertices.size(); ++i) { - if (nextNormal) prevNormal = { -nextNormal.x, -nextNormal.y }; - if (currentVertex) prevVertex = currentVertex; - - currentVertex = vertices[i]; - currentJoin = layout.join; - - if (prevVertex) distance += util::dist<double>(currentVertex, prevVertex); - - // Find the next vertex. - if (i + 1 < vertices.size()) { + for (size_t i = 0; i < len; ++i) { + if (closed && i == len - 1) { + // if the line is closed, we treat the last vertex like the first + nextVertex = vertices[i]; + } else if (i + 1 < len) { + // just the next vertex nextVertex = vertices[i + 1]; } else { + // there is no next vertex nextVertex = Coordinate::null(); } - // If the line is closed, we treat the last vertex like the first vertex. - if (!nextVertex && closed) { - nextVertex = vertices[1]; + // if two consecutive vertices exist, skip the current one + if (nextVertex && vertices[i] == nextVertex) { + continue; } - if (nextVertex) { - // if two consecutive vertices exist, skip one - if (currentVertex.x == nextVertex.x && currentVertex.y == nextVertex.y) continue; + if (nextNormal) { + prevNormal = nextNormal; + } + if (currentVertex) { + prevVertex = currentVertex; } + currentVertex = vertices[i]; + + // Calculate how far along the line the currentVertex is + if (prevVertex) + distance += util::dist<double>(currentVertex, prevVertex); + // Calculate the normal towards the next vertex in this line. In case // there is no next vertex, pretend that the line is continuing straight, - // meaning that we are just reversing the previous normal - if (nextVertex) { - nextNormal = util::normal<double>(currentVertex, nextVertex); - } else { - nextNormal = { -prevNormal.x, -prevNormal.y }; - } + // meaning that we are just using the previous normal. + nextNormal = nextVertex ? util::perp(util::unit(vec2<double>(nextVertex - currentVertex))) + : prevNormal; // If we still don't have a previous normal, this is the beginning of a // non-closed line, so we're doing a straight "join". if (!prevNormal) { - prevNormal = { -nextNormal.x, -nextNormal.y }; + prevNormal = nextNormal; } // Determine the normal of the join extrusion. It is the angle bisector // of the segments between the previous line and the next line. - vec2<double> joinNormal = { - prevNormal.x + nextNormal.x, - prevNormal.y + nextNormal.y - }; - - // Cross product yields 0..1 depending on whether they are parallel - // or perpendicular. - double joinAngularity = nextNormal.x * joinNormal.y - nextNormal.y * joinNormal.x; - joinNormal.x /= joinAngularity; - joinNormal.y /= joinAngularity; - double roundness = std::fmax(std::abs(joinNormal.x), std::abs(joinNormal.y)); - - - // Switch to miter joins if the angle is very low. - if (currentJoin != JoinType::Miter) { - if (std::fabs(joinAngularity) < 0.5 && roundness < layout.miter_limit) { + vec2<double> joinNormal = util::unit(prevNormal + nextNormal); + + /* joinNormal prevNormal + * ↖ ↑ + * .________. prevVertex + * | + * nextNormal ← | currentVertex + * | + * nextVertex ! + * + */ + + // Calculate the length of the miter (the ratio of the miter to the width). + // Find the cosine of the angle between the next and join normals + // using dot product. The inverse of that is the miter length. + const float cosHalfAngle = joinNormal.x * nextNormal.x + joinNormal.y * nextNormal.y; + const float miterLength = 1 / cosHalfAngle; + + // The join if a middle vertex, otherwise the cap + const bool middleVertex = prevVertex && nextVertex; + JoinType currentJoin = layout.join; + const CapType currentCap = nextVertex ? beginCap : endCap; + + if (middleVertex) { + if (currentJoin == JoinType::Round && miterLength < layout.round_limit) { currentJoin = JoinType::Miter; } - } - - // Add offset square begin cap. - if (!prevVertex && beginCap == CapType::Square) { - // Add first vertex - e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos - flip * (prevNormal.x + prevNormal.y), flip * (-prevNormal.x + prevNormal.y), // extrude normal - 0, 0, distance) - start_vertex; // texture normal - - if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3); - e1 = e2; e2 = e3; - - // Add second vertex - e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos - flip * (prevNormal.x - prevNormal.y), flip * (prevNormal.x + prevNormal.y), // extrude normal - 0, 1, distance) - start_vertex; // texture normal - if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3); - e1 = e2; e2 = e3; - } - - // Add offset square end cap. - else if (!nextVertex && endCap == CapType::Square) { - // Add first vertex - e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos - nextNormal.x - flip * nextNormal.y, flip * nextNormal.x + nextNormal.y, // extrude normal - 0, 0, distance) - start_vertex; // texture normal - - if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3); - e1 = e2; e2 = e3; + if (currentJoin == JoinType::Miter && miterLength > miterLimit) { + currentJoin = JoinType::Bevel; + } - // Add second vertex - e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos - nextNormal.x + flip * nextNormal.y, -flip * nextNormal.x + nextNormal.y, // extrude normal - 0, 1, distance) - start_vertex; // texture normal + if (currentJoin == JoinType::Bevel) { + // The maximum extrude length is 128 / 63 = 2 times the width of the line + // so if miterLength >= 2 we need to draw a different type of bevel where. + if (miterLength > 2) { + currentJoin = JoinType::FlipBevel; + } - if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3); - e1 = e2; e2 = e3; + // If the miterLength is really small and the line bevel wouldn't be visible, + // just draw a miter join to save a triangle. + if (miterLength < miterLimit) { + currentJoin = JoinType::Miter; + } + } } - else if (currentJoin == JoinType::Miter) { - // MITER JOIN - if (std::fabs(joinAngularity) < 0.01) { - // The two normals are almost parallel. - joinNormal.x = -nextNormal.y; - joinNormal.y = nextNormal.x; - } else if (roundness > layout.miter_limit) { - // If the miter grows too large, flip the direction to make a - // bevel join. - joinNormal.x = (prevNormal.x - nextNormal.x) / joinAngularity; - joinNormal.y = (prevNormal.y - nextNormal.y) / joinAngularity; + if (middleVertex && currentJoin == JoinType::Miter) { + joinNormal = joinNormal * miterLength; + addCurrentVertex(currentVertex, flip, distance, joinNormal, 0, 0, false, startVertex, + triangleStore); + + } else if (middleVertex && currentJoin == JoinType::FlipBevel) { + // miter is too big, flip the direction to make a beveled join + + if (miterLength > 100) { + // Almost parallel lines + joinNormal = nextNormal; + } else { + const float direction = prevNormal.x * nextNormal.y - prevNormal.y * nextNormal.x > 0 ? -1 : 1; + const float bevelLength = miterLength * util::mag(prevNormal + nextNormal) / + util::mag(prevNormal - nextNormal * direction); + joinNormal = util::perp(joinNormal) * bevelLength; } - if (roundness > layout.miter_limit) { - flip = -flip; + addCurrentVertex(currentVertex, flip, distance, joinNormal, 0, 0, false, startVertex, + triangleStore); + flip = -flip; + + } else if (middleVertex && currentJoin == JoinType::Bevel) { + const float dir = prevNormal.x * nextNormal.y - prevNormal.y * nextNormal.x; + const float offset = -std::sqrt(miterLength * miterLength - 1); + float offsetA; + float offsetB; + + if (flip * dir > 0) { + offsetB = 0; + offsetA = offset; + } else { + offsetA = 0; + offsetB = offset; } - // Add first vertex - e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos - flip * joinNormal.x, flip * joinNormal.y, // extrude normal - 0, 0, distance) - start_vertex; // texture normal - - if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3); - e1 = e2; e2 = e3; + // Close previous segement with bevel + if (!startOfLine) { + addCurrentVertex(currentVertex, flip, distance, prevNormal, offsetA, offsetB, false, + startVertex, triangleStore); + } - // Add second vertex - e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos - -flip * joinNormal.x, -flip * joinNormal.y, // extrude normal - 0, 1, distance) - start_vertex; // texture normal + // Start next segment + if (nextVertex) { + addCurrentVertex(currentVertex, flip, distance, nextNormal, -offsetA, -offsetB, + false, startVertex, triangleStore); + } - if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3); - e1 = e2; e2 = e3; + } else if (!middleVertex && currentCap == CapType::Butt) { + if (!startOfLine) { + // Close previous segment with a butt + addCurrentVertex(currentVertex, flip, distance, prevNormal, 0, 0, false, + startVertex, triangleStore); + } - if ((!prevVertex && beginCap == CapType::Round) || - (!nextVertex && endCap == CapType::Round)) { - point_store.emplace_back(e1); + // Start next segment with a butt + if (nextVertex) { + addCurrentVertex(currentVertex, flip, distance, nextNormal, 0, 0, false, + startVertex, triangleStore); } - } - else { - // Close up the previous line - // Add first vertex - e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos - flip * prevNormal.y, -flip * prevNormal.x, // extrude normal - 0, 0, distance) - start_vertex; // texture normal + } else if (!middleVertex && currentCap == CapType::Square) { + if (!startOfLine) { + // Close previous segment with a square cap + addCurrentVertex(currentVertex, flip, distance, prevNormal, 1, 1, false, + startVertex, triangleStore); - if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3); - e1 = e2; e2 = e3; + // The segment is done. Unset vertices to disconnect segments. + e1 = e2 = -1; + flip = 1; + } - // Add second vertex. - e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos - -flip * prevNormal.y, flip * prevNormal.x, // extrude normal - 0, 1, distance) - start_vertex; // texture normal + // Start next segment + if (nextVertex) { + addCurrentVertex(currentVertex, flip, distance, nextNormal, -1, -1, false, + startVertex, triangleStore); + } - if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3); - e1 = e2; e2 = e3; + } else if (middleVertex ? currentJoin == JoinType::Round : currentCap == CapType::Round) { + if (!startOfLine) { + // Close previous segment with a butt + addCurrentVertex(currentVertex, flip, distance, prevNormal, 0, 0, false, + startVertex, triangleStore); - prevNormal = { -nextNormal.x, -nextNormal.y }; - flip = 1; + // Add round cap or linejoin at end of segment + addCurrentVertex(currentVertex, flip, distance, prevNormal, 1, 1, true, startVertex, + triangleStore); + // The segment is done. Unset vertices to disconnect segments. + e1 = e2 = -1; + flip = 1; - // begin/end caps - if ((!prevVertex && beginCap == CapType::Round) || - (!nextVertex && endCap == CapType::Round)) { - point_store.emplace_back(e1); + } else if (beginCap == CapType::Round) { + // Add round cap before first segment + addCurrentVertex(currentVertex, flip, distance, nextNormal, -1, -1, true, + startVertex, triangleStore); } - - if (currentJoin == JoinType::Round) { - if (prevVertex && nextVertex && (!closed || i > 0)) { - point_store.emplace_back(e1); - } - - // Reset the previous vertices so that we don't accidentally create - // any triangles. - e1 = -1; e2 = -1; e3 = -1; + // Start next segment with a butt + if (nextVertex) { + addCurrentVertex(currentVertex, flip, distance, nextNormal, 0, 0, false, + startVertex, triangleStore); } - - // Start the new quad. - // Add first vertex - e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos - -flip * nextNormal.y, flip * nextNormal.x, // extrude normal - 0, 0, distance) - start_vertex; // texture normal - - if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3); - e1 = e2; e2 = e3; - - // Add second vertex - e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos - flip * nextNormal.y, -flip * nextNormal.x, // extrude normal - 0, 1, distance) - start_vertex; // texture normal - - if (e1 >= 0 && e2 >= 0 && e3 >= 0) triangle_store.emplace_back(e1, e2, e3); - e1 = e2; e2 = e3; } + + startOfLine = false; } - size_t end_vertex = vertexBuffer.index(); - size_t vertex_count = end_vertex - start_vertex; + const size_t endVertex = vertexBuffer.index(); + const size_t vertexCount = endVertex - startVertex; // Store the triangle/line groups. { - if (!triangleGroups.size() || (triangleGroups.back()->vertex_length + vertex_count > 65535)) { + if (!triangleGroups.size() || + (triangleGroups.back()->vertex_length + vertexCount > 65535)) { // Move to a new group because the old one can't hold the geometry. - triangleGroups.emplace_back(util::make_unique<triangle_group_type>()); + triangleGroups.emplace_back(util::make_unique<TriangleGroup>()); } assert(triangleGroups.back()); - triangle_group_type& group = *triangleGroups.back(); - for (const auto& triangle : triangle_store) { - triangleElementsBuffer.add( - group.vertex_length + triangle.a, - group.vertex_length + triangle.b, - group.vertex_length + triangle.c - ); + auto& group = *triangleGroups.back(); + for (const auto& triangle : triangleStore) { + triangleElementsBuffer.add(group.vertex_length + triangle.a, + group.vertex_length + triangle.b, + group.vertex_length + triangle.c); } - group.vertex_length += vertex_count; - group.elements_length += triangle_store.size(); + group.vertex_length += vertexCount; + group.elements_length += triangleStore.size(); } +} - // Store the line join/cap groups. - { - if (!pointGroups.size() || (pointGroups.back()->vertex_length + vertex_count > 65535)) { - // Move to a new group because the old one can't hold the geometry. - pointGroups.emplace_back(util::make_unique<point_group_type>()); - } +void LineBucket::addCurrentVertex(const Coordinate& currentVertex, + float flip, + double distance, + const vec2<double>& normal, + float endLeft, + float endRight, + bool round, + int32_t startVertex, + std::vector<TriangleElement>& triangleStore) { + int8_t tx = round ? 1 : 0; + + vec2<double> extrude = normal * flip; + if (endLeft) + extrude = extrude - (util::perp(normal) * endLeft); + e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, extrude.x, extrude.y, tx, 0, + distance) - + startVertex; + if (e1 >= 0 && e2 >= 0) { + triangleStore.emplace_back(e1, e2, e3); + } + e1 = e2; + e2 = e3; + + extrude = normal * (-flip); + if (endRight) + extrude = extrude - (util::perp(normal) * endRight); + e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, extrude.x, extrude.y, tx, 1, + distance) - + startVertex; + if (e1 >= 0 && e2 >= 0) { + triangleStore.emplace_back(e1, e2, e3); + } + e1 = e2; + e2 = e3; +} - assert(pointGroups.back()); - point_group_type& group = *pointGroups.back(); - for (const auto point : point_store) { - pointElementsBuffer.add(group.vertex_length + point); - } +void LineBucket::upload() { + vertexBuffer.upload(); + triangleElementsBuffer.upload(); - group.vertex_length += vertex_count; - group.elements_length += point_store.size(); - } + // From now on, we're only going to render during the translucent pass. + uploaded = true; } -void LineBucket::render(Painter &painter, const StyleLayer &layer_desc, const TileID &id, - const mat4 &matrix) { +void LineBucket::render(Painter& painter, + const StyleLayer& layer_desc, + const TileID& id, + const mat4& matrix) { painter.renderLine(*this, layer_desc, id, matrix); } bool LineBucket::hasData() const { - return !triangleGroups.empty() || !pointGroups.empty(); -} - -bool LineBucket::hasPoints() const { - if (!pointGroups.empty()) { - for (const auto& group : pointGroups) { - assert(group); - if (group->elements_length) { - return true; - } - } - } - return false; + return !triangleGroups.empty(); } void LineBucket::drawLines(LineShader& shader) { - char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize); - char *elements_index = BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer.itemSize); + char* vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize); + char* elements_index = BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer.itemSize); for (auto& group : triangleGroups) { assert(group); if (!group->elements_length) { continue; } group->array[0].bind(shader, vertexBuffer, triangleElementsBuffer, vertex_index); - MBGL_CHECK_ERROR(glDrawElements(GL_TRIANGLES, group->elements_length * 3, GL_UNSIGNED_SHORT, elements_index)); + MBGL_CHECK_ERROR(glDrawElements(GL_TRIANGLES, group->elements_length * 3, GL_UNSIGNED_SHORT, + elements_index)); vertex_index += group->vertex_length * vertexBuffer.itemSize; elements_index += group->elements_length * triangleElementsBuffer.itemSize; } } void LineBucket::drawLineSDF(LineSDFShader& shader) { - char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize); - char *elements_index = BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer.itemSize); + char* vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize); + char* elements_index = BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer.itemSize); for (auto& group : triangleGroups) { assert(group); if (!group->elements_length) { continue; } group->array[2].bind(shader, vertexBuffer, triangleElementsBuffer, vertex_index); - MBGL_CHECK_ERROR(glDrawElements(GL_TRIANGLES, group->elements_length * 3, GL_UNSIGNED_SHORT, elements_index)); + MBGL_CHECK_ERROR(glDrawElements(GL_TRIANGLES, group->elements_length * 3, GL_UNSIGNED_SHORT, + elements_index)); vertex_index += group->vertex_length * vertexBuffer.itemSize; elements_index += group->elements_length * triangleElementsBuffer.itemSize; } } void LineBucket::drawLinePatterns(LinepatternShader& shader) { - char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize); - char *elements_index = BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer.itemSize); + char* vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize); + char* elements_index = BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer.itemSize); for (auto& group : triangleGroups) { assert(group); if (!group->elements_length) { continue; } group->array[1].bind(shader, vertexBuffer, triangleElementsBuffer, vertex_index); - MBGL_CHECK_ERROR(glDrawElements(GL_TRIANGLES, group->elements_length * 3, GL_UNSIGNED_SHORT, elements_index)); + MBGL_CHECK_ERROR(glDrawElements(GL_TRIANGLES, group->elements_length * 3, GL_UNSIGNED_SHORT, + elements_index)); vertex_index += group->vertex_length * vertexBuffer.itemSize; elements_index += group->elements_length * triangleElementsBuffer.itemSize; } } - -void LineBucket::drawPoints(LinejoinShader& shader) { - char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize); - char *elements_index = BUFFER_OFFSET(point_elements_start * pointElementsBuffer.itemSize); - for (auto& group : pointGroups) { - assert(group); - if (!group->elements_length) { - continue; - } - group->array[0].bind(shader, vertexBuffer, pointElementsBuffer, vertex_index); - MBGL_CHECK_ERROR(glDrawElements(GL_POINTS, group->elements_length, GL_UNSIGNED_SHORT, elements_index)); - vertex_index += group->vertex_length * vertexBuffer.itemSize; - elements_index += group->elements_length * pointElementsBuffer.itemSize; - } -} diff --git a/src/mbgl/renderer/line_bucket.hpp b/src/mbgl/renderer/line_bucket.hpp index d70801e0bf..6f13e3c819 100644 --- a/src/mbgl/renderer/line_bucket.hpp +++ b/src/mbgl/renderer/line_bucket.hpp @@ -18,33 +18,35 @@ class Style; class LineVertexBuffer; class TriangleElementsBuffer; class LineShader; -class LinejoinShader; class LineSDFShader; class LinepatternShader; class LineBucket : public Bucket { - typedef ElementGroup<3> triangle_group_type; - typedef ElementGroup<1> point_group_type; + using TriangleGroup = ElementGroup<3>; public: - LineBucket(LineVertexBuffer &vertexBuffer, - TriangleElementsBuffer &triangleElementsBuffer, - PointElementsBuffer &pointElementsBuffer); + LineBucket(LineVertexBuffer &vertexBuffer, TriangleElementsBuffer &triangleElementsBuffer); ~LineBucket() override; - void render(Painter &painter, const StyleLayer &layer_desc, const TileID &id, - const mat4 &matrix) override; - bool hasData() const override; + void upload() override; + void render(Painter&, const StyleLayer&, const TileID&, const mat4&) override; + bool hasData() const; void addGeometry(const GeometryCollection&); void addGeometry(const std::vector<Coordinate>& line); - bool hasPoints() const; - void drawLines(LineShader& shader); void drawLineSDF(LineSDFShader& shader); void drawLinePatterns(LinepatternShader& shader); - void drawPoints(LinejoinShader& shader); + +private: + struct TriangleElement { + TriangleElement(uint16_t a_, uint16_t b_, uint16_t c_) : a(a_), b(b_), c(c_) {} + uint16_t a, b, c; + }; + void addCurrentVertex(const Coordinate& currentVertex, float flip, double distance, + const vec2<double>& normal, float endLeft, float endRight, bool round, + int32_t startVertex, std::vector<LineBucket::TriangleElement>& triangleStore); public: StyleLayoutLine layout; @@ -52,14 +54,15 @@ public: private: LineVertexBuffer& vertexBuffer; TriangleElementsBuffer& triangleElementsBuffer; - PointElementsBuffer& pointElementsBuffer; const size_t vertex_start; const size_t triangle_elements_start; - const size_t point_elements_start; - std::vector<std::unique_ptr<triangle_group_type>> triangleGroups; - std::vector<std::unique_ptr<point_group_type>> pointGroups; + int32_t e1; + int32_t e2; + int32_t e3; + + std::vector<std::unique_ptr<TriangleGroup>> triangleGroups; }; } diff --git a/src/mbgl/renderer/painter.cpp b/src/mbgl/renderer/painter.cpp index cafd614244..a80fd4e8b7 100644 --- a/src/mbgl/renderer/painter.cpp +++ b/src/mbgl/renderer/painter.cpp @@ -1,16 +1,33 @@ #include <mbgl/renderer/painter.hpp> + +#include <mbgl/map/source.hpp> +#include <mbgl/map/tile.hpp> + #include <mbgl/platform/log.hpp> + #include <mbgl/style/style.hpp> #include <mbgl/style/style_layer.hpp> #include <mbgl/style/style_bucket.hpp> + +#include <mbgl/geometry/sprite_atlas.hpp> +#include <mbgl/geometry/line_atlas.hpp> +#include <mbgl/geometry/glyph_atlas.hpp> + +#include <mbgl/shader/pattern_shader.hpp> +#include <mbgl/shader/plain_shader.hpp> +#include <mbgl/shader/outline_shader.hpp> +#include <mbgl/shader/line_shader.hpp> +#include <mbgl/shader/linesdf_shader.hpp> +#include <mbgl/shader/linepattern_shader.hpp> +#include <mbgl/shader/icon_shader.hpp> +#include <mbgl/shader/raster_shader.hpp> +#include <mbgl/shader/sdf_shader.hpp> +#include <mbgl/shader/dot_shader.hpp> +#include <mbgl/shader/gaussian_shader.hpp> + #include <mbgl/util/std.hpp> -#include <mbgl/util/string.hpp> -#include <mbgl/util/clip_id.hpp> #include <mbgl/util/constants.hpp> #include <mbgl/util/mat3.hpp> -#include <mbgl/geometry/sprite_atlas.hpp> -#include <mbgl/map/source.hpp> -#include <mbgl/map/tile.hpp> #if defined(DEBUG) #include <mbgl/util/stopwatch.hpp> @@ -39,10 +56,6 @@ bool Painter::needsAnimation() const { } void Painter::setup() { -#if defined(DEBUG) - util::stopwatch stopwatch("painter setup"); -#endif - // Enable GL debugging if ((gl::DebugMessageControl != nullptr) && (gl::DebugMessageCallback != nullptr)) { // This will enable all messages including performance hints @@ -62,7 +75,6 @@ void Painter::setup() { assert(plainShader); assert(outlineShader); assert(lineShader); - assert(linejoinShader); assert(linepatternShader); assert(patternShader); assert(rasterShader); @@ -76,16 +88,15 @@ void Painter::setup() { // We are blending new pixels on top of old pixels. Since we have depth testing // and are drawing opaque fragments first front-to-back, then translucent // fragments back-to-front, this shades the fewest fragments possible. - MBGL_CHECK_ERROR(glEnable(GL_BLEND)); + config.blend = true; MBGL_CHECK_ERROR(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)); // Set clear values - MBGL_CHECK_ERROR(glClearColor(0.0f, 0.0f, 0.0f, 0.0f)); - MBGL_CHECK_ERROR(glClearDepth(1.0f)); - MBGL_CHECK_ERROR(glClearStencil(0x0)); + config.clearColor = { 0.0f, 0.0f, 0.0f, 0.0f }; + config.clearDepth = 1.0f; + config.clearStencil = 0x0; // Stencil test - MBGL_CHECK_ERROR(glEnable(GL_STENCIL_TEST)); MBGL_CHECK_ERROR(glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE)); // Depth test @@ -96,7 +107,6 @@ void Painter::setupShaders() { if (!plainShader) plainShader = util::make_unique<PlainShader>(); if (!outlineShader) outlineShader = util::make_unique<OutlineShader>(); if (!lineShader) lineShader = util::make_unique<LineShader>(); - if (!linejoinShader) linejoinShader = util::make_unique<LinejoinShader>(); if (!linesdfShader) linesdfShader = util::make_unique<LineSDFShader>(); if (!linepatternShader) linepatternShader = util::make_unique<LinepatternShader>(); if (!patternShader) patternShader = util::make_unique<PatternShader>(); @@ -108,25 +118,6 @@ void Painter::setupShaders() { if (!gaussianShader) gaussianShader = util::make_unique<GaussianShader>(); } -void Painter::deleteShaders() { - plainShader = nullptr; - outlineShader = nullptr; - lineShader = nullptr; - linejoinShader = nullptr; - linepatternShader = nullptr; - patternShader = nullptr; - iconShader = nullptr; - rasterShader = nullptr; - sdfGlyphShader = nullptr; - sdfIconShader = nullptr; - dotShader = nullptr; - gaussianShader = nullptr; -} - -void Painter::terminate() { - deleteShaders(); -} - void Painter::resize() { if (gl_viewport != state.getFramebufferDimensions()) { gl_viewport = state.getFramebufferDimensions(); @@ -153,21 +144,6 @@ void Painter::lineWidth(float line_width) { } } -void Painter::depthMask(bool value) { - if (gl_depthMask != value) { - MBGL_CHECK_ERROR(glDepthMask(value ? GL_TRUE : GL_FALSE)); - gl_depthMask = value; - } -} - -void Painter::depthRange(const float near, const float far) { - if (gl_depthRange[0] != near || gl_depthRange[1] != far) { - MBGL_CHECK_ERROR(glDepthRange(near, far)); - gl_depthRange = {{ near, far }}; - } -} - - void Painter::changeMatrix() { // Initialize projection matrix matrix::ortho(projMatrix, 0, state.getWidth(), state.getHeight(), 0, 0, 1); @@ -185,24 +161,25 @@ void Painter::changeMatrix() { void Painter::clear() { gl::group group("clear"); - MBGL_CHECK_ERROR(glStencilMask(0xFF)); - depthMask(true); - - MBGL_CHECK_ERROR(glClearColor(0, 0, 0, 0)); + config.stencilTest = true; + config.stencilMask = 0xFF; + config.depthTest = false; + config.depthMask = GL_TRUE; + config.clearColor = { 0.0f, 0.0f, 0.0f, 0.0f }; MBGL_CHECK_ERROR(glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); } void Painter::setOpaque() { if (pass != RenderPass::Opaque) { pass = RenderPass::Opaque; - MBGL_CHECK_ERROR(glDisable(GL_BLEND)); + config.blend = false; } } void Painter::setTranslucent() { if (pass != RenderPass::Translucent) { pass = RenderPass::Translucent; - MBGL_CHECK_ERROR(glEnable(GL_BLEND)); + config.blend = true; } } @@ -213,16 +190,12 @@ void Painter::setStrata(float value) { void Painter::prepareTile(const Tile& tile) { const GLint ref = (GLint)tile.clip.reference.to_ulong(); const GLuint mask = (GLuint)tile.clip.mask.to_ulong(); - MBGL_CHECK_ERROR(glStencilFunc(GL_EQUAL, ref, mask)); + config.stencilFunc = { GL_EQUAL, ref, mask }; } void Painter::render(const Style& style, TransformState state_, TimePoint time) { state = state_; - clear(); - resize(); - changeMatrix(); - std::set<Source*> sources; for (const auto& source : style.sources) { if (source->enabled) { @@ -230,14 +203,46 @@ void Painter::render(const Style& style, TransformState state_, TimePoint time) } } - // Update all clipping IDs. - ClipIDGenerator generator; - for (const auto& source : sources) { - generator.update(source->getLoadedTiles()); - source->updateMatrices(projMatrix, state); + // Figure out what buckets we have to draw and what order we have to draw them in. + const auto order = determineRenderOrder(style); + + // - UPLOAD PASS ------------------------------------------------------------------------------- + // Uploads all required buffers and images before we do any actual rendering. + { + const gl::group upload("upload"); + + tileStencilBuffer.upload(); + tileBorderBuffer.upload(); + spriteAtlas.upload(); + lineAtlas.upload(); + glyphAtlas.upload(); + + for (const auto& item : order) { + if (item.bucket && item.bucket->needsUpload()) { + item.bucket->upload(); + } + } } - drawClippingMasks(sources); + + // - CLIPPING MASKS ---------------------------------------------------------------------------- + // Draws the clipping masks to the stencil buffer. + { + const gl::group clip("clip"); + + // Update all clipping IDs. + ClipIDGenerator generator; + for (const auto& source : sources) { + generator.update(source->getLoadedTiles()); + source->updateMatrices(projMatrix, state); + } + + clear(); + resize(); + changeMatrix(); + + drawClippingMasks(sources); + } frameHistory.record(time, state.getNormalizedZoom()); @@ -245,122 +250,169 @@ void Painter::render(const Style& style, TransformState state_, TimePoint time) if (debug::renderTree) { Log::Info(Event::Render, "{"); indent++; } // TODO: Correctly compute the number of layers recursively beforehand. - float strata_thickness = 1.0f / (style.layers.size() + 1); - - // - FIRST PASS ------------------------------------------------------------ - // Render everything top-to-bottom by using reverse iterators. Render opaque - // objects first. + const float strata_thickness = 1.0f / (order.size() + 1); - if (debug::renderTree) { - Log::Info(Event::Render, "%*s%s", indent++ * 4, "", "OPAQUE {"); - } + // Layer index int i = 0; - for (auto it = style.layers.rbegin(), end = style.layers.rend(); it != end; ++it, ++i) { + + // - OPAQUE PASS ------------------------------------------------------------------------------- + // Render everything top-to-bottom by using reverse iterators. Render opaque objects first. + { + const gl::group _("opaque"); + + if (debug::renderTree) { + Log::Info(Event::Render, "%*s%s", indent++ * 4, "", "OPAQUE {"); + } + i = 0; setOpaque(); - setStrata(i * strata_thickness); - renderLayer(**it); - } - if (debug::renderTree) { - Log::Info(Event::Render, "%*s%s", --indent * 4, "", "}"); + for (auto it = order.rbegin(), end = order.rend(); it != end; ++it, ++i) { + const auto& item = *it; + if (item.bucket && item.tile) { + if (item.hasRenderPass(RenderPass::Opaque)) { + const gl::group group(item.layer.id + " - " + std::string(item.tile->id)); + setStrata(i * strata_thickness); + prepareTile(*item.tile); + item.bucket->render(*this, item.layer, item.tile->id, item.tile->matrix); + } + } else { + const gl::group group("background"); + renderBackground(item.layer); + } + } + if (debug::renderTree) { + Log::Info(Event::Render, "%*s%s", --indent * 4, "", "}"); + } } - // - SECOND PASS ----------------------------------------------------------- - // Make a second pass, rendering translucent objects. This time, we render - // bottom-to-top. - if (debug::renderTree) { - Log::Info(Event::Render, "%*s%s", indent++ * 4, "", "TRANSLUCENT {"); - } - --i; - for (auto it = style.layers.begin(), end = style.layers.end(); it != end; ++it, --i) { + // - TRANSLUCENT PASS -------------------------------------------------------------------------- + // Make a second pass, rendering translucent objects. This time, we render bottom-to-top. + { + const gl::group _("translucent"); + + if (debug::renderTree) { + Log::Info(Event::Render, "%*s%s", indent++ * 4, "", "TRANSLUCENT {"); + } + --i; // After the last iteration, this is incremented, so we have to decrement it again. setTranslucent(); - setStrata(i * strata_thickness); - renderLayer(**it); - } - if (debug::renderTree) { - Log::Info(Event::Render, "%*s%s", --indent * 4, "", "}"); + for (auto it = order.begin(), end = order.end(); it != end; ++it, --i) { + const auto& item = *it; + if (item.bucket && item.tile) { + if (item.hasRenderPass(RenderPass::Translucent)) { + const gl::group group(item.layer.id + " - " + std::string(item.tile->id)); + setStrata(i * strata_thickness); + prepareTile(*item.tile); + item.bucket->render(*this, item.layer, item.tile->id, item.tile->matrix); + } + } + } + if (debug::renderTree) { + Log::Info(Event::Render, "%*s%s", --indent * 4, "", "}"); + } } if (debug::renderTree) { Log::Info(Event::Render, "}"); indent--; } - // Finalize the rendering, e.g. by calling debug render calls per tile. - // This guarantees that we have at least one function per tile called. - // When only rendering layers via the stylesheet, it's possible that we don't - // ever visit a tile during rendering. - for (const auto& source : sources) { - source->finishRender(*this); + // - DEBUG PASS -------------------------------------------------------------------------------- + // Renders debug overlays. + { + const gl::group _("debug"); + + // Finalize the rendering, e.g. by calling debug render calls per tile. + // This guarantees that we have at least one function per tile called. + // When only rendering layers via the stylesheet, it's possible that we don't + // ever visit a tile during rendering. + for (const auto& source : sources) { + source->finishRender(*this); + } + } + + // TODO: Find a better way to unbind VAOs after we're done with them without introducing + // unnecessary bind(0)/bind(N) sequences. + { + const gl::group _("cleanup"); + + MBGL_CHECK_ERROR(glBindTexture(GL_TEXTURE_2D, 0)); + MBGL_CHECK_ERROR(gl::BindVertexArray(0)); } } -void Painter::renderLayer(const StyleLayer &layer_desc) { - if (layer_desc.bucket->visibility == VisibilityType::None) return; - if (layer_desc.type == StyleLayerType::Background) { - // This layer defines a background color/image. +std::vector<RenderItem> Painter::determineRenderOrder(const Style& style) { + std::vector<RenderItem> order; - if (debug::renderTree) { - Log::Info(Event::Render, "%*s- %s (%s)", indent * 4, "", layer_desc.id.c_str(), - StyleLayerTypeClass(layer_desc.type).c_str()); + for (const auto& layerPtr : style.layers) { + const auto& layer = *layerPtr; + if (layer.bucket->visibility == VisibilityType::None) continue; + if (layer.type == StyleLayerType::Background) { + // This layer defines a background color/image. + order.emplace_back(layer); + continue; } - renderBackground(layer_desc); - } else { // This is a singular layer. - if (!layer_desc.bucket) { - Log::Warning(Event::Render, "layer '%s' is missing bucket", layer_desc.id.c_str()); - return; + if (!layer.bucket) { + Log::Warning(Event::Render, "layer '%s' is missing bucket", layer.id.c_str()); + continue; } - if (!layer_desc.bucket->source) { - Log::Warning(Event::Render, "can't find source for layer '%s'", layer_desc.id.c_str()); - return; + if (!layer.bucket->source) { + Log::Warning(Event::Render, "can't find source for layer '%s'", layer.id.c_str()); + continue; } // Skip this layer if it's outside the range of min/maxzoom. // This may occur when there /is/ a bucket created for this layer, but the min/max-zoom // is set to a fractional value, or value that is larger than the source maxzoom. const double zoom = state.getZoom(); - if (layer_desc.bucket->min_zoom > zoom || - layer_desc.bucket->max_zoom <= zoom) { - return; + if (layer.bucket->min_zoom > zoom || + layer.bucket->max_zoom <= zoom) { + continue; } - // Abort early if we can already deduce from the bucket type that - // we're not going to render anything anyway during this pass. - switch (layer_desc.type) { - case StyleLayerType::Fill: - if (!layer_desc.getProperties<FillProperties>().isVisible()) return; - break; - case StyleLayerType::Line: - if (pass == RenderPass::Opaque) return; - if (!layer_desc.getProperties<LineProperties>().isVisible()) return; - break; - case StyleLayerType::Symbol: - if (pass == RenderPass::Opaque) return; - if (!layer_desc.getProperties<SymbolProperties>().isVisible()) return; - break; - case StyleLayerType::Raster: - if (pass == RenderPass::Opaque) return; - if (!layer_desc.getProperties<RasterProperties>().isVisible()) return; - break; - default: - break; + // Don't include invisible layers. + if (!layer.isVisible()) { + continue; } - if (debug::renderTree) { - Log::Info(Event::Render, "%*s- %s (%s)", indent * 4, "", layer_desc.id.c_str(), - StyleLayerTypeClass(layer_desc.type).c_str()); - } + // Determine what render passes we need for this layer. + const RenderPass passes = determineRenderPasses(layer); + + const auto& tiles = layer.bucket->source->getTiles(); + for (auto tile : tiles) { + assert(tile); + if (!tile->data && !tile->data->ready()) { + continue; + } - layer_desc.bucket->source->render(*this, layer_desc); + auto bucket = tile->data->getBucket(layer); + if (bucket) { + order.emplace_back(layer, tile, bucket, passes); + } + } } + + return order; } -void Painter::renderTileLayer(const Tile& tile, const StyleLayer &layer_desc, const mat4 &matrix) { - assert(tile.data); - if (tile.data->hasData(layer_desc) || layer_desc.type == StyleLayerType::Raster) { - gl::group group(std::string { "render " } + tile.data->name); - prepareTile(tile); - tile.data->render(*this, layer_desc, matrix); +RenderPass Painter::determineRenderPasses(const StyleLayer& layer) { + RenderPass passes = RenderPass::None; + + if (layer.properties.is<FillProperties>()) { + const FillProperties &properties = layer.properties.get<FillProperties>(); + const float alpha = properties.fill_color[3] * properties.opacity; + + if (properties.antialias) { + passes |= RenderPass::Translucent; + } + if (properties.image.from.size() || alpha < 1.0f) { + passes |= RenderPass::Translucent; + } else { + passes |= RenderPass::Opaque; + } + } else { + passes |= RenderPass::Translucent; } + + return passes; } void Painter::renderBackground(const StyleLayer &layer_desc) { @@ -437,10 +489,10 @@ void Painter::renderBackground(const StyleLayer &layer_desc) { backgroundArray.bind(*plainShader, backgroundBuffer, BUFFER_OFFSET(0)); } - MBGL_CHECK_ERROR(glDisable(GL_STENCIL_TEST)); - depthRange(strata + strata_epsilon, 1.0f); + config.stencilTest = false; + config.depthTest = true; + config.depthRange = { strata + strata_epsilon, 1.0f }; MBGL_CHECK_ERROR(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)); - MBGL_CHECK_ERROR(glEnable(GL_STENCIL_TEST)); } mat4 Painter::translatedMatrix(const mat4& matrix, const std::array<float, 2> &translation, const TileID &id, TranslateAnchorType anchor) { diff --git a/src/mbgl/renderer/painter.hpp b/src/mbgl/renderer/painter.hpp index cf01c03918..54c23b393a 100644 --- a/src/mbgl/renderer/painter.hpp +++ b/src/mbgl/renderer/painter.hpp @@ -1,61 +1,81 @@ #ifndef MBGL_RENDERER_PAINTER #define MBGL_RENDERER_PAINTER -#include <mbgl/map/tile_data.hpp> +#include <mbgl/map/transform_state.hpp> + +#include <mbgl/renderer/frame_history.hpp> +#include <mbgl/renderer/bucket.hpp> + #include <mbgl/geometry/vao.hpp> #include <mbgl/geometry/static_vertex_buffer.hpp> -#include <mbgl/util/mat4.hpp> -#include <mbgl/util/noncopyable.hpp> -#include <mbgl/renderer/frame_history.hpp> + +#include <mbgl/renderer/gl_config.hpp> + #include <mbgl/style/types.hpp> -#include <mbgl/shader/plain_shader.hpp> -#include <mbgl/shader/outline_shader.hpp> -#include <mbgl/shader/pattern_shader.hpp> -#include <mbgl/shader/line_shader.hpp> -#include <mbgl/shader/linejoin_shader.hpp> -#include <mbgl/shader/linesdf_shader.hpp> -#include <mbgl/shader/linepattern_shader.hpp> -#include <mbgl/shader/icon_shader.hpp> -#include <mbgl/shader/raster_shader.hpp> -#include <mbgl/shader/sdf_shader.hpp> -#include <mbgl/shader/dot_shader.hpp> -#include <mbgl/shader/gaussian_shader.hpp> +#include <mbgl/platform/gl.hpp> -#include <mbgl/map/transform_state.hpp> -#include <mbgl/util/ptr.hpp> +#include <mbgl/util/noncopyable.hpp> #include <mbgl/util/chrono.hpp> -#include <map> -#include <unordered_map> +#include <array> +#include <vector> #include <set> namespace mbgl { -enum class RenderPass : bool { Opaque, Translucent }; - -class Transform; class Style; +class StyleLayer; class Tile; -class Sprite; class SpriteAtlas; class GlyphAtlas; class LineAtlas; class Source; + +class DebugBucket; class FillBucket; class LineBucket; class SymbolBucket; class RasterBucket; -class PrerenderedTexture; -struct FillProperties; struct RasterProperties; -class LayerDescription; -class RasterTileData; +class SDFShader; +class PlainShader; +class OutlineShader; +class LineShader; +class LinejoinShader; +class LineSDFShader; +class LinepatternShader; +class PatternShader; +class IconShader; +class RasterShader; +class SDFGlyphShader; +class SDFIconShader; +class DotShader; +class GaussianShader; + struct ClipID; +struct RenderItem { + inline RenderItem(const StyleLayer& layer_, + const Tile* tile_ = nullptr, + Bucket* bucket_ = nullptr, + RenderPass passes_ = RenderPass::Opaque) + : tile(tile_), bucket(bucket_), layer(layer_), passes(passes_) { + } + + const Tile* const tile; + Bucket* const bucket; + const StyleLayer& layer; + const RenderPass passes; + + inline bool hasRenderPass(RenderPass pass) const { + return bool(passes & pass); + } +}; + class Painter : private util::noncopyable { public: Painter(SpriteAtlas&, GlyphAtlas&, LineAtlas&); @@ -63,11 +83,6 @@ public: void setup(); - // Perform cleanup tasks that prepare shutting down the app. This doesn't mean that the - // app will be shut down. That means all operations must be automatically be reversed (e.g. through - // lazy initialization) in case rendering continues. - void terminate(); - // Renders the backdrop of the OpenGL view. This also paints in areas where we don't have any // tiles whatsoever. void clear(); @@ -79,11 +94,6 @@ public: TransformState state, TimePoint time); - void renderLayer(const StyleLayer&); - - // Renders a particular layer from a tile. - void renderTileLayer(const Tile& tile, const StyleLayer &layer_desc, const mat4 &matrix); - // Renders debug information for a tile. void renderTileDebug(const Tile& tile); @@ -133,9 +143,11 @@ public: private: void setupShaders(); - void deleteShaders(); mat4 translatedMatrix(const mat4& matrix, const std::array<float, 2> &translation, const TileID &id, TranslateAnchorType anchor); + std::vector<RenderItem> determineRenderOrder(const Style& style); + static RenderPass determineRenderPasses(const StyleLayer&); + void prepareTile(const Tile& tile); template <typename BucketProperties, typename StyleProperties> @@ -152,8 +164,6 @@ private: public: void useProgram(uint32_t program); void lineWidth(float lineWidth); - void depthMask(bool value); - void depthRange(float near, float far); public: mat4 projMatrix; @@ -180,11 +190,11 @@ private: bool debug = false; int indent = 0; + gl::Config config; + uint32_t gl_program = 0; float gl_lineWidth = 0; - bool gl_depthMask = true; std::array<uint16_t, 2> gl_viewport = {{ 0, 0 }}; - std::array<float, 2> gl_depthRange = {{ 0, 1 }}; float strata = 0; RenderPass pass = RenderPass::Opaque; const float strata_epsilon = 1.0f / (1 << 16); @@ -199,7 +209,6 @@ public: std::unique_ptr<PlainShader> plainShader; std::unique_ptr<OutlineShader> outlineShader; std::unique_ptr<LineShader> lineShader; - std::unique_ptr<LinejoinShader> linejoinShader; std::unique_ptr<LineSDFShader> linesdfShader; std::unique_ptr<LinepatternShader> linepatternShader; std::unique_ptr<PatternShader> patternShader; diff --git a/src/mbgl/renderer/painter_clipping.cpp b/src/mbgl/renderer/painter_clipping.cpp index 3a981ed1ec..c495359282 100644 --- a/src/mbgl/renderer/painter_clipping.cpp +++ b/src/mbgl/renderer/painter_clipping.cpp @@ -1,20 +1,23 @@ #include <mbgl/renderer/painter.hpp> -#include <mbgl/renderer/fill_bucket.hpp> -#include <mbgl/map/map.hpp> -#include <mbgl/map/tile.hpp> #include <mbgl/map/source.hpp> +#include <mbgl/shader/plain_shader.hpp> #include <mbgl/util/clip_id.hpp> +#ifndef BUFFER_OFFSET +#define BUFFER_OFFSET(i) ((char *)nullptr + (i)) +#endif + using namespace mbgl; void Painter::drawClippingMasks(const std::set<Source*>& sources) { gl::group group("clipping masks"); useProgram(plainShader->program); - MBGL_CHECK_ERROR(glDisable(GL_DEPTH_TEST)); - depthMask(false); - MBGL_CHECK_ERROR(glColorMask(false, false, false, false)); - depthRange(1.0f, 1.0f); + config.stencilTest = true; + config.depthTest = true; + config.depthMask = GL_FALSE; + config.colorMask = { false, false, false, false }; + config.depthRange = { 1.0f, 1.0f }; coveringPlainArray.bind(*plainShader, tileStencilBuffer, BUFFER_OFFSET(0)); @@ -22,10 +25,10 @@ void Painter::drawClippingMasks(const std::set<Source*>& sources) { source->drawClippingMasks(*this); } - MBGL_CHECK_ERROR(glEnable(GL_DEPTH_TEST)); - MBGL_CHECK_ERROR(glColorMask(true, true, true, true)); - depthMask(true); - MBGL_CHECK_ERROR(glStencilMask(0x0)); + config.depthTest = true; + config.colorMask = { true, true, true, true }; + config.depthMask = GL_TRUE; + config.stencilMask = 0x0; } void Painter::drawClippingMask(const mat4& matrix, const ClipID &clip) { @@ -33,8 +36,7 @@ void Painter::drawClippingMask(const mat4& matrix, const ClipID &clip) { const GLint ref = (GLint)(clip.reference.to_ulong()); const GLuint mask = (GLuint)(clip.mask.to_ulong()); - MBGL_CHECK_ERROR(glStencilFunc(GL_ALWAYS, ref, mask)); - MBGL_CHECK_ERROR(glStencilMask(mask)); - + config.stencilFunc = { GL_ALWAYS, ref, mask }; + config.stencilMask = mask; MBGL_CHECK_ERROR(glDrawArrays(GL_TRIANGLES, 0, (GLsizei)tileStencilBuffer.index())); } diff --git a/src/mbgl/renderer/painter_debug.cpp b/src/mbgl/renderer/painter_debug.cpp index 2931473283..b252e2e835 100644 --- a/src/mbgl/renderer/painter_debug.cpp +++ b/src/mbgl/renderer/painter_debug.cpp @@ -1,9 +1,14 @@ #include <mbgl/renderer/painter.hpp> #include <mbgl/renderer/debug_bucket.hpp> -#include <mbgl/map/map.hpp> #include <mbgl/map/tile.hpp> +#include <mbgl/map/tile_data.hpp> +#include <mbgl/shader/plain_shader.hpp> #include <mbgl/util/string.hpp> +#ifndef BUFFER_OFFSET +#define BUFFER_OFFSET(i) ((char *)nullptr + (i)) +#endif + using namespace mbgl; void Painter::renderTileDebug(const Tile& tile) { @@ -19,7 +24,7 @@ void Painter::renderTileDebug(const Tile& tile) { void Painter::renderDebugText(DebugBucket& bucket, const mat4 &matrix) { gl::group group("debug text"); - MBGL_CHECK_ERROR(glDisable(GL_DEPTH_TEST)); + config.depthTest = false; useProgram(plainShader->program); plainShader->u_matrix = matrix; @@ -40,7 +45,7 @@ void Painter::renderDebugText(DebugBucket& bucket, const mat4 &matrix) { lineWidth(2.0f * state.getPixelRatio()); bucket.drawLines(*plainShader); - MBGL_CHECK_ERROR(glEnable(GL_DEPTH_TEST)); + config.depthTest = true; } void Painter::renderDebugFrame(const mat4 &matrix) { @@ -49,7 +54,8 @@ void Painter::renderDebugFrame(const mat4 &matrix) { // Disable depth test and don't count this towards the depth buffer, // but *don't* disable stencil test, as we want to clip the red tile border // to the tile viewport. - MBGL_CHECK_ERROR(glDisable(GL_DEPTH_TEST)); + config.depthTest = false; + config.stencilTest = true; useProgram(plainShader->program); plainShader->u_matrix = matrix; @@ -59,8 +65,6 @@ void Painter::renderDebugFrame(const mat4 &matrix) { plainShader->u_color = {{ 1.0f, 0.0f, 0.0f, 1.0f }}; lineWidth(4.0f * state.getPixelRatio()); MBGL_CHECK_ERROR(glDrawArrays(GL_LINE_STRIP, 0, (GLsizei)tileBorderBuffer.index())); - - MBGL_CHECK_ERROR(glEnable(GL_DEPTH_TEST)); } void Painter::renderDebugText(const std::vector<std::string> &strings) { @@ -70,8 +74,9 @@ void Painter::renderDebugText(const std::vector<std::string> &strings) { gl::group group("debug text"); - MBGL_CHECK_ERROR(glDisable(GL_DEPTH_TEST)); - MBGL_CHECK_ERROR(glStencilFunc(GL_ALWAYS, 0xFF, 0xFF)); + config.depthTest = false; + config.stencilTest = true; + config.stencilFunc = { GL_ALWAYS, 0xFF, 0xFF }; useProgram(plainShader->program); plainShader->u_matrix = nativeMatrix; @@ -98,6 +103,4 @@ void Painter::renderDebugText(const std::vector<std::string> &strings) { lineWidth(2.0f * state.getPixelRatio()); MBGL_CHECK_ERROR(glDrawArrays(GL_LINES, 0, (GLsizei)debugFontBuffer.index())); } - - MBGL_CHECK_ERROR(glEnable(GL_DEPTH_TEST)); } diff --git a/src/mbgl/renderer/painter_fill.cpp b/src/mbgl/renderer/painter_fill.cpp index 8025cf3469..6f6783f0e2 100644 --- a/src/mbgl/renderer/painter_fill.cpp +++ b/src/mbgl/renderer/painter_fill.cpp @@ -3,18 +3,18 @@ #include <mbgl/style/style.hpp> #include <mbgl/style/style_layer.hpp> #include <mbgl/style/style_layout.hpp> -#include <mbgl/map/map.hpp> #include <mbgl/map/sprite.hpp> +#include <mbgl/map/tile_id.hpp> #include <mbgl/geometry/sprite_atlas.hpp> +#include <mbgl/shader/outline_shader.hpp> +#include <mbgl/shader/pattern_shader.hpp> +#include <mbgl/shader/plain_shader.hpp> #include <mbgl/util/std.hpp> #include <mbgl/util/mat3.hpp> using namespace mbgl; void Painter::renderFill(FillBucket& bucket, const StyleLayer &layer_desc, const TileID& id, const mat4 &matrix) { - // Abort early. - if (!bucket.hasData()) return; - const FillProperties &properties = layer_desc.getProperties<FillProperties>(); mat4 vtxMatrix = translatedMatrix(matrix, properties.translate, id, properties.translateAnchor); @@ -39,6 +39,9 @@ void Painter::renderFill(FillBucket& bucket, const StyleLayer &layer_desc, const bool outline = properties.antialias && !pattern && stroke_color != fill_color; bool fringeline = properties.antialias && !pattern && stroke_color == fill_color; + config.stencilTest = true; + config.depthTest = true; + // Because we're drawing top-to-bottom, and we update the stencil mask // befrom, we have to draw the outline first (!) if (outline && pass == RenderPass::Translucent) { @@ -53,7 +56,7 @@ void Painter::renderFill(FillBucket& bucket, const StyleLayer &layer_desc, const static_cast<float>(state.getFramebufferWidth()), static_cast<float>(state.getFramebufferHeight()) }}; - depthRange(strata, 1.0f); + config.depthRange = { strata, 1.0f }; bucket.drawVertices(*outlineShader); } @@ -92,8 +95,8 @@ void Painter::renderFill(FillBucket& bucket, const StyleLayer &layer_desc, const spriteAtlas.bind(true); // Draw the actual triangles into the color & stencil buffer. - depthMask(true); - depthRange(strata, 1.0f); + config.depthMask = GL_TRUE; + config.depthRange = { strata, 1.0f }; bucket.drawElements(*patternShader); } } @@ -109,8 +112,8 @@ void Painter::renderFill(FillBucket& bucket, const StyleLayer &layer_desc, const plainShader->u_color = fill_color; // Draw the actual triangles into the color & stencil buffer. - depthMask(true); - depthRange(strata + strata_epsilon, 1.0f); + config.depthMask = GL_TRUE; + config.depthRange = { strata + strata_epsilon, 1.0f }; bucket.drawElements(*plainShader); } } @@ -130,7 +133,7 @@ void Painter::renderFill(FillBucket& bucket, const StyleLayer &layer_desc, const static_cast<float>(state.getFramebufferHeight()) }}; - depthRange(strata + strata_epsilon + strata_epsilon, 1.0f); + config.depthRange = { strata + strata_epsilon + strata_epsilon, 1.0f }; bucket.drawVertices(*outlineShader); } } diff --git a/src/mbgl/renderer/painter_line.cpp b/src/mbgl/renderer/painter_line.cpp index 2f8c3face3..f552ea6c43 100644 --- a/src/mbgl/renderer/painter_line.cpp +++ b/src/mbgl/renderer/painter_line.cpp @@ -4,18 +4,22 @@ #include <mbgl/style/style_layer.hpp> #include <mbgl/style/style_layout.hpp> #include <mbgl/map/sprite.hpp> +#include <mbgl/map/tile_id.hpp> +#include <mbgl/shader/line_shader.hpp> +#include <mbgl/shader/linesdf_shader.hpp> +#include <mbgl/shader/linepattern_shader.hpp> #include <mbgl/geometry/sprite_atlas.hpp> #include <mbgl/geometry/line_atlas.hpp> -#include <mbgl/map/map.hpp> using namespace mbgl; void Painter::renderLine(LineBucket& bucket, const StyleLayer &layer_desc, const TileID& id, const mat4 &matrix) { // Abort early. if (pass == RenderPass::Opaque) return; - if (!bucket.hasData()) return; - depthMask(false); + config.stencilTest = true; + config.depthTest = true; + config.depthMask = GL_FALSE; const auto &properties = layer_desc.getProperties<LineProperties>(); const auto &layout = bucket.layout; @@ -49,30 +53,7 @@ void Painter::renderLine(LineBucket& bucket, const StyleLayer &layer_desc, const float ratio = state.getPixelRatio(); mat4 vtxMatrix = translatedMatrix(matrix, properties.translate, id, properties.translateAnchor); - depthRange(strata, 1.0f); - - // We're only drawing end caps + round line joins if the line is > 2px. Otherwise, they aren't visible anyway. - if (bucket.hasPoints() && outset > 1.0f) { - useProgram(linejoinShader->program); - linejoinShader->u_matrix = vtxMatrix; - linejoinShader->u_color = color; - linejoinShader->u_world = {{ - state.getFramebufferWidth() * 0.5f, - state.getFramebufferHeight() * 0.5f - }}; - linejoinShader->u_linewidth = {{ - ((outset - 0.25f) * state.getPixelRatio()), - ((inset - 0.25f) * state.getPixelRatio()) - }}; - - float pointSize = std::ceil(state.getPixelRatio() * outset * 2.0); -#if defined(GL_ES_VERSION_2_0) - linejoinShader->u_size = pointSize; -#else - MBGL_CHECK_ERROR(glPointSize(pointSize)); -#endif - bucket.drawPoints(*linejoinShader); - } + config.depthRange = { strata, 1.0f }; if (properties.dash_array.from.size()) { @@ -130,7 +111,7 @@ void Painter::renderLine(LineBucket& bucket, const StyleLayer &layer_desc, const MBGL_CHECK_ERROR(glActiveTexture(GL_TEXTURE0)); spriteAtlas.bind(true); - MBGL_CHECK_ERROR(glDepthRange(strata + strata_epsilon, 1.0f)); // may or may not matter + config.depthRange = { strata + strata_epsilon, 1.0f }; // may or may not matter bucket.drawLinePatterns(*linepatternShader); diff --git a/src/mbgl/renderer/painter_raster.cpp b/src/mbgl/renderer/painter_raster.cpp index 5fac248ee6..61aff9c1a8 100644 --- a/src/mbgl/renderer/painter_raster.cpp +++ b/src/mbgl/renderer/painter_raster.cpp @@ -2,8 +2,8 @@ #include <mbgl/platform/gl.hpp> #include <mbgl/renderer/raster_bucket.hpp> #include <mbgl/style/style_layer.hpp> +#include <mbgl/shader/raster_shader.hpp> #include <mbgl/util/std.hpp> -#include <mbgl/map/map.hpp> using namespace mbgl; @@ -23,8 +23,9 @@ void Painter::renderRaster(RasterBucket& bucket, const StyleLayer &layer_desc, c rasterShader->u_contrast_factor = contrastFactor(properties.contrast); rasterShader->u_spin_weights = spinWeights(properties.hue_rotate); - depthRange(strata + strata_epsilon, 1.0f); - + config.stencilTest = true; + config.depthTest = true; + config.depthRange = { strata + strata_epsilon, 1.0f }; bucket.drawRaster(*rasterShader, tileStencilBuffer, coveringRasterArray); } } diff --git a/src/mbgl/renderer/painter_symbol.cpp b/src/mbgl/renderer/painter_symbol.cpp index 2785e8bae3..1841f2bc98 100644 --- a/src/mbgl/renderer/painter_symbol.cpp +++ b/src/mbgl/renderer/painter_symbol.cpp @@ -4,7 +4,8 @@ #include <mbgl/style/style_layout.hpp> #include <mbgl/geometry/glyph_atlas.hpp> #include <mbgl/geometry/sprite_atlas.hpp> -#include <mbgl/map/map.hpp> +#include <mbgl/shader/sdf_shader.hpp> +#include <mbgl/shader/icon_shader.hpp> #include <mbgl/util/math.hpp> #include <cmath> @@ -35,7 +36,7 @@ void Painter::renderSDF(SymbolBucket &bucket, } // If layerStyle.size > bucket.info.fontSize then labels may collide - float fontSize = std::fmin(styleProperties.size, bucketProperties.max_size); + float fontSize = styleProperties.size; float fontScale = fontSize / sdfFontSize; matrix::scale(exMatrix, exMatrix, fontScale, fontScale, 1.0f); @@ -86,7 +87,7 @@ void Painter::renderSDF(SymbolBucket &bucket, sdfShader.u_buffer = (haloOffset - styleProperties.halo_width / fontScale) / sdfPx; - depthRange(strata, 1.0f); + config.depthRange = { strata, 1.0f }; (bucket.*drawSDF)(sdfShader); } @@ -107,7 +108,7 @@ void Painter::renderSDF(SymbolBucket &bucket, sdfShader.u_buffer = (256.0f - 64.0f) / 256.0f; - depthRange(strata + strata_epsilon, 1.0f); + config.depthRange = { strata + strata_epsilon, 1.0f }; (bucket.*drawSDF)(sdfShader); } } @@ -121,8 +122,9 @@ void Painter::renderSymbol(SymbolBucket &bucket, const StyleLayer &layer_desc, c const auto &properties = layer_desc.getProperties<SymbolProperties>(); const auto &layout = bucket.layout; - MBGL_CHECK_ERROR(glDisable(GL_STENCIL_TEST)); - depthMask(false); + config.stencilTest = false; + config.depthTest = true; + config.depthMask = GL_FALSE; if (bucket.hasIconData()) { bool sdf = bucket.sdfIcons; @@ -184,7 +186,7 @@ void Painter::renderSymbol(SymbolBucket &bucket, const StyleLayer &layer_desc, c iconShader->u_fadezoom = state.getNormalizedZoom() * 10; iconShader->u_opacity = properties.icon.opacity; - depthRange(strata, 1.0f); + config.depthRange = { strata, 1.0f }; bucket.drawIcons(*iconShader); } } @@ -202,6 +204,4 @@ void Painter::renderSymbol(SymbolBucket &bucket, const StyleLayer &layer_desc, c *sdfGlyphShader, &SymbolBucket::drawGlyphs); } - - MBGL_CHECK_ERROR(glEnable(GL_STENCIL_TEST)); } diff --git a/src/mbgl/renderer/raster_bucket.cpp b/src/mbgl/renderer/raster_bucket.cpp index b00933d24b..7ee64baaf5 100644 --- a/src/mbgl/renderer/raster_bucket.cpp +++ b/src/mbgl/renderer/raster_bucket.cpp @@ -1,6 +1,11 @@ #include <mbgl/renderer/raster_bucket.hpp> +#include <mbgl/shader/raster_shader.hpp> #include <mbgl/renderer/painter.hpp> +#ifndef BUFFER_OFFSET +#define BUFFER_OFFSET(i) ((char *)nullptr + (i)) +#endif + using namespace mbgl; RasterBucket::RasterBucket(TexturePool& texturePool, const StyleLayoutRaster& layout_) @@ -8,8 +13,17 @@ RasterBucket::RasterBucket(TexturePool& texturePool, const StyleLayoutRaster& la raster(texturePool) { } -void RasterBucket::render(Painter &painter, const StyleLayer &layer_desc, const TileID &id, - const mat4 &matrix) { +void RasterBucket::upload() { + if (hasData()) { + raster.upload(); + uploaded = true; + } +} + +void RasterBucket::render(Painter& painter, + const StyleLayer& layer_desc, + const TileID& id, + const mat4& matrix) { painter.renderRaster(*this, layer_desc, id, matrix); } @@ -24,13 +38,6 @@ void RasterBucket::drawRaster(RasterShader& shader, StaticVertexBuffer &vertices MBGL_CHECK_ERROR(glDrawArrays(GL_TRIANGLES, 0, (GLsizei)vertices.index())); } -void RasterBucket::drawRaster(RasterShader& shader, StaticVertexBuffer &vertices, VertexArrayObject &array, GLuint texture_) { - raster.bind(texture_); - shader.u_image = 0; - array.bind(shader, vertices, BUFFER_OFFSET(0)); - MBGL_CHECK_ERROR(glDrawArrays(GL_TRIANGLES, 0, (GLsizei)vertices.index())); -} - bool RasterBucket::hasData() const { return raster.isLoaded(); } diff --git a/src/mbgl/renderer/raster_bucket.hpp b/src/mbgl/renderer/raster_bucket.hpp index 22a151cf7d..70434b7d60 100644 --- a/src/mbgl/renderer/raster_bucket.hpp +++ b/src/mbgl/renderer/raster_bucket.hpp @@ -5,8 +5,6 @@ #include <mbgl/util/raster.hpp> #include <mbgl/style/style_bucket.hpp> - - namespace mbgl { class StyleLayoutRaster; @@ -18,9 +16,9 @@ class RasterBucket : public Bucket { public: RasterBucket(TexturePool&, const StyleLayoutRaster&); - void render(Painter &painter, const StyleLayer &layer_desc, const TileID &id, - const mat4 &matrix) override; - bool hasData() const override; + void upload() override; + void render(Painter&, const StyleLayer&, const TileID&, const mat4&) override; + bool hasData() const; bool setImage(const std::string &data); @@ -28,8 +26,6 @@ public: void drawRaster(RasterShader& shader, StaticVertexBuffer &vertices, VertexArrayObject &array); - void drawRaster(RasterShader& shader, StaticVertexBuffer &vertices, VertexArrayObject &array, GLuint texture); - Raster raster; }; diff --git a/src/mbgl/renderer/render_pass.hpp b/src/mbgl/renderer/render_pass.hpp new file mode 100644 index 0000000000..a298b8b57e --- /dev/null +++ b/src/mbgl/renderer/render_pass.hpp @@ -0,0 +1,31 @@ +#ifndef MBGL_RENDERER_RENDER_PASS +#define MBGL_RENDERER_RENDER_PASS + +#include <cstdint> +#include <type_traits> + +namespace mbgl { + +enum class RenderPass : uint8_t { + None = 0, + Opaque = 1 << 0, + Translucent = 1 << 1, +}; + +constexpr inline RenderPass operator|(RenderPass a, RenderPass b) { + return static_cast<RenderPass>(static_cast<std::underlying_type<RenderPass>::type>(a) | + static_cast<std::underlying_type<RenderPass>::type>(b)); +} + +inline RenderPass operator|=(RenderPass& a, RenderPass b) { + return (a = a | b); +} + +constexpr inline RenderPass operator&(RenderPass a, RenderPass b) { + return static_cast<RenderPass>(static_cast<std::underlying_type<RenderPass>::type>(a) & + static_cast<std::underlying_type<RenderPass>::type>(b)); +} + +} + +#endif diff --git a/src/mbgl/renderer/symbol_bucket.cpp b/src/mbgl/renderer/symbol_bucket.cpp index 517c8835c6..769feb67a4 100644 --- a/src/mbgl/renderer/symbol_bucket.cpp +++ b/src/mbgl/renderer/symbol_bucket.cpp @@ -12,6 +12,8 @@ #include <mbgl/text/placement.hpp> #include <mbgl/platform/log.hpp> #include <mbgl/text/collision.hpp> +#include <mbgl/shader/sdf_shader.hpp> +#include <mbgl/shader/icon_shader.hpp> #include <mbgl/map/sprite.hpp> #include <mbgl/util/utf.hpp> @@ -20,6 +22,10 @@ #include <mbgl/util/merge_lines.hpp> #include <mbgl/util/std.hpp> +#ifndef BUFFER_OFFSET +#define BUFFER_OFFSET(i) ((char *)nullptr + (i)) +#endif + namespace mbgl { SymbolBucket::SymbolBucket(Collision &collision_) @@ -30,8 +36,23 @@ SymbolBucket::~SymbolBucket() { // Do not remove. header file only contains forward definitions to unique pointers. } -void SymbolBucket::render(Painter &painter, const StyleLayer &layer_desc, const TileID &id, - const mat4 &matrix) { +void SymbolBucket::upload() { + if (hasTextData()) { + text.vertices.upload(); + text.triangles.upload(); + } + if (hasIconData()) { + icon.vertices.upload(); + icon.triangles.upload(); + } + + uploaded = true; +} + +void SymbolBucket::render(Painter& painter, + const StyleLayer& layer_desc, + const TileID& id, + const mat4& matrix) { painter.renderSymbol(*this, layer_desc, id, matrix); } @@ -183,7 +204,8 @@ void SymbolBucket::addFeatures(const GeometryTileLayer& layer, if (feature.label.length()) { shaping = fontStack->getShaping( /* string */ feature.label, - /* maxWidth: ems */ layout.text.max_width * 24, + /* maxWidth: ems */ layout.placement != PlacementType::Line ? + layout.text.max_width * 24 : 0, /* lineHeight: ems */ layout.text.line_height * 24, /* horizontalAlign */ horizontalAlign, /* verticalAlign */ verticalAlign, diff --git a/src/mbgl/renderer/symbol_bucket.hpp b/src/mbgl/renderer/symbol_bucket.hpp index 9ddd653c5d..b1dc44a113 100644 --- a/src/mbgl/renderer/symbol_bucket.hpp +++ b/src/mbgl/renderer/symbol_bucket.hpp @@ -55,9 +55,9 @@ public: SymbolBucket(Collision &collision); ~SymbolBucket() override; - void render(Painter &painter, const StyleLayer &layer_desc, const TileID &id, - const mat4 &matrix) override; - bool hasData() const override; + void upload() override; + void render(Painter&, const StyleLayer&, const TileID&, const mat4&) override; + bool hasData() const; bool hasTextData() const; bool hasIconData() const; diff --git a/src/mbgl/shader/linejoin.fragment.glsl b/src/mbgl/shader/linejoin.fragment.glsl deleted file mode 100644 index 705a57766e..0000000000 --- a/src/mbgl/shader/linejoin.fragment.glsl +++ /dev/null @@ -1,14 +0,0 @@ -uniform vec4 u_color; -uniform vec2 u_linewidth; - -varying vec2 v_pos; - -void main() { - float dist = length(v_pos - gl_FragCoord.xy); - - // Calculate the antialiasing fade factor. This is either when fading in - // the line in case of an offset line (v_linewidth.t) or when fading out - // (v_linewidth.s) - float alpha = clamp(min(dist - (u_linewidth.t - 1.0), u_linewidth.s - dist), 0.0, 1.0); - gl_FragColor = u_color * alpha; -} diff --git a/src/mbgl/shader/linejoin.vertex.glsl b/src/mbgl/shader/linejoin.vertex.glsl deleted file mode 100644 index 2e03561e5b..0000000000 --- a/src/mbgl/shader/linejoin.vertex.glsl +++ /dev/null @@ -1,13 +0,0 @@ -attribute vec2 a_pos; - -uniform mat4 u_matrix; -uniform vec2 u_world; -uniform float u_size; - -varying vec2 v_pos; - -void main() { - gl_Position = u_matrix * vec4(floor(a_pos / 2.0), 0.0, 1.0); - v_pos = (gl_Position.xy + 1.0) * u_world; - gl_PointSize = u_size; -} diff --git a/src/mbgl/shader/linejoin_shader.cpp b/src/mbgl/shader/linejoin_shader.cpp deleted file mode 100644 index b3c5638b5d..0000000000 --- a/src/mbgl/shader/linejoin_shader.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include <mbgl/shader/linejoin_shader.hpp> -#include <mbgl/shader/shaders.hpp> -#include <mbgl/platform/gl.hpp> - -#include <cstdio> - -using namespace mbgl; - -LinejoinShader::LinejoinShader() - : Shader( - "linejoin", - shaders[LINEJOIN_SHADER].vertex, - shaders[LINEJOIN_SHADER].fragment - ) { - a_pos = MBGL_CHECK_ERROR(glGetAttribLocation(program, "a_pos")); -} - -void LinejoinShader::bind(char *offset) { - MBGL_CHECK_ERROR(glEnableVertexAttribArray(a_pos)); - // Note: We're referring to the vertices in a line array, which are 8 bytes long! - MBGL_CHECK_ERROR(glVertexAttribPointer(a_pos, 2, GL_SHORT, false, 8, offset)); -} diff --git a/src/mbgl/shader/linejoin_shader.hpp b/src/mbgl/shader/linejoin_shader.hpp deleted file mode 100644 index 61406fd45c..0000000000 --- a/src/mbgl/shader/linejoin_shader.hpp +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef MBGL_SHADER_SHADER_LINEJOIN -#define MBGL_SHADER_SHADER_LINEJOIN - -#include <mbgl/shader/shader.hpp> -#include <mbgl/shader/uniform.hpp> - -namespace mbgl { - -class LinejoinShader : public Shader { -public: - LinejoinShader(); - - void bind(char *offset); - - UniformMatrix<4> u_matrix = {"u_matrix", *this}; - Uniform<std::array<float, 4>> u_color = {"u_color", *this}; - Uniform<std::array<float, 2>> u_world = {"u_world", *this}; - Uniform<std::array<float, 2>> u_linewidth = {"u_linewidth", *this}; - Uniform<float> u_size = {"u_size", *this}; - -private: - int32_t a_pos = -1; -}; - -} - -#endif diff --git a/src/mbgl/shader/uniform.hpp b/src/mbgl/shader/uniform.hpp index 9a25d105cf..d2a248c609 100644 --- a/src/mbgl/shader/uniform.hpp +++ b/src/mbgl/shader/uniform.hpp @@ -9,7 +9,7 @@ namespace mbgl { template <typename T> class Uniform { public: - Uniform(const GLchar* name, const Shader& shader) { + Uniform(const GLchar* name, const Shader& shader) : current() { location = MBGL_CHECK_ERROR(glGetUniformLocation(shader.program, name)); } @@ -32,7 +32,7 @@ class UniformMatrix { public: typedef std::array<float, C*R> T; - UniformMatrix(const GLchar* name, const Shader& shader) { + UniformMatrix(const GLchar* name, const Shader& shader) : current() { location = MBGL_CHECK_ERROR(glGetUniformLocation(shader.program, name)); } diff --git a/src/mbgl/storage/asset_context.hpp b/src/mbgl/storage/asset_context.hpp new file mode 100644 index 0000000000..44ebdba4ff --- /dev/null +++ b/src/mbgl/storage/asset_context.hpp @@ -0,0 +1,23 @@ +#ifndef MBGL_STORAGE_DEFAULT_ASSET_CONTEXT +#define MBGL_STORAGE_DEFAULT_ASSET_CONTEXT + +#include <mbgl/storage/request_base.hpp> + +typedef struct uv_loop_s uv_loop_t; + +namespace mbgl { + +class AssetContext { +public: + static std::unique_ptr<AssetContext> createContext(uv_loop_t*); + + virtual ~AssetContext() = default; + virtual RequestBase* createRequest(const Resource&, + RequestBase::Callback, + uv_loop_t*, + const std::string& assetRoot) = 0; +}; + +} + +#endif diff --git a/src/mbgl/storage/asset_request.hpp b/src/mbgl/storage/asset_request.hpp deleted file mode 100644 index 48d421c3be..0000000000 --- a/src/mbgl/storage/asset_request.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef MBGL_STORAGE_DEFAULT_ASSET_REQUEST -#define MBGL_STORAGE_DEFAULT_ASSET_REQUEST - -#include "shared_request_base.hpp" - -namespace mbgl { - -class AssetRequest : public SharedRequestBase { -public: - AssetRequest(DefaultFileSource::Impl *source, const Resource &resource); - - void start(uv_loop_t *loop, std::shared_ptr<const Response> response = nullptr); - void cancel(); - -private: - ~AssetRequest(); - void *ptr = nullptr; - - friend class AssetRequestImpl; -}; - -} - -#endif diff --git a/src/mbgl/storage/default_file_source.cpp b/src/mbgl/storage/default_file_source.cpp index 4055001fc4..ddfcc89845 100644 --- a/src/mbgl/storage/default_file_source.cpp +++ b/src/mbgl/storage/default_file_source.cpp @@ -1,7 +1,7 @@ #include <mbgl/storage/default_file_source_impl.hpp> #include <mbgl/storage/request.hpp> -#include <mbgl/storage/asset_request.hpp> -#include <mbgl/storage/http_request.hpp> +#include <mbgl/storage/asset_context.hpp> +#include <mbgl/storage/http_context.hpp> #include <mbgl/storage/response.hpp> #include <mbgl/platform/platform.hpp> @@ -10,13 +10,11 @@ #include <mbgl/util/chrono.hpp> #include <mbgl/util/thread.hpp> #include <mbgl/platform/log.hpp> -#include <mbgl/map/environment.hpp> #pragma GCC diagnostic push -#ifndef __clang__ -#pragma GCC diagnostic ignored "-Wunused-local-typedefs" #pragma GCC diagnostic ignored "-Wshadow" -#endif +#pragma GCC diagnostic ignored "-Wunknown-pragmas" +#pragma GCC diagnostic ignored "-Wunused-local-typedefs" #include <boost/algorithm/string.hpp> #pragma GCC diagnostic pop @@ -28,45 +26,28 @@ namespace algo = boost::algorithm; namespace mbgl { -DefaultFileSource::Impl::Impl(FileCache* cache_, const std::string& root) - : assetRoot(root.empty() ? platform::assetRoot() : root), cache(cache_) { -} - DefaultFileSource::DefaultFileSource(FileCache* cache, const std::string& root) - : thread(util::make_unique<util::Thread<Impl>>("FileSource", cache, root)) { + : thread(util::make_unique<util::Thread<Impl>>("FileSource", util::ThreadPriority::Low, cache, root)) { } DefaultFileSource::~DefaultFileSource() { MBGL_VERIFY_THREAD(tid); } -SharedRequestBase *DefaultFileSource::Impl::find(const Resource &resource) { - // We're using a set of pointers here instead of a map between url and SharedRequestBase because - // we need to find the requests both by pointer and by URL. Given that the number of requests - // is generally very small (typically < 10 at a time), hashing by URL incurs too much overhead - // anyway. - const auto it = pending.find(resource); - if (it != pending.end()) { - return it->second; - } - return nullptr; -} - Request* DefaultFileSource::request(const Resource& resource, uv_loop_t* l, - const Environment& env, Callback callback) { - auto req = new Request(resource, l, env, std::move(callback)); + auto req = new Request(resource, l, std::move(callback)); // This function can be called from any thread. Make sure we're executing the actual call in the // file source loop by sending it over the queue. - thread->invoke(&Impl::add, std::move(req), thread->get()); + thread->invoke(&Impl::add, req); return req; } -void DefaultFileSource::request(const Resource& resource, const Environment& env, Callback callback) { - request(resource, nullptr, env, std::move(callback)); +void DefaultFileSource::request(const Resource& resource, Callback callback) { + request(resource, nullptr, std::move(callback)); } void DefaultFileSource::cancel(Request *req) { @@ -74,133 +55,127 @@ void DefaultFileSource::cancel(Request *req) { // This function can be called from any thread. Make sure we're executing the actual call in the // file source loop by sending it over the queue. - thread->invoke(&Impl::cancel, std::move(req)); + thread->invoke(&Impl::cancel, req); } -void DefaultFileSource::abort(const Environment &env) { - thread->invoke(&Impl::abort, std::ref(env)); -} +// ----- Impl ----- -void DefaultFileSource::Impl::add(Request* req, uv_loop_t* loop) { - const Resource &resource = req->resource; +DefaultFileSource::Impl::Impl(uv_loop_t* loop_, FileCache* cache_, const std::string& root) + : loop(loop_), + cache(cache_), + assetRoot(root.empty() ? platform::assetRoot() : root), + assetContext(AssetContext::createContext(loop_)), + httpContext(HTTPContext::createContext(loop_)) { +} - // We're adding a new Request. - SharedRequestBase *sharedRequest = find(resource); - if (!sharedRequest) { - // There is no request for this URL yet. Create a new one and start it. - if (algo::starts_with(resource.url, "asset://")) { - sharedRequest = new AssetRequest(this, resource); - } else { - sharedRequest = new HTTPRequest(this, resource); - } +DefaultFileRequest* DefaultFileSource::Impl::find(const Resource& resource) { + const auto it = pending.find(resource); + if (it != pending.end()) { + return &it->second; + } + return nullptr; +} - const bool inserted = pending.emplace(resource, sharedRequest).second; - assert(inserted); - (void (inserted)); // silence unused variable warning on Release builds. +void DefaultFileSource::Impl::add(Request* req) { + const Resource& resource = req->resource; + DefaultFileRequest* request = find(resource); - // But first, we're going to start querying the database if it exists. - if (!cache) { - sharedRequest->start(loop); - } else { - // Otherwise, first check the cache for existing data so that we can potentially - // revalidate the information without having to redownload everything. - cache->get(resource, [this, resource, loop](std::unique_ptr<Response> response) { - processResult(resource, std::move(response), loop); - }); - } + if (request) { + request->observers.insert(req); + return; } - sharedRequest->subscribe(req); -} -void DefaultFileSource::Impl::cancel(Request* req) { - SharedRequestBase *sharedRequest = find(req->resource); - if (sharedRequest) { - // If the number of dependent requests of the SharedRequestBase drops to zero, the - // unsubscribe callback triggers the removal of the SharedRequestBase pointer from the list - // of pending requests and initiates cancelation. - sharedRequest->unsubscribe(req); + request = &pending.emplace(resource, DefaultFileRequest(resource)).first->second; + request->observers.insert(req); + + if (cache) { + startCacheRequest(resource); } else { - // There is no request for this URL anymore. Likely, the request already completed - // before we got around to process the cancelation request. + startRealRequest(resource); } - - // Send a message back to the requesting thread and notify it that this request has been - // canceled and is now safe to be deleted. - req->destruct(); } -void DefaultFileSource::Impl::processResult(const Resource& resource, std::shared_ptr<const Response> response, uv_loop_t* loop) { - SharedRequestBase *sharedRequest = find(resource); - if (sharedRequest) { - if (response) { - // This entry was stored in the cache. Now determine if we need to revalidate. +void DefaultFileSource::Impl::startCacheRequest(const Resource& resource) { + // Check the cache for existing data so that we can potentially + // revalidate the information without having to redownload everything. + cache->get(resource, [this, resource](std::unique_ptr<Response> response) { + DefaultFileRequest* request = find(resource); + + if (!request) { + // There is no request for this URL anymore. Likely, the request was canceled + // before we got around to process the cache result. + return; + } + + auto expired = [&response] { const int64_t now = std::chrono::duration_cast<std::chrono::seconds>( SystemClock::now().time_since_epoch()).count(); - if (response->expires > now) { - // The response is fresh. We're good to notify the caller. - sharedRequest->notify(response, FileCache::Hint::No); - sharedRequest->cancel(); - return; - } else { - // The cached response is stale. Now run the real request. - sharedRequest->start(loop, response); - } + return response->expires <= now; + }; + + if (!response || expired()) { + // No response or stale cache. Run the real request. + startRealRequest(resource, std::move(response)); } else { - // There is no response. Now run the real request. - sharedRequest->start(loop); + // The response is fresh. We're good to notify the caller. + notify(request, std::move(response), FileCache::Hint::No); } + }); +} + +void DefaultFileSource::Impl::startRealRequest(const Resource& resource, std::shared_ptr<const Response> response) { + DefaultFileRequest* request = find(resource); + + auto callback = [request, this] (std::shared_ptr<const Response> res, FileCache::Hint hint) { + notify(request, res, hint); + }; + + if (algo::starts_with(resource.url, "asset://")) { + request->request = assetContext->createRequest(resource, callback, loop, assetRoot); } else { - // There is no request for this URL anymore. Likely, the request was canceled - // before we got around to process the cache result. + request->request = httpContext->createRequest(resource, callback, loop, response); } } -// Aborts all requests that are part of the current environment. -void DefaultFileSource::Impl::abort(const Environment& env) { - // Construct a cancellation response. - auto res = util::make_unique<Response>(); - res->status = Response::Error; - res->message = "Environment is terminating"; - std::shared_ptr<const Response> response = std::move(res); - - // Iterate through all pending requests and remove them in case they're abandoned. - util::erase_if(pending, [&](const std::pair<Resource, SharedRequestBase *> &it) -> bool { - // Obtain all pending requests that are in the current environment. - const auto aborted = it.second->removeAllInEnvironment(env); - - // Notify all observers. - for (auto req : aborted) { - req->notify(response); +void DefaultFileSource::Impl::cancel(Request* req) { + DefaultFileRequest* request = find(req->resource); + + if (request) { + // If the number of dependent requests of the DefaultFileRequest drops to zero, + // cancel the request and remove it from the pending list. + request->observers.erase(req); + if (request->observers.empty()) { + if (request->request) { + request->request->cancel(); + } + pending.erase(request->resource); } + } else { + // There is no request for this URL anymore. Likely, the request already completed + // before we got around to process the cancelation request. + } - // Finally, remove all requests that are now abandoned. - if (it.second->abandoned()) { - it.second->cancel(); - return true; - } else { - return false; - } - }); + // Send a message back to the requesting thread and notify it that this request has been + // canceled and is now safe to be deleted. + req->destruct(); } -void DefaultFileSource::Impl::notify(SharedRequestBase *sharedRequest, - const std::set<Request *> &observers, - std::shared_ptr<const Response> response, FileCache::Hint hint) { +void DefaultFileSource::Impl::notify(DefaultFileRequest* request, std::shared_ptr<const Response> response, FileCache::Hint hint) { // First, remove the request, since it might be destructed at any point now. - assert(find(sharedRequest->resource) == sharedRequest); - pending.erase(sharedRequest->resource); + assert(find(request->resource) == request); + assert(response); - if (response) { - if (cache) { - // Store response in database - cache->put(sharedRequest->resource, response, hint); - } + // Notify all observers. + for (auto req : request->observers) { + req->notify(response); + } - // Notify all observers. - for (auto req : observers) { - req->notify(response); - } + if (cache) { + // Store response in database + cache->put(request->resource, response, hint); } + + pending.erase(request->resource); } } diff --git a/src/mbgl/storage/default_file_source_impl.hpp b/src/mbgl/storage/default_file_source_impl.hpp index 97210dc442..ed2d248d0a 100644 --- a/src/mbgl/storage/default_file_source_impl.hpp +++ b/src/mbgl/storage/default_file_source_impl.hpp @@ -2,33 +2,47 @@ #define MBGL_STORAGE_DEFAULT_DEFAULT_FILE_SOURCE_IMPL #include <mbgl/storage/default_file_source.hpp> +#include <mbgl/storage/asset_context.hpp> +#include <mbgl/storage/http_context.hpp> #include <set> #include <unordered_map> +typedef struct uv_loop_s uv_loop_t; + namespace mbgl { -class SharedRequestBase; +class RequestBase; -class DefaultFileSource::Impl { -public: - Impl(FileCache *cache, const std::string &root = ""); +struct DefaultFileRequest { + const Resource resource; + std::set<Request*> observers; + RequestBase* request = nullptr; - void notify(SharedRequestBase *sharedRequest, const std::set<Request *> &observers, - std::shared_ptr<const Response> response, FileCache::Hint hint); - SharedRequestBase *find(const Resource &resource); + DefaultFileRequest(const Resource& resource_) + : resource(resource_) {} +}; - void add(Request* request, uv_loop_t* loop); - void cancel(Request* request); - void abort(const Environment& env); +class DefaultFileSource::Impl { +public: + Impl(uv_loop_t*, FileCache*, const std::string& = ""); - const std::string assetRoot; + void add(Request*); + void cancel(Request*); private: - void processResult(const Resource& resource, std::shared_ptr<const Response> response, uv_loop_t* loop); + DefaultFileRequest* find(const Resource&); - std::unordered_map<Resource, SharedRequestBase *, Resource::Hash> pending; - FileCache *cache = nullptr; + void startCacheRequest(const Resource&); + void startRealRequest(const Resource&, std::shared_ptr<const Response> = nullptr); + void notify(DefaultFileRequest*, std::shared_ptr<const Response>, FileCache::Hint); + + std::unordered_map<Resource, DefaultFileRequest, Resource::Hash> pending; + uv_loop_t* loop = nullptr; + FileCache* cache = nullptr; + const std::string assetRoot; + std::unique_ptr<AssetContext> assetContext; + std::unique_ptr<HTTPContext> httpContext; }; } diff --git a/src/mbgl/storage/http_context.cpp b/src/mbgl/storage/http_context.cpp new file mode 100644 index 0000000000..c747490804 --- /dev/null +++ b/src/mbgl/storage/http_context.cpp @@ -0,0 +1,30 @@ +#include <mbgl/storage/http_context.hpp> + +namespace mbgl { + +HTTPContext::HTTPContext(uv_loop_t* loop_) + : reachability(loop_, [this] { retryRequests(); }) { + NetworkStatus::Subscribe(reachability.get()); + reachability.unref(); +} + +HTTPContext::~HTTPContext() { + assert(requests.empty()); + NetworkStatus::Unsubscribe(reachability.get()); +} + +void HTTPContext::addRequest(RequestBase* request) { + requests.insert(request); +} + +void HTTPContext::removeRequest(RequestBase* request) { + requests.erase(request); +} + +void HTTPContext::retryRequests() { + for (auto request : requests) { + request->retry(); + } +} + +} diff --git a/src/mbgl/storage/http_context.hpp b/src/mbgl/storage/http_context.hpp index 6b9518dab3..64a8afa6dd 100644 --- a/src/mbgl/storage/http_context.hpp +++ b/src/mbgl/storage/http_context.hpp @@ -1,76 +1,40 @@ #ifndef MBGL_STORAGE_DEFAULT_HTTP_CONTEXT #define MBGL_STORAGE_DEFAULT_HTTP_CONTEXT -#include "thread_context.hpp" +#include <mbgl/storage/request_base.hpp> #include <mbgl/storage/network_status.hpp> +#include <mbgl/util/uv_detail.hpp> #include <set> namespace mbgl { -class HTTPRequest; +class HTTPContext { +public: + static std::unique_ptr<HTTPContext> createContext(uv_loop_t*); -// This is a template class that provides a per-thread Context object. It can be used by HTTP -// implementations to store global state. It also implements the NetworkStatus mechanism and -// triggers immediate retries on all requests waiting for network status changes. + HTTPContext(uv_loop_t*); + virtual ~HTTPContext(); -template <typename Context> -class HTTPContext : public ThreadContext<Context> { -public: - HTTPContext(uv_loop_t *loop); - ~HTTPContext(); + virtual RequestBase* createRequest(const Resource&, + RequestBase::Callback, + uv_loop_t*, + std::shared_ptr<const Response>) = 0; - void addRequest(HTTPRequest *request); - void removeRequest(HTTPRequest *baton); + void addRequest(RequestBase*); + void removeRequest(RequestBase*); + +private: + void retryRequests(); -public: // Will be fired when the network status becomes reachable. - uv_async_t *reachability = nullptr; + uv::async reachability; // A list of all pending HTTPRequestImpls that we need to notify when the network status // changes. - std::set<HTTPRequest *> requests; + std::set<RequestBase*> requests; }; -template <typename Context> -HTTPContext<Context>::HTTPContext(uv_loop_t *loop_) - : ThreadContext<Context>(loop_) { - reachability = new uv_async_t; - reachability->data = this; -#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10 - uv_async_init(loop_, reachability, [](uv_async_t *async, int) { -#else - uv_async_init(loop_, reachability, [](uv_async_t *async) { -#endif - for (auto request : reinterpret_cast<Context *>(async->data)->requests) { - request->retryImmediately(); - } - }); - // Allow the loop to quit even though this handle is still active. - uv_unref(reinterpret_cast<uv_handle_t *>(reachability)); - NetworkStatus::Subscribe(reachability); -} - -template <typename Context> -HTTPContext<Context>::~HTTPContext() { - MBGL_VERIFY_THREAD(HTTPContext<Context>::tid); - - assert(requests.empty()); - - NetworkStatus::Unsubscribe(reachability); - uv::close(reachability); -} - -template <typename Context> -void HTTPContext<Context>::addRequest(HTTPRequest *request) { - requests.insert(request); -} - -template <typename Context> -void HTTPContext<Context>::removeRequest(HTTPRequest *request) { - requests.erase(request); -} - } #endif diff --git a/src/mbgl/storage/http_request.hpp b/src/mbgl/storage/http_request.hpp deleted file mode 100644 index 54e9a77ef0..0000000000 --- a/src/mbgl/storage/http_request.hpp +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef MBGL_STORAGE_DEFAULT_HTTP_REQUEST -#define MBGL_STORAGE_DEFAULT_HTTP_REQUEST - -#include "shared_request_base.hpp" - -namespace mbgl { - -class HTTPRequest : public SharedRequestBase { -public: - HTTPRequest(DefaultFileSource::Impl *source, const Resource &resource); - - void start(uv_loop_t *loop, std::shared_ptr<const Response> response = nullptr); - void cancel(); - - void retryImmediately(); - -private: - ~HTTPRequest(); - void *ptr = nullptr; - - friend class HTTPRequestImpl; -}; - -} - -#endif diff --git a/src/mbgl/storage/request.cpp b/src/mbgl/storage/request.cpp index ea80e59503..d413cabbe7 100644 --- a/src/mbgl/storage/request.cpp +++ b/src/mbgl/storage/request.cpp @@ -16,8 +16,8 @@ namespace mbgl { struct Request::Canceled { std::mutex mutex; bool confirmed = false; }; // Note: This requires that loop is running in the current thread (or not yet running). -Request::Request(const Resource &resource_, uv_loop_t *loop, const Environment &env_, Callback callback_) - : callback(callback_), resource(resource_), env(env_) { +Request::Request(const Resource &resource_, uv_loop_t *loop, Callback callback_) + : callback(callback_), resource(resource_) { // When there is no loop supplied (== nullptr), the callback will be fired in an arbitrary // thread (the thread notify() is called from) rather than kicking back to the calling thread. if (loop) { diff --git a/src/mbgl/storage/request_base.hpp b/src/mbgl/storage/request_base.hpp new file mode 100644 index 0000000000..c8dfad4778 --- /dev/null +++ b/src/mbgl/storage/request_base.hpp @@ -0,0 +1,35 @@ +#ifndef MBGL_STORAGE_REQUEST +#define MBGL_STORAGE_REQUEST + +#include <mbgl/util/noncopyable.hpp> +#include <mbgl/storage/file_cache.hpp> +#include <mbgl/storage/resource.hpp> + +#include <memory> +#include <functional> + +namespace mbgl { + +class Response; + +class RequestBase : private util::noncopyable { +public: + using Callback = std::function<void (std::shared_ptr<const Response> response, FileCache::Hint hint)>; + + RequestBase(const Resource& resource_, Callback notify_) + : resource(resource_) + , notify(notify_) { + } + + virtual ~RequestBase() = default; + virtual void cancel() = 0; + virtual void retry() {}; + +protected: + Resource resource; + Callback notify; +}; + +} + +#endif diff --git a/src/mbgl/storage/shared_request_base.hpp b/src/mbgl/storage/shared_request_base.hpp deleted file mode 100644 index d7ed00264a..0000000000 --- a/src/mbgl/storage/shared_request_base.hpp +++ /dev/null @@ -1,106 +0,0 @@ -#ifndef MBGL_STORAGE_DEFAULT_SHARED_REQUEST_BASE -#define MBGL_STORAGE_DEFAULT_SHARED_REQUEST_BASE - -#include <mbgl/storage/resource.hpp> -#include <mbgl/storage/file_cache.hpp> -#include <mbgl/storage/default_file_source_impl.hpp> -#include <mbgl/storage/request.hpp> -#include <mbgl/util/util.hpp> -#include <mbgl/util/noncopyable.hpp> - -#include <string> -#include <set> -#include <vector> -#include <cassert> - -typedef struct uv_loop_s uv_loop_t; - -namespace mbgl { - -class Request; -class Response; -class DefaultFileSource; - -class SharedRequestBase : private util::noncopyable { -protected: - MBGL_STORE_THREAD(tid) - -public: - SharedRequestBase(DefaultFileSource::Impl *source_, const Resource &resource_) - : resource(resource_), source(source_) {} - - virtual void start(uv_loop_t *loop, std::shared_ptr<const Response> response = nullptr) = 0; - virtual void cancel() = 0; - - void notify(std::shared_ptr<const Response> response, FileCache::Hint hint) { - MBGL_VERIFY_THREAD(tid); - - if (source) { - source->notify(this, observers, response, hint); - } - } - - void subscribe(Request *request) { - MBGL_VERIFY_THREAD(tid); - - observers.insert(request); - } - - void unsubscribe(Request *request) { - MBGL_VERIFY_THREAD(tid); - - observers.erase(request); - - if (abandoned()) { - // There are no observers anymore. We are initiating cancelation. - if (source) { - // First, remove this SharedRequestBase from the source. - source->notify(this, observers, nullptr, FileCache::Hint::No); - } - - // Then, initiate cancelation of this request - cancel(); - } - } - - bool abandoned() const { - return observers.empty(); - } - - std::vector<Request *> removeAllInEnvironment(const Environment &env) { - MBGL_VERIFY_THREAD(tid); - - std::vector<Request *> result; - - // Removes all Requests in the supplied environment and returns a list - // of them. - util::erase_if(observers, [&](Request *req) -> bool { - if (&req->env == &env) { - result.push_back(req); - return true; - } else { - return false; - } - }); - - return result; - } - -protected: - virtual ~SharedRequestBase() { - MBGL_VERIFY_THREAD(tid); - } - -public: - const Resource resource; - -protected: - DefaultFileSource::Impl *source = nullptr; - -private: - std::set<Request *> observers; -}; - -} - -#endif diff --git a/src/mbgl/storage/thread_context.hpp b/src/mbgl/storage/thread_context.hpp deleted file mode 100644 index 763c83a25b..0000000000 --- a/src/mbgl/storage/thread_context.hpp +++ /dev/null @@ -1,78 +0,0 @@ -#ifndef MBGL_STORAGE_DEFAULT_THREAD_CONTEXT -#define MBGL_STORAGE_DEFAULT_THREAD_CONTEXT - -#include <mbgl/util/noncopyable.hpp> -#include <mbgl/util/std.hpp> -#include <mbgl/util/util.hpp> -#include <mbgl/util/uv.hpp> - -#include <uv.h> -#include <pthread.h> - -#include <map> -#include <cassert> - -namespace mbgl { - -// This is a template class that provides a per-thread and per-loop Context object. It can be used -// by implementations to store global state. - -template <typename Context> -class ThreadContext : private util::noncopyable { -protected: - MBGL_STORE_THREAD(tid) - using Map = std::map<uv_loop_t *, std::unique_ptr<Context>>; - -public: - static Context *Get(uv_loop_t *loop); - -private: - static pthread_key_t key; - static pthread_once_t once; - -public: - ThreadContext(uv_loop_t *loop); - ~ThreadContext(); - -public: - uv_loop_t *loop; -}; - -template <typename Context> -Context *ThreadContext<Context>::Get(uv_loop_t *loop) { - pthread_once(&once, []() { - pthread_key_create(&key, [](void *ptr) { - assert(ptr); - delete reinterpret_cast<Map *>(ptr); - }); - }); - auto contexts = reinterpret_cast<Map *>(pthread_getspecific(key)); - if (!contexts) { - contexts = new Map(); - pthread_setspecific(key, contexts); - } - - // Now find a ThreadContext that matches the requested loop. - auto it = contexts->find(loop); - if (it == contexts->end()) { - auto result = contexts->emplace(loop, util::make_unique<Context>(loop)); - assert(result.second); // Make sure it was actually inserted. - return result.first->second.get(); - } else { - return it->second.get(); - } -} - -template <typename Context> -ThreadContext<Context>::ThreadContext(uv_loop_t *loop_) : loop(loop_) { -} - -template <typename Context> -ThreadContext<Context>::~ThreadContext() { - MBGL_VERIFY_THREAD(tid); -} - - -} - -#endif diff --git a/src/mbgl/style/property_fallback.cpp b/src/mbgl/style/property_fallback.cpp index dce226cdef..655a75df9b 100644 --- a/src/mbgl/style/property_fallback.cpp +++ b/src/mbgl/style/property_fallback.cpp @@ -22,7 +22,6 @@ const std::map<PropertyKey, PropertyValue> PropertyFallbackValue::properties = { { PropertyKey::LineBlur, defaultStyleProperties<LineProperties>().blur }, { PropertyKey::IconOpacity, defaultStyleProperties<SymbolProperties>().icon.opacity }, - { PropertyKey::IconRotate, defaultStyleProperties<SymbolProperties>().icon.rotate }, { PropertyKey::IconSize, defaultStyleProperties<SymbolProperties>().icon.size }, { PropertyKey::IconColor, defaultStyleProperties<SymbolProperties>().icon.color }, { PropertyKey::IconHaloColor, defaultStyleProperties<SymbolProperties>().icon.halo_color }, diff --git a/src/mbgl/style/style.cpp b/src/mbgl/style/style.cpp index 0a2a53075b..d592e61317 100644 --- a/src/mbgl/style/style.cpp +++ b/src/mbgl/style/style.cpp @@ -17,7 +17,8 @@ namespace mbgl { Style::Style() - : mtx(util::make_unique<uv::rwlock>()) { + : mtx(util::make_unique<uv::rwlock>()), + workers(4) { } // Note: This constructor is seemingly empty, but we need to declare it anyway @@ -86,4 +87,24 @@ void Style::loadJSON(const uint8_t *const data) { glyph_url = parser.getGlyphURL(); } +bool Style::isLoaded() const { + // TODO: move loading into Style +// if (!loaded) { +// return false; +// } + + for (const auto& source : sources) { + if (!source->isLoaded()) { + return false; + } + } + + // TODO: move sprite into Style +// if (sprite && !sprite.isLoaded()) { +// return false; +// } + + return true; +} + } diff --git a/src/mbgl/style/style.hpp b/src/mbgl/style/style.hpp index dde21173f3..42e94984dd 100644 --- a/src/mbgl/style/style.hpp +++ b/src/mbgl/style/style.hpp @@ -8,6 +8,7 @@ #include <mbgl/util/ptr.hpp> #include <mbgl/util/noncopyable.hpp> #include <mbgl/util/chrono.hpp> +#include <mbgl/util/worker.hpp> #include <cstdint> #include <string> @@ -24,6 +25,7 @@ public: ~Style(); void loadJSON(const uint8_t *const data); + bool isLoaded() const; void cascade(const std::vector<std::string>&); void recalculate(float z, TimePoint now); @@ -43,6 +45,9 @@ private: PropertyTransition defaultTransition; std::unique_ptr<uv::rwlock> mtx; ZoomHistory zoomHistory; + +public: + Worker workers; }; } diff --git a/src/mbgl/style/style_layer.cpp b/src/mbgl/style/style_layer.cpp index 542c4eb8a4..8713b73b12 100644 --- a/src/mbgl/style/style_layer.cpp +++ b/src/mbgl/style/style_layer.cpp @@ -13,6 +13,21 @@ bool StyleLayer::isBackground() const { return type == StyleLayerType::Background; } +bool StyleLayer::isVisible() const { + switch (type) { + case StyleLayerType::Fill: + return getProperties<FillProperties>().isVisible(); + case StyleLayerType::Line: + return getProperties<LineProperties>().isVisible(); + case StyleLayerType::Symbol: + return getProperties<SymbolProperties>().isVisible(); + case StyleLayerType::Raster: + return getProperties<RasterProperties>().isVisible(); + default: + return false; + } +} + void StyleLayer::setClasses(const std::vector<std::string> &class_names, const TimePoint now, const PropertyTransition &defaultTransition) { // Stores all keys that we have already added transitions for. @@ -194,7 +209,6 @@ void StyleLayer::applyStyleProperties<SymbolProperties>(const float z, const Tim properties.set<SymbolProperties>(); SymbolProperties &symbol = properties.get<SymbolProperties>(); applyTransitionedStyleProperty(PropertyKey::IconOpacity, symbol.icon.opacity, z, now, zoomHistory); - applyTransitionedStyleProperty(PropertyKey::IconRotate, symbol.icon.rotate, z, now, zoomHistory); applyTransitionedStyleProperty(PropertyKey::IconSize, symbol.icon.size, z, now, zoomHistory); applyTransitionedStyleProperty(PropertyKey::IconColor, symbol.icon.color, z, now, zoomHistory); applyTransitionedStyleProperty(PropertyKey::IconHaloColor, symbol.icon.halo_color, z, now, zoomHistory); diff --git a/src/mbgl/style/style_layer.hpp b/src/mbgl/style/style_layer.hpp index a00b7084fa..774ed39012 100644 --- a/src/mbgl/style/style_layer.hpp +++ b/src/mbgl/style/style_layer.hpp @@ -35,6 +35,9 @@ public: // Determines whether this layer is the background layer. bool isBackground() const; + // Checks whether the layer is currently visible at all. + bool isVisible() const; + // Updates the StyleProperties information in this layer by evaluating all // pending transitions and applied classes in order. void updateProperties(float z, TimePoint now, ZoomHistory &zoomHistory); diff --git a/src/mbgl/style/style_parser.cpp b/src/mbgl/style/style_parser.cpp index afed2eeee6..313fe3df89 100644 --- a/src/mbgl/style/style_parser.cpp +++ b/src/mbgl/style/style_parser.cpp @@ -10,9 +10,9 @@ #include <csscolorparser/csscolorparser.hpp> #pragma GCC diagnostic push -#ifndef __clang__ +#pragma GCC diagnostic ignored "-Wshadow" +#pragma GCC diagnostic ignored "-Wunknown-pragmas" #pragma GCC diagnostic ignored "-Wunused-local-typedefs" -#endif #include <boost/algorithm/string.hpp> #pragma GCC diagnostic pop @@ -811,7 +811,6 @@ void StyleParser::parsePaint(JSVal value, ClassProperties &klass) { parseOptionalProperty<Function<float>>("icon-opacity", Key::IconOpacity, klass, value); parseOptionalProperty<PropertyTransition>("icon-opacity-transition", Key::IconOpacity, klass, value); - parseOptionalProperty<Function<float>>("icon-rotate", Key::IconRotate, klass, value); parseOptionalProperty<Function<float>>("icon-size", Key::IconSize, klass, value); parseOptionalProperty<PropertyTransition>("icon-size-transition", Key::IconSize, klass, value); parseOptionalProperty<Function<Color>>("icon-color", Key::IconColor, klass, value); diff --git a/src/mbgl/style/style_properties.hpp b/src/mbgl/style/style_properties.hpp index f50722542d..8e8619fb99 100644 --- a/src/mbgl/style/style_properties.hpp +++ b/src/mbgl/style/style_properties.hpp @@ -51,7 +51,6 @@ struct SymbolProperties { struct { float opacity = 1.0f; - float rotate = 0.0f; float size = 1.0f; Color color = {{ 0, 0, 0, 1 }}; Color halo_color = {{ 0, 0, 0, 0 }}; diff --git a/src/mbgl/style/types.hpp b/src/mbgl/style/types.hpp index 3b24d63998..f6ffcd6865 100644 --- a/src/mbgl/style/types.hpp +++ b/src/mbgl/style/types.hpp @@ -91,13 +91,15 @@ MBGL_DEFINE_ENUM_CLASS(CapTypeClass, CapType, { enum class JoinType : uint8_t { Miter, Bevel, - Round + Round, + FlipBevel }; MBGL_DEFINE_ENUM_CLASS(JoinTypeClass, JoinType, { { JoinType::Miter, "miter" }, { JoinType::Bevel, "bevel" }, { JoinType::Round, "round" }, + { JoinType::FlipBevel, "flipbevel" }, }); // ------------------------------------------------------------------------------------------------- diff --git a/src/mbgl/text/collision.hpp b/src/mbgl/text/collision.hpp index bba07b0fb1..50b8e5b218 100644 --- a/src/mbgl/text/collision.hpp +++ b/src/mbgl/text/collision.hpp @@ -9,12 +9,13 @@ #pragma GCC diagnostic ignored "-Wunused-variable" #pragma GCC diagnostic ignored "-Wshadow" #ifdef __clang__ +#pragma GCC diagnostic ignored "-Wunknown-pragmas" +#endif +#pragma GCC diagnostic ignored "-Wpragmas" #pragma GCC diagnostic ignored "-Wdeprecated-register" #pragma GCC diagnostic ignored "-Wshorten-64-to-32" -#else #pragma GCC diagnostic ignored "-Wunused-local-typedefs" #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" -#endif #include <boost/geometry.hpp> #include <boost/geometry/geometries/point.hpp> #include <boost/geometry/geometries/box.hpp> diff --git a/src/mbgl/util/mat4.cpp b/src/mbgl/util/mat4.cpp index 50270d9217..cabd8e2842 100644 --- a/src/mbgl/util/mat4.cpp +++ b/src/mbgl/util/mat4.cpp @@ -87,7 +87,7 @@ void matrix::copy(mat4& out, const mat4& a) { } void matrix::translate(mat4& out, const mat4& a, float x, float y, float z) { - if (a == out) { + if (&a == &out) { out[12] = a[0] * x + a[4] * y + a[8] * z + a[12]; out[13] = a[1] * x + a[5] * y + a[9] * z + a[13]; out[14] = a[2] * x + a[6] * y + a[10] * z + a[14]; @@ -124,7 +124,7 @@ void matrix::rotate_z(mat4& out, const mat4& a, float rad) { a12 = a[6], a13 = a[7]; - if (a != out) { // If the source and destination differ, copy the unchanged last row + if (&a != &out) { // If the source and destination differ, copy the unchanged last row out[8] = a[8]; out[9] = a[9]; out[10] = a[10]; diff --git a/src/mbgl/util/raster.cpp b/src/mbgl/util/raster.cpp index d0e305c33a..f2171a6165 100644 --- a/src/mbgl/util/raster.cpp +++ b/src/mbgl/util/raster.cpp @@ -46,16 +46,7 @@ void Raster::bind(bool linear) { } if (img && !textured) { - texture = texturePool.getTextureID(); - MBGL_CHECK_ERROR(glBindTexture(GL_TEXTURE_2D, texture)); -#ifndef GL_ES_VERSION_2_0 - MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0)); -#endif - MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); - MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); - MBGL_CHECK_ERROR(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, img->getData())); - img.reset(); - textured = true; + upload(); } else if (textured) { MBGL_CHECK_ERROR(glBindTexture(GL_TEXTURE_2D, texture)); } @@ -68,24 +59,17 @@ void Raster::bind(bool linear) { } } -// overload ::bind for prerendered raster textures -void Raster::bind(const GLuint custom_texture) { +void Raster::upload() { if (img && !textured) { - MBGL_CHECK_ERROR(glBindTexture(GL_TEXTURE_2D, custom_texture)); + texture = texturePool.getTextureID(); + MBGL_CHECK_ERROR(glBindTexture(GL_TEXTURE_2D, texture)); +#ifndef GL_ES_VERSION_2_0 + MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0)); +#endif MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); MBGL_CHECK_ERROR(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, img->getData())); img.reset(); textured = true; - } else if (textured) { - MBGL_CHECK_ERROR(glBindTexture(GL_TEXTURE_2D, custom_texture)); } - - GLuint new_filter = GL_LINEAR; - if (new_filter != this->filter) { - MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, new_filter)); - MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, new_filter)); - filter = new_filter; - } - } diff --git a/src/mbgl/util/raster.hpp b/src/mbgl/util/raster.hpp index f19ff7178a..1789ae87f7 100644 --- a/src/mbgl/util/raster.hpp +++ b/src/mbgl/util/raster.hpp @@ -25,8 +25,8 @@ public: // bind current texture void bind(bool linear = false); - // bind prerendered texture - void bind(const GLuint texture); + // uploads the texture if it hasn't been uploaded yet. + void upload(); // loaded status bool isLoaded() const; diff --git a/src/mbgl/util/run_loop.cpp b/src/mbgl/util/run_loop.cpp index fd9ad43060..7f277c9885 100644 --- a/src/mbgl/util/run_loop.cpp +++ b/src/mbgl/util/run_loop.cpp @@ -5,8 +5,13 @@ namespace util { uv::tls<RunLoop> RunLoop::current; -RunLoop::RunLoop() - : async(*loop, std::bind(&RunLoop::process, this)) { +RunLoop::RunLoop(uv_loop_t* loop) + : async(loop, std::bind(&RunLoop::process, this)) { + current.set(this); +} + +RunLoop::~RunLoop() { + current.set(nullptr); } void RunLoop::withMutex(std::function<void()>&& fn) { @@ -24,13 +29,6 @@ void RunLoop::process() { } } -void RunLoop::run() { - assert(!current.get()); - current.set(this); - loop.run(); - current.set(nullptr); -} - void RunLoop::stop() { invoke([&] { async.unref(); }); } diff --git a/src/mbgl/util/run_loop.hpp b/src/mbgl/util/run_loop.hpp index d785854e79..767a47b754 100644 --- a/src/mbgl/util/run_loop.hpp +++ b/src/mbgl/util/run_loop.hpp @@ -14,9 +14,9 @@ namespace util { class RunLoop : private util::noncopyable { public: - RunLoop(); + RunLoop(uv_loop_t*); + ~RunLoop(); - void run(); void stop(); // Invoke fn() in the runloop thread. @@ -29,7 +29,7 @@ public: // Invoke fn() in the runloop thread, then invoke callback(result) in the current thread. template <class Fn, class R> - void invokeWithResult(Fn&& fn, std::function<void (R)> callback) { + void invokeWithResult(Fn&& fn, std::function<void (R)>&& callback) { RunLoop* outer = current.get(); assert(outer); @@ -37,21 +37,35 @@ public: /* With C++14, we could write: - outer->invoke([callback, result = std::move(fn())] () mutable { - callback(std::move(result)); + outer->invoke([cb = std::move(callback), result = std::move(fn())] () mutable { + cb(std::move(result)); }); Instead we're using a workaround with std::bind to obtain move-capturing semantics with C++11: http://stackoverflow.com/a/12744730/52207 */ - outer->invoke(std::bind([callback] (R& result) { - callback(std::move(result)); - }, std::move(fn()))); + outer->invoke(std::bind([] (std::function<void (R)>& cb, R& result) { + cb(std::move(result)); + }, std::move(callback), std::move(fn()))); }); } - uv_loop_t* get() { return *loop; } + // Invoke fn() in the runloop thread, then invoke callback() in the current thread. + template <class Fn> + void invokeWithResult(Fn&& fn, std::function<void ()>&& callback) { + RunLoop* outer = current.get(); + assert(outer); + + invoke([fn, callback, outer] { + fn(); + outer->invoke(std::move(callback)); + }); + } + + uv_loop_t* get() { return async.get()->loop; } + + static uv::tls<RunLoop> current; private: // A movable type-erasing invokable entity wrapper. This allows to store arbitrary invokable @@ -71,15 +85,11 @@ private: using Queue = std::queue<std::unique_ptr<Message>>; - static uv::tls<RunLoop> current; - void withMutex(std::function<void()>&&); void process(); Queue queue; std::mutex mutex; - - uv::loop loop; uv::async async; }; diff --git a/src/mbgl/util/thread.hpp b/src/mbgl/util/thread.hpp index 4831b9efc2..e97872a502 100644 --- a/src/mbgl/util/thread.hpp +++ b/src/mbgl/util/thread.hpp @@ -3,9 +3,11 @@ #include <future> #include <thread> +#include <atomic> #include <functional> #include <mbgl/util/run_loop.hpp> +#include <mbgl/platform/platform.hpp> namespace { @@ -33,11 +35,16 @@ namespace util { // Thread<> constructor blocks until the thread and the Object are fully created, so after the // object creation, it's safe to obtain the Object stored in this thread. +enum class ThreadPriority : bool { + Regular, + Low, +}; + template <class Object> class Thread { public: template <class... Args> - Thread(const std::string& name, Args&&... args); + Thread(const std::string& name, ThreadPriority priority, Args&&... args); ~Thread(); // Invoke object->fn(args...) in the runloop thread. @@ -48,11 +55,33 @@ public: // Invoke object->fn(args...) in the runloop thread, then invoke callback(result) in the current thread. template <typename Fn, class R, class... Args> - void invokeWithResult(Fn fn, std::function<void (R)> callback, Args&&... args) { - loop->invokeWithResult(std::bind(fn, object, args...), callback); + void invokeWithResult(Fn fn, std::function<void (R)>&& callback, Args&&... args) { + loop->invokeWithResult(std::bind(fn, object, args...), std::move(callback)); + } + + // Invoke object->fn(args...) in the runloop thread, then invoke callback() in the current thread. + template <typename Fn, class... Args> + void invokeWithResult(Fn fn, std::function<void ()>&& callback, Args&&... args) { + loop->invokeWithResult(std::bind(fn, object, args...), std::move(callback)); + } + + // Invoke object->fn(args...) in the runloop thread, and wait for the result. + template <class R, typename Fn, class... Args> + R invokeSync(Fn fn, Args&&... args) { + std::packaged_task<R ()> task(std::bind(fn, object, args...)); + std::future<R> future = task.get_future(); + loop->invoke(std::move(task)); + return future.get(); } - uv_loop_t* get() { return loop->get(); } + // Invoke object->fn(args...) in the runloop thread, and wait for it to complete. + template <typename Fn, class... Args> + void invokeSync(Fn fn, Args&&... args) { + std::packaged_task<void ()> task(std::bind(fn, object, args...)); + std::future<void> future = task.get_future(); + loop->invoke(std::move(task)); + return future.get(); + } private: Thread(const Thread&) = delete; @@ -74,7 +103,7 @@ private: template <class Object> template <class... Args> -Thread<Object>::Thread(const std::string& name, Args&&... args) { +Thread<Object>::Thread(const std::string& name, ThreadPriority priority, Args&&... args) { // Note: We're using std::tuple<> to store the arguments because GCC 4.9 has a bug // when expanding parameters packs captured in lambdas. std::tuple<Args...> params = std::forward_as_tuple(::std::forward<Args>(args)...); @@ -86,6 +115,10 @@ Thread<Object>::Thread(const std::string& name, Args&&... args) { (void(name)); #endif + if (priority == ThreadPriority::Low) { + platform::makeThreadLowPriority(); + } + constexpr auto seq = typename integer_sequence<sizeof...(Args)>::type(); run(std::move(params), seq); }); @@ -96,14 +129,24 @@ Thread<Object>::Thread(const std::string& name, Args&&... args) { template <class Object> template <typename P, std::size_t... I> void Thread<Object>::run(P&& params, index_sequence<I...>) { - Object object_(std::get<I>(std::forward<P>(params))...); - object = &object_; + uv::loop l; + + { + RunLoop loop_(l.get()); + loop = &loop_; - RunLoop loop_; - loop = &loop_; + Object object_(l.get(), std::get<I>(std::forward<P>(params))...); + object = &object_; + + running.set_value(); + l.run(); + + loop = nullptr; + object = nullptr; + } - running.set_value(); - loop_.run(); + // Run the loop again to ensure that async close callbacks have been called. + l.run(); joinable.get_future().get(); } diff --git a/src/mbgl/util/uv.cpp b/src/mbgl/util/uv.cpp index 5dae34ebd0..d76e42f67d 100644 --- a/src/mbgl/util/uv.cpp +++ b/src/mbgl/util/uv.cpp @@ -1,8 +1,29 @@ #include <mbgl/util/uv.hpp> #include <mbgl/util/uv_detail.hpp> +#include <mbgl/util/string.hpp> #include <uv.h> +// Check libuv library version. +const static bool uvVersionCheck = []() { + const unsigned int version = uv_version(); + const unsigned int major = (version >> 16) & 0xFF; + const unsigned int minor = (version >> 8) & 0xFF; + const unsigned int patch = version & 0xFF; + +#ifndef UV_VERSION_PATCH + // 0.10 doesn't have UV_VERSION_PATCH defined, so we "fake" it by using the library patch level. + const unsigned int UV_VERSION_PATCH = version & 0xFF; +#endif + + if (major != UV_VERSION_MAJOR || minor != UV_VERSION_MINOR || patch != UV_VERSION_PATCH) { + throw std::runtime_error(mbgl::util::sprintf<96>( + "libuv version mismatch: headers report %d.%d.%d, but library reports %d.%d.%d", UV_VERSION_MAJOR, + UV_VERSION_MINOR, UV_VERSION_PATCH, major, minor, patch)); + } + return true; +}(); + #if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10 int uv_key_create(uv_key_t* key) { diff --git a/src/mbgl/util/uv_detail.hpp b/src/mbgl/util/uv_detail.hpp index 96d5442462..059d7c0ddb 100644 --- a/src/mbgl/util/uv_detail.hpp +++ b/src/mbgl/util/uv_detail.hpp @@ -28,13 +28,6 @@ UV_EXTERN void uv_key_set(uv_key_t* key, void* value); namespace uv { -template <class T> -void close(std::unique_ptr<T> ptr) { - uv_close(reinterpret_cast<uv_handle_t*>(ptr.release()), [](uv_handle_t* handle) { - delete reinterpret_cast<T*>(handle); - }); -} - class loop : public mbgl::util::noncopyable { public: inline loop() { @@ -74,46 +67,93 @@ private: uv_loop_t *l = nullptr; }; -class async : public mbgl::util::noncopyable { +template <class T> +class handle : public mbgl::util::noncopyable { +public: + inline handle() : t(reinterpret_cast<uv_handle_t*>(new T)) { + t->data = this; + } + + inline ~handle() { + uv_close(t.release(), [](uv_handle_t* handle) { + delete reinterpret_cast<T*>(handle); + }); + } + + inline void ref() { + uv_ref(t.get()); + } + + inline void unref() { + uv_unref(t.get()); + } + + inline T* get() { + return reinterpret_cast<T*>(t.get()); + } + +private: + std::unique_ptr<uv_handle_t> t; +}; + +class async : public handle<uv_async_t> { public: inline async(uv_loop_t* loop, std::function<void ()> fn_) - : a(new uv_async_t) - , fn(fn_) - { - a->data = this; - if (uv_async_init(loop, a.get(), async_cb) != 0) { + : fn(fn_) { + if (uv_async_init(loop, get(), async_cb) != 0) { throw std::runtime_error("failed to initialize async"); } } - inline ~async() { - close(std::move(a)); - } - inline void send() { - if (uv_async_send(a.get()) != 0) { + if (uv_async_send(get()) != 0) { throw std::runtime_error("failed to async send"); } } - inline void ref() { - uv_ref(reinterpret_cast<uv_handle_t*>(a.get())); +private: +#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10 + static void async_cb(uv_async_t* a, int) { +#else + static void async_cb(uv_async_t* a) { +#endif + reinterpret_cast<async*>(a->data)->fn(); } - inline void unref() { - uv_unref(reinterpret_cast<uv_handle_t*>(a.get())); + std::function<void ()> fn; +}; + +class timer : public handle<uv_timer_t> { +public: + inline timer(uv_loop_t* loop) { + if (uv_timer_init(loop, get()) != 0) { + throw std::runtime_error("failed to initialize timer"); + } + } + + inline void start(uint64_t timeout, uint64_t repeat, std::function<void ()> fn_) { + fn = fn_; + if (uv_timer_start(get(), timer_cb, timeout, repeat) != 0) { + throw std::runtime_error("failed to start timer"); + } + } + + inline void stop() { + fn = nullptr; + if (uv_timer_stop(get()) != 0) { + throw std::runtime_error("failed to stop timer"); + } } private: #if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10 - static void async_cb(uv_async_t* a, int) { + static void timer_cb(uv_timer_t* t, int) { #else - static void async_cb(uv_async_t* a) { + static void timer_cb(uv_timer_t* t) { #endif - reinterpret_cast<async*>(a->data)->fn(); + reinterpret_cast<timer*>(t->data)->fn(); } - std::unique_ptr<uv_async_t> a; std::function<void ()> fn; }; diff --git a/src/mbgl/util/worker.cpp b/src/mbgl/util/worker.cpp index 3559cdd71f..9792f1a099 100644 --- a/src/mbgl/util/worker.cpp +++ b/src/mbgl/util/worker.cpp @@ -1,73 +1,30 @@ #include <mbgl/util/worker.hpp> +#include <mbgl/platform/platform.hpp> #include <cassert> namespace mbgl { -Worker::Worker(uv_loop_t* loop, std::size_t count) - : queue(new Queue(loop, [this](Fn after) { afterWork(after); })) -{ - queue->unref(); +class Worker::Impl { +public: + Impl(uv_loop_t*) {} - for (std::size_t i = 0; i < count; i++) { - threads.emplace_back(&Worker::workLoop, this); - } -} - -Worker::~Worker() { - MBGL_VERIFY_THREAD(tid); - - if (active++ == 0) { - queue->ref(); - } - - channel.send(Work()); - - for (auto& thread : threads) { - thread.join(); - } - - queue->stop(); -} - -void Worker::send(Fn work, Fn after) { - MBGL_VERIFY_THREAD(tid); - assert(work); - - if (active++ == 0) { - queue->ref(); + void doWork(Fn work) { + work(); } +}; - channel.send({work, after}); -} - -void Worker::workLoop() { -#ifdef __APPLE__ - pthread_setname_np("Worker"); -#endif - - while (true) { - Work item = channel.receive(); - - if (!item.work) - break; - - item.work(); - queue->send(std::move(item.after)); +Worker::Worker(std::size_t count) { + for (std::size_t i = 0; i < count; i++) { + threads.emplace_back(util::make_unique<util::Thread<Impl>>("Worker", util::ThreadPriority::Low)); } - - // Make sure to close all other workers too. - channel.send(Work()); } -void Worker::afterWork(Fn after) { - if (after) { - after(); - } +Worker::~Worker() = default; - if (--active == 0) { - queue->unref(); - } +void Worker::send(Fn&& work, Fn&& after) { + threads[current]->invokeWithResult(&Worker::Impl::doWork, std::move(after), std::move(work)); + current = (current + 1) % threads.size(); } } diff --git a/src/mbgl/util/worker.hpp b/src/mbgl/util/worker.hpp index 86c2e6acf4..d8fbf6df7d 100644 --- a/src/mbgl/util/worker.hpp +++ b/src/mbgl/util/worker.hpp @@ -2,11 +2,8 @@ #define MBGL_UTIL_WORKER #include <mbgl/util/noncopyable.hpp> -#include <mbgl/util/async_queue.hpp> -#include <mbgl/util/channel.hpp> -#include <mbgl/util/util.hpp> +#include <mbgl/util/thread.hpp> -#include <thread> #include <functional> namespace mbgl { @@ -15,28 +12,15 @@ class Worker : public mbgl::util::noncopyable { public: using Fn = std::function<void ()>; - Worker(uv_loop_t* loop, std::size_t count); + Worker(std::size_t count); ~Worker(); - void send(Fn work, Fn after); + void send(Fn&& work, Fn&& after); private: - void workLoop(); - void afterWork(Fn after); - - struct Work { - Fn work; - Fn after; - }; - - using Queue = util::AsyncQueue<std::function<void ()>>; - - std::size_t active = 0; - Queue* queue = nullptr; - Channel<Work> channel; - std::vector<std::thread> threads; - - MBGL_STORE_THREAD(tid) + class Impl; + std::vector<std::unique_ptr<util::Thread<Impl>>> threads; + std::size_t current = 0; }; } |