fix: checkpoint STEP-097 A1 renderer correctness

This commit is contained in:
tei
2026-09-25 22:26:33 +09:00
parent 720d309364
commit e67e4961b5
14 changed files with 295 additions and 63 deletions

View File

@@ -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)。尚未实施。

View File

@@ -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。

View File

@@ -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 命令。

View File

@@ -5,6 +5,7 @@
#include <mbgl/mtl/texture2d.hpp>
#include <Metal/Metal.hpp>
#include <stdexcept>
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<Texture2D*>(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<Texture2D*>(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<Texture2D*>(colorTexture.get())->getMetalTexture());
colorTarget->setTexture(colorMetalTexture);
} else {
throw std::runtime_error("Metal offscreen color attachment missing");
}
if (depthTexture) {
depthTexture->create();
auto* depthMetalTexture = static_cast<Texture2D*>(depthTexture.get())->getMetalTexture();
if (!depthMetalTexture) throw std::runtime_error("Metal offscreen depth attachment unavailable");
if (auto* depthTarget = renderPassDescriptor->depthAttachment()) {
depthTarget->setTexture(static_cast<Texture2D*>(depthTexture.get())->getMetalTexture());
depthTarget->setTexture(depthMetalTexture);
} else {
throw std::runtime_error("Metal offscreen depth attachment missing");
}
}
if (stencilTexture) {
stencilTexture->create();
auto* stencilMetalTexture = static_cast<Texture2D*>(stencilTexture.get())->getMetalTexture();
if (!stencilMetalTexture) throw std::runtime_error("Metal offscreen stencil attachment unavailable");
if (auto* stencilTarget = renderPassDescriptor->stencilAttachment()) {
stencilTarget->setTexture(static_cast<Texture2D*>(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<Texture2D*>(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<Texture2D*>(colorTexture.get())->getMetalTexture()) return false;
if (needsDepth || (needsStencil && !stencilTexture)) {
if (!depthTexture) return false;
depthTexture->create();
if (!static_cast<Texture2D*>(depthTexture.get())->getMetalTexture()) return false;
}
if (stencilTexture) {
stencilTexture->create();
if (!static_cast<Texture2D*>(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<OffscreenTextureResource>(context, size_, type, depth, stencil)) {}
bool OffscreenTexture::isRenderable() {
try {
auto& texture = getResource<OffscreenTextureResource>().getTexture();
if (!texture) return false;
auto* metalTexture = static_cast<Texture2D*>(texture.get());
metalTexture->create();
return metalTexture->getMetalTexture() != nullptr;
} catch (...) {
return false;
}
return getResource<OffscreenTextureResource>().isRenderable();
}
PremultipliedImage OffscreenTexture::readStillImage() {

View File

@@ -6,6 +6,7 @@
#include <mbgl/util/logging.hpp>
#include <Metal/Metal.hpp>
#include <stdexcept>
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<uint32_t>(*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) {

View File

@@ -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);

View File

@@ -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();

View File

@@ -153,19 +153,22 @@ public:
}
template <typename Func /* void(LayerGroupBase&, size_t originalOrdinal) */>
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<size_t>(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 <typename Func /* void(LayerGroupBase&, size_t originalOrdinal) */>
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<size_t>(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());
}
}
}
}

View File

@@ -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();

View File

@@ -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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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();
}
};

View File

@@ -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;

View File

@@ -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<uint32_t> renderSegmentIndex;
uint64_t segmentTopologyRevision = 0;
std::string renderSegmentLabel;
};

View File

@@ -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<std::mutex> 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) {

View File

@@ -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;