Changeset View
Standalone View
effects/blur/blur.cpp
Show All 21 Lines | |||||
22 | #include "blur.h" | 22 | #include "blur.h" | ||
23 | #include "effects.h" | 23 | #include "effects.h" | ||
24 | #include "blurshader.h" | 24 | #include "blurshader.h" | ||
25 | // KConfigSkeleton | 25 | // KConfigSkeleton | ||
26 | #include "blurconfig.h" | 26 | #include "blurconfig.h" | ||
27 | 27 | | |||
28 | #include <QMatrix4x4> | 28 | #include <QMatrix4x4> | ||
29 | #include <QLinkedList> | 29 | #include <QLinkedList> | ||
30 | #include <QScreen> // for QGuiApplication | ||||
31 | #include <QTime> | ||||
30 | #include <cmath> // for ceil() | 32 | #include <cmath> // for ceil() | ||
31 | 33 | | |||
32 | #include <KWayland/Server/surface_interface.h> | 34 | #include <KWayland/Server/surface_interface.h> | ||
33 | #include <KWayland/Server/blur_interface.h> | 35 | #include <KWayland/Server/blur_interface.h> | ||
34 | #include <KWayland/Server/shadow_interface.h> | 36 | #include <KWayland/Server/shadow_interface.h> | ||
35 | #include <KWayland/Server/display.h> | 37 | #include <KWayland/Server/display.h> | ||
38 | #include <KSharedConfig> | ||||
39 | #include <KConfigGroup> | ||||
36 | 40 | | |||
37 | namespace KWin | 41 | namespace KWin | ||
38 | { | 42 | { | ||
39 | 43 | | |||
40 | static const QByteArray s_blurAtomName = QByteArrayLiteral("_KDE_NET_WM_BLUR_BEHIND_REGION"); | 44 | static const QByteArray s_blurAtomName = QByteArrayLiteral("_KDE_NET_WM_BLUR_BEHIND_REGION"); | ||
41 | 45 | | |||
42 | BlurEffect::BlurEffect() | 46 | BlurEffect::BlurEffect() | ||
43 | { | 47 | { | ||
▲ Show 20 Lines • Show All 94 Lines • ▼ Show 20 Line(s) | 120 | { | |||
138 | m_renderTextures.last().setWrapMode(GL_CLAMP_TO_EDGE); | 142 | m_renderTextures.last().setWrapMode(GL_CLAMP_TO_EDGE); | ||
139 | 143 | | |||
140 | m_renderTargets.append(new GLRenderTarget(m_renderTextures.last())); | 144 | m_renderTargets.append(new GLRenderTarget(m_renderTextures.last())); | ||
141 | 145 | | |||
142 | m_renderTargetsValid = renderTargetsValid(); | 146 | m_renderTargetsValid = renderTargetsValid(); | ||
143 | 147 | | |||
144 | // Prepare the stack for the rendering | 148 | // Prepare the stack for the rendering | ||
145 | m_renderTargetStack.clear(); | 149 | m_renderTargetStack.clear(); | ||
146 | m_renderTargets.reserve(m_downSampleIterations * 2 - 1); | 150 | m_renderTargetStack.reserve(m_downSampleIterations * 2); | ||
fredrik: Should this be m_renderTargetStack? | |||||
anemeth: Whoops, you're right. Fixed. | |||||
147 | 151 | | |||
148 | // Upsample | 152 | // Upsample | ||
149 | for (int i = 1; i < m_downSampleIterations; i++) { | 153 | for (int i = 1; i < m_downSampleIterations; i++) { | ||
150 | m_renderTargetStack.push(m_renderTargets[i]); | 154 | m_renderTargetStack.push(m_renderTargets[i]); | ||
151 | } | 155 | } | ||
152 | 156 | | |||
153 | // Downsample | 157 | // Downsample | ||
154 | for (int i = m_downSampleIterations; i > 0; i--) { | 158 | for (int i = m_downSampleIterations; i > 0; i--) { | ||
155 | m_renderTargetStack.push(m_renderTargets[i]); | 159 | m_renderTargetStack.push(m_renderTargets[i]); | ||
156 | } | 160 | } | ||
157 | 161 | | |||
158 | // Copysample | 162 | // Copysample | ||
159 | m_renderTargetStack.push(m_renderTargets[0]); | 163 | m_renderTargetStack.push(m_renderTargets[0]); | ||
164 | | ||||
165 | // Generate the noise helper texture | ||||
166 | generateNoiseTexture(); | ||||
160 | } | 167 | } | ||
161 | 168 | | |||
162 | void BlurEffect::initBlurStrengthValues() | 169 | void BlurEffect::initBlurStrengthValues() | ||
163 | { | 170 | { | ||
164 | // This function creates an array of blur strength values that are evenly distributed | 171 | // This function creates an array of blur strength values that are evenly distributed | ||
165 | 172 | | |||
166 | // The range of the slider on the blur settings UI | 173 | // The range of the slider on the blur settings UI | ||
167 | int numOfBlurSteps = 15; | 174 | int numOfBlurSteps = 15; | ||
▲ Show 20 Lines • Show All 54 Lines • ▼ Show 20 Line(s) | 228 | { | |||
222 | Q_UNUSED(flags) | 229 | Q_UNUSED(flags) | ||
223 | 230 | | |||
224 | BlurConfig::self()->read(); | 231 | BlurConfig::self()->read(); | ||
225 | 232 | | |||
226 | int blurStrength = BlurConfig::blurStrength() - 1; | 233 | int blurStrength = BlurConfig::blurStrength() - 1; | ||
227 | m_downSampleIterations = blurStrengthValues[blurStrength].iteration; | 234 | m_downSampleIterations = blurStrengthValues[blurStrength].iteration; | ||
228 | m_offset = blurStrengthValues[blurStrength].offset; | 235 | m_offset = blurStrengthValues[blurStrength].offset; | ||
229 | m_expandSize = blurOffsets[m_downSampleIterations - 1].expandSize; | 236 | m_expandSize = blurOffsets[m_downSampleIterations - 1].expandSize; | ||
237 | m_noiseStrength = BlurConfig::noiseStrength(); | ||||
238 | | ||||
239 | m_scalingFactor = QGuiApplication::primaryScreen()->logicalDotsPerInch() / 96.0; | ||||
zzag: What about displays with different scaling factors? | |||||
Is displays with different scaling factors supported in Plasma? anemeth: Is displays with different scaling factors supported in Plasma?
Last time I checked it wasn't… | |||||
Yeah, you're right, scaling factor is shared among all displays, which sucks ofc. Mark it as done. zzag: Yeah, you're right, scaling factor is shared among all displays, which sucks ofc. Mark it as… | |||||
230 | 240 | | |||
231 | updateTexture(); | 241 | updateTexture(); | ||
232 | 242 | | |||
233 | if (!m_shader || !m_shader->isValid()) { | 243 | if (!m_shader || !m_shader->isValid()) { | ||
234 | effects->removeSupportProperty(s_blurAtomName, this); | 244 | effects->removeSupportProperty(s_blurAtomName, this); | ||
235 | delete m_blurManager; | 245 | delete m_blurManager; | ||
236 | m_blurManager = nullptr; | 246 | m_blurManager = nullptr; | ||
237 | } | 247 | } | ||
▲ Show 20 Lines • Show All 307 Lines • ▼ Show 20 Line(s) | 537 | if (shouldBlur(w, mask, data)) { | |||
545 | 555 | | |||
546 | //Only translated, not scaled | 556 | //Only translated, not scaled | ||
547 | } else if (translated) { | 557 | } else if (translated) { | ||
548 | shape = shape.translated(data.xTranslation(), data.yTranslation()); | 558 | shape = shape.translated(data.xTranslation(), data.yTranslation()); | ||
549 | shape = shape & region; | 559 | shape = shape & region; | ||
550 | } | 560 | } | ||
551 | 561 | | |||
552 | if (!shape.isEmpty()) { | 562 | if (!shape.isEmpty()) { | ||
553 | doBlur(shape, screen, data.opacity(), data.screenProjectionMatrix(), w->isDock()); | 563 | doBlur(shape, screen, data.opacity(), data.screenProjectionMatrix(), w->isDock(), w->geometry()); | ||
554 | } | 564 | } | ||
555 | } | 565 | } | ||
556 | 566 | | |||
557 | // Draw the window over the blurred area | 567 | // Draw the window over the blurred area | ||
558 | effects->drawWindow(w, mask, region, data); | 568 | effects->drawWindow(w, mask, region, data); | ||
559 | } | 569 | } | ||
560 | 570 | | |||
561 | void BlurEffect::paintEffectFrame(EffectFrame *frame, QRegion region, double opacity, double frameOpacity) | 571 | void BlurEffect::paintEffectFrame(EffectFrame *frame, QRegion region, double opacity, double frameOpacity) | ||
562 | { | 572 | { | ||
563 | const QRect screen = effects->virtualScreenGeometry(); | 573 | const QRect screen = effects->virtualScreenGeometry(); | ||
564 | bool valid = m_renderTargetsValid && m_shader && m_shader->isValid(); | 574 | bool valid = m_renderTargetsValid && m_shader && m_shader->isValid(); | ||
565 | 575 | | |||
566 | QRegion shape = frame->geometry().adjusted(-borderSize, -borderSize, borderSize, borderSize) & screen; | 576 | QRegion shape = frame->geometry().adjusted(-borderSize, -borderSize, borderSize, borderSize) & screen; | ||
567 | 577 | | |||
568 | if (valid && !shape.isEmpty() && region.intersects(shape.boundingRect()) && frame->style() != EffectFrameNone) { | 578 | if (valid && !shape.isEmpty() && region.intersects(shape.boundingRect()) && frame->style() != EffectFrameNone) { | ||
569 | doBlur(shape, screen, opacity * frameOpacity, frame->screenProjectionMatrix(), false); | 579 | doBlur(shape, screen, opacity * frameOpacity, frame->screenProjectionMatrix(), false, frame->geometry()); | ||
570 | } | 580 | } | ||
571 | effects->paintEffectFrame(frame, region, opacity, frameOpacity); | 581 | effects->paintEffectFrame(frame, region, opacity, frameOpacity); | ||
572 | } | 582 | } | ||
573 | 583 | | |||
574 | void BlurEffect::doBlur(const QRegion& shape, const QRect& screen, const float opacity, const QMatrix4x4 &screenProjection, bool isDock) | 584 | void BlurEffect::generateNoiseTexture() | ||
585 | { | ||||
586 | if (m_noiseStrength == 0) { | ||||
587 | return; | ||||
588 | } | ||||
589 | | ||||
590 | // Init randomness based on time | ||||
Using a screen-sized texture for this seems excessive. I suggest creating a 32x32 or 64x64 texture, and setting the wrap mode to GL_REPEAT. I also suspect that the pixels are too small on a 4K monitor for the effect to be noticeable. It would probably be better to use a scalable noise pattern, such as Perlin or simplex noise: https://en.wikipedia.org/wiki/Perlin_noise It might also be faster to generate the noise value for each pixel in the shader, based on the fragcoord. fredrik: Using a screen-sized texture for this seems excessive. I suggest creating a 32x32 or 64x64… | |||||
591 | qsrand((uint)QTime::currentTime().msec()); | ||||
592 | | ||||
593 | QImage noiseImage(QSize(256, 256), QImage::Format_Grayscale8); | ||||
594 | | ||||
With the image being 8-bit, it would make more sense to manipulate the data directly, i.e.: uint8_t *pixels = (uint8_t *) noiseImage.scanLine(y); for (int x = 0; x < noiseImage.width(); x++) { pixels[x] = qrand() % m_noiseStrength; } fredrik: With the image being 8-bit, it would make more sense to manipulate the data directly, i.e. | |||||
595 | for (int y = 0; y < noiseImage.height(); y++) { | ||||
I suggest using QImage::Format_Grayscale8 or Alpha8 when the channels have the same value. fredrik: I suggest using QImage::Format_Grayscale8 or Alpha8 when the channels have the same value. | |||||
596 | uint8_t *noiseImageLine = (uint8_t *) noiseImage.scanLine(y); | ||||
597 | | ||||
598 | for (int x = 0; x < noiseImage.width(); x++) { | ||||
599 | noiseImageLine[x] = qrand() % m_noiseStrength + (128 - m_noiseStrength / 2); | ||||
600 | } | ||||
601 | } | ||||
602 | | ||||
603 | // The noise texture looks distorted when not scaled with integer | ||||
604 | noiseImage = noiseImage.scaled(noiseImage.size() * m_scalingFactor); | ||||
605 | | ||||
Does scaling the texture coordinates not work? You know I always worry about memory usage :) fredrik: Does scaling the texture coordinates not work?
You know I always worry about memory usage :) | |||||
anemeth: {F5708140}
According to this formula the 256x256 grayscale texture would take 64KB and the 2x… | |||||
I could not get it working with scaling the coordinates without distortion or glitches. anemeth: I could not get it working with scaling the coordinates without distortion or glitches.
This is… | |||||
fredrik: Alright, fine :) | |||||
606 | m_noiseTexture = GLTexture(noiseImage); | ||||
607 | m_noiseTexture.setFilter(GL_NEAREST); | ||||
608 | m_noiseTexture.setWrapMode(GL_REPEAT); | ||||
609 | } | ||||
610 | | ||||
611 | void BlurEffect::doBlur(const QRegion& shape, const QRect& screen, const float opacity, const QMatrix4x4 &screenProjection, bool isDock, QRect windowRect) | ||||
575 | { | 612 | { | ||
576 | QRegion expandedBlurRegion = expand(shape) & expand(screen); | 613 | QRegion expandedBlurRegion = expand(shape) & expand(screen); | ||
577 | 614 | | |||
578 | // Upload geometry for the down and upsample iterations | 615 | // Upload geometry for the down and upsample iterations | ||
579 | GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); | 616 | GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); | ||
580 | uploadGeometry(vbo, expandedBlurRegion, shape); | 617 | uploadGeometry(vbo, expandedBlurRegion, shape); | ||
581 | vbo->bindArrays(); | 618 | vbo->bindArrays(); | ||
582 | 619 | | |||
Show All 40 Lines | |||||
623 | #else // sigmoid shape, above y = x for x > 0.5, below y = x for x < 0.5 | 660 | #else // sigmoid shape, above y = x for x > 0.5, below y = x for x < 0.5 | ||
624 | float o = 2.0f*opacity - 1.0f; | 661 | float o = 2.0f*opacity - 1.0f; | ||
625 | o = 0.5f + o / (1.0f + qAbs(o)); | 662 | o = 0.5f + o / (1.0f + qAbs(o)); | ||
626 | #endif | 663 | #endif | ||
627 | glBlendColor(0, 0, 0, o); | 664 | glBlendColor(0, 0, 0, o); | ||
628 | glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); | 665 | glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); | ||
629 | } | 666 | } | ||
630 | 667 | | |||
631 | //Final upscale to the screen | 668 | upscaleRenderToScreen(vbo, blurRectCount * (m_downSampleIterations + 1), shape.rectCount() * 6, screenProjection, shape.boundingRect(), windowRect.topLeft()); | ||
632 | m_shader->bind(BlurShader::UpSampleType); | | |||
633 | m_shader->setOffset(m_offset); | | |||
634 | 669 | | |||
635 | m_shader->setModelViewProjectionMatrix(screenProjection); | 670 | if (opacity < 1.0) { | ||
636 | m_shader->setTargetSize(m_renderTextures[0].size()); | 671 | glDisable(GL_BLEND); | ||
672 | } | ||||
637 | 673 | | |||
638 | //Copy the image from this texture | 674 | vbo->unbindArrays(); | ||
675 | } | ||||
676 | | ||||
677 | void BlurEffect::upscaleRenderToScreen(GLVertexBuffer *vbo, int vboStart, int blurRectCount, QMatrix4x4 screenProjection, QRect windowShape, QPoint windowPosition) | ||||
678 | { | ||||
679 | glActiveTexture(GL_TEXTURE0); | ||||
639 | m_renderTextures[1].bind(); | 680 | m_renderTextures[1].bind(); | ||
640 | 681 | | |||
641 | //Render to the screen | 682 | if (m_noiseStrength > 0) { | ||
642 | vbo->draw(GL_TRIANGLES, blurRectCount * (m_downSampleIterations + 1), shape.rectCount() * 6); | 683 | m_shader->bind(BlurShader::NoiseSampleType); | ||
684 | m_shader->setTargetTextureSize(m_renderTextures[0].size()); | ||||
fredrik: The noise texture is technically not a target. | |||||
anemeth: Separated the 2 texture sizes into 2 functions | |||||
685 | m_shader->setNoiseTextureSize(m_noiseTexture.size()); | ||||
686 | m_shader->setTexturePosition(windowPosition); | ||||
643 | 687 | | |||
644 | if (opacity < 1.0) { | 688 | glActiveTexture(GL_TEXTURE1); | ||
645 | glDisable(GL_BLEND); | 689 | m_noiseTexture.bind(); | ||
690 | } else { | ||||
691 | m_shader->bind(BlurShader::UpSampleType); | ||||
692 | m_shader->setTargetTextureSize(m_renderTextures[0].size()); | ||||
646 | } | 693 | } | ||
647 | 694 | | |||
648 | vbo->unbindArrays(); | 695 | m_shader->setOffset(m_offset); | ||
696 | m_shader->setModelViewProjectionMatrix(screenProjection); | ||||
697 | | ||||
698 | //Render to the screen | ||||
699 | vbo->draw(GL_TRIANGLES, vboStart, blurRectCount); | ||||
700 | | ||||
701 | glActiveTexture(GL_TEXTURE0); | ||||
649 | m_shader->unbind(); | 702 | m_shader->unbind(); | ||
650 | } | 703 | } | ||
651 | 704 | | |||
652 | void BlurEffect::downSampleTexture(GLVertexBuffer *vbo, int blurRectCount) | 705 | void BlurEffect::downSampleTexture(GLVertexBuffer *vbo, int blurRectCount) | ||
653 | { | 706 | { | ||
654 | QMatrix4x4 modelViewProjectionMatrix; | 707 | QMatrix4x4 modelViewProjectionMatrix; | ||
655 | 708 | | |||
656 | m_shader->bind(BlurShader::DownSampleType); | 709 | m_shader->bind(BlurShader::DownSampleType); | ||
657 | m_shader->setOffset(m_offset); | 710 | m_shader->setOffset(m_offset); | ||
658 | 711 | | |||
659 | for (int i = 1; i <= m_downSampleIterations; i++) { | 712 | for (int i = 1; i <= m_downSampleIterations; i++) { | ||
660 | modelViewProjectionMatrix.setToIdentity(); | 713 | modelViewProjectionMatrix.setToIdentity(); | ||
661 | modelViewProjectionMatrix.ortho(0, m_renderTextures[i].width(), m_renderTextures[i].height(), 0 , 0, 65535); | 714 | modelViewProjectionMatrix.ortho(0, m_renderTextures[i].width(), m_renderTextures[i].height(), 0 , 0, 65535); | ||
662 | 715 | | |||
663 | m_shader->setModelViewProjectionMatrix(modelViewProjectionMatrix); | 716 | m_shader->setModelViewProjectionMatrix(modelViewProjectionMatrix); | ||
664 | m_shader->setTargetSize(m_renderTextures[i].size()); | 717 | m_shader->setTargetTextureSize(m_renderTextures[i].size()); | ||
665 | 718 | | |||
666 | //Copy the image from this texture | 719 | //Copy the image from this texture | ||
667 | m_renderTextures[i - 1].bind(); | 720 | m_renderTextures[i - 1].bind(); | ||
668 | 721 | | |||
669 | vbo->draw(GL_TRIANGLES, blurRectCount * i, blurRectCount); | 722 | vbo->draw(GL_TRIANGLES, blurRectCount * i, blurRectCount); | ||
670 | GLRenderTarget::popRenderTarget(); | 723 | GLRenderTarget::popRenderTarget(); | ||
671 | } | 724 | } | ||
672 | 725 | | |||
673 | m_shader->unbind(); | 726 | m_shader->unbind(); | ||
674 | } | 727 | } | ||
675 | 728 | | |||
676 | void BlurEffect::upSampleTexture(GLVertexBuffer *vbo, int blurRectCount) | 729 | void BlurEffect::upSampleTexture(GLVertexBuffer *vbo, int blurRectCount) | ||
677 | { | 730 | { | ||
678 | QMatrix4x4 modelViewProjectionMatrix; | 731 | QMatrix4x4 modelViewProjectionMatrix; | ||
679 | 732 | | |||
680 | m_shader->bind(BlurShader::UpSampleType); | 733 | m_shader->bind(BlurShader::UpSampleType); | ||
681 | m_shader->setOffset(m_offset); | 734 | m_shader->setOffset(m_offset); | ||
682 | 735 | | |||
683 | for (int i = m_downSampleIterations - 1; i > 0; i--) { | 736 | for (int i = m_downSampleIterations - 1; i >= 1; i--) { | ||
684 | modelViewProjectionMatrix.setToIdentity(); | 737 | modelViewProjectionMatrix.setToIdentity(); | ||
685 | modelViewProjectionMatrix.ortho(0, m_renderTextures[i].width(), m_renderTextures[i].height(), 0 , 0, 65535); | 738 | modelViewProjectionMatrix.ortho(0, m_renderTextures[i].width(), m_renderTextures[i].height(), 0 , 0, 65535); | ||
686 | 739 | | |||
687 | m_shader->setModelViewProjectionMatrix(modelViewProjectionMatrix); | 740 | m_shader->setModelViewProjectionMatrix(modelViewProjectionMatrix); | ||
688 | m_shader->setTargetSize(m_renderTextures[i].size()); | 741 | m_shader->setTargetTextureSize(m_renderTextures[i].size()); | ||
689 | 742 | | |||
690 | //Copy the image from this texture | 743 | //Copy the image from this texture | ||
691 | m_renderTextures[i + 1].bind(); | 744 | m_renderTextures[i + 1].bind(); | ||
692 | 745 | | |||
693 | vbo->draw(GL_TRIANGLES, blurRectCount * i, blurRectCount); | 746 | vbo->draw(GL_TRIANGLES, blurRectCount * i, blurRectCount); | ||
694 | GLRenderTarget::popRenderTarget(); | 747 | GLRenderTarget::popRenderTarget(); | ||
695 | } | 748 | } | ||
696 | 749 | | |||
697 | m_shader->unbind(); | 750 | m_shader->unbind(); | ||
698 | } | 751 | } | ||
699 | 752 | | |||
700 | void BlurEffect::copyScreenSampleTexture(GLVertexBuffer *vbo, int blurRectCount, QRegion blurShape, QSize screenSize, QMatrix4x4 screenProjection) | 753 | void BlurEffect::copyScreenSampleTexture(GLVertexBuffer *vbo, int blurRectCount, QRegion blurShape, QSize screenSize, QMatrix4x4 screenProjection) | ||
701 | { | 754 | { | ||
702 | m_shader->bind(BlurShader::CopySampleType); | 755 | m_shader->bind(BlurShader::CopySampleType); | ||
703 | 756 | | |||
704 | m_shader->setModelViewProjectionMatrix(screenProjection); | 757 | m_shader->setModelViewProjectionMatrix(screenProjection); | ||
705 | m_shader->setTargetSize(screenSize); | 758 | m_shader->setTargetTextureSize(screenSize); | ||
706 | 759 | | |||
707 | /* | 760 | /* | ||
708 | * This '1' sized adjustment is necessary do avoid windows affecting the blur that are | 761 | * This '1' sized adjustment is necessary do avoid windows affecting the blur that are | ||
709 | * right next to this window. | 762 | * right next to this window. | ||
710 | */ | 763 | */ | ||
711 | m_shader->setBlurRect(blurShape.boundingRect().adjusted(1, 1, -1, -1), screenSize); | 764 | m_shader->setBlurRect(blurShape.boundingRect().adjusted(1, 1, -1, -1), screenSize); | ||
712 | 765 | | |||
713 | vbo->draw(GL_TRIANGLES, 0, blurRectCount); | 766 | vbo->draw(GL_TRIANGLES, 0, blurRectCount); | ||
714 | GLRenderTarget::popRenderTarget(); | 767 | GLRenderTarget::popRenderTarget(); | ||
715 | 768 | | |||
716 | m_shader->unbind(); | 769 | m_shader->unbind(); | ||
717 | } | 770 | } | ||
718 | 771 | | |||
719 | } // namespace KWin | 772 | } // namespace KWin | ||
720 | 773 | |
Should this be m_renderTargetStack?