Changeset View
Standalone View
effects/blur/blur.cpp
Show All 27 Lines | |||||
28 | #include <QMatrix4x4> | 28 | #include <QMatrix4x4> | ||
29 | #include <QLinkedList> | 29 | #include <QLinkedList> | ||
30 | #include <cmath> // for ceil() | 30 | #include <cmath> // for ceil() | ||
31 | 31 | | |||
32 | #include <KWayland/Server/surface_interface.h> | 32 | #include <KWayland/Server/surface_interface.h> | ||
33 | #include <KWayland/Server/blur_interface.h> | 33 | #include <KWayland/Server/blur_interface.h> | ||
34 | #include <KWayland/Server/shadow_interface.h> | 34 | #include <KWayland/Server/shadow_interface.h> | ||
35 | #include <KWayland/Server/display.h> | 35 | #include <KWayland/Server/display.h> | ||
36 | #include <KSharedConfig> | ||||
37 | #include <KConfigGroup> | ||||
36 | 38 | | |||
37 | namespace KWin | 39 | namespace KWin | ||
38 | { | 40 | { | ||
39 | 41 | | |||
40 | static const QByteArray s_blurAtomName = QByteArrayLiteral("_KDE_NET_WM_BLUR_BEHIND_REGION"); | 42 | static const QByteArray s_blurAtomName = QByteArrayLiteral("_KDE_NET_WM_BLUR_BEHIND_REGION"); | ||
41 | 43 | | |||
42 | BlurEffect::BlurEffect() | 44 | BlurEffect::BlurEffect() | ||
43 | { | 45 | { | ||
▲ Show 20 Lines • Show All 94 Lines • ▼ Show 20 Line(s) | 118 | { | |||
138 | m_renderTextures.last().setWrapMode(GL_CLAMP_TO_EDGE); | 140 | m_renderTextures.last().setWrapMode(GL_CLAMP_TO_EDGE); | ||
139 | 141 | | |||
140 | m_renderTargets.append(new GLRenderTarget(m_renderTextures.last())); | 142 | m_renderTargets.append(new GLRenderTarget(m_renderTextures.last())); | ||
141 | 143 | | |||
142 | m_renderTargetsValid = renderTargetsValid(); | 144 | m_renderTargetsValid = renderTargetsValid(); | ||
143 | 145 | | |||
144 | // Prepare the stack for the rendering | 146 | // Prepare the stack for the rendering | ||
145 | m_renderTargetStack.clear(); | 147 | m_renderTargetStack.clear(); | ||
146 | m_renderTargets.reserve(m_downSampleIterations * 2 - 1); | 148 | m_renderTargets.reserve(m_downSampleIterations * 2); | ||
149 | | ||||
150 | int upSampleEnd = 0; | ||||
151 | | ||||
152 | if (m_noiseStrength == 0) { | ||||
153 | upSampleEnd = 1; | ||||
154 | } | ||||
fredrik: Should this be m_renderTargetStack? | |||||
anemeth: Whoops, you're right. Fixed. | |||||
147 | 155 | | |||
148 | // Upsample | 156 | // Upsample | ||
149 | for (int i = 1; i < m_downSampleIterations; i++) { | 157 | for (int i = upSampleEnd; i < m_downSampleIterations; i++) { | ||
150 | m_renderTargetStack.push(m_renderTargets[i]); | 158 | m_renderTargetStack.push(m_renderTargets[i]); | ||
151 | } | 159 | } | ||
152 | 160 | | |||
153 | // Downsample | 161 | // Downsample | ||
154 | for (int i = m_downSampleIterations; i > 0; i--) { | 162 | for (int i = m_downSampleIterations; i > 0; i--) { | ||
155 | m_renderTargetStack.push(m_renderTargets[i]); | 163 | m_renderTargetStack.push(m_renderTargets[i]); | ||
156 | } | 164 | } | ||
157 | 165 | | |||
158 | // Copysample | 166 | // Copysample | ||
159 | m_renderTargetStack.push(m_renderTargets[0]); | 167 | m_renderTargetStack.push(m_renderTargets[0]); | ||
168 | | ||||
169 | // Generate the noise helper texture | ||||
170 | generateNoiseTexture(); | ||||
160 | } | 171 | } | ||
161 | 172 | | |||
162 | void BlurEffect::initBlurStrengthValues() | 173 | void BlurEffect::initBlurStrengthValues() | ||
163 | { | 174 | { | ||
164 | // This function creates an array of blur strength values that are evenly distributed | 175 | // This function creates an array of blur strength values that are evenly distributed | ||
165 | 176 | | |||
166 | // The range of the slider on the blur settings UI | 177 | // The range of the slider on the blur settings UI | ||
167 | int numOfBlurSteps = 15; | 178 | int numOfBlurSteps = 15; | ||
▲ Show 20 Lines • Show All 54 Lines • ▼ Show 20 Line(s) | 232 | { | |||
222 | Q_UNUSED(flags) | 233 | Q_UNUSED(flags) | ||
223 | 234 | | |||
224 | BlurConfig::self()->read(); | 235 | BlurConfig::self()->read(); | ||
225 | 236 | | |||
226 | int blurStrength = BlurConfig::blurStrength() - 1; | 237 | int blurStrength = BlurConfig::blurStrength() - 1; | ||
227 | m_downSampleIterations = blurStrengthValues[blurStrength].iteration; | 238 | m_downSampleIterations = blurStrengthValues[blurStrength].iteration; | ||
228 | m_offset = blurStrengthValues[blurStrength].offset; | 239 | m_offset = blurStrengthValues[blurStrength].offset; | ||
229 | m_expandSize = blurOffsets[m_downSampleIterations - 1].expandSize; | 240 | m_expandSize = blurOffsets[m_downSampleIterations - 1].expandSize; | ||
241 | m_noiseStrength = BlurConfig::noiseStrength(); | ||||
242 | | ||||
243 | m_scalingFactor = KSharedConfig::openConfig(QStringLiteral("kdeglobals"))->group("KScreen").readEntry("ScaleFactor", 1.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 | 244 | | |||
231 | updateTexture(); | 245 | updateTexture(); | ||
232 | 246 | | |||
233 | if (!m_shader || !m_shader->isValid()) { | 247 | if (!m_shader || !m_shader->isValid()) { | ||
234 | effects->removeSupportProperty(s_blurAtomName, this); | 248 | effects->removeSupportProperty(s_blurAtomName, this); | ||
235 | delete m_blurManager; | 249 | delete m_blurManager; | ||
236 | m_blurManager = nullptr; | 250 | m_blurManager = nullptr; | ||
237 | } | 251 | } | ||
▲ Show 20 Lines • Show All 328 Lines • ▼ Show 20 Line(s) | 576 | { | |||
566 | QRegion shape = frame->geometry().adjusted(-borderSize, -borderSize, borderSize, borderSize) & screen; | 580 | QRegion shape = frame->geometry().adjusted(-borderSize, -borderSize, borderSize, borderSize) & screen; | ||
567 | 581 | | |||
568 | if (valid && !shape.isEmpty() && region.intersects(shape.boundingRect()) && frame->style() != EffectFrameNone) { | 582 | if (valid && !shape.isEmpty() && region.intersects(shape.boundingRect()) && frame->style() != EffectFrameNone) { | ||
569 | doBlur(shape, screen, opacity * frameOpacity, frame->screenProjectionMatrix(), false); | 583 | doBlur(shape, screen, opacity * frameOpacity, frame->screenProjectionMatrix(), false); | ||
570 | } | 584 | } | ||
571 | effects->paintEffectFrame(frame, region, opacity, frameOpacity); | 585 | effects->paintEffectFrame(frame, region, opacity, frameOpacity); | ||
572 | } | 586 | } | ||
573 | 587 | | |||
588 | void BlurEffect::generateNoiseTexture() | ||||
589 | { | ||||
590 | if (m_noiseStrength == 0) { | ||||
591 | return; | ||||
592 | } | ||||
593 | | ||||
594 | QImage noiseImage(QSize(128, 128), QImage::Format_RGBA8888); | ||||
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… | |||||
595 | int randomNumber; | ||||
596 | | ||||
597 | for (int y = 0; y < noiseImage.height(); y++) { | ||||
598 | for (int x = 0; x < noiseImage.width(); x++) { | ||||
599 | randomNumber = qrand() % m_noiseStrength; | ||||
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. | |||||
600 | noiseImage.setPixelColor(x, y, QColor( | ||||
601 | randomNumber, // red | ||||
602 | randomNumber, // green | ||||
603 | randomNumber, // blue | ||||
604 | 0 | ||||
605 | )); | ||||
606 | } | ||||
607 | } | ||||
608 | | ||||
609 | m_noiseTexture = GLTexture(noiseImage); | ||||
610 | m_noiseTexture.setFilter(GL_NEAREST); | ||||
611 | m_noiseTexture.setWrapMode(GL_REPEAT); | ||||
612 | } | ||||
613 | | ||||
574 | void BlurEffect::doBlur(const QRegion& shape, const QRect& screen, const float opacity, const QMatrix4x4 &screenProjection, bool isDock) | 614 | void BlurEffect::doBlur(const QRegion& shape, const QRect& screen, const float opacity, const QMatrix4x4 &screenProjection, bool isDock) | ||
575 | { | 615 | { | ||
576 | QRegion expandedBlurRegion = expand(shape) & expand(screen); | 616 | QRegion expandedBlurRegion = expand(shape) & expand(screen); | ||
577 | 617 | | |||
578 | // Upload geometry for the down and upsample iterations | 618 | // Upload geometry for the down and upsample iterations | ||
579 | GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); | 619 | GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer(); | ||
580 | uploadGeometry(vbo, expandedBlurRegion, shape); | 620 | uploadGeometry(vbo, expandedBlurRegion, shape); | ||
581 | vbo->bindArrays(); | 621 | vbo->bindArrays(); | ||
582 | 622 | | |||
583 | /* | 623 | /* | ||
584 | * If the window is a dock or panel we avoid the "extended blur" effect. | 624 | * If the window is a dock or panel we avoid the "extended blur" effect. | ||
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. | |||||
585 | * Extended blur is when windows that are not under the blurred area affect | 625 | * Extended blur is when windows that are not under the blurred area affect | ||
586 | * the final blur result. | 626 | * the final blur result. | ||
587 | * We want to avoid this on panels, because it looks really weird and ugly | 627 | * We want to avoid this on panels, because it looks really weird and ugly | ||
588 | * when maximized windows or windows near the panel affect the dock blur. | 628 | * when maximized windows or windows near the panel affect the dock blur. | ||
589 | */ | 629 | */ | ||
590 | isDock ? m_renderTextures.last().bind() : m_renderTextures[0].bind(); | 630 | isDock ? m_renderTextures.last().bind() : m_renderTextures[0].bind(); | ||
591 | 631 | | |||
592 | QRect copyRect = expandedBlurRegion.boundingRect() & screen; | 632 | QRect copyRect = expandedBlurRegion.boundingRect() & screen; | ||
593 | glCopyTexSubImage2D( | 633 | glCopyTexSubImage2D( | ||
594 | GL_TEXTURE_2D, | 634 | GL_TEXTURE_2D, | ||
595 | 0, | 635 | 0, | ||
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 :) | |||||
596 | copyRect.x(), | 636 | copyRect.x(), | ||
597 | effects->virtualScreenSize().height() - copyRect.y() - copyRect.height(), | 637 | effects->virtualScreenSize().height() - copyRect.y() - copyRect.height(), | ||
598 | copyRect.x(), | 638 | copyRect.x(), | ||
599 | effects->virtualScreenSize().height() - copyRect.y() - copyRect.height(), | 639 | effects->virtualScreenSize().height() - copyRect.y() - copyRect.height(), | ||
600 | copyRect.width(), | 640 | copyRect.width(), | ||
601 | copyRect.height() | 641 | copyRect.height() | ||
602 | ); | 642 | ); | ||
603 | 643 | | |||
Show All 19 Lines | |||||
623 | #else // sigmoid shape, above y = x for x > 0.5, below y = x for x < 0.5 | 663 | #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; | 664 | float o = 2.0f*opacity - 1.0f; | ||
625 | o = 0.5f + o / (1.0f + qAbs(o)); | 665 | o = 0.5f + o / (1.0f + qAbs(o)); | ||
626 | #endif | 666 | #endif | ||
627 | glBlendColor(0, 0, 0, o); | 667 | glBlendColor(0, 0, 0, o); | ||
628 | glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); | 668 | glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); | ||
629 | } | 669 | } | ||
630 | 670 | | |||
631 | //Final upscale to the screen | 671 | if (m_noiseStrength == 0) { | ||
672 | upscaleRenderToScreen(vbo, blurRectCount * (m_downSampleIterations + 1), shape.rectCount() * 6, screenProjection); | ||||
673 | } else { | ||||
fredrik: The noise texture is technically not a target. | |||||
anemeth: Separated the 2 texture sizes into 2 functions | |||||
674 | noiseSampleTexture(vbo, blurRectCount * (m_downSampleIterations + 1), shape.rectCount() * 6); | ||||
675 | } | ||||
676 | | ||||
677 | if (opacity < 1.0) { | ||||
678 | glDisable(GL_BLEND); | ||||
679 | } | ||||
680 | | ||||
681 | vbo->unbindArrays(); | ||||
682 | } | ||||
683 | | ||||
684 | void BlurEffect::upscaleRenderToScreen(GLVertexBuffer *vbo, int vboStart, int blurRectCount, QMatrix4x4 screenProjection) | ||||
685 | { | ||||
632 | m_shader->bind(BlurShader::UpSampleType); | 686 | m_shader->bind(BlurShader::UpSampleType); | ||
633 | m_shader->setOffset(m_offset); | 687 | m_shader->setOffset(m_offset); | ||
634 | 688 | | |||
635 | m_shader->setModelViewProjectionMatrix(screenProjection); | 689 | m_shader->setModelViewProjectionMatrix(screenProjection); | ||
636 | m_shader->setTargetSize(m_renderTextures[0].size()); | 690 | m_shader->setTargetSize(m_renderTextures[0].size()); | ||
637 | 691 | | |||
638 | //Copy the image from this texture | 692 | //Copy the image from this texture | ||
639 | m_renderTextures[1].bind(); | 693 | m_renderTextures[1].bind(); | ||
640 | 694 | | |||
641 | //Render to the screen | 695 | //Render to the screen | ||
642 | vbo->draw(GL_TRIANGLES, blurRectCount * (m_downSampleIterations + 1), shape.rectCount() * 6); | 696 | vbo->draw(GL_TRIANGLES, vboStart, blurRectCount); | ||
643 | | ||||
644 | if (opacity < 1.0) { | | |||
645 | glDisable(GL_BLEND); | | |||
646 | } | | |||
647 | 697 | | |||
648 | vbo->unbindArrays(); | | |||
649 | m_shader->unbind(); | 698 | m_shader->unbind(); | ||
650 | } | 699 | } | ||
651 | 700 | | |||
652 | void BlurEffect::downSampleTexture(GLVertexBuffer *vbo, int blurRectCount) | 701 | void BlurEffect::downSampleTexture(GLVertexBuffer *vbo, int blurRectCount) | ||
653 | { | 702 | { | ||
654 | QMatrix4x4 modelViewProjectionMatrix; | 703 | QMatrix4x4 modelViewProjectionMatrix; | ||
655 | 704 | | |||
656 | m_shader->bind(BlurShader::DownSampleType); | 705 | m_shader->bind(BlurShader::DownSampleType); | ||
Show All 18 Lines | |||||
675 | 724 | | |||
676 | void BlurEffect::upSampleTexture(GLVertexBuffer *vbo, int blurRectCount) | 725 | void BlurEffect::upSampleTexture(GLVertexBuffer *vbo, int blurRectCount) | ||
677 | { | 726 | { | ||
678 | QMatrix4x4 modelViewProjectionMatrix; | 727 | QMatrix4x4 modelViewProjectionMatrix; | ||
679 | 728 | | |||
680 | m_shader->bind(BlurShader::UpSampleType); | 729 | m_shader->bind(BlurShader::UpSampleType); | ||
681 | m_shader->setOffset(m_offset); | 730 | m_shader->setOffset(m_offset); | ||
682 | 731 | | |||
683 | for (int i = m_downSampleIterations - 1; i > 0; i--) { | 732 | int upSampleEnd = 0; | ||
733 | | ||||
734 | if (m_noiseStrength == 0) { | ||||
735 | upSampleEnd = 1; | ||||
736 | } | ||||
737 | | ||||
738 | for (int i = m_downSampleIterations - 1; i >= upSampleEnd; i--) { | ||||
684 | modelViewProjectionMatrix.setToIdentity(); | 739 | modelViewProjectionMatrix.setToIdentity(); | ||
685 | modelViewProjectionMatrix.ortho(0, m_renderTextures[i].width(), m_renderTextures[i].height(), 0 , 0, 65535); | 740 | modelViewProjectionMatrix.ortho(0, m_renderTextures[i].width(), m_renderTextures[i].height(), 0 , 0, 65535); | ||
686 | 741 | | |||
687 | m_shader->setModelViewProjectionMatrix(modelViewProjectionMatrix); | 742 | m_shader->setModelViewProjectionMatrix(modelViewProjectionMatrix); | ||
688 | m_shader->setTargetSize(m_renderTextures[i].size()); | 743 | m_shader->setTargetSize(m_renderTextures[i].size()); | ||
689 | 744 | | |||
690 | //Copy the image from this texture | 745 | //Copy the image from this texture | ||
691 | m_renderTextures[i + 1].bind(); | 746 | m_renderTextures[i + 1].bind(); | ||
Show All 19 Lines | 756 | { | |||
711 | m_shader->setBlurRect(blurShape.boundingRect().adjusted(1, 1, -1, -1), screenSize); | 766 | m_shader->setBlurRect(blurShape.boundingRect().adjusted(1, 1, -1, -1), screenSize); | ||
712 | 767 | | |||
713 | vbo->draw(GL_TRIANGLES, 0, blurRectCount); | 768 | vbo->draw(GL_TRIANGLES, 0, blurRectCount); | ||
714 | GLRenderTarget::popRenderTarget(); | 769 | GLRenderTarget::popRenderTarget(); | ||
715 | 770 | | |||
716 | m_shader->unbind(); | 771 | m_shader->unbind(); | ||
717 | } | 772 | } | ||
718 | 773 | | |||
774 | void BlurEffect::noiseSampleTexture(GLVertexBuffer *vbo, int vboStart, int blurRectCount) | ||||
775 | { | ||||
776 | m_shader->bind(BlurShader::NoiseSampleType); | ||||
777 | | ||||
778 | QMatrix4x4 modelViewProjectionMatrix; | ||||
779 | modelViewProjectionMatrix.setToIdentity(); | ||||
780 | modelViewProjectionMatrix.ortho(0, m_renderTextures[0].width(), m_renderTextures[0].height(), 0 , 0, 65535); | ||||
781 | | ||||
782 | m_shader->setModelViewProjectionMatrix(modelViewProjectionMatrix); | ||||
783 | m_shader->setTargetSize(m_renderTextures[0].size()); | ||||
784 | m_shader->setNoiseTextureRatio((float)m_renderTextures[0].width() / m_noiseTexture.width() / m_scalingFactor); | ||||
785 | | ||||
786 | glActiveTexture(GL_TEXTURE0); | ||||
787 | m_renderTextures[0].bind(); | ||||
788 | | ||||
789 | glActiveTexture(GL_TEXTURE1); | ||||
790 | m_noiseTexture.bind(); | ||||
791 | | ||||
792 | vbo->draw(GL_TRIANGLES, vboStart, blurRectCount); | ||||
793 | | ||||
794 | glActiveTexture(GL_TEXTURE0); | ||||
795 | m_shader->unbind(); | ||||
796 | } | ||||
797 | | ||||
719 | } // namespace KWin | 798 | } // namespace KWin | ||
720 | 799 | |
Should this be m_renderTargetStack?