feat: add retained underlay revision invalidation
This commit is contained in:
144
docs/40_任务/STEP-089_静态渲染Revision与Invalidation基础层_Codex任务_v1.md
Normal file
144
docs/40_任务/STEP-089_静态渲染Revision与Invalidation基础层_Codex任务_v1.md
Normal file
@@ -0,0 +1,144 @@
|
||||
# STEP-089 静态渲染 Revision 与 Invalidation 基础层 Codex 任务 v1
|
||||
|
||||
## 目标
|
||||
|
||||
为 STEP-090 Static CPU Fast Path 建立可靠、可验证的静态状态失效判据。
|
||||
|
||||
STEP-089 不负责跳过 static source/tile/render-tree/prepare,也不负责降低 CPU;只建立“静态前缀当前是否仍可安全复用”的 revision / invalidation 基础,确保后续 fast path 不会复用过期静态状态。
|
||||
|
||||
当前基线:
|
||||
|
||||
- branch: `particle/main`
|
||||
- baseline HEAD: `07a17b4c701cd22c8e8b516ad84f4044f291f9fa`
|
||||
- STEP-088 retained underlay 已完成:
|
||||
- GPU static draw cache
|
||||
- map-scoped runtime Cache ON/OFF
|
||||
- Release 最小 diagnostics
|
||||
- Android/iOS/RN runtime API
|
||||
- Android NavSea 真实 PBF 性能场景
|
||||
- 实测证明主要剩余成本是 Particle continuous repaint 下 PBF/vector/static renderer pipeline 被重复 processing。
|
||||
- STEP-090 才负责 CPU fast path。
|
||||
|
||||
## 必做项
|
||||
|
||||
- [ ] 引入单调递增的 static/retained revision 机制,能表达“静态前缀相关输入已变化”。
|
||||
- [ ] retained cache build 时记录所依据的 revision。
|
||||
- [ ] reuse 前必须确认当前 revision 与 build revision 一致。
|
||||
- [ ] 下列变化必须使 revision 前进或明确 invalidate:
|
||||
- [ ] camera / transform
|
||||
- [ ] viewport / size / pixel ratio
|
||||
- [ ] style reload / style mutation
|
||||
- [ ] layer add/remove/reorder/property mutation
|
||||
- [ ] source add/remove/change
|
||||
- [ ] tile / drawable 内容更新
|
||||
- [ ] image / sprite / glyph 变化
|
||||
- [ ] placement / symbol fade 状态变化
|
||||
- [ ] render context / surface / backend resource reset
|
||||
- [ ] debug render options
|
||||
- [ ] terrain / 3D / custom layer 等当前不能安全证明静态的场景
|
||||
- [ ] symbol fade / placement transition 活跃时不得复用 stale static cache。
|
||||
- [ ] unsupported / uncertain 情况继续 fail closed 到 full renderer。
|
||||
- [ ] Cache OFF/ON runtime 行为保持不变。
|
||||
- [ ] Release diagnostics 保持低成本;允许新增只读 revision 字段,但禁止 per-frame Release 日志。
|
||||
- [ ] Debug 日志可增加 revision / invalidation reason,便于 RDC 验证。
|
||||
- [ ] 原有 LayerGroup ordinal、dynamic boundary、transparent underlay、legacy ordering 语义不得回退。
|
||||
- [ ] 不实现 STEP-090 的 static CPU processing skip。
|
||||
- [ ] 不新增 React 管理的 renderer cache。
|
||||
- [ ] 不把 provider/network/NavSea Store 逻辑引入 MapLibre Core。
|
||||
- [ ] 不使用 Jest。
|
||||
|
||||
## Revision 设计约束
|
||||
|
||||
优先采用单调 `uint64_t` revision / generation:
|
||||
|
||||
- revision 只增不减;
|
||||
- 不依赖 wall clock;
|
||||
- 不要求每种事件都维护独立缓存副本;
|
||||
- 可以保留 reason/category 作为诊断,但 correctness 依赖 revision,而不是字符串 reason;
|
||||
- revision wraparound 可按现实上不可达处理,但类型必须明确。
|
||||
|
||||
建议最少区分:
|
||||
|
||||
- 当前 static revision;
|
||||
- retained underlay build revision;
|
||||
|
||||
如现有 Renderer/RenderOrchestrator 的生命周期更适合其他命名,可调整,但不要把模型复杂化。
|
||||
|
||||
## Invalidation 范围原则
|
||||
|
||||
STEP-089 首选“安全偏保守”。
|
||||
|
||||
如果某类状态变化是否会影响 static prefix 无法可靠判断:
|
||||
|
||||
- 直接 bump revision / invalidate;
|
||||
- 不做复杂局部 dependency graph;
|
||||
- 不为了提高 hit rate 提前实现 STEP-090 级别的细粒度 source/layer dependency tracking。
|
||||
|
||||
目标是让 STEP-090 有一个可信的 fast-path gate,而不是在 089 追求最高 cache reuse。
|
||||
|
||||
## Placement / Symbol 特别规则
|
||||
|
||||
已有 Android 真机证据:
|
||||
|
||||
- Particle 放到全部 63 层上方时,symbol-heavy prefix 会出现持续 `static-placement` / `static-transition`。
|
||||
- BEFORE_SYMBOL_CLUSTER(fill/line 在下,symbol cluster 在上)是目前最稳定边界。
|
||||
|
||||
因此:
|
||||
|
||||
- placement/fade active 必须阻止旧 static state 被错误复用;
|
||||
- fade/placement 稳定后允许重新 build 并记录新 revision;
|
||||
- 不得简单忽略 symbol placement 来提高 TOP 场景 reuse。
|
||||
|
||||
## Runtime API / Diagnostics
|
||||
|
||||
保持现有:
|
||||
|
||||
- `setRetainedUnderlayEnabled`
|
||||
- `getRetainedUnderlayDiagnostics`
|
||||
|
||||
允许在 diagnostics 增加:
|
||||
|
||||
- `staticRevision`
|
||||
- `buildRevision`
|
||||
|
||||
如增加,Android/iOS/RN 三端需一致;如果为了控制 STEP-089 范围决定暂不公开,也至少在 Debug Native 日志中可观察。
|
||||
|
||||
不要新增第二套 runtime module / map registry。
|
||||
|
||||
## 测试要求
|
||||
|
||||
新增/扩展 Native directed tests,至少覆盖:
|
||||
|
||||
1. revision 单调递增;
|
||||
2. build revision 与 current revision 相等时允许 reuse;
|
||||
3. camera/viewport/style/source 变化后 revision 改变并禁止旧 cache reuse;
|
||||
4. placement/fade active 时不 reuse stale cache;
|
||||
5. Cache OFF -> invalidate;重新 ON -> 下一次重新 build;
|
||||
6. unsupported / resource failure 保持安全 fallback;
|
||||
7. 原有 RetainedDynamicBoundary / LayerGroupRange / RetainedUnderlayCache 测试继续通过。
|
||||
|
||||
不要求 Jest。
|
||||
|
||||
## Codex 执行规则
|
||||
|
||||
- 先完整阅读本任务、AGENTS.md、STEP-088 v3 Step/报告,以及 retained underlay 当前实现。
|
||||
- 不需要提交实施计划。
|
||||
- 直接实现。
|
||||
- 不运行 Gradle / Android build / ADB / Xcode。
|
||||
- 不运行大型 noisy build/test;编译和最终验证由 RDC 负责。
|
||||
- 可以做必要的静态代码检查,但不要替代 RDC 编译。
|
||||
- 不 commit/push,等 RDC 验证后统一收尾。
|
||||
- 不触碰用户既有无关 dirty/untracked。
|
||||
- 不使用 worktree。
|
||||
- 无法完成的项明确说明,不得静默跳过。
|
||||
|
||||
## 完成时必须报告
|
||||
|
||||
逐项按“已完成 / 未完成 / 无法验证”报告上述必做项,并列出:
|
||||
|
||||
- 修改文件;
|
||||
- revision 的实际所有者与生命周期;
|
||||
- 每类 invalidation 在哪里触发;
|
||||
- 新增测试名;
|
||||
- RDC 应执行的精确 OpenGL / Metal / Android Validation 命令;
|
||||
- 明确说明没有实现 STEP-090 fast path。
|
||||
117
docs/50_Step记录/STEP-089_静态渲染Revision与Invalidation基础层_v1.md
Normal file
117
docs/50_Step记录/STEP-089_静态渲染Revision与Invalidation基础层_v1.md
Normal file
@@ -0,0 +1,117 @@
|
||||
# STEP-089 静态渲染 Revision 与 Invalidation 基础层 v1
|
||||
|
||||
## 状态
|
||||
|
||||
**CLOSED / PASS**
|
||||
|
||||
STEP-089 已完成 retained underlay 的静态状态 revision / invalidation 基础层,为 STEP-090 Static CPU Fast Path 提供可靠的安全门控。
|
||||
|
||||
## 核心实现
|
||||
|
||||
- 新增单调递增的静态 revision / generation。
|
||||
- retained cache build 时记录 buildRevision。
|
||||
- reuse 前要求 buildRevision 与当前 staticRevision 一致。
|
||||
- 新增稳定 topology snapshot:
|
||||
- dynamic boundary;
|
||||
- static prefix 中每个 LayerGroup 的 layerIndex;
|
||||
- groupName;
|
||||
- groupType。
|
||||
- 真正的 LayerGroup 增删、排序、index 变化会推进 topologyRevision。
|
||||
- Renderer 内部 transient drawable churn 不再直接推进 static revision。
|
||||
- 等价的 stencil tile set 不因 shared_ptr identity 变化而误失效。
|
||||
- 支持并记录以下语义失效来源:
|
||||
- camera / viewport / pixel ratio;
|
||||
- style / layer / source mutation;
|
||||
- tile content change;
|
||||
- image / sprite / glyph;
|
||||
- placement / fade;
|
||||
- context reset;
|
||||
- debug options;
|
||||
- 3D / unsupported custom/static layer。
|
||||
- unsupported / uncertain 场景继续 fail-closed。
|
||||
- STEP-088 runtime Cache ON/OFF 行为保持不变。
|
||||
- 未实现 STEP-090 CPU fast path。
|
||||
|
||||
## Android 实机关键验证
|
||||
|
||||
测试场景:
|
||||
- Android 13 / x86_64;
|
||||
- NavSea 正式 63 层 PBF Style;
|
||||
- Particle = AFTER_CLIP;
|
||||
- 固定 Camera:Karatsu;
|
||||
- Cache ON。
|
||||
|
||||
### 稳态
|
||||
|
||||
修复前曾出现:
|
||||
- drawable-mutation 每帧失效;
|
||||
- layer-group-mutation 每帧失效;
|
||||
- staticRevision 每帧递增;
|
||||
- build 每帧递增;
|
||||
- reuse=0。
|
||||
|
||||
最终版本:
|
||||
|
||||
```text
|
||||
frame=480 build=2 reuse=302 staticRevision=21 buildRevision=21
|
||||
frame=600 build=2 reuse=422 staticRevision=21 buildRevision=21
|
||||
frame=720 build=2 reuse=542 staticRevision=21 buildRevision=21
|
||||
```
|
||||
|
||||
确认:
|
||||
- staticRevision 稳定;
|
||||
- buildRevision == staticRevision;
|
||||
- build 不再增长;
|
||||
- reuse 持续增长。
|
||||
|
||||
### Camera 变化
|
||||
|
||||
相机变化后:
|
||||
- staticRevision: 41 -> 44;
|
||||
- build: 18 -> 19;
|
||||
- buildRevision = 44;
|
||||
|
||||
随后稳定:
|
||||
|
||||
```text
|
||||
build=19
|
||||
reuse=3519 -> 3639 -> 3759 -> 3879
|
||||
staticRevision=44
|
||||
buildRevision=44
|
||||
```
|
||||
|
||||
确认真实变化会使 revision 前进并重建一次,稳定后恢复 reuse。
|
||||
|
||||
## Native 测试
|
||||
|
||||
Retained directed tests:12/12 PASS。
|
||||
|
||||
覆盖:
|
||||
- dynamic boundary;
|
||||
- layer group range;
|
||||
- cache build/reuse/invalidation;
|
||||
- runtime Cache ON/OFF;
|
||||
- revision 单调;
|
||||
- 无语义变化时 revision 不前进;
|
||||
- 等价 intra-frame remove/add 不推进 topology;
|
||||
- 持久 topology 变化只推进一次;
|
||||
- ordering/index 变化推进 topology;
|
||||
- buildRevision 匹配才允许 reuse;
|
||||
- placement/fade 活跃时拒绝 stale cache。
|
||||
|
||||
## 构建验证
|
||||
|
||||
- Mac OpenGL mbgl-test-runner:PASS。
|
||||
- Metal/iOS mbgl-core:BUILD SUCCEEDED。
|
||||
- Android x86_64 stripped Debug Validation APK:BUILD SUCCESSFUL,约 57 MB。
|
||||
- Android 实机稳态 reuse:PASS。
|
||||
- Android Camera invalidation -> rebuild -> reuse:PASS。
|
||||
- git diff --check:PASS。
|
||||
|
||||
## 结论
|
||||
|
||||
STEP-089 已建立可信的 static revision gate。后续 STEP-090 可以在该 gate 为稳定状态时尝试跳过静态 PBF/vector CPU processing;089 本身不做 CPU fast path。
|
||||
|
||||
## 下一步
|
||||
|
||||
进入 STEP-090:Static CPU Fast Path。
|
||||
@@ -0,0 +1,60 @@
|
||||
# STEP-089 静态渲染 Revision 与 Invalidation 基础层 Codex 完成报告 v1
|
||||
|
||||
## 最终状态
|
||||
|
||||
**CLOSED / PASS**
|
||||
|
||||
## 完成项
|
||||
|
||||
- 已完成 static revision / buildRevision 门控。
|
||||
- 已完成 topology snapshot / topologyRevision。
|
||||
- 已覆盖 camera、viewport、pixel ratio、style、layer、source、tile、image、glyph、placement/fade、context、debug、3D/custom fail-closed。
|
||||
- 已移除 transient drawable churn 对 static revision 的误触发。
|
||||
- 已修复 equivalent stencil tile set 因 shared_ptr identity 导致的误失效。
|
||||
- 已保留真正 LayerGroup topology 变化的失效能力。
|
||||
- 已保持 STEP-088 Cache ON/OFF 运行时能力。
|
||||
- 未实现 STEP-090 fast path。
|
||||
|
||||
## 实机回归中发现并修复的问题
|
||||
|
||||
1. 首版把 LayerGroup drawable maintenance 当成 semantic mutation,导致 staticRevision 每帧递增。
|
||||
2. stencil tile shared_ptr identity 在正常帧中变化,导致 false positive。
|
||||
3. LayerGroup transient churn 继续导致 topology false positive。
|
||||
4. 最终改为 semantic content revision + stable topology snapshot,稳态复用恢复。
|
||||
|
||||
## 验证
|
||||
|
||||
- OpenGL:PASS。
|
||||
- Retained directed tests:12/12 PASS。
|
||||
- Metal/iOS mbgl-core:BUILD SUCCEEDED。
|
||||
- Android APK:BUILD SUCCESSFUL。
|
||||
- Android 稳态:
|
||||
- build 固定;
|
||||
- reuse 持续增长;
|
||||
- staticRevision == buildRevision 且保持稳定。
|
||||
- Camera 变化:
|
||||
- revision 前进;
|
||||
- cache rebuild 一次;
|
||||
- 随后恢复稳定 reuse。
|
||||
- git diff --check:PASS。
|
||||
|
||||
## Android 代表日志
|
||||
|
||||
```text
|
||||
frame=480 build=2 reuse=302 staticRevision=21 buildRevision=21
|
||||
frame=600 build=2 reuse=422 staticRevision=21 buildRevision=21
|
||||
frame=720 build=2 reuse=542 staticRevision=21 buildRevision=21
|
||||
```
|
||||
|
||||
相机变化后:
|
||||
|
||||
```text
|
||||
build=19 reuse=3519 staticRevision=44 buildRevision=44
|
||||
build=19 reuse=3639 staticRevision=44 buildRevision=44
|
||||
build=19 reuse=3759 staticRevision=44 buildRevision=44
|
||||
build=19 reuse=3879 staticRevision=44 buildRevision=44
|
||||
```
|
||||
|
||||
## Git
|
||||
|
||||
将在 scoped commit/push 后补充最终 commit hash。
|
||||
@@ -31,6 +31,7 @@
|
||||
#include <mbgl/util/logging.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <string_view>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
@@ -151,6 +152,16 @@ RenderOrchestrator::~RenderOrchestrator() {
|
||||
threadPool.waitForEmpty();
|
||||
}
|
||||
|
||||
void RenderOrchestrator::advanceRetainedUnderlayContentRevision(RetainedUnderlayReason reason) noexcept {
|
||||
retainedUnderlayContentReason.store(reason, std::memory_order_release);
|
||||
retainedUnderlayContentRevision.fetch_add(1, std::memory_order_acq_rel);
|
||||
}
|
||||
|
||||
void RenderOrchestrator::markContextLost() {
|
||||
contextLost = true;
|
||||
advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::ContextReset);
|
||||
}
|
||||
|
||||
void RenderOrchestrator::setObserver(RendererObserver* observer_) {
|
||||
observer = observer_ ? observer_ : &nullObserver();
|
||||
}
|
||||
@@ -161,6 +172,20 @@ std::unique_ptr<RenderTree> RenderOrchestrator::createRenderTree(
|
||||
|
||||
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.
|
||||
@@ -211,6 +236,7 @@ std::unique_ptr<RenderTree> RenderOrchestrator::createRenderTree(
|
||||
const bool lightChanged = renderLight.impl != updateParameters->light;
|
||||
|
||||
if (lightChanged) {
|
||||
advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::StyleMutation);
|
||||
renderLight.impl = updateParameters->light;
|
||||
renderLight.transition(transitionParameters);
|
||||
}
|
||||
@@ -220,6 +246,9 @@ std::unique_ptr<RenderTree> RenderOrchestrator::createRenderTree(
|
||||
}
|
||||
|
||||
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
|
||||
@@ -249,6 +278,9 @@ std::unique_ptr<RenderTree> RenderOrchestrator::createRenderTree(
|
||||
imageManager->setLoaded(updateParameters->spriteLoaded);
|
||||
|
||||
const LayerDifference layerDiff = diffLayers(layerImpls, updateParameters->layers);
|
||||
if (!layerDiff.added.empty() || !layerDiff.removed.empty() || !layerDiff.changed.empty()) {
|
||||
advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::LayerMutation);
|
||||
}
|
||||
layerImpls = updateParameters->layers;
|
||||
const bool layersAddedOrRemoved = !layerDiff.added.empty() || !layerDiff.removed.empty();
|
||||
|
||||
@@ -327,6 +359,9 @@ std::unique_ptr<RenderTree> RenderOrchestrator::createRenderTree(
|
||||
}
|
||||
|
||||
const SourceDifference sourceDiff = diffSources(sourceImpls, updateParameters->sources);
|
||||
if (!sourceDiff.added.empty() || !sourceDiff.removed.empty() || !sourceDiff.changed.empty()) {
|
||||
advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::SourceMutation);
|
||||
}
|
||||
sourceImpls = updateParameters->sources;
|
||||
|
||||
// Remove render layers for removed sources.
|
||||
@@ -751,6 +786,7 @@ void RenderOrchestrator::setFeatureState(const std::string& sourceID,
|
||||
|
||||
if (RenderSource* renderSource = getRenderSource(sourceID)) {
|
||||
renderSource->setFeatureState(sourceLayerID, featureID, state);
|
||||
advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::SourceMutation);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -773,6 +809,7 @@ void RenderOrchestrator::removeFeatureState(const std::string& sourceID,
|
||||
|
||||
if (RenderSource* renderSource = getRenderSource(sourceID)) {
|
||||
renderSource->removeFeatureState(sourceLayerID, featureID, stateKey);
|
||||
advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::SourceMutation);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -882,6 +919,16 @@ bool RenderOrchestrator::retainedUnderlayIsStable(TimePoint timePoint, int32_t b
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RenderOrchestrator::retainedUnderlayHasUnsupportedLayer(int32_t boundary) const {
|
||||
const auto staticLayerCount = std::min<std::size_t>(
|
||||
boundary > 0 ? static_cast<std::size_t>(boundary) : 0, orderedLayers.size());
|
||||
for (std::size_t index = 0; index < staticLayerCount; ++index) {
|
||||
const std::string_view type = orderedLayers[index].get().baseImpl->getTypeInfo()->type;
|
||||
if (type == "custom" || type == "custom-drawable") return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RenderOrchestrator::isLoaded() const {
|
||||
MLN_TRACE_FUNC();
|
||||
|
||||
@@ -924,6 +971,7 @@ void RenderOrchestrator::clearData() {
|
||||
|
||||
imageManager->clear();
|
||||
glyphManager->evict(fontStacks(*layerImpls));
|
||||
advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::StyleMutation);
|
||||
}
|
||||
|
||||
void RenderOrchestrator::addChanges(UniqueChangeRequestVec& changes) {
|
||||
@@ -1076,6 +1124,7 @@ void RenderOrchestrator::updateDebugLayerGroups(const RenderTree& renderTree, Pa
|
||||
}
|
||||
|
||||
void RenderOrchestrator::onGlyphsLoaded(const FontStack& fontStack, const GlyphRange& range) {
|
||||
advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::GlyphMutation);
|
||||
observer->onGlyphsLoaded(fontStack, range);
|
||||
}
|
||||
|
||||
@@ -1113,6 +1162,7 @@ void RenderOrchestrator::onTileError(RenderSource& source, const OverscaledTileI
|
||||
void RenderOrchestrator::onTileChanged(RenderSource&, const OverscaledTileID&) {
|
||||
MLN_TRACE_FUNC();
|
||||
|
||||
advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::TileMutation);
|
||||
observer->onInvalidate();
|
||||
}
|
||||
|
||||
@@ -1126,12 +1176,16 @@ void RenderOrchestrator::onTileAction(RenderSource&,
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <mbgl/renderer/render_tree.hpp>
|
||||
#include <mbgl/renderer/retained_underlay_cache.hpp>
|
||||
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
@@ -64,7 +65,7 @@ public:
|
||||
void enableAndroidEmulatorGoldfishMitigation(bool enable) { androidGoldfishMitigationEnabled = enable; }
|
||||
#endif
|
||||
|
||||
void markContextLost() { contextLost = true; };
|
||||
void markContextLost();
|
||||
// TODO: Introduce RenderOrchestratorObserver.
|
||||
void setObserver(RendererObserver*);
|
||||
|
||||
@@ -111,6 +112,13 @@ public:
|
||||
std::optional<int32_t> getRetainedDynamicBoundary() const noexcept { return retainedDynamicBoundary; }
|
||||
bool retainedUnderlayUsesPlacement(int32_t boundary) const;
|
||||
bool retainedUnderlayIsStable(TimePoint, int32_t boundary) const;
|
||||
bool retainedUnderlayHasUnsupportedLayer(int32_t boundary) const;
|
||||
uint64_t getRetainedUnderlayContentRevision() const noexcept {
|
||||
return retainedUnderlayContentRevision.load(std::memory_order_acquire);
|
||||
}
|
||||
RetainedUnderlayReason getRetainedUnderlayContentReason() const noexcept {
|
||||
return retainedUnderlayContentReason.load(std::memory_order_acquire);
|
||||
}
|
||||
void updateLayerIndex(LayerGroupBasePtr, int32_t newIndex);
|
||||
|
||||
template <typename Func /* void(LayerGroupBase&) */>
|
||||
@@ -269,6 +277,7 @@ private:
|
||||
|
||||
/// Move changes into the pending set, clearing the provided collection
|
||||
void addChanges(UniqueChangeRequestVec&);
|
||||
void advanceRetainedUnderlayContentRevision(RetainedUnderlayReason) noexcept;
|
||||
|
||||
RendererObserver* observer;
|
||||
|
||||
@@ -295,6 +304,12 @@ private:
|
||||
bool contextLost = false;
|
||||
bool placedSymbolDataCollected = false;
|
||||
bool tileCacheEnabled = true;
|
||||
bool retainedInputStateInitialized = false;
|
||||
bool retainedSpriteLoaded = false;
|
||||
std::string retainedGlyphURL;
|
||||
const void* retainedFontFacesIdentity = nullptr;
|
||||
std::atomic<uint64_t> retainedUnderlayContentRevision{1};
|
||||
std::atomic<RetainedUnderlayReason> retainedUnderlayContentReason{RetainedUnderlayReason::None};
|
||||
|
||||
#if MLN_RENDER_BACKEND_OPENGL
|
||||
bool androidGoldfishMitigationEnabled{false};
|
||||
|
||||
@@ -75,6 +75,8 @@ void logRetainedRendererCounters(const RetainedUnderlayCache& cache) {
|
||||
<< " fallback=" << retained.fallback
|
||||
<< " invalidation=" << retained.invalidation
|
||||
<< " reason=" << retained.lastReason
|
||||
<< " staticRevision=" << cache.getStaticRevision()
|
||||
<< " buildRevision=" << cache.getBuildRevision()
|
||||
<< " staticDraw=" << detailed.retainedUnderlayStaticGroupDraw
|
||||
<< " dynamicDraw=" << detailed.retainedDynamicGroupDraw
|
||||
<< " particleAdvance=" << detailed.particleSimulationAdvance;
|
||||
@@ -301,14 +303,19 @@ void Renderer::Impl::render(const RenderTree& renderTree, const std::shared_ptr<
|
||||
|
||||
std::optional<RetainedUnderlayKey> retainedKey;
|
||||
const auto retainedBoundary = orchestrator.getRetainedDynamicBoundary();
|
||||
size_t retainedStaticGroupCount = 0;
|
||||
RetainedUnderlayTopologySnapshot retainedTopology{.boundary = retainedBoundary, .staticPrefix = {}};
|
||||
mat4 retainedCameraMatrix;
|
||||
state.getProjMatrix(retainedCameraMatrix);
|
||||
if (retainedBoundary) {
|
||||
orchestrator.visitLayerGroupsBefore(*retainedBoundary,
|
||||
[&](LayerGroupBase&, size_t) { ++retainedStaticGroupCount; });
|
||||
mat4 cameraMatrix;
|
||||
state.getProjMatrix(cameraMatrix);
|
||||
retainedKey = RetainedUnderlayKey{size, *retainedBoundary, cameraMatrix};
|
||||
orchestrator.visitLayerGroupsBefore(*retainedBoundary, [&](LayerGroupBase& layerGroup, size_t) {
|
||||
retainedTopology.staticPrefix.push_back({.layerIndex = layerGroup.getLayerIndex(),
|
||||
.groupName = layerGroup.getName(),
|
||||
.groupType = static_cast<uint8_t>(layerGroup.getType())});
|
||||
});
|
||||
retainedKey = RetainedUnderlayKey{size, *retainedBoundary, retainedCameraMatrix};
|
||||
}
|
||||
const auto retainedTopologyRevision = retainedUnderlayTopologyTracker.observe(retainedTopology);
|
||||
const auto retainedStaticGroupCount = retainedTopology.staticPrefix.size();
|
||||
bool legacyBelowBoundary = false;
|
||||
if (retainedBoundary) {
|
||||
for (const auto& item : layerRenderItems) {
|
||||
@@ -325,11 +332,33 @@ void Renderer::Impl::render(const RenderTree& renderTree, const std::shared_ptr<
|
||||
const bool retainedPlacementStable =
|
||||
!retainedStaticUsesPlacement ||
|
||||
(!renderTreeParameters.placementChanged && renderTreeParameters.symbolFadeChange == 0.0f);
|
||||
const bool retainedUnsupportedStaticLayer =
|
||||
retainedBoundary && orchestrator.retainedUnderlayHasUnsupportedLayer(*retainedBoundary);
|
||||
const auto retainedContentRevision = orchestrator.getRetainedUnderlayContentRevision();
|
||||
const auto retainedContentReason = orchestrator.getRetainedUnderlayContentReason();
|
||||
const RetainedUnderlayRevisionState retainedRevisionState{
|
||||
.viewport = size,
|
||||
.cameraMatrix = retainedCameraMatrix,
|
||||
.boundary = retainedBoundary,
|
||||
.pixelRatio = updateParameters->pixelRatio,
|
||||
.contentRevision = retainedContentRevision,
|
||||
.topologyRevision = retainedTopologyRevision,
|
||||
.mapMode = static_cast<uint8_t>(updateParameters->mode),
|
||||
.debugOptions = static_cast<uint32_t>(renderTreeParameters.debugOptions),
|
||||
.styleLoaded = updateParameters->styleLoaded,
|
||||
.renderTreeLoaded = renderTreeParameters.loaded,
|
||||
.staticStable = retainedStaticStable,
|
||||
.placementStable = retainedPlacementStable,
|
||||
.has3D = renderTreeParameters.has3D,
|
||||
.unsupportedStaticLayer = retainedUnsupportedStaticLayer,
|
||||
};
|
||||
const auto retainedStaticRevision = retainedUnderlayRevisionTracker.observe(
|
||||
retainedRevisionState, retainedUnderlayReasonText(retainedContentReason));
|
||||
const bool retainedSupported = retainedBoundary && retainedStaticGroupCount > 0 &&
|
||||
renderTreeParameters.loaded && !renderTreeParameters.has3D &&
|
||||
renderTreeParameters.debugOptions == MapDebugOptions::NoDebug &&
|
||||
retainedStaticStable && retainedPlacementStable &&
|
||||
!legacyBelowBoundary;
|
||||
!legacyBelowBoundary && !retainedUnsupportedStaticLayer;
|
||||
const char* retainedFallbackReason = !retainedBoundary ? "no-boundary"
|
||||
: retainedStaticGroupCount == 0 ? "empty-static-prefix"
|
||||
: !renderTreeParameters.loaded ? "style-not-loaded"
|
||||
@@ -341,10 +370,13 @@ void Renderer::Impl::render(const RenderTree& renderTree, const std::shared_ptr<
|
||||
: !retainedPlacementStable
|
||||
? "static-placement"
|
||||
: legacyBelowBoundary ? "legacy-ordering"
|
||||
: retainedUnsupportedStaticLayer ? "unsupported"
|
||||
: nullptr;
|
||||
auto retainedDecision = retainedUnderlayCache->evaluate(retainedKey,
|
||||
retainedStaticRevision,
|
||||
retainedSupported,
|
||||
retainedFallbackReason);
|
||||
retainedFallbackReason,
|
||||
retainedUnderlayRevisionTracker.reason().c_str());
|
||||
if ((retainedDecision == RetainedUnderlayDecision::Build ||
|
||||
retainedDecision == RetainedUnderlayDecision::Reuse) &&
|
||||
(!staticData->shaders || !retainedUnderlayCache->prepareResources(context, *staticData->shaders, *retainedKey))) {
|
||||
|
||||
@@ -57,6 +57,8 @@ private:
|
||||
const float pixelRatio;
|
||||
std::unique_ptr<RenderStaticData> staticData;
|
||||
std::unique_ptr<RetainedUnderlayCache> retainedUnderlayCache;
|
||||
RetainedUnderlayRevisionTracker retainedUnderlayRevisionTracker;
|
||||
RetainedUnderlayTopologyTracker retainedUnderlayTopologyTracker;
|
||||
gfx::DynamicTextureAtlasPtr dynamicTextureAtlas;
|
||||
bool styleLoaded = false;
|
||||
|
||||
|
||||
@@ -39,11 +39,73 @@ RetainedUnderlayReason retainedReason(const char* reason) noexcept {
|
||||
if (value == "static-placement") return RetainedUnderlayReason::StaticPlacement;
|
||||
if (value == "legacy-ordering") return RetainedUnderlayReason::LegacyOrdering;
|
||||
if (value == "resource-or-compositor") return RetainedUnderlayReason::ResourceOrCompositor;
|
||||
if (value == "style-mutation") return RetainedUnderlayReason::StyleMutation;
|
||||
if (value == "layer-mutation") return RetainedUnderlayReason::LayerMutation;
|
||||
if (value == "source-mutation") return RetainedUnderlayReason::SourceMutation;
|
||||
if (value == "tile-mutation") return RetainedUnderlayReason::TileMutation;
|
||||
if (value == "image-mutation") return RetainedUnderlayReason::ImageMutation;
|
||||
if (value == "glyph-mutation") return RetainedUnderlayReason::GlyphMutation;
|
||||
if (value == "layer-group-mutation") return RetainedUnderlayReason::LayerGroupMutation;
|
||||
if (value == "context-reset") return RetainedUnderlayReason::ContextReset;
|
||||
if (value == "pixel-ratio") return RetainedUnderlayReason::PixelRatio;
|
||||
if (value == "static-revision") return RetainedUnderlayReason::StaticRevision;
|
||||
if (value == "unsupported") return RetainedUnderlayReason::Unsupported;
|
||||
return RetainedUnderlayReason::Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t RetainedUnderlayTopologyTracker::observe(const RetainedUnderlayTopologySnapshot& next) {
|
||||
if (!snapshot) {
|
||||
snapshot = next;
|
||||
return revision;
|
||||
}
|
||||
if (*snapshot == next) return revision;
|
||||
snapshot = next;
|
||||
return ++revision;
|
||||
}
|
||||
|
||||
uint64_t RetainedUnderlayRevisionTracker::observe(const RetainedUnderlayRevisionState& next,
|
||||
const char* contentReason) {
|
||||
if (!state) {
|
||||
state = next;
|
||||
return revision;
|
||||
}
|
||||
if (*state == next) return revision;
|
||||
|
||||
const auto& previous = *state;
|
||||
if (previous.viewport != next.viewport) {
|
||||
lastReason = "viewport";
|
||||
} else if (previous.pixelRatio != next.pixelRatio) {
|
||||
lastReason = "pixel-ratio";
|
||||
} else if (previous.cameraMatrix != next.cameraMatrix || previous.boundary != next.boundary) {
|
||||
lastReason = "camera-or-boundary";
|
||||
} else if (previous.topologyRevision != next.topologyRevision) {
|
||||
lastReason = "layer-group-mutation";
|
||||
} else if (previous.contentRevision != next.contentRevision) {
|
||||
lastReason = contentReason ? contentReason : "static-revision";
|
||||
} else if (previous.styleLoaded != next.styleLoaded) {
|
||||
lastReason = "style-mutation";
|
||||
} else if (previous.renderTreeLoaded != next.renderTreeLoaded) {
|
||||
lastReason = "tile-mutation";
|
||||
} else if (previous.staticStable != next.staticStable) {
|
||||
lastReason = "static-transition";
|
||||
} else if (previous.placementStable != next.placementStable) {
|
||||
lastReason = "static-placement";
|
||||
} else if (previous.debugOptions != next.debugOptions) {
|
||||
lastReason = "debug-options";
|
||||
} else if (previous.has3D != next.has3D) {
|
||||
lastReason = "3d";
|
||||
} else if (previous.unsupportedStaticLayer != next.unsupportedStaticLayer ||
|
||||
previous.mapMode != next.mapMode) {
|
||||
lastReason = "unsupported";
|
||||
} else {
|
||||
lastReason = "static-revision";
|
||||
}
|
||||
state = next;
|
||||
++revision;
|
||||
return revision;
|
||||
}
|
||||
|
||||
RetainedUnderlayCache::RetainedUnderlayCache()
|
||||
: runtimeState(std::make_shared<RetainedUnderlayRuntimeState>()) {}
|
||||
RetainedUnderlayCache::~RetainedUnderlayCache() = default;
|
||||
@@ -69,9 +131,19 @@ void RetainedUnderlayCache::synchronizeRuntimeState(
|
||||
}
|
||||
|
||||
RetainedUnderlayDecision RetainedUnderlayCache::evaluate(const std::optional<RetainedUnderlayKey>& nextKey,
|
||||
uint64_t staticRevision,
|
||||
bool supported,
|
||||
const char* fallbackReason) {
|
||||
if (!nextKey) return RetainedUnderlayDecision::Disabled;
|
||||
const char* fallbackReason,
|
||||
const char* revisionReason) {
|
||||
const bool revisionChanged = observedStaticRevision != 0 && observedStaticRevision != staticRevision;
|
||||
observedStaticRevision = staticRevision;
|
||||
if (!nextKey) {
|
||||
if (valid || key) {
|
||||
invalidate(fallbackReason ? fallbackReason : "no-boundary");
|
||||
releaseResources();
|
||||
}
|
||||
return RetainedUnderlayDecision::Disabled;
|
||||
}
|
||||
if (runtimeState && !runtimeState->isEnabled()) {
|
||||
++fallbackCount;
|
||||
runtimeState->recordFallback();
|
||||
@@ -83,13 +155,26 @@ RetainedUnderlayDecision RetainedUnderlayCache::evaluate(const std::optional<Ret
|
||||
runtimeState->recordFallback();
|
||||
particle::incrementRendererDebugCounter(particle::RendererDebugCounter::RetainedUnderlayFallback);
|
||||
invalidate(fallbackReason ? fallbackReason : "unsupported");
|
||||
if (revisionChanged) releaseResources();
|
||||
return RetainedUnderlayDecision::Fallback;
|
||||
}
|
||||
if (!key || *key != *nextKey) {
|
||||
if (revisionChanged) {
|
||||
invalidate(revisionReason ? revisionReason : "static-revision");
|
||||
// A content or backend generation change may have made any retained GPU object stale.
|
||||
// Releasing here is deliberately conservative; these events are not the continuous
|
||||
// Particle repaint itself and therefore do not affect the steady-state reuse path.
|
||||
releaseResources();
|
||||
key = nextKey;
|
||||
} else if (!key || *key != *nextKey) {
|
||||
const char* reason = !key ? "first-frame" : (key->viewport != nextKey->viewport ? "viewport" : "camera-or-boundary");
|
||||
invalidate(reason);
|
||||
key = nextKey;
|
||||
}
|
||||
if (valid && buildRevision != observedStaticRevision) {
|
||||
invalidate("static-revision");
|
||||
releaseResources();
|
||||
key = nextKey;
|
||||
}
|
||||
if (valid) {
|
||||
++reuseCount;
|
||||
runtimeState->recordReuse();
|
||||
@@ -181,6 +266,7 @@ void RetainedUnderlayCache::composite(RenderOrchestrator& orchestrator, PaintPar
|
||||
|
||||
void RetainedUnderlayCache::markBuildSucceeded() {
|
||||
valid = true;
|
||||
buildRevision = observedStaticRevision;
|
||||
++buildCount;
|
||||
runtimeState->recordBuild();
|
||||
particle::incrementRendererDebugCounter(particle::RendererDebugCounter::RetainedUnderlayBuild);
|
||||
@@ -191,6 +277,7 @@ void RetainedUnderlayCache::markBuildFailed(const char* reason) {
|
||||
runtimeState->recordFallback();
|
||||
particle::incrementRendererDebugCounter(particle::RendererDebugCounter::RetainedUnderlayFallback);
|
||||
invalidate(reason);
|
||||
releaseResources();
|
||||
}
|
||||
|
||||
void RetainedUnderlayCache::invalidate(const char* reason) {
|
||||
@@ -202,6 +289,7 @@ void RetainedUnderlayCache::invalidate(const char* reason) {
|
||||
|
||||
void RetainedUnderlayCache::releaseResources() {
|
||||
valid = false;
|
||||
buildRevision = 0;
|
||||
key.reset();
|
||||
compositeLayerGroup.reset();
|
||||
offscreenTexture.reset();
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
@@ -30,6 +31,62 @@ struct RetainedUnderlayKey {
|
||||
bool operator==(const RetainedUnderlayKey&) const = default;
|
||||
};
|
||||
|
||||
struct RetainedUnderlayTopologyEntry {
|
||||
int32_t layerIndex = 0;
|
||||
std::string groupName;
|
||||
uint8_t groupType = 0;
|
||||
|
||||
bool operator==(const RetainedUnderlayTopologyEntry&) const = default;
|
||||
};
|
||||
|
||||
struct RetainedUnderlayTopologySnapshot {
|
||||
std::optional<int32_t> boundary;
|
||||
std::vector<RetainedUnderlayTopologyEntry> staticPrefix;
|
||||
|
||||
bool operator==(const RetainedUnderlayTopologySnapshot&) const = default;
|
||||
};
|
||||
|
||||
class RetainedUnderlayTopologyTracker {
|
||||
public:
|
||||
uint64_t observe(const RetainedUnderlayTopologySnapshot&);
|
||||
uint64_t current() const noexcept { return revision; }
|
||||
|
||||
private:
|
||||
std::optional<RetainedUnderlayTopologySnapshot> snapshot;
|
||||
uint64_t revision = 1;
|
||||
};
|
||||
|
||||
struct RetainedUnderlayRevisionState {
|
||||
Size viewport;
|
||||
mat4 cameraMatrix{};
|
||||
std::optional<int32_t> boundary;
|
||||
float pixelRatio = 1.0f;
|
||||
uint64_t contentRevision = 0;
|
||||
uint64_t topologyRevision = 0;
|
||||
uint8_t mapMode = 0;
|
||||
uint32_t debugOptions = 0;
|
||||
bool styleLoaded = false;
|
||||
bool renderTreeLoaded = false;
|
||||
bool staticStable = false;
|
||||
bool placementStable = false;
|
||||
bool has3D = false;
|
||||
bool unsupportedStaticLayer = false;
|
||||
|
||||
bool operator==(const RetainedUnderlayRevisionState&) const = default;
|
||||
};
|
||||
|
||||
class RetainedUnderlayRevisionTracker {
|
||||
public:
|
||||
uint64_t observe(const RetainedUnderlayRevisionState&, const char* contentReason = nullptr);
|
||||
uint64_t current() const noexcept { return revision; }
|
||||
const std::string& reason() const noexcept { return lastReason; }
|
||||
|
||||
private:
|
||||
std::optional<RetainedUnderlayRevisionState> state;
|
||||
uint64_t revision = 1;
|
||||
std::string lastReason{"first-frame"};
|
||||
};
|
||||
|
||||
enum class RetainedUnderlayDecision : uint8_t { Disabled, Build, Reuse, Fallback };
|
||||
|
||||
class RetainedDynamicBoundaryTracker {
|
||||
@@ -53,8 +110,11 @@ public:
|
||||
RetainedUnderlayCache();
|
||||
~RetainedUnderlayCache();
|
||||
|
||||
RetainedUnderlayDecision evaluate(const std::optional<RetainedUnderlayKey>&, bool supported,
|
||||
const char* fallbackReason = nullptr);
|
||||
RetainedUnderlayDecision evaluate(const std::optional<RetainedUnderlayKey>&,
|
||||
uint64_t staticRevision,
|
||||
bool supported,
|
||||
const char* fallbackReason = nullptr,
|
||||
const char* revisionReason = nullptr);
|
||||
void synchronizeRuntimeState(const std::shared_ptr<RetainedUnderlayRuntimeState>&);
|
||||
bool prepareResources(gfx::Context&, gfx::ShaderRegistry&, const RetainedUnderlayKey&);
|
||||
void upload(gfx::UploadPass&);
|
||||
@@ -69,6 +129,8 @@ public:
|
||||
uint64_t getBuildCount() const noexcept { return buildCount; }
|
||||
uint64_t getReuseCount() const noexcept { return reuseCount; }
|
||||
uint64_t getFallbackCount() const noexcept { return fallbackCount; }
|
||||
uint64_t getStaticRevision() const noexcept { return observedStaticRevision; }
|
||||
uint64_t getBuildRevision() const noexcept { return buildRevision; }
|
||||
const std::string& getLastInvalidationReason() const noexcept { return lastInvalidationReason; }
|
||||
RetainedUnderlayDiagnostics getDiagnostics() const { return runtimeState->snapshot(); }
|
||||
|
||||
@@ -83,6 +145,8 @@ private:
|
||||
uint64_t buildCount = 0;
|
||||
uint64_t reuseCount = 0;
|
||||
uint64_t fallbackCount = 0;
|
||||
uint64_t observedStaticRevision = 0;
|
||||
uint64_t buildRevision = 0;
|
||||
std::string lastInvalidationReason;
|
||||
std::shared_ptr<RetainedUnderlayRuntimeState> runtimeState;
|
||||
uint64_t observedControlRevision = 0;
|
||||
|
||||
@@ -21,6 +21,16 @@ enum class RetainedUnderlayReason : uint8_t {
|
||||
StaticPlacement,
|
||||
LegacyOrdering,
|
||||
ResourceOrCompositor,
|
||||
StyleMutation,
|
||||
LayerMutation,
|
||||
SourceMutation,
|
||||
TileMutation,
|
||||
ImageMutation,
|
||||
GlyphMutation,
|
||||
LayerGroupMutation,
|
||||
ContextReset,
|
||||
PixelRatio,
|
||||
StaticRevision,
|
||||
Unsupported,
|
||||
Unknown,
|
||||
};
|
||||
@@ -41,6 +51,16 @@ inline const char* retainedUnderlayReasonText(RetainedUnderlayReason reason) noe
|
||||
case RetainedUnderlayReason::StaticPlacement: return "static-placement";
|
||||
case RetainedUnderlayReason::LegacyOrdering: return "legacy-ordering";
|
||||
case RetainedUnderlayReason::ResourceOrCompositor: return "resource-or-compositor";
|
||||
case RetainedUnderlayReason::StyleMutation: return "style-mutation";
|
||||
case RetainedUnderlayReason::LayerMutation: return "layer-mutation";
|
||||
case RetainedUnderlayReason::SourceMutation: return "source-mutation";
|
||||
case RetainedUnderlayReason::TileMutation: return "tile-mutation";
|
||||
case RetainedUnderlayReason::ImageMutation: return "image-mutation";
|
||||
case RetainedUnderlayReason::GlyphMutation: return "glyph-mutation";
|
||||
case RetainedUnderlayReason::LayerGroupMutation: return "layer-group-mutation";
|
||||
case RetainedUnderlayReason::ContextReset: return "context-reset";
|
||||
case RetainedUnderlayReason::PixelRatio: return "pixel-ratio";
|
||||
case RetainedUnderlayReason::StaticRevision: return "static-revision";
|
||||
case RetainedUnderlayReason::Unsupported: return "unsupported";
|
||||
case RetainedUnderlayReason::Unknown: return "unknown";
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
using namespace mbgl;
|
||||
@@ -15,6 +16,21 @@ RetainedUnderlayKey key(Size size = {640, 480}, int32_t boundary = 4) {
|
||||
result.cameraMatrix[15] = 1.0;
|
||||
return result;
|
||||
}
|
||||
|
||||
RetainedUnderlayRevisionState revisionState(Size size = {640, 480}, int32_t boundary = 4) {
|
||||
RetainedUnderlayRevisionState result;
|
||||
result.viewport = size;
|
||||
result.boundary = boundary;
|
||||
result.pixelRatio = 2.0f;
|
||||
result.contentRevision = 1;
|
||||
result.topologyRevision = 1;
|
||||
result.styleLoaded = true;
|
||||
result.renderTreeLoaded = true;
|
||||
result.staticStable = true;
|
||||
result.placementStable = true;
|
||||
result.cameraMatrix = key(size, boundary).cameraMatrix;
|
||||
return result;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST(RetainedDynamicBoundary, SelectsFirstCandidate) {
|
||||
@@ -64,25 +80,206 @@ TEST(RetainedLayerGroupRange, PreservesForwardReverseOriginalOrdinals) {
|
||||
|
||||
TEST(RetainedUnderlayCache, BuildReuseAndInvalidation) {
|
||||
RetainedUnderlayCache cache;
|
||||
EXPECT_EQ(cache.evaluate(std::nullopt, false), RetainedUnderlayDecision::Disabled);
|
||||
EXPECT_EQ(cache.evaluate(key(), true), RetainedUnderlayDecision::Build);
|
||||
EXPECT_EQ(cache.evaluate(std::nullopt, 1, false), RetainedUnderlayDecision::Disabled);
|
||||
EXPECT_EQ(cache.evaluate(key(), 1, true), RetainedUnderlayDecision::Build);
|
||||
cache.markBuildSucceeded();
|
||||
EXPECT_EQ(cache.evaluate(key(), true), RetainedUnderlayDecision::Reuse);
|
||||
EXPECT_EQ(cache.evaluate(key(), 1, true), RetainedUnderlayDecision::Reuse);
|
||||
EXPECT_EQ(cache.getBuildRevision(), 1u);
|
||||
|
||||
EXPECT_EQ(cache.evaluate(key({800, 600}), true), RetainedUnderlayDecision::Build);
|
||||
EXPECT_EQ(cache.evaluate(key({800, 600}), 2, true, nullptr, "viewport"), RetainedUnderlayDecision::Build);
|
||||
EXPECT_EQ(cache.getLastInvalidationReason(), "viewport");
|
||||
cache.markBuildSucceeded();
|
||||
EXPECT_EQ(cache.evaluate(key({800, 600}, 6), true), RetainedUnderlayDecision::Build);
|
||||
EXPECT_EQ(cache.evaluate(key({800, 600}, 6), 3, true, nullptr, "camera-or-boundary"),
|
||||
RetainedUnderlayDecision::Build);
|
||||
EXPECT_EQ(cache.getLastInvalidationReason(), "camera-or-boundary");
|
||||
}
|
||||
|
||||
TEST(RetainedUnderlayRevision, MonotonicAcrossCameraViewportStyleAndSourceChanges) {
|
||||
RetainedUnderlayRevisionTracker tracker;
|
||||
RetainedUnderlayCache cache;
|
||||
auto state = revisionState();
|
||||
const auto currentKey = [&] {
|
||||
return RetainedUnderlayKey{state.viewport, *state.boundary, state.cameraMatrix};
|
||||
};
|
||||
EXPECT_EQ(tracker.observe(state), 1u);
|
||||
EXPECT_EQ(tracker.observe(state), 1u);
|
||||
EXPECT_EQ(cache.evaluate(currentKey(), tracker.current(), true), RetainedUnderlayDecision::Build);
|
||||
cache.markBuildSucceeded();
|
||||
|
||||
state.cameraMatrix[12] = 4.0;
|
||||
EXPECT_EQ(tracker.observe(state), 2u);
|
||||
EXPECT_EQ(tracker.reason(), "camera-or-boundary");
|
||||
EXPECT_EQ(cache.evaluate(currentKey(), tracker.current(), true, nullptr, tracker.reason().c_str()),
|
||||
RetainedUnderlayDecision::Build);
|
||||
cache.markBuildSucceeded();
|
||||
|
||||
state.viewport = {800, 600};
|
||||
EXPECT_EQ(tracker.observe(state), 3u);
|
||||
EXPECT_EQ(tracker.reason(), "viewport");
|
||||
EXPECT_EQ(cache.evaluate(currentKey(), tracker.current(), true, nullptr, tracker.reason().c_str()),
|
||||
RetainedUnderlayDecision::Build);
|
||||
cache.markBuildSucceeded();
|
||||
|
||||
state.pixelRatio = 3.0f;
|
||||
EXPECT_EQ(tracker.observe(state), 4u);
|
||||
EXPECT_EQ(tracker.reason(), "pixel-ratio");
|
||||
EXPECT_EQ(cache.evaluate(currentKey(), tracker.current(), true, nullptr, tracker.reason().c_str()),
|
||||
RetainedUnderlayDecision::Build);
|
||||
cache.markBuildSucceeded();
|
||||
|
||||
++state.contentRevision;
|
||||
EXPECT_EQ(tracker.observe(state, "style-mutation"), 5u);
|
||||
EXPECT_EQ(tracker.reason(), "style-mutation");
|
||||
EXPECT_EQ(cache.evaluate(currentKey(), tracker.current(), true, nullptr, tracker.reason().c_str()),
|
||||
RetainedUnderlayDecision::Build);
|
||||
cache.markBuildSucceeded();
|
||||
|
||||
++state.contentRevision;
|
||||
EXPECT_EQ(tracker.observe(state, "layer-mutation"), 6u);
|
||||
EXPECT_EQ(tracker.reason(), "layer-mutation");
|
||||
EXPECT_EQ(cache.evaluate(currentKey(), tracker.current(), true, nullptr, tracker.reason().c_str()),
|
||||
RetainedUnderlayDecision::Build);
|
||||
cache.markBuildSucceeded();
|
||||
|
||||
++state.contentRevision;
|
||||
EXPECT_EQ(tracker.observe(state, "source-mutation"), 7u);
|
||||
EXPECT_EQ(tracker.reason(), "source-mutation");
|
||||
EXPECT_EQ(cache.evaluate(currentKey(), tracker.current(), true, nullptr, tracker.reason().c_str()),
|
||||
RetainedUnderlayDecision::Build);
|
||||
cache.markBuildSucceeded();
|
||||
|
||||
++state.contentRevision;
|
||||
EXPECT_EQ(tracker.observe(state, "tile-mutation"), 8u);
|
||||
EXPECT_EQ(tracker.reason(), "tile-mutation");
|
||||
EXPECT_EQ(cache.evaluate(currentKey(), tracker.current(), true, nullptr, tracker.reason().c_str()),
|
||||
RetainedUnderlayDecision::Build);
|
||||
cache.markBuildSucceeded();
|
||||
|
||||
++state.topologyRevision;
|
||||
EXPECT_EQ(tracker.observe(state), 9u);
|
||||
EXPECT_EQ(tracker.reason(), "layer-group-mutation");
|
||||
EXPECT_EQ(cache.evaluate(currentKey(), tracker.current(), true, nullptr, tracker.reason().c_str()),
|
||||
RetainedUnderlayDecision::Build);
|
||||
}
|
||||
|
||||
TEST(RetainedUnderlayRevision, RepeatedFramesWithoutSemanticMutationDoNotAdvanceRevision) {
|
||||
RetainedUnderlayRevisionTracker tracker;
|
||||
auto state = revisionState();
|
||||
const auto settledRevision = tracker.observe(state);
|
||||
|
||||
// Transient drawable add/remove/rebuild and equivalent stencil assignment are
|
||||
// deliberately not inputs to the semantic revision state. Stable frames must
|
||||
// therefore keep the revision used by the retained build.
|
||||
for (int frame = 0; frame < 240; ++frame) {
|
||||
EXPECT_EQ(tracker.observe(state), settledRevision);
|
||||
}
|
||||
|
||||
++state.contentRevision;
|
||||
EXPECT_GT(tracker.observe(state, "tile-mutation"), settledRevision);
|
||||
EXPECT_EQ(tracker.reason(), "tile-mutation");
|
||||
}
|
||||
|
||||
TEST(RetainedUnderlayRevision, EquivalentIntraFrameRemoveAddDoesNotAdvanceTopology) {
|
||||
RetainedUnderlayTopologyTracker tracker;
|
||||
const RetainedUnderlayTopologySnapshot initial{
|
||||
.boundary = 32,
|
||||
.staticPrefix = {{3, "sea-area-fill", 1}, {31, "clip-outline", 1}},
|
||||
};
|
||||
const auto settledRevision = tracker.observe(initial);
|
||||
|
||||
// The request processor may temporarily remove a group and add an equivalent
|
||||
// replacement. Only the final post-processChanges snapshot is observed.
|
||||
auto finalTopology = initial;
|
||||
finalTopology.staticPrefix.erase(finalTopology.staticPrefix.begin());
|
||||
finalTopology.staticPrefix.insert(finalTopology.staticPrefix.begin(), {3, "sea-area-fill", 1});
|
||||
EXPECT_EQ(finalTopology, initial);
|
||||
EXPECT_EQ(tracker.observe(finalTopology), settledRevision);
|
||||
}
|
||||
|
||||
TEST(RetainedUnderlayRevision, PersistentTopologyChangeAdvancesOnce) {
|
||||
RetainedUnderlayTopologyTracker tracker;
|
||||
const RetainedUnderlayTopologySnapshot initial{
|
||||
.boundary = 32,
|
||||
.staticPrefix = {{3, "sea-area-fill", 1}, {31, "clip-outline", 1}},
|
||||
};
|
||||
const auto initialRevision = tracker.observe(initial);
|
||||
|
||||
auto changed = initial;
|
||||
changed.staticPrefix.pop_back();
|
||||
const auto changedRevision = tracker.observe(changed);
|
||||
EXPECT_GT(changedRevision, initialRevision);
|
||||
EXPECT_EQ(tracker.observe(changed), changedRevision);
|
||||
}
|
||||
|
||||
TEST(RetainedUnderlayRevision, OrderingAndIndexChangesAdvanceTopology) {
|
||||
RetainedUnderlayTopologyTracker tracker;
|
||||
RetainedUnderlayTopologySnapshot topology{
|
||||
.boundary = 32,
|
||||
.staticPrefix = {{3, "sea-area-fill", 1}, {31, "clip-outline", 1}},
|
||||
};
|
||||
const auto initialRevision = tracker.observe(topology);
|
||||
|
||||
std::swap(topology.staticPrefix[0], topology.staticPrefix[1]);
|
||||
const auto reorderedRevision = tracker.observe(topology);
|
||||
EXPECT_GT(reorderedRevision, initialRevision);
|
||||
|
||||
topology.staticPrefix[0].layerIndex = 30;
|
||||
const auto reindexedRevision = tracker.observe(topology);
|
||||
EXPECT_GT(reindexedRevision, reorderedRevision);
|
||||
EXPECT_EQ(tracker.observe(topology), reindexedRevision);
|
||||
}
|
||||
|
||||
TEST(RetainedUnderlayRevision, MatchingBuildRevisionAllowsReuseAndMismatchRebuilds) {
|
||||
RetainedUnderlayCache cache;
|
||||
EXPECT_EQ(cache.evaluate(key(), 7, true), RetainedUnderlayDecision::Build);
|
||||
cache.markBuildSucceeded();
|
||||
EXPECT_EQ(cache.getBuildRevision(), 7u);
|
||||
EXPECT_EQ(cache.evaluate(key(), 7, true), RetainedUnderlayDecision::Reuse);
|
||||
|
||||
EXPECT_EQ(cache.evaluate(key(), 8, true, nullptr, "tile-mutation"), RetainedUnderlayDecision::Build);
|
||||
EXPECT_FALSE(cache.isValid());
|
||||
EXPECT_EQ(cache.getLastInvalidationReason(), "tile-mutation");
|
||||
}
|
||||
|
||||
TEST(RetainedUnderlayRevision, PlacementAndFadeActivityRejectsStaleCacheUntilStable) {
|
||||
RetainedUnderlayRevisionTracker tracker;
|
||||
RetainedUnderlayCache cache;
|
||||
auto state = revisionState();
|
||||
auto revision = tracker.observe(state);
|
||||
EXPECT_EQ(cache.evaluate(key(), revision, true), RetainedUnderlayDecision::Build);
|
||||
cache.markBuildSucceeded();
|
||||
|
||||
state.placementStable = false;
|
||||
revision = tracker.observe(state);
|
||||
EXPECT_EQ(cache.evaluate(key(), revision, false, "static-placement", tracker.reason().c_str()),
|
||||
RetainedUnderlayDecision::Fallback);
|
||||
EXPECT_FALSE(cache.isValid());
|
||||
|
||||
state.placementStable = true;
|
||||
revision = tracker.observe(state);
|
||||
EXPECT_EQ(cache.evaluate(key(), revision, true, nullptr, tracker.reason().c_str()),
|
||||
RetainedUnderlayDecision::Build);
|
||||
cache.markBuildSucceeded();
|
||||
|
||||
state.staticStable = false;
|
||||
revision = tracker.observe(state);
|
||||
EXPECT_EQ(cache.evaluate(key(), revision, false, "static-transition", tracker.reason().c_str()),
|
||||
RetainedUnderlayDecision::Fallback);
|
||||
EXPECT_FALSE(cache.isValid());
|
||||
|
||||
state.staticStable = true;
|
||||
revision = tracker.observe(state);
|
||||
EXPECT_EQ(cache.evaluate(key(), revision, true, nullptr, tracker.reason().c_str()),
|
||||
RetainedUnderlayDecision::Build);
|
||||
}
|
||||
|
||||
TEST(RetainedUnderlayCache, UnsupportedAndResourceFailureFallBackSafely) {
|
||||
RetainedUnderlayCache cache;
|
||||
EXPECT_EQ(cache.evaluate(key(), false, "3d"), RetainedUnderlayDecision::Fallback);
|
||||
EXPECT_EQ(cache.evaluate(key(), 1, false, "3d"), RetainedUnderlayDecision::Fallback);
|
||||
EXPECT_EQ(cache.getLastInvalidationReason(), "3d");
|
||||
EXPECT_FALSE(cache.isValid());
|
||||
|
||||
EXPECT_EQ(cache.evaluate(key(), true), RetainedUnderlayDecision::Build);
|
||||
EXPECT_EQ(cache.evaluate(key(), 1, true), RetainedUnderlayDecision::Build);
|
||||
cache.markBuildFailed("resource-or-compositor");
|
||||
EXPECT_FALSE(cache.isValid());
|
||||
EXPECT_EQ(cache.getFallbackCount(), 2u);
|
||||
@@ -93,14 +290,14 @@ TEST(RetainedUnderlayCache, RuntimeControlDisablesAndReenablesMapScopedCache) {
|
||||
RetainedUnderlayCache cache;
|
||||
cache.synchronizeRuntimeState(state);
|
||||
|
||||
EXPECT_EQ(cache.evaluate(key(), true), RetainedUnderlayDecision::Build);
|
||||
EXPECT_EQ(cache.evaluate(key(), 1, true), RetainedUnderlayDecision::Build);
|
||||
cache.markBuildSucceeded();
|
||||
EXPECT_TRUE(cache.isValid());
|
||||
|
||||
EXPECT_TRUE(state->setEnabled(false));
|
||||
cache.synchronizeRuntimeState(state);
|
||||
EXPECT_FALSE(cache.isValid());
|
||||
EXPECT_EQ(cache.evaluate(key(), true), RetainedUnderlayDecision::Fallback);
|
||||
EXPECT_EQ(cache.evaluate(key(), 1, true), RetainedUnderlayDecision::Fallback);
|
||||
auto disabled = state->snapshot();
|
||||
EXPECT_FALSE(disabled.enabled);
|
||||
EXPECT_EQ(disabled.build, 1u);
|
||||
@@ -110,6 +307,6 @@ TEST(RetainedUnderlayCache, RuntimeControlDisablesAndReenablesMapScopedCache) {
|
||||
|
||||
EXPECT_TRUE(state->setEnabled(true));
|
||||
cache.synchronizeRuntimeState(state);
|
||||
EXPECT_EQ(cache.evaluate(key(), true), RetainedUnderlayDecision::Build);
|
||||
EXPECT_EQ(cache.evaluate(key(), 1, true), RetainedUnderlayDecision::Build);
|
||||
EXPECT_TRUE(state->snapshot().enabled);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user