diff --git a/README.md b/README.md index 9dc7b2825..d4e070eb4 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 c889ce701..ed6317a56 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 #include + #include namespace llmr { -std::forward_list covering_tiles(int32_t zoom, const box& points); +std::forward_list 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 b6cffb417..62a6c01dc 100644 --- a/include/llmr/map/map.hpp +++ b/include/llmr/map/map.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -23,9 +24,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(); @@ -68,12 +69,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; @@ -82,6 +83,8 @@ private: float pixel_ratio; + bool use_raster = false; + std::forward_list tiles; std::forward_list> tile_data; }; diff --git a/include/llmr/map/tile_data.hpp b/include/llmr/map/tile_data.hpp index 98709ad32..3328800f9 100644 --- a/include/llmr/map/tile_data.hpp +++ b/include/llmr/map/tile_data.hpp @@ -20,6 +20,7 @@ namespace llmr { class Style; +class Raster; class Bucket; class LayerDescription; class BucketDescription; @@ -52,7 +53,7 @@ public: }; public: - TileData(Tile::ID id, const Style& style); + TileData(Tile::ID id, const Style& style, const bool use_raster = false, const bool use_retina = false); ~TileData(); // Start loading the tile. @@ -72,7 +73,10 @@ public: public: const Tile::ID id; + const bool use_raster; + const bool use_retina; std::atomic state; + std::shared_ptr 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 806c69f8d..0f7ba8ec4 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 0080773f2..6ae5fbbbf 100644 --- a/include/llmr/renderer/painter.hpp +++ b/include/llmr/renderer/painter.hpp @@ -13,7 +13,7 @@ #include #include #include - +#include namespace llmr { @@ -42,10 +42,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& tile); void renderLayers(const std::shared_ptr& tile, const std::vector& layers); void renderLayer(const std::shared_ptr& tile_data, const LayerDescription& layer_desc); void renderDebug(const std::shared_ptr& tile); @@ -80,6 +81,7 @@ private: std::unique_ptr linejoinShader; std::unique_ptr patternShader; std::unique_ptr pointShader; + std::unique_ptr rasterShader; // Set up the stencil quad we're using to generate the stencil mask. VertexBuffer tileStencilBuffer = { @@ -96,6 +98,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 000000000..00f423cbf --- /dev/null +++ b/include/llmr/shader/raster_shader.hpp @@ -0,0 +1,28 @@ +#ifndef LLMR_RENDERER_SHADER_RASTER +#define LLMR_RENDERER_SHADER_RASTER + +#include + +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 6312cc694..5089147e2 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, SHADER_COUNT }; diff --git a/include/llmr/style/sprite.hpp b/include/llmr/style/sprite.hpp index 78ca76f23..f7d281e20 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 #include #include #include #include +#include #include namespace llmr { @@ -34,27 +33,24 @@ public: class Sprite : public std::enable_shared_from_this { 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; + 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 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 000000000..cf69663ce --- /dev/null +++ b/include/llmr/util/raster.hpp @@ -0,0 +1,72 @@ +#ifndef LLMR_UTIL_RASTER +#define LLMR_UTIL_RASTER + +#include +#include + +#include +#include + +namespace llmr { + +class Raster : public std::enable_shared_from_this { + +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 fade_animation = nullptr; +}; + +} + +#endif diff --git a/include/llmr/util/texturepool.hpp b/include/llmr/util/texturepool.hpp new file mode 100644 index 000000000..348d25d29 --- /dev/null +++ b/include/llmr/util/texturepool.hpp @@ -0,0 +1,25 @@ +#ifndef LLMR_UTIL_TEXTUREPOOL +#define LLMR_UTIL_TEXTUREPOOL + +#include +#include + +#include +#include + +namespace llmr { + +class Texturepool : private util::noncopyable { + +public: + GLuint getTextureID(); + void removeTextureID(GLuint texture_id); + void clearTextureIDs(); + +private: + std::set texture_ids; +}; + +} + +#endif diff --git a/ios/MBXViewController.mm b/ios/MBXViewController.mm index 6713f3345..7cc616c0e 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 c040811b5..b98518a58 100644 --- a/src/map/coverage.cpp +++ b/src/map/coverage.cpp @@ -1,4 +1,7 @@ #include +#include +#include + #include #include @@ -64,15 +67,28 @@ void _scanTriangle(const llmr::vec2 a, const llmr::vec2 b, const if (bc.dy) _scanSpans(ca, bc, ymin, ymax, scanLine); } -std::forward_list llmr::covering_tiles(int32_t zoom, const box& points) { +std::forward_list 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 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 06aea39a3..7882f6d4b 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include @@ -14,8 +15,9 @@ Map::Map(Settings& settings) transform(), style(), painter(transform, settings, style), + texturepool(), min_zoom(0), - max_zoom(14) { + max_zoom((use_raster ? kTileRasterMaxZoom : kTileVectorMaxZoom)) { } Map::~Map() { @@ -23,6 +25,7 @@ Map::~Map() { } void Map::setup(float pixelRatio) { + painter.setup(); pixel_ratio = pixelRatio; @@ -33,10 +36,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(); @@ -60,6 +59,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(); @@ -278,7 +284,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(normalized_id, style); + new_tile.data = std::make_shared(normalized_id, style, use_raster, (pixel_ratio > 1.0)); new_tile.data->request(); tile_data.push_front(new_tile.data); } @@ -352,12 +358,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 required = llmr::covering_tiles(zoom, box); + std::forward_list 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 @@ -369,6 +377,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. @@ -431,6 +442,10 @@ bool Map::render() { transform.updateAnimations(); } + if (*style.sprite->raster && !style.sprite->raster->textured) { + style.sprite->raster->setTexturepool(&texturepool); + } + bool changed = updateTiles(); painter.clear(); @@ -447,13 +462,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_data.cpp b/src/map/tile_data.cpp index d62b323db..a51d2b8ac 100644 --- a/src/map/tile_data.cpp +++ b/src/map/tile_data.cpp @@ -10,15 +10,19 @@ #include #include #include +#include #include #include using namespace llmr; -TileData::TileData(Tile::ID id, const Style& style) +TileData::TileData(Tile::ID id, const Style& style, const bool use_raster, const bool use_retina) : id(id), state(State::initial), + use_raster(use_raster), + use_retina(use_retina), + raster(), fillVertexBuffer(std::make_shared()), lineVertexBuffer(std::make_shared()), pointVertexBuffer(std::make_shared()), @@ -44,9 +48,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 weak_tile = shared_from_this(); @@ -80,6 +88,13 @@ bool TileData::parse() { return false; } + if (use_raster) { + raster = std::make_shared(); + raster->load(data); + state = State::parsed; + return true; + } + try { double parse_time_start = platform::time(); VectorTile tile(pbf((const uint8_t *)data.data(), data.size())); diff --git a/src/platform/platform.cpp b/src/platform/platform.cpp index ebe65184a..a0fde3a05 100644 --- a/src/platform/platform.cpp +++ b/src/platform/platform.cpp @@ -1,4 +1,8 @@ #include -const char *llmr::kTileURL = "http://a.gl-api-us-east-1.tilestream.net/v3/mapbox.mapbox-streets-v4/%d/%d/%d.gl.pbf"; +const char *llmr::kTileVectorURL = "http://a.gl-api-us-east-1.tilestream.net/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 = "https://dl.dropboxusercontent.com/u/575564/sprite"; + +const int32_t llmr::kTileVectorMaxZoom = 14; +const int32_t llmr::kTileRasterMaxZoom = 21; \ No newline at end of file diff --git a/src/renderer/painter.cpp b/src/renderer/painter.cpp index a9094fdfb..8f388c800 100644 --- a/src/renderer/painter.cpp +++ b/src/renderer/painter.cpp @@ -15,6 +15,7 @@ #include #include #include +#include using namespace llmr; @@ -26,7 +27,6 @@ Painter::Painter(Transform& transform, Settings& settings, Style& style) style(style) { } - void Painter::setup() { setupShaders(); @@ -36,6 +36,7 @@ void Painter::setup() { assert(lineShader); assert(linejoinShader); assert(patternShader); + assert(rasterShader); // Blending @@ -62,6 +63,7 @@ void Painter::setupShaders() { linejoinShader = std::make_unique(); patternShader = std::make_unique(); pointShader = std::make_unique(); + rasterShader = std::make_unique(); } void Painter::resize(int width, int height) { @@ -113,9 +115,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()); @@ -148,13 +152,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& 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& tile_data, const std::vector& layers) { float strata_thickness = 1.0f / (layers.size() + 1); @@ -289,7 +309,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); @@ -446,7 +466,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 000000000..feb0a58f0 --- /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 000000000..75d6c8d14 --- /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 000000000..5852c45c7 --- /dev/null +++ b/src/shader/raster_shader.cpp @@ -0,0 +1,47 @@ +#include +#include +#include + +#include + +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 44524f30d..b75d7812c 100644 --- a/src/shader/shaders.cpp +++ b/src/shader/shaders.cpp @@ -27,5 +27,9 @@ const shader_source llmr::shaders[SHADER_COUNT] = { { "attribute vec2 a_pos;\n\nuniform mat4 u_matrix;\nuniform float u_size;\n\nvoid main() {\n gl_Position = u_matrix * vec4(a_pos, 0, 1);\n gl_PointSize = u_size;\n}\n", "#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", } }; diff --git a/src/style/sprite.cpp b/src/style/sprite.cpp index 2052c5437..3998afc62 100644 --- a/src/style/sprite.cpp +++ b/src/style/sprite.cpp @@ -1,10 +1,8 @@ #include +#include #include #include -#include - -#include #include @@ -22,16 +20,8 @@ ImagePosition::ImagePosition(const vec2& size, vec2 tl, vec2 lock(mtx); - return loaded; +Sprite::Sprite() { + raster = std::make_shared(); } 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 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 lock(mtx); + return loaded; +} void Sprite::parseJSON(const std::string& data) { std::lock_guard 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(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 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 000000000..121fe53bc --- /dev/null +++ b/src/util/raster.cpp @@ -0,0 +1,195 @@ +#include + +#include +#include + +#include + +#include + +using namespace llmr; + +Raster::~Raster() { + if (img && !textured) { + free(img); + } else if (textured) { + texturepool->removeTextureID(texture); + } +} + +Raster::operator bool() const { + std::lock_guard lock(mtx); + return loaded; +} + +void Raster::load(const std::string& data) { + std::shared_ptr raster = shared_from_this(); + + raster->loadImage(data); + + std::lock_guard 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(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 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) { + if (!texturepool) { + fprintf(stderr, "no available texture pool\n"); + return; + } + 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(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 000000000..c0857caa4 --- /dev/null +++ b/src/util/texturepool.cpp @@ -0,0 +1,56 @@ +#include + +#include + +#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::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 ids_to_remove; + ids_to_remove.reserve(texture_ids.size()); + + for (std::set::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(); +}