1 module dlangui.graphics.scene.effect;
2 
3 public import dlangui.core.config;
4 static if (ENABLE_OPENGL):
5 static if (BACKEND_GUI):
6 
7 import dlangui.core.types;
8 import dlangui.core.logger;
9 import dlangui.graphics.glsupport;
10 import dlangui.graphics.gldrawbuf;
11 import dlangui.graphics.scene.mesh;
12 
13 /// Reference counted Effect object
14 alias EffectRef = Ref!Effect;
15 
16 /// Effect (aka OpenGL program)
17 class Effect : GLProgram {
18     EffectId _id;
19     string[string] _defs;
20     string _defText;
21 
22     @property ref const(EffectId) id() const { return _id; }
23     this(EffectId id) {
24         _id = id;
25         init();
26     }
27     this(string vertexShader, string fragmentShader, string defs) {
28         _id = EffectId(vertexShader, fragmentShader, defs);
29         init();
30     }
31 
32     ~this() {
33         EffectCache.instance.onObjectDestroyed(_id);
34     }
35 
36     protected void init() {
37         // parse defs
38         import std.array : split;
39         string[] defs = _id.definitions.split(";");
40         char[] buf;
41         foreach(def; defs) {
42             assert(def.length > 0);
43             string[] items = def.split(" ");
44             if (items.length > 0) {
45                 string value = items.length > 1 ? items[1] : "";
46                 _defs[items[0]] = value;
47                 buf ~= "#define ";
48                 buf ~= items[0];
49                 buf ~= " ";
50                 buf ~= value;
51                 buf ~= "\n";
52             }
53         }
54         _defText = buf.dup;
55         // compile shaders
56         compile();
57         if (!check()) {
58             Log.e("Failed to compile shaders ", _id.vertexShaderName, " ", _id.fragmentShaderName, " ", _id.definitions);
59             assert(false);
60         }
61     }
62 
63     protected bool[string] _visitedIncludes;
64     protected void preProcessIncludes(ref char[] buf, string src) {
65         import std.string : strip, startsWith, endsWith;
66         import dlangui.graphics.resources : splitLines;
67         foreach(line; src.splitLines) {
68             string s = line.strip;
69             if (s.startsWith("#include ")) {
70                 s = s[9 .. $].strip; // remove #include
71                 if (s.startsWith("\"") && s.endsWith("\"")) {
72                     s = s[1 .. $-1]; // remove ""
73                     if (!(s in _visitedIncludes)) { // protect from duplicate include
74                         _visitedIncludes[s] = true; // mark as included
75                         string includedSrc = loadVertexSource(s);
76                         preProcessIncludes(buf, includedSrc);
77                     }
78                 }
79             } else {
80                 buf ~= line;
81                 buf ~= "\n";
82             }
83         }
84     }
85 
86     protected string preProcessSource(string src) {
87         char[] buf;
88         buf.assumeSafeAppend;
89         // append definitions
90         buf ~= _defText;
91         // append source body
92         preProcessIncludes(buf, src);
93         return buf.dup;
94     }
95 
96     protected string loadVertexSource(string resourceId) {
97         import dlangui.graphics.resources;
98         import std.string : endsWith;
99         string filename;
100         filename = drawableCache.findResource(resourceId);
101         if (!filename) {
102             Log.e("Shader source resource file not found for resourceId ", resourceId);
103             assert(false);
104         }
105         if (!filename.endsWith(".vert") && !filename.endsWith(".frag")) {
106             Log.e("Shader source resource name should have .vert or .frag extension, but found ", filename);
107             assert(false);
108         }
109         string s = cast(string)loadResourceBytes(filename);
110         if (!s) {
111             Log.e("Cannot read shader source resource ", resourceId, " from file ", filename);
112             assert(false);
113         }
114         return s;
115     }
116 
117     @property override string vertexSource() {
118         _visitedIncludes = null;
119         _visitedIncludes[_id.vertexShaderName] = true; // mark as included
120         string res = preProcessSource(loadVertexSource(_id.vertexShaderName));
121         //Log.v("vertexSource:", res);
122         return res;
123     }
124 
125     @property override string fragmentSource() {
126         _visitedIncludes = null;
127         _visitedIncludes[_id.fragmentShaderName] = true; // mark as included
128         string res = preProcessSource(loadVertexSource(_id.fragmentShaderName));
129         //Log.v("fragmentSource:", res);
130         return res;
131     }
132 
133     override bool initLocations() {
134         return getUniformLocation(DefaultUniform.u_worldViewProjectionMatrix) >= 0 && getAttribLocation(DefaultAttribute.a_position) >= 0; // && colAttrLocation >= 0 && texCoordLocation >= 0
135     }
136 
137     /// get location for vertex attribute
138     override int getVertexElementLocation(VertexElementType type) {
139         switch(type) with(VertexElementType) {
140             case POSITION:
141                 return getAttribLocation(DefaultAttribute.a_position);
142             case COLOR:
143                 return getAttribLocation(DefaultAttribute.a_color);
144             case TEXCOORD0:
145                 return getAttribLocation(DefaultAttribute.a_texCoord);
146             case NORMAL:
147                 return getAttribLocation(DefaultAttribute.a_normal);
148             case TANGENT:
149                 return getAttribLocation(DefaultAttribute.a_tangent);
150             case BINORMAL:
151                 return getAttribLocation(DefaultAttribute.a_binormal);
152             default:
153                 return super.getVertexElementLocation(type);
154         }
155     }
156 
157 }
158 
159 /// Effects cache
160 class EffectCache {
161     private Effect[EffectId] _map;
162 
163     private static __gshared EffectCache _instance;
164 
165     /// returns effect cache singleton instance
166     static @property EffectCache instance() {
167         if (!_instance)
168             _instance = new EffectCache();
169         return _instance;
170     }
171 
172     static private void onObjectDestroyed(EffectId id) {
173         if (id in _instance._map) {
174             Log.d("Destroyed effect instance: ", id);
175             _instance._map.remove(id);
176         }
177     }
178 
179     /// get effect from cache or create new if not exist
180     Effect get(string vertexShader, string fragmentShader, string defs = null) {
181         return get(EffectId(vertexShader, fragmentShader, defs));
182     }
183 
184     /// get effect from cache or create new if not exist
185     Effect get(const EffectId id) {
186         if (auto p = id in _map) {
187             return *p;
188         }
189         Effect e = new Effect(id);
190         Log.d("New effect instance: ", id);
191         _map[id] = e;
192         return e;
193     }
194 }
195 
196 
197 /// Effect ID
198 struct EffectId {
199     string vertexShaderName;
200     string fragmentShaderName;
201     string definitions;
202     this(string vertexShader, string fragmentShader, string defs) {
203         vertexShaderName = vertexShader;
204         fragmentShaderName = fragmentShader;
205         definitions = defs;
206     }
207 
208     this(ref EffectId v, string additionalParams) {
209         import std.string : empty;
210         vertexShaderName = v.vertexShaderName;
211         fragmentShaderName = v.fragmentShaderName;
212         definitions = v.definitions;
213         if (!additionalParams.empty) {
214             if (!definitions.empty) {
215                 definitions ~= ";";
216                 definitions ~= additionalParams;
217             } else {
218                 definitions = additionalParams;
219             }
220         }
221     }
222 
223     /// returns true if ID is not assigned
224     @property bool empty() {
225         return !vertexShaderName.length || !vertexShaderName.length;
226     }
227 
228     size_t toHash() const @safe pure nothrow
229     {
230         size_t hash;
231         foreach (char c; vertexShaderName)
232             hash = (hash * 9) + c;
233         hash = (hash * 31) + 198237283;
234         foreach (char c; fragmentShaderName)
235             hash = (hash * 9) + c;
236         hash = (hash * 31) + 84574112;
237         foreach (char c; definitions)
238             hash = (hash * 9) + c;
239         return hash;
240     }
241 
242     bool opEquals(ref const EffectId s) const @safe pure nothrow
243     {
244         return
245             this.vertexShaderName == s.vertexShaderName &&
246             this.fragmentShaderName == s.fragmentShaderName &&
247             this.definitions == s.definitions;
248     }
249 }
250