feat: draw all raster particle states
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:
37
docs/50_Step记录/STEP-044_Android_OpenGL粒子数量容量基线_v2.md
Normal file
37
docs/50_Step记录/STEP-044_Android_OpenGL粒子数量容量基线_v2.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# STEP-044 Android OpenGL 粒子数量容量基线 v2
|
||||
|
||||
- 状态:`IMPLEMENTED / WAITING FOR ANDROID VALIDATION`
|
||||
- 基线:`c0d1ad43c49ed6d526c3fd8b982c04840326d0d0` 的容量档位实现。
|
||||
- 范围:补齐正式 RasterParticle 的 full particle draw;不是新 Step,也不是容量结论或性能优化。
|
||||
|
||||
## v1 的实际缺口
|
||||
|
||||
v1 已让 State A/B 容量随 `raster-particle-particle-count` 改为 256 至 1,048,576,但 Trail fragment 只读取四个固定 State UV。因此当时只能证明 N 个 State 同时 simulation,不能证明 N 个粒子同时 Trail/render/composite;Android 容量验收据此暂停。
|
||||
|
||||
## v2 补齐
|
||||
|
||||
- `effectiveParticleCount=N -> State N -> Trail draw N -> Composite` 已进入唯一正式 `RasterArraySource -> RasterParticleLayer -> RenderRasterParticleLayer` 链。
|
||||
- 每个粒子在 State A/B 中对应一条静态 GPU vertex:`particleIndex -> x=index%stateGridWidth, y=index/stateGridWidth`。vertex buffer 仅在 resource create 或 particle-count change 重建;顶点 shader 用 `(x+0.5,y+0.5)/stateGrid` 采样当前 State write texture,位置从未 CPU 生成或 readback。
|
||||
- Trail write target 先由全屏 Trail fade quad 消费前一 Trail,再追加 full point draw。每粒子一个 point,`trailDrawCount=N`;Composite 继续消费同一 Trail A/B write target。
|
||||
- 现有 16-bit index 路径每 segment 最多 65,536 points;更高 count(包括 1M)被分为多个 segment,仍使用同一 static index buffer、同一 shader 和同一 State texture,非第二 simulation。
|
||||
- legacy `ParticleValidationLayer` 仍保留四个 deterministic sample 诊断;正式 RasterParticle Layer 的路径不再使用它们作为 draw 数量限制。
|
||||
|
||||
## 生命周期与冻结语义
|
||||
|
||||
- particle-count change 继续只触发合理的 State A/B 和 full-draw resource rebuild,以及 `SIMULATION_RESET reason=PARTICLE_COUNT_CHANGE` / reseed。
|
||||
- Field T0/T1、MIX、interpolation、Field reuse、mask、A/B→B/C 及既有 State/Trail ping-pong 没有改变。MIX 仍不能导致 draw resource rebuild、Style reload、Field upload 或 reset。
|
||||
- State seed 仍是每个 State texel 的 `v_uv`,所以 full draw 的首次 256/1K 粒子已经 deterministic 地分布在完整 grid,而非重叠于一个点。
|
||||
|
||||
## 观测日志
|
||||
|
||||
资源 create/rebuild/release 日志含 `effectiveParticleCount`、`stateGrid`、`trailDrawCount`、`particleIndexCount`、`fullParticleDraw=true` 和 `resourceGeneration`。`PARTICLE_DRAW_REJECTED` 明确报告缺失 draw resource 或 drawable finalize failure。
|
||||
|
||||
## 待 Android 验收
|
||||
|
||||
| 用例 | 要求 | 状态 |
|
||||
|---|---|---|
|
||||
| T-FULL-01 | 256:`16x16`、`trailDrawCount=256`、明显粒子群并持续运动 | 待测 |
|
||||
| T-FULL-02 | 1K:`32x32`、`trailDrawCount=1024`、密度明显大于 256、Trail/Composite 正常 | 待测 |
|
||||
| T-FULL-03 | 256→1K→256:draw count 同步变化,无 crash、Style reload、Field upload 或 MIX 改变 | 待测 |
|
||||
|
||||
只有 T-FULL-01~03 通过,才恢复 4K / 16K / 64K / 1M 的容量矩阵。Codex 未运行 Android Gradle、ADB、安装或设备测试;未加入 Jest。
|
||||
42
docs/报告/40-03_Android_OpenGL粒子数量容量基线_Codex完成报告_v2.md
Normal file
42
docs/报告/40-03_Android_OpenGL粒子数量容量基线_Codex完成报告_v2.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# 40-03 Android OpenGL 粒子数量容量基线 Codex 完成报告 v2
|
||||
|
||||
## 状态
|
||||
|
||||
`40-03:IMPLEMENTED / WAITING FOR ANDROID VALIDATION`
|
||||
|
||||
v1 的 Android 容量验证暂停是正确的:它的 State A/B 已支持 N 个状态,但 Trail/render 只消费四个固定 State sample。本次补齐后尚无 Android 实机证据,不能写 `CLOSED / PASS`。
|
||||
|
||||
## 已完成
|
||||
|
||||
1. 原固定 sample 位于 `shaders/particle_gpu_validation.fragment.glsl` 的 Trail 分支,固定 State UV 为 0.125、0.375、0.625、0.875;原 Trail 实际是一个全屏 quad,6 个 index,并非 N 个粒子 draw。
|
||||
2. 正式 full draw 采用静态 State-index vertex buffer,而不是 GPU instance ID:每个 index 一次性映射到 `(x=index%width, y=index/width)`,数目等于 `effectiveParticleCount`。它在初始资源创建和 count rebuild 创建/释放,稳定帧不做 CPU 粒子循环。
|
||||
3. formal Trail pass 先用既有全屏 quad 衰减 previous Trail,随后以 `gfx::Points` 提交所有 static State index。vertex shader 从当前 State write texture 读取该 index 的 RG position;每个 point 进入同一 Trail A/B target,Composite 无改动地读取 Trail write。
|
||||
4. 256 时 `trailDrawCount=256`;1K 时 `trailDrawCount=1024`。1M 使用相同机制,按最多 65,536 points 的 16-bit-index segment 切分为多个 draw segment,理论路径为 1,048,576 State texel / point draw。
|
||||
5. 固定四 sample 仅保留给 legacy `ParticleValidationLayer` 诊断。正式 RasterParticle Layer 不再依赖它们;没有 GPU→CPU readback、每帧 CPU position 生成、旁路 overlay 或第二 simulation。
|
||||
6. State seed 保持每个 State texel 的 `v_uv`,无需改变 reseed;每粒子初始位置已是 deterministic grid-distributed。没有修改 U/V、`dy=-v`、Field sampling、T0/T1、MIX、JNI 或 40-02 resource identity 语义。
|
||||
7. `PARTICLE_DRAW_RESOURCES_CREATE|REBUILD|RELEASE` 与 `PARTICLE_DRAW_REJECTED` 日志新增/补齐 `effectiveParticleCount`、`stateGrid`、`trailDrawCount`、`particleIndexCount`、`fullParticleDraw=true` 和 generation。
|
||||
|
||||
## 本地检查
|
||||
|
||||
- shader generator 连续两次运行,生成物稳定;源 GLSL 是唯一修改入口。
|
||||
- Linux/OpenGL 精确编译通过:`render_particle_validation_layer.cpp.o`、`raster_particle_layer.cpp.o`、`style_layer.test.cpp.o`。
|
||||
- 相关 Layer test object 已编译;完整测试二进制当前 build tree 不存在,未误报运行通过。
|
||||
- `clang-format --dry-run --Werror`、`git diff --check`:通过。
|
||||
- 未运行 Android Gradle、ADB、APK、设备测试或 Jest;没有 APK packaging 改动。
|
||||
|
||||
## 待用户 Android 验证
|
||||
|
||||
先只执行 T-FULL-01~03:256、1K、256→1K→256。确认 `trailDrawCount` 等于 effective count、视觉密度变化、持续运动,以及没有 Style reload / Field upload / MIX 改变后,再扩展到 4K/16K/64K/1M。
|
||||
|
||||
PowerShell 或 CMD:
|
||||
|
||||
```powershell
|
||||
cd /mnt/sda1/MapLibreParticleLab/platform/android
|
||||
./gradlew :MapLibreAndroidTestApp:assembleOpenglDebug -Pandroid.injected.build.abi=x86_64 --console=plain
|
||||
adb -s 192.168.1.146 install -r .\MapLibreAndroidTestApp\build\intermediates\apk\opengl\debug\MapLibreAndroidTestApp-opengl-debug.apk
|
||||
adb -s 192.168.1.146 logcat -c
|
||||
adb -s 192.168.1.146 shell am start -n org.maplibre.android.testapp/.activity.particle.ParticleCoreLayerValidationActivity
|
||||
adb -s 192.168.1.146 logcat -v time | Select-String -Pattern 'ParticleGPU|PARTICLE_COUNT|STATE|DRAW|trailDrawCount|effectiveParticleCount|stateGrid|fullParticleDraw|STYLE|FIELD|MIX'
|
||||
```
|
||||
|
||||
APK:`./MapLibreAndroidTestApp/build/intermediates/apk/opengl/debug/MapLibreAndroidTestApp-opengl-debug.apk`。
|
||||
@@ -20,11 +20,22 @@ layout (std140) uniform ParticleGpuValidationUBO {
|
||||
highp mat4 u_matrix;
|
||||
};
|
||||
|
||||
uniform sampler2D u_state_texture;
|
||||
|
||||
void main() {
|
||||
v_uv = a_pos;
|
||||
v_world = a_pos;
|
||||
if (u_pass.x < 1.5) {
|
||||
gl_Position = vec4(a_pos * 2.0 - 1.0, 0.0, 1.0);
|
||||
} else if (u_pass.x > 3.5) {
|
||||
// 40-03: a_pos is a static State texel index generated only when the
|
||||
// particle capacity changes. The position itself remains GPU-owned.
|
||||
vec2 stateUV = (a_pos + vec2(0.5)) / u_mask.xy;
|
||||
vec2 position = texture(u_state_texture, stateUV).rg;
|
||||
v_uv = position;
|
||||
v_world = position;
|
||||
gl_PointSize = 3.0;
|
||||
gl_Position = vec4(position * 2.0 - 1.0, 0.0, 1.0);
|
||||
} else {
|
||||
// VALIDATION ONLY: a_pos is canonical z0 world geometry; u_matrix is
|
||||
// PaintParameters::matrixForTile() for the selected MapLibre world copy.
|
||||
@@ -74,8 +85,15 @@ void main() {
|
||||
if (any(isnan(velocity))) position = v_uv;
|
||||
fragColor = vec4(position, fract(previous.b + 0.03125 * u_pass.y), 1.0);
|
||||
} else if (mode < 1.5) {
|
||||
// The trail consumes State B/A written by the real field-sampling pass.
|
||||
// Four deterministic texels are enough for the Stage A visual proof.
|
||||
// Formal Trail first fades the previous target. Its full-particle point
|
||||
// draw follows in the same target and consumes every State texel.
|
||||
if (u_mask.z > 0.5) {
|
||||
fragColor = vec4(previous.rgb * u_marker.w, 1.0);
|
||||
return;
|
||||
}
|
||||
|
||||
// LEGACY VALIDATION DISPLAY: retain the original four deterministic
|
||||
// samples for the historical ParticleValidationLayer only.
|
||||
float marker = 0.0;
|
||||
marker = max(marker, 1.0 - smoothstep(0.0, u_marker.z, distance(v_uv, texture(u_state_texture, vec2(0.125, 0.125)).rg)));
|
||||
marker = max(marker, 1.0 - smoothstep(0.0, u_marker.z, distance(v_uv, texture(u_state_texture, vec2(0.375, 0.375)).rg)));
|
||||
@@ -83,6 +101,11 @@ void main() {
|
||||
marker = max(marker, 1.0 - smoothstep(0.0, u_marker.z, distance(v_uv, texture(u_state_texture, vec2(0.875, 0.875)).rg)));
|
||||
vec3 color = previous.rgb * u_marker.w + marker * vec3(0.08, 0.55, 1.0);
|
||||
fragColor = vec4(min(color, vec3(1.0)), 1.0);
|
||||
} else if (mode > 3.5) {
|
||||
// The vertex shader fetched this particle's State texel directly from
|
||||
// the current State write texture. Every point contributes one formal
|
||||
// particle draw; no CPU position data is involved per frame.
|
||||
fragColor = vec4(0.08, 0.55, 1.0, 1.0);
|
||||
} else {
|
||||
// The formal RasterParticle layer displays the Trail texture written by
|
||||
// the current State -> Trail pass. Legacy ParticleValidation keeps its
|
||||
|
||||
@@ -40,8 +40,15 @@ void main() {
|
||||
if (any(isnan(velocity))) position = v_uv;
|
||||
fragColor = vec4(position, fract(previous.b + 0.03125 * u_pass.y), 1.0);
|
||||
} else if (mode < 1.5) {
|
||||
// The trail consumes State B/A written by the real field-sampling pass.
|
||||
// Four deterministic texels are enough for the Stage A visual proof.
|
||||
// Formal Trail first fades the previous target. Its full-particle point
|
||||
// draw follows in the same target and consumes every State texel.
|
||||
if (u_mask.z > 0.5) {
|
||||
fragColor = vec4(previous.rgb * u_marker.w, 1.0);
|
||||
return;
|
||||
}
|
||||
|
||||
// LEGACY VALIDATION DISPLAY: retain the original four deterministic
|
||||
// samples for the historical ParticleValidationLayer only.
|
||||
float marker = 0.0;
|
||||
marker = max(marker, 1.0 - smoothstep(0.0, u_marker.z, distance(v_uv, texture(u_state_texture, vec2(0.125, 0.125)).rg)));
|
||||
marker = max(marker, 1.0 - smoothstep(0.0, u_marker.z, distance(v_uv, texture(u_state_texture, vec2(0.375, 0.375)).rg)));
|
||||
@@ -49,6 +56,11 @@ void main() {
|
||||
marker = max(marker, 1.0 - smoothstep(0.0, u_marker.z, distance(v_uv, texture(u_state_texture, vec2(0.875, 0.875)).rg)));
|
||||
vec3 color = previous.rgb * u_marker.w + marker * vec3(0.08, 0.55, 1.0);
|
||||
fragColor = vec4(min(color, vec3(1.0)), 1.0);
|
||||
} else if (mode > 3.5) {
|
||||
// The vertex shader fetched this particle's State texel directly from
|
||||
// the current State write texture. Every point contributes one formal
|
||||
// particle draw; no CPU position data is involved per frame.
|
||||
fragColor = vec4(0.08, 0.55, 1.0, 1.0);
|
||||
} else {
|
||||
// The formal RasterParticle layer displays the Trail texture written by
|
||||
// the current State -> Trail pass. Legacy ParticleValidation keeps its
|
||||
|
||||
@@ -10,11 +10,22 @@ layout (std140) uniform ParticleGpuValidationUBO {
|
||||
highp mat4 u_matrix;
|
||||
};
|
||||
|
||||
uniform sampler2D u_state_texture;
|
||||
|
||||
void main() {
|
||||
v_uv = a_pos;
|
||||
v_world = a_pos;
|
||||
if (u_pass.x < 1.5) {
|
||||
gl_Position = vec4(a_pos * 2.0 - 1.0, 0.0, 1.0);
|
||||
} else if (u_pass.x > 3.5) {
|
||||
// 40-03: a_pos is a static State texel index generated only when the
|
||||
// particle capacity changes. The position itself remains GPU-owned.
|
||||
vec2 stateUV = (a_pos + vec2(0.5)) / u_mask.xy;
|
||||
vec2 position = texture(u_state_texture, stateUV).rg;
|
||||
v_uv = position;
|
||||
v_world = position;
|
||||
gl_PointSize = 3.0;
|
||||
gl_Position = vec4(position * 2.0 - 1.0, 0.0, 1.0);
|
||||
} else {
|
||||
// VALIDATION ONLY: a_pos is canonical z0 world geometry; u_matrix is
|
||||
// PaintParameters::matrixForTile() for the selected MapLibre world copy.
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <mbgl/gfx/drawable.hpp>
|
||||
#include <mbgl/gfx/drawable_builder.hpp>
|
||||
#include <mbgl/gfx/drawable_tweaker.hpp>
|
||||
#include <mbgl/gfx/index_vector.hpp>
|
||||
#include <mbgl/gfx/shader_registry.hpp>
|
||||
#include <mbgl/gfx/texture2d.hpp>
|
||||
#include <mbgl/map/transform_state.hpp>
|
||||
@@ -34,6 +35,7 @@
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
@@ -73,6 +75,7 @@ enum class ValidationPass : uint8_t {
|
||||
Trail = 1,
|
||||
Composite = 2,
|
||||
FrozenComposite = 3,
|
||||
TrailParticles = 4,
|
||||
};
|
||||
|
||||
const char* slotName(const uint8_t slot) {
|
||||
@@ -206,7 +209,10 @@ public:
|
||||
std::shared_ptr<PremultipliedImage> nextFieldImage;
|
||||
std::shared_ptr<PremultipliedImage> maskFixture;
|
||||
std::shared_ptr<gfx::VertexVector<HeatmapTextureLayoutVertex>> vertices;
|
||||
std::shared_ptr<gfx::VertexVector<HeatmapTextureLayoutVertex>> particleVertices;
|
||||
gfx::IndexVectorBasePtr particleIndices;
|
||||
SegmentVector segments;
|
||||
SegmentVector particleSegments;
|
||||
|
||||
uint8_t stateWrite() const { return static_cast<uint8_t>(1 - stateRead); }
|
||||
uint8_t trailWrite() const { return static_cast<uint8_t>(1 - trailRead); }
|
||||
@@ -255,6 +261,33 @@ public:
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint32_t MaxParticleVerticesPerSegment = std::numeric_limits<uint16_t>::max() + 1u;
|
||||
|
||||
void rebuildParticleDrawResources(const std::shared_ptr<ParticleGpuResources>& resources) {
|
||||
auto vertices = std::make_shared<gfx::VertexVector<HeatmapTextureLayoutVertex>>();
|
||||
vertices->reserve(resources->particleCount);
|
||||
for (uint32_t particleIndex = 0; particleIndex < resources->particleCount; ++particleIndex) {
|
||||
const auto x = static_cast<int16_t>(particleIndex % resources->stateSize.width);
|
||||
const auto y = static_cast<int16_t>(particleIndex / resources->stateSize.width);
|
||||
vertices->emplace_back(HeatmapBucket::textureVertex({x, y}));
|
||||
}
|
||||
|
||||
auto indices = std::make_shared<gfx::IndexVectorBase>();
|
||||
indices->reserve(std::min(resources->particleCount, MaxParticleVerticesPerSegment));
|
||||
for (uint32_t index = 0; index < std::min(resources->particleCount, MaxParticleVerticesPerSegment); ++index) {
|
||||
indices->extend(1, static_cast<uint16_t>(index));
|
||||
}
|
||||
|
||||
SegmentVector segments;
|
||||
for (uint32_t offset = 0; offset < resources->particleCount; offset += MaxParticleVerticesPerSegment) {
|
||||
const auto count = std::min(resources->particleCount - offset, MaxParticleVerticesPerSegment);
|
||||
segments.emplace_back(offset, 0, count, count);
|
||||
}
|
||||
resources->particleVertices = std::move(vertices);
|
||||
resources->particleIndices = std::move(indices);
|
||||
resources->particleSegments = std::move(segments);
|
||||
}
|
||||
|
||||
class ParticleGpuValidationTweaker final : public gfx::DrawableTweaker {
|
||||
public:
|
||||
ParticleGpuValidationTweaker(std::shared_ptr<ParticleGpuResources> state_,
|
||||
@@ -311,8 +344,8 @@ public:
|
||||
const shaders::ParticleGpuValidationUBO ubo = {
|
||||
.pass = {static_cast<float>(pass), frame, maskMode == 0 ? 0.0f : 1.0f, static_cast<float>(maskMode)},
|
||||
.marker = {markerX, markerY, 0.035f, 0.965f},
|
||||
.mask = {static_cast<float>(resources->maskGeneration),
|
||||
static_cast<float>(worldWrap),
|
||||
.mask = {static_cast<float>(resources->stateSize.width),
|
||||
static_cast<float>(resources->stateSize.height),
|
||||
resources->formalDisplay ? 1.0f : 0.0f,
|
||||
seedState ? 1.0f : 0.0f},
|
||||
.matrix = util::cast<float>(parameters.matrixForTile({worldWrap, {0, 0, 0}})),
|
||||
@@ -527,6 +560,63 @@ bool buildQuad(gfx::Context& context,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool buildParticlePoints(gfx::Context& context,
|
||||
const std::shared_ptr<ParticleGpuResources>& resources,
|
||||
LayerGroup& group,
|
||||
const std::string& name,
|
||||
const gfx::Texture2DPtr& stateTexture,
|
||||
const gfx::Texture2DPtr& target,
|
||||
const uint8_t stateSlot,
|
||||
const uint8_t trailSlot) {
|
||||
if (!resources->particleVertices || !resources->particleIndices || resources->particleSegments.empty() ||
|
||||
!stateTexture || !target) {
|
||||
Log::Error(Event::Render, "ParticleGPU: event=PARTICLE_DRAW_REJECTED reason=MISSING_DRAW_RESOURCES");
|
||||
return false;
|
||||
}
|
||||
auto attributes = context.createVertexAttributeArray();
|
||||
if (const auto& attribute = attributes->set(shaders::idParticleGpuValidationPosVertexAttribute)) {
|
||||
attribute->setSharedRawData(resources->particleVertices,
|
||||
offsetof(HeatmapTextureLayoutVertex, a1),
|
||||
0,
|
||||
sizeof(HeatmapTextureLayoutVertex),
|
||||
gfx::AttributeDataType::Short2);
|
||||
}
|
||||
|
||||
auto builder = context.createDrawableBuilder(name);
|
||||
builder->setShader(resources->shader);
|
||||
builder->setRenderPass(RenderPass::Translucent);
|
||||
builder->setEnableDepth(false);
|
||||
builder->setColorMode(gfx::ColorMode::unblended());
|
||||
builder->setCullFaceMode(gfx::CullFaceMode::disabled());
|
||||
builder->setVertexAttributes(std::move(attributes));
|
||||
builder->setRawVertices({}, resources->particleVertices->elements(), gfx::AttributeDataType::Short2);
|
||||
builder->setSegments(gfx::Points(3.0f),
|
||||
resources->particleIndices,
|
||||
resources->particleSegments.data(),
|
||||
resources->particleSegments.size());
|
||||
builder->setTexture(stateTexture, shaders::idParticleGpuValidationPreviousTexture);
|
||||
builder->setTexture(stateTexture, shaders::idParticleGpuValidationStateTexture);
|
||||
builder->addTweaker(std::make_shared<ParticleGpuValidationTweaker>(resources,
|
||||
ValidationPass::TrailParticles,
|
||||
stateSlot,
|
||||
trailSlot,
|
||||
stateTexture,
|
||||
target,
|
||||
resources->maskTexture,
|
||||
stateTexture,
|
||||
0.0f,
|
||||
0,
|
||||
0));
|
||||
builder->flush(context);
|
||||
auto drawables = builder->clearDrawables();
|
||||
if (drawables.size() != 1 || !drawables.front()) {
|
||||
Log::Error(Event::Render, "ParticleGPU: event=PARTICLE_DRAW_REJECTED reason=DRAWABLE_FINALIZE_FAILED");
|
||||
return false;
|
||||
}
|
||||
group.addDrawable(std::move(drawables.front()));
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
RenderRasterParticleLayer::RenderRasterParticleLayer(Immutable<style::Layer::Impl> impl_)
|
||||
@@ -596,6 +686,10 @@ void RenderRasterParticleLayer::layerRemoved(UniqueChangeRequestVec& changes) {
|
||||
"ParticleGPU: event=PARTICLE_RESOURCES_RELEASE reason=LAYER_REMOVED particleCount=" +
|
||||
std::to_string(gpuResources->particleCount) +
|
||||
" resourceGeneration=" + std::to_string(gpuResources->generation));
|
||||
Log::Info(Event::Render,
|
||||
"ParticleGPU: event=PARTICLE_DRAW_RESOURCES_RELEASE reason=LAYER_REMOVED trailDrawCount=" +
|
||||
std::to_string(gpuResources->particleVertices ? gpuResources->particleVertices->elements() : 0) +
|
||||
" fullParticleDraw=true resourceGeneration=" + std::to_string(gpuResources->generation));
|
||||
removeTargets(gpuResources->registeredTargets, changes);
|
||||
gpuResources->registeredTargets.clear();
|
||||
}
|
||||
@@ -609,6 +703,10 @@ void RenderRasterParticleLayer::markContextDestroyed() {
|
||||
"ParticleGPU: event=PARTICLE_RESOURCES_RELEASE reason=CONTEXT_DESTROYED particleCount=" +
|
||||
std::to_string(gpuResources->particleCount) +
|
||||
" resourceGeneration=" + std::to_string(gpuResources->generation));
|
||||
Log::Info(Event::Render,
|
||||
"ParticleGPU: event=PARTICLE_DRAW_RESOURCES_RELEASE reason=CONTEXT_DESTROYED trailDrawCount=" +
|
||||
std::to_string(gpuResources->particleVertices ? gpuResources->particleVertices->elements() : 0) +
|
||||
" fullParticleDraw=true resourceGeneration=" + std::to_string(gpuResources->generation));
|
||||
Log::Info(
|
||||
Event::Render,
|
||||
"ParticleValidation30_02: context lost discard generation=" + std::to_string(gpuResources->generation) +
|
||||
@@ -665,6 +763,7 @@ void RenderRasterParticleLayer::update(gfx::ShaderRegistry& shaders,
|
||||
owned->segments = RenderStaticData::heatmapTextureSegments();
|
||||
owned->particleCount = particleCount;
|
||||
owned->stateSize = particleStateSize(owned->particleCount);
|
||||
rebuildParticleDrawResources(owned);
|
||||
for (uint8_t i = 0; i < 2; ++i) {
|
||||
owned->stateTargets[i] = createValidationTarget(
|
||||
context, owned->stateSize, getID() + "-state-" + slotName(i), gfx::TextureFilterType::Nearest);
|
||||
@@ -749,6 +848,13 @@ void RenderRasterParticleLayer::update(gfx::ShaderRegistry& shaders,
|
||||
" effectiveParticleCount=" + std::to_string(resources->particleCount) + " stateGrid=" +
|
||||
std::to_string(resources->stateSize.width) + "x" + std::to_string(resources->stateSize.height) +
|
||||
" resourceGeneration=" + std::to_string(resources->generation));
|
||||
Log::Info(Event::Render,
|
||||
"ParticleGPU: event=PARTICLE_DRAW_RESOURCES_CREATE effectiveParticleCount=" +
|
||||
std::to_string(resources->particleCount) + " stateGrid=" +
|
||||
std::to_string(resources->stateSize.width) + "x" + std::to_string(resources->stateSize.height) +
|
||||
" trailDrawCount=" + std::to_string(resources->particleVertices->elements()) +
|
||||
" particleIndexCount=" + std::to_string(resources->particleVertices->elements()) +
|
||||
" fullParticleDraw=true resourceGeneration=" + std::to_string(resources->generation));
|
||||
Log::Info(
|
||||
Event::Render,
|
||||
"ParticleField: fixture loaded id=" + resources->field.fixtureID + " type=" + resources->field.fieldType +
|
||||
@@ -806,10 +912,15 @@ void RenderRasterParticleLayer::update(gfx::ShaderRegistry& shaders,
|
||||
std::to_string(previousCount) + " stateGrid=" + std::to_string(previousSize.width) + "x" +
|
||||
std::to_string(previousSize.height) +
|
||||
" resourceGeneration=" + std::to_string(resources->generation));
|
||||
Log::Info(Event::Render,
|
||||
"ParticleGPU: event=PARTICLE_DRAW_RESOURCES_RELEASE reason=PARTICLE_COUNT_CHANGE trailDrawCount=" +
|
||||
std::to_string(resources->particleVertices ? resources->particleVertices->elements() : 0) +
|
||||
" fullParticleDraw=true resourceGeneration=" + std::to_string(resources->generation));
|
||||
removeTargets(resources->stateTargets, changes);
|
||||
resources->stateTargets = std::move(replacementTargets);
|
||||
resources->particleCount = particleCount;
|
||||
resources->stateSize = requestedSize;
|
||||
rebuildParticleDrawResources(resources);
|
||||
resources->resetSequence("PARTICLE_COUNT_CHANGE");
|
||||
Log::Info(Event::Render,
|
||||
"ParticleGPU: event=PARTICLE_RESOURCES_REBUILD reason=PARTICLE_COUNT_CHANGE requestedParticleCount=" +
|
||||
@@ -817,6 +928,13 @@ void RenderRasterParticleLayer::update(gfx::ShaderRegistry& shaders,
|
||||
" effectiveParticleCount=" + std::to_string(resources->particleCount) + " stateGrid=" +
|
||||
std::to_string(resources->stateSize.width) + "x" + std::to_string(resources->stateSize.height) +
|
||||
" resourceGeneration=" + std::to_string(resources->generation));
|
||||
Log::Info(Event::Render,
|
||||
"ParticleGPU: event=PARTICLE_DRAW_RESOURCES_REBUILD effectiveParticleCount=" +
|
||||
std::to_string(resources->particleCount) + " stateGrid=" +
|
||||
std::to_string(resources->stateSize.width) + "x" + std::to_string(resources->stateSize.height) +
|
||||
" trailDrawCount=" + std::to_string(resources->particleVertices->elements()) +
|
||||
" particleIndexCount=" + std::to_string(resources->particleVertices->elements()) +
|
||||
" fullParticleDraw=true resourceGeneration=" + std::to_string(resources->generation));
|
||||
}
|
||||
|
||||
const bool sourceChanged = formalSource && sourceField && sourceNextField &&
|
||||
@@ -1028,6 +1146,15 @@ void RenderRasterParticleLayer::update(gfx::ShaderRegistry& shaders,
|
||||
0,
|
||||
maskMode,
|
||||
true);
|
||||
const bool particlesBuilt = !resources->formalDisplay ||
|
||||
buildParticlePoints(context,
|
||||
resources,
|
||||
trailGroup,
|
||||
getID() + "-static-bootstrap-trail-particles",
|
||||
resources->stateTargets[stateWrite]->getTexture(),
|
||||
resources->trailTargets[trailWrite]->getTexture(),
|
||||
stateWrite,
|
||||
trailWrite);
|
||||
compositeGroup.clearDrawables();
|
||||
bool compositeBuilt = true;
|
||||
const auto center = transformState.getLatLng(LatLng::Unwrapped);
|
||||
@@ -1050,7 +1177,7 @@ void RenderRasterParticleLayer::update(gfx::ShaderRegistry& shaders,
|
||||
false) &&
|
||||
compositeBuilt;
|
||||
}
|
||||
if (!stateBuilt || !trailBuilt || !compositeBuilt) {
|
||||
if (!stateBuilt || !trailBuilt || !particlesBuilt || !compositeBuilt) {
|
||||
compositeGroup.clearDrawables();
|
||||
return;
|
||||
}
|
||||
@@ -1136,6 +1263,15 @@ void RenderRasterParticleLayer::update(gfx::ShaderRegistry& shaders,
|
||||
0,
|
||||
maskMode,
|
||||
true);
|
||||
const bool particlesBuilt = !resources->formalDisplay ||
|
||||
buildParticlePoints(context,
|
||||
resources,
|
||||
trailGroup,
|
||||
getID() + "-trail-particles",
|
||||
resources->stateTargets[stateWrite]->getTexture(),
|
||||
resources->trailTargets[trailWrite]->getTexture(),
|
||||
stateWrite,
|
||||
trailWrite);
|
||||
compositeGroup.clearDrawables();
|
||||
bool compositeBuilt = true;
|
||||
const auto center = transformState.getLatLng(LatLng::Unwrapped);
|
||||
@@ -1158,7 +1294,7 @@ void RenderRasterParticleLayer::update(gfx::ShaderRegistry& shaders,
|
||||
false) &&
|
||||
compositeBuilt;
|
||||
}
|
||||
if (!stateBuilt || !trailBuilt || !compositeBuilt) {
|
||||
if (!stateBuilt || !trailBuilt || !particlesBuilt || !compositeBuilt) {
|
||||
compositeGroup.clearDrawables();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -266,3 +266,11 @@
|
||||
- 日志包含请求/实际 count、State grid、State target create/rebuild/release 和拒绝原因;Trail 继续用四个确定性 State texel作低开销可视健康指示,实际并行 simulation 数仍是整个 State 网格。
|
||||
- Linux OpenGL 受影响 Core 和新增 Layer test object 精确编译、C++ format、diff check 均通过;未运行 Android Gradle、ADB、APK 或设备,也未加入 Jest。
|
||||
- 当前状态只能是 `IMPLEMENTED / WAITING FOR ANDROID VALIDATION`。用户将以 x86_64 Android OpenGL 逐档填写容量矩阵后,才能确定稳定区间、性能下降区间和压力边界。
|
||||
|
||||
## 2026-09-03 — 40-03 Full Particle Draw 补齐(STEP-044 v2)
|
||||
|
||||
- 暂停 v1 容量验收的根因已补齐:v1 的 State A/B 虽可容纳 N 个 State texel,但 Trail fragment 只读取四个固定 sample,实际 Trail draw 只是全屏 quad,不能证明 N 个粒子已绘制。
|
||||
- 正式 RasterParticle Trail 现在先保留既有全屏 fade,再在同一 Trail A/B write target 绘制 N 个 point。particle index 在资源创建/count change 时一次性映射到 State texel `(x,y)`;vertex shader 每帧直接从当前 State write texture fetch RG position,Composite 仍消费 Trail write。没有 CPU readback、每帧 CPU position loop、第二 simulation 或 overlay。
|
||||
- 16-bit index buffer 每 segment 最多 65,536 point;1M 使用多个 segment,但继续走同一 State→Trail→Composite GPU path。日志明确输出 `trailDrawCount`、`particleIndexCount` 与 `fullParticleDraw=true`;legacy validation 的四 sample 诊断保留且不再限制正式层。
|
||||
- U/V、Mercator `dy=-v`、T0/T1、MIX、Field reuse、seed/lifecycle 和 Android JNI 均未改变。Linux OpenGL 精确 Core/test object 编译、shader generator 二次幂等、format 与 diff check 通过;未运行 Android Gradle、ADB、设备或 Jest。
|
||||
- 状态继续为 `IMPLEMENTED / WAITING FOR ANDROID VALIDATION`,用户下一轮只验收 256、1K、256→1K→256 的 full draw 正确性,之后才恢复完整容量矩阵。
|
||||
|
||||
Reference in New Issue
Block a user