fixes #70: raster tile rendering as toggleable mode from vector

This commit is contained in:
Justin R. Miller
2014-03-18 16:57:17 -07:00
parent bdfa761c39
commit 00a3245ce8
24 changed files with 604 additions and 196 deletions

View File

@@ -115,8 +115,9 @@ This is automatically taken care of as a build phase if you are using the Xcode
## Desktop
- Press 'R' to reset the transform
- Press 'X' to reset the transform
- Press 'N' to reset north
- Press 'R' to toggle raster
- Press Tab to toggle debug information
- Press Esc to quit
@@ -130,3 +131,4 @@ This is automatically taken care of as a build phase if you are using the Xcode
- Long-press to reset north
- Two-finger long press to reset the transform
- Three-finger long press to toggle debug information
- Four-finger long press to toggle raster

View File

@@ -1,13 +1,13 @@
#ifndef LLMR_MAP_COVERAGE
#define LLMR_MAP_COVERAGE
#include <llmr/util/vec.hpp>
#include <llmr/map/tile.hpp>
#include <forward_list>
namespace llmr {
std::forward_list<Tile::ID> covering_tiles(int32_t zoom, const box& points);
std::forward_list<Tile::ID> covering_tiles(int32_t zoom, const box& points, const bool use_raster = false, const bool use_retina = false);
}

View File

@@ -7,6 +7,7 @@
#include <llmr/style/style.hpp>
#include <llmr/renderer/painter.hpp>
#include <llmr/util/noncopyable.hpp>
#include <llmr/util/texturepool.hpp>
#include <cstdint>
#include <string>
@@ -23,9 +24,9 @@ public:
/* setup */
void setup(float pixelRatio = 1);
void loadStyle(const uint8_t *const data, uint32_t bytes);
void loadSprite(const std::string& url);
void loadSettings();
void resize(uint32_t width, uint32_t height, uint32_t fb_width, uint32_t fb_height);
void toggleRaster();
/* callback */
bool render();
@@ -68,12 +69,12 @@ private:
TileData::State addTile(const Tile::ID& id);
TileData::State hasTile(const Tile::ID& id);
void update();
private:
Settings& settings;
Transform transform;
Texturepool texturepool;
Style style;
Painter painter;
@@ -82,6 +83,8 @@ private:
float pixel_ratio;
bool use_raster = false;
std::forward_list<Tile> tiles;
std::forward_list<std::weak_ptr<TileData>> tile_data;
};

View File

@@ -20,6 +20,7 @@
namespace llmr {
class Style;
class Raster;
class Bucket;
class LayerDescription;
class BucketDescription;
@@ -52,7 +53,7 @@ public:
};
public:
TileData(Tile::ID id, const Style& style);
TileData(Tile::ID id, const Style& style, const bool use_raster = false, const bool use_retina = false);
~TileData();
// Start loading the tile.
@@ -72,7 +73,10 @@ public:
public:
const Tile::ID id;
const bool use_raster;
const bool use_retina;
std::atomic<State> state;
std::shared_ptr<Raster> raster;
// Holds the actual geometries in this tile.
DebugFontBuffer debugFontBuffer;

View File

@@ -7,8 +7,11 @@
namespace llmr {
extern const char *kTileURL;
extern const char *kTileVectorURL;
extern const char *kTileRasterURL;
extern const char *kSpriteURL;
extern const int32_t kTileVectorMaxZoom;
extern const int32_t kTileRasterMaxZoom;
namespace platform {

View File

@@ -13,7 +13,7 @@
#include <llmr/shader/line_shader.hpp>
#include <llmr/shader/linejoin_shader.hpp>
#include <llmr/shader/point_shader.hpp>
#include <llmr/shader/raster_shader.hpp>
namespace llmr {
@@ -42,10 +42,11 @@ public:
void resize(int width, int height);
void prepareClippingMask();
void drawClippingMask(const mat4& matrix, uint8_t clip_id);
void drawClippingMask(const mat4& matrix, uint8_t clip_id, bool opaque = true);
void finishClippingMask();
private:
void setupShaders();
void renderRaster(const std::shared_ptr<TileData>& tile);
void renderLayers(const std::shared_ptr<TileData>& tile, const std::vector<LayerDescription>& layers);
void renderLayer(const std::shared_ptr<TileData>& tile_data, const LayerDescription& layer_desc);
void renderDebug(const std::shared_ptr<TileData>& tile);
@@ -80,6 +81,7 @@ private:
std::unique_ptr<LinejoinShader> linejoinShader;
std::unique_ptr<PatternShader> patternShader;
std::unique_ptr<PointShader> pointShader;
std::unique_ptr<RasterShader> rasterShader;
// Set up the stencil quad we're using to generate the stencil mask.
VertexBuffer tileStencilBuffer = {
@@ -96,6 +98,7 @@ private:
VertexArrayObject coveringPlainArray;
VertexArrayObject coveringPatternArray;
VertexArrayObject coveringRasterArray;
// Set up the tile boundary lines we're using to draw the tile outlines.
VertexBuffer tileBorderBuffer = {

View File

@@ -0,0 +1,28 @@
#ifndef LLMR_RENDERER_SHADER_RASTER
#define LLMR_RENDERER_SHADER_RASTER
#include <llmr/shader/shader.hpp>
namespace llmr {
class RasterShader : public Shader {
public:
RasterShader();
void bind(char *offset);
void setImage(int32_t image);
void setOpacity(float opacity);
private:
int32_t a_pos = -1;
int32_t image = -1;
int32_t u_image = -1;
float opacity = 0.0f;
float u_opacity = 0.0f;
};
}
#endif

View File

@@ -17,6 +17,7 @@ enum {
PATTERN_SHADER,
PLAIN_SHADER,
POINT_SHADER,
RASTER_SHADER,
SHADER_COUNT
};

View File

@@ -1,13 +1,12 @@
#ifndef LLMR_STYLE_SPRITE
#define LLMR_STYLE_SPRITE
#include <cstdint>
#include <map>
#include <string>
#include <mutex>
#include <memory>
#include <llmr/util/raster.hpp>
#include <llmr/util/vec.hpp>
namespace llmr {
@@ -34,27 +33,24 @@ public:
class Sprite : public std::enable_shared_from_this<Sprite> {
public:
~Sprite();
Sprite();
void load(const std::string& base_url, float pixelRatio = 1);
void bind(bool linear = false);
ImagePosition getPosition(const std::string& name, bool repeating = false);
operator bool() const;
public:
std::shared_ptr<Raster> raster;
private:
void parseJSON(const std::string& data);
void loadImage(const std::string& data);
private:
mutable std::mutex mtx;
bool loaded = false;
uint32_t filter = 0;
uint32_t texture = 0;
std::map<std::string, SpritePosition> pos;
uint32_t width = 0, height = 0;
char *img = nullptr;
};
}

View File

@@ -0,0 +1,72 @@
#ifndef LLMR_UTIL_RASTER
#define LLMR_UTIL_RASTER
#include <llmr/util/animation.hpp>
#include <llmr/util/texturepool.hpp>
#include <string>
#include <mutex>
namespace llmr {
class Raster : public std::enable_shared_from_this<Raster> {
public:
~Raster();
// load image data
void load(const std::string& data);
// set shared texture pool
void setTexturepool(Texturepool* texturepool);
// bind current texture
void bind(bool linear = false);
// loaded status
operator bool() const;
// animations
void beginFadeInAnimation();
bool needsAnimation() const;
void updateAnimations();
public:
// loaded image dimensions
uint32_t width = 0, height = 0;
// has been uploaded to texture
bool textured = false;
// the uploaded texture
uint32_t texture = 0;
// texture opacity
double opacity = 0;
private:
// load raw pixels
void loadImage(const std::string& data);
private:
mutable std::mutex mtx;
// raw pixels have been loaded
bool loaded = false;
// shared texture pool
Texturepool* texturepool = nullptr;
// min/mag filter
uint32_t filter = 0;
// the raw pixels
char *img = nullptr;
// fade in animation
std::shared_ptr<util::animation> fade_animation = nullptr;
};
}
#endif

View File

@@ -0,0 +1,25 @@
#ifndef LLMR_UTIL_TEXTUREPOOL
#define LLMR_UTIL_TEXTUREPOOL
#include <llmr/util/noncopyable.hpp>
#include <llmr/platform/gl.hpp>
#include <set>
#include <mutex>
namespace llmr {
class Texturepool : private util::noncopyable {
public:
GLuint getTextureID();
void removeTextureID(GLuint texture_id);
void clearTextureIDs();
private:
std::set<GLuint> texture_ids;
};
}
#endif

View File

@@ -121,6 +121,10 @@ class MBXMapView
threeFingerLongPress.numberOfTouchesRequired = 3;
[self.view addGestureRecognizer:threeFingerLongPress];
UILongPressGestureRecognizer *fourFingerLongPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleFourFingerLongPressGesture:)];
fourFingerLongPress.numberOfTouchesRequired = 4;
[self.view addGestureRecognizer:fourFingerLongPress];
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)
{
UILongPressGestureRecognizer *quickZoom = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleQuickZoomGesture:)];
@@ -360,6 +364,16 @@ class MBXMapView
[self updateRender];
}
- (void)handleFourFingerLongPressGesture:(UILongPressGestureRecognizer *)fourFingerLongPress
{
mapView->map.cancelAnimations();
if (fourFingerLongPress.state == UIGestureRecognizerStateBegan)
mapView->map.toggleRaster();
[self updateRender];
}
- (void)handleQuickZoomGesture:(UILongPressGestureRecognizer *)quickZoom
{
mapView->map.cancelAnimations();

View File

@@ -1,4 +1,7 @@
#include <llmr/map/coverage.hpp>
#include <llmr/util/constants.hpp>
#include <llmr/util/vec.hpp>
#include <functional>
#include <cmath>
@@ -64,15 +67,28 @@ void _scanTriangle(const llmr::vec2<double> a, const llmr::vec2<double> b, const
if (bc.dy) _scanSpans(ca, bc, ymin, ymax, scanLine);
}
std::forward_list<llmr::Tile::ID> llmr::covering_tiles(int32_t zoom, const box& points) {
std::forward_list<llmr::Tile::ID> llmr::covering_tiles(int32_t zoom, const box& points, const bool use_raster, const bool use_retina) {
int32_t dim = pow(2, zoom);
std::forward_list<llmr::Tile::ID> tiles;
auto scanLine = [&tiles, zoom](int32_t x0, int32_t x1, int32_t y, int32_t ymax) {
auto scanLine = [&tiles, zoom, use_raster, use_retina](int32_t x0, int32_t x1, int32_t y, int32_t ymax) {
int32_t x;
if (y >= 0 && y <= ymax) {
for (x = x0; x < x1; x++) {
tiles.emplace_front(zoom, x, y);
if (use_raster) {
uint32_t search_zoom = zoom;
search_zoom += (uint32_t)((llmr::util::tileSize / 256.0f) - 1.0f);
if (use_retina) {
search_zoom += 1;
}
Tile::ID id = Tile::ID(zoom, x, y);
auto ids = id.children(search_zoom);
for (const Tile::ID& child_id : ids) {
tiles.emplace_front(child_id.z, child_id.x, child_id.y);
}
} else {
tiles.emplace_front(zoom, x, y);
}
}
}
};

View File

@@ -4,6 +4,7 @@
#include <llmr/style/resources.hpp>
#include <llmr/style/sprite.hpp>
#include <llmr/map/coverage.hpp>
#include <llmr/util/animation.hpp>
#include <algorithm>
@@ -14,8 +15,9 @@ Map::Map(Settings& settings)
transform(),
style(),
painter(transform, settings, style),
texturepool(),
min_zoom(0),
max_zoom(14) {
max_zoom((use_raster ? kTileRasterMaxZoom : kTileVectorMaxZoom)) {
}
Map::~Map() {
@@ -23,6 +25,7 @@ Map::~Map() {
}
void Map::setup(float pixelRatio) {
painter.setup();
pixel_ratio = pixelRatio;
@@ -33,10 +36,6 @@ void Map::setup(float pixelRatio) {
style.loadJSON(resources::style, resources::style_size);
}
void Map::loadSprite(const std::string& url) {
}
void Map::loadStyle(const uint8_t *const data, uint32_t bytes) {
style.loadJSON(data, bytes);
update();
@@ -60,6 +59,13 @@ void Map::resize(uint32_t width, uint32_t height, uint32_t fb_width, uint32_t fb
update();
}
void Map::toggleRaster() {
use_raster = ! use_raster;
max_zoom = (use_raster ? kTileRasterMaxZoom : kTileVectorMaxZoom);
tiles.clear();
update();
}
void Map::moveBy(double dx, double dy, double duration) {
transform.moveBy(dx, dy, duration);
update();
@@ -278,7 +284,7 @@ TileData::State Map::addTile(const Tile::ID& id) {
if (!new_tile.data) {
// If we don't find working tile data, we're just going to load it.
new_tile.data = std::make_shared<TileData>(normalized_id, style);
new_tile.data = std::make_shared<TileData>(normalized_id, style, use_raster, (pixel_ratio > 1.0));
new_tile.data->request();
tile_data.push_front(new_tile.data);
}
@@ -352,12 +358,14 @@ bool Map::updateTiles() {
int32_t min_covering_zoom = zoom - 10;
if (min_covering_zoom < min_zoom) min_covering_zoom = min_zoom;
int32_t max_dim = pow(2, zoom);
// Map four viewport corners to pixel coordinates
box box = transform.mapCornersToBox(zoom);
// Performs a scanline algorithm search that covers the rectangle of the box
// and sorts them by proximity to the center.
std::forward_list<Tile::ID> required = llmr::covering_tiles(zoom, box);
std::forward_list<Tile::ID> required = llmr::covering_tiles(zoom, box, use_raster, (pixel_ratio > 1.0));
// Retain is a list of tiles that we shouldn't delete, even if they are not
// the most ideal tile for the current viewport. This may include tiles like
@@ -369,6 +377,9 @@ bool Map::updateTiles() {
const TileData::State state = addTile(id);
if (state != TileData::State::parsed) {
if (use_raster && (transform.rotating || transform.scaling || transform.panning))
break;
// The tile we require is not yet loaded. Try to find a parent or
// child tile that we already have.
@@ -431,6 +442,10 @@ bool Map::render() {
transform.updateAnimations();
}
if (*style.sprite->raster && !style.sprite->raster->textured) {
style.sprite->raster->setTexturepool(&texturepool);
}
bool changed = updateTiles();
painter.clear();
@@ -447,13 +462,20 @@ bool Map::render() {
transform.matrixFor(tile.matrix, tile.id);
matrix::multiply(tile.matrix, painter.projMatrix, tile.matrix);
tile.clip_id = i++;
painter.drawClippingMask(tile.matrix, tile.clip_id);
painter.drawClippingMask(tile.matrix, tile.clip_id, !tile.data->use_raster);
}
}
painter.finishClippingMask();
for (const Tile& tile : tiles) {
if (tile.data && tile.data->state == TileData::State::parsed) {
if (tile.data->use_raster && *tile.data->raster && !tile.data->raster->textured) {
tile.data->raster->setTexturepool(&texturepool);
tile.data->raster->beginFadeInAnimation();
}
if (tile.data->use_raster && tile.data->raster->needsAnimation()) {
tile.data->raster->updateAnimations();
}
painter.render(tile);
}
}

View File

@@ -10,15 +10,19 @@
#include <llmr/renderer/line_bucket.hpp>
#include <llmr/renderer/point_bucket.hpp>
#include <llmr/util/pbf.hpp>
#include <llmr/util/raster.hpp>
#include <llmr/util/string.hpp>
#include <chrono>
using namespace llmr;
TileData::TileData(Tile::ID id, const Style& style)
TileData::TileData(Tile::ID id, const Style& style, const bool use_raster, const bool use_retina)
: id(id),
state(State::initial),
use_raster(use_raster),
use_retina(use_retina),
raster(),
fillVertexBuffer(std::make_shared<FillVertexBuffer>()),
lineVertexBuffer(std::make_shared<LineVertexBuffer>()),
pointVertexBuffer(std::make_shared<PointVertexBuffer>()),
@@ -44,9 +48,13 @@ const std::string TileData::toString() const {
void TileData::request() {
state = State::loading;
// Create http request
std::string url = util::sprintf(kTileURL,
id.z, id.x, id.y);
std::string url;
if (use_raster) {
url = util::sprintf(kTileRasterURL, id.z, id.x, id.y, (use_retina ? "@2x" : ""));
} else {
url = util::sprintf(kTileVectorURL, id.z, id.x, id.y);
}
// Note: Somehow this feels slower than the change to request_http()
std::weak_ptr<TileData> weak_tile = shared_from_this();
@@ -80,6 +88,13 @@ bool TileData::parse() {
return false;
}
if (use_raster) {
raster = std::make_shared<Raster>();
raster->load(data);
state = State::parsed;
return true;
}
try {
double parse_time_start = platform::time();
VectorTile tile(pbf((const uint8_t *)data.data(), data.size()));

View File

@@ -1,4 +1,8 @@
#include <llmr/platform/platform.hpp>
const char *llmr::kTileURL = "http://a.gl-api-us-east-1.tilestream.net/v3/mapbox.mapbox-streets-v4/%d/%d/%d.gl.pbf";
const char *llmr::kTileVectorURL = "http://a.gl-api-us-east-1.tilestream.net/v3/mapbox.mapbox-streets-v4/%d/%d/%d.gl.pbf";
const char *llmr::kTileRasterURL = "https://a.tiles.mapbox.com/v3/justin.map-pgygbwdm/%d/%d/%d%s.png256";
const char *llmr::kSpriteURL = "https://dl.dropboxusercontent.com/u/575564/sprite";
const int32_t llmr::kTileVectorMaxZoom = 14;
const int32_t llmr::kTileRasterMaxZoom = 21;

View File

@@ -15,6 +15,7 @@
#include <llmr/platform/gl.hpp>
#include <llmr/style/style.hpp>
#include <llmr/style/sprite.hpp>
#include <llmr/util/raster.hpp>
using namespace llmr;
@@ -26,7 +27,6 @@ Painter::Painter(Transform& transform, Settings& settings, Style& style)
style(style) {
}
void Painter::setup() {
setupShaders();
@@ -36,6 +36,7 @@ void Painter::setup() {
assert(lineShader);
assert(linejoinShader);
assert(patternShader);
assert(rasterShader);
// Blending
@@ -62,6 +63,7 @@ void Painter::setupShaders() {
linejoinShader = std::make_unique<LinejoinShader>();
patternShader = std::make_unique<PatternShader>();
pointShader = std::make_unique<PointShader>();
rasterShader = std::make_unique<RasterShader>();
}
void Painter::resize(int width, int height) {
@@ -113,9 +115,11 @@ void Painter::prepareClippingMask() {
coveringPlainArray.bind(*plainShader, tileStencilBuffer, BUFFER_OFFSET(0));
}
void Painter::drawClippingMask(const mat4& matrix, uint8_t clip_id) {
void Painter::drawClippingMask(const mat4& matrix, uint8_t clip_id, bool opaque) {
plainShader->setMatrix(matrix);
plainShader->setColor(style.computed.background.color);
if (opaque) {
plainShader->setColor(style.computed.background.color);
}
glStencilFunc(GL_ALWAYS, clip_id, 0xFF);
glDrawArrays(GL_TRIANGLES, 0, (GLsizei)tileStencilBuffer.index());
@@ -148,13 +152,29 @@ void Painter::render(const Tile& tile) {
matrix = tile.matrix;
glStencilFunc(GL_EQUAL, tile.clip_id, 0xFF);
renderLayers(tile.data, style.layers);
if (tile.data->use_raster) {
renderRaster(tile.data);
} else {
renderLayers(tile.data, style.layers);
}
if (settings.debug) {
renderDebug(tile.data);
}
}
void Painter::renderRaster(const std::shared_ptr<TileData>& tile_data) {
useProgram(rasterShader->program);
rasterShader->setMatrix(matrix);
rasterShader->setImage(0);
rasterShader->setOpacity(tile_data->raster->opacity);
tile_data->raster->bind(true);
coveringRasterArray.bind(*rasterShader, tileStencilBuffer, BUFFER_OFFSET(0));
glDepthRange(strata, 1.0f);
glDrawArrays(GL_TRIANGLES, 0, (GLsizei)tileStencilBuffer.index());
}
void Painter::renderLayers(const std::shared_ptr<TileData>& tile_data, const std::vector<LayerDescription>& layers) {
float strata_thickness = 1.0f / (layers.size() + 1);
@@ -289,7 +309,7 @@ void Painter::renderFill(FillBucket& bucket, const std::string& layer_name, cons
patternShader->setPatternBottomRight({{ imagePos.br.x, imagePos.br.y }});
patternShader->setColor(fill_color);
patternShader->setMix(mix);
style.sprite->bind(true);
style.sprite->raster->bind(true);
// Draw the actual triangles into the color & stencil buffer.
glDepthRange(strata + strata_epsilon, 1.0f);
@@ -446,7 +466,9 @@ void Painter::renderPoint(PointBucket& bucket, const std::string& layer_name, co
#endif
pointShader->setPointTopLeft({{ imagePos.tl.x, imagePos.tl.y }});
pointShader->setPointBottomRight({{ imagePos.br.x, imagePos.br.y }});
style.sprite->bind(transform.rotating || transform.scaling || transform.panning);
if (*style.sprite->raster) {
style.sprite->raster->bind(transform.rotating || transform.scaling || transform.panning);
}
glDepthRange(strata, 1.0f);
bucket.drawPoints(*pointShader);
}

View File

@@ -0,0 +1,8 @@
uniform sampler2D u_image;
uniform float u_opacity;
varying vec2 v_pos;
void main() {
gl_FragColor = texture2D(u_image, v_pos) * u_opacity;
}

View File

@@ -0,0 +1,10 @@
uniform mat4 u_matrix;
attribute vec2 a_pos;
varying vec2 v_pos;
void main() {
gl_Position = u_matrix * vec4(a_pos, 0, 1);
v_pos = a_pos / 4096.0;
}

View File

@@ -0,0 +1,47 @@
#include <llmr/shader/raster_shader.hpp>
#include <llmr/shader/shaders.hpp>
#include <llmr/platform/gl.hpp>
#include <cstdio>
using namespace llmr;
RasterShader::RasterShader()
: Shader(
shaders[RASTER_SHADER].vertex,
shaders[RASTER_SHADER].fragment
) {
if (!valid) {
fprintf(stderr, "invalid raster shader\n");
return;
}
a_pos = glGetAttribLocation(program, "a_pos");
u_matrix = glGetUniformLocation(program, "u_matrix");
u_opacity = glGetUniformLocation(program, "u_opacity");
// fprintf(stderr, "RasterShader:\n");
// fprintf(stderr, " - u_matrix: %d\n", u_matrix);
// fprintf(stderr, " - u_image: %d\n", u_image);
// fprintf(stderr, " - u_opacity: %f\n", u_opacity);
}
void RasterShader::bind(char *offset) {
glEnableVertexAttribArray(a_pos);
glVertexAttribPointer(a_pos, 2, GL_SHORT, false, 0, offset);
}
void RasterShader::setImage(int32_t new_image) {
if (image != new_image) {
glUniform1i(u_image, new_image);
image = new_image;
}
}
void RasterShader::setOpacity(float new_opacity) {
if (opacity != new_opacity) {
glUniform1f(u_opacity, new_opacity);
opacity = new_opacity;
}
}

View File

@@ -27,5 +27,9 @@ const shader_source llmr::shaders[SHADER_COUNT] = {
{
"attribute vec2 a_pos;\n\nuniform mat4 u_matrix;\nuniform float u_size;\n\nvoid main() {\n gl_Position = u_matrix * vec4(a_pos, 0, 1);\n gl_PointSize = u_size;\n}\n",
"#define root2 1.4142135623730951\n\nuniform sampler2D u_image;\nuniform vec2 u_tl;\nuniform vec2 u_br;\nuniform vec4 u_color;\n\nuniform vec2 pos;\nuniform float inbounds;\nuniform vec4 color;\n\nvoid main() {\n vec2 pos = (gl_PointCoord * 2.0 - 1.0) * root2 / 2.0 + 0.5;\n\n float inbounds = step(0.0, pos.x) * step(0.0, pos.y) *\n (1.0 - step(1.0, pos.x)) * (1.0 - step(1.0, pos.y));\n\n vec4 color = texture2D(u_image, mix(u_tl, u_br, pos)) * inbounds;\n\n gl_FragColor = color * u_color;\n}\n",
},
{
"uniform mat4 u_matrix;\n\nattribute vec2 a_pos;\n\nvarying vec2 v_pos;\n\nvoid main() {\n gl_Position = u_matrix * vec4(a_pos, 0, 1);\n v_pos = a_pos / 4096.0;\n}\n",
"uniform sampler2D u_image;\nuniform float u_opacity;\n\nvarying vec2 v_pos;\n\nvoid main() {\n gl_FragColor = texture2D(u_image, v_pos) * u_opacity;\n}\n",
}
};

View File

@@ -1,10 +1,8 @@
#include <llmr/style/sprite.hpp>
#include <llmr/util/raster.hpp>
#include <string>
#include <llmr/platform/platform.hpp>
#include <llmr/platform/gl.hpp>
#include <png.h>
#include <rapidjson/document.h>
@@ -22,16 +20,8 @@ ImagePosition::ImagePosition(const vec2<uint16_t>& size, vec2<float> tl, vec2<fl
tl(tl),
br(br) {}
Sprite::~Sprite() {
if (img) {
free(img);
}
}
Sprite::operator bool() const {
std::lock_guard<std::mutex> lock(mtx);
return loaded;
Sprite::Sprite() {
raster = std::make_shared<Raster>();
}
void Sprite::load(const std::string& base_url, float pixelRatio) {
@@ -39,7 +29,7 @@ void Sprite::load(const std::string& base_url, float pixelRatio) {
auto complete = [sprite]() {
std::lock_guard<std::mutex> lock(sprite->mtx);
if (sprite->img && sprite->pos.size()) {
if (*sprite->raster && sprite->pos.size()) {
sprite->loaded = true;
platform::restart();
fprintf(stderr, "sprite loaded\n");
@@ -58,13 +48,17 @@ void Sprite::load(const std::string& base_url, float pixelRatio) {
platform::request_http(base_url + suffix + ".png", [sprite](const platform::Response & res) {
if (res.code == 200) {
sprite->loadImage(res.body);
sprite->raster->load(res.body);
} else {
fprintf(stderr, "failed to load sprite image\n");
}
}, complete);
}
Sprite::operator bool() const {
std::lock_guard<std::mutex> lock(mtx);
return loaded;
}
void Sprite::parseJSON(const std::string& data) {
std::lock_guard<std::mutex> lock(mtx);
@@ -96,118 +90,8 @@ void Sprite::parseJSON(const std::string& data) {
}
}
struct Buffer {
Buffer(const std::string& data)
: data(data.data()), length(data.size()) {}
const char *const data = 0;
const size_t length = 0;
size_t pos = 0;
};
void readCallback(png_structp png, png_bytep data, png_size_t length) {
Buffer *reader = static_cast<Buffer *>(png_get_io_ptr(png));
// Read `length` bytes into `data`.
if (reader->pos + length > reader->length) {
png_error(png, "Read Error");
} else {
memcpy(data, reader->data + reader->pos, length);
reader->pos += length;
}
}
void errorHandler(png_structp png, png_const_charp error_msg) {
throw std::runtime_error(error_msg);
}
void warningHandler(png_structp png, png_const_charp error_msg) {
fprintf(stderr, "PNG: %s\n", error_msg);
}
void Sprite::loadImage(const std::string& data) {
std::lock_guard<std::mutex> lock(mtx);
Buffer buffer(data);
if (buffer.length < 8 || !png_check_sig((const png_bytep)buffer.data, 8)) {
fprintf(stderr, "image is not a valid PNG image\n");
return;
}
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, errorHandler, warningHandler);
assert(png);
png_infop info = png_create_info_struct(png);
assert(info);
int depth, color, interlace;
try {
png_set_read_fn(png, (png_voidp)&buffer, readCallback);
png_read_info(png, info);
png_get_IHDR(png, info, (png_uint_32*)&width, (png_uint_32*)&height, &depth, &color, &interlace, nullptr, nullptr);
bool alpha = (color & PNG_COLOR_MASK_ALPHA) || png_get_valid(png, info, PNG_INFO_tRNS);
// From http://trac.mapnik.org/browser/trunk/src/png_reader.cpp
if (color == PNG_COLOR_TYPE_PALETTE)
png_set_expand(png);
if (color == PNG_COLOR_TYPE_GRAY)
png_set_expand(png);
if (png_get_valid(png, info, PNG_INFO_tRNS))
png_set_expand(png);
if (depth == 16)
png_set_strip_16(png);
if (depth < 8)
png_set_packing(png);
if (color == PNG_COLOR_TYPE_GRAY ||
color == PNG_COLOR_TYPE_GRAY_ALPHA)
png_set_gray_to_rgb(png);
if (interlace == PNG_INTERLACE_ADAM7)
png_set_interlace_handling(png);
// Always add an alpha channel.
if (!alpha) {
png_set_add_alpha(png, 0xFF, PNG_FILLER_AFTER);
}
double gamma;
if (png_get_gAMA(png, info, &gamma))
png_set_gamma(png, 2.2, gamma);
png_read_update_info(png, info);
png_size_t rowbytes = png_get_rowbytes(png, info);
assert(width * 4 == rowbytes);
img = (char *)malloc(width * height * 4);
char *surface = img;
assert(surface);
png_bytep row_pointers[height];
for (unsigned i = 0; i < height; ++i) {
row_pointers[i] = (png_bytep)(surface + (i * rowbytes));
}
// Read image data
png_read_image(png, row_pointers);
png_read_end(png, nullptr);
} catch (std::exception& e) {
fprintf(stderr, "loading PNG failed: %s\n", e.what());
png_destroy_read_struct(&png, &info, nullptr);
if (img) {
free(img);
img = nullptr;
}
width = 0;
height = 0;
}
}
ImagePosition Sprite::getPosition(const std::string& name, bool repeating) {
if (!*this) return {};
if (!*this->raster) return {};
// `repeating` indicates that the image will be used in a repeating pattern
// repeating pattern images are assumed to have a 1px padding that mirrors the opposite edge
@@ -224,38 +108,12 @@ ImagePosition Sprite::getPosition(const std::string& name, bool repeating) {
pos.height
},
{
(float)(pos.x + offset) / width,
(float)(pos.y + offset) / height
(float)(pos.x + offset) / raster->width,
(float)(pos.y + offset) / raster->height
},
{
(float)(pos.x + pos.width - 2 * offset) / width,
(float)(pos.y + pos.height - 2 * offset) / height
(float)(pos.x + pos.width - 2 * offset) / raster->width,
(float)(pos.y + pos.height - 2 * offset) / raster->height
}
};
}
void Sprite::bind(bool linear) {
if (!width || !height) {
// fprintf(stderr, "trying to bind texture without dimension\n");
return;
}
if (!texture) {
glGenTextures(1, &texture);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, img);
} else {
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
}
GLuint filter = linear ? GL_LINEAR : GL_NEAREST;
if (filter != this->filter) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
this->filter = filter;
}
}

195
src/util/raster.cpp Normal file
View File

@@ -0,0 +1,195 @@
#include <llmr/util/raster.hpp>
#include <memory>
#include <cassert>
#include <llmr/platform/gl.hpp>
#include <png.h>
using namespace llmr;
Raster::~Raster() {
if (img && !textured) {
free(img);
} else if (textured) {
texturepool->removeTextureID(texture);
}
}
Raster::operator bool() const {
std::lock_guard<std::mutex> lock(mtx);
return loaded;
}
void Raster::load(const std::string& data) {
std::shared_ptr<Raster> raster = shared_from_this();
raster->loadImage(data);
std::lock_guard<std::mutex> lock(raster->mtx);
if (raster->img) {
raster->loaded = true;
}
}
struct Buffer {
Buffer(const std::string& data)
: data(data.data()), length(data.size()) {}
const char *const data = 0;
const size_t length = 0;
size_t pos = 0;
};
void readCallback(png_structp png, png_bytep data, png_size_t length) {
Buffer *reader = static_cast<Buffer *>(png_get_io_ptr(png));
// Read `length` bytes into `data`.
if (reader->pos + length > reader->length) {
png_error(png, "Read Error");
} else {
memcpy(data, reader->data + reader->pos, length);
reader->pos += length;
}
}
void errorHandler(png_structp png, png_const_charp error_msg) {
throw std::runtime_error(error_msg);
}
void warningHandler(png_structp png, png_const_charp error_msg) {
fprintf(stderr, "PNG: %s\n", error_msg);
}
void Raster::loadImage(const std::string& data) {
std::lock_guard<std::mutex> lock(mtx);
Buffer buffer(data);
if (buffer.length < 8 || !png_check_sig((const png_bytep)buffer.data, 8)) {
fprintf(stderr, "image is not a valid PNG image\n");
return;
}
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, errorHandler, warningHandler);
assert(png);
png_infop info = png_create_info_struct(png);
assert(info);
int depth, color, interlace;
try {
png_set_read_fn(png, (png_voidp)&buffer, readCallback);
png_read_info(png, info);
png_get_IHDR(png, info, (png_uint_32*)&width, (png_uint_32*)&height, &depth, &color, &interlace, nullptr, nullptr);
bool alpha = (color & PNG_COLOR_MASK_ALPHA) || png_get_valid(png, info, PNG_INFO_tRNS);
// From http://trac.mapnik.org/browser/trunk/src/png_reader.cpp
if (color == PNG_COLOR_TYPE_PALETTE)
png_set_expand(png);
if (color == PNG_COLOR_TYPE_GRAY)
png_set_expand(png);
if (png_get_valid(png, info, PNG_INFO_tRNS))
png_set_expand(png);
if (depth == 16)
png_set_strip_16(png);
if (depth < 8)
png_set_packing(png);
if (color == PNG_COLOR_TYPE_GRAY ||
color == PNG_COLOR_TYPE_GRAY_ALPHA)
png_set_gray_to_rgb(png);
if (interlace == PNG_INTERLACE_ADAM7)
png_set_interlace_handling(png);
// Always add an alpha channel.
if (!alpha) {
png_set_add_alpha(png, 0xFF, PNG_FILLER_AFTER);
}
double gamma;
if (png_get_gAMA(png, info, &gamma))
png_set_gamma(png, 2.2, gamma);
png_read_update_info(png, info);
png_size_t rowbytes = png_get_rowbytes(png, info);
assert(width * 4 == rowbytes);
img = (char *)malloc(width * height * 4);
char *surface = img;
assert(surface);
png_bytep row_pointers[height];
for (unsigned i = 0; i < height; ++i) {
row_pointers[i] = (png_bytep)(surface + (i * rowbytes));
}
// Read image data
png_read_image(png, row_pointers);
png_read_end(png, nullptr);
png_destroy_read_struct(&png, &info, nullptr);
} catch (std::exception& e) {
fprintf(stderr, "loading PNG failed: %s\n", e.what());
png_destroy_read_struct(&png, &info, nullptr);
if (img) {
free(img);
img = nullptr;
}
width = 0;
height = 0;
}
}
void Raster::setTexturepool(Texturepool* new_texturepool) {
texturepool = new_texturepool;
}
void Raster::bind(bool linear) {
if (!width || !height) {
fprintf(stderr, "trying to bind texture without dimension\n");
return;
}
if (img && !textured) {
if (!texturepool) {
fprintf(stderr, "no available texture pool\n");
return;
}
texture = texturepool->getTextureID();
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, img);
free(img);
img = nullptr;
textured = true;
} else {
glBindTexture(GL_TEXTURE_2D, texture);
}
GLuint filter = linear ? GL_LINEAR : GL_NEAREST;
if (filter != this->filter) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
this->filter = filter;
}
}
void Raster::beginFadeInAnimation() {
fade_animation = std::make_shared<util::ease_animation>(opacity, 1.0, opacity, 0.25);
}
bool Raster::needsAnimation() const {
return fade_animation != nullptr;
}
void Raster::updateAnimations() {
if (fade_animation->update() == util::animation::complete) {
fade_animation = nullptr;
}
}

56
src/util/texturepool.cpp Normal file
View File

@@ -0,0 +1,56 @@
#include <llmr/util/texturepool.hpp>
#include <vector>
#define TextureMax 64
using namespace llmr;
GLuint Texturepool::getTextureID() {
if (texture_ids.empty())
{
GLuint new_texture_ids[TextureMax];
glGenTextures(TextureMax, new_texture_ids);
for (uint32_t id = 0; id < TextureMax; id++) {
texture_ids.insert(new_texture_ids[id]);
}
}
GLuint id = 0;
if (!texture_ids.empty()) {
std::set<GLuint>::iterator id_iterator = texture_ids.begin();
id = *id_iterator;
texture_ids.erase(id_iterator);
}
return id;
}
void Texturepool::removeTextureID(GLuint texture_id) {
bool needs_clear = false;
glBindTexture(GL_TEXTURE_2D, texture_id);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
texture_ids.insert(texture_id);
if (texture_ids.size() > TextureMax)
needs_clear = true;
if (needs_clear)
clearTextureIDs();
}
void Texturepool::clearTextureIDs() {
std::vector<GLuint> ids_to_remove;
ids_to_remove.reserve(texture_ids.size());
for (std::set<GLuint>::iterator id_iterator = texture_ids.begin(); id_iterator != texture_ids.end(); ++id_iterator)
ids_to_remove.push_back(*id_iterator);
if (!ids_to_remove.empty())
glDeleteTextures((GLsizei)ids_to_remove.size(), &ids_to_remove[0]);
texture_ids.clear();
}