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