core: rename mbgl directories to mln (#4511)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Bart Louwers
2026-08-22 20:50:14 +02:00
committed by GitHub
parent 06fcf13d42
commit 2a8ebc4906
1924 changed files with 10489 additions and 10489 deletions

170
src/mln/actor/mailbox.cpp Normal file
View File

@@ -0,0 +1,170 @@
#include <mln/actor/mailbox.hpp>
#include <mln/actor/message.hpp>
#include <mln/actor/scheduler.hpp>
#include <mln/util/instrumentation.hpp>
#include <mln/util/scoped.hpp>
#include <cassert>
namespace mln {
Mailbox::Mailbox() = default;
Mailbox::Mailbox(Scheduler& scheduler_)
: weakScheduler(scheduler_.makeWeakPtr()) {}
Mailbox::Mailbox(const TaggedScheduler& scheduler_)
: schedulerTag(scheduler_.tag),
weakScheduler(scheduler_.get()->makeWeakPtr()) {}
void Mailbox::open(const TaggedScheduler& scheduler_) {
assert(!weakScheduler);
schedulerTag = scheduler_.tag;
open(*scheduler_.get());
}
void Mailbox::open(Scheduler& scheduler_) {
assert(!weakScheduler);
// As with close(), block until neither receive() nor push() are in
// progress, and acquire the two mutexes in the same order.
std::scoped_lock receivingLock(receivingMutex);
std::scoped_lock pushingLock(pushingMutex);
if (closed) {
return;
}
weakScheduler = scheduler_.makeWeakPtr();
if (!queue.empty()) {
scheduleToRecieve();
}
}
void Mailbox::close() {
abandon();
// Block until neither receive() nor push() are in progress. Two mutexes are
// used because receive() must not block send(). Of the two, the receiving
// mutex must be acquired first, because that is the order that an actor
// will obtain them when it self-sends a message, and consistent lock
// acquisition order prevents deadlocks. The receiving mutex is recursive to
// allow a mailbox (and thus the actor) to close itself.
std::scoped_lock receivingLock(receivingMutex);
std::scoped_lock pushingLock(pushingMutex);
closed = true;
weakScheduler = {};
}
void Mailbox::abandon() {
auto idleValue = State::Idle;
while (!state.compare_exchange_strong(idleValue, State::Abandoned)) {
if (state == State::Abandoned) {
break;
}
}
}
bool Mailbox::isOpen() const {
return !closed && weakScheduler;
}
void Mailbox::push(std::unique_ptr<Message> message) {
MLN_TRACE_FUNC();
auto idleState = State::Idle;
while (!state.compare_exchange_strong(idleState, State::Processing)) {
if (state == State::Abandoned) {
return;
}
}
Scoped activityFlag{[this]() {
if (state == State::Processing) {
state = State::Idle;
}
}};
{
MLN_TRACE_ZONE(push lock);
std::scoped_lock pushingLock(pushingMutex);
if (closed) {
state = State::Abandoned;
return;
}
bool wasEmpty = false;
{
MLN_TRACE_ZONE(queue lock);
std::scoped_lock queueLock(queueMutex);
wasEmpty = queue.empty();
queue.push(std::move(message));
}
if (wasEmpty) {
MLN_TRACE_ZONE(schedule);
scheduleToRecieve(schedulerTag);
}
}
}
void Mailbox::receive() {
auto idleState = State::Idle;
while (!state.compare_exchange_strong(idleState, State::Processing)) {
if (state == State::Abandoned) {
return;
}
}
Scoped activityFlag{[this]() {
if (state == State::Processing) {
state = State::Idle;
}
}};
std::scoped_lock receivingLock(receivingMutex);
if (closed) {
state = State::Abandoned;
return;
}
std::unique_ptr<Message> message;
bool wasEmpty = false;
{
std::scoped_lock queueLock(queueMutex);
assert(!queue.empty());
message = std::move(queue.front());
queue.pop();
wasEmpty = queue.empty();
}
(*message)();
// If there are more messages in the queue and the scheduler
// is still active, create a new task to handle the next one
if (!wasEmpty) {
scheduleToRecieve();
}
}
void Mailbox::scheduleToRecieve(const std::optional<util::SimpleIdentity>& tag) {
if (auto guard = weakScheduler.lock(); weakScheduler) {
std::weak_ptr<Mailbox> mailbox = shared_from_this();
auto setToRecieve = [mbox = std::move(mailbox)]() {
if (auto locked = mbox.lock()) {
locked->receive();
}
};
if (tag) {
weakScheduler->schedule(*tag, std::move(setToRecieve));
} else {
weakScheduler->schedule(std::move(setToRecieve));
}
}
}
} // namespace mln

View File

@@ -0,0 +1,70 @@
#include <mln/actor/scheduler.hpp>
#include <mln/util/thread_local.hpp>
#include <mln/util/thread_pool.hpp>
#include <mln/util/run_loop.hpp>
namespace mln {
std::function<void()> Scheduler::bindOnce(std::function<void()> fn) {
assert(fn);
return [scheduler = makeWeakPtr(), scheduled = std::move(fn)]() mutable {
if (!scheduled) return; // Repeated call.
if (auto guard = scheduler.lock(); scheduler) {
scheduler->schedule(std::move(scheduled));
}
};
}
namespace {
thread_local Scheduler* localScheduler;
} // namespace
void Scheduler::SetCurrent(Scheduler* scheduler) {
localScheduler = scheduler;
}
Scheduler* Scheduler::GetCurrent(bool init) {
if (!localScheduler && init) {
static thread_local util::RunLoop runLoop;
SetCurrent(&runLoop);
}
return localScheduler;
}
// static
std::shared_ptr<Scheduler> Scheduler::GetBackground() {
static std::weak_ptr<Scheduler> weak;
static std::mutex mtx;
std::scoped_lock lock(mtx);
std::shared_ptr<Scheduler> scheduler = weak.lock();
if (!scheduler) {
weak = scheduler = std::make_shared<ThreadPool>();
}
return scheduler;
}
// static
std::shared_ptr<Scheduler> Scheduler::GetSequenced() {
constexpr std::size_t kSchedulersCount = 10;
static std::vector<std::weak_ptr<Scheduler>> weaks(kSchedulersCount);
static std::mutex mtx;
static std::size_t lastUsedIndex = 0u;
std::scoped_lock lock(mtx);
lastUsedIndex = (lastUsedIndex + 1) % kSchedulersCount;
if (auto scheduler = weaks[lastUsedIndex].lock()) {
return scheduler;
} else {
auto result = std::make_shared<SequencedScheduler>();
weaks[lastUsedIndex] = result;
return result;
}
}
} // namespace mln

View File

@@ -0,0 +1,163 @@
#pragma once
#include <mln/tile/tile_id.hpp>
#include <mln/tile/tile_necessity.hpp>
#include <mln/util/range.hpp>
#include <unordered_set>
#include <optional>
namespace mln {
namespace algorithm {
template <typename GetTileFn,
typename CreateTileFn,
typename RetainTileFn,
typename RenderTileFn,
typename IdealTileIDs,
typename PrefetchedTileMap>
void updateRenderables(GetTileFn getTile,
CreateTileFn createTile,
RetainTileFn retainTile,
RenderTileFn renderTile,
const IdealTileIDs& idealTileIDs,
const PrefetchedTileMap& prefetchedTiles,
const Range<uint8_t>& zoomRange,
const std::optional<uint8_t>& maxParentOverscaleFactor = std::nullopt) {
std::unordered_set<OverscaledTileID> checked;
bool covered = false;
bool parentOrChildTileFound = false;
int32_t overscaledZ = 0;
// for (all in the set of ideal tiles of the source) {
for (const auto& idealDataTileID : idealTileIDs) {
assert(idealDataTileID.canonical.z >= zoomRange.min);
assert(idealDataTileID.canonical.z <= zoomRange.max);
assert(idealDataTileID.overscaledZ >= idealDataTileID.canonical.z);
const UnwrappedTileID idealRenderTileID = idealDataTileID.toUnwrapped();
auto tile = getTile(idealDataTileID);
if (!tile) {
tile = createTile(idealDataTileID);
// For source types where TileJSON.bounds is set, tiles outside the
// bounds are not created
if (tile == nullptr) {
continue;
}
}
// if (source has the tile and bucket is loaded) {
if (tile->isRenderable()) {
retainTile(*tile, TileNecessity::Required);
renderTile(idealRenderTileID, *tile);
} else {
// We are now attempting to load child and parent tiles.
bool parentHasTriedOptional = tile->hasTriedCache();
bool parentIsLoaded = tile->isLoaded();
// The tile isn't loaded yet, but retain it anyway because it's an ideal tile.
retainTile(*tile, TileNecessity::Required);
covered = true;
parentOrChildTileFound = false;
overscaledZ = idealDataTileID.overscaledZ + 1;
if (std::cmp_greater(overscaledZ, zoomRange.max)) {
// We're looking for an overzoomed child tile.
const auto childDataTileID = idealDataTileID.scaledTo(overscaledZ);
tile = getTile(childDataTileID);
if (tile && tile->isRenderable()) {
retainTile(*tile, TileNecessity::Optional);
renderTile(idealRenderTileID, *tile);
parentOrChildTileFound = true;
} else {
covered = false;
}
} else {
// Check all four actual child tiles.
for (const auto& childTileID : idealDataTileID.canonical.children()) {
const OverscaledTileID childDataTileID(overscaledZ, idealRenderTileID.wrap, childTileID);
tile = getTile(childDataTileID);
if (tile && tile->isRenderable()) {
retainTile(*tile, TileNecessity::Optional);
renderTile(childDataTileID.toUnwrapped(), *tile);
parentOrChildTileFound = true;
} else {
// At least one child tile doesn't exist, so we are
// going to look for parents as well.
covered = false;
}
}
}
if (!covered) {
// We couldn't find child tiles that entirely cover the ideal tile.
for (overscaledZ = idealDataTileID.overscaledZ - 1; std::cmp_greater_equal(overscaledZ, zoomRange.min);
--overscaledZ) {
const auto parentDataTileID = idealDataTileID.scaledTo(overscaledZ);
// Request / render parent tile only if it's overscale
// factor is less than defined maximum.
if (maxParentOverscaleFactor &&
(idealDataTileID.overscaledZ - overscaledZ) > *maxParentOverscaleFactor) {
break;
}
if (checked.find(parentDataTileID) != checked.end()) {
// Break parent tile ascent, this route has been checked
// by another child tile before.
break;
} else {
checked.emplace(parentDataTileID);
}
tile = getTile(parentDataTileID);
if (!tile && (parentHasTriedOptional || parentIsLoaded)) {
tile = createTile(parentDataTileID);
}
if (tile) {
if (!parentIsLoaded) {
// We haven't completed loading the child, so we
// only do an optional (cache) request in an attempt
// to quickly load data that we can show.
retainTile(*tile, TileNecessity::Optional);
} else {
// Now that we've checked the child and know for
// sure that we can't load it, we attempt to load
// the parent from the network.
retainTile(*tile, TileNecessity::Required);
}
// Save the current values, since they're the parent of
// the next iteration of the parent tile ascent loop.
parentHasTriedOptional = tile->hasTriedCache();
parentIsLoaded = tile->isLoaded();
if (tile->isRenderable()) {
renderTile(parentDataTileID.toUnwrapped(), *tile);
parentOrChildTileFound = true;
// Break parent tile ascent, since we found one.
break;
}
}
}
if (!parentOrChildTileFound) {
// Reuse prefetched tiles in order to avoid empty screen
for (auto& prefetchedTileEntry : prefetchedTiles) {
const auto& prefetchedDataTileID = prefetchedTileEntry.first;
const UnwrappedTileID prefetchedRenderTileID = prefetchedDataTileID.toUnwrapped();
auto* prefetchedTile = prefetchedTileEntry.second.get();
if (prefetchedTile->isRenderable() && prefetchedDataTileID.canonical.z <= zoomRange.max &&
prefetchedDataTileID.isChildOf(idealDataTileID)) {
retainTile(*prefetchedTile, TileNecessity::Optional);
renderTile(prefetchedRenderTileID, *prefetchedTile);
}
}
}
}
}
}
}
} // namespace algorithm
} // namespace mln

View File

@@ -0,0 +1,143 @@
#pragma once
#include <mln/renderer/tile_mask.hpp>
#include <vector>
#include <functional>
#include <algorithm>
namespace mln {
namespace algorithm {
template <typename T>
bool tileNeedsMask(const std::reference_wrapper<T>& tile) {
return tile.get().usedByRenderedLayers;
}
template <typename T>
void setTileMask(const std::reference_wrapper<T>& tile, TileMask&& mask) {
return tile.get().setMask(std::move(mask));
}
// Overloads for tests
template <typename T>
bool tileNeedsMask(const T& tile) {
return tile.usedByRenderedLayers;
}
template <typename T>
void setTileMask(T& tile, TileMask&& mask) {
return tile.setMask(std::move(mask));
}
template <typename Iterator>
void computeTileMasks(
const CanonicalTileID& root, const UnwrappedTileID& ref, const Iterator begin, const Iterator end, TileMask& mask) {
// If the reference or any of its children is found in the list, we need to recurse.
for (auto it = begin; it != end; ++it) {
const UnwrappedTileID& id = it->first;
if (!tileNeedsMask(it->second)) {
continue;
}
if (ref == id) {
// The current tile is masked out, so we don't need to add them to the mask set.
return;
}
if (id.isChildOf(ref)) {
// There's at least one child tile that is masked out, so recursively descend.
for (const auto& child : ref.children()) {
computeTileMasks(root, child, it, end, mask);
}
return;
}
}
// We couldn't find a child, so it's definitely a masked part.
// Compute the difference between the root tile ID and the reference tile
// ID, since TileMask elements are always relative (see below for
// explanation).
const uint8_t diffZ = ref.canonical.z - root.z;
mask.emplace(diffZ, ref.canonical.x - (root.x << diffZ), ref.canonical.y - (root.y << diffZ));
}
// Updates the TileMasks for all renderable tiles. Each renderable tile has a
// corresponding UnwrappedTileID indicating where it should be rendered on the
// screen. A TileMask describes all regions within a renderable tile that are
// *not* covered by other renderable tiles. Example: Renderable tiles in our
// list are 2/1/3, 3/3/6, and 4/5/13. The schematic for creating the TileMask
// for 2/1/3 looks like this:
//
// ┌────────┬────────┬─────────────────┐
// │ │ │#################│
// │ 4/4/12 │ 4/5/12 │#################│
// │ │ │#################│
// ├──────3/2/6──────┤#####3/3/6#######│
// │ │########│#################│
// │ 4/4/13 │#4/5/13#│#################│
// │ │########│#################│
// ├────────┴──────2/1/3───────────────┤
// │ │ │
// │ │ │
// │ │ │
// │ 3/2/7 │ 3/3/7 │
// │ │ │
// │ │ │
// │ │ │
// └─────────────────┴─────────────────┘
//
// The TileMask for 2/1/3 thus consists of the tiles 4/4/12, 4/5/12, 4/4/13,
// 3/2/7, and 3/3/7, but it does *not* include 4/5/13, and 3/3/6, since these
// are other renderable tiles. A TileMask always contains TileIDs *relative* to
// the tile it is generated for, so 2/1/3 is "subtracted" from these TileIDs.
// The final TileMask for 2/1/3 will thus be:
//
// ┌────────┬────────┬─────────────────┐
// │ │ │#################│
// │ 2/0/0 │ 2/1/0 │#################│
// │ │ │#################│
// ├────────┼────────┤#################│
// │ │########│#################│
// │ 2/0/1 │########│#################│
// │ │########│#################│
// ├────────┴────────┼─────────────────┤
// │ │ │
// │ │ │
// │ │ │
// │ 1/0/1 │ 1/1/1 │
// │ │ │
// │ │ │
// │ │ │
// └─────────────────┴─────────────────┘
//
// Only other renderable tiles that are *children* of the renderable tile we are
// generating the mask for will will be considered. For example, adding a
// renderable tile with TileID 4/8/13 won't affect the TileMask for 2/1/3, since
// it is not a descendant of it.
template <typename RenderableTilesMap>
void updateTileMasks(RenderableTilesMap& renderables) {
TileMask mask;
const auto end = renderables.end();
for (auto it = renderables.begin(); it != end; ++it) {
const UnwrappedTileID& id = it->first;
if (!tileNeedsMask(it->second)) {
continue;
}
// Try to add all remaining ids as children. We sorted the tile list
// by z earlier, so all preceding items cannot be children of the
// current tile. We also compute the lower bound of the next wrap,
// because items of the next wrap can never be children of the current
// wrap.
auto child_it = std::next(it);
const auto children_end = std::lower_bound(
child_it, end, UnwrappedTileID{static_cast<int16_t>(id.wrap + 1), {0, 0, 0}}, [](auto& a, auto& b) {
return a.first < b;
});
mask.clear();
computeTileMasks(id.canonical, id, child_it, children_end, mask);
setTileMask(it->second, std::move(mask));
}
}
} // namespace algorithm
} // namespace mln

View File

@@ -0,0 +1,273 @@
#include <mln/annotation/annotation_manager.hpp>
#include <mln/annotation/annotation_source.hpp>
#include <mln/annotation/annotation_tile.hpp>
#include <mln/annotation/fill_annotation_impl.hpp>
#include <mln/annotation/line_annotation_impl.hpp>
#include <mln/annotation/symbol_annotation_impl.hpp>
#include <mln/layermanager/layer_manager.hpp>
#include <mln/style/expression/dsl.hpp>
#include <mln/style/expression/image.hpp>
#include <mln/style/layers/symbol_layer.hpp>
#include <mln/style/layers/symbol_layer_impl.hpp>
#include <mln/style/style.hpp>
#include <mln/style/style_impl.hpp>
#include <boost/iterator/function_output_iterator.hpp>
// Note: LayerManager::annotationsEnabled is defined
// at compile time, so that linker (with LTO on) is able
// to optimize out the unreachable code.
#define CHECK_ANNOTATIONS_ENABLED_AND_RETURN_NOARG() \
if (!LayerManager::annotationsEnabled) { \
assert(false); \
return; \
}
#define CHECK_ANNOTATIONS_ENABLED_AND_RETURN(result) \
if (!LayerManager::annotationsEnabled) { \
assert(false); \
return result; \
}
namespace mln {
using namespace style;
const std::string AnnotationManager::SourceID = "org.maplibre.annotations";
const std::string AnnotationManager::PointLayerID = "org.maplibre.annotations.points";
const std::string AnnotationManager::ShapeLayerID = "org.maplibre.annotations.shape.";
AnnotationManager::AnnotationManager(Style& style_)
: style(style_) {}
AnnotationManager::~AnnotationManager() = default;
void AnnotationManager::setStyle(Style& style_) {
CHECK_ANNOTATIONS_ENABLED_AND_RETURN_NOARG();
style = style_;
}
void AnnotationManager::onStyleLoaded() {
CHECK_ANNOTATIONS_ENABLED_AND_RETURN_NOARG();
updateStyle();
}
AnnotationID AnnotationManager::addAnnotation(const Annotation& annotation) {
CHECK_ANNOTATIONS_ENABLED_AND_RETURN(nextID++);
std::scoped_lock lock(mutex);
AnnotationID id = nextID++;
Annotation::visit(annotation, [&](const auto& annotation_) { this->add(id, annotation_); });
dirty = true;
return id;
}
bool AnnotationManager::updateAnnotation(const AnnotationID& id, const Annotation& annotation) {
CHECK_ANNOTATIONS_ENABLED_AND_RETURN(true);
std::scoped_lock lock(mutex);
Annotation::visit(annotation, [&](const auto& annotation_) { this->update(id, annotation_); });
return dirty;
}
void AnnotationManager::removeAnnotation(const AnnotationID& id) {
CHECK_ANNOTATIONS_ENABLED_AND_RETURN_NOARG();
std::scoped_lock lock(mutex);
remove(id);
dirty = true;
}
void AnnotationManager::add(const AnnotationID& id, const SymbolAnnotation& annotation) {
auto impl = std::make_shared<SymbolAnnotationImpl>(id, annotation);
symbolTree.insert(impl);
symbolAnnotations.emplace(id, impl);
}
void AnnotationManager::add(const AnnotationID& id, const LineAnnotation& annotation) {
ShapeAnnotationImpl& impl =
*shapeAnnotations.emplace(id, std::make_unique<LineAnnotationImpl>(id, annotation)).first->second;
impl.updateStyle(*style.get().impl);
}
void AnnotationManager::add(const AnnotationID& id, const FillAnnotation& annotation) {
ShapeAnnotationImpl& impl =
*shapeAnnotations.emplace(id, std::make_unique<FillAnnotationImpl>(id, annotation)).first->second;
impl.updateStyle(*style.get().impl);
}
void AnnotationManager::update(const AnnotationID& id, const SymbolAnnotation& annotation) {
auto it = symbolAnnotations.find(id);
if (it == symbolAnnotations.end()) {
assert(false); // Attempt to update a non-existent symbol annotation
return;
}
const SymbolAnnotation& existing = it->second->annotation;
if (existing.geometry != annotation.geometry || existing.icon != annotation.icon) {
dirty = true;
remove(id);
add(id, annotation);
}
}
void AnnotationManager::update(const AnnotationID& id, const LineAnnotation& annotation) {
auto it = shapeAnnotations.find(id);
if (it == shapeAnnotations.end()) {
assert(false); // Attempt to update a non-existent shape annotation
return;
}
shapeAnnotations.erase(it);
add(id, annotation);
dirty = true;
}
void AnnotationManager::update(const AnnotationID& id, const FillAnnotation& annotation) {
auto it = shapeAnnotations.find(id);
if (it == shapeAnnotations.end()) {
assert(false); // Attempt to update a non-existent shape annotation
return;
}
shapeAnnotations.erase(it);
add(id, annotation);
dirty = true;
}
void AnnotationManager::remove(const AnnotationID& id) {
CHECK_ANNOTATIONS_ENABLED_AND_RETURN_NOARG();
if (symbolAnnotations.contains(id)) {
symbolTree.remove(symbolAnnotations.at(id));
symbolAnnotations.erase(id);
} else if (shapeAnnotations.contains(id)) {
auto it = shapeAnnotations.find(id);
(void)*style.get().impl->removeLayer(it->second->layerID);
shapeAnnotations.erase(it);
} else {
assert(false); // Should never happen
}
}
std::unique_ptr<AnnotationTileData> AnnotationManager::getTileData(const CanonicalTileID& tileID) {
if (symbolAnnotations.empty() && shapeAnnotations.empty()) return nullptr;
auto tileData = std::make_unique<AnnotationTileData>();
auto pointLayer = tileData->addLayer(PointLayerID);
LatLngBounds tileBounds(tileID);
// Hack for https://github.com/mapbox/mapbox-gl-native/issues/12472
// To handle precision issues, query a slightly larger area than the tile bounds
// Symbols at a border can be included in vector data for both tiles
// The rendering/querying logic will make sure the symbols show up in only one of the tiles
tileBounds.extend(LatLng(tileBounds.south() - 0.000000001, tileBounds.west() - 0.000000001));
tileBounds.extend(LatLng(tileBounds.north() + 0.000000001, tileBounds.east() + 0.000000001));
symbolTree.query(
boost::geometry::index::intersects(tileBounds),
boost::make_function_output_iterator([&](const auto& val) { val->updateLayer(tileID, *pointLayer); }));
for (const auto& shape : shapeAnnotations) {
shape.second->updateTileData(tileID, *tileData);
}
return tileData;
}
void AnnotationManager::updateStyle() {
// Create annotation source, point layer, and point bucket. We do everything
// via Style::Impl because we don't want annotation mutations to trigger
// Style::Impl::styleMutated to be set.
if (!style.get().impl->getSource(SourceID)) {
style.get().impl->addSource(std::make_unique<AnnotationSource>());
std::unique_ptr<SymbolLayer> layer = std::make_unique<SymbolLayer>(PointLayerID, SourceID);
using namespace expression::dsl;
layer->setSourceLayer(PointLayerID);
layer->setIconImage(PropertyExpression<expression::Image>(
image(concat(vec(literal(SourceID + "."), toString(get("sprite")))))));
layer->setIconAllowOverlap(true);
layer->setIconIgnorePlacement(true);
style.get().impl->addLayer(std::move(layer));
}
std::scoped_lock lock(mutex);
for (const auto& shape : shapeAnnotations) {
shape.second->updateStyle(*style.get().impl);
}
for (const auto& image : images) {
// Call addImage even for images we may have previously added, because
// we must support addAnnotationImage being used to update an existing
// image. Creating a new image is relatively cheap, as it copies only
// the Immutable reference. (We can't keep track of which images need to
// be added because we don't know if the style is the same instance as
// in the last updateStyle call. If it's a new style, we need to add all
// images.)
style.get().impl->addImage(std::make_unique<style::Image>(image.second));
}
}
void AnnotationManager::updateData() {
CHECK_ANNOTATIONS_ENABLED_AND_RETURN_NOARG();
std::scoped_lock lock(mutex);
if (dirty) {
for (auto& tile : tiles) {
tile->setData(getTileData(tile->id.canonical));
}
dirty = false;
}
}
void AnnotationManager::addTile(AnnotationTile& tile) {
CHECK_ANNOTATIONS_ENABLED_AND_RETURN_NOARG();
std::scoped_lock lock(mutex);
tiles.insert(&tile);
tile.setData(getTileData(tile.id.canonical));
}
void AnnotationManager::removeTile(AnnotationTile& tile) {
CHECK_ANNOTATIONS_ENABLED_AND_RETURN_NOARG();
std::scoped_lock lock(mutex);
tiles.erase(&tile);
}
namespace {
// To ensure that annotation images do not collide with images from the style,
// we prefix input image IDs with "org.maplibre.annotations".
std::string prefixedImageID(const std::string& id) {
return AnnotationManager::SourceID + "." + id;
}
} // namespace
void AnnotationManager::addImage(std::unique_ptr<style::Image> image) {
CHECK_ANNOTATIONS_ENABLED_AND_RETURN_NOARG();
std::scoped_lock lock(mutex);
const std::string id = prefixedImageID(image->getID());
images.erase(id);
auto inserted = images.emplace(id,
style::Image(id, image->getImage().clone(), image->getPixelRatio(), image->isSdf()));
style.get().impl->addImage(std::make_unique<style::Image>(inserted.first->second));
}
void AnnotationManager::removeImage(const std::string& id_) {
CHECK_ANNOTATIONS_ENABLED_AND_RETURN_NOARG();
std::scoped_lock lock(mutex);
const std::string id = prefixedImageID(id_);
images.erase(id);
style.get().impl->removeImage(id);
}
double AnnotationManager::getTopOffsetPixelsForImage(const std::string& id_) {
CHECK_ANNOTATIONS_ENABLED_AND_RETURN(0.0);
std::scoped_lock lock(mutex);
const std::string id = prefixedImageID(id_);
auto it = images.find(id);
return it != images.end() ? -(it->second.getImage().size.height / it->second.getPixelRatio()) / 2 : 0.0;
}
} // namespace mln

View File

@@ -0,0 +1,97 @@
#pragma once
#include <mln/annotation/annotation.hpp>
#include <mln/annotation/symbol_annotation_impl.hpp>
#include <mln/style/image.hpp>
#include <mln/util/noncopyable.hpp>
#include <mapbox/std/weak.hpp>
#include <mutex>
#include <string>
#include <unordered_map>
#include <unordered_set>
namespace mln {
class LatLngBounds;
class AnnotationTile;
class AnnotationTileData;
class SymbolAnnotationImpl;
class ShapeAnnotationImpl;
namespace style {
class Style;
} // namespace style
// NOTE: Any derived class must invalidate `weakFactory` in the destructor
class AnnotationManager final : private util::noncopyable {
public:
AnnotationManager(style::Style&);
~AnnotationManager();
AnnotationID addAnnotation(const Annotation&);
bool updateAnnotation(const AnnotationID&, const Annotation&);
void removeAnnotation(const AnnotationID&);
void addImage(std::unique_ptr<style::Image>);
void removeImage(const std::string&);
double getTopOffsetPixelsForImage(const std::string&);
void setStyle(style::Style&);
void onStyleLoaded();
void updateData();
void addTile(AnnotationTile&);
void removeTile(AnnotationTile&);
static const std::string SourceID;
static const std::string PointLayerID;
static const std::string ShapeLayerID;
mapbox::base::WeakPtr<AnnotationManager> makeWeakPtr() { return weakFactory.makeWeakPtr(); }
private:
void add(const AnnotationID&, const SymbolAnnotation&);
void add(const AnnotationID&, const LineAnnotation&);
void add(const AnnotationID&, const FillAnnotation&);
void update(const AnnotationID&, const SymbolAnnotation&);
void update(const AnnotationID&, const LineAnnotation&);
void update(const AnnotationID&, const FillAnnotation&);
void remove(const AnnotationID&);
void updateStyle();
std::unique_ptr<AnnotationTileData> getTileData(const CanonicalTileID&);
std::reference_wrapper<style::Style> style;
std::mutex mutex;
bool dirty = false;
AnnotationID nextID = 0;
using SymbolAnnotationTree = boost::geometry::index::rtree<std::shared_ptr<const SymbolAnnotationImpl>,
boost::geometry::index::rstar<16, 4>>;
// Unlike std::unordered_map, std::map is guaranteed to sort by
// AnnotationID, ensuring that older annotations are below newer
// annotations. <https://github.com/mapbox/mapbox-gl-native/issues/5691>
using SymbolAnnotationMap = std::map<AnnotationID, std::shared_ptr<SymbolAnnotationImpl>>;
using ShapeAnnotationMap = std::map<AnnotationID, std::unique_ptr<ShapeAnnotationImpl>>;
using ImageMap = std::unordered_map<std::string, style::Image>;
SymbolAnnotationTree symbolTree;
SymbolAnnotationMap symbolAnnotations;
ShapeAnnotationMap shapeAnnotations;
ImageMap images;
std::unordered_set<AnnotationTile*> tiles;
mapbox::base::WeakPtrFactory<AnnotationManager> weakFactory{this};
// Do not add members here, see `WeakPtrFactory`
};
} // namespace mln

View File

@@ -0,0 +1,35 @@
#include <mln/annotation/annotation_manager.hpp>
#include <mln/annotation/annotation_source.hpp>
#include <mln/style/layer.hpp>
namespace mln {
using namespace style;
AnnotationSource::AnnotationSource()
: Source(makeMutable<Impl>()) {}
AnnotationSource::Impl::Impl()
: Source::Impl(SourceType::Annotations, AnnotationManager::SourceID) {}
const AnnotationSource::Impl& AnnotationSource::impl() const {
return static_cast<const Impl&>(*baseImpl);
}
void AnnotationSource::loadDescription(FileSource&) {
loaded = true;
}
std::optional<std::string> AnnotationSource::Impl::getAttribution() const {
return {};
}
bool AnnotationSource::supportsLayerType(const mln::style::LayerTypeInfo* info) const {
return !std::strcmp(info->type, "line") || !std::strcmp(info->type, "symbol") || !std::strcmp(info->type, "fill");
}
Mutable<Source::Impl> AnnotationSource::createMutable() const noexcept {
return staticMutableCast<Source::Impl>(makeMutable<Impl>(impl()));
}
} // namespace mln

View File

@@ -0,0 +1,34 @@
#pragma once
#include <mln/style/source.hpp>
#include <mln/style/source_impl.hpp>
namespace mln {
// NOTE: Any derived class must invalidate `weakFactory` in the destructor
class AnnotationSource final : public style::Source {
public:
AnnotationSource();
class Impl;
const Impl& impl() const;
protected:
Mutable<Source::Impl> createMutable() const noexcept final;
private:
void loadDescription(FileSource&) final;
bool supportsLayerType(const mln::style::LayerTypeInfo*) const override;
mapbox::base::WeakPtr<Source> makeWeakPtr() override { return weakFactory.makeWeakPtr(); }
mapbox::base::WeakPtrFactory<Source> weakFactory{this};
// Do not add members here, see `WeakPtrFactory`
};
class AnnotationSource::Impl : public style::Source::Impl {
public:
Impl();
std::optional<std::string> getAttribution() const final;
};
} // namespace mln

View File

@@ -0,0 +1,121 @@
#include <mln/annotation/annotation_tile.hpp>
#include <mln/annotation/annotation_manager.hpp>
#include <mln/util/constants.hpp>
#include <mln/renderer/tile_parameters.hpp>
#include <utility>
namespace mln {
AnnotationTile::AnnotationTile(const OverscaledTileID& overscaledTileID,
const TileParameters& parameters,
TileObserver* observer_)
: GeometryTile(overscaledTileID, AnnotationManager::SourceID, parameters, observer_),
annotationManager(parameters.annotationManager) {
if (auto guard = annotationManager.lock(); annotationManager) {
annotationManager->addTile(*this);
}
}
AnnotationTile::~AnnotationTile() {
if (auto guard = annotationManager.lock(); annotationManager) {
annotationManager->removeTile(*this);
}
}
class AnnotationTileFeatureData {
public:
AnnotationTileFeatureData(const AnnotationID id_,
FeatureType type_,
GeometryCollection&& geometries_,
std::unordered_map<std::string, std::string>&& properties_)
: id(id_),
type(type_),
geometries(std::move(geometries_)),
properties(std::move(properties_)) {}
AnnotationID id;
FeatureType type;
GeometryCollection geometries;
std::unordered_map<std::string, std::string> properties;
};
AnnotationTileFeature::AnnotationTileFeature(std::shared_ptr<const AnnotationTileFeatureData> data_)
: data(std::move(data_)) {}
AnnotationTileFeature::~AnnotationTileFeature() = default;
FeatureType AnnotationTileFeature::getType() const {
return data->type;
}
std::optional<Value> AnnotationTileFeature::getValue(const std::string& key) const {
auto it = data->properties.find(key);
if (it != data->properties.end()) {
return std::optional<Value>(it->second);
}
return std::nullopt;
}
FeatureIdentifier AnnotationTileFeature::getID() const {
return data->id;
}
const GeometryCollection& AnnotationTileFeature::getGeometries() const {
return data->geometries;
}
class AnnotationTileLayerData {
public:
explicit AnnotationTileLayerData(std::string name_)
: name(std::move(name_)) {}
const std::string name;
std::vector<std::shared_ptr<const AnnotationTileFeatureData>> features;
};
AnnotationTileLayer::AnnotationTileLayer(std::shared_ptr<AnnotationTileLayerData> layer_)
: layer(std::move(layer_)) {}
std::size_t AnnotationTileLayer::featureCount() const {
return layer->features.size();
}
std::unique_ptr<GeometryTileFeature> AnnotationTileLayer::getFeature(std::size_t i) const {
return std::make_unique<AnnotationTileFeature>(layer->features.at(i));
}
std::string AnnotationTileLayer::getName() const {
return layer->name;
}
void AnnotationTileLayer::addFeature(const AnnotationID id,
FeatureType type,
GeometryCollection geometries,
std::unordered_map<std::string, std::string> properties) {
layer->features.emplace_back(
std::make_shared<AnnotationTileFeatureData>(id, type, std::move(geometries), std::move(properties)));
}
std::unique_ptr<GeometryTileData> AnnotationTileData::clone() const {
return std::make_unique<AnnotationTileData>(*this);
}
std::unique_ptr<GeometryTileLayer> AnnotationTileData::getLayer(const std::string& name) const {
auto it = layers.find(name);
if (it != layers.end()) {
return std::make_unique<AnnotationTileLayer>(it->second);
}
return nullptr;
}
std::unique_ptr<AnnotationTileLayer> AnnotationTileData::addLayer(const std::string& name) {
// Only constructs a new layer if it doesn't yet exist, otherwise, we'll use the existing one.
auto it = layers.find(name);
if (it == layers.end()) {
it = layers.emplace(name, std::make_shared<AnnotationTileLayerData>(name)).first;
}
return std::make_unique<AnnotationTileLayer>(it->second);
}
} // namespace mln

View File

@@ -0,0 +1,67 @@
#pragma once
#include <mln/annotation/annotation.hpp>
#include <mln/tile/geometry_tile.hpp>
#include <mln/tile/geometry_tile_data.hpp>
namespace mln {
class AnnotationManager;
class TileParameters;
class AnnotationTile : public GeometryTile {
public:
AnnotationTile(const OverscaledTileID&, const TileParameters&, TileObserver*);
~AnnotationTile() override;
private:
mapbox::base::WeakPtr<AnnotationManager> annotationManager;
};
class AnnotationTileFeatureData;
class AnnotationTileFeature : public GeometryTileFeature {
public:
AnnotationTileFeature(std::shared_ptr<const AnnotationTileFeatureData>);
~AnnotationTileFeature() override;
FeatureType getType() const override;
std::optional<Value> getValue(const std::string&) const override;
FeatureIdentifier getID() const override;
const GeometryCollection& getGeometries() const override;
private:
std::shared_ptr<const AnnotationTileFeatureData> data;
};
class AnnotationTileLayerData;
class AnnotationTileLayer : public GeometryTileLayer {
public:
AnnotationTileLayer(std::shared_ptr<AnnotationTileLayerData>);
std::size_t featureCount() const override;
std::unique_ptr<GeometryTileFeature> getFeature(std::size_t i) const override;
std::string getName() const override;
void addFeature(AnnotationID,
FeatureType,
GeometryCollection,
std::unordered_map<std::string, std::string> properties = {{}});
private:
std::shared_ptr<AnnotationTileLayerData> layer;
};
class AnnotationTileData : public GeometryTileData {
public:
std::unique_ptr<GeometryTileData> clone() const override;
std::unique_ptr<GeometryTileLayer> getLayer(const std::string&) const override;
std::unique_ptr<AnnotationTileLayer> addLayer(const std::string&);
private:
std::unordered_map<std::string, std::shared_ptr<AnnotationTileLayerData>> layers;
};
} // namespace mln

View File

@@ -0,0 +1,36 @@
#include <mln/annotation/fill_annotation_impl.hpp>
#include <mln/annotation/annotation_manager.hpp>
#include <mln/style/style_impl.hpp>
#include <mln/style/layers/fill_layer.hpp>
namespace mln {
using namespace style;
FillAnnotationImpl::FillAnnotationImpl(AnnotationID id_, FillAnnotation annotation_)
: ShapeAnnotationImpl(id_),
annotation(ShapeAnnotationGeometry::visit(annotation_.geometry, CloseShapeAnnotation{}),
annotation_.opacity,
annotation_.color,
annotation_.outlineColor) {}
void FillAnnotationImpl::updateStyle(Style::Impl& style) const {
Layer* layer = style.getLayer(layerID);
if (!layer) {
auto newLayer = std::make_unique<FillLayer>(layerID, AnnotationManager::SourceID);
newLayer->setSourceLayer(layerID);
layer = style.addLayer(std::move(newLayer), AnnotationManager::PointLayerID);
}
auto* fillLayer = static_cast<FillLayer*>(layer);
fillLayer->setFillOpacity(annotation.opacity);
fillLayer->setFillColor(annotation.color);
fillLayer->setFillOutlineColor(annotation.outlineColor);
}
const ShapeAnnotationGeometry& FillAnnotationImpl::geometry() const {
return annotation.geometry;
}
} // namespace mln

View File

@@ -0,0 +1,19 @@
#pragma once
#include <mln/annotation/shape_annotation_impl.hpp>
#include <mln/annotation/annotation.hpp>
namespace mln {
class FillAnnotationImpl : public ShapeAnnotationImpl {
public:
FillAnnotationImpl(AnnotationID, FillAnnotation);
void updateStyle(style::Style::Impl&) const final;
const ShapeAnnotationGeometry& geometry() const final;
private:
const FillAnnotation annotation;
};
} // namespace mln

View File

@@ -0,0 +1,37 @@
#include <mln/annotation/line_annotation_impl.hpp>
#include <mln/annotation/annotation_manager.hpp>
#include <mln/style/style_impl.hpp>
#include <mln/style/layers/line_layer.hpp>
namespace mln {
using namespace style;
LineAnnotationImpl::LineAnnotationImpl(AnnotationID id_, LineAnnotation annotation_)
: ShapeAnnotationImpl(id_),
annotation(ShapeAnnotationGeometry::visit(annotation_.geometry, CloseShapeAnnotation{}),
annotation_.opacity,
annotation_.width,
annotation_.color) {}
void LineAnnotationImpl::updateStyle(Style::Impl& style) const {
Layer* layer = style.getLayer(layerID);
if (!layer) {
auto newLayer = std::make_unique<LineLayer>(layerID, AnnotationManager::SourceID);
newLayer->setSourceLayer(layerID);
newLayer->setLineJoin(LineJoinType::Round);
layer = style.addLayer(std::move(newLayer), AnnotationManager::PointLayerID);
}
auto* lineLayer = static_cast<LineLayer*>(layer);
lineLayer->setLineOpacity(annotation.opacity);
lineLayer->setLineWidth(annotation.width);
lineLayer->setLineColor(annotation.color);
}
const ShapeAnnotationGeometry& LineAnnotationImpl::geometry() const {
return annotation.geometry;
}
} // namespace mln

View File

@@ -0,0 +1,19 @@
#pragma once
#include <mln/annotation/shape_annotation_impl.hpp>
#include <mln/annotation/annotation.hpp>
namespace mln {
class LineAnnotationImpl : public ShapeAnnotationImpl {
public:
LineAnnotationImpl(AnnotationID, LineAnnotation);
void updateStyle(style::Style::Impl&) const final;
const ShapeAnnotationGeometry& geometry() const final;
private:
const LineAnnotation annotation;
};
} // namespace mln

View File

@@ -0,0 +1,63 @@
#include <mln/annotation/render_annotation_source.hpp>
#include <mln/annotation/annotation_tile.hpp>
#include <mln/renderer/render_tile.hpp>
#include <mln/renderer/paint_parameters.hpp>
#include <mln/util/instrumentation.hpp>
#include <mln/layermanager/layer_manager.hpp>
namespace mln {
using namespace style;
RenderAnnotationSource::RenderAnnotationSource(Immutable<AnnotationSource::Impl> impl_,
const TaggedScheduler& threadPool_)
: RenderTileSource(std::move(impl_), threadPool_) {
assert(LayerManager::annotationsEnabled);
tilePyramid.setObserver(this);
}
const AnnotationSource::Impl& RenderAnnotationSource::impl() const {
return static_cast<const AnnotationSource::Impl&>(*baseImpl);
}
void RenderAnnotationSource::update(Immutable<style::Source::Impl> baseImpl_,
const std::vector<Immutable<style::LayerProperties>>& layers,
const bool needsRendering,
const bool needsRelayout,
const TileParameters& parameters) {
MLN_TRACE_FUNC();
std::swap(baseImpl, baseImpl_);
enabled = needsRendering;
tilePyramid.update(layers,
needsRendering,
needsRelayout,
parameters,
*baseImpl,
util::tileSize_I,
// Zoom level 16 is typically sufficient for annotations.
// See https://github.com/mapbox/mapbox-gl-native/issues/10197
{0, 16},
std::nullopt,
[&](const OverscaledTileID& tileID, TileObserver* observer_) {
return std::make_unique<AnnotationTile>(tileID, parameters, observer_);
});
}
std::unordered_map<std::string, std::vector<Feature>> RenderAnnotationSource::queryRenderedFeatures(
const ScreenLineString& geometry,
const TransformState& transformState,
const std::unordered_map<std::string, const RenderLayer*>& layers,
const RenderedQueryOptions& options,
const mat4& projMatrix) const {
return tilePyramid.queryRenderedFeatures(geometry, transformState, layers, options, projMatrix, {});
}
std::vector<Feature> RenderAnnotationSource::querySourceFeatures(const SourceQueryOptions&) const {
return {};
}
} // namespace mln

View File

@@ -0,0 +1,31 @@
#pragma once
#include <mln/annotation/annotation_source.hpp>
#include <mln/renderer/sources/render_tile_source.hpp>
namespace mln {
class RenderAnnotationSource final : public RenderTileSource {
public:
explicit RenderAnnotationSource(Immutable<AnnotationSource::Impl>, const TaggedScheduler&);
void update(Immutable<style::Source::Impl>,
const std::vector<Immutable<style::LayerProperties>>&,
bool needsRendering,
bool needsRelayout,
const TileParameters&) final;
std::unordered_map<std::string, std::vector<Feature>> queryRenderedFeatures(
const ScreenLineString& geometry,
const TransformState& transformState,
const std::unordered_map<std::string, const RenderLayer*>& layers,
const RenderedQueryOptions& options,
const mat4& projMatrix) const final;
std::vector<Feature> querySourceFeatures(const SourceQueryOptions&) const final;
private:
const AnnotationSource::Impl& impl() const;
};
} // namespace mln

View File

@@ -0,0 +1,58 @@
#include <mln/annotation/shape_annotation_impl.hpp>
#include <mln/annotation/annotation_tile.hpp>
#include <mln/annotation/annotation_manager.hpp>
#include <mln/tile/tile_id.hpp>
#include <mln/math/wrap.hpp>
#include <mln/math/clamp.hpp>
#include <mln/util/string.hpp>
#include <mln/util/constants.hpp>
#include <mln/util/geometry.hpp>
namespace mln {
using namespace style;
ShapeAnnotationImpl::ShapeAnnotationImpl(const AnnotationID id_)
: id(id_),
layerID(AnnotationManager::ShapeLayerID + util::toString(id)) {}
void ShapeAnnotationImpl::updateTileData(const CanonicalTileID& tileID, AnnotationTileData& data) {
static const double baseTolerance = 4;
if (!shapeTiler) {
mapbox::feature::feature_collection<double> features;
features.emplace_back(ShapeAnnotationGeometry::visit(
geometry(), [](auto&& geom) { return Feature{std::forward<decltype(geom)>(geom)}; }));
mapbox::geojsonvt::Options options;
// The annotation source is currently hard coded to maxzoom 16, so we're
// topping out at z16 here as well.
options.maxZoom = 16;
options.buffer = 255u;
options.extent = util::EXTENT;
options.tolerance = baseTolerance;
shapeTiler = std::make_unique<mapbox::geojsonvt::GeoJSONVT>(features, options);
}
const auto& shapeTile = shapeTiler->getTile(tileID.z, tileID.x, tileID.y);
if (shapeTile.features.empty()) return;
auto layer = data.addLayer(layerID);
ToGeometryCollection toGeometryCollection;
ToFeatureType toFeatureType;
for (const auto& shapeFeature : shapeTile.features) {
FeatureType featureType = apply_visitor(toFeatureType, shapeFeature.geometry);
GeometryCollection renderGeometry = apply_visitor(toGeometryCollection, shapeFeature.geometry);
assert(featureType != FeatureType::Unknown);
// https://github.com/mapbox/geojson-vt-cpp/issues/44
if (featureType == FeatureType::Polygon) {
renderGeometry = fixupPolygons(renderGeometry);
}
layer->addFeature(id, featureType, renderGeometry);
}
}
} // namespace mln

View File

@@ -0,0 +1,58 @@
#pragma once
#include <mln/util/string.hpp>
#include <mapbox/geojsonvt.hpp>
#include <mln/annotation/annotation.hpp>
#include <mln/util/geometry.hpp>
#include <mln/style/style.hpp>
#include <string>
#include <memory>
namespace mln {
class AnnotationTileData;
class CanonicalTileID;
class ShapeAnnotationImpl {
public:
ShapeAnnotationImpl(AnnotationID);
virtual ~ShapeAnnotationImpl() = default;
virtual void updateStyle(style::Style::Impl &) const = 0;
virtual const ShapeAnnotationGeometry &geometry() const = 0;
void updateTileData(const CanonicalTileID &, AnnotationTileData &);
const AnnotationID id;
const std::string layerID;
std::unique_ptr<mapbox::geojsonvt::GeoJSONVT> shapeTiler;
};
struct CloseShapeAnnotation {
ShapeAnnotationGeometry operator()(const mln::LineString<double> &geom) const { return geom; }
ShapeAnnotationGeometry operator()(const mln::MultiLineString<double> &geom) const { return geom; }
ShapeAnnotationGeometry operator()(const mln::Polygon<double> &geom) const {
mln::Polygon<double> closed = geom;
for (auto &ring : closed) {
if (!ring.empty() && ring.front() != ring.back()) {
ring.emplace_back(ring.front());
}
}
return closed;
}
ShapeAnnotationGeometry operator()(const mln::MultiPolygon<double> &geom) const {
mln::MultiPolygon<double> closed = geom;
for (auto &polygon : closed) {
for (auto &ring : polygon) {
if (!ring.empty() && ring.front() != ring.back()) {
ring.emplace_back(ring.front());
}
}
}
return closed;
}
};
} // namespace mln

View File

@@ -0,0 +1,23 @@
#include <mln/annotation/symbol_annotation_impl.hpp>
#include <mln/annotation/annotation_tile.hpp>
#include <mln/tile/tile_id.hpp>
#include <mln/math/clamp.hpp>
#include <mln/util/tile_coordinate.hpp>
namespace mln {
SymbolAnnotationImpl::SymbolAnnotationImpl(AnnotationID id_, SymbolAnnotation annotation_)
: id(id_),
annotation(std::move(annotation_)) {}
void SymbolAnnotationImpl::updateLayer(const CanonicalTileID& tileID, AnnotationTileLayer& layer) const {
std::unordered_map<std::string, std::string> featureProperties;
featureProperties.emplace("sprite", annotation.icon.empty() ? std::string("default_marker") : annotation.icon);
LatLng latLng{annotation.geometry.y, annotation.geometry.x};
TileCoordinate coordinate = TileCoordinate::fromLatLng(0, latLng);
GeometryCoordinate tilePoint = TileCoordinate::toGeometryCoordinate(UnwrappedTileID(0, tileID), coordinate.p);
layer.addFeature(id, FeatureType::Point, GeometryCollection{{{{tilePoint}}}}, featureProperties);
}
} // namespace mln

View File

@@ -0,0 +1,102 @@
#pragma once
#include <mln/annotation/annotation.hpp>
#include <mln/util/geo.hpp>
#include <string>
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point.hpp>
#include <boost/geometry/geometries/box.hpp>
#include <boost/geometry/geometries/register/point.hpp>
#include <boost/geometry/geometries/register/box.hpp>
#include <boost/geometry/index/rtree.hpp>
namespace mln {
class AnnotationTileLayer;
class CanonicalTileID;
class SymbolAnnotationImpl {
public:
SymbolAnnotationImpl(AnnotationID, SymbolAnnotation);
void updateLayer(const CanonicalTileID&, AnnotationTileLayer&) const;
const AnnotationID id;
const SymbolAnnotation annotation;
};
} // namespace mln
namespace boost {
namespace geometry {
// Make Boost Geometry aware of our LatLng type
namespace traits {
template <>
struct tag<mln::LatLng> {
using type = point_tag;
};
template <>
struct dimension<mln::LatLng> : boost::mpl::int_<2> {};
template <>
struct coordinate_type<mln::LatLng> {
using type = double;
};
template <>
struct coordinate_system<mln::LatLng> {
using type = boost::geometry::cs::cartesian;
};
template <>
struct access<mln::LatLng, 0> {
static inline double get(mln::LatLng const& p) { return p.longitude(); }
};
template <>
struct access<mln::LatLng, 1> {
static inline double get(mln::LatLng const& p) { return p.latitude(); }
};
template <>
struct tag<mln::LatLngBounds> {
using type = box_tag;
};
template <>
struct point_type<mln::LatLngBounds> {
using type = mln::LatLng;
};
template <size_t D>
struct indexed_access<mln::LatLngBounds, min_corner, D> {
using ct = coordinate_type<mln::LatLng>::type;
static inline ct get(mln::LatLngBounds const& b) { return geometry::get<D>(b.southwest()); }
static inline void set(mln::LatLngBounds& b, ct const& value) { geometry::set<D>(b.southwest(), value); }
};
template <size_t D>
struct indexed_access<mln::LatLngBounds, max_corner, D> {
using ct = coordinate_type<mln::LatLng>::type;
static inline ct get(mln::LatLngBounds const& b) { return geometry::get<D>(b.northeast()); }
static inline void set(mln::LatLngBounds& b, ct const& value) { geometry::set<D>(b.northeast(), value); }
};
} // namespace traits
// Tell Boost Geometry how to access a std::shared_ptr<mln::SymbolAnnotation> object.
namespace index {
template <>
struct indexable<std::shared_ptr<const mln::SymbolAnnotationImpl>> {
using result_type = mln::LatLng;
mln::LatLng operator()(const std::shared_ptr<const mln::SymbolAnnotationImpl>& v) const {
const mln::Point<double>& p = v->annotation.geometry;
return {p.y, p.x};
}
};
} // namespace index
} // namespace geometry
} // namespace boost

View File

@@ -0,0 +1,25 @@
#pragma once
#include <mln/util/geometry.hpp>
#include <utility>
#include <vector>
#include <optional>
namespace mln {
class Anchor {
public:
Point<float> point;
float angle = 0.0f;
std::optional<std::size_t> segment;
Anchor(float x_, float y_, float angle_, std::optional<std::size_t> segment_ = std::nullopt)
: point(x_, y_),
angle(angle_),
segment(std::move(segment_)) {}
};
using Anchors = std::vector<Anchor>;
} // namespace mln

View File

@@ -0,0 +1,349 @@
// This is an implementation file, so omit include guards.
#include <cstdint>
#include <map>
const int8_t simplex_1[] = {5, 21, 5, 7, -1, -1, 5, 2, 4, 1, 5, 0, 6, 1, 5, 2};
const int8_t simplex_2[] = {4, 21, 4, 14, -1, -1, 12, 21, 12, 14};
const int8_t simplex_3[] = {11, 25, 4, -7, -1, -1, 17, 25, 10, -7, -1, -1, 4, 12, 18, 12, -1, -1, 3, 6, 17, 6};
const int8_t simplex_4[] = {8, 25, 8, -4, -1, -1, 12, 25, 12, -4, -1, -1, 17, 18, 15, 20, 12, 21,
8, 21, 5, 20, 3, 18, 3, 16, 4, 14, 5, 13, 7, 12, 13, 10, 15, 9,
16, 8, 17, 6, 17, 3, 15, 1, 12, 0, 8, 0, 5, 1, 3, 3};
const int8_t simplex_5[] = {21, 21, 3, 0, -1, -1, 8, 21, 10, 19, 10, 17, 9, 15, 7, 14, 5, 14, 3, 16, 3,
18, 4, 20, 6, 21, 8, 21, 10, 20, 13, 19, 16, 19, 19, 20, 21, 21, -1, -1, 17, 7,
15, 6, 14, 4, 14, 2, 16, 0, 18, 0, 20, 1, 21, 3, 21, 5, 19, 7, 17, 7};
const int8_t simplex_6[] = {23, 12, 23, 13, 22, 14, 21, 14, 20, 13, 19, 11, 17, 6, 15, 3, 13, 1, 11, 0, 7, 0, 5,
1, 4, 2, 3, 4, 3, 6, 4, 8, 5, 9, 12, 13, 13, 14, 14, 16, 14, 18, 13, 20, 11, 21,
9, 20, 8, 18, 8, 16, 9, 13, 11, 10, 16, 3, 18, 1, 20, 0, 22, 0, 23, 1, 23, 2};
const int8_t simplex_7[] = {5, 19, 4, 20, 5, 21, 6, 20, 6, 18, 5, 16, 4, 15};
const int8_t simplex_8[] = {11, 25, 9, 23, 7, 20, 5, 16, 4, 11, 4, 7, 5, 2, 7, -2, 9, -5, 11, -7};
const int8_t simplex_9[] = {3, 25, 5, 23, 7, 20, 9, 16, 10, 11, 10, 7, 9, 2, 7, -2, 5, -5, 3, -7};
const int8_t simplex_10[] = {8, 21, 8, 9, -1, -1, 3, 18, 13, 12, -1, -1, 13, 18, 3, 12};
const int8_t simplex_11[] = {13, 18, 13, 0, -1, -1, 4, 9, 22, 9};
const int8_t simplex_12[] = {6, 1, 5, 0, 4, 1, 5, 2, 6, 1, 6, -1, 5, -3, 4, -4};
const int8_t simplex_13[] = {4, 9, 22, 9};
const int8_t simplex_14[] = {5, 2, 4, 1, 5, 0, 6, 1, 5, 2};
const int8_t simplex_15[] = {20, 25, 2, -7};
const int8_t simplex_16[] = {9, 21, 6, 20, 4, 17, 3, 12, 3, 9, 4, 4, 6, 1, 9, 0, 11,
0, 14, 1, 16, 4, 17, 9, 17, 12, 16, 17, 14, 20, 11, 21, 9, 21};
const int8_t simplex_17[] = {6, 17, 8, 18, 11, 21, 11, 0};
const int8_t simplex_18[] = {4, 16, 4, 17, 5, 19, 6, 20, 8, 21, 12, 21, 14, 20,
15, 19, 16, 17, 16, 15, 15, 13, 13, 10, 3, 0, 17, 0};
const int8_t simplex_19[] = {5, 21, 16, 21, 10, 13, 13, 13, 15, 12, 16, 11, 17, 8, 17,
6, 16, 3, 14, 1, 11, 0, 8, 0, 5, 1, 4, 2, 3, 4};
const int8_t simplex_20[] = {13, 21, 3, 7, 18, 7, -1, -1, 13, 21, 13, 0};
const int8_t simplex_21[] = {15, 21, 5, 21, 4, 12, 5, 13, 8, 14, 11, 14, 14, 13, 16, 11, 17,
8, 17, 6, 16, 3, 14, 1, 11, 0, 8, 0, 5, 1, 4, 2, 3, 4};
const int8_t simplex_22[] = {16, 18, 15, 20, 12, 21, 10, 21, 7, 20, 5, 17, 4, 12, 4, 7, 5, 3, 7, 1, 10, 0, 11,
0, 14, 1, 16, 3, 17, 6, 17, 7, 16, 10, 14, 12, 11, 13, 10, 13, 7, 12, 5, 10, 4, 7};
const int8_t simplex_23[] = {17, 21, 7, 0, -1, -1, 3, 21, 17, 21};
const int8_t simplex_24[] = {8, 21, 5, 20, 4, 18, 4, 16, 5, 14, 7, 13, 11, 12, 14, 11, 16, 9, 17, 7,
17, 4, 16, 2, 15, 1, 12, 0, 8, 0, 5, 1, 4, 2, 3, 4, 3, 7, 4, 9,
6, 11, 9, 12, 13, 13, 15, 14, 16, 16, 16, 18, 15, 20, 12, 21, 8, 21};
const int8_t simplex_25[] = {16, 14, 15, 11, 13, 9, 10, 8, 9, 8, 6, 9, 4, 11, 3, 14, 3, 15, 4, 18, 6, 20, 9,
21, 10, 21, 13, 20, 15, 18, 16, 14, 16, 9, 15, 4, 13, 1, 10, 0, 8, 0, 5, 1, 4, 3};
const int8_t simplex_26[] = {5, 14, 4, 13, 5, 12, 6, 13, 5, 14, -1, -1, 5, 2, 4, 1, 5, 0, 6, 1, 5, 2};
const int8_t simplex_27[] = {5, 14, 4, 13, 5, 12, 6, 13, 5, 14, -1, -1, 6, 1,
5, 0, 4, 1, 5, 2, 6, 1, 6, -1, 5, -3, 4, -4};
const int8_t simplex_28[] = {20, 18, 4, 9, 20, 0};
const int8_t simplex_29[] = {4, 12, 22, 12, -1, -1, 4, 6, 22, 6};
const int8_t simplex_30[] = {4, 18, 20, 9, 4, 0};
const int8_t simplex_31[] = {3, 16, 3, 17, 4, 19, 5, 20, 7, 21, 11, 21, 13, 20, 14, 19, 15, 17, 15, 15,
14, 13, 13, 12, 9, 10, 9, 7, -1, -1, 9, 2, 8, 1, 9, 0, 10, 1, 9, 2};
const int8_t simplex_32[] = {18, 13, 17, 15, 15, 16, 12, 16, 10, 15, 9, 14, 8, 11, 8, 8, 9, 6, 11, 5, 14, 5,
16, 6, 17, 8, -1, -1, 12, 16, 10, 14, 9, 11, 9, 8, 10, 6, 11, 5, -1, -1, 18, 16,
17, 8, 17, 6, 19, 5, 21, 5, 23, 7, 24, 10, 24, 12, 23, 15, 22, 17, 20, 19, 18, 20,
15, 21, 12, 21, 9, 20, 7, 19, 5, 17, 4, 15, 3, 12, 3, 9, 4, 6, 5, 4, 7, 2,
9, 1, 12, 0, 15, 0, 18, 1, 20, 2, 21, 3, -1, -1, 19, 16, 18, 8, 18, 6, 19, 5};
const int8_t simplex_33[] = {9, 21, 1, 0, -1, -1, 9, 21, 17, 0, -1, -1, 4, 7, 14, 7};
const int8_t simplex_34[] = {4, 21, 4, 0, -1, -1, 4, 21, 13, 21, 16, 20, 17, 19, 18, 17, 18, 15, 17, 13, 16, 12, 13,
11, -1, -1, 4, 11, 13, 11, 16, 10, 17, 9, 18, 7, 18, 4, 17, 2, 16, 1, 13, 0, 4, 0};
const int8_t simplex_35[] = {18, 16, 17, 18, 15, 20, 13, 21, 9, 21, 7, 20, 5, 18, 4, 16, 3, 13,
3, 8, 4, 5, 5, 3, 7, 1, 9, 0, 13, 0, 15, 1, 17, 3, 18, 5};
const int8_t simplex_36[] = {4, 21, 4, 0, -1, -1, 4, 21, 11, 21, 14, 20, 16, 18, 17,
16, 18, 13, 18, 8, 17, 5, 16, 3, 14, 1, 11, 0, 4, 0};
const int8_t simplex_37[] = {4, 21, 4, 0, -1, -1, 4, 21, 17, 21, -1, -1, 4, 11, 12, 11, -1, -1, 4, 0, 17, 0};
const int8_t simplex_38[] = {4, 21, 4, 0, -1, -1, 4, 21, 17, 21, -1, -1, 4, 11, 12, 11};
const int8_t simplex_39[] = {18, 16, 17, 18, 15, 20, 13, 21, 9, 21, 7, 20, 5, 18, 4, 16, 3, 13, 3, 8, 4, 5,
5, 3, 7, 1, 9, 0, 13, 0, 15, 1, 17, 3, 18, 5, 18, 8, -1, -1, 13, 8, 18, 8};
const int8_t simplex_40[] = {4, 21, 4, 0, -1, -1, 18, 21, 18, 0, -1, -1, 4, 11, 18, 11};
const int8_t simplex_41[] = {4, 21, 4, 0};
const int8_t simplex_42[] = {12, 21, 12, 5, 11, 2, 10, 1, 8, 0, 6, 0, 4, 1, 3, 2, 2, 5, 2, 7};
const int8_t simplex_43[] = {4, 21, 4, 0, -1, -1, 18, 21, 4, 7, -1, -1, 9, 12, 18, 0};
const int8_t simplex_44[] = {4, 21, 4, 0, -1, -1, 4, 0, 16, 0};
const int8_t simplex_45[] = {4, 21, 4, 0, -1, -1, 4, 21, 12, 0, -1, -1, 20, 21, 12, 0, -1, -1, 20, 21, 20, 0};
const int8_t simplex_46[] = {4, 21, 4, 0, -1, -1, 4, 21, 18, 0, -1, -1, 18, 21, 18, 0};
const int8_t simplex_47[] = {9, 21, 7, 20, 5, 18, 4, 16, 3, 13, 3, 8, 4, 5, 5, 3, 7, 1, 9, 0, 13,
0, 15, 1, 17, 3, 18, 5, 19, 8, 19, 13, 18, 16, 17, 18, 15, 20, 13, 21, 9, 21};
const int8_t simplex_48[] = {4, 21, 4, 0, -1, -1, 4, 21, 13, 21, 16, 20, 17,
19, 18, 17, 18, 14, 17, 12, 16, 11, 13, 10, 4, 10};
const int8_t simplex_49[] = {9, 21, 7, 20, 5, 18, 4, 16, 3, 13, 3, 8, 4, 5, 5, 3,
7, 1, 9, 0, 13, 0, 15, 1, 17, 3, 18, 5, 19, 8, 19, 13,
18, 16, 17, 18, 15, 20, 13, 21, 9, 21, -1, -1, 12, 4, 18, -2};
const int8_t simplex_50[] = {4, 21, 4, 0, -1, -1, 4, 21, 13, 21, 16, 20, 17, 19, 18, 17,
18, 15, 17, 13, 16, 12, 13, 11, 4, 11, -1, -1, 11, 11, 18, 0};
const int8_t simplex_51[] = {17, 18, 15, 20, 12, 21, 8, 21, 5, 20, 3, 18, 3, 16, 4, 14, 5, 13, 7, 12,
13, 10, 15, 9, 16, 8, 17, 6, 17, 3, 15, 1, 12, 0, 8, 0, 5, 1, 3, 3};
const int8_t simplex_52[] = {8, 21, 8, 0, -1, -1, 1, 21, 15, 21};
const int8_t simplex_53[] = {4, 21, 4, 6, 5, 3, 7, 1, 10, 0, 12, 0, 15, 1, 17, 3, 18, 6, 18, 21};
const int8_t simplex_54[] = {1, 21, 9, 0, -1, -1, 17, 21, 9, 0};
const int8_t simplex_55[] = {2, 21, 7, 0, -1, -1, 12, 21, 7, 0, -1, -1, 12, 21, 17, 0, -1, -1, 22, 21, 17, 0};
const int8_t simplex_56[] = {3, 21, 17, 0, -1, -1, 17, 21, 3, 0};
const int8_t simplex_57[] = {1, 21, 9, 11, 9, 0, -1, -1, 17, 21, 9, 11};
const int8_t simplex_58[] = {17, 21, 3, 0, -1, -1, 3, 21, 17, 21, -1, -1, 3, 0, 17, 0};
const int8_t simplex_59[] = {4, 25, 4, -7, -1, -1, 5, 25, 5, -7, -1, -1, 4, 25, 11, 25, -1, -1, 4, -7, 11, -7};
const int8_t simplex_60[] = {0, 21, 14, -3};
const int8_t simplex_61[] = {9, 25, 9, -7, -1, -1, 10, 25, 10, -7, -1, -1, 3, 25, 10, 25, -1, -1, 3, -7, 10, -7};
const int8_t simplex_62[] = {6, 15, 8, 18, 10, 15, -1, -1, 3, 12, 8, 17, 13, 12, -1, -1, 8, 17, 8, 0};
const int8_t simplex_63[] = {0, -2, 16, -2};
const int8_t simplex_64[] = {6, 21, 5, 20, 4, 18, 4, 16, 5, 15, 6, 16, 5, 17};
const int8_t simplex_65[] = {15, 14, 15, 0, -1, -1, 15, 11, 13, 13, 11, 14, 8, 14, 6, 13, 4,
11, 3, 8, 3, 6, 4, 3, 6, 1, 8, 0, 11, 0, 13, 1, 15, 3};
const int8_t simplex_66[] = {4, 21, 4, 0, -1, -1, 4, 11, 6, 13, 8, 14, 11, 14, 13, 13, 15,
11, 16, 8, 16, 6, 15, 3, 13, 1, 11, 0, 8, 0, 6, 1, 4, 3};
const int8_t simplex_67[] = {15, 11, 13, 13, 11, 14, 8, 14, 6, 13, 4, 11, 3, 8,
3, 6, 4, 3, 6, 1, 8, 0, 11, 0, 13, 1, 15, 3};
const int8_t simplex_68[] = {15, 21, 15, 0, -1, -1, 15, 11, 13, 13, 11, 14, 8, 14, 6, 13, 4,
11, 3, 8, 3, 6, 4, 3, 6, 1, 8, 0, 11, 0, 13, 1, 15, 3};
const int8_t simplex_69[] = {3, 8, 15, 8, 15, 10, 14, 12, 13, 13, 11, 14, 8, 14, 6, 13, 4,
11, 3, 8, 3, 6, 4, 3, 6, 1, 8, 0, 11, 0, 13, 1, 15, 3};
const int8_t simplex_70[] = {10, 21, 8, 21, 6, 20, 5, 17, 5, 0, -1, -1, 2, 14, 9, 14};
const int8_t simplex_71[] = {15, 14, 15, -2, 14, -5, 13, -6, 11, -7, 8, -7, 6, -6, -1, -1, 15, 11, 13, 13, 11, 14,
8, 14, 6, 13, 4, 11, 3, 8, 3, 6, 4, 3, 6, 1, 8, 0, 11, 0, 13, 1, 15, 3};
const int8_t simplex_72[] = {4, 21, 4, 0, -1, -1, 4, 10, 7, 13, 9, 14, 12, 14, 14, 13, 15, 10, 15, 0};
const int8_t simplex_73[] = {3, 21, 4, 20, 5, 21, 4, 22, 3, 21, -1, -1, 4, 14, 4, 0};
const int8_t simplex_74[] = {5, 21, 6, 20, 7, 21, 6, 22, 5, 21, -1, -1, 6, 14, 6, -3, 5, -6, 3, -7, 1, -7};
const int8_t simplex_75[] = {4, 21, 4, 0, -1, -1, 14, 14, 4, 4, -1, -1, 8, 8, 15, 0};
const int8_t simplex_76[] = {4, 21, 4, 0};
const int8_t simplex_77[] = {4, 14, 4, 0, -1, -1, 4, 10, 7, 13, 9, 14, 12, 14, 14, 13, 15, 10,
15, 0, -1, -1, 15, 10, 18, 13, 20, 14, 23, 14, 25, 13, 26, 10, 26, 0};
const int8_t simplex_78[] = {4, 14, 4, 0, -1, -1, 4, 10, 7, 13, 9, 14, 12, 14, 14, 13, 15, 10, 15, 0};
const int8_t simplex_79[] = {8, 14, 6, 13, 4, 11, 3, 8, 3, 6, 4, 3, 6, 1, 8, 0, 11,
0, 13, 1, 15, 3, 16, 6, 16, 8, 15, 11, 13, 13, 11, 14, 8, 14};
const int8_t simplex_80[] = {4, 14, 4, -7, -1, -1, 4, 11, 6, 13, 8, 14, 11, 14, 13, 13, 15,
11, 16, 8, 16, 6, 15, 3, 13, 1, 11, 0, 8, 0, 6, 1, 4, 3};
const int8_t simplex_81[] = {15, 14, 15, -7, -1, -1, 15, 11, 13, 13, 11, 14, 8, 14, 6, 13, 4,
11, 3, 8, 3, 6, 4, 3, 6, 1, 8, 0, 11, 0, 13, 1, 15, 3};
const int8_t simplex_82[] = {4, 14, 4, 0, -1, -1, 4, 8, 5, 11, 7, 13, 9, 14, 12, 14};
const int8_t simplex_83[] = {14, 11, 13, 13, 10, 14, 7, 14, 4, 13, 3, 11, 4, 9, 6, 8, 11,
7, 13, 6, 14, 4, 14, 3, 13, 1, 10, 0, 7, 0, 4, 1, 3, 3};
const int8_t simplex_84[] = {5, 21, 5, 4, 6, 1, 8, 0, 10, 0, -1, -1, 2, 14, 9, 14};
const int8_t simplex_85[] = {4, 14, 4, 4, 5, 1, 7, 0, 10, 0, 12, 1, 15, 4, -1, -1, 15, 14, 15, 0};
const int8_t simplex_86[] = {2, 14, 8, 0, -1, -1, 14, 14, 8, 0};
const int8_t simplex_87[] = {3, 14, 7, 0, -1, -1, 11, 14, 7, 0, -1, -1, 11, 14, 15, 0, -1, -1, 19, 14, 15, 0};
const int8_t simplex_88[] = {3, 14, 14, 0, -1, -1, 14, 14, 3, 0};
const int8_t simplex_89[] = {2, 14, 8, 0, -1, -1, 14, 14, 8, 0, 6, -4, 4, -6, 2, -7, 1, -7};
const int8_t simplex_90[] = {14, 14, 3, 0, -1, -1, 3, 14, 14, 14, -1, -1, 3, 0, 14, 0};
const int8_t simplex_91[] = {9, 25, 7, 24, 6, 23, 5, 21, 5, 19, 6, 17, 7, 16, 8, 14, 8, 12, 6, 10,
-1, -1, 7, 24, 6, 22, 6, 20, 7, 18, 8, 17, 9, 15, 9, 13, 8, 11, 4, 9,
8, 7, 9, 5, 9, 3, 8, 1, 7, 0, 6, -2, 6, -4, 7, -6, -1, -1, 6, 8,
8, 6, 8, 4, 7, 2, 6, 1, 5, -1, 5, -3, 6, -5, 7, -6, 9, -7};
const int8_t simplex_92[] = {4, 25, 4, -7};
const int8_t simplex_93[] = {5, 25, 7, 24, 8, 23, 9, 21, 9, 19, 8, 17, 7, 16, 6, 14, 6, 12, 8, 10,
-1, -1, 7, 24, 8, 22, 8, 20, 7, 18, 6, 17, 5, 15, 5, 13, 6, 11, 10, 9,
6, 7, 5, 5, 5, 3, 6, 1, 7, 0, 8, -2, 8, -4, 7, -6, -1, -1, 8, 8,
6, 6, 6, 4, 7, 2, 8, 1, 9, -1, 9, -3, 8, -5, 7, -6, 5, -7};
const int8_t simplex_94[] = {3, 6, 3, 8, 4, 11, 6, 12, 8, 12, 10, 11, 14, 8, 16, 7, 18, 7, 20, 8, 21, 10, -1,
-1, 3, 8, 4, 10, 6, 11, 8, 11, 10, 10, 14, 7, 16, 6, 18, 6, 20, 7, 21, 10, 21, 12};
struct glyph {
uint8_t width;
uint8_t length;
const int8_t *data;
};
// Font data From Hershey Simplex Font
// http://paulbourke.net/dataformats/hershey/
// NOLINTBEGIN(modernize-use-designated-initializers)
const glyph simplex[] = {
/* 32 */ {16, 0, nullptr},
/* 33 ! */
{10, sizeof(simplex_1), simplex_1},
/* 34 " */
{16, sizeof(simplex_2), simplex_2},
/* 35 # */
{21, sizeof(simplex_3), simplex_3},
/* 36 $ */
{20, sizeof(simplex_4), simplex_4},
/* 37 % */
{24, sizeof(simplex_5), simplex_5},
/* 38 & */
{26, sizeof(simplex_6), simplex_6},
/* 39 ' */
{10, sizeof(simplex_7), simplex_7},
/* 40 ( */
{14, sizeof(simplex_8), simplex_8},
/* 41 ) */
{14, sizeof(simplex_9), simplex_9},
/* 42 * */
{16, sizeof(simplex_10), simplex_10},
/* 43 + */
{26, sizeof(simplex_11), simplex_11},
/* 44 , */
{10, sizeof(simplex_12), simplex_12},
/* 45 - */
{26, sizeof(simplex_13), simplex_13},
/* 46 . */
{10, sizeof(simplex_14), simplex_14},
/* 47 / */
{22, sizeof(simplex_15), simplex_15},
/* 48 0 */
{20, sizeof(simplex_16), simplex_16},
/* 49 1 */
{20, sizeof(simplex_17), simplex_17},
/* 50 2 */
{20, sizeof(simplex_18), simplex_18},
/* 51 3 */
{20, sizeof(simplex_19), simplex_19},
/* 52 4 */
{20, sizeof(simplex_20), simplex_20},
/* 53 5 */
{20, sizeof(simplex_21), simplex_21},
/* 54 6 */
{20, sizeof(simplex_22), simplex_22},
/* 55 7 */
{20, sizeof(simplex_23), simplex_23},
/* 56 8 */
{20, sizeof(simplex_24), simplex_24},
/* 57 9 */
{20, sizeof(simplex_25), simplex_25},
/* 58 : */
{10, sizeof(simplex_26), simplex_26},
/* 59 ; */
{10, sizeof(simplex_27), simplex_27},
/* 60 < */
{24, sizeof(simplex_28), simplex_28},
/* 61 = */
{26, sizeof(simplex_29), simplex_29},
/* 62 > */
{24, sizeof(simplex_30), simplex_30},
/* 63 ? */
{18, sizeof(simplex_31), simplex_31},
/* 64 @ */
{27, sizeof(simplex_32), simplex_32},
/* 65 A */
{18, sizeof(simplex_33), simplex_33},
/* 66 B */
{21, sizeof(simplex_34), simplex_34},
/* 67 C */
{21, sizeof(simplex_35), simplex_35},
/* 68 D */
{21, sizeof(simplex_36), simplex_36},
/* 69 E */
{19, sizeof(simplex_37), simplex_37},
/* 70 F */
{18, sizeof(simplex_38), simplex_38},
/* 71 G */
{21, sizeof(simplex_39), simplex_39},
/* 72 H */
{22, sizeof(simplex_40), simplex_40},
/* 73 I */
{8, sizeof(simplex_41), simplex_41},
/* 74 J */
{16, sizeof(simplex_42), simplex_42},
/* 75 K */
{21, sizeof(simplex_43), simplex_43},
/* 76 L */
{17, sizeof(simplex_44), simplex_44},
/* 77 M */
{24, sizeof(simplex_45), simplex_45},
/* 78 N */
{22, sizeof(simplex_46), simplex_46},
/* 79 O */
{22, sizeof(simplex_47), simplex_47},
/* 80 P */
{21, sizeof(simplex_48), simplex_48},
/* 81 Q */
{22, sizeof(simplex_49), simplex_49},
/* 82 R */
{21, sizeof(simplex_50), simplex_50},
/* 83 S */
{20, sizeof(simplex_51), simplex_51},
/* 84 T */
{16, sizeof(simplex_52), simplex_52},
/* 85 U */
{22, sizeof(simplex_53), simplex_53},
/* 86 V */
{18, sizeof(simplex_54), simplex_54},
/* 87 W */
{24, sizeof(simplex_55), simplex_55},
/* 88 X */
{20, sizeof(simplex_56), simplex_56},
/* 89 Y */
{18, sizeof(simplex_57), simplex_57},
/* 90 Z */
{20, sizeof(simplex_58), simplex_58},
/* 91 [ */
{14, sizeof(simplex_59), simplex_59},
/* 92 \ */
{14, sizeof(simplex_60), simplex_60},
/* 93 ] */
{14, sizeof(simplex_61), simplex_61},
/* 94 ^ */
{16, sizeof(simplex_62), simplex_62},
/* 95 _ */
{16, sizeof(simplex_63), simplex_63},
/* 96 ` */
{10, sizeof(simplex_64), simplex_64},
/* 97 a */
{19, sizeof(simplex_65), simplex_65},
/* 98 b */
{19, sizeof(simplex_66), simplex_66},
/* 99 c */
{18, sizeof(simplex_67), simplex_67},
/* 100 d */
{19, sizeof(simplex_68), simplex_68},
/* 101 e */
{18, sizeof(simplex_69), simplex_69},
/* 102 f */
{12, sizeof(simplex_70), simplex_70},
/* 103 g */
{19, sizeof(simplex_71), simplex_71},
/* 104 h */
{19, sizeof(simplex_72), simplex_72},
/* 105 i */
{8, sizeof(simplex_73), simplex_73},
/* 106 j */
{10, sizeof(simplex_74), simplex_74},
/* 107 k */
{17, sizeof(simplex_75), simplex_75},
/* 108 l */
{8, sizeof(simplex_76), simplex_76},
/* 109 m */
{30, sizeof(simplex_77), simplex_77},
/* 110 n */
{19, sizeof(simplex_78), simplex_78},
/* 111 o */
{19, sizeof(simplex_79), simplex_79},
/* 112 p */
{19, sizeof(simplex_80), simplex_80},
/* 113 q */
{19, sizeof(simplex_81), simplex_81},
/* 114 r */
{13, sizeof(simplex_82), simplex_82},
/* 115 s */
{17, sizeof(simplex_83), simplex_83},
/* 116 t */
{12, sizeof(simplex_84), simplex_84},
/* 117 u */
{19, sizeof(simplex_85), simplex_85},
/* 118 v */
{16, sizeof(simplex_86), simplex_86},
/* 119 w */
{22, sizeof(simplex_87), simplex_87},
/* 120 x */
{17, sizeof(simplex_88), simplex_88},
/* 121 y */
{16, sizeof(simplex_89), simplex_89},
/* 122 z */
{17, sizeof(simplex_90), simplex_90},
/* 123 { */
{14, sizeof(simplex_91), simplex_91},
/* 124 | */
{8, sizeof(simplex_92), simplex_92},
/* 125 } */
{14, sizeof(simplex_93), simplex_93},
/* 126 ~ */
{24, sizeof(simplex_94), simplex_94},
};
// NOLINTEND(modernize-use-designated-initializers)

View File

@@ -0,0 +1,105 @@
#include <mln/geometry/dem_data.hpp>
#include <mln/math/clamp.hpp>
namespace mln {
DEMData::DEMData(const PremultipliedImage& _image, Tileset::RasterEncoding _encoding)
: dim(_image.size.height),
// extra two pixels per row for border backfilling on either edge
stride(dim + 2),
encoding(_encoding) {
image = std::make_shared<PremultipliedImage>(Size(static_cast<uint32_t>(stride), static_cast<uint32_t>(stride)));
if (_image.size.height != _image.size.width) {
throw std::runtime_error("raster-dem tiles must be square.");
}
auto* dest = reinterpret_cast<uint32_t*>(image->data.get()) + stride + 1;
auto* source = reinterpret_cast<uint32_t*>(_image.data.get());
for (int32_t y = 0; y < dim; y++) {
memcpy(dest, source, dim * 4);
dest += stride;
source += dim;
}
// in order to avoid flashing seams between tiles, here we are initially
// populating a 1px border of pixels around the image with the data of the
// nearest pixel from the image. this data is eventually replaced when the
// tile's neighboring tiles are loaded and the accurate data can be
// backfilled using DEMData#backfillBorder
auto* data = reinterpret_cast<uint32_t*>(image->data.get());
for (int32_t x = 0; x < dim; x++) {
auto rowOffset = stride * (x + 1);
// left vertical border
data[rowOffset] = data[rowOffset + 1];
// right vertical border
data[rowOffset + dim + 1] = data[rowOffset + dim];
}
// top horizontal border with corners
memcpy(data, data + stride, stride * 4);
// bottom horizontal border with corners
memcpy(data + (dim + 1) * stride, data + dim * stride, stride * 4);
}
// This function takes the DEMData from a neighboring tile and backfills the
// edge/corner data in order to create a one pixel "buffer" of image data around
// the tile. This is necessary because the hillshade formula calculates the
// dx/dz, dy/dz derivatives at each pixel of the tile by querying the 8
// surrounding pixels, and if we don't have the pixel buffer we get seams at
// tile boundaries.
void DEMData::backfillBorder(const DEMData& borderTileData, int8_t dx, int8_t dy) {
auto& o = borderTileData;
// Tiles from the same source should always be of the same dimensions.
assert(dim == o.dim);
// We determine the pixel range to backfill based which corner/edge
// `borderTileData` represents. For example, dx = -1, dy = -1 represents the
// upper left corner of the base tile, so we only need to backfill one pixel
// at coordinates (-1, -1) of the tile image.
int32_t xMin = dx * dim;
int32_t xMax = dx * dim + dim;
int32_t yMin = dy * dim;
int32_t yMax = dy * dim + dim;
if (dx == -1)
xMin = xMax - 1;
else if (dx == 1)
xMax = xMin + 1;
if (dy == -1)
yMin = yMax - 1;
else if (dy == 1)
yMax = yMin + 1;
int32_t ox = -dx * dim;
int32_t oy = -dy * dim;
auto* dest = reinterpret_cast<uint32_t*>(image->data.get());
auto* source = reinterpret_cast<uint32_t*>(o.image->data.get());
for (int32_t y = yMin; y < yMax; y++) {
for (int32_t x = xMin; x < xMax; x++) {
dest[idx(x, y)] = source[idx(x + ox, y + oy)];
}
}
}
int32_t DEMData::get(const int32_t x, const int32_t y) const {
const auto& unpack = getUnpackVector();
const uint8_t* value = image->data.get() + idx(x, y) * 4;
return static_cast<int32_t>(value[0] * unpack[0] + value[1] * unpack[1] + value[2] * unpack[2] - unpack[3]);
}
const std::array<float, 4>& DEMData::getUnpackVector() const {
// https://www.mapbox.com/help/access-elevation-data/#mapbox-terrain-rgb
static const std::array<float, 4> unpackMapbox = {{6553.6f, 25.6f, 0.1f, 10000.0f}};
// https://aws.amazon.com/public-datasets/terrain/
static const std::array<float, 4> unpackTerrarium = {{256.0f, 1.0f, 1.0f / 256.0f, 32768.0f}};
return encoding == Tileset::RasterEncoding::Terrarium ? unpackTerrarium : unpackMapbox;
}
} // namespace mln

View File

@@ -0,0 +1,41 @@
#pragma once
#include <mln/math/clamp.hpp>
#include <mln/util/image.hpp>
#include <mln/util/tileset.hpp>
#include <memory>
#include <array>
#include <cassert>
#include <vector>
namespace mln {
class DEMData {
public:
DEMData(const PremultipliedImage& image, Tileset::RasterEncoding encoding);
void backfillBorder(const DEMData& borderTileData, int8_t dx, int8_t dy);
int32_t get(int32_t x, int32_t y) const;
const std::array<float, 4>& getUnpackVector() const;
const PremultipliedImage* getImage() const { return &*image; }
const std::shared_ptr<PremultipliedImage>& getImagePtr() const { return image; }
const int32_t dim;
const int32_t stride;
const Tileset::RasterEncoding encoding;
private:
std::shared_ptr<PremultipliedImage> image;
size_t idx(const int32_t x, const int32_t y) const {
assert(x >= -1);
assert(x < dim + 1);
assert(y >= -1);
assert(y < dim + 1);
return (y + 1) * stride + (x + 1);
}
};
} // namespace mln

View File

@@ -0,0 +1,369 @@
#include <mln/geometry/feature_index.hpp>
#include <mln/math/minmax.hpp>
#include <mln/renderer/layers/render_symbol_layer.hpp>
#include <mln/renderer/query.hpp>
#include <mln/renderer/render_layer.hpp>
#include <mln/renderer/source_state.hpp>
#include <mln/style/filter.hpp>
#include <mln/text/collision_index.hpp>
#include <mln/tile/tile_id.hpp>
#include <mln/util/constants.hpp>
#include <mln/util/geometry_util.hpp>
#include <mln/util/math.hpp>
#include <mln/util/projection.hpp>
#include <mapbox/geometry/envelope.hpp>
#include <cassert>
#include <string>
namespace {
mln::LatLng screenCoordinateToLatLng(mln::ScreenCoordinate point,
const mln::TransformState& state,
mln::LatLng::WrapMode wrapMode = mln::LatLng::Wrapped) {
point.y = state.getSize().height - point.y;
return state.screenCoordinateToLatLng(point, wrapMode);
}
mln::Point<double> project(const mln::LatLng& coordinate, const mln::TransformState& state) {
mln::LatLng unwrappedLatLng = coordinate.wrapped();
unwrappedLatLng.unwrapForShortestPath(state.getLatLng(mln::LatLng::Wrapped));
return mln::Projection::project(unwrappedLatLng, state.getScale());
}
} // namespace
namespace mln {
constexpr auto expectedUniqueLayerIDs = 10;
constexpr auto expectedUniqueLeaderIDs = 30;
RefIndexedSubfeature::RefIndexedSubfeature(std::size_t index_,
const std::string& sourceLayerName_,
const std::string& bucketName_,
size_t sortIndex_,
uint32_t bucketInstanceId_,
uint16_t collisionGroupId_)
: index(index_),
sortIndex(sortIndex_),
sourceLayerName(sourceLayerName_),
bucketLeaderID(bucketName_),
bucketInstanceId(bucketInstanceId_),
collisionGroupId(collisionGroupId_) {}
IndexedSubfeature::IndexedSubfeature(const RefIndexedSubfeature& other)
: IndexedSubfeature(other, other.getBucketInstanceId(), other.getCollisionGroupId()) {}
IndexedSubfeature::IndexedSubfeature(const RefIndexedSubfeature& other,
uint32_t bucketInstanceId_,
uint16_t collisionGroupId_)
: IndexedSubfeature(other.getIndex(),
other.getSourceLayerName(),
other.getBucketLeaderID(),
other.getSortIndex(),
bucketInstanceId_,
collisionGroupId_) {}
IndexedSubfeature::IndexedSubfeature(const IndexedSubfeature& other)
: IndexedSubfeature(other.getIndex(),
other.sourceLayerName,
other.bucketLeaderID,
other.getSortIndex(),
other.getBucketInstanceId(),
other.getCollisionGroupId()) {}
IndexedSubfeature::IndexedSubfeature(IndexedSubfeature&& other)
: IndexedSubfeature(other.getIndex(),
std::move(other.sourceLayerName),
std::move(other.bucketLeaderID),
other.getSortIndex(),
other.getBucketInstanceId(),
other.getCollisionGroupId()) {}
IndexedSubfeature::IndexedSubfeature(std::size_t index_,
std::string sourceLayerName_,
std::string bucketName_,
size_t sortIndex_,
uint32_t bucketInstanceId_,
uint16_t collisionGroupId_)
: RefIndexedSubfeature(index_,
this->sourceLayerNameCopy,
this->bucketLeaderIDCopy,
sortIndex_,
bucketInstanceId_,
collisionGroupId_),
sourceLayerNameCopy(std::move(sourceLayerName_)),
bucketLeaderIDCopy(std::move(bucketName_)) {}
IndexedSubfeature& IndexedSubfeature::operator=(const IndexedSubfeature& other) {
index = other.index;
sourceLayerName = sourceLayerNameCopy;
bucketLeaderID = bucketLeaderIDCopy;
sortIndex = other.sortIndex;
bucketInstanceId = other.bucketInstanceId;
collisionGroupId = other.collisionGroupId;
sourceLayerNameCopy = other.sourceLayerNameCopy;
bucketLeaderIDCopy = other.bucketLeaderIDCopy;
return *this;
}
IndexedSubfeature& IndexedSubfeature::operator=(IndexedSubfeature&& other) {
index = other.index;
sourceLayerName = sourceLayerNameCopy;
bucketLeaderID = bucketLeaderIDCopy;
sortIndex = other.sortIndex;
bucketInstanceId = other.bucketInstanceId;
collisionGroupId = other.collisionGroupId;
sourceLayerNameCopy = std::move(other.sourceLayerNameCopy);
bucketLeaderIDCopy = std::move(other.bucketLeaderIDCopy);
return *this;
}
FeatureIndex::FeatureIndex(std::unique_ptr<const GeometryTileData> tileData_)
: grid(util::EXTENT, util::EXTENT, util::EXTENT / 16), // 16x16 grid -> 32px cell
tileData(std::move(tileData_)) {}
void FeatureIndex::insert(const GeometryCollection& geometries,
std::size_t index,
const std::string& sourceLayerName,
const std::string& bucketLeaderID) {
if (uniqueLayerIDs.empty()) {
uniqueLayerIDs.reserve(expectedUniqueLayerIDs);
}
const std::string& emplacedLayerName = *uniqueLayerIDs.insert(sourceLayerName).first;
if (bucketLayerIDs.empty()) {
bucketLayerIDs.reserve(expectedUniqueLeaderIDs);
}
const std::string& emplacedLeaderID =
bucketLayerIDs.insert(std::make_pair(bucketLeaderID, std::vector<std::string>{})).first->first;
auto featureSortIndex = sortIndex++;
for (const auto& ring : geometries) {
const auto envelope = mapbox::geometry::envelope(ring);
if (envelope.min.x < util::EXTENT && envelope.min.y < util::EXTENT && envelope.max.x >= 0 &&
envelope.max.y >= 0) {
grid.insert(RefIndexedSubfeature(index, emplacedLayerName, emplacedLeaderID, featureSortIndex),
{convertPoint<float>(envelope.min), convertPoint<float>(envelope.max)});
}
}
}
void FeatureIndex::query(std::unordered_map<std::string, std::vector<Feature>>& result,
const GeometryCoordinates& queryGeometry,
const TransformState& transformState,
const mat4& posMatrix,
const double tileSize,
const double scale,
const RenderedQueryOptions& queryOptions,
const UnwrappedTileID& tileID,
const std::unordered_map<std::string, const RenderLayer*>& layers,
const float additionalQueryPadding,
const SourceFeatureState& sourceFeatureState) const {
if (!tileData) {
return;
}
// Determine query radius
const auto pixelsToTileUnits = static_cast<float>(util::EXTENT / tileSize / scale);
const int16_t additionalPadding = static_cast<int16_t>(
std::min(static_cast<float>(util::EXTENT), additionalQueryPadding * pixelsToTileUnits));
// Query the grid index
mapbox::geometry::box<int16_t> box = mapbox::geometry::envelope(queryGeometry);
std::vector<RefIndexedSubfeature> features = grid.query(
{convertPoint<float>(box.min - additionalPadding), convertPoint<float>(box.max + additionalPadding)});
std::ranges::sort(features, [](const RefIndexedSubfeature& a, const RefIndexedSubfeature& b) {
return a.getSortIndex() > b.getSortIndex();
});
size_t previousSortIndex = std::numeric_limits<size_t>::max();
for (const auto& indexedFeature : features) {
// If this feature is the same as the previous feature, skip it.
if (indexedFeature.getSortIndex() == previousSortIndex) continue;
previousSortIndex = indexedFeature.getSortIndex();
addFeature(result,
indexedFeature,
queryOptions,
tileID.canonical,
layers,
queryGeometry,
transformState,
pixelsToTileUnits,
posMatrix,
&sourceFeatureState);
}
}
std::unordered_map<std::string, std::vector<Feature>> FeatureIndex::lookupSymbolFeatures(
const std::vector<IndexedSubfeature>& symbolFeatures,
const RenderedQueryOptions& queryOptions,
const std::unordered_map<std::string, const RenderLayer*>& layers,
const OverscaledTileID& tileID,
const FeatureSortOrder& featureSortOrder) const {
std::unordered_map<std::string, std::vector<Feature>> result;
if (!tileData) {
return result;
}
std::vector<std::reference_wrapper<const RefIndexedSubfeature>> sortedFeatures(symbolFeatures.begin(),
symbolFeatures.end());
std::ranges::sort(sortedFeatures, [featureSortOrder](const RefIndexedSubfeature& a, const RefIndexedSubfeature& b) {
// Same idea as the non-symbol sort order, but symbol features may
// have changed their sort order since their corresponding
// IndexedSubfeature was added to the CollisionIndex The
// 'featureSortOrder' is relatively inefficient for querying but
// cheap to build on every bucket sort
if (featureSortOrder) {
// queryRenderedSymbols documentation says we'll return features in
// "top-to-bottom" rendering order (aka last-to-first).
// Actually there can be multiple symbol instances per feature, so
// we sort each feature based on the first matching symbol instance.
auto sortedA = std::find(featureSortOrder->begin(), featureSortOrder->end(), a.getIndex());
auto sortedB = std::find(featureSortOrder->begin(), featureSortOrder->end(), b.getIndex());
assert(sortedA != featureSortOrder->end());
assert(sortedB != featureSortOrder->end());
return sortedA > sortedB;
} else {
// Bucket hasn't been re-sorted based on angle, so use same
// "reverse of appearance in source data" logic as non-symboles
return a.getSortIndex() > b.getSortIndex();
}
});
for (const auto& symbolFeature : sortedFeatures) {
mat4 unusedMatrix;
addFeature(result,
symbolFeature,
queryOptions,
tileID.canonical,
layers,
GeometryCoordinates(),
{},
0,
unusedMatrix,
nullptr);
}
return result;
}
void FeatureIndex::addFeature(std::unordered_map<std::string, std::vector<Feature>>& result,
const RefIndexedSubfeature& indexedFeature,
const RenderedQueryOptions& options,
const CanonicalTileID& tileID,
const std::unordered_map<std::string, const RenderLayer*>& layers,
const GeometryCoordinates& queryGeometry,
const TransformState& transformState,
const float pixelsToTileUnits,
const mat4& posMatrix,
const SourceFeatureState* sourceFeatureState) const {
// Lazily calculated.
std::unique_ptr<GeometryTileLayer> sourceLayer;
std::unique_ptr<GeometryTileFeature> geometryTileFeature;
for (const std::string& layerID : bucketLayerIDs.at(indexedFeature.getBucketLeaderID())) {
const auto it = layers.find(layerID);
if (it == layers.end()) {
continue;
}
const RenderLayer* renderLayer = it->second;
if (!geometryTileFeature) {
sourceLayer = tileData->getLayer(indexedFeature.getSourceLayerName());
assert(sourceLayer);
geometryTileFeature = sourceLayer->getFeature(indexedFeature.getIndex());
assert(geometryTileFeature);
}
FeatureState state;
if (sourceFeatureState != nullptr) {
std::optional<std::string> idStr = featureIDtoString(geometryTileFeature->getID());
if (idStr) {
sourceFeatureState->getState(state, sourceLayer->getName(), *idStr);
}
}
bool needsCrossTileIndex = renderLayer->baseImpl->getTypeInfo()->crossTileIndex ==
style::LayerTypeInfo::CrossTileIndex::Required;
if (!needsCrossTileIndex &&
!renderLayer->queryIntersectsFeature(
queryGeometry, *geometryTileFeature, tileID.z, transformState, pixelsToTileUnits, posMatrix, state)) {
continue;
}
if (options.filter && !(*options.filter)(style::expression::EvaluationContext{static_cast<float>(tileID.z),
geometryTileFeature.get()})) {
continue;
}
Feature feature = convertFeature(*geometryTileFeature, tileID);
feature.source = renderLayer->baseImpl->source;
feature.sourceLayer = sourceLayer->getName();
feature.state = state;
result[layerID].emplace_back(feature);
}
}
std::optional<GeometryCoordinates> FeatureIndex::translateQueryGeometry(const GeometryCoordinates& queryGeometry,
const std::array<float, 2>& translate,
const style::TranslateAnchorType anchorType,
const float bearing,
const float pixelsToTileUnits) {
if (translate[0] == 0 && translate[1] == 0) {
return {};
}
GeometryCoordinate translateVec(static_cast<int16_t>(translate[0] * pixelsToTileUnits),
static_cast<int16_t>(translate[1] * pixelsToTileUnits));
if (anchorType == style::TranslateAnchorType::Viewport) {
translateVec = util::rotate(translateVec, -bearing);
}
GeometryCoordinates translated;
translated.reserve(queryGeometry.size());
for (const auto& p : queryGeometry) {
translated.push_back(p - translateVec);
}
return translated;
}
void FeatureIndex::setBucketLayerIDs(const std::string& bucketLeaderID, const std::vector<std::string>& layerIDs) {
if (bucketLayerIDs.empty()) {
bucketLayerIDs.reserve(expectedUniqueLeaderIDs);
}
bucketLayerIDs[bucketLeaderID] = layerIDs;
}
DynamicFeatureIndex::~DynamicFeatureIndex() = default;
void DynamicFeatureIndex::query(std::unordered_map<std::string, std::vector<Feature>>& result,
const mln::ScreenLineString& queryGeometry,
const TransformState& state) const {
if (features.empty()) return;
mln::GeometryBBox<int64_t> queryBox = DefaultWithinBBox;
for (const auto& p : queryGeometry) {
const LatLng c = screenCoordinateToLatLng(p, state);
const Point<double> pm = project(c, state);
const Point<int64_t> coord = {int64_t(pm.x), int64_t(pm.y)};
mln::updateBBox(queryBox, coord);
}
for (const auto& f : features) {
// hit testing
mln::GeometryBBox<int64_t> featureBox = DefaultWithinBBox;
for (const auto& p : f.envelope->front()) mln::updateBBox(featureBox, p);
const bool hit = mln::boxWithinBox(featureBox, queryBox) || mln::boxWithinBox(queryBox, featureBox);
if (hit) {
assert(f.feature);
result[f.feature->sourceLayer].push_back(*f.feature);
}
}
}
void DynamicFeatureIndex::insert(std::shared_ptr<Feature> feature,
std::shared_ptr<mapbox::geometry::polygon<int64_t>> envelope) {
features.push_back({std::move(feature), std::move(envelope)});
}
} // namespace mln

View File

@@ -0,0 +1,171 @@
#pragma once
#include <mln/style/types.hpp>
#include <mln/tile/geometry_tile_data.hpp>
#include <mln/tile/tile_id.hpp>
#include <mln/util/feature.hpp>
#include <mln/util/geo.hpp>
#include <mln/util/grid_index.hpp>
#include <mln/util/mat4.hpp>
#include <vector>
#include <string>
#include <unordered_map>
namespace mln {
class RenderedQueryOptions;
class RenderLayer;
class TransformState;
class SourceFeatureState;
class CollisionIndex;
/// An indexed element within a feature index.
/// This version holds a reference to the strings within the index itself to avoid making tens of thousands of
/// unnecessary copies of them. As a result, its lifetime cannot exceed that of the index.
class RefIndexedSubfeature {
public:
RefIndexedSubfeature() = delete;
RefIndexedSubfeature(RefIndexedSubfeature&&) = default;
RefIndexedSubfeature(const RefIndexedSubfeature&) = default;
RefIndexedSubfeature(std::size_t index_,
const std::string& sourceLayerName_,
const std::string& bucketName_,
size_t sortIndex_,
uint32_t bucketInstanceId_ = 0,
uint16_t collisionGroupId_ = 0);
RefIndexedSubfeature& operator=(const RefIndexedSubfeature&) = default;
RefIndexedSubfeature& operator=(RefIndexedSubfeature&&) = default;
size_t getIndex() const { return index; }
size_t getSortIndex() const { return sortIndex; }
const std::string& getSourceLayerName() const { return sourceLayerName.get(); }
const std::string& getBucketLeaderID() const { return bucketLeaderID.get(); }
uint32_t getBucketInstanceId() const { return bucketInstanceId; }
uint16_t getCollisionGroupId() const { return collisionGroupId; }
protected:
size_t index;
size_t sortIndex;
std::reference_wrapper<const std::string> sourceLayerName;
std::reference_wrapper<const std::string> bucketLeaderID;
// Only used for symbol features
uint32_t bucketInstanceId;
uint16_t collisionGroupId;
};
/// This version of an indexed feature has copies of the necessary strings, so that it can be used outside the index.
class IndexedSubfeature : public RefIndexedSubfeature {
public:
IndexedSubfeature(const RefIndexedSubfeature&);
IndexedSubfeature(const RefIndexedSubfeature&, uint32_t bucketInstanceId_, uint16_t collisionGroupId_);
IndexedSubfeature(const IndexedSubfeature&);
IndexedSubfeature(IndexedSubfeature&&);
IndexedSubfeature(std::size_t index_,
std::string sourceLayerName_,
std::string bucketName_,
size_t sortIndex_,
uint32_t bucketInstanceId_ = 0,
uint16_t collisionGroupId_ = 0);
IndexedSubfeature& operator=(const IndexedSubfeature&);
IndexedSubfeature& operator=(IndexedSubfeature&&);
private:
std::string sourceLayerNameCopy;
std::string bucketLeaderIDCopy;
};
using FeatureSortOrder = std::shared_ptr<const std::vector<size_t>>;
class DynamicFeatureIndex {
public:
~DynamicFeatureIndex();
void query(std::unordered_map<std::string, std::vector<Feature>>& result,
const mln::ScreenLineString& queryGeometry,
const TransformState& state) const;
void insert(std::shared_ptr<Feature> feature, std::shared_ptr<mapbox::geometry::polygon<int64_t>> envelope);
protected:
struct FeatureRecord {
std::shared_ptr<Feature> feature;
std::shared_ptr<mapbox::geometry::polygon<int64_t>> envelope;
};
std::vector<FeatureRecord> features;
};
class FeatureIndex {
public:
FeatureIndex(std::unique_ptr<const GeometryTileData> tileData_);
const GeometryTileData* getData() { return tileData.get(); }
/// Set the expected number of elements per cell to avoid small re-allocations for populated cells
void reserve(std::size_t value) { grid.reserve(value); }
void insert(const GeometryCollection&,
std::size_t index,
const std::string& sourceLayerName,
const std::string& bucketLeaderID);
void query(std::unordered_map<std::string, std::vector<Feature>>& result,
const GeometryCoordinates& queryGeometry,
const TransformState&,
const mat4& posMatrix,
double tileSize,
double scale,
const RenderedQueryOptions& options,
const UnwrappedTileID&,
const std::unordered_map<std::string, const RenderLayer*>&,
float additionalQueryPadding,
const SourceFeatureState& sourceFeatureState) const;
static std::optional<GeometryCoordinates> translateQueryGeometry(const GeometryCoordinates& queryGeometry,
const std::array<float, 2>& translate,
style::TranslateAnchorType,
float bearing,
float pixelsToTileUnits);
void setBucketLayerIDs(const std::string& bucketLeaderID, const std::vector<std::string>& layerIDs);
std::unordered_map<std::string, std::vector<Feature>> lookupSymbolFeatures(
const std::vector<IndexedSubfeature>& symbolFeatures,
const RenderedQueryOptions& options,
const std::unordered_map<std::string, const RenderLayer*>& layers,
const OverscaledTileID& tileID,
const FeatureSortOrder& featureSortOrder) const;
private:
void addFeature(std::unordered_map<std::string, std::vector<Feature>>& result,
const RefIndexedSubfeature&,
const RenderedQueryOptions& options,
const CanonicalTileID&,
const std::unordered_map<std::string, const RenderLayer*>&,
const GeometryCoordinates& queryGeometry,
const TransformState& transformState,
float pixelsToTileUnits,
const mat4& posMatrix,
const SourceFeatureState* sourceFeatureState) const;
GridIndex<RefIndexedSubfeature> grid;
unsigned int sortIndex = 0;
// mln::unordered_* cannot be used here, as we rely on holding references to elements:
// 22.2.7 Unordered associative containers [unord.req]
// The insert and emplace members shall not affect the validity of references to
// container elements, but may invalidate all iterators to the container.
std::unordered_map<std::string, std::vector<std::string>> bucketLayerIDs;
std::unordered_set<std::string> uniqueLayerIDs;
std::unique_ptr<const GeometryTileData> tileData;
};
} // namespace mln

View File

@@ -0,0 +1,264 @@
#include <cmath>
#include <mln/geometry/line_atlas.hpp>
#include <mln/gfx/upload_pass.hpp>
#include <mln/gfx/context.hpp>
#include <mln/math/log2.hpp>
#include <mln/math/minmax.hpp>
#include <mln/util/hash.hpp>
#include <mln/util/logging.hpp>
#include <mln/util/platform.hpp>
namespace mln {
namespace {
size_t getDashPatternHash(const std::vector<float>& dasharray, const LinePatternCap patternCap) {
size_t key = patternCap == LinePatternCap::Round ? std::numeric_limits<size_t>::min()
: std::numeric_limits<size_t>::max();
for (const float part : dasharray) {
util::hash_combine<float>(key, part);
}
return key;
}
std::vector<DashRange> getDashRanges(const std::vector<float>& dasharray, float stretch) {
// If dasharray has an odd length, both the first and last parts
// are dashes and should be joined seamlessly.
const bool oddDashArray = dasharray.size() % 2 == 1;
float left = oddDashArray ? -dasharray.back() * stretch : 0.0f;
float right = dasharray.front() * stretch;
bool isDash = true;
std::vector<DashRange> ranges;
ranges.reserve(dasharray.size());
ranges.push_back({left, right, isDash, dasharray.front() == 0.0f});
float currentDashLength = dasharray.front();
for (size_t i = 1; i < dasharray.size(); ++i) {
isDash = !isDash;
const float dashLength = dasharray[i];
left = currentDashLength * stretch;
currentDashLength += dashLength;
right = currentDashLength * stretch;
ranges.push_back({left, right, isDash, dashLength == 0.0f});
}
return ranges;
}
void addRoundDash(
const std::vector<DashRange>& ranges, uint32_t yOffset, float stretch, const int n, AlphaImage& image) {
const float halfStretch = stretch * 0.5f;
if (ranges.empty()) return;
for (int y = -n; y <= n; y++) {
int row = static_cast<int32_t>(yOffset) + n + y;
const uint32_t index = image.size.width * row;
uint32_t currIndex = 0;
DashRange range = ranges[currIndex];
for (uint32_t x = 0; x < image.size.width; ++x) {
if (range.right == 0) {
if (x != 0 && ++currIndex < ranges.size()) {
range = ranges[currIndex];
}
} else if (x / range.right > 1.0f && ++currIndex < ranges.size()) {
range = ranges[currIndex];
}
float distLeft = fabsf(x - range.left);
float distRight = fabsf(x - range.right);
float minDist = fminf(distLeft, distRight);
float signedDistance;
float distMiddle = static_cast<float>(y) / n * (halfStretch + 1.0f);
if (range.isDash) {
float distEdge = halfStretch - fabsf(distMiddle);
signedDistance = sqrtf(minDist * minDist + distEdge * distEdge);
} else {
signedDistance = halfStretch - sqrtf(minDist * minDist + distMiddle * distMiddle);
}
image.data[index + x] = static_cast<uint8_t>(fmaxf(0.0f, fminf(255.0f, signedDistance + 128.0f)));
}
}
}
void addRegularDash(std::vector<DashRange>& ranges, uint32_t yOffset, AlphaImage& image) {
// Collapse any zero-length range
for (auto it = ranges.begin(); it != ranges.end();) {
if (it->isZeroLength) {
it = ranges.erase(it);
} else {
++it;
}
}
if (ranges.empty()) return;
if (ranges.size() > 1) {
// Collapse neighbouring same-type parts into a single part
for (auto curr = ranges.begin(), next = ranges.begin() + 1; next != ranges.end();) {
if (next->isDash == curr->isDash) {
next->left = curr->left;
curr = ranges.erase(curr);
} else {
++curr;
}
next = curr + 1;
}
}
DashRange& first = ranges.front();
DashRange& last = ranges.back();
if (first.isDash == last.isDash) {
first.left = last.left - image.size.width;
last.right = first.right + image.size.width;
}
const uint32_t index = image.size.width * yOffset;
uint32_t currIndex = 0;
DashRange range = ranges[currIndex];
for (uint32_t x = 0; x < image.size.width; ++x) {
if (x / range.right > 1.0f && ++currIndex < ranges.size()) {
range = ranges[currIndex];
}
float distLeft = fabsf(x - range.left);
float distRight = fabsf(x - range.right);
float minDist = fminf(distLeft, distRight);
float signedDistance = range.isDash ? minDist : -minDist;
image.data[index + x] = static_cast<uint8_t>(fmaxf(0.0f, fminf(255.0f, signedDistance + 128.0f)));
}
}
LinePatternPos addDashPattern(AlphaImage& image,
uint32_t yOffset,
const std::vector<float>& dasharray,
const LinePatternCap patternCap) {
const uint8_t n = patternCap == LinePatternCap::Round ? 7 : 0;
if (dasharray.size() < 2) {
Log::Warning(Event::ParseStyle, "line dasharray requires at least two elements");
return {};
}
float length = 0;
for (const float part : dasharray) {
length += part;
}
float stretch = image.size.width / length;
std::vector<DashRange> ranges = getDashRanges(dasharray, stretch);
if (patternCap == LinePatternCap::Round) {
addRoundDash(ranges, yOffset, stretch, n, image);
} else {
addRegularDash(ranges, yOffset, image);
}
LinePatternPos position;
position.y = (0.5f + yOffset + n) / image.size.height;
position.height = (2.0f * n + 1) / image.size.height;
position.width = length;
return position;
}
} // namespace
DashPatternTexture::DashPatternTexture(const std::vector<float>& from_,
const std::vector<float>& to_,
const LinePatternCap cap) {
const bool patternsIdentical = from_ == to_;
const uint32_t patternHeight = cap == LinePatternCap::Round ? 15 : 1;
const uint32_t height = (patternsIdentical ? 1 : 2) * patternHeight;
// The OpenGL ES 2.0 spec, section 3.8.2 states:
//
// Calling a sampler from a fragment shader will return (R,G,B,A) =
// (0,0,0,1) if any of the following conditions are true: […]
// - A two-dimensional sampler is called, the corresponding texture
// image is a
// non-power-of-two image […], and either the texture wrap mode is not
// CLAMP_TO_EDGE, or the minification filter is neither NEAREST nor
// LINEAR.
// […]
//
// This means that texture lookups won't work for NPOT textures unless they
// use GL_CLAMP_TO_EDGE. We're using GL_CLAMP_TO_EDGE for the vertical
// direction, but GL_REPEAT for the horizontal direction, which means that
// we need a power-of-two texture for our line dash patterns to work on
// OpenGL ES 2.0 conforming implementations. Fortunately, this just means
// changing the height from 15 to 16, and from 30 to 32, so we don't waste
// many pixels.
const uint32_t textureHeight = 1 << util::ceil_log2(height);
AlphaImage image({256, textureHeight});
from = addDashPattern(image, 0, from_, cap);
to = patternsIdentical ? from : addDashPattern(image, patternHeight, to_, cap);
texture = std::move(image);
}
void DashPatternTexture::upload(gfx::UploadPass& uploadPass) {
if (std::holds_alternative<AlphaImage>(texture)) {
auto tempTexture = uploadPass.getContext().createTexture2D();
tempTexture->upload(std::get<AlphaImage>(texture));
tempTexture->setSamplerConfiguration({.filter = gfx::TextureFilterType::Linear,
.wrapU = gfx::TextureWrapType::Repeat,
.wrapV = gfx::TextureWrapType::Clamp});
texture = std::move(tempTexture);
}
}
static const gfx::Texture2DPtr noTexture;
const std::shared_ptr<gfx::Texture2D>& DashPatternTexture::getTexture() const {
return (std::holds_alternative<gfx::Texture2DPtr>(texture)) ? std::get<gfx::Texture2DPtr>(texture) : noTexture;
}
Size DashPatternTexture::getSize() const {
if (std::holds_alternative<AlphaImage>(texture)) {
return std::get<AlphaImage>(texture).size;
}
return std::get<gfx::Texture2DPtr>(texture)->getSize();
}
LineAtlas::LineAtlas() = default;
LineAtlas::~LineAtlas() = default;
DashPatternTexture& LineAtlas::getDashPatternTexture(const std::vector<float>& from,
const std::vector<float>& to,
const LinePatternCap cap) {
const size_t hash = util::hash(getDashPatternHash(from, cap), getDashPatternHash(to, cap));
// Note: We're not handling hash collisions here.
const auto it = textures.find(hash);
if (it == textures.end()) {
auto inserted = textures.emplace(
std::piecewise_construct, std::forward_as_tuple(hash), std::forward_as_tuple(from, to, cap));
assert(inserted.second);
needsUpload.emplace_back(hash);
return inserted.first->second;
} else {
return it->second;
}
}
void LineAtlas::upload(gfx::UploadPass& uploadPass) {
for (const size_t hash : needsUpload) {
const auto it = textures.find(hash);
if (it != textures.end()) {
it->second.upload(uploadPass);
}
}
needsUpload.clear();
}
} // namespace mln

View File

@@ -0,0 +1,83 @@
#pragma once
#include <mln/gfx/context.hpp>
#include <mln/util/image.hpp>
#include <mln/gfx/texture2d.hpp>
#include <variant>
#include <map>
#include <memory>
#include <vector>
namespace mln {
namespace gfx {
class UploadPass;
} // namespace gfx
class LinePatternPos {
public:
float width;
float height;
float y;
};
enum class LinePatternCap : bool {
Square = false,
Round = true,
};
struct DashRange {
float left;
float right;
bool isDash;
bool isZeroLength;
};
class DashPatternTexture {
public:
DashPatternTexture(const std::vector<float>& from, const std::vector<float>& to, LinePatternCap);
// Uploads the texture to the GPU to be available when we need it. This is a
// lazy operation; the texture is only bound when the data is uploaded for
// the first time.
void upload(gfx::UploadPass&);
// Binds the atlas texture to the GPU, and uploads data if it is out of date.
const std::shared_ptr<gfx::Texture2D>& getTexture() const;
// Returns the size of the texture image.
Size getSize() const;
const LinePatternPos& getFrom() const { return from; }
const LinePatternPos& getTo() const { return to; }
private:
LinePatternPos from, to;
std::variant<AlphaImage, gfx::Texture2DPtr> texture;
};
class LineAtlas {
public:
LineAtlas();
~LineAtlas();
// Obtains or creates a texture that has both line patterns in it
DashPatternTexture& getDashPatternTexture(const std::vector<float>& from,
const std::vector<float>& to,
LinePatternCap);
// Uploads the textures to the GPU to be available when we need it.
void upload(gfx::UploadPass&);
bool isEmpty() const { return textures.empty(); }
private:
std::map<size_t, DashPatternTexture> textures;
// Stores a list of hashes of texture objects that need uploading.
std::vector<size_t> needsUpload;
};
} // namespace mln

22
src/mln/gfx/attribute.cpp Normal file
View File

@@ -0,0 +1,22 @@
#include <mln/gfx/attribute.hpp>
#include <limits>
#include <cassert>
namespace mln {
namespace gfx {
std::optional<AttributeBinding> offsetAttributeBinding(const std::optional<AttributeBinding>& binding,
std::size_t vertexOffset) {
assert(vertexOffset <= std::numeric_limits<uint32_t>::max());
if (binding) {
AttributeBinding result = *binding;
result.vertexOffset = static_cast<uint32_t>(vertexOffset);
return result;
} else {
return binding;
}
}
} // namespace gfx
} // namespace mln

333
src/mln/gfx/attribute.hpp Normal file
View File

@@ -0,0 +1,333 @@
#pragma once
#include <mln/gfx/types.hpp>
#include <mln/gfx/vertex_buffer.hpp>
#include <mln/util/type_list.hpp>
#include <mln/util/indexed_tuple.hpp>
#include <mln/util/ignore.hpp>
#include <array>
#include <type_traits>
#include <cstddef>
#include <optional>
#define MBGL_DEFINE_ATTRIBUTE(type_, n_, name_) \
struct name_ { \
using Type = ::mln::gfx::AttributeType<type_, n_>; \
static constexpr auto name() { \
return #name_; \
} \
}
#if defined(_MSC_VER) && !defined(__clang__)
#define MBGL_VERTEX_ALIGN __declspec(align(4))
#else
#define MBGL_VERTEX_ALIGN __attribute__((aligned(4)))
#endif
namespace mln {
namespace gfx {
template <typename, std::size_t>
struct AttributeDataTypeOf;
template <>
struct AttributeDataTypeOf<int8_t, 1> : std::integral_constant<AttributeDataType, AttributeDataType::Byte> {};
template <>
struct AttributeDataTypeOf<int8_t, 2> : std::integral_constant<AttributeDataType, AttributeDataType::Byte2> {};
template <>
struct AttributeDataTypeOf<int8_t, 3> : std::integral_constant<AttributeDataType, AttributeDataType::Byte3> {};
template <>
struct AttributeDataTypeOf<int8_t, 4> : std::integral_constant<AttributeDataType, AttributeDataType::Byte4> {};
template <>
struct AttributeDataTypeOf<uint8_t, 1> : std::integral_constant<AttributeDataType, AttributeDataType::UByte> {};
template <>
struct AttributeDataTypeOf<uint8_t, 2> : std::integral_constant<AttributeDataType, AttributeDataType::UByte2> {};
template <>
struct AttributeDataTypeOf<uint8_t, 3> : std::integral_constant<AttributeDataType, AttributeDataType::UByte3> {};
template <>
struct AttributeDataTypeOf<uint8_t, 4> : std::integral_constant<AttributeDataType, AttributeDataType::UByte4> {};
template <>
struct AttributeDataTypeOf<int16_t, 1> : std::integral_constant<AttributeDataType, AttributeDataType::Short> {};
template <>
struct AttributeDataTypeOf<int16_t, 2> : std::integral_constant<AttributeDataType, AttributeDataType::Short2> {};
template <>
struct AttributeDataTypeOf<int16_t, 3> : std::integral_constant<AttributeDataType, AttributeDataType::Short3> {};
template <>
struct AttributeDataTypeOf<int16_t, 4> : std::integral_constant<AttributeDataType, AttributeDataType::Short4> {};
template <>
struct AttributeDataTypeOf<uint16_t, 1> : std::integral_constant<AttributeDataType, AttributeDataType::UShort> {};
template <>
struct AttributeDataTypeOf<uint16_t, 2> : std::integral_constant<AttributeDataType, AttributeDataType::UShort2> {};
template <>
struct AttributeDataTypeOf<uint16_t, 3> : std::integral_constant<AttributeDataType, AttributeDataType::UShort3> {};
template <>
struct AttributeDataTypeOf<uint16_t, 4> : std::integral_constant<AttributeDataType, AttributeDataType::UShort4> {};
template <>
struct AttributeDataTypeOf<uint16_t, 8> : std::integral_constant<AttributeDataType, AttributeDataType::UShort8> {};
template <>
struct AttributeDataTypeOf<int32_t, 1> : std::integral_constant<AttributeDataType, AttributeDataType::Int> {};
template <>
struct AttributeDataTypeOf<int32_t, 2> : std::integral_constant<AttributeDataType, AttributeDataType::Int2> {};
template <>
struct AttributeDataTypeOf<int32_t, 3> : std::integral_constant<AttributeDataType, AttributeDataType::Int3> {};
template <>
struct AttributeDataTypeOf<int32_t, 4> : std::integral_constant<AttributeDataType, AttributeDataType::Int4> {};
template <>
struct AttributeDataTypeOf<uint32_t, 1> : std::integral_constant<AttributeDataType, AttributeDataType::UInt> {};
template <>
struct AttributeDataTypeOf<uint32_t, 2> : std::integral_constant<AttributeDataType, AttributeDataType::UInt2> {};
template <>
struct AttributeDataTypeOf<uint32_t, 3> : std::integral_constant<AttributeDataType, AttributeDataType::UInt3> {};
template <>
struct AttributeDataTypeOf<uint32_t, 4> : std::integral_constant<AttributeDataType, AttributeDataType::UInt4> {};
template <>
struct AttributeDataTypeOf<float, 1> : std::integral_constant<AttributeDataType, AttributeDataType::Float> {};
template <>
struct AttributeDataTypeOf<float, 2> : std::integral_constant<AttributeDataType, AttributeDataType::Float2> {};
template <>
struct AttributeDataTypeOf<float, 3> : std::integral_constant<AttributeDataType, AttributeDataType::Float3> {};
template <>
struct AttributeDataTypeOf<float, 4> : std::integral_constant<AttributeDataType, AttributeDataType::Float4> {};
template <typename T, std::size_t N>
class AttributeType {
public:
using ElementType = T;
static constexpr AttributeDataType DataType = AttributeDataTypeOf<T, N>::value;
static constexpr size_t Dimensions = N;
using Value = std::array<T, N>;
};
struct AttributeDescriptor {
AttributeDataType dataType;
uint32_t offset;
};
inline bool operator==(const AttributeDescriptor& lhs, const AttributeDescriptor& rhs) {
return lhs.dataType == rhs.dataType && lhs.offset == rhs.offset;
}
struct VertexDescriptor {
uint8_t stride;
uint8_t count;
AttributeDescriptor attributes[5];
};
class AttributeBinding {
public:
AttributeDescriptor attribute;
uint32_t vertexStride;
const VertexBufferResource* vertexBufferResource;
uint32_t vertexOffset;
uint32_t bufferIndex;
friend bool operator==(const AttributeBinding& lhs, const AttributeBinding& rhs) {
return lhs.attribute == rhs.attribute && lhs.vertexStride == rhs.vertexStride &&
lhs.vertexBufferResource == rhs.vertexBufferResource && lhs.vertexOffset == rhs.vertexOffset &&
lhs.bufferIndex == rhs.bufferIndex;
}
bool operator!=(const AttributeBinding& rhs) const { return !(*this == rhs); }
};
// Attribute binding requires member offsets. The only standard way to
// obtain an offset is the offsetof macro. The offsetof macro has defined
// behavior only for standard layout types. That rules out std::tuple and
// any other solution that relies on chained inheritance. Manually implemented
// variadic specialization looks like the only solution. Fortunately, we
// only use a maximum of five attributes.
namespace detail {
template <class...>
struct VertexType;
template <class A1>
struct MBGL_VERTEX_ALIGN VertexType<A1> {
using Type = VertexType<A1>;
typename A1::Value a1;
};
template <class A1, class A2>
struct MBGL_VERTEX_ALIGN VertexType<A1, A2> {
using Type = VertexType<A1, A2>;
typename A1::Value a1;
typename A2::Value a2;
};
template <class A1, class A2, class A3>
struct MBGL_VERTEX_ALIGN VertexType<A1, A2, A3> {
using Type = VertexType<A1, A2, A3>;
typename A1::Value a1;
typename A2::Value a2;
typename A3::Value a3;
};
template <class A1, class A2, class A3, class A4>
struct MBGL_VERTEX_ALIGN VertexType<A1, A2, A3, A4> {
using Type = VertexType<A1, A2, A3, A4>;
typename A1::Value a1;
typename A2::Value a2;
typename A3::Value a3;
typename A4::Value a4;
};
template <class A1, class A2, class A3, class A4, class A5>
struct MBGL_VERTEX_ALIGN VertexType<A1, A2, A3, A4, A5> {
using Type = VertexType<A1, A2, A3, A4, A5>;
typename A1::Value a1;
typename A2::Value a2;
typename A3::Value a3;
typename A4::Value a4;
typename A5::Value a5;
};
template <class>
struct Descriptor;
template <class A1>
struct Descriptor<VertexType<A1>> {
using Type = VertexType<A1>;
static_assert(sizeof(Type) < 256, "vertex type must be smaller than 256 bytes");
static_assert(std::is_standard_layout_v<Type>, "vertex type must use standard layout");
static constexpr const VertexDescriptor data = {sizeof(Type),
1,
{
{A1::DataType, offsetof(Type, a1)},
}};
};
template <class A1>
constexpr const VertexDescriptor Descriptor<VertexType<A1>>::data;
template <class A1, class A2>
struct Descriptor<VertexType<A1, A2>> {
using Type = VertexType<A1, A2>;
static_assert(sizeof(Type) < 256, "vertex type must be smaller than 256 bytes");
static_assert(std::is_standard_layout_v<Type>, "vertex type must use standard layout");
static constexpr const VertexDescriptor data = {sizeof(Type),
2,
{
{A1::DataType, offsetof(Type, a1)},
{A2::DataType, offsetof(Type, a2)},
}};
};
template <class A1, class A2>
constexpr const VertexDescriptor Descriptor<VertexType<A1, A2>>::data;
template <class A1, class A2, class A3>
struct Descriptor<VertexType<A1, A2, A3>> {
using Type = VertexType<A1, A2, A3>;
static_assert(sizeof(Type) < 256, "vertex type must be smaller than 256 bytes");
static_assert(std::is_standard_layout_v<Type>, "vertex type must use standard layout");
static constexpr const VertexDescriptor data = {sizeof(Type),
3,
{
{A1::DataType, offsetof(Type, a1)},
{A2::DataType, offsetof(Type, a2)},
{A3::DataType, offsetof(Type, a3)},
}};
};
template <class A1, class A2, class A3>
constexpr const VertexDescriptor Descriptor<VertexType<A1, A2, A3>>::data;
template <class A1, class A2, class A3, class A4>
struct Descriptor<VertexType<A1, A2, A3, A4>> {
using Type = VertexType<A1, A2, A3, A4>;
static_assert(sizeof(Type) < 256, "vertex type must be smaller than 256 bytes");
static_assert(std::is_standard_layout_v<Type>, "vertex type must use standard layout");
static constexpr const VertexDescriptor data = {sizeof(Type),
4,
{
{A1::DataType, offsetof(Type, a1)},
{A2::DataType, offsetof(Type, a2)},
{A3::DataType, offsetof(Type, a3)},
{A4::DataType, offsetof(Type, a4)},
}};
};
template <class A1, class A2, class A3, class A4>
constexpr const VertexDescriptor Descriptor<VertexType<A1, A2, A3, A4>>::data;
template <class A1, class A2, class A3, class A4, class A5>
struct Descriptor<VertexType<A1, A2, A3, A4, A5>> {
using Type = VertexType<A1, A2, A3, A4, A5>;
static_assert(sizeof(Type) < 256, "vertex type must be smaller than 256 bytes");
static_assert(std::is_standard_layout_v<Type>, "vertex type must use standard layout");
static constexpr const VertexDescriptor data = {sizeof(Type),
5,
{
{A1::DataType, offsetof(Type, a1)},
{A2::DataType, offsetof(Type, a2)},
{A3::DataType, offsetof(Type, a3)},
{A4::DataType, offsetof(Type, a4)},
{A5::DataType, offsetof(Type, a5)},
}};
};
template <class A1, class A2, class A3, class A4, class A5>
constexpr const VertexDescriptor Descriptor<VertexType<A1, A2, A3, A4, A5>>::data;
template <class>
struct Vertex;
template <class... As>
struct Vertex<TypeList<As...>> {
using Type = VertexType<typename As::Type...>;
};
} // namespace detail
template <class A>
using Vertex = typename detail::Vertex<A>::Type;
template <class T>
using VertexType = typename detail::VertexType<T>;
template <size_t I = 0, class... As>
AttributeBinding attributeBinding(const VertexBuffer<detail::VertexType<As...>>& buffer) {
using Descriptor = detail::Descriptor<detail::VertexType<As...>>;
static_assert(I < Descriptor::data.count, "attribute index must be in range");
return {
Descriptor::data.attributes[I],
Descriptor::data.stride,
&buffer.getResource(),
0,
0,
};
}
std::optional<gfx::AttributeBinding> offsetAttributeBinding(const std::optional<gfx::AttributeBinding>& binding,
std::size_t vertexOffset);
template <class>
class AttributeBindings;
template <class... As>
class AttributeBindings<TypeList<As...>> final
: public IndexedTuple<TypeList<As...>, TypeList<ExpandToType<As, std::optional<AttributeBinding>>...>> {
using Base = IndexedTuple<TypeList<As...>, TypeList<ExpandToType<As, std::optional<AttributeBinding>>...>>;
public:
AttributeBindings(const VertexBuffer<Vertex<TypeList<As...>>>& buffer)
: Base{attributeBinding<TypeIndex<As, As...>::value>(buffer)...} {}
template <class... Args>
AttributeBindings(Args&&... args)
: Base(std::forward<Args>(args)...) {}
AttributeBindings offset(const std::size_t vertexOffset) const {
return {offsetAttributeBinding(Base::template get<As>(), vertexOffset)...};
}
uint32_t activeCount() const {
uint32_t result = 0;
util::ignore({((result += bool(Base::template get<As>())), 0)...});
return result;
}
};
} // namespace gfx
} // namespace mln

View File

@@ -0,0 +1,24 @@
#pragma once
#include <mln/gfx/drawable_data.hpp>
#include <mln/style/types.hpp>
#include <memory>
namespace mln {
namespace gfx {
struct CollisionDrawableData : public DrawableData {
CollisionDrawableData(const std::array<float, 2> translate_, const style::TranslateAnchorType translateAnchor_)
: translate(translate_),
translateAnchor(translateAnchor_) {}
~CollisionDrawableData() override = default;
std::array<float, 2> translate;
style::TranslateAnchorType translateAnchor;
};
using UniqueCollisionDrawableData = std::unique_ptr<CollisionDrawableData>;
} // namespace gfx
} // namespace mln

View File

@@ -0,0 +1,26 @@
#pragma once
#include <mln/gfx/drawable_data.hpp>
#include <mln/shaders/color_relief_layer_ubo.hpp>
namespace mln {
namespace gfx {
/**
* @brief Drawable data for color-relief layer
*
* Stores per-tile DEM properties that are set during drawable creation
* and read by the tweaker during rendering. This is necessary because
* the DEM unpack vector and stride are only available from the bucket
* during drawable creation.
*/
class ColorReliefDrawableData : public DrawableData {
public:
explicit ColorReliefDrawableData(const shaders::ColorReliefTilePropsUBO& props)
: tileProps(props) {}
shaders::ColorReliefTilePropsUBO tileProps;
};
} // namespace gfx
} // namespace mln

View File

@@ -0,0 +1,24 @@
#pragma once
#include <mln/gfx/types.hpp>
namespace mln {
namespace gfx {
class CullFaceMode {
public:
bool enabled;
CullFaceSideType side;
CullFaceWindingType winding;
static CullFaceMode disabled() {
return {.enabled = false, .side = CullFaceSideType::Back, .winding = CullFaceWindingType::CounterClockwise};
}
static CullFaceMode backCCW() {
return {.enabled = true, .side = CullFaceSideType::Back, .winding = CullFaceWindingType::CounterClockwise};
}
};
} // namespace gfx
} // namespace mln

94
src/mln/gfx/drawable.cpp Normal file
View File

@@ -0,0 +1,94 @@
#include <mln/gfx/drawable.hpp>
#include <mln/gfx/color_mode.hpp>
#include <mln/gfx/cull_face_mode.hpp>
#include <mln/gfx/drawable_impl.hpp>
#include <mln/gfx/index_vector.hpp>
#include <mln/gfx/types.hpp>
#include <mln/renderer/render_tile.hpp>
#include <mln/renderer/render_pass.hpp>
namespace mln {
namespace gfx {
struct Drawable::Impl {
gfx::ColorMode colorMode = gfx::ColorMode::disabled();
gfx::CullFaceMode cullFaceMode = gfx::CullFaceMode::disabled();
std::shared_ptr<Bucket> bucket;
PaintPropertyBindersBase* binders = nullptr; // owned by `bucket`
Immutable<std::vector<RenderTile>> renderTiles = makeMutable<std::vector<RenderTile>>();
const RenderTile* renderTile = nullptr; // owned by `renderTiles`
};
Drawable::Drawable(std::string name_)
: name(name_),
renderPass(mln::RenderPass::Opaque),
depthType(DepthMaskType::ReadOnly),
impl(std::make_unique<Impl>()) {}
Drawable::~Drawable() = default;
const gfx::ColorMode& Drawable::getColorMode() const {
return impl->colorMode;
}
void Drawable::setColorMode(const gfx::ColorMode& value) {
impl->colorMode = value;
}
const gfx::CullFaceMode& Drawable::getCullFaceMode() const {
return impl->cullFaceMode;
}
void Drawable::setCullFaceMode(const gfx::CullFaceMode& value) {
impl->cullFaceMode = value;
}
void Drawable::setIndexData(std::vector<std::uint16_t> indexes, std::vector<UniqueDrawSegment> segments) {
setIndexData(std::make_shared<gfx::IndexVectorBase>(std::move(indexes)), std::move(segments));
}
static const gfx::Texture2DPtr noTexture;
const gfx::Texture2DPtr& Drawable::getTexture(size_t id) const {
return (id < textures.size()) ? textures[id] : noTexture;
}
void Drawable::setTexture(std::shared_ptr<gfx::Texture2D> texture, size_t id) {
assert(id < textures.size());
if (id >= textures.size()) {
return;
}
textures[id] = std::move(texture);
}
PaintPropertyBindersBase* Drawable::getBinders() {
return impl->binders;
}
const PaintPropertyBindersBase* Drawable::getBinders() const {
return impl->binders;
}
/// Set the property binders used for property updates
void Drawable::setBinders(std::shared_ptr<Bucket> bucket_, PaintPropertyBindersBase* binders_) {
impl->bucket = std::move(bucket_);
impl->binders = binders_;
}
const RenderTile* Drawable::getRenderTile() const {
return impl->renderTile;
}
const std::shared_ptr<Bucket>& Drawable::getBucket() const {
return impl->bucket;
}
void Drawable::setRenderTile(Immutable<std::vector<RenderTile>> renderTiles_, const RenderTile* tile_) {
impl->renderTiles = std::move(renderTiles_);
impl->renderTile = tile_;
}
} // namespace gfx
} // namespace mln

View File

@@ -0,0 +1,49 @@
#include <mln/gfx/drawable_atlases_tweaker.hpp>
#include <mln/gfx/drawable.hpp>
#include <mln/renderer/tile_render_data.hpp>
#include <mln/shaders/shader_program_base.hpp>
#include <mln/renderer/paint_parameters.hpp>
namespace mln {
namespace gfx {
void DrawableAtlasesTweaker::setupTextures(gfx::Drawable& drawable, const bool linearFilterForIcons) {
if (const auto& shader = drawable.getShader()) {
if (glyphTextureId && atlases) {
if (atlases->glyph) {
atlases->glyph->setSamplerConfiguration({.filter = TextureFilterType::Linear,
.wrapU = TextureWrapType::Clamp,
.wrapV = TextureWrapType::Clamp});
}
if (atlases->icon) {
atlases->icon->setSamplerConfiguration(
{.filter = linearFilterForIcons ? TextureFilterType::Linear : TextureFilterType::Nearest,
.wrapU = TextureWrapType::Clamp,
.wrapV = TextureWrapType::Clamp});
}
if (iconTextureId && shader->getSamplerLocation(*iconTextureId)) {
assert(*glyphTextureId != *iconTextureId);
drawable.setTexture(atlases ? atlases->glyph : nullptr, *glyphTextureId);
drawable.setTexture(atlases ? atlases->icon : nullptr, *iconTextureId);
} else {
drawable.setTexture(atlases ? (isText ? atlases->glyph : atlases->icon) : nullptr, *glyphTextureId);
}
}
}
}
void DrawableAtlasesTweaker::init(gfx::Drawable& drawable) {
setupTextures(drawable, true);
}
void DrawableAtlasesTweaker::execute(gfx::Drawable& drawable, PaintParameters& parameters) {
const bool transformed = rotationAlignment == style::AlignmentType::Map || parameters.state.getPitch() != 0;
const bool linearFilterForIcons = isText ? (parameters.state.isChanging() || transformed || !textSizeIsZoomConstant)
: (sdfIcons || parameters.state.isChanging() || iconScaled || transformed);
setupTextures(drawable, linearFilterForIcons);
}
} // namespace gfx
} // namespace mln

View File

@@ -0,0 +1,313 @@
#include <mln/gfx/drawable_builder.hpp>
#include <mln/gfx/drawable_builder_impl.hpp>
#include <mln/gfx/drawable_impl.hpp>
#include <mln/gfx/vertex_attribute.hpp>
#include <mln/renderer/render_pass.hpp>
#include <mln/util/logging.hpp>
#include <mln/gfx/gfx_types.hpp>
namespace mln {
namespace gfx {
DrawableBuilder::DrawableBuilder(std::string name_)
: name(std::move(name_)),
vertexAttrId(0),
renderPass(mln::RenderPass::Opaque),
impl(std::make_unique<Impl>()) {}
DrawableBuilder::~DrawableBuilder() = default;
const gfx::ColorMode& DrawableBuilder::getColorMode() const {
return impl->colorMode;
}
void DrawableBuilder::setColorMode(const gfx::ColorMode& value) {
impl->colorMode = value;
}
const gfx::CullFaceMode& DrawableBuilder::getCullFaceMode() const {
return impl->cullFaceMode;
}
void DrawableBuilder::setCullFaceMode(const gfx::CullFaceMode& value) {
impl->cullFaceMode = value;
}
UniqueDrawable& DrawableBuilder::getCurrentDrawable(bool createIfNone) {
if (!currentDrawable && createIfNone) {
currentDrawable = createDrawable();
}
return currentDrawable;
}
void DrawableBuilder::flush(gfx::Context& context) {
if (curVertexCount()) {
if (Impl::Mode::Polylines == impl->getMode()) {
// setup for polylines
impl->setupForPolylines(context, *this);
} else if (Impl::Mode::WideVectorLocal == impl->getMode() || Impl::Mode::WideVectorGlobal == impl->getMode()) {
// setup for wide vectors
impl->setupForWideVectors(context, *this);
}
const auto& draw = getCurrentDrawable(/*createIfNone=*/true);
draw->setEnabled(enabled);
draw->setLineWidth(static_cast<int32_t>(lineWidth));
draw->setEnableColor(enableColor);
draw->setEnableStencil(enableStencil);
draw->setRenderPass(renderPass);
draw->setDrawPriority(drawPriority);
draw->setSubLayerIndex(subLayerIndex);
draw->setEnableDepth(enableDepth);
draw->setDepthType(depthType);
draw->setIs3D(is3D);
draw->setColorMode(impl->colorMode);
draw->setCullFaceMode(impl->cullFaceMode);
draw->setShader(shader);
draw->setTextures(textures);
draw->setTweakers(tweakers);
draw->setOrigin(origin);
if (vertexAttrs) {
draw->setVertexAttributes(vertexAttrs);
} else {
draw->setVertexAttributes(context.createVertexAttributeArray());
}
if (instanceAttrs) {
draw->setInstanceAttributes(instanceAttrs);
}
init();
// reset mode
impl->setMode(Impl::Mode::Custom);
// reset origin
origin.reset();
}
if (currentDrawable) {
drawables.emplace_back(std::move(currentDrawable));
}
}
util::SimpleIdentity DrawableBuilder::getDrawableId() {
return currentDrawable ? currentDrawable->getID() : util::SimpleIdentity::Empty;
}
DrawPriority DrawableBuilder::getDrawPriority() const {
return drawPriority;
}
void DrawableBuilder::setDrawPriority(DrawPriority value) {
drawPriority = value;
if (currentDrawable) {
currentDrawable->setDrawPriority(value);
}
}
void DrawableBuilder::resetDrawPriority(DrawPriority value) {
setDrawPriority(value);
for (auto& drawble : drawables) {
drawble->setDrawPriority(value);
}
}
static const gfx::Texture2DPtr noTexture;
const gfx::Texture2DPtr& DrawableBuilder::getTexture(size_t id) const {
return (id < textures.size()) ? textures[id] : noTexture;
}
void DrawableBuilder::setTexture(const std::shared_ptr<gfx::Texture2D>& texture, size_t id) {
assert(id < textures.size());
if (id >= textures.size()) {
return;
}
textures[id] = std::move(texture);
}
void DrawableBuilder::addTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2) {
// check and set the current mode
if (!impl->checkAndSetMode(Impl::Mode::Primitives)) return;
const auto n = static_cast<uint16_t>(impl->vertices.elements());
impl->vertices.emplace_back(Impl::VT({{{x0, y0}}}));
impl->vertices.emplace_back(Impl::VT({{{x1, y1}}}));
impl->vertices.emplace_back(Impl::VT({{{x2, y2}}}));
impl->buildIndexes.insert(impl->buildIndexes.end(),
{n, static_cast<uint16_t>(n + 1), static_cast<uint16_t>(n + 2)});
if (impl->segments.empty()) {
impl->segments.emplace_back(createSegment(gfx::Triangles(), {0, 0}));
}
auto& segment = impl->segments.back();
segment->getSegment().vertexLength += 3;
segment->getSegment().indexLength += 3;
}
void DrawableBuilder::appendTriangle(int16_t x0, int16_t y0) {
// check and set the current mode
if (!impl->checkAndSetMode(Impl::Mode::Primitives)) return;
const auto n = (uint16_t)impl->vertices.elements();
impl->vertices.emplace_back(Impl::VT({{{x0, y0}}}));
impl->buildIndexes.insert(impl->buildIndexes.end(),
{static_cast<uint16_t>(n - 2), static_cast<uint16_t>(n - 1), n});
assert(!impl->segments.empty());
auto& segment = impl->segments.back();
segment->getSegment().vertexLength += 1;
segment->getSegment().indexLength += 3;
}
void DrawableBuilder::addQuad(int16_t x0, int16_t y0, int16_t x1, int16_t y1) {
// check and set the current mode
if (!impl->checkAndSetMode(Impl::Mode::Primitives)) return;
addTriangle(x0, y0, x1, y0, x0, y1);
appendTriangle(x1, y1);
}
std::size_t DrawableBuilder::addVertices(const std::vector<std::array<int16_t, 2>>& vertices,
std::size_t vertexOffset,
std::size_t vertexLength) {
const auto baseIndex = impl->vertices.elements();
std::for_each(std::next(vertices.begin(), vertexOffset),
std::next(vertices.begin(), vertexOffset + vertexLength),
[&](const std::array<int16_t, 2>& x) { impl->vertices.emplace_back(Impl::VT({{x}})); });
return baseIndex;
}
void DrawableBuilder::setRawVertices(std::vector<uint8_t>&& data, std::size_t count, AttributeDataType type) {
impl->rawVertices = std::move(data);
impl->rawVerticesCount = count;
impl->rawVerticesType = type;
}
void DrawableBuilder::setSegments(const gfx::DrawMode mode,
std::vector<uint16_t> indexes,
const SegmentBase* segments,
const std::size_t segmentCount) {
setSegments(mode, std::make_shared<gfx::IndexVectorBase>(std::move(indexes)), segments, segmentCount);
}
void DrawableBuilder::setSegments(const gfx::DrawMode mode,
gfx::IndexVectorBasePtr indexes,
const SegmentBase* segments,
const std::size_t segmentCount) {
if (!indexes || !indexes->elements()) {
assert("Missing segment indexes");
return;
}
impl->sharedIndexes = std::move(indexes);
for (std::size_t i = 0; i < segmentCount; ++i) {
const auto& seg = segments[i];
#if !defined(NDEBUG)
if (mode.type == DrawModeType::Triangles) {
assert(seg.indexLength % 3 == 0);
} else if (mode.type == DrawModeType::Lines) {
assert(seg.indexLength % 2 == 0);
}
const auto vertexCount = curVertexCount();
assert(!vertexCount || seg.vertexOffset + seg.vertexLength <= curVertexCount());
assert(seg.indexOffset + seg.indexLength <= impl->sharedIndexes->elements());
if (vertexCount) {
for (decltype(seg.indexLength) j = 0; j < seg.indexLength; ++j) {
assert(impl->sharedIndexes->vector()[seg.indexOffset + j] < vertexCount);
}
}
#endif
auto segCopy = SegmentBase{
// no copy constructor
seg.vertexOffset,
seg.indexOffset,
seg.vertexLength,
seg.indexLength,
seg.sortKey,
};
impl->segments.emplace_back(createSegment(mode, std::move(segCopy)));
}
}
void DrawableBuilder::addLines(const std::vector<uint16_t>& indexes,
std::size_t indexOffset,
std::size_t indexLength,
std::size_t baseIndex) {
if (!indexLength) {
return;
}
impl->segments.emplace_back(createSegment(Lines(lineWidth),
SegmentBase{/*.vertexOffset = */ 0,
/*.indexOffset = */ indexes.size(),
/*.vertexLength = */ 0,
/*.indexLength = */ indexLength}));
if (impl->buildIndexes.empty()) {
impl->buildIndexes.reserve(indexLength);
}
for (auto i = std::next(indexes.begin(), indexOffset); i != std::next(indexes.begin(), indexOffset + indexLength);
++i) {
impl->buildIndexes.emplace_back(static_cast<uint16_t>(*i + baseIndex));
}
}
void DrawableBuilder::addTriangles(const std::vector<uint16_t>& indexes,
std::size_t indexOffset,
std::size_t indexLength,
std::size_t baseIndex) {
if (!indexLength) {
return;
}
impl->segments.emplace_back(createSegment(Triangles(),
SegmentBase{/*.vertexOffset = */ 0,
/*.indexOffset = */ impl->buildIndexes.size(),
/*.vertexLength = */ impl->vertices.elements(),
/*.indexLength = */ indexLength}));
if (impl->buildIndexes.empty()) {
impl->buildIndexes.reserve(indexLength);
}
for (auto i = std::next(indexes.begin(), indexOffset); i != std::next(indexes.begin(), indexOffset + indexLength);
++i) {
impl->buildIndexes.emplace_back(static_cast<uint16_t>(*i + baseIndex));
}
}
std::size_t DrawableBuilder::curVertexCount() const {
return impl->vertexCount();
}
void DrawableBuilder::addPolyline(const GeometryCoordinates& coordinates,
const gfx::PolylineGeneratorOptions& options) {
// mark the current mode
if (!impl->checkAndSetMode(Impl::Mode::Polylines)) return;
// append polyline
impl->addPolyline(*this, coordinates, options);
}
void DrawableBuilder::addWideVectorPolylineLocal(const GeometryCoordinates& coordinates,
const gfx::PolylineGeneratorOptions& options) {
// mark the current mode
if (!impl->checkAndSetMode(Impl::Mode::WideVectorLocal)) return;
// append polyline
impl->addWideVectorPolylineLocal(*this, coordinates, options);
}
void DrawableBuilder::addWideVectorPolylineGlobal(const LineString<double>& coordinates,
const gfx::PolylineGeneratorOptions& options) {
// mark the current mode
if (!impl->checkAndSetMode(Impl::Mode::WideVectorGlobal)) return;
// append polyline
origin = impl->addWideVectorPolylineGlobal(*this, coordinates, options);
}
} // namespace gfx
} // namespace mln

View File

@@ -0,0 +1,311 @@
#include <mln/gfx/drawable_builder_impl.hpp>
#include <mln/gfx/drawable_impl.hpp>
#include <mln/gfx/polyline_generator.hpp>
#include <mln/style/types.hpp>
#include <mln/util/logging.hpp>
#include <mln/util/math.hpp>
#include <mln/util/projection.hpp>
#include <string>
namespace mln {
namespace gfx {
using namespace shaders;
// MARK: - Polylines
DrawableBuilder::Impl::LineLayoutVertex DrawableBuilder::Impl::layoutVertex(
Point<int16_t> p, Point<double> e, bool round, bool up, int8_t dir, int32_t linesofar /*= 0*/) {
/*
* Scale the extrusion vector so that the normal length is this value.
* Contains the "texture" normals (-1..1). This is distinct from the extrude
* normals for line joins, because the x-value remains 0 for the texture
* normal array, while the extrude normal actually moves the vertex to
* create the acute/bevelled line join.
*/
static const int8_t extrudeScale = 63;
return LineLayoutVertex{
.a1 = {{static_cast<int16_t>((p.x * 2) | (round ? 1 : 0)), static_cast<int16_t>((p.y * 2) | (up ? 1 : 0))}},
.a2 = {{// add 128 to store a byte in an unsigned byte
static_cast<uint8_t>(::round(extrudeScale * e.x) + 128),
static_cast<uint8_t>(::round(extrudeScale * e.y) + 128),
// Encode the -1/0/1 direction value into the first two bits of .z
// of a_data. Combine it with the lower 6 bits of `linesofar`
// (shifted by 2 bites to make room for the direction value). The
// upper 8 bits of `linesofar` are placed in the `w` component.
// `linesofar` is scaled down by `LINE_DISTANCE_SCALE` so that we
// can store longer distances while sacrificing precision.
// Encode the -1/0/1 direction value into .zw coordinates of
// a_data, which is normally covered by linesofar, so we need to
// merge them. The z component's first bit, as well as the sign
// bit is reserved for the direction, so we need to shift the
// linesofar.
static_cast<uint8_t>(((dir == 0 ? 0 : (dir < 0 ? -1 : 1)) + 1) | ((linesofar & 0x3F) << 2)),
static_cast<uint8_t>(linesofar >> 6)}}};
}
void DrawableBuilder::Impl::addPolyline(gfx::DrawableBuilder& builder,
const GeometryCoordinates& coordinates,
const gfx::PolylineGeneratorOptions& options) {
using namespace std::placeholders;
gfx::PolylineGenerator<DrawableBuilder::Impl::LineLayoutVertex, std::unique_ptr<Drawable::DrawSegment>> generator(
polylineVertices,
std::bind(&DrawableBuilder::Impl::layoutVertex, this, _1, _2, _3, _4, _5, _6),
segments,
[&builder](std::size_t vertexOffset, std::size_t indexOffset) -> std::unique_ptr<Drawable::DrawSegment> {
return builder.createSegment(gfx::Triangles(), SegmentBase(vertexOffset, indexOffset));
},
[](auto& uniqueSegment) -> SegmentBase& { return uniqueSegment->getSegment(); },
polylineIndexes);
generator.generate(coordinates, options);
}
void DrawableBuilder::Impl::setupForPolylines(gfx::Context& context, gfx::DrawableBuilder& builder) {
// setup vertex attributes
builder.setVertexAttrId(idLinePosNormalVertexAttribute);
builder.setRawVertices({}, polylineVertices.elements(), gfx::AttributeDataType::Short2);
auto attrs = context.createVertexAttributeArray();
using VertexVector = gfx::VertexVector<LineLayoutVertex>;
std::shared_ptr<VertexVector> verts = std::make_shared<VertexVector>(std::move(polylineVertices));
if (const auto& attr = attrs->set(idLinePosNormalVertexAttribute)) {
attr->setSharedRawData(verts,
offsetof(LineLayoutVertex, a1),
/*vertexOffset=*/0,
sizeof(LineLayoutVertex),
gfx::AttributeDataType::Short2);
}
if (const auto& attr = attrs->set(idLineDataVertexAttribute)) {
attr->setSharedRawData(verts,
offsetof(LineLayoutVertex, a2),
/*vertexOffset=*/0,
sizeof(LineLayoutVertex),
gfx::AttributeDataType::UByte4);
}
builder.setVertexAttributes(std::move(attrs));
sharedIndexes = std::make_shared<gfx::IndexVectorBase>(std::move(polylineIndexes));
}
// MARK: - Wide Vector Polylines
namespace {
inline void genInstanceLinks(
int32_t& outPrev, int32_t& outNext, const int base, const int coord_size, const int index, bool loop) {
if (loop) {
// loop line
outPrev = base + (index - 1 + coord_size) % coord_size;
outNext = base + (index + 1) % coord_size;
} else {
// line string
outPrev = (0 == index) ? -1 : base + index - 1;
outNext = (index + 1 >= coord_size) ? -1 : base + index + 1;
}
}
} // namespace
void DrawableBuilder::Impl::addWideVectorPolylineLocal(gfx::DrawableBuilder& /*builder*/,
const GeometryCoordinates& coordinates,
const gfx::PolylineGeneratorOptions& options) {
// add instance data
const int base = static_cast<int>(wideVectorInstanceData.elements());
const int coord_size = static_cast<int>(coordinates.size());
int index = 0;
for (const auto& coord : coordinates) {
VertexTriWideVecInstance data;
data.center = {static_cast<float>(coord.x), static_cast<float>(coord.y), 0};
genInstanceLinks(data.prev, data.next, base, coord_size, index, FeatureType::Polygon == options.type);
wideVectorInstanceData.emplace_back(std::move(data));
++index;
}
}
mln::Point<double> DrawableBuilder::Impl::addWideVectorPolylineGlobal(gfx::DrawableBuilder& /*builder*/,
const LineString<double>& coordinates,
const gfx::PolylineGeneratorOptions& options) {
constexpr int32_t zoom = 0;
// get center
constexpr double maxd = std::numeric_limits<double>::max();
constexpr double mind = std::numeric_limits<double>::min();
Point<double> minPoint{maxd, maxd}, maxPoint{mind, mind};
for (const auto& coord : coordinates) {
auto merc = Projection::project(LatLng(coord.y, coord.x), zoom);
Point<double> pSource{merc.x * mln::util::EXTENT, merc.y * mln::util::EXTENT};
minPoint.x = std::min(pSource.x, minPoint.x);
minPoint.y = std::min(pSource.y, minPoint.y);
maxPoint.x = std::max(pSource.x, maxPoint.x);
maxPoint.y = std::max(pSource.y, maxPoint.y);
}
Point<double> pCenter{(minPoint.x + maxPoint.x) / 2.0, (minPoint.y + maxPoint.y) / 2.0};
// add centerline instance data
const int base = static_cast<int>(wideVectorInstanceData.elements());
const int coord_size = static_cast<int>(coordinates.size());
int index = 0;
for (const auto& coord : coordinates) {
auto merc = Projection::project(LatLng(coord.y, coord.x), zoom);
Point<double> pSource{merc.x * mln::util::EXTENT, merc.y * mln::util::EXTENT};
pSource.x -= pCenter.x;
pSource.y -= pCenter.y;
VertexTriWideVecInstance data;
data.center = {static_cast<float>(pSource.x), static_cast<float>(pSource.y), 0};
genInstanceLinks(data.prev, data.next, base, coord_size, index, FeatureType::Polygon == options.type);
wideVectorInstanceData.emplace_back(std::move(data));
++index;
}
return pCenter;
}
void DrawableBuilder::Impl::setupForWideVectors(gfx::Context& context, gfx::DrawableBuilder& builder) {
// vertices
if (!wideVectorVertices) {
constexpr shaders::VertexTriWideVecB kWideVectorVertices[]{
{.screenPos = {0}, .color = {0}, .index = (0 << 16) + 0},
{.screenPos = {0}, .color = {0}, .index = (1 << 16) + 0},
{.screenPos = {0}, .color = {0}, .index = (2 << 16) + 0},
{.screenPos = {0}, .color = {0}, .index = (3 << 16) + 0},
{.screenPos = {0}, .color = {0}, .index = (4 << 16) + 1},
{.screenPos = {0}, .color = {0}, .index = (5 << 16) + 1},
{.screenPos = {0}, .color = {0}, .index = (6 << 16) + 1},
{.screenPos = {0}, .color = {0}, .index = (7 << 16) + 1},
{.screenPos = {0}, .color = {0}, .index = (8 << 16) + 2},
{.screenPos = {0}, .color = {0}, .index = (9 << 16) + 2},
{.screenPos = {0}, .color = {0}, .index = (10 << 16) + 2},
{.screenPos = {0}, .color = {0}, .index = (11 << 16) + 2},
};
wideVectorVertices = std::make_shared<gfx::VertexVector<shaders::VertexTriWideVecB>>();
for (auto& v : kWideVectorVertices) {
wideVectorVertices->emplace_back(v);
}
}
// instance data
using InstanceVector = gfx::VertexVector<VertexTriWideVecInstance>;
const std::shared_ptr<InstanceVector> sharedInstanceData = std::make_shared<InstanceVector>(
std::move(wideVectorInstanceData));
// indexes
if (!wideVectorIndexes) {
wideVectorIndexes = std::make_shared<gfx::IndexVector<gfx::Triangles>>();
wideVectorIndexes->emplace_back(
0, 3, 1, 0, 2, 3, 4 + 0, 4 + 3, 4 + 1, 4 + 0, 4 + 2, 4 + 3, 8 + 0, 8 + 3, 8 + 1, 8 + 0, 8 + 2, 8 + 3);
}
// segments
SegmentVector triangleSegments;
triangleSegments.emplace_back(0, 0, 12, 18);
// add to builder
{
// add vertex attributes
auto vertexAttributes = context.createVertexAttributeArray();
if (const auto& attr = vertexAttributes->set(idWideVectorScreenPos)) {
attr->setSharedRawData(wideVectorVertices,
offsetof(VertexTriWideVecB, screenPos),
/*vertexOffset=*/0,
sizeof(VertexTriWideVecB),
gfx::AttributeDataType::Float3);
}
if (const auto& attr = vertexAttributes->set(idWideVectorColor)) {
attr->setSharedRawData(wideVectorVertices,
offsetof(VertexTriWideVecB, color),
/*vertexOffset=*/0,
sizeof(VertexTriWideVecB),
gfx::AttributeDataType::Float4);
}
if (const auto& attr = vertexAttributes->set(idWideVectorIndex)) {
attr->setSharedRawData(wideVectorVertices,
offsetof(VertexTriWideVecB, index),
/*vertexOffset=*/0,
sizeof(VertexTriWideVecB),
gfx::AttributeDataType::Int);
}
builder.setVertexAttributes(std::move(vertexAttributes));
}
{
// add instance attributes
auto instanceAttributes = context.createVertexAttributeArray();
if (const auto& attr = instanceAttributes->set(idWideVectorInstanceCenter)) {
attr->setSharedRawData(sharedInstanceData,
offsetof(VertexTriWideVecInstance, center),
/*vertexOffset=*/0,
sizeof(VertexTriWideVecInstance),
gfx::AttributeDataType::Float3);
}
if (const auto& attr = instanceAttributes->set(idWideVectorInstanceColor)) {
attr->setSharedRawData(sharedInstanceData,
offsetof(VertexTriWideVecInstance, color),
/*vertexOffset=*/0,
sizeof(VertexTriWideVecInstance),
gfx::AttributeDataType::Float4);
}
if (const auto& attr = instanceAttributes->set(idWideVectorInstancePrevious)) {
attr->setSharedRawData(sharedInstanceData,
offsetof(VertexTriWideVecInstance, prev),
/*vertexOffset=*/0,
sizeof(VertexTriWideVecInstance),
gfx::AttributeDataType::Int);
}
if (const auto& attr = instanceAttributes->set(idWideVectorInstanceNext)) {
attr->setSharedRawData(sharedInstanceData,
offsetof(VertexTriWideVecInstance, next),
/*vertexOffset=*/0,
sizeof(VertexTriWideVecInstance),
gfx::AttributeDataType::Int);
}
builder.setInstanceAttributes(std::move(instanceAttributes));
}
builder.setRawVertices({}, wideVectorVertices->elements(), gfx::AttributeDataType::Float2);
builder.setSegments(gfx::Triangles(), wideVectorIndexes, triangleSegments.data(), triangleSegments.size());
}
// MARK: - Common
bool DrawableBuilder::Impl::checkAndSetMode(Mode target) {
if (target != mode && vertexCount()) {
// log error
using namespace std::string_literals;
auto to_string = [](Mode value) -> std::string {
switch (value) {
case Mode::Primitives:
return "Mode::Primitives";
case Mode::Polylines:
return "Mode::Polylines";
case Mode::WideVectorLocal:
return "Mode::WideVectorLocal";
case Mode::WideVectorGlobal:
return "Mode::WideVectorGlobal";
case Mode::Custom:
return "Mode::Custom";
default:
return "Unknown"; // Handle unknown enum values if needed
}
};
mln::Log::Error(
mln::Event::General,
"DrawableBuilder mode mismatch. Target is "s + to_string(target) + ", current is " + to_string(mode));
// the builder is building in a different mode
assert(false);
// TODO: throw?
return false;
}
mode = target;
return true;
}
} // namespace gfx
} // namespace mln

View File

@@ -0,0 +1,103 @@
#include <mln/gfx/attribute.hpp>
#include <mln/gfx/color_mode.hpp>
#include <mln/gfx/cull_face_mode.hpp>
#include <mln/gfx/index_vector.hpp>
#include <mln/gfx/vertex_attribute.hpp>
#include <mln/gfx/vertex_vector.hpp>
#include <mln/shaders/segment.hpp>
#include <mln/gfx/drawable_builder.hpp>
#include <mln/gfx/polyline_generator.hpp>
#include <mln/shaders/widevector_ubo.hpp>
#include <cstdint>
#include <cstddef>
#include <memory>
#include <vector>
#include <algorithm>
namespace mln {
namespace gfx {
class DrawableBuilder::Impl {
public:
using VT = gfx::detail::VertexType<gfx::AttributeType<std::int16_t, 2>>;
enum class Mode {
Primitives, ///< building primitive drawables. Not implemented
Polylines, ///< building drawables for thick polylines
WideVectorLocal, ///< building drawables for thick polylines using wide vectors in local coordinates
WideVectorGlobal, ///< building drawables for thick polylines using wide vectors in global coordinates
Custom ///< building custom drawables.
};
struct LineLayoutVertex {
std::array<int16_t, 2> a1;
std::array<uint8_t, 4> a2;
};
public:
gfx::VertexVector<VT> vertices;
std::vector<uint8_t> rawVertices;
std::size_t rawVerticesCount = 0;
gfx::VertexVector<LineLayoutVertex> polylineVertices;
gfx::IndexVector<gfx::Triangles> polylineIndexes;
std::shared_ptr<gfx::VertexVector<shaders::VertexTriWideVecB>> wideVectorVertices;
gfx::VertexVector<shaders::VertexTriWideVecInstance> wideVectorInstanceData;
std::shared_ptr<gfx::IndexVector<gfx::Triangles>> wideVectorIndexes;
std::vector<uint16_t> buildIndexes;
std::shared_ptr<gfx::IndexVectorBase> sharedIndexes;
std::vector<std::unique_ptr<Drawable::DrawSegment>> segments;
AttributeDataType rawVerticesType = static_cast<AttributeDataType>(-1);
gfx::ColorMode colorMode = gfx::ColorMode::disabled();
gfx::CullFaceMode cullFaceMode = gfx::CullFaceMode::disabled();
// methods
void addPolyline(gfx::DrawableBuilder& builder,
const GeometryCoordinates& coordinates,
const gfx::PolylineGeneratorOptions& options);
void setupForPolylines(gfx::Context&, gfx::DrawableBuilder&);
void addWideVectorPolylineLocal(gfx::DrawableBuilder& builder,
const GeometryCoordinates& coordinates,
const gfx::PolylineGeneratorOptions& options);
mln::Point<double> addWideVectorPolylineGlobal(gfx::DrawableBuilder& builder,
const LineString<double>& coordinates,
const gfx::PolylineGeneratorOptions& options);
void setupForWideVectors(gfx::Context&, gfx::DrawableBuilder&);
bool checkAndSetMode(Mode);
Mode getMode() const { return mode; };
bool setMode(Mode value) { return mode == value; };
std::size_t vertexCount() const {
return std::max(
{rawVerticesCount, vertices.elements(), polylineVertices.elements(), wideVectorInstanceData.elements()});
}
void clear() {
vertices.clear();
rawVertices.clear();
rawVerticesCount = 0;
polylineVertices.clear();
polylineIndexes.clear();
buildIndexes.clear();
segments.clear();
}
private:
LineLayoutVertex layoutVertex(
Point<int16_t> p, Point<double> e, bool round, bool up, int8_t dir, int32_t linesofar = 0);
Mode mode{Mode::Custom};
};
} // namespace gfx
} // namespace mln

View File

@@ -0,0 +1,51 @@
#include <mln/gfx/drawable_custom_layer_host_tweaker.hpp>
#include <mln/renderer/paint_parameters.hpp>
#include <mln/gfx/renderer_backend.hpp>
#include <mln/gfx/renderable.hpp>
#include <mln/gfx/drawable.hpp>
#include <mln/gfx/context.hpp>
#if MLN_RENDER_BACKEND_METAL
#include <mln/style/layers/mtl/custom_layer_render_parameters.hpp>
#include <mln/mtl/render_pass.hpp>
#elif MLN_RENDER_BACKEND_VULKAN
#include <mln/style/layers/vulkan/custom_layer_render_parameters.hpp>
#include <mln/vulkan/render_pass.hpp>
#endif
#include <memory>
namespace mln {
namespace gfx {
void DrawableCustomLayerHostTweaker::execute([[maybe_unused]] gfx::Drawable& drawable,
mln::PaintParameters& paintParameters) {
// custom drawing
auto& context = paintParameters.context;
context.resetState(paintParameters.depthModeForSublayer(0, gfx::DepthMaskType::ReadOnly),
paintParameters.colorModeForRenderPass());
#if MLN_RENDER_BACKEND_METAL
const auto& mtlRenderPass = static_cast<mtl::RenderPass*>(paintParameters.renderPass.get());
mtlRenderPass->resetState();
style::mtl::CustomLayerRenderParameters parameters(paintParameters);
#elif MLN_RENDER_BACKEND_VULKAN
style::vulkan::CustomLayerRenderParameters parameters(paintParameters);
#else
style::CustomLayerRenderParameters parameters(paintParameters);
#endif
host->render(parameters);
// Reset the view back to our original one, just in case the CustomLayer
// changed the viewport or Framebuffer.
paintParameters.backend.getDefaultRenderable().getResource<gfx::RenderableResource>().bind();
context.setDirtyState();
context.bindGlobalUniformBuffers(*paintParameters.renderPass);
}
} // namespace gfx
} // namespace mln

View File

@@ -0,0 +1,79 @@
#include <mln/gfx/dynamic_texture.hpp>
#include <mln/gfx/texture2d.hpp>
#include <mln/gfx/context.hpp>
namespace mln {
namespace gfx {
DynamicTexture::DynamicTexture(Size size, TexturePixelType pixelType)
: pixelFormat(pixelType) {
mapbox::ShelfPack::ShelfPackOptions options;
options.autoResize = false;
shelfPack = mapbox::ShelfPack(size.width, size.height, options);
}
DynamicTexture::DynamicTexture(Context& context, Size size, TexturePixelType pixelType)
: DynamicTexture(size, pixelType) {
texture = context.createTexture2D();
texture->setSize(size);
texture->setFormat(pixelType, gfx::TextureChannelDataType::UnsignedByte);
texture->setSamplerConfiguration({.filter = gfx::TextureFilterType::Linear,
.wrapU = gfx::TextureWrapType::Clamp,
.wrapV = gfx::TextureWrapType::Clamp});
}
const gfx::Texture2DPtr& DynamicTexture::getTexture() const {
assert(texture);
return texture;
}
TexturePixelType DynamicTexture::getPixelFormat() const {
return pixelFormat;
}
bool DynamicTexture::isEmpty() const {
return (numTextures == 0);
}
std::optional<TextureHandle> DynamicTexture::reserveSize(const Size& size, int32_t uniqueId) {
std::scoped_lock lock(mutex);
mapbox::Bin* bin = shelfPack.packOne(uniqueId, size.width, size.height);
if (!bin) {
return std::nullopt;
}
if (bin->refcount() == 1) {
numTextures++;
}
return TextureHandle(*bin);
}
std::optional<TextureHandle> DynamicTexture::addImage(const uint8_t* pixelData,
const Size& imageSize,
int32_t uniqueId) {
auto texHandle = reserveSize(imageSize, uniqueId);
if (texHandle && texHandle->isUploadNeeded()) {
uploadImage(pixelData, *texHandle);
}
return texHandle;
}
void DynamicTexture::uploadImage(const uint8_t* /*pixelData*/, gfx::TextureHandle& texHandle) {
texHandle.needsUpload = false;
}
bool DynamicTexture::removeTexture(const TextureHandle& texHandle) {
std::scoped_lock lock(mutex);
auto* bin = shelfPack.getBin(texHandle.getId());
if (!bin) {
return false;
}
auto refcount = shelfPack.unref(*bin);
if (refcount == 0) {
numTextures--;
return true;
}
return false;
}
} // namespace gfx
} // namespace mln

View File

@@ -0,0 +1,263 @@
#include <mln/gfx/dynamic_texture_atlas.hpp>
#include <mln/gfx/context.hpp>
#include <algorithm>
#include <cmath>
namespace mln {
namespace gfx {
constexpr const uint16_t extraPadding = 1;
constexpr const uint16_t padding = ImagePosition::padding + extraPadding;
constexpr const Size startSize = {512, 512};
constexpr const Size dummySize = {1, 1};
namespace {
Rect<uint16_t> rectWithoutExtraPadding(const Rect<uint16_t>& rect) {
return Rect<uint16_t>(
rect.x + extraPadding, rect.y + extraPadding, rect.w - 2 * extraPadding, rect.h - 2 * extraPadding);
}
} // namespace
GlyphAtlas DynamicTextureAtlas::uploadGlyphs(const GlyphMap& glyphs) {
using GlyphsToUpload = std::vector<std::tuple<TextureHandle, Immutable<Glyph>, FontStackHash>>;
std::scoped_lock lock(mutex);
GlyphAtlas glyphAtlas;
if (!glyphs.size()) {
glyphAtlas.dynamicTexture = dummyDynamicTexture[TexturePixelType::Alpha];
if (!glyphAtlas.dynamicTexture) {
AlphaImage dummyImage(dummySize);
dummyImage.fill(0);
glyphAtlas.dynamicTexture = context.createDynamicTexture(dummySize, TexturePixelType::Alpha);
glyphAtlas.dynamicTexture->addImage(dummyImage.data.get(), dummySize);
dummyDynamicTexture[TexturePixelType::Alpha] = glyphAtlas.dynamicTexture;
}
return glyphAtlas;
}
size_t dynTexIndex = 0;
Size dynTexSize = startSize;
GlyphsToUpload glyphsToUpload;
while (!glyphAtlas.dynamicTexture) {
if (dynTexIndex < dynamicTextures.size()) {
glyphAtlas.dynamicTexture = dynamicTextures[dynTexIndex++];
} else {
glyphAtlas.dynamicTexture = context.createDynamicTexture(dynTexSize, TexturePixelType::Alpha);
dynTexSize = Size(dynTexSize.width * 2, dynTexSize.height * 2);
dynTexIndex++;
}
if (glyphAtlas.dynamicTexture->getPixelFormat() != TexturePixelType::Alpha) {
glyphAtlas.dynamicTexture = nullptr;
continue;
}
bool hasSpace = true;
for (const auto& glyphMapEntry : glyphs) {
FontStackHash fontStack = glyphMapEntry.first;
for (const auto& glyphEntry : glyphMapEntry.second) {
const auto& glyph = glyphEntry.second;
if (glyph.has_value() && glyph.value()->bitmap.valid()) {
int32_t uniqueId = static_cast<int32_t>(sqrt(fontStack) / 2 + glyph.value()->id.hash);
const auto size = Size(glyph.value()->bitmap.size.width + 2 * padding,
glyph.value()->bitmap.size.height + 2 * padding);
const auto& texHandle = glyphAtlas.dynamicTexture->reserveSize(size, uniqueId);
if (!texHandle) {
hasSpace = false;
break;
}
glyphsToUpload.emplace_back(std::make_tuple(*texHandle, glyph.value(), fontStack));
}
}
if (!hasSpace) {
for (const auto& [texHandle, glyph, fontStack_] : glyphsToUpload) {
glyphAtlas.dynamicTexture->removeTexture(texHandle);
}
glyphsToUpload.clear();
glyphAtlas.dynamicTexture = nullptr;
break;
}
}
}
if (dynTexIndex > dynamicTextures.size()) {
dynamicTextures.emplace_back(glyphAtlas.dynamicTexture);
}
for (auto& [texHandle, glyph, fontStack] : glyphsToUpload) {
const auto& rect = texHandle.getRectangle();
if (texHandle.isUploadNeeded()) {
AlphaImage paddedImage(Size{rect.w, rect.h});
paddedImage.fill(0);
AlphaImage::copy(glyph->bitmap, paddedImage, {0, 0}, {padding, padding}, glyph->bitmap.size);
glyphAtlas.dynamicTexture->uploadImage(paddedImage.data.get(), texHandle);
}
glyphAtlas.textureHandles.emplace_back(texHandle);
glyphAtlas.glyphPositions[fontStack].emplace(
glyph->id, GlyphPosition{.rect = rectWithoutExtraPadding(rect), .metrics = glyph->metrics});
}
return glyphAtlas;
}
ImageAtlas DynamicTextureAtlas::uploadIconsAndPatterns(const ImageMap& icons,
const ImageMap& patterns,
const ImageVersionMap& versionMap) {
using ImagesToUpload = std::vector<std::pair<TextureHandle, Immutable<style::Image::Impl>>>;
std::scoped_lock lock(mutex);
ImageAtlas imageAtlas;
if (!icons.size() && !patterns.size()) {
imageAtlas.dynamicTexture = dummyDynamicTexture[TexturePixelType::RGBA];
if (!imageAtlas.dynamicTexture) {
PremultipliedImage dummyImage(dummySize);
dummyImage.fill(0);
imageAtlas.dynamicTexture = context.createDynamicTexture(dummySize, TexturePixelType::RGBA);
imageAtlas.dynamicTexture->addImage(dummyImage.data.get(), dummySize);
dummyDynamicTexture[TexturePixelType::RGBA] = imageAtlas.dynamicTexture;
}
return imageAtlas;
}
size_t dynTexIndex = 0;
Size dynTexSize = startSize;
ImagesToUpload iconsToUpload;
ImagesToUpload patternsToUpload;
iconsToUpload.reserve(icons.size());
patternsToUpload.reserve(patterns.size());
while (!imageAtlas.dynamicTexture) {
if (dynTexIndex < dynamicTextures.size()) {
imageAtlas.dynamicTexture = dynamicTextures[dynTexIndex++];
} else {
imageAtlas.dynamicTexture = context.createDynamicTexture(dynTexSize, TexturePixelType::RGBA);
dynTexSize = Size(dynTexSize.width * 2, dynTexSize.height * 2);
dynTexIndex++;
}
if (imageAtlas.dynamicTexture->getPixelFormat() != TexturePixelType::RGBA) {
imageAtlas.dynamicTexture = nullptr;
continue;
}
bool hasSpace = true;
for (const auto& iconEntry : icons) {
const auto& icon = iconEntry.second;
auto imageHash = util::hash(icon->id);
int32_t uniqueId = static_cast<int32_t>(sqrt(imageHash) / 2 + icon->image.size.area());
const auto size = Size(icon->image.size.width + 2 * padding, icon->image.size.height + 2 * padding);
const auto& texHandle = imageAtlas.dynamicTexture->reserveSize(size, uniqueId);
if (!texHandle) {
hasSpace = false;
break;
}
iconsToUpload.emplace_back(std::make_pair(*texHandle, icon));
}
if (hasSpace) {
for (const auto& patternEntry : patterns) {
const auto& pattern = patternEntry.second;
auto patternHash = util::hash(pattern->id);
int32_t uniqueId = static_cast<int32_t>(sqrt(patternHash) / 2 + pattern->image.size.area());
const auto size = Size(pattern->image.size.width + 2 * padding,
pattern->image.size.height + 2 * padding);
const auto& texHandle = imageAtlas.dynamicTexture->reserveSize(size, uniqueId);
if (!texHandle) {
hasSpace = false;
break;
}
patternsToUpload.emplace_back(std::make_pair(*texHandle, pattern));
}
}
if (!hasSpace) {
for (const auto& [texHandle, icon] : iconsToUpload) {
imageAtlas.dynamicTexture->removeTexture(texHandle);
}
iconsToUpload.clear();
for (const auto& [texHandle, pattern] : patternsToUpload) {
imageAtlas.dynamicTexture->removeTexture(texHandle);
}
patternsToUpload.clear();
imageAtlas.dynamicTexture = nullptr;
continue;
}
}
if (dynTexIndex > dynamicTextures.size()) {
dynamicTextures.emplace_back(imageAtlas.dynamicTexture);
}
imageAtlas.iconPositions.reserve(icons.size());
for (auto& [texHandle, icon] : iconsToUpload) {
const auto& rect = texHandle.getRectangle();
if (texHandle.isUploadNeeded()) {
PremultipliedImage paddedImage(Size{rect.w, rect.h});
paddedImage.fill(0);
PremultipliedImage::copy(icon->image, paddedImage, {0, 0}, {padding, padding}, icon->image.size);
imageAtlas.dynamicTexture->uploadImage(paddedImage.data.get(), texHandle);
}
imageAtlas.textureHandles.emplace_back(texHandle);
const auto it = versionMap.find(icon->id);
const auto version = it != versionMap.end() ? it->second : 0;
imageAtlas.iconPositions.emplace(icon->id, ImagePosition{rectWithoutExtraPadding(rect), *icon, version});
}
imageAtlas.patternPositions.reserve(patterns.size());
for (auto& [texHandle, pattern] : patternsToUpload) {
const auto& rect = texHandle.getRectangle();
if (texHandle.isUploadNeeded()) {
PremultipliedImage paddedImage(Size{rect.w, rect.h});
paddedImage.fill(0);
PremultipliedImage::copy(pattern->image, paddedImage, {0, 0}, {padding, padding}, pattern->image.size);
const uint32_t x = padding;
const uint32_t y = padding;
const uint32_t w = pattern->image.size.width;
const uint32_t h = pattern->image.size.height;
// Add 1 pixel wrapped padding on each side of the image.
PremultipliedImage::copy(pattern->image, paddedImage, {0, h - 1}, {x, y - 1}, {w, 1}); // T
PremultipliedImage::copy(pattern->image, paddedImage, {0, 0}, {x, y + h}, {w, 1}); // B
PremultipliedImage::copy(pattern->image, paddedImage, {w - 1, 0}, {x - 1, y}, {1, h}); // L
PremultipliedImage::copy(pattern->image, paddedImage, {0, 0}, {x + w, y}, {1, h}); // R
imageAtlas.dynamicTexture->uploadImage(paddedImage.data.get(), texHandle);
}
imageAtlas.textureHandles.emplace_back(texHandle);
const auto it = versionMap.find(pattern->id);
const auto version = it != versionMap.end() ? it->second : 0;
imageAtlas.patternPositions.emplace(pattern->id,
ImagePosition{rectWithoutExtraPadding(rect), *pattern, version});
}
return imageAtlas;
}
void DynamicTextureAtlas::removeTextures(const std::vector<TextureHandle>& textureHandles,
const DynamicTexturePtr& dynamicTexture) {
if (!dynamicTexture) {
return;
}
std::scoped_lock lock(mutex);
for (const auto& texHandle : textureHandles) {
dynamicTexture->removeTexture(texHandle);
}
}
void DynamicTextureAtlas::removeUnusedDynamicTextures() {
std::scoped_lock lock(mutex);
std::erase_if(dynamicTextures, [](auto& dynamicTexture) { return dynamicTexture->isEmpty(); });
}
} // namespace gfx
} // namespace mln

View File

@@ -0,0 +1,248 @@
#include <mln/gfx/fill_generator.hpp>
#include <mln/gfx/polyline_generator.hpp>
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4244)
#endif
#include <mapbox/earcut.hpp>
#ifdef _MSC_VER
#pragma warning(pop)
#endif
#include <cassert>
#include <limits>
namespace mapbox {
namespace util {
template <>
struct nth<0, mln::GeometryCoordinate> {
static int64_t get(const mln::GeometryCoordinate& t) { return t.x; };
};
template <>
struct nth<1, mln::GeometryCoordinate> {
static int64_t get(const mln::GeometryCoordinate& t) { return t.y; };
};
} // namespace util
} // namespace mapbox
namespace mln {
namespace gfx {
struct GeometryTooLongException : std::exception {};
namespace {
std::size_t addRingVertices(gfx::VertexVector<FillLayoutVertex>& vertices, const GeometryCoordinates& ring) {
for (auto& point : ring) {
vertices.emplace_back(FillBucket::layoutVertex(point));
}
return ring.size();
}
std::size_t totalVerticesCheck(const GeometryCollection& polygon) {
std::size_t totalVertices = 0;
for (const auto& ring : polygon) {
totalVertices += ring.size();
}
if (totalVertices > std::numeric_limits<uint16_t>::max()) throw GeometryTooLongException();
return totalVertices;
}
void addFillIndices(SegmentVector& fillSegments,
gfx::IndexVector<gfx::Triangles>& fillIndexes,
const std::span<const uint32_t>& indices,
const std::size_t startVertices,
const std::size_t totalVertices) {
const std::size_t nIndices = indices.size();
assert(nIndices % 3 == 0);
if (fillSegments.empty() ||
fillSegments.back().vertexLength + totalVertices > std::numeric_limits<uint16_t>::max()) {
fillSegments.emplace_back(startVertices, fillIndexes.elements());
}
auto& triangleSegment = fillSegments.back();
assert(triangleSegment.vertexLength + totalVertices <= std::numeric_limits<uint16_t>::max());
const auto triangleIndex = static_cast<uint16_t>(triangleSegment.vertexLength);
for (std::size_t i = 0; i < nIndices; i += 3) {
assert(
std::max({triangleIndex + indices[i + 0], triangleIndex + indices[i + 1], triangleIndex + indices[i + 2]}) <
triangleIndex + totalVertices);
fillIndexes.emplace_back(
triangleIndex + indices[i], triangleIndex + indices[i + 1], triangleIndex + indices[i + 2]);
}
triangleSegment.vertexLength += totalVertices;
triangleSegment.indexLength += nIndices;
}
void addOutlineIndices(const std::size_t base,
const std::size_t nVertices,
SegmentVector& lineSegments,
gfx::IndexVector<gfx::Lines>& lineIndexes) {
if (nVertices == 0) return;
if (lineSegments.empty() || lineSegments.back().vertexLength + nVertices > std::numeric_limits<uint16_t>::max()) {
lineSegments.emplace_back(base, lineIndexes.elements());
}
auto& lineSegment = lineSegments.back();
assert(lineSegment.vertexLength <= std::numeric_limits<uint16_t>::max());
const auto lineIndex = static_cast<uint16_t>(lineSegment.vertexLength);
lineIndexes.emplace_back(static_cast<uint16_t>(lineIndex + nVertices - 1), lineIndex);
for (std::size_t i = 1; i < nVertices; i++) {
lineIndexes.emplace_back(static_cast<uint16_t>(lineIndex + i - 1), static_cast<uint16_t>(lineIndex + i));
}
lineSegment.vertexLength += nVertices;
lineSegment.indexLength += nVertices * 2;
}
} // namespace
void generateFillBuffers(const GeometryCollection& geometry,
gfx::VertexVector<FillLayoutVertex>& fillVertices,
gfx::IndexVector<Triangles>& fillIndexes,
SegmentVector& fillSegments) {
for (auto& polygon : classifyRings(geometry)) {
// Optimize polygons with many interior rings for earcut tessellation.
limitHoles(polygon, 500);
std::size_t totalVertices = totalVerticesCheck(polygon);
std::size_t startVertices = fillVertices.elements();
for (const auto& ring : polygon) {
addRingVertices(fillVertices, ring);
}
std::vector<uint32_t> indices = mapbox::earcut(polygon);
addFillIndices(fillSegments, fillIndexes, indices, startVertices, totalVertices);
}
}
void generateFillAndOutineBuffers(const GeometryCollection& geometry,
gfx::VertexVector<FillLayoutVertex>& vertices,
gfx::IndexVector<gfx::Triangles>& fillIndexes,
SegmentVector& fillSegments,
gfx::IndexVector<gfx::Lines>& lineIndexes,
SegmentVector& lineSegments) {
for (auto& polygon : classifyRings(geometry)) {
// Optimize polygons with many interior rings for earcut tessellation.
limitHoles(polygon, 500);
std::size_t totalVertices = totalVerticesCheck(polygon);
std::size_t startVertices = vertices.elements();
for (const auto& ring : polygon) {
std::size_t base = vertices.elements();
std::size_t nVertices = addRingVertices(vertices, ring);
addOutlineIndices(base, nVertices, lineSegments, lineIndexes);
}
std::vector<uint32_t> indices = mapbox::earcut(polygon);
addFillIndices(fillSegments, fillIndexes, indices, startVertices, totalVertices);
}
}
void generateFillAndOutineBuffers(const GeometryCollection& geometry,
gfx::VertexVector<FillLayoutVertex>& fillVertices,
gfx::IndexVector<gfx::Triangles>& fillIndexes,
SegmentVector& fillSegments,
gfx::VertexVector<LineLayoutVertex>& lineVertices,
gfx::IndexVector<gfx::Triangles>& lineIndexes,
SegmentVector& lineSegments) {
gfx::PolylineGenerator<LineLayoutVertex, SegmentBase> lineGenerator(
lineVertices,
LineBucket::layoutVertex,
lineSegments,
[](std::size_t vertexOffset, std::size_t indexOffset) -> SegmentBase {
return SegmentBase(vertexOffset, indexOffset);
},
[](auto& seg) -> SegmentBase& { return seg; },
lineIndexes);
gfx::PolylineGeneratorOptions lineOptions;
lineOptions.type = FeatureType::Polygon;
for (auto& polygon : classifyRings(geometry)) {
// Optimize polygons with many interior rings for earcut tessellation.
limitHoles(polygon, 500);
std::size_t totalVertices = totalVerticesCheck(polygon);
std::size_t startVertices = fillVertices.elements();
for (const auto& ring : polygon) {
addRingVertices(fillVertices, ring);
lineGenerator.generate(ring, lineOptions);
}
std::vector<uint32_t> indices = mapbox::earcut(polygon);
addFillIndices(fillSegments, fillIndexes, indices, startVertices, totalVertices);
}
}
void generateFillAndOutineBuffers(const GeometryCollection& geometry,
gfx::VertexVector<FillLayoutVertex>& fillVertices,
gfx::IndexVector<gfx::Triangles>& fillIndexes,
SegmentVector& fillSegments,
gfx::VertexVector<LineLayoutVertex>& lineVertices,
gfx::IndexVector<gfx::Triangles>& lineIndexes,
SegmentVector& lineSegments,
gfx::IndexVector<gfx::Lines>& basicLineIndexes,
SegmentVector& basicLineSegments) {
gfx::PolylineGenerator<LineLayoutVertex, SegmentBase> lineGenerator(
lineVertices,
LineBucket::layoutVertex,
lineSegments,
[](std::size_t vertexOffset, std::size_t indexOffset) -> SegmentBase {
return SegmentBase(vertexOffset, indexOffset);
},
[](auto& seg) -> SegmentBase& { return seg; },
lineIndexes);
gfx::PolylineGeneratorOptions lineOptions;
lineOptions.type = FeatureType::Polygon;
// If we have pre-tessellated geometry, multi-polygons are tessellated
// together, so we need to add them to the fill segment all at once.
if (!geometry.getTriangles().empty()) {
const std::size_t startVertices = fillVertices.elements();
std::size_t totalVertices = 0;
for (const auto& polygon : geometry) {
totalVertices += polygon.size();
addRingVertices(fillVertices, polygon);
}
addFillIndices(fillSegments, fillIndexes, geometry.getTriangles(), startVertices, totalVertices);
return;
}
for (auto& polygon : classifyRings(geometry)) {
// Optimize polygons with many interior rings for earcut tessellation.
limitHoles(polygon, 500);
const std::size_t totalVertices = totalVerticesCheck(polygon);
const std::size_t startVertices = fillVertices.elements();
for (const auto& ring : polygon) {
const std::size_t base = fillVertices.elements();
const std::size_t nVertices = addRingVertices(fillVertices, ring);
addOutlineIndices(base, nVertices, basicLineSegments, basicLineIndexes);
lineGenerator.generate(ring, lineOptions);
}
// tessellate, if no triangles are provided
std::vector<uint32_t> indices = mapbox::earcut(polygon);
addFillIndices(fillSegments, fillIndexes, indices, startVertices, totalVertices);
}
}
} // namespace gfx
} // namespace mln

View File

@@ -0,0 +1,187 @@
#include <mln/gfx/gpu_expression.hpp>
#include <mln/renderer/paint_property_binder.hpp>
#include <mln/style/expression/expression.hpp>
#include <mln/style/expression/interpolate.hpp>
#include <mln/style/expression/step.hpp>
#include <mln/style/expression/value.hpp>
#include <algorithm>
namespace mln {
namespace gfx {
using namespace style::expression;
namespace {
inline GPUOutputType getOutputType(const Expression& expression) {
if (expression.getType().is<type::NumberType>()) {
return GPUOutputType::Float;
} else {
return GPUOutputType::Color;
}
}
inline GPUInterpType getInterpType(const Interpolator& interp) {
return interp.match(
[&](const ExponentialInterpolator& exp) {
return (exp.base == 1.0f) ? GPUInterpType::Linear : GPUInterpType::Exponential;
},
[&](const CubicBezierInterpolator&) { return GPUInterpType::Bezier; });
}
inline float getInterpBase(const Interpolator& interp) {
return interp.match([&](const ExponentialInterpolator& exp) { return static_cast<float>(exp.base); },
[&](const CubicBezierInterpolator&) { return 0.0f; });
}
// Produce a function for `eachStop` that extracts a single stop into the target expression
auto addStop(UniqueGPUExpression& expr, GPUOutputType outType, std::size_t& index) {
return [&, outType](double input, const Expression& output) {
if (expr) {
if (const auto result = output.evaluate(EvaluationContext{/*zoom=*/0.0f})) {
const style::expression::Value& value = *result;
expr->inputs[index] = static_cast<float>(input);
if (outType == GPUOutputType::Float) {
assert(value.is<double>());
if (value.is<double>()) {
expr->stops.floats[index++] = static_cast<float>(value.get<double>());
} else {
// Evaluation error, cancel
expr.reset();
}
} else if (outType == GPUOutputType::Color) {
assert(value.is<Color>());
if (value.is<Color>()) {
const auto& color = attributeValue(value.get<Color>());
std::ranges::copy(color, &expr->stops.colors[2 * index++]);
} else {
// Evaluation error, cancel
expr.reset();
}
}
}
}
};
};
} // namespace
const GPUExpression GPUExpression::empty = {GPUOutputType::Float, 0};
UniqueGPUExpression GPUExpression::create(GPUOutputType type, std::uint16_t count) {
if (1 < count && count <= maxStops) {
return std::make_unique<GPUExpression>(type, count);
}
return {};
}
UniqueGPUExpression GPUExpression::create(const Expression& expression, const ZoomCurvePtr& zoomCurve, bool intZoom) {
std::size_t index = 0;
const auto outType = getOutputType(expression);
const auto options = (intZoom ? GPUOptions::IntegerZoom : GPUOptions::None);
return zoomCurve.match(
[&](const Step* step) {
if (step->getStopCount() > maxStops) {
return UniqueGPUExpression{};
}
auto expr = GPUExpression::create(outType, step->getStopCount());
expr->options = options;
expr->interpolation = GPUInterpType::Step;
step->eachStop(addStop(expr, outType, index));
return expr;
},
[&](const Interpolate* interp) {
if (interp->getStopCount() > maxStops) {
return UniqueGPUExpression{};
}
auto expr = GPUExpression::create(outType, interp->getStopCount());
expr->options = options;
expr->interpolation = getInterpType(interp->getInterpolator());
expr->interpOptions.exponential.base = getInterpBase(interp->getInterpolator());
interp->eachStop(addStop(expr, outType, index));
return expr;
},
[](std::nullptr_t) { return UniqueGPUExpression{}; });
}
float GPUExpression::evaluateFloat(const float zoom) const {
const auto index = static_cast<std::uint16_t>(
std::distance(&inputs[0], std::upper_bound(&inputs[0], &inputs[stopCount], zoom)));
if (index == 0) {
return stops.floats[0];
} else if (index == stopCount) {
return stops.floats[stopCount - 1];
}
switch (interpolation) {
case GPUInterpType::Step:
return stops.floats[index - 1];
default:
assert(false);
[[fallthrough]];
case GPUInterpType::Linear:
assert(interpOptions.exponential.base == 1.0f);
[[fallthrough]];
case GPUInterpType::Exponential: {
const Range<double> range{inputs[index - 1], inputs[index]};
const ExponentialInterpolator interpolator{interpOptions.exponential.base};
const auto t = interpolator.interpolationFactor(range, zoom);
return util::Interpolator<float>()(stops.floats[index - 1], stops.floats[index], t);
}
case GPUInterpType::Bezier:
assert(false);
return stops.floats[0];
}
}
namespace {
std::tuple<float, float> unpack_float(const float packedValue) {
const int packedIntValue = int(packedValue);
const int v0 = packedIntValue / 256;
return {static_cast<float>(v0) / 255, static_cast<float>(packedIntValue - v0 * 256) / 255};
}
std::tuple<float, float, float, float> decode_color(const float encoded[2]) {
return std::tuple_cat(unpack_float(encoded[0]), unpack_float(encoded[1]));
}
Color toColor(std::tuple<float, float, float, float> rgba) {
return {std::get<0>(rgba), std::get<1>(rgba), std::get<2>(rgba), std::get<3>(rgba)};
}
} // namespace
Color GPUExpression::getColor(std::size_t index) const {
return toColor(decode_color(&stops.colors[2 * index]));
}
Color GPUExpression::evaluateColor(const float zoom) const {
const auto index = static_cast<std::uint16_t>(
std::distance(&inputs[0], std::upper_bound(&inputs[0], &inputs[stopCount], zoom)));
if (index == 0) {
return getColor(0);
} else if (index == stopCount) {
return getColor(stopCount - 1);
}
switch (interpolation) {
case GPUInterpType::Step:
return getColor(index - 1);
default:
assert(false);
[[fallthrough]];
case GPUInterpType::Linear:
assert(interpOptions.exponential.base == 1.0f);
[[fallthrough]];
case GPUInterpType::Exponential: {
const Range<double> range{inputs[index - 1], inputs[index]};
const ExponentialInterpolator interpolator{interpOptions.exponential.base};
const auto t = interpolator.interpolationFactor(range, zoom);
return util::Interpolator<Color>()(getColor(index - 1), getColor(index), t);
break;
}
case GPUInterpType::Bezier:
assert(false);
return getColor(0);
}
}
} // namespace gfx
} // namespace mln

View File

@@ -0,0 +1,27 @@
#pragma once
#include <mln/gfx/drawable_data.hpp>
#include <mln/util/tileset.hpp>
#include <memory>
namespace mln {
namespace gfx {
class HillshadePrepareDrawableData : public DrawableData {
public:
HillshadePrepareDrawableData(int32_t stride_, Tileset::RasterEncoding encoding_, uint8_t maxzoom_)
: stride(stride_),
encoding(encoding_),
maxzoom(maxzoom_) {}
int32_t stride;
Tileset::RasterEncoding encoding;
uint8_t maxzoom;
};
using UniqueHillshadePrepareDrawableData = std::unique_ptr<HillshadePrepareDrawableData>;
} // namespace gfx
} // namespace mln

View File

@@ -0,0 +1,23 @@
#pragma once
#include <mln/gfx/drawable_data.hpp>
#include <mln/util/mat4.hpp>
#include <memory>
namespace mln {
namespace gfx {
class ImageDrawableData : public DrawableData {
public:
ImageDrawableData(mat4 matrix_)
: matrix(matrix_) {}
mat4 matrix;
};
using UniqueImageDrawableData = std::unique_ptr<ImageDrawableData>;
} // namespace gfx
} // namespace mln

View File

@@ -0,0 +1,36 @@
#pragma once
#include <memory>
#include <cassert>
namespace mln {
namespace gfx {
class IndexBufferResource {
protected:
IndexBufferResource() noexcept = default;
public:
virtual ~IndexBufferResource() noexcept = default;
};
class IndexBuffer {
public:
IndexBuffer(const std::size_t elements_, std::unique_ptr<IndexBufferResource>&& resource_)
: elements(elements_),
resource(std::move(resource_)) {}
std::size_t elements;
template <typename T = IndexBufferResource>
T& getResource() const {
assert(resource);
return static_cast<T&>(*resource);
}
protected:
std::unique_ptr<IndexBufferResource> resource;
};
} // namespace gfx
} // namespace mln

View File

@@ -0,0 +1,112 @@
#pragma once
#include <mln/gfx/draw_mode.hpp>
#include <mln/util/ignore.hpp>
#include <memory>
#include <vector>
namespace mln {
namespace gfx {
struct IndexBufferBase {
virtual ~IndexBufferBase() = default;
};
class IndexVectorBase {
protected:
std::vector<uint16_t> v;
public:
using value_type = decltype(v)::value_type;
IndexVectorBase() = default;
IndexVectorBase(std::vector<uint16_t>&& indexes)
: v(std::move(indexes)) {}
IndexVectorBase(const IndexVectorBase& other)
: v(other.v) {} // buffer is not copied
IndexVectorBase(IndexVectorBase&& other)
: v(std::move(other.v)),
buffer(std::move(other.buffer)),
dirty(other.dirty),
released(other.released) {}
virtual ~IndexVectorBase() = default;
IndexBufferBase* getBuffer() const { return buffer.get(); }
void setBuffer(std::unique_ptr<IndexBufferBase>&& value) { buffer = std::move(value); }
bool getDirty() const { return dirty; }
void setDirty(bool value = true) { dirty = value; }
bool isReleased() const { return released; }
void reserve(std::size_t count) { v.reserve(count); }
void extend(std::size_t n, const uint16_t val) {
assert(!released);
v.resize(v.size() + n, val);
dirty = true;
}
uint16_t& at(std::size_t n) {
assert(n < v.size());
assert(!released);
dirty = true;
return v.at(n);
}
const uint16_t& at(std::size_t n) const {
assert(n < v.size());
return v.at(n);
}
std::size_t elements() const { return v.size(); }
std::size_t bytes() const { return v.size() * sizeof(uint16_t); }
bool empty() const { return v.empty(); }
void clear() {
dirty = true;
v.clear();
buffer.reset();
}
/// Indicate that this shared index vector will no longer be updated.
void release() {
// If we've already created a buffer, we don't need the raw data any more.
if (buffer) {
v.clear();
}
released = true;
}
const uint16_t* data() const { return v.data(); }
const std::vector<uint16_t>& vector() const { return v; }
protected:
std::unique_ptr<IndexBufferBase> buffer;
bool dirty = true;
bool released = false;
};
using IndexVectorBasePtr = std::shared_ptr<IndexVectorBase>;
template <class DrawMode>
class IndexVector final : public IndexVectorBase {
public:
static constexpr std::size_t groupSize = BufferGroupSizeOf<DrawMode>::value;
template <class... Args>
void emplace_back(Args&&... args) {
static_assert(sizeof...(args) % groupSize == 0, "wrong buffer element count");
assert(!released);
util::ignore({(v.emplace_back(std::forward<Args>(args)), 0)...});
dirty = true;
}
};
using IndexVectorBasePtr = std::shared_ptr<IndexVectorBase>;
} // namespace gfx
} // namespace mln

View File

@@ -0,0 +1,23 @@
#pragma once
#include <mln/gfx/drawable_data.hpp>
#include <mln/geometry/line_atlas.hpp>
#include <memory>
namespace mln {
namespace gfx {
class LineDrawableData : public DrawableData {
public:
LineDrawableData(LinePatternCap linePatternCap_)
: linePatternCap(linePatternCap_) {}
LinePatternCap linePatternCap;
};
using UniqueLineDrawableData = std::unique_ptr<LineDrawableData>;
} // namespace gfx
} // namespace mln

View File

@@ -0,0 +1,29 @@
#pragma once
#include <mln/gfx/renderable.hpp>
#include <mln/util/image.hpp>
namespace mln {
namespace gfx {
class Texture;
class Texture2D;
using Texture2DPtr = std::shared_ptr<Texture2D>;
class OffscreenTexture : public gfx::Renderable {
protected:
OffscreenTexture(const Size size_, std::unique_ptr<RenderableResource> resource_)
: gfx::Renderable(size_, std::move(resource_)) {}
public:
~OffscreenTexture() override = default;
virtual bool isRenderable() = 0;
virtual PremultipliedImage readStillImage() = 0;
virtual const gfx::Texture2DPtr& getTexture() = 0;
};
} // namespace gfx
} // namespace mln

View File

@@ -0,0 +1,643 @@
#include <mln/gfx/polyline_generator.hpp>
#include <mln/style/types.hpp>
#include <mln/util/constants.hpp>
#include <mln/renderer/buckets/line_bucket.hpp>
#include <mln/gfx/drawable_builder.hpp>
#include <mln/gfx/drawable_builder_impl.hpp>
#include <mln/gfx/drawable_impl.hpp>
#include <memory>
#include <numbers>
using namespace std::numbers;
namespace mln {
namespace gfx {
namespace {
/*
* Sharp corners cause dashed lines to tilt because the distance along the line
* is the same at both the inner and outer corners. To improve the appearance of
* dashed lines we add extra points near sharp corners so that a smaller part
* of the line is tilted.
*
* COS_HALF_SHARP_CORNER controls how sharp a corner has to be for us to add an
* extra vertex. The default is 75 degrees.
*
* The newly created vertices are placed SHARP_CORNER_OFFSET pixels from the corner.
*/
const float COS_HALF_SHARP_CORNER = std::cos(75.0f / 2.0f * (pi_v<float> / 180.0f));
constexpr float SHARP_CORNER_OFFSET = 15.0f;
// Angle per triangle for approximating round line joins.
constexpr float DEG_PER_TRIANGLE = 20.0f;
// The number of bits that is used to store the line distance in the buffer.
constexpr int LINE_DISTANCE_BUFFER_BITS = 14;
// We don't have enough bits for the line distance as we'd like to have, so
// use this value to scale the line distance (in tile units) down to a smaller
// value. This lets us store longer distances while sacrificing precision.
constexpr float LINE_DISTANCE_SCALE = 1.0 / 2.0;
// The maximum line distance, in tile units, that fits in the buffer.
constexpr auto MAX_LINE_DISTANCE = static_cast<float>((1u << LINE_DISTANCE_BUFFER_BITS) / LINE_DISTANCE_SCALE);
} // namespace
double PolylineGeneratorDistances::scaleToMaxLineDistance(double tileDistance) const {
double relativeTileDistance = tileDistance / total;
if (std::isinf(relativeTileDistance) || std::isnan(relativeTileDistance)) {
assert(false);
relativeTileDistance = 0.0;
}
return (relativeTileDistance * (clipEnd - clipStart) + clipStart) * (MAX_LINE_DISTANCE - 1);
}
template <class PLV, class PS>
struct PolylineGenerator<PLV, PS>::TriangleElement {
TriangleElement(uint16_t a_, uint16_t b_, uint16_t c_)
: a(a_),
b(b_),
c(c_) {}
uint16_t a, b, c;
};
template <class PLV, class PS>
PolylineGenerator<PLV, PS>::PolylineGenerator(Vertices& polylineVertices,
LayoutVertexFunc layoutVertexFunc,
Segments& polylineSegments,
CreateSegmentFunc createSegmentFunc,
GetSegmentFunc getSegmentFunc,
Indexes& polylineIndexes)
: vertices(polylineVertices),
layoutVertex(layoutVertexFunc),
segments(polylineSegments),
createSegment(createSegmentFunc),
getSegment(getSegmentFunc),
indexes(polylineIndexes) {}
template <class PLV, class PS>
void PolylineGenerator<PLV, PS>::generate(const GeometryCoordinates& coordinates,
const PolylineGeneratorOptions& options) {
const std::size_t len = [&coordinates] {
std::size_t l = coordinates.size();
// If the line has duplicate vertices at the end, adjust length to remove them.
while (l >= 2 && coordinates[l - 1] == coordinates[l - 2]) {
l--;
}
return l;
}();
const std::size_t first = [&coordinates, &len] {
std::size_t i = 0;
// If the line has duplicate vertices at the start, adjust index to remove them.
while (i + 1 < len && coordinates[i] == coordinates[i + 1]) {
i++;
}
return i;
}();
// Ignore invalid geometry.
if (len < ((options.type == FeatureType::Polygon) ? 3u : 2u)) {
return;
}
const style::LineJoinType joinType = options.joinType;
const float miterLimit = joinType == style::LineJoinType::Bevel ? 1.05f : options.miterLimit;
const uint32_t overscaling = options.overscaling;
const double sharpCornerOffset =
overscaling == 0
? SHARP_CORNER_OFFSET * (util::EXTENT / util::tileSize_D)
: (overscaling <= 16.0 ? SHARP_CORNER_OFFSET * (util::EXTENT / (util::tileSize_D * overscaling)) : 0.0);
const GeometryCoordinate firstCoordinate = coordinates[first];
const style::LineCapType beginCap = options.beginCap;
const style::LineCapType endCap = options.type == FeatureType::Polygon ? style::LineCapType::Butt : options.endCap;
double distance = 0.0;
bool startOfLine = true;
std::optional<GeometryCoordinate> currentCoordinate;
std::optional<GeometryCoordinate> prevCoordinate;
std::optional<GeometryCoordinate> nextCoordinate;
std::optional<Point<double>> prevNormal;
std::optional<Point<double>> nextNormal;
// the last three vertices added
e1 = e2 = e3 = -1;
if (options.type == FeatureType::Polygon) {
currentCoordinate = coordinates[len - 2];
nextNormal = util::perp(util::unit(convertPoint<double>(firstCoordinate - *currentCoordinate)));
}
const std::size_t startVertex = vertices.elements();
std::vector<TriangleElement> triangleStore;
// Pre-allocate for triangles based on measuring benchmark execution
constexpr auto approxTrianglesPerSegment = 6;
triangleStore.reserve((len - first) * approxTrianglesPerSegment);
// Vertex count depends on length rather than segment count, and two elements often generates
// thousands of vertices, so we allocate some extra memory to skip the next 10 allocations.
if (vertices.empty()) {
vertices.reserve(1 << 10);
}
for (std::size_t i = first; i < len; ++i) {
if (options.type == FeatureType::Polygon && i == len - 1) {
// if the line is closed, we treat the last vertex like the first
nextCoordinate = coordinates[first + 1];
} else if (i + 1 < len) {
// just the next vertex
nextCoordinate = coordinates[i + 1];
} else {
// there is no next vertex
nextCoordinate = {};
}
// if two consecutive vertices exist, skip the current one
if (nextCoordinate && coordinates[i] == *nextCoordinate) {
continue;
}
if (nextNormal) {
prevNormal = *nextNormal;
}
if (currentCoordinate) {
prevCoordinate = *currentCoordinate;
}
currentCoordinate = coordinates[i];
// Calculate the normal towards the next vertex in this line. In case
// there is no next vertex, pretend that the line is continuing
// straight, meaning that we are just using the previous normal.
nextNormal = nextCoordinate ? util::perp(util::unit(convertPoint<double>(*nextCoordinate - *currentCoordinate)))
: prevNormal;
// If we still don't have a previous normal, this is the beginning of a
// non-closed line, so we're doing a straight "join".
if (!prevNormal) {
prevNormal = *nextNormal;
}
// Determine the normal of the join extrusion. It is the angle bisector
// of the segments between the previous line and the next line.
// In the case of 180° angles, the prev and next normals cancel each
// other out: prevNormal + nextNormal = (0, 0), its magnitude is 0, so
// the unit vector would be undefined. In that case, we're keeping the
// joinNormal at (0, 0), so that the cosHalfAngle below will also become
// 0 and miterLength will become Infinity.
Point<double> joinNormal = *prevNormal + *nextNormal;
if (joinNormal.x != 0 || joinNormal.y != 0) {
joinNormal = util::unit(joinNormal);
}
// * joinNormal prevNormal
// * ↖ ↑
// * .________. prevVertex
// * |
// * nextNormal ← | currentVertex
// * |
// * nextVertex !
// *
//
// Calculate cosines of the angle (and its half) using dot product.
const double cosAngle = prevNormal->x * nextNormal->x + prevNormal->y * nextNormal->y;
const double cosHalfAngle = joinNormal.x * nextNormal->x + joinNormal.y * nextNormal->y;
// Calculate the length of the miter (the ratio of the miter to the width)
// as the inverse of cosine of the angle between next and join normals.
const double miterLength = cosHalfAngle != 0 ? 1 / cosHalfAngle : std::numeric_limits<double>::infinity();
// Approximate angle from cosine.
const double approxAngle = 2 * std::sqrt(2 - 2 * cosHalfAngle);
const bool isSharpCorner = cosHalfAngle < COS_HALF_SHARP_CORNER && prevCoordinate && nextCoordinate;
if (isSharpCorner && i > first) {
const auto prevSegmentLength = util::dist<double>(*currentCoordinate, *prevCoordinate);
if (prevSegmentLength > 2.0 * sharpCornerOffset) {
GeometryCoordinate newPrevVertex = *currentCoordinate -
convertPoint<int16_t>(util::round(
convertPoint<double>(*currentCoordinate - *prevCoordinate) *
(sharpCornerOffset / prevSegmentLength)));
distance += util::dist<double>(newPrevVertex, *prevCoordinate);
addCurrentVertex(newPrevVertex,
distance,
*prevNormal,
0,
0,
false,
startVertex,
triangleStore,
options.clipDistances);
prevCoordinate = newPrevVertex;
}
}
// The join if a middle vertex, otherwise the cap
const bool middleVertex = prevCoordinate && nextCoordinate;
style::LineJoinType currentJoin = joinType;
const style::LineCapType currentCap = nextCoordinate ? beginCap : endCap;
if (middleVertex) {
if (currentJoin == style::LineJoinType::Round) {
if (miterLength < options.roundLimit) {
currentJoin = style::LineJoinType::Miter;
} else if (miterLength <= 2) {
currentJoin = style::LineJoinType::FakeRound;
}
}
if (currentJoin == style::LineJoinType::Miter && miterLength > miterLimit) {
currentJoin = style::LineJoinType::Bevel;
}
if (currentJoin == style::LineJoinType::Bevel) {
// The maximum extrude length is 128 / 63 = 2 times the width of
// the line so if miterLength >= 2 we need to draw a different
// type of bevel here.
if (miterLength > 2) {
currentJoin = style::LineJoinType::FlipBevel;
}
// If the miterLength is really small and the line bevel wouldn't be visible,
// just draw a miter join to save a triangle.
if (miterLength < miterLimit) {
currentJoin = style::LineJoinType::Miter;
}
}
}
// Calculate how far along the line the currentVertex is
if (prevCoordinate) distance += util::dist<double>(*currentCoordinate, *prevCoordinate);
if (middleVertex && currentJoin == style::LineJoinType::Miter) {
joinNormal = joinNormal * miterLength;
addCurrentVertex(*currentCoordinate,
distance,
joinNormal,
0,
0,
false,
startVertex,
triangleStore,
options.clipDistances);
} else if (middleVertex && currentJoin == style::LineJoinType::FlipBevel) {
// miter is too big, flip the direction to make a beveled join
if (miterLength > 100) {
// Almost parallel lines
joinNormal = *nextNormal * -1.0;
} else {
const double direction = prevNormal->x * nextNormal->y - prevNormal->y * nextNormal->x > 0 ? -1 : 1;
const double bevelLength = miterLength * util::mag(*prevNormal + *nextNormal) /
util::mag(*prevNormal - *nextNormal);
joinNormal = util::perp(joinNormal) * bevelLength * direction;
}
addCurrentVertex(*currentCoordinate,
distance,
joinNormal,
0,
0,
false,
startVertex,
triangleStore,
options.clipDistances);
addCurrentVertex(*currentCoordinate,
distance,
joinNormal * -1.0,
0,
0,
false,
startVertex,
triangleStore,
options.clipDistances);
} else if (middleVertex &&
(currentJoin == style::LineJoinType::Bevel || currentJoin == style::LineJoinType::FakeRound)) {
const bool lineTurnsLeft = (prevNormal->x * nextNormal->y - prevNormal->y * nextNormal->x) > 0;
const auto offset = static_cast<float>(-std::sqrt(miterLength * miterLength - 1));
float offsetA;
float offsetB;
if (lineTurnsLeft) {
offsetB = 0;
offsetA = offset;
} else {
offsetA = 0;
offsetB = offset;
}
// Close previous segment with bevel
if (!startOfLine) {
addCurrentVertex(*currentCoordinate,
distance,
*prevNormal,
offsetA,
offsetB,
false,
startVertex,
triangleStore,
options.clipDistances);
}
if (currentJoin == style::LineJoinType::FakeRound) {
// The join angle is sharp enough that a round join would be
// visible. Bevel joins fill the gap between segments with a
// single pie slice triangle. Create a round join by adding
// multiple pie slices. The join isn't actually round, but it
// looks like it is at the sizes we render lines at.
// Pick the number of triangles for approximating round join by
// based on the angle between normals.
const auto n = static_cast<unsigned>(::round((approxAngle * 180 / pi) / DEG_PER_TRIANGLE));
for (unsigned m = 1; m < n; ++m) {
double t = static_cast<double>(m) / n;
if (t != 0.5) {
// approximate spherical interpolation
// https://observablehq.com/@mourner/approximating-geometric-slerp
const double t2 = t - 0.5;
const double A = 1.0904 + cosAngle * (-3.2452 + cosAngle * (3.55645 - cosAngle * 1.43519));
const double B = 0.848013 + cosAngle * (-1.06021 + cosAngle * 0.215638);
t = t + t * t2 * (t - 1) * (A * t2 * t2 + B);
}
auto approxFractionalNormal = util::unit(*prevNormal * (1.0 - t) + *nextNormal * t);
addPieSliceVertex(*currentCoordinate,
distance,
approxFractionalNormal,
lineTurnsLeft,
startVertex,
triangleStore,
options.clipDistances);
}
}
// Start next segment
if (nextCoordinate) {
addCurrentVertex(*currentCoordinate,
distance,
*nextNormal,
-offsetA,
-offsetB,
false,
startVertex,
triangleStore,
options.clipDistances);
}
} else if (!middleVertex && currentCap == style::LineCapType::Butt) {
if (!startOfLine) {
// Close previous segment with a butt
addCurrentVertex(*currentCoordinate,
distance,
*prevNormal,
0,
0,
false,
startVertex,
triangleStore,
options.clipDistances);
}
// Start next segment with a butt
if (nextCoordinate) {
addCurrentVertex(*currentCoordinate,
distance,
*nextNormal,
0,
0,
false,
startVertex,
triangleStore,
options.clipDistances);
}
} else if (!middleVertex && currentCap == style::LineCapType::Square) {
if (!startOfLine) {
// Close previous segment with a square cap
addCurrentVertex(*currentCoordinate,
distance,
*prevNormal,
1,
1,
false,
startVertex,
triangleStore,
options.clipDistances);
// The segment is done. Unset vertices to disconnect segments.
e1 = e2 = -1;
}
// Start next segment
if (nextCoordinate) {
addCurrentVertex(*currentCoordinate,
distance,
*nextNormal,
-1,
-1,
false,
startVertex,
triangleStore,
options.clipDistances);
}
} else if (middleVertex ? currentJoin == style::LineJoinType::Round : currentCap == style::LineCapType::Round) {
if (!startOfLine) {
// Close previous segment with a butt
addCurrentVertex(*currentCoordinate,
distance,
*prevNormal,
0,
0,
false,
startVertex,
triangleStore,
options.clipDistances);
// Add round cap or linejoin at end of segment
addCurrentVertex(*currentCoordinate,
distance,
*prevNormal,
1,
1,
true,
startVertex,
triangleStore,
options.clipDistances);
// The segment is done. Unset vertices to disconnect segments.
e1 = e2 = -1;
}
// Start next segment with a butt
if (nextCoordinate) {
// Add round cap before first segment
addCurrentVertex(*currentCoordinate,
distance,
*nextNormal,
-1,
-1,
true,
startVertex,
triangleStore,
options.clipDistances);
addCurrentVertex(*currentCoordinate,
distance,
*nextNormal,
0,
0,
false,
startVertex,
triangleStore,
options.clipDistances);
}
}
if (isSharpCorner && i < len - 1) {
const auto nextSegmentLength = util::dist<double>(*currentCoordinate, *nextCoordinate);
if (nextSegmentLength > 2 * sharpCornerOffset) {
GeometryCoordinate newCurrentVertex = *currentCoordinate +
convertPoint<int16_t>(util::round(
convertPoint<double>(*nextCoordinate - *currentCoordinate) *
(sharpCornerOffset / nextSegmentLength)));
distance += util::dist<double>(newCurrentVertex, *currentCoordinate);
addCurrentVertex(newCurrentVertex,
distance,
*nextNormal,
0,
0,
false,
startVertex,
triangleStore,
options.clipDistances);
currentCoordinate = newCurrentVertex;
}
}
startOfLine = false;
}
// add segment(s) and indices
const std::size_t endVertex = vertices.elements();
const std::size_t vertexCount = endVertex - startVertex;
if (segments.empty() ||
getSegment(segments.back()).vertexLength + vertexCount > std::numeric_limits<uint16_t>::max()) {
segments.emplace_back(createSegment(startVertex, indexes.elements()));
}
auto& segment = getSegment(segments.back());
assert(segment.vertexLength <= std::numeric_limits<uint16_t>::max());
const uint16_t index = static_cast<uint16_t>(segment.vertexLength);
if (indexes.empty()) {
indexes.reserve(triangleStore.size() * 3);
}
for (const auto& triangle : triangleStore) {
indexes.emplace_back(index + triangle.a, index + triangle.b, index + triangle.c);
}
segment.vertexLength += vertexCount;
segment.indexLength += triangleStore.size() * 3;
}
template <class PLV, class PS>
void PolylineGenerator<PLV, PS>::addCurrentVertex(const GeometryCoordinate& currentCoordinate,
double& distance,
const Point<double>& normal,
double endLeft,
double endRight,
bool round,
std::size_t startVertex,
std::vector<TriangleElement>& triangleStore,
std::optional<PolylineGeneratorDistances> lineDistances) {
Point<double> extrude = normal;
const double scaledDistance = lineDistances ? lineDistances->scaleToMaxLineDistance(distance) : distance;
if (endLeft) extrude = extrude - (util::perp(normal) * endLeft);
vertices.emplace_back(layoutVertex(currentCoordinate,
extrude,
round,
false,
static_cast<int8_t>(endLeft),
static_cast<int32_t>(scaledDistance * LINE_DISTANCE_SCALE)));
e3 = vertices.elements() - 1 - startVertex;
if (e1 >= 0 && e2 >= 0) {
triangleStore.emplace_back(static_cast<uint16_t>(e1), static_cast<uint16_t>(e2), static_cast<uint16_t>(e3));
}
e1 = e2;
e2 = e3;
extrude = normal * -1.0;
if (endRight) extrude = extrude - (util::perp(normal) * endRight);
vertices.emplace_back(layoutVertex(currentCoordinate,
extrude,
round,
true,
static_cast<int8_t>(-endRight),
static_cast<int32_t>(scaledDistance * LINE_DISTANCE_SCALE)));
e3 = vertices.elements() - 1 - startVertex;
if (e1 >= 0 && e2 >= 0) {
triangleStore.emplace_back(static_cast<uint16_t>(e1), static_cast<uint16_t>(e2), static_cast<uint16_t>(e3));
}
e1 = e2;
e2 = e3;
// There is a maximum "distance along the line" that we can store in the
// buffers. When we get close to the distance, reset it to zero and add the
// vertex again with a distance of zero. The max distance is determined by
// the number of bits we allocate to `linesofar`.
if (distance > MAX_LINE_DISTANCE / 2.0f && !lineDistances) {
distance = 0.0;
addCurrentVertex(
currentCoordinate, distance, normal, endLeft, endRight, round, startVertex, triangleStore, lineDistances);
}
}
template <class PLV, class PS>
void PolylineGenerator<PLV, PS>::addPieSliceVertex(const GeometryCoordinate& currentVertex,
double distance,
const Point<double>& extrude,
bool lineTurnsLeft,
std::size_t startVertex,
std::vector<TriangleElement>& triangleStore,
std::optional<PolylineGeneratorDistances> lineDistances) {
Point<double> flippedExtrude = extrude * (lineTurnsLeft ? -1.0 : 1.0);
if (lineDistances) {
distance = lineDistances->scaleToMaxLineDistance(distance);
}
vertices.emplace_back(layoutVertex(
currentVertex, flippedExtrude, false, lineTurnsLeft, 0, static_cast<int32_t>(distance * LINE_DISTANCE_SCALE)));
e3 = vertices.elements() - 1 - startVertex;
if (e1 >= 0 && e2 >= 0) {
triangleStore.emplace_back(static_cast<uint16_t>(e1), static_cast<uint16_t>(e2), static_cast<uint16_t>(e3));
}
if (lineTurnsLeft) {
e2 = e3;
} else {
e1 = e3;
}
}
template class PolylineGenerator<gfx::DrawableBuilder::Impl::LineLayoutVertex,
std::unique_ptr<gfx::Drawable::DrawSegment>>;
template class PolylineGenerator<LineLayoutVertex, SegmentBase>;
} // namespace gfx
} // namespace mln

View File

@@ -0,0 +1,43 @@
#pragma once
#include <mln/gfx/debug_group.hpp>
#include <mln/util/color.hpp>
#include <mln/gfx/renderable.hpp>
#include <cstdint>
namespace mln {
namespace gfx {
class RenderPassDescriptor {
public:
Renderable& renderable;
std::optional<Color> clearColor;
std::optional<float> clearDepth;
std::optional<int32_t> clearStencil;
bool operator!=(const RenderPassDescriptor& other) const {
return other.clearColor != clearColor || other.clearDepth != clearDepth || other.clearStencil != clearStencil ||
other.renderable != renderable;
}
};
class RenderPass {
protected:
RenderPass() = default;
friend class DebugGroup<RenderPass>;
virtual void pushDebugGroup(const char* name) = 0;
virtual void popDebugGroup() = 0;
virtual void addDebugSignpost(const char*) {}
public:
virtual ~RenderPass() = default;
RenderPass(const RenderPass&) = delete;
RenderPass& operator=(const RenderPass&) = delete;
DebugGroup<RenderPass> createDebugGroup(const char* name) { return {*this, name}; }
};
} // namespace gfx
} // namespace mln

View File

@@ -0,0 +1,26 @@
#include <mln/gfx/renderer_backend.hpp>
#include <mln/gfx/backend_scope.hpp>
#include <mln/gfx/context.hpp>
namespace mln {
namespace gfx {
RendererBackend::RendererBackend(const ContextMode contextMode_)
: contextMode(contextMode_),
threadPool(Scheduler::GetBackground(), uniqueID) {}
RendererBackend::RendererBackend(const ContextMode contextMode_, const TaggedScheduler& threadPool_)
: contextMode(contextMode_),
threadPool(threadPool_) {}
RendererBackend::~RendererBackend() = default;
gfx::Context& RendererBackend::getContext() {
assert(BackendScope::exists());
std::call_once(initialized, [this] { context = createContext(); });
assert(context);
return *context;
}
} // namespace gfx
} // namespace mln

View File

@@ -0,0 +1,291 @@
#include <mln/gfx/rendering_stats.hpp>
#include <mln/style/style.hpp>
#include <mln/style/sources/custom_geometry_source.hpp>
#include <mln/style/layers/symbol_layer.hpp>
#include <mln/style/layers/symbol_layer_impl.hpp>
#include <mln/util/monotonic_timer.hpp>
#include <initializer_list>
#include <sstream>
#include <iomanip>
namespace mln {
namespace gfx {
bool RenderingStats::isZero() const {
const auto expectedZeros = {numActiveTextures,
numTextureBindings,
numBuffers,
numVertexBuffers,
numIndexBuffers,
numUniformBuffers,
numFrameBuffers,
memTextures,
memBuffers,
memIndexBuffers,
memVertexBuffers,
memUniformBuffers};
return std::ranges::all_of(expectedZeros, [](auto x) { return x == 0; });
}
RenderingStats& RenderingStats::operator+=(const RenderingStats& r) {
encodingTime += r.encodingTime;
renderingTime += r.renderingTime;
numFrames += r.numFrames;
numDrawCalls += r.numDrawCalls;
totalDrawCalls += r.totalDrawCalls;
numCreatedTextures += r.numCreatedTextures;
numActiveTextures += r.numActiveTextures;
numTextureBindings += r.numTextureBindings;
numTextureUpdates += r.numTextureUpdates;
textureUpdateBytes += r.textureUpdateBytes;
totalBuffers += r.totalBuffers;
totalBufferObjs += r.totalBufferObjs;
bufferUpdates += r.bufferUpdates;
bufferObjUpdates += r.bufferObjUpdates;
bufferUpdateBytes += r.bufferUpdateBytes;
numBuffers += r.numBuffers;
numFrameBuffers += r.numFrameBuffers;
numIndexBuffers += r.numIndexBuffers;
indexUpdateBytes += r.indexUpdateBytes;
numVertexBuffers += r.numVertexBuffers;
vertexUpdateBytes += r.vertexUpdateBytes;
numUniformBuffers += r.numUniformBuffers;
numUniformUpdates += r.numUniformUpdates;
uniformUpdateBytes += r.uniformUpdateBytes;
memTextures += r.memTextures;
memBuffers += r.memBuffers;
memIndexBuffers += r.memIndexBuffers;
memVertexBuffers += r.memVertexBuffers;
memUniformBuffers += r.memUniformBuffers;
stencilClears += r.stencilClears;
stencilUpdates += r.stencilUpdates;
return *this;
}
#if !defined(NDEBUG)
template <typename T>
std::ostream& optionalStatLine(std::ostream& stream, T value, std::string_view label, std::string_view sep) {
if (value) {
stream << label << " = " << value << sep;
}
return stream;
}
std::string RenderingStats::toString(std::string_view sep) const {
std::stringstream ss;
ss.precision(2);
optionalStatLine(ss, encodingTime, "encodingTime", sep);
optionalStatLine(ss, renderingTime, "renderingTime", sep);
optionalStatLine(ss, numFrames, "numFrames", sep);
optionalStatLine(ss, numDrawCalls, "numDrawCalls", sep);
optionalStatLine(ss, totalDrawCalls, "totalDrawCalls", sep);
optionalStatLine(ss, numCreatedTextures, "numCreatedTextures", sep);
optionalStatLine(ss, numActiveTextures, "numActiveTextures", sep);
optionalStatLine(ss, numTextureBindings, "numTextureBindings", sep);
optionalStatLine(ss, numTextureUpdates, "numTextureUpdates", sep);
optionalStatLine(ss, textureUpdateBytes, "textureUpdateBytes", sep);
optionalStatLine(ss, totalBuffers, "totalBuffers", sep);
optionalStatLine(ss, totalBufferObjs, "totalBufferObjs", sep);
optionalStatLine(ss, bufferUpdates, "bufferUpdates", sep);
optionalStatLine(ss, bufferObjUpdates, "bufferObjUpdates", sep);
optionalStatLine(ss, bufferUpdateBytes, "bufferUpdateBytes", sep);
optionalStatLine(ss, numBuffers, "numBuffers", sep);
optionalStatLine(ss, numFrameBuffers, "numFrameBuffers", sep);
optionalStatLine(ss, numIndexBuffers, "numIndexBuffers", sep);
optionalStatLine(ss, indexUpdateBytes, "indexUpdateBytes", sep);
optionalStatLine(ss, numVertexBuffers, "numVertexBuffers", sep);
optionalStatLine(ss, vertexUpdateBytes, "vertexUpdateBytes", sep);
optionalStatLine(ss, numUniformBuffers, "numUniformBuffers", sep);
optionalStatLine(ss, numUniformUpdates, "numUniformUpdates", sep);
optionalStatLine(ss, uniformUpdateBytes, "uniformUpdateBytes", sep);
optionalStatLine(ss, memTextures, "memTextures", sep);
optionalStatLine(ss, memBuffers, "memBuffers", sep);
optionalStatLine(ss, memIndexBuffers, "memIndexBuffers", sep);
optionalStatLine(ss, memVertexBuffers, "memVertexBuffers", sep);
optionalStatLine(ss, memUniformBuffers, "memUniformBuffers", sep);
optionalStatLine(ss, stencilClears, "stencilClears", sep);
optionalStatLine(ss, stencilUpdates, "stencilUpdates", sep);
return ss.str();
}
#endif
void RenderingStatsView::create(style::Style& style) {
if (!style.getSource(sourceID)) {
style::CustomGeometrySource::Options sourceOptions;
sourceOptions.zoomRange = {0, 0};
sourceOptions.fetchTileFunction = [&](const CanonicalTileID& tileID) {
auto source = static_cast<style::CustomGeometrySource*>(style.getSource(sourceID));
if (!source) {
return;
}
mln::FeatureCollection features;
mapbox::geojson::feature feature;
feature.geometry = mapbox::geometry::geometry<double>(Point<double>{0, 0});
features.emplace_back(feature);
source->setTileData(tileID, features);
};
style.addSource(std::make_unique<style::CustomGeometrySource>(sourceID, sourceOptions));
}
if (!style.getLayer(layerID)) {
auto infoLayer = std::make_unique<style::SymbolLayer>(layerID, sourceID);
// required
infoLayer->setSymbolScreenSpace(true);
infoLayer->setTextAllowOverlap(true);
infoLayer->setIconAllowOverlap(true);
infoLayer->setTextIgnorePlacement(true);
// customizable
infoLayer->setTextColor(options.textColor);
infoLayer->setTextSize(options.textSize);
infoLayer->setTextMaxWidth(300.0f);
infoLayer->setTextJustify(mln::style::TextJustifyType::Left);
infoLayer->setTextAnchor(mln::style::SymbolAnchorType::TopRight);
const float translation = mln::util::EXTENT / 2.0f - 100.0f;
infoLayer->setTextTranslateAnchor(mln::style::TranslateAnchorType::Viewport);
infoLayer->setTextTranslate(std::array<float, 2>{translation, translation});
style.addLayer(std::move(infoLayer));
}
}
void RenderingStatsView::destroy(style::Style& style) {
style.removeLayer(layerID);
style.removeSource(sourceID);
}
mln::style::SymbolLayer* RenderingStatsView::getLayer(style::Style& style) {
return static_cast<mln::style::SymbolLayer*>(style.getLayer(layerID));
}
namespace {
template <typename T>
void printNumber(std::stringstream& ss, const std::string_view label, const T& value, bool print) {
if (!print) {
return;
}
ss << label << ": ";
if (value >= 1e9) {
ss << value / 1e9 << "B";
} else if (value >= 1e6) {
ss << value / 1e6 << "M";
} else if (value >= 1e3) {
ss << value / 1e3 << "K";
} else {
ss << value;
}
ss << "\n";
};
template <typename T>
void printMemory(std::stringstream& ss, const std::string_view label, const T& value, bool print) {
if (!print) {
return;
}
constexpr auto base = 1024;
constexpr auto kb = base;
constexpr auto mb = kb * base;
constexpr auto gb = mb * base;
ss << label << ": ";
if (value >= gb) {
ss << value / gb << "GB";
} else if (value >= mb) {
ss << value / mb << "MB";
} else if (value >= kb) {
ss << value / kb << "KB";
} else {
ss << value << "B";
}
ss << "\n";
};
} // namespace
void RenderingStatsView::update(style::Style& style, const gfx::RenderingStats& stats) {
auto layer = getLayer(style);
// style reloaded? layer got removed?
if (!layer) {
create(style);
layer = getLayer(style);
}
if (!layer) {
return;
}
++frameCount;
encodingTime += stats.encodingTime;
renderingTime += stats.renderingTime;
const auto currentTime = util::MonotonicTimer::now().count();
if (currentTime - lastUpdate < options.updateInterval) {
return;
}
std::stringstream ss;
ss << std::setprecision(3) << std::fixed;
ss << "Encoding time (ms): " << std::setw(7) << encodingTime / frameCount * 1000 << "\n";
ss << "Rendering time (ms): " << std::setw(7) << renderingTime / frameCount * 1000 << "\n";
printNumber(ss, "Frame count", stats.numFrames, true);
printNumber(ss, "Draw calls", stats.numDrawCalls, true);
printNumber(ss, "Total draw calls", stats.totalDrawCalls, options.verbose);
printNumber(ss, "Textures", stats.numActiveTextures, true);
printNumber(ss, "Total textures", stats.numCreatedTextures, options.verbose);
printNumber(ss, "Texture updates", stats.numTextureUpdates, options.verbose);
printMemory(ss, "Texture updates", stats.textureUpdateBytes, options.verbose);
printNumber(ss, "Buffers", stats.numBuffers, true);
printNumber(ss, "Total buffers", stats.totalBuffers, options.verbose);
printNumber(ss, "Total buffer updates", stats.bufferUpdates, options.verbose);
printMemory(ss, "Total buffer updates", stats.bufferUpdateBytes, options.verbose);
printNumber(ss, "Index buffers", stats.numIndexBuffers, true);
printMemory(ss, "Index buffers updates", stats.indexUpdateBytes, options.verbose);
printNumber(ss, "Vertex buffers", stats.numVertexBuffers, true);
printMemory(ss, "Vertex buffers updates", stats.vertexUpdateBytes, options.verbose);
printNumber(ss, "Uniform buffers", stats.numUniformBuffers, true);
printNumber(ss, "Uniform buffer updates", stats.numUniformUpdates, options.verbose);
printMemory(ss, "Uniform buffer updates", stats.uniformUpdateBytes, options.verbose);
printMemory(ss, "Texture memory", stats.memTextures, true);
printMemory(ss, "Buffer memory", stats.memBuffers, true);
printMemory(ss, "Index buffer memory", stats.memIndexBuffers, true);
printMemory(ss, "Vertex buffer memory", stats.memVertexBuffers, true);
printMemory(ss, "Uniform buffer memory", stats.memUniformBuffers, true);
printNumber(ss, "Stencil buffer clears", stats.stencilClears, true);
printNumber(ss, "Stencil buffer updates", stats.stencilUpdates, options.verbose);
layer->setTextField(mln::style::expression::Formatted(ss.str().c_str()));
frameCount = 0;
encodingTime = 0.0;
renderingTime = 0.0;
lastUpdate = currentTime;
}
} // namespace gfx
} // namespace mln

View File

@@ -0,0 +1,51 @@
#include <mln/gfx/shader_group.hpp>
#include <mln/gfx/shader.hpp>
namespace mln {
namespace gfx {
bool ShaderGroup::isShader(const std::string& shaderName) const noexcept {
std::shared_lock<std::shared_mutex> readerLock(programLock);
return programs.contains(shaderName);
}
const std::shared_ptr<gfx::Shader> ShaderGroup::getShader(const std::string& shaderName) const noexcept {
std::shared_lock<std::shared_mutex> readerLock(programLock);
const auto it = programs.find(shaderName);
if (it == programs.end()) {
return nullptr;
}
return it->second;
}
bool ShaderGroup::replaceShader(std::shared_ptr<gfx::Shader>&& shader) noexcept {
return replaceShader(std::move(shader), std::string{shader->typeName()});
}
bool ShaderGroup::replaceShader(std::shared_ptr<Shader>&& shader, const std::string& shaderName) noexcept {
std::unique_lock<std::shared_mutex> writerLock(programLock);
if (!programs.contains(shaderName)) {
return false;
}
programs[shaderName] = std::move(shader);
return true;
}
bool ShaderGroup::registerShader(std::shared_ptr<gfx::Shader>&& shader) noexcept {
return registerShader(std::move(shader), std::string{shader->typeName()});
}
bool ShaderGroup::registerShader(std::shared_ptr<Shader>&& shader, const std::string& shaderName) noexcept {
std::unique_lock<std::shared_mutex> writerLock(programLock);
if (programs.contains(shaderName)) {
return false;
}
programs.emplace(shaderName, std::move(shader));
return true;
}
} // namespace gfx
} // namespace mln

View File

@@ -0,0 +1,50 @@
#include <mln/gfx/shader_registry.hpp>
#include <mln/gfx/shader_group.hpp>
namespace mln {
namespace gfx {
ShaderRegistry::ShaderRegistry()
: legacyGroup() {}
ShaderGroup& ShaderRegistry::getLegacyGroup() noexcept {
return legacyGroup;
}
bool ShaderRegistry::isShaderGroup(const std::string& shaderGroupName) const noexcept {
std::shared_lock<std::shared_mutex> readerLock(shaderGroupLock);
return shaderGroups.contains(shaderGroupName);
}
const ShaderGroupPtr ShaderRegistry::getShaderGroup(const std::string& shaderGroupName) const noexcept {
std::shared_lock<std::shared_mutex> readerLock(shaderGroupLock);
const auto it = shaderGroups.find(shaderGroupName);
if (it == shaderGroups.end()) {
return nullptr;
}
return it->second;
}
bool ShaderRegistry::replaceShader(ShaderGroupPtr&& shader, const std::string& shaderGroupName) noexcept {
std::unique_lock<std::shared_mutex> writerLock(shaderGroupLock);
if (!shaderGroups.contains(shaderGroupName)) {
return false;
}
shaderGroups[shaderGroupName] = std::move(shader);
return true;
}
bool ShaderRegistry::registerShaderGroup(ShaderGroupPtr&& shader, const std::string& shaderGroupName) noexcept {
std::unique_lock<std::shared_mutex> writerLock(shaderGroupLock);
if (shaderGroups.contains(shaderGroupName)) {
return false;
}
shaderGroups.emplace(shaderGroupName, std::move(shader));
return true;
}
} // namespace gfx
} // namespace mln

View File

@@ -0,0 +1,47 @@
#pragma once
#include <mln/gfx/drawable_data.hpp>
#include <mln/style/types.hpp>
#include <mln/util/tiny_unordered_map.hpp>
#include <memory>
namespace mln {
namespace style {
enum class SymbolType : uint8_t;
} // namespace style
namespace gfx {
struct SymbolDrawableData : public DrawableData {
SymbolDrawableData(const bool isHalo_,
const bool bucketVariablePlacement_,
const style::SymbolType symbolType_,
const style::AlignmentType pitchAlignment_,
const style::AlignmentType rotationAlignment_,
const style::SymbolPlacementType placement_,
const style::IconTextFitType textFit_,
const bool isOffset_)
: isHalo(isHalo_),
bucketVariablePlacement(bucketVariablePlacement_),
symbolType(symbolType_),
pitchAlignment(pitchAlignment_),
rotationAlignment(rotationAlignment_),
placement(placement_),
textFit(textFit_),
isOffset(isOffset_) {}
~SymbolDrawableData() override = default;
const bool isHalo;
bool bucketVariablePlacement;
const style::SymbolType symbolType;
const style::AlignmentType pitchAlignment;
const style::AlignmentType rotationAlignment;
const style::SymbolPlacementType placement;
const style::IconTextFitType textFit;
const bool isOffset;
};
using UniqueSymbolDrawableData = std::unique_ptr<SymbolDrawableData>;
} // namespace gfx
} // namespace mln

49
src/mln/gfx/uniform.hpp Normal file
View File

@@ -0,0 +1,49 @@
#pragma once
#include <mln/util/type_list.hpp>
#include <mln/util/indexed_tuple.hpp>
#include <array>
#define MBGL_DEFINE_UNIFORM_SCALAR(type_, name_) \
struct name_ { \
using Value = type_; \
static constexpr auto name() { \
return #name_; \
} \
}
#define MBGL_DEFINE_UNIFORM_VECTOR(type_, n_, name_) \
struct name_ { \
using Value = std::array<type_, n_>; \
static constexpr auto name() { \
return #name_; \
} \
}
#define MBGL_DEFINE_UNIFORM_MATRIX(type_, n_, name_) \
struct name_ { \
using Value = std::array<type_, n_ * n_>; \
static constexpr auto name() { \
return #name_; \
} \
}
namespace mln {
namespace gfx {
template <class>
class UniformValues;
template <class... Us>
class UniformValues<TypeList<Us...>> final : public IndexedTuple<TypeList<Us...>, TypeList<typename Us::Value...>> {
using Base = IndexedTuple<TypeList<Us...>, TypeList<typename Us::Value...>>;
public:
template <class... Args>
UniformValues(Args&&... args)
: Base(std::forward<Args>(args)...) {}
};
} // namespace gfx
} // namespace mln

View File

@@ -0,0 +1,56 @@
#include <mln/gfx/uniform_buffer.hpp>
#include <mln/gfx/context.hpp>
namespace mln {
namespace gfx {
std::shared_ptr<UniformBuffer> UniformBufferArray::nullref = nullptr;
UniformBufferArray::UniformBufferArray(UniformBufferArray&& other)
: uniformBufferVector(std::move(other.uniformBufferVector)) {}
UniformBufferArray& UniformBufferArray::operator=(UniformBufferArray&& other) {
uniformBufferVector = std::move(other.uniformBufferVector);
return *this;
}
UniformBufferArray& UniformBufferArray::operator=(const UniformBufferArray& other) {
for (size_t id = 0; id < other.uniformBufferVector.size(); id++) {
uniformBufferVector[id] = other.uniformBufferVector[id];
}
return *this;
}
const std::shared_ptr<UniformBuffer>& UniformBufferArray::get(const size_t id) const {
return (id < uniformBufferVector.size()) ? uniformBufferVector[id] : nullref;
}
const std::shared_ptr<UniformBuffer>& UniformBufferArray::set(const size_t id,
std::shared_ptr<UniformBuffer> uniformBuffer) {
assert(id < uniformBufferVector.size());
if (id >= uniformBufferVector.size()) {
return nullref;
}
uniformBufferVector[id] = std::move(uniformBuffer);
return uniformBufferVector[id];
}
void UniformBufferArray::createOrUpdate(const size_t id,
const std::vector<uint8_t>& data,
gfx::Context& context,
bool persistent) {
createOrUpdate(id, data.data(), data.size(), context, persistent);
}
void UniformBufferArray::createOrUpdate(
const size_t id, const void* data, const std::size_t size, gfx::Context& context, bool persistent) {
if (auto& ubo = get(id); ubo && ubo->getSize() == size) {
ubo->update(data, size);
} else {
uniformBufferVector[id] = context.createUniformBuffer(data, size, persistent);
}
}
} // namespace gfx
} // namespace mln

102
src/mln/gfx/upload_pass.hpp Normal file
View File

@@ -0,0 +1,102 @@
#pragma once
#include <mln/gfx/attribute.hpp>
#include <mln/gfx/debug_group.hpp>
#include <mln/gfx/vertex_vector.hpp>
#include <mln/gfx/vertex_buffer.hpp>
#include <mln/gfx/index_vector.hpp>
#include <mln/gfx/index_buffer.hpp>
#include <mln/util/size.hpp>
#include <mln/util/image.hpp>
#include <mln/gfx/texture2d.hpp>
#include <optional>
#include <string>
#include <vector>
namespace mln {
namespace gfx {
class Conext;
class Texture2D;
class VertexAttributeArray;
using AttributeBindingArray = std::vector<std::optional<gfx::AttributeBinding>>;
using Texture2DPtr = std::shared_ptr<Texture2D>;
class UploadPass {
protected:
UploadPass() = default;
friend class DebugGroup<UploadPass>;
virtual void pushDebugGroup(const char* name) = 0;
virtual void popDebugGroup() = 0;
public:
virtual ~UploadPass() = default;
UploadPass(const UploadPass&) = delete;
UploadPass& operator=(const UploadPass&) = delete;
DebugGroup<UploadPass> createDebugGroup(const char* name) { return {*this, name}; }
// NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage)
DebugGroup<UploadPass> createDebugGroup(std::string_view name) { return createDebugGroup(name.data()); }
virtual Context& getContext() = 0;
virtual const Context& getContext() const = 0;
public:
template <class Vertex>
VertexBuffer<Vertex> createVertexBuffer(const VertexVector<Vertex>& v,
const BufferUsageType usage = BufferUsageType::StaticDraw,
bool persistent = false) {
return {v.elements(), createVertexBufferResource(v.data(), v.bytes(), usage, persistent)};
}
template <class Vertex>
void updateVertexBuffer(VertexBuffer<Vertex>& buffer, const VertexVector<Vertex>& v) {
assert(v.elements() == buffer.elements);
updateVertexBufferResource(buffer.getResource(), v.data(), v.bytes());
}
template <class DrawMode>
IndexBuffer createIndexBuffer(IndexVector<DrawMode>&& v,
const BufferUsageType usage = BufferUsageType::StaticDraw,
bool persistent = false) {
return {v.elements(), createIndexBufferResource(v.data(), v.bytes(), usage, persistent)};
}
template <class DrawMode>
void updateIndexBuffer(IndexBuffer& buffer, IndexVector<DrawMode>&& v) {
assert(v.elements() == buffer.elements);
updateIndexBufferResource(buffer.getResource(), v.data(), v.bytes());
}
virtual gfx::AttributeBindingArray buildAttributeBindings(
const std::size_t vertexCount,
const gfx::AttributeDataType vertexType,
const std::size_t vertexAttributeIndex,
const std::vector<std::uint8_t>& vertexData,
const gfx::VertexAttributeArray& defaults,
const gfx::VertexAttributeArray& overrides,
gfx::BufferUsageType,
const std::optional<std::chrono::duration<double>> lastUpdate,
/*out*/ std::vector<std::unique_ptr<gfx::VertexBufferResource>>& outBuffers) = 0;
protected:
virtual std::unique_ptr<VertexBufferResource> createVertexBufferResource(const void* data,
std::size_t size,
BufferUsageType,
bool persistent = false) = 0;
virtual void updateVertexBufferResource(VertexBufferResource&, const void* data, std::size_t size) = 0;
public:
virtual std::unique_ptr<IndexBufferResource> createIndexBufferResource(const void* data,
std::size_t size,
BufferUsageType,
bool persistent = false) = 0;
virtual void updateIndexBufferResource(IndexBufferResource&, const void* data, std::size_t size) = 0;
};
} // namespace gfx
} // namespace mln

View File

@@ -0,0 +1,132 @@
#include <mln/gfx/vertex_attribute.hpp>
#include <mln/gfx/vertex_vector.hpp>
#include <algorithm>
#include <numeric>
#include <optional>
namespace mln {
namespace gfx {
namespace {
const UniqueVertexAttribute nullref;
} // namespace
std::size_t VertexAttribute::getStrideOf(gfx::AttributeDataType type) {
switch (type) {
case gfx::AttributeDataType::Byte:
return 1;
case gfx::AttributeDataType::Byte2:
return 2;
case gfx::AttributeDataType::Byte3:
return 3;
case gfx::AttributeDataType::Byte4:
return 4;
case gfx::AttributeDataType::UByte:
return 1;
case gfx::AttributeDataType::UByte2:
return 2;
case gfx::AttributeDataType::UByte3:
return 3;
case gfx::AttributeDataType::UByte4:
return 4;
case gfx::AttributeDataType::Short:
return 2;
case gfx::AttributeDataType::Short2:
return 4;
case gfx::AttributeDataType::Short3:
return 6;
case gfx::AttributeDataType::Short4:
return 8;
case gfx::AttributeDataType::UShort:
return 2;
case gfx::AttributeDataType::UShort2:
return 4;
case gfx::AttributeDataType::UShort3:
return 6;
case gfx::AttributeDataType::UShort4:
return 8;
case gfx::AttributeDataType::UShort8:
return 16;
case gfx::AttributeDataType::Int:
return 4;
case gfx::AttributeDataType::Int2:
return 8;
case gfx::AttributeDataType::Int3:
return 12;
case gfx::AttributeDataType::Int4:
return 16;
case gfx::AttributeDataType::UInt:
return 4;
case gfx::AttributeDataType::UInt2:
return 8;
case gfx::AttributeDataType::UInt3:
return 12;
case gfx::AttributeDataType::UInt4:
return 16;
case gfx::AttributeDataType::Float:
return 4;
case gfx::AttributeDataType::Float2:
return 8;
case gfx::AttributeDataType::Float3:
return 12;
case gfx::AttributeDataType::Float4:
return 16;
default:
return 0;
}
}
std::size_t VertexAttribute::getCount() const {
return sharedRawData ? sharedRawData->getRawCount() : items.size();
}
const std::unique_ptr<VertexAttribute> VertexAttributeArray::nullref;
const std::string VertexAttributeArray::attributePrefix = "a_";
VertexAttributeArray::VertexAttributeArray(VertexAttributeArray&& other)
: attrs(std::move(other.attrs)) {}
VertexAttributeArray& VertexAttributeArray::operator=(VertexAttributeArray&& other) {
attrs = std::move(other.attrs);
return *this;
}
const std::unique_ptr<VertexAttribute>& VertexAttributeArray::get(const size_t id) const {
return (id < attrs.size()) ? attrs[id] : nullref;
}
const std::unique_ptr<VertexAttribute>& VertexAttributeArray::set(const size_t id,
int index,
AttributeDataType dataType,
std::size_t count) {
assert(id < attrs.size());
if (id >= attrs.size()) {
return nullref;
}
attrs[id] = create(index, dataType, count);
return attrs[id];
}
std::size_t VertexAttributeArray::getTotalSize() const {
return std::accumulate(attrs.begin(), attrs.end(), std::size_t(0), [](const auto acc, const auto& attr) {
return acc + (attr ? attr->getStride() : 0);
});
}
std::size_t VertexAttributeArray::getMinCount() const {
return std::accumulate(
attrs.begin(), attrs.end(), std::numeric_limits<std::size_t>::max(), [](const auto acc, const auto& attr) {
return std::min(acc, (attr ? attr->getCount() : std::numeric_limits<std::size_t>::max()));
});
}
void VertexAttributeArray::clear() {
for (auto& attr : attrs) {
attr = nullptr;
}
}
} // namespace gfx
} // namespace mln

View File

@@ -0,0 +1,131 @@
#pragma once
#include <mln/util/ignore.hpp>
#include <mln/util/monotonic_timer.hpp>
#include <memory>
#include <vector>
namespace mln {
namespace gfx {
struct VertexBufferBase {
virtual ~VertexBufferBase() = default;
};
class VertexVectorBase {
public:
VertexVectorBase() = default;
VertexVectorBase(const VertexVectorBase&) {} // buffer is not copied
VertexVectorBase(VertexVectorBase&& other)
: buffer(std::move(other.buffer)),
dirty(other.dirty),
released(other.released) {}
virtual ~VertexVectorBase() = default;
virtual const void* getRawData() const = 0;
virtual std::size_t getRawSize() const = 0;
virtual std::size_t getRawCount() const = 0;
VertexBufferBase* getBuffer() const { return buffer.get(); }
void setBuffer(std::unique_ptr<VertexBufferBase>&& value) { buffer = std::move(value); }
std::chrono::duration<double> getLastModified() const { return lastModified; }
bool isModifiedAfter(std::chrono::duration<double> t) const { return t < lastModified; }
void updateModified(bool force = false) {
if (dirty || force) {
lastModified = util::MonotonicTimer::now();
dirty = false;
}
}
// Indicates that the owner/producer will not modify this again
bool isReleased() const { return released; }
protected:
std::unique_ptr<VertexBufferBase> buffer;
bool dirty = true;
bool released = false;
std::chrono::duration<double> lastModified = util::MonotonicTimer::now();
};
using VertexVectorBasePtr = std::shared_ptr<VertexVectorBase>;
template <class V>
class VertexVector final : public VertexVectorBase {
public:
using Vertex = V;
VertexVector() = default;
VertexVector(const VertexVector<V>& other)
: VertexVectorBase(other),
v(other.v) {}
VertexVector(VertexVector<V>&& other)
: VertexVectorBase(static_cast<VertexVectorBase&&>(other)),
v(std::move(other.v)) {}
~VertexVector() override = default;
template <class... Args>
void emplace_back(Args&&... args) {
assert(!released);
util::ignore({(v.emplace_back(std::forward<Args>(args)), 0)...});
dirty = true;
}
void extend(std::size_t n, const Vertex& val) {
assert(!released);
v.resize(v.size() + n, val);
dirty = true;
}
Vertex& at(std::size_t n) {
assert(n < v.size());
assert(!released);
dirty = true;
return v.at(n);
}
const Vertex& at(std::size_t n) const {
assert(n < v.size());
return v.at(n);
}
std::size_t elements() const { return v.size(); }
std::size_t bytes() const { return v.size() * sizeof(Vertex); }
bool empty() const { return v.empty(); }
void clear() {
dirty = true;
v.clear();
}
void reserve(std::size_t count) { v.reserve(count); }
/// Indicate that this shared vertex vector instance will no longer be updated.
void release() {
// If we've already created a buffer, we don't need the raw data any more.
if (buffer) {
v.clear();
}
released = true;
}
const Vertex* data() const { return v.data(); }
const std::vector<Vertex>& vector() const { return v; }
const void* getRawData() const override { return v.data(); }
std::size_t getRawSize() const override { return sizeof(Vertex); }
std::size_t getRawCount() const override { return v.size(); }
private:
std::vector<Vertex> v;
};
template <typename T>
using VertexVectorPtr = std::shared_ptr<VertexVector<T>>;
} // namespace gfx
} // namespace mln

19
src/mln/gl/attribute.cpp Normal file
View File

@@ -0,0 +1,19 @@
#include <mln/gl/attribute.hpp>
#include <mln/gl/context.hpp>
namespace mln {
namespace gl {
using namespace platform;
std::optional<AttributeLocation> queryLocation(ProgramID id, const char* name) {
GLint attributeLocation = MBGL_CHECK_ERROR(glGetAttribLocation(id, name));
if (attributeLocation != -1) {
return attributeLocation;
} else {
return {};
}
}
} // namespace gl
} // namespace mln

133
src/mln/gl/attribute.hpp Normal file
View File

@@ -0,0 +1,133 @@
#pragma once
#include <mln/gfx/attribute.hpp>
#include <mln/gl/types.hpp>
#include <mln/shaders/attributes.hpp>
#include <mln/util/literal.hpp>
#include <algorithm>
#include <optional>
#include <string>
#include <type_traits>
#include <vector>
namespace mln {
namespace gl {
using AttributeBindingArray = std::vector<std::optional<gfx::AttributeBinding>>;
using NamedAttributeLocations = std::vector<std::pair<const std::string, AttributeLocation>>;
std::optional<AttributeLocation> queryLocation(ProgramID id, const char* name);
template <class>
class AttributeLocations;
template <class... As>
class AttributeLocations<TypeList<As...>> final {
private:
using Locations = IndexedTuple<TypeList<As...>, TypeList<ExpandToType<As, std::optional<AttributeLocation>>...>>;
Locations locations;
public:
AttributeLocations() = default;
void queryLocations(const ProgramID& id) {
locations = Locations{
queryLocation(id, concat_literals<&string_literal<'a', '_'>::value, &As::name>::value())...};
}
bool hasExpectedFirstLocation() const {
using TypeOfFirst = typename std::tuple_element_t<0, std::tuple<As...>>;
const auto location = locations.template get<TypeOfFirst>();
return location && *location == 0;
}
static constexpr const char* getFirstAttribName() {
// Static assert that attribute list starts with position: we bind it on location 0.
using TypeOfFirst = typename std::tuple_element_t<0, std::tuple<As...>>;
static_assert(std::is_same_v<attributes::pos, TypeOfFirst> ||
std::is_same_v<attributes::pos_offset, TypeOfFirst> ||
std::is_same_v<attributes::pos_normal, TypeOfFirst>,
"Program must start with position related attribute.");
return concat_literals<&string_literal<'a', '_'>::value, TypeOfFirst::name>::value();
}
NamedAttributeLocations getNamedLocations() const {
NamedAttributeLocations result;
auto maybeAddLocation = [&](const std::string& name, const std::optional<AttributeLocation>& location) {
if (location) {
result.emplace_back(name, *location);
}
};
util::ignore({(maybeAddLocation(concat_literals<&string_literal<'a', '_'>::value, &As::name>::value(),
locations.template get<As>()),
0)...});
return result;
}
AttributeBindingArray toBindingArray(const gfx::AttributeBindings<TypeList<As...>>& bindings) const {
AttributeBindingArray result;
std::optional<AttributeLocation> maxLocation;
auto updateMaxLocation = [&](const std::optional<AttributeLocation>& location) {
if (location) {
maxLocation = maxLocation ? std::max(*maxLocation, *location) : *location;
}
};
util::ignore({(updateMaxLocation(locations.template get<As>()), 0)...});
if (maxLocation) {
result.resize(*maxLocation + 1);
}
auto maybeAddBinding = [&](const std::optional<AttributeLocation>& location,
const std::optional<gfx::AttributeBinding>& binding) {
if (location) {
result[*location] = binding;
}
};
util::ignore({(maybeAddBinding(locations.template get<As>(), bindings.template get<As>()), 0)...});
return result;
}
};
template <class>
class AttributeKey;
constexpr auto attributeDefinePrefix() {
return "#define HAS_UNIFORM_u_";
}
template <class... As>
class AttributeKey<TypeList<As...>> final {
public:
static_assert(sizeof...(As) <= 32, "attribute count exceeds 32");
static uint32_t compute(const gfx::AttributeBindings<TypeList<As...>>& bindings) {
uint32_t value = 0;
util::ignore(
{(bindings.template get<As>() ? (void)(value |= 1 << TypeIndex<As, As...>::value) : (void)0, 0)...});
return value;
}
static std::string defines(const gfx::AttributeBindings<TypeList<As...>>& bindings) {
std::string result;
util::ignore(
{(!bindings.template get<As>()
? (void)(result +=
concat_literals<&attributeDefinePrefix, &As::name, &string_literal<'\n'>::value>::value())
: (void)0,
0)...});
return result;
}
};
} // namespace gl
} // namespace mln

View File

@@ -0,0 +1,521 @@
#include <mln/gl/buffer_allocator.hpp>
#include <mln/gl/defines.hpp>
#include <mln/gl/context.hpp>
#include <mln/gl/uniform_buffer_gl.hpp>
#include <mln/util/instrumentation.hpp>
#include <utility>
namespace mln {
using namespace platform;
namespace gl {
/// @brief BufferAllocator is a semi-generic allocation strategy for uniform buffer objects.
/// This allocator works by streaming buffer writes into sub-allocated sections of actual UBOs.
/// As UBOs fill up, they are marked 'in-flight' and placed in a waiting area for references to
/// be released. Once all references to a UBO are gone (Or the buffer is defragmented), the
/// buffer is recycled.
/// @tparam OwnerClass The class type having allocations managed by this allocator.
/// @tparam type GL_UNIFORM_BUFFER, no other type is currently valid.
/// @tparam PageSizeKB Size of underlying UBO allocations, in KB. 8KB has been observed to work well.
/// @tparam MaxFragmentationOccupancy Buffers under this percentage of occupancy are considered fragmented
/// and the defragmentation routine will attempt to relocate living references to them to other buffers.
/// @tparam MaxFreeBuffers When buffers are made free for recycling, buffers in excess of this amount are
/// released to reduce memory consumption.
/// @tparam InitialBufferSize Initial size of the reference list. 128 has been observed to work well.
template <typename OwnerClass, // UniformBufferGL
GLenum type, // GL_UNIFORM_BUFFER
size_t PageSizeKB = 8,
size_t MaxFragmentationOccupancy = 10,
size_t MaxFreeBuffers = 48,
size_t InitialBufferSize = 256>
class BufferAllocator : public IBufferAllocator {
static_assert(type == GL_UNIFORM_BUFFER);
public:
static constexpr const size_t PageSize = 1024 * PageSizeKB;
static constexpr const size_t FragmentedOccupancyLevel = static_cast<size_t>(
PageSize * (static_cast<double>(MaxFragmentationOccupancy) / 100.0));
using BufferTy =
BufferAllocator<OwnerClass, type, PageSizeKB, MaxFragmentationOccupancy, MaxFreeBuffers, InitialBufferSize>;
using RefTy = TypedBufferRef<OwnerClass>;
~BufferAllocator() override = default;
private:
/// @brief A buffer in-flight is one that is being used by the GPU. Such buffers become free
/// once their `frameSync` fence becomes signaled.
struct InFlightBuffer {
size_t bufferIndex; // Index into buffers list
std::shared_ptr<gl::Fence> frameSync;
InFlightBuffer(size_t buf, std::shared_ptr<gl::Fence> sync)
: bufferIndex(buf),
frameSync(std::move(sync)) {}
};
public:
/// @brief An allocated buffer the allocator manages. These buffers are sub-allocated
/// in monotonic fashion. Once a buffer's capacity has been reached, it is stored until
/// it can be recycled and a new buffer is acquired to replace it.
struct Buffer {
BufferTy& allocator;
// The pointer points to the next unused region of memory in the buffer
ptrdiff_t pointer = 0;
// References to buffer objects allocating from this block of memory.
// For performance reasons, we use a vector here. To deal with vector storage reallocation,
// vector resizes are handled explicitly and Refs are made aware of relocation events.
std::vector<RefTy> refs;
// Backing GL object for this buffer
GLuint id = 0;
// Tombstones indicating a removed ref. When a Ref is removed, a tombstone is added to this
// counter. This is much faster vs. erasing from our vector. This works because we enforce
// a monotonic allocation scheme on our buffers and recycle the whole buffer at once.
size_t tombstones = 0;
// Used to drive defragmentation, occupancy is a measure of the number of bytes held by
// living references
size_t occupancyBytes = 0;
// We need to know the index to ourselves in the main buffer list
size_t bufferIndex = 0;
// If true, the buffer's memory has been released to compact memory
bool reclaimed = false;
Buffer(BufferAllocator& allocator_)
: allocator(allocator_) {
refs.reserve(InitialBufferSize);
MBGL_CHECK_ERROR(glGenBuffers(1, &id));
MBGL_CHECK_ERROR(glBindBuffer(type, id));
MBGL_CHECK_ERROR(glBufferData(type, PageSize, nullptr, GL_DYNAMIC_DRAW));
}
~Buffer() {
if (id != 0) {
glDeleteBuffers(1, &id);
id = 0;
}
}
void reclaim() {
refs.clear();
refs = decltype(refs)();
if (id != 0) {
glDeleteBuffers(1, &id);
id = 0;
}
}
Buffer(const Buffer&) = delete;
Buffer(Buffer&& rhs) noexcept
: allocator(rhs.allocator),
pointer(rhs.pointer),
refs(std::move(rhs.refs)),
id(rhs.id),
tombstones(rhs.tombstones),
occupancyBytes(rhs.occupancyBytes),
bufferIndex(rhs.bufferIndex) {
rhs.id = 0;
}
Buffer& operator=(const Buffer&) = delete;
Buffer& operator=(Buffer&& rhs) noexcept {
allocator = rhs.allocator;
pointer = rhs.pointer;
refs = std::move(rhs.refs);
id = rhs.id;
rhs.id = 0;
tombstones = rhs.tombstones;
occupancyBytes = rhs.occupancyBytes;
bufferIndex = rhs.bufferIndex;
return *this;
}
void setBufferIndex(size_t index) noexcept { bufferIndex = index; }
// Reset this buffer for a fresh recording session
void reset() {
assert(refs.size() - tombstones == 0);
pointer = tombstones = occupancyBytes = 0;
refs.clear();
}
/// @brief Once the buffer is full, we mark it 'in-flight', storing it in another location
/// in the allocator. The defragmentation routine will traverse this location for buffers
/// that can be recycled.
/// @param index The buffer index that maps to this buffer instance.
void markInFlight(size_t index) noexcept { allocator.inFlightBuffers.emplace_back(index); }
/// @brief Number of living references to this buffer
size_t numRefs() const noexcept { return refs.size() - tombstones; }
/// @brief Add a living reference to a sub-allocated region of this buffer
/// @param bufObj Managed instance that owns this sub-allocation
/// @param bufPtr Offset into the buffer the allocation begins at
/// @param size_ Size of the allocated region
/// @return BufferRef instance
RefTy* addRef(OwnerClass* bufObj, ptrdiff_t bufPtr, size_t size_) {
// Important: Check if our list needs to reallocate, if so we need to handle it manually
// so we can update the references with their new memory locations
if (refs.size() == refs.capacity()) {
auto oldList = std::exchange(refs, {});
refs.reserve(oldList.size() * 2);
for (size_t i = 0; i < oldList.size(); ++i) {
if (!oldList[i].getOwner()) {
// Released ref, skip it
continue;
}
// Relocate the reference
refs.push_back(std::move(oldList[i]));
refs.back().getOwner()->getManagedBuffer().relocRef(&refs.back());
}
// Since we dropped released refs, we can clear the tombstone count
tombstones = 0;
}
refs.emplace_back(bufObj, bufferIndex, bufPtr, size_);
occupancyBytes += size_;
return &refs.back();
}
/// @brief Remove a living reference from this buffer.
/// @note: Remember buffers are only permitted to grow monotonically. A buffer can only be
/// reused once all living references are removed or relocated to other buffers.
/// @param bufObj BufferRef to remove
void decRef(RefTy* bufObj) {
#ifndef NDEBUG
assert(bufObj->getOwner());
// Sanity check this ref actually belongs
auto it = std::find(refs.begin(), refs.end(), *bufObj);
if (it != refs.end()) {
tombstones++;
} else {
assert(0);
}
#else
tombstones++;
#endif
bufObj->setOwner(nullptr);
assert(occupancyBytes >= bufObj->getSize());
occupancyBytes -= bufObj->getSize();
}
// Align a pointer on a set block size
static size_t align(size_t ptr, size_t alignment) {
if (ptr == alignment) {
return ptr;
}
return ((ptr + alignment) / alignment) * alignment;
}
};
public:
// Write a block of memory to the recording buffer
// Returns the base index of written memory in the buffer
bool write(const void* data, size_t size, BufferRef*& residentBuffer) noexcept override {
Buffer* buffer = nullptr;
if (buffers.size() == 0) {
// Query for the alignment we must use
int32_t alignment = 0;
MBGL_CHECK_ERROR(glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &alignment));
sharedBufferAlignment = static_cast<size_t>(alignment);
recordingBuffer = newBuffer();
}
buffer = &buffers[recordingBuffer];
const auto alignedSize = Buffer::align(size, sharedBufferAlignment);
// Reached past the buffer range
if ((buffer->pointer + alignedSize) > PageSize) {
// Put the buffer in the pending list
buffer->markInFlight(recordingBuffer);
buffer = nullptr;
// Look for a free buffer
const auto freeIndex = getFreeBuffer();
if (freeIndex != -1) {
recordingBuffer = freeIndex;
} else {
// Otherwise, allocate a fresh buffer
recordingBuffer = newBuffer();
}
buffer = &buffers[recordingBuffer];
}
assert(buffer);
if (!buffer) {
assert(0);
return false;
}
MBGL_CHECK_ERROR(glBindBuffer(type, buffer->id));
// Map the next available slice of memory
auto* buf = MBGL_CHECK_ERROR(
glMapBufferRange(type,
buffer->pointer,
alignedSize,
GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_WRITE_BIT));
const auto writtenIndex = buffer->pointer;
if (buf) {
std::memcpy(buf, data, size);
MBGL_CHECK_ERROR(glUnmapBuffer(type));
residentBuffer = buffer->addRef(nullptr, writtenIndex, size);
buffer->pointer += alignedSize;
} else {
assert(0);
return false;
}
#ifndef NDEBUG
MBGL_CHECK_ERROR(glBindBuffer(type, 0));
#endif
return true;
}
/// Release a living reference
void release(BufferRef* ref) noexcept override {
assert(ref);
if (!ref) {
return;
}
buffers[ref->getBufferIndex()].decRef(static_cast<RefTy*>(ref));
}
// Look for buffers that either have zero living references or equal or under FragmentationThresh
// references. In the latter case, attempt to relocate these allocations to more utilized buffers.
void defragment(const std::shared_ptr<gl::Fence>& fence) override {
MLN_TRACE_FUNC();
if (buffers.size() == 0) {
return;
}
intptr_t recycleIndex = recordingBuffer;
// Phase one: Mark free buffers
updateInFlight();
// Phase two: Consolidate fragmentation
MBGL_CHECK_ERROR(glBindBuffer(type, buffers[recycleIndex].id));
for (auto it = inFlightBuffers.begin(); it != inFlightBuffers.end();) {
auto& fragBuffer = buffers[*it];
if (fragBuffer.occupancyBytes > FragmentedOccupancyLevel) {
++it;
continue;
}
// This buffer is fragmented. Move allocations from this buffer and append to fresh ones
// so that this one may be recycled.
// For each ref, append to new buffer and update that ref
for (auto refIt = fragBuffer.refs.begin(); refIt != fragBuffer.refs.end();) {
if (!refIt->getOwner()) {
++refIt;
continue; // This is a released reference
}
const auto alignedSize = Buffer::align(refIt->getOwner()->getSize(), sharedBufferAlignment);
// 2.a: Get recycle buffer
if (recycleIndex == -1 || (buffers[recycleIndex].pointer + alignedSize) > PageSize) {
if (recycleIndex != -1) {
// We filled this one up, mark it in-flight and get a fresh one.
buffers[recycleIndex].markInFlight(recycleIndex);
}
// We need to get a new buffer.
recycleIndex = recordingBuffer = getFreeBuffer();
if (recycleIndex == -1) {
// No more buffers available, we're done.
break;
}
MBGL_CHECK_ERROR(glBindBuffer(type, buffers[recycleIndex].id));
}
// 2.b: Copy into recycle buffer
if (!moveRef(*refIt, recycleIndex, alignedSize)) {
// Relocation failure, stop trying to defrag.
#ifndef NDEBUG
MBGL_CHECK_ERROR(glBindBuffer(GL_UNIFORM_BUFFER, 0));
#endif
return;
}
}
// All refs have relocated, clear this buffer.
if (fragBuffer.numRefs() == 0) {
fragBuffer.reset();
// Store this buffer in a waiting area until the GPU is done using it
waitingFree.emplace_back(fragBuffer.bufferIndex, fence);
it = inFlightBuffers.erase(it);
} else {
// We only managed a partial (or no) defrag. Do nothing with this buffer.
++it;
}
// No more buffers to write into, stop.
if (recycleIndex == -1) {
break;
}
}
#ifndef NDEBUG
MBGL_CHECK_ERROR(glBindBuffer(GL_UNIFORM_BUFFER, 0));
#endif
if (recordingBuffer == -1) {
recordingBuffer = newBuffer();
}
}
size_t pageSize() const noexcept override { return PageSize; }
int32_t getBufferID(size_t bufferIndex) const noexcept override { return buffers[bufferIndex].id; }
private:
size_t newBuffer() noexcept {
buffers.emplace_back(*this);
const auto index = recordingBuffer = buffers.size() - 1;
buffers[index].setBufferIndex(index);
return index;
}
intptr_t getFreeBuffer() {
if (freeList.size() > 0) {
assert(buffers[freeList.back()].pointer == 0);
const auto index = freeList.back();
freeList.pop_back();
return index;
} else if (reclaimedList.size() > 0) {
const auto index = reclaimedList.back();
reclaimedList.pop_back();
new (&buffers[index]) Buffer(*this);
buffers[index].setBufferIndex(index);
return index;
}
return -1;
}
// Check waiting in-flight buffers and move them to the free list
void updateInFlight() {
for (auto it = waitingFree.begin(); it != waitingFree.end();) {
if (it->frameSync->isSignaled()) {
freeList.push_back(it->bufferIndex);
it = waitingFree.erase(it);
} else {
++it;
}
}
// And check the free list for an excess of buffers, reclaim if we need to
while (freeList.size() > MaxFreeBuffers) {
reclaimedList.push_back(freeList.back());
freeList.pop_back();
buffers[reclaimedList.back()].reclaim();
}
}
// Move a buffer reference (An allocation from a UniformBufferGL instance) to a new buffer
bool moveRef(RefTy& ref, size_t toIndex, size_t alignedSize) {
auto& destBuffer = buffers[toIndex];
const auto recycledWriteIndex = destBuffer.pointer;
auto* buf = MBGL_CHECK_ERROR(
glMapBufferRange(type,
recycledWriteIndex,
alignedSize,
GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_WRITE_BIT));
if (buf) {
std::memcpy(buf, ref.getOwner()->getManagedBuffer().getContents().data(), ref.getOwner()->getSize());
MBGL_CHECK_ERROR(glUnmapBuffer(type));
destBuffer.pointer += alignedSize;
} else {
assert(0);
return false;
}
// 2.c: Now the ref must be made aware of the relocation of its contents.
// Note that this means defragmentation can never run on refs that are
// expected to remain bound after defragmentation. They must be re-bound
// to become valid again.
auto owner = ref.getOwner();
const auto refSize = ref.getSize();
const auto oldIndex = ref.getBufferIndex();
const auto newRef = destBuffer.addRef(owner, recycledWriteIndex, refSize);
buffers[oldIndex].decRef(&ref);
owner->getManagedBuffer().relocRef(newRef);
return true;
}
private:
// GL requires us to satisfy this alignment for sub-allocations in our buffers
size_t sharedBufferAlignment = 0;
// Our active recording buffer
intptr_t recordingBuffer = 0;
// Buffers free for re-use
std::vector<size_t> freeList;
// Buffers reclaimed that can be recreated if needed
std::vector<size_t> reclaimedList;
// Buffers recorded and currently in-use
std::list<size_t> inFlightBuffers;
// When we recycle a buffer, we copy from an in-flight buffer to a fresh one.
// Before we can convert the in-flight buffer to a free one however, we must wait
// after the copy operation(s) to ensure the GPU is done with it before we reuse it.
std::list<InFlightBuffer> waitingFree;
public:
// All buffers allocated so far
std::vector<Buffer> buffers;
friend Buffer;
};
class UniformBufferAllocator::Impl : public gl::BufferAllocator<gl::UniformBufferGL, GL_UNIFORM_BUFFER> {};
UniformBufferAllocator::~UniformBufferAllocator() = default;
UniformBufferAllocator::UniformBufferAllocator() {
impl = std::make_unique<UniformBufferAllocator::Impl>();
}
bool UniformBufferAllocator::write(const void* data, size_t size, BufferRef*& ref) noexcept {
return impl->write(data, size, ref);
}
void UniformBufferAllocator::release(BufferRef* ref) noexcept {
impl->release(ref);
}
void UniformBufferAllocator::defragment(const std::shared_ptr<gl::Fence>& fence) {
impl->defragment(fence);
}
size_t UniformBufferAllocator::pageSize() const noexcept {
return impl->pageSize();
}
int32_t UniformBufferAllocator::getBufferID(size_t bufferIndex) const noexcept {
return impl->getBufferID(bufferIndex);
}
} // namespace gl
} // namespace mln

View File

@@ -0,0 +1,59 @@
#include <mln/gl/command_encoder.hpp>
#include <mln/gl/upload_pass.hpp>
#include <mln/gl/render_pass.hpp>
#include <mln/gl/context.hpp>
#include <mln/gl/renderable_resource.hpp>
#include <mln/gl/debugging_extension.hpp>
#include <mln/platform/gl_functions.hpp>
#include <cstring>
namespace mln {
namespace gl {
CommandEncoder::~CommandEncoder() {
const auto debugGroup(createDebugGroup("cleanup"));
context.performCleanup();
}
std::unique_ptr<gfx::UploadPass> CommandEncoder::createUploadPass(const char* name, gfx::Renderable&) {
return std::make_unique<gl::UploadPass>(*this, name);
}
std::unique_ptr<gfx::RenderPass> CommandEncoder::createRenderPass(const char* name,
const gfx::RenderPassDescriptor& descriptor) {
return std::make_unique<gl::RenderPass>(*this, name, descriptor);
}
void CommandEncoder::present(gfx::Renderable& renderable) {
renderable.getResource<gl::RenderableResource>().swap();
}
void CommandEncoder::pushDebugGroup(const char* name) {
(void)name;
#ifndef NDEBUG
if (auto debugging = context.getDebuggingExtension()) {
if (debugging->pushDebugGroup) {
MBGL_CHECK_ERROR(
debugging->pushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 0, platform::GLsizei(strlen(name)), name));
} else if (debugging->pushGroupMarkerEXT) {
MBGL_CHECK_ERROR(debugging->pushGroupMarkerEXT(platform::GLsizei(strlen(name) + 1), name));
}
}
#endif
}
void CommandEncoder::popDebugGroup() {
#ifndef NDEBUG
if (auto debugging = context.getDebuggingExtension()) {
if (debugging->popDebugGroup) {
MBGL_CHECK_ERROR(debugging->popDebugGroup());
} else if (debugging->popGroupMarkerEXT) {
MBGL_CHECK_ERROR(debugging->popGroupMarkerEXT());
}
}
#endif
}
} // namespace gl
} // namespace mln

View File

@@ -0,0 +1,33 @@
#pragma once
#include <mln/gfx/command_encoder.hpp>
namespace mln {
namespace gl {
class Context;
class CommandEncoder final : public gfx::CommandEncoder {
public:
explicit CommandEncoder(gl::Context& context_)
: context(context_) {}
~CommandEncoder() override;
friend class UploadPass;
friend class RenderPass;
std::unique_ptr<gfx::UploadPass> createUploadPass(const char* name, gfx::Renderable&) override;
std::unique_ptr<gfx::RenderPass> createRenderPass(const char* name, const gfx::RenderPassDescriptor&) override;
void present(gfx::Renderable&) override;
private:
void pushDebugGroup(const char* name) override;
void popDebugGroup() override;
public:
gl::Context& context;
};
} // namespace gl
} // namespace mln

886
src/mln/gl/context.cpp Normal file
View File

@@ -0,0 +1,886 @@
#include <mln/gl/context.hpp>
#include <mln/gfx/shader_registry.hpp>
#include <mln/gl/command_encoder.hpp>
#include <mln/gl/defines.hpp>
#include <mln/gl/draw_scope_resource.hpp>
#include <mln/gl/enum.hpp>
#include <mln/gl/renderer_backend.hpp>
#include <mln/gl/renderbuffer_resource.hpp>
#include <mln/gl/offscreen_texture.hpp>
#include <mln/gl/debugging_extension.hpp>
#include <mln/gl/timestamp_query_extension.hpp>
#include <mln/renderer/paint_parameters.hpp>
#include <mln/util/traits.hpp>
#include <mln/util/std.hpp>
#include <mln/util/logging.hpp>
#include <mln/util/instrumentation.hpp>
#include <mln/util/thread_pool.hpp>
#include <mln/gl/drawable_gl.hpp>
#include <mln/gl/drawable_gl_builder.hpp>
#include <mln/gl/dynamic_texture.hpp>
#include <mln/gl/layer_group_gl.hpp>
#include <mln/gl/uniform_buffer_gl.hpp>
#include <mln/gl/texture2d.hpp>
#include <mln/renderer/render_target.hpp>
#include <mln/shaders/gl/shader_program_gl.hpp>
#include <cstring>
#include <iterator>
namespace mln {
namespace gl {
using namespace platform;
static_assert(underlying_type(ShaderType::Vertex) == GL_VERTEX_SHADER, "OpenGL type mismatch");
static_assert(underlying_type(ShaderType::Fragment) == GL_FRAGMENT_SHADER, "OpenGL type mismatch");
static_assert(std::is_same_v<ProgramID, GLuint>, "OpenGL type mismatch");
static_assert(std::is_same_v<ShaderID, GLuint>, "OpenGL type mismatch");
static_assert(std::is_same_v<BufferID, GLuint>, "OpenGL type mismatch");
static_assert(std::is_same_v<TextureID, GLuint>, "OpenGL type mismatch");
static_assert(std::is_same_v<VertexArrayID, GLuint>, "OpenGL type mismatch");
static_assert(std::is_same_v<FramebufferID, GLuint>, "OpenGL type mismatch");
static_assert(std::is_same_v<RenderbufferID, GLuint>, "OpenGL type mismatch");
static_assert(underlying_type(UniformDataType::Float) == GL_FLOAT, "OpenGL type mismatch");
static_assert(underlying_type(UniformDataType::FloatVec2) == GL_FLOAT_VEC2, "OpenGL type mismatch");
static_assert(underlying_type(UniformDataType::FloatVec3) == GL_FLOAT_VEC3, "OpenGL type mismatch");
static_assert(underlying_type(UniformDataType::FloatVec4) == GL_FLOAT_VEC4, "OpenGL type mismatch");
static_assert(underlying_type(UniformDataType::Int) == GL_INT, "OpenGL type mismatch");
static_assert(underlying_type(UniformDataType::IntVec2) == GL_INT_VEC2, "OpenGL type mismatch");
static_assert(underlying_type(UniformDataType::IntVec3) == GL_INT_VEC3, "OpenGL type mismatch");
static_assert(underlying_type(UniformDataType::IntVec4) == GL_INT_VEC4, "OpenGL type mismatch");
static_assert(underlying_type(UniformDataType::Bool) == GL_BOOL, "OpenGL type mismatch");
static_assert(underlying_type(UniformDataType::BoolVec2) == GL_BOOL_VEC2, "OpenGL type mismatch");
static_assert(underlying_type(UniformDataType::BoolVec3) == GL_BOOL_VEC3, "OpenGL type mismatch");
static_assert(underlying_type(UniformDataType::BoolVec4) == GL_BOOL_VEC4, "OpenGL type mismatch");
static_assert(underlying_type(UniformDataType::FloatMat2) == GL_FLOAT_MAT2, "OpenGL type mismatch");
static_assert(underlying_type(UniformDataType::FloatMat3) == GL_FLOAT_MAT3, "OpenGL type mismatch");
static_assert(underlying_type(UniformDataType::FloatMat4) == GL_FLOAT_MAT4, "OpenGL type mismatch");
static_assert(underlying_type(UniformDataType::Sampler2D) == GL_SAMPLER_2D, "OpenGL type mismatch");
static_assert(underlying_type(UniformDataType::SamplerCube) == GL_SAMPLER_CUBE, "OpenGL type mismatch");
namespace {
GLint getMaxVertexAttribs() {
GLint value = 0;
MBGL_CHECK_ERROR(glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &value));
return value;
}
// Currently renderBufferByteSize is only used when Tracy profiling is enabled
#ifdef MLN_TRACY_ENABLE
constexpr size_t renderBufferByteSize(const gfx::RenderbufferPixelType type, const Size size) noexcept {
size_t sz = size.width * size.height;
switch (type) {
case gfx::RenderbufferPixelType::RGBA:
[[fallthrough]];
case gfx::RenderbufferPixelType::DepthStencil:
sz *= 4;
break;
case gfx::RenderbufferPixelType::Depth:
sz *= 2;
break;
}
return sz;
}
#endif
} // namespace
Context::Context(RendererBackend& backend_)
: gfx::Context(/*maximumVertexBindingCount=*/getMaxVertexAttribs()),
backend(backend_) {
uboAllocator = std::make_unique<gl::UniformBufferAllocator>();
texturePool = std::make_unique<Texture2DPool>(this);
}
Context::~Context() noexcept {
if (cleanupOnDestruction) {
backend.getThreadPool().runRenderJobs(true /* closeQueue */);
glUseProgram(0);
for (size_t i = 0; i < globalUniformBuffers.allocatedSize(); i++) {
globalUniformBuffers.set(i, nullptr);
}
reset();
// Delete all pooled resources while the context is still valid
texturePool.reset();
uboAllocator.reset();
#if !defined(NDEBUG)
Log::Debug(Event::General, "Rendering Stats:\n" + stats.toString("\n"));
#endif
assert(stats.isZero());
}
}
void Context::beginFrame() {
MLN_TRACE_FUNC();
backend.getThreadPool().runRenderJobs();
frameInFlightFence = std::make_shared<gl::Fence>();
// Run allocator defragmentation on this frame interval.
constexpr auto defragFreq = 4;
if (frameNum == defragFreq) {
uboAllocator->defragment(frameInFlightFence);
frameNum = 0;
} else {
frameNum++;
}
}
void Context::endFrame() {
MLN_TRACE_FUNC();
if (!frameInFlightFence) {
return;
}
frameInFlightFence->insert();
}
void Context::initializeExtensions(const std::function<gl::ProcAddress(const char*)>& getProcAddress) {
MLN_TRACE_FUNC();
if (const auto* extensions = reinterpret_cast<const char*>(MBGL_CHECK_ERROR(glGetString(GL_EXTENSIONS)))) {
auto fn = [&](std::initializer_list<std::pair<const char*, const char*>> probes) -> ProcAddress {
for (auto probe : probes) {
if (strstr(extensions, probe.first) != nullptr) {
if (ProcAddress ptr = getProcAddress(probe.second)) {
return ptr;
}
}
}
return nullptr;
};
static const std::string renderer = []() {
std::string r = reinterpret_cast<const char*>(MBGL_CHECK_ERROR(glGetString(GL_RENDERER)));
Log::Info(Event::General, "GPU Identifier: " + r);
return r;
}();
// Block ANGLE on Direct3D since the debugging extension is causing crashes
if (!(renderer.find("ANGLE") != std::string::npos && renderer.find("Direct3D") != std::string::npos)) {
debugging = std::make_unique<extension::Debugging>(fn);
}
// Currently GL timestamp queries are only used when Tracy profiling is enabled
#ifdef MLN_TRACY_ENABLE
extension::loadTimeStampQueryExtension(fn);
#endif
}
MLN_TRACE_GL_CONTEXT();
}
void Context::enableDebugging() {
if (!debugging || !debugging->debugMessageControl || !debugging->debugMessageCallback) {
return;
}
// This will enable all messages including performance hints
// MBGL_CHECK_ERROR(debugging->debugMessageControl(GL_DONT_CARE,
// GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE));
// This will only enable high and medium severity messages
MBGL_CHECK_ERROR(
debugging->debugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_HIGH, 0, nullptr, GL_TRUE));
MBGL_CHECK_ERROR(
debugging->debugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_MEDIUM, 0, nullptr, GL_TRUE));
MBGL_CHECK_ERROR(debugging->debugMessageControl(
GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_NOTIFICATION, 0, nullptr, GL_FALSE));
MBGL_CHECK_ERROR(debugging->debugMessageCallback(extension::Debugging::DebugCallback, nullptr));
}
UniqueShader Context::createShader(ShaderType type, const std::initializer_list<const char*>& sources) {
UniqueShader result{MBGL_CHECK_ERROR(glCreateShader(static_cast<GLenum>(type))), {this}};
MBGL_CHECK_ERROR(glShaderSource(result, static_cast<GLsizei>(sources.size()), sources.begin(), nullptr));
MBGL_CHECK_ERROR(glCompileShader(result));
GLint status = 0;
MBGL_CHECK_ERROR(glGetShaderiv(result, GL_COMPILE_STATUS, &status));
if (status != 0) {
return result;
}
GLint logLength;
MBGL_CHECK_ERROR(glGetShaderiv(result, GL_INFO_LOG_LENGTH, &logLength));
if (logLength > 0) {
const auto log = std::make_unique<GLchar[]>(logLength);
MBGL_CHECK_ERROR(glGetShaderInfoLog(result, logLength, &logLength, log.get()));
Log::Error(Event::Shader, std::string("Shader failed to compile: ") + log.get());
}
throw std::runtime_error("shader failed to compile");
}
UniqueProgram Context::createProgram(ShaderID vertexShader, ShaderID fragmentShader, const char* location0AttribName) {
UniqueProgram result{MBGL_CHECK_ERROR(glCreateProgram()), {this}};
MBGL_CHECK_ERROR(glAttachShader(result, vertexShader));
MBGL_CHECK_ERROR(glAttachShader(result, fragmentShader));
// It is important to have attribute at position 0 enabled: conveniently,
// position attribute is always first and always enabled. The integrity of
// this assumption is verified in AttributeLocations::queryLocations and
// AttributeLocations::getFirstAttribName.
MBGL_CHECK_ERROR(glBindAttribLocation(result, 0, location0AttribName));
linkProgram(result);
return result;
}
void Context::linkProgram(ProgramID program_) {
MLN_TRACE_FUNC();
MBGL_CHECK_ERROR(glLinkProgram(program_));
verifyProgramLinkage(program_);
}
void Context::verifyProgramLinkage(ProgramID program_) {
MLN_TRACE_FUNC();
GLint status;
MBGL_CHECK_ERROR(glGetProgramiv(program_, GL_LINK_STATUS, &status));
if (status == GL_TRUE) {
return;
}
GLint logLength;
MBGL_CHECK_ERROR(glGetProgramiv(program_, GL_INFO_LOG_LENGTH, &logLength));
const auto log = std::make_unique<GLchar[]>(logLength);
if (logLength > 0) {
MBGL_CHECK_ERROR(glGetProgramInfoLog(program_, logLength, &logLength, log.get()));
Log::Error(Event::Shader, std::string("Program failed to link: ") + log.get());
}
throw std::runtime_error("program failed to link");
}
UniqueTexture Context::createUniqueTexture(const Size& size,
gfx::TexturePixelType format,
gfx::TextureChannelDataType type) {
MLN_TRACE_FUNC();
assert(texturePool);
TextureID id = texturePool->alloc(size, format, type);
// NOLINTNEXTLINE(performance-move-const-arg)
return UniqueTexture{std::move(id), {this}};
}
VertexArray Context::createVertexArray() {
MLN_TRACE_FUNC();
VertexArrayID id = 0;
MBGL_CHECK_ERROR(glGenVertexArrays(1, &id));
// NOLINTNEXTLINE(performance-move-const-arg)
UniqueVertexArray vao(std::move(id), {this});
return {UniqueVertexArrayState(new VertexArrayState(std::move(vao)), VertexArrayStateDeleter{true})};
}
UniqueFramebuffer Context::createFramebuffer() {
MLN_TRACE_FUNC();
FramebufferID id = 0;
MBGL_CHECK_ERROR(glGenFramebuffers(1, &id));
stats.numFrameBuffers++;
// NOLINTNEXTLINE(performance-move-const-arg)
return UniqueFramebuffer{std::move(id), {this}};
}
std::unique_ptr<gfx::RenderbufferResource> Context::createRenderbufferResource(const gfx::RenderbufferPixelType type,
const Size size) {
MLN_TRACE_FUNC();
RenderbufferID id = 0;
MBGL_CHECK_ERROR(glGenRenderbuffers(1, &id));
MLN_TRACE_ALLOC_RT(id, renderBufferByteSize(type, size));
// NOLINTNEXTLINE(performance-move-const-arg)
UniqueRenderbuffer renderbuffer{std::move(id), {this}};
bindRenderbuffer = renderbuffer;
MBGL_CHECK_ERROR(
glRenderbufferStorage(GL_RENDERBUFFER, Enum<gfx::RenderbufferPixelType>::to(type), size.width, size.height));
bindRenderbuffer = 0;
return std::make_unique<gl::RenderbufferResource>(std::move(renderbuffer));
}
std::unique_ptr<uint8_t[]> Context::readFramebuffer(const Size size,
const gfx::TexturePixelType format,
const bool flip) {
MLN_TRACE_FUNC();
MLN_TRACE_FUNC_GL();
const size_t stride = size.width * (format == gfx::TexturePixelType::RGBA ? 4 : 1);
auto data = std::make_unique<uint8_t[]>(stride * size.height);
// When reading data from the framebuffer, make sure that we are storing the
// values tightly packed into the buffer to avoid buffer overruns.
pixelStorePack = {1};
MBGL_CHECK_ERROR(glReadPixels(
0, 0, size.width, size.height, Enum<gfx::TexturePixelType>::to(format), GL_UNSIGNED_BYTE, data.get()));
if (flip) {
auto tmp = std::make_unique<uint8_t[]>(stride);
uint8_t* rgba = data.get();
for (int i = 0, j = size.height - 1; i < j; i++, j--) {
std::memcpy(tmp.get(), rgba + i * stride, stride);
std::memcpy(rgba + i * stride, rgba + j * stride, stride);
std::memcpy(rgba + j * stride, tmp.get(), stride);
}
}
return data;
}
namespace {
void checkFramebuffer() {
MLN_TRACE_FUNC();
GLenum status = MBGL_CHECK_ERROR(glCheckFramebufferStatus(GL_FRAMEBUFFER));
if (status != GL_FRAMEBUFFER_COMPLETE) {
switch (status) {
case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
throw std::runtime_error("Couldn't create framebuffer: incomplete attachment");
case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
throw std::runtime_error(
"Couldn't create framebuffer: incomplete missing "
"attachment");
#ifdef GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER
case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
throw std::runtime_error("Couldn't create framebuffer: incomplete draw buffer");
#endif
#ifdef GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER
case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
throw std::runtime_error("Couldn't create framebuffer: incomplete read buffer");
#endif
#ifdef GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS
case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
throw std::runtime_error("Couldn't create framebuffer: incomplete dimensions");
#endif
case GL_FRAMEBUFFER_UNSUPPORTED:
throw std::runtime_error("Couldn't create framebuffer: unsupported");
default:
throw std::runtime_error("Couldn't create framebuffer: other");
}
}
}
void bindDepthStencilRenderbuffer(const gfx::Renderbuffer<gfx::RenderbufferPixelType::DepthStencil>& depthStencil) {
MLN_TRACE_FUNC();
MLN_TRACE_FUNC_GL();
auto& depthStencilResource = depthStencil.getResource<gl::RenderbufferResource>();
#ifdef GL_DEPTH_STENCIL_ATTACHMENT
MBGL_CHECK_ERROR(glFramebufferRenderbuffer(
GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthStencilResource.renderbuffer));
#else
MBGL_CHECK_ERROR(glFramebufferRenderbuffer(
GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthStencilResource.renderbuffer));
MBGL_CHECK_ERROR(glFramebufferRenderbuffer(
GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthStencilResource.renderbuffer));
#endif
}
} // namespace
Framebuffer Context::createFramebuffer(
const gfx::Renderbuffer<gfx::RenderbufferPixelType::RGBA>& color,
const gfx::Renderbuffer<gfx::RenderbufferPixelType::DepthStencil>& depthStencil) {
MLN_TRACE_FUNC();
if (color.getSize() != depthStencil.getSize()) {
throw std::runtime_error("Renderbuffer size mismatch");
}
auto fbo = createFramebuffer();
bindFramebuffer = fbo;
auto& colorResource = color.getResource<gl::RenderbufferResource>();
MBGL_CHECK_ERROR(
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorResource.renderbuffer));
bindDepthStencilRenderbuffer(depthStencil);
checkFramebuffer();
return {color.getSize(), std::move(fbo)};
}
Framebuffer Context::createFramebuffer(const gfx::Renderbuffer<gfx::RenderbufferPixelType::RGBA>& color) {
MLN_TRACE_FUNC();
auto fbo = createFramebuffer();
bindFramebuffer = fbo;
auto& colorResource = color.getResource<gl::RenderbufferResource>();
MBGL_CHECK_ERROR(
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorResource.renderbuffer));
checkFramebuffer();
return {color.getSize(), std::move(fbo)};
}
std::unique_ptr<gfx::OffscreenTexture> Context::createOffscreenTexture(const Size size,
const gfx::TextureChannelDataType type) {
MLN_TRACE_FUNC();
return std::make_unique<gl::OffscreenTexture>(*this, size, type);
}
std::unique_ptr<gfx::DrawScopeResource> Context::createDrawScopeResource() {
MLN_TRACE_FUNC();
return std::make_unique<gl::DrawScopeResource>(createVertexArray());
}
void Context::reset() {
MLN_TRACE_FUNC();
performCleanup();
// Delete all unused pooled resources
texturePool->shrink();
}
void Context::resetState(gfx::DepthMode depthMode, gfx::ColorMode colorMode) {
MLN_TRACE_FUNC();
MLN_TRACE_FUNC_GL();
// Reset GL state to a known state so the CustomLayer always has a clean slate.
bindVertexArray = value::BindVertexArray::Default;
setDepthMode(depthMode);
setStencilMode(gfx::StencilMode::disabled());
setColorMode(colorMode);
setCullFaceMode(gfx::CullFaceMode::disabled());
setScissorTest({0, 0, 0, 0});
}
bool Context::emplaceOrUpdateUniformBuffer(gfx::UniformBufferPtr& buffer,
const void* data,
std::size_t size,
bool persistent) {
MLN_TRACE_FUNC();
if (buffer) {
buffer->update(data, size);
return false;
} else {
buffer = createUniformBuffer(data, size, persistent);
return true;
}
}
void Context::bindGlobalUniformBuffers(gfx::RenderPass&) const noexcept {
MLN_TRACE_FUNC();
globalUniformBuffers.bind();
}
void Context::unbindGlobalUniformBuffers(gfx::RenderPass&) const noexcept {
MLN_TRACE_FUNC();
globalUniformBuffers.unbind();
}
void Context::setDirtyState() {
MLN_TRACE_FUNC();
// Note: does not set viewport/bindFramebuffer to dirty
// since they are handled separately in the view object.
stencilFunc.setDirty();
stencilMask.setDirty();
stencilTest.setDirty();
stencilOp.setDirty();
#if MLN_RENDER_BACKEND_OPENGL
depthRange.setDirty();
#endif
depthMask.setDirty();
depthTest.setDirty();
depthFunc.setDirty();
blend.setDirty();
blendEquation.setDirty();
blendFunc.setDirty();
blendColor.setDirty();
colorMask.setDirty();
clearDepth.setDirty();
clearColor.setDirty();
clearStencil.setDirty();
cullFace.setDirty();
cullFaceSide.setDirty();
cullFaceWinding.setDirty();
scissorTest.setDirty();
program.setDirty();
lineWidth.setDirty();
activeTextureUnit.setDirty();
pixelStorePack.setDirty();
pixelStoreUnpack.setDirty();
for (auto& tex : texture) {
tex.setDirty();
}
vertexBuffer.setDirty();
bindVertexArray.setDirty();
globalVertexArrayState.setDirty();
}
gfx::UniqueDrawableBuilder Context::createDrawableBuilder(std::string name) {
MLN_TRACE_FUNC();
return std::make_unique<gl::DrawableGLBuilder>(std::move(name));
}
gfx::UniformBufferPtr Context::createUniformBuffer(const void* data,
std::size_t size,
bool /*persistent*/,
bool /*ssbo*/) {
MLN_TRACE_FUNC();
return std::make_shared<gl::UniformBufferGL>(*this, data, size, *uboAllocator);
}
gfx::UniqueUniformBufferArray Context::createLayerUniformBufferArray() {
return std::make_unique<UniformBufferArrayGL>();
}
gfx::ShaderProgramBasePtr Context::getGenericShader(gfx::ShaderRegistry& shaders, const std::string& name) {
MLN_TRACE_FUNC();
auto shaderGroup = shaders.getShaderGroup(name);
if (!shaderGroup) {
return nullptr;
}
return std::static_pointer_cast<gfx::ShaderProgramBase>(shaderGroup->getOrCreateShader(*this, {}));
}
TileLayerGroupPtr Context::createTileLayerGroup(int32_t layerIndex, std::size_t initialCapacity, std::string name) {
MLN_TRACE_FUNC();
auto tileLayerGroup = std::make_shared<TileLayerGroupGL>(layerIndex, initialCapacity, std::move(name));
tileLayerGroup->setObserver(observer);
return tileLayerGroup;
}
LayerGroupPtr Context::createLayerGroup(int32_t layerIndex, std::size_t initialCapacity, std::string name) {
MLN_TRACE_FUNC();
auto layerGroup = std::make_shared<LayerGroupGL>(layerIndex, initialCapacity, std::move(name));
layerGroup->setObserver(observer);
return layerGroup;
}
gfx::Texture2DPtr Context::createTexture2D() {
MLN_TRACE_FUNC();
return std::make_shared<gl::Texture2D>(*this);
}
gfx::DynamicTexturePtr Context::createDynamicTexture(Size size, gfx::TexturePixelType pixelType) {
MLN_TRACE_FUNC();
return std::make_shared<DynamicTexture>(*this, size, pixelType);
}
RenderTargetPtr Context::createRenderTarget(const Size size, const gfx::TextureChannelDataType type) {
MLN_TRACE_FUNC();
return std::make_shared<RenderTarget>(*this, size, type);
}
Framebuffer Context::createFramebuffer(const gfx::Texture2D& color) {
MLN_TRACE_FUNC();
auto fbo = createFramebuffer();
bindFramebuffer = fbo;
MBGL_CHECK_ERROR(glFramebufferTexture2D(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D,
static_cast<const gl::Texture2D&>(color).getTextureID(),
0));
checkFramebuffer();
return {color.getSize(), std::move(fbo)};
}
gfx::VertexAttributeArrayPtr Context::createVertexAttributeArray() const {
return std::make_shared<VertexAttributeArrayGL>();
}
void Context::clear(std::optional<mln::Color> color, std::optional<float> depth, std::optional<int32_t> stencil) {
MLN_TRACE_FUNC();
MLN_TRACE_FUNC_GL();
GLbitfield mask = 0;
if (color) {
mask |= GL_COLOR_BUFFER_BIT;
clearColor = *color;
colorMask = value::ColorMask::Default;
}
if (depth) {
mask |= GL_DEPTH_BUFFER_BIT;
clearDepth = *depth;
depthMask = value::DepthMask::Default;
}
if (stencil) {
mask |= GL_STENCIL_BUFFER_BIT;
clearStencil = *stencil;
stencilMask = value::StencilMask::Default;
}
MBGL_CHECK_ERROR(glClear(mask));
stats.numDrawCalls = 0;
stats.numFrames++;
}
void Context::setCullFaceMode(const gfx::CullFaceMode& mode) {
cullFace = mode.enabled;
// These shouldn't need to be updated when face culling is disabled, but we
// might end up having the same issues with Adreno 2xx GPUs as noted in
// Context::setDepthMode.
cullFaceSide = mode.side;
cullFaceWinding = mode.winding;
}
void Context::setScissorTest(const gfx::ScissorRect& rect) {
scissorTest = rect;
}
void Context::setDepthMode(const gfx::DepthMode& depth) {
if (depth.func == gfx::DepthFunctionType::Always && depth.mask != gfx::DepthMaskType::ReadWrite) {
depthTest = false;
// Workaround for rendering errors on Adreno 2xx GPUs. Depth-related
// state should not matter when the depth test is disabled, but on these
// GPUs it apparently does.
// https://github.com/mapbox/mapbox-gl-native/issues/9164
depthFunc = depth.func;
depthMask = depth.mask;
#if MLN_RENDER_BACKEND_OPENGL
depthRange = depth.range;
#endif
} else {
depthTest = true;
depthFunc = depth.func;
depthMask = depth.mask;
#if MLN_RENDER_BACKEND_OPENGL
depthRange = depth.range;
#endif
}
}
void Context::setStencilMode(const gfx::StencilMode& stencil) {
if (stencil.test.is<gfx::StencilMode::Always>() && !stencil.mask) {
stencilTest = false;
} else {
stencilTest = true;
stencilMask = stencil.mask;
stencilOp = {stencil.fail, stencil.depthFail, stencil.pass};
apply_visitor([&](const auto& test) { stencilFunc = {test.func, stencil.ref, test.mask}; }, stencil.test);
}
}
bool Context::hasStencilBuffer() const {
GLint bits = 0;
MBGL_CHECK_ERROR(glGetIntegerv(GL_STENCIL_BITS, &bits));
return bits > 0;
}
void Context::setColorMode(const gfx::ColorMode& color) {
if (color.blendFunction.is<gfx::ColorMode::Replace>()) {
blend = false;
} else {
blend = true;
blendColor = color.blendColor;
apply_visitor(
[&](const auto& blendFunction) {
blendEquation = gfx::ColorBlendEquationType(blendFunction.equation);
blendFunc = {blendFunction.srcFactor, blendFunction.dstFactor};
},
color.blendFunction);
}
colorMask = color.mask;
}
std::unique_ptr<gfx::CommandEncoder> Context::createCommandEncoder() {
MLN_TRACE_FUNC();
backend.updateAssumedState();
if (backend.contextIsShared()) {
setDirtyState();
}
return std::make_unique<gl::CommandEncoder>(*this);
}
void Context::finish() {
MLN_TRACE_FUNC();
MLN_TRACE_FUNC_GL();
MBGL_CHECK_ERROR(glFinish());
}
std::shared_ptr<gl::Fence> Context::getCurrentFrameFence() const {
return frameInFlightFence;
}
void Context::draw(const gfx::DrawMode& drawMode, std::size_t indexOffset, std::size_t indexLength) {
MLN_TRACE_FUNC();
MLN_TRACE_FUNC_GL();
switch (drawMode.type) {
case gfx::DrawModeType::Points:
break;
case gfx::DrawModeType::Lines:
case gfx::DrawModeType::LineLoop:
case gfx::DrawModeType::LineStrip:
lineWidth = drawMode.size;
break;
default:
break;
}
MBGL_CHECK_ERROR(glDrawElements(Enum<gfx::DrawModeType>::to(drawMode.type),
static_cast<GLsizei>(indexLength),
GL_UNSIGNED_SHORT,
reinterpret_cast<GLvoid*>(sizeof(uint16_t) * indexOffset)));
stats.numDrawCalls++;
stats.totalDrawCalls++;
}
void Context::performCleanup() {
MLN_TRACE_FUNC();
#ifndef NDEBUG
// In debug builds, un-bind all texture units so that any incorrect use results in an
// error rather than using whatever texture happened to have been bound previously.
// This takes some time, however, so don't do it in release builds.
{
for (auto i = 0; i < gfx::MaxActiveTextureUnits; i++) {
activeTextureUnit = i;
texture[i] = 0;
}
bindVertexArray = 0;
}
#endif
for (auto id : abandonedPrograms) {
if (program == id) {
program.setDirty();
}
MBGL_CHECK_ERROR(glDeleteProgram(id));
}
abandonedPrograms.clear();
for (auto id : abandonedShaders) {
MBGL_CHECK_ERROR(glDeleteShader(id));
}
abandonedShaders.clear();
if (!abandonedBuffers.empty()) {
for (const auto id : abandonedBuffers) {
if (vertexBuffer == id) {
vertexBuffer.setDirty();
} else if (globalVertexArrayState.indexBuffer == id) {
globalVertexArrayState.indexBuffer.setDirty();
}
}
MBGL_CHECK_ERROR(glDeleteBuffers(int(abandonedBuffers.size()), abandonedBuffers.data()));
stats.numBuffers -= int(abandonedBuffers.size());
abandonedBuffers.clear();
}
if (!abandonedTextures.empty()) {
for (const auto id : abandonedTextures) {
for (auto& binding : texture) {
if (binding == id) {
binding.setDirty();
}
}
texturePool->release(id);
}
abandonedTextures.clear();
}
if (!abandonedVertexArrays.empty()) {
for (const auto id : abandonedVertexArrays) {
if (bindVertexArray == id) {
bindVertexArray.setDirty();
}
}
MBGL_CHECK_ERROR(glDeleteVertexArrays(int(abandonedVertexArrays.size()), abandonedVertexArrays.data()));
abandonedVertexArrays.clear();
}
if (!abandonedFramebuffers.empty()) {
for (const auto id : abandonedFramebuffers) {
if (bindFramebuffer == id) {
bindFramebuffer.setDirty();
}
}
MBGL_CHECK_ERROR(glDeleteFramebuffers(int(abandonedFramebuffers.size()), abandonedFramebuffers.data()));
stats.numFrameBuffers -= int(abandonedFramebuffers.size());
assert(stats.numFrameBuffers >= 0);
abandonedFramebuffers.clear();
}
if (!abandonedRenderbuffers.empty()) {
MBGL_CHECK_ERROR(glDeleteRenderbuffers(int(abandonedRenderbuffers.size()), abandonedRenderbuffers.data()));
abandonedRenderbuffers.clear();
}
}
void Context::reduceMemoryUsage() {
MLN_TRACE_FUNC();
MLN_TRACE_FUNC_GL();
performCleanup();
assert(texturePool);
texturePool->shrink();
// Ensure that all pending actions are executed to ensure that they happen
// before the app goes to the background.
MBGL_CHECK_ERROR(glFinish());
}
#if !defined(NDEBUG)
void Context::visualizeStencilBuffer() {
throw std::runtime_error("Not yet implemented");
}
void Context::visualizeDepthBuffer([[maybe_unused]] const float depthRangeSize) {
throw std::runtime_error("Not yet implemented");
}
#endif
void Context::clearStencilBuffer(const int32_t bits) {
MLN_TRACE_FUNC();
MLN_TRACE_FUNC_GL();
MBGL_CHECK_ERROR(glClearStencil(bits));
MBGL_CHECK_ERROR(glClear(GL_STENCIL_BUFFER_BIT));
stats.stencilClears++;
}
Texture2DPool& Context::getTexturePool() {
assert(texturePool);
return *texturePool;
}
} // namespace gl
} // namespace mln

258
src/mln/gl/context.hpp Normal file
View File

@@ -0,0 +1,258 @@
#pragma once
#include <mln/gfx/draw_mode.hpp>
#include <mln/gfx/depth_mode.hpp>
#include <mln/gfx/stencil_mode.hpp>
#include <mln/gfx/color_mode.hpp>
#include <mln/gfx/context.hpp>
#include <mln/gfx/scissor_rect.hpp>
#include <mln/gl/object.hpp>
#include <mln/gl/state.hpp>
#include <mln/gl/value.hpp>
#include <mln/gl/framebuffer.hpp>
#include <mln/gl/resource_pool.hpp>
#include <mln/gl/vertex_array.hpp>
#include <mln/gl/types.hpp>
#include <mln/platform/gl_functions.hpp>
#include <mln/util/noncopyable.hpp>
#include <mln/gl/fence.hpp>
#include <mln/gl/buffer_allocator.hpp>
#include <mln/gfx/texture2d.hpp>
#include <mln/gl/uniform_buffer_gl.hpp>
#include <array>
#include <functional>
#include <vector>
namespace mln {
namespace gl {
using ProcAddress = void (*)();
class RendererBackend;
namespace extension {
class VertexArray;
class Debugging;
} // namespace extension
class Context final : public gfx::Context {
public:
Context(RendererBackend&);
~Context() noexcept override;
Context(const Context&) = delete;
Context& operator=(const Context& other) = delete;
std::unique_ptr<gfx::CommandEncoder> createCommandEncoder() override;
gfx::ContextObserver& getObserver() const { return *observer; }
void beginFrame() override;
void endFrame() override;
void initializeExtensions(const std::function<gl::ProcAddress(const char*)>&);
void enableDebugging();
UniqueShader createShader(ShaderType type, const std::initializer_list<const char*>& sources);
UniqueProgram createProgram(ShaderID vertexShader, ShaderID fragmentShader, const char* location0AttribName);
void verifyProgramLinkage(ProgramID);
void linkProgram(ProgramID);
UniqueTexture createUniqueTexture(const Size& size, gfx::TexturePixelType format, gfx::TextureChannelDataType type);
Framebuffer createFramebuffer(const gfx::Renderbuffer<gfx::RenderbufferPixelType::RGBA>&,
const gfx::Renderbuffer<gfx::RenderbufferPixelType::DepthStencil>&);
Framebuffer createFramebuffer(const gfx::Renderbuffer<gfx::RenderbufferPixelType::RGBA>&);
template <typename Image,
gfx::TexturePixelType format = Image::channels == 4 ? gfx::TexturePixelType::RGBA
: gfx::TexturePixelType::Alpha>
Image readFramebuffer(const Size size, bool flip = true) {
static_assert(Image::channels == (format == gfx::TexturePixelType::RGBA ? 4 : 1), "image format mismatch");
return {size, readFramebuffer(size, format, flip)};
}
void clear(std::optional<mln::Color> color, std::optional<float> depth, std::optional<int32_t> stencil);
void setDepthMode(const gfx::DepthMode&);
void setStencilMode(const gfx::StencilMode&);
void setColorMode(const gfx::ColorMode&);
void setCullFaceMode(const gfx::CullFaceMode&);
void setScissorTest(const gfx::ScissorRect&);
bool hasStencilBuffer() const;
void draw(const gfx::DrawMode&, std::size_t indexOffset, std::size_t indexLength);
void finish();
std::shared_ptr<gl::Fence> getCurrentFrameFence() const;
// Actually remove the objects we marked as abandoned with the above methods.
// Only call this while the OpenGL context is exclusive to this thread.
// Pooled textures are retained
void performCleanup() override;
// Called when the app receives a memory warning and before it goes to the background.
// Calls performCleanup and destroy all pooled resources
void reduceMemoryUsage() override;
// Drain pools and remove abandoned objects, in preparation for destroying the store.
// Only call this while the OpenGL context is exclusive to this thread.
void reset();
// Returns whether there are any objects that need to be cleaned up.
// If considerPool is true, it will also check if resources are still pooled.
bool empty(bool considerPool = false) const {
return abandonedPrograms.empty() && abandonedShaders.empty() && abandonedBuffers.empty() &&
abandonedTextures.empty() && abandonedVertexArrays.empty() && abandonedFramebuffers.empty() &&
(considerPool ? texturePool->empty() : true);
}
extension::Debugging* getDebuggingExtension() const { return debugging.get(); }
bool getCleanupOnDestruction() { return cleanupOnDestruction; }
void setCleanupOnDestruction(bool cleanup) { cleanupOnDestruction = cleanup; }
gfx::UniqueDrawableBuilder createDrawableBuilder(std::string name) override;
gfx::UniformBufferPtr createUniformBuffer(const void* data,
std::size_t size,
bool persistent = false,
bool ssbo = false) override;
gfx::UniqueUniformBufferArray createLayerUniformBufferArray() override;
gfx::ShaderProgramBasePtr getGenericShader(gfx::ShaderRegistry&, const std::string& name) override;
TileLayerGroupPtr createTileLayerGroup(int32_t layerIndex, std::size_t initialCapacity, std::string name) override;
LayerGroupPtr createLayerGroup(int32_t layerIndex, std::size_t initialCapacity, std::string name) override;
gfx::Texture2DPtr createTexture2D() override;
gfx::DynamicTexturePtr createDynamicTexture(Size size, gfx::TexturePixelType pixelType) override;
RenderTargetPtr createRenderTarget(const Size size, const gfx::TextureChannelDataType type) override;
Framebuffer createFramebuffer(const gfx::Texture2D& color);
gfx::VertexAttributeArrayPtr createVertexAttributeArray() const override;
void resetState(gfx::DepthMode depthMode, gfx::ColorMode colorMode) override;
bool emplaceOrUpdateUniformBuffer(gfx::UniformBufferPtr&,
const void* data,
std::size_t size,
bool persistent) override;
/// Get the global uniform buffers
const gfx::UniformBufferArray& getGlobalUniformBuffers() const override { return globalUniformBuffers; };
/// Get the mutable global uniform buffer array
gfx::UniformBufferArray& mutableGlobalUniformBuffers() override { return globalUniformBuffers; };
/// Bind the global uniform buffers
void bindGlobalUniformBuffers(gfx::RenderPass&) const noexcept override;
/// Unbind the global uniform buffers
void unbindGlobalUniformBuffers(gfx::RenderPass&) const noexcept override;
void setDirtyState() override;
Texture2DPool& getTexturePool();
private:
RendererBackend& backend;
bool cleanupOnDestruction = true;
std::unique_ptr<extension::Debugging> debugging;
std::shared_ptr<gl::Fence> frameInFlightFence;
std::unique_ptr<gl::UniformBufferAllocator> uboAllocator;
size_t frameNum = 0;
UniformBufferArrayGL globalUniformBuffers;
public:
State<value::ActiveTextureUnit> activeTextureUnit;
State<value::BindFramebuffer> bindFramebuffer;
State<value::Viewport> viewport;
State<value::ScissorTest> scissorTest;
std::array<State<value::BindTexture>, gfx::MaxActiveTextureUnits> texture;
State<value::Program> program;
State<value::BindVertexBuffer> vertexBuffer;
State<value::BindVertexArray> bindVertexArray;
VertexArrayState globalVertexArrayState{UniqueVertexArray(0, {const_cast<Context*>(this)})};
State<value::PixelStorePack> pixelStorePack;
State<value::PixelStoreUnpack> pixelStoreUnpack;
private:
State<value::StencilFunc> stencilFunc;
State<value::StencilMask> stencilMask;
State<value::StencilTest> stencilTest;
State<value::StencilOp> stencilOp;
#if MLN_RENDER_BACKEND_OPENGL
State<value::DepthRange> depthRange;
#endif
State<value::DepthMask> depthMask;
State<value::DepthTest> depthTest;
State<value::DepthFunc> depthFunc;
State<value::Blend> blend;
State<value::BlendEquation> blendEquation;
State<value::BlendFunc> blendFunc;
State<value::BlendColor> blendColor;
State<value::ColorMask> colorMask;
State<value::ClearDepth> clearDepth;
State<value::ClearColor> clearColor;
State<value::ClearStencil> clearStencil;
State<value::LineWidth> lineWidth;
State<value::BindRenderbuffer> bindRenderbuffer;
State<value::CullFace> cullFace;
State<value::CullFaceSide> cullFaceSide;
State<value::CullFaceWinding> cullFaceWinding;
public:
std::unique_ptr<gfx::OffscreenTexture> createOffscreenTexture(Size, gfx::TextureChannelDataType) override;
private:
std::unique_ptr<gfx::RenderbufferResource> createRenderbufferResource(gfx::RenderbufferPixelType,
Size size) override;
std::unique_ptr<gfx::DrawScopeResource> createDrawScopeResource() override;
UniqueFramebuffer createFramebuffer();
std::unique_ptr<uint8_t[]> readFramebuffer(Size, gfx::TexturePixelType, bool flip);
public:
VertexArray createVertexArray();
private:
friend detail::ProgramDeleter;
friend detail::ShaderDeleter;
friend detail::BufferDeleter;
friend detail::TextureDeleter;
friend detail::VertexArrayDeleter;
friend detail::FramebufferDeleter;
friend detail::RenderbufferDeleter;
std::vector<ProgramID> abandonedPrograms;
std::vector<ShaderID> abandonedShaders;
std::vector<BufferID> abandonedBuffers;
std::vector<TextureID> abandonedTextures;
std::vector<VertexArrayID> abandonedVertexArrays;
std::vector<FramebufferID> abandonedFramebuffers;
std::vector<RenderbufferID> abandonedRenderbuffers;
std::unique_ptr<Texture2DPool> texturePool;
public:
#if !defined(NDEBUG)
public:
void visualizeStencilBuffer() override;
void visualizeDepthBuffer(float depthRangeSize) override;
#endif
void clearStencilBuffer(int32_t) override;
};
} // namespace gl
} // namespace mln

View File

@@ -0,0 +1,106 @@
#include <mln/gl/debugging_extension.hpp>
#include <mln/util/logging.hpp>
namespace mln {
namespace gl {
namespace extension {
void Debugging::DebugCallback(GLenum source,
GLenum type,
GLuint id,
GLenum severity,
GLsizei /* length */,
const GLchar* message,
const void* /* userParam */
) {
std::string strSource;
switch (source) {
case GL_DEBUG_SOURCE_API:
strSource = "DEBUG_SOURCE_API";
break;
case GL_DEBUG_SOURCE_WINDOW_SYSTEM:
strSource = "DEBUG_SOURCE_WINDOW_SYSTEM";
break;
case GL_DEBUG_SOURCE_SHADER_COMPILER:
strSource = "DEBUG_SOURCE_SHADER_COMPILER";
break;
case GL_DEBUG_SOURCE_THIRD_PARTY:
strSource = "DEBUG_SOURCE_THIRD_PARTY";
break;
case GL_DEBUG_SOURCE_APPLICATION:
strSource = "DEBUG_SOURCE_APPLICATION";
break;
case GL_DEBUG_SOURCE_OTHER:
strSource = "DEBUG_SOURCE_OTHER";
break;
default:
strSource = "(unknown)";
break;
}
std::string strType;
switch (type) {
case GL_DEBUG_TYPE_ERROR:
strType = "DEBUG_TYPE_ERROR";
break;
case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR:
strType = "DEBUG_TYPE_DEPRECATED_BEHAVIOR";
break;
case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:
strType = "DEBUG_TYPE_UNDEFINED_BEHAVIOR";
break;
case GL_DEBUG_TYPE_PERFORMANCE:
strType = "DEBUG_TYPE_PERFORMANCE";
break;
case GL_DEBUG_TYPE_PORTABILITY:
strType = "DEBUG_TYPE_PORTABILITY";
break;
case GL_DEBUG_TYPE_OTHER:
strType = "DEBUG_TYPE_OTHER";
break;
case GL_DEBUG_TYPE_MARKER:
strType = "DEBUG_TYPE_MARKER";
break;
case GL_DEBUG_TYPE_PUSH_GROUP:
strType = "DEBUG_TYPE_OTHER";
break;
case GL_DEBUG_TYPE_POP_GROUP:
strType = "DEBUG_TYPE_POP_GROUP";
break;
default:
strSource = "(unknown)";
break;
}
std::string strSeverity;
mln::EventSeverity evtSeverity;
switch (severity) {
case GL_DEBUG_SEVERITY_HIGH:
strSeverity = "DEBUG_SEVERITY_HIGH";
evtSeverity = mln::EventSeverity::Error;
break;
case GL_DEBUG_SEVERITY_MEDIUM:
strSeverity = "DEBUG_SEVERITY_MEDIUM";
evtSeverity = mln::EventSeverity::Warning;
break;
case GL_DEBUG_SEVERITY_LOW:
strSeverity = "DEBUG_SEVERITY_LOW";
evtSeverity = mln::EventSeverity::Info;
break;
case GL_DEBUG_SEVERITY_NOTIFICATION:
strSeverity = "DEBUG_SEVERITY_NOTIFICATION";
evtSeverity = mln::EventSeverity::Debug;
break;
default:
strSource = "(unknown)";
evtSeverity = mln::EventSeverity::Debug;
break;
}
const std::string logMsg = "GL_" + strSource + " GL_" + strType + " " + std::to_string(id) + " GL_" + strSeverity +
" - " + message;
mln::Log::Record(evtSeverity, mln::Event::OpenGL, logMsg);
}
} // namespace extension
} // namespace gl
} // namespace mln

View File

@@ -0,0 +1,110 @@
#pragma once
#include <mln/gl/extension.hpp>
#include <mln/gl/defines.hpp>
#include <mln/platform/gl_functions.hpp>
#define GL_DEBUG_OUTPUT_SYNCHRONOUS 0x8242
#define GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH 0x8243
#define GL_DEBUG_CALLBACK_FUNCTION 0x8244
#define GL_DEBUG_CALLBACK_USER_PARAM 0x8245
#define GL_DEBUG_SOURCE_API 0x8246
#define GL_DEBUG_SOURCE_WINDOW_SYSTEM 0x8247
#define GL_DEBUG_SOURCE_SHADER_COMPILER 0x8248
#define GL_DEBUG_SOURCE_THIRD_PARTY 0x8249
#define GL_DEBUG_SOURCE_APPLICATION 0x824A
#define GL_DEBUG_SOURCE_OTHER 0x824B
#define GL_DEBUG_TYPE_ERROR 0x824C
#define GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR 0x824D
#define GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR 0x824E
#define GL_DEBUG_TYPE_PORTABILITY 0x824F
#define GL_DEBUG_TYPE_PERFORMANCE 0x8250
#define GL_DEBUG_TYPE_OTHER 0x8251
#define GL_MAX_DEBUG_MESSAGE_LENGTH 0x9143
#define GL_MAX_DEBUG_LOGGED_MESSAGES 0x9144
#define GL_DEBUG_LOGGED_MESSAGES 0x9145
#define GL_DEBUG_SEVERITY_HIGH 0x9146
#define GL_DEBUG_SEVERITY_MEDIUM 0x9147
#define GL_DEBUG_SEVERITY_LOW 0x9148
#define GL_DEBUG_TYPE_MARKER 0x8268
#define GL_DEBUG_TYPE_PUSH_GROUP 0x8269
#define GL_DEBUG_TYPE_POP_GROUP 0x826A
#define GL_DEBUG_SEVERITY_NOTIFICATION 0x826B
#define GL_MAX_DEBUG_GROUP_STACK_DEPTH 0x826C
#define GL_DEBUG_GROUP_STACK_DEPTH 0x826D
#define GL_BUFFER 0x82E0
#define GL_SHADER 0x82E1
#define GL_PROGRAM 0x82E2
#define GL_QUERY 0x82E3
#define GL_PROGRAM_PIPELINE 0x82E4
#define GL_SAMPLER 0x82E6
#define GL_MAX_LABEL_LENGTH 0x82E8
#define GL_DEBUG_OUTPUT 0x92E0
#define GL_CONTEXT_FLAG_DEBUG_BIT 0x00000002
#define GL_DISPLAY_LIST 0x82E7
#define GL_VERTEX_ARRAY 0x8074
#define GL_TRANSFORM_FEEDBACK 0x8E22
#define GL_TEXTURE 0x1702
#define GL_RENDERBUFFER 0x8D41
#define GL_FRAMEBUFFER 0x8D40
namespace mln {
namespace gl {
namespace extension {
using namespace platform;
class Debugging {
public:
using Callback = void (*)(GLenum source,
GLenum type,
GLuint id,
GLenum severity,
GLsizei length,
const GLchar* message,
const void* userParam);
static void DebugCallback(GLenum source,
GLenum type,
GLuint id,
GLenum severity,
GLsizei /* length */,
const GLchar* message,
const void* /* userParam */
);
template <typename Fn>
Debugging(const Fn& loadExtension)
:
#ifndef NDEBUG
pushDebugGroup(loadExtension({{"GL_KHR_debug", "glPushDebugGroup"}})),
popDebugGroup(loadExtension({{"GL_KHR_debug", "glPopDebugGroup"}})),
pushGroupMarkerEXT(loadExtension({{"GL_EXT_debug_marker", "glPushGroupMarkerEXT"}})),
popGroupMarkerEXT(loadExtension({{"GL_EXT_debug_marker", "glPopGroupMarkerEXT"}})),
#endif
debugMessageControl(loadExtension(
{{"GL_KHR_debug", "glDebugMessageControl"}, {"GL_ARB_debug_output", "glDebugMessageControlARB"}})),
debugMessageCallback(loadExtension(
{{"GL_KHR_debug", "glDebugMessageCallback"}, {"GL_ARB_debug_output", "glDebugMessageCallbackARB"}})) {
}
#ifndef NDEBUG
const ExtensionFunction<void(GLenum source, GLuint id, GLsizei length, const GLchar* message)> pushDebugGroup;
const ExtensionFunction<void()> popDebugGroup;
const ExtensionFunction<void(GLsizei length, const GLchar* marker)> pushGroupMarkerEXT;
const ExtensionFunction<void()> popGroupMarkerEXT;
#endif
const ExtensionFunction<void(
GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint* ids, GLboolean enabled)>
debugMessageControl;
const ExtensionFunction<void(Callback callback, const void* userParam)> debugMessageCallback;
};
} // namespace extension
} // namespace gl
} // namespace mln

731
src/mln/gl/defines.hpp Normal file
View File

@@ -0,0 +1,731 @@
#pragma once
/* OpenGL ES 2.0 */
/* ClearBufferMask */
#define GL_DEPTH_BUFFER_BIT 0x00000100
#define GL_STENCIL_BUFFER_BIT 0x00000400
#define GL_COLOR_BUFFER_BIT 0x00004000
/* Boolean */
#define GL_FALSE 0
#define GL_TRUE 1
/* BeginMode */
#define GL_POINTS 0x0000
#define GL_LINES 0x0001
#define GL_LINE_LOOP 0x0002
#define GL_LINE_STRIP 0x0003
#define GL_TRIANGLES 0x0004
#define GL_TRIANGLE_STRIP 0x0005
#define GL_TRIANGLE_FAN 0x0006
/* BlendingFactorDest */
#define GL_ZERO 0
#define GL_ONE 1
#define GL_SRC_COLOR 0x0300
#define GL_ONE_MINUS_SRC_COLOR 0x0301
#define GL_SRC_ALPHA 0x0302
#define GL_ONE_MINUS_SRC_ALPHA 0x0303
#define GL_DST_ALPHA 0x0304
#define GL_ONE_MINUS_DST_ALPHA 0x0305
/* BlendingFactorSrc */
/* GL_ZERO */
/* GL_ONE */
#define GL_DST_COLOR 0x0306
#define GL_ONE_MINUS_DST_COLOR 0x0307
#define GL_SRC_ALPHA_SATURATE 0x0308
/* GL_SRC_ALPHA */
/* GL_ONE_MINUS_SRC_ALPHA */
/* GL_DST_ALPHA */
/* GL_ONE_MINUS_DST_ALPHA */
/* BlendEquationSeparate */
#define GL_FUNC_ADD 0x8006
#define GL_BLEND_EQUATION 0x8009
#define GL_BLEND_EQUATION_RGB 0x8009 /* same as BLEND_EQUATION */
#define GL_BLEND_EQUATION_ALPHA 0x883D
/* BlendSubtract */
#define GL_FUNC_SUBTRACT 0x800A
#define GL_FUNC_REVERSE_SUBTRACT 0x800B
/* Separate Blend Functions */
#define GL_BLEND_DST_RGB 0x80C8
#define GL_BLEND_SRC_RGB 0x80C9
#define GL_BLEND_DST_ALPHA 0x80CA
#define GL_BLEND_SRC_ALPHA 0x80CB
#define GL_CONSTANT_COLOR 0x8001
#define GL_ONE_MINUS_CONSTANT_COLOR 0x8002
#define GL_CONSTANT_ALPHA 0x8003
#define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004
#define GL_BLEND_COLOR 0x8005
/* Buffer Objects */
#define GL_ARRAY_BUFFER 0x8892
#define GL_ELEMENT_ARRAY_BUFFER 0x8893
#define GL_ARRAY_BUFFER_BINDING 0x8894
#define GL_ELEMENT_ARRAY_BUFFER_BINDING 0x8895
#define GL_STREAM_DRAW 0x88E0
#define GL_STATIC_DRAW 0x88E4
#define GL_DYNAMIC_DRAW 0x88E8
#define GL_BUFFER_SIZE 0x8764
#define GL_BUFFER_USAGE 0x8765
#define GL_CURRENT_VERTEX_ATTRIB 0x8626
/* CullFaceMode */
#define GL_FRONT 0x0404
#define GL_BACK 0x0405
#define GL_FRONT_AND_BACK 0x0408
/* DepthFunction */
/* GL_NEVER */
/* GL_LESS */
/* GL_EQUAL */
/* GL_LEQUAL */
/* GL_GREATER */
/* GL_NOTEQUAL */
/* GL_GEQUAL */
/* GL_ALWAYS */
/* EnableCap */
#define GL_TEXTURE_2D 0x0DE1
#define GL_CULL_FACE 0x0B44
#define GL_BLEND 0x0BE2
#define GL_DITHER 0x0BD0
#define GL_STENCIL_TEST 0x0B90
#define GL_DEPTH_TEST 0x0B71
#define GL_SCISSOR_TEST 0x0C11
#define GL_POLYGON_OFFSET_FILL 0x8037
#define GL_SAMPLE_ALPHA_TO_COVERAGE 0x809E
#define GL_SAMPLE_COVERAGE 0x80A0
/* ErrorCode */
#define GL_NO_ERROR 0
#define GL_INVALID_ENUM 0x0500
#define GL_INVALID_VALUE 0x0501
#define GL_INVALID_OPERATION 0x0502
#define GL_OUT_OF_MEMORY 0x0505
/* FrontFaceDirection */
#define GL_CW 0x0900
#define GL_CCW 0x0901
/* GetPName */
#define GL_LINE_WIDTH 0x0B21
#define GL_ALIASED_POINT_SIZE_RANGE 0x846D
#define GL_ALIASED_LINE_WIDTH_RANGE 0x846E
#define GL_CULL_FACE_MODE 0x0B45
#define GL_FRONT_FACE 0x0B46
#define GL_DEPTH_RANGE 0x0B70
#define GL_DEPTH_WRITEMASK 0x0B72
#define GL_DEPTH_CLEAR_VALUE 0x0B73
#define GL_DEPTH_FUNC 0x0B74
#define GL_STENCIL_CLEAR_VALUE 0x0B91
#define GL_STENCIL_FUNC 0x0B92
#define GL_STENCIL_FAIL 0x0B94
#define GL_STENCIL_PASS_DEPTH_FAIL 0x0B95
#define GL_STENCIL_PASS_DEPTH_PASS 0x0B96
#define GL_STENCIL_REF 0x0B97
#define GL_STENCIL_VALUE_MASK 0x0B93
#define GL_STENCIL_WRITEMASK 0x0B98
#define GL_STENCIL_BACK_FUNC 0x8800
#define GL_STENCIL_BACK_FAIL 0x8801
#define GL_STENCIL_BACK_PASS_DEPTH_FAIL 0x8802
#define GL_STENCIL_BACK_PASS_DEPTH_PASS 0x8803
#define GL_STENCIL_BACK_REF 0x8CA3
#define GL_STENCIL_BACK_VALUE_MASK 0x8CA4
#define GL_STENCIL_BACK_WRITEMASK 0x8CA5
#define GL_VIEWPORT 0x0BA2
#define GL_SCISSOR_BOX 0x0C10
/* GL_SCISSOR_TEST */
#define GL_COLOR_CLEAR_VALUE 0x0C22
#define GL_COLOR_WRITEMASK 0x0C23
#define GL_UNPACK_ALIGNMENT 0x0CF5
#define GL_PACK_ALIGNMENT 0x0D05
#define GL_MAX_TEXTURE_SIZE 0x0D33
#define GL_MAX_VIEWPORT_DIMS 0x0D3A
#define GL_SUBPIXEL_BITS 0x0D50
#define GL_RED_BITS 0x0D52
#define GL_GREEN_BITS 0x0D53
#define GL_BLUE_BITS 0x0D54
#define GL_ALPHA_BITS 0x0D55
#define GL_DEPTH_BITS 0x0D56
#define GL_STENCIL_BITS 0x0D57
#define GL_POLYGON_OFFSET_UNITS 0x2A00
/* GL_POLYGON_OFFSET_FILL */
#define GL_POLYGON_OFFSET_FACTOR 0x8038
#define GL_TEXTURE_BINDING_2D 0x8069
#define GL_SAMPLE_BUFFERS 0x80A8
#define GL_SAMPLES 0x80A9
#define GL_SAMPLE_COVERAGE_VALUE 0x80AA
#define GL_SAMPLE_COVERAGE_INVERT 0x80AB
/* GetTextureParameter */
/* GL_TEXTURE_MAG_FILTER */
/* GL_TEXTURE_MIN_FILTER */
/* GL_TEXTURE_WRAP_S */
/* GL_TEXTURE_WRAP_T */
#define GL_NUM_COMPRESSED_TEXTURE_FORMATS 0x86A2
#define GL_COMPRESSED_TEXTURE_FORMATS 0x86A3
/* HintMode */
#define GL_DONT_CARE 0x1100
#define GL_FASTEST 0x1101
#define GL_NICEST 0x1102
/* HintTarget */
#define GL_GENERATE_MIPMAP_HINT 0x8192
/* DataType */
#define GL_BYTE 0x1400
#define GL_UNSIGNED_BYTE 0x1401
#define GL_SHORT 0x1402
#define GL_UNSIGNED_SHORT 0x1403
#define GL_INT 0x1404
#define GL_UNSIGNED_INT 0x1405
#define GL_FLOAT 0x1406
#define GL_FIXED 0x140C
/* PixelFormat */
#define GL_STENCIL_INDEX 0x1901
#define GL_DEPTH_COMPONENT 0x1902
#define GL_ALPHA 0x1906
#define GL_RGB 0x1907
#define GL_RGBA 0x1908
#define GL_LUMINANCE 0x1909
#define GL_LUMINANCE_ALPHA 0x190A
/* PixelType */
/* GL_UNSIGNED_BYTE */
#define GL_UNSIGNED_SHORT_4_4_4_4 0x8033
#define GL_UNSIGNED_SHORT_5_5_5_1 0x8034
#define GL_UNSIGNED_SHORT_5_6_5 0x8363
/* Shaders */
#define GL_FRAGMENT_SHADER 0x8B30
#define GL_VERTEX_SHADER 0x8B31
#define GL_MAX_VERTEX_ATTRIBS 0x8869
#define GL_MAX_VERTEX_UNIFORM_VECTORS 0x8DFB
#define GL_MAX_VARYING_VECTORS 0x8DFC
#define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 0x8B4D
#define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS 0x8B4C
#define GL_MAX_TEXTURE_IMAGE_UNITS 0x8872
#define GL_MAX_FRAGMENT_UNIFORM_VECTORS 0x8DFD
#define GL_SHADER_TYPE 0x8B4F
#define GL_DELETE_STATUS 0x8B80
#define GL_LINK_STATUS 0x8B82
#define GL_VALIDATE_STATUS 0x8B83
#define GL_ATTACHED_SHADERS 0x8B85
#define GL_ACTIVE_UNIFORMS 0x8B86
#define GL_ACTIVE_UNIFORM_MAX_LENGTH 0x8B87
#define GL_ACTIVE_ATTRIBUTES 0x8B89
#define GL_ACTIVE_ATTRIBUTE_MAX_LENGTH 0x8B8A
#define GL_SHADING_LANGUAGE_VERSION 0x8B8C
#define GL_CURRENT_PROGRAM 0x8B8D
/* StencilFunction */
#define GL_NEVER 0x0200
#define GL_LESS 0x0201
#define GL_EQUAL 0x0202
#define GL_LEQUAL 0x0203
#define GL_GREATER 0x0204
#define GL_NOTEQUAL 0x0205
#define GL_GEQUAL 0x0206
#define GL_ALWAYS 0x0207
/* StencilOp */
/* GL_ZERO */
#define GL_KEEP 0x1E00
#define GL_REPLACE 0x1E01
#define GL_INCR 0x1E02
#define GL_DECR 0x1E03
#define GL_INVERT 0x150A
#define GL_INCR_WRAP 0x8507
#define GL_DECR_WRAP 0x8508
/* StringName */
#define GL_VENDOR 0x1F00
#define GL_RENDERER 0x1F01
#define GL_VERSION 0x1F02
#define GL_EXTENSIONS 0x1F03
/* TextureMagFilter */
#define GL_NEAREST 0x2600
#define GL_LINEAR 0x2601
/* TextureMinFilter */
/* GL_NEAREST */
/* GL_LINEAR */
#define GL_NEAREST_MIPMAP_NEAREST 0x2700
#define GL_LINEAR_MIPMAP_NEAREST 0x2701
#define GL_NEAREST_MIPMAP_LINEAR 0x2702
#define GL_LINEAR_MIPMAP_LINEAR 0x2703
/* TextureParameterName */
#define GL_TEXTURE_MAG_FILTER 0x2800
#define GL_TEXTURE_MIN_FILTER 0x2801
#define GL_TEXTURE_WRAP_S 0x2802
#define GL_TEXTURE_WRAP_T 0x2803
/* TextureTarget */
/* GL_TEXTURE_2D */
#define GL_TEXTURE 0x1702
#define GL_TEXTURE_CUBE_MAP 0x8513
#define GL_TEXTURE_BINDING_CUBE_MAP 0x8514
#define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515
#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516
#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517
#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518
#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519
#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A
#define GL_MAX_CUBE_MAP_TEXTURE_SIZE 0x851C
/* TextureUnit */
#define GL_TEXTURE0 0x84C0
#define GL_TEXTURE1 0x84C1
#define GL_TEXTURE2 0x84C2
#define GL_TEXTURE3 0x84C3
#define GL_TEXTURE4 0x84C4
#define GL_TEXTURE5 0x84C5
#define GL_TEXTURE6 0x84C6
#define GL_TEXTURE7 0x84C7
#define GL_TEXTURE8 0x84C8
#define GL_TEXTURE9 0x84C9
#define GL_TEXTURE10 0x84CA
#define GL_TEXTURE11 0x84CB
#define GL_TEXTURE12 0x84CC
#define GL_TEXTURE13 0x84CD
#define GL_TEXTURE14 0x84CE
#define GL_TEXTURE15 0x84CF
#define GL_TEXTURE16 0x84D0
#define GL_TEXTURE17 0x84D1
#define GL_TEXTURE18 0x84D2
#define GL_TEXTURE19 0x84D3
#define GL_TEXTURE20 0x84D4
#define GL_TEXTURE21 0x84D5
#define GL_TEXTURE22 0x84D6
#define GL_TEXTURE23 0x84D7
#define GL_TEXTURE24 0x84D8
#define GL_TEXTURE25 0x84D9
#define GL_TEXTURE26 0x84DA
#define GL_TEXTURE27 0x84DB
#define GL_TEXTURE28 0x84DC
#define GL_TEXTURE29 0x84DD
#define GL_TEXTURE30 0x84DE
#define GL_TEXTURE31 0x84DF
#define GL_ACTIVE_TEXTURE 0x84E0
/* TextureWrapMode */
#define GL_REPEAT 0x2901
#define GL_CLAMP_TO_EDGE 0x812F
#define GL_MIRRORED_REPEAT 0x8370
/* Uniform Types */
#define GL_FLOAT_VEC2 0x8B50
#define GL_FLOAT_VEC3 0x8B51
#define GL_FLOAT_VEC4 0x8B52
#define GL_INT_VEC2 0x8B53
#define GL_INT_VEC3 0x8B54
#define GL_INT_VEC4 0x8B55
#define GL_BOOL 0x8B56
#define GL_BOOL_VEC2 0x8B57
#define GL_BOOL_VEC3 0x8B58
#define GL_BOOL_VEC4 0x8B59
#define GL_FLOAT_MAT2 0x8B5A
#define GL_FLOAT_MAT3 0x8B5B
#define GL_FLOAT_MAT4 0x8B5C
#define GL_SAMPLER_2D 0x8B5E
#define GL_SAMPLER_CUBE 0x8B60
/* Vertex Arrays */
#define GL_VERTEX_ATTRIB_ARRAY_ENABLED 0x8622
#define GL_VERTEX_ATTRIB_ARRAY_SIZE 0x8623
#define GL_VERTEX_ATTRIB_ARRAY_STRIDE 0x8624
#define GL_VERTEX_ATTRIB_ARRAY_TYPE 0x8625
#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED 0x886A
#define GL_VERTEX_ATTRIB_ARRAY_POINTER 0x8645
#define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING 0x889F
/* Read Format */
#define GL_IMPLEMENTATION_COLOR_READ_TYPE 0x8B9A
#define GL_IMPLEMENTATION_COLOR_READ_FORMAT 0x8B9B
/* Shader Source */
#define GL_COMPILE_STATUS 0x8B81
#define GL_INFO_LOG_LENGTH 0x8B84
#define GL_SHADER_SOURCE_LENGTH 0x8B88
#define GL_SHADER_COMPILER 0x8DFA
/* Shader Binary */
#define GL_SHADER_BINARY_FORMATS 0x8DF8
#define GL_NUM_SHADER_BINARY_FORMATS 0x8DF9
/* Shader Precision-Specified Types */
#define GL_LOW_FLOAT 0x8DF0
#define GL_MEDIUM_FLOAT 0x8DF1
#define GL_HIGH_FLOAT 0x8DF2
#define GL_LOW_INT 0x8DF3
#define GL_MEDIUM_INT 0x8DF4
#define GL_HIGH_INT 0x8DF5
/* Framebuffer Object. */
#define GL_FRAMEBUFFER 0x8D40
#define GL_RENDERBUFFER 0x8D41
#define GL_RGBA4 0x8056
#define GL_RGB5_A1 0x8057
#define GL_RGB565 0x8D62
#define GL_DEPTH_COMPONENT16 0x81A5
#define GL_STENCIL_INDEX8 0x8D48
#define GL_RENDERBUFFER_WIDTH 0x8D42
#define GL_RENDERBUFFER_HEIGHT 0x8D43
#define GL_RENDERBUFFER_INTERNAL_FORMAT 0x8D44
#define GL_RENDERBUFFER_RED_SIZE 0x8D50
#define GL_RENDERBUFFER_GREEN_SIZE 0x8D51
#define GL_RENDERBUFFER_BLUE_SIZE 0x8D52
#define GL_RENDERBUFFER_ALPHA_SIZE 0x8D53
#define GL_RENDERBUFFER_DEPTH_SIZE 0x8D54
#define GL_RENDERBUFFER_STENCIL_SIZE 0x8D55
#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE 0x8CD0
#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME 0x8CD1
#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL 0x8CD2
#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE 0x8CD3
#define GL_COLOR_ATTACHMENT0 0x8CE0
#define GL_DEPTH_ATTACHMENT 0x8D00
#define GL_STENCIL_ATTACHMENT 0x8D20
#define GL_NONE 0
#define GL_FRAMEBUFFER_COMPLETE 0x8CD5
#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT 0x8CD6
#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT 0x8CD7
#define GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS 0x8CD9
#define GL_FRAMEBUFFER_UNSUPPORTED 0x8CDD
#define GL_FRAMEBUFFER_BINDING 0x8CA6
#define GL_RENDERBUFFER_BINDING 0x8CA7
#define GL_MAX_RENDERBUFFER_SIZE 0x84E8
#define GL_INVALID_FRAMEBUFFER_OPERATION 0x0506
/* OpenGL ES 3.0 */
#define GL_READ_BUFFER 0x0C02
#define GL_UNPACK_ROW_LENGTH 0x0CF2
#define GL_UNPACK_SKIP_ROWS 0x0CF3
#define GL_UNPACK_SKIP_PIXELS 0x0CF4
#define GL_PACK_ROW_LENGTH 0x0D02
#define GL_PACK_SKIP_ROWS 0x0D03
#define GL_PACK_SKIP_PIXELS 0x0D04
#define GL_COLOR 0x1800
#define GL_DEPTH 0x1801
#define GL_STENCIL 0x1802
#define GL_RED 0x1903
#define GL_RGB8 0x8051
#define GL_RGBA8 0x8058
#define GL_RGB10_A2 0x8059
#define GL_TEXTURE_BINDING_3D 0x806A
#define GL_UNPACK_SKIP_IMAGES 0x806D
#define GL_UNPACK_IMAGE_HEIGHT 0x806E
#define GL_TEXTURE_3D 0x806F
#define GL_TEXTURE_WRAP_R 0x8072
#define GL_MAX_3D_TEXTURE_SIZE 0x8073
#define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368
#define GL_MAX_ELEMENTS_VERTICES 0x80E8
#define GL_MAX_ELEMENTS_INDICES 0x80E9
#define GL_TEXTURE_MIN_LOD 0x813A
#define GL_TEXTURE_MAX_LOD 0x813B
#define GL_TEXTURE_BASE_LEVEL 0x813C
#define GL_TEXTURE_MAX_LEVEL 0x813D
#define GL_MIN 0x8007
#define GL_MAX 0x8008
#define GL_DEPTH_COMPONENT24 0x81A6
#define GL_MAX_TEXTURE_LOD_BIAS 0x84FD
#define GL_TEXTURE_COMPARE_MODE 0x884C
#define GL_TEXTURE_COMPARE_FUNC 0x884D
#define GL_CURRENT_QUERY 0x8865
#define GL_QUERY_RESULT 0x8866
#define GL_QUERY_RESULT_AVAILABLE 0x8867
#define GL_BUFFER_MAPPED 0x88BC
#define GL_BUFFER_MAP_POINTER 0x88BD
#define GL_STREAM_READ 0x88E1
#define GL_STREAM_COPY 0x88E2
#define GL_STATIC_READ 0x88E5
#define GL_STATIC_COPY 0x88E6
#define GL_DYNAMIC_READ 0x88E9
#define GL_DYNAMIC_COPY 0x88EA
#define GL_MAX_DRAW_BUFFERS 0x8824
#define GL_DRAW_BUFFER0 0x8825
#define GL_DRAW_BUFFER1 0x8826
#define GL_DRAW_BUFFER2 0x8827
#define GL_DRAW_BUFFER3 0x8828
#define GL_DRAW_BUFFER4 0x8829
#define GL_DRAW_BUFFER5 0x882A
#define GL_DRAW_BUFFER6 0x882B
#define GL_DRAW_BUFFER7 0x882C
#define GL_DRAW_BUFFER8 0x882D
#define GL_DRAW_BUFFER9 0x882E
#define GL_DRAW_BUFFER10 0x882F
#define GL_DRAW_BUFFER11 0x8830
#define GL_DRAW_BUFFER12 0x8831
#define GL_DRAW_BUFFER13 0x8832
#define GL_DRAW_BUFFER14 0x8833
#define GL_DRAW_BUFFER15 0x8834
#define GL_MAX_FRAGMENT_UNIFORM_COMPONENTS 0x8B49
#define GL_MAX_VERTEX_UNIFORM_COMPONENTS 0x8B4A
#define GL_SAMPLER_3D 0x8B5F
#define GL_SAMPLER_2D_SHADOW 0x8B62
#define GL_FRAGMENT_SHADER_DERIVATIVE_HINT 0x8B8B
#define GL_PIXEL_PACK_BUFFER 0x88EB
#define GL_PIXEL_UNPACK_BUFFER 0x88EC
#define GL_PIXEL_PACK_BUFFER_BINDING 0x88ED
#define GL_PIXEL_UNPACK_BUFFER_BINDING 0x88EF
#define GL_FLOAT_MAT2x3 0x8B65
#define GL_FLOAT_MAT2x4 0x8B66
#define GL_FLOAT_MAT3x2 0x8B67
#define GL_FLOAT_MAT3x4 0x8B68
#define GL_FLOAT_MAT4x2 0x8B69
#define GL_FLOAT_MAT4x3 0x8B6A
#define GL_SRGB 0x8C40
#define GL_SRGB8 0x8C41
#define GL_SRGB8_ALPHA8 0x8C43
#define GL_COMPARE_REF_TO_TEXTURE 0x884E
#define GL_MAJOR_VERSION 0x821B
#define GL_MINOR_VERSION 0x821C
#define GL_NUM_EXTENSIONS 0x821D
#define GL_RGBA32F 0x8814
#define GL_RGB32F 0x8815
#define GL_RGBA16F 0x881A
#define GL_RGB16F 0x881B
#define GL_VERTEX_ATTRIB_ARRAY_INTEGER 0x88FD
#define GL_MAX_ARRAY_TEXTURE_LAYERS 0x88FF
#define GL_MIN_PROGRAM_TEXEL_OFFSET 0x8904
#define GL_MAX_PROGRAM_TEXEL_OFFSET 0x8905
#define GL_MAX_VARYING_COMPONENTS 0x8B4B
#define GL_TEXTURE_2D_ARRAY 0x8C1A
#define GL_TEXTURE_BINDING_2D_ARRAY 0x8C1D
#define GL_R11F_G11F_B10F 0x8C3A
#define GL_UNSIGNED_INT_10F_11F_11F_REV 0x8C3B
#define GL_RGB9_E5 0x8C3D
#define GL_UNSIGNED_INT_5_9_9_9_REV 0x8C3E
#define GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH 0x8C76
#define GL_TRANSFORM_FEEDBACK_BUFFER_MODE 0x8C7F
#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS 0x8C80
#define GL_TRANSFORM_FEEDBACK_VARYINGS 0x8C83
#define GL_TRANSFORM_FEEDBACK_BUFFER_START 0x8C84
#define GL_TRANSFORM_FEEDBACK_BUFFER_SIZE 0x8C85
#define GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN 0x8C88
#define GL_RASTERIZER_DISCARD 0x8C89
#define GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS 0x8C8A
#define GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS 0x8C8B
#define GL_INTERLEAVED_ATTRIBS 0x8C8C
#define GL_SEPARATE_ATTRIBS 0x8C8D
#define GL_TRANSFORM_FEEDBACK_BUFFER 0x8C8E
#define GL_TRANSFORM_FEEDBACK_BUFFER_BINDING 0x8C8F
#define GL_RGBA32UI 0x8D70
#define GL_RGB32UI 0x8D71
#define GL_RGBA16UI 0x8D76
#define GL_RGB16UI 0x8D77
#define GL_RGBA8UI 0x8D7C
#define GL_RGB8UI 0x8D7D
#define GL_RGBA32I 0x8D82
#define GL_RGB32I 0x8D83
#define GL_RGBA16I 0x8D88
#define GL_RGB16I 0x8D89
#define GL_RGBA8I 0x8D8E
#define GL_RGB8I 0x8D8F
#define GL_RED_INTEGER 0x8D94
#define GL_RGB_INTEGER 0x8D98
#define GL_RGBA_INTEGER 0x8D99
#define GL_SAMPLER_2D_ARRAY 0x8DC1
#define GL_SAMPLER_2D_ARRAY_SHADOW 0x8DC4
#define GL_SAMPLER_CUBE_SHADOW 0x8DC5
#define GL_UNSIGNED_INT_VEC2 0x8DC6
#define GL_UNSIGNED_INT_VEC3 0x8DC7
#define GL_UNSIGNED_INT_VEC4 0x8DC8
#define GL_INT_SAMPLER_2D 0x8DCA
#define GL_INT_SAMPLER_3D 0x8DCB
#define GL_INT_SAMPLER_CUBE 0x8DCC
#define GL_INT_SAMPLER_2D_ARRAY 0x8DCF
#define GL_UNSIGNED_INT_SAMPLER_2D 0x8DD2
#define GL_UNSIGNED_INT_SAMPLER_3D 0x8DD3
#define GL_UNSIGNED_INT_SAMPLER_CUBE 0x8DD4
#define GL_UNSIGNED_INT_SAMPLER_2D_ARRAY 0x8DD7
#define GL_BUFFER_ACCESS_FLAGS 0x911F
#define GL_BUFFER_MAP_LENGTH 0x9120
#define GL_BUFFER_MAP_OFFSET 0x9121
#define GL_DEPTH_COMPONENT32F 0x8CAC
#define GL_DEPTH32F_STENCIL8 0x8CAD
#define GL_FLOAT_32_UNSIGNED_INT_24_8_REV 0x8DAD
#define GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING 0x8210
#define GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE 0x8211
#define GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE 0x8212
#define GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE 0x8213
#define GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE 0x8214
#define GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE 0x8215
#define GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE 0x8216
#define GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE 0x8217
#define GL_FRAMEBUFFER_DEFAULT 0x8218
#define GL_FRAMEBUFFER_UNDEFINED 0x8219
#define GL_DEPTH_STENCIL_ATTACHMENT 0x821A
#define GL_DEPTH_STENCIL 0x84F9
#define GL_UNSIGNED_INT_24_8 0x84FA
#define GL_DEPTH24_STENCIL8 0x88F0
#define GL_UNSIGNED_NORMALIZED 0x8C17
#define GL_DRAW_FRAMEBUFFER_BINDING GL_FRAMEBUFFER_BINDING
#define GL_READ_FRAMEBUFFER 0x8CA8
#define GL_DRAW_FRAMEBUFFER 0x8CA9
#define GL_READ_FRAMEBUFFER_BINDING 0x8CAA
#define GL_RENDERBUFFER_SAMPLES 0x8CAB
#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER 0x8CD4
#define GL_MAX_COLOR_ATTACHMENTS 0x8CDF
#define GL_COLOR_ATTACHMENT1 0x8CE1
#define GL_COLOR_ATTACHMENT2 0x8CE2
#define GL_COLOR_ATTACHMENT3 0x8CE3
#define GL_COLOR_ATTACHMENT4 0x8CE4
#define GL_COLOR_ATTACHMENT5 0x8CE5
#define GL_COLOR_ATTACHMENT6 0x8CE6
#define GL_COLOR_ATTACHMENT7 0x8CE7
#define GL_COLOR_ATTACHMENT8 0x8CE8
#define GL_COLOR_ATTACHMENT9 0x8CE9
#define GL_COLOR_ATTACHMENT10 0x8CEA
#define GL_COLOR_ATTACHMENT11 0x8CEB
#define GL_COLOR_ATTACHMENT12 0x8CEC
#define GL_COLOR_ATTACHMENT13 0x8CED
#define GL_COLOR_ATTACHMENT14 0x8CEE
#define GL_COLOR_ATTACHMENT15 0x8CEF
#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE 0x8D56
#define GL_MAX_SAMPLES 0x8D57
#define GL_HALF_FLOAT 0x140B
#define GL_MAP_READ_BIT 0x0001
#define GL_MAP_WRITE_BIT 0x0002
#define GL_MAP_INVALIDATE_RANGE_BIT 0x0004
#define GL_MAP_INVALIDATE_BUFFER_BIT 0x0008
#define GL_MAP_FLUSH_EXPLICIT_BIT 0x0010
#define GL_MAP_UNSYNCHRONIZED_BIT 0x0020
#define GL_RG 0x8227
#define GL_RG_INTEGER 0x8228
#define GL_R8 0x8229
#define GL_RG8 0x822B
#define GL_R16F 0x822D
#define GL_R32F 0x822E
#define GL_RG16F 0x822F
#define GL_RG32F 0x8230
#define GL_R8I 0x8231
#define GL_R8UI 0x8232
#define GL_R16I 0x8233
#define GL_R16UI 0x8234
#define GL_R32I 0x8235
#define GL_R32UI 0x8236
#define GL_RG8I 0x8237
#define GL_RG8UI 0x8238
#define GL_RG16I 0x8239
#define GL_RG16UI 0x823A
#define GL_RG32I 0x823B
#define GL_RG32UI 0x823C
#define GL_VERTEX_ARRAY_BINDING 0x85B5
#define GL_R8_SNORM 0x8F94
#define GL_RG8_SNORM 0x8F95
#define GL_RGB8_SNORM 0x8F96
#define GL_RGBA8_SNORM 0x8F97
#define GL_SIGNED_NORMALIZED 0x8F9C
#define GL_PRIMITIVE_RESTART_FIXED_INDEX 0x8D69
#define GL_COPY_READ_BUFFER 0x8F36
#define GL_COPY_WRITE_BUFFER 0x8F37
#define GL_COPY_READ_BUFFER_BINDING GL_COPY_READ_BUFFER
#define GL_COPY_WRITE_BUFFER_BINDING GL_COPY_WRITE_BUFFER
#define GL_UNIFORM_BUFFER 0x8A11
#define GL_UNIFORM_BUFFER_BINDING 0x8A28
#define GL_UNIFORM_BUFFER_START 0x8A29
#define GL_UNIFORM_BUFFER_SIZE 0x8A2A
#define GL_MAX_VERTEX_UNIFORM_BLOCKS 0x8A2B
#define GL_MAX_FRAGMENT_UNIFORM_BLOCKS 0x8A2D
#define GL_MAX_COMBINED_UNIFORM_BLOCKS 0x8A2E
#define GL_MAX_UNIFORM_BUFFER_BINDINGS 0x8A2F
#define GL_MAX_UNIFORM_BLOCK_SIZE 0x8A30
#define GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS 0x8A31
#define GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS 0x8A33
#define GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT 0x8A34
#define GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH 0x8A35
#define GL_ACTIVE_UNIFORM_BLOCKS 0x8A36
#define GL_UNIFORM_TYPE 0x8A37
#define GL_UNIFORM_SIZE 0x8A38
#define GL_UNIFORM_NAME_LENGTH 0x8A39
#define GL_UNIFORM_BLOCK_INDEX 0x8A3A
#define GL_UNIFORM_OFFSET 0x8A3B
#define GL_UNIFORM_ARRAY_STRIDE 0x8A3C
#define GL_UNIFORM_MATRIX_STRIDE 0x8A3D
#define GL_UNIFORM_IS_ROW_MAJOR 0x8A3E
#define GL_UNIFORM_BLOCK_BINDING 0x8A3F
#define GL_UNIFORM_BLOCK_DATA_SIZE 0x8A40
#define GL_UNIFORM_BLOCK_NAME_LENGTH 0x8A41
#define GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS 0x8A42
#define GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES 0x8A43
#define GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER 0x8A44
#define GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER 0x8A46
#define GL_INVALID_INDEX 0xFFFFFFFFu
#define GL_MAX_VERTEX_OUTPUT_COMPONENTS 0x9122
#define GL_MAX_FRAGMENT_INPUT_COMPONENTS 0x9125
#define GL_MAX_SERVER_WAIT_TIMEOUT 0x9111
#define GL_OBJECT_TYPE 0x9112
#define GL_SYNC_CONDITION 0x9113
#define GL_SYNC_STATUS 0x9114
#define GL_SYNC_FLAGS 0x9115
#define GL_SYNC_FENCE 0x9116
#define GL_SYNC_GPU_COMMANDS_COMPLETE 0x9117
#define GL_UNSIGNALED 0x9118
#define GL_SIGNALED 0x9119
#define GL_ALREADY_SIGNALED 0x911A
#define GL_TIMEOUT_EXPIRED 0x911B
#define GL_CONDITION_SATISFIED 0x911C
#define GL_WAIT_FAILED 0x911D
#define GL_SYNC_FLUSH_COMMANDS_BIT 0x00000001
#define GL_TIMEOUT_IGNORED 0xFFFFFFFFFFFFFFFFull
#define GL_VERTEX_ATTRIB_ARRAY_DIVISOR 0x88FE
#define GL_ANY_SAMPLES_PASSED 0x8C2F
#define GL_ANY_SAMPLES_PASSED_CONSERVATIVE 0x8D6A
#define GL_SAMPLER_BINDING 0x8919
#define GL_RGB10_A2UI 0x906F
#define GL_TEXTURE_SWIZZLE_R 0x8E42
#define GL_TEXTURE_SWIZZLE_G 0x8E43
#define GL_TEXTURE_SWIZZLE_B 0x8E44
#define GL_TEXTURE_SWIZZLE_A 0x8E45
#define GL_GREEN 0x1904
#define GL_BLUE 0x1905
#define GL_INT_2_10_10_10_REV 0x8D9F
#define GL_TRANSFORM_FEEDBACK 0x8E22
#define GL_TRANSFORM_FEEDBACK_PAUSED 0x8E23
#define GL_TRANSFORM_FEEDBACK_ACTIVE 0x8E24
#define GL_TRANSFORM_FEEDBACK_BINDING 0x8E25
#define GL_PROGRAM_BINARY_RETRIEVABLE_HINT 0x8257
#define GL_PROGRAM_BINARY_LENGTH 0x8741
#define GL_NUM_PROGRAM_BINARY_FORMATS 0x87FE
#define GL_PROGRAM_BINARY_FORMATS 0x87FF
#define GL_COMPRESSED_R11_EAC 0x9270
#define GL_COMPRESSED_SIGNED_R11_EAC 0x9271
#define GL_COMPRESSED_RG11_EAC 0x9272
#define GL_COMPRESSED_SIGNED_RG11_EAC 0x9273
#define GL_COMPRESSED_RGB8_ETC2 0x9274
#define GL_COMPRESSED_SRGB8_ETC2 0x9275
#define GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 0x9276
#define GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 0x9277
#define GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278
#define GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC 0x9279
#define GL_TEXTURE_IMMUTABLE_FORMAT 0x912F
#define GL_MAX_ELEMENT_INDEX 0x8D6B
#define GL_NUM_SAMPLE_COUNTS 0x9380
#define GL_TEXTURE_IMMUTABLE_LEVELS 0x82DF
/* OpenGL ES Extensions */
#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE

View File

@@ -0,0 +1,18 @@
#pragma once
#include <mln/gfx/draw_scope.hpp>
#include <mln/gl/vertex_array.hpp>
namespace mln {
namespace gl {
class DrawScopeResource : public gfx::DrawScopeResource {
public:
DrawScopeResource(VertexArray&& vertexArray_)
: vertexArray(std::move(vertexArray_)) {}
VertexArray vertexArray;
};
} // namespace gl
} // namespace mln

284
src/mln/gl/drawable_gl.cpp Normal file
View File

@@ -0,0 +1,284 @@
#include <mln/gl/drawable_gl.hpp>
#include <mln/gl/drawable_gl_impl.hpp>
#include <mln/gl/texture2d.hpp>
#include <mln/gl/upload_pass.hpp>
#include <mln/gl/vertex_array.hpp>
#include <mln/gl/vertex_attribute_gl.hpp>
#include <mln/gl/vertex_buffer_resource.hpp>
#include <mln/shaders/segment.hpp>
#include <mln/shaders/gl/shader_program_gl.hpp>
#include <mln/util/instrumentation.hpp>
#include <mln/util/logging.hpp>
namespace mln {
namespace gl {
DrawableGL::DrawableGL(std::string name_)
: Drawable(std::move(name_)),
impl(std::make_unique<Impl>()) {}
DrawableGL::~DrawableGL() {
impl->attributeBuffers.clear();
}
void DrawableGL::draw(PaintParameters& parameters) const {
MLN_TRACE_FUNC();
if (isCustom) {
return;
}
auto& context = static_cast<gl::Context&>(parameters.context);
if (shader) {
const auto& shaderGL = static_cast<const ShaderProgramGL&>(*shader);
if (shaderGL.getGLProgramID() != context.program.getCurrentValue()) {
context.program = shaderGL.getGLProgramID();
}
}
if (!shader || context.program.getCurrentValue() == 0) {
mln::Log::Warning(Event::General, "Missing shader for drawable " + util::toString(getID()) + "/" + getName());
assert(false);
return;
}
if (enableDepth) {
context.setDepthMode(getIs3D() ? parameters.depthModeFor3D()
: parameters.depthModeForSublayer(getSubLayerIndex(), getDepthType()));
} else {
context.setDepthMode(gfx::DepthMode::disabled());
}
// force disable depth test for debugging
// context.setDepthMode({gfx::DepthFunctionType::Always, gfx::DepthMaskType::ReadOnly, {0,1}});
// For 3D mode, stenciling is handled by the layer group
if (!is3D) {
context.setStencilMode(makeStencilMode(parameters));
}
context.setColorMode(getColorMode());
context.setCullFaceMode(getCullFaceMode());
context.setScissorTest(parameters.scissorRect);
impl->uniformBuffers.bind();
bindTextures();
for (const auto& seg : impl->segments) {
const auto& glSeg = static_cast<DrawSegmentGL&>(*seg);
const auto& mlSeg = glSeg.getSegment();
if (mlSeg.indexLength > 0 && glSeg.getVertexArray().isValid()) {
context.bindVertexArray = glSeg.getVertexArray().getID();
context.draw(glSeg.getMode(), mlSeg.indexOffset, mlSeg.indexLength);
}
}
// Unbind the VAO so that future buffer commands outside Drawable do not change the current VAO state
context.bindVertexArray = value::BindVertexArray::Default;
unbindTextures();
impl->uniformBuffers.unbind();
}
void DrawableGL::setIndexData(gfx::IndexVectorBasePtr indexes, std::vector<UniqueDrawSegment> segments) {
impl->indexes = std::move(indexes);
impl->segments = std::move(segments);
}
void DrawableGL::updateVertexAttributes(gfx::VertexAttributeArrayPtr vertices,
std::size_t vertexCount,
gfx::DrawMode mode,
gfx::IndexVectorBasePtr indexes,
const SegmentBase* segments,
std::size_t segmentCount) {
gfx::Drawable::setVertexAttributes(std::move(vertices));
impl->vertexCount = vertexCount;
std::vector<std::unique_ptr<Drawable::DrawSegment>> drawSegs;
drawSegs.reserve(segmentCount);
for (std::size_t i = 0; i < segmentCount; ++i) {
const auto& seg = segments[i];
auto segCopy = SegmentBase{
// no copy constructor
seg.vertexOffset,
seg.indexOffset,
seg.vertexLength,
seg.indexLength,
seg.sortKey,
};
auto drawSeg = std::make_unique<DrawableGL::DrawSegmentGL>(
mode, std::move(segCopy), VertexArray{{nullptr, false}});
drawSegs.push_back(std::move(drawSeg));
}
impl->indexes = std::move(indexes);
impl->segments = std::move(drawSegs);
}
void DrawableGL::setVertices(std::vector<uint8_t>&& data, std::size_t count, gfx::AttributeDataType type_) {
impl->vertexData = std::move(data);
impl->vertexCount = count;
impl->vertexType = type_;
}
const gfx::UniformBufferArray& DrawableGL::getUniformBuffers() const {
return impl->uniformBuffers;
}
gfx::UniformBufferArray& DrawableGL::mutableUniformBuffers() {
return impl->uniformBuffers;
}
void DrawableGL::setVertexAttrId(const size_t id) {
impl->vertexAttrId = id;
}
struct IndexBufferGL : public gfx::IndexBufferBase {
IndexBufferGL(std::unique_ptr<gfx::IndexBuffer>&& buffer_)
: buffer(std::move(buffer_)) {}
~IndexBufferGL() override = default;
std::unique_ptr<mln::gfx::IndexBuffer> buffer;
};
void DrawableGL::upload(gfx::UploadPass& uploadPass) {
if (isCustom) {
return;
}
if (!shader) {
Log::Warning(Event::General, "Missing shader for drawable " + util::toString(getID()) + "/" + getName());
assert(false);
return;
}
MLN_TRACE_FUNC();
#ifdef MLN_TRACY_ENABLE
{
auto str = name + "/" + (tileID ? util::toString(*tileID) : std::string());
MLN_ZONE_STR(str);
}
#endif
auto& context = uploadPass.getContext();
auto& glContext = static_cast<gl::Context&>(context);
constexpr auto usage = gfx::BufferUsageType::StaticDraw;
// Create an index buffer if necessary}
if (impl->indexes && (!impl->indexes->getBuffer() || impl->indexes->getDirty())) {
MLN_TRACE_ZONE(build indexes);
auto indexBufferResource{
uploadPass.createIndexBufferResource(impl->indexes->data(), impl->indexes->bytes(), usage)};
auto indexBuffer = std::make_unique<gfx::IndexBuffer>(impl->indexes->elements(),
std::move(indexBufferResource));
auto buffer = std::make_unique<IndexBufferGL>(std::move(indexBuffer));
impl->indexes->setBuffer(std::move(buffer));
impl->indexes->setDirty(false);
}
// Build the vertex attributes and bindings, if necessary
if (impl->attributeBindings.empty() ||
(vertexAttributes && (!attributeUpdateTime || vertexAttributes->isModifiedAfter(*attributeUpdateTime)))) {
MLN_TRACE_ZONE(build attributes);
// Apply drawable values to shader defaults
const auto& defaults = shader->getVertexAttributes();
const auto& overrides = *vertexAttributes;
const auto& indexAttribute = defaults.get(impl->vertexAttrId);
const auto vertexAttributeIndex = static_cast<std::size_t>(indexAttribute ? indexAttribute->getIndex() : -1);
std::vector<std::unique_ptr<gfx::VertexBufferResource>> vertexBuffers;
impl->attributeBindings = uploadPass.buildAttributeBindings(impl->vertexCount,
impl->vertexType,
vertexAttributeIndex,
impl->vertexData,
defaults,
overrides,
usage,
attributeUpdateTime,
vertexBuffers);
impl->attributeBuffers = std::move(vertexBuffers);
}
// Bind a VAO for each group of vertexes described by a segment
for (const auto& seg : impl->segments) {
MLN_TRACE_ZONE(segment);
auto& glSeg = static_cast<DrawSegmentGL&>(*seg);
const auto& mlSeg = glSeg.getSegment();
if (mlSeg.indexLength == 0) {
continue;
}
for (auto& binding : impl->attributeBindings) {
if (binding) {
binding->vertexOffset = static_cast<uint32_t>(mlSeg.vertexOffset);
}
}
if (!glSeg.getVertexArray().isValid() && impl->indexes) {
auto vertexArray = glContext.createVertexArray();
const auto& indexBuffer = static_cast<IndexBufferGL&>(*impl->indexes->getBuffer());
vertexArray.bind(glContext, *indexBuffer.buffer, impl->attributeBindings);
assert(vertexArray.isValid());
if (vertexArray.isValid()) {
glSeg.setVertexArray(std::move(vertexArray));
}
}
}
const auto needsUpload = [](const auto& texture) {
return texture && texture->needsUpload();
};
if (std::any_of(textures.begin(), textures.end(), needsUpload)) {
uploadTextures();
}
attributeUpdateTime = util::MonotonicTimer::now();
}
gfx::ColorMode DrawableGL::makeColorMode(PaintParameters& parameters) const {
return enableColor ? parameters.colorModeForRenderPass() : gfx::ColorMode::disabled();
}
gfx::StencilMode DrawableGL::makeStencilMode(PaintParameters& parameters) const {
if (enableStencil && parameters.stencilClippingAvailable) {
if (!is3D && tileID) {
return parameters.stencilModeForClipping(tileID->toUnwrapped());
}
assert(false);
}
return gfx::StencilMode::disabled();
}
void DrawableGL::uploadTextures() const {
MLN_TRACE_FUNC();
for (const auto& texture : textures) {
if (texture) {
texture->upload();
}
}
}
void DrawableGL::bindTextures() const {
int32_t unit = 0;
for (size_t id = 0; id < textures.size(); id++) {
if (const auto& texture = textures[id]) {
if (const auto& location = shader->getSamplerLocation(id)) {
static_cast<gl::Texture2D&>(*texture).bind(static_cast<int32_t>(*location), unit++);
}
}
}
}
void DrawableGL::unbindTextures() const {
for (const auto& texture : textures) {
if (texture) {
static_cast<gl::Texture2D&>(*texture).unbind();
}
}
}
} // namespace gl
} // namespace mln

View File

@@ -0,0 +1,50 @@
#include <mln/gl/drawable_gl_builder.hpp>
#include <mln/gfx/drawable_builder_impl.hpp>
#include <mln/gfx/drawable_impl.hpp>
#include <mln/gl/drawable_gl.hpp>
#include <mln/gl/drawable_gl_impl.hpp>
#include <mln/util/convert.hpp>
#include <cstring>
namespace mln {
namespace gl {
gfx::UniqueDrawable DrawableGLBuilder::createDrawable() const {
return std::make_unique<DrawableGL>(drawableName.empty() ? name : drawableName);
};
std::unique_ptr<gfx::Drawable::DrawSegment> DrawableGLBuilder::createSegment(gfx::DrawMode drawMode,
SegmentBase&& seg) {
return std::make_unique<DrawableGL::DrawSegmentGL>(drawMode, std::move(seg), VertexArray{{nullptr, false}});
}
void DrawableGLBuilder::init() {
auto& drawableGL = static_cast<DrawableGL&>(*currentDrawable);
drawableGL.setVertexAttrId(vertexAttrId);
if (impl->rawVerticesCount) {
auto raw = impl->rawVertices;
drawableGL.setVertices(std::move(raw), impl->rawVerticesCount, impl->rawVerticesType);
} else {
const auto& verts = impl->vertices.vector();
constexpr auto vertSize = sizeof(std::remove_reference<decltype(verts)>::type::value_type);
std::vector<uint8_t> raw(verts.size() * vertSize);
std::memcpy(raw.data(), verts.data(), raw.size());
drawableGL.setVertices(std::move(raw), verts.size(), gfx::AttributeDataType::Short2);
}
if (!impl->sharedIndexes && !impl->buildIndexes.empty()) {
impl->sharedIndexes = std::make_shared<gfx::IndexVectorBase>(std::move(impl->buildIndexes));
}
assert(impl->sharedIndexes && impl->sharedIndexes->elements());
drawableGL.setIndexData(std::move(impl->sharedIndexes), std::move(impl->segments));
impl->clear();
textures.fill(nullptr);
}
} // namespace gl
} // namespace mln

View File

@@ -0,0 +1,67 @@
#pragma once
#include <mln/gfx/drawable_impl.hpp>
#include <mln/gfx/index_buffer.hpp>
#include <mln/gfx/uniform.hpp>
#include <mln/gl/defines.hpp>
#include <mln/gl/enum.hpp>
#include <mln/gl/uniform_buffer_gl.hpp>
#include <mln/gl/vertex_array.hpp>
#include <mln/platform/gl_functions.hpp>
#include <mln/shaders/segment.hpp>
#include <mln/renderer/paint_parameters.hpp>
#include <mln/util/mat4.hpp>
#include <cstdint>
#include <memory>
#include <vector>
namespace mln {
namespace gl {
using namespace platform;
class DrawableGL::Impl final {
public:
Impl() = default;
~Impl() = default;
std::vector<UniqueDrawSegment> segments;
std::vector<TextureID> textures;
gfx::IndexVectorBasePtr indexes;
std::vector<std::uint8_t> vertexData;
std::size_t vertexCount = 0;
gfx::AttributeDataType vertexType = static_cast<gfx::AttributeDataType>(-1);
AttributeBindingArray attributeBindings;
std::vector<gfx::UniqueVertexBufferResource> attributeBuffers;
UniformBufferArrayGL uniformBuffers;
gfx::DepthMode depthMode = gfx::DepthMode::disabled();
gfx::StencilMode stencilMode;
gfx::CullFaceMode cullFaceMode;
GLfloat pointSize = 0.0f;
size_t vertexAttrId = 0;
};
struct DrawableGL::DrawSegmentGL final : public gfx::Drawable::DrawSegment {
DrawSegmentGL(gfx::DrawMode mode_, SegmentBase&& segment_, VertexArray&& vertexArray_)
: gfx::Drawable::DrawSegment(mode_, std::move(segment_)),
vertexArray(std::move(vertexArray_)) {}
~DrawSegmentGL() override = default;
const VertexArray& getVertexArray() const { return vertexArray; }
void setVertexArray(VertexArray&& value) { vertexArray = std::move(value); }
protected:
VertexArray vertexArray;
};
} // namespace gl
} // namespace mln

View File

@@ -0,0 +1,49 @@
#include <mln/gl/dynamic_texture.hpp>
#include <mln/gl/texture2d.hpp>
#include <mln/gl/context.hpp>
namespace mln {
namespace gl {
DynamicTexture::DynamicTexture(Context& context, Size size, gfx::TexturePixelType pixelType)
: gfx::DynamicTexture(context, size, pixelType) {
deferredCreation = true;
}
void DynamicTexture::uploadImage(const uint8_t* pixelData, gfx::TextureHandle& texHandle) {
std::scoped_lock lock(mutex);
const auto& rect = texHandle.getRectangle();
const auto imageSize = Size(rect.w, rect.h);
auto size = imageSize.area() * texture->getPixelStride();
auto imageData = std::make_unique<uint8_t[]>(size);
std::copy(pixelData, pixelData + size, imageData.get());
imagesToUpload.emplace(texHandle, std::move(imageData));
gfx::DynamicTexture::uploadImage(pixelData, texHandle);
}
void DynamicTexture::uploadDeferredImages(gfx::UploadPass&) {
std::scoped_lock lock(mutex);
if (deferredCreation) {
texture->create();
deferredCreation = false;
}
for (const auto& pair : imagesToUpload) {
const auto& rect = pair.first.getRectangle();
texture->uploadSubRegion(pair.second.get(), Size(rect.w, rect.h), rect.x, rect.y);
}
imagesToUpload.clear();
}
bool DynamicTexture::removeTexture(const gfx::TextureHandle& texHandle) {
if (gfx::DynamicTexture::removeTexture(texHandle)) {
std::scoped_lock lock(mutex);
imagesToUpload.erase(texHandle);
return true;
}
return false;
}
} // namespace gl
} // namespace mln

480
src/mln/gl/enum.cpp Normal file
View File

@@ -0,0 +1,480 @@
#include <mln/gl/enum.hpp>
#include <mln/gfx/types.hpp>
#include <mln/gl/defines.hpp>
namespace mln {
namespace gl {
template <>
platform::GLenum Enum<gfx::DrawModeType>::to(const gfx::DrawModeType value) {
switch (value) {
case gfx::DrawModeType::Points:
return GL_POINTS;
case gfx::DrawModeType::Lines:
return GL_LINES;
case gfx::DrawModeType::LineLoop:
return GL_LINE_LOOP;
case gfx::DrawModeType::LineStrip:
return GL_LINE_STRIP;
case gfx::DrawModeType::Triangles:
return GL_TRIANGLES;
case gfx::DrawModeType::TriangleStrip:
return GL_TRIANGLE_STRIP;
case gfx::DrawModeType::TriangleFan:
return GL_TRIANGLE_FAN;
}
return GL_INVALID_ENUM;
}
template <>
gfx::ColorBlendEquationType Enum<gfx::ColorBlendEquationType>::from(const platform::GLint value) {
switch (value) {
case GL_FUNC_ADD:
return gfx::ColorBlendEquationType::Add;
case GL_FUNC_SUBTRACT:
return gfx::ColorBlendEquationType::Subtract;
case GL_FUNC_REVERSE_SUBTRACT:
return gfx::ColorBlendEquationType::ReverseSubtract;
default:
return {};
}
}
template <>
platform::GLenum Enum<gfx::ColorBlendEquationType>::to(const gfx::ColorBlendEquationType value) {
switch (value) {
case gfx::ColorBlendEquationType::Add:
return GL_FUNC_ADD;
case gfx::ColorBlendEquationType::Subtract:
return GL_FUNC_SUBTRACT;
case gfx::ColorBlendEquationType::ReverseSubtract:
return GL_FUNC_REVERSE_SUBTRACT;
}
return GL_INVALID_ENUM;
}
template <>
gfx::ColorBlendFactorType Enum<gfx::ColorBlendFactorType>::from(const platform::GLint value) {
switch (value) {
case GL_ZERO:
return gfx::ColorBlendFactorType::Zero;
case GL_ONE:
return gfx::ColorBlendFactorType::One;
case GL_SRC_COLOR:
return gfx::ColorBlendFactorType::SrcColor;
case GL_ONE_MINUS_SRC_COLOR:
return gfx::ColorBlendFactorType::OneMinusSrcColor;
case GL_DST_COLOR:
return gfx::ColorBlendFactorType::DstColor;
case GL_ONE_MINUS_DST_COLOR:
return gfx::ColorBlendFactorType::OneMinusDstColor;
case GL_SRC_ALPHA:
return gfx::ColorBlendFactorType::SrcAlpha;
case GL_ONE_MINUS_SRC_ALPHA:
return gfx::ColorBlendFactorType::OneMinusSrcAlpha;
case GL_DST_ALPHA:
return gfx::ColorBlendFactorType::DstAlpha;
case GL_ONE_MINUS_DST_ALPHA:
return gfx::ColorBlendFactorType::OneMinusDstAlpha;
case GL_CONSTANT_COLOR:
return gfx::ColorBlendFactorType::ConstantColor;
case GL_ONE_MINUS_CONSTANT_COLOR:
return gfx::ColorBlendFactorType::OneMinusConstantColor;
case GL_CONSTANT_ALPHA:
return gfx::ColorBlendFactorType::ConstantAlpha;
case GL_ONE_MINUS_CONSTANT_ALPHA:
return gfx::ColorBlendFactorType::OneMinusConstantAlpha;
case GL_SRC_ALPHA_SATURATE:
return gfx::ColorBlendFactorType::SrcAlphaSaturate;
default:
return {};
}
}
template <>
platform::GLenum Enum<gfx::ColorBlendFactorType>::to(const gfx::ColorBlendFactorType value) {
switch (value) {
case gfx::ColorBlendFactorType::Zero:
return GL_ZERO;
case gfx::ColorBlendFactorType::One:
return GL_ONE;
case gfx::ColorBlendFactorType::SrcColor:
return GL_SRC_COLOR;
case gfx::ColorBlendFactorType::OneMinusSrcColor:
return GL_ONE_MINUS_SRC_COLOR;
case gfx::ColorBlendFactorType::DstColor:
return GL_DST_COLOR;
case gfx::ColorBlendFactorType::OneMinusDstColor:
return GL_ONE_MINUS_DST_COLOR;
case gfx::ColorBlendFactorType::SrcAlpha:
return GL_SRC_ALPHA;
case gfx::ColorBlendFactorType::OneMinusSrcAlpha:
return GL_ONE_MINUS_SRC_ALPHA;
case gfx::ColorBlendFactorType::DstAlpha:
return GL_DST_ALPHA;
case gfx::ColorBlendFactorType::OneMinusDstAlpha:
return GL_ONE_MINUS_DST_ALPHA;
case gfx::ColorBlendFactorType::ConstantColor:
return GL_CONSTANT_COLOR;
case gfx::ColorBlendFactorType::OneMinusConstantColor:
return GL_ONE_MINUS_CONSTANT_COLOR;
case gfx::ColorBlendFactorType::ConstantAlpha:
return GL_CONSTANT_ALPHA;
case gfx::ColorBlendFactorType::OneMinusConstantAlpha:
return GL_ONE_MINUS_CONSTANT_ALPHA;
case gfx::ColorBlendFactorType::SrcAlphaSaturate:
return GL_SRC_ALPHA_SATURATE;
}
return GL_INVALID_ENUM;
}
template <>
gfx::DepthFunctionType Enum<gfx::DepthFunctionType>::from(const platform::GLint value) {
switch (value) {
case GL_NEVER:
return gfx::DepthFunctionType::Never;
case GL_LESS:
return gfx::DepthFunctionType::Less;
case GL_EQUAL:
return gfx::DepthFunctionType::Equal;
case GL_LEQUAL:
return gfx::DepthFunctionType::LessEqual;
case GL_GREATER:
return gfx::DepthFunctionType::Greater;
case GL_NOTEQUAL:
return gfx::DepthFunctionType::NotEqual;
case GL_GEQUAL:
return gfx::DepthFunctionType::GreaterEqual;
case GL_ALWAYS:
return gfx::DepthFunctionType::Always;
default:
return {};
}
}
template <>
platform::GLenum Enum<gfx::DepthFunctionType>::to(const gfx::DepthFunctionType value) {
switch (value) {
case gfx::DepthFunctionType::Never:
return GL_NEVER;
case gfx::DepthFunctionType::Less:
return GL_LESS;
case gfx::DepthFunctionType::Equal:
return GL_EQUAL;
case gfx::DepthFunctionType::LessEqual:
return GL_LEQUAL;
case gfx::DepthFunctionType::Greater:
return GL_GREATER;
case gfx::DepthFunctionType::NotEqual:
return GL_NOTEQUAL;
case gfx::DepthFunctionType::GreaterEqual:
return GL_GEQUAL;
case gfx::DepthFunctionType::Always:
return GL_ALWAYS;
}
return GL_INVALID_ENUM;
}
template <>
gfx::DepthMaskType Enum<gfx::DepthMaskType>::from(const platform::GLboolean value) {
return value ? gfx::DepthMaskType::ReadWrite : gfx::DepthMaskType::ReadOnly;
}
template <>
platform::GLboolean Enum<gfx::DepthMaskType>::to(const gfx::DepthMaskType value) {
return value == gfx::DepthMaskType::ReadWrite ? GL_TRUE : GL_FALSE;
}
template <>
gfx::StencilFunctionType Enum<gfx::StencilFunctionType>::from(const platform::GLint value) {
switch (value) {
case GL_NEVER:
return gfx::StencilFunctionType::Never;
case GL_LESS:
return gfx::StencilFunctionType::Less;
case GL_EQUAL:
return gfx::StencilFunctionType::Equal;
case GL_LEQUAL:
return gfx::StencilFunctionType::LessEqual;
case GL_GREATER:
return gfx::StencilFunctionType::Greater;
case GL_NOTEQUAL:
return gfx::StencilFunctionType::NotEqual;
case GL_GEQUAL:
return gfx::StencilFunctionType::GreaterEqual;
case GL_ALWAYS:
return gfx::StencilFunctionType::Always;
default:
return {};
}
}
template <>
platform::GLenum Enum<gfx::StencilFunctionType>::to(const gfx::StencilFunctionType value) {
switch (value) {
case gfx::StencilFunctionType::Never:
return GL_NEVER;
case gfx::StencilFunctionType::Less:
return GL_LESS;
case gfx::StencilFunctionType::Equal:
return GL_EQUAL;
case gfx::StencilFunctionType::LessEqual:
return GL_LEQUAL;
case gfx::StencilFunctionType::Greater:
return GL_GREATER;
case gfx::StencilFunctionType::NotEqual:
return GL_NOTEQUAL;
case gfx::StencilFunctionType::GreaterEqual:
return GL_GEQUAL;
case gfx::StencilFunctionType::Always:
return GL_ALWAYS;
}
return GL_INVALID_ENUM;
}
template <>
gfx::StencilOpType Enum<gfx::StencilOpType>::from(const platform::GLint value) {
switch (value) {
case GL_KEEP:
return gfx::StencilOpType::Keep;
case GL_ZERO:
return gfx::StencilOpType::Zero;
case GL_REPLACE:
return gfx::StencilOpType::Replace;
case GL_INCR:
return gfx::StencilOpType::Increment;
case GL_INCR_WRAP:
return gfx::StencilOpType::IncrementWrap;
case GL_DECR:
return gfx::StencilOpType::Decrement;
case GL_DECR_WRAP:
return gfx::StencilOpType::DecrementWrap;
case GL_INVERT:
return gfx::StencilOpType::Invert;
default:
return {};
}
}
template <>
platform::GLenum Enum<gfx::StencilOpType>::to(const gfx::StencilOpType value) {
switch (value) {
case gfx::StencilOpType::Keep:
return GL_KEEP;
case gfx::StencilOpType::Zero:
return GL_ZERO;
case gfx::StencilOpType::Replace:
return GL_REPLACE;
case gfx::StencilOpType::Increment:
return GL_INCR;
case gfx::StencilOpType::IncrementWrap:
return GL_INCR_WRAP;
case gfx::StencilOpType::Decrement:
return GL_DECR;
case gfx::StencilOpType::DecrementWrap:
return GL_DECR_WRAP;
case gfx::StencilOpType::Invert:
return GL_INVERT;
}
return GL_INVALID_ENUM;
}
template <>
gfx::CullFaceSideType Enum<gfx::CullFaceSideType>::from(const platform::GLint value) {
switch (value) {
case GL_FRONT:
return gfx::CullFaceSideType::Front;
case GL_BACK:
return gfx::CullFaceSideType::Back;
case GL_FRONT_AND_BACK:
return gfx::CullFaceSideType::FrontAndBack;
default:
return {};
}
}
template <>
platform::GLenum Enum<gfx::CullFaceSideType>::to(const gfx::CullFaceSideType value) {
switch (value) {
case gfx::CullFaceSideType::Front:
return GL_FRONT;
case gfx::CullFaceSideType::Back:
return GL_BACK;
case gfx::CullFaceSideType::FrontAndBack:
return GL_FRONT_AND_BACK;
}
return GL_INVALID_ENUM;
}
template <>
gfx::CullFaceWindingType Enum<gfx::CullFaceWindingType>::from(const platform::GLint value) {
switch (value) {
case GL_CW:
return gfx::CullFaceWindingType::Clockwise;
case GL_CCW:
return gfx::CullFaceWindingType::CounterClockwise;
default:
return {};
}
}
template <>
platform::GLenum Enum<gfx::CullFaceWindingType>::to(const gfx::CullFaceWindingType value) {
switch (value) {
case gfx::CullFaceWindingType::Clockwise:
return GL_CW;
case gfx::CullFaceWindingType::CounterClockwise:
return GL_CCW;
}
return GL_INVALID_ENUM;
}
template <>
gfx::BufferUsageType Enum<gfx::BufferUsageType>::from(const platform::GLint value) {
switch (value) {
case GL_STREAM_DRAW:
return gfx::BufferUsageType::StreamDraw;
case GL_STATIC_DRAW:
return gfx::BufferUsageType::StaticDraw;
case GL_DYNAMIC_DRAW:
return gfx::BufferUsageType::DynamicDraw;
default:
return {};
}
}
template <>
platform::GLenum Enum<gfx::BufferUsageType>::to(const gfx::BufferUsageType value) {
switch (value) {
case gfx::BufferUsageType::StreamDraw:
return GL_STREAM_DRAW;
case gfx::BufferUsageType::StaticDraw:
return GL_STATIC_DRAW;
case gfx::BufferUsageType::DynamicDraw:
return GL_DYNAMIC_DRAW;
}
return GL_INVALID_ENUM;
}
template <>
gfx::TexturePixelType Enum<gfx::TexturePixelType>::from(const platform::GLint value) {
switch (value) {
case GL_RGBA:
return gfx::TexturePixelType::RGBA;
case GL_ALPHA:
return gfx::TexturePixelType::Alpha;
case GL_STENCIL_INDEX:
return gfx::TexturePixelType::Stencil;
case GL_DEPTH_COMPONENT:
return gfx::TexturePixelType::Depth;
case GL_LUMINANCE:
return gfx::TexturePixelType::Luminance;
default:
return {};
}
}
template <>
platform::GLenum Enum<gfx::TexturePixelType>::to(const gfx::TexturePixelType value) {
switch (value) {
case gfx::TexturePixelType::RGBA:
return GL_RGBA;
case gfx::TexturePixelType::Alpha:
return GL_ALPHA;
case gfx::TexturePixelType::Stencil:
return GL_STENCIL_INDEX;
case gfx::TexturePixelType::Depth:
return GL_DEPTH_COMPONENT;
case gfx::TexturePixelType::Luminance:
return GL_LUMINANCE;
}
return GL_INVALID_ENUM;
}
template <>
template <>
platform::GLenum Enum<gfx::TexturePixelType>::sizedFor<>(const gfx::TexturePixelType value,
gfx::TextureChannelDataType type) {
switch (type) {
case gfx::TextureChannelDataType::UnsignedByte: {
return Enum<gfx::TexturePixelType>::to(value);
}
case gfx::TextureChannelDataType::HalfFloat: {
switch (value) {
case gfx::TexturePixelType::RGBA:
return GL_RGBA16F;
default:
break;
}
break;
}
case gfx::TextureChannelDataType::Float: {
switch (value) {
case gfx::TexturePixelType::RGBA:
return GL_RGBA32F;
default:
break;
}
break;
}
}
return GL_INVALID_ENUM;
}
template <>
gfx::TextureChannelDataType Enum<gfx::TextureChannelDataType>::from(const platform::GLint value) {
switch (value) {
case GL_UNSIGNED_BYTE:
return gfx::TextureChannelDataType::UnsignedByte;
case GL_HALF_FLOAT:
return gfx::TextureChannelDataType::HalfFloat;
case GL_FLOAT:
return gfx::TextureChannelDataType::Float;
default:
return {};
}
}
template <>
platform::GLenum Enum<gfx::TextureChannelDataType>::to(const gfx::TextureChannelDataType value) {
switch (value) {
case gfx::TextureChannelDataType::UnsignedByte:
return GL_UNSIGNED_BYTE;
case gfx::TextureChannelDataType::HalfFloat:
return GL_HALF_FLOAT;
case gfx::TextureChannelDataType::Float:
return GL_FLOAT;
}
return GL_INVALID_ENUM;
}
template <>
gfx::RenderbufferPixelType Enum<gfx::RenderbufferPixelType>::from(const platform::GLint value) {
switch (value) {
case GL_RGBA8:
return gfx::RenderbufferPixelType::RGBA;
case GL_DEPTH_COMPONENT16:
return gfx::RenderbufferPixelType::Depth;
case GL_DEPTH24_STENCIL8:
return gfx::RenderbufferPixelType::DepthStencil;
default:
return {};
}
}
template <>
platform::GLenum Enum<gfx::RenderbufferPixelType>::to(const gfx::RenderbufferPixelType value) {
switch (value) {
case gfx::RenderbufferPixelType::RGBA:
return GL_RGBA8;
case gfx::RenderbufferPixelType::Depth:
return GL_DEPTH_COMPONENT16;
case gfx::RenderbufferPixelType::DepthStencil:
return GL_DEPTH24_STENCIL8;
}
return GL_INVALID_ENUM;
}
} // namespace gl
} // namespace mln

26
src/mln/gl/enum.hpp Normal file
View File

@@ -0,0 +1,26 @@
#pragma once
#include <mln/platform/gl_functions.hpp>
#include <type_traits>
namespace mln {
namespace gl {
template <typename T>
class Enum {
public:
using InType =
std::conditional_t<std::is_same_v<std::underlying_type_t<T>, bool>, platform::GLboolean, platform::GLint>;
using OutType =
std::conditional_t<std::is_same_v<std::underlying_type_t<T>, bool>, platform::GLboolean, platform::GLenum>;
static T from(InType);
static OutType to(T);
template <typename U>
static OutType sizedFor(T, U type);
};
} // namespace gl
} // namespace mln

30
src/mln/gl/extension.hpp Normal file
View File

@@ -0,0 +1,30 @@
#pragma once
#include <initializer_list>
#include <utility>
#include <functional>
namespace mln {
namespace gl {
using ProcAddress = void (*)();
template <class>
class ExtensionFunction;
template <class R, class... Args>
class ExtensionFunction<R(Args...)> {
public:
ExtensionFunction(const ProcAddress ptr_)
: ptr(ptr_) {}
explicit operator bool() const { return ptr; }
R operator()(Args... args) const { return (*reinterpret_cast<R (*)(Args...)>(ptr))(std::forward<Args>(args)...); }
private:
const ProcAddress ptr;
};
} // namespace gl
} // namespace mln

69
src/mln/gl/fence.cpp Normal file
View File

@@ -0,0 +1,69 @@
#include <mln/gl/fence.hpp>
#include <mln/gl/defines.hpp>
#include <mln/util/instrumentation.hpp>
#include <cassert>
#include <stdexcept>
#include <string>
namespace mln {
namespace gl {
using namespace platform;
namespace {
std::string glErrors() {
std::string error_list = "";
for (auto error = glGetError(); error != GL_NO_ERROR; error = glGetError()) {
error_list += std::to_string(error) + " ";
}
if (error_list.empty()) {
return error_list;
}
return "GL error codes: " + error_list;
}
} // namespace
Fence::Fence() {}
Fence::~Fence() {
MLN_TRACE_FUNC();
if (fence) {
glDeleteSync(fence);
}
}
void Fence::insert() noexcept {
MLN_TRACE_FUNC();
assert(!fence);
fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
}
bool Fence::isSignaled() const {
MLN_TRACE_FUNC();
if (!fence) {
return false;
}
GLenum fenceStatus = glClientWaitSync(fence, GL_SYNC_FLUSH_COMMANDS_BIT, 0);
switch (fenceStatus) {
case GL_ALREADY_SIGNALED:
[[fallthrough]];
case GL_CONDITION_SATISFIED:
return true;
case GL_TIMEOUT_EXPIRED:
return false;
case GL_WAIT_FAILED:
throw std::runtime_error("glClientWaitSync failed. " + glErrors());
return false;
default:
assert(false); // unreachable
return false;
}
}
} // namespace gl
} // namespace mln

22
src/mln/gl/fence.hpp Normal file
View File

@@ -0,0 +1,22 @@
#pragma once
#include <mln/gl/types.hpp>
#include <mln/platform/gl_functions.hpp>
namespace mln {
namespace gl {
class Fence {
public:
Fence();
~Fence();
void insert() noexcept;
bool isSignaled() const;
private:
platform::GLsync fence{nullptr};
};
} // namespace gl
} // namespace mln

View File

@@ -0,0 +1,16 @@
#pragma once
#include <mln/gl/object.hpp>
#include <mln/util/size.hpp>
namespace mln {
namespace gl {
class Framebuffer {
public:
Size size;
gl::UniqueFramebuffer framebuffer;
};
} // namespace gl
} // namespace mln

View File

@@ -0,0 +1,38 @@
#include <mln/gl/context.hpp>
#include <mln/gl/index_buffer_resource.hpp>
#include <mln/util/instrumentation.hpp>
namespace mln {
namespace gl {
IndexBufferResource::IndexBufferResource(UniqueBuffer&& buffer_, int byteSize_)
: buffer(std::move(buffer_)),
byteSize(byteSize_) {
MLN_TRACE_ALLOC_INDEX_BUFFER(buffer.get(), byteSize);
if (buffer) {
auto& stats = buffer.get_deleter().context.renderingStats();
stats.numIndexBuffers++;
stats.memIndexBuffers += byteSize;
stats.numBuffers++;
stats.totalBuffers++;
stats.memBuffers += byteSize;
}
}
IndexBufferResource::~IndexBufferResource() noexcept {
MLN_TRACE_FREE_INDEX_BUFFER(buffer.get());
if (buffer) {
auto& stats = buffer.get_deleter().context.renderingStats();
stats.numIndexBuffers--;
stats.memIndexBuffers -= byteSize;
stats.memBuffers -= byteSize;
assert(stats.memIndexBuffers >= 0);
}
}
} // namespace gl
} // namespace mln

View File

@@ -0,0 +1,19 @@
#pragma once
#include <mln/gfx/index_buffer.hpp>
#include <mln/gl/object.hpp>
namespace mln {
namespace gl {
class IndexBufferResource : public gfx::IndexBufferResource {
public:
IndexBufferResource(UniqueBuffer&& buffer_, int byteSize_);
~IndexBufferResource() noexcept override;
UniqueBuffer buffer;
int byteSize;
};
} // namespace gl
} // namespace mln

View File

@@ -0,0 +1,211 @@
#include <mln/gl/layer_group_gl.hpp>
#include <mln/gfx/drawable_tweaker.hpp>
#include <mln/gfx/render_pass.hpp>
#include <mln/gfx/renderable.hpp>
#include <mln/gfx/renderer_backend.hpp>
#include <mln/gfx/upload_pass.hpp>
#include <mln/gl/drawable_gl.hpp>
#include <mln/renderer/paint_parameters.hpp>
#include <mln/shaders/gl/shader_program_gl.hpp>
#include <mln/util/convert.hpp>
#include <mln/util/instrumentation.hpp>
namespace mln {
namespace gl {
using namespace platform;
TileLayerGroupGL::TileLayerGroupGL(int32_t layerIndex_, std::size_t initialCapacity, std::string name_)
: TileLayerGroup(layerIndex_, initialCapacity, std::move(name_)) {}
void TileLayerGroupGL::upload(gfx::UploadPass& uploadPass) {
MLN_TRACE_FUNC();
MLN_ZONE_STR(name);
if (!enabled) {
return;
}
visitDrawables([&](gfx::Drawable& drawable) {
if (!drawable.getEnabled()) {
return;
}
auto& drawableGL = static_cast<gl::DrawableGL&>(drawable);
#if !defined(NDEBUG)
std::string label;
if (const auto& tileID = drawable.getTileID()) {
label = drawable.getName() + "/" + util::toString(*tileID);
}
const auto labelPtr = (label.empty() ? drawable.getName() : label).c_str();
const auto debugGroup = uploadPass.createDebugGroup(labelPtr);
#endif
drawableGL.upload(uploadPass);
});
}
void TileLayerGroupGL::render(RenderOrchestrator&, PaintParameters& parameters) {
if (!enabled) {
return;
}
MLN_TRACE_FUNC();
auto& context = static_cast<gl::Context&>(parameters.context);
// `stencilModeFor3D` uses a different stencil mask value each time its called, so if the
// drawables in this layer use 3D stencil mode, we need to set it up here so that all the
// drawables end up using the same mode value.
// 2D and 3D features in the same layer group is not supported.
bool features3d = false;
bool stencil3d = false;
gfx::StencilMode stencilMode3d;
parameters.stencilClippingAvailable = parameters.renderTargetHasStencilBuffer;
if (getDrawableCount() && parameters.stencilClippingAvailable) {
MLN_TRACE_ZONE(clip masks);
#if !defined(NDEBUG)
const auto label_clip = getName() + (getName().empty() ? "" : "-") + "tile-clip-masks";
const auto debugGroupClip = parameters.encoder->createDebugGroup(label_clip.c_str());
#endif
// If we're using stencil clipping, we need to handle 3D features separately
if (stencilTiles && !stencilTiles->empty()) {
visitDrawables([&](const gfx::Drawable& drawable) {
if (drawable.getEnabled() && drawable.getIs3D() && drawable.hasRenderPass(parameters.pass)) {
features3d = true;
if (drawable.getEnableStencil()) {
stencil3d = true;
}
}
});
}
// If we're doing 3D stenciling and have any features
// to draw, set up the single-value stencil mask.
// If we're doing 2D stenciling and have any drawables with tile IDs,
// render each tile into the stencil buffer with a different value.
if (features3d) {
stencilMode3d = stencil3d ? parameters.stencilModeFor3D() : gfx::StencilMode::disabled();
} else if (stencilTiles && !stencilTiles->empty()) {
if (!parameters.renderTileClippingMasks(stencilTiles)) {
parameters.stencilClippingAvailable = false;
}
}
}
if (!parameters.stencilClippingAvailable) {
context.setStencilMode(gfx::StencilMode::disabled());
}
#if !defined(NDEBUG)
const auto label_render = getName() + (getName().empty() ? "" : "-") + "render";
const auto debugGroupRender = parameters.encoder->createDebugGroup(label_render.c_str());
#endif
bool bindUBOs = false;
visitDrawables([&](gfx::Drawable& drawable) {
if (!drawable.getEnabled() || !drawable.hasRenderPass(parameters.pass)) {
return;
}
#if !defined(NDEBUG)
std::string label_tile;
if (const auto& tileID = drawable.getTileID()) {
label_tile = util::toString(drawable.getID().id()) + "/" + drawable.getName() + "/" +
util::toString(*tileID);
}
const auto labelPtr = (label_tile.empty() ? drawable.getName() : label_tile).c_str();
const auto debugGroupTile = parameters.encoder->createDebugGroup(labelPtr);
#endif
if (!bindUBOs) {
uniformBuffers.bind();
bindUBOs = true;
}
for (const auto& tweaker : drawable.getTweakers()) {
tweaker->execute(drawable, parameters);
}
// For layer groups with 3D features, enable either the single-value
// stencil mode for features with stencil enabled or disable stenciling.
// 2D drawables will set their own stencil mode within `draw`.
if (features3d) {
context.setStencilMode(drawable.getEnableStencil() ? stencilMode3d : gfx::StencilMode::disabled());
}
drawable.draw(parameters);
});
if (bindUBOs) {
uniformBuffers.unbind();
}
}
LayerGroupGL::LayerGroupGL(int32_t layerIndex_, std::size_t initialCapacity, std::string name_)
: LayerGroup(layerIndex_, initialCapacity, std::move(name_)) {}
void LayerGroupGL::upload(gfx::UploadPass& uploadPass) {
if (!enabled) {
return;
}
visitDrawables([&](gfx::Drawable& drawable) {
if (!drawable.getEnabled()) {
return;
}
auto& drawableGL = static_cast<gl::DrawableGL&>(drawable);
#if !defined(NDEBUG)
const auto debugGroup = uploadPass.createDebugGroup(drawable.getName().c_str());
#endif
drawableGL.upload(uploadPass);
});
}
void LayerGroupGL::render(RenderOrchestrator&, PaintParameters& parameters) {
if (!enabled) {
return;
}
auto& context = static_cast<gl::Context&>(parameters.context);
parameters.stencilClippingAvailable = parameters.renderTargetHasStencilBuffer;
if (!parameters.stencilClippingAvailable) {
context.setStencilMode(gfx::StencilMode::disabled());
}
bool bindUBOs = false;
visitDrawables([&](gfx::Drawable& drawable) {
if (!drawable.getEnabled() || !drawable.hasRenderPass(parameters.pass)) {
return;
}
#if !defined(NDEBUG)
const auto debugGroup = parameters.encoder->createDebugGroup(drawable.getName().c_str());
#endif
if (!bindUBOs) {
uniformBuffers.bind();
bindUBOs = true;
}
for (const auto& tweaker : drawable.getTweakers()) {
tweaker->execute(drawable, parameters);
}
drawable.draw(parameters);
});
if (bindUBOs) {
uniformBuffers.unbind();
}
}
} // namespace gl
} // namespace mln

51
src/mln/gl/object.cpp Normal file
View File

@@ -0,0 +1,51 @@
#include <mln/gl/object.hpp>
#include <mln/gl/context.hpp>
#include <cassert>
namespace mln {
namespace gl {
namespace detail {
void ProgramDeleter::operator()(ProgramID id) const {
assert(context);
context->abandonedPrograms.push_back(id);
}
void ShaderDeleter::operator()(ShaderID id) const {
assert(context);
context->abandonedShaders.push_back(id);
}
void BufferDeleter::operator()(BufferID id) const {
context.abandonedBuffers.push_back(id);
}
void TextureDeleter::operator()(TextureID id) const {
assert(context);
context->abandonedTextures.push_back(id);
context->renderingStats().numActiveTextures--;
assert(context->renderingStats().numActiveTextures >= 0);
assert(context->renderingStats().numActiveTextures < context->renderingStats().numCreatedTextures);
}
void VertexArrayDeleter::operator()(VertexArrayID id) const {
assert(context);
if (id != 0) {
context->abandonedVertexArrays.push_back(id);
}
}
void FramebufferDeleter::operator()(FramebufferID id) const {
assert(context);
context->abandonedFramebuffers.push_back(id);
}
void RenderbufferDeleter::operator()(RenderbufferID id) const {
assert(context);
context->abandonedRenderbuffers.push_back(id);
}
} // namespace detail
} // namespace gl
} // namespace mln

60
src/mln/gl/object.hpp Normal file
View File

@@ -0,0 +1,60 @@
#pragma once
#include <mln/gl/types.hpp>
#include <unique_resource.hpp>
namespace mln {
namespace gl {
class Context;
namespace detail {
struct ProgramDeleter {
Context* context;
void operator()(ProgramID) const;
};
struct ShaderDeleter {
Context* context;
void operator()(ShaderID) const;
};
struct BufferDeleter {
Context& context;
void operator()(BufferID) const;
};
struct TextureDeleter {
Context* context;
void operator()(TextureID) const;
};
struct VertexArrayDeleter {
Context* context;
void operator()(VertexArrayID) const;
};
struct FramebufferDeleter {
Context* context;
void operator()(FramebufferID) const;
};
struct RenderbufferDeleter {
Context* context;
void operator()(RenderbufferID) const;
};
} // namespace detail
using UniqueProgram = std_experimental::unique_resource<ProgramID, detail::ProgramDeleter>;
using UniqueShader = std_experimental::unique_resource<ShaderID, detail::ShaderDeleter>;
using UniqueBuffer = std_experimental::unique_resource<BufferID, detail::BufferDeleter>;
using UniqueTexture = std_experimental::unique_resource<TextureID, detail::TextureDeleter>;
using UniqueVertexArray = std_experimental::unique_resource<VertexArrayID, detail::VertexArrayDeleter>;
using UniqueFramebuffer = std_experimental::unique_resource<FramebufferID, detail::FramebufferDeleter>;
using UniqueRenderbuffer = std_experimental::unique_resource<RenderbufferID, detail::RenderbufferDeleter>;
} // namespace gl
} // namespace mln

View File

@@ -0,0 +1,80 @@
#include <mln/gl/offscreen_texture.hpp>
#include <mln/gl/renderable_resource.hpp>
#include <mln/gl/context.hpp>
#include <mln/gl/framebuffer.hpp>
namespace mln {
namespace gl {
class OffscreenTextureResource final : public gl::RenderableResource {
public:
OffscreenTextureResource(gl::Context& context_, const Size size_, const gfx::TextureChannelDataType type_)
: context(context_),
size(size_),
type(type_) {
assert(!size.isEmpty());
texture = context.createTexture2D();
texture->setSize(size);
texture->setFormat(gfx::TexturePixelType::RGBA, type);
texture->setSamplerConfiguration({.filter = gfx::TextureFilterType::Linear,
.wrapU = gfx::TextureWrapType::Clamp,
.wrapV = gfx::TextureWrapType::Clamp});
}
~OffscreenTextureResource() noexcept override = default;
void bind() override {
if (!framebuffer) {
assert(texture);
texture->create();
framebuffer = context.createFramebuffer(*texture);
} else {
context.bindFramebuffer = framebuffer->framebuffer;
}
context.activeTextureUnit = 0;
context.scissorTest = {0, 0, 0, 0};
context.viewport = {.x = 0, .y = 0, .size = size};
}
PremultipliedImage readStillImage() {
assert(framebuffer);
context.bindFramebuffer = framebuffer->framebuffer;
return context.readFramebuffer<PremultipliedImage>(size);
}
gfx::Texture2DPtr& getTexture() {
assert(texture);
return texture;
}
private:
gl::Context& context;
const Size size;
gfx::Texture2DPtr texture;
const gfx::TextureChannelDataType type;
std::optional<gl::Framebuffer> framebuffer;
};
OffscreenTexture::OffscreenTexture(gl::Context& context, const Size size_, const gfx::TextureChannelDataType type)
: gfx::OffscreenTexture(size, std::make_unique<OffscreenTextureResource>(context, size_, type)) {}
bool OffscreenTexture::isRenderable() {
try {
getResource<OffscreenTextureResource>().bind();
return true;
} catch (const std::runtime_error&) {
return false;
}
}
PremultipliedImage OffscreenTexture::readStillImage() {
return getResource<OffscreenTextureResource>().readStillImage();
}
const gfx::Texture2DPtr& OffscreenTexture::getTexture() {
return getResource<OffscreenTextureResource>().getTexture();
}
} // namespace gl
} // namespace mln

View File

@@ -0,0 +1,24 @@
#pragma once
#include <mln/gfx/offscreen_texture.hpp>
#include <mln/gfx/types.hpp>
namespace mln {
namespace gl {
class Context;
class OffscreenTexture final : public gfx::OffscreenTexture {
public:
OffscreenTexture(gl::Context&,
Size size,
gfx::TextureChannelDataType type = gfx::TextureChannelDataType::UnsignedByte);
bool isRenderable() override;
PremultipliedImage readStillImage() override;
const gfx::Texture2DPtr& getTexture() override;
};
} // namespace gl
} // namespace mln

View File

@@ -0,0 +1,29 @@
#include <mln/gl/render_pass.hpp>
#include <mln/gl/command_encoder.hpp>
#include <mln/gl/renderable_resource.hpp>
#include <mln/gl/context.hpp>
namespace mln {
namespace gl {
RenderPass::RenderPass(gl::CommandEncoder& commandEncoder_,
const char* name,
const gfx::RenderPassDescriptor& descriptor)
: commandEncoder(commandEncoder_),
debugGroup(commandEncoder.createDebugGroup(name)) {
descriptor.renderable.getResource<gl::RenderableResource>().bind();
const auto clearDebugGroup(commandEncoder.createDebugGroup("clear"));
commandEncoder.context.setScissorTest({.x = 0, .y = 0, .width = 0, .height = 0});
commandEncoder.context.clear(descriptor.clearColor, descriptor.clearDepth, descriptor.clearStencil);
}
void RenderPass::pushDebugGroup(const char* name) {
commandEncoder.pushDebugGroup(name);
}
void RenderPass::popDebugGroup() {
commandEncoder.popDebugGroup();
}
} // namespace gl
} // namespace mln

View File

@@ -0,0 +1,31 @@
#pragma once
#include <mln/gfx/render_pass.hpp>
namespace mln {
namespace gfx {
class CommandEncoder;
} // namespace gfx
namespace gl {
class CommandEncoder;
class Context;
class RenderPass final : public gfx::RenderPass {
public:
RenderPass(gl::CommandEncoder&, const char* name, const gfx::RenderPassDescriptor&);
private:
void pushDebugGroup(const char* name) override;
void popDebugGroup() override;
private:
gl::CommandEncoder& commandEncoder;
const gfx::DebugGroup<gfx::CommandEncoder> debugGroup;
};
} // namespace gl
} // namespace mln

View File

@@ -0,0 +1,13 @@
#include <mln/gl/renderbuffer_resource.hpp>
#include <mln/util/instrumentation.hpp>
namespace mln {
namespace gl {
RenderbufferResource::~RenderbufferResource() noexcept {
MLN_TRACE_FREE_RT(renderbuffer.get());
}
} // namespace gl
} // namespace mln

View File

@@ -0,0 +1,20 @@
#pragma once
#include <mln/gfx/renderbuffer.hpp>
#include <mln/gl/object.hpp>
namespace mln {
namespace gl {
class RenderbufferResource final : public gfx::RenderbufferResource {
public:
explicit RenderbufferResource(UniqueRenderbuffer renderbuffer_)
: renderbuffer(std::move(renderbuffer_)) {}
~RenderbufferResource() noexcept override;
UniqueRenderbuffer renderbuffer;
};
} // namespace gl
} // namespace mln

View File

@@ -0,0 +1,158 @@
#include <mln/gl/renderer_backend.hpp>
#include <mln/gfx/backend_scope.hpp>
#include <mln/gfx/shader_registry.hpp>
#include <mln/gl/context.hpp>
#include <mln/gl/extension.hpp>
#include <mln/shaders/shader_manifest.hpp>
#include <mln/util/instrumentation.hpp>
#include <mln/util/logging.hpp>
#include <mln/shaders/gl/shader_group_gl.hpp>
#include <mln/shaders/gl/legacy/programs.hpp>
#include <cassert>
namespace mln {
namespace gl {
RendererBackend::RendererBackend(const gfx::ContextMode contextMode_)
: gfx::RendererBackend(contextMode_) {}
RendererBackend::RendererBackend(const gfx::ContextMode contextMode_, const TaggedScheduler& threadPool_)
: gfx::RendererBackend(contextMode_, threadPool_) {}
std::unique_ptr<gfx::Context> RendererBackend::createContext() {
MLN_TRACE_FUNC();
auto result = std::make_unique<gl::Context>(
*this); // Tagged background thread pool will be owned by the RendererBackend
result->enableDebugging();
result->initializeExtensions(std::bind(&RendererBackend::getExtensionFunctionPointer, this, std::placeholders::_1));
return result;
}
PremultipliedImage RendererBackend::readFramebuffer(const Size& size) {
MLN_TRACE_FUNC();
return getContext<gl::Context>().readFramebuffer<PremultipliedImage>(size);
}
void RendererBackend::assumeFramebufferBinding(const gl::FramebufferID fbo) {
MLN_TRACE_FUNC();
getContext<gl::Context>().bindFramebuffer.setCurrentValue(fbo);
if (fbo != ImplicitFramebufferBinding) {
assert(gl::value::BindFramebuffer::Get() == getContext<gl::Context>().bindFramebuffer.getCurrentValue());
}
}
void RendererBackend::assumeViewport(int32_t x, int32_t y, const Size& size) {
MLN_TRACE_FUNC();
getContext<gl::Context>().viewport.setCurrentValue({x, y, size});
assert(gl::value::Viewport::Get() == getContext<gl::Context>().viewport.getCurrentValue());
}
void RendererBackend::assumeScissorTest(int32_t x, int32_t y, uint32_t width, uint32_t height) {
MLN_TRACE_FUNC();
getContext<gl::Context>().scissorTest.setCurrentValue({x, y, width, height});
assert(gl::value::ScissorTest::Get() == getContext<gl::Context>().scissorTest.getCurrentValue());
}
bool RendererBackend::implicitFramebufferBound() {
MLN_TRACE_FUNC();
return getContext<gl::Context>().bindFramebuffer.getCurrentValue() == ImplicitFramebufferBinding;
}
void RendererBackend::setFramebufferBinding(const gl::FramebufferID fbo) {
MLN_TRACE_FUNC();
getContext<gl::Context>().bindFramebuffer = fbo;
if (fbo != ImplicitFramebufferBinding) {
assert(gl::value::BindFramebuffer::Get() == getContext<gl::Context>().bindFramebuffer.getCurrentValue());
}
}
void RendererBackend::setViewport(int32_t x, int32_t y, const Size& size) {
MLN_TRACE_FUNC();
getContext<gl::Context>().viewport = {x, y, size};
assert(gl::value::Viewport::Get() == getContext<gl::Context>().viewport.getCurrentValue());
}
void RendererBackend::setScissorTest(int32_t x, int32_t y, uint32_t width, uint32_t height) {
MLN_TRACE_FUNC();
getContext<gl::Context>().scissorTest = {x, y, width, height};
assert(gl::value::ScissorTest::Get() == getContext<gl::Context>().scissorTest.getCurrentValue());
}
RendererBackend::~RendererBackend() = default;
/// @brief Register a list of types with a shader registry instance
/// @tparam ...ShaderID Pack of BuiltIn:: shader IDs
/// @param registry A shader registry instance
/// @param programParameters ProgramParameters used to initialize each instance
template <shaders::BuiltIn... ShaderID>
void registerTypes(gfx::ShaderRegistry& registry, const ProgramParameters& programParameters) {
MLN_TRACE_FUNC();
/// The following fold expression will create a shader for every type
/// in the parameter pack and register it with the shader registry.
/// Registration calls are wrapped in a lambda that throws on registration
/// failure, we shouldn't expect registration to fail unless the shader
/// registry instance provided already has conflicting programs present.
(
[&]() {
const auto name = std::string(shaders::ShaderSource<ShaderID, gfx::Backend::Type::OpenGL>::name);
if (!registry.registerShaderGroup(std::make_shared<ShaderGroupGL<ShaderID>>(programParameters), name)) {
throw std::runtime_error("Failed to register " + name + " with shader registry!");
}
}(),
...);
}
void RendererBackend::initShaders(gfx::ShaderRegistry& shaders, const ProgramParameters& programParameters) {
MLN_TRACE_FUNC();
registerTypes<shaders::BuiltIn::BackgroundShader,
shaders::BuiltIn::BackgroundPatternShader,
shaders::BuiltIn::CircleShader,
shaders::BuiltIn::CollisionBoxShader,
shaders::BuiltIn::CollisionCircleShader,
shaders::BuiltIn::CustomGeometryShader,
shaders::BuiltIn::CustomSymbolIconShader,
shaders::BuiltIn::DebugShader,
shaders::BuiltIn::FillShader,
shaders::BuiltIn::FillOutlineShader,
shaders::BuiltIn::FillPatternShader,
shaders::BuiltIn::FillOutlinePatternShader,
shaders::BuiltIn::FillOutlineTriangulatedShader,
shaders::BuiltIn::FillExtrusionShader,
shaders::BuiltIn::FillExtrusionPatternShader,
shaders::BuiltIn::HeatmapShader,
shaders::BuiltIn::HeatmapTextureShader,
shaders::BuiltIn::HillshadePrepareShader,
shaders::BuiltIn::HillshadeShader,
shaders::BuiltIn::ColorReliefShader,
shaders::BuiltIn::LineShader,
shaders::BuiltIn::LineGradientShader,
shaders::BuiltIn::LinePatternShader,
shaders::BuiltIn::LineSDFShader,
shaders::BuiltIn::LocationIndicatorShader,
shaders::BuiltIn::LocationIndicatorTexturedShader,
shaders::BuiltIn::RasterShader,
shaders::BuiltIn::SymbolIconShader,
shaders::BuiltIn::SymbolSDFShader,
shaders::BuiltIn::SymbolTextAndIconShader>(shaders, programParameters);
// Initialize legacy shader programs
Programs programs(programParameters);
programs.registerWith(shaders);
}
} // namespace gl
} // namespace mln

View File

@@ -0,0 +1,286 @@
#include <mln/gl/resource_pool.hpp>
#include <cassert>
#include <type_traits>
#include <mln/gl/context.hpp>
#include <mln/gl/defines.hpp>
#include <mln/gl/enum.hpp>
#include <mln/util/hash.hpp>
#include <mln/util/instrumentation.hpp>
#include <mln/util/logging.hpp>
namespace mln {
namespace gl {
using namespace platform;
namespace {
#if !defined(NDEBUG)
void logDebugMessage(const std::string& message) {
Log::Debug(Event::General, "Texture2DPool: " + message);
}
#else
void logDebugMessage(const std::string&) {}
#endif
} // namespace
size_t Texture2DDesc::channelCount() const {
switch (pixelFormat) {
case gfx::TexturePixelType::Alpha:
case gfx::TexturePixelType::Depth:
case gfx::TexturePixelType::Luminance:
case gfx::TexturePixelType::Stencil:
return 1;
case gfx::TexturePixelType::RGBA:
return 4;
default:
assert(!"Unknown texture pixel type");
return 0;
}
}
size_t Texture2DDesc::channelStorageSize() const {
switch (channelType) {
case gfx::TextureChannelDataType::Float:
return 4;
case gfx::TextureChannelDataType::HalfFloat:
return 2;
case gfx::TextureChannelDataType::UnsignedByte:
return 1;
default:
assert(!"Unknown texture channel data type");
return 0;
}
}
size_t Texture2DDesc::getStorageSize() const {
return size.width * size.height * channelCount() * channelStorageSize();
}
std::size_t Texture2DDescHash::operator()(const Texture2DDesc& desc) const {
std::size_t seed = 0;
util::hash_combine(seed, desc.size.width);
util::hash_combine(seed, desc.size.height);
util::hash_combine(seed, std::underlying_type_t<gfx::TexturePixelType>(desc.pixelFormat));
util::hash_combine(seed, std::underlying_type_t<gfx::TextureChannelDataType>(desc.channelType));
return seed;
}
Texture2DPool::Texture2DPool(Context* context_, size_t maxPoolStorage_)
: context(context_),
maxPoolStorage(maxPoolStorage_) {
assert(context);
}
Texture2DPool::~Texture2DPool() {
if (!context->getCleanupOnDestruction()) {
return;
}
assert(usedStorage() == 0);
assert(unusedStorage() == poolStorage);
shrink();
assert(usedStorage() == 0);
assert(unusedStorage() == 0);
assert(poolStorage == 0);
assert(lru_cache.empty());
assert(descriptions.empty());
assert(pool.empty());
logDebugMessage("total allocations " + util::toString(allocCount));
logDebugMessage("total reused allocations " + util::toString(reuseCount));
logDebugMessage("total textures released " + util::toString(releaseCount));
logDebugMessage("total textures released and deallocated " + util::toString(freedCount));
}
TextureID Texture2DPool::alloc(const Texture2DDesc& desc) {
MLN_TRACE_FUNC();
context->renderingStats().numActiveTextures++;
// Check if the texture is already allocated
auto set_it = pool.find(desc);
if (set_it != pool.end() && !set_it->second.free.empty()) {
// Reuse an existing texture
TextureID id = *set_it->second.free.begin();
set_it->second.free.erase(id);
set_it->second.used.insert(id);
assert(lru_cache.isHit(id));
lru_cache.remove(id);
++reuseCount;
return id;
}
// Allocate a new texture
++allocCount;
return allocateGLMemory(desc);
}
void Texture2DPool::release(TextureID id) {
MLN_TRACE_FUNC();
assert(isUsed(id));
auto desc = descriptions.at(id);
auto set_it = pool.find(desc);
set_it->second.used.erase(id);
set_it->second.free.insert(id);
lru_cache.touch(id);
// Evict old textures if necessary
evict();
++releaseCount;
}
TextureID Texture2DPool::allocateGLMemory(const Texture2DDesc& desc) {
MLN_TRACE_FUNC();
// Create handle
TextureID id = 0;
MBGL_CHECK_ERROR(glGenTextures(1, &id));
assert(id != 0);
// Allocate memory
// Bind to TU 0 and upload
context->activeTextureUnit = 0;
context->texture[0] = id;
MBGL_CHECK_ERROR(glTexImage2D(GL_TEXTURE_2D,
0,
Enum<gfx::TexturePixelType>::sizedFor(desc.pixelFormat, desc.channelType),
desc.size.width,
desc.size.height,
0,
Enum<gfx::TexturePixelType>::to(desc.pixelFormat),
Enum<gfx::TextureChannelDataType>::to(desc.channelType),
nullptr));
if (glGetError()) {
throw std::bad_alloc();
}
// Update stats
context->renderingStats().numCreatedTextures++;
// Add to descriptions
descriptions.emplace(id, desc);
// Add to pool
pool[desc].used.insert(id);
// Update poolStorage
auto storage = desc.getStorageSize();
poolStorage += storage;
context->renderingStats().memTextures += storage;
MLN_TRACE_ALLOC_TEXTURE(id, storage);
// Evict old textures if necessary
evict();
return id;
}
void Texture2DPool::freeAllocatedGLMemory(TextureID id) {
MLN_TRACE_FUNC();
// Remove from descriptions
auto desc_it = descriptions.find(id);
assert(desc_it != descriptions.end());
auto desc = desc_it->second;
descriptions.erase(desc_it);
// Remove from descriptions lru_cache if it exists
lru_cache.remove(id);
// Remove from pool
auto set_it = pool.find(desc);
assert(set_it != pool.end());
auto free_it = set_it->second.free.find(id);
assert(free_it != set_it->second.free.end());
set_it->second.free.erase(free_it);
if (set_it->second.free.empty() && set_it->second.used.empty()) {
pool.erase(set_it);
}
// Update poolStorage
auto storage = desc.getStorageSize();
poolStorage -= storage;
context->renderingStats().memTextures -= storage;
assert(context->renderingStats().memTextures >= 0);
MLN_TRACE_FREE_TEXTURE(id);
// Delete the texture
MBGL_CHECK_ERROR(glDeleteTextures(1, &id));
// Update stats
context->renderingStats().numCreatedTextures--;
++freedCount;
}
void Texture2DPool::evict() {
while (poolStorage > maxPoolStorage && !lru_cache.empty()) {
TextureID id = lru_cache.evict();
freeAllocatedGLMemory(id);
}
}
void Texture2DPool::shrink() {
MLN_TRACE_FUNC();
while (!lru_cache.empty()) {
TextureID id = lru_cache.evict();
freeAllocatedGLMemory(id);
}
}
bool Texture2DPool::isPooled(TextureID id) const {
return descriptions.find(id) != descriptions.end();
}
bool Texture2DPool::isUsed(TextureID id) const {
auto desc_it = descriptions.find(id);
if (desc_it == descriptions.end()) {
return false;
}
const auto& desc = desc_it->second;
const auto& used_set = pool.at(desc).used;
return used_set.find(id) != used_set.end();
}
bool Texture2DPool::isUnused(TextureID id) const {
auto desc_it = descriptions.find(id);
if (desc_it == descriptions.end()) {
return false;
}
const auto& desc = desc_it->second;
const auto& free_set = pool.at(desc).free;
return free_set.find(id) != free_set.end();
}
size_t Texture2DPool::usedStorage() const {
size_t total = 0;
for (const auto& set : pool) {
total += set.first.getStorageSize() * set.second.used.size();
}
return total;
}
size_t Texture2DPool::unusedStorage() const {
size_t total = 0;
for (const auto& set : pool) {
total += set.first.getStorageSize() * set.second.free.size();
}
return total;
}
const Texture2DDesc& Texture2DPool::desc(TextureID id) const {
assert(descriptions.find(id) != descriptions.end());
return descriptions.at(id);
}
size_t Texture2DPool::storage(TextureID id) const {
assert(descriptions.find(id) != descriptions.end());
return descriptions.at(id).getStorageSize();
}
} // namespace gl
} // namespace mln

View File

@@ -0,0 +1,122 @@
#pragma once
#include <cstddef>
#include <functional>
#include <unordered_map>
#include <unordered_set>
#include <mln/gfx/types.hpp>
#include <mln/gl/types.hpp>
#include <mln/util/lru_cache.hpp>
#include <mln/util/size.hpp>
namespace mln {
namespace gl {
class Context;
struct Texture2DDesc {
Size size;
gfx::TexturePixelType pixelFormat;
gfx::TextureChannelDataType channelType;
size_t channelCount() const;
size_t channelStorageSize() const;
size_t getStorageSize() const;
};
struct Texture2DDescHash {
std::size_t operator()(const Texture2DDesc& desc) const;
};
inline bool operator==(const Texture2DDesc& desc1, const Texture2DDesc& desc2) {
return desc1.size == desc2.size && desc1.pixelFormat == desc2.pixelFormat && desc1.channelType == desc2.channelType;
}
inline bool operator!=(const Texture2DDesc& desc1, const Texture2DDesc& desc2) {
return !(desc1 == desc2);
}
class Texture2DPool {
public:
// Create a texture pool with a maximum storage size maxPoolStorage in bytes
// If maxPoolStorage is 0, pooling will disabled
Texture2DPool(Context* context, size_t maxPoolStorage = 16 * 1024 * 1024);
// Deallocate all storage
~Texture2DPool();
// Allocate a texture with the given size, pixel format, and channel type
// If the texture is already allocated in the pool, the existing texture ID will be returned
TextureID alloc(const Size& size, gfx::TexturePixelType pixelFormat, gfx::TextureChannelDataType channelType) {
return alloc({size, pixelFormat, channelType});
}
TextureID alloc(const Texture2DDesc& desc);
// Release the texture but keep it in the pool
void release(TextureID id);
// Free all unused textures
void shrink();
// Get the description of a texture
const Texture2DDesc& desc(TextureID id) const;
// Get the storage size in byte of a texture
size_t storage(TextureID id) const;
// Get the storage size in byte of all textures being used
size_t usedStorage() const;
// Get the storage size in byte of all unused textures
size_t unusedStorage() const;
// Get the storage size in byte of all textures in the pool
size_t storage() const { return poolStorage; }
// Get the maximum storage size before starting to evict unused textures
size_t maxStorage() const { return maxPoolStorage; }
bool empty() const { return poolStorage == 0; }
// Check if a texture is pooled
bool isPooled(TextureID id) const;
// Check if a texture is pooled and being used
bool isUsed(TextureID id) const;
// Check if a texture is pooled but released
bool isUnused(TextureID id) const;
private:
struct TextureSet {
std::unordered_set<TextureID> free;
std::unordered_set<TextureID> used;
};
// Effectively glGenTextures and glTexImage2D
TextureID allocateGLMemory(const Texture2DDesc& desc);
// Effectively call glDeleteTextures
void freeAllocatedGLMemory(TextureID id);
// Evict the least recently used textures until the pool storage is below maxPoolStorage
void evict();
private:
Context* context = nullptr;
size_t maxPoolStorage = 0;
std::size_t poolStorage = 0;
std::unordered_map<Texture2DDesc, TextureSet, Texture2DDescHash> pool;
std::unordered_map<TextureID, Texture2DDesc> descriptions;
LRU<TextureID> lru_cache;
// stats
int64_t allocCount = 0; // total allocations
int64_t reuseCount = 0; // total reused allocations
int64_t releaseCount = 0; // total textures released
int64_t freedCount = 0; // total textures released and deallocated
};
} // namespace gl
} // namespace mln

62
src/mln/gl/state.hpp Normal file
View File

@@ -0,0 +1,62 @@
#pragma once
#include <tuple>
namespace mln {
namespace gl {
// Wraps a piece of OpenGL state and remember its value to avoid redundant state
// calls. Wrapped types need to implement to the Value class interface:
//
// class Value {
// using Type = ...;
// static const constexpr Type Default = ...;
// static void Set(const Type& value);
// static Type Get();
// };
template <typename T, typename... Args>
class State {
public:
State(Args&&... args)
: params(std::forward_as_tuple(::std::forward<Args>(args)...)) {}
State& operator=(const typename T::Type& value) {
if (*this != value) {
setCurrentValue(value);
set(std::index_sequence_for<Args...>{});
}
return *this;
}
bool operator==(const typename T::Type& value) const { return !(*this != value); }
bool operator!=(const typename T::Type& value) const { return dirty || currentValue != value; }
void setCurrentValue(const typename T::Type& value) {
dirty = false;
currentValue = value;
}
// Mark the state as dirty. This means that the next time we are assigning a value to this
// piece of OpenGL state will always result in an actual OpenGL call.
void setDirty() { dirty = true; }
typename T::Type getCurrentValue() const { return currentValue; }
bool isDirty() const { return dirty; }
private:
template <std::size_t... I>
void set(std::index_sequence<I...>) {
T::Set(currentValue, std::get<I>(params)...);
}
private:
typename T::Type currentValue = T::Default;
bool dirty = true;
const std::tuple<Args...> params;
};
} // namespace gl
} // namespace mln

206
src/mln/gl/texture2d.cpp Normal file
View File

@@ -0,0 +1,206 @@
#include <mln/gl/texture2d.hpp>
#include <mln/gl/context.hpp>
#include <mln/gl/defines.hpp>
#include <mln/gl/enum.hpp>
#include <mln/platform/gl_functions.hpp>
#include <mln/util/instrumentation.hpp>
namespace mln {
namespace gl {
Texture2D::Texture2D(gl::Context& context_)
: context(context_) {}
Texture2D::~Texture2D() {}
Texture2D& Texture2D::setSamplerConfiguration(const SamplerState& samplerState_) noexcept {
samplerState = samplerState_;
samplerStateDirty = texture != nullptr;
return *this;
}
Texture2D& Texture2D::setFormat(gfx::TexturePixelType pixelFormat_, gfx::TextureChannelDataType channelType_) noexcept {
if (pixelFormat_ == pixelFormat && channelType_ == channelType) {
return *this;
}
assert(!texture);
pixelFormat = pixelFormat_;
channelType = channelType_;
storageDirty = true;
return *this;
}
Texture2D& Texture2D::setSize(mln::Size size_) noexcept {
size = size_;
storageDirty = true;
return *this;
}
Texture2D& Texture2D::setImage(std::shared_ptr<PremultipliedImage> image_) noexcept {
image = std::move(image_);
return *this;
}
size_t Texture2D::getDataSize() const noexcept {
return size.width * size.height * getPixelStride();
}
size_t Texture2D::getPixelStride() const noexcept {
switch (channelType) {
case gfx::TextureChannelDataType::UnsignedByte:
return 1 * numChannels();
case gfx::TextureChannelDataType::HalfFloat:
return 2 * numChannels();
case gfx::TextureChannelDataType::Float:
return 4 * numChannels();
default:
return 0;
}
}
size_t Texture2D::numChannels() const noexcept {
switch (pixelFormat) {
case gfx::TexturePixelType::RGBA:
return 4;
case gfx::TexturePixelType::Alpha:
return 1;
default:
return 0;
}
}
void Texture2D::allocateTexture() {
MLN_TRACE_FUNC();
// Create a new texture object
auto obj = context.createUniqueTexture(size, pixelFormat, channelType);
texture = std::make_unique<UniqueTexture>(std::move(obj));
}
void Texture2D::updateTextureData(const void* data) noexcept {
MLN_TRACE_FUNC();
if (data) {
uploadSubRegion(data, size, 0, 0);
}
storageDirty = false;
updateSamplerConfiguration();
}
void Texture2D::create() {
allocateTexture();
if (storageDirty) {
updateTextureData();
}
}
platform::GLuint Texture2D::getTextureID() const noexcept {
return texture ? *texture : 0;
}
void Texture2D::updateSamplerConfiguration() noexcept {
using namespace platform;
samplerStateDirty = false;
MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_MIN_FILTER,
samplerState.filter == gfx::TextureFilterType::Nearest ? GL_NEAREST : GL_LINEAR));
MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_MAG_FILTER,
samplerState.filter == gfx::TextureFilterType::Nearest ? GL_NEAREST : GL_LINEAR));
MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_WRAP_S,
samplerState.wrapU == gfx::TextureWrapType::Clamp ? GL_CLAMP_TO_EDGE : GL_REPEAT));
MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_WRAP_T,
samplerState.wrapV == gfx::TextureWrapType::Clamp ? GL_CLAMP_TO_EDGE : GL_REPEAT));
}
void Texture2D::bind(int32_t location, int32_t textureUnit) noexcept {
using namespace platform;
assert(gfx::MaxActiveTextureUnits > textureUnit);
if (gfx::MaxActiveTextureUnits <= textureUnit) return;
// Bind to the texture unit
context.activeTextureUnit = static_cast<uint8_t>(textureUnit);
context.texture[static_cast<size_t>(textureUnit)] = getTextureID();
boundTextureUnit = textureUnit;
// Update the sampler state if it was changed after resource creation
if (samplerStateDirty) {
updateSamplerConfiguration();
}
// Link the bound texture unit with the requested sampler in the active shader
glUniform1i(location, textureUnit);
boundLocation = location;
}
void Texture2D::unbind() noexcept {
using namespace platform;
// Unlink the texture from the last used texture unit
if (boundTextureUnit != -1) {
context.activeTextureUnit = boundTextureUnit;
context.texture[static_cast<size_t>(boundTextureUnit)] = 0;
boundTextureUnit = -1;
}
// And clear the uniform value linking a sampler to the texture unit
// Default back to GL_TEXTURE0
if (boundLocation != -1) {
glUniform1i(boundLocation, 0);
boundLocation = -1;
}
}
void Texture2D::upload(const void* pixelData, const Size& size_) {
if (!texture || storageDirty || size_ == Size{0, 0} || size_ != size) {
size = size_;
// Create the texture object if we don't already have one or if storage is dirty
allocateTexture();
}
updateTextureData(pixelData);
}
void Texture2D::uploadSubRegion(const void* pixelData, const Size& size_, uint16_t xOffset, uint16_t yOffset) noexcept {
using namespace platform;
assert(texture);
// update sampler configuration if needed
if (samplerStateDirty) {
updateSamplerConfiguration();
}
// Bind to TU 0 and upload
context.activeTextureUnit = 0;
context.texture[0] = getTextureID();
context.pixelStoreUnpack = {1};
MBGL_CHECK_ERROR(glTexSubImage2D(GL_TEXTURE_2D,
0,
xOffset,
yOffset,
size_.width,
size_.height,
Enum<gfx::TexturePixelType>::to(pixelFormat),
Enum<gfx::TextureChannelDataType>::to(channelType),
pixelData));
context.renderingStats().numTextureUpdates++;
context.renderingStats().textureUpdateBytes += static_cast<size_t>(getPixelStride() * size_.width * size_.height);
}
void Texture2D::upload() {
if (image && image->valid()) {
setFormat(gfx::TexturePixelType::RGBA, gfx::TextureChannelDataType::UnsignedByte);
upload(image->data.get(), image->size);
image.reset();
}
}
} // namespace gl
} // namespace mln

View File

@@ -0,0 +1,122 @@
#include <mln/gl/timestamp_query_extension.hpp>
#include <cassert>
#include <memory>
namespace mln {
namespace gl {
namespace extension {
using namespace platform;
namespace {
constexpr const char *const extName = "GL_EXT_disjoint_timer_query";
struct TimestampQueryLoader {
TimestampQueryLoader(const GlContexsLoader &loadExtension)
: glGenQueries(loadExtension({{extName, "glGenQueries"}, {extName, "glGenQueriesEXT"}})),
glDeleteQueries(loadExtension({{extName, "glDeleteQueries"}, {extName, "glDeleteQueriesEXT"}})),
glIsQuery(loadExtension({{extName, "glIsQuery"}, {extName, "glIsQueryEXT"}})),
glBeginQuery(loadExtension({{extName, "glBeginQuery"}, {extName, "glBeginQueryEXT"}})),
glEndQuery(loadExtension({{extName, "glEndQuery"}, {extName, "glEndQueryEXT"}})),
glQueryCounter(loadExtension({{extName, "glQueryCounter"}, {extName, "glQueryCounterEXT"}})),
glGetQueryiv(loadExtension({{extName, "glGetQueryiv"}, {extName, "glGetQueryivEXT"}})),
glGetQueryObjectiv(loadExtension({{extName, "glGetQueryObjectiv"}, {extName, "glGetQueryObjectivEXT"}})),
glGetQueryObjectuiv(loadExtension({{extName, "glGetQueryObjectuiv"}, {extName, "glGetQueryObjectuivEXT"}})),
glGetQueryObjecti64v(
loadExtension({{extName, "glGetQueryObjecti64v"}, {extName, "glGetQueryObjecti64vEXT"}})),
glGetQueryObjectui64v(
loadExtension({{extName, "glGetQueryObjectui64v"}, {extName, "glGetQueryObjectui64vEXT"}})),
glGetInteger64v(loadExtension({{extName, "glGetInteger64v"}, {extName, "glGetInteger64vEXT"}})) {}
ExtensionFunction<void(GLsizei n, GLuint *ids)> glGenQueries;
ExtensionFunction<void(GLsizei n, const GLuint *ids)> glDeleteQueries;
ExtensionFunction<GLboolean(GLuint id)> glIsQuery;
ExtensionFunction<void(GLenum target, GLuint id)> glBeginQuery;
ExtensionFunction<void(GLenum target)> glEndQuery;
ExtensionFunction<void(GLuint id, GLenum target)> glQueryCounter;
ExtensionFunction<void(GLenum target, GLenum pname, GLint *params)> glGetQueryiv;
ExtensionFunction<void(GLuint id, GLenum pname, GLint *params)> glGetQueryObjectiv;
ExtensionFunction<void(GLuint id, GLenum pname, GLuint *params)> glGetQueryObjectuiv;
ExtensionFunction<void(GLuint id, GLenum pname, GLint64 *params)> glGetQueryObjecti64v;
ExtensionFunction<void(GLuint id, GLenum pname, GLuint64 *params)> glGetQueryObjectui64v;
ExtensionFunction<void(GLenum pname, GLint64 *data)> glGetInteger64v;
};
std::unique_ptr<TimestampQueryLoader> &singleton() {
static std::unique_ptr<TimestampQueryLoader> loader;
return loader;
}
} // namespace
void glGenQueries(GLsizei n, GLuint *ids) {
assert(singleton() && singleton()->glGenQueries);
singleton()->glGenQueries(n, ids);
}
void glDeleteQueries(GLsizei n, const GLuint *ids) {
assert(singleton() && singleton()->glDeleteQueries);
singleton()->glDeleteQueries(n, ids);
}
GLboolean glIsQuery(GLuint id) {
assert(singleton() && singleton()->glIsQuery);
return singleton()->glIsQuery(id);
}
void glBeginQuery(GLenum target, GLuint id) {
assert(singleton() && singleton()->glBeginQuery);
singleton()->glBeginQuery(target, id);
}
void glEndQuery(GLenum target) {
assert(singleton() && singleton()->glEndQuery);
singleton()->glEndQuery(target);
}
void glQueryCounter(GLuint id, GLenum target) {
assert(singleton() && singleton()->glQueryCounter);
singleton()->glQueryCounter(id, target);
}
void glGetQueryiv(GLenum target, GLenum pname, GLint *params) {
assert(singleton() && singleton()->glGetQueryiv);
singleton()->glGetQueryiv(target, pname, params);
}
void glGetQueryObjectiv(GLuint id, GLenum pname, GLint *params) {
assert(singleton() && singleton()->glGetQueryObjectiv);
singleton()->glGetQueryObjectiv(id, pname, params);
}
void glGetQueryObjectuiv(GLuint id, GLenum pname, GLuint *params) {
assert(singleton() && singleton()->glGetQueryObjectuiv);
singleton()->glGetQueryObjectuiv(id, pname, params);
}
void glGetQueryObjecti64v(GLuint id, GLenum pname, GLint64 *params) {
assert(singleton() && singleton()->glGetQueryObjecti64v);
singleton()->glGetQueryObjecti64v(id, pname, params);
}
void glGetQueryObjectui64v(GLuint id, GLenum pname, GLuint64 *params) {
assert(singleton() && singleton()->glGetQueryObjectui64v);
singleton()->glGetQueryObjectui64v(id, pname, params);
}
void glGetInteger64v(GLenum pname, GLint64 *data) {
assert(singleton() && singleton()->glGetInteger64v);
singleton()->glGetInteger64v(pname, data);
}
void loadTimeStampQueryExtension(const GlContexsLoader &loadExtension) {
auto &loader = singleton();
loader = std::make_unique<TimestampQueryLoader>(loadExtension);
}
} // namespace extension
} // namespace gl
} // namespace mln

View File

@@ -0,0 +1,41 @@
#pragma once
#include <mln/platform/gl_functions.hpp>
#include <mln/gl/extension.hpp>
#include <functional>
#define GL_QUERY_COUNTER_BITS 0x8864
#define GL_CURRENT_QUERY 0x8865
#define GL_QUERY_RESULT 0x8866
#define GL_QUERY_RESULT_AVAILABLE 0x8867
#define GL_TIME_ELAPSED 0x88BF
#define GL_TIMESTAMP 0x8E28
#define GL_GPU_DISJOINT 0x8FBB
namespace mln {
namespace gl {
namespace extension {
using namespace platform;
void glGenQueries(GLsizei n, GLuint *ids);
void glDeleteQueries(GLsizei n, const GLuint *ids);
GLboolean glIsQuery(GLuint id);
void glBeginQuery(GLenum target, GLuint id);
void glEndQuery(GLenum target);
void glQueryCounter(GLuint id, GLenum target);
void glGetQueryiv(GLenum target, GLenum pname, GLint *params);
void glGetQueryObjectiv(GLuint id, GLenum pname, GLint *params);
void glGetQueryObjectuiv(GLuint id, GLenum pname, GLuint *params);
void glGetQueryObjecti64v(GLuint id, GLenum pname, GLint64 *params);
void glGetQueryObjectui64v(GLuint id, GLenum pname, GLuint64 *params);
void glGetInteger64v(GLenum pname, GLint64 *data);
using GlContexsLoader = std::function<ProcAddress(std::initializer_list<std::pair<const char *, const char *>>)>;
void loadTimeStampQueryExtension(const GlContexsLoader &loadExtension);
} // namespace extension
} // namespace gl
} // namespace mln

Some files were not shown because too many files have changed in this diff Show More