feat: establish formal raster particle source chain
This commit is contained in:
@@ -249,6 +249,7 @@ list(APPEND INCLUDE_FILES
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/gfx/vertex_buffer.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/layermanager/background_layer_factory.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/layermanager/particle_validation_layer_factory.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/layermanager/raster_particle_layer_factory.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/layermanager/circle_layer_factory.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/layermanager/custom_layer_factory.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/layermanager/fill_extrusion_layer_factory.hpp
|
||||
@@ -361,6 +362,8 @@ list(APPEND INCLUDE_FILES
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/style/layer.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/style/layers/background_layer.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/style/layers/particle_validation_layer.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/style/layers/raster_particle_layer.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/style/sources/raster_array_source.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/style/layers/circle_layer.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/style/layers/custom_layer_host.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/style/layers/custom_layer_init_parameters.hpp
|
||||
@@ -506,6 +509,7 @@ list(APPEND SRC_FILES
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/gfx/vertex_vector.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/layermanager/background_layer_factory.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/layermanager/particle_validation_layer_factory.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/layermanager/raster_particle_layer_factory.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/layermanager/circle_layer_factory.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/layermanager/fill_extrusion_layer_factory.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/layermanager/fill_layer_factory.cpp
|
||||
@@ -585,6 +589,7 @@ list(APPEND SRC_FILES
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/render_background_layer.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/render_particle_validation_layer.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/render_particle_validation_layer.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/render_raster_particle_layer.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/render_circle_layer.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/render_circle_layer.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/render_fill_extrusion_layer.cpp
|
||||
@@ -756,6 +761,9 @@ list(APPEND SRC_FILES
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/background_layer.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/particle_validation_layer.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/particle_validation_layer_impl.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/particle_render_layer_properties.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/raster_particle_layer.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/raster_particle_layer_impl.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/background_layer_impl.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/background_layer_impl.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/background_layer_properties.cpp
|
||||
@@ -833,6 +841,8 @@ list(APPEND SRC_FILES
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/style/sources/raster_dem_source.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/style/sources/tile_source.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/style/sources/raster_source.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/style/sources/raster_array_source.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/style/sources/raster_array_source_impl.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/style/sources/tile_source_impl.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/style/sources/tile_source_impl.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/style/sources/vector_source.cpp
|
||||
|
||||
39
docs/50_Step记录/STEP-040_Android离线Fixture正式粒子最小闭环_v1.md
Normal file
39
docs/50_Step记录/STEP-040_Android离线Fixture正式粒子最小闭环_v1.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# STEP-040 Android 离线 Fixture 正式粒子最小闭环 v1
|
||||
|
||||
## 状态
|
||||
|
||||
进行中。40-01 Stage A 的代码和 Linux 静态验证完成;最终 Android 状态固定为 `WAITING_MANUAL_VALIDATION`,尚未进入手工 APK 验收。
|
||||
|
||||
## 目标与基线
|
||||
|
||||
- 目标:建立 `RasterArraySource -> RenderRasterArraySource -> RasterParticleLayer -> RenderRasterParticleLayer` 的第一条正式离线粒子闭环,并修复 STATIC recreation 的零步 bootstrap。
|
||||
- 基线:`343cecc778c3ffaa00adf94f82d7ad5f494a3877`,分支 `particle/main`。
|
||||
- 前置:30-01、30-02、30-03 均为 PASS;EGL context recreation 的独立实机证据仍是已接受风险。
|
||||
|
||||
## 必做项
|
||||
|
||||
- [x] `RasterArraySource` 以 FileSource byte boundary 读取 fixture metadata 与 field,生成 immutable `ParticleFieldSnapshot`。
|
||||
- [x] `RenderRasterArraySource`、`RasterParticleLayer`(`Source::Required`)和 `RenderRasterParticleLayer` 进入 Style/RenderTree 链。
|
||||
- [x] 正式 RenderLayer 复用既有 GPU field、State A/B、Trail A/B、mask、camera/world-wrap/antimeridian 与 resource lifecycle;没有第二套 simulation。
|
||||
- [x] Android Test App 从 APK `asset://integration/particle-fixtures/...` 提供正式 source 输入;Linux smoke helper 仍可读取仓库 fixture。
|
||||
- [x] NoData cell 作为 GPU field texture alpha=0 上传;shader 遇到 invalid sample 确定性回到 seed position,不把 NoData 当速度。
|
||||
- [x] STATIC recreation 仍走 zero-step State/Trail/bootstrap composite,commit 不增加 `simulationFrame`。
|
||||
- [ ] Android OpenGL APK 手工编译与 T1-T12:仅 GPT 复审远端代码通过后由用户执行。
|
||||
|
||||
## 允许修改
|
||||
|
||||
隔离 particle field/source/layer/render source/render layer、最小 LayerManager/Style Source/shader 注册、Android Test App fixture assets 与本 Step 文档。
|
||||
|
||||
## 禁止修改
|
||||
|
||||
不得运行 Gradle、安装 APK、ADB/T1-T12;不得新增 Overlay、Android UI/Core 耦合、在线数据、第二套 GPU simulation,或声称 Android/Vulkan/iOS PASS。
|
||||
|
||||
## 构建与检查
|
||||
|
||||
- Linux OpenGL 受影响 Core 精确对象编译:PASS。
|
||||
- `clang-format --dry-run --Werror`、`git diff --check`、fixture generator:待本轮最终收尾复跑。
|
||||
- adapter smoke:`uniform-east`、`uniform-north`、`vortex` 均 valid;`nodata` valid 且有 1 个 invalid cell。
|
||||
|
||||
## 当前结果、阻塞与下一步
|
||||
|
||||
正式 Source/Layer/RenderLayer 已不依赖 source-less `particle-validation` Layer;历史 validation Layer 只保留为既有验证工具。唯一阻塞是按用户指令等待 GPT 复审新远端 commit;复审通过后才允许用户开始 Android OpenGL APK 手工验收。
|
||||
@@ -9,23 +9,21 @@
|
||||
- 分支:`particle/main`
|
||||
- 开始 HEAD:`1ec44701146925dec9a87618e3f90bee9a12073f`
|
||||
- 平台范围:Android OpenGL;未修改 iOS、Vulkan、NavSea 或在线数据链。
|
||||
- Test App 继续使用既有 `Particle Core Layer Validation` 入口;Core adapter 不依赖 Kotlin/Android UI。
|
||||
- Test App 继续使用既有 `Particle Core Layer Validation` 入口,但其 Style 现在创建正式 `raster-array` source 和 `raster-particle` layer;Core adapter 不依赖 Kotlin/Android UI 或 Android asset API。
|
||||
|
||||
## 已实现的 Stage A 链路
|
||||
|
||||
```text
|
||||
particle-fixtures/{uniform-east,uniform-north,vortex}
|
||||
-> OfflineFixtureParticleAdapter
|
||||
-> ParticleFieldSnapshot
|
||||
-> GPU field texture
|
||||
-> State A/B field sampling
|
||||
-> Trail A/B consuming State A/B
|
||||
-> canonical world composite / shared coastal mask
|
||||
APK asset://integration/particle-fixtures/{uniform-east,uniform-north}/
|
||||
-> RasterArraySource -> RenderRasterArraySource
|
||||
-> ParticleFieldSnapshot -> GPU field texture
|
||||
-> RasterParticleLayer (Source::Required) -> RenderRasterParticleLayer
|
||||
-> State A/B field sampling -> Trail A/B -> canonical world composite / shared coastal mask
|
||||
```
|
||||
|
||||
- fixture contract 使用 `metadata.json + field.f32`,校验 schema、id、WIND/GLOBAL、EPSG:4326、bounds、grid registration/order、interleaved UV、little-endian float32、NoData 声明、长度和 SHA-256。
|
||||
- fixture contract 使用 `metadata.json + field.f32`,校验 schema、id、WIND/GLOBAL、EPSG:4326、bounds、grid registration/order、interleaved UV、little-endian float32、NoData 声明、长度和 SHA-256。Android asset 由 Test App 打包,Core 只接收 FileSource 返回的 metadata/field bytes;Linux helper 才读取仓库 `particle-fixtures/`。
|
||||
- `uniform-east`、`uniform-north` 和 `vortex` 均为确定性 16x16 fixture;不会在每帧解析或上传。切换 fixture 时递增 `fieldGeneration/gpuFieldGeneration`,清空旧 trail;Camera 操作不触发 field upload。
|
||||
- shader 的 State pass 实际采样 GPU field texture,Trail pass 从 State write texture 读取粒子位置,未使用 Kotlin/CPU 每帧粒子循环。
|
||||
- shader 的 State pass 实际采样 GPU field texture,Trail pass 从 State write texture 读取粒子位置,未使用 Kotlin/CPU 每帧粒子循环。field texture alpha 表示 cell validity;NoData cell 上传为 alpha=0,State pass 在该 sample 上确定性 respawn 到 seed position,绝不将 `-9999` clamp 成速度。
|
||||
- STATIC 不再因资源缺失提前返回。新的 context/resource generation 会建立 field、mask、trail 和 state;STATIC 首帧走零步 bootstrap,提交 state/trail slot 但不增加 `simulationFrame`,之后保持 frozen composite。
|
||||
- Activity Bundle 保存/恢复 active、enabled、mask mode 与 fixture mode;GPU runtime state 不写入 Bundle。
|
||||
|
||||
@@ -33,11 +31,12 @@ particle-fixtures/{uniform-east,uniform-north,vortex}
|
||||
|
||||
| 检查 | 结果 |
|
||||
|---|---|
|
||||
| `ninja -C build-linux-opengl` 受影响 Core 对象 | PASS:`offline_fixture_particle_adapter.cpp`、`render_particle_validation_layer.cpp`、`shader_info.cpp`、`particle_validation_layer.cpp` |
|
||||
| `ninja -C build-linux-opengl` 受影响 Core 对象 | PASS:正式 `raster_array_source.cpp`、`raster_particle_layer.cpp`、`raster_particle_layer_factory.cpp`、`render_source.cpp`、`RenderRasterParticleLayer` 实现、adapter 与 source conversion |
|
||||
| `clang-format --dry-run --Werror` | PASS |
|
||||
| `git diff --check` | PASS |
|
||||
| `python3 tools/generate_particle_fixture_f32.py` 后 metadata JSON 与 SHA-256 | PASS |
|
||||
| adapter smoke test | PASS:`uniform-east`、`uniform-north`、`vortex` 分别加载为 16x16 |
|
||||
| adapter smoke test | PASS:`uniform-east`、`uniform-north`、`vortex` 分别加载为 16x16;`nodata` 为合法 16x16 field 且恰有 1 个 invalid cell |
|
||||
| APK asset parity | PASS:四份 Android asset 的 metadata 与 field bytes 均与仓库 fixture 完全相同 |
|
||||
|
||||
首次整库构建尝试被 sandbox 默认 ccache 目录只读中断;以 `CCACHE_DIR=/tmp/maplibre-particle-ccache` 重新执行上述受影响对象检查后通过。这不是代码编译失败。
|
||||
|
||||
@@ -57,7 +56,7 @@ cd platform/android
|
||||
- T1-T12 Android 设备验收、截图和 OpenGL shader 实机链接尚未执行。
|
||||
- 独立 EGL context-loss 设备证据沿用 30-02 的 Accepted Risk;本实现已按 resource generation 重建 CPU field 的 GPU texture。
|
||||
- iOS、Metal、Android Vulkan、真实数据、生产 coastline/mask、最终粒子数和性能指标均不在本 Stage A 范围。
|
||||
- fixture 含 NoData cell 的实机路径尚未有单独 fixture;adapter 已校验 NoData contract,当前三份 deterministic field 均无 NoData cell。
|
||||
- Android T1-T12 和实机 OpenGL shader 链接尚未执行;NoData 已有 deterministic fixture/static smoke 覆盖,但没有实机视觉证据。
|
||||
|
||||
## 下一步
|
||||
|
||||
|
||||
15
include/mbgl/layermanager/raster_particle_layer_factory.hpp
Normal file
15
include/mbgl/layermanager/raster_particle_layer_factory.hpp
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/layermanager/layer_factory.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
class RasterParticleLayerFactory final : public LayerFactory {
|
||||
public:
|
||||
const style::LayerTypeInfo* getTypeInfo() const noexcept override;
|
||||
std::unique_ptr<style::Layer> createLayer(const std::string&,
|
||||
const style::conversion::Convertible&) noexcept override;
|
||||
std::unique_ptr<RenderLayer> createRenderLayer(Immutable<style::Layer::Impl>) noexcept override;
|
||||
};
|
||||
|
||||
} // namespace mbgl
|
||||
@@ -56,7 +56,12 @@ void main() {
|
||||
// texture encodes canonical U-east/V-north at that geographic sample.
|
||||
vec2 position = previous.rg;
|
||||
if (previous.a < 0.5) position = v_uv;
|
||||
vec2 velocity = texture(u_field_texture, position).rg * 2.0 - 1.0;
|
||||
vec4 field = texture(u_field_texture, position);
|
||||
vec2 velocity = field.rg * 2.0 - 1.0;
|
||||
if (field.a < 0.5) {
|
||||
position = v_uv;
|
||||
velocity = vec2(0.0);
|
||||
}
|
||||
position = fract(position + velocity * u_pass.y * 0.004);
|
||||
if (any(isnan(velocity))) position = v_uv;
|
||||
fragColor = vec4(position, fract(previous.b + 0.03125 * u_pass.y), 1.0);
|
||||
|
||||
28
include/mbgl/style/layers/raster_particle_layer.hpp
Normal file
28
include/mbgl/style/layers/raster_particle_layer.hpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/style/layer.hpp>
|
||||
|
||||
namespace mbgl::style {
|
||||
|
||||
class RasterParticleLayer final : public Layer {
|
||||
public:
|
||||
RasterParticleLayer(const std::string& layerID, const std::string& sourceID);
|
||||
~RasterParticleLayer() override;
|
||||
bool isActive() const;
|
||||
void setActive(bool);
|
||||
uint8_t getMaskMode() const;
|
||||
void setMaskMode(uint8_t);
|
||||
class Impl;
|
||||
const Impl& impl() const;
|
||||
Mutable<Impl> mutableImpl() const;
|
||||
|
||||
private:
|
||||
explicit RasterParticleLayer(Immutable<Impl>);
|
||||
std::optional<conversion::Error> setPropertyInternal(const std::string&, const conversion::Convertible&) override;
|
||||
StyleProperty getProperty(const std::string&) const override;
|
||||
Value serialize() const override;
|
||||
std::unique_ptr<Layer> cloneRef(const std::string&) const override;
|
||||
Mutable<Layer::Impl> mutableBaseImpl() const override;
|
||||
};
|
||||
|
||||
} // namespace mbgl::style
|
||||
43
include/mbgl/style/sources/raster_array_source.hpp
Normal file
43
include/mbgl/style/sources/raster_array_source.hpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/style/source.hpp>
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace mbgl {
|
||||
class AsyncRequest;
|
||||
|
||||
namespace style {
|
||||
|
||||
class RasterArraySource final : public Source {
|
||||
public:
|
||||
RasterArraySource(std::string id, std::string fixtureID, std::string metadataURL, std::string fieldURL);
|
||||
~RasterArraySource() override;
|
||||
|
||||
class Impl;
|
||||
const Impl& impl() const;
|
||||
void loadDescription(FileSource&) override;
|
||||
bool supportsLayerType(const LayerTypeInfo*) const override;
|
||||
mapbox::base::WeakPtr<Source> makeWeakPtr() override { return weakFactory.makeWeakPtr(); }
|
||||
|
||||
protected:
|
||||
Mutable<Source::Impl> createMutable() const noexcept override;
|
||||
|
||||
private:
|
||||
void tryLoad();
|
||||
|
||||
std::unique_ptr<AsyncRequest> metadataRequest;
|
||||
std::unique_ptr<AsyncRequest> fieldRequest;
|
||||
std::optional<std::string> metadata;
|
||||
std::optional<std::vector<char>> fieldBytes;
|
||||
uint64_t generation = 0;
|
||||
mapbox::base::WeakPtrFactory<Source> weakFactory{this};
|
||||
};
|
||||
|
||||
template <>
|
||||
inline bool Source::is<RasterArraySource>() const {
|
||||
return getType() == SourceType::RasterArray;
|
||||
}
|
||||
|
||||
} // namespace style
|
||||
} // namespace mbgl
|
||||
@@ -15,7 +15,8 @@ enum class SourceType : uint8_t {
|
||||
Video,
|
||||
Annotations,
|
||||
Image,
|
||||
CustomVector
|
||||
CustomVector,
|
||||
RasterArray
|
||||
};
|
||||
|
||||
enum class VisibilityType : bool {
|
||||
|
||||
BIN
particle-fixtures/nodata/field.f32
Normal file
BIN
particle-fixtures/nodata/field.f32
Normal file
Binary file not shown.
15
particle-fixtures/nodata/metadata.json
Normal file
15
particle-fixtures/nodata/metadata.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"schema": "maplibre-particle-fixture/v1",
|
||||
"fixtureId": "nodata",
|
||||
"fieldType": "WIND",
|
||||
"displayDomain": "GLOBAL",
|
||||
"dataVersion": 1,
|
||||
"timeIndex": 0,
|
||||
"timestamp": null,
|
||||
"bounds": {"west": -180.0, "south": -85.0, "east": 180.0, "north": 85.0},
|
||||
"crs": "EPSG:4326",
|
||||
"grid": {"width": 16, "height": 16, "registration": "node", "origin": "north-west", "rowOrder": "north-to-south", "columnOrder": "west-to-east"},
|
||||
"vector": {"uPositive": "east", "vPositive": "north", "unit": "m/s", "layout": "interleaved-uv", "encoding": "float32-le", "noData": -9999.0, "file": "field.f32"},
|
||||
"mask": {"mode": "none"},
|
||||
"integrity": {"fieldByteLength": 2048, "fieldSha256": "5756a6ac403a435ff001ff7261160de718c4a2964434ba7b9be2bfe74f071ff0"}
|
||||
}
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "fill_extrusion_layer.hpp"
|
||||
#include "location_indicator_layer.hpp"
|
||||
#include <mbgl/layermanager/particle_validation_layer_factory.hpp>
|
||||
#include <mbgl/layermanager/raster_particle_layer_factory.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
@@ -28,6 +29,7 @@ namespace android {
|
||||
LayerManagerAndroid::LayerManagerAndroid() {
|
||||
// VALIDATION ONLY: JSON style access; intentionally no Android product API/JNI peer.
|
||||
addLayerTypeCoreOnly(std::make_unique<ParticleValidationLayerFactory>());
|
||||
addLayerTypeCoreOnly(std::make_unique<RasterParticleLayerFactory>());
|
||||
#if defined(MBGL_LAYER_FILL_DISABLE_RUNTIME)
|
||||
addLayerTypeCoreOnly(std::make_unique<FillLayerFactory>());
|
||||
#elif !defined(MBGL_LAYER_FILL_DISABLE_ALL)
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"schema": "maplibre-particle-fixture/v1",
|
||||
"fixtureId": "nodata",
|
||||
"fieldType": "WIND",
|
||||
"displayDomain": "GLOBAL",
|
||||
"dataVersion": 1,
|
||||
"timeIndex": 0,
|
||||
"timestamp": null,
|
||||
"bounds": {"west": -180.0, "south": -85.0, "east": 180.0, "north": 85.0},
|
||||
"crs": "EPSG:4326",
|
||||
"grid": {"width": 16, "height": 16, "registration": "node", "origin": "north-west", "rowOrder": "north-to-south", "columnOrder": "west-to-east"},
|
||||
"vector": {"uPositive": "east", "vPositive": "north", "unit": "m/s", "layout": "interleaved-uv", "encoding": "float32-le", "noData": -9999.0, "file": "field.f32"},
|
||||
"mask": {"mode": "none"},
|
||||
"integrity": {"fieldByteLength": 2048, "fieldSha256": "5756a6ac403a435ff001ff7261160de718c4a2964434ba7b9be2bfe74f071ff0"}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"schema": "maplibre-particle-fixture/v1",
|
||||
"fixtureId": "uniform-east",
|
||||
"fieldType": "WIND",
|
||||
"displayDomain": "GLOBAL",
|
||||
"dataVersion": 1,
|
||||
"timeIndex": 0,
|
||||
"timestamp": null,
|
||||
"bounds": {"west": -180.0, "south": -85.0, "east": 180.0, "north": 85.0},
|
||||
"crs": "EPSG:4326",
|
||||
"grid": {"width": 16, "height": 16, "registration": "node", "origin": "north-west", "rowOrder": "north-to-south", "columnOrder": "west-to-east"},
|
||||
"vector": {"uPositive": "east", "vPositive": "north", "unit": "m/s", "layout": "interleaved-uv", "encoding": "float32-le", "noData": -9999.0, "file": "field.f32"},
|
||||
"mask": {"mode": "none"},
|
||||
"integrity": {"fieldByteLength": 2048, "fieldSha256": "0da648fdcf61b5ea6383960583bdc74287c3fa758c6e5a7eb7d234f2d7c3283f"}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"schema": "maplibre-particle-fixture/v1",
|
||||
"fixtureId": "uniform-north",
|
||||
"fieldType": "WIND",
|
||||
"displayDomain": "GLOBAL",
|
||||
"dataVersion": 1,
|
||||
"timeIndex": 0,
|
||||
"timestamp": null,
|
||||
"bounds": {"west": -180.0, "south": -85.0, "east": 180.0, "north": 85.0},
|
||||
"crs": "EPSG:4326",
|
||||
"grid": {"width": 16, "height": 16, "registration": "node", "origin": "north-west", "rowOrder": "north-to-south", "columnOrder": "west-to-east"},
|
||||
"vector": {"uPositive": "east", "vPositive": "north", "unit": "m/s", "layout": "interleaved-uv", "encoding": "float32-le", "noData": -9999.0, "file": "field.f32"},
|
||||
"mask": {"mode": "none"},
|
||||
"integrity": {"fieldByteLength": 2048, "fieldSha256": "20eb0da1812e616cee1740ee8e22a7bda377d6785cc6e7007fb26afa5ab255bc"}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"schema": "maplibre-particle-fixture/v1",
|
||||
"fixtureId": "vortex",
|
||||
"fieldType": "WIND",
|
||||
"displayDomain": "GLOBAL",
|
||||
"dataVersion": 1,
|
||||
"timeIndex": 0,
|
||||
"timestamp": null,
|
||||
"bounds": {"west": -180.0, "south": -85.0, "east": 180.0, "north": 85.0},
|
||||
"crs": "EPSG:4326",
|
||||
"grid": {"width": 16, "height": 16, "registration": "node", "origin": "north-west", "rowOrder": "north-to-south", "columnOrder": "west-to-east"},
|
||||
"vector": {"uPositive": "east", "vPositive": "north", "unit": "m/s", "layout": "interleaved-uv", "encoding": "float32-le", "noData": -9999.0, "file": "field.f32"},
|
||||
"mask": {"mode": "none"},
|
||||
"integrity": {"fieldByteLength": 2048, "fieldSha256": "aa7fd26d5e4605dc363636b99e17c6bb495d51350a5a2cd8abe5bf0ada741c5a"}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import org.maplibre.android.maps.Style
|
||||
import org.maplibre.android.style.layers.PaintPropertyValue
|
||||
import org.maplibre.android.testapp.R
|
||||
|
||||
/** VALIDATION ONLY: Android OpenGL host for the 30-01/30-02 Core RenderLayer experiments. */
|
||||
/** Android OpenGL host for the formal raster-array to raster-particle fixture path. */
|
||||
class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
private lateinit var mapView: MapView
|
||||
private lateinit var maplibreMap: MapLibreMap
|
||||
@@ -72,10 +72,12 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
if (this::maplibreMap.isInitialized) setValidationProperties(active = active, maskMode = MASK_OCEAN_B)
|
||||
}
|
||||
findViewById<Button>(R.id.validationUniformEast).setOnClickListener {
|
||||
if (this::maplibreMap.isInitialized) setValidationProperties(active, maskMode, FIXTURE_UNIFORM_EAST)
|
||||
fixtureMode = FIXTURE_UNIFORM_EAST
|
||||
if (this::maplibreMap.isInitialized) loadValidationStyle(active, enabled)
|
||||
}
|
||||
findViewById<Button>(R.id.validationUniformNorth).setOnClickListener {
|
||||
if (this::maplibreMap.isInitialized) setValidationProperties(active, maskMode, FIXTURE_UNIFORM_NORTH)
|
||||
fixtureMode = FIXTURE_UNIFORM_NORTH
|
||||
if (this::maplibreMap.isInitialized) loadValidationStyle(active, enabled)
|
||||
}
|
||||
findViewById<Button>(R.id.validationDateline).setOnClickListener {
|
||||
maplibreMap.cameraPosition = CameraPosition.Builder().target(LatLng(0.0, 179.0)).zoom(2.0).build()
|
||||
@@ -107,9 +109,8 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
return
|
||||
}
|
||||
layer.setProperties(
|
||||
PaintPropertyValue("particle-validation-active", active),
|
||||
PaintPropertyValue("particle-validation-mask-mode", maskMode),
|
||||
PaintPropertyValue("particle-fixture-mode", fixtureMode)
|
||||
PaintPropertyValue("raster-particle-active", active),
|
||||
PaintPropertyValue("raster-particle-mask-mode", maskMode)
|
||||
)
|
||||
status.text = statusText()
|
||||
Log.i(TAG, "ParticleRender: property update active=$active maskMode=$maskMode fixtureMode=$fixtureMode")
|
||||
@@ -124,9 +125,10 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private fun validationStyle(active: Boolean, enabled: Boolean): String {
|
||||
val fixtureId = fixtureId()
|
||||
val particleLayer = if (enabled) {
|
||||
"""{"id":"particle-validation-core","type":"particle-validation",
|
||||
"paint":{"particle-validation-active":$active,"particle-validation-mask-mode":$maskMode,"particle-fixture-mode":$fixtureMode}},"""
|
||||
"""{"id":"particle-raster-core","type":"raster-particle","source":"particle-fixture",
|
||||
"paint":{"raster-particle-active":$active,"raster-particle-mask-mode":$maskMode}},"""
|
||||
} else {
|
||||
""
|
||||
}
|
||||
@@ -135,6 +137,12 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
"version": 8,
|
||||
"name": "Particle Core Layer Validation",
|
||||
"sources": {
|
||||
"particle-fixture": {
|
||||
"type": "raster-array",
|
||||
"fixture": "$fixtureId",
|
||||
"metadata": "asset://integration/particle-fixtures/$fixtureId/metadata.json",
|
||||
"field": "asset://integration/particle-fixtures/$fixtureId/field.f32"
|
||||
},
|
||||
"validation-point": {
|
||||
"type": "geojson",
|
||||
"data": {"type":"Feature","geometry":{"type":"Point","coordinates":[0,0]}}
|
||||
@@ -168,13 +176,18 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
mapView.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
private fun fixtureId(): String = when (fixtureMode) {
|
||||
FIXTURE_UNIFORM_NORTH -> "uniform-north"
|
||||
else -> "uniform-east"
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "ParticleValidation"
|
||||
private const val STATE_ACTIVE = "particle_validation.active"
|
||||
private const val STATE_ENABLED = "particle_validation.enabled"
|
||||
private const val STATE_MASK_MODE = "particle_validation.mask_mode"
|
||||
private const val STATE_FIXTURE_MODE = "particle_validation.fixture_mode"
|
||||
private const val PARTICLE_LAYER_ID = "particle-validation-core"
|
||||
private const val PARTICLE_LAYER_ID = "particle-raster-core"
|
||||
private const val MASK_GLOBAL = 0
|
||||
private const val MASK_OCEAN_A = 1
|
||||
private const val MASK_OCEAN_B = 2
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <mbgl/layermanager/line_layer_factory.hpp>
|
||||
#include <mbgl/layermanager/location_indicator_layer_factory.hpp>
|
||||
#include <mbgl/layermanager/particle_validation_layer_factory.hpp>
|
||||
#include <mbgl/layermanager/raster_particle_layer_factory.hpp>
|
||||
#include <mbgl/layermanager/raster_layer_factory.hpp>
|
||||
#include <mbgl/layermanager/symbol_layer_factory.hpp>
|
||||
#include <mbgl/util/logging.hpp>
|
||||
@@ -52,6 +53,7 @@ void LayerManagerDefault::addLayerTypeCoreOnly(std::unique_ptr<mbgl::LayerFactor
|
||||
LayerManagerDefault::LayerManagerDefault() {
|
||||
// VALIDATION ONLY: 30-01 source-less Core RenderLayer experiment.
|
||||
addLayerType(std::make_unique<ParticleValidationLayerFactory>());
|
||||
addLayerType(std::make_unique<RasterParticleLayerFactory>());
|
||||
#if !defined(MBGL_LAYER_FILL_DISABLE_ALL)
|
||||
addLayerType(std::make_unique<FillLayerFactory>());
|
||||
#endif
|
||||
|
||||
@@ -22,7 +22,12 @@ void main() {
|
||||
// texture encodes canonical U-east/V-north at that geographic sample.
|
||||
vec2 position = previous.rg;
|
||||
if (previous.a < 0.5) position = v_uv;
|
||||
vec2 velocity = texture(u_field_texture, position).rg * 2.0 - 1.0;
|
||||
vec4 field = texture(u_field_texture, position);
|
||||
vec2 velocity = field.rg * 2.0 - 1.0;
|
||||
if (field.a < 0.5) {
|
||||
position = v_uv;
|
||||
velocity = vec2(0.0);
|
||||
}
|
||||
position = fract(position + velocity * u_pass.y * 0.004);
|
||||
if (any(isnan(velocity))) position = v_uv;
|
||||
fragColor = vec4(position, fract(previous.b + 0.03125 * u_pass.y), 1.0);
|
||||
|
||||
@@ -18,8 +18,7 @@ std::unique_ptr<style::Layer> ParticleValidationLayerFactory::createLayer(
|
||||
std::unique_ptr<RenderLayer> ParticleValidationLayerFactory::createRenderLayer(
|
||||
Immutable<style::Layer::Impl> impl) noexcept {
|
||||
assert(impl->getTypeInfo() == getTypeInfo());
|
||||
return std::make_unique<RenderParticleValidationLayer>(
|
||||
staticImmutableCast<style::ParticleValidationLayer::Impl>(impl));
|
||||
return std::make_unique<RenderParticleValidationLayer>(std::move(impl));
|
||||
}
|
||||
|
||||
} // namespace mbgl
|
||||
|
||||
25
src/mbgl/layermanager/raster_particle_layer_factory.cpp
Normal file
25
src/mbgl/layermanager/raster_particle_layer_factory.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#include <mbgl/layermanager/raster_particle_layer_factory.hpp>
|
||||
|
||||
#include <mbgl/renderer/layers/render_raster_particle_layer.hpp>
|
||||
#include <mbgl/style/layers/raster_particle_layer.hpp>
|
||||
#include <mbgl/style/layers/raster_particle_layer_impl.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
const style::LayerTypeInfo* RasterParticleLayerFactory::getTypeInfo() const noexcept {
|
||||
return style::RasterParticleLayer::Impl::staticTypeInfo();
|
||||
}
|
||||
|
||||
std::unique_ptr<style::Layer> RasterParticleLayerFactory::createLayer(
|
||||
const std::string& id, const style::conversion::Convertible& value) noexcept {
|
||||
const auto source = getSource(value);
|
||||
return source ? std::make_unique<style::RasterParticleLayer>(id, *source) : nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<RenderLayer> RasterParticleLayerFactory::createRenderLayer(
|
||||
Immutable<style::Layer::Impl> impl) noexcept {
|
||||
assert(impl->getTypeInfo() == getTypeInfo());
|
||||
return std::make_unique<RenderRasterParticleLayer>(std::move(impl));
|
||||
}
|
||||
|
||||
} // namespace mbgl
|
||||
@@ -14,7 +14,8 @@ namespace mbgl::particle {
|
||||
namespace {
|
||||
|
||||
bool knownFixture(const std::string& fixtureID) {
|
||||
return fixtureID == "uniform-east" || fixtureID == "uniform-north" || fixtureID == "vortex";
|
||||
return fixtureID == "uniform-east" || fixtureID == "uniform-north" || fixtureID == "vortex" ||
|
||||
fixtureID == "nodata";
|
||||
}
|
||||
|
||||
std::string readText(const std::filesystem::path& path) {
|
||||
@@ -108,25 +109,28 @@ std::string sha256(const std::vector<char>& input) {
|
||||
|
||||
bool ParticleFieldSnapshot::isValid() const {
|
||||
if (fixtureID.empty() || fieldType != "WIND" || displayDomain != "GLOBAL" || width == 0 || height == 0 ||
|
||||
!(west < east) || !(south < north) || velocity.size() != static_cast<size_t>(width) * height * 2) {
|
||||
!(west < east) || !(south < north) || velocity.size() != static_cast<size_t>(width) * height * 2 ||
|
||||
valid.size() != static_cast<size_t>(width) * height) {
|
||||
return false;
|
||||
}
|
||||
for (const float component : velocity) {
|
||||
if (!std::isfinite(component) || component == noData) {
|
||||
if (!std::isfinite(component)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ParticleFieldSnapshot OfflineFixtureParticleAdapter::load(const std::string& fixtureID, const uint64_t generation) {
|
||||
ParticleFieldSnapshot OfflineFixtureParticleAdapter::load(const std::string& fixtureID,
|
||||
const std::string& metadataText,
|
||||
const std::vector<char>& bytes,
|
||||
const uint64_t generation) {
|
||||
if (!knownFixture(fixtureID)) {
|
||||
throw std::invalid_argument("unknown offline particle fixture: " + fixtureID);
|
||||
}
|
||||
|
||||
const auto directory = std::filesystem::path{"particle-fixtures"} / fixtureID;
|
||||
rapidjson::Document metadata;
|
||||
metadata.Parse(readText(directory / "metadata.json").c_str());
|
||||
metadata.Parse(metadataText.c_str());
|
||||
if (metadata.HasParseError() || !metadata.IsObject() || !member(metadata, "schema").IsString() ||
|
||||
std::string(member(metadata, "schema").GetString()) != "maplibre-particle-fixture/v1" ||
|
||||
!member(metadata, "fixtureId").IsString() ||
|
||||
@@ -174,9 +178,11 @@ ParticleFieldSnapshot OfflineFixtureParticleAdapter::load(const std::string& fix
|
||||
field.east = bounds["east"].GetDouble();
|
||||
field.north = bounds["north"].GetDouble();
|
||||
field.noData = static_cast<float>(vector["noData"].GetDouble());
|
||||
if (!std::isfinite(field.noData)) {
|
||||
throw std::invalid_argument("non-finite fixture noData: " + fixtureID);
|
||||
}
|
||||
field.dataVersion = member(metadata, "dataVersion").GetUint64();
|
||||
field.generation = generation;
|
||||
const auto bytes = readBinary(directory / "field.f32");
|
||||
const size_t expected = static_cast<size_t>(field.width) * field.height * 2 * sizeof(float);
|
||||
if (bytes.size() != expected || member(integrity, "fieldByteLength").GetUint64() != expected) {
|
||||
throw std::invalid_argument("fixture field length mismatch: " + fixtureID);
|
||||
@@ -186,10 +192,27 @@ ParticleFieldSnapshot OfflineFixtureParticleAdapter::load(const std::string& fix
|
||||
}
|
||||
field.velocity.resize(expected / sizeof(float));
|
||||
std::memcpy(field.velocity.data(), bytes.data(), expected);
|
||||
field.valid.resize(static_cast<size_t>(field.width) * field.height);
|
||||
for (size_t cell = 0; cell < field.valid.size(); ++cell) {
|
||||
const float u = field.velocity[cell * 2];
|
||||
const float v = field.velocity[cell * 2 + 1];
|
||||
if (!std::isfinite(u) || !std::isfinite(v)) {
|
||||
throw std::invalid_argument("non-finite fixture velocity: " + fixtureID);
|
||||
}
|
||||
field.valid[cell] = (u == field.noData || v == field.noData) ? 0 : 1;
|
||||
}
|
||||
if (!field.isValid()) {
|
||||
throw std::invalid_argument("invalid offline particle fixture: " + fixtureID);
|
||||
}
|
||||
return field;
|
||||
}
|
||||
|
||||
ParticleFieldSnapshot OfflineFixtureParticleAdapter::load(const std::string& fixtureID, const uint64_t generation) {
|
||||
if (!knownFixture(fixtureID)) {
|
||||
throw std::invalid_argument("unknown offline particle fixture: " + fixtureID);
|
||||
}
|
||||
const auto directory = std::filesystem::path{"particle-fixtures"} / fixtureID;
|
||||
return load(fixtureID, readText(directory / "metadata.json"), readBinary(directory / "field.f32"), generation);
|
||||
}
|
||||
|
||||
} // namespace mbgl::particle
|
||||
|
||||
@@ -24,6 +24,7 @@ struct ParticleFieldSnapshot {
|
||||
// Row-major, little-endian contract decoded as interleaved canonical
|
||||
// U-east/V-north values. A noData component makes the cell invalid.
|
||||
std::vector<float> velocity;
|
||||
std::vector<uint8_t> valid;
|
||||
|
||||
bool isValid() const;
|
||||
};
|
||||
@@ -33,6 +34,14 @@ struct ParticleFieldSnapshot {
|
||||
// used by future Android Vulkan and iOS renderers.
|
||||
class OfflineFixtureParticleAdapter final {
|
||||
public:
|
||||
// Generic loader boundary used by FileSource-backed platform inputs. It
|
||||
// intentionally receives bytes only: no Android asset API crosses Core.
|
||||
static ParticleFieldSnapshot load(const std::string& fixtureID,
|
||||
const std::string& metadata,
|
||||
const std::vector<char>& fieldBytes,
|
||||
uint64_t generation);
|
||||
|
||||
// Linux fixture smoke-test helper. Android must use the byte loader above.
|
||||
static ParticleFieldSnapshot load(const std::string& fixtureID, uint64_t generation);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include <mbgl/renderer/layers/render_particle_validation_layer.hpp>
|
||||
#include <mbgl/renderer/layers/render_raster_particle_layer.hpp>
|
||||
|
||||
#include <mbgl/gfx/color_mode.hpp>
|
||||
#include <mbgl/gfx/context.hpp>
|
||||
@@ -17,10 +17,14 @@
|
||||
#include <mbgl/renderer/render_pass.hpp>
|
||||
#include <mbgl/renderer/render_static_data.hpp>
|
||||
#include <mbgl/renderer/render_target.hpp>
|
||||
#include <mbgl/renderer/sources/render_raster_array_source.hpp>
|
||||
#include <mbgl/shaders/particle_gpu_validation_ubo.hpp>
|
||||
#include <mbgl/shaders/shader_defines.hpp>
|
||||
#include <mbgl/shaders/shader_program_base.hpp>
|
||||
#include <mbgl/style/types.hpp>
|
||||
#include <mbgl/style/layers/raster_particle_layer_impl.hpp>
|
||||
#include <mbgl/style/layers/particle_render_layer_properties.hpp>
|
||||
#include <mbgl/style/layers/particle_validation_layer_impl.hpp>
|
||||
#include <mbgl/tile/tile_id.hpp>
|
||||
#include <mbgl/util/image.hpp>
|
||||
#include <mbgl/util/logging.hpp>
|
||||
@@ -45,9 +49,21 @@ std::atomic_uint64_t nextGpuGeneration{0};
|
||||
std::atomic_uint64_t nextMaskGeneration{0};
|
||||
std::atomic_uint64_t nextFieldGeneration{0};
|
||||
|
||||
const style::ParticleValidationLayer::Impl& impl(const Immutable<style::Layer::Impl>& value) {
|
||||
struct ParticleSettings {
|
||||
bool active;
|
||||
uint8_t maskMode;
|
||||
uint8_t fixtureMode;
|
||||
bool usesFormalSource;
|
||||
};
|
||||
|
||||
ParticleSettings particleSettings(const Immutable<style::Layer::Impl>& value) {
|
||||
if (value->getTypeInfo() == style::RasterParticleLayer::Impl::staticTypeInfo()) {
|
||||
const auto& layer = static_cast<const style::RasterParticleLayer::Impl&>(*value);
|
||||
return {layer.active, layer.maskMode, 0, true};
|
||||
}
|
||||
assert(value->getTypeInfo() == style::ParticleValidationLayer::Impl::staticTypeInfo());
|
||||
return static_cast<const style::ParticleValidationLayer::Impl&>(*value);
|
||||
const auto& layer = static_cast<const style::ParticleValidationLayer::Impl&>(*value);
|
||||
return {layer.active, layer.maskMode, layer.fixtureMode, false};
|
||||
}
|
||||
|
||||
enum class ValidationPass : uint8_t {
|
||||
@@ -87,7 +103,7 @@ std::shared_ptr<PremultipliedImage> makeVelocityFieldImage(const particle::Parti
|
||||
image->data[pixel + 1] = static_cast<uint8_t>(
|
||||
std::clamp(field.velocity[cell + 1] * 0.5f + 0.5f, 0.0f, 1.0f) * 255.0f);
|
||||
image->data[pixel + 2] = 0;
|
||||
image->data[pixel + 3] = 255;
|
||||
image->data[pixel + 3] = field.valid[y * field.width + x] ? 255 : 0;
|
||||
}
|
||||
}
|
||||
return image;
|
||||
@@ -125,7 +141,7 @@ void removeTargets(const std::vector<RenderTargetPtr>& targets, UniqueChangeRequ
|
||||
|
||||
} // namespace
|
||||
|
||||
class ParticleGpuValidationResources {
|
||||
class ParticleGpuResources {
|
||||
public:
|
||||
uint64_t generation = 0;
|
||||
uint64_t committedFrame = 0;
|
||||
@@ -203,7 +219,7 @@ namespace {
|
||||
|
||||
class ParticleGpuValidationTweaker final : public gfx::DrawableTweaker {
|
||||
public:
|
||||
ParticleGpuValidationTweaker(std::shared_ptr<ParticleGpuValidationResources> state_,
|
||||
ParticleGpuValidationTweaker(std::shared_ptr<ParticleGpuResources> state_,
|
||||
ValidationPass pass_,
|
||||
uint8_t readSlot_,
|
||||
uint8_t writeSlot_,
|
||||
@@ -351,7 +367,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
std::weak_ptr<ParticleGpuValidationResources> state;
|
||||
std::weak_ptr<ParticleGpuResources> state;
|
||||
ValidationPass pass;
|
||||
uint8_t readSlot;
|
||||
uint8_t writeSlot;
|
||||
@@ -382,7 +398,7 @@ RenderTargetPtr createValidationTarget(gfx::Context& context,
|
||||
}
|
||||
|
||||
bool buildQuad(gfx::Context& context,
|
||||
const std::shared_ptr<ParticleGpuValidationResources>& resources,
|
||||
const std::shared_ptr<ParticleGpuResources>& resources,
|
||||
LayerGroup& group,
|
||||
const std::string& name,
|
||||
const gfx::Texture2DPtr& source,
|
||||
@@ -448,74 +464,87 @@ bool buildQuad(gfx::Context& context,
|
||||
|
||||
} // namespace
|
||||
|
||||
RenderParticleValidationLayer::RenderParticleValidationLayer(Immutable<style::ParticleValidationLayer::Impl> impl_)
|
||||
: RenderLayer(makeMutable<style::ParticleValidationLayerProperties>(
|
||||
impl_, impl_->active, impl_->maskMode, impl_->fixtureMode)) {
|
||||
Log::Info(
|
||||
Event::Render,
|
||||
"ParticleValidation: RenderLayer create id=" + getID() + " source-less=true pass=translucent stage=30-02");
|
||||
RenderRasterParticleLayer::RenderRasterParticleLayer(Immutable<style::Layer::Impl> impl_)
|
||||
: RenderLayer([&] {
|
||||
const auto settings = particleSettings(impl_);
|
||||
return makeMutable<style::ParticleRenderLayerProperties>(
|
||||
std::move(impl_), settings.active, settings.maskMode, settings.fixtureMode);
|
||||
}()) {
|
||||
formalSource = baseImpl->getTypeInfo() == style::RasterParticleLayer::Impl::staticTypeInfo();
|
||||
Log::Info(Event::Render,
|
||||
"ParticleRender: RenderLayer create id=" + getID() +
|
||||
" source=" + (formalSource ? baseImpl->source : std::string("validation-only")));
|
||||
}
|
||||
|
||||
RenderParticleValidationLayer::~RenderParticleValidationLayer() {
|
||||
if (gpuValidation) {
|
||||
RenderRasterParticleLayer::~RenderRasterParticleLayer() {
|
||||
if (gpuResources) {
|
||||
Log::Info(Event::Render,
|
||||
"ParticleValidation30_02: resources destroy generation=" + std::to_string(gpuValidation->generation) +
|
||||
" contextDestroyed=" + (gpuValidation->contextDestroyed ? "true" : "false"));
|
||||
"ParticleValidation30_02: resources destroy generation=" + std::to_string(gpuResources->generation) +
|
||||
" contextDestroyed=" + (gpuResources->contextDestroyed ? "true" : "false"));
|
||||
}
|
||||
Log::Info(Event::Render, "ParticleValidation: RenderLayer destroy id=" + getID());
|
||||
}
|
||||
|
||||
void RenderParticleValidationLayer::evaluate(const PropertyEvaluationParameters&) {
|
||||
const bool active = impl(baseImpl).active;
|
||||
const uint8_t maskMode = impl(baseImpl).maskMode;
|
||||
const uint8_t fixtureMode = impl(baseImpl).fixtureMode;
|
||||
auto properties = makeMutable<style::ParticleValidationLayerProperties>(
|
||||
staticImmutableCast<style::ParticleValidationLayer::Impl>(baseImpl), active, maskMode, fixtureMode);
|
||||
void RenderRasterParticleLayer::evaluate(const PropertyEvaluationParameters&) {
|
||||
const auto settings = particleSettings(baseImpl);
|
||||
auto properties = makeMutable<style::ParticleRenderLayerProperties>(
|
||||
baseImpl, settings.active, settings.maskMode, settings.fixtureMode);
|
||||
passes = RenderPass::Translucent;
|
||||
properties->renderPasses = mbgl::underlying_type(passes);
|
||||
evaluatedProperties = std::move(properties);
|
||||
|
||||
Log::Info(Event::Render,
|
||||
"ParticleValidation: state=" + std::string(active ? "ACTIVE" : "STATIC") +
|
||||
" maskMode=" + std::to_string(maskMode) + " id=" + getID() + " fixture=" + fixtureID(fixtureMode) +
|
||||
"ParticleRender: state=" + std::string(settings.active ? "ACTIVE" : "STATIC") +
|
||||
" maskMode=" + std::to_string(settings.maskMode) + " id=" + getID() + " fixture=" +
|
||||
(formalSource && sourceField ? sourceField->fixtureID : fixtureID(settings.fixtureMode)) +
|
||||
" layerIndex=" + std::to_string(getLayerIndex()) + " stage=40-01");
|
||||
}
|
||||
|
||||
bool RenderParticleValidationLayer::needsRepaint() const {
|
||||
const auto& properties = static_cast<const style::ParticleValidationLayerProperties&>(*evaluatedProperties);
|
||||
bool RenderRasterParticleLayer::needsRepaint() const {
|
||||
const auto& properties = static_cast<const style::ParticleRenderLayerProperties&>(*evaluatedProperties);
|
||||
return properties.active && baseImpl->visibility != style::VisibilityType::None;
|
||||
}
|
||||
|
||||
void RenderParticleValidationLayer::markLayerRenderable(bool willRender, UniqueChangeRequestVec& changes) {
|
||||
void RenderRasterParticleLayer::markLayerRenderable(bool willRender, UniqueChangeRequestVec& changes) {
|
||||
RenderLayer::markLayerRenderable(willRender, changes);
|
||||
if (!willRender && gpuValidation) {
|
||||
removeTargets(gpuValidation->registeredTargets, changes);
|
||||
gpuValidation->registeredTargets.clear();
|
||||
gpuValidation->resetSequence();
|
||||
if (!willRender && gpuResources) {
|
||||
removeTargets(gpuResources->registeredTargets, changes);
|
||||
gpuResources->registeredTargets.clear();
|
||||
gpuResources->resetSequence();
|
||||
Log::Info(Event::Render,
|
||||
"ParticleValidation30_02: disabled reset generation=" + std::to_string(gpuValidation->generation));
|
||||
"ParticleValidation30_02: disabled reset generation=" + std::to_string(gpuResources->generation));
|
||||
}
|
||||
}
|
||||
|
||||
void RenderParticleValidationLayer::layerRemoved(UniqueChangeRequestVec& changes) {
|
||||
if (gpuValidation) {
|
||||
removeTargets(gpuValidation->registeredTargets, changes);
|
||||
gpuValidation->registeredTargets.clear();
|
||||
void RenderRasterParticleLayer::layerRemoved(UniqueChangeRequestVec& changes) {
|
||||
if (gpuResources) {
|
||||
removeTargets(gpuResources->registeredTargets, changes);
|
||||
gpuResources->registeredTargets.clear();
|
||||
}
|
||||
RenderLayer::layerRemoved(changes);
|
||||
}
|
||||
|
||||
void RenderParticleValidationLayer::markContextDestroyed() {
|
||||
if (gpuValidation) {
|
||||
gpuValidation->contextDestroyed = true;
|
||||
Log::Info(Event::Render,
|
||||
"ParticleValidation30_02: context lost discard generation=" +
|
||||
std::to_string(gpuValidation->generation) + " stateRead=" + slotName(gpuValidation->stateRead) +
|
||||
" trailRead=" + slotName(gpuValidation->trailRead));
|
||||
void RenderRasterParticleLayer::markContextDestroyed() {
|
||||
if (gpuResources) {
|
||||
gpuResources->contextDestroyed = true;
|
||||
Log::Info(
|
||||
Event::Render,
|
||||
"ParticleValidation30_02: context lost discard generation=" + std::to_string(gpuResources->generation) +
|
||||
" stateRead=" + slotName(gpuResources->stateRead) + " trailRead=" + slotName(gpuResources->trailRead));
|
||||
}
|
||||
}
|
||||
|
||||
void RenderParticleValidationLayer::update(gfx::ShaderRegistry& shaders,
|
||||
void RenderRasterParticleLayer::prepare(const LayerPrepareParameters& parameters) {
|
||||
if (!formalSource || !parameters.source || parameters.source->baseImpl->type != style::SourceType::RasterArray) {
|
||||
return;
|
||||
}
|
||||
const auto& source = static_cast<const RenderRasterArraySource&>(*parameters.source);
|
||||
if (source.impl().field) {
|
||||
sourceField = *source.impl().field;
|
||||
}
|
||||
}
|
||||
|
||||
void RenderRasterParticleLayer::update(gfx::ShaderRegistry& shaders,
|
||||
gfx::Context& context,
|
||||
const TransformState& transformState,
|
||||
const std::shared_ptr<UpdateParameters>&,
|
||||
@@ -530,19 +559,19 @@ void RenderParticleValidationLayer::update(gfx::ShaderRegistry& shaders,
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& properties = static_cast<const style::ParticleValidationLayerProperties&>(*evaluatedProperties);
|
||||
const auto& properties = static_cast<const style::ParticleRenderLayerProperties&>(*evaluatedProperties);
|
||||
const bool active = properties.active;
|
||||
const uint8_t maskMode = properties.maskMode;
|
||||
const uint8_t fixtureMode = properties.fixtureMode;
|
||||
auto resources = gpuValidation;
|
||||
auto resources = gpuResources;
|
||||
if (resources && resources->contextDestroyed) {
|
||||
// VALIDATION ONLY: follow the 30-02 resource-generation rule. No stale
|
||||
// State, Trail, or Mask texture survives a notified context loss.
|
||||
gpuValidation.reset();
|
||||
gpuResources.reset();
|
||||
resources.reset();
|
||||
}
|
||||
if (!resources) {
|
||||
auto owned = std::make_shared<ParticleGpuValidationResources>();
|
||||
auto owned = std::make_shared<ParticleGpuResources>();
|
||||
owned->generation = ++nextGpuGeneration;
|
||||
owned->shader = context.getGenericShader(shaders, ShaderName);
|
||||
owned->vertices = std::make_shared<gfx::VertexVector<HeatmapTextureLayoutVertex>>(
|
||||
@@ -568,9 +597,18 @@ void RenderParticleValidationLayer::update(gfx::ShaderRegistry& shaders,
|
||||
.wrapV = gfx::TextureWrapType::Clamp});
|
||||
owned->maskGeneration = ++nextMaskGeneration;
|
||||
try {
|
||||
if (formalSource) {
|
||||
if (!sourceField) {
|
||||
Log::Error(Event::Render, "ParticleField: raster-array source has no loaded field");
|
||||
return;
|
||||
}
|
||||
owned->field = *sourceField;
|
||||
owned->fieldGeneration = owned->field.generation;
|
||||
} else {
|
||||
owned->fieldGeneration = ++nextFieldGeneration;
|
||||
owned->field = particle::OfflineFixtureParticleAdapter::load(fixtureID(fixtureMode),
|
||||
owned->fieldGeneration);
|
||||
}
|
||||
owned->fieldImage = makeVelocityFieldImage(owned->field);
|
||||
owned->fieldTexture = context.createTexture2D();
|
||||
if (!owned->fieldImage || !owned->fieldTexture) {
|
||||
@@ -587,8 +625,8 @@ void RenderParticleValidationLayer::update(gfx::ShaderRegistry& shaders,
|
||||
Log::Error(Event::Render, "ParticleField: fixture load failed: " + std::string(error.what()));
|
||||
return;
|
||||
}
|
||||
gpuValidation = std::move(owned);
|
||||
resources = gpuValidation;
|
||||
gpuResources = std::move(owned);
|
||||
resources = gpuResources;
|
||||
Log::Info(Event::Render,
|
||||
"ParticleValidation30_02: State A/B create generation=" + std::to_string(resources->generation) +
|
||||
" size=16x16 independent=true");
|
||||
@@ -621,10 +659,13 @@ void RenderParticleValidationLayer::update(gfx::ShaderRegistry& shaders,
|
||||
"ParticleValidation30_03: anchors A=0,0 B=0,-90 C=0,-172.8 D=0,172.8 canonical=Mercator-z0");
|
||||
}
|
||||
|
||||
if (resources->fixtureMode != fixtureMode) {
|
||||
const bool sourceChanged = formalSource && sourceField && resources->fieldGeneration != sourceField->generation;
|
||||
if ((!formalSource && resources->fixtureMode != fixtureMode) || sourceChanged) {
|
||||
try {
|
||||
const auto fieldGeneration = ++nextFieldGeneration;
|
||||
auto field = particle::OfflineFixtureParticleAdapter::load(fixtureID(fixtureMode), fieldGeneration);
|
||||
const auto fieldGeneration = formalSource ? sourceField->generation : ++nextFieldGeneration;
|
||||
auto field = formalSource
|
||||
? *sourceField
|
||||
: particle::OfflineFixtureParticleAdapter::load(fixtureID(fixtureMode), fieldGeneration);
|
||||
auto image = makeVelocityFieldImage(field);
|
||||
auto texture = context.createTexture2D();
|
||||
if (!image || !texture) {
|
||||
|
||||
@@ -1,38 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/renderer/render_layer.hpp>
|
||||
#include <mbgl/style/layers/particle_validation_layer_impl.hpp>
|
||||
#include <mbgl/renderer/layers/render_raster_particle_layer.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
class ParticleGpuValidationResources;
|
||||
|
||||
// VALIDATION ONLY: proves Core RenderLayer/GPU ping-pong integration.
|
||||
// STATIC redraws only the frozen Trail composite; it never advances State/Trail slots.
|
||||
class RenderParticleValidationLayer final : public RenderLayer {
|
||||
public:
|
||||
explicit RenderParticleValidationLayer(Immutable<style::ParticleValidationLayer::Impl>);
|
||||
~RenderParticleValidationLayer() override;
|
||||
|
||||
void update(gfx::ShaderRegistry&,
|
||||
gfx::Context&,
|
||||
const TransformState&,
|
||||
const std::shared_ptr<UpdateParameters>&,
|
||||
const RenderTree&,
|
||||
UniqueChangeRequestVec&) override;
|
||||
|
||||
private:
|
||||
void transition(const TransitionParameters&) override {}
|
||||
void evaluate(const PropertyEvaluationParameters&) override;
|
||||
bool hasTransition() const override { return false; }
|
||||
bool hasCrossfade() const override { return false; }
|
||||
bool needsRepaint() const override;
|
||||
void prepare(const LayerPrepareParameters&) override {}
|
||||
void markLayerRenderable(bool, UniqueChangeRequestVec&) override;
|
||||
void layerRemoved(UniqueChangeRequestVec&) override;
|
||||
void markContextDestroyed() override;
|
||||
|
||||
std::shared_ptr<ParticleGpuValidationResources> gpuValidation;
|
||||
};
|
||||
// Validation styles reuse the formal GPU renderer and do not own another
|
||||
// particle simulation.
|
||||
using RenderParticleValidationLayer = RenderRasterParticleLayer;
|
||||
|
||||
} // namespace mbgl
|
||||
|
||||
38
src/mbgl/renderer/layers/render_raster_particle_layer.hpp
Normal file
38
src/mbgl/renderer/layers/render_raster_particle_layer.hpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/particle/field/offline_fixture_particle_adapter.hpp>
|
||||
#include <mbgl/renderer/render_layer.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
class ParticleGpuResources;
|
||||
|
||||
class RenderRasterParticleLayer final : public RenderLayer {
|
||||
public:
|
||||
explicit RenderRasterParticleLayer(Immutable<style::Layer::Impl>);
|
||||
~RenderRasterParticleLayer() override;
|
||||
|
||||
void update(gfx::ShaderRegistry&,
|
||||
gfx::Context&,
|
||||
const TransformState&,
|
||||
const std::shared_ptr<UpdateParameters>&,
|
||||
const RenderTree&,
|
||||
UniqueChangeRequestVec&) override;
|
||||
|
||||
private:
|
||||
void transition(const TransitionParameters&) override {}
|
||||
void evaluate(const PropertyEvaluationParameters&) override;
|
||||
bool hasTransition() const override { return false; }
|
||||
bool hasCrossfade() const override { return false; }
|
||||
bool needsRepaint() const override;
|
||||
void prepare(const LayerPrepareParameters&) override;
|
||||
void markLayerRenderable(bool, UniqueChangeRequestVec&) override;
|
||||
void layerRemoved(UniqueChangeRequestVec&) override;
|
||||
void markContextDestroyed() override;
|
||||
|
||||
std::shared_ptr<ParticleGpuResources> gpuResources;
|
||||
std::optional<particle::ParticleFieldSnapshot> sourceField;
|
||||
bool formalSource = false;
|
||||
};
|
||||
|
||||
} // namespace mbgl
|
||||
@@ -8,7 +8,9 @@
|
||||
#include <mbgl/renderer/sources/render_image_source.hpp>
|
||||
#include <mbgl/renderer/sources/render_raster_dem_source.hpp>
|
||||
#include <mbgl/renderer/sources/render_raster_source.hpp>
|
||||
#include <mbgl/renderer/sources/render_raster_array_source.hpp>
|
||||
#include <mbgl/renderer/sources/render_vector_source.hpp>
|
||||
#include <mbgl/style/sources/raster_array_source.hpp>
|
||||
#include <mbgl/renderer/tile_parameters.hpp>
|
||||
#include <mbgl/renderer/update_parameters.hpp>
|
||||
#include <mbgl/tile/tile.hpp>
|
||||
@@ -49,6 +51,8 @@ std::unique_ptr<RenderSource> RenderSource::create(const Immutable<Source::Impl>
|
||||
case SourceType::CustomVector:
|
||||
return std::make_unique<RenderCustomGeometrySource>(staticImmutableCast<CustomGeometrySource::Impl>(impl),
|
||||
threadPool);
|
||||
case SourceType::RasterArray:
|
||||
return std::make_unique<RenderRasterArraySource>(staticImmutableCast<RasterArraySource::Impl>(impl));
|
||||
}
|
||||
|
||||
// Not reachable, but placate GCC.
|
||||
|
||||
40
src/mbgl/renderer/sources/render_raster_array_source.hpp
Normal file
40
src/mbgl/renderer/sources/render_raster_array_source.hpp
Normal file
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/renderer/render_source.hpp>
|
||||
#include <mbgl/style/sources/raster_array_source.hpp>
|
||||
#include <mbgl/style/sources/raster_array_source_impl.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
class RenderRasterArraySource final : public RenderSource {
|
||||
public:
|
||||
explicit RenderRasterArraySource(Immutable<style::RasterArraySource::Impl> impl)
|
||||
: RenderSource(std::move(impl)) {}
|
||||
bool isLoaded() const override { return impl().field.has_value(); }
|
||||
void update(Immutable<style::Source::Impl> value,
|
||||
const std::vector<Immutable<style::LayerProperties>>&,
|
||||
bool needsRendering,
|
||||
bool,
|
||||
const TileParameters&) override {
|
||||
baseImpl = std::move(value);
|
||||
enabled = needsRendering && isLoaded();
|
||||
}
|
||||
std::unique_ptr<RenderItem> createRenderItem() override { return {}; }
|
||||
void prepare(const SourcePrepareParameters&) override {}
|
||||
void updateFadingTiles() override {}
|
||||
bool hasFadingTiles() const override { return false; }
|
||||
std::unordered_map<std::string, std::vector<Feature>> queryRenderedFeatures(
|
||||
const ScreenLineString&,
|
||||
const TransformState&,
|
||||
const std::unordered_map<std::string, const RenderLayer*>&,
|
||||
const RenderedQueryOptions&,
|
||||
const mat4&) const override {
|
||||
return {};
|
||||
}
|
||||
std::vector<Feature> querySourceFeatures(const SourceQueryOptions&) const override { return {}; }
|
||||
void reduceMemoryUse() override {}
|
||||
void dumpDebugLogs() const override {}
|
||||
const style::RasterArraySource::Impl& impl() const {
|
||||
return static_cast<const style::RasterArraySource::Impl&>(*baseImpl);
|
||||
}
|
||||
};
|
||||
} // namespace mbgl
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <mbgl/style/sources/raster_dem_source.hpp>
|
||||
#include <mbgl/style/sources/vector_source.hpp>
|
||||
#include <mbgl/style/sources/image_source.hpp>
|
||||
#include <mbgl/style/sources/raster_array_source.hpp>
|
||||
#include <mbgl/util/geo.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
@@ -202,6 +203,26 @@ std::optional<std::unique_ptr<Source>> convertImageSource(const std::string& id,
|
||||
|
||||
return {std::move(result)};
|
||||
}
|
||||
|
||||
std::optional<std::unique_ptr<Source>> convertRasterArraySource(const std::string& id,
|
||||
const Convertible& value,
|
||||
Error& error) {
|
||||
const auto fixture = objectMember(value, "fixture");
|
||||
const auto metadata = objectMember(value, "metadata");
|
||||
const auto field = objectMember(value, "field");
|
||||
if (!fixture || !metadata || !field) {
|
||||
error.message = "raster-array source requires fixture, metadata, and field";
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto fixtureID = toString(*fixture);
|
||||
const auto metadataURL = toString(*metadata);
|
||||
const auto fieldURL = toString(*field);
|
||||
if (!fixtureID || !metadataURL || !fieldURL) {
|
||||
error.message = "raster-array fixture inputs must be strings";
|
||||
return std::nullopt;
|
||||
}
|
||||
return {std::make_unique<RasterArraySource>(id, *fixtureID, *metadataURL, *fieldURL)};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::optional<std::unique_ptr<Source>> Converter<std::unique_ptr<Source>>::operator()(const Convertible& value,
|
||||
@@ -234,6 +255,8 @@ std::optional<std::unique_ptr<Source>> Converter<std::unique_ptr<Source>>::opera
|
||||
return convertGeoJSONSource(id, value, error);
|
||||
} else if (tname == "image") {
|
||||
return convertImageSource(id, value, error);
|
||||
} else if (tname == "raster-array") {
|
||||
return convertRasterArraySource(id, value, error);
|
||||
} else {
|
||||
error.message = "invalid source type";
|
||||
return std::nullopt;
|
||||
|
||||
20
src/mbgl/style/layers/particle_render_layer_properties.hpp
Normal file
20
src/mbgl/style/layers/particle_render_layer_properties.hpp
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/style/layer_properties.hpp>
|
||||
|
||||
namespace mbgl::style {
|
||||
|
||||
class ParticleRenderLayerProperties final : public LayerProperties {
|
||||
public:
|
||||
ParticleRenderLayerProperties(Immutable<Layer::Impl> impl, bool active_, uint8_t maskMode_, uint8_t fixtureMode_)
|
||||
: LayerProperties(std::move(impl)),
|
||||
active(active_),
|
||||
maskMode(maskMode_),
|
||||
fixtureMode(fixtureMode_) {}
|
||||
expression::Dependency getDependencies() const noexcept override { return expression::Dependency::None; }
|
||||
bool active;
|
||||
uint8_t maskMode;
|
||||
uint8_t fixtureMode;
|
||||
};
|
||||
|
||||
} // namespace mbgl::style
|
||||
99
src/mbgl/style/layers/raster_particle_layer.cpp
Normal file
99
src/mbgl/style/layers/raster_particle_layer.cpp
Normal file
@@ -0,0 +1,99 @@
|
||||
#include <mbgl/style/layers/raster_particle_layer.hpp>
|
||||
|
||||
#include <mbgl/style/conversion/constant.hpp>
|
||||
#include <mbgl/style/conversion_impl.hpp>
|
||||
#include <mbgl/style/layer_observer.hpp>
|
||||
#include <mbgl/style/layers/raster_particle_layer_impl.hpp>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace mbgl::style {
|
||||
namespace {
|
||||
constexpr const char* ActiveProperty = "raster-particle-active";
|
||||
constexpr const char* MaskModeProperty = "raster-particle-mask-mode";
|
||||
const LayerTypeInfo typeInfo{.type = "raster-particle",
|
||||
.source = LayerTypeInfo::Source::Required,
|
||||
.pass3d = LayerTypeInfo::Pass3D::NotRequired,
|
||||
.layout = LayerTypeInfo::Layout::NotRequired,
|
||||
.fadingTiles = LayerTypeInfo::FadingTiles::NotRequired,
|
||||
.crossTileIndex = LayerTypeInfo::CrossTileIndex::NotRequired,
|
||||
.tileKind = LayerTypeInfo::TileKind::NotRequired};
|
||||
} // namespace
|
||||
|
||||
const LayerTypeInfo* RasterParticleLayer::Impl::staticTypeInfo() noexcept {
|
||||
return &typeInfo;
|
||||
}
|
||||
RasterParticleLayer::RasterParticleLayer(const std::string& id, const std::string& source)
|
||||
: Layer(makeMutable<Impl>(id, source)) {}
|
||||
RasterParticleLayer::RasterParticleLayer(Immutable<Impl> impl_)
|
||||
: Layer(std::move(impl_)) {}
|
||||
RasterParticleLayer::~RasterParticleLayer() {
|
||||
weakFactory.invalidateWeakPtrs();
|
||||
}
|
||||
const RasterParticleLayer::Impl& RasterParticleLayer::impl() const {
|
||||
return static_cast<const Impl&>(*baseImpl);
|
||||
}
|
||||
Mutable<RasterParticleLayer::Impl> RasterParticleLayer::mutableImpl() const {
|
||||
return makeMutable<Impl>(impl());
|
||||
}
|
||||
bool RasterParticleLayer::isActive() const {
|
||||
return impl().active;
|
||||
}
|
||||
uint8_t RasterParticleLayer::getMaskMode() const {
|
||||
return impl().maskMode;
|
||||
}
|
||||
void RasterParticleLayer::setActive(bool active) {
|
||||
if (active != isActive()) {
|
||||
auto next = mutableImpl();
|
||||
next->active = active;
|
||||
baseImpl = std::move(next);
|
||||
observer->onLayerChanged(*this);
|
||||
}
|
||||
}
|
||||
void RasterParticleLayer::setMaskMode(uint8_t mode) {
|
||||
if (mode <= 2 && mode != getMaskMode()) {
|
||||
auto next = mutableImpl();
|
||||
next->maskMode = mode;
|
||||
baseImpl = std::move(next);
|
||||
observer->onLayerChanged(*this);
|
||||
}
|
||||
}
|
||||
std::optional<conversion::Error> RasterParticleLayer::setPropertyInternal(const std::string& name,
|
||||
const conversion::Convertible& value) {
|
||||
conversion::Error error;
|
||||
if (name == ActiveProperty) {
|
||||
auto converted = conversion::convert<bool>(value, error);
|
||||
if (!converted) return error;
|
||||
setActive(*converted);
|
||||
return std::nullopt;
|
||||
}
|
||||
if (name == MaskModeProperty) {
|
||||
auto converted = conversion::convert<float>(value, error);
|
||||
if (!converted || *converted < 0 || *converted > 2 || std::floor(*converted) != *converted)
|
||||
return conversion::Error{"raster-particle-mask-mode must be 0, 1, or 2"};
|
||||
setMaskMode(static_cast<uint8_t>(*converted));
|
||||
return std::nullopt;
|
||||
}
|
||||
return conversion::Error{"layer doesn't support this property"};
|
||||
}
|
||||
StyleProperty RasterParticleLayer::getProperty(const std::string& name) const {
|
||||
if (name == ActiveProperty) return StyleProperty(Value{isActive()}, StyleProperty::Kind::Constant);
|
||||
if (name == MaskModeProperty)
|
||||
return StyleProperty(Value{static_cast<uint64_t>(getMaskMode())}, StyleProperty::Kind::Constant);
|
||||
return {};
|
||||
}
|
||||
Value RasterParticleLayer::serialize() const {
|
||||
auto result = Layer::serialize();
|
||||
serializeProperty(result, getProperty(ActiveProperty), ActiveProperty, true);
|
||||
serializeProperty(result, getProperty(MaskModeProperty), MaskModeProperty, true);
|
||||
return result;
|
||||
}
|
||||
std::unique_ptr<Layer> RasterParticleLayer::cloneRef(const std::string& id) const {
|
||||
auto next = mutableImpl();
|
||||
next->id = id;
|
||||
return std::unique_ptr<Layer>(new RasterParticleLayer(std::move(next)));
|
||||
}
|
||||
Mutable<Layer::Impl> RasterParticleLayer::mutableBaseImpl() const {
|
||||
return staticMutableCast<Layer::Impl>(mutableImpl());
|
||||
}
|
||||
} // namespace mbgl::style
|
||||
20
src/mbgl/style/layers/raster_particle_layer_impl.hpp
Normal file
20
src/mbgl/style/layers/raster_particle_layer_impl.hpp
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/style/layer_impl.hpp>
|
||||
#include <mbgl/style/layers/raster_particle_layer.hpp>
|
||||
|
||||
namespace mbgl::style {
|
||||
|
||||
class RasterParticleLayer::Impl final : public Layer::Impl {
|
||||
public:
|
||||
Impl(const std::string& id, const std::string& source)
|
||||
: Layer::Impl(id, source) {}
|
||||
Impl(const Impl&) = default;
|
||||
bool hasLayoutDifference(const Layer::Impl&) const override { return false; }
|
||||
void stringifyLayout(rapidjson::Writer<rapidjson::StringBuffer>&) const override {}
|
||||
bool active = true;
|
||||
uint8_t maskMode = 0;
|
||||
DECLARE_LAYER_TYPE_INFO;
|
||||
};
|
||||
|
||||
} // namespace mbgl::style
|
||||
80
src/mbgl/style/sources/raster_array_source.cpp
Normal file
80
src/mbgl/style/sources/raster_array_source.cpp
Normal file
@@ -0,0 +1,80 @@
|
||||
#include <mbgl/style/sources/raster_array_source.hpp>
|
||||
|
||||
#include <mbgl/particle/field/offline_fixture_particle_adapter.hpp>
|
||||
#include <mbgl/storage/file_source.hpp>
|
||||
#include <mbgl/storage/resource.hpp>
|
||||
#include <mbgl/style/source_observer.hpp>
|
||||
#include <mbgl/style/sources/raster_array_source_impl.hpp>
|
||||
#include <mbgl/util/async_request.hpp>
|
||||
#include <mbgl/util/logging.hpp>
|
||||
|
||||
namespace mbgl::style {
|
||||
|
||||
RasterArraySource::RasterArraySource(std::string id,
|
||||
std::string fixtureID,
|
||||
std::string metadataURL,
|
||||
std::string fieldURL)
|
||||
: Source(makeMutable<Impl>(std::move(id), std::move(fixtureID), std::move(metadataURL), std::move(fieldURL))) {}
|
||||
|
||||
RasterArraySource::~RasterArraySource() {
|
||||
weakFactory.invalidateWeakPtrs();
|
||||
}
|
||||
|
||||
const RasterArraySource::Impl& RasterArraySource::impl() const {
|
||||
return static_cast<const Impl&>(*baseImpl);
|
||||
}
|
||||
|
||||
void RasterArraySource::loadDescription(FileSource& fileSource) {
|
||||
if (metadataRequest || fieldRequest) return;
|
||||
metadataRequest = fileSource.request(
|
||||
Resource::source(impl().metadataURL), [this, self = makeWeakPtr()](const Response& response) {
|
||||
if (auto guard = self.lock(); self) {
|
||||
if (response.error || response.noContent || !response.data) {
|
||||
observer->onSourceError(
|
||||
*this, std::make_exception_ptr(std::runtime_error("particle metadata request failed")));
|
||||
return;
|
||||
}
|
||||
metadata = *response.data;
|
||||
tryLoad();
|
||||
}
|
||||
});
|
||||
fieldRequest = fileSource.request(
|
||||
Resource::source(impl().fieldURL), [this, self = makeWeakPtr()](const Response& response) {
|
||||
if (auto guard = self.lock(); self) {
|
||||
if (response.error || response.noContent || !response.data) {
|
||||
observer->onSourceError(
|
||||
*this, std::make_exception_ptr(std::runtime_error("particle field request failed")));
|
||||
return;
|
||||
}
|
||||
fieldBytes = std::vector<char>(response.data->begin(), response.data->end());
|
||||
tryLoad();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void RasterArraySource::tryLoad() {
|
||||
if (!metadata || !fieldBytes) return;
|
||||
try {
|
||||
auto field = particle::OfflineFixtureParticleAdapter::load(
|
||||
impl().fixtureID, *metadata, *fieldBytes, ++generation);
|
||||
baseImpl = makeMutable<Impl>(impl(), std::move(field));
|
||||
loaded = true;
|
||||
Log::Info(Event::Render,
|
||||
"ParticleField: source loaded fixtureId=" + impl().fixtureID + " metadata=" + impl().metadataURL +
|
||||
" field=" + impl().fieldURL);
|
||||
observer->onSourceLoaded(*this);
|
||||
observer->onSourceChanged(*this);
|
||||
} catch (const std::exception& error) {
|
||||
observer->onSourceError(*this, std::make_exception_ptr(std::runtime_error(error.what())));
|
||||
}
|
||||
}
|
||||
|
||||
bool RasterArraySource::supportsLayerType(const LayerTypeInfo* info) const {
|
||||
return info && std::string(info->type) == "raster-particle";
|
||||
}
|
||||
|
||||
Mutable<Source::Impl> RasterArraySource::createMutable() const noexcept {
|
||||
return staticMutableCast<Source::Impl>(makeMutable<Impl>(impl()));
|
||||
}
|
||||
|
||||
} // namespace mbgl::style
|
||||
31
src/mbgl/style/sources/raster_array_source_impl.hpp
Normal file
31
src/mbgl/style/sources/raster_array_source_impl.hpp
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/particle/field/offline_fixture_particle_adapter.hpp>
|
||||
#include <mbgl/style/sources/raster_array_source.hpp>
|
||||
#include <mbgl/style/source_impl.hpp>
|
||||
|
||||
namespace mbgl::style {
|
||||
|
||||
class RasterArraySource::Impl final : public Source::Impl {
|
||||
public:
|
||||
Impl(std::string id, std::string fixtureID_, std::string metadataURL_, std::string fieldURL_)
|
||||
: Source::Impl(SourceType::RasterArray, std::move(id)),
|
||||
fixtureID(std::move(fixtureID_)),
|
||||
metadataURL(std::move(metadataURL_)),
|
||||
fieldURL(std::move(fieldURL_)) {}
|
||||
Impl(const Impl&) = default;
|
||||
Impl(const Impl& other, particle::ParticleFieldSnapshot field_)
|
||||
: Source::Impl(other),
|
||||
fixtureID(other.fixtureID),
|
||||
metadataURL(other.metadataURL),
|
||||
fieldURL(other.fieldURL),
|
||||
field(std::move(field_)) {}
|
||||
std::optional<std::string> getAttribution() const override { return {}; }
|
||||
|
||||
std::string fixtureID;
|
||||
std::string metadataURL;
|
||||
std::string fieldURL;
|
||||
std::optional<particle::ParticleFieldSnapshot> field;
|
||||
};
|
||||
|
||||
} // namespace mbgl::style
|
||||
@@ -12,7 +12,8 @@ MBGL_DEFINE_ENUM(SourceType,
|
||||
{SourceType::Video, "video"},
|
||||
{SourceType::Annotations, "annotations"},
|
||||
{SourceType::Image, "image"},
|
||||
{SourceType::CustomVector, "customvector"}});
|
||||
{SourceType::CustomVector, "customvector"},
|
||||
{SourceType::RasterArray, "raster-array"}});
|
||||
|
||||
MBGL_DEFINE_ENUM(VisibilityType,
|
||||
{
|
||||
|
||||
@@ -7,6 +7,10 @@ import struct
|
||||
|
||||
GRID = 16
|
||||
ROOT = Path(__file__).resolve().parents[1] / "particle-fixtures"
|
||||
ANDROID_ASSETS = (
|
||||
Path(__file__).resolve().parents[1]
|
||||
/ "platform/android/MapLibreAndroidTestApp/src/main/assets/integration/particle-fixtures"
|
||||
)
|
||||
|
||||
|
||||
def velocity(fixture: str, x: int, y: int) -> tuple[float, float]:
|
||||
@@ -14,14 +18,20 @@ def velocity(fixture: str, x: int, y: int) -> tuple[float, float]:
|
||||
return 1.0, 0.0
|
||||
if fixture == "uniform-north":
|
||||
return 0.0, 1.0
|
||||
if fixture == "nodata":
|
||||
return (-9999.0, -9999.0) if (x, y) == (3, 4) else (1.0, 0.0)
|
||||
local_x = (x + 0.5) / GRID - 0.5
|
||||
local_y = (y + 0.5) / GRID - 0.5
|
||||
return -2.0 * local_y, 2.0 * local_x
|
||||
|
||||
|
||||
for fixture in ("uniform-east", "uniform-north", "vortex"):
|
||||
for fixture in ("uniform-east", "uniform-north", "vortex", "nodata"):
|
||||
payload = bytearray()
|
||||
for y in range(GRID):
|
||||
for x in range(GRID):
|
||||
payload.extend(struct.pack("<ff", *velocity(fixture, x, y)))
|
||||
(ROOT / fixture / "field.f32").write_bytes(payload)
|
||||
asset_path = ANDROID_ASSETS / fixture / "field.f32"
|
||||
asset_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
asset_path.write_bytes(payload)
|
||||
(asset_path.parent / "metadata.json").write_bytes((ROOT / fixture / "metadata.json").read_bytes())
|
||||
|
||||
@@ -158,3 +158,11 @@
|
||||
- Linux OpenGL 受影响 Core 对象、C++ format、diff、fixture checksum 和 adapter smoke test 均通过;未运行 Gradle/ADB/实机验证。
|
||||
- 当前结论:`WAITING_MANUAL_VALIDATION`。用户仍需完成 Android OpenGL T1-T12,尤其 T1 rotation 无按钮恢复;iOS、Vulkan、真实数据、生产 mask 和性能均未覆盖,尚不建议进入下一 Android 子任务。
|
||||
- Stage A 实现提交 `0b076c6d52fffb2ee4411cac64f06d43cea260c8` 已成功 push 到 `origin/particle/main`。
|
||||
|
||||
## 2026-08-31 — 40-01 Stage A 正式链、Android fixture 边界与 NoData 修正
|
||||
|
||||
- 纠正此前实现仍借用 source-less `ParticleValidationLayer` 的偏差:新增正式 `RasterArraySource -> RenderRasterArraySource -> RasterParticleLayer (Source::Required) -> RenderRasterParticleLayer`,并让正式 RenderLayer 复用既有 GPU field、State A/B、Trail A/B、Mask、Camera/World Wrap/Antimeridian 与生命周期。
|
||||
- Android Test App 的 Style 现在创建 `raster-array` / `raster-particle`,fixture 由 APK `assets/integration/particle-fixtures/` 的 `asset://` URI 提供;Core adapter 仅接收 FileSource bytes。Linux smoke helper 继续读取仓库 fixture,生成工具验证并同步两端完全相同的 metadata/field 内容。
|
||||
- `ParticleFieldSnapshot` 将 cell validity 独立于 fixture validity:合法 NoData cell 产生 invalid mask,GPU texture alpha 表示该 mask;State sample 命中 NoData 后确定性 respawn,不将 `-9999` 当普通速度;NaN/Inf 仍拒绝。新增 `nodata` deterministic fixture。
|
||||
- STATIC/context recreation 的 zero-step bootstrap 未推翻:新资源自动建立并首帧 composite,bootstrap commit 不增加 simulation frame。
|
||||
- Linux Core 精确编译、format/diff、fixture generator、adapter/asset parity 检查在最终收尾复跑;未运行 Gradle、APK、ADB 或 T1-T12。最终状态仍只能是 `WAITING_MANUAL_VALIDATION`,等待 GPT 再次复审远端代码。
|
||||
|
||||
Reference in New Issue
Block a user