Subject: [PATCH 21/24] drm: generalize tiled startup topology selection

From: Justin Youens <[email-masked]>
Date: Sat, 15 Aug 2026 16:21:16 -0500

---
 src/backend/drm/DRM.cpp                 |  18 +-
 src/backend/drm/TiledDRMIntegration.cpp | 244 +++++++++++++++++-------
 src/backend/drm/TiledDRMIntegration.hpp |  11 ++
 3 files changed, 195 insertions(+), 78 deletions(-)

diff --git a/src/backend/drm/DRM.cpp b/src/backend/drm/DRM.cpp
index 39174449a51a74cca3a9660e0725f4bb21f6b850..90bdeae1b2561736b81087b37c5c4bc1b7662bdc 100644
--- a/src/backend/drm/DRM.cpp
+++ b/src/backend/drm/DRM.cpp
@@ -111,7 +111,9 @@ namespace {
         if (!LOGGER || !probe.attempted)
             return;
         if (probe.test) {
-            LOGGER->log(AQ_LOG_WARNING, std::format("drm: AQ_DRM_TILED_PROBE=1 TEST_ONLY PASS on {}", device));
+            LOGGER->log(AQ_LOG_WARNING,
+                        std::format("drm: AQ_DRM_TILED_PROBE=1 TEST_ONLY PASS {}x{}@{:.2f}Hz members={} preferred={} on {}", probe.mode.width, probe.mode.height,
+                                    probe.mode.refreshRate / 1000.0, probe.mode.memberCount, probe.mode.preferred, device));
             return;
         }
         const auto EXECUTION = probe.test.executionStatus ? static_cast<uint32_t>(*probe.test.executionStatus) : std::numeric_limits<uint32_t>::max();
@@ -1913,7 +1915,9 @@ void Aquamarine::CDRMBackend::recheckOutputs() {
         if (tiledStartupEnabled()) {
             const auto STARTUP = NDRM::NTiledDRMIntegration::prepareGroupedStartup(SELF, connectors, CAPTURE_PROVIDER);
             if (STARTUP)
-                backend->log(AQ_LOG_WARNING, std::format("drm: AQ_DRM_TILED_STARTUP=1 PREPARED 5120x2880@60 aggregate on {}", gpu->path));
+                backend->log(AQ_LOG_WARNING,
+                             std::format("drm: AQ_DRM_TILED_STARTUP=1 PREPARED {}x{}@{:.2f}Hz aggregate members={} preferred={} on {}", STARTUP.mode.width,
+                                         STARTUP.mode.height, STARTUP.mode.refreshRate / 1000.0, STARTUP.mode.memberCount, STARTUP.mode.preferred, gpu->path));
             else
                 backend->log(AQ_LOG_ERROR,
                              std::format("drm: AQ_DRM_TILED_STARTUP=1 FALLBACK ordinary outputs on {} status={} test={} route={}", gpu->path, static_cast<uint32_t>(STARTUP.status),
@@ -3308,10 +3312,12 @@ bool Aquamarine::CDRMOutput::commitState(bool onlyTest) {
                 return true;
 
             const auto& TRANSACTION_RESULT = RESULT.initial ? RESULT.initialFrame.transaction : RESULT.frame.transaction;
-            BACKEND->log(AQ_LOG_DEBUG,
-                         std::format("drm: AQ_DRM_TILED_STARTUP=1 {} {} accepted transaction={} status={} kms={} runtime={} registry={}", RESULT.initial ? "initial" : "frame",
-                                     name, TRANSACTION, static_cast<uint32_t>(TRANSACTION_RESULT.status), TRANSACTION_RESULT.effects.kmsAccepted,
-                                     TRANSACTION_RESULT.effects.runtimeAccepted, TRANSACTION_RESULT.effects.registryAccepted));
+            if (RESULT.initial) {
+                BACKEND->log(AQ_LOG_DEBUG,
+                             std::format("drm: AQ_DRM_TILED_STARTUP=1 initial {} accepted transaction={} status={} kms={} runtime={} registry={}", name, TRANSACTION,
+                                         static_cast<uint32_t>(TRANSACTION_RESULT.status), TRANSACTION_RESULT.effects.kmsAccepted,
+                                         TRANSACTION_RESULT.effects.runtimeAccepted, TRANSACTION_RESULT.effects.registryAccepted));
+            }
 
             enabledState       = true;
             connector->refresh = MODE->refreshRate;
diff --git a/src/backend/drm/TiledDRMIntegration.cpp b/src/backend/drm/TiledDRMIntegration.cpp
index ac1b9baf20722a46164495179225016fa4157f14..188ee7802e8a69a2ddd4ec6af20496d8530f1217 100644
--- a/src/backend/drm/TiledDRMIntegration.cpp
+++ b/src/backend/drm/TiledDRMIntegration.cpp
@@ -110,10 +110,135 @@ namespace {
         return lhs.impl_ != nullptr && lhs.impl_ == rhs.impl_;
     }
 
+    constexpr size_t GROUPED_STARTUP_MIN_MEMBERS     = 2;
+    constexpr size_t GROUPED_STARTUP_MAX_MEMBERS     = std::numeric_limits<uint32_t>::digits;
+    constexpr size_t GROUPED_STARTUP_RELEASE_BUFFERS = GROUPED_STARTUP_MAX_MEMBERS * 3U;
+
+    enum class eBoundedGroupSelection : uint8_t {
+        SELECTED,
+        NO_ALLOCATED_GROUP,
+        INVALID_GROUP,
+        MULTIPLE_ALLOCATED_GROUPS,
+    };
+
+    struct SBoundedGroupSelection {
+        eBoundedGroupSelection       status = eBoundedGroupSelection::NO_ALLOCATED_GROUP;
+        const CGroupProjection*      group  = nullptr;
+        CSharedPointer<SOutputMode>  mode;
+        SGroupedModeDiagnostics      diagnostics;
+    };
+
+    [[nodiscard]] bool exactBoundedAllocation(const CGroupProjection& candidate) {
+        if (candidate.projection() != eGroupProjection::MULTI_STREAM_ALLOCATED || !candidate.scanDefinition() || !candidate.allocationDefinition() ||
+            !candidate.modeDefinition() || !candidate.modeSet() || !candidate.definitionIdentity())
+            return false;
+
+        const auto  ALLOCATION = candidate.allocation();
+        const auto& TOPOLOGY   = candidate.scanDefinition()->group();
+        if (!ALLOCATION || ALLOCATION->get().status() != eGroupStatus::ALLOCATED || TOPOLOGY.kind != NTiling::eTileGroupKind::MULTI_STREAM ||
+            TOPOLOGY.members.size() < GROUPED_STARTUP_MIN_MEMBERS || TOPOLOGY.members.size() > GROUPED_STARTUP_MAX_MEMBERS || TOPOLOGY.primaryConnector == 0 ||
+            !sameOwner(ALLOCATION->get().definition(), candidate.allocationDefinition()) ||
+            !sameOwner(candidate.allocationDefinition()->scanDefinition(), candidate.scanDefinition()) || candidate.allocationDefinition()->group() != TOPOLOGY ||
+            !sameOwner(candidate.modeSet()->definition(), candidate.modeDefinition()) || !sameOwner(candidate.modeDefinition()->definition(), candidate.scanDefinition()) ||
+            ALLOCATION->get().bindings().size() != TOPOLOGY.members.size())
+            return false;
+
+        std::set<uint32_t> connectorIds;
+        std::set<uint32_t> crtcIds;
+        std::set<uint32_t> planeIds;
+        for (size_t index = 0; index < TOPOLOGY.members.size(); ++index) {
+            const auto& MEMBER  = TOPOLOGY.members.at(index);
+            const auto& BINDING = ALLOCATION->get().bindings().at(index);
+            if (!BINDING.tiled() || !sameOwner(BINDING.definition(), candidate.allocationDefinition()) || !BINDING.connector() || !BINDING.connectorIncarnation() ||
+                !BINDING.crtc() || !BINDING.crtcIncarnation() || !BINDING.primaryPlane() || !BINDING.primaryPlaneIncarnation() ||
+                BINDING.connector()->id != MEMBER.connectorId || BINDING.tileX() != MEMBER.tileHLoc || BINDING.tileY() != MEMBER.tileVLoc || BINDING.crtc()->id == 0 ||
+                BINDING.primaryPlane()->id == 0 || !connectorIds.insert(BINDING.connector()->id).second || !crtcIds.insert(BINDING.crtc()->id).second ||
+                !planeIds.insert(BINDING.primaryPlane()->id).second)
+                return false;
+        }
+        return connectorIds.contains(TOPOLOGY.primaryConnector);
+    }
+
+    [[nodiscard]] const SModeBinding* selectBoundedMode(const CGroupProjection& candidate) {
+        const auto& MODE_SET = candidate.modeSet();
+        if (!MODE_SET || !MODE_SET->definition() || MODE_SET->modes().empty() || MODE_SET->modes().size() != MODE_SET->definition()->logicalModes().size())
+            return nullptr;
+
+        const SModeBinding* firstValid = nullptr;
+        for (const auto& BINDING : MODE_SET->modes()) {
+            const auto* LOGICAL = MODE_SET->logicalModeFor(BINDING.outputMode);
+            const auto* MAPPING = MODE_SET->physicalMappingFor(BINDING.outputMode);
+            if (!BINDING.outputMode || !LOGICAL || !MAPPING || LOGICAL->id == 0 || BINDING.logicalModeId != LOGICAL->id || MAPPING->logicalModeId != LOGICAL->id ||
+                LOGICAL->width == 0 || LOGICAL->height == 0 || LOGICAL->refreshRate == 0 || BINDING.outputMode->modeInfo.has_value() ||
+                BINDING.outputMode->pixelSize.x != static_cast<double>(LOGICAL->width) || BINDING.outputMode->pixelSize.y != static_cast<double>(LOGICAL->height) ||
+                BINDING.outputMode->refreshRate != LOGICAL->refreshRate || BINDING.outputMode->preferred != LOGICAL->preferred ||
+                MAPPING->members.size() != candidate.scanDefinition()->group().members.size())
+                continue;
+
+            bool exactMapping = true;
+            for (size_t index = 0; index < MAPPING->members.size(); ++index) {
+                const auto& MEMBER   = candidate.scanDefinition()->group().members.at(index);
+                const auto& PHYSICAL = MAPPING->members.at(index);
+                if (PHYSICAL.connectorId != MEMBER.connectorId || PHYSICAL.modeInfo.hdisplay != MEMBER.width || PHYSICAL.modeInfo.vdisplay != MEMBER.height) {
+                    exactMapping = false;
+                    break;
+                }
+            }
+            if (!exactMapping)
+                continue;
+            if (LOGICAL->preferred)
+                return &BINDING;
+            if (!firstValid)
+                firstValid = &BINDING;
+        }
+        return firstValid;
+    }
+
+    [[nodiscard]] SBoundedGroupSelection selectBoundedGroup(const POutputBridgeObservation& observation) {
+        if (!observation)
+            return {.status = eBoundedGroupSelection::INVALID_GROUP};
+
+        // Bounded checkpoint: production intentionally supports exactly one
+        // allocated aggregate group per backend. A second allocation is an
+        // explicit status, never an implicit first-group choice.
+        const CGroupProjection* selected = nullptr;
+        for (const auto& CANDIDATE : observation->groups()) {
+            if (CANDIDATE.projection() != eGroupProjection::MULTI_STREAM_ALLOCATED)
+                continue;
+            if (selected)
+                return {.status = eBoundedGroupSelection::MULTIPLE_ALLOCATED_GROUPS};
+            selected = &CANDIDATE;
+        }
+        if (!selected)
+            return {.status = eBoundedGroupSelection::NO_ALLOCATED_GROUP};
+        if (!exactBoundedAllocation(*selected))
+            return {.status = eBoundedGroupSelection::INVALID_GROUP};
+
+        const auto* MODE = selectBoundedMode(*selected);
+        if (!MODE || !MODE->outputMode)
+            return {.status = eBoundedGroupSelection::INVALID_GROUP};
+        const auto* LOGICAL = selected->modeSet()->logicalModeFor(MODE->outputMode);
+        if (!LOGICAL)
+            return {.status = eBoundedGroupSelection::INVALID_GROUP};
+        return {
+            .status = eBoundedGroupSelection::SELECTED,
+            .group  = selected,
+            .mode   = MODE->outputMode,
+            .diagnostics =
+                {
+                    .memberCount = static_cast<uint32_t>(selected->scanDefinition()->group().members.size()),
+                    .width       = LOGICAL->width,
+                    .height      = LOGICAL->height,
+                    .refreshRate = LOGICAL->refreshRate,
+                    .preferred   = LOGICAL->preferred,
+                },
+        };
+    }
+
     class CGroupedStartupEffectSink final : public IGroupedPublicationEffectSink {
       public:
-        CGroupedStartupEffectSink(CWeakPointer<CDRMBackend> backend, CSharedPointer<SDRMConnector> primary) noexcept :
-            m_backend(std::move(backend)), m_primary(std::move(primary)) {}
+        CGroupedStartupEffectSink(CWeakPointer<CDRMBackend> backend, CSharedPointer<SDRMConnector> primary, size_t memberCount) noexcept :
+            m_backend(std::move(backend)), m_primary(std::move(primary)), m_memberCount(memberCount) {}
 
         void frameSubmitted(uintptr_t transactionId) noexcept override {
             if (!transactionId || !m_primary || !m_primary->output)
@@ -123,7 +248,8 @@ namespace {
         }
 
         [[nodiscard]] bool frameAccepted(uintptr_t transactionId, const PResolvedTiledAtomicRoute& route, const CSharedPointer<CDRMFB>& framebuffer) noexcept {
-            if (!transactionId || transactionId != m_submittedTransaction || m_pendingRoute || !route || !framebuffer || framebuffer->id != route->framebuffer().id)
+            if (!transactionId || transactionId != m_submittedTransaction || m_pendingRoute || !route || !framebuffer || framebuffer->id != route->framebuffer().id ||
+                m_memberCount < GROUPED_STARTUP_MIN_MEMBERS || m_memberCount > GROUPED_STARTUP_MAX_MEMBERS || route->members().size() != m_memberCount)
                 return false;
             for (const auto& MEMBER : route->members()) {
                 if (!MEMBER.primaryPlane || MEMBER.primaryPlane->id != MEMBER.primaryPlaneId)
@@ -140,27 +266,37 @@ namespace {
                             std::span<const CGroupedRuntime::SRelease> releases) noexcept override {
             const auto BACKEND = m_backend.lock();
             if (!BACKEND || !m_primary || !m_primary->output || !acceptedRoute || presentation.transactionId == 0 || presentation.transactionId != m_submittedTransaction ||
-                !sameOwner(acceptedRoute, m_pendingRoute) || !m_pendingFramebuffer || m_pendingFramebuffer->id != acceptedRoute->framebuffer().id) {
+                !sameOwner(acceptedRoute, m_pendingRoute) || !m_pendingFramebuffer || m_pendingFramebuffer->id != acceptedRoute->framebuffer().id ||
+                m_memberCount < GROUPED_STARTUP_MIN_MEMBERS || m_memberCount > GROUPED_STARTUP_MAX_MEMBERS || acceptedRoute->members().size() != m_memberCount ||
+                releases.size() > m_memberCount) {
                 m_terminalFailure = true;
                 if (m_primary)
                     m_primary->sched.invalidate();
                 return;
             }
 
-            std::array<CSharedPointer<IBuffer>, 8> releaseBuffers;
-            size_t                                 releaseCount    = 0;
-            const auto                             REMEMBER_BUFFER = [&](const CSharedPointer<IBuffer>& buffer) noexcept {
+            // Each bounded member can contribute old last + front cache owners,
+            // and the runtime can contribute at most one displaced release.
+            // Keep completion allocation-free while covering the full 32-CRTC
+            // route and fail closed instead of dropping an unlock on overflow.
+            std::array<CSharedPointer<IBuffer>, GROUPED_STARTUP_RELEASE_BUFFERS> releaseBuffers;
+            size_t                                                                  releaseCount    = 0;
+            bool                                                                    releaseOverflow = false;
+            const auto REMEMBER_BUFFER = [&](const CSharedPointer<IBuffer>& buffer) noexcept {
                 if (!buffer || std::ranges::any_of(std::span{releaseBuffers}.first(releaseCount), [&](const auto& existing) { return sameOwner(existing, buffer); }))
                     return;
-                if (releaseCount < releaseBuffers.size())
-                    releaseBuffers.at(releaseCount++) = buffer;
+                if (releaseCount == releaseBuffers.size()) {
+                    releaseOverflow = true;
+                    return;
+                }
+                releaseBuffers.at(releaseCount++) = buffer;
             };
             const auto REMEMBER_RELEASE = [&](const CSharedPointer<CDRMFB>& fb) noexcept {
                 const auto BUFFER = fb ? fb->buffer.lock() : CSharedPointer<IBuffer>{};
                 REMEMBER_BUFFER(BUFFER);
             };
-            const bool CACHE_VALID =
-                acceptedRoute->members().size() == 2 && std::ranges::all_of(acceptedRoute->members(), [&](const auto& member) {
+            bool CACHE_VALID =
+                acceptedRoute->members().size() == m_memberCount && std::ranges::all_of(acceptedRoute->members(), [&](const auto& member) {
                     return member.primaryPlane && member.primaryPlane->id == member.primaryPlaneId && sameOwner(member.primaryPlane->back, m_pendingFramebuffer);
                 });
             if (!CACHE_VALID)
@@ -184,6 +320,10 @@ namespace {
                 const auto& BUFFER = OWNER->buffer();
                 REMEMBER_BUFFER(BUFFER);
             }
+            if (releaseOverflow) {
+                CACHE_VALID       = false;
+                m_terminalFailure = true;
+            }
             m_submittedTransaction = 0;
             m_pendingRoute.reset();
             m_pendingFramebuffer.reset();
@@ -222,9 +362,6 @@ namespace {
             });
             if (BACKEND->sessionActive() && m_primary->output->enabledState && !m_primary->sched.frameScheduled())
                 m_frameReadyPending = true;
-            try {
-                BACKEND->log(AQ_LOG_DEBUG, "drm: AQ_DRM_TILED_STARTUP=1 grouped frame completed");
-            } catch (...) {}
         }
 
         [[nodiscard]] bool deliverPendingFrameReady() noexcept {
@@ -265,6 +402,7 @@ namespace {
 
         CWeakPointer<CDRMBackend>     m_backend;
         CSharedPointer<SDRMConnector> m_primary;
+        size_t                        m_memberCount          = 0;
         uintptr_t                     m_submittedTransaction = 0;
         PResolvedTiledAtomicRoute     m_pendingRoute;
         CSharedPointer<CDRMFB>        m_pendingFramebuffer;
@@ -1235,30 +1373,13 @@ SGroupedTestProbeResult CTiledDRMIntegrationRegistry::probeCurrentGroupedTestMod
             if (CURRENT_ENTRY == m_impl->entries.end() || !CURRENT_ENTRY->second.outputBridge || !CURRENT_ENTRY->second.groupedRuntime)
                 return;
             bridgeCurrent = CURRENT_ENTRY->second.outputBridge->withCurrentOwner(current, [&](const POutputBridgeObservation& observation) {
-                const CGroupProjection* selected = nullptr;
-                for (const auto& candidate : observation->groups()) {
-                    if (candidate.projection() != eGroupProjection::MULTI_STREAM_ALLOCATED || !candidate.scanDefinition() || !candidate.modeSet() ||
-                        candidate.modeSet()->modes().empty())
-                        continue;
-                    selected = &candidate;
-                    break;
-                }
-                if (!selected)
-                    return;
-
-                const auto MODES = selected->modeSet()->modes();
-                auto       mode  = std::ranges::find_if(MODES, [](const auto& binding) {
-                    return binding.outputMode && binding.outputMode->pixelSize.x == 5120.0 && binding.outputMode->pixelSize.y == 2880.0 && binding.outputMode->preferred;
-                });
-                if (mode == MODES.end()) {
-                    mode = std::ranges::find_if(
-                        MODES, [](const auto& binding) { return binding.outputMode && binding.outputMode->pixelSize.x == 5120.0 && binding.outputMode->pixelSize.y == 2880.0; });
-                }
-                if (mode == MODES.end() || !mode->outputMode)
+                const auto SELECTED = selectBoundedGroup(observation);
+                if (SELECTED.status != eBoundedGroupSelection::SELECTED || !SELECTED.group || !SELECTED.mode)
                     return;
 
+                result.mode = SELECTED.diagnostics;
                 invoked     = true;
-                result.test = CURRENT_ENTRY->second.groupedRuntime->executeCurrentTestProbe(observation, selected->scanDefinition(), mode->outputMode, captureProvider);
+                result.test = CURRENT_ENTRY->second.groupedRuntime->executeCurrentTestProbe(observation, SELECTED.group->scanDefinition(), SELECTED.mode, captureProvider);
             });
         });
         if (!ALLOCATION_CURRENT || !bridgeCurrent || !invoked)
@@ -1294,37 +1415,17 @@ SGroupedStartupPrepareResult CTiledDRMIntegrationRegistry::prepareGroupedStartup
     try {
         allocationCurrent = withCurrentAllocationKey(key, SHADOW, [&](const CPlan&, const SCurrentState& current) {
             bridgeCurrent = ENTRY->second.outputBridge->withCurrentOwner(current, [&](const POutputBridgeObservation& observation) {
-                const CGroupProjection* selected = nullptr;
-                for (const auto& candidate : observation->groups()) {
-                    if (candidate.projection() != eGroupProjection::MULTI_STREAM_ALLOCATED || !candidate.scanDefinition() || !candidate.modeSet() ||
-                        !candidate.definitionIdentity() || candidate.scanDefinition()->group().members.size() != 2)
-                        continue;
-                    if (selected) {
-                        result.status = eGroupedStartupPrepareStatus::UNSUPPORTED_TOPOLOGY;
-                        return;
-                    }
-                    selected = &candidate;
-                }
-                if (!selected) {
-                    result.status = eGroupedStartupPrepareStatus::UNSUPPORTED_TOPOLOGY;
-                    return;
-                }
-
-                const auto MODES = selected->modeSet()->modes();
-                auto       mode  = std::ranges::find_if(MODES, [](const auto& binding) {
-                    return binding.outputMode && binding.outputMode->pixelSize.x == 5120.0 && binding.outputMode->pixelSize.y == 2880.0 &&
-                        binding.outputMode->refreshRate >= 59'000 && binding.outputMode->refreshRate <= 61'000 && binding.outputMode->preferred;
-                });
-                if (mode == MODES.end()) {
-                    mode = std::ranges::find_if(MODES, [](const auto& binding) {
-                        return binding.outputMode && binding.outputMode->pixelSize.x == 5120.0 && binding.outputMode->pixelSize.y == 2880.0 &&
-                            binding.outputMode->refreshRate >= 59'000 && binding.outputMode->refreshRate <= 61'000;
-                    });
-                }
-                if (mode == MODES.end() || !mode->outputMode) {
-                    result.status = eGroupedStartupPrepareStatus::UNSUPPORTED_TOPOLOGY;
+                const auto SELECTED = selectBoundedGroup(observation);
+                if (SELECTED.status != eBoundedGroupSelection::SELECTED || !SELECTED.group || !SELECTED.mode) {
+                    result.status = SELECTED.status == eBoundedGroupSelection::MULTIPLE_ALLOCATED_GROUPS ?
+                        eGroupedStartupPrepareStatus::MULTIPLE_ALLOCATED_GROUPS_UNSUPPORTED :
+                        eGroupedStartupPrepareStatus::UNSUPPORTED_TOPOLOGY;
                     return;
                 }
+                const auto* const selected = SELECTED.group;
+                const auto        mode     = SELECTED.mode;
+                const auto        MODES    = selected->modeSet()->modes();
+                result.mode                = SELECTED.diagnostics;
 
                 std::map<uint32_t, CSharedPointer<SDRMConnector>> connectorById;
                 for (const auto& CONNECTOR : connectors) {
@@ -1346,7 +1447,7 @@ SGroupedStartupPrepareResult CTiledDRMIntegrationRegistry::prepareGroupedStartup
                     if (MEMBER.connectorId == selected->scanDefinition()->group().primaryConnector)
                         primary = FOUND->second;
                 }
-                if (!primary || members.size() != 2) {
+                if (!primary || members.size() != selected->scanDefinition()->group().members.size()) {
                     result.status = eGroupedStartupPrepareStatus::UNSUPPORTED_TOPOLOGY;
                     return;
                 }
@@ -1359,13 +1460,12 @@ SGroupedStartupPrepareResult CTiledDRMIntegrationRegistry::prepareGroupedStartup
                     return;
                 }
                 for (size_t index = 0; index < members.size(); ++index) {
-                    const auto BINDING = std::ranges::find(ALLOCATION->get().bindings(), members.at(index)->id,
-                                                           [](const auto& candidate) { return candidate.connector() ? candidate.connector()->id : 0; });
-                    if (BINDING == ALLOCATION->get().bindings().end() || !BINDING->primaryPlane()) {
+                    const auto& BINDING = ALLOCATION->get().bindings().at(index);
+                    if (!BINDING.primaryPlane() || !sameOwner(BINDING.connector(), members.at(index)) || !sameOwner(BINDING.crtc(), members.at(index)->crtc)) {
                         result.status = eGroupedStartupPrepareStatus::INVALID_CURRENT;
                         return;
                     }
-                    formatSets.push_back({.formats = BINDING->primaryPlane()->formats});
+                    formatSets.push_back({.formats = BINDING.primaryPlane()->formats});
                     if (sameOwner(members.at(index), primary))
                         primaryIndex = index;
                 }
@@ -1375,13 +1475,13 @@ SGroupedStartupPrepareResult CTiledDRMIntegrationRegistry::prepareGroupedStartup
                     return;
                 }
 
-                result.test = ENTRY->second.groupedRuntime->executeCurrentTestProbe(observation, selected->scanDefinition(), mode->outputMode, captureProvider);
+                result.test = ENTRY->second.groupedRuntime->executeCurrentTestProbe(observation, selected->scanDefinition(), mode, captureProvider);
                 if (!result.test) {
                     result.status = eGroupedStartupPrepareStatus::TEST_FAILED;
                     return;
                 }
 
-                CSharedPointer<CGroupedStartupEffectSink>     concreteSink{new CGroupedStartupEffectSink{backend, primary}};
+                CSharedPointer<CGroupedStartupEffectSink>     concreteSink{new CGroupedStartupEffectSink{backend, primary, members.size()}};
                 CSharedPointer<IGroupedPublicationEffectSink> sink        = concreteSink;
                 auto                                          publication = CGroupedPublicationInstance::create(selected->definitionIdentity(), sink);
                 if (!sink || !publication) {
@@ -1413,7 +1513,7 @@ SGroupedStartupPrepareResult CTiledDRMIntegrationRegistry::prepareGroupedStartup
                 };
                 ENTRY->second.groupedStartup.emplace(std::move(startup));
                 primary->output->modes = std::move(logicalModes);
-                primary->output->state->setMode(mode->outputMode);
+                primary->output->state->setMode(mode);
                 primary->output->supportsExplicit = false;
                 primary->output->vrrCapable       = false;
                 result.status                     = eGroupedStartupPrepareStatus::PREPARED;
diff --git a/src/backend/drm/TiledDRMIntegration.hpp b/src/backend/drm/TiledDRMIntegration.hpp
index 7bb8c26c9c38ed6de7e4469586e5d2d25c8ffdb0..8f0539b99ec0a63e65634f24fa6913b5eb3fd1b4 100644
--- a/src/backend/drm/TiledDRMIntegration.hpp
+++ b/src/backend/drm/TiledDRMIntegration.hpp
@@ -57,8 +57,17 @@ namespace Aquamarine::NDRM::NTiledDRMIntegration { // NOLINT(readability-identif
         INVALID_STATE,
     };
 
+    struct AQ_TILED_DRM_PRIVATE SGroupedModeDiagnostics {
+        uint32_t memberCount = 0;
+        uint32_t width       = 0;
+        uint32_t height      = 0;
+        uint32_t refreshRate = 0;
+        bool     preferred   = false;
+    };
+
     struct AQ_TILED_DRM_PRIVATE SGroupedTestProbeResult {
         bool                      attempted = false;
+        SGroupedModeDiagnostics   mode;
         SGroupedTestModesetResult test;
     };
 
@@ -69,12 +78,14 @@ namespace Aquamarine::NDRM::NTiledDRMIntegration { // NOLINT(readability-identif
         BUSY,
         INVALID_CURRENT,
         UNSUPPORTED_TOPOLOGY,
+        MULTIPLE_ALLOCATED_GROUPS_UNSUPPORTED,
         TEST_FAILED,
         ALLOCATION_FAILED,
     };
 
     struct AQ_TILED_DRM_PRIVATE SGroupedStartupPrepareResult {
         eGroupedStartupPrepareStatus status = eGroupedStartupPrepareStatus::INVALID_CURRENT;
+        SGroupedModeDiagnostics      mode;
         SGroupedTestModesetResult    test;
 
         explicit                     operator bool() const noexcept;