fix: use float particle state targets
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:
30
docs/50_Step记录/STEP-055_Android真实WeatherV2风场综合可视化_v2.md
Normal file
30
docs/50_Step记录/STEP-055_Android真实WeatherV2风场综合可视化_v2.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# STEP-055 Android 真实 WeatherV2 风场综合可视化 v2
|
||||||
|
|
||||||
|
状态:**IMPLEMENTED / WAITING_ANDROID_VALIDATION**
|
||||||
|
|
||||||
|
Starting HEAD:`37c9aa31da1d2b3495c8802e6fd2f6b999562b06`
|
||||||
|
|
||||||
|
## 本轮问题与根因
|
||||||
|
|
||||||
|
Android 已确认真实 WeatherV2 Field、Host submission、WeatherColorLayer、Timeline State A/B 调度、非零 U/V 与非零 m/s→Web Mercator 位移均存在,但 Timeline Pause 下粒子仍肉眼静止。
|
||||||
|
|
||||||
|
根因是 Particle State A/B 与 Trail A/B 共用 `createValidationTarget()` 的 `UnsignedByte` target。State RG 保存的是 Web Mercator world position;RGBA8 的一个量化步长约为 `1/255=0.00392 world`,远大于日志中每个真实 Weather simulation step 的 `0.000003–0.000008 world`,写回后不能累积有效位移。
|
||||||
|
|
||||||
|
## 修复
|
||||||
|
|
||||||
|
- State A/B target 改为 `TextureChannelDataType::Float`,包括初次创建、context/resource recreation 和 particleCount 变更的 replacement targets。
|
||||||
|
- Trail A/B 保持 `TextureChannelDataType::UnsignedByte`,继续是 viewport-sized 颜色/透明度 target;没有升级 WeatherColorLayer 或任意无关 full-screen target。
|
||||||
|
- `RenderTarget` 新增 `isRenderable()`,在 Particle target 创建时立即驱动现有 `OffscreenTexture → GL texture → framebuffer` 创建/检查。Android OpenGL 已有映射链为 `Float → RGBA32F + GL_FLOAT → glFramebufferTexture2D → checkFramebuffer()`;无需新增 backend 抽象。
|
||||||
|
- Float State FBO 不能创建时不回退到 UByte:`ParticleStateTarget: event=CREATE_FAILED ... dataType=Float` 后停止该 State resource 创建;count change 同样明确返回 `STATE_RESOURCE_CREATE_FAILED dataType=Float`。
|
||||||
|
- 每个成功 target 输出 `CREATE_SUCCESS`,Trail pair 就绪时另输出一次 `ParticleStateTarget: stateAType=Float stateBType=Float trailAType=UnsignedByte trailBType=UnsignedByte`。
|
||||||
|
- Shader 注释校正为 State RG 保存 `Web Mercator world position`,不是 field-normalized position。
|
||||||
|
|
||||||
|
## 冻结边界
|
||||||
|
|
||||||
|
未修改 canonical Field、STEP-054 assets、Host Submission Contract、Timeline、Playback、Runtime Field Window、WeatherColorLayer、U/V=m/s 或 `visualSpeedScale=6000`。继续保持 Timeline Pause 只冻结 forecast 时间、Particle simulation 在当前 Field 中运行;Window reuse、NEW_T1-only、MIX property-only、normal boundary `simulationRebuild=false` / `trailCleared=false` 不变。
|
||||||
|
|
||||||
|
## 验证与待验收
|
||||||
|
|
||||||
|
已执行 shader 生成、Linux OpenGL 受影响 Core 精确编译、相关测试源精确编译和 `git diff --check`;未执行 Android Gradle/APK/ADB/App/设备或 Jest。
|
||||||
|
|
||||||
|
下一轮人工验收的第一优先项是:进入 REAL WEATHER、保持 Timeline Pause,确认 Float State 类型日志后粒子中心位置连续运动。若仍完全不动,停止调节 6000,改以单独任务调查 GPU State readback / State Update→Draw 链。
|
||||||
35
docs/60_测试/STEP-055_Android真实WeatherV2风场综合可视化_人工验收指令_v3.md
Normal file
35
docs/60_测试/STEP-055_Android真实WeatherV2风场综合可视化_人工验收指令_v3.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# STEP-055 Android 真实 WeatherV2 风场综合可视化人工验收 v3
|
||||||
|
|
||||||
|
状态:等待用户手工 Android 验收。Codex 不执行 Android Gradle、APK、ADB、App 启动或设备测试。
|
||||||
|
|
||||||
|
## Windows 11 构建与日志
|
||||||
|
|
||||||
|
在 Windows 11 项目目录手工执行:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
./gradlew :MapLibreAndroidTestApp:assembleOpenGLDebug --console=plain
|
||||||
|
adb -s 192.168.1.146 install -r <APK_PATH>
|
||||||
|
adb -s 192.168.1.146 logcat -c
|
||||||
|
adb -s 192.168.1.146 logcat | findstr /I "ParticleStateTarget ParticleMotion REAL_WEATHER ParticleTimeline FIELD_REUSE FIELD_UPLOAD simulationRebuild trailCleared"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 第一优先:State 精度
|
||||||
|
|
||||||
|
1. 进入 `Particle Core Layer Validation → REAL WEATHER`,确认 GSI、真实色场、HUD 默认 `P 2048 · W 0.75 · L 0.0040 · O 0.75 · T 0.55 · F 0.88`。
|
||||||
|
2. **不要点击 PLAY,或在 `TIME → PAUSE` 后观察。** Particle position 必须在固定真实风场中连续、明显移动;Timeline cursor/MIX 冻结不应暂停 simulation。
|
||||||
|
3. 日志必须有:
|
||||||
|
|
||||||
|
```text
|
||||||
|
ParticleStateTarget: stateAType=Float stateBType=Float trailAType=UnsignedByte trailBType=UnsignedByte
|
||||||
|
ParticleStateTarget: event=CREATE_SUCCESS kind=STATE dataType=Float
|
||||||
|
ParticleMotion: ... sampleU=... sampleV=... dt=... visualSpeedScale=6000 estimatedStateDeltaWorld=...
|
||||||
|
```
|
||||||
|
|
||||||
|
4. 若有 `CREATE_FAILED kind=STATE dataType=Float`,记录完整错误;Particle State 应 fail-closed,不得静默以 UByte 继续。
|
||||||
|
5. 若 Float 日志正确但粒子仍完全不动,**停止调节 visualSpeedScale=6000**,保存日志并进入后续 GPU State texel readback / State Update→Draw 定向任务。
|
||||||
|
|
||||||
|
## 继续综合验收
|
||||||
|
|
||||||
|
在第一优先项通过后,沿用 v2 的 REAL WEATHER tuning/RESET、PLAY T0→T3、两次 Window Boundary、Duplicate/Invalid、Pan/Zoom/Rotate/Activity Rotation 检查。正常边界仍应为 OLD_T1→NEW_T0 reuse、only NEW_T1 upload、`simulationRebuild=false`、`trailCleared=false`。
|
||||||
|
|
||||||
|
本轮没有 Weather Service、React Native、Jest 或 canonical 数据变化。
|
||||||
39
docs/报告/STEP-055_Android真实WeatherV2风场综合可视化_Codex完成报告_v3.md
Normal file
39
docs/报告/STEP-055_Android真实WeatherV2风场综合可视化_Codex完成报告_v3.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# STEP-055 真实 WeatherV2 风场 Particle State 纹理精度修复 Codex 完成报告 v3
|
||||||
|
|
||||||
|
## 基线、范围与根因
|
||||||
|
|
||||||
|
- Starting HEAD:`37c9aa31da1d2b3495c8802e6fd2f6b999562b06`
|
||||||
|
- 分支:`particle/main`
|
||||||
|
- 状态:**IMPLEMENTED / WAITING_ANDROID_VALIDATION**
|
||||||
|
|
||||||
|
真实 Weather State shader 已计算非零 m/s→Web Mercator 位移且 State A/B ping-pong 持续,但 State RG world coordinate 写回被共用的 RGBA8/UnsignedByte target 量化。`1/255≈0.00392 world` 大于 Android 已观测的 `0.000003–0.000008 world` 单步位移,故每步无法积累,是“simulation 在跑但粒子静止”的确定性根因。
|
||||||
|
|
||||||
|
## 修改
|
||||||
|
|
||||||
|
- `render_particle_validation_layer.cpp`:State A/B 初始创建与 particleCount replacement 均为 `TextureChannelDataType::Float`;Trail A/B 仍为 `UnsignedByte`。创建函数显式接收 data type/kind,并在 FBO 创建失败时 log error、拒绝 target,无 UByte fallback。
|
||||||
|
- `render_target.hpp/.cpp`:新增最小 `RenderTarget::isRenderable()` forwarding,使 target 创建即可验证当前后端实际 FBO。
|
||||||
|
- `particle_gpu_validation.fragment.glsl`:只修正 RG 为 Web Mercator world position 的注释;未改 m/s conversion 或 `visualSpeedScale=6000`。
|
||||||
|
- 生成的 OpenGL shader header 同步更新。
|
||||||
|
|
||||||
|
Android OpenGL 既有实现已经支持 Float:`gl::Context::createRenderTarget → RenderTarget → gl::OffscreenTexture → Texture2D(RGBA, Float) → GL_RGBA32F/GL_FLOAT → glFramebufferTexture2D → checkFramebuffer()`。因此未修改 Android/OpenGL backend;若设备 FBO 不完整,现有 `isRenderable()` 返回失败,Particle target 记录具体 Float failure 并 fail-closed。
|
||||||
|
|
||||||
|
运行时日志包括每个成功 `CREATE_SUCCESS`,以及 State/Trail pair summary:`stateAType=Float stateBType=Float trailAType=UnsignedByte trailBType=UnsignedByte`。这基于已实际完成 backend FBO validation,而非仅打印请求值。
|
||||||
|
|
||||||
|
## 架构保护
|
||||||
|
|
||||||
|
未修改 canonical Field、U/V 语义、STEP-054 assets、Host submission、Timeline、Runtime Field Window、WeatherColorLayer 或 6000 visual speed scale。没有新增 GPU readback、网络、Provider、React Native、Jest 或 Android UI 变化。
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
|
||||||
|
- `node shaders/generate_shader_code.mjs`:PASS;generated OpenGL header 与 fragment source 同步。
|
||||||
|
- Linux OpenGL CMake 精确编译:`render_target.cpp`、`render_particle_validation_layer.cpp`、`gl/offscreen_texture.cpp`、`gl/texture2d.cpp`:PASS。
|
||||||
|
- 现有非 Jest `test/style/style_layer.test.cpp`:以相同 OpenGL CMake 参数精确编译 PASS;本轮没有适合脱离实际 GL context 的 Float FBO 单元 runner,因此 FBO runtime 完整性保留 Android 实机检查。
|
||||||
|
- `git diff --check`:PASS(最终提交前将再次执行)。
|
||||||
|
- Android Gradle/APK/ADB/App/设备:未执行。
|
||||||
|
- Jest:未执行,未修改。
|
||||||
|
|
||||||
|
人工验收说明:[v3](../60_测试/STEP-055_Android真实WeatherV2风场综合可视化_人工验收指令_v3.md)。第一门槛是 Timeline Pause 下 Float State 粒子连续运动;若仍不动,不调 6000,转入后续 GPU State 读回诊断。
|
||||||
|
|
||||||
|
## Git
|
||||||
|
|
||||||
|
本报告将在功能提交及 push 后记录实际 commit、push result 与 Final HEAD。用户既有未跟踪文件未触碰、未 stage。
|
||||||
@@ -34,6 +34,9 @@ public:
|
|||||||
/// Get the render target texture
|
/// Get the render target texture
|
||||||
const gfx::Texture2DPtr& getTexture();
|
const gfx::Texture2DPtr& getTexture();
|
||||||
|
|
||||||
|
/// Create and validate the backing offscreen framebuffer.
|
||||||
|
bool isRenderable();
|
||||||
|
|
||||||
/// @brief Add a layer group to the render target
|
/// @brief Add a layer group to the render target
|
||||||
/// @param replace Flag to replace if exists
|
/// @param replace Flag to replace if exists
|
||||||
/// @return whether added
|
/// @return whether added
|
||||||
|
|||||||
@@ -155,8 +155,8 @@ void main() {
|
|||||||
vec4 previous = texture(u_previous, v_uv);
|
vec4 previous = texture(u_previous, v_uv);
|
||||||
|
|
||||||
if (mode < 0.5) {
|
if (mode < 0.5) {
|
||||||
// 40-01: State A/B samples the uploaded OfflineFixture Particle Field.
|
// State A/B samples the uploaded canonical Particle Field.
|
||||||
// RG stores field-normalized position, B stores age, and the Field
|
// RG stores Web Mercator world position, B stores age, and the Field
|
||||||
// texture encodes canonical U-east/V-north at that geographic sample.
|
// texture encodes canonical U-east/V-north at that geographic sample.
|
||||||
// u_mask.w is the explicit State lifecycle flag: new RenderTargets are
|
// u_mask.w is the explicit State lifecycle flag: new RenderTargets are
|
||||||
// cleared opaque by MapLibre, so sampled alpha cannot identify a seed.
|
// cleared opaque by MapLibre, so sampled alpha cannot identify a seed.
|
||||||
|
|||||||
@@ -70,8 +70,8 @@ void main() {
|
|||||||
vec4 previous = texture(u_previous, v_uv);
|
vec4 previous = texture(u_previous, v_uv);
|
||||||
|
|
||||||
if (mode < 0.5) {
|
if (mode < 0.5) {
|
||||||
// 40-01: State A/B samples the uploaded OfflineFixture Particle Field.
|
// State A/B samples the uploaded canonical Particle Field.
|
||||||
// RG stores field-normalized position, B stores age, and the Field
|
// RG stores Web Mercator world position, B stores age, and the Field
|
||||||
// texture encodes canonical U-east/V-north at that geographic sample.
|
// texture encodes canonical U-east/V-north at that geographic sample.
|
||||||
// u_mask.w is the explicit State lifecycle flag: new RenderTargets are
|
// u_mask.w is the explicit State lifecycle flag: new RenderTargets are
|
||||||
// cleared opaque by MapLibre, so sampled alpha cannot identify a seed.
|
// cleared opaque by MapLibre, so sampled alpha cannot identify a seed.
|
||||||
|
|||||||
@@ -105,6 +105,18 @@ const char* slotName(const uint8_t slot) {
|
|||||||
return slot == 0 ? "A" : "B";
|
return slot == 0 ? "A" : "B";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char* textureDataTypeName(const gfx::TextureChannelDataType type) {
|
||||||
|
switch (type) {
|
||||||
|
case gfx::TextureChannelDataType::UnsignedByte:
|
||||||
|
return "UnsignedByte";
|
||||||
|
case gfx::TextureChannelDataType::HalfFloat:
|
||||||
|
return "HalfFloat";
|
||||||
|
case gfx::TextureChannelDataType::Float:
|
||||||
|
return "Float";
|
||||||
|
}
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
Size validViewportSize(const Size size) {
|
Size validViewportSize(const Size size) {
|
||||||
return {std::max<uint32_t>(1, size.width), std::max<uint32_t>(1, size.height)};
|
return {std::max<uint32_t>(1, size.width), std::max<uint32_t>(1, size.height)};
|
||||||
}
|
}
|
||||||
@@ -601,17 +613,29 @@ private:
|
|||||||
RenderTargetPtr createValidationTarget(gfx::Context& context,
|
RenderTargetPtr createValidationTarget(gfx::Context& context,
|
||||||
const Size size,
|
const Size size,
|
||||||
const std::string& name,
|
const std::string& name,
|
||||||
const gfx::TextureFilterType filter) {
|
const gfx::TextureFilterType filter,
|
||||||
auto target = context.createRenderTarget(size, gfx::TextureChannelDataType::UnsignedByte);
|
const gfx::TextureChannelDataType type,
|
||||||
if (!target || !target->getTexture()) {
|
const char* targetKind) {
|
||||||
|
auto target = context.createRenderTarget(size, type);
|
||||||
|
if (!target || !target->isRenderable() || !target->getTexture()) {
|
||||||
|
Log::Error(Event::Render,
|
||||||
|
"ParticleStateTarget: event=CREATE_FAILED kind=" + std::string(targetKind) +
|
||||||
|
" dataType=" + textureDataTypeName(type) + " name=" + name);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
target->getTexture()->setSamplerConfiguration(
|
target->getTexture()->setSamplerConfiguration(
|
||||||
{.filter = filter, .wrapU = gfx::TextureWrapType::Clamp, .wrapV = gfx::TextureWrapType::Clamp});
|
{.filter = filter, .wrapU = gfx::TextureWrapType::Clamp, .wrapV = gfx::TextureWrapType::Clamp});
|
||||||
auto group = context.createLayerGroup(0, 1, name);
|
auto group = context.createLayerGroup(0, 1, name);
|
||||||
if (!group || !target->addLayerGroup(std::move(group), true)) {
|
if (!group || !target->addLayerGroup(std::move(group), true)) {
|
||||||
|
Log::Error(Event::Render,
|
||||||
|
"ParticleStateTarget: event=CREATE_FAILED kind=" + std::string(targetKind) +
|
||||||
|
" dataType=" + textureDataTypeName(type) + " name=" + name + " reason=LAYER_GROUP");
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
Log::Info(Event::Render,
|
||||||
|
"ParticleStateTarget: event=CREATE_SUCCESS kind=" + std::string(targetKind) +
|
||||||
|
" dataType=" + textureDataTypeName(type) + " name=" + name + " size=" +
|
||||||
|
std::to_string(size.width) + "x" + std::to_string(size.height));
|
||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -927,10 +951,16 @@ void RenderRasterParticleLayer::update(gfx::ShaderRegistry& shaders,
|
|||||||
rebuildParticleDrawResources(owned);
|
rebuildParticleDrawResources(owned);
|
||||||
for (uint8_t i = 0; i < 2; ++i) {
|
for (uint8_t i = 0; i < 2; ++i) {
|
||||||
owned->stateTargets[i] = createValidationTarget(
|
owned->stateTargets[i] = createValidationTarget(
|
||||||
context, owned->stateSize, getID() + "-state-" + slotName(i), gfx::TextureFilterType::Nearest);
|
context,
|
||||||
|
owned->stateSize,
|
||||||
|
getID() + "-state-" + slotName(i),
|
||||||
|
gfx::TextureFilterType::Nearest,
|
||||||
|
gfx::TextureChannelDataType::Float,
|
||||||
|
"STATE");
|
||||||
}
|
}
|
||||||
if (!owned->shader || !owned->stateTargets[0] || !owned->stateTargets[1]) {
|
if (!owned->shader || !owned->stateTargets[0] || !owned->stateTargets[1]) {
|
||||||
Log::Error(Event::Render, "ParticleValidation30_02: State resource creation failed");
|
Log::Error(Event::Render,
|
||||||
|
"ParticleStateTarget: event=CREATE_FAILED kind=STATE dataType=Float reason=FRAMEBUFFER_UNAVAILABLE");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
owned->maskFixture = makeCoastalMaskFixture();
|
owned->maskFixture = makeCoastalMaskFixture();
|
||||||
@@ -1059,11 +1089,16 @@ void RenderRasterParticleLayer::update(gfx::ShaderRegistry& shaders,
|
|||||||
std::array<RenderTargetPtr, 2> replacementTargets;
|
std::array<RenderTargetPtr, 2> replacementTargets;
|
||||||
for (uint8_t i = 0; i < 2; ++i) {
|
for (uint8_t i = 0; i < 2; ++i) {
|
||||||
replacementTargets[i] = createValidationTarget(
|
replacementTargets[i] = createValidationTarget(
|
||||||
context, requestedSize, getID() + "-state-" + slotName(i), gfx::TextureFilterType::Nearest);
|
context,
|
||||||
|
requestedSize,
|
||||||
|
getID() + "-state-" + slotName(i),
|
||||||
|
gfx::TextureFilterType::Nearest,
|
||||||
|
gfx::TextureChannelDataType::Float,
|
||||||
|
"STATE");
|
||||||
}
|
}
|
||||||
if (!replacementTargets[0] || !replacementTargets[1]) {
|
if (!replacementTargets[0] || !replacementTargets[1]) {
|
||||||
Log::Error(Event::Render,
|
Log::Error(Event::Render,
|
||||||
"ParticleGPU: event=PARTICLE_COUNT_REJECTED reason=STATE_RESOURCE_CREATE_FAILED "
|
"ParticleGPU: event=PARTICLE_COUNT_REJECTED reason=STATE_RESOURCE_CREATE_FAILED dataType=Float "
|
||||||
"requestedParticleCount=" +
|
"requestedParticleCount=" +
|
||||||
std::to_string(particleCount) +
|
std::to_string(particleCount) +
|
||||||
" effectiveParticleCount=" + std::to_string(resources->particleCount) +
|
" effectiveParticleCount=" + std::to_string(resources->particleCount) +
|
||||||
@@ -1286,7 +1321,12 @@ void RenderRasterParticleLayer::update(gfx::ShaderRegistry& shaders,
|
|||||||
resources->trailTargets = {};
|
resources->trailTargets = {};
|
||||||
for (uint8_t i = 0; i < 2; ++i) {
|
for (uint8_t i = 0; i < 2; ++i) {
|
||||||
resources->trailTargets[i] = createValidationTarget(
|
resources->trailTargets[i] = createValidationTarget(
|
||||||
context, viewport, getID() + "-trail-" + slotName(i), gfx::TextureFilterType::Linear);
|
context,
|
||||||
|
viewport,
|
||||||
|
getID() + "-trail-" + slotName(i),
|
||||||
|
gfx::TextureFilterType::Linear,
|
||||||
|
gfx::TextureChannelDataType::UnsignedByte,
|
||||||
|
"TRAIL");
|
||||||
}
|
}
|
||||||
if (!resources->trailTargets[0] || !resources->trailTargets[1]) {
|
if (!resources->trailTargets[0] || !resources->trailTargets[1]) {
|
||||||
Log::Error(Event::Render, "ParticleGPU: Trail resource creation failed");
|
Log::Error(Event::Render, "ParticleGPU: Trail resource creation failed");
|
||||||
@@ -1295,6 +1335,12 @@ void RenderRasterParticleLayer::update(gfx::ShaderRegistry& shaders,
|
|||||||
resources->trailSize = viewport;
|
resources->trailSize = viewport;
|
||||||
resources->trailRead = 0;
|
resources->trailRead = 0;
|
||||||
resources->trailSeeded = false;
|
resources->trailSeeded = false;
|
||||||
|
Log::Info(Event::Render,
|
||||||
|
"ParticleStateTarget: stateAType=Float stateBType=Float trailAType=UnsignedByte trailBType=UnsignedByte "
|
||||||
|
"stateGrid=" + std::to_string(resources->stateSize.width) + "x" +
|
||||||
|
std::to_string(resources->stateSize.height) + " trailSize=" +
|
||||||
|
std::to_string(viewport.width) + "x" + std::to_string(viewport.height) +
|
||||||
|
" resourceGeneration=" + std::to_string(resources->generation));
|
||||||
Log::Info(Event::Render,
|
Log::Info(Event::Render,
|
||||||
"ParticleGPU: Trail A/B " + std::string(previousSize.isEmpty() ? "create" : "resize") +
|
"ParticleGPU: Trail A/B " + std::string(previousSize.isEmpty() ? "create" : "resize") +
|
||||||
" resourceGeneration=" + std::to_string(resources->generation) +
|
" resourceGeneration=" + std::to_string(resources->generation) +
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ const gfx::Texture2DPtr& RenderTarget::getTexture() {
|
|||||||
return offscreenTexture->getTexture();
|
return offscreenTexture->getTexture();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
bool RenderTarget::isRenderable() {
|
||||||
|
return offscreenTexture && offscreenTexture->isRenderable();
|
||||||
|
}
|
||||||
|
|
||||||
bool RenderTarget::addLayerGroup(LayerGroupBasePtr layerGroup, const bool replace) {
|
bool RenderTarget::addLayerGroup(LayerGroupBasePtr layerGroup, const bool replace) {
|
||||||
const auto index = layerGroup->getLayerIndex();
|
const auto index = layerGroup->getLayerIndex();
|
||||||
const auto result = layerGroupsByLayerIndex.insert(std::make_pair(index, LayerGroupBasePtr{}));
|
const auto result = layerGroupsByLayerIndex.insert(std::make_pair(index, LayerGroupBasePtr{}));
|
||||||
|
|||||||
@@ -417,3 +417,9 @@
|
|||||||
- REAL WEATHER 首次加载采用 P=2048、W=.75、L=.0040、O=.75、T=.55、F=.88,并提供同状态 HUD、低频 `PARTICLE TUNING` 调整与 RESET;不重载 Style/Field/Timeline,只有容量调整会重建粒子 State/draw resources。
|
- REAL WEATHER 首次加载采用 P=2048、W=.75、L=.0040、O=.75、T=.55、F=.88,并提供同状态 HUD、低频 `PARTICLE TUNING` 调整与 RESET;不重载 Style/Field/Timeline,只有容量调整会重建粒子 State/draw resources。
|
||||||
- 将原无单位 `velocity * 0.00004` 位移替换为 canonical m/s 到 Web Mercator world 的 Earth-circumference/cos(latitude) 换算;6000 simulation-seconds/real-second 是独立的 validation visual clock,不污染 U/V、validTime 或 Timeline。新增低频 `ParticleMotion` CPU 估算日志,未做 GPU readback。
|
- 将原无单位 `velocity * 0.00004` 位移替换为 canonical m/s 到 Web Mercator world 的 Earth-circumference/cos(latitude) 换算;6000 simulation-seconds/real-second 是独立的 validation visual clock,不污染 U/V、validTime 或 Timeline。新增低频 `ParticleMotion` CPU 估算日志,未做 GPU readback。
|
||||||
- Linux OpenGL 受影响 Core/style test translation unit 精确编译与 shader generator 通过;Android Gradle/APK/ADB/Jest 未执行。等待用户按人工验收 v2 做一次综合复验。
|
- Linux OpenGL 受影响 Core/style test translation unit 精确编译与 shader generator 通过;Android Gradle/APK/ADB/Jest 未执行。等待用户按人工验收 v2 做一次综合复验。
|
||||||
|
|
||||||
|
## 2026-09-05 — STEP-055 Particle State Float 精度修复(IMPLEMENTED / WAITING_ANDROID_VALIDATION)
|
||||||
|
|
||||||
|
- Android 日志已将“真实 U/V、非零 world displacement、State A/B ping-pong 均存在但粒子静止”收敛为 RGBA8 State 量化:单步 `3e-6–8e-6 world` 小于 UByte `1/255` 的可表示间隔。
|
||||||
|
- State A/B 现在为 Float,Trail A/B 保持 UnsignedByte;初次创建、context recreation 与 particleCount replacement 均通过同一 Float target 路径,FBO 创建失败明确 fail-closed,不静默降级。
|
||||||
|
- 现有 OpenGL `Float → RGBA32F/GL_FLOAT → FBO check` 支持直接复用,未改 backend、canonical/Timeline/Window/WeatherColorLayer 或 visualSpeedScale=6000。下一次 Android 的首要检查是 Timeline Pause 下真实固定场粒子是否持续移动,并核对 `ParticleStateTarget` 类型日志。
|
||||||
|
|||||||
Reference in New Issue
Block a user