mirror of
https://github.com/hyprwm/Hyprland.git
synced 2026-08-24 02:24:14 -05:00
renderer/transformer: make window transformers adapt to viewport constraints (#15759)
This commit is contained in:
@@ -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),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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]
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user