diff --git a/CMakeLists.txt b/CMakeLists.txt index 675880a..9adf819 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,7 @@ set(INCLUDE ${CMAKE_INSTALL_FULL_INCLUDEDIR}) set(LIBDIR ${CMAKE_INSTALL_FULL_LIBDIR}) find_package(PkgConfig REQUIRED) -pkg_check_modules(deps REQUIRED IMPORTED_TARGET libseat libinput wayland-client wayland-protocols hyprutils>=0.1.2 pixman-1 wayland-client libdrm gbm libudev) +pkg_check_modules(deps REQUIRED IMPORTED_TARGET libseat libinput wayland-client wayland-protocols hyprutils>=0.1.2 pixman-1 wayland-client libdrm gbm libudev libdisplay-info) configure_file(aquamarine.pc.in aquamarine.pc @ONLY) diff --git a/include/aquamarine/allocator/Allocator.hpp b/include/aquamarine/allocator/Allocator.hpp index 35b20e5..09c6b74 100644 --- a/include/aquamarine/allocator/Allocator.hpp +++ b/include/aquamarine/allocator/Allocator.hpp @@ -9,7 +9,8 @@ namespace Aquamarine { struct SAllocatorBufferParams { Hyprutils::Math::Vector2D size; - uint32_t format = DRM_FORMAT_INVALID; + uint32_t format = DRM_FORMAT_INVALID; + bool scanout = false; }; class IAllocator { diff --git a/include/aquamarine/allocator/Swapchain.hpp b/include/aquamarine/allocator/Swapchain.hpp index 90fdc68..885bb3d 100644 --- a/include/aquamarine/allocator/Swapchain.hpp +++ b/include/aquamarine/allocator/Swapchain.hpp @@ -7,7 +7,8 @@ namespace Aquamarine { struct SSwapchainOptions { size_t length = 0; Hyprutils::Math::Vector2D size; - uint32_t format = DRM_FORMAT_INVALID; + uint32_t format = DRM_FORMAT_INVALID; + bool scanout = false; }; class CSwapchain { diff --git a/include/aquamarine/backend/Backend.hpp b/include/aquamarine/backend/Backend.hpp index ab8ef49..559c740 100644 --- a/include/aquamarine/backend/Backend.hpp +++ b/include/aquamarine/backend/Backend.hpp @@ -100,6 +100,9 @@ namespace Aquamarine { /* Get the primary DRM FD */ int drmFD(); + /* Get the render formats the primary backend supports */ + std::vector getPrimaryRenderFormats(); + struct { Hyprutils::Signal::CSignal newOutput; Hyprutils::Signal::CSignal newPointer; diff --git a/include/aquamarine/backend/DRM.hpp b/include/aquamarine/backend/DRM.hpp index 2844a2a..9c94c67 100644 --- a/include/aquamarine/backend/DRM.hpp +++ b/include/aquamarine/backend/DRM.hpp @@ -10,16 +10,51 @@ namespace Aquamarine { class CDRMBackend; + class CDRMFB; struct SDRMConnector; - struct SDRMFB { + typedef std::function FIdleCallback; + + class CDRMBufferUnimportable : public IAttachment { + public: + CDRMBufferUnimportable() { + ; + } + virtual ~CDRMBufferUnimportable() { + ; + } + virtual eAttachmentType type() { + return AQ_ATTACHMENT_DRM_KMS_UNIMPORTABLE; + } + }; + + class CDRMFB { + public: + ~CDRMFB(); + + static Hyprutils::Memory::CSharedPointer create(Hyprutils::Memory::CSharedPointer buffer_, Hyprutils::Memory::CWeakPointer backend_); + + void closeHandles(); + // drops the buffer from KMS + void drop(); + uint32_t id = 0; Hyprutils::Memory::CSharedPointer buffer; Hyprutils::Memory::CWeakPointer backend; + + private: + CDRMFB(Hyprutils::Memory::CSharedPointer buffer_, Hyprutils::Memory::CWeakPointer backend_); + uint32_t submitBuffer(); + + bool dropped = false, handlesClosed = false; + + std::array boHandles = {0}; }; struct SDRMLayer { - Hyprutils::Memory::CSharedPointer current /* displayed */, queued /* submitted */, pending /* to be submitted */; + // we expect the consumers to use double-buffering, so we keep the 2 last FBs around. If any of these goes out of + // scope, the DRM FB will be destroyed, but the IBuffer will stay, as long as it's ref'd somewhere. + Hyprutils::Memory::CSharedPointer front /* currently displaying */, back; Hyprutils::Memory::CWeakPointer backend; }; @@ -30,7 +65,7 @@ namespace Aquamarine { uint32_t id = 0; uint32_t initialID = 0; - Hyprutils::Memory::CSharedPointer current /* displayed */, queued /* submitted */; + Hyprutils::Memory::CSharedPointer front /* currently displaying */, back /* submitted */; Hyprutils::Memory::CWeakPointer backend; Hyprutils::Memory::CWeakPointer self; std::vector formats; @@ -108,12 +143,29 @@ namespace Aquamarine { private: CDRMOutput(const std::string& name_, Hyprutils::Memory::CWeakPointer backend_, Hyprutils::Memory::CSharedPointer connector_); + bool commitState(bool onlyTest = false); + Hyprutils::Memory::CWeakPointer backend; Hyprutils::Memory::CSharedPointer connector; friend struct SDRMConnector; }; + struct SDRMPageFlip { + Hyprutils::Memory::CWeakPointer connector; + }; + + struct SDRMConnectorCommitData { + Hyprutils::Memory::CSharedPointer mainFB, cursorFB; + bool modeset = false; + bool blocking = false; + uint32_t flags = 0; + bool test = false; + drmModeModeInfo modeInfo; + + void calculateMode(Hyprutils::Memory::CSharedPointer connector); + }; + struct SDRMConnector { ~SDRMConnector(); @@ -123,6 +175,10 @@ namespace Aquamarine { Hyprutils::Memory::CSharedPointer getCurrentCRTC(const drmModeConnector* connector); drmModeModeInfo* getCurrentMode(); void parseEDID(std::vector data); + bool commitState(const SDRMConnectorCommitData& data); + void applyCommit(const SDRMConnectorCommitData& data); + void rollbackCommit(const SDRMConnectorCommitData& data); + void onPresent(); Hyprutils::Memory::CSharedPointer output; Hyprutils::Memory::CWeakPointer backend; @@ -135,10 +191,16 @@ namespace Aquamarine { int32_t refresh = 0; uint32_t possibleCrtcs = 0; std::string make, serial, model; + bool canDoVrr = false; bool cursorEnabled = false; Hyprutils::Math::Vector2D cursorPos, cursorSize, cursorHotspot; - Hyprutils::Memory::CSharedPointer pendingCursorFB; + Hyprutils::Memory::CSharedPointer pendingCursorFB; + + bool isPageFlipPending = false; + SDRMPageFlip pendingPageFlip; + + drmModeModeInfo fallbackModeInfo; union UDRMConnectorProps { struct { @@ -162,6 +224,11 @@ namespace Aquamarine { UDRMConnectorProps props; }; + class IDRMImplementation { + public: + virtual bool commit(Hyprutils::Memory::CSharedPointer connector, const SDRMConnectorCommitData& data) = 0; + }; + class CDRMBackend : public IBackendImplementation { public: virtual ~CDRMBackend(); @@ -178,6 +245,11 @@ namespace Aquamarine { Hyprutils::Memory::CWeakPointer self; + void log(eBackendLogLevel, const std::string&); + bool sessionActive(); + + std::vector idleCallbacks; + private: CDRMBackend(Hyprutils::Memory::CSharedPointer backend); @@ -189,6 +261,7 @@ namespace Aquamarine { void scanConnectors(); Hyprutils::Memory::CSharedPointer gpu; + Hyprutils::Memory::CSharedPointer impl; Hyprutils::Memory::CWeakPointer primary; Hyprutils::Memory::CWeakPointer backend; @@ -205,10 +278,13 @@ namespace Aquamarine { } drmProps; friend class CBackend; - friend struct SDRMFB; + friend class CDRMFB; + friend class CDRMFBAttachment; friend struct SDRMConnector; friend struct SDRMCRTC; friend struct SDRMPlane; - friend struct CDRMOutput; + friend class CDRMOutput; + friend struct SDRMPageFlip; + friend class CDRMLegacyImpl; }; }; diff --git a/include/aquamarine/backend/drm/Legacy.hpp b/include/aquamarine/backend/drm/Legacy.hpp new file mode 100644 index 0000000..9baff38 --- /dev/null +++ b/include/aquamarine/backend/drm/Legacy.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "../DRM.hpp" + +namespace Aquamarine { + class CDRMLegacyImpl : public IDRMImplementation { + public: + CDRMLegacyImpl(Hyprutils::Memory::CSharedPointer backend_); + virtual bool commit(Hyprutils::Memory::CSharedPointer connector, const SDRMConnectorCommitData& data); + + private: + + bool commitInternal(Hyprutils::Memory::CSharedPointer connector, const SDRMConnectorCommitData& data); + bool testInternal(Hyprutils::Memory::CSharedPointer connector, const SDRMConnectorCommitData& data); + + Hyprutils::Memory::CWeakPointer backend; + }; +}; diff --git a/include/aquamarine/buffer/Buffer.hpp b/include/aquamarine/buffer/Buffer.hpp index 9aa1cb5..77e428c 100644 --- a/include/aquamarine/buffer/Buffer.hpp +++ b/include/aquamarine/buffer/Buffer.hpp @@ -4,6 +4,7 @@ #include #include #include +#include "../misc/Attachment.hpp" namespace Aquamarine { enum eBufferCapability { @@ -62,6 +63,8 @@ namespace Aquamarine { Hyprutils::Math::Vector2D size; bool opaque = false; + CAttachmentManager attachments; + struct { Hyprutils::Signal::CSignal destroy; } events; diff --git a/include/aquamarine/misc/Attachment.hpp b/include/aquamarine/misc/Attachment.hpp new file mode 100644 index 0000000..969eea2 --- /dev/null +++ b/include/aquamarine/misc/Attachment.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +namespace Aquamarine { + enum eAttachmentType { + AQ_ATTACHMENT_DRM_BUFFER = 0, + AQ_ATTACHMENT_DRM_KMS_UNIMPORTABLE, + }; + + class IAttachment { + public: + virtual ~IAttachment() { + ; + } + + virtual eAttachmentType type() = 0; + }; + + class CAttachmentManager { + public: + bool has(eAttachmentType type); + Hyprutils::Memory::CSharedPointer get(eAttachmentType type); + void add(Hyprutils::Memory::CSharedPointer attachment); + void remove(Hyprutils::Memory::CSharedPointer attachment); + void removeByType(eAttachmentType type); + + private: + std::vector> attachments; + }; +}; diff --git a/include/aquamarine/output/Output.hpp b/include/aquamarine/output/Output.hpp index e2a4b20..73fe4a3 100644 --- a/include/aquamarine/output/Output.hpp +++ b/include/aquamarine/output/Output.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "../allocator/Swapchain.hpp" #include "../buffer/Buffer.hpp" @@ -14,9 +15,10 @@ namespace Aquamarine { class IBackendImplementation; struct SOutputMode { - Hyprutils::Math::Vector2D pixelSize; - unsigned int refreshRate = 0 /* in mHz */; - bool preferred = false; + Hyprutils::Math::Vector2D pixelSize; + unsigned int refreshRate = 0 /* in mHz */; + bool preferred = false; + std::optional modeInfo; // if this is a drm mode, this will be populated. }; enum eOutputPresentationMode { @@ -83,6 +85,7 @@ namespace Aquamarine { friend class IOutput; friend class CWaylandOutput; + friend class CDRMOutput; }; class IOutput { @@ -106,6 +109,8 @@ namespace Aquamarine { bool enabled = false; bool nonDesktop = false; eSubpixelMode subpixel = AQ_SUBPIXEL_NONE; + bool vrrCapable = false; + bool needsFrame = false; // std::vector> modes; @@ -113,10 +118,25 @@ namespace Aquamarine { Hyprutils::Memory::CSharedPointer swapchain; // + + enum eOutputPresentFlags : uint32_t { + AQ_OUTPUT_PRESENT_VSYNC = (1 << 0), + AQ_OUTPUT_PRESENT_HW_CLOCK = (1 << 1), + AQ_OUTPUT_PRESENT_HW_COMPLETION = (1 << 2), + AQ_OUTPUT_PRESENT_ZEROCOPY = (1 << 3), + }; struct SStateEvent { Hyprutils::Math::Vector2D size; }; + struct SPresentEvent { + bool presented = true; + timespec* when = nullptr; + unsigned int seq = 0; + int refresh = 0; + uint32_t flags = 0; + }; + struct { Hyprutils::Signal::CSignal destroy; Hyprutils::Signal::CSignal frame; diff --git a/src/allocator/GBM.cpp b/src/allocator/GBM.cpp index 40d1609..13f7d98 100644 --- a/src/allocator/GBM.cpp +++ b/src/allocator/GBM.cpp @@ -15,10 +15,38 @@ Aquamarine::CGBMBuffer::CGBMBuffer(const SAllocatorBufferParams& params, Hypruti attrs.size = params.size; attrs.format = params.format; - // FIXME: proper modifier support? This might implode on some GPUs on the Wayland backend - // for sure. + const auto FORMATS = allocator->backend->getPrimaryRenderFormats(); - bo = gbm_bo_create(allocator->gbmDevice, params.size.x, params.size.y, params.format, GBM_BO_USE_RENDERING); + std::vector explicitModifiers; + + // check if we can use modifiers. If the requested support has any explicit modifier + // supported by the primary backend, we can. + allocator->backend->log(AQ_LOG_TRACE, std::format("GBM: Searching for modifiers. Format len: {}", FORMATS.size())); + for (auto& f : FORMATS) { + if (f.drmFormat != params.format) + continue; + + allocator->backend->log(AQ_LOG_TRACE, "GBM: Format matched"); + + for (auto& m : f.modifiers) { + if (m == DRM_FORMAT_MOD_LINEAR || m == DRM_FORMAT_MOD_INVALID) + continue; + + explicitModifiers.push_back(m); + + allocator->backend->log(AQ_LOG_TRACE, "GBM: Modifier matched"); + } + } + + uint32_t flags = GBM_BO_USE_RENDERING; + if (params.scanout) + flags |= GBM_BO_USE_SCANOUT; + + if (explicitModifiers.empty()) { + allocator->backend->log(AQ_LOG_WARNING, "GBM: Using modifier-less allocation"); + bo = gbm_bo_create(allocator->gbmDevice, params.size.x, params.size.y, params.format, flags); + } else + bo = gbm_bo_create_with_modifiers2(allocator->gbmDevice, params.size.x, params.size.y, params.format, explicitModifiers.data(), explicitModifiers.size(), flags); if (!bo) { allocator->backend->log(AQ_LOG_ERROR, "GBM: Failed to allocate a GBM buffer: bo null"); diff --git a/src/allocator/Swapchain.cpp b/src/allocator/Swapchain.cpp index ff76bd5..fc486ed 100644 --- a/src/allocator/Swapchain.cpp +++ b/src/allocator/Swapchain.cpp @@ -4,6 +4,7 @@ using namespace Aquamarine; using namespace Hyprutils::Memory; +using namespace Hyprutils::Math; #define SP CSharedPointer Aquamarine::CSwapchain::CSwapchain(SP allocator_) : allocator(allocator_) { @@ -15,6 +16,14 @@ bool Aquamarine::CSwapchain::reconfigure(const SSwapchainOptions& options_) { if (!allocator) return false; + if (options_.size == Vector2D{} || options_.length == 0) { + // clear the swapchain + allocator->getBackend()->log(AQ_LOG_DEBUG, "Swapchain: Clearing"); + buffers.clear(); + options = options_; + return true; + } + if (options_.format == options.format && options_.size == options.size && options_.length == options.length) return true; // no need to reconfigure diff --git a/src/backend/Backend.cpp b/src/backend/Backend.cpp index 38ed42b..0a3d9a2 100644 --- a/src/backend/Backend.cpp +++ b/src/backend/Backend.cpp @@ -99,7 +99,12 @@ bool Aquamarine::CBackend::start() { } // erase failed impls - std::erase_if(implementations, [](const auto& i) { return i->pollFD() < 0; }); + std::erase_if(implementations, [this](const auto& i) { + bool failed = i->pollFD() < 0; + if (failed) + log(AQ_LOG_ERROR, std::format("Implementation {} failed, erasing.", backendTypeToName(i->type()))); + return failed; + }); // TODO: obviously change this when (if) we add different allocators. for (auto& b : implementations) { @@ -225,6 +230,20 @@ void Aquamarine::CBackend::dispatchEventsAsync() { } bool Aquamarine::CBackend::hasSession() { - // TODO: - return false; + return session; +} + +std::vector Aquamarine::CBackend::getPrimaryRenderFormats() { + for (auto& b : implementations) { + if (b->type() != AQ_BACKEND_DRM && b->type() != AQ_BACKEND_WAYLAND) + continue; + + return b->getRenderFormats(); + } + + for (auto& b : implementations) { + return b->getRenderFormats(); + } + + return {}; } diff --git a/src/backend/drm/DRM.cpp b/src/backend/drm/DRM.cpp index 841975e..888ab6a 100644 --- a/src/backend/drm/DRM.cpp +++ b/src/backend/drm/DRM.cpp @@ -1,17 +1,21 @@ #include +#include #include #include #include #include +#include extern "C" { #include #include #include #include +#include } #include "Props.hpp" +#include "FormatUtils.hpp" using namespace Aquamarine; using namespace Hyprutils::Memory; @@ -194,6 +198,14 @@ Aquamarine::CDRMBackend::~CDRMBackend() { ; } +void Aquamarine::CDRMBackend::log(eBackendLogLevel l, const std::string& s) { + backend->log(l, s); +} + +bool Aquamarine::CDRMBackend::sessionActive() { + return backend->session->active; +} + bool Aquamarine::CDRMBackend::checkFeatures() { uint64_t curW = 0, curH = 0; if (drmGetCap(gpu->fd, DRM_CAP_CURSOR_WIDTH, &curW)) @@ -227,6 +239,11 @@ bool Aquamarine::CDRMBackend::checkFeatures() { drmProps.supportsAsyncCommit = drmGetCap(gpu->fd, DRM_CAP_ASYNC_PAGE_FLIP, &cap) == 0 && cap == 1; drmProps.supportsAddFb2Modifiers = drmGetCap(gpu->fd, DRM_CAP_ADDFB2_MODIFIERS, &cap) == 0 && cap == 1; + backend->log(AQ_LOG_DEBUG, std::format("drm: drmProps.supportsAsyncCommit: {}", drmProps.supportsAsyncCommit)); + backend->log(AQ_LOG_DEBUG, std::format("drm: drmProps.supportsAddFb2Modifiers: {}", drmProps.supportsAddFb2Modifiers)); + + impl = makeShared(self.lock()); + // TODO: allow no-modifiers? return true; @@ -303,11 +320,14 @@ bool Aquamarine::CDRMBackend::initResources() { return false; } + planes.emplace_back(aqPlane); + drmModeFreePlane(plane); } drmModeFreePlaneResources(planeResources); drmModeFreeResources(resources); + return true; } @@ -372,7 +392,9 @@ void Aquamarine::CDRMBackend::scanConnectors() { } else conn = *it; - backend->log(AQ_LOG_DEBUG, std::format("drm: Connector {} connection state:", (int)drmConn->connection)); + backend->log(AQ_LOG_DEBUG, std::format("drm: Connectors size {}", connectors.size())); + + backend->log(AQ_LOG_DEBUG, std::format("drm: Connector {} connection state: {}", connectorID, (int)drmConn->connection)); if (conn->status == DRM_MODE_DISCONNECTED && drmConn->connection == DRM_MODE_CONNECTED) { backend->log(AQ_LOG_DEBUG, std::format("drm: Connector {} connected", conn->szName)); @@ -389,8 +411,6 @@ void Aquamarine::CDRMBackend::scanConnectors() { } bool Aquamarine::CDRMBackend::start() { - scanConnectors(); - return true; } @@ -403,7 +423,38 @@ int Aquamarine::CDRMBackend::drmFD() { } static void handlePF(int fd, unsigned seq, unsigned tv_sec, unsigned tv_usec, unsigned crtc_id, void* data) { - // FIXME: + auto pageFlip = (SDRMPageFlip*)data; + + if (!pageFlip->connector) + return; + + pageFlip->connector->isPageFlipPending = false; + + const auto& BACKEND = pageFlip->connector->backend; + + 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) { + BACKEND->log(AQ_LOG_DEBUG, "drm: Ignoring a pf event from a disabled crtc / connector"); + return; + } + + pageFlip->connector->onPresent(); + + uint32_t flags = IOutput::AQ_OUTPUT_PRESENT_VSYNC | IOutput::AQ_OUTPUT_PRESENT_HW_CLOCK | IOutput::AQ_OUTPUT_PRESENT_HW_COMPLETION | IOutput::AQ_OUTPUT_PRESENT_ZEROCOPY; + + timespec presented = {.tv_sec = tv_sec, .tv_nsec = tv_usec * 1000}; + + pageFlip->connector->output->events.present.emit(IOutput::SPresentEvent{ + .presented = BACKEND->sessionActive(), + .when = &presented, + .seq = seq, + .refresh = (int)(pageFlip->connector->refresh ? (1000000000000LL / pageFlip->connector->refresh) : 0), + .flags = flags, + }); + + if (BACKEND->sessionActive()) + pageFlip->connector->output->events.frame.emit(); } bool Aquamarine::CDRMBackend::dispatchEvents() { @@ -415,6 +466,13 @@ bool Aquamarine::CDRMBackend::dispatchEvents() { if (drmHandleEvent(gpu->fd, &event) != 0) backend->log(AQ_LOG_ERROR, std::format("drm: Failed to handle event on fd {}", gpu->fd)); + if (!idleCallbacks.empty()) { + for (auto& c : idleCallbacks) { + c(); + } + idleCallbacks.clear(); + } + return true; } @@ -427,7 +485,22 @@ bool Aquamarine::CDRMBackend::setCursor(SP buffer, const Hyprutils::Mat } void Aquamarine::CDRMBackend::onReady() { - ; + backend->log(AQ_LOG_DEBUG, std::format("drm: Connectors size2 {}", connectors.size())); + + for (auto& c : connectors) { + backend->log(AQ_LOG_DEBUG, std::format("drm: onReady: connector {}", c->id)); + if (!c->output) + continue; + + backend->log(AQ_LOG_DEBUG, std::format("drm: onReady: connector {} has output name {}", c->id, c->output->name)); + + // swapchain has to be created here because allocator is absent in connect if not ready + c->output->swapchain = makeShared(backend->allocator); + c->output->swapchain->reconfigure(SSwapchainOptions{.length = 0, .scanout = true}); // mark the swapchain for scanout + c->output->needsFrame = true; + + backend->events.newOutput.emit(SP(c->output)); + } } std::vector Aquamarine::CDRMBackend::getRenderFormats() { @@ -463,26 +536,40 @@ bool Aquamarine::SDRMPlane::init(drmModePlane* plane) { initialID = id; + backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Plane {} has type {}", id, (int)type)); + + backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Plane {} has {} formats", id, plane->count_formats)); + for (size_t i = 0; i < plane->count_formats; ++i) { if (type != DRM_PLANE_TYPE_CURSOR) formats.emplace_back(SDRMFormat{.drmFormat = plane->formats[i], .modifiers = {DRM_FORMAT_MOD_LINEAR, DRM_FORMAT_MOD_INVALID}}); else formats.emplace_back(SDRMFormat{.drmFormat = plane->formats[i], .modifiers = {DRM_FORMAT_MOD_LINEAR}}); + + backend->backend->log(AQ_LOG_TRACE, std::format("drm: | Format {}", fourccToName(plane->formats[i]))); } if (props.in_formats && backend->drmProps.supportsAddFb2Modifiers) { + backend->backend->log(AQ_LOG_DEBUG, "drm: Plane: checking for modifiers"); + uint64_t blobID = 0; - if (!getDRMProp(backend->gpu->fd, id, props.in_formats, &blobID)) + if (!getDRMProp(backend->gpu->fd, id, props.in_formats, &blobID)) { + backend->backend->log(AQ_LOG_ERROR, "drm: Plane: No blob id"); return false; + } auto blob = drmModeGetPropertyBlob(backend->gpu->fd, blobID); - if (!blob) + if (!blob) { + backend->backend->log(AQ_LOG_ERROR, "drm: Plane: No property"); return false; + } drmModeFormatModifierIterator iter = {0}; while (drmModeFormatModifierBlobIterNext(blob, &iter)) { auto it = std::find_if(formats.begin(), formats.end(), [iter](const auto& e) { return e.drmFormat == iter.fmt; }); + backend->backend->log(AQ_LOG_TRACE, std::format("drm: | Modifier {} with format {}", iter.mod, fourccToName(iter.fmt))); + if (it == formats.end()) formats.emplace_back(SDRMFormat{.drmFormat = iter.fmt, .modifiers = {iter.mod}}); else @@ -543,7 +630,8 @@ SP Aquamarine::SDRMConnector::getCurrentCRTC(const drmModeConnector* c } bool Aquamarine::SDRMConnector::init(drmModeConnector* connector) { - id = connector->connector_id; + id = connector->connector_id; + pendingPageFlip.connector = self.lock(); if (!getDRMConnectorProps(backend->gpu->fd, id, &props)) return false; @@ -640,10 +728,14 @@ void Aquamarine::SDRMConnector::connect(drmModeConnector* connector) { continue; } + if (i == 1) + fallbackModeInfo = drmMode; + auto aqMode = makeShared(); aqMode->pixelSize = {drmMode.hdisplay, drmMode.vdisplay}; aqMode->refreshRate = calculateRefresh(drmMode); aqMode->preferred = (drmMode.type & DRM_MODE_TYPE_PREFERRED); + aqMode->modeInfo = drmMode; output->modes.emplace_back(aqMode); @@ -682,6 +774,9 @@ void Aquamarine::SDRMConnector::connect(drmModeConnector* connector) { output->nonDesktop = prop; } + canDoVrr = props.vrr_capable && crtc->props.vrr_enabled && !getDRMProp(backend->gpu->fd, id, props.vrr_capable, &prop) && prop; + output->vrrCapable = canDoVrr; + maxBpcBounds.fill(0); if (props.max_bpc && !introspectDRMPropRange(backend->gpu->fd, props.max_bpc, maxBpcBounds.data(), &maxBpcBounds[1])) @@ -702,12 +797,18 @@ void Aquamarine::SDRMConnector::connect(drmModeConnector* connector) { output->model = model; output->serial = serial; output->description = std::format("{} {} {} ({})", make, model, serial, szName); + output->needsFrame = true; backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Description {}", output->description)); status = DRM_MODE_CONNECTED; + if (!backend->backend->ready) + return; + + output->swapchain = makeShared(backend->backend->allocator); backend->backend->events.newOutput.emit(output); + output->scheduleFrame(); } void Aquamarine::SDRMConnector::disconnect() { @@ -722,16 +823,162 @@ void Aquamarine::SDRMConnector::disconnect() { status = DRM_MODE_DISCONNECTED; } +bool Aquamarine::SDRMConnector::commitState(const SDRMConnectorCommitData& data) { + const bool ok = backend->impl->commit(self.lock(), data); + + if (ok && !data.test) + applyCommit(data); + else + rollbackCommit(data); + + return ok; +} + +void Aquamarine::SDRMConnector::applyCommit(const SDRMConnectorCommitData& data) { + crtc->primary->back = crtc->primary->front; + crtc->primary->front = data.mainFB; + if (crtc->cursor) { + crtc->cursor->back = crtc->cursor->front; + crtc->cursor->front = data.cursorFB; + } + + pendingCursorFB.reset(); + + if (output->state->state().committed & COutputState::AQ_OUTPUT_STATE_MODE) + refresh = calculateRefresh(data.modeInfo); +} + +void Aquamarine::SDRMConnector::rollbackCommit(const SDRMConnectorCommitData& data) { + ; +} + +void Aquamarine::SDRMConnector::onPresent() { + ; +} + Aquamarine::CDRMOutput::~CDRMOutput() { ; } bool Aquamarine::CDRMOutput::commit() { - return true; + return commitState(); } bool Aquamarine::CDRMOutput::test() { - return true; + return commitState(true); +} + +bool Aquamarine::CDRMOutput::commitState(bool onlyTest) { + if (!backend->backend->session->active) { + backend->backend->log(AQ_LOG_ERROR, "drm: Session inactive"); + return false; + } + + if (!connector->crtc) { + backend->backend->log(AQ_LOG_ERROR, "drm: No CRTC attached to output"); + return false; + } + + const auto& STATE = state->state(); + const uint32_t COMMITTED = STATE.committed; + + if ((COMMITTED & COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_ENABLED) && STATE.enabled) { + if (!STATE.mode && STATE.customMode) { + backend->backend->log(AQ_LOG_ERROR, "drm: No mode on enable commit"); + return false; + } + } + + if (STATE.adaptiveSync && !connector->canDoVrr) { + backend->backend->log(AQ_LOG_ERROR, "drm: No Adaptive sync support for output"); + return false; + } + + if (STATE.presentationMode == AQ_OUTPUT_PRESENTATION_IMMEDIATE && !backend->drmProps.supportsAsyncCommit) { + backend->backend->log(AQ_LOG_ERROR, "drm: No Immediate presentation support in the backend"); + return false; + } + + if (COMMITTED & COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_BUFFER && !STATE.buffer) { + backend->backend->log(AQ_LOG_ERROR, "drm: No buffer committed"); + return false; + } + + // If we are changing the rendering format, we may need to reconfigure the output (aka modeset) + // which may result in some glitches + const bool NEEDS_RECONFIG = COMMITTED & + (COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_ENABLED | COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_FORMAT | + COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_MODE); + + const bool BLOCKING = NEEDS_RECONFIG || !(COMMITTED & COutputState::eOutputStateProperties::AQ_OUTPUT_STATE_BUFFER); + + const auto MODE = STATE.mode ? STATE.mode : STATE.customMode; + + uint32_t flags = 0; + + if (!onlyTest) { + if (NEEDS_RECONFIG) { + if (STATE.enabled) + backend->backend->log(AQ_LOG_DEBUG, + std::format("drm: Modesetting {} with {}x{}@{:.2f}Hz", name, (int)MODE->pixelSize.x, (int)MODE->pixelSize.y, MODE->refreshRate / 1000.F)); + else + backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Disabling output {}", name)); + } + + if (!BLOCKING && connector->isPageFlipPending) { + backend->backend->log(AQ_LOG_ERROR, "drm: Cannot commit when a page-flip is awaiting"); + return false; + } + + if (STATE.enabled) + flags |= DRM_MODE_PAGE_FLIP_EVENT; + if (STATE.presentationMode == AQ_OUTPUT_PRESENTATION_IMMEDIATE) + flags |= DRM_MODE_PAGE_FLIP_ASYNC; + } + + SDRMConnectorCommitData data; + + if (STATE.buffer) { + backend->backend->log(AQ_LOG_TRACE, "drm: Committed a buffer, updating state"); + + SP drmFB; + auto buf = STATE.buffer; + // try to find the buffer in its layer + if (connector->crtc->primary->back && connector->crtc->primary->back->buffer == buf) { + backend->backend->log(AQ_LOG_TRACE, "drm: CRTC's back buffer matches committed :D"); + drmFB = connector->crtc->primary->back; + } else if (connector->crtc->primary->front && connector->crtc->primary->front->buffer == buf) { + backend->backend->log(AQ_LOG_TRACE, "drm: CRTC's front buffer matches committed"); + drmFB = connector->crtc->primary->front; + } + + if (!drmFB) + drmFB = CDRMFB::create(buf, backend); + + if (!drmFB) { + backend->backend->log(AQ_LOG_ERROR, "drm: Buffer failed to import to KMS"); + return false; + } + + data.mainFB = drmFB; + } + + data.blocking = BLOCKING; + data.modeset = NEEDS_RECONFIG; + data.flags = flags; + data.test = onlyTest; + if (MODE->modeInfo.has_value()) + data.modeInfo = *MODE->modeInfo; + else + data.calculateMode(connector); + + bool ok = connector->commitState(data); + + events.commit.emit(); + + state->onCommit(); + + return ok; } SP Aquamarine::CDRMOutput::getBackend() { @@ -747,14 +994,175 @@ void Aquamarine::CDRMOutput::moveCursor(const Vector2D& coord) { } void Aquamarine::CDRMOutput::scheduleFrame() { - ; + if (connector->isPageFlipPending) + return; + + backend->idleCallbacks.emplace_back([this]() { events.frame.emit(); }); } Vector2D Aquamarine::CDRMOutput::maxCursorSize() { return backend->drmProps.cursorSize; } -Aquamarine::CDRMOutput::CDRMOutput(const std::string& name_, Hyprutils::Memory::CWeakPointer backend_, Hyprutils::Memory::CSharedPointer connector_) : +Aquamarine::CDRMOutput::CDRMOutput(const std::string& name_, Hyprutils::Memory::CWeakPointer backend_, SP connector_) : backend(backend_), connector(connector_) { name = name_; } + +SP Aquamarine::CDRMFB::create(SP buffer_, Hyprutils::Memory::CWeakPointer backend_) { + auto fb = SP(new CDRMFB(buffer_, backend_)); + + if (!fb->id) + return nullptr; + + return fb; +} + +Aquamarine::CDRMFB::CDRMFB(SP buffer_, Hyprutils::Memory::CWeakPointer backend_) : buffer(buffer_), backend(backend_) { + auto attrs = buffer->dmabuf(); + if (!attrs.success) { + backend->backend->log(AQ_LOG_ERROR, "drm: Buffer submitted has no dmabuf"); + return; + } + + if (buffer->attachments.has(AQ_ATTACHMENT_DRM_KMS_UNIMPORTABLE)) { + backend->backend->log(AQ_LOG_ERROR, "drm: Buffer submitted is unimportable"); + return; + } + + // TODO: check format + + for (int i = 0; i < attrs.planes; ++i) { + int ret = drmPrimeFDToHandle(backend->gpu->fd, attrs.fds.at(i), &boHandles.at(i)); + if (ret) { + backend->backend->log(AQ_LOG_ERROR, "drm: drmPrimeFDToHandle failed"); + drop(); + return; + } + + backend->backend->log(AQ_LOG_TRACE, std::format("drm: CDRMFB: plane {} has fd {}, got handle {}", i, attrs.fds.at(i), boHandles.at(i))); + } + + id = submitBuffer(); + if (!id) { + backend->backend->log(AQ_LOG_ERROR, "drm: Failed to submit a buffer to KMS"); + buffer->attachments.add(makeShared()); + drop(); + return; + } + + backend->backend->log(AQ_LOG_TRACE, std::format("drm: new buffer {}", id)); + + // FIXME: wlroots does this, I am unsure why, but if I do, the gpu driver will kill us. + // closeHandles(); +} + +Aquamarine::CDRMFB::~CDRMFB() { + drop(); +} + +void Aquamarine::CDRMFB::closeHandles() { + if (handlesClosed) + return; + + handlesClosed = true; + + for (auto& h : boHandles) { + if (h == 0) + continue; + + if (drmCloseBufferHandle(backend->gpu->fd, h)) + backend->backend->log(AQ_LOG_ERROR, "drm: drmCloseBufferHandle failed"); + h = 0; + } +} + +void Aquamarine::CDRMFB::drop() { + if (dropped) + return; + + dropped = true; + + if (!id) + return; + + backend->backend->log(AQ_LOG_TRACE, std::format("drm: dropping buffer {}", id)); + + int ret = drmModeCloseFB(backend->gpu->fd, id); + if (ret == -EINVAL) + ret = drmModeRmFB(backend->gpu->fd, id); + + if (ret) + backend->backend->log(AQ_LOG_ERROR, std::format("drm: Failed to close a buffer: {}", strerror(-ret))); +} + +uint32_t Aquamarine::CDRMFB::submitBuffer() { + auto attrs = buffer->dmabuf(); + uint32_t newID = 0; + std::array mods = {0}; + for (size_t i = 0; i < attrs.planes; ++i) { + mods.at(i) = attrs.modifier; + } + + if (backend->drmProps.supportsAddFb2Modifiers && attrs.modifier != DRM_FORMAT_MOD_INVALID) { + backend->backend->log(AQ_LOG_TRACE, + std::format("drm: Using drmModeAddFB2WithModifiers to import buffer into KMS: Size {} with format {} and mod {}", attrs.size, + fourccToName(attrs.format), attrs.modifier)); + if (drmModeAddFB2WithModifiers(backend->gpu->fd, attrs.size.x, attrs.size.y, attrs.format, boHandles.data(), attrs.strides.data(), attrs.offsets.data(), mods.data(), + &newID, DRM_MODE_FB_MODIFIERS)) { + backend->backend->log(AQ_LOG_ERROR, "drm: Failed to submit a buffer with AddFB2"); + return 0; + } + } else { + if (attrs.modifier != DRM_FORMAT_MOD_INVALID && attrs.modifier != DRM_FORMAT_MOD_LINEAR) { + backend->backend->log(AQ_LOG_ERROR, "drm: drmModeAddFB2WithModifiers unsupported and buffer has explicit modifiers"); + return 0; + } + + backend->backend->log( + AQ_LOG_TRACE, + std::format("drm: Using drmModeAddFB2 to import buffer into KMS: Size {} with format {} and mod {}", attrs.size, fourccToName(attrs.format), attrs.modifier)); + + if (drmModeAddFB2(backend->gpu->fd, attrs.size.x, attrs.size.y, attrs.format, boHandles.data(), attrs.strides.data(), attrs.offsets.data(), &newID, 0)) { + backend->backend->log(AQ_LOG_ERROR, "drm: drmModeAddFB2 failed"); + return 0; + } + } + + return newID; +} + +void Aquamarine::SDRMConnectorCommitData::calculateMode(Hyprutils::Memory::CSharedPointer connector) { + const auto& STATE = connector->output->state->state(); + const auto MODE = STATE.mode ? STATE.mode : STATE.customMode; + + di_cvt_options options = { + .red_blank_ver = DI_CVT_REDUCED_BLANKING_NONE, + .h_pixels = (int)MODE->pixelSize.x, + .v_lines = (int)MODE->pixelSize.y, + .ip_freq_rqd = MODE->refreshRate ? MODE->refreshRate / 1000.0 : 60.0, + }; + di_cvt_timing timing; + + di_cvt_compute(&timing, &options); + + uint16_t hsync_start = (int)MODE->pixelSize.y + timing.h_front_porch; + uint16_t vsync_start = timing.v_lines_rnd + timing.v_front_porch; + uint16_t hsync_end = hsync_start + timing.h_sync; + uint16_t vsync_end = vsync_start + timing.v_sync; + + modeInfo = (drmModeModeInfo){ + .clock = (uint32_t)std::round(timing.act_pixel_freq * 1000), + .hdisplay = (uint16_t)MODE->pixelSize.y, + .hsync_start = hsync_start, + .hsync_end = hsync_end, + .htotal = (uint16_t)(hsync_end + timing.h_back_porch), + .vdisplay = (uint16_t)timing.v_lines_rnd, + .vsync_start = vsync_start, + .vsync_end = vsync_end, + .vtotal = (uint16_t)(vsync_end + timing.v_back_porch), + .vrefresh = (uint32_t)std::round(timing.act_frame_rate), + .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC, + }; + snprintf(modeInfo.name, sizeof(modeInfo.name), "%dx%d", (int)MODE->pixelSize.x, (int)MODE->pixelSize.y); +} diff --git a/src/backend/drm/impl/Legacy.cpp b/src/backend/drm/impl/Legacy.cpp new file mode 100644 index 0000000..389cfa5 --- /dev/null +++ b/src/backend/drm/impl/Legacy.cpp @@ -0,0 +1,88 @@ +#include +#include +#include +#include +#include + +using namespace Aquamarine; +using namespace Hyprutils::Memory; +using namespace Hyprutils::Math; +#define SP CSharedPointer + +Aquamarine::CDRMLegacyImpl::CDRMLegacyImpl(Hyprutils::Memory::CSharedPointer backend_) : backend(backend_) { + ; +} + +bool Aquamarine::CDRMLegacyImpl::commitInternal(Hyprutils::Memory::CSharedPointer connector, const SDRMConnectorCommitData& data) { + const auto& STATE = connector->output->state->state(); + SP mainFB; + bool enable = STATE.enabled; + + if (enable) { + if (!data.mainFB) + connector->backend->backend->log(AQ_LOG_WARNING, "legacy drm: No buffer, will fall back to only modeset (if present)"); + else + mainFB = data.mainFB; + } + + if (data.modeset) { + connector->backend->backend->log(AQ_LOG_DEBUG, std::format("legacy drm: Modesetting CRTC {}", connector->crtc->id)); + + uint32_t dpms = enable ? DRM_MODE_DPMS_ON : DRM_MODE_DPMS_OFF; + if (drmModeConnectorSetProperty(connector->backend->gpu->fd, connector->id, connector->props.dpms, dpms)) { + connector->backend->backend->log(AQ_LOG_ERROR, "legacy drm: Failed to set dpms"); + return false; + } + + std::vector connectors; + drmModeModeInfo* mode = nullptr; + if (enable) { + connectors.push_back(connector->id); + mode = (drmModeModeInfo*)&data.modeInfo; + } + + connector->backend->backend->log(AQ_LOG_DEBUG, std::format("legacy drm: Modesetting CRTC, connectors: {}", connectors.size())); + connector->backend->backend->log( + AQ_LOG_DEBUG, + std::format("legacy drm: Modesetting CRTC, mode: clock {} hdisplay {} vdisplay {} vrefresh {}", mode->clock, mode->hdisplay, mode->vdisplay, mode->vrefresh)); + + if (auto ret = drmModeSetCrtc(connector->backend->gpu->fd, connector->crtc->id, mainFB ? mainFB->id : -1, 0, 0, connectors.data(), connectors.size(), mode); ret) { + connector->backend->backend->log(AQ_LOG_ERROR, std::format("legacy drm: drmModeSetCrtc failed: {}", strerror(-ret))); + return false; + } + } + + // TODO: gamma + + // TODO: Adaptive sync + + // TODO: cursor plane + if (drmModeSetCursor(connector->backend->gpu->fd, connector->crtc->id, 0, 0, 0)) + connector->backend->backend->log(AQ_LOG_ERROR, "legacy drm: cursor null failed"); + + if (!enable) + return true; + + if (!(data.flags & DRM_MODE_PAGE_FLIP_EVENT)) + return true; + + if (int ret = drmModePageFlip(connector->backend->gpu->fd, connector->crtc->id, mainFB ? mainFB->id : -1, data.flags, &connector->pendingPageFlip); ret) { + connector->backend->backend->log(AQ_LOG_ERROR, std::format("legacy drm: drmModePageFlip failed: {}", strerror(-ret))); + return false; + } + + connector->isPageFlipPending = true; + + return true; +} + +bool Aquamarine::CDRMLegacyImpl::testInternal(Hyprutils::Memory::CSharedPointer connector, const SDRMConnectorCommitData& data) { + return true; // TODO: lol +} + +bool Aquamarine::CDRMLegacyImpl::commit(Hyprutils::Memory::CSharedPointer connector, const SDRMConnectorCommitData& data) { + if (!testInternal(connector, data)) + return false; + + return commitInternal(connector, data); +} \ No newline at end of file diff --git a/src/misc/Attachment.cpp b/src/misc/Attachment.cpp new file mode 100644 index 0000000..16a251f --- /dev/null +++ b/src/misc/Attachment.cpp @@ -0,0 +1,33 @@ +#include + +using namespace Aquamarine; +using namespace Hyprutils::Memory; +#define SP CSharedPointer + +bool Aquamarine::CAttachmentManager::has(eAttachmentType type) { + for (auto& a : attachments) { + if (a->type() == type) + return true; + } + return false; +} + +SP Aquamarine::CAttachmentManager::get(eAttachmentType type) { + for (auto& a : attachments) { + if (a->type() == type) + return a; + } + return nullptr; +} + +void Aquamarine::CAttachmentManager::add(SP attachment) { + attachments.emplace_back(attachment); +} + +void Aquamarine::CAttachmentManager::remove(SP attachment) { + std::erase(attachments, attachment); +} + +void Aquamarine::CAttachmentManager::removeByType(eAttachmentType type) { + std::erase_if(attachments, [type](const auto& e) { return e->type() == type; }); +}