Before this change, ImageManger's 'done' callback for onStyleImageMissing observer notification that was created on renderer thread, could be called from different thread, therefore, is not thread safe. For example, on Android platform, callback was invoked from UI thread. This change makes callback to be scheduled on originating thread.
318 lines
11 KiB
C++
318 lines
11 KiB
C++
#include <mbgl/test/util.hpp>
|
|
#include <mbgl/test/fixture_log_observer.hpp>
|
|
#include <mbgl/test/stub_style_observer.hpp>
|
|
|
|
#include <mbgl/renderer/image_manager.hpp>
|
|
#include <mbgl/renderer/image_manager_observer.hpp>
|
|
#include <mbgl/sprite/sprite_parser.hpp>
|
|
#include <mbgl/style/image_impl.hpp>
|
|
#include <mbgl/util/io.hpp>
|
|
#include <mbgl/util/image.hpp>
|
|
#include <mbgl/util/run_loop.hpp>
|
|
#include <mbgl/util/default_thread_pool.hpp>
|
|
#include <mbgl/util/string.hpp>
|
|
|
|
#include <utility>
|
|
|
|
using namespace mbgl;
|
|
|
|
TEST(ImageManager, Missing) {
|
|
ImageManager imageManager;
|
|
EXPECT_FALSE(imageManager.getImage("doesnotexist"));
|
|
}
|
|
|
|
TEST(ImageManager, Basic) {
|
|
FixtureLog log;
|
|
ImageManager imageManager;
|
|
|
|
auto images = parseSprite(util::read_file("test/fixtures/annotations/emerald.png"),
|
|
util::read_file("test/fixtures/annotations/emerald.json"));
|
|
for (auto& image : images) {
|
|
imageManager.addImage(image->baseImpl);
|
|
}
|
|
|
|
auto metro = *imageManager.getPattern("metro");
|
|
EXPECT_EQ(1, metro.tl()[0]);
|
|
EXPECT_EQ(1, metro.tl()[1]);
|
|
EXPECT_EQ(19, metro.br()[0]);
|
|
EXPECT_EQ(19, metro.br()[1]);
|
|
EXPECT_EQ(18, metro.displaySize()[0]);
|
|
EXPECT_EQ(18, metro.displaySize()[1]);
|
|
EXPECT_EQ(1.0f, metro.pixelRatio);
|
|
EXPECT_EQ(imageManager.getPixelSize(), imageManager.getAtlasImage().size);
|
|
|
|
test::checkImage("test/fixtures/image_manager/basic", imageManager.getAtlasImage());
|
|
}
|
|
|
|
TEST(ImageManager, Updates) {
|
|
ImageManager imageManager;
|
|
|
|
PremultipliedImage imageA({ 16, 12 });
|
|
imageA.fill(255);
|
|
imageManager.addImage(makeMutable<style::Image::Impl>("one", std::move(imageA), 1));
|
|
|
|
auto a = *imageManager.getPattern("one");
|
|
EXPECT_EQ(1, a.tl()[0]);
|
|
EXPECT_EQ(1, a.tl()[1]);
|
|
EXPECT_EQ(17, a.br()[0]);
|
|
EXPECT_EQ(13, a.br()[1]);
|
|
EXPECT_EQ(16, a.displaySize()[0]);
|
|
EXPECT_EQ(12, a.displaySize()[1]);
|
|
EXPECT_EQ(1.0f, a.pixelRatio);
|
|
test::checkImage("test/fixtures/image_manager/updates_before", imageManager.getAtlasImage());
|
|
|
|
PremultipliedImage imageB({ 5, 5 });
|
|
imageA.fill(200);
|
|
imageManager.updateImage(makeMutable<style::Image::Impl>("one", std::move(imageB), 1));
|
|
|
|
auto b = *imageManager.getPattern("one");
|
|
EXPECT_EQ(1, b.tl()[0]);
|
|
EXPECT_EQ(1, b.tl()[1]);
|
|
EXPECT_EQ(6, b.br()[0]);
|
|
EXPECT_EQ(6, b.br()[1]);
|
|
EXPECT_EQ(5, b.displaySize()[0]);
|
|
EXPECT_EQ(5, b.displaySize()[1]);
|
|
EXPECT_EQ(1.0f, b.pixelRatio);
|
|
test::checkImage("test/fixtures/image_manager/updates_after", imageManager.getAtlasImage());
|
|
}
|
|
|
|
TEST(ImageManager, AddRemove) {
|
|
FixtureLog log;
|
|
ImageManager imageManager;
|
|
|
|
imageManager.addImage(makeMutable<style::Image::Impl>("one", PremultipliedImage({ 16, 16 }), 2));
|
|
imageManager.addImage(makeMutable<style::Image::Impl>("two", PremultipliedImage({ 16, 16 }), 2));
|
|
imageManager.addImage(makeMutable<style::Image::Impl>("three", PremultipliedImage({ 16, 16 }), 2));
|
|
|
|
imageManager.removeImage("one");
|
|
imageManager.removeImage("two");
|
|
|
|
EXPECT_NE(nullptr, imageManager.getImage("three"));
|
|
EXPECT_EQ(nullptr, imageManager.getImage("two"));
|
|
EXPECT_EQ(nullptr, imageManager.getImage("four"));
|
|
}
|
|
|
|
TEST(ImageManager, RemoveReleasesBinPackRect) {
|
|
FixtureLog log;
|
|
ImageManager imageManager;
|
|
|
|
imageManager.addImage(makeMutable<style::Image::Impl>("big", PremultipliedImage({ 32, 32 }), 1));
|
|
EXPECT_TRUE(imageManager.getImage("big"));
|
|
|
|
imageManager.removeImage("big");
|
|
|
|
imageManager.addImage(makeMutable<style::Image::Impl>("big", PremultipliedImage({ 32, 32 }), 1));
|
|
EXPECT_TRUE(imageManager.getImage("big"));
|
|
EXPECT_TRUE(log.empty());
|
|
}
|
|
|
|
class StubImageRequestor : public ImageRequestor {
|
|
public:
|
|
StubImageRequestor(ImageManager& imageManager) : ImageRequestor(imageManager) {}
|
|
|
|
void onImagesAvailable(ImageMap icons, ImageMap patterns, std::unordered_map<std::string, uint32_t> versionMap, uint64_t imageCorrelationID_) final {
|
|
if (imagesAvailable && imageCorrelationID == imageCorrelationID_) imagesAvailable(icons, patterns, versionMap);
|
|
}
|
|
|
|
std::function<void (ImageMap, ImageMap, std::unordered_map<std::string, uint32_t>)> imagesAvailable;
|
|
uint64_t imageCorrelationID = 0;
|
|
};
|
|
|
|
TEST(ImageManager, NotifiesRequestorWhenSpriteIsLoaded) {
|
|
util::RunLoop runLoop;
|
|
ImageManager imageManager;
|
|
StubImageRequestor requestor(imageManager);
|
|
bool notified = false;
|
|
|
|
ImageManagerObserver observer;
|
|
imageManager.setObserver(&observer);
|
|
|
|
requestor.imagesAvailable = [&] (ImageMap, ImageMap, std::unordered_map<std::string, uint32_t>) {
|
|
notified = true;
|
|
};
|
|
|
|
uint64_t imageCorrelationID = 0;
|
|
ImageDependencies dependencies;
|
|
dependencies.emplace("one", ImageType::Icon);
|
|
imageManager.getImages(requestor, std::make_pair(dependencies, imageCorrelationID));
|
|
runLoop.runOnce();
|
|
|
|
ASSERT_FALSE(notified);
|
|
|
|
imageManager.setLoaded(true);
|
|
runLoop.runOnce();
|
|
ASSERT_FALSE(notified);
|
|
imageManager.notifyIfMissingImageAdded();
|
|
runLoop.runOnce();
|
|
ASSERT_TRUE(notified);
|
|
}
|
|
|
|
TEST(ImageManager, NotifiesRequestorImmediatelyIfDependenciesAreSatisfied) {
|
|
ImageManager imageManager;
|
|
StubImageRequestor requestor(imageManager);
|
|
bool notified = false;
|
|
|
|
requestor.imagesAvailable = [&] (ImageMap, ImageMap, std::unordered_map<std::string, uint32_t>) {
|
|
notified = true;
|
|
};
|
|
|
|
uint64_t imageCorrelationID = 0;
|
|
ImageDependencies dependencies;
|
|
dependencies.emplace("one", ImageType::Icon);
|
|
imageManager.addImage(makeMutable<style::Image::Impl>("one", PremultipliedImage({ 16, 16 }), 2));
|
|
imageManager.getImages(requestor, std::make_pair(dependencies, imageCorrelationID));
|
|
|
|
ASSERT_TRUE(notified);
|
|
}
|
|
|
|
|
|
class StubImageManagerObserver : public ImageManagerObserver {
|
|
public:
|
|
int count = 0;
|
|
std::function<void (const std::string&)> imageMissing = [](const std::string&){};
|
|
virtual void onStyleImageMissing(const std::string& id, std::function<void()> done) override {
|
|
count++;
|
|
imageMissing(id);
|
|
done();
|
|
}
|
|
};
|
|
|
|
TEST(ImageManager, OnStyleImageMissingBeforeSpriteLoaded) {
|
|
util::RunLoop runLoop;
|
|
ImageManager imageManager;
|
|
StubImageRequestor requestor(imageManager);
|
|
StubImageManagerObserver observer;
|
|
|
|
imageManager.setObserver(&observer);
|
|
|
|
bool notified = false;
|
|
|
|
requestor.imagesAvailable = [&] (ImageMap, ImageMap, std::unordered_map<std::string, uint32_t>) {
|
|
notified = true;
|
|
};
|
|
|
|
uint64_t imageCorrelationID = 0;
|
|
ImageDependencies dependencies;
|
|
dependencies.emplace("pre", ImageType::Icon);
|
|
imageManager.getImages(requestor, std::make_pair(dependencies, imageCorrelationID));
|
|
runLoop.runOnce();
|
|
|
|
EXPECT_EQ(observer.count, 0);
|
|
ASSERT_FALSE(notified);
|
|
|
|
imageManager.setLoaded(true);
|
|
runLoop.runOnce();
|
|
|
|
EXPECT_EQ(observer.count, 1);
|
|
ASSERT_FALSE(notified);
|
|
|
|
imageManager.notifyIfMissingImageAdded();
|
|
runLoop.runOnce();
|
|
|
|
EXPECT_EQ(observer.count, 1);
|
|
ASSERT_TRUE(notified);
|
|
|
|
}
|
|
|
|
TEST(ImageManager, OnStyleImageMissingAfterSpriteLoaded) {
|
|
util::RunLoop runLoop;
|
|
ImageManager imageManager;
|
|
StubImageRequestor requestor(imageManager);
|
|
StubImageManagerObserver observer;
|
|
|
|
imageManager.setObserver(&observer);
|
|
|
|
bool notified = false;
|
|
|
|
requestor.imagesAvailable = [&] (ImageMap, ImageMap, std::unordered_map<std::string, uint32_t>) {
|
|
notified = true;
|
|
};
|
|
|
|
EXPECT_EQ(observer.count, 0);
|
|
ASSERT_FALSE(notified);
|
|
|
|
imageManager.setLoaded(true);
|
|
runLoop.runOnce();
|
|
|
|
uint64_t imageCorrelationID = 0;
|
|
ImageDependencies dependencies;
|
|
dependencies.emplace("after", ImageType::Icon);
|
|
imageManager.getImages(requestor, std::make_pair(dependencies, imageCorrelationID));
|
|
runLoop.runOnce();
|
|
|
|
EXPECT_EQ(observer.count, 1);
|
|
ASSERT_FALSE(notified);
|
|
|
|
imageManager.notifyIfMissingImageAdded();
|
|
runLoop.runOnce();
|
|
|
|
EXPECT_EQ(observer.count, 1);
|
|
ASSERT_TRUE(notified);
|
|
}
|
|
|
|
TEST(ImageManager, ReduceMemoryUsage) {
|
|
util::RunLoop runLoop;
|
|
ImageManager imageManager;
|
|
StubImageManagerObserver observer;
|
|
|
|
observer.imageMissing = [&imageManager] (const std::string& id) {
|
|
imageManager.addImage(makeMutable<style::Image::Impl>(id, PremultipliedImage({ 16, 16 }), 1));
|
|
};
|
|
|
|
imageManager.setObserver(&observer);
|
|
imageManager.setLoaded(true);
|
|
runLoop.runOnce();
|
|
|
|
// Single requestor
|
|
{
|
|
std::unique_ptr<StubImageRequestor> requestor = std::make_unique<StubImageRequestor>(imageManager);
|
|
imageManager.getImages(*requestor, std::make_pair(ImageDependencies{{"missing", ImageType::Icon}}, 0ull));
|
|
runLoop.runOnce();
|
|
EXPECT_EQ(observer.count, 1);
|
|
ASSERT_FALSE(imageManager.getImage("missing") == nullptr);
|
|
}
|
|
|
|
// Reduce memory usage and check that unused image was deleted.
|
|
imageManager.reduceMemoryUse();
|
|
runLoop.runOnce();
|
|
ASSERT_TRUE(imageManager.getImage("missing") == nullptr);
|
|
|
|
// Multiple requestors
|
|
{
|
|
std::unique_ptr<StubImageRequestor> requestor1 = std::make_unique<StubImageRequestor>(imageManager);
|
|
std::unique_ptr<StubImageRequestor> requestor2 = std::make_unique<StubImageRequestor>(imageManager);
|
|
imageManager.getImages(*requestor1, std::make_pair(ImageDependencies{{"missing", ImageType::Icon}}, 0ull));
|
|
imageManager.getImages(*requestor2, std::make_pair(ImageDependencies{{"missing", ImageType::Icon}}, 1ull));
|
|
runLoop.runOnce();
|
|
EXPECT_EQ(observer.count, 2);
|
|
ASSERT_FALSE(imageManager.getImage("missing") == nullptr);
|
|
}
|
|
|
|
// Reduce memory usage and check that unused image was deleted when all requestors are destructed.
|
|
imageManager.reduceMemoryUse();
|
|
runLoop.runOnce();
|
|
ASSERT_TRUE(imageManager.getImage("missing") == nullptr);
|
|
|
|
// Multiple requestors, check that image resource is not destroyed if there is at least 1 requestor that uses it.
|
|
std::unique_ptr<StubImageRequestor> requestor = std::make_unique<StubImageRequestor>(imageManager);
|
|
{
|
|
std::unique_ptr<StubImageRequestor> requestor1 = std::make_unique<StubImageRequestor>(imageManager);
|
|
imageManager.getImages(*requestor, std::make_pair(ImageDependencies{{"missing", ImageType::Icon}}, 0ull));
|
|
imageManager.getImages(*requestor1, std::make_pair(ImageDependencies{{"missing", ImageType::Icon}}, 1ull));
|
|
runLoop.runOnce();
|
|
EXPECT_EQ(observer.count, 3);
|
|
ASSERT_FALSE(imageManager.getImage("missing") == nullptr);
|
|
}
|
|
|
|
// Reduce memory usage and check that requested image is not destructed.
|
|
imageManager.reduceMemoryUse();
|
|
runLoop.runOnce();
|
|
ASSERT_FALSE(imageManager.getImage("missing") == nullptr);
|
|
|
|
// Release last requestor and check if resource was released after reduceMemoryUse().
|
|
requestor.reset();
|
|
imageManager.reduceMemoryUse();
|
|
runLoop.runOnce();
|
|
ASSERT_TRUE(imageManager.getImage("missing") == nullptr);
|
|
}
|