KWin
Loading...
Searching...
No Matches
glshadermanager.cpp
Go to the documentation of this file.
1/*
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5 SPDX-FileCopyrightText: 2006-2007 Rivo Laks <rivolaks@hot.ee>
6 SPDX-FileCopyrightText: 2010, 2011 Martin Gräßlin <mgraesslin@kde.org>
7 SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@kde.org>
8
9 SPDX-License-Identifier: GPL-2.0-or-later
10*/
11#include "glshadermanager.h"
12#include "glplatform.h"
13#include "glshader.h"
14#include "glvertexbuffer.h"
15#include "utils/common.h"
16
17#include <QFile>
18#include <QTextStream>
19
20namespace KWin
21{
22
23std::unique_ptr<ShaderManager> ShaderManager::s_shaderManager;
24
26{
27 if (!s_shaderManager) {
28 s_shaderManager = std::make_unique<ShaderManager>();
29 }
30 return s_shaderManager.get();
31}
32
34{
35 s_shaderManager.reset();
36}
37
41
43{
44 while (!m_boundShaders.isEmpty()) {
45 popShader();
46 }
47}
48
49QByteArray ShaderManager::generateVertexSource(ShaderTraits traits) const
50{
51 QByteArray source;
52 QTextStream stream(&source);
53
54 GLPlatform *const gl = GLPlatform::instance();
55 QByteArray attribute, varying;
56
57 if (!gl->isGLES()) {
58 const bool glsl_140 = gl->glslVersion() >= Version(1, 40);
59
60 attribute = glsl_140 ? QByteArrayLiteral("in") : QByteArrayLiteral("attribute");
61 varying = glsl_140 ? QByteArrayLiteral("out") : QByteArrayLiteral("varying");
62
63 if (glsl_140) {
64 stream << "#version 140\n\n";
65 }
66 } else {
67 const bool glsl_es_300 = gl->glslVersion() >= Version(3, 0);
68
69 attribute = glsl_es_300 ? QByteArrayLiteral("in") : QByteArrayLiteral("attribute");
70 varying = glsl_es_300 ? QByteArrayLiteral("out") : QByteArrayLiteral("varying");
71
72 if (glsl_es_300) {
73 stream << "#version 300 es\n\n";
74 }
75 }
76
77 stream << attribute << " vec4 position;\n";
79 stream << attribute << " vec4 texcoord;\n\n";
80 stream << varying << " vec2 texcoord0;\n\n";
81 } else {
82 stream << "\n";
83 }
84
85 stream << "uniform mat4 modelViewProjectionMatrix;\n\n";
86
87 stream << "void main()\n{\n";
89 stream << " texcoord0 = texcoord.st;\n";
90 }
91
92 stream << " gl_Position = modelViewProjectionMatrix * position;\n";
93 stream << "}\n";
94
95 stream.flush();
96 return source;
97}
98
99QByteArray ShaderManager::generateFragmentSource(ShaderTraits traits) const
100{
101 QByteArray source;
102 QTextStream stream(&source);
103
104 GLPlatform *const gl = GLPlatform::instance();
105 QByteArray varying, output, textureLookup;
106
107 if (!gl->isGLES()) {
108 const bool glsl_140 = gl->glslVersion() >= Version(1, 40);
109
110 if (glsl_140) {
111 stream << "#version 140\n\n";
112 }
113
114 varying = glsl_140 ? QByteArrayLiteral("in") : QByteArrayLiteral("varying");
115 textureLookup = glsl_140 ? QByteArrayLiteral("texture") : QByteArrayLiteral("texture2D");
116 output = glsl_140 ? QByteArrayLiteral("fragColor") : QByteArrayLiteral("gl_FragColor");
117 } else {
118 const bool glsl_es_300 = GLPlatform::instance()->glslVersion() >= Version(3, 0);
119
120 if (glsl_es_300) {
121 stream << "#version 300 es\n\n";
122 }
123
124 // From the GLSL ES specification:
125 //
126 // "The fragment language has no default precision qualifier for floating point types."
127 stream << "precision highp float;\n\n";
128
129 varying = glsl_es_300 ? QByteArrayLiteral("in") : QByteArrayLiteral("varying");
130 textureLookup = glsl_es_300 ? QByteArrayLiteral("texture") : QByteArrayLiteral("texture2D");
131 output = glsl_es_300 ? QByteArrayLiteral("fragColor") : QByteArrayLiteral("gl_FragColor");
132 }
133
134 if (traits & ShaderTrait::MapTexture) {
135 stream << "uniform sampler2D sampler;\n";
136 stream << "uniform sampler2D sampler1;\n";
137 stream << "uniform int converter;\n";
138 stream << varying << " vec2 texcoord0;\n";
139 } else if (traits & ShaderTrait::MapExternalTexture) {
140 stream << "#extension GL_OES_EGL_image_external : require\n\n";
141 stream << "uniform samplerExternalOES sampler;\n";
142 stream << varying << " vec2 texcoord0;\n";
143 } else if (traits & ShaderTrait::UniformColor) {
144 stream << "uniform vec4 geometryColor;\n";
145 }
146 if (traits & ShaderTrait::Modulate) {
147 stream << "uniform vec4 modulation;\n";
148 }
149 if (traits & ShaderTrait::AdjustSaturation) {
150 stream << "#include \"saturation.glsl\"\n";
151 }
153 stream << "#include \"colormanagement.glsl\"\n";
154 }
155
156 if (output != QByteArrayLiteral("gl_FragColor")) {
157 stream << "\nout vec4 " << output << ";\n";
158 }
159
160 if (traits & ShaderTrait::MapTexture) {
161 // limited range BT601 in -> full range BT709 out
162 stream << "vec4 transformY_UV(sampler2D tex0, sampler2D tex1, vec2 texcoord0) {\n";
163 stream << " float y = 1.16438356 * (" << textureLookup << "(tex0, texcoord0).x - 0.0625);\n";
164 stream << " float u = " << textureLookup << "(tex1, texcoord0).r - 0.5;\n";
165 stream << " float v = " << textureLookup << "(tex1, texcoord0).g - 0.5;\n";
166 stream << " return vec4(y + 1.59602678 * v"
167 " , y - 0.39176229 * u - 0.81296764 * v"
168 " , y + 2.01723214 * u"
169 " , 1);\n";
170 stream << "}\n";
171 stream << "\n";
172 }
173
174 stream << "\nvoid main(void)\n{\n";
175 stream << " vec4 result;\n";
176 if (traits & ShaderTrait::MapTexture) {
177 stream << " if (converter == 0) {\n";
178 stream << " result = " << textureLookup << "(sampler, texcoord0);\n";
179 stream << " } else {\n";
180 stream << " result = transformY_UV(sampler, sampler1, texcoord0);\n";
181 stream << " }\n";
182 } else if (traits & ShaderTrait::MapExternalTexture) {
183 // external textures require texture2D for sampling
184 stream << " result = texture2D(sampler, texcoord0);\n";
185 } else if (traits & ShaderTrait::UniformColor) {
186 stream << " result = geometryColor;\n";
187 }
189 stream << " result = sourceEncodingToNitsInDestinationColorspace(result);\n";
190 }
191 if (traits & ShaderTrait::AdjustSaturation) {
192 stream << " result = adjustSaturation(result);\n";
193 }
194 if (traits & ShaderTrait::Modulate) {
195 stream << " result *= modulation;\n";
196 }
198 stream << " result = nitsToDestinationEncoding(result);\n";
199 }
200
201 stream << " " << output << " = result;\n";
202 stream << "}";
203 stream.flush();
204 return source;
205}
206
207std::unique_ptr<GLShader> ShaderManager::generateShader(ShaderTraits traits)
208{
209 return generateCustomShader(traits);
210}
211
212std::optional<QByteArray> ShaderManager::preprocess(const QByteArray &src, int recursionDepth) const
213{
214 recursionDepth++;
215 if (recursionDepth > 10) {
216 qCWarning(KWIN_OPENGL, "shader has too many recursive includes!");
217 return std::nullopt;
218 }
219 QByteArray ret;
220 ret.reserve(src.size());
221 const auto split = src.split('\n');
222 for (auto it = split.begin(); it != split.end(); it++) {
223 const auto &line = *it;
224 if (line.startsWith("#include \"") && line.endsWith("\"")) {
225 static constexpr ssize_t includeLength = QByteArrayView("#include \"").size();
226 const QByteArray path = ":/opengl/" + line.mid(includeLength, line.size() - includeLength - 1);
227 QFile file(path);
228 if (!file.open(QIODevice::ReadOnly)) {
229 qCWarning(KWIN_OPENGL, "failed to read include line %s", qPrintable(line));
230 return std::nullopt;
231 }
232 const auto processed = preprocess(file.readAll(), recursionDepth);
233 if (!processed) {
234 return std::nullopt;
235 }
236 ret.append(*processed);
237 } else {
238 ret.append(line);
239 ret.append('\n');
240 }
241 }
242 return ret;
243}
244
245std::unique_ptr<GLShader> ShaderManager::generateCustomShader(ShaderTraits traits, const QByteArray &vertexSource, const QByteArray &fragmentSource)
246{
247 const auto vertex = preprocess(vertexSource.isEmpty() ? generateVertexSource(traits) : vertexSource);
248 const auto fragment = preprocess(fragmentSource.isEmpty() ? generateFragmentSource(traits) : fragmentSource);
249 if (!vertex || !fragment) {
250 return nullptr;
251 }
252
253 std::unique_ptr<GLShader> shader{new GLShader(GLShader::ExplicitLinking)};
254 shader->load(*vertex, *fragment);
255
258 shader->bindFragDataLocation("fragColor", 0);
259
260 shader->link();
261 return shader;
262}
263
264static QString resolveShaderFilePath(const QString &filePath)
265{
266 QString suffix;
267 QString extension;
268
269 const Version coreVersionNumber = GLPlatform::instance()->isGLES() ? Version(3, 0) : Version(1, 40);
270 if (GLPlatform::instance()->glslVersion() >= coreVersionNumber) {
271 suffix = QStringLiteral("_core");
272 }
273
274 if (filePath.endsWith(QStringLiteral(".frag"))) {
275 extension = QStringLiteral(".frag");
276 } else if (filePath.endsWith(QStringLiteral(".vert"))) {
277 extension = QStringLiteral(".vert");
278 } else {
279 qCWarning(KWIN_OPENGL) << filePath << "must end either with .vert or .frag";
280 return QString();
281 }
282
283 const QString prefix = filePath.chopped(extension.size());
284 return prefix + suffix + extension;
285}
286
287std::unique_ptr<GLShader> ShaderManager::generateShaderFromFile(ShaderTraits traits, const QString &vertexFile, const QString &fragmentFile)
288{
289 auto loadShaderFile = [](const QString &filePath) {
290 QFile file(filePath);
291 if (file.open(QIODevice::ReadOnly)) {
292 return file.readAll();
293 }
294 qCCritical(KWIN_OPENGL) << "Failed to read shader " << filePath;
295 return QByteArray();
296 };
297 QByteArray vertexSource;
298 QByteArray fragmentSource;
299 if (!vertexFile.isEmpty()) {
300 vertexSource = loadShaderFile(resolveShaderFilePath(vertexFile));
301 if (vertexSource.isEmpty()) {
302 return std::unique_ptr<GLShader>(new GLShader());
303 }
304 }
305 if (!fragmentFile.isEmpty()) {
306 fragmentSource = loadShaderFile(resolveShaderFilePath(fragmentFile));
307 if (fragmentSource.isEmpty()) {
308 return std::unique_ptr<GLShader>(new GLShader());
309 }
310 }
311 return generateCustomShader(traits, vertexSource, fragmentSource);
312}
313
314GLShader *ShaderManager::shader(ShaderTraits traits)
315{
316 std::unique_ptr<GLShader> &shader = m_shaderHash[traits];
317 if (!shader) {
318 shader = generateShader(traits);
319 }
320 return shader.get();
321}
322
324{
325 if (m_boundShaders.isEmpty()) {
326 return nullptr;
327 } else {
328 return m_boundShaders.top();
329 }
330}
331
333{
334 return !m_boundShaders.isEmpty();
335}
336
338{
339 GLShader *shader = this->shader(traits);
341 return shader;
342}
343
345{
346 // only bind shader if it is not already bound
347 if (shader != getBoundShader()) {
348 shader->bind();
349 }
350 m_boundShaders.push(shader);
351}
352
354{
355 if (m_boundShaders.isEmpty()) {
356 return;
357 }
358 GLShader *shader = m_boundShaders.pop();
359 if (m_boundShaders.isEmpty()) {
360 // no more shader bound - unbind
361 shader->unbind();
362 } else if (shader != m_boundShaders.top()) {
363 // only rebind if a different shader is on top of stack
364 m_boundShaders.top()->bind();
365 }
366}
367
368void ShaderManager::bindFragDataLocations(GLShader *shader)
369{
370 shader->bindFragDataLocation("fragColor", 0);
371}
372
373void ShaderManager::bindAttributeLocations(GLShader *shader) const
374{
377}
378
379std::unique_ptr<GLShader> ShaderManager::loadShaderFromCode(const QByteArray &vertexSource, const QByteArray &fragmentSource)
380{
381 std::unique_ptr<GLShader> shader{new GLShader(GLShader::ExplicitLinking)};
382 shader->load(vertexSource, fragmentSource);
383 bindAttributeLocations(shader.get());
384 bindFragDataLocations(shader.get());
385 shader->link();
386 return shader;
387}
388
389}
Version glslVersion() const
static GLPlatform * instance()
Definition glplatform.h:394
bool isGLES() const
bool load(const QByteArray &vertexSource, const QByteArray &fragmentSource)
Definition glshader.cpp:153
void bindAttributeLocation(const char *name, int index)
Definition glshader.cpp:183
void bindFragDataLocation(const char *name, int index)
Definition glshader.cpp:188
Manager for Shaders.
GLShader * getBoundShader() const
std::unique_ptr< GLShader > generateShaderFromFile(ShaderTraits traits, const QString &vertexFile=QString(), const QString &fragmentFile=QString())
GLShader * shader(ShaderTraits traits)
static ShaderManager * instance()
std::unique_ptr< GLShader > generateCustomShader(ShaderTraits traits, const QByteArray &vertexSource=QByteArray(), const QByteArray &fragmentSource=QByteArray())
std::unique_ptr< GLShader > loadShaderFromCode(const QByteArray &vertexSource, const QByteArray &fragmentSource)
GLShader * pushShader(ShaderTraits traits)
@ VA_Position
@ VA_TexCoord