[core] reparse tiles when new data arrives
We're now reparsing tiles when they expire. We're also swapping out buckets atomically to avoid flickering data; i.e. we're displaying the old data as long as we don't have a new parsed bucket for that layer yet. The parsed buckets now live in the *TileData objects rather than in the TileWorker; only partially parsed == pending buckets will remain in the TileWorker. Once they're parsed, they're moved to the *TileData object.
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
#define MBGL_STORAGE_RESPONSE
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <mbgl/util/worker.hpp>
|
||||
#include <mbgl/util/work_request.hpp>
|
||||
#include <mbgl/style/style.hpp>
|
||||
#include <mbgl/style/style_bucket.hpp>
|
||||
|
||||
#include <sstream>
|
||||
|
||||
@@ -32,21 +33,27 @@ LiveTileData::LiveTileData(const TileID& id_,
|
||||
return;
|
||||
}
|
||||
|
||||
reparse(callback);
|
||||
parsePending(callback);
|
||||
}
|
||||
|
||||
bool LiveTileData::reparse(std::function<void()> callback) {
|
||||
if (parsing || (state != State::loaded && state != State::partial)) {
|
||||
bool LiveTileData::parsePending(std::function<void()> callback) {
|
||||
if (workRequest || (state != State::loaded && state != State::partial)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
parsing = true;
|
||||
|
||||
workRequest = worker.parseLiveTile(tileWorker, *tile, [this, callback] (TileParseResult result) {
|
||||
parsing = false;
|
||||
workRequest.reset();
|
||||
|
||||
if (result.is<TileParseResultBuckets>()) {
|
||||
auto& resultBuckets = result.get<TileParseResultBuckets>();
|
||||
state = resultBuckets.state;
|
||||
|
||||
// Move over all buckets we received in this parse request, potentially overwriting
|
||||
// existing buckets in case we got a refresh parse.
|
||||
for (auto& bucket : resultBuckets.buckets) {
|
||||
buckets[bucket.first] = std::move(bucket.second);
|
||||
}
|
||||
|
||||
if (result.is<State>()) {
|
||||
state = result.get<State>();
|
||||
} else {
|
||||
error = result.get<std::string>();
|
||||
state = State::obsolete;
|
||||
@@ -67,7 +74,13 @@ Bucket* LiveTileData::getBucket(const StyleLayer& layer) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return tileWorker.getBucket(layer);
|
||||
const auto it = buckets.find(layer.bucket->name);
|
||||
if (it == buckets.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
assert(it->second);
|
||||
return it->second.get();
|
||||
}
|
||||
|
||||
void LiveTileData::cancel() {
|
||||
|
||||
@@ -20,7 +20,7 @@ public:
|
||||
std::function<void ()> callback);
|
||||
~LiveTileData();
|
||||
|
||||
bool reparse(std::function<void ()> callback) override;
|
||||
bool parsePending(std::function<void ()> callback) override;
|
||||
|
||||
void cancel() override;
|
||||
Bucket* getBucket(const StyleLayer&) override;
|
||||
@@ -29,8 +29,11 @@ private:
|
||||
Worker& worker;
|
||||
TileWorker tileWorker;
|
||||
std::unique_ptr<WorkRequest> workRequest;
|
||||
bool parsing = false;
|
||||
std::unique_ptr<AnnotationTile> tile;
|
||||
|
||||
// Contains all the Bucket objects for the tile. Buckets are render
|
||||
// objects and they get added by tile parsing operations.
|
||||
std::unordered_map<std::string, std::unique_ptr<Bucket>> buckets;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -11,13 +11,13 @@
|
||||
using namespace mbgl;
|
||||
|
||||
RasterTileData::RasterTileData(const TileID& id_,
|
||||
TexturePool &texturePool,
|
||||
TexturePool &texturePool_,
|
||||
const SourceInfo &source_,
|
||||
Worker& worker_)
|
||||
: TileData(id_),
|
||||
texturePool(texturePool_),
|
||||
source(source_),
|
||||
worker(worker_),
|
||||
bucket(texturePool, layout) {
|
||||
worker(worker_) {
|
||||
}
|
||||
|
||||
RasterTileData::~RasterTileData() {
|
||||
@@ -52,15 +52,24 @@ void RasterTileData::request(float pixelRatio,
|
||||
return;
|
||||
}
|
||||
|
||||
state = State::loaded;
|
||||
if (state == State::loading) {
|
||||
// Only overwrite the state when we didn't have a previous tile.
|
||||
state = State::loaded;
|
||||
}
|
||||
|
||||
workRequest = worker.parseRasterTile(bucket, res.data, [this, callback] (TileParseResult result) {
|
||||
workRequest = worker.parseRasterTile(std::make_unique<RasterBucket>(texturePool, layout), res.data, [this, callback] (TileParseResult result) {
|
||||
workRequest.reset();
|
||||
if (state != State::loaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.is<State>()) {
|
||||
state = result.get<State>();
|
||||
if (result.is<TileParseResultBuckets>()) {
|
||||
auto& buckets = result.get<TileParseResultBuckets>();
|
||||
state = buckets.state;
|
||||
// TODO: Make this less awkward; we're only getting one bucket back.
|
||||
if (!buckets.buckets.empty()) {
|
||||
bucket = std::move(buckets.buckets.front().second);
|
||||
}
|
||||
} else {
|
||||
std::stringstream message;
|
||||
message << "Failed to parse [" << std::string(id) << "]: " << result.get<std::string>();
|
||||
@@ -74,7 +83,7 @@ void RasterTileData::request(float pixelRatio,
|
||||
}
|
||||
|
||||
Bucket* RasterTileData::getBucket(StyleLayer const&) {
|
||||
return &bucket;
|
||||
return bucket.get();
|
||||
}
|
||||
|
||||
void RasterTileData::cancel() {
|
||||
|
||||
@@ -27,12 +27,13 @@ public:
|
||||
Bucket* getBucket(StyleLayer const &layer_desc) override;
|
||||
|
||||
private:
|
||||
TexturePool& texturePool;
|
||||
const SourceInfo& source;
|
||||
Worker& worker;
|
||||
RequestHolder req;
|
||||
|
||||
RasterLayoutProperties layout;
|
||||
RasterBucket bucket;
|
||||
std::unique_ptr<Bucket> bucket;
|
||||
|
||||
std::unique_ptr<WorkRequest> workRequest;
|
||||
};
|
||||
|
||||
@@ -242,7 +242,7 @@ bool Source::handlePartialTile(const TileID& id, Worker&) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return data->reparse([this]() {
|
||||
return data->parsePending([this]() {
|
||||
emitTileLoaded(false);
|
||||
});
|
||||
}
|
||||
@@ -364,7 +364,8 @@ bool Source::findLoadedChildren(const TileID& id, int32_t maxCoveringZoom, std::
|
||||
const TileData::State state = hasTile(child_id);
|
||||
if (TileData::isReadyState(state)) {
|
||||
retain.emplace_front(child_id);
|
||||
} else {
|
||||
}
|
||||
if (state != TileData::State::parsed) {
|
||||
complete = false;
|
||||
if (z < maxCoveringZoom) {
|
||||
// Go further down the hierarchy to find more unloaded children.
|
||||
@@ -384,16 +385,17 @@ bool Source::findLoadedChildren(const TileID& id, int32_t maxCoveringZoom, std::
|
||||
*
|
||||
* @return boolean Whether a parent was found.
|
||||
*/
|
||||
bool Source::findLoadedParent(const TileID& id, int32_t minCoveringZoom, std::forward_list<TileID>& retain) {
|
||||
void Source::findLoadedParent(const TileID& id, int32_t minCoveringZoom, std::forward_list<TileID>& retain) {
|
||||
for (int32_t z = id.z - 1; z >= minCoveringZoom; --z) {
|
||||
const TileID parent_id = id.parent(z, info.max_zoom);
|
||||
const TileData::State state = hasTile(parent_id);
|
||||
if (TileData::isReadyState(state)) {
|
||||
retain.emplace_front(parent_id);
|
||||
return true;
|
||||
if (state == TileData::State::parsed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Source::update(MapData& data,
|
||||
|
||||
@@ -105,7 +105,7 @@ private:
|
||||
|
||||
bool handlePartialTile(const TileID &id, Worker &worker);
|
||||
bool findLoadedChildren(const TileID& id, int32_t maxCoveringZoom, std::forward_list<TileID>& retain);
|
||||
bool findLoadedParent(const TileID& id, int32_t minCoveringZoom, std::forward_list<TileID>& retain);
|
||||
void findLoadedParent(const TileID& id, int32_t minCoveringZoom, std::forward_list<TileID>& retain);
|
||||
int32_t coveringZoomLevel(const TransformState&) const;
|
||||
std::forward_list<TileID> coveringTiles(const TransformState&) const;
|
||||
|
||||
|
||||
@@ -1,11 +1,26 @@
|
||||
#include <mbgl/map/tile_data.hpp>
|
||||
#include <mbgl/renderer/debug_bucket.hpp>
|
||||
|
||||
using namespace mbgl;
|
||||
namespace mbgl {
|
||||
|
||||
TileData::TileData(const TileID& id_)
|
||||
: id(id_),
|
||||
debugBucket(debugFontBuffer),
|
||||
state(State::initial) {
|
||||
// Initialize tile debug coordinates
|
||||
debugFontBuffer.addText(std::string(id).c_str(), 50, 200, 5);
|
||||
}
|
||||
|
||||
TileData::~TileData() = default;
|
||||
|
||||
const char* TileData::StateToString(const State state) {
|
||||
switch (state) {
|
||||
case TileData::State::initial: return "initial";
|
||||
case TileData::State::invalid : return "invalid";
|
||||
case TileData::State::loading : return "loading";
|
||||
case TileData::State::loaded : return "loaded";
|
||||
case TileData::State::obsolete : return "obsolete";
|
||||
case TileData::State::parsed : return "parsed";
|
||||
case TileData::State::partial : return "partial";
|
||||
default: return "<unknown>";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mbgl
|
||||
|
||||
@@ -3,17 +3,18 @@
|
||||
|
||||
#include <mbgl/util/noncopyable.hpp>
|
||||
#include <mbgl/map/tile_id.hpp>
|
||||
#include <mbgl/renderer/debug_bucket.hpp>
|
||||
#include <mbgl/geometry/debug_font_buffer.hpp>
|
||||
#include <mbgl/renderer/bucket.hpp>
|
||||
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
class StyleLayer;
|
||||
class Worker;
|
||||
class DebugBucket;
|
||||
|
||||
class TileData : private util::noncopyable {
|
||||
public:
|
||||
@@ -57,6 +58,8 @@ public:
|
||||
obsolete
|
||||
};
|
||||
|
||||
static const char* StateToString(State);
|
||||
|
||||
// Tile data considered "Ready" can be used for rendering. Data in
|
||||
// partial state is still waiting for network resources but can also
|
||||
// be rendered, although layers will be missing.
|
||||
@@ -65,14 +68,14 @@ public:
|
||||
}
|
||||
|
||||
TileData(const TileID&);
|
||||
virtual ~TileData() = default;
|
||||
virtual ~TileData();
|
||||
|
||||
// Mark this tile as no longer needed and cancel any pending work.
|
||||
virtual void cancel() = 0;
|
||||
|
||||
virtual Bucket* getBucket(const StyleLayer&) = 0;
|
||||
|
||||
virtual bool reparse(std::function<void ()>) { return true; }
|
||||
virtual bool parsePending(std::function<void ()>) { return true; }
|
||||
virtual void redoPlacement(float, float, bool) {}
|
||||
|
||||
bool isReady() const {
|
||||
@@ -90,14 +93,13 @@ public:
|
||||
const TileID id;
|
||||
|
||||
// Contains the tile ID string for painting debug information.
|
||||
DebugBucket debugBucket;
|
||||
DebugFontBuffer debugFontBuffer;
|
||||
std::unique_ptr<DebugBucket> debugBucket;
|
||||
|
||||
protected:
|
||||
std::atomic<State> state;
|
||||
std::string error;
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace mbgl
|
||||
|
||||
#endif
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <mbgl/style/style.hpp>
|
||||
#include <mbgl/style/style_layer.hpp>
|
||||
#include <mbgl/style/property_evaluator.hpp>
|
||||
#include <mbgl/geometry/sprite_atlas.hpp>
|
||||
#include <mbgl/geometry/glyph_atlas.hpp>
|
||||
#include <mbgl/renderer/fill_bucket.hpp>
|
||||
#include <mbgl/renderer/line_bucket.hpp>
|
||||
@@ -10,6 +11,7 @@
|
||||
#include <mbgl/renderer/symbol_bucket.hpp>
|
||||
#include <mbgl/platform/log.hpp>
|
||||
#include <mbgl/util/constants.hpp>
|
||||
#include <mbgl/util/exception.hpp>
|
||||
|
||||
using namespace mbgl;
|
||||
|
||||
@@ -33,34 +35,66 @@ TileWorker::~TileWorker() {
|
||||
style.glyphAtlas->removeGlyphs(reinterpret_cast<uintptr_t>(this));
|
||||
}
|
||||
|
||||
Bucket* TileWorker::getBucket(const StyleLayer& layer) const {
|
||||
std::lock_guard<std::mutex> lock(bucketsMutex);
|
||||
TileParseResult TileWorker::parseAllLayers(const GeometryTile& geometryTile) {
|
||||
// We're doing a fresh parse of the tile, because the underlying data has changed.
|
||||
pending.clear();
|
||||
|
||||
const auto it = buckets.find(layer.bucket->name);
|
||||
if (it == buckets.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
assert(it->second);
|
||||
return it->second.get();
|
||||
}
|
||||
|
||||
TileParseResult TileWorker::parse(const GeometryTile& geometryTile) {
|
||||
partialParse = false;
|
||||
// We're storing a list of buckets we've parsed to avoid parsing a bucket twice that is
|
||||
// referenced from more than one layer
|
||||
std::set<StyleBucket*> parsed;
|
||||
|
||||
for (auto i = layers.rbegin(); i != layers.rend(); i++) {
|
||||
parseLayer(**i, geometryTile);
|
||||
const StyleLayer& layer = **i;
|
||||
if (layer.bucket && parsed.find(layer.bucket.get()) == parsed.end()) {
|
||||
parsed.emplace(layer.bucket.get());
|
||||
parseLayer(layer, geometryTile);
|
||||
}
|
||||
}
|
||||
|
||||
return partialParse ? TileData::State::partial : TileData::State::parsed;
|
||||
result.state = pending.empty() ? TileData::State::parsed : TileData::State::partial;
|
||||
return std::move(result);
|
||||
}
|
||||
|
||||
void TileWorker::redoPlacement(float angle, float pitch, bool collisionDebug) {
|
||||
TileParseResult TileWorker::parsePendingLayers() {
|
||||
// Try parsing the remaining layers that we couldn't parse in the first step due to missing
|
||||
// dependencies.
|
||||
for (auto it = pending.begin(); it != pending.end();) {
|
||||
auto& styleBucket = it->first;
|
||||
auto& bucket = it->second;
|
||||
assert(bucket);
|
||||
|
||||
if (styleBucket.type == StyleLayerType::Symbol) {
|
||||
auto symbolBucket = dynamic_cast<SymbolBucket*>(bucket.get());
|
||||
if (!symbolBucket->needsDependencies(*style.glyphStore, *style.sprite)) {
|
||||
symbolBucket->addFeatures(reinterpret_cast<uintptr_t>(this), *style.spriteAtlas,
|
||||
*style.glyphAtlas, *style.glyphStore, *collisionTile);
|
||||
insertBucket(styleBucket.name, std::move(bucket));
|
||||
pending.erase(it++);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Advance the iterator here; we're skipping this when erasing an element from this list.
|
||||
++it;
|
||||
}
|
||||
|
||||
result.state = pending.empty() ? TileData::State::parsed : TileData::State::partial;
|
||||
return std::move(result);
|
||||
}
|
||||
|
||||
void TileWorker::redoPlacement(
|
||||
const std::unordered_map<std::string, std::unique_ptr<Bucket>>* buckets,
|
||||
float angle,
|
||||
float pitch,
|
||||
bool collisionDebug) {
|
||||
|
||||
// Reset the collision tile so we have a clean slate; we're placing all features anyway.
|
||||
collisionTile = std::make_unique<CollisionTile>(angle, pitch, collisionDebug);
|
||||
|
||||
for (auto i = layers.rbegin(); i != layers.rend(); i++) {
|
||||
auto bucket = getBucket(**i);
|
||||
if (bucket) {
|
||||
bucket->placeFeatures(*collisionTile);
|
||||
const auto it = buckets->find((*i)->id);
|
||||
if (it != buckets->end()) {
|
||||
it->second->placeFeatures(*collisionTile);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,21 +122,15 @@ void TileWorker::parseLayer(const StyleLayer& layer, const GeometryTile& geometr
|
||||
return;
|
||||
}
|
||||
|
||||
// This is a singular layer. Check if this bucket already exists.
|
||||
if (getBucket(layer))
|
||||
return;
|
||||
|
||||
const StyleBucket& styleBucket = *layer.bucket;
|
||||
|
||||
// Skip this bucket if we are to not render this
|
||||
if (styleBucket.source != sourceID)
|
||||
return;
|
||||
if (id.z < std::floor(styleBucket.min_zoom))
|
||||
return;
|
||||
if (id.z >= std::ceil(styleBucket.max_zoom))
|
||||
return;
|
||||
if (styleBucket.visibility == mbgl::VisibilityType::None)
|
||||
if ((styleBucket.source != sourceID) ||
|
||||
(id.z < std::floor(styleBucket.min_zoom)) ||
|
||||
(id.z >= std::ceil(styleBucket.max_zoom)) ||
|
||||
(styleBucket.visibility == VisibilityType::None)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto geometryLayer = geometryTile.getLayer(styleBucket.source_layer);
|
||||
if (!geometryLayer) {
|
||||
@@ -114,35 +142,25 @@ void TileWorker::parseLayer(const StyleLayer& layer, const GeometryTile& geometr
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_ptr<Bucket> bucket;
|
||||
|
||||
switch (styleBucket.type) {
|
||||
case StyleLayerType::Fill:
|
||||
bucket = createFillBucket(*geometryLayer, styleBucket);
|
||||
createFillBucket(*geometryLayer, styleBucket);
|
||||
break;
|
||||
case StyleLayerType::Line:
|
||||
bucket = createLineBucket(*geometryLayer, styleBucket);
|
||||
createLineBucket(*geometryLayer, styleBucket);
|
||||
break;
|
||||
case StyleLayerType::Circle:
|
||||
bucket = createCircleBucket(*geometryLayer, styleBucket);
|
||||
createCircleBucket(*geometryLayer, styleBucket);
|
||||
break;
|
||||
case StyleLayerType::Symbol:
|
||||
bucket = createSymbolBucket(*geometryLayer, styleBucket);
|
||||
createSymbolBucket(*geometryLayer, styleBucket);
|
||||
break;
|
||||
case StyleLayerType::Raster:
|
||||
return;
|
||||
break;
|
||||
default:
|
||||
Log::Warning(Event::ParseTile, "unknown bucket render type for layer '%s' (source layer '%s')",
|
||||
styleBucket.name.c_str(), styleBucket.source_layer.c_str());
|
||||
}
|
||||
|
||||
// Bucket creation might fail because the data tile may not
|
||||
// contain any data that falls into this bucket.
|
||||
if (!bucket)
|
||||
return;
|
||||
|
||||
std::lock_guard<std::mutex> lock(bucketsMutex);
|
||||
buckets[styleBucket.name] = std::move(bucket);
|
||||
}
|
||||
|
||||
template <class Bucket>
|
||||
@@ -161,101 +179,108 @@ void TileWorker::addBucketGeometries(Bucket& bucket, const GeometryTileLayer& la
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Bucket> TileWorker::createFillBucket(const GeometryTileLayer& layer,
|
||||
const StyleBucket& bucket_desc) {
|
||||
void TileWorker::createFillBucket(const GeometryTileLayer& layer,
|
||||
const StyleBucket& styleBucket) {
|
||||
auto bucket = std::make_unique<FillBucket>();
|
||||
addBucketGeometries(bucket, layer, bucket_desc.filter);
|
||||
return bucket->hasData() ? std::move(bucket) : nullptr;
|
||||
|
||||
// Fill does not have layout properties to apply.
|
||||
|
||||
addBucketGeometries(bucket, layer, styleBucket.filter);
|
||||
|
||||
insertBucket(styleBucket.name, std::move(bucket));
|
||||
}
|
||||
|
||||
std::unique_ptr<Bucket> TileWorker::createLineBucket(const GeometryTileLayer& layer,
|
||||
const StyleBucket& bucket_desc) {
|
||||
void TileWorker::createLineBucket(const GeometryTileLayer& layer,
|
||||
const StyleBucket& styleBucket) {
|
||||
auto bucket = std::make_unique<LineBucket>();
|
||||
|
||||
const float z = id.z;
|
||||
auto& layout = bucket->layout;
|
||||
|
||||
applyLayoutProperty(PropertyKey::LineCap, bucket_desc.layout, layout.cap, z);
|
||||
applyLayoutProperty(PropertyKey::LineJoin, bucket_desc.layout, layout.join, z);
|
||||
applyLayoutProperty(PropertyKey::LineMiterLimit, bucket_desc.layout, layout.miter_limit, z);
|
||||
applyLayoutProperty(PropertyKey::LineRoundLimit, bucket_desc.layout, layout.round_limit, z);
|
||||
applyLayoutProperty(PropertyKey::LineCap, styleBucket.layout, layout.cap, z);
|
||||
applyLayoutProperty(PropertyKey::LineJoin, styleBucket.layout, layout.join, z);
|
||||
applyLayoutProperty(PropertyKey::LineMiterLimit, styleBucket.layout, layout.miter_limit, z);
|
||||
applyLayoutProperty(PropertyKey::LineRoundLimit, styleBucket.layout, layout.round_limit, z);
|
||||
|
||||
addBucketGeometries(bucket, layer, bucket_desc.filter);
|
||||
return bucket->hasData() ? std::move(bucket) : nullptr;
|
||||
addBucketGeometries(bucket, layer, styleBucket.filter);
|
||||
|
||||
insertBucket(styleBucket.name, std::move(bucket));
|
||||
}
|
||||
|
||||
std::unique_ptr<Bucket> TileWorker::createCircleBucket(const GeometryTileLayer& layer,
|
||||
const StyleBucket& bucket_desc) {
|
||||
void TileWorker::createCircleBucket(const GeometryTileLayer& layer,
|
||||
const StyleBucket& styleBucket) {
|
||||
auto bucket = std::make_unique<CircleBucket>();
|
||||
|
||||
// Circle does not have layout properties to apply.
|
||||
|
||||
addBucketGeometries(bucket, layer, bucket_desc.filter);
|
||||
return bucket->hasData() ? std::move(bucket) : nullptr;
|
||||
addBucketGeometries(bucket, layer, styleBucket.filter);
|
||||
|
||||
insertBucket(styleBucket.name, std::move(bucket));
|
||||
}
|
||||
|
||||
std::unique_ptr<Bucket> TileWorker::createSymbolBucket(const GeometryTileLayer& layer,
|
||||
const StyleBucket& bucket_desc) {
|
||||
void TileWorker::createSymbolBucket(const GeometryTileLayer& layer,
|
||||
const StyleBucket& styleBucket) {
|
||||
auto bucket = std::make_unique<SymbolBucket>(id.overscaling, id.z);
|
||||
|
||||
const float z = id.z;
|
||||
auto& layout = bucket->layout;
|
||||
|
||||
applyLayoutProperty(PropertyKey::SymbolPlacement, bucket_desc.layout, layout.placement, z);
|
||||
applyLayoutProperty(PropertyKey::SymbolPlacement, styleBucket.layout, layout.placement, z);
|
||||
if (layout.placement == PlacementType::Line) {
|
||||
layout.icon.rotation_alignment = RotationAlignmentType::Map;
|
||||
layout.text.rotation_alignment = RotationAlignmentType::Map;
|
||||
};
|
||||
applyLayoutProperty(PropertyKey::SymbolSpacing, bucket_desc.layout, layout.spacing, z);
|
||||
applyLayoutProperty(PropertyKey::SymbolAvoidEdges, bucket_desc.layout, layout.avoid_edges, z);
|
||||
applyLayoutProperty(PropertyKey::SymbolSpacing, styleBucket.layout, layout.spacing, z);
|
||||
applyLayoutProperty(PropertyKey::SymbolAvoidEdges, styleBucket.layout, layout.avoid_edges, z);
|
||||
|
||||
applyLayoutProperty(PropertyKey::IconAllowOverlap, bucket_desc.layout, layout.icon.allow_overlap, z);
|
||||
applyLayoutProperty(PropertyKey::IconIgnorePlacement, bucket_desc.layout, layout.icon.ignore_placement, z);
|
||||
applyLayoutProperty(PropertyKey::IconOptional, bucket_desc.layout, layout.icon.optional, z);
|
||||
applyLayoutProperty(PropertyKey::IconRotationAlignment, bucket_desc.layout, layout.icon.rotation_alignment, z);
|
||||
applyLayoutProperty(PropertyKey::IconImage, bucket_desc.layout, layout.icon.image, z);
|
||||
applyLayoutProperty(PropertyKey::IconPadding, bucket_desc.layout, layout.icon.padding, z);
|
||||
applyLayoutProperty(PropertyKey::IconRotate, bucket_desc.layout, layout.icon.rotate, z);
|
||||
applyLayoutProperty(PropertyKey::IconKeepUpright, bucket_desc.layout, layout.icon.keep_upright, z);
|
||||
applyLayoutProperty(PropertyKey::IconOffset, bucket_desc.layout, layout.icon.offset, z);
|
||||
applyLayoutProperty(PropertyKey::IconAllowOverlap, styleBucket.layout, layout.icon.allow_overlap, z);
|
||||
applyLayoutProperty(PropertyKey::IconIgnorePlacement, styleBucket.layout, layout.icon.ignore_placement, z);
|
||||
applyLayoutProperty(PropertyKey::IconOptional, styleBucket.layout, layout.icon.optional, z);
|
||||
applyLayoutProperty(PropertyKey::IconRotationAlignment, styleBucket.layout, layout.icon.rotation_alignment, z);
|
||||
applyLayoutProperty(PropertyKey::IconImage, styleBucket.layout, layout.icon.image, z);
|
||||
applyLayoutProperty(PropertyKey::IconPadding, styleBucket.layout, layout.icon.padding, z);
|
||||
applyLayoutProperty(PropertyKey::IconRotate, styleBucket.layout, layout.icon.rotate, z);
|
||||
applyLayoutProperty(PropertyKey::IconKeepUpright, styleBucket.layout, layout.icon.keep_upright, z);
|
||||
applyLayoutProperty(PropertyKey::IconOffset, styleBucket.layout, layout.icon.offset, z);
|
||||
|
||||
applyLayoutProperty(PropertyKey::TextRotationAlignment, bucket_desc.layout, layout.text.rotation_alignment, z);
|
||||
applyLayoutProperty(PropertyKey::TextField, bucket_desc.layout, layout.text.field, z);
|
||||
applyLayoutProperty(PropertyKey::TextFont, bucket_desc.layout, layout.text.font, z);
|
||||
applyLayoutProperty(PropertyKey::TextMaxWidth, bucket_desc.layout, layout.text.max_width, z);
|
||||
applyLayoutProperty(PropertyKey::TextLineHeight, bucket_desc.layout, layout.text.line_height, z);
|
||||
applyLayoutProperty(PropertyKey::TextLetterSpacing, bucket_desc.layout, layout.text.letter_spacing, z);
|
||||
applyLayoutProperty(PropertyKey::TextMaxAngle, bucket_desc.layout, layout.text.max_angle, z);
|
||||
applyLayoutProperty(PropertyKey::TextRotate, bucket_desc.layout, layout.text.rotate, z);
|
||||
applyLayoutProperty(PropertyKey::TextPadding, bucket_desc.layout, layout.text.padding, z);
|
||||
applyLayoutProperty(PropertyKey::TextIgnorePlacement, bucket_desc.layout, layout.text.ignore_placement, z);
|
||||
applyLayoutProperty(PropertyKey::TextOptional, bucket_desc.layout, layout.text.optional, z);
|
||||
applyLayoutProperty(PropertyKey::TextJustify, bucket_desc.layout, layout.text.justify, z);
|
||||
applyLayoutProperty(PropertyKey::TextAnchor, bucket_desc.layout, layout.text.anchor, z);
|
||||
applyLayoutProperty(PropertyKey::TextKeepUpright, bucket_desc.layout, layout.text.keep_upright, z);
|
||||
applyLayoutProperty(PropertyKey::TextTransform, bucket_desc.layout, layout.text.transform, z);
|
||||
applyLayoutProperty(PropertyKey::TextOffset, bucket_desc.layout, layout.text.offset, z);
|
||||
applyLayoutProperty(PropertyKey::TextAllowOverlap, bucket_desc.layout, layout.text.allow_overlap, z);
|
||||
applyLayoutProperty(PropertyKey::TextRotationAlignment, styleBucket.layout, layout.text.rotation_alignment, z);
|
||||
applyLayoutProperty(PropertyKey::TextField, styleBucket.layout, layout.text.field, z);
|
||||
applyLayoutProperty(PropertyKey::TextFont, styleBucket.layout, layout.text.font, z);
|
||||
applyLayoutProperty(PropertyKey::TextMaxWidth, styleBucket.layout, layout.text.max_width, z);
|
||||
applyLayoutProperty(PropertyKey::TextLineHeight, styleBucket.layout, layout.text.line_height, z);
|
||||
applyLayoutProperty(PropertyKey::TextLetterSpacing, styleBucket.layout, layout.text.letter_spacing, z);
|
||||
applyLayoutProperty(PropertyKey::TextMaxAngle, styleBucket.layout, layout.text.max_angle, z);
|
||||
applyLayoutProperty(PropertyKey::TextRotate, styleBucket.layout, layout.text.rotate, z);
|
||||
applyLayoutProperty(PropertyKey::TextPadding, styleBucket.layout, layout.text.padding, z);
|
||||
applyLayoutProperty(PropertyKey::TextIgnorePlacement, styleBucket.layout, layout.text.ignore_placement, z);
|
||||
applyLayoutProperty(PropertyKey::TextOptional, styleBucket.layout, layout.text.optional, z);
|
||||
applyLayoutProperty(PropertyKey::TextJustify, styleBucket.layout, layout.text.justify, z);
|
||||
applyLayoutProperty(PropertyKey::TextAnchor, styleBucket.layout, layout.text.anchor, z);
|
||||
applyLayoutProperty(PropertyKey::TextKeepUpright, styleBucket.layout, layout.text.keep_upright, z);
|
||||
applyLayoutProperty(PropertyKey::TextTransform, styleBucket.layout, layout.text.transform, z);
|
||||
applyLayoutProperty(PropertyKey::TextOffset, styleBucket.layout, layout.text.offset, z);
|
||||
applyLayoutProperty(PropertyKey::TextAllowOverlap, styleBucket.layout, layout.text.allow_overlap, z);
|
||||
|
||||
applyLayoutProperty(PropertyKey::IconSize, bucket_desc.layout, layout.icon.size, z + 1);
|
||||
applyLayoutProperty(PropertyKey::IconSize, bucket_desc.layout, layout.icon.max_size, 18);
|
||||
applyLayoutProperty(PropertyKey::TextSize, bucket_desc.layout, layout.text.size, z + 1);
|
||||
applyLayoutProperty(PropertyKey::TextSize, bucket_desc.layout, layout.text.max_size, 18);
|
||||
applyLayoutProperty(PropertyKey::IconSize, styleBucket.layout, layout.icon.size, z + 1);
|
||||
applyLayoutProperty(PropertyKey::IconSize, styleBucket.layout, layout.icon.max_size, 18);
|
||||
applyLayoutProperty(PropertyKey::TextSize, styleBucket.layout, layout.text.size, z + 1);
|
||||
applyLayoutProperty(PropertyKey::TextSize, styleBucket.layout, layout.text.max_size, 18);
|
||||
|
||||
if (bucket->needsDependencies(layer, bucket_desc.filter, *style.glyphStore, *style.sprite)) {
|
||||
partialParse = true;
|
||||
bucket->parseFeatures(layer, styleBucket.filter);
|
||||
|
||||
const bool needsDependencies = bucket->needsDependencies(*style.glyphStore, *style.sprite);
|
||||
if (needsDependencies) {
|
||||
// We cannot parse this bucket yet. Instead, we're saving it for later.
|
||||
pending.emplace_back(styleBucket, std::move(bucket));
|
||||
} else {
|
||||
bucket->addFeatures(reinterpret_cast<uintptr_t>(this), *style.spriteAtlas,
|
||||
*style.glyphAtlas, *style.glyphStore, *collisionTile);
|
||||
insertBucket(styleBucket.name, std::move(bucket));
|
||||
}
|
||||
}
|
||||
|
||||
void TileWorker::insertBucket(const std::string& name, std::unique_ptr<Bucket> bucket) {
|
||||
if (bucket->hasData()) {
|
||||
result.buckets.emplace_back(name, std::move(bucket));
|
||||
}
|
||||
|
||||
// We do not proceed if the parser is in a "partial" state because
|
||||
// the layer ordering needs to be respected when calculating text
|
||||
// collisions. Although, at this point, we requested all the resources
|
||||
// needed by this tile.
|
||||
if (partialParse) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bucket->addFeatures(reinterpret_cast<uintptr_t>(this), *style.spriteAtlas, *style.glyphAtlas,
|
||||
*style.glyphStore, *collisionTile);
|
||||
|
||||
return bucket->hasData() ? std::move(bucket) : nullptr;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <list>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace mbgl {
|
||||
@@ -24,9 +25,16 @@ class StyleLayer;
|
||||
class StyleBucket;
|
||||
class GeometryTileLayer;
|
||||
|
||||
using TileParseResult = mapbox::util::variant<
|
||||
TileData::State, // success
|
||||
std::string>; // error
|
||||
// We're using this class to shuttle the resulting buckets from the worker thread to the MapContext
|
||||
// thread. This class is movable-only because the vector contains movable-only value elements.
|
||||
class TileParseResultBuckets {
|
||||
public:
|
||||
TileData::State state = TileData::State::invalid;
|
||||
std::vector<std::pair<std::string, std::unique_ptr<Bucket>>> buckets;
|
||||
};
|
||||
|
||||
using TileParseResult = mapbox::util::variant<TileParseResultBuckets, // success
|
||||
std::string>; // error
|
||||
|
||||
class TileWorker : public util::noncopyable {
|
||||
public:
|
||||
@@ -38,20 +46,24 @@ public:
|
||||
std::unique_ptr<CollisionTile>);
|
||||
~TileWorker();
|
||||
|
||||
Bucket* getBucket(const StyleLayer&) const;
|
||||
|
||||
TileParseResult parse(const GeometryTile&);
|
||||
void redoPlacement(float angle, float pitch, bool collisionDebug);
|
||||
TileParseResult parseAllLayers(const GeometryTile&);
|
||||
TileParseResult parsePendingLayers();
|
||||
void redoPlacement(const std::unordered_map<std::string, std::unique_ptr<Bucket>>*,
|
||||
float angle,
|
||||
float pitch,
|
||||
bool collisionDebug);
|
||||
|
||||
std::vector<util::ptr<StyleLayer>> layers;
|
||||
|
||||
private:
|
||||
void parseLayer(const StyleLayer&, const GeometryTile&);
|
||||
|
||||
std::unique_ptr<Bucket> createFillBucket(const GeometryTileLayer&, const StyleBucket&);
|
||||
std::unique_ptr<Bucket> createLineBucket(const GeometryTileLayer&, const StyleBucket&);
|
||||
std::unique_ptr<Bucket> createCircleBucket(const GeometryTileLayer&, const StyleBucket&);
|
||||
std::unique_ptr<Bucket> createSymbolBucket(const GeometryTileLayer&, const StyleBucket&);
|
||||
void createFillBucket(const GeometryTileLayer&, const StyleBucket&);
|
||||
void createLineBucket(const GeometryTileLayer&, const StyleBucket&);
|
||||
void createCircleBucket(const GeometryTileLayer&, const StyleBucket&);
|
||||
void createSymbolBucket(const GeometryTileLayer&, const StyleBucket&);
|
||||
|
||||
void insertBucket(const std::string& name, std::unique_ptr<Bucket>);
|
||||
|
||||
template <class Bucket>
|
||||
void addBucketGeometries(Bucket&, const GeometryTileLayer&, const FilterExpression&);
|
||||
@@ -63,19 +75,16 @@ private:
|
||||
Style& style;
|
||||
const std::atomic<TileData::State>& state;
|
||||
|
||||
bool partialParse = false;
|
||||
|
||||
std::unique_ptr<CollisionTile> collisionTile;
|
||||
|
||||
// Contains all the Bucket objects for the tile. Buckets are render
|
||||
// objects and they get added to this map as they get processed.
|
||||
// Tiles partially parsed can get new buckets at any moment but are
|
||||
// also fit for rendering. That said, access to this list needs locking
|
||||
// unless the tile is completely parsed.
|
||||
std::unordered_map<std::string, std::unique_ptr<Bucket>> buckets;
|
||||
mutable std::mutex bucketsMutex;
|
||||
// Contains buckets that we couldn't parse so far due to missing resources.
|
||||
// They will be attempted on subsequent parses.
|
||||
std::list<std::pair<const StyleBucket&, std::unique_ptr<Bucket>>> pending;
|
||||
|
||||
// Temporary holder
|
||||
TileParseResultBuckets result;
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace mbgl
|
||||
|
||||
#endif
|
||||
|
||||
@@ -43,11 +43,12 @@ void VectorTileData::request(float pixelRatio, const std::function<void()>& call
|
||||
|
||||
FileSource* fs = util::ThreadContext::getFileSource();
|
||||
req = fs->request({ Resource::Kind::Tile, url }, util::RunLoop::getLoop(), [url, callback, this](const Response &res) {
|
||||
if (res.stale) {
|
||||
// Only handle fresh responses.
|
||||
// Do not cancel the request here; we want to get notified about tiles that expire.
|
||||
|
||||
if (res.data && data == res.data) {
|
||||
// We got the same data again. Abort early.
|
||||
return;
|
||||
}
|
||||
req = nullptr;
|
||||
|
||||
if (res.status == Response::NotFound) {
|
||||
state = State::parsed;
|
||||
@@ -57,39 +58,80 @@ void VectorTileData::request(float pixelRatio, const std::function<void()>& call
|
||||
|
||||
if (res.status != Response::Successful) {
|
||||
std::stringstream message;
|
||||
message << "Failed to load [" << url << "]: " << res.message;
|
||||
message << "Failed to load [" << url << "]: " << res.message;
|
||||
error = message.str();
|
||||
state = State::obsolete;
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
state = State::loaded;
|
||||
if (state == State::loading) {
|
||||
state = State::loaded;
|
||||
} else if (isReady()) {
|
||||
state = State::partial;
|
||||
}
|
||||
data = res.data;
|
||||
|
||||
reparse(callback);
|
||||
parse(callback);
|
||||
});
|
||||
}
|
||||
|
||||
bool VectorTileData::reparse(std::function<void()> callback) {
|
||||
if (parsing || (state != State::loaded && state != State::partial)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
parsing = true;
|
||||
|
||||
void VectorTileData::parse(std::function<void ()> callback) {
|
||||
// Kick off a fresh parse of this tile. This happens when the tile is new, or
|
||||
// when tile data changed. Replacing the workdRequest will cancel a pending work
|
||||
// request in case there is one.
|
||||
workRequest.reset();
|
||||
workRequest = worker.parseVectorTile(tileWorker, data, [this, callback] (TileParseResult result) {
|
||||
parsing = false;
|
||||
|
||||
workRequest.reset();
|
||||
if (state == State::obsolete) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.is<State>()) {
|
||||
state = result.get<State>();
|
||||
if (result.is<TileParseResultBuckets>()) {
|
||||
auto& resultBuckets = result.get<TileParseResultBuckets>();
|
||||
state = resultBuckets.state;
|
||||
|
||||
// Move over all buckets we received in this parse request, potentially overwriting
|
||||
// existing buckets in case we got a refresh parse.
|
||||
for (auto& bucket : resultBuckets.buckets) {
|
||||
buckets[bucket.first] = std::move(bucket.second);
|
||||
}
|
||||
} else {
|
||||
std::stringstream message;
|
||||
message << "Failed to parse [" << std::string(id) << "]: " << result.get<std::string>();
|
||||
message << "Failed to parse [" << std::string(id) << "]: " << result.get<std::string>();
|
||||
error = message.str();
|
||||
state = State::obsolete;
|
||||
}
|
||||
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
bool VectorTileData::parsePending(std::function<void()> callback) {
|
||||
if (workRequest) {
|
||||
// There's already parsing or placement going on.
|
||||
return false;
|
||||
}
|
||||
|
||||
workRequest.reset();
|
||||
workRequest = worker.parsePendingVectorTileLayers(tileWorker, [this, callback] (TileParseResult result) {
|
||||
workRequest.reset();
|
||||
if (state == State::obsolete) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.is<TileParseResultBuckets>()) {
|
||||
auto& resultBuckets = result.get<TileParseResultBuckets>();
|
||||
state = resultBuckets.state;
|
||||
|
||||
// Move over all buckets we received in this parse request, potentially overwriting
|
||||
// existing buckets in case we got a refresh parse.
|
||||
for (auto& bucket : resultBuckets.buckets) {
|
||||
buckets[bucket.first] = std::move(bucket.second);
|
||||
}
|
||||
} else {
|
||||
std::stringstream message;
|
||||
message << "Failed to parse [" << std::string(id) << "]: " << result.get<std::string>();
|
||||
error = message.str();
|
||||
state = State::obsolete;
|
||||
}
|
||||
@@ -101,39 +143,46 @@ bool VectorTileData::reparse(std::function<void()> callback) {
|
||||
}
|
||||
|
||||
Bucket* VectorTileData::getBucket(const StyleLayer& layer) {
|
||||
if (!isReady() || !layer.bucket) {
|
||||
if (!layer.bucket) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return tileWorker.getBucket(layer);
|
||||
const auto it = buckets.find(layer.bucket->name);
|
||||
if (it == buckets.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
assert(it->second);
|
||||
return it->second.get();
|
||||
}
|
||||
|
||||
void VectorTileData::redoPlacement(float angle, float pitch, bool collisionDebug) {
|
||||
if (angle == currentAngle &&
|
||||
pitch == currentPitch &&
|
||||
collisionDebug == currentCollisionDebug)
|
||||
if (angle == currentAngle && pitch == currentPitch && collisionDebug == currentCollisionDebug)
|
||||
return;
|
||||
|
||||
lastAngle = angle;
|
||||
lastPitch = pitch;
|
||||
lastCollisionDebug = collisionDebug;
|
||||
|
||||
if (state != State::parsed || redoingPlacement)
|
||||
if (workRequest) {
|
||||
// Don't start a new placement request when the current one hasn't completed yet, or when
|
||||
// we are parsing buckets.
|
||||
return;
|
||||
}
|
||||
|
||||
redoingPlacement = true;
|
||||
currentAngle = angle;
|
||||
currentPitch = pitch;
|
||||
currentCollisionDebug = collisionDebug;
|
||||
|
||||
workRequest = worker.redoPlacement(tileWorker, angle, pitch, collisionDebug, [this] {
|
||||
workRequest.reset();
|
||||
workRequest = worker.redoPlacement(tileWorker, buckets, angle, pitch, collisionDebug, [this] {
|
||||
workRequest.reset();
|
||||
for (const auto& layer : tileWorker.layers) {
|
||||
auto bucket = getBucket(*layer);
|
||||
if (bucket) {
|
||||
bucket->swapRenderData();
|
||||
}
|
||||
}
|
||||
redoingPlacement = false;
|
||||
redoPlacement(lastAngle, lastPitch, lastCollisionDebug);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -16,20 +16,16 @@ class Request;
|
||||
|
||||
class VectorTileData : public TileData {
|
||||
public:
|
||||
VectorTileData(const TileID&,
|
||||
Style&,
|
||||
const SourceInfo&,
|
||||
float angle_,
|
||||
float pitch_,
|
||||
bool collisionDebug_);
|
||||
VectorTileData(
|
||||
const TileID&, Style&, const SourceInfo&, float angle_, float pitch_, bool collisionDebug_);
|
||||
~VectorTileData();
|
||||
|
||||
Bucket* getBucket(const StyleLayer&) override;
|
||||
|
||||
void request(float pixelRatio,
|
||||
const std::function<void()>& callback);
|
||||
void request(float pixelRatio, const std::function<void()>& callback);
|
||||
|
||||
bool reparse(std::function<void ()> callback) override;
|
||||
void parse(std::function<void()> callback);
|
||||
bool parsePending(std::function<void()> callback) override;
|
||||
|
||||
void redoPlacement(float angle, float pitch, bool collisionDebug) override;
|
||||
|
||||
@@ -39,7 +35,11 @@ private:
|
||||
Worker& worker;
|
||||
TileWorker tileWorker;
|
||||
std::unique_ptr<WorkRequest> workRequest;
|
||||
bool parsing = false;
|
||||
|
||||
// Contains all the Bucket objects for the tile. Buckets are render
|
||||
// objects and they get added by tile parsing operations.
|
||||
std::unordered_map<std::string, std::unique_ptr<Bucket>> buckets;
|
||||
|
||||
const SourceInfo& source;
|
||||
RequestHolder req;
|
||||
std::shared_ptr<const std::string> data;
|
||||
@@ -49,9 +49,8 @@ private:
|
||||
float currentPitch;
|
||||
bool lastCollisionDebug = 0;
|
||||
bool currentCollisionDebug = 0;
|
||||
bool redoingPlacement = false;
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace mbgl
|
||||
|
||||
#endif
|
||||
|
||||
@@ -32,6 +32,8 @@ public:
|
||||
|
||||
virtual ~Bucket() {}
|
||||
|
||||
virtual bool hasData() const = 0;
|
||||
|
||||
inline bool needsUpload() const {
|
||||
return !uploaded;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ public:
|
||||
void upload() override;
|
||||
void render(Painter&, const StyleLayer&, const TileID&, const mat4&) override;
|
||||
|
||||
bool hasData() const;
|
||||
bool hasData() const override;
|
||||
void addGeometry(const GeometryCollection&);
|
||||
|
||||
void drawCircles(CircleShader& shader);
|
||||
|
||||
@@ -5,21 +5,13 @@
|
||||
#include <mbgl/platform/gl.hpp>
|
||||
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
|
||||
using namespace mbgl;
|
||||
|
||||
DebugBucket::DebugBucket(DebugFontBuffer& fontBuffer_)
|
||||
: fontBuffer(fontBuffer_) {
|
||||
}
|
||||
|
||||
void DebugBucket::upload() {
|
||||
fontBuffer.upload();
|
||||
|
||||
uploaded = true;
|
||||
}
|
||||
|
||||
void DebugBucket::render(Painter& painter, const StyleLayer&, const TileID&, const mat4& matrix) {
|
||||
painter.renderDebugText(*this, matrix);
|
||||
DebugBucket::DebugBucket(const TileID id, const TileData::State state_) : state(state_) {
|
||||
const std::string text = std::string(id) + " - " + TileData::StateToString(state);
|
||||
fontBuffer.addText(text.c_str(), 50, 200, 5);
|
||||
}
|
||||
|
||||
void DebugBucket::drawLines(PlainShader& shader) {
|
||||
|
||||
@@ -1,28 +1,25 @@
|
||||
#ifndef MBGL_RENDERER_DEBUGBUCKET
|
||||
#define MBGL_RENDERER_DEBUGBUCKET
|
||||
|
||||
#include <mbgl/renderer/bucket.hpp>
|
||||
#include <mbgl/map/tile_data.hpp>
|
||||
#include <mbgl/geometry/debug_font_buffer.hpp>
|
||||
#include <mbgl/geometry/vao.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
class PlainShader;
|
||||
|
||||
class DebugBucket : public Bucket {
|
||||
class DebugBucket : private util::noncopyable {
|
||||
public:
|
||||
DebugBucket(DebugFontBuffer& fontBuffer);
|
||||
|
||||
void upload() override;
|
||||
void render(Painter&, const StyleLayer&, const TileID&, const mat4&) override;
|
||||
DebugBucket(TileID id, TileData::State);
|
||||
|
||||
void drawLines(PlainShader& shader);
|
||||
void drawPoints(PlainShader& shader);
|
||||
|
||||
const TileData::State state;
|
||||
|
||||
private:
|
||||
DebugFontBuffer& fontBuffer;
|
||||
DebugFontBuffer fontBuffer;
|
||||
VertexArrayObject array;
|
||||
};
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ public:
|
||||
|
||||
void upload() override;
|
||||
void render(Painter&, const StyleLayer&, const TileID&, const mat4&) override;
|
||||
bool hasData() const;
|
||||
bool hasData() const override;
|
||||
|
||||
void addGeometry(const GeometryCollection&);
|
||||
void tessellate();
|
||||
|
||||
@@ -30,7 +30,7 @@ public:
|
||||
|
||||
void upload() override;
|
||||
void render(Painter&, const StyleLayer&, const TileID&, const mat4&) override;
|
||||
bool hasData() const;
|
||||
bool hasData() const override;
|
||||
|
||||
void addGeometry(const GeometryCollection&);
|
||||
void addGeometry(const std::vector<Coordinate>& line);
|
||||
|
||||
@@ -102,7 +102,7 @@ public:
|
||||
// Renders the red debug frame around a tile, visualizing its perimeter.
|
||||
void renderDebugFrame(const mat4 &matrix);
|
||||
|
||||
void renderDebugText(DebugBucket&, const mat4&);
|
||||
void renderDebugText(TileData&, const mat4&);
|
||||
void renderFill(FillBucket&, const FillLayer&, const TileID&, const mat4&);
|
||||
void renderLine(LineBucket&, const LineLayer&, const TileID&, const mat4&);
|
||||
void renderCircle(CircleBucket&, const CircleLayer&, const TileID&, const mat4&);
|
||||
|
||||
@@ -14,34 +14,38 @@ void Painter::renderTileDebug(const Tile& tile) {
|
||||
assert(tile.data);
|
||||
if (data.getDebug()) {
|
||||
prepareTile(tile);
|
||||
renderDebugText(tile.data->debugBucket, tile.matrix);
|
||||
renderDebugText(*tile.data, tile.matrix);
|
||||
renderDebugFrame(tile.matrix);
|
||||
}
|
||||
}
|
||||
|
||||
void Painter::renderDebugText(DebugBucket& bucket, const mat4 &matrix) {
|
||||
void Painter::renderDebugText(TileData& tileData, const mat4 &matrix) {
|
||||
MBGL_DEBUG_GROUP("debug text");
|
||||
|
||||
config.depthTest = false;
|
||||
|
||||
if (!tileData.debugBucket || tileData.debugBucket->state != tileData.getState()) {
|
||||
tileData.debugBucket = std::make_unique<DebugBucket>(tileData.id, tileData.getState());
|
||||
}
|
||||
|
||||
useProgram(plainShader->program);
|
||||
plainShader->u_matrix = matrix;
|
||||
|
||||
// Draw white outline
|
||||
plainShader->u_color = {{ 1.0f, 1.0f, 1.0f, 1.0f }};
|
||||
lineWidth(4.0f * data.pixelRatio);
|
||||
bucket.drawLines(*plainShader);
|
||||
tileData.debugBucket->drawLines(*plainShader);
|
||||
|
||||
#ifndef GL_ES_VERSION_2_0
|
||||
// Draw line "end caps"
|
||||
MBGL_CHECK_ERROR(glPointSize(2));
|
||||
bucket.drawPoints(*plainShader);
|
||||
tileData.debugBucket->drawPoints(*plainShader);
|
||||
#endif
|
||||
|
||||
// Draw black text.
|
||||
plainShader->u_color = {{ 0.0f, 0.0f, 0.0f, 1.0f }};
|
||||
lineWidth(2.0f * data.pixelRatio);
|
||||
bucket.drawLines(*plainShader);
|
||||
tileData.debugBucket->drawLines(*plainShader);
|
||||
|
||||
config.depthTest = true;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ public:
|
||||
|
||||
void upload() override;
|
||||
void render(Painter&, const StyleLayer&, const TileID&, const mat4&) override;
|
||||
bool hasData() const;
|
||||
bool hasData() const override;
|
||||
|
||||
bool setImage(std::unique_ptr<util::Image> image);
|
||||
|
||||
|
||||
@@ -90,20 +90,16 @@ bool SymbolBucket::hasIconData() const { return renderData && !renderData->icon.
|
||||
|
||||
bool SymbolBucket::hasCollisionBoxData() const { return renderData && !renderData->collisionBox.groups.empty(); }
|
||||
|
||||
bool SymbolBucket::needsDependencies(const GeometryTileLayer& layer,
|
||||
const FilterExpression& filter,
|
||||
GlyphStore& glyphStore,
|
||||
Sprite& sprite) {
|
||||
void SymbolBucket::parseFeatures(const GeometryTileLayer& layer,
|
||||
const FilterExpression& filter) {
|
||||
const bool has_text = !layout.text.field.empty() && !layout.text.font.empty();
|
||||
const bool has_icon = !layout.icon.image.empty();
|
||||
|
||||
if (!has_text && !has_icon) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine and load glyph ranges
|
||||
std::set<GlyphRange> ranges;
|
||||
|
||||
const GLsizei featureCount = static_cast<GLsizei>(layer.featureCount());
|
||||
for (GLsizei i = 0; i < featureCount; i++) {
|
||||
auto feature = layer.getFeature(i);
|
||||
@@ -161,12 +157,15 @@ bool SymbolBucket::needsDependencies(const GeometryTileLayer& layer,
|
||||
if (layout.placement == PlacementType::Line) {
|
||||
util::mergeLines(features);
|
||||
}
|
||||
}
|
||||
|
||||
if (!glyphStore.hasGlyphRanges(layout.text.font, ranges)) {
|
||||
bool SymbolBucket::needsDependencies(GlyphStore& glyphStore,
|
||||
Sprite& sprite) {
|
||||
if (!layout.text.field.empty() && !layout.text.font.empty() && !glyphStore.hasGlyphRanges(layout.text.font, ranges)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!sprite.isLoaded()) {
|
||||
if (!layout.icon.image.empty() && !sprite.isLoaded()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
#include <memory>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
namespace mbgl {
|
||||
@@ -69,7 +70,7 @@ public:
|
||||
|
||||
void upload() override;
|
||||
void render(Painter&, const StyleLayer&, const TileID&, const mat4&) override;
|
||||
bool hasData() const;
|
||||
bool hasData() const override;
|
||||
bool hasTextData() const;
|
||||
bool hasIconData() const;
|
||||
bool hasCollisionBoxData() const;
|
||||
@@ -85,10 +86,10 @@ public:
|
||||
void drawIcons(IconShader& shader);
|
||||
void drawCollisionBoxes(CollisionBoxShader& shader);
|
||||
|
||||
bool needsDependencies(const GeometryTileLayer&,
|
||||
const FilterExpression&,
|
||||
GlyphStore&,
|
||||
Sprite&);
|
||||
void parseFeatures(const GeometryTileLayer&,
|
||||
const FilterExpression&);
|
||||
bool needsDependencies(GlyphStore& glyphStore,
|
||||
Sprite& sprite);
|
||||
void placeFeatures(CollisionTile&) override;
|
||||
|
||||
private:
|
||||
@@ -120,6 +121,7 @@ private:
|
||||
const float tileExtent = 4096.0f;
|
||||
const float tilePixelRatio;
|
||||
|
||||
std::set<GlyphRange> ranges;
|
||||
std::vector<SymbolInstance> symbolInstances;
|
||||
std::vector<SymbolFeature> features;
|
||||
|
||||
|
||||
@@ -16,7 +16,9 @@ class Worker::Impl {
|
||||
public:
|
||||
Impl() = default;
|
||||
|
||||
void parseRasterTile(RasterBucket* bucket, const std::shared_ptr<const std::string> data, std::function<void (TileParseResult)> callback) {
|
||||
void parseRasterTile(std::unique_ptr<RasterBucket> bucket,
|
||||
const std::shared_ptr<const std::string> data,
|
||||
std::function<void(TileParseResult)> callback) {
|
||||
std::unique_ptr<util::Image> image(new util::Image(*data));
|
||||
if (!(*image)) {
|
||||
callback(TileParseResult("error parsing raster image"));
|
||||
@@ -26,34 +28,56 @@ public:
|
||||
callback(TileParseResult("error setting raster image to bucket"));
|
||||
}
|
||||
|
||||
callback(TileParseResult(TileData::State::parsed));
|
||||
TileParseResultBuckets result;
|
||||
result.buckets.emplace_back("raster", std::move(bucket));
|
||||
result.state = TileData::State::parsed;
|
||||
|
||||
callback(std::move(result));
|
||||
}
|
||||
|
||||
void parseVectorTile(TileWorker* worker, const std::shared_ptr<const std::string> data, std::function<void (TileParseResult)> callback) {
|
||||
void parseVectorTile(TileWorker* worker,
|
||||
const std::shared_ptr<const std::string> data,
|
||||
std::function<void(TileParseResult)> callback) {
|
||||
try {
|
||||
pbf tilePBF(reinterpret_cast<const unsigned char*>(data->data()), data->size());
|
||||
callback(worker->parse(VectorTile(tilePBF)));
|
||||
callback(worker->parseAllLayers(VectorTile(tilePBF)));
|
||||
} catch (const std::exception& ex) {
|
||||
callback(TileParseResult(ex.what()));
|
||||
}
|
||||
}
|
||||
|
||||
void parseLiveTile(TileWorker* worker, const AnnotationTile* tile, std::function<void (TileParseResult)> callback) {
|
||||
void parsePendingVectorTileLayers(TileWorker* worker,
|
||||
std::function<void(TileParseResult)> callback) {
|
||||
try {
|
||||
callback(worker->parse(*tile));
|
||||
callback(worker->parsePendingLayers());
|
||||
} catch (const std::exception& ex) {
|
||||
callback(TileParseResult(ex.what()));
|
||||
}
|
||||
}
|
||||
|
||||
void redoPlacement(TileWorker* worker, float angle, float pitch, bool collisionDebug, std::function<void ()> callback) {
|
||||
worker->redoPlacement(angle, pitch, collisionDebug);
|
||||
void parseLiveTile(TileWorker* worker,
|
||||
const AnnotationTile* tile,
|
||||
std::function<void(TileParseResult)> callback) {
|
||||
try {
|
||||
callback(worker->parseAllLayers(*tile));
|
||||
} catch (const std::exception& ex) {
|
||||
callback(TileParseResult(ex.what()));
|
||||
}
|
||||
}
|
||||
|
||||
void redoPlacement(TileWorker* worker,
|
||||
const std::unordered_map<std::string, std::unique_ptr<Bucket>>* buckets,
|
||||
float angle,
|
||||
float pitch,
|
||||
bool collisionDebug,
|
||||
std::function<void()> callback) {
|
||||
worker->redoPlacement(buckets, angle, pitch, collisionDebug);
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
Worker::Worker(std::size_t count) {
|
||||
util::ThreadContext context = {"Worker", util::ThreadType::Worker, util::ThreadPriority::Low};
|
||||
util::ThreadContext context = { "Worker", util::ThreadType::Worker, util::ThreadPriority::Low };
|
||||
for (std::size_t i = 0; i < count; i++) {
|
||||
threads.emplace_back(std::make_unique<util::Thread<Impl>>(context));
|
||||
}
|
||||
@@ -61,24 +85,50 @@ Worker::Worker(std::size_t count) {
|
||||
|
||||
Worker::~Worker() = default;
|
||||
|
||||
std::unique_ptr<WorkRequest> Worker::parseRasterTile(RasterBucket& bucket, const std::shared_ptr<const std::string> data, std::function<void (TileParseResult)> callback) {
|
||||
std::unique_ptr<WorkRequest>
|
||||
Worker::parseRasterTile(std::unique_ptr<RasterBucket> bucket,
|
||||
const std::shared_ptr<const std::string> data,
|
||||
std::function<void(TileParseResult)> callback) {
|
||||
current = (current + 1) % threads.size();
|
||||
return threads[current]->invokeWithCallback(&Worker::Impl::parseRasterTile, callback, &bucket, data);
|
||||
return threads[current]->invokeWithCallback(&Worker::Impl::parseRasterTile, callback, bucket,
|
||||
data);
|
||||
}
|
||||
|
||||
std::unique_ptr<WorkRequest> Worker::parseVectorTile(TileWorker& worker, const std::shared_ptr<const std::string> data, std::function<void (TileParseResult)> callback) {
|
||||
std::unique_ptr<WorkRequest>
|
||||
Worker::parseVectorTile(TileWorker& worker,
|
||||
const std::shared_ptr<const std::string> data,
|
||||
std::function<void(TileParseResult)> callback) {
|
||||
current = (current + 1) % threads.size();
|
||||
return threads[current]->invokeWithCallback(&Worker::Impl::parseVectorTile, callback, &worker, data);
|
||||
return threads[current]->invokeWithCallback(&Worker::Impl::parseVectorTile, callback, &worker,
|
||||
data);
|
||||
}
|
||||
|
||||
std::unique_ptr<WorkRequest> Worker::parseLiveTile(TileWorker& worker, const AnnotationTile& tile, std::function<void (TileParseResult)> callback) {
|
||||
std::unique_ptr<WorkRequest>
|
||||
Worker::parsePendingVectorTileLayers(TileWorker& worker,
|
||||
std::function<void(TileParseResult)> callback) {
|
||||
current = (current + 1) % threads.size();
|
||||
return threads[current]->invokeWithCallback(&Worker::Impl::parseLiveTile, callback, &worker, &tile);
|
||||
return threads[current]->invokeWithCallback(&Worker::Impl::parsePendingVectorTileLayers,
|
||||
callback, &worker);
|
||||
}
|
||||
|
||||
std::unique_ptr<WorkRequest> Worker::redoPlacement(TileWorker& worker, float angle, float pitch, bool collisionDebug, std::function<void ()> callback) {
|
||||
std::unique_ptr<WorkRequest> Worker::parseLiveTile(TileWorker& worker,
|
||||
const AnnotationTile& tile,
|
||||
std::function<void(TileParseResult)> callback) {
|
||||
current = (current + 1) % threads.size();
|
||||
return threads[current]->invokeWithCallback(&Worker::Impl::redoPlacement, callback, &worker, angle, pitch, collisionDebug);
|
||||
return threads[current]->invokeWithCallback(&Worker::Impl::parseLiveTile, callback, &worker,
|
||||
&tile);
|
||||
}
|
||||
|
||||
std::unique_ptr<WorkRequest>
|
||||
Worker::redoPlacement(TileWorker& worker,
|
||||
const std::unordered_map<std::string, std::unique_ptr<Bucket>>& buckets,
|
||||
float angle,
|
||||
float pitch,
|
||||
bool collisionDebug,
|
||||
std::function<void()> callback) {
|
||||
current = (current + 1) % threads.size();
|
||||
return threads[current]->invokeWithCallback(&Worker::Impl::redoPlacement, callback, &worker,
|
||||
&buckets, angle, pitch, collisionDebug);
|
||||
}
|
||||
|
||||
} // end namespace mbgl
|
||||
|
||||
@@ -31,34 +31,33 @@ public:
|
||||
|
||||
using Request = std::unique_ptr<WorkRequest>;
|
||||
|
||||
Request parseRasterTile(
|
||||
RasterBucket&,
|
||||
std::shared_ptr<const std::string> data,
|
||||
std::function<void (TileParseResult)> callback);
|
||||
Request parseRasterTile(std::unique_ptr<RasterBucket> bucket,
|
||||
std::shared_ptr<const std::string> data,
|
||||
std::function<void(TileParseResult)> callback);
|
||||
|
||||
Request parseVectorTile(
|
||||
TileWorker&,
|
||||
std::shared_ptr<const std::string> data,
|
||||
std::function<void (TileParseResult)> callback);
|
||||
Request parseVectorTile(TileWorker&,
|
||||
std::shared_ptr<const std::string> data,
|
||||
std::function<void(TileParseResult)> callback);
|
||||
|
||||
Request parseLiveTile(
|
||||
TileWorker&,
|
||||
const AnnotationTile&,
|
||||
std::function<void (TileParseResult)> callback);
|
||||
Request parsePendingVectorTileLayers(TileWorker&,
|
||||
std::function<void(TileParseResult)> callback);
|
||||
|
||||
Request redoPlacement(
|
||||
TileWorker&,
|
||||
float angle,
|
||||
float pitch,
|
||||
bool collisionDebug,
|
||||
std::function<void ()> callback);
|
||||
Request parseLiveTile(TileWorker&,
|
||||
const AnnotationTile&,
|
||||
std::function<void(TileParseResult)> callback);
|
||||
|
||||
Request redoPlacement(TileWorker&,
|
||||
const std::unordered_map<std::string, std::unique_ptr<Bucket>>&,
|
||||
float angle,
|
||||
float pitch,
|
||||
bool collisionDebug,
|
||||
std::function<void()> callback);
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::vector<std::unique_ptr<util::Thread<Impl>>> threads;
|
||||
std::size_t current = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -38,6 +38,10 @@ public:
|
||||
}
|
||||
|
||||
~MockMapContext() {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
void cleanup() {
|
||||
style_.reset();
|
||||
}
|
||||
|
||||
@@ -118,6 +122,7 @@ void runTestCase(MockFileSource::Type type,
|
||||
|
||||
// Needed because it will make the Map thread
|
||||
// join and cease logging after this point.
|
||||
context->invoke(&MockMapContext::cleanup);
|
||||
context.reset();
|
||||
|
||||
uint32_t match = 0;
|
||||
|
||||
Reference in New Issue
Block a user