1679 lines
73 KiB
C++
1679 lines
73 KiB
C++
#include <mln/renderer/render_orchestrator.hpp>
|
|
#include <mln/renderer/layers/render_symbol_layer.hpp>
|
|
#include <mln/renderer/continuous_layer_range.hpp>
|
|
#include <mln/renderer/particle_fast_path.hpp>
|
|
|
|
#include <mln/annotation/annotation_manager.hpp>
|
|
#include <mln/layermanager/layer_manager.hpp>
|
|
#include <mln/renderer/change_request.hpp>
|
|
#include <mln/renderer/renderer_observer.hpp>
|
|
#include <mln/renderer/render_source.hpp>
|
|
#include <mln/renderer/render_layer.hpp>
|
|
#include <mln/renderer/render_static_data.hpp>
|
|
#include <mln/renderer/retained_underlay_cache.hpp>
|
|
#include <mln/renderer/render_tree.hpp>
|
|
#include <mln/renderer/update_parameters.hpp>
|
|
#include <mln/renderer/upload_parameters.hpp>
|
|
#include <mln/renderer/pattern_atlas.hpp>
|
|
#include <mln/renderer/paint_parameters.hpp>
|
|
#include <mln/renderer/transition_parameters.hpp>
|
|
#include <mln/renderer/property_evaluation_parameters.hpp>
|
|
#include <mln/renderer/tile_parameters.hpp>
|
|
#include <mln/renderer/render_tile.hpp>
|
|
#include <mln/renderer/style_diff.hpp>
|
|
#include <mln/renderer/query.hpp>
|
|
#include <mln/renderer/image_manager.hpp>
|
|
#include <mln/geometry/line_atlas.hpp>
|
|
#include <mln/style/source_impl.hpp>
|
|
#include <mln/style/transition_options.hpp>
|
|
#include <mln/text/glyph_manager.hpp>
|
|
#include <mln/tile/tile.hpp>
|
|
#include <mln/util/instrumentation.hpp>
|
|
#include <mln/util/math.hpp>
|
|
#include <mln/util/string.hpp>
|
|
#include <mln/util/logging.hpp>
|
|
|
|
#include <algorithm>
|
|
#include <string_view>
|
|
|
|
namespace mln {
|
|
|
|
using namespace style;
|
|
|
|
LayerRenderItem::LayerRenderItem(RenderLayer& layer_, RenderSource* source_, uint32_t index_)
|
|
: layer(layer_),
|
|
source(source_),
|
|
index(index_) {}
|
|
|
|
bool LayerRenderItem::hasRenderPass(RenderPass pass) const {
|
|
return layer.get().hasRenderPass(pass);
|
|
}
|
|
void LayerRenderItem::upload(gfx::UploadPass& pass) const {
|
|
layer.get().upload(pass);
|
|
}
|
|
void LayerRenderItem::render(PaintParameters& parameters) const {
|
|
layer.get().render(parameters);
|
|
}
|
|
const std::string& LayerRenderItem::getName() const {
|
|
return layer.get().getID();
|
|
}
|
|
|
|
void LayerRenderItem::updateDebugDrawables(DebugLayerGroupMap&, PaintParameters&) const {};
|
|
|
|
namespace {
|
|
|
|
RendererObserver& nullObserver() {
|
|
static RendererObserver observer;
|
|
return observer;
|
|
}
|
|
|
|
const char* sourceTypeText(style::SourceType type) noexcept {
|
|
switch (type) {
|
|
case style::SourceType::Vector: return "vector";
|
|
case style::SourceType::Raster: return "raster";
|
|
case style::SourceType::RasterDEM: return "raster-dem";
|
|
case style::SourceType::GeoJSON: return "geojson";
|
|
case style::SourceType::Video: return "video";
|
|
case style::SourceType::Annotations: return "annotations";
|
|
case style::SourceType::Image: return "image";
|
|
case style::SourceType::CustomVector: return "customvector";
|
|
case style::SourceType::CustomMVTVector: return "unknown";
|
|
case style::SourceType::RasterArray: return "raster-array";
|
|
}
|
|
return "unknown";
|
|
}
|
|
|
|
const char* tileOperationText(TileOperation op) noexcept {
|
|
switch (op) {
|
|
case TileOperation::RequestedFromCache: return "RequestedFromCache";
|
|
case TileOperation::RequestedFromNetwork: return "RequestedFromNetwork";
|
|
case TileOperation::LoadFromNetwork: return "LoadFromNetwork";
|
|
case TileOperation::LoadFromCache: return "LoadFromCache";
|
|
case TileOperation::StartParse: return "StartParse";
|
|
case TileOperation::EndParse: return "EndParse";
|
|
case TileOperation::Error: return "Error";
|
|
case TileOperation::Cancelled: return "Cancelled";
|
|
case TileOperation::NullOp: return "NullOp";
|
|
}
|
|
return "Unknown";
|
|
}
|
|
|
|
const char* readinessEventType(RetainedUnderlayReason reason) noexcept {
|
|
switch (reason) {
|
|
case RetainedUnderlayReason::SourceMutation: return "SourceMutation";
|
|
case RetainedUnderlayReason::TileMutation: return "TileMutation";
|
|
case RetainedUnderlayReason::ImageMutation: return "ImageMutation";
|
|
case RetainedUnderlayReason::GlyphMutation: return "GlyphMutation";
|
|
case RetainedUnderlayReason::StyleMutation: return "StyleMutation";
|
|
case RetainedUnderlayReason::LayerMutation:
|
|
case RetainedUnderlayReason::LayerGroupMutation: return "LayerMutation";
|
|
case RetainedUnderlayReason::ContextReset: return "ContextReset";
|
|
default: return "OtherMutation";
|
|
}
|
|
}
|
|
|
|
class RenderTreeImpl final : public RenderTree {
|
|
public:
|
|
RenderTreeImpl(std::unique_ptr<RenderTreeParameters> parameters_,
|
|
std::set<LayerRenderItem> layerRenderItems_,
|
|
std::vector<std::unique_ptr<RenderItem>> sourceRenderItems_,
|
|
LineAtlas& lineAtlas_,
|
|
PatternAtlas& patternAtlas_,
|
|
RenderLayerReferences layersNeedPlacement_,
|
|
Immutable<Placement> placement_,
|
|
bool updateSymbolOpacities_,
|
|
double startTime_)
|
|
: RenderTree(std::move(parameters_), startTime_),
|
|
layerRenderItems(std::move(layerRenderItems_)),
|
|
sourceRenderItems(std::move(sourceRenderItems_)),
|
|
lineAtlas(lineAtlas_),
|
|
patternAtlas(patternAtlas_),
|
|
layersNeedPlacement(std::move(layersNeedPlacement_)),
|
|
placement(std::move(placement_)),
|
|
updateSymbolOpacities(updateSymbolOpacities_) {}
|
|
|
|
void prepare() override {
|
|
MLN_TRACE_FUNC();
|
|
|
|
for (auto it = layersNeedPlacement.rbegin(); it != layersNeedPlacement.rend(); ++it) {
|
|
placement->updateLayerBuckets(*it, parameters->transformParams.state, updateSymbolOpacities);
|
|
}
|
|
}
|
|
|
|
const std::set<LayerRenderItem>& getLayerRenderItemMap() const noexcept override { return layerRenderItems; }
|
|
RenderItems getLayerRenderItems() const override { return {layerRenderItems.begin(), layerRenderItems.end()}; }
|
|
RenderItems getSourceRenderItems() const override {
|
|
RenderItems result;
|
|
result.reserve(sourceRenderItems.size());
|
|
for (const auto& item : sourceRenderItems) result.emplace_back(*item);
|
|
return result;
|
|
}
|
|
LineAtlas& getLineAtlas() const override { return lineAtlas; }
|
|
PatternAtlas& getPatternAtlas() const override { return patternAtlas; }
|
|
|
|
std::set<LayerRenderItem> layerRenderItems;
|
|
std::vector<std::unique_ptr<RenderItem>> sourceRenderItems;
|
|
std::reference_wrapper<LineAtlas> lineAtlas;
|
|
std::reference_wrapper<PatternAtlas> patternAtlas;
|
|
RenderLayerReferences layersNeedPlacement;
|
|
Immutable<Placement> placement;
|
|
bool updateSymbolOpacities;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
RenderOrchestrator::RenderOrchestrator(bool backgroundLayerAsColor_,
|
|
TaggedScheduler& threadPool_,
|
|
const std::optional<std::string>& localFontFamily_)
|
|
: observer(&nullObserver()),
|
|
glyphManager(std::make_unique<GlyphManager>(std::make_unique<LocalGlyphRasterizer>(localFontFamily_))),
|
|
imageManager(ImageManager::create()),
|
|
lineAtlas(std::make_unique<LineAtlas>()),
|
|
patternAtlas(std::make_unique<PatternAtlas>()),
|
|
imageImpls(makeMutable<std::vector<Immutable<style::Image::Impl>>>()),
|
|
sourceImpls(makeMutable<std::vector<Immutable<style::Source::Impl>>>()),
|
|
layerImpls(makeMutable<std::vector<Immutable<style::Layer::Impl>>>()),
|
|
renderLight(makeMutable<Light::Impl>()),
|
|
backgroundLayerAsColor(backgroundLayerAsColor_),
|
|
threadPool(threadPool_) {
|
|
glyphManager->setObserver(this);
|
|
imageManager->setObserver(this);
|
|
}
|
|
|
|
RenderOrchestrator::~RenderOrchestrator() {
|
|
MLN_TRACE_FUNC();
|
|
|
|
if (contextLost) {
|
|
// Signal all RenderLayers that the context was lost
|
|
// before cleaning up. At the moment, only CustomLayer is
|
|
// interested whether rendering context is lost. However, it would be
|
|
// beneficial for dynamically loaded or other custom built-in plugins.
|
|
for (const auto& entry : renderLayers) {
|
|
RenderLayer& layer = *entry.second;
|
|
layer.markContextDestroyed();
|
|
}
|
|
}
|
|
|
|
// Finish work that can still reach the source observers before detaching them.
|
|
threadPool.waitForEmpty();
|
|
|
|
for (auto& entry : renderSources) {
|
|
entry.second->setObserver(nullptr);
|
|
}
|
|
glyphManager->setObserver(nullptr);
|
|
imageManager->setObserver(nullptr);
|
|
|
|
// Tile destructors can synchronously report Cancelled and enqueue further work.
|
|
// Destroy sources while the readiness state and its mutex are still alive.
|
|
renderSources.clear();
|
|
|
|
// Drain work submitted by source/tile destruction before the scheduler and
|
|
// the remaining orchestrator state are torn down.
|
|
threadPool.waitForEmpty();
|
|
}
|
|
|
|
void RenderOrchestrator::recordReadinessMutation(const char* eventType,
|
|
RetainedUnderlayReason reason,
|
|
const std::optional<std::string>& sourceId,
|
|
const std::string& tileAction) noexcept {
|
|
try {
|
|
std::lock_guard<std::mutex> lock(readinessDiagnosticsMutex);
|
|
const auto sequence = ++readinessEventSequence;
|
|
lastReadinessMutation.sequence = sequence;
|
|
lastReadinessMutation.contentRevision = retainedUnderlayContentRevision.load(std::memory_order_acquire);
|
|
lastReadinessMutation.eventType = eventType;
|
|
lastReadinessMutation.reason = retainedUnderlayReasonText(reason);
|
|
lastReadinessMutation.sourceId = sourceId;
|
|
lastReadinessMutation.affectedSegmentIndices.clear();
|
|
lastReadinessMutation.dependencyScope = sourceId ? "unresolved-until-snapshot" : "shared/global/unknown";
|
|
|
|
if (sourceId) {
|
|
auto& state = sourceReadinessEvents[*sourceId];
|
|
state.lastMutationReason = retainedUnderlayReasonText(reason);
|
|
if (reason == RetainedUnderlayReason::SourceMutation) state.lastSourceMutationSequence = sequence;
|
|
if (reason == RetainedUnderlayReason::TileMutation) state.lastTileChangedSequence = sequence;
|
|
if (!tileAction.empty()) {
|
|
state.lastTileActionSequence = sequence;
|
|
state.lastTileAction = tileAction;
|
|
}
|
|
}
|
|
} catch (...) {
|
|
// Diagnostic recording is best-effort and must not escape into renderer mutation paths.
|
|
}
|
|
}
|
|
|
|
void RenderOrchestrator::advanceRetainedSourceRevision(RetainedUnderlayReason reason,
|
|
const std::string& sourceId) noexcept {
|
|
if (sourceId.empty()) {
|
|
std::lock_guard<std::mutex> lock(readinessDiagnosticsMutex);
|
|
retainedSegmentRevisions.advanceGlobal();
|
|
return;
|
|
}
|
|
try {
|
|
std::lock_guard<std::mutex> lock(readinessDiagnosticsMutex);
|
|
retainedSegmentRevisions.advanceSource(sourceId);
|
|
} catch (...) {
|
|
// Correctness state must fail closed if a source revision cannot be recorded.
|
|
std::lock_guard<std::mutex> lock(readinessDiagnosticsMutex);
|
|
retainedSegmentRevisions.advanceGlobal();
|
|
}
|
|
recordReadinessMutation(readinessEventType(reason), reason, sourceId);
|
|
}
|
|
|
|
void RenderOrchestrator::advanceRetainedUnderlayContentRevision(
|
|
RetainedUnderlayReason reason,
|
|
const std::optional<std::string>& sourceId) noexcept {
|
|
retainedUnderlayContentReason.store(reason, std::memory_order_release);
|
|
retainedUnderlayContentRevision.fetch_add(1, std::memory_order_acq_rel);
|
|
if (sourceId && (reason == RetainedUnderlayReason::SourceMutation ||
|
|
reason == RetainedUnderlayReason::TileMutation)) {
|
|
advanceRetainedSourceRevision(reason, *sourceId);
|
|
return;
|
|
}
|
|
{
|
|
std::lock_guard<std::mutex> lock(readinessDiagnosticsMutex);
|
|
retainedSegmentRevisions.advanceGlobal();
|
|
}
|
|
recordReadinessMutation(readinessEventType(reason), reason, sourceId);
|
|
}
|
|
|
|
RendererReadinessDiagnostics RenderOrchestrator::buildReadinessDiagnostics(bool styleLoaded,
|
|
bool spriteLoaded) const {
|
|
RendererReadinessDiagnostics result;
|
|
result.styleLoaded = styleLoaded;
|
|
result.imageManagerLoaded = imageManager->isLoaded();
|
|
result.spriteLoaded = spriteLoaded;
|
|
result.contentRevision = retainedUnderlayContentRevision.load(std::memory_order_acquire);
|
|
|
|
std::unordered_map<std::string, SourceReadinessEventState> sourceEvents;
|
|
{
|
|
std::lock_guard<std::mutex> lock(readinessDiagnosticsMutex);
|
|
// Snapshot-only: do not prune or otherwise mutate diagnostic history from a query path.
|
|
// Entries for sources no longer present are naturally ignored below because we enumerate
|
|
// the current renderSources set and only look up matching ids.
|
|
sourceEvents = sourceReadinessEvents;
|
|
result.lastMutation = lastReadinessMutation;
|
|
}
|
|
|
|
result.renderOrchestratorLoaded = result.imageManagerLoaded;
|
|
result.sources.reserve(renderSources.size());
|
|
for (const auto& [sourceId, renderSource] : renderSources) {
|
|
const auto stats = renderSource->getReadinessDiagnostics();
|
|
RenderSourceReadinessDiagnostics source;
|
|
source.sourceId = sourceId;
|
|
source.sourceType = sourceTypeText(renderSource->baseImpl->type);
|
|
source.enabled = renderSource->isEnabled();
|
|
source.isLoaded = renderSource->isLoaded();
|
|
source.tileBacked = stats.tileBacked;
|
|
source.tileCount = stats.tileCount;
|
|
source.incompleteTileCount = stats.incompleteTileCount;
|
|
source.loadedTileCount = stats.loadedTileCount;
|
|
source.hasFadingTiles = stats.hasFadingTiles;
|
|
result.hasFadingTiles = result.hasFadingTiles || stats.hasFadingTiles;
|
|
result.renderOrchestratorLoaded = result.renderOrchestratorLoaded && source.isLoaded;
|
|
if (const auto it = sourceEvents.find(sourceId); it != sourceEvents.end()) {
|
|
source.lastSourceMutationSequence = it->second.lastSourceMutationSequence;
|
|
source.lastTileChangedSequence = it->second.lastTileChangedSequence;
|
|
source.lastTileActionSequence = it->second.lastTileActionSequence;
|
|
source.lastTileAction = it->second.lastTileAction;
|
|
source.lastMutationReason = it->second.lastMutationReason;
|
|
}
|
|
result.sources.push_back(std::move(source));
|
|
}
|
|
std::ranges::sort(result.sources, {}, &RenderSourceReadinessDiagnostics::sourceId);
|
|
return result;
|
|
}
|
|
|
|
void RenderOrchestrator::markContextLost() {
|
|
contextLost = true;
|
|
advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::ContextReset);
|
|
}
|
|
|
|
void RenderOrchestrator::setObserver(RendererObserver* observer_) {
|
|
observer = observer_ ? observer_ : &nullObserver();
|
|
}
|
|
|
|
std::unique_ptr<RenderTree> RenderOrchestrator::createRenderTree(
|
|
const std::shared_ptr<UpdateParameters>& updateParameters, gfx::DynamicTextureAtlasPtr dynamicTextureAtlas) {
|
|
MLN_TRACE_FUNC();
|
|
|
|
const auto startTime = util::MonotonicTimer::now().count();
|
|
|
|
const auto fontFacesIdentity = static_cast<const void*>(updateParameters->fontFaces.get());
|
|
if (retainedInputStateInitialized &&
|
|
(retainedGlyphURL != updateParameters->glyphURL ||
|
|
retainedFontFacesIdentity != fontFacesIdentity)) {
|
|
advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::GlyphMutation);
|
|
}
|
|
if (retainedInputStateInitialized && retainedSpriteLoaded != updateParameters->spriteLoaded) {
|
|
advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::ImageMutation);
|
|
}
|
|
retainedInputStateInitialized = true;
|
|
retainedGlyphURL = updateParameters->glyphURL;
|
|
retainedFontFacesIdentity = fontFacesIdentity;
|
|
retainedSpriteLoaded = updateParameters->spriteLoaded;
|
|
|
|
const bool isMapModeContinuous = updateParameters->mode == MapMode::Continuous;
|
|
if (!isMapModeContinuous) {
|
|
// Reset zoom history state.
|
|
zoomHistory.first = true;
|
|
}
|
|
|
|
if (LayerManager::annotationsEnabled) {
|
|
if (auto guard = updateParameters->annotationManager.lock(); updateParameters->annotationManager) {
|
|
updateParameters->annotationManager->updateData();
|
|
}
|
|
}
|
|
|
|
const bool zoomChanged = zoomHistory.update(static_cast<float>(updateParameters->transformState.getZoom()),
|
|
updateParameters->timePoint);
|
|
|
|
const TransitionOptions transitionOptions = isMapModeContinuous ? updateParameters->transitionOptions
|
|
: TransitionOptions();
|
|
|
|
const TransitionParameters transitionParameters{.now = updateParameters->timePoint,
|
|
.transition = transitionOptions};
|
|
const auto transitionDuration = transitionOptions.duration.value_or(
|
|
isMapModeContinuous ? util::DEFAULT_TRANSITION_DURATION : Duration::zero());
|
|
|
|
PropertyEvaluationParameters evaluationParameters{zoomHistory, updateParameters->timePoint, transitionDuration};
|
|
evaluationParameters.zoomChanged = zoomChanged;
|
|
|
|
TileParameters tileParameters{.pixelRatio = updateParameters->pixelRatio,
|
|
.debugOptions = updateParameters->debugOptions,
|
|
.transformState = updateParameters->transformState,
|
|
.fileSource = updateParameters->fileSource,
|
|
.mode = updateParameters->mode,
|
|
.annotationManager = updateParameters->annotationManager,
|
|
.imageManager = imageManager,
|
|
.glyphManager = glyphManager,
|
|
.prefetchZoomDelta = updateParameters->prefetchZoomDelta,
|
|
.threadPool = threadPool,
|
|
.tileLodMinRadius = updateParameters->tileLodMinRadius,
|
|
.tileLodScale = updateParameters->tileLodScale,
|
|
.tileLodPitchThreshold = updateParameters->tileLodPitchThreshold,
|
|
.tileLodZoomShift = updateParameters->tileLodZoomShift,
|
|
.tileLodMode = updateParameters->tileLodMode,
|
|
.dynamicTextureAtlas = dynamicTextureAtlas};
|
|
|
|
glyphManager->setURL(updateParameters->glyphURL);
|
|
glyphManager->setFontFaces(updateParameters->fontFaces);
|
|
|
|
// Update light.
|
|
const bool lightChanged = renderLight.impl != updateParameters->light;
|
|
|
|
if (lightChanged) {
|
|
advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::StyleMutation);
|
|
renderLight.impl = updateParameters->light;
|
|
renderLight.transition(transitionParameters);
|
|
}
|
|
|
|
if (lightChanged || zoomChanged || renderLight.hasTransition()) {
|
|
renderLight.evaluate(evaluationParameters);
|
|
}
|
|
|
|
const ImageDifference imageDiff = diffImages(imageImpls, updateParameters->images);
|
|
if (!imageDiff.added.empty() || !imageDiff.removed.empty() || !imageDiff.changed.empty()) {
|
|
advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::ImageMutation);
|
|
}
|
|
imageImpls = updateParameters->images;
|
|
|
|
// Only trigger tile reparse for changed images. Changed images only need a
|
|
// relayout when they have a different size.
|
|
bool hasImageDiff = !imageDiff.removed.empty();
|
|
|
|
// Remove removed images from sprite atlas.
|
|
for (const auto& entry : imageDiff.removed) {
|
|
imageManager->removeImage(entry.first);
|
|
patternAtlas->removePattern(entry.first);
|
|
}
|
|
|
|
// Add added images to sprite atlas.
|
|
for (const auto& entry : imageDiff.added) {
|
|
imageManager->addImage(entry.second);
|
|
}
|
|
|
|
// Update changed images.
|
|
for (const auto& entry : imageDiff.changed) {
|
|
if (imageManager->updateImage(entry.second.after)) {
|
|
patternAtlas->removePattern(entry.first);
|
|
hasImageDiff = true;
|
|
}
|
|
}
|
|
|
|
imageManager->notifyIfMissingImageAdded();
|
|
imageManager->setLoaded(updateParameters->spriteLoaded);
|
|
|
|
const LayerDifference layerDiff = diffLayers(layerImpls, updateParameters->layers);
|
|
// Layer mutations are tracked by per-segment immutable-Impl fingerprints below. Source,
|
|
// tile, image, glyph and camera changes remain deliberately global invalidations.
|
|
layerImpls = updateParameters->layers;
|
|
const bool layersAddedOrRemoved = !layerDiff.added.empty() || !layerDiff.removed.empty();
|
|
|
|
std::vector<std::unique_ptr<ChangeRequest>> changes;
|
|
|
|
// Remove render layers for removed layers.
|
|
for (const auto& entry : layerDiff.removed) {
|
|
MLN_TRACE_ZONE(remove layer);
|
|
const auto hit = renderLayers.find(entry.first);
|
|
if (hit != renderLayers.end()) {
|
|
hit->second->layerRemoved(changes);
|
|
renderLayers.erase(hit);
|
|
}
|
|
}
|
|
|
|
// Create render layers for newly added layers.
|
|
for (const auto& entry : layerDiff.added) {
|
|
MLN_TRACE_ZONE(add layer);
|
|
auto renderLayer = LayerManager::get()->createRenderLayer(entry.second);
|
|
renderLayer->transition(transitionParameters);
|
|
renderLayers.emplace(entry.first, std::move(renderLayer));
|
|
}
|
|
|
|
// Update render layers for changed layers.
|
|
for (const auto& entry : layerDiff.changed) {
|
|
MLN_TRACE_ZONE(change layer);
|
|
if (const auto& renderLayer = renderLayers.at(entry.first)) {
|
|
const auto& newLayer = entry.second.after;
|
|
|
|
renderLayer->layerChanged(transitionParameters, newLayer, changes);
|
|
renderLayer->transition(transitionParameters, newLayer);
|
|
}
|
|
}
|
|
|
|
if (layersAddedOrRemoved) {
|
|
orderedLayers.clear();
|
|
orderedLayers.reserve(layerImpls->size());
|
|
[[maybe_unused]] int32_t layerIndex = 0;
|
|
for (const auto& layerImpl : *layerImpls) {
|
|
RenderLayer* layer = renderLayers.at(layerImpl->id).get();
|
|
assert(layer);
|
|
orderedLayers.emplace_back(*layer);
|
|
|
|
// We're mutating the list of ordered layers and must notify them of their new assigned indices
|
|
layer->layerIndexChanged(layerIndex++, changes);
|
|
}
|
|
}
|
|
assert(orderedLayers.size() == renderLayers.size());
|
|
|
|
if (layersAddedOrRemoved || !layerDiff.changed.empty()) {
|
|
glyphManager->evict(fontStacks(*layerImpls));
|
|
}
|
|
|
|
// Update layers for class and zoom changes.
|
|
std::unordered_set<std::string> constantsMaskChanged;
|
|
for (RenderLayer& layer : orderedLayers) {
|
|
MLN_TRACE_ZONE(update layer);
|
|
const std::string& id = layer.getID();
|
|
const bool layerAddedOrChanged = layerDiff.added.contains(id) || layerDiff.changed.contains(id);
|
|
evaluationParameters.layerChanged = layerAddedOrChanged;
|
|
evaluationParameters.hasCrossfade = layer.hasCrossfade();
|
|
|
|
// Only re-evaluate on change of zoom if the style has some reference to it
|
|
using Dependency = expression::Dependency;
|
|
const bool zoomChangedAndMatters = zoomChanged && !layerAddedOrChanged &&
|
|
(layer.getStyleDependencies() & Dependency::Zoom);
|
|
|
|
if (layerAddedOrChanged || zoomChangedAndMatters || evaluationParameters.hasCrossfade ||
|
|
layer.hasTransition()) {
|
|
const auto previousMask = layer.evaluatedProperties->constantsMask();
|
|
layer.evaluate(evaluationParameters);
|
|
if (previousMask != layer.evaluatedProperties->constantsMask()) {
|
|
constantsMaskChanged.insert(id);
|
|
}
|
|
}
|
|
}
|
|
|
|
const SourceDifference sourceDiff = diffSources(sourceImpls, updateParameters->sources);
|
|
if (!sourceDiff.added.empty() || !sourceDiff.removed.empty() || !sourceDiff.changed.empty()) {
|
|
retainedUnderlayContentReason.store(RetainedUnderlayReason::SourceMutation, std::memory_order_release);
|
|
retainedUnderlayContentRevision.fetch_add(1, std::memory_order_acq_rel);
|
|
for (const auto& entry : sourceDiff.added) {
|
|
advanceRetainedSourceRevision(RetainedUnderlayReason::SourceMutation, entry.first);
|
|
}
|
|
for (const auto& entry : sourceDiff.removed) {
|
|
advanceRetainedSourceRevision(RetainedUnderlayReason::SourceMutation, entry.first);
|
|
}
|
|
for (const auto& entry : sourceDiff.changed) {
|
|
advanceRetainedSourceRevision(RetainedUnderlayReason::SourceMutation, entry.first);
|
|
}
|
|
}
|
|
sourceImpls = updateParameters->sources;
|
|
|
|
// Remove render layers for removed sources.
|
|
for (const auto& entry : sourceDiff.removed) {
|
|
renderSources.erase(entry.first);
|
|
}
|
|
|
|
// Create render sources for newly added sources.
|
|
for (const auto& entry : sourceDiff.added) {
|
|
MLN_TRACE_ZONE(create source);
|
|
std::unique_ptr<RenderSource> renderSource = RenderSource::create(entry.second, threadPool);
|
|
renderSource->setObserver(this);
|
|
renderSource->setCacheEnabled(tileCacheEnabled);
|
|
renderSource->setFastPFOREnabled(updateParameters->fastPFOREnabled);
|
|
renderSources.emplace(entry.first, std::move(renderSource));
|
|
}
|
|
transformState = updateParameters->transformState;
|
|
const bool tiltedView = transformState.getPitch() != 0.0f;
|
|
|
|
// Create parameters for the render tree.
|
|
auto renderTreeParameters = std::make_unique<RenderTreeParameters>(updateParameters->transformState,
|
|
updateParameters->mode,
|
|
updateParameters->debugOptions,
|
|
updateParameters->timePoint,
|
|
renderLight.getEvaluated());
|
|
|
|
std::set<LayerRenderItem> layerRenderItems;
|
|
layersNeedPlacement.clear();
|
|
auto renderItemsEmplaceHint = layerRenderItems.begin();
|
|
|
|
// Reserve size for filteredLayersForSource if there are sources.
|
|
if (!sourceImpls->empty()) {
|
|
filteredLayersForSource.reserve(layerImpls->size());
|
|
}
|
|
|
|
// Track which layers are flagged for rendering
|
|
std::vector<bool> updateList(orderedLayers.size());
|
|
|
|
RetainedDynamicBoundaryTracker boundaryTracker;
|
|
for (std::size_t index = 0; index < orderedLayers.size(); ++index) {
|
|
boundaryTracker.consider(static_cast<int32_t>(index),
|
|
orderedLayers[index].get().startsRetainedDynamicRange());
|
|
}
|
|
retainedDynamicBoundary = boundaryTracker.value();
|
|
std::vector<std::string> orderedLayerIds;
|
|
orderedLayerIds.reserve(orderedLayers.size());
|
|
for (const auto& layer : orderedLayers) orderedLayerIds.push_back(layer.get().getID());
|
|
continuousLayerRange = resolveContinuousLayerRange(
|
|
updateParameters->retainedUnderlayState
|
|
? updateParameters->retainedUnderlayState->continuousLayerRangeConfig()
|
|
: ContinuousLayerRangeConfig{},
|
|
orderedLayerIds);
|
|
if (continuousLayerRange.configured && !updateParameters->styleLoaded) {
|
|
continuousLayerRange.valid = false;
|
|
continuousLayerRange.startIndex = -1;
|
|
continuousLayerRange.endIndex = -1;
|
|
continuousLayerRange.reason = "style-not-ready";
|
|
}
|
|
if (updateParameters->retainedUnderlayState) {
|
|
updateParameters->retainedUnderlayState->recordRangeResolution(
|
|
continuousLayerRange,
|
|
continuousLayerRange.bottomPresent(),
|
|
continuousLayerRange.topPresent(static_cast<int32_t>(orderedLayers.size())));
|
|
|
|
const auto segmentConfig = updateParameters->retainedUnderlayState->renderSegmentPlanConfig();
|
|
const bool segmentMetadataChanged = renderSegmentPlan.configRevision != segmentConfig.revision ||
|
|
renderSegmentPlan.layerCount != static_cast<int32_t>(orderedLayers.size()) ||
|
|
renderSegmentStyleReady != updateParameters->styleLoaded || layersAddedOrRemoved ||
|
|
!layerDiff.changed.empty();
|
|
if (segmentMetadataChanged) {
|
|
renderSegmentPlan = resolveRenderSegmentPlan(
|
|
segmentConfig, orderedLayerIds, updateParameters->styleLoaded, retainedDynamicBoundary);
|
|
renderSegmentStyleReady = updateParameters->styleLoaded;
|
|
std::vector<StyleLayerDiagnostics> styleLayerDiagnostics;
|
|
styleLayerDiagnostics.reserve(orderedLayers.size());
|
|
for (std::size_t index = 0; index < orderedLayers.size(); ++index) {
|
|
const auto& layer = orderedLayers[index].get();
|
|
const auto& impl = *layer.baseImpl;
|
|
StyleLayerDiagnostics value;
|
|
value.layerIndex = static_cast<int32_t>(index);
|
|
value.layerId = layer.getID();
|
|
value.layerType = impl.getTypeInfo()->type;
|
|
value.visible = impl.visibility != style::VisibilityType::None;
|
|
if (!impl.source.empty()) value.sourceId = impl.source;
|
|
if (!impl.sourceLayer.empty()) value.sourceLayer = impl.sourceLayer;
|
|
styleLayerDiagnostics.push_back(std::move(value));
|
|
}
|
|
retainedStyleLayers = styleLayerDiagnostics;
|
|
updateParameters->retainedUnderlayState->recordRenderSegmentResolution(
|
|
renderSegmentPlan, std::move(styleLayerDiagnostics));
|
|
}
|
|
} else {
|
|
renderSegmentPlan = {};
|
|
retainedStyleLayers.clear();
|
|
}
|
|
|
|
// Update all sources and initialize renderItems.
|
|
for (const auto& sourceImpl : *sourceImpls) {
|
|
MLN_TRACE_ZONE(update source);
|
|
MLN_ZONE_STR(sourceImpl->id);
|
|
|
|
RenderSource* source = renderSources.at(sourceImpl->id).get();
|
|
bool sourceNeedsRendering = false;
|
|
bool sourceNeedsRelayout = false;
|
|
|
|
for (std::size_t index = 0; index < orderedLayers.size(); ++index) {
|
|
RenderLayer& layer = orderedLayers[index];
|
|
const auto* layerInfo = layer.baseImpl->getTypeInfo();
|
|
const bool layerIsVisible = layer.baseImpl->visibility != style::VisibilityType::None;
|
|
const bool zoomFitsLayer = layer.supportsZoom(zoomHistory.lastZoom);
|
|
renderTreeParameters->has3D |= (layerInfo->pass3d == LayerTypeInfo::Pass3D::Required);
|
|
|
|
if (layerInfo->source != LayerTypeInfo::Source::NotRequired) {
|
|
if (layer.baseImpl->source == sourceImpl->id) {
|
|
const std::string& layerId = layer.getID();
|
|
sourceNeedsRelayout = (sourceNeedsRelayout || hasImageDiff ||
|
|
constantsMaskChanged.contains(layerId) ||
|
|
hasLayoutDifference(layerDiff, layerId));
|
|
if (layerIsVisible) {
|
|
filteredLayersForSource.push_back(layer.evaluatedProperties);
|
|
if (zoomFitsLayer) {
|
|
sourceNeedsRendering = true;
|
|
renderItemsEmplaceHint = layerRenderItems.emplace_hint(
|
|
renderItemsEmplaceHint, layer, source, static_cast<uint32_t>(index));
|
|
updateList[index] = true;
|
|
}
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Handle layers without source.
|
|
if (layerIsVisible && zoomFitsLayer && sourceImpl.get() == sourceImpls->at(0).get()) {
|
|
if (backgroundLayerAsColor && layer.baseImpl == layerImpls->front()) {
|
|
const auto& solidBackground = layer.getSolidBackground();
|
|
if (solidBackground) {
|
|
renderTreeParameters->backgroundColor = *solidBackground;
|
|
continue; // This layer is shown with background color,
|
|
// and it shall not be added to render items.
|
|
}
|
|
}
|
|
renderItemsEmplaceHint = layerRenderItems.emplace_hint(
|
|
renderItemsEmplaceHint, layer, nullptr, static_cast<uint32_t>(index));
|
|
updateList[index] = true;
|
|
}
|
|
}
|
|
tileParameters.isUpdateSynchronous = sourceImpl->isUpdateSynchronous();
|
|
source->update(sourceImpl, filteredLayersForSource, sourceNeedsRendering, sourceNeedsRelayout, tileParameters);
|
|
filteredLayersForSource.clear();
|
|
|
|
// Update all layers with their new renderability status, if it changed.
|
|
for (size_t i = 0; i < updateList.size(); i++) {
|
|
if (orderedLayers[i].get().isLayerRenderable() != updateList[i]) {
|
|
orderedLayers[i].get().markLayerRenderable(updateList[i], changes);
|
|
}
|
|
}
|
|
addChanges(changes);
|
|
}
|
|
|
|
// Readiness is sampled for diagnostics only. Preserve the renderer's formal load gate.
|
|
renderTreeParameters->loaded = updateParameters->styleLoaded && isLoaded();
|
|
if (updateParameters->retainedUnderlayState) {
|
|
try {
|
|
auto readinessDiagnostics =
|
|
buildReadinessDiagnostics(updateParameters->styleLoaded, updateParameters->spriteLoaded);
|
|
updateParameters->retainedUnderlayState->recordRendererReadiness(std::move(readinessDiagnostics));
|
|
} catch (...) {
|
|
// A failed diagnostic snapshot must not affect the render tree or its formal loaded gate.
|
|
}
|
|
}
|
|
if (!isMapModeContinuous && !renderTreeParameters->loaded) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Prepare. Update all matrices and generate data that we should upload to the GPU.
|
|
for (const auto& [name, renderSource] : renderSources) {
|
|
MLN_TRACE_ZONE(prepare source);
|
|
if (renderSource->isEnabled()) {
|
|
renderSource->prepare({.transform = renderTreeParameters->transformParams,
|
|
.debugOptions = updateParameters->debugOptions,
|
|
.imageManager = *imageManager,
|
|
.sourceName = name});
|
|
}
|
|
}
|
|
|
|
// Only weather layers that explicitly opt in may clean up after Host clear.
|
|
// Ordinary hidden layers retain MapLibre's existing lifecycle.
|
|
for (auto& entry : orderedLayers) {
|
|
auto& layer = entry.get();
|
|
const auto source = renderSources.find(layer.baseImpl->source);
|
|
if (!layer.isLayerRenderable() && source != renderSources.end() &&
|
|
layer.needsHiddenLayerSourceCleanup(*source->second)) {
|
|
layer.prepare({.source = source->second.get(), .imageManager = *imageManager,
|
|
.patternAtlas = *patternAtlas, .lineAtlas = *lineAtlas,
|
|
.state = updateParameters->transformState});
|
|
}
|
|
}
|
|
auto opaquePassCutOffEstimation = layerRenderItems.size();
|
|
for (auto& renderItem : layerRenderItems) {
|
|
RenderLayer& renderLayer = renderItem.layer;
|
|
MLN_TRACE_ZONE(prepare layer);
|
|
MLN_ZONE_STR(renderLayer.getID());
|
|
|
|
renderLayer.prepare({.source = renderItem.source,
|
|
.imageManager = *imageManager,
|
|
.patternAtlas = *patternAtlas,
|
|
.lineAtlas = *lineAtlas,
|
|
.state = updateParameters->transformState});
|
|
if (renderLayer.needsPlacement()) {
|
|
layersNeedPlacement.emplace_back(renderLayer);
|
|
}
|
|
if (renderTreeParameters->opaquePassCutOff == 0) {
|
|
--opaquePassCutOffEstimation;
|
|
if (renderLayer.is3D()) {
|
|
renderTreeParameters->opaquePassCutOff = uint32_t(opaquePassCutOffEstimation);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Symbol placement.
|
|
assert((updateParameters->mode == MapMode::Tile) || !placedSymbolDataCollected);
|
|
bool symbolBucketsChanged = false;
|
|
bool symbolBucketsAdded = false;
|
|
std::set<std::string> usedSymbolLayers;
|
|
const auto longitude = static_cast<float>(updateParameters->transformState.getLatLng().longitude());
|
|
for (auto it = layersNeedPlacement.crbegin(); it != layersNeedPlacement.crend(); ++it) {
|
|
MLN_TRACE_ZONE(placement layer);
|
|
RenderLayer& layer = *it;
|
|
auto result = crossTileSymbolIndex.addLayer(layer, longitude);
|
|
if (isMapModeContinuous) {
|
|
usedSymbolLayers.insert(layer.getID());
|
|
symbolBucketsAdded = symbolBucketsAdded || (result & CrossTileSymbolIndex::AddLayerResult::BucketsAdded);
|
|
symbolBucketsChanged = symbolBucketsChanged || (result != CrossTileSymbolIndex::AddLayerResult::NoChanges);
|
|
}
|
|
}
|
|
|
|
if (isMapModeContinuous) {
|
|
MLN_TRACE_ZONE(placement);
|
|
|
|
std::optional<Duration> placementUpdatePeriodOverride;
|
|
if (symbolBucketsAdded && !tiltedView) {
|
|
// If the view is not tilted, we want *the new* symbols to show up
|
|
// faster, however simple setting `placementChanged` to `true` would
|
|
// initiate placement too often as new buckets usually come from
|
|
// several rendered tiles in a row within a short period of time.
|
|
// Instead, we squeeze placement update period to coalesce buckets
|
|
// updates from several tiles. On contrary, with the tilted view
|
|
// it's more important to make placement rarely for performance
|
|
// reasons and as the new symbols are normally "far away" and the
|
|
// user is not that interested to see them ASAP.
|
|
placementUpdatePeriodOverride = std::optional<Duration>(Milliseconds(30));
|
|
}
|
|
|
|
renderTreeParameters->placementChanged = !placementController.placementIsRecent(
|
|
updateParameters->timePoint,
|
|
static_cast<float>(updateParameters->transformState.getZoom()),
|
|
placementUpdatePeriodOverride);
|
|
symbolBucketsChanged |= renderTreeParameters->placementChanged;
|
|
if (renderTreeParameters->placementChanged) {
|
|
Mutable<Placement> placement = Placement::create(updateParameters, placementController.getPlacement());
|
|
placement->placeLayers(layersNeedPlacement);
|
|
placementController.setPlacement(std::move(placement));
|
|
crossTileSymbolIndex.pruneUnusedLayers(usedSymbolLayers);
|
|
for (const auto& entry : renderSources) {
|
|
entry.second->updateFadingTiles();
|
|
}
|
|
} else {
|
|
placementController.setPlacementStale();
|
|
}
|
|
renderTreeParameters->symbolFadeChange = placementController.getPlacement()->symbolFadeChange(
|
|
updateParameters->timePoint);
|
|
renderTreeParameters->needsRepaint = hasTransitions(updateParameters->timePoint);
|
|
} else {
|
|
MLN_TRACE_ZONE(placement);
|
|
|
|
renderTreeParameters->placementChanged = symbolBucketsChanged = !layersNeedPlacement.empty();
|
|
if (renderTreeParameters->placementChanged) {
|
|
Mutable<Placement> placement = Placement::create(updateParameters);
|
|
placement->collectPlacedSymbolData(placedSymbolDataCollected);
|
|
placement->placeLayers(layersNeedPlacement);
|
|
placementController.setPlacement(std::move(placement));
|
|
}
|
|
crossTileSymbolIndex.reset();
|
|
renderTreeParameters->symbolFadeChange = 1.0f;
|
|
renderTreeParameters->needsRepaint = false;
|
|
}
|
|
|
|
if (!renderTreeParameters->needsRepaint && renderTreeParameters->loaded) {
|
|
MLN_TRACE_ZONE(reduce);
|
|
// Notify observer about unused images when map is fully loaded
|
|
// and there are no ongoing transitions.
|
|
imageManager->reduceMemoryUseIfCacheSizeExceedsLimit();
|
|
}
|
|
|
|
std::vector<std::unique_ptr<RenderItem>> sourceRenderItems;
|
|
for (const auto& entry : renderSources) {
|
|
if (entry.second->isEnabled()) {
|
|
sourceRenderItems.emplace_back(entry.second->createRenderItem());
|
|
}
|
|
}
|
|
|
|
return std::make_unique<RenderTreeImpl>(std::move(renderTreeParameters),
|
|
std::move(layerRenderItems),
|
|
std::move(sourceRenderItems),
|
|
*lineAtlas,
|
|
*patternAtlas,
|
|
std::move(layersNeedPlacement),
|
|
placementController.getPlacement(),
|
|
symbolBucketsChanged,
|
|
startTime);
|
|
}
|
|
|
|
std::vector<Feature> RenderOrchestrator::queryRenderedFeatures(const ScreenLineString& geometry,
|
|
const RenderedQueryOptions& options) const {
|
|
MLN_TRACE_FUNC();
|
|
|
|
std::unordered_map<std::string, const RenderLayer*> layers;
|
|
if (options.layerIDs) {
|
|
for (const auto& layerID : *options.layerIDs) {
|
|
if (const RenderLayer* layer = getRenderLayer(layerID)) {
|
|
layers.emplace(layer->getID(), layer);
|
|
}
|
|
}
|
|
} else {
|
|
for (const auto& entry : renderLayers) {
|
|
layers.emplace(entry.second->getID(), entry.second.get());
|
|
}
|
|
}
|
|
|
|
return queryRenderedFeatures(geometry, options, layers);
|
|
}
|
|
|
|
void RenderOrchestrator::queryRenderedSymbols(std::unordered_map<std::string, std::vector<Feature>>& resultsByLayer,
|
|
const ScreenLineString& geometry,
|
|
const std::unordered_map<std::string, const RenderLayer*>& layers,
|
|
const RenderedQueryOptions& options) const {
|
|
MLN_TRACE_FUNC();
|
|
|
|
const auto hasCrossTileIndex = [](const auto& pair) {
|
|
return pair.second->baseImpl->getTypeInfo()->crossTileIndex == style::LayerTypeInfo::CrossTileIndex::Required;
|
|
};
|
|
|
|
std::unordered_map<std::string, const RenderLayer*> crossTileSymbolIndexLayers;
|
|
std::ranges::copy_if(
|
|
layers, std::inserter(crossTileSymbolIndexLayers, crossTileSymbolIndexLayers.begin()), hasCrossTileIndex);
|
|
|
|
if (crossTileSymbolIndexLayers.empty()) {
|
|
return;
|
|
}
|
|
const Placement& placement = *placementController.getPlacement();
|
|
auto renderedSymbols = placement.getCollisionIndex().queryRenderedSymbols(geometry);
|
|
std::vector<std::reference_wrapper<const RetainedQueryData>> bucketQueryData;
|
|
bucketQueryData.reserve(renderedSymbols.size());
|
|
for (const auto& entry : renderedSymbols) {
|
|
bucketQueryData.emplace_back(placement.getQueryData(entry.first));
|
|
}
|
|
// Although symbol query is global, symbol results are only sortable within
|
|
// a bucket For a predictable global sort renderItems, we sort the buckets
|
|
// based on their corresponding tile position
|
|
std::ranges::sort(bucketQueryData, [](const RetainedQueryData& a, const RetainedQueryData& b) {
|
|
return std::tie(a.tileID.canonical.z, a.tileID.canonical.y, a.tileID.wrap, a.tileID.canonical.x) <
|
|
std::tie(b.tileID.canonical.z, b.tileID.canonical.y, b.tileID.wrap, b.tileID.canonical.x);
|
|
});
|
|
|
|
for (auto wrappedQueryData : bucketQueryData) {
|
|
auto& queryData = wrappedQueryData.get();
|
|
auto bucketSymbols = queryData.featureIndex->lookupSymbolFeatures(renderedSymbols[queryData.bucketInstanceId],
|
|
options,
|
|
crossTileSymbolIndexLayers,
|
|
queryData.tileID,
|
|
queryData.featureSortOrder);
|
|
|
|
for (auto layer : bucketSymbols) {
|
|
auto& resultFeatures = resultsByLayer[layer.first];
|
|
std::ranges::move(layer.second, std::inserter(resultFeatures, resultFeatures.end()));
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<Feature> RenderOrchestrator::queryRenderedFeatures(
|
|
const ScreenLineString& geometry,
|
|
const RenderedQueryOptions& options,
|
|
const std::unordered_map<std::string, const RenderLayer*>& layers) const {
|
|
MLN_TRACE_FUNC();
|
|
|
|
std::unordered_set<std::string> sourceIDs;
|
|
std::unordered_map<std::string, const RenderLayer*> filteredLayers;
|
|
for (const auto& pair : layers) {
|
|
if (!pair.second->needsRendering() || !pair.second->supportsZoom(zoomHistory.lastZoom)) {
|
|
continue;
|
|
}
|
|
filteredLayers.emplace(pair);
|
|
sourceIDs.emplace(pair.second->baseImpl->source);
|
|
}
|
|
|
|
mat4 projMatrix;
|
|
transformState.getProjMatrix(projMatrix);
|
|
|
|
std::unordered_map<std::string, std::vector<Feature>> resultsByLayer;
|
|
for (const auto& sourceID : sourceIDs) {
|
|
if (RenderSource* renderSource = getRenderSource(sourceID)) {
|
|
auto sourceResults = renderSource->queryRenderedFeatures(
|
|
geometry, transformState, filteredLayers, options, projMatrix);
|
|
std::ranges::move(sourceResults, std::inserter(resultsByLayer, resultsByLayer.begin()));
|
|
}
|
|
}
|
|
|
|
queryRenderedSymbols(resultsByLayer, geometry, filteredLayers, options);
|
|
|
|
mln::DynamicFeatureIndex dynamicIndex;
|
|
for (const auto& pair : filteredLayers) {
|
|
const RenderLayer* layer = pair.second;
|
|
layer->populateDynamicRenderFeatureIndex(dynamicIndex);
|
|
}
|
|
dynamicIndex.query(resultsByLayer, geometry, transformState);
|
|
std::vector<Feature> result;
|
|
|
|
if (resultsByLayer.empty()) {
|
|
return result;
|
|
}
|
|
|
|
// Combine all results based on the style layer renderItems.
|
|
for (const auto& pair : filteredLayers) {
|
|
auto it = resultsByLayer.find(pair.second->baseImpl->id);
|
|
if (it != resultsByLayer.end()) {
|
|
std::move(it->second.begin(), it->second.end(), std::back_inserter(result));
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
std::vector<Feature> RenderOrchestrator::queryShapeAnnotations(const ScreenLineString& geometry) const {
|
|
MLN_TRACE_FUNC();
|
|
|
|
assert(LayerManager::annotationsEnabled);
|
|
std::unordered_map<std::string, const RenderLayer*> shapeAnnotationLayers;
|
|
RenderedQueryOptions options;
|
|
for (const auto& layerImpl : *layerImpls) {
|
|
if (std::mismatch(layerImpl->id.begin(),
|
|
layerImpl->id.end(),
|
|
AnnotationManager::ShapeLayerID.begin(),
|
|
AnnotationManager::ShapeLayerID.end())
|
|
.second == AnnotationManager::ShapeLayerID.end()) {
|
|
if (const RenderLayer* layer = getRenderLayer(layerImpl->id)) {
|
|
shapeAnnotationLayers.emplace(layer->getID(), layer);
|
|
}
|
|
}
|
|
}
|
|
|
|
return queryRenderedFeatures(geometry, options, shapeAnnotationLayers);
|
|
}
|
|
|
|
std::vector<Feature> RenderOrchestrator::querySourceFeatures(const std::string& sourceID,
|
|
const SourceQueryOptions& options) const {
|
|
MLN_TRACE_FUNC();
|
|
|
|
const RenderSource* source = getRenderSource(sourceID);
|
|
if (!source) return {};
|
|
|
|
return source->querySourceFeatures(options);
|
|
}
|
|
|
|
FeatureExtensionValue RenderOrchestrator::queryFeatureExtensions(
|
|
const std::string& sourceID,
|
|
const Feature& feature,
|
|
const std::string& extension,
|
|
const std::string& extensionField,
|
|
const std::optional<std::map<std::string, Value>>& args) const {
|
|
MLN_TRACE_FUNC();
|
|
|
|
if (RenderSource* renderSource = getRenderSource(sourceID)) {
|
|
return renderSource->queryFeatureExtensions(feature, extension, extensionField, args);
|
|
}
|
|
return {};
|
|
}
|
|
|
|
void RenderOrchestrator::setFeatureState(const std::string& sourceID,
|
|
const std::optional<std::string>& sourceLayerID,
|
|
const std::string& featureID,
|
|
const FeatureState& state) {
|
|
MLN_TRACE_FUNC();
|
|
|
|
if (RenderSource* renderSource = getRenderSource(sourceID)) {
|
|
renderSource->setFeatureState(sourceLayerID, featureID, state);
|
|
advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::SourceMutation, sourceID);
|
|
}
|
|
}
|
|
|
|
void RenderOrchestrator::getFeatureState(FeatureState& state,
|
|
const std::string& sourceID,
|
|
const std::optional<std::string>& sourceLayerID,
|
|
const std::string& featureID) const {
|
|
MLN_TRACE_FUNC();
|
|
|
|
if (RenderSource* renderSource = getRenderSource(sourceID)) {
|
|
renderSource->getFeatureState(state, sourceLayerID, featureID);
|
|
}
|
|
}
|
|
|
|
void RenderOrchestrator::removeFeatureState(const std::string& sourceID,
|
|
const std::optional<std::string>& sourceLayerID,
|
|
const std::optional<std::string>& featureID,
|
|
const std::optional<std::string>& stateKey) {
|
|
MLN_TRACE_FUNC();
|
|
|
|
if (RenderSource* renderSource = getRenderSource(sourceID)) {
|
|
renderSource->removeFeatureState(sourceLayerID, featureID, stateKey);
|
|
advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::SourceMutation, sourceID);
|
|
}
|
|
}
|
|
|
|
void RenderOrchestrator::setTileCacheEnabled(bool enable) {
|
|
tileCacheEnabled = enable;
|
|
|
|
for (const auto& entry : renderSources) {
|
|
entry.second->setCacheEnabled(enable);
|
|
}
|
|
}
|
|
|
|
bool RenderOrchestrator::getTileCacheEnabled() const {
|
|
return tileCacheEnabled;
|
|
}
|
|
|
|
void RenderOrchestrator::reduceMemoryUse() {
|
|
MLN_TRACE_FUNC();
|
|
|
|
filteredLayersForSource.shrink_to_fit();
|
|
for (const auto& entry : renderSources) {
|
|
entry.second->reduceMemoryUse();
|
|
}
|
|
imageManager->reduceMemoryUse();
|
|
observer->onInvalidate();
|
|
}
|
|
|
|
void RenderOrchestrator::dumpDebugLogs() {
|
|
MLN_TRACE_FUNC();
|
|
|
|
for (const auto& entry : renderSources) {
|
|
entry.second->dumpDebugLogs();
|
|
}
|
|
|
|
imageManager->dumpDebugLogs();
|
|
}
|
|
|
|
void RenderOrchestrator::collectPlacedSymbolData(bool enable) {
|
|
placedSymbolDataCollected = enable;
|
|
}
|
|
|
|
const std::vector<PlacedSymbolData>& RenderOrchestrator::getPlacedSymbolsData() const {
|
|
return placementController.getPlacement()->getPlacedSymbolsData();
|
|
}
|
|
|
|
RenderLayer* RenderOrchestrator::getRenderLayer(const std::string& id) {
|
|
auto it = renderLayers.find(id);
|
|
return it != renderLayers.end() ? it->second.get() : nullptr;
|
|
}
|
|
|
|
const RenderLayer* RenderOrchestrator::getRenderLayer(const std::string& id) const {
|
|
auto it = renderLayers.find(id);
|
|
return it != renderLayers.end() ? it->second.get() : nullptr;
|
|
}
|
|
|
|
RenderSource* RenderOrchestrator::getRenderSource(const std::string& id) const {
|
|
auto it = renderSources.find(id);
|
|
return it != renderSources.end() ? it->second.get() : nullptr;
|
|
}
|
|
|
|
bool RenderOrchestrator::hasTransitions(TimePoint timePoint) const {
|
|
if (renderLight.hasTransition()) {
|
|
return true;
|
|
}
|
|
|
|
for (const auto& entry : renderLayers) {
|
|
if (entry.second->hasTransition() || entry.second->needsRepaint()) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (placementController.hasTransitions(timePoint)) {
|
|
return true;
|
|
}
|
|
|
|
for (const auto& entry : renderSources) {
|
|
if (entry.second->hasFadingTiles()) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool RenderOrchestrator::retainedUnderlayUsesPlacement(int32_t boundary) const {
|
|
return retainedRangeUsesPlacement(0, boundary);
|
|
}
|
|
|
|
bool RenderOrchestrator::retainedRangeUsesPlacement(int32_t begin, int32_t endExclusive) const {
|
|
const auto first = std::clamp<int32_t>(begin, 0, static_cast<int32_t>(orderedLayers.size()));
|
|
const auto last = std::clamp<int32_t>(endExclusive, first, static_cast<int32_t>(orderedLayers.size()));
|
|
for (int32_t index = first; index < last; ++index) {
|
|
if (orderedLayers[index].get().needsPlacement()) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool RenderOrchestrator::retainedRangeHasPlacementCapableLayer(int32_t begin, int32_t endExclusive) const {
|
|
if (begin < 0 || endExclusive < begin || endExclusive > static_cast<int32_t>(orderedLayers.size())) return true;
|
|
if (renderSegmentHasPlacementCapableLayer(retainedStyleLayers, begin, endExclusive)) return true;
|
|
for (int32_t index = begin; index < endExclusive; ++index) {
|
|
if (orderedLayers[index].get().baseImpl->getTypeInfo()->crossTileIndex ==
|
|
style::LayerTypeInfo::CrossTileIndex::Required) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void RenderOrchestrator::recordRenderError(std::exception_ptr error) {
|
|
invalidateRetainedRenderResults();
|
|
observer->onRenderError(error);
|
|
}
|
|
|
|
void RenderOrchestrator::invalidateRetainedRenderResults() {
|
|
++renderErrorRevision;
|
|
advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::LayerGroupMutation);
|
|
}
|
|
|
|
SymbolRenderResult RenderOrchestrator::retainedSymbolRenderResult(
|
|
int32_t begin, int32_t endExclusive, float fadeChange, uint64_t frame) {
|
|
SymbolRenderResult result;
|
|
result.append(numLayerGroups()); // global depth range depends on all groups
|
|
if (begin < 0 || endExclusive > static_cast<int32_t>(orderedLayers.size()) || begin > endExclusive) {
|
|
result.complete = false;
|
|
return result;
|
|
}
|
|
// Registry membership is checked after processChanges, including old groups
|
|
// of hidden layers. Preserve global ordinals for depth and cross-segment order.
|
|
for (int32_t index = begin; index < endExclusive; ++index) {
|
|
auto& layer = orderedLayers[index].get();
|
|
const auto* info = layer.baseImpl->getTypeInfo();
|
|
const bool placementCapable = info->type == std::string_view("symbol") ||
|
|
info->crossTileIndex == style::LayerTypeInfo::CrossTileIndex::Required;
|
|
if (!placementCapable) continue;
|
|
if (info != style::SymbolLayer::Impl::staticTypeInfo()) {
|
|
result.complete = false;
|
|
continue;
|
|
}
|
|
auto& symbol = static_cast<RenderSymbolLayer&>(layer);
|
|
const auto registered = layerGroupsByLayerIndex.equal_range(index);
|
|
std::size_t groups = 0;
|
|
for (auto it = registered.first; it != registered.second; ++it) {
|
|
++groups;
|
|
if (!symbol.isLayerRenderable() || it->second.get() != symbol.retainedResultGroup()) {
|
|
result.complete = false;
|
|
}
|
|
}
|
|
if (groups > 1 || (symbol.isLayerRenderable() && symbol.retainedResultGroup() && groups != 1)) {
|
|
result.complete = false;
|
|
}
|
|
result.appendResult(symbol.retainedRenderResult(fadeChange, frame));
|
|
}
|
|
auto it = layerGroupsByLayerIndex.lower_bound(begin);
|
|
std::size_t ordinal = static_cast<std::size_t>(std::distance(layerGroupsByLayerIndex.begin(), it));
|
|
for (; it != layerGroupsByLayerIndex.end() && it->first < endExclusive; ++it, ++ordinal) {
|
|
if (!it->second) { result.complete = false; continue; }
|
|
const auto& group = *it->second;
|
|
result.append(ordinal);
|
|
result.retain(it->second);
|
|
result.append(group.getLayerIndex());
|
|
result.append(group.getDrawableCount());
|
|
result.append(group.getEnabled());
|
|
}
|
|
result.complete = result.complete && pendingChanges.empty();
|
|
return result;
|
|
}
|
|
|
|
bool RenderOrchestrator::retainedPlacementTransitionActive(TimePoint timePoint) const {
|
|
return placementController.hasActiveSymbolFade(timePoint);
|
|
}
|
|
|
|
bool RenderOrchestrator::retainedUnderlayIsStable(TimePoint timePoint, int32_t boundary) const {
|
|
return retainedRangeIsStable(timePoint, 0, boundary);
|
|
}
|
|
|
|
bool RenderOrchestrator::retainedRangeIsStable(TimePoint timePoint, int32_t begin, int32_t endExclusive) const {
|
|
if (renderLight.hasTransition()) return false;
|
|
if (retainedRangeUsesPlacement(begin, endExclusive) && placementController.hasActiveSymbolFade(timePoint)) return false;
|
|
|
|
const auto first = std::clamp<int32_t>(begin, 0, static_cast<int32_t>(orderedLayers.size()));
|
|
const auto last = std::clamp<int32_t>(endExclusive, first, static_cast<int32_t>(orderedLayers.size()));
|
|
for (int32_t index = first; index < last; ++index) {
|
|
const auto& layer = orderedLayers[index].get();
|
|
if (layer.hasTransition() || layer.needsRepaint()) return false;
|
|
}
|
|
|
|
for (const auto& entry : renderSources) {
|
|
if (entry.second->hasFadingTiles()) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
RenderOrchestrator::RetainedSegmentSourceState RenderOrchestrator::retainedSegmentSourceState(
|
|
int32_t begin, int32_t endExclusive) const {
|
|
RetainedSegmentSourceState result;
|
|
const auto dependencies = renderSegmentDependencies(retainedStyleLayers, begin, endExclusive);
|
|
if (dependencies.unknown) return result;
|
|
// The new upstream source has no retained-cache revision contract yet.
|
|
// Keep its ordinary renderer active and fail closed only for cache reuse.
|
|
for (const auto& sourceId : dependencies.sourceIds) {
|
|
const auto source = renderSources.find(sourceId);
|
|
if (source == renderSources.end()) {
|
|
// A missing source also has no revision-book entry. Report the
|
|
// concrete dependency failure before revisionFor collapses it to unknown.
|
|
result.reason = "missing-source";
|
|
return result;
|
|
}
|
|
if (source->second->baseImpl->type == style::SourceType::CustomMVTVector) {
|
|
return result;
|
|
}
|
|
}
|
|
// The global epoch contains only changes whose effect cannot be scoped to one source.
|
|
{
|
|
std::lock_guard<std::mutex> lock(readinessDiagnosticsMutex);
|
|
const auto value = retainedSegmentRevisions.revisionFor(dependencies);
|
|
if (!value) return result;
|
|
result.revision = *value;
|
|
}
|
|
const auto assessment = assessRenderSegmentSources(dependencies, [&](const std::string& sourceId)
|
|
-> std::optional<RenderSegmentSourceStatus> {
|
|
const auto source = renderSources.find(sourceId);
|
|
if (source == renderSources.end()) return std::nullopt;
|
|
return RenderSegmentSourceStatus{source->second->isLoaded(), source->second->hasFadingTiles()};
|
|
});
|
|
result.ready = assessment.ready;
|
|
result.stable = assessment.stable;
|
|
result.reason = assessment.reason;
|
|
return result;
|
|
}
|
|
|
|
bool RenderOrchestrator::retainedSegmentIsStable(TimePoint timePoint,
|
|
int32_t begin,
|
|
int32_t endExclusive) const {
|
|
if (renderLight.hasTransition()) return false;
|
|
if (retainedRangeUsesPlacement(begin, endExclusive) && placementController.hasActiveSymbolFade(timePoint)) return false;
|
|
const auto first = std::clamp<int32_t>(begin, 0, static_cast<int32_t>(orderedLayers.size()));
|
|
const auto last = std::clamp<int32_t>(endExclusive, first, static_cast<int32_t>(orderedLayers.size()));
|
|
for (int32_t index = first; index < last; ++index) {
|
|
const auto& layer = orderedLayers[index].get();
|
|
if (layer.hasTransition() || layer.needsRepaint()) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool RenderOrchestrator::retainedGlobalResourcesReady() const {
|
|
return imageManager->isLoaded();
|
|
}
|
|
|
|
bool RenderOrchestrator::retainedGlobalLightTransition() const {
|
|
return renderLight.hasTransition();
|
|
}
|
|
|
|
bool RenderOrchestrator::retainedUnderlayHasUnsupportedLayer(int32_t boundary) const {
|
|
return retainedRangeHasUnsupportedLayer(0, boundary);
|
|
}
|
|
|
|
bool RenderOrchestrator::retainedRangeHasUnsupportedLayer(int32_t begin, int32_t endExclusive) const {
|
|
const auto first = std::clamp<int32_t>(begin, 0, static_cast<int32_t>(orderedLayers.size()));
|
|
const auto last = std::clamp<int32_t>(endExclusive, first, static_cast<int32_t>(orderedLayers.size()));
|
|
for (int32_t index = first; index < last; ++index) {
|
|
const std::string_view type = orderedLayers[index].get().baseImpl->getTypeInfo()->type;
|
|
if (type == "custom" || type == "custom-drawable") return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool RenderOrchestrator::continuousRangeSupportsParticleFastPath(int32_t begin, int32_t endExclusive) const {
|
|
const auto first = std::clamp<int32_t>(begin, 0, static_cast<int32_t>(orderedLayers.size()));
|
|
const auto last = std::clamp<int32_t>(endExclusive, first, static_cast<int32_t>(orderedLayers.size()));
|
|
bool particle = false;
|
|
for (int32_t index = first; index < last; ++index) {
|
|
const auto& layer = orderedLayers[index].get();
|
|
if (!layer.startsRetainedDynamicRange()) return false;
|
|
particle = particle || layer.isParticleAnimationLayer();
|
|
}
|
|
return particle;
|
|
}
|
|
|
|
uint64_t RenderOrchestrator::retainedRangeContentFingerprint(int32_t begin, int32_t endExclusive) const {
|
|
const auto first = std::clamp<int32_t>(begin, 0, static_cast<int32_t>(orderedLayers.size()));
|
|
const auto last = std::clamp<int32_t>(endExclusive, first, static_cast<int32_t>(orderedLayers.size()));
|
|
uint64_t value = 1469598103934665603ULL;
|
|
for (int32_t index = first; index < last; ++index) {
|
|
const auto& layer = orderedLayers[static_cast<std::size_t>(index)].get();
|
|
const auto identity = reinterpret_cast<std::uintptr_t>(layer.baseImpl.get());
|
|
value ^= static_cast<uint64_t>(identity);
|
|
value *= 1099511628211ULL;
|
|
value ^= static_cast<uint64_t>(index);
|
|
value *= 1099511628211ULL;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
ParticleFastPathDynamicState RenderOrchestrator::particleFastPathDynamicState(TimePoint timePoint) const {
|
|
ParticleFastPathDynamicState result;
|
|
result.blockingTransition = renderLight.hasTransition();
|
|
for (const auto& entry : renderLayers) {
|
|
const auto& layer = *entry.second;
|
|
result.blockingTransition = result.blockingTransition || layer.hasTransition();
|
|
if (layer.isLayerRenderable() && layer.isParticleAnimationActive()) result.particleNeedsRepaint = true;
|
|
if (layer.needsRepaint()) {
|
|
if (!layer.isParticleAnimationLayer()) {
|
|
result.nonParticleNeedsRepaint = true;
|
|
}
|
|
}
|
|
}
|
|
const bool controllerTransition = placementController.hasActiveSymbolFade(timePoint);
|
|
result.placementTransition = controllerTransition &&
|
|
(retainedStyleLayers.size() != renderLayers.size() ||
|
|
particleFastPathHasPlacementTransition(retainedStyleLayers, controllerTransition));
|
|
for (const auto& entry : renderSources) {
|
|
result.fadingTiles = result.fadingTiles || entry.second->hasFadingTiles();
|
|
}
|
|
result.pendingChanges = !pendingChanges.empty();
|
|
return result;
|
|
}
|
|
|
|
bool RenderOrchestrator::isLoaded() const {
|
|
MLN_TRACE_FUNC();
|
|
|
|
// do the simple boolean check before iterating over all the tiles in all the sources
|
|
if (!imageManager->isLoaded()) {
|
|
return false;
|
|
}
|
|
|
|
for (const auto& entry : renderSources) {
|
|
if (!entry.second->isLoaded()) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void RenderOrchestrator::clearData() {
|
|
MLN_TRACE_FUNC();
|
|
|
|
if (!sourceImpls->empty()) sourceImpls = makeMutable<std::vector<Immutable<style::Source::Impl>>>();
|
|
if (!layerImpls->empty()) layerImpls = makeMutable<std::vector<Immutable<style::Layer::Impl>>>();
|
|
if (!imageImpls->empty()) imageImpls = makeMutable<std::vector<Immutable<style::Image::Impl>>>();
|
|
|
|
UniqueChangeRequestVec changes;
|
|
for (const auto& entry : renderLayers) {
|
|
entry.second->layerRemoved(changes);
|
|
}
|
|
addChanges(changes);
|
|
|
|
debugLayerGroups.clear();
|
|
|
|
renderSources.clear();
|
|
renderLayers.clear();
|
|
|
|
crossTileSymbolIndex.reset();
|
|
|
|
if (!lineAtlas->isEmpty()) lineAtlas = std::make_unique<LineAtlas>();
|
|
if (!patternAtlas->isEmpty()) patternAtlas = std::make_unique<PatternAtlas>();
|
|
|
|
imageManager->clear();
|
|
glyphManager->evict(fontStacks(*layerImpls));
|
|
try {
|
|
std::lock_guard<std::mutex> lock(readinessDiagnosticsMutex);
|
|
sourceReadinessEvents.clear();
|
|
lastReadinessMutation = {};
|
|
} catch (...) {
|
|
// Keep renderer style cleanup independent from optional diagnostic history cleanup.
|
|
}
|
|
advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::StyleMutation);
|
|
}
|
|
|
|
void RenderOrchestrator::addChanges(UniqueChangeRequestVec& changes) {
|
|
pendingChanges.insert(
|
|
pendingChanges.end(), std::make_move_iterator(changes.begin()), std::make_move_iterator(changes.end()));
|
|
changes.clear();
|
|
}
|
|
|
|
void RenderOrchestrator::updateLayerIndex(LayerGroupBasePtr layerGroup, const int32_t newIndex) {
|
|
MLN_TRACE_FUNC();
|
|
|
|
if (!layerGroup || layerGroup->getLayerIndex() == newIndex) {
|
|
return;
|
|
}
|
|
const auto range = layerGroupsByLayerIndex.equal_range(layerGroup->getLayerIndex());
|
|
for (auto it = range.first; it != range.second; ++it) {
|
|
if (it->second == layerGroup) {
|
|
layerGroupsByLayerIndex.erase(it);
|
|
layerGroup->updateLayerIndex(newIndex);
|
|
layerGroupsByLayerIndex.insert(std::make_pair(newIndex, std::move(layerGroup)));
|
|
return;
|
|
}
|
|
}
|
|
// We're not tracking the layer, indicating that it's currently disabled, so update it directly.
|
|
layerGroup->updateLayerIndex(newIndex);
|
|
}
|
|
|
|
bool RenderOrchestrator::addLayerGroup(LayerGroupBasePtr layerGroup) {
|
|
MLN_TRACE_FUNC();
|
|
|
|
const auto index = layerGroup->getLayerIndex();
|
|
const auto range = layerGroupsByLayerIndex.equal_range(index);
|
|
bool found = false;
|
|
for (auto it = range.first; it != range.second; ++it) {
|
|
if (it->second == layerGroup) {
|
|
assert(layerGroup->getLayerIndex() == it->first);
|
|
found = true;
|
|
// not added
|
|
break;
|
|
}
|
|
}
|
|
if (found) {
|
|
return false; // not added
|
|
} else {
|
|
layerGroupsByLayerIndex.insert(std::make_pair(index, std::move(layerGroup)));
|
|
return true; // added
|
|
}
|
|
}
|
|
|
|
bool RenderOrchestrator::removeLayerGroup(const LayerGroupBasePtr& layerGroup) {
|
|
MLN_TRACE_FUNC();
|
|
|
|
if (!layerGroup) {
|
|
return false;
|
|
}
|
|
const auto range = layerGroupsByLayerIndex.equal_range(layerGroup->getLayerIndex());
|
|
for (auto it = range.first; it != range.second; ++it) {
|
|
if (it->second == layerGroup) {
|
|
layerGroupsByLayerIndex.erase(it);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
size_t RenderOrchestrator::numLayerGroups() const noexcept {
|
|
return layerGroupsByLayerIndex.size();
|
|
}
|
|
|
|
void RenderOrchestrator::updateLayers(gfx::ShaderRegistry& shaders,
|
|
gfx::Context& context,
|
|
const TransformState& state,
|
|
const std::shared_ptr<UpdateParameters>& updateParameters,
|
|
const PaintParameters& paintParameters,
|
|
const RenderTree& renderTree) {
|
|
MLN_TRACE_FUNC();
|
|
|
|
const bool isMapModeContinuous = updateParameters->mode == MapMode::Continuous;
|
|
const auto transitionOptions = isMapModeContinuous ? updateParameters->transitionOptions
|
|
: style::TransitionOptions();
|
|
const auto defDuration = isMapModeContinuous ? util::DEFAULT_TRANSITION_DURATION : Duration::zero();
|
|
const PropertyEvaluationParameters evalParameters{
|
|
getZoomHistory(),
|
|
updateParameters->timePoint,
|
|
transitionOptions.duration.value_or(defDuration),
|
|
};
|
|
|
|
const auto& items = renderTree.getLayerRenderItemMap();
|
|
|
|
std::vector<std::unique_ptr<ChangeRequest>> changes;
|
|
changes.reserve(items.size() * 3);
|
|
|
|
for (auto& entry : orderedLayers) {
|
|
auto& layer = entry.get();
|
|
const auto source = renderSources.find(layer.baseImpl->source);
|
|
if (!layer.isLayerRenderable() && source != renderSources.end() &&
|
|
layer.needsHiddenLayerSourceCleanup(*source->second)) {
|
|
layer.update(shaders, context, state, updateParameters, paintParameters, renderTree, changes);
|
|
}
|
|
}
|
|
|
|
for (const auto& item : items) {
|
|
auto& renderLayer = item.layer.get();
|
|
#if MLN_RENDER_BACKEND_OPENGL
|
|
// Android Emulator: Goldfish is *very* broken. This will prevent a crash
|
|
// inside the GL translation layer at the cost of emulator performance.
|
|
if (androidGoldfishMitigationEnabled) {
|
|
renderLayer.removeAllDrawables();
|
|
}
|
|
#endif
|
|
try {
|
|
renderLayer.update(shaders, context, state, updateParameters, paintParameters, renderTree, changes);
|
|
} catch (...) {
|
|
recordRenderError(std::current_exception());
|
|
}
|
|
}
|
|
addChanges(changes);
|
|
}
|
|
|
|
void RenderOrchestrator::updateParticleLayers(gfx::ShaderRegistry& shaders,
|
|
gfx::Context& context,
|
|
const TransformState& state,
|
|
const std::shared_ptr<UpdateParameters>& updateParameters,
|
|
const PaintParameters& paintParameters,
|
|
const RenderTree& renderTree) {
|
|
MLN_TRACE_FUNC();
|
|
|
|
const auto& items = renderTree.getLayerRenderItemMap();
|
|
std::vector<std::unique_ptr<ChangeRequest>> changes;
|
|
changes.reserve(8);
|
|
for (const auto& item : items) {
|
|
auto& renderLayer = item.layer.get();
|
|
if (!renderLayer.isParticleAnimationLayer()) continue;
|
|
#if MLN_RENDER_BACKEND_OPENGL
|
|
if (androidGoldfishMitigationEnabled) {
|
|
renderLayer.removeAllDrawables();
|
|
}
|
|
#endif
|
|
try {
|
|
renderLayer.update(shaders, context, state, updateParameters, paintParameters, renderTree, changes);
|
|
} catch (...) {
|
|
recordRenderError(std::current_exception());
|
|
}
|
|
}
|
|
addChanges(changes);
|
|
}
|
|
|
|
void RenderOrchestrator::processChanges() {
|
|
auto localChanges = std::move(pendingChanges);
|
|
for (auto& change : localChanges) {
|
|
change->execute(*this);
|
|
}
|
|
}
|
|
|
|
bool RenderOrchestrator::addRenderTarget(RenderTargetPtr renderTarget) {
|
|
auto it = std::ranges::find(renderTargets, renderTarget);
|
|
if (it == renderTargets.end()) {
|
|
renderTargets.emplace_back(renderTarget);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool RenderOrchestrator::removeRenderTarget(const RenderTargetPtr& renderTarget) {
|
|
auto it = std::ranges::find(renderTargets, renderTarget);
|
|
if (it != renderTargets.end()) {
|
|
renderTargets.erase(it);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void RenderOrchestrator::updateDebugLayerGroups(const RenderTree& renderTree, PaintParameters& parameters) {
|
|
for (const RenderItem& item : renderTree.getSourceRenderItems()) {
|
|
item.updateDebugDrawables(debugLayerGroups, parameters);
|
|
}
|
|
}
|
|
|
|
void RenderOrchestrator::onGlyphsLoaded(const FontStack& fontStack, const GlyphRange& range) {
|
|
advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::GlyphMutation);
|
|
observer->onGlyphsLoaded(fontStack, range);
|
|
}
|
|
|
|
void RenderOrchestrator::onGlyphsError(const FontStack& fontStack,
|
|
const GlyphRange& glyphRange,
|
|
std::exception_ptr error) {
|
|
MLN_TRACE_FUNC();
|
|
advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::GlyphMutation);
|
|
|
|
std::stringstream ss;
|
|
ss << "Failed to load glyph range ";
|
|
if (glyphRange.type == FontPBF) {
|
|
ss << glyphRange.first << "-" << glyphRange.second;
|
|
} else {
|
|
ss << (int)glyphRange.type << "(font file)";
|
|
}
|
|
ss << " for font stack " << fontStackToString(fontStack) << ":( " << util::toString(error) << ")";
|
|
auto errorDetail = ss.str();
|
|
Log::Error(Event::Style, errorDetail);
|
|
observer->onResourceError(error);
|
|
}
|
|
|
|
void RenderOrchestrator::onGlyphsRequested(const FontStack& fontStack, const GlyphRange& range) {
|
|
advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::GlyphMutation);
|
|
observer->onGlyphsRequested(fontStack, range);
|
|
}
|
|
|
|
void RenderOrchestrator::onTileError(RenderSource& source, const OverscaledTileID& tileID, std::exception_ptr error) {
|
|
MLN_TRACE_FUNC();
|
|
advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::TileMutation, source.baseImpl->id);
|
|
|
|
Log::Error(Event::Style,
|
|
"Failed to load tile " + util::toString(tileID) + " for source " + source.baseImpl->id + ": " +
|
|
util::toString(error));
|
|
observer->onResourceError(error);
|
|
}
|
|
|
|
void RenderOrchestrator::onTileChanged(RenderSource& source, const OverscaledTileID&) {
|
|
MLN_TRACE_FUNC();
|
|
|
|
advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::TileMutation, source.baseImpl->id);
|
|
observer->onInvalidate();
|
|
}
|
|
|
|
void RenderOrchestrator::onTileAction(RenderSource&,
|
|
TileOperation op,
|
|
const OverscaledTileID& id,
|
|
const std::string& sourceID) {
|
|
try {
|
|
std::lock_guard<std::mutex> lock(readinessDiagnosticsMutex);
|
|
const auto sequence = ++readinessEventSequence;
|
|
auto& state = sourceReadinessEvents[sourceID];
|
|
state.lastTileActionSequence = sequence;
|
|
state.lastTileAction = tileOperationText(op);
|
|
} catch (...) {
|
|
// Tile action observation is best-effort; always forward the original observer callback.
|
|
}
|
|
observer->onTileAction(op, id, sourceID);
|
|
}
|
|
|
|
void RenderOrchestrator::onSymbolError(RenderSource& source, const std::string& message) {
|
|
advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::TileMutation, source.baseImpl->id);
|
|
observer->onSymbolError(message);
|
|
}
|
|
|
|
void RenderOrchestrator::onStyleImageMissing(const std::string& id, const std::function<void()>& done) {
|
|
MLN_TRACE_FUNC();
|
|
|
|
advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::ImageMutation);
|
|
observer->onStyleImageMissing(id, done);
|
|
}
|
|
|
|
void RenderOrchestrator::onRemoveUnusedStyleImages(const std::vector<std::string>& unusedImageIDs) {
|
|
MLN_TRACE_FUNC();
|
|
|
|
if (!unusedImageIDs.empty()) {
|
|
advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::ImageMutation);
|
|
}
|
|
observer->onRemoveUnusedStyleImages(unusedImageIDs);
|
|
}
|
|
|
|
} // namespace mln
|