diff --git a/CMakeLists.txt b/CMakeLists.txt index 245edf8..a2965f9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/include/aquamarine/backend/Backend.hpp b/include/aquamarine/backend/Backend.hpp index 8f1c558..b115fa3 100644 --- a/include/aquamarine/backend/Backend.hpp +++ b/include/aquamarine/backend/Backend.hpp @@ -123,10 +123,9 @@ namespace Aquamarine { Hyprutils::Signal::CSignal newTabletPad; } events; - Hyprutils::Memory::CSharedPointer primaryAllocator; - std::vector> allocators; - bool ready = false; - Hyprutils::Memory::CSharedPointer session; + Hyprutils::Memory::CSharedPointer primaryAllocator; + bool ready = false; + Hyprutils::Memory::CSharedPointer session; private: CBackend(); diff --git a/include/aquamarine/backend/DRM.hpp b/include/aquamarine/backend/DRM.hpp index 403fac9..7ff4ed8 100644 --- a/include/aquamarine/backend/DRM.hpp +++ b/include/aquamarine/backend/DRM.hpp @@ -13,6 +13,7 @@ namespace Aquamarine { class CDRMFB; class CDRMOutput; struct SDRMConnector; + class CDRMRenderer; typedef std::function FIdleCallback; @@ -341,7 +342,6 @@ namespace Aquamarine { std::vector idleCallbacks; std::string gpuName; - Hyprutils::Memory::CWeakPointer allocator; private: CDRMBackend(Hyprutils::Memory::CSharedPointer backend); @@ -350,15 +350,24 @@ namespace Aquamarine { bool registerGPU(Hyprutils::Memory::CSharedPointer gpu_, Hyprutils::Memory::CSharedPointer primary_ = {}); bool checkFeatures(); bool initResources(); + bool initMgpu(); bool grabFormats(); + bool shouldBlit(); void scanConnectors(); void scanLeases(); void restoreAfterVT(); void recheckCRTCs(); - Hyprutils::Memory::CSharedPointer gpu; - Hyprutils::Memory::CSharedPointer impl; - Hyprutils::Memory::CWeakPointer primary; + Hyprutils::Memory::CSharedPointer gpu; + Hyprutils::Memory::CSharedPointer impl; + Hyprutils::Memory::CWeakPointer primary; + + // multigpu state, only present if this backend is not primary, aka if this->primary != nullptr + struct { + Hyprutils::Memory::CSharedPointer allocator; + Hyprutils::Memory::CSharedPointer swapchain; + Hyprutils::Memory::CSharedPointer renderer; + } mgpu; Hyprutils::Memory::CWeakPointer backend; diff --git a/include/aquamarine/output/Output.hpp b/include/aquamarine/output/Output.hpp index 957ff41..8411422 100644 --- a/include/aquamarine/output/Output.hpp +++ b/include/aquamarine/output/Output.hpp @@ -139,10 +139,7 @@ namespace Aquamarine { std::vector> modes; Hyprutils::Memory::CSharedPointer state = Hyprutils::Memory::makeShared(); - // 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 swapchain; + Hyprutils::Memory::CSharedPointer swapchain; // diff --git a/src/backend/Backend.cpp b/src/backend/Backend.cpp index 9ba029b..b2b6abd 100644 --- a/src/backend/Backend.cpp +++ b/src/backend/Backend.cpp @@ -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; diff --git a/src/backend/drm/DRM.cpp b/src/backend/drm/DRM.cpp index 1f6ba11..58c62d9 100644 --- a/src/backend/drm/DRM.cpp +++ b/src/backend/drm/DRM.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -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(c->output)); } + + if (!initMgpu()) { + backend->log(AQ_LOG_ERROR, "drm: Failed initializing mgpu"); + return; + } } std::vector Aquamarine::CDRMBackend::getRenderFormats() { @@ -852,7 +877,7 @@ int Aquamarine::CDRMBackend::getNonMasterFD() { } SP 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 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"); diff --git a/src/backend/drm/Math.cpp b/src/backend/drm/Math.cpp new file mode 100644 index 0000000..04e4944 --- /dev/null +++ b/src/backend/drm/Math.cpp @@ -0,0 +1,207 @@ +#include "Math.hpp" +#include +#include +#include + +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> 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); +} diff --git a/src/backend/drm/Math.hpp b/src/backend/drm/Math.hpp new file mode 100644 index 0000000..7cef791 --- /dev/null +++ b/src/backend/drm/Math.hpp @@ -0,0 +1,18 @@ +#pragma once + +// FIXME: migrate this to utils + +// includes box and vector as well +#include + +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]); diff --git a/src/backend/drm/Renderer.cpp b/src/backend/drm/Renderer.cpp new file mode 100644 index 0000000..5f0ebf9 --- /dev/null +++ b/src/backend/drm/Renderer.cpp @@ -0,0 +1,465 @@ +#include "Renderer.hpp" +#include +#include +#include +#include +#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 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::attempt(int drmfd, SP backend_) { + SP renderer = SP(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 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 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 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 from, SP 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; +} diff --git a/src/backend/drm/Renderer.hpp b/src/backend/drm/Renderer.hpp new file mode 100644 index 0000000..683ddb3 --- /dev/null +++ b/src/backend/drm/Renderer.hpp @@ -0,0 +1,65 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace Aquamarine { + class CDRMRenderer { + public: + static Hyprutils::Memory::CSharedPointer attempt(int drmfd, Hyprutils::Memory::CSharedPointer backend_); + + int drmFD = -1; + + bool blit(Hyprutils::Memory::CSharedPointer from, Hyprutils::Memory::CSharedPointer 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 buf); + + private: + CDRMRenderer() = default; + + EGLImageKHR createEGLImage(const SDMABUFAttrs& attrs); + + Hyprutils::Memory::CWeakPointer backend; + }; +}; \ No newline at end of file