File indexing completed on 2024-11-10 04:56: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 }