From 38a7f03cf0e5ee116794a78581634ba3dab508fa Mon Sep 17 00:00:00 2001 From: UjinT34 <41110182+UjinT34@users.noreply.github.com> Date: Sun, 29 Mar 2026 00:41:45 +0300 Subject: renderer: refactor part 7: api fixes (#13631) Part 7 of ujin's refactors --------- Co-authored-by: Vaxry --- src/render/ElementRenderer.cpp | 485 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 485 insertions(+) create mode 100644 src/render/ElementRenderer.cpp (limited to 'src/render/ElementRenderer.cpp') diff --git a/src/render/ElementRenderer.cpp b/src/render/ElementRenderer.cpp new file mode 100644 index 00000000..8d2dedf7 --- /dev/null +++ b/src/render/ElementRenderer.cpp @@ -0,0 +1,485 @@ +#include "ElementRenderer.hpp" +#include "Renderer.hpp" +#include "../layout/LayoutManager.hpp" +#include "../desktop/view/Window.hpp" +#include +#include +#include + +using namespace Render; + +void IElementRenderer::drawElement(WP element, const CRegion& damage) { + if (!element) + return; + + switch (element->type()) { + case EK_BORDER: draw(dynamicPointerCast(element), damage); break; + case EK_CLEAR: draw(dynamicPointerCast(element), damage); break; + case EK_FRAMEBUFFER: draw(dynamicPointerCast(element), damage); break; + case EK_PRE_BLUR: drawPreBlur(dynamicPointerCast(element), damage); break; + case EK_RECT: drawRect(dynamicPointerCast(element), damage); break; + case EK_HINTS: drawHints(dynamicPointerCast(element), damage); break; + case EK_SHADOW: draw(dynamicPointerCast(element), damage); break; + case EK_INNER_GLOW: draw(dynamicPointerCast(element), damage); break; + case EK_SURFACE: preDrawSurface(dynamicPointerCast(element), damage); break; + case EK_TEXTURE: drawTex(dynamicPointerCast(element), damage); break; + case EK_TEXTURE_MATTE: drawTexMatte(dynamicPointerCast(element), damage); break; + case EK_CUSTOM: drawCustom(element, damage); break; + default: Log::logger->log(Log::WARN, "Unimplimented draw for {}", element->passName()); + } +} + +static std::optional getSurfaceExpectedSize(PHLWINDOW pWindow, SP pSurface, PHLMONITOR pMonitor, bool main) { + const auto CAN_USE_WINDOW = pWindow && main; + const auto WINDOW_SIZE_MISALIGN = CAN_USE_WINDOW && pWindow->getReportedSize() != pWindow->wlSurface()->resource()->m_current.size; + + if (pSurface->m_current.viewport.hasDestination) + return (pSurface->m_current.viewport.destination * pMonitor->m_scale).round(); + + if (pSurface->m_current.viewport.hasSource) + return (pSurface->m_current.viewport.source.size() * pMonitor->m_scale).round(); + + if (WINDOW_SIZE_MISALIGN) + return (pSurface->m_current.size * pMonitor->m_scale).round(); + + if (CAN_USE_WINDOW) + return (pWindow->getReportedSize() * pMonitor->m_scale).round(); + + return std::nullopt; +} + +void IElementRenderer::calculateUVForSurface(PHLWINDOW pWindow, SP pSurface, PHLMONITOR pMonitor, bool main, const Vector2D& projSize, + const Vector2D& projSizeUnscaled, bool fixMisalignedFSV1) { + auto& m_renderData = g_pHyprRenderer->m_renderData; + + if (!pWindow || !pWindow->m_isX11) { + static auto PEXPANDEDGES = CConfigValue("render:expand_undersized_textures"); + + Vector2D uvTL; + Vector2D uvBR = Vector2D(1, 1); + + if (pSurface->m_current.viewport.hasSource) { + // we stretch it to dest. if no dest, to 1,1 + Vector2D const& bufferSize = pSurface->m_current.bufferSize; + auto const& bufferSource = pSurface->m_current.viewport.source; + + // calculate UV for the basic src_box. Assume dest == size. Scale to dest later + uvTL = Vector2D(bufferSource.x / bufferSize.x, bufferSource.y / bufferSize.y); + uvBR = Vector2D((bufferSource.x + bufferSource.width) / bufferSize.x, (bufferSource.y + bufferSource.height) / bufferSize.y); + + if (uvBR.x < 0.01f || uvBR.y < 0.01f) { + uvTL = Vector2D(); + uvBR = Vector2D(1, 1); + } + } + + if (projSize != Vector2D{} && fixMisalignedFSV1) { + // instead of nearest_neighbor (we will repeat / skip) + // just cut off / expand surface + const Vector2D PIXELASUV = Vector2D{1, 1} / pSurface->m_current.bufferSize; + const auto& BUFFER_SIZE = pSurface->m_current.bufferSize; + + // compute MISALIGN from the adjusted UV coordinates. + const Vector2D MISALIGNMENT = (uvBR - uvTL) * BUFFER_SIZE - projSize; + + if (MISALIGNMENT != Vector2D{}) + uvBR -= MISALIGNMENT * PIXELASUV; + } else { + // if the surface is smaller than our viewport, extend its edges. + // this will break if later on xdg geometry is hit, but we really try + // to let the apps know to NOT add CSD. Also if source is there. + // there is no way to fix this if that's the case + const auto MONITOR_WL_SCALE = std::ceil(pMonitor->m_scale); + const bool SCALE_UNAWARE = pMonitor->m_scale != 1.f && (MONITOR_WL_SCALE == pSurface->m_current.scale || !pSurface->m_current.viewport.hasDestination); + const auto EXPECTED_SIZE = getSurfaceExpectedSize(pWindow, pSurface, pMonitor, main).value_or((projSize * pMonitor->m_scale).round()); + + const auto RATIO = projSize / EXPECTED_SIZE; + if (!SCALE_UNAWARE || MONITOR_WL_SCALE == 1) { + if (*PEXPANDEDGES && !SCALE_UNAWARE && (RATIO.x > 1 || RATIO.y > 1)) { + const auto FIX = RATIO.clamp(Vector2D{1, 1}, Vector2D{1000000, 1000000}); + uvBR = uvBR * FIX; + } + + // FIXME: probably do this for in anims on all views... + const auto SHOULD_SKIP = !pWindow || pWindow->m_animatingIn; + if (!SHOULD_SKIP && (RATIO.x < 1 || RATIO.y < 1)) { + const auto FIX = RATIO.clamp(Vector2D{0.0001, 0.0001}, Vector2D{1, 1}); + uvBR = uvBR * FIX; + } + } + } + + m_renderData.primarySurfaceUVTopLeft = uvTL; + m_renderData.primarySurfaceUVBottomRight = uvBR; + + if (m_renderData.primarySurfaceUVTopLeft == Vector2D() && m_renderData.primarySurfaceUVBottomRight == Vector2D(1, 1)) { + // No special UV mods needed + m_renderData.primarySurfaceUVTopLeft = Vector2D(-1, -1); + m_renderData.primarySurfaceUVBottomRight = Vector2D(-1, -1); + } + + if (!main || !pWindow) + return; + + // FIXME: this doesn't work. We always set MAXIMIZED anyways, so this doesn't need to work, but it's problematic. + + // CBox geom = pWindow->m_xdgSurface->m_current.geometry; + + // // Adjust UV based on the xdg_surface geometry + // if (geom.x != 0 || geom.y != 0 || geom.w != 0 || geom.h != 0) { + // const auto XPERC = geom.x / pSurface->m_current.size.x; + // const auto YPERC = geom.y / pSurface->m_current.size.y; + // const auto WPERC = (geom.x + geom.w ? geom.w : pSurface->m_current.size.x) / pSurface->m_current.size.x; + // const auto HPERC = (geom.y + geom.h ? geom.h : pSurface->m_current.size.y) / pSurface->m_current.size.y; + + // const auto TOADDTL = Vector2D(XPERC * (uvBR.x - uvTL.x), YPERC * (uvBR.y - uvTL.y)); + // uvBR = uvBR - Vector2D((1.0 - WPERC) * (uvBR.x - uvTL.x), (1.0 - HPERC) * (uvBR.y - uvTL.y)); + // uvTL = uvTL + TOADDTL; + // } + + m_renderData.primarySurfaceUVTopLeft = uvTL; + m_renderData.primarySurfaceUVBottomRight = uvBR; + + if (m_renderData.primarySurfaceUVTopLeft == Vector2D() && m_renderData.primarySurfaceUVBottomRight == Vector2D(1, 1)) { + // No special UV mods needed + m_renderData.primarySurfaceUVTopLeft = Vector2D(-1, -1); + m_renderData.primarySurfaceUVBottomRight = Vector2D(-1, -1); + } + } else { + m_renderData.primarySurfaceUVTopLeft = Vector2D(-1, -1); + m_renderData.primarySurfaceUVBottomRight = Vector2D(-1, -1); + } +} + +void IElementRenderer::drawRect(WP element, const CRegion& damage) { + auto& data = element->m_data; + auto& m_renderData = g_pHyprRenderer->m_renderData; + + if (data.box.w <= 0 || data.box.h <= 0) + return; + + if (!data.clipBox.empty()) + m_renderData.clipBox = data.clipBox; + + data.modifiedBox = data.box; + m_renderData.renderModif.applyToBox(data.modifiedBox); + + CBox transformedBox = data.box; + transformedBox.transform(Math::wlTransformToHyprutils(Math::invertTransform(m_renderData.pMonitor->m_transform)), m_renderData.pMonitor->m_transformedSize.x, + m_renderData.pMonitor->m_transformedSize.y); + + data.TOPLEFT[0] = sc(transformedBox.x); + data.TOPLEFT[1] = sc(transformedBox.y); + data.FULLSIZE[0] = sc(transformedBox.width); + data.FULLSIZE[1] = sc(transformedBox.height); + + data.drawRegion = data.color.a == 1.F || !data.blur ? damage : m_renderData.damage; + + if (m_renderData.clipBox.width != 0 && m_renderData.clipBox.height != 0) { + CRegion damageClip{m_renderData.clipBox.x, m_renderData.clipBox.y, m_renderData.clipBox.width, m_renderData.clipBox.height}; + data.drawRegion = damageClip.intersect(data.drawRegion); + } + + draw(element, damage); + + m_renderData.clipBox = {}; +} + +void IElementRenderer::drawHints(WP element, const CRegion& damage) { + const auto m_data = element->m_data; + if (m_data.renderModif.has_value()) + g_pHyprRenderer->m_renderData.renderModif = *m_data.renderModif; +} + +void IElementRenderer::drawPreBlur(WP element, const CRegion& damage) { + TRACY_GPU_ZONE("RenderPreBlurForCurrentMonitor"); + auto& m_renderData = g_pHyprRenderer->m_renderData; + + const auto SAVEDRENDERMODIF = m_renderData.renderModif; + m_renderData.renderModif = {}; // fix shit + + // make the fake dmg + CRegion fakeDamage{0, 0, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y}; + + draw(element, fakeDamage); + + m_renderData.pMonitor->m_blurFBDirty = false; + m_renderData.pMonitor->m_blurFBShouldRender = false; + + m_renderData.renderModif = SAVEDRENDERMODIF; +} + +void IElementRenderer::drawSurface(WP element, const CRegion& damage) { + const auto m_data = element->m_data; + auto& m_renderData = g_pHyprRenderer->m_renderData; + + Hyprutils::Utils::CScopeGuard x = {[]() { + g_pHyprRenderer->m_renderData.primarySurfaceUVTopLeft = Vector2D(-1, -1); + g_pHyprRenderer->m_renderData.primarySurfaceUVBottomRight = Vector2D(-1, -1); + }}; + + if (!m_data.texture) + return; + + const auto& TEXTURE = m_data.texture; + + // this is bad, probably has been logged elsewhere. Means the texture failed + // uploading to the GPU. + if (!TEXTURE->ok()) + return; + + const auto INTERACTIVERESIZEINPROGRESS = m_data.pWindow && g_layoutManager->dragController()->target() && g_layoutManager->dragController()->mode() == MBIND_RESIZE; + TRACY_GPU_ZONE("RenderSurface"); + + auto PSURFACE = Desktop::View::CWLSurface::fromResource(m_data.surface); + + const float ALPHA = m_data.alpha * m_data.fadeAlpha * (PSURFACE ? PSURFACE->m_alphaModifier : 1.F); + const float OVERALL_ALPHA = PSURFACE ? PSURFACE->m_overallOpacity : 1.F; + const bool BLUR = m_data.blur && (!TEXTURE->m_opaque || ALPHA < 1.F || OVERALL_ALPHA < 1.F); + + auto windowBox = element->getTexBox(); + + const auto PROJSIZEUNSCALED = windowBox.size(); + + windowBox.scale(m_data.pMonitor->m_scale); + windowBox.round(); + + if (windowBox.width <= 1 || windowBox.height <= 1) { + element->discard(); + return; + } + + const bool MISALIGNEDFSV1 = std::floor(m_data.pMonitor->m_scale) != m_data.pMonitor->m_scale /* Fractional */ && m_data.surface->m_current.scale == 1 /* fs protocol */ && + windowBox.size() != m_data.surface->m_current.bufferSize /* misaligned */ && DELTALESSTHAN(windowBox.width, m_data.surface->m_current.bufferSize.x, 3) && + DELTALESSTHAN(windowBox.height, m_data.surface->m_current.bufferSize.y, 3) /* off by one-or-two */ && + (!m_data.pWindow || (!m_data.pWindow->m_realSize->isBeingAnimated() && !INTERACTIVERESIZEINPROGRESS)) /* not window or not animated/resizing */ && + (!m_data.pLS || (!m_data.pLS->m_realSize->isBeingAnimated())); /* not LS or not animated */ + + calculateUVForSurface(m_data.pWindow, m_data.surface, m_data.pMonitor->m_self.lock(), m_data.mainSurface, windowBox.size(), PROJSIZEUNSCALED, MISALIGNEDFSV1); + + auto cancelRender = false; + auto clipRegion = element->visibleRegion(cancelRender); + if (cancelRender) + return; + + // check for fractional scale surfaces misaligning the buffer size + // in those cases it's better to just force nearest neighbor + // as long as the window is not animated. During those it'd look weird. + // UV will fixup it as well + if (MISALIGNEDFSV1) + m_renderData.useNearestNeighbor = true; + + float rounding = m_data.rounding; + float roundingPower = m_data.roundingPower; + + rounding -= 1; // to fix a border issue + + if (m_data.dontRound) { + rounding = 0; + roundingPower = 2.0f; + } + + const bool WINDOWOPAQUE = m_data.pWindow && m_data.pWindow->wlSurface()->resource() == m_data.surface ? m_data.pWindow->opaque() : false; + const bool CANDISABLEBLEND = ALPHA >= 1.f && OVERALL_ALPHA >= 1.f && rounding <= 0 && WINDOWOPAQUE; + + if (CANDISABLEBLEND) + g_pHyprRenderer->blend(false); + else + g_pHyprRenderer->blend(true); + + // FIXME: This is wrong and will bug the blur out as shit if the first surface + // is a subsurface that does NOT cover the entire frame. In such cases, we probably should fall back + // to what we do for misaligned surfaces (blur the entire thing and then render shit without blur) + if (m_data.surfaceCounter == 0 && !m_data.popup) { + if (BLUR) + drawElement(makeShared(CTexPassElement::SRenderData{ + .tex = TEXTURE, + .box = windowBox, + .a = ALPHA, + .blurA = m_data.fadeAlpha, + .overallA = OVERALL_ALPHA, + .round = rounding, + .roundingPower = roundingPower, + .blur = true, + .blockBlurOptimization = m_data.blockBlurOptimization, + .allowCustomUV = true, + .surface = m_data.surface, + .discardMode = m_data.discardMode, + .discardOpacity = m_data.discardOpacity, + .clipRegion = clipRegion, + .currentLS = m_data.pLS, + }), + m_renderData.damage.copy().intersect(windowBox)); + else + drawElement(makeShared(CTexPassElement::SRenderData{ + .tex = TEXTURE, + .box = windowBox, + .a = ALPHA * OVERALL_ALPHA, + .round = rounding, + .roundingPower = roundingPower, + .discardActive = false, + .allowCustomUV = true, + .surface = m_data.surface, + .discardMode = m_data.discardMode, + .discardOpacity = m_data.discardOpacity, + .clipRegion = clipRegion, + .currentLS = m_data.pLS, + }), + m_renderData.damage.copy().intersect(windowBox)); + } else { + if (BLUR && m_data.popup) + drawElement(makeShared(CTexPassElement::SRenderData{ + .tex = TEXTURE, + .box = windowBox, + .a = ALPHA, + .blurA = m_data.fadeAlpha, + .overallA = OVERALL_ALPHA, + .round = rounding, + .roundingPower = roundingPower, + .blur = true, + .blockBlurOptimization = true, + .allowCustomUV = true, + .surface = m_data.surface, + .discardMode = m_data.discardMode, + .discardOpacity = m_data.discardOpacity, + .clipRegion = clipRegion, + .currentLS = m_data.pLS, + }), + m_renderData.damage.copy().intersect(windowBox)); + else + drawElement(makeShared(CTexPassElement::SRenderData{ + .tex = TEXTURE, + .box = windowBox, + .a = ALPHA * OVERALL_ALPHA, + .round = rounding, + .roundingPower = roundingPower, + .discardActive = false, + .allowCustomUV = true, + .surface = m_data.surface, + .discardMode = m_data.discardMode, + .discardOpacity = m_data.discardOpacity, + .clipRegion = clipRegion, + .currentLS = m_data.pLS, + }), + m_renderData.damage.copy().intersect(windowBox)); + } + + g_pHyprRenderer->blend(true); +}; + +void IElementRenderer::preDrawSurface(WP element, const CRegion& damage) { + auto& m_renderData = g_pHyprRenderer->m_renderData; + m_renderData.clipBox = element->m_data.clipBox; + m_renderData.useNearestNeighbor = element->m_data.useNearestNeighbor; + g_pHyprRenderer->pushMonitorTransformEnabled(element->m_data.flipEndFrame); + m_renderData.currentWindow = element->m_data.pWindow; + + drawSurface(element, damage); + + if (!g_pHyprRenderer->m_bBlockSurfaceFeedback) + element->m_data.surface->presentFeedback(element->m_data.when, element->m_data.pMonitor->m_self.lock()); + + // add async (dmabuf) buffers to usedBuffers so we can handle release later + // sync (shm) buffers will be released in commitState, so no need to track them here + if (element->m_data.surface->m_current.buffer && !element->m_data.surface->m_current.buffer->isSynchronous()) + g_pHyprRenderer->m_usedAsyncBuffers.emplace_back(element->m_data.surface->m_current.buffer); + + m_renderData.clipBox = {}; + m_renderData.useNearestNeighbor = false; + g_pHyprRenderer->popMonitorTransformEnabled(); + m_renderData.currentWindow.reset(); +} + +void IElementRenderer::drawTex(WP element, const CRegion& damage) { + auto& m_renderData = g_pHyprRenderer->m_renderData; + if (!element->m_data.clipBox.empty()) + m_renderData.clipBox = element->m_data.clipBox; + + g_pHyprRenderer->pushMonitorTransformEnabled(element->m_data.flipEndFrame); + if (element->m_data.useMirrorProjection) + g_pHyprRenderer->setProjectionType(RPT_MIRROR); + + m_renderData.surface = element->m_data.surface; + + Hyprutils::Utils::CScopeGuard x = {[useMirrorProjection = element->m_data.useMirrorProjection]() { + g_pHyprRenderer->popMonitorTransformEnabled(); + if (useMirrorProjection) + g_pHyprRenderer->setProjectionType(RPT_MONITOR); + g_pHyprRenderer->m_renderData.surface.reset(); + g_pHyprRenderer->m_renderData.clipBox = {}; + }}; + + if (element->m_data.blur) { + // make a damage region for this window + CRegion texDamage{m_renderData.damage}; + texDamage.intersect(element->m_data.box.x, element->m_data.box.y, element->m_data.box.width, element->m_data.box.height); + + // While renderTextureInternalWithDamage will clip the blur as well, + // clipping texDamage here allows blur generation to be optimized. + if (!element->m_data.clipRegion.empty()) + texDamage.intersect(element->m_data.clipRegion); + + if (texDamage.empty()) + return; + + m_renderData.renderModif.applyToRegion(texDamage); + + element->m_data.damage = texDamage; + + // amazing hack: the surface has an opaque region! + const auto& surface = element->m_data.surface; + const auto& box = element->m_data.box; + CRegion inverseOpaque; + if (element->m_data.a >= 1.f && surface && std::round(surface->m_current.size.x * m_renderData.pMonitor->m_scale) == box.w && + std::round(surface->m_current.size.y * m_renderData.pMonitor->m_scale) == box.h) { + pixman_box32_t surfbox = {0, 0, surface->m_current.size.x * surface->m_current.scale, surface->m_current.size.y * surface->m_current.scale}; + inverseOpaque = surface->m_current.opaque; + inverseOpaque.invert(&surfbox).intersect(0, 0, surface->m_current.size.x * surface->m_current.scale, surface->m_current.size.y * surface->m_current.scale); + + if (inverseOpaque.empty()) { + element->m_data.blur = false; + draw(element, damage); + return; + } + } else + inverseOpaque = {0, 0, element->m_data.box.width, element->m_data.box.height}; + + inverseOpaque.scale(m_renderData.pMonitor->m_scale); + element->m_data.blockBlurOptimization = element->m_data.blockBlurOptimization.value_or(false) || + !g_pHyprRenderer->shouldUseNewBlurOptimizations(element->m_data.currentLS.lock(), m_renderData.currentWindow.lock()); + + // vvv TODO: layered blur fbs? + if (element->m_data.blockBlurOptimization.value_or(false)) { + inverseOpaque.translate(box.pos()); + m_renderData.renderModif.applyToRegion(inverseOpaque); + inverseOpaque.intersect(element->m_data.damage); + element->m_data.blurredBG = g_pHyprRenderer->blurMainFramebuffer(element->m_data.a, &inverseOpaque); + } else + element->m_data.blurredBG = m_renderData.pMonitor->resources()->m_blurFB->getTexture(); + + draw(element, damage); + } else + draw(element, damage); +} + +void IElementRenderer::drawTexMatte(WP element, const CRegion& damage) { + if (g_pHyprRenderer->m_renderData.damage.empty()) + return; + + const auto m_data = element->m_data; + if (m_data.disableTransformAndModify) { + g_pHyprRenderer->pushMonitorTransformEnabled(true); + g_pHyprRenderer->m_renderData.renderModif.enabled = false; + draw(element, damage); + g_pHyprRenderer->m_renderData.renderModif.enabled = true; + g_pHyprRenderer->popMonitorTransformEnabled(); + } else + draw(element, damage); +} + +void IElementRenderer::drawCustom(WP element, const CRegion& damage) { + const auto& elements = element->draw(); + for (const auto& el : elements) { + drawElement(el, damage); + } +} -- cgit v1.2.3