test(step097): add A1 renderer regression entry points
This commit is contained in:
@@ -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&);
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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 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");
|
||||||
|
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
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