Branch data Line data Source code
1 : : #pragma once
2 : :
3 : : /*
4 : : MIT License
5 : :
6 : : Copyright (c) 2014-2024 Stephane Cuillerdier (aka aiekick)
7 : :
8 : : Permission is hereby granted, free of charge, to any person obtaining a copy
9 : : of this software and associated documentation files (the "Software"), to deal
10 : : in the Software without restriction, including without limitation the rights
11 : : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 : : copies of the Software, and to permit persons to whom the Software is
13 : : furnished to do so, subject to the following conditions:
14 : :
15 : : The above copyright notice and this permission notice shall be included in all
16 : : copies or substantial portions of the Software.
17 : :
18 : : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 : : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 : : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 : : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 : : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 : : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 : : SOFTWARE.
25 : : */
26 : :
27 : : // ezGL is part of the ezLibs project : https://github.com/aiekick/ezLibs.git
28 : :
29 : :
30 : : #include <vector>
31 : : #include <memory>
32 : : #include <string>
33 : : #include <cassert>
34 : : #include <functional>
35 : :
36 : : #include "ezGL.hpp"
37 : :
38 : : #ifdef IMGUI_INCLUDE
39 : : #include IMGUI_INCLUDE
40 : : #endif
41 : :
42 : : namespace ez {
43 : : namespace gl {
44 : :
45 : : class ProgramAuto;
46 : : typedef std::shared_ptr<ProgramAuto> ProgramAutoPtr;
47 : : typedef std::weak_ptr<ProgramAuto> ProgramAutoWeak;
48 : :
49 : : class ProgramAuto {
50 : : public:
51 : : struct Uniform;
52 : : typedef std::map<GLenum, std::map<std::string, Uniform>> UniformPerShaderTypeContainer;
53 : : typedef std::function<void(FBOPipeLinePtr, Uniform&)> UniformPreUploadFunctor;
54 : : typedef std::function<void(Uniform&)> UniformWidgetFunctor;
55 : : struct Uniform {
56 : : std::string name;
57 : : float* datas_f = nullptr; // float
58 : : int32_t* datas_i = nullptr; // int
59 : : int32_t data_s2d = -1; // sampler2D
60 : : GLint loc = -1;
61 : : GLuint channels = 1U;
62 : : bool used = false;
63 : : bool showed = false;
64 : : BufferBlock* buffer_ptr = nullptr; // a buffer block ex: UBO /SSBO
65 : : int32_t bufferBinding = -1; // the binding point in the sahder of the buffer block
66 : : UniformWidgetFunctor widget_functor = nullptr;
67 : : };
68 : :
69 : : private:
70 : : ProgramAutoWeak m_This;
71 : : GLuint m_ProgramAutoId = 0U;
72 : : std::string m_ProgramAutoName;
73 : : std::map<uintptr_t, ShaderAutoWeak> m_Shaders; // a same shader object can be added two times
74 : : UniformPerShaderTypeContainer m_Uniforms;
75 : : UniformPreUploadFunctor m_UniformPreUploadFunctor = nullptr; // lanbda to execute just before the uniform upload
76 : : UniformsManager m_UniformsManager;
77 : :
78 : : public:
79 : 0 : static ProgramAutoPtr create(const std::string& vProgramAutoName) {
80 : 0 : auto res = std::make_shared<ProgramAuto>();
81 : 0 : res->m_This = res;
82 : 0 : if (!res->init(vProgramAutoName)) {
83 : 0 : res.reset();
84 : 0 : }
85 : 0 : return res;
86 : 0 : }
87 : :
88 : : public:
89 : : ProgramAuto() = default;
90 : 0 : ~ProgramAuto() {
91 : 0 : unit();
92 : 0 : }
93 : 0 : bool init(const std::string& vProgramAutoName) {
94 : 0 : assert(!vProgramAutoName.empty());
95 : 0 : m_ProgramAutoName = vProgramAutoName;
96 : 0 : m_ProgramAutoId = glCreateProgram();
97 : 0 : CheckGLErrors;
98 : 0 : if (m_ProgramAutoId > 0U) {
99 : 0 : return true;
100 : 0 : }
101 : 0 : return false;
102 : 0 : }
103 : 0 : bool addShader(ShaderAutoWeak vShader) {
104 : 0 : if (!vShader.expired()) {
105 : 0 : m_Shaders[(uintptr_t)vShader.lock().get()] = vShader;
106 : 0 : return true;
107 : 0 : }
108 : 0 : return false;
109 : 0 : }
110 : 0 : bool link() {
111 : 0 : bool res = false;
112 : 0 : if (m_ProgramAutoId > 0U) {
113 : 0 : bool one_shader_at_least = false;
114 : 0 : for (auto& shader : m_Shaders) {
115 : 0 : auto ptr = shader.second.lock();
116 : 0 : if (ptr != nullptr) {
117 : 0 : one_shader_at_least = true;
118 : 0 : glAttachShader(m_ProgramAutoId, ptr->getShaderId());
119 : 0 : CheckGLErrors;
120 : 0 : // we could delete shader id after linking,
121 : 0 : // but we dont since we can have many shader for the same program
122 : 0 : }
123 : 0 : }
124 : 0 : if (one_shader_at_least) {
125 : 0 : glLinkProgram(m_ProgramAutoId);
126 : 0 : CheckGLErrors;
127 : 0 : glFinish();
128 : 0 : GLint linked = 0;
129 : 0 : glGetProgramiv(m_ProgramAutoId, GL_LINK_STATUS, &linked);
130 : 0 : CheckGLErrors;
131 : 0 : if (!linked) {
132 : 0 : if (!printProgramAutoLogs(m_ProgramAutoName, "Link Errors")) {
133 : 0 : LogVarError("ProgramAuto \"%s\" linking fail for unknown reason", m_ProgramAutoName.c_str());
134 : 0 : }
135 : 0 : res = false;
136 : 0 : } else {
137 : 0 : printProgramAutoLogs(m_ProgramAutoName, "Link Warnings");
138 : 0 : res = true;
139 : 0 : }
140 : 0 : }
141 : 0 : }
142 : 0 : return res;
143 : 0 : }
144 : 0 : void setUniformPreUploadFunctor(UniformPreUploadFunctor vUniformPreUploadFunctor) { m_UniformPreUploadFunctor = vUniformPreUploadFunctor; }
145 : 0 : void addBufferBlock(const GLenum vShaderType, const std::string& vBufferName, const int32_t vBinding, BufferBlock* vBufferPtr) {
146 : 0 : assert(vShaderType > 0);
147 : 0 : assert(!vBufferName.empty());
148 : 0 : assert(vBinding > 0);
149 : 0 : assert(vBufferPtr != nullptr);
150 : 0 : Uniform uni;
151 : 0 : uni.bufferBinding = vBinding;
152 : 0 : uni.buffer_ptr = vBufferPtr;
153 : 0 : m_Uniforms[vShaderType][vBufferName] = uni;
154 : 0 : }
155 : : void addUniformFloat(const GLenum vShaderType, const std::string& vUniformName, float* vUniformPtr, const GLuint vCountChannels,
156 : 0 : const bool vShowWidget, const UniformWidgetFunctor& vWidgetFunctor) {
157 : 0 : assert(vShaderType > 0);
158 : 0 : assert(!vUniformName.empty());
159 : 0 : assert(vUniformPtr != nullptr);
160 : 0 : assert(vCountChannels > 0U);
161 : 0 : Uniform uni;
162 : 0 : uni.name = vUniformName;
163 : 0 : uni.showed = vShowWidget;
164 : 0 : uni.datas_f = vUniformPtr;
165 : 0 : uni.channels = vCountChannels;
166 : 0 : uni.widget_functor = vWidgetFunctor;
167 : 0 : m_Uniforms[vShaderType][vUniformName] = uni;
168 : 0 : }
169 : : void addUniformInt(const GLenum vShaderType, const std::string& vUniformName, int32_t* vUniformPtr, const GLuint vCountChannels,
170 : 0 : const bool vShowWidget, const UniformWidgetFunctor& vWidgetFunctor) {
171 : 0 : assert(vShaderType > 0);
172 : 0 : assert(!vUniformName.empty());
173 : 0 : assert(vUniformPtr != nullptr);
174 : 0 : assert(vCountChannels > 0U);
175 : 0 : Uniform uni;
176 : 0 : uni.name = vUniformName;
177 : 0 : uni.showed = vShowWidget;
178 : 0 : uni.datas_i = vUniformPtr;
179 : 0 : uni.channels = vCountChannels;
180 : 0 : uni.widget_functor = vWidgetFunctor;
181 : 0 : m_Uniforms[vShaderType][vUniformName] = uni;
182 : 0 : }
183 : : void addUniformSampler2D(const GLenum vShaderType, const std::string& vUniformName, int32_t vSampler2D,
184 : 0 : const bool vShowWidget, const UniformWidgetFunctor& vWidgetFunctor) {
185 : 0 : assert(vShaderType > 0);
186 : 0 : assert(!vUniformName.empty());
187 : 0 : // assert(vSampler2D != -1);, if the sampler must point on a buffer after, its normal to have it at -1
188 : 0 : Uniform uni;
189 : 0 : uni.name = vUniformName;
190 : 0 : uni.showed = vShowWidget;
191 : 0 : uni.data_s2d = vSampler2D;
192 : 0 : uni.widget_functor = vWidgetFunctor;
193 : 0 : m_Uniforms[vShaderType][vUniformName] = uni;
194 : 0 : }
195 : 0 : void uploadUniforms(FBOPipeLinePtr vFBOPipeLinePtr) {
196 : 0 : #ifdef PROFILER_SCOPED
197 : 0 : PROFILER_SCOPED(m_ProgramAutoName, "uploadUniforms");
198 : 0 : #endif
199 : 0 : int32_t textureSlotId = 0;
200 : 0 : for (auto& shader_type : m_Uniforms) {
201 : 0 : for (auto& uni : shader_type.second) {
202 : 0 : if (m_UniformPreUploadFunctor != nullptr) {
203 : 0 : m_UniformPreUploadFunctor(vFBOPipeLinePtr, uni.second);
204 : 0 : }
205 : 0 : if (uni.second.used) {
206 : 0 : if (uni.second.datas_f != nullptr) {
207 : 0 : switch (uni.second.channels) {
208 : 0 : case 1U: glUniform1fv(uni.second.loc, 1, uni.second.datas_f); break;
209 : 0 : case 2U: glUniform2fv(uni.second.loc, 1, uni.second.datas_f); break;
210 : 0 : case 3U: glUniform3fv(uni.second.loc, 1, uni.second.datas_f); break;
211 : 0 : case 4U: glUniform4fv(uni.second.loc, 1, uni.second.datas_f); break;
212 : 0 : }
213 : 0 : CheckGLErrors;
214 : 0 : } else if (uni.second.datas_i != nullptr) {
215 : 0 : switch (uni.second.channels) {
216 : 0 : case 1U: glUniform1iv(uni.second.loc, 1, uni.second.datas_i); break;
217 : 0 : case 2U: glUniform2iv(uni.second.loc, 1, uni.second.datas_i); break;
218 : 0 : case 3U: glUniform3iv(uni.second.loc, 1, uni.second.datas_i); break;
219 : 0 : case 4U: glUniform4iv(uni.second.loc, 1, uni.second.datas_i); break;
220 : 0 : }
221 : 0 : CheckGLErrors;
222 : 0 : } else if (uni.second.data_s2d > -1) {
223 : 0 : glActiveTexture(GL_TEXTURE0 + textureSlotId);
224 : 0 : CheckGLErrors;
225 : 0 : glBindTexture(GL_TEXTURE_2D, uni.second.data_s2d);
226 : 0 : CheckGLErrors;
227 : 0 : glUniform1i(uni.second.loc, textureSlotId);
228 : 0 : CheckGLErrors;
229 : 0 : ++textureSlotId;
230 : 0 : }
231 : 0 : }
232 : 0 : }
233 : 0 : }
234 : 0 : }
235 : : #ifdef IMGUI_INCLUDE
236 : : void drawUniformWidgets() {
237 : : ImGui::PushID(m_ProgramAutoName.c_str());
238 : : if (ImGui::CollapsingHeader(m_ProgramAutoName.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) {
239 : : ImGui::Indent();
240 : : for (auto& shader_type : m_Uniforms) {
241 : : switch (shader_type.first) {
242 : : case GL_VERTEX_SHADER: ImGui::Text("%s", "Stage Vertex"); break;
243 : : case GL_FRAGMENT_SHADER: ImGui::Text("%s", "Stage Fragment"); break;
244 : : case GL_TESS_EVALUATION_SHADER: ImGui::Text("%s", "Stage Tesselation Evaluation"); break;
245 : : case GL_TESS_CONTROL_SHADER: ImGui::Text("%s", "Stage Tesselation Control"); break;
246 : : }
247 : : ImGui::Indent();
248 : : for (auto& uni : shader_type.second) {
249 : : if (uni.second.showed && uni.second.used) {
250 : : if (uni.second.widget_functor != nullptr) {
251 : : uni.second.widget_functor(uni.second);
252 : : } else {
253 : : if (uni.second.datas_f != nullptr) {
254 : : switch (uni.second.channels) {
255 : : case 1U: ImGui::DragFloat(uni.second.name.c_str(), uni.second.datas_f); break;
256 : : case 2U: ImGui::DragFloat2(uni.second.name.c_str(), uni.second.datas_f); break;
257 : : case 3U: ImGui::DragFloat3(uni.second.name.c_str(), uni.second.datas_f); break;
258 : : case 4U: ImGui::DragFloat4(uni.second.name.c_str(), uni.second.datas_f); break;
259 : : }
260 : : } else if (uni.second.datas_i != nullptr) {
261 : : switch (uni.second.channels) {
262 : : case 1U: ImGui::DragInt(uni.second.name.c_str(), uni.second.datas_i); break;
263 : : case 2U: ImGui::DragInt2(uni.second.name.c_str(), uni.second.datas_i); break;
264 : : case 3U: ImGui::DragInt3(uni.second.name.c_str(), uni.second.datas_i); break;
265 : : case 4U: ImGui::DragInt4(uni.second.name.c_str(), uni.second.datas_i); break;
266 : : }
267 : : }
268 : : }
269 : : }
270 : : }
271 : : ImGui::Unindent();
272 : : }
273 : : ImGui::Unindent();
274 : : }
275 : : ImGui::PopID();
276 : : }
277 : : #endif
278 : 0 : void locateUniforms() {
279 : 0 : assert(m_ProgramAutoId > 0U);
280 : 0 : const char* stage_name = nullptr;
281 : 0 : for (auto& shader_type : m_Uniforms) {
282 : 0 : switch (shader_type.first) {
283 : 0 : case GL_VERTEX_SHADER: stage_name = "VERTEX"; break;
284 : 0 : case GL_FRAGMENT_SHADER: stage_name = "FRAGMENT"; break;
285 : 0 : case GL_TESS_EVALUATION_SHADER: stage_name = "TESSEVAL"; break;
286 : 0 : case GL_TESS_CONTROL_SHADER: stage_name = "TESSCTRL"; break;
287 : 0 : }
288 : 0 : for (auto& uni : shader_type.second) {
289 : 0 : uni.second.loc = glGetUniformLocation(m_ProgramAutoId, uni.second.name.c_str());
290 : 0 : CheckGLErrors;
291 : 0 : uni.second.used = (uni.second.loc > -1);
292 : 0 : if (uni.second.loc == -1) {
293 : 0 : LogVarInfo("ProgramAuto \'%s\' Stage \'%s\' is not using the uniform \'%s\'", m_ProgramAutoName.c_str(), stage_name, uni.second.name.c_str());
294 : 0 : }
295 : 0 : }
296 : 0 : }
297 : 0 : }
298 : 0 : void unit() {
299 : 0 : if (m_ProgramAutoId > 0U) {
300 : 0 : glDeleteProgram(m_ProgramAutoId);
301 : 0 : CheckGLErrors;
302 : 0 : m_ProgramAutoId = 0U;
303 : 0 : }
304 : 0 : }
305 : 0 : bool use() {
306 : 0 : if (m_ProgramAutoId > 0U) {
307 : 0 : glUseProgram(m_ProgramAutoId);
308 : 0 : CheckGLErrors;
309 : 0 : return true;
310 : 0 : }
311 : 0 : return false;
312 : 0 : }
313 : 0 : void unuse() {
314 : 0 : glUseProgram(0);
315 : 0 : }
316 : :
317 : : private:
318 : 0 : bool printProgramAutoLogs(const std::string& vProgramAutoName, const std::string& vLogTypes) {
319 : 0 : assert(!vProgramAutoName.empty());
320 : 0 : assert(!vLogTypes.empty());
321 : 0 : if (m_ProgramAutoId > 0U) {
322 : 0 : GLint infoLen = 0;
323 : 0 : glGetProgramiv(m_ProgramAutoId, GL_INFO_LOG_LENGTH, &infoLen);
324 : 0 : CheckGLErrors;
325 : 0 : if (infoLen > 1) {
326 : 0 : char* infoLog = new char[infoLen];
327 : 0 : glGetProgramInfoLog(m_ProgramAutoId, infoLen, nullptr, infoLog);
328 : 0 : CheckGLErrors;
329 : 0 : LogVarLightInfo("#### PROGRAM %s ####", vProgramAutoName.c_str());
330 : 0 : LogVarLightInfo("%s : %s", vLogTypes.c_str(), infoLog);
331 : 0 : delete[] infoLog;
332 : 0 : return true;
333 : 0 : }
334 : 0 : }
335 : 0 : return false;
336 : 0 : }
337 : : };
338 : :
339 : : } // namespace gl
340 : : } // namespace ez
|