#include "Renderer.hpp" #include #include #include #include #include #include #include "Math.hpp" #include "Shared.hpp" #include "FormatUtils.hpp" #include #include using namespace Aquamarine; using namespace Hyprutils::Memory; using namespace Hyprutils::Math; using namespace Hyprutils::OS; #define SP CSharedPointer #define WP CWeakPointer // macros #define GLCALL(__CALL__) \ { \ __CALL__; \ if (Aquamarine::isTrace()) { \ 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)); \ } \ } \ } // static funcs static WP gBackend; // ------------------- shader utils static 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; } static 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"#( #version 300 es precision highp float; uniform mat3 proj; in vec2 pos; in vec2 texcoord; out 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"#( #version 300 es precision highp float; in vec2 v_texcoord; out vec4 fragColor; uniform sampler2D tex; void main() { fragColor = texture(tex, v_texcoord); })#"; inline const std::string FRAG_SRC_EXT = R"#( #version 300 es #extension GL_OES_EGL_image_external_essl3 : require precision highp float; in vec2 v_texcoord; out vec4 fragColor; uniform samplerExternalOES texture0; void main() { fragColor = texture(texture0, v_texcoord); })#"; // ------------------- egl stuff static inline void loadGLProc(void* pProc, const char* name) { void* proc = (void*)eglGetProcAddress(name); if (!proc) { gBackend->log(AQ_LOG_ERROR, std::format("eglGetProcAddress({}) failed, the display driver doesn't support it", name)); abort(); } *(void**)pProc = proc; } static enum eBackendLogLevel eglLogToLevel(EGLint type) { switch (type) { case EGL_DEBUG_MSG_CRITICAL_KHR: return AQ_LOG_CRITICAL; case EGL_DEBUG_MSG_ERROR_KHR: return AQ_LOG_ERROR; case EGL_DEBUG_MSG_WARN_KHR: return AQ_LOG_WARNING; case EGL_DEBUG_MSG_INFO_KHR: return AQ_LOG_DEBUG; default: return AQ_LOG_DEBUG; } } static const char* eglErrorToString(EGLint error) { switch (error) { case EGL_SUCCESS: return "EGL_SUCCESS"; case EGL_NOT_INITIALIZED: return "EGL_NOT_INITIALIZED"; case EGL_BAD_ACCESS: return "EGL_BAD_ACCESS"; case EGL_BAD_ALLOC: return "EGL_BAD_ALLOC"; case EGL_BAD_ATTRIBUTE: return "EGL_BAD_ATTRIBUTE"; case EGL_BAD_CONTEXT: return "EGL_BAD_CONTEXT"; case EGL_BAD_CONFIG: return "EGL_BAD_CONFIG"; case EGL_BAD_CURRENT_SURFACE: return "EGL_BAD_CURRENT_SURFACE"; case EGL_BAD_DISPLAY: return "EGL_BAD_DISPLAY"; case EGL_BAD_DEVICE_EXT: return "EGL_BAD_DEVICE_EXT"; case EGL_BAD_SURFACE: return "EGL_BAD_SURFACE"; case EGL_BAD_MATCH: return "EGL_BAD_MATCH"; case EGL_BAD_PARAMETER: return "EGL_BAD_PARAMETER"; case EGL_BAD_NATIVE_PIXMAP: return "EGL_BAD_NATIVE_PIXMAP"; case EGL_BAD_NATIVE_WINDOW: return "EGL_BAD_NATIVE_WINDOW"; case EGL_CONTEXT_LOST: return "EGL_CONTEXT_LOST"; default: return "Unknown"; } } static void eglLog(EGLenum error, const char* command, EGLint type, EGLLabelKHR thread, EGLLabelKHR obj, const char* msg) { gBackend->log(eglLogToLevel(type), std::format("[EGL] Command {} errored out with {} (0x{}): {}", command, eglErrorToString(error), error, msg)); } static bool drmDeviceHasName(const drmDevice* device, const std::string& name) { for (size_t i = 0; i < DRM_NODE_MAX; i++) { if (!(device->available_nodes & (1 << i))) continue; if (device->nodes[i] == name) return true; } return false; } // ------------------- CDRMRenderer::SShader::~SShader() { if (program == 0) return; if (shaderVao) glDeleteVertexArrays(1, &shaderVao); if (shaderVboPos) glDeleteBuffers(1, &shaderVboPos); if (shaderVboUv) glDeleteBuffers(1, &shaderVboUv); glDeleteProgram(program); program = 0; } void CDRMRenderer::SShader::createVao() { const float fullVerts[] = { 1, 0, // top right 0, 0, // top left 1, 1, // bottom right 0, 1, // bottom left }; glGenVertexArrays(1, &shaderVao); glBindVertexArray(shaderVao); if (posAttrib != -1) { glGenBuffers(1, &shaderVboPos); glBindBuffer(GL_ARRAY_BUFFER, shaderVboPos); glBufferData(GL_ARRAY_BUFFER, sizeof(fullVerts), fullVerts, GL_STATIC_DRAW); glEnableVertexAttribArray(posAttrib); glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, 0, nullptr); } if (texAttrib != -1) { glGenBuffers(1, &shaderVboUv); glBindBuffer(GL_ARRAY_BUFFER, shaderVboUv); glBufferData(GL_ARRAY_BUFFER, sizeof(fullVerts), fullVerts, GL_STATIC_DRAW); glEnableVertexAttribArray(texAttrib); glVertexAttribPointer(texAttrib, 2, GL_FLOAT, GL_FALSE, 0, nullptr); } glBindVertexArray(0); glBindBuffer(GL_ARRAY_BUFFER, 0); } EGLDeviceEXT CDRMRenderer::eglDeviceFromDRMFD(int drmFD) { EGLint nDevices = 0; if (!proc.eglQueryDevicesEXT(0, nullptr, &nDevices)) { backend->log(AQ_LOG_ERROR, "CDRMRenderer(drm): eglQueryDevicesEXT failed"); return EGL_NO_DEVICE_EXT; } if (nDevices <= 0) { backend->log(AQ_LOG_ERROR, "CDRMRenderer(drm): no devices"); return EGL_NO_DEVICE_EXT; } std::vector devices; devices.resize(nDevices); if (!proc.eglQueryDevicesEXT(nDevices, devices.data(), &nDevices)) { backend->log(AQ_LOG_ERROR, "CDRMRenderer(drm): eglQueryDevicesEXT failed (2)"); return EGL_NO_DEVICE_EXT; } drmDevice* drmDev = nullptr; if (int ret = drmGetDevice(drmFD, &drmDev); ret < 0) { backend->log(AQ_LOG_ERROR, "CDRMRenderer(drm): drmGetDevice failed"); drmFreeDevice(&drmDev); return EGL_NO_DEVICE_EXT; } for (auto const& d : devices) { auto devName = proc.eglQueryDeviceStringEXT(d, EGL_DRM_DEVICE_FILE_EXT); if (!devName) continue; if (drmDeviceHasName(drmDev, devName)) { backend->log(AQ_LOG_DEBUG, std::format("CDRMRenderer(drm): Using device {}", devName)); drmFreeDevice(&drmDev); return d; } } drmFreeDevice(&drmDev); return EGL_NO_DEVICE_EXT; } std::optional>> CDRMRenderer::getModsForFormat(EGLint format) { // TODO: return std::expected when clang supports it EGLint len = 0; if (!proc.eglQueryDmaBufModifiersEXT(egl.display, format, 0, nullptr, nullptr, &len)) { backend->log(AQ_LOG_ERROR, std::format("EGL: eglQueryDmaBufModifiersEXT failed for format {}", fourccToName(format))); return std::nullopt; } if (len <= 0) return std::vector>{}; std::vector mods; std::vector external; mods.resize(len); external.resize(len); proc.eglQueryDmaBufModifiersEXT(egl.display, format, len, mods.data(), external.data(), &len); std::vector> result; result.reserve(mods.size()); bool linearIsExternal = false; for (size_t i = 0; i < mods.size(); ++i) { if (external.at(i) && mods.at(i) == DRM_FORMAT_MOD_LINEAR) linearIsExternal = true; result.emplace_back(mods.at(i), external.at(i)); } // if the driver doesn't mark linear as external, add it. It's allowed unless the driver says otherwise. (e.g. nvidia) if (!linearIsExternal && std::ranges::find(mods, DRM_FORMAT_MOD_LINEAR) == mods.end()) result.emplace_back(DRM_FORMAT_MOD_LINEAR, true); return result; } void CDRMRenderer::useProgram(GLuint prog) { if (m_currentProgram == prog) return; GLCALL(glUseProgram(prog)); } bool CDRMRenderer::initDRMFormats() { std::vector formats; EGLint len = 0; proc.eglQueryDmaBufFormatsEXT(egl.display, 0, nullptr, &len); formats.resize(len); proc.eglQueryDmaBufFormatsEXT(egl.display, len, formats.data(), &len); if (formats.empty()) { backend->log(AQ_LOG_ERROR, "EGL: Failed to get formats"); return false; } TRACE(backend->log(AQ_LOG_TRACE, "EGL: Supported formats:")); std::vector dmaFormats; dmaFormats.reserve(formats.size()); for (auto const& fmt : formats) { std::vector> mods; if (exts.EXT_image_dma_buf_import_modifiers) { auto ret = getModsForFormat(fmt); if (!ret.has_value()) continue; mods = *ret; } hasModifiers = hasModifiers || !mods.empty(); // EGL can always do implicit modifiers. mods.emplace_back(DRM_FORMAT_MOD_INVALID, true); for (auto const& [mod, external] : mods) { dmaFormats.push_back(SGLFormat{ .drmFormat = (uint32_t)fmt, .modifier = mod, .external = external, }); } TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL: GPU Supports Format {} (0x{:x})", fourccToName((uint32_t)fmt), fmt))); for (auto const& [mod, external] : mods) { auto modName = drmGetFormatModifierName(mod); TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL: | {}with modifier 0x{:x}: {}", (external ? "external only " : ""), mod, modName ? modName : "?unknown?"))); free(modName); } } TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL: Found {} formats", dmaFormats.size()))); if (dmaFormats.empty()) { backend->log(AQ_LOG_ERROR, "EGL: No formats"); return false; } this->formats = dmaFormats; return true; } Aquamarine::CDRMRenderer::~CDRMRenderer() { if (egl.display) eglMakeCurrent(egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); if (egl.display && egl.context != EGL_NO_CONTEXT && egl.context != nullptr) eglDestroyContext(egl.display, egl.context); if (egl.display) eglTerminate(egl.display); eglReleaseThread(); } void CDRMRenderer::loadEGLAPI() { const std::string EGLEXTENSIONS = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); backend->log(AQ_LOG_DEBUG, std::format("Supported EGL client extensions: ({}) {}", std::count(EGLEXTENSIONS.begin(), EGLEXTENSIONS.end(), ' '), EGLEXTENSIONS)); exts.KHR_display_reference = EGLEXTENSIONS.contains("KHR_display_reference"); exts.EXT_platform_device = EGLEXTENSIONS.contains("EXT_platform_device"); exts.KHR_platform_gbm = EGLEXTENSIONS.contains("KHR_platform_gbm"); loadGLProc(&proc.eglGetPlatformDisplayEXT, "eglGetPlatformDisplayEXT"); loadGLProc(&proc.eglCreateImageKHR, "eglCreateImageKHR"); loadGLProc(&proc.eglDestroyImageKHR, "eglDestroyImageKHR"); loadGLProc(&proc.eglQueryDmaBufFormatsEXT, "eglQueryDmaBufFormatsEXT"); loadGLProc(&proc.eglQueryDmaBufModifiersEXT, "eglQueryDmaBufModifiersEXT"); loadGLProc(&proc.glEGLImageTargetTexture2DOES, "glEGLImageTargetTexture2DOES"); loadGLProc(&proc.glEGLImageTargetRenderbufferStorageOES, "glEGLImageTargetRenderbufferStorageOES"); loadGLProc(&proc.eglDestroySyncKHR, "eglDestroySyncKHR"); loadGLProc(&proc.eglWaitSyncKHR, "eglWaitSyncKHR"); loadGLProc(&proc.eglCreateSyncKHR, "eglCreateSyncKHR"); loadGLProc(&proc.eglDupNativeFenceFDANDROID, "eglDupNativeFenceFDANDROID"); loadGLProc(&proc.glReadnPixelsEXT, "glReadnPixelsEXT"); if (EGLEXTENSIONS.contains("EGL_EXT_device_base") || EGLEXTENSIONS.contains("EGL_EXT_device_enumeration")) loadGLProc(&proc.eglQueryDevicesEXT, "eglQueryDevicesEXT"); if (EGLEXTENSIONS.contains("EGL_EXT_device_base") || EGLEXTENSIONS.contains("EGL_EXT_device_query")) loadGLProc(&proc.eglQueryDeviceStringEXT, "eglQueryDeviceStringEXT"); if (EGLEXTENSIONS.contains("EGL_KHR_debug")) { loadGLProc(&proc.eglDebugMessageControlKHR, "eglDebugMessageControlKHR"); static const EGLAttrib debugAttrs[] = { EGL_DEBUG_MSG_CRITICAL_KHR, EGL_TRUE, EGL_DEBUG_MSG_ERROR_KHR, EGL_TRUE, EGL_DEBUG_MSG_WARN_KHR, EGL_TRUE, EGL_DEBUG_MSG_INFO_KHR, EGL_TRUE, EGL_NONE, }; proc.eglDebugMessageControlKHR(::eglLog, debugAttrs); } if (EGLEXTENSIONS.contains("EXT_platform_device")) { loadGLProc(&proc.eglQueryDevicesEXT, "eglQueryDevicesEXT"); loadGLProc(&proc.eglQueryDeviceStringEXT, "eglQueryDeviceStringEXT"); } RASSERT(eglBindAPI(EGL_OPENGL_ES_API) != EGL_FALSE, "Couldn't bind to EGL's opengl ES API. This means your gpu driver f'd up. This is not a Hyprland or Aquamarine issue."); } void CDRMRenderer::initContext() { RASSERT(egl.display != nullptr && egl.display != EGL_NO_DISPLAY, "CDRMRenderer: Can't create EGL context without display"); EGLint major, minor; if (eglInitialize(egl.display, &major, &minor) == EGL_FALSE) { backend->log(AQ_LOG_ERROR, "CDRMRenderer: fail, eglInitialize failed"); return; } std::string EGLEXTENSIONS = eglQueryString(egl.display, EGL_EXTENSIONS); exts.IMG_context_priority = EGLEXTENSIONS.contains("IMG_context_priority"); exts.EXT_create_context_robustness = EGLEXTENSIONS.contains("EXT_create_context_robustness"); exts.EXT_image_dma_buf_import = EGLEXTENSIONS.contains("EXT_image_dma_buf_import"); exts.EXT_image_dma_buf_import_modifiers = EGLEXTENSIONS.contains("EXT_image_dma_buf_import_modifiers"); std::vector attrs; if (exts.IMG_context_priority) { backend->log(AQ_LOG_DEBUG, "CDRMRenderer: IMG_context_priority supported, requesting high"); attrs.push_back(EGL_CONTEXT_PRIORITY_LEVEL_IMG); attrs.push_back(EGL_CONTEXT_PRIORITY_HIGH_IMG); } if (exts.EXT_create_context_robustness) { backend->log(AQ_LOG_DEBUG, "CDRMRenderer: EXT_create_context_robustness supported, requesting lose on reset"); attrs.push_back(EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT); attrs.push_back(EGL_LOSE_CONTEXT_ON_RESET_EXT); } attrs.push_back(EGL_CONTEXT_OPENGL_DEBUG); attrs.push_back(Aquamarine::isTrace() ? EGL_TRUE : EGL_FALSE); auto attrsNoVer = attrs; attrs.push_back(EGL_CONTEXT_MAJOR_VERSION); attrs.push_back(3); attrs.push_back(EGL_CONTEXT_MINOR_VERSION); attrs.push_back(2); attrs.push_back(EGL_NONE); egl.context = eglCreateContext(egl.display, EGL_NO_CONFIG_KHR, EGL_NO_CONTEXT, attrs.data()); if (egl.context == EGL_NO_CONTEXT) { backend->log(AQ_LOG_ERROR, "CDRMRenderer: eglCreateContext failed with GLES 3.2, retrying GLES 3.0"); attrs = attrsNoVer; attrs.push_back(EGL_CONTEXT_MAJOR_VERSION); attrs.push_back(3); attrs.push_back(EGL_CONTEXT_MINOR_VERSION); attrs.push_back(0); attrs.push_back(EGL_NONE); egl.context = eglCreateContext(egl.display, EGL_NO_CONFIG_KHR, EGL_NO_CONTEXT, attrs.data()); if (egl.context == EGL_NO_CONTEXT) { backend->log(AQ_LOG_ERROR, "CDRMRenderer: Can't create renderer, eglCreateContext failed with both GLES 3.2 and GLES 3.0"); return; } } if (exts.IMG_context_priority) { EGLint priority = EGL_CONTEXT_PRIORITY_MEDIUM_IMG; eglQueryContext(egl.display, egl.context, EGL_CONTEXT_PRIORITY_LEVEL_IMG, &priority); if (priority != EGL_CONTEXT_PRIORITY_HIGH_IMG) backend->log(AQ_LOG_DEBUG, "CDRMRenderer: Failed to get a high priority context"); else backend->log(AQ_LOG_DEBUG, "CDRMRenderer: Got a high priority context"); } CEglContextGuard eglContext(*this); EGLEXTENSIONS = (const char*)glGetString(GL_EXTENSIONS); std::string gpuName = "unknown"; char* drmName = drmGetDeviceNameFromFd2(drmFD); if (drmName != nullptr) { gpuName = std::string{drmName}; free(drmName); } backend->log(AQ_LOG_DEBUG, std::format("Creating CDRMRenderer on gpu {}", gpuName)); backend->log(AQ_LOG_DEBUG, std::format("Using: {}", (char*)glGetString(GL_VERSION))); backend->log(AQ_LOG_DEBUG, std::format("Vendor: {}", (char*)glGetString(GL_VENDOR))); backend->log(AQ_LOG_DEBUG, std::format("Renderer: {}", (char*)glGetString(GL_RENDERER))); backend->log(AQ_LOG_DEBUG, std::format("Supported context extensions: ({}) {}", std::count(EGLEXTENSIONS.begin(), EGLEXTENSIONS.end(), ' '), EGLEXTENSIONS)); exts.EXT_read_format_bgra = EGLEXTENSIONS.contains("GL_EXT_read_format_bgra"); exts.EXT_texture_format_BGRA8888 = EGLEXTENSIONS.contains("GL_EXT_texture_format_BGRA8888"); } void CDRMRenderer::initResources() { CEglContextGuard eglContext(*this); if (!exts.EXT_image_dma_buf_import || !initDRMFormats()) backend->log(AQ_LOG_ERROR, "CDRMRenderer: initDRMFormats failed, dma-buf won't work"); shader.program = createProgram(VERT_SRC, FRAG_SRC); if (shader.program == 0) backend->log(AQ_LOG_ERROR, "CDRMRenderer: texture shader failed"); shader.proj = glGetUniformLocation(shader.program, "proj"); shader.posAttrib = glGetAttribLocation(shader.program, "pos"); shader.texAttrib = glGetAttribLocation(shader.program, "texcoord"); shader.tex = glGetUniformLocation(shader.program, "tex"); shader.createVao(); shaderExt.program = createProgram(VERT_SRC, FRAG_SRC_EXT); if (shaderExt.program == 0) backend->log(AQ_LOG_ERROR, "CDRMRenderer: external texture shader failed"); shaderExt.proj = glGetUniformLocation(shaderExt.program, "proj"); shaderExt.posAttrib = glGetAttribLocation(shaderExt.program, "pos"); shaderExt.texAttrib = glGetAttribLocation(shaderExt.program, "texcoord"); shaderExt.tex = glGetUniformLocation(shaderExt.program, "tex"); shaderExt.createVao(); } SP CDRMRenderer::attempt(SP backend_, int drmFD) { SP renderer = SP(new CDRMRenderer()); renderer->drmFD = drmFD; renderer->backend = backend_; gBackend = backend_; renderer->loadEGLAPI(); if (!renderer->exts.EXT_platform_device) { backend_->log(AQ_LOG_ERROR, "CDRMRenderer(drm): Can't create renderer, EGL doesn't support EXT_platform_device"); return nullptr; } EGLDeviceEXT device = renderer->eglDeviceFromDRMFD(drmFD); if (device == EGL_NO_DEVICE_EXT) { backend_->log(AQ_LOG_ERROR, "CDRMRenderer(drm): Can't create renderer, no matching devices found"); return nullptr; } std::vector attrs; if (renderer->exts.KHR_display_reference) { attrs.push_back(EGL_TRACK_REFERENCES_KHR); attrs.push_back(EGL_TRUE); } attrs.push_back(EGL_NONE); renderer->egl.display = renderer->proc.eglGetPlatformDisplayEXT(EGL_PLATFORM_DEVICE_EXT, device, attrs.data()); if (renderer->egl.display == EGL_NO_DISPLAY) { backend_->log(AQ_LOG_ERROR, "CDRMRenderer: fail, eglGetPlatformDisplayEXT failed"); return nullptr; } renderer->initContext(); if (renderer->egl.context == nullptr || renderer->egl.context == EGL_NO_CONTEXT) return nullptr; renderer->initResources(); return renderer; } SP CDRMRenderer::attempt(SP backend_, Hyprutils::Memory::CSharedPointer allocator_) { SP renderer = SP(new CDRMRenderer()); renderer->drmFD = allocator_->drmFD(); renderer->backend = backend_; gBackend = backend_; renderer->loadEGLAPI(); if (!renderer->exts.KHR_platform_gbm) { backend_->log(AQ_LOG_ERROR, "CDRMRenderer(gbm): Can't create renderer, EGL doesn't support KHR_platform_gbm"); return nullptr; } std::vector attrs; if (renderer->exts.KHR_display_reference) { attrs.push_back(EGL_TRACK_REFERENCES_KHR); attrs.push_back(EGL_TRUE); } attrs.push_back(EGL_NONE); renderer->egl.display = renderer->proc.eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_KHR, allocator_->gbmDevice, attrs.data()); if (renderer->egl.display == EGL_NO_DISPLAY) { backend_->log(AQ_LOG_ERROR, "CDRMRenderer: fail, eglGetPlatformDisplayEXT failed"); return nullptr; } renderer->initContext(); if (renderer->egl.context == nullptr || renderer->egl.context == EGL_NO_CONTEXT) return nullptr; renderer->initResources(); return renderer; } CEglContextGuard::CEglContextGuard(const CDRMRenderer& renderer_) : renderer(renderer_) { savedEGLState.display = eglGetCurrentDisplay(); savedEGLState.context = eglGetCurrentContext(); savedEGLState.draw = eglGetCurrentSurface(EGL_DRAW); savedEGLState.read = eglGetCurrentSurface(EGL_READ); if (!eglMakeCurrent(renderer.egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, renderer.egl.context)) renderer.backend->log(AQ_LOG_WARNING, "CDRMRenderer: setEGL eglMakeCurrent failed"); } CEglContextGuard::~CEglContextGuard() { EGLDisplay dpy = savedEGLState.display ? savedEGLState.display : renderer.egl.display; // egl can't handle this if (dpy == EGL_NO_DISPLAY) return; if (!eglMakeCurrent(dpy, savedEGLState.draw, savedEGLState.read, savedEGLState.context)) renderer.backend->log(AQ_LOG_WARNING, "CDRMRenderer: restoreEGL eglMakeCurrent failed"); } EGLImageKHR CDRMRenderer::createEGLImage(const SDMABUFAttrs& attrs) { std::array attribs; size_t idx = 0; attribs[idx++] = EGL_WIDTH; attribs[idx++] = attrs.size.x; attribs[idx++] = EGL_HEIGHT; attribs[idx++] = attrs.size.y; attribs[idx++] = EGL_LINUX_DRM_FOURCC_EXT; attribs[idx++] = attrs.format; TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL: createEGLImage: size {} with format {} and modifier 0x{:x}", attrs.size, fourccToName(attrs.format), attrs.modifier))); struct { EGLint fd; EGLint offset; EGLint pitch; EGLint modlo; EGLint modhi; } attrNames[4] = {{.fd = EGL_DMA_BUF_PLANE0_FD_EXT, .offset = EGL_DMA_BUF_PLANE0_OFFSET_EXT, .pitch = EGL_DMA_BUF_PLANE0_PITCH_EXT, .modlo = EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, .modhi = EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT}, {.fd = EGL_DMA_BUF_PLANE1_FD_EXT, .offset = EGL_DMA_BUF_PLANE1_OFFSET_EXT, .pitch = EGL_DMA_BUF_PLANE1_PITCH_EXT, .modlo = EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT, .modhi = EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT}, {.fd = EGL_DMA_BUF_PLANE2_FD_EXT, .offset = EGL_DMA_BUF_PLANE2_OFFSET_EXT, .pitch = EGL_DMA_BUF_PLANE2_PITCH_EXT, .modlo = EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT, .modhi = EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT}, {.fd = EGL_DMA_BUF_PLANE3_FD_EXT, .offset = EGL_DMA_BUF_PLANE3_OFFSET_EXT, .pitch = EGL_DMA_BUF_PLANE3_PITCH_EXT, .modlo = EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT, .modhi = EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT}}; for (int i = 0; i < attrs.planes; i++) { attribs[idx++] = attrNames[i].fd; attribs[idx++] = attrs.fds[i]; attribs[idx++] = attrNames[i].offset; attribs[idx++] = attrs.offsets[i]; attribs[idx++] = attrNames[i].pitch; attribs[idx++] = attrs.strides[i]; if (hasModifiers && attrs.modifier != DRM_FORMAT_MOD_INVALID) { attribs[idx++] = attrNames[i].modlo; attribs[idx++] = attrs.modifier & 0xFFFFFFFF; attribs[idx++] = attrNames[i].modhi; attribs[idx++] = attrs.modifier >> 32; } } attribs[idx++] = EGL_IMAGE_PRESERVED_KHR; attribs[idx++] = EGL_TRUE; attribs[idx++] = EGL_NONE; RASSERT(idx <= attribs.size(), "EGL: EGLCreateImageKHR: attribs array out of bounds."); EGLImageKHR image = proc.eglCreateImageKHR(egl.display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, 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; } CGLTex CDRMRenderer::glTex(Hyprutils::Memory::CSharedPointer buffa) { CGLTex tex; const auto& dma = buffa->dmabuf(); tex.image = createEGLImage(dma); if (tex.image == EGL_NO_IMAGE_KHR) { backend->log(AQ_LOG_ERROR, std::format("EGL (glTex): createEGLImage failed: {}", eglGetError())); return tex; } bool external = false; for (auto const& fmt : formats) { if (fmt.drmFormat != dma.format || fmt.modifier != dma.modifier) continue; backend->log(AQ_LOG_DEBUG, std::format("CDRMRenderer::glTex: found format+mod, external = {}", fmt.external)); external = fmt.external; break; } tex.target = external ? GL_TEXTURE_EXTERNAL_OES : GL_TEXTURE_2D; GLCALL(glGenTextures(1, &tex.texid)); GLCALL(tex.bind()); GLCALL(tex.setTexParameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); GLCALL(tex.setTexParameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); GLCALL(proc.glEGLImageTargetTexture2DOES(tex.target, tex.image)); GLCALL(tex.unbind()); return tex; } constexpr GLenum PIXEL_BUFFER_FORMAT = GL_RGBA; void CDRMRenderer::readBuffer(Hyprutils::Memory::CSharedPointer buf, std::span out) { CEglContextGuard eglContext(*this); auto att = buf->attachments.get(); if (!att) { att = makeShared(self, buf, nullptr, 0, 0, CGLTex{}, std::vector()); buf->attachments.add(att); } const auto& dma = buf->dmabuf(); if (!att->eglImage) { att->eglImage = createEGLImage(dma); if (att->eglImage == EGL_NO_IMAGE_KHR) { backend->log(AQ_LOG_ERROR, std::format("EGL (readBuffer): createEGLImage failed: {}", eglGetError())); return; } GLCALL(glGenRenderbuffers(1, &att->rbo)); GLCALL(glBindRenderbuffer(GL_RENDERBUFFER, att->rbo)); GLCALL(proc.glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, (GLeglImageOES)att->eglImage)); GLCALL(glBindRenderbuffer(GL_RENDERBUFFER, 0)); GLCALL(glGenFramebuffers(1, &att->fbo)); GLCALL(glBindFramebuffer(GL_FRAMEBUFFER, att->fbo)); GLCALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, att->rbo)); if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { backend->log(AQ_LOG_ERROR, std::format("EGL (readBuffer): glCheckFramebufferStatus failed: {}", glGetError())); return; } } GLCALL(glBindFramebuffer(GL_FRAMEBUFFER, att->fbo)); GLCALL(proc.glReadnPixelsEXT(0, 0, dma.size.x, dma.size.y, GL_RGBA, GL_UNSIGNED_BYTE, out.size(), out.data())); GLCALL(glBindFramebuffer(GL_FRAMEBUFFER, 0)); } void CDRMRenderer::waitOnSync(int fd) { TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL (waitOnSync): attempting to wait on fd {}", fd))); std::array attribs; int dupFd = fcntl(fd, F_DUPFD_CLOEXEC, 0); if (dupFd < 0) { backend->log(AQ_LOG_TRACE, "EGL (waitOnSync): failed to dup fd for wait"); return; } attribs[0] = EGL_SYNC_NATIVE_FENCE_FD_ANDROID; attribs[1] = dupFd; attribs[2] = EGL_NONE; EGLSyncKHR sync = proc.eglCreateSyncKHR(egl.display, EGL_SYNC_NATIVE_FENCE_ANDROID, attribs.data()); if (sync == EGL_NO_SYNC_KHR) { TRACE(backend->log(AQ_LOG_TRACE, "EGL (waitOnSync): failed to create an egl sync for explicit")); if (dupFd >= 0) close(dupFd); return; } // we got a sync, now we just tell egl to wait before sampling if (proc.eglWaitSyncKHR(egl.display, sync, 0) != EGL_TRUE) { if (proc.eglDestroySyncKHR(egl.display, sync) != EGL_TRUE) TRACE(backend->log(AQ_LOG_TRACE, "EGL (waitOnSync): failed to destroy sync")); TRACE(backend->log(AQ_LOG_TRACE, "EGL (waitOnSync): failed to wait on the sync object")); return; } if (proc.eglDestroySyncKHR(egl.display, sync) != EGL_TRUE) TRACE(backend->log(AQ_LOG_TRACE, "EGL (waitOnSync): failed to destroy sync")); } int CDRMRenderer::recreateBlitSync() { TRACE(backend->log(AQ_LOG_TRACE, "EGL (recreateBlitSync): recreating blit sync")); if (egl.lastBlitSync) { TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL (recreateBlitSync): cleaning up old sync (fd {})", egl.lastBlitSyncFD))); // cleanup last sync if (proc.eglDestroySyncKHR(egl.display, egl.lastBlitSync) != EGL_TRUE) TRACE(backend->log(AQ_LOG_TRACE, "EGL (recreateBlitSync): failed to destroy old sync")); if (egl.lastBlitSyncFD >= 0) close(egl.lastBlitSyncFD); egl.lastBlitSyncFD = -1; egl.lastBlitSync = nullptr; } EGLSyncKHR sync = proc.eglCreateSyncKHR(egl.display, EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr); if (sync == EGL_NO_SYNC_KHR) { TRACE(backend->log(AQ_LOG_TRACE, "EGL (recreateBlitSync): failed to create an egl sync for explicit")); return -1; } // we need to flush otherwise we might not get a valid fd glFlush(); int fd = proc.eglDupNativeFenceFDANDROID(egl.display, sync); if (fd == EGL_NO_NATIVE_FENCE_FD_ANDROID) { TRACE(backend->log(AQ_LOG_TRACE, "EGL (recreateBlitSync): failed to dup egl fence fd")); if (proc.eglDestroySyncKHR(egl.display, sync) != EGL_TRUE) TRACE(backend->log(AQ_LOG_TRACE, "EGL (recreateBlitSync): failed to destroy new sync")); return -1; } egl.lastBlitSync = sync; egl.lastBlitSyncFD = fd; TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL (recreateBlitSync): success, new fence exported with fd {}", fd))); return fd; } void CDRMRenderer::clearBuffer(IBuffer* buf) { CEglContextGuard eglContext(*this); const auto& dmabuf = buf->dmabuf(); GLuint rboID = 0, fboID = 0; if (!dmabuf.success) { backend->log(AQ_LOG_ERROR, "EGL (clear): cannot clear a non-dmabuf"); return; } auto rboImage = createEGLImage(dmabuf); if (rboImage == EGL_NO_IMAGE_KHR) { backend->log(AQ_LOG_ERROR, std::format("EGL (clear): createEGLImage failed: {}", eglGetError())); return; } GLCALL(glGenRenderbuffers(1, &rboID)); GLCALL(glBindRenderbuffer(GL_RENDERBUFFER, rboID)); GLCALL(proc.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)); GLCALL(glBindRenderbuffer(GL_RENDERBUFFER, rboID)); GLCALL(glBindFramebuffer(GL_FRAMEBUFFER, fboID)); TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL (clear): fbo {} rbo {}", fboID, rboID))); glClearColor(0.F, 0.F, 0.F, 1.F); glClear(GL_COLOR_BUFFER_BIT); GLCALL(glBindFramebuffer(GL_FRAMEBUFFER, 0)); GLCALL(glBindRenderbuffer(GL_RENDERBUFFER, 0)); glDeleteFramebuffers(1, &fboID); glDeleteRenderbuffers(1, &rboID); proc.eglDestroyImageKHR(egl.display, rboImage); } CDRMRenderer::SBlitResult CDRMRenderer::blit(SP from, SP to, SP primaryRenderer, int waitFD) { CEglContextGuard eglContext(*this); if (from->dmabuf().size != to->dmabuf().size) { backend->log(AQ_LOG_ERROR, "EGL (blit): buffer sizes mismatched"); return {}; } if (waitFD >= 0 && !CFileDescriptor::isReadable(waitFD)) { // wait on a provided explicit fence waitOnSync(waitFD); } // firstly, get a texture from the from buffer // if it has an attachment, use that // both from and to have the same AQ_ATTACHMENT_DRM_RENDERER_DATA. // Those buffers always come from different swapchains, so it's OK. WP fromTex; const auto& fromDma = from->dmabuf(); std::span intermediateBuf; { auto attachment = from->attachments.get(); if (attachment) { TRACE(backend->log(AQ_LOG_TRACE, "EGL (blit): From attachment found")); fromTex = attachment->tex; intermediateBuf = attachment->intermediateBuf; } if ((!fromTex || !fromTex->image) && intermediateBuf.empty()) { backend->log(AQ_LOG_DEBUG, "EGL (blit): No attachment in from, creating a new image"); attachment = makeShared(self, from, nullptr, 0, 0, glTex(from), std::vector()); from->attachments.add(attachment); if (!attachment->tex->image && primaryRenderer) { backend->log(AQ_LOG_DEBUG, "EGL (blit): Failed to create image from source buffer directly, allocating intermediate buffer"); static_assert(PIXEL_BUFFER_FORMAT == GL_RGBA); // If the pixel buffer format changes, the below size calculation probably needs to as well. attachment->intermediateBuf.resize(fromDma.size.x * fromDma.size.y * 4); intermediateBuf = attachment->intermediateBuf; attachment->tex->target = GL_TEXTURE_2D; GLCALL(glGenTextures(1, &attachment->tex->texid)); } fromTex = attachment->tex; } if (!intermediateBuf.empty() && primaryRenderer) { // Note: this might modify from's attachments primaryRenderer->readBuffer(from, intermediateBuf); } } TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL (blit): fromTex id {}, image 0x{:x}, target {}", fromTex->texid, (uintptr_t)fromTex->image, fromTex->target == GL_TEXTURE_2D ? "GL_TEXTURE_2D" : "GL_TEXTURE_EXTERNAL_OES"))); // then, get a rbo from our to buffer // if it has an attachment, use that EGLImageKHR rboImage = nullptr; GLuint rboID = 0, fboID = 0; const auto& toDma = to->dmabuf(); if (!verifyDestinationDMABUF(toDma)) { backend->log(AQ_LOG_ERROR, "EGL (blit): failed to blit: destination dmabuf unsupported"); return {}; } { auto attachment = to->attachments.get(); if (attachment) { TRACE(backend->log(AQ_LOG_TRACE, "EGL (blit): To attachment found")); rboImage = attachment->eglImage; fboID = attachment->fbo; rboID = attachment->rbo; } if (!rboImage) { backend->log(AQ_LOG_DEBUG, "EGL (blit): No attachment in to, creating a new image"); rboImage = createEGLImage(toDma); if (rboImage == EGL_NO_IMAGE_KHR) { backend->log(AQ_LOG_ERROR, std::format("EGL (blit): createEGLImage failed: {}", eglGetError())); return {}; } GLCALL(glGenRenderbuffers(1, &rboID)); GLCALL(glBindRenderbuffer(GL_RENDERBUFFER, rboID)); GLCALL(proc.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 {}; } to->attachments.add(makeShared(self, to, rboImage, fboID, rboID, CGLTex{}, std::vector())); } } TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL (blit): rboImage 0x{:x}", (uintptr_t)rboImage))); 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 base[9]; float monitorProj[9]; matrixIdentity(base); auto& SHADER = fromTex->target == GL_TEXTURE_2D ? shader : shaderExt; // KMS uses flipped y, we have to do FLIPPED_180 matrixTranslate(base, toDma.size.x / 2.0, toDma.size.y / 2.0); matrixTransform(base, HYPRUTILS_TRANSFORM_FLIPPED_180); matrixTranslate(base, -toDma.size.x / 2.0, -toDma.size.y / 2.0); projectBox(mtx, renderBox, HYPRUTILS_TRANSFORM_FLIPPED_180, 0, base); matrixProjection(monitorProj, toDma.size.x, toDma.size.y, HYPRUTILS_TRANSFORM_FLIPPED_180); float glMtx[9]; matrixMultiply(glMtx, monitorProj, mtx); static Vector2D lastViewportSize = {-1, -1}; if (lastViewportSize != toDma.size) { GLCALL(glViewport(0, 0, toDma.size.x, toDma.size.y)); lastViewportSize = toDma.size; } GLCALL(glActiveTexture(GL_TEXTURE0)); GLCALL(fromTex->bind()); GLCALL(fromTex->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST)); GLCALL(fromTex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST)); if (!intermediateBuf.empty()) GLCALL(glTexImage2D(fromTex->target, 0, PIXEL_BUFFER_FORMAT, fromDma.size.x, fromDma.size.y, 0, PIXEL_BUFFER_FORMAT, GL_UNSIGNED_BYTE, intermediateBuf.data())); useProgram(SHADER.program); GLCALL(glDisable(GL_BLEND)); GLCALL(glDisable(GL_SCISSOR_TEST)); matrixTranspose(glMtx, glMtx); GLCALL(glUniformMatrix3fv(SHADER.proj, 1, GL_FALSE, glMtx)); GLCALL(glUniform1i(SHADER.tex, 0)); GLCALL(glBindVertexArray(SHADER.shaderVao)); GLCALL(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)); GLCALL(glBindVertexArray(0)); GLCALL(fromTex->unbind()); // get an explicit sync fd for the secondary gpu. // when we pass buffers between gpus we should always use explicit sync, // as implicit is not guaranteed at all int explicitFD = recreateBlitSync(); GLCALL(glBindFramebuffer(GL_FRAMEBUFFER, 0)); GLCALL(glBindRenderbuffer(GL_RENDERBUFFER, 0)); return {.success = true, .syncFD = explicitFD == -1 ? std::nullopt : std::optional{explicitFD}}; } void CDRMRenderer::onBufferAttachmentDrop(CDRMRendererBufferAttachment* attachment) { CEglContextGuard eglContext(*this); TRACE(backend->log(AQ_LOG_TRACE, std::format("EGL (onBufferAttachmentDrop): dropping fbo {} rbo {} image 0x{:x}", attachment->fbo, attachment->rbo, (uintptr_t)attachment->eglImage))); if (attachment->tex && attachment->tex->texid) GLCALL(glDeleteTextures(1, &attachment->tex->texid)); if (attachment->rbo) GLCALL(glDeleteRenderbuffers(1, &attachment->rbo)); if (attachment->fbo) GLCALL(glDeleteFramebuffers(1, &attachment->fbo)); if (attachment->eglImage) proc.eglDestroyImageKHR(egl.display, attachment->eglImage); if (attachment->tex && attachment->tex->image) proc.eglDestroyImageKHR(egl.display, attachment->tex->image); } bool CDRMRenderer::verifyDestinationDMABUF(const SDMABUFAttrs& attrs) { for (auto const& fmt : formats) { if (fmt.drmFormat != attrs.format) continue; if (fmt.modifier != attrs.modifier) continue; if (fmt.modifier != DRM_FORMAT_INVALID && fmt.external) { backend->log(AQ_LOG_ERROR, "EGL (verifyDestinationDMABUF): FAIL, format is external-only"); return false; } return true; } backend->log(AQ_LOG_ERROR, "EGL (verifyDestinationDMABUF): FAIL, format is unsupported by EGL"); return false; } constexpr std::optional CGLTex::getCacheStateIndex(GLenum pname) { switch (pname) { case GL_TEXTURE_WRAP_S: return TEXTURE_PAR_WRAP_S; case GL_TEXTURE_WRAP_T: return TEXTURE_PAR_WRAP_T; case GL_TEXTURE_MAG_FILTER: return TEXTURE_PAR_MAG_FILTER; case GL_TEXTURE_MIN_FILTER: return TEXTURE_PAR_MIN_FILTER; default: return std::nullopt; } } void CGLTex::bind() { glBindTexture(target, texid); } void CGLTex::unbind() { glBindTexture(target, 0); } void CGLTex::setTexParameter(GLenum pname, GLint param) { const auto cacheIndex = getCacheStateIndex(pname); if (!cacheIndex) { glTexParameteri(target, pname, param); return; } const auto idx = cacheIndex.value(); if (m_cachedStates[idx] == param) return; m_cachedStates[idx] = param; glTexParameteri(target, pname, param); } CDRMRendererBufferAttachment::CDRMRendererBufferAttachment(Hyprutils::Memory::CWeakPointer renderer_, Hyprutils::Memory::CSharedPointer buffer, EGLImageKHR image, GLuint fbo_, GLuint rbo_, CGLTex&& tex_, std::vector intermediateBuf_) : eglImage(image), fbo(fbo_), rbo(rbo_), tex(makeUnique(std::move(tex_))), intermediateBuf(intermediateBuf_), renderer(renderer_) { bufferDestroy = buffer->events.destroy.listen([this] { renderer->onBufferAttachmentDrop(this); }); }