1 /*
2 Copyright (c) 2018-2020 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.image.color;
44 import dlib.filesystem.stdfs;
45 import dlib.text.str;
46 
47 import dagon.core.bindings;
48 import dagon.graphics.shaderloader;
49 import dagon.graphics.texture;
50 import dagon.graphics.state;
51 
52 // TODO: move to separate module
53 class MappedList(T): Owner
54 {
55     Array!T data;
56     Dict!(size_t, string) indices;
57 
58     this(Owner o)
59     {
60         super(o);
61         indices = New!(Dict!(size_t, string))();
62     }
63 
64     void set(string name, T val)
65     {
66         data.append(val);
67         indices[name] = data.length - 1;
68     }
69 
70     T get(string name)
71     {
72         return data[indices[name]];
73     }
74 
75     ~this()
76     {
77         data.free();
78         Delete(indices);
79     }
80 }
81 
82 /**
83    A shader program class that can be shared between multiple Shaders.
84  */
85 class ShaderProgram: Owner
86 {
87     immutable GLuint program;
88 
89     this(string vertexShaderSrc, string fragmentShaderSrc, Owner o)
90     {
91         super(o);
92 
93         GLuint vert = compileShader(vertexShaderSrc, ShaderStage.vertex);
94         GLuint frag = compileShader(fragmentShaderSrc, ShaderStage.fragment);
95         if (vert != 0 && frag != 0)
96             program = linkShaders(vert, frag);
97     }
98 
99     void bind()
100     {
101         glUseProgram(program);
102     }
103 
104     void unbind()
105     {
106         glUseProgram(0);
107     }
108 }
109 
110 /**
111    A shader class that wraps OpenGL shader creation and uniform initialization.
112  */
113 abstract class BaseShaderParameter: Owner
114 {
115     Shader shader;
116     string name;
117     GLint location;
118     bool autoBind = true;
119 
120     this(Shader shader, string name)
121     {
122         super(shader);
123         this.shader = shader;
124         this.name = name;
125     }
126 
127     void initUniform();
128     void bind();
129     void unbind();
130 }
131 
132 enum ShaderType
133 {
134     Vertex,
135     Fragment
136 }
137 
138 class ShaderSubroutine: BaseShaderParameter
139 {
140     ShaderType shaderType;
141     GLint location;
142     GLuint index;
143     string subroutineName;
144 
145     this(Shader shader, ShaderType shaderType, string name, string subroutineName)
146     {
147         super(shader, name);
148         this.shaderType = shaderType;
149         this.subroutineName = subroutineName;
150         initUniform();
151     }
152 
153     override void initUniform()
154     {
155         if (shaderType == ShaderType.Vertex)
156         {
157             location = glGetSubroutineUniformLocation(shader.program.program, GL_VERTEX_SHADER, toStringz(name));
158             index = glGetSubroutineIndex(shader.program.program, GL_VERTEX_SHADER, toStringz(subroutineName));
159         }
160         else if (shaderType == ShaderType.Fragment)
161         {
162             location = glGetSubroutineUniformLocation(shader.program.program, GL_FRAGMENT_SHADER, toStringz(name));
163             index = glGetSubroutineIndex(shader.program.program, GL_FRAGMENT_SHADER, toStringz(subroutineName));
164         }
165     }
166 
167     override void bind()
168     {
169         if (shaderType == ShaderType.Vertex)
170         {
171             if (location != -1)
172                 shader.vertexSubroutineIndices[location] = index;
173         }
174         else if (shaderType == ShaderType.Fragment)
175         {
176             if (location != -1)
177                 shader.fragmentSubroutineIndices[location] = index;
178         }
179     }
180 
181     override void unbind()
182     {
183     }
184 }
185 
186 class ShaderParameter(T): BaseShaderParameter
187 if (is(T == bool) ||
188     is(T == int) ||
189     is(T == float) ||
190     is(T == Vector2f) ||
191     is(T == Vector3f) ||
192     is(T == Vector4f) ||
193     is(T == Color4f) ||
194     is(T == Matrix4x4f))
195 {
196     T* source;
197     T value;
198     T delegate() callback;
199 
200     this(Shader shader, string name, T* source)
201     {
202         super(shader, name);
203         this.source = source;
204         this.callback = null;
205         initUniform();
206     }
207 
208     this(Shader shader, string name, T value)
209     {
210         super(shader, name);
211         this.source = null;
212         this.value = value;
213         this.callback = null;
214         initUniform();
215     }
216 
217     this(Shader shader, string name, T delegate() callback)
218     {
219         super(shader, name);
220         this.source = null;
221         this.value = value;
222         this.callback = callback;
223         initUniform();
224     }
225 
226     override void initUniform()
227     {
228         location = glGetUniformLocation(shader.program.program, toStringz(name));
229     }
230 
231     override void bind()
232     {
233         if (callback)
234             value = callback();
235         else if (source)
236             value = *source;
237 
238         static if (is(T == bool) || is(T == int))
239         {
240             glUniform1i(location, value);
241         }
242         else static if (is(T == float))
243         {
244             glUniform1f(location, value);
245         }
246         else static if (is(T == Vector2f))
247         {
248             glUniform2fv(location, 1, value.arrayof.ptr);
249         }
250         else static if (is(T == Vector3f))
251         {
252             glUniform3fv(location, 1, value.arrayof.ptr);
253         }
254         else static if (is(T == Vector4f))
255         {
256             glUniform4fv(location, 1, value.arrayof.ptr);
257         }
258         else static if (is(T == Color4f))
259         {
260             glUniform4fv(location, 1, value.arrayof.ptr);
261         }
262         else static if (is(T == Matrix4x4f))
263         {
264             glUniformMatrix4fv(location, 1, GL_FALSE, value.arrayof.ptr);
265         }
266     }
267 
268     override void unbind()
269     {
270         //TODO
271     }
272 }
273 
274 class Shader: Owner
275 {
276     ShaderProgram program;
277     MappedList!BaseShaderParameter parameters;
278     GLuint[] vertexSubroutineIndices;
279     GLuint[] fragmentSubroutineIndices;
280 
281     this(ShaderProgram program, Owner o)
282     {
283         super(o);
284         this.program = program;
285         this.parameters = New!(MappedList!BaseShaderParameter)(this);
286     }
287 
288     static String load(string filename)
289     {
290         auto fs = New!StdFileSystem();
291         auto istrm = fs.openForInput(filename);
292         string inputText = readText(istrm);
293         Delete(istrm);
294 
295         string includePath = "data/__internal/shaders/include/";
296         String outputText;
297         foreach(line; lineSplitter(inputText))
298         {
299             auto s = line.strip;
300             if (s.startsWith("#include"))
301             {
302                 char[64] buf;
303                 if (sscanf(s.ptr, "#include <%s>", buf.ptr) == 1)
304                 {
305                     string includeFilename = cast(string)buf[0..strlen(buf.ptr)-1];
306                     String includeFullPath = includePath;
307                     includeFullPath ~= includeFilename;
308                     if (exists(includeFullPath))
309                     {
310                         istrm = fs.openForInput(includeFullPath.toString);
311                         string includeText = readText(istrm);
312                         Delete(istrm);
313                         outputText ~= includeText;
314                         outputText ~= "\n";
315                         Delete(includeText);
316                     }
317                     includeFullPath.free();
318                 }
319                 else
320                 {
321                     writeln("Error");
322                     break;
323                 }
324             }
325             else
326             {
327                 outputText ~= line;
328                 outputText ~= "\n";
329             }
330         }
331 
332         Delete(fs);
333         Delete(inputText);
334 
335         return outputText;
336     }
337 
338     ShaderSubroutine setParameterSubroutine(string name, ShaderType shaderType, string subroutineName)
339     {
340         if (name in parameters.indices)
341         {
342             auto sp = cast(ShaderSubroutine)parameters.get(name);
343             if (sp is null)
344             {
345                 writefln("Warning: type mismatch for shader parameter \"%s\"", name);
346                 return null;
347             }
348             sp.shaderType = shaderType;
349             sp.subroutineName = subroutineName;
350             sp.initUniform();
351             return sp;
352         }
353         else
354         {
355             auto sp = New!ShaderSubroutine(this, shaderType, name, subroutineName);
356             parameters.set(name, sp);
357             return sp;
358         }
359     }
360 
361     ShaderParameter!T setParameter(T)(string name, T val)
362     {
363         if (name in parameters.indices)
364         {
365             auto sp = cast(ShaderParameter!T)parameters.get(name);
366             if (sp is null)
367             {
368                 writefln("Warning: type mismatch for shader parameter \"%s\"", name);
369                 return null;
370             }
371 
372             sp.value = val;
373             sp.source = null;
374             return sp;
375         }
376         else
377         {
378             auto sp = New!(ShaderParameter!T)(this, name, val);
379             parameters.set(name, sp);
380             return sp;
381         }
382     }
383 
384     ShaderParameter!T setParameterRef(T)(string name, ref T val)
385     {
386         if (name in parameters.indices)
387         {
388             auto sp = cast(ShaderParameter!T)parameters.get(name);
389             if (sp is null)
390             {
391                 writefln("Warning: type mismatch for shader parameter \"%s\"", name);
392                 return null;
393             }
394 
395             sp.source = &val;
396             return sp;
397         }
398         else
399         {
400             auto sp = New!(ShaderParameter!T)(this, name, &val);
401             parameters.set(name, sp);
402             return sp;
403         }
404     }
405 
406     ShaderParameter!T setParameterCallback(T)(string name, T delegate() val)
407     {
408         if (name in parameters.indices)
409         {
410             auto sp = cast(ShaderParameter!T)parameters.get(name);
411             if (sp is null)
412             {
413                 writefln("Warning: type mismatch for shader parameter \"%s\"", name);
414                 return null;
415             }
416 
417             sp.callback = val;
418             return sp;
419         }
420         else
421         {
422             auto sp = New!(ShaderParameter!T)(this, name, val);
423             parameters.set(name, sp);
424             return sp;
425         }
426     }
427 
428     BaseShaderParameter getParameter(string name)
429     {
430         if (name in parameters.indices)
431         {
432             return parameters.get(name);
433         }
434         else
435         {
436             writefln("Warning: unknown shader parameter \"%s\"", name);
437             return null;
438         }
439     }
440 
441     T getParameterValue(T)(string name)
442     {
443         if (name in parameters.indices)
444         {
445             auto sp = cast(ShaderParameter!T)parameters.get(name);
446             if (sp is null)
447             {
448                 writefln("Warning: type mismatch for shader parameter \"%s\"", name);
449                 return T.init;
450             }
451 
452             if (sp.source)
453                 return *sp.source;
454             else
455                 return sp.value;
456         }
457         else
458         {
459             writefln("Warning: unknown shader parameter \"%s\"", name);
460             return T.init;
461         }
462     }
463 
464     void bind()
465     {
466         program.bind();
467     }
468 
469     void unbind()
470     {
471         program.unbind();
472     }
473 
474     void bindParameters(GraphicsState* state)
475     {
476         GLsizei n;
477         glGetProgramStageiv(program.program, GL_VERTEX_SHADER, GL_ACTIVE_SUBROUTINE_UNIFORM_LOCATIONS, &n);
478         if (n > 0 && n != vertexSubroutineIndices.length)
479             vertexSubroutineIndices = New!(GLuint[])(n);
480 
481         glGetProgramStageiv(program.program, GL_FRAGMENT_SHADER, GL_ACTIVE_SUBROUTINE_UNIFORM_LOCATIONS, &n);
482         if (n > 0 && n != fragmentSubroutineIndices.length)
483             fragmentSubroutineIndices = New!(GLuint[])(n);
484 
485         foreach(v; parameters.data)
486         {
487             if (v.autoBind)
488                 v.bind();
489         }
490 
491         if (vertexSubroutineIndices.length)
492             glUniformSubroutinesuiv(GL_VERTEX_SHADER, cast(uint)vertexSubroutineIndices.length, vertexSubroutineIndices.ptr);
493 
494         if (fragmentSubroutineIndices.length)
495             glUniformSubroutinesuiv(GL_FRAGMENT_SHADER, cast(uint)fragmentSubroutineIndices.length, fragmentSubroutineIndices.ptr);
496     }
497 
498     void unbindParameters(GraphicsState* state)
499     {
500         foreach(v; parameters.data)
501         {
502             if (v.autoBind)
503                 v.unbind();
504         }
505     }
506 
507     void validate()
508     {
509         glValidateProgram(program.program);
510     }
511 
512     ~this()
513     {
514         if (vertexSubroutineIndices.length)
515             Delete(vertexSubroutineIndices);
516         if (fragmentSubroutineIndices.length)
517             Delete(fragmentSubroutineIndices);
518     }
519 }