summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKonstantin Käfer <mail@kkaefer.com>2014-03-21 13:36:32 +0100
committerKonstantin Käfer <mail@kkaefer.com>2014-03-21 13:36:32 +0100
commit3413ee536d1e3fdd30aeb8a0105f713115302279 (patch)
treef5d622751328ab7233b3631ae167f781c6e7e292
parent4ee0cf1791f8f4b787793d2612c583bfb8b9c62f (diff)
parent0730ca640b095662a918fa55486c7b5bc4eba31c (diff)
downloadqtlocation-mapboxgl-3413ee536d1e3fdd30aeb8a0105f713115302279.tar.gz
Merge branch 'master' into sdf
Conflicts: include/llmr/map/tile_data.hpp include/llmr/shader/shaders.hpp src/map/map.cpp src/map/tile_data.cpp src/platform/platform.cpp src/shader/shaders.cpp
-rw-r--r--README.md4
-rw-r--r--include/llmr/map/coverage.hpp4
-rw-r--r--include/llmr/map/map.hpp7
-rw-r--r--include/llmr/map/tile.hpp4
-rw-r--r--include/llmr/map/tile_data.hpp6
-rw-r--r--include/llmr/platform/platform.hpp5
-rw-r--r--include/llmr/renderer/painter.hpp7
-rw-r--r--include/llmr/shader/raster_shader.hpp28
-rw-r--r--include/llmr/shader/shaders.hpp1
-rw-r--r--include/llmr/style/sprite.hpp14
-rw-r--r--include/llmr/util/raster.hpp72
-rw-r--r--include/llmr/util/texturepool.hpp25
-rw-r--r--ios/MBXViewController.mm14
-rw-r--r--src/map/coverage.cpp22
-rw-r--r--src/map/map.cpp38
-rw-r--r--src/map/tile.cpp15
-rw-r--r--src/map/tile_data.cpp23
-rw-r--r--src/platform/platform.cpp6
-rw-r--r--src/renderer/painter.cpp34
-rw-r--r--src/shader/raster.fragment.glsl8
-rw-r--r--src/shader/raster.vertex.glsl10
-rw-r--r--src/shader/raster_shader.cpp47
-rw-r--r--src/shader/shaders.cpp4
-rw-r--r--src/style/sprite.cpp170
-rw-r--r--src/util/raster.cpp191
-rw-r--r--src/util/texturepool.cpp56
26 files changed, 609 insertions, 206 deletions
diff --git a/README.md b/README.md
index 9dc7b28254..d4e070eb4e 100644
--- a/README.md
+++ b/README.md
@@ -115,8 +115,9 @@ This is automatically taken care of as a build phase if you are using the Xcode
## Desktop
-- Press 'R' to reset the transform
+- Press 'X' to reset the transform
- Press 'N' to reset north
+- Press 'R' to toggle raster
- Press Tab to toggle debug information
- Press Esc to quit
@@ -130,3 +131,4 @@ This is automatically taken care of as a build phase if you are using the Xcode
- Long-press to reset north
- Two-finger long press to reset the transform
- Three-finger long press to toggle debug information
+- Four-finger long press to toggle raster
diff --git a/include/llmr/map/coverage.hpp b/include/llmr/map/coverage.hpp
index c889ce7016..ed6317a560 100644
--- a/include/llmr/map/coverage.hpp
+++ b/include/llmr/map/coverage.hpp
@@ -1,13 +1,13 @@
#ifndef LLMR_MAP_COVERAGE
#define LLMR_MAP_COVERAGE
-#include <llmr/util/vec.hpp>
#include <llmr/map/tile.hpp>
+
#include <forward_list>
namespace llmr {
-std::forward_list<Tile::ID> covering_tiles(int32_t zoom, const box& points);
+std::forward_list<Tile::ID> covering_tiles(int32_t zoom, const box& points, const bool use_raster = false, const bool use_retina = false);
}
diff --git a/include/llmr/map/map.hpp b/include/llmr/map/map.hpp
index 3506f4f43b..b24a7bc5ae 100644
--- a/include/llmr/map/map.hpp
+++ b/include/llmr/map/map.hpp
@@ -9,6 +9,7 @@
#include <llmr/geometry/glyph_atlas.hpp>
#include <llmr/renderer/painter.hpp>
#include <llmr/util/noncopyable.hpp>
+#include <llmr/util/texturepool.hpp>
#include <cstdint>
#include <string>
@@ -25,9 +26,9 @@ public:
/* setup */
void setup(float pixelRatio = 1);
void loadStyle(const uint8_t *const data, uint32_t bytes);
- void loadSprite(const std::string& url);
void loadSettings();
void resize(uint32_t width, uint32_t height, uint32_t fb_width, uint32_t fb_height);
+ void toggleRaster();
/* callback */
bool render();
@@ -70,12 +71,12 @@ private:
TileData::State addTile(const Tile::ID& id);
TileData::State hasTile(const Tile::ID& id);
-
void update();
private:
Settings& settings;
Transform transform;
+ Texturepool texturepool;
Style style;
Painter painter;
GlyphAtlas glyphAtlas;
@@ -85,6 +86,8 @@ private:
float pixel_ratio;
+ bool use_raster = false;
+
std::forward_list<Tile> tiles;
std::forward_list<std::weak_ptr<TileData>> tile_data;
};
diff --git a/include/llmr/map/tile.hpp b/include/llmr/map/tile.hpp
index 2ae1a64fe1..0857cf17b5 100644
--- a/include/llmr/map/tile.hpp
+++ b/include/llmr/map/tile.hpp
@@ -18,7 +18,7 @@ public:
int8_t z = 0;
int32_t x = 0, y = 0;
- inline ID(uint8_t z, uint32_t x, uint32_t y)
+ inline ID(int8_t z, int32_t x, int32_t y)
: z(z), x(x), y(y) {
}
@@ -31,7 +31,7 @@ public:
return z == rhs.z && x == rhs.x && y == rhs.y;
}
- ID parent(int32_t z) const;
+ ID parent(int8_t z) const;
void normalize();
ID normalized() const;
std::forward_list<ID> children(int32_t z) const;
diff --git a/include/llmr/map/tile_data.hpp b/include/llmr/map/tile_data.hpp
index c136f334ca..c5a664eb16 100644
--- a/include/llmr/map/tile_data.hpp
+++ b/include/llmr/map/tile_data.hpp
@@ -22,6 +22,7 @@
namespace llmr {
class Style;
+class Raster;
class Bucket;
class LayerDescription;
class BucketDescription;
@@ -54,7 +55,7 @@ public:
};
public:
- TileData(Tile::ID id, const Style& style, GlyphAtlas& glyphAtlas);
+ TileData(Tile::ID id, const Style& style, GlyphAtlas& glyphAtlas, const bool use_raster = false, const bool use_retina = false);
~TileData();
void request();
@@ -65,7 +66,10 @@ public:
public:
const Tile::ID id;
+ const bool use_raster;
+ const bool use_retina;
std::atomic<State> state;
+ std::shared_ptr<Raster> raster;
// Holds the actual geometries in this tile.
DebugFontBuffer debugFontBuffer;
diff --git a/include/llmr/platform/platform.hpp b/include/llmr/platform/platform.hpp
index bb9e619cba..1724ebd358 100644
--- a/include/llmr/platform/platform.hpp
+++ b/include/llmr/platform/platform.hpp
@@ -7,8 +7,11 @@
namespace llmr {
-extern const char *kTileURL;
+extern const char *kTileVectorURL;
+extern const char *kTileRasterURL;
extern const char *kSpriteURL;
+extern const int32_t kTileVectorMaxZoom;
+extern const int32_t kTileRasterMaxZoom;
namespace platform {
diff --git a/include/llmr/renderer/painter.hpp b/include/llmr/renderer/painter.hpp
index 71bb606153..0f8118ca5f 100644
--- a/include/llmr/renderer/painter.hpp
+++ b/include/llmr/renderer/painter.hpp
@@ -13,7 +13,7 @@
#include <llmr/shader/line_shader.hpp>
#include <llmr/shader/linejoin_shader.hpp>
#include <llmr/shader/point_shader.hpp>
-
+#include <llmr/shader/raster_shader.hpp>
namespace llmr {
@@ -44,10 +44,11 @@ public:
void resize(int width, int height);
void prepareClippingMask();
- void drawClippingMask(const mat4& matrix, uint8_t clip_id);
+ void drawClippingMask(const mat4& matrix, uint8_t clip_id, bool opaque = true);
void finishClippingMask();
private:
void setupShaders();
+ void renderRaster(const std::shared_ptr<TileData>& tile);
void renderLayers(const std::shared_ptr<TileData>& tile, const std::vector<LayerDescription>& layers);
void renderLayer(const std::shared_ptr<TileData>& tile_data, const LayerDescription& layer_desc);
void renderDebug(const std::shared_ptr<TileData>& tile);
@@ -82,6 +83,7 @@ private:
std::unique_ptr<LinejoinShader> linejoinShader;
std::unique_ptr<PatternShader> patternShader;
std::unique_ptr<PointShader> pointShader;
+ std::unique_ptr<RasterShader> rasterShader;
// Set up the stencil quad we're using to generate the stencil mask.
VertexBuffer tileStencilBuffer = {
@@ -98,6 +100,7 @@ private:
VertexArrayObject coveringPlainArray;
VertexArrayObject coveringPatternArray;
+ VertexArrayObject coveringRasterArray;
// Set up the tile boundary lines we're using to draw the tile outlines.
VertexBuffer tileBorderBuffer = {
diff --git a/include/llmr/shader/raster_shader.hpp b/include/llmr/shader/raster_shader.hpp
new file mode 100644
index 0000000000..00f423cbfc
--- /dev/null
+++ b/include/llmr/shader/raster_shader.hpp
@@ -0,0 +1,28 @@
+#ifndef LLMR_RENDERER_SHADER_RASTER
+#define LLMR_RENDERER_SHADER_RASTER
+
+#include <llmr/shader/shader.hpp>
+
+namespace llmr {
+
+class RasterShader : public Shader {
+public:
+ RasterShader();
+
+ void bind(char *offset);
+
+ void setImage(int32_t image);
+ void setOpacity(float opacity);
+
+private:
+ int32_t a_pos = -1;
+
+ int32_t image = -1;
+ int32_t u_image = -1;
+ float opacity = 0.0f;
+ float u_opacity = 0.0f;
+};
+
+}
+
+#endif
diff --git a/include/llmr/shader/shaders.hpp b/include/llmr/shader/shaders.hpp
index 5cd4f493e8..4f85a33028 100644
--- a/include/llmr/shader/shaders.hpp
+++ b/include/llmr/shader/shaders.hpp
@@ -17,6 +17,7 @@ enum {
PATTERN_SHADER,
PLAIN_SHADER,
POINT_SHADER,
+ RASTER_SHADER,
TEXT_SHADER,
SHADER_COUNT
};
diff --git a/include/llmr/style/sprite.hpp b/include/llmr/style/sprite.hpp
index 78ca76f237..f7d281e209 100644
--- a/include/llmr/style/sprite.hpp
+++ b/include/llmr/style/sprite.hpp
@@ -1,13 +1,12 @@
#ifndef LLMR_STYLE_SPRITE
#define LLMR_STYLE_SPRITE
-
-#include <cstdint>
#include <map>
#include <string>
#include <mutex>
#include <memory>
+#include <llmr/util/raster.hpp>
#include <llmr/util/vec.hpp>
namespace llmr {
@@ -34,27 +33,24 @@ public:
class Sprite : public std::enable_shared_from_this<Sprite> {
public:
- ~Sprite();
+ Sprite();
void load(const std::string& base_url, float pixelRatio = 1);
- void bind(bool linear = false);
ImagePosition getPosition(const std::string& name, bool repeating = false);
operator bool() const;
+public:
+ std::shared_ptr<Raster> raster;
+
private:
void parseJSON(const std::string& data);
- void loadImage(const std::string& data);
private:
mutable std::mutex mtx;
bool loaded = false;
- uint32_t filter = 0;
- uint32_t texture = 0;
std::map<std::string, SpritePosition> pos;
- uint32_t width = 0, height = 0;
- char *img = nullptr;
};
}
diff --git a/include/llmr/util/raster.hpp b/include/llmr/util/raster.hpp
new file mode 100644
index 0000000000..cf69663ce0
--- /dev/null
+++ b/include/llmr/util/raster.hpp
@@ -0,0 +1,72 @@
+#ifndef LLMR_UTIL_RASTER
+#define LLMR_UTIL_RASTER
+
+#include <llmr/util/animation.hpp>
+#include <llmr/util/texturepool.hpp>
+
+#include <string>
+#include <mutex>
+
+namespace llmr {
+
+class Raster : public std::enable_shared_from_this<Raster> {
+
+public:
+ ~Raster();
+
+ // load image data
+ void load(const std::string& data);
+
+ // set shared texture pool
+ void setTexturepool(Texturepool* texturepool);
+
+ // bind current texture
+ void bind(bool linear = false);
+
+ // loaded status
+ operator bool() const;
+
+ // animations
+ void beginFadeInAnimation();
+ bool needsAnimation() const;
+ void updateAnimations();
+
+public:
+ // loaded image dimensions
+ uint32_t width = 0, height = 0;
+
+ // has been uploaded to texture
+ bool textured = false;
+
+ // the uploaded texture
+ uint32_t texture = 0;
+
+ // texture opacity
+ double opacity = 0;
+
+private:
+ // load raw pixels
+ void loadImage(const std::string& data);
+
+private:
+ mutable std::mutex mtx;
+
+ // raw pixels have been loaded
+ bool loaded = false;
+
+ // shared texture pool
+ Texturepool* texturepool = nullptr;
+
+ // min/mag filter
+ uint32_t filter = 0;
+
+ // the raw pixels
+ char *img = nullptr;
+
+ // fade in animation
+ std::shared_ptr<util::animation> fade_animation = nullptr;
+};
+
+}
+
+#endif
diff --git a/include/llmr/util/texturepool.hpp b/include/llmr/util/texturepool.hpp
new file mode 100644
index 0000000000..348d25d29a
--- /dev/null
+++ b/include/llmr/util/texturepool.hpp
@@ -0,0 +1,25 @@
+#ifndef LLMR_UTIL_TEXTUREPOOL
+#define LLMR_UTIL_TEXTUREPOOL
+
+#include <llmr/util/noncopyable.hpp>
+#include <llmr/platform/gl.hpp>
+
+#include <set>
+#include <mutex>
+
+namespace llmr {
+
+class Texturepool : private util::noncopyable {
+
+public:
+ GLuint getTextureID();
+ void removeTextureID(GLuint texture_id);
+ void clearTextureIDs();
+
+private:
+ std::set<GLuint> texture_ids;
+};
+
+}
+
+#endif
diff --git a/ios/MBXViewController.mm b/ios/MBXViewController.mm
index 6713f33457..7cc616c0e0 100644
--- a/ios/MBXViewController.mm
+++ b/ios/MBXViewController.mm
@@ -121,6 +121,10 @@ class MBXMapView
threeFingerLongPress.numberOfTouchesRequired = 3;
[self.view addGestureRecognizer:threeFingerLongPress];
+ UILongPressGestureRecognizer *fourFingerLongPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleFourFingerLongPressGesture:)];
+ fourFingerLongPress.numberOfTouchesRequired = 4;
+ [self.view addGestureRecognizer:fourFingerLongPress];
+
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)
{
UILongPressGestureRecognizer *quickZoom = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleQuickZoomGesture:)];
@@ -360,6 +364,16 @@ class MBXMapView
[self updateRender];
}
+- (void)handleFourFingerLongPressGesture:(UILongPressGestureRecognizer *)fourFingerLongPress
+{
+ mapView->map.cancelAnimations();
+
+ if (fourFingerLongPress.state == UIGestureRecognizerStateBegan)
+ mapView->map.toggleRaster();
+
+ [self updateRender];
+}
+
- (void)handleQuickZoomGesture:(UILongPressGestureRecognizer *)quickZoom
{
mapView->map.cancelAnimations();
diff --git a/src/map/coverage.cpp b/src/map/coverage.cpp
index c040811b52..b98518a588 100644
--- a/src/map/coverage.cpp
+++ b/src/map/coverage.cpp
@@ -1,4 +1,7 @@
#include <llmr/map/coverage.hpp>
+#include <llmr/util/constants.hpp>
+#include <llmr/util/vec.hpp>
+
#include <functional>
#include <cmath>
@@ -64,15 +67,28 @@ void _scanTriangle(const llmr::vec2<double> a, const llmr::vec2<double> b, const
if (bc.dy) _scanSpans(ca, bc, ymin, ymax, scanLine);
}
-std::forward_list<llmr::Tile::ID> llmr::covering_tiles(int32_t zoom, const box& points) {
+std::forward_list<llmr::Tile::ID> llmr::covering_tiles(int32_t zoom, const box& points, const bool use_raster, const bool use_retina) {
int32_t dim = pow(2, zoom);
std::forward_list<llmr::Tile::ID> tiles;
- auto scanLine = [&tiles, zoom](int32_t x0, int32_t x1, int32_t y, int32_t ymax) {
+ auto scanLine = [&tiles, zoom, use_raster, use_retina](int32_t x0, int32_t x1, int32_t y, int32_t ymax) {
int32_t x;
if (y >= 0 && y <= ymax) {
for (x = x0; x < x1; x++) {
- tiles.emplace_front(zoom, x, y);
+ if (use_raster) {
+ uint32_t search_zoom = zoom;
+ search_zoom += (uint32_t)((llmr::util::tileSize / 256.0f) - 1.0f);
+ if (use_retina) {
+ search_zoom += 1;
+ }
+ Tile::ID id = Tile::ID(zoom, x, y);
+ auto ids = id.children(search_zoom);
+ for (const Tile::ID& child_id : ids) {
+ tiles.emplace_front(child_id.z, child_id.x, child_id.y);
+ }
+ } else {
+ tiles.emplace_front(zoom, x, y);
+ }
}
}
};
diff --git a/src/map/map.cpp b/src/map/map.cpp
index 1a9e6c609b..961cf7423c 100644
--- a/src/map/map.cpp
+++ b/src/map/map.cpp
@@ -4,6 +4,7 @@
#include <llmr/style/resources.hpp>
#include <llmr/style/sprite.hpp>
#include <llmr/map/coverage.hpp>
+#include <llmr/util/animation.hpp>
#include <algorithm>
@@ -12,11 +13,12 @@ using namespace llmr;
Map::Map(Settings& settings)
: settings(settings),
transform(),
+ texturepool(),
style(),
painter(transform, settings, style),
glyphAtlas(1024, 1024),
min_zoom(0),
- max_zoom(14) {
+ max_zoom((use_raster ? kTileRasterMaxZoom : kTileVectorMaxZoom)) {
}
Map::~Map() {
@@ -24,6 +26,7 @@ Map::~Map() {
}
void Map::setup(float pixelRatio) {
+
painter.setup();
pixel_ratio = pixelRatio;
@@ -34,10 +37,6 @@ void Map::setup(float pixelRatio) {
style.loadJSON(resources::style, resources::style_size);
}
-void Map::loadSprite(const std::string& url) {
-
-}
-
void Map::loadStyle(const uint8_t *const data, uint32_t bytes) {
style.loadJSON(data, bytes);
update();
@@ -61,6 +60,13 @@ void Map::resize(uint32_t width, uint32_t height, uint32_t fb_width, uint32_t fb
update();
}
+void Map::toggleRaster() {
+ use_raster = ! use_raster;
+ max_zoom = (use_raster ? kTileRasterMaxZoom : kTileVectorMaxZoom);
+ tiles.clear();
+ update();
+}
+
void Map::moveBy(double dx, double dy, double duration) {
transform.moveBy(dx, dy, duration);
update();
@@ -279,7 +285,7 @@ TileData::State Map::addTile(const Tile::ID& id) {
if (!new_tile.data) {
// If we don't find working tile data, we're just going to load it.
- new_tile.data = std::make_shared<TileData>(normalized_id, style, glyphAtlas);
+ new_tile.data = std::make_shared<TileData>(normalized_id, style, glyphAtlas, use_raster, (pixel_ratio > 1.0));
new_tile.data->request();
tile_data.push_front(new_tile.data);
}
@@ -353,12 +359,14 @@ bool Map::updateTiles() {
int32_t min_covering_zoom = zoom - 10;
if (min_covering_zoom < min_zoom) min_covering_zoom = min_zoom;
+ int32_t max_dim = pow(2, zoom);
+
// Map four viewport corners to pixel coordinates
box box = transform.mapCornersToBox(zoom);
// Performs a scanline algorithm search that covers the rectangle of the box
// and sorts them by proximity to the center.
- std::forward_list<Tile::ID> required = llmr::covering_tiles(zoom, box);
+ std::forward_list<Tile::ID> required = llmr::covering_tiles(zoom, box, use_raster, (pixel_ratio > 1.0));
// Retain is a list of tiles that we shouldn't delete, even if they are not
// the most ideal tile for the current viewport. This may include tiles like
@@ -370,6 +378,9 @@ bool Map::updateTiles() {
const TileData::State state = addTile(id);
if (state != TileData::State::parsed) {
+ if (use_raster && (transform.rotating || transform.scaling || transform.panning))
+ break;
+
// The tile we require is not yet loaded. Try to find a parent or
// child tile that we already have.
@@ -434,6 +445,10 @@ bool Map::render() {
glyphAtlas.bind();
+ if (*style.sprite->raster && !style.sprite->raster->textured) {
+ style.sprite->raster->setTexturepool(&texturepool);
+ }
+
bool changed = updateTiles();
painter.clear();
@@ -450,13 +465,20 @@ bool Map::render() {
transform.matrixFor(tile.matrix, tile.id);
matrix::multiply(tile.matrix, painter.projMatrix, tile.matrix);
tile.clip_id = i++;
- painter.drawClippingMask(tile.matrix, tile.clip_id);
+ painter.drawClippingMask(tile.matrix, tile.clip_id, !tile.data->use_raster);
}
}
painter.finishClippingMask();
for (const Tile& tile : tiles) {
if (tile.data && tile.data->state == TileData::State::parsed) {
+ if (tile.data->use_raster && *tile.data->raster && !tile.data->raster->textured) {
+ tile.data->raster->setTexturepool(&texturepool);
+ tile.data->raster->beginFadeInAnimation();
+ }
+ if (tile.data->use_raster && tile.data->raster->needsAnimation()) {
+ tile.data->raster->updateAnimations();
+ }
painter.render(tile);
}
}
diff --git a/src/map/tile.cpp b/src/map/tile.cpp
index 163aa60afd..6ec05beca7 100644
--- a/src/map/tile.cpp
+++ b/src/map/tile.cpp
@@ -8,15 +8,14 @@ Tile::Tile(const ID& id)
: id(id) {
}
-Tile::ID Tile::ID::parent(int32_t parent_z) const {
+Tile::ID Tile::ID::parent(int8_t parent_z) const {
assert(parent_z < z);
- ID pos(z, x, y);
- while (pos.z > parent_z) {
- --pos.z;
- pos.x = floor(pos.x / 2);
- pos.y = floor(pos.y / 2);
- }
- return pos;
+ int32_t dim = pow(2, z - parent_z);
+ return Tile::ID{
+ parent_z,
+ (x >= 0 ? x : x - dim + 1) / dim,
+ y / dim
+ };
}
std::forward_list<Tile::ID> Tile::ID::children(int32_t child_z) const {
diff --git a/src/map/tile_data.cpp b/src/map/tile_data.cpp
index 57c8885e38..7b070f8696 100644
--- a/src/map/tile_data.cpp
+++ b/src/map/tile_data.cpp
@@ -6,13 +6,17 @@
#include <llmr/geometry/point_buffer.hpp>
#include <llmr/geometry/text_buffer.hpp>
#include <llmr/geometry/elements_buffer.hpp>
+#include <llmr/util/raster.hpp>
#include <llmr/util/string.hpp>
using namespace llmr;
-TileData::TileData(Tile::ID id, const Style& style, GlyphAtlas& glyphAtlas)
+TileData::TileData(Tile::ID id, const Style& style, GlyphAtlas& glyphAtlas, const bool use_raster, const bool use_retina)
: id(id),
+ use_raster(use_raster),
+ use_retina(use_retina),
state(State::initial),
+ raster(),
fillVertexBuffer(std::make_shared<FillVertexBuffer>()),
lineVertexBuffer(std::make_shared<LineVertexBuffer>()),
pointVertexBuffer(std::make_shared<PointVertexBuffer>()),
@@ -41,9 +45,13 @@ const std::string TileData::toString() const {
void TileData::request() {
state = State::loading;
- // Create http request
- std::string url = util::sprintf(kTileURL,
- id.z, id.x, id.y);
+ std::string url;
+
+ if (use_raster) {
+ url = util::sprintf(kTileRasterURL, id.z, id.x, id.y, (use_retina ? "@2x" : ""));
+ } else {
+ url = util::sprintf(kTileVectorURL, id.z, id.x, id.y);
+ }
// Note: Somehow this feels slower than the change to request_http()
std::weak_ptr<TileData> weak_tile = shared_from_this();
@@ -77,6 +85,13 @@ bool TileData::parse() {
return false;
}
+ if (use_raster) {
+ raster = std::make_shared<Raster>();
+ raster->load(data);
+ state = State::parsed;
+ return true;
+ }
+
try {
// Parsing creates state that is encapsulated in TileParser. While parsing,
// the TileParser object writes results into this objects. All other state
diff --git a/src/platform/platform.cpp b/src/platform/platform.cpp
index e2d6d828cf..6d0f845bde 100644
--- a/src/platform/platform.cpp
+++ b/src/platform/platform.cpp
@@ -1,4 +1,8 @@
#include <llmr/platform/platform.hpp>
-const char *llmr::kTileURL = "http://localhost:3333/v3/mapbox.mapbox-streets-v4/%d/%d/%d.gl.pbf";
+const char *llmr::kTileVectorURL = "http://localhost:3333/v3/mapbox.mapbox-streets-v4/%d/%d/%d.gl.pbf";
+const char *llmr::kTileRasterURL = "https://a.tiles.mapbox.com/v3/justin.map-pgygbwdm/%d/%d/%d%s.png256";
const char *llmr::kSpriteURL = "http://localhost:3333/images/sprite";
+
+const int32_t llmr::kTileVectorMaxZoom = 14;
+const int32_t llmr::kTileRasterMaxZoom = 21;
diff --git a/src/renderer/painter.cpp b/src/renderer/painter.cpp
index ed7cc506e3..4e625ab435 100644
--- a/src/renderer/painter.cpp
+++ b/src/renderer/painter.cpp
@@ -16,6 +16,7 @@
#include <llmr/platform/gl.hpp>
#include <llmr/style/style.hpp>
#include <llmr/style/sprite.hpp>
+#include <llmr/util/raster.hpp>
using namespace llmr;
@@ -27,7 +28,6 @@ Painter::Painter(Transform& transform, Settings& settings, Style& style)
style(style) {
}
-
void Painter::setup() {
setupShaders();
@@ -37,6 +37,7 @@ void Painter::setup() {
assert(lineShader);
assert(linejoinShader);
assert(patternShader);
+ assert(rasterShader);
// Blending
@@ -63,6 +64,7 @@ void Painter::setupShaders() {
linejoinShader = std::make_unique<LinejoinShader>();
patternShader = std::make_unique<PatternShader>();
pointShader = std::make_unique<PointShader>();
+ rasterShader = std::make_unique<RasterShader>();
}
void Painter::resize(int width, int height) {
@@ -114,9 +116,11 @@ void Painter::prepareClippingMask() {
coveringPlainArray.bind(*plainShader, tileStencilBuffer, BUFFER_OFFSET(0));
}
-void Painter::drawClippingMask(const mat4& matrix, uint8_t clip_id) {
+void Painter::drawClippingMask(const mat4& matrix, uint8_t clip_id, bool opaque) {
plainShader->setMatrix(matrix);
- plainShader->setColor(style.computed.background.color);
+ if (opaque) {
+ plainShader->setColor(style.computed.background.color);
+ }
glStencilFunc(GL_ALWAYS, clip_id, 0xFF);
glDrawArrays(GL_TRIANGLES, 0, (GLsizei)tileStencilBuffer.index());
@@ -149,13 +153,29 @@ void Painter::render(const Tile& tile) {
matrix = tile.matrix;
glStencilFunc(GL_EQUAL, tile.clip_id, 0xFF);
- renderLayers(tile.data, style.layers);
+ if (tile.data->use_raster) {
+ renderRaster(tile.data);
+ } else {
+ renderLayers(tile.data, style.layers);
+ }
if (settings.debug) {
renderDebug(tile.data);
}
}
+void Painter::renderRaster(const std::shared_ptr<TileData>& tile_data) {
+ useProgram(rasterShader->program);
+ rasterShader->setMatrix(matrix);
+ rasterShader->setImage(0);
+ rasterShader->setOpacity(tile_data->raster->opacity);
+ tile_data->raster->bind(true);
+
+ coveringRasterArray.bind(*rasterShader, tileStencilBuffer, BUFFER_OFFSET(0));
+ glDepthRange(strata, 1.0f);
+ glDrawArrays(GL_TRIANGLES, 0, (GLsizei)tileStencilBuffer.index());
+}
+
void Painter::renderLayers(const std::shared_ptr<TileData>& tile_data, const std::vector<LayerDescription>& layers) {
float strata_thickness = 1.0f / (layers.size() + 1);
@@ -290,7 +310,7 @@ void Painter::renderFill(FillBucket& bucket, const std::string& layer_name, cons
patternShader->setPatternBottomRight({{ imagePos.br.x, imagePos.br.y }});
patternShader->setColor(fill_color);
patternShader->setMix(mix);
- style.sprite->bind(true);
+ style.sprite->raster->bind(true);
// Draw the actual triangles into the color & stencil buffer.
glDepthRange(strata + strata_epsilon, 1.0f);
@@ -447,7 +467,9 @@ void Painter::renderPoint(PointBucket& bucket, const std::string& layer_name, co
#endif
pointShader->setPointTopLeft({{ imagePos.tl.x, imagePos.tl.y }});
pointShader->setPointBottomRight({{ imagePos.br.x, imagePos.br.y }});
- style.sprite->bind(transform.rotating || transform.scaling || transform.panning);
+ if (*style.sprite->raster) {
+ style.sprite->raster->bind(transform.rotating || transform.scaling || transform.panning);
+ }
glDepthRange(strata, 1.0f);
bucket.drawPoints(*pointShader);
}
diff --git a/src/shader/raster.fragment.glsl b/src/shader/raster.fragment.glsl
new file mode 100644
index 0000000000..feb0a58f07
--- /dev/null
+++ b/src/shader/raster.fragment.glsl
@@ -0,0 +1,8 @@
+uniform sampler2D u_image;
+uniform float u_opacity;
+
+varying vec2 v_pos;
+
+void main() {
+ gl_FragColor = texture2D(u_image, v_pos) * u_opacity;
+}
diff --git a/src/shader/raster.vertex.glsl b/src/shader/raster.vertex.glsl
new file mode 100644
index 0000000000..75d6c8d145
--- /dev/null
+++ b/src/shader/raster.vertex.glsl
@@ -0,0 +1,10 @@
+uniform mat4 u_matrix;
+
+attribute vec2 a_pos;
+
+varying vec2 v_pos;
+
+void main() {
+ gl_Position = u_matrix * vec4(a_pos, 0, 1);
+ v_pos = a_pos / 4096.0;
+}
diff --git a/src/shader/raster_shader.cpp b/src/shader/raster_shader.cpp
new file mode 100644
index 0000000000..5852c45c7b
--- /dev/null
+++ b/src/shader/raster_shader.cpp
@@ -0,0 +1,47 @@
+#include <llmr/shader/raster_shader.hpp>
+#include <llmr/shader/shaders.hpp>
+#include <llmr/platform/gl.hpp>
+
+#include <cstdio>
+
+using namespace llmr;
+
+RasterShader::RasterShader()
+ : Shader(
+ shaders[RASTER_SHADER].vertex,
+ shaders[RASTER_SHADER].fragment
+ ) {
+ if (!valid) {
+ fprintf(stderr, "invalid raster shader\n");
+ return;
+ }
+
+ a_pos = glGetAttribLocation(program, "a_pos");
+
+ u_matrix = glGetUniformLocation(program, "u_matrix");
+ u_opacity = glGetUniformLocation(program, "u_opacity");
+
+ // fprintf(stderr, "RasterShader:\n");
+ // fprintf(stderr, " - u_matrix: %d\n", u_matrix);
+ // fprintf(stderr, " - u_image: %d\n", u_image);
+ // fprintf(stderr, " - u_opacity: %f\n", u_opacity);
+}
+
+void RasterShader::bind(char *offset) {
+ glEnableVertexAttribArray(a_pos);
+ glVertexAttribPointer(a_pos, 2, GL_SHORT, false, 0, offset);
+}
+
+void RasterShader::setImage(int32_t new_image) {
+ if (image != new_image) {
+ glUniform1i(u_image, new_image);
+ image = new_image;
+ }
+}
+
+void RasterShader::setOpacity(float new_opacity) {
+ if (opacity != new_opacity) {
+ glUniform1f(u_opacity, new_opacity);
+ opacity = new_opacity;
+ }
+}
diff --git a/src/shader/shaders.cpp b/src/shader/shaders.cpp
index f5cc884c26..ff83a1af27 100644
--- a/src/shader/shaders.cpp
+++ b/src/shader/shaders.cpp
@@ -29,6 +29,10 @@ const shader_source llmr::shaders[SHADER_COUNT] = {
"#define root2 1.4142135623730951\n\nuniform sampler2D u_image;\nuniform vec2 u_tl;\nuniform vec2 u_br;\nuniform vec4 u_color;\n\nuniform vec2 pos;\nuniform float inbounds;\nuniform vec4 color;\n\nvoid main() {\n vec2 pos = (gl_PointCoord * 2.0 - 1.0) * root2 / 2.0 + 0.5;\n\n float inbounds = step(0.0, pos.x) * step(0.0, pos.y) *\n (1.0 - step(1.0, pos.x)) * (1.0 - step(1.0, pos.y));\n\n vec4 color = texture2D(u_image, mix(u_tl, u_br, pos)) * inbounds;\n\n gl_FragColor = color * u_color;\n}\n",
},
{
+ "uniform mat4 u_matrix;\n\nattribute vec2 a_pos;\n\nvarying vec2 v_pos;\n\nvoid main() {\n gl_Position = u_matrix * vec4(a_pos, 0, 1);\n v_pos = a_pos / 4096.0;\n}\n",
+ "uniform sampler2D u_image;\nuniform float u_opacity;\n\nvarying vec2 v_pos;\n\nvoid main() {\n gl_FragColor = texture2D(u_image, v_pos) * u_opacity;\n}\n",
+ },
+ {
"attribute vec2 a_pos;\n\nuniform mat4 u_matrix;\n\nvoid main() {\n gl_Position = u_matrix * vec4(a_pos, 0, 1);\n}\n",
"uniform vec4 u_color;\n\nvoid main() {\n gl_FragColor = u_color;\n}\n",
}
diff --git a/src/style/sprite.cpp b/src/style/sprite.cpp
index 2052c54370..3998afc628 100644
--- a/src/style/sprite.cpp
+++ b/src/style/sprite.cpp
@@ -1,10 +1,8 @@
#include <llmr/style/sprite.hpp>
+#include <llmr/util/raster.hpp>
#include <string>
#include <llmr/platform/platform.hpp>
-#include <llmr/platform/gl.hpp>
-
-#include <png.h>
#include <rapidjson/document.h>
@@ -22,16 +20,8 @@ ImagePosition::ImagePosition(const vec2<uint16_t>& size, vec2<float> tl, vec2<fl
tl(tl),
br(br) {}
-
-Sprite::~Sprite() {
- if (img) {
- free(img);
- }
-}
-
-Sprite::operator bool() const {
- std::lock_guard<std::mutex> lock(mtx);
- return loaded;
+Sprite::Sprite() {
+ raster = std::make_shared<Raster>();
}
void Sprite::load(const std::string& base_url, float pixelRatio) {
@@ -39,7 +29,7 @@ void Sprite::load(const std::string& base_url, float pixelRatio) {
auto complete = [sprite]() {
std::lock_guard<std::mutex> lock(sprite->mtx);
- if (sprite->img && sprite->pos.size()) {
+ if (*sprite->raster && sprite->pos.size()) {
sprite->loaded = true;
platform::restart();
fprintf(stderr, "sprite loaded\n");
@@ -58,13 +48,17 @@ void Sprite::load(const std::string& base_url, float pixelRatio) {
platform::request_http(base_url + suffix + ".png", [sprite](const platform::Response & res) {
if (res.code == 200) {
- sprite->loadImage(res.body);
+ sprite->raster->load(res.body);
} else {
fprintf(stderr, "failed to load sprite image\n");
}
}, complete);
}
+Sprite::operator bool() const {
+ std::lock_guard<std::mutex> lock(mtx);
+ return loaded;
+}
void Sprite::parseJSON(const std::string& data) {
std::lock_guard<std::mutex> lock(mtx);
@@ -96,118 +90,8 @@ void Sprite::parseJSON(const std::string& data) {
}
}
-struct Buffer {
- Buffer(const std::string& data)
- : data(data.data()), length(data.size()) {}
- const char *const data = 0;
- const size_t length = 0;
- size_t pos = 0;
-};
-
-
-void readCallback(png_structp png, png_bytep data, png_size_t length) {
- Buffer *reader = static_cast<Buffer *>(png_get_io_ptr(png));
-
- // Read `length` bytes into `data`.
- if (reader->pos + length > reader->length) {
- png_error(png, "Read Error");
- } else {
- memcpy(data, reader->data + reader->pos, length);
- reader->pos += length;
- }
-}
-
-void errorHandler(png_structp png, png_const_charp error_msg) {
- throw std::runtime_error(error_msg);
-}
-
-void warningHandler(png_structp png, png_const_charp error_msg) {
- fprintf(stderr, "PNG: %s\n", error_msg);
-}
-
-void Sprite::loadImage(const std::string& data) {
- std::lock_guard<std::mutex> lock(mtx);
-
- Buffer buffer(data);
-
- if (buffer.length < 8 || !png_check_sig((const png_bytep)buffer.data, 8)) {
- fprintf(stderr, "image is not a valid PNG image\n");
- return;
- }
-
- png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, errorHandler, warningHandler);
- assert(png);
-
- png_infop info = png_create_info_struct(png);
- assert(info);
-
- int depth, color, interlace;
-
- try {
- png_set_read_fn(png, (png_voidp)&buffer, readCallback);
- png_read_info(png, info);
- png_get_IHDR(png, info, (png_uint_32*)&width, (png_uint_32*)&height, &depth, &color, &interlace, nullptr, nullptr);
- bool alpha = (color & PNG_COLOR_MASK_ALPHA) || png_get_valid(png, info, PNG_INFO_tRNS);
-
- // From http://trac.mapnik.org/browser/trunk/src/png_reader.cpp
- if (color == PNG_COLOR_TYPE_PALETTE)
- png_set_expand(png);
- if (color == PNG_COLOR_TYPE_GRAY)
- png_set_expand(png);
- if (png_get_valid(png, info, PNG_INFO_tRNS))
- png_set_expand(png);
- if (depth == 16)
- png_set_strip_16(png);
- if (depth < 8)
- png_set_packing(png);
- if (color == PNG_COLOR_TYPE_GRAY ||
- color == PNG_COLOR_TYPE_GRAY_ALPHA)
- png_set_gray_to_rgb(png);
-
- if (interlace == PNG_INTERLACE_ADAM7)
- png_set_interlace_handling(png);
-
- // Always add an alpha channel.
- if (!alpha) {
- png_set_add_alpha(png, 0xFF, PNG_FILLER_AFTER);
- }
-
- double gamma;
- if (png_get_gAMA(png, info, &gamma))
- png_set_gamma(png, 2.2, gamma);
-
- png_read_update_info(png, info);
-
- png_size_t rowbytes = png_get_rowbytes(png, info);
- assert(width * 4 == rowbytes);
-
- img = (char *)malloc(width * height * 4);
- char *surface = img;
- assert(surface);
-
- png_bytep row_pointers[height];
- for (unsigned i = 0; i < height; ++i) {
- row_pointers[i] = (png_bytep)(surface + (i * rowbytes));
- }
-
- // Read image data
- png_read_image(png, row_pointers);
-
- png_read_end(png, nullptr);
- } catch (std::exception& e) {
- fprintf(stderr, "loading PNG failed: %s\n", e.what());
- png_destroy_read_struct(&png, &info, nullptr);
- if (img) {
- free(img);
- img = nullptr;
- }
- width = 0;
- height = 0;
- }
-}
-
ImagePosition Sprite::getPosition(const std::string& name, bool repeating) {
- if (!*this) return {};
+ if (!*this->raster) return {};
// `repeating` indicates that the image will be used in a repeating pattern
// repeating pattern images are assumed to have a 1px padding that mirrors the opposite edge
@@ -224,38 +108,12 @@ ImagePosition Sprite::getPosition(const std::string& name, bool repeating) {
pos.height
},
{
- (float)(pos.x + offset) / width,
- (float)(pos.y + offset) / height
+ (float)(pos.x + offset) / raster->width,
+ (float)(pos.y + offset) / raster->height
},
{
- (float)(pos.x + pos.width - 2 * offset) / width,
- (float)(pos.y + pos.height - 2 * offset) / height
+ (float)(pos.x + pos.width - 2 * offset) / raster->width,
+ (float)(pos.y + pos.height - 2 * offset) / raster->height
}
};
}
-
-void Sprite::bind(bool linear) {
- if (!width || !height) {
- // fprintf(stderr, "trying to bind texture without dimension\n");
- return;
- }
-
- if (!texture) {
- glGenTextures(1, &texture);
- glActiveTexture(GL_TEXTURE0);
- glBindTexture(GL_TEXTURE_2D, texture);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, img);
- } else {
- glActiveTexture(GL_TEXTURE0);
- glBindTexture(GL_TEXTURE_2D, texture);
- }
-
- GLuint filter = linear ? GL_LINEAR : GL_NEAREST;
- if (filter != this->filter) {
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
- this->filter = filter;
- }
-}
diff --git a/src/util/raster.cpp b/src/util/raster.cpp
new file mode 100644
index 0000000000..566f0941e7
--- /dev/null
+++ b/src/util/raster.cpp
@@ -0,0 +1,191 @@
+#include <llmr/util/raster.hpp>
+
+#include <memory>
+#include <cassert>
+
+#include <llmr/platform/gl.hpp>
+
+#include <png.h>
+
+using namespace llmr;
+
+Raster::~Raster() {
+ if (img && !textured) {
+ free(img);
+ } else if (textured) {
+ texturepool->removeTextureID(texture);
+ }
+}
+
+Raster::operator bool() const {
+ std::lock_guard<std::mutex> lock(mtx);
+ return loaded;
+}
+
+void Raster::load(const std::string& data) {
+ std::shared_ptr<Raster> raster = shared_from_this();
+
+ raster->loadImage(data);
+
+ std::lock_guard<std::mutex> lock(raster->mtx);
+ if (raster->img) {
+ raster->loaded = true;
+ }
+}
+
+struct Buffer {
+ Buffer(const std::string& data)
+ : data(data.data()), length(data.size()) {}
+ const char *const data = 0;
+ const size_t length = 0;
+ size_t pos = 0;
+};
+
+void readCallback(png_structp png, png_bytep data, png_size_t length) {
+ Buffer *reader = static_cast<Buffer *>(png_get_io_ptr(png));
+
+ // Read `length` bytes into `data`.
+ if (reader->pos + length > reader->length) {
+ png_error(png, "Read Error");
+ } else {
+ memcpy(data, reader->data + reader->pos, length);
+ reader->pos += length;
+ }
+}
+
+void errorHandler(png_structp png, png_const_charp error_msg) {
+ throw std::runtime_error(error_msg);
+}
+
+void warningHandler(png_structp png, png_const_charp error_msg) {
+ fprintf(stderr, "PNG: %s\n", error_msg);
+}
+
+void Raster::loadImage(const std::string& data) {
+ std::lock_guard<std::mutex> lock(mtx);
+
+ Buffer buffer(data);
+
+ if (buffer.length < 8 || !png_check_sig((const png_bytep)buffer.data, 8)) {
+ fprintf(stderr, "image is not a valid PNG image\n");
+ return;
+ }
+
+ png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, errorHandler, warningHandler);
+ assert(png);
+
+ png_infop info = png_create_info_struct(png);
+ assert(info);
+
+ int depth, color, interlace;
+
+ try {
+ png_set_read_fn(png, (png_voidp)&buffer, readCallback);
+ png_read_info(png, info);
+ png_get_IHDR(png, info, (png_uint_32*)&width, (png_uint_32*)&height, &depth, &color, &interlace, nullptr, nullptr);
+ bool alpha = (color & PNG_COLOR_MASK_ALPHA) || png_get_valid(png, info, PNG_INFO_tRNS);
+
+ // From http://trac.mapnik.org/browser/trunk/src/png_reader.cpp
+ if (color == PNG_COLOR_TYPE_PALETTE)
+ png_set_expand(png);
+ if (color == PNG_COLOR_TYPE_GRAY)
+ png_set_expand(png);
+ if (png_get_valid(png, info, PNG_INFO_tRNS))
+ png_set_expand(png);
+ if (depth == 16)
+ png_set_strip_16(png);
+ if (depth < 8)
+ png_set_packing(png);
+ if (color == PNG_COLOR_TYPE_GRAY ||
+ color == PNG_COLOR_TYPE_GRAY_ALPHA)
+ png_set_gray_to_rgb(png);
+
+ if (interlace == PNG_INTERLACE_ADAM7)
+ png_set_interlace_handling(png);
+
+ // Always add an alpha channel.
+ if (!alpha) {
+ png_set_add_alpha(png, 0xFF, PNG_FILLER_AFTER);
+ }
+
+ double gamma;
+ if (png_get_gAMA(png, info, &gamma))
+ png_set_gamma(png, 2.2, gamma);
+
+ png_read_update_info(png, info);
+
+ png_size_t rowbytes = png_get_rowbytes(png, info);
+ assert(width * 4 == rowbytes);
+
+ img = (char *)malloc(width * height * 4);
+ char *surface = img;
+ assert(surface);
+
+ png_bytep row_pointers[height];
+ for (unsigned i = 0; i < height; ++i) {
+ row_pointers[i] = (png_bytep)(surface + (i * rowbytes));
+ }
+
+ // Read image data
+ png_read_image(png, row_pointers);
+
+ png_read_end(png, nullptr);
+
+ png_destroy_read_struct(&png, &info, nullptr);
+ } catch (std::exception& e) {
+ fprintf(stderr, "loading PNG failed: %s\n", e.what());
+ png_destroy_read_struct(&png, &info, nullptr);
+ if (img) {
+ free(img);
+ img = nullptr;
+ }
+ width = 0;
+ height = 0;
+ }
+}
+
+void Raster::setTexturepool(Texturepool* new_texturepool) {
+ texturepool = new_texturepool;
+}
+
+void Raster::bind(bool linear) {
+ if (!width || !height) {
+ fprintf(stderr, "trying to bind texture without dimension\n");
+ return;
+ }
+
+ if (img && !textured && texturepool) {
+ texture = texturepool->getTextureID();
+ glBindTexture(GL_TEXTURE_2D, texture);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, img);
+ free(img);
+ img = nullptr;
+ textured = true;
+ } else {
+ glBindTexture(GL_TEXTURE_2D, texture);
+ }
+
+ GLuint filter = linear ? GL_LINEAR : GL_NEAREST;
+ if (filter != this->filter) {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
+ this->filter = filter;
+ }
+}
+
+void Raster::beginFadeInAnimation() {
+ fade_animation = std::make_shared<util::ease_animation>(opacity, 1.0, opacity, 0.25);
+}
+
+bool Raster::needsAnimation() const {
+ return fade_animation != nullptr;
+}
+
+void Raster::updateAnimations() {
+ if (fade_animation->update() == util::animation::complete) {
+ fade_animation = nullptr;
+ }
+}
+
diff --git a/src/util/texturepool.cpp b/src/util/texturepool.cpp
new file mode 100644
index 0000000000..c0857caa4d
--- /dev/null
+++ b/src/util/texturepool.cpp
@@ -0,0 +1,56 @@
+#include <llmr/util/texturepool.hpp>
+
+#include <vector>
+
+#define TextureMax 64
+
+using namespace llmr;
+
+GLuint Texturepool::getTextureID() {
+ if (texture_ids.empty())
+ {
+ GLuint new_texture_ids[TextureMax];
+ glGenTextures(TextureMax, new_texture_ids);
+ for (uint32_t id = 0; id < TextureMax; id++) {
+ texture_ids.insert(new_texture_ids[id]);
+ }
+ }
+
+ GLuint id = 0;
+
+ if (!texture_ids.empty()) {
+ std::set<GLuint>::iterator id_iterator = texture_ids.begin();
+ id = *id_iterator;
+ texture_ids.erase(id_iterator);
+ }
+
+ return id;
+}
+
+void Texturepool::removeTextureID(GLuint texture_id) {
+ bool needs_clear = false;
+
+ glBindTexture(GL_TEXTURE_2D, texture_id);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+
+ texture_ids.insert(texture_id);
+
+ if (texture_ids.size() > TextureMax)
+ needs_clear = true;
+
+ if (needs_clear)
+ clearTextureIDs();
+}
+
+void Texturepool::clearTextureIDs() {
+ std::vector<GLuint> ids_to_remove;
+ ids_to_remove.reserve(texture_ids.size());
+
+ for (std::set<GLuint>::iterator id_iterator = texture_ids.begin(); id_iterator != texture_ids.end(); ++id_iterator)
+ ids_to_remove.push_back(*id_iterator);
+
+ if (!ids_to_remove.empty())
+ glDeleteTextures((GLsizei)ids_to_remove.size(), &ids_to_remove[0]);
+
+ texture_ids.clear();
+}