feat: admit settled Symbol render results to retained cache
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
This commit is contained in:
@@ -19,6 +19,9 @@
|
||||
#include <mln/renderer/render_target.hpp>
|
||||
#include <mln/shaders/mtl/clipping_mask.hpp>
|
||||
#include <mln/shaders/mtl/shader_program.hpp>
|
||||
#include <mln/shaders/mtl/symbol.hpp>
|
||||
#include <mln/shaders/mtl/common.hpp>
|
||||
#include <mln/renderer/symbol_shader_provenance.hpp>
|
||||
#include <mln/shaders/program_parameters.hpp>
|
||||
#include <mln/util/traits.hpp>
|
||||
#include <mln/util/std.hpp>
|
||||
@@ -182,6 +185,26 @@ UniqueShaderProgram Context::createProgram(shaders::BuiltIn shaderID,
|
||||
std::move(name), backend, std::move(vertexFunction), std::move(fragmentFunction));
|
||||
observer->onPostCompileShader(shaderID, gfx::Backend::Type::Metal, defineStr);
|
||||
|
||||
const auto matchesSymbol = [&]<shaders::BuiltIn ID>() {
|
||||
using Source = shaders::ShaderSource<ID, gfx::Backend::Type::Metal>;
|
||||
return source == std::string(shaders::prelude) + Source::prelude + Source::source &&
|
||||
vertexName == Source::vertexMainFunction && fragmentName == Source::fragmentMainFunction;
|
||||
};
|
||||
bool originalSymbol = false;
|
||||
switch (shaderID) {
|
||||
case shaders::BuiltIn::SymbolIconShader:
|
||||
originalSymbol = matchesSymbol.template operator()<shaders::BuiltIn::SymbolIconShader>(); break;
|
||||
case shaders::BuiltIn::SymbolSDFShader:
|
||||
originalSymbol = matchesSymbol.template operator()<shaders::BuiltIn::SymbolSDFShader>(); break;
|
||||
case shaders::BuiltIn::SymbolTextAndIconShader:
|
||||
originalSymbol = matchesSymbol.template operator()<shaders::BuiltIn::SymbolTextAndIconShader>(); break;
|
||||
default: break;
|
||||
}
|
||||
for (const auto& [name_, value] : additionalDefines) {
|
||||
originalSymbol = originalSymbol && symbolUniformDefine(name_) && value.empty();
|
||||
}
|
||||
if (originalSymbol) SymbolShaderProvenance::get().compiledOwned(*shader);
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
#include <mln/gfx/collision_drawable_data.hpp>
|
||||
#include <mln/renderer/layer_group.hpp>
|
||||
#include <mln/renderer/layers/symbol_layer_tweaker.hpp>
|
||||
#include <mln/renderer/symbol_render_inputs.hpp>
|
||||
#include <mln/renderer/symbol_shader_provenance.hpp>
|
||||
#include <mln/renderer/update_parameters.hpp>
|
||||
#include <mln/shaders/shader_program_base.hpp>
|
||||
#include <mln/shaders/symbol_layer_ubo.hpp>
|
||||
@@ -473,6 +475,147 @@ std::size_t RenderSymbolLayer::removeAllDrawables() {
|
||||
return stats.drawablesRemoved - oldValue;
|
||||
}
|
||||
|
||||
SymbolRenderResult RenderSymbolLayer::retainedRenderResult(float fadeChange, uint64_t frame) {
|
||||
SymbolRenderResult result;
|
||||
result.append(layerIndex);
|
||||
result.append(passes);
|
||||
result.append(isLayerRenderable());
|
||||
// A hidden layer is admitted only after processChanges has removed both
|
||||
// groups (the orchestrator verifies registry membership separately).
|
||||
if (!isLayerRenderable()) return result;
|
||||
if (collisionTileLayerGroup && !collisionTileLayerGroup->empty()) result.complete = false;
|
||||
if (!layerGroup || layerGroup->empty()) {
|
||||
// Empty is provable only if there are no renderable Symbol buckets.
|
||||
if (renderTiles) for (const auto& tileRef : *renderTiles) {
|
||||
const auto* bucket = static_cast<const SymbolBucket*>(tileRef.get().getBucket(*baseImpl));
|
||||
if (bucket && (bucket->hasTextData() || bucket->hasIconData() || bucket->hasSdfIconData())) {
|
||||
result.complete = false;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (!layerTweaker) {
|
||||
result.complete = false;
|
||||
return result;
|
||||
}
|
||||
// RenderSymbolLayer is final and creates only this tweaker and the native
|
||||
// atlas tweaker. Unknown placement-capable layers never call this method.
|
||||
const auto& tweaker = static_cast<const SymbolLayerTweaker&>(*layerTweaker);
|
||||
if (tweaker.getRenderResultFrame() != frame) result.complete = false;
|
||||
result.appendResult(tweaker.getRenderResult());
|
||||
result.retain(layerGroup);
|
||||
result.append(layerGroup->getEnabled());
|
||||
result.append(layerGroup->getDrawableCount());
|
||||
std::size_t visited = 0;
|
||||
visitLayerGroupDrawables(*layerGroup, [&](gfx::Drawable& drawable) {
|
||||
++visited;
|
||||
if (drawable.getIsCustom() || drawable.getLayerTweaker() != layerTweaker ||
|
||||
!drawable.getBucket() || !drawable.getTileID() || !drawable.getData() ||
|
||||
!drawable.getShader() || drawable.getTweakers().size() != 1) {
|
||||
result.complete = false;
|
||||
return;
|
||||
}
|
||||
result.append(drawable.getID());
|
||||
if (!SymbolShaderProvenance::get().supports(*drawable.getShader())) result.complete = false;
|
||||
result.retain(drawable.getTweakers().front()); // immutable native atlas/sampler policy
|
||||
result.retain(drawable.getShader());
|
||||
result.retain(drawable.getBucket());
|
||||
const auto tileID = util::toString(*drawable.getTileID());
|
||||
result.appendBlock(tileID.data(), tileID.size());
|
||||
result.append(drawable.getDrawPriority());
|
||||
result.append(drawable.getRenderPass());
|
||||
result.append(drawable.getEnabled());
|
||||
result.append(drawable.getEnableColor());
|
||||
result.append(drawable.getEnableDepth());
|
||||
result.append(drawable.getEnableStencil());
|
||||
result.append(drawable.getSubLayerIndex());
|
||||
result.append(drawable.getDepthType());
|
||||
result.append(drawable.getIs3D());
|
||||
const auto& cull = drawable.getCullFaceMode();
|
||||
result.append(cull.enabled);
|
||||
result.append(cull.side);
|
||||
result.append(cull.winding);
|
||||
const auto& color = drawable.getColorMode();
|
||||
color.blendFunction.match([&](const auto& blend) {
|
||||
result.append(blend.equation);
|
||||
result.append(blend.srcFactor);
|
||||
result.append(blend.dstFactor);
|
||||
});
|
||||
result.append(color.blendColor);
|
||||
result.append(color.mask.r);
|
||||
result.append(color.mask.g);
|
||||
result.append(color.mask.b);
|
||||
result.append(color.mask.a);
|
||||
result.append(drawable.getUBOIndex());
|
||||
appendSymbolAttributes(result, drawable.getVertexAttributes());
|
||||
appendSymbolAttributes(result, drawable.getInstanceAttributes());
|
||||
|
||||
const auto& bucket = static_cast<const SymbolBucket&>(*drawable.getBucket());
|
||||
const auto& data = static_cast<const gfx::SymbolDrawableData&>(*drawable.getData());
|
||||
const auto& buffer = data.symbolType == SymbolType::Text ? bucket.text :
|
||||
data.symbolType == SymbolType::IconSDF ? bucket.sdfIcon : bucket.icon;
|
||||
const auto& opacity = buffer.opacityAttributeData();
|
||||
if (opacity.empty()) result.complete = false;
|
||||
for (const auto& vertex : opacity.vector()) {
|
||||
if (!symbolOpacityIsSettled(vertex.a1[0], fadeChange)) result.complete = false;
|
||||
}
|
||||
result.append(buffer.segments.size());
|
||||
for (const auto& segment : buffer.segments) {
|
||||
result.append(segment.vertexOffset);
|
||||
result.append(segment.indexOffset);
|
||||
result.append(segment.vertexLength);
|
||||
result.append(segment.indexLength);
|
||||
result.append(segment.baseInstance);
|
||||
result.append(segment.instanceCount);
|
||||
result.append(segment.sortKey);
|
||||
}
|
||||
#if MLN_USE_SYMBOL_INSTANCING
|
||||
const auto& indices = staticDataIndices;
|
||||
#else
|
||||
const auto& indices = buffer.sharedTriangles;
|
||||
#endif
|
||||
if (!indices) {
|
||||
result.complete = false;
|
||||
} else {
|
||||
if (!indices->getBuffer() || indices->getDirty()) result.complete = false;
|
||||
result.append(indices->isReleased());
|
||||
if (indices->isReleased()) {
|
||||
result.retain(indices);
|
||||
result.append(reinterpret_cast<std::uintptr_t>(indices->getBuffer()));
|
||||
if (!indices->getBuffer()) result.complete = false;
|
||||
} else {
|
||||
result.appendBlock(indices->data(), indices->bytes());
|
||||
if (indices->empty()) result.complete = false;
|
||||
}
|
||||
}
|
||||
const auto* tile = drawable.getRenderTile();
|
||||
const auto& atlases = tile ? tile->getAtlasTextures() : TileAtlasTexturesPtr{};
|
||||
if (!atlases) {
|
||||
result.complete = false;
|
||||
return;
|
||||
}
|
||||
result.retain(atlases);
|
||||
result.retain(atlases->glyph);
|
||||
result.retain(atlases->icon);
|
||||
for (std::size_t id = 0; id < shaders::maxTextureCountPerShader; ++id) {
|
||||
const auto& texture = drawable.getTexture(id);
|
||||
result.retain(texture);
|
||||
if (texture) {
|
||||
result.append(texture->getSize());
|
||||
result.append(texture->getFormat());
|
||||
if (texture->needsUpload()) result.complete = false;
|
||||
}
|
||||
}
|
||||
const auto& expected = data.symbolType == SymbolType::Text ? atlases->glyph : atlases->icon;
|
||||
if (!expected || expected->needsUpload() ||
|
||||
drawable.getTexture(shaders::idSymbolImageTexture) != expected) result.complete = false;
|
||||
if (drawable.getShader()->getSamplerLocation(shaders::idSymbolImageIconTexture) &&
|
||||
drawable.getTexture(shaders::idSymbolImageIconTexture) != atlases->icon) result.complete = false;
|
||||
});
|
||||
if (visited != layerGroup->getDrawableCount()) result.complete = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
void RenderSymbolLayer::update(gfx::ShaderRegistry& shaders,
|
||||
gfx::Context& context,
|
||||
const TransformState& state,
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <mln/style/image_impl.hpp>
|
||||
#include <mln/style/layers/symbol_layer_impl.hpp>
|
||||
#include <mln/style/layers/symbol_layer_properties.hpp>
|
||||
#include <mln/renderer/symbol_render_result.hpp>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
@@ -57,6 +58,9 @@ public:
|
||||
|
||||
/// Remove all the drawables for tiles
|
||||
std::size_t removeAllDrawables() override;
|
||||
// Called after Full update/processChanges/tweakers/uploads, before Build.
|
||||
SymbolRenderResult retainedRenderResult(float fadeChange, uint64_t frame);
|
||||
const LayerGroupBase* retainedResultGroup() const { return layerGroup.get(); }
|
||||
|
||||
protected:
|
||||
/// @brief Called by the RenderOrchestrator during RenderTree construction.
|
||||
|
||||
@@ -53,6 +53,8 @@ auto getInterpFactor(const SymbolBucket::PaintProperties& paintProps, bool isTex
|
||||
} // namespace
|
||||
|
||||
void SymbolLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters& parameters) {
|
||||
renderResult = {};
|
||||
renderResultFrame = parameters.frameCount;
|
||||
if (layerGroup.empty()) {
|
||||
return;
|
||||
}
|
||||
@@ -84,10 +86,16 @@ void SymbolLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParamete
|
||||
.icon_halo_blur = constOrDefault<IconHaloBlur>(evaluated),
|
||||
.pad2 = 0};
|
||||
context.emplaceOrUpdateUniformBuffer(evaluatedPropsUniformBuffer, &propsUBO);
|
||||
evaluatedResult = {};
|
||||
evaluatedResult.append(propsUBO);
|
||||
propertiesUpdated = false;
|
||||
}
|
||||
auto& layerUniforms = layerGroup.mutableUniformBuffers();
|
||||
layerUniforms.set(idSymbolEvaluatedPropsUBO, evaluatedPropsUniformBuffer);
|
||||
if (!evaluatedPropsUniformBuffer || evaluatedPropsUniformBuffer->getSize() < sizeof(SymbolEvaluatedPropsUBO)) {
|
||||
renderResult.complete = false;
|
||||
}
|
||||
renderResult.appendResult(evaluatedResult);
|
||||
|
||||
#if MLN_UBO_CONSOLIDATION
|
||||
int i = 0;
|
||||
@@ -102,6 +110,7 @@ void SymbolLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParamete
|
||||
|
||||
visitLayerGroupDrawables(layerGroup, [&](gfx::Drawable& drawable) {
|
||||
if (!drawable.getTileID() || !drawable.getData()) {
|
||||
renderResult.complete = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -115,6 +124,7 @@ void SymbolLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParamete
|
||||
const auto bucket = std::static_pointer_cast<SymbolBucket>(drawable.getBucket());
|
||||
const auto* tile = drawable.getRenderTile();
|
||||
if (!bucket || !tile || (!textBinders && !iconBinders)) {
|
||||
renderResult.complete = false;
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
@@ -205,12 +215,29 @@ void SymbolLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParamete
|
||||
.pad1 = 0,
|
||||
};
|
||||
|
||||
#if MLN_UBO_CONSOLIDATION
|
||||
// Capture the same CPU inputs passed to the consolidated GPU buffers.
|
||||
const auto& resultDrawableUBO = drawableUBOVector[i];
|
||||
const auto& resultTileUBO = tilePropsUBOVector[i];
|
||||
#else
|
||||
const auto& resultDrawableUBO = drawableUBO;
|
||||
const auto& resultTileUBO = tilePropsUBO;
|
||||
#endif
|
||||
renderResult.append(drawable.getID());
|
||||
// SymbolDrawableUBO has twelve bytes of trailing alignment padding.
|
||||
// Compare shader fields only, never indeterminate padding.
|
||||
renderResult.appendBytes(&resultDrawableUBO, offsetof(SymbolDrawableUBO, halo_blur_t) + sizeof(float));
|
||||
renderResult.append(resultTileUBO);
|
||||
|
||||
#if MLN_UBO_CONSOLIDATION
|
||||
drawable.setUBOIndex(i++);
|
||||
#else
|
||||
auto& drawableUniforms = drawable.mutableUniformBuffers();
|
||||
drawableUniforms.createOrUpdate(idSymbolDrawableUBO, &drawableUBO, context);
|
||||
drawableUniforms.createOrUpdate(idSymbolTilePropsUBO, &tilePropsUBO, context);
|
||||
if (!drawableUniforms.get(idSymbolDrawableUBO) || !drawableUniforms.get(idSymbolTilePropsUBO)) {
|
||||
renderResult.complete = false;
|
||||
}
|
||||
#endif
|
||||
});
|
||||
|
||||
@@ -233,6 +260,9 @@ void SymbolLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParamete
|
||||
|
||||
layerUniforms.set(idSymbolDrawableUBO, drawableUniformBuffer);
|
||||
layerUniforms.set(idSymbolTilePropsUBO, tilePropsUniformBuffer);
|
||||
if (!drawableUniformBuffer || !tilePropsUniformBuffer ||
|
||||
drawableUniformBuffer->getSize() < drawableUBOVectorSize ||
|
||||
tilePropsUniformBuffer->getSize() < tilePropsUBOVectorSize) renderResult.complete = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <mln/renderer/layer_tweaker.hpp>
|
||||
#include <mln/renderer/symbol_render_result.hpp>
|
||||
#include <mln/tile/tile_id.hpp>
|
||||
#include <mln/util/containers.hpp>
|
||||
#include <mln/util/hash.hpp>
|
||||
@@ -19,8 +20,13 @@ public:
|
||||
~SymbolLayerTweaker() override = default;
|
||||
|
||||
void execute(LayerGroupBase&, const PaintParameters&) override;
|
||||
const SymbolRenderResult& getRenderResult() const { return renderResult; }
|
||||
uint64_t getRenderResultFrame() const { return renderResultFrame; }
|
||||
|
||||
private:
|
||||
SymbolRenderResult renderResult;
|
||||
SymbolRenderResult evaluatedResult;
|
||||
uint64_t renderResultFrame = 0;
|
||||
gfx::UniformBufferPtr evaluatedPropsUniformBuffer;
|
||||
|
||||
#if MLN_UBO_CONSOLIDATION
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#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>
|
||||
|
||||
@@ -1143,11 +1144,76 @@ bool RenderOrchestrator::retainedRangeUsesPlacement(int32_t begin, int32_t endEx
|
||||
}
|
||||
|
||||
bool RenderOrchestrator::retainedRangeHasPlacementCapableLayer(int32_t begin, int32_t endExclusive) const {
|
||||
return renderSegmentHasPlacementCapableLayer(retainedStyleLayers, begin, endExclusive);
|
||||
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.hasTransitions(timePoint);
|
||||
return placementController.hasActiveSymbolFade(timePoint);
|
||||
}
|
||||
|
||||
bool RenderOrchestrator::retainedUnderlayIsStable(TimePoint timePoint, int32_t boundary) const {
|
||||
@@ -1156,7 +1222,7 @@ bool RenderOrchestrator::retainedUnderlayIsStable(TimePoint timePoint, int32_t b
|
||||
|
||||
bool RenderOrchestrator::retainedRangeIsStable(TimePoint timePoint, int32_t begin, int32_t endExclusive) const {
|
||||
if (renderLight.hasTransition()) return false;
|
||||
if (retainedRangeUsesPlacement(begin, endExclusive) && placementController.hasTransitions(timePoint)) 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()));
|
||||
@@ -1213,7 +1279,7 @@ bool RenderOrchestrator::retainedSegmentIsStable(TimePoint timePoint,
|
||||
int32_t begin,
|
||||
int32_t endExclusive) const {
|
||||
if (renderLight.hasTransition()) return false;
|
||||
if (retainedRangeUsesPlacement(begin, endExclusive) && placementController.hasTransitions(timePoint)) 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) {
|
||||
@@ -1285,7 +1351,7 @@ ParticleFastPathDynamicState RenderOrchestrator::particleFastPathDynamicState(Ti
|
||||
}
|
||||
}
|
||||
}
|
||||
const bool controllerTransition = placementController.hasTransitions(timePoint);
|
||||
const bool controllerTransition = placementController.hasActiveSymbolFade(timePoint);
|
||||
result.placementTransition = controllerTransition &&
|
||||
(retainedStyleLayers.size() != renderLayers.size() ||
|
||||
particleFastPathHasPlacementTransition(retainedStyleLayers, controllerTransition));
|
||||
@@ -1459,7 +1525,7 @@ void RenderOrchestrator::updateLayers(gfx::ShaderRegistry& shaders,
|
||||
try {
|
||||
renderLayer.update(shaders, context, state, updateParameters, paintParameters, renderTree, changes);
|
||||
} catch (...) {
|
||||
observer->onRenderError(std::current_exception());
|
||||
recordRenderError(std::current_exception());
|
||||
}
|
||||
}
|
||||
addChanges(changes);
|
||||
@@ -1487,7 +1553,7 @@ void RenderOrchestrator::updateParticleLayers(gfx::ShaderRegistry& shaders,
|
||||
try {
|
||||
renderLayer.update(shaders, context, state, updateParameters, paintParameters, renderTree, changes);
|
||||
} catch (...) {
|
||||
observer->onRenderError(std::current_exception());
|
||||
recordRenderError(std::current_exception());
|
||||
}
|
||||
}
|
||||
addChanges(changes);
|
||||
@@ -1535,6 +1601,7 @@ 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 ";
|
||||
@@ -1550,11 +1617,13 @@ void RenderOrchestrator::onGlyphsError(const FontStack& fontStack,
|
||||
}
|
||||
|
||||
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 + ": " +
|
||||
@@ -1585,7 +1654,8 @@ void RenderOrchestrator::onTileAction(RenderSource&,
|
||||
observer->onTileAction(op, id, sourceID);
|
||||
}
|
||||
|
||||
void RenderOrchestrator::onSymbolError(RenderSource&, const std::string& message) {
|
||||
void RenderOrchestrator::onSymbolError(RenderSource& source, const std::string& message) {
|
||||
advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::TileMutation, source.baseImpl->id);
|
||||
observer->onSymbolError(message);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <mln/text/placement.hpp>
|
||||
#include <mln/renderer/render_tree.hpp>
|
||||
#include <mln/renderer/retained_underlay_cache.hpp>
|
||||
#include <mln/renderer/symbol_render_result.hpp>
|
||||
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
@@ -128,6 +129,10 @@ public:
|
||||
bool retainedUnderlayHasUnsupportedLayer(int32_t boundary) const;
|
||||
bool retainedRangeUsesPlacement(int32_t begin, int32_t endExclusive) const;
|
||||
bool retainedRangeHasPlacementCapableLayer(int32_t begin, int32_t endExclusive) const;
|
||||
SymbolRenderResult retainedSymbolRenderResult(int32_t begin, int32_t endExclusive, float fadeChange, uint64_t frame);
|
||||
void recordRenderError(std::exception_ptr);
|
||||
void invalidateRetainedRenderResults();
|
||||
uint64_t getRenderErrorRevision() const { return renderErrorRevision; }
|
||||
bool retainedPlacementTransitionActive(TimePoint timePoint) const;
|
||||
bool retainedRangeIsStable(TimePoint, int32_t begin, int32_t endExclusive) const;
|
||||
struct RetainedSegmentSourceState {
|
||||
@@ -159,7 +164,7 @@ public:
|
||||
try {
|
||||
f(*pair.second);
|
||||
} catch (...) {
|
||||
observer->onRenderError(std::current_exception());
|
||||
recordRenderError(std::current_exception());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -174,7 +179,7 @@ public:
|
||||
if (it->second) {
|
||||
try { f(*it->second, ordinal); } catch (...) {
|
||||
if (failed) *failed = true;
|
||||
observer->onRenderError(std::current_exception());
|
||||
recordRenderError(std::current_exception());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -191,7 +196,7 @@ public:
|
||||
if (last->second) {
|
||||
try { f(*last->second, ordinal); } catch (...) {
|
||||
if (failed) *failed = true;
|
||||
observer->onRenderError(std::current_exception());
|
||||
recordRenderError(std::current_exception());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -206,7 +211,7 @@ public:
|
||||
try {
|
||||
f(*pair.second, ordinal);
|
||||
} catch (...) {
|
||||
observer->onRenderError(std::current_exception());
|
||||
recordRenderError(std::current_exception());
|
||||
}
|
||||
}
|
||||
++ordinal;
|
||||
@@ -223,7 +228,7 @@ public:
|
||||
try {
|
||||
f(*it->second, ordinal);
|
||||
} catch (...) {
|
||||
observer->onRenderError(std::current_exception());
|
||||
recordRenderError(std::current_exception());
|
||||
}
|
||||
}
|
||||
++ordinal;
|
||||
@@ -241,7 +246,7 @@ public:
|
||||
try {
|
||||
f(*end->second, ordinal);
|
||||
} catch (...) {
|
||||
observer->onRenderError(std::current_exception());
|
||||
recordRenderError(std::current_exception());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -257,7 +262,7 @@ public:
|
||||
try {
|
||||
f(*it->second, ordinal);
|
||||
} catch (...) {
|
||||
observer->onRenderError(std::current_exception());
|
||||
recordRenderError(std::current_exception());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -270,7 +275,7 @@ public:
|
||||
try {
|
||||
f(*rit->second);
|
||||
} catch (...) {
|
||||
observer->onRenderError(std::current_exception());
|
||||
recordRenderError(std::current_exception());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -413,6 +418,7 @@ private:
|
||||
// Vectors with reserved capacity of layerImpls->size() to avoid
|
||||
// reallocation on each frame.
|
||||
std::vector<Immutable<style::LayerProperties>> filteredLayersForSource;
|
||||
uint64_t renderErrorRevision = 0;
|
||||
RenderLayerReferences orderedLayers;
|
||||
std::vector<StyleLayerDiagnostics> retainedStyleLayers;
|
||||
ResolvedRenderSegmentPlan renderSegmentPlan;
|
||||
|
||||
@@ -106,7 +106,8 @@ public:
|
||||
parameters->timePoint = timePoint;
|
||||
parameters->needsRepaint = true;
|
||||
parameters->placementChanged = false;
|
||||
parameters->symbolFadeChange = 0.0f;
|
||||
// Preserve the certified Full shader value. Zero would undo a settled
|
||||
// fade if any certified Symbol input is inspected on the fast frame.
|
||||
startTime = util::MonotonicTimer::now().count();
|
||||
}
|
||||
|
||||
|
||||
@@ -53,18 +53,31 @@ void Renderer::render(const std::shared_ptr<UpdateParameters>& updateParameters)
|
||||
auto& context = impl->backend.getContext();
|
||||
impl->dynamicTextureAtlas = std::make_unique<gfx::DynamicTextureAtlas>(context);
|
||||
}
|
||||
if (impl->tryRenderParticleFastFrame(updateParameters)) return;
|
||||
// A full createRenderTree may replace RenderLayer/RenderSource instances.
|
||||
// Drop the old prepared tree first so its reference-based render items can
|
||||
// never outlive the orchestrator objects they describe.
|
||||
impl->clearPreparedRenderTree();
|
||||
if (auto renderTree = impl->orchestrator.createRenderTree(updateParameters, impl->dynamicTextureAtlas)) {
|
||||
renderTree->prepare();
|
||||
impl->render(*renderTree, updateParameters, false);
|
||||
impl->commitPreparedRenderTree(std::move(renderTree), updateParameters);
|
||||
} else {
|
||||
try {
|
||||
if (impl->tryRenderParticleFastFrame(updateParameters)) return;
|
||||
// A full createRenderTree may replace RenderLayer/RenderSource instances.
|
||||
// Drop the old prepared tree first so its reference-based render items can
|
||||
// never outlive the orchestrator objects they describe.
|
||||
// Result evidence owns copied inputs/resources, not RenderLayer references.
|
||||
// Preserve it across ordinary Full replacement; resets still invalidate it.
|
||||
impl->clearPreparedRenderTree(false);
|
||||
if (auto renderTree = impl->orchestrator.createRenderTree(updateParameters, impl->dynamicTextureAtlas)) {
|
||||
renderTree->prepare();
|
||||
impl->render(*renderTree, updateParameters, false);
|
||||
impl->commitPreparedRenderTree(std::move(renderTree), updateParameters);
|
||||
} else {
|
||||
impl->clearPreparedRenderTree();
|
||||
if (impl->retainedRuntimeState) impl->retainedRuntimeState->completeFastGateAttempt("no-render-tree");
|
||||
}
|
||||
} catch (...) {
|
||||
// Upload/encode/present failures that escape the visitors must not leave
|
||||
// a previously valid cache or completed evidence available for retry.
|
||||
impl->clearPreparedRenderTree();
|
||||
if (impl->retainedRuntimeState) impl->retainedRuntimeState->completeFastGateAttempt("no-render-tree");
|
||||
impl->orchestrator.invalidateRetainedRenderResults();
|
||||
for (auto& cache : impl->retainedSegmentCaches) {
|
||||
if (cache) cache->invalidate("render-error");
|
||||
}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -131,11 +131,14 @@ void Renderer::Impl::onPostCompileShader(shaders::BuiltIn shaderID,
|
||||
void Renderer::Impl::onShaderCompileFailed(shaders::BuiltIn shaderID,
|
||||
gfx::Backend::Type type,
|
||||
const std::string& additionalDefines) {
|
||||
for (auto& certificate : retainedSymbolCertificates) certificate.invalidate();
|
||||
orchestrator.invalidateRetainedRenderResults();
|
||||
observer->onShaderCompileFailed(shaderID, type, additionalDefines);
|
||||
}
|
||||
|
||||
void Renderer::Impl::onRenderError(std::exception_ptr error) {
|
||||
observer->onRenderError(error);
|
||||
for (auto& certificate : retainedSymbolCertificates) certificate.invalidate();
|
||||
orchestrator.recordRenderError(error);
|
||||
}
|
||||
|
||||
void Renderer::Impl::setObserver(RendererObserver* observer_) {
|
||||
@@ -249,10 +252,23 @@ bool Renderer::Impl::tryRenderParticleFastFrame(const std::shared_ptr<UpdatePara
|
||||
const bool contentRevisionMatches =
|
||||
particlePreparedContentRevision == orchestrator.getRetainedUnderlayContentRevision();
|
||||
bool staticStable = plan.valid;
|
||||
const SymbolResultBinding fastSymbolBinding{
|
||||
.viewport = backend.getDefaultRenderable().getSize(),
|
||||
.camera = signature.cameraMatrix,
|
||||
.pixelRatio = signature.pixelRatio,
|
||||
.content = orchestrator.getRetainedUnderlayContentRevision(),
|
||||
.topology = retainedUnderlayTopologyTracker.current(),
|
||||
.config = plan.configRevision,
|
||||
.crossSourceCollisions = updateParameters->crossSourceCollisions,
|
||||
};
|
||||
for (const auto& segment : plan.segments) {
|
||||
if (segment.strategy == RenderSegmentStrategy::Retained) {
|
||||
staticStable = staticStable && orchestrator.retainedRangeIsStable(
|
||||
updateParameters->timePoint, segment.beginIndex, segment.endExclusive);
|
||||
if (orchestrator.retainedRangeHasPlacementCapableLayer(segment.beginIndex, segment.endExclusive)) {
|
||||
staticStable = staticStable && segment.segmentIndex < retainedSymbolCertificates.size() &&
|
||||
retainedSymbolCertificates[segment.segmentIndex].canFast(fastSymbolBinding);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,10 +285,9 @@ bool Renderer::Impl::tryRenderParticleFastFrame(const std::shared_ptr<UpdatePara
|
||||
.boundaryMatches = boundaryMatches,
|
||||
.topologyMatches = topologyMatches,
|
||||
.staticStable = staticStable,
|
||||
// placementChanged/symbolFadeChange belong to the previous prepared
|
||||
// full frame. The current controller transition is the authoritative
|
||||
// semantic state: while active we fail closed, and once settled the
|
||||
// old full-frame snapshot must not permanently block the fast path.
|
||||
// Scheduling staleness is independent of active fade. StaticStable
|
||||
// additionally requires the uploaded result certificate and a submitted
|
||||
// Reuse; all 24 gates retain their original decision/trace contract.
|
||||
.hasPlacementTransition = dynamicState.placementTransition,
|
||||
.hasPendingChanges = dynamicState.pendingChanges,
|
||||
.hasNonParticleTransition = dynamicState.blockingTransition,
|
||||
@@ -312,6 +327,7 @@ void Renderer::Impl::commitPreparedRenderTree(std::unique_ptr<RenderTree> render
|
||||
const auto& plan = orchestrator.getRenderSegmentPlan();
|
||||
const auto* continuous = singleContinuousSegment(plan);
|
||||
particlePreparedSignature = makeParticleFastPathUpdateSignature(*updateParameters);
|
||||
symbolCompletedSignature = particlePreparedSignature;
|
||||
particlePreparedContentRevision = orchestrator.getRetainedUnderlayContentRevision();
|
||||
particlePreparedTopologyRevision = retainedUnderlayTopologyTracker.current();
|
||||
particlePreparedStaticRevision = retainedUnderlayRevisionTracker.current();
|
||||
@@ -322,7 +338,11 @@ void Renderer::Impl::commitPreparedRenderTree(std::unique_ptr<RenderTree> render
|
||||
particlePreparedTree.replace(std::move(renderTree));
|
||||
}
|
||||
|
||||
void Renderer::Impl::clearPreparedRenderTree() {
|
||||
void Renderer::Impl::clearPreparedRenderTree(bool invalidateSymbolResults) {
|
||||
if (invalidateSymbolResults) {
|
||||
retainedSymbolCertificates.clear();
|
||||
symbolCompletedSignature.reset();
|
||||
}
|
||||
particlePreparedTree.reset();
|
||||
particlePreparedSignature.reset();
|
||||
particlePreparedContentRevision = 0;
|
||||
@@ -434,6 +454,7 @@ void Renderer::Impl::render(const RenderTree& renderTree,
|
||||
}
|
||||
|
||||
const auto& renderTreeParameters = renderTree.getParameters();
|
||||
const auto renderErrorRevisionAtStart = orchestrator.getRenderErrorRevision();
|
||||
staticData->has3D = renderTreeParameters.has3D;
|
||||
const Size renderableSize = backend.getDefaultRenderable().getSize();
|
||||
|
||||
@@ -538,6 +559,8 @@ void Renderer::Impl::render(const RenderTree& renderTree,
|
||||
retainedSegmentCaches.clear();
|
||||
retainedSegmentCaches.resize(segmentPlan.segments.size());
|
||||
retainedSegmentFingerprints.assign(segmentPlan.segments.size(), 0);
|
||||
retainedSymbolCertificates.clear();
|
||||
retainedSymbolCertificates.resize(segmentPlan.segments.size());
|
||||
bool continuousSeen = false;
|
||||
for (const auto& segment : segmentPlan.segments) {
|
||||
if (segment.strategy == RenderSegmentStrategy::Continuous) {
|
||||
@@ -595,21 +618,107 @@ void Renderer::Impl::render(const RenderTree& renderTree,
|
||||
}
|
||||
}
|
||||
}
|
||||
bool retainedStaticUsesPlacement = false;
|
||||
// Upload layer groups
|
||||
{
|
||||
const auto uploadPass = parameters.encoder->createUploadPass("layerGroup-upload",
|
||||
parameters.backend.getDefaultRenderable());
|
||||
#if !defined(NDEBUG)
|
||||
const auto debugGroup = uploadPass->createDebugGroup("layerGroup-upload");
|
||||
#endif
|
||||
|
||||
// Update the debug layer groups only on the full path. Debug rendering
|
||||
// is excluded by the fast-path gate.
|
||||
if (!particleFastFrame) orchestrator.updateDebugLayerGroups(renderTree, parameters);
|
||||
|
||||
// Tweakers are run in the upload pass so they can set up uniforms.
|
||||
parameters.currentLayer = 0;
|
||||
if (particleFastFrame && singleContinuous) {
|
||||
orchestrator.visitLayerGroupsInRange(
|
||||
singleContinuous->beginIndex, singleContinuous->endExclusive,
|
||||
[&](LayerGroupBase& layerGroup, size_t ordinal) {
|
||||
parameters.currentLayer = static_cast<uint32_t>(ordinal);
|
||||
layerGroup.runTweakers(renderTree, parameters);
|
||||
});
|
||||
} else {
|
||||
orchestrator.visitLayerGroups([&](LayerGroupBase& layerGroup) {
|
||||
layerGroup.runTweakers(renderTree, parameters);
|
||||
parameters.currentLayer++;
|
||||
});
|
||||
parameters.currentLayer = 0;
|
||||
orchestrator.visitDebugLayerGroups([&](LayerGroupBase& layerGroup) {
|
||||
layerGroup.runTweakers(renderTree, parameters);
|
||||
parameters.currentLayer++;
|
||||
});
|
||||
}
|
||||
|
||||
// Give the layers a chance to upload
|
||||
if (particleFastFrame && singleContinuous) {
|
||||
orchestrator.visitLayerGroupsInRange(
|
||||
singleContinuous->beginIndex, singleContinuous->endExclusive,
|
||||
[&](LayerGroupBase& layerGroup, size_t) { layerGroup.upload(*uploadPass); });
|
||||
} else {
|
||||
orchestrator.visitLayerGroups([&](LayerGroupBase& layerGroup) { layerGroup.upload(*uploadPass); });
|
||||
}
|
||||
|
||||
// Give the render targets a chance to upload
|
||||
orchestrator.visitRenderTargets([&](RenderTarget& renderTarget) { renderTarget.upload(*uploadPass); });
|
||||
|
||||
// Upload the Debug layer group
|
||||
if (!particleFastFrame) {
|
||||
orchestrator.visitDebugLayerGroups([&](LayerGroupBase& layerGroup) { layerGroup.upload(*uploadPass); });
|
||||
}
|
||||
}
|
||||
|
||||
if (retainedSymbolCertificates.size() != segmentPlan.segments.size()) {
|
||||
retainedSymbolCertificates.resize(segmentPlan.segments.size());
|
||||
}
|
||||
const SymbolResultBinding symbolBinding{
|
||||
.viewport = parameters.renderableSize,
|
||||
.camera = retainedCameraMatrix,
|
||||
.pixelRatio = updateParameters->pixelRatio,
|
||||
.content = orchestrator.getRetainedUnderlayContentRevision(),
|
||||
.topology = retainedTopologyRevision,
|
||||
.config = segmentPlan.configRevision,
|
||||
.crossSourceCollisions = updateParameters->crossSourceCollisions,
|
||||
};
|
||||
if (!particleFastFrame && symbolCompletedSignature &&
|
||||
!particleFastPathNonCameraInputsMatch(*symbolCompletedSignature,
|
||||
makeParticleFastPathUpdateSignature(*updateParameters))) {
|
||||
for (auto& certificate : retainedSymbolCertificates) certificate.invalidate();
|
||||
}
|
||||
std::vector<bool> symbolResultGates(segmentPlan.segments.size(), true);
|
||||
bool retainedPlacementStable = true;
|
||||
for (const auto& segment : segmentPlan.segments) {
|
||||
if (segment.strategy != RenderSegmentStrategy::Retained ||
|
||||
!orchestrator.retainedRangeHasPlacementCapableLayer(segment.beginIndex, segment.endExclusive)) continue;
|
||||
auto& certificate = retainedSymbolCertificates[segment.segmentIndex];
|
||||
if (particleFastFrame) {
|
||||
symbolResultGates[segment.segmentIndex] = certificate.canReuse(symbolBinding);
|
||||
} else {
|
||||
const auto source = orchestrator.retainedSegmentSourceState(segment.beginIndex, segment.endExclusive);
|
||||
const bool ready = renderTreeParameters.loaded && updateParameters->styleLoaded &&
|
||||
updateParameters->spriteLoaded && orchestrator.retainedGlobalResourcesReady() &&
|
||||
source.ready && source.stable && !state.isChanging() && !state.isGestureInProgress() &&
|
||||
!orchestrator.retainedPlacementTransitionActive(renderTreeParameters.timePoint) &&
|
||||
orchestrator.retainedSegmentIsStable(renderTreeParameters.timePoint,
|
||||
segment.beginIndex, segment.endExclusive) &&
|
||||
orchestrator.getRenderErrorRevision() == renderErrorRevisionAtStart;
|
||||
symbolResultGates[segment.segmentIndex] = certificate.observeFull(
|
||||
orchestrator.retainedSymbolRenderResult(segment.beginIndex, segment.endExclusive,
|
||||
parameters.symbolFadeChange, frameCount),
|
||||
symbolBinding, ready);
|
||||
}
|
||||
retainedPlacementStable = retainedPlacementStable && symbolResultGates[segment.segmentIndex];
|
||||
}
|
||||
bool retainedStaticStable = retainedMode;
|
||||
bool retainedUnsupportedStaticLayer = false;
|
||||
for (const auto& segment : segmentPlan.segments) {
|
||||
if (segment.strategy != RenderSegmentStrategy::Retained) continue;
|
||||
retainedStaticUsesPlacement = retainedStaticUsesPlacement ||
|
||||
orchestrator.retainedRangeUsesPlacement(segment.beginIndex, segment.endExclusive);
|
||||
retainedStaticStable = retainedStaticStable && orchestrator.retainedRangeIsStable(
|
||||
renderTreeParameters.timePoint, segment.beginIndex, segment.endExclusive);
|
||||
retainedUnsupportedStaticLayer = retainedUnsupportedStaticLayer ||
|
||||
orchestrator.retainedRangeHasUnsupportedLayer(segment.beginIndex, segment.endExclusive);
|
||||
}
|
||||
const bool retainedPlacementStable =
|
||||
!retainedStaticUsesPlacement ||
|
||||
(!renderTreeParameters.placementChanged && renderTreeParameters.symbolFadeChange == 0.0f);
|
||||
const bool placementTransitionActive = orchestrator.retainedPlacementTransitionActive(
|
||||
renderTreeParameters.timePoint);
|
||||
const auto retainedContentRevision = orchestrator.getRetainedUnderlayContentRevision();
|
||||
@@ -666,7 +775,7 @@ void Renderer::Impl::render(const RenderTree& renderTree,
|
||||
const auto sourceState = orchestrator.retainedSegmentSourceState(
|
||||
segment.beginIndex, segment.endExclusive);
|
||||
segmentSourceStates[segment.segmentIndex] = sourceState;
|
||||
const auto segmentContentRevision = sourceState.revision ^
|
||||
auto segmentContentRevision = sourceState.revision ^
|
||||
(fingerprint + 0x9e3779b97f4a7c15ULL + (sourceState.revision << 6U) +
|
||||
(sourceState.revision >> 2U)) ^
|
||||
static_cast<uint64_t>(updateParameters->pixelRatio * 1000000.0f);
|
||||
@@ -674,9 +783,12 @@ void Renderer::Impl::render(const RenderTree& renderTree,
|
||||
renderTreeParameters.timePoint, segment.beginIndex, segment.endExclusive);
|
||||
const bool placementCapable = orchestrator.retainedRangeHasPlacementCapableLayer(
|
||||
segment.beginIndex, segment.endExclusive);
|
||||
const bool segmentPlacementGatePassed = renderSegmentPlacementGatePassed(
|
||||
placementCapable, renderTreeParameters.placementChanged,
|
||||
renderTreeParameters.symbolFadeChange, placementTransitionActive);
|
||||
const bool segmentPlacementGatePassed = !placementCapable || symbolResultGates[segment.segmentIndex];
|
||||
if (placementCapable) {
|
||||
const auto revision = retainedSymbolCertificates[segment.segmentIndex].current();
|
||||
segmentContentRevision ^= revision + 0x9e3779b97f4a7c15ULL +
|
||||
(segmentContentRevision << 6U) + (segmentContentRevision >> 2U);
|
||||
}
|
||||
segmentPlacementGates[segment.segmentIndex] = segmentPlacementGatePassed;
|
||||
const bool segmentSupported = retainedMode && updateParameters->styleLoaded &&
|
||||
updateParameters->spriteLoaded && orchestrator.retainedGlobalResourcesReady() &&
|
||||
@@ -698,6 +810,12 @@ void Renderer::Impl::render(const RenderTree& renderTree,
|
||||
: renderTreeParameters.debugOptions != MapDebugOptions::NoDebug ? "debug-options"
|
||||
: updateParameters->mode != MapMode::Continuous ? "map-mode"
|
||||
: nullptr;
|
||||
// Exact Symbol proof also controls Full Reuse. A combined revision hash
|
||||
// cannot authorize reuse of a cache built for different result bytes.
|
||||
if (placementCapable && segmentPlacementGatePassed && cache->isValid() &&
|
||||
!retainedSymbolCertificates[segment.segmentIndex].candidateHasBuild(symbolBinding)) {
|
||||
cache->invalidate("symbol-result-mutation");
|
||||
}
|
||||
decision = cache->evaluate(segmentKeys[segment.segmentIndex],
|
||||
segmentContentRevision,
|
||||
segmentSupported,
|
||||
@@ -712,61 +830,16 @@ void Renderer::Impl::render(const RenderTree& renderTree,
|
||||
}
|
||||
}
|
||||
|
||||
// Upload layer groups
|
||||
{
|
||||
const auto uploadPass = parameters.encoder->createUploadPass("layerGroup-upload",
|
||||
parameters.backend.getDefaultRenderable());
|
||||
#if !defined(NDEBUG)
|
||||
const auto debugGroup = uploadPass->createDebugGroup("layerGroup-upload");
|
||||
#endif
|
||||
|
||||
// Update the debug layer groups only on the full path. Debug rendering
|
||||
// is excluded by the fast-path gate.
|
||||
if (!particleFastFrame) orchestrator.updateDebugLayerGroups(renderTree, parameters);
|
||||
|
||||
// Tweakers are run in the upload pass so they can set up uniforms.
|
||||
parameters.currentLayer = 0;
|
||||
if (particleFastFrame && singleContinuous) {
|
||||
orchestrator.visitLayerGroupsInRange(
|
||||
singleContinuous->beginIndex, singleContinuous->endExclusive,
|
||||
[&](LayerGroupBase& layerGroup, size_t ordinal) {
|
||||
parameters.currentLayer = static_cast<uint32_t>(ordinal);
|
||||
layerGroup.runTweakers(renderTree, parameters);
|
||||
});
|
||||
} else {
|
||||
orchestrator.visitLayerGroups([&](LayerGroupBase& layerGroup) {
|
||||
layerGroup.runTweakers(renderTree, parameters);
|
||||
parameters.currentLayer++;
|
||||
});
|
||||
parameters.currentLayer = 0;
|
||||
orchestrator.visitDebugLayerGroups([&](LayerGroupBase& layerGroup) {
|
||||
layerGroup.runTweakers(renderTree, parameters);
|
||||
parameters.currentLayer++;
|
||||
});
|
||||
}
|
||||
|
||||
// Give the layers a chance to upload
|
||||
if (particleFastFrame && singleContinuous) {
|
||||
orchestrator.visitLayerGroupsInRange(
|
||||
singleContinuous->beginIndex, singleContinuous->endExclusive,
|
||||
[&](LayerGroupBase& layerGroup, size_t) { layerGroup.upload(*uploadPass); });
|
||||
} else {
|
||||
orchestrator.visitLayerGroups([&](LayerGroupBase& layerGroup) { layerGroup.upload(*uploadPass); });
|
||||
}
|
||||
|
||||
// Give the render targets a chance to upload
|
||||
orchestrator.visitRenderTargets([&](RenderTarget& renderTarget) { renderTarget.upload(*uploadPass); });
|
||||
|
||||
// Upload the Debug layer group
|
||||
if (!particleFastFrame) {
|
||||
orchestrator.visitDebugLayerGroups([&](LayerGroupBase& layerGroup) { layerGroup.upload(*uploadPass); });
|
||||
}
|
||||
if (retainedMode) {
|
||||
for (const auto& segment : segmentPlan.segments) {
|
||||
if (segment.strategy == RenderSegmentStrategy::Retained &&
|
||||
wantsResources(segmentDecisions[segment.segmentIndex])) {
|
||||
retainedSegmentCaches[segment.segmentIndex]->upload(*uploadPass);
|
||||
}
|
||||
// Admit and upload compositors only after the Full Symbol inputs have been
|
||||
// updated, tweaked and uploaded. Build has no dependency on an old cache.
|
||||
if (retainedMode) {
|
||||
const auto uploadPass = parameters.encoder->createUploadPass(
|
||||
"retained-composite-upload", parameters.backend.getDefaultRenderable());
|
||||
for (const auto& segment : segmentPlan.segments) {
|
||||
if (segment.strategy == RenderSegmentStrategy::Retained &&
|
||||
wantsResources(segmentDecisions[segment.segmentIndex])) {
|
||||
retainedSegmentCaches[segment.segmentIndex]->upload(*uploadPass);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -833,7 +906,8 @@ void Renderer::Impl::render(const RenderTree& renderTree,
|
||||
const auto buildRetainedRange = [&](RetainedUnderlayCache& cache,
|
||||
RetainedUnderlayDecision& decision,
|
||||
int32_t begin,
|
||||
int32_t endExclusive) {
|
||||
int32_t endExclusive,
|
||||
uint32_t segmentIndex) {
|
||||
if (decision != RetainedUnderlayDecision::Build || begin >= endExclusive) return;
|
||||
bool drawFailed = false;
|
||||
bool globalUniformsBound = false;
|
||||
@@ -870,9 +944,14 @@ void Renderer::Impl::render(const RenderTree& renderTree,
|
||||
if (globalUniformsBound && parameters.renderPass) {
|
||||
context.unbindGlobalUniformBuffers(*parameters.renderPass);
|
||||
}
|
||||
observer->onRenderError(error);
|
||||
onRenderError(error);
|
||||
parameters.renderPass.reset();
|
||||
}
|
||||
if (orchestrator.retainedRangeHasPlacementCapableLayer(begin, endExclusive)) {
|
||||
drawFailed = drawFailed || orchestrator.getRetainedUnderlayContentRevision() != symbolBinding.content ||
|
||||
!retainedSymbolCertificates[segmentIndex].matchesCandidate(
|
||||
orchestrator.retainedSymbolRenderResult(begin, endExclusive, parameters.symbolFadeChange, frameCount));
|
||||
}
|
||||
if (drawFailed) {
|
||||
cache.markBuildFailed("resource-or-compositor");
|
||||
decision = RetainedUnderlayDecision::Fallback;
|
||||
@@ -990,12 +1069,23 @@ void Renderer::Impl::render(const RenderTree& renderTree,
|
||||
drawable3DPass();
|
||||
}
|
||||
drawableTargetsPass();
|
||||
if (orchestrator.getRetainedUnderlayContentRevision() != symbolBinding.content ||
|
||||
orchestrator.getRenderErrorRevision() != renderErrorRevisionAtStart) {
|
||||
particlePreparedSceneSupported = false;
|
||||
for (const auto& segment : segmentPlan.segments) {
|
||||
if (segment.strategy != RenderSegmentStrategy::Retained) continue;
|
||||
retainedSegmentCaches[segment.segmentIndex]->invalidate("content-mutation");
|
||||
retainedSymbolCertificates[segment.segmentIndex].invalidate();
|
||||
segmentDecisions[segment.segmentIndex] = RetainedUnderlayDecision::Fallback;
|
||||
}
|
||||
}
|
||||
for (const auto& segment : segmentPlan.segments) {
|
||||
if (segment.strategy == RenderSegmentStrategy::Retained) {
|
||||
buildRetainedRange(*retainedSegmentCaches[segment.segmentIndex],
|
||||
segmentDecisions[segment.segmentIndex],
|
||||
segment.beginIndex,
|
||||
segment.endExclusive);
|
||||
segment.endExclusive,
|
||||
segment.segmentIndex);
|
||||
}
|
||||
}
|
||||
if (retainedRuntimeState) {
|
||||
@@ -1072,6 +1162,24 @@ void Renderer::Impl::render(const RenderTree& renderTree,
|
||||
parameters.encoder.reset();
|
||||
context.endFrame();
|
||||
|
||||
if (!particleFastFrame) {
|
||||
auto completionBinding = symbolBinding;
|
||||
completionBinding.content = orchestrator.getRetainedUnderlayContentRevision();
|
||||
const bool submitted = orchestrator.getRenderErrorRevision() == renderErrorRevisionAtStart;
|
||||
for (const auto& segment : segmentPlan.segments) {
|
||||
if (segment.strategy != RenderSegmentStrategy::Retained ||
|
||||
!orchestrator.retainedRangeHasPlacementCapableLayer(segment.beginIndex, segment.endExclusive)) continue;
|
||||
const auto index = segment.segmentIndex;
|
||||
const bool resultUnchanged = retainedSymbolCertificates[index].matchesCandidate(
|
||||
orchestrator.retainedSymbolRenderResult(segment.beginIndex, segment.endExclusive,
|
||||
parameters.symbolFadeChange, frameCount));
|
||||
retainedSymbolCertificates[index].completeFull(
|
||||
completionBinding, submitted && resultUnchanged,
|
||||
segmentDecisions[index] == RetainedUnderlayDecision::Build && retainedSegmentCaches[index]->isValid(),
|
||||
segmentDecisions[index] == RetainedUnderlayDecision::Reuse && retainedSegmentCaches[index]->isValid());
|
||||
}
|
||||
}
|
||||
|
||||
#if MLN_RENDER_BACKEND_METAL
|
||||
if constexpr (EnableMetalCapture) {
|
||||
if (commandCaptureScope) {
|
||||
|
||||
@@ -49,7 +49,7 @@ private:
|
||||
void synchronizeRetainedUnderlayState(const std::shared_ptr<RetainedUnderlayRuntimeState>&);
|
||||
bool tryRenderParticleFastFrame(const std::shared_ptr<UpdateParameters>&);
|
||||
void commitPreparedRenderTree(std::unique_ptr<RenderTree>, const std::shared_ptr<UpdateParameters>&);
|
||||
void clearPreparedRenderTree();
|
||||
void clearPreparedRenderTree(bool invalidateSymbolResults = true);
|
||||
|
||||
void reduceMemoryUse();
|
||||
|
||||
@@ -64,6 +64,7 @@ private:
|
||||
std::unique_ptr<RenderStaticData> staticData;
|
||||
std::vector<std::unique_ptr<RetainedUnderlayCache>> retainedSegmentCaches;
|
||||
std::vector<uint64_t> retainedSegmentFingerprints;
|
||||
std::vector<SymbolResultCertificate> retainedSymbolCertificates;
|
||||
std::shared_ptr<RetainedUnderlayRuntimeState> retainedRuntimeState;
|
||||
uint64_t retainedCacheTopologyRevision = 0;
|
||||
RetainedUnderlayRevisionTracker retainedUnderlayRevisionTracker;
|
||||
@@ -107,6 +108,7 @@ private:
|
||||
const ParticleFastPathUpdateSignature&);
|
||||
|
||||
std::optional<ParticleFastPathUpdateSignature> particlePreparedSignature;
|
||||
std::optional<ParticleFastPathUpdateSignature> symbolCompletedSignature;
|
||||
uint64_t particlePreparedContentRevision = 0;
|
||||
uint64_t particlePreparedTopologyRevision = 0;
|
||||
uint64_t particlePreparedStaticRevision = 0;
|
||||
|
||||
51
src/mln/renderer/symbol_render_inputs.hpp
Normal file
51
src/mln/renderer/symbol_render_inputs.hpp
Normal file
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include <mln/renderer/symbol_render_result.hpp>
|
||||
#include <mln/gfx/vertex_attribute.hpp>
|
||||
#include <mln/gfx/vertex_vector.hpp>
|
||||
|
||||
namespace mln {
|
||||
|
||||
inline void appendSymbolAttributes(SymbolRenderResult& result, const std::shared_ptr<gfx::VertexAttributeArray>& attributes) {
|
||||
result.append(static_cast<bool>(attributes));
|
||||
if (!attributes) return;
|
||||
result.append(attributes->allocatedSize());
|
||||
for (std::size_t id = 0; id < attributes->allocatedSize(); ++id) {
|
||||
const auto& attr = attributes->get(id);
|
||||
result.append(static_cast<bool>(attr));
|
||||
if (!attr) continue;
|
||||
result.append(attr->getIndex());
|
||||
result.append(attr->getDataType());
|
||||
result.append(attr->getCount());
|
||||
result.append(attr->getStride());
|
||||
result.append(attr->getSharedType());
|
||||
result.append(attr->getSharedOffset());
|
||||
result.append(attr->getSharedVertexOffset());
|
||||
result.append(attr->getSharedStride());
|
||||
const auto& shared = attr->getSharedRawData();
|
||||
if (shared) {
|
||||
// Collection happens after upload; readable CPU bytes alone cannot
|
||||
// certify an attribute that never obtained a GPU backing buffer.
|
||||
if (!shared->getBuffer()) result.complete = false;
|
||||
// Released vectors are immutable by the producer contract; retain
|
||||
// their identity and GPU backing to prevent pointer ABA. Mutable
|
||||
// vectors (placement, variable anchors, sort and paint) use bytes.
|
||||
result.append(shared->isReleased());
|
||||
if (shared->isReleased()) {
|
||||
result.retain(shared);
|
||||
result.append(reinterpret_cast<std::uintptr_t>(shared->getBuffer()));
|
||||
if (!shared->getBuffer()) result.complete = false;
|
||||
} else {
|
||||
result.append(shared->getRawSize());
|
||||
result.appendBlock(shared->getRawData(), shared->getRawSize() * shared->getRawCount());
|
||||
}
|
||||
} else {
|
||||
const auto& raw = attr->getRawData();
|
||||
result.appendBlock(raw.data(), raw.size());
|
||||
// A non-shared attribute without readable bytes is not a proof.
|
||||
if (attr->getCount() && raw.empty()) result.complete = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mln
|
||||
141
src/mln/renderer/symbol_render_result.hpp
Normal file
141
src/mln/renderer/symbol_render_result.hpp
Normal file
@@ -0,0 +1,141 @@
|
||||
#pragma once
|
||||
|
||||
#include <mln/util/mat4.hpp>
|
||||
#include <mln/util/size.hpp>
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace mln {
|
||||
|
||||
// Internal CPU evidence for the inputs actually consumed by the Symbol shaders.
|
||||
// This is exact byte comparison, not a hash of placed/unplaced booleans.
|
||||
struct SymbolRenderResult {
|
||||
std::vector<uint8_t> bytes;
|
||||
std::vector<std::shared_ptr<const void>> resources;
|
||||
bool complete = true;
|
||||
static constexpr std::size_t maxBytes = 16 * 1024 * 1024;
|
||||
|
||||
void appendBytes(const void* data, std::size_t size) {
|
||||
if (!complete || size > maxBytes - bytes.size() || (size && !data)) {
|
||||
complete = false;
|
||||
return;
|
||||
}
|
||||
if (size) {
|
||||
const auto* begin = static_cast<const uint8_t*>(data);
|
||||
bytes.insert(bytes.end(), begin, begin + size);
|
||||
}
|
||||
}
|
||||
template <class T> void append(const T& value) {
|
||||
static_assert(std::is_trivially_copyable_v<T>);
|
||||
appendBytes(&value, sizeof(value));
|
||||
}
|
||||
void appendBlock(const void* data, std::size_t size) {
|
||||
append(size);
|
||||
appendBytes(data, size);
|
||||
}
|
||||
template <class T> void retain(const std::shared_ptr<T>& resource) {
|
||||
append(reinterpret_cast<std::uintptr_t>(resource.get()));
|
||||
if (resource) resources.emplace_back(resource);
|
||||
}
|
||||
void appendResult(const SymbolRenderResult& result) {
|
||||
complete = complete && result.complete;
|
||||
appendBlock(result.bytes.data(), result.bytes.size());
|
||||
resources.insert(resources.end(), result.resources.begin(), result.resources.end());
|
||||
}
|
||||
};
|
||||
|
||||
// GL and Metal unpack the low bit as the fade direction and clamp the
|
||||
// remaining seven bits / 127 + signed fadeChange to [0,1]. An endpoint in
|
||||
// the target direction remains constant as time advances. A stale controller
|
||||
// says "refresh placement", not "the shader is changing opacity".
|
||||
inline bool symbolOpacityIsSettled(float packed, float fadeChange) {
|
||||
if (!std::isfinite(packed) || !std::isfinite(fadeChange) || fadeChange < 0 ||
|
||||
packed < 0 || packed > 255 || std::floor(packed) != packed) return false;
|
||||
const auto value = static_cast<unsigned>(packed);
|
||||
const float opacity = static_cast<float>(value / 2) / 127.0f;
|
||||
return (value & 1U) ? opacity + fadeChange >= 1.0f : opacity - fadeChange <= 0.0f;
|
||||
}
|
||||
|
||||
struct SymbolResultBinding {
|
||||
Size viewport;
|
||||
mat4 camera{};
|
||||
float pixelRatio = 1;
|
||||
uint64_t content = 0;
|
||||
uint64_t topology = 0;
|
||||
uint64_t config = 0;
|
||||
bool crossSourceCollisions = true;
|
||||
bool operator==(const SymbolResultBinding&) const = default;
|
||||
};
|
||||
|
||||
// Build admission is independent of a previous cache. It requires two
|
||||
// completed Full observations of the same settled, uploaded result. Reuse
|
||||
// additionally requires a Build submitted with this exact result revision.
|
||||
class SymbolResultCertificate {
|
||||
public:
|
||||
bool observeFull(SymbolRenderResult result, const SymbolResultBinding& binding, bool ready) {
|
||||
inFlight = true;
|
||||
const bool changed = !candidateBinding || *candidateBinding != binding ||
|
||||
candidate.bytes != result.bytes || !ready || !result.complete;
|
||||
if (changed) {
|
||||
++revision;
|
||||
builtRevision = 0;
|
||||
}
|
||||
candidate = std::move(result);
|
||||
candidateBinding = binding;
|
||||
candidateReady = ready && candidate.complete;
|
||||
stable = candidateReady && completedBinding && *completedBinding == binding &&
|
||||
completed.bytes == candidate.bytes;
|
||||
return stable;
|
||||
}
|
||||
void completeFull(const SymbolResultBinding& binding, bool submitted, bool built, bool reused = false) {
|
||||
if (!submitted || !candidateReady || !candidateBinding || *candidateBinding != binding) {
|
||||
invalidate();
|
||||
return;
|
||||
}
|
||||
completed = candidate;
|
||||
completedBinding = binding;
|
||||
inFlight = false;
|
||||
if (stable && built) {
|
||||
builtRevision = revision;
|
||||
reuseSubmitted = false;
|
||||
}
|
||||
if (stable && reused && builtRevision == revision) reuseSubmitted = true;
|
||||
}
|
||||
bool canReuse(const SymbolResultBinding& binding) const {
|
||||
return !inFlight && stable && builtRevision == revision && builtRevision != 0 &&
|
||||
completedBinding && *completedBinding == binding;
|
||||
}
|
||||
bool canFast(const SymbolResultBinding& binding) const { return canReuse(binding) && reuseSubmitted; }
|
||||
bool candidateHasBuild(const SymbolResultBinding& binding) const {
|
||||
return candidateReady && stable && builtRevision != 0 && builtRevision == revision &&
|
||||
candidateBinding && *candidateBinding == binding;
|
||||
}
|
||||
bool matchesCandidate(const SymbolRenderResult& result) const {
|
||||
return candidateReady && result.complete && candidate.bytes == result.bytes;
|
||||
}
|
||||
uint64_t current() const { return revision; }
|
||||
void invalidate() {
|
||||
++revision;
|
||||
stable = candidateReady = false;
|
||||
inFlight = reuseSubmitted = false;
|
||||
builtRevision = 0;
|
||||
completedBinding.reset();
|
||||
candidateBinding.reset();
|
||||
candidate = {};
|
||||
completed = {};
|
||||
}
|
||||
|
||||
private:
|
||||
SymbolRenderResult candidate, completed;
|
||||
std::optional<SymbolResultBinding> candidateBinding, completedBinding;
|
||||
uint64_t revision = 1, builtRevision = 0;
|
||||
bool candidateReady = false, stable = false;
|
||||
bool inFlight = false, reuseSubmitted = false;
|
||||
};
|
||||
|
||||
} // namespace mln
|
||||
51
src/mln/renderer/symbol_shader_provenance.hpp
Normal file
51
src/mln/renderer/symbol_shader_provenance.hpp
Normal file
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include <mln/shaders/shader_program_base.hpp>
|
||||
#include <algorithm>
|
||||
#include <mutex>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace mln {
|
||||
|
||||
// No public shader API/ABI changes. Only compilation sites that verify the
|
||||
// original Symbol source can register provenance. Names/types alone cannot
|
||||
// certify a replacement/custom shader with a different fade contract.
|
||||
class SymbolShaderProvenance {
|
||||
public:
|
||||
static SymbolShaderProvenance& get() { static SymbolShaderProvenance value; return value; }
|
||||
void compiled(const std::shared_ptr<gfx::ShaderProgramBase>& shader) {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
std::erase_if(shared, [](const auto& item) { return item.expired(); });
|
||||
shared.emplace_back(shader);
|
||||
}
|
||||
void compiledOwned(const gfx::ShaderProgramBase& shader) {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
owned.push_back(shader.getID().id());
|
||||
}
|
||||
void destroyedOwned(const gfx::ShaderProgramBase& shader) {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
std::erase(owned, shader.getID().id());
|
||||
}
|
||||
bool supports(const gfx::ShaderProgramBase& shader) const {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
if (std::find(owned.begin(), owned.end(), shader.getID().id()) != owned.end()) return true;
|
||||
return std::any_of(shared.begin(), shared.end(), [&](const auto& item) {
|
||||
const auto program = item.lock();
|
||||
return program && program.get() == &shader;
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::mutex mutex;
|
||||
std::vector<std::weak_ptr<gfx::ShaderProgramBase>> shared;
|
||||
std::vector<int64_t> owned;
|
||||
};
|
||||
|
||||
inline bool symbolUniformDefine(std::string_view name) {
|
||||
return name == "HAS_UNIFORM_u_opacity" || name == "HAS_UNIFORM_u_fill_color" ||
|
||||
name == "HAS_UNIFORM_u_halo_color" || name == "HAS_UNIFORM_u_halo_width" ||
|
||||
name == "HAS_UNIFORM_u_halo_blur" || name == "HAS_UNIFORM_u_sorted_instance";
|
||||
}
|
||||
|
||||
} // namespace mln
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <mln/platform/gl_functions.hpp>
|
||||
#include <mln/shaders/program_parameters.hpp>
|
||||
#include <mln/shaders/shader_manifest.hpp>
|
||||
#include <mln/renderer/symbol_shader_provenance.hpp>
|
||||
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
@@ -177,7 +178,31 @@ std::shared_ptr<ShaderProgramGL> ShaderProgramGL::create(
|
||||
addAttr(attrs, attributesInfo[location].id, location, length, size, glType);
|
||||
}
|
||||
|
||||
return std::make_shared<ShaderProgramGL>(std::move(program), std::move(attrs), std::move(samplerLocations));
|
||||
auto result = std::make_shared<ShaderProgramGL>(std::move(program), std::move(attrs), std::move(samplerLocations));
|
||||
bool originalSymbol = false;
|
||||
const auto matches = [&]<shaders::BuiltIn ID>() {
|
||||
using Source = shaders::ShaderSource<ID, gfx::Backend::Type::OpenGL>;
|
||||
return vertexSource == Source::vertex && fragmentSource == Source::fragment;
|
||||
};
|
||||
switch (programParameters.getProgramType()) {
|
||||
case shaders::BuiltIn::SymbolIconShader:
|
||||
originalSymbol = matches.template operator()<shaders::BuiltIn::SymbolIconShader>(); break;
|
||||
case shaders::BuiltIn::SymbolSDFShader:
|
||||
originalSymbol = matches.template operator()<shaders::BuiltIn::SymbolSDFShader>(); break;
|
||||
case shaders::BuiltIn::SymbolTextAndIconShader:
|
||||
originalSymbol = matches.template operator()<shaders::BuiltIn::SymbolTextAndIconShader>(); break;
|
||||
default: break;
|
||||
}
|
||||
std::size_t offset = 0;
|
||||
while (originalSymbol && offset < additionalDefines.size()) {
|
||||
const auto end = additionalDefines.find('\n', offset);
|
||||
const std::string_view line(additionalDefines.data() + offset,
|
||||
(end == std::string::npos ? additionalDefines.size() : end) - offset);
|
||||
originalSymbol = line.starts_with("#define ") && symbolUniformDefine(line.substr(8));
|
||||
offset = end == std::string::npos ? additionalDefines.size() : end + 1;
|
||||
}
|
||||
if (originalSymbol) SymbolShaderProvenance::get().compiled(result);
|
||||
return result;
|
||||
} catch (const std::exception& e) {
|
||||
context.getObserver().onShaderCompileFailed(
|
||||
programParameters.getProgramType(), gfx::Backend::Type::OpenGL, additionalDefines);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <mln/mtl/vertex_attribute.hpp>
|
||||
#include <mln/shaders/program_parameters.hpp>
|
||||
#include <mln/shaders/shader_manifest.hpp>
|
||||
#include <mln/renderer/symbol_shader_provenance.hpp>
|
||||
#include <mln/util/logging.hpp>
|
||||
#include <mln/util/hash.hpp>
|
||||
|
||||
@@ -80,7 +81,9 @@ ShaderProgram::ShaderProgram(std::string name, RendererBackend& backend_, MTLFun
|
||||
vertexFunction(std::move(vert)),
|
||||
fragmentFunction(std::move(frag)) {}
|
||||
|
||||
ShaderProgram::~ShaderProgram() noexcept = default;
|
||||
ShaderProgram::~ShaderProgram() noexcept {
|
||||
SymbolShaderProvenance::get().destroyedOwned(*this);
|
||||
}
|
||||
|
||||
MTLRenderPipelineStatePtr ShaderProgram::getRenderPipelineState(const gfx::Renderable& renderable,
|
||||
const MTLVertexDescriptorPtr& vertexDescriptor,
|
||||
|
||||
@@ -175,6 +175,10 @@ bool PlacementController::hasTransitions(TimePoint now) const {
|
||||
return placement->hasTransitions(now);
|
||||
}
|
||||
|
||||
bool PlacementController::hasActiveSymbolFade(TimePoint now) const {
|
||||
return placement->transitionsEnabled() && placement->hasTransitions(now);
|
||||
}
|
||||
|
||||
// Placement implementation
|
||||
|
||||
Placement::Placement(std::shared_ptr<const UpdateParameters> updateParameters_,
|
||||
|
||||
@@ -102,6 +102,8 @@ public:
|
||||
void setPlacementStale() { stale = true; }
|
||||
bool placementIsRecent(TimePoint now, float zoom, std::optional<Duration> periodOverride = std::nullopt) const;
|
||||
bool hasTransitions(TimePoint now) const;
|
||||
// Cache result stability excludes stale's scheduling-only refresh request.
|
||||
bool hasActiveSymbolFade(TimePoint now) const;
|
||||
|
||||
private:
|
||||
Immutable<Placement> placement;
|
||||
|
||||
Reference in New Issue
Block a user