DRM: Init DRM/Libinput session code

Adds the bare basic of DRM code, which only initializes the environment atm. We cannot render yet.
This commit is contained in:
Vaxry 2024-06-23 19:40:40 +02:00
parent edbec62fbd
commit f888bfb6e4
10 changed files with 1529 additions and 2 deletions

View file

@ -16,7 +16,7 @@ set(INCLUDE ${CMAKE_INSTALL_FULL_INCLUDEDIR})
set(LIBDIR ${CMAKE_INSTALL_FULL_LIBDIR}) set(LIBDIR ${CMAKE_INSTALL_FULL_LIBDIR})
find_package(PkgConfig REQUIRED) find_package(PkgConfig REQUIRED)
pkg_check_modules(deps REQUIRED IMPORTED_TARGET wayland-client wayland-protocols hyprutils>=0.1.2 pixman-1 wayland-client libdrm gbm) pkg_check_modules(deps REQUIRED IMPORTED_TARGET libseat libinput wayland-client wayland-protocols hyprutils>=0.1.2 pixman-1 wayland-client libdrm gbm libudev)
configure_file(aquamarine.pc.in aquamarine.pc @ONLY) configure_file(aquamarine.pc.in aquamarine.pc @ONLY)
@ -83,7 +83,7 @@ protocolNew("stable/linux-dmabuf" "linux-dmabuf-v1" false)
add_custom_target(tests) add_custom_target(tests)
add_executable(simpleWindow "tests/SimpleWindow.cpp") add_executable(simpleWindow "tests/SimpleWindow.cpp")
target_link_libraries(simpleWindow PRIVATE aquamarine PkgConfig::deps) target_link_libraries(simpleWindow PRIVATE PkgConfig::deps aquamarine)
add_test(NAME "simpleWindow" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND simpleWindow "simpleWindow") add_test(NAME "simpleWindow" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests COMMAND simpleWindow "simpleWindow")
add_dependencies(tests simpleWindow) add_dependencies(tests simpleWindow)

View file

@ -8,6 +8,7 @@
#include <condition_variable> #include <condition_variable>
#include "../allocator/Allocator.hpp" #include "../allocator/Allocator.hpp"
#include "Misc.hpp" #include "Misc.hpp"
#include "Session.hpp"
namespace Aquamarine { namespace Aquamarine {
enum eBackendType { enum eBackendType {
@ -108,6 +109,7 @@ namespace Aquamarine {
Hyprutils::Memory::CSharedPointer<IAllocator> allocator; Hyprutils::Memory::CSharedPointer<IAllocator> allocator;
bool ready = false; bool ready = false;
Hyprutils::Memory::CSharedPointer<CSession> session;
private: private:
CBackend(); CBackend();
@ -118,6 +120,7 @@ namespace Aquamarine {
std::vector<Hyprutils::Memory::CSharedPointer<IBackendImplementation>> implementations; std::vector<Hyprutils::Memory::CSharedPointer<IBackendImplementation>> implementations;
SBackendOptions options; SBackendOptions options;
Hyprutils::Memory::CWeakPointer<CBackend> self; Hyprutils::Memory::CWeakPointer<CBackend> self;
std::vector<int> sessionFDs;
// //
struct { struct {

View file

@ -0,0 +1,214 @@
#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 <xf86drmMode.h>
namespace Aquamarine {
class CDRMBackend;
struct SDRMConnector;
struct SDRMFB {
uint32_t id = 0;
Hyprutils::Memory::CSharedPointer<IBuffer> buffer;
Hyprutils::Memory::CWeakPointer<CDRMBackend> backend;
};
struct SDRMLayer {
Hyprutils::Memory::CSharedPointer<SDRMFB> current /* displayed */, queued /* submitted */, pending /* to be submitted */;
Hyprutils::Memory::CWeakPointer<CDRMBackend> backend;
};
struct SDRMPlane {
bool init(drmModePlane* plane);
uint64_t type = 0;
uint32_t id = 0;
uint32_t initialID = 0;
Hyprutils::Memory::CSharedPointer<SDRMFB> current /* displayed */, queued /* submitted */;
Hyprutils::Memory::CWeakPointer<CDRMBackend> backend;
Hyprutils::Memory::CWeakPointer<SDRMPlane> self;
std::vector<SDRMFormat> formats;
union UDRMPlaneProps {
struct {
uint32_t type;
uint32_t rotation; // Not guaranteed to exist
uint32_t in_formats; // Not guaranteed to exist
// atomic-modesetting only
uint32_t src_x;
uint32_t src_y;
uint32_t src_w;
uint32_t src_h;
uint32_t crtc_x;
uint32_t crtc_y;
uint32_t crtc_w;
uint32_t crtc_h;
uint32_t fb_id;
uint32_t crtc_id;
uint32_t fb_damage_clips;
uint32_t hotspot_x;
uint32_t hotspot_y;
};
uint32_t props[16] = {0};
};
UDRMPlaneProps props;
};
struct SDRMCRTC {
uint32_t id = 0;
std::vector<SDRMLayer> layers;
int32_t refresh = 0;
struct {
int gammaSize = 0;
} legacy;
Hyprutils::Memory::CSharedPointer<SDRMPlane> primary;
Hyprutils::Memory::CSharedPointer<SDRMPlane> cursor;
Hyprutils::Memory::CWeakPointer<CDRMBackend> backend;
union UDRMCRTCProps {
struct {
// None of these are guaranteed to exist
uint32_t vrr_enabled;
uint32_t gamma_lut;
uint32_t gamma_lut_size;
// atomic-modesetting only
uint32_t active;
uint32_t mode_id;
};
uint32_t props[6] = {0};
};
UDRMCRTCProps props;
};
class CDRMOutput : public IOutput {
public:
virtual ~CDRMOutput();
virtual bool commit();
virtual bool test();
virtual Hyprutils::Memory::CSharedPointer<IBackendImplementation> getBackend();
virtual bool setCursor(Hyprutils::Memory::CSharedPointer<IBuffer> buffer, const Hyprutils::Math::Vector2D& hotspot);
virtual void moveCursor(const Hyprutils::Math::Vector2D& coord);
virtual void scheduleFrame();
virtual Hyprutils::Math::Vector2D maxCursorSize();
Hyprutils::Memory::CWeakPointer<CDRMOutput> self;
private:
CDRMOutput(const std::string& name_, Hyprutils::Memory::CWeakPointer<CDRMBackend> backend_, Hyprutils::Memory::CSharedPointer<SDRMConnector> connector_);
Hyprutils::Memory::CWeakPointer<CDRMBackend> backend;
Hyprutils::Memory::CSharedPointer<SDRMConnector> connector;
friend struct SDRMConnector;
};
struct SDRMConnector {
~SDRMConnector();
bool init(drmModeConnector* connector);
void connect(drmModeConnector* connector);
void disconnect();
Hyprutils::Memory::CSharedPointer<SDRMCRTC> getCurrentCRTC(const drmModeConnector* connector);
drmModeModeInfo* getCurrentMode();
void parseEDID(std::vector<uint8_t> data);
Hyprutils::Memory::CSharedPointer<CDRMOutput> output;
Hyprutils::Memory::CWeakPointer<CDRMBackend> backend;
Hyprutils::Memory::CWeakPointer<SDRMConnector> self;
std::string szName;
drmModeConnection status = DRM_MODE_DISCONNECTED;
uint32_t id = 0;
std::array<uint64_t, 2> maxBpcBounds = {0, 0};
Hyprutils::Memory::CSharedPointer<SDRMCRTC> crtc;
int32_t refresh = 0;
uint32_t possibleCrtcs = 0;
std::string make, serial, model;
bool cursorEnabled = false;
Hyprutils::Math::Vector2D cursorPos, cursorSize, cursorHotspot;
Hyprutils::Memory::CSharedPointer<SDRMFB> pendingCursorFB;
union UDRMConnectorProps {
struct {
uint32_t edid;
uint32_t dpms;
uint32_t link_status; // not guaranteed to exist
uint32_t path;
uint32_t vrr_capable; // not guaranteed to exist
uint32_t subconnector; // not guaranteed to exist
uint32_t non_desktop;
uint32_t panel_orientation; // not guaranteed to exist
uint32_t content_type; // not guaranteed to exist
uint32_t max_bpc; // not guaranteed to exist
// atomic-modesetting only
uint32_t crtc_id;
};
uint32_t props[4] = {0};
};
UDRMConnectorProps props;
};
class CDRMBackend : public IBackendImplementation {
public:
virtual ~CDRMBackend();
virtual eBackendType type();
virtual bool start();
virtual int pollFD();
virtual int drmFD();
virtual bool dispatchEvents();
virtual uint32_t capabilities();
virtual bool setCursor(Hyprutils::Memory::CSharedPointer<IBuffer> buffer, const Hyprutils::Math::Vector2D& hotspot);
virtual void onReady();
virtual std::vector<SDRMFormat> getRenderFormats();
virtual std::vector<SDRMFormat> getCursorFormats();
Hyprutils::Memory::CWeakPointer<CDRMBackend> self;
private:
CDRMBackend(Hyprutils::Memory::CSharedPointer<CBackend> backend);
static Hyprutils::Memory::CSharedPointer<CDRMBackend> attempt(Hyprutils::Memory::CSharedPointer<CBackend> backend);
bool registerGPU(Hyprutils::Memory::CSharedPointer<CSessionDevice> gpu_, Hyprutils::Memory::CSharedPointer<CDRMBackend> primary_ = {});
bool checkFeatures();
bool initResources();
bool grabFormats();
void scanConnectors();
Hyprutils::Memory::CSharedPointer<CSessionDevice> gpu;
Hyprutils::Memory::CWeakPointer<CDRMBackend> primary;
Hyprutils::Memory::CWeakPointer<CBackend> backend;
std::vector<Hyprutils::Memory::CSharedPointer<SDRMCRTC>> crtcs;
std::vector<Hyprutils::Memory::CSharedPointer<SDRMPlane>> planes;
std::vector<Hyprutils::Memory::CSharedPointer<SDRMConnector>> connectors;
std::vector<SDRMFormat> formats;
struct {
Hyprutils::Math::Vector2D cursorSize;
bool supportsAsyncCommit = false;
bool supportsAddFb2Modifiers = false;
} drmProps;
friend class CBackend;
friend struct SDRMFB;
friend struct SDRMConnector;
friend struct SDRMCRTC;
friend struct SDRMPlane;
friend struct CDRMOutput;
};
};

View file

@ -0,0 +1,11 @@
#pragma once
#include <cstdint>
#include <vector>
namespace Aquamarine {
struct SDRMFormat {
uint32_t drmFormat = 0; /* DRM_FORMAT_INVALID */
std::vector<uint64_t> modifiers;
};
};

View file

@ -0,0 +1,90 @@
#pragma once
#include <sys/types.h>
#include <hyprutils/signal/Signal.hpp>
#include <hyprutils/memory/SharedPtr.hpp>
#include <vector>
struct udev;
struct udev_monitor;
struct udev_device;
struct libseat;
namespace Aquamarine {
class CBackend;
class CSession;
class CSessionDevice {
public:
CSessionDevice(Hyprutils::Memory::CSharedPointer<CSession> session_, const std::string& path_);
~CSessionDevice();
static Hyprutils::Memory::CSharedPointer<CSessionDevice> openIfKMS(Hyprutils::Memory::CSharedPointer<CSession> session_, const std::string& path_);
bool supportsKMS();
int fd = -1;
int deviceID = -1;
dev_t dev;
std::string path;
enum eChangeEventType {
AQ_SESSION_EVENT_CHANGE_HOTPLUG = 0,
AQ_SESSION_EVENT_CHANGE_LEASE,
};
struct SChangeEvent {
eChangeEventType type = AQ_SESSION_EVENT_CHANGE_HOTPLUG;
struct {
uint32_t connectorID = 0, propID = 0;
} hotplug;
};
struct {
Hyprutils::Signal::CSignal change;
Hyprutils::Signal::CSignal remove;
} events;
private:
Hyprutils::Memory::CWeakPointer<CSession> session;
};
class CSession {
public:
~CSession();
static Hyprutils::Memory::CSharedPointer<CSession> attempt(Hyprutils::Memory::CSharedPointer<CBackend> backend_);
bool active = true; // whether the current vt is ours
uint32_t vt = 0; // 0 means unsupported
std::string seatName;
std::vector<Hyprutils::Memory::CSharedPointer<CSessionDevice>> sessionDevices;
udev* udevHandle = nullptr;
udev_monitor* udevMonitor = nullptr;
libseat* libseatHandle = nullptr;
std::vector<int> pollFDs();
void dispatchPendingEventsAsync();
struct SAddDrmCardEvent {
std::string path;
};
struct {
Hyprutils::Signal::CSignal changeActive;
Hyprutils::Signal::CSignal addDrmCard;
Hyprutils::Signal::CSignal destroy;
} events;
private:
Hyprutils::Memory::CWeakPointer<CBackend> backend;
void dispatchUdevEvents();
friend class CSessionDevice;
};
};

View file

@ -1,5 +1,6 @@
#include <aquamarine/backend/Backend.hpp> #include <aquamarine/backend/Backend.hpp>
#include <aquamarine/backend/Wayland.hpp> #include <aquamarine/backend/Wayland.hpp>
#include <aquamarine/backend/DRM.hpp>
#include <aquamarine/allocator/GBM.hpp> #include <aquamarine/allocator/GBM.hpp>
#include <sys/poll.h> #include <sys/poll.h>
#include <thread> #include <thread>
@ -49,6 +50,14 @@ Hyprutils::Memory::CSharedPointer<CBackend> Aquamarine::CBackend::create(const s
auto ref = SP<CWaylandBackend>(new CWaylandBackend(backend)); auto ref = SP<CWaylandBackend>(new CWaylandBackend(backend));
backend->implementations.emplace_back(ref); backend->implementations.emplace_back(ref);
ref->self = ref; ref->self = ref;
} else if (b.backendType == AQ_BACKEND_DRM) {
auto ref = CDRMBackend::attempt(backend);
if (!ref) {
backend->log(AQ_LOG_ERROR, "DRM Backend failed");
continue;
}
backend->implementations.emplace_back(ref);
ref->self = ref;
} else { } else {
backend->log(AQ_LOG_ERROR, std::format("Unknown backend id: {}", (int)b.backendType)); backend->log(AQ_LOG_ERROR, std::format("Unknown backend id: {}", (int)b.backendType));
continue; continue;
@ -108,6 +117,8 @@ bool Aquamarine::CBackend::start() {
b->onReady(); b->onReady();
} }
sessionFDs = session->pollFDs();
return true; return true;
} }
@ -186,6 +197,10 @@ std::vector<int> Aquamarine::CBackend::getPollFDs() {
result.push_back(fd); result.push_back(fd);
} }
for (auto& sfd : sessionFDs) {
result.push_back(sfd);
}
return result; return result;
} }
@ -204,6 +219,9 @@ void Aquamarine::CBackend::dispatchEventsAsync() {
for (auto& i : implementations) { for (auto& i : implementations) {
i->dispatchEvents(); i->dispatchEvents();
} }
if (session)
session->dispatchPendingEventsAsync();
} }
bool Aquamarine::CBackend::hasSession() { bool Aquamarine::CBackend::hasSession() {

235
src/backend/Session.cpp Normal file
View file

@ -0,0 +1,235 @@
#include <aquamarine/backend/Backend.hpp>
extern "C" {
#include <libseat.h>
#include <libudev.h>
#include <cstring>
#include <xf86drm.h>
#include <sys/stat.h>
#include <xf86drmMode.h>
}
using namespace Aquamarine;
using namespace Hyprutils::Memory;
#define SP CSharedPointer
// we can't really do better with libseat logs
// because they don't allow us to pass "data" or anything...
// Nobody should create multiple backends anyways really
Hyprutils::Memory::CSharedPointer<CBackend> backendInUse;
static Aquamarine::eBackendLogLevel logLevelFromLibseat(enum libseat_log_level level) {
switch (level) {
case LIBSEAT_LOG_LEVEL_ERROR: return AQ_LOG_ERROR;
case LIBSEAT_LOG_LEVEL_SILENT: return AQ_LOG_TRACE;
default: break;
}
return AQ_LOG_DEBUG;
}
static void libseatLog(libseat_log_level level, const char* fmt, va_list args) {
if (!backendInUse)
return;
static char string[1024];
vsnprintf(string, sizeof(string), fmt, args);
backendInUse->log(logLevelFromLibseat(level), std::format("[libseat] {}", string));
}
static void libseatEnableSeat(struct libseat* seat, void* data) {
auto PSESSION = (Aquamarine::CSession*)data;
PSESSION->active = true;
PSESSION->events.changeActive.emit();
}
static void libseatDisableSeat(struct libseat* seat, void* data) {
auto PSESSION = (Aquamarine::CSession*)data;
PSESSION->active = false;
PSESSION->events.changeActive.emit();
libseat_disable_seat(PSESSION->libseatHandle);
}
static const libseat_seat_listener libseatListener = {
.enable_seat = libseatEnableSeat,
.disable_seat = libseatDisableSeat,
};
Aquamarine::CSessionDevice::CSessionDevice(Hyprutils::Memory::CSharedPointer<CSession> session_, const std::string& path_) : session(session_), path(path_) {
deviceID = libseat_open_device(session->libseatHandle, path.c_str(), &fd);
if (deviceID < 0) {
session->backend->log(AQ_LOG_ERROR, std::format("libseat: Couldn't open device at {}", path_));
return;
}
struct stat stat_;
if (fstat(fd, &stat_) < 0) {
session->backend->log(AQ_LOG_ERROR, std::format("libseat: Couldn't stat device at {}", path_));
deviceID = -1;
return;
}
dev = stat_.st_rdev;
}
Aquamarine::CSessionDevice::~CSessionDevice() {
if (fd < 0)
return;
libseat_close_device(session->libseatHandle, fd);
close(fd);
}
bool Aquamarine::CSessionDevice::supportsKMS() {
if (deviceID < 0)
return false;
bool kms = drmIsKMS(fd);
if (kms)
session->backend->log(AQ_LOG_DEBUG, std::format("libseat: Device {} supports kms", path));
else
session->backend->log(AQ_LOG_DEBUG, std::format("libseat: Device {} does not support kms", path));
return kms;
}
SP<CSessionDevice> Aquamarine::CSessionDevice::openIfKMS(SP<CSession> session_, const std::string& path_) {
auto dev = makeShared<CSessionDevice>(session_, path_);
if (!dev->supportsKMS())
return nullptr;
return dev;
}
SP<CSession> Aquamarine::CSession::attempt(Hyprutils::Memory::CSharedPointer<CBackend> backend_) {
if (!backend_)
return nullptr;
auto session = makeShared<CSession>();
session->backend = backend_;
backendInUse = backend_;
// ------------ Libseat
libseat_set_log_handler(libseatLog);
libseat_set_log_level(LIBSEAT_LOG_LEVEL_INFO);
session->libseatHandle = libseat_open_seat(&libseatListener, session.get());
if (!session->libseatHandle) {
session->backend->log(AQ_LOG_ERROR, "libseat: failed to open a seat");
return nullptr;
}
auto seatName = libseat_seat_name(session->libseatHandle);
if (!seatName) {
session->backend->log(AQ_LOG_ERROR, "libseat: failed to get seat name");
return nullptr;
}
session->seatName = seatName;
// dispatch any already pending events
session->dispatchPendingEventsAsync();
// ----------- Udev
session->udevHandle = udev_new();
if (!session->udevHandle) {
session->backend->log(AQ_LOG_ERROR, "udev: failed to create a new context");
return nullptr;
}
session->udevMonitor = udev_monitor_new_from_netlink(session->udevHandle, "udev");
if (!session->udevMonitor) {
session->backend->log(AQ_LOG_ERROR, "udev: failed to create a new udevMonitor");
return nullptr;
}
udev_monitor_filter_add_match_subsystem_devtype(session->udevMonitor, "drm", nullptr);
udev_monitor_enable_receiving(session->udevMonitor);
return session;
}
Aquamarine::CSession::~CSession() {
sessionDevices.clear();
if (libseatHandle)
libseat_close_seat(libseatHandle);
if (udevMonitor)
udev_monitor_unref(udevMonitor);
if (udevHandle)
udev_unref(udevHandle);
libseatHandle = nullptr;
udevMonitor = nullptr;
udevHandle = nullptr;
}
static bool isDRMCard(const char* sysname) {
const char prefix[] = DRM_PRIMARY_MINOR_NAME;
if (strncmp(sysname, prefix, strlen(prefix)) != 0)
return false;
for (size_t i = strlen(prefix); sysname[i] != '\0'; i++) {
if (sysname[i] < '0' || sysname[i] > '9')
return false;
}
return true;
}
void Aquamarine::CSession::dispatchUdevEvents() {
auto device = udev_monitor_receive_device(udevMonitor);
if (!device)
return;
auto sysname = udev_device_get_sysname(device);
auto devnode = udev_device_get_devnode(device);
auto action = udev_device_get_action(device);
backend->log(AQ_LOG_DEBUG, std::format("udev: new udev {} event for {}", action ? action : "unknown", sysname ? sysname : "unknown"));
if (!isDRMCard(sysname) || !action || !devnode) {
udev_device_unref(device);
return;
}
if (action == std::string{"add"})
events.addDrmCard.emit(SAddDrmCardEvent{.path = devnode});
else if (action == std::string{"change"} || action == std::string{"remove"}) {
dev_t deviceNum = udev_device_get_devnum(device);
for (auto& d : sessionDevices) {
if (d->dev != deviceNum)
continue;
if (action == std::string{"change"}) {
backend->log(AQ_LOG_DEBUG, std::format("udev: DRM device {} changed", sysname ? sysname : "unknown"));
backend->log(AQ_LOG_ERROR, "udev: FIXME: change event is a STUB");
} else if (action == std::string{"remove"}) {
backend->log(AQ_LOG_DEBUG, std::format("udev: DRM device {} removed", sysname ? sysname : "unknown"));
backend->log(AQ_LOG_ERROR, "udev: FIXME: remove event is a STUB");
}
}
}
udev_device_unref(device);
return;
}
void Aquamarine::CSession::dispatchPendingEventsAsync() {
if (libseat_dispatch(libseatHandle, 0) == -1)
backend->log(AQ_LOG_ERROR, "Couldn't dispatch libseat events");
dispatchUdevEvents();
}
std::vector<int> Aquamarine::CSession::pollFDs() {
if (!libseatHandle || !udevMonitor || !udevHandle)
return {};
return {libseat_get_fd(libseatHandle), udev_monitor_get_fd(udevMonitor)};
}

760
src/backend/drm/DRM.cpp Normal file
View file

@ -0,0 +1,760 @@
#include <aquamarine/backend/DRM.hpp>
#include <chrono>
#include <thread>
#include <deque>
#include <cstring>
extern "C" {
#include <libseat.h>
#include <libudev.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
}
#include "Props.hpp"
using namespace Aquamarine;
using namespace Hyprutils::Memory;
using namespace Hyprutils::Math;
#define SP CSharedPointer
Aquamarine::CDRMBackend::CDRMBackend(SP<CBackend> backend_) : backend(backend_) {
;
}
static udev_enumerate* enumDRMCards(udev* udev) {
auto enumerate = udev_enumerate_new(udev);
if (!enumerate)
return nullptr;
udev_enumerate_add_match_subsystem(enumerate, "drm");
udev_enumerate_add_match_sysname(enumerate, DRM_PRIMARY_MINOR_NAME "[0-9]*");
if (udev_enumerate_scan_devices(enumerate)) {
udev_enumerate_unref(enumerate);
return nullptr;
}
return enumerate;
}
static std::vector<SP<CSessionDevice>> scanGPUs(SP<CBackend> backend) {
// FIXME: This provides no explicit way to set a preferred gpu
auto enumerate = enumDRMCards(backend->session->udevHandle);
if (!enumerate) {
backend->log(AQ_LOG_ERROR, "drm: couldn't enumerate gpus with udev");
return {};
}
if (!udev_enumerate_get_list_entry(enumerate)) {
// TODO: wait for them.
backend->log(AQ_LOG_ERROR, "drm: No gpus in scanGPUs.");
return {};
}
udev_list_entry* entry = nullptr;
size_t i = 0;
std::deque<SP<CSessionDevice>> devices;
udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(enumerate)) {
auto path = udev_list_entry_get_name(entry);
auto device = udev_device_new_from_syspath(backend->session->udevHandle, path);
if (!device) {
backend->log(AQ_LOG_WARNING, std::format("drm: Skipping device {}", path ? path : "unknown"));
continue;
}
backend->log(AQ_LOG_DEBUG, std::format("drm: Enumerated device {}", path ? path : "unknown"));
auto seat = udev_device_get_property_value(device, "ID_SEAT");
if (!seat)
seat = "seat0";
if (!backend->session->seatName.empty() && backend->session->seatName != seat) {
backend->log(AQ_LOG_WARNING, std::format("drm: Skipping device {} because seat {} doesn't match our {}", path ? path : "unknown", seat, backend->session->seatName));
udev_device_unref(device);
continue;
}
auto pciDevice = udev_device_get_parent_with_subsystem_devtype(device, "pci", nullptr);
bool isBootVGA = false;
if (pciDevice) {
auto id = udev_device_get_sysattr_value(pciDevice, "boot_vga");
isBootVGA = id && id == std::string{"1"};
}
if (!udev_device_get_devnode(device)) {
backend->log(AQ_LOG_ERROR, std::format("drm: Skipping device {}, no devnode", path ? path : "unknown"));
udev_device_unref(device);
continue;
}
auto sessionDevice = CSessionDevice::openIfKMS(backend->session, udev_device_get_devnode(device));
if (!sessionDevice) {
backend->log(AQ_LOG_ERROR, std::format("drm: Skipping device {}, not a KMS device", path ? path : "unknown"));
udev_device_unref(device);
continue;
}
udev_device_unref(device);
if (isBootVGA)
devices.push_front(sessionDevice);
else
devices.push_back(sessionDevice);
++i;
}
udev_enumerate_unref(enumerate);
std::vector<SP<CSessionDevice>> vecDevices;
for (auto& d : devices) {
vecDevices.push_back(d);
}
return vecDevices;
}
SP<CDRMBackend> Aquamarine::CDRMBackend::attempt(SP<CBackend> backend) {
if (!backend->session)
backend->session = CSession::attempt(backend);
if (!backend->session) {
backend->log(AQ_LOG_ERROR, "Failed to open a session");
return nullptr;
}
if (!backend->session->active) {
backend->log(AQ_LOG_DEBUG, "Session is not active, waiting for 5s");
auto started = std::chrono::system_clock::now();
while (!backend->session->active) {
std::this_thread::sleep_for(std::chrono::milliseconds(250));
backend->session->dispatchPendingEventsAsync();
if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - started).count() >= 5000) {
backend->log(AQ_LOG_DEBUG, "Session timeout reached");
break;
}
}
if (!backend->session->active) {
backend->log(AQ_LOG_DEBUG, "Session could not be activated in time");
return nullptr;
}
}
auto gpus = scanGPUs(backend);
if (gpus.empty()) {
backend->log(AQ_LOG_ERROR, "drm: Found no gpus to use, cannot continue");
return nullptr;
}
backend->log(AQ_LOG_DEBUG, std::format("drm: Found {} GPUs", gpus.size()));
// FIXME: this will ignore multi-gpu setups and only create one backend
auto drmBackend = SP<CDRMBackend>(new CDRMBackend(backend));
drmBackend->self = drmBackend;
if (!drmBackend->registerGPU(gpus.at(0))) {
backend->log(AQ_LOG_ERROR, std::format("drm: Failed to register gpu at fd {}", gpus[0]->fd));
return nullptr;
} else
backend->log(AQ_LOG_DEBUG, std::format("drm: Registered gpu at fd {}", gpus[0]->fd));
// TODO: consider listening for new devices
// But if you expect me to handle gpu hotswaps you are probably insane LOL
if (!drmBackend->checkFeatures()) {
backend->log(AQ_LOG_ERROR, "drm: Failed checking features");
return nullptr;
}
if (!drmBackend->initResources()) {
backend->log(AQ_LOG_ERROR, "drm: Failed initializing resources");
return nullptr;
}
backend->log(AQ_LOG_DEBUG, std::format("drm: Basic init pass for gpu {}", gpus[0]->path));
drmBackend->grabFormats();
drmBackend->scanConnectors();
return drmBackend;
}
Aquamarine::CDRMBackend::~CDRMBackend() {
;
}
bool Aquamarine::CDRMBackend::checkFeatures() {
uint64_t curW = 0, curH = 0;
if (drmGetCap(gpu->fd, DRM_CAP_CURSOR_WIDTH, &curW))
curW = 64;
if (drmGetCap(gpu->fd, DRM_CAP_CURSOR_HEIGHT, &curH))
curH = 64;
drmProps.cursorSize = Hyprutils::Math::Vector2D{(double)curW, (double)curH};
uint64_t cap = 0;
if (drmGetCap(gpu->fd, DRM_CAP_PRIME, &cap) || !(cap & DRM_PRIME_CAP_IMPORT)) {
backend->log(AQ_LOG_ERROR, std::format("drm: DRM_PRIME_CAP_IMPORT unsupported"));
return false;
}
if (drmGetCap(gpu->fd, DRM_CAP_CRTC_IN_VBLANK_EVENT, &cap) || !cap) {
backend->log(AQ_LOG_ERROR, std::format("drm: DRM_CAP_CRTC_IN_VBLANK_EVENT unsupported"));
return false;
}
if (drmGetCap(gpu->fd, DRM_CAP_TIMESTAMP_MONOTONIC, &cap) || !cap) {
backend->log(AQ_LOG_ERROR, std::format("drm: DRM_PRIME_CAP_IMPORT unsupported"));
return false;
}
if (drmSetClientCap(gpu->fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1)) {
backend->log(AQ_LOG_ERROR, std::format("drm: DRM_CLIENT_CAP_UNIVERSAL_PLANES unsupported"));
return false;
}
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;
// TODO: allow no-modifiers?
return true;
}
bool Aquamarine::CDRMBackend::initResources() {
auto resources = drmModeGetResources(gpu->fd);
if (!resources) {
backend->log(AQ_LOG_ERROR, std::format("drm: drmModeGetResources failed"));
return false;
}
backend->log(AQ_LOG_DEBUG, std::format("drm: found {} CRTCs", resources->count_crtcs));
for (size_t i = 0; i < resources->count_crtcs; ++i) {
auto CRTC = makeShared<SDRMCRTC>();
CRTC->id = resources->crtcs[i];
CRTC->backend = self;
auto drmCRTC = drmModeGetCrtc(gpu->fd, CRTC->id);
if (!drmCRTC) {
backend->log(AQ_LOG_ERROR, std::format("drm: drmModeGetCrtc for crtc {} failed", CRTC->id));
drmModeFreeResources(resources);
crtcs.clear();
return false;
}
CRTC->legacy.gammaSize = drmCRTC->gamma_size;
drmModeFreeCrtc(drmCRTC);
if (!getDRMCRTCProps(gpu->fd, CRTC->id, &CRTC->props)) {
backend->log(AQ_LOG_ERROR, std::format("drm: getDRMCRTCProps for crtc {} failed", CRTC->id));
drmModeFreeResources(resources);
crtcs.clear();
return false;
}
crtcs.emplace_back(CRTC);
}
if (crtcs.size() > 32) {
backend->log(AQ_LOG_CRITICAL, "drm: Cannot support more than 32 CRTCs");
return false;
}
// initialize planes
auto planeResources = drmModeGetPlaneResources(gpu->fd);
if (!planeResources) {
backend->log(AQ_LOG_ERROR, std::format("drm: drmModeGetPlaneResources failed"));
return false;
}
backend->log(AQ_LOG_DEBUG, std::format("drm: found {} planes", planeResources->count_planes));
for (uint32_t i = 0; i < planeResources->count_planes; ++i) {
auto id = planeResources->planes[i];
auto plane = drmModeGetPlane(gpu->fd, id);
if (!plane) {
backend->log(AQ_LOG_ERROR, std::format("drm: drmModeGetPlane for plane {} failed", id));
drmModeFreeResources(resources);
crtcs.clear();
planes.clear();
return false;
}
auto aqPlane = makeShared<SDRMPlane>();
aqPlane->backend = self;
aqPlane->self = aqPlane;
if (!aqPlane->init((drmModePlane*)plane)) {
backend->log(AQ_LOG_ERROR, std::format("drm: aqPlane->init for plane {} failed", id));
drmModeFreeResources(resources);
crtcs.clear();
planes.clear();
return false;
}
drmModeFreePlane(plane);
}
drmModeFreePlaneResources(planeResources);
drmModeFreeResources(resources);
return true;
}
bool Aquamarine::CDRMBackend::grabFormats() {
// FIXME: do this properly maybe?
return true;
}
bool Aquamarine::CDRMBackend::registerGPU(SP<CSessionDevice> gpu_, SP<CDRMBackend> primary_) {
gpu = gpu_;
primary = primary_;
auto drmName = drmGetDeviceNameFromFd2(gpu->fd);
auto drmVer = drmGetVersion(gpu->fd);
backend->log(AQ_LOG_DEBUG, std::format("drm: Starting backend for {}, with driver {}", drmName ? drmName : "unknown", drmVer->name ? drmVer->name : "unknown"));
drmFreeVersion(drmVer);
// FIXME: listen to change and remove events from session
return true;
}
eBackendType Aquamarine::CDRMBackend::type() {
return eBackendType::AQ_BACKEND_DRM;
}
void Aquamarine::CDRMBackend::scanConnectors() {
backend->log(AQ_LOG_DEBUG, std::format("drm: Scanning connectors for {}", gpu->path));
auto resources = drmModeGetResources(gpu->fd);
if (!resources) {
backend->log(AQ_LOG_ERROR, std::format("drm: Scanning connectors for {} failed", gpu->path));
return;
}
for (size_t i = 0; i < resources->count_connectors; ++i) {
uint32_t connectorID = resources->connectors[i];
SP<SDRMConnector> conn;
auto drmConn = drmModeGetConnector(gpu->fd, connectorID);
backend->log(AQ_LOG_DEBUG, std::format("drm: Scanning connector id {}", connectorID));
if (!drmConn) {
backend->log(AQ_LOG_ERROR, std::format("drm: Failed to get connector id {}", connectorID));
continue;
}
auto it = std::find_if(connectors.begin(), connectors.end(), [connectorID](const auto& e) { return e->id == connectorID; });
if (it == connectors.end()) {
backend->log(AQ_LOG_DEBUG, std::format("drm: Initializing connector id {}", connectorID));
conn = connectors.emplace_back(SP<SDRMConnector>(new SDRMConnector()));
conn->self = conn;
conn->backend = self;
if (!conn->init(drmConn)) {
backend->log(AQ_LOG_ERROR, std::format("drm: Connector id {} failed initializing", connectorID));
connectors.pop_back();
continue;
}
} else
conn = *it;
backend->log(AQ_LOG_DEBUG, std::format("drm: Connector {} connection state:", (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));
conn->connect(drmConn);
} else if (conn->status == DRM_MODE_CONNECTED && drmConn->connection == DRM_MODE_DISCONNECTED) {
backend->log(AQ_LOG_DEBUG, std::format("drm: Connector {} disconnected", conn->szName));
conn->disconnect();
}
drmModeFreeConnector(drmConn);
}
drmModeFreeResources(resources);
}
bool Aquamarine::CDRMBackend::start() {
scanConnectors();
return true;
}
int Aquamarine::CDRMBackend::pollFD() {
return gpu->fd;
}
int Aquamarine::CDRMBackend::drmFD() {
return gpu->fd;
}
static void handlePF(int fd, unsigned seq, unsigned tv_sec, unsigned tv_usec, unsigned crtc_id, void* data) {
// FIXME:
}
bool Aquamarine::CDRMBackend::dispatchEvents() {
drmEventContext event = {
.version = 3,
.page_flip_handler2 = ::handlePF,
};
if (drmHandleEvent(gpu->fd, &event) != 0)
backend->log(AQ_LOG_ERROR, std::format("drm: Failed to handle event on fd {}", gpu->fd));
return true;
}
uint32_t Aquamarine::CDRMBackend::capabilities() {
return eBackendCapabilities::AQ_BACKEND_CAPABILITY_POINTER;
}
bool Aquamarine::CDRMBackend::setCursor(SP<IBuffer> buffer, const Hyprutils::Math::Vector2D& hotspot) {
return false;
}
void Aquamarine::CDRMBackend::onReady() {
;
}
std::vector<SDRMFormat> Aquamarine::CDRMBackend::getRenderFormats() {
for (auto& p : planes) {
if (p->type != DRM_PLANE_TYPE_PRIMARY)
continue;
return p->formats;
}
return {};
}
std::vector<SDRMFormat> Aquamarine::CDRMBackend::getCursorFormats() {
for (auto& p : planes) {
if (p->type != DRM_PLANE_TYPE_CURSOR)
continue;
return p->formats;
}
return {};
}
bool Aquamarine::SDRMPlane::init(drmModePlane* plane) {
id = plane->plane_id;
if (!getDRMPlaneProps(backend->gpu->fd, id, &props))
return false;
if (!getDRMProp(backend->gpu->fd, id, props.type, &type))
return false;
initialID = id;
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}});
}
if (props.in_formats && backend->drmProps.supportsAddFb2Modifiers) {
uint64_t blobID = 0;
if (!getDRMProp(backend->gpu->fd, id, props.in_formats, &blobID))
return false;
auto blob = drmModeGetPropertyBlob(backend->gpu->fd, blobID);
if (!blob)
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; });
if (it == formats.end())
formats.emplace_back(SDRMFormat{.drmFormat = iter.fmt, .modifiers = {iter.mod}});
else
it->modifiers.emplace_back(iter.mod);
}
drmModeFreePropertyBlob(blob);
}
for (size_t i = 0; i < backend->crtcs.size(); ++i) {
uint32_t crtcBit = (1 << i);
if (!(plane->possible_crtcs & crtcBit))
continue;
auto CRTC = backend->crtcs.at(i);
if (type == DRM_PLANE_TYPE_PRIMARY && !CRTC->primary) {
CRTC->primary = self.lock();
break;
}
if (type == DRM_PLANE_TYPE_CURSOR && !CRTC->cursor) {
CRTC->cursor = self.lock();
break;
}
}
return true;
}
SP<SDRMCRTC> Aquamarine::SDRMConnector::getCurrentCRTC(const drmModeConnector* connector) {
uint32_t crtcID = 0;
if (props.crtc_id) {
uint64_t value = 0;
if (!getDRMProp(backend->gpu->fd, id, props.crtc_id, &value)) {
backend->backend->log(AQ_LOG_ERROR, "drm: Failed to get CRTC_ID");
return nullptr;
}
crtcID = static_cast<uint32_t>(value);
} else if (connector->encoder_id) {
auto encoder = drmModeGetEncoder(backend->gpu->fd, connector->encoder_id);
if (!encoder) {
backend->backend->log(AQ_LOG_ERROR, "drm: drmModeGetEncoder failed");
return nullptr;
}
crtcID = encoder->crtc_id;
drmModeFreeEncoder(encoder);
} else
return nullptr;
auto it = std::find_if(backend->crtcs.begin(), backend->crtcs.end(), [crtcID](const auto& e) { return e->id == crtcID; });
if (it == backend->crtcs.end()) {
backend->backend->log(AQ_LOG_ERROR, std::format("drm: Failed to find a CRTC with ID {}", crtcID));
return nullptr;
}
return *it;
}
bool Aquamarine::SDRMConnector::init(drmModeConnector* connector) {
id = connector->connector_id;
if (!getDRMConnectorProps(backend->gpu->fd, id, &props))
return false;
auto name = drmModeGetConnectorTypeName(connector->connector_type);
if (!name)
name = "ERROR";
szName = std::format("{}-{}", name, connector->connector_type_id);
possibleCrtcs = drmModeConnectorGetPossibleCrtcs(backend->gpu->fd, connector);
if (!possibleCrtcs)
backend->backend->log(AQ_LOG_ERROR, "drm: No CRTCs possible");
crtc = getCurrentCRTC(connector);
return true;
}
Aquamarine::SDRMConnector::~SDRMConnector() {
disconnect();
}
static int32_t calculateRefresh(const drmModeModeInfo& mode) {
int32_t refresh = (mode.clock * 1000000LL / mode.htotal + mode.vtotal / 2) / mode.vtotal;
if (mode.flags & DRM_MODE_FLAG_INTERLACE)
refresh *= 2;
if (mode.flags & DRM_MODE_FLAG_DBLSCAN)
refresh /= 2;
if (mode.vscan > 1)
refresh /= mode.vscan;
return refresh;
}
drmModeModeInfo* Aquamarine::SDRMConnector::getCurrentMode() {
if (!crtc)
return nullptr;
if (crtc->props.mode_id) {
size_t size = 0;
return (drmModeModeInfo*)getDRMPropBlob(backend->gpu->fd, crtc->id, crtc->props.mode_id, &size);
;
}
auto drmCrtc = drmModeGetCrtc(backend->gpu->fd, crtc->id);
if (!drmCrtc)
return nullptr;
if (!drmCrtc->mode_valid) {
drmModeFreeCrtc(drmCrtc);
return nullptr;
}
drmModeModeInfo* modeInfo = (drmModeModeInfo*)malloc(sizeof(drmModeModeInfo));
if (!modeInfo) {
drmModeFreeCrtc(drmCrtc);
return nullptr;
}
*modeInfo = drmCrtc->mode;
drmModeFreeCrtc(drmCrtc);
return modeInfo;
}
void Aquamarine::SDRMConnector::parseEDID(std::vector<uint8_t> data) {
// TODO: libdisplay-info prolly
}
void Aquamarine::SDRMConnector::connect(drmModeConnector* connector) {
if (output) {
backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Not connecting connector {} because it's already connected", szName));
return;
}
backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Connecting connector {}, CRTC ID {}", szName, crtc ? crtc->id : -1));
output = SP<CDRMOutput>(new CDRMOutput(szName, backend, self.lock()));
output->self = output;
output->connector = self.lock();
backend->backend->log(AQ_LOG_DEBUG, "drm: Dumping detected modes:");
auto currentModeInfo = getCurrentMode();
for (int i = 0; i < connector->count_modes; ++i) {
auto& drmMode = connector->modes[i];
if (drmMode.flags & DRM_MODE_FLAG_INTERLACE) {
backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Skipping mode {} because it's interlaced", i));
continue;
}
auto aqMode = makeShared<SOutputMode>();
aqMode->pixelSize = {drmMode.hdisplay, drmMode.vdisplay};
aqMode->refreshRate = calculateRefresh(drmMode);
aqMode->preferred = (drmMode.type & DRM_MODE_TYPE_PREFERRED);
output->modes.emplace_back(aqMode);
if (currentModeInfo && std::memcmp(&drmMode, currentModeInfo, sizeof(drmModeModeInfo))) {
output->state->setMode(aqMode);
//uint64_t modeID = 0;
// getDRMProp(backend->gpu->fd, crtc->id, crtc->props.mode_id, &modeID);
crtc->refresh = calculateRefresh(drmMode);
}
backend->backend->log(AQ_LOG_DEBUG,
std::format("drm: Mode {}: {}x{}@{:.2f}Hz {}", i, (int)aqMode->pixelSize.x, (int)aqMode->pixelSize.y, aqMode->refreshRate / 1000.0,
aqMode->preferred ? " (preferred)" : ""));
}
output->physicalSize = {(double)connector->mmWidth, (double)connector->mmHeight};
backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Physical size {} (mm)", output->physicalSize));
switch (connector->subpixel) {
case DRM_MODE_SUBPIXEL_NONE: output->subpixel = eSubpixelMode::AQ_SUBPIXEL_NONE; break;
case DRM_MODE_SUBPIXEL_UNKNOWN: output->subpixel = eSubpixelMode::AQ_SUBPIXEL_UNKNOWN; break;
case DRM_MODE_SUBPIXEL_HORIZONTAL_RGB: output->subpixel = eSubpixelMode::AQ_SUBPIXEL_HORIZONTAL_RGB; break;
case DRM_MODE_SUBPIXEL_HORIZONTAL_BGR: output->subpixel = eSubpixelMode::AQ_SUBPIXEL_HORIZONTAL_BGR; break;
case DRM_MODE_SUBPIXEL_VERTICAL_RGB: output->subpixel = eSubpixelMode::AQ_SUBPIXEL_VERTICAL_RGB; break;
case DRM_MODE_SUBPIXEL_VERTICAL_BGR: output->subpixel = eSubpixelMode::AQ_SUBPIXEL_VERTICAL_BGR; break;
default: output->subpixel = eSubpixelMode::AQ_SUBPIXEL_UNKNOWN;
}
uint64_t prop = 0;
if (getDRMProp(backend->gpu->fd, id, props.non_desktop, &prop)) {
if (prop == 1)
backend->backend->log(AQ_LOG_DEBUG, "drm: Non-desktop connector");
output->nonDesktop = prop;
}
maxBpcBounds.fill(0);
if (props.max_bpc && !introspectDRMPropRange(backend->gpu->fd, props.max_bpc, maxBpcBounds.data(), &maxBpcBounds[1]))
backend->backend->log(AQ_LOG_ERROR, "drm: Failed to check max_bpc");
size_t edidLen = 0;
uint8_t* edidData = (uint8_t*)getDRMPropBlob(backend->gpu->fd, id, props.edid, &edidLen);
std::vector<uint8_t> edid{edidData, edidData + edidLen};
parseEDID(edid);
free(edidData);
edid = {};
// TODO: subconnectors
output->make = make;
output->model = model;
output->serial = serial;
output->description = std::format("{} {} {} ({})", make, model, serial, szName);
backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Description {}", output->description));
status = DRM_MODE_CONNECTED;
backend->backend->events.newOutput.emit(output);
}
void Aquamarine::SDRMConnector::disconnect() {
if (!output) {
backend->backend->log(AQ_LOG_DEBUG, std::format("drm: Not disconnecting connector {} because it's already disconnected", szName));
return;
}
output->events.destroy.emit();
output.reset();
status = DRM_MODE_DISCONNECTED;
}
Aquamarine::CDRMOutput::~CDRMOutput() {
;
}
bool Aquamarine::CDRMOutput::commit() {
return true;
}
bool Aquamarine::CDRMOutput::test() {
return true;
}
SP<IBackendImplementation> Aquamarine::CDRMOutput::getBackend() {
return backend.lock();
}
bool Aquamarine::CDRMOutput::setCursor(SP<IBuffer> buffer, const Vector2D& hotspot) {
return false; // FIXME:
}
void Aquamarine::CDRMOutput::moveCursor(const Vector2D& coord) {
; // FIXME:
}
void Aquamarine::CDRMOutput::scheduleFrame() {
;
}
Vector2D Aquamarine::CDRMOutput::maxCursorSize() {
return backend->drmProps.cursorSize;
}
Aquamarine::CDRMOutput::CDRMOutput(const std::string& name_, Hyprutils::Memory::CWeakPointer<CDRMBackend> backend_, Hyprutils::Memory::CSharedPointer<SDRMConnector> connector_) :
backend(backend_), connector(connector_) {
name = name_;
}

183
src/backend/drm/Props.cpp Normal file
View file

@ -0,0 +1,183 @@
#include <aquamarine/backend/DRM.hpp>
extern "C" {
#include <libseat.h>
#include <libudev.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
}
#include <cstring>
using namespace Aquamarine;
using namespace Hyprutils::Memory;
#define SP CSharedPointer
struct prop_info {
const char* name;
size_t index;
};
static const struct prop_info connector_info[] = {
#define INDEX(name) (offsetof(SDRMConnector::UDRMConnectorProps, name) / sizeof(uint32_t))
{"CRTC_ID", INDEX(crtc_id)},
{"DPMS", INDEX(dpms)},
{"EDID", INDEX(edid)},
{"PATH", INDEX(path)},
{"content type", INDEX(content_type)},
{"link-status", INDEX(link_status)},
{"max bpc", INDEX(max_bpc)},
{"non-desktop", INDEX(non_desktop)},
{"panel orientation", INDEX(panel_orientation)},
{"subconnector", INDEX(subconnector)},
{"vrr_capable", INDEX(vrr_capable)},
#undef INDEX
};
static const struct prop_info crtc_info[] = {
#define INDEX(name) (offsetof(SDRMCRTC::UDRMCRTCProps, name) / sizeof(uint32_t))
{"ACTIVE", INDEX(active)}, {"GAMMA_LUT", INDEX(gamma_lut)}, {"GAMMA_LUT_SIZE", INDEX(gamma_lut_size)}, {"MODE_ID", INDEX(mode_id)}, {"VRR_ENABLED", INDEX(vrr_enabled)},
#undef INDEX
};
static const struct prop_info plane_info[] = {
#define INDEX(name) (offsetof(SDRMPlane::UDRMPlaneProps, name) / sizeof(uint32_t))
{"CRTC_H", INDEX(crtc_h)}, {"CRTC_ID", INDEX(crtc_id)},
{"CRTC_W", INDEX(crtc_w)}, {"CRTC_X", INDEX(crtc_x)},
{"CRTC_Y", INDEX(crtc_y)}, {"FB_DAMAGE_CLIPS", INDEX(fb_damage_clips)},
{"FB_ID", INDEX(fb_id)}, {"HOTSPOT_X", INDEX(hotspot_x)},
{"HOTSPOT_Y", INDEX(hotspot_y)}, {"IN_FORMATS", INDEX(in_formats)},
{"SRC_H", INDEX(src_h)}, {"SRC_W", INDEX(src_w)},
{"SRC_X", INDEX(src_x)}, {"SRC_Y", INDEX(src_y)},
{"rotation", INDEX(rotation)}, {"type", INDEX(type)},
#undef INDEX
};
namespace Aquamarine {
static int comparePropInfo(const void* arg1, const void* arg2) {
const char* key = (const char*)arg1;
const prop_info* elem = (prop_info*)arg2;
return strcmp(key, elem->name);
}
static bool scanProperties(int fd, uint32_t id, uint32_t type, uint32_t* result, const prop_info* info, size_t info_len) {
drmModeObjectProperties* props = drmModeObjectGetProperties(fd, id, type);
if (!props)
return false;
for (uint32_t i = 0; i < props->count_props; ++i) {
drmModePropertyRes* prop = drmModeGetProperty(fd, props->props[i]);
if (!prop)
continue;
const prop_info* p = (prop_info*)bsearch(prop->name, info, info_len, sizeof(info[0]), comparePropInfo);
if (p)
result[p->index] = prop->prop_id;
drmModeFreeProperty(prop);
}
drmModeFreeObjectProperties(props);
return true;
}
bool getDRMConnectorProps(int fd, uint32_t id, SDRMConnector::UDRMConnectorProps* out) {
return scanProperties(fd, id, DRM_MODE_OBJECT_CONNECTOR, out->props, connector_info, sizeof(connector_info) / sizeof(connector_info[0]));
}
bool getDRMCRTCProps(int fd, uint32_t id, SDRMCRTC::UDRMCRTCProps* out) {
return scanProperties(fd, id, DRM_MODE_OBJECT_CRTC, out->props, crtc_info, sizeof(crtc_info) / sizeof(crtc_info[0]));
}
bool getDRMPlaneProps(int fd, uint32_t id, SDRMPlane::UDRMPlaneProps* out) {
return scanProperties(fd, id, DRM_MODE_OBJECT_PLANE, out->props, plane_info, sizeof(plane_info) / sizeof(plane_info[0]));
}
bool getDRMProp(int fd, uint32_t obj, uint32_t prop, uint64_t* ret) {
drmModeObjectProperties* props = drmModeObjectGetProperties(fd, obj, DRM_MODE_OBJECT_ANY);
if (!props)
return false;
bool found = false;
for (uint32_t i = 0; i < props->count_props; ++i) {
if (props->props[i] == prop) {
*ret = props->prop_values[i];
found = true;
break;
}
}
drmModeFreeObjectProperties(props);
return found;
}
void* getDRMPropBlob(int fd, uint32_t obj, uint32_t prop, size_t* ret_len) {
uint64_t blob_id;
if (!getDRMProp(fd, obj, prop, &blob_id))
return nullptr;
drmModePropertyBlobRes* blob = drmModeGetPropertyBlob(fd, blob_id);
if (!blob)
return nullptr;
void* ptr = malloc(blob->length);
if (!ptr) {
drmModeFreePropertyBlob(blob);
return nullptr;
}
memcpy(ptr, blob->data, blob->length);
*ret_len = blob->length;
drmModeFreePropertyBlob(blob);
return ptr;
}
char* getDRMPropEnum(int fd, uint32_t obj, uint32_t prop_id) {
uint64_t value;
if (!getDRMProp(fd, obj, prop_id, &value))
return nullptr;
drmModePropertyRes* prop = drmModeGetProperty(fd, prop_id);
if (!prop)
return nullptr;
char* str = nullptr;
for (int i = 0; i < prop->count_enums; i++) {
if (prop->enums[i].value == value) {
str = strdup(prop->enums[i].name);
break;
}
}
drmModeFreeProperty(prop);
return str;
}
bool introspectDRMPropRange(int fd, uint32_t prop_id, uint64_t* min, uint64_t* max) {
drmModePropertyRes* prop = drmModeGetProperty(fd, prop_id);
if (!prop)
return false;
if (drmModeGetPropertyType(prop) != DRM_MODE_PROP_RANGE) {
drmModeFreeProperty(prop);
return false;
}
if (prop->count_values != 2)
abort();
if (min != nullptr)
*min = prop->values[0];
if (max != nullptr)
*max = prop->values[1];
drmModeFreeProperty(prop);
return true;
}
};

13
src/backend/drm/Props.hpp Normal file
View file

@ -0,0 +1,13 @@
#pragma once
#include <aquamarine/backend/DRM.hpp>
namespace Aquamarine {
bool getDRMConnectorProps(int fd, uint32_t id, SDRMConnector::UDRMConnectorProps* out);
bool getDRMCRTCProps(int fd, uint32_t id, SDRMCRTC::UDRMCRTCProps* out);
bool getDRMPlaneProps(int fd, uint32_t id, SDRMPlane::UDRMPlaneProps* out);
bool getDRMProp(int fd, uint32_t obj, uint32_t prop, uint64_t* ret);
void* getDRMPropBlob(int fd, uint32_t obj, uint32_t prop, size_t* ret_len);
char* getDRMPropEnum(int fd, uint32_t obj, uint32_t prop_id);
bool introspectDRMPropRange(int fd, uint32_t prop_id, uint64_t* min, uint64_t* max);
};