1 /* This file is part of the KDE project
2  * Copyright (C) Julian Thijssen <julianthijssen@gmail.com>, (C) 2016
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  */
18 
19 #include "kis_opengl_shader_loader.h"
20 
21 #include "opengl/kis_opengl.h"
22 #include "kis_config.h"
23 
24 #include <QFile>
25 #include <QMessageBox>
26 #include <KLocalizedString>
27 
28 #define PROGRAM_VERTEX_ATTRIBUTE 0
29 #define PROGRAM_TEXCOORD_ATTRIBUTE 1
30 
31 // Mapping of uniforms to uniform names
32 std::map<Uniform, const char *> KisShaderProgram::names = {
33    {ModelViewProjection, "modelViewProjection"},
34    {TextureMatrix, "textureMatrix"},
35    {ViewportScale, "viewportScale"},
36    {TexelSize, "texelSize"},
37    {Texture0, "texture0"},
38    {Texture1, "texture1"},
39    {FixedLodLevel, "fixedLodLevel"},
40    {FragmentColor, "fragColor"}
41 };
42 
43 /**
44  * Generic shader loading function that will compile a shader program given
45  * a vertex shader and fragment shader resource path. Extra code can be prepended
46  * to each shader respectively using the header parameters.
47  *
48  * @param vertPath Resource path to a vertex shader
49  * @param fragPath Resource path to a fragment shader
50  * @param vertHeader Extra code which will be prepended to the vertex shader
51  * @param fragHeader Extra code which will be prepended to the fragment shader
52  */
loadShader(QString vertPath,QString fragPath,QByteArray vertHeader,QByteArray fragHeader)53 KisShaderProgram *KisOpenGLShaderLoader::loadShader(QString vertPath, QString fragPath,
54                                                     QByteArray vertHeader, QByteArray fragHeader)
55 {
56     bool result;
57 
58     KisShaderProgram *shader = new KisShaderProgram();
59 
60     // Load vertex shader
61     QByteArray vertSource;
62 
63 // XXX Check can be removed and set to the MAC version after we move to Qt5.7
64 #ifdef Q_OS_MACOS
65     vertSource.append(KisOpenGL::hasOpenGL3() ? "#version 150 core\n" : "#version 120\n");
66     // OpenColorIO doesn't support the new GLSL version yet.
67     vertSource.append("#define texture2D texture\n");
68     vertSource.append("#define texture3D texture\n");
69 #else
70     if (KisOpenGL::hasOpenGLES()) {
71         vertSource.append("#version 300 es\n");
72     } else {
73         vertSource.append(KisOpenGL::supportsLoD() ? "#version 130\n" : "#version 120\n");
74     }
75 #endif
76     vertSource.append(vertHeader);
77     QFile vertexShaderFile(":/" + vertPath);
78     vertexShaderFile.open(QIODevice::ReadOnly);
79     vertSource.append(vertexShaderFile.readAll());
80 
81     result = shader->addShaderFromSourceCode(QOpenGLShader::Vertex, vertSource);
82     if (!result)
83         throw ShaderLoaderException(QString("%1: %2 - Cause: %3").arg("Failed to add vertex shader source from file", vertPath, shader->log()));
84 
85     // Load fragment shader
86     QByteArray fragSource;
87 
88 // XXX Check can be removed and set to the MAC version after we move to Qt5.7
89 #ifdef Q_OS_MACOS
90     fragSource.append(KisOpenGL::hasOpenGL3() ? "#version 150 core\n" : "#version 120\n");
91     // OpenColorIO doesn't support the new GLSL version yet.
92     fragSource.append("#define texture2D texture\n");
93     fragSource.append("#define texture3D texture\n");
94 #else
95     if (KisOpenGL::hasOpenGLES()) {
96         fragSource.append(
97                     "#version 300 es\n"
98                     "precision mediump float;\n"
99                     "precision mediump sampler3D;\n");
100 
101         // OpenColorIO doesn't support the new GLSL version yet.
102         fragSource.append("#define texture2D texture\n");
103         fragSource.append("#define texture3D texture\n");
104     } else {
105         fragSource.append(KisOpenGL::supportsLoD() ? "#version 130\n" : "#version 120\n");
106     }
107 #endif
108     fragSource.append(fragHeader);
109     QFile fragmentShaderFile(":/" + fragPath);
110     fragmentShaderFile.open(QIODevice::ReadOnly);
111     fragSource.append(fragmentShaderFile.readAll());
112 
113     result = shader->addShaderFromSourceCode(QOpenGLShader::Fragment, fragSource);
114     if (!result)
115         throw ShaderLoaderException(QString("%1: %2 - Cause: %3").arg("Failed to add fragment shader source from file", fragPath, shader->log()));
116 
117     // Bind attributes
118     shader->bindAttributeLocation("a_vertexPosition", PROGRAM_VERTEX_ATTRIBUTE);
119     shader->bindAttributeLocation("a_textureCoordinate", PROGRAM_TEXCOORD_ATTRIBUTE);
120 
121     // Link
122     result = shader->link();
123     if (!result)
124         throw ShaderLoaderException(QString("Failed to link shader: ").append(vertPath));
125 
126     Q_ASSERT(shader->isLinked());
127 
128     return shader;
129 }
130 
131 /**
132  * Specific display shader loading function. It adds the appropriate extra code
133  * to the fragment shader depending on what is available on the target machine.
134  * Additionally, it picks the appropriate shader files depending on the availability
135  * of OpenGL3.
136  */
loadDisplayShader(QSharedPointer<KisDisplayFilter> displayFilter,bool useHiQualityFiltering)137 KisShaderProgram *KisOpenGLShaderLoader::loadDisplayShader(QSharedPointer<KisDisplayFilter> displayFilter, bool useHiQualityFiltering)
138 {
139     QByteArray fragHeader;
140 
141     if (KisOpenGL::supportsLoD()) {
142         fragHeader.append("#define DIRECT_LOD_FETCH\n");
143         if (useHiQualityFiltering) {
144             fragHeader.append("#define HIGHQ_SCALING\n");
145         }
146     }
147 
148     // If we have an OCIO display filter and it contains a function we add
149     // it to our shader header which will sit on top of the fragment code.
150     bool haveDisplayFilter = displayFilter && !displayFilter->program().isEmpty();
151     if (haveDisplayFilter) {
152         fragHeader.append("#define USE_OCIO\n");
153         fragHeader.append(displayFilter->program().toLatin1());
154     }
155 
156     QString vertPath, fragPath;
157     // Select appropriate shader files
158     if (KisOpenGL::supportsLoD()) {
159         vertPath = "matrix_transform.vert";
160         fragPath = "highq_downscale.frag";
161     } else {
162         vertPath = "matrix_transform_legacy.vert";
163         fragPath = "simple_texture_legacy.frag";
164     }
165 
166     KisShaderProgram *shader = loadShader(vertPath, fragPath, QByteArray(), fragHeader);
167 
168     return shader;
169 }
170 
171 /**
172  * Specific checker shader loading function. It picks the appropriate shader
173  * files depending on the availability of OpenGL3 on the target machine.
174  */
loadCheckerShader()175 KisShaderProgram *KisOpenGLShaderLoader::loadCheckerShader()
176 {
177     QString vertPath, fragPath;
178     // Select appropriate shader files
179     if (KisOpenGL::supportsLoD()) {
180         vertPath = "matrix_transform.vert";
181         fragPath = "simple_texture.frag";
182     } else {
183         vertPath = "matrix_transform_legacy.vert";
184         fragPath = "simple_texture_legacy.frag";
185     }
186 
187     KisShaderProgram *shader = loadShader(vertPath, fragPath, QByteArray(), QByteArray());
188 
189     return shader;
190 }
191 
192 /**
193  * Specific uniform shader loading function. It picks the appropriate shader
194  * files depending on the availability of OpenGL3 on the target machine.
195  */
loadSolidColorShader()196 KisShaderProgram *KisOpenGLShaderLoader::loadSolidColorShader()
197 {
198     QString vertPath, fragPath;
199     // Select appropriate shader files
200     if (KisOpenGL::supportsLoD()) {
201         vertPath = "matrix_transform.vert";
202         fragPath = "solid_color.frag";
203     } else {
204         vertPath = "matrix_transform_legacy.vert";
205         fragPath = "solid_color_legacy.frag";
206     }
207 
208     KisShaderProgram *shader = loadShader(vertPath, fragPath, QByteArray(), QByteArray());
209 
210     return shader;
211 }
212 
loadOverlayInvertedShader()213 KisShaderProgram *KisOpenGLShaderLoader::loadOverlayInvertedShader()
214 {
215     QString vertPath, fragPath;
216 
217     // Select appropriate shader files
218     if (KisOpenGL::supportsLoD() || KisOpenGL::hasOpenGLES()) {
219         vertPath = "matrix_transform.vert";
220 
221         if (KisOpenGL::useFBOForToolOutlineRendering()) {
222             fragPath = "overlay_inverted.frag";
223         } else {
224             fragPath = "solid_color.frag";
225         }
226     } else {
227         vertPath = "matrix_transform_legacy.vert";
228         fragPath = "solid_color_legacy.frag";
229     }
230 
231     KisShaderProgram *shader = loadShader(vertPath, fragPath, QByteArray(), QByteArray());
232 
233     return shader;
234 }
235