From 087d80d0527c33743a6c59a5c7cce5eeb836dfb5 Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Tue, 1 Sep 2026 22:40:26 +0800 Subject: [PATCH] fix: seed particle state explicitly --- ...040_Android离线Fixture正式粒子最小闭环_v1.md | 2 +- ...oid正式粒子StateSeed修复_Codex完成报告_v1.md | 37 +++++++++++++++++++ .../shaders/gl/particle_gpu_validation.hpp | 5 ++- shaders/particle_gpu_validation.fragment.glsl | 5 ++- .../render_particle_validation_layer.cpp | 12 +++++- 粒子开发记录.md | 8 ++++ 6 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 docs/报告/40_01_Android正式粒子StateSeed修复_Codex完成报告_v1.md diff --git a/docs/50_Step记录/STEP-040_Android离线Fixture正式粒子最小闭环_v1.md b/docs/50_Step记录/STEP-040_Android离线Fixture正式粒子最小闭环_v1.md index b36800e6a..10e82afcc 100644 --- a/docs/50_Step记录/STEP-040_Android离线Fixture正式粒子最小闭环_v1.md +++ b/docs/50_Step记录/STEP-040_Android离线Fixture正式粒子最小闭环_v1.md @@ -36,4 +36,4 @@ ## 当前结果、阻塞与下一步 -正式 Source/Layer/RenderLayer 已不依赖 source-less `particle-validation` Layer;历史 validation Layer 只保留为既有验证工具。Android 首次实际启动的 RasterArray source RenderItem 空指针已在 source 内补为合法 no-op RenderItem,保持全局 RenderSource 契约。随后实机日志发现 ACTIVE/STATIC 每帧重置:RenderOrchestrator 的按 source 可渲染性扫描会短暂调用 `markLayerRenderable(false)`,它不是持久 disabled;粒子层原先却在该回调中 `resetSequence()`,每帧清空跨 render 的 frame/slot/seed 状态。已删除该错误 reset 边界,真实重建仍限于 field、viewport、context 与 layer removal;新增状态转换及前 3 帧采样日志。最新实机日志确认 simulation 已正常推进但画面静止,根因是正式 layer 的 Composite 仍输出旧 validation stripe/anchors,Trail RGB 只影响 alpha。已以 GLSL source 为唯一输入重新生成 OpenGL header;正式 display mode 现在输出当前 Trail RGB,legacy validation display 保留原 stripe/anchor/world-wrap 分支。Linux 精确编译、shader generator 幂等、format 与 diff 已通过;Android 实机验收仍由用户执行,40-01 保持 `WAITING_MANUAL_VALIDATION`。 +正式 Source/Layer/RenderLayer 已不依赖 source-less `particle-validation` Layer;历史 validation Layer 只保留为既有验证工具。Android 首次实际启动的 RasterArray source RenderItem 空指针已在 source 内补为合法 no-op RenderItem,保持全局 RenderSource 契约。随后实机日志发现 ACTIVE/STATIC 每帧重置:RenderOrchestrator 的按 source 可渲染性扫描会短暂调用 `markLayerRenderable(false)`,它不是持久 disabled;粒子层原先却在该回调中 `resetSequence()`,每帧清空跨 render 的 frame/slot/seed 状态。已删除该错误 reset 边界,真实重建仍限于 field、viewport、context 与 layer removal;新增状态转换及前 3 帧采样日志。正式 Composite 已改为显示 Trail RGB,legacy validation display 保留原 stripe/anchor/world-wrap 分支。最新实机仍不可见的根因进一步确认是 State seed:MapLibre RenderTarget 新 target 清为 opaque black,原 shader 错把 `previous.a` 当 unseeded 标记,令所有 State texel 从 `(0,0)` 开始。现以 `ParticleGpuResources::stateSeeded` 为权威,使用 `u_mask.w` 显式指示 `SEED_STATE`;ACTIVE 与 STATIC zero-step bootstrap 都从各自 `v_uv` 分散初始化,后续 frame 继续 State read。Linux 精确编译、shader generator 幂等、GLSL/header parity、format 与 diff 已通过;Android 实机验收仍由用户执行,40-01 保持 `WAITING_MANUAL_VALIDATION`。 diff --git a/docs/报告/40_01_Android正式粒子StateSeed修复_Codex完成报告_v1.md b/docs/报告/40_01_Android正式粒子StateSeed修复_Codex完成报告_v1.md new file mode 100644 index 000000000..93281ad69 --- /dev/null +++ b/docs/报告/40_01_Android正式粒子StateSeed修复_Codex完成报告_v1.md @@ -0,0 +1,37 @@ +# 40-01 Android 正式粒子 State Seed 修复 Codex 完成报告 v1 + +## 1. 结论 + +已修复正式粒子的 State 初始化契约。40-01 保持 `WAITING_MANUAL_VALIDATION`;未运行 Android Gradle、ADB 或实体机。 + +## 2. 根因 + +`RenderTarget::render()` 按 MapLibre 既有 contract 将每个 offscreen pass clear 为 `(0,0,0,1)`。State shader 原本把 `previous.a < 0.5` 当作“尚未 seed”,但新的 State A/B target 因此是 opaque black,条件恒为 false。所有 16×16 State texel 都从 `(0,0)` 开始;uniform-east 仅移动 x,四个 deterministic marker 聚在 canonical world 顶缘,当前视口通常不可见。 + +没有修改 `src/mbgl/renderer/render_target.cpp`。 + +## 3. Seed contract + +- `ParticleGpuResources::stateSeeded` 是唯一生命周期权威。 +- State pass 使用已有 UBO 的 `u_mask.w`:`1.0 = SEED_STATE`,`0.0 = CONTINUE_STATE`。 +- shader 在 `SEED_STATE` 时让每个 texel 从自身 `v_uv` 初始化,再采样 field;在 `CONTINUE_STATE` 时从 State read texture 的 `previous.rg` 继续。`field.a` 仍仅表示 Field/NoData validity。 +- Composite commit 后既有 `commit()` 将 `stateSeeded=true`。新 resource、context recreation 和 fixture/field reset 令其恢复 false;普通 ACTIVE、Camera、STATIC 与 transient `markLayerRenderable(false)` 不会重 seed。 +- STATIC zero-step bootstrap 使用相同 seed flag,但 `simulationStep=0`,因此建立分散的 deterministic state 而不推进 simulation。 + +Trail/Composite 的 State write → Trail write wiring、slot swap、hazard=SAFE、formal display、legacy validation display、fixture、mask、world wrap、antimeridian、rotation resource lifecycle 均未改变。 + +## 4. 诊断与静态验证 + +- 新增低频 `ParticleRender: stateSeed=INITIALIZE|CONTINUE`,含 generation、State A/B 和 fixture。 +- shader source:`shaders/particle_gpu_validation.fragment.glsl`;由 `node shaders/generate_shader_code.mjs` 生成 OpenGL header,未手改 generated header。 +- generator 二次运行幂等:PASS;GLSL source 与 generated fragment parity:PASS。 +- Linux OpenGL `render_particle_validation_layer.cpp.o`、相关 `test/style/source.test.cpp.o` 精确编译:PASS。 +- `clang-format --dry-run --Werror` 与 `git diff --check`:PASS。 + +## 5. 下一步 + +用户下一次只验收 GLOBAL + ACTIVE + `uniform-east`:四个 deterministic marker/trail 应分散在 world 内,且连续向 east 移动。通过前不声明 40-01 PASS。 + +## 6. Git 收尾 + +最终代码 commit、push 与 `origin/particle/main` HEAD 在交付收尾后回填。 diff --git a/include/mbgl/shaders/gl/particle_gpu_validation.hpp b/include/mbgl/shaders/gl/particle_gpu_validation.hpp index c4dd0541c..90063fe67 100644 --- a/include/mbgl/shaders/gl/particle_gpu_validation.hpp +++ b/include/mbgl/shaders/gl/particle_gpu_validation.hpp @@ -54,8 +54,9 @@ void main() { // 40-01: State A/B samples the uploaded OfflineFixture Particle Field. // RG stores field-normalized position, B stores age, and the Field // texture encodes canonical U-east/V-north at that geographic sample. - vec2 position = previous.rg; - if (previous.a < 0.5) position = v_uv; + // u_mask.w is the explicit State lifecycle flag: new RenderTargets are + // cleared opaque by MapLibre, so sampled alpha cannot identify a seed. + vec2 position = u_mask.w > 0.5 ? v_uv : previous.rg; vec4 field = texture(u_field_texture, position); vec2 velocity = field.rg * 2.0 - 1.0; if (field.a < 0.5) { diff --git a/shaders/particle_gpu_validation.fragment.glsl b/shaders/particle_gpu_validation.fragment.glsl index 6bb81a5c4..dd6cc8091 100644 --- a/shaders/particle_gpu_validation.fragment.glsl +++ b/shaders/particle_gpu_validation.fragment.glsl @@ -20,8 +20,9 @@ void main() { // 40-01: State A/B samples the uploaded OfflineFixture Particle Field. // RG stores field-normalized position, B stores age, and the Field // texture encodes canonical U-east/V-north at that geographic sample. - vec2 position = previous.rg; - if (previous.a < 0.5) position = v_uv; + // u_mask.w is the explicit State lifecycle flag: new RenderTargets are + // cleared opaque by MapLibre, so sampled alpha cannot identify a seed. + vec2 position = u_mask.w > 0.5 ? v_uv : previous.rg; vec4 field = texture(u_field_texture, position); vec2 velocity = field.rg * 2.0 - 1.0; if (field.a < 0.5) { diff --git a/src/mbgl/renderer/layers/render_particle_validation_layer.cpp b/src/mbgl/renderer/layers/render_particle_validation_layer.cpp index 2594fbce8..9f2475574 100644 --- a/src/mbgl/renderer/layers/render_particle_validation_layer.cpp +++ b/src/mbgl/renderer/layers/render_particle_validation_layer.cpp @@ -270,13 +270,16 @@ public: const float frame = simulationStep; const float markerX = 0.5f + 0.32f * std::sin(frame * 0.071f); const float markerY = 0.5f + 0.32f * std::cos(frame * 0.053f); + // RenderTarget clear is opaque black. State initialization must therefore + // be an explicit lifecycle signal, never inferred from sampled alpha. + const bool seedState = pass == ValidationPass::State && !resources->stateSeeded; const shaders::ParticleGpuValidationUBO ubo = { .pass = {static_cast(pass), frame, maskMode == 0 ? 0.0f : 1.0f, static_cast(maskMode)}, .marker = {markerX, markerY, 0.035f, 0.965f}, .mask = {static_cast(resources->maskGeneration), static_cast(worldWrap), resources->formalDisplay ? 1.0f : 0.0f, - 0.0f}, + seedState ? 1.0f : 0.0f}, .matrix = util::cast(parameters.matrixForTile({worldWrap, {0, 0, 0}})), }; drawable.mutableUniformBuffers().createOrUpdate(shaders::idParticleGpuValidationUBO, &ubo, parameters.context); @@ -381,6 +384,13 @@ public: " frame=" + std::to_string(resources->committedFrame + 1) + " read=" + slotName(readSlot) + " write=" + slotName(writeSlot) + " distinct=true"); } + if (pass == ValidationPass::State && + (!resources->stateSeeded || resources->committedFrame < 3 || (resources->committedFrame + 1) % 60 == 0)) { + Log::Info(Event::Render, + "ParticleRender: stateSeed=" + std::string(seedState ? "INITIALIZE" : "CONTINUE") + + " generation=" + std::to_string(resources->generation) + " state=" + slotName(readSlot) + + "->" + slotName(writeSlot) + " fixture=" + resources->field.fixtureID); + } } private: diff --git a/粒子开发记录.md b/粒子开发记录.md index 2925072a3..b5ea8e3ee 100644 --- a/粒子开发记录.md +++ b/粒子开发记录.md @@ -197,3 +197,11 @@ - `ParticleRender` 新增一次性 displayMode 日志;既有低频 schedule 日志补充 `trailStateSource`、`compositeTrailSource` 和 displayMode,可证明 State write → Trail write → Composite Trail write 的数据关系,而不每帧刷屏。 - `shaders/particle_gpu_validation.fragment.glsl` 为唯一 shader source,使用 `node shaders/generate_shader_code.mjs` 正常生成 OpenGL header;二次生成哈希一致。生成器同时规范化 `src/mbgl/shaders/shader_source.cpp`,该生成物一并保留以保证生成后工作树稳定。 - Linux OpenGL 受影响 RenderLayer object、相关 `source.test.cpp.o` 均精确编译;C++ format、diff check 通过。按任务边界未运行 Android Gradle、ADB 或真机;40-01 继续为 `WAITING_MANUAL_VALIDATION`,下一轮只验收 GLOBAL + ACTIVE + uniform-east 的可见 eastward 运动。 + +## 2026-09-01 — 40-01 Formal RasterParticle 显式 State Seed 修复 + +- 实机日志确认 formal display、State/Trail/Composite slot wiring、simulation frame 与 hazard 均正确,但正式粒子仍不可见。`RenderTarget::render()` 的既有全局 clear 是 opaque black `(0,0,0,1)`;未修改该 MapLibre contract。 +- 原 State shader 用 `previous.a < 0.5` 判断是否第一次 seed,但新 State target alpha 为 1,导致全部 16×16 texel 从 `(0,0)` 而非各自 `v_uv` 开始。uniform-east 只推动 x,marker 聚集在 canonical world 顶缘、常在当前视口外。 +- `ParticleGpuResources::stateSeeded` 现在是唯一权威。State pass 把 `!stateSeeded` 通过现有 UBO 的 `u_mask.w` 传入;shader 仅以该 flag 选择 `v_uv` 或 `previous.rg`,不再以 texture alpha 推断生命周期。Field `alpha` 仍仅表示 NoData validity。 +- ACTIVE 第一次 State pass 与 STATIC zero-step bootstrap 都对每个 texel 进行 `v_uv` seed;Composite commit 后 `stateSeeded=true`,之后继续 read texture。新 resources、context recreation 和 fixture/field `resetSequence()` 保持重新 seed;普通 ACTIVE、Camera、STATIC 与 transient source renderability 不会 seed。 +- 新增低频 `ParticleRender: stateSeed=INITIALIZE|CONTINUE` 日志,含 generation、State A/B 与 fixture。shader source/header generator parity、生成器幂等、Linux Core 与 Source test object 编译、format 和 diff 均通过;未运行 Android Gradle/ADB/真机,状态仍为 `WAITING_MANUAL_VALIDATION`。