drm: properly blit multigpu surfaces

This commit is contained in:
Vaxry 2024-07-11 20:41:53 +02:00
parent c3bfe3de89
commit c4a5fafe76
10 changed files with 842 additions and 37 deletions

View file

@ -17,6 +17,7 @@ set(INCLUDE ${CMAKE_INSTALL_FULL_INCLUDEDIR})
set(LIBDIR ${CMAKE_INSTALL_FULL_LIBDIR})
find_package(PkgConfig REQUIRED)
find_package(OpenGL REQUIRED COMPONENTS "GLES2")
find_package(hyprwayland-scanner 0.4.0 REQUIRED)
pkg_check_modules(deps REQUIRED IMPORTED_TARGET
libseat>=0.8.0 libinput>=1.26.0
@ -51,7 +52,11 @@ set_target_properties(aquamarine PROPERTIES
VERSION ${AQUAMARINE_VERSION}
SOVERSION 0
)
target_link_libraries(aquamarine PkgConfig::deps)
target_link_libraries(aquamarine
OpenGL::EGL
OpenGL::GL
PkgConfig::deps
)
check_include_file("sys/timerfd.h" HAS_TIMERFD)
pkg_check_modules(epoll IMPORTED_TARGET epoll-shim)

View file

@ -123,10 +123,9 @@ namespace Aquamarine {
Hyprutils::Signal::CSignal newTabletPad;
} events;
Hyprutils::Memory::CSharedPointer<IAllocator> primaryAllocator;
std::vector<Hyprutils::Memory::CSharedPointer<IAllocator>> allocators;
bool ready = false;
Hyprutils::Memory::CSharedPointer<CSession> session;
Hyprutils::Memory::CSharedPointer<IAllocator> primaryAllocator;
bool ready = false;
Hyprutils::Memory::CSharedPointer<CSession> session;
private:
CBackend();

View file

@ -13,6 +13,7 @@ namespace Aquamarine {
class CDRMFB;
class CDRMOutput;
struct SDRMConnector;
class CDRMRenderer;
typedef std::function<void(void)> FIdleCallback;
@ -341,7 +342,6 @@ namespace Aquamarine {
std::vector<FIdleCallback> idleCallbacks;
std::string gpuName;
Hyprutils::Memory::CWeakPointer<IAllocator> allocator;
private:
CDRMBackend(Hyprutils::Memory::CSharedPointer<CBackend> backend);
@ -350,15 +350,24 @@ namespace Aquamarine {
bool registerGPU(Hyprutils::Memory::CSharedPointer<CSessionDevice> gpu_, Hyprutils::Memory::CSharedPointer<CDRMBackend> primary_ = {});
bool checkFeatures();
bool initResources();
bool initMgpu();
bool grabFormats();
bool shouldBlit();
void scanConnectors();
void scanLeases();
void restoreAfterVT();
void recheckCRTCs();
Hyprutils::Memory::CSharedPointer<CSessionDevice> gpu;
Hyprutils::Memory::CSharedPointer<IDRMImplementation> impl;
Hyprutils::Memory::CWeakPointer<CDRMBackend> primary;
Hyprutils::Memory::CSharedPointer<CSessionDevice> gpu;
Hyprutils::Memory::CSharedPointer<IDRMImplementation> impl;
Hyprutils::Memory::CWeakPointer<CDRMBackend> primary;
// multigpu state, only present if this backend is not primary, aka if this->primary != nullptr
struct {
Hyprutils::Memory::CSharedPointer<IAllocator> allocator;
Hyprutils::Memory::CSharedPointer<CSwapchain> swapchain;
Hyprutils::Memory::CSharedPointer<CDRMRenderer> renderer;
} mgpu;
Hyprutils::Memory::CWeakPointer<CBackend> backend;

View file

@ -139,10 +139,7 @@ namespace Aquamarine {
std::vector<Hyprutils::Memory::CSharedPointer<SOutputMode>> modes;
Hyprutils::Memory::CSharedPointer<COutputState> state = Hyprutils::Memory::makeShared<COutputState>();
// please note that for multigpu setups, this swapchain resides on the target gpu,
// so it's recommended that if this swapchain's allocator FD doesn't match the primary
// drmFD used, you should render to a buffer on the main gpu and only perform the final copy to this swapchain.
Hyprutils::Memory::CSharedPointer<CSwapchain> swapchain;
Hyprutils::Memory::CSharedPointer<CSwapchain> swapchain;
//

View file

@ -144,13 +144,12 @@ bool Aquamarine::CBackend::start() {
// TODO: obviously change this when (if) we add different allocators.
for (auto& b : implementations) {
if (b->drmFD() >= 0) {
allocators.emplace_back(CGBMAllocator::create(b->drmFD(), self));
if (!primaryAllocator)
primaryAllocator = allocators.front();
primaryAllocator = CGBMAllocator::create(b->drmFD(), self);
break;
}
}
if (allocators.empty() || !primaryAllocator)
if (!primaryAllocator)
return false;
ready = true;

View file

@ -2,6 +2,7 @@
#include <aquamarine/backend/DRM.hpp>
#include <aquamarine/backend/drm/Legacy.hpp>
#include <aquamarine/backend/drm/Atomic.hpp>
#include <aquamarine/allocator/GBM.hpp>
#include <hyprutils/string/VarList.hpp>
#include <chrono>
#include <thread>
@ -24,6 +25,7 @@ extern "C" {
#include "FormatUtils.hpp"
#include "Shared.hpp"
#include "hwdata.hpp"
#include "Renderer.hpp"
using namespace Aquamarine;
using namespace Hyprutils::Memory;
@ -465,6 +467,38 @@ bool Aquamarine::CDRMBackend::initResources() {
return true;
}
bool Aquamarine::CDRMBackend::shouldBlit() {
return primary;
}
bool Aquamarine::CDRMBackend::initMgpu() {
if (!primary)
return true;
mgpu.allocator = CGBMAllocator::create(gpu->fd, backend);
if (!mgpu.allocator) {
backend->log(AQ_LOG_ERROR, "drm: initMgpu: no allocator");
return false;
}
mgpu.swapchain = CSwapchain::create(mgpu.allocator, self.lock());
if (!mgpu.swapchain) {
backend->log(AQ_LOG_ERROR, "drm: initMgpu: no swapchain");
return false;
}
mgpu.renderer = CDRMRenderer::attempt(gpu->fd, backend.lock());
if (!mgpu.renderer) {
backend->log(AQ_LOG_ERROR, "drm: initMgpu: no renderer");
return false;
}
return true;
}
void Aquamarine::CDRMBackend::recheckCRTCs() {
if (connectors.empty() || crtcs.empty())
return;
@ -760,27 +794,18 @@ void Aquamarine::CDRMBackend::onReady() {
backend->log(AQ_LOG_DEBUG, std::format("drm: onReady: connector {} has output name {}", c->id, c->output->name));
// find our allocator, for multigpu setups there will be 2
for (auto& alloc : backend->allocators) {
if (alloc->drmFD() != gpu->fd)
continue;
allocator = alloc;
break;
}
if (!allocator) {
backend->log(AQ_LOG_ERROR, std::format("drm: backend for gpu {} doesn't have an allocator?!", gpu->path));
return;
}
// swapchain has to be created here because allocator is absent in connect if not ready
c->output->swapchain = CSwapchain::create(allocator.lock(), self.lock());
c->output->swapchain = CSwapchain::create(backend->primaryAllocator, self.lock());
c->output->swapchain->reconfigure(SSwapchainOptions{.length = 0, .scanout = true, .multigpu = !!primary}); // mark the swapchain for scanout
c->output->needsFrame = true;
backend->events.newOutput.emit(SP<IOutput>(c->output));
}
if (!initMgpu()) {
backend->log(AQ_LOG_ERROR, "drm: Failed initializing mgpu");
return;
}
}
std::vector<SDRMFormat> Aquamarine::CDRMBackend::getRenderFormats() {
@ -852,7 +877,7 @@ int Aquamarine::CDRMBackend::getNonMasterFD() {
}
SP<IAllocator> Aquamarine::CDRMBackend::preferredAllocator() {
return allocator.lock();
return backend->primaryAllocator;
}
bool Aquamarine::SDRMPlane::init(drmModePlane* plane) {
@ -1172,7 +1197,7 @@ void Aquamarine::SDRMConnector::connect(drmModeConnector* connector) {
if (!backend->backend->ready)
return;
output->swapchain = CSwapchain::create(backend->allocator.lock(), backend->self.lock());
output->swapchain = CSwapchain::create(backend->backend->primaryAllocator, backend->self.lock());
backend->backend->events.newOutput.emit(output);
output->scheduleFrame(IOutput::AQ_SCHEDULE_NEW_CONNECTOR);
}
@ -1327,10 +1352,26 @@ bool Aquamarine::CDRMOutput::commitState(bool onlyTest) {
TRACE(backend->backend->log(AQ_LOG_TRACE, "drm: Committed a buffer, updating state"));
SP<CDRMFB> drmFB;
auto buf = STATE.buffer;
bool isNew = false;
drmFB = CDRMFB::create(buf, backend, &isNew); // will return attachment if present
if (backend->shouldBlit()) {
TRACE(backend->backend->log(AQ_LOG_TRACE, "drm: Backend requires blit, blitting"));
auto OPTIONS = swapchain->currentOptions();
OPTIONS.multigpu = false; // this is not a shared swapchain, and additionally, don't make it linear, nvidia would be mad
if (!backend->mgpu.swapchain->reconfigure(OPTIONS)) {
backend->backend->log(AQ_LOG_ERROR, "drm: Backend requires blit, but the mgpu swapchain failed reconfiguring");
return false;
}
auto NEWAQBUF = backend->mgpu.swapchain->next(nullptr);
if (!backend->mgpu.renderer->blit(STATE.buffer, NEWAQBUF)) {
backend->backend->log(AQ_LOG_ERROR, "drm: Backend requires blit, but blit failed");
return false;
}
drmFB = CDRMFB::create(NEWAQBUF, backend, nullptr); // will return attachment if present
} else
drmFB = CDRMFB::create(STATE.buffer, backend, nullptr); // will return attachment if present
if (!drmFB) {
backend->backend->log(AQ_LOG_ERROR, "drm: Buffer failed to import to KMS");

207
src/backend/drm/Math.cpp Normal file
View file

@ -0,0 +1,207 @@
#include "Math.hpp"
#include <unordered_map>
#include <cstring>
#include <cmath>
void matrixIdentity(float mat[9]) {
static const float identity[9] = {
1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
};
memcpy(mat, identity, sizeof(identity));
}
void matrixMultiply(float mat[9], const float a[9], const float b[9]) {
float product[9];
product[0] = a[0] * b[0] + a[1] * b[3] + a[2] * b[6];
product[1] = a[0] * b[1] + a[1] * b[4] + a[2] * b[7];
product[2] = a[0] * b[2] + a[1] * b[5] + a[2] * b[8];
product[3] = a[3] * b[0] + a[4] * b[3] + a[5] * b[6];
product[4] = a[3] * b[1] + a[4] * b[4] + a[5] * b[7];
product[5] = a[3] * b[2] + a[4] * b[5] + a[5] * b[8];
product[6] = a[6] * b[0] + a[7] * b[3] + a[8] * b[6];
product[7] = a[6] * b[1] + a[7] * b[4] + a[8] * b[7];
product[8] = a[6] * b[2] + a[7] * b[5] + a[8] * b[8];
memcpy(mat, product, sizeof(product));
}
void matrixTranspose(float mat[9], const float a[9]) {
float transposition[9] = {
a[0], a[3], a[6], a[1], a[4], a[7], a[2], a[5], a[8],
};
memcpy(mat, transposition, sizeof(transposition));
}
void matrixTranslate(float mat[9], float x, float y) {
float translate[9] = {
1.0f, 0.0f, x, 0.0f, 1.0f, y, 0.0f, 0.0f, 1.0f,
};
matrixMultiply(mat, mat, translate);
}
void matrixScale(float mat[9], float x, float y) {
float scale[9] = {
x, 0.0f, 0.0f, 0.0f, y, 0.0f, 0.0f, 0.0f, 1.0f,
};
matrixMultiply(mat, mat, scale);
}
void matrixRotate(float mat[9], float rad) {
float rotate[9] = {
(float)cos(rad), (float)-sin(rad), 0.0f, (float)sin(rad), (float)cos(rad), 0.0f, 0.0f, 0.0f, 1.0f,
};
matrixMultiply(mat, mat, rotate);
}
std::unordered_map<eTransform, std::array<float, 9>> transforms = {
{HYPRUTILS_TRANSFORM_NORMAL,
{
1.0f,
0.0f,
0.0f,
0.0f,
1.0f,
0.0f,
0.0f,
0.0f,
1.0f,
}},
{HYPRUTILS_TRANSFORM_90,
{
0.0f,
1.0f,
0.0f,
-1.0f,
0.0f,
0.0f,
0.0f,
0.0f,
1.0f,
}},
{HYPRUTILS_TRANSFORM_180,
{
-1.0f,
0.0f,
0.0f,
0.0f,
-1.0f,
0.0f,
0.0f,
0.0f,
1.0f,
}},
{HYPRUTILS_TRANSFORM_270,
{
0.0f,
-1.0f,
0.0f,
1.0f,
0.0f,
0.0f,
0.0f,
0.0f,
1.0f,
}},
{HYPRUTILS_TRANSFORM_FLIPPED,
{
-1.0f,
0.0f,
0.0f,
0.0f,
1.0f,
0.0f,
0.0f,
0.0f,
1.0f,
}},
{HYPRUTILS_TRANSFORM_FLIPPED_90,
{
0.0f,
1.0f,
0.0f,
1.0f,
0.0f,
0.0f,
0.0f,
0.0f,
1.0f,
}},
{HYPRUTILS_TRANSFORM_FLIPPED_180,
{
1.0f,
0.0f,
0.0f,
0.0f,
-1.0f,
0.0f,
0.0f,
0.0f,
1.0f,
}},
{HYPRUTILS_TRANSFORM_FLIPPED_270,
{
0.0f,
-1.0f,
0.0f,
-1.0f,
0.0f,
0.0f,
0.0f,
0.0f,
1.0f,
}},
};
void matrixTransform(float mat[9], eTransform transform) {
matrixMultiply(mat, mat, transforms.at(transform).data());
}
void matrixProjection(float mat[9], int width, int height, eTransform transform) {
memset(mat, 0, sizeof(*mat) * 9);
const float* t = transforms.at(transform).data();
float x = 2.0f / width;
float y = 2.0f / height;
// Rotation + reflection
mat[0] = x * t[0];
mat[1] = x * t[1];
mat[3] = y * -t[3];
mat[4] = y * -t[4];
// Translation
mat[2] = -copysign(1.0f, mat[0] + mat[1]);
mat[5] = -copysign(1.0f, mat[3] + mat[4]);
// Identity
mat[8] = 1.0f;
}
void projectBox(float mat[9], CBox& box, eTransform transform, float rotation, const float projection[9]) {
double x = box.x;
double y = box.y;
double width = box.width;
double height = box.height;
matrixIdentity(mat);
matrixTranslate(mat, x, y);
if (rotation != 0) {
matrixTranslate(mat, width / 2, height / 2);
matrixRotate(mat, rotation);
matrixTranslate(mat, -width / 2, -height / 2);
}
matrixScale(mat, width, height);
if (transform != HYPRUTILS_TRANSFORM_NORMAL) {
matrixTranslate(mat, 0.5, 0.5);
matrixTransform(mat, transform);
matrixTranslate(mat, -0.5, -0.5);
}
matrixMultiply(mat, projection, mat);
}

18
src/backend/drm/Math.hpp Normal file
View file

@ -0,0 +1,18 @@
#pragma once
// FIXME: migrate this to utils
// includes box and vector as well
#include <hyprutils/math/Region.hpp>
using namespace Hyprutils::Math;
void projectBox(float mat[9], CBox& box, eTransform transform, float rotation, const float projection[9]);
void matrixProjection(float mat[9], int width, int height, eTransform transform);
void matrixTransform(float mat[9], eTransform transform);
void matrixRotate(float mat[9], float rad);
void matrixScale(float mat[9], float x, float y);
void matrixTranslate(float mat[9], float x, float y);
void matrixTranspose(float mat[9], const float a[9]);
void matrixMultiply(float mat[9], const float a[9], const float b[9]);
void matrixIdentity(float mat[9]);

View file

@ -0,0 +1,465 @@
#include "Renderer.hpp"
#include <xf86drm.h>
#include <xf86drmMode.h>
#include <cstring>
#include <fcntl.h>
#include "Math.hpp"
#include "Shared.hpp"
using namespace Aquamarine;
using namespace Hyprutils::Memory;
using namespace Hyprutils::Math;
#define SP CSharedPointer
#define WP CWeakPointer
// static funcs
WP<CBackend> gBackend;
// ------------------- shader utils
GLuint compileShader(const GLuint& type, std::string src) {
auto shader = glCreateShader(type);
auto shaderSource = src.c_str();
glShaderSource(shader, 1, (const GLchar**)&shaderSource, nullptr);
glCompileShader(shader);
GLint ok;
glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);
if (ok == GL_FALSE)
return 0;
return shader;
}
GLuint createProgram(const std::string& vert, const std::string& frag) {
auto vertCompiled = compileShader(GL_VERTEX_SHADER, vert);
if (vertCompiled == 0)
return 0;
auto fragCompiled = compileShader(GL_FRAGMENT_SHADER, frag);
if (fragCompiled == 0)
return 0;
auto prog = glCreateProgram();
glAttachShader(prog, vertCompiled);
glAttachShader(prog, fragCompiled);
glLinkProgram(prog);
glDetachShader(prog, vertCompiled);
glDetachShader(prog, fragCompiled);
glDeleteShader(vertCompiled);
glDeleteShader(fragCompiled);
GLint ok;
glGetProgramiv(prog, GL_LINK_STATUS, &ok);
if (ok == GL_FALSE)
return 0;
return prog;
}
inline const std::string VERT_SRC = R"#(
uniform mat3 proj;
attribute vec2 pos;
attribute vec2 texcoord;
varying vec2 v_texcoord;
void main() {
gl_Position = vec4(proj * vec3(pos, 1.0), 1.0);
v_texcoord = texcoord;
})#";
inline const std::string FRAG_SRC = R"#(
precision highp float;
varying vec2 v_texcoord; // is in 0-1
uniform sampler2D tex;
void main() {
gl_FragColor = texture2D(tex, v_texcoord);
})#";
// ------------------- gbm stuff
static int openRenderNode(int drmFd) {
auto renderName = drmGetRenderDeviceNameFromFd(drmFd);
if (!renderName) {
// This can happen on split render/display platforms, fallback to
// primary node
renderName = drmGetPrimaryDeviceNameFromFd(drmFd);
if (!renderName) {
gBackend->log(AQ_LOG_ERROR, "drmRenderer: drmGetPrimaryDeviceNameFromFd failed");
return -1;
}
gBackend->log(AQ_LOG_WARNING, std::format("DRM dev {} has no render node, falling back to primary", renderName));
drmVersion* render_version = drmGetVersion(drmFd);
if (render_version && render_version->name) {
if (strcmp(render_version->name, "evdi") == 0) {
free(renderName);
renderName = (char*)malloc(sizeof(char) * 15);
strcpy(renderName, "/dev/dri/card0");
}
drmFreeVersion(render_version);
}
}
int renderFD = open(renderName, O_RDWR | O_CLOEXEC);
if (renderFD < 0)
gBackend->log(AQ_LOG_ERROR, std::format("openRenderNode failed to open drm device {}", renderName));
free(renderName);
return renderFD;
}
// ------------------- egl stuff
inline void loadGLProc(void* pProc, const char* name) {
void* proc = (void*)eglGetProcAddress(name);
if (proc == NULL) {
gBackend->log(AQ_LOG_ERROR, std::format("eglGetProcAddress({}) failed", name));
abort();
}
*(void**)pProc = proc;
}
// -------------------
SP<CDRMRenderer> CDRMRenderer::attempt(int drmfd, SP<CBackend> backend_) {
SP<CDRMRenderer> renderer = SP<CDRMRenderer>(new CDRMRenderer());
renderer->drmFD = drmfd;
renderer->backend = backend_;
gBackend = backend_;
const std::string EGLEXTENSIONS = (const char*)eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
if (!EGLEXTENSIONS.contains("KHR_platform_gbm")) {
backend_->log(AQ_LOG_ERROR, "CDRMRenderer: fail, no gbm support");
return nullptr;
}
// init gbm stuff
renderer->gbm.fd = openRenderNode(drmfd);
if (!renderer->gbm.fd) {
backend_->log(AQ_LOG_ERROR, "CDRMRenderer: fail, no gbm fd");
return nullptr;
}
renderer->gbm.device = gbm_create_device(renderer->gbm.fd);
if (!renderer->gbm.device) {
backend_->log(AQ_LOG_ERROR, "CDRMRenderer: fail, no gbm device");
return nullptr;
}
// init egl
if (eglBindAPI(EGL_OPENGL_ES_API) == EGL_FALSE) {
backend_->log(AQ_LOG_ERROR, "CDRMRenderer: fail, eglBindAPI failed");
return nullptr;
}
loadGLProc(&renderer->egl.eglGetPlatformDisplayEXT, "eglGetPlatformDisplayEXT");
loadGLProc(&renderer->egl.eglCreateImageKHR, "eglCreateImageKHR");
loadGLProc(&renderer->egl.eglDestroyImageKHR, "eglDestroyImageKHR");
loadGLProc(&renderer->egl.glEGLImageTargetTexture2DOES, "glEGLImageTargetTexture2DOES");
loadGLProc(&renderer->egl.glEGLImageTargetRenderbufferStorageOES, "glEGLImageTargetRenderbufferStorageOES");
if (!renderer->egl.eglGetPlatformDisplayEXT) {
backend_->log(AQ_LOG_ERROR, "CDRMRenderer: fail, no eglGetPlatformDisplayEXT");
return nullptr;
}
if (!renderer->egl.eglCreateImageKHR) {
backend_->log(AQ_LOG_ERROR, "CDRMRenderer: fail, no eglCreateImageKHR");
return nullptr;
}
std::vector<EGLint> attrs = {EGL_NONE};
renderer->egl.display = renderer->egl.eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_KHR, renderer->gbm.device, attrs.data());
if (renderer->egl.display == EGL_NO_DISPLAY) {
backend_->log(AQ_LOG_ERROR, "CDRMRenderer: fail, eglGetPlatformDisplayEXT failed");
return nullptr;
}
EGLint major, minor;
if (eglInitialize(renderer->egl.display, &major, &minor) == EGL_FALSE) {
backend_->log(AQ_LOG_ERROR, "CDRMRenderer: fail, eglInitialize failed");
return nullptr;
}
attrs.clear();
const std::string EGLEXTENSIONS2 = (const char*)eglQueryString(renderer->egl.display, EGL_EXTENSIONS);
if (EGLEXTENSIONS2.contains("IMG_context_priority")) {
attrs.push_back(EGL_CONTEXT_PRIORITY_LEVEL_IMG);
attrs.push_back(EGL_CONTEXT_PRIORITY_HIGH_IMG);
}
attrs.push_back(EGL_CONTEXT_MAJOR_VERSION);
attrs.push_back(2);
attrs.push_back(EGL_CONTEXT_MINOR_VERSION);
attrs.push_back(0);
attrs.push_back(EGL_CONTEXT_OPENGL_DEBUG);
attrs.push_back(EGL_FALSE);
attrs.push_back(EGL_NONE);
renderer->egl.context = eglCreateContext(renderer->egl.display, EGL_NO_CONFIG_KHR, EGL_NO_CONTEXT, attrs.data());
if (renderer->egl.context == EGL_NO_CONTEXT) {
backend_->log(AQ_LOG_ERROR, "CDRMRenderer: fail, eglCreateContext failed");
return nullptr;
}
if (EGLEXTENSIONS2.contains("IMG_context_priority")) {
EGLint priority = EGL_CONTEXT_PRIORITY_MEDIUM_IMG;
eglQueryContext(renderer->egl.display, renderer->egl.context, EGL_CONTEXT_PRIORITY_LEVEL_IMG, &priority);
if (priority != EGL_CONTEXT_PRIORITY_HIGH_IMG)
backend_->log(AQ_LOG_DEBUG, "CDRMRenderer: didnt get a high priority context");
else
backend_->log(AQ_LOG_DEBUG, "CDRMRenderer: got a high priority context");
}
// init shaders
renderer->setEGL();
renderer->gl.shader.program = createProgram(VERT_SRC, FRAG_SRC);
if (renderer->gl.shader.program == 0) {
backend_->log(AQ_LOG_ERROR, "CDRMRenderer: fail, shader failed");
return nullptr;
}
renderer->gl.shader.proj = glGetUniformLocation(renderer->gl.shader.program, "proj");
renderer->gl.shader.posAttrib = glGetAttribLocation(renderer->gl.shader.program, "pos");
renderer->gl.shader.texAttrib = glGetAttribLocation(renderer->gl.shader.program, "texcoord");
renderer->gl.shader.tex = glGetUniformLocation(renderer->gl.shader.program, "tex");
renderer->restoreEGL();
backend_->log(AQ_LOG_DEBUG, "CDRMRenderer: success");
return renderer;
}
void CDRMRenderer::setEGL() {
savedEGLState.display = eglGetCurrentDisplay();
savedEGLState.context = eglGetCurrentContext();
savedEGLState.draw = eglGetCurrentSurface(EGL_DRAW);
savedEGLState.read = eglGetCurrentSurface(EGL_READ);
if (!eglMakeCurrent(egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, egl.context))
backend->log(AQ_LOG_WARNING, "CDRMRenderer: setEGL eglMakeCurrent failed");
}
void CDRMRenderer::restoreEGL() {
EGLDisplay dpy = savedEGLState.display ? savedEGLState.display : egl.display;
// egl can't handle this
if (dpy == EGL_NO_DISPLAY)
return;
if (!eglMakeCurrent(dpy, savedEGLState.draw, savedEGLState.read, savedEGLState.context))
backend->log(AQ_LOG_WARNING, "CDRMRenderer: restoreEGL eglMakeCurrent failed");
}
EGLImageKHR CDRMRenderer::createEGLImage(const SDMABUFAttrs& attrs) {
std::vector<uint32_t> attribs;
attribs.push_back(EGL_WIDTH);
attribs.push_back(attrs.size.x);
attribs.push_back(EGL_HEIGHT);
attribs.push_back(attrs.size.y);
attribs.push_back(EGL_LINUX_DRM_FOURCC_EXT);
attribs.push_back(attrs.format);
struct {
EGLint fd;
EGLint offset;
EGLint pitch;
EGLint modlo;
EGLint modhi;
} attrNames[4] = {
{EGL_DMA_BUF_PLANE0_FD_EXT, EGL_DMA_BUF_PLANE0_OFFSET_EXT, EGL_DMA_BUF_PLANE0_PITCH_EXT, EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT},
{EGL_DMA_BUF_PLANE1_FD_EXT, EGL_DMA_BUF_PLANE1_OFFSET_EXT, EGL_DMA_BUF_PLANE1_PITCH_EXT, EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT},
{EGL_DMA_BUF_PLANE2_FD_EXT, EGL_DMA_BUF_PLANE2_OFFSET_EXT, EGL_DMA_BUF_PLANE2_PITCH_EXT, EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT},
{EGL_DMA_BUF_PLANE3_FD_EXT, EGL_DMA_BUF_PLANE3_OFFSET_EXT, EGL_DMA_BUF_PLANE3_PITCH_EXT, EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT, EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT}};
for (int i = 0; i < attrs.planes; i++) {
attribs.push_back(attrNames[i].fd);
attribs.push_back(attrs.fds[i]);
attribs.push_back(attrNames[i].offset);
attribs.push_back(attrs.offsets[i]);
attribs.push_back(attrNames[i].pitch);
attribs.push_back(attrs.strides[i]);
if (attrs.modifier != DRM_FORMAT_MOD_INVALID) { // FIXME: this will implode if we don't support mods. Does anyone not support them??
attribs.push_back(attrNames[i].modlo);
attribs.push_back(attrs.modifier & 0xFFFFFFFF);
attribs.push_back(attrNames[i].modhi);
attribs.push_back(attrs.modifier >> 32);
}
}
attribs.push_back(EGL_IMAGE_PRESERVED_KHR);
attribs.push_back(EGL_TRUE);
attribs.push_back(EGL_NONE);
EGLImageKHR image = egl.eglCreateImageKHR(egl.display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, (int*)attribs.data());
if (image == EGL_NO_IMAGE_KHR) {
backend->log(AQ_LOG_ERROR, std::format("EGL: EGLCreateImageKHR failed: {}", eglGetError()));
return EGL_NO_IMAGE_KHR;
}
return image;
}
#define GLCALL(__CALL__) \
{ \
__CALL__; \
auto err = glGetError(); \
if (err != GL_NO_ERROR) { \
backend->log(AQ_LOG_ERROR, \
std::format("[GLES] Error in call at {}@{}: 0x{:x}", __LINE__, \
([]() constexpr -> std::string { return std::string(__FILE__).substr(std::string(__FILE__).find_last_of('/') + 1); })(), err)); \
} \
}
CDRMRenderer::GLTex CDRMRenderer::glTex(Hyprutils::Memory::CSharedPointer<IBuffer> buffa) {
GLTex tex;
tex.image = createEGLImage(buffa->dmabuf());
if (tex.image == EGL_NO_IMAGE_KHR) {
backend->log(AQ_LOG_ERROR, std::format("EGL (glTex): createEGLImage failed: {}", eglGetError()));
return tex;
}
GLCALL(glGenTextures(1, &tex.texid));
GLCALL(glBindTexture(GL_TEXTURE_2D, tex.texid));
GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
GLCALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
egl.glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, tex.image);
GLCALL(glBindTexture(GL_TEXTURE_2D, 0));
return tex;
}
inline const float fullVerts[] = {
1, 0, // top right
0, 0, // top left
1, 1, // bottom right
0, 1, // bottom left
};
bool CDRMRenderer::blit(SP<IBuffer> from, SP<IBuffer> to) {
setEGL();
if (from->dmabuf().size != to->dmabuf().size) {
backend->log(AQ_LOG_ERROR, "EGL (blit): buffer sizes mismatched");
return false;
}
// firstly, get a texture from the from buffer
auto fromTex = glTex(from);
TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL (blit): fromTex id {}, image 0x{:x}", fromTex.texid, (uintptr_t)fromTex.image)));
// then, get a rbo from our to buffer
auto toDma = to->dmabuf();
auto rboImage = createEGLImage(toDma);
if (rboImage == EGL_NO_IMAGE_KHR) {
backend->log(AQ_LOG_ERROR, std::format("EGL (blit): createEGLImage failed: {}", eglGetError()));
return false;
}
TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL (blit): rboImage 0x{:x}", (uintptr_t)rboImage)));
GLuint rboID = 0, fboID = 0;
// TODO: don't spam this?
GLCALL(glGenRenderbuffers(1, &rboID));
GLCALL(glBindRenderbuffer(GL_RENDERBUFFER, rboID));
egl.glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, (GLeglImageOES)rboImage);
GLCALL(glBindRenderbuffer(GL_RENDERBUFFER, 0));
GLCALL(glGenFramebuffers(1, &fboID));
GLCALL(glBindFramebuffer(GL_FRAMEBUFFER, fboID));
GLCALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rboID));
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
backend->log(AQ_LOG_ERROR, std::format("EGL (blit): glCheckFramebufferStatus failed: {}", glGetError()));
return false;
}
GLCALL(glBindRenderbuffer(GL_RENDERBUFFER, rboID));
GLCALL(glBindFramebuffer(GL_FRAMEBUFFER, fboID));
TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL (blit): fbo {} rbo {}", fboID, rboID)));
glClearColor(0.77F, 0.F, 0.74F, 1.F);
glClear(GL_COLOR_BUFFER_BIT);
// done, let's render the texture to the rbo
CBox renderBox = {{}, toDma.size};
TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL (blit): box size {}", renderBox.size())));
float mtx[9];
float identity[9];
float monitorProj[9];
matrixIdentity(identity);
projectBox(mtx, renderBox, HYPRUTILS_TRANSFORM_NORMAL, 0, identity);
matrixProjection(monitorProj, toDma.size.x, toDma.size.y, HYPRUTILS_TRANSFORM_NORMAL);
float glMtx[9];
matrixMultiply(glMtx, monitorProj, mtx);
GLCALL(glViewport(0, 0, toDma.size.x, toDma.size.y));
GLCALL(glActiveTexture(GL_TEXTURE0));
GLCALL(glBindTexture(GL_TEXTURE_2D, fromTex.texid));
GLCALL(glUseProgram(gl.shader.program));
GLCALL(glDisable(GL_BLEND));
GLCALL(glDisable(GL_SCISSOR_TEST));
matrixTranspose(glMtx, glMtx);
GLCALL(glUniformMatrix3fv(gl.shader.proj, 1, GL_FALSE, glMtx));
GLCALL(glUniform1i(gl.shader.tex, 0));
GLCALL(glVertexAttribPointer(gl.shader.posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts));
GLCALL(glVertexAttribPointer(gl.shader.texAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts));
GLCALL(glEnableVertexAttribArray(gl.shader.posAttrib));
GLCALL(glEnableVertexAttribArray(gl.shader.texAttrib));
GLCALL(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4));
GLCALL(glDisableVertexAttribArray(gl.shader.posAttrib));
GLCALL(glDisableVertexAttribArray(gl.shader.texAttrib));
GLCALL(glBindTexture(GL_TEXTURE_2D, 0));
// rendered, cleanup
GLCALL(glBindRenderbuffer(GL_RENDERBUFFER, 0));
GLCALL(glBindFramebuffer(GL_FRAMEBUFFER, 0));
GLCALL(glDeleteTextures(1, &fromTex.texid));
GLCALL(glDeleteRenderbuffers(1, &rboID));
GLCALL(glDeleteFramebuffers(1, &fboID));
egl.eglDestroyImageKHR(egl.display, rboImage);
egl.eglDestroyImageKHR(egl.display, fromTex.image);
restoreEGL();
return true;
}

View file

@ -0,0 +1,65 @@
#pragma once
#include <aquamarine/backend/DRM.hpp>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <gbm.h>
namespace Aquamarine {
class CDRMRenderer {
public:
static Hyprutils::Memory::CSharedPointer<CDRMRenderer> attempt(int drmfd, Hyprutils::Memory::CSharedPointer<CBackend> backend_);
int drmFD = -1;
bool blit(Hyprutils::Memory::CSharedPointer<IBuffer> from, Hyprutils::Memory::CSharedPointer<IBuffer> to);
void setEGL();
void restoreEGL();
struct {
struct {
GLuint program = 0;
GLint proj = -1, tex = -1, posAttrib = -1, texAttrib = -1;
} shader;
} gl;
struct {
int fd = -1;
gbm_device* device = nullptr;
} gbm;
struct {
EGLDisplay display = nullptr;
EGLContext context = nullptr;
PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT = nullptr;
PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR = nullptr;
PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR = nullptr;
PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES = nullptr;
PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC glEGLImageTargetRenderbufferStorageOES = nullptr;
} egl;
struct {
EGLDisplay display = nullptr;
EGLContext context = nullptr;
EGLSurface draw = nullptr, read = nullptr;
} savedEGLState;
struct GLTex {
EGLImage image = nullptr;
GLuint texid = 0;
};
GLTex glTex(Hyprutils::Memory::CSharedPointer<IBuffer> buf);
private:
CDRMRenderer() = default;
EGLImageKHR createEGLImage(const SDMABUFAttrs& attrs);
Hyprutils::Memory::CWeakPointer<CBackend> backend;
};
};