#include "GLRenderer.hpp" #include #include "../config/ConfigValue.hpp" #include "../managers/CursorManager.hpp" #include "../managers/PointerManager.hpp" #include "../protocols/SessionLock.hpp" #include "../protocols/LayerShell.hpp" #include "../protocols/PresentationTime.hpp" #include "../protocols/core/DataDevice.hpp" #include "../protocols/core/Compositor.hpp" #include "../debug/HyprDebugOverlay.hpp" #include "helpers/Monitor.hpp" #include "pass/TexPassElement.hpp" #include "pass/ClearPassElement.hpp" #include "pass/RectPassElement.hpp" #include "pass/SurfacePassElement.hpp" #include "debug/log/Logger.hpp" #include "../protocols/types/ContentType.hpp" #include "render/OpenGL.hpp" #include "render/Renderer.hpp" #include "render/gl/GLFramebuffer.hpp" #include "render/gl/GLTexture.hpp" #include "decorations/CHyprDropShadowDecoration.hpp" #include #include #include using namespace Hyprutils::Utils; using namespace Hyprutils::OS; using enum NContentType::eContentType; using namespace NColorManagement; extern "C" { #include } CHyprGLRenderer::CHyprGLRenderer() : IHyprRenderer() {} void CHyprGLRenderer::initRender() { g_pHyprOpenGL->makeEGLCurrent(); g_pHyprRenderer->m_renderData.pMonitor = renderData().pMonitor; } bool CHyprGLRenderer::initRenderBuffer(SP buffer, uint32_t fmt) { try { m_currentRenderbuffer = getOrCreateRenderbuffer(m_currentBuffer, fmt); } catch (std::exception& e) { Log::logger->log(Log::ERR, "getOrCreateRenderbuffer failed for {}", NFormatUtils::drmFormatName(fmt)); return false; } return m_currentRenderbuffer; } bool CHyprGLRenderer::beginFullFakeRenderInternal(PHLMONITOR pMonitor, CRegion& damage, SP fb, bool simple) { initRender(); RASSERT(fb, "Cannot render FULL_FAKE without a provided fb!"); fb->bind(); if (simple) g_pHyprOpenGL->beginSimple(pMonitor, damage, nullptr, fb); else g_pHyprOpenGL->begin(pMonitor, damage, fb); return true; } bool CHyprGLRenderer::beginRenderInternal(PHLMONITOR pMonitor, CRegion& damage, bool simple) { m_currentRenderbuffer->bind(); if (simple) g_pHyprOpenGL->beginSimple(pMonitor, damage, m_currentRenderbuffer); else g_pHyprOpenGL->begin(pMonitor, damage); return true; } void CHyprGLRenderer::endRender(const std::function& renderingDoneCallback) { const auto PMONITOR = g_pHyprRenderer->m_renderData.pMonitor; static auto PNVIDIAANTIFLICKER = CConfigValue("opengl:nvidia_anti_flicker"); g_pHyprRenderer->m_renderData.damage = m_renderPass.render(g_pHyprRenderer->m_renderData.damage); auto cleanup = CScopeGuard([this]() { if (m_currentRenderbuffer) m_currentRenderbuffer->unbind(); m_currentRenderbuffer = nullptr; m_currentBuffer = nullptr; }); if (m_renderMode != RENDER_MODE_TO_BUFFER_READ_ONLY) g_pHyprOpenGL->end(); else { g_pHyprRenderer->m_renderData.pMonitor.reset(); g_pHyprRenderer->m_renderData.mouseZoomFactor = 1.f; g_pHyprRenderer->m_renderData.mouseZoomUseMouse = true; } if (m_renderMode == RENDER_MODE_FULL_FAKE) return; if (m_renderMode == RENDER_MODE_NORMAL) PMONITOR->m_output->state->setBuffer(m_currentBuffer); if (!explicitSyncSupported()) { Log::logger->log(Log::TRACE, "renderer: Explicit sync unsupported, falling back to implicit in endRender"); // nvidia doesn't have implicit sync, so we have to explicitly wait here, llvmpipe and other software renderer seems to bug out aswell. if ((isNvidia() && *PNVIDIAANTIFLICKER) || isSoftware()) glFinish(); else glFlush(); // mark an implicit sync point m_usedAsyncBuffers.clear(); // release all buffer refs and hope implicit sync works if (renderingDoneCallback) renderingDoneCallback(); return; } UP eglSync = CEGLSync::create(); if LIKELY (eglSync && eglSync->isValid()) { for (auto const& buf : m_usedAsyncBuffers) { for (const auto& releaser : buf->m_syncReleasers) { releaser->addSyncFileFd(eglSync->fd()); } } // release buffer refs with release points now, since syncReleaser handles actual buffer release based on EGLSync std::erase_if(m_usedAsyncBuffers, [](const auto& buf) { return !buf->m_syncReleasers.empty(); }); // release buffer refs without release points when EGLSync sync_file/fence is signalled g_pEventLoopManager->doOnReadable(eglSync->fd().duplicate(), [renderingDoneCallback, prevbfs = std::move(m_usedAsyncBuffers)]() mutable { prevbfs.clear(); if (renderingDoneCallback) renderingDoneCallback(); }); m_usedAsyncBuffers.clear(); if (m_renderMode == RENDER_MODE_NORMAL) { PMONITOR->m_inFence = eglSync->takeFd(); PMONITOR->m_output->state->setExplicitInFence(PMONITOR->m_inFence.get()); } } else { Log::logger->log(Log::ERR, "renderer: Explicit sync failed, releasing resources"); m_usedAsyncBuffers.clear(); // release all buffer refs and hope implicit sync works if (renderingDoneCallback) renderingDoneCallback(); } } void CHyprGLRenderer::renderOffToMain(IFramebuffer* off) { g_pHyprOpenGL->renderOffToMain(off); } SP CHyprGLRenderer::getOrCreateRenderbufferInternal(SP buffer, uint32_t fmt) { g_pHyprOpenGL->makeEGLCurrent(); return makeShared(buffer, fmt); } SP CHyprGLRenderer::createStencilTexture(const int width, const int height) { g_pHyprOpenGL->makeEGLCurrent(); auto tex = makeShared(); tex->allocate({width, height}); return tex; } SP CHyprGLRenderer::createTexture(bool opaque) { g_pHyprOpenGL->makeEGLCurrent(); return makeShared(opaque); } SP CHyprGLRenderer::createTexture(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const Vector2D& size, bool keepDataCopy, bool opaque) { g_pHyprOpenGL->makeEGLCurrent(); return makeShared(drmFormat, pixels, stride, size, keepDataCopy, opaque); } SP CHyprGLRenderer::createTexture(const Aquamarine::SDMABUFAttrs& attrs, bool opaque) { g_pHyprOpenGL->makeEGLCurrent(); const auto image = g_pHyprOpenGL->createEGLImage(attrs); if (!image) return nullptr; return makeShared(attrs, image, opaque); } SP CHyprGLRenderer::createTexture(const int width, const int height, unsigned char* const data) { g_pHyprOpenGL->makeEGLCurrent(); SP tex = makeShared(); tex->allocate({width, height}); tex->m_size = {width, height}; // copy the data to an OpenGL texture we have const GLint glFormat = GL_RGBA; const GLint glType = GL_UNSIGNED_BYTE; tex->bind(); tex->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR); tex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); tex->setTexParameter(GL_TEXTURE_SWIZZLE_R, GL_BLUE); tex->setTexParameter(GL_TEXTURE_SWIZZLE_B, GL_RED); glTexImage2D(GL_TEXTURE_2D, 0, glFormat, tex->m_size.x, tex->m_size.y, 0, glFormat, glType, data); tex->unbind(); return tex; } SP CHyprGLRenderer::createTexture(cairo_surface_t* cairo) { g_pHyprOpenGL->makeEGLCurrent(); const auto CAIROFORMAT = cairo_image_surface_get_format(cairo); auto tex = makeShared(); tex->allocate({cairo_image_surface_get_width(cairo), cairo_image_surface_get_height(cairo)}); const GLint glIFormat = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_RGB32F : GL_RGBA; const GLint glFormat = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_RGB : GL_RGBA; const GLint glType = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_FLOAT : GL_UNSIGNED_BYTE; const auto DATA = cairo_image_surface_get_data(cairo); tex->bind(); tex->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR); tex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); if (CAIROFORMAT != CAIRO_FORMAT_RGB96F) { tex->setTexParameter(GL_TEXTURE_SWIZZLE_R, GL_BLUE); tex->setTexParameter(GL_TEXTURE_SWIZZLE_B, GL_RED); } glTexImage2D(GL_TEXTURE_2D, 0, glIFormat, tex->m_size.x, tex->m_size.y, 0, glFormat, glType, DATA); return tex; } SP CHyprGLRenderer::createTexture(std::span lut3D, size_t N) { g_pHyprOpenGL->makeEGLCurrent(); return makeShared(lut3D, N); } bool CHyprGLRenderer::explicitSyncSupported() { return g_pHyprOpenGL->explicitSyncSupported(); } std::vector CHyprGLRenderer::getDRMFormats() { return g_pHyprOpenGL->getDRMFormats(); } std::vector CHyprGLRenderer::getDRMFormatModifiers(DRMFormat format) { return g_pHyprOpenGL->getDRMFormatModifiers(format); } SP CHyprGLRenderer::createFB(const std::string& name) { g_pHyprOpenGL->makeEGLCurrent(); return makeShared(name); } void CHyprGLRenderer::disableScissor() { g_pHyprOpenGL->scissor(nullptr); } void CHyprGLRenderer::blend(bool enabled) { g_pHyprOpenGL->blend(enabled); } void CHyprGLRenderer::drawShadow(const CBox& box, int round, float roundingPower, int range, CHyprColor color, float a) { g_pHyprOpenGL->renderRoundedShadow(box, round, roundingPower, range, color, a); } SP CHyprGLRenderer::blurFramebuffer(SP source, float a, CRegion* originalDamage) { auto src = GLFB(source); return g_pHyprOpenGL->blurFramebufferWithDamage(a, originalDamage, *src)->getTexture(); } void CHyprGLRenderer::setViewport(int x, int y, int width, int height) { g_pHyprOpenGL->setViewport(x, y, width, height); } bool CHyprGLRenderer::reloadShaders(const std::string& path) { return g_pHyprOpenGL->initShaders(path); } void CHyprGLRenderer::draw(CBorderPassElement* element, const CRegion& damage) { const auto m_data = element->m_data; if (m_data.hasGrad2) g_pHyprOpenGL->renderBorder( m_data.box, m_data.grad1, m_data.grad2, m_data.lerp, {.round = m_data.round, .roundingPower = m_data.roundingPower, .borderSize = m_data.borderSize, .a = m_data.a, .outerRound = m_data.outerRound}); else g_pHyprOpenGL->renderBorder( m_data.box, m_data.grad1, {.round = m_data.round, .roundingPower = m_data.roundingPower, .borderSize = m_data.borderSize, .a = m_data.a, .outerRound = m_data.outerRound}); }; void CHyprGLRenderer::draw(CClearPassElement* element, const CRegion& damage) { g_pHyprOpenGL->clear(element->m_data.color); }; void CHyprGLRenderer::draw(CFramebufferElement* element, const CRegion& damage) { const auto m_data = element->m_data; SP fb = nullptr; if (m_data.main) { switch (m_data.framebufferID) { case FB_MONITOR_RENDER_MAIN: fb = g_pHyprRenderer->m_renderData.mainFB; break; case FB_MONITOR_RENDER_CURRENT: fb = g_pHyprRenderer->m_renderData.currentFB; break; case FB_MONITOR_RENDER_OUT: fb = g_pHyprRenderer->m_renderData.outFB; break; default: fb = nullptr; } if (!fb) { Log::logger->log(Log::ERR, "BUG THIS: CFramebufferElement::draw: main but null"); return; } } else { switch (m_data.framebufferID) { case FB_MONITOR_RENDER_EXTRA_OFFLOAD: fb = g_pHyprRenderer->m_renderData.pMonitor->m_offloadFB; break; case FB_MONITOR_RENDER_EXTRA_MIRROR: fb = m_renderData.pMonitor->m_mirrorFB; break; case FB_MONITOR_RENDER_EXTRA_MIRROR_SWAP: fb = m_renderData.pMonitor->m_mirrorSwapFB; break; case FB_MONITOR_RENDER_EXTRA_OFF_MAIN: fb = g_pHyprRenderer->m_renderData.pMonitor->m_offMainFB; break; case FB_MONITOR_RENDER_EXTRA_MONITOR_MIRROR: fb = g_pHyprRenderer->m_renderData.pMonitor->m_monitorMirrorFB; break; case FB_MONITOR_RENDER_EXTRA_BLUR: fb = g_pHyprRenderer->m_renderData.pMonitor->m_blurFB; break; default: fb = nullptr; } if (!fb) { Log::logger->log(Log::ERR, "BUG THIS: CFramebufferElement::draw: not main but null"); return; } } fb->bind(); }; void CHyprGLRenderer::draw(CPreBlurElement* element, const CRegion& damage) { auto dmg = damage; g_pHyprRenderer->preBlurForCurrentMonitor(&dmg); }; void CHyprGLRenderer::draw(CRectPassElement* element, const CRegion& damage) { const auto m_data = element->m_data; if (m_data.color.a == 1.F || !m_data.blur) g_pHyprOpenGL->renderRect(m_data.box, m_data.color, {.damage = &damage, .round = m_data.round, .roundingPower = m_data.roundingPower}); else g_pHyprOpenGL->renderRect(m_data.box, m_data.color, {.round = m_data.round, .roundingPower = m_data.roundingPower, .blur = true, .blurA = m_data.blurA, .xray = m_data.xray}); }; void CHyprGLRenderer::draw(CShadowPassElement* element, const CRegion& damage) { const auto m_data = element->m_data; m_data.deco->render(g_pHyprRenderer->m_renderData.pMonitor.lock(), m_data.a); }; void CHyprGLRenderer::draw(CTexPassElement* element, const CRegion& damage) { const auto m_data = element->m_data; g_pHyprOpenGL->renderTexture( // m_data.tex, m_data.box, { // blur settings for m_data.blur == true .blur = m_data.blur, .blurA = m_data.blurA, .overallA = m_data.overallA, .blockBlurOptimization = m_data.blockBlurOptimization.value_or(false), .blurredBG = m_data.blurredBG, // common settings .damage = m_data.damage.empty() ? &damage : &m_data.damage, .surface = m_data.surface, .a = m_data.a, .round = m_data.round, .roundingPower = m_data.roundingPower, .discardActive = m_data.discardActive, .allowCustomUV = m_data.allowCustomUV, .cmBackToSRGB = m_data.cmBackToSRGB, .cmBackToSRGBSource = m_data.cmBackToSRGBSource, .discardMode = m_data.ignoreAlpha.has_value() ? sc(DISCARD_ALPHA) : m_data.discardMode, .discardOpacity = m_data.ignoreAlpha.has_value() ? *m_data.ignoreAlpha : m_data.discardOpacity, .clipRegion = m_data.clipRegion, .currentLS = m_data.currentLS, .primarySurfaceUVTopLeft = g_pHyprRenderer->m_renderData.primarySurfaceUVTopLeft, .primarySurfaceUVBottomRight = g_pHyprRenderer->m_renderData.primarySurfaceUVBottomRight, }); }; void CHyprGLRenderer::draw(CTextureMatteElement* element, const CRegion& damage) { const auto m_data = element->m_data; g_pHyprOpenGL->renderTextureMatte(m_data.tex, m_data.box, m_data.fb); }; SP CHyprGLRenderer::getBlurTexture(PHLMONITORREF pMonitor) { if (!pMonitor->m_blurFB) return nullptr; return pMonitor->m_blurFB->getTexture(); } void CHyprGLRenderer::unsetEGL() { if (!g_pHyprOpenGL) return; eglMakeCurrent(g_pHyprOpenGL->m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); }