renderer/transformer: make window transformers adapt to viewport constraints (#15759)

This commit is contained in:
Vaxry
2026-08-05 20:38:11 +02:00
committed by GitHub
parent cab296c38b
commit 64962f89e4
21 changed files with 598 additions and 230 deletions
+21 -2
View File
@@ -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<SMeshRenderVertex> CDeformableMesh::verticesForBox(const CBox& box, const CBox& outputBox, const Vector2D& textureSize, double displacementScale,
eTransform textureTransform) const {
eTransform textureTransform, const Vector2D& textureOrigin) const {
std::vector<SMeshRenderVertex> 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<SMeshRenderVertex> 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<float>((POS.x - outputBox.x) / outputBox.w),
.y = sc<float>((POS.y - outputBox.y) / outputBox.h),
+2 -1
View File
@@ -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<SMeshRenderVertex> 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 {
+69 -7
View File
@@ -3,13 +3,15 @@
#include "../helpers/cm/ColorManagement.hpp"
#include "../render/Renderer.hpp"
#include <algorithm>
#include <cstdint>
#include <format>
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<CMonitor> 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<Render::IFramebuffer> CMonitorResources::getUnusedWorkBuffer() {
return res.buffer;
}
void CMonitorResources::forEachUnusedFB(std::function<void(SP<Render::IFramebuffer>)> callback, bool includeNamed) {
for (const auto& res : m_workBuffers) {
if (res.buffer.strongRef() > 1)
continue;
SP<Render::IFramebuffer> 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<uint64_t>(size.x) * sc<uint64_t>(size.y);
if (REQUESTEDPIXELS > MAX_SIZED_WORK_BUFFER_PIXELS)
return nullptr;
uint64_t allocatedPixels = 0;
for (const auto& resource : m_sizedWorkBuffers)
allocatedPixels += sc<uint64_t>(resource.buffer->m_size.x) * sc<uint64_t>(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<uint64_t>(resource->buffer->m_size.x) * sc<uint64_t>(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<void(SP<Render::IFramebuffer>)> 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);
+2
View File
@@ -13,6 +13,7 @@ namespace Monitor {
CMonitorResources(WP<CMonitor> monitor, DRMFormat format, Vector2D size, NColorManagement::PImageDescription imageDescription);
SP<Render::IFramebuffer> getUnusedWorkBuffer();
SP<Render::IFramebuffer> getUnusedWorkBuffer(const Vector2D& size);
void forEachUnusedFB(std::function<void(SP<Render::IFramebuffer>)> callback, bool includeNamed = false);
bool hasMirrorFB() const;
bool shouldKeepMirrorFB() const;
@@ -52,6 +53,7 @@ namespace Monitor {
bool m_mirrorFBNeedsFullRefresh = true;
std::vector<SResource> m_workBuffers;
std::vector<SResource> m_sizedWorkBuffers;
friend class CMonitor;
};
+232 -114
View File
@@ -7,6 +7,9 @@
#include <hyprutils/memory/UniquePtr.hpp>
#include <hyprutils/utils/ScopeGuard.hpp>
#include <algorithm>
#include <cstdint>
using namespace Render;
void IElementRenderer::drawElement(WP<IPassElement> element, const CRegion& damage) {
@@ -440,8 +443,8 @@ void IElementRenderer::drawTex(WP<CTexPassElement> 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<CTexPassElement> 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<CTextureMatteElement> 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<double>(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<uint64_t>(canvas.w) * sc<uint64_t>(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<CTransformedWindowPassElement> 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<IFramebuffer> 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<IFramebuffer> in) {
SP<IFramebuffer> 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<int>(SOURCECANVAS.w), sc<int>(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<int>(pMonitor->m_transformedSize.x), sc<int>(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<int>(pMonitor->m_transformedSize.x), sc<int>(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<IFramebuffer> blurAlphaMatteFB; // keep the matte FB out of the workbuffer pool until the final draw samples it
SP<IFramebuffer> blurAlphaMatteFB;
SP<Render::ITexture> blurAlphaMatte;
if (element->m_data.blur) {
const auto matteFB = pMonitor->resources()->getUnusedWorkBuffer();
if (matteFB) {
SP<IFramebuffer> 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<int>(pMonitor->m_transformedSize.x), sc<int>(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;
+1 -1
View File
@@ -1517,11 +1517,11 @@ WP<CShader> CHyprOpenGLImpl::renderToFBInternal(SP<ITexture> 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);
}
+19 -18
View File
@@ -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]
+1
View File
@@ -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,
+8 -6
View File
@@ -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;
+6 -2
View File
@@ -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;
+4 -2
View File
@@ -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
@@ -30,7 +30,7 @@ bool CMotionBlurTransformer::shouldEnable(PHLWINDOW window) {
return *PMBENABLED && *PMBSAMPLES > 1 && !Fullscreen::controller()->isFullscreen(window);
}
SP<Render::IFramebuffer> CMotionBlurTransformer::transform(SP<Render::IFramebuffer> 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)
@@ -10,9 +10,11 @@ namespace Render {
static bool shouldEnable(PHLWINDOW window);
virtual SP<Render::IFramebuffer> transform(SP<Render::IFramebuffer> 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);
+19 -2
View File
@@ -1,7 +1,20 @@
#include "Transformer.hpp"
#include <cmath>
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 {
+35 -9
View File
@@ -5,12 +5,37 @@
#include "../Framebuffer.hpp"
#include "../pass/SurfacePassElement.hpp"
#include <vector>
class CEventLoopTimer;
namespace Render {
CBox pixelBoxForLogical(const CBox& box, double scale);
struct SWindowTransformBuffer {
SP<Render::IFramebuffer> 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<SWindowTransformStage> 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<Render::IFramebuffer> transform(SP<Render::IFramebuffer> 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);
+39 -7
View File
@@ -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<Render::IFramebuffer> CWindowTransformerList::transform(SP<Render::IFramebuffer> in, const SWindowTransformContext& context) const {
SP<Render::IFramebuffer> 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;
+9 -9
View File
@@ -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<Render::IFramebuffer> transform(SP<Render::IFramebuffer> 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();
+34 -35
View File
@@ -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<Render::IFramebuffer> CWobbleTransformer::transform(SP<Render::IFramebuffer> 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<int>(context.monitor->m_transformedSize.x), sc<int>(context.monitor->m_transformedSize.y)};
renderData.damage = CRegion{0, 0, sc<int>(OUTPUTCANVAS.w), sc<int>(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<Vector2D> grabPoint) {
+12 -12
View File
@@ -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<Render::IFramebuffer> transform(SP<Render::IFramebuffer> 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<Vector2D> grabPoint = std::nullopt);
void reset();
void resetWithDamage();
bool tick();
void record(const CBox& previous, const CBox& current, std::optional<Vector2D> grabPoint = std::nullopt);
void reset();
void resetWithDamage();
bool tick();
private:
Hyprutils::Animation::SSpringCurve spring() const;
+27
View File
@@ -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;
+46 -1
View File
@@ -8,7 +8,7 @@ class CTestDamageTransformer : public Render::IWindowTransformer {
;
}
virtual SP<Render::IFramebuffer> transform(SP<Render::IFramebuffer> 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<CTestDamageTransformer>(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<CTestRegionTransformer>(20, 5.0);
list.emplace<CTestRegionTransformer>(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));
}