aquamarine/src/allocator/GBM.cpp

321 lines
12 KiB
C++

#include <aquamarine/allocator/GBM.hpp>
#include <aquamarine/backend/Backend.hpp>
#include <aquamarine/backend/DRM.hpp>
#include <aquamarine/allocator/Swapchain.hpp>
#include "FormatUtils.hpp"
#include "Shared.hpp"
#include <xf86drm.h>
#include <gbm.h>
#include <unistd.h>
#include "../backend/drm/Renderer.hpp"
using namespace Aquamarine;
using namespace Hyprutils::Memory;
#define SP CSharedPointer
static SDRMFormat guessFormatFrom(std::vector<SDRMFormat> formats, bool cursor) {
if (formats.empty())
return SDRMFormat{};
if (!cursor) {
/*
Try to find 10bpp formats first, as they offer better color precision.
For cursors, don't, as these almost never support that.
*/
if (auto it = std::find_if(formats.begin(), formats.end(), [](const auto& f) { return f.drmFormat == DRM_FORMAT_ARGB2101010; }); it != formats.end())
return *it;
if (auto it = std::find_if(formats.begin(), formats.end(), [](const auto& f) { return f.drmFormat == DRM_FORMAT_XRGB2101010; }); it != formats.end())
return *it;
}
if (auto it = std::find_if(formats.begin(), formats.end(), [](const auto& f) { return f.drmFormat == DRM_FORMAT_ARGB8888; }); it != formats.end())
return *it;
if (auto it = std::find_if(formats.begin(), formats.end(), [](const auto& f) { return f.drmFormat == DRM_FORMAT_XRGB8888; }); it != formats.end())
return *it;
for (auto& f : formats) {
auto name = fourccToName(f.drmFormat);
/* 10 bpp RGB */
if (name.contains("30"))
return f;
}
for (auto& f : formats) {
auto name = fourccToName(f.drmFormat);
/* 8 bpp RGB */
if (name.contains("24"))
return f;
}
return formats.at(0);
}
Aquamarine::CGBMBuffer::CGBMBuffer(const SAllocatorBufferParams& params, Hyprutils::Memory::CWeakPointer<CGBMAllocator> allocator_,
Hyprutils::Memory::CSharedPointer<CSwapchain> swapchain) : allocator(allocator_) {
if (!allocator)
return;
attrs.size = params.size;
attrs.format = params.format;
size = attrs.size;
const bool CURSOR = params.cursor && params.scanout;
const bool MULTIGPU = params.multigpu && params.scanout;
TRACE(allocator->backend->log(AQ_LOG_TRACE,
std::format("GBM: Allocating a buffer: size {}, format {}, cursor: {}, multigpu: {}, scanout: {}", attrs.size, fourccToName(attrs.format), CURSOR,
MULTIGPU, params.scanout)));
const auto FORMATS = CURSOR ? swapchain->backendImpl->getCursorFormats() : swapchain->backendImpl->getRenderFormats();
const auto RENDERABLE = swapchain->backendImpl->getRenderableFormats();
std::vector<uint64_t> explicitModifiers;
if (attrs.format == DRM_FORMAT_INVALID) {
attrs.format = guessFormatFrom(FORMATS, CURSOR).drmFormat;
if (attrs.format != DRM_FORMAT_INVALID)
allocator->backend->log(AQ_LOG_DEBUG, std::format("GBM: Automatically selected format {} for new GBM buffer", fourccToName(attrs.format)));
}
if (attrs.format == DRM_FORMAT_INVALID) {
allocator->backend->log(AQ_LOG_ERROR, "GBM: Failed to allocate a GBM buffer: no format found");
return;
}
// check if we can use modifiers. If the requested support has any explicit modifier
// supported by the primary backend, we can.
for (auto& f : FORMATS) {
if (f.drmFormat != attrs.format)
continue;
for (auto& m : f.modifiers) {
if (m == DRM_FORMAT_MOD_INVALID)
continue;
if (!RENDERABLE.empty() && params.scanout && !CURSOR && !MULTIGPU) {
// regular scanout plane, check if the format is renderable
auto rformat = std::find_if(RENDERABLE.begin(), RENDERABLE.end(), [f](const auto& e) { return e.drmFormat == f.drmFormat; });
if (rformat == RENDERABLE.end()) {
TRACE(allocator->backend->log(AQ_LOG_TRACE, std::format("GBM: Dropping format {} as it's not renderable", fourccToName(f.drmFormat))));
break;
}
if (std::find(rformat->modifiers.begin(), rformat->modifiers.end(), m) == rformat->modifiers.end()) {
TRACE(allocator->backend->log(AQ_LOG_TRACE, std::format("GBM: Dropping modifier 0x{:x} as it's not renderable", m)));
continue;
}
}
explicitModifiers.push_back(m);
}
}
// FIXME: Nvidia cannot render to linear buffers. What do?
if (MULTIGPU) {
allocator->backend->log(AQ_LOG_DEBUG, "GBM: Buffer is marked as multigpu, forcing linear");
explicitModifiers = {DRM_FORMAT_MOD_LINEAR};
}
if (explicitModifiers.empty()) {
// fall back to using a linear buffer.
explicitModifiers.push_back(DRM_FORMAT_MOD_LINEAR);
}
uint32_t flags = GBM_BO_USE_RENDERING;
if (params.scanout)
flags |= GBM_BO_USE_SCANOUT;
if (explicitModifiers.empty()) {
allocator->backend->log(AQ_LOG_WARNING, "GBM: Using modifier-less allocation");
bo = gbm_bo_create(allocator->gbmDevice, attrs.size.x, attrs.size.y, attrs.format, flags);
} else {
TRACE(allocator->backend->log(AQ_LOG_TRACE, std::format("GBM: Using modifier-based allocation, modifiers: {}", explicitModifiers.size())));
for (auto& mod : explicitModifiers) {
TRACE(allocator->backend->log(AQ_LOG_TRACE, std::format("GBM: | mod 0x{:x}", mod)));
}
bo = gbm_bo_create_with_modifiers2(allocator->gbmDevice, attrs.size.x, attrs.size.y, attrs.format, explicitModifiers.data(), explicitModifiers.size(), flags);
if (!bo && CURSOR) {
// allow non-renderable cursor buffer for nvidia
allocator->backend->log(AQ_LOG_ERROR, "GBM: Allocating with modifiers and flags failed, falling back to modifiers without flags");
bo = gbm_bo_create_with_modifiers(allocator->gbmDevice, attrs.size.x, attrs.size.y, attrs.format, explicitModifiers.data(), explicitModifiers.size());
}
if (!bo) {
if (explicitModifiers.size() == 1 && explicitModifiers[0] == DRM_FORMAT_MOD_LINEAR) {
flags |= GBM_BO_USE_LINEAR;
allocator->backend->log(AQ_LOG_ERROR, "GBM: Allocating with modifiers failed, falling back to modifier-less allocation");
} else
allocator->backend->log(AQ_LOG_ERROR, "GBM: Allocating with modifiers failed, falling back to implicit");
bo = gbm_bo_create(allocator->gbmDevice, attrs.size.x, attrs.size.y, attrs.format, flags);
}
}
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 = (flags & GBM_BO_USE_LINEAR) ? DRM_FORMAT_MOD_LINEAR : 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_DEBUG,
std::format("GBM: Allocated a new buffer with size {} and format {} with modifier {} aka {}", attrs.size, fourccToName(attrs.format), attrs.modifier,
modName ? modName : "Unknown"));
free(modName);
if (params.scanout && swapchain->backendImpl->type() == AQ_BACKEND_DRM) {
// clear the buffer using the DRM renderer to avoid uninitialized mem
auto impl = (CDRMBackend*)swapchain->backendImpl.get();
if (impl->rendererState.renderer)
impl->rendererState.renderer->clearBuffer(this);
}
}
Aquamarine::CGBMBuffer::~CGBMBuffer() {
events.destroy.emit();
if (bo) {
if (gboMapping)
gbm_bo_unmap(bo, gboMapping); // FIXME: is it needed before destroy?
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;
}
std::tuple<uint8_t*, uint32_t, size_t> Aquamarine::CGBMBuffer::beginDataPtr(uint32_t flags) {
uint32_t stride = 0;
if (boBuffer)
allocator->backend->log(AQ_LOG_ERROR, "beginDataPtr is called a second time without calling endDataPtr first. Returning old mapping");
else
boBuffer = gbm_bo_map(bo, 0, 0, attrs.size.x, attrs.size.y, flags, &stride, &gboMapping);
return {(uint8_t*)boBuffer, attrs.format, stride * attrs.size.y};
}
void Aquamarine::CGBMBuffer::endDataPtr() {
if (gboMapping) {
gbm_bo_unmap(bo, gboMapping);
gboMapping = nullptr;
boBuffer = nullptr;
}
}
CGBMAllocator::~CGBMAllocator() {
if (gbmDevice)
gbm_device_destroy(gbmDevice);
}
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, Hyprutils::Memory::CSharedPointer<CSwapchain> swapchain_) {
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;
}
auto newBuffer = SP<CGBMBuffer>(new CGBMBuffer(params, self, swapchain_));
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();
}
int Aquamarine::CGBMAllocator::drmFD() {
return fd;
}
eAllocatorType Aquamarine::CGBMAllocator::type() {
return AQ_ALLOCATOR_TYPE_GBM;
}