├── .github └── FUNDING.yml ├── .gitignore ├── CMakeLists.txt ├── README.md ├── screenshot.png └── src ├── CMakeLists.txt ├── blur ├── CMakeLists.txt ├── blur.cpp ├── blur.h ├── blur.kcfg ├── blur.qrc ├── blurconfig.kcfgc ├── kcm │ ├── CMakeLists.txt │ ├── blur_config.cpp │ ├── blur_config.h │ └── blur_config.ui ├── main.cpp ├── metadata.json └── shaders │ ├── downsample.frag │ ├── downsample_core.frag │ ├── noise.frag │ ├── noise_core.frag │ ├── upsample.frag │ ├── upsample_core.frag │ ├── vertex.vert │ └── vertex_core.vert ├── liblshelper ├── CMakeLists.txt ├── lshelper.cpp └── lshelper.h └── lightlyshaders ├── CMakeLists.txt ├── kcm ├── CMakeLists.txt ├── lightlyshaders_config.ui ├── lightlyshaders_kcm.cpp └── lightlyshaders_kcm.h ├── lightlyshaders.cpp ├── lightlyshaders.h ├── lightlyshaders.json ├── lightlyshaders.qrc ├── lightlyshaders_config.kcfg ├── lightlyshaders_config.kcfgc └── shaders ├── lightlyshaders.frag └── lightlyshaders_core.frag /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | custom: ['https://www.paypal.com/donate/?hosted_button_id=M4KBSFJJ5KB6G'] 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .cache/ 3 | .vscode/ 4 | .idea/ 5 | build 6 | cmake-build* 7 | qt6build -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16.0) 2 | 3 | project(lightlyshaders) 4 | set(PROJECT_VERSION "3.0.0") 5 | set(PROJECT_VERSION_MAJOR 0) 6 | 7 | set(KF_MIN_VERSION "5.240.0") 8 | set(QT_MIN_VERSION "6.6.0") 9 | set(QT_MAJOR_VERSION 6) 10 | 11 | set(CMAKE_CXX_STANDARD 20) 12 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 13 | set(CMAKE_CXX_EXTENSIONS OFF) 14 | 15 | if(NOT CMAKE_BUILD_TYPE) 16 | set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose Release or Debug" FORCE) 17 | endif() 18 | set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DQT_NO_DEBUG_OUTPUT") 19 | 20 | find_package(ECM REQUIRED NO_MODULE) 21 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) 22 | 23 | include(FeatureSummary) 24 | include(KDEInstallDirs) 25 | include(KDECMakeSettings) 26 | include(KDECompilerSettings NO_POLICY_SCOPE) 27 | 28 | find_package(Qt6 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS 29 | Gui 30 | Core 31 | DBus 32 | UiTools 33 | Widgets 34 | OpenGL 35 | Network 36 | Xml 37 | ) 38 | 39 | include_directories(${Qt6Widgets_INCLUDE_DIRS} ${Qt6Network_INCLUDE_DIRS} ${Qt6OpenGL_INCLUDE_DIRS} ${Qt6Xml_INCLUDE_DIRS}) 40 | add_definitions(${Qt6Widgets_DEFINITIONS}) 41 | 42 | find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS 43 | Config 44 | ConfigWidgets 45 | CoreAddons 46 | Crash 47 | GlobalAccel 48 | I18n 49 | KIO 50 | Service 51 | #Init 52 | Notifications 53 | Service 54 | WidgetsAddons 55 | WindowSystem 56 | GuiAddons 57 | KCMUtils 58 | ) 59 | 60 | find_package(epoxy REQUIRED) 61 | 62 | find_package(X11 REQUIRED) 63 | find_package(XCB REQUIRED COMPONENTS XCB) 64 | 65 | find_package(KWin REQUIRED COMPONENTS 66 | kwineffects 67 | ) 68 | 69 | find_package(KDecoration3 REQUIRED) 70 | 71 | find_package(KWinDBusInterface CONFIG REQUIRED) 72 | 73 | add_subdirectory(src) 74 | 75 | feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) 76 | 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Support Ukraine: 2 | - Via United24 platform (the initiative of the President of Ukraine): 3 | - [One click donation (credit card, bank transfer or crypto)](https://u24.gov.ua/) 4 | - Via National Bank of Ukraine: 5 | - [Ukrainian army](https://bank.gov.ua/en/about/support-the-armed-forces) 6 | - [Humanitarian aid to Ukraine](https://bank.gov.ua/en/about/humanitarian-aid-to-ukraine) 7 | 8 | # LightlyShaders v3.0 9 | This is a fork of Luwx's [LightlyShaders](https://github.com/Luwx/LightlyShaders), which in turn is a fork of [ShapeCorners](https://sourceforge.net/projects/shapecorners/). 10 | 11 | **It now also includes a fork of KWin Blur effect to fix the "korner bug".** It is disabled by default, you will have to enter Effects settings, disable the stock blur and enable the one with the "LightlyShaders" lable. 12 | 13 | This effect works correctly with stock Plasma effects. 14 | 15 | ![default](https://github.com/a-parhom/LightlyShaders/blob/plasma6/screenshot.png) 16 | 17 | # Dependencies: 18 | 19 | Plasma >= 6.0. 20 | 21 | You will need qt6, kf6 and kwin development packages. 22 | 23 | # Manual installation 24 | ``` 25 | git clone https://github.com/a-parhom/LightlyShaders 26 | 27 | cd LightlyShaders; 28 | 29 | mkdir qt6build; cd qt6build; cmake ../ -DCMAKE_INSTALL_PREFIX=/usr && make && sudo make install 30 | ``` 31 | 32 | ## Note 33 | After some updates of Plasma this plugin may need to be recompiled in order to work with changes introduced to KWin. 34 | 35 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a-parhom/LightlyShaders/42ba6df20b52bdb79e686e5a89d250884d64c4b3/screenshot.png -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(lightlyshaders) 2 | add_subdirectory(liblshelper) 3 | add_subdirectory(blur) -------------------------------------------------------------------------------- /src/blur/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(kcm) 2 | 3 | set(lightlyshaders_blur_SOURCES 4 | blur.cpp 5 | blur.qrc 6 | main.cpp 7 | ) 8 | 9 | kconfig_add_kcfg_files(lightlyshaders_blur_SOURCES 10 | blurconfig.kcfgc 11 | ) 12 | 13 | add_library(lightlyshaders_blur MODULE ${lightlyshaders_blur_SOURCES}) 14 | target_link_libraries(lightlyshaders_blur PRIVATE 15 | KWin::kwin 16 | 17 | KF6::ConfigGui 18 | PRIVATE XCB::XCB 19 | KDecoration3::KDecoration 20 | 21 | lshelper 22 | ) 23 | 24 | install(TARGETS lightlyshaders_blur DESTINATION ${KDE_INSTALL_PLUGINDIR}/kwin/effects/plugins) -------------------------------------------------------------------------------- /src/blur/blur.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2010 Fredrik Höglund 3 | SPDX-FileCopyrightText: 2011 Philipp Knechtges 4 | SPDX-FileCopyrightText: 2018 Alex Nemeth 5 | 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #include "blur.h" 10 | // KConfigSkeleton 11 | #include "blurconfig.h" 12 | 13 | #include "core/pixelgrid.h" 14 | #include "core/rendertarget.h" 15 | #include "core/renderviewport.h" 16 | #include "effect/effecthandler.h" 17 | #include "opengl/glplatform.h" 18 | #include "scene/decorationitem.h" 19 | // #include "scene/surfaceitem.h" 20 | #include "scene/windowitem.h" 21 | #include "wayland/blur.h" 22 | // #include "wayland/display.h" 23 | #include "wayland/surface.h" 24 | 25 | #if KWIN_BUILD_X11 26 | #include "utils/xcbutils.h" 27 | #endif 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include // for ceil() 36 | #include 37 | 38 | #include 39 | #include 40 | 41 | #include 42 | 43 | Q_LOGGING_CATEGORY(KWIN_BLUR, "kwin_effect_blur", QtWarningMsg) 44 | 45 | static void ensureResources() 46 | { 47 | // Must initialize resources manually because the effect is a static lib. 48 | Q_INIT_RESOURCE(blur); 49 | } 50 | 51 | namespace KWin 52 | { 53 | 54 | static const QByteArray s_blurAtomName = QByteArrayLiteral("_KDE_NET_WM_BLUR_BEHIND_REGION"); 55 | 56 | BlurManagerInterface *BlurEffect::s_blurManager = nullptr; 57 | QTimer *BlurEffect::s_blurManagerRemoveTimer = nullptr; 58 | 59 | BlurEffect::BlurEffect() 60 | { 61 | BlurConfig::instance(effects->config()); 62 | ensureResources(); 63 | 64 | m_helper = new LSHelper(); 65 | 66 | m_downsamplePass.shader = ShaderManager::instance()->generateShaderFromFile(ShaderTrait::MapTexture, 67 | QStringLiteral(":/effects/blur/shaders/vertex.vert"), 68 | QStringLiteral(":/effects/blur/shaders/downsample.frag")); 69 | if (!m_downsamplePass.shader) { 70 | qCWarning(KWIN_BLUR) << "Failed to load downsampling pass shader"; 71 | return; 72 | } else { 73 | m_downsamplePass.mvpMatrixLocation = m_downsamplePass.shader->uniformLocation("modelViewProjectionMatrix"); 74 | m_downsamplePass.offsetLocation = m_downsamplePass.shader->uniformLocation("offset"); 75 | m_downsamplePass.halfpixelLocation = m_downsamplePass.shader->uniformLocation("halfpixel"); 76 | } 77 | 78 | m_upsamplePass.shader = ShaderManager::instance()->generateShaderFromFile(ShaderTrait::MapTexture, 79 | QStringLiteral(":/effects/blur/shaders/vertex.vert"), 80 | QStringLiteral(":/effects/blur/shaders/upsample.frag")); 81 | if (!m_upsamplePass.shader) { 82 | qCWarning(KWIN_BLUR) << "Failed to load upsampling pass shader"; 83 | return; 84 | } else { 85 | m_upsamplePass.mvpMatrixLocation = m_upsamplePass.shader->uniformLocation("modelViewProjectionMatrix"); 86 | m_upsamplePass.offsetLocation = m_upsamplePass.shader->uniformLocation("offset"); 87 | m_upsamplePass.halfpixelLocation = m_upsamplePass.shader->uniformLocation("halfpixel"); 88 | } 89 | 90 | m_noisePass.shader = ShaderManager::instance()->generateShaderFromFile(ShaderTrait::MapTexture, 91 | QStringLiteral(":/effects/blur/shaders/vertex.vert"), 92 | QStringLiteral(":/effects/blur/shaders/noise.frag")); 93 | if (!m_noisePass.shader) { 94 | qCWarning(KWIN_BLUR) << "Failed to load noise pass shader"; 95 | return; 96 | } else { 97 | m_noisePass.mvpMatrixLocation = m_noisePass.shader->uniformLocation("modelViewProjectionMatrix"); 98 | m_noisePass.noiseTextureSizeLocation = m_noisePass.shader->uniformLocation("noiseTextureSize"); 99 | m_noisePass.texStartPosLocation = m_noisePass.shader->uniformLocation("texStartPos"); 100 | } 101 | 102 | initBlurStrengthValues(); 103 | reconfigure(ReconfigureAll); 104 | 105 | #if KWIN_BUILD_X11 106 | if (effects->xcbConnection()) { 107 | net_wm_blur_region = effects->announceSupportProperty(s_blurAtomName, this); 108 | } 109 | #endif 110 | 111 | if (effects->waylandDisplay()) { 112 | if (!s_blurManagerRemoveTimer) { 113 | s_blurManagerRemoveTimer = new QTimer(QCoreApplication::instance()); 114 | s_blurManagerRemoveTimer->setSingleShot(true); 115 | s_blurManagerRemoveTimer->callOnTimeout([]() { 116 | s_blurManager->remove(); 117 | s_blurManager = nullptr; 118 | }); 119 | } 120 | s_blurManagerRemoveTimer->stop(); 121 | if (!s_blurManager) { 122 | s_blurManager = new BlurManagerInterface(effects->waylandDisplay(), s_blurManagerRemoveTimer); 123 | } 124 | } 125 | 126 | connect(effects, &EffectsHandler::windowAdded, this, &BlurEffect::slotWindowAdded); 127 | connect(effects, &EffectsHandler::windowDeleted, this, &BlurEffect::slotWindowDeleted); 128 | connect(effects, &EffectsHandler::screenRemoved, this, &BlurEffect::slotScreenRemoved); 129 | #if KWIN_BUILD_X11 130 | connect(effects, &EffectsHandler::propertyNotify, this, &BlurEffect::slotPropertyNotify); 131 | connect(effects, &EffectsHandler::xcbConnectionChanged, this, [this]() { 132 | net_wm_blur_region = effects->announceSupportProperty(s_blurAtomName, this); 133 | }); 134 | #endif 135 | 136 | // Fetch the blur regions for all windows 137 | const auto stackingOrder = effects->stackingOrder(); 138 | for (EffectWindow *window : stackingOrder) { 139 | slotWindowAdded(window); 140 | } 141 | 142 | m_valid = true; 143 | } 144 | 145 | BlurEffect::~BlurEffect() 146 | { 147 | // When compositing is restarted, avoid removing the manager immediately. 148 | if (s_blurManager) { 149 | s_blurManagerRemoveTimer->start(1000); 150 | } 151 | } 152 | 153 | void BlurEffect::initBlurStrengthValues() 154 | { 155 | // This function creates an array of blur strength values that are evenly distributed 156 | 157 | // The range of the slider on the blur settings UI 158 | int numOfBlurSteps = 15; 159 | int remainingSteps = numOfBlurSteps; 160 | 161 | /* 162 | * Explanation for these numbers: 163 | * 164 | * The texture blur amount depends on the downsampling iterations and the offset value. 165 | * By changing the offset we can alter the blur amount without relying on further downsampling. 166 | * But there is a minimum and maximum value of offset per downsample iteration before we 167 | * get artifacts. 168 | * 169 | * The minOffset variable is the minimum offset value for an iteration before we 170 | * get blocky artifacts because of the downsampling. 171 | * 172 | * The maxOffset value is the maximum offset value for an iteration before we 173 | * get diagonal line artifacts because of the nature of the dual kawase blur algorithm. 174 | * 175 | * The expandSize value is the minimum value for an iteration before we reach the end 176 | * of a texture in the shader and sample outside of the area that was copied into the 177 | * texture from the screen. 178 | */ 179 | 180 | // {minOffset, maxOffset, expandSize} 181 | blurOffsets.append({1.0, 2.0, 10}); // Down sample size / 2 182 | blurOffsets.append({2.0, 3.0, 20}); // Down sample size / 4 183 | blurOffsets.append({2.0, 5.0, 50}); // Down sample size / 8 184 | blurOffsets.append({3.0, 8.0, 150}); // Down sample size / 16 185 | // blurOffsets.append({5.0, 10.0, 400}); // Down sample size / 32 186 | // blurOffsets.append({7.0, ?.0}); // Down sample size / 64 187 | 188 | float offsetSum = 0; 189 | 190 | for (int i = 0; i < blurOffsets.size(); i++) { 191 | offsetSum += blurOffsets[i].maxOffset - blurOffsets[i].minOffset; 192 | } 193 | 194 | for (int i = 0; i < blurOffsets.size(); i++) { 195 | int iterationNumber = std::ceil((blurOffsets[i].maxOffset - blurOffsets[i].minOffset) / offsetSum * numOfBlurSteps); 196 | remainingSteps -= iterationNumber; 197 | 198 | if (remainingSteps < 0) { 199 | iterationNumber += remainingSteps; 200 | } 201 | 202 | float offsetDifference = blurOffsets[i].maxOffset - blurOffsets[i].minOffset; 203 | 204 | for (int j = 1; j <= iterationNumber; j++) { 205 | // {iteration, offset} 206 | blurStrengthValues.append({i + 1, blurOffsets[i].minOffset + (offsetDifference / iterationNumber) * j}); 207 | } 208 | } 209 | } 210 | 211 | void BlurEffect::reconfigure(ReconfigureFlags flags) 212 | { 213 | Q_UNUSED(flags) 214 | BlurConfig::self()->read(); 215 | 216 | int blurStrength = BlurConfig::blurStrength() - 1; 217 | m_iterationCount = blurStrengthValues[blurStrength].iteration; 218 | m_offset = blurStrengthValues[blurStrength].offset; 219 | m_expandSize = blurOffsets[m_iterationCount - 1].expandSize; 220 | m_noiseStrength = BlurConfig::noiseStrength(); 221 | 222 | // Update all windows for the blur to take effect 223 | effects->addRepaintFull(); 224 | 225 | m_helper->reconfigure(); 226 | } 227 | 228 | void BlurEffect::updateBlurRegion(EffectWindow *w) 229 | { 230 | std::optional content; 231 | std::optional frame; 232 | 233 | #if KWIN_BUILD_X11 234 | if (net_wm_blur_region != XCB_ATOM_NONE) { 235 | const QByteArray value = w->readProperty(net_wm_blur_region, XCB_ATOM_CARDINAL, 32); 236 | QRegion region; 237 | if (value.size() > 0 && !(value.size() % (4 * sizeof(uint32_t)))) { 238 | const uint32_t *cardinals = reinterpret_cast(value.constData()); 239 | for (unsigned int i = 0; i < value.size() / sizeof(uint32_t);) { 240 | int x = cardinals[i++]; 241 | int y = cardinals[i++]; 242 | int w = cardinals[i++]; 243 | int h = cardinals[i++]; 244 | region += Xcb::fromXNative(QRect(x, y, w, h)).toRect(); 245 | } 246 | } 247 | if (!value.isNull()) { 248 | content = region; 249 | } 250 | } 251 | #endif 252 | 253 | SurfaceInterface *surf = w->surface(); 254 | 255 | if (surf && surf->blur()) { 256 | content = surf->blur()->region(); 257 | } 258 | 259 | if (auto internal = w->internalWindow()) { 260 | const auto property = internal->property("kwin_blur"); 261 | if (property.isValid()) { 262 | content = property.value(); 263 | } 264 | } 265 | 266 | if (w->decorationHasAlpha() && decorationSupportsBlurBehind(w)) { 267 | frame = decorationBlurRegion(w); 268 | } 269 | 270 | if (content.has_value() || frame.has_value()) { 271 | BlurEffectData &data = m_windows[w]; 272 | data.content = content; 273 | data.frame = frame; 274 | data.windowEffect = ItemEffect(w->windowItem()); 275 | } else { 276 | if (auto it = m_windows.find(w); it != m_windows.end()) { 277 | effects->makeOpenGLContextCurrent(); 278 | m_windows.erase(it); 279 | } 280 | } 281 | } 282 | 283 | void BlurEffect::slotWindowAdded(EffectWindow *w) 284 | { 285 | SurfaceInterface *surf = w->surface(); 286 | 287 | if (surf) { 288 | windowBlurChangedConnections[w] = connect(surf, &SurfaceInterface::blurChanged, this, [this, w]() { 289 | if (w) { 290 | updateBlurRegion(w); 291 | } 292 | }); 293 | } 294 | if (auto internal = w->internalWindow()) { 295 | internal->installEventFilter(this); 296 | } 297 | 298 | connect(w, &EffectWindow::windowDecorationChanged, this, &BlurEffect::setupDecorationConnections); 299 | setupDecorationConnections(w); 300 | 301 | updateBlurRegion(w); 302 | 303 | // Check if window needs rounding corners 304 | m_helper->blurWindowAdded(w); 305 | } 306 | 307 | void BlurEffect::slotWindowDeleted(EffectWindow *w) 308 | { 309 | if (auto it = m_windows.find(w); it != m_windows.end()) { 310 | effects->makeOpenGLContextCurrent(); 311 | m_windows.erase(it); 312 | } 313 | if (auto it = windowBlurChangedConnections.find(w); it != windowBlurChangedConnections.end()) { 314 | disconnect(*it); 315 | windowBlurChangedConnections.erase(it); 316 | } 317 | 318 | // remove window 319 | m_helper->blurWindowDeleted(w); 320 | } 321 | 322 | void BlurEffect::slotScreenRemoved(KWin::Output *screen) 323 | { 324 | for (auto &[window, data] : m_windows) { 325 | if (auto it = data.render.find(screen); it != data.render.end()) { 326 | effects->makeOpenGLContextCurrent(); 327 | data.render.erase(it); 328 | } 329 | } 330 | } 331 | 332 | #if KWIN_BUILD_X11 333 | void BlurEffect::slotPropertyNotify(EffectWindow *w, long atom) 334 | { 335 | if (w && atom == net_wm_blur_region && net_wm_blur_region != XCB_ATOM_NONE) { 336 | updateBlurRegion(w); 337 | } 338 | } 339 | #endif 340 | 341 | void BlurEffect::setupDecorationConnections(EffectWindow *w) 342 | { 343 | if (!w->decoration()) { 344 | return; 345 | } 346 | 347 | connect(w->decoration(), &KDecoration3::Decoration::blurRegionChanged, this, [this, w]() { 348 | updateBlurRegion(w); 349 | }); 350 | } 351 | 352 | bool BlurEffect::eventFilter(QObject *watched, QEvent *event) 353 | { 354 | auto internal = qobject_cast(watched); 355 | if (internal && event->type() == QEvent::DynamicPropertyChange) { 356 | QDynamicPropertyChangeEvent *pe = static_cast(event); 357 | if (pe->propertyName() == "kwin_blur") { 358 | if (auto w = effects->findWindow(internal)) { 359 | updateBlurRegion(w); 360 | } 361 | } 362 | } 363 | return false; 364 | } 365 | 366 | bool BlurEffect::enabledByDefault() 367 | { 368 | return false; 369 | } 370 | 371 | bool BlurEffect::supported() 372 | { 373 | return effects->openglContext() && (effects->openglContext()->supportsBlits() || effects->waylandDisplay()); 374 | } 375 | 376 | bool BlurEffect::decorationSupportsBlurBehind(const EffectWindow *w) const 377 | { 378 | return w->decoration() && !w->decoration()->blurRegion().isNull(); 379 | } 380 | 381 | QRegion BlurEffect::decorationBlurRegion(const EffectWindow *w) const 382 | { 383 | if (!decorationSupportsBlurBehind(w)) { 384 | return QRegion(); 385 | } 386 | // QRectF decorationRect = w->decoration()->rect(); 387 | // QPainterPath decorationPath; 388 | QRegion decorationRegion = QRegion(w->decoration()->rect().toAlignedRect()) - w->contentsRect().toRect(); 389 | //! we return only blurred regions that belong to decoration region 390 | return decorationRegion.intersected(w->decoration()->blurRegion()); 391 | } 392 | 393 | QRegion BlurEffect::blurRegion(EffectWindow *w) const 394 | { 395 | QRegion region; 396 | 397 | if (auto it = m_windows.find(w); it != m_windows.end()) { 398 | const std::optional &content = it->second.content; 399 | const std::optional &frame = it->second.frame; 400 | if (content.has_value()) { 401 | if (content->isEmpty()) { 402 | // An empty region means that the blur effect should be enabled 403 | // for the whole window. 404 | region = w->contentsRect().toRect(); 405 | } else { 406 | region = content->translated(w->contentsRect().topLeft().toPoint()) & w->contentsRect().toRect(); 407 | } 408 | if (frame.has_value()) { 409 | region += frame.value(); 410 | } 411 | } else if (frame.has_value()) { 412 | region = frame.value(); 413 | } 414 | 415 | // Apply LighlyShaders to blur region 416 | m_helper->roundBlurRegion(w, ®ion); 417 | } 418 | 419 | return region; 420 | } 421 | 422 | void BlurEffect::prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime) 423 | { 424 | m_paintedArea = QRegion(); 425 | m_currentBlur = QRegion(); 426 | m_currentScreen = effects->waylandDisplay() ? data.screen : nullptr; 427 | 428 | effects->prePaintScreen(data, presentTime); 429 | } 430 | 431 | void BlurEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std::chrono::milliseconds presentTime) 432 | { 433 | // this effect relies on prePaintWindow being called in the bottom to top order 434 | 435 | effects->prePaintWindow(w, data, presentTime); 436 | 437 | const QRegion oldOpaque = data.opaque; 438 | if (data.opaque.intersects(m_currentBlur)) { 439 | // to blur an area partially we have to shrink the opaque area of a window 440 | QRegion newOpaque; 441 | for (const QRect &rect : data.opaque) { 442 | newOpaque += rect.adjusted(m_expandSize, m_expandSize, -m_expandSize, -m_expandSize); 443 | } 444 | data.opaque = newOpaque; 445 | 446 | // we don't have to blur a region we don't see 447 | m_currentBlur -= newOpaque; 448 | } 449 | 450 | // if we have to paint a non-opaque part of this window that intersects with the 451 | // currently blurred region we have to redraw the whole region 452 | if ((data.paint - oldOpaque).intersects(m_currentBlur)) { 453 | data.paint += m_currentBlur; 454 | } 455 | 456 | // in case this window has regions to be blurred 457 | const QRegion blurArea = blurRegion(w).boundingRect().translated(w->pos().toPoint()); 458 | 459 | // if this window or a window underneath the blurred area is painted again we have to 460 | // blur everything 461 | if (m_paintedArea.intersects(blurArea) || data.paint.intersects(blurArea)) { 462 | data.paint += blurArea; 463 | // we have to check again whether we do not damage a blurred area 464 | // of a window 465 | if (blurArea.intersects(m_currentBlur)) { 466 | data.paint += m_currentBlur; 467 | } 468 | } 469 | 470 | m_currentBlur += blurArea; 471 | 472 | m_paintedArea -= data.opaque; 473 | m_paintedArea += data.paint; 474 | } 475 | 476 | bool BlurEffect::shouldBlur(const EffectWindow *w, int mask, const WindowPaintData &data) const 477 | { 478 | if (effects->activeFullScreenEffect() && !w->data(WindowForceBlurRole).toBool()) { 479 | return false; 480 | } 481 | 482 | if (w->isDesktop()) { 483 | return false; 484 | } 485 | 486 | bool scaled = !qFuzzyCompare(data.xScale(), 1.0) && !qFuzzyCompare(data.yScale(), 1.0); 487 | bool translated = data.xTranslation() || data.yTranslation(); 488 | 489 | if ((scaled || (translated || (mask & PAINT_WINDOW_TRANSFORMED))) && !w->data(WindowForceBlurRole).toBool()) { 490 | return false; 491 | } 492 | 493 | return true; 494 | } 495 | 496 | void BlurEffect::drawWindow(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, const QRegion ®ion, WindowPaintData &data) 497 | { 498 | blur(renderTarget, viewport, w, mask, region, data); 499 | 500 | // Draw the window over the blurred area 501 | effects->drawWindow(renderTarget, viewport, w, mask, region, data); 502 | } 503 | 504 | GLTexture *BlurEffect::ensureNoiseTexture() 505 | { 506 | if (m_noiseStrength == 0) { 507 | return nullptr; 508 | } 509 | 510 | const qreal scale = std::max(1.0, QGuiApplication::primaryScreen()->logicalDotsPerInch() / 96.0); 511 | if (!m_noisePass.noiseTexture || m_noisePass.noiseTextureScale != scale || m_noisePass.noiseTextureStength != m_noiseStrength) { 512 | // Init randomness based on time 513 | std::srand((uint)QTime::currentTime().msec()); 514 | 515 | QImage noiseImage(QSize(256, 256), QImage::Format_Grayscale8); 516 | 517 | for (int y = 0; y < noiseImage.height(); y++) { 518 | uint8_t *noiseImageLine = (uint8_t *)noiseImage.scanLine(y); 519 | 520 | for (int x = 0; x < noiseImage.width(); x++) { 521 | noiseImageLine[x] = std::rand() % m_noiseStrength; 522 | } 523 | } 524 | 525 | noiseImage = noiseImage.scaled(noiseImage.size() * scale); 526 | 527 | m_noisePass.noiseTexture = GLTexture::upload(noiseImage); 528 | if (!m_noisePass.noiseTexture) { 529 | return nullptr; 530 | } 531 | m_noisePass.noiseTexture->setFilter(GL_NEAREST); 532 | m_noisePass.noiseTexture->setWrapMode(GL_REPEAT); 533 | m_noisePass.noiseTextureScale = scale; 534 | m_noisePass.noiseTextureStength = m_noiseStrength; 535 | } 536 | 537 | return m_noisePass.noiseTexture.get(); 538 | } 539 | 540 | void BlurEffect::blur(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, const QRegion ®ion, WindowPaintData &data) 541 | { 542 | auto it = m_windows.find(w); 543 | if (it == m_windows.end()) { 544 | return; 545 | } 546 | 547 | BlurEffectData &blurInfo = it->second; 548 | BlurRenderData &renderInfo = blurInfo.render[m_currentScreen]; 549 | if (!shouldBlur(w, mask, data)) { 550 | return; 551 | } 552 | 553 | // Compute the effective blur shape. Note that if the window is transformed, so will be the blur shape. 554 | QRegion blurShape = blurRegion(w).translated(w->pos().toPoint()); 555 | if (data.xScale() != 1 || data.yScale() != 1) { 556 | QPoint pt = blurShape.boundingRect().topLeft(); 557 | QRegion scaledShape; 558 | for (const QRect &r : blurShape) { 559 | const QPointF topLeft(pt.x() + (r.x() - pt.x()) * data.xScale() + data.xTranslation(), 560 | pt.y() + (r.y() - pt.y()) * data.yScale() + data.yTranslation()); 561 | const QPoint bottomRight(std::floor(topLeft.x() + r.width() * data.xScale()) - 1, 562 | std::floor(topLeft.y() + r.height() * data.yScale()) - 1); 563 | scaledShape += QRect(QPoint(std::floor(topLeft.x()), std::floor(topLeft.y())), bottomRight); 564 | } 565 | blurShape = scaledShape; 566 | } else if (data.xTranslation() || data.yTranslation()) { 567 | blurShape.translate(std::round(data.xTranslation()), std::round(data.yTranslation())); 568 | } 569 | 570 | const QRect backgroundRect = blurShape.boundingRect(); 571 | const QRect deviceBackgroundRect = snapToPixelGrid(scaledRect(backgroundRect, viewport.scale())); 572 | const auto opacity = w->opacity() * data.opacity(); 573 | 574 | // Get the effective shape that will be actually blurred. It's possible that all of it will be clipped. 575 | QList effectiveShape; 576 | effectiveShape.reserve(blurShape.rectCount()); 577 | if (region != infiniteRegion()) { 578 | for (const QRect &clipRect : region) { 579 | const QRectF deviceClipRect = snapToPixelGridF(scaledRect(clipRect, viewport.scale())) 580 | .translated(-deviceBackgroundRect.topLeft()); 581 | for (const QRect &shapeRect : blurShape) { 582 | const QRectF deviceShapeRect = snapToPixelGridF(scaledRect(shapeRect.translated(-backgroundRect.topLeft()), viewport.scale())); 583 | if (const QRectF intersected = deviceClipRect.intersected(deviceShapeRect); !intersected.isEmpty()) { 584 | effectiveShape.append(intersected); 585 | } 586 | } 587 | } 588 | } else { 589 | for (const QRect &rect : blurShape) { 590 | effectiveShape.append(snapToPixelGridF(scaledRect(rect.translated(-backgroundRect.topLeft()), viewport.scale()))); 591 | } 592 | } 593 | if (effectiveShape.isEmpty()) { 594 | return; 595 | } 596 | 597 | // Maybe reallocate offscreen render targets. Keep in mind that the first one contains 598 | // original background behind the window, it's not blurred. 599 | GLenum textureFormat = GL_RGBA8; 600 | if (renderTarget.texture()) { 601 | textureFormat = renderTarget.texture()->internalFormat(); 602 | } 603 | 604 | if (renderInfo.framebuffers.size() != (m_iterationCount + 1) || renderInfo.textures[0]->size() != backgroundRect.size() || renderInfo.textures[0]->internalFormat() != textureFormat) { 605 | renderInfo.framebuffers.clear(); 606 | renderInfo.textures.clear(); 607 | 608 | for (size_t i = 0; i <= m_iterationCount; ++i) { 609 | auto texture = GLTexture::allocate(textureFormat, backgroundRect.size() / (1 << i)); 610 | if (!texture) { 611 | qCWarning(KWIN_BLUR) << "Failed to allocate an offscreen texture"; 612 | return; 613 | } 614 | texture->setFilter(GL_LINEAR); 615 | texture->setWrapMode(GL_CLAMP_TO_EDGE); 616 | 617 | auto framebuffer = std::make_unique(texture.get()); 618 | if (!framebuffer->valid()) { 619 | qCWarning(KWIN_BLUR) << "Failed to create an offscreen framebuffer"; 620 | return; 621 | } 622 | renderInfo.textures.push_back(std::move(texture)); 623 | renderInfo.framebuffers.push_back(std::move(framebuffer)); 624 | } 625 | } 626 | 627 | // Fetch the pixels behind the shape that is going to be blurred. 628 | const QRegion dirtyRegion = region & backgroundRect; 629 | for (const QRect &dirtyRect : dirtyRegion) { 630 | renderInfo.framebuffers[0]->blitFromRenderTarget(renderTarget, viewport, dirtyRect, dirtyRect.translated(-backgroundRect.topLeft())); 631 | } 632 | 633 | // Upload the geometry: the first 6 vertices are used when downsampling and upsampling offscreen, 634 | // the remaining vertices are used when rendering on the screen. 635 | GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); 636 | vbo->reset(); 637 | vbo->setAttribLayout(std::span(GLVertexBuffer::GLVertex2DLayout), sizeof(GLVertex2D)); 638 | 639 | const int vertexCount = effectiveShape.size() * 6; 640 | if (auto result = vbo->map(6 + vertexCount)) { 641 | auto map = *result; 642 | 643 | size_t vboIndex = 0; 644 | 645 | // The geometry that will be blurred offscreen, in logical pixels. 646 | { 647 | const QRectF localRect = QRectF(0, 0, backgroundRect.width(), backgroundRect.height()); 648 | 649 | const float x0 = localRect.left(); 650 | const float y0 = localRect.top(); 651 | const float x1 = localRect.right(); 652 | const float y1 = localRect.bottom(); 653 | 654 | const float u0 = x0 / backgroundRect.width(); 655 | const float v0 = 1.0f - y0 / backgroundRect.height(); 656 | const float u1 = x1 / backgroundRect.width(); 657 | const float v1 = 1.0f - y1 / backgroundRect.height(); 658 | 659 | // first triangle 660 | map[vboIndex++] = GLVertex2D{ 661 | .position = QVector2D(x0, y0), 662 | .texcoord = QVector2D(u0, v0), 663 | }; 664 | map[vboIndex++] = GLVertex2D{ 665 | .position = QVector2D(x1, y1), 666 | .texcoord = QVector2D(u1, v1), 667 | }; 668 | map[vboIndex++] = GLVertex2D{ 669 | .position = QVector2D(x0, y1), 670 | .texcoord = QVector2D(u0, v1), 671 | }; 672 | 673 | // second triangle 674 | map[vboIndex++] = GLVertex2D{ 675 | .position = QVector2D(x0, y0), 676 | .texcoord = QVector2D(u0, v0), 677 | }; 678 | map[vboIndex++] = GLVertex2D{ 679 | .position = QVector2D(x1, y0), 680 | .texcoord = QVector2D(u1, v0), 681 | }; 682 | map[vboIndex++] = GLVertex2D{ 683 | .position = QVector2D(x1, y1), 684 | .texcoord = QVector2D(u1, v1), 685 | }; 686 | } 687 | 688 | // The geometry that will be painted on screen, in device pixels. 689 | for (const QRectF &rect : effectiveShape) { 690 | const float x0 = rect.left(); 691 | const float y0 = rect.top(); 692 | const float x1 = rect.right(); 693 | const float y1 = rect.bottom(); 694 | 695 | const float u0 = x0 / deviceBackgroundRect.width(); 696 | const float v0 = 1.0f - y0 / deviceBackgroundRect.height(); 697 | const float u1 = x1 / deviceBackgroundRect.width(); 698 | const float v1 = 1.0f - y1 / deviceBackgroundRect.height(); 699 | 700 | // first triangle 701 | map[vboIndex++] = GLVertex2D{ 702 | .position = QVector2D(x0, y0), 703 | .texcoord = QVector2D(u0, v0), 704 | }; 705 | map[vboIndex++] = GLVertex2D{ 706 | .position = QVector2D(x1, y1), 707 | .texcoord = QVector2D(u1, v1), 708 | }; 709 | map[vboIndex++] = GLVertex2D{ 710 | .position = QVector2D(x0, y1), 711 | .texcoord = QVector2D(u0, v1), 712 | }; 713 | 714 | // second triangle 715 | map[vboIndex++] = GLVertex2D{ 716 | .position = QVector2D(x0, y0), 717 | .texcoord = QVector2D(u0, v0), 718 | }; 719 | map[vboIndex++] = GLVertex2D{ 720 | .position = QVector2D(x1, y0), 721 | .texcoord = QVector2D(u1, v0), 722 | }; 723 | map[vboIndex++] = GLVertex2D{ 724 | .position = QVector2D(x1, y1), 725 | .texcoord = QVector2D(u1, v1), 726 | }; 727 | } 728 | 729 | vbo->unmap(); 730 | } else { 731 | qCWarning(KWIN_BLUR) << "Failed to map vertex buffer"; 732 | return; 733 | } 734 | 735 | vbo->bindArrays(); 736 | 737 | // The downsample pass of the dual Kawase algorithm: the background will be scaled down 50% every iteration. 738 | { 739 | ShaderManager::instance()->pushShader(m_downsamplePass.shader.get()); 740 | 741 | QMatrix4x4 projectionMatrix; 742 | projectionMatrix.ortho(QRectF(0.0, 0.0, backgroundRect.width(), backgroundRect.height())); 743 | 744 | m_downsamplePass.shader->setUniform(m_downsamplePass.mvpMatrixLocation, projectionMatrix); 745 | m_downsamplePass.shader->setUniform(m_downsamplePass.offsetLocation, float(m_offset)); 746 | 747 | for (size_t i = 1; i < renderInfo.framebuffers.size(); ++i) { 748 | const auto &read = renderInfo.framebuffers[i - 1]; 749 | const auto &draw = renderInfo.framebuffers[i]; 750 | 751 | const QVector2D halfpixel(0.5 / read->colorAttachment()->width(), 752 | 0.5 / read->colorAttachment()->height()); 753 | m_downsamplePass.shader->setUniform(m_downsamplePass.halfpixelLocation, halfpixel); 754 | 755 | read->colorAttachment()->bind(); 756 | 757 | GLFramebuffer::pushFramebuffer(draw.get()); 758 | vbo->draw(GL_TRIANGLES, 0, 6); 759 | } 760 | 761 | ShaderManager::instance()->popShader(); 762 | } 763 | 764 | // The upsample pass of the dual Kawase algorithm: the background will be scaled up 200% every iteration. 765 | { 766 | ShaderManager::instance()->pushShader(m_upsamplePass.shader.get()); 767 | 768 | QMatrix4x4 projectionMatrix; 769 | projectionMatrix.ortho(QRectF(0.0, 0.0, backgroundRect.width(), backgroundRect.height())); 770 | 771 | m_upsamplePass.shader->setUniform(m_upsamplePass.mvpMatrixLocation, projectionMatrix); 772 | m_upsamplePass.shader->setUniform(m_upsamplePass.offsetLocation, float(m_offset)); 773 | 774 | for (size_t i = renderInfo.framebuffers.size() - 1; i > 1; --i) { 775 | GLFramebuffer::popFramebuffer(); 776 | const auto &read = renderInfo.framebuffers[i]; 777 | 778 | const QVector2D halfpixel(0.5 / read->colorAttachment()->width(), 779 | 0.5 / read->colorAttachment()->height()); 780 | m_upsamplePass.shader->setUniform(m_upsamplePass.halfpixelLocation, halfpixel); 781 | 782 | read->colorAttachment()->bind(); 783 | 784 | vbo->draw(GL_TRIANGLES, 0, 6); 785 | } 786 | 787 | // The last upsampling pass is rendered on the screen, not in framebuffers[0]. 788 | GLFramebuffer::popFramebuffer(); 789 | const auto &read = renderInfo.framebuffers[1]; 790 | 791 | projectionMatrix = viewport.projectionMatrix(); 792 | projectionMatrix.translate(deviceBackgroundRect.x(), deviceBackgroundRect.y()); 793 | m_upsamplePass.shader->setUniform(m_upsamplePass.mvpMatrixLocation, projectionMatrix); 794 | 795 | const QVector2D halfpixel(0.5 / read->colorAttachment()->width(), 796 | 0.5 / read->colorAttachment()->height()); 797 | m_upsamplePass.shader->setUniform(m_upsamplePass.halfpixelLocation, halfpixel); 798 | 799 | read->colorAttachment()->bind(); 800 | 801 | // Modulate the blurred texture with the window opacity if the window isn't opaque 802 | if (opacity < 1.0) { 803 | glEnable(GL_BLEND); 804 | float o = 1.0f - (opacity); 805 | o = 1.0f - o * o; 806 | glBlendColor(0, 0, 0, o); 807 | glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); 808 | } 809 | 810 | vbo->draw(GL_TRIANGLES, 6, vertexCount); 811 | 812 | if (opacity < 1.0) { 813 | glDisable(GL_BLEND); 814 | } 815 | 816 | ShaderManager::instance()->popShader(); 817 | } 818 | 819 | if (m_noiseStrength > 0) { 820 | // Apply an additive noise onto the blurred image. The noise is useful to mask banding 821 | // artifacts, which often happens due to the smooth color transitions in the blurred image. 822 | 823 | glEnable(GL_BLEND); 824 | if (opacity < 1.0) { 825 | glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE); 826 | } else { 827 | glBlendFunc(GL_ONE, GL_ONE); 828 | } 829 | 830 | if (GLTexture *noiseTexture = ensureNoiseTexture()) { 831 | ShaderManager::instance()->pushShader(m_noisePass.shader.get()); 832 | 833 | QMatrix4x4 projectionMatrix = viewport.projectionMatrix(); 834 | projectionMatrix.translate(deviceBackgroundRect.x(), deviceBackgroundRect.y()); 835 | 836 | m_noisePass.shader->setUniform(m_noisePass.mvpMatrixLocation, projectionMatrix); 837 | m_noisePass.shader->setUniform(m_noisePass.noiseTextureSizeLocation, QVector2D(noiseTexture->width(), noiseTexture->height())); 838 | m_noisePass.shader->setUniform(m_noisePass.texStartPosLocation, QVector2D(deviceBackgroundRect.topLeft())); 839 | 840 | noiseTexture->bind(); 841 | 842 | vbo->draw(GL_TRIANGLES, 6, vertexCount); 843 | 844 | ShaderManager::instance()->popShader(); 845 | } 846 | 847 | glDisable(GL_BLEND); 848 | } 849 | 850 | vbo->unbindArrays(); 851 | } 852 | 853 | bool BlurEffect::isActive() const 854 | { 855 | return m_valid && !effects->isScreenLocked(); 856 | } 857 | 858 | bool BlurEffect::blocksDirectScanout() const 859 | { 860 | return false; 861 | } 862 | 863 | } // namespace KWin 864 | 865 | #include "moc_blur.cpp" 866 | -------------------------------------------------------------------------------- /src/blur/blur.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2010 Fredrik Höglund 3 | SPDX-FileCopyrightText: 2018 Alex Nemeth 4 | 5 | SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | 8 | #pragma once 9 | 10 | #include "effect/effect.h" 11 | #include "opengl/glutils.h" 12 | #include "scene/item.h" 13 | 14 | #include 15 | 16 | #include 17 | 18 | #include "lshelper.h" 19 | 20 | namespace KWin 21 | { 22 | 23 | class BlurManagerInterface; 24 | 25 | struct BlurRenderData 26 | { 27 | /// Temporary render targets needed for the Dual Kawase algorithm, the first texture 28 | /// contains not blurred background behind the window, it's cached. 29 | std::vector> textures; 30 | std::vector> framebuffers; 31 | }; 32 | 33 | struct BlurEffectData 34 | { 35 | /// The region that should be blurred behind the window 36 | std::optional content; 37 | 38 | /// The region that should be blurred behind the frame 39 | std::optional frame; 40 | 41 | /// The render data per screen. Screens can have different color spaces. 42 | std::unordered_map render; 43 | 44 | ItemEffect windowEffect; 45 | }; 46 | 47 | class BlurEffect : public KWin::Effect 48 | { 49 | Q_OBJECT 50 | 51 | public: 52 | BlurEffect(); 53 | ~BlurEffect() override; 54 | 55 | static bool supported(); 56 | static bool enabledByDefault(); 57 | 58 | void reconfigure(ReconfigureFlags flags) override; 59 | void prePaintScreen(ScreenPrePaintData &data, std::chrono::milliseconds presentTime) override; 60 | void prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std::chrono::milliseconds presentTime) override; 61 | void drawWindow(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, const QRegion ®ion, WindowPaintData &data) override; 62 | 63 | bool provides(Feature feature) override; 64 | bool isActive() const override; 65 | 66 | int requestedEffectChainPosition() const override 67 | { 68 | return 20; 69 | } 70 | 71 | bool eventFilter(QObject *watched, QEvent *event) override; 72 | 73 | bool blocksDirectScanout() const override; 74 | 75 | public Q_SLOTS: 76 | void slotWindowAdded(KWin::EffectWindow *w); 77 | void slotWindowDeleted(KWin::EffectWindow *w); 78 | void slotScreenRemoved(KWin::Output *screen); 79 | #if KWIN_BUILD_X11 80 | void slotPropertyNotify(KWin::EffectWindow *w, long atom); 81 | #endif 82 | void setupDecorationConnections(EffectWindow *w); 83 | 84 | private: 85 | void initBlurStrengthValues(); 86 | QRegion blurRegion(EffectWindow *w) const; 87 | QRegion decorationBlurRegion(const EffectWindow *w) const; 88 | bool decorationSupportsBlurBehind(const EffectWindow *w) const; 89 | bool shouldBlur(const EffectWindow *w, int mask, const WindowPaintData &data) const; 90 | void updateBlurRegion(EffectWindow *w); 91 | void blur(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, const QRegion ®ion, WindowPaintData &data); 92 | GLTexture *ensureNoiseTexture(); 93 | 94 | private: 95 | LSHelper *m_helper; 96 | 97 | struct 98 | { 99 | std::unique_ptr shader; 100 | int mvpMatrixLocation; 101 | int offsetLocation; 102 | int halfpixelLocation; 103 | } m_downsamplePass; 104 | 105 | struct 106 | { 107 | std::unique_ptr shader; 108 | int mvpMatrixLocation; 109 | int offsetLocation; 110 | int halfpixelLocation; 111 | } m_upsamplePass; 112 | 113 | struct 114 | { 115 | std::unique_ptr shader; 116 | int mvpMatrixLocation; 117 | int noiseTextureSizeLocation; 118 | int texStartPosLocation; 119 | 120 | std::unique_ptr noiseTexture; 121 | qreal noiseTextureScale = 1.0; 122 | int noiseTextureStength = 0; 123 | } m_noisePass; 124 | 125 | bool m_valid = false; 126 | #if KWIN_BUILD_X11 127 | long net_wm_blur_region = 0; 128 | #endif 129 | QRegion m_paintedArea; // keeps track of all painted areas (from bottom to top) 130 | QRegion m_currentBlur; // keeps track of the currently blured area of the windows(from bottom to top) 131 | Output *m_currentScreen = nullptr; 132 | 133 | size_t m_iterationCount; // number of times the texture will be downsized to half size 134 | int m_offset; 135 | int m_expandSize; 136 | int m_noiseStrength; 137 | 138 | struct OffsetStruct 139 | { 140 | float minOffset; 141 | float maxOffset; 142 | int expandSize; 143 | }; 144 | 145 | QList blurOffsets; 146 | 147 | struct BlurValuesStruct 148 | { 149 | int iteration; 150 | float offset; 151 | }; 152 | 153 | QList blurStrengthValues; 154 | 155 | QMap windowBlurChangedConnections; 156 | std::unordered_map m_windows; 157 | 158 | static BlurManagerInterface *s_blurManager; 159 | static QTimer *s_blurManagerRemoveTimer; 160 | }; 161 | 162 | inline bool BlurEffect::provides(Effect::Feature feature) 163 | { 164 | if (feature == Blur) { 165 | return true; 166 | } 167 | return KWin::Effect::provides(feature); 168 | } 169 | 170 | } // namespace KWin 171 | -------------------------------------------------------------------------------- /src/blur/blur.kcfg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 15 10 | 11 | 12 | 5 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/blur/blur.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | shaders/downsample.frag 4 | shaders/downsample_core.frag 5 | shaders/noise.frag 6 | shaders/noise_core.frag 7 | shaders/upsample.frag 8 | shaders/upsample_core.frag 9 | shaders/vertex.vert 10 | shaders/vertex_core.vert 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/blur/blurconfig.kcfgc: -------------------------------------------------------------------------------- 1 | File=blur.kcfg 2 | ClassName=BlurConfig 3 | NameSpace=KWin 4 | Singleton=true 5 | Mutators=true 6 | -------------------------------------------------------------------------------- /src/blur/kcm/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(kwin_lightlyshaders_blur_config_SRCS blur_config.cpp) 2 | ki18n_wrap_ui(kwin_lightlyshaders_blur_config_SRCS blur_config.ui) 3 | kconfig_add_kcfg_files(kwin_lightlyshaders_blur_config_SRCS ../blurconfig.kcfgc) 4 | 5 | qt_add_dbus_interface(kwin_lightlyshaders_blur_config_SRCS ${KWIN_EFFECTS_INTERFACE} kwineffects_interface) 6 | 7 | #kwin_add_effect_config(kwin_lightlyshaders_blur_config ${kwin_lightlyshaders_blur_config_SRCS}) 8 | add_library(kwin_lightlyshaders_blur_config MODULE ${kwin_lightlyshaders_blur_config_SRCS}) 9 | target_link_libraries(kwin_lightlyshaders_blur_config 10 | KF6::KCMUtils 11 | KF6::CoreAddons 12 | KF6::I18n 13 | Qt6::DBus 14 | ) 15 | 16 | install(TARGETS kwin_lightlyshaders_blur_config DESTINATION ${KDE_INSTALL_PLUGINDIR}/kwin/effects/configs) 17 | -------------------------------------------------------------------------------- /src/blur/kcm/blur_config.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2010 Fredrik Höglund 3 | 4 | SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | #include "blur_config.h" 7 | 8 | //#include 9 | 10 | // KConfigSkeleton 11 | #include "blurconfig.h" 12 | 13 | #include 14 | #include "kwineffects_interface.h" 15 | 16 | 17 | namespace KWin 18 | { 19 | 20 | K_PLUGIN_CLASS(BlurEffectConfig) 21 | 22 | BlurEffectConfig::BlurEffectConfig(QObject *parent, const KPluginMetaData &data) 23 | : KCModule(parent, data) 24 | { 25 | ui.setupUi(widget()); 26 | //BlurConfig::instance(KWIN_CONFIG); 27 | BlurConfig::instance("kwinrc"); 28 | addConfig(BlurConfig::self(), widget()); 29 | } 30 | 31 | BlurEffectConfig::~BlurEffectConfig() 32 | { 33 | } 34 | 35 | void BlurEffectConfig::save() 36 | { 37 | KCModule::save(); 38 | 39 | OrgKdeKwinEffectsInterface interface(QStringLiteral("org.kde.KWin"), 40 | QStringLiteral("/Effects"), 41 | QDBusConnection::sessionBus()); 42 | interface.reconfigureEffect(QStringLiteral("lightlyshaders_blur")); 43 | } 44 | 45 | } // namespace KWin 46 | 47 | #include "blur_config.moc" 48 | 49 | #include "moc_blur_config.cpp" 50 | -------------------------------------------------------------------------------- /src/blur/kcm/blur_config.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2010 Fredrik Höglund 3 | 4 | SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "ui_blur_config.h" 10 | #include 11 | 12 | namespace KWin 13 | { 14 | 15 | class BlurEffectConfig : public KCModule 16 | { 17 | Q_OBJECT 18 | 19 | public: 20 | explicit BlurEffectConfig(QObject *parent, const KPluginMetaData &data); 21 | ~BlurEffectConfig() override; 22 | 23 | void save() override; 24 | 25 | private: 26 | ::Ui::BlurEffectConfig ui; 27 | }; 28 | 29 | } // namespace KWin 30 | -------------------------------------------------------------------------------- /src/blur/kcm/blur_config.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | BlurEffectConfig 4 | 5 | 6 | 7 | 0 8 | 0 9 | 480 10 | 184 11 | 12 | 13 | 14 | 15 | 16 | 17 | Blur strength: 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | Qt::Horizontal 27 | 28 | 29 | QSizePolicy::Fixed 30 | 31 | 32 | 33 | 20 34 | 20 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | Light 43 | 44 | 45 | 46 | 47 | 48 | 49 | 1 50 | 51 | 52 | 15 53 | 54 | 55 | 1 56 | 57 | 58 | 1 59 | 60 | 61 | 10 62 | 63 | 64 | Qt::Horizontal 65 | 66 | 67 | QSlider::TicksBelow 68 | 69 | 70 | 71 | 72 | 73 | 74 | Strong 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | Noise strength: 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | Qt::Horizontal 93 | 94 | 95 | QSizePolicy::Fixed 96 | 97 | 98 | 99 | 20 100 | 20 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | Light 109 | 110 | 111 | 112 | 113 | 114 | 115 | 14 116 | 117 | 118 | 5 119 | 120 | 121 | 5 122 | 123 | 124 | Qt::Horizontal 125 | 126 | 127 | QSlider::TicksBelow 128 | 129 | 130 | 1 131 | 132 | 133 | 134 | 135 | 136 | 137 | Strong 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | Qt::Vertical 147 | 148 | 149 | 150 | 0 151 | 0 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | -------------------------------------------------------------------------------- /src/blur/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2021 Vlad Zahorodnii 3 | 4 | SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | #include "blur.h" 8 | 9 | namespace KWin 10 | { 11 | 12 | KWIN_EFFECT_FACTORY_SUPPORTED_ENABLED(BlurEffect, 13 | "metadata.json", 14 | return BlurEffect::supported(); 15 | , 16 | return BlurEffect::enabledByDefault();) 17 | 18 | } // namespace KWin 19 | 20 | #include "main.moc" 21 | -------------------------------------------------------------------------------- /src/blur/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "KPlugin": { 3 | "Category": "Appearance", 4 | "Description": "Blurs the background behind semi-transparent windows", 5 | "Description[ar]": "تغشي الخلفية خلف النوافذ شبه الشفافة", 6 | "Description[az]": "Yarımşəffaf pəncərələrin altındakı fonu yayğınlaşdırmaq", 7 | "Description[be]": "Размыццё фону за паўпразрыстымі вокнамі", 8 | "Description[bg]": "Размива фона зад полупрозрачни прозорци", 9 | "Description[ca@valencia]": "Difumina el fons de darrere de les finestres semitransparents", 10 | "Description[ca]": "Difumina el fons de darrere de les finestres semitransparents", 11 | "Description[cs]": "Rozostří pozadí poloprůhledných oken", 12 | "Description[de]": "Verwischt den Hintergrund halbtransparenter Fenster", 13 | "Description[en_GB]": "Blurs the background behind semi-transparent windows", 14 | "Description[eo]": "Malklarigas la fonon malantaŭ duontravideblaj fenestroj", 15 | "Description[es]": "Difumina el fondo detrás de las ventanas semitransparentes", 16 | "Description[et]": "Poolläbipaistvate akende tausta hägustamine", 17 | "Description[eu]": "Leiho erdi-gardenen atzeko planoa lausotzen du", 18 | "Description[fi]": "Sumentaa taustan puoliksi läpikuultavien ikkunoiden takana", 19 | "Description[fr]": "Rend flou l'arrière-plan sous les fenêtres semi-transparentes", 20 | "Description[gl]": "Desenfoca o fondo tras das xanelas semitransparentes.", 21 | "Description[he]": "מטשטש את הרקע מאחורי חלונות שקופים למחצה", 22 | "Description[hu]": "Elmosódottá teszi a félig áttetsző ablakok hátterét", 23 | "Description[ia]": "Obscura le fundo detra fenestras semi-transparente", 24 | "Description[id]": "Memburamkan belakang latar-belakang jendela semi transparan", 25 | "Description[is]": "Setur bakgrunn undir hálfgagnsæjum gluggum í móðu", 26 | "Description[it]": "Sfoca lo sfondo dietro a finestre semitrasparenti", 27 | "Description[ja]": "半透明なウィンドウの背景をぼかします", 28 | "Description[ka]": "ნახევრადგამჭვირვალე ფანჯრებს მიღმა ფონის დაბინდვა", 29 | "Description[ko]": "반투명 창의 뒷배경을 흐리게 합니다", 30 | "Description[nl]": "Vervaagt de achtergrond van halftransparante vensters", 31 | "Description[nn]": "Gjer bakgrunnen til halvgjennomsiktige vindauge uklar", 32 | "Description[pl]": "Rozmywa tło za półprzezroczystymi oknami", 33 | "Description[pt]": "Borra o fundo atrás das janelas semi-transparentes", 34 | "Description[pt_BR]": "Borra o plano de fundo por trás das janelas semitransparentes", 35 | "Description[ro]": "Estompează fundalul în spatele ferestrelor semi-transparente", 36 | "Description[ru]": "Размывание фона под полупрозрачными окнами", 37 | "Description[sk]": "Rozmaže pozadie za polopriehľadnými oknami", 38 | "Description[sl]": "Zabriše ozadje za polprosojnimi okni", 39 | "Description[sv]": "Gör bakgrunden bakom halvgenomskinliga fönster suddig", 40 | "Description[ta]": "ஒளிபுகும் சாளரங்களின் பின்புலத்தை மங்கலாக்கும்", 41 | "Description[tr]": "Yarısaydam pencerelerin ardını bulanıklaştırır", 42 | "Description[uk]": "Розмивання тла напівпрозорих вікон", 43 | "Description[vi]": "Làm mờ phần nền phía sau các cửa sổ bán trong suốt", 44 | "Description[x-test]": "xxBlurs the background behind semi-transparent windowsxx", 45 | "Description[zh_CN]": "对半透明窗口的背景进行虚化处理", 46 | "Description[zh_TW]": "模糊半透明視窗的背景", 47 | "EnabledByDefault": false, 48 | "License": "GPL", 49 | "Name": "Blur (LightlyShaders)", 50 | "Name[ar]": "غشاوة (LightlyShaders)", 51 | "Name[az]": "Bulanıq (LightlyShaders)", 52 | "Name[be]": "Размыццё (LightlyShaders)", 53 | "Name[bg]": "Замъгляване (LightlyShaders)", 54 | "Name[ca@valencia]": "Difuminat (LightlyShaders)", 55 | "Name[ca]": "Difuminat (LightlyShaders)", 56 | "Name[cs]": "Rozostření (LightlyShaders)", 57 | "Name[de]": "Verwischen (LightlyShaders)", 58 | "Name[en_GB]": "Blur (LightlyShaders)", 59 | "Name[eo]": "Malklarigi (LightlyShaders)", 60 | "Name[es]": "Desenfocar (LightlyShaders)", 61 | "Name[et]": "Hägu (LightlyShaders)", 62 | "Name[eu]": "Lausotu (LightlyShaders)", 63 | "Name[fi]": "Sumennus (LightlyShaders)", 64 | "Name[fr]": "Flou (LightlyShaders)", 65 | "Name[gl]": "Desenfocar (LightlyShaders)", 66 | "Name[he]": "טשטוש (LightlyShaders)", 67 | "Name[hu]": "Elmosás (LightlyShaders)", 68 | "Name[ia]": "Obscura (Blur) (LightlyShaders)", 69 | "Name[id]": "Blur (LightlyShaders)", 70 | "Name[is]": "Móða (LightlyShaders)", 71 | "Name[it]": "Sfocatura (LightlyShaders)", 72 | "Name[ja]": "ぼかし (LightlyShaders)", 73 | "Name[ka]": "ბუნდოვნება (LightlyShaders)", 74 | "Name[ko]": "흐리게 (LightlyShaders)", 75 | "Name[nl]": "Vervagen (LightlyShaders)", 76 | "Name[nn]": "Uklar (LightlyShaders)", 77 | "Name[pl]": "Rozmycie (LightlyShaders)", 78 | "Name[pt]": "BlueFish (LightlyShaders)", 79 | "Name[pt_BR]": "Borrar (LightlyShaders)", 80 | "Name[ro]": "Estompare (LightlyShaders)", 81 | "Name[ru]": "Размытие (LightlyShaders)", 82 | "Name[sk]": "Rozmazanie (LightlyShaders)", 83 | "Name[sl]": "Zabriši (LightlyShaders)", 84 | "Name[sv]": "Oskärpa (LightlyShaders)", 85 | "Name[ta]": "மங்கலாக்கு (LightlyShaders)", 86 | "Name[tr]": "Bulanıklaştır (LightlyShaders)", 87 | "Name[uk]": "Розмиття (LightlyShaders)", 88 | "Name[vi]": "Mờ (LightlyShaders)", 89 | "Name[x-test]": "xxBlurxx (LightlyShaders)", 90 | "Name[zh_CN]": "窗口背景虚化 (LightlyShaders)", 91 | "Name[zh_TW]": "模糊 (LightlyShaders)" 92 | }, 93 | "X-KDE-ConfigModule": "kwin_lightlyshaders_blur_config", 94 | "org.kde.kwin.effect": { 95 | "enabledByDefaultMethod": true 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/blur/shaders/downsample.frag: -------------------------------------------------------------------------------- 1 | uniform sampler2D texUnit; 2 | uniform float offset; 3 | uniform vec2 halfpixel; 4 | 5 | varying vec2 uv; 6 | 7 | void main(void) 8 | { 9 | vec4 sum = texture2D(texUnit, uv) * 4.0; 10 | sum += texture2D(texUnit, uv - halfpixel.xy * offset); 11 | sum += texture2D(texUnit, uv + halfpixel.xy * offset); 12 | sum += texture2D(texUnit, uv + vec2(halfpixel.x, -halfpixel.y) * offset); 13 | sum += texture2D(texUnit, uv - vec2(halfpixel.x, -halfpixel.y) * offset); 14 | 15 | gl_FragColor = sum / 8.0; 16 | } 17 | -------------------------------------------------------------------------------- /src/blur/shaders/downsample_core.frag: -------------------------------------------------------------------------------- 1 | #version 140 2 | 3 | uniform sampler2D texUnit; 4 | uniform float offset; 5 | uniform vec2 halfpixel; 6 | 7 | in vec2 uv; 8 | 9 | out vec4 fragColor; 10 | 11 | void main(void) 12 | { 13 | vec4 sum = texture(texUnit, uv) * 4.0; 14 | sum += texture(texUnit, uv - halfpixel.xy * offset); 15 | sum += texture(texUnit, uv + halfpixel.xy * offset); 16 | sum += texture(texUnit, uv + vec2(halfpixel.x, -halfpixel.y) * offset); 17 | sum += texture(texUnit, uv - vec2(halfpixel.x, -halfpixel.y) * offset); 18 | 19 | fragColor = sum / 8.0; 20 | } 21 | -------------------------------------------------------------------------------- /src/blur/shaders/noise.frag: -------------------------------------------------------------------------------- 1 | uniform sampler2D texUnit; 2 | uniform vec2 noiseTextureSize; 3 | uniform vec2 texStartPos; 4 | 5 | varying vec2 uv; 6 | 7 | void main(void) 8 | { 9 | vec2 uvNoise = vec2((texStartPos.xy + gl_FragCoord.xy) / noiseTextureSize); 10 | 11 | gl_FragColor = vec4(texture2D(texUnit, uvNoise).rrr, 0); 12 | } 13 | -------------------------------------------------------------------------------- /src/blur/shaders/noise_core.frag: -------------------------------------------------------------------------------- 1 | #version 140 2 | 3 | uniform sampler2D texUnit; 4 | uniform vec2 noiseTextureSize; 5 | uniform vec2 texStartPos; 6 | 7 | in vec2 uv; 8 | 9 | out vec4 fragColor; 10 | 11 | void main(void) 12 | { 13 | vec2 uvNoise = vec2((texStartPos.xy + gl_FragCoord.xy) / noiseTextureSize); 14 | 15 | fragColor = vec4(texture(texUnit, uvNoise).rrr, 0); 16 | } 17 | -------------------------------------------------------------------------------- /src/blur/shaders/upsample.frag: -------------------------------------------------------------------------------- 1 | uniform sampler2D texUnit; 2 | uniform float offset; 3 | uniform vec2 halfpixel; 4 | 5 | varying vec2 uv; 6 | 7 | void main(void) 8 | { 9 | vec4 sum = texture2D(texUnit, uv + vec2(-halfpixel.x * 2.0, 0.0) * offset); 10 | sum += texture2D(texUnit, uv + vec2(-halfpixel.x, halfpixel.y) * offset) * 2.0; 11 | sum += texture2D(texUnit, uv + vec2(0.0, halfpixel.y * 2.0) * offset); 12 | sum += texture2D(texUnit, uv + vec2(halfpixel.x, halfpixel.y) * offset) * 2.0; 13 | sum += texture2D(texUnit, uv + vec2(halfpixel.x * 2.0, 0.0) * offset); 14 | sum += texture2D(texUnit, uv + vec2(halfpixel.x, -halfpixel.y) * offset) * 2.0; 15 | sum += texture2D(texUnit, uv + vec2(0.0, -halfpixel.y * 2.0) * offset); 16 | sum += texture2D(texUnit, uv + vec2(-halfpixel.x, -halfpixel.y) * offset) * 2.0; 17 | 18 | gl_FragColor = sum / 12.0; 19 | } 20 | -------------------------------------------------------------------------------- /src/blur/shaders/upsample_core.frag: -------------------------------------------------------------------------------- 1 | #version 140 2 | 3 | uniform sampler2D texUnit; 4 | uniform float offset; 5 | uniform vec2 halfpixel; 6 | 7 | in vec2 uv; 8 | 9 | out vec4 fragColor; 10 | 11 | void main(void) 12 | { 13 | vec4 sum = texture(texUnit, uv + vec2(-halfpixel.x * 2.0, 0.0) * offset); 14 | sum += texture(texUnit, uv + vec2(-halfpixel.x, halfpixel.y) * offset) * 2.0; 15 | sum += texture(texUnit, uv + vec2(0.0, halfpixel.y * 2.0) * offset); 16 | sum += texture(texUnit, uv + vec2(halfpixel.x, halfpixel.y) * offset) * 2.0; 17 | sum += texture(texUnit, uv + vec2(halfpixel.x * 2.0, 0.0) * offset); 18 | sum += texture(texUnit, uv + vec2(halfpixel.x, -halfpixel.y) * offset) * 2.0; 19 | sum += texture(texUnit, uv + vec2(0.0, -halfpixel.y * 2.0) * offset); 20 | sum += texture(texUnit, uv + vec2(-halfpixel.x, -halfpixel.y) * offset) * 2.0; 21 | 22 | fragColor = sum / 12.0; 23 | } 24 | -------------------------------------------------------------------------------- /src/blur/shaders/vertex.vert: -------------------------------------------------------------------------------- 1 | uniform mat4 modelViewProjectionMatrix; 2 | 3 | attribute vec2 position; 4 | attribute vec2 texcoord; 5 | 6 | varying vec2 uv; 7 | 8 | void main(void) 9 | { 10 | gl_Position = modelViewProjectionMatrix * vec4(position, 0.0, 1.0); 11 | uv = texcoord; 12 | } 13 | -------------------------------------------------------------------------------- /src/blur/shaders/vertex_core.vert: -------------------------------------------------------------------------------- 1 | #version 140 2 | 3 | uniform mat4 modelViewProjectionMatrix; 4 | 5 | in vec2 position; 6 | in vec2 texcoord; 7 | 8 | out vec2 uv; 9 | 10 | void main(void) 11 | { 12 | gl_Position = modelViewProjectionMatrix * vec4(position, 0.0, 1.0); 13 | uv = texcoord; 14 | } 15 | -------------------------------------------------------------------------------- /src/liblshelper/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(lshelper_LIB_SRCS 2 | lshelper.h 3 | lshelper.cpp 4 | ) 5 | 6 | kconfig_add_kcfg_files(lshelper_LIB_SRCS ../lightlyshaders/lightlyshaders_config.kcfgc) 7 | 8 | add_library(lshelper ${lshelper_LIB_SRCS}) 9 | 10 | include(GenerateExportHeader) 11 | generate_export_header(lshelper 12 | BASE_NAME liblshelper 13 | EXPORT_FILE_NAME liblshelper_export.h) 14 | 15 | target_link_libraries(lshelper 16 | Qt6::Core 17 | Qt6::CorePrivate 18 | Qt6::Gui 19 | Qt6::DBus 20 | 21 | KWin::kwin 22 | epoxy::epoxy 23 | GL 24 | 25 | KF6::ConfigCore 26 | KF6::ConfigGui 27 | KF6::CoreAddons 28 | KF6::ConfigWidgets 29 | KF6::GuiAddons 30 | KF6::WindowSystem 31 | ) 32 | 33 | set_target_properties(lshelper PROPERTIES 34 | VERSION ${PROJECT_VERSION} 35 | SOVERSION ${PROJECT_VERSION_MAJOR}) 36 | 37 | #install(TARGETS lshelper DESTINATION /usr/lib64/qt6/plugins/kwin/effects/plugins) 38 | install(TARGETS lshelper ${INSTALL_TARGETS_DEFAULT_ARGS} LIBRARY NAMELINK_SKIP) 39 | -------------------------------------------------------------------------------- /src/liblshelper/lshelper.cpp: -------------------------------------------------------------------------------- 1 | #include "lshelper.h" 2 | #include "lightlyshaders_config.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | Q_LOGGING_CATEGORY(LSHELPER, "liblshelper", QtWarningMsg) 10 | 11 | namespace KWin { 12 | 13 | LSHelper::LSHelper() : QObject() 14 | { 15 | for (int i = 0; i < NTex; ++i) 16 | { 17 | m_maskRegions[i] = 0; 18 | } 19 | } 20 | 21 | LSHelper::~LSHelper() 22 | { 23 | //delete mask regions 24 | for (int i = 0; i < NTex; ++i) 25 | { 26 | if (m_maskRegions[i]) 27 | delete m_maskRegions[i]; 28 | } 29 | 30 | m_managed.clear(); 31 | } 32 | 33 | void 34 | LSHelper::reconfigure() 35 | { 36 | LightlyShadersConfig::self()->load(); 37 | 38 | m_cornersType = LightlyShadersConfig::cornersType(); 39 | m_squircleRatio = LightlyShadersConfig::squircleRatio(); 40 | m_shadowOffset = LightlyShadersConfig::shadowOffset(); 41 | m_size = LightlyShadersConfig::roundness(); 42 | m_disabledForMaximized = LightlyShadersConfig::disabledForMaximized(); 43 | 44 | if(m_cornersType == SquircledCorners) { 45 | m_size = m_size * 0.5 * m_squircleRatio; 46 | } 47 | 48 | setMaskRegions(); 49 | } 50 | 51 | int 52 | LSHelper::roundness() 53 | { 54 | return m_size; 55 | } 56 | 57 | void 58 | LSHelper::setMaskRegions() 59 | { 60 | int size = m_size + m_shadowOffset; 61 | QImage img = genMaskImg(size, true, false); 62 | 63 | m_maskRegions[TopLeft] = createMaskRegion(img, size, TopLeft); 64 | m_maskRegions[TopRight] = createMaskRegion(img, size, TopRight); 65 | m_maskRegions[BottomRight] = createMaskRegion(img, size, BottomRight); 66 | m_maskRegions[BottomLeft] = createMaskRegion(img, size, BottomLeft); 67 | } 68 | 69 | QRegion * 70 | LSHelper::createMaskRegion(QImage img, int size, int corner) 71 | { 72 | QImage img_copy; 73 | 74 | switch(corner) { 75 | case TopLeft: 76 | img_copy = img.copy(0, 0, size, size); 77 | break; 78 | case TopRight: 79 | img_copy = img.copy(size, 0, size, size); 80 | break; 81 | case BottomRight: 82 | img_copy = img.copy(size, size, size, size); 83 | break; 84 | case BottomLeft: 85 | img_copy = img.copy(0, size, size, size); 86 | break; 87 | } 88 | 89 | img_copy = img_copy.createMaskFromColor(QColor(Qt::black).rgb(), Qt::MaskOutColor); 90 | QBitmap bitmap = QBitmap::fromImage(img_copy, Qt::DiffuseAlphaDither); 91 | 92 | return new QRegion(bitmap); 93 | } 94 | 95 | void 96 | LSHelper::roundBlurRegion(EffectWindow *w, QRegion *blur_region) 97 | { 98 | if(blur_region->isEmpty()) { 99 | return; 100 | } 101 | 102 | if(!m_managed.contains(w)) { 103 | return; 104 | } 105 | 106 | const QRectF geo(w->frameGeometry()); 107 | 108 | QRectF maximized_area = effects->clientArea(MaximizeArea, w); 109 | if (maximized_area == geo && m_disabledForMaximized) { 110 | return; 111 | } 112 | 113 | QRegion top_left = *m_maskRegions[TopLeft]; 114 | top_left.translate(0-m_shadowOffset+1, 0-m_shadowOffset+1); 115 | *blur_region = blur_region->subtracted(top_left); 116 | 117 | QRegion top_right = *m_maskRegions[TopRight]; 118 | top_right.translate(geo.width() - m_size-1, 0-m_shadowOffset+1); 119 | *blur_region = blur_region->subtracted(top_right); 120 | 121 | QRegion bottom_right = *m_maskRegions[BottomRight]; 122 | bottom_right.translate(geo.width() - m_size-1, geo.height()-m_size-1); 123 | *blur_region = blur_region->subtracted(bottom_right); 124 | 125 | QRegion bottom_left = *m_maskRegions[BottomLeft]; 126 | bottom_left.translate(0-m_shadowOffset+1, geo.height()-m_size-1); 127 | *blur_region = blur_region->subtracted(bottom_left); 128 | } 129 | 130 | QPainterPath 131 | LSHelper::superellipse(float size, int n, int translate) 132 | { 133 | float n2 = 2.0 / n; 134 | 135 | int steps = 360; 136 | 137 | float step = (2 * M_PI) / steps; 138 | 139 | QPainterPath path; 140 | path.moveTo(2*size, size); 141 | 142 | for (int i = 1; i < steps; ++i) 143 | { 144 | float t = i * step; 145 | 146 | float cosT = qCos(t); 147 | float sinT = qSin(t); 148 | 149 | float x = size + (qPow(qAbs(cosT), n2) * size * signum(cosT)); 150 | float y = size - (qPow(qAbs(sinT), n2) * size * signum(sinT)); 151 | 152 | path.lineTo(x, y); 153 | 154 | //qCWarning(LSHELPER) << "x: " << x << ", y: " << y << ", t: " << t << ", size: " << size << ", sinT: " << sinT << ", cosT: " << cosT << ", n2: " << n2; 155 | } 156 | path.lineTo(2*size, size); 157 | 158 | path.translate(translate,translate); 159 | 160 | return path; 161 | } 162 | 163 | QImage 164 | LSHelper::genMaskImg(int size, bool mask, bool outer_rect) 165 | { 166 | QImage img(size*2, size*2, QImage::Format_ARGB32_Premultiplied); 167 | img.fill(Qt::transparent); 168 | QPainter p(&img); 169 | QRect r(img.rect()); 170 | int offset_decremented; 171 | if(outer_rect) { 172 | offset_decremented = m_shadowOffset-1; 173 | } else { 174 | offset_decremented = m_shadowOffset; 175 | } 176 | 177 | if(mask) { 178 | p.fillRect(img.rect(), Qt::black); 179 | p.setCompositionMode(QPainter::CompositionMode_DestinationOut); 180 | p.setPen(Qt::NoPen); 181 | p.setBrush(Qt::black); 182 | p.setRenderHint(QPainter::Antialiasing); 183 | if (m_cornersType == SquircledCorners) { 184 | const QPainterPath squircle1 = superellipse((size-m_shadowOffset), m_squircleRatio, m_shadowOffset); 185 | p.drawPolygon(squircle1.toFillPolygon()); 186 | } else { 187 | p.drawEllipse(r.adjusted(m_shadowOffset,m_shadowOffset,-m_shadowOffset,-m_shadowOffset)); 188 | } 189 | } else { 190 | p.setPen(Qt::NoPen); 191 | p.setRenderHint(QPainter::Antialiasing); 192 | r.adjust(offset_decremented, offset_decremented, -offset_decremented, -offset_decremented); 193 | if(outer_rect) { 194 | p.setBrush(QColor(0, 0, 0, 255)); 195 | } else { 196 | p.setBrush(QColor(255, 255, 255, 255)); 197 | } 198 | if (m_cornersType == SquircledCorners) { 199 | const QPainterPath squircle2 = superellipse((size-offset_decremented), m_squircleRatio, offset_decremented); 200 | p.drawPolygon(squircle2.toFillPolygon()); 201 | } else { 202 | p.drawEllipse(r); 203 | } 204 | p.setCompositionMode(QPainter::CompositionMode_DestinationOut); 205 | p.setBrush(Qt::black); 206 | r.adjust(1, 1, -1, -1); 207 | if (m_cornersType == SquircledCorners) { 208 | const QPainterPath squircle3 = superellipse((size-(offset_decremented+1)), m_squircleRatio, (offset_decremented+1)); 209 | p.drawPolygon(squircle3.toFillPolygon()); 210 | } else { 211 | p.drawEllipse(r); 212 | } 213 | } 214 | p.end(); 215 | 216 | return img; 217 | } 218 | 219 | bool 220 | LSHelper::hasShadow(EffectWindow *w) 221 | { 222 | if(w->expandedGeometry().size() != w->frameGeometry().size()) 223 | return true; 224 | return false; 225 | } 226 | 227 | bool 228 | LSHelper::isManagedWindow(EffectWindow *w) 229 | { 230 | if (w->isDesktop() 231 | || w->isFullScreen() 232 | || w->isPopupMenu() 233 | || w->isTooltip() 234 | || w->isSpecialWindow() 235 | || w->isDropdownMenu() 236 | || w->isPopupWindow() 237 | || w->isLockScreen() 238 | || w->isSplash() 239 | || w->isOnScreenDisplay() 240 | || w->isUtility() 241 | || w->isDock() 242 | || w->isToolbar() 243 | || w->isMenu()) 244 | return false; 245 | 246 | //qCWarning(LSHELPER) << w->windowRole() << w->windowType() << w->windowClass(); 247 | if ( 248 | (!w->hasDecoration() 249 | && ( 250 | w->windowClass().contains("plasma", Qt::CaseInsensitive) 251 | || w->windowClass().contains("krunner", Qt::CaseInsensitive) 252 | || w->windowClass().contains("sddm", Qt::CaseInsensitive) 253 | || w->windowClass().contains("vmware-user", Qt::CaseInsensitive) 254 | || w->windowClass().contains("latte-dock", Qt::CaseInsensitive) 255 | || w->windowClass().contains("lattedock", Qt::CaseInsensitive) 256 | || w->windowClass().contains("plank", Qt::CaseInsensitive) 257 | || w->windowClass().contains("cairo-dock", Qt::CaseInsensitive) 258 | || w->windowClass().contains("albert", Qt::CaseInsensitive) 259 | || w->windowClass().contains("ulauncher", Qt::CaseInsensitive) 260 | || w->windowClass().contains("ksplash", Qt::CaseInsensitive) 261 | || w->windowClass().contains("ksmserver", Qt::CaseInsensitive) 262 | || (w->windowClass().contains("reaper", Qt::CaseInsensitive) && !hasShadow(w)) 263 | ) 264 | ) 265 | || w->windowClass().contains("xwaylandvideobridge", Qt::CaseInsensitive) 266 | 267 | ) return false; 268 | 269 | if(w->windowClass().contains("jetbrains", Qt::CaseInsensitive) && w->caption().contains(QRegularExpression ("win[0-9]+"))) 270 | return false; 271 | 272 | if (w->windowClass().contains("plasma", Qt::CaseInsensitive) && !w->isNormalWindow() && !w->isDialog() && !w->isModal()) 273 | return false; 274 | 275 | //qCWarning(LSHELPER) << w->windowClass() << w->windowClass().contains("xwaylandvideobridge", Qt::CaseInsensitive); 276 | return true; 277 | } 278 | 279 | void 280 | LSHelper::blurWindowAdded(EffectWindow *w) 281 | { 282 | if(isManagedWindow(w)) { 283 | m_managed.append(w); 284 | } 285 | } 286 | 287 | void 288 | LSHelper::blurWindowDeleted(EffectWindow *w) 289 | { 290 | if(m_managed.contains(w)) { 291 | m_managed.removeAll(w); 292 | } 293 | } 294 | 295 | } //namespace 296 | -------------------------------------------------------------------------------- /src/liblshelper/lshelper.h: -------------------------------------------------------------------------------- 1 | #include "liblshelper_export.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | template int signum(T val) { 9 | return (T(0) < val) - (val < T(0)); 10 | } 11 | 12 | namespace KWin { 13 | 14 | class LIBLSHELPER_EXPORT LSHelper: public QObject 15 | { 16 | Q_OBJECT 17 | 18 | public: 19 | LSHelper(); 20 | ~LSHelper(); 21 | 22 | void reconfigure(); 23 | QPainterPath superellipse(float size, int n, int translate); 24 | QImage genMaskImg(int size, bool mask, bool outer_rect); 25 | void roundBlurRegion(EffectWindow *w, QRegion *region); 26 | bool isManagedWindow(EffectWindow *w); 27 | void blurWindowAdded(EffectWindow *w); 28 | void blurWindowDeleted(EffectWindow *w); 29 | int roundness(); 30 | 31 | enum { RoundedCorners = 0, SquircledCorners }; 32 | enum { TopLeft = 0, TopRight, BottomRight, BottomLeft, NTex }; 33 | 34 | QRegion* m_maskRegions[NTex]; 35 | 36 | private: 37 | bool hasShadow(EffectWindow *w); 38 | void setMaskRegions(); 39 | QRegion* createMaskRegion(QImage img, int size, int corner); 40 | 41 | int m_size, m_cornersType, m_squircleRatio, m_shadowOffset; 42 | bool m_disabledForMaximized; 43 | QList m_managed; 44 | }; 45 | 46 | } //namespace -------------------------------------------------------------------------------- /src/lightlyshaders/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(kcm) 2 | 3 | set(LIGHTLYSHADERS kwin_effect_lightlyshaders) 4 | 5 | set(LIGHTLYSHADERS_SRCS 6 | lightlyshaders.h 7 | lightlyshaders.qrc 8 | lightlyshaders.cpp 9 | ) 10 | 11 | kconfig_add_kcfg_files(LIGHTLYSHADERS_SRCS lightlyshaders_config.kcfgc) 12 | 13 | add_library(${LIGHTLYSHADERS} MODULE ${LIGHTLYSHADERS_SRCS}) 14 | 15 | target_link_libraries(${LIGHTLYSHADERS} 16 | 17 | Qt6::Core 18 | Qt6::CorePrivate 19 | Qt6::Gui 20 | Qt6::DBus 21 | 22 | KWin::kwin 23 | epoxy::epoxy 24 | GL 25 | 26 | KF6::ConfigCore 27 | KF6::ConfigGui 28 | KF6::CoreAddons 29 | KF6::ConfigWidgets 30 | KF6::GuiAddons 31 | KF6::WindowSystem 32 | 33 | lshelper 34 | ) 35 | 36 | 37 | install(TARGETS ${LIGHTLYSHADERS} DESTINATION ${KDE_INSTALL_PLUGINDIR}/kwin/effects/plugins) 38 | -------------------------------------------------------------------------------- /src/lightlyshaders/kcm/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(kwin_lightlyshaders_config_SOURCES lightlyshaders_kcm.cpp) 2 | 3 | kconfig_add_kcfg_files(kwin_lightlyshaders_config_SOURCES ../lightlyshaders_config.kcfgc) 4 | 5 | ki18n_wrap_ui(kwin_lightlyshaders_config_SOURCES lightlyshaders_config.ui) 6 | 7 | qt_add_dbus_interface(kwin_lightlyshaders_config_SOURCES ${KWIN_EFFECTS_INTERFACE} kwineffects_interface) 8 | 9 | add_library(kwin_lightlyshaders_config MODULE ${kwin_lightlyshaders_config_SOURCES}) 10 | 11 | target_link_libraries(kwin_lightlyshaders_config 12 | KWin::kwin 13 | Qt6::Widgets 14 | Qt6::Core 15 | Qt6::CorePrivate 16 | Qt6::DBus 17 | Qt6::Gui 18 | KF6::ConfigCore 19 | KF6::CoreAddons 20 | KF6::ConfigWidgets 21 | KF6::ConfigGui 22 | KF6::GlobalAccel 23 | KF6::WindowSystem 24 | KF6::KCMUtils 25 | KF6::I18n 26 | 27 | lshelper 28 | ) 29 | 30 | install(TARGETS kwin_lightlyshaders_config DESTINATION ${KDE_INSTALL_PLUGINDIR}/kwin/effects/configs) 31 | 32 | -------------------------------------------------------------------------------- /src/lightlyshaders/kcm/lightlyshaders_config.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | LightlyShadersKCM 4 | 5 | 6 | 7 | 0 8 | 0 9 | 300 10 | 400 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | Corner size (radius): 25 | 26 | 27 | 28 | 29 | 30 | 31 | px 32 | 33 | 34 | 3 35 | 36 | 37 | 64 38 | 39 | 40 | 6 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | Shadow offset: 52 | 53 | 54 | 55 | 56 | 57 | 58 | px 59 | 60 | 61 | 2 62 | 63 | 64 | 6 65 | 66 | 67 | 2 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | Draw inner outline 78 | 79 | 80 | true 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | true 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 7 97 | 98 | 99 | 100 | 101 | Inner outline width: 102 | 103 | 104 | 105 | 106 | 107 | 108 | px 109 | 110 | 111 | 1 112 | 113 | 114 | 10 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | Draw outer outline 125 | 126 | 127 | true 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | true 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 7 144 | 145 | 146 | 147 | 148 | Outer outline width: 149 | 150 | 151 | 152 | 153 | 154 | 155 | px 156 | 157 | 158 | 1 159 | 160 | 161 | 10 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | Disable for maximized windows 172 | 173 | 174 | false 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | Rounded 183 | 184 | 185 | 186 | 187 | Squircled 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | Squircle ratio 196 | 197 | 198 | 199 | 200 | 201 | 202 | 2 203 | 204 | 205 | 12 206 | 207 | 208 | 1 209 | 210 | 211 | 5 212 | 213 | 214 | Qt::Horizontal 215 | 216 | 217 | QSlider::TicksBelow 218 | 219 | 220 | 1 221 | 222 | 223 | 224 | 225 | 226 | 227 | Qt::Vertical 228 | 229 | 230 | 231 | 20 232 | 40 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | -------------------------------------------------------------------------------- /src/lightlyshaders/kcm/lightlyshaders_kcm.cpp: -------------------------------------------------------------------------------- 1 | #include "../lightlyshaders.h" 2 | #include "lightlyshaders_kcm.h" 3 | #include "kwineffects_interface.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | namespace KWin 12 | { 13 | 14 | K_PLUGIN_CLASS(LightlyShadersKCM) 15 | 16 | LightlyShadersKCM::LightlyShadersKCM(QObject* parent, const KPluginMetaData &data) 17 | : KCModule(parent, data) 18 | { 19 | ui.setupUi(widget()); 20 | addConfig(LightlyShadersConfig::self(), widget()); 21 | 22 | if(ui.kcfg_CornersType->currentIndex() == LSHelper::SquircledCorners) { 23 | ui.kcfg_SquircleRatio->setEnabled(true); 24 | } else { 25 | ui.kcfg_SquircleRatio->setEnabled(false); 26 | } 27 | 28 | connect( ui.kcfg_CornersType, SIGNAL(currentIndexChanged(int)), SLOT(updateChanged()) ); 29 | 30 | } 31 | 32 | void 33 | LightlyShadersKCM::load() 34 | { 35 | KCModule::load(); 36 | LightlyShadersConfig::self()->load(); 37 | } 38 | 39 | void 40 | LightlyShadersKCM::save() 41 | { 42 | LightlyShadersConfig::self()->save(); 43 | KCModule::save(); 44 | 45 | OrgKdeKwinEffectsInterface interface(QStringLiteral("org.kde.KWin"), 46 | QStringLiteral("/Effects"), 47 | QDBusConnection::sessionBus()); 48 | interface.reconfigureEffect(QStringLiteral("kwin_effect_lightlyshaders")); 49 | interface.reconfigureEffect(QStringLiteral("lightlyshaders_blur")); 50 | } 51 | 52 | void 53 | LightlyShadersKCM::defaults() 54 | { 55 | KCModule::defaults(); 56 | LightlyShadersConfig::self()->setDefaults(); 57 | } 58 | 59 | void 60 | LightlyShadersKCM::updateChanged() 61 | { 62 | 63 | if(ui.kcfg_CornersType->currentIndex() == LSHelper::SquircledCorners) { 64 | ui.kcfg_SquircleRatio->setEnabled(true); 65 | } else { 66 | ui.kcfg_SquircleRatio->setEnabled(false); 67 | } 68 | 69 | } 70 | 71 | } // namespace KWin 72 | 73 | #include "lightlyshaders_kcm.moc" 74 | -------------------------------------------------------------------------------- /src/lightlyshaders/kcm/lightlyshaders_kcm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "lightlyshaders_config.h" 5 | #include "ui_lightlyshaders_config.h" 6 | 7 | namespace KWin 8 | { 9 | 10 | class LightlyShadersKCM : public KCModule 11 | { 12 | Q_OBJECT 13 | public: 14 | explicit LightlyShadersKCM(QObject* parent, const KPluginMetaData &data); 15 | public Q_SLOTS: 16 | void save() override; 17 | void load() override; 18 | void defaults() override; 19 | void updateChanged(); 20 | 21 | private: 22 | void setChanged(bool value); 23 | Ui::LightlyShadersKCM ui; 24 | }; 25 | 26 | } // namespace KWin -------------------------------------------------------------------------------- /src/lightlyshaders/lightlyshaders.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2015 Robert Metsäranta 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; see the file COPYING. if not, write to 16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 17 | * Boston, MA 02110-1301, USA. 18 | */ 19 | 20 | #include "lightlyshaders.h" 21 | #include "lightlyshaders_config.h" 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | Q_LOGGING_CATEGORY(LIGHTLYSHADERS, "kwin_effect_lightlyshaders", QtWarningMsg) 40 | 41 | static void ensureResources() 42 | { 43 | // Must initialize resources manually because the effect is a static lib. 44 | Q_INIT_RESOURCE(lightlyshaders); 45 | } 46 | 47 | namespace KWin { 48 | 49 | KWIN_EFFECT_FACTORY_SUPPORTED_ENABLED( LightlyShadersEffect, 50 | "lightlyshaders.json", 51 | return LightlyShadersEffect::supported();, 52 | return LightlyShadersEffect::enabledByDefault();) 53 | 54 | LightlyShadersEffect::LightlyShadersEffect() : OffscreenEffect() 55 | { 56 | ensureResources(); 57 | 58 | m_helper = new LSHelper(); 59 | reconfigure(ReconfigureAll); 60 | 61 | m_shader = std::unique_ptr(ShaderManager::instance()->generateShaderFromFile(ShaderTrait::MapTexture, QStringLiteral(""), QStringLiteral(":/effects/lightlyshaders/shaders/lightlyshaders.frag"))); 62 | 63 | if (!m_shader) { 64 | qCWarning(LIGHTLYSHADERS) << "Failed to load shader"; 65 | return; 66 | } 67 | 68 | if (m_shader->isValid()) 69 | { 70 | const auto stackingOrder = effects->stackingOrder(); 71 | for (EffectWindow *window : stackingOrder) { 72 | windowAdded(window); 73 | } 74 | 75 | connect(effects, &EffectsHandler::windowAdded, this, &LightlyShadersEffect::windowAdded); 76 | connect(effects, &EffectsHandler::windowDeleted, this, &LightlyShadersEffect::windowDeleted); 77 | 78 | qCWarning(LIGHTLYSHADERS) << "LightlyShaders loaded."; 79 | } 80 | else 81 | qCWarning(LIGHTLYSHADERS) << "LightlyShaders: no valid shaders found! LightlyShaders will not work."; 82 | } 83 | 84 | LightlyShadersEffect::~LightlyShadersEffect() 85 | { 86 | m_windows.clear(); 87 | } 88 | 89 | void 90 | LightlyShadersEffect::windowDeleted(EffectWindow *w) 91 | { 92 | m_windows.remove(w); 93 | } 94 | 95 | void 96 | LightlyShadersEffect::windowAdded(EffectWindow *w) 97 | { 98 | m_windows[w].isManaged = false; 99 | 100 | if(!m_helper->isManagedWindow(w)) 101 | return; 102 | 103 | m_windows[w].isManaged = true; 104 | m_windows[w].skipEffect = false; 105 | 106 | connect(w, &EffectWindow::windowMaximizedStateChanged, 107 | this, &LightlyShadersEffect::windowMaximizedStateChanged); 108 | connect(w, &EffectWindow::windowFullScreenChanged, 109 | this, &LightlyShadersEffect::windowFullScreenChanged); 110 | 111 | QRectF maximized_area = effects->clientArea(MaximizeArea, w); 112 | if (maximized_area == w->frameGeometry() && m_disabledForMaximized) 113 | m_windows[w].skipEffect = true; 114 | 115 | redirect(w); 116 | setShader(w, m_shader.get()); 117 | } 118 | 119 | void 120 | LightlyShadersEffect::windowFullScreenChanged(EffectWindow *w) 121 | { 122 | if(w->isFullScreen()) { 123 | m_windows[w].isManaged = false; 124 | } else { 125 | m_windows[w].isManaged = true; 126 | } 127 | } 128 | 129 | void 130 | LightlyShadersEffect::windowMaximizedStateChanged(EffectWindow *w, bool horizontal, bool vertical) 131 | { 132 | if (!m_disabledForMaximized) return; 133 | 134 | if ((horizontal == true) && (vertical == true)) { 135 | m_windows[w].skipEffect = true; 136 | } else { 137 | m_windows[w].skipEffect = false; 138 | } 139 | } 140 | 141 | void 142 | LightlyShadersEffect::setRoundness(const int r, Output *s) 143 | { 144 | m_size = r; 145 | m_screens[s].sizeScaled = float(r)*m_screens[s].scale; 146 | m_corner = QSize(m_size+(m_shadowOffset-1), m_size+(m_shadowOffset-1)); 147 | } 148 | 149 | void 150 | LightlyShadersEffect::reconfigure(ReconfigureFlags flags) 151 | { 152 | Q_UNUSED(flags) 153 | 154 | LightlyShadersConfig::self()->load(); 155 | 156 | m_innerOutlineWidth = LightlyShadersConfig::innerOutlineWidth(); 157 | m_outerOutlineWidth = LightlyShadersConfig::outerOutlineWidth(); 158 | m_innerOutline = LightlyShadersConfig::innerOutline(); 159 | m_outerOutline = LightlyShadersConfig::outerOutline(); 160 | m_innerOutlineColor = LightlyShadersConfig::innerOutlineColor(); 161 | m_outerOutlineColor = LightlyShadersConfig::outerOutlineColor(); 162 | m_disabledForMaximized = LightlyShadersConfig::disabledForMaximized(); 163 | m_shadowOffset = LightlyShadersConfig::shadowOffset(); 164 | m_squircleRatio = LightlyShadersConfig::squircleRatio(); 165 | m_cornersType = LightlyShadersConfig::cornersType(); 166 | 167 | m_helper->reconfigure(); 168 | m_roundness = m_helper->roundness(); 169 | 170 | if(m_shadowOffset>=m_roundness) { 171 | m_shadowOffset = m_roundness-1; 172 | } 173 | 174 | if(!m_innerOutline) { 175 | m_innerOutlineWidth = 0.0; 176 | } 177 | if(!m_outerOutline) { 178 | m_outerOutlineWidth = 0.0; 179 | } 180 | 181 | const auto screens = effects->screens(); 182 | for(Output *s : screens) 183 | { 184 | if (effects->waylandDisplay() == nullptr) { 185 | s = nullptr; 186 | } 187 | setRoundness(m_roundness, s); 188 | 189 | if (effects->waylandDisplay() == nullptr) { 190 | break; 191 | } 192 | } 193 | 194 | effects->addRepaintFull(); 195 | } 196 | 197 | void 198 | LightlyShadersEffect::paintScreen(const RenderTarget &renderTarget, const RenderViewport &viewport, int mask, const QRegion ®ion, Output *s) 199 | { 200 | bool set_roundness = false; 201 | 202 | if (!m_screens[s].configured) { 203 | m_screens[s].configured = true; 204 | set_roundness = true; 205 | } 206 | 207 | qreal scale = viewport.scale(); 208 | 209 | if(scale != m_screens[s].scale) { 210 | m_screens[s].scale = scale; 211 | set_roundness = true; 212 | } 213 | 214 | if(set_roundness) { 215 | setRoundness(m_roundness, s); 216 | m_helper->reconfigure(); 217 | } 218 | 219 | effects->paintScreen(renderTarget, viewport, mask, region, s); 220 | } 221 | 222 | void 223 | LightlyShadersEffect::prePaintWindow(EffectWindow *w, WindowPrePaintData &data, std::chrono::milliseconds time) 224 | { 225 | if (!isValidWindow(w) ) 226 | { 227 | effects->prePaintWindow(w, data, time); 228 | return; 229 | } 230 | 231 | Output *s = w->screen(); 232 | if (effects->waylandDisplay() == nullptr) { 233 | s = nullptr; 234 | } 235 | 236 | const QRectF geo(w->frameGeometry()); 237 | for (int corner = 0; corner < LSHelper::NTex; ++corner) 238 | { 239 | QRegion reg = QRegion(scale(m_helper->m_maskRegions[corner]->boundingRect(), m_screens[s].scale).toRect()); 240 | switch(corner) { 241 | case LSHelper::TopLeft: 242 | reg.translate(geo.x()-m_shadowOffset, geo.y()-m_shadowOffset); 243 | break; 244 | case LSHelper::TopRight: 245 | reg.translate(geo.x() + geo.width() - m_size, geo.y()-m_shadowOffset); 246 | break; 247 | case LSHelper::BottomRight: 248 | reg.translate(geo.x() + geo.width() - m_size-1, geo.y()+geo.height()-m_size-1); 249 | break; 250 | case LSHelper::BottomLeft: 251 | reg.translate(geo.x()-m_shadowOffset+1, geo.y()+geo.height()-m_size-1); 252 | break; 253 | default: 254 | break; 255 | } 256 | 257 | data.opaque -= reg; 258 | } 259 | 260 | effects->prePaintWindow(w, data, time); 261 | } 262 | 263 | bool 264 | LightlyShadersEffect::isValidWindow(EffectWindow *w) 265 | { 266 | if (!m_shader->isValid() 267 | || !m_windows[w].isManaged 268 | || m_windows[w].skipEffect 269 | ) 270 | { 271 | return false; 272 | } 273 | return true; 274 | } 275 | 276 | void 277 | LightlyShadersEffect::drawWindow(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow *w, int mask, const QRegion ®ion, WindowPaintData &data) 278 | { 279 | QRectF screen = viewport.renderRect().toRect(); 280 | 281 | if (!isValidWindow(w) || (!screen.intersects(w->frameGeometry()) && !(mask & PAINT_WINDOW_TRANSFORMED)) ) 282 | { 283 | effects->drawWindow(renderTarget, viewport, w, mask, region, data); 284 | return; 285 | } 286 | 287 | Output *s = w->screen(); 288 | if (effects->waylandDisplay() == nullptr) { 289 | s = nullptr; 290 | } 291 | 292 | QRectF geo(w->frameGeometry()); 293 | QRectF exp_geo(w->expandedGeometry()); 294 | QRectF contents_geo(w->contentsRect()); 295 | 296 | const QRectF geo_scaled = scale(geo, m_screens[s].scale); 297 | const QRectF exp_geo_scaled = scale(exp_geo, m_screens[s].scale); 298 | 299 | //Draw rounded corners with shadows 300 | const int frameSizeLocation = m_shader->uniformLocation("frame_size"); 301 | const int expandedSizeLocation = m_shader->uniformLocation("expanded_size"); 302 | const int shadowSizeLocation = m_shader->uniformLocation("shadow_size"); 303 | const int radiusLocation = m_shader->uniformLocation("radius"); 304 | const int shadowOffsetLocation = m_shader->uniformLocation("shadow_sample_offset"); 305 | const int innerOutlineColorLocation = m_shader->uniformLocation("inner_outline_color"); 306 | const int outerOutlineColorLocation = m_shader->uniformLocation("outer_outline_color"); 307 | const int innerOutlineWidthLocation = m_shader->uniformLocation("inner_outline_width"); 308 | const int outerOutlineWidthLocation = m_shader->uniformLocation("outer_outline_width"); 309 | const int drawInnerOutlineLocation = m_shader->uniformLocation("draw_inner_outline"); 310 | const int drawOuterOutlineLocation = m_shader->uniformLocation("draw_outer_outline"); 311 | const int squircleRatioLocation = m_shader->uniformLocation("squircle_ratio"); 312 | const int isSquircleLocation = m_shader->uniformLocation("is_squircle"); 313 | ShaderManager *sm = ShaderManager::instance(); 314 | sm->pushShader(m_shader.get()); 315 | 316 | //qCWarning(LIGHTLYSHADERS) << geo_scaled.width() << geo_scaled.height(); 317 | m_shader->setUniform(frameSizeLocation, QVector2D(geo_scaled.width(), geo_scaled.height())); 318 | m_shader->setUniform(expandedSizeLocation, QVector2D(exp_geo_scaled.width(), exp_geo_scaled.height())); 319 | m_shader->setUniform(shadowSizeLocation, QVector3D(geo_scaled.x() - exp_geo_scaled.x(), geo_scaled.y()-exp_geo_scaled.y(), exp_geo_scaled.height() - geo_scaled.height() - geo_scaled.y() + exp_geo_scaled.y() )); 320 | m_shader->setUniform(radiusLocation, m_screens[s].sizeScaled); 321 | m_shader->setUniform(shadowOffsetLocation, float(m_shadowOffset*m_screens[s].scale)); 322 | m_shader->setUniform(innerOutlineColorLocation, QVector4D(m_innerOutlineColor.red()/255.0,m_innerOutlineColor.green()/255.0,m_innerOutlineColor.blue()/255.0,m_innerOutlineColor.alpha()/255.0)); 323 | m_shader->setUniform(outerOutlineColorLocation, QVector4D(m_outerOutlineColor.red()/255.0,m_outerOutlineColor.green()/255.0,m_outerOutlineColor.blue()/255.0,m_outerOutlineColor.alpha()/255.0)); 324 | m_shader->setUniform(innerOutlineWidthLocation, float(m_innerOutlineWidth*m_screens[s].scale)); 325 | m_shader->setUniform(outerOutlineWidthLocation, float(m_outerOutlineWidth*m_screens[s].scale)); 326 | m_shader->setUniform(drawInnerOutlineLocation, m_innerOutline); 327 | m_shader->setUniform(drawOuterOutlineLocation, m_outerOutline); 328 | m_shader->setUniform(squircleRatioLocation, m_squircleRatio); 329 | m_shader->setUniform(isSquircleLocation, (m_cornersType == LSHelper::SquircledCorners)); 330 | 331 | glActiveTexture(GL_TEXTURE0); 332 | 333 | OffscreenEffect::drawWindow(renderTarget, viewport, w, mask, region, data); 334 | 335 | sm->popShader(); 336 | } 337 | 338 | QRectF 339 | LightlyShadersEffect::scale(const QRectF rect, qreal scaleFactor) 340 | { 341 | return QRectF( 342 | rect.x()*scaleFactor, 343 | rect.y()*scaleFactor, 344 | rect.width()*scaleFactor, 345 | rect.height()*scaleFactor 346 | ); 347 | } 348 | 349 | bool 350 | LightlyShadersEffect::enabledByDefault() 351 | { 352 | return supported(); 353 | } 354 | 355 | bool 356 | LightlyShadersEffect::supported() 357 | { 358 | return effects->openglContext() && effects->openglContext()->checkSupported() && effects->openglContext()->supportsBlits(); 359 | } 360 | 361 | } // namespace KWin 362 | 363 | #include "lightlyshaders.moc" 364 | -------------------------------------------------------------------------------- /src/lightlyshaders/lightlyshaders.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2015 Robert Metsäranta 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | * General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; see the file COPYING. if not, write to 16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 17 | * Boston, MA 02110-1301, USA. 18 | */ 19 | 20 | #ifndef LIGHTLYSHADERS_H 21 | #define LIGHTLYSHADERS_H 22 | 23 | #include 24 | #include 25 | 26 | #include "lshelper.h" 27 | 28 | namespace KWin { 29 | 30 | class GLTexture; 31 | 32 | class Q_DECL_EXPORT LightlyShadersEffect : public OffscreenEffect 33 | { 34 | Q_OBJECT 35 | public: 36 | LightlyShadersEffect(); 37 | ~LightlyShadersEffect(); 38 | 39 | static bool supported(); 40 | static bool enabledByDefault(); 41 | 42 | void setRoundness(const int r, Output *s); 43 | 44 | void reconfigure(ReconfigureFlags flags) override; 45 | void paintScreen(const RenderTarget &renderTarget, const RenderViewport &viewport, int mask, const QRegion ®ion, Output *s) override; 46 | void prePaintWindow(EffectWindow* w, WindowPrePaintData& data, std::chrono::milliseconds time) override; 47 | void drawWindow(const RenderTarget &renderTarget, const RenderViewport &viewport, EffectWindow* w, int mask, const QRegion& region, WindowPaintData& data) override; 48 | virtual int requestedEffectChainPosition() const override { return 99; } 49 | 50 | protected Q_SLOTS: 51 | void windowAdded(EffectWindow *window); 52 | void windowDeleted(EffectWindow *window); 53 | void windowMaximizedStateChanged(EffectWindow *window, bool horizontal, bool vertical); 54 | void windowFullScreenChanged(EffectWindow *window); 55 | 56 | private: 57 | enum { Top = 0, Bottom, NShad }; 58 | 59 | struct LSWindowStruct 60 | { 61 | bool skipEffect; 62 | bool isManaged; 63 | }; 64 | 65 | struct LSScreenStruct 66 | { 67 | bool configured=false; 68 | qreal scale=1.0; 69 | float sizeScaled; 70 | }; 71 | 72 | bool isValidWindow(EffectWindow *w); 73 | 74 | void fillRegion(const QRegion ®, const QColor &c); 75 | QRectF scale(const QRectF rect, qreal scaleFactor); 76 | 77 | LSHelper *m_helper; 78 | 79 | int m_size, m_innerOutlineWidth, m_outerOutlineWidth, m_roundness, m_shadowOffset, m_squircleRatio, m_cornersType; 80 | bool m_innerOutline, m_outerOutline, m_darkTheme, m_disabledForMaximized; 81 | QColor m_innerOutlineColor, m_outerOutlineColor; 82 | std::unique_ptr m_shader; 83 | QSize m_corner; 84 | 85 | std::unordered_map m_screens; 86 | QMap m_windows; 87 | }; 88 | 89 | } // namespace KWin 90 | 91 | #endif //LIGHTLYSHADERS_H 92 | -------------------------------------------------------------------------------- /src/lightlyshaders/lightlyshaders.json: -------------------------------------------------------------------------------- 1 | { 2 | "KPlugin": { 3 | "Authors": [ 4 | { 5 | "Email": "therealestrob@gmail.com", 6 | "Name": "Rob" 7 | }, 8 | { 9 | "Name": "Luwx" 10 | }, 11 | { 12 | "Name": "Parhom" 13 | } 14 | ], 15 | "Category": "Appearance", 16 | "Description": "An effect that shapes/rounds corners of windows.", 17 | "EnabledByDefault": true, 18 | "License": "GPL", 19 | "Name": "LightlyShaders" 20 | }, 21 | "X-KDE-ConfigModule": "kwin_lightlyshaders_config" 22 | } -------------------------------------------------------------------------------- /src/lightlyshaders/lightlyshaders.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | shaders/lightlyshaders.frag 4 | shaders/lightlyshaders_core.frag 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/lightlyshaders/lightlyshaders_config.kcfg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 5 10 | 11 | 12 | 13 | true 14 | 15 | 16 | 255, 255, 255, 75 17 | 18 | 19 | 1 20 | 21 | 22 | 23 | true 24 | 25 | 26 | 0, 0, 0, 75 27 | 28 | 29 | 1 30 | 31 | 32 | 33 | false 34 | 35 | 36 | 37 | 38 | 39 | 40 | RoundedCorners 41 | 42 | 43 | 5 44 | 45 | 46 | 2 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/lightlyshaders/lightlyshaders_config.kcfgc: -------------------------------------------------------------------------------- 1 | File=lightlyshaders_config.kcfg 2 | ClassName=LightlyShadersConfig 3 | NameSpace=KWin 4 | Singleton=true 5 | Mutators=true -------------------------------------------------------------------------------- /src/lightlyshaders/shaders/lightlyshaders.frag: -------------------------------------------------------------------------------- 1 | #version 110 2 | 3 | uniform sampler2D sampler; 4 | 5 | uniform vec2 expanded_size; 6 | uniform vec2 frame_size; 7 | uniform vec3 shadow_size; 8 | uniform float radius; 9 | uniform float shadow_sample_offset; 10 | uniform bool draw_inner_outline; 11 | uniform bool draw_outer_outline; 12 | uniform float inner_outline_width; 13 | uniform float outer_outline_width; 14 | uniform vec4 inner_outline_color; 15 | uniform vec4 outer_outline_color; 16 | uniform int squircle_ratio; 17 | uniform bool is_squircle; 18 | 19 | uniform mat4 modelViewProjectionMatrix; 20 | 21 | uniform vec4 modulation; 22 | uniform float saturation; 23 | 24 | varying vec2 texcoord0; 25 | 26 | #include "colormanagement.glsl" 27 | 28 | //Used code from https://github.com/yilozt/rounded-window-corners project 29 | float squircleBounds(vec2 p, vec2 center, float clip_radius) 30 | { 31 | vec2 delta = abs(p - center); 32 | float f_squircle_ratio = float(squircle_ratio); 33 | 34 | float pow_dx = pow(delta.x, f_squircle_ratio); 35 | float pow_dy = pow(delta.y, f_squircle_ratio); 36 | 37 | float dist = pow(pow_dx + pow_dy, 1.0 / f_squircle_ratio); 38 | 39 | return clamp(clip_radius - dist + 0.5, 0.0, 1.0); 40 | } 41 | 42 | //Used code from https://github.com/yilozt/rounded-window-corners project 43 | float circleBounds(vec2 p, vec2 center, float clip_radius) 44 | { 45 | vec2 delta = p - vec2(center.x, center.y); 46 | float dist_squared = dot(delta, delta); 47 | 48 | float outer_radius = clip_radius + 0.5; 49 | if(dist_squared >= (outer_radius * outer_radius)) 50 | return 0.0; 51 | 52 | float inner_radius = clip_radius - 0.5; 53 | if(dist_squared <= (inner_radius * inner_radius)) 54 | return 1.0; 55 | 56 | return outer_radius - sqrt(dist_squared); 57 | } 58 | 59 | vec4 shapeWindow(vec4 tex, vec2 p, vec2 center, float clip_radius) 60 | { 61 | float alpha; 62 | if(is_squircle) { 63 | alpha = squircleBounds(p, center, clip_radius); 64 | } else { 65 | alpha = circleBounds(p, center, clip_radius); 66 | } 67 | return vec4(tex.rgb*alpha, min(alpha, tex.a)); 68 | } 69 | 70 | vec4 shapeShadowWindow(vec2 start, vec4 tex, vec2 p, vec2 center, float clip_radius) 71 | { 72 | vec2 ShadowHorCoord = vec2(texcoord0.x, start.y); 73 | vec2 ShadowVerCoord = vec2(start.x, texcoord0.y); 74 | 75 | vec4 texShadowHorCur = texture2D(sampler, ShadowHorCoord); 76 | vec4 texShadowVerCur = texture2D(sampler, ShadowVerCoord); 77 | vec4 texShadow0 = texture2D(sampler, start); 78 | 79 | vec4 texShadow = texShadowHorCur + (texShadowVerCur - texShadow0); 80 | 81 | float alpha; 82 | if(is_squircle) { 83 | alpha = squircleBounds(p, center, clip_radius); 84 | } else { 85 | alpha = circleBounds(p, center, clip_radius); 86 | } 87 | 88 | if(alpha == 0.0) { 89 | return texShadow; 90 | } else if(alpha < 1.0) { 91 | return mix(vec4(tex.rgb*alpha, min(alpha, tex.a)), texShadow, 1.0-alpha); 92 | } else { 93 | return tex; 94 | } 95 | } 96 | 97 | vec4 cornerOutline(vec4 outColor, bool inner, vec2 coord0, float radius, vec2 center, float outline_width, bool invert) 98 | { 99 | vec4 outline_color; 100 | float radius_delta_inner; 101 | float radius_delta_outer; 102 | 103 | if(inner) { 104 | outline_color = inner_outline_color; 105 | radius_delta_outer = 0.0; 106 | radius_delta_inner = -outline_width; 107 | 108 | if(invert) { 109 | radius_delta_inner = 0.0; 110 | radius_delta_outer = outline_width; 111 | } 112 | } else { 113 | outline_color = outer_outline_color; 114 | radius_delta_inner = 0.0; 115 | radius_delta_outer = outline_width; 116 | 117 | if(invert) { 118 | radius_delta_outer = 0.0; 119 | radius_delta_inner = -outline_width; 120 | } 121 | } 122 | 123 | float outline_alpha; 124 | float outline_alpha_inner; 125 | float outline_alpha_outer; 126 | 127 | if(is_squircle) { 128 | outline_alpha_inner = squircleBounds(coord0, vec2(center.x, center.y), radius + radius_delta_inner); 129 | outline_alpha_outer = squircleBounds(coord0, vec2(center.x, center.y), radius + radius_delta_outer); 130 | } else { 131 | outline_alpha_inner = circleBounds(coord0, vec2(center.x, center.y), radius + radius_delta_inner); 132 | outline_alpha_outer = circleBounds(coord0, vec2(center.x, center.y), radius + radius_delta_outer); 133 | } 134 | outline_alpha = 1.0 - clamp(abs(outline_alpha_outer - outline_alpha_inner), 0.0, 1.0); 135 | outColor = mix(outColor, vec4(outline_color.rgb,1.0), (1.0-outline_alpha) * outline_color.a); 136 | return outColor; 137 | } 138 | 139 | void main() 140 | { 141 | vec4 tex = texture2D(sampler, texcoord0); 142 | vec2 coord0; 143 | vec4 outColor; 144 | float start_x; 145 | float start_y; 146 | 147 | float f_shadow_sample_offset = float(shadow_sample_offset); 148 | float f_radius = float(radius); 149 | 150 | //Window without shadow 151 | if(expanded_size == frame_size) { 152 | coord0 = vec2(texcoord0.x*frame_size.x, texcoord0.y*frame_size.y); 153 | //Left side 154 | if (coord0.x < f_radius) { 155 | //Bottom left corner 156 | if (coord0.y < f_radius) { 157 | outColor = shapeWindow(tex, coord0, vec2(f_radius, f_radius), f_radius); 158 | 159 | //Inner outline 160 | if(draw_inner_outline) { 161 | outColor = cornerOutline(outColor, true, coord0, f_radius-outer_outline_width, vec2(f_radius, f_radius), inner_outline_width, false); 162 | } 163 | //Outer outline 164 | if(draw_outer_outline) { 165 | outColor = cornerOutline(outColor, false, coord0, f_radius, vec2(f_radius, f_radius), outer_outline_width, true); 166 | } 167 | //Top left corner 168 | } else if (coord0.y > frame_size.y - f_radius) { 169 | outColor = shapeWindow(tex, coord0, vec2(f_radius, frame_size.y - f_radius), f_radius); 170 | 171 | //Inner outline 172 | if(draw_inner_outline) { 173 | outColor = cornerOutline(outColor, true, coord0, f_radius-outer_outline_width, vec2(f_radius, frame_size.y - f_radius), inner_outline_width, false); 174 | } 175 | //Outer outline 176 | if(draw_outer_outline) { 177 | outColor = cornerOutline(outColor, false, coord0, f_radius, vec2(f_radius, frame_size.y - f_radius), outer_outline_width, true); 178 | } 179 | //Center 180 | } else { 181 | outColor = tex; 182 | 183 | //Outline 184 | if(coord0.y > f_radius && coord0.y < frame_size.y - f_radius) { 185 | //Inner outline 186 | if(draw_inner_outline) { 187 | if(coord0.x >= outer_outline_width && coord0.x <= outer_outline_width+inner_outline_width) { 188 | outColor = mix(outColor, vec4(inner_outline_color.rgb,1.0), inner_outline_color.a); 189 | } 190 | } 191 | //Outer outline 192 | if(draw_outer_outline) { 193 | if( 194 | (coord0.x >= 0.0 && coord0.x <= outer_outline_width) 195 | ) { 196 | outColor = mix(outColor, vec4(outer_outline_color.rgb,1.0), outer_outline_color.a); 197 | } 198 | } 199 | } 200 | } 201 | //Right side 202 | } else if (coord0.x > frame_size.x - f_radius) { 203 | //Bottom right corner 204 | if (coord0.y < f_radius) { 205 | outColor = shapeWindow(tex, coord0, vec2(frame_size.x - f_radius, f_radius), f_radius); 206 | 207 | //Inner outline 208 | if(draw_inner_outline) { 209 | outColor = cornerOutline(outColor, true, coord0, f_radius-outer_outline_width, vec2(frame_size.x - f_radius, f_radius), inner_outline_width, false); 210 | } 211 | //Outer outline 212 | if(draw_outer_outline) { 213 | outColor = cornerOutline(outColor, false, coord0, f_radius, vec2(frame_size.x - f_radius, f_radius), outer_outline_width, true); 214 | } 215 | //Top right corner 216 | } else if (coord0.y > frame_size.y - f_radius) { 217 | outColor = shapeWindow(tex, coord0, vec2(frame_size.x - f_radius, frame_size.y - f_radius), f_radius); 218 | 219 | //Inner outline 220 | if(draw_inner_outline) { 221 | outColor = cornerOutline(outColor, true, coord0, f_radius-outer_outline_width, vec2(frame_size.x - f_radius, frame_size.y - f_radius), inner_outline_width, false); 222 | } 223 | //Outer outline 224 | if(draw_outer_outline) { 225 | outColor = cornerOutline(outColor, false, coord0, f_radius, vec2(frame_size.x - f_radius, frame_size.y - f_radius), outer_outline_width, true); 226 | } 227 | //Center 228 | } else { 229 | outColor = tex; 230 | 231 | //Outline 232 | if(coord0.y > f_radius && coord0.y < frame_size.y - f_radius) { 233 | //Inner outline 234 | if(draw_inner_outline) { 235 | if(coord0.x >= frame_size.x - inner_outline_width - outer_outline_width && coord0.x <= frame_size.x - outer_outline_width) { 236 | outColor = mix(outColor, vec4(inner_outline_color.rgb,1.0), inner_outline_color.a); 237 | } 238 | } 239 | //Outer outline 240 | if(draw_outer_outline) { 241 | if( 242 | (coord0.x >= frame_size.x - outer_outline_width && coord0.x <= frame_size.x ) 243 | ) { 244 | outColor = mix(outColor, vec4(outer_outline_color.rgb,1.0), outer_outline_color.a); 245 | } 246 | } 247 | } 248 | } 249 | //Center 250 | } else { 251 | outColor = tex; 252 | 253 | //Outline 254 | if(coord0.x > f_radius && coord0.x < frame_size.x - f_radius) { 255 | //Inner outline 256 | if(draw_inner_outline) { 257 | if( 258 | (coord0.y >= frame_size.y - inner_outline_width - outer_outline_width && coord0.y <= frame_size.y - outer_outline_width ) 259 | || (coord0.y >= outer_outline_width && coord0.y <= outer_outline_width + inner_outline_width) 260 | ) { 261 | outColor = mix(outColor, vec4(inner_outline_color.rgb,1.0), inner_outline_color.a); 262 | } 263 | } 264 | //Outer outline 265 | if(draw_outer_outline) { 266 | if( 267 | (coord0.y >= frame_size.y - outer_outline_width && coord0.y <= frame_size.y) 268 | || (coord0.y >= 0.0 && coord0.y <= outer_outline_width) 269 | ) { 270 | outColor = mix(outColor, vec4(outer_outline_color.rgb,1.0), outer_outline_color.a); 271 | } 272 | } 273 | } 274 | } 275 | //Window with shadow 276 | } else if(expanded_size != frame_size) { 277 | coord0 = vec2(texcoord0.x*expanded_size.x, texcoord0.y*expanded_size.y); 278 | //Left side 279 | if (coord0.x > shadow_size.x - max(f_shadow_sample_offset, outer_outline_width) && coord0.x < f_radius + shadow_size.x) { 280 | //Top left corner 281 | if (coord0.y > frame_size.y + shadow_size.z - f_radius && coord0.y < frame_size.y + shadow_size.z + max(f_shadow_sample_offset, outer_outline_width)) { 282 | start_x = (shadow_size.x - f_shadow_sample_offset)/expanded_size.x; 283 | start_y = (shadow_size.z + frame_size.y + f_shadow_sample_offset)/expanded_size.y; 284 | 285 | outColor = shapeShadowWindow(vec2(start_x, start_y), tex, coord0, vec2(f_radius + shadow_size.x, frame_size.y + shadow_size.z - f_radius), f_radius); 286 | 287 | //Inner outline 288 | if(draw_inner_outline) { 289 | outColor = cornerOutline(outColor, true, coord0, f_radius, vec2(f_radius + shadow_size.x, frame_size.y + shadow_size.z - f_radius), inner_outline_width, false); 290 | } 291 | //Outer outline 292 | if(draw_outer_outline) { 293 | outColor = cornerOutline(outColor, false, coord0, f_radius, vec2(f_radius + shadow_size.x, frame_size.y + shadow_size.z - f_radius), outer_outline_width, false); 294 | } 295 | //Bottom left corner 296 | } else if (coord0.y > shadow_size.z - max(f_shadow_sample_offset, outer_outline_width) && coord0.y < f_radius + shadow_size.z) { 297 | start_x = (shadow_size.x - f_shadow_sample_offset)/expanded_size.x; 298 | start_y = (shadow_size.z - f_shadow_sample_offset)/expanded_size.y; 299 | 300 | outColor = shapeShadowWindow(vec2(start_x, start_y), tex, coord0, vec2(f_radius + shadow_size.x, shadow_size.z + f_radius), f_radius); 301 | 302 | //Inner outline 303 | if(draw_inner_outline) { 304 | outColor = cornerOutline(outColor, true, coord0, f_radius, vec2(f_radius + shadow_size.x, shadow_size.z + f_radius), inner_outline_width, false); 305 | } 306 | //Outer outline 307 | if(draw_outer_outline) { 308 | outColor = cornerOutline(outColor, false, coord0, f_radius, vec2(f_radius + shadow_size.x, shadow_size.z + f_radius), outer_outline_width, false); 309 | } 310 | //Center 311 | } else { 312 | outColor = tex; 313 | 314 | //Outline and shadow 315 | if(coord0.y > f_radius + shadow_size.z && coord0.y < shadow_size.z + frame_size.y - f_radius) { 316 | //Left shadow padding 317 | if(coord0.x > shadow_size.x - f_shadow_sample_offset && coord0.x <= shadow_size.x) { 318 | start_x = (shadow_size.x - f_shadow_sample_offset)/expanded_size.x; 319 | vec4 texShadowEdge = texture2D(sampler, vec2(start_x, texcoord0.y)); 320 | outColor = texShadowEdge; 321 | } 322 | 323 | //Inner outline 324 | if(draw_inner_outline) { 325 | if(coord0.x >= shadow_size.x && coord0.x <= shadow_size.x+inner_outline_width) { 326 | outColor = mix(outColor, vec4(inner_outline_color.rgb,1.0), inner_outline_color.a); 327 | } 328 | } 329 | //Outer outline 330 | if(draw_outer_outline) { 331 | if( 332 | (coord0.x >= shadow_size.x - outer_outline_width && coord0.x <= shadow_size.x) 333 | ) { 334 | outColor = mix(outColor, vec4(outer_outline_color.rgb,1.0), outer_outline_color.a); 335 | } 336 | } 337 | } 338 | } 339 | //Right side 340 | } else if (coord0.x > shadow_size.x + frame_size.x - f_radius && coord0.x < shadow_size.x + frame_size.x + max(f_shadow_sample_offset, outer_outline_width)) { 341 | //Top right corner 342 | if (coord0.y > frame_size.y + shadow_size.z - f_radius && coord0.y < frame_size.y + shadow_size.z + max(f_shadow_sample_offset, outer_outline_width)) { 343 | start_x = (shadow_size.x + frame_size.x + f_shadow_sample_offset)/expanded_size.x; 344 | start_y = (shadow_size.z + frame_size.y + f_shadow_sample_offset)/expanded_size.y; 345 | 346 | outColor = shapeShadowWindow(vec2(start_x, start_y), tex, coord0, vec2(shadow_size.x + frame_size.x - f_radius, frame_size.y + shadow_size.z - f_radius), f_radius); 347 | 348 | //Inner outline 349 | if(draw_inner_outline) { 350 | outColor = cornerOutline(outColor, true, coord0, f_radius, vec2(shadow_size.x + frame_size.x - f_radius, frame_size.y + shadow_size.z - f_radius), inner_outline_width, false); 351 | } 352 | //Outer outline 353 | if(draw_outer_outline) { 354 | outColor = cornerOutline(outColor, false, coord0, f_radius, vec2(shadow_size.x + frame_size.x - f_radius, frame_size.y + shadow_size.z - f_radius), outer_outline_width, false); 355 | } 356 | //Bottom right corner 357 | } else if (coord0.y > shadow_size.z - max(f_shadow_sample_offset, outer_outline_width) && coord0.y < f_radius + shadow_size.z) { 358 | start_x = (shadow_size.x + frame_size.x + f_shadow_sample_offset)/expanded_size.x; 359 | start_y = (shadow_size.z - f_shadow_sample_offset)/expanded_size.y; 360 | 361 | outColor = shapeShadowWindow(vec2(start_x, start_y), tex, coord0, vec2(shadow_size.x + frame_size.x - f_radius, shadow_size.z + f_radius), f_radius); 362 | 363 | //Inner outline 364 | if(draw_inner_outline) { 365 | outColor = cornerOutline(outColor, true, coord0, f_radius, vec2(shadow_size.x + frame_size.x - f_radius, shadow_size.z + f_radius), inner_outline_width, false); 366 | } 367 | //Outer outline 368 | if(draw_outer_outline) { 369 | outColor = cornerOutline(outColor, false, coord0, f_radius, vec2(shadow_size.x + frame_size.x - f_radius, shadow_size.z + f_radius), outer_outline_width, false); 370 | } 371 | //Center 372 | } else { 373 | outColor = tex; 374 | 375 | //Outline and shadow 376 | if(coord0.y > f_radius + shadow_size.z && coord0.y < shadow_size.z + frame_size.y - f_radius) { 377 | //Right shadow padding 378 | if(coord0.x >= shadow_size.x + frame_size.x && coord0.x < shadow_size.x + frame_size.x + f_shadow_sample_offset) { 379 | start_x = (shadow_size.x + frame_size.x + f_shadow_sample_offset)/expanded_size.x; 380 | vec4 texShadowEdge = texture2D(sampler, vec2(start_x, texcoord0.y)); 381 | outColor = texShadowEdge; 382 | } 383 | 384 | //Inner outline 385 | if(draw_inner_outline) { 386 | if(coord0.x >= frame_size.x + shadow_size.x - inner_outline_width && coord0.x <= frame_size.x + shadow_size.x) { 387 | outColor = mix(outColor, vec4(inner_outline_color.rgb,1.0), inner_outline_color.a); 388 | } 389 | } 390 | //Outer outline 391 | if(draw_outer_outline) { 392 | if( 393 | (coord0.x >= frame_size.x + shadow_size.x && coord0.x <= frame_size.x + shadow_size.x + outer_outline_width) 394 | ) { 395 | outColor = mix(outColor, vec4(outer_outline_color.rgb,1.0), outer_outline_color.a); 396 | } 397 | } 398 | } 399 | } 400 | //Center 401 | } else { 402 | outColor = tex; 403 | 404 | //Outline and shadow 405 | if(coord0.x > f_radius + shadow_size.x && coord0.x < shadow_size.x + frame_size.x - f_radius) { 406 | //Top shadow padding 407 | if(coord0.y >= frame_size.y + shadow_size.z && coord0.y < frame_size.y + shadow_size.z + f_shadow_sample_offset) { 408 | start_y = (shadow_size.z + frame_size.y + f_shadow_sample_offset)/expanded_size.y; 409 | vec4 texShadowEdge = texture2D(sampler, vec2(texcoord0.x, start_y)); 410 | outColor = texShadowEdge; 411 | //Bottom shadow padding 412 | } else if(coord0.y <= shadow_size.z && coord0.y > shadow_size.z - f_shadow_sample_offset) { 413 | start_y = (shadow_size.z - f_shadow_sample_offset)/expanded_size.y; 414 | vec4 texShadowEdge = texture2D(sampler, vec2(texcoord0.x, start_y)); 415 | outColor = texShadowEdge; 416 | } 417 | 418 | //Inner outline 419 | if(draw_inner_outline) { 420 | if( 421 | (coord0.y >= frame_size.y + shadow_size.z - inner_outline_width && coord0.y <= frame_size.y + shadow_size.z) 422 | || (coord0.y >= shadow_size.z && coord0.y <= shadow_size.z + inner_outline_width) 423 | ) { 424 | outColor = mix(outColor, vec4(inner_outline_color.rgb,1.0), inner_outline_color.a); 425 | } 426 | } 427 | //Outer outline 428 | if(draw_outer_outline) { 429 | if( 430 | (coord0.y >= frame_size.y + shadow_size.z && coord0.y <= frame_size.y + shadow_size.z + outer_outline_width) 431 | || (coord0.y >= shadow_size.z - outer_outline_width && coord0.y <= shadow_size.z) 432 | ) { 433 | outColor = mix(outColor, vec4(outer_outline_color.rgb,1.0), outer_outline_color.a); 434 | } 435 | } 436 | } 437 | } 438 | } 439 | 440 | outColor = sourceEncodingToNitsInDestinationColorspace(outColor); 441 | 442 | //Support opacity 443 | if (saturation != 1.0) { 444 | vec3 desaturated = outColor.rgb * vec3( 0.30, 0.59, 0.11 ); 445 | desaturated = vec3( dot( desaturated, outColor.rgb )); 446 | outColor.rgb = outColor.rgb * vec3( saturation ) + desaturated * vec3( 1.0 - saturation ); 447 | } 448 | outColor *= modulation; 449 | 450 | //Output result 451 | gl_FragColor = nitsToDestinationEncoding(outColor); 452 | //gl_FragColor = vec4(tex.r, tex.g, 1.0, tex.a); 453 | } 454 | -------------------------------------------------------------------------------- /src/lightlyshaders/shaders/lightlyshaders_core.frag: -------------------------------------------------------------------------------- 1 | #version 140 2 | 3 | uniform sampler2D sampler; 4 | 5 | uniform vec2 expanded_size; 6 | uniform vec2 frame_size; 7 | uniform vec3 shadow_size; 8 | uniform float radius; 9 | uniform float shadow_sample_offset; 10 | uniform bool draw_inner_outline; 11 | uniform bool draw_outer_outline; 12 | uniform float inner_outline_width; 13 | uniform float outer_outline_width; 14 | uniform vec4 inner_outline_color; 15 | uniform vec4 outer_outline_color; 16 | uniform int squircle_ratio; 17 | uniform bool is_squircle; 18 | 19 | uniform mat4 modelViewProjectionMatrix; 20 | 21 | uniform vec4 modulation; 22 | uniform float saturation; 23 | 24 | in vec2 texcoord0; 25 | out vec4 fragColor; 26 | 27 | #include "colormanagement.glsl" 28 | 29 | //Used code from https://github.com/yilozt/rounded-window-corners project 30 | float squircleBounds(vec2 p, vec2 center, float clip_radius) 31 | { 32 | vec2 delta = abs(p - center); 33 | 34 | float pow_dx = pow(delta.x, squircle_ratio); 35 | float pow_dy = pow(delta.y, squircle_ratio); 36 | 37 | float dist = pow(pow_dx + pow_dy, 1.0 / squircle_ratio); 38 | 39 | return clamp(clip_radius - dist + 0.5, 0.0, 1.0); 40 | } 41 | 42 | //Used code from https://github.com/yilozt/rounded-window-corners project 43 | float circleBounds(vec2 p, vec2 center, float clip_radius) 44 | { 45 | vec2 delta = p - vec2(center.x, center.y); 46 | float dist_squared = dot(delta, delta); 47 | 48 | float outer_radius = clip_radius + 0.5; 49 | if(dist_squared >= (outer_radius * outer_radius)) 50 | return 0.0; 51 | 52 | float inner_radius = clip_radius - 0.5; 53 | if(dist_squared <= (inner_radius * inner_radius)) 54 | return 1.0; 55 | 56 | return outer_radius - sqrt(dist_squared); 57 | } 58 | 59 | vec4 shapeWindow(vec4 tex, vec2 p, vec2 center, float clip_radius) 60 | { 61 | float alpha; 62 | if(is_squircle) { 63 | alpha = squircleBounds(p, center, clip_radius); 64 | } else { 65 | alpha = circleBounds(p, center, clip_radius); 66 | } 67 | return vec4(tex.rgb*alpha, min(alpha, tex.a)); 68 | } 69 | 70 | vec4 shapeShadowWindow(vec2 start, vec4 tex, vec2 p, vec2 center, float clip_radius) 71 | { 72 | vec2 ShadowHorCoord = vec2(texcoord0.x, start.y); 73 | vec2 ShadowVerCoord = vec2(start.x, texcoord0.y); 74 | 75 | vec4 texShadowHorCur = texture2D(sampler, ShadowHorCoord); 76 | vec4 texShadowVerCur = texture2D(sampler, ShadowVerCoord); 77 | vec4 texShadow0 = texture2D(sampler, start); 78 | 79 | vec4 texShadow = texShadowHorCur + (texShadowVerCur - texShadow0); 80 | 81 | float alpha; 82 | if(is_squircle) { 83 | alpha = squircleBounds(p, center, clip_radius); 84 | } else { 85 | alpha = circleBounds(p, center, clip_radius); 86 | } 87 | 88 | if(alpha == 0.0) { 89 | return texShadow; 90 | } else if(alpha < 1.0) { 91 | return mix(vec4(tex.rgb*alpha, min(alpha, tex.a)), texShadow, 1.0-alpha); 92 | } else { 93 | return tex; 94 | } 95 | } 96 | 97 | vec4 cornerOutline(vec4 outColor, bool inner, vec2 coord0, float radius, vec2 center, float outline_width, bool invert) 98 | { 99 | vec4 outline_color; 100 | float radius_delta_inner; 101 | float radius_delta_outer; 102 | 103 | if(inner) { 104 | outline_color = inner_outline_color; 105 | radius_delta_outer = 0; 106 | radius_delta_inner = -outline_width; 107 | 108 | if(invert) { 109 | radius_delta_inner = 0; 110 | radius_delta_outer = outline_width; 111 | } 112 | } else { 113 | outline_color = outer_outline_color; 114 | radius_delta_inner = 0; 115 | radius_delta_outer = outline_width; 116 | 117 | if(invert) { 118 | radius_delta_outer = 0; 119 | radius_delta_inner = -outline_width; 120 | } 121 | } 122 | 123 | float outline_alpha; 124 | float outline_alpha_inner; 125 | float outline_alpha_outer; 126 | 127 | if(is_squircle) { 128 | outline_alpha_inner = squircleBounds(coord0, vec2(center.x, center.y), radius + radius_delta_inner); 129 | outline_alpha_outer = squircleBounds(coord0, vec2(center.x, center.y), radius + radius_delta_outer); 130 | } else { 131 | outline_alpha_inner = circleBounds(coord0, vec2(center.x, center.y), radius + radius_delta_inner); 132 | outline_alpha_outer = circleBounds(coord0, vec2(center.x, center.y), radius + radius_delta_outer); 133 | } 134 | outline_alpha = 1.0 - clamp(abs(outline_alpha_outer - outline_alpha_inner), 0.0, 1.0); 135 | outColor = mix(outColor, vec4(outline_color.rgb,1.0), (1.0-outline_alpha) * outline_color.a); 136 | return outColor; 137 | } 138 | 139 | void main(void) 140 | { 141 | vec4 tex = texture2D(sampler, texcoord0); 142 | vec2 coord0; 143 | vec4 outColor; 144 | float start_x; 145 | float start_y; 146 | 147 | //Window without shadow 148 | if(expanded_size == frame_size) { 149 | coord0 = vec2(texcoord0.x*frame_size.x, texcoord0.y*frame_size.y); 150 | //Left side 151 | if (coord0.x < radius) { 152 | //Bottom left corner 153 | if (coord0.y < radius) { 154 | outColor = shapeWindow(tex, coord0, vec2(radius, radius), radius); 155 | 156 | //Inner outline 157 | if(draw_inner_outline) { 158 | outColor = cornerOutline(outColor, true, coord0, radius-outer_outline_width, vec2(radius, radius), inner_outline_width, false); 159 | } 160 | //Outer outline 161 | if(draw_outer_outline) { 162 | outColor = cornerOutline(outColor, false, coord0, radius, vec2(radius, radius), outer_outline_width, true); 163 | } 164 | //Top left corner 165 | } else if (coord0.y > frame_size.y - radius) { 166 | outColor = shapeWindow(tex, coord0, vec2(radius, frame_size.y - radius), radius); 167 | 168 | //Inner outline 169 | if(draw_inner_outline) { 170 | outColor = cornerOutline(outColor, true, coord0, radius-outer_outline_width, vec2(radius, frame_size.y - radius), inner_outline_width, false); 171 | } 172 | //Outer outline 173 | if(draw_outer_outline) { 174 | outColor = cornerOutline(outColor, false, coord0, radius, vec2(radius, frame_size.y - radius), outer_outline_width, true); 175 | } 176 | //Center 177 | } else { 178 | outColor = tex; 179 | 180 | //Outline 181 | if(coord0.y > radius && coord0.y < frame_size.y - radius) { 182 | //Inner outline 183 | if(draw_inner_outline) { 184 | if(coord0.x >= outer_outline_width && coord0.x <= outer_outline_width+inner_outline_width) { 185 | outColor = mix(outColor, vec4(inner_outline_color.rgb,1.0), inner_outline_color.a); 186 | } 187 | } 188 | //Outer outline 189 | if(draw_outer_outline) { 190 | if( 191 | (coord0.x >= 0.0 && coord0.x <= outer_outline_width) 192 | ) { 193 | outColor = mix(outColor, vec4(outer_outline_color.rgb,1.0), outer_outline_color.a); 194 | } 195 | } 196 | } 197 | } 198 | //Right side 199 | } else if (coord0.x > frame_size.x - radius) { 200 | //Bottom right corner 201 | if (coord0.y < radius) { 202 | outColor = shapeWindow(tex, coord0, vec2(frame_size.x - radius, radius), radius); 203 | 204 | //Inner outline 205 | if(draw_inner_outline) { 206 | outColor = cornerOutline(outColor, true, coord0, radius-outer_outline_width, vec2(frame_size.x - radius, radius), inner_outline_width, false); 207 | } 208 | //Outer outline 209 | if(draw_outer_outline) { 210 | outColor = cornerOutline(outColor, false, coord0, radius, vec2(frame_size.x - radius, radius), outer_outline_width, true); 211 | } 212 | //Top right corner 213 | } else if (coord0.y > frame_size.y - radius) { 214 | outColor = shapeWindow(tex, coord0, vec2(frame_size.x - radius, frame_size.y - radius), radius); 215 | 216 | //Inner outline 217 | if(draw_inner_outline) { 218 | outColor = cornerOutline(outColor, true, coord0, radius-outer_outline_width, vec2(frame_size.x - radius, frame_size.y - radius), inner_outline_width, false); 219 | } 220 | //Outer outline 221 | if(draw_outer_outline) { 222 | outColor = cornerOutline(outColor, false, coord0, radius, vec2(frame_size.x - radius, frame_size.y - radius), outer_outline_width, true); 223 | } 224 | //Center 225 | } else { 226 | outColor = tex; 227 | 228 | //Outline 229 | if(coord0.y > radius && coord0.y < frame_size.y - radius) { 230 | //Inner outline 231 | if(draw_inner_outline) { 232 | if(coord0.x >= frame_size.x - inner_outline_width - outer_outline_width && coord0.x <= frame_size.x - outer_outline_width) { 233 | outColor = mix(outColor, vec4(inner_outline_color.rgb,1.0), inner_outline_color.a); 234 | } 235 | } 236 | //Outer outline 237 | if(draw_outer_outline) { 238 | if( 239 | (coord0.x >= frame_size.x - outer_outline_width && coord0.x <= frame_size.x ) 240 | ) { 241 | outColor = mix(outColor, vec4(outer_outline_color.rgb,1.0), outer_outline_color.a); 242 | } 243 | } 244 | } 245 | } 246 | //Center 247 | } else { 248 | outColor = tex; 249 | 250 | //Outline 251 | if(coord0.x > radius && coord0.x < frame_size.x - radius) { 252 | //Inner outline 253 | if(draw_inner_outline) { 254 | if( 255 | (coord0.y >= frame_size.y - inner_outline_width - outer_outline_width && coord0.y <= frame_size.y - outer_outline_width ) 256 | || (coord0.y >= outer_outline_width && coord0.y <= outer_outline_width + inner_outline_width) 257 | ) { 258 | outColor = mix(outColor, vec4(inner_outline_color.rgb,1.0), inner_outline_color.a); 259 | } 260 | } 261 | //Outer outline 262 | if(draw_outer_outline) { 263 | if( 264 | (coord0.y >= frame_size.y - outer_outline_width && coord0.y <= frame_size.y) 265 | || (coord0.y >= 0.0 && coord0.y <= outer_outline_width) 266 | ) { 267 | outColor = mix(outColor, vec4(outer_outline_color.rgb,1.0), outer_outline_color.a); 268 | } 269 | } 270 | } 271 | } 272 | //Window with shadow 273 | } else if(expanded_size != frame_size) { 274 | coord0 = vec2(texcoord0.x*expanded_size.x, texcoord0.y*expanded_size.y); 275 | //Left side 276 | if (coord0.x > shadow_size.x - max(shadow_sample_offset, outer_outline_width) && coord0.x < radius + shadow_size.x) { 277 | //Top left corner 278 | if (coord0.y > frame_size.y + shadow_size.z - radius && coord0.y < frame_size.y + shadow_size.z + max(shadow_sample_offset, outer_outline_width)) { 279 | start_x = (shadow_size.x - shadow_sample_offset)/expanded_size.x; 280 | start_y = (shadow_size.z + frame_size.y + shadow_sample_offset)/expanded_size.y; 281 | 282 | outColor = shapeShadowWindow(vec2(start_x, start_y), tex, coord0, vec2(radius + shadow_size.x, frame_size.y + shadow_size.z - radius), radius); 283 | 284 | //Inner outline 285 | if(draw_inner_outline) { 286 | outColor = cornerOutline(outColor, true, coord0, radius, vec2(radius + shadow_size.x, frame_size.y + shadow_size.z - radius), inner_outline_width, false); 287 | } 288 | //Outer outline 289 | if(draw_outer_outline) { 290 | outColor = cornerOutline(outColor, false, coord0, radius, vec2(radius + shadow_size.x, frame_size.y + shadow_size.z - radius), outer_outline_width, false); 291 | } 292 | //Bottom left corner 293 | } else if (coord0.y > shadow_size.z - max(shadow_sample_offset, outer_outline_width) && coord0.y < radius + shadow_size.z) { 294 | start_x = (shadow_size.x - shadow_sample_offset)/expanded_size.x; 295 | start_y = (shadow_size.z - shadow_sample_offset)/expanded_size.y; 296 | 297 | outColor = shapeShadowWindow(vec2(start_x, start_y), tex, coord0, vec2(radius + shadow_size.x, shadow_size.z + radius), radius); 298 | 299 | //Inner outline 300 | if(draw_inner_outline) { 301 | outColor = cornerOutline(outColor, true, coord0, radius, vec2(radius + shadow_size.x, shadow_size.z + radius), inner_outline_width, false); 302 | } 303 | //Outer outline 304 | if(draw_outer_outline) { 305 | outColor = cornerOutline(outColor, false, coord0, radius, vec2(radius + shadow_size.x, shadow_size.z + radius), outer_outline_width, false); 306 | } 307 | //Center 308 | } else { 309 | outColor = tex; 310 | 311 | //Outline and shadow 312 | if(coord0.y > radius + shadow_size.z && coord0.y < shadow_size.z + frame_size.y - radius) { 313 | //Left shadow padding 314 | if(coord0.x > shadow_size.x - shadow_sample_offset && coord0.x <= shadow_size.x) { 315 | start_x = (shadow_size.x - shadow_sample_offset)/expanded_size.x; 316 | vec4 texShadowEdge = texture2D(sampler, vec2(start_x, texcoord0.y)); 317 | outColor = texShadowEdge; 318 | } 319 | 320 | //Inner outline 321 | if(draw_inner_outline) { 322 | if(coord0.x >= shadow_size.x && coord0.x <= shadow_size.x+inner_outline_width) { 323 | outColor = mix(outColor, vec4(inner_outline_color.rgb,1.0), inner_outline_color.a); 324 | } 325 | } 326 | //Outer outline 327 | if(draw_outer_outline) { 328 | if( 329 | (coord0.x >= shadow_size.x - outer_outline_width && coord0.x <= shadow_size.x) 330 | ) { 331 | outColor = mix(outColor, vec4(outer_outline_color.rgb,1.0), outer_outline_color.a); 332 | } 333 | } 334 | } 335 | } 336 | //Right side 337 | } else if (coord0.x > shadow_size.x + frame_size.x - radius && coord0.x < shadow_size.x + frame_size.x + max(shadow_sample_offset, outer_outline_width)) { 338 | //Top right corner 339 | if (coord0.y > frame_size.y + shadow_size.z - radius && coord0.y < frame_size.y + shadow_size.z + max(shadow_sample_offset, outer_outline_width)) { 340 | start_x = (shadow_size.x + frame_size.x + shadow_sample_offset)/expanded_size.x; 341 | start_y = (shadow_size.z + frame_size.y + shadow_sample_offset)/expanded_size.y; 342 | 343 | outColor = shapeShadowWindow(vec2(start_x, start_y), tex, coord0, vec2(shadow_size.x + frame_size.x - radius, frame_size.y + shadow_size.z - radius), radius); 344 | 345 | //Inner outline 346 | if(draw_inner_outline) { 347 | outColor = cornerOutline(outColor, true, coord0, radius, vec2(shadow_size.x + frame_size.x - radius, frame_size.y + shadow_size.z - radius), inner_outline_width, false); 348 | } 349 | //Outer outline 350 | if(draw_outer_outline) { 351 | outColor = cornerOutline(outColor, false, coord0, radius, vec2(shadow_size.x + frame_size.x - radius, frame_size.y + shadow_size.z - radius), outer_outline_width, false); 352 | } 353 | //Bottom right corner 354 | } else if (coord0.y > shadow_size.z - max(shadow_sample_offset, outer_outline_width) && coord0.y < radius + shadow_size.z) { 355 | start_x = (shadow_size.x + frame_size.x + shadow_sample_offset)/expanded_size.x; 356 | start_y = (shadow_size.z - shadow_sample_offset)/expanded_size.y; 357 | 358 | outColor = shapeShadowWindow(vec2(start_x, start_y), tex, coord0, vec2(shadow_size.x + frame_size.x - radius, shadow_size.z + radius), radius); 359 | 360 | //Inner outline 361 | if(draw_inner_outline) { 362 | outColor = cornerOutline(outColor, true, coord0, radius, vec2(shadow_size.x + frame_size.x - radius, shadow_size.z + radius), inner_outline_width, false); 363 | } 364 | //Outer outline 365 | if(draw_outer_outline) { 366 | outColor = cornerOutline(outColor, false, coord0, radius, vec2(shadow_size.x + frame_size.x - radius, shadow_size.z + radius), outer_outline_width, false); 367 | } 368 | //Center 369 | } else { 370 | outColor = tex; 371 | 372 | //Outline and shadow 373 | if(coord0.y > radius + shadow_size.z && coord0.y < shadow_size.z + frame_size.y - radius) { 374 | //Right shadow padding 375 | if(coord0.x >= shadow_size.x + frame_size.x && coord0.x < shadow_size.x + frame_size.x + shadow_sample_offset) { 376 | start_x = (shadow_size.x + frame_size.x + shadow_sample_offset)/expanded_size.x; 377 | vec4 texShadowEdge = texture2D(sampler, vec2(start_x, texcoord0.y)); 378 | outColor = texShadowEdge; 379 | } 380 | 381 | //Inner outline 382 | if(draw_inner_outline) { 383 | if(coord0.x >= frame_size.x + shadow_size.x - inner_outline_width && coord0.x <= frame_size.x + shadow_size.x) { 384 | outColor = mix(outColor, vec4(inner_outline_color.rgb,1.0), inner_outline_color.a); 385 | } 386 | } 387 | //Outer outline 388 | if(draw_outer_outline) { 389 | if( 390 | (coord0.x >= frame_size.x + shadow_size.x && coord0.x <= frame_size.x + shadow_size.x + outer_outline_width) 391 | ) { 392 | outColor = mix(outColor, vec4(outer_outline_color.rgb,1.0), outer_outline_color.a); 393 | } 394 | } 395 | } 396 | } 397 | //Center 398 | } else { 399 | outColor = tex; 400 | 401 | //Outline and shadow 402 | if(coord0.x > radius + shadow_size.x && coord0.x < shadow_size.x + frame_size.x - radius) { 403 | //Top shadow padding 404 | if(coord0.y >= frame_size.y + shadow_size.z && coord0.y < frame_size.y + shadow_size.z + shadow_sample_offset) { 405 | start_y = (shadow_size.z + frame_size.y + shadow_sample_offset)/expanded_size.y; 406 | vec4 texShadowEdge = texture2D(sampler, vec2(texcoord0.x, start_y)); 407 | outColor = texShadowEdge; 408 | //Bottom shadow padding 409 | } else if(coord0.y <= shadow_size.z && coord0.y > shadow_size.z - shadow_sample_offset) { 410 | start_y = (shadow_size.z - shadow_sample_offset)/expanded_size.y; 411 | vec4 texShadowEdge = texture2D(sampler, vec2(texcoord0.x, start_y)); 412 | outColor = texShadowEdge; 413 | } 414 | 415 | //Inner outline 416 | if(draw_inner_outline) { 417 | if( 418 | (coord0.y >= frame_size.y + shadow_size.z - inner_outline_width && coord0.y <= frame_size.y + shadow_size.z) 419 | || (coord0.y >= shadow_size.z && coord0.y <= shadow_size.z + inner_outline_width) 420 | ) { 421 | outColor = mix(outColor, vec4(inner_outline_color.rgb,1.0), inner_outline_color.a); 422 | } 423 | } 424 | //Outer outline 425 | if(draw_outer_outline) { 426 | if( 427 | (coord0.y >= frame_size.y + shadow_size.z && coord0.y <= frame_size.y + shadow_size.z + outer_outline_width) 428 | || (coord0.y >= shadow_size.z - outer_outline_width && coord0.y <= shadow_size.z) 429 | ) { 430 | outColor = mix(outColor, vec4(outer_outline_color.rgb,1.0), outer_outline_color.a); 431 | } 432 | } 433 | } 434 | } 435 | } 436 | 437 | outColor = sourceEncodingToNitsInDestinationColorspace(outColor); 438 | 439 | //Support opacity 440 | if (saturation != 1.0) { 441 | vec3 desaturated = outColor.rgb * vec3( 0.30, 0.59, 0.11 ); 442 | desaturated = vec3( dot( desaturated, outColor.rgb )); 443 | outColor.rgb = outColor.rgb * vec3( saturation ) + desaturated * vec3( 1.0 - saturation ); 444 | } 445 | outColor *= modulation; 446 | 447 | //Output result 448 | fragColor = nitsToDestinationEncoding(outColor); 449 | //fragColor = vec4(tex.r, tex.g, 1.0, tex.a); 450 | } 451 | --------------------------------------------------------------------------------