/* SPDX-FileCopyrightText: 2022 Aleix Pol Gonzalez SPDX-License-Identifier: GPL-2.0-or-later */ #include "regionscreencastsource.h" #include "screencastutils.h" #include "cursor.h" #include "opengl/gltexture.h" #include "opengl/glutils.h" #include #include #include #include #include #include namespace KWin { RegionScreenCastScrapper::RegionScreenCastScrapper(RegionScreenCastSource *source, Output *output) : m_source(source) , m_output(output) { connect(output, &Output::enabledChanged, this, [this]() { if (!m_output->isEnabled()) { m_source->close(); } }); connect(output, &Output::geometryChanged, this, [this]() { m_source->close(); }); connect(output, &Output::outputChange, this, [this](const QRegion &damage) { if (!damage.isEmpty()) { m_source->update(m_output, damage); } }); } RegionScreenCastSource::RegionScreenCastSource(const QRect ®ion, qreal scale, QObject *parent) : ScreenCastSource(parent) , m_region(region) , m_scale(scale) { Q_ASSERT(m_region.isValid()); Q_ASSERT(m_scale > 0); } RegionScreenCastSource::~RegionScreenCastSource() { pause(); } QSize RegionScreenCastSource::textureSize() const { return m_region.size() * m_scale; } qreal RegionScreenCastSource::devicePixelRatio() const { return m_scale; } quint32 RegionScreenCastSource::drmFormat() const { return DRM_FORMAT_ARGB8888; } void RegionScreenCastSource::update(Output *output, const QRegion &damage) { blit(output); const QRegion effectiveDamage = damage .translated(-m_region.topLeft()) .intersected(m_region); const QRegion nativeDamage = scaleRegion(effectiveDamage, m_scale); Q_EMIT frame(nativeDamage); } std::chrono::nanoseconds RegionScreenCastSource::clock() const { return m_last; } void RegionScreenCastSource::blit(Output *output) { m_last = output->renderLoop()->lastPresentationTimestamp(); if (m_renderedTexture) { const auto [outputTexture, colorDescription] = Compositor::self()->scene()->textureForOutput(output); const auto outputGeometry = output->geometry(); if (!outputTexture) { return; } GLFramebuffer::pushFramebuffer(m_target.get()); ShaderBinder shaderBinder(ShaderTrait::MapTexture | ShaderTrait::TransformColorspace); QMatrix4x4 projectionMatrix; projectionMatrix.scale(1, -1); projectionMatrix.ortho(m_region); projectionMatrix.translate(outputGeometry.left(), outputGeometry.top()); shaderBinder.shader()->setUniform(GLShader::Mat4Uniform::ModelViewProjectionMatrix, projectionMatrix); shaderBinder.shader()->setColorspaceUniforms(colorDescription, ColorDescription::sRGB, RenderingIntent::Perceptual); outputTexture->render(outputGeometry.size()); GLFramebuffer::popFramebuffer(); } } void RegionScreenCastSource::ensureTexture() { if (!m_renderedTexture) { m_renderedTexture = GLTexture::allocate(GL_RGBA8, textureSize()); if (!m_renderedTexture) { return; } m_renderedTexture->setContentTransform(OutputTransform::FlipY); m_renderedTexture->setFilter(GL_LINEAR); m_renderedTexture->setWrapMode(GL_CLAMP_TO_EDGE); m_target = std::make_unique(m_renderedTexture.get()); const auto allOutputs = workspace()->outputs(); for (auto output : allOutputs) { if (output->geometry().intersects(m_region)) { blit(output); } } } } void RegionScreenCastSource::render(GLFramebuffer *target) { ensureTexture(); GLFramebuffer::pushFramebuffer(target); auto shader = ShaderManager::instance()->pushShader(ShaderTrait::MapTexture); QMatrix4x4 projectionMatrix; projectionMatrix.scale(1, -1); projectionMatrix.ortho(QRect(QPoint(), target->size())); shader->setUniform(GLShader::Mat4Uniform::ModelViewProjectionMatrix, projectionMatrix); m_renderedTexture->render(target->size()); ShaderManager::instance()->popShader(); GLFramebuffer::popFramebuffer(); } void RegionScreenCastSource::render(QImage *target) { ensureTexture(); grabTexture(m_renderedTexture.get(), target); } uint RegionScreenCastSource::refreshRate() const { uint ret = 0; const auto allOutputs = workspace()->outputs(); for (auto output : allOutputs) { if (output->geometry().intersects(m_region)) { ret = std::max(ret, output->refreshRate()); } } return ret; } void RegionScreenCastSource::close() { if (!m_closed) { m_closed = true; Q_EMIT closed(); } } void RegionScreenCastSource::pause() { if (!m_active) { return; } m_scrappers.clear(); m_active = false; } void RegionScreenCastSource::resume() { if (m_active) { return; } const QList outputs = workspace()->outputs(); for (Output *output : outputs) { if (output->geometry().intersects(m_region)) { m_scrappers.emplace_back(std::make_unique(this, output)); } } if (m_scrappers.empty()) { close(); return; } Compositor::self()->scene()->addRepaint(m_region); m_active = true; } bool RegionScreenCastSource::includesCursor(Cursor *cursor) const { if (Cursors::self()->isCursorHidden()) { return false; } return cursor->geometry().intersects(m_region); } QPointF RegionScreenCastSource::mapFromGlobal(const QPointF &point) const { return point - m_region.topLeft(); } QRectF RegionScreenCastSource::mapFromGlobal(const QRectF &rect) const { return rect.translated(-m_region.topLeft()); } } // namespace KWin #include "moc_regionscreencastsource.cpp"