fixes #70: raster tile rendering as toggleable mode from vector
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
28
include/llmr/shader/raster_shader.hpp
Normal file
28
include/llmr/shader/raster_shader.hpp
Normal 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
|
||||
@@ -17,6 +17,7 @@ enum {
|
||||
PATTERN_SHADER,
|
||||
PLAIN_SHADER,
|
||||
POINT_SHADER,
|
||||
RASTER_SHADER,
|
||||
SHADER_COUNT
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
72
include/llmr/util/raster.hpp
Normal file
72
include/llmr/util/raster.hpp
Normal 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
|
||||
25
include/llmr/util/texturepool.hpp
Normal file
25
include/llmr/util/texturepool.hpp
Normal 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
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
8
src/shader/raster.fragment.glsl
Normal file
8
src/shader/raster.fragment.glsl
Normal 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;
|
||||
}
|
||||
10
src/shader/raster.vertex.glsl
Normal file
10
src/shader/raster.vertex.glsl
Normal 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;
|
||||
}
|
||||
47
src/shader/raster_shader.cpp
Normal file
47
src/shader/raster_shader.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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
195
src/util/raster.cpp
Normal 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
56
src/util/texturepool.cpp
Normal 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();
|
||||
}
|
||||
Reference in New Issue
Block a user