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