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:
170
src/mln/actor/mailbox.cpp
Normal file
170
src/mln/actor/mailbox.cpp
Normal 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
|
||||
70
src/mln/actor/scheduler.cpp
Normal file
70
src/mln/actor/scheduler.cpp
Normal 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
|
||||
163
src/mln/algorithm/update_renderables.hpp
Normal file
163
src/mln/algorithm/update_renderables.hpp
Normal 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
|
||||
143
src/mln/algorithm/update_tile_masks.hpp
Normal file
143
src/mln/algorithm/update_tile_masks.hpp
Normal 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
|
||||
273
src/mln/annotation/annotation_manager.cpp
Normal file
273
src/mln/annotation/annotation_manager.cpp
Normal 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
|
||||
97
src/mln/annotation/annotation_manager.hpp
Normal file
97
src/mln/annotation/annotation_manager.hpp
Normal 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
|
||||
35
src/mln/annotation/annotation_source.cpp
Normal file
35
src/mln/annotation/annotation_source.cpp
Normal 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
|
||||
34
src/mln/annotation/annotation_source.hpp
Normal file
34
src/mln/annotation/annotation_source.hpp
Normal 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
|
||||
121
src/mln/annotation/annotation_tile.cpp
Normal file
121
src/mln/annotation/annotation_tile.cpp
Normal 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
|
||||
67
src/mln/annotation/annotation_tile.hpp
Normal file
67
src/mln/annotation/annotation_tile.hpp
Normal 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
|
||||
36
src/mln/annotation/fill_annotation_impl.cpp
Normal file
36
src/mln/annotation/fill_annotation_impl.cpp
Normal 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
|
||||
19
src/mln/annotation/fill_annotation_impl.hpp
Normal file
19
src/mln/annotation/fill_annotation_impl.hpp
Normal 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
|
||||
37
src/mln/annotation/line_annotation_impl.cpp
Normal file
37
src/mln/annotation/line_annotation_impl.cpp
Normal 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
|
||||
19
src/mln/annotation/line_annotation_impl.hpp
Normal file
19
src/mln/annotation/line_annotation_impl.hpp
Normal 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
|
||||
63
src/mln/annotation/render_annotation_source.cpp
Normal file
63
src/mln/annotation/render_annotation_source.cpp
Normal 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
|
||||
31
src/mln/annotation/render_annotation_source.hpp
Normal file
31
src/mln/annotation/render_annotation_source.hpp
Normal 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
|
||||
58
src/mln/annotation/shape_annotation_impl.cpp
Normal file
58
src/mln/annotation/shape_annotation_impl.cpp
Normal 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
|
||||
58
src/mln/annotation/shape_annotation_impl.hpp
Normal file
58
src/mln/annotation/shape_annotation_impl.hpp
Normal 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
|
||||
23
src/mln/annotation/symbol_annotation_impl.cpp
Normal file
23
src/mln/annotation/symbol_annotation_impl.cpp
Normal 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
|
||||
102
src/mln/annotation/symbol_annotation_impl.hpp
Normal file
102
src/mln/annotation/symbol_annotation_impl.hpp
Normal 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
|
||||
25
src/mln/geometry/anchor.hpp
Normal file
25
src/mln/geometry/anchor.hpp
Normal 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
|
||||
349
src/mln/geometry/debug_font_data.hpp
Normal file
349
src/mln/geometry/debug_font_data.hpp
Normal 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)
|
||||
105
src/mln/geometry/dem_data.cpp
Normal file
105
src/mln/geometry/dem_data.cpp
Normal 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
|
||||
41
src/mln/geometry/dem_data.hpp
Normal file
41
src/mln/geometry/dem_data.hpp
Normal 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
|
||||
369
src/mln/geometry/feature_index.cpp
Normal file
369
src/mln/geometry/feature_index.cpp
Normal 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
|
||||
171
src/mln/geometry/feature_index.hpp
Normal file
171
src/mln/geometry/feature_index.hpp
Normal 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
|
||||
264
src/mln/geometry/line_atlas.cpp
Normal file
264
src/mln/geometry/line_atlas.cpp
Normal 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
|
||||
83
src/mln/geometry/line_atlas.hpp
Normal file
83
src/mln/geometry/line_atlas.hpp
Normal 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
22
src/mln/gfx/attribute.cpp
Normal 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
333
src/mln/gfx/attribute.hpp
Normal 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
|
||||
24
src/mln/gfx/collision_drawable_data.hpp
Normal file
24
src/mln/gfx/collision_drawable_data.hpp
Normal 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
|
||||
26
src/mln/gfx/color_relief_drawable_data.hpp
Normal file
26
src/mln/gfx/color_relief_drawable_data.hpp
Normal 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
|
||||
24
src/mln/gfx/cull_face_mode.hpp
Normal file
24
src/mln/gfx/cull_face_mode.hpp
Normal 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
94
src/mln/gfx/drawable.cpp
Normal 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
|
||||
49
src/mln/gfx/drawable_atlases_tweaker.cpp
Normal file
49
src/mln/gfx/drawable_atlases_tweaker.cpp
Normal 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
|
||||
313
src/mln/gfx/drawable_builder.cpp
Normal file
313
src/mln/gfx/drawable_builder.cpp
Normal 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
|
||||
311
src/mln/gfx/drawable_builder_impl.cpp
Normal file
311
src/mln/gfx/drawable_builder_impl.cpp
Normal 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
|
||||
103
src/mln/gfx/drawable_builder_impl.hpp
Normal file
103
src/mln/gfx/drawable_builder_impl.hpp
Normal 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
|
||||
51
src/mln/gfx/drawable_custom_layer_host_tweaker.cpp
Normal file
51
src/mln/gfx/drawable_custom_layer_host_tweaker.cpp
Normal 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
|
||||
79
src/mln/gfx/dynamic_texture.cpp
Normal file
79
src/mln/gfx/dynamic_texture.cpp
Normal 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
|
||||
263
src/mln/gfx/dynamic_texture_atlas.cpp
Normal file
263
src/mln/gfx/dynamic_texture_atlas.cpp
Normal 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
|
||||
248
src/mln/gfx/fill_generator.cpp
Normal file
248
src/mln/gfx/fill_generator.cpp
Normal 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
|
||||
187
src/mln/gfx/gpu_expression.cpp
Normal file
187
src/mln/gfx/gpu_expression.cpp
Normal 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
|
||||
27
src/mln/gfx/hillshade_prepare_drawable_data.hpp
Normal file
27
src/mln/gfx/hillshade_prepare_drawable_data.hpp
Normal 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
|
||||
23
src/mln/gfx/image_drawable_data.hpp
Normal file
23
src/mln/gfx/image_drawable_data.hpp
Normal 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
|
||||
36
src/mln/gfx/index_buffer.hpp
Normal file
36
src/mln/gfx/index_buffer.hpp
Normal 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
|
||||
112
src/mln/gfx/index_vector.hpp
Normal file
112
src/mln/gfx/index_vector.hpp
Normal 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
|
||||
23
src/mln/gfx/line_drawable_data.hpp
Normal file
23
src/mln/gfx/line_drawable_data.hpp
Normal 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
|
||||
29
src/mln/gfx/offscreen_texture.hpp
Normal file
29
src/mln/gfx/offscreen_texture.hpp
Normal 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
|
||||
643
src/mln/gfx/polyline_generator.cpp
Normal file
643
src/mln/gfx/polyline_generator.cpp
Normal 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
|
||||
43
src/mln/gfx/render_pass.hpp
Normal file
43
src/mln/gfx/render_pass.hpp
Normal 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
|
||||
26
src/mln/gfx/renderer_backend.cpp
Normal file
26
src/mln/gfx/renderer_backend.cpp
Normal 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
|
||||
291
src/mln/gfx/rendering_stats.cpp
Normal file
291
src/mln/gfx/rendering_stats.cpp
Normal 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
|
||||
51
src/mln/gfx/shader_group.cpp
Normal file
51
src/mln/gfx/shader_group.cpp
Normal 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
|
||||
50
src/mln/gfx/shader_registry.cpp
Normal file
50
src/mln/gfx/shader_registry.cpp
Normal 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
|
||||
47
src/mln/gfx/symbol_drawable_data.hpp
Normal file
47
src/mln/gfx/symbol_drawable_data.hpp
Normal 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
49
src/mln/gfx/uniform.hpp
Normal 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
|
||||
56
src/mln/gfx/uniform_buffer.cpp
Normal file
56
src/mln/gfx/uniform_buffer.cpp
Normal 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
102
src/mln/gfx/upload_pass.hpp
Normal 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
|
||||
132
src/mln/gfx/vertex_attribute.cpp
Normal file
132
src/mln/gfx/vertex_attribute.cpp
Normal 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
|
||||
131
src/mln/gfx/vertex_vector.hpp
Normal file
131
src/mln/gfx/vertex_vector.hpp
Normal 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
19
src/mln/gl/attribute.cpp
Normal 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
133
src/mln/gl/attribute.hpp
Normal 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
|
||||
521
src/mln/gl/buffer_allocator.cpp
Normal file
521
src/mln/gl/buffer_allocator.cpp
Normal 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
|
||||
59
src/mln/gl/command_encoder.cpp
Normal file
59
src/mln/gl/command_encoder.cpp
Normal 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
|
||||
33
src/mln/gl/command_encoder.hpp
Normal file
33
src/mln/gl/command_encoder.hpp
Normal 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
886
src/mln/gl/context.cpp
Normal 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
258
src/mln/gl/context.hpp
Normal 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
|
||||
106
src/mln/gl/debugging_extension.cpp
Normal file
106
src/mln/gl/debugging_extension.cpp
Normal 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
|
||||
110
src/mln/gl/debugging_extension.hpp
Normal file
110
src/mln/gl/debugging_extension.hpp
Normal 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
731
src/mln/gl/defines.hpp
Normal 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
|
||||
18
src/mln/gl/draw_scope_resource.hpp
Normal file
18
src/mln/gl/draw_scope_resource.hpp
Normal 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
284
src/mln/gl/drawable_gl.cpp
Normal 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
|
||||
50
src/mln/gl/drawable_gl_builder.cpp
Normal file
50
src/mln/gl/drawable_gl_builder.cpp
Normal 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
|
||||
67
src/mln/gl/drawable_gl_impl.hpp
Normal file
67
src/mln/gl/drawable_gl_impl.hpp
Normal 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
|
||||
49
src/mln/gl/dynamic_texture.cpp
Normal file
49
src/mln/gl/dynamic_texture.cpp
Normal 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
480
src/mln/gl/enum.cpp
Normal 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
26
src/mln/gl/enum.hpp
Normal 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
30
src/mln/gl/extension.hpp
Normal 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
69
src/mln/gl/fence.cpp
Normal 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
22
src/mln/gl/fence.hpp
Normal 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
|
||||
16
src/mln/gl/framebuffer.hpp
Normal file
16
src/mln/gl/framebuffer.hpp
Normal 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
|
||||
38
src/mln/gl/index_buffer_resource.cpp
Normal file
38
src/mln/gl/index_buffer_resource.cpp
Normal 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
|
||||
19
src/mln/gl/index_buffer_resource.hpp
Normal file
19
src/mln/gl/index_buffer_resource.hpp
Normal 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
|
||||
211
src/mln/gl/layer_group_gl.cpp
Normal file
211
src/mln/gl/layer_group_gl.cpp
Normal 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
51
src/mln/gl/object.cpp
Normal 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
60
src/mln/gl/object.hpp
Normal 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
|
||||
80
src/mln/gl/offscreen_texture.cpp
Normal file
80
src/mln/gl/offscreen_texture.cpp
Normal 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
|
||||
24
src/mln/gl/offscreen_texture.hpp
Normal file
24
src/mln/gl/offscreen_texture.hpp
Normal 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
|
||||
29
src/mln/gl/render_pass.cpp
Normal file
29
src/mln/gl/render_pass.cpp
Normal 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
|
||||
31
src/mln/gl/render_pass.hpp
Normal file
31
src/mln/gl/render_pass.hpp
Normal 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
|
||||
13
src/mln/gl/renderbuffer_resource.cpp
Normal file
13
src/mln/gl/renderbuffer_resource.cpp
Normal 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
|
||||
20
src/mln/gl/renderbuffer_resource.hpp
Normal file
20
src/mln/gl/renderbuffer_resource.hpp
Normal 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
|
||||
158
src/mln/gl/renderer_backend.cpp
Normal file
158
src/mln/gl/renderer_backend.cpp
Normal 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
|
||||
286
src/mln/gl/resource_pool.cpp
Normal file
286
src/mln/gl/resource_pool.cpp
Normal 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
|
||||
122
src/mln/gl/resource_pool.hpp
Normal file
122
src/mln/gl/resource_pool.hpp
Normal 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
62
src/mln/gl/state.hpp
Normal 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
206
src/mln/gl/texture2d.cpp
Normal 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
|
||||
122
src/mln/gl/timestamp_query_extension.cpp
Normal file
122
src/mln/gl/timestamp_query_extension.cpp
Normal 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
|
||||
41
src/mln/gl/timestamp_query_extension.hpp
Normal file
41
src/mln/gl/timestamp_query_extension.hpp
Normal 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
Reference in New Issue
Block a user