fix: correct particle time-window reuse state
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:
6
docs/50_Step记录/STEP-043A_40-02C源码审查后最小修正_v1.md
Normal file
6
docs/50_Step记录/STEP-043A_40-02C源码审查后最小修正_v1.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# STEP-043A 40-02C 源码审查后最小修正
|
||||
|
||||
- 基线:`5dd4dbf4967c1238e1bde8fb66fe3dbd2c4fe52a`
|
||||
- 状态:`IMPLEMENTED / WAITING FOR ANDROID VALIDATION`
|
||||
- 修正:GPU texture reuse 保留旧 backing 但消费 wanted snapshot revision;MIX_REJECTED 不再改变 timeMix;增加 Layer/Resource/Field upload/reuse/reset 生命周期日志;所有 Style reload 调用点有具体 reason。
|
||||
- 检查:shader generator、Linux Core/source-test object、format、diff check 通过;未运行 Android Gradle、ADB 或设备。
|
||||
@@ -5,3 +5,4 @@
|
||||
- 规则:generation 仍是 source revision;GPU reuse 比较 fixtureID、dataVersion、已验证 `fieldSha256` 和 grid compatibility。MIX 未就绪只记录 `MIX_REJECTED`,不 reload style。
|
||||
- 检查:shader generator、Linux Core/source test object、format、diff check 通过;未运行 Android Gradle/ADB。
|
||||
- Android:T1--T10 仍待用户执行,不能判 PASS。
|
||||
- STEP-043A 源码审查发现 reuse revision、MIX reject state、diagnostics 与 Style reason 缺口,均已最小修正;状态不变。
|
||||
|
||||
18
docs/报告/STEP-043A_40-02C源码审查后最小修正_Codex完成报告_v1.md
Normal file
18
docs/报告/STEP-043A_40-02C源码审查后最小修正_Codex完成报告_v1.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# STEP-043A 40-02C 源码审查后最小修正完成报告
|
||||
|
||||
开始基线为 `5dd4dbf4967c1238e1bde8fb66fe3dbd2c4fe52a`,状态为 `IMPLEMENTED / WAITING FOR ANDROID VALIDATION`。
|
||||
|
||||
- Reuse:旧 T0/T1 texture/image 可复用,但 `resources` snapshot 现在写入 wanted revision,避免下一帧重复 sourceChanged。
|
||||
- MIX:guard 完成后才赋值 `timeMix`;拒绝时没有 state/property/style mutation。
|
||||
- Diagnostics:新增 renderLayerInstanceId、LAYER_CREATE/DESTROY/CHANGED、FIELD_UPLOAD、FIELD_REUSE、SIMULATION_RESET/resetCount 与 TIME_MIX_UPDATE 关联字段。
|
||||
- Style:INITIAL、ENABLE、DISABLE、UNIFORM_EAST、UNIFORM_NORTH、TIME_WINDOW、ADVANCE 等调用点均传明确 reason;MIX 不会触发 Style load。
|
||||
- 检查:shader generator、Linux Core/source-test object、clang-format、diff check 通过。未运行 Android Gradle、ADB 或设备验证。
|
||||
|
||||
用户手工:
|
||||
|
||||
```bash
|
||||
cd /mnt/sda1/MapLibreParticleLab/platform/android
|
||||
./gradlew :MapLibreAndroidTestApp:assembleOpenglDebug --console=plain
|
||||
```
|
||||
|
||||
随后以 `adb -s 192.168.1.146` 安装并验证 T1--T10;在用户设备验收前不得判 PASS。
|
||||
@@ -53,7 +53,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
.zoom(2.0)
|
||||
.build()
|
||||
}
|
||||
loadValidationStyle(active = active, enabled = enabled)
|
||||
loadValidationStyle(active = active, enabled = enabled, reason = "INITIAL")
|
||||
}
|
||||
|
||||
findViewById<Button>(R.id.validationActive).setOnClickListener {
|
||||
@@ -63,10 +63,10 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
if (this::maplibreMap.isInitialized) setValidationProperties(active = false, maskMode = maskMode)
|
||||
}
|
||||
findViewById<Button>(R.id.validationDisable).setOnClickListener {
|
||||
if (this::maplibreMap.isInitialized) loadValidationStyle(active = active, enabled = false)
|
||||
if (this::maplibreMap.isInitialized) loadValidationStyle(active = active, enabled = false, reason = "DISABLE")
|
||||
}
|
||||
findViewById<Button>(R.id.validationEnable).setOnClickListener {
|
||||
if (this::maplibreMap.isInitialized) loadValidationStyle(active = active, enabled = true)
|
||||
if (this::maplibreMap.isInitialized) loadValidationStyle(active = active, enabled = true, reason = "ENABLE")
|
||||
}
|
||||
findViewById<Button>(R.id.validationGlobal).setOnClickListener {
|
||||
if (this::maplibreMap.isInitialized) setValidationProperties(active = active, maskMode = MASK_GLOBAL)
|
||||
@@ -80,18 +80,18 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
findViewById<Button>(R.id.validationUniformEast).setOnClickListener {
|
||||
timeWindowMode = false
|
||||
fixtureMode = FIXTURE_UNIFORM_EAST
|
||||
if (this::maplibreMap.isInitialized) loadValidationStyle(active, enabled)
|
||||
if (this::maplibreMap.isInitialized) loadValidationStyle(active, enabled, "UNIFORM_EAST")
|
||||
}
|
||||
findViewById<Button>(R.id.validationUniformNorth).setOnClickListener {
|
||||
timeWindowMode = false
|
||||
fixtureMode = FIXTURE_UNIFORM_NORTH
|
||||
if (this::maplibreMap.isInitialized) loadValidationStyle(active, enabled)
|
||||
if (this::maplibreMap.isInitialized) loadValidationStyle(active, enabled, "UNIFORM_NORTH")
|
||||
}
|
||||
findViewById<Button>(R.id.validationTimeWindow).setOnClickListener {
|
||||
timeWindowMode = true
|
||||
fixtureMode = FIXTURE_UNIFORM_EAST
|
||||
timeMix = 0.0f
|
||||
if (this::maplibreMap.isInitialized) loadValidationStyle(active, enabled)
|
||||
if (this::maplibreMap.isInitialized) loadValidationStyle(active, enabled, "TIME_WINDOW")
|
||||
}
|
||||
findViewById<Button>(R.id.validationMix0).setOnClickListener { setTimeMix(0.0f) }
|
||||
findViewById<Button>(R.id.validationMix50).setOnClickListener { setTimeMix(0.5f) }
|
||||
@@ -100,7 +100,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
timeWindowMode = true
|
||||
fixtureMode = FIXTURE_UNIFORM_NORTH
|
||||
timeMix = 0.0f
|
||||
if (this::maplibreMap.isInitialized) loadValidationStyle(active, enabled)
|
||||
if (this::maplibreMap.isInitialized) loadValidationStyle(active, enabled, "ADVANCE")
|
||||
}
|
||||
findViewById<Button>(R.id.validationDateline).setOnClickListener {
|
||||
maplibreMap.cameraPosition = CameraPosition.Builder().target(LatLng(0.0, 179.0)).zoom(2.0).build()
|
||||
@@ -131,7 +131,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
this.fixtureMode = fixtureMode
|
||||
val layer = maplibreMap.style?.getLayer(PARTICLE_LAYER_ID)
|
||||
if (layer == null || !enabled) {
|
||||
loadValidationStyle(active, enabled)
|
||||
loadValidationStyle(active, enabled, "LAYER_NOT_READY")
|
||||
return
|
||||
}
|
||||
layer.setProperties(
|
||||
@@ -143,7 +143,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private fun setTimeMix(value: Float) {
|
||||
timeMix = value.coerceIn(0.0f, 1.0f)
|
||||
val requestedTimeMix = value.coerceIn(0.0f, 1.0f)
|
||||
val layer = maplibreMap.style?.getLayer(PARTICLE_LAYER_ID)
|
||||
val rejectReason = when {
|
||||
!styleReady -> "STYLE_LOADING"
|
||||
@@ -156,6 +156,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
Log.i(TAG, "ParticleGPU: event=MIX_REJECTED reason=$rejectReason styleEpoch=$styleLoadEpoch")
|
||||
return
|
||||
}
|
||||
timeMix = requestedTimeMix
|
||||
layer.setProperties(PaintPropertyValue("raster-particle-time-mix", timeMix))
|
||||
status.text = statusText()
|
||||
Log.i(TAG, "ParticleGPU: event=MIX_PROPERTY_ONLY value=$timeMix styleEpoch=$styleLoadEpoch")
|
||||
|
||||
@@ -48,6 +48,7 @@ constexpr Size MaskSize{16, 16};
|
||||
std::atomic_uint64_t nextGpuGeneration{0};
|
||||
std::atomic_uint64_t nextMaskGeneration{0};
|
||||
std::atomic_uint64_t nextFieldGeneration{0};
|
||||
std::atomic_uint64_t nextRenderLayerInstanceId{0};
|
||||
|
||||
struct ParticleSettings {
|
||||
bool active;
|
||||
@@ -144,6 +145,7 @@ void removeTargets(const std::vector<RenderTargetPtr>& targets, UniqueChangeRequ
|
||||
|
||||
class ParticleGpuResources {
|
||||
public:
|
||||
uint64_t renderLayerInstanceId = 0;
|
||||
uint64_t generation = 0;
|
||||
uint64_t committedFrame = 0;
|
||||
uint8_t stateRead = 0;
|
||||
@@ -168,6 +170,7 @@ public:
|
||||
uint64_t nextFieldGeneration = 0;
|
||||
uint64_t gpuNextFieldGeneration = 0;
|
||||
uint64_t fieldUploadCount = 0;
|
||||
uint64_t resetCount = 0;
|
||||
float timeMix = 0.0f;
|
||||
uint8_t fixtureMode = 255;
|
||||
uint8_t loggedMaskMode = 255;
|
||||
@@ -192,7 +195,12 @@ public:
|
||||
uint8_t stateWrite() const { return static_cast<uint8_t>(1 - stateRead); }
|
||||
uint8_t trailWrite() const { return static_cast<uint8_t>(1 - trailRead); }
|
||||
|
||||
void resetSequence() {
|
||||
void resetSequence(const char* reason) {
|
||||
++resetCount;
|
||||
Log::Info(Event::Render,
|
||||
"ParticleGPU: event=SIMULATION_RESET reason=" + std::string(reason) + " renderLayerInstanceId=" +
|
||||
std::to_string(renderLayerInstanceId) + " resourceGeneration=" + std::to_string(generation) +
|
||||
" resetCount=" + std::to_string(resetCount));
|
||||
committedFrame = 0;
|
||||
stateRead = 0;
|
||||
trailRead = 0;
|
||||
@@ -511,22 +519,29 @@ RenderRasterParticleLayer::RenderRasterParticleLayer(Immutable<style::Layer::Imp
|
||||
return makeMutable<style::ParticleRenderLayerProperties>(
|
||||
std::move(impl_), settings.active, settings.maskMode, settings.fixtureMode, settings.timeMix);
|
||||
}()) {
|
||||
renderLayerInstanceId = ++nextRenderLayerInstanceId;
|
||||
formalSource = baseImpl->getTypeInfo() == style::RasterParticleLayer::Impl::staticTypeInfo();
|
||||
Log::Info(Event::Render,
|
||||
"ParticleRender: RenderLayer create id=" + getID() +
|
||||
"ParticleGPU: event=LAYER_CREATE layerId=" + getID() +
|
||||
" renderLayerInstanceId=" + std::to_string(renderLayerInstanceId) +
|
||||
" source=" + (formalSource ? baseImpl->source : std::string("validation-only")));
|
||||
}
|
||||
|
||||
RenderRasterParticleLayer::~RenderRasterParticleLayer() {
|
||||
if (gpuResources) {
|
||||
Log::Info(Event::Render,
|
||||
"ParticleValidation30_02: resources destroy generation=" + std::to_string(gpuResources->generation) +
|
||||
"ParticleGPU: event=LAYER_DESTROY layerId=" + getID() +
|
||||
" renderLayerInstanceId=" + std::to_string(renderLayerInstanceId) +
|
||||
" resourceGeneration=" + std::to_string(gpuResources->generation) +
|
||||
" contextDestroyed=" + (gpuResources->contextDestroyed ? "true" : "false"));
|
||||
}
|
||||
Log::Info(Event::Render, "ParticleValidation: RenderLayer destroy id=" + getID());
|
||||
}
|
||||
|
||||
void RenderRasterParticleLayer::evaluate(const PropertyEvaluationParameters&) {
|
||||
Log::Info(Event::Render,
|
||||
"ParticleGPU: event=LAYER_CHANGED layerId=" + getID() +
|
||||
" renderLayerInstanceId=" + std::to_string(renderLayerInstanceId));
|
||||
const auto settings = particleSettings(baseImpl);
|
||||
auto properties = makeMutable<style::ParticleRenderLayerProperties>(
|
||||
baseImpl, settings.active, settings.maskMode, settings.fixtureMode, settings.timeMix);
|
||||
@@ -612,6 +627,7 @@ void RenderRasterParticleLayer::update(gfx::ShaderRegistry& shaders,
|
||||
}
|
||||
if (!resources) {
|
||||
auto owned = std::make_shared<ParticleGpuResources>();
|
||||
owned->renderLayerInstanceId = renderLayerInstanceId;
|
||||
owned->generation = ++nextGpuGeneration;
|
||||
owned->formalDisplay = formalSource;
|
||||
owned->shader = context.getGenericShader(shaders, ShaderName);
|
||||
@@ -663,10 +679,22 @@ void RenderRasterParticleLayer::update(gfx::ShaderRegistry& shaders,
|
||||
return;
|
||||
}
|
||||
owned->fieldTexture->setImage(owned->fieldImage);
|
||||
Log::Info(Event::Render,
|
||||
"ParticleGPU: event=FIELD_UPLOAD slot=T0 reason=RESOURCE_CREATE fieldUploadCount=1 "
|
||||
"sourceGeneration=" +
|
||||
std::to_string(owned->field.generation) +
|
||||
" renderLayerInstanceId=" + std::to_string(owned->renderLayerInstanceId) +
|
||||
" resourceGeneration=" + std::to_string(owned->generation));
|
||||
owned->fieldTexture->setSamplerConfiguration({.filter = gfx::TextureFilterType::Nearest,
|
||||
.wrapU = gfx::TextureWrapType::Repeat,
|
||||
.wrapV = gfx::TextureWrapType::Clamp});
|
||||
owned->nextFieldTexture->setImage(owned->nextFieldImage);
|
||||
Log::Info(Event::Render,
|
||||
"ParticleGPU: event=FIELD_UPLOAD slot=T1 reason=RESOURCE_CREATE fieldUploadCount=2 "
|
||||
"sourceGeneration=" +
|
||||
std::to_string(owned->nextField.generation) +
|
||||
" renderLayerInstanceId=" + std::to_string(owned->renderLayerInstanceId) +
|
||||
" resourceGeneration=" + std::to_string(owned->generation));
|
||||
owned->nextFieldTexture->setSamplerConfiguration({.filter = gfx::TextureFilterType::Nearest,
|
||||
.wrapU = gfx::TextureWrapType::Repeat,
|
||||
.wrapV = gfx::TextureWrapType::Clamp});
|
||||
@@ -748,22 +776,41 @@ void RenderRasterParticleLayer::update(gfx::ShaderRegistry& shaders,
|
||||
.wrapU = gfx::TextureWrapType::Repeat,
|
||||
.wrapV = gfx::TextureWrapType::Clamp});
|
||||
++resources->fieldUploadCount;
|
||||
Log::Info(Event::Render,
|
||||
"ParticleGPU: event=FIELD_UPLOAD reason=NEW_CONTENT fieldUploadCount=" +
|
||||
std::to_string(resources->fieldUploadCount) +
|
||||
" sourceGeneration=" + std::to_string(field.generation) +
|
||||
" renderLayerInstanceId=" + std::to_string(resources->renderLayerInstanceId) +
|
||||
" resourceGeneration=" + std::to_string(resources->generation));
|
||||
return true;
|
||||
};
|
||||
auto takeSlot = [&](const particle::ParticleFieldSnapshot& wanted,
|
||||
const char* destination,
|
||||
particle::ParticleFieldSnapshot& assigned,
|
||||
std::shared_ptr<PremultipliedImage>& image,
|
||||
gfx::Texture2DPtr& texture) {
|
||||
if (particle::sameFieldGpuContent(oldT0, wanted)) {
|
||||
assigned = oldT0;
|
||||
assigned = wanted;
|
||||
image = resources->fieldImage;
|
||||
texture = resources->fieldTexture;
|
||||
Log::Info(Event::Render,
|
||||
"ParticleGPU: event=FIELD_REUSE from=OLD_T0 to=" + std::string(destination) +
|
||||
" oldGeneration=" + std::to_string(oldT0.generation) +
|
||||
" newGeneration=" + std::to_string(wanted.generation) +
|
||||
" renderLayerInstanceId=" + std::to_string(resources->renderLayerInstanceId) +
|
||||
" resourceGeneration=" + std::to_string(resources->generation));
|
||||
return true;
|
||||
}
|
||||
if (particle::sameFieldGpuContent(oldT1, wanted)) {
|
||||
assigned = oldT1;
|
||||
assigned = wanted;
|
||||
image = resources->nextFieldImage;
|
||||
texture = resources->nextFieldTexture;
|
||||
Log::Info(Event::Render,
|
||||
"ParticleGPU: event=FIELD_REUSE from=OLD_T1 to=" + std::string(destination) +
|
||||
" oldGeneration=" + std::to_string(oldT1.generation) +
|
||||
" newGeneration=" + std::to_string(wanted.generation) +
|
||||
" renderLayerInstanceId=" + std::to_string(resources->renderLayerInstanceId) +
|
||||
" resourceGeneration=" + std::to_string(resources->generation));
|
||||
return true;
|
||||
}
|
||||
assigned = wanted;
|
||||
@@ -775,8 +822,8 @@ void RenderRasterParticleLayer::update(gfx::ShaderRegistry& shaders,
|
||||
std::shared_ptr<PremultipliedImage> imageT1;
|
||||
gfx::Texture2DPtr textureT0;
|
||||
gfx::Texture2DPtr textureT1;
|
||||
if (!takeSlot(nextT0, assignedT0, imageT0, textureT0) ||
|
||||
!takeSlot(nextT1, assignedT1, imageT1, textureT1)) {
|
||||
if (!takeSlot(nextT0, "NEW_T0", assignedT0, imageT0, textureT0) ||
|
||||
!takeSlot(nextT1, "NEW_T1", assignedT1, imageT1, textureT1)) {
|
||||
Log::Error(Event::Render, "ParticleGPU: field switch resource creation failed");
|
||||
return;
|
||||
}
|
||||
@@ -791,7 +838,7 @@ void RenderRasterParticleLayer::update(gfx::ShaderRegistry& shaders,
|
||||
resources->gpuFieldGeneration = resources->fieldGeneration;
|
||||
resources->gpuNextFieldGeneration = resources->nextFieldGeneration;
|
||||
resources->fixtureMode = fixtureMode;
|
||||
if (!windowOverlap) resources->resetSequence();
|
||||
if (!windowOverlap) resources->resetSequence("FIELD_NO_OVERLAP");
|
||||
Log::Info(Event::Render,
|
||||
"ParticleGPU: time window advance T0=" + oldT0.fixtureID + " T1=" + oldT1.fixtureID +
|
||||
" -> T0=" + resources->field.fixtureID + " T1=" + resources->nextField.fixtureID +
|
||||
@@ -807,9 +854,12 @@ void RenderRasterParticleLayer::update(gfx::ShaderRegistry& shaders,
|
||||
if (resources->timeMix != requestedTimeMix) {
|
||||
resources->timeMix = requestedTimeMix;
|
||||
Log::Info(Event::Render,
|
||||
"ParticleGPU: timeMix=" + std::to_string(resources->timeMix) + " T0=" + resources->field.fixtureID +
|
||||
" T1=" + resources->nextField.fixtureID + " uploads=" +
|
||||
std::to_string(resources->fieldUploadCount) + " simulationRebuild=0 trailRebuild=0");
|
||||
"ParticleGPU: event=TIME_MIX_UPDATE value=" + std::to_string(resources->timeMix) +
|
||||
" T0=" + resources->field.fixtureID + " T1=" + resources->nextField.fixtureID +
|
||||
" renderLayerInstanceId=" + std::to_string(resources->renderLayerInstanceId) +
|
||||
" resourceGeneration=" + std::to_string(resources->generation) +
|
||||
" fieldUploadCount=" + std::to_string(resources->fieldUploadCount) +
|
||||
" resetCount=" + std::to_string(resources->resetCount));
|
||||
}
|
||||
|
||||
if (!resources->lifecycleModeKnown || resources->lifecycleActive != active) {
|
||||
|
||||
@@ -34,6 +34,7 @@ private:
|
||||
std::optional<particle::ParticleFieldSnapshot> sourceField;
|
||||
std::optional<particle::ParticleFieldSnapshot> sourceNextField;
|
||||
bool formalSource = false;
|
||||
uint64_t renderLayerInstanceId = 0;
|
||||
};
|
||||
|
||||
} // namespace mbgl
|
||||
|
||||
@@ -231,6 +231,16 @@ TEST(Source, RasterArrayGpuContentIdentityExcludesRuntimeGeneration) {
|
||||
EXPECT_FALSE(particle::sameFieldGpuContent(east, changed));
|
||||
}
|
||||
|
||||
TEST(Source, RasterArrayGpuReuseConsumesNewRevision) {
|
||||
const auto oldT1 = particle::OfflineFixtureParticleAdapter::load("uniform-north", 100);
|
||||
auto wantedNewT0 = oldT1;
|
||||
wantedNewT0.generation = 200;
|
||||
const auto assignedAfterReuse = wantedNewT0;
|
||||
|
||||
EXPECT_TRUE(particle::sameFieldGpuContent(oldT1, wantedNewT0));
|
||||
EXPECT_FALSE(particle::fieldIdentityChanged(assignedAfterReuse, wantedNewT0));
|
||||
}
|
||||
|
||||
TEST(Source, RasterArrayTimeWindowKeepsIndependentCompatibleSnapshots) {
|
||||
const auto east = particle::OfflineFixtureParticleAdapter::load("uniform-east", 11);
|
||||
const auto north = particle::OfflineFixtureParticleAdapter::load("uniform-north", 12);
|
||||
|
||||
@@ -244,3 +244,7 @@
|
||||
- 修正 north-positive V 到 Mercator Y 的最终 displacement 符号;Field canonical contract 未变。
|
||||
- MIX 移除 Style reload fallback,未就绪只拒绝并记录日志;GPU reuse 使用已验证 SHA-256 content identity,revision generation 仍独立保留。
|
||||
- 状态:`IMPLEMENTED / WAITING FOR ANDROID VALIDATION`;未运行 Android Gradle/ADB,T1--T10 待用户验收。
|
||||
|
||||
## 2026-09-03 — STEP-043A 源码审查后最小修正
|
||||
|
||||
- texture reuse 现消费 wanted revision;MIX reject 不再改变 timeMix;补充 lifecycle diagnostics 与 Style reason。状态仍为 `IMPLEMENTED / WAITING FOR ANDROID VALIDATION`。
|
||||
|
||||
Reference in New Issue
Block a user