test(step097): add A1 renderer regression entry points

This commit is contained in:
tei
2026-09-26 09:15:10 +09:00
parent be5b9080ed
commit d0ced72351
7 changed files with 422 additions and 28 deletions

View File

@@ -17,6 +17,19 @@ class BufferResource;
class CommandEncoder; class CommandEncoder;
class Context; class Context;
namespace detail {
struct ClearedAttachments {
bool color = false;
bool depth = false;
bool stencil = false;
};
ClearedAttachments applyRenderPassClears(MTL::RenderPassDescriptor&,
const std::optional<Color>&,
const std::optional<float>&,
const std::optional<int32_t>&);
} // namespace detail
class RenderPass final : public gfx::RenderPass { class RenderPass final : public gfx::RenderPass {
public: public:
RenderPass(CommandEncoder&, const char* name, const gfx::RenderPassDescriptor&); RenderPass(CommandEncoder&, const char* name, const gfx::RenderPassDescriptor&);

View File

@@ -55,6 +55,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
private var navSeaPbfLayerVisibility = emptyList<NavSeaPbfLayerVisibility>() private var navSeaPbfLayerVisibility = emptyList<NavSeaPbfLayerVisibility>()
private var retainedCacheEnabled = true private var retainedCacheEnabled = true
private var step093Mode = false private var step093Mode = false
private var step097Topology: Step097StencilFixture.Topology? = null
private var step093Topology = Step093Topology.BOTTOM_CONTINUOUS_TOP private var step093Topology = Step093Topology.BOTTOM_CONTINUOUS_TOP
private var step093Variant = Step093Variant.DEFAULT private var step093Variant = Step093Variant.DEFAULT
private var step093StartVisible = true private var step093StartVisible = true
@@ -63,8 +64,9 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
private val step093Handler = Handler(Looper.getMainLooper()) private val step093Handler = Handler(Looper.getMainLooper())
private val step093DiagnosticsPoll = object : Runnable { private val step093DiagnosticsPoll = object : Runnable {
override fun run() { override fun run() {
if (step093Mode && this@ParticleCoreLayerValidationActivity::maplibreMap.isInitialized) { if ((step093Mode || step097Topology != null) && this@ParticleCoreLayerValidationActivity::maplibreMap.isInitialized) {
refreshStep093Diagnostics(log = false) if (step097Topology != null) refreshStep097Diagnostics(log = false)
else refreshStep093Diagnostics(log = false)
step093Handler.postDelayed(this, STEP093_DIAGNOSTICS_INTERVAL_MS) step093Handler.postDelayed(this, STEP093_DIAGNOSTICS_INTERVAL_MS)
} }
} }
@@ -107,6 +109,8 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
navSeaPbfRenderEnabled = savedInstanceState?.getBoolean(STATE_NAVSEA_PBF_RENDER_ENABLED, true) ?: true navSeaPbfRenderEnabled = savedInstanceState?.getBoolean(STATE_NAVSEA_PBF_RENDER_ENABLED, true) ?: true
retainedCacheEnabled = savedInstanceState?.getBoolean(STATE_RETAINED_CACHE_ENABLED, true) ?: true retainedCacheEnabled = savedInstanceState?.getBoolean(STATE_RETAINED_CACHE_ENABLED, true) ?: true
step093Mode = savedInstanceState?.getBoolean(STATE_STEP093_MODE, false) ?: false step093Mode = savedInstanceState?.getBoolean(STATE_STEP093_MODE, false) ?: false
step097Topology = savedInstanceState?.getString(STATE_STEP097_TOPOLOGY)
?.let { value -> Step097StencilFixture.Topology.values().firstOrNull { it.name == value } }
step093Topology = savedInstanceState?.getString(STATE_STEP093_TOPOLOGY) step093Topology = savedInstanceState?.getString(STATE_STEP093_TOPOLOGY)
?.let { value -> Step093Topology.values().firstOrNull { it.name == value } } ?.let { value -> Step093Topology.values().firstOrNull { it.name == value } }
?: Step093Topology.BOTTOM_CONTINUOUS_TOP ?: Step093Topology.BOTTOM_CONTINUOUS_TOP
@@ -145,7 +149,8 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
updateNavSeaPbfButtonState() updateNavSeaPbfButtonState()
updatePbfModeButtonState() updatePbfModeButtonState()
updateRetainedCacheButtonState() updateRetainedCacheButtonState()
if (step093Mode) loadStep093Style(configureBeforeStyle = false) if (step097Topology != null) loadStep097StencilStyle()
else if (step093Mode) loadStep093Style(configureBeforeStyle = false)
else loadValidationStyle(active = active, enabled = enabled, reason = "INITIAL") else loadValidationStyle(active = active, enabled = enabled, reason = "INITIAL")
} }
@@ -317,7 +322,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
} }
private fun activateRealWeather(anchor: android.view.View?) { private fun activateRealWeather(anchor: android.view.View?) {
if (isPbfMatrixMode() || step093Mode) leaveRealWeatherMode() if (isPbfMatrixMode() || step093Mode || step097Topology != null) leaveRealWeatherMode()
realWeatherMenuAnchor = anchor ?: findViewById(R.id.validationMenuRealWeather) realWeatherMenuAnchor = anchor ?: findViewById(R.id.validationMenuRealWeather)
if (realWeatherMode) { if (realWeatherMode) {
if (realWeatherTuningMenu == null) showRealWeatherTuning(realWeatherMenuAnchor!!) else dismissRealWeatherTuning() if (realWeatherTuningMenu == null) showRealWeatherTuning(realWeatherMenuAnchor!!) else dismissRealWeatherTuning()
@@ -349,6 +354,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
private fun leaveRealWeatherMode() { private fun leaveRealWeatherMode() {
step093Mode = false step093Mode = false
step097Topology = null
step093Handler.removeCallbacks(step093DiagnosticsPoll) step093Handler.removeCallbacks(step093DiagnosticsPoll)
dismissRealWeatherTuning() dismissRealWeatherTuning()
weatherRuntime?.dispose() weatherRuntime?.dispose()
@@ -446,6 +452,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
menu.add(0, 13, 13, "CAMERA NEXT") menu.add(0, 13, 13, "CAMERA NEXT")
menu.add(0, 14, 14, "READ DIAGNOSTICS") menu.add(0, 14, 14, "READ DIAGNOSTICS")
menu.add(0, 15, 15, "RESET DEFAULT A") menu.add(0, 15, 15, "RESET DEFAULT A")
menu.add(0, 16, 16, "STEP-097 SAME SOURCE STENCIL")
setOnMenuItemClickListener { item -> setOnMenuItemClickListener { item ->
when (item.itemId) { when (item.itemId) {
in Step093Topology.values().indices -> applyStep093Scene(Step093Topology.values()[item.itemId]) in Step093Topology.values().indices -> applyStep093Scene(Step093Topology.values()[item.itemId])
@@ -455,6 +462,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
13 -> applyStep093CameraPreset() 13 -> applyStep093CameraPreset()
14 -> refreshStep093Diagnostics(log = true) 14 -> refreshStep093Diagnostics(log = true)
15 -> applyStep093Scene(Step093Topology.BOTTOM_CONTINUOUS_TOP) 15 -> applyStep093Scene(Step093Topology.BOTTOM_CONTINUOUS_TOP)
16 -> showStep097StencilMenu(anchor)
} }
true true
} }
@@ -462,6 +470,70 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
} }
} }
private fun showStep097StencilMenu(anchor: android.view.View) {
PopupMenu(this, anchor).apply {
Step097StencilFixture.Topology.values().forEachIndexed { index, topology ->
menu.add(0, index, index, "${topology.name} · same source / same tiles")
}
menu.add(0, 10, 10, "REBUILD CURRENT")
menu.add(0, 11, 11, "READ DIAGNOSTICS")
setOnMenuItemClickListener { item ->
when (item.itemId) {
in Step097StencilFixture.Topology.values().indices ->
applyStep097StencilScene(Step097StencilFixture.Topology.values()[item.itemId])
10 -> if (step097Topology != null) loadStep097StencilStyle()
11 -> if (step097Topology != null) refreshStep097Diagnostics(log = true)
}
true
}
show()
}
}
private fun applyStep097StencilScene(topology: Step097StencilFixture.Topology) {
leaveRealWeatherMode()
step097Topology = topology
retainedCacheEnabled = true
maplibreMap.setRetainedUnderlayEnabled(true)
updateRetainedCacheButtonState()
loadStep097StencilStyle()
}
private fun loadStep097StencilStyle() {
val topology = step097Topology ?: return
styleReady = false
val epoch = ++styleLoadEpoch
status.text = "STEP-097 · ${topology.name} · LOADING"
val accepted = maplibreMap.setRenderSegmentPlan(Step097StencilFixture.planJson(topology))
Log.i(TAG, "STEP097_STENCIL_PLAN topology=${topology.name} accepted=$accepted beforeStyle=true")
maplibreMap.setStyle(Style.Builder().fromJson(Step097StencilFixture.styleJson())) {
if (step097Topology != topology || epoch != styleLoadEpoch) return@setStyle
styleReady = true
maplibreMap.moveCamera(CameraUpdateFactory.newCameraPosition(
CameraPosition.Builder().target(LatLng(0.0, 0.0)).zoom(2.0).build()
))
status.text = statusText()
startStep093DiagnosticsPolling()
refreshStep097Diagnostics(log = true)
Log.i(TAG, "STEP097_STENCIL_READY topology=${topology.name} source=step097-shared-polygon")
}
}
private fun refreshStep097Diagnostics(log: Boolean) {
val topology = step097Topology ?: return
val snapshot = JSONObject(maplibreMap.getRenderSegmentDiagnostics())
diagnostics.text = buildString {
append("STEP-097 ${topology.name} valid=${snapshot.optBoolean("valid")} fallback=${snapshot.optBoolean("fallback")} topology=${snapshot.optLong("topologyRevision")} reason=${snapshot.optString("lastReason")}\n")
val segments = snapshot.optJSONArray("segments")
for (index in 0 until (segments?.length() ?: 0)) {
val segment = segments?.optJSONObject(index) ?: continue
val cache = segment.optJSONObject("cache")
append("$index ${segment.optString("strategy")} ${cache?.optString("cacheState")} build=${cache?.optLong("build")} reuse=${cache?.optLong("reuse")}\n")
}
}
if (log) Log.i(TAG, "STEP097_STENCIL_DIAGNOSTICS ${diagnostics.text.toString().replace('\n', ' ')}")
}
private fun showStep093MutationMenu(anchor: android.view.View) { private fun showStep093MutationMenu(anchor: android.view.View) {
val actions = arrayOf( val actions = arrayOf(
"CONTINUOUS ADD", "CONTINUOUS REORDER", "BOTTOM ADD", "BOTTOM REORDER", "CONTINUOUS ADD", "CONTINUOUS REORDER", "BOTTOM ADD", "BOTTOM REORDER",
@@ -845,7 +917,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
} }
private fun enterPbfMatrix() { private fun enterPbfMatrix() {
if (realWeatherMode || step093Mode) leaveRealWeatherMode() if (realWeatherMode || step093Mode || step097Topology != null) leaveRealWeatherMode()
if (!isPbfMatrixMode()) { if (!isPbfMatrixMode()) {
preRealWeatherVisuals = ParticleVisuals(particleCount, lineWidth, particleLength, particleOpacity, trailOpacity, trailFade) preRealWeatherVisuals = ParticleVisuals(particleCount, lineWidth, particleLength, particleOpacity, trailOpacity, trailFade)
particleCount = REAL_WEATHER_PARTICLE_COUNT particleCount = REAL_WEATHER_PARTICLE_COUNT
@@ -1558,6 +1630,9 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
} }
private fun statusText(): String { private fun statusText(): String {
step097Topology?.let { topology ->
return "STEP-097 · ${topology.name} · SAME SOURCE STENCIL · CACHE ${if (retainedCacheEnabled) "ON" else "OFF"}"
}
if (step093Mode) { if (step093Mode) {
return "STEP-093 · ${step093Topology.label} · ${step093Variant.name} · " + return "STEP-093 · ${step093Topology.label} · ${step093Variant.name} · " +
"CACHE ${if (retainedCacheEnabled) "ON" else "OFF"}" "CACHE ${if (retainedCacheEnabled) "ON" else "OFF"}"
@@ -1769,7 +1844,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
""".trimIndent() """.trimIndent()
} }
override fun onStart() { super.onStart(); mapView.onStart(); if (step093Mode) startStep093DiagnosticsPolling() } override fun onStart() { super.onStart(); mapView.onStart(); if (step093Mode || step097Topology != null) startStep093DiagnosticsPolling() }
override fun onResume() { super.onResume(); mapView.onResume() } override fun onResume() { super.onResume(); mapView.onResume() }
override fun onPause() { mapView.onPause(); super.onPause() } override fun onPause() { mapView.onPause(); super.onPause() }
override fun onStop() { step093Handler.removeCallbacks(step093DiagnosticsPoll); mapView.onStop(); super.onStop() } override fun onStop() { step093Handler.removeCallbacks(step093DiagnosticsPoll); mapView.onStop(); super.onStop() }
@@ -1799,6 +1874,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
outState.putBoolean(STATE_NAVSEA_PBF_RENDER_ENABLED, navSeaPbfRenderEnabled) outState.putBoolean(STATE_NAVSEA_PBF_RENDER_ENABLED, navSeaPbfRenderEnabled)
outState.putBoolean(STATE_RETAINED_CACHE_ENABLED, retainedCacheEnabled) outState.putBoolean(STATE_RETAINED_CACHE_ENABLED, retainedCacheEnabled)
outState.putBoolean(STATE_STEP093_MODE, step093Mode) outState.putBoolean(STATE_STEP093_MODE, step093Mode)
outState.putString(STATE_STEP097_TOPOLOGY, step097Topology?.name)
outState.putString(STATE_STEP093_TOPOLOGY, step093Topology.name) outState.putString(STATE_STEP093_TOPOLOGY, step093Topology.name)
outState.putString(STATE_STEP093_VARIANT, step093Variant.name) outState.putString(STATE_STEP093_VARIANT, step093Variant.name)
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
@@ -1835,6 +1911,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
private const val STATE_NAVSEA_PBF_RENDER_ENABLED = "particle_validation.navsea_pbf_render_enabled" private const val STATE_NAVSEA_PBF_RENDER_ENABLED = "particle_validation.navsea_pbf_render_enabled"
private const val STATE_RETAINED_CACHE_ENABLED = "particle_validation.retained_cache_enabled" private const val STATE_RETAINED_CACHE_ENABLED = "particle_validation.retained_cache_enabled"
private const val STATE_STEP093_MODE = "particle_validation.step093_mode" private const val STATE_STEP093_MODE = "particle_validation.step093_mode"
private const val STATE_STEP097_TOPOLOGY = "particle_validation.step097_topology"
private const val STATE_STEP093_TOPOLOGY = "particle_validation.step093_topology" private const val STATE_STEP093_TOPOLOGY = "particle_validation.step093_topology"
private const val STATE_STEP093_VARIANT = "particle_validation.step093_variant" private const val STATE_STEP093_VARIANT = "particle_validation.step093_variant"
private const val PARTICLE_LAYER_ID = "particle-raster-core" private const val PARTICLE_LAYER_ID = "particle-raster-core"

View File

@@ -0,0 +1,76 @@
package org.maplibre.android.testapp.activity.particle
import org.json.JSONArray
import org.json.JSONObject
/** Offline same-source tile fixture for retained target stencil regression. */
internal object Step097StencilFixture {
enum class Topology { RR, RCR, CRR }
const val FIRST_FILL = "step097-shared-fill-a"
const val SECOND_FILL = "step097-shared-fill-b"
const val THIRD_FILL = "step097-shared-fill-c"
private const val FOURTH_FILL = "step097-shared-fill-d"
private const val SOURCE = "step097-shared-polygon"
fun styleJson(): String {
val geometry = JSONObject()
.put("type", "Polygon")
.put("coordinates", JSONArray().put(
JSONArray().put(JSONArray().put(-45).put(-35))
.put(JSONArray().put(45).put(-35))
.put(JSONArray().put(45).put(35))
.put(JSONArray().put(-45).put(35))
.put(JSONArray().put(-45).put(-35))
))
val source = JSONObject().put("type", "geojson").put("data", JSONObject()
.put("type", "Feature")
.put("properties", JSONObject())
.put("geometry", geometry))
val layers = JSONArray()
.put(JSONObject().put("id", "step097-background").put("type", "background")
.put("paint", JSONObject().put("background-color", "#15243a")))
.put(fill(FIRST_FILL, "#f94144"))
.put(fill(SECOND_FILL, "#43aa8b"))
.put(fill(THIRD_FILL, "#577590"))
.put(fill(FOURTH_FILL, "#f9c74f"))
return JSONObject()
.put("version", 8)
.put("name", "STEP-097 same-source stencil fixture")
.put("sources", JSONObject().put(SOURCE, source))
.put("layers", layers)
.toString()
}
fun planJson(topology: Topology): String {
val segments = JSONArray()
fun add(strategy: String, start: String? = null) {
val segment = JSONObject().put("strategy", strategy)
if (start != null) segment.put("startLayerId", start)
segments.put(segment)
}
when (topology) {
Topology.RR -> {
add("RETAINED")
add("RETAINED", SECOND_FILL)
}
Topology.RCR -> {
add("RETAINED")
add("CONTINUOUS", SECOND_FILL)
add("RETAINED", THIRD_FILL)
}
Topology.CRR -> {
add("CONTINUOUS")
add("RETAINED", SECOND_FILL)
add("RETAINED", THIRD_FILL)
}
}
return JSONObject().put("schemaVersion", 1).put("segments", segments).toString()
}
private fun fill(id: String, color: String): JSONObject = JSONObject()
.put("id", id)
.put("type", "fill")
.put("source", SOURCE)
.put("paint", JSONObject().put("fill-color", color).put("fill-opacity", 0.55))
}

View File

@@ -11,6 +11,39 @@
namespace mbgl { namespace mbgl {
namespace mtl { namespace mtl {
detail::ClearedAttachments detail::applyRenderPassClears(MTL::RenderPassDescriptor& rpd,
const std::optional<Color>& clearColor,
const std::optional<float>& clearDepth,
const std::optional<int32_t>& clearStencil) {
ClearedAttachments result;
if (clearColor) {
if (auto* target = rpd.colorAttachments()->object(0); target && target->texture()) {
const auto& c = *clearColor;
target->setLoadAction(MTL::LoadActionClear);
target->setClearColor(MTL::ClearColor::Make(c.r, c.g, c.b, c.a));
target->setStoreAction(MTL::StoreActionStore);
result.color = true;
}
}
if (clearDepth) {
if (auto* target = rpd.depthAttachment(); target && target->texture()) {
target->setLoadAction(MTL::LoadActionClear);
target->setClearDepth(*clearDepth);
target->setStoreAction(MTL::StoreActionStore);
result.depth = true;
}
}
if (clearStencil) {
if (auto* target = rpd.stencilAttachment(); target && target->texture()) {
target->setLoadAction(MTL::LoadActionClear);
target->setClearStencil(static_cast<uint32_t>(*clearStencil));
target->setStoreAction(MTL::StoreActionStore);
result.stencil = true;
}
}
return result;
}
RenderPass::RenderPass(CommandEncoder& commandEncoder_, const char* name, const gfx::RenderPassDescriptor& descriptor) RenderPass::RenderPass(CommandEncoder& commandEncoder_, const char* name, const gfx::RenderPassDescriptor& descriptor)
: descriptor(descriptor), : descriptor(descriptor),
commandEncoder(commandEncoder_) { commandEncoder(commandEncoder_) {
@@ -22,28 +55,7 @@ RenderPass::RenderPass(CommandEncoder& commandEncoder_, const char* name, const
if (const auto& original = resource.getRenderPassDescriptor()) { if (const auto& original = resource.getRenderPassDescriptor()) {
auto rpd = NS::TransferPtr(original->copy()); auto rpd = NS::TransferPtr(original->copy());
if (!rpd) throw std::runtime_error("Metal render pass descriptor copy failed"); if (!rpd) throw std::runtime_error("Metal render pass descriptor copy failed");
if (descriptor.clearColor) { detail::applyRenderPassClears(*rpd, descriptor.clearColor, descriptor.clearDepth, descriptor.clearStencil);
if (auto* target = rpd->colorAttachments()->object(0); target && target->texture()) {
const auto& c = *descriptor.clearColor;
target->setLoadAction(MTL::LoadActionClear);
target->setClearColor(MTL::ClearColor::Make(c.r, c.g, c.b, c.a));
target->setStoreAction(MTL::StoreActionStore);
}
}
if (descriptor.clearDepth) {
if (auto* target = rpd->depthAttachment(); target && target->texture()) {
target->setLoadAction(MTL::LoadActionClear);
target->setClearDepth(*descriptor.clearDepth);
target->setStoreAction(MTL::StoreActionStore);
}
}
if (descriptor.clearStencil) {
if (auto* target = rpd->stencilAttachment(); target && target->texture()) {
target->setLoadAction(MTL::LoadActionClear);
target->setClearStencil(static_cast<uint32_t>(*descriptor.clearStencil));
target->setStoreAction(MTL::StoreActionStore);
}
}
encoder = NS::RetainPtr(buffer->renderCommandEncoder(rpd.get())); encoder = NS::RetainPtr(buffer->renderCommandEncoder(rpd.get()));
if (!encoder) throw std::runtime_error("Metal render command encoder unavailable"); if (!encoder) throw std::runtime_error("Metal render command encoder unavailable");

View File

@@ -29,6 +29,7 @@ add_library(
${PROJECT_SOURCE_DIR}/test/renderer/pattern_atlas.test.cpp ${PROJECT_SOURCE_DIR}/test/renderer/pattern_atlas.test.cpp
${PROJECT_SOURCE_DIR}/test/renderer/particle_viewport_reseed.test.cpp ${PROJECT_SOURCE_DIR}/test/renderer/particle_viewport_reseed.test.cpp
${PROJECT_SOURCE_DIR}/test/renderer/retained_underlay_cache.test.cpp ${PROJECT_SOURCE_DIR}/test/renderer/retained_underlay_cache.test.cpp
${PROJECT_SOURCE_DIR}/test/renderer/render_orchestrator_error.test.cpp
${PROJECT_SOURCE_DIR}/test/renderer/shader_registry.test.cpp ${PROJECT_SOURCE_DIR}/test/renderer/shader_registry.test.cpp
$<$<BOOL:${MLN_WITH_WEBGPU}>:${PROJECT_SOURCE_DIR}/test/renderer/wgsl_preprocessor.test.cpp> $<$<BOOL:${MLN_WITH_WEBGPU}>:${PROJECT_SOURCE_DIR}/test/renderer/wgsl_preprocessor.test.cpp>
${PROJECT_SOURCE_DIR}/test/sprite/sprite_loader.test.cpp ${PROJECT_SOURCE_DIR}/test/sprite/sprite_loader.test.cpp
@@ -160,6 +161,10 @@ if(MLN_WITH_OPENGL)
) )
endif() endif()
if(MLN_WITH_METAL)
target_sources(mbgl-test PRIVATE ${PROJECT_SOURCE_DIR}/test/renderer/mtl_retained_attachments.test.cpp)
endif()
if(MLN_WITH_VULKAN) if(MLN_WITH_VULKAN)
target_sources( target_sources(

View File

@@ -0,0 +1,101 @@
#if MLN_RENDER_BACKEND_METAL
#include <mbgl/mtl/context.hpp>
#include <mbgl/mtl/offscreen_texture.hpp>
#include <mbgl/mtl/render_pass.hpp>
#include <mbgl/mtl/renderable_resource.hpp>
#include <mbgl/mtl/renderer_backend.hpp>
#include <Foundation/Foundation.hpp>
#include <Metal/Metal.hpp>
#include <gtest/gtest.h>
#include <cstdlib>
using namespace mbgl;
namespace {
class TestMetalBackend final : public mtl::RendererBackend {
public:
TestMetalBackend() : RendererBackend(gfx::ContextMode::Unique) {}
gfx::Renderable& getDefaultRenderable() override { std::abort(); }
void updateAssumedState() override {}
private:
void activate() override {}
void deactivate() override {}
};
} // namespace
TEST(MetalRetainedAttachments, ClearActionsApplyToCombinedDepthStencil) {
auto pool = NS::TransferPtr(NS::AutoreleasePool::alloc()->init());
auto device = NS::TransferPtr(MTL::CreateSystemDefaultDevice());
if (!device) GTEST_SKIP() << "Metal device unavailable";
auto colorDescriptor = MTL::TextureDescriptor::texture2DDescriptor(MTL::PixelFormatRGBA8Unorm, 4, 4, false);
auto combinedDescriptor =
MTL::TextureDescriptor::texture2DDescriptor(MTL::PixelFormatDepth32Float_Stencil8, 4, 4, false);
colorDescriptor->setUsage(MTL::TextureUsageRenderTarget);
combinedDescriptor->setUsage(MTL::TextureUsageRenderTarget);
auto colorTexture = NS::TransferPtr(device->newTexture(colorDescriptor));
auto combinedTexture = NS::TransferPtr(device->newTexture(combinedDescriptor));
if (!combinedTexture) GTEST_SKIP() << "Combined depth-stencil texture unavailable";
ASSERT_TRUE(colorTexture);
auto descriptor = NS::TransferPtr(MTL::RenderPassDescriptor::alloc()->init());
descriptor->colorAttachments()->object(0)->setTexture(colorTexture.get());
descriptor->depthAttachment()->setTexture(combinedTexture.get());
descriptor->stencilAttachment()->setTexture(combinedTexture.get());
const auto applied = mtl::detail::applyRenderPassClears(
*descriptor, Color{0.2f, 0.3f, 0.4f, 0.5f}, 1.0f, 0);
EXPECT_TRUE(applied.color);
EXPECT_TRUE(applied.depth);
EXPECT_TRUE(applied.stencil);
EXPECT_EQ(descriptor->colorAttachments()->object(0)->loadAction(), MTL::LoadActionClear);
EXPECT_EQ(descriptor->depthAttachment()->loadAction(), MTL::LoadActionClear);
EXPECT_EQ(descriptor->stencilAttachment()->loadAction(), MTL::LoadActionClear);
EXPECT_EQ(descriptor->colorAttachments()->object(0)->storeAction(), MTL::StoreActionStore);
EXPECT_EQ(descriptor->depthAttachment()->storeAction(), MTL::StoreActionStore);
EXPECT_EQ(descriptor->stencilAttachment()->storeAction(), MTL::StoreActionStore);
EXPECT_DOUBLE_EQ(descriptor->depthAttachment()->clearDepth(), 1.0);
EXPECT_EQ(descriptor->stencilAttachment()->clearStencil(), 0u);
EXPECT_EQ(descriptor->depthAttachment()->texture(), descriptor->stencilAttachment()->texture());
}
TEST(MetalRetainedAttachments, OffscreenBindsRequestedAttachmentsAndSkipsAbsentOnes) {
auto pool = NS::TransferPtr(NS::AutoreleasePool::alloc()->init());
TestMetalBackend backend;
mtl::Context context{backend};
mtl::OffscreenTexture retained(context, {4, 4}, gfx::TextureChannelDataType::UnsignedByte, true, true);
ASSERT_TRUE(retained.isRenderable());
auto& retainedResource = retained.getResource<mtl::RenderableResource>();
retainedResource.bind();
const auto& retainedDescriptor = retainedResource.getRenderPassDescriptor();
ASSERT_TRUE(retainedDescriptor);
ASSERT_NE(retainedDescriptor->colorAttachments()->object(0)->texture(), nullptr);
ASSERT_NE(retainedDescriptor->depthAttachment()->texture(), nullptr);
ASSERT_NE(retainedDescriptor->stencilAttachment()->texture(), nullptr);
#if TARGET_OS_SIMULATOR || defined(__x86_64__)
EXPECT_EQ(retainedDescriptor->depthAttachment()->texture(), retainedDescriptor->stencilAttachment()->texture());
#else
EXPECT_NE(retainedDescriptor->depthAttachment()->texture(), retainedDescriptor->stencilAttachment()->texture());
#endif
mtl::OffscreenTexture colorOnly(context, {4, 4}, gfx::TextureChannelDataType::UnsignedByte, false, false);
ASSERT_TRUE(colorOnly.isRenderable());
auto& colorResource = colorOnly.getResource<mtl::RenderableResource>();
colorResource.bind();
const auto& colorDescriptor = colorResource.getRenderPassDescriptor();
ASSERT_TRUE(colorDescriptor);
EXPECT_EQ(colorDescriptor->depthAttachment()->texture(), nullptr);
EXPECT_EQ(colorDescriptor->stencilAttachment()->texture(), nullptr);
const auto applied = mtl::detail::applyRenderPassClears(*colorDescriptor, std::nullopt, 1.0f, 0);
EXPECT_FALSE(applied.depth);
EXPECT_FALSE(applied.stencil);
}
#endif // MLN_RENDER_BACKEND_METAL

View File

@@ -0,0 +1,110 @@
#include <mbgl/actor/scheduler.hpp>
#include <mbgl/gfx/uniform_buffer.hpp>
#include <mbgl/renderer/layer_group.hpp>
#include <mbgl/renderer/render_orchestrator.hpp>
#include <mbgl/renderer/renderer_observer.hpp>
#include <mbgl/renderer/retained_underlay_cache.hpp>
#include <gtest/gtest.h>
#include <stdexcept>
using namespace mbgl;
namespace {
class TestUniformBuffers final : public gfx::UniformBufferArray {
public:
void bind(gfx::RenderPass&) override {}
protected:
std::unique_ptr<gfx::UniformBuffer> copy(const gfx::UniformBuffer&) override { return nullptr; }
};
class TestLayerGroup final : public LayerGroup {
public:
explicit TestLayerGroup(int32_t index)
: LayerGroup(index, 0, "step097-error-group") {}
const gfx::UniformBufferArray& getUniformBuffers() const override { return buffers; }
gfx::UniformBufferArray& mutableUniformBuffers() override { return buffers; }
private:
TestUniformBuffers buffers;
};
class ErrorObserver final : public RendererObserver {
public:
void onRenderError(std::exception_ptr error) override {
++errors;
try {
std::rethrow_exception(error);
} catch (const std::runtime_error& e) {
message = e.what();
}
}
int errors = 0;
std::string message;
};
} // namespace
TEST(RenderOrchestratorError, RangeVisitorReportsOpaqueAndTranslucentFailures) {
TaggedScheduler scheduler{Scheduler::GetBackground(), {}};
RenderOrchestrator orchestrator(false, scheduler, std::nullopt);
ErrorObserver observer;
orchestrator.setObserver(&observer);
ASSERT_TRUE(orchestrator.addLayerGroup(std::make_shared<TestLayerGroup>(0)));
ASSERT_TRUE(orchestrator.addLayerGroup(std::make_shared<TestLayerGroup>(1)));
bool failed = false;
orchestrator.visitLayerGroupsInRangeReversed(0, 2, [](LayerGroupBase&, size_t) {
throw std::runtime_error("opaque failure");
}, &failed);
EXPECT_TRUE(failed);
EXPECT_EQ(observer.errors, 2);
EXPECT_EQ(observer.message, "opaque failure");
failed = false;
observer.errors = 0;
orchestrator.visitLayerGroupsInRange(0, 2, [](LayerGroupBase&, size_t) {
throw std::runtime_error("translucent failure");
}, &failed);
EXPECT_TRUE(failed);
EXPECT_EQ(observer.errors, 2);
EXPECT_EQ(observer.message, "translucent failure");
failed = false;
int visits = 0;
orchestrator.visitLayerGroupsInRange(0, 2, [&](LayerGroupBase&, size_t) { ++visits; }, &failed);
EXPECT_FALSE(failed);
EXPECT_EQ(visits, 2);
}
TEST(RenderOrchestratorError, FailedRangeCannotReuseAndCleanRetryCanRecover) {
TaggedScheduler scheduler{Scheduler::GetBackground(), {}};
RenderOrchestrator orchestrator(false, scheduler, std::nullopt);
ErrorObserver observer;
orchestrator.setObserver(&observer);
ASSERT_TRUE(orchestrator.addLayerGroup(std::make_shared<TestLayerGroup>(0)));
RetainedUnderlayCache cache;
RetainedUnderlayKey key{{4, 4}, 0, 1, {}};
key.cameraMatrix[0] = key.cameraMatrix[5] = key.cameraMatrix[10] = key.cameraMatrix[15] = 1.0;
for (bool opaque : {true, false}) {
EXPECT_EQ(cache.evaluate(key, 1, true), RetainedUnderlayDecision::Build);
bool failed = false;
const auto throwOnRender = [](LayerGroupBase&, size_t) { throw std::runtime_error("render failure"); };
if (opaque) orchestrator.visitLayerGroupsInRangeReversed(0, 1, throwOnRender, &failed);
else orchestrator.visitLayerGroupsInRange(0, 1, throwOnRender, &failed);
ASSERT_TRUE(failed);
cache.markBuildFailed("layer-render-error");
EXPECT_FALSE(cache.isValid());
EXPECT_EQ(cache.evaluate(key, 1, true), RetainedUnderlayDecision::Build);
}
bool failed = false;
orchestrator.visitLayerGroupsInRange(0, 1, [](LayerGroupBase&, size_t) {}, &failed);
ASSERT_FALSE(failed);
cache.markBuildSucceeded();
EXPECT_EQ(cache.evaluate(key, 1, true), RetainedUnderlayDecision::Reuse);
}