Wayland: primitive but working backend

This implements enough for wayland to be a functioning backend.
This commit is contained in:
Vaxry 2024-06-19 22:40:23 +02:00
parent 01766c0956
commit 790ce7dfbf
15 changed files with 745 additions and 39 deletions

View file

@ -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)

View file

@ -0,0 +1,20 @@
#pragma once
#include <hyprutils/memory/SharedPtr.hpp>
#include "../buffer/Buffer.hpp"
#include <drm_fourcc.h>
namespace Aquamarine {
class CBackend;
struct SAllocatorBufferParams {
Hyprutils::Math::Vector2D size;
uint32_t format = DRM_FORMAT_INVALID;
};
class IAllocator {
public:
virtual Hyprutils::Memory::CSharedPointer<IBuffer> acquire(const SAllocatorBufferParams& params) = 0;
virtual Hyprutils::Memory::CSharedPointer<CBackend> getBackend() = 0;
};
};

View file

@ -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<CGBMAllocator> allocator_);
Hyprutils::Memory::CWeakPointer<CGBMAllocator> allocator;
// gbm stuff
gbm_bo* bo = nullptr;
SDMABUFAttrs attrs{.success = false};
friend class CGBMAllocator;
};
class CGBMAllocator : public IAllocator {
public:
static Hyprutils::Memory::CSharedPointer<CGBMAllocator> create(int drmfd_, Hyprutils::Memory::CWeakPointer<CBackend> backend_);
virtual Hyprutils::Memory::CSharedPointer<IBuffer> acquire(const SAllocatorBufferParams& params);
virtual Hyprutils::Memory::CSharedPointer<CBackend> getBackend();
//
Hyprutils::Memory::CWeakPointer<CGBMAllocator> self;
private:
CGBMAllocator(int fd_, Hyprutils::Memory::CWeakPointer<CBackend> backend_);
// a vector purely for tracking (debugging) the buffers and nothing more
std::vector<Hyprutils::Memory::CWeakPointer<CGBMBuffer>> buffers;
int fd = -1;
Hyprutils::Memory::CWeakPointer<CBackend> backend;
// gbm stuff
gbm_device* gbmDevice = nullptr;
std::string gbmDeviceBackendName = "";
std::string drmName = "";
friend class CGBMBuffer;
};
};

View file

@ -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<IAllocator> allocator_);
bool reconfigure(const SSwapchainOptions& options_);
bool contains(Hyprutils::Memory::CSharedPointer<IBuffer> buffer);
Hyprutils::Memory::CSharedPointer<IBuffer> next(int* age);
private:
bool fullReconfigure(const SSwapchainOptions& options_);
bool resize(size_t newSize);
//
SSwapchainOptions options;
Hyprutils::Memory::CSharedPointer<IAllocator> allocator;
std::vector<Hyprutils::Memory::CSharedPointer<IBuffer>> buffers;
int lastAcquired = 0;
};
};

View file

@ -6,6 +6,7 @@
#include <functional>
#include <mutex>
#include <condition_variable>
#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<IAllocator> allocator;
private:
CBackend();
@ -90,6 +94,7 @@ namespace Aquamarine {
std::vector<SBackendImplementationOptions> implementationOptions;
std::vector<Hyprutils::Memory::CSharedPointer<IBackendImplementation>> implementations;
SBackendOptions options;
Hyprutils::Memory::CWeakPointer<CBackend> self;
//
struct {

View file

@ -1,32 +1,64 @@
#pragma once
#include "./Backend.hpp"
#include "../allocator/Swapchain.hpp"
#include "../output/Output.hpp"
#include "../input/Input.hpp"
#include <hyprutils/memory/WeakPtr.hpp>
#include <wayland-client.h>
#include <wayland.hpp>
#include <xdg-shell.hpp>
#include <linux-dmabuf-v1.hpp>
#include <tuple>
namespace Aquamarine {
class CBackend;
class CWaylandBackend;
class CWaylandOutput;
class CWaylandBuffer {
public:
CWaylandBuffer(Hyprutils::Memory::CSharedPointer<IBuffer> buffer_, Hyprutils::Memory::CWeakPointer<CWaylandBackend> backend_);
~CWaylandBuffer();
bool good();
bool pendingRelease = false;
private:
struct {
Hyprutils::Memory::CSharedPointer<CWlBuffer> buffer;
} waylandState;
Hyprutils::Memory::CWeakPointer<IBuffer> buffer;
Hyprutils::Memory::CWeakPointer<CWaylandBackend> 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<CWaylandBackend> backend_);
std::string name;
Hyprutils::Memory::CWeakPointer<CWaylandBackend> backend;
std::string name;
Hyprutils::Memory::CWeakPointer<CWaylandBackend> backend;
Hyprutils::Memory::CSharedPointer<CWaylandBuffer> wlBufferFromBuffer(Hyprutils::Memory::CSharedPointer<IBuffer> buffer);
void sendFrameAndSetCallback();
struct {
std::vector<std::pair<Hyprutils::Memory::CWeakPointer<IBuffer>, Hyprutils::Memory::CSharedPointer<CWaylandBuffer>>> buffers;
} backendState;
struct {
Hyprutils::Memory::CSharedPointer<CWlSurface> surface;
Hyprutils::Memory::CSharedPointer<CXdgSurface> xdgSurface;
Hyprutils::Memory::CSharedPointer<CXdgToplevel> xdgToplevel;
Hyprutils::Memory::CSharedPointer<CWlCallback> 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<CWaylandBackend> 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<CWlRegistry> registry;
Hyprutils::Memory::CSharedPointer<CWlSeat> seat;
Hyprutils::Memory::CSharedPointer<CXdgWmBase> xdg;
Hyprutils::Memory::CSharedPointer<CWlCompositor> compositor;
Hyprutils::Memory::CSharedPointer<CWlRegistry> registry;
Hyprutils::Memory::CSharedPointer<CWlSeat> seat;
Hyprutils::Memory::CSharedPointer<CXdgWmBase> xdg;
Hyprutils::Memory::CSharedPointer<CWlCompositor> compositor;
Hyprutils::Memory::CSharedPointer<CZwpLinuxDmabufV1> dmabuf;
Hyprutils::Memory::CSharedPointer<CZwpLinuxDmabufFeedbackV1> 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;
};
};

View file

@ -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<uint8_t*, uint32_t, size_t> beginDataPtr(uint32_t flags);

View file

@ -4,6 +4,9 @@
#include <hyprutils/signal/Signal.hpp>
#include <hyprutils/memory/SharedPtr.hpp>
#include <hyprutils/math/Region.hpp>
#include <drm_fourcc.h>
#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<IOutput> parent);
Hyprutils::Math::CRegion damage;
bool enabled = false;
bool adaptiveSync = false;
@ -30,6 +31,9 @@ namespace Aquamarine {
std::vector<uint16_t> gammaLut;
Hyprutils::Math::Vector2D lastModeSize;
Hyprutils::Memory::CWeakPointer<SOutputMode> mode;
std::optional<SOutputMode> customMode;
uint32_t drmFormat = DRM_FORMAT_INVALID;
Hyprutils::Memory::CSharedPointer<IBuffer> 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<Hyprutils::Memory::CSharedPointer<SOutputMode>> modes;
Hyprutils::Memory::CSharedPointer<COutputState> state;
Hyprutils::Memory::CSharedPointer<COutputState> state = Hyprutils::Memory::makeShared<COutputState>();
Hyprutils::Memory::CSharedPointer<CSwapchain> swapchain;
//
struct SStateEvent {

147
src/allocator/GBM.cpp Normal file
View file

@ -0,0 +1,147 @@
#include <aquamarine/allocator/GBM.hpp>
#include <aquamarine/backend/Backend.hpp>
#include "FormatUtils.hpp"
#include <xf86drm.h>
#include <gbm.h>
using namespace Aquamarine;
using namespace Hyprutils::Memory;
#define SP CSharedPointer
Aquamarine::CGBMBuffer::CGBMBuffer(const SAllocatorBufferParams& params, Hyprutils::Memory::CWeakPointer<CGBMAllocator> 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<CGBMAllocator> Aquamarine::CGBMAllocator::create(int drmfd_, Hyprutils::Memory::CWeakPointer<CBackend> 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<CGBMAllocator>(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<CBackend> 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<IBuffer> 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<CGBMBuffer>(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<CBackend> Aquamarine::CGBMAllocator::getBackend() {
return backend.lock();
}

View file

@ -0,0 +1,93 @@
#include <aquamarine/allocator/Swapchain.hpp>
#include <aquamarine/backend/Backend.hpp>
#include "FormatUtils.hpp"
using namespace Aquamarine;
using namespace Hyprutils::Memory;
#define SP CSharedPointer
Aquamarine::CSwapchain::CSwapchain(SP<IAllocator> 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<IBuffer> 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<IBuffer> buffer) {
return std::find(buffers.begin(), buffers.end(), buffer) != buffers.end();
}

View file

@ -1,5 +1,6 @@
#include <aquamarine/backend/Backend.hpp>
#include <aquamarine/backend/Wayland.hpp>
#include <aquamarine/allocator/GBM.hpp>
#include <sys/poll.h>
#include <thread>
#include <chrono>
@ -36,6 +37,10 @@ Hyprutils::Memory::CSharedPointer<CBackend> 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;
}

View file

@ -1,6 +1,11 @@
#include <aquamarine/backend/Wayland.hpp>
#include <wayland.hpp>
#include <xdg-shell.hpp>
#include "Shared.hpp"
#include <string.h>
#include <xf86drm.h>
#include <gbm.h>
#include <fcntl.h>
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<CBackend> backend_) : backend(backend_) {
Aquamarine::CWaylandBackend::CWaylandBackend(SP<CBackend> 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<CWlCompositor>((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<CZwpLinuxDmabufV1>((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<CWaylandOutput>(new CWaylandOutput(szName, self)));
auto o = outputs.emplace_back(SP<CWaylandOutput>(new CWaylandOutput(szName, self)));
backend->events.newOutput.emit(SP<IOutput>(o));
}
int Aquamarine::CWaylandBackend::pollFD() {
@ -95,8 +115,7 @@ bool Aquamarine::CWaylandBackend::dispatchEvents() {
return true;
}
Aquamarine::CWaylandKeyboard::CWaylandKeyboard(Hyprutils::Memory::CSharedPointer<CWlKeyboard> keyboard_, Hyprutils::Memory::CWeakPointer<CWaylandBackend> backend_) :
keyboard(keyboard_), backend(backend_) {
Aquamarine::CWaylandKeyboard::CWaylandKeyboard(SP<CWlKeyboard> keyboard_, Hyprutils::Memory::CWeakPointer<CWaylandBackend> 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<CWlPointer> pointer_, Hyprutils::Memory::CWeakPointer<CWaylandBackend> backend_) :
pointer(pointer_), backend(backend_) {
Aquamarine::CWaylandPointer::CWaylandPointer(SP<CWlPointer> pointer_, Hyprutils::Memory::CWeakPointer<CWaylandBackend> 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<CWaylandKeyboard>(SP<CWlKeyboard>(waylandState.seat->sendGetKeyboard()), self));
else if (!HAS_KEYBOARD && !keyboards.empty())
if (HAS_KEYBOARD && keyboards.empty()) {
auto k = keyboards.emplace_back(makeShared<CWaylandKeyboard>(makeShared<CWlKeyboard>(waylandState.seat->sendGetKeyboard()), self));
backend->events.newKeyboard.emit(SP<IKeyboard>(k));
} else if (!HAS_KEYBOARD && !keyboards.empty())
keyboards.clear();
if (HAS_POINTER && pointers.empty())
keyboards.emplace_back(makeShared<CWaylandKeyboard>(SP<CWlKeyboard>(waylandState.seat->sendGetKeyboard()), self));
else if (!HAS_POINTER && !keyboards.empty())
keyboards.clear();
if (HAS_POINTER && pointers.empty()) {
auto p = pointers.emplace_back(makeShared<CWaylandPointer>(makeShared<CWlPointer>(waylandState.seat->sendGetPointer()), self));
backend->events.newPointer.emit(SP<IPointer>(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<CZwpLinuxDmabufFeedbackV1>(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<CWaylandBackend> backend_) : name(name_), backend(backend_) {
errno = 0;
waylandState.surface = SP<CWlSurface>(backend->waylandState.compositor->sendCreateSurface());
waylandState.surface = makeShared<CWlSurface>(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<CXdgSurface>(backend->waylandState.xdg->sendGetXdgSurface(waylandState.surface->resource()));
waylandState.xdgSurface = makeShared<CXdgSurface>(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<CXdgToplevel>(waylandState.xdgSurface->sendGetToplevel());
waylandState.xdgToplevel = makeShared<CXdgToplevel>(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<CSwapchain>(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<CWlRegion>(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() {
;
}
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<CWaylandBuffer> Aquamarine::CWaylandOutput::wlBufferFromBuffer(SP<IBuffer> 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<CWaylandBuffer>(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<CWlCallback>(waylandState.surface->sendFrame());
waylandState.frameCallback->setDone([this](CWlCallback* r, uint32_t ms) {
events.frame.emit();
waylandState.frameCallback.reset();
});
}
Aquamarine::CWaylandBuffer::CWaylandBuffer(SP<IBuffer> buffer_, Hyprutils::Memory::CWeakPointer<CWaylandBackend> backend_) : buffer(buffer_), backend(backend_) {
auto params = makeShared<CZwpLinuxBufferParamsV1>(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<CWlBuffer>(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();
}

View file

@ -0,0 +1,6 @@
#pragma once
#include <string>
#include <cstdint>
std::string fourccToName(uint32_t drmFormat);

View file

@ -0,0 +1,8 @@
#include "FormatUtils.hpp"
#include <drm_fourcc.h>
#include <xf86drm.h>
std::string fourccToName(uint32_t drmFormat) {
auto fmt = drmGetFormatName(drmFormat);
return fmt ? fmt : "unknown";
}

View file

@ -1,6 +1,12 @@
#include <aquamarine/backend/Backend.hpp>
#include <aquamarine/output/Output.hpp>
#include <aquamarine/input/Input.hpp>
#include <iostream>
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<Aquamarine::IOutput> 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<Aquamarine::SBackendImplementationOptions> 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<SP<Aquamarine::IOutput>>(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<Aquamarine::IOutput::SStateEvent>(data)); });
});
newMouseListener = aqBackend->events.newPointer.registerListener([] (std::any pointer) {
auto p = std::any_cast<SP<Aquamarine::IPointer>>(pointer);
mouseMotionListener = p->events.warp.registerListener([] (std::any data) {
auto e = std::any_cast<Aquamarine::IPointer::SWarpEvent>(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<SP<Aquamarine::IKeyboard>>(keeb);
keyboardKeyListener = k->events.key.registerListener([] (std::any data) {
auto e = std::any_cast<Aquamarine::IKeyboard::SKeyEvent>(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;
}