1 /*
2 Copyright (c) 2018-2022 Timur Gafarov
3 
4 Boost Software License - Version 1.0 - August 17th, 2003
5 Permission is hereby granted, free of charge, to any person or organization
6 obtaining a copy of the software and accompanying documentation covered by
7 this license (the "Software") to use, reproduce, display, distribute,
8 execute, and transmit the Software, and to prepare derivative works of the
9 Software, and to permit third-parties to whom the Software is furnished to
10 do so, all subject to the following:
11 
12 The copyright notices in the Software and this entire statement, including
13 the above license grant, this restriction and the following disclaimer,
14 must be included in all copies of the Software, in whole or in part, and
15 all derivative works of the Software, unless such copies or derivative
16 works are solely in the form of machine-executable object code generated by
17 a source language processor.
18 
19 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
22 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
23 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
24 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25 DEALINGS IN THE SOFTWARE.
26 */
27 
28 module dagon.graphics.shader;
29 
30 import core.stdc.string;
31 import std.stdio;
32 import std.stdio;
33 import std.string;
34 import std.algorithm;
35 import std.file;
36 
37 import dlib.core.ownership;
38 import dlib.core.memory;
39 import dlib.container.array;
40 import dlib.container.dict;
41 import dlib.math.vector;
42 import dlib.math.matrix;
43 import dlib.math.utils: min2;
44 import dlib.image.color;
45 import dlib.filesystem.stdfs;
46 import dlib.text.str;
47 
48 import dagon.core.application;
49 import dagon.core.bindings;
50 import dagon.graphics.shaderloader;
51 import dagon.graphics.texture;
52 import dagon.graphics.state;
53 
54 // TODO: move to separate module
55 class MappedList(T): Owner
56 {
57     Array!T data;
58     Dict!(size_t, string) indices;
59 
60     this(Owner o)
61     {
62         super(o);
63         indices = New!(Dict!(size_t, string))();
64     }
65 
66     void set(string name, T val)
67     {
68         data.append(val);
69         indices[name] = data.length - 1;
70     }
71 
72     T get(string name)
73     {
74         return data[indices[name]];
75     }
76 
77     ~this()
78     {
79         data.free();
80         Delete(indices);
81     }
82 }
83 
84 /**
85    A shader program class that can be shared between multiple Shaders.
86  */
87 class ShaderProgram: Owner
88 {
89     immutable GLuint program;
90 
91     this(string vertexShaderSrc, string fragmentShaderSrc, Owner o)
92     {
93         super(o);
94 
95         GLuint vert = compileShader(vertexShaderSrc, ShaderStage.vertex);
96         GLuint frag = compileShader(fragmentShaderSrc, ShaderStage.fragment);
97         if (vert != 0 && frag != 0)
98             program = linkShaders(vert, frag);
99     }
100 
101     void bind()
102     {
103         glUseProgram(program);
104     }
105 
106     void unbind()
107     {
108         glUseProgram(0);
109     }
110 }
111 
112 /**
113    A shader class that wraps OpenGL shader creation and uniform initialization.
114  */
115 abstract class BaseShaderParameter: Owner
116 {
117     Shader shader;
118     string name;
119     GLint location;
120     bool autoBind = true;
121 
122     this(Shader shader, string name)
123     {
124         super(shader);
125         this.shader = shader;
126         this.name = name;
127     }
128 
129     void initUniform();
130     void bind();
131     void unbind();
132 }
133 
134 enum ShaderType
135 {
136     Vertex,
137     Fragment
138 }
139 
140 class ShaderSubroutine: BaseShaderParameter
141 {
142     ShaderType shaderType;
143     GLint location;
144     GLuint index;
145     string subroutineName;
146 
147     this(Shader shader, ShaderType shaderType, string name, string subroutineName)
148     {
149         super(shader, name);
150         this.shaderType = shaderType;
151         this.subroutineName = subroutineName;
152         initUniform();
153     }
154 
155     override void initUniform()
156     {
157         if (shaderType == ShaderType.Vertex)
158         {
159             location = glGetSubroutineUniformLocation(shader.program.program, GL_VERTEX_SHADER, toStringz(name));
160             index = glGetSubroutineIndex(shader.program.program, GL_VERTEX_SHADER, toStringz(subroutineName));
161         }
162         else if (shaderType == ShaderType.Fragment)
163         {
164             location = glGetSubroutineUniformLocation(shader.program.program, GL_FRAGMENT_SHADER, toStringz(name));
165             index = glGetSubroutineIndex(shader.program.program, GL_FRAGMENT_SHADER, toStringz(subroutineName));
166         }
167     }
168 
169     override void bind()
170     {
171         if (shaderType == ShaderType.Vertex)
172         {
173             if (location != -1)
174                 shader.vertexSubroutineIndices[location] = index;
175         }
176         else if (shaderType == ShaderType.Fragment)
177         {
178             if (location != -1)
179                 shader.fragmentSubroutineIndices[location] = index;
180         }
181     }
182 
183     override void unbind()
184     {
185     }
186 }
187 
188 class ShaderParameter(T): BaseShaderParameter
189 if (is(T == bool) ||
190     is(T == int) ||
191     is(T == float) ||
192     is(T == Vector2f) ||
193     is(T == Vector3f) ||
194     is(T == Vector4f) ||
195     is(T == Color4f) ||
196     is(T == Matrix4x4f))
197 {
198     T* source;
199     T value;
200     T delegate() callback;
201 
202     this(Shader shader, string name, T* source)
203     {
204         super(shader, name);
205         this.source = source;
206         this.callback = null;
207         initUniform();
208     }
209 
210     this(Shader shader, string name, T value)
211     {
212         super(shader, name);
213         this.source = null;
214         this.value = value;
215         this.callback = null;
216         initUniform();
217     }
218 
219     this(Shader shader, string name, T delegate() callback)
220     {
221         super(shader, name);
222         this.source = null;
223         this.value = value;
224         this.callback = callback;
225         initUniform();
226     }
227 
228     override void initUniform()
229     {
230         location = glGetUniformLocation(shader.program.program, toStringz(name));
231     }
232 
233     override void bind()
234     {
235         if (callback)
236             value = callback();
237         else if (source)
238             value = *source;
239 
240         static if (is(T == bool) || is(T == int))
241         {
242             glUniform1i(location, value);
243         }
244         else static if (is(T == float))
245         {
246             glUniform1f(location, value);
247         }
248         else static if (is(T == Vector2f))
249         {
250             glUniform2fv(location, 1, value.arrayof.ptr);
251         }
252         else static if (is(T == Vector3f))
253         {
254             glUniform3fv(location, 1, value.arrayof.ptr);
255         }
256         else static if (is(T == Vector4f))
257         {
258             glUniform4fv(location, 1, value.arrayof.ptr);
259         }
260         else static if (is(T == Color4f))
261         {
262             glUniform4fv(location, 1, value.arrayof.ptr);
263         }
264         else static if (is(T == Matrix4x4f))
265         {
266             glUniformMatrix4fv(location, 1, GL_FALSE, value.arrayof.ptr);
267         }
268     }
269 
270     override void unbind()
271     {
272         //TODO
273     }
274 }
275 
276 class Shader: Owner
277 {
278     ShaderProgram program;
279     MappedList!BaseShaderParameter parameters;
280     GLuint[] vertexSubroutineIndices;
281     GLuint[] fragmentSubroutineIndices;
282 
283     this(ShaderProgram program, Owner o)
284     {
285         super(o);
286         this.program = program;
287         this.parameters = New!(MappedList!BaseShaderParameter)(this);
288     }
289 
290     static String load(string filename)
291     {
292         if (!exists(filename))
293         {
294             exitWithError("Error: shader \"" ~ filename ~ "\" not found!");
295         }
296         
297         auto fs = New!StdFileSystem();
298         auto istrm = fs.openForInput(filename);
299         string inputText = readText(istrm);
300         Delete(istrm);
301 
302         string includePath = "data/__internal/shaders/include/";
303         String outputText;
304         foreach(line; lineSplitter(inputText))
305         {
306             auto s = line.strip;
307             if (s.startsWith("#include"))
308             {
309                 char[64] buf;
310                 if (sscanf(s.ptr, "#include <%s>", buf.ptr) == 1)
311                 {
312                     string includeFilename = cast(string)buf[0..strlen(buf.ptr)-1];
313                     String includeFullPath = includePath;
314                     includeFullPath ~= includeFilename;
315                     if (exists(includeFullPath))
316                     {
317                         istrm = fs.openForInput(includeFullPath.toString);
318                         string includeText = readText(istrm);
319                         Delete(istrm);
320                         outputText ~= includeText;
321                         outputText ~= "\n";
322                         Delete(includeText);
323                     }
324                     includeFullPath.free();
325                 }
326                 else
327                 {
328                     writeln("Error");
329                     break;
330                 }
331             }
332             else
333             {
334                 outputText ~= line;
335                 outputText ~= "\n";
336             }
337         }
338 
339         Delete(fs);
340         Delete(inputText);
341 
342         return outputText;
343     }
344 
345     ShaderSubroutine setParameterSubroutine(string name, ShaderType shaderType, string subroutineName)
346     {
347         if (name in parameters.indices)
348         {
349             auto sp = cast(ShaderSubroutine)parameters.get(name);
350             if (sp is null)
351             {
352                 writefln("Warning: type mismatch for shader parameter \"%s\"", name);
353                 return null;
354             }
355             sp.shaderType = shaderType;
356             sp.subroutineName = subroutineName;
357             sp.initUniform();
358             return sp;
359         }
360         else
361         {
362             auto sp = New!ShaderSubroutine(this, shaderType, name, subroutineName);
363             parameters.set(name, sp);
364             return sp;
365         }
366     }
367 
368     ShaderParameter!T setParameter(T)(string name, T val)
369     {
370         if (name in parameters.indices)
371         {
372             auto sp = cast(ShaderParameter!T)parameters.get(name);
373             if (sp is null)
374             {
375                 writefln("Warning: type mismatch for shader parameter \"%s\"", name);
376                 return null;
377             }
378 
379             sp.value = val;
380             sp.source = null;
381             return sp;
382         }
383         else
384         {
385             auto sp = New!(ShaderParameter!T)(this, name, val);
386             parameters.set(name, sp);
387             return sp;
388         }
389     }
390 
391     ShaderParameter!T setParameterRef(T)(string name, ref T val)
392     {
393         if (name in parameters.indices)
394         {
395             auto sp = cast(ShaderParameter!T)parameters.get(name);
396             if (sp is null)
397             {
398                 writefln("Warning: type mismatch for shader parameter \"%s\"", name);
399                 return null;
400             }
401 
402             sp.source = &val;
403             return sp;
404         }
405         else
406         {
407             auto sp = New!(ShaderParameter!T)(this, name, &val);
408             parameters.set(name, sp);
409             return sp;
410         }
411     }
412 
413     ShaderParameter!T setParameterCallback(T)(string name, T delegate() val)
414     {
415         if (name in parameters.indices)
416         {
417             auto sp = cast(ShaderParameter!T)parameters.get(name);
418             if (sp is null)
419             {
420                 writefln("Warning: type mismatch for shader parameter \"%s\"", name);
421                 return null;
422             }
423 
424             sp.callback = val;
425             return sp;
426         }
427         else
428         {
429             auto sp = New!(ShaderParameter!T)(this, name, val);
430             parameters.set(name, sp);
431             return sp;
432         }
433     }
434 
435     BaseShaderParameter getParameter(string name)
436     {
437         if (name in parameters.indices)
438         {
439             return parameters.get(name);
440         }
441         else
442         {
443             writefln("Warning: unknown shader parameter \"%s\"", name);
444             return null;
445         }
446     }
447 
448     T getParameterValue(T)(string name)
449     {
450         if (name in parameters.indices)
451         {
452             auto sp = cast(ShaderParameter!T)parameters.get(name);
453             if (sp is null)
454             {
455                 writefln("Warning: type mismatch for shader parameter \"%s\"", name);
456                 return T.init;
457             }
458 
459             if (sp.source)
460                 return *sp.source;
461             else
462                 return sp.value;
463         }
464         else
465         {
466             writefln("Warning: unknown shader parameter \"%s\"", name);
467             return T.init;
468         }
469     }
470 
471     void bind()
472     {
473         program.bind();
474     }
475 
476     void unbind()
477     {
478         program.unbind();
479     }
480 
481     void bindParameters(GraphicsState* state)
482     {
483         GLsizei n;
484         glGetProgramStageiv(program.program, GL_VERTEX_SHADER, GL_ACTIVE_SUBROUTINE_UNIFORM_LOCATIONS, &n);
485         if (n > 0 && n != vertexSubroutineIndices.length)
486             vertexSubroutineIndices = New!(GLuint[])(n);
487 
488         glGetProgramStageiv(program.program, GL_FRAGMENT_SHADER, GL_ACTIVE_SUBROUTINE_UNIFORM_LOCATIONS, &n);
489         if (n > 0 && n != fragmentSubroutineIndices.length)
490             fragmentSubroutineIndices = New!(GLuint[])(n);
491 
492         foreach(v; parameters.data)
493         {
494             if (v.autoBind)
495                 v.bind();
496         }
497 
498         if (vertexSubroutineIndices.length)
499             glUniformSubroutinesuiv(GL_VERTEX_SHADER, cast(uint)vertexSubroutineIndices.length, vertexSubroutineIndices.ptr);
500 
501         if (fragmentSubroutineIndices.length)
502             glUniformSubroutinesuiv(GL_FRAGMENT_SHADER, cast(uint)fragmentSubroutineIndices.length, fragmentSubroutineIndices.ptr);
503         
504         debug validate();
505     }
506 
507     void unbindParameters(GraphicsState* state)
508     {
509         foreach(v; parameters.data)
510         {
511             if (v.autoBind)
512                 v.unbind();
513         }
514     }
515 
516     void validate()
517     {
518         glValidateProgram(program.program);
519         
520         GLint status;
521         glGetProgramiv(program.program, GL_VALIDATE_STATUS, &status);
522         
523         if (status != GL_TRUE)
524         {
525             GLint infolen;
526             glGetProgramiv(program.program, GL_INFO_LOG_LENGTH, &infolen);
527             if (infolen > 0)
528             {
529                 char[logMaxLen + 1] infobuffer = 0;
530                 glGetProgramInfoLog(program.program, logMaxLen, null, infobuffer.ptr);
531                 infolen = min2(infolen - 1, logMaxLen);
532                 char[] s = stripRight(infobuffer[0..infolen]);
533                 writeln(s);
534             }
535         }
536         assert(status == GL_TRUE, "Shader program validation failed");
537     }
538     
539     ~this()
540     {
541         if (vertexSubroutineIndices.length)
542             Delete(vertexSubroutineIndices);
543         if (fragmentSubroutineIndices.length)
544             Delete(fragmentSubroutineIndices);
545     }
546 }