File indexing completed on 2024-05-19 05:31:55

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2006-2007 Rivo Laks <rivolaks@hot.ee>
0006     SPDX-FileCopyrightText: 2010, 2011 Martin Gräßlin <mgraesslin@kde.org>
0007     SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@kde.org>
0008 
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 */
0011 #include "glshadermanager.h"
0012 #include "glplatform.h"
0013 #include "glshader.h"
0014 #include "glvertexbuffer.h"
0015 #include "utils/common.h"
0016 
0017 #include <QFile>
0018 #include <QTextStream>
0019 
0020 namespace KWin
0021 {
0022 
0023 std::unique_ptr<ShaderManager> ShaderManager::s_shaderManager;
0024 
0025 ShaderManager *ShaderManager::instance()
0026 {
0027     if (!s_shaderManager) {
0028         s_shaderManager = std::make_unique<ShaderManager>();
0029     }
0030     return s_shaderManager.get();
0031 }
0032 
0033 void ShaderManager::cleanup()
0034 {
0035     s_shaderManager.reset();
0036 }
0037 
0038 ShaderManager::ShaderManager()
0039 {
0040 }
0041 
0042 ShaderManager::~ShaderManager()
0043 {
0044     while (!m_boundShaders.isEmpty()) {
0045         popShader();
0046     }
0047 }
0048 
0049 QByteArray ShaderManager::generateVertexSource(ShaderTraits traits) const
0050 {
0051     QByteArray source;
0052     QTextStream stream(&source);
0053 
0054     GLPlatform *const gl = GLPlatform::instance();
0055     QByteArray attribute, varying;
0056 
0057     if (!gl->isGLES()) {
0058         const bool glsl_140 = gl->glslVersion() >= Version(1, 40);
0059 
0060         attribute = glsl_140 ? QByteArrayLiteral("in") : QByteArrayLiteral("attribute");
0061         varying = glsl_140 ? QByteArrayLiteral("out") : QByteArrayLiteral("varying");
0062 
0063         if (glsl_140) {
0064             stream << "#version 140\n\n";
0065         }
0066     } else {
0067         const bool glsl_es_300 = gl->glslVersion() >= Version(3, 0);
0068 
0069         attribute = glsl_es_300 ? QByteArrayLiteral("in") : QByteArrayLiteral("attribute");
0070         varying = glsl_es_300 ? QByteArrayLiteral("out") : QByteArrayLiteral("varying");
0071 
0072         if (glsl_es_300) {
0073             stream << "#version 300 es\n\n";
0074         }
0075     }
0076 
0077     stream << attribute << " vec4 position;\n";
0078     if (traits & (ShaderTrait::MapTexture | ShaderTrait::MapExternalTexture)) {
0079         stream << attribute << " vec4 texcoord;\n\n";
0080         stream << varying << " vec2 texcoord0;\n\n";
0081     } else {
0082         stream << "\n";
0083     }
0084 
0085     stream << "uniform mat4 modelViewProjectionMatrix;\n\n";
0086 
0087     stream << "void main()\n{\n";
0088     if (traits & (ShaderTrait::MapTexture | ShaderTrait::MapExternalTexture)) {
0089         stream << "    texcoord0 = texcoord.st;\n";
0090     }
0091 
0092     stream << "    gl_Position = modelViewProjectionMatrix * position;\n";
0093     stream << "}\n";
0094 
0095     stream.flush();
0096     return source;
0097 }
0098 
0099 QByteArray ShaderManager::generateFragmentSource(ShaderTraits traits) const
0100 {
0101     QByteArray source;
0102     QTextStream stream(&source);
0103 
0104     GLPlatform *const gl = GLPlatform::instance();
0105     QByteArray varying, output, textureLookup;
0106 
0107     if (!gl->isGLES()) {
0108         const bool glsl_140 = gl->glslVersion() >= Version(1, 40);
0109 
0110         if (glsl_140) {
0111             stream << "#version 140\n\n";
0112         }
0113 
0114         varying = glsl_140 ? QByteArrayLiteral("in") : QByteArrayLiteral("varying");
0115         textureLookup = glsl_140 ? QByteArrayLiteral("texture") : QByteArrayLiteral("texture2D");
0116         output = glsl_140 ? QByteArrayLiteral("fragColor") : QByteArrayLiteral("gl_FragColor");
0117     } else {
0118         const bool glsl_es_300 = GLPlatform::instance()->glslVersion() >= Version(3, 0);
0119 
0120         if (glsl_es_300) {
0121             stream << "#version 300 es\n\n";
0122         }
0123 
0124         // From the GLSL ES specification:
0125         //
0126         //     "The fragment language has no default precision qualifier for floating point types."
0127         stream << "precision highp float;\n\n";
0128 
0129         varying = glsl_es_300 ? QByteArrayLiteral("in") : QByteArrayLiteral("varying");
0130         textureLookup = glsl_es_300 ? QByteArrayLiteral("texture") : QByteArrayLiteral("texture2D");
0131         output = glsl_es_300 ? QByteArrayLiteral("fragColor") : QByteArrayLiteral("gl_FragColor");
0132     }
0133 
0134     if (traits & ShaderTrait::MapTexture) {
0135         stream << "uniform sampler2D sampler;\n";
0136         stream << "uniform sampler2D sampler1;\n";
0137         stream << "uniform int converter;\n";
0138         stream << varying << " vec2 texcoord0;\n";
0139     } else if (traits & ShaderTrait::MapExternalTexture) {
0140         stream << "#extension GL_OES_EGL_image_external : require\n\n";
0141         stream << "uniform samplerExternalOES sampler;\n";
0142         stream << varying << " vec2 texcoord0;\n";
0143     } else if (traits & ShaderTrait::UniformColor) {
0144         stream << "uniform vec4 geometryColor;\n";
0145     }
0146     if (traits & ShaderTrait::Modulate) {
0147         stream << "uniform vec4 modulation;\n";
0148     }
0149     if (traits & ShaderTrait::AdjustSaturation) {
0150         stream << "#include \"saturation.glsl\"\n";
0151     }
0152     if (traits & ShaderTrait::TransformColorspace) {
0153         stream << "#include \"colormanagement.glsl\"\n";
0154     }
0155 
0156     if (output != QByteArrayLiteral("gl_FragColor")) {
0157         stream << "\nout vec4 " << output << ";\n";
0158     }
0159 
0160     if (traits & ShaderTrait::MapTexture) {
0161         // limited range BT601 in -> full range BT709 out
0162         stream << "vec4 transformY_UV(sampler2D tex0, sampler2D tex1, vec2 texcoord0) {\n";
0163         stream << "    float y = 1.16438356 * (" << textureLookup << "(tex0, texcoord0).x - 0.0625);\n";
0164         stream << "    float u = " << textureLookup << "(tex1, texcoord0).r - 0.5;\n";
0165         stream << "    float v = " << textureLookup << "(tex1, texcoord0).g - 0.5;\n";
0166         stream << "    return vec4(y + 1.59602678 * v"
0167                   "              , y - 0.39176229 * u - 0.81296764 * v"
0168                   "              , y + 2.01723214 * u"
0169                   "              , 1);\n";
0170         stream << "}\n";
0171         stream << "\n";
0172     }
0173 
0174     stream << "\nvoid main(void)\n{\n";
0175     stream << "    vec4 result;\n";
0176     if (traits & ShaderTrait::MapTexture) {
0177         stream << "    if (converter == 0) {\n";
0178         stream << "        result = " << textureLookup << "(sampler, texcoord0);\n";
0179         stream << "    } else {\n";
0180         stream << "        result = transformY_UV(sampler, sampler1, texcoord0);\n";
0181         stream << "    }\n";
0182     } else if (traits & ShaderTrait::MapExternalTexture) {
0183         // external textures require texture2D for sampling
0184         stream << "    result = texture2D(sampler, texcoord0);\n";
0185     } else if (traits & ShaderTrait::UniformColor) {
0186         stream << "    result = geometryColor;\n";
0187     }
0188     if (traits & ShaderTrait::TransformColorspace) {
0189         stream << "    result = sourceEncodingToNitsInDestinationColorspace(result);\n";
0190     }
0191     if (traits & ShaderTrait::AdjustSaturation) {
0192         stream << "    result = adjustSaturation(result);\n";
0193     }
0194     if (traits & ShaderTrait::Modulate) {
0195         stream << "    result *= modulation;\n";
0196     }
0197     if (traits & ShaderTrait::TransformColorspace) {
0198         stream << "    result = nitsToDestinationEncoding(result);\n";
0199     }
0200 
0201     stream << "    " << output << " = result;\n";
0202     stream << "}";
0203     stream.flush();
0204     return source;
0205 }
0206 
0207 std::unique_ptr<GLShader> ShaderManager::generateShader(ShaderTraits traits)
0208 {
0209     return generateCustomShader(traits);
0210 }
0211 
0212 std::optional<QByteArray> ShaderManager::preprocess(const QByteArray &src, int recursionDepth) const
0213 {
0214     recursionDepth++;
0215     if (recursionDepth > 10) {
0216         qCWarning(KWIN_OPENGL, "shader has too many recursive includes!");
0217         return std::nullopt;
0218     }
0219     QByteArray ret;
0220     ret.reserve(src.size());
0221     const auto split = src.split('\n');
0222     for (auto it = split.begin(); it != split.end(); it++) {
0223         const auto &line = *it;
0224         if (line.startsWith("#include \"") && line.endsWith("\"")) {
0225             static constexpr ssize_t includeLength = QByteArrayView("#include \"").size();
0226             const QByteArray path = ":/opengl/" + line.mid(includeLength, line.size() - includeLength - 1);
0227             QFile file(path);
0228             if (!file.open(QIODevice::ReadOnly)) {
0229                 qCWarning(KWIN_OPENGL, "failed to read include line %s", qPrintable(line));
0230                 return std::nullopt;
0231             }
0232             const auto processed = preprocess(file.readAll(), recursionDepth);
0233             if (!processed) {
0234                 return std::nullopt;
0235             }
0236             ret.append(*processed);
0237         } else {
0238             ret.append(line);
0239             ret.append('\n');
0240         }
0241     }
0242     return ret;
0243 }
0244 
0245 std::unique_ptr<GLShader> ShaderManager::generateCustomShader(ShaderTraits traits, const QByteArray &vertexSource, const QByteArray &fragmentSource)
0246 {
0247     const auto vertex = preprocess(vertexSource.isEmpty() ? generateVertexSource(traits) : vertexSource);
0248     const auto fragment = preprocess(fragmentSource.isEmpty() ? generateFragmentSource(traits) : fragmentSource);
0249     if (!vertex || !fragment) {
0250         return nullptr;
0251     }
0252 
0253     std::unique_ptr<GLShader> shader{new GLShader(GLShader::ExplicitLinking)};
0254     shader->load(*vertex, *fragment);
0255 
0256     shader->bindAttributeLocation("position", VA_Position);
0257     shader->bindAttributeLocation("texcoord", VA_TexCoord);
0258     shader->bindFragDataLocation("fragColor", 0);
0259 
0260     shader->link();
0261     return shader;
0262 }
0263 
0264 static QString resolveShaderFilePath(const QString &filePath)
0265 {
0266     QString suffix;
0267     QString extension;
0268 
0269     const Version coreVersionNumber = GLPlatform::instance()->isGLES() ? Version(3, 0) : Version(1, 40);
0270     if (GLPlatform::instance()->glslVersion() >= coreVersionNumber) {
0271         suffix = QStringLiteral("_core");
0272     }
0273 
0274     if (filePath.endsWith(QStringLiteral(".frag"))) {
0275         extension = QStringLiteral(".frag");
0276     } else if (filePath.endsWith(QStringLiteral(".vert"))) {
0277         extension = QStringLiteral(".vert");
0278     } else {
0279         qCWarning(KWIN_OPENGL) << filePath << "must end either with .vert or .frag";
0280         return QString();
0281     }
0282 
0283     const QString prefix = filePath.chopped(extension.size());
0284     return prefix + suffix + extension;
0285 }
0286 
0287 std::unique_ptr<GLShader> ShaderManager::generateShaderFromFile(ShaderTraits traits, const QString &vertexFile, const QString &fragmentFile)
0288 {
0289     auto loadShaderFile = [](const QString &filePath) {
0290         QFile file(filePath);
0291         if (file.open(QIODevice::ReadOnly)) {
0292             return file.readAll();
0293         }
0294         qCCritical(KWIN_OPENGL) << "Failed to read shader " << filePath;
0295         return QByteArray();
0296     };
0297     QByteArray vertexSource;
0298     QByteArray fragmentSource;
0299     if (!vertexFile.isEmpty()) {
0300         vertexSource = loadShaderFile(resolveShaderFilePath(vertexFile));
0301         if (vertexSource.isEmpty()) {
0302             return std::unique_ptr<GLShader>(new GLShader());
0303         }
0304     }
0305     if (!fragmentFile.isEmpty()) {
0306         fragmentSource = loadShaderFile(resolveShaderFilePath(fragmentFile));
0307         if (fragmentSource.isEmpty()) {
0308             return std::unique_ptr<GLShader>(new GLShader());
0309         }
0310     }
0311     return generateCustomShader(traits, vertexSource, fragmentSource);
0312 }
0313 
0314 GLShader *ShaderManager::shader(ShaderTraits traits)
0315 {
0316     std::unique_ptr<GLShader> &shader = m_shaderHash[traits];
0317     if (!shader) {
0318         shader = generateShader(traits);
0319     }
0320     return shader.get();
0321 }
0322 
0323 GLShader *ShaderManager::getBoundShader() const
0324 {
0325     if (m_boundShaders.isEmpty()) {
0326         return nullptr;
0327     } else {
0328         return m_boundShaders.top();
0329     }
0330 }
0331 
0332 bool ShaderManager::isShaderBound() const
0333 {
0334     return !m_boundShaders.isEmpty();
0335 }
0336 
0337 GLShader *ShaderManager::pushShader(ShaderTraits traits)
0338 {
0339     GLShader *shader = this->shader(traits);
0340     pushShader(shader);
0341     return shader;
0342 }
0343 
0344 void ShaderManager::pushShader(GLShader *shader)
0345 {
0346     // only bind shader if it is not already bound
0347     if (shader != getBoundShader()) {
0348         shader->bind();
0349     }
0350     m_boundShaders.push(shader);
0351 }
0352 
0353 void ShaderManager::popShader()
0354 {
0355     if (m_boundShaders.isEmpty()) {
0356         return;
0357     }
0358     GLShader *shader = m_boundShaders.pop();
0359     if (m_boundShaders.isEmpty()) {
0360         // no more shader bound - unbind
0361         shader->unbind();
0362     } else if (shader != m_boundShaders.top()) {
0363         // only rebind if a different shader is on top of stack
0364         m_boundShaders.top()->bind();
0365     }
0366 }
0367 
0368 void ShaderManager::bindFragDataLocations(GLShader *shader)
0369 {
0370     shader->bindFragDataLocation("fragColor", 0);
0371 }
0372 
0373 void ShaderManager::bindAttributeLocations(GLShader *shader) const
0374 {
0375     shader->bindAttributeLocation("vertex", VA_Position);
0376     shader->bindAttributeLocation("texCoord", VA_TexCoord);
0377 }
0378 
0379 std::unique_ptr<GLShader> ShaderManager::loadShaderFromCode(const QByteArray &vertexSource, const QByteArray &fragmentSource)
0380 {
0381     std::unique_ptr<GLShader> shader{new GLShader(GLShader::ExplicitLinking)};
0382     shader->load(vertexSource, fragmentSource);
0383     bindAttributeLocations(shader.get());
0384     bindFragDataLocations(shader.get());
0385     shader->link();
0386     return shader;
0387 }
0388 
0389 }