test(step097): add A1 renderer regression entry points
This commit is contained in:
@@ -17,6 +17,19 @@ class BufferResource;
|
||||
class CommandEncoder;
|
||||
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 {
|
||||
public:
|
||||
RenderPass(CommandEncoder&, const char* name, const gfx::RenderPassDescriptor&);
|
||||
|
||||
@@ -55,6 +55,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
private var navSeaPbfLayerVisibility = emptyList<NavSeaPbfLayerVisibility>()
|
||||
private var retainedCacheEnabled = true
|
||||
private var step093Mode = false
|
||||
private var step097Topology: Step097StencilFixture.Topology? = null
|
||||
private var step093Topology = Step093Topology.BOTTOM_CONTINUOUS_TOP
|
||||
private var step093Variant = Step093Variant.DEFAULT
|
||||
private var step093StartVisible = true
|
||||
@@ -63,8 +64,9 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
private val step093Handler = Handler(Looper.getMainLooper())
|
||||
private val step093DiagnosticsPoll = object : Runnable {
|
||||
override fun run() {
|
||||
if (step093Mode && this@ParticleCoreLayerValidationActivity::maplibreMap.isInitialized) {
|
||||
refreshStep093Diagnostics(log = false)
|
||||
if ((step093Mode || step097Topology != null) && this@ParticleCoreLayerValidationActivity::maplibreMap.isInitialized) {
|
||||
if (step097Topology != null) refreshStep097Diagnostics(log = false)
|
||||
else refreshStep093Diagnostics(log = false)
|
||||
step093Handler.postDelayed(this, STEP093_DIAGNOSTICS_INTERVAL_MS)
|
||||
}
|
||||
}
|
||||
@@ -107,6 +109,8 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
navSeaPbfRenderEnabled = savedInstanceState?.getBoolean(STATE_NAVSEA_PBF_RENDER_ENABLED, true) ?: true
|
||||
retainedCacheEnabled = savedInstanceState?.getBoolean(STATE_RETAINED_CACHE_ENABLED, true) ?: true
|
||||
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)
|
||||
?.let { value -> Step093Topology.values().firstOrNull { it.name == value } }
|
||||
?: Step093Topology.BOTTOM_CONTINUOUS_TOP
|
||||
@@ -145,7 +149,8 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
updateNavSeaPbfButtonState()
|
||||
updatePbfModeButtonState()
|
||||
updateRetainedCacheButtonState()
|
||||
if (step093Mode) loadStep093Style(configureBeforeStyle = false)
|
||||
if (step097Topology != null) loadStep097StencilStyle()
|
||||
else if (step093Mode) loadStep093Style(configureBeforeStyle = false)
|
||||
else loadValidationStyle(active = active, enabled = enabled, reason = "INITIAL")
|
||||
}
|
||||
|
||||
@@ -317,7 +322,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private fun activateRealWeather(anchor: android.view.View?) {
|
||||
if (isPbfMatrixMode() || step093Mode) leaveRealWeatherMode()
|
||||
if (isPbfMatrixMode() || step093Mode || step097Topology != null) leaveRealWeatherMode()
|
||||
realWeatherMenuAnchor = anchor ?: findViewById(R.id.validationMenuRealWeather)
|
||||
if (realWeatherMode) {
|
||||
if (realWeatherTuningMenu == null) showRealWeatherTuning(realWeatherMenuAnchor!!) else dismissRealWeatherTuning()
|
||||
@@ -349,6 +354,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
|
||||
private fun leaveRealWeatherMode() {
|
||||
step093Mode = false
|
||||
step097Topology = null
|
||||
step093Handler.removeCallbacks(step093DiagnosticsPoll)
|
||||
dismissRealWeatherTuning()
|
||||
weatherRuntime?.dispose()
|
||||
@@ -446,6 +452,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
menu.add(0, 13, 13, "CAMERA NEXT")
|
||||
menu.add(0, 14, 14, "READ DIAGNOSTICS")
|
||||
menu.add(0, 15, 15, "RESET DEFAULT A")
|
||||
menu.add(0, 16, 16, "STEP-097 SAME SOURCE STENCIL")
|
||||
setOnMenuItemClickListener { item ->
|
||||
when (item.itemId) {
|
||||
in Step093Topology.values().indices -> applyStep093Scene(Step093Topology.values()[item.itemId])
|
||||
@@ -455,6 +462,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
13 -> applyStep093CameraPreset()
|
||||
14 -> refreshStep093Diagnostics(log = true)
|
||||
15 -> applyStep093Scene(Step093Topology.BOTTOM_CONTINUOUS_TOP)
|
||||
16 -> showStep097StencilMenu(anchor)
|
||||
}
|
||||
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) {
|
||||
val actions = arrayOf(
|
||||
"CONTINUOUS ADD", "CONTINUOUS REORDER", "BOTTOM ADD", "BOTTOM REORDER",
|
||||
@@ -845,7 +917,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private fun enterPbfMatrix() {
|
||||
if (realWeatherMode || step093Mode) leaveRealWeatherMode()
|
||||
if (realWeatherMode || step093Mode || step097Topology != null) leaveRealWeatherMode()
|
||||
if (!isPbfMatrixMode()) {
|
||||
preRealWeatherVisuals = ParticleVisuals(particleCount, lineWidth, particleLength, particleOpacity, trailOpacity, trailFade)
|
||||
particleCount = REAL_WEATHER_PARTICLE_COUNT
|
||||
@@ -1558,6 +1630,9 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private fun statusText(): String {
|
||||
step097Topology?.let { topology ->
|
||||
return "STEP-097 · ${topology.name} · SAME SOURCE STENCIL · CACHE ${if (retainedCacheEnabled) "ON" else "OFF"}"
|
||||
}
|
||||
if (step093Mode) {
|
||||
return "STEP-093 · ${step093Topology.label} · ${step093Variant.name} · " +
|
||||
"CACHE ${if (retainedCacheEnabled) "ON" else "OFF"}"
|
||||
@@ -1769,7 +1844,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
""".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 onPause() { mapView.onPause(); super.onPause() }
|
||||
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_RETAINED_CACHE_ENABLED, retainedCacheEnabled)
|
||||
outState.putBoolean(STATE_STEP093_MODE, step093Mode)
|
||||
outState.putString(STATE_STEP097_TOPOLOGY, step097Topology?.name)
|
||||
outState.putString(STATE_STEP093_TOPOLOGY, step093Topology.name)
|
||||
outState.putString(STATE_STEP093_VARIANT, step093Variant.name)
|
||||
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_RETAINED_CACHE_ENABLED = "particle_validation.retained_cache_enabled"
|
||||
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_VARIANT = "particle_validation.step093_variant"
|
||||
private const val PARTICLE_LAYER_ID = "particle-raster-core"
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
@@ -11,6 +11,39 @@
|
||||
namespace mbgl {
|
||||
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)
|
||||
: descriptor(descriptor),
|
||||
commandEncoder(commandEncoder_) {
|
||||
@@ -22,28 +55,7 @@ RenderPass::RenderPass(CommandEncoder& commandEncoder_, const char* name, const
|
||||
if (const auto& original = resource.getRenderPassDescriptor()) {
|
||||
auto rpd = NS::TransferPtr(original->copy());
|
||||
if (!rpd) throw std::runtime_error("Metal render pass descriptor copy failed");
|
||||
if (descriptor.clearColor) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
detail::applyRenderPassClears(*rpd, descriptor.clearColor, descriptor.clearDepth, descriptor.clearStencil);
|
||||
encoder = NS::RetainPtr(buffer->renderCommandEncoder(rpd.get()));
|
||||
if (!encoder) throw std::runtime_error("Metal render command encoder unavailable");
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ add_library(
|
||||
${PROJECT_SOURCE_DIR}/test/renderer/pattern_atlas.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/render_orchestrator_error.test.cpp
|
||||
${PROJECT_SOURCE_DIR}/test/renderer/shader_registry.test.cpp
|
||||
$<$<BOOL:${MLN_WITH_WEBGPU}>:${PROJECT_SOURCE_DIR}/test/renderer/wgsl_preprocessor.test.cpp>
|
||||
${PROJECT_SOURCE_DIR}/test/sprite/sprite_loader.test.cpp
|
||||
@@ -160,6 +161,10 @@ if(MLN_WITH_OPENGL)
|
||||
)
|
||||
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)
|
||||
target_sources(
|
||||
|
||||
101
test/renderer/mtl_retained_attachments.test.cpp
Normal file
101
test/renderer/mtl_retained_attachments.test.cpp
Normal 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
|
||||
110
test/renderer/render_orchestrator_error.test.cpp
Normal file
110
test/renderer/render_orchestrator_error.test.cpp
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user