2007-11-27 19:40:25 +00:00
|
|
|
/********************************************************************
|
2007-04-29 17:35:43 +00:00
|
|
|
KWin - the KDE window manager
|
|
|
|
This file is part of the KDE project.
|
|
|
|
|
|
|
|
Copyright (C) 2006 Lubos Lunak <l.lunak@kde.org>
|
2009-04-22 17:29:56 +00:00
|
|
|
Copyright (C) 2009 Fredrik Höglund <fredrik@kde.org>
|
2013-02-27 14:08:09 +00:00
|
|
|
Copyright (C) 2013 Martin Gräßlin <mgraesslin@kde.org>
|
2007-04-29 17:35:43 +00:00
|
|
|
|
2007-11-27 19:40:25 +00:00
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*********************************************************************/
|
2007-04-29 17:35:43 +00:00
|
|
|
#include "scene_xrender.h"
|
|
|
|
|
2014-12-05 10:42:15 +00:00
|
|
|
#include "utils.h"
|
|
|
|
|
2007-12-17 14:14:53 +00:00
|
|
|
#ifdef KWIN_HAVE_XRENDER_COMPOSITING
|
2007-04-29 17:35:43 +00:00
|
|
|
|
2017-08-10 16:13:42 +00:00
|
|
|
#include "logging.h"
|
2007-04-29 17:35:43 +00:00
|
|
|
#include "toplevel.h"
|
|
|
|
#include "client.h"
|
2013-06-19 11:31:28 +00:00
|
|
|
#include "composite.h"
|
2007-04-29 17:35:43 +00:00
|
|
|
#include "deleted.h"
|
|
|
|
#include "effects.h"
|
2013-06-25 07:53:45 +00:00
|
|
|
#include "main.h"
|
2011-07-06 09:58:23 +00:00
|
|
|
#include "overlaywindow.h"
|
2017-08-07 15:54:56 +00:00
|
|
|
#include "platform.h"
|
2016-06-08 10:46:02 +00:00
|
|
|
#include "screens.h"
|
2012-12-21 14:11:31 +00:00
|
|
|
#include "xcbutils.h"
|
2008-05-07 14:43:13 +00:00
|
|
|
#include "kwinxrenderutils.h"
|
2014-07-22 11:11:19 +00:00
|
|
|
#include "decorations/decoratedclient.h"
|
2007-04-29 17:35:43 +00:00
|
|
|
|
2013-02-06 06:58:33 +00:00
|
|
|
#include <xcb/xfixes.h>
|
2008-09-18 15:27:13 +00:00
|
|
|
|
2013-09-02 11:14:39 +00:00
|
|
|
#include <QDebug>
|
2010-07-18 16:32:37 +00:00
|
|
|
#include <QtGui/QPainter>
|
2013-02-26 08:00:51 +00:00
|
|
|
#include <qmath.h>
|
2010-07-18 16:32:37 +00:00
|
|
|
|
2007-04-29 17:35:43 +00:00
|
|
|
namespace KWin
|
|
|
|
{
|
|
|
|
|
|
|
|
ScreenPaintData SceneXrender::screen_paint;
|
|
|
|
|
2013-07-03 19:30:51 +00:00
|
|
|
#define DOUBLE_TO_FIXED(d) ((xcb_render_fixed_t) ((d) * 65536))
|
|
|
|
#define FIXED_TO_DOUBLE(f) ((double) ((f) / 65536.0))
|
|
|
|
|
|
|
|
|
2013-06-17 13:25:32 +00:00
|
|
|
//****************************************
|
|
|
|
// XRenderBackend
|
|
|
|
//****************************************
|
|
|
|
XRenderBackend::XRenderBackend()
|
|
|
|
: m_buffer(XCB_RENDER_PICTURE_NONE)
|
|
|
|
, m_failed(false)
|
2011-01-30 14:34:42 +00:00
|
|
|
{
|
2012-12-21 14:11:31 +00:00
|
|
|
if (!Xcb::Extensions::self()->isRenderAvailable()) {
|
2013-06-17 13:25:32 +00:00
|
|
|
setFailed("No XRender extension available");
|
2007-05-30 14:22:09 +00:00
|
|
|
return;
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
2012-12-21 14:11:31 +00:00
|
|
|
if (!Xcb::Extensions::self()->isFixesRegionAvailable()) {
|
2013-06-17 13:25:32 +00:00
|
|
|
setFailed("No XFixes v3+ extension available");
|
2007-05-30 14:22:09 +00:00
|
|
|
return;
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
2011-11-26 15:15:46 +00:00
|
|
|
}
|
|
|
|
|
2013-06-17 13:25:32 +00:00
|
|
|
XRenderBackend::~XRenderBackend()
|
2011-11-26 15:15:46 +00:00
|
|
|
{
|
2013-06-17 13:25:32 +00:00
|
|
|
if (m_buffer) {
|
|
|
|
xcb_render_free_picture(connection(), m_buffer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
OverlayWindow* XRenderBackend::overlayWindow()
|
|
|
|
{
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
void XRenderBackend::showOverlay()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void XRenderBackend::setBuffer(xcb_render_picture_t buffer)
|
|
|
|
{
|
|
|
|
if (m_buffer != XCB_RENDER_PICTURE_NONE) {
|
|
|
|
xcb_render_free_picture(connection(), m_buffer);
|
|
|
|
}
|
|
|
|
m_buffer = buffer;
|
|
|
|
}
|
|
|
|
|
|
|
|
void XRenderBackend::setFailed(const QString& reason)
|
|
|
|
{
|
2017-08-10 16:13:42 +00:00
|
|
|
qCCritical(KWIN_XRENDER) << "Creating the XRender backend failed: " << reason;
|
2013-06-17 13:25:32 +00:00
|
|
|
m_failed = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void XRenderBackend::screenGeometryChanged(const QSize &size)
|
|
|
|
{
|
|
|
|
Q_UNUSED(size)
|
|
|
|
}
|
|
|
|
|
2013-06-19 09:12:57 +00:00
|
|
|
|
2013-06-17 13:25:32 +00:00
|
|
|
//****************************************
|
|
|
|
// X11XRenderBackend
|
|
|
|
//****************************************
|
|
|
|
X11XRenderBackend::X11XRenderBackend()
|
|
|
|
: XRenderBackend()
|
2017-08-07 15:54:56 +00:00
|
|
|
, m_overlayWindow(kwinApp()->platform()->createOverlayWindow())
|
2013-06-17 13:25:32 +00:00
|
|
|
, m_front(XCB_RENDER_PICTURE_NONE)
|
|
|
|
, m_format(0)
|
|
|
|
{
|
|
|
|
init(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
X11XRenderBackend::~X11XRenderBackend()
|
|
|
|
{
|
|
|
|
if (m_front) {
|
|
|
|
xcb_render_free_picture(connection(), m_front);
|
2011-11-26 15:15:46 +00:00
|
|
|
}
|
|
|
|
m_overlayWindow->destroy();
|
|
|
|
}
|
|
|
|
|
2013-06-17 13:25:32 +00:00
|
|
|
OverlayWindow* X11XRenderBackend::overlayWindow()
|
|
|
|
{
|
|
|
|
return m_overlayWindow.data();
|
|
|
|
}
|
|
|
|
|
|
|
|
void X11XRenderBackend::showOverlay()
|
|
|
|
{
|
|
|
|
if (m_overlayWindow->window()) // show the window only after the first pass, since
|
|
|
|
m_overlayWindow->show(); // that pass may take long
|
|
|
|
}
|
|
|
|
|
|
|
|
void X11XRenderBackend::init(bool createOverlay)
|
2011-11-26 15:15:46 +00:00
|
|
|
{
|
2013-06-17 13:25:32 +00:00
|
|
|
if (m_front != XCB_RENDER_PICTURE_NONE)
|
|
|
|
xcb_render_free_picture(connection(), m_front);
|
2013-02-27 14:08:09 +00:00
|
|
|
bool haveOverlay = createOverlay ? m_overlayWindow->create() : (m_overlayWindow->window() != XCB_WINDOW_NONE);
|
2011-11-26 15:15:46 +00:00
|
|
|
if (haveOverlay) {
|
2013-02-27 14:08:09 +00:00
|
|
|
m_overlayWindow->setup(XCB_WINDOW_NONE);
|
|
|
|
ScopedCPointer<xcb_get_window_attributes_reply_t> attribs(xcb_get_window_attributes_reply(connection(),
|
|
|
|
xcb_get_window_attributes_unchecked(connection(), m_overlayWindow->window()), NULL));
|
|
|
|
if (!attribs) {
|
2013-06-17 13:25:32 +00:00
|
|
|
setFailed("Failed getting window attributes for overlay window");
|
2013-02-27 14:08:09 +00:00
|
|
|
return;
|
|
|
|
}
|
2014-08-28 18:20:41 +00:00
|
|
|
m_format = XRenderUtils::findPictFormat(attribs->visual);
|
2013-06-17 13:25:32 +00:00
|
|
|
if (m_format == 0) {
|
|
|
|
setFailed("Failed to find XRender format for overlay window");
|
2008-05-08 09:22:24 +00:00
|
|
|
return;
|
2007-04-29 17:35:43 +00:00
|
|
|
}
|
2013-06-17 13:25:32 +00:00
|
|
|
m_front = xcb_generate_id(connection());
|
|
|
|
xcb_render_create_picture(connection(), m_front, m_overlayWindow->window(), m_format, 0, NULL);
|
2011-01-30 14:34:42 +00:00
|
|
|
} else {
|
2008-05-08 09:22:24 +00:00
|
|
|
// create XRender picture for the root window
|
2014-08-28 18:20:41 +00:00
|
|
|
m_format = XRenderUtils::findPictFormat(defaultScreen()->root_visual);
|
2013-06-17 13:25:32 +00:00
|
|
|
if (m_format == 0) {
|
|
|
|
setFailed("Failed to find XRender format for root window");
|
2008-05-08 09:22:24 +00:00
|
|
|
return; // error
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
2013-06-17 13:25:32 +00:00
|
|
|
m_front = xcb_generate_id(connection());
|
2013-02-27 14:08:09 +00:00
|
|
|
const uint32_t values[] = {XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS};
|
2013-06-17 13:25:32 +00:00
|
|
|
xcb_render_create_picture(connection(), m_front, rootWindow(), m_format, XCB_RENDER_CP_SUBWINDOW_MODE, values);
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
2007-04-29 17:35:43 +00:00
|
|
|
createBuffer();
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
2007-05-30 14:22:09 +00:00
|
|
|
|
2013-06-17 13:25:32 +00:00
|
|
|
void X11XRenderBackend::createBuffer()
|
2011-01-30 14:34:42 +00:00
|
|
|
{
|
2013-02-27 14:08:09 +00:00
|
|
|
xcb_pixmap_t pixmap = xcb_generate_id(connection());
|
2016-06-08 10:46:02 +00:00
|
|
|
const auto displaySize = screens()->displaySize();
|
|
|
|
xcb_create_pixmap(connection(), Xcb::defaultDepth(), pixmap, rootWindow(), displaySize.width(), displaySize.height());
|
2013-06-17 13:25:32 +00:00
|
|
|
xcb_render_picture_t b = xcb_generate_id(connection());
|
|
|
|
xcb_render_create_picture(connection(), b, pixmap, m_format, 0, NULL);
|
2013-02-27 14:08:09 +00:00
|
|
|
xcb_free_pixmap(connection(), pixmap); // The picture owns the pixmap now
|
2013-06-17 13:25:32 +00:00
|
|
|
setBuffer(b);
|
|
|
|
}
|
|
|
|
|
|
|
|
void X11XRenderBackend::present(int mask, const QRegion &damage)
|
|
|
|
{
|
2016-06-08 10:46:02 +00:00
|
|
|
const auto displaySize = screens()->displaySize();
|
2013-06-17 13:25:32 +00:00
|
|
|
if (mask & Scene::PAINT_SCREEN_REGION) {
|
|
|
|
// Use the damage region as the clip region for the root window
|
|
|
|
XFixesRegion frontRegion(damage);
|
|
|
|
xcb_xfixes_set_picture_clip_region(connection(), m_front, frontRegion, 0, 0);
|
|
|
|
// copy composed buffer to the root window
|
|
|
|
xcb_xfixes_set_picture_clip_region(connection(), buffer(), XCB_XFIXES_REGION_NONE, 0, 0);
|
|
|
|
xcb_render_composite(connection(), XCB_RENDER_PICT_OP_SRC, buffer(), XCB_RENDER_PICTURE_NONE,
|
2016-06-08 10:46:02 +00:00
|
|
|
m_front, 0, 0, 0, 0, 0, 0, displaySize.width(), displaySize.height());
|
2013-06-17 13:25:32 +00:00
|
|
|
xcb_xfixes_set_picture_clip_region(connection(), m_front, XCB_XFIXES_REGION_NONE, 0, 0);
|
|
|
|
xcb_flush(connection());
|
|
|
|
} else {
|
|
|
|
// copy composed buffer to the root window
|
|
|
|
xcb_render_composite(connection(), XCB_RENDER_PICT_OP_SRC, buffer(), XCB_RENDER_PICTURE_NONE,
|
2016-06-08 10:46:02 +00:00
|
|
|
m_front, 0, 0, 0, 0, 0, 0, displaySize.width(), displaySize.height());
|
2013-06-17 13:25:32 +00:00
|
|
|
xcb_flush(connection());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void X11XRenderBackend::screenGeometryChanged(const QSize &size)
|
|
|
|
{
|
|
|
|
Q_UNUSED(size)
|
|
|
|
init(false);
|
|
|
|
}
|
|
|
|
|
2013-06-19 10:26:34 +00:00
|
|
|
bool X11XRenderBackend::usesOverlayWindow() const
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2013-06-17 13:25:32 +00:00
|
|
|
//****************************************
|
|
|
|
// SceneXrender
|
|
|
|
//****************************************
|
2016-06-01 10:51:10 +00:00
|
|
|
|
2015-02-23 13:41:45 +00:00
|
|
|
SceneXrender* SceneXrender::createScene(QObject *parent)
|
2013-06-17 13:25:32 +00:00
|
|
|
{
|
|
|
|
QScopedPointer<XRenderBackend> backend;
|
|
|
|
backend.reset(new X11XRenderBackend);
|
|
|
|
if (backend->isFailed()) {
|
|
|
|
return NULL;
|
|
|
|
}
|
2015-02-23 13:41:45 +00:00
|
|
|
return new SceneXrender(backend.take(), parent);
|
2013-06-17 13:25:32 +00:00
|
|
|
}
|
|
|
|
|
2015-02-23 13:41:45 +00:00
|
|
|
SceneXrender::SceneXrender(XRenderBackend *backend, QObject *parent)
|
|
|
|
: Scene(parent)
|
2013-06-17 13:25:32 +00:00
|
|
|
, m_backend(backend)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
SceneXrender::~SceneXrender()
|
|
|
|
{
|
|
|
|
SceneXrender::Window::cleanup();
|
|
|
|
SceneXrender::EffectFrame::cleanup();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SceneXrender::initFailed() const
|
|
|
|
{
|
|
|
|
return false;
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
2007-05-30 14:22:09 +00:00
|
|
|
|
2007-04-29 17:35:43 +00:00
|
|
|
// the entry point for painting
|
2013-03-28 20:53:25 +00:00
|
|
|
qint64 SceneXrender::paint(QRegion damage, ToplevelList toplevels)
|
2011-01-30 14:34:42 +00:00
|
|
|
{
|
2011-10-29 15:09:26 +00:00
|
|
|
QElapsedTimer renderTimer;
|
|
|
|
renderTimer.start();
|
|
|
|
|
2013-06-24 07:53:11 +00:00
|
|
|
createStackingOrder(toplevels);
|
2011-10-29 15:09:26 +00:00
|
|
|
|
2007-04-29 17:35:43 +00:00
|
|
|
int mask = 0;
|
2013-11-21 09:44:06 +00:00
|
|
|
QRegion updateRegion, validRegion;
|
|
|
|
paintScreen(&mask, damage, QRegion(), &updateRegion, &validRegion);
|
2011-10-29 15:09:26 +00:00
|
|
|
|
2013-06-17 13:25:32 +00:00
|
|
|
m_backend->showOverlay();
|
2011-10-29 15:09:26 +00:00
|
|
|
|
2013-06-17 13:25:32 +00:00
|
|
|
m_backend->present(mask, updateRegion);
|
2008-09-18 15:27:13 +00:00
|
|
|
// do cleanup
|
2013-06-24 07:53:11 +00:00
|
|
|
clearStackingOrder();
|
2012-03-29 20:11:28 +00:00
|
|
|
|
2013-03-28 20:53:25 +00:00
|
|
|
return renderTimer.nsecsElapsed();
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
2008-09-18 15:27:13 +00:00
|
|
|
|
2012-03-20 21:44:20 +00:00
|
|
|
void SceneXrender::paintGenericScreen(int mask, ScreenPaintData data)
|
|
|
|
{
|
|
|
|
screen_paint = data; // save, transformations will be done when painting windows
|
|
|
|
Scene::paintGenericScreen(mask, data);
|
|
|
|
}
|
|
|
|
|
2012-03-29 18:17:57 +00:00
|
|
|
void SceneXrender::paintDesktop(int desktop, int mask, const QRegion ®ion, ScreenPaintData &data)
|
|
|
|
{
|
|
|
|
PaintClipper::push(region);
|
|
|
|
KWin::Scene::paintDesktop(desktop, mask, region, data);
|
|
|
|
PaintClipper::pop(region);
|
|
|
|
}
|
|
|
|
|
2007-04-29 17:35:43 +00:00
|
|
|
// fill the screen background
|
2011-01-30 14:34:42 +00:00
|
|
|
void SceneXrender::paintBackground(QRegion region)
|
|
|
|
{
|
2013-02-27 14:08:09 +00:00
|
|
|
xcb_render_color_t col = { 0, 0, 0, 0xffff }; // black
|
|
|
|
const QVector<xcb_rectangle_t> &rects = Xcb::regionToRects(region);
|
2017-08-08 15:13:59 +00:00
|
|
|
xcb_render_fill_rectangles(connection(), XCB_RENDER_PICT_OP_SRC, xrenderBufferPicture(), col, rects.count(), rects.data());
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
2007-04-29 17:35:43 +00:00
|
|
|
|
2013-06-24 07:53:11 +00:00
|
|
|
Scene::Window *SceneXrender::createWindow(Toplevel *toplevel)
|
2011-01-30 14:34:42 +00:00
|
|
|
{
|
2013-06-17 13:25:32 +00:00
|
|
|
return new Window(toplevel, this);
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
2007-04-29 17:35:43 +00:00
|
|
|
|
2013-06-24 06:49:24 +00:00
|
|
|
Scene::EffectFrame *SceneXrender::createEffectFrame(EffectFrameImpl *frame)
|
|
|
|
{
|
|
|
|
return new SceneXrender::EffectFrame(frame);
|
|
|
|
}
|
|
|
|
|
2013-06-24 07:06:50 +00:00
|
|
|
Shadow *SceneXrender::createShadow(Toplevel *toplevel)
|
|
|
|
{
|
|
|
|
return new SceneXRenderShadow(toplevel);
|
|
|
|
}
|
|
|
|
|
2014-07-22 11:11:19 +00:00
|
|
|
Decoration::Renderer *SceneXrender::createDecorationRenderer(Decoration::DecoratedClientImpl* client)
|
|
|
|
{
|
|
|
|
return new SceneXRenderDecorationRenderer(client);
|
|
|
|
}
|
|
|
|
|
2007-04-29 17:35:43 +00:00
|
|
|
//****************************************
|
|
|
|
// SceneXrender::Window
|
|
|
|
//****************************************
|
|
|
|
|
2013-02-11 20:01:41 +00:00
|
|
|
XRenderPicture *SceneXrender::Window::s_tempPicture = 0;
|
2012-03-10 10:34:56 +00:00
|
|
|
QRect SceneXrender::Window::temp_visibleRect;
|
2016-06-01 10:51:10 +00:00
|
|
|
XRenderPicture *SceneXrender::Window::s_fadeAlphaPicture = nullptr;
|
2009-04-22 17:29:56 +00:00
|
|
|
|
2013-06-17 13:25:32 +00:00
|
|
|
SceneXrender::Window::Window(Toplevel* c, SceneXrender *scene)
|
2011-01-30 14:34:42 +00:00
|
|
|
: Scene::Window(c)
|
2013-06-17 13:25:32 +00:00
|
|
|
, m_scene(scene)
|
2014-08-28 18:20:41 +00:00
|
|
|
, format(XRenderUtils::findPictFormat(c->visual()))
|
2011-01-30 14:34:42 +00:00
|
|
|
{
|
|
|
|
}
|
2007-04-29 17:35:43 +00:00
|
|
|
|
|
|
|
SceneXrender::Window::~Window()
|
2011-01-30 14:34:42 +00:00
|
|
|
{
|
2007-04-29 17:35:43 +00:00
|
|
|
discardShape();
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
2007-04-29 17:35:43 +00:00
|
|
|
|
2013-02-11 20:01:41 +00:00
|
|
|
void SceneXrender::Window::cleanup()
|
|
|
|
{
|
|
|
|
delete s_tempPicture;
|
|
|
|
s_tempPicture = NULL;
|
2016-06-01 10:51:10 +00:00
|
|
|
delete s_fadeAlphaPicture;
|
|
|
|
s_fadeAlphaPicture = nullptr;
|
2013-02-11 20:01:41 +00:00
|
|
|
}
|
|
|
|
|
2009-04-22 17:29:56 +00:00
|
|
|
// Maps window coordinates to screen coordinates
|
2011-01-30 14:34:42 +00:00
|
|
|
QRect SceneXrender::Window::mapToScreen(int mask, const WindowPaintData &data, const QRect &rect) const
|
|
|
|
{
|
2009-04-22 17:29:56 +00:00
|
|
|
QRect r = rect;
|
|
|
|
|
2011-01-30 14:34:42 +00:00
|
|
|
if (mask & PAINT_WINDOW_TRANSFORMED) {
|
2009-04-22 17:29:56 +00:00
|
|
|
// Apply the window transformation
|
2012-05-28 12:45:46 +00:00
|
|
|
r.moveTo(r.x() * data.xScale() + data.xTranslation(),
|
|
|
|
r.y() * data.yScale() + data.yTranslation());
|
2012-05-28 10:00:31 +00:00
|
|
|
r.setWidth(r.width() * data.xScale());
|
|
|
|
r.setHeight(r.height() * data.yScale());
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
2009-04-22 17:29:56 +00:00
|
|
|
|
|
|
|
// Move the rectangle to the screen position
|
2011-01-30 14:34:42 +00:00
|
|
|
r.translate(x(), y());
|
2009-04-22 17:29:56 +00:00
|
|
|
|
2011-01-30 14:34:42 +00:00
|
|
|
if (mask & PAINT_SCREEN_TRANSFORMED) {
|
2009-04-22 17:29:56 +00:00
|
|
|
// Apply the screen transformation
|
2012-05-28 12:45:46 +00:00
|
|
|
r.moveTo(r.x() * screen_paint.xScale() + screen_paint.xTranslation(),
|
|
|
|
r.y() * screen_paint.yScale() + screen_paint.yTranslation());
|
2012-05-28 10:00:31 +00:00
|
|
|
r.setWidth(r.width() * screen_paint.xScale());
|
|
|
|
r.setHeight(r.height() * screen_paint.yScale());
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
2009-04-22 17:29:56 +00:00
|
|
|
|
|
|
|
return r;
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
2009-04-22 17:29:56 +00:00
|
|
|
|
|
|
|
// Maps window coordinates to screen coordinates
|
2011-01-30 14:34:42 +00:00
|
|
|
QPoint SceneXrender::Window::mapToScreen(int mask, const WindowPaintData &data, const QPoint &point) const
|
|
|
|
{
|
2009-04-22 17:29:56 +00:00
|
|
|
QPoint pt = point;
|
|
|
|
|
2011-01-30 14:34:42 +00:00
|
|
|
if (mask & PAINT_WINDOW_TRANSFORMED) {
|
2009-04-22 17:29:56 +00:00
|
|
|
// Apply the window transformation
|
2012-05-28 12:45:46 +00:00
|
|
|
pt.rx() = pt.x() * data.xScale() + data.xTranslation();
|
|
|
|
pt.ry() = pt.y() * data.yScale() + data.yTranslation();
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
2009-04-22 17:29:56 +00:00
|
|
|
|
|
|
|
// Move the point to the screen position
|
|
|
|
pt += QPoint(x(), y());
|
|
|
|
|
2011-01-30 14:34:42 +00:00
|
|
|
if (mask & PAINT_SCREEN_TRANSFORMED) {
|
2009-04-22 17:29:56 +00:00
|
|
|
// Apply the screen transformation
|
2012-05-28 12:45:46 +00:00
|
|
|
pt.rx() = pt.x() * screen_paint.xScale() + screen_paint.xTranslation();
|
|
|
|
pt.ry() = pt.y() * screen_paint.yScale() + screen_paint.yTranslation();
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
2009-04-22 17:29:56 +00:00
|
|
|
|
|
|
|
return pt;
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
2009-04-22 17:29:56 +00:00
|
|
|
|
2011-07-24 21:04:06 +00:00
|
|
|
void SceneXrender::Window::prepareTempPixmap()
|
2011-01-30 14:34:42 +00:00
|
|
|
{
|
2013-02-11 20:01:41 +00:00
|
|
|
const QSize oldSize = temp_visibleRect.size();
|
2012-03-10 10:34:56 +00:00
|
|
|
temp_visibleRect = toplevel->visibleRect().translated(-toplevel->pos());
|
2013-02-11 20:01:41 +00:00
|
|
|
if (s_tempPicture && (oldSize.width() < temp_visibleRect.width() || oldSize.height() < temp_visibleRect.height())) {
|
|
|
|
delete s_tempPicture;
|
|
|
|
s_tempPicture = NULL;
|
2012-03-04 08:52:32 +00:00
|
|
|
scene_setXRenderOffscreenTarget(0); // invalidate, better crash than cause weird results for developers
|
|
|
|
}
|
2013-02-11 20:01:41 +00:00
|
|
|
if (!s_tempPicture) {
|
|
|
|
xcb_pixmap_t pix = xcb_generate_id(connection());
|
|
|
|
xcb_create_pixmap(connection(), 32, pix, rootWindow(), temp_visibleRect.width(), temp_visibleRect.height());
|
|
|
|
s_tempPicture = new XRenderPicture(pix, 32);
|
|
|
|
xcb_free_pixmap(connection(), pix);
|
2011-05-12 16:52:38 +00:00
|
|
|
}
|
2013-02-11 20:01:41 +00:00
|
|
|
const xcb_render_color_t transparent = {0, 0, 0, 0};
|
|
|
|
const xcb_rectangle_t rect = {0, 0, uint16_t(temp_visibleRect.width()), uint16_t(temp_visibleRect.height())};
|
|
|
|
xcb_render_fill_rectangles(connection(), XCB_RENDER_PICT_OP_SRC, *s_tempPicture, transparent, 1, &rect);
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
2009-04-22 17:29:56 +00:00
|
|
|
|
2007-04-29 17:35:43 +00:00
|
|
|
// paint the window
|
2011-01-30 14:34:42 +00:00
|
|
|
void SceneXrender::Window::performPaint(int mask, QRegion region, WindowPaintData data)
|
|
|
|
{
|
|
|
|
setTransformedShape(QRegion()); // maybe nothing will be painted
|
2007-04-29 17:35:43 +00:00
|
|
|
// check if there is something to paint
|
2012-07-12 15:20:17 +00:00
|
|
|
bool opaque = isOpaque() && qFuzzyCompare(data.opacity(), 1.0);
|
2008-10-04 17:36:26 +00:00
|
|
|
/* HACK: It seems this causes painting glitches, disable temporarily
|
2011-01-30 14:34:42 +00:00
|
|
|
if (( mask & PAINT_WINDOW_OPAQUE ) ^ ( mask & PAINT_WINDOW_TRANSLUCENT ))
|
2008-09-27 06:23:33 +00:00
|
|
|
{ // We are only painting either opaque OR translucent windows, not both
|
2011-01-30 14:34:42 +00:00
|
|
|
if ( mask & PAINT_WINDOW_OPAQUE && !opaque )
|
2008-09-27 06:23:33 +00:00
|
|
|
return; // Only painting opaque and window is translucent
|
2011-01-30 14:34:42 +00:00
|
|
|
if ( mask & PAINT_WINDOW_TRANSLUCENT && opaque )
|
2008-09-27 06:23:33 +00:00
|
|
|
return; // Only painting translucent and window is opaque
|
2008-10-04 17:36:26 +00:00
|
|
|
}*/
|
2011-04-28 15:34:50 +00:00
|
|
|
// Intersect the clip region with the rectangle the window occupies on the screen
|
|
|
|
if (!(mask & (PAINT_WINDOW_TRANSFORMED | PAINT_SCREEN_TRANSFORMED)))
|
|
|
|
region &= toplevel->visibleRect();
|
|
|
|
|
|
|
|
if (region.isEmpty())
|
|
|
|
return;
|
2013-05-10 10:07:56 +00:00
|
|
|
XRenderWindowPixmap *pixmap = windowPixmap<XRenderWindowPixmap>();
|
|
|
|
if (!pixmap || !pixmap->isValid()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
xcb_render_picture_t pic = pixmap->picture();
|
2013-02-27 14:08:09 +00:00
|
|
|
if (pic == XCB_RENDER_PICTURE_NONE) // The render format can be null for GL and/or Xv visuals
|
2007-04-29 17:35:43 +00:00
|
|
|
return;
|
2013-05-10 10:07:56 +00:00
|
|
|
toplevel->resetDamage();
|
2007-04-29 17:35:43 +00:00
|
|
|
// set picture filter
|
2012-02-20 09:25:13 +00:00
|
|
|
if (options->isXrenderSmoothScale()) { // only when forced, it's slow
|
2011-01-30 14:34:42 +00:00
|
|
|
if (mask & PAINT_WINDOW_TRANSFORMED)
|
2007-04-29 17:35:43 +00:00
|
|
|
filter = ImageFilterGood;
|
2011-01-30 14:34:42 +00:00
|
|
|
else if (mask & PAINT_SCREEN_TRANSFORMED)
|
2007-04-29 17:35:43 +00:00
|
|
|
filter = ImageFilterGood;
|
|
|
|
else
|
|
|
|
filter = ImageFilterFast;
|
2011-01-30 14:34:42 +00:00
|
|
|
} else
|
2007-04-29 17:35:43 +00:00
|
|
|
filter = ImageFilterFast;
|
|
|
|
// do required transformations
|
2009-04-22 17:29:56 +00:00
|
|
|
const QRect wr = mapToScreen(mask, data, QRect(0, 0, width(), height()));
|
2011-07-24 21:04:06 +00:00
|
|
|
QRect cr = QRect(toplevel->clientPos(), toplevel->clientSize()); // Client rect (in the window)
|
2012-05-28 10:00:31 +00:00
|
|
|
qreal xscale = 1;
|
|
|
|
qreal yscale = 1;
|
2009-04-22 17:29:56 +00:00
|
|
|
bool scaled = false;
|
2009-05-04 22:35:33 +00:00
|
|
|
|
2011-01-30 14:34:42 +00:00
|
|
|
Client *client = dynamic_cast<Client*>(toplevel);
|
|
|
|
Deleted *deleted = dynamic_cast<Deleted*>(toplevel);
|
2011-09-26 13:01:05 +00:00
|
|
|
const QRect decorationRect = toplevel->decorationRect();
|
2012-12-27 08:05:13 +00:00
|
|
|
if (((client && !client->noBorder()) || (deleted && !deleted->noBorder())) &&
|
2014-07-22 11:11:19 +00:00
|
|
|
true) {
|
2012-12-06 14:37:18 +00:00
|
|
|
// decorated client
|
2011-09-26 13:01:05 +00:00
|
|
|
transformed_shape = decorationRect;
|
2012-12-06 14:37:18 +00:00
|
|
|
if (toplevel->shape()) {
|
|
|
|
// "xeyes" + decoration
|
|
|
|
transformed_shape -= cr;
|
|
|
|
transformed_shape += shape();
|
|
|
|
}
|
|
|
|
} else {
|
2009-05-04 22:35:33 +00:00
|
|
|
transformed_shape = shape();
|
2012-12-06 14:37:18 +00:00
|
|
|
}
|
2011-07-02 19:39:01 +00:00
|
|
|
if (toplevel->hasShadow())
|
|
|
|
transformed_shape |= toplevel->shadow()->shadowRegion();
|
|
|
|
|
2013-02-27 14:08:09 +00:00
|
|
|
xcb_render_transform_t xform = {
|
|
|
|
DOUBLE_TO_FIXED(1), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0),
|
|
|
|
DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(1), DOUBLE_TO_FIXED(0),
|
|
|
|
DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(1)
|
2011-01-30 14:34:42 +00:00
|
|
|
};
|
2014-09-27 18:30:49 +00:00
|
|
|
static const xcb_render_transform_t identity = {
|
2013-02-27 14:08:09 +00:00
|
|
|
DOUBLE_TO_FIXED(1), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0),
|
|
|
|
DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(1), DOUBLE_TO_FIXED(0),
|
|
|
|
DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(1)
|
2011-01-30 14:34:42 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
if (mask & PAINT_WINDOW_TRANSFORMED) {
|
2012-05-28 10:00:31 +00:00
|
|
|
xscale = data.xScale();
|
|
|
|
yscale = data.yScale();
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
|
|
|
if (mask & PAINT_SCREEN_TRANSFORMED) {
|
2012-05-28 10:00:31 +00:00
|
|
|
xscale *= screen_paint.xScale();
|
|
|
|
yscale *= screen_paint.yScale();
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
|
|
|
if (!qFuzzyCompare(xscale, 1.0) || !qFuzzyCompare(yscale, 1.0)) {
|
2009-04-22 17:29:56 +00:00
|
|
|
scaled = true;
|
2013-02-27 14:08:09 +00:00
|
|
|
xform.matrix11 = DOUBLE_TO_FIXED(1.0 / xscale);
|
|
|
|
xform.matrix22 = DOUBLE_TO_FIXED(1.0 / yscale);
|
2009-04-22 17:29:56 +00:00
|
|
|
|
2011-04-25 14:47:10 +00:00
|
|
|
// transform the shape for clipping in paintTransformedScreen()
|
|
|
|
QVector<QRect> rects = transformed_shape.rects();
|
2011-07-24 21:04:06 +00:00
|
|
|
for (int i = 0; i < rects.count(); ++i) {
|
2011-04-25 14:47:10 +00:00
|
|
|
QRect& r = rects[ i ];
|
2011-12-13 13:35:07 +00:00
|
|
|
r.setRect(qRound(r.x() * xscale), qRound(r.y() * yscale),
|
2011-07-24 21:04:06 +00:00
|
|
|
qRound(r.width() * xscale), qRound(r.height() * yscale));
|
2011-04-25 14:47:10 +00:00
|
|
|
}
|
|
|
|
transformed_shape.setRects(rects.constData(), rects.count());
|
|
|
|
}
|
|
|
|
|
|
|
|
transformed_shape.translate(mapToScreen(mask, data, QPoint(0, 0)));
|
|
|
|
PaintClipper pcreg(region); // clip by the region to paint
|
|
|
|
PaintClipper pc(transformed_shape); // clip by window's shape
|
|
|
|
|
2012-02-07 09:41:40 +00:00
|
|
|
const bool wantShadow = m_shadow && !m_shadow->shadowRegion().isEmpty();
|
2011-07-24 21:04:06 +00:00
|
|
|
|
2011-04-25 14:47:10 +00:00
|
|
|
// In order to obtain a pixel perfect rescaling
|
|
|
|
// we need to blit the window content togheter with
|
|
|
|
// decorations in a temporary pixmap and scale
|
|
|
|
// the temporary pixmap at the end.
|
|
|
|
// We should do this only if there is scaling and
|
|
|
|
// the window has border
|
|
|
|
// This solves a number of glitches and on top of this
|
|
|
|
// it optimizes painting quite a bit
|
2013-07-03 19:30:51 +00:00
|
|
|
const bool blitInTempPixmap = xRenderOffscreen() || (data.crossFadeProgress() < 1.0 && !opaque) ||
|
|
|
|
(scaled && (wantShadow || (client && !client->noBorder()) || (deleted && !deleted->noBorder())));
|
2011-04-25 14:47:10 +00:00
|
|
|
|
2017-08-08 15:13:59 +00:00
|
|
|
xcb_render_picture_t renderTarget = m_scene->xrenderBufferPicture();
|
2011-07-24 21:04:06 +00:00
|
|
|
if (blitInTempPixmap) {
|
2012-03-10 10:34:56 +00:00
|
|
|
if (scene_xRenderOffscreenTarget()) {
|
|
|
|
temp_visibleRect = toplevel->visibleRect().translated(-toplevel->pos());
|
2012-03-04 08:52:32 +00:00
|
|
|
renderTarget = *scene_xRenderOffscreenTarget();
|
2012-03-10 10:34:56 +00:00
|
|
|
} else {
|
2012-03-04 08:52:32 +00:00
|
|
|
prepareTempPixmap();
|
2013-02-11 20:01:41 +00:00
|
|
|
renderTarget = *s_tempPicture;
|
2012-03-04 08:52:32 +00:00
|
|
|
}
|
2011-07-24 21:04:06 +00:00
|
|
|
} else {
|
2013-02-27 14:08:09 +00:00
|
|
|
xcb_render_set_picture_transform(connection(), pic, xform);
|
2011-04-25 14:47:10 +00:00
|
|
|
if (filter == ImageFilterGood) {
|
2013-02-27 14:08:09 +00:00
|
|
|
setPictureFilter(pic, KWin::Scene::ImageFilterGood);
|
2011-04-25 14:47:10 +00:00
|
|
|
}
|
2009-04-22 17:29:56 +00:00
|
|
|
|
2011-07-24 21:04:06 +00:00
|
|
|
//BEGIN OF STUPID RADEON HACK
|
2009-12-16 18:51:36 +00:00
|
|
|
// This is needed to avoid hitting a fallback in the radeon driver.
|
|
|
|
// The Render specification states that sampling pixels outside the
|
|
|
|
// source picture results in alpha=0 pixels. This can be achieved by
|
|
|
|
// setting the border color to transparent black, but since the border
|
|
|
|
// color has the same format as the texture, it only works when the
|
|
|
|
// texture has an alpha channel. So the driver falls back to software
|
|
|
|
// when the repeat mode is RepeatNone, the picture has a non-identity
|
|
|
|
// transformation matrix, and doesn't have an alpha channel.
|
|
|
|
// Since we only scale the picture, we can work around this by setting
|
|
|
|
// the repeat mode to RepeatPad.
|
2011-01-30 14:34:42 +00:00
|
|
|
if (!window()->hasAlpha()) {
|
2013-02-27 14:08:09 +00:00
|
|
|
const uint32_t values[] = {XCB_RENDER_REPEAT_PAD};
|
|
|
|
xcb_render_change_picture(connection(), pic, XCB_RENDER_CP_REPEAT, values);
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
2011-07-24 21:04:06 +00:00
|
|
|
//END OF STUPID RADEON HACK
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
2011-07-24 21:04:06 +00:00
|
|
|
#define MAP_RECT_TO_TARGET(_RECT_) \
|
2012-03-10 10:34:56 +00:00
|
|
|
if (blitInTempPixmap) _RECT_.translate(-temp_visibleRect.topLeft()); else _RECT_ = mapToScreen(mask, data, _RECT_)
|
2011-07-24 21:04:06 +00:00
|
|
|
|
|
|
|
//BEGIN deco preparations
|
|
|
|
bool noBorder = true;
|
2013-02-18 08:07:42 +00:00
|
|
|
xcb_render_picture_t left = XCB_RENDER_PICTURE_NONE;
|
|
|
|
xcb_render_picture_t top = XCB_RENDER_PICTURE_NONE;
|
|
|
|
xcb_render_picture_t right = XCB_RENDER_PICTURE_NONE;
|
|
|
|
xcb_render_picture_t bottom = XCB_RENDER_PICTURE_NONE;
|
2011-07-24 21:04:06 +00:00
|
|
|
QRect dtr, dlr, drr, dbr;
|
2014-07-23 07:20:28 +00:00
|
|
|
const SceneXRenderDecorationRenderer *renderer = nullptr;
|
2014-07-22 11:11:19 +00:00
|
|
|
if (client) {
|
2011-07-24 21:04:06 +00:00
|
|
|
if (client && !client->noBorder()) {
|
2014-10-21 05:46:44 +00:00
|
|
|
if (client->isDecorated()) {
|
|
|
|
SceneXRenderDecorationRenderer *r = static_cast<SceneXRenderDecorationRenderer*>(client->decoratedClient()->renderer());
|
2014-07-23 07:20:28 +00:00
|
|
|
if (r) {
|
|
|
|
r->render();
|
|
|
|
renderer = r;
|
|
|
|
}
|
2014-07-22 11:11:19 +00:00
|
|
|
}
|
2011-07-24 21:04:06 +00:00
|
|
|
noBorder = client->noBorder();
|
2014-07-25 10:55:28 +00:00
|
|
|
client->layoutDecorationRects(dlr, dtr, drr, dbr);
|
2011-07-24 21:04:06 +00:00
|
|
|
}
|
2014-07-22 11:11:19 +00:00
|
|
|
}
|
2014-07-23 07:20:28 +00:00
|
|
|
if (deleted && !deleted->noBorder()) {
|
|
|
|
renderer = static_cast<const SceneXRenderDecorationRenderer*>(deleted->decorationRenderer());
|
|
|
|
noBorder = deleted->noBorder();
|
2014-07-25 10:55:28 +00:00
|
|
|
deleted->layoutDecorationRects(dlr, dtr, drr, dbr);
|
2014-07-23 07:20:28 +00:00
|
|
|
}
|
2014-07-22 11:11:19 +00:00
|
|
|
if (renderer) {
|
|
|
|
left = renderer->picture(SceneXRenderDecorationRenderer::DecorationPart::Left);
|
|
|
|
top = renderer->picture(SceneXRenderDecorationRenderer::DecorationPart::Top);
|
|
|
|
right = renderer->picture(SceneXRenderDecorationRenderer::DecorationPart::Right);
|
|
|
|
bottom = renderer->picture(SceneXRenderDecorationRenderer::DecorationPart::Bottom);
|
|
|
|
}
|
|
|
|
if (!noBorder) {
|
|
|
|
MAP_RECT_TO_TARGET(dtr);
|
|
|
|
MAP_RECT_TO_TARGET(dlr);
|
|
|
|
MAP_RECT_TO_TARGET(drr);
|
|
|
|
MAP_RECT_TO_TARGET(dbr);
|
2011-07-24 21:04:06 +00:00
|
|
|
}
|
|
|
|
//END deco preparations
|
|
|
|
|
|
|
|
//BEGIN shadow preparations
|
2011-07-02 19:39:01 +00:00
|
|
|
QRect stlr, str, strr, srr, sbrr, sbr, sblr, slr;
|
|
|
|
SceneXRenderShadow* m_xrenderShadow = static_cast<SceneXRenderShadow*>(m_shadow);
|
2011-07-24 21:04:06 +00:00
|
|
|
|
2011-07-02 19:39:01 +00:00
|
|
|
if (wantShadow) {
|
2011-04-28 15:22:17 +00:00
|
|
|
m_xrenderShadow->layoutShadowRects(str, strr, srr, sbrr, sbr, sblr, slr, stlr);
|
2011-07-24 21:04:06 +00:00
|
|
|
MAP_RECT_TO_TARGET(stlr);
|
|
|
|
MAP_RECT_TO_TARGET(str);
|
|
|
|
MAP_RECT_TO_TARGET(strr);
|
|
|
|
MAP_RECT_TO_TARGET(srr);
|
|
|
|
MAP_RECT_TO_TARGET(sbrr);
|
|
|
|
MAP_RECT_TO_TARGET(sbr);
|
|
|
|
MAP_RECT_TO_TARGET(sblr);
|
|
|
|
MAP_RECT_TO_TARGET(slr);
|
|
|
|
}
|
|
|
|
//BEGIN end preparations
|
|
|
|
|
|
|
|
//BEGIN client preparations
|
|
|
|
QRect dr = cr;
|
|
|
|
if (blitInTempPixmap) {
|
2012-03-10 10:34:56 +00:00
|
|
|
dr.translate(-temp_visibleRect.topLeft());
|
2011-07-24 21:04:06 +00:00
|
|
|
} else {
|
|
|
|
dr = mapToScreen(mask, data, dr); // Destination rect
|
|
|
|
if (scaled) {
|
|
|
|
cr.moveLeft(cr.x() * xscale);
|
|
|
|
cr.moveTop(cr.y() * yscale);
|
2011-04-28 15:22:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-02-27 14:08:09 +00:00
|
|
|
const int clientRenderOp = (opaque || blitInTempPixmap) ? XCB_RENDER_PICT_OP_SRC : XCB_RENDER_PICT_OP_OVER;
|
2011-07-24 21:04:06 +00:00
|
|
|
//END client preparations
|
|
|
|
|
|
|
|
#undef MAP_RECT_TO_TARGET
|
|
|
|
|
|
|
|
for (PaintClipper::Iterator iterator; !iterator.isDone(); iterator.next()) {
|
2011-07-02 19:39:01 +00:00
|
|
|
|
|
|
|
#define RENDER_SHADOW_TILE(_TILE_, _RECT_) \
|
2013-02-27 11:56:38 +00:00
|
|
|
xcb_render_composite(connection(), XCB_RENDER_PICT_OP_OVER, m_xrenderShadow->picture(SceneXRenderShadow::ShadowElement##_TILE_), \
|
2011-07-24 21:04:06 +00:00
|
|
|
shadowAlpha, renderTarget, 0, 0, 0, 0, _RECT_.x(), _RECT_.y(), _RECT_.width(), _RECT_.height())
|
2011-07-02 19:39:01 +00:00
|
|
|
|
|
|
|
//shadow
|
|
|
|
if (wantShadow) {
|
2013-02-27 11:10:22 +00:00
|
|
|
xcb_render_picture_t shadowAlpha = XCB_RENDER_PICTURE_NONE;
|
|
|
|
if (!opaque) {
|
|
|
|
shadowAlpha = xRenderBlendPicture(data.opacity());
|
|
|
|
}
|
2011-07-24 21:04:06 +00:00
|
|
|
RENDER_SHADOW_TILE(TopLeft, stlr);
|
|
|
|
RENDER_SHADOW_TILE(Top, str);
|
|
|
|
RENDER_SHADOW_TILE(TopRight, strr);
|
|
|
|
RENDER_SHADOW_TILE(Left, slr);
|
|
|
|
RENDER_SHADOW_TILE(Right, srr);
|
|
|
|
RENDER_SHADOW_TILE(BottomLeft, sblr);
|
|
|
|
RENDER_SHADOW_TILE(Bottom, sbr);
|
|
|
|
RENDER_SHADOW_TILE(BottomRight, sbrr);
|
2011-07-02 19:39:01 +00:00
|
|
|
}
|
|
|
|
#undef RENDER_SHADOW_TILE
|
|
|
|
|
2012-01-10 17:56:14 +00:00
|
|
|
// Paint the window contents
|
2012-03-07 21:02:26 +00:00
|
|
|
if (!(client && client->isShade())) {
|
2013-02-27 11:10:22 +00:00
|
|
|
xcb_render_picture_t clientAlpha = XCB_RENDER_PICTURE_NONE;
|
|
|
|
if (!opaque) {
|
|
|
|
clientAlpha = xRenderBlendPicture(data.opacity());
|
|
|
|
}
|
2013-07-03 19:30:51 +00:00
|
|
|
xcb_render_composite(connection(), clientRenderOp, pic, clientAlpha, renderTarget,
|
|
|
|
cr.x(), cr.y(), 0, 0, dr.x(), dr.y(), dr.width(), dr.height());
|
|
|
|
if (data.crossFadeProgress() < 1.0 && data.crossFadeProgress() > 0.0) {
|
|
|
|
XRenderWindowPixmap *previous = previousWindowPixmap<XRenderWindowPixmap>();
|
|
|
|
if (previous && previous != pixmap) {
|
|
|
|
static xcb_render_color_t cFadeColor = {0, 0, 0, 0};
|
|
|
|
cFadeColor.alpha = uint16_t((1.0 - data.crossFadeProgress()) * 0xffff);
|
2016-06-01 10:51:10 +00:00
|
|
|
if (!s_fadeAlphaPicture) {
|
|
|
|
s_fadeAlphaPicture = new XRenderPicture(xRenderFill(cFadeColor));
|
2013-07-03 19:30:51 +00:00
|
|
|
} else {
|
|
|
|
xcb_rectangle_t rect = {0, 0, 1, 1};
|
2016-06-01 10:51:10 +00:00
|
|
|
xcb_render_fill_rectangles(connection(), XCB_RENDER_PICT_OP_SRC, *s_fadeAlphaPicture, cFadeColor , 1, &rect);
|
2013-07-03 19:30:51 +00:00
|
|
|
}
|
|
|
|
if (previous->size() != pixmap->size()) {
|
|
|
|
xcb_render_transform_t xform2 = {
|
|
|
|
DOUBLE_TO_FIXED(FIXED_TO_DOUBLE(xform.matrix11) * previous->size().width() / pixmap->size().width()), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0),
|
|
|
|
DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(FIXED_TO_DOUBLE(xform.matrix22) * previous->size().height() / pixmap->size().height()), DOUBLE_TO_FIXED(0),
|
|
|
|
DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(1)
|
|
|
|
};
|
|
|
|
xcb_render_set_picture_transform(connection(), previous->picture(), xform2);
|
|
|
|
}
|
|
|
|
|
|
|
|
xcb_render_composite(connection(), opaque ? XCB_RENDER_PICT_OP_OVER : XCB_RENDER_PICT_OP_ATOP,
|
2016-06-01 10:51:10 +00:00
|
|
|
previous->picture(), *s_fadeAlphaPicture, renderTarget,
|
2013-07-03 19:30:51 +00:00
|
|
|
cr.x(), cr.y(), 0, 0, dr.x(), dr.y(), dr.width(), dr.height());
|
|
|
|
|
|
|
|
if (previous->size() != pixmap->size()) {
|
|
|
|
xcb_render_set_picture_transform(connection(), previous->picture(), identity);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2011-11-20 00:12:14 +00:00
|
|
|
if (!opaque)
|
|
|
|
transformed_shape = QRegion();
|
|
|
|
}
|
|
|
|
|
2011-01-30 14:34:42 +00:00
|
|
|
if (client || deleted) {
|
|
|
|
if (!noBorder) {
|
2014-08-04 19:12:34 +00:00
|
|
|
xcb_render_picture_t decorationAlpha = xRenderBlendPicture(data.opacity());
|
2014-04-04 13:23:23 +00:00
|
|
|
auto renderDeco = [decorationAlpha, renderTarget](xcb_render_picture_t deco, const QRect &rect) {
|
|
|
|
if (deco == XCB_RENDER_PICTURE_NONE) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
xcb_render_composite(connection(), XCB_RENDER_PICT_OP_OVER, deco, decorationAlpha, renderTarget,
|
|
|
|
0, 0, 0, 0, rect.x(), rect.y(), rect.width(), rect.height());
|
|
|
|
};
|
|
|
|
renderDeco(top, dtr);
|
|
|
|
renderDeco(left, dlr);
|
|
|
|
renderDeco(right, drr);
|
|
|
|
renderDeco(bottom, dbr);
|
2009-04-22 17:29:56 +00:00
|
|
|
}
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
2011-07-24 21:04:06 +00:00
|
|
|
|
2012-12-22 22:40:20 +00:00
|
|
|
if (data.brightness() != 1.0) {
|
2008-05-08 19:08:05 +00:00
|
|
|
// fake brightness change by overlaying black
|
2012-12-22 22:40:20 +00:00
|
|
|
const float alpha = (1 - data.brightness()) * data.opacity();
|
2013-02-12 09:57:27 +00:00
|
|
|
xcb_rectangle_t rect;
|
2011-04-25 14:47:10 +00:00
|
|
|
if (blitInTempPixmap) {
|
2013-02-12 09:57:27 +00:00
|
|
|
rect.x = -temp_visibleRect.left();
|
|
|
|
rect.y = -temp_visibleRect.top();
|
|
|
|
rect.width = width();
|
|
|
|
rect.height = height();
|
2011-04-25 14:47:10 +00:00
|
|
|
} else {
|
2013-02-12 09:57:27 +00:00
|
|
|
rect.x = wr.x();
|
|
|
|
rect.y = wr.y();
|
|
|
|
rect.width = wr.width();
|
|
|
|
rect.height = wr.height();
|
2011-04-25 14:47:10 +00:00
|
|
|
}
|
2013-02-12 09:57:27 +00:00
|
|
|
xcb_render_fill_rectangles(connection(), XCB_RENDER_PICT_OP_OVER, renderTarget,
|
|
|
|
preMultiply(data.brightness() < 1.0 ? QColor(0,0,0,255*alpha) : QColor(255,255,255,-alpha*255)),
|
|
|
|
1, &rect);
|
2011-04-25 14:47:10 +00:00
|
|
|
}
|
|
|
|
if (blitInTempPixmap) {
|
2012-03-10 10:34:56 +00:00
|
|
|
const QRect r = mapToScreen(mask, data, temp_visibleRect);
|
2013-02-27 14:08:09 +00:00
|
|
|
xcb_render_set_picture_transform(connection(), *s_tempPicture, xform);
|
2013-07-07 18:09:50 +00:00
|
|
|
setPictureFilter(*s_tempPicture, filter);
|
2013-02-27 14:08:09 +00:00
|
|
|
xcb_render_composite(connection(), XCB_RENDER_PICT_OP_OVER, *s_tempPicture,
|
2017-08-08 15:13:59 +00:00
|
|
|
XCB_RENDER_PICTURE_NONE, m_scene->xrenderBufferPicture(),
|
2013-02-27 14:08:09 +00:00
|
|
|
0, 0, 0, 0, r.x(), r.y(), r.width(), r.height());
|
|
|
|
xcb_render_set_picture_transform(connection(), *s_tempPicture, identity);
|
2007-04-29 17:35:43 +00:00
|
|
|
}
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
2011-04-25 14:47:10 +00:00
|
|
|
if (scaled && !blitInTempPixmap) {
|
2013-02-27 14:08:09 +00:00
|
|
|
xcb_render_set_picture_transform(connection(), pic, identity);
|
2011-01-30 14:34:42 +00:00
|
|
|
if (filter == ImageFilterGood)
|
2013-02-27 14:08:09 +00:00
|
|
|
setPictureFilter(pic, KWin::Scene::ImageFilterFast);
|
2011-01-30 14:34:42 +00:00
|
|
|
if (!window()->hasAlpha()) {
|
2013-02-27 14:08:09 +00:00
|
|
|
const uint32_t values[] = {XCB_RENDER_REPEAT_NONE};
|
|
|
|
xcb_render_change_picture(connection(), pic, XCB_RENDER_CP_REPEAT, values);
|
2007-04-29 17:35:43 +00:00
|
|
|
}
|
|
|
|
}
|
2012-03-04 08:52:32 +00:00
|
|
|
if (xRenderOffscreen())
|
2013-02-11 20:01:41 +00:00
|
|
|
scene_setXRenderOffscreenTarget(*s_tempPicture);
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
2007-04-29 17:35:43 +00:00
|
|
|
|
2013-02-27 14:08:09 +00:00
|
|
|
void SceneXrender::Window::setPictureFilter(xcb_render_picture_t pic, Scene::ImageFilterType filter)
|
|
|
|
{
|
|
|
|
QByteArray filterName;
|
|
|
|
switch (filter) {
|
|
|
|
case KWin::Scene::ImageFilterFast:
|
|
|
|
filterName = QByteArray("fast");
|
|
|
|
break;
|
|
|
|
case KWin::Scene::ImageFilterGood:
|
|
|
|
filterName = QByteArray("good");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
xcb_render_set_picture_filter(connection(), pic, filterName.length(), filterName.constData(), 0, NULL);
|
|
|
|
}
|
|
|
|
|
2013-05-10 10:07:56 +00:00
|
|
|
WindowPixmap* SceneXrender::Window::createWindowPixmap()
|
|
|
|
{
|
|
|
|
return new XRenderWindowPixmap(this, format);
|
|
|
|
}
|
|
|
|
|
2011-11-26 15:15:46 +00:00
|
|
|
void SceneXrender::screenGeometryChanged(const QSize &size)
|
|
|
|
{
|
|
|
|
Scene::screenGeometryChanged(size);
|
2013-06-17 13:25:32 +00:00
|
|
|
m_backend->screenGeometryChanged(size);
|
2011-11-26 15:15:46 +00:00
|
|
|
}
|
|
|
|
|
2013-05-10 10:07:56 +00:00
|
|
|
//****************************************
|
|
|
|
// XRenderWindowPixmap
|
|
|
|
//****************************************
|
|
|
|
|
|
|
|
XRenderWindowPixmap::XRenderWindowPixmap(Scene::Window *window, xcb_render_pictformat_t format)
|
|
|
|
: WindowPixmap(window)
|
|
|
|
, m_picture(XCB_RENDER_PICTURE_NONE)
|
|
|
|
, m_format(format)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
XRenderWindowPixmap::~XRenderWindowPixmap()
|
|
|
|
{
|
|
|
|
if (m_picture != XCB_RENDER_PICTURE_NONE) {
|
|
|
|
xcb_render_free_picture(connection(), m_picture);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void XRenderWindowPixmap::create()
|
|
|
|
{
|
|
|
|
if (isValid()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
KWin::WindowPixmap::create();
|
|
|
|
if (!isValid()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
m_picture = xcb_generate_id(connection());
|
|
|
|
xcb_render_create_picture(connection(), m_picture, pixmap(), m_format, 0, NULL);
|
|
|
|
}
|
|
|
|
|
2010-07-18 16:32:37 +00:00
|
|
|
//****************************************
|
|
|
|
// SceneXrender::EffectFrame
|
|
|
|
//****************************************
|
|
|
|
|
2013-02-06 07:03:14 +00:00
|
|
|
XRenderPicture *SceneXrender::EffectFrame::s_effectFrameCircle = NULL;
|
|
|
|
|
2011-01-30 14:34:42 +00:00
|
|
|
SceneXrender::EffectFrame::EffectFrame(EffectFrameImpl* frame)
|
|
|
|
: Scene::EffectFrame(frame)
|
|
|
|
{
|
2010-07-18 16:32:37 +00:00
|
|
|
m_picture = NULL;
|
|
|
|
m_textPicture = NULL;
|
2010-07-20 21:11:03 +00:00
|
|
|
m_iconPicture = NULL;
|
2010-07-26 20:00:04 +00:00
|
|
|
m_selectionPicture = NULL;
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
2010-07-18 16:32:37 +00:00
|
|
|
|
|
|
|
SceneXrender::EffectFrame::~EffectFrame()
|
2011-01-30 14:34:42 +00:00
|
|
|
{
|
2010-07-18 16:32:37 +00:00
|
|
|
delete m_picture;
|
|
|
|
delete m_textPicture;
|
2010-07-20 21:11:03 +00:00
|
|
|
delete m_iconPicture;
|
2010-07-26 20:00:04 +00:00
|
|
|
delete m_selectionPicture;
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
2010-07-18 16:32:37 +00:00
|
|
|
|
2013-02-06 07:03:14 +00:00
|
|
|
void SceneXrender::EffectFrame::cleanup()
|
|
|
|
{
|
|
|
|
delete s_effectFrameCircle;
|
|
|
|
s_effectFrameCircle = NULL;
|
|
|
|
}
|
|
|
|
|
2010-07-18 16:32:37 +00:00
|
|
|
void SceneXrender::EffectFrame::free()
|
2011-01-30 14:34:42 +00:00
|
|
|
{
|
2010-07-18 16:32:37 +00:00
|
|
|
delete m_picture;
|
|
|
|
m_picture = NULL;
|
|
|
|
delete m_textPicture;
|
|
|
|
m_textPicture = NULL;
|
2010-07-20 21:11:03 +00:00
|
|
|
delete m_iconPicture;
|
|
|
|
m_iconPicture = NULL;
|
2010-07-26 20:00:04 +00:00
|
|
|
delete m_selectionPicture;
|
|
|
|
m_selectionPicture = NULL;
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
2010-07-20 21:11:03 +00:00
|
|
|
|
|
|
|
void SceneXrender::EffectFrame::freeIconFrame()
|
2011-01-30 14:34:42 +00:00
|
|
|
{
|
2010-07-20 21:11:03 +00:00
|
|
|
delete m_iconPicture;
|
|
|
|
m_iconPicture = NULL;
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
2010-07-18 16:32:37 +00:00
|
|
|
|
|
|
|
void SceneXrender::EffectFrame::freeTextFrame()
|
2011-01-30 14:34:42 +00:00
|
|
|
{
|
2010-07-18 16:32:37 +00:00
|
|
|
delete m_textPicture;
|
|
|
|
m_textPicture = NULL;
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
2010-07-18 16:32:37 +00:00
|
|
|
|
2010-07-26 20:00:04 +00:00
|
|
|
void SceneXrender::EffectFrame::freeSelection()
|
2011-01-30 14:34:42 +00:00
|
|
|
{
|
2010-07-26 20:00:04 +00:00
|
|
|
delete m_selectionPicture;
|
|
|
|
m_selectionPicture = NULL;
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
2010-07-26 20:00:04 +00:00
|
|
|
|
2010-08-07 14:08:34 +00:00
|
|
|
void SceneXrender::EffectFrame::crossFadeIcon()
|
2011-01-30 14:34:42 +00:00
|
|
|
{
|
2010-08-07 14:08:34 +00:00
|
|
|
// TODO: implement me
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
2010-08-07 14:08:34 +00:00
|
|
|
|
|
|
|
void SceneXrender::EffectFrame::crossFadeText()
|
2011-01-30 14:34:42 +00:00
|
|
|
{
|
2010-08-07 14:08:34 +00:00
|
|
|
// TODO: implement me
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
2010-08-07 14:08:34 +00:00
|
|
|
|
2011-01-30 14:34:42 +00:00
|
|
|
void SceneXrender::EffectFrame::render(QRegion region, double opacity, double frameOpacity)
|
|
|
|
{
|
2011-12-31 14:04:14 +00:00
|
|
|
Q_UNUSED(region)
|
2011-01-30 14:34:42 +00:00
|
|
|
if (m_effectFrame->geometry().isEmpty()) {
|
2010-07-18 16:32:37 +00:00
|
|
|
return; // Nothing to display
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
2010-07-18 16:32:37 +00:00
|
|
|
|
|
|
|
// Render the actual frame
|
2013-02-06 07:03:14 +00:00
|
|
|
if (m_effectFrame->style() == EffectFrameUnstyled) {
|
|
|
|
renderUnstyled(effects->xrenderBufferPicture(), m_effectFrame->geometry(), opacity * frameOpacity);
|
|
|
|
} else if (m_effectFrame->style() == EffectFrameStyled) {
|
2011-01-30 14:34:42 +00:00
|
|
|
if (!m_picture) { // Lazy creation
|
2010-07-18 16:32:37 +00:00
|
|
|
updatePicture();
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
2011-06-11 12:48:16 +00:00
|
|
|
if (m_picture) {
|
|
|
|
qreal left, top, right, bottom;
|
|
|
|
m_effectFrame->frame().getMargins(left, top, right, bottom); // m_geometry is the inner geometry
|
|
|
|
QRect geom = m_effectFrame->geometry().adjusted(-left, -top, right, bottom);
|
2013-02-27 12:04:06 +00:00
|
|
|
xcb_render_composite(connection(), XCB_RENDER_PICT_OP_OVER, *m_picture,
|
|
|
|
XCB_RENDER_PICTURE_NONE, effects->xrenderBufferPicture(),
|
|
|
|
0, 0, 0, 0, geom.x(), geom.y(), geom.width(), geom.height());
|
2011-06-11 12:48:16 +00:00
|
|
|
}
|
2011-04-28 11:41:11 +00:00
|
|
|
}
|
|
|
|
if (!m_effectFrame->selection().isNull()) {
|
|
|
|
if (!m_selectionPicture) { // Lazy creation
|
2011-06-11 12:48:16 +00:00
|
|
|
const QPixmap pix = m_effectFrame->selectionFrame().framePixmap();
|
|
|
|
if (!pix.isNull()) // don't try if there's no content
|
2013-08-13 05:19:09 +00:00
|
|
|
m_selectionPicture = new XRenderPicture(m_effectFrame->selectionFrame().framePixmap().toImage());
|
2011-06-11 12:48:16 +00:00
|
|
|
}
|
|
|
|
if (m_selectionPicture) {
|
|
|
|
const QRect geom = m_effectFrame->selection();
|
2013-02-27 12:04:06 +00:00
|
|
|
xcb_render_composite(connection(), XCB_RENDER_PICT_OP_OVER, *m_selectionPicture,
|
|
|
|
XCB_RENDER_PICTURE_NONE, effects->xrenderBufferPicture(),
|
|
|
|
0, 0, 0, 0, geom.x(), geom.y(), geom.width(), geom.height());
|
2010-07-18 16:32:37 +00:00
|
|
|
}
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
2010-07-18 16:32:37 +00:00
|
|
|
|
|
|
|
XRenderPicture fill = xRenderBlendPicture(opacity);
|
|
|
|
|
|
|
|
// Render icon
|
2011-01-30 14:34:42 +00:00
|
|
|
if (!m_effectFrame->icon().isNull() && !m_effectFrame->iconSize().isEmpty()) {
|
|
|
|
QPoint topLeft(m_effectFrame->geometry().x(), m_effectFrame->geometry().center().y() - m_effectFrame->iconSize().height() / 2);
|
|
|
|
|
|
|
|
if (!m_iconPicture) // lazy creation
|
2013-12-06 13:41:23 +00:00
|
|
|
m_iconPicture = new XRenderPicture(m_effectFrame->icon().pixmap(m_effectFrame->iconSize()).toImage());
|
2011-01-30 14:34:42 +00:00
|
|
|
QRect geom = QRect(topLeft, m_effectFrame->iconSize());
|
2013-02-27 12:04:06 +00:00
|
|
|
xcb_render_composite(connection(), XCB_RENDER_PICT_OP_OVER, *m_iconPicture, fill,
|
|
|
|
effects->xrenderBufferPicture(),
|
|
|
|
0, 0, 0, 0, geom.x(), geom.y(), geom.width(), geom.height());
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
2010-07-18 16:32:37 +00:00
|
|
|
|
|
|
|
// Render text
|
2011-01-30 14:34:42 +00:00
|
|
|
if (!m_effectFrame->text().isEmpty()) {
|
|
|
|
if (!m_textPicture) { // Lazy creation
|
2010-07-18 16:32:37 +00:00
|
|
|
updateTextPicture();
|
|
|
|
}
|
2016-04-08 07:26:12 +00:00
|
|
|
|
|
|
|
if (m_textPicture) {
|
|
|
|
xcb_render_composite(connection(), XCB_RENDER_PICT_OP_OVER, *m_textPicture, fill, effects->xrenderBufferPicture(),
|
|
|
|
0, 0, 0, 0, m_effectFrame->geometry().x(), m_effectFrame->geometry().y(),
|
|
|
|
m_effectFrame->geometry().width(), m_effectFrame->geometry().height());
|
|
|
|
}
|
2010-07-18 16:32:37 +00:00
|
|
|
}
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
2010-07-18 16:32:37 +00:00
|
|
|
|
2013-02-06 07:03:14 +00:00
|
|
|
void SceneXrender::EffectFrame::renderUnstyled(xcb_render_picture_t pict, const QRect &rect, qreal opacity)
|
|
|
|
{
|
|
|
|
const int roundness = 5;
|
|
|
|
const QRect area = rect.adjusted(-roundness, -roundness, roundness, roundness);
|
|
|
|
xcb_rectangle_t rects[3];
|
|
|
|
// center
|
|
|
|
rects[0].x = area.left();
|
|
|
|
rects[0].y = area.top() + roundness;
|
|
|
|
rects[0].width = area.width();
|
|
|
|
rects[0].height = area.height() - roundness * 2;
|
|
|
|
// top
|
|
|
|
rects[1].x = area.left() + roundness;
|
|
|
|
rects[1].y = area.top();
|
|
|
|
rects[1].width = area.width() - roundness * 2;
|
|
|
|
rects[1].height = roundness;
|
|
|
|
// bottom
|
|
|
|
rects[2].x = area.left() + roundness;
|
|
|
|
rects[2].y = area.top() + area.height() - roundness;
|
|
|
|
rects[2].width = area.width() - roundness * 2;
|
|
|
|
rects[2].height = roundness;
|
|
|
|
xcb_render_color_t color = {0, 0, 0, uint16_t(opacity * 0xffff)};
|
|
|
|
xcb_render_fill_rectangles(connection(), XCB_RENDER_PICT_OP_OVER, pict, color, 3, rects);
|
|
|
|
|
|
|
|
if (!s_effectFrameCircle) {
|
|
|
|
// create the circle
|
|
|
|
const int diameter = roundness * 2;
|
|
|
|
xcb_pixmap_t pix = xcb_generate_id(connection());
|
|
|
|
xcb_create_pixmap(connection(), 32, pix, rootWindow(), diameter, diameter);
|
|
|
|
s_effectFrameCircle = new XRenderPicture(pix, 32);
|
|
|
|
xcb_free_pixmap(connection(), pix);
|
|
|
|
|
|
|
|
// clear it with transparent
|
|
|
|
xcb_rectangle_t xrect = {0, 0, diameter, diameter};
|
|
|
|
xcb_render_color_t tranparent = {0, 0, 0, 0};
|
|
|
|
xcb_render_fill_rectangles(connection(), XCB_RENDER_PICT_OP_SRC, *s_effectFrameCircle, tranparent, 1, &xrect);
|
|
|
|
|
2014-09-27 18:30:49 +00:00
|
|
|
static const int num_segments = 80;
|
|
|
|
static const qreal theta = 2 * M_PI / qreal(num_segments);
|
|
|
|
static const qreal c = qCos(theta); //precalculate the sine and cosine
|
|
|
|
static const qreal s = qSin(theta);
|
2013-02-06 07:03:14 +00:00
|
|
|
qreal t;
|
|
|
|
|
|
|
|
qreal x = roundness;//we start at angle = 0
|
|
|
|
qreal y = 0;
|
2013-07-03 19:30:51 +00:00
|
|
|
|
2013-02-06 07:03:14 +00:00
|
|
|
QVector<xcb_render_pointfix_t> points;
|
|
|
|
xcb_render_pointfix_t point;
|
|
|
|
point.x = DOUBLE_TO_FIXED(roundness);
|
|
|
|
point.y = DOUBLE_TO_FIXED(roundness);
|
|
|
|
points << point;
|
|
|
|
for (int ii = 0; ii <= num_segments; ++ii) {
|
|
|
|
point.x = DOUBLE_TO_FIXED(x + roundness);
|
|
|
|
point.y = DOUBLE_TO_FIXED(y + roundness);
|
|
|
|
points << point;
|
|
|
|
//apply the rotation matrix
|
|
|
|
t = x;
|
|
|
|
x = c * x - s * y;
|
|
|
|
y = s * t + c * y;
|
|
|
|
}
|
|
|
|
XRenderPicture fill = xRenderFill(Qt::black);
|
|
|
|
xcb_render_tri_fan(connection(), XCB_RENDER_PICT_OP_OVER, fill, *s_effectFrameCircle,
|
|
|
|
0, 0, 0, points.count(), points.constData());
|
|
|
|
}
|
|
|
|
// TODO: merge alpha mask with SceneXrender::Window::alphaMask
|
|
|
|
// alpha mask
|
|
|
|
xcb_pixmap_t pix = xcb_generate_id(connection());
|
|
|
|
xcb_create_pixmap(connection(), 8, pix, rootWindow(), 1, 1);
|
|
|
|
XRenderPicture alphaMask(pix, 8);
|
|
|
|
xcb_free_pixmap(connection(), pix);
|
|
|
|
const uint32_t values[] = {true};
|
|
|
|
xcb_render_change_picture(connection(), alphaMask, XCB_RENDER_CP_REPEAT, values);
|
|
|
|
color.alpha = int(opacity * 0xffff);
|
|
|
|
xcb_rectangle_t xrect = {0, 0, 1, 1};
|
|
|
|
xcb_render_fill_rectangles(connection(), XCB_RENDER_PICT_OP_SRC, alphaMask, color, 1, &xrect);
|
|
|
|
|
|
|
|
// TODO: replace by lambda
|
|
|
|
#define RENDER_CIRCLE(srcX, srcY, destX, destY) \
|
|
|
|
xcb_render_composite(connection(), XCB_RENDER_PICT_OP_OVER, *s_effectFrameCircle, alphaMask, \
|
|
|
|
pict, srcX, srcY, 0, 0, destX, destY, roundness, roundness)
|
|
|
|
|
|
|
|
RENDER_CIRCLE(0, 0, area.left(), area.top());
|
|
|
|
RENDER_CIRCLE(0, roundness, area.left(), area.top() + area.height() - roundness);
|
|
|
|
RENDER_CIRCLE(roundness, 0, area.left() + area.width() - roundness, area.top());
|
|
|
|
RENDER_CIRCLE(roundness, roundness,
|
|
|
|
area.left() + area.width() - roundness, area.top() + area.height() - roundness);
|
|
|
|
#undef RENDER_CIRCLE
|
|
|
|
}
|
|
|
|
|
2010-07-18 16:32:37 +00:00
|
|
|
void SceneXrender::EffectFrame::updatePicture()
|
2011-01-30 14:34:42 +00:00
|
|
|
{
|
2010-07-18 16:32:37 +00:00
|
|
|
delete m_picture;
|
2011-06-11 12:48:16 +00:00
|
|
|
m_picture = 0L;
|
|
|
|
if (m_effectFrame->style() == EffectFrameStyled) {
|
|
|
|
const QPixmap pix = m_effectFrame->frame().framePixmap();
|
|
|
|
if (!pix.isNull())
|
2013-08-13 05:19:09 +00:00
|
|
|
m_picture = new XRenderPicture(pix.toImage());
|
2011-06-11 12:48:16 +00:00
|
|
|
}
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
2010-07-18 16:32:37 +00:00
|
|
|
|
|
|
|
void SceneXrender::EffectFrame::updateTextPicture()
|
2011-01-30 14:34:42 +00:00
|
|
|
{
|
|
|
|
// Mostly copied from SceneOpenGL::EffectFrame::updateTextTexture() above
|
2010-07-18 16:32:37 +00:00
|
|
|
delete m_textPicture;
|
2011-06-11 12:48:16 +00:00
|
|
|
m_textPicture = 0L;
|
2010-07-18 16:32:37 +00:00
|
|
|
|
2011-01-30 14:34:42 +00:00
|
|
|
if (m_effectFrame->text().isEmpty()) {
|
2010-07-18 16:32:37 +00:00
|
|
|
return;
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
2010-07-18 16:32:37 +00:00
|
|
|
|
|
|
|
// Determine position on texture to paint text
|
2011-01-30 14:34:42 +00:00
|
|
|
QRect rect(QPoint(0, 0), m_effectFrame->geometry().size());
|
|
|
|
if (!m_effectFrame->icon().isNull() && !m_effectFrame->iconSize().isEmpty()) {
|
|
|
|
rect.setLeft(m_effectFrame->iconSize().width());
|
|
|
|
}
|
2010-07-18 16:32:37 +00:00
|
|
|
|
|
|
|
// If static size elide text as required
|
|
|
|
QString text = m_effectFrame->text();
|
2011-01-30 14:34:42 +00:00
|
|
|
if (m_effectFrame->isStatic()) {
|
|
|
|
QFontMetrics metrics(m_effectFrame->text());
|
|
|
|
text = metrics.elidedText(text, Qt::ElideRight, rect.width());
|
|
|
|
}
|
|
|
|
|
|
|
|
QPixmap pixmap(m_effectFrame->geometry().size());
|
|
|
|
pixmap.fill(Qt::transparent);
|
|
|
|
QPainter p(&pixmap);
|
|
|
|
p.setFont(m_effectFrame->font());
|
|
|
|
if (m_effectFrame->style() == EffectFrameStyled) {
|
|
|
|
p.setPen(m_effectFrame->styledTextColor());
|
|
|
|
} else {
|
2010-07-18 16:32:37 +00:00
|
|
|
// TODO: What about no frame? Custom color setting required
|
2011-01-30 14:34:42 +00:00
|
|
|
p.setPen(Qt::white);
|
2010-07-18 16:32:37 +00:00
|
|
|
}
|
2011-01-30 14:34:42 +00:00
|
|
|
p.drawText(rect, m_effectFrame->alignment(), text);
|
|
|
|
p.end();
|
2013-08-13 05:19:09 +00:00
|
|
|
m_textPicture = new XRenderPicture(pixmap.toImage());
|
2011-01-30 14:34:42 +00:00
|
|
|
}
|
2010-07-18 16:32:37 +00:00
|
|
|
|
2011-04-28 15:22:17 +00:00
|
|
|
SceneXRenderShadow::SceneXRenderShadow(Toplevel *toplevel)
|
|
|
|
:Shadow(toplevel)
|
|
|
|
{
|
2013-02-27 11:56:38 +00:00
|
|
|
for (int i=0; i<ShadowElementsCount; ++i) {
|
|
|
|
m_pictures[i] = NULL;
|
|
|
|
}
|
2011-04-28 15:22:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
SceneXRenderShadow::~SceneXRenderShadow()
|
|
|
|
{
|
2013-02-27 11:56:38 +00:00
|
|
|
for (int i=0; i<ShadowElementsCount; ++i) {
|
|
|
|
delete m_pictures[i];
|
|
|
|
}
|
2011-04-28 15:22:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void SceneXRenderShadow::layoutShadowRects(QRect& top, QRect& topRight,
|
|
|
|
QRect& right, QRect& bottomRight,
|
|
|
|
QRect& bottom, QRect& bottomLeft,
|
|
|
|
QRect& left, QRect& topLeft)
|
|
|
|
{
|
|
|
|
WindowQuadList quads = shadowQuads();
|
|
|
|
|
|
|
|
if (quads.count() == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
WindowQuad topQuad = quads.select(WindowQuadShadowTop)[0];
|
|
|
|
WindowQuad topRightQuad = quads.select(WindowQuadShadowTopRight)[0];
|
|
|
|
WindowQuad topLeftQuad = quads.select(WindowQuadShadowTopLeft)[0];
|
|
|
|
WindowQuad leftQuad = quads.select(WindowQuadShadowLeft)[0];
|
|
|
|
WindowQuad rightQuad = quads.select(WindowQuadShadowRight)[0];
|
|
|
|
WindowQuad bottomQuad = quads.select(WindowQuadShadowBottom)[0];
|
|
|
|
WindowQuad bottomRightQuad = quads.select(WindowQuadShadowBottomRight)[0];
|
|
|
|
WindowQuad bottomLeftQuad = quads.select(WindowQuadShadowBottomLeft)[0];
|
|
|
|
|
|
|
|
top = QRect(topQuad.left(), topQuad.top(), (topQuad.right()-topQuad.left()), (topQuad.bottom()-topQuad.top()));
|
|
|
|
topLeft = QRect(topLeftQuad.left(), topLeftQuad.top(), (topLeftQuad.right()-topLeftQuad.left()), (topLeftQuad.bottom()-topLeftQuad.top()));
|
|
|
|
topRight = QRect(topRightQuad.left(), topRightQuad.top(), (topRightQuad.right()-topRightQuad.left()), (topRightQuad.bottom()-topRightQuad.top()));
|
|
|
|
left = QRect(leftQuad.left(), leftQuad.top(), (leftQuad.right()-leftQuad.left()), (leftQuad.bottom()-leftQuad.top()));
|
|
|
|
right = QRect(rightQuad.left(), rightQuad.top(), (rightQuad.right()-rightQuad.left()), (rightQuad.bottom()-rightQuad.top()));
|
|
|
|
bottom = QRect(bottomQuad.left(), bottomQuad.top(), (bottomQuad.right()-bottomQuad.left()), (bottomQuad.bottom()-bottomQuad.top()));
|
|
|
|
bottomLeft = QRect(bottomLeftQuad.left(), bottomLeftQuad.top(), (bottomLeftQuad.right()-bottomLeftQuad.left()), (bottomLeftQuad.bottom()-bottomLeftQuad.top()));
|
|
|
|
bottomRight = QRect(bottomRightQuad.left(), bottomRightQuad.top(), (bottomRightQuad.right()-bottomRightQuad.left()), (bottomRightQuad.bottom()-bottomRightQuad.top()));
|
|
|
|
}
|
|
|
|
|
|
|
|
void SceneXRenderShadow::buildQuads()
|
|
|
|
{
|
|
|
|
Shadow::buildQuads();
|
|
|
|
|
2011-05-09 15:13:05 +00:00
|
|
|
if (shadowQuads().count() == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-04-28 15:22:17 +00:00
|
|
|
QRect stlr, str, strr, srr, sbrr, sbr, sblr, slr;
|
|
|
|
layoutShadowRects(str, strr, srr, sbrr, sbr, sblr, slr, stlr);
|
2013-02-27 11:56:38 +00:00
|
|
|
}
|
2011-04-28 15:22:17 +00:00
|
|
|
|
2013-02-27 11:56:38 +00:00
|
|
|
bool SceneXRenderShadow::prepareBackend()
|
|
|
|
{
|
2014-07-24 06:39:25 +00:00
|
|
|
if (hasDecorationShadow()) {
|
2015-01-12 09:13:47 +00:00
|
|
|
const QImage shadowImage = decorationShadowImage();
|
|
|
|
QPainter p;
|
|
|
|
int x = 0;
|
|
|
|
int y = 0;
|
|
|
|
auto drawElement = [this, &x, &y, &p, &shadowImage] (ShadowElements element) {
|
|
|
|
QPixmap pix(elementSize(element));
|
|
|
|
pix.fill(Qt::transparent);
|
|
|
|
p.begin(&pix);
|
|
|
|
p.drawImage(0, 0, shadowImage, x, y, pix.width(), pix.height());
|
|
|
|
p.end();
|
|
|
|
setShadowElement(pix, element);
|
|
|
|
return pix.size();
|
|
|
|
};
|
|
|
|
x += drawElement(ShadowElementTopLeft).width();
|
|
|
|
x += drawElement(ShadowElementTop).width();
|
|
|
|
y += drawElement(ShadowElementTopRight).height();
|
|
|
|
drawElement(ShadowElementRight);
|
|
|
|
x = 0;
|
|
|
|
y += drawElement(ShadowElementLeft).height();
|
|
|
|
x += drawElement(ShadowElementBottomLeft).width();
|
|
|
|
x += drawElement(ShadowElementBottom).width();
|
|
|
|
drawElement(ShadowElementBottomRight).width();
|
2014-07-24 06:39:25 +00:00
|
|
|
}
|
2013-02-27 11:56:38 +00:00
|
|
|
const uint32_t values[] = {XCB_RENDER_REPEAT_NORMAL};
|
|
|
|
for (int i=0; i<ShadowElementsCount; ++i) {
|
|
|
|
delete m_pictures[i];
|
2013-08-13 05:19:09 +00:00
|
|
|
m_pictures[i] = new XRenderPicture(shadowPixmap(ShadowElements(i)).toImage());
|
2013-02-27 11:56:38 +00:00
|
|
|
xcb_render_change_picture(connection(), *m_pictures[i], XCB_RENDER_CP_REPEAT, values);
|
2011-07-24 21:04:06 +00:00
|
|
|
}
|
2013-02-27 11:56:38 +00:00
|
|
|
return true;
|
|
|
|
}
|
2011-07-24 21:04:06 +00:00
|
|
|
|
2013-02-27 11:56:38 +00:00
|
|
|
xcb_render_picture_t SceneXRenderShadow::picture(Shadow::ShadowElements element) const
|
|
|
|
{
|
|
|
|
if (!m_pictures[element]) {
|
|
|
|
return XCB_RENDER_PICTURE_NONE;
|
|
|
|
}
|
|
|
|
return *m_pictures[element];
|
2011-04-28 15:22:17 +00:00
|
|
|
}
|
|
|
|
|
2014-07-22 11:11:19 +00:00
|
|
|
SceneXRenderDecorationRenderer::SceneXRenderDecorationRenderer(Decoration::DecoratedClientImpl *client)
|
|
|
|
: Renderer(client)
|
|
|
|
, m_gc(XCB_NONE)
|
|
|
|
{
|
2015-12-03 16:12:25 +00:00
|
|
|
connect(this, &Renderer::renderScheduled, client->client(), static_cast<void (AbstractClient::*)(const QRect&)>(&AbstractClient::addRepaint));
|
2014-07-22 11:11:19 +00:00
|
|
|
for (int i = 0; i < int(DecorationPart::Count); ++i) {
|
|
|
|
m_pixmaps[i] = XCB_PIXMAP_NONE;
|
|
|
|
m_pictures[i] = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
SceneXRenderDecorationRenderer::~SceneXRenderDecorationRenderer()
|
|
|
|
{
|
|
|
|
for (int i = 0; i < int(DecorationPart::Count); ++i) {
|
|
|
|
if (m_pixmaps[i] != XCB_PIXMAP_NONE) {
|
|
|
|
xcb_free_pixmap(connection(), m_pixmaps[i]);
|
|
|
|
}
|
|
|
|
delete m_pictures[i];
|
|
|
|
}
|
|
|
|
if (m_gc != 0) {
|
|
|
|
xcb_free_gc(connection(), m_gc);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SceneXRenderDecorationRenderer::render()
|
|
|
|
{
|
2015-01-19 14:28:17 +00:00
|
|
|
QRegion scheduled = getScheduled();
|
2014-07-22 11:11:19 +00:00
|
|
|
if (scheduled.isEmpty()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (areImageSizesDirty()) {
|
|
|
|
resizePixmaps();
|
|
|
|
resetImageSizesDirty();
|
2015-01-19 14:28:17 +00:00
|
|
|
scheduled = client()->client()->decorationRect();
|
2014-07-22 11:11:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const QRect top(QPoint(0, 0), m_sizes[int(DecorationPart::Top)]);
|
|
|
|
const QRect left(QPoint(0, top.height()), m_sizes[int(DecorationPart::Left)]);
|
|
|
|
const QRect right(QPoint(top.width() - m_sizes[int(DecorationPart::Right)].width(), top.height()), m_sizes[int(DecorationPart::Right)]);
|
|
|
|
const QRect bottom(QPoint(0, left.y() + left.height()), m_sizes[int(DecorationPart::Bottom)]);
|
|
|
|
|
|
|
|
xcb_connection_t *c = connection();
|
|
|
|
if (m_gc == 0) {
|
|
|
|
m_gc = xcb_generate_id(connection());
|
|
|
|
xcb_create_gc(c, m_gc, m_pixmaps[int(DecorationPart::Top)], 0, nullptr);
|
|
|
|
}
|
|
|
|
auto renderPart = [this, c](const QRect &geo, const QPoint &offset, int index) {
|
|
|
|
if (geo.isNull()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
QImage image = renderToImage(geo);
|
2017-11-01 17:54:21 +00:00
|
|
|
Q_ASSERT(image.devicePixelRatio() == 1);
|
2014-07-22 11:11:19 +00:00
|
|
|
xcb_put_image(c, XCB_IMAGE_FORMAT_Z_PIXMAP, m_pixmaps[index], m_gc,
|
|
|
|
image.width(), image.height(), geo.x() - offset.x(), geo.y() - offset.y(), 0, 32,
|
|
|
|
image.byteCount(), image.constBits());
|
|
|
|
};
|
|
|
|
const QRect geometry = scheduled.boundingRect();
|
|
|
|
renderPart(left.intersected(geometry), left.topLeft(), int(DecorationPart::Left));
|
|
|
|
renderPart(top.intersected(geometry), top.topLeft(), int(DecorationPart::Top));
|
|
|
|
renderPart(right.intersected(geometry), right.topLeft(), int(DecorationPart::Right));
|
|
|
|
renderPart(bottom.intersected(geometry), bottom.topLeft(), int(DecorationPart::Bottom));
|
|
|
|
xcb_flush(c);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SceneXRenderDecorationRenderer::resizePixmaps()
|
|
|
|
{
|
|
|
|
QRect left, top, right, bottom;
|
2014-07-25 10:55:28 +00:00
|
|
|
client()->client()->layoutDecorationRects(left, top, right, bottom);
|
2014-07-22 11:11:19 +00:00
|
|
|
|
|
|
|
xcb_connection_t *c = connection();
|
|
|
|
auto checkAndCreate = [this, c](int border, const QRect &rect) {
|
|
|
|
const QSize size = rect.size();
|
|
|
|
if (m_sizes[border] != size) {
|
|
|
|
m_sizes[border] = size;
|
|
|
|
if (m_pixmaps[border] != XCB_PIXMAP_NONE) {
|
|
|
|
xcb_free_pixmap(c, m_pixmaps[border]);
|
|
|
|
}
|
|
|
|
delete m_pictures[border];
|
|
|
|
if (!size.isEmpty()) {
|
|
|
|
m_pixmaps[border] = xcb_generate_id(connection());
|
|
|
|
xcb_create_pixmap(connection(), 32, m_pixmaps[border], rootWindow(), size.width(), size.height());
|
|
|
|
m_pictures[border] = new XRenderPicture(m_pixmaps[border], 32);
|
|
|
|
} else {
|
|
|
|
m_pixmaps[border] = XCB_PIXMAP_NONE;
|
|
|
|
m_pictures[border] = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!m_pictures[border]) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// fill transparent
|
|
|
|
xcb_rectangle_t r = {0, 0, uint16_t(size.width()), uint16_t(size.height())};
|
|
|
|
xcb_render_fill_rectangles(connection(), XCB_RENDER_PICT_OP_SRC, *m_pictures[border], preMultiply(Qt::transparent), 1, &r);
|
|
|
|
};
|
|
|
|
|
|
|
|
checkAndCreate(int(DecorationPart::Left), left);
|
|
|
|
checkAndCreate(int(DecorationPart::Top), top);
|
|
|
|
checkAndCreate(int(DecorationPart::Right), right);
|
|
|
|
checkAndCreate(int(DecorationPart::Bottom), bottom);
|
|
|
|
}
|
|
|
|
|
|
|
|
xcb_render_picture_t SceneXRenderDecorationRenderer::picture(SceneXRenderDecorationRenderer::DecorationPart part) const
|
|
|
|
{
|
|
|
|
Q_ASSERT(part != DecorationPart::Count);
|
|
|
|
XRenderPicture *picture = m_pictures[int(part)];
|
|
|
|
if (!picture) {
|
|
|
|
return XCB_RENDER_PICTURE_NONE;
|
|
|
|
}
|
|
|
|
return *picture;
|
|
|
|
}
|
|
|
|
|
2014-07-23 07:20:28 +00:00
|
|
|
void SceneXRenderDecorationRenderer::reparent(Deleted *deleted)
|
|
|
|
{
|
|
|
|
render();
|
|
|
|
Renderer::reparent(deleted);
|
|
|
|
}
|
|
|
|
|
2013-07-03 19:30:51 +00:00
|
|
|
#undef DOUBLE_TO_FIXED
|
|
|
|
#undef FIXED_TO_DOUBLE
|
|
|
|
|
2017-08-10 16:13:42 +00:00
|
|
|
XRenderFactory::XRenderFactory(QObject *parent)
|
|
|
|
: SceneFactory(parent)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
XRenderFactory::~XRenderFactory() = default;
|
|
|
|
|
|
|
|
Scene *XRenderFactory::create(QObject *parent) const
|
|
|
|
{
|
|
|
|
auto s = SceneXrender::createScene(parent);
|
|
|
|
if (s && s->initFailed()) {
|
|
|
|
delete s;
|
|
|
|
s = nullptr;
|
|
|
|
}
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
2007-04-29 17:35:43 +00:00
|
|
|
} // namespace
|
|
|
|
#endif
|
2017-06-12 20:28:39 +00:00
|
|
|
|
|
|
|
|
|
|
|
void KWin::SceneXrender::paintCursor()
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|