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