From b2f6c1a838fa33540e2b4ff593e1672c4bb12175 Mon Sep 17 00:00:00 2001 From: Andrei Sabalenka Date: Thu, 12 Feb 2026 04:05:18 +0300 Subject: [PATCH] drm: keep secondary renderers alive only when they're needed (#239) Currently aquamarine initializes all the secondary renderers and keeps them alive all the time, even when no output on that GPU is enabled. Keeping a secondary renderer alive prevents unused GPU from powering down completely, which e.g. makes battery life significantly worse on Optimus laptops with DP/HDMI ports wired to dGPU. This patch initializes secondary renderers only when at least one connected output is enabled, and deinitializes them when no enabled outputs remain. --- include/aquamarine/backend/DRM.hpp | 3 + src/backend/drm/DRM.cpp | 136 +++++++++++++++++++++++++++-- src/backend/drm/Renderer.cpp | 33 +++++-- 3 files changed, 156 insertions(+), 16 deletions(-) diff --git a/include/aquamarine/backend/DRM.hpp b/include/aquamarine/backend/DRM.hpp index 063a21e..c63c74f 100644 --- a/include/aquamarine/backend/DRM.hpp +++ b/include/aquamarine/backend/DRM.hpp @@ -202,6 +202,7 @@ namespace Aquamarine { virtual size_t getGammaSize(); virtual size_t getDeGammaSize(); virtual std::vector getRenderFormats(); + void releaseMgpuResources(); int getConnectorID(); @@ -287,6 +288,7 @@ namespace Aquamarine { void onPresent(); void recheckCRTCProps(); void parseTileInfo(); + void releaseFBReferences(); Hyprutils::Memory::CSharedPointer output; Hyprutils::Memory::CWeakPointer backend; @@ -407,6 +409,7 @@ namespace Aquamarine { bool checkFeatures(); bool initResources(); bool initMgpu(); + bool updateSecondaryRendererState(); bool grabFormats(); bool shouldBlit(); void scanConnectors(); diff --git a/src/backend/drm/DRM.cpp b/src/backend/drm/DRM.cpp index 5e4c318..c78c448 100644 --- a/src/backend/drm/DRM.cpp +++ b/src/backend/drm/DRM.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -358,7 +359,8 @@ Aquamarine::CDRMBackend::~CDRMBackend() { conn.reset(); } - rendererState.allocator->destroyBuffers(); + if (rendererState.allocator) + rendererState.allocator->destroyBuffers(); rendererState.renderer.reset(); rendererState.allocator.reset(); @@ -621,6 +623,49 @@ bool Aquamarine::CDRMBackend::initMgpu() { return true; } +bool Aquamarine::CDRMBackend::updateSecondaryRendererState() { + if (!backend->ready) + return true; + + if (!primary) { + if (rendererState.renderer && rendererState.allocator) + return true; + + return initMgpu(); + } + + const bool hasEnabledOutputs = + std::ranges::any_of(connectors, [](const auto& c) { return c->status == DRM_MODE_CONNECTED && c->output && c->output->state && c->output->state->state().enabled; }); + + if (hasEnabledOutputs) { + if (rendererState.renderer && rendererState.allocator) + return true; + + backend->log(AQ_LOG_DEBUG, std::format("drm: Initializing secondary renderer on {}, has enabled outputs", gpu->path)); + return initMgpu(); + } + + if (!rendererState.renderer && !rendererState.allocator) + return true; + + backend->log(AQ_LOG_DEBUG, std::format("drm: Deinitializing secondary renderer on {}, no enabled outputs", gpu->path)); + + for (auto const& c : connectors) { + c->releaseFBReferences(); + + if (c->output) + c->output->releaseMgpuResources(); + } + + if (rendererState.allocator) + rendererState.allocator->destroyBuffers(); + + rendererState.renderer.reset(); + rendererState.allocator.reset(); + + return true; +} + void Aquamarine::CDRMBackend::buildGlFormats(const std::vector& fmts) { std::vector result; @@ -854,6 +899,9 @@ void Aquamarine::CDRMBackend::recheckOutputs() { drmModeFreeConnector(drmConn); } } + + if (!updateSecondaryRendererState()) + backend->log(AQ_LOG_ERROR, std::format("drm: Failed to update renderer state for {}", gpu->path)); } void Aquamarine::CDRMBackend::scanConnectors() { @@ -994,7 +1042,7 @@ static void handlePF(int fd, unsigned seq, unsigned tv_sec, unsigned tv_usec, un TRACE(BACKEND->log(AQ_LOG_TRACE, std::format("drm: pf event seq {} sec {} usec {} crtc {}", seq, tv_sec, tv_usec, crtc_id))); - if (pageFlip->connector->status != DRM_MODE_CONNECTED || !pageFlip->connector->crtc) { + if (pageFlip->connector->status != DRM_MODE_CONNECTED || !pageFlip->connector->crtc || !pageFlip->connector->output) { BACKEND->log(AQ_LOG_DEBUG, "drm: Ignoring a pf event from a disabled crtc / connector"); return; } @@ -1077,8 +1125,8 @@ void Aquamarine::CDRMBackend::onReady() { backend->events.newOutput.emit(SP(c->output)); } - if (!initMgpu()) { - backend->log(AQ_LOG_ERROR, "drm: Failed initializing mgpu"); + if (!updateSecondaryRendererState()) { + backend->log(AQ_LOG_ERROR, std::format("drm: Failed to initialize renderer state for {}", gpu->path)); return; } } @@ -1324,6 +1372,42 @@ void Aquamarine::SDRMConnector::parseTileInfo() { free(blobData); } +void Aquamarine::SDRMConnector::releaseFBReferences() { + std::unordered_set releasedBuffers; + + const auto releaseFB = [&releasedBuffers](SP& fb) { + if (!fb) { + fb.reset(); + return; + } + + if (auto buf = fb->buffer.lock(); buf && buf->lockedByBackend && releasedBuffers.emplace(buf.get()).second) { + buf->lockedByBackend = false; + buf->events.backendRelease.emit(); + } + + fb.reset(); + }; + + if (crtc) { + if (crtc->primary) { + releaseFB(crtc->primary->front); + releaseFB(crtc->primary->back); + releaseFB(crtc->primary->last); + } + + if (crtc->cursor) { + releaseFB(crtc->cursor->front); + releaseFB(crtc->cursor->back); + releaseFB(crtc->cursor->last); + } + + releaseFB(crtc->pendingCursor); + } + + releaseFB(pendingCursorFB); +} + Aquamarine::SDRMConnector::~SDRMConnector() { disconnect(); } @@ -1586,6 +1670,11 @@ void Aquamarine::SDRMConnector::connect(drmModeConnector* connector) { if (!backend->backend->ready) return; + if (!backend->updateSecondaryRendererState()) { + backend->backend->log(AQ_LOG_ERROR, std::format("drm: Failed to update renderer state for {} on connect", szName)); + return; + } + auto primaryBackend = backend->primary ? backend->primary : backend; output->swapchain = CSwapchain::create(backend->backend->primaryAllocator, primaryBackend.lock()); output->swapchain->reconfigure(SSwapchainOptions{.length = 0, .scanout = true, .multigpu = !!backend->primary, .scanoutOutput = output}); // mark the swapchain for scanout @@ -1600,10 +1689,11 @@ void Aquamarine::SDRMConnector::disconnect() { return; } + status = DRM_MODE_DISCONNECTED; + releaseFBReferences(); + output->events.destroy.emit(); output.reset(); - - status = DRM_MODE_DISCONNECTED; } bool Aquamarine::SDRMConnector::commitState(SDRMConnectorCommitData& data) { @@ -1633,6 +1723,12 @@ void Aquamarine::SDRMConnector::applyCommit(const SDRMConnectorCommitData& data) refresh = calculateRefresh(data.modeInfo); output->enabledState = output->state->state().enabled; + + if (!output->enabledState) + releaseFBReferences(); + + if (!backend->updateSecondaryRendererState()) + backend->backend->log(AQ_LOG_ERROR, std::format("drm: Failed to update renderer state for {} on applyCommit", szName)); } void Aquamarine::SDRMConnector::rollbackCommit(const SDRMConnectorCommitData& data) { @@ -1672,6 +1768,17 @@ Aquamarine::CDRMOutput::~CDRMOutput() { connector->frameEventScheduled = false; } +void Aquamarine::CDRMOutput::releaseMgpuResources() { + mgpu.swapchain.reset(); + mgpu.cursorSwapchain.reset(); + + if (swapchain) { + auto options = swapchain->currentOptions(); + options.length = 0; + swapchain->reconfigure(options); + } +} + bool Aquamarine::CDRMOutput::commit() { return commitState(); } @@ -1789,15 +1896,18 @@ bool Aquamarine::CDRMOutput::commitState(bool onlyTest) { SDRMConnectorCommitData data; - if (STATE.buffer) { + if (STATE.buffer && STATE.enabled) { TRACE(backend->backend->log(AQ_LOG_TRACE, "drm: Committed a buffer, updating state")); SP drmFB; if (backend->shouldBlit()) { if (!backend->rendererState.renderer) { - backend->backend->log(AQ_LOG_ERROR, "drm: No renderer attached to backend when required for blitting"); - return false; + backend->backend->log(AQ_LOG_DEBUG, "drm: No renderer attached to backend when required for blitting, initializing"); + if (!backend->initMgpu() || !backend->rendererState.renderer || !backend->rendererState.allocator) { + backend->backend->log(AQ_LOG_ERROR, "drm: Failed to initialize renderer backend for blitting"); + return false; + } } TRACE(backend->backend->log(AQ_LOG_TRACE, "drm: Backend requires blit, blitting")); @@ -1982,6 +2092,14 @@ bool Aquamarine::CDRMOutput::setCursor(SP buffer, const Vector2D& hotsp if (backend->primary) { TRACE(backend->backend->log(AQ_LOG_TRACE, "drm: Backend requires cursor blit, blitting")); + if (!backend->rendererState.renderer || !backend->rendererState.allocator) { + backend->backend->log(AQ_LOG_DEBUG, "drm: No renderer attached to backend when required for cursor blitting, initializing"); + if (!backend->initMgpu() || !backend->rendererState.renderer || !backend->rendererState.allocator) { + backend->backend->log(AQ_LOG_ERROR, "drm: Failed to initialize renderer backend for cursor blitting"); + return false; + } + } + // TODO: will this not implode on drm_dumb?! if (!mgpu.cursorSwapchain) { diff --git a/src/backend/drm/Renderer.cpp b/src/backend/drm/Renderer.cpp index ba93d17..7049a2d 100644 --- a/src/backend/drm/Renderer.cpp +++ b/src/backend/drm/Renderer.cpp @@ -773,6 +773,13 @@ constexpr GLenum PIXEL_BUFFER_FORMAT = GL_RGBA; void CDRMRenderer::readBuffer(Hyprutils::Memory::CSharedPointer buf, std::span out) { CEglContextGuard eglContext(*this); auto att = buf->attachments.get(); + if (att && att->renderer.lock() != self.lock()) { + if (auto oldRenderer = att->renderer.lock(); oldRenderer && oldRenderer != self.lock()) + oldRenderer->onBufferAttachmentDrop(att.get()); + buf->attachments.removeByType(); + att.reset(); + } + if (!att) { att = makeShared(self, buf, nullptr, 0, 0, CGLTex{}, std::vector()); buf->attachments.add(att); @@ -948,6 +955,13 @@ CDRMRenderer::SBlitResult CDRMRenderer::blit(SP from, SP to, S std::span intermediateBuf; { auto attachment = from->attachments.get(); + if (attachment && attachment->renderer.lock() != self.lock()) { + if (auto oldRenderer = attachment->renderer.lock(); oldRenderer && oldRenderer != self.lock()) + oldRenderer->onBufferAttachmentDrop(attachment.get()); + from->attachments.removeByType(); + attachment.reset(); + } + if (attachment) { TRACE(backend->log(AQ_LOG_TRACE, "EGL (blit): From attachment found")); fromTex = attachment->tex; @@ -996,6 +1010,13 @@ CDRMRenderer::SBlitResult CDRMRenderer::blit(SP from, SP to, S { auto attachment = to->attachments.get(); + if (attachment && attachment->renderer != self) { + if (auto oldRenderer = attachment->renderer.lock(); oldRenderer && oldRenderer != self.lock()) + oldRenderer->onBufferAttachmentDrop(attachment.get()); + to->attachments.removeByType(); + attachment.reset(); + } + if (attachment) { TRACE(backend->log(AQ_LOG_TRACE, "EGL (blit): To attachment found")); rboImage = attachment->eglImage; @@ -1064,12 +1085,7 @@ CDRMRenderer::SBlitResult CDRMRenderer::blit(SP from, SP to, S float glMtx[9]; matrixMultiply(glMtx, monitorProj, mtx); - static Vector2D lastViewportSize = {-1, -1}; - if (lastViewportSize != toDma.size) { - GLCALL(glViewport(0, 0, toDma.size.x, toDma.size.y)); - lastViewportSize = toDma.size; - } - + GLCALL(glViewport(0, 0, toDma.size.x, toDma.size.y)); GLCALL(glActiveTexture(GL_TEXTURE0)); GLCALL(fromTex->bind()); GLCALL(fromTex->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST)); @@ -1180,5 +1196,8 @@ void CGLTex::setTexParameter(GLenum pname, GLint param) { CDRMRendererBufferAttachment::CDRMRendererBufferAttachment(Hyprutils::Memory::CWeakPointer renderer_, Hyprutils::Memory::CSharedPointer buffer, EGLImageKHR image, GLuint fbo_, GLuint rbo_, CGLTex&& tex_, std::vector intermediateBuf_) : eglImage(image), fbo(fbo_), rbo(rbo_), tex(makeUnique(std::move(tex_))), intermediateBuf(intermediateBuf_), renderer(renderer_) { - bufferDestroy = buffer->events.destroy.listen([this] { renderer->onBufferAttachmentDrop(this); }); + bufferDestroy = buffer->events.destroy.listen([this] { + if (auto r = renderer.lock()) + r->onBufferAttachmentDrop(this); + }); }