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