File: Backend.cpp

package info (click to toggle)
aquamarine 0.10.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 664 kB
  • sloc: cpp: 8,508; makefile: 13; sh: 4
file content (374 lines) | stat: -rw-r--r-- 12,064 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
#include <aquamarine/backend/Backend.hpp>
#include <aquamarine/backend/Wayland.hpp>
#include <aquamarine/backend/Headless.hpp>
#include <aquamarine/backend/DRM.hpp>
#include <aquamarine/backend/Null.hpp>
#include <aquamarine/allocator/GBM.hpp>
#include <ranges>
#include <sys/timerfd.h>
#include <ctime>
#include <cstring>
#include <xf86drm.h>
#include <fcntl.h>
#include <unistd.h>

#include "Logger.hpp"

using namespace Hyprutils::Memory;
using namespace Aquamarine;
#define SP CSharedPointer

#define TIMESPEC_NSEC_PER_SEC 1000000000LL

static void timespecAddNs(timespec* pTimespec, int64_t delta) {
    int delta_ns_low = delta % TIMESPEC_NSEC_PER_SEC;
    int delta_s_high = delta / TIMESPEC_NSEC_PER_SEC;

    pTimespec->tv_sec += delta_s_high;

    pTimespec->tv_nsec += (long)delta_ns_low;
    if (pTimespec->tv_nsec >= TIMESPEC_NSEC_PER_SEC) {
        pTimespec->tv_nsec -= TIMESPEC_NSEC_PER_SEC;
        ++pTimespec->tv_sec;
    }
}

static const char* backendTypeToName(eBackendType type) {
    switch (type) {
        case AQ_BACKEND_DRM: return "drm";
        case AQ_BACKEND_HEADLESS: return "headless";
        case AQ_BACKEND_WAYLAND: return "wayland";
        default: break;
    }
    return "invalid";
}

Aquamarine::CBackend::CBackend() {
    ;
}

Aquamarine::SBackendImplementationOptions::SBackendImplementationOptions() : backendType(AQ_BACKEND_WAYLAND), backendRequestMode(AQ_BACKEND_REQUEST_IF_AVAILABLE) {
    ;
}

Aquamarine::SBackendOptions::SBackendOptions() : logFunction(nullptr) {
    ;
}

Hyprutils::Memory::CSharedPointer<CBackend> Aquamarine::CBackend::create(const std::vector<SBackendImplementationOptions>& backends, const SBackendOptions& options) {
    auto backend = SP<CBackend>(new CBackend());

    backend->options               = options;
    backend->implementationOptions = backends;
    backend->self                  = backend;

    g_logger->m_loggerConnection = options.logConnection;
    g_logger->m_logFn            = options.logFunction;
    g_logger->updateLevels();

    if (backends.size() <= 0)
        return nullptr;

    backend->log(AQ_LOG_DEBUG, "Creating an Aquamarine backend!");

    for (auto const& b : backends) {
        if (b.backendType == AQ_BACKEND_WAYLAND) {
            auto ref = SP<CWaylandBackend>(new CWaylandBackend(backend));
            backend->implementations.emplace_back(ref);
            ref->self = ref;
        } else if (b.backendType == AQ_BACKEND_DRM) {
            auto ref = CDRMBackend::attempt(backend);
            if (ref.empty()) {
                backend->log(AQ_LOG_ERROR, "DRM Backend failed");
                continue;
            }

            for (auto const& r : ref) {
                backend->implementations.emplace_back(r);
            }
        } else if (b.backendType == AQ_BACKEND_HEADLESS) {
            auto ref = SP<CHeadlessBackend>(new CHeadlessBackend(backend));
            backend->implementations.emplace_back(ref);
            ref->self = ref;
        } else if (b.backendType == AQ_BACKEND_NULL) {
            auto ref = SP<CNullBackend>(new CNullBackend(backend));
            backend->implementations.emplace_back(ref);
            ref->self = ref;
        } else {
            backend->log(AQ_LOG_ERROR, std::format("Unknown backend id: {}", (int)b.backendType));
            continue;
        }
    }

    // create a timerfd for idle events
    backend->idle.fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);

    return backend;
}

Aquamarine::CBackend::~CBackend() {
    ;
}

bool Aquamarine::CBackend::start() {
    log(AQ_LOG_DEBUG, "Starting the Aquamarine backend!");

    int  started = 0;

    auto optionsForType = [this](eBackendType type) -> SBackendImplementationOptions {
        for (auto const& o : implementationOptions) {
            if (o.backendType == type)
                return o;
        }
        return SBackendImplementationOptions{};
    };

    for (size_t i = 0; i < implementations.size(); ++i) {
        const bool ok = implementations.at(i)->start();

        if (!ok) {
            log(AQ_LOG_ERROR, std::format("Requested backend ({}) could not start, enabling fallbacks", backendTypeToName(implementations.at(i)->type())));
            if (optionsForType(implementations.at(i)->type()).backendRequestMode == AQ_BACKEND_REQUEST_MANDATORY) {
                log(AQ_LOG_CRITICAL, std::format("Requested backend ({}) could not start and it's mandatory, cannot continue!", backendTypeToName(implementations.at(i)->type())));
                implementations.clear();
                return false;
            }
        } else
            started++;
    }

    if (implementations.empty() || started <= 0) {
        log(AQ_LOG_CRITICAL, "No backend could be opened. Make sure there was a correct backend passed to CBackend, and that your environment supports at least one of them.");
        return false;
    }

    // erase failed impls
    std::erase_if(implementations, [this](const auto& i) {
        bool failed = i->pollFDs().empty() && i->type() != AQ_BACKEND_NULL;
        if (failed)
            log(AQ_LOG_ERROR, std::format("Implementation {} failed, erasing.", backendTypeToName(i->type())));
        return failed;
    });

    // TODO: obviously change this when (if) we add different allocators.
    for (auto const& b : implementations) {
        if (b->drmFD() >= 0) {
            auto fd = reopenDRMNode(b->drmFD());
            if (fd < 0) {
                // this is critical, we cannot create an allocator properly
                log(AQ_LOG_CRITICAL, "Failed to create an allocator (reopenDRMNode failed)");
                return false;
            }
            primaryAllocator = CGBMAllocator::create(fd, self);
            break;
        }
    }

    if (!primaryAllocator && (implementations.empty() || implementations.at(0)->type() != AQ_BACKEND_NULL)) {
        log(AQ_LOG_CRITICAL, "Cannot open backend: no allocator available");
        return false;
    }

    ready = true;
    for (auto const& b : implementations) {
        b->onReady();
    }

    if (session)
        session->onReady();

    sessionFDs = session ? session->pollFDs() : std::vector<Hyprutils::Memory::CSharedPointer<SPollFD>>{};

    return true;
}

void Aquamarine::CBackend::log(eBackendLogLevel level, const std::string& msg) {
    g_logger->log(level, msg);
}

std::vector<Hyprutils::Memory::CSharedPointer<SPollFD>> Aquamarine::CBackend::getPollFDs() {
    std::vector<Hyprutils::Memory::CSharedPointer<SPollFD>> result;
    for (auto const& i : implementations) {
        auto pollfds = i->pollFDs();
        for (auto const& p : pollfds) {
            log(AQ_LOG_DEBUG, std::format("backend: poll fd {} for implementation {}", p->fd, backendTypeToName(i->type())));
            result.emplace_back(p);
        }
    }

    for (auto const& sfd : sessionFDs) {
        log(AQ_LOG_DEBUG, std::format("backend: poll fd {} for session", sfd->fd));
        result.emplace_back(sfd);
    }

    log(AQ_LOG_DEBUG, std::format("backend: poll fd {} for idle", idle.fd));
    result.emplace_back(makeShared<SPollFD>(idle.fd, [this]() { dispatchIdle(); }));

    return result;
}

int Aquamarine::CBackend::drmFD() {
    for (auto const& i : implementations) {
        int fd = i->drmFD();
        if (fd < 0)
            continue;

        return fd;
    }
    return -1;
}

int Aquamarine::CBackend::drmRenderNodeFD() {
    for (auto const& i : implementations) {
        int fd = i->drmRenderNodeFD();
        if (fd < 0)
            continue;

        return fd;
    }
    return -1;
}

bool Aquamarine::CBackend::hasSession() {
    return session;
}

std::vector<SDRMFormat> Aquamarine::CBackend::getPrimaryRenderFormats() {
    for (auto const& b : implementations) {
        if (b->type() != AQ_BACKEND_DRM && b->type() != AQ_BACKEND_WAYLAND)
            continue;

        return b->getRenderFormats();
    }

    for (auto const& b : implementations) {
        return b->getRenderFormats();
    }

    return {};
}

const std::vector<SP<IBackendImplementation>>& Aquamarine::CBackend::getImplementations() {
    return implementations;
}

void Aquamarine::CBackend::addIdleEvent(SP<std::function<void(void)>> fn) {
    auto r = idle.pending.emplace_back(fn);

    updateIdleTimer();
}

void Aquamarine::CBackend::updateIdleTimer() {
    uint64_t ADD_NS = idle.pending.empty() ? TIMESPEC_NSEC_PER_SEC * 240ULL /* 240s, 4 mins */ : 0;

    timespec now;
    clock_gettime(CLOCK_MONOTONIC, &now);
    timespecAddNs(&now, ADD_NS);

    itimerspec ts = {.it_value = now};

    if (timerfd_settime(idle.fd, TFD_TIMER_ABSTIME, &ts, nullptr))
        log(AQ_LOG_ERROR, std::format("backend: failed to arm timerfd: {}", strerror(errno)));
}

void Aquamarine::CBackend::removeIdleEvent(SP<std::function<void(void)>> pfn) {
    std::erase(idle.pending, pfn);
}

void Aquamarine::CBackend::dispatchIdle() {
    auto cpy = idle.pending;
    idle.pending.clear();

    for (auto const& i : cpy) {
        if (i && *i)
            (*i)();
    }

    updateIdleTimer();
}

void Aquamarine::CBackend::onNewGpu(std::string path) {
    const auto primary    = std::ranges::find_if(implementations, [](SP<IBackendImplementation> value) { return value->type() == Aquamarine::AQ_BACKEND_DRM; });
    const auto primaryDrm = primary != implementations.end() ? ((Aquamarine::CDRMBackend*)(*primary).get())->self.lock() : nullptr;

    auto       ref = CDRMBackend::fromGpu(path, self.lock(), primaryDrm);
    if (!ref) {
        log(AQ_LOG_ERROR, std::format("DRM Backend failed for device {}", path));
        return;
    }
    if (!ref->start()) {
        log(AQ_LOG_ERROR, std::format("Couldn't start DRM Backend for device {}", path));
        return;
    }

    implementations.emplace_back(ref);
    events.pollFDsChanged.emit();

    ref->onReady();        // Renderer created here
    ref->recheckOutputs(); // Now we can recheck outputs
}

// Yoinked from wlroots, render/allocator/allocator.c
// Ref-counting reasons, see https://gitlab.freedesktop.org/mesa/drm/-/merge_requests/110
int Aquamarine::CBackend::reopenDRMNode(int drmFD, bool allowRenderNode) {

    if (drmIsMaster(drmFD)) {
        // Only recent kernels support empty leases
        uint32_t lesseeID = 0;
        int      leaseFD  = drmModeCreateLease(drmFD, nullptr, 0, O_CLOEXEC, &lesseeID);
        if (leaseFD >= 0) {
            return leaseFD;
        } else if (leaseFD != -EINVAL && leaseFD != -EOPNOTSUPP) {
            log(AQ_LOG_ERROR, "reopenDRMNode: drmModeCreateLease failed");
            return -1;
        }
        log(AQ_LOG_DEBUG, "reopenDRMNode: drmModeCreateLease failed, falling back to open");
    }

    char* name = nullptr;
    if (allowRenderNode)
        name = drmGetRenderDeviceNameFromFd(drmFD);

    if (!name) {
        // primary node or no name
        name = drmGetDeviceNameFromFd2(drmFD);

        if (!name) {
            log(AQ_LOG_ERROR, "reopenDRMNode: drmGetDeviceNameFromFd2 failed");
            return -1;
        }
    }

    log(AQ_LOG_DEBUG, std::format("reopenDRMNode: opening node {}", name));

    int newFD = open(name, O_RDWR | O_CLOEXEC);
    if (newFD < 0) {
        log(AQ_LOG_ERROR, std::format("reopenDRMNode: failed to open node {}", name));
        free(name);
        return -1;
    }

    free(name);

    // We need to authenticate if we are using a DRM primary node and are the master
    if (drmIsMaster(drmFD) && drmGetNodeTypeFromFd(newFD) == DRM_NODE_PRIMARY) {
        drm_magic_t magic;
        if (int ret = drmGetMagic(newFD, &magic); ret < 0) {
            log(AQ_LOG_ERROR, std::format("reopenDRMNode: drmGetMagic failed: {}", strerror(-ret)));
            close(newFD);
            return -1;
        }

        if (int ret = drmAuthMagic(drmFD, magic); ret < 0) {
            log(AQ_LOG_ERROR, std::format("reopenDRMNode: drmAuthMagic failed: {}", strerror(-ret)));
            close(newFD);
            return -1;
        }
    }

    return newFD;
}

std::vector<SDRMFormat> Aquamarine::IBackendImplementation::getRenderableFormats() {
    return {};
}