From e67e4961b5b1a1ff8fa7c1e3263799d63f89cadc Mon Sep 17 00:00:00 2001 From: tei Date: Fri, 25 Sep 2026 22:26:33 +0900 Subject: [PATCH] fix: checkpoint STEP-097 A1 renderer correctness --- .../STEP-097_V13三轮问题去重与修复矩阵_v1.md | 43 +++++++++++ .../STEP-097_V13三轮审查问题合并修复_v1.md | 14 ++++ .../STEP-097_V13修复后集中验证清单_v1.md | 45 +++++++++++ src/mbgl/mtl/offscreen_texture.cpp | 75 +++++++++++++++---- src/mbgl/mtl/render_pass.cpp | 38 +++++++--- src/mbgl/renderer/paint_parameters.cpp | 8 ++ src/mbgl/renderer/paint_parameters.hpp | 3 + src/mbgl/renderer/render_orchestrator.hpp | 14 +++- src/mbgl/renderer/render_target.cpp | 4 +- src/mbgl/renderer/renderer_impl.cpp | 69 +++++++++++------ src/mbgl/renderer/retained_underlay_cache.cpp | 8 +- src/mbgl/renderer/retained_underlay_cache.hpp | 2 + .../retained_underlay_runtime_state.hpp | 8 +- .../renderer/retained_underlay_cache.test.cpp | 27 +++++++ 14 files changed, 295 insertions(+), 63 deletions(-) create mode 100644 docs/30_设计/STEP-097_V13三轮问题去重与修复矩阵_v1.md create mode 100644 docs/50_Step记录/STEP-097_V13三轮审查问题合并修复_v1.md create mode 100644 docs/60_测试/STEP-097_V13修复后集中验证清单_v1.md diff --git a/docs/30_设计/STEP-097_V13三轮问题去重与修复矩阵_v1.md b/docs/30_设计/STEP-097_V13三轮问题去重与修复矩阵_v1.md new file mode 100644 index 000000000..334a5c51a --- /dev/null +++ b/docs/30_设计/STEP-097_V13三轮问题去重与修复矩阵_v1.md @@ -0,0 +1,43 @@ +# STEP-097 V13 三轮问题去重与修复矩阵 v1 + +基线:Mac-mini-tei.lan;`particle/main`;起始 `720d309364628f85049cf9969d71c71e8dfacce9`,与查询时 `origin/particle/main` 一致;工作区干净。冻结 `.13` tag 指向 `ee0b89e71`。该 HEAD 相对任务书的 `b5c1c4879` 只新增 STEP-097 任务文档。Integration 本地 C-001/RFC-001 已只读;其远端是否最新尚未由 RDC 证实。所有构建、测试和运行验证:**NOT RUN**。 + +## 公共不变量 + +`fieldId/generation/validTime`、东向 U/北向 V、T0/T1/MIX、旧 T1→新 T0 复用、Style 全局 Z 顺序、每个 R 独立缓存、WeatherColor 与 Particle 分离、Host 拥有天气获取。无权改变 C-001 的公开生命周期、错误或数据所有权语义。 + +以下“入口”均为待执行回归,不能当作运行结果。源码状态随阶段更新;`WAITING_RDC_BUILD_AND_RUNTIME` 表示仅完成定位,尚未实现。 + +| # / 原编号 | 当前源码定位与最短触发 | 反例/安全约束 | 拟修文件与回归入口 | 状态 / commit / 平台待测 | +| --- | --- | --- | --- | --- | +| 01 N01/B01 | `paint_parameters.cpp:185` tile 集合相同跨 R-R target 复用 CPU mask | 同一未清 stencil target 可复用 | PaintParameters、Renderer、RenderTarget 已在新目标重置;同 tiles R-R/R-C-R 像素入口尚缺 | WAITING_RDC_BUILD_AND_RUNTIME / A1 部分提交 / GL、Metal | +| 02 N02/B02 | `mtl/render_pass.cpp:21` 仅 color clear;`offscreen_texture.cpp:67` 附件创建 | 无 depth/stencil 的 target 不可盲设附件 | Metal descriptor/combined 附件已修改;专用附件故障测试源码尚缺 | WAITING_RDC_BUILD_AND_RUNTIME / A1 部分提交 / Metal | +| 03 N03/B12 | `render_orchestrator.hpp:156` catch;`renderer_impl.cpp:824` 仍 mark success | 失败后 full fallback、下帧重试 | visitor 错误标志与 fallback 已接入;异常注入测试源码尚缺 | WAITING_RDC_BUILD_AND_RUNTIME / A1 部分提交 / GL、Metal | +| 04 N04/B06 | encoded playback clock 的生产调用方待核 | 显式 setTime 不双 tick | source/clock;PLAY/PAUSE/rate | WAITING_RDC_BUILD_AND_RUNTIME / 待提交 / Core、双平台 | +| 05 B13 | Particle GPU endpoint 更新门槛仅局部身份 | mix-only 不上传;跨窗只新端上传 | Particle field/renderer;仅 V/validity 变动 | WAITING_RDC_BUILD_AND_RUNTIME / 待提交 / GL、Metal | +| 06 B07 | 8-bit 中心量化不能精确表示零 | 不改公共 QBIN encoding | Particle texture/shader;零/微速/NaN | DEFERRED_DESIGN / 无 / GL、Metal | +| 07 B08 | Mercator 粒子位移 U 纬度度量 | 保持 U/V m/s 与既有时间倍率 | Particle shader;0°/45°/60° 四方向 | WAITING_RDC_BUILD_AND_RUNTIME / 待提交 / GL、Metal | +| 08 N05 | `render_segment_json.cpp` C 字符串解析 | invalid 清旧计划 | JSON/parser;NUL、重复键、恢复 | WAITING_RDC_BUILD_AND_RUNTIME / 待提交 / Core、桥接 | +| 09 N06 | `raster_array_source.cpp` 入库早于物化数值校验 | validity=0/noData 合法 | source/store;好旧帧→坏高 revision | WAITING_RDC_BUILD_AND_RUNTIME / 待提交 / Core | +| 10 N07 | Android attach map listener 重复注册 | detach/destroy 可重入 | Android bridge;100 次 attach/detach | WAITING_RDC_BUILD_AND_RUNTIME / 待提交 / Android | +| 11 F01/C01 | `renderer_impl.cpp:505` full diagnostics 快照 | 外部 pull schema 不变 | runtime/renderer;快照计数 | WAITING_RDC_BUILD_AND_RUNTIME / 待提交 / Core | +| 12 F02/C02/B03 | Metal offscreen `swap()` 同步等待 | 不删不明依赖 wait | Metal;GPU capture | DEFERRED_DESIGN / 无 / Metal | +| 13 F03/C03/B04 | cache revision 变化释放 texture | target clear 必须先正确 | cache;同尺寸重绘/resize | WAITING_RDC_BUILD_AND_RUNTIME / 待提交 / GL、Metal | +| 14 F04/C04 | R 数 × viewport 独立附件内存 | 保留独立 R 与失败 fallback | cache;资源阶梯、失败注入 | DEFERRED_DESIGN / 无 / GL、Metal | +| 15 C04/B11 | encoded Store 无总 key/byte 预算 | 不静默驱逐绑定窗口 | Store;容量/旧读者 | DEFERRED_DESIGN / 无 / Core | +| 16 F07/C05 | `render_orchestrator.hpp:156` 每段前缀 distance | 全局 ordinal 与 Z 顺序不变 | orchestrator;重排/正反遍历 | WAITING_RDC_BUILD_AND_RUNTIME / 待提交 / Core | +| 17 C06/B05/B10 | 相同时间/mix-only 物化全网格 | owner-thread 世代发布 | runtime/source;重复时间/边界 | WAITING_RDC_BUILD_AND_RUNTIME / 待提交 / Core、桥接 | +| 18 C07 | drawable/tweaker tick 重建、端点复制 | 不保留 stale GPU 引用 | Particle renderer;tick/resize | DEFERRED_DESIGN / 无 / GL、Metal | +| 19 F09/O01 | `runtime_state.hpp:230` 新 topology;旧 cache 析构按 index 计数 | 总量与当前 topology 分离 | runtime/cache 按 revision 过滤;新增 `OldCacheReleaseCannotChargeReplacementAtSameIndex` GTest | CODE_IMPLEMENTED_STATIC_CHECKED / A1 部分提交 / Core 测试 NOT RUN | +| 20 F05/E01 | Host package `.10`、Native plugin `.13` | 精确同版,勿动发布产物 | Host package/lock;默认 install/typecheck | WAITING_RDC_BUILD_AND_RUNTIME / 待提交 / RN、Android | +| 21 F06 | `renderer_impl.cpp` multi-C 走 full CPU | 保留安全 fallback | 后续 A/B 设计 | DEFERRED_DESIGN / 无 / GL、Metal | +| 22 F08 | source/tile 等全局失效 | 无依赖图前不缩窄 | 后续依赖设计 | DEFERRED_DESIGN / 无 / Core | +| 23 F10/E02 | Integration status/compatibility 仍写 `.5` | 发布≠NavSea 支持 | 负责人协调证据请求 | DEFERRED_DESIGN / 无 / 跨项目 | +| 24 STEP-095 §7/B09 | synthetic mask 非共享地理 mask | 不新建公开 mask API | C-001/RFC/ADR 协调 | DEFERRED_DESIGN / 无 / 跨项目 | + +## 操作次数模型(静态,非性能实测) + +- A1:跨 target 清除 CPU mask 为每次目标切换一次 O(1) 状态清理;当前同 tiles 可能零次重绘但产生错误像素。Metal clear 每个 pass 最多各一次附件 load 设置。异常后额外一次 full fallback,避免错误缓存跨帧复用。 +- A2:预期 PLAY 每 map/source 唯一时钟一次 tick;endpoint 内容识别随被提交场的数据量变化,mix-only 应为零次 GPU endpoint 上传。尚未实施。 +- B:预期 JSON 校验 O(输入长度),事务校验 O(有效 cell 数),attach 重复次数不再增加 listener 数。尚未实施。 +- C:预期 full diagnostics 深复制从每 render 次降到每外部 pull 次;ordinal 前缀从每段 O(G) 降到 topology 更新 O(G);同时间物化从 O(cell) 降为 O(1)。尚未实施。 diff --git a/docs/50_Step记录/STEP-097_V13三轮审查问题合并修复_v1.md b/docs/50_Step记录/STEP-097_V13三轮审查问题合并修复_v1.md new file mode 100644 index 000000000..180aac957 --- /dev/null +++ b/docs/50_Step记录/STEP-097_V13三轮审查问题合并修复_v1.md @@ -0,0 +1,14 @@ +# STEP-097 V13 三轮审查问题合并修复 v1 + +状态:进行中。目标:按阶段修复三轮审查的当前源码问题,保护 C-001 与既有公开协议。基线:Mac-mini-tei.lan,`/Users/tei/dev/MapLibreParticleLab`,`particle/main`,起始 `720d309364628f85049cf9969d71c71e8dfacce9`,起始工作树 clean,子模块无偏离。当前 `.13` 发布 tag 指向 `ee0b89e71`;不修改发布坐标。 + +| 检查点 | 状态 | 实际事实、阻塞和下一步 | +| --- | --- | --- | +| 阶段 0 基线与反证 | 完成(静态) | 读取三轮审查、STEP-094~096 交付及 C-001/RFC-001 本地副本。当前 HEAD 比任务书基线只新增任务文档;远端 particle/main 经 `git ls-remote` 核对一致。建立 24 项矩阵。Integration 远端同步状态待 RDC 复核。 | +| A1 Renderer 正确性 | 阻塞 | 01/02/03/19 定点源码路径成立;代码改动落盘,19 有 GTest。01/02/03 缺少任务要求的专用可执行像素/附件/故障注入测试入口;且未进行 GL/Metal 编译。故 A1 不判完成,严格停止于此,不进入 A2。 | +| A2 Field/粒子正确性 | 计划 | 尚未开始。 | +| B 输入/生命周期 | 计划 | 尚未开始。 | +| C 安全性能 | 计划 | 尚未开始;必须先完成 A1 正确性。 | +| D 交付 | 计划 | 集中验证与报告待写。所有构建、测试、运行、发布均 NOT RUN。 | + +允许范围与禁止范围按任务书。未完成项目不勾选。RDC 后续执行构建及运行;本轮不运行构建或 Jest。 diff --git a/docs/60_测试/STEP-097_V13修复后集中验证清单_v1.md b/docs/60_测试/STEP-097_V13修复后集中验证清单_v1.md new file mode 100644 index 000000000..be099885a --- /dev/null +++ b/docs/60_测试/STEP-097_V13修复后集中验证清单_v1.md @@ -0,0 +1,45 @@ +# STEP-097 V13 修复后集中验证清单 v1 + +状态:**NOT RUN**。本轮停在 A1 部分代码检查点;下列命令只为 RDC 后续集中验证准备,不能当作已通过证据。先由 RDC 独立审查 diff,补齐 A1 的专用像素、Metal 附件和异常注入测试源码,再执行。A2/B/C 未实施,相关集合不得用于验收。 + +## 通用 Core 与现有测试入口(RDC) + +在本仓 Mac 当前 checkout,使用已配置的现有 build 目录;若目录不存在,先按当前构建规范配置。此处不自动运行: + +```sh +cmake --build build-step088-opengl --target mbgl-test-runner -j3 +build-step088-opengl/mbgl-test-runner --gtest_filter='RenderSegmentPlan.*:RenderSegmentDiagnostics.*:RetainedUnderlayCache.*:RetainedUnderlayRevision.*:ParticleFastPath.*' +``` + +新入口 `RenderSegmentDiagnostics.OldCacheReleaseCannotChargeReplacementAtSameIndex` 预期同 index 的旧 revision release 不计入新 topology,当前 revision release 计 1。`RetainedUnderlayCache.UnsupportedAndResourceFailureFallBackSafely` 为旧入口,用于资源失败后的基础状态;尚不能替代真实 layer render 异常注入。失败保留完整日志,回传首个错误和日志路径。 + +## Android OpenGL(Particle-Android / RDC,NOT RUN) + +```sh +./tools/particle_android_validation/build_validation_apk.sh +``` + +RDC 应使用同一 commit 生成 APK,再由后续授权的 Win11/模拟器操作按标准流水线安装。现有 Test Host 仅有 C-R 控制,尚缺同一 vector source/same tiles 的 R-R、R-C-R、C-R-R fixture,故该 APK 即使构建成功也不能关闭 01/03。需比较 cache ON/OFF 的首帧、重建、reuse、主目标与离屏目标像素;失败后 diagnostics 不得 Ready,下帧可恢复。记录 Renderer、ABI、SHA-256、截图与日志。不得引用 STEP-094 历史 43/43 作为本轮结果。 + +## iOS Metal(Particle-iOS / RDC,NOT RUN) + +```sh +cmake --build build-ios --target ios-sdk-static -- -quiet +``` + +同 commit 的 iOS simulator 与真机需开启 Metal API Validation,捕获 retained R-R/R-C-R 的 color/depth/stencil texture、load/clear/store,特别确认 simulator 的 Depth32Float_Stencil8 同时绑定 depth 和 stencil。故障注入覆盖附件创建失败、encoder 创建失败及 layer render 异常;预期不发布 Ready cache、后续能 fallback/retry。专用自动测试入口尚缺,不能因静态修改或 `ios-sdk-static` 成功就宣称 Metal runtime PASS。 + +## 后续 GPU 性能(粒子负责人协调,NOT RUN) + +完成 A1/A2/B/C 代码与平台测试入口后,以相同设备、commit、Style、视口和场数据采样 cache ON/OFF、R×viewport×fieldKey 阶梯,记录 30/60 FPS、p50/p95/p99、GPU/CPU frame time、纹理创建/释放、峰值/长稳内存、方向/缩放/状态恢复。Metal capture 记录 `waitUntilCompleted` 的实际同步点及资源依赖。未经运行不填写数字;F06 multi-C 与 F08 精细失效另行设计。 + +## Test Host 依赖(B 阶段后,NOT RUN) + +目前默认 RN `.10` 与 Native `.13` 错配,阶段 B 未开始,不得运行安装并声称配对成功。B 修正 package/lock/SPM 后,RDC 再在 Test Host 目录执行: + +```sh +npm ci +npm run typecheck +``` + +必须同时检查实际已安装 RN tgz、Android Gradle substitution、iOS SPM exactVersion 与 lockfile;不得以 node_modules 临时软链代替默认安装结果。本清单不含 Jest 命令。 diff --git a/src/mbgl/mtl/offscreen_texture.cpp b/src/mbgl/mtl/offscreen_texture.cpp index 13fb8d947..260ce84de 100644 --- a/src/mbgl/mtl/offscreen_texture.cpp +++ b/src/mbgl/mtl/offscreen_texture.cpp @@ -5,6 +5,7 @@ #include #include +#include namespace mbgl { namespace mtl { @@ -15,10 +16,12 @@ public: const Size size_, const gfx::TextureChannelDataType type_, bool depth, - [[maybe_unused]] bool stencil) + bool stencil) : context(context_), size(size_), - type(type_) { + type(type_), + needsDepth(depth), + needsStencil(stencil) { assert(!size.isEmpty()); colorTexture = context.createTexture2D(); colorTexture->setSize(size); @@ -29,7 +32,12 @@ public: static_cast(colorTexture.get()) ->setUsage(MTL::TextureUsageShaderRead | MTL::TextureUsageShaderWrite | MTL::TextureUsageRenderTarget); - if (depth) { +#if TARGET_OS_SIMULATOR || defined(__x86_64__) + constexpr bool combinedDepthStencil = true; +#else + constexpr bool combinedDepthStencil = false; +#endif + if (depth || (stencil && combinedDepthStencil)) { depthTexture = context.createTexture2D(); depthTexture->setSize(size); depthTexture->setFormat(gfx::TexturePixelType::Depth, gfx::TextureChannelDataType::Float); @@ -41,7 +49,7 @@ public: } // On iOS simulator, the depth target is PixelFormatDepth32Float_Stencil8 -#if !TARGET_OS_SIMULATOR +#if !TARGET_OS_SIMULATOR && !defined(__x86_64__) if (stencil) { stencilTexture = context.createTexture2D(); stencilTexture->setSize(size); @@ -63,23 +71,45 @@ public: assert(context.getBackend().getCommandQueue()); commandBuffer = NS::RetainPtr(context.getBackend().getCommandQueue()->commandBuffer()); colorTexture->create(); + auto* colorMetalTexture = static_cast(colorTexture.get())->getMetalTexture(); + if (!commandBuffer || !colorMetalTexture) throw std::runtime_error("Metal offscreen color attachment unavailable"); renderPassDescriptor = NS::TransferPtr(MTL::RenderPassDescriptor::alloc()->init()); + if (!renderPassDescriptor) throw std::runtime_error("Metal offscreen render pass descriptor unavailable"); if (auto* colorTarget = renderPassDescriptor->colorAttachments()->object(0)) { - colorTarget->setTexture(static_cast(colorTexture.get())->getMetalTexture()); + colorTarget->setTexture(colorMetalTexture); + } else { + throw std::runtime_error("Metal offscreen color attachment missing"); } if (depthTexture) { depthTexture->create(); + auto* depthMetalTexture = static_cast(depthTexture.get())->getMetalTexture(); + if (!depthMetalTexture) throw std::runtime_error("Metal offscreen depth attachment unavailable"); if (auto* depthTarget = renderPassDescriptor->depthAttachment()) { - depthTarget->setTexture(static_cast(depthTexture.get())->getMetalTexture()); + depthTarget->setTexture(depthMetalTexture); + } else { + throw std::runtime_error("Metal offscreen depth attachment missing"); } } if (stencilTexture) { stencilTexture->create(); + auto* stencilMetalTexture = static_cast(stencilTexture.get())->getMetalTexture(); + if (!stencilMetalTexture) throw std::runtime_error("Metal offscreen stencil attachment unavailable"); if (auto* stencilTarget = renderPassDescriptor->stencilAttachment()) { - stencilTarget->setTexture(static_cast(stencilTexture.get())->getMetalTexture()); + stencilTarget->setTexture(stencilMetalTexture); + } else { + throw std::runtime_error("Metal offscreen stencil attachment missing"); } + } else if (needsStencil && depthTexture) { + // Simulator/x86 Metal uses one Depth32Float_Stencil8 texture for both attachments. + if (auto* stencilTarget = renderPassDescriptor->stencilAttachment()) { + stencilTarget->setTexture(static_cast(depthTexture.get())->getMetalTexture()); + } else { + throw std::runtime_error("Metal offscreen combined stencil attachment missing"); + } + } else if (needsStencil) { + throw std::runtime_error("Metal offscreen stencil attachment unavailable"); } } @@ -108,6 +138,25 @@ public: return colorTexture; } + bool isRenderable() { + try { + colorTexture->create(); + if (!static_cast(colorTexture.get())->getMetalTexture()) return false; + if (needsDepth || (needsStencil && !stencilTexture)) { + if (!depthTexture) return false; + depthTexture->create(); + if (!static_cast(depthTexture.get())->getMetalTexture()) return false; + } + if (stencilTexture) { + stencilTexture->create(); + if (!static_cast(stencilTexture.get())->getMetalTexture()) return false; + } + return true; + } catch (...) { + return false; + } + } + const RendererBackend& getBackend() const override { return context.getBackend(); } const MTLCommandBufferPtr& getCommandBuffer() const override { return commandBuffer; } @@ -125,6 +174,8 @@ private: Context& context; const Size size; const gfx::TextureChannelDataType type; + const bool needsDepth; + const bool needsStencil; gfx::Texture2DPtr colorTexture; gfx::Texture2DPtr depthTexture; gfx::Texture2DPtr stencilTexture; @@ -137,15 +188,7 @@ OffscreenTexture::OffscreenTexture( : gfx::OffscreenTexture(size_, std::make_unique(context, size_, type, depth, stencil)) {} bool OffscreenTexture::isRenderable() { - try { - auto& texture = getResource().getTexture(); - if (!texture) return false; - auto* metalTexture = static_cast(texture.get()); - metalTexture->create(); - return metalTexture->getMetalTexture() != nullptr; - } catch (...) { - return false; - } + return getResource().isRenderable(); } PremultipliedImage OffscreenTexture::readStillImage() { diff --git a/src/mbgl/mtl/render_pass.cpp b/src/mbgl/mtl/render_pass.cpp index 7af045165..9b15cb50c 100644 --- a/src/mbgl/mtl/render_pass.cpp +++ b/src/mbgl/mtl/render_pass.cpp @@ -6,6 +6,7 @@ #include #include +#include namespace mbgl { namespace mtl { @@ -18,26 +19,43 @@ RenderPass::RenderPass(CommandEncoder& commandEncoder_, const char* name, const resource.bind(); if (const auto& buffer = resource.getCommandBuffer()) { - if (auto rpd = resource.getRenderPassDescriptor()) { + if (const auto& original = resource.getRenderPassDescriptor()) { + auto rpd = NS::TransferPtr(original->copy()); + if (!rpd) throw std::runtime_error("Metal render pass descriptor copy failed"); if (descriptor.clearColor) { - if (auto copy = NS::TransferPtr(rpd->copy())) { - if (auto* colorTarget = copy->colorAttachments()->object(0)) { - const auto& c = *descriptor.clearColor; - colorTarget->setLoadAction(MTL::LoadActionClear); - colorTarget->setClearColor(MTL::ClearColor::Make(c.r, c.g, c.b, c.a)); - rpd = std::move(copy); - } + if (auto* target = rpd->colorAttachments()->object(0); target && target->texture()) { + const auto& c = *descriptor.clearColor; + target->setLoadAction(MTL::LoadActionClear); + target->setClearColor(MTL::ClearColor::Make(c.r, c.g, c.b, c.a)); + target->setStoreAction(MTL::StoreActionStore); + } + } + if (descriptor.clearDepth) { + if (auto* target = rpd->depthAttachment(); target && target->texture()) { + target->setLoadAction(MTL::LoadActionClear); + target->setClearDepth(*descriptor.clearDepth); + target->setStoreAction(MTL::StoreActionStore); + } + } + if (descriptor.clearStencil) { + if (auto* target = rpd->stencilAttachment(); target && target->texture()) { + target->setLoadAction(MTL::LoadActionClear); + target->setClearStencil(static_cast(*descriptor.clearStencil)); + target->setStoreAction(MTL::StoreActionStore); } } encoder = NS::RetainPtr(buffer->renderCommandEncoder(rpd.get())); + if (!encoder) throw std::runtime_error("Metal render command encoder unavailable"); - const auto& texture = rpd->colorAttachments()->object(0)->texture(); + const auto* colorTarget = rpd->colorAttachments()->object(0); + if (!colorTarget || !colorTarget->texture()) throw std::runtime_error("Metal color attachment unavailable"); + const auto& texture = colorTarget->texture(); width = texture->width(); height = texture->height(); } } - assert(encoder); + if (!encoder) throw std::runtime_error("Metal render pass unavailable"); // Push the groups already accumulated by the encoder commandEncoder.visitDebugGroups([this](const auto& group) { diff --git a/src/mbgl/renderer/paint_parameters.cpp b/src/mbgl/renderer/paint_parameters.cpp index 6ca276bf9..bd39de2e1 100644 --- a/src/mbgl/renderer/paint_parameters.cpp +++ b/src/mbgl/renderer/paint_parameters.cpp @@ -108,6 +108,14 @@ void PaintParameters::updateStencilBufferAvailability() { } #endif +void PaintParameters::beginRenderTarget() { + nextStencilID = 1; + tileClippingMaskIDs.clear(); +#if MLN_RENDER_BACKEND_OPENGL + updateStencilBufferAvailability(); +#endif +} + mat4 PaintParameters::matrixForTile(const UnwrappedTileID& tileID, bool aligned) const { mat4 matrix; state.matrixFor(matrix, tileID); diff --git a/src/mbgl/renderer/paint_parameters.hpp b/src/mbgl/renderer/paint_parameters.hpp index a585ba07a..0810c1a69 100644 --- a/src/mbgl/renderer/paint_parameters.hpp +++ b/src/mbgl/renderer/paint_parameters.hpp @@ -112,6 +112,9 @@ public: /// @return True when clipping mask state is ready or no update was needed. bool renderTileClippingMasks(const RenderTiles&); + /// A newly bound render target has no tile masks from the previous target. + void beginRenderTarget(); + /// Clear the stencil buffer, even if there are no tile masks (for 3D) void clearStencil(); diff --git a/src/mbgl/renderer/render_orchestrator.hpp b/src/mbgl/renderer/render_orchestrator.hpp index f6b0d5722..28b3395ba 100644 --- a/src/mbgl/renderer/render_orchestrator.hpp +++ b/src/mbgl/renderer/render_orchestrator.hpp @@ -153,19 +153,22 @@ public: } template - void visitLayerGroupsInRange(int32_t begin, int32_t endExclusive, Func f) { + void visitLayerGroupsInRange(int32_t begin, int32_t endExclusive, Func f, bool* failed = nullptr) { size_t ordinal = static_cast(std::distance(layerGroupsByLayerIndex.begin(), layerGroupsByLayerIndex.lower_bound(begin))); for (auto it = layerGroupsByLayerIndex.lower_bound(begin); it != layerGroupsByLayerIndex.end() && it->first < endExclusive; ++it, ++ordinal) { if (it->second) { - try { f(*it->second, ordinal); } catch (...) { observer->onRenderError(std::current_exception()); } + try { f(*it->second, ordinal); } catch (...) { + if (failed) *failed = true; + observer->onRenderError(std::current_exception()); + } } } } template - void visitLayerGroupsInRangeReversed(int32_t begin, int32_t endExclusive, Func f) { + void visitLayerGroupsInRangeReversed(int32_t begin, int32_t endExclusive, Func f, bool* failed = nullptr) { auto first = layerGroupsByLayerIndex.lower_bound(begin); auto last = layerGroupsByLayerIndex.lower_bound(endExclusive); size_t ordinal = static_cast(std::distance(layerGroupsByLayerIndex.begin(), last)); @@ -173,7 +176,10 @@ public: --last; --ordinal; if (last->second) { - try { f(*last->second, ordinal); } catch (...) { observer->onRenderError(std::current_exception()); } + try { f(*last->second, ordinal); } catch (...) { + if (failed) *failed = true; + observer->onRenderError(std::current_exception()); + } } } } diff --git a/src/mbgl/renderer/render_target.cpp b/src/mbgl/renderer/render_target.cpp index 496fbf054..7bf1b8ba2 100644 --- a/src/mbgl/renderer/render_target.cpp +++ b/src/mbgl/renderer/render_target.cpp @@ -74,9 +74,7 @@ void RenderTarget::render(RenderOrchestrator& orchestrator, const RenderTree& re .clearColor = Color{0.0f, 0.0f, 0.0f, 1.0f}, .clearDepth = {}, .clearStencil = {}}); -#if MLN_RENDER_BACKEND_OPENGL - parameters.updateStencilBufferAvailability(); -#endif + parameters.beginRenderTarget(); const gfx::ScissorRect prevScissorRect = parameters.scissorRect; const auto& size = getTexture()->getSize(); diff --git a/src/mbgl/renderer/renderer_impl.cpp b/src/mbgl/renderer/renderer_impl.cpp index 48dcc84c0..bd81c0659 100644 --- a/src/mbgl/renderer/renderer_impl.cpp +++ b/src/mbgl/renderer/renderer_impl.cpp @@ -517,6 +517,7 @@ void Renderer::Impl::render(const RenderTree& renderTree, segment.segmentIndex, "retained-segment-" + std::to_string(segment.segmentIndex), continuousSeen ? RetainedRangeRole::Top : RetainedRangeRole::Bottom); + cache->setSegmentTopologyRevision(segmentSnapshot.topologyRevision); cache->synchronizeRuntimeState(retainedRuntimeState); retainedSegmentCaches[segment.segmentIndex] = std::move(cache); } @@ -800,28 +801,50 @@ void Renderer::Impl::render(const RenderTree& renderTree, int32_t begin, int32_t endExclusive) { if (!useRetainedRanges || decision != RetainedUnderlayDecision::Build || begin >= endExclusive) return; - cache.beginBuild(parameters); - context.bindGlobalUniformBuffers(*parameters.renderPass); - parameters.depthRangeSize = 1 - (orchestrator.numLayerGroups() + 2) * PaintParameters::numSublayers * - PaintParameters::depthEpsilon; - parameters.pass = RenderPass::Opaque; - orchestrator.visitLayerGroupsInRangeReversed(begin, endExclusive, + bool drawFailed = false; + bool globalUniformsBound = false; + try { + cache.beginBuild(parameters); + context.bindGlobalUniformBuffers(*parameters.renderPass); + globalUniformsBound = true; + parameters.depthRangeSize = 1 - (orchestrator.numLayerGroups() + 2) * PaintParameters::numSublayers * + PaintParameters::depthEpsilon; + parameters.pass = RenderPass::Opaque; + orchestrator.visitLayerGroupsInRangeReversed(begin, endExclusive, + [&](LayerGroupBase& layerGroup, size_t ordinal) { + parameters.currentLayer = static_cast(orchestrator.numLayerGroups() - 1 - ordinal); + layerGroup.render(orchestrator, parameters); + particle::incrementRendererDebugCounter( + particle::RendererDebugCounter::RetainedUnderlayStaticGroupDraw); + }, &drawFailed); + if (!drawFailed) { + parameters.pass = RenderPass::Translucent; + orchestrator.visitLayerGroupsInRange(begin, endExclusive, [&](LayerGroupBase& layerGroup, size_t ordinal) { - parameters.currentLayer = static_cast(orchestrator.numLayerGroups() - 1 - ordinal); - layerGroup.render(orchestrator, parameters); - particle::incrementRendererDebugCounter( - particle::RendererDebugCounter::RetainedUnderlayStaticGroupDraw); - }); - parameters.pass = RenderPass::Translucent; - orchestrator.visitLayerGroupsInRange(begin, endExclusive, [&](LayerGroupBase& layerGroup, size_t ordinal) { - parameters.currentLayer = static_cast(orchestrator.numLayerGroups() - 1 - ordinal); - layerGroup.render(orchestrator, parameters); - particle::incrementRendererDebugCounter( - particle::RendererDebugCounter::RetainedUnderlayStaticGroupDraw); - }); - context.unbindGlobalUniformBuffers(*parameters.renderPass); - cache.finishBuild(parameters); - cache.markBuildSucceeded(); + parameters.currentLayer = static_cast(orchestrator.numLayerGroups() - 1 - ordinal); + layerGroup.render(orchestrator, parameters); + particle::incrementRendererDebugCounter( + particle::RendererDebugCounter::RetainedUnderlayStaticGroupDraw); + }, &drawFailed); + } + context.unbindGlobalUniformBuffers(*parameters.renderPass); + globalUniformsBound = false; + cache.finishBuild(parameters); + } catch (...) { + drawFailed = true; + const auto error = std::current_exception(); + if (globalUniformsBound && parameters.renderPass) { + context.unbindGlobalUniformBuffers(*parameters.renderPass); + } + observer->onRenderError(error); + parameters.renderPass.reset(); + } + if (drawFailed) { + cache.markBuildFailed("resource-or-compositor"); + useRetainedRanges = false; + } else { + cache.markBuildSucceeded(); + } }; const auto commonClearPass = [&] { @@ -842,9 +865,7 @@ void Renderer::Impl::render(const RenderTree& renderTree, .clearColor = color, .clearDepth = 1.0f, .clearStencil = 0}); -#if MLN_RENDER_BACKEND_OPENGL - parameters.updateStencilBufferAvailability(); -#endif + parameters.beginRenderTarget(); } }; diff --git a/src/mbgl/renderer/retained_underlay_cache.cpp b/src/mbgl/renderer/retained_underlay_cache.cpp index ad2e333e0..1a171f851 100644 --- a/src/mbgl/renderer/retained_underlay_cache.cpp +++ b/src/mbgl/renderer/retained_underlay_cache.cpp @@ -308,9 +308,7 @@ void RetainedUnderlayCache::beginBuild(PaintParameters& parameters) { .clearColor = Color{0.0f, 0.0f, 0.0f, 0.0f}, .clearDepth = 1.0f, .clearStencil = 0}); -#if MLN_RENDER_BACKEND_OPENGL - parameters.updateStencilBufferAvailability(); -#endif + parameters.beginRenderTarget(); } void RetainedUnderlayCache::finishBuild(PaintParameters& parameters) { @@ -365,7 +363,9 @@ void RetainedUnderlayCache::invalidate(const char* reason) { void RetainedUnderlayCache::releaseResources() { if (offscreenTexture || compositeLayerGroup) { runtimeState->recordRangeResourceRelease(role == RetainedRangeRole::Top); - if (renderSegmentIndex) runtimeState->recordSegmentResourceRelease(*renderSegmentIndex); + if (renderSegmentIndex) { + runtimeState->recordSegmentResourceRelease(*renderSegmentIndex, segmentTopologyRevision); + } } valid = false; buildRevision = 0; diff --git a/src/mbgl/renderer/retained_underlay_cache.hpp b/src/mbgl/renderer/retained_underlay_cache.hpp index c88b33109..17c0e0da4 100644 --- a/src/mbgl/renderer/retained_underlay_cache.hpp +++ b/src/mbgl/renderer/retained_underlay_cache.hpp @@ -146,6 +146,7 @@ public: runtimeState->recordFastPathDecision(active, reason); } void recordFullCpuFrame() { runtimeState->recordFullCpuFrame(); } + void setSegmentTopologyRevision(uint64_t revision) noexcept { segmentTopologyRevision = revision; } private: bool rebuildComposite(gfx::Context&, gfx::ShaderRegistry&); @@ -165,6 +166,7 @@ private: uint64_t observedControlRevision = 0; RetainedRangeRole role; std::optional renderSegmentIndex; + uint64_t segmentTopologyRevision = 0; std::string renderSegmentLabel; }; diff --git a/src/mbgl/renderer/retained_underlay_runtime_state.hpp b/src/mbgl/renderer/retained_underlay_runtime_state.hpp index 61634982d..41785950c 100644 --- a/src/mbgl/renderer/retained_underlay_runtime_state.hpp +++ b/src/mbgl/renderer/retained_underlay_runtime_state.hpp @@ -277,8 +277,12 @@ public: void recordSegmentCompositeCreate(uint32_t index) { updateSegmentCache(index, [](auto& value) { ++value.compositeCreate; }); } - void recordSegmentResourceRelease(uint32_t index) { - updateSegmentCache(index, [](auto& value) { ++value.resourceRelease; }); + void recordSegmentResourceRelease(uint32_t index, uint64_t topologyRevision) { + std::lock_guard lock(rangeMutex); + if (topologyRevision != renderSegmentTopologyRevision || + index >= renderSegmentDiagnostics.segments.size()) return; + auto& cache = renderSegmentDiagnostics.segments[index].cache; + if (cache) ++cache->resourceRelease; } void recordRangeResolution(const ContinuousLayerRangeResolution& value, bool bottom, bool top) { diff --git a/test/renderer/retained_underlay_cache.test.cpp b/test/renderer/retained_underlay_cache.test.cpp index 77b1393ec..b675bce7b 100644 --- a/test/renderer/retained_underlay_cache.test.cpp +++ b/test/renderer/retained_underlay_cache.test.cpp @@ -830,6 +830,33 @@ TEST(RenderSegmentDiagnostics, VisibilityChangesMetadataButNotTopologyRevision) EXPECT_EQ(hidden.segments[1].beginIndex, 2); } +TEST(RenderSegmentDiagnostics, OldCacheReleaseCannotChargeReplacementAtSameIndex) { + RetainedUnderlayRuntimeState state; + RenderSegmentPlan configured; + configured.segments = {{RenderSegmentStrategy::Retained, std::nullopt}, + {RenderSegmentStrategy::Continuous, "weather"}}; + ASSERT_TRUE(state.setRenderSegmentPlan(configured)); + const auto oldPlan = resolveRenderSegmentPlan(state.renderSegmentPlanConfig(), segmentLayerIds(), true); + state.recordRenderSegmentResolution(oldPlan, {}); + const auto oldRevision = state.renderSegmentSnapshot().topologyRevision; + + configured.segments = {{RenderSegmentStrategy::Retained, std::nullopt}, + {RenderSegmentStrategy::Continuous, "land"}}; + ASSERT_TRUE(state.setRenderSegmentPlan(configured)); + const auto newPlan = resolveRenderSegmentPlan(state.renderSegmentPlanConfig(), segmentLayerIds(), true); + state.recordRenderSegmentResolution(newPlan, {}); + const auto newRevision = state.renderSegmentSnapshot().topologyRevision; + ASSERT_NE(oldRevision, newRevision); + + state.recordSegmentResourceRelease(0, oldRevision); + auto snapshot = state.renderSegmentSnapshot(); + ASSERT_TRUE(snapshot.segments[0].cache); + EXPECT_EQ(snapshot.segments[0].cache->resourceRelease, 0u); + state.recordSegmentResourceRelease(0, newRevision); + snapshot = state.renderSegmentSnapshot(); + EXPECT_EQ(snapshot.segments[0].cache->resourceRelease, 1u); +} + TEST(RenderSegmentDiagnostics, CacheOffKeepsResolvedTopology) { RetainedUnderlayRuntimeState state; RenderSegmentPlan configured;