Browse code

Add preprocessing version

Robert Cranston authored on 26/02/2021 16:06:29
Showing 6 changed files

... ...
@@ -138,6 +138,22 @@ case.
138 138
 
139 139
 [use]: https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glUseProgram.xhtml
140 140
 
141
+### Preprocessing
142
+
143
+A simple preprocessor is run before shader sources are passed to OpenGL.
144
+
145
+At present the preprocessor does not interpret line continuations or multi-line
146
+comments correctly on lines it acts upon. Other lines are left alone so they
147
+can be used there without issue.
148
+
149
+#### `#version`
150
+
151
+Each supplied shader file is checked for the existence of one, and only one,
152
+[`#version`][]. An error is thrown if this is not the case. (The OpenGL default
153
+is to use `#version 110` if none is provided.)
154
+
155
+[`#version`]: https://www.khronos.org/opengl/wiki/Core_Language_(GLSL)#Version
156
+
141 157
 ## Dependencies
142 158
 
143 159
 Public (interface):
144 160
new file mode 100644
... ...
@@ -0,0 +1,7 @@
1
+#version 1.10
2
+
3
+
4
+void main()
5
+{
6
+    gl_Position = gl_Vertex;
7
+}
0 8
new file mode 100644
... ...
@@ -0,0 +1,4 @@
1
+void main()
2
+{
3
+    gl_Position = gl_Vertex;
4
+}
0 5
new file mode 100644
... ...
@@ -0,0 +1,8 @@
1
+#version 110
2
+#version 110
3
+
4
+
5
+void main()
6
+{
7
+    gl_Position = gl_Vertex;
8
+}
... ...
@@ -6,8 +6,11 @@
6 6
 #include <cstring>
7 7
 #include <fstream>
8 8
 #include <memory>
9
+#include <regex>
10
+#include <sstream>
9 11
 #include <stdexcept>
10 12
 #include <string>
13
+#include <tuple>
11 14
 #include <utility>
12 15
 #include <vector>
13 16
 
... ...
@@ -16,6 +19,9 @@
16 19
 #include <str.hpp>
17 20
 
18 21
 
22
+using Here = std::tuple<std::string, int, std::string>;
23
+
24
+
19 25
 // NOLINTNEXTLINE
20 26
 #define GLSHADER_INIT_(NAME, INIT) decltype(NAME) NAME INIT;
21 27
 GLSHADER_INIT_(Shader::root_, {})
... ...
@@ -70,6 +76,106 @@ static void info_log_action_(
70 76
 }
71 77
 
72 78
 
79
+static std::string source_(
80
+    std::string const & error,
81
+    std::string const & path,
82
+    std::string const & root
83
+)
84
+{
85
+    // Set here error.
86
+    auto const here_error = [](Here const & here)
87
+    {
88
+        return STR(
89
+            std::get<0>(here) << ":" <<
90
+            std::get<1>(here) << ": " <<
91
+            std::get<2>(here)
92
+        );
93
+    };
94
+
95
+    // Set full path.
96
+    auto path_full = path;
97
+    if (!root.empty())
98
+        path_full = STR(root << "/" << path_full);
99
+
100
+    // Define and open input stream.
101
+    auto istream = std::ifstream{path_full};
102
+    if (!istream)
103
+        throw std::runtime_error{STR(
104
+            error << "; " <<
105
+            "could not open file '" << path_full << "':\n" <<
106
+            std::strerror(errno)
107
+        )};
108
+
109
+    // Define output stream.
110
+    auto ostream = std::ostringstream{};
111
+
112
+    // Define parse regexes.
113
+    static auto const re_ignored = std::regex{R"(\s*//.*$)"};
114
+    static auto const re_words   = std::regex{R"((\w+(?:\s+\w+)*))"};
115
+    static auto const re_version = std::regex{R"(\s*#\s*version\s*(.*))"};
116
+
117
+    // Parse.
118
+    auto version_number = 0;
119
+    auto line_number    = 0;
120
+    auto line           = std::string{};
121
+    auto match          = std::smatch{};
122
+    auto here           = [&]()
123
+    {
124
+        return Here{path_full, line_number, line};
125
+    };
126
+    while (++line_number, std::getline(istream, line))
127
+    {
128
+        // Remove ignored.
129
+        auto const content = std::regex_replace(line, re_ignored, "");
130
+
131
+        // Process version.
132
+        if (std::regex_match(content, match, re_version))
133
+        {
134
+            // Parse.
135
+            auto const words = match.str(1);
136
+            if (!std::regex_match(words, match, re_words))
137
+                throw std::runtime_error{STR(
138
+                    error << "; " <<
139
+                    "malformed #version:\n" <<
140
+                    here_error(here())
141
+                )};
142
+            auto const version = match.str(1);
143
+
144
+            // Check for errors.
145
+            if (version_number)
146
+                throw std::runtime_error{STR(
147
+                    error << "; " <<
148
+                    "found repeated #version:\n" <<
149
+                    here_error(here())
150
+                )};
151
+
152
+            // Process.
153
+            version_number = std::stoi(version);
154
+
155
+            // Output.
156
+            ostream << line << "\n";
157
+        }
158
+
159
+        // Non-processed line.
160
+        else
161
+        {
162
+            // Output.
163
+            ostream << line << "\n";
164
+        }
165
+    }
166
+
167
+    // Check for version.
168
+    if (!version_number)
169
+        throw std::runtime_error{STR(
170
+            error << "; " <<
171
+            "found no #version."
172
+        )};
173
+
174
+    // Return.
175
+    return ostream.str();
176
+}
177
+
178
+
73 179
 Shader::Shader(Paths const & paths)
74 180
 :
75 181
     program_{0},
... ...
@@ -157,17 +263,7 @@ Shader::Shader(Paths const & paths)
157 263
 
158 264
             // Set shader source.
159 265
             auto const source_error = STR("Failed to source " << shader_name);
160
-            auto path_full = path;
161
-            if (!root_.empty())
162
-                path_full = STR(root_ << "/" << path_full);
163
-            auto source_istream = std::ifstream{path_full};
164
-            if (!source_istream)
165
-                throw std::runtime_error{STR(
166
-                    source_error << "; " <<
167
-                    "could not open file '" << path_full << "':\n" <<
168
-                    std::strerror(errno)
169
-                )};
170
-            auto const source = STR(source_istream.rdbuf());
266
+            auto const source = source_(source_error, path, root_);
171 267
             auto const sources = std::array<char const *, 1>{{
172 268
                 source.c_str()
173 269
             }};
... ...
@@ -53,6 +53,25 @@ GLTEST(2, 0, 640, 480, glshader)
53 53
         "shader program not current."
54 54
     )
55 55
 
56
+    // Version.
57
+    GLTEST_EXPECT_EXCEPTION(false,
58
+        Shader({"tests/version_none.vert"}),
59
+        "Failed to source shader 'tests/version_none.vert' of shader program 'tests/version_none.vert'; "
60
+        "found no #version."
61
+    )
62
+    GLTEST_EXPECT_EXCEPTION(false,
63
+        Shader({"tests/version_malformed.vert"}),
64
+        "Failed to source shader 'tests/version_malformed.vert' of shader program 'tests/version_malformed.vert'; "
65
+        "malformed #version:\n"
66
+        "assets/shaders/tests/version_malformed.vert:1: #version 1.10"
67
+    )
68
+    GLTEST_EXPECT_EXCEPTION(false,
69
+        Shader({"tests/version_repeated.vert"}),
70
+        "Failed to source shader 'tests/version_repeated.vert' of shader program 'tests/version_repeated.vert'; "
71
+        "found repeated #version:\n"
72
+        "assets/shaders/tests/version_repeated.vert:2: #version 110"
73
+    )
74
+
56 75
     auto all = Shader({
57 76
         "tests/all.vert",
58 77
         "tests/all.frag",