Shader.cpp
1// Copyright 2019 Arthur Sonzogni. All rights reserved.
2// Use of this source code is governed by the MIT license that can be found in
3// the LICENSE file.
4
5#include <cstdlib>
6#include <fstream>
7#include <glm/gtc/type_ptr.hpp>
8#include <iostream>
9#include <smk/Shader.hpp>
10#include <stdexcept>
11#include <streambuf>
12#include <string>
13#include <vector>
14
15namespace smk {
16
17extern bool g_khr_parallel_shader; // NOLINT
18
19using namespace glm;
20
21const std::string kShaderHeader =
22#ifdef __EMSCRIPTEN__
23 "#version 300 es\n"
24 "precision mediump float;\n"
25 "precision mediump int;\n"
26 "precision mediump sampler2DArray;\n";
27#else
28 "#version 330\n";
29#endif
30
31// static
32/// @brief Load a shader from a file.
33/// @param filename The text filename where the shader is written.
34/// @param type
35/// Either GL_VERTEX_SHADER or GL_FRAGMENT_SHADER. It can also be
36/// any other shader type defined by OpenGL.
37/// See
38/// https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glCreateShader.xhtml
39//
40/// @see Shader::FromString
41/// @see Shader::FromFile
42Shader Shader::FromFile(const std::string& filename, GLenum type) {
43 std::ifstream file(filename);
44 return Shader::FromString(std::string(std::istreambuf_iterator<char>(file),
45 std::istreambuf_iterator<char>()),
46 type);
47}
48
49// static
50/// @brief Load a shader from a std::string.
51/// @param content The string representing the shader.
52/// @param type
53/// Either GL_VERTEX_SHADER or GL_FRAGMENT_SHADER. It can also be
54/// any other shader type defined by OpenGL.
55/// See
56/// https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glCreateShader.xhtml
57//
58/// @see Shader::FromString
59/// @see Shader::FromFile
60Shader Shader::FromString(const std::string& content, GLenum type) {
61 std::vector<char> buffer;
62 for (const auto& c : kShaderHeader) {
63 buffer.push_back(c);
64 }
65 for (const auto& c : content) {
66 buffer.push_back(c);
67 }
68 buffer.push_back('\0');
69 return Shader(buffer, type);
70}
71
72/// @brief The GPU shader id.
73/// @return The OpenGL shader id. If the Shader is invalid, returns zero.
74GLuint Shader::id() const {
75 return id_;
76}
77
78Shader::Shader() = default;
79Shader::Shader(const std::vector<char>& content, GLenum type) {
80 // creation
81 id_ = glCreateShader(type);
82 if (id_ == 0) {
83 std::cerr << "[Error] Impossible to create a new Shader" << std::endl;
84 throw std::runtime_error("[Error] Impossible to create a new Shader");
85 }
86
87 // code source assignation
88 const char* shaderText(&content[0]);
89 glShaderSource(id_, 1, (const GLchar**)&shaderText, nullptr);
90
91 // compilation
92 glCompileShader(id_);
93}
94
95/// @brief Check the status of a Shader.
96/// Linking shader is an asynchronous process. Using the shader can causes the
97/// CPU to wait until its completion. If you need to do some work before the
98/// completion, you can use this function and use the Shader only after it
99/// becomes ready.
100bool Shader::IsReady() const {
101 if (g_khr_parallel_shader) {
102 GLint completion_status = {};
103 glGetShaderiv(id_, GL_COMPLETION_STATUS_KHR, &completion_status);
104 return completion_status == GL_TRUE;
105 }
106
107 std::cerr << "Used bad path" << std::endl;
108 return true;
109}
110
111/// @brief Wait until the Shader to be ready.
112/// @return True if it suceeded, false otherwise.
114 GLint compile_status = {};
115 glGetShaderiv(id_, GL_COMPILE_STATUS, &compile_status);
116 if (compile_status == GL_TRUE) {
117 return true;
118 }
119
120 GLsizei logsize = 0;
121 glGetShaderiv(id_, GL_INFO_LOG_LENGTH, &logsize);
122
123 std::vector<char> log(logsize + 1);
124 glGetShaderInfoLog(id_, logsize, &logsize, log.data());
125
126 std::cout << "[Error] compilation error: " << std::endl;
127 std::cout << log.data() << std::endl;
128
129 return false;
130}
131
132Shader::~Shader() {
133 Release();
134}
135
136Shader::Shader(const Shader& other) noexcept {
137 this->operator=(other);
138}
139
140Shader::Shader(Shader&& other) noexcept {
141 this->operator=(std::move(other));
142}
143
144Shader& Shader::operator=(const Shader& other) noexcept {
145 if (this == &other) {
146 return *this;
147 }
148 Release();
149 if (!other.id_) {
150 return *this;
151 }
152
153 if (!other.ref_count_) {
154 other.ref_count_ = new int(1); // NOLINT
155 }
156
157 id_ = other.id_;
158 ref_count_ = other.ref_count_;
159
160 (*ref_count_)++;
161 return *this;
162}
163
164Shader& Shader::operator=(Shader&& other) noexcept {
165 std::swap(id_, other.id_);
166 std::swap(ref_count_, other.ref_count_);
167 return *this;
168}
169
170void Shader::Release() {
171 // Nothing to do for the null Shader.
172 if (!id_) {
173 return;
174 }
175
176 // Transfert state to local:
177 GLuint id = 0;
178 int* ref_count = nullptr;
179 std::swap(id_, id);
180 std::swap(ref_count_, ref_count);
181
182 // Early return without releasing the resource if it is still hold by copy of
183 // this class.
184 if (ref_count) {
185 --(*ref_count);
186 if (*ref_count) {
187 return;
188 }
189 delete ref_count; // NOLINT
190 ref_count = nullptr;
191 }
192
193 // Release the OpenGL objects.
194 glDeleteShader(id);
195}
196
197struct ShaderProgram::Impl {
198 Impl() = default;
199 ~Impl() {
200 if (!id) {
201 return;
202 }
203 glDeleteProgram(id);
204 id = 0;
205 }
206
207 Impl(const Impl&) = delete;
208 Impl(Impl&&) = delete;
209 Impl& operator=(const Impl&) = delete;
210 Impl& operator=(Impl&&) = delete;
211
212 std::map<std::string, GLint> uniforms;
213 GLuint id = 0;
214};
215
216/// @brief The constructor. The ShaderProgram is initially invalid. You need to
217/// call @ref AddShader and @ref Link before being able to use it.
218// NOLINTNEXTLINE
219ShaderProgram::ShaderProgram() : impl_(std::make_shared<Impl>()) {}
220
221/// @brief Add a Shader to the program list. This must called multiple time for
222/// each shader components before calling @ref Link.
223/// @param shader The Shader to be added to the program list.
224void ShaderProgram::AddShader(const Shader& shader) {
225 if (!impl_->id) {
226 impl_->id = glCreateProgram();
227 if (!impl_->id) {
228 std::cerr << "[Error] Impossible to create a new Shader" << std::endl;
229 }
230 }
231
232 glAttachShader(id(), shader.id());
233}
234
235/// @brief Add a Shader to the program list.
237 glLinkProgram(id());
238}
239
240// Linking shader is an asynchronous process. Using the shader can causes the
241// CPU to wait until its completion. If you need to do some work before the
242// completion, you can use this function and use the Shader only after it
243// becomes ready.
244bool ShaderProgram::IsReady() const {
245 if (g_khr_parallel_shader) {
246 GLint completion_status = {};
247 std::cerr << GL_COMPLETION_STATUS_KHR << std::endl;
248 std::cerr << id() << std::endl;
249 glGetProgramiv(id(), GL_COMPLETION_STATUS_KHR, &completion_status);
250 return completion_status == GL_TRUE;
251 }
252
253 return true;
254}
255
256bool ShaderProgram::LinkStatus() const {
257 GLint result = {};
258 glGetProgramiv(id(), GL_LINK_STATUS, &result);
259 if (result == GL_TRUE) {
260 return true;
261 }
262
263 std::cout << "[Error] linkage error" << std::endl;
264
265 GLsizei logsize = 0;
266 glGetProgramiv(id(), GL_INFO_LOG_LENGTH, &logsize);
267
268 std::vector<char> log(logsize);
269 glGetProgramInfoLog(id(), logsize, &logsize, log.data());
270
271 std::cout << log.data() << std::endl;
272 return false;
273}
274
275/// @brief Return the uniform ID.
276/// @param name The uniform name in the Shader.
277/// @return The GPU uniform ID. Return 0 and display an error if not found.
278GLint ShaderProgram::Uniform(const std::string& name) {
279 auto it = impl_->uniforms.find(name);
280 if (it == impl_->uniforms.end()) {
281 // impl_->uniforms that is not referenced
282 GLint r = glGetUniformLocation(id(), name.c_str());
283
284 if (r == GL_INVALID_OPERATION || r < 0) {
285 std::cerr << "[Error] Uniform " << name << " doesn't exist in program"
286 << std::endl;
287 }
288 // add it anyways
289 impl_->uniforms[name] = r;
290
291 return r;
292 } else {
293 return it->second;
294 }
295}
296
297GLint ShaderProgram::operator[](const std::string& name) {
298 return Uniform(name);
299}
300
301/// @brief Return the GPU attribute id.
302/// @param name The attribute name in the Shader.
303/// @return The GPU attribute ID. Return 0 and display an error if not found.
304GLint ShaderProgram::Attribute(const std::string& name) const {
305 GLint attrib = glGetAttribLocation(id(), name.c_str());
306 if (attrib == GL_INVALID_OPERATION || attrib < 0) {
307 std::cerr << "[Error] Attribute " << name << " doesn't exist in program"
308 << std::endl;
309 }
310
311 return attrib;
312}
313
314/// @brief
315/// Set an OpenGL attribute properties.
316/// @param name
317/// Attribute name in the Shader.
318/// @param size
319/// Specify the number of component per object. One of {1,2,3,4}.
320/// @param stride
321/// Specify the byte offset in between consecutive attribute of
322/// the same kind.
323/// @param offset
324/// Offset of the attribute in the struct.
325/// @param type
326/// The type of data. For instance GL_FLOAT.
327/// @return
328/// The GPU attribute ID. Return 0 and display an error if not found.
329///
330/// @see glVertexAttribPointer
331void ShaderProgram::SetAttribute(const std::string& name,
332 GLint size,
333 GLsizei stride,
334 GLuint offset,
335 GLboolean normalize,
336 GLenum type) const {
337 GLint loc = Attribute(name);
338 glEnableVertexAttribArray(loc);
339 glVertexAttribPointer(loc, size, type, normalize, stride,
340 reinterpret_cast<void*>(offset)); // NOLINT
341}
342
343/// @brief Set an OpenGL attribute properties, assuming data are float.
344/// @see SetAttribute.
345void ShaderProgram::SetAttribute(const std::string& name,
346 GLint size,
347 GLsizei stride,
348 GLuint offset,
349 GLboolean normalize) const {
350 SetAttribute(name, size, stride, offset, normalize, GL_FLOAT);
351}
352
353/// @brief Set an OpenGL attribute properties, assuming data are float.
354/// @see SetAttribute.
355void ShaderProgram::SetAttribute(const std::string& name,
356 GLint size,
357 GLsizei stride,
358 GLuint offset,
359 GLenum type) const {
360 SetAttribute(name, size, stride, offset, false, type);
361}
362
363/// @brief Set an OpenGL attribute properties, assuming data are float.
364/// @see SetAttribute.
365void ShaderProgram::SetAttribute(const std::string& name,
366 GLint size,
367 GLsizei stride,
368 GLuint offset) const {
369 SetAttribute(name, size, stride, offset, false, GL_FLOAT);
370}
371
372/// @brief Assign shader vec3 uniform
373/// @param x First vec3 component.
374/// @param y Second vec3 component.
375/// @param z Third vec3 component
376/// @overload
377void ShaderProgram::SetUniform(const std::string& name,
378 float x,
379 float y,
380 float z) {
381 glUniform3f(Uniform(name), x, y, z);
382}
383
384/// @brief Assign shader vec3 uniform
385/// @param v vec3 value
386/// @overload
387void ShaderProgram::SetUniform(const std::string& name, const vec3& v) {
388 glUniform3fv(Uniform(name), 1, value_ptr(v));
389}
390
391/// @brief Assign shader vec4 uniform
392/// @param v vec4 value
393/// @overload
394void ShaderProgram::SetUniform(const std::string& name, const vec4& v) {
395 glUniform4fv(Uniform(name), 1, value_ptr(v));
396}
397
398/// @brief Assign shader mat4 uniform
399/// @param m mat4 value
400/// @overload
401void ShaderProgram::SetUniform(const std::string& name, const mat4& m) {
402 glUniformMatrix4fv(Uniform(name), 1, GL_FALSE, value_ptr(m));
403}
404
405/// @brief Assign shader mat3 uniform
406/// @param m mat3 value
407/// @overload
408void ShaderProgram::SetUniform(const std::string& name, const mat3& m) {
409 glUniformMatrix3fv(Uniform(name), 1, GL_FALSE, value_ptr(m));
410}
411
412/// @brief Assign shader float uniform
413/// @param val float value
414/// @overload
415void ShaderProgram::SetUniform(const std::string& name, float val) {
416 glUniform1f(Uniform(name), val);
417}
418
419/// @brief Assign shader int uniform
420/// @param val int value
421/// @overload
422void ShaderProgram::SetUniform(const std::string& name, int val) {
423 glUniform1i(Uniform(name), val);
424}
425
426
427/// @brief Bind the ShaderProgram. Future draw will use it. This unbind any
428/// previously bound ShaderProgram.
429void ShaderProgram::Use() const {
430 glUseProgram(id());
431}
432
433/// @brief Unbind the ShaderProgram.
434// NOLINTNEXTLINE
436 glUseProgram(0);
437}
438
439/// @brief The GPU id to the ShaderProgram.
440/// @return The GPU id to the ShaderProgram.
441GLuint ShaderProgram::id() const {
442 return impl_->id;
443}
444
445bool ShaderProgram::operator==(const ShaderProgram& rhs) const {
446 return impl_ == rhs.impl_;
447}
448
449bool ShaderProgram::operator!=(const ShaderProgram& rhs) const {
450 return impl_ != rhs.impl_;
451}
452
453ShaderProgram::~ShaderProgram() = default;
454ShaderProgram::ShaderProgram(ShaderProgram&&) noexcept = default;
455ShaderProgram::ShaderProgram(const ShaderProgram&) = default;
456ShaderProgram& ShaderProgram::operator=(ShaderProgram&&) noexcept = default;
457ShaderProgram& ShaderProgram::operator=(const ShaderProgram&) = default;
458
459
460} // namespace smk
A shader program is a set of shader (for instance vertex shader + pixel shader) defining the renderin...
Definition: Shader.hpp:114
GLint Attribute(const std::string &name) const
Return the GPU attribute id.
Definition: Shader.cpp:304
void SetAttribute(const std::string &name, GLint size, GLsizei stride, GLuint offset, GLboolean normalize, GLenum type) const
Set an OpenGL attribute properties.
Definition: Shader.cpp:331
ShaderProgram()
The constructor. The ShaderProgram is initially invalid. You need to call AddShader and Link before b...
Definition: Shader.cpp:219
GLint Uniform(const std::string &name)
Return the uniform ID.
Definition: Shader.cpp:278
void SetUniform(const std::string &name, float x, float y, float z)
Assign shader vec3 uniform.
Definition: Shader.cpp:377
void Unuse() const
Unbind the ShaderProgram.
Definition: Shader.cpp:435
void AddShader(const Shader &shader)
Add a Shader to the program list. This must called multiple time for each shader components before ca...
Definition: Shader.cpp:224
GLuint id() const
The GPU id to the ShaderProgram.
Definition: Shader.cpp:441
void Use() const
Bind the ShaderProgram. Future draw will use it. This unbind any previously bound ShaderProgram.
Definition: Shader.cpp:429
void Link() const
Add a Shader to the program list.
Definition: Shader.cpp:236
A Shader is a little program that rest on the GPU. They are run on a specific section of the graphic ...
Definition: Shader.hpp:66
bool CompileStatus() const
Wait until the Shader to be ready.
Definition: Shader.cpp:113
static Shader FromFile(const std::string &filename, GLenum type)
Load a shader from a file.
Definition: Shader.cpp:42
GLuint id() const
The GPU shader id.
Definition: Shader.cpp:74
static Shader FromString(const std::string &content, GLenum type)
Load a shader from a std::string.
Definition: Shader.cpp:60
bool IsReady() const
Check the status of a Shader. Linking shader is an asynchronous process. Using the shader can causes ...
Definition: Shader.cpp:100