test: add particle gpu ping-pong validation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled

This commit is contained in:
OpenAI Codex
2026-08-29 21:24:58 +08:00
parent b34b0a3e22
commit 2f1506747b
17 changed files with 598 additions and 64 deletions

View File

@@ -0,0 +1,54 @@
// Generated code, do not modify this file!
#pragma once
#include <mbgl/shaders/shader_source.hpp>
namespace mbgl {
namespace shaders {
template <>
struct ShaderSource<BuiltIn::ParticleGpuValidationShader, gfx::Backend::Type::OpenGL> {
static constexpr const char* name = "ParticleGpuValidationShader";
static constexpr const char* vertex = R"(layout (location = 0) in vec2 a_pos;
out vec2 v_uv;
void main() {
gl_Position = vec4(a_pos * 2.0 - 1.0, 0.0, 1.0);
v_uv = a_pos;
}
)";
static constexpr const char* fragment = R"(layout (std140) uniform ParticleGpuValidationUBO {
highp vec4 u_pass;
highp vec4 u_marker;
};
uniform sampler2D u_previous;
in vec2 v_uv;
void main() {
vec4 previous = texture(u_previous, v_uv);
float mode = u_pass.x;
if (mode < 0.5) {
// State pass: deterministic GPU-only state evolution.
fragColor = vec4(fract(previous.r + 0.03125), v_uv, 1.0);
} else if (mode < 1.5) {
// Trail pass: fade the previous trail and add a moving validation marker.
float marker = 1.0 - smoothstep(0.0, u_marker.z, distance(v_uv, u_marker.xy));
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 {
// Main pass: composite the latest trail without hiding the validation map.
float intensity = max(previous.r, max(previous.g, previous.b));
fragColor = vec4(previous.rgb * 0.72, min(intensity * 0.72, 0.72));
}
#ifdef OVERDRAW_INSPECTOR
fragColor = vec4(1.0);
#endif
}
)";
};
} // namespace shaders
} // namespace mbgl

View File

@@ -149,6 +149,13 @@ struct ShaderInfo<BuiltIn::HeatmapTextureShader, gfx::Backend::Type::OpenGL> {
static const std::vector<TextureInfo> textures;
};
template <>
struct ShaderInfo<BuiltIn::ParticleGpuValidationShader, gfx::Backend::Type::OpenGL> {
static const std::vector<AttributeInfo> attributes;
static const std::vector<UniformBlockInfo> uniformBlocks;
static const std::vector<TextureInfo> textures;
};
template <>
struct ShaderInfo<BuiltIn::HillshadePrepareShader, gfx::Backend::Type::OpenGL> {
static const std::vector<AttributeInfo> attributes;

View File

@@ -0,0 +1,16 @@
#pragma once
#include <array>
namespace mbgl {
namespace shaders {
// VALIDATION ONLY: parameters for the 30-02 State/Trail ping-pong proof.
struct alignas(16) ParticleGpuValidationUBO {
std::array<float, 4> pass;
std::array<float, 4> marker;
};
static_assert(sizeof(ParticleGpuValidationUBO) == 2 * 16);
} // namespace shaders
} // namespace mbgl

View File

@@ -127,6 +127,11 @@ enum {
heatmapTextureUBOCount
};
enum {
idParticleGpuValidationUBO = getEnumValue(drawableReservedUBOCount, layerUBOStartId),
particleGpuValidationUBOCount
};
enum {
idHillshadeEvaluatedPropsUBO = getEnumValue(hillshadeLayerSSBOCount, layerUBOStartId),
hillshadeLayerUBOCount
@@ -268,6 +273,7 @@ static constexpr uint32_t maxUBOCountPerShader = std::max({static_cast<uint32_t>
static_cast<uint32_t>(fillUBOCount),
static_cast<uint32_t>(fillExtrusionUBOCount),
static_cast<uint32_t>(heatmapTextureUBOCount),
static_cast<uint32_t>(particleGpuValidationUBOCount),
static_cast<uint32_t>(heatmapUBOCount),
static_cast<uint32_t>(hillshadePrepareUBOCount),
static_cast<uint32_t>(hillshadeUBOCount),
@@ -332,6 +338,11 @@ enum {
heatmapTextureCount
};
enum {
idParticleGpuValidationPreviousTexture,
particleGpuValidationTextureCount
};
enum {
idHillshadeImageTexture,
hillshadeTextureCount
@@ -376,6 +387,7 @@ static constexpr uint32_t maxTextureCountPerShader = std::max({static_cast<uint3
static_cast<uint32_t>(fillTextureCount),
static_cast<uint32_t>(fillExtrusionTextureCount),
static_cast<uint32_t>(heatmapTextureCount),
static_cast<uint32_t>(particleGpuValidationTextureCount),
static_cast<uint32_t>(hillshadeTextureCount),
static_cast<uint32_t>(colorReliefTextureCount),
static_cast<uint32_t>(lineTextureCount),
@@ -389,6 +401,11 @@ enum {
backgroundVertexAttributeCount
};
enum {
idParticleGpuValidationPosVertexAttribute,
particleGpuValidationVertexAttributeCount
};
enum {
idCirclePosVertexAttribute,
@@ -556,6 +573,7 @@ enum {
static constexpr uint32_t maxVertexAttributeCountPerShader = std::max({
static_cast<uint32_t>(backgroundVertexAttributeCount),
static_cast<uint32_t>(particleGpuValidationVertexAttributeCount),
static_cast<uint32_t>(circleVertexAttributeCount),
static_cast<uint32_t>(clippingMaskVertexAttributeCount),
static_cast<uint32_t>(collisionVertexAttributeCount),

View File

@@ -24,6 +24,7 @@
#include <mbgl/shaders/gl/fill_extrusion_pattern_instanced.hpp>
#include <mbgl/shaders/gl/heatmap.hpp>
#include <mbgl/shaders/gl/heatmap_texture.hpp>
#include <mbgl/shaders/gl/particle_gpu_validation.hpp>
#include <mbgl/shaders/gl/hillshade_prepare.hpp>
#include <mbgl/shaders/gl/color_relief.hpp>
#include <mbgl/shaders/gl/hillshade.hpp>

View File

@@ -30,6 +30,7 @@ enum class BuiltIn {
FillExtrusionPatternInstancedShader,
HeatmapShader,
HeatmapTextureShader,
ParticleGpuValidationShader,
HillshadePrepareShader,
ColorReliefShader,
HillshadeShader,

View File

@@ -12,11 +12,13 @@ import org.maplibre.android.maps.MapLibreMap
import org.maplibre.android.maps.Style
import org.maplibre.android.testapp.R
/** VALIDATION ONLY: Android OpenGL host for the 30-01 Core RenderLayer experiment. */
/** VALIDATION ONLY: Android OpenGL host for the 30-01/30-02 Core RenderLayer experiments. */
class ParticleCoreLayerValidationActivity : AppCompatActivity() {
private lateinit var mapView: MapView
private lateinit var maplibreMap: MapLibreMap
private lateinit var status: TextView
private var active = true
private var enabled = true
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -31,29 +33,47 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
.target(LatLng(0.0, 0.0))
.zoom(2.0)
.build()
loadValidationStyle(active = true)
loadValidationStyle(active = true, enabled = true)
}
findViewById<Button>(R.id.validationActive).setOnClickListener {
if (this::maplibreMap.isInitialized) loadValidationStyle(active = true)
if (this::maplibreMap.isInitialized) loadValidationStyle(active = true, enabled = enabled)
}
findViewById<Button>(R.id.validationStatic).setOnClickListener {
if (this::maplibreMap.isInitialized) loadValidationStyle(active = false)
if (this::maplibreMap.isInitialized) loadValidationStyle(active = false, enabled = enabled)
}
findViewById<Button>(R.id.validationDisable).setOnClickListener {
if (this::maplibreMap.isInitialized) loadValidationStyle(active = active, enabled = false)
}
findViewById<Button>(R.id.validationEnable).setOnClickListener {
if (this::maplibreMap.isInitialized) loadValidationStyle(active = active, enabled = true)
}
}
private fun loadValidationStyle(active: Boolean) {
val state = if (active) "ACTIVE" else "STATIC"
Log.i(TAG, "ParticleValidation: Test App switching to $state")
maplibreMap.setStyle(Style.Builder().fromJson(validationStyle(active))) {
private fun loadValidationStyle(active: Boolean, enabled: Boolean) {
this.active = active
this.enabled = enabled
val state = if (!enabled) "DISABLED" else if (active) "ACTIVE" else "STATIC"
Log.i(TAG, "ParticleValidation30_02: Test App switching to $state")
maplibreMap.setStyle(Style.Builder().fromJson(validationStyle(active, enabled))) {
status.text = getString(
if (active) R.string.particle_validation_active_status
else R.string.particle_validation_static_status
when {
!enabled -> R.string.particle_validation_disabled_status
active -> R.string.particle_validation_active_status
else -> R.string.particle_validation_static_status
}
)
}
}
private fun validationStyle(active: Boolean) = """
private fun validationStyle(active: Boolean, enabled: Boolean): String {
val particleLayer = if (enabled) {
"""{"id":"particle-validation-core","type":"particle-validation",
"paint":{"particle-validation-active":$active}},"""
} else {
""
}
return """
{
"version": 8,
"name": "Particle Core Layer Validation",
@@ -67,13 +87,13 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
{"id":"validation-background","type":"background","paint":{"background-color":"#102030"}},
{"id":"validation-below","type":"circle","source":"validation-point",
"paint":{"circle-radius":46,"circle-color":"#247ba0"}},
{"id":"particle-validation-core","type":"particle-validation",
"paint":{"particle-validation-active":$active}},
$particleLayer
{"id":"validation-above","type":"circle","source":"validation-point",
"paint":{"circle-radius":16,"circle-color":"#ffe066"}}
]
}
""".trimIndent()
}
override fun onStart() { super.onStart(); mapView.onStart() }
override fun onResume() { super.onResume(); mapView.onResume() }

View File

@@ -43,6 +43,26 @@
android:layout_weight="1"
android:text="@string/particle_validation_static" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/validationDisable"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/particle_validation_disable" />
<Button
android:id="@+id/validationEnable"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/particle_validation_enable" />
</LinearLayout>
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -12,6 +12,9 @@
<string name="category_data">Data Sources</string>
<string name="particle_validation_active">ACTIVE</string>
<string name="particle_validation_static">STATIC</string>
<string name="particle_validation_active_status">ACTIVE: Core Layer requests MapLibre frames. Move, zoom, rotate and pitch the map.</string>
<string name="particle_validation_static_status">STATIC: Core Layer stopped requesting frames. Map gestures must still render normally.</string>
<string name="particle_validation_disable">DISABLE</string>
<string name="particle_validation_enable">ENABLE</string>
<string name="particle_validation_active_status">30-02 ACTIVE: GPU State/Trail ping-pong requests frames. Move, zoom, rotate, pitch and resize.</string>
<string name="particle_validation_static_status">30-02 STATIC: GPU ping-pong stopped requesting frames. Gestures must still render normally.</string>
<string name="particle_validation_disabled_status">30-02 DISABLED: validation layer and its GPU resources were removed. Tap ENABLE to recreate them.</string>
</resources>

View File

@@ -144,6 +144,13 @@
"glsl_frag": "heatmap_texture.fragment.glsl",
"uses_ubos": true
},
{
"name": "ParticleGpuValidationShader",
"header": "particle_gpu_validation",
"glsl_vert": "particle_gpu_validation.vertex.glsl",
"glsl_frag": "particle_gpu_validation.fragment.glsl",
"uses_ubos": true
},
{
"name": "HillshadePrepareShader",
"header": "hillshade_prepare",

View File

@@ -0,0 +1,30 @@
layout (std140) uniform ParticleGpuValidationUBO {
highp vec4 u_pass;
highp vec4 u_marker;
};
uniform sampler2D u_previous;
in vec2 v_uv;
void main() {
vec4 previous = texture(u_previous, v_uv);
float mode = u_pass.x;
if (mode < 0.5) {
// State pass: deterministic GPU-only state evolution.
fragColor = vec4(fract(previous.r + 0.03125), v_uv, 1.0);
} else if (mode < 1.5) {
// Trail pass: fade the previous trail and add a moving validation marker.
float marker = 1.0 - smoothstep(0.0, u_marker.z, distance(v_uv, u_marker.xy));
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 {
// Main pass: composite the latest trail without hiding the validation map.
float intensity = max(previous.r, max(previous.g, previous.b));
fragColor = vec4(previous.rgb * 0.72, min(intensity * 0.72, 0.72));
}
#ifdef OVERDRAW_INSPECTOR
fragColor = vec4(1.0);
#endif
}

View File

@@ -0,0 +1,8 @@
layout (location = 0) in vec2 a_pos;
out vec2 v_uv;
void main() {
gl_Position = vec4(a_pos * 2.0 - 1.0, 0.0, 1.0);
v_uv = a_pos;
}

View File

@@ -135,6 +135,7 @@ void RendererBackend::initShaders(gfx::ShaderRegistry& shaders, const ProgramPar
shaders::BuiltIn::FillExtrusionPatternShader,
shaders::BuiltIn::HeatmapShader,
shaders::BuiltIn::HeatmapTextureShader,
shaders::BuiltIn::ParticleGpuValidationShader,
shaders::BuiltIn::HillshadePrepareShader,
shaders::BuiltIn::HillshadeShader,
shaders::BuiltIn::ColorReliefShader,

View File

@@ -1,78 +1,285 @@
#include <mbgl/renderer/layers/render_particle_validation_layer.hpp>
#include <mbgl/gfx/color_mode.hpp>
#include <mbgl/gfx/context.hpp>
#include <mbgl/gfx/cull_face_mode.hpp>
#include <mbgl/gfx/drawable.hpp>
#include <mbgl/gfx/drawable_builder.hpp>
#include <mbgl/gfx/drawable_tweaker.hpp>
#include <mbgl/gfx/shader_registry.hpp>
#include <mbgl/map/transform_state.hpp>
#include <mbgl/renderer/buckets/heatmap_bucket.hpp>
#include <mbgl/renderer/change_request.hpp>
#include <mbgl/renderer/layer_group.hpp>
#include <mbgl/renderer/paint_parameters.hpp>
#include <mbgl/renderer/render_pass.hpp>
#include <mbgl/renderer/render_static_data.hpp>
#include <mbgl/renderer/render_target.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/util/logging.hpp>
#include <algorithm>
#include <array>
#include <atomic>
#include <cmath>
#include <sstream>
#include <vector>
namespace mbgl {
namespace {
constexpr auto ShaderName = "ParticleGpuValidationShader";
constexpr Size StateSize{16, 16};
std::atomic_uint64_t nextGpuGeneration{0};
const style::ParticleValidationLayer::Impl& impl(const Immutable<style::Layer::Impl>& value) {
assert(value->getTypeInfo() == style::ParticleValidationLayer::Impl::staticTypeInfo());
return static_cast<const style::ParticleValidationLayer::Impl&>(*value);
}
class ParticleValidationDrawableTweaker final : public gfx::DrawableTweaker {
public:
ParticleValidationDrawableTweaker(std::string id_, bool active_, int32_t layerIndex_)
: id(std::move(id_)),
active(active_),
layerIndex(layerIndex_) {}
enum class ValidationPass : uint8_t {
State = 0,
Trail = 1,
Composite = 2,
};
~ParticleValidationDrawableTweaker() override {
Log::Info(Event::Render,
"ParticleValidation: validation drawable destroy id=" + id +
" validationFrames=" + std::to_string(validationFrameCount));
const char* slotName(const uint8_t slot) {
return slot == 0 ? "A" : "B";
}
Size validViewportSize(const Size size) {
return {std::max<uint32_t>(1, size.width), std::max<uint32_t>(1, size.height)};
}
void removeTargets(const std::vector<RenderTargetPtr>& targets, UniqueChangeRequestVec& changes) {
for (const auto& target : targets) {
if (target) {
changes.emplace_back(std::make_unique<RemoveRenderTargetRequest>(target));
}
}
}
} // namespace
class ParticleGpuValidationResources {
public:
uint64_t generation = 0;
uint64_t committedFrame = 0;
uint8_t stateRead = 0;
uint8_t trailRead = 0;
bool stateSeeded = false;
bool trailSeeded = false;
bool pending = false;
bool contextDestroyed = false;
Size trailSize{};
std::array<RenderTargetPtr, 2> stateTargets;
std::array<RenderTargetPtr, 2> trailTargets;
std::vector<RenderTargetPtr> registeredTargets;
gfx::ShaderProgramBasePtr shader;
std::shared_ptr<gfx::VertexVector<HeatmapTextureLayoutVertex>> vertices;
SegmentVector segments;
uint8_t stateWrite() const { return static_cast<uint8_t>(1 - stateRead); }
uint8_t trailWrite() const { return static_cast<uint8_t>(1 - trailRead); }
void resetSequence() {
committedFrame = 0;
stateRead = 0;
trailRead = 0;
stateSeeded = false;
trailSeeded = false;
pending = false;
for (const auto& target : stateTargets) {
if (target && target->getLayerGroup(0)) {
target->getLayerGroup(0)->clearDrawables();
}
}
for (const auto& target : trailTargets) {
if (target && target->getLayerGroup(0)) {
target->getLayerGroup(0)->clearDrawables();
}
}
}
void commit() {
if (!pending) {
return;
}
stateRead = stateWrite();
trailRead = trailWrite();
stateSeeded = true;
trailSeeded = true;
++committedFrame;
pending = false;
}
};
namespace {
class ParticleGpuValidationTweaker final : public gfx::DrawableTweaker {
public:
ParticleGpuValidationTweaker(std::shared_ptr<ParticleGpuValidationResources> state_,
ValidationPass pass_,
uint8_t readSlot_,
uint8_t writeSlot_,
gfx::Texture2DPtr source_,
gfx::Texture2DPtr target_)
: state(std::move(state_)),
pass(pass_),
readSlot(readSlot_),
writeSlot(writeSlot_),
source(std::move(source_)),
target(std::move(target_)) {}
void init(gfx::Drawable&) override {}
void execute(gfx::Drawable&, PaintParameters& parameters) override {
if (parameters.pass != RenderPass::Translucent) {
void execute(gfx::Drawable& drawable, PaintParameters& parameters) override {
auto resources = state.lock();
if (parameters.pass != RenderPass::Translucent || !resources || !source) {
return;
}
++validationFrameCount;
if (validationFrameCount != 1 && validationFrameCount % 60 != 0) {
drawable.setTexture(source, shaders::idParticleGpuValidationPreviousTexture);
const float frame = static_cast<float>(resources->committedFrame + 1);
const float markerX = 0.5f + 0.32f * std::sin(frame * 0.071f);
const float markerY = 0.5f + 0.32f * std::cos(frame * 0.053f);
const shaders::ParticleGpuValidationUBO ubo = {
.pass = {static_cast<float>(pass), frame, 0.0f, 0.0f},
.marker = {markerX, markerY, 0.035f, 0.965f},
};
drawable.mutableUniformBuffers().createOrUpdate(shaders::idParticleGpuValidationUBO, &ubo, parameters.context);
if (pass == ValidationPass::Composite) {
const auto beforeState = resources->stateRead;
const auto beforeTrail = resources->trailRead;
resources->commit();
if (resources->committedFrame == 1 || resources->committedFrame % 60 == 0) {
const auto size = parameters.state.getSize();
std::ostringstream message;
message << "ParticleValidation30_02: commit generation=" << resources->generation
<< " frame=" << resources->committedFrame << " state=" << slotName(beforeState) << "->"
<< slotName(resources->stateRead) << " trail=" << slotName(beforeTrail) << "->"
<< slotName(resources->trailRead)
<< " order=State,Trail,Composite hazard=SAFE viewport=" << size.width << 'x' << size.height
<< " zoom=" << parameters.state.getZoom() << " bearing=" << parameters.state.getBearing()
<< " pitch=" << parameters.state.getPitch();
Log::Info(Event::Render, message.str());
}
return;
}
const auto size = parameters.state.getSize();
const auto& matrix = parameters.transformParams.projMatrix;
std::ostringstream message;
message << "ParticleValidation: render frame=" << validationFrameCount << " mapFrame=" << parameters.frameCount
<< " state=" << (active ? "ACTIVE" : "STATIC") << " repaint=" << (active ? "requested" : "stopped")
<< " pass=translucent" << " layerIndex=" << layerIndex << " currentLayer=" << parameters.currentLayer
<< " viewport=" << size.width << 'x' << size.height << " zoom=" << parameters.state.getZoom()
<< " bearing=" << parameters.state.getBearing() << " pitch=" << parameters.state.getPitch() << " proj=["
<< matrix[0] << ',' << matrix[5] << ',' << matrix[12] << ',' << matrix[13] << ']';
Log::Info(Event::Render, message.str());
if (source == target) {
Log::Error(Event::Render,
"ParticleValidation30_02: HAZARD source equals target generation=" +
std::to_string(resources->generation));
return;
}
if (resources->committedFrame == 0 || (resources->committedFrame + 1) % 60 == 0) {
const auto passName = pass == ValidationPass::State ? "State" : "Trail";
Log::Info(Event::Render,
"ParticleValidation30_02: pass=" + std::string(passName) +
" generation=" + std::to_string(resources->generation) +
" frame=" + std::to_string(resources->committedFrame + 1) + " read=" + slotName(readSlot) +
" write=" + slotName(writeSlot) + " distinct=true");
}
}
private:
std::string id;
bool active;
int32_t layerIndex;
uint64_t validationFrameCount = 0;
std::weak_ptr<ParticleGpuValidationResources> state;
ValidationPass pass;
uint8_t readSlot;
uint8_t writeSlot;
gfx::Texture2DPtr source;
gfx::Texture2DPtr target;
};
RenderTargetPtr createValidationTarget(gfx::Context& context,
const Size size,
const std::string& name,
const gfx::TextureFilterType filter) {
auto target = context.createRenderTarget(size, gfx::TextureChannelDataType::UnsignedByte);
if (!target || !target->getTexture()) {
return {};
}
target->getTexture()->setSamplerConfiguration(
{.filter = filter, .wrapU = gfx::TextureWrapType::Clamp, .wrapV = gfx::TextureWrapType::Clamp});
auto group = context.createLayerGroup(0, 1, name);
if (!group || !target->addLayerGroup(std::move(group), true)) {
return {};
}
return target;
}
bool buildQuad(gfx::Context& context,
const std::shared_ptr<ParticleGpuValidationResources>& resources,
LayerGroup& group,
const std::string& name,
const gfx::Texture2DPtr& source,
const gfx::Texture2DPtr& target,
const ValidationPass pass,
const uint8_t readSlot,
const uint8_t writeSlot) {
group.clearDrawables();
auto attributes = context.createVertexAttributeArray();
if (const auto& attribute = attributes->set(shaders::idParticleGpuValidationPosVertexAttribute)) {
attribute->setSharedRawData(resources->vertices,
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(pass == ValidationPass::Composite ? gfx::ColorMode::alphaBlended()
: gfx::ColorMode::unblended());
builder->setCullFaceMode(gfx::CullFaceMode::disabled());
builder->setVertexAttributes(std::move(attributes));
builder->setRawVertices({}, resources->vertices->elements(), gfx::AttributeDataType::Short2);
builder->setSegments(gfx::Triangles(),
RenderStaticData::quadTriangleIndices().vector(),
resources->segments.data(),
resources->segments.size());
builder->setTexture(source, shaders::idParticleGpuValidationPreviousTexture);
builder->addTweaker(
std::make_shared<ParticleGpuValidationTweaker>(resources, pass, readSlot, writeSlot, source, target));
// DrawableBuilder owns the current drawable until flush finalizes it.
builder->flush(context);
auto drawables = builder->clearDrawables();
if (drawables.size() != 1 || !drawables.front()) {
Log::Error(Event::Render, "ParticleValidation30_02: failed to finalize drawable name=" + name);
return false;
}
group.addDrawable(std::move(drawables.front()));
return true;
}
} // namespace
RenderParticleValidationLayer::RenderParticleValidationLayer(Immutable<style::ParticleValidationLayer::Impl> impl_)
: RenderLayer(makeMutable<style::ParticleValidationLayerProperties>(impl_, impl_->active)) {
Log::Info(Event::Render,
"ParticleValidation: RenderLayer create id=" + getID() + " source-less=true pass=translucent");
Log::Info(
Event::Render,
"ParticleValidation: RenderLayer create id=" + getID() + " source-less=true pass=translucent stage=30-02");
}
RenderParticleValidationLayer::~RenderParticleValidationLayer() {
if (gpuValidation) {
Log::Info(Event::Render,
"ParticleValidation30_02: resources destroy generation=" + std::to_string(gpuValidation->generation) +
" contextDestroyed=" + (gpuValidation->contextDestroyed ? "true" : "false"));
}
Log::Info(Event::Render, "ParticleValidation: RenderLayer destroy id=" + getID());
}
@@ -86,7 +293,7 @@ void RenderParticleValidationLayer::evaluate(const PropertyEvaluationParameters&
Log::Info(Event::Render,
"ParticleValidation: state=" + std::string(active ? "ACTIVE" : "STATIC") + " id=" + getID() +
" layerIndex=" + std::to_string(getLayerIndex()));
" layerIndex=" + std::to_string(getLayerIndex()) + " stage=30-02");
}
bool RenderParticleValidationLayer::needsRepaint() const {
@@ -94,9 +301,38 @@ bool RenderParticleValidationLayer::needsRepaint() const {
return properties.active && baseImpl->visibility != style::VisibilityType::None;
}
void RenderParticleValidationLayer::update(gfx::ShaderRegistry&,
void RenderParticleValidationLayer::markLayerRenderable(bool willRender, UniqueChangeRequestVec& changes) {
RenderLayer::markLayerRenderable(willRender, changes);
if (!willRender && gpuValidation) {
removeTargets(gpuValidation->registeredTargets, changes);
gpuValidation->registeredTargets.clear();
gpuValidation->resetSequence();
Log::Info(Event::Render,
"ParticleValidation30_02: disabled reset generation=" + std::to_string(gpuValidation->generation));
}
}
void RenderParticleValidationLayer::layerRemoved(UniqueChangeRequestVec& changes) {
if (gpuValidation) {
removeTargets(gpuValidation->registeredTargets, changes);
gpuValidation->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 RenderParticleValidationLayer::update(gfx::ShaderRegistry& shaders,
gfx::Context& context,
const TransformState&,
const TransformState& transformState,
const std::shared_ptr<UpdateParameters>&,
const RenderTree&,
UniqueChangeRequestVec& changes) {
@@ -105,29 +341,120 @@ void RenderParticleValidationLayer::update(gfx::ShaderRegistry&,
setLayerGroup(std::move(group), changes);
}
}
if (!layerGroup) {
if (!layerGroup || !isLayerRenderable()) {
return;
}
auto* group = static_cast<LayerGroup*>(layerGroup.get());
if (group->getDrawableCount() != 0) {
auto resources = gpuValidation;
if (!resources) {
auto owned = std::make_shared<ParticleGpuValidationResources>();
owned->generation = ++nextGpuGeneration;
owned->shader = context.getGenericShader(shaders, ShaderName);
owned->vertices = std::make_shared<gfx::VertexVector<HeatmapTextureLayoutVertex>>(
RenderStaticData::heatmapTextureVertices());
owned->segments = RenderStaticData::heatmapTextureSegments();
for (uint8_t i = 0; i < 2; ++i) {
owned->stateTargets[i] = createValidationTarget(
context, StateSize, getID() + "-state-" + slotName(i), gfx::TextureFilterType::Nearest);
}
if (!owned->shader || !owned->stateTargets[0] || !owned->stateTargets[1]) {
Log::Error(Event::Render, "ParticleValidation30_02: State resource creation failed");
return;
}
gpuValidation = std::move(owned);
resources = gpuValidation;
Log::Info(Event::Render,
"ParticleValidation30_02: State A/B create generation=" + std::to_string(resources->generation) +
" size=16x16 independent=true");
}
removeTargets(resources->registeredTargets, changes);
resources->registeredTargets.clear();
const auto viewport = validViewportSize(transformState.getSize());
if (!resources->trailTargets[0] || !resources->trailTargets[1] || resources->trailSize != viewport) {
const auto previousSize = resources->trailSize;
resources->trailTargets = {};
for (uint8_t i = 0; i < 2; ++i) {
resources->trailTargets[i] = createValidationTarget(
context, viewport, getID() + "-trail-" + slotName(i), gfx::TextureFilterType::Linear);
}
if (!resources->trailTargets[0] || !resources->trailTargets[1]) {
Log::Error(Event::Render, "ParticleValidation30_02: Trail resource creation failed");
return;
}
resources->trailSize = viewport;
resources->trailRead = 0;
resources->trailSeeded = false;
Log::Info(Event::Render,
"ParticleValidation30_02: Trail A/B " + std::string(previousSize.isEmpty() ? "create" : "resize") +
" generation=" + std::to_string(resources->generation) +
" old=" + std::to_string(previousSize.width) + "x" + std::to_string(previousSize.height) +
" new=" + std::to_string(viewport.width) + "x" + std::to_string(viewport.height) +
" StatePreserved=true");
}
const auto stateWrite = resources->stateWrite();
const auto trailWrite = resources->trailWrite();
auto& stateGroup = static_cast<LayerGroup&>(*resources->stateTargets[stateWrite]->getLayerGroup(0));
auto& trailGroup = static_cast<LayerGroup&>(*resources->trailTargets[trailWrite]->getLayerGroup(0));
auto& compositeGroup = static_cast<LayerGroup&>(*layerGroup);
const bool stateBuilt = buildQuad(context,
resources,
stateGroup,
getID() + "-state-pass",
resources->stateTargets[resources->stateRead]->getTexture(),
resources->stateTargets[stateWrite]->getTexture(),
ValidationPass::State,
resources->stateRead,
stateWrite);
const bool trailBuilt = buildQuad(context,
resources,
trailGroup,
getID() + "-trail-pass",
resources->trailTargets[resources->trailRead]->getTexture(),
resources->trailTargets[trailWrite]->getTexture(),
ValidationPass::Trail,
resources->trailRead,
trailWrite);
const bool compositeBuilt = buildQuad(context,
resources,
compositeGroup,
getID() + "-composite-pass",
resources->trailTargets[trailWrite]->getTexture(),
{},
ValidationPass::Composite,
resources->trailRead,
trailWrite);
if (!stateBuilt || !trailBuilt || !compositeBuilt) {
compositeGroup.clearDrawables();
return;
}
const bool active = static_cast<const style::ParticleValidationLayerProperties&>(*evaluatedProperties).active;
auto builder = context.createDrawableBuilder(getID());
auto& drawable = builder->getCurrentDrawable(true);
drawable->setIsCustom(true);
drawable->setRenderPass(RenderPass::Translucent);
drawable->addTweaker(std::make_shared<ParticleValidationDrawableTweaker>(getID(), active, getLayerIndex()));
builder->flush(context);
auto drawables = builder->clearDrawables();
group->addDrawable(std::move(drawables.front()));
++stats.drawablesAdded;
// Read targets are registered only for their one-time clear. Normal frames register
// only the opposite write targets, in dependency order, so a sampled texture can
// never be attached to the active render pass.
if (!resources->stateSeeded) {
resources->registeredTargets.emplace_back(resources->stateTargets[resources->stateRead]);
}
resources->registeredTargets.emplace_back(resources->stateTargets[stateWrite]);
if (!resources->trailSeeded) {
resources->registeredTargets.emplace_back(resources->trailTargets[resources->trailRead]);
}
resources->registeredTargets.emplace_back(resources->trailTargets[trailWrite]);
for (const auto& target : resources->registeredTargets) {
changes.emplace_back(std::make_unique<AddRenderTargetRequest>(target));
}
resources->pending = true;
Log::Info(Event::Render,
"ParticleValidation: validation drawable create id=" + getID() +
" layerIndex=" + std::to_string(getLayerIndex()) + " pass=translucent");
if (resources->committedFrame == 0 || (resources->committedFrame + 1) % 60 == 0) {
Log::Info(Event::Render,
"ParticleValidation30_02: schedule generation=" + std::to_string(resources->generation) + " frame=" +
std::to_string(resources->committedFrame + 1) + " state=" + slotName(resources->stateRead) +
"->" + slotName(stateWrite) + " trail=" + slotName(resources->trailRead) + "->" +
slotName(trailWrite) + " targetsDistinct=true cpuStateCopy=false");
}
}
} // namespace mbgl

View File

@@ -5,6 +5,8 @@
namespace mbgl {
class ParticleGpuValidationResources;
// VALIDATION ONLY: proves Core RenderLayer/frame-transform/repaint integration.
class RenderParticleValidationLayer final : public RenderLayer {
public:
@@ -25,6 +27,11 @@ private:
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;
};
} // namespace mbgl

View File

@@ -287,6 +287,19 @@ const std::vector<TextureInfo> HeatmapTextureShaderInfo::textures = {
TextureInfo{"u_color_ramp", idHeatmapColorRampTexture},
};
// Particle GPU Validation (30-02, OpenGL validation only)
using ParticleGpuValidationShaderInfo = ShaderInfo<BuiltIn::ParticleGpuValidationShader, gfx::Backend::Type::OpenGL>;
const std::vector<UniformBlockInfo> ParticleGpuValidationShaderInfo::uniformBlocks = {
UniformBlockInfo{"ParticleGpuValidationUBO", idParticleGpuValidationUBO},
};
const std::vector<AttributeInfo> ParticleGpuValidationShaderInfo::attributes = {
AttributeInfo{"a_pos", idParticleGpuValidationPosVertexAttribute},
};
const std::vector<TextureInfo> ParticleGpuValidationShaderInfo::textures = {
TextureInfo{"u_previous", idParticleGpuValidationPreviousTexture},
};
// Hillshade Prepare
using HillshadePrepareShaderInfo = ShaderInfo<BuiltIn::HillshadePrepareShader, gfx::Backend::Type::OpenGL>;

View File

@@ -29,6 +29,7 @@ MBGL_DEFINE_ENUM(BuiltIn,
{BuiltIn::FillExtrusionPatternInstancedShader, "FillExtrusionPatternInstancedShader"},
{BuiltIn::HeatmapShader, "HeatmapShader"},
{BuiltIn::HeatmapTextureShader, "HeatmapTextureShader"},
{BuiltIn::ParticleGpuValidationShader, "ParticleGpuValidationShader"},
{BuiltIn::HillshadePrepareShader, "HillshadePrepareShader"},
{BuiltIn::ColorReliefShader, "ColorReliefShader"},
{BuiltIn::HillshadeShader, "HillshadeShader"},