From 64962f89e48a1b50214dd3eeb001aa1cc010ff25 Mon Sep 17 00:00:00 2001 From: Vaxry <43317083+vaxerski@users.noreply.github.com> Date: Wed, 5 Aug 2026 19:38:11 +0100 Subject: [PATCH] renderer/transformer: make window transformers adapt to viewport constraints (#15759) --- src/helpers/DeformableMesh.cpp | 23 +- src/helpers/DeformableMesh.hpp | 3 +- src/output/MonitorResources.cpp | 76 +++- src/output/MonitorResources.hpp | 2 + src/render/ElementRenderer.cpp | 346 ++++++++++++------ src/render/OpenGL.cpp | 2 +- src/render/Shader.cpp | 37 +- src/render/Shader.hpp | 1 + src/render/pass/TexPassElement.hpp | 14 +- src/render/shaders/glsl/motion_blur.glsl | 8 +- src/render/shaders/glsl/surface.frag | 6 +- .../transformer/MotionBlurTransformer.cpp | 10 +- .../transformer/MotionBlurTransformer.hpp | 4 +- src/render/transformer/Transformer.cpp | 21 +- src/render/transformer/Transformer.hpp | 44 ++- src/render/transformer/TransformerList.cpp | 46 ++- src/render/transformer/TransformerList.hpp | 18 +- src/render/transformer/WobbleTransformer.cpp | 69 ++-- src/render/transformer/WobbleTransformer.hpp | 24 +- tests/helpers/DeformableMesh.cpp | 27 ++ tests/render/TransformerList.cpp | 47 ++- 21 files changed, 598 insertions(+), 230 deletions(-) diff --git a/src/helpers/DeformableMesh.cpp b/src/helpers/DeformableMesh.cpp index b1fbb7e7c..f64bd4ec1 100644 --- a/src/helpers/DeformableMesh.cpp +++ b/src/helpers/DeformableMesh.cpp @@ -134,8 +134,27 @@ CBox CDeformableMesh::transformedExtents(const CBox& box) const { return {minX, minY, maxX - minX, maxY - minY}; } +CBox CDeformableMesh::sourceBoxForOutput(const CBox& box, const CBox& outputBox) const { + if (m_points.empty()) + return outputBox.intersection(box); + + Vector2D maxDisplacement; + for (const auto& point : m_points) { + maxDisplacement.x = std::max(maxDisplacement.x, std::abs(point.displacement.x)); + maxDisplacement.y = std::max(maxDisplacement.y, std::abs(point.displacement.y)); + } + + CBox source = { + outputBox.x - maxDisplacement.x, + outputBox.y - maxDisplacement.y, + outputBox.w + maxDisplacement.x * 2.0, + outputBox.h + maxDisplacement.y * 2.0, + }; + return source.intersection(box); +} + std::vector CDeformableMesh::verticesForBox(const CBox& box, const CBox& outputBox, const Vector2D& textureSize, double displacementScale, - eTransform textureTransform) const { + eTransform textureTransform, const Vector2D& textureOrigin) const { std::vector vertices; if (m_verticesPerEdge < 2 || outputBox.w <= 0.F || outputBox.h <= 0.F || textureSize.x <= 0.F || textureSize.y <= 0.F) return vertices; @@ -145,7 +164,7 @@ std::vector CDeformableMesh::verticesForBox(const CBox& box, const auto makeVertex = [&](size_t x, size_t y) -> SMeshRenderVertex { const Vector2D REST = restPoint(box, x, y); const Vector2D POS = REST + point(x, y).displacement * displacementScale; - const Vector2D TEXTUREPOS = REST.transform(textureTransform, textureSize); + const Vector2D TEXTUREPOS = (REST - textureOrigin).transform(textureTransform, textureSize); return { .x = sc((POS.x - outputBox.x) / outputBox.w), .y = sc((POS.y - outputBox.y) / outputBox.h), diff --git a/src/helpers/DeformableMesh.hpp b/src/helpers/DeformableMesh.hpp index 028d48328..ba8dad8dc 100644 --- a/src/helpers/DeformableMesh.hpp +++ b/src/helpers/DeformableMesh.hpp @@ -28,8 +28,9 @@ class CDeformableMesh { bool stable(float positionEpsilon, float velocityEpsilon) const; CBox transformedExtents(const CBox& box) const; + CBox sourceBoxForOutput(const CBox& box, const CBox& outputBox) const; std::vector verticesForBox(const CBox& box, const CBox& outputBox, const Vector2D& textureSize, double displacementScale = 1.0, - eTransform textureTransform = HYPRUTILS_TRANSFORM_NORMAL) const; + eTransform textureTransform = HYPRUTILS_TRANSFORM_NORMAL, const Vector2D& textureOrigin = {}) const; private: struct SPoint { diff --git a/src/output/MonitorResources.cpp b/src/output/MonitorResources.cpp index 4e53f09e6..53c73b0bc 100644 --- a/src/output/MonitorResources.cpp +++ b/src/output/MonitorResources.cpp @@ -3,13 +3,15 @@ #include "../helpers/cm/ColorManagement.hpp" #include "../render/Renderer.hpp" #include +#include #include using namespace Monitor; using namespace NColorManagement; -static const int MAX_WORK_BUFFERS = 8; -static const int MAX_UNUSED_SECONDS = 5; +static const int MAX_WORK_BUFFERS = 8; +static const int MAX_UNUSED_SECONDS = 5; +static constexpr uint64_t MAX_SIZED_WORK_BUFFER_PIXELS = 64ULL * 1024ULL * 1024ULL; CMonitorResources::CMonitorResources(WP monitor, DRMFormat format, Vector2D size, NColorManagement::PImageDescription imageDescription) : m_stencilTex(g_pHyprRenderer->createStencilTexture(size.x, size.y)), m_blurFB(g_pHyprRenderer->createFB(std::format("Monitor {} blur FB", monitor->m_name))), @@ -31,6 +33,8 @@ void CMonitorResources::setImageDescription(NColorManagement::PImageDescription m_blurFB->setImageDescription(imageDescription); for (const auto& res : m_workBuffers) res.buffer->setImageDescription(imageDescription); + for (const auto& res : m_sizedWorkBuffers) + res.buffer->setImageDescription(imageDescription); if (m_monitorMirrorFB) m_monitorMirrorFB->setImageDescription(getMirrorTexImageDescription()); if (m_mirrorTex) @@ -55,13 +59,71 @@ SP CMonitorResources::getUnusedWorkBuffer() { return res.buffer; } -void CMonitorResources::forEachUnusedFB(std::function)> callback, bool includeNamed) { - for (const auto& res : m_workBuffers) { - if (res.buffer.strongRef() > 1) - continue; +SP CMonitorResources::getUnusedWorkBuffer(const Vector2D& size) { + RASSERT((size.x > 0 && size.y > 0), "cannot get a workbuffer with negative / zero size! (attempted {}x{})", size.x, size.y); - callback(res.buffer); + std::erase_if(m_sizedWorkBuffers, [](const auto& res) { return res.lastUsed.getSeconds() >= MAX_UNUSED_SECONDS; }); + + auto found = std::ranges::find_if(m_sizedWorkBuffers, [&](const auto& res) { + return res.buffer.strongRef() < 2 && res.buffer->isAllocated() && res.buffer->m_size == size && res.buffer->m_drmFormat == m_drmFormat; + }); + if (found != m_sizedWorkBuffers.end()) { + found->lastUsed.reset(); + return found->buffer; } + + const uint64_t REQUESTEDPIXELS = sc(size.x) * sc(size.y); + if (REQUESTEDPIXELS > MAX_SIZED_WORK_BUFFER_PIXELS) + return nullptr; + + uint64_t allocatedPixels = 0; + for (const auto& resource : m_sizedWorkBuffers) + allocatedPixels += sc(resource.buffer->m_size.x) * sc(resource.buffer->m_size.y); + + for (auto resource = m_sizedWorkBuffers.begin(); resource != m_sizedWorkBuffers.end() && allocatedPixels + REQUESTEDPIXELS > MAX_SIZED_WORK_BUFFER_PIXELS;) { + if (resource->buffer.strongRef() >= 2) { + ++resource; + continue; + } + + allocatedPixels -= sc(resource->buffer->m_size.x) * sc(resource->buffer->m_size.y); + resource = m_sizedWorkBuffers.erase(resource); + } + + if (allocatedPixels + REQUESTEDPIXELS > MAX_SIZED_WORK_BUFFER_PIXELS) + return nullptr; + + if (m_sizedWorkBuffers.size() < MAX_WORK_BUFFERS) { + auto& res = m_sizedWorkBuffers.emplace_back(g_pHyprRenderer->createFB(std::format("Monitor {} sized workbuffer", m_monitor->m_name))); + if (!res.buffer->alloc(size.x, size.y, m_drmFormat)) { + m_sizedWorkBuffers.pop_back(); + return nullptr; + } + res.buffer->setImageDescription(m_imageDescription); + res.lastUsed.reset(); + return res.buffer; + } + + found = std::ranges::find_if(m_sizedWorkBuffers, [](const auto& res) { return res.buffer.strongRef() < 2; }); + if (found == m_sizedWorkBuffers.end() || !found->buffer->alloc(size.x, size.y, m_drmFormat)) + return nullptr; + + found->buffer->setImageDescription(m_imageDescription); + found->lastUsed.reset(); + return found->buffer; +} + +void CMonitorResources::forEachUnusedFB(std::function)> callback, bool includeNamed) { + const auto FOR_EACH_UNUSED = [&](const auto& buffers) { + for (const auto& res : buffers) { + if (res.buffer.strongRef() > 1) + continue; + + callback(res.buffer); + } + }; + FOR_EACH_UNUSED(m_workBuffers); + FOR_EACH_UNUSED(m_sizedWorkBuffers); if (includeNamed) { if (m_blurFB && m_blurFB->isAllocated() && m_blurFB.strongRef() < 2) callback(m_blurFB); diff --git a/src/output/MonitorResources.hpp b/src/output/MonitorResources.hpp index be3a311b4..7bffe56d5 100644 --- a/src/output/MonitorResources.hpp +++ b/src/output/MonitorResources.hpp @@ -13,6 +13,7 @@ namespace Monitor { CMonitorResources(WP monitor, DRMFormat format, Vector2D size, NColorManagement::PImageDescription imageDescription); SP getUnusedWorkBuffer(); + SP getUnusedWorkBuffer(const Vector2D& size); void forEachUnusedFB(std::function)> callback, bool includeNamed = false); bool hasMirrorFB() const; bool shouldKeepMirrorFB() const; @@ -52,6 +53,7 @@ namespace Monitor { bool m_mirrorFBNeedsFullRefresh = true; std::vector m_workBuffers; + std::vector m_sizedWorkBuffers; friend class CMonitor; }; diff --git a/src/render/ElementRenderer.cpp b/src/render/ElementRenderer.cpp index 838ef05df..36ba431f3 100644 --- a/src/render/ElementRenderer.cpp +++ b/src/render/ElementRenderer.cpp @@ -7,6 +7,9 @@ #include #include +#include +#include + using namespace Render; void IElementRenderer::drawElement(WP element, const CRegion& damage) { @@ -440,8 +443,8 @@ void IElementRenderer::drawTex(WP element, const CRegion& damag if (element->m_data.blur) { // make a damage region for this window - CRegion texDamage{m_renderData.damage}; - if (!m_renderData.renderingTransformedSource) + CRegion texDamage = element->m_data.useProvidedDamage ? element->m_data.damage : m_renderData.damage; + if (!element->m_data.useProvidedDamage && !m_renderData.renderingTransformedSource) 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, @@ -452,7 +455,8 @@ void IElementRenderer::drawTex(WP element, const CRegion& damag if (texDamage.empty()) return; - m_renderData.renderModif.applyToRegion(texDamage); + if (!element->m_data.useProvidedDamage) + m_renderData.renderModif.applyToRegion(texDamage); element->m_data.damage = texDamage; @@ -509,167 +513,281 @@ void IElementRenderer::drawTexMatte(WP element, const CReg draw(element, damage); } +static CBox motionBlurSourceBox(const SMotionBlurData& motionBlur, const CBox& outputBox, double padding) { + if (!motionBlur.enabled || motionBlur.samples < 1) + return outputBox.intersection(motionBlur.current); + + CBox required; + bool hasRequired = false; + for (int i = 0; i < motionBlur.samples; ++i) { + const double t = sc(i) / motionBlur.samples; + const CBox sampleBox = { + motionBlur.current.x + (motionBlur.previous.x - motionBlur.current.x) * t, + motionBlur.current.y + (motionBlur.previous.y - motionBlur.current.y) * t, + motionBlur.current.w + (motionBlur.previous.w - motionBlur.current.w) * t, + motionBlur.current.h + (motionBlur.previous.h - motionBlur.current.h) * t, + }; + const CBox visibleSample = outputBox.intersection(sampleBox); + if (visibleSample.empty() || sampleBox.w <= 0.0 || sampleBox.h <= 0.0) + continue; + + const Vector2D scale = motionBlur.current.size() / sampleBox.size(); + CBox sourceSample = { + motionBlur.current.pos() + (visibleSample.pos() - sampleBox.pos()) * scale, + visibleSample.size() * scale, + }; + sourceSample.expand(padding); + + if (!hasRequired) { + required = sourceSample; + hasRequired = true; + continue; + } + + const double x1 = std::min(required.x, sourceSample.x); + const double y1 = std::min(required.y, sourceSample.y); + const double x2 = std::max(required.x + required.w, sourceSample.x + sourceSample.w); + const double y2 = std::max(required.y + required.h, sourceSample.y + sourceSample.h); + required = {x1, y1, x2 - x1, y2 - y1}; + } + + return hasRequired ? required.intersection(motionBlur.current) : CBox{}; +} + +static bool transformPlanFits(const SWindowTransformPlan& plan, double scale, bool hasMatte) { + static const auto LIMITS = [] { + GLint textureSize = 0; + GLint viewport[2] = {0, 0}; + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &textureSize); + glGetIntegerv(GL_MAX_VIEWPORT_DIMS, viewport); + return Vector2D{std::min(textureSize, viewport[0]), std::min(textureSize, viewport[1])}; + }(); + + constexpr uint64_t MAX_TRANSFORMER_PIXELS = 64ULL * 1024ULL * 1024ULL; + uint64_t totalPixels = 0; + + const double canvasPadding = 1.0 / scale; + const auto ADD_CANVAS = [&](const CBox& logicalBox) { + const CBox canvas = pixelBoxForLogical(logicalBox.copy().expand(canvasPadding), scale); + if (canvas.empty() || canvas.w > LIMITS.x || canvas.h > LIMITS.y) + return false; + + const uint64_t pixels = sc(canvas.w) * sc(canvas.h); + if (pixels > MAX_TRANSFORMER_PIXELS - totalPixels) + return false; + + totalPixels += pixels; + return true; + }; + + if (!ADD_CANVAS(plan.sourceBox)) + return false; + + for (const auto& stage : plan.stages) { + if (stage.allocatesOutputBuffer && !ADD_CANVAS(stage.outputBox)) + return false; + } + + return !hasMatte || totalPixels <= MAX_TRANSFORMER_PIXELS / 2; +} + void IElementRenderer::drawTransformedWindow(WP element, const CRegion& damage) { if (!element || !element->m_data.pass) return; auto& renderData = g_pHyprRenderer->m_renderData; const auto pMonitor = renderData.pMonitor; - const auto fb = pMonitor->resources()->getUnusedWorkBuffer(); - if (!fb) + if (!pMonitor) return; - const CRegion oldDamage = renderData.damage.copy(); - const CRegion oldFinalDamage = renderData.finalDamage.copy(); - const auto oldWindow = renderData.currentWindow; - const auto oldSurface = renderData.surface; - const CBox oldClipBox = renderData.clipBox; - const auto oldProjection = renderData.projectionType; - const auto oldFBSize = renderData.fbSize; - const bool oldTransformDamage = renderData.transformDamage; + const auto PWINDOW = element->m_data.window.lock(); + bool applyTransformers = PWINDOW && !element->m_data.standalone && !element->m_data.renderingSnapshot; + const CBox MONITORBOX = CBox{{}, pMonitor->m_size}; - g_pHyprRenderer->setProjectionType(RPT_MONITOR); - renderData.transformDamage = false; + SMotionBlurData motionBlur = applyTransformers ? element->m_data.motionBlur : SMotionBlurData{}; + const CBox visualBox = applyTransformers ? (motionBlur.enabled ? motionBlur.extents() : element->m_data.transformedBox) : element->m_data.currentBox; + const bool HASRENDERMODIFIERS = renderData.renderModif.enabled && !renderData.renderModif.modifs.empty(); + CBox visibleOutput = HASRENDERMODIFIERS ? visualBox : visualBox.intersection(MONITORBOX); + if (visibleOutput.empty()) + return; - SP last; + CBox transformerOutput = motionBlur.enabled ? motionBlurSourceBox(motionBlur, visibleOutput, 1.0 / pMonitor->m_scale) : visibleOutput; - CBox currentBox = element->m_data.currentBox.copy(); - CBox sourceBox = currentBox; + SWindowTransformPlan plan; + if (applyTransformers) + plan = PWINDOW->m_transformers.plan(element->m_data.currentBox, transformerOutput); + else { + plan.sourceBox = transformerOutput.intersection(element->m_data.currentBox); + plan.outputBox = plan.sourceBox; + } - if (const auto PWINDOW = element->m_data.window.lock(); !element->m_data.standalone && !element->m_data.renderingSnapshot && PWINDOW) - sourceBox = PWINDOW->m_transformers.sourceBoxForRender(currentBox, CBox{{}, pMonitor->m_size}); - - const auto SOURCEOFFSET = (sourceBox.pos() - currentBox.pos()) * pMonitor->m_scale; - - const auto transformWindowFB = [&element, &pMonitor, &sourceBox](SP in) { - SP transformed = in; - - if (const auto PWINDOW = element->m_data.window.lock()) - transformed = PWINDOW->m_transformers.transform(in, - Render::SWindowTransformContext{.currentBox = element->m_data.currentBox, - .sourceBox = sourceBox, - .monitor = pMonitor, - .standalone = element->m_data.standalone, - .renderingSnapshot = element->m_data.renderingSnapshot}); - - return transformed; + const auto OLDRENDERDATA = renderData; + const bool OLDBLURSHOULDRENDER = pMonitor->m_blurFBShouldRender; + const auto renderNestedDirect = [&] { + { + auto guard = g_pHyprRenderer->bindTempFB(OLDRENDERDATA.currentFB); + renderData.currentWindow = element->m_data.window; + renderData.surface.reset(); + renderData.clipBox = {}; + renderData.renderingTransformedSource = false; + element->m_data.pass->render(damage); + } + renderData = OLDRENDERDATA; + pMonitor->m_blurFBShouldRender = OLDBLURSHOULDRENDER; }; + if (plan.sourceBox.empty() || !transformPlanFits(plan, pMonitor->m_scale, element->m_data.blur)) { + applyTransformers = false; + motionBlur = {}; + visibleOutput = HASRENDERMODIFIERS ? element->m_data.currentBox : element->m_data.currentBox.intersection(MONITORBOX); + if (visibleOutput.empty()) + return; + + plan = {}; + plan.sourceBox = visibleOutput; + plan.outputBox = visibleOutput; + if (!transformPlanFits(plan, pMonitor->m_scale, element->m_data.blur)) { + renderNestedDirect(); + return; + } + } + + const double CANVASPADDING = 1.0 / pMonitor->m_scale; + CBox SOURCECANVAS = pixelBoxForLogical(plan.sourceBox.copy().expand(CANVASPADDING), pMonitor->m_scale); + auto fb = pMonitor->resources()->getUnusedWorkBuffer(SOURCECANVAS.size()); + auto matteFB = element->m_data.blur ? pMonitor->resources()->getUnusedWorkBuffer(SOURCECANVAS.size()) : nullptr; + if (!fb || (element->m_data.blur && !matteFB)) { + fb.reset(); + matteFB.reset(); + + applyTransformers = false; + motionBlur = {}; + plan = {}; + plan.sourceBox = MONITORBOX; + plan.outputBox = MONITORBOX; + SOURCECANVAS = {0, 0, pMonitor->m_transformedSize.x, pMonitor->m_transformedSize.y}; + fb = pMonitor->resources()->getUnusedWorkBuffer(); + matteFB = element->m_data.blur ? pMonitor->resources()->getUnusedWorkBuffer() : nullptr; + if (!fb || (element->m_data.blur && !matteFB)) { + renderNestedDirect(); + return; + } + } + + const CRegion CANVASDAMAGE = CRegion{0, 0, sc(SOURCECANVAS.w), sc(SOURCECANVAS.h)}; + const Vector2D CANVASTRANSLATION = -SOURCECANVAS.pos(); + const auto transformWindowFB = [&](const SWindowTransformBuffer& in) { + if (!applyTransformers) + return in; + + return PWINDOW->m_transformers.transform(in, plan, + SWindowTransformContext{ + .currentBox = element->m_data.currentBox, + .inputBox = plan.sourceBox, + .outputBox = plan.outputBox, + .monitor = pMonitor, + .standalone = element->m_data.standalone, + .renderingSnapshot = element->m_data.renderingSnapshot, + }); + }; + + SWindowTransformBuffer last; { auto guard = g_pHyprRenderer->bindTempFB(fb); renderData.currentWindow = element->m_data.window; renderData.surface.reset(); - renderData.clipBox = {}; - renderData.damage = CRegion{0, 0, sc(pMonitor->m_transformedSize.x), sc(pMonitor->m_transformedSize.y)}; + renderData.clipBox = {}; + renderData.damage = CANVASDAMAGE; + renderData.transformDamage = false; + renderData.fbSize = SOURCECANVAS.size(); + g_pHyprRenderer->setProjectionType(RPT_EXPORT); g_pHyprRenderer->draw(CClearPassElement::SClearData{CHyprColor(0, 0, 0, 0)}); - const auto SAVEDRENDERMODIF = renderData.renderModif; - const auto SAVEDNOSIMPLIFY = renderData.noSimplify; - const auto SAVEDTRANSFORMEDSOURCE = renderData.renderingTransformedSource; - - if (SOURCEOFFSET != Vector2D{}) { - renderData.renderModif.modifs.emplace_back(std::make_pair<>(SRenderModifData::eRenderModifType::RMOD_TYPE_TRANSLATE, SOURCEOFFSET)); - renderData.noSimplify = true; - } - + renderData.renderModif = {}; + renderData.renderModif.modifs.emplace_back(std::make_pair<>(SRenderModifData::eRenderModifType::RMOD_TYPE_TRANSLATE, CANVASTRANSLATION)); + renderData.noSimplify = true; renderData.renderingTransformedSource = true; + element->m_data.pass->render(CANVASDAMAGE); - CRegion nestedDamage; - if (SOURCEOFFSET != Vector2D{}) - nestedDamage = CRegion{0, 0, sc(pMonitor->m_transformedSize.x), sc(pMonitor->m_transformedSize.y)}; - else { - CBox nestedDamageBox = currentBox.copy().scale(pMonitor->m_scale).round(); - nestedDamageBox.expand(2); - nestedDamage = CRegion{nestedDamageBox}; - } - - element->m_data.pass->render(nestedDamage); - - renderData.renderModif = SAVEDRENDERMODIF; - renderData.noSimplify = SAVEDNOSIMPLIFY; - renderData.renderingTransformedSource = SAVEDTRANSFORMEDSOURCE; - - last = transformWindowFB(fb); + renderData.renderModif = {}; + last = transformWindowFB({.framebuffer = fb, .box = SOURCECANVAS}); } - SP blurAlphaMatteFB; // keep the matte FB out of the workbuffer pool until the final draw samples it + SP blurAlphaMatteFB; SP blurAlphaMatte; - if (element->m_data.blur) { - const auto matteFB = pMonitor->resources()->getUnusedWorkBuffer(); - if (matteFB) { - SP matteLast; + if (matteFB) { + SWindowTransformBuffer matteLast; + { + auto guard = g_pHyprRenderer->bindTempFB(matteFB); - { - auto guard = g_pHyprRenderer->bindTempFB(matteFB); + renderData.currentWindow = element->m_data.window; + renderData.surface.reset(); + renderData.clipBox = {}; + renderData.damage = CANVASDAMAGE; + renderData.transformDamage = false; + renderData.fbSize = SOURCECANVAS.size(); + g_pHyprRenderer->setProjectionType(RPT_EXPORT); - renderData.currentWindow = element->m_data.window; - renderData.surface.reset(); - renderData.clipBox = {}; - renderData.damage = CRegion{0, 0, sc(pMonitor->m_transformedSize.x), sc(pMonitor->m_transformedSize.y)}; + g_pHyprRenderer->draw(CClearPassElement::SClearData{CHyprColor(0, 0, 0, 1)}); - g_pHyprRenderer->draw(CClearPassElement::SClearData{CHyprColor(0, 0, 0, 1)}); + renderData.renderModif = {}; + renderData.renderModif.modifs.emplace_back(std::make_pair<>(SRenderModifData::eRenderModifType::RMOD_TYPE_TRANSLATE, CANVASTRANSLATION)); - const auto SAVEDRENDERMODIF = renderData.renderModif; - if (SOURCEOFFSET != Vector2D{}) - renderData.renderModif.modifs.emplace_back(std::make_pair<>(SRenderModifData::eRenderModifType::RMOD_TYPE_TRANSLATE, SOURCEOFFSET)); + g_pHyprRenderer->draw( + CRectPassElement::SRectData{ + .box = element->m_data.blurBox, + .color = CHyprColor(1, 1, 1, 1), + .round = element->m_data.blurRound, + .roundingPower = element->m_data.blurRoundingPower, + }, + CANVASDAMAGE); - g_pHyprRenderer->draw( - CRectPassElement::SRectData{ - .box = element->m_data.blurBox, - .color = CHyprColor(1, 1, 1, 1), - .round = element->m_data.blurRound, - .roundingPower = element->m_data.blurRoundingPower, - }, - renderData.damage); + renderData.renderModif = {}; + matteLast = transformWindowFB({.framebuffer = matteFB, .box = SOURCECANVAS}); + } - renderData.renderModif = SAVEDRENDERMODIF; - - matteLast = transformWindowFB(matteFB); - } - - if (matteLast && matteLast->getTexture()) { - blurAlphaMatteFB = matteLast; - blurAlphaMatte = matteLast->getTexture(); - } + if (last.framebuffer && matteLast.framebuffer && matteLast.framebuffer->getTexture() && matteLast.success == last.success && matteLast.box == last.box && + matteLast.framebuffer->getTexture()->m_size == last.framebuffer->getTexture()->m_size) { + blurAlphaMatteFB = matteLast.framebuffer; + blurAlphaMatte = matteLast.framebuffer->getTexture(); } } - renderData.damage = oldDamage; - renderData.finalDamage = oldFinalDamage; - renderData.currentWindow = oldWindow; - renderData.surface = oldSurface; - renderData.clipBox = oldClipBox; - renderData.fbSize = oldFBSize; - renderData.transformDamage = oldTransformDamage; - g_pHyprRenderer->setProjectionType(oldProjection); + renderData = OLDRENDERDATA; + pMonitor->m_blurFBShouldRender = OLDBLURSHOULDRENDER; - if (!last || !last->getTexture()) + if (!last.framebuffer || !last.framebuffer->getTexture()) return; - SMotionBlurData motionBlur = element->m_data.motionBlur; - currentBox.round(); - CBox outputBox = currentBox; + const CBox EXPECTEDOUTPUT = plan.stages.empty() ? SOURCECANVAS : pixelBoxForLogical(plan.stages.back().outputBox.copy().expand(CANVASPADDING), pMonitor->m_scale); + if (!last.success || last.box != EXPECTEDOUTPUT) + motionBlur = {}; + CBox outputBox = last.box; if (motionBlur.enabled) { motionBlur.previous.scale(pMonitor->m_scale); motionBlur.current.scale(pMonitor->m_scale); - motionBlur.source = motionBlur.current; - motionBlur.sourceTexSize = last->getTexture()->m_size; - outputBox = motionBlur.extents().round(); - } else - outputBox = {0, 0, pMonitor->m_transformedSize.x, pMonitor->m_transformedSize.y}; + motionBlur.source = motionBlur.current; + motionBlur.sourceTexOrigin = last.box.pos(); + motionBlur.sourceTexSize = last.framebuffer->getTexture()->m_size; + outputBox = pixelBoxForLogical(visibleOutput, pMonitor->m_scale); + } CTexPassElement::SRenderData data; - data.tex = last->getTexture(); + data.tex = last.framebuffer->getTexture(); data.box = outputBox; data.a = 1.F; data.motionBlur = motionBlur; - const CRegion drawDamage = damage.copy().intersect(outputBox); + CRegion outputRegion{outputBox}; + renderData.renderModif.applyToRegion(outputRegion); + const CRegion drawDamage = damage.copy().intersect(outputRegion); + data.damage = drawDamage; + data.useProvidedDamage = true; - if (element->m_data.blur) { + if (element->m_data.blur && blurAlphaMatte) { data.blur = true; data.forceBlurBlend = true; data.blockBlurOptimization = true; diff --git a/src/render/OpenGL.cpp b/src/render/OpenGL.cpp index 626fe586a..346a52db1 100644 --- a/src/render/OpenGL.cpp +++ b/src/render/OpenGL.cpp @@ -1517,11 +1517,11 @@ WP CHyprOpenGLImpl::renderToFBInternal(SP tex, const STexture CBox motionSource = data.motionBlur.source; m_renderData.renderModif.applyToBox(motionPrev); m_renderData.renderModif.applyToBox(motionCurr); - m_renderData.renderModif.applyToBox(motionSource); shader->setUniformFloat4(SHADER_MOTION_PREV_BOX, motionPrev.x, motionPrev.y, motionPrev.w, motionPrev.h); shader->setUniformFloat4(SHADER_MOTION_CURR_BOX, motionCurr.x, motionCurr.y, motionCurr.w, motionCurr.h); shader->setUniformFloat4(SHADER_MOTION_SOURCE_BOX, motionSource.x, motionSource.y, motionSource.w, motionSource.h); + shader->setUniformFloat2(SHADER_MOTION_SOURCE_TEX_ORIGIN, data.motionBlur.sourceTexOrigin.x, data.motionBlur.sourceTexOrigin.y); shader->setUniformFloat2(SHADER_MOTION_SOURCE_TEX_SIZE, data.motionBlur.sourceTexSize.x, data.motionBlur.sourceTexSize.y); shader->setUniformInt(SHADER_MOTION_SAMPLES, data.motionBlur.samples); } diff --git a/src/render/Shader.cpp b/src/render/Shader.cpp index 611726edb..ba7e53159 100644 --- a/src/render/Shader.cpp +++ b/src/render/Shader.cpp @@ -147,24 +147,25 @@ void CShader::getUniformLocations() { m_uniformLocations[SHADER_LUT_SIZE] = getUniform("iccLutSize"); m_uniformLocations[SHADER_TONEMAP_MODE] = getUniform("tonemapMode"); // - m_uniformLocations[SHADER_TEX] = getUniform("tex"); - m_uniformLocations[SHADER_BLURRED_BG] = getUniform("blurredBG"); - m_uniformLocations[SHADER_UV_SIZE] = getUniform("uvSize"); - m_uniformLocations[SHADER_UV_OFFSET] = getUniform("uvOffset"); - m_uniformLocations[SHADER_MOTION_PREV_BOX] = getUniform("motionPrevBox"); - m_uniformLocations[SHADER_MOTION_CURR_BOX] = getUniform("motionCurrBox"); - m_uniformLocations[SHADER_MOTION_SOURCE_BOX] = getUniform("motionSourceBox"); - m_uniformLocations[SHADER_MOTION_SOURCE_TEX_SIZE] = getUniform("motionSourceTexSize"); - m_uniformLocations[SHADER_MOTION_SAMPLES] = getUniform("motionSamples"); - m_uniformLocations[SHADER_BLUR_ALPHA_MATTE] = getUniform("blurAlphaMatte"); - m_uniformLocations[SHADER_BLUR_ALPHA] = getUniform("blurAlpha"); - m_uniformLocations[SHADER_ALPHA] = getUniform("alpha"); - m_uniformLocations[SHADER_POS_ATTRIB] = getAttrib("pos"); - m_uniformLocations[SHADER_TEX_ATTRIB] = getAttrib("texcoord"); - m_uniformLocations[SHADER_MATTE_TEX_ATTRIB] = getAttrib("texcoordMatte"); - m_uniformLocations[SHADER_DISCARD_OPAQUE] = getUniform("discardOpaque"); - m_uniformLocations[SHADER_DISCARD_ALPHA] = getUniform("discardAlpha"); - m_uniformLocations[SHADER_DISCARD_ALPHA_VALUE] = getUniform("discardAlphaValue"); + m_uniformLocations[SHADER_TEX] = getUniform("tex"); + m_uniformLocations[SHADER_BLURRED_BG] = getUniform("blurredBG"); + m_uniformLocations[SHADER_UV_SIZE] = getUniform("uvSize"); + m_uniformLocations[SHADER_UV_OFFSET] = getUniform("uvOffset"); + m_uniformLocations[SHADER_MOTION_PREV_BOX] = getUniform("motionPrevBox"); + m_uniformLocations[SHADER_MOTION_CURR_BOX] = getUniform("motionCurrBox"); + m_uniformLocations[SHADER_MOTION_SOURCE_BOX] = getUniform("motionSourceBox"); + m_uniformLocations[SHADER_MOTION_SOURCE_TEX_ORIGIN] = getUniform("motionSourceTexOrigin"); + m_uniformLocations[SHADER_MOTION_SOURCE_TEX_SIZE] = getUniform("motionSourceTexSize"); + m_uniformLocations[SHADER_MOTION_SAMPLES] = getUniform("motionSamples"); + m_uniformLocations[SHADER_BLUR_ALPHA_MATTE] = getUniform("blurAlphaMatte"); + m_uniformLocations[SHADER_BLUR_ALPHA] = getUniform("blurAlpha"); + m_uniformLocations[SHADER_ALPHA] = getUniform("alpha"); + m_uniformLocations[SHADER_POS_ATTRIB] = getAttrib("pos"); + m_uniformLocations[SHADER_TEX_ATTRIB] = getAttrib("texcoord"); + m_uniformLocations[SHADER_MATTE_TEX_ATTRIB] = getAttrib("texcoordMatte"); + m_uniformLocations[SHADER_DISCARD_OPAQUE] = getUniform("discardOpaque"); + m_uniformLocations[SHADER_DISCARD_ALPHA] = getUniform("discardAlpha"); + m_uniformLocations[SHADER_DISCARD_ALPHA_VALUE] = getUniform("discardAlphaValue"); /* set in createVao m_uniformLocations[SHADER_SHADER_VAO] m_uniformLocations[SHADER_SHADER_VBO] diff --git a/src/render/Shader.hpp b/src/render/Shader.hpp index fa3daae08..bee4cd6ac 100644 --- a/src/render/Shader.hpp +++ b/src/render/Shader.hpp @@ -87,6 +87,7 @@ enum eShaderUniform : uint8_t { SHADER_MOTION_PREV_BOX, SHADER_MOTION_CURR_BOX, SHADER_MOTION_SOURCE_BOX, + SHADER_MOTION_SOURCE_TEX_ORIGIN, SHADER_MOTION_SOURCE_TEX_SIZE, SHADER_MOTION_SAMPLES, SHADER_BLUR_ALPHA_MATTE, diff --git a/src/render/pass/TexPassElement.hpp b/src/render/pass/TexPassElement.hpp index 66d9eed05..7c2fb5288 100644 --- a/src/render/pass/TexPassElement.hpp +++ b/src/render/pass/TexPassElement.hpp @@ -19,10 +19,11 @@ enum eWrapMode : uint8_t { }; struct SMotionBlurData { - bool enabled = false; - CBox previous = {}; - CBox current = {}; - CBox source = {}; + bool enabled = false; + CBox previous = {}; + CBox current = {}; + CBox source = {}; + Vector2D sourceTexOrigin = {}; Vector2D sourceTexSize; int samples = 1; @@ -38,8 +39,9 @@ class CTexPassElement : public IPassElement { float blurA = 1.F; float overallA = 1.F; CRegion damage; - int round = 0; - float roundingPower = 2.0f; + bool useProvidedDamage = false; + int round = 0; + float roundingPower = 2.0f; CBox clipBox; bool blur = false; bool forceBlurBlend = false; diff --git a/src/render/shaders/glsl/motion_blur.glsl b/src/render/shaders/glsl/motion_blur.glsl index 9e368f44e..a5a394b17 100644 --- a/src/render/shaders/glsl/motion_blur.glsl +++ b/src/render/shaders/glsl/motion_blur.glsl @@ -3,7 +3,8 @@ const int MOTION_BLUR_MAX_SAMPLES = 64; -vec4 motionBlurSample(sampler2D texSampler, vec4 previousBox, vec4 currentBox, vec4 sourceBox, vec2 sourceTexSize, int requestedSamples, bool useRGBA) { +vec4 motionBlurSample(sampler2D texSampler, vec4 previousBox, vec4 currentBox, vec4 sourceBox, vec2 sourceTexOrigin, vec2 sourceTexSize, int requestedSamples, + bool useRGBA) { int samples = int(clamp(float(requestedSamples), 1.0, float(MOTION_BLUR_MAX_SAMPLES))); vec4 accumulated = vec4(0.0); @@ -13,12 +14,15 @@ vec4 motionBlurSample(sampler2D texSampler, vec4 previousBox, vec4 currentBox, v float t = float(i) / float(samples); vec4 box = mix(currentBox, previousBox, t); + if (box.z <= 0.0 || box.w <= 0.0) + continue; + vec2 uv = (vec2(gl_FragCoord) - box.xy) / box.zw; if (uv.x < 0.0 || uv.y < 0.0 || uv.x > 1.0 || uv.y > 1.0) continue; - vec2 sourceUV = (sourceBox.xy + uv * sourceBox.zw) / sourceTexSize; + vec2 sourceUV = (sourceBox.xy + uv * sourceBox.zw - sourceTexOrigin) / sourceTexSize; vec4 color = texture(texSampler, sourceUV); if (!useRGBA) color.a = 1.0; diff --git a/src/render/shaders/glsl/surface.frag b/src/render/shaders/glsl/surface.frag index 08d14db98..174c41e4c 100644 --- a/src/render/shaders/glsl/surface.frag +++ b/src/render/shaders/glsl/surface.frag @@ -44,6 +44,7 @@ const float roundingPower = 2.0; uniform vec4 motionPrevBox; uniform vec4 motionCurrBox; uniform vec4 motionSourceBox; +uniform vec2 motionSourceTexOrigin; uniform vec2 motionSourceTexSize; uniform int motionSamples; #include "motion_blur.glsl" @@ -68,9 +69,10 @@ layout(location = 1) out vec4 mirrorColor; #endif void main() { #if USE_MOTION_BLUR - vec4 pixColor = motionBlurSample(tex, motionPrevBox, motionCurrBox, motionSourceBox, motionSourceTexSize, motionSamples, USE_RGBA == 1); + vec4 pixColor = motionBlurSample(tex, motionPrevBox, motionCurrBox, motionSourceBox, motionSourceTexOrigin, motionSourceTexSize, motionSamples, USE_RGBA == 1); #if USE_BLUR_MATTE - float blurAlphaMask = clamp(motionBlurSample(blurAlphaMatte, motionPrevBox, motionCurrBox, motionSourceBox, motionSourceTexSize, motionSamples, true).r, 0.0, 1.0); + float blurAlphaMask = + clamp(motionBlurSample(blurAlphaMatte, motionPrevBox, motionCurrBox, motionSourceBox, motionSourceTexOrigin, motionSourceTexSize, motionSamples, true).r, 0.0, 1.0); #endif #else #if USE_RGBA diff --git a/src/render/transformer/MotionBlurTransformer.cpp b/src/render/transformer/MotionBlurTransformer.cpp index 8babff1d5..1be606174 100644 --- a/src/render/transformer/MotionBlurTransformer.cpp +++ b/src/render/transformer/MotionBlurTransformer.cpp @@ -30,7 +30,7 @@ bool CMotionBlurTransformer::shouldEnable(PHLWINDOW window) { return *PMBENABLED && *PMBSAMPLES > 1 && !Fullscreen::controller()->isFullscreen(window); } -SP CMotionBlurTransformer::transform(SP in, const SWindowTransformContext&) { +SWindowTransformBuffer CMotionBlurTransformer::transform(const SWindowTransformBuffer& in, const SWindowTransformContext&) { return in; } @@ -42,6 +42,14 @@ bool CMotionBlurTransformer::active() const { return state(true).has_value(); } +bool CMotionBlurTransformer::allocatesOutputBuffer() const { + return false; +} + +CBox CMotionBlurTransformer::sourceBoxForOutput(const CBox& outputBox, const CBox& inputBox) const { + return outputBox.intersection(inputBox); +} + CBox CMotionBlurTransformer::transformBoxForDamage(const CBox& currentBox) const { const auto STATE = state(true); if (!STATE) diff --git a/src/render/transformer/MotionBlurTransformer.hpp b/src/render/transformer/MotionBlurTransformer.hpp index 6c522d689..aa6220e4c 100644 --- a/src/render/transformer/MotionBlurTransformer.hpp +++ b/src/render/transformer/MotionBlurTransformer.hpp @@ -10,9 +10,11 @@ namespace Render { static bool shouldEnable(PHLWINDOW window); - virtual SP transform(SP in, const SWindowTransformContext& context); + virtual SWindowTransformBuffer transform(const SWindowTransformBuffer& in, const SWindowTransformContext& context); virtual int priority() const; virtual bool active() const; + virtual bool allocatesOutputBuffer() const; + virtual CBox sourceBoxForOutput(const CBox& outputBox, const CBox& inputBox) const; virtual CBox transformBoxForDamage(const CBox& currentBox) const; virtual void amendTransformedRenderData(const CBox& currentBox, SMotionBlurData* pMotionBlurData); diff --git a/src/render/transformer/Transformer.cpp b/src/render/transformer/Transformer.cpp index cb6031b91..57678d925 100644 --- a/src/render/transformer/Transformer.cpp +++ b/src/render/transformer/Transformer.cpp @@ -1,7 +1,20 @@ #include "Transformer.hpp" +#include + using namespace Render; +CBox Render::pixelBoxForLogical(const CBox& box, double scale) { + if (!std::isfinite(box.x) || !std::isfinite(box.y) || !std::isfinite(box.w) || !std::isfinite(box.h) || !std::isfinite(scale) || box.w <= 0.0 || box.h <= 0.0 || scale <= 0.0) + return {}; + + const double x1 = std::floor(box.x * scale); + const double y1 = std::floor(box.y * scale); + const double x2 = std::ceil((box.x + box.w) * scale); + const double y2 = std::ceil((box.y + box.h) * scale); + return {x1, y1, x2 - x1, y2 - y1}; +} + void IWindowTransformer::preWindowRender(CSurfacePassElement::SRenderData* pRenderData) { ; } @@ -18,12 +31,16 @@ bool IWindowTransformer::blocksDirectScanout() const { return active(); } +bool IWindowTransformer::allocatesOutputBuffer() const { + return true; +} + CBox IWindowTransformer::transformedExtents(const CBox& currentBox) const { return currentBox; } -CBox IWindowTransformer::sourceBoxForRender(const CBox& currentBox, const CBox&) const { - return currentBox; +CBox IWindowTransformer::sourceBoxForOutput(const CBox&, const CBox& inputBox) const { + return inputBox; } CBox IWindowTransformer::transformBoxForDamage(const CBox& currentBox) const { diff --git a/src/render/transformer/Transformer.hpp b/src/render/transformer/Transformer.hpp index bcd2e5ccb..7f793bf61 100644 --- a/src/render/transformer/Transformer.hpp +++ b/src/render/transformer/Transformer.hpp @@ -5,12 +5,37 @@ #include "../Framebuffer.hpp" #include "../pass/SurfacePassElement.hpp" +#include + class CEventLoopTimer; namespace Render { + CBox pixelBoxForLogical(const CBox& box, double scale); + + struct SWindowTransformBuffer { + SP framebuffer; + CBox box; + bool success = true; + }; + + struct SWindowTransformStage { + CBox fullInputBox; + CBox fullOutputBox; + CBox inputBox; + CBox outputBox; + bool allocatesOutputBuffer = true; + }; + + struct SWindowTransformPlan { + CBox sourceBox; + CBox outputBox; + std::vector stages; + }; + struct SWindowTransformContext { CBox currentBox; - CBox sourceBox; + CBox inputBox; + CBox outputBox; PHLMONITORREF monitor; bool standalone = false; bool renderingSnapshot = false; @@ -26,15 +51,16 @@ namespace Render { virtual ~IWindowTransformer() = default; // called by Hyprland. For more data about what is being rendered, inspect render data. - // returns the out fb. - virtual SP transform(SP in, const SWindowTransformContext& context) = 0; + // returns the output framebuffer and the monitor-local pixel box represented by it. + virtual SWindowTransformBuffer transform(const SWindowTransformBuffer& in, const SWindowTransformContext& context) = 0; - virtual int priority() const; - virtual bool active() const; - virtual bool blocksDirectScanout() const; - virtual CBox transformedExtents(const CBox& currentBox) const; - virtual CBox sourceBoxForRender(const CBox& currentBox, const CBox& monitorBox) const; - virtual CBox transformBoxForDamage(const CBox& currentBox) const; + virtual int priority() const; + virtual bool active() const; + virtual bool blocksDirectScanout() const; + virtual bool allocatesOutputBuffer() const; + virtual CBox transformedExtents(const CBox& currentBox) const; + virtual CBox sourceBoxForOutput(const CBox& outputBox, const CBox& inputBox) const; + virtual CBox transformBoxForDamage(const CBox& currentBox) const; // called by Hyprland before a window main pass is started. virtual void preWindowRender(CSurfacePassElement::SRenderData* pRenderData); diff --git a/src/render/transformer/TransformerList.cpp b/src/render/transformer/TransformerList.cpp index 9ba33a674..298fe4c14 100644 --- a/src/render/transformer/TransformerList.cpp +++ b/src/render/transformer/TransformerList.cpp @@ -22,16 +22,36 @@ CBox CWindowTransformerList::transformedExtents(const CBox& currentBox) const { return box; } -CBox CWindowTransformerList::sourceBoxForRender(const CBox& currentBox, const CBox& monitorBox) const { - CBox box = currentBox; +SWindowTransformPlan CWindowTransformerList::plan(const CBox& currentBox, const CBox& outputBox) const { + SWindowTransformPlan plan; + plan.outputBox = outputBox; + + CBox fullBox = currentBox; for (auto const& transformer : m_transformers) { if (!transformer->active()) continue; - box = transformer->sourceBoxForRender(box, monitorBox); + auto& stage = plan.stages.emplace_back(); + stage.fullInputBox = fullBox; + stage.fullOutputBox = transformer->transformedExtents(fullBox); + stage.allocatesOutputBuffer = transformer->allocatesOutputBuffer(); + fullBox = stage.fullOutputBox; } - return box; + CBox requiredBox = outputBox.intersection(fullBox); + size_t stageIndex = plan.stages.size(); + for (auto transformer = m_transformers.rbegin(); transformer != m_transformers.rend(); ++transformer) { + if (!(*transformer)->active()) + continue; + + auto& stage = plan.stages[--stageIndex]; + stage.outputBox = requiredBox.intersection(stage.fullOutputBox); + requiredBox = (*transformer)->sourceBoxForOutput(stage.outputBox, stage.fullInputBox).intersection(stage.fullInputBox); + stage.inputBox = requiredBox; + } + + plan.sourceBox = requiredBox; + return plan; } CBox CWindowTransformerList::transformBoxForDamage(const CBox& currentBox) const { @@ -60,13 +80,25 @@ void CWindowTransformerList::amendTransformedRenderData(const CBox& currentBox, } } -SP CWindowTransformerList::transform(SP in, const SWindowTransformContext& context) const { - SP last = in; +SWindowTransformBuffer CWindowTransformerList::transform(const SWindowTransformBuffer& in, const SWindowTransformPlan& plan, const SWindowTransformContext& context) const { + SWindowTransformBuffer last = in; + size_t stage = 0; for (auto const& transformer : m_transformers) { if (!transformer->active()) continue; - last = transformer->transform(last, context); + if (stage >= plan.stages.size()) + break; + + auto stageContext = context; + stageContext.currentBox = plan.stages[stage].fullInputBox; + stageContext.inputBox = plan.stages[stage].inputBox; + stageContext.outputBox = plan.stages[stage].outputBox; + last = transformer->transform(last, stageContext); + if (!last.success) + break; + + ++stage; } return last; diff --git a/src/render/transformer/TransformerList.hpp b/src/render/transformer/TransformerList.hpp index 1455b59fc..4dc880d7f 100644 --- a/src/render/transformer/TransformerList.hpp +++ b/src/render/transformer/TransformerList.hpp @@ -38,17 +38,17 @@ namespace Render { return nullptr; } - bool empty() const; - bool blocksDirectScanout() const; - CBox transformedExtents(const CBox& currentBox) const; - CBox sourceBoxForRender(const CBox& currentBox, const CBox& monitorBox) const; - CBox transformBoxForDamage(const CBox& currentBox) const; + bool empty() const; + bool blocksDirectScanout() const; + CBox transformedExtents(const CBox& currentBox) const; + SWindowTransformPlan plan(const CBox& currentBox, const CBox& outputBox) const; + CBox transformBoxForDamage(const CBox& currentBox) const; - void preWindowRender(CSurfacePassElement::SRenderData* pRenderData) const; - void amendTransformedRenderData(const CBox& currentBox, SMotionBlurData* pMotionBlurData) const; - SP transform(SP in, const SWindowTransformContext& context) const; + void preWindowRender(CSurfacePassElement::SRenderData* pRenderData) const; + void amendTransformedRenderData(const CBox& currentBox, SMotionBlurData* pMotionBlurData) const; + SWindowTransformBuffer transform(const SWindowTransformBuffer& in, const SWindowTransformPlan& plan, const SWindowTransformContext& context) const; - void removeInactive(); + void removeInactive(); private: void sort(); diff --git a/src/render/transformer/WobbleTransformer.cpp b/src/render/transformer/WobbleTransformer.cpp index b9e39e8ac..56addab64 100644 --- a/src/render/transformer/WobbleTransformer.cpp +++ b/src/render/transformer/WobbleTransformer.cpp @@ -21,11 +21,7 @@ using namespace Hyprutils::Animation; static CHyprSignalListener g_wobbleTickListener; static constexpr double WOBBLE_EXTENTS_PADDING = 4.0; -static double sourceOffsetForAxis(double min, double size, double monitorSize) { - return monitorSize / 2.0 - (min + size / 2.0); -} - -static void tickWobbles() { +static void tickWobbles() { bool anyActive = false; for (auto const& window : Desktop::windowState()->windows()) { @@ -68,46 +64,55 @@ void CWobbleTransformer::ensureTickListener() { g_wobbleTickListener = Event::bus()->m_events.tick.listen([] { tickWobbles(); }); } -SP CWobbleTransformer::transform(SP in, const SWindowTransformContext& context) { - if (!m_active || context.standalone || context.renderingSnapshot || !context.monitor || !in || !in->getTexture()) +SWindowTransformBuffer CWobbleTransformer::transform(const SWindowTransformBuffer& in, const SWindowTransformContext& context) { + if (!m_active || context.standalone || context.renderingSnapshot || !context.monitor || !in.framebuffer || !in.framebuffer->getTexture()) return in; const auto PWINDOW = m_window.lock(); if (!shouldEnable(PWINDOW)) return in; - const auto OUT = context.monitor->resources()->getUnusedWorkBuffer(); + const double CANVASPADDING = 1.0 / context.monitor->m_scale; + const auto OUTPUTCANVAS = pixelBoxForLogical(context.outputBox.copy().expand(CANVASPADDING), context.monitor->m_scale); + if (OUTPUTCANVAS.empty()) + return in; + + const auto OUT = context.monitor->resources()->getUnusedWorkBuffer(OUTPUTCANVAS.size()); if (!OUT) + return {.framebuffer = in.framebuffer, .box = in.box, .success = false}; + + const double SCALE = context.monitor->m_scale; + const CBox SOURCEBOX = context.currentBox.copy().scale(SCALE); + const CBox OUTPUTBOX = transformedExtents(context.currentBox).scale(SCALE); + const CBox LOCALOUTPUTBOX = OUTPUTBOX.copy().translate(-OUTPUTCANVAS.pos()); + + if (SOURCEBOX.empty() || OUTPUTBOX.empty() || OUTPUTCANVAS.empty()) return in; - const double SCALE = context.monitor->m_scale; - CBox sourceBox = context.sourceBox.empty() ? context.currentBox.copy() : context.sourceBox.copy(); - const auto SOURCEOFFSET = sourceBox.pos() - context.currentBox.pos(); - CBox sourceOutputBox = transformedExtents(context.currentBox).translate(SOURCEOFFSET).scale(SCALE).round(); - CBox outputBox = transformedExtents(context.currentBox).scale(SCALE).round(); - sourceBox.scale(SCALE).round(); - - if (sourceBox.empty() || outputBox.empty()) - return in; - - const auto VERTICES = m_mesh.verticesForBox(sourceBox, sourceOutputBox, in->getTexture()->m_size, SCALE); + const auto VERTICES = m_mesh.verticesForBox(SOURCEBOX, OUTPUTBOX, in.framebuffer->getTexture()->m_size, SCALE, HYPRUTILS_TRANSFORM_NORMAL, in.box.pos()); if (VERTICES.empty()) - return in; + return {.framebuffer = in.framebuffer, .box = in.box, .success = false}; - auto& renderData = g_pHyprRenderer->m_renderData; - const CRegion oldDamage = renderData.damage.copy(); + auto& renderData = g_pHyprRenderer->m_renderData; + const CRegion oldDamage = renderData.damage.copy(); + const auto oldProjection = renderData.projectionType; + const auto oldFBSize = renderData.fbSize; { auto guard = g_pHyprRenderer->bindTempFB(OUT); - renderData.damage = CRegion{0, 0, sc(context.monitor->m_transformedSize.x), sc(context.monitor->m_transformedSize.y)}; + renderData.damage = CRegion{0, 0, sc(OUTPUTCANVAS.w), sc(OUTPUTCANVAS.h)}; + renderData.fbSize = OUTPUTCANVAS.size(); + g_pHyprRenderer->setProjectionType(RPT_EXPORT); g_pHyprRenderer->draw(CClearPassElement::SClearData{CHyprColor(0, 0, 0, 0)}); - GL::g_pHyprOpenGL->renderTextureMesh(in->getTexture(), outputBox, VERTICES, + GL::g_pHyprOpenGL->renderTextureMesh(in.framebuffer->getTexture(), LOCALOUTPUTBOX, VERTICES, GL::CHyprOpenGLImpl::STextureRenderData{.damage = &renderData.damage, .a = 1.F, .allowCustomUV = true}); } renderData.damage = oldDamage; - return OUT; + renderData.fbSize = oldFBSize; + g_pHyprRenderer->setProjectionType(oldProjection); + return {.framebuffer = OUT, .box = OUTPUTCANVAS}; } int CWobbleTransformer::priority() const { @@ -130,17 +135,11 @@ CBox CWobbleTransformer::transformedExtents(const CBox& currentBox) const { return m_mesh.transformedExtents(currentBox).expand(WOBBLE_EXTENTS_PADDING); } -CBox CWobbleTransformer::sourceBoxForRender(const CBox& currentBox, const CBox& monitorBox) const { - if (!m_active || monitorBox.w <= 0.0 || monitorBox.h <= 0.0) - return currentBox; +CBox CWobbleTransformer::sourceBoxForOutput(const CBox& outputBox, const CBox& inputBox) const { + if (!m_active) + return outputBox.intersection(inputBox); - const auto EXTENTS = transformedExtents(currentBox); - const Vector2D OFFSET = { - sourceOffsetForAxis(EXTENTS.x, EXTENTS.w, monitorBox.w), - sourceOffsetForAxis(EXTENTS.y, EXTENTS.h, monitorBox.h), - }; - - return currentBox.copy().translate(OFFSET); + return m_mesh.sourceBoxForOutput(inputBox, outputBox).expand(WOBBLE_EXTENTS_PADDING).intersection(inputBox); } void CWobbleTransformer::record(const CBox& previous, const CBox& current, std::optional grabPoint) { diff --git a/src/render/transformer/WobbleTransformer.hpp b/src/render/transformer/WobbleTransformer.hpp index fb644c3bc..9f2cc91a4 100644 --- a/src/render/transformer/WobbleTransformer.hpp +++ b/src/render/transformer/WobbleTransformer.hpp @@ -13,20 +13,20 @@ namespace Render { CWobbleTransformer(PHLWINDOWREF window); virtual ~CWobbleTransformer() = default; - static bool shouldEnable(PHLWINDOW window); - static void ensureTickListener(); + static bool shouldEnable(PHLWINDOW window); + static void ensureTickListener(); - virtual SP transform(SP in, const SWindowTransformContext& context); - virtual int priority() const; - virtual bool active() const; - virtual bool blocksDirectScanout() const; - virtual CBox transformedExtents(const CBox& currentBox) const; - virtual CBox sourceBoxForRender(const CBox& currentBox, const CBox& monitorBox) const; + virtual SWindowTransformBuffer transform(const SWindowTransformBuffer& in, const SWindowTransformContext& context); + virtual int priority() const; + virtual bool active() const; + virtual bool blocksDirectScanout() const; + virtual CBox transformedExtents(const CBox& currentBox) const; + virtual CBox sourceBoxForOutput(const CBox& outputBox, const CBox& inputBox) const; - void record(const CBox& previous, const CBox& current, std::optional grabPoint = std::nullopt); - void reset(); - void resetWithDamage(); - bool tick(); + void record(const CBox& previous, const CBox& current, std::optional grabPoint = std::nullopt); + void reset(); + void resetWithDamage(); + bool tick(); private: Hyprutils::Animation::SSpringCurve spring() const; diff --git a/tests/helpers/DeformableMesh.cpp b/tests/helpers/DeformableMesh.cpp index eb2175dfe..408745a5a 100644 --- a/tests/helpers/DeformableMesh.cpp +++ b/tests/helpers/DeformableMesh.cpp @@ -91,6 +91,33 @@ TEST(Helpers, deformableMeshVerticesForBoxBuildsTriangles) { EXPECT_FLOAT_EQ(VERTICES.back().v, 1.F); } +TEST(Helpers, deformableMeshVerticesUseTextureOrigin) { + CDeformableMesh mesh(2); + + const auto VERTICES = mesh.verticesForBox({100, 200, 300, 400}, {100, 200, 300, 400}, {300, 400}, 1.0, HYPRUTILS_TRANSFORM_NORMAL, {100, 200}); + + ASSERT_EQ(VERTICES.size(), 6u); + EXPECT_FLOAT_EQ(VERTICES.front().u, 0.F); + EXPECT_FLOAT_EQ(VERTICES.front().v, 0.F); + EXPECT_FLOAT_EQ(VERTICES.back().u, 1.F); + EXPECT_FLOAT_EQ(VERTICES.back().v, 1.F); +} + +TEST(Helpers, deformableMeshSourceBoxIsLimitedToInput) { + CDeformableMesh mesh(4); + + const CBox PREVIOUS = {0, 0, 100, 100}; + const CBox CURRENT = {10, 0, 100, 100}; + mesh.onPositionUpdate(PREVIOUS, CURRENT, 1.F); + + const auto SOURCE = mesh.sourceBoxForOutput(CURRENT, {20, 20, 20, 20}); + EXPECT_FALSE(SOURCE.empty()); + EXPECT_GE(SOURCE.x, CURRENT.x); + EXPECT_GE(SOURCE.y, CURRENT.y); + EXPECT_LE(SOURCE.x + SOURCE.w, CURRENT.x + CURRENT.w); + EXPECT_LE(SOURCE.y + SOURCE.h, CURRENT.y + CURRENT.h); +} + TEST(Helpers, deformableMeshVerticesForBoxTransformsUVs) { struct STransformCase { eTransform transform = HYPRUTILS_TRANSFORM_NORMAL; diff --git a/tests/render/TransformerList.cpp b/tests/render/TransformerList.cpp index 5aea67e7e..0af83d177 100644 --- a/tests/render/TransformerList.cpp +++ b/tests/render/TransformerList.cpp @@ -8,7 +8,7 @@ class CTestDamageTransformer : public Render::IWindowTransformer { ; } - virtual SP transform(SP in, const Render::SWindowTransformContext& context) { + virtual Render::SWindowTransformBuffer transform(const Render::SWindowTransformBuffer& in, const Render::SWindowTransformContext& context) { (void)context; return in; } @@ -35,6 +35,34 @@ class CTestDamageTransformer : public Render::IWindowTransformer { bool m_active = true; }; +class CTestRegionTransformer : public Render::IWindowTransformer { + public: + CTestRegionTransformer(int priority, double translation) : m_priority(priority), m_translation(translation) { + ; + } + + virtual Render::SWindowTransformBuffer transform(const Render::SWindowTransformBuffer& in, const Render::SWindowTransformContext& context) { + (void)context; + return in; + } + + virtual int priority() const { + return m_priority; + } + + virtual CBox transformedExtents(const CBox& currentBox) const { + return currentBox.copy().translate({m_translation, 0.0}); + } + + virtual CBox sourceBoxForOutput(const CBox& outputBox, const CBox& inputBox) const { + return outputBox.copy().translate({-m_translation, 0.0}).intersection(inputBox); + } + + private: + int m_priority = 0; + double m_translation = 0.0; +}; + TEST(Render, transformerListDamageBoxFollowsPriorityOrder) { Render::CWindowTransformerList list; list.emplace(20, 2.0, 0.0); @@ -60,3 +88,20 @@ TEST(Render, transformerListDamageBoxSkipsInactiveTransformers) { EXPECT_DOUBLE_EQ(OUT.x, 4.0); EXPECT_DOUBLE_EQ(OUT.w, 10.0); } + +TEST(Render, transformerListPlansRequiredSourceInReverseOrder) { + Render::CWindowTransformerList list; + list.emplace(20, 5.0); + list.emplace(10, 10.0); + + const auto PLAN = list.plan({0, 0, 100, 50}, {30, 0, 20, 50}); + + ASSERT_EQ(PLAN.stages.size(), 2u); + EXPECT_EQ(PLAN.sourceBox, CBox(15, 0, 20, 50)); + EXPECT_EQ(PLAN.stages[0].outputBox, CBox(25, 0, 20, 50)); + EXPECT_EQ(PLAN.stages[1].outputBox, CBox(30, 0, 20, 50)); +} + +TEST(Render, transformerPixelBoxRoundsOutward) { + EXPECT_EQ(Render::pixelBoxForLogical({-1.25, 2.25, 3.5, 4.5}, 2.0), CBox(-3, 4, 8, 10)); +}