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         import std.string : splitLines;
68         foreach(line; src.splitLines) {
69             string s = line.strip;
70             if (s.startsWith("#include ")) {
71                 s = s[9 .. $].strip; // remove #include
72                 if (s.startsWith("\"") && s.endsWith("\"")) {
73                     s = s[1 .. $-1]; // remove ""
74                     if (!(s in _visitedIncludes)) { // protect from duplicate include
75                         _visitedIncludes[s] = true; // mark as included
76                         string includedSrc = loadVertexSource(s);
77                         preProcessIncludes(buf, includedSrc);
78                     }
79                 }
80             } else {
81                 buf ~= line;
82                 buf ~= "\n";
83             }
84         }
85     }
86 
87     protected string preProcessSource(string src) {
88         char[] buf;
89         buf.assumeSafeAppend;
90         // append definitions
91         buf ~= _defText;
92         // append source body
93         preProcessIncludes(buf, src);
94         return buf.dup;
95     }
96 
97     protected string loadVertexSource(string resourceId) {
98         import dlangui.graphics.resources;
99         import std.string : endsWith;
100         string filename;
101         filename = drawableCache.findResource(resourceId);
102         if (!filename) {
103             Log.e("Shader source resource file not found for resourceId ", resourceId);
104             assert(false);
105         }
106         if (!filename.endsWith(".vert") && !filename.endsWith(".frag")) {
107             Log.e("Shader source resource name should have .vert or .frag extension, but found ", filename);
108             assert(false);
109         }
110         string s = cast(string)loadResourceBytes(filename);
111         if (!s) {
112             Log.e("Cannot read shader source resource ", resourceId, " from file ", filename);
113             assert(false);
114         }
115         return s;
116     }
117 
118     @property override string vertexSource() {
119         _visitedIncludes = null;
120         _visitedIncludes[_id.vertexShaderName] = true; // mark as included
121         string res = preProcessSource(loadVertexSource(_id.vertexShaderName));
122         //Log.v("vertexSource:", res);
123         return res;
124     }
125 
126     @property override string fragmentSource() {
127         _visitedIncludes = null;
128         _visitedIncludes[_id.fragmentShaderName] = true; // mark as included
129         string res = preProcessSource(loadVertexSource(_id.fragmentShaderName));
130         //Log.v("fragmentSource:", res);
131         return res;
132     }
133 
134     override bool initLocations() {
135         return getUniformLocation(DefaultUniform.u_worldViewProjectionMatrix) >= 0 && getAttribLocation(DefaultAttribute.a_position) >= 0; // && colAttrLocation >= 0 && texCoordLocation >= 0
136     }
137 
138     /// get location for vertex attribute
139     override int getVertexElementLocation(VertexElementType type) {
140         switch(type) with(VertexElementType) {
141             case POSITION:
142                 return getAttribLocation(DefaultAttribute.a_position);
143             case COLOR:
144                 return getAttribLocation(DefaultAttribute.a_color);
145             case TEXCOORD0:
146                 return getAttribLocation(DefaultAttribute.a_texCoord);
147             case NORMAL:
148                 return getAttribLocation(DefaultAttribute.a_normal);
149             case TANGENT:
150                 return getAttribLocation(DefaultAttribute.a_tangent);
151             case BINORMAL:
152                 return getAttribLocation(DefaultAttribute.a_binormal);
153             default:
154                 return super.getVertexElementLocation(type);
155         }
156     }
157 
158 }
159 
160 /// Effects cache
161 class EffectCache {
162     private Effect[EffectId] _map;
163 
164     private static __gshared EffectCache _instance;
165 
166     /// returns effect cache singleton instance
167     static @property EffectCache instance() {
168         if (!_instance)
169             _instance = new EffectCache();
170         return _instance;
171     }
172 
173     static private void onObjectDestroyed(EffectId id) {
174         if (id in _instance._map) {
175             Log.d("Destroyed effect instance: ", id);
176             _instance._map.remove(id);
177         }
178     }
179 
180     /// get effect from cache or create new if not exist
181     Effect get(string vertexShader, string fragmentShader, string defs = null) {
182         return get(EffectId(vertexShader, fragmentShader, defs));
183     }
184 
185     /// get effect from cache or create new if not exist
186     Effect get(const EffectId id) {
187         if (auto p = id in _map) {
188             return *p;
189         }
190         Effect e = new Effect(id);
191         Log.d("New effect instance: ", id);
192         _map[id] = e;
193         return e;
194     }
195 }
196 
197 
198 /// Effect ID
199 struct EffectId {
200     string vertexShaderName;
201     string fragmentShaderName;
202     string definitions;
203     this(string vertexShader, string fragmentShader, string defs) {
204         vertexShaderName = vertexShader;
205         fragmentShaderName = fragmentShader;
206         definitions = defs;
207     }
208 
209     this(ref EffectId v, string additionalParams) {
210         import std.string : empty;
211         vertexShaderName = v.vertexShaderName;
212         fragmentShaderName = v.fragmentShaderName;
213         definitions = v.definitions;
214         if (!additionalParams.empty) {
215             if (!definitions.empty) {
216                 definitions ~= ";";
217                 definitions ~= additionalParams;
218             } else {
219                 definitions = additionalParams;
220             }
221         }
222     }
223 
224     /// returns true if ID is not assigned
225     @property bool empty() {
226         return !vertexShaderName.length || !vertexShaderName.length;
227     }
228 
229     size_t toHash() const @safe pure nothrow
230     {
231         size_t hash;
232         foreach (char c; vertexShaderName)
233             hash = (hash * 9) + c;
234         hash = (hash * 31) + 198237283;
235         foreach (char c; fragmentShaderName)
236             hash = (hash * 9) + c;
237         hash = (hash * 31) + 84574112;
238         foreach (char c; definitions)
239             hash = (hash * 9) + c;
240         return hash;
241     }
242 
243     bool opEquals(ref const EffectId s) const @safe pure nothrow
244     {
245         return
246             this.vertexShaderName == s.vertexShaderName &&
247             this.fragmentShaderName == s.fragmentShaderName &&
248             this.definitions == s.definitions;
249     }
250 }
251