From 27f8ed29e4b79a917b4aa715469bc22018a06037 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Thu, 15 May 2014 17:19:14 +0200 Subject: [PATCH 1/7] encode icon texture coordinate in buffer so we can render all pois with 1 draw call --- bin/style.js | 182 +--------------------- include/llmr/geometry/buffer.hpp | 38 +++-- include/llmr/geometry/icon_buffer.hpp | 5 +- include/llmr/map/tile_parser.hpp | 1 + include/llmr/renderer/icon_bucket.hpp | 5 +- include/llmr/shader/icon_shader.hpp | 5 +- include/llmr/style/bucket_description.hpp | 5 +- src/geometry/icon_buffer.cpp | 10 +- src/map/tile_parser.cpp | 19 ++- src/renderer/icon_bucket.cpp | 27 +++- src/renderer/painter_icon.cpp | 22 +-- src/renderer/painter_text.cpp | 4 +- src/renderer/text_bucket.cpp | 2 +- src/shader/dot_shader.cpp | 2 +- src/shader/icon.fragment.glsl | 5 +- src/shader/icon.vertex.glsl | 4 + src/shader/icon_shader.cpp | 15 +- src/shader/shaders_gl.cpp | 4 +- src/shader/shaders_gles2.cpp | 4 +- src/style/bucket_description.cpp | 2 +- src/style/style_parser.cpp | 8 +- src/text/placement.cpp | 2 +- 22 files changed, 119 insertions(+), 252 deletions(-) diff --git a/bin/style.js b/bin/style.js index 4bd98db8db3..558c642422c 100644 --- a/bin/style.js +++ b/bin/style.js @@ -452,116 +452,13 @@ module.exports = { "feature_type": "point", "type": "text" }, - "poi_airport": { + "poi": { "source": "mapbox streets", "layer": "poi_label", - "field": "maki", - "value": "airport", - "size": { - "x": 12, - "y": 12 - }, - "type": "point" - }, - "poi_rail": { - "source": "mapbox streets", - "layer": "poi_label", - "field": "maki", - "value": "rail", - "size": { - "x": 12, - "y": 12 - }, - "type": "point" - }, - "poi_fire_station": { - "source": "mapbox streets", - "layer": "poi_label", - "field": "maki", - "value": "fire-station", - "size": { - "x": 12, - "y": 12 - }, - "type": "point" - }, - "poi_bus": { - "source": "mapbox streets", - "layer": "poi_label", - "field": "maki", - "value": "bus", - "size": { - "x": 12, - "y": 12 - }, - "type": "point" - }, - "poi_restaurant": { - "source": "mapbox streets", - "layer": "poi_label", - "field": "maki", - "value": "restaurant", - "size": { - "x": 12, - "y": 12 - }, - "type": "point" - }, - "poi_park": { - "source": "mapbox streets", - "layer": "poi_label", - "field": "maki", - "value": "park", - "size": { - "x": 12, - "y": 12 - }, - "type": "point" - }, - "poi_playground": { - "source": "mapbox streets", - "layer": "poi_label", - "field": "maki", - "value": "playground", - "size": { - "x": 12, - "y": 12 - }, - "type": "point" - }, - "poi_hospital": { - "source": "mapbox streets", - "layer": "poi_label", - "field": "maki", - "value": "hospital", - "size": { - "x": 12, - "y": 12 - }, + "icon": "maki", + "size": 12, "type": "point" }, - "poi_cafe": { - "source": "mapbox streets", - "layer": "poi_label", - "field": "maki", - "value": "cafe", - "size": { - "x": 12, - "y": 12 - }, - "type": "point" - }, - "poi_beer": { - "source": "mapbox streets", - "layer": "poi_label", - "field": "maki", - "value": "beer", - "size": { - "x": 12, - "y": 12 - }, - "type": "point" - } }, "constants": { "land": "#f8f4f0", @@ -801,42 +698,12 @@ module.exports = { }, { "name": "road_label", "bucket": "road_label" - }, { - "name": "poi_label", - "bucket": "poi_label" }, { "name": "water_label", "bucket": "water_label" }, { - "name": "poi_airport", - "bucket": "poi_airport" - }, { - "name": "poi_rail", - "bucket": "poi_rail" - }, { - "name": "poi_bus", - "bucket": "poi_bus" - }, { - "name": "poi_fire_station", - "bucket": "poi_fire_station" - }, { - "name": "poi_restaurant", - "bucket": "poi_restaurant" - }, { - "name": "poi_park", - "bucket": "poi_park" - }, { - "name": "poi_hospital", - "bucket": "poi_hospital" - }, { - "name": "poi_playground", - "bucket": "poi_playground" - }, { - "name": "poi_cafe", - "bucket": "poi_cafe" - }, { - "name": "poi_beer", - "bucket": "poi_beer" + "name": "poi", + "bucket": "poi" }], "classes": [{ "name": "default", @@ -2010,45 +1877,8 @@ module.exports = { 0.75 ], }, - "poi_airport": { - "color": "maki", - "image": "airport-12", - }, - "poi_restaurant": { - "color": "maki", - "image": "restaurant-12", - }, - "poi_bus": { - "color": "maki", - "image": "bus-12", - }, - "poi_rail": { - "color": "maki", - "image": "rail-12", - }, - "poi_fire_station": { - "color": "maki", - "image": "fire-station-12", - }, - "poi_park": { - "color": "maki", - "image": "park-12", - }, - "poi_hospital": { - "color": "maki", - "image": "hospital-12", - }, - "poi_playground": { - "color": "maki", - "image": "playground-12", - }, - "poi_cafe": { - "color": "maki", - "image": "cafe-12", - }, - "poi_beer": { + "poi": { "color": "maki", - "image": "beer-12", }, "satellite": { "type": "raster", diff --git a/include/llmr/geometry/buffer.hpp b/include/llmr/geometry/buffer.hpp index 89adfb37f7b..0e31cf4381a 100644 --- a/include/llmr/geometry/buffer.hpp +++ b/include/llmr/geometry/buffer.hpp @@ -11,15 +11,13 @@ namespace llmr { template < size_t item_size, int bufferType = GL_ARRAY_BUFFER, - size_t defaultLength = 8192 + size_t defaultLength = 8192, + bool retainAfterUpload = false > class Buffer : private util::noncopyable { public: ~Buffer() { - if (array) { - free(array); - array = nullptr; - } + cleanup(); if (buffer != 0) { glDeleteBuffers(1, &buffer); buffer = 0; @@ -33,19 +31,28 @@ class Buffer : private util::noncopyable { } // Transfers this buffer to the GPU and binds the buffer to the GL context. - void bind() { + void bind(bool force = false) { if (buffer == 0) { glGenBuffers(1, &buffer); - glBindBuffer(bufferType, buffer); + force = true; + } + glBindBuffer(bufferType, buffer); + if (force) { + assert("Buffer was already deleted" && array != nullptr); glBufferData(bufferType, pos, array, GL_STATIC_DRAW); + if (!retainAfterUpload) { + cleanup(); + } + } + } + + void cleanup() { + if (array) { free(array); array = nullptr; - } else { - glBindBuffer(bufferType, buffer); } } - protected: // increase the buffer size by at least /required/ bytes. inline void *addElement() { @@ -59,6 +66,17 @@ class Buffer : private util::noncopyable { return static_cast(array) + (pos - itemSize); } + // Get a pointer to the item at a given index. + inline void *getElement(size_t index) { + assert("Buffer was deleted" && array != nullptr); + + if (index * itemSize >= pos) { + throw new std::runtime_error("Can't get element after array bounds"); + } else { + return static_cast(array) + (index * itemSize); + } + } + public: static const size_t itemSize = item_size; diff --git a/include/llmr/geometry/icon_buffer.hpp b/include/llmr/geometry/icon_buffer.hpp index fc918c54515..6bb0ec30c14 100644 --- a/include/llmr/geometry/icon_buffer.hpp +++ b/include/llmr/geometry/icon_buffer.hpp @@ -6,12 +6,13 @@ namespace llmr { class IconVertexBuffer : public Buffer< - 4 // 2 coordinates per vertex (== 4 bytes) + 4 + // int16 x/y coordinates per vertex (== 4 bytes) + 4 // uint16 x/y coordinates of icon in sprite (== 4 bytes) > { public: typedef int16_t vertex_type; - void add(vertex_type x, vertex_type y); + void add(vertex_type x, vertex_type y, uint16_t tx, uint16_t ty); }; } diff --git a/include/llmr/map/tile_parser.hpp b/include/llmr/map/tile_parser.hpp index 91566d40aaa..12f3da8ff2f 100644 --- a/include/llmr/map/tile_parser.hpp +++ b/include/llmr/map/tile_parser.hpp @@ -27,6 +27,7 @@ class TileParser { std::unique_ptr createIconBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc); std::unique_ptr createTextBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc); template void addBucketFeatures(Bucket& bucket, const VectorTileLayer& layer, const BucketDescription& bucket_desc); + template void addBucketFeatures(Bucket& bucket, const VectorTileLayer& layer, const BucketDescription& bucket_desc, Args... args); private: const VectorTile vector_data; diff --git a/include/llmr/renderer/icon_bucket.hpp b/include/llmr/renderer/icon_bucket.hpp index a3338767e70..ce90edce30d 100644 --- a/include/llmr/renderer/icon_bucket.hpp +++ b/include/llmr/renderer/icon_bucket.hpp @@ -20,7 +20,8 @@ class IconVertexBuffer; class BucketDescription; class IconShader; class DotShader; -struct pbf; +class Sprite; +class VectorTileFeature; class IconBucket : public Bucket { public: @@ -30,7 +31,7 @@ class IconBucket : public Bucket { virtual void render(Painter& painter, const std::string& layer_name, const Tile::ID& id); virtual bool hasData() const; - void addGeometry(pbf& data); + void addFeature(const VectorTileFeature &feature, const std::shared_ptr &sprite); void drawIcons(IconShader& shader); void drawIcons(DotShader& shader); diff --git a/include/llmr/shader/icon_shader.hpp b/include/llmr/shader/icon_shader.hpp index db3bfe68674..6b3644f28ee 100644 --- a/include/llmr/shader/icon_shader.hpp +++ b/include/llmr/shader/icon_shader.hpp @@ -13,12 +13,12 @@ class IconShader : public Shader { void setImage(int32_t image); void setColor(const std::array& color); - void setPosition(const std::array& pos); void setDimension(const std::array& dimension); void setSize(float size); private: int32_t a_pos = -1; + int32_t a_tex = -1; int32_t image = -1; int32_t u_image = -1; @@ -26,9 +26,6 @@ class IconShader : public Shader { std::array color = {{}}; int32_t u_color = -1; - std::array pos = {{}}; - int32_t u_pos = -1; - std::array dimension = {{}}; int32_t u_dimension = -1; diff --git a/include/llmr/style/bucket_description.hpp b/include/llmr/style/bucket_description.hpp index 615b5af6505..54593eec76c 100644 --- a/include/llmr/style/bucket_description.hpp +++ b/include/llmr/style/bucket_description.hpp @@ -73,8 +73,9 @@ class BucketGeometryDescription { CapType cap = CapType::None; JoinType join = JoinType::None; std::string font; - std::string text_field; - float font_size = 0.0f; + std::string field; + std::string icon; + float size = 0.0f; float miter_limit = 2.0f; float round_limit = 1.0f; TextPathType path = TextPathType::Horizontal; diff --git a/src/geometry/icon_buffer.cpp b/src/geometry/icon_buffer.cpp index df5b7434d3c..22a47ff0939 100644 --- a/src/geometry/icon_buffer.cpp +++ b/src/geometry/icon_buffer.cpp @@ -5,8 +5,14 @@ using namespace llmr; -void IconVertexBuffer::add(vertex_type x, vertex_type y) { - vertex_type *vertices = static_cast(addElement()); +void IconVertexBuffer::add(vertex_type x, vertex_type y, uint16_t tx, uint16_t ty) { + void *data = addElement(); + + vertex_type *vertices = static_cast(data); vertices[0] = x; vertices[1] = y; + + uint16_t *texture = static_cast(data); + texture[2] = tx; + texture[3] = ty; } diff --git a/src/map/tile_parser.cpp b/src/map/tile_parser.cpp index ebbba78d3d8..61957e60748 100644 --- a/src/map/tile_parser.cpp +++ b/src/map/tile_parser.cpp @@ -120,6 +120,15 @@ void TileParser::addBucketFeatures(Bucket& bucket, const VectorTileLayer& layer, } } +template +void TileParser::addBucketFeatures(Bucket& bucket, const VectorTileLayer& layer, const BucketDescription& bucket_desc, Args... args) { + FilteredVectorTileLayer filtered_layer(layer, bucket_desc); + for (const pbf& feature_pbf : filtered_layer) { + if (obsolete()) return; + bucket->addFeature({ feature_pbf, layer }, args...); + } +} + std::unique_ptr TileParser::createFillBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc) { std::unique_ptr bucket = std::make_unique( tile.fillVertexBuffer, tile.triangleElementsBuffer, tile.lineElementsBuffer, bucket_desc); @@ -137,7 +146,7 @@ std::unique_ptr TileParser::createLineBucket(const VectorTileLayer& laye std::unique_ptr TileParser::createIconBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc) { std::unique_ptr bucket = std::make_unique( tile.iconVertexBuffer, bucket_desc); - addBucketFeatures(bucket, layer, bucket_desc); + addBucketFeatures(bucket, layer, bucket_desc, style.sprite); return obsolete() ? nullptr : std::move(bucket); } @@ -166,12 +175,6 @@ std::unique_ptr TileParser::createTextBucket(const VectorTileLayer& laye std::unique_ptr bucket = std::make_unique( tile.textVertexBuffer, tile.triangleElementsBuffer, bucket_desc, placement); - - FilteredVectorTileLayer filtered_layer(layer, bucket_desc); - for (const pbf& feature_pbf : filtered_layer) { - if (obsolete()) return nullptr; - bucket->addFeature({ feature_pbf, layer }, faces, shaping); - } - + addBucketFeatures(bucket, layer, bucket_desc, faces, shaping); return std::move(bucket); } diff --git a/src/renderer/icon_bucket.cpp b/src/renderer/icon_bucket.cpp index 6e3c65276c9..060e1e01673 100644 --- a/src/renderer/icon_bucket.cpp +++ b/src/renderer/icon_bucket.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -22,13 +23,35 @@ IconBucket::IconBucket(IconVertexBuffer& vertexBuffer, vertex_start(vertexBuffer.index()) { } -void IconBucket::addGeometry(pbf& geom) { +void IconBucket::addFeature(const VectorTileFeature &feature, const std::shared_ptr &sprite) { + // TODO: We somehow need to reparse the stylesheet, or maintain a mapping of id => sprite name + // For now, we're just not showing points if the sprite was not yet loaded at *parse time* of + // the tile. + if (!sprite || !sprite->isLoaded()) return; + + auto field_it = feature.properties.find(geometry.field); + if (field_it == feature.properties.end()) { + fprintf(stderr, "feature doesn't contain field '%s'\n", geometry.field.c_str()); + return; + } + + std::string field = toString(field_it->second); + if (geometry.size) { + field.append("-"); + field.append(std::to_string(static_cast(std::round(geometry.size)))); + } + + const SpritePosition &pos = sprite->getSpritePosition(field); + const uint16_t tx = pos.x + pos.width / 2; + const uint16_t ty = pos.y + pos.height / 2; + Geometry::command cmd; + pbf geom = feature.geometry; Geometry geometry(geom); int32_t x, y; while ((cmd = geometry.next(x, y)) != Geometry::end) { if (cmd == Geometry::move_to) { - vertexBuffer.add(x, y); + vertexBuffer.add(x, y, tx, ty); } else { fprintf(stderr, "other command than move_to in icon geometry\n"); } diff --git a/src/renderer/painter_icon.cpp b/src/renderer/painter_icon.cpp index 28e9c6f66d2..0590aeb1d7d 100644 --- a/src/renderer/painter_icon.cpp +++ b/src/renderer/painter_icon.cpp @@ -24,21 +24,11 @@ void Painter::renderIcon(IconBucket& bucket, const std::string& layer_name, cons color[2] *= properties.opacity; color[3] *= properties.opacity; - auto &sprite = map.getStyle().sprite; - SpritePosition spritePos; - - if (properties.image.length() && sprite && sprite->isLoaded()) { - std::string sized_image = properties.image; - if (properties.size) { - sized_image.append("-"); - sized_image.append(std::to_string(static_cast(std::round(properties.size)))); - } - spritePos = sprite->getSpritePosition(sized_image); - } - const mat4 &vtxMatrix = translatedMatrix(properties.translate, id, properties.translateAnchor); - if (!spritePos.width || !spritePos.height) { + const std::shared_ptr &sprite = map.getStyle().sprite; + + if (!sprite || !sprite->isLoaded()) { useProgram(dotShader->program); dotShader->setMatrix(vtxMatrix); dotShader->setColor(color); @@ -58,10 +48,6 @@ void Painter::renderIcon(IconBucket& bucket, const std::string& layer_name, cons iconShader->setMatrix(vtxMatrix); iconShader->setColor(color); iconShader->setImage(0); - iconShader->setPosition({{ - spritePos.x + (float)spritePos.width / 2.0f, - spritePos.y + (float)spritePos.height / 2.0f, - }}); iconShader->setDimension({{ static_cast(sprite->raster.width), @@ -70,7 +56,7 @@ void Painter::renderIcon(IconBucket& bucket, const std::string& layer_name, cons sprite->raster.bind(map.getState().isChanging()); - const float iconSize = util::max(spritePos.width, spritePos.height) + 2; + const float iconSize = bucket.geometry.size * map.getState().getPixelRatio(); iconShader->setSize(iconSize); #ifndef GL_ES_VERSION_2_0 glPointSize(iconSize); diff --git a/src/renderer/painter_text.cpp b/src/renderer/painter_text.cpp index c083da964d6..a94df46134d 100644 --- a/src/renderer/painter_text.cpp +++ b/src/renderer/painter_text.cpp @@ -28,7 +28,7 @@ void Painter::renderText(TextBucket& bucket, const std::string& layer_name, cons } // If layerStyle.size > bucket.info.fontSize then labels may collide - float fontSize = std::fmin(properties.size, bucket.geom_desc.font_size); + float fontSize = std::fmin(properties.size, bucket.geom_desc.size); matrix::scale(exMatrix, exMatrix, fontSize / 24.0f, fontSize / 24.0f, 1.0f); const mat4 &vtxMatrix = translatedMatrix(properties.translate, id, properties.translateAnchor); @@ -47,7 +47,7 @@ void Painter::renderText(TextBucket& bucket, const std::string& layer_name, cons float angle = std::round((map.getState().getAngle() + rotate) / M_PI * 128); // adjust min/max zooms for variable font sies - float zoomAdjust = log(fontSize / bucket.geom_desc.font_size) / log(2); + float zoomAdjust = log(fontSize / bucket.geom_desc.size) / log(2); textShader->setAngle((int32_t)(angle + 256) % 256); textShader->setFlip(bucket.geom_desc.path == TextPathType::Curve ? 1 : 0); diff --git a/src/renderer/text_bucket.cpp b/src/renderer/text_bucket.cpp index 052fd5a08f2..d9aa76d89d6 100644 --- a/src/renderer/text_bucket.cpp +++ b/src/renderer/text_bucket.cpp @@ -102,7 +102,7 @@ void TextBucket::addGlyphs(const PlacedGlyphs &glyphs, float placementZoom, void TextBucket::addFeature(const VectorTileFeature &feature, const IndexedFaces &faces, const std::map &shapings) { - auto it_prop = feature.properties.find(geom_desc.text_field); + auto it_prop = feature.properties.find(geom_desc.field); if (it_prop == feature.properties.end()) { // feature does not have the correct property return; diff --git a/src/shader/dot_shader.cpp b/src/shader/dot_shader.cpp index 2f0325427d8..e159f0f11bb 100644 --- a/src/shader/dot_shader.cpp +++ b/src/shader/dot_shader.cpp @@ -32,7 +32,7 @@ DotShader::DotShader() void DotShader::bind(char *offset) { glEnableVertexAttribArray(a_pos); - glVertexAttribPointer(a_pos, 2, GL_SHORT, false, 0, offset); + glVertexAttribPointer(a_pos, 2, GL_SHORT, false, 8, offset); } void DotShader::setColor(const std::array& new_color) { diff --git a/src/shader/icon.fragment.glsl b/src/shader/icon.fragment.glsl index f5a424ae9f4..aacbb6a54bb 100644 --- a/src/shader/icon.fragment.glsl +++ b/src/shader/icon.fragment.glsl @@ -1,10 +1,11 @@ uniform sampler2D u_image; -uniform vec2 u_pos; uniform vec2 u_dimension; uniform vec4 u_color; uniform float u_size; +varying vec2 v_tex; + void main() { - vec2 pos = (u_pos + (gl_PointCoord - 0.5) * u_size) / u_dimension; + vec2 pos = (v_tex + (gl_PointCoord - 0.5) * u_size) / u_dimension; gl_FragColor = texture2D(u_image, pos) * u_color; } diff --git a/src/shader/icon.vertex.glsl b/src/shader/icon.vertex.glsl index 4ccc52663bc..51b378f3fde 100644 --- a/src/shader/icon.vertex.glsl +++ b/src/shader/icon.vertex.glsl @@ -1,9 +1,13 @@ attribute vec2 a_pos; +attribute vec2 a_tex; uniform mat4 u_matrix; uniform float u_size; +varying vec2 v_tex; + void main() { gl_Position = u_matrix * vec4(a_pos, 0, 1); gl_PointSize = u_size; + v_tex = a_tex; } diff --git a/src/shader/icon_shader.cpp b/src/shader/icon_shader.cpp index 820a5118bbe..f991d1f39aa 100644 --- a/src/shader/icon_shader.cpp +++ b/src/shader/icon_shader.cpp @@ -17,25 +17,27 @@ IconShader::IconShader() } a_pos = glGetAttribLocation(program, "a_pos"); + a_tex = glGetAttribLocation(program, "a_tex"); u_matrix = glGetUniformLocation(program, "u_matrix"); u_color = glGetUniformLocation(program, "u_color"); u_size = glGetUniformLocation(program, "u_size"); - u_pos = glGetUniformLocation(program, "u_pos"); u_dimension = glGetUniformLocation(program, "u_dimension"); // fprintf(stderr, "IconShader:\n"); // fprintf(stderr, " - u_matrix: %d\n", u_matrix); // fprintf(stderr, " - u_color: %d\n", u_color); // fprintf(stderr, " - u_size: %d\n", u_size); - // fprintf(stderr, " - u_pos: %d\n", u_pos); // fprintf(stderr, " - u_dimension: %d\n", u_dimension); // fprintf(stderr, " - u_image: %d\n", u_image); } void IconShader::bind(char *offset) { glEnableVertexAttribArray(a_pos); - glVertexAttribPointer(a_pos, 2, GL_SHORT, false, 0, offset); + glVertexAttribPointer(a_pos, 2, GL_SHORT, false, 8, offset); + + glEnableVertexAttribArray(a_tex); + glVertexAttribPointer(a_tex, 2, GL_UNSIGNED_SHORT, false, 8, offset + 4); } void IconShader::setImage(int32_t new_image) { @@ -59,13 +61,6 @@ void IconShader::setSize(float new_size) { } } -void IconShader::setPosition(const std::array& new_pos) { - if (pos != new_pos) { - glUniform2fv(u_pos, 1, new_pos.data()); - pos = new_pos; - } -} - void IconShader::setDimension(const std::array& new_dimension) { if (dimension != new_dimension) { glUniform2fv(u_dimension, 1, new_dimension.data()); diff --git a/src/shader/shaders_gl.cpp b/src/shader/shaders_gl.cpp index 45f1651dc46..aebd5bfac4e 100644 --- a/src/shader/shaders_gl.cpp +++ b/src/shader/shaders_gl.cpp @@ -11,8 +11,8 @@ const shader_source llmr::shaders[SHADER_COUNT] = { "#version 120\nuniform vec4 u_color;\nuniform float u_blur;\nvoid main ()\n{\n vec2 x_1;\n x_1 = (gl_PointCoord - 0.5);\n float tmpvar_2;\n tmpvar_2 = clamp (((\n sqrt(dot (x_1, x_1))\n - 0.5) / (\n (0.5 - u_blur)\n - 0.5)), 0.0, 1.0);\n gl_FragColor = (u_color * (tmpvar_2 * (tmpvar_2 * \n (3.0 - (2.0 * tmpvar_2))\n )));\n}\n\n", }, { - "#version 120\nattribute vec2 a_pos;\nuniform mat4 u_matrix;\nuniform float u_size;\nvoid main ()\n{\n vec4 tmpvar_1;\n tmpvar_1.zw = vec2(0.0, 1.0);\n tmpvar_1.xy = a_pos;\n gl_Position = (u_matrix * tmpvar_1);\n gl_PointSize = u_size;\n}\n\n", - "#version 120\nuniform sampler2D u_image;\nuniform vec2 u_pos;\nuniform vec2 u_dimension;\nuniform vec4 u_color;\nuniform float u_size;\nvoid main ()\n{\n gl_FragColor = (texture2D (u_image, ((u_pos + \n ((gl_PointCoord - 0.5) * u_size)\n ) / u_dimension)) * u_color);\n}\n\n", + "#version 120\nattribute vec2 a_pos;\nattribute vec2 a_tex;\nuniform mat4 u_matrix;\nuniform float u_size;\nvarying vec2 v_tex;\nvoid main ()\n{\n vec4 tmpvar_1;\n tmpvar_1.zw = vec2(0.0, 1.0);\n tmpvar_1.xy = a_pos;\n gl_Position = (u_matrix * tmpvar_1);\n gl_PointSize = u_size;\n v_tex = a_tex;\n}\n\n", + "#version 120\nuniform sampler2D u_image;\nuniform vec2 u_dimension;\nuniform vec4 u_color;\nuniform float u_size;\nvarying vec2 v_tex;\nvoid main ()\n{\n gl_FragColor = (texture2D (u_image, ((v_tex + \n ((gl_PointCoord - 0.5) * u_size)\n ) / u_dimension)) * u_color);\n}\n\n", }, { "#version 120\nattribute vec2 a_pos;\nattribute vec2 a_extrude;\nattribute float a_linesofar;\nuniform mat4 u_matrix;\nuniform mat4 u_exmatrix;\nuniform float u_ratio;\nuniform vec2 u_linewidth;\nvarying vec2 v_normal;\nvarying float v_linesofar;\nvoid main ()\n{\n vec2 normal_1;\n vec2 tmpvar_2;\n tmpvar_2 = (vec2(mod (a_pos, 2.0)));\n normal_1.x = tmpvar_2.x;\n normal_1.y = sign((tmpvar_2.y - 0.5));\n v_normal = normal_1;\n vec4 tmpvar_3;\n tmpvar_3.zw = vec2(0.0, 0.0);\n tmpvar_3.xy = ((u_linewidth.x * a_extrude) * 0.015873);\n vec4 tmpvar_4;\n tmpvar_4.zw = vec2(0.0, 1.0);\n tmpvar_4.xy = floor((a_pos * 0.5));\n gl_Position = ((u_matrix * tmpvar_4) + (u_exmatrix * tmpvar_3));\n v_linesofar = (a_linesofar * u_ratio);\n}\n\n", diff --git a/src/shader/shaders_gles2.cpp b/src/shader/shaders_gles2.cpp index d94e04f2956..185e4c0a879 100644 --- a/src/shader/shaders_gles2.cpp +++ b/src/shader/shaders_gles2.cpp @@ -11,8 +11,8 @@ const shader_source llmr::shaders[SHADER_COUNT] = { "precision highp float;\nuniform vec4 u_color;\nuniform float u_blur;\nvoid main ()\n{\n mediump vec2 x_1;\n x_1 = (gl_PointCoord - 0.5);\n mediump float tmpvar_2;\n tmpvar_2 = clamp (((\n sqrt(dot (x_1, x_1))\n - 0.5) / (\n (0.5 - u_blur)\n - 0.5)), 0.0, 1.0);\n gl_FragColor = (u_color * (tmpvar_2 * (tmpvar_2 * \n (3.0 - (2.0 * tmpvar_2))\n )));\n}\n\n", }, { - "precision highp float;\nattribute vec2 a_pos;\nuniform mat4 u_matrix;\nuniform float u_size;\nvoid main ()\n{\n vec4 tmpvar_1;\n tmpvar_1.zw = vec2(0.0, 1.0);\n tmpvar_1.xy = a_pos;\n gl_Position = (u_matrix * tmpvar_1);\n gl_PointSize = u_size;\n}\n\n", - "precision highp float;\nuniform sampler2D u_image;\nuniform vec2 u_pos;\nuniform vec2 u_dimension;\nuniform vec4 u_color;\nuniform float u_size;\nvoid main ()\n{\n mediump vec2 tmpvar_1;\n tmpvar_1 = ((u_pos + (\n (gl_PointCoord - 0.5)\n * u_size)) / u_dimension);\n lowp vec4 tmpvar_2;\n tmpvar_2 = (texture2D (u_image, tmpvar_1) * u_color);\n gl_FragColor = tmpvar_2;\n}\n\n", + "precision highp float;\nattribute vec2 a_pos;\nattribute vec2 a_tex;\nuniform mat4 u_matrix;\nuniform float u_size;\nvarying vec2 v_tex;\nvoid main ()\n{\n vec4 tmpvar_1;\n tmpvar_1.zw = vec2(0.0, 1.0);\n tmpvar_1.xy = a_pos;\n gl_Position = (u_matrix * tmpvar_1);\n gl_PointSize = u_size;\n v_tex = a_tex;\n}\n\n", + "precision highp float;\nuniform sampler2D u_image;\nuniform vec2 u_dimension;\nuniform vec4 u_color;\nuniform float u_size;\nvarying vec2 v_tex;\nvoid main ()\n{\n mediump vec2 tmpvar_1;\n tmpvar_1 = ((v_tex + (\n (gl_PointCoord - 0.5)\n * u_size)) / u_dimension);\n lowp vec4 tmpvar_2;\n tmpvar_2 = (texture2D (u_image, tmpvar_1) * u_color);\n gl_FragColor = tmpvar_2;\n}\n\n", }, { "precision highp float;\nattribute vec2 a_pos;\nattribute vec2 a_extrude;\nattribute float a_linesofar;\nuniform mat4 u_matrix;\nuniform mat4 u_exmatrix;\nuniform float u_ratio;\nuniform vec2 u_linewidth;\nvarying vec2 v_normal;\nvarying float v_linesofar;\nvoid main ()\n{\n vec2 normal_1;\n vec2 tmpvar_2;\n tmpvar_2 = (vec2(mod (a_pos, 2.0)));\n normal_1.x = tmpvar_2.x;\n normal_1.y = sign((tmpvar_2.y - 0.5));\n v_normal = normal_1;\n vec4 tmpvar_3;\n tmpvar_3.zw = vec2(0.0, 0.0);\n tmpvar_3.xy = ((u_linewidth.x * a_extrude) * 0.015873);\n vec4 tmpvar_4;\n tmpvar_4.zw = vec2(0.0, 1.0);\n tmpvar_4.xy = floor((a_pos * 0.5));\n gl_Position = ((u_matrix * tmpvar_4) + (u_exmatrix * tmpvar_3));\n v_linesofar = (a_linesofar * u_ratio);\n}\n\n", diff --git a/src/style/bucket_description.cpp b/src/style/bucket_description.cpp index eb3b550befb..0dc1a111d2b 100644 --- a/src/style/bucket_description.cpp +++ b/src/style/bucket_description.cpp @@ -14,6 +14,6 @@ std::ostream& llmr::operator<<(std::ostream& os, const BucketDescription& bucket os << " - cap: " << (uint32_t)bucket.geometry.cap << std::endl; os << " - join: " << (uint32_t)bucket.geometry.join << std::endl; os << " - font: " << bucket.geometry.font << std::endl; - os << " - font_size: " << bucket.geometry.font_size << std::endl; + os << " - size: " << bucket.geometry.size << std::endl; return os; } diff --git a/src/style/style_parser.cpp b/src/style/style_parser.cpp index 057b75d0960..f2c1f30939b 100644 --- a/src/style/style_parser.cpp +++ b/src/style/style_parser.cpp @@ -83,15 +83,15 @@ BucketDescription StyleParser::parseBucket(JSVal value) { } else { throw Style::exception("font stack must be a string"); } - } else if (name == "fontSize") { + } else if (name == "fontSize" || name == "size") { if (value.IsNumber()) { - bucket.geometry.font_size = value.GetDouble(); + bucket.geometry.size = value.GetDouble(); } else { throw Style::exception("font size must be a number"); } - } else if (name == "text_field") { + } else if (name == "text_field" || name == "icon") { if (value.IsString()) { - bucket.geometry.text_field = { value.GetString(), value.GetStringLength() }; + bucket.geometry.field = { value.GetString(), value.GetStringLength() }; } else { throw Style::exception("text field must be a string"); } diff --git a/src/text/placement.cpp b/src/text/placement.cpp index 5c8b1547411..e48eff5c439 100644 --- a/src/text/placement.cpp +++ b/src/text/placement.cpp @@ -278,7 +278,7 @@ void Placement::addFeature(TextBucket& bucket, const float textMinDistance = info.textMinDistance; const float rotate = info.rotate; const float fontScale = - (tileExtent / util::tileSize) / (glyphSize / info.font_size); + (tileExtent / util::tileSize) / (glyphSize / info.size); const float advance = measureText(faces, shaping); Anchors anchors; From 24bd0bb667e6c2570838cd64992ffa388e8fb941 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Thu, 15 May 2014 17:46:36 +0200 Subject: [PATCH 2/7] include header --- include/llmr/geometry/buffer.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/include/llmr/geometry/buffer.hpp b/include/llmr/geometry/buffer.hpp index 0e31cf4381a..6f43774e509 100644 --- a/include/llmr/geometry/buffer.hpp +++ b/include/llmr/geometry/buffer.hpp @@ -1,11 +1,13 @@ #ifndef LLMR_GEOMETRY_BUFFER #define LLMR_GEOMETRY_BUFFER -#include -#include #include #include +#include +#include +#include + namespace llmr { template < From 13575330f7d32630f6dbf4244405c46b453152fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Mon, 19 May 2014 10:48:11 +0200 Subject: [PATCH 3/7] use sprite atlas --- common/glfw_view.cpp | 29 +++ include/llmr/geometry/sprite_atlas.hpp | 68 +++++++ include/llmr/map/map.hpp | 2 + include/llmr/map/tile_parser.hpp | 6 +- include/llmr/platform/platform.hpp | 5 +- include/llmr/renderer/icon_bucket.hpp | 4 +- include/llmr/style/style.hpp | 1 + include/llmr/util/math.hpp | 16 ++ include/llmr/util/raster.hpp | 2 + include/llmr/util/rect.hpp | 5 + src/geometry/sprite_atlas.cpp | 235 +++++++++++++++++++++++++ src/map/map.cpp | 15 ++ src/map/tile_parser.cpp | 11 +- src/map/vector_tile_data.cpp | 2 +- src/renderer/icon_bucket.cpp | 19 +- src/renderer/painter_icon.cpp | 49 ++---- src/shader/icon.fragment.glsl | 2 +- src/shader/shaders_gl.cpp | 2 +- src/shader/shaders_gles2.cpp | 2 +- src/style/sprite.cpp | 52 +++--- 20 files changed, 440 insertions(+), 87 deletions(-) create mode 100644 include/llmr/geometry/sprite_atlas.hpp create mode 100644 src/geometry/sprite_atlas.cpp diff --git a/common/glfw_view.cpp b/common/glfw_view.cpp index 7ec5d3b0838..2542e6cf93d 100644 --- a/common/glfw_view.cpp +++ b/common/glfw_view.cpp @@ -231,5 +231,34 @@ void show_debug_image(std::string name, const char *data, size_t width, size_t h glfwMakeContextCurrent(current_window); } + +void show_color_debug_image(std::string name, const char *data, size_t width, size_t height) { + static GLFWwindow *debug_window = nullptr; + if (!debug_window) { + debug_window = glfwCreateWindow(width, height, name.c_str(), nullptr, nullptr); + if (!debug_window) { + glfwTerminate(); + fprintf(stderr, "Failed to initialize window\n"); + exit(1); + } + } + + GLFWwindow *current_window = glfwGetCurrentContext(); + + glfwSetWindowSize(debug_window, width, height); + glfwMakeContextCurrent(debug_window); + + int fb_width, fb_height; + glfwGetFramebufferSize(debug_window, &fb_width, &fb_height); + float scale = (float)fb_width / (float)width; + + glPixelZoom(scale, -scale); + glRasterPos2f(-1.0f, 1.0f); + glDrawPixels(width, height, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, data); + glfwSwapBuffers(debug_window); + + glfwMakeContextCurrent(current_window); +} + } } diff --git a/include/llmr/geometry/sprite_atlas.hpp b/include/llmr/geometry/sprite_atlas.hpp new file mode 100644 index 00000000000..e6557846afe --- /dev/null +++ b/include/llmr/geometry/sprite_atlas.hpp @@ -0,0 +1,68 @@ +#ifndef LLMR_GEOMETRY_SPRITE_ATLAS +#define LLMR_GEOMETRY_SPRITE_ATLAS + +#include + +#include +#include +#include +#include +#include + +namespace llmr { + +class Sprite; + +class SpriteAtlas { +public: + typedef uint16_t dimension; + +public: + // Add way to construct this from another SpriteAtlas (e.g. with another pixelRatio) + SpriteAtlas(dimension width, dimension height); + ~SpriteAtlas(); + + // Changes the pixel ratio. + bool resize(float newRatio); + + // Update uninitialized sprites in this atlas from the given sprite. + void update(const Sprite &sprite); + + // Returns the coordinates of the icon. The getter also *creates* new icons in the atlas + // if they don't exist, but they'll be default-initialized with a a black circle. + Rect getIcon(int size, const std::string &name); + + // Updates or creates an icon with a given size and name. The data must be a + // a (pixelRatio * size)^2 * 4 byte long RGBA image buffer. + Rect setIcon(int size, const std::string &name, const std::string &icon); + + // Binds the image buffer of this sprite atlas to the GPU, and uploads data if it is out + // of date. + void bind(bool linear = false); + + inline float getTextureWidth() const { return width * pixelRatio; } + inline float getTextureHeight() const { return height * pixelRatio; } + +private: + void allocate(); + +public: + const dimension width = 0; + const dimension height = 0; + +private: + std::mutex mtx; + float pixelRatio = 1.0f; + BinPack bin; + std::map>> index; + std::set> uninitialized; + char *data = nullptr; + std::atomic dirty; + uint32_t texture = 0; + uint32_t filter = 0; + static const int buffer = 1; +}; + +}; + +#endif diff --git a/include/llmr/map/map.hpp b/include/llmr/map/map.hpp index 4600e1a967c..87d64b0733d 100644 --- a/include/llmr/map/map.hpp +++ b/include/llmr/map/map.hpp @@ -92,6 +92,7 @@ class Map : private util::noncopyable { inline const TransformState &getState() const { return state; } inline const Style &getStyle() const { return style; } inline GlyphAtlas &getGlyphAtlas() { return glyphAtlas; } + inline SpriteAtlas &getSpriteAtlas() { return spriteAtlas; } inline uv_loop_t *getLoop() { return loop; } inline time getAnimationTime() const { return animationTime; } inline Texturepool &getTexturepool() { return texturepool; } @@ -143,6 +144,7 @@ class Map : private util::noncopyable { Texturepool texturepool; Style style; GlyphAtlas glyphAtlas; + SpriteAtlas spriteAtlas; Painter painter; std::map> sources; diff --git a/include/llmr/map/tile_parser.hpp b/include/llmr/map/tile_parser.hpp index 12f3da8ff2f..86545e4f764 100644 --- a/include/llmr/map/tile_parser.hpp +++ b/include/llmr/map/tile_parser.hpp @@ -9,13 +9,14 @@ namespace llmr { class Style; class GlyphAtlas; +class SpriteAtlas; class LayerDescription; class Bucket; class TileParser { public: - TileParser(const std::string& data, VectorTileData& tile, const Style& style, GlyphAtlas& glyphAtlas); + TileParser(const std::string& data, VectorTileData& tile, const Style& style, GlyphAtlas& glyphAtlas, SpriteAtlas &spriteAtlas); private: bool obsolete() const; @@ -27,13 +28,14 @@ class TileParser { std::unique_ptr createIconBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc); std::unique_ptr createTextBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc); template void addBucketFeatures(Bucket& bucket, const VectorTileLayer& layer, const BucketDescription& bucket_desc); - template void addBucketFeatures(Bucket& bucket, const VectorTileLayer& layer, const BucketDescription& bucket_desc, Args... args); + template void addBucketFeatures(Bucket& bucket, const VectorTileLayer& layer, const BucketDescription& bucket_desc, Args&& ...args); private: const VectorTile vector_data; VectorTileData& tile; const Style& style; GlyphAtlas& glyphAtlas; + SpriteAtlas &spriteAtlas; Faces faces; Placement placement; }; diff --git a/include/llmr/platform/platform.hpp b/include/llmr/platform/platform.hpp index 4f7b4ed6933..dc8f7b5faf3 100644 --- a/include/llmr/platform/platform.hpp +++ b/include/llmr/platform/platform.hpp @@ -32,8 +32,11 @@ std::shared_ptr request_http(const std::string &url, // Cancels an HTTP request. void cancel_request_http(const std::shared_ptr &req); -// Shows an RGBA image with the specified dimensions in a named window. +// Shows an alpha image with the specified dimensions in a named window. void show_debug_image(std::string name, const char *data, size_t width, size_t height); + +// Shows an alpha image with the specified dimensions in a named window. +void show_color_debug_image(std::string name, const char *data, size_t width, size_t height); } } diff --git a/include/llmr/renderer/icon_bucket.hpp b/include/llmr/renderer/icon_bucket.hpp index ce90edce30d..0878622ec3c 100644 --- a/include/llmr/renderer/icon_bucket.hpp +++ b/include/llmr/renderer/icon_bucket.hpp @@ -20,7 +20,7 @@ class IconVertexBuffer; class BucketDescription; class IconShader; class DotShader; -class Sprite; +class SpriteAtlas; class VectorTileFeature; class IconBucket : public Bucket { @@ -31,7 +31,7 @@ class IconBucket : public Bucket { virtual void render(Painter& painter, const std::string& layer_name, const Tile::ID& id); virtual bool hasData() const; - void addFeature(const VectorTileFeature &feature, const std::shared_ptr &sprite); + void addFeature(const VectorTileFeature &feature, SpriteAtlas &sprite_atlas); void drawIcons(IconShader& shader); void drawIcons(DotShader& shader); diff --git a/include/llmr/style/style.hpp b/include/llmr/style/style.hpp index 8fcb99732ec..d9c8c22bdad 100644 --- a/include/llmr/style/style.hpp +++ b/include/llmr/style/style.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include diff --git a/include/llmr/util/math.hpp b/include/llmr/util/math.hpp index 531a13a1a59..ab8e392c481 100644 --- a/include/llmr/util/math.hpp +++ b/include/llmr/util/math.hpp @@ -87,12 +87,28 @@ inline T dist(const S1& a, const S2& b) { return c; } +template +inline T length(T a, T b) { + return std::sqrt(a * a + b * b); +} + // Take the magnitude of vector a. template inline T mag(const S& a) { return std::sqrt(a.x * a.x + a.y * a.y); } +template +T clamp(T value, T min, T max) { + return value < min ? min : (value > max ? max : value); +} + +template +T smoothstep(T edge0, T edge1, T x) { + T t = clamp((x - edge0) / (edge1 - edge0), T(0), T(1)); + return t * t * (T(3) - T(2) * t); +} + } } diff --git a/include/llmr/util/raster.hpp b/include/llmr/util/raster.hpp index ccdffeef373..0ad29dc7a20 100644 --- a/include/llmr/util/raster.hpp +++ b/include/llmr/util/raster.hpp @@ -32,6 +32,8 @@ class Raster : public std::enable_shared_from_this { bool needsTransition() const; void updateTransitions(time now); + inline const char *getData() const { return img; } + public: // loaded image dimensions uint32_t width = 0, height = 0; diff --git a/include/llmr/util/rect.hpp b/include/llmr/util/rect.hpp index 56bab2c9c97..2e142c30185 100644 --- a/include/llmr/util/rect.hpp +++ b/include/llmr/util/rect.hpp @@ -9,6 +9,11 @@ struct Rect { T x = 0, y = 0; T w = 0, h = 0; + template + Rect operator *(Number value) const { + return Rect(x * value, y * value, w * value, h * value); + } + operator bool() const { return w == 0 || h == 0; } }; } diff --git a/src/geometry/sprite_atlas.cpp b/src/geometry/sprite_atlas.cpp new file mode 100644 index 00000000000..80212c1dfbc --- /dev/null +++ b/src/geometry/sprite_atlas.cpp @@ -0,0 +1,235 @@ +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + + +using namespace llmr; + +SpriteAtlas::SpriteAtlas(dimension width, dimension height) + : width(width), + height(height), + bin(width, height), + dirty(true) { +} + +bool SpriteAtlas::resize(const float newRatio) { + if (pixelRatio == newRatio) return false; + + std::lock_guard lock(mtx); + + const float oldRatio = pixelRatio; + pixelRatio = newRatio; + + if (data) { + char *old_data = data; + + data = nullptr; + allocate(); + + dimension w = static_cast(width * newRatio); + dimension h = static_cast(height * newRatio); + float s = std::pow(oldRatio / newRatio, 2); + + // Basic image scaling. TODO: Replace this with better image scaling. + uint32_t *img_new = reinterpret_cast(data); + uint32_t *img_old = reinterpret_cast(old_data); + for (size_t i = 0, length = w * h; i < length; i++) { + img_new[i] = img_old[static_cast(s * i)]; + } + + free(old_data); + dirty = true; + } + + return dirty; +} + +Rect SpriteAtlas::getIcon(const int size, const std::string &name) { + std::lock_guard lock(mtx); + + std::map> &size_index = index[size]; + + auto rect_it = size_index.find(name); + if (rect_it != size_index.end()) { + return rect_it->second; + } + + // We have to allocate a new area in the bin, and store an empty image in it. + // Add a 1px border around every image. + const dimension pack_size = size + 2 * buffer; + + Rect rect = bin.allocate(pack_size, pack_size); + if (rect.w == 0) { + fprintf(stderr, "sprite atlas bitmap overflow"); + return rect; + } + + size_index.emplace(name, rect); + + allocate(); + + + // Draw an antialiased circle. + const int img_size = size * pixelRatio; + const int img_offset_x = (rect.x + buffer) * pixelRatio; + const int img_offset_y = (rect.y + buffer) * pixelRatio; + + uint32_t *sprite_img = reinterpret_cast(data); + const float blur = 1.5f / size; + + const uint8_t r = 0x7F; + const uint8_t g = 0x7F; + const uint8_t b = 0x7F; + + const int sprite_stride = width * pixelRatio; + for (int y = 0; y < img_size; y++) { + const int img_y = (img_offset_y + y) * sprite_stride + img_offset_x; + for (int x = 0; x < img_size; x++) { + + const float dist = util::length(float(x) / img_size - 0.5f, float(y) / img_size - 0.5f); + const float t = util::smoothstep(0.5f, 0.5f - blur, dist); + const uint8_t alpha = t * 255; + + uint32_t color = (uint32_t(r * t) << 0) | + (uint32_t(g * t) << 8) | + (uint32_t(b * t) << 16) | + (uint32_t(alpha) << 24); + sprite_img[img_y + x] = color; + } + } + + uninitialized.emplace(size, name); + + dirty = true; + + return rect; +} + +void SpriteAtlas::allocate() { + if (!data) { + dimension w = static_cast(width * pixelRatio); + dimension h = static_cast(height * pixelRatio); + data = (char *)calloc(w * h, sizeof(uint32_t)); + } +} + +Rect SpriteAtlas::setIcon(const int size, const std::string &name, const std::string &icon) { + Rect rect = getIcon(size, name); + + // Copy the bitmap + const int img_size = size * pixelRatio; + const int img_offset_x = (rect.x + buffer) * pixelRatio; + const int img_offset_y = (rect.y + buffer) * pixelRatio; + + if (std::pow(size * pixelRatio, 2) * 4 /* rgba */ != icon.size()) { fprintf(stderr, "mismatched icon buffer size!"); } + + const uint32_t *icon_img = reinterpret_cast(icon.data()); + uint32_t *sprite_img = reinterpret_cast(data); + + const int sprite_stride = width * pixelRatio; + const int icon_stride = size * pixelRatio; + for (size_t y = 0; y < img_size; y++) { + const int img_y = (img_offset_y + y) * sprite_stride + img_offset_x; + const int icon_y = y * icon_stride; + for (size_t x = 0; x < img_size; x++) { + sprite_img[img_y + x] = icon_img[icon_y + x]; + } + } + + dirty = true; + + return rect; +} + +void SpriteAtlas::update(const Sprite &sprite) { + if (!sprite.isLoaded()) return; + + SpriteAtlas &atlas = *this; + std::erase_if(uninitialized, [&sprite, &atlas](const std::pair &pair) { + const int &size = pair.first; + const std::string &name = pair.second; + const SpritePosition &src = sprite.getSpritePosition(name + "-" + std::to_string(size)); + if (src.width == size && src.height == size && src.pixelRatio == atlas.pixelRatio) { + const uint32_t *src_img = reinterpret_cast(sprite.raster.getData()); + + uint32_t *dst_img = reinterpret_cast(atlas.data); + Rect dst = atlas.getIcon(size, name); + dst.x = (dst.x + buffer) * atlas.pixelRatio; + dst.y = (dst.y + buffer) * atlas.pixelRatio; + dst.w = (dst.w - 2 * buffer) * atlas.pixelRatio; + dst.h = (dst.h - 2 * buffer) * atlas.pixelRatio; + + + const int src_image_stride = sprite.raster.width; + const int dst_image_stride = atlas.width * atlas.pixelRatio; + const int src_stride = src.width * src.pixelRatio; + + for (int y = 0; y < dst.h; y++) { + const int src_pos = (src.y * src.pixelRatio + y) * src_image_stride + src.x * src.pixelRatio; + const int dst_pos = (dst.y + y) * dst_image_stride + dst.x; + + // TODO: this is crashing + // memcpy(dst_img + dst_pos, src_img + src_pos, src_stride * sizeof(uint32_t)); + } + + + return true; + } else { + return false; + } + }); +} + +void SpriteAtlas::bind(bool linear) { + if (!texture) { + glGenTextures(1, &texture); + 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); + } else { + glBindTexture(GL_TEXTURE_2D, texture); + } + + GLuint filter_val = linear ? GL_LINEAR : GL_NEAREST; + if (filter_val != filter) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter_val); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter_val); + filter = filter_val; + } + + if (dirty) { + std::lock_guard lock(mtx); + allocate(); + glTexImage2D( + GL_TEXTURE_2D, // GLenum target + 0, // GLint level + GL_RGBA, // GLint internalformat + width * pixelRatio, // GLsizei width + height * pixelRatio, // GLsizei height + 0, // GLint border + GL_RGBA, // GLenum format + GL_UNSIGNED_BYTE, // GLenum type + data // const GLvoid * data + ); + + platform::show_color_debug_image("Sprite Atlas", data, width * pixelRatio, height * pixelRatio); + + dirty = false; + } +}; + +SpriteAtlas::~SpriteAtlas() { + std::lock_guard lock(mtx); + if (data) { + free(data); + data = nullptr; + } +} diff --git a/src/map/map.cpp b/src/map/map.cpp index 79a26305e4d..4a16ac417ab 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -20,6 +20,7 @@ Map::Map(View& view) texturepool(), style(), glyphAtlas(1024, 1024), + spriteAtlas(512, 512), painter(*this), loop(uv_loop_new()) { @@ -175,7 +176,16 @@ void Map::resize(uint16_t width, uint16_t height, float ratio) { } void Map::resize(uint16_t width, uint16_t height, float ratio, uint16_t fb_width, uint16_t fb_height) { + bool changed = false; + if (transform.resize(width, height, ratio, fb_width, fb_height)) { + changed = true; + } + if (spriteAtlas.resize(ratio)) { + changed = true; + } + + if (changed) { update(); } } @@ -393,6 +403,11 @@ void Map::prepare() { style.sprite->load(kSpriteURL); } + // Allow the sprite atlas to potentially pull new sprite images if needed. + if (style.sprite && style.sprite->isLoaded()) { + spriteAtlas.update(*style.sprite); + } + updateTiles(); updateClippingIDs(); diff --git a/src/map/tile_parser.cpp b/src/map/tile_parser.cpp index 61957e60748..67a32d4cba1 100644 --- a/src/map/tile_parser.cpp +++ b/src/map/tile_parser.cpp @@ -14,11 +14,12 @@ using namespace llmr; -TileParser::TileParser(const std::string& data, VectorTileData& tile, const Style& style, GlyphAtlas& glyphAtlas) +TileParser::TileParser(const std::string& data, VectorTileData& tile, const Style& style, GlyphAtlas& glyphAtlas, SpriteAtlas &spriteAtlas) : vector_data(pbf((const uint8_t *)data.data(), data.size())), tile(tile), style(style), glyphAtlas(glyphAtlas), + spriteAtlas(spriteAtlas), placement(tile.id.z) { parseGlyphs(); parseStyleLayers(style.layers); @@ -120,12 +121,12 @@ void TileParser::addBucketFeatures(Bucket& bucket, const VectorTileLayer& layer, } } -template -void TileParser::addBucketFeatures(Bucket& bucket, const VectorTileLayer& layer, const BucketDescription& bucket_desc, Args... args) { +template +void TileParser::addBucketFeatures(Bucket& bucket, const VectorTileLayer& layer, const BucketDescription& bucket_desc, Args&& ...args) { FilteredVectorTileLayer filtered_layer(layer, bucket_desc); for (const pbf& feature_pbf : filtered_layer) { if (obsolete()) return; - bucket->addFeature({ feature_pbf, layer }, args...); + bucket->addFeature({ feature_pbf, layer }, std::forward(args)...); } } @@ -146,7 +147,7 @@ std::unique_ptr TileParser::createLineBucket(const VectorTileLayer& laye std::unique_ptr TileParser::createIconBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc) { std::unique_ptr bucket = std::make_unique( tile.iconVertexBuffer, bucket_desc); - addBucketFeatures(bucket, layer, bucket_desc, style.sprite); + addBucketFeatures(bucket, layer, bucket_desc, spriteAtlas); return obsolete() ? nullptr : std::move(bucket); } diff --git a/src/map/vector_tile_data.cpp b/src/map/vector_tile_data.cpp index 84fc4ffc4d1..337e2fcc352 100644 --- a/src/map/vector_tile_data.cpp +++ b/src/map/vector_tile_data.cpp @@ -22,7 +22,7 @@ void VectorTileData::parse() { // Parsing creates state that is encapsulated in TileParser. While parsing, // the TileParser object writes results into this objects. All other state // is going to be discarded afterwards. - TileParser parser(data, *this, map.getStyle(), map.getGlyphAtlas()); + TileParser parser(data, *this, map.getStyle(), map.getGlyphAtlas(), map.getSpriteAtlas()); } catch (const std::exception& ex) { fprintf(stderr, "[%p] exception [%d/%d/%d]... failed: %s\n", this, id.z, id.x, id.y, ex.what()); cancel(); diff --git a/src/renderer/icon_bucket.cpp b/src/renderer/icon_bucket.cpp index 060e1e01673..9d1505c03f0 100644 --- a/src/renderer/icon_bucket.cpp +++ b/src/renderer/icon_bucket.cpp @@ -23,27 +23,18 @@ IconBucket::IconBucket(IconVertexBuffer& vertexBuffer, vertex_start(vertexBuffer.index()) { } -void IconBucket::addFeature(const VectorTileFeature &feature, const std::shared_ptr &sprite) { - // TODO: We somehow need to reparse the stylesheet, or maintain a mapping of id => sprite name - // For now, we're just not showing points if the sprite was not yet loaded at *parse time* of - // the tile. - if (!sprite || !sprite->isLoaded()) return; - +void IconBucket::addFeature(const VectorTileFeature &feature, SpriteAtlas &sprite_atlas) { auto field_it = feature.properties.find(geometry.field); if (field_it == feature.properties.end()) { - fprintf(stderr, "feature doesn't contain field '%s'\n", geometry.field.c_str()); + // fprintf(stderr, "feature doesn't contain field '%s'\n", geometry.field.c_str()); return; } std::string field = toString(field_it->second); - if (geometry.size) { - field.append("-"); - field.append(std::to_string(static_cast(std::round(geometry.size)))); - } - const SpritePosition &pos = sprite->getSpritePosition(field); - const uint16_t tx = pos.x + pos.width / 2; - const uint16_t ty = pos.y + pos.height / 2; + const Rect rect = sprite_atlas.getIcon(geometry.size, field); + const uint16_t tx = rect.x + rect.w / 2; + const uint16_t ty = rect.y + rect.h / 2; Geometry::command cmd; pbf geom = feature.geometry; diff --git a/src/renderer/painter_icon.cpp b/src/renderer/painter_icon.cpp index 0590aeb1d7d..53d775a1765 100644 --- a/src/renderer/painter_icon.cpp +++ b/src/renderer/painter_icon.cpp @@ -26,44 +26,27 @@ void Painter::renderIcon(IconBucket& bucket, const std::string& layer_name, cons const mat4 &vtxMatrix = translatedMatrix(properties.translate, id, properties.translateAnchor); - const std::shared_ptr &sprite = map.getStyle().sprite; + SpriteAtlas &spriteAtlas = map.getSpriteAtlas(); - if (!sprite || !sprite->isLoaded()) { - useProgram(dotShader->program); - dotShader->setMatrix(vtxMatrix); - dotShader->setColor(color); + useProgram(iconShader->program); + iconShader->setMatrix(vtxMatrix); + iconShader->setColor(color); + iconShader->setImage(0); - const float iconSize = (properties.radius ? properties.radius * 2 : 8) * map.getState().getPixelRatio(); - dotShader->setSize(iconSize); -#ifndef GL_ES_VERSION_2_0 - glPointSize(iconSize); - glEnable(GL_POINT_SPRITE); -#endif - dotShader->setBlur((properties.blur ? properties.blur : 1.5) / iconSize); - - glDepthRange(strata, 1.0f); - bucket.drawIcons(*dotShader); - } else { - useProgram(iconShader->program); - iconShader->setMatrix(vtxMatrix); - iconShader->setColor(color); - iconShader->setImage(0); - - iconShader->setDimension({{ - static_cast(sprite->raster.width), - static_cast(sprite->raster.height) - }}); + iconShader->setDimension({{ + spriteAtlas.getTextureWidth(), + spriteAtlas.getTextureHeight(), + }}); - sprite->raster.bind(map.getState().isChanging()); + spriteAtlas.bind(map.getState().isChanging()); - const float iconSize = bucket.geometry.size * map.getState().getPixelRatio(); - iconShader->setSize(iconSize); + const float iconSize = bucket.geometry.size * map.getState().getPixelRatio(); + iconShader->setSize(iconSize); #ifndef GL_ES_VERSION_2_0 - glPointSize(iconSize); - glEnable(GL_POINT_SPRITE); + glPointSize(iconSize); + glEnable(GL_POINT_SPRITE); #endif - glDepthRange(strata, 1.0f); - bucket.drawIcons(*iconShader); - } + glDepthRange(strata, 1.0f); + bucket.drawIcons(*iconShader); } diff --git a/src/shader/icon.fragment.glsl b/src/shader/icon.fragment.glsl index aacbb6a54bb..a3191b3a8d2 100644 --- a/src/shader/icon.fragment.glsl +++ b/src/shader/icon.fragment.glsl @@ -7,5 +7,5 @@ varying vec2 v_tex; void main() { vec2 pos = (v_tex + (gl_PointCoord - 0.5) * u_size) / u_dimension; - gl_FragColor = texture2D(u_image, pos) * u_color; + gl_FragColor = texture2D(u_image, pos); } diff --git a/src/shader/shaders_gl.cpp b/src/shader/shaders_gl.cpp index aebd5bfac4e..8473296f223 100644 --- a/src/shader/shaders_gl.cpp +++ b/src/shader/shaders_gl.cpp @@ -12,7 +12,7 @@ const shader_source llmr::shaders[SHADER_COUNT] = { }, { "#version 120\nattribute vec2 a_pos;\nattribute vec2 a_tex;\nuniform mat4 u_matrix;\nuniform float u_size;\nvarying vec2 v_tex;\nvoid main ()\n{\n vec4 tmpvar_1;\n tmpvar_1.zw = vec2(0.0, 1.0);\n tmpvar_1.xy = a_pos;\n gl_Position = (u_matrix * tmpvar_1);\n gl_PointSize = u_size;\n v_tex = a_tex;\n}\n\n", - "#version 120\nuniform sampler2D u_image;\nuniform vec2 u_dimension;\nuniform vec4 u_color;\nuniform float u_size;\nvarying vec2 v_tex;\nvoid main ()\n{\n gl_FragColor = (texture2D (u_image, ((v_tex + \n ((gl_PointCoord - 0.5) * u_size)\n ) / u_dimension)) * u_color);\n}\n\n", + "#version 120\nuniform sampler2D u_image;\nuniform vec2 u_dimension;\nuniform float u_size;\nvarying vec2 v_tex;\nvoid main ()\n{\n gl_FragColor = texture2D (u_image, ((v_tex + (\n (gl_PointCoord - 0.5)\n * u_size)) / u_dimension));\n}\n\n", }, { "#version 120\nattribute vec2 a_pos;\nattribute vec2 a_extrude;\nattribute float a_linesofar;\nuniform mat4 u_matrix;\nuniform mat4 u_exmatrix;\nuniform float u_ratio;\nuniform vec2 u_linewidth;\nvarying vec2 v_normal;\nvarying float v_linesofar;\nvoid main ()\n{\n vec2 normal_1;\n vec2 tmpvar_2;\n tmpvar_2 = (vec2(mod (a_pos, 2.0)));\n normal_1.x = tmpvar_2.x;\n normal_1.y = sign((tmpvar_2.y - 0.5));\n v_normal = normal_1;\n vec4 tmpvar_3;\n tmpvar_3.zw = vec2(0.0, 0.0);\n tmpvar_3.xy = ((u_linewidth.x * a_extrude) * 0.015873);\n vec4 tmpvar_4;\n tmpvar_4.zw = vec2(0.0, 1.0);\n tmpvar_4.xy = floor((a_pos * 0.5));\n gl_Position = ((u_matrix * tmpvar_4) + (u_exmatrix * tmpvar_3));\n v_linesofar = (a_linesofar * u_ratio);\n}\n\n", diff --git a/src/shader/shaders_gles2.cpp b/src/shader/shaders_gles2.cpp index 185e4c0a879..db78f465706 100644 --- a/src/shader/shaders_gles2.cpp +++ b/src/shader/shaders_gles2.cpp @@ -12,7 +12,7 @@ const shader_source llmr::shaders[SHADER_COUNT] = { }, { "precision highp float;\nattribute vec2 a_pos;\nattribute vec2 a_tex;\nuniform mat4 u_matrix;\nuniform float u_size;\nvarying vec2 v_tex;\nvoid main ()\n{\n vec4 tmpvar_1;\n tmpvar_1.zw = vec2(0.0, 1.0);\n tmpvar_1.xy = a_pos;\n gl_Position = (u_matrix * tmpvar_1);\n gl_PointSize = u_size;\n v_tex = a_tex;\n}\n\n", - "precision highp float;\nuniform sampler2D u_image;\nuniform vec2 u_dimension;\nuniform vec4 u_color;\nuniform float u_size;\nvarying vec2 v_tex;\nvoid main ()\n{\n mediump vec2 tmpvar_1;\n tmpvar_1 = ((v_tex + (\n (gl_PointCoord - 0.5)\n * u_size)) / u_dimension);\n lowp vec4 tmpvar_2;\n tmpvar_2 = (texture2D (u_image, tmpvar_1) * u_color);\n gl_FragColor = tmpvar_2;\n}\n\n", + "precision highp float;\nuniform sampler2D u_image;\nuniform vec2 u_dimension;\nuniform float u_size;\nvarying vec2 v_tex;\nvoid main ()\n{\n mediump vec2 tmpvar_1;\n tmpvar_1 = ((v_tex + (\n (gl_PointCoord - 0.5)\n * u_size)) / u_dimension);\n lowp vec4 tmpvar_2;\n tmpvar_2 = texture2D (u_image, tmpvar_1);\n gl_FragColor = tmpvar_2;\n}\n\n", }, { "precision highp float;\nattribute vec2 a_pos;\nattribute vec2 a_extrude;\nattribute float a_linesofar;\nuniform mat4 u_matrix;\nuniform mat4 u_exmatrix;\nuniform float u_ratio;\nuniform vec2 u_linewidth;\nvarying vec2 v_normal;\nvarying float v_linesofar;\nvoid main ()\n{\n vec2 normal_1;\n vec2 tmpvar_2;\n tmpvar_2 = (vec2(mod (a_pos, 2.0)));\n normal_1.x = tmpvar_2.x;\n normal_1.y = sign((tmpvar_2.y - 0.5));\n v_normal = normal_1;\n vec4 tmpvar_3;\n tmpvar_3.zw = vec2(0.0, 0.0);\n tmpvar_3.xy = ((u_linewidth.x * a_extrude) * 0.015873);\n vec4 tmpvar_4;\n tmpvar_4.zw = vec2(0.0, 1.0);\n tmpvar_4.xy = floor((a_pos * 0.5));\n gl_Position = ((u_matrix * tmpvar_4) + (u_exmatrix * tmpvar_3));\n v_linesofar = (a_linesofar * u_ratio);\n}\n\n", diff --git a/src/style/sprite.cpp b/src/style/sprite.cpp index 93dd6bd2481..fb3a959f98a 100644 --- a/src/style/sprite.cpp +++ b/src/style/sprite.cpp @@ -31,35 +31,35 @@ Sprite::Sprite(Map &map, float pixelRatio) } void Sprite::load(const std::string& base_url) { - std::shared_ptr sprite = shared_from_this(); - - std::string suffix = (pixelRatio > 1 ? "@2x" : ""); - - platform::request_http(base_url + suffix + ".json", [sprite](platform::Response *res) { - if (res->code == 200) { - sprite->body.swap(res->body); - sprite->asyncParseJSON(); - } else { - fprintf(stderr, "failed to load sprite\n"); - } - }, map.getLoop()); - - platform::request_http(base_url + suffix + ".png", [sprite](platform::Response *res) { - if (res->code == 200) { - sprite->image.swap(res->body); - sprite->asyncParseImage(); - } else { - fprintf(stderr, "failed to load sprite image\n"); - } - }, map.getLoop()); + std::shared_ptr sprite = shared_from_this(); + + std::string suffix = (pixelRatio > 1 ? "@2x" : ""); + + platform::request_http(base_url + suffix + ".json", [sprite](platform::Response *res) { + if (res->code == 200) { + sprite->body.swap(res->body); + sprite->asyncParseJSON(); + } else { + fprintf(stderr, "failed to load sprite\n"); + } + }, map.getLoop()); + + platform::request_http(base_url + suffix + ".png", [sprite](platform::Response *res) { + if (res->code == 200) { + sprite->image.swap(res->body); + sprite->asyncParseImage(); + } else { + fprintf(stderr, "failed to load sprite image\n"); + } + }, map.getLoop()); } void Sprite::complete(std::shared_ptr &sprite) { - if (sprite->raster.isLoaded() && sprite->pos.size()) { - sprite->loaded = true; - sprite->map.update(); - fprintf(stderr, "sprite loaded\n"); - } + if (sprite->raster.isLoaded() && sprite->pos.size()) { + sprite->loaded = true; + sprite->map.update(); + fprintf(stderr, "sprite loaded\n"); + } } bool Sprite::isLoaded() const { From 898c6d6714bcbd1a8600deca1437a59f3c33f10e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Mon, 19 May 2014 11:13:22 +0200 Subject: [PATCH 4/7] fix for retina screens --- common/glfw_view.cpp | 11 ++++++----- include/llmr/platform/platform.hpp | 2 +- include/llmr/shader/icon_shader.hpp | 4 ++++ src/geometry/sprite_atlas.cpp | 4 ++-- src/renderer/painter_icon.cpp | 2 +- src/shader/icon.vertex.glsl | 3 ++- src/shader/icon_shader.cpp | 9 +++++++++ src/shader/shaders_gl.cpp | 2 +- src/shader/shaders_gles2.cpp | 2 +- 9 files changed, 27 insertions(+), 12 deletions(-) diff --git a/common/glfw_view.cpp b/common/glfw_view.cpp index 2542e6cf93d..3ef7bc36a9b 100644 --- a/common/glfw_view.cpp +++ b/common/glfw_view.cpp @@ -232,10 +232,10 @@ void show_debug_image(std::string name, const char *data, size_t width, size_t h } -void show_color_debug_image(std::string name, const char *data, size_t width, size_t height) { +void show_color_debug_image(std::string name, const char *data, size_t logical_width, size_t logical_height, size_t width, size_t height) { static GLFWwindow *debug_window = nullptr; if (!debug_window) { - debug_window = glfwCreateWindow(width, height, name.c_str(), nullptr, nullptr); + debug_window = glfwCreateWindow(logical_width, logical_height, name.c_str(), nullptr, nullptr); if (!debug_window) { glfwTerminate(); fprintf(stderr, "Failed to initialize window\n"); @@ -245,14 +245,15 @@ void show_color_debug_image(std::string name, const char *data, size_t width, si GLFWwindow *current_window = glfwGetCurrentContext(); - glfwSetWindowSize(debug_window, width, height); + glfwSetWindowSize(debug_window, logical_width, logical_height); glfwMakeContextCurrent(debug_window); int fb_width, fb_height; glfwGetFramebufferSize(debug_window, &fb_width, &fb_height); - float scale = (float)fb_width / (float)width; + float x_scale = (float)fb_width / (float)width; + float y_scale = (float)fb_height / (float)height; - glPixelZoom(scale, -scale); + glPixelZoom(x_scale, -y_scale); glRasterPos2f(-1.0f, 1.0f); glDrawPixels(width, height, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, data); glfwSwapBuffers(debug_window); diff --git a/include/llmr/platform/platform.hpp b/include/llmr/platform/platform.hpp index dc8f7b5faf3..62fa1badc94 100644 --- a/include/llmr/platform/platform.hpp +++ b/include/llmr/platform/platform.hpp @@ -36,7 +36,7 @@ void cancel_request_http(const std::shared_ptr &req); void show_debug_image(std::string name, const char *data, size_t width, size_t height); // Shows an alpha image with the specified dimensions in a named window. -void show_color_debug_image(std::string name, const char *data, size_t width, size_t height); +void show_color_debug_image(std::string name, const char *data, size_t logical_width, size_t logical_height, size_t width, size_t height); } } diff --git a/include/llmr/shader/icon_shader.hpp b/include/llmr/shader/icon_shader.hpp index 6b3644f28ee..daf37b374ad 100644 --- a/include/llmr/shader/icon_shader.hpp +++ b/include/llmr/shader/icon_shader.hpp @@ -15,6 +15,7 @@ class IconShader : public Shader { void setColor(const std::array& color); void setDimension(const std::array& dimension); void setSize(float size); + void setRatio(float ratio); private: int32_t a_pos = -1; @@ -31,6 +32,9 @@ class IconShader : public Shader { float size = 0; int32_t u_size = -1; + + float ratio = 0; + int32_t u_ratio = -1; }; } diff --git a/src/geometry/sprite_atlas.cpp b/src/geometry/sprite_atlas.cpp index 80212c1dfbc..4c6f5187861 100644 --- a/src/geometry/sprite_atlas.cpp +++ b/src/geometry/sprite_atlas.cpp @@ -83,7 +83,7 @@ Rect SpriteAtlas::getIcon(const int size, const std::str const int img_offset_y = (rect.y + buffer) * pixelRatio; uint32_t *sprite_img = reinterpret_cast(data); - const float blur = 1.5f / size; + const float blur = 1.0f / size / pixelRatio; const uint8_t r = 0x7F; const uint8_t g = 0x7F; @@ -220,7 +220,7 @@ void SpriteAtlas::bind(bool linear) { data // const GLvoid * data ); - platform::show_color_debug_image("Sprite Atlas", data, width * pixelRatio, height * pixelRatio); + platform::show_color_debug_image("Sprite Atlas", data, width, height, width * pixelRatio, height * pixelRatio); dirty = false; } diff --git a/src/renderer/painter_icon.cpp b/src/renderer/painter_icon.cpp index 53d775a1765..b5f4964e6a4 100644 --- a/src/renderer/painter_icon.cpp +++ b/src/renderer/painter_icon.cpp @@ -32,7 +32,7 @@ void Painter::renderIcon(IconBucket& bucket, const std::string& layer_name, cons iconShader->setMatrix(vtxMatrix); iconShader->setColor(color); iconShader->setImage(0); - + iconShader->setRatio(map.getState().getPixelRatio()); iconShader->setDimension({{ spriteAtlas.getTextureWidth(), spriteAtlas.getTextureHeight(), diff --git a/src/shader/icon.vertex.glsl b/src/shader/icon.vertex.glsl index 51b378f3fde..d0ffe4b33e5 100644 --- a/src/shader/icon.vertex.glsl +++ b/src/shader/icon.vertex.glsl @@ -3,11 +3,12 @@ attribute vec2 a_tex; uniform mat4 u_matrix; uniform float u_size; +uniform float u_ratio; varying vec2 v_tex; void main() { gl_Position = u_matrix * vec4(a_pos, 0, 1); gl_PointSize = u_size; - v_tex = a_tex; + v_tex = a_tex * u_ratio; } diff --git a/src/shader/icon_shader.cpp b/src/shader/icon_shader.cpp index f991d1f39aa..c656eb3338e 100644 --- a/src/shader/icon_shader.cpp +++ b/src/shader/icon_shader.cpp @@ -22,12 +22,14 @@ IconShader::IconShader() u_matrix = glGetUniformLocation(program, "u_matrix"); u_color = glGetUniformLocation(program, "u_color"); u_size = glGetUniformLocation(program, "u_size"); + u_ratio = glGetUniformLocation(program, "u_ratio"); u_dimension = glGetUniformLocation(program, "u_dimension"); // fprintf(stderr, "IconShader:\n"); // fprintf(stderr, " - u_matrix: %d\n", u_matrix); // fprintf(stderr, " - u_color: %d\n", u_color); // fprintf(stderr, " - u_size: %d\n", u_size); + // fprintf(stderr, " - u_ratio: %d\n", u_ratio); // fprintf(stderr, " - u_dimension: %d\n", u_dimension); // fprintf(stderr, " - u_image: %d\n", u_image); } @@ -61,6 +63,13 @@ void IconShader::setSize(float new_size) { } } +void IconShader::setRatio(float new_ratio) { + if (ratio != new_ratio) { + glUniform1f(u_ratio, new_ratio); + ratio = new_ratio; + } +} + void IconShader::setDimension(const std::array& new_dimension) { if (dimension != new_dimension) { glUniform2fv(u_dimension, 1, new_dimension.data()); diff --git a/src/shader/shaders_gl.cpp b/src/shader/shaders_gl.cpp index 8473296f223..29cbc0eadff 100644 --- a/src/shader/shaders_gl.cpp +++ b/src/shader/shaders_gl.cpp @@ -11,7 +11,7 @@ const shader_source llmr::shaders[SHADER_COUNT] = { "#version 120\nuniform vec4 u_color;\nuniform float u_blur;\nvoid main ()\n{\n vec2 x_1;\n x_1 = (gl_PointCoord - 0.5);\n float tmpvar_2;\n tmpvar_2 = clamp (((\n sqrt(dot (x_1, x_1))\n - 0.5) / (\n (0.5 - u_blur)\n - 0.5)), 0.0, 1.0);\n gl_FragColor = (u_color * (tmpvar_2 * (tmpvar_2 * \n (3.0 - (2.0 * tmpvar_2))\n )));\n}\n\n", }, { - "#version 120\nattribute vec2 a_pos;\nattribute vec2 a_tex;\nuniform mat4 u_matrix;\nuniform float u_size;\nvarying vec2 v_tex;\nvoid main ()\n{\n vec4 tmpvar_1;\n tmpvar_1.zw = vec2(0.0, 1.0);\n tmpvar_1.xy = a_pos;\n gl_Position = (u_matrix * tmpvar_1);\n gl_PointSize = u_size;\n v_tex = a_tex;\n}\n\n", + "#version 120\nattribute vec2 a_pos;\nattribute vec2 a_tex;\nuniform mat4 u_matrix;\nuniform float u_size;\nuniform float u_ratio;\nvarying vec2 v_tex;\nvoid main ()\n{\n vec4 tmpvar_1;\n tmpvar_1.zw = vec2(0.0, 1.0);\n tmpvar_1.xy = a_pos;\n gl_Position = (u_matrix * tmpvar_1);\n gl_PointSize = u_size;\n v_tex = (a_tex * u_ratio);\n}\n\n", "#version 120\nuniform sampler2D u_image;\nuniform vec2 u_dimension;\nuniform float u_size;\nvarying vec2 v_tex;\nvoid main ()\n{\n gl_FragColor = texture2D (u_image, ((v_tex + (\n (gl_PointCoord - 0.5)\n * u_size)) / u_dimension));\n}\n\n", }, { diff --git a/src/shader/shaders_gles2.cpp b/src/shader/shaders_gles2.cpp index db78f465706..f19edf54062 100644 --- a/src/shader/shaders_gles2.cpp +++ b/src/shader/shaders_gles2.cpp @@ -11,7 +11,7 @@ const shader_source llmr::shaders[SHADER_COUNT] = { "precision highp float;\nuniform vec4 u_color;\nuniform float u_blur;\nvoid main ()\n{\n mediump vec2 x_1;\n x_1 = (gl_PointCoord - 0.5);\n mediump float tmpvar_2;\n tmpvar_2 = clamp (((\n sqrt(dot (x_1, x_1))\n - 0.5) / (\n (0.5 - u_blur)\n - 0.5)), 0.0, 1.0);\n gl_FragColor = (u_color * (tmpvar_2 * (tmpvar_2 * \n (3.0 - (2.0 * tmpvar_2))\n )));\n}\n\n", }, { - "precision highp float;\nattribute vec2 a_pos;\nattribute vec2 a_tex;\nuniform mat4 u_matrix;\nuniform float u_size;\nvarying vec2 v_tex;\nvoid main ()\n{\n vec4 tmpvar_1;\n tmpvar_1.zw = vec2(0.0, 1.0);\n tmpvar_1.xy = a_pos;\n gl_Position = (u_matrix * tmpvar_1);\n gl_PointSize = u_size;\n v_tex = a_tex;\n}\n\n", + "precision highp float;\nattribute vec2 a_pos;\nattribute vec2 a_tex;\nuniform mat4 u_matrix;\nuniform float u_size;\nuniform float u_ratio;\nvarying vec2 v_tex;\nvoid main ()\n{\n vec4 tmpvar_1;\n tmpvar_1.zw = vec2(0.0, 1.0);\n tmpvar_1.xy = a_pos;\n gl_Position = (u_matrix * tmpvar_1);\n gl_PointSize = u_size;\n v_tex = (a_tex * u_ratio);\n}\n\n", "precision highp float;\nuniform sampler2D u_image;\nuniform vec2 u_dimension;\nuniform float u_size;\nvarying vec2 v_tex;\nvoid main ()\n{\n mediump vec2 tmpvar_1;\n tmpvar_1 = ((v_tex + (\n (gl_PointCoord - 0.5)\n * u_size)) / u_dimension);\n lowp vec4 tmpvar_2;\n tmpvar_2 = texture2D (u_image, tmpvar_1);\n gl_FragColor = tmpvar_2;\n}\n\n", }, { From f848868c16ff4b55e0f983e25853589a50d94824 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Mon, 19 May 2014 14:18:29 +0200 Subject: [PATCH 5/7] fix texture binding --- include/llmr/geometry/sprite_atlas.hpp | 20 ++- include/llmr/style/sprite.hpp | 14 +- include/llmr/util/image.hpp | 20 +++ include/llmr/util/raster.hpp | 9 +- src/geometry/sprite_atlas.cpp | 198 ++++++++++++++----------- src/renderer/painter_fill.cpp | 24 ++- src/style/sprite.cpp | 40 +---- src/util/image.cpp | 126 ++++++++++++++++ src/util/raster.cpp | 131 +--------------- 9 files changed, 301 insertions(+), 281 deletions(-) diff --git a/include/llmr/geometry/sprite_atlas.hpp b/include/llmr/geometry/sprite_atlas.hpp index e6557846afe..220edc70147 100644 --- a/include/llmr/geometry/sprite_atlas.hpp +++ b/include/llmr/geometry/sprite_atlas.hpp @@ -12,6 +12,7 @@ namespace llmr { class Sprite; +class SpritePosition; class SpriteAtlas { public: @@ -28,23 +29,28 @@ class SpriteAtlas { // Update uninitialized sprites in this atlas from the given sprite. void update(const Sprite &sprite); - // Returns the coordinates of the icon. The getter also *creates* new icons in the atlas - // if they don't exist, but they'll be default-initialized with a a black circle. + // Returns the coordinates of a square icon. The getter also *creates* new square icons in the + // atlas if they don't exist, but they'll be default-initialized with a a black circle. Rect getIcon(int size, const std::string &name); - // Updates or creates an icon with a given size and name. The data must be a - // a (pixelRatio * size)^2 * 4 byte long RGBA image buffer. - Rect setIcon(int size, const std::string &name, const std::string &icon); + // Returns the coordinates of an image that is sourced from the sprite image. + // This getter does not create images, as the dimension of the texture us unknown if the + // sprite is not yet loaded. Instead, it returns a 0/0/0/0 rect. + Rect getImage(const std::string &name, const Sprite &sprite); // Binds the image buffer of this sprite atlas to the GPU, and uploads data if it is out // of date. void bind(bool linear = false); + inline float getWidth() const { return width; } + inline float getHeight() const { return height; } inline float getTextureWidth() const { return width * pixelRatio; } inline float getTextureHeight() const { return height * pixelRatio; } private: void allocate(); + Rect allocateImage(size_t width, size_t height); + void copy(const Rect &dst, const SpritePosition &src, const Sprite &sprite); public: const dimension width = 0; @@ -54,8 +60,8 @@ class SpriteAtlas { std::mutex mtx; float pixelRatio = 1.0f; BinPack bin; - std::map>> index; - std::set> uninitialized; + std::map> images; + std::set uninitialized; char *data = nullptr; std::atomic dirty; uint32_t texture = 0; diff --git a/include/llmr/style/sprite.hpp b/include/llmr/style/sprite.hpp index 58fd2b1efab..60a61c033d5 100644 --- a/include/llmr/style/sprite.hpp +++ b/include/llmr/style/sprite.hpp @@ -24,31 +24,19 @@ class SpritePosition { uint8_t pixelRatio = 1; }; -class ImagePosition { -public: - explicit ImagePosition() {} - explicit ImagePosition(const vec2& size, vec2 tl, vec2 br); - - vec2 size = { 0, 0 }; - vec2 tl = { 0, 0 }; - vec2 br = { 0, 0 }; -}; - - class Sprite : public std::enable_shared_from_this { public: Sprite(Map &map, float pixelRatio = 1); void load(const std::string& base_url); - ImagePosition getPosition(const std::string& name, bool repeating = false) const; const SpritePosition &getSpritePosition(const std::string& name) const; bool isLoaded() const; public: const float pixelRatio; - Raster raster; + std::unique_ptr raster; private: void asyncParseJSON(); diff --git a/include/llmr/util/image.hpp b/include/llmr/util/image.hpp index abc021e03a1..d38567041fb 100644 --- a/include/llmr/util/image.hpp +++ b/include/llmr/util/image.hpp @@ -8,6 +8,26 @@ namespace util { std::string compress_png(int width, int height, void *rgba, bool flip = false); + +class Image { +public: + Image(const std::string &img); + ~Image(); + + inline const char *getData() const { return img; } + inline uint32_t getWidth() const { return width; } + inline uint32_t getHeight() const { return height; } + +private: + // loaded image dimensions + uint32_t width = 0, height = 0; + + // the raw image data + char *img = nullptr; + +}; + + } } diff --git a/include/llmr/util/raster.hpp b/include/llmr/util/raster.hpp index 0ad29dc7a20..b6d32eac8e7 100644 --- a/include/llmr/util/raster.hpp +++ b/include/llmr/util/raster.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -32,8 +33,6 @@ class Raster : public std::enable_shared_from_this { bool needsTransition() const; void updateTransitions(time now); - inline const char *getData() const { return img; } - public: // loaded image dimensions uint32_t width = 0, height = 0; @@ -47,10 +46,6 @@ class Raster : public std::enable_shared_from_this { // texture opacity double opacity = 0; -private: - // load raw pixels - void loadImage(const std::string& data); - private: mutable std::mutex mtx; @@ -64,7 +59,7 @@ class Raster : public std::enable_shared_from_this { uint32_t filter = 0; // the raw pixels - char *img = nullptr; + std::unique_ptr img; // fade in transition std::shared_ptr fade_transition = nullptr; diff --git a/src/geometry/sprite_atlas.cpp b/src/geometry/sprite_atlas.cpp index 4c6f5187861..4f05df62011 100644 --- a/src/geometry/sprite_atlas.cpp +++ b/src/geometry/sprite_atlas.cpp @@ -52,61 +52,114 @@ bool SpriteAtlas::resize(const float newRatio) { return dirty; } -Rect SpriteAtlas::getIcon(const int size, const std::string &name) { - std::lock_guard lock(mtx); +void copy_bitmap(const uint32_t *src, const int src_stride, const int src_x, const int src_y, + uint32_t *dst, const int dst_stride, const int dst_x, const int dst_y, + const int width, const int height) { + const int stride = width * sizeof(uint32_t); + src += src_y * src_stride + src_x; + dst += dst_y * dst_stride + dst_x; + for (int y = 0; y < height; y++, src += src_stride, dst += dst_stride) { + memcpy(dst, src, stride); + } +} - std::map> &size_index = index[size]; +void draw_circle(uint32_t *dst, const int dst_stride, const int dst_x, const int dst_y, + const int width, const int height, const float blur, + const uint8_t r = 0xFF, const uint8_t g = 0xFF, const uint8_t b = 0xFF) { + const int sprite_stride = dst_stride; + const int radius = util::min(width, height); + for (int y = 0; y < height; y++) { + const int img_y = (dst_y + y) * sprite_stride + dst_x; + for (int x = 0; x < height; x++) { + const float dist = util::length(float(x) / radius - 0.5f, float(y) / radius - 0.5f); + const float t = util::smoothstep(0.5f, 0.5f - blur, dist); + const uint8_t alpha = t * 255; - auto rect_it = size_index.find(name); - if (rect_it != size_index.end()) { - return rect_it->second; + uint32_t color = (uint32_t(r * t) << 0) | + (uint32_t(g * t) << 8) | + (uint32_t(b * t) << 16) | + (uint32_t(alpha) << 24); + dst[img_y + x] = color; + } } +} +Rect SpriteAtlas::allocateImage(size_t width, size_t height) { // We have to allocate a new area in the bin, and store an empty image in it. // Add a 1px border around every image. - const dimension pack_size = size + 2 * buffer; + Rect rect = bin.allocate(width + 2 * buffer, height + 2 * buffer); + if (rect.w == 0) { + return rect; + } + + rect.x += buffer; + rect.y += buffer; + rect.w -= 2 * buffer; + rect.h -= 2 * buffer; - Rect rect = bin.allocate(pack_size, pack_size); + return rect; +} + +Rect SpriteAtlas::getIcon(const int size, const std::string &name) { + std::lock_guard lock(mtx); + + const std::string id = name + "-" + std::to_string(size); + + auto rect_it = images.find(id); + if (rect_it != images.end()) { + return rect_it->second; + } + + Rect rect = allocateImage(size, size); if (rect.w == 0) { - fprintf(stderr, "sprite atlas bitmap overflow"); + fprintf(stderr, "sprite atlas bitmap overflow\n"); return rect; } - size_index.emplace(name, rect); + images.emplace(id, rect); allocate(); - // Draw an antialiased circle. - const int img_size = size * pixelRatio; - const int img_offset_x = (rect.x + buffer) * pixelRatio; - const int img_offset_y = (rect.y + buffer) * pixelRatio; + draw_circle( + reinterpret_cast(data), + width * pixelRatio, + rect.x * pixelRatio, + rect.y * pixelRatio, + size * pixelRatio, + size * pixelRatio, + 1.0f / size / pixelRatio + ); + + uninitialized.emplace(id); - uint32_t *sprite_img = reinterpret_cast(data); - const float blur = 1.0f / size / pixelRatio; + dirty = true; - const uint8_t r = 0x7F; - const uint8_t g = 0x7F; - const uint8_t b = 0x7F; + return rect; +} - const int sprite_stride = width * pixelRatio; - for (int y = 0; y < img_size; y++) { - const int img_y = (img_offset_y + y) * sprite_stride + img_offset_x; - for (int x = 0; x < img_size; x++) { +Rect SpriteAtlas::getImage(const std::string &name, const Sprite &sprite) { + std::lock_guard lock(mtx); - const float dist = util::length(float(x) / img_size - 0.5f, float(y) / img_size - 0.5f); - const float t = util::smoothstep(0.5f, 0.5f - blur, dist); - const uint8_t alpha = t * 255; + auto rect_it = images.find(name); + if (rect_it != images.end()) { + return rect_it->second; + } - uint32_t color = (uint32_t(r * t) << 0) | - (uint32_t(g * t) << 8) | - (uint32_t(b * t) << 16) | - (uint32_t(alpha) << 24); - sprite_img[img_y + x] = color; - } + const SpritePosition &pos = sprite.getSpritePosition(name); + if (!pos.width || !pos.height) { + return Rect { 0, 0, 0, 0 }; } - uninitialized.emplace(size, name); + Rect rect = allocateImage(pos.width / pos.pixelRatio, pos.height / pos.pixelRatio); + if (rect.w == 0) { + fprintf(stderr, "sprite atlas bitmap overflow\n"); + return rect; + } + + images.emplace(name, rect); + + copy(rect, pos, sprite); dirty = true; @@ -121,68 +174,39 @@ void SpriteAtlas::allocate() { } } -Rect SpriteAtlas::setIcon(const int size, const std::string &name, const std::string &icon) { - Rect rect = getIcon(size, name); - - // Copy the bitmap - const int img_size = size * pixelRatio; - const int img_offset_x = (rect.x + buffer) * pixelRatio; - const int img_offset_y = (rect.y + buffer) * pixelRatio; - - if (std::pow(size * pixelRatio, 2) * 4 /* rgba */ != icon.size()) { fprintf(stderr, "mismatched icon buffer size!"); } - - const uint32_t *icon_img = reinterpret_cast(icon.data()); - uint32_t *sprite_img = reinterpret_cast(data); - - const int sprite_stride = width * pixelRatio; - const int icon_stride = size * pixelRatio; - for (size_t y = 0; y < img_size; y++) { - const int img_y = (img_offset_y + y) * sprite_stride + img_offset_x; - const int icon_y = y * icon_stride; - for (size_t x = 0; x < img_size; x++) { - sprite_img[img_y + x] = icon_img[icon_y + x]; - } - } - - dirty = true; - - return rect; +void SpriteAtlas::copy(const Rect &dst, const SpritePosition &src, const Sprite &sprite) { + if (!sprite.raster) return; + const uint32_t *src_img = reinterpret_cast(sprite.raster->getData()); + allocate(); + uint32_t *dst_img = reinterpret_cast(data); + + copy_bitmap( + /* source buffer */ src_img, + /* source stride */ sprite.raster->getWidth(), + /* source x */ src.x, + /* source y */ src.y, + /* dest buffer */ dst_img, + /* dest stride */ width * pixelRatio, + /* dest x */ dst.x * pixelRatio, + /* dest y */ dst.y * pixelRatio, + /* icon dimension */ src.width, + /* icon dimension */ src.height + ); } + void SpriteAtlas::update(const Sprite &sprite) { if (!sprite.isLoaded()) return; SpriteAtlas &atlas = *this; - std::erase_if(uninitialized, [&sprite, &atlas](const std::pair &pair) { - const int &size = pair.first; - const std::string &name = pair.second; - const SpritePosition &src = sprite.getSpritePosition(name + "-" + std::to_string(size)); - if (src.width == size && src.height == size && src.pixelRatio == atlas.pixelRatio) { - const uint32_t *src_img = reinterpret_cast(sprite.raster.getData()); - - uint32_t *dst_img = reinterpret_cast(atlas.data); - Rect dst = atlas.getIcon(size, name); - dst.x = (dst.x + buffer) * atlas.pixelRatio; - dst.y = (dst.y + buffer) * atlas.pixelRatio; - dst.w = (dst.w - 2 * buffer) * atlas.pixelRatio; - dst.h = (dst.h - 2 * buffer) * atlas.pixelRatio; - - - const int src_image_stride = sprite.raster.width; - const int dst_image_stride = atlas.width * atlas.pixelRatio; - const int src_stride = src.width * src.pixelRatio; - - for (int y = 0; y < dst.h; y++) { - const int src_pos = (src.y * src.pixelRatio + y) * src_image_stride + src.x * src.pixelRatio; - const int dst_pos = (dst.y + y) * dst_image_stride + dst.x; - - // TODO: this is crashing - // memcpy(dst_img + dst_pos, src_img + src_pos, src_stride * sizeof(uint32_t)); - } - - + std::erase_if(uninitialized, [&sprite, &atlas](const std::string &name) { + Rect dst = atlas.getImage(name, sprite); + const SpritePosition &src = sprite.getSpritePosition(name); + if (src.width == dst.w * atlas.pixelRatio && src.height == dst.h * atlas.pixelRatio && src.pixelRatio == atlas.pixelRatio) { + // atlas.copy(dst, src, sprite); return true; } else { + fprintf(stderr, "sprite icon dimension mismatch\n"); return false; } }); diff --git a/src/renderer/painter_fill.cpp b/src/renderer/painter_fill.cpp index 6193e306861..289e85dfb77 100644 --- a/src/renderer/painter_fill.cpp +++ b/src/renderer/painter_fill.cpp @@ -2,6 +2,7 @@ #include #include #include +#include using namespace llmr; @@ -73,16 +74,17 @@ void Painter::renderFill(FillBucket& bucket, const std::string& layer_name, cons if ((fill_color[3] >= 1.0f) == (pass == Opaque)) { auto &sprite = map.getStyle().sprite; - if (properties.image.size() && sprite && sprite->isLoaded()) { - // Draw texture fill - ImagePosition imagePos = sprite->getPosition(properties.image, true); + if (properties.image.size() && sprite) { + auto &spriteAtlas = map.getSpriteAtlas(); + Rect imagePos = spriteAtlas.getImage(properties.image, *sprite); + float factor = 8.0 / std::pow(2, map.getState().getIntegerZoom() - id.z); float mix = std::fmod(map.getState().getZoom(), 1.0); std::array imageSize = {{ - imagePos.size.x * factor, - imagePos.size.y *factor + imagePos.w * factor, + imagePos.h * factor } }; @@ -96,11 +98,17 @@ void Painter::renderFill(FillBucket& bucket, const std::string& layer_name, cons patternShader->setMatrix(vtxMatrix); patternShader->setOffset(offset); patternShader->setPatternSize(imageSize); - patternShader->setPatternTopLeft({{ imagePos.tl.x, imagePos.tl.y }}); - patternShader->setPatternBottomRight({{ imagePos.br.x, imagePos.br.y }}); + patternShader->setPatternTopLeft({{ + float(imagePos.x) / spriteAtlas.getWidth(), + float(imagePos.y) / spriteAtlas.getHeight(), + }}); + patternShader->setPatternBottomRight({{ + float(imagePos.x + imagePos.w) / spriteAtlas.getWidth(), + float(imagePos.y + imagePos.h) / spriteAtlas.getHeight(), + }}); patternShader->setColor(fill_color); patternShader->setMix(mix); - sprite->raster.bind(true); + spriteAtlas.bind(true); // Draw the actual triangles into the color & stencil buffer. glDepthRange(strata + strata_epsilon, 1.0f); diff --git a/src/style/sprite.cpp b/src/style/sprite.cpp index fb3a959f98a..bb24c5fec70 100644 --- a/src/style/sprite.cpp +++ b/src/style/sprite.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include @@ -18,14 +19,9 @@ SpritePosition::SpritePosition(uint16_t x, uint16_t y, uint16_t width, uint16_t pixelRatio(pixelRatio) { } -ImagePosition::ImagePosition(const vec2& size, vec2 tl, vec2 br) - : size(size), - tl(tl), - br(br) {} - Sprite::Sprite(Map &map, float pixelRatio) : pixelRatio(pixelRatio), - raster(map.getTexturepool()), + raster(), map(map), loaded(false) { } @@ -55,7 +51,7 @@ void Sprite::load(const std::string& base_url) { } void Sprite::complete(std::shared_ptr &sprite) { - if (sprite->raster.isLoaded() && sprite->pos.size()) { + if (sprite->raster && sprite->pos.size()) { sprite->loaded = true; sprite->map.update(); fprintf(stderr, "sprite loaded\n"); @@ -75,7 +71,7 @@ void Sprite::asyncParseJSON() { } void Sprite::parseImage(std::shared_ptr &sprite) { - sprite->raster.load(sprite->image); + sprite->raster = std::make_unique(sprite->image); sprite->image.clear(); } @@ -107,34 +103,6 @@ void Sprite::parseJSON(std::shared_ptr &sprite) { } } -ImagePosition Sprite::getPosition(const std::string& name, bool repeating) const { - if (!isLoaded()) return ImagePosition {}; - - // `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 - // positions for repeating images are adjusted to exclude the edge - int8_t offset = repeating ? 1 : 0; - - auto it = pos.find(name); - if (it == pos.end()) return ImagePosition {}; - - const SpritePosition& pos = it->second; - return ImagePosition { - { - pos.width, - pos.height - }, - { - (float)(pos.x + offset) / raster.width, - (float)(pos.y + offset) / raster.height - }, - { - (float)(pos.x + pos.width - 2 * offset) / raster.width, - (float)(pos.y + pos.height - 2 * offset) / raster.height - } - }; -} - const SpritePosition &Sprite::getSpritePosition(const std::string& name) const { if (!isLoaded()) return empty; auto it = pos.find(name); diff --git a/src/util/image.cpp b/src/util/image.cpp index 3a29a95a28c..156828183ff 100644 --- a/src/util/image.cpp +++ b/src/util/image.cpp @@ -1,6 +1,8 @@ #include #include +#include + std::string llmr::util::compress_png(int width, int height, void *rgba, bool flip) { png_voidp error_ptr = 0; @@ -48,3 +50,127 @@ std::string llmr::util::compress_png(int width, int height, void *rgba, bool fli return result; } + + +using namespace llmr::util; + + +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_const_charp error_msg) { + throw std::runtime_error(error_msg); +} + +void warningHandler(png_structp, png_const_charp error_msg) { + fprintf(stderr, "PNG: %s\n", error_msg); +} + +Image::Image(const std::string &data) { + 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); + + struct ptrs { + ptrs(size_t count) : rows(new png_bytep[count]) {} + ~ptrs() { delete[] rows; } + png_bytep *rows = nullptr; + } pointers(height); + for (unsigned i = 0; i < height; ++i) { + pointers.rows[i] = (png_bytep)(surface + (i * rowbytes)); + } + + // Read image data + png_read_image(png, pointers.rows); + + 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; + } +} + +Image::~Image() { + if (img) { + free(img); + img = nullptr; + } +} diff --git a/src/util/raster.cpp b/src/util/raster.cpp index 90268c09b0b..4f26f8b5586 100644 --- a/src/util/raster.cpp +++ b/src/util/raster.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include @@ -18,9 +19,7 @@ Raster::Raster(Texturepool &texturepool) {} Raster::~Raster() { - if (img && !textured) { - free(img); - } else if (textured) { + if (textured) { texturepool.removeTextureID(texture); } } @@ -31,130 +30,17 @@ bool Raster::isLoaded() const { } bool Raster::load(const std::string &data) { - loadImage(data); + img = std::make_unique(data); + width = img->getWidth(); + height = img->getHeight(); std::lock_guard lock(mtx); - if (img) { + if (img->getData()) { loaded = true; } return loaded; } -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_const_charp error_msg) { - throw std::runtime_error(error_msg); -} - -void warningHandler(png_structp, 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); - - struct ptrs { - ptrs(size_t count) : rows(new png_bytep[count]) {} - ~ptrs() { delete[] rows; } - png_bytep *rows = nullptr; - } pointers(height); - for (unsigned i = 0; i < height; ++i) { - pointers.rows[i] = (png_bytep)(surface + (i * rowbytes)); - } - - // Read image data - png_read_image(png, pointers.rows); - - 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::bind(bool linear) { if (!width || !height) { @@ -167,9 +53,8 @@ void Raster::bind(bool linear) { 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; + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, img->getData()); + img.reset(); textured = true; } else if (textured) { glBindTexture(GL_TEXTURE_2D, texture); From 92706445d9054fbe7ad66581c8bd56f4e0de4a69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Mon, 19 May 2014 14:21:15 +0200 Subject: [PATCH 6/7] init glfw for debug images + actually copy images over --- common/glfw_view.cpp | 4 ++++ src/geometry/sprite_atlas.cpp | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/common/glfw_view.cpp b/common/glfw_view.cpp index 3ef7bc36a9b..e03b5ed2fed 100644 --- a/common/glfw_view.cpp +++ b/common/glfw_view.cpp @@ -204,6 +204,8 @@ namespace platform { double elapsed() { return glfwGetTime(); } void show_debug_image(std::string name, const char *data, size_t width, size_t height) { + glfwInit(); + static GLFWwindow *debug_window = nullptr; if (!debug_window) { debug_window = glfwCreateWindow(width, height, name.c_str(), nullptr, nullptr); @@ -233,6 +235,8 @@ void show_debug_image(std::string name, const char *data, size_t width, size_t h void show_color_debug_image(std::string name, const char *data, size_t logical_width, size_t logical_height, size_t width, size_t height) { + glfwInit(); + static GLFWwindow *debug_window = nullptr; if (!debug_window) { debug_window = glfwCreateWindow(logical_width, logical_height, name.c_str(), nullptr, nullptr); diff --git a/src/geometry/sprite_atlas.cpp b/src/geometry/sprite_atlas.cpp index 4f05df62011..1c6afcd3063 100644 --- a/src/geometry/sprite_atlas.cpp +++ b/src/geometry/sprite_atlas.cpp @@ -203,7 +203,7 @@ void SpriteAtlas::update(const Sprite &sprite) { Rect dst = atlas.getImage(name, sprite); const SpritePosition &src = sprite.getSpritePosition(name); if (src.width == dst.w * atlas.pixelRatio && src.height == dst.h * atlas.pixelRatio && src.pixelRatio == atlas.pixelRatio) { - // atlas.copy(dst, src, sprite); + atlas.copy(dst, src, sprite); return true; } else { fprintf(stderr, "sprite icon dimension mismatch\n"); From ee82986717ed6684a382d0e262523479f794d769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Mon, 19 May 2014 14:21:57 +0200 Subject: [PATCH 7/7] disable debug image --- src/geometry/sprite_atlas.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/geometry/sprite_atlas.cpp b/src/geometry/sprite_atlas.cpp index 1c6afcd3063..fbdf5b3484f 100644 --- a/src/geometry/sprite_atlas.cpp +++ b/src/geometry/sprite_atlas.cpp @@ -244,7 +244,7 @@ void SpriteAtlas::bind(bool linear) { data // const GLvoid * data ); - platform::show_color_debug_image("Sprite Atlas", data, width, height, width * pixelRatio, height * pixelRatio); + // platform::show_color_debug_image("Sprite Atlas", data, width, height, width * pixelRatio, height * pixelRatio); dirty = false; }