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