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