1 //
2 // Copyright (c) 2008-2017 the Urho3D project.
3 //
4 // Permission is hereby granted, free of charge, to any person obtaining a copy
5 // of this software and associated documentation files (the "Software"), to deal
6 // in the Software without restriction, including without limitation the rights
7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 // copies of the Software, and to permit persons to whom the Software is
9 // furnished to do so, subject to the following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included in
12 // all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 // THE SOFTWARE.
21 //
22 
23 #include "../Precompiled.h"
24 
25 #include "../Core/Context.h"
26 #include "../Graphics/Graphics.h"
27 #include "../Graphics/Shader.h"
28 #include "../Graphics/ShaderVariation.h"
29 #include "../IO/Deserializer.h"
30 #include "../IO/FileSystem.h"
31 #include "../IO/Log.h"
32 #include "../Resource/ResourceCache.h"
33 
34 #include "../DebugNew.h"
35 
36 namespace Urho3D
37 {
38 
CommentOutFunction(String & code,const String & signature)39 void CommentOutFunction(String& code, const String& signature)
40 {
41     unsigned startPos = code.Find(signature);
42     unsigned braceLevel = 0;
43     if (startPos == String::NPOS)
44         return;
45 
46     code.Insert(startPos, "/*");
47 
48     for (unsigned i = startPos + 2 + signature.Length(); i < code.Length(); ++i)
49     {
50         if (code[i] == '{')
51             ++braceLevel;
52         else if (code[i] == '}')
53         {
54             --braceLevel;
55             if (braceLevel == 0)
56             {
57                 code.Insert(i + 1, "*/");
58                 return;
59             }
60         }
61     }
62 }
63 
Shader(Context * context)64 Shader::Shader(Context* context) :
65     Resource(context),
66     timeStamp_(0),
67     numVariations_(0)
68 {
69     RefreshMemoryUse();
70 }
71 
~Shader()72 Shader::~Shader()
73 {
74     ResourceCache* cache = GetSubsystem<ResourceCache>();
75     if (cache)
76         cache->ResetDependencies(this);
77 }
78 
RegisterObject(Context * context)79 void Shader::RegisterObject(Context* context)
80 {
81     context->RegisterFactory<Shader>();
82 }
83 
BeginLoad(Deserializer & source)84 bool Shader::BeginLoad(Deserializer& source)
85 {
86     Graphics* graphics = GetSubsystem<Graphics>();
87     if (!graphics)
88         return false;
89 
90     // Load the shader source code and resolve any includes
91     timeStamp_ = 0;
92     String shaderCode;
93     if (!ProcessSource(shaderCode, source))
94         return false;
95 
96     // Comment out the unneeded shader function
97     vsSourceCode_ = shaderCode;
98     psSourceCode_ = shaderCode;
99     CommentOutFunction(vsSourceCode_, "void PS(");
100     CommentOutFunction(psSourceCode_, "void VS(");
101 
102     // OpenGL: rename either VS() or PS() to main()
103 #ifdef URHO3D_OPENGL
104     vsSourceCode_.Replace("void VS(", "void main(");
105     psSourceCode_.Replace("void PS(", "void main(");
106 #endif
107 
108     RefreshMemoryUse();
109     return true;
110 }
111 
EndLoad()112 bool Shader::EndLoad()
113 {
114     // If variations had already been created, release them and require recompile
115     for (HashMap<StringHash, SharedPtr<ShaderVariation> >::Iterator i = vsVariations_.Begin(); i != vsVariations_.End(); ++i)
116         i->second_->Release();
117     for (HashMap<StringHash, SharedPtr<ShaderVariation> >::Iterator i = psVariations_.Begin(); i != psVariations_.End(); ++i)
118         i->second_->Release();
119 
120     return true;
121 }
122 
GetVariation(ShaderType type,const String & defines)123 ShaderVariation* Shader::GetVariation(ShaderType type, const String& defines)
124 {
125     return GetVariation(type, defines.CString());
126 }
127 
GetVariation(ShaderType type,const char * defines)128 ShaderVariation* Shader::GetVariation(ShaderType type, const char* defines)
129 {
130     StringHash definesHash(defines);
131     HashMap<StringHash, SharedPtr<ShaderVariation> >& variations(type == VS ? vsVariations_ : psVariations_);
132     HashMap<StringHash, SharedPtr<ShaderVariation> >::Iterator i = variations.Find(definesHash);
133     if (i == variations.End())
134     {
135         // If shader not found, normalize the defines (to prevent duplicates) and check again. In that case make an alias
136         // so that further queries are faster
137         String normalizedDefines = NormalizeDefines(defines);
138         StringHash normalizedHash(normalizedDefines);
139 
140         i = variations.Find(normalizedHash);
141         if (i != variations.End())
142             variations.Insert(MakePair(definesHash, i->second_));
143         else
144         {
145             // No shader variation found. Create new
146             i = variations.Insert(MakePair(normalizedHash, SharedPtr<ShaderVariation>(new ShaderVariation(this, type))));
147             if (definesHash != normalizedHash)
148                 variations.Insert(MakePair(definesHash, i->second_));
149 
150             i->second_->SetName(GetFileName(GetName()));
151             i->second_->SetDefines(normalizedDefines);
152             ++numVariations_;
153             RefreshMemoryUse();
154         }
155     }
156 
157     return i->second_;
158 }
159 
ProcessSource(String & code,Deserializer & source)160 bool Shader::ProcessSource(String& code, Deserializer& source)
161 {
162     ResourceCache* cache = GetSubsystem<ResourceCache>();
163 
164     // If the source if a non-packaged file, store the timestamp
165     File* file = dynamic_cast<File*>(&source);
166     if (file && !file->IsPackaged())
167     {
168         FileSystem* fileSystem = GetSubsystem<FileSystem>();
169         String fullName = cache->GetResourceFileName(file->GetName());
170         unsigned fileTimeStamp = fileSystem->GetLastModifiedTime(fullName);
171         if (fileTimeStamp > timeStamp_)
172             timeStamp_ = fileTimeStamp;
173     }
174 
175     // Store resource dependencies for includes so that we know to reload if any of them changes
176     if (source.GetName() != GetName())
177         cache->StoreResourceDependency(this, source.GetName());
178 
179     while (!source.IsEof())
180     {
181         String line = source.ReadLine();
182 
183         if (line.StartsWith("#include"))
184         {
185             String includeFileName = GetPath(source.GetName()) + line.Substring(9).Replaced("\"", "").Trimmed();
186 
187             SharedPtr<File> includeFile = cache->GetFile(includeFileName);
188             if (!includeFile)
189                 return false;
190 
191             // Add the include file into the current code recursively
192             if (!ProcessSource(code, *includeFile))
193                 return false;
194         }
195         else
196         {
197             code += line;
198             code += "\n";
199         }
200     }
201 
202     // Finally insert an empty line to mark the space between files
203     code += "\n";
204 
205     return true;
206 }
207 
NormalizeDefines(const String & defines)208 String Shader::NormalizeDefines(const String& defines)
209 {
210     Vector<String> definesVec = defines.ToUpper().Split(' ');
211     Sort(definesVec.Begin(), definesVec.End());
212     return String::Joined(definesVec, " ");
213 }
214 
RefreshMemoryUse()215 void Shader::RefreshMemoryUse()
216 {
217     SetMemoryUse(
218         (unsigned)(sizeof(Shader) + vsSourceCode_.Length() + psSourceCode_.Length() + numVariations_ * sizeof(ShaderVariation)));
219 }
220 
221 }
222