use SpriteStore from the SpriteAtlas

This commit is contained in:
Konstantin Käfer
2015-07-01 18:13:16 +02:00
parent d2ee014c6e
commit afaf74e41a
14 changed files with 91 additions and 227 deletions

View File

@@ -9,13 +9,15 @@ namespace mbgl {
SpriteImage::SpriteImage(const uint16_t width_,
const uint16_t height_,
const float pixelRatio_,
std::string&& data_)
std::string&& data_,
bool sdf_)
: width(width_),
height(height_),
pixelRatio(pixelRatio_),
pixelWidth(std::ceil(width * pixelRatio)),
pixelHeight(std::ceil(height * pixelRatio)),
data(std::move(data_)) {
data(std::move(data_)),
sdf(sdf_) {
const size_t size = pixelWidth * pixelHeight * 4;
if (size == 0) {
throw util::SpriteImageException("Sprite image dimensions may not be zero");

View File

@@ -12,7 +12,8 @@ namespace mbgl {
class SpriteImage : private util::noncopyable {
public:
SpriteImage(uint16_t width, uint16_t height, float pixelRatio, std::string&& data);
SpriteImage(
uint16_t width, uint16_t height, float pixelRatio, std::string&& data, bool sdf = false);
// Logical dimensions of the sprite image.
const uint16_t width;
@@ -29,6 +30,9 @@ public:
// (width * ratio) * (height * ratio) * 4 (RGBA) bytes. The scan lines may
// not have gaps between them (i.e. stride == 0).
const std::string data;
// Whether this image should be interpreted as a signed distance field icon.
const bool sdf;
};
}

View File

@@ -18,7 +18,7 @@ SpriteImagePtr createSpriteImage(const util::Image& image,
const uint16_t srcWidth,
const uint16_t srcHeight,
const double ratio,
const bool) {
const bool sdf) {
// Disallow invalid parameter configurations.
if (srcWidth == 0 || srcHeight == 0 || ratio <= 0 || ratio > 10 || srcWidth > 1024 ||
srcHeight > 1024) {
@@ -52,7 +52,7 @@ SpriteImagePtr createSpriteImage(const util::Image& image,
}
}
return std::make_unique<const SpriteImage>(width, height, ratio, std::move(data));
return std::make_unique<const SpriteImage>(width, height, ratio, std::move(data), sdf);
}
namespace {

View File

@@ -22,50 +22,12 @@ using namespace mbgl;
SpriteAtlas::SpriteAtlas(dimension width_, dimension height_, SpriteStore& store_)
: width(width_),
height(height_),
bin(width_, height_),
store(store_),
pixelRatio(store.pixelRatio),
bin(width_, height_),
dirty(true) {
}
bool SpriteAtlas::resize(const float newRatio) {
if (pixelRatio == newRatio) return false;
std::lock_guard<std::recursive_mutex> lock(mtx);
const float oldRatio = pixelRatio;
pixelRatio = newRatio;
if (data) {
const auto oldData = std::move(data);
allocate();
const int old_w = width * oldRatio;
const int old_h = height * oldRatio;
const int new_w = width * newRatio;
const int new_h = height * newRatio;
// Basic image scaling. TODO: Replace this with better image scaling.
for (int y = 0; y < new_h; y++) {
const int old_yoffset = ((y * old_h) / new_h) * old_w;
const int new_yoffset = y * new_w;
for (int x = 0; x < new_w; x++) {
const int old_x = (x * old_w) / new_w;
data[new_yoffset + x] = oldData[old_yoffset + old_x];
}
}
dirty = true;
fullUploadRequired = true;
// Mark all sprite images as in need of update
for (const auto &pair : images) {
uninitialized.emplace(pair.first);
}
}
return dirty;
}
Rect<SpriteAtlas::dimension> SpriteAtlas::allocateImage(const size_t pixel_width, const size_t pixel_height) {
// Increase to next number divisible by 4, but at least 1.
// This is so we can scale down the texture coordinates and pack them
@@ -86,17 +48,17 @@ Rect<SpriteAtlas::dimension> SpriteAtlas::allocateImage(const size_t pixel_width
return rect;
}
Rect<SpriteAtlas::dimension> SpriteAtlas::getImage(const std::string& name, const bool wrap) {
SpriteAtlasElement SpriteAtlas::getImage(const std::string& name, const bool wrap) {
std::lock_guard<std::recursive_mutex> lock(mtx);
auto rect_it = images.find(name);
if (rect_it != images.end()) {
return rect_it->second.pos;
return { rect_it->second.pos, rect_it->second.texture };
}
auto sprite = store.getSprite(name);
if (!sprite) {
return Rect<dimension> { 0, 0, 0, 0 };
return { Rect<dimension> { 0, 0, 0, 0 }, nullptr };
}
Rect<dimension> rect = allocateImage(sprite->width, sprite->height);
@@ -104,19 +66,19 @@ Rect<SpriteAtlas::dimension> SpriteAtlas::getImage(const std::string& name, cons
if (debug::spriteWarnings) {
Log::Warning(Event::Sprite, "sprite atlas bitmap overflow");
}
return rect;
return { Rect<dimension> { 0, 0, 0, 0 }, nullptr };
}
const Holder& holder = images.emplace(name, Holder{ sprite, rect }).first->second;
copy(holder, wrap);
return rect;
return { rect, sprite };
}
SpriteAtlasPosition SpriteAtlas::getPosition(const std::string& name, bool repeating) {
std::lock_guard<std::recursive_mutex> lock(mtx);
Rect<dimension> rect = getImage(name, repeating);
auto rect = getImage(name, repeating).pos;
if (repeating) {
// When the image is repeating, get the correct position of the image, rather than the
// one rounded up to 4 pixels.
@@ -202,35 +164,6 @@ void SpriteAtlas::copy(const Holder& holder, const bool wrap) {
dirty = true;
}
// void SpriteAtlas::setSprite(util::ptr<Sprite> sprite_) {
// std::lock_guard<std::recursive_mutex> lock(mtx);
// sprite = sprite_;
// if (!sprite->isLoaded()) return;
// util::erase_if(uninitialized, [this](const std::string &name) {
// Rect<dimension> dst = getImage(name, false);
// const SpritePosition& src = sprite->getSpritePosition(name);
// if (!src) {
// if (debug::spriteWarnings) {
// Log::Warning(Event::Sprite, "sprite doesn't have image with name '%s'", name.c_str());
// }
// return true;
// }
// if (src.width == dst.w * pixelRatio && src.height == dst.h * pixelRatio && src.pixelRatio == pixelRatio) {
// copy(dst, src, false);
// return true;
// } else {
// if (debug::spriteWarnings) {
// Log::Warning(Event::Sprite, "sprite icon dimension mismatch");
// }
// return false;
// }
// });
// }
void SpriteAtlas::upload() {
if (dirty) {
bind();
@@ -294,7 +227,9 @@ void SpriteAtlas::bind(bool linear) {
dirty = false;
#ifndef GL_ES_VERSION_2_0
// platform::showColorDebugImage("Sprite Atlas", reinterpret_cast<const char *>(data), width * pixelRatio, height * pixelRatio, width * pixelRatio, height * pixelRatio);
// platform::showColorDebugImage("Sprite Atlas", reinterpret_cast<const char*>(data.get()),
// width * pixelRatio, height * pixelRatio, width * pixelRatio,
// height * pixelRatio);
#endif
}
};
@@ -306,3 +241,11 @@ SpriteAtlas::~SpriteAtlas() {
texture = 0;
}
}
SpriteAtlas::Holder::Holder(const std::shared_ptr<const SpriteImage>& texture_,
const Rect<dimension>& pos_)
: texture(texture_), pos(pos_) {
}
SpriteAtlas::Holder::Holder(Holder&& h) : texture(std::move(h.texture)), pos(h.pos) {
}

View File

@@ -29,6 +29,11 @@ struct SpriteAtlasPosition {
std::array<float, 2> br;
};
struct SpriteAtlasElement {
const Rect<uint16_t> pos;
const std::shared_ptr<const SpriteImage> texture;
};
class SpriteAtlas : public util::noncopyable {
public:
typedef uint16_t dimension;
@@ -37,15 +42,12 @@ public:
SpriteAtlas(dimension width, dimension height, SpriteStore& store);
~SpriteAtlas();
// Changes the pixel ratio.
bool resize(float newRatio);
// Returns the coordinates of an image that is sourced from the sprite image.
// This getter attempts to read the image from the sprite if it is already loaded.
// In that case, it copies it into the sprite atlas and returns the dimensions.
// Otherwise, it returns a 0/0/0/0 rect.
// This function is used during bucket creation.
Rect<dimension> getImage(const std::string& name, const bool wrap);
SpriteAtlasElement getImage(const std::string& name, const bool wrap);
// This function is used for getting the position during render time.
SpriteAtlasPosition getPosition(const std::string& name, bool repeating = false);
@@ -68,10 +70,9 @@ public:
private:
struct Holder : private util::noncopyable {
Holder(const std::shared_ptr<const SpriteImage>&, const Rect<dimension>&);
Holder(Holder&&);
inline Holder(const std::shared_ptr<const SpriteImage>&, const Rect<dimension>&);
inline Holder(Holder&&);
std::shared_ptr<const SpriteImage> texture;
std::set<uintptr_t> references;
const Rect<dimension> pos;
};
@@ -80,9 +81,9 @@ private:
void copy(const Holder& holder, const bool wrap);
std::recursive_mutex mtx;
float pixelRatio = 1.0f;
BinPack<dimension> bin;
SpriteStore& store;
const float pixelRatio;
BinPack<dimension> bin;
std::map<std::string, Holder> images;
std::set<std::string> uninitialized;
std::unique_ptr<uint32_t[]> data;

View File

@@ -3,6 +3,7 @@
#include <mbgl/map/view.hpp>
#include <mbgl/map/still_image.hpp>
#include <mbgl/map/annotation.hpp>
#include <mbgl/annotation/sprite_store.hpp>
#include <mbgl/platform/log.hpp>
@@ -124,7 +125,7 @@ void MapContext::loadStyleJSON(const std::string& json, const std::string& base)
assert(util::ThreadContext::currentlyOn(util::ThreadType::Map));
style.reset();
style = std::make_unique<Style>(json, base, asyncUpdate->get()->loop);
style = std::make_unique<Style>(json, base, data, asyncUpdate->get()->loop);
style->cascade(data.getClasses());
style->setDefaultTransitionDuration(data.getDefaultTransitionDuration());
style->setObserver(this);
@@ -257,7 +258,7 @@ void MapContext::update() {
style->recalculate(transformState.getNormalizedZoom(), now);
}
style->update(data, transformState, *texturePool);
style->update(transformState, *texturePool);
if (data.mode == MapMode::Continuous) {
view.invalidate();
@@ -343,8 +344,12 @@ bool MapContext::isLoaded() const {
double MapContext::getTopOffsetPixelsForAnnotationSymbol(const std::string& symbol) {
assert(util::ThreadContext::currentlyOn(util::ThreadType::Map));
const SpritePosition pos = style->sprite->getSpritePosition(symbol);
return -pos.height / pos.pixelRatio / 2;
auto sprite = style->spriteStore->getSprite(symbol);
if (sprite) {
return -sprite->height / 2;
} else {
return 0;
}
}
void MapContext::setSourceTileCacheSize(size_t size) {

View File

@@ -17,20 +17,8 @@
using namespace mbgl;
SpritePosition::SpritePosition(uint16_t x_, uint16_t y_, uint16_t width_, uint16_t height_, float pixelRatio_, bool sdf_)
: x(x_),
y(y_),
width(width_),
height(height_),
pixelRatio(pixelRatio_),
sdf(sdf_) {
}
Sprite::Sprite(const std::string& baseUrl, float pixelRatio_)
: pixelRatio(pixelRatio_ > 1 ? 2 : 1),
raster(),
loadedImage(false),
loadedJSON(false) {
: pixelRatio(pixelRatio_ > 1 ? 2 : 1) {
if (baseUrl.empty()) {
// Treat a non-existent sprite as a successfully loaded empty sprite.
loadedImage = true;
@@ -45,8 +33,8 @@ Sprite::Sprite(const std::string& baseUrl, float pixelRatio_)
jsonRequest = fs->request({ Resource::Kind::JSON, jsonURL }, util::RunLoop::getLoop(), [this, jsonURL](const Response &res) {
jsonRequest = nullptr;
if (res.status == Response::Successful) {
body = res.data;
parseJSON(jsonURL);
json = res.data;
loadedJSON = true;
} else {
std::stringstream message;
message << "Failed to load [" << jsonURL << "]: " << res.message;
@@ -60,7 +48,7 @@ Sprite::Sprite(const std::string& baseUrl, float pixelRatio_)
spriteRequest = nullptr;
if (res.status == Response::Successful) {
image = res.data;
parseImage(spriteURL);
loadedImage = true;
} else {
std::stringstream message;
message << "Failed to load [" << spriteURL << "]: " << res.message;
@@ -100,69 +88,6 @@ bool Sprite::isLoaded() const {
return loadedImage && loadedJSON;
}
bool Sprite::hasPixelRatio(float ratio) const {
return pixelRatio == (ratio > 1 ? 2 : 1);
}
void Sprite::parseImage(const std::string& spriteURL) {
raster = std::make_unique<util::Image>(image);
if (!*raster) {
raster.reset();
std::stringstream message;
message << "Failed to parse [" << spriteURL << "]";
emitSpriteLoadingFailed(message.str());
return;
}
image.clear();
loadedImage = true;
}
void Sprite::parseJSON(const std::string& jsonURL) {
rapidjson::Document d;
d.Parse<0>(body.c_str());
body.clear();
if (d.HasParseError()) {
std::stringstream message;
message << "Failed to parse [" << jsonURL << "]: " << d.GetErrorOffset() << " - " << d.GetParseError();
emitSpriteLoadingFailed(message.str());
} else if (d.IsObject()) {
for (rapidjson::Value::ConstMemberIterator itr = d.MemberBegin(); itr != d.MemberEnd(); ++itr) {
const std::string name = { itr->name.GetString(), itr->name.GetStringLength() };
const rapidjson::Value& value = itr->value;
if (value.IsObject()) {
uint16_t x = 0;
uint16_t y = 0;
uint16_t width = 0;
uint16_t height = 0;
float spritePixelRatio = 1.0f;
bool sdf = false;
if (value.HasMember("x")) x = value["x"].GetInt();
if (value.HasMember("y")) y = value["y"].GetInt();
if (value.HasMember("width")) width = value["width"].GetInt();
if (value.HasMember("height")) height = value["height"].GetInt();
if (value.HasMember("pixelRatio")) spritePixelRatio = value["pixelRatio"].GetInt();
if (value.HasMember("sdf")) sdf = value["sdf"].GetBool();
pos.emplace(name, SpritePosition { x, y, width, height, spritePixelRatio, sdf });
}
}
loadedJSON = true;
} else {
std::stringstream message;
message << "Failed to parse [" << jsonURL << "]: Root is not an object";
emitSpriteLoadingFailed(message.str());
}
}
const SpritePosition &Sprite::getSpritePosition(const std::string& name) const {
if (!isLoaded()) return empty;
auto it = pos.find(name);
return it == pos.end() ? empty : it->second;
}
void Sprite::setObserver(Observer* observer_) {
observer = observer_;
}

View File

@@ -16,21 +16,6 @@ namespace mbgl {
class Request;
class SpritePosition {
public:
explicit SpritePosition() {}
explicit SpritePosition(uint16_t x, uint16_t y, uint16_t width, uint16_t height, float pixelRatio, bool sdf);
operator bool() const {
return !(width == 0 && height == 0 && x == 0 && y == 0);
}
uint16_t x = 0, y = 0;
uint16_t width = 0, height = 0;
float pixelRatio = 1.0f;
bool sdf = false;
};
class Sprite : private util::noncopyable {
public:
class Observer {
@@ -44,29 +29,24 @@ public:
Sprite(const std::string& baseUrl, float pixelRatio);
~Sprite();
const SpritePosition &getSpritePosition(const std::string& name) const;
bool hasPixelRatio(float ratio) const;
bool isLoaded() const;
const float pixelRatio;
std::unique_ptr<util::Image> raster;
void setObserver(Observer* observer);
inline const std::string& getImage() const { return image; }
inline const std::string& getJSON() const { return json; }
private:
void emitSpriteLoadedIfComplete();
void emitSpriteLoadingFailed(const std::string& message);
void parseJSON(const std::string& jsonURL);
void parseImage(const std::string& spriteURL);
bool loadedJSON = false;
std::string json;
std::string body;
bool loadedImage = false;
std::string image;
std::atomic<bool> loadedImage;
std::atomic<bool> loadedJSON;
std::unordered_map<std::string, SpritePosition> pos;
const SpritePosition empty;
Request* jsonRequest = nullptr;
Request* spriteRequest = nullptr;

View File

@@ -265,8 +265,8 @@ std::unique_ptr<Bucket> TileWorker::createSymbolBucket(const GeometryTileLayer&
return nullptr;
}
bucket->addFeatures(
reinterpret_cast<uintptr_t>(this), *style.spriteAtlas, *style.sprite, *style.glyphAtlas, *style.glyphStore);
bucket->addFeatures(reinterpret_cast<uintptr_t>(this), *style.spriteAtlas, *style.glyphAtlas,
*style.glyphStore);
return bucket->hasData() ? std::move(bucket) : nullptr;
}

View File

@@ -1,6 +1,7 @@
#include <mbgl/renderer/symbol_bucket.hpp>
#include <mbgl/map/geometry_tile.hpp>
#include <mbgl/style/style_layout.hpp>
#include <mbgl/annotation/sprite_image.hpp>
#include <mbgl/geometry/text_buffer.hpp>
#include <mbgl/geometry/icon_buffer.hpp>
#include <mbgl/geometry/glyph_atlas.hpp>
@@ -172,7 +173,6 @@ bool SymbolBucket::needsDependencies(const GeometryTileLayer& layer,
void SymbolBucket::addFeatures(uintptr_t tileUID,
SpriteAtlas& spriteAtlas,
Sprite& sprite,
GlyphAtlas& glyphAtlas,
GlyphStore& glyphStore) {
float horizontalAlign = 0.5;
@@ -246,11 +246,13 @@ void SymbolBucket::addFeatures(uintptr_t tileUID,
// if feature has icon, get sprite atlas position
if (feature.sprite.length()) {
Rect<uint16_t> image = spriteAtlas.getImage(feature.sprite, false);
shapedIcon = shapeIcon(image, layout);
if (sprite.getSpritePosition(feature.sprite).sdf) {
sdfIcons = true;
auto image = spriteAtlas.getImage(feature.sprite, false);
if (image.pos.hasArea() && image.texture) {
shapedIcon = shapeIcon(image.pos, layout);
assert(image.texture);
if (image.texture->sdf) {
sdfIcons = true;
}
}
}

View File

@@ -76,7 +76,6 @@ public:
void addFeatures(uintptr_t tileUID,
SpriteAtlas&,
Sprite&,
GlyphAtlas&,
GlyphStore&);

View File

@@ -3,6 +3,8 @@
#include <mbgl/map/map_data.hpp>
#include <mbgl/map/source.hpp>
#include <mbgl/map/transform_state.hpp>
#include <mbgl/annotation/sprite_store.hpp>
#include <mbgl/annotation/sprite_parser.hpp>
#include <mbgl/style/style_layer.hpp>
#include <mbgl/style/style_parser.hpp>
#include <mbgl/style/style_bucket.hpp>
@@ -20,17 +22,19 @@
namespace mbgl {
Style::Style(const std::string& data, const std::string&,
Style::Style(const std::string& json, const std::string&, MapData& data_,
uv_loop_t* loop)
: glyphStore(std::make_unique<GlyphStore>(loop)),
: data(data_),
glyphStore(std::make_unique<GlyphStore>(loop)),
glyphAtlas(std::make_unique<GlyphAtlas>(1024, 1024)),
spriteStore(std::make_unique<SpriteStore>(data.pixelRatio)),
spriteAtlas(std::make_unique<SpriteAtlas>(512, 512, *spriteStore)),
lineAtlas(std::make_unique<LineAtlas>(512, 512)),
mtx(std::make_unique<uv::rwlock>()),
workers(4) {
rapidjson::Document doc;
doc.Parse<0>((const char *const)data.c_str());
doc.Parse<0>((const char *const)json.c_str());
if (doc.HasParseError()) {
Log::Error(Event::ParseStyle, "Error parsing style JSON at %i: %s", doc.GetErrorOffset(), doc.GetParseError());
return;
@@ -42,7 +46,9 @@ Style::Style(const std::string& data, const std::string&,
sources = parser.getSources();
layers = parser.getLayers();
spriteURL = parser.getSprite();
sprite = std::make_unique<Sprite>(parser.getSprite(), data.pixelRatio);
sprite->setObserver(this);
glyphStore->setURL(parser.getGlyphURL());
for (const auto& source : sources) {
@@ -65,17 +71,8 @@ Style::~Style() {
}
}
void Style::update(MapData& data,
const TransformState& transform,
void Style::update(const TransformState& transform,
TexturePool& texturePool) {
if (!sprite || !sprite->hasPixelRatio(data.pixelRatio)) {
sprite = std::make_unique<Sprite>(spriteURL, data.pixelRatio);
sprite->setObserver(this);
spriteAtlas->resize(data.pixelRatio);
spriteAtlas->setSprite(sprite);
}
bool allTilesUpdated = true;
for (const auto& source : sources) {
if (!source->update(data, transform, *this, texturePool, shouldReparsePartialTiles)) {
@@ -195,6 +192,9 @@ void Style::onTileLoadingFailed(std::exception_ptr error) {
}
void Style::onSpriteLoaded() {
// Add all sprite images to the SpriteStore object
spriteStore->setSprites(parseSprite(sprite->getImage(), sprite->getJSON()));
shouldReparsePartialTiles = true;
emitTileDataChanged();

View File

@@ -22,6 +22,7 @@ namespace mbgl {
class GlyphAtlas;
class GlyphStore;
class SpriteStore;
class SpriteAtlas;
class LineAtlas;
class StyleLayer;
@@ -33,6 +34,7 @@ class Style : public GlyphStore::Observer,
public:
Style(const std::string& data,
const std::string& base,
MapData&,
uv_loop_t*);
~Style();
@@ -50,7 +52,7 @@ public:
// Fetch the tiles needed by the current viewport and emit a signal when
// a tile is ready so observers can render the tile.
void update(MapData&, const TransformState&, TexturePool&);
void update(const TransformState&, TexturePool&);
void cascade(const std::vector<std::string>&);
void recalculate(float z, TimePoint now);
@@ -64,9 +66,11 @@ public:
Source* getSource(const std::string& id) const;
MapData& data;
std::unique_ptr<GlyphStore> glyphStore;
std::unique_ptr<GlyphAtlas> glyphAtlas;
util::ptr<Sprite> sprite;
std::unique_ptr<SpriteStore> spriteStore;
std::unique_ptr<SpriteAtlas> spriteAtlas;
std::unique_ptr<LineAtlas> lineAtlas;
@@ -97,7 +101,6 @@ private:
std::exception_ptr lastError;
std::string spriteURL;
PropertyTransition defaultTransition;
std::unique_ptr<uv::rwlock> mtx;
ZoomHistory zoomHistory;

View File

@@ -32,7 +32,7 @@ public:
transform_.setLatLngZoom({0, 0}, 16);
const std::string style = util::read_file("test/fixtures/resources/style.json");
style_ = std::make_unique<Style>(style, "", util::RunLoop::getLoop());
style_ = std::make_unique<Style>(style, "", data_, util::RunLoop::getLoop());
style_->setObserver(this);
}
@@ -46,7 +46,7 @@ public:
data_.setAnimationTime(now);
transform_.updateTransitions(now);
style_->update(data_, transform_.getState(), texturePool_);
style_->update(transform_.getState(), texturePool_);
}
// Style::Observer implementation.