Files
MapLibreParticleLab/test/renderer/image_manager.test.cpp
Thiago Marcos P. Santos 06f1dc48a2 [core] Make the BackgroundScheduler a singleton
- Do not carry it over everywhere as parameter, it is a shared
  instance anyway and the lifecycle is pretty much the app lifecycle
  from the moment we instantiate a map.
- Rename to BackgroundScheduler because it is a Scheduler that will
  do tasks in the background, we don't make assumptions if it is a
  thread pool or a single thread.
- Most importantly, remove the dependency from `core` on `platform`.
2019-05-10 14:20:38 +03:00

317 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/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);
}