From 7929052c5828ca86e97a830b75168d34ea516c19 Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Sat, 5 Sep 2026 10:36:07 +0800 Subject: [PATCH] fix: stagger particle lifecycle respawn --- ...EP-055_Android真实WeatherV2风场综合可视化_v5.md | 16 ++++++++++++++++ .../mbgl/shaders/gl/particle_gpu_validation.hpp | 14 ++++++++++---- shaders/particle_gpu_validation.fragment.glsl | 14 ++++++++++---- .../layers/render_particle_validation_layer.cpp | 11 +++++++---- 粒子开发记录.md | 3 +++ 5 files changed, 46 insertions(+), 12 deletions(-) create mode 100644 docs/50_Step记录/STEP-055_Android真实WeatherV2风场综合可视化_v5.md diff --git a/docs/50_Step记录/STEP-055_Android真实WeatherV2风场综合可视化_v5.md b/docs/50_Step记录/STEP-055_Android真实WeatherV2风场综合可视化_v5.md new file mode 100644 index 000000000..3f11d43a3 --- /dev/null +++ b/docs/50_Step记录/STEP-055_Android真实WeatherV2风场综合可视化_v5.md @@ -0,0 +1,16 @@ +# STEP-055 Android 真实 WeatherV2 风场综合可视化 v5 + +状态:**IMPLEMENTED / WAITING_ANDROID_VALIDATION** + +Starting HEAD:`c7acefcb21840bc1db736a5f061ba39b18ba2c36` + +## 生命周期错峰小修复 + +- `ParticleLifecycleSeconds` 从 `3600` 改为 `14400` simulation seconds;`VisualSimulationSecondsPerRealSecond=1200` 未变,因此基础生命周期为 12 real seconds。 +- 首次 State seed 的 age 改为 `particleHash(v_uv + vec2(71.19, 29.47)) * maxAge`,不再统一为零。 +- 每个 slot 的 `maxAge = base * mix(0.75, 1.25, particleHash(v_uv + vec2(17.31, 83.17)))`;普通 respawn 仍重置 age=0,initial seed 保留错峰 age。 +- `ParticleLifecycle` 日志使用 `configuredSlots`,并标示 `initialAge=HASH_STAGGERED`、`maxAge=HASH_JITTERED_0.75_1.25` 与 `readback=disabled`。 + +State Float、Trail UByte、previous→current segment、view-space Trail、time-based fade、canonical Field、Timeline、Runtime Window、WeatherColorLayer 和 REAL WEATHER UI 均未修改。 + +shader generator、Linux OpenGL 精确编译、既有非 Jest style layer 精确编译和 `git diff --check` 均通过;未执行 Android 或 Jest。 diff --git a/include/mbgl/shaders/gl/particle_gpu_validation.hpp b/include/mbgl/shaders/gl/particle_gpu_validation.hpp index 1e29ac78c..0d6267108 100644 --- a/include/mbgl/shaders/gl/particle_gpu_validation.hpp +++ b/include/mbgl/shaders/gl/particle_gpu_validation.hpp @@ -196,12 +196,16 @@ void main() { // Formal particles use GPU-resident lifecycle state. Each slot keeps // its age in B; A=0 marks this frame as a respawn so Trail does not // connect the retired position to the new randomized one. - bool respawn = u_mask.w > 0.5; - float age = respawn ? 0.0 : previous.b + u_pass.y; + bool initialSeed = u_mask.w > 0.5; + float lifecycleHash = particleHash(v_uv + vec2(17.31, 83.17)); + float maxAge = u_marker.z * mix(0.75, 1.25, lifecycleHash); + float initialAgeHash = particleHash(v_uv + vec2(71.19, 29.47)); + bool respawn = initialSeed; + float age = initialSeed ? initialAgeHash * maxAge : previous.b + u_pass.y; vec2 position = previous.rg; vec2 velocity; bool valid = !respawn && fieldVelocityAt(position, velocity); - if (!valid || age >= u_marker.z) { + if (!valid || age >= maxAge) { respawn = true; } @@ -235,7 +239,9 @@ void main() { // frame; alpha still prevents an invalid visible segment. position = respawnSeed(v_uv, previous.rg, 3.0); } - age = 0.0; + if (!initialSeed) { + age = 0.0; + } } fragColor = vec4(position, age, respawn ? 0.0 : 1.0); return; diff --git a/shaders/particle_gpu_validation.fragment.glsl b/shaders/particle_gpu_validation.fragment.glsl index 695a033d7..8474156d7 100644 --- a/shaders/particle_gpu_validation.fragment.glsl +++ b/shaders/particle_gpu_validation.fragment.glsl @@ -100,12 +100,16 @@ void main() { // Formal particles use GPU-resident lifecycle state. Each slot keeps // its age in B; A=0 marks this frame as a respawn so Trail does not // connect the retired position to the new randomized one. - bool respawn = u_mask.w > 0.5; - float age = respawn ? 0.0 : previous.b + u_pass.y; + bool initialSeed = u_mask.w > 0.5; + float lifecycleHash = particleHash(v_uv + vec2(17.31, 83.17)); + float maxAge = u_marker.z * mix(0.75, 1.25, lifecycleHash); + float initialAgeHash = particleHash(v_uv + vec2(71.19, 29.47)); + bool respawn = initialSeed; + float age = initialSeed ? initialAgeHash * maxAge : previous.b + u_pass.y; vec2 position = previous.rg; vec2 velocity; bool valid = !respawn && fieldVelocityAt(position, velocity); - if (!valid || age >= u_marker.z) { + if (!valid || age >= maxAge) { respawn = true; } @@ -139,7 +143,9 @@ void main() { // frame; alpha still prevents an invalid visible segment. position = respawnSeed(v_uv, previous.rg, 3.0); } - age = 0.0; + if (!initialSeed) { + age = 0.0; + } } fragColor = vec4(position, age, respawn ? 0.0 : 1.0); return; diff --git a/src/mbgl/renderer/layers/render_particle_validation_layer.cpp b/src/mbgl/renderer/layers/render_particle_validation_layer.cpp index d1973f145..5ccc6e7c3 100644 --- a/src/mbgl/renderer/layers/render_particle_validation_layer.cpp +++ b/src/mbgl/renderer/layers/render_particle_validation_layer.cpp @@ -53,7 +53,7 @@ constexpr float EarthCircumferenceMeters = 40075016.68557849f; // moves about 100 m (roughly 0.65 px at z9), while a 30 m/s sample remains // below a four-pixel segment at that zoom. constexpr float VisualSimulationSecondsPerRealSecond = 1200.0f; -constexpr float ParticleLifecycleSeconds = 3600.0f; +constexpr float ParticleLifecycleSeconds = 14400.0f; constexpr float TrailFadeReferenceFramesPerSecond = 60.0f; std::atomic_uint64_t nextGpuGeneration{0}; @@ -463,9 +463,12 @@ public: if (resources->formalDisplay && now - resources->lastLifecycleLog >= std::chrono::seconds(1)) { resources->lastLifecycleLog = now; Log::Info(Event::Render, - "ParticleLifecycle: activeSlots=" + std::to_string(resources->particleCount) + - " respawn=GPU_HASH_ON_INVALID_OR_AGE_EXPIRED lifecycleSeconds=" + - std::to_string(ParticleLifecycleSeconds) + + "ParticleLifecycle: configuredSlots=" + std::to_string(resources->particleCount) + + " initialAge=HASH_STAGGERED maxAge=HASH_JITTERED_0.75_1.25 " + "lifecycleBaseSimulationSeconds=" + std::to_string(ParticleLifecycleSeconds) + + " lifecycleBaseRealSeconds=" + + std::to_string(ParticleLifecycleSeconds / VisualSimulationSecondsPerRealSecond) + + " respawn=GPU_HASH_ON_INVALID_OR_AGE_EXPIRED" " fieldId=" + resources->field.fieldID + " simulationFrame=" + std::to_string(resources->committedFrame) + " readback=disabled"); } diff --git a/粒子开发记录.md b/粒子开发记录.md index e251fa0ca..8845d209f 100644 --- a/粒子开发记录.md +++ b/粒子开发记录.md @@ -429,3 +429,6 @@ ## STEP-055 生命周期、真实轨迹与 view-space Trail 修复 真实 Weather 路径改为 GPU age/哈希 respawn,StateRead→StateWrite 真实位移 segment,且 Trail 仅在当前 camera view 内累积。相机变化只重建 Trail;`trailFade` 改为 wall-time 指数衰减。visual clock 调整为 1200 simulation seconds/real second。canonical、Timeline、Runtime、WeatherColorLayer 未改,仍待 Android 综合验收。 +## STEP-055 生命周期错峰小修复 + +粒子基础生命周期调整为 14400 simulation seconds(12 real seconds),首次 seed 与 maxAge 都由稳定 hash 错峰,消除同步生命周期到期。日志改用 configuredSlots 并说明无 GPU readback;其他粒子视觉与数据链语义未改。