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 : : #include "ezGL.hpp"
30 : : #include <unordered_map>
31 : : #include <vector>
32 : : #include <string>
33 : : #include <memory>
34 : : #include <cstring> // memset, memcpy
35 : : #include <cmath>
36 : :
37 : : #define PRINT_BLOCK_DATAS
38 : :
39 : : namespace ez {
40 : : namespace gl {
41 : :
42 : : class BufferBlock;
43 : : using BufferBlockPtr = std::unique_ptr<BufferBlock>;
44 : :
45 : : class BufferBlock {
46 : : public:
47 : 0 : static BufferBlockPtr create(const std::string& vName, GLenum vTarget) {
48 : 0 : return BufferBlockPtr(new BufferBlock(vName, vTarget)); // cpp11
49 : 0 : }
50 : :
51 : : protected:
52 : : GLuint m_buffer = 0; // buffer id
53 : : GLenum m_target = 0; // ex : GL_UNIFORM_BUFFER;
54 : : std::string m_name;
55 : :
56 : : public:
57 : : BufferBlock() = default;
58 : 0 : explicit BufferBlock(const std::string& vName, GLenum vTarget) : m_name(vName), m_target(vTarget) { m_create(); }
59 : 0 : virtual ~BufferBlock() { m_destroy(); }
60 : 0 : const std::string& getName() const { return m_name; }
61 : 0 : void upload(GLenum vUsage, const void* vData, size_t vSize) {
62 : 0 : #ifdef PROFILER_SCOPED_PTR
63 : 0 : PROFILER_SCOPED_PTR(this, "upload BufferBlock ", "%s", m_name.c_str());
64 : 0 : #endif
65 : 0 : glBindBuffer(m_target, m_buffer);
66 : 0 : glBufferData(m_target, vSize, vData, vUsage);
67 : 0 : CheckGLErrors;
68 : 0 : }
69 : 0 : void bind(GLuint vBinding) {
70 : 0 : glBindBufferBase(m_target, vBinding, m_buffer);
71 : 0 : CheckGLErrors;
72 : 0 : }
73 : 0 : GLuint id() const { return m_buffer; }
74 : :
75 : : private:
76 : 0 : void m_create() {
77 : 0 : glGenBuffers(1, &m_buffer);
78 : 0 : CheckGLErrors;
79 : 0 : }
80 : 0 : void m_destroy() {
81 : 0 : if (m_buffer != 0) {
82 : 0 : glDeleteBuffers(1, &m_buffer);
83 : 0 : CheckGLErrors;
84 : 0 : m_buffer = 0;
85 : 0 : }
86 : 0 : }
87 : : };
88 : :
89 : : /*
90 : : usage :
91 : : struct Datas {
92 : : fvec2 p;
93 : : fvec2 t
94 : : };
95 : : BufferBlockAuto<GL_SHADER_STORAGE_BUFFER, Datas> ssbo;
96 : : ssbo.getDatasRef().push_back(Datas{}};
97 : : ssbo.upload(GL_STATIC_DRAW);
98 : : */
99 : : template <GLenum TTARGET, typename TDATAS>
100 : : class BufferBlockAuto;
101 : : template <GLenum TTARGET, typename TDATAS>
102 : : using BufferBlockAutoPtr = std::unique_ptr<BufferBlockAuto<TTARGET, TDATAS>>;
103 : :
104 : : template <GLenum TTARGET, typename TDATAS>
105 : : class BufferBlockAuto {
106 : : public:
107 : : static BufferBlockAutoPtr<TTARGET, TDATAS> create(const std::string& vName) {
108 : : return BufferBlockAutoPtr<TTARGET, TDATAS>(new BufferBlockAuto<TTARGET, TDATAS>(vName)); // cpp11
109 : : }
110 : :
111 : : private:
112 : : BufferBlockPtr mp_buffer;
113 : : std::vector<TDATAS> m_datas;
114 : :
115 : : public:
116 : : BufferBlockAuto() = default;
117 : : explicit BufferBlockAuto(const std::string& vName) { mp_buffer = BufferBlock::create(vName, TTARGET); }
118 : : virtual ~BufferBlockAuto() = default;
119 : : const std::vector<TDATAS>& getDatas() const { return m_datas; }
120 : : std::vector<TDATAS>& getDatasRef() { return m_datas; }
121 : : GLuint getId() const { return mp_buffer->id(); }
122 : : void bind(GLuint vBinding) { mp_buffer->bind(vBinding); }
123 : : void upload(GLenum vUsage) { mp_buffer->upload(vUsage, m_datas.data(), sizeof(TDATAS) * m_datas.size()); }
124 : : BufferBlock* getBufferBlockPtr() { return mp_buffer.get(); } // for adding in es::gl::program
125 : : };
126 : : template <typename TDATAS>
127 : : using SSBO = BufferBlockAuto<GL_SHADER_STORAGE_BUFFER, TDATAS>;
128 : : template <typename TDATAS>
129 : : using SSBOPtr = std::unique_ptr<SSBO<TDATAS>>;
130 : :
131 : : // UBOAuto : layout std140
132 : : // this ubo can be udpated dynamically by code and will manage automatically resources and layout std140
133 : :
134 : : class UBOAuto {
135 : : private:
136 : : BufferBlockPtr mp_buffer;
137 : : std::string m_name;
138 : : // key = uniform name, offset in buffer for have op on uniform data
139 : : std::unordered_map<std::string, uint32_t> m_offsets;
140 : : // uniforms datas buffer
141 : : std::vector<uint8_t> m_datas;
142 : : // if he is dirty, value has been changed and mut be uploaded in gpu memory
143 : : // dirty at first for init in gpu memory
144 : : bool m_isDirty = true;
145 : :
146 : : public:
147 : 0 : UBOAuto(const std::string& vName) : m_name(vName) { create(); }
148 : 0 : ~UBOAuto() { unit(); }
149 : 0 : bool init() { return true; }
150 : 0 : void unit() { clear(); }
151 : 0 : void clear() {
152 : 0 : m_datas.clear();
153 : 0 : m_offsets.clear();
154 : 0 : m_isDirty = false;
155 : 0 : }
156 : 0 : void setDirty() { m_isDirty = true; }
157 : 0 : bool isDirty() const { return m_isDirty; }
158 : 0 : void create() {
159 : 0 : mp_buffer = BufferBlock::create(m_name, GL_UNIFORM_BUFFER);
160 : 0 : mp_buffer->upload(GL_DYNAMIC_DRAW, m_datas.data(), m_datas.size());
161 : 0 : }
162 : 0 : void destroy() { mp_buffer.reset(); }
163 : 0 : bool recreate() {
164 : 0 : bool res = false;
165 : 0 : if (!m_datas.empty() && mp_buffer->id() != 0U) {
166 : 0 : destroy();
167 : 0 : create();
168 : 0 : res = true;
169 : 0 : }
170 : 0 : return res;
171 : 0 : }
172 : 0 : void uploadIfDirty() {
173 : 0 : if (m_isDirty && !m_datas.empty()) {
174 : 0 : mp_buffer->upload(GL_DYNAMIC_DRAW, m_datas.data(), m_datas.size());
175 : 0 : m_isDirty = false;
176 : 0 : }
177 : 0 : }
178 : 0 : void bind(const uint32_t vBinding) { mp_buffer->bind(vBinding); }
179 : 0 : GLuint id() const { return mp_buffer->id(); }
180 : : // add size to uniform block, return startOffset
181 : 0 : bool registerByteSize(const std::string& vKey, uint32_t vSizeInBytes, uint32_t* vStartOffset) {
182 : 0 : if (offsetExist(vKey)) {
183 : 0 : LogVarDebugWarning("Debug : key %s is already defined in buffer. %s fail.", vKey.c_str(), __FUNCTION__);
184 : 0 : } else if (vSizeInBytes > 0) {
185 : 0 : uint32_t newSize = vSizeInBytes;
186 : 0 : uint32_t lastOffset = (uint32_t)m_datas.size();
187 : 0 : auto baseAlign = getStd140Alignment(newSize);
188 : 0 : // il faut trouver le prochain offset qui est multiple de baseAlign
189 : 0 : auto startOffset = baseAlign * static_cast<uint32_t>(std::ceil(static_cast<double>(lastOffset) / static_cast<double>(baseAlign)));
190 : 0 : auto newSizeToAllocate = startOffset - lastOffset + newSize;
191 : 0 : #ifdef PRINT_BLOCK_DATAS
192 : 0 : auto endOffset = startOffset + newSize;
193 : 0 : LogVarDebugInfo(
194 : 0 : "key %s, size %u, align %u, Offsets : %u => %u, size to alloc %u", vKey.c_str(), newSize, baseAlign, startOffset, endOffset, newSizeToAllocate);
195 : 0 : #endif
196 : 0 : m_datas.resize(lastOffset + newSizeToAllocate);
197 : 0 : // on set de "lastOffset" a "lastOffset + newSizeToAllocate"
198 : 0 : std::memset(m_datas.data() + lastOffset, 0, newSizeToAllocate);
199 : 0 : addOffsetForKey(vKey, startOffset);
200 : 0 : if (vStartOffset) {
201 : 0 : *vStartOffset = startOffset;
202 : 0 : }
203 : 0 : return true;
204 : 0 : }
205 : 0 : return false;
206 : 0 : }
207 : :
208 : : // templates (defined under class)
209 : : // add a variable
210 : : template <typename T>
211 : : void registerVar(const std::string& vKey, T vValue); // add var to uniform block
212 : : template <typename T>
213 : : void registerVar(const std::string& vKey, T* vValue, uint32_t vSizeInBytes); // add var to uniform block
214 : :
215 : : // Get / set + op on variables
216 : : template <typename T>
217 : : bool getVar(const std::string& vKey, T& vValue); // Get
218 : : template <typename T>
219 : : bool setVar(const std::string& vKey, T vValue); // set
220 : : template <typename T>
221 : : bool setVar(const std::string& vKey, T* vValue, uint32_t vSizeInBytes); // set
222 : : template <typename T>
223 : : bool setAddVar(const std::string&, T vValue); // add and set like +=
224 : :
225 : : private:
226 : 0 : void addOffsetForKey(const std::string& vKey, uint32_t vOffset) { m_offsets[vKey] = vOffset; }
227 : 0 : bool offsetExist(const std::string& vKey) { return m_offsets.find(vKey) != m_offsets.end(); }
228 : :
229 : : protected:
230 : : #if 1
231 : : // tested
232 : 0 : uint32_t getStd140Alignment(uint32_t vSize) {
233 : 0 : auto goodAlign = static_cast<uint32_t>(std::pow(2, std::ceil(std::log(vSize) / std::log(2))));
234 : 0 : if (goodAlign > 16U) {
235 : 0 : return 16U;
236 : 0 : }
237 : 0 : return goodAlign;
238 : 0 : }
239 : : #else
240 : : // maybe closer to the std140, to test
241 : : uint32_t getStd140Alignment(uint32_t vSize) {
242 : : if (vSize <= 4) {
243 : : return 4;
244 : : }
245 : : if (vSize <= 8) {
246 : : return 8;
247 : : }
248 : : return 16;
249 : : }
250 : : // ou un mix des deux ?
251 : : uint32_t getStd140Alignment(uint32_t vSize) {
252 : : if (vSize <= 16) {
253 : : if (vSize <= 4)
254 : : return 4; // float, int, uint
255 : : if (vSize <= 8)
256 : : return 8; // vec2, ivec2
257 : : return 16; // vec3 (12), vec4 (16), mat3 row (12)
258 : : }
259 : :
260 : : // vSize > 16 → struct, array, mat*, etc.
261 : : uint32_t pow2 = 1 << (uint32_t)std::ceil(std::log2(vSize));
262 : : return std::min(pow2, 16u); // clip max align à 16
263 : : }
264 : : #endif
265 : : };
266 : :
267 : : ////////////////////////////////////////////////////////////////////////////////////////////////////////////
268 : : //// PUBLIC TEMPLATES //////////////////////////////////////////////////////////////////////////////////////
269 : : ////////////////////////////////////////////////////////////////////////////////////////////////////////////
270 : :
271 : : template <typename T>
272 : : void UBOAuto::registerVar(const std::string& vKey, T* vValue, uint32_t vSizeInBytes) {
273 : : uint32_t startOffset;
274 : : if (registerByteSize(vKey, vSizeInBytes, &startOffset)) {
275 : : // on copy de "startOffset" a "startOffset + vSizeInBytes"
276 : : std::memcpy(m_datas.data() + startOffset, vValue, vSizeInBytes);
277 : : }
278 : : }
279 : :
280 : : template <typename T>
281 : : void UBOAuto::registerVar(const std::string& vKey, T vValue) {
282 : : registerVar(vKey, &vValue, sizeof(vValue));
283 : : }
284 : :
285 : : template <typename T>
286 : : bool UBOAuto::getVar(const std::string& vKey, T& vValue) {
287 : : if (offsetExist(vKey)) {
288 : : uint32_t offset = m_offsets[vKey];
289 : : uint32_t size = sizeof(vValue);
290 : : std::memcpy(&vValue, m_datas.data() + offset, size);
291 : : return true;
292 : : }
293 : : LogVarDebugInfo("Debug : key %s not exist in buffer. %s fail.", vKey.c_str(), __FUNCTION__);
294 : : return false;
295 : : }
296 : :
297 : : template <typename T>
298 : : bool UBOAuto::setVar(const std::string& vKey, T* vValue, uint32_t vSizeInBytes) {
299 : : if (offsetExist(vKey) && vSizeInBytes > 0) {
300 : : uint32_t newSize = vSizeInBytes;
301 : : uint32_t offset = m_offsets[vKey];
302 : : std::memcpy(m_datas.data() + offset, vValue, newSize);
303 : : m_isDirty = true;
304 : : return true;
305 : : }
306 : : LogVarDebugInfo("Debug : key %s not exist in buffer. %s fail.", vKey.c_str(), __FUNCTION__);
307 : : return false;
308 : : }
309 : :
310 : : template <typename T>
311 : : bool UBOAuto::setVar(const std::string& vKey, T vValue) {
312 : : return setVar(vKey, &vValue, sizeof(vValue));
313 : : }
314 : :
315 : : template <typename T>
316 : : bool UBOAuto::setAddVar(const std::string& vKey, T vValue) {
317 : : T v;
318 : : if (getVar(vKey, v)) {
319 : : v += vValue;
320 : : return setVar(vKey, v);
321 : : }
322 : : LogVarDebugInfo("Debug : key %s not exist in buffer. %s fail.", vKey.c_str(), __FUNCTION__);
323 : : return false;
324 : : }
325 : :
326 : : } // namespace gl
327 : : } // namespace ez
|