From 790ce7dfbf420d629b97adbe655e9570cfcfa8ed Mon Sep 17 00:00:00 2001 From: Vaxry Date: Wed, 19 Jun 2024 22:40:23 +0200 Subject: [PATCH] Wayland: primitive but working backend This implements enough for wayland to be a functioning backend. --- CMakeLists.txt | 3 +- include/aquamarine/allocator/Allocator.hpp | 20 ++ include/aquamarine/allocator/GBM.hpp | 61 +++++ include/aquamarine/allocator/Swapchain.hpp | 32 +++ include/aquamarine/backend/Backend.hpp | 5 + include/aquamarine/backend/Wayland.hpp | 59 ++++- include/aquamarine/buffer/Buffer.hpp | 1 + include/aquamarine/output/Output.hpp | 13 +- src/allocator/GBM.cpp | 147 ++++++++++++ src/allocator/Swapchain.cpp | 93 ++++++++ src/backend/Backend.cpp | 16 ++ src/backend/Wayland.cpp | 260 +++++++++++++++++++-- src/include/FormatUtils.hpp | 6 + src/utils/FormatUtils.cpp | 8 + tests/SimpleWindow.cpp | 60 ++++- 15 files changed, 745 insertions(+), 39 deletions(-) create mode 100644 include/aquamarine/allocator/Allocator.hpp create mode 100644 include/aquamarine/allocator/GBM.hpp create mode 100644 include/aquamarine/allocator/Swapchain.hpp create mode 100644 src/allocator/GBM.cpp create mode 100644 src/allocator/Swapchain.cpp create mode 100644 src/include/FormatUtils.hpp create mode 100644 src/utils/FormatUtils.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a1d9dae..fd994ff 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 wayland-client wayland-protocols hyprutils>=0.1.2 pixman-1 wayland-client) +pkg_check_modules(deps REQUIRED IMPORTED_TARGET wayland-client wayland-protocols hyprutils>=0.1.2 pixman-1 wayland-client libdrm gbm) configure_file(aquamarine.pc.in aquamarine.pc @ONLY) @@ -77,6 +77,7 @@ endfunction() protocolWayland() protocolNew("stable/xdg-shell" "xdg-shell" false) +protocolNew("stable/linux-dmabuf" "linux-dmabuf-v1" false) # tests add_custom_target(tests) diff --git a/include/aquamarine/allocator/Allocator.hpp b/include/aquamarine/allocator/Allocator.hpp new file mode 100644 index 0000000..35b20e5 --- /dev/null +++ b/include/aquamarine/allocator/Allocator.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include +#include "../buffer/Buffer.hpp" +#include + +namespace Aquamarine { + class CBackend; + + struct SAllocatorBufferParams { + Hyprutils::Math::Vector2D size; + uint32_t format = DRM_FORMAT_INVALID; + }; + + class IAllocator { + public: + virtual Hyprutils::Memory::CSharedPointer acquire(const SAllocatorBufferParams& params) = 0; + virtual Hyprutils::Memory::CSharedPointer getBackend() = 0; + }; +}; diff --git a/include/aquamarine/allocator/GBM.hpp b/include/aquamarine/allocator/GBM.hpp new file mode 100644 index 0000000..e8df447 --- /dev/null +++ b/include/aquamarine/allocator/GBM.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include "Allocator.hpp" + +struct gbm_device; +struct gbm_bo; + +namespace Aquamarine { + class CGBMAllocator; + class CBackend; + + class CGBMBuffer : public IBuffer { + public: + virtual ~CGBMBuffer(); + + virtual eBufferCapability caps(); + virtual eBufferType type(); + virtual void update(const Hyprutils::Math::CRegion& damage); + virtual bool isSynchronous(); + virtual bool good(); + virtual SDMABUFAttrs dmabuf(); + + private: + CGBMBuffer(const SAllocatorBufferParams& params, Hyprutils::Memory::CWeakPointer allocator_); + + Hyprutils::Memory::CWeakPointer allocator; + + // gbm stuff + gbm_bo* bo = nullptr; + SDMABUFAttrs attrs{.success = false}; + + friend class CGBMAllocator; + }; + + class CGBMAllocator : public IAllocator { + public: + static Hyprutils::Memory::CSharedPointer create(int drmfd_, Hyprutils::Memory::CWeakPointer backend_); + + virtual Hyprutils::Memory::CSharedPointer acquire(const SAllocatorBufferParams& params); + virtual Hyprutils::Memory::CSharedPointer getBackend(); + + // + Hyprutils::Memory::CWeakPointer self; + + private: + CGBMAllocator(int fd_, Hyprutils::Memory::CWeakPointer backend_); + + // a vector purely for tracking (debugging) the buffers and nothing more + std::vector> buffers; + + int fd = -1; + Hyprutils::Memory::CWeakPointer backend; + + // gbm stuff + gbm_device* gbmDevice = nullptr; + std::string gbmDeviceBackendName = ""; + std::string drmName = ""; + + friend class CGBMBuffer; + }; +}; diff --git a/include/aquamarine/allocator/Swapchain.hpp b/include/aquamarine/allocator/Swapchain.hpp new file mode 100644 index 0000000..0d8d391 --- /dev/null +++ b/include/aquamarine/allocator/Swapchain.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include "Allocator.hpp" + +namespace Aquamarine { + + struct SSwapchainOptions { + size_t length = 0; + Hyprutils::Math::Vector2D size; + uint32_t format = DRM_FORMAT_INVALID; + }; + + class CSwapchain { + public: + CSwapchain(Hyprutils::Memory::CSharedPointer allocator_); + + bool reconfigure(const SSwapchainOptions& options_); + + bool contains(Hyprutils::Memory::CSharedPointer buffer); + Hyprutils::Memory::CSharedPointer next(int* age); + + private: + bool fullReconfigure(const SSwapchainOptions& options_); + bool resize(size_t newSize); + + // + SSwapchainOptions options; + Hyprutils::Memory::CSharedPointer allocator; + std::vector> buffers; + int lastAcquired = 0; + }; +}; diff --git a/include/aquamarine/backend/Backend.hpp b/include/aquamarine/backend/Backend.hpp index 31b7504..574ef2c 100644 --- a/include/aquamarine/backend/Backend.hpp +++ b/include/aquamarine/backend/Backend.hpp @@ -6,6 +6,7 @@ #include #include #include +#include "../allocator/Allocator.hpp" namespace Aquamarine { enum eBackendType { @@ -56,6 +57,7 @@ namespace Aquamarine { virtual eBackendType type() = 0; virtual bool start() = 0; virtual int pollFD() = 0; + virtual int drmFD() = 0; virtual bool dispatchEvents() = 0; }; @@ -82,6 +84,8 @@ namespace Aquamarine { Hyprutils::Signal::CSignal newTouch; } events; + Hyprutils::Memory::CSharedPointer allocator; + private: CBackend(); @@ -90,6 +94,7 @@ namespace Aquamarine { std::vector implementationOptions; std::vector> implementations; SBackendOptions options; + Hyprutils::Memory::CWeakPointer self; // struct { diff --git a/include/aquamarine/backend/Wayland.hpp b/include/aquamarine/backend/Wayland.hpp index b14ef6c..b8a5621 100644 --- a/include/aquamarine/backend/Wayland.hpp +++ b/include/aquamarine/backend/Wayland.hpp @@ -1,32 +1,64 @@ #pragma once #include "./Backend.hpp" +#include "../allocator/Swapchain.hpp" #include "../output/Output.hpp" #include "../input/Input.hpp" #include #include #include #include +#include +#include namespace Aquamarine { class CBackend; class CWaylandBackend; + class CWaylandOutput; + + class CWaylandBuffer { + public: + CWaylandBuffer(Hyprutils::Memory::CSharedPointer buffer_, Hyprutils::Memory::CWeakPointer backend_); + ~CWaylandBuffer(); + bool good(); + + bool pendingRelease = false; + + private: + struct { + Hyprutils::Memory::CSharedPointer buffer; + } waylandState; + + Hyprutils::Memory::CWeakPointer buffer; + Hyprutils::Memory::CWeakPointer backend; + + friend class CWaylandOutput; + }; class CWaylandOutput : public IOutput { public: virtual ~CWaylandOutput(); - virtual void commit(); + virtual bool commit(); private: CWaylandOutput(const std::string& name_, Hyprutils::Memory::CWeakPointer backend_); - std::string name; - Hyprutils::Memory::CWeakPointer backend; + std::string name; + Hyprutils::Memory::CWeakPointer backend; + + Hyprutils::Memory::CSharedPointer wlBufferFromBuffer(Hyprutils::Memory::CSharedPointer buffer); + + void sendFrameAndSetCallback(); + + struct { + std::vector, Hyprutils::Memory::CSharedPointer>> buffers; + } backendState; struct { Hyprutils::Memory::CSharedPointer surface; Hyprutils::Memory::CSharedPointer xdgSurface; Hyprutils::Memory::CSharedPointer xdgToplevel; + Hyprutils::Memory::CSharedPointer frameCallback; } waylandState; friend class CWaylandBackend; @@ -67,6 +99,7 @@ namespace Aquamarine { virtual eBackendType type(); virtual bool start(); virtual int pollFD(); + virtual int drmFD(); virtual bool dispatchEvents(); Hyprutils::Memory::CWeakPointer self; @@ -76,6 +109,7 @@ namespace Aquamarine { void initSeat(); void initShell(); + bool initDmabuf(); void createOutput(const std::string& szName); // @@ -92,15 +126,26 @@ namespace Aquamarine { wl_display* display = nullptr; // hw-s types - Hyprutils::Memory::CSharedPointer registry; - Hyprutils::Memory::CSharedPointer seat; - Hyprutils::Memory::CSharedPointer xdg; - Hyprutils::Memory::CSharedPointer compositor; + Hyprutils::Memory::CSharedPointer registry; + Hyprutils::Memory::CSharedPointer seat; + Hyprutils::Memory::CSharedPointer xdg; + Hyprutils::Memory::CSharedPointer compositor; + Hyprutils::Memory::CSharedPointer dmabuf; + Hyprutils::Memory::CSharedPointer dmabufFeedback; + + // control + bool dmabufFailed = false; } waylandState; + struct { + int fd = -1; + std::string nodeName = ""; + } drmState; + friend class CBackend; friend class CWaylandKeyboard; friend class CWaylandPointer; friend class CWaylandOutput; + friend class CWaylandBuffer; }; }; diff --git a/include/aquamarine/buffer/Buffer.hpp b/include/aquamarine/buffer/Buffer.hpp index 2565bba..9aa1cb5 100644 --- a/include/aquamarine/buffer/Buffer.hpp +++ b/include/aquamarine/buffer/Buffer.hpp @@ -49,6 +49,7 @@ namespace Aquamarine { virtual eBufferType type() = 0; virtual void update(const Hyprutils::Math::CRegion& damage) = 0; virtual bool isSynchronous() = 0; // whether the updates to this buffer are synchronous, aka happen over cpu + virtual bool good() = 0; virtual SDMABUFAttrs dmabuf(); virtual SSHMAttrs shm(); virtual std::tuple beginDataPtr(uint32_t flags); diff --git a/include/aquamarine/output/Output.hpp b/include/aquamarine/output/Output.hpp index 7b0add0..cc9cc38 100644 --- a/include/aquamarine/output/Output.hpp +++ b/include/aquamarine/output/Output.hpp @@ -4,6 +4,9 @@ #include #include #include +#include +#include "../allocator/Swapchain.hpp" +#include "../buffer/Buffer.hpp" namespace Aquamarine { struct SOutputMode { @@ -21,8 +24,6 @@ namespace Aquamarine { class COutputState { public: - COutputState(Hyprutils::Memory::CSharedPointer parent); - Hyprutils::Math::CRegion damage; bool enabled = false; bool adaptiveSync = false; @@ -30,6 +31,9 @@ namespace Aquamarine { std::vector gammaLut; Hyprutils::Math::Vector2D lastModeSize; Hyprutils::Memory::CWeakPointer mode; + std::optional customMode; + uint32_t drmFormat = DRM_FORMAT_INVALID; + Hyprutils::Memory::CSharedPointer buffer; }; class IOutput { @@ -38,7 +42,7 @@ namespace Aquamarine { ; } - virtual void commit() = 0; + virtual bool commit() = 0; std::string name, description, make, model, serial; Hyprutils::Math::Vector2D physicalSize; @@ -48,7 +52,8 @@ namespace Aquamarine { // std::vector> modes; - Hyprutils::Memory::CSharedPointer state; + Hyprutils::Memory::CSharedPointer state = Hyprutils::Memory::makeShared(); + Hyprutils::Memory::CSharedPointer swapchain; // struct SStateEvent { diff --git a/src/allocator/GBM.cpp b/src/allocator/GBM.cpp new file mode 100644 index 0000000..5d76ab4 --- /dev/null +++ b/src/allocator/GBM.cpp @@ -0,0 +1,147 @@ +#include +#include +#include "FormatUtils.hpp" +#include +#include + +using namespace Aquamarine; +using namespace Hyprutils::Memory; +#define SP CSharedPointer + +Aquamarine::CGBMBuffer::CGBMBuffer(const SAllocatorBufferParams& params, Hyprutils::Memory::CWeakPointer allocator_) : allocator(allocator_) { + if (!allocator) + return; + + attrs.size = params.size; + attrs.format = params.format; + + // FIXME: proper modifier support? This might implode on some GPUs on the Wayland backend + // for sure. + + bo = gbm_bo_create(allocator->gbmDevice, params.size.x, params.size.y, params.format, GBM_BO_USE_RENDERING); + + if (!bo) { + allocator->backend->log(AQ_LOG_ERROR, "GBM: Failed to allocate a GBM buffer: bo null"); + return; + } + + attrs.planes = gbm_bo_get_plane_count(bo); + attrs.modifier = gbm_bo_get_modifier(bo); + + for (size_t i = 0; i < (size_t)attrs.planes; ++i) { + attrs.strides.at(i) = gbm_bo_get_stride_for_plane(bo, i); + attrs.offsets.at(i) = gbm_bo_get_offset(bo, i); + attrs.fds.at(i) = gbm_bo_get_fd_for_plane(bo, i); + + if (attrs.fds.at(i) < 0) { + allocator->backend->log(AQ_LOG_ERROR, std::format("GBM: Failed to query fd for plane {}", i)); + for (size_t j = 0; j < i; ++j) { + close(attrs.fds.at(j)); + } + attrs.planes = 0; + return; + } + } + + attrs.success = true; + + auto modName = drmGetFormatModifierName(attrs.modifier); + + allocator->backend->log( + AQ_LOG_ERROR, + std::format("GBM: Allocated a new buffer with size {} and format {} with modifier {}", attrs.size, fourccToName(attrs.format), modName ? modName : "Unknown")); + + free(modName); +} + +Aquamarine::CGBMBuffer::~CGBMBuffer() { + if (bo) + gbm_bo_destroy(bo); + for (size_t i = 0; i < (size_t)attrs.planes; i++) + close(attrs.fds.at(i)); +} + +eBufferCapability Aquamarine::CGBMBuffer::caps() { + return (Aquamarine::eBufferCapability)0; +} + +eBufferType Aquamarine::CGBMBuffer::type() { + return Aquamarine::eBufferType::BUFFER_TYPE_DMABUF; +} + +void Aquamarine::CGBMBuffer::update(const Hyprutils::Math::CRegion& damage) { + ; +} + +bool Aquamarine::CGBMBuffer::isSynchronous() { + return false; +} + +bool Aquamarine::CGBMBuffer::good() { + return true; +} + +SDMABUFAttrs Aquamarine::CGBMBuffer::dmabuf() { + return attrs; +} + +SP Aquamarine::CGBMAllocator::create(int drmfd_, Hyprutils::Memory::CWeakPointer backend_) { + uint64_t capabilities = 0; + if (drmGetCap(drmfd_, DRM_CAP_PRIME, &capabilities) || !(capabilities & DRM_PRIME_CAP_EXPORT)) { + backend_->log(AQ_LOG_ERROR, "Cannot create a GBM Allocator: PRIME export is not supported by the gpu."); + return nullptr; + } + + auto allocator = SP(new CGBMAllocator(drmfd_, backend_)); + + if (!allocator->gbmDevice) { + backend_->log(AQ_LOG_ERROR, "Cannot create a GBM Allocator: gbm failed to create a device."); + return nullptr; + } + + backend_->log(AQ_LOG_DEBUG, std::format("Created a GBM allocator with drm fd {}", drmfd_)); + + allocator->self = allocator; + + return allocator; +} + +Aquamarine::CGBMAllocator::CGBMAllocator(int fd_, Hyprutils::Memory::CWeakPointer backend_) : fd(fd_), backend(backend_) { + gbmDevice = gbm_create_device(fd_); + if (!gbmDevice) { + backend->log(AQ_LOG_ERROR, std::format("Couldn't open a GBM device at fd {}", fd_)); + return; + } + + gbmDeviceBackendName = gbm_device_get_backend_name(gbmDevice); + auto drmName_ = drmGetDeviceNameFromFd2(fd_); + drmName = drmName_; + free(drmName_); +} + +SP Aquamarine::CGBMAllocator::acquire(const SAllocatorBufferParams& params) { + if (params.size.x < 1 || params.size.y < 1) { + backend->log(AQ_LOG_ERROR, std::format("Couldn't allocate a gbm buffer with invalid size {}", params.size)); + return nullptr; + } + + if (params.format == DRM_FORMAT_INVALID) { + backend->log(AQ_LOG_ERROR, "Couldn't allocate a gbm buffer with invalid format"); + return nullptr; + } + + auto newBuffer = SP(new CGBMBuffer(params, self)); + + if (!newBuffer->good()) { + backend->log(AQ_LOG_ERROR, std::format("Couldn't allocate a gbm buffer with size {} and format {}", params.size, fourccToName(params.format))); + return nullptr; + } + + buffers.emplace_back(newBuffer); + std::erase_if(buffers, [](const auto& b) { return b.expired(); }); + return newBuffer; +} + +Hyprutils::Memory::CSharedPointer Aquamarine::CGBMAllocator::getBackend() { + return backend.lock(); +} diff --git a/src/allocator/Swapchain.cpp b/src/allocator/Swapchain.cpp new file mode 100644 index 0000000..7225958 --- /dev/null +++ b/src/allocator/Swapchain.cpp @@ -0,0 +1,93 @@ +#include +#include +#include "FormatUtils.hpp" + +using namespace Aquamarine; +using namespace Hyprutils::Memory; +#define SP CSharedPointer + +Aquamarine::CSwapchain::CSwapchain(SP allocator_) : allocator(allocator_) { + if (!allocator) + return; +} + +bool Aquamarine::CSwapchain::reconfigure(const SSwapchainOptions& options_) { + if (!allocator) + return false; + + if (options_.format == options.format && options_.size == options.size && options_.length == options.length) + return true; // no need to reconfigure + + if (options_.format == options.format && options_.size == options.size) { + bool ok = resize(options_.length); + if (!ok) + return false; + + options = options_; + + allocator->getBackend()->log(AQ_LOG_DEBUG, std::format("Swapchain: Resized a {} {} swapchain to length {}", options.size, fourccToName(options.format), options.length)); + return true; + } + + bool ok = fullReconfigure(options_); + if (!ok) + return false; + + options = options_; + + allocator->getBackend()->log(AQ_LOG_DEBUG, + std::format("Swapchain: Reconfigured a swapchain to {} {} of length {}", options.size, fourccToName(options.format), options.length)); + return true; +} + +SP Aquamarine::CSwapchain::next(int* age) { + if (!allocator || options.length <= 0) + return nullptr; + + lastAcquired = (lastAcquired + 1) % options.length; + + if (age) + *age = 1; + + return buffers.at(lastAcquired); +} + +bool Aquamarine::CSwapchain::fullReconfigure(const SSwapchainOptions& options_) { + buffers.clear(); + for (size_t i = 0; i < options_.length; ++i) { + auto buf = allocator->acquire(SAllocatorBufferParams{.size = options_.size, .format = options_.format}); + if (!buf) { + allocator->getBackend()->log(AQ_LOG_ERROR, "Swapchain: Failed acquiring a buffer"); + return false; + } + buffers.emplace_back(buf); + } + + return true; +} + +bool Aquamarine::CSwapchain::resize(size_t newSize) { + if (newSize == buffers.size()) + return true; + + if (newSize < buffers.size()) { + while (buffers.size() > newSize) { + buffers.pop_back(); + } + } else { + while (buffers.size() < newSize) { + auto buf = allocator->acquire(SAllocatorBufferParams{.size = options.size, .format = options.format}); + if (!buf) { + allocator->getBackend()->log(AQ_LOG_ERROR, "Swapchain: Failed acquiring a buffer"); + return false; + } + buffers.emplace_back(buf); + } + } + + return true; +} + +bool Aquamarine::CSwapchain::contains(Hyprutils::Memory::CSharedPointer buffer) { + return std::find(buffers.begin(), buffers.end(), buffer) != buffers.end(); +} diff --git a/src/backend/Backend.cpp b/src/backend/Backend.cpp index a2fd423..47d2b72 100644 --- a/src/backend/Backend.cpp +++ b/src/backend/Backend.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -36,6 +37,10 @@ Hyprutils::Memory::CSharedPointer Aquamarine::CBackend::create(const s backend->options = options; backend->implementationOptions = backends; + backend->self = backend; + + if (backends.size() <= 0) + return nullptr; backend->log(AQ_LOG_DEBUG, "Creating an Aquamarine backend!"); @@ -87,6 +92,17 @@ bool Aquamarine::CBackend::start() { // erase failed impls std::erase_if(implementations, [](const auto& i) { return i->pollFD() < 0; }); + // TODO: obviously change this when (if) we add different allocators. + for (auto& b : implementations) { + if (b->drmFD() >= 0) { + allocator = CGBMAllocator::create(b->drmFD(), self); + break; + } + } + + if (!allocator) + return false; + return true; } diff --git a/src/backend/Wayland.cpp b/src/backend/Wayland.cpp index 5584763..8c278d6 100644 --- a/src/backend/Wayland.cpp +++ b/src/backend/Wayland.cpp @@ -1,6 +1,11 @@ #include #include #include +#include "Shared.hpp" +#include +#include +#include +#include using namespace Aquamarine; using namespace Hyprutils::Memory; @@ -8,13 +13,15 @@ using namespace Hyprutils::Math; #define SP CSharedPointer Aquamarine::CWaylandBackend::~CWaylandBackend() { - ; + if (drmState.fd >= 0) + close(drmState.fd); } + eBackendType Aquamarine::CWaylandBackend::type() { return AQ_BACKEND_WAYLAND; } -Aquamarine::CWaylandBackend::CWaylandBackend(Hyprutils::Memory::CSharedPointer backend_) : backend(backend_) { +Aquamarine::CWaylandBackend::CWaylandBackend(SP backend_) : backend(backend_) { ; } @@ -48,13 +55,21 @@ bool Aquamarine::CWaylandBackend::start() { } else if (NAME == "wl_compositor") { backend->log(AQ_LOG_TRACE, std::format(" > binding to global: {} (version {}) with id {}", name, 6, id)); waylandState.compositor = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)waylandState.registry->resource(), id, &wl_compositor_interface, 6)); + } else if (NAME == "zwp_linux_dmabuf_v1") { + backend->log(AQ_LOG_TRACE, std::format(" > binding to global: {} (version {}) with id {}", name, 5, id)); + waylandState.dmabuf = + makeShared((wl_proxy*)wl_registry_bind((wl_registry*)waylandState.registry->resource(), id, &zwp_linux_dmabuf_v1_interface, 5)); + if (!initDmabuf()) { + backend->log(AQ_LOG_ERROR, "Wayland backend cannot start: zwp_linux_dmabuf_v1 init failed"); + waylandState.dmabufFailed = true; + } } }); waylandState.registry->setGlobalRemove([this](CWlRegistry* r, uint32_t id) { ; }); wl_display_roundtrip(waylandState.display); - if (!waylandState.xdg || !waylandState.compositor || !waylandState.seat) { + if (!waylandState.xdg || !waylandState.compositor || !waylandState.seat || !waylandState.dmabuf || waylandState.dmabufFailed) { backend->log(AQ_LOG_ERROR, "Wayland backend cannot start: Missing protocols"); return false; } @@ -66,8 +81,13 @@ bool Aquamarine::CWaylandBackend::start() { return true; } +int Aquamarine::CWaylandBackend::drmFD() { + return drmState.fd; +} + void Aquamarine::CWaylandBackend::createOutput(const std::string& szName) { - outputs.emplace_back(SP(new CWaylandOutput(szName, self))); + auto o = outputs.emplace_back(SP(new CWaylandOutput(szName, self))); + backend->events.newOutput.emit(SP(o)); } int Aquamarine::CWaylandBackend::pollFD() { @@ -95,8 +115,7 @@ bool Aquamarine::CWaylandBackend::dispatchEvents() { return true; } -Aquamarine::CWaylandKeyboard::CWaylandKeyboard(Hyprutils::Memory::CSharedPointer keyboard_, Hyprutils::Memory::CWeakPointer backend_) : - keyboard(keyboard_), backend(backend_) { +Aquamarine::CWaylandKeyboard::CWaylandKeyboard(SP keyboard_, Hyprutils::Memory::CWeakPointer backend_) : keyboard(keyboard_), backend(backend_) { if (!keyboard->resource()) return; @@ -111,7 +130,7 @@ Aquamarine::CWaylandKeyboard::CWaylandKeyboard(Hyprutils::Memory::CSharedPointer }); keyboard->setModifiers([this](CWlKeyboard* r, uint32_t serial, uint32_t depressed, uint32_t latched, uint32_t locked, uint32_t group) { - events.key.emit(SModifiersEvent{ + events.modifiers.emit(SModifiersEvent{ .depressed = depressed, .latched = latched, .locked = locked, @@ -128,19 +147,21 @@ const std::string& Aquamarine::CWaylandKeyboard::getName() { return name; } -Aquamarine::CWaylandPointer::CWaylandPointer(Hyprutils::Memory::CSharedPointer pointer_, Hyprutils::Memory::CWeakPointer backend_) : - pointer(pointer_), backend(backend_) { +Aquamarine::CWaylandPointer::CWaylandPointer(SP pointer_, Hyprutils::Memory::CWeakPointer backend_) : pointer(pointer_), backend(backend_) { if (!pointer->resource()) return; backend->backend->log(AQ_LOG_DEBUG, "New wayland pointer wl_pointer"); pointer->setMotion([this](CWlPointer* r, uint32_t serial, wl_fixed_t x, wl_fixed_t y) { - if (!backend->focusedOutput || !backend->focusedOutput->state->mode) + if (!backend->focusedOutput || (!backend->focusedOutput->state->mode && !backend->focusedOutput->state->customMode.has_value())) return; + const Vector2D size = + backend->focusedOutput->state->customMode.has_value() ? backend->focusedOutput->state->customMode->pixelSize : backend->focusedOutput->state->mode->pixelSize; + Vector2D local = {wl_fixed_to_double(x), wl_fixed_to_double(y)}; - local = local / backend->focusedOutput->state->mode->pixelSize; + local = local / size; events.warp.emit(SWarpEvent{ .absolute = local, @@ -192,15 +213,17 @@ void Aquamarine::CWaylandBackend::initSeat() { const bool HAS_KEYBOARD = ((uint32_t)cap) & WL_SEAT_CAPABILITY_KEYBOARD; const bool HAS_POINTER = ((uint32_t)cap) & WL_SEAT_CAPABILITY_POINTER; - if (HAS_KEYBOARD && keyboards.empty()) - keyboards.emplace_back(makeShared(SP(waylandState.seat->sendGetKeyboard()), self)); - else if (!HAS_KEYBOARD && !keyboards.empty()) + if (HAS_KEYBOARD && keyboards.empty()) { + auto k = keyboards.emplace_back(makeShared(makeShared(waylandState.seat->sendGetKeyboard()), self)); + backend->events.newKeyboard.emit(SP(k)); + } else if (!HAS_KEYBOARD && !keyboards.empty()) keyboards.clear(); - if (HAS_POINTER && pointers.empty()) - keyboards.emplace_back(makeShared(SP(waylandState.seat->sendGetKeyboard()), self)); - else if (!HAS_POINTER && !keyboards.empty()) - keyboards.clear(); + if (HAS_POINTER && pointers.empty()) { + auto p = pointers.emplace_back(makeShared(makeShared(waylandState.seat->sendGetPointer()), self)); + backend->events.newPointer.emit(SP(p)); + } else if (!HAS_POINTER && !pointers.empty()) + pointers.clear(); }); } @@ -208,17 +231,82 @@ void Aquamarine::CWaylandBackend::initShell() { waylandState.xdg->setPing([](CXdgWmBase* r, uint32_t serial) { r->sendPong(serial); }); } +bool Aquamarine::CWaylandBackend::initDmabuf() { + waylandState.dmabufFeedback = makeShared(waylandState.dmabuf->sendGetDefaultFeedback()); + if (!waylandState.dmabufFeedback) { + backend->log(AQ_LOG_ERROR, "initDmabuf: failed to get default feedback"); + return false; + } + + waylandState.dmabufFeedback->setDone([this](CZwpLinuxDmabufFeedbackV1* r) { + // no-op + backend->log(AQ_LOG_DEBUG, "zwp_linux_dmabuf_v1: Got done"); + }); + + waylandState.dmabufFeedback->setMainDevice([this](CZwpLinuxDmabufFeedbackV1* r, wl_array* deviceArr) { + backend->log(AQ_LOG_DEBUG, "zwp_linux_dmabuf_v1: Got main device"); + + dev_t device; + ASSERT(deviceArr->size == sizeof(device)); + memcpy(&device, deviceArr->data, sizeof(device)); + + drmDevice* drmDev; + if (drmGetDeviceFromDevId(device, /* flags */ 0, &drmDev) != 0) { + backend->log(AQ_LOG_ERROR, "zwp_linux_dmabuf_v1: drmGetDeviceFromDevId failed"); + return; + } + + const char* name = nullptr; + if (drmDev->available_nodes & (1 << DRM_NODE_RENDER)) + name = drmDev->nodes[DRM_NODE_RENDER]; + else { + // Likely a split display/render setup. Pick the primary node and hope + // Mesa will open the right render node under-the-hood. + ASSERT(drmDev->available_nodes & (1 << DRM_NODE_PRIMARY)); + name = drmDev->nodes[DRM_NODE_PRIMARY]; + backend->log(AQ_LOG_WARNING, "zwp_linux_dmabuf_v1: DRM device has no render node, using primary."); + } + + if (!name) { + backend->log(AQ_LOG_ERROR, "zwp_linux_dmabuf_v1: no node name"); + return; + } + + drmState.nodeName = name; + + drmFreeDevice(&drmDev); + + backend->log(AQ_LOG_DEBUG, std::format("zwp_linux_dmabuf_v1: Got node {}", drmState.nodeName)); + }); + + // TODO: format table and tranche + + wl_display_roundtrip(waylandState.display); + + if (!drmState.nodeName.empty()) { + drmState.fd = open(drmState.nodeName.c_str(), O_RDWR | O_NONBLOCK | O_CLOEXEC); + if (drmState.fd < 0) { + backend->log(AQ_LOG_ERROR, std::format("zwp_linux_dmabuf_v1: Failed to open node {}", drmState.nodeName)); + return false; + } + + backend->log(AQ_LOG_DEBUG, std::format("zwp_linux_dmabuf_v1: opened node {} with fd {}", drmState.nodeName, drmState.fd)); + } + + return true; +} + Aquamarine::CWaylandOutput::CWaylandOutput(const std::string& name_, Hyprutils::Memory::CWeakPointer backend_) : name(name_), backend(backend_) { errno = 0; - waylandState.surface = SP(backend->waylandState.compositor->sendCreateSurface()); + waylandState.surface = makeShared(backend->waylandState.compositor->sendCreateSurface()); if (!waylandState.surface->resource()) { backend->backend->log(AQ_LOG_ERROR, std::format("Output {} failed: no surface given. Errno: {}", name, errno)); return; } - waylandState.xdgSurface = SP(backend->waylandState.xdg->sendGetXdgSurface(waylandState.surface->resource())); + waylandState.xdgSurface = makeShared(backend->waylandState.xdg->sendGetXdgSurface(waylandState.surface->resource())); if (!waylandState.xdgSurface->resource()) { backend->backend->log(AQ_LOG_ERROR, std::format("Output {} failed: no xdgSurface given. Errno: {}", name, errno)); @@ -230,7 +318,7 @@ Aquamarine::CWaylandOutput::CWaylandOutput(const std::string& name_, Hyprutils:: r->sendAckConfigure(serial); }); - waylandState.xdgToplevel = SP(waylandState.xdgSurface->sendGetToplevel()); + waylandState.xdgToplevel = makeShared(waylandState.xdgSurface->sendGetToplevel()); if (!waylandState.xdgToplevel->resource()) { backend->backend->log(AQ_LOG_ERROR, std::format("Output {} failed: no xdgToplevel given. Errno: {}", name, errno)); @@ -241,13 +329,29 @@ Aquamarine::CWaylandOutput::CWaylandOutput(const std::string& name_, Hyprutils:: [this](CXdgToplevel* r, wl_array* arr) { backend->backend->log(AQ_LOG_DEBUG, std::format("Output {}: wm_capabilities received", name)); }); waylandState.xdgToplevel->setConfigure([this](CXdgToplevel* r, int32_t w, int32_t h, wl_array* arr) { + // we only create the swapchain here because in the main func we still don't have an allocator + if (!swapchain) { + swapchain = makeShared(backend->backend->allocator); + if (!swapchain) { + backend->backend->log(AQ_LOG_ERROR, std::format("Output {} failed: swapchain creation failed", name)); + return; + } + } + backend->backend->log(AQ_LOG_DEBUG, std::format("Output {}: configure toplevel with {}x{}", name, w, h)); events.state.emit(SStateEvent{.size = {w, h}}); + sendFrameAndSetCallback(); }); + auto inputRegion = makeShared(backend->waylandState.compositor->sendCreateRegion()); + inputRegion->sendAdd(0, 0, INT32_MAX, INT32_MAX); + + waylandState.surface->sendSetInputRegion(inputRegion.get()); waylandState.surface->sendAttach(nullptr, 0, 0); waylandState.surface->sendCommit(); + inputRegion->sendDestroy(); + backend->backend->log(AQ_LOG_DEBUG, std::format("Output {}: initialized", name)); } @@ -260,6 +364,114 @@ Aquamarine::CWaylandOutput::~CWaylandOutput() { waylandState.surface->sendDestroy(); } -void Aquamarine::CWaylandOutput::commit() { - ; -} \ No newline at end of file +bool Aquamarine::CWaylandOutput::commit() { + Vector2D pixelSize = {}; + uint32_t refreshRate = 0; + + if (state->customMode) + pixelSize = state->customMode->pixelSize; + else if (state->mode) + pixelSize = state->mode->pixelSize; + else { + backend->backend->log(AQ_LOG_ERROR, std::format("Output {}: pending state rejected: invalid mode", name)); + return false; + } + + uint32_t format = state->drmFormat; + + if (format == DRM_FORMAT_INVALID) { + backend->backend->log(AQ_LOG_ERROR, std::format("Output {}: pending state rejected: invalid format", name)); + return false; + } + + if (!swapchain->reconfigure(SSwapchainOptions{.length = 2, .size = pixelSize, .format = format})) { + backend->backend->log(AQ_LOG_ERROR, std::format("Output {}: pending state rejected: swapchain failed reconfiguring", name)); + return false; + } + + if (!state->buffer) { + backend->backend->log(AQ_LOG_ERROR, std::format("Output {}: pending state rejected: no buffer", name)); + return false; + } + + auto wlBuffer = wlBufferFromBuffer(state->buffer); + + if (!wlBuffer) { + backend->backend->log(AQ_LOG_ERROR, std::format("Output {}: pending state rejected: no wlBuffer??", name)); + return false; + } + + if (wlBuffer->pendingRelease) + backend->backend->log(AQ_LOG_WARNING, std::format("Output {}: pending state has a non-released buffer??", name)); + + wlBuffer->pendingRelease = true; + + waylandState.surface->sendAttach(wlBuffer->waylandState.buffer.get(), 0, 0); + waylandState.surface->sendDamageBuffer(0, 0, INT32_MAX, INT32_MAX); + waylandState.surface->sendCommit(); + + return true; +} + +SP Aquamarine::CWaylandOutput::wlBufferFromBuffer(SP buffer) { + std::erase_if(backendState.buffers, [this](const auto& el) { return el.first.expired() || !swapchain->contains(el.first.lock()); }); + + for (auto& [k, v] : backendState.buffers) { + if (k != buffer) + continue; + + return v; + } + + // create a new one + auto wlBuffer = makeShared(buffer, backend); + + if (!wlBuffer->good()) + return nullptr; + + backendState.buffers.emplace_back(std::make_pair<>(buffer, wlBuffer)); + + return wlBuffer; +} + +void Aquamarine::CWaylandOutput::sendFrameAndSetCallback() { + events.frame.emit(); + if (waylandState.frameCallback) + return; + + waylandState.frameCallback = makeShared(waylandState.surface->sendFrame()); + waylandState.frameCallback->setDone([this](CWlCallback* r, uint32_t ms) { + events.frame.emit(); + waylandState.frameCallback.reset(); + }); +} + +Aquamarine::CWaylandBuffer::CWaylandBuffer(SP buffer_, Hyprutils::Memory::CWeakPointer backend_) : buffer(buffer_), backend(backend_) { + auto params = makeShared(backend->waylandState.dmabuf->sendCreateParams()); + + if (!params) { + backend->backend->log(AQ_LOG_ERROR, "WaylandBuffer: failed to query params"); + return; + } + + auto attrs = buffer->dmabuf(); + + for (size_t i = 0; i < attrs.planes; ++i) { + params->sendAdd(attrs.fds.at(i), i, attrs.offsets.at(i), attrs.strides.at(i), attrs.modifier >> 32, attrs.modifier & 0xFFFFFFFF); + } + + waylandState.buffer = makeShared(params->sendCreateImmed(attrs.size.x, attrs.size.y, attrs.format, (zwpLinuxBufferParamsV1Flags)0)); + + waylandState.buffer->setRelease([this](CWlBuffer* r) { pendingRelease = false; }); + + params->sendDestroy(); +} + +Aquamarine::CWaylandBuffer::~CWaylandBuffer() { + if (waylandState.buffer && waylandState.buffer->resource()) + waylandState.buffer->sendDestroy(); +} + +bool Aquamarine::CWaylandBuffer::good() { + return waylandState.buffer && waylandState.buffer->resource(); +} diff --git a/src/include/FormatUtils.hpp b/src/include/FormatUtils.hpp new file mode 100644 index 0000000..212e01a --- /dev/null +++ b/src/include/FormatUtils.hpp @@ -0,0 +1,6 @@ +#pragma once + +#include +#include + +std::string fourccToName(uint32_t drmFormat); \ No newline at end of file diff --git a/src/utils/FormatUtils.cpp b/src/utils/FormatUtils.cpp new file mode 100644 index 0000000..e091505 --- /dev/null +++ b/src/utils/FormatUtils.cpp @@ -0,0 +1,8 @@ +#include "FormatUtils.hpp" +#include +#include + +std::string fourccToName(uint32_t drmFormat) { + auto fmt = drmGetFormatName(drmFormat); + return fmt ? fmt : "unknown"; +} diff --git a/tests/SimpleWindow.cpp b/tests/SimpleWindow.cpp index 3f8a3d0..a09b856 100644 --- a/tests/SimpleWindow.cpp +++ b/tests/SimpleWindow.cpp @@ -1,6 +1,12 @@ #include +#include +#include #include +using namespace Hyprutils::Signal; +using namespace Hyprutils::Memory; +#define SP CSharedPointer + static const char* aqLevelToString(Aquamarine::eBackendLogLevel level) { switch (level) { case Aquamarine::eBackendLogLevel::AQ_LOG_TRACE: return "TRACE"; @@ -18,19 +24,67 @@ void aqLog(Aquamarine::eBackendLogLevel level, std::string msg) { std::cout << "[AQ] [" << aqLevelToString(level) << "] " << msg << "\n"; } +CHyprSignalListener newOutputListener, outputFrameListener, outputStateListener, mouseMotionListener, keyboardKeyListener, newMouseListener, newKeyboardListener; +SP output; + +// +void onFrame() { + std::cout << "[Client] onFrame\n"; + + auto buf = output->swapchain->next(nullptr); + + output->state->buffer = buf; + output->commit(); +} + +void onState(const Aquamarine::IOutput::SStateEvent& event) { + std::cout << "[Client] onState with size " << std::format("{}", event.size) << "\n"; + + output->state->enabled = true; + output->state->customMode = {.pixelSize = event.size}; + output->state->drmFormat = DRM_FORMAT_XRGB8888; + + output->commit(); +} + int main(int argc, char** argv, char** envp) { Aquamarine::SBackendOptions options; options.logFunction = aqLog; std::vector implementations; - Aquamarine::SBackendImplementationOptions waylandOptions; - waylandOptions.backendType = Aquamarine::eBackendType::AQ_BACKEND_WAYLAND; + Aquamarine::SBackendImplementationOptions waylandOptions; + waylandOptions.backendType = Aquamarine::eBackendType::AQ_BACKEND_WAYLAND; waylandOptions.backendRequestMode = Aquamarine::eBackendRequestMode::AQ_BACKEND_REQUEST_IF_AVAILABLE; implementations.emplace_back(waylandOptions); auto aqBackend = Aquamarine::CBackend::create(implementations, options); - if (!aqBackend->start()) { + newOutputListener = aqBackend->events.newOutput.registerListener([](std::any data) { + output = std::any_cast>(data); + + std::cout << "[Client] Got a new output named " << output->name << "\n"; + + outputFrameListener = output->events.frame.registerListener([](std::any data) { onFrame(); }); + outputStateListener = output->events.state.registerListener([](std::any data) { onState(std::any_cast(data)); }); + }); + + newMouseListener = aqBackend->events.newPointer.registerListener([] (std::any pointer) { + auto p = std::any_cast>(pointer); + mouseMotionListener = p->events.warp.registerListener([] (std::any data) { + auto e = std::any_cast(data); + std::cout << "[Client] Mouse warped to " << std::format("{}", e.absolute) << "\n"; + }); + }); + + newKeyboardListener = aqBackend->events.newKeyboard.registerListener([] (std::any keeb) { + auto k = std::any_cast>(keeb); + keyboardKeyListener = k->events.key.registerListener([] (std::any data) { + auto e = std::any_cast(data); + std::cout << "[Client] Key " << std::format("{}", e.key) << " state: " << e.pressed << " \n"; + }); + }); + + if (!aqBackend || !aqBackend->start()) { std::cout << "Failed to start the aq backend\n"; return 1; }