[core] Merge SpriteStore and SpriteAtlas
This commit is contained in:
@@ -210,12 +210,10 @@ set(MBGL_CORE_FILES
|
||||
include/mbgl/sprite/sprite_image.hpp
|
||||
src/mbgl/sprite/sprite_atlas.cpp
|
||||
src/mbgl/sprite/sprite_atlas.hpp
|
||||
src/mbgl/sprite/sprite_atlas_observer.hpp
|
||||
src/mbgl/sprite/sprite_image.cpp
|
||||
src/mbgl/sprite/sprite_parser.cpp
|
||||
src/mbgl/sprite/sprite_parser.hpp
|
||||
src/mbgl/sprite/sprite_store.cpp
|
||||
src/mbgl/sprite/sprite_store.hpp
|
||||
src/mbgl/sprite/sprite_store_observer.hpp
|
||||
|
||||
# storage
|
||||
include/mbgl/storage/default_file_source.hpp
|
||||
|
||||
@@ -40,7 +40,6 @@ set(MBGL_TEST_FILES
|
||||
test/sprite/sprite_atlas.cpp
|
||||
test/sprite/sprite_image.cpp
|
||||
test/sprite/sprite_parser.cpp
|
||||
test/sprite/sprite_store.cpp
|
||||
|
||||
# src
|
||||
test/src/main.cpp
|
||||
@@ -81,7 +80,6 @@ set(MBGL_TEST_FILES
|
||||
test/style/style_layer.cpp
|
||||
test/style/style_parser.cpp
|
||||
test/style/tile_source.cpp
|
||||
test/style/variant.cpp
|
||||
|
||||
# text
|
||||
test/text/quads.cpp
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <mbgl/style/style.hpp>
|
||||
#include <mbgl/style/layers/symbol_layer.hpp>
|
||||
#include <mbgl/style/layers/symbol_layer_impl.hpp>
|
||||
#include <mbgl/storage/file_source.hpp>
|
||||
|
||||
#include <boost/function_output_iterator.hpp>
|
||||
|
||||
@@ -19,8 +20,20 @@ const std::string AnnotationManager::SourceID = "com.mapbox.annotations";
|
||||
const std::string AnnotationManager::PointLayerID = "com.mapbox.annotations.points";
|
||||
|
||||
AnnotationManager::AnnotationManager(float pixelRatio)
|
||||
: spriteStore(pixelRatio),
|
||||
spriteAtlas(1024, 1024, pixelRatio, spriteStore) {
|
||||
: spriteAtlas(1024, 1024, pixelRatio) {
|
||||
|
||||
struct NullFileSource : public FileSource {
|
||||
std::unique_ptr<AsyncRequest> request(const Resource&, Callback) override {
|
||||
assert(false);
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
NullFileSource nullFileSource;
|
||||
|
||||
// This is a special atlas, holding only images added via addIcon. But we need its isLoaded()
|
||||
// method to return true.
|
||||
spriteAtlas.load("", nullFileSource);
|
||||
}
|
||||
|
||||
AnnotationManager::~AnnotationManager() = default;
|
||||
@@ -187,17 +200,17 @@ void AnnotationManager::removeTile(AnnotationTile& tile) {
|
||||
}
|
||||
|
||||
void AnnotationManager::addIcon(const std::string& name, std::shared_ptr<const SpriteImage> sprite) {
|
||||
spriteStore.setSprite(name, sprite);
|
||||
spriteAtlas.setSprite(name, sprite);
|
||||
spriteAtlas.updateDirty();
|
||||
}
|
||||
|
||||
void AnnotationManager::removeIcon(const std::string& name) {
|
||||
spriteStore.removeSprite(name);
|
||||
spriteAtlas.removeSprite(name);
|
||||
spriteAtlas.updateDirty();
|
||||
}
|
||||
|
||||
double AnnotationManager::getTopOffsetPixelsForIcon(const std::string& name) {
|
||||
auto sprite = spriteStore.getSprite(name);
|
||||
auto sprite = spriteAtlas.getSprite(name);
|
||||
return sprite ? -(sprite->image.height / sprite->pixelRatio) / 2 : 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include <mbgl/annotation/annotation.hpp>
|
||||
#include <mbgl/annotation/symbol_annotation_impl.hpp>
|
||||
#include <mbgl/sprite/sprite_store.hpp>
|
||||
#include <mbgl/sprite/sprite_atlas.hpp>
|
||||
#include <mbgl/map/update.hpp>
|
||||
#include <mbgl/util/noncopyable.hpp>
|
||||
@@ -75,8 +74,6 @@ private:
|
||||
ShapeAnnotationMap shapeAnnotations;
|
||||
std::set<std::string> obsoleteShapeAnnotationLayers;
|
||||
std::set<AnnotationTile*> tiles;
|
||||
|
||||
SpriteStore spriteStore;
|
||||
SpriteAtlas spriteAtlas;
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <mbgl/layout/clip_lines.hpp>
|
||||
#include <mbgl/renderer/symbol_bucket.hpp>
|
||||
#include <mbgl/style/filter_evaluator.hpp>
|
||||
#include <mbgl/sprite/sprite_store.hpp>
|
||||
#include <mbgl/sprite/sprite_atlas.hpp>
|
||||
#include <mbgl/geometry/glyph_atlas.hpp>
|
||||
#include <mbgl/text/get_anchors.hpp>
|
||||
@@ -125,12 +124,12 @@ bool SymbolLayout::hasSymbolInstances() const {
|
||||
return !symbolInstances.empty();
|
||||
}
|
||||
|
||||
bool SymbolLayout::canPrepare(GlyphStore& glyphStore, SpriteStore& spriteStore) {
|
||||
bool SymbolLayout::canPrepare(GlyphStore& glyphStore) {
|
||||
if (!layout.textField.value.empty() && !layout.textFont.value.empty() && !glyphStore.hasGlyphRanges(layout.textFont, ranges)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!layout.iconImage.value.empty() && !spriteStore.isLoaded()) {
|
||||
if (!layout.iconImage.value.empty() && !spriteAtlas.isLoaded()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ namespace mbgl {
|
||||
class GeometryTileLayer;
|
||||
class CollisionTile;
|
||||
class SpriteAtlas;
|
||||
class SpriteStore;
|
||||
class GlyphAtlas;
|
||||
class GlyphStore;
|
||||
class SymbolBucket;
|
||||
@@ -39,7 +38,7 @@ public:
|
||||
float textMaxSize,
|
||||
SpriteAtlas&);
|
||||
|
||||
bool canPrepare(GlyphStore&, SpriteStore&);
|
||||
bool canPrepare(GlyphStore&);
|
||||
|
||||
void prepare(uintptr_t tileUID,
|
||||
GlyphAtlas&,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <mbgl/sprite/sprite_atlas.hpp>
|
||||
#include <mbgl/sprite/sprite_store.hpp>
|
||||
#include <mbgl/sprite/sprite_atlas_observer.hpp>
|
||||
#include <mbgl/sprite/sprite_parser.hpp>
|
||||
#include <mbgl/gl/gl.hpp>
|
||||
#include <mbgl/gl/gl_config.hpp>
|
||||
#include <mbgl/platform/log.hpp>
|
||||
@@ -7,6 +8,10 @@
|
||||
#include <mbgl/util/math.hpp>
|
||||
#include <mbgl/util/std.hpp>
|
||||
#include <mbgl/util/constants.hpp>
|
||||
#include <mbgl/util/exception.hpp>
|
||||
#include <mbgl/storage/file_source.hpp>
|
||||
#include <mbgl/storage/resource.hpp>
|
||||
#include <mbgl/storage/response.hpp>
|
||||
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
@@ -14,15 +19,147 @@
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
SpriteAtlas::SpriteAtlas(dimension width_, dimension height_, float pixelRatio_, SpriteStore& store_)
|
||||
static SpriteAtlasObserver nullObserver;
|
||||
|
||||
struct SpriteAtlas::Loader {
|
||||
std::shared_ptr<const std::string> image;
|
||||
std::shared_ptr<const std::string> json;
|
||||
std::unique_ptr<AsyncRequest> jsonRequest;
|
||||
std::unique_ptr<AsyncRequest> spriteRequest;
|
||||
};
|
||||
|
||||
SpriteAtlas::SpriteAtlas(dimension width_, dimension height_, float pixelRatio_)
|
||||
: width(width_),
|
||||
height(height_),
|
||||
pixelWidth(std::ceil(width * pixelRatio_)),
|
||||
pixelHeight(std::ceil(height * pixelRatio_)),
|
||||
pixelRatio(pixelRatio_),
|
||||
store(store_),
|
||||
observer(&nullObserver),
|
||||
bin(width_, height_),
|
||||
dirty(true) {
|
||||
dirtyFlag(true) {
|
||||
}
|
||||
|
||||
SpriteAtlas::~SpriteAtlas() = default;
|
||||
|
||||
void SpriteAtlas::load(const std::string& url, FileSource& fileSource) {
|
||||
if (url.empty()) {
|
||||
// Treat a non-existent sprite as a successfully loaded empty sprite.
|
||||
loaded = true;
|
||||
return;
|
||||
}
|
||||
|
||||
loader = std::make_unique<Loader>();
|
||||
|
||||
loader->jsonRequest = fileSource.request(Resource::spriteJSON(url, pixelRatio), [this](Response res) {
|
||||
if (res.error) {
|
||||
observer->onSpriteError(std::make_exception_ptr(std::runtime_error(res.error->message)));
|
||||
} else if (res.notModified) {
|
||||
return;
|
||||
} else if (res.noContent) {
|
||||
loader->json = std::make_shared<const std::string>();
|
||||
emitSpriteLoadedIfComplete();
|
||||
} else {
|
||||
// Only trigger a sprite loaded event we got new data.
|
||||
loader->json = res.data;
|
||||
emitSpriteLoadedIfComplete();
|
||||
}
|
||||
});
|
||||
|
||||
loader->spriteRequest = fileSource.request(Resource::spriteImage(url, pixelRatio), [this](Response res) {
|
||||
if (res.error) {
|
||||
observer->onSpriteError(std::make_exception_ptr(std::runtime_error(res.error->message)));
|
||||
} else if (res.notModified) {
|
||||
return;
|
||||
} else if (res.noContent) {
|
||||
loader->image = std::make_shared<const std::string>();
|
||||
emitSpriteLoadedIfComplete();
|
||||
} else {
|
||||
loader->image = res.data;
|
||||
emitSpriteLoadedIfComplete();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SpriteAtlas::emitSpriteLoadedIfComplete() {
|
||||
assert(loader);
|
||||
|
||||
if (!loader->image || !loader->json) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto result = parseSprite(*loader->image, *loader->json);
|
||||
if (result.is<Sprites>()) {
|
||||
loaded = true;
|
||||
setSprites(result.get<Sprites>());
|
||||
observer->onSpriteLoaded();
|
||||
} else {
|
||||
observer->onSpriteError(result.get<std::exception_ptr>());
|
||||
}
|
||||
}
|
||||
|
||||
void SpriteAtlas::setObserver(SpriteAtlasObserver* observer_) {
|
||||
observer = observer_;
|
||||
}
|
||||
|
||||
void SpriteAtlas::dumpDebugLogs() const {
|
||||
Log::Info(Event::General, "SpriteAtlas::loaded: %d", loaded);
|
||||
}
|
||||
|
||||
void SpriteAtlas::setSprites(const Sprites& newSprites) {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
for (const auto& pair : newSprites) {
|
||||
_setSprite(pair.first, pair.second);
|
||||
}
|
||||
}
|
||||
|
||||
void SpriteAtlas::setSprite(const std::string& name, std::shared_ptr<const SpriteImage> sprite) {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
_setSprite(name, sprite);
|
||||
}
|
||||
|
||||
void SpriteAtlas::removeSprite(const std::string& name) {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
_setSprite(name);
|
||||
}
|
||||
|
||||
void SpriteAtlas::_setSprite(const std::string& name,
|
||||
const std::shared_ptr<const SpriteImage>& sprite) {
|
||||
if (sprite) {
|
||||
auto it = sprites.find(name);
|
||||
if (it != sprites.end()) {
|
||||
// There is already a sprite with that name in our store.
|
||||
if ((it->second->image.width != sprite->image.width || it->second->image.height != sprite->image.height)) {
|
||||
Log::Warning(Event::Sprite, "Can't change sprite dimensions for '%s'", name.c_str());
|
||||
return;
|
||||
}
|
||||
it->second = sprite;
|
||||
} else {
|
||||
sprites.emplace(name, sprite);
|
||||
}
|
||||
|
||||
// Always add/replace the value in the dirty list.
|
||||
auto dirty_it = dirtySprites.find(name);
|
||||
if (dirty_it != dirtySprites.end()) {
|
||||
dirty_it->second = sprite;
|
||||
} else {
|
||||
dirtySprites.emplace(name, sprite);
|
||||
}
|
||||
} else if (sprites.erase(name) > 0) {
|
||||
dirtySprites.emplace(name, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<const SpriteImage> SpriteAtlas::getSprite(const std::string& name) {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
const auto it = sprites.find(name);
|
||||
if (it != sprites.end()) {
|
||||
return it->second;
|
||||
} else {
|
||||
if (!sprites.empty()) {
|
||||
Log::Info(Event::Sprite, "Can't find sprite named '%s'", name.c_str());
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Rect<SpriteAtlas::dimension> SpriteAtlas::allocateImage(const SpriteImage& spriteImage) {
|
||||
@@ -55,7 +192,7 @@ optional<SpriteAtlasElement> SpriteAtlas::getImage(const std::string& name,
|
||||
return SpriteAtlasElement { rect_it->second.pos, rect_it->second.spriteImage, rect_it->second.spriteImage->pixelRatio / pixelRatio };
|
||||
}
|
||||
|
||||
auto sprite = store.getSprite(name);
|
||||
auto sprite = getSprite(name);
|
||||
if (!sprite) {
|
||||
return {};
|
||||
}
|
||||
@@ -142,21 +279,16 @@ void SpriteAtlas::copy(const Holder& holder, const SpritePatternMode mode) {
|
||||
dstData, pixelWidth, (holder.pos.x + padding) * pixelRatio, (holder.pos.y + padding) * pixelRatio, pixelWidth * pixelHeight,
|
||||
uint32_t(holder.spriteImage->image.width), uint32_t(holder.spriteImage->image.height), mode);
|
||||
|
||||
dirty = true;
|
||||
dirtyFlag = true;
|
||||
}
|
||||
|
||||
void SpriteAtlas::upload(gl::ObjectStore& objectStore, gl::Config& config, uint32_t unit) {
|
||||
if (dirty) {
|
||||
if (dirtyFlag) {
|
||||
bind(false, objectStore, config, unit);
|
||||
}
|
||||
}
|
||||
|
||||
void SpriteAtlas::updateDirty() {
|
||||
auto dirtySprites = store.getDirty();
|
||||
if (dirtySprites.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lock(mtx);
|
||||
|
||||
auto imageIterator = images.begin();
|
||||
@@ -180,6 +312,8 @@ void SpriteAtlas::updateDirty() {
|
||||
// name, but a different wrap value.
|
||||
}
|
||||
}
|
||||
|
||||
dirtySprites.clear();
|
||||
}
|
||||
|
||||
void SpriteAtlas::bind(bool linear, gl::ObjectStore& objectStore, gl::Config& config, uint32_t unit) {
|
||||
@@ -212,7 +346,7 @@ void SpriteAtlas::bind(bool linear, gl::ObjectStore& objectStore, gl::Config& co
|
||||
filter = filter_val;
|
||||
}
|
||||
|
||||
if (dirty) {
|
||||
if (dirtyFlag) {
|
||||
std::lock_guard<std::recursive_mutex> lock(mtx);
|
||||
|
||||
config.activeTexture = unit;
|
||||
@@ -243,7 +377,7 @@ void SpriteAtlas::bind(bool linear, gl::ObjectStore& objectStore, gl::Config& co
|
||||
));
|
||||
}
|
||||
|
||||
dirty = false;
|
||||
dirtyFlag = false;
|
||||
|
||||
#ifndef GL_ES_VERSION_2_0
|
||||
// platform::showColorDebugImage("Sprite Atlas", reinterpret_cast<const char*>(data.get()),
|
||||
@@ -252,8 +386,6 @@ void SpriteAtlas::bind(bool linear, gl::ObjectStore& objectStore, gl::Config& co
|
||||
}
|
||||
}
|
||||
|
||||
SpriteAtlas::~SpriteAtlas() = default;
|
||||
|
||||
SpriteAtlas::Holder::Holder(std::shared_ptr<const SpriteImage> spriteImage_, Rect<dimension> pos_)
|
||||
: spriteImage(std::move(spriteImage_)), pos(std::move(pos_)) {
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <mbgl/gl/object_store.hpp>
|
||||
#include <mbgl/util/noncopyable.hpp>
|
||||
#include <mbgl/util/optional.hpp>
|
||||
#include <mbgl/sprite/sprite_image.hpp>
|
||||
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
@@ -12,14 +13,17 @@
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
class FileSource;
|
||||
class SpriteAtlasObserver;
|
||||
|
||||
namespace gl {
|
||||
class Config;
|
||||
} // namespace gl
|
||||
|
||||
class SpriteStore;
|
||||
class SpriteImage;
|
||||
class SpritePosition;
|
||||
|
||||
@@ -43,10 +47,33 @@ enum class SpritePatternMode : bool {
|
||||
class SpriteAtlas : public util::noncopyable {
|
||||
public:
|
||||
typedef uint16_t dimension;
|
||||
using Sprites = std::map<std::string, std::shared_ptr<const SpriteImage>>;
|
||||
|
||||
SpriteAtlas(dimension width, dimension height, float pixelRatio, SpriteStore& store);
|
||||
SpriteAtlas(dimension width, dimension height, float pixelRatio);
|
||||
~SpriteAtlas();
|
||||
|
||||
void load(const std::string& url, FileSource&);
|
||||
|
||||
bool isLoaded() const {
|
||||
return loaded;
|
||||
}
|
||||
|
||||
void dumpDebugLogs() const;
|
||||
|
||||
void setObserver(SpriteAtlasObserver*);
|
||||
|
||||
// Adds/replaces a Sprite image.
|
||||
void setSprite(const std::string&, std::shared_ptr<const SpriteImage>);
|
||||
|
||||
// Adds/replaces mutliple Sprite images.
|
||||
void setSprites(const Sprites& sprites);
|
||||
|
||||
// Removes a Sprite.
|
||||
void removeSprite(const std::string&);
|
||||
|
||||
// Obtains a Sprite image.
|
||||
std::shared_ptr<const SpriteImage> getSprite(const std::string&);
|
||||
|
||||
// If the sprite is loaded, copies the requsted image from it into the atlas and returns
|
||||
// the resulting icon measurements. If not, returns an empty optional.
|
||||
optional<SpriteAtlasElement> getImage(const std::string& name, SpritePatternMode mode);
|
||||
@@ -58,7 +85,7 @@ public:
|
||||
// Binds the atlas texture to the GPU, and uploads data if it is out of date.
|
||||
void bind(bool linear, gl::ObjectStore&, gl::Config&, uint32_t unit);
|
||||
|
||||
// Updates sprites in the atlas texture that may have changed in the source SpriteStore object.
|
||||
// Updates sprites in the atlas texture that may have changed.
|
||||
void updateDirty();
|
||||
|
||||
// Uploads the texture to the GPU to be available when we need it. This is a lazy operation;
|
||||
@@ -75,10 +102,29 @@ public:
|
||||
const uint32_t* getData() const { return data.get(); }
|
||||
|
||||
private:
|
||||
void _setSprite(const std::string&, const std::shared_ptr<const SpriteImage>& = nullptr);
|
||||
void emitSpriteLoadedIfComplete();
|
||||
|
||||
const GLsizei width, height;
|
||||
const dimension pixelWidth, pixelHeight;
|
||||
const float pixelRatio;
|
||||
|
||||
struct Loader;
|
||||
std::unique_ptr<Loader> loader;
|
||||
|
||||
bool loaded = false;
|
||||
|
||||
SpriteAtlasObserver* observer = nullptr;
|
||||
|
||||
// Lock for sprites and dirty maps.
|
||||
std::mutex mutex;
|
||||
|
||||
// Stores all current sprites.
|
||||
Sprites sprites;
|
||||
|
||||
// Stores all Sprite IDs that changed since the last invocation.
|
||||
Sprites dirtySprites;
|
||||
|
||||
struct Holder : private util::noncopyable {
|
||||
Holder(std::shared_ptr<const SpriteImage>, Rect<dimension>);
|
||||
Holder(Holder&&);
|
||||
@@ -92,12 +138,11 @@ private:
|
||||
void copy(const Holder& holder, SpritePatternMode mode);
|
||||
|
||||
std::recursive_mutex mtx;
|
||||
SpriteStore& store;
|
||||
BinPack<dimension> bin;
|
||||
std::map<Key, Holder> images;
|
||||
std::set<std::string> uninitialized;
|
||||
std::unique_ptr<uint32_t[]> data;
|
||||
std::atomic<bool> dirty;
|
||||
std::atomic<bool> dirtyFlag;
|
||||
bool fullUploadRequired = true;
|
||||
mbgl::optional<gl::UniqueTexture> texture;
|
||||
uint32_t filter = 0;
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
class SpriteStoreObserver {
|
||||
class SpriteAtlasObserver {
|
||||
public:
|
||||
virtual ~SpriteStoreObserver() = default;
|
||||
virtual ~SpriteAtlasObserver() = default;
|
||||
|
||||
virtual void onSpriteLoaded() {}
|
||||
virtual void onSpriteError(std::exception_ptr) {}
|
||||
@@ -1,158 +0,0 @@
|
||||
#include <mbgl/sprite/sprite_store.hpp>
|
||||
#include <mbgl/sprite/sprite_store_observer.hpp>
|
||||
#include <mbgl/sprite/sprite_parser.hpp>
|
||||
#include <mbgl/platform/log.hpp>
|
||||
#include <mbgl/storage/file_source.hpp>
|
||||
#include <mbgl/storage/resource.hpp>
|
||||
#include <mbgl/storage/response.hpp>
|
||||
#include <mbgl/util/exception.hpp>
|
||||
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
static SpriteStoreObserver nullObserver;
|
||||
|
||||
struct SpriteStore::Loader {
|
||||
std::shared_ptr<const std::string> image;
|
||||
std::shared_ptr<const std::string> json;
|
||||
std::unique_ptr<AsyncRequest> jsonRequest;
|
||||
std::unique_ptr<AsyncRequest> spriteRequest;
|
||||
};
|
||||
|
||||
SpriteStore::SpriteStore(float pixelRatio_)
|
||||
: pixelRatio(pixelRatio_ > 1 ? 2 : 1), observer(&nullObserver) {
|
||||
}
|
||||
|
||||
SpriteStore::~SpriteStore() = default;
|
||||
|
||||
void SpriteStore::load(const std::string& url, FileSource& fileSource) {
|
||||
if (url.empty()) {
|
||||
// Treat a non-existent sprite as a successfully loaded empty sprite.
|
||||
loaded = true;
|
||||
return;
|
||||
}
|
||||
|
||||
loader = std::make_unique<Loader>();
|
||||
|
||||
loader->jsonRequest = fileSource.request(Resource::spriteJSON(url, pixelRatio), [this](Response res) {
|
||||
if (res.error) {
|
||||
observer->onSpriteError(std::make_exception_ptr(std::runtime_error(res.error->message)));
|
||||
} else if (res.notModified) {
|
||||
return;
|
||||
} else if (res.noContent) {
|
||||
loader->json = std::make_shared<const std::string>();
|
||||
emitSpriteLoadedIfComplete();
|
||||
} else {
|
||||
// Only trigger a sprite loaded event we got new data.
|
||||
loader->json = res.data;
|
||||
emitSpriteLoadedIfComplete();
|
||||
}
|
||||
});
|
||||
|
||||
loader->spriteRequest = fileSource.request(Resource::spriteImage(url, pixelRatio), [this](Response res) {
|
||||
if (res.error) {
|
||||
observer->onSpriteError(std::make_exception_ptr(std::runtime_error(res.error->message)));
|
||||
} else if (res.notModified) {
|
||||
return;
|
||||
} else if (res.noContent) {
|
||||
loader->image = std::make_shared<const std::string>();
|
||||
emitSpriteLoadedIfComplete();
|
||||
} else {
|
||||
loader->image = res.data;
|
||||
emitSpriteLoadedIfComplete();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SpriteStore::emitSpriteLoadedIfComplete() {
|
||||
assert(loader);
|
||||
|
||||
if (!loader->image || !loader->json) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto result = parseSprite(*loader->image, *loader->json);
|
||||
if (result.is<Sprites>()) {
|
||||
loaded = true;
|
||||
setSprites(result.get<Sprites>());
|
||||
observer->onSpriteLoaded();
|
||||
} else {
|
||||
observer->onSpriteError(result.get<std::exception_ptr>());
|
||||
}
|
||||
}
|
||||
|
||||
void SpriteStore::setObserver(SpriteStoreObserver* observer_) {
|
||||
observer = observer_;
|
||||
}
|
||||
|
||||
void SpriteStore::dumpDebugLogs() const {
|
||||
Log::Info(Event::General, "SpriteStore::loaded: %d", loaded);
|
||||
}
|
||||
|
||||
void SpriteStore::setSprite(const std::string& name, std::shared_ptr<const SpriteImage> sprite) {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
_setSprite(name, sprite);
|
||||
}
|
||||
|
||||
void SpriteStore::_setSprite(const std::string& name,
|
||||
const std::shared_ptr<const SpriteImage>& sprite) {
|
||||
if (sprite) {
|
||||
auto it = sprites.find(name);
|
||||
if (it != sprites.end()) {
|
||||
// There is already a sprite with that name in our store.
|
||||
if ((it->second->image.width != sprite->image.width || it->second->image.height != sprite->image.height)) {
|
||||
Log::Warning(Event::Sprite, "Can't change sprite dimensions for '%s'", name.c_str());
|
||||
return;
|
||||
}
|
||||
it->second = sprite;
|
||||
} else {
|
||||
sprites.emplace(name, sprite);
|
||||
}
|
||||
|
||||
// Always add/replace the value in the dirty list.
|
||||
auto dirty_it = dirty.find(name);
|
||||
if (dirty_it != dirty.end()) {
|
||||
dirty_it->second = sprite;
|
||||
} else {
|
||||
dirty.emplace(name, sprite);
|
||||
}
|
||||
} else if (sprites.erase(name) > 0) {
|
||||
dirty.emplace(name, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void SpriteStore::setSprites(const Sprites& newSprites) {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
for (const auto& pair : newSprites) {
|
||||
_setSprite(pair.first, pair.second);
|
||||
}
|
||||
}
|
||||
|
||||
void SpriteStore::removeSprite(const std::string& name) {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
_setSprite(name);
|
||||
}
|
||||
|
||||
std::shared_ptr<const SpriteImage> SpriteStore::getSprite(const std::string& name) {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
const auto it = sprites.find(name);
|
||||
if (it != sprites.end()) {
|
||||
return it->second;
|
||||
} else {
|
||||
if (!sprites.empty()) {
|
||||
Log::Info(Event::Sprite, "Can't find sprite named '%s'", name.c_str());
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
SpriteStore::Sprites SpriteStore::getDirty() {
|
||||
Sprites result;
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
dirty.swap(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace mbgl
|
||||
@@ -1,70 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/sprite/sprite_image.hpp>
|
||||
#include <mbgl/util/noncopyable.hpp>
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
class FileSource;
|
||||
class SpriteStoreObserver;
|
||||
|
||||
class SpriteStore : private util::noncopyable {
|
||||
public:
|
||||
using Sprites = std::map<std::string, std::shared_ptr<const SpriteImage>>;
|
||||
|
||||
SpriteStore(float pixelRatio);
|
||||
~SpriteStore();
|
||||
|
||||
void load(const std::string& url, FileSource&);
|
||||
|
||||
bool isLoaded() const {
|
||||
return loaded;
|
||||
}
|
||||
|
||||
void dumpDebugLogs() const;
|
||||
|
||||
void setObserver(SpriteStoreObserver* observer);
|
||||
|
||||
// Adds/replaces a Sprite image.
|
||||
void setSprite(const std::string&, std::shared_ptr<const SpriteImage> = nullptr);
|
||||
|
||||
// Adds/replaces mutliple Sprite images.
|
||||
void setSprites(const Sprites& sprites);
|
||||
|
||||
// Removes a Sprite.
|
||||
void removeSprite(const std::string&);
|
||||
|
||||
// Obtains a Sprite image.
|
||||
std::shared_ptr<const SpriteImage> getSprite(const std::string&);
|
||||
|
||||
// Returns Sprite images that changed since the last invocation of this function.
|
||||
Sprites getDirty();
|
||||
|
||||
const float pixelRatio;
|
||||
|
||||
private:
|
||||
void _setSprite(const std::string&, const std::shared_ptr<const SpriteImage>& = nullptr);
|
||||
void emitSpriteLoadedIfComplete();
|
||||
|
||||
struct Loader;
|
||||
std::unique_ptr<Loader> loader;
|
||||
|
||||
bool loaded = false;
|
||||
|
||||
SpriteStoreObserver* observer = nullptr;
|
||||
|
||||
// Lock for sprites and dirty maps.
|
||||
std::mutex mutex;
|
||||
|
||||
// Stores all current sprites.
|
||||
Sprites sprites;
|
||||
|
||||
// Stores all Sprite IDs that changed since the last invocation.
|
||||
Sprites dirty;
|
||||
};
|
||||
|
||||
} // namespace mbgl
|
||||
@@ -12,7 +12,6 @@ namespace mbgl {
|
||||
class TileID;
|
||||
class GeometryTileLayer;
|
||||
class GeometryTileFeature;
|
||||
class SpriteStore;
|
||||
class GlyphAtlas;
|
||||
class GlyphStore;
|
||||
class CollisionTile;
|
||||
@@ -26,7 +25,6 @@ public:
|
||||
const GeometryTileLayer& layer_,
|
||||
const std::atomic<bool>& obsolete_,
|
||||
uintptr_t tileUID_,
|
||||
SpriteStore& spriteStore_,
|
||||
GlyphAtlas& glyphAtlas_,
|
||||
GlyphStore& glyphStore_,
|
||||
FeatureIndex& featureIndex_,
|
||||
@@ -35,7 +33,6 @@ public:
|
||||
layer(layer_),
|
||||
obsolete(obsolete_),
|
||||
tileUID(tileUID_),
|
||||
spriteStore(spriteStore_),
|
||||
glyphAtlas(glyphAtlas_),
|
||||
glyphStore(glyphStore_),
|
||||
featureIndex(featureIndex_),
|
||||
@@ -51,7 +48,6 @@ public:
|
||||
const GeometryTileLayer& layer;
|
||||
const std::atomic<bool>& obsolete;
|
||||
uintptr_t tileUID;
|
||||
SpriteStore& spriteStore;
|
||||
GlyphAtlas& glyphAtlas;
|
||||
GlyphStore& glyphStore;
|
||||
FeatureIndex& featureIndex;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/text/glyph_store_observer.hpp>
|
||||
#include <mbgl/sprite/sprite_store_observer.hpp>
|
||||
#include <mbgl/sprite/sprite_atlas_observer.hpp>
|
||||
#include <mbgl/style/source_observer.hpp>
|
||||
#include <mbgl/map/update.hpp>
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace mbgl {
|
||||
namespace style {
|
||||
|
||||
class Observer : public GlyphStoreObserver,
|
||||
public SpriteStoreObserver,
|
||||
public SpriteAtlasObserver,
|
||||
public SourceObserver {
|
||||
public:
|
||||
virtual void onUpdate(Update) {}
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
#include <mbgl/style/update_parameters.hpp>
|
||||
#include <mbgl/style/cascade_parameters.hpp>
|
||||
#include <mbgl/style/calculation_parameters.hpp>
|
||||
#include <mbgl/sprite/sprite_store.hpp>
|
||||
#include <mbgl/sprite/sprite_atlas.hpp>
|
||||
#include <mbgl/geometry/glyph_atlas.hpp>
|
||||
#include <mbgl/geometry/line_atlas.hpp>
|
||||
@@ -40,12 +39,11 @@ Style::Style(FileSource& fileSource_, float pixelRatio)
|
||||
: fileSource(fileSource_),
|
||||
glyphStore(std::make_unique<GlyphStore>(fileSource)),
|
||||
glyphAtlas(std::make_unique<GlyphAtlas>(2048, 2048)),
|
||||
spriteStore(std::make_unique<SpriteStore>(pixelRatio)),
|
||||
spriteAtlas(std::make_unique<SpriteAtlas>(1024, 1024, pixelRatio, *spriteStore)),
|
||||
spriteAtlas(std::make_unique<SpriteAtlas>(1024, 1024, pixelRatio)),
|
||||
lineAtlas(std::make_unique<LineAtlas>(256, 512)),
|
||||
observer(&nullObserver) {
|
||||
glyphStore->setObserver(this);
|
||||
spriteStore->setObserver(this);
|
||||
spriteAtlas->setObserver(this);
|
||||
}
|
||||
|
||||
Style::~Style() {
|
||||
@@ -54,7 +52,7 @@ Style::~Style() {
|
||||
}
|
||||
|
||||
glyphStore->setObserver(nullptr);
|
||||
spriteStore->setObserver(nullptr);
|
||||
spriteAtlas->setObserver(nullptr);
|
||||
}
|
||||
|
||||
bool Style::addClass(const std::string& className) {
|
||||
@@ -125,7 +123,7 @@ void Style::setJSON(const std::string& json) {
|
||||
defaultPitch = parser.pitch;
|
||||
|
||||
glyphStore->setURL(parser.glyphURL);
|
||||
spriteStore->load(parser.spriteURL, fileSource);
|
||||
spriteAtlas->load(parser.spriteURL, fileSource);
|
||||
|
||||
loaded = true;
|
||||
}
|
||||
@@ -302,7 +300,7 @@ bool Style::isLoaded() const {
|
||||
}
|
||||
}
|
||||
|
||||
if (!spriteStore->isLoaded()) {
|
||||
if (!spriteAtlas->isLoaded()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -524,7 +522,7 @@ void Style::dumpDebugLogs() const {
|
||||
source->baseImpl->dumpDebugLogs();
|
||||
}
|
||||
|
||||
spriteStore->dumpDebugLogs();
|
||||
spriteAtlas->dumpDebugLogs();
|
||||
}
|
||||
|
||||
} // namespace style
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <mbgl/style/layer_observer.hpp>
|
||||
#include <mbgl/style/update_batch.hpp>
|
||||
#include <mbgl/text/glyph_store_observer.hpp>
|
||||
#include <mbgl/sprite/sprite_store_observer.hpp>
|
||||
#include <mbgl/sprite/sprite_atlas_observer.hpp>
|
||||
#include <mbgl/map/mode.hpp>
|
||||
#include <mbgl/map/zoom_history.hpp>
|
||||
|
||||
@@ -26,7 +26,6 @@ namespace mbgl {
|
||||
class FileSource;
|
||||
class GlyphStore;
|
||||
class GlyphAtlas;
|
||||
class SpriteStore;
|
||||
class SpriteAtlas;
|
||||
class LineAtlas;
|
||||
class RenderData;
|
||||
@@ -38,7 +37,7 @@ class UpdateParameters;
|
||||
class QueryParameters;
|
||||
|
||||
class Style : public GlyphStoreObserver,
|
||||
public SpriteStoreObserver,
|
||||
public SpriteAtlasObserver,
|
||||
public SourceObserver,
|
||||
public LayerObserver,
|
||||
public util::noncopyable {
|
||||
@@ -106,7 +105,6 @@ public:
|
||||
FileSource& fileSource;
|
||||
std::unique_ptr<GlyphStore> glyphStore;
|
||||
std::unique_ptr<GlyphAtlas> glyphAtlas;
|
||||
std::unique_ptr<SpriteStore> spriteStore;
|
||||
std::unique_ptr<SpriteAtlas> spriteAtlas;
|
||||
std::unique_ptr<LineAtlas> lineAtlas;
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@ GeometryTile::GeometryTile(const OverscaledTileID& id_,
|
||||
worker(parameters.workerScheduler,
|
||||
ActorRef<GeometryTile>(*this, mailbox),
|
||||
id_,
|
||||
*parameters.style.spriteStore,
|
||||
*parameters.style.glyphAtlas,
|
||||
*parameters.style.glyphStore,
|
||||
obsolete,
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <mbgl/style/bucket_parameters.hpp>
|
||||
#include <mbgl/style/layers/symbol_layer.hpp>
|
||||
#include <mbgl/style/layers/symbol_layer_impl.hpp>
|
||||
#include <mbgl/sprite/sprite_atlas.hpp>
|
||||
#include <mbgl/geometry/glyph_atlas.hpp>
|
||||
#include <mbgl/renderer/symbol_bucket.hpp>
|
||||
#include <mbgl/platform/log.hpp>
|
||||
@@ -23,7 +22,6 @@ using namespace style;
|
||||
GeometryTileWorker::GeometryTileWorker(ActorRef<GeometryTileWorker> self_,
|
||||
ActorRef<GeometryTile> parent_,
|
||||
OverscaledTileID id_,
|
||||
SpriteStore& spriteStore_,
|
||||
GlyphAtlas& glyphAtlas_,
|
||||
GlyphStore& glyphStore_,
|
||||
const std::atomic<bool>& obsolete_,
|
||||
@@ -31,7 +29,6 @@ GeometryTileWorker::GeometryTileWorker(ActorRef<GeometryTileWorker> self_,
|
||||
: self(std::move(self_)),
|
||||
parent(std::move(parent_)),
|
||||
id(std::move(id_)),
|
||||
spriteStore(spriteStore_),
|
||||
glyphAtlas(glyphAtlas_),
|
||||
glyphStore(glyphStore_),
|
||||
obsolete(obsolete_),
|
||||
@@ -219,7 +216,6 @@ void GeometryTileWorker::redoLayout() {
|
||||
*geometryLayer,
|
||||
obsolete,
|
||||
reinterpret_cast<uintptr_t>(this),
|
||||
spriteStore,
|
||||
glyphAtlas,
|
||||
glyphStore,
|
||||
*featureIndex,
|
||||
@@ -259,7 +255,7 @@ void GeometryTileWorker::attemptPlacement() {
|
||||
}
|
||||
|
||||
if (symbolLayout->state == SymbolLayout::Pending) {
|
||||
if (symbolLayout->canPrepare(glyphStore, spriteStore)) {
|
||||
if (symbolLayout->canPrepare(glyphStore)) {
|
||||
symbolLayout->state = SymbolLayout::Prepared;
|
||||
symbolLayout->prepare(reinterpret_cast<uintptr_t>(this),
|
||||
glyphAtlas,
|
||||
|
||||
@@ -14,7 +14,6 @@ namespace mbgl {
|
||||
|
||||
class GeometryTile;
|
||||
class GeometryTileData;
|
||||
class SpriteStore;
|
||||
class GlyphAtlas;
|
||||
class GlyphStore;
|
||||
class SymbolLayout;
|
||||
@@ -28,7 +27,6 @@ public:
|
||||
GeometryTileWorker(ActorRef<GeometryTileWorker> self,
|
||||
ActorRef<GeometryTile> parent,
|
||||
OverscaledTileID,
|
||||
SpriteStore&,
|
||||
GlyphAtlas&,
|
||||
GlyphStore&,
|
||||
const std::atomic<bool>&,
|
||||
@@ -49,7 +47,6 @@ private:
|
||||
ActorRef<GeometryTile> parent;
|
||||
|
||||
const OverscaledTileID id;
|
||||
SpriteStore& spriteStore;
|
||||
GlyphAtlas& glyphAtlas;
|
||||
GlyphStore& glyphStore;
|
||||
const std::atomic<bool>& obsolete;
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
#include <mbgl/test/util.hpp>
|
||||
#include <mbgl/test/fixture_log_observer.hpp>
|
||||
#include <mbgl/test/stub_file_source.hpp>
|
||||
#include <mbgl/test/stub_style_observer.hpp>
|
||||
|
||||
#include <mbgl/sprite/sprite_atlas.hpp>
|
||||
#include <mbgl/sprite/sprite_store.hpp>
|
||||
#include <mbgl/sprite/sprite_atlas.hpp>
|
||||
#include <mbgl/sprite/sprite_parser.hpp>
|
||||
#include <mbgl/util/io.hpp>
|
||||
#include <mbgl/util/image.hpp>
|
||||
#include <mbgl/util/run_loop.hpp>
|
||||
#include <mbgl/util/string.hpp>
|
||||
|
||||
#include <utility>
|
||||
|
||||
using namespace mbgl;
|
||||
|
||||
@@ -25,16 +31,14 @@ auto imageFromAtlas(const SpriteAtlas& atlas) {
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(Sprite, SpriteAtlas) {
|
||||
TEST(SpriteAtlas, Basic) {
|
||||
FixtureLog log;
|
||||
|
||||
auto spriteParseResult = parseSprite(util::read_file("test/fixtures/annotations/emerald.png"),
|
||||
util::read_file("test/fixtures/annotations/emerald.json"));
|
||||
|
||||
SpriteStore store(1);
|
||||
store.setSprites(spriteParseResult.get<Sprites>());
|
||||
|
||||
SpriteAtlas atlas(63, 112, 1, store);
|
||||
SpriteAtlas atlas(63, 112, 1);
|
||||
atlas.setSprites(spriteParseResult.get<Sprites>());
|
||||
|
||||
EXPECT_EQ(1.0f, atlas.getPixelRatio());
|
||||
EXPECT_EQ(63, atlas.getWidth());
|
||||
@@ -86,14 +90,12 @@ TEST(Sprite, SpriteAtlas) {
|
||||
EXPECT_EQ(readImage("test/fixtures/annotations/result-spriteatlas.png"), imageFromAtlas(atlas));
|
||||
}
|
||||
|
||||
TEST(Sprite, SpriteAtlasSize) {
|
||||
TEST(SpriteAtlas, Size) {
|
||||
auto spriteParseResult = parseSprite(util::read_file("test/fixtures/annotations/emerald.png"),
|
||||
util::read_file("test/fixtures/annotations/emerald.json"));
|
||||
|
||||
SpriteStore store(1);
|
||||
store.setSprites(spriteParseResult.get<Sprites>());
|
||||
|
||||
SpriteAtlas atlas(63, 112, 1.4, store);
|
||||
SpriteAtlas atlas(63, 112, 1.4);
|
||||
atlas.setSprites(spriteParseResult.get<Sprites>());
|
||||
|
||||
EXPECT_DOUBLE_EQ(1.4f, atlas.getPixelRatio());
|
||||
EXPECT_EQ(63, atlas.getWidth());
|
||||
@@ -116,10 +118,8 @@ TEST(Sprite, SpriteAtlasSize) {
|
||||
imageFromAtlas(atlas));
|
||||
}
|
||||
|
||||
TEST(Sprite, SpriteAtlasUpdates) {
|
||||
SpriteStore store(1);
|
||||
|
||||
SpriteAtlas atlas(32, 32, 1, store);
|
||||
TEST(SpriteAtlas, Updates) {
|
||||
SpriteAtlas atlas(32, 32, 1);
|
||||
|
||||
EXPECT_EQ(1.0f, atlas.getPixelRatio());
|
||||
EXPECT_EQ(32, atlas.getWidth());
|
||||
@@ -127,7 +127,7 @@ TEST(Sprite, SpriteAtlasUpdates) {
|
||||
EXPECT_EQ(32, atlas.getTextureWidth());
|
||||
EXPECT_EQ(32, atlas.getTextureHeight());
|
||||
|
||||
store.setSprite("one", std::make_shared<SpriteImage>(PremultipliedImage(16, 12), 1));
|
||||
atlas.setSprite("one", std::make_shared<SpriteImage>(PremultipliedImage(16, 12), 1));
|
||||
auto one = *atlas.getImage("one", SpritePatternMode::Single);
|
||||
EXPECT_EQ(0, one.pos.x);
|
||||
EXPECT_EQ(0, one.pos.y);
|
||||
@@ -148,8 +148,8 @@ TEST(Sprite, SpriteAtlasUpdates) {
|
||||
image2.data.get()[i] = 255;
|
||||
}
|
||||
auto newSprite = std::make_shared<SpriteImage>(std::move(image2), 1);
|
||||
store.setSprite("one", newSprite);
|
||||
ASSERT_EQ(newSprite, store.getSprite("one"));
|
||||
atlas.setSprite("one", newSprite);
|
||||
ASSERT_EQ(newSprite, atlas.getSprite("one"));
|
||||
|
||||
// Atlas texture hasn't changed yet.
|
||||
EXPECT_EQ(readImage("test/fixtures/annotations/result-spriteatlas-empty.png"),
|
||||
@@ -161,3 +161,259 @@ TEST(Sprite, SpriteAtlasUpdates) {
|
||||
EXPECT_EQ(readImage("test/fixtures/annotations/result-spriteatlas-updated.png"),
|
||||
imageFromAtlas(atlas));
|
||||
}
|
||||
|
||||
TEST(SpriteAtlas, AddRemove) {
|
||||
FixtureLog log;
|
||||
|
||||
const auto sprite1 = std::make_shared<SpriteImage>(PremultipliedImage(16, 16), 2);
|
||||
const auto sprite2 = std::make_shared<SpriteImage>(PremultipliedImage(16, 16), 2);
|
||||
const auto sprite3 = std::make_shared<SpriteImage>(PremultipliedImage(16, 16), 2);
|
||||
|
||||
SpriteAtlas atlas(32, 32, 1);
|
||||
|
||||
// Adding single
|
||||
atlas.setSprite("one", sprite1);
|
||||
|
||||
// Adding multiple
|
||||
atlas.setSprite("two", sprite2);
|
||||
atlas.setSprite("three", sprite3);
|
||||
|
||||
// Removing
|
||||
atlas.removeSprite("one");
|
||||
atlas.removeSprite("two");
|
||||
|
||||
// Accessing
|
||||
EXPECT_EQ(sprite3, atlas.getSprite("three"));
|
||||
|
||||
EXPECT_TRUE(log.empty());
|
||||
|
||||
EXPECT_EQ(nullptr, atlas.getSprite("two"));
|
||||
EXPECT_EQ(nullptr, atlas.getSprite("four"));
|
||||
|
||||
EXPECT_EQ(1u, log.count({
|
||||
EventSeverity::Info,
|
||||
Event::Sprite,
|
||||
int64_t(-1),
|
||||
"Can't find sprite named 'two'",
|
||||
}));
|
||||
EXPECT_EQ(1u, log.count({
|
||||
EventSeverity::Info,
|
||||
Event::Sprite,
|
||||
int64_t(-1),
|
||||
"Can't find sprite named 'four'",
|
||||
}));
|
||||
|
||||
// Overwriting
|
||||
atlas.setSprite("three", sprite1);
|
||||
}
|
||||
|
||||
TEST(SpriteAtlas, OtherPixelRatio) {
|
||||
FixtureLog log;
|
||||
|
||||
const auto sprite1 = std::make_shared<SpriteImage>(PremultipliedImage(8, 8), 1);
|
||||
|
||||
SpriteAtlas atlas(32, 32, 1);
|
||||
|
||||
// Adding mismatched sprite image
|
||||
atlas.setSprite("one", sprite1);
|
||||
}
|
||||
|
||||
TEST(SpriteAtlas, Multiple) {
|
||||
const auto sprite1 = std::make_shared<SpriteImage>(PremultipliedImage(16, 16), 2);
|
||||
const auto sprite2 = std::make_shared<SpriteImage>(PremultipliedImage(16, 16), 2);
|
||||
|
||||
SpriteAtlas atlas(32, 32, 1);
|
||||
|
||||
atlas.setSprites({
|
||||
{ "one", sprite1 }, { "two", sprite2 },
|
||||
});
|
||||
}
|
||||
|
||||
TEST(SpriteAtlas, Replace) {
|
||||
FixtureLog log;
|
||||
|
||||
const auto sprite1 = std::make_shared<SpriteImage>(PremultipliedImage(16, 16), 2);
|
||||
const auto sprite2 = std::make_shared<SpriteImage>(PremultipliedImage(16, 16), 2);
|
||||
|
||||
SpriteAtlas atlas(32, 32, 1);
|
||||
|
||||
atlas.setSprite("sprite", sprite1);
|
||||
EXPECT_EQ(sprite1, atlas.getSprite("sprite"));
|
||||
atlas.setSprite("sprite", sprite2);
|
||||
EXPECT_EQ(sprite2, atlas.getSprite("sprite"));
|
||||
}
|
||||
|
||||
TEST(SpriteAtlas, ReplaceWithDifferentDimensions) {
|
||||
FixtureLog log;
|
||||
|
||||
PremultipliedImage image(16, 16);
|
||||
PremultipliedImage image2(18, 18);
|
||||
const auto sprite1 = std::make_shared<SpriteImage>(PremultipliedImage(16, 16), 2);
|
||||
const auto sprite2 = std::make_shared<SpriteImage>(PremultipliedImage(18, 18), 2);
|
||||
|
||||
SpriteAtlas atlas(32, 32, 1);
|
||||
|
||||
atlas.setSprite("sprite", sprite1);
|
||||
atlas.setSprite("sprite", sprite2);
|
||||
|
||||
EXPECT_EQ(1u, log.count({
|
||||
EventSeverity::Warning,
|
||||
Event::Sprite,
|
||||
int64_t(-1),
|
||||
"Can't change sprite dimensions for 'sprite'",
|
||||
}));
|
||||
|
||||
EXPECT_EQ(sprite1, atlas.getSprite("sprite"));
|
||||
}
|
||||
|
||||
class SpriteAtlasTest {
|
||||
public:
|
||||
SpriteAtlasTest() = default;
|
||||
|
||||
util::RunLoop loop;
|
||||
StubFileSource fileSource;
|
||||
StubStyleObserver observer;
|
||||
SpriteAtlas spriteAtlas { 32, 32, 1 };
|
||||
|
||||
void run() {
|
||||
// Squelch logging.
|
||||
Log::setObserver(std::make_unique<Log::NullObserver>());
|
||||
|
||||
spriteAtlas.setObserver(&observer);
|
||||
spriteAtlas.load("test/fixtures/resources/sprite", fileSource);
|
||||
|
||||
loop.run();
|
||||
}
|
||||
|
||||
void end() {
|
||||
loop.stop();
|
||||
}
|
||||
};
|
||||
|
||||
Response successfulSpriteImageResponse(const Resource& resource) {
|
||||
EXPECT_EQ("test/fixtures/resources/sprite.png", resource.url);
|
||||
Response response;
|
||||
response.data = std::make_shared<std::string>(util::read_file(resource.url));
|
||||
return response;
|
||||
}
|
||||
|
||||
Response successfulSpriteJSONResponse(const Resource& resource) {
|
||||
EXPECT_EQ("test/fixtures/resources/sprite.json", resource.url);
|
||||
Response response;
|
||||
response.data = std::make_shared<std::string>(util::read_file(resource.url));
|
||||
return response;
|
||||
}
|
||||
|
||||
Response failedSpriteResponse(const Resource&) {
|
||||
Response response;
|
||||
response.error = std::make_unique<Response::Error>(
|
||||
Response::Error::Reason::Other,
|
||||
"Failed by the test case");
|
||||
return response;
|
||||
}
|
||||
|
||||
Response corruptSpriteResponse(const Resource&) {
|
||||
Response response;
|
||||
response.data = std::make_shared<std::string>("CORRUPT");
|
||||
return response;
|
||||
}
|
||||
|
||||
TEST(SpriteAtlas, LoadingSuccess) {
|
||||
SpriteAtlasTest test;
|
||||
|
||||
test.fileSource.spriteImageResponse = successfulSpriteImageResponse;
|
||||
test.fileSource.spriteJSONResponse = successfulSpriteJSONResponse;
|
||||
|
||||
test.observer.spriteError = [&] (std::exception_ptr error) {
|
||||
FAIL() << util::toString(error);
|
||||
test.end();
|
||||
};
|
||||
|
||||
test.observer.spriteLoaded = [&] () {
|
||||
EXPECT_EQ(1.0, test.spriteAtlas.getPixelRatio());
|
||||
EXPECT_TRUE(test.spriteAtlas.isLoaded());
|
||||
test.end();
|
||||
};
|
||||
|
||||
test.run();
|
||||
}
|
||||
|
||||
TEST(SpriteAtlas, JSONLoadingFail) {
|
||||
SpriteAtlasTest test;
|
||||
|
||||
test.fileSource.spriteImageResponse = successfulSpriteImageResponse;
|
||||
test.fileSource.spriteJSONResponse = failedSpriteResponse;
|
||||
|
||||
test.observer.spriteError = [&] (std::exception_ptr error) {
|
||||
EXPECT_TRUE(error != nullptr);
|
||||
EXPECT_EQ("Failed by the test case", util::toString(error));
|
||||
EXPECT_FALSE(test.spriteAtlas.isLoaded());
|
||||
test.end();
|
||||
};
|
||||
|
||||
test.run();
|
||||
}
|
||||
|
||||
TEST(SpriteAtlas, ImageLoadingFail) {
|
||||
SpriteAtlasTest test;
|
||||
|
||||
test.fileSource.spriteImageResponse = failedSpriteResponse;
|
||||
test.fileSource.spriteJSONResponse = successfulSpriteJSONResponse;
|
||||
|
||||
test.observer.spriteError = [&] (std::exception_ptr error) {
|
||||
EXPECT_TRUE(error != nullptr);
|
||||
EXPECT_EQ("Failed by the test case", util::toString(error));
|
||||
EXPECT_FALSE(test.spriteAtlas.isLoaded());
|
||||
test.end();
|
||||
};
|
||||
|
||||
test.run();
|
||||
}
|
||||
|
||||
TEST(SpriteAtlas, JSONLoadingCorrupted) {
|
||||
SpriteAtlasTest test;
|
||||
|
||||
test.fileSource.spriteImageResponse = successfulSpriteImageResponse;
|
||||
test.fileSource.spriteJSONResponse = corruptSpriteResponse;
|
||||
|
||||
test.observer.spriteError = [&] (std::exception_ptr error) {
|
||||
EXPECT_TRUE(error != nullptr);
|
||||
EXPECT_EQ("Failed to parse JSON: Invalid value. at offset 0", util::toString(error));
|
||||
EXPECT_FALSE(test.spriteAtlas.isLoaded());
|
||||
test.end();
|
||||
};
|
||||
|
||||
test.run();
|
||||
}
|
||||
|
||||
TEST(SpriteAtlas, ImageLoadingCorrupted) {
|
||||
SpriteAtlasTest test;
|
||||
|
||||
test.fileSource.spriteImageResponse = corruptSpriteResponse;
|
||||
test.fileSource.spriteJSONResponse = successfulSpriteJSONResponse;
|
||||
|
||||
test.observer.spriteError = [&] (std::exception_ptr error) {
|
||||
EXPECT_TRUE(error != nullptr);
|
||||
// Not asserting on platform-specific error text.
|
||||
EXPECT_FALSE(test.spriteAtlas.isLoaded());
|
||||
test.end();
|
||||
};
|
||||
|
||||
test.run();
|
||||
}
|
||||
|
||||
TEST(SpriteAtlas, LoadingCancel) {
|
||||
SpriteAtlasTest test;
|
||||
|
||||
test.fileSource.spriteImageResponse =
|
||||
test.fileSource.spriteJSONResponse = [&] (const Resource&) {
|
||||
test.end();
|
||||
return optional<Response>();
|
||||
};
|
||||
|
||||
test.observer.spriteLoaded = [&] () {
|
||||
FAIL() << "Should never be called";
|
||||
};
|
||||
|
||||
test.run();
|
||||
}
|
||||
|
||||
@@ -1,305 +0,0 @@
|
||||
#include <mbgl/test/util.hpp>
|
||||
#include <mbgl/test/fixture_log_observer.hpp>
|
||||
#include <mbgl/test/stub_file_source.hpp>
|
||||
#include <mbgl/test/stub_style_observer.hpp>
|
||||
|
||||
#include <mbgl/sprite/sprite_store.hpp>
|
||||
#include <mbgl/util/run_loop.hpp>
|
||||
#include <mbgl/util/string.hpp>
|
||||
#include <mbgl/util/io.hpp>
|
||||
|
||||
#include <utility>
|
||||
|
||||
using namespace mbgl;
|
||||
|
||||
TEST(SpriteStore, SpriteStore) {
|
||||
FixtureLog log;
|
||||
|
||||
const auto sprite1 = std::make_shared<SpriteImage>(PremultipliedImage(16, 16), 2);
|
||||
const auto sprite2 = std::make_shared<SpriteImage>(PremultipliedImage(16, 16), 2);
|
||||
const auto sprite3 = std::make_shared<SpriteImage>(PremultipliedImage(16, 16), 2);
|
||||
|
||||
using Sprites = std::map<std::string, std::shared_ptr<const SpriteImage>>;
|
||||
SpriteStore store(1);
|
||||
|
||||
// Adding single
|
||||
store.setSprite("one", sprite1);
|
||||
EXPECT_EQ(Sprites({
|
||||
{ "one", sprite1 },
|
||||
}),
|
||||
store.getDirty());
|
||||
EXPECT_EQ(Sprites(), store.getDirty());
|
||||
|
||||
// Adding multiple
|
||||
store.setSprite("two", sprite2);
|
||||
store.setSprite("three", sprite3);
|
||||
EXPECT_EQ(Sprites({
|
||||
{ "two", sprite2 }, { "three", sprite3 },
|
||||
}),
|
||||
store.getDirty());
|
||||
EXPECT_EQ(Sprites(), store.getDirty());
|
||||
|
||||
// Removing
|
||||
store.removeSprite("one");
|
||||
store.removeSprite("two");
|
||||
EXPECT_EQ(Sprites({
|
||||
{ "one", nullptr }, { "two", nullptr },
|
||||
}),
|
||||
store.getDirty());
|
||||
EXPECT_EQ(Sprites(), store.getDirty());
|
||||
|
||||
// Accessing
|
||||
EXPECT_EQ(sprite3, store.getSprite("three"));
|
||||
|
||||
EXPECT_TRUE(log.empty());
|
||||
|
||||
EXPECT_EQ(nullptr, store.getSprite("two"));
|
||||
EXPECT_EQ(nullptr, store.getSprite("four"));
|
||||
|
||||
EXPECT_EQ(1u, log.count({
|
||||
EventSeverity::Info,
|
||||
Event::Sprite,
|
||||
int64_t(-1),
|
||||
"Can't find sprite named 'two'",
|
||||
}));
|
||||
EXPECT_EQ(1u, log.count({
|
||||
EventSeverity::Info,
|
||||
Event::Sprite,
|
||||
int64_t(-1),
|
||||
"Can't find sprite named 'four'",
|
||||
}));
|
||||
|
||||
// Overwriting
|
||||
store.setSprite("three", sprite1);
|
||||
EXPECT_EQ(Sprites({
|
||||
{ "three", sprite1 },
|
||||
}),
|
||||
store.getDirty());
|
||||
EXPECT_EQ(Sprites(), store.getDirty());
|
||||
}
|
||||
|
||||
TEST(SpriteStore, OtherPixelRatio) {
|
||||
FixtureLog log;
|
||||
|
||||
const auto sprite1 = std::make_shared<SpriteImage>(PremultipliedImage(8, 8), 1);
|
||||
|
||||
using Sprites = std::map<std::string, std::shared_ptr<const SpriteImage>>;
|
||||
SpriteStore store(1);
|
||||
|
||||
// Adding mismatched sprite image
|
||||
store.setSprite("one", sprite1);
|
||||
EXPECT_EQ(Sprites({ { "one", sprite1 } }), store.getDirty());
|
||||
}
|
||||
|
||||
TEST(SpriteStore, Multiple) {
|
||||
const auto sprite1 = std::make_shared<SpriteImage>(PremultipliedImage(16, 16), 2);
|
||||
const auto sprite2 = std::make_shared<SpriteImage>(PremultipliedImage(16, 16), 2);
|
||||
|
||||
using Sprites = std::map<std::string, std::shared_ptr<const SpriteImage>>;
|
||||
SpriteStore store(1);
|
||||
|
||||
store.setSprites({
|
||||
{ "one", sprite1 }, { "two", sprite2 },
|
||||
});
|
||||
EXPECT_EQ(Sprites({
|
||||
{ "one", sprite1 }, { "two", sprite2 },
|
||||
}),
|
||||
store.getDirty());
|
||||
EXPECT_EQ(Sprites(), store.getDirty());
|
||||
}
|
||||
|
||||
TEST(SpriteStore, Replace) {
|
||||
FixtureLog log;
|
||||
|
||||
const auto sprite1 = std::make_shared<SpriteImage>(PremultipliedImage(16, 16), 2);
|
||||
const auto sprite2 = std::make_shared<SpriteImage>(PremultipliedImage(16, 16), 2);
|
||||
|
||||
using Sprites = std::map<std::string, std::shared_ptr<const SpriteImage>>;
|
||||
SpriteStore store(1);
|
||||
|
||||
store.setSprite("sprite", sprite1);
|
||||
EXPECT_EQ(sprite1, store.getSprite("sprite"));
|
||||
store.setSprite("sprite", sprite2);
|
||||
EXPECT_EQ(sprite2, store.getSprite("sprite"));
|
||||
|
||||
EXPECT_EQ(Sprites({ { "sprite", sprite2 } }), store.getDirty());
|
||||
}
|
||||
|
||||
TEST(SpriteStore, ReplaceWithDifferentDimensions) {
|
||||
FixtureLog log;
|
||||
|
||||
PremultipliedImage image(16, 16);
|
||||
PremultipliedImage image2(18, 18);
|
||||
const auto sprite1 = std::make_shared<SpriteImage>(PremultipliedImage(16, 16), 2);
|
||||
const auto sprite2 = std::make_shared<SpriteImage>(PremultipliedImage(18, 18), 2);
|
||||
|
||||
using Sprites = std::map<std::string, std::shared_ptr<const SpriteImage>>;
|
||||
SpriteStore store(1);
|
||||
|
||||
store.setSprite("sprite", sprite1);
|
||||
store.setSprite("sprite", sprite2);
|
||||
|
||||
EXPECT_EQ(1u, log.count({
|
||||
EventSeverity::Warning,
|
||||
Event::Sprite,
|
||||
int64_t(-1),
|
||||
"Can't change sprite dimensions for 'sprite'",
|
||||
}));
|
||||
|
||||
EXPECT_EQ(sprite1, store.getSprite("sprite"));
|
||||
|
||||
EXPECT_EQ(Sprites({ { "sprite", sprite1 } }), store.getDirty());
|
||||
}
|
||||
|
||||
class SpriteStoreTest {
|
||||
public:
|
||||
SpriteStoreTest() = default;
|
||||
|
||||
util::RunLoop loop;
|
||||
StubFileSource fileSource;
|
||||
StubStyleObserver observer;
|
||||
SpriteStore spriteStore = { 1.0 };
|
||||
|
||||
void run() {
|
||||
// Squelch logging.
|
||||
Log::setObserver(std::make_unique<Log::NullObserver>());
|
||||
|
||||
spriteStore.setObserver(&observer);
|
||||
spriteStore.load("test/fixtures/resources/sprite", fileSource);
|
||||
|
||||
loop.run();
|
||||
}
|
||||
|
||||
void end() {
|
||||
loop.stop();
|
||||
}
|
||||
};
|
||||
|
||||
Response successfulSpriteImageResponse(const Resource& resource) {
|
||||
EXPECT_EQ("test/fixtures/resources/sprite.png", resource.url);
|
||||
Response response;
|
||||
response.data = std::make_shared<std::string>(util::read_file(resource.url));
|
||||
return response;
|
||||
}
|
||||
|
||||
Response successfulSpriteJSONResponse(const Resource& resource) {
|
||||
EXPECT_EQ("test/fixtures/resources/sprite.json", resource.url);
|
||||
Response response;
|
||||
response.data = std::make_shared<std::string>(util::read_file(resource.url));
|
||||
return response;
|
||||
}
|
||||
|
||||
Response failedSpriteResponse(const Resource&) {
|
||||
Response response;
|
||||
response.error = std::make_unique<Response::Error>(
|
||||
Response::Error::Reason::Other,
|
||||
"Failed by the test case");
|
||||
return response;
|
||||
}
|
||||
|
||||
Response corruptSpriteResponse(const Resource&) {
|
||||
Response response;
|
||||
response.data = std::make_shared<std::string>("CORRUPT");
|
||||
return response;
|
||||
}
|
||||
|
||||
TEST(SpriteStore, LoadingSuccess) {
|
||||
SpriteStoreTest test;
|
||||
|
||||
test.fileSource.spriteImageResponse = successfulSpriteImageResponse;
|
||||
test.fileSource.spriteJSONResponse = successfulSpriteJSONResponse;
|
||||
|
||||
test.observer.spriteError = [&] (std::exception_ptr error) {
|
||||
FAIL() << util::toString(error);
|
||||
test.end();
|
||||
};
|
||||
|
||||
test.observer.spriteLoaded = [&] () {
|
||||
EXPECT_TRUE(!test.spriteStore.getDirty().empty());
|
||||
EXPECT_EQ(1.0, test.spriteStore.pixelRatio);
|
||||
EXPECT_TRUE(test.spriteStore.isLoaded());
|
||||
test.end();
|
||||
};
|
||||
|
||||
test.run();
|
||||
}
|
||||
|
||||
TEST(SpriteStore, JSONLoadingFail) {
|
||||
SpriteStoreTest test;
|
||||
|
||||
test.fileSource.spriteImageResponse = successfulSpriteImageResponse;
|
||||
test.fileSource.spriteJSONResponse = failedSpriteResponse;
|
||||
|
||||
test.observer.spriteError = [&] (std::exception_ptr error) {
|
||||
EXPECT_TRUE(error != nullptr);
|
||||
EXPECT_EQ("Failed by the test case", util::toString(error));
|
||||
EXPECT_FALSE(test.spriteStore.isLoaded());
|
||||
test.end();
|
||||
};
|
||||
|
||||
test.run();
|
||||
}
|
||||
|
||||
TEST(SpriteStore, ImageLoadingFail) {
|
||||
SpriteStoreTest test;
|
||||
|
||||
test.fileSource.spriteImageResponse = failedSpriteResponse;
|
||||
test.fileSource.spriteJSONResponse = successfulSpriteJSONResponse;
|
||||
|
||||
test.observer.spriteError = [&] (std::exception_ptr error) {
|
||||
EXPECT_TRUE(error != nullptr);
|
||||
EXPECT_EQ("Failed by the test case", util::toString(error));
|
||||
EXPECT_FALSE(test.spriteStore.isLoaded());
|
||||
test.end();
|
||||
};
|
||||
|
||||
test.run();
|
||||
}
|
||||
|
||||
TEST(SpriteStore, JSONLoadingCorrupted) {
|
||||
SpriteStoreTest test;
|
||||
|
||||
test.fileSource.spriteImageResponse = successfulSpriteImageResponse;
|
||||
test.fileSource.spriteJSONResponse = corruptSpriteResponse;
|
||||
|
||||
test.observer.spriteError = [&] (std::exception_ptr error) {
|
||||
EXPECT_TRUE(error != nullptr);
|
||||
EXPECT_EQ("Failed to parse JSON: Invalid value. at offset 0", util::toString(error));
|
||||
EXPECT_FALSE(test.spriteStore.isLoaded());
|
||||
test.end();
|
||||
};
|
||||
|
||||
test.run();
|
||||
}
|
||||
|
||||
TEST(SpriteStore, ImageLoadingCorrupted) {
|
||||
SpriteStoreTest test;
|
||||
|
||||
test.fileSource.spriteImageResponse = corruptSpriteResponse;
|
||||
test.fileSource.spriteJSONResponse = successfulSpriteJSONResponse;
|
||||
|
||||
test.observer.spriteError = [&] (std::exception_ptr error) {
|
||||
EXPECT_TRUE(error != nullptr);
|
||||
// Not asserting on platform-specific error text.
|
||||
EXPECT_FALSE(test.spriteStore.isLoaded());
|
||||
test.end();
|
||||
};
|
||||
|
||||
test.run();
|
||||
}
|
||||
|
||||
TEST(SpriteStore, LoadingCancel) {
|
||||
SpriteStoreTest test;
|
||||
|
||||
test.fileSource.spriteImageResponse =
|
||||
test.fileSource.spriteJSONResponse = [&] (const Resource&) {
|
||||
test.end();
|
||||
return optional<Response>();
|
||||
};
|
||||
|
||||
test.observer.spriteLoaded = [&] () {
|
||||
FAIL() << "Should never be called";
|
||||
};
|
||||
|
||||
test.run();
|
||||
}
|
||||
Reference in New Issue
Block a user