feat: admit settled Symbol render results to retained cache
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
This commit is contained in:
77
docs/20_调查/STEP-119_Symbol结果证书与R缓存Build实现依据_v1.md
Normal file
77
docs/20_调查/STEP-119_Symbol结果证书与R缓存Build实现依据_v1.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# STEP-119 Symbol结果证书与R缓存Build实现依据 v1
|
||||
|
||||
日期:2026-10-11(JST)。状态:IMPLEMENTED_WAITING_ANDROID;Shared Core已有实质实现,尚无本轮编译、GPU、Android或视觉验收结论。
|
||||
|
||||
## 1. 基线与原证据
|
||||
|
||||
仓库 `/Users/tei/dev/MapLibreParticleLab`,Mac.lan / Darwin24.6.0 / x86_64,particle/main@869930046b11dbeb3b147e3be952a2e847316292。Native版本13.6.1-particle.23。Integration本地只读HEAD=80667d6c343411d03a760425438e3d292f452118;已读取AGENTS、instructions、README、WORKFLOW、TEAM_MODEL和contracts/rfc/adr/status/compatibility,未写跨仓文件。纯Renderer内部优化,不新增公共接口、所有权或错误协议;C-001/C-002和Core-owned timeline/playback保持。
|
||||
|
||||
STEP117揭示:Continuous重新计算令placementChanged=true;recent复用设置stale,hasTransitions=true。旧Symbol gate要求changed=false、fade=0、transition=false,两个分支都不能自举。global retainedPlacementStable、retainedStaticStable、retainedSupported、preparedSceneSupported还有独立阻断。
|
||||
|
||||
STEP118保留原63基础层manifest,仅测试副本排除tide-spots:62基础层+2天气层,Native64,R[0,62)、C[62,64),missingSourceIds=[],static-placement、Build0/Reuse0。旧设备90.00806697399821秒FastDelta0/FullDelta942、decisionMismatch0,不是本轮结果。
|
||||
|
||||
旧APK源码3051196054c7b69815c298040dae61d26499d9f2,SHA=44afcfabe0cf4f1ec93f35899241bd73093aa6db4b5cb82ca2120dd531b8f3e9。旧result.json SHA=d3d20fc35bbd17e9f0ea0dd014ca4af8e0c5c5284d9f0c009bc20981d876ff10;screen.png SHA=c8b57a8432626c9209ca9a614a747728d4256bd09273b28c26a793a75a6d44d4。本轮只读解析/核对,文件未修改。
|
||||
|
||||
## 2. 实际渲染生命周期
|
||||
|
||||
1. 正常Full先createRenderTree:Source/style差分、跨瓦片ID匹配、全场Placement碰撞与commit继续使用原实现,没有按R段拆碰撞或冻结新结果。
|
||||
2. RenderTreeImpl::prepare调用Placement::updateLayerBuckets,继续处理text/icon、重复符号、variable anchors、方向、opacity、动态顶点、沿线投影和排序。
|
||||
3. Renderer依次上传source/layer/atlas,updateLayers创建或更新Drawable,processChanges实际应用注册/移除队列。
|
||||
4. 所有正常Full LayerGroup先runTweakers并upload。SymbolLayerTweaker记录**同一份传给GPU的**evaluated props、drawable/tile UBO字段;记录frameCount,并确认evaluated/drawable/tile UBO存在和consolidated容量,未执行、出错、缺buffer或错误帧不能补证。
|
||||
5. 此后采集每R的原生Symbol绘制输入,与上一成功提交Full的证据精确比较。符合就准入首次Build,不依赖已有缓存。
|
||||
6. Build保持原双pass和原始全局ordinal;finishBuild后再检查输入未改变。资源/绘制错误走Fallback;实际主缓冲按原R/C层序composite或live绘制。
|
||||
7. present和endFrame成功返回之后再次检查输入、epoch及错误代次,才记录完成Full/Build证书。它证明成功编码和提交,不宣称GPU fence完成或像素等价已经测量。
|
||||
8. 后续一次正常Full Reuse成功提交后,才允许Fast。Fast使用相同证书版本、content/topology/config、相机、backend viewport、pixelRatio和crossSourceCollisions,并继续原24项Gate。
|
||||
|
||||
普通Full替换旧prepared tree时只销毁引用式RenderItem;证据拥有复制的字节和必要资源引用,跨Full保留。style/context/显式prepared-tree reset仍清证书。symbolCompletedSignature独立保存上一Full的非相机输入,防止glyphURL/fontFaces/placement设置等在旧树销毁后丢失保护。
|
||||
|
||||
## 3. 结果证据包含什么
|
||||
|
||||
| 实际输入 | 采集与保护 |
|
||||
|---|---|
|
||||
| Symbol uniforms | 从SymbolLayerTweaker实际提交的CPU参数复制;包含paint颜色/透明度/halo、tile/label/coord矩阵、尺寸/插值、gamma、text/icon/halo标志;忽略SymbolDrawableUBO末尾12字节非shader padding |
|
||||
| 顶点与实例属性 | 对实际Drawable绑定的VertexAttributeArray/InstanceAttributeArray记录属性索引、类型、stride、offset、vertexOffset和共享原始字节;包含静态布局、动态位置/角度、opacity、data-driven paint、排序实例;共享属性必须已有GPU背板,索引必须已上传且非dirty |
|
||||
| 已释放的不可变buffer | 只在producer已release且存在GPU backing时用保持存活的对象/背板身份;不能读取的可变输入拒绝。不是把任意空数据当相等 |
|
||||
| text/icon/索引与排序 | 原生SymbolBucket对应text/icon/sdfIcon buffer、segment范围、instance/vertex/index长度、sortKey;两条路径均记录实际三角索引字节,release后必须保持不可变索引对象与GPU背板身份;instancing固定quad和segment仍核对 |
|
||||
| fade | 检查每个实际opacity顶点的packed值及当帧fade;必须在放置目标方向达到shader clamp终点,并且真实Placement fade已结束;非有限值、负值、非整数或越界packed值拒绝 |
|
||||
| atlas/texture/sampler | 保持atlas、glyph/icon及实际绑定纹理/原生atlas tweaker身份,核对尺寸/format、上传状态和实际绑定一致。原生tweaker的sampler策略由固定bucket/layout和相同camera/无手势状态决定;glyph/icon像素原地更新由既有全局epoch保护 |
|
||||
| shader | 独立provenance记录只在实际GL/Metal编译源码与原生Symbol源码完全相符、额外宏属于原生uniform白名单时登记;保留shader对象身份。名称相同的未知/替换shader不借用fade契约 |
|
||||
| LayerGroup/层序 | processChanges之后验证Symbol组确实注册,隐藏层无旧注册组,collision额外组或陌生组拒绝;记录全局group总数、原始ordinal、组身份/层索引/数量/enable,以及Drawable实际混合方程/因子/颜色mask、剔除/深度/3D状态。全局depth range与跨段排序因此参与证据 |
|
||||
| 版本与生命周期 | 精确结果revision、成功Build revision、成功Reuse状态;绑定content/topology/config、camera矩阵、viewport、pixelRatio、crossSourceCollisions;原update signature/SourceRevisionBook/Annotation Gate继续使用 |
|
||||
|
||||
每个结果字节缓冲上限16MiB,超限拒绝证书;没有提高公共容量或假装完整证明。资源引用避免地址ABA;比较对象是字节序列而不是散列。既有segment cache revision组合仍保留,但Fast还直接核对证书状态,不能靠组合散列碰撞取得证书;Full Reuse也要求candidateHasBuild精确版本,缺匹配Build时显式invalidate旧Cache,再走Build。
|
||||
|
||||
## 4. Placement与fade语义
|
||||
|
||||
没有修改placementIsRecent、setPlacementStale、原hasTransitions、commit/fadeStartTime、updatePeriod或placement计算频率。原hasTransitions仍用于正常renderer请求刷新。新增内部hasActiveSymbolFade仅表示实际fade;retainedRangeIsStable、retainedSegmentIsStable、retainedPlacementTransitionActive和Fast动态输入改用它。
|
||||
|
||||
GL三个原生Symbol vertex shader和Metal symbol.hpp三类shader均使用unpack后的base opacity加/减fadeChange并clamp到[0,1]。>=1在合法packed值上足以达到fade终点,但不能证明顶点、uniform、atlas或层序相同。因此没有单独用>=1放行,而是逐opacity检查+完整结果比较+提交代次。
|
||||
|
||||
重算仍令placementChanged=true,诊断继续如实记录;它不再被当成最终GPU输入改变。recent/stale仍请求刷新,不能单独代表正在fade。Fast rebase保留被证书确认的Full fade值,不把它清为0来取得准入。
|
||||
|
||||
## 5. 完整准入与失效
|
||||
|
||||
首次Build路径:已完成Full证据 → 下一Full同绑定同输入且已settled → Build编码成功 → present/endFrame及复核 → Build证书。Reuse/Fast路径:同版本缓存 → 正常Full Reuse成功提交 → 原24Gate全部通过 → Fast。preparedSceneSupported与global/perSegment稳定性均使用同一结果证明,不存在只改perSegment条件的旁路。
|
||||
|
||||
以下继续fail-closed:missing Source、CustomMVTVector未知revision、tile fading、未loaded style/sprite/image manager、paint/light transition、非粒子repaint、3D/debug/非continuous、不支持C、Annotation pending、相机/viewport/pixelRatio/非相机参数、异步content或pending changes、资源/编译/上传/绘制错误、未知placement-capable类型或shader(Symbol原生typeInfo指针必须匹配,名称相同不足以强制转换)、证据超限/不可读取。
|
||||
|
||||
Glyph requested/error/loaded、Tile error/changed、Symbol error新增或保持epoch推进;Image missing/remove/update、Source差分、Context reset、Style clear沿用原保护。捕获到的renderer/registry错误推进错误代次与全局content epoch;逃出上传/编码/present的异常由Renderer外围捕获,清prepared/证书、失效所有旧Cache后原样抛出;证书不能使用失败更新的旧Drawables。Build前和成功提交后都有epoch复核,证据观察期间的变化拒绝完成证书。
|
||||
|
||||
现有回调/registry操作运行在renderer调度线程;content revision是原有atomic。此优化不建立后台并发访问Drawable的通道,不改变callback线程合同。真正跨线程竞态仍需RDC平台实测。
|
||||
|
||||
## 6. 反例与保守限制
|
||||
|
||||
- 同placed布尔但variable anchor/角度/opacity不同:实际属性字节变化,revision递增,重新Full取证。
|
||||
- controller不fade但尚未上传/陌生shader/atlas未绑定:complete=false,拒绝证书。
|
||||
- glyph/icon纹理指针不变但内容更新:content epoch变化,旧Build/ReUse证书失配。
|
||||
- Source同名替换、原63层缺tide:SourceRevisionBook/loaded/缺源保护仍拒绝,不能借空RenderItem作零贡献证明。
|
||||
- 下方段删除group导致上方R ordinal/depth改变:全局group数量/ordinal证据变化,上方R不得复用旧结果。
|
||||
- Build存在但没有成功Full Reuse:canFast=false;失败/未完成的Full不能刷新成功提交证据。
|
||||
- Vulkan/WebGPU目前没有本轮原生shader provenance登记,Symbol结果证书保守拒绝;它们的普通Full渲染不变。本轮不宣称跨后端运行通过,也不以GL代替Metal验证。
|
||||
- 16MiB上限、非常大的PBF、持续Glyph/Tile更新、不同native实例buffer生命周期均可能继续阻止Build;须保留真实结果修复,不能提高测试阈值制造PASS。
|
||||
|
||||
## 7. 测试与待证事实
|
||||
|
||||
新增13项Step119SymbolResults,位于已登记的两个test/renderer文件。覆盖真实Controller lifecycle+SymbolBucket opacity/dynamic buffer、packed clamp反例、variable位置/角度、camera/viewport/pixelRatio、CollisionGroups、真实uniform/wrapped tile编码、未知shader、失败提交、Annotation与24Gate、真实Tile/Glyph/Image/Context/Style回调、Source同名替换/缺失和跨段registry移除。
|
||||
|
||||
所有新增测试NOT COMPILED/NOT RUN。部分Build标记和字节payload是状态机fixture,绝不称真实GPU等价;现有CrossTileSymbolLayerIndex和Annotations图像测试必须由RDC一并验证。原PBF跨段collision、实际Glyph/Atlas延迟、Full/Fast图像、真实Build/Reuse/Fast和性能仍全部待独立验收。源码去除逻辑自举死锁,不承诺本机实机目标已实现。
|
||||
@@ -0,0 +1,33 @@
|
||||
# STEP-119 Symbol稳定结果证书与R首次Build/Fast安全准入
|
||||
|
||||
日期2026-10-11。用户批准立即继续实施上一轮讨论的Shared Core方案,不是只读调查。
|
||||
项目 /Users/tei/dev/MapLibreParticleLab,particle/main基线869930046b11dbeb3b147e3be952a2e847316292;Integration /Users/tei/dev/navsea-particle-integration已同步origin/main=80667d6c343411d03a760425438e3d292f452118。实际Codex须GPT-6.1-Sol High(gpt-6.1-sol,model_reasoning_effort=high)。
|
||||
Codex负责开发、定向非Jest测试源码、中文文档,不执行Gradle/ADB/Native大编译/Bazel/Jest/commit/push。RDC独立核验、限定Git收尾并按用户授权集中执行一次APK build、install和真实设备验证。
|
||||
|
||||
## 优先读取
|
||||
根AGENTS.md、PARTICLE_BASELINE.md、docs/00_项目规范、docs/00_项目总则README和4份当前总则;Integration的instructions/PARTICLE_PROJECT_INSTRUCTIONS.md、README.md、WORKFLOW.md、coordination/TEAM_MODEL.md、contracts/、rfc/、adr/、status/、compatibility/。STEP117根因调查 docs/20_调查/STEP-117_PBF_R首次缓存Build安全证据_v1.md 与STEP118真机报告 docs/60_测试/STEP-118_潮汐清理新APK设备实测结果_20261011.md及STEP112-116关键报告。必须逐段核对src/mln/text/placement.cpp、render_orchestrator.cpp、renderer_impl.cpp、render_tree.cpp、retained_underlay_cache.cpp、include/mln/renderer/render_segment.hpp、RenderSymbolLayer/SymbolBucket/atlas/shader、24FastGate/RevisionBook及现有测试。
|
||||
|
||||
## 已证实
|
||||
STEP118原生产63基础PBF图层测试副本只排除一条失效tide-spots(用户允许),保留其它62基础层、天气2层=Native64;R[0,62)、C[62,64),missingSourceIds=[]、R fallback=static-placement、R Cache Build0/Reuse0;90.008s FastDelta0 FullDelta942;PlacementGatePassed=false,24Gate mismatch0。当前源码perSegment Gate对Symbol要求 !placementChanged && symbolFadeChange==0 && !placementTransitionActive;Full新排布置changed=true,recent复用设stale->hasTransitions=true,而Fade完全稳定时通常>=1而非0。独立global retainedPlacementStable/retainedStaticStable/retainedSupported/particlePreparedSceneSupported又阻断。首次Build与Fast复用需要分开。
|
||||
旧设备真实证据在Git忽略artifacts/maplibre-particle/android-validation/step113-retest-20261011_140448/static_rc_no_tide/result.json和screen.png。新测试APK源commit=3051196054c7b69815c298040dae61d26499d9f2、SHA=44afcfabe0cf4f1ec93f35899241bd73093aa6db4b5cb82ca2120dd531b8f3e9。现HEAD较新只包含报告。
|
||||
|
||||
## 本次必须优先真正实施的内容
|
||||
1. 在平台无关的Shared Core内部区分Placement计算调用与最终Renderable Symbol结果变化,建立可检验稳定状态、revisions/epoch/证书。不能用单一布尔值代替真正GPU输入一致性,不能把placed结果布尔不变当成vertices/opacity/atlas全部一致。保留跨瓦片collision、text/icon、variable anchors/方向、fade、glyph/icon/atlas、shader uniform/tweaker及绘制层序语义。必须检查GL和Metal两侧shader的symbolFadeChange clamp行为。普通Full应完成placement、updateBuckets/vertices、processChanges、tweakers/uploads,才决定可安全建立R缓存(同次或下一帧)。Build/reuse需要匹配版本,任何可影响像素的异步状态立即invalid并走Full。
|
||||
2. 解决Placement::placementIsRecent / setPlacementStale / hasTransitions语义和symbolFadeChange的>=1状态对首次Build的逻辑死锁。重新计算 != 真正GPU结果变化;stale不是强制正在fade。fade未完成仍必须阻断,所有真实动态Symbol/tiles/glyphs/Source/style/camera/viewport/pixelRatio/context/Annotation更新都fail-closed。不得简单常置false或单改renderSegmentPlacementGatePassed、删除24Gate、修改计数或强行把其它Symbol放进C。
|
||||
3. 必须同时审查并修复global/perSegment稳定性、revision/state跟踪和preparedSceneSupported,让首次R在安全的Full后建立Build,Reuse后才能Fast。缺失证明的Symbol子类严格拒绝,不能用概括性稳定状态假设可缓存。尽可能真正做到原62基础层静止时Build>0、Reuse>0、FastDelta>0;若架构上无法证明,明示BLOCKED,安全停止危险路径,但应尽量完成有证明的实现。
|
||||
4. 非Jest C++定向测试(已登记test/renderer文件),覆盖真实PlacementController生命周期、fresh->settled->build->reuse、fade过程拒绝、相机/缩放/旋转、纹理字形异步、新Source/style、Annotation、Context、tile/wrap/crossTile collisions、跨段LayerGroup安全重建;不能把无GPU predicate当真正GPU等价。
|
||||
5. 不改RN/JNI/ObjC/Java/Kotlin公共API与ABI、正式PBF/Source/MapLibre外部URL或NavSea/iOS业务、不变更Core-owned timeline/playback、不能改天气层语义。跨项目公共接口新语义须Integration RFC/ADR授权,必要则只报告并保留旧合同。
|
||||
6. 保持原STEP113 63 PBF和新STEP118 STATIC_RC_NO_TIDE派生测试、原24Gate、现有真实version/SHA的严格核对。不能新增虚构tide数据。
|
||||
7. 不触碰用户两个未跟踪 docs/40_任务/STEP-110_...与STEP-111_...,不git add . / add -A / reset --hard / clean;不修改Git忽略旧真机证据。Codex不运行Gradle/ADB/Jest/Native大编译、也不commit/push,最后交RDC。
|
||||
|
||||
## 必做中文输出文件(缺一不得完成)
|
||||
- 实际Core实现+必要非Jest C++测试源码及直接相关诊断/README改动。
|
||||
- docs/20_调查/STEP-119_Symbol结果证书与R缓存Build实现依据_v1.md:真实渲染生命周期、核心不变量、结果证书如何绑定真实GPU绘制、反例、风险。
|
||||
- docs/50_Step记录/STEP-119_Symbol缓存实际实现_v1.md:状态、变更、验证、下一步。
|
||||
- docs/60_测试/STEP-119_原PBF_FAST实机联合验收计划_v1.md:一次build/install,新STATIC_RC_NO_TIDE >=90秒真实Build/Reuse/Fast+24Gate mismatch0,原63负对照及source/style/camera/glyph/annotation/context负例,视觉核验与fail closed。
|
||||
- docs/报告/STEP-119_Symbol缓存_Codex完成报告_v1.md:真实模型档位、文件、已实施/未实施、清楚BLOCKED或IMPLEMENTED_WAITING_ANDROID、静态检查、RDC命令、git commit由RDC负责。
|
||||
- 粒子开发记录.md在末尾中文追加。
|
||||
Codex只跑轻量静态范围和git diff --check;不可宣称Native/Android/iOS/用户视觉PASS。
|
||||
|
||||
## RDC严格实机目标
|
||||
有实质Shared Core行为修改且独立审计通过后,Mac标准 ./tools/particle_android_validation/build_validation_apk.sh 一次Android OpenGL x86_64 APK;设备FZG1-2 ADB serial 192.168.200.139:5555且全部命令显式-s;运行 python3 -B tools/particle_android_validation/verify_step113_fast_path.py --scenario STATIC_RC_NO_TIDE --seconds 90。必须真实R Build>0、Reuse>0、FastDelta>0、Symbol画面/24Gate一致、更新时缓存失效与Full正确。失败最多5轮当前步骤修复,不能跳过并声称通过。无行为改动则不重编毫无意义APK。
|
||||
51
docs/50_Step记录/STEP-119_Symbol缓存实际实现_v1.md
Normal file
51
docs/50_Step记录/STEP-119_Symbol缓存实际实现_v1.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# STEP-119 Symbol缓存实际实现 v1
|
||||
|
||||
日期:2026-10-11(JST)。状态:进行中;代码阶段IMPLEMENTED_WAITING_ANDROID,独立验收未完成。
|
||||
|
||||
## 目标与基线
|
||||
|
||||
实现Shared Core内部最终Symbol结果证书,解决静止真实PBF派生R首次Build逻辑自举死锁,支持后续Reuse/Fast,同时保护所有真实渲染失效与原24Gate。
|
||||
|
||||
particle/main@869930046b11dbeb3b147e3be952a2e847316292;Mac.lan/Darwin24.6.0/x86_64。Native13.6.1-particle.23;Integration本地HEAD80667d6c343411d03a760425438e3d292f452118,只读。实际会话rollout 01a1299f-0c2b-78b0-8599-e50812c108dd的turn_context核实gpt-6.1-sol/high,未升级、未启动子代理。
|
||||
|
||||
## 必做项
|
||||
|
||||
- [x] 读取根规则、规范/总则、当前任务、Integration规则与STEP112–118关键证据,核对实际Renderer/Placement/Bucket/Atlas/GL/Metal。
|
||||
- [x] 实现真实上传输入证据、revision/epoch、完成Full/Build/Reuse绑定,global/perSegment/preparedScene同时接线。
|
||||
- [x] 分离stale刷新需求与真实fade;保留原Placement算法/原Style/Source/公开API/ABI。
|
||||
- [x] 完成13项非Jest C++针对性测试源码,两个文件均已登记。
|
||||
- [x] 中文调查、阶段记录、实机联合验收计划、完成报告与开发记录追加。
|
||||
- [x] 轻量静态范围/原证据哈希/git diff --check检查。
|
||||
- [ ] Native编译与13项GTest实际运行,GL/Metal真实代码路径独立验证。
|
||||
- [ ] 同源新APK/设备SHA与>=90秒真实Build>0、Reuse>0、FastDelta>0、24Gate mismatch0。
|
||||
- [ ] 原63缺tide负对照、全部失效矩阵、Full/Fast视觉与iOS/Metal独立验收。
|
||||
|
||||
## 允许与禁止范围
|
||||
|
||||
仅内部Shared Core/原生shader来源薄接入、已登记C++测试与本Step文档。未修改include公开头、RN/JNI/ObjC/Java/Kotlin、正式PBF/URL/Style、天气语义、Core-owned timeline/playback、发行版本或Integration协议。
|
||||
|
||||
没有Gradle/ADB/Native构建/Bazel/Jest/codegen/commit/push;没有更改STEP110/111未跟踪任务书或Git忽略原设备证据。没有修改长期项目总则、RDC Runner或后台执行器。
|
||||
|
||||
## 当前结果
|
||||
|
||||
两次成功Full取证的同一settled GPU输入版本可建立R Build,成功Full Reuse后才允许Fast。真实opacity clamp、动态/paint属性、UBO、索引/排序/tiles、atlas/shader身份、registry ordinal/depth与异步epoch共同参与;缺失证明仍Full。
|
||||
|
||||
普通Full销毁旧RenderTree时保留复制证据,真正reset时清理;否则两帧比较仍会自举失败。原24Gate evaluator/掩码/顺序/计数和公开诊断结构没有修改。STEP117历史保守helper与断言保留,生产接线改为最终结果证书。
|
||||
|
||||
源码没有运行验证,不宣称当前设备已从STEP118的Build0/Reuse0/Fast0改善。证书明确绑定编码/提交成功,GPU完成与像素仍需独立验收。Vulkan/WebGPU无本轮原生shader来源登记,Symbol证书继续fail-closed,普通Full不变。
|
||||
|
||||
## 阻塞与下一步
|
||||
|
||||
本工作区未锁定源码;既有验证器会拒绝tracked dirty,不能绕过。历史Mac缺libuv,本轮没有重跑配置。新的Native可编译性、真实PBF输入是否低于16MiB、实际GPU等价、异步竞态与性能均待证。
|
||||
|
||||
立即交RDC按本Step联合验收计划独立审计、在其授权范围完成源码锁/定向Native验证,并集中一次APK build/install/真实设备验收。失败保存证据,按固定Codex修复→RDC验证闭环;未通过不得标完成/冻结,不自行执行下一阶段。
|
||||
|
||||
## RDC独立编译检查记录(2026-10-11 JST)
|
||||
|
||||
用户明确要求编译然后测试,并要求不要因本次功能无新第三方依赖而破坏已存在原生环境。RDC先同步核实Integration origin/main=80667d6c343411d03a760425438e3d292f452118与Particle origin/particle/main=869930046b11dbeb3b147e3be952a2e847316292,且保护的两份STEP110/111未跟踪MD的SHA未变。
|
||||
|
||||
Mac Metal原生CMake配置首次因PATH找不到cmake而失败,改用既有/usr/local/opt/cmake/bin/cmake后,配置因早已缺少的libuv失败(不是本轮代码引入的依赖)。Homebrew建议连带升级CMake、Python和OpenSSL,RDC及时中断该安装并复核原CMake仍存在、无libuv新依赖安装。**Mac Metal CMake/GTest仍未运行,不能记PASS。**
|
||||
|
||||
改走原项目已存在 Android NDK OpenGL/x86_64 compile_commands.json,保持实际C++20警告/Werror、不加Wno-error、不切宏、不改源数据。编译实际Core生产7文件(placement.cpp、render_symbol_layer.cpp、symbol_layer_tweaker.cpp、render_orchestrator.cpp、renderer_impl.cpp、renderer.cpp、shader_program_gl.cpp)与两个非Jest已注册GTest源码,合计9个编译单元,经过本地定点修复后全部严格 -fsyntax-only exit0(每文件独立日志保存在Git忽略artifacts/maplibre-particle/step119-native-syntax/)。本轮修复3处小编译缺口:SymbolLayer及Retained test各一处toString补上util命名空间,Orchestrator error test将构造结果从const Mutable转换为Immutable,保留测试语义。该结果是**Android NDK语法/类型检查**,非真实链接、GTest运行或Metal构建。
|
||||
|
||||
下一关:RDC scoped Git锁定源码后,基于新源码一次集中标准Android Gradle/OpenGL x86_64 APK构建;FZG1-2安装和STATIC_RC_NO_TIDE 90秒验证。任何编译/设备错误继续按原步修复,不能绕过24Gate或声称Fast已通过。
|
||||
89
docs/60_测试/STEP-119_原PBF_FAST实机联合验收计划_v1.md
Normal file
89
docs/60_测试/STEP-119_原PBF_FAST实机联合验收计划_v1.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# STEP-119 原PBF FAST实机联合验收计划 v1
|
||||
|
||||
日期:2026-10-11(JST)。执行者:RDC/用户,Codex本轮仅交源码和命令。状态:NOT RUN;没有新的Native、Android、Metal或视觉PASS。
|
||||
|
||||
## 1. 先独立审计,再锁定源码
|
||||
|
||||
读取AGENTS、STEP119任务、调查、Step、完成报告及实际diff,检查原Style/API/ABI/24Gate/SourceRevisionBook、原STEP117/118证据与两个保护任务书。重点核对:普通Full不清结果证据、证书在upload之后采集、成功present之后完成、Build不能借Reuse自举、Reuse成功后才Fast、所有缺证/变更/错误仍拒绝。
|
||||
|
||||
此工作区尚有本轮未提交源码。现有verify_step113_fast_path.py严格要求tracked clean与APK来源HEAD一致,会报SOURCE_NOT_LOCKED;本轮保留该保护,不能添加dirty bypass、借旧APK或只提交报告来通过预检。源码锁定由RDC按其阶段明确授权scoped处理;Codex没有commit/push。任何Git收尾不得包含STEP110/111、无关dirty或旧ignored artifacts;若push已获授权,必须回读远端HEAD。
|
||||
|
||||
如果仍无法审计实际GPU输入覆盖、shader provenance、提交/恢复或异步线程边界,保持BLOCKED,不进入APK周期。
|
||||
|
||||
## 2. Native定向验证(仅交RDC执行)
|
||||
|
||||
在本轮最终锁定源码上,逐条执行;任一步失败立即停止后续命令并保存首个真实error、target、exit、耗时、完整日志路径。旧Mac缺libuv是历史环境证据,本轮未重试/未安装依赖,不能推断当前结果。
|
||||
|
||||
```bash
|
||||
cd /Users/tei/dev/MapLibreParticleLab
|
||||
cmake --preset macos-metal
|
||||
cmake --build build-macos-metal --target mbgl-test-runner --parallel 3
|
||||
build-macos-metal/mbgl-test-runner --gtest_filter='Step119SymbolResults.*' --gtest_list_tests
|
||||
build-macos-metal/mbgl-test-runner --gtest_filter='Step119SymbolResults.*:Step117Safety.*:Step116Safety.*:Step114FastPathSafety.*:Annotations.*:CrossTileSymbolLayerIndex.*:RenderOrchestratorError.*:RenderOrchestratorTeardown.*:RetainedUnderlayCache.*:RetainedUnderlayRevision.*:FastGateTrace.*:ParticleFastPath.*:RenderSegmentPlacementFree.*:RenderSegmentLocality.*:RenderSegmentPlan.*:RenderSegmentDiagnostics.*' --gtest_color=no
|
||||
```
|
||||
|
||||
必须发现并运行13个新增Step119SymbolResults;不得用零匹配/旧runner/仅fsyntax-only称GTest PASS。renderer=Metal、target=mbgl-test-runner、并行3;Host Native测试不能替代iOS真机或Android OpenGL。本轮没有Jest,没有Bazel/codegen授权。
|
||||
|
||||
RDC还须按已有Android OpenGL/x86_64真实compile_commands.json独立核查修改的Core/GL及两个测试文件,保持-Werror,不附加-Wno-error;语法结果单独记录。Metal context/provenance/UBO consolidation和原生source组合须编译实际路径,不能靠宏关闭绕过。
|
||||
|
||||
## 3. 一次集中APK build/install与严格来源闭环
|
||||
|
||||
独立审计及源码锁通过后,由RDC/用户按本阶段授权执行一次标准流水线;任何失败不得安装旧文件,不重复启动并发构建。本轮不是正式发行,不改VERSION/Source Lock/公开坐标。
|
||||
|
||||
```bash
|
||||
cd /Users/tei/dev/MapLibreParticleLab
|
||||
./tools/particle_android_validation/build_validation_apk.sh
|
||||
adb -s 192.168.200.139:5555 install -r artifacts/maplibre-particle/android-validation/MapLibreAndroidTestApp-validation-opengl-debug-x86_64.apk
|
||||
python3 -B tools/particle_android_validation/verify_step113_fast_path.py --serial 192.168.200.139:5555 --preflight-only
|
||||
python3 -B tools/particle_android_validation/verify_step113_fast_path.py --serial 192.168.200.139:5555 --scenario STATIC_RC_NO_TIDE --seconds 90
|
||||
```
|
||||
|
||||
固定FZG1-2、x86_64、OpenGL Debug、Java17。ADB每条显式-s,包括人工补采样命令。流水线记录build-info、branch/HEAD/dirty、完整build.log、APK大小/ZIP/ABI/strip及SHA;设备base.apk SHA必须与本次本地APK相同。版本号仍.23不足以判定新源码,旧3051196054/44afcfabe… APK不得冒充本轮。
|
||||
|
||||
原STEP118 result.json与screen.png只读保留,不覆盖、移动或加入Git。现有verifier固定生成新的step113-retest-<timestamp>/<scenario>目录(Git忽略);保留此真实输出路径,在RDC交付报告标记为STEP119本轮证据,不改脚本或覆盖旧目录。
|
||||
|
||||
## 4. 静止真实PBF正向验收
|
||||
|
||||
| 项目 | 必须取得的真实结果 |
|
||||
|---|---|
|
||||
| 原始资源 | 相同正式PBF/Sprite/Glyph/Tile URI,不修改服务器或造tide;manifest保留原63基础层 |
|
||||
| 派生场景 | STATIC_RC_NO_TIDE,唯一排除tide-spots/tide;有效基础62+天气2,Native64,R[0,62)、C[62,64),missingSourceIds=[] |
|
||||
| 启动/settle | 正常Full完成原生Placement/vertices/opacity/atlas/UBO;fade未完成拒绝;不得预置Cache有效 |
|
||||
| 首次Build | R真实Build>0,texture/composite创建与Build后的static/build revision匹配,placementGatePassed=true,截图仍包含其余Symbol |
|
||||
| Reuse | R真实Reuse>0;成功Full Reuse先于Fast完成,不能只在状态机测试手工mark |
|
||||
| Fast | PID确认之后持续观察>=90秒,FastDelta>0;FullDelta、真实窗口和生命周期分别记录,不把生命周期累计当区间增量 |
|
||||
| 24Gate | 完整120帧knownMask=0xffffff,decisionMismatch=0;candidate帧24项全部通过、completed fast-rendered;失败帧保留全部失败位 |
|
||||
| 像素/层序 | 同相机和资源对照强制Full与开启retained后的截图;用户核对航标/文字/图标/重叠/方向/沿线/variable anchors及WeatherColor/Particle层序,无缺失或ghost |
|
||||
|
||||
自动验证器已有真实Build/Reuse/Fast检查,不改判据;完整120帧通过现有EXPORT LAST SNAPSHOT取得。保存场景开始、Build后、Reuse后及Fast稳定的快照/日志,pre是上一完成树、post是当前完成树,不能跨帧拼接条件。没有图像证据时即使计数增长仍为PARTIAL。
|
||||
|
||||
## 5. 原63层负对照与变更矩阵
|
||||
|
||||
在同一个新APK中运行原STATIC_RC至少90秒,原63基础层+天气2保持;missingSourceIds=[tide]、missing-source、R不Build、不Reuse、不Fast。这是缺源负对照,不是需要被此次证书放行的正向目标。PBF63/PARTICLE_OFF/PARTIAL_RCRR/SOURCE_CHANGE原入口与定义保留,不能用STEP114烟测替代。
|
||||
|
||||
每项动作保存操作前、动作提交时和稳定后证据;至少保留一次真实失效后的Full与新Build/Reuse。500ms之后的摘要不足以证明瞬态,必要时现有完整环形快照与平台trace补证。
|
||||
|
||||
| 负例 | 安全预期与证据 |
|
||||
|---|---|
|
||||
| Source数据/同名Source替换/恢复 | Native source/global revision与lastMutation变化;旧证书失配、Full先执行;不能复用同名旧GPU结果 |
|
||||
| Style布局/paint/显隐/层序、glyphURL/fontFaces | signature或结果/segment revision变化;真实颜色/透明度/位置正确;恢复仍重新取证 |
|
||||
| pan/zoom/rotate/pitch、手势/动画 | #7/#8或动态保护失效,旧camera结果不composite;停稳重新Build,Symbol没有跳位/碰撞残影 |
|
||||
| viewport/pixelRatio | #6/#8及新binding,缓存按新尺寸重建,没有拉伸或旧FBO |
|
||||
| Glyph/Icon/Atlas延迟加载与替换 | requested/error/loaded/image epoch实际更新;opacity/vertex/atlas已上传才取证,旧纹理不可凭指针复用 |
|
||||
| text/icon fade、variable anchor/方向/沿线 | fade过程中拒绝,settled不靠fade==0;新顶点/UBO或排序改变时旧证书无效 |
|
||||
| tile边界/world wrap/crossTile碰撞/跨Source | 正常全场Placement不拆段,异步tile epoch和最终多段结果对应;碰撞受影响的另一R也正确重建 |
|
||||
| 跨段LayerGroup添加/移除/隐藏Symbol | pending先拒绝;processChanges后全局数量/ordinal/深度和注册关系更新,不能使用旧上方R |
|
||||
| 真实Annotation添加/更新/删除 | #24或Source/update输入失效,先Full;原SDK helper恢复正确、没有遗漏Annotation。执行既有Annotations.Step116图像测试 |
|
||||
| context丢失/后台恢复、缓存关闭再开、Style clear/reload | GPU资源与prepared结果重置,旧证书不能跨代;退出没有旧Drawables/资源悬挂 |
|
||||
| shader替换/未知placement-capable类型/不可读或超限输入 | strict拒绝证书,普通Full继续;不能用相同shader名或空数据取得证明 |
|
||||
| 上传/绘制/提交错误 | error/content revision变化,失败Full不记已完成证明,保存失败现场 |
|
||||
|
||||
设备缺少某个可控入口时写NOT VERIFIED并给出具体限制,不新增业务数据、不修改正式Style或公开接口。完整测试源码的predicate/模拟buffer只验证局部不变量,不能替代这张设备矩阵。
|
||||
|
||||
## 6. 结果判定、修复闭环与交付证据
|
||||
|
||||
实现审计、Native构建、GTest、APK自验、来源/设备SHA、90秒正向、原63负对照、各负例、视觉、Metal/iOS分别记录。全部必需项实际通过之前不写全任务PASS。
|
||||
|
||||
FastDelta=0、Build=0、Reuse=0、24Gate mismatch非零或可见Symbol异常:保存新的result.json、完整120帧、screenshots、crash log、source HEAD/APK与设备SHA,报告FAIL/PARTIAL/BLOCKED,不降低阈值。最多5轮当前阶段明确修复;编译/测试首次真error保留,架构或公共协议问题交Integration,不能跳下一阶段或改Contract。
|
||||
|
||||
RDC回传必要信息:命令、exit、耗时、首错/target及日志路径、实际用例数量、正负场景Layer/Source/R/C清单、Build/Reuse/Full/Fast增量、24Gate window、frame输入来源、视觉判定与未验项目。本计划命令全部尚未由Codex运行。
|
||||
88
docs/报告/STEP-119_Symbol缓存_Codex完成报告_v1.md
Normal file
88
docs/报告/STEP-119_Symbol缓存_Codex完成报告_v1.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# STEP-119 Symbol缓存 Codex完成报告 v1
|
||||
|
||||
日期:2026-10-11(JST)。结论:**IMPLEMENTED_WAITING_ANDROID**。Shared Core实现与13项非Jest测试源码已完成;Native可编译性、测试运行、真实R Build/Reuse/Fast和视觉结果均未验收,不能写全任务PASS。现阶段立即交RDC独立审计与验证。
|
||||
|
||||
## 1. 真实执行环境与授权
|
||||
|
||||
- 仓库:/Users/tei/dev/MapLibreParticleLab;Mac.lan,Darwin24.6.0,x86_64。
|
||||
- 分支:particle/main;开工/交付HEAD均为869930046b11dbeb3b147e3be952a2e847316292,本轮没有Git提交或push。
|
||||
- 实际模型/推理档位:gpt-6.1-sol / high,来自本会话rollout的turn_context核实,未升级、未启动子代理或另一个Codex实例。核实文件:/Users/tei/.codex/sessions/2026/10/11/rollout-2026-10-11T15-20-51-01a1299f-0c2b-78b0-8599-e50812c108dd.jsonl。
|
||||
- Native版本仍13.6.1-particle.23,未更改发行坐标。Integration本地HEAD80667d6c343411d03a760425438e3d292f452118;已读取当前全部要求的协作规则/协议,只读,无跨仓写入。
|
||||
- 当前任务仅内部Renderer实现、两个已登记测试文件与四份中文文档/开发记录。禁止的构建、设备、Jest及Git写操作全部未执行。
|
||||
|
||||
## 2. 已实施的行为
|
||||
|
||||
原死锁是多个条件共同导致:新Placement令changed=true、recent令stale/hasTransitions=true、已结束fade通常>=1,并且global/perSegment/preparedScene存在独立阻断。本轮实现最终绘制输入证书,没有只改单个布尔条件。
|
||||
|
||||
正常Full保持原全场Placement/crossTile碰撞、updateLayerBuckets、动态/opacity/paint顶点、processChanges和原Style语义。先运行原生Symbol tweaker及上传,核查UBO容量、共享属性GPU背板与非dirty索引,再复制实际UBO、Drawable绑定属性、text/icon索引与segments/排序、wrap tile、atlas/texture/shader身份、实际混合/剔除/深度状态、全局组数/ordinal和原注册关系。精确字节比较不以placed布尔或hash代替GPU输入。
|
||||
|
||||
同绑定、同settled输入与上一成功提交Full相等时准入首次Build;成功present/endFrame及再次复核后,建立匹配Build revision。下一成功Full Reuse之后才Fast。Full Reuse也检查精确candidateHasBuild,防止组合revision碰撞借旧结果。普通Full替换旧prepared tree保留复制证据;真实reset仍清空。global稳定性、perSegment Gate和preparedSceneSupported全部接入同一证书。
|
||||
|
||||
新增内部hasActiveSymbolFade区分刷新stale与真实fade,旧hasTransitions继续用于正常刷新。GL/Metal原生opacity clamp逐顶点检查,未结束fade/异常packed值拒绝;Fast保留被认证的Full fade值。只有实际编译的原生GL/Metal Symbol源码及合法uniform宏才能登记shader来源,名字相同的未知shader拒绝。
|
||||
|
||||
Source/style/tile/glyph/image/atlas/camera/viewport/pixelRatio/context/Annotation/update signature、ready与异步epoch保护继续保留;补上Glyph requested/error、Tile/Symbol error。注册/绘制错误推进error/content代次;上传/编码/提交异常清旧缓存和证书后原样抛出。未知Symbol类型须原生typeInfo身份一致;pending/旧隐藏组/collision额外组/不可读或超16MiB输入均保守拒绝。原24Gate evaluator、顺序、mask、诊断公开结构和计数判据未修改。
|
||||
|
||||
## 3. 实际修改文件(共25个)
|
||||
|
||||
| 文件 | 变更用途 |
|
||||
|---|---|
|
||||
| src/mln/renderer/symbol_render_result.hpp(新增) | 精确输入、合法fade终点、绑定、完成Full/Build/Reuse证书 |
|
||||
| src/mln/renderer/symbol_render_inputs.hpp(新增) | 实际vertex/instance属性字节与不可变buffer身份 |
|
||||
| src/mln/renderer/symbol_shader_provenance.hpp(新增) | 原生shader来源及uniform宏白名单 |
|
||||
| src/mln/renderer/layers/render_symbol_layer.hpp | 内部结果采集入口与原group身份 |
|
||||
| src/mln/renderer/layers/render_symbol_layer.cpp | 原生Symbol实际Drawable/Bucket/Atlas/索引/绘制状态取证 |
|
||||
| src/mln/renderer/layers/symbol_layer_tweaker.hpp | 实际UBO证据与执行帧记录 |
|
||||
| src/mln/renderer/layers/symbol_layer_tweaker.cpp | 复制实际上传字段,覆盖consolidation两路径 |
|
||||
| src/mln/text/placement.hpp | 内部真实fade查询声明 |
|
||||
| src/mln/text/placement.cpp | 真实fade查询,保留原stale/placement频率 |
|
||||
| src/mln/renderer/render_orchestrator.hpp | 内部采集/error代次与visitor错误失效 |
|
||||
| src/mln/renderer/render_orchestrator.cpp | registry/source/global取证、真fade与异步epoch |
|
||||
| src/mln/renderer/renderer_impl.hpp | 证书容器及独立完成signature |
|
||||
| src/mln/renderer/renderer_impl.cpp | 上传后准入、global/perSegment/prepared/Fast、Build/Reuse/完成复核 |
|
||||
| src/mln/renderer/renderer.cpp | 普通Full保留证据,逃出编码/提交异常清缓存 |
|
||||
| src/mln/renderer/render_tree.hpp | Fast保留认证fade参数 |
|
||||
| src/mln/shaders/gl/shader_program_gl.cpp | 原生GL编译来源薄接入 |
|
||||
| src/mln/mtl/context.cpp | 原生Metal源码/entry/宏薄接入 |
|
||||
| src/mln/shaders/mtl/shader_program.cpp | Metal来源生命周期撤销 |
|
||||
| test/renderer/retained_underlay_cache.test.cpp | 新增9项Step119SymbolResults |
|
||||
| test/renderer/render_orchestrator_error.test.cpp | 新增4项Step119SymbolResults |
|
||||
| docs/20_调查/STEP-119_Symbol结果证书与R缓存Build实现依据_v1.md(新增) | 生命周期、不变量、反例、限制 |
|
||||
| docs/50_Step记录/STEP-119_Symbol缓存实际实现_v1.md(新增) | 本阶段状态、已做/未验与下一步 |
|
||||
| docs/60_测试/STEP-119_原PBF_FAST实机联合验收计划_v1.md(新增) | RDC精确命令、来源锁、正负场景/失效/视觉矩阵 |
|
||||
| docs/报告/STEP-119_Symbol缓存_Codex完成报告_v1.md(新增) | 本报告 |
|
||||
| 粒子开发记录.md | 仅末尾追加STEP119中文记录 |
|
||||
|
||||
没有修改include公开API头、RN/JNI/ObjC/Java/Kotlin、正式PBF/Style/URL、STEP118派生场景、Integration协议、天气层或Core-owned timeline/playback。未修改测试登记/build规范、验证器、STEP110/111任务书、历史报告与ignored原设备证据。
|
||||
|
||||
## 4. 已执行检查与结果
|
||||
|
||||
- 仓库/Integration规则、当前源码/任务与STEP112–118关键证据阅读;实际branch/HEAD/status/version、模型turn_context核实。
|
||||
- 轻量静态接线复核:Full/Build/Reuse/Fast、原24Gate、SourceRevisionBook、GL/Metal source与clamp、reset/error路径、测试登记与验收命令。
|
||||
- git diff --check:exit0;这是空白检查,不是C++编译或测试。
|
||||
- 13项新增TEST源码静态计数,两个文件已有CMake登记;实际共享CPU vector无GPU背板/已release无背板反例纳入失败测试;未修改STEP117/118既有断言。
|
||||
- 静态文件范围、开发记录仅追加、公开头/Style/设备证据及两个保护任务书哈希核查。具体保护SHA见下节。
|
||||
|
||||
本轮未执行CMake/Ninja/Native编译、fsyntax-only、GTest、Metal/iOS运行、Gradle/APK构建、ADB安装/设备命令、Bazel/codegen、Jest、commit/push或网络远端HEAD回读。没有新的APK/设备SHA、GPU fence、截图或计数结果;没有凭进程退出或静态检查声称PASS。未产生新的编译/测试失败日志;旧libuv环境问题仅作为历史限制。
|
||||
|
||||
## 5. 原证据与保护边界
|
||||
|
||||
STEP118旧真机事实:原63→派生62基础Layer+天气2=Native64,R[0,62)、C[62,64),missingSourceIds=[];fallback=static-placement,Build0/Reuse0;90.00806697399821秒FastDelta0/FullDelta942、24Gate mismatch0。旧APK源3051196054c7b69815c298040dae61d26499d9f2,SHA44afcfabe0cf4f1ec93f35899241bd73093aa6db4b5cb82ca2120dd531b8f3e9。这些是旧失败证据,不能改写成本轮成功。
|
||||
|
||||
| 只读保护文件 | SHA-256 |
|
||||
|---|---|
|
||||
| 未跟踪STEP110任务MD | 33037df71b4b30aa7956d59f4b99f77af3085405b24111bc0fe11ec857a93db0 |
|
||||
| 未跟踪STEP111任务MD | f3a5806f25384bf46055db2edfea0d21bfbc4124184bc813b46e38b8079c1ec4 |
|
||||
| artifacts/maplibre-particle/android-validation/step113-retest-20261011_140448/static_rc_no_tide/result.json | d3d20fc35bbd17e9f0ea0dd014ca4af8e0c5c5284d9f0c009bc20981d876ff10 |
|
||||
| 同目录screen.png | c8b57a8432626c9209ca9a614a747728d4256bd09273b28c26a793a75a6d44d4 |
|
||||
|
||||
## 6. 未验收、阻塞和RDC交接
|
||||
|
||||
- 当前源码尚未由RDC独立审计/锁定。现有verifier要求tracked clean以及APK来源HEAD/本地与设备SHA一致,会对当前dirty报SOURCE_NOT_LOCKED;这不是本轮已运行报错,而是读取代码确认的预检。Codex无commit/push授权,不绕过保护,Git收尾由RDC在其明确阶段授权内scoped负责。
|
||||
- 所有新增C++代码/13项测试仍NOT COMPILED/NOT RUN。状态机fixture的手工Build标记、模拟payload/CollisionGroups不等于真实Symbol GPU、碰撞或像素等价,必须另跑真实路径。
|
||||
- Android原STATIC_RC_NO_TIDE Build>0/Reuse>0/FastDelta>0尚未证实;原63缺tide负对照仍应拒绝。Source/Style/相机/异步/Annotation/Context矩阵与Full/Fast视觉全部待证。
|
||||
- Metal/iOS实际source/UBO/instancing路径尚未验证;Vulkan/WebGPU无本轮provenance登记,Symbol缓存继续保守拒绝,普通Full不变。
|
||||
- 16MiB输入上限、数据量、取证CPU/内存成本、真实异步竞态与平台GPU完成仍是待测限制。超限或缺证明继续Full,不能调低验收判据制造通过。
|
||||
|
||||
RDC先读本报告/实际diff,按[联合验收计划](../60_测试/STEP-119_原PBF_FAST实机联合验收计划_v1.md)锁源码并执行准确命令:macos-metal配置、mbgl-test-runner编译、13项发现与定向GTest;Android GL真实编译参数核查。通过后集中一次标准APK流水线/install,以显式serial 192.168.200.139:5555验证STATIC_RC_NO_TIDE至少90秒、真实R Build/Reuse/Fast与120帧24Gate mismatch0,再做原63负对照及变更/视觉矩阵。
|
||||
|
||||
RDC须保留新锁定HEAD、本地APK/设备SHA、命令/exit/耗时/日志、测试数量、Layer/Source/R/C清单、Build/Reuse/Full/Fast区间增量、完整24Gate快照、截图与用户视觉结果。失败保存首个真实错误/现场,最多5轮当前Step修复,不跳后续阶段。源码交付到此停止;独立验收尚未完成。
|
||||
@@ -19,6 +19,9 @@
|
||||
#include <mln/renderer/render_target.hpp>
|
||||
#include <mln/shaders/mtl/clipping_mask.hpp>
|
||||
#include <mln/shaders/mtl/shader_program.hpp>
|
||||
#include <mln/shaders/mtl/symbol.hpp>
|
||||
#include <mln/shaders/mtl/common.hpp>
|
||||
#include <mln/renderer/symbol_shader_provenance.hpp>
|
||||
#include <mln/shaders/program_parameters.hpp>
|
||||
#include <mln/util/traits.hpp>
|
||||
#include <mln/util/std.hpp>
|
||||
@@ -182,6 +185,26 @@ UniqueShaderProgram Context::createProgram(shaders::BuiltIn shaderID,
|
||||
std::move(name), backend, std::move(vertexFunction), std::move(fragmentFunction));
|
||||
observer->onPostCompileShader(shaderID, gfx::Backend::Type::Metal, defineStr);
|
||||
|
||||
const auto matchesSymbol = [&]<shaders::BuiltIn ID>() {
|
||||
using Source = shaders::ShaderSource<ID, gfx::Backend::Type::Metal>;
|
||||
return source == std::string(shaders::prelude) + Source::prelude + Source::source &&
|
||||
vertexName == Source::vertexMainFunction && fragmentName == Source::fragmentMainFunction;
|
||||
};
|
||||
bool originalSymbol = false;
|
||||
switch (shaderID) {
|
||||
case shaders::BuiltIn::SymbolIconShader:
|
||||
originalSymbol = matchesSymbol.template operator()<shaders::BuiltIn::SymbolIconShader>(); break;
|
||||
case shaders::BuiltIn::SymbolSDFShader:
|
||||
originalSymbol = matchesSymbol.template operator()<shaders::BuiltIn::SymbolSDFShader>(); break;
|
||||
case shaders::BuiltIn::SymbolTextAndIconShader:
|
||||
originalSymbol = matchesSymbol.template operator()<shaders::BuiltIn::SymbolTextAndIconShader>(); break;
|
||||
default: break;
|
||||
}
|
||||
for (const auto& [name_, value] : additionalDefines) {
|
||||
originalSymbol = originalSymbol && symbolUniformDefine(name_) && value.empty();
|
||||
}
|
||||
if (originalSymbol) SymbolShaderProvenance::get().compiledOwned(*shader);
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
#include <mln/gfx/collision_drawable_data.hpp>
|
||||
#include <mln/renderer/layer_group.hpp>
|
||||
#include <mln/renderer/layers/symbol_layer_tweaker.hpp>
|
||||
#include <mln/renderer/symbol_render_inputs.hpp>
|
||||
#include <mln/renderer/symbol_shader_provenance.hpp>
|
||||
#include <mln/renderer/update_parameters.hpp>
|
||||
#include <mln/shaders/shader_program_base.hpp>
|
||||
#include <mln/shaders/symbol_layer_ubo.hpp>
|
||||
@@ -473,6 +475,147 @@ std::size_t RenderSymbolLayer::removeAllDrawables() {
|
||||
return stats.drawablesRemoved - oldValue;
|
||||
}
|
||||
|
||||
SymbolRenderResult RenderSymbolLayer::retainedRenderResult(float fadeChange, uint64_t frame) {
|
||||
SymbolRenderResult result;
|
||||
result.append(layerIndex);
|
||||
result.append(passes);
|
||||
result.append(isLayerRenderable());
|
||||
// A hidden layer is admitted only after processChanges has removed both
|
||||
// groups (the orchestrator verifies registry membership separately).
|
||||
if (!isLayerRenderable()) return result;
|
||||
if (collisionTileLayerGroup && !collisionTileLayerGroup->empty()) result.complete = false;
|
||||
if (!layerGroup || layerGroup->empty()) {
|
||||
// Empty is provable only if there are no renderable Symbol buckets.
|
||||
if (renderTiles) for (const auto& tileRef : *renderTiles) {
|
||||
const auto* bucket = static_cast<const SymbolBucket*>(tileRef.get().getBucket(*baseImpl));
|
||||
if (bucket && (bucket->hasTextData() || bucket->hasIconData() || bucket->hasSdfIconData())) {
|
||||
result.complete = false;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (!layerTweaker) {
|
||||
result.complete = false;
|
||||
return result;
|
||||
}
|
||||
// RenderSymbolLayer is final and creates only this tweaker and the native
|
||||
// atlas tweaker. Unknown placement-capable layers never call this method.
|
||||
const auto& tweaker = static_cast<const SymbolLayerTweaker&>(*layerTweaker);
|
||||
if (tweaker.getRenderResultFrame() != frame) result.complete = false;
|
||||
result.appendResult(tweaker.getRenderResult());
|
||||
result.retain(layerGroup);
|
||||
result.append(layerGroup->getEnabled());
|
||||
result.append(layerGroup->getDrawableCount());
|
||||
std::size_t visited = 0;
|
||||
visitLayerGroupDrawables(*layerGroup, [&](gfx::Drawable& drawable) {
|
||||
++visited;
|
||||
if (drawable.getIsCustom() || drawable.getLayerTweaker() != layerTweaker ||
|
||||
!drawable.getBucket() || !drawable.getTileID() || !drawable.getData() ||
|
||||
!drawable.getShader() || drawable.getTweakers().size() != 1) {
|
||||
result.complete = false;
|
||||
return;
|
||||
}
|
||||
result.append(drawable.getID());
|
||||
if (!SymbolShaderProvenance::get().supports(*drawable.getShader())) result.complete = false;
|
||||
result.retain(drawable.getTweakers().front()); // immutable native atlas/sampler policy
|
||||
result.retain(drawable.getShader());
|
||||
result.retain(drawable.getBucket());
|
||||
const auto tileID = util::toString(*drawable.getTileID());
|
||||
result.appendBlock(tileID.data(), tileID.size());
|
||||
result.append(drawable.getDrawPriority());
|
||||
result.append(drawable.getRenderPass());
|
||||
result.append(drawable.getEnabled());
|
||||
result.append(drawable.getEnableColor());
|
||||
result.append(drawable.getEnableDepth());
|
||||
result.append(drawable.getEnableStencil());
|
||||
result.append(drawable.getSubLayerIndex());
|
||||
result.append(drawable.getDepthType());
|
||||
result.append(drawable.getIs3D());
|
||||
const auto& cull = drawable.getCullFaceMode();
|
||||
result.append(cull.enabled);
|
||||
result.append(cull.side);
|
||||
result.append(cull.winding);
|
||||
const auto& color = drawable.getColorMode();
|
||||
color.blendFunction.match([&](const auto& blend) {
|
||||
result.append(blend.equation);
|
||||
result.append(blend.srcFactor);
|
||||
result.append(blend.dstFactor);
|
||||
});
|
||||
result.append(color.blendColor);
|
||||
result.append(color.mask.r);
|
||||
result.append(color.mask.g);
|
||||
result.append(color.mask.b);
|
||||
result.append(color.mask.a);
|
||||
result.append(drawable.getUBOIndex());
|
||||
appendSymbolAttributes(result, drawable.getVertexAttributes());
|
||||
appendSymbolAttributes(result, drawable.getInstanceAttributes());
|
||||
|
||||
const auto& bucket = static_cast<const SymbolBucket&>(*drawable.getBucket());
|
||||
const auto& data = static_cast<const gfx::SymbolDrawableData&>(*drawable.getData());
|
||||
const auto& buffer = data.symbolType == SymbolType::Text ? bucket.text :
|
||||
data.symbolType == SymbolType::IconSDF ? bucket.sdfIcon : bucket.icon;
|
||||
const auto& opacity = buffer.opacityAttributeData();
|
||||
if (opacity.empty()) result.complete = false;
|
||||
for (const auto& vertex : opacity.vector()) {
|
||||
if (!symbolOpacityIsSettled(vertex.a1[0], fadeChange)) result.complete = false;
|
||||
}
|
||||
result.append(buffer.segments.size());
|
||||
for (const auto& segment : buffer.segments) {
|
||||
result.append(segment.vertexOffset);
|
||||
result.append(segment.indexOffset);
|
||||
result.append(segment.vertexLength);
|
||||
result.append(segment.indexLength);
|
||||
result.append(segment.baseInstance);
|
||||
result.append(segment.instanceCount);
|
||||
result.append(segment.sortKey);
|
||||
}
|
||||
#if MLN_USE_SYMBOL_INSTANCING
|
||||
const auto& indices = staticDataIndices;
|
||||
#else
|
||||
const auto& indices = buffer.sharedTriangles;
|
||||
#endif
|
||||
if (!indices) {
|
||||
result.complete = false;
|
||||
} else {
|
||||
if (!indices->getBuffer() || indices->getDirty()) result.complete = false;
|
||||
result.append(indices->isReleased());
|
||||
if (indices->isReleased()) {
|
||||
result.retain(indices);
|
||||
result.append(reinterpret_cast<std::uintptr_t>(indices->getBuffer()));
|
||||
if (!indices->getBuffer()) result.complete = false;
|
||||
} else {
|
||||
result.appendBlock(indices->data(), indices->bytes());
|
||||
if (indices->empty()) result.complete = false;
|
||||
}
|
||||
}
|
||||
const auto* tile = drawable.getRenderTile();
|
||||
const auto& atlases = tile ? tile->getAtlasTextures() : TileAtlasTexturesPtr{};
|
||||
if (!atlases) {
|
||||
result.complete = false;
|
||||
return;
|
||||
}
|
||||
result.retain(atlases);
|
||||
result.retain(atlases->glyph);
|
||||
result.retain(atlases->icon);
|
||||
for (std::size_t id = 0; id < shaders::maxTextureCountPerShader; ++id) {
|
||||
const auto& texture = drawable.getTexture(id);
|
||||
result.retain(texture);
|
||||
if (texture) {
|
||||
result.append(texture->getSize());
|
||||
result.append(texture->getFormat());
|
||||
if (texture->needsUpload()) result.complete = false;
|
||||
}
|
||||
}
|
||||
const auto& expected = data.symbolType == SymbolType::Text ? atlases->glyph : atlases->icon;
|
||||
if (!expected || expected->needsUpload() ||
|
||||
drawable.getTexture(shaders::idSymbolImageTexture) != expected) result.complete = false;
|
||||
if (drawable.getShader()->getSamplerLocation(shaders::idSymbolImageIconTexture) &&
|
||||
drawable.getTexture(shaders::idSymbolImageIconTexture) != atlases->icon) result.complete = false;
|
||||
});
|
||||
if (visited != layerGroup->getDrawableCount()) result.complete = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
void RenderSymbolLayer::update(gfx::ShaderRegistry& shaders,
|
||||
gfx::Context& context,
|
||||
const TransformState& state,
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <mln/style/image_impl.hpp>
|
||||
#include <mln/style/layers/symbol_layer_impl.hpp>
|
||||
#include <mln/style/layers/symbol_layer_properties.hpp>
|
||||
#include <mln/renderer/symbol_render_result.hpp>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
@@ -57,6 +58,9 @@ public:
|
||||
|
||||
/// Remove all the drawables for tiles
|
||||
std::size_t removeAllDrawables() override;
|
||||
// Called after Full update/processChanges/tweakers/uploads, before Build.
|
||||
SymbolRenderResult retainedRenderResult(float fadeChange, uint64_t frame);
|
||||
const LayerGroupBase* retainedResultGroup() const { return layerGroup.get(); }
|
||||
|
||||
protected:
|
||||
/// @brief Called by the RenderOrchestrator during RenderTree construction.
|
||||
|
||||
@@ -53,6 +53,8 @@ auto getInterpFactor(const SymbolBucket::PaintProperties& paintProps, bool isTex
|
||||
} // namespace
|
||||
|
||||
void SymbolLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters& parameters) {
|
||||
renderResult = {};
|
||||
renderResultFrame = parameters.frameCount;
|
||||
if (layerGroup.empty()) {
|
||||
return;
|
||||
}
|
||||
@@ -84,10 +86,16 @@ void SymbolLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParamete
|
||||
.icon_halo_blur = constOrDefault<IconHaloBlur>(evaluated),
|
||||
.pad2 = 0};
|
||||
context.emplaceOrUpdateUniformBuffer(evaluatedPropsUniformBuffer, &propsUBO);
|
||||
evaluatedResult = {};
|
||||
evaluatedResult.append(propsUBO);
|
||||
propertiesUpdated = false;
|
||||
}
|
||||
auto& layerUniforms = layerGroup.mutableUniformBuffers();
|
||||
layerUniforms.set(idSymbolEvaluatedPropsUBO, evaluatedPropsUniformBuffer);
|
||||
if (!evaluatedPropsUniformBuffer || evaluatedPropsUniformBuffer->getSize() < sizeof(SymbolEvaluatedPropsUBO)) {
|
||||
renderResult.complete = false;
|
||||
}
|
||||
renderResult.appendResult(evaluatedResult);
|
||||
|
||||
#if MLN_UBO_CONSOLIDATION
|
||||
int i = 0;
|
||||
@@ -102,6 +110,7 @@ void SymbolLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParamete
|
||||
|
||||
visitLayerGroupDrawables(layerGroup, [&](gfx::Drawable& drawable) {
|
||||
if (!drawable.getTileID() || !drawable.getData()) {
|
||||
renderResult.complete = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -115,6 +124,7 @@ void SymbolLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParamete
|
||||
const auto bucket = std::static_pointer_cast<SymbolBucket>(drawable.getBucket());
|
||||
const auto* tile = drawable.getRenderTile();
|
||||
if (!bucket || !tile || (!textBinders && !iconBinders)) {
|
||||
renderResult.complete = false;
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
@@ -205,12 +215,29 @@ void SymbolLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParamete
|
||||
.pad1 = 0,
|
||||
};
|
||||
|
||||
#if MLN_UBO_CONSOLIDATION
|
||||
// Capture the same CPU inputs passed to the consolidated GPU buffers.
|
||||
const auto& resultDrawableUBO = drawableUBOVector[i];
|
||||
const auto& resultTileUBO = tilePropsUBOVector[i];
|
||||
#else
|
||||
const auto& resultDrawableUBO = drawableUBO;
|
||||
const auto& resultTileUBO = tilePropsUBO;
|
||||
#endif
|
||||
renderResult.append(drawable.getID());
|
||||
// SymbolDrawableUBO has twelve bytes of trailing alignment padding.
|
||||
// Compare shader fields only, never indeterminate padding.
|
||||
renderResult.appendBytes(&resultDrawableUBO, offsetof(SymbolDrawableUBO, halo_blur_t) + sizeof(float));
|
||||
renderResult.append(resultTileUBO);
|
||||
|
||||
#if MLN_UBO_CONSOLIDATION
|
||||
drawable.setUBOIndex(i++);
|
||||
#else
|
||||
auto& drawableUniforms = drawable.mutableUniformBuffers();
|
||||
drawableUniforms.createOrUpdate(idSymbolDrawableUBO, &drawableUBO, context);
|
||||
drawableUniforms.createOrUpdate(idSymbolTilePropsUBO, &tilePropsUBO, context);
|
||||
if (!drawableUniforms.get(idSymbolDrawableUBO) || !drawableUniforms.get(idSymbolTilePropsUBO)) {
|
||||
renderResult.complete = false;
|
||||
}
|
||||
#endif
|
||||
});
|
||||
|
||||
@@ -233,6 +260,9 @@ void SymbolLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParamete
|
||||
|
||||
layerUniforms.set(idSymbolDrawableUBO, drawableUniformBuffer);
|
||||
layerUniforms.set(idSymbolTilePropsUBO, tilePropsUniformBuffer);
|
||||
if (!drawableUniformBuffer || !tilePropsUniformBuffer ||
|
||||
drawableUniformBuffer->getSize() < drawableUBOVectorSize ||
|
||||
tilePropsUniformBuffer->getSize() < tilePropsUBOVectorSize) renderResult.complete = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <mln/renderer/layer_tweaker.hpp>
|
||||
#include <mln/renderer/symbol_render_result.hpp>
|
||||
#include <mln/tile/tile_id.hpp>
|
||||
#include <mln/util/containers.hpp>
|
||||
#include <mln/util/hash.hpp>
|
||||
@@ -19,8 +20,13 @@ public:
|
||||
~SymbolLayerTweaker() override = default;
|
||||
|
||||
void execute(LayerGroupBase&, const PaintParameters&) override;
|
||||
const SymbolRenderResult& getRenderResult() const { return renderResult; }
|
||||
uint64_t getRenderResultFrame() const { return renderResultFrame; }
|
||||
|
||||
private:
|
||||
SymbolRenderResult renderResult;
|
||||
SymbolRenderResult evaluatedResult;
|
||||
uint64_t renderResultFrame = 0;
|
||||
gfx::UniformBufferPtr evaluatedPropsUniformBuffer;
|
||||
|
||||
#if MLN_UBO_CONSOLIDATION
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <mln/renderer/render_orchestrator.hpp>
|
||||
#include <mln/renderer/layers/render_symbol_layer.hpp>
|
||||
#include <mln/renderer/continuous_layer_range.hpp>
|
||||
#include <mln/renderer/particle_fast_path.hpp>
|
||||
|
||||
@@ -1143,11 +1144,76 @@ bool RenderOrchestrator::retainedRangeUsesPlacement(int32_t begin, int32_t endEx
|
||||
}
|
||||
|
||||
bool RenderOrchestrator::retainedRangeHasPlacementCapableLayer(int32_t begin, int32_t endExclusive) const {
|
||||
return renderSegmentHasPlacementCapableLayer(retainedStyleLayers, begin, endExclusive);
|
||||
if (begin < 0 || endExclusive < begin || endExclusive > static_cast<int32_t>(orderedLayers.size())) return true;
|
||||
if (renderSegmentHasPlacementCapableLayer(retainedStyleLayers, begin, endExclusive)) return true;
|
||||
for (int32_t index = begin; index < endExclusive; ++index) {
|
||||
if (orderedLayers[index].get().baseImpl->getTypeInfo()->crossTileIndex ==
|
||||
style::LayerTypeInfo::CrossTileIndex::Required) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void RenderOrchestrator::recordRenderError(std::exception_ptr error) {
|
||||
invalidateRetainedRenderResults();
|
||||
observer->onRenderError(error);
|
||||
}
|
||||
|
||||
void RenderOrchestrator::invalidateRetainedRenderResults() {
|
||||
++renderErrorRevision;
|
||||
advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::LayerGroupMutation);
|
||||
}
|
||||
|
||||
SymbolRenderResult RenderOrchestrator::retainedSymbolRenderResult(
|
||||
int32_t begin, int32_t endExclusive, float fadeChange, uint64_t frame) {
|
||||
SymbolRenderResult result;
|
||||
result.append(numLayerGroups()); // global depth range depends on all groups
|
||||
if (begin < 0 || endExclusive > static_cast<int32_t>(orderedLayers.size()) || begin > endExclusive) {
|
||||
result.complete = false;
|
||||
return result;
|
||||
}
|
||||
// Registry membership is checked after processChanges, including old groups
|
||||
// of hidden layers. Preserve global ordinals for depth and cross-segment order.
|
||||
for (int32_t index = begin; index < endExclusive; ++index) {
|
||||
auto& layer = orderedLayers[index].get();
|
||||
const auto* info = layer.baseImpl->getTypeInfo();
|
||||
const bool placementCapable = info->type == std::string_view("symbol") ||
|
||||
info->crossTileIndex == style::LayerTypeInfo::CrossTileIndex::Required;
|
||||
if (!placementCapable) continue;
|
||||
if (info != style::SymbolLayer::Impl::staticTypeInfo()) {
|
||||
result.complete = false;
|
||||
continue;
|
||||
}
|
||||
auto& symbol = static_cast<RenderSymbolLayer&>(layer);
|
||||
const auto registered = layerGroupsByLayerIndex.equal_range(index);
|
||||
std::size_t groups = 0;
|
||||
for (auto it = registered.first; it != registered.second; ++it) {
|
||||
++groups;
|
||||
if (!symbol.isLayerRenderable() || it->second.get() != symbol.retainedResultGroup()) {
|
||||
result.complete = false;
|
||||
}
|
||||
}
|
||||
if (groups > 1 || (symbol.isLayerRenderable() && symbol.retainedResultGroup() && groups != 1)) {
|
||||
result.complete = false;
|
||||
}
|
||||
result.appendResult(symbol.retainedRenderResult(fadeChange, frame));
|
||||
}
|
||||
auto it = layerGroupsByLayerIndex.lower_bound(begin);
|
||||
std::size_t ordinal = static_cast<std::size_t>(std::distance(layerGroupsByLayerIndex.begin(), it));
|
||||
for (; it != layerGroupsByLayerIndex.end() && it->first < endExclusive; ++it, ++ordinal) {
|
||||
if (!it->second) { result.complete = false; continue; }
|
||||
const auto& group = *it->second;
|
||||
result.append(ordinal);
|
||||
result.retain(it->second);
|
||||
result.append(group.getLayerIndex());
|
||||
result.append(group.getDrawableCount());
|
||||
result.append(group.getEnabled());
|
||||
}
|
||||
result.complete = result.complete && pendingChanges.empty();
|
||||
return result;
|
||||
}
|
||||
|
||||
bool RenderOrchestrator::retainedPlacementTransitionActive(TimePoint timePoint) const {
|
||||
return placementController.hasTransitions(timePoint);
|
||||
return placementController.hasActiveSymbolFade(timePoint);
|
||||
}
|
||||
|
||||
bool RenderOrchestrator::retainedUnderlayIsStable(TimePoint timePoint, int32_t boundary) const {
|
||||
@@ -1156,7 +1222,7 @@ bool RenderOrchestrator::retainedUnderlayIsStable(TimePoint timePoint, int32_t b
|
||||
|
||||
bool RenderOrchestrator::retainedRangeIsStable(TimePoint timePoint, int32_t begin, int32_t endExclusive) const {
|
||||
if (renderLight.hasTransition()) return false;
|
||||
if (retainedRangeUsesPlacement(begin, endExclusive) && placementController.hasTransitions(timePoint)) return false;
|
||||
if (retainedRangeUsesPlacement(begin, endExclusive) && placementController.hasActiveSymbolFade(timePoint)) return false;
|
||||
|
||||
const auto first = std::clamp<int32_t>(begin, 0, static_cast<int32_t>(orderedLayers.size()));
|
||||
const auto last = std::clamp<int32_t>(endExclusive, first, static_cast<int32_t>(orderedLayers.size()));
|
||||
@@ -1213,7 +1279,7 @@ bool RenderOrchestrator::retainedSegmentIsStable(TimePoint timePoint,
|
||||
int32_t begin,
|
||||
int32_t endExclusive) const {
|
||||
if (renderLight.hasTransition()) return false;
|
||||
if (retainedRangeUsesPlacement(begin, endExclusive) && placementController.hasTransitions(timePoint)) return false;
|
||||
if (retainedRangeUsesPlacement(begin, endExclusive) && placementController.hasActiveSymbolFade(timePoint)) return false;
|
||||
const auto first = std::clamp<int32_t>(begin, 0, static_cast<int32_t>(orderedLayers.size()));
|
||||
const auto last = std::clamp<int32_t>(endExclusive, first, static_cast<int32_t>(orderedLayers.size()));
|
||||
for (int32_t index = first; index < last; ++index) {
|
||||
@@ -1285,7 +1351,7 @@ ParticleFastPathDynamicState RenderOrchestrator::particleFastPathDynamicState(Ti
|
||||
}
|
||||
}
|
||||
}
|
||||
const bool controllerTransition = placementController.hasTransitions(timePoint);
|
||||
const bool controllerTransition = placementController.hasActiveSymbolFade(timePoint);
|
||||
result.placementTransition = controllerTransition &&
|
||||
(retainedStyleLayers.size() != renderLayers.size() ||
|
||||
particleFastPathHasPlacementTransition(retainedStyleLayers, controllerTransition));
|
||||
@@ -1459,7 +1525,7 @@ void RenderOrchestrator::updateLayers(gfx::ShaderRegistry& shaders,
|
||||
try {
|
||||
renderLayer.update(shaders, context, state, updateParameters, paintParameters, renderTree, changes);
|
||||
} catch (...) {
|
||||
observer->onRenderError(std::current_exception());
|
||||
recordRenderError(std::current_exception());
|
||||
}
|
||||
}
|
||||
addChanges(changes);
|
||||
@@ -1487,7 +1553,7 @@ void RenderOrchestrator::updateParticleLayers(gfx::ShaderRegistry& shaders,
|
||||
try {
|
||||
renderLayer.update(shaders, context, state, updateParameters, paintParameters, renderTree, changes);
|
||||
} catch (...) {
|
||||
observer->onRenderError(std::current_exception());
|
||||
recordRenderError(std::current_exception());
|
||||
}
|
||||
}
|
||||
addChanges(changes);
|
||||
@@ -1535,6 +1601,7 @@ void RenderOrchestrator::onGlyphsError(const FontStack& fontStack,
|
||||
const GlyphRange& glyphRange,
|
||||
std::exception_ptr error) {
|
||||
MLN_TRACE_FUNC();
|
||||
advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::GlyphMutation);
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "Failed to load glyph range ";
|
||||
@@ -1550,11 +1617,13 @@ void RenderOrchestrator::onGlyphsError(const FontStack& fontStack,
|
||||
}
|
||||
|
||||
void RenderOrchestrator::onGlyphsRequested(const FontStack& fontStack, const GlyphRange& range) {
|
||||
advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::GlyphMutation);
|
||||
observer->onGlyphsRequested(fontStack, range);
|
||||
}
|
||||
|
||||
void RenderOrchestrator::onTileError(RenderSource& source, const OverscaledTileID& tileID, std::exception_ptr error) {
|
||||
MLN_TRACE_FUNC();
|
||||
advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::TileMutation, source.baseImpl->id);
|
||||
|
||||
Log::Error(Event::Style,
|
||||
"Failed to load tile " + util::toString(tileID) + " for source " + source.baseImpl->id + ": " +
|
||||
@@ -1585,7 +1654,8 @@ void RenderOrchestrator::onTileAction(RenderSource&,
|
||||
observer->onTileAction(op, id, sourceID);
|
||||
}
|
||||
|
||||
void RenderOrchestrator::onSymbolError(RenderSource&, const std::string& message) {
|
||||
void RenderOrchestrator::onSymbolError(RenderSource& source, const std::string& message) {
|
||||
advanceRetainedUnderlayContentRevision(RetainedUnderlayReason::TileMutation, source.baseImpl->id);
|
||||
observer->onSymbolError(message);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <mln/text/placement.hpp>
|
||||
#include <mln/renderer/render_tree.hpp>
|
||||
#include <mln/renderer/retained_underlay_cache.hpp>
|
||||
#include <mln/renderer/symbol_render_result.hpp>
|
||||
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
@@ -128,6 +129,10 @@ public:
|
||||
bool retainedUnderlayHasUnsupportedLayer(int32_t boundary) const;
|
||||
bool retainedRangeUsesPlacement(int32_t begin, int32_t endExclusive) const;
|
||||
bool retainedRangeHasPlacementCapableLayer(int32_t begin, int32_t endExclusive) const;
|
||||
SymbolRenderResult retainedSymbolRenderResult(int32_t begin, int32_t endExclusive, float fadeChange, uint64_t frame);
|
||||
void recordRenderError(std::exception_ptr);
|
||||
void invalidateRetainedRenderResults();
|
||||
uint64_t getRenderErrorRevision() const { return renderErrorRevision; }
|
||||
bool retainedPlacementTransitionActive(TimePoint timePoint) const;
|
||||
bool retainedRangeIsStable(TimePoint, int32_t begin, int32_t endExclusive) const;
|
||||
struct RetainedSegmentSourceState {
|
||||
@@ -159,7 +164,7 @@ public:
|
||||
try {
|
||||
f(*pair.second);
|
||||
} catch (...) {
|
||||
observer->onRenderError(std::current_exception());
|
||||
recordRenderError(std::current_exception());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -174,7 +179,7 @@ public:
|
||||
if (it->second) {
|
||||
try { f(*it->second, ordinal); } catch (...) {
|
||||
if (failed) *failed = true;
|
||||
observer->onRenderError(std::current_exception());
|
||||
recordRenderError(std::current_exception());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -191,7 +196,7 @@ public:
|
||||
if (last->second) {
|
||||
try { f(*last->second, ordinal); } catch (...) {
|
||||
if (failed) *failed = true;
|
||||
observer->onRenderError(std::current_exception());
|
||||
recordRenderError(std::current_exception());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -206,7 +211,7 @@ public:
|
||||
try {
|
||||
f(*pair.second, ordinal);
|
||||
} catch (...) {
|
||||
observer->onRenderError(std::current_exception());
|
||||
recordRenderError(std::current_exception());
|
||||
}
|
||||
}
|
||||
++ordinal;
|
||||
@@ -223,7 +228,7 @@ public:
|
||||
try {
|
||||
f(*it->second, ordinal);
|
||||
} catch (...) {
|
||||
observer->onRenderError(std::current_exception());
|
||||
recordRenderError(std::current_exception());
|
||||
}
|
||||
}
|
||||
++ordinal;
|
||||
@@ -241,7 +246,7 @@ public:
|
||||
try {
|
||||
f(*end->second, ordinal);
|
||||
} catch (...) {
|
||||
observer->onRenderError(std::current_exception());
|
||||
recordRenderError(std::current_exception());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -257,7 +262,7 @@ public:
|
||||
try {
|
||||
f(*it->second, ordinal);
|
||||
} catch (...) {
|
||||
observer->onRenderError(std::current_exception());
|
||||
recordRenderError(std::current_exception());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -270,7 +275,7 @@ public:
|
||||
try {
|
||||
f(*rit->second);
|
||||
} catch (...) {
|
||||
observer->onRenderError(std::current_exception());
|
||||
recordRenderError(std::current_exception());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -413,6 +418,7 @@ private:
|
||||
// Vectors with reserved capacity of layerImpls->size() to avoid
|
||||
// reallocation on each frame.
|
||||
std::vector<Immutable<style::LayerProperties>> filteredLayersForSource;
|
||||
uint64_t renderErrorRevision = 0;
|
||||
RenderLayerReferences orderedLayers;
|
||||
std::vector<StyleLayerDiagnostics> retainedStyleLayers;
|
||||
ResolvedRenderSegmentPlan renderSegmentPlan;
|
||||
|
||||
@@ -106,7 +106,8 @@ public:
|
||||
parameters->timePoint = timePoint;
|
||||
parameters->needsRepaint = true;
|
||||
parameters->placementChanged = false;
|
||||
parameters->symbolFadeChange = 0.0f;
|
||||
// Preserve the certified Full shader value. Zero would undo a settled
|
||||
// fade if any certified Symbol input is inspected on the fast frame.
|
||||
startTime = util::MonotonicTimer::now().count();
|
||||
}
|
||||
|
||||
|
||||
@@ -53,18 +53,31 @@ void Renderer::render(const std::shared_ptr<UpdateParameters>& updateParameters)
|
||||
auto& context = impl->backend.getContext();
|
||||
impl->dynamicTextureAtlas = std::make_unique<gfx::DynamicTextureAtlas>(context);
|
||||
}
|
||||
if (impl->tryRenderParticleFastFrame(updateParameters)) return;
|
||||
// A full createRenderTree may replace RenderLayer/RenderSource instances.
|
||||
// Drop the old prepared tree first so its reference-based render items can
|
||||
// never outlive the orchestrator objects they describe.
|
||||
impl->clearPreparedRenderTree();
|
||||
if (auto renderTree = impl->orchestrator.createRenderTree(updateParameters, impl->dynamicTextureAtlas)) {
|
||||
renderTree->prepare();
|
||||
impl->render(*renderTree, updateParameters, false);
|
||||
impl->commitPreparedRenderTree(std::move(renderTree), updateParameters);
|
||||
} else {
|
||||
try {
|
||||
if (impl->tryRenderParticleFastFrame(updateParameters)) return;
|
||||
// A full createRenderTree may replace RenderLayer/RenderSource instances.
|
||||
// Drop the old prepared tree first so its reference-based render items can
|
||||
// never outlive the orchestrator objects they describe.
|
||||
// Result evidence owns copied inputs/resources, not RenderLayer references.
|
||||
// Preserve it across ordinary Full replacement; resets still invalidate it.
|
||||
impl->clearPreparedRenderTree(false);
|
||||
if (auto renderTree = impl->orchestrator.createRenderTree(updateParameters, impl->dynamicTextureAtlas)) {
|
||||
renderTree->prepare();
|
||||
impl->render(*renderTree, updateParameters, false);
|
||||
impl->commitPreparedRenderTree(std::move(renderTree), updateParameters);
|
||||
} else {
|
||||
impl->clearPreparedRenderTree();
|
||||
if (impl->retainedRuntimeState) impl->retainedRuntimeState->completeFastGateAttempt("no-render-tree");
|
||||
}
|
||||
} catch (...) {
|
||||
// Upload/encode/present failures that escape the visitors must not leave
|
||||
// a previously valid cache or completed evidence available for retry.
|
||||
impl->clearPreparedRenderTree();
|
||||
if (impl->retainedRuntimeState) impl->retainedRuntimeState->completeFastGateAttempt("no-render-tree");
|
||||
impl->orchestrator.invalidateRetainedRenderResults();
|
||||
for (auto& cache : impl->retainedSegmentCaches) {
|
||||
if (cache) cache->invalidate("render-error");
|
||||
}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -131,11 +131,14 @@ void Renderer::Impl::onPostCompileShader(shaders::BuiltIn shaderID,
|
||||
void Renderer::Impl::onShaderCompileFailed(shaders::BuiltIn shaderID,
|
||||
gfx::Backend::Type type,
|
||||
const std::string& additionalDefines) {
|
||||
for (auto& certificate : retainedSymbolCertificates) certificate.invalidate();
|
||||
orchestrator.invalidateRetainedRenderResults();
|
||||
observer->onShaderCompileFailed(shaderID, type, additionalDefines);
|
||||
}
|
||||
|
||||
void Renderer::Impl::onRenderError(std::exception_ptr error) {
|
||||
observer->onRenderError(error);
|
||||
for (auto& certificate : retainedSymbolCertificates) certificate.invalidate();
|
||||
orchestrator.recordRenderError(error);
|
||||
}
|
||||
|
||||
void Renderer::Impl::setObserver(RendererObserver* observer_) {
|
||||
@@ -249,10 +252,23 @@ bool Renderer::Impl::tryRenderParticleFastFrame(const std::shared_ptr<UpdatePara
|
||||
const bool contentRevisionMatches =
|
||||
particlePreparedContentRevision == orchestrator.getRetainedUnderlayContentRevision();
|
||||
bool staticStable = plan.valid;
|
||||
const SymbolResultBinding fastSymbolBinding{
|
||||
.viewport = backend.getDefaultRenderable().getSize(),
|
||||
.camera = signature.cameraMatrix,
|
||||
.pixelRatio = signature.pixelRatio,
|
||||
.content = orchestrator.getRetainedUnderlayContentRevision(),
|
||||
.topology = retainedUnderlayTopologyTracker.current(),
|
||||
.config = plan.configRevision,
|
||||
.crossSourceCollisions = updateParameters->crossSourceCollisions,
|
||||
};
|
||||
for (const auto& segment : plan.segments) {
|
||||
if (segment.strategy == RenderSegmentStrategy::Retained) {
|
||||
staticStable = staticStable && orchestrator.retainedRangeIsStable(
|
||||
updateParameters->timePoint, segment.beginIndex, segment.endExclusive);
|
||||
if (orchestrator.retainedRangeHasPlacementCapableLayer(segment.beginIndex, segment.endExclusive)) {
|
||||
staticStable = staticStable && segment.segmentIndex < retainedSymbolCertificates.size() &&
|
||||
retainedSymbolCertificates[segment.segmentIndex].canFast(fastSymbolBinding);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,10 +285,9 @@ bool Renderer::Impl::tryRenderParticleFastFrame(const std::shared_ptr<UpdatePara
|
||||
.boundaryMatches = boundaryMatches,
|
||||
.topologyMatches = topologyMatches,
|
||||
.staticStable = staticStable,
|
||||
// placementChanged/symbolFadeChange belong to the previous prepared
|
||||
// full frame. The current controller transition is the authoritative
|
||||
// semantic state: while active we fail closed, and once settled the
|
||||
// old full-frame snapshot must not permanently block the fast path.
|
||||
// Scheduling staleness is independent of active fade. StaticStable
|
||||
// additionally requires the uploaded result certificate and a submitted
|
||||
// Reuse; all 24 gates retain their original decision/trace contract.
|
||||
.hasPlacementTransition = dynamicState.placementTransition,
|
||||
.hasPendingChanges = dynamicState.pendingChanges,
|
||||
.hasNonParticleTransition = dynamicState.blockingTransition,
|
||||
@@ -312,6 +327,7 @@ void Renderer::Impl::commitPreparedRenderTree(std::unique_ptr<RenderTree> render
|
||||
const auto& plan = orchestrator.getRenderSegmentPlan();
|
||||
const auto* continuous = singleContinuousSegment(plan);
|
||||
particlePreparedSignature = makeParticleFastPathUpdateSignature(*updateParameters);
|
||||
symbolCompletedSignature = particlePreparedSignature;
|
||||
particlePreparedContentRevision = orchestrator.getRetainedUnderlayContentRevision();
|
||||
particlePreparedTopologyRevision = retainedUnderlayTopologyTracker.current();
|
||||
particlePreparedStaticRevision = retainedUnderlayRevisionTracker.current();
|
||||
@@ -322,7 +338,11 @@ void Renderer::Impl::commitPreparedRenderTree(std::unique_ptr<RenderTree> render
|
||||
particlePreparedTree.replace(std::move(renderTree));
|
||||
}
|
||||
|
||||
void Renderer::Impl::clearPreparedRenderTree() {
|
||||
void Renderer::Impl::clearPreparedRenderTree(bool invalidateSymbolResults) {
|
||||
if (invalidateSymbolResults) {
|
||||
retainedSymbolCertificates.clear();
|
||||
symbolCompletedSignature.reset();
|
||||
}
|
||||
particlePreparedTree.reset();
|
||||
particlePreparedSignature.reset();
|
||||
particlePreparedContentRevision = 0;
|
||||
@@ -434,6 +454,7 @@ void Renderer::Impl::render(const RenderTree& renderTree,
|
||||
}
|
||||
|
||||
const auto& renderTreeParameters = renderTree.getParameters();
|
||||
const auto renderErrorRevisionAtStart = orchestrator.getRenderErrorRevision();
|
||||
staticData->has3D = renderTreeParameters.has3D;
|
||||
const Size renderableSize = backend.getDefaultRenderable().getSize();
|
||||
|
||||
@@ -538,6 +559,8 @@ void Renderer::Impl::render(const RenderTree& renderTree,
|
||||
retainedSegmentCaches.clear();
|
||||
retainedSegmentCaches.resize(segmentPlan.segments.size());
|
||||
retainedSegmentFingerprints.assign(segmentPlan.segments.size(), 0);
|
||||
retainedSymbolCertificates.clear();
|
||||
retainedSymbolCertificates.resize(segmentPlan.segments.size());
|
||||
bool continuousSeen = false;
|
||||
for (const auto& segment : segmentPlan.segments) {
|
||||
if (segment.strategy == RenderSegmentStrategy::Continuous) {
|
||||
@@ -595,21 +618,107 @@ void Renderer::Impl::render(const RenderTree& renderTree,
|
||||
}
|
||||
}
|
||||
}
|
||||
bool retainedStaticUsesPlacement = false;
|
||||
// Upload layer groups
|
||||
{
|
||||
const auto uploadPass = parameters.encoder->createUploadPass("layerGroup-upload",
|
||||
parameters.backend.getDefaultRenderable());
|
||||
#if !defined(NDEBUG)
|
||||
const auto debugGroup = uploadPass->createDebugGroup("layerGroup-upload");
|
||||
#endif
|
||||
|
||||
// Update the debug layer groups only on the full path. Debug rendering
|
||||
// is excluded by the fast-path gate.
|
||||
if (!particleFastFrame) orchestrator.updateDebugLayerGroups(renderTree, parameters);
|
||||
|
||||
// Tweakers are run in the upload pass so they can set up uniforms.
|
||||
parameters.currentLayer = 0;
|
||||
if (particleFastFrame && singleContinuous) {
|
||||
orchestrator.visitLayerGroupsInRange(
|
||||
singleContinuous->beginIndex, singleContinuous->endExclusive,
|
||||
[&](LayerGroupBase& layerGroup, size_t ordinal) {
|
||||
parameters.currentLayer = static_cast<uint32_t>(ordinal);
|
||||
layerGroup.runTweakers(renderTree, parameters);
|
||||
});
|
||||
} else {
|
||||
orchestrator.visitLayerGroups([&](LayerGroupBase& layerGroup) {
|
||||
layerGroup.runTweakers(renderTree, parameters);
|
||||
parameters.currentLayer++;
|
||||
});
|
||||
parameters.currentLayer = 0;
|
||||
orchestrator.visitDebugLayerGroups([&](LayerGroupBase& layerGroup) {
|
||||
layerGroup.runTweakers(renderTree, parameters);
|
||||
parameters.currentLayer++;
|
||||
});
|
||||
}
|
||||
|
||||
// Give the layers a chance to upload
|
||||
if (particleFastFrame && singleContinuous) {
|
||||
orchestrator.visitLayerGroupsInRange(
|
||||
singleContinuous->beginIndex, singleContinuous->endExclusive,
|
||||
[&](LayerGroupBase& layerGroup, size_t) { layerGroup.upload(*uploadPass); });
|
||||
} else {
|
||||
orchestrator.visitLayerGroups([&](LayerGroupBase& layerGroup) { layerGroup.upload(*uploadPass); });
|
||||
}
|
||||
|
||||
// Give the render targets a chance to upload
|
||||
orchestrator.visitRenderTargets([&](RenderTarget& renderTarget) { renderTarget.upload(*uploadPass); });
|
||||
|
||||
// Upload the Debug layer group
|
||||
if (!particleFastFrame) {
|
||||
orchestrator.visitDebugLayerGroups([&](LayerGroupBase& layerGroup) { layerGroup.upload(*uploadPass); });
|
||||
}
|
||||
}
|
||||
|
||||
if (retainedSymbolCertificates.size() != segmentPlan.segments.size()) {
|
||||
retainedSymbolCertificates.resize(segmentPlan.segments.size());
|
||||
}
|
||||
const SymbolResultBinding symbolBinding{
|
||||
.viewport = parameters.renderableSize,
|
||||
.camera = retainedCameraMatrix,
|
||||
.pixelRatio = updateParameters->pixelRatio,
|
||||
.content = orchestrator.getRetainedUnderlayContentRevision(),
|
||||
.topology = retainedTopologyRevision,
|
||||
.config = segmentPlan.configRevision,
|
||||
.crossSourceCollisions = updateParameters->crossSourceCollisions,
|
||||
};
|
||||
if (!particleFastFrame && symbolCompletedSignature &&
|
||||
!particleFastPathNonCameraInputsMatch(*symbolCompletedSignature,
|
||||
makeParticleFastPathUpdateSignature(*updateParameters))) {
|
||||
for (auto& certificate : retainedSymbolCertificates) certificate.invalidate();
|
||||
}
|
||||
std::vector<bool> symbolResultGates(segmentPlan.segments.size(), true);
|
||||
bool retainedPlacementStable = true;
|
||||
for (const auto& segment : segmentPlan.segments) {
|
||||
if (segment.strategy != RenderSegmentStrategy::Retained ||
|
||||
!orchestrator.retainedRangeHasPlacementCapableLayer(segment.beginIndex, segment.endExclusive)) continue;
|
||||
auto& certificate = retainedSymbolCertificates[segment.segmentIndex];
|
||||
if (particleFastFrame) {
|
||||
symbolResultGates[segment.segmentIndex] = certificate.canReuse(symbolBinding);
|
||||
} else {
|
||||
const auto source = orchestrator.retainedSegmentSourceState(segment.beginIndex, segment.endExclusive);
|
||||
const bool ready = renderTreeParameters.loaded && updateParameters->styleLoaded &&
|
||||
updateParameters->spriteLoaded && orchestrator.retainedGlobalResourcesReady() &&
|
||||
source.ready && source.stable && !state.isChanging() && !state.isGestureInProgress() &&
|
||||
!orchestrator.retainedPlacementTransitionActive(renderTreeParameters.timePoint) &&
|
||||
orchestrator.retainedSegmentIsStable(renderTreeParameters.timePoint,
|
||||
segment.beginIndex, segment.endExclusive) &&
|
||||
orchestrator.getRenderErrorRevision() == renderErrorRevisionAtStart;
|
||||
symbolResultGates[segment.segmentIndex] = certificate.observeFull(
|
||||
orchestrator.retainedSymbolRenderResult(segment.beginIndex, segment.endExclusive,
|
||||
parameters.symbolFadeChange, frameCount),
|
||||
symbolBinding, ready);
|
||||
}
|
||||
retainedPlacementStable = retainedPlacementStable && symbolResultGates[segment.segmentIndex];
|
||||
}
|
||||
bool retainedStaticStable = retainedMode;
|
||||
bool retainedUnsupportedStaticLayer = false;
|
||||
for (const auto& segment : segmentPlan.segments) {
|
||||
if (segment.strategy != RenderSegmentStrategy::Retained) continue;
|
||||
retainedStaticUsesPlacement = retainedStaticUsesPlacement ||
|
||||
orchestrator.retainedRangeUsesPlacement(segment.beginIndex, segment.endExclusive);
|
||||
retainedStaticStable = retainedStaticStable && orchestrator.retainedRangeIsStable(
|
||||
renderTreeParameters.timePoint, segment.beginIndex, segment.endExclusive);
|
||||
retainedUnsupportedStaticLayer = retainedUnsupportedStaticLayer ||
|
||||
orchestrator.retainedRangeHasUnsupportedLayer(segment.beginIndex, segment.endExclusive);
|
||||
}
|
||||
const bool retainedPlacementStable =
|
||||
!retainedStaticUsesPlacement ||
|
||||
(!renderTreeParameters.placementChanged && renderTreeParameters.symbolFadeChange == 0.0f);
|
||||
const bool placementTransitionActive = orchestrator.retainedPlacementTransitionActive(
|
||||
renderTreeParameters.timePoint);
|
||||
const auto retainedContentRevision = orchestrator.getRetainedUnderlayContentRevision();
|
||||
@@ -666,7 +775,7 @@ void Renderer::Impl::render(const RenderTree& renderTree,
|
||||
const auto sourceState = orchestrator.retainedSegmentSourceState(
|
||||
segment.beginIndex, segment.endExclusive);
|
||||
segmentSourceStates[segment.segmentIndex] = sourceState;
|
||||
const auto segmentContentRevision = sourceState.revision ^
|
||||
auto segmentContentRevision = sourceState.revision ^
|
||||
(fingerprint + 0x9e3779b97f4a7c15ULL + (sourceState.revision << 6U) +
|
||||
(sourceState.revision >> 2U)) ^
|
||||
static_cast<uint64_t>(updateParameters->pixelRatio * 1000000.0f);
|
||||
@@ -674,9 +783,12 @@ void Renderer::Impl::render(const RenderTree& renderTree,
|
||||
renderTreeParameters.timePoint, segment.beginIndex, segment.endExclusive);
|
||||
const bool placementCapable = orchestrator.retainedRangeHasPlacementCapableLayer(
|
||||
segment.beginIndex, segment.endExclusive);
|
||||
const bool segmentPlacementGatePassed = renderSegmentPlacementGatePassed(
|
||||
placementCapable, renderTreeParameters.placementChanged,
|
||||
renderTreeParameters.symbolFadeChange, placementTransitionActive);
|
||||
const bool segmentPlacementGatePassed = !placementCapable || symbolResultGates[segment.segmentIndex];
|
||||
if (placementCapable) {
|
||||
const auto revision = retainedSymbolCertificates[segment.segmentIndex].current();
|
||||
segmentContentRevision ^= revision + 0x9e3779b97f4a7c15ULL +
|
||||
(segmentContentRevision << 6U) + (segmentContentRevision >> 2U);
|
||||
}
|
||||
segmentPlacementGates[segment.segmentIndex] = segmentPlacementGatePassed;
|
||||
const bool segmentSupported = retainedMode && updateParameters->styleLoaded &&
|
||||
updateParameters->spriteLoaded && orchestrator.retainedGlobalResourcesReady() &&
|
||||
@@ -698,6 +810,12 @@ void Renderer::Impl::render(const RenderTree& renderTree,
|
||||
: renderTreeParameters.debugOptions != MapDebugOptions::NoDebug ? "debug-options"
|
||||
: updateParameters->mode != MapMode::Continuous ? "map-mode"
|
||||
: nullptr;
|
||||
// Exact Symbol proof also controls Full Reuse. A combined revision hash
|
||||
// cannot authorize reuse of a cache built for different result bytes.
|
||||
if (placementCapable && segmentPlacementGatePassed && cache->isValid() &&
|
||||
!retainedSymbolCertificates[segment.segmentIndex].candidateHasBuild(symbolBinding)) {
|
||||
cache->invalidate("symbol-result-mutation");
|
||||
}
|
||||
decision = cache->evaluate(segmentKeys[segment.segmentIndex],
|
||||
segmentContentRevision,
|
||||
segmentSupported,
|
||||
@@ -712,61 +830,16 @@ void Renderer::Impl::render(const RenderTree& renderTree,
|
||||
}
|
||||
}
|
||||
|
||||
// Upload layer groups
|
||||
{
|
||||
const auto uploadPass = parameters.encoder->createUploadPass("layerGroup-upload",
|
||||
parameters.backend.getDefaultRenderable());
|
||||
#if !defined(NDEBUG)
|
||||
const auto debugGroup = uploadPass->createDebugGroup("layerGroup-upload");
|
||||
#endif
|
||||
|
||||
// Update the debug layer groups only on the full path. Debug rendering
|
||||
// is excluded by the fast-path gate.
|
||||
if (!particleFastFrame) orchestrator.updateDebugLayerGroups(renderTree, parameters);
|
||||
|
||||
// Tweakers are run in the upload pass so they can set up uniforms.
|
||||
parameters.currentLayer = 0;
|
||||
if (particleFastFrame && singleContinuous) {
|
||||
orchestrator.visitLayerGroupsInRange(
|
||||
singleContinuous->beginIndex, singleContinuous->endExclusive,
|
||||
[&](LayerGroupBase& layerGroup, size_t ordinal) {
|
||||
parameters.currentLayer = static_cast<uint32_t>(ordinal);
|
||||
layerGroup.runTweakers(renderTree, parameters);
|
||||
});
|
||||
} else {
|
||||
orchestrator.visitLayerGroups([&](LayerGroupBase& layerGroup) {
|
||||
layerGroup.runTweakers(renderTree, parameters);
|
||||
parameters.currentLayer++;
|
||||
});
|
||||
parameters.currentLayer = 0;
|
||||
orchestrator.visitDebugLayerGroups([&](LayerGroupBase& layerGroup) {
|
||||
layerGroup.runTweakers(renderTree, parameters);
|
||||
parameters.currentLayer++;
|
||||
});
|
||||
}
|
||||
|
||||
// Give the layers a chance to upload
|
||||
if (particleFastFrame && singleContinuous) {
|
||||
orchestrator.visitLayerGroupsInRange(
|
||||
singleContinuous->beginIndex, singleContinuous->endExclusive,
|
||||
[&](LayerGroupBase& layerGroup, size_t) { layerGroup.upload(*uploadPass); });
|
||||
} else {
|
||||
orchestrator.visitLayerGroups([&](LayerGroupBase& layerGroup) { layerGroup.upload(*uploadPass); });
|
||||
}
|
||||
|
||||
// Give the render targets a chance to upload
|
||||
orchestrator.visitRenderTargets([&](RenderTarget& renderTarget) { renderTarget.upload(*uploadPass); });
|
||||
|
||||
// Upload the Debug layer group
|
||||
if (!particleFastFrame) {
|
||||
orchestrator.visitDebugLayerGroups([&](LayerGroupBase& layerGroup) { layerGroup.upload(*uploadPass); });
|
||||
}
|
||||
if (retainedMode) {
|
||||
for (const auto& segment : segmentPlan.segments) {
|
||||
if (segment.strategy == RenderSegmentStrategy::Retained &&
|
||||
wantsResources(segmentDecisions[segment.segmentIndex])) {
|
||||
retainedSegmentCaches[segment.segmentIndex]->upload(*uploadPass);
|
||||
}
|
||||
// Admit and upload compositors only after the Full Symbol inputs have been
|
||||
// updated, tweaked and uploaded. Build has no dependency on an old cache.
|
||||
if (retainedMode) {
|
||||
const auto uploadPass = parameters.encoder->createUploadPass(
|
||||
"retained-composite-upload", parameters.backend.getDefaultRenderable());
|
||||
for (const auto& segment : segmentPlan.segments) {
|
||||
if (segment.strategy == RenderSegmentStrategy::Retained &&
|
||||
wantsResources(segmentDecisions[segment.segmentIndex])) {
|
||||
retainedSegmentCaches[segment.segmentIndex]->upload(*uploadPass);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -833,7 +906,8 @@ void Renderer::Impl::render(const RenderTree& renderTree,
|
||||
const auto buildRetainedRange = [&](RetainedUnderlayCache& cache,
|
||||
RetainedUnderlayDecision& decision,
|
||||
int32_t begin,
|
||||
int32_t endExclusive) {
|
||||
int32_t endExclusive,
|
||||
uint32_t segmentIndex) {
|
||||
if (decision != RetainedUnderlayDecision::Build || begin >= endExclusive) return;
|
||||
bool drawFailed = false;
|
||||
bool globalUniformsBound = false;
|
||||
@@ -870,9 +944,14 @@ void Renderer::Impl::render(const RenderTree& renderTree,
|
||||
if (globalUniformsBound && parameters.renderPass) {
|
||||
context.unbindGlobalUniformBuffers(*parameters.renderPass);
|
||||
}
|
||||
observer->onRenderError(error);
|
||||
onRenderError(error);
|
||||
parameters.renderPass.reset();
|
||||
}
|
||||
if (orchestrator.retainedRangeHasPlacementCapableLayer(begin, endExclusive)) {
|
||||
drawFailed = drawFailed || orchestrator.getRetainedUnderlayContentRevision() != symbolBinding.content ||
|
||||
!retainedSymbolCertificates[segmentIndex].matchesCandidate(
|
||||
orchestrator.retainedSymbolRenderResult(begin, endExclusive, parameters.symbolFadeChange, frameCount));
|
||||
}
|
||||
if (drawFailed) {
|
||||
cache.markBuildFailed("resource-or-compositor");
|
||||
decision = RetainedUnderlayDecision::Fallback;
|
||||
@@ -990,12 +1069,23 @@ void Renderer::Impl::render(const RenderTree& renderTree,
|
||||
drawable3DPass();
|
||||
}
|
||||
drawableTargetsPass();
|
||||
if (orchestrator.getRetainedUnderlayContentRevision() != symbolBinding.content ||
|
||||
orchestrator.getRenderErrorRevision() != renderErrorRevisionAtStart) {
|
||||
particlePreparedSceneSupported = false;
|
||||
for (const auto& segment : segmentPlan.segments) {
|
||||
if (segment.strategy != RenderSegmentStrategy::Retained) continue;
|
||||
retainedSegmentCaches[segment.segmentIndex]->invalidate("content-mutation");
|
||||
retainedSymbolCertificates[segment.segmentIndex].invalidate();
|
||||
segmentDecisions[segment.segmentIndex] = RetainedUnderlayDecision::Fallback;
|
||||
}
|
||||
}
|
||||
for (const auto& segment : segmentPlan.segments) {
|
||||
if (segment.strategy == RenderSegmentStrategy::Retained) {
|
||||
buildRetainedRange(*retainedSegmentCaches[segment.segmentIndex],
|
||||
segmentDecisions[segment.segmentIndex],
|
||||
segment.beginIndex,
|
||||
segment.endExclusive);
|
||||
segment.endExclusive,
|
||||
segment.segmentIndex);
|
||||
}
|
||||
}
|
||||
if (retainedRuntimeState) {
|
||||
@@ -1072,6 +1162,24 @@ void Renderer::Impl::render(const RenderTree& renderTree,
|
||||
parameters.encoder.reset();
|
||||
context.endFrame();
|
||||
|
||||
if (!particleFastFrame) {
|
||||
auto completionBinding = symbolBinding;
|
||||
completionBinding.content = orchestrator.getRetainedUnderlayContentRevision();
|
||||
const bool submitted = orchestrator.getRenderErrorRevision() == renderErrorRevisionAtStart;
|
||||
for (const auto& segment : segmentPlan.segments) {
|
||||
if (segment.strategy != RenderSegmentStrategy::Retained ||
|
||||
!orchestrator.retainedRangeHasPlacementCapableLayer(segment.beginIndex, segment.endExclusive)) continue;
|
||||
const auto index = segment.segmentIndex;
|
||||
const bool resultUnchanged = retainedSymbolCertificates[index].matchesCandidate(
|
||||
orchestrator.retainedSymbolRenderResult(segment.beginIndex, segment.endExclusive,
|
||||
parameters.symbolFadeChange, frameCount));
|
||||
retainedSymbolCertificates[index].completeFull(
|
||||
completionBinding, submitted && resultUnchanged,
|
||||
segmentDecisions[index] == RetainedUnderlayDecision::Build && retainedSegmentCaches[index]->isValid(),
|
||||
segmentDecisions[index] == RetainedUnderlayDecision::Reuse && retainedSegmentCaches[index]->isValid());
|
||||
}
|
||||
}
|
||||
|
||||
#if MLN_RENDER_BACKEND_METAL
|
||||
if constexpr (EnableMetalCapture) {
|
||||
if (commandCaptureScope) {
|
||||
|
||||
@@ -49,7 +49,7 @@ private:
|
||||
void synchronizeRetainedUnderlayState(const std::shared_ptr<RetainedUnderlayRuntimeState>&);
|
||||
bool tryRenderParticleFastFrame(const std::shared_ptr<UpdateParameters>&);
|
||||
void commitPreparedRenderTree(std::unique_ptr<RenderTree>, const std::shared_ptr<UpdateParameters>&);
|
||||
void clearPreparedRenderTree();
|
||||
void clearPreparedRenderTree(bool invalidateSymbolResults = true);
|
||||
|
||||
void reduceMemoryUse();
|
||||
|
||||
@@ -64,6 +64,7 @@ private:
|
||||
std::unique_ptr<RenderStaticData> staticData;
|
||||
std::vector<std::unique_ptr<RetainedUnderlayCache>> retainedSegmentCaches;
|
||||
std::vector<uint64_t> retainedSegmentFingerprints;
|
||||
std::vector<SymbolResultCertificate> retainedSymbolCertificates;
|
||||
std::shared_ptr<RetainedUnderlayRuntimeState> retainedRuntimeState;
|
||||
uint64_t retainedCacheTopologyRevision = 0;
|
||||
RetainedUnderlayRevisionTracker retainedUnderlayRevisionTracker;
|
||||
@@ -107,6 +108,7 @@ private:
|
||||
const ParticleFastPathUpdateSignature&);
|
||||
|
||||
std::optional<ParticleFastPathUpdateSignature> particlePreparedSignature;
|
||||
std::optional<ParticleFastPathUpdateSignature> symbolCompletedSignature;
|
||||
uint64_t particlePreparedContentRevision = 0;
|
||||
uint64_t particlePreparedTopologyRevision = 0;
|
||||
uint64_t particlePreparedStaticRevision = 0;
|
||||
|
||||
51
src/mln/renderer/symbol_render_inputs.hpp
Normal file
51
src/mln/renderer/symbol_render_inputs.hpp
Normal file
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include <mln/renderer/symbol_render_result.hpp>
|
||||
#include <mln/gfx/vertex_attribute.hpp>
|
||||
#include <mln/gfx/vertex_vector.hpp>
|
||||
|
||||
namespace mln {
|
||||
|
||||
inline void appendSymbolAttributes(SymbolRenderResult& result, const std::shared_ptr<gfx::VertexAttributeArray>& attributes) {
|
||||
result.append(static_cast<bool>(attributes));
|
||||
if (!attributes) return;
|
||||
result.append(attributes->allocatedSize());
|
||||
for (std::size_t id = 0; id < attributes->allocatedSize(); ++id) {
|
||||
const auto& attr = attributes->get(id);
|
||||
result.append(static_cast<bool>(attr));
|
||||
if (!attr) continue;
|
||||
result.append(attr->getIndex());
|
||||
result.append(attr->getDataType());
|
||||
result.append(attr->getCount());
|
||||
result.append(attr->getStride());
|
||||
result.append(attr->getSharedType());
|
||||
result.append(attr->getSharedOffset());
|
||||
result.append(attr->getSharedVertexOffset());
|
||||
result.append(attr->getSharedStride());
|
||||
const auto& shared = attr->getSharedRawData();
|
||||
if (shared) {
|
||||
// Collection happens after upload; readable CPU bytes alone cannot
|
||||
// certify an attribute that never obtained a GPU backing buffer.
|
||||
if (!shared->getBuffer()) result.complete = false;
|
||||
// Released vectors are immutable by the producer contract; retain
|
||||
// their identity and GPU backing to prevent pointer ABA. Mutable
|
||||
// vectors (placement, variable anchors, sort and paint) use bytes.
|
||||
result.append(shared->isReleased());
|
||||
if (shared->isReleased()) {
|
||||
result.retain(shared);
|
||||
result.append(reinterpret_cast<std::uintptr_t>(shared->getBuffer()));
|
||||
if (!shared->getBuffer()) result.complete = false;
|
||||
} else {
|
||||
result.append(shared->getRawSize());
|
||||
result.appendBlock(shared->getRawData(), shared->getRawSize() * shared->getRawCount());
|
||||
}
|
||||
} else {
|
||||
const auto& raw = attr->getRawData();
|
||||
result.appendBlock(raw.data(), raw.size());
|
||||
// A non-shared attribute without readable bytes is not a proof.
|
||||
if (attr->getCount() && raw.empty()) result.complete = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mln
|
||||
141
src/mln/renderer/symbol_render_result.hpp
Normal file
141
src/mln/renderer/symbol_render_result.hpp
Normal file
@@ -0,0 +1,141 @@
|
||||
#pragma once
|
||||
|
||||
#include <mln/util/mat4.hpp>
|
||||
#include <mln/util/size.hpp>
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace mln {
|
||||
|
||||
// Internal CPU evidence for the inputs actually consumed by the Symbol shaders.
|
||||
// This is exact byte comparison, not a hash of placed/unplaced booleans.
|
||||
struct SymbolRenderResult {
|
||||
std::vector<uint8_t> bytes;
|
||||
std::vector<std::shared_ptr<const void>> resources;
|
||||
bool complete = true;
|
||||
static constexpr std::size_t maxBytes = 16 * 1024 * 1024;
|
||||
|
||||
void appendBytes(const void* data, std::size_t size) {
|
||||
if (!complete || size > maxBytes - bytes.size() || (size && !data)) {
|
||||
complete = false;
|
||||
return;
|
||||
}
|
||||
if (size) {
|
||||
const auto* begin = static_cast<const uint8_t*>(data);
|
||||
bytes.insert(bytes.end(), begin, begin + size);
|
||||
}
|
||||
}
|
||||
template <class T> void append(const T& value) {
|
||||
static_assert(std::is_trivially_copyable_v<T>);
|
||||
appendBytes(&value, sizeof(value));
|
||||
}
|
||||
void appendBlock(const void* data, std::size_t size) {
|
||||
append(size);
|
||||
appendBytes(data, size);
|
||||
}
|
||||
template <class T> void retain(const std::shared_ptr<T>& resource) {
|
||||
append(reinterpret_cast<std::uintptr_t>(resource.get()));
|
||||
if (resource) resources.emplace_back(resource);
|
||||
}
|
||||
void appendResult(const SymbolRenderResult& result) {
|
||||
complete = complete && result.complete;
|
||||
appendBlock(result.bytes.data(), result.bytes.size());
|
||||
resources.insert(resources.end(), result.resources.begin(), result.resources.end());
|
||||
}
|
||||
};
|
||||
|
||||
// GL and Metal unpack the low bit as the fade direction and clamp the
|
||||
// remaining seven bits / 127 + signed fadeChange to [0,1]. An endpoint in
|
||||
// the target direction remains constant as time advances. A stale controller
|
||||
// says "refresh placement", not "the shader is changing opacity".
|
||||
inline bool symbolOpacityIsSettled(float packed, float fadeChange) {
|
||||
if (!std::isfinite(packed) || !std::isfinite(fadeChange) || fadeChange < 0 ||
|
||||
packed < 0 || packed > 255 || std::floor(packed) != packed) return false;
|
||||
const auto value = static_cast<unsigned>(packed);
|
||||
const float opacity = static_cast<float>(value / 2) / 127.0f;
|
||||
return (value & 1U) ? opacity + fadeChange >= 1.0f : opacity - fadeChange <= 0.0f;
|
||||
}
|
||||
|
||||
struct SymbolResultBinding {
|
||||
Size viewport;
|
||||
mat4 camera{};
|
||||
float pixelRatio = 1;
|
||||
uint64_t content = 0;
|
||||
uint64_t topology = 0;
|
||||
uint64_t config = 0;
|
||||
bool crossSourceCollisions = true;
|
||||
bool operator==(const SymbolResultBinding&) const = default;
|
||||
};
|
||||
|
||||
// Build admission is independent of a previous cache. It requires two
|
||||
// completed Full observations of the same settled, uploaded result. Reuse
|
||||
// additionally requires a Build submitted with this exact result revision.
|
||||
class SymbolResultCertificate {
|
||||
public:
|
||||
bool observeFull(SymbolRenderResult result, const SymbolResultBinding& binding, bool ready) {
|
||||
inFlight = true;
|
||||
const bool changed = !candidateBinding || *candidateBinding != binding ||
|
||||
candidate.bytes != result.bytes || !ready || !result.complete;
|
||||
if (changed) {
|
||||
++revision;
|
||||
builtRevision = 0;
|
||||
}
|
||||
candidate = std::move(result);
|
||||
candidateBinding = binding;
|
||||
candidateReady = ready && candidate.complete;
|
||||
stable = candidateReady && completedBinding && *completedBinding == binding &&
|
||||
completed.bytes == candidate.bytes;
|
||||
return stable;
|
||||
}
|
||||
void completeFull(const SymbolResultBinding& binding, bool submitted, bool built, bool reused = false) {
|
||||
if (!submitted || !candidateReady || !candidateBinding || *candidateBinding != binding) {
|
||||
invalidate();
|
||||
return;
|
||||
}
|
||||
completed = candidate;
|
||||
completedBinding = binding;
|
||||
inFlight = false;
|
||||
if (stable && built) {
|
||||
builtRevision = revision;
|
||||
reuseSubmitted = false;
|
||||
}
|
||||
if (stable && reused && builtRevision == revision) reuseSubmitted = true;
|
||||
}
|
||||
bool canReuse(const SymbolResultBinding& binding) const {
|
||||
return !inFlight && stable && builtRevision == revision && builtRevision != 0 &&
|
||||
completedBinding && *completedBinding == binding;
|
||||
}
|
||||
bool canFast(const SymbolResultBinding& binding) const { return canReuse(binding) && reuseSubmitted; }
|
||||
bool candidateHasBuild(const SymbolResultBinding& binding) const {
|
||||
return candidateReady && stable && builtRevision != 0 && builtRevision == revision &&
|
||||
candidateBinding && *candidateBinding == binding;
|
||||
}
|
||||
bool matchesCandidate(const SymbolRenderResult& result) const {
|
||||
return candidateReady && result.complete && candidate.bytes == result.bytes;
|
||||
}
|
||||
uint64_t current() const { return revision; }
|
||||
void invalidate() {
|
||||
++revision;
|
||||
stable = candidateReady = false;
|
||||
inFlight = reuseSubmitted = false;
|
||||
builtRevision = 0;
|
||||
completedBinding.reset();
|
||||
candidateBinding.reset();
|
||||
candidate = {};
|
||||
completed = {};
|
||||
}
|
||||
|
||||
private:
|
||||
SymbolRenderResult candidate, completed;
|
||||
std::optional<SymbolResultBinding> candidateBinding, completedBinding;
|
||||
uint64_t revision = 1, builtRevision = 0;
|
||||
bool candidateReady = false, stable = false;
|
||||
bool inFlight = false, reuseSubmitted = false;
|
||||
};
|
||||
|
||||
} // namespace mln
|
||||
51
src/mln/renderer/symbol_shader_provenance.hpp
Normal file
51
src/mln/renderer/symbol_shader_provenance.hpp
Normal file
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include <mln/shaders/shader_program_base.hpp>
|
||||
#include <algorithm>
|
||||
#include <mutex>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace mln {
|
||||
|
||||
// No public shader API/ABI changes. Only compilation sites that verify the
|
||||
// original Symbol source can register provenance. Names/types alone cannot
|
||||
// certify a replacement/custom shader with a different fade contract.
|
||||
class SymbolShaderProvenance {
|
||||
public:
|
||||
static SymbolShaderProvenance& get() { static SymbolShaderProvenance value; return value; }
|
||||
void compiled(const std::shared_ptr<gfx::ShaderProgramBase>& shader) {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
std::erase_if(shared, [](const auto& item) { return item.expired(); });
|
||||
shared.emplace_back(shader);
|
||||
}
|
||||
void compiledOwned(const gfx::ShaderProgramBase& shader) {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
owned.push_back(shader.getID().id());
|
||||
}
|
||||
void destroyedOwned(const gfx::ShaderProgramBase& shader) {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
std::erase(owned, shader.getID().id());
|
||||
}
|
||||
bool supports(const gfx::ShaderProgramBase& shader) const {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
if (std::find(owned.begin(), owned.end(), shader.getID().id()) != owned.end()) return true;
|
||||
return std::any_of(shared.begin(), shared.end(), [&](const auto& item) {
|
||||
const auto program = item.lock();
|
||||
return program && program.get() == &shader;
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::mutex mutex;
|
||||
std::vector<std::weak_ptr<gfx::ShaderProgramBase>> shared;
|
||||
std::vector<int64_t> owned;
|
||||
};
|
||||
|
||||
inline bool symbolUniformDefine(std::string_view name) {
|
||||
return name == "HAS_UNIFORM_u_opacity" || name == "HAS_UNIFORM_u_fill_color" ||
|
||||
name == "HAS_UNIFORM_u_halo_color" || name == "HAS_UNIFORM_u_halo_width" ||
|
||||
name == "HAS_UNIFORM_u_halo_blur" || name == "HAS_UNIFORM_u_sorted_instance";
|
||||
}
|
||||
|
||||
} // namespace mln
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <mln/platform/gl_functions.hpp>
|
||||
#include <mln/shaders/program_parameters.hpp>
|
||||
#include <mln/shaders/shader_manifest.hpp>
|
||||
#include <mln/renderer/symbol_shader_provenance.hpp>
|
||||
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
@@ -177,7 +178,31 @@ std::shared_ptr<ShaderProgramGL> ShaderProgramGL::create(
|
||||
addAttr(attrs, attributesInfo[location].id, location, length, size, glType);
|
||||
}
|
||||
|
||||
return std::make_shared<ShaderProgramGL>(std::move(program), std::move(attrs), std::move(samplerLocations));
|
||||
auto result = std::make_shared<ShaderProgramGL>(std::move(program), std::move(attrs), std::move(samplerLocations));
|
||||
bool originalSymbol = false;
|
||||
const auto matches = [&]<shaders::BuiltIn ID>() {
|
||||
using Source = shaders::ShaderSource<ID, gfx::Backend::Type::OpenGL>;
|
||||
return vertexSource == Source::vertex && fragmentSource == Source::fragment;
|
||||
};
|
||||
switch (programParameters.getProgramType()) {
|
||||
case shaders::BuiltIn::SymbolIconShader:
|
||||
originalSymbol = matches.template operator()<shaders::BuiltIn::SymbolIconShader>(); break;
|
||||
case shaders::BuiltIn::SymbolSDFShader:
|
||||
originalSymbol = matches.template operator()<shaders::BuiltIn::SymbolSDFShader>(); break;
|
||||
case shaders::BuiltIn::SymbolTextAndIconShader:
|
||||
originalSymbol = matches.template operator()<shaders::BuiltIn::SymbolTextAndIconShader>(); break;
|
||||
default: break;
|
||||
}
|
||||
std::size_t offset = 0;
|
||||
while (originalSymbol && offset < additionalDefines.size()) {
|
||||
const auto end = additionalDefines.find('\n', offset);
|
||||
const std::string_view line(additionalDefines.data() + offset,
|
||||
(end == std::string::npos ? additionalDefines.size() : end) - offset);
|
||||
originalSymbol = line.starts_with("#define ") && symbolUniformDefine(line.substr(8));
|
||||
offset = end == std::string::npos ? additionalDefines.size() : end + 1;
|
||||
}
|
||||
if (originalSymbol) SymbolShaderProvenance::get().compiled(result);
|
||||
return result;
|
||||
} catch (const std::exception& e) {
|
||||
context.getObserver().onShaderCompileFailed(
|
||||
programParameters.getProgramType(), gfx::Backend::Type::OpenGL, additionalDefines);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <mln/mtl/vertex_attribute.hpp>
|
||||
#include <mln/shaders/program_parameters.hpp>
|
||||
#include <mln/shaders/shader_manifest.hpp>
|
||||
#include <mln/renderer/symbol_shader_provenance.hpp>
|
||||
#include <mln/util/logging.hpp>
|
||||
#include <mln/util/hash.hpp>
|
||||
|
||||
@@ -80,7 +81,9 @@ ShaderProgram::ShaderProgram(std::string name, RendererBackend& backend_, MTLFun
|
||||
vertexFunction(std::move(vert)),
|
||||
fragmentFunction(std::move(frag)) {}
|
||||
|
||||
ShaderProgram::~ShaderProgram() noexcept = default;
|
||||
ShaderProgram::~ShaderProgram() noexcept {
|
||||
SymbolShaderProvenance::get().destroyedOwned(*this);
|
||||
}
|
||||
|
||||
MTLRenderPipelineStatePtr ShaderProgram::getRenderPipelineState(const gfx::Renderable& renderable,
|
||||
const MTLVertexDescriptorPtr& vertexDescriptor,
|
||||
|
||||
@@ -175,6 +175,10 @@ bool PlacementController::hasTransitions(TimePoint now) const {
|
||||
return placement->hasTransitions(now);
|
||||
}
|
||||
|
||||
bool PlacementController::hasActiveSymbolFade(TimePoint now) const {
|
||||
return placement->transitionsEnabled() && placement->hasTransitions(now);
|
||||
}
|
||||
|
||||
// Placement implementation
|
||||
|
||||
Placement::Placement(std::shared_ptr<const UpdateParameters> updateParameters_,
|
||||
|
||||
@@ -102,6 +102,8 @@ public:
|
||||
void setPlacementStale() { stale = true; }
|
||||
bool placementIsRecent(TimePoint now, float zoom, std::optional<Duration> periodOverride = std::nullopt) const;
|
||||
bool hasTransitions(TimePoint now) const;
|
||||
// Cache result stability excludes stale's scheduling-only refresh request.
|
||||
bool hasActiveSymbolFade(TimePoint now) const;
|
||||
|
||||
private:
|
||||
Immutable<Placement> placement;
|
||||
|
||||
@@ -10,8 +10,10 @@
|
||||
#include <mln/renderer/render_source.hpp>
|
||||
#include <mln/renderer/render_source_observer.hpp>
|
||||
#include <mln/renderer/tile_render_data.hpp>
|
||||
#include <mln/renderer/symbol_render_result.hpp>
|
||||
#include <mln/style/sources/geojson_source_impl.hpp>
|
||||
#include <mln/style/layers/symbol_layer.hpp>
|
||||
#include <mln/style/layers/background_layer.hpp>
|
||||
#include <mln/tile/tile.hpp>
|
||||
#include <mln/tile/tile_observer.hpp>
|
||||
|
||||
@@ -435,3 +437,137 @@ TEST(Step117Safety, RealAsyncCallbacksAndContextStyleCleanupAdvanceContentEpoch)
|
||||
scheduler.runRenderJobs();
|
||||
// Actual callback dispatch, but no network delivery, GPU, or Android assertion.
|
||||
}
|
||||
|
||||
namespace {
|
||||
void step119Certify(SymbolResultCertificate& certificate, SymbolResultBinding binding) {
|
||||
SymbolRenderResult result;
|
||||
result.append(255.0f);
|
||||
EXPECT_FALSE(certificate.observeFull(result, binding, true));
|
||||
certificate.completeFull(binding, true, false);
|
||||
EXPECT_TRUE(certificate.observeFull(result, binding, true));
|
||||
certificate.completeFull(binding, true, true);
|
||||
EXPECT_TRUE(certificate.observeFull(result, binding, true));
|
||||
certificate.completeFull(binding, true, false, true);
|
||||
EXPECT_TRUE(certificate.canFast(binding));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST(Step119SymbolResults, RealTileGlyphImageStyleContextCallbacksRejectBuiltCertificate) {
|
||||
TaggedScheduler scheduler{Scheduler::GetBackground(), {}};
|
||||
RenderOrchestrator orchestrator(false, scheduler, std::nullopt);
|
||||
std::atomic<int> deferred{0}, jobs{0}, detached{0};
|
||||
auto source = std::make_unique<TeardownSource>(scheduler, deferred, jobs, detached);
|
||||
const auto rejects = [&](auto event) {
|
||||
SymbolResultBinding binding;
|
||||
binding.content = orchestrator.getRetainedUnderlayContentRevision();
|
||||
SymbolResultCertificate certificate;
|
||||
step119Certify(certificate, binding);
|
||||
event();
|
||||
binding.content = orchestrator.getRetainedUnderlayContentRevision();
|
||||
EXPECT_FALSE(certificate.canFast(binding));
|
||||
};
|
||||
rejects([&] {
|
||||
static_cast<RenderSourceObserver&>(orchestrator).onTileChanged(*source, OverscaledTileID{2, 1, {2, 1, 1}});
|
||||
});
|
||||
rejects([&] { static_cast<GlyphManagerObserver&>(orchestrator).onGlyphsLoaded({}, {0, 255}); });
|
||||
rejects([&] { static_cast<ImageManagerObserver&>(orchestrator).onStyleImageMissing("step119-icon", [] {}); });
|
||||
rejects([&] { orchestrator.markContextLost(); });
|
||||
rejects([&] { orchestrator.clearData(); });
|
||||
source->setObserver(nullptr);
|
||||
source.reset();
|
||||
scheduler.waitForEmpty();
|
||||
scheduler.runRenderJobs();
|
||||
}
|
||||
|
||||
TEST(Step119SymbolResults, AppliedCrossSegmentRegistryRebuildChangesExactEvidence) {
|
||||
TaggedScheduler scheduler{Scheduler::GetBackground(), {}};
|
||||
RenderOrchestrator orchestrator(false, scheduler, std::nullopt);
|
||||
auto runtime = std::make_shared<RetainedUnderlayRuntimeState>();
|
||||
style::BackgroundLayer lower("step119-lower"), continuous("step119-middle"), upper("step119-upper");
|
||||
const Immutable<std::vector<Immutable<style::Layer::Impl>>> layers =
|
||||
makeMutable<std::vector<Immutable<style::Layer::Impl>>>(
|
||||
std::initializer_list<Immutable<style::Layer::Impl>>{lower.baseImpl, continuous.baseImpl, upper.baseImpl});
|
||||
const Immutable<std::vector<Immutable<style::Source::Impl>>> sources =
|
||||
makeMutable<std::vector<Immutable<style::Source::Impl>>>();
|
||||
auto tree = orchestrator.createRenderTree(step117TreeParameters(layers, sources, runtime, TimePoint{}), nullptr);
|
||||
ASSERT_TRUE(tree);
|
||||
orchestrator.processChanges();
|
||||
auto group = std::make_shared<TestLayerGroup>(0);
|
||||
ASSERT_TRUE(orchestrator.addLayerGroup(group));
|
||||
ASSERT_TRUE(orchestrator.addLayerGroup(std::make_shared<TestLayerGroup>(2)));
|
||||
const auto before = orchestrator.retainedSymbolRenderResult(2, 3, 1, 0);
|
||||
ASSERT_TRUE(before.complete);
|
||||
SymbolResultCertificate certificate;
|
||||
SymbolResultBinding binding;
|
||||
step119Certify(certificate, binding);
|
||||
UniqueChangeRequestVec changes;
|
||||
changes.emplace_back(std::make_unique<RemoveLayerGroupRequest>(group));
|
||||
RenderOrchestratorTeardownTestAccess::enqueue(orchestrator, changes);
|
||||
const auto pending = orchestrator.retainedSymbolRenderResult(2, 3, 1, 0);
|
||||
EXPECT_FALSE(pending.complete);
|
||||
EXPECT_FALSE(certificate.observeFull(pending, binding, true));
|
||||
orchestrator.processChanges();
|
||||
EXPECT_EQ(orchestrator.numLayerGroups(), 1u);
|
||||
const auto after = orchestrator.retainedSymbolRenderResult(2, 3, 1, 0);
|
||||
EXPECT_TRUE(after.complete);
|
||||
EXPECT_NE(before.bytes, after.bytes); // actual global count + original ordinal changed
|
||||
EXPECT_FALSE(certificate.canFast(binding));
|
||||
}
|
||||
|
||||
TEST(Step119SymbolResults, RangeDrawFailureInvalidatesContentAndCompletedEvidence) {
|
||||
TaggedScheduler scheduler{Scheduler::GetBackground(), {}};
|
||||
RenderOrchestrator orchestrator(false, scheduler, std::nullopt);
|
||||
ErrorObserver observer;
|
||||
orchestrator.setObserver(&observer);
|
||||
ASSERT_TRUE(orchestrator.addLayerGroup(std::make_shared<TestLayerGroup>(0)));
|
||||
SymbolResultBinding binding;
|
||||
binding.content = orchestrator.getRetainedUnderlayContentRevision();
|
||||
SymbolResultCertificate certificate;
|
||||
step119Certify(certificate, binding);
|
||||
const auto errors = orchestrator.getRenderErrorRevision();
|
||||
bool failed = false;
|
||||
orchestrator.visitLayerGroupsInRange(0, 1, [](LayerGroupBase&, size_t) {
|
||||
throw std::runtime_error("step119-upload-or-draw");
|
||||
}, &failed);
|
||||
EXPECT_TRUE(failed);
|
||||
EXPECT_EQ(observer.errors, 1);
|
||||
EXPECT_GT(orchestrator.getRenderErrorRevision(), errors);
|
||||
binding.content = orchestrator.getRetainedUnderlayContentRevision();
|
||||
EXPECT_FALSE(certificate.canReuse(binding));
|
||||
EXPECT_FALSE(certificate.canFast(binding));
|
||||
}
|
||||
|
||||
TEST(Step119SymbolResults, RealSourceReplacementAndMissingSourceRetainFailClosed) {
|
||||
TaggedScheduler scheduler{Scheduler::GetBackground(), {}};
|
||||
RenderOrchestrator orchestrator(false, scheduler, std::nullopt);
|
||||
auto runtime = std::make_shared<RetainedUnderlayRuntimeState>();
|
||||
ASSERT_TRUE(runtime->setRenderSegmentPlan({1, {{RenderSegmentStrategy::Retained, std::nullopt}}}));
|
||||
style::SymbolLayer symbol("step119-symbol", "step119-source");
|
||||
const Immutable<std::vector<Immutable<style::Layer::Impl>>> layers =
|
||||
makeMutable<std::vector<Immutable<style::Layer::Impl>>>(
|
||||
std::initializer_list<Immutable<style::Layer::Impl>>{symbol.baseImpl});
|
||||
const auto sourceList = []() -> Immutable<std::vector<Immutable<style::Source::Impl>>> {
|
||||
return makeMutable<std::vector<Immutable<style::Source::Impl>>>(
|
||||
std::initializer_list<Immutable<style::Source::Impl>>{
|
||||
makeMutable<style::GeoJSONSource::Impl>("step119-source", style::GeoJSONOptions::defaultOptions())});
|
||||
};
|
||||
auto tree = orchestrator.createRenderTree(step117TreeParameters(layers, sourceList(), runtime, TimePoint{}), nullptr);
|
||||
ASSERT_TRUE(tree);
|
||||
SymbolResultBinding binding;
|
||||
binding.content = orchestrator.getRetainedUnderlayContentRevision();
|
||||
SymbolResultCertificate certificate;
|
||||
step119Certify(certificate, binding);
|
||||
tree.reset();
|
||||
tree = orchestrator.createRenderTree(step117TreeParameters(layers, sourceList(), runtime, TimePoint{} + Seconds(1)), nullptr);
|
||||
ASSERT_TRUE(tree);
|
||||
binding.content = orchestrator.getRetainedUnderlayContentRevision();
|
||||
EXPECT_FALSE(certificate.canFast(binding));
|
||||
tree.reset();
|
||||
const Immutable<std::vector<Immutable<style::Source::Impl>>> absent =
|
||||
makeMutable<std::vector<Immutable<style::Source::Impl>>>();
|
||||
tree = orchestrator.createRenderTree(step117TreeParameters(layers, absent, runtime, TimePoint{} + Seconds(2)), nullptr);
|
||||
ASSERT_TRUE(tree);
|
||||
EXPECT_STREQ(orchestrator.retainedSegmentSourceState(0, 1).reason, "missing-source");
|
||||
binding.content = orchestrator.getRetainedUnderlayContentRevision();
|
||||
EXPECT_FALSE(certificate.canFast(binding));
|
||||
}
|
||||
|
||||
@@ -5,11 +5,18 @@
|
||||
#include <mln/renderer/render_segment_plan.hpp>
|
||||
#include <mln/renderer/update_parameters.hpp>
|
||||
#include <mln/text/placement.hpp>
|
||||
#include <mln/renderer/symbol_render_result.hpp>
|
||||
#include <mln/renderer/symbol_render_inputs.hpp>
|
||||
#include <mln/renderer/buckets/symbol_bucket.hpp>
|
||||
#include <mln/renderer/symbol_shader_provenance.hpp>
|
||||
#include <mln/shaders/symbol_layer_ubo.hpp>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <initializer_list>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -98,6 +105,265 @@ std::shared_ptr<UpdateParameters> step117PlacementParameters(TimePoint now,
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
SymbolResultBinding step119Binding() {
|
||||
SymbolResultBinding binding;
|
||||
binding.viewport = {640, 480};
|
||||
binding.camera = key().cameraMatrix;
|
||||
binding.content = binding.topology = binding.config = 1;
|
||||
return binding;
|
||||
}
|
||||
|
||||
SymbolRenderResult step119BufferResult(const SymbolBucket::Buffer& buffer, float fade) {
|
||||
SymbolRenderResult result;
|
||||
const auto& dynamic = buffer.dynamicAttributeData();
|
||||
const auto& opacity = buffer.opacityAttributeData();
|
||||
result.appendBlock(dynamic.getRawData(), dynamic.bytes());
|
||||
result.appendBlock(opacity.getRawData(), opacity.bytes());
|
||||
for (const auto& vertex : opacity.vector()) {
|
||||
result.complete = result.complete && symbolOpacityIsSettled(vertex.a1[0], fade);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void step119Settle(SymbolResultCertificate& certificate, const SymbolRenderResult& result,
|
||||
const SymbolResultBinding& binding) {
|
||||
EXPECT_FALSE(certificate.observeFull(result, binding, true));
|
||||
certificate.completeFull(binding, true, false);
|
||||
EXPECT_TRUE(certificate.observeFull(result, binding, true));
|
||||
EXPECT_FALSE(certificate.candidateHasBuild(binding));
|
||||
certificate.completeFull(binding, true, true);
|
||||
EXPECT_TRUE(certificate.canReuse(binding));
|
||||
EXPECT_FALSE(certificate.canFast(binding));
|
||||
EXPECT_TRUE(certificate.observeFull(result, binding, true));
|
||||
EXPECT_TRUE(certificate.candidateHasBuild(binding));
|
||||
certificate.completeFull(binding, true, false, true);
|
||||
EXPECT_TRUE(certificate.canFast(binding));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST(Step119SymbolResults, FreshSettledBuildReuseWithRealControllerAndBuffer) {
|
||||
const auto start = TimePoint{} + Seconds(10);
|
||||
PlacementController controller;
|
||||
SymbolBucket::Buffer buffer;
|
||||
buffer.dynamicAttributeData().emplace_back(SymbolBucket::dynamicLayoutAttributes({20, 30}, 0));
|
||||
buffer.opacityAttributeData().emplace_back(SymbolBucket::opacityAttributes(true, 1));
|
||||
SymbolResultCertificate certificate;
|
||||
RetainedUnderlayCache cache;
|
||||
const auto binding = step119Binding();
|
||||
unsigned builds = 0, reuses = 0, calculations = 0;
|
||||
for (unsigned frame = 0; frame < 50; ++frame) {
|
||||
const auto now = start + Milliseconds(frame * 30);
|
||||
if (!controller.placementIsRecent(now, 0)) {
|
||||
auto placement = Placement::create(step117PlacementParameters(now), controller.getPlacement());
|
||||
placement->placeLayers({});
|
||||
controller.setPlacement(std::move(placement));
|
||||
++calculations;
|
||||
} else {
|
||||
controller.setPlacementStale();
|
||||
EXPECT_TRUE(controller.hasTransitions(now)); // scheduling preserved
|
||||
EXPECT_FALSE(controller.hasActiveSymbolFade(now));
|
||||
}
|
||||
const auto result = step119BufferResult(buffer, controller.getPlacement()->symbolFadeChange(now));
|
||||
const bool stable = certificate.observeFull(result, binding, !controller.hasActiveSymbolFade(now));
|
||||
const auto decision = cache.evaluate(key(), certificate.current(), stable, "static-placement");
|
||||
EXPECT_FALSE(certificate.canFast(binding)); // Full still in flight
|
||||
if (decision == RetainedUnderlayDecision::Build) { cache.markBuildSucceeded(); ++builds; }
|
||||
if (decision == RetainedUnderlayDecision::Reuse) ++reuses;
|
||||
certificate.completeFull(binding, true, decision == RetainedUnderlayDecision::Build,
|
||||
decision == RetainedUnderlayDecision::Reuse);
|
||||
}
|
||||
EXPECT_GT(calculations, 1u);
|
||||
EXPECT_EQ(builds, 1u);
|
||||
EXPECT_GT(reuses, 0u);
|
||||
EXPECT_TRUE(certificate.canFast(binding));
|
||||
// Cache submission here is a state-machine fixture, not a GPU draw proof.
|
||||
}
|
||||
|
||||
TEST(Step119SymbolResults, ActualPackedOpacityRejectsFadeAndAcceptsShaderEndpoints) {
|
||||
for (unsigned opacity = 0; opacity <= 127; ++opacity) {
|
||||
const float packedIn = static_cast<float>(opacity * 2 + 1);
|
||||
const float packedOut = static_cast<float>(opacity * 2);
|
||||
EXPECT_EQ(symbolOpacityIsSettled(packedIn, 0), opacity == 127);
|
||||
EXPECT_EQ(symbolOpacityIsSettled(packedOut, 0), opacity == 0);
|
||||
EXPECT_TRUE(symbolOpacityIsSettled(packedIn, 1));
|
||||
EXPECT_TRUE(symbolOpacityIsSettled(packedOut, 1));
|
||||
EXPECT_TRUE(symbolOpacityIsSettled(packedIn, 5));
|
||||
}
|
||||
EXPECT_FALSE(symbolOpacityIsSettled(1, 0.5f));
|
||||
EXPECT_FALSE(symbolOpacityIsSettled(254, 0.5f));
|
||||
EXPECT_FALSE(symbolOpacityIsSettled(1.5f, 1));
|
||||
EXPECT_FALSE(symbolOpacityIsSettled(256, 1));
|
||||
EXPECT_FALSE(symbolOpacityIsSettled(1, std::numeric_limits<float>::infinity()));
|
||||
EXPECT_FALSE(symbolOpacityIsSettled(1, std::numeric_limits<float>::quiet_NaN()));
|
||||
EXPECT_FALSE(symbolOpacityIsSettled(255, -1));
|
||||
SymbolBucket::Buffer buffer;
|
||||
buffer.opacityAttributeData().emplace_back(SymbolBucket::opacityAttributes(true, 0));
|
||||
SymbolResultCertificate certificate;
|
||||
const auto binding = step119Binding();
|
||||
const auto fading = step119BufferResult(buffer, 0.5f);
|
||||
ASSERT_FALSE(fading.complete);
|
||||
EXPECT_FALSE(certificate.observeFull(fading, binding, true));
|
||||
certificate.completeFull(binding, true, true);
|
||||
EXPECT_FALSE(certificate.canReuse(binding));
|
||||
}
|
||||
|
||||
TEST(Step119SymbolResults, SamePlacedBooleanDoesNotProveVariableAnchorOrOrientation) {
|
||||
SymbolBucket::Buffer buffer;
|
||||
buffer.dynamicAttributeData().emplace_back(SymbolBucket::dynamicLayoutAttributes({20, 30}, 0));
|
||||
buffer.opacityAttributeData().emplace_back(SymbolBucket::opacityAttributes(true, 1));
|
||||
const auto binding = step119Binding();
|
||||
for (const auto changed : {SymbolBucket::dynamicLayoutAttributes({21, 30}, 0),
|
||||
SymbolBucket::dynamicLayoutAttributes({20, 30}, 1)}) {
|
||||
buffer.dynamicAttributeData().at(0) = SymbolBucket::dynamicLayoutAttributes({20, 30}, 0);
|
||||
SymbolResultCertificate certificate;
|
||||
step119Settle(certificate, step119BufferResult(buffer, 1), binding);
|
||||
buffer.dynamicAttributeData().at(0) = changed;
|
||||
EXPECT_FALSE(certificate.observeFull(step119BufferResult(buffer, 1), binding, true));
|
||||
EXPECT_FALSE(certificate.canFast(binding));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Step119SymbolResults, CameraViewportPixelRatioAndCollisionConfigurationInvalidate) {
|
||||
SymbolRenderResult result;
|
||||
result.append(255.0f);
|
||||
const auto original = step119Binding();
|
||||
std::vector<SymbolResultBinding> updates(7, original);
|
||||
updates[0].camera[12] += 1; // pan
|
||||
updates[1].camera[0] *= 2; // zoom
|
||||
updates[2].camera[1] = 0.5; // rotation/pitch projection
|
||||
updates[3].viewport.width += 1;
|
||||
updates[4].pixelRatio = 2;
|
||||
updates[5].crossSourceCollisions = false;
|
||||
updates[6].config += 1;
|
||||
for (const auto& update : updates) {
|
||||
SymbolResultCertificate certificate;
|
||||
step119Settle(certificate, result, original);
|
||||
EXPECT_FALSE(certificate.canFast(update));
|
||||
EXPECT_FALSE(certificate.observeFull(result, update, true));
|
||||
}
|
||||
CollisionGroups shared(true), isolated(false);
|
||||
EXPECT_EQ(shared.get("A").first, shared.get("B").first);
|
||||
EXPECT_NE(isolated.get("A").first, isolated.get("B").first);
|
||||
}
|
||||
|
||||
TEST(Step119SymbolResults, FailedUploadDrawSubmissionAndUnreadableResultsCannotCertify) {
|
||||
SymbolRenderResult result;
|
||||
result.append(255.0f);
|
||||
const auto binding = step119Binding();
|
||||
SymbolResultCertificate certificate;
|
||||
EXPECT_FALSE(certificate.observeFull(result, binding, true));
|
||||
certificate.completeFull(binding, false, false);
|
||||
EXPECT_FALSE(certificate.observeFull(result, binding, true));
|
||||
certificate.completeFull(binding, true, false);
|
||||
EXPECT_TRUE(certificate.observeFull(result, binding, true));
|
||||
certificate.completeFull(binding, true, false); // resource/compositor failed
|
||||
EXPECT_FALSE(certificate.canReuse(binding));
|
||||
result.complete = false;
|
||||
EXPECT_FALSE(certificate.observeFull(result, binding, true));
|
||||
certificate.completeFull(binding, true, true);
|
||||
EXPECT_FALSE(certificate.canFast(binding));
|
||||
SymbolRenderResult oversize;
|
||||
oversize.appendBytes(nullptr, SymbolRenderResult::maxBytes + 1);
|
||||
EXPECT_FALSE(oversize.complete);
|
||||
auto attributes = std::make_shared<gfx::VertexAttributeArray>();
|
||||
const auto& attribute = attributes->set(shaders::idSymbolFadeOpacityAttribute);
|
||||
ASSERT_TRUE(attribute);
|
||||
auto opacity = std::make_shared<SymbolBucket::OpacityAttributeVector>();
|
||||
opacity->emplace_back(SymbolBucket::opacityAttributes(true, 1));
|
||||
attribute->setSharedRawData(opacity, 0, 0, sizeof(SymbolOpacityAttributes), gfx::AttributeDataType::Float);
|
||||
SymbolRenderResult notUploaded;
|
||||
appendSymbolAttributes(notUploaded, attributes);
|
||||
EXPECT_FALSE(notUploaded.complete); // real CPU vector without a GPU buffer
|
||||
opacity->release();
|
||||
SymbolRenderResult releasedWithoutBacking;
|
||||
appendSymbolAttributes(releasedWithoutBacking, attributes);
|
||||
EXPECT_FALSE(releasedWithoutBacking.complete);
|
||||
}
|
||||
|
||||
TEST(Step119SymbolResults, ExactPayloadAndAtlasIdentityRequireNewResultRevision) {
|
||||
const auto binding = step119Binding();
|
||||
SymbolRenderResult original;
|
||||
original.append(255.0f);
|
||||
original.append(0.0f); // uniform / draw ordering test payload
|
||||
for (unsigned component = 0; component < original.bytes.size(); ++component) {
|
||||
SymbolResultCertificate certificate;
|
||||
step119Settle(certificate, original, binding);
|
||||
auto changed = original;
|
||||
changed.bytes[component] ^= 1;
|
||||
const auto revision = certificate.current();
|
||||
EXPECT_FALSE(certificate.observeFull(changed, binding, true));
|
||||
EXPECT_FALSE(certificate.candidateHasBuild(binding));
|
||||
EXPECT_GT(certificate.current(), revision);
|
||||
EXPECT_FALSE(certificate.matchesCandidate(original));
|
||||
}
|
||||
// Shader/texture identities are kept alive as part of the exact evidence.
|
||||
auto oldAtlas = std::make_shared<unsigned>(1), newAtlas = std::make_shared<unsigned>(1);
|
||||
SymbolRenderResult oldResult, newResult;
|
||||
oldResult.retain(oldAtlas);
|
||||
newResult.retain(newAtlas);
|
||||
SymbolResultCertificate certificate;
|
||||
step119Settle(certificate, oldResult, binding);
|
||||
EXPECT_FALSE(certificate.observeFull(newResult, binding, true));
|
||||
// Payload coverage is not a texture readback or real wrap/collision draw.
|
||||
}
|
||||
|
||||
TEST(Step119SymbolResults, ActualUniformAndWrappedTileInputsDifferEvenWithSamePlacement) {
|
||||
shaders::SymbolDrawableUBO uniform{};
|
||||
uniform.matrix[0] = 1;
|
||||
uniform.opacity_t = 0.5;
|
||||
const auto payload = [](const shaders::SymbolDrawableUBO& ubo, const OverscaledTileID& tile) {
|
||||
SymbolRenderResult result;
|
||||
result.appendBytes(&ubo, offsetof(shaders::SymbolDrawableUBO, halo_blur_t) + sizeof(float));
|
||||
const auto name = util::toString(tile);
|
||||
result.appendBlock(name.data(), name.size());
|
||||
return result;
|
||||
};
|
||||
const auto binding = step119Binding();
|
||||
const OverscaledTileID tile{2, 0, {2, 1, 1}}, wrapped{2, 1, {2, 1, 1}};
|
||||
const auto original = payload(uniform, tile);
|
||||
SymbolResultCertificate certificate;
|
||||
step119Settle(certificate, original, binding);
|
||||
EXPECT_FALSE(certificate.matchesCandidate(payload(uniform, wrapped)));
|
||||
uniform.opacity_t = 0.75;
|
||||
EXPECT_FALSE(certificate.observeFull(payload(uniform, tile), binding, true));
|
||||
EXPECT_FALSE(certificate.canFast(binding));
|
||||
}
|
||||
|
||||
TEST(Step119SymbolResults, AnnotationGateBlocksOtherwiseCertifiedResultAndKeeps24GateAgreement) {
|
||||
SymbolRenderResult result;
|
||||
result.append(255.0f);
|
||||
const auto binding = step119Binding();
|
||||
SymbolResultCertificate certificate;
|
||||
step119Settle(certificate, result, binding);
|
||||
auto input = fastPathInput();
|
||||
input.staticStable = certificate.canFast(binding);
|
||||
ASSERT_TRUE(evaluateParticleFastPath(input).candidate);
|
||||
input.annotationMutation = true;
|
||||
EXPECT_FALSE(evaluateParticleFastPath(input).candidate);
|
||||
EXPECT_EQ(evaluateParticleFastPath(input).reason, ParticleFastPathMissReason::AnnotationMutation);
|
||||
EXPECT_EQ(particleFastGateFailureMask(input), 1u << 23);
|
||||
}
|
||||
|
||||
namespace {
|
||||
class Step119UnknownSymbolProgram final : public gfx::ShaderProgramBase {
|
||||
public:
|
||||
const std::string_view typeName() const noexcept override { return "GenericGLShader"; }
|
||||
std::optional<size_t> getSamplerLocation(size_t) const override { return std::nullopt; }
|
||||
const gfx::VertexAttributeArray& getVertexAttributes() const override { throw std::logic_error("unknown shader"); }
|
||||
const gfx::VertexAttributeArray& getInstanceAttributes() const override { throw std::logic_error("unknown shader"); }
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST(Step119SymbolResults, UnknownShaderCannotBorrowNativeFadeContractByName) {
|
||||
Step119UnknownSymbolProgram replacement;
|
||||
EXPECT_FALSE(SymbolShaderProvenance::get().supports(replacement));
|
||||
EXPECT_TRUE(symbolUniformDefine("HAS_UNIFORM_u_opacity"));
|
||||
EXPECT_TRUE(symbolUniformDefine("HAS_UNIFORM_u_sorted_instance"));
|
||||
EXPECT_FALSE(symbolUniformDefine("HAS_UNIFORM_u_opacity 1\n#define main custom"));
|
||||
EXPECT_FALSE(symbolUniformDefine("HAS_UNIFORM_u_custom_animation"));
|
||||
}
|
||||
|
||||
TEST(RetainedDynamicBoundary, SelectsFirstCandidate) {
|
||||
RetainedDynamicBoundaryTracker none;
|
||||
none.consider(0, false);
|
||||
|
||||
@@ -1187,3 +1187,10 @@ Android V3-08 单次综合验收已完成并关闭。Native `13.4.1-particle.2`
|
||||
- 2026-10-11 STEP-118 用户允许删除tide:旧PBF正式sources实际无tide,唯一坏引用为63基础Layer的tide-spots Symbol。RDC新增TestApp独立STATIC_RC_NO_TIDE副本,只排除唯一孤立Layer,原五场景/63正式Style/Native24Gate不变;严格Python verifier校验原63→有效62加天气2(预期Native64)及真实SHA、来源和R边界。正式远端Style未改。待本轮Mac APK与设备实测判断R missing-source是否解除、Symbol/Cache/Fast是否改善。
|
||||
|
||||
- 2026-10-11 STEP-118 实机:用户授权后RDC Mac构建Test App源码3051196054,APK SHA 44afcfabe0cf4f1ec93f35899241bd73093aa6db4b5cb82ca2120dd531b8f3e9,FZG1-2设备adb install成功且base.apk SHA相同。新STATIC_RC_NO_TIDE(仅Test App修改)90.008秒:原63→有效62基础Layer、Native64,R[0,62)、missingSourceIds=[]、fallback由missing-source变static-placement,缺tide已真机解除;Cache Build/Reuse0/0,FastDelta0、FullDelta942,PlacementGate=false,整体PARTIAL/原Fast FAIL。报告docs/60_测试/STEP-118_潮汐清理新APK设备实测结果_20261011.md。
|
||||
|
||||
|
||||
- 2026-10-11 STEP-119 Codex(实际gpt-6.1-sol/high已核实):在particle/main@869930046b11dbeb3b147e3be952a2e847316292实现Shared Core最终Symbol结果证书。普通Full先完成全场Placement、vertices、processChanges、tweakers/uploads,再精确比较真实UBO、动态/paint/opacity/索引/排序、wrap、atlas/shader、混合/剔除/全局ordinal与版本;成功提交Full/Build后绑定revision,成功Full Reuse后才Fast。区分stale刷新与实际fade,global/perSegment/preparedScene全部接入;普通Full保留证据、真正reset/异步/错误失效,原24Gate/API/ABI/Style/Source/天气/timeline不改。
|
||||
- 新增13项非Jest C++测试源码(两个已登记文件),包含真实Controller/Buffer/回调/Source/registry与失败/Annotation/未知shader反例;全部NOT COMPILED/NOT RUN,fixture不是GPU等价证据。四份中文文档已写,完成报告docs/报告/STEP-119_Symbol缓存_Codex完成报告_v1.md;状态IMPLEMENTED_WAITING_ANDROID,不宣称真机Build/Reuse/Fast改善。16MiB上限、取证性能、Metal/iOS与实际像素/竞态待独立验证,Vulkan/WebGPU Symbol缓存保持保守拒绝。
|
||||
- 本轮仅轻量阅读、静态范围/保护哈希与git diff --check;未运行Native/CMake/Ninja/fsyntax-only/GTest、Gradle/APK、ADB/设备、Bazel/codegen/Jest,未commit/push。STEP110/111未跟踪MD、STEP117/118原证据与63/62场景保留。现有verifier源码锁/新APK设备SHA保护不绕过;立即交RDC按STEP119联合验收计划独立审计、源码锁、定向验证及一次集中build/install和>=90秒正负场景/失效/视觉闭环。
|
||||
|
||||
- 2026-10-11 STEP119 RDC编译核验:Integration与Particle已fetch对应远端主线,保护STEP110/111 SHA不变。Mac CMake配置因既有libuv缺失失败;Homebrew意外尝试连带升级工具链,已停止且原CMake可用,不继续扩大环境修改。复用现有Android NDK OpenGL x86_64精确compile_commands,7个生产Core文件+2个非Jest GTest源码合计9个严格-Werror语法编译最终PASS;修正SymbolLayer/test各一处util::toString命名空间、test Mutable→Immutable。GTest/完整链接/Android真机仍待独立执行,未宣称PASS。
|
||||
|
||||
Reference in New Issue
Block a user