[Shader Generator] using Raw String Literal
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / shader-generator / shader-generator.cpp
1 /*
2  * Copyright (c) 2021 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 #include <algorithm>
19 #include <filesystem>
20 #include <fstream>
21 #include <iostream>
22 #include <string>
23 #include <string_view>
24 #include <vector>
25
26 using namespace std;
27 namespace fs = filesystem;
28
29 namespace
30 {
31 ///////////////////////////////////////////////////////////////////////////////////////////////////
32 string      PROGRAM_NAME; ///< We set the program name on this global early on for use in Usage.
33 string_view VERSION = "1.0.0";
34
35 ///////////////////////////////////////////////////////////////////////////////////////////////////
36 /// Supported extensions for the files in the input directory.
37 // clang-format off
38 constexpr string_view SHADER_EXTENSIONS[] =
39 {
40   ".vert",
41   ".frag",
42   ".def"
43 };
44 // clang-format on
45
46 ///////////////////////////////////////////////////////////////////////////////////////////////////
47 /// Function & variable to retrieve the size of the extension with the largest string size.
48 constexpr auto GetShaderExtensionMaxSize()
49 {
50   auto maxSize = 0u;
51   for(const auto& extension : SHADER_EXTENSIONS)
52   {
53     if(extension.size() > maxSize)
54     {
55       maxSize = extension.size();
56     }
57   }
58   return maxSize;
59 }
60 constexpr const int SHADER_MAX_EXTENSION_SIZE(GetShaderExtensionMaxSize());
61
62 ///////////////////////////////////////////////////////////////////////////////////////////////////
63 /// Prints out the Usage to standard output.
64 void Usage()
65 {
66   cout << "Usage: " << PROGRAM_NAME << " [OPTIONS] [IN_DIR] [OUT_DIR]" << endl;
67   cout << "  IN_DIR:  Input Directory which has all the shader files." << endl;
68   cout << "           Supported extensions:";
69   string extensions;
70   for(const auto& extension : SHADER_EXTENSIONS)
71   {
72     extensions = extensions + " \"" + string(extension) + "\",";
73   }
74   extensions.pop_back(); // Remove the last comma.
75   cout << extensions << "." << endl;
76   cout << "  OUT_DIR: Directory where the generated shader source code will be outputted to." << endl;
77   cout << "           This directory will be created if it does not exist." << endl;
78   cout << "           Any existing files of the same name in the directory will be overwritten." << endl;
79   cout << "  Options: " << endl;
80   cout << "     -s|--skip     Skips the generation of the built-in header and source files" << endl;
81   cout << "     -v|--version  Prints out the version" << endl;
82   cout << "     -h|--help     Help" << endl;
83   cout << "  NOTE: The options can be placed after the IN_DIR & OUT_DIR as well" << endl;
84 }
85
86 ///////////////////////////////////////////////////////////////////////////////////////////////////
87 /// Uses the filename to generate the shader variable name to use in source code.
88 /// @param[in]  filename  The filename of the shader (including the extension)
89 /// @return The shader variable name
90 string GetShaderVariableName(const string& filename)
91 {
92   string shaderVariableName("SHADER_" + filename);
93   for_each(
94     shaderVariableName.begin(),
95     shaderVariableName.end(),
96     [](char& character) {
97       switch(character)
98       {
99         case '-':
100         case '.':
101         {
102           character = '_';
103           break;
104         }
105
106         default:
107         {
108           character = ::toupper(character);
109           break;
110         }
111       }
112     });
113   return shaderVariableName;
114 }
115
116 ///////////////////////////////////////////////////////////////////////////////////////////////////
117 /// Uses the ourDir & filename to generate the path of the output header file for the shader.
118 /// @param[in]  outDir    The directory where the readable shaders will be outputted to
119 /// @param[in]  filename  The filename of the shader (including the extension)
120 /// @return The path to the output file
121 fs::path GetShaderOutputFilePath(fs::path& outDir, const string& filename)
122 {
123   string outFilename(filename);
124   replace(outFilename.end() - SHADER_MAX_EXTENSION_SIZE, outFilename.end(), '.', '-');
125   outFilename = outDir.string() + "/" + outFilename + ".h";
126   return outFilename;
127 }
128
129 ///////////////////////////////////////////////////////////////////////////////////////////////////
130 /// Generates the header file from the input shader file.
131 /// @param[in]  shaderFile          The full path of the input shader file
132 /// @param[in]  shaderVariableName  The variable name to use for the string_view
133 /// @param[in]  outFilePath         The full path to the output file
134 void GenerateHeaderFile(
135   ifstream&       shaderFile,
136   const string&   shaderVariableName,
137   const fs::path& outFilePath)
138 {
139   cout << "  Generating \"" << shaderVariableName << "\" in " << outFilePath.filename();
140   ofstream outFile(outFilePath);
141   if(outFile.is_open())
142   {
143     outFile << "#pragma once" << endl
144             << endl;
145     outFile << "const std::string_view " << shaderVariableName << endl;
146     outFile << "{" << endl;
147     outFile << "R\"(" << endl;
148     string line;
149     while(getline(shaderFile, line))
150     {
151       outFile << line << endl;
152     }
153     outFile << ")\"" << endl;
154     outFile << "};" << endl;
155     cout << " [OK]" << endl;
156   }
157   else
158   {
159     cout << " [FAIL]" << endl;
160   }
161 }
162
163 ///////////////////////////////////////////////////////////////////////////////////////////////////
164 /// If required, this accumulates data about all the shaders & generates the built-in cpp & header
165 class BuiltInFilesGenerator
166 {
167 public:
168   /// Constructor
169   /// @param[in]  outDir  The path to the output directory
170   BuiltInFilesGenerator(const fs::path& outDir)
171   : mHeaderFilePath(outDir.string() + "/../" + string(HEADER_FILE_NAME)),
172     mSourceFilePath(outDir.string() + "/" + string(SOURCE_FILE_NAME))
173   {
174   }
175
176   /// Default destructor
177   ~BuiltInFilesGenerator() = default;
178
179   /// Adds the variable and the header file name to the appropriate vectors.
180   /// @param[in]  variableName    The string_view variable name used
181   /// @param[in]  headerFileName  The name of the header used
182   void Add(string&& variableName, const std::string& headerFilename)
183   {
184     mVariableNames.emplace_back(variableName);
185     mHeaderFileNames.emplace_back(headerFilename);
186   }
187
188   // Generates the built in files.
189   void Generate()
190   {
191     GenerateFile(
192       mVariableNames,
193       mHeaderFilePath,
194       "#pragma once\n\n#include <string_view>\n\n",
195       "extern const std::string_view ",
196       ";");
197
198     GenerateFile(
199       mHeaderFileNames,
200       mSourceFilePath,
201       "#include \"../" + string(HEADER_FILE_NAME) + "\"\n\n",
202       "#include \"",
203       "\"");
204   }
205
206 private:
207   /// Generates the required file.
208   /// @param[in]  strings   A reference to the vector to parse
209   /// @param[in]  filePath  Outputs the data to this file
210   /// @param[in]  header    Puts this before parsing any of the vector
211   /// @param[in]  before    For each string, puts this string before it on every line
212   /// @param[in]  after     For each string, puts this string after it on every line
213   void GenerateFile(
214     vector<string>&   strings,
215     const string&     filePath,
216     const string_view header,
217     const string_view before,
218     const string_view after)
219   {
220     sort(strings.begin(), strings.end());
221     cout << "  Generating \"" << filePath << "\"";
222     ofstream outFile(filePath);
223     if(outFile)
224     {
225       outFile << header;
226       for(auto& current : strings)
227       {
228         outFile << before << current << after << endl;
229       }
230       cout << " [OK]" << endl;
231     }
232     else
233     {
234       cout << " [FAIL]" << endl;
235     }
236   }
237
238   constexpr static string_view HEADER_FILE_NAME = "builtin-shader-extern-gen.h";
239   constexpr static string_view SOURCE_FILE_NAME = "builtin-shader-gen.cpp";
240
241   const string   mHeaderFilePath;  ///< Path to the header file to generate
242   const string   mSourceFilePath;  ///< Path to the source file to generate
243   vector<string> mVariableNames;   ///< Holds all the variable names added through Add
244   vector<string> mHeaderFileNames; ///< Holds all the header file names added through Add
245 };
246
247 ///////////////////////////////////////////////////////////////////////////////////////////////////
248 /// Generates the header files from the shaders in the input directory & built-in files if reqruied.
249 ///
250 /// @param[in]  inDir                 The directory where all the input shader source is
251 /// @param[in]  outDir                The directory where the readable shaders will be outputted to
252 /// @param[in]  generateBuiltInFiles  If true, we generate the built-in files as well
253 /// @return 0 if successful, 1 if failure
254 int GenerateShaderSources(fs::path inDir, fs::path outDir, const bool generateBuiltInFiles)
255 {
256   if(!fs::is_directory(inDir))
257   {
258     cerr << "ERROR: " << inDir << " is not a valid directory" << endl;
259     Usage();
260     return 1;
261   }
262
263   try
264   {
265     fs::create_directories(outDir);
266   }
267   catch(...)
268   {
269     cerr << "ERROR: Unable to create directory " << outDir << endl;
270     return 1;
271   }
272
273   cout << "====================================================================" << endl;
274   cout << "Shader Input Directory:  " << inDir << endl;
275   cout << "Shader Output Directory: " << outDir << endl;
276   cout << "====================================================================" << endl;
277
278   BuiltInFilesGenerator generator(outDir);
279
280   for(auto& file : fs::directory_iterator(inDir))
281   {
282     if(file.is_regular_file())
283     {
284       for(const auto& extension : SHADER_EXTENSIONS)
285       {
286         if(file.path().extension() == extension)
287         {
288           const fs::path& path(file.path());
289           const string    filename(path.filename().string());
290           string          shaderVariableName(GetShaderVariableName(filename));
291           ifstream        shaderFile(path);
292           if(shaderFile.is_open())
293           {
294             fs::path outFilePath(GetShaderOutputFilePath(outDir, filename));
295             GenerateHeaderFile(shaderFile, shaderVariableName, outFilePath);
296             generator.Add(std::move(shaderVariableName), outFilePath.filename().string());
297           }
298           break;
299         }
300       }
301     }
302   }
303
304   if(generateBuiltInFiles)
305   {
306     generator.Generate();
307   }
308
309   cout << "====================================================================" << endl;
310   return 0;
311 }
312
313 } // unnamed namespace
314
315 ///////////////////////////////////////////////////////////////////////////////////////////////////
316 /// MAIN.
317 int main(int argc, char* argv[])
318 {
319   PROGRAM_NAME = argv[0];
320
321   bool generateBuiltInFiles = true;
322
323   string inDir;
324   string outDir;
325
326   for(auto i = 1; i < argc; ++i)
327   {
328     string option(argv[i]);
329     if(option == "--skip" || option == "-s")
330     {
331       generateBuiltInFiles = false;
332     }
333     else if(option == "--help" || option == "-h")
334     {
335       cout << "DALi Shader Generator v" << VERSION << endl
336            << endl;
337       Usage();
338       return 0;
339     }
340     else if(option == "--version" || option == "-v")
341     {
342       cout << VERSION << endl;
343       return 0;
344     }
345     else if(*option.begin() == '-')
346     {
347       cerr << "ERROR: " << option << " is not a supported option" << endl;
348       Usage();
349       return 1;
350     }
351     else if(inDir.empty())
352     {
353       inDir = option;
354     }
355     else if(outDir.empty())
356     {
357       outDir = option;
358     }
359     else if(inDir.size() && outDir.size())
360     {
361       cerr << "ERROR: Too many options" << endl;
362       Usage();
363       return 1;
364     }
365   }
366
367   if(inDir.empty() || outDir.empty())
368   {
369     cerr << "ERROR: Both IN_DIR & OUT_DIR not provided" << endl;
370     Usage();
371     return 1;
372   }
373
374   return GenerateShaderSources(inDir, outDir, generateBuiltInFiles);
375 }