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 "ezGL.hpp"
31 : :
32 : : #ifdef STB_IMAGE_READER_INCLUDE
33 : : #include STB_IMAGE_READER_INCLUDE
34 : : #endif // STB_IMAGE_READER_INCLUDE
35 : :
36 : : #ifdef STB_IMAGE_WRITER_INCLUDE
37 : : #include STB_IMAGE_WRITER_INCLUDE
38 : : #endif // STB_IMAGE_WRITER_INCLUDE
39 : :
40 : : #include <algorithm>
41 : : #include <cassert>
42 : : #include <memory>
43 : : #include <string>
44 : : #include <array>
45 : :
46 : : namespace ez {
47 : : namespace gl {
48 : :
49 : : class Texture;
50 : : typedef std::shared_ptr<Texture> TexturePtr;
51 : : typedef std::weak_ptr<Texture> TextureWeak;
52 : :
53 : : class Texture {
54 : : public:
55 : : // wrap (repeat|mirror|clamp), filter (linear|nearest)
56 : 0 : static TexturePtr createEmpty(const GLsizei vSx, const GLsizei vSy, const std::string vWrap, const std::string vFilter, const bool vEnableMipMap) {
57 : 0 : auto res = std::make_shared<Texture>();
58 : 0 : res->m_This = res;
59 : 0 : if (!res->initEmpty(vSx, vSy, vWrap, vFilter, vEnableMipMap)) {
60 : 0 : res.reset();
61 : 0 : }
62 : 0 : return res;
63 : 0 : }
64 : : // wrap (repeat|mirror|clamp), filter (linear|nearest)
65 : : static TexturePtr createFromBuffer(
66 : : const uint8_t* vBuffer,
67 : : const GLsizei vSx,
68 : : const GLsizei vSy,
69 : : const GLenum vInternalFormat,
70 : : const GLenum vFormat,
71 : : const GLenum vPixelFormat,
72 : : const std::string vWrap,
73 : : const std::string vFilter,
74 : 0 : const bool vEnableMipMap) {
75 : 0 : auto res = std::make_shared<Texture>();
76 : 0 : res->m_This = res;
77 : 0 : if (!res->initFromBuffer(vBuffer, vSx, vSy, vInternalFormat, vFormat, vPixelFormat, vWrap, vFilter, vEnableMipMap)) {
78 : 0 : res.reset();
79 : 0 : }
80 : 0 : return res;
81 : 0 : }
82 : : // wrap (repeat|mirror|clamp), filter (linear|nearest)
83 : : static TexturePtr createFromBuffer(
84 : : const uint8_t* vBuffer,
85 : : const GLsizei vSx,
86 : : const GLsizei vSy,
87 : : const GLint vChannelsCount,
88 : : const GLenum vPixelFormat,
89 : : const std::string vWrap,
90 : : const std::string vFilter,
91 : 0 : const bool vEnableMipMap) {
92 : 0 : auto res = std::make_shared<Texture>();
93 : 0 : res->m_This = res;
94 : 0 : if (!res->initFromBuffer(vBuffer, vSx, vSy, vChannelsCount, vPixelFormat, vWrap, vFilter, vEnableMipMap)) {
95 : 0 : res.reset();
96 : 0 : }
97 : 0 : return res;
98 : 0 : }
99 : : #ifdef STB_IMAGE_READER_INCLUDE
100 : : // wrap (repeat|mirror|clamp), filter (linear|nearest)
101 : : static TexturePtr createFromFile(const std::string& vFilePathName, bool vInvertY, std::string vWrap, std::string vFilter, bool vEnableMipMap) {
102 : : auto res = std::make_shared<Texture>();
103 : : res->m_This = res;
104 : : if (!res->initFromFile(vFilePathName, vInvertY, vWrap, vFilter, vEnableMipMap)) {
105 : : res.reset();
106 : : }
107 : : return res;
108 : : }
109 : : #endif // STB_IMAGE_READER_INCLUDE
110 : :
111 : : private:
112 : : TextureWeak m_This;
113 : : GLuint m_TexId = 0U;
114 : : bool m_EnableMipMap = false;
115 : : GLsizei m_Width = 0U;
116 : : GLsizei m_Height = 0U;
117 : : GLuint m_ChannelsCount = 0U;
118 : : GLenum m_Format = GL_RGBA;
119 : : GLenum m_InternalFormat = GL_RGBA32F;
120 : : GLenum m_PixelFormat = GL_FLOAT;
121 : : GLenum m_WrapS = GL_REPEAT;
122 : : GLenum m_WrapT = GL_REPEAT;
123 : : GLenum m_MinFilter = GL_LINEAR_MIPMAP_LINEAR;
124 : : GLenum m_MagFilter = GL_LINEAR;
125 : :
126 : : public:
127 : : Texture() = default;
128 : 0 : ~Texture() { unit(); }
129 : 0 : bool initEmpty(const GLsizei vSx, const GLsizei vSy, const std::string vWrap, const std::string vFilter, const bool vEnableMipMap) {
130 : 0 : assert(vSx > 0);
131 : 0 : assert(vSy > 0);
132 : 0 : m_Width = vSx;
133 : 0 : m_Height = vSy;
134 : 0 : m_EnableMipMap = vEnableMipMap;
135 : 0 : glGenTextures(1, &m_TexId);
136 : 0 : CheckGLErrors;
137 : 0 : glBindTexture(GL_TEXTURE_2D, m_TexId);
138 : 0 : CheckGLErrors;
139 : 0 : m_setFormat(GL_FLOAT, 4);
140 : 0 : glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
141 : 0 : glTexImage2D(GL_TEXTURE_2D, 0, m_InternalFormat, vSx, vSy, 0, m_Format, GL_FLOAT, nullptr);
142 : 0 : CheckGLErrors;
143 : 0 : glFinish();
144 : 0 : CheckGLErrors;
145 : 0 : m_setParameters(vWrap, vFilter, vEnableMipMap);
146 : 0 : glFinish();
147 : 0 : CheckGLErrors;
148 : 0 : glBindTexture(GL_TEXTURE_2D, 0);
149 : 0 : CheckGLErrors;
150 : 0 : return check();
151 : 0 : }
152 : : // wrap (repeat|mirror|clamp), filter (linear|nearest)
153 : : bool initFromBuffer(
154 : : const uint8_t* vBuffer,
155 : : const GLsizei vSx,
156 : : const GLsizei vSy,
157 : : const GLenum vInternalFormat,
158 : : const GLenum vFormat,
159 : : const GLenum vPixelFormat,
160 : : const std::string vWrap,
161 : : const std::string vFilter,
162 : 0 : const bool vEnableMipMap) {
163 : 0 : assert(vBuffer != nullptr);
164 : 0 : assert(vSx > 0);
165 : 0 : assert(vSy > 0);
166 : 0 : m_Width = vSx;
167 : 0 : m_Height = vSy;
168 : 0 : glGenTextures(1, &m_TexId);
169 : 0 : CheckGLErrors;
170 : 0 : glBindTexture(GL_TEXTURE_2D, m_TexId);
171 : 0 : CheckGLErrors;
172 : 0 : m_InternalFormat = vInternalFormat;
173 : 0 : m_Format = vFormat;
174 : 0 : m_PixelFormat = vPixelFormat;
175 : 0 : glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
176 : 0 : glTexImage2D(GL_TEXTURE_2D, 0, m_InternalFormat, m_Width, m_Height, 0, m_Format, m_PixelFormat, vBuffer);
177 : 0 : CheckGLErrors;
178 : 0 : glFinish();
179 : 0 : CheckGLErrors;
180 : 0 : m_setParameters(vWrap, vFilter, vEnableMipMap);
181 : 0 : glFinish();
182 : 0 : CheckGLErrors;
183 : 0 : glBindTexture(GL_TEXTURE_2D, 0);
184 : 0 : CheckGLErrors;
185 : 0 : return check();
186 : 0 : }
187 : : // wrap (repeat|mirror|clamp), filter (linear|nearest)
188 : : bool initFromBuffer(
189 : : const uint8_t* vBuffer,
190 : : const GLsizei vSx,
191 : : const GLsizei vSy,
192 : : const GLint vChannelsCount,
193 : : const GLenum vPixelFormat,
194 : : const std::string vWrap,
195 : : const std::string vFilter,
196 : 0 : const bool vEnableMipMap) {
197 : 0 : assert(vBuffer != nullptr);
198 : 0 : assert(vSx > 0);
199 : 0 : assert(vSy > 0);
200 : 0 : assert(vChannelsCount > 0);
201 : 0 : m_Width = vSx;
202 : 0 : m_Height = vSy;
203 : 0 : glGenTextures(1, &m_TexId);
204 : 0 : CheckGLErrors;
205 : 0 : glBindTexture(GL_TEXTURE_2D, m_TexId);
206 : 0 : CheckGLErrors;
207 : 0 : m_setFormat(vPixelFormat, vChannelsCount);
208 : 0 : glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
209 : 0 : glTexImage2D(GL_TEXTURE_2D, 0, m_InternalFormat, m_Width, m_Height, 0, m_Format, m_PixelFormat, vBuffer);
210 : 0 : CheckGLErrors;
211 : 0 : glFinish();
212 : 0 : CheckGLErrors;
213 : 0 : m_setParameters(vWrap, vFilter, vEnableMipMap);
214 : 0 : glFinish();
215 : 0 : CheckGLErrors;
216 : 0 : glBindTexture(GL_TEXTURE_2D, 0);
217 : 0 : CheckGLErrors;
218 : 0 : return check();
219 : 0 : }
220 : : #ifdef STB_IMAGE_READER_INCLUDE
221 : : // wrap (repeat|mirror|clamp), filter (linear|nearest)
222 : : bool initFromFile(
223 : : const std::string& vFilePathName,
224 : : const bool vInvertY,
225 : : const std::string vWrap,
226 : : const std::string vFilter,
227 : : const bool vEnableMipMap) {
228 : : assert(!vFilePathName.empty());
229 : : stbi_set_flip_vertically_on_load(vInvertY);
230 : : auto w = 0;
231 : : auto h = 0;
232 : : auto chans = 0;
233 : : auto buffer = stbi_load(vFilePathName.c_str(), &w, &h, &chans, 0);
234 : : if (buffer) {
235 : : stbi_image_free(buffer);
236 : : if (chans == 4) {
237 : : buffer = stbi_load(vFilePathName.c_str(), &w, &h, &chans, STBI_rgb_alpha);
238 : : } else if (chans == 3) {
239 : : buffer = stbi_load(vFilePathName.c_str(), &w, &h, &chans, STBI_rgb);
240 : : } else if (chans == 2) {
241 : : buffer = stbi_load(vFilePathName.c_str(), &w, &h, &chans, STBI_grey_alpha);
242 : : } else if (chans == 1) {
243 : : buffer = stbi_load(vFilePathName.c_str(), &w, &h, &chans, STBI_grey);
244 : : }
245 : : }
246 : : if (buffer) {
247 : : m_Width = w;
248 : : m_Height = h;
249 : : glGenTextures(1, &m_TexId);
250 : : CheckGLErrors;
251 : : glBindTexture(GL_TEXTURE_2D, m_TexId);
252 : : CheckGLErrors;
253 : : glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
254 : : CheckGLErrors;
255 : : m_setFormat(GL_UNSIGNED_BYTE, chans);
256 : : glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
257 : : glTexImage2D(GL_TEXTURE_2D, 0, m_InternalFormat, m_Width, m_Height, 0, m_Format, m_PixelFormat, buffer);
258 : : CheckGLErrors;
259 : : glFinish();
260 : : CheckGLErrors;
261 : : m_setParameters(vWrap, vFilter, vEnableMipMap);
262 : : glFinish();
263 : : CheckGLErrors;
264 : : glBindTexture(GL_TEXTURE_2D, 0);
265 : : CheckGLErrors;
266 : : stbi_image_free(buffer);
267 : : }
268 : : return check();
269 : : }
270 : : #endif // STB_IMAGE_READER_INCLUDE
271 : 0 : void updateMipMaping() {
272 : 0 : if (m_EnableMipMap) {
273 : 0 : #ifdef PROFILER_SCOPED
274 : 0 : PROFILER_SCOPED("Opengl", "glGenerateMipmap %u", m_TexId);
275 : 0 : #endif
276 : 0 : glGenerateMipmap(GL_TEXTURE_2D);
277 : 0 : CheckGLErrors;
278 : 0 : glFinish();
279 : 0 : CheckGLErrors;
280 : 0 : }
281 : 0 : }
282 : 0 : bool resize(const GLsizei& vSx, const GLsizei& vSy) {
283 : 0 : if (m_TexId > 0U) {
284 : 0 : glBindTexture(GL_TEXTURE_2D, m_TexId);
285 : 0 : CheckGLErrors;
286 : 0 : glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, vSx, vSy, 0, GL_RGBA, GL_FLOAT, nullptr);
287 : 0 : CheckGLErrors;
288 : 0 : glFinish();
289 : 0 : glBindTexture(GL_TEXTURE_2D, 0);
290 : 0 : CheckGLErrors;
291 : 0 : return true;
292 : 0 : }
293 : 0 : return false;
294 : 0 : }
295 : 0 : void unit() {
296 : 0 : glDeleteTextures(1, &m_TexId);
297 : 0 : CheckGLErrors;
298 : 0 : }
299 : 0 : bool check() { return (glIsTexture(m_TexId) == GL_TRUE); }
300 : 0 : GLuint getTexId() const { return m_TexId; }
301 : 0 : std::array<GLsizei, 2U> getSize() const { return {m_Width, m_Height}; }
302 : :
303 : : #ifdef STB_IMAGE_WRITER_INCLUDE
304 : : bool saveToPng(const std::string& vFilePathName) const {
305 : : if (vFilePathName.empty() || m_TexId == 0)
306 : : return false;
307 : :
308 : : // 1) Dimensions
309 : : glBindTexture(GL_TEXTURE_2D, m_TexId);
310 : : GLint w = 0, h = 0;
311 : : glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &w);
312 : : glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &h);
313 : : if (w <= 0 || h <= 0)
314 : : return false;
315 : :
316 : : // 2) Sauvegarde ?tat pack + configuration propre
317 : : GLint prevAlign = 0, prevRowLen = 0, prevSkipRows = 0, prevSkipPix = 0;
318 : : glGetIntegerv(GL_PACK_ALIGNMENT, &prevAlign);
319 : : glGetIntegerv(GL_PACK_ROW_LENGTH, &prevRowLen);
320 : : glGetIntegerv(GL_PACK_SKIP_ROWS, &prevSkipRows);
321 : : glGetIntegerv(GL_PACK_SKIP_PIXELS, &prevSkipPix);
322 : :
323 : : glPixelStorei(GL_PACK_ALIGNMENT, 1);
324 : : glPixelStorei(GL_PACK_ROW_LENGTH, 0);
325 : : glPixelStorei(GL_PACK_SKIP_ROWS, 0);
326 : : glPixelStorei(GL_PACK_SKIP_PIXELS, 0);
327 : :
328 : : // 3) Lecture RGBA8
329 : : std::vector<unsigned char> pixels(static_cast<size_t>(w) * static_cast<size_t>(h) * 4u);
330 : : glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
331 : :
332 : : // 4) ?criture PNG (top-left) : flip vertical pour correspondre aux viewers
333 : : stbi_flip_vertically_on_write(1);
334 : : const int ok = stbi_write_png(vFilePathName.c_str(), w, h, 4, pixels.data(), w * 4);
335 : :
336 : : // 5) Restauration ?tat pack
337 : : glPixelStorei(GL_PACK_ALIGNMENT, prevAlign);
338 : : glPixelStorei(GL_PACK_ROW_LENGTH, prevRowLen);
339 : : glPixelStorei(GL_PACK_SKIP_ROWS, prevSkipRows);
340 : : glPixelStorei(GL_PACK_SKIP_PIXELS, prevSkipPix);
341 : :
342 : : return ok != 0;
343 : : }
344 : : #endif//STB_IMAGE_WRITER_INCLUDE
345 : :
346 : : private:
347 : : // wrap (repeat|mirror|clamp), filter (linear|nearest)
348 : 0 : void m_setParameters(const std::string vWrap, const std::string vFilter, const bool vEnableMipMap) {
349 : 0 : if (vWrap == "repeat") {
350 : 0 : m_WrapS = GL_REPEAT;
351 : 0 : m_WrapT = GL_REPEAT;
352 : 0 : glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, m_WrapS);
353 : 0 : glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, m_WrapT);
354 : 0 : CheckGLErrors;
355 : 0 : } else if (vWrap == "mirror") {
356 : 0 : m_WrapS = GL_MIRRORED_REPEAT;
357 : 0 : m_WrapT = GL_MIRRORED_REPEAT;
358 : 0 : glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, m_WrapS);
359 : 0 : glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, m_WrapT);
360 : 0 : CheckGLErrors;
361 : 0 : } else if (vWrap == "clamp") {
362 : 0 : m_WrapS = GL_CLAMP_TO_EDGE;
363 : 0 : m_WrapT = GL_CLAMP_TO_EDGE;
364 : 0 : glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, m_WrapS);
365 : 0 : glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, m_WrapT);
366 : 0 : CheckGLErrors;
367 : 0 : } else {
368 : 0 : m_WrapS = GL_CLAMP_TO_EDGE;
369 : 0 : m_WrapT = GL_CLAMP_TO_EDGE;
370 : 0 : glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, m_WrapS);
371 : 0 : glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, m_WrapT);
372 : 0 : CheckGLErrors;
373 : 0 : }
374 : 0 :
375 : 0 : if (vFilter == "linear") {
376 : 0 : if (vEnableMipMap) {
377 : 0 : m_MinFilter = GL_LINEAR_MIPMAP_LINEAR;
378 : 0 : m_MagFilter = GL_LINEAR;
379 : 0 : glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, m_MinFilter);
380 : 0 : glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, m_MagFilter);
381 : 0 : CheckGLErrors;
382 : 0 : } else {
383 : 0 : m_MinFilter = GL_LINEAR;
384 : 0 : m_MagFilter = GL_LINEAR;
385 : 0 : glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, m_MinFilter);
386 : 0 : glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, m_MagFilter);
387 : 0 : CheckGLErrors;
388 : 0 : }
389 : 0 : } else if (vFilter == "nearest") {
390 : 0 : if (vEnableMipMap) {
391 : 0 : m_MinFilter = GL_NEAREST_MIPMAP_NEAREST;
392 : 0 : m_MagFilter = GL_NEAREST;
393 : 0 : glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, m_MinFilter);
394 : 0 : glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, m_MagFilter);
395 : 0 : CheckGLErrors;
396 : 0 : } else {
397 : 0 : m_MinFilter = GL_NEAREST;
398 : 0 : m_MagFilter = GL_NEAREST;
399 : 0 : glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, m_MinFilter);
400 : 0 : glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, m_MagFilter);
401 : 0 : CheckGLErrors;
402 : 0 : }
403 : 0 : } else {
404 : 0 : m_MinFilter = GL_LINEAR;
405 : 0 : m_MagFilter = GL_LINEAR;
406 : 0 : glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, m_MinFilter);
407 : 0 : glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, m_MagFilter);
408 : 0 : CheckGLErrors;
409 : 0 : }
410 : 0 : if (m_EnableMipMap) {
411 : 0 : glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
412 : 0 : CheckGLErrors;
413 : 0 : glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 8);
414 : 0 : CheckGLErrors;
415 : 0 : updateMipMaping();
416 : 0 : } else {
417 : 0 : glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
418 : 0 : CheckGLErrors;
419 : 0 : glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
420 : 0 : CheckGLErrors;
421 : 0 : }
422 : 0 : }
423 : 0 : void m_setFormat(const GLenum vPixelFormat, const GLint& vChannelsCount) {
424 : 0 : assert(
425 : 0 : vPixelFormat == GL_UNSIGNED_BYTE || // format BYTE
426 : 0 : vPixelFormat == GL_FLOAT); // format FLOAT
427 : 0 : m_PixelFormat = vPixelFormat;
428 : 0 : m_ChannelsCount = vChannelsCount;
429 : 0 : // 1:r, 2:rg, 3:rgb, 4:rgba
430 : 0 : switch (vChannelsCount) {
431 : 0 : case 1: {
432 : 0 : m_Format = GL_RED;
433 : 0 : if (vPixelFormat == GL_UNSIGNED_BYTE) {
434 : 0 : m_InternalFormat = GL_R8;
435 : 0 : } else if (vPixelFormat == GL_FLOAT) {
436 : 0 : m_InternalFormat = GL_R32F;
437 : 0 : }
438 : 0 : } break;
439 : 0 : case 2: {
440 : 0 : m_Format = GL_RG;
441 : 0 : if (vPixelFormat == GL_UNSIGNED_BYTE) {
442 : 0 : m_InternalFormat = GL_RG8;
443 : 0 : } else if (vPixelFormat == GL_FLOAT) {
444 : 0 : m_InternalFormat = GL_RG32F;
445 : 0 : }
446 : 0 : } break;
447 : 0 : case 3: {
448 : 0 : m_Format = GL_RGB;
449 : 0 : if (vPixelFormat == GL_UNSIGNED_BYTE) {
450 : 0 : m_InternalFormat = GL_RGB8;
451 : 0 : } else if (vPixelFormat == GL_FLOAT) {
452 : 0 : m_InternalFormat = GL_RGB32F;
453 : 0 : }
454 : 0 : } break;
455 : 0 : case 4: {
456 : 0 : m_Format = GL_RGBA;
457 : 0 : if (vPixelFormat == GL_UNSIGNED_BYTE) {
458 : 0 : m_InternalFormat = GL_RGBA8;
459 : 0 : } else if (vPixelFormat == GL_FLOAT) {
460 : 0 : m_InternalFormat = GL_RGBA32F;
461 : 0 : }
462 : 0 : } break;
463 : 0 : }
464 : 0 : }
465 : : };
466 : : class Texture2DArray;
467 : : typedef std::shared_ptr<Texture2DArray> Texture2DArrayPtr;
468 : : typedef std::weak_ptr<Texture2DArray> Texture2DArrayWeak;
469 : :
470 : : class Texture2DArray {
471 : : public:
472 : : static Texture2DArrayPtr create(
473 : : const GLsizei vSx,
474 : : const GLsizei vSy,
475 : : const GLsizei vLayers,
476 : : const GLenum vInternalFormat, // ex: GL_RGBA8, GL_R16F, ...
477 : : const GLenum vAllocFormat, // ex: GL_RGBA, GL_RED, GL_RG
478 : : const GLenum vPixelFormat, // ex: GL_UNSIGNED_BYTE, GL_HALF_FLOAT, GL_FLOAT
479 : : const std::string& vWrap,
480 : : const std::string& vFilter,
481 : 0 : const bool vUseMipMapping) {
482 : 0 : auto res = std::make_shared<Texture2DArray>();
483 : 0 : res->m_This = res;
484 : 0 : if (!res->init(vSx, vSy, vLayers, vInternalFormat, vAllocFormat, vPixelFormat, vWrap, vFilter, vUseMipMapping)) {
485 : 0 : res.reset();
486 : 0 : }
487 : 0 : return res;
488 : 0 : }
489 : :
490 : : private:
491 : : GLuint m_TexId = 0U;
492 : :
493 : : GLsizei m_Width = 0;
494 : : GLsizei m_Height = 0;
495 : : GLsizei m_Layers = 0;
496 : :
497 : : GLenum m_InternalFormat = GL_RGBA8; // stockage interne
498 : : GLenum m_AllocFormat = GL_RGBA; // format d?claration
499 : : GLenum m_PixelFormat = GL_UNSIGNED_BYTE;
500 : :
501 : : std::string m_wrap;
502 : : std::string m_filter;
503 : :
504 : : std::vector<int> m_FreeList; // pile de layers libres
505 : :
506 : : bool m_useMipMapping = false;
507 : : GLsizei m_MipCount = 0;
508 : : bool m_useImmutable = false; // vrai si glTexStorage3D est dispo et choisi
509 : : std::vector<bool> m_LevelDeclared; // niveaux (L) ?disponibles? pour sampling
510 : : GLint m_MinDeclaredLevel = -1;
511 : : GLint m_MaxDeclaredLevel = -1;
512 : :
513 : : Texture2DArrayWeak m_This;
514 : :
515 : : public:
516 : : Texture2DArray() = default;
517 : 0 : ~Texture2DArray() { unit(); }
518 : :
519 : : bool init(
520 : : const GLsizei vSx,
521 : : const GLsizei vSy,
522 : : const GLsizei vLayers,
523 : : const GLenum vInternalFormat,
524 : : const GLenum vAllocFormat,
525 : : const GLenum vPixelFormat,
526 : : const std::string& vWrap,
527 : : const std::string& vFilter,
528 : 0 : const bool vUseMipMapping) {
529 : 0 : unit();
530 : 0 : assert(vSx > 0 && vSy > 0 && vLayers > 0);
531 : 0 : m_Width = vSx;
532 : 0 : m_Height = vSy;
533 : 0 : m_Layers = vLayers;
534 : 0 : m_InternalFormat = vInternalFormat;
535 : 0 : m_AllocFormat = vAllocFormat;
536 : 0 : m_PixelFormat = vPixelFormat;
537 : 0 : m_wrap = vWrap;
538 : 0 : m_filter = vFilter;
539 : 0 : m_useMipMapping = vUseMipMapping;
540 : 0 :
541 : 0 : GLint maxLayers = 0;
542 : 0 : glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &maxLayers);
543 : 0 : if (vLayers > maxLayers) {
544 : 0 : #ifdef EZ_TOOLS_LOG
545 : 0 : LogVarError("The layers count is superior to the max admissible of %i for your GPU", maxLayers);
546 : 0 : #endif
547 : 0 : return false;
548 : 0 : }
549 : 0 :
550 : 0 : glGenTextures(1, &m_TexId);
551 : 0 : CheckGLErrors;
552 : 0 : if (m_TexId == 0U) {
553 : 0 : return false;
554 : 0 : }
555 : 0 :
556 : 0 : // Mip count th?orique
557 : 0 : m_MipCount = m_useMipMapping ? m_computeMipCount(m_Width, m_Height) : 1;
558 : 0 : m_LevelDeclared.assign((size_t)m_MipCount, false);
559 : 0 : m_MinDeclaredLevel = -1;
560 : 0 : m_MaxDeclaredLevel = -1;
561 : 0 :
562 : 0 : // Param?tres de base (wrap/filter + fen?tre LOD initiale)
563 : 0 : m_setParameters(m_wrap, m_filter);
564 : 0 :
565 : 0 : // D?tection simple de glTexStorage3D ? l?ex?cution
566 : 0 : m_useImmutable = (reinterpret_cast<void*>(glTexStorage3D) != nullptr);
567 : 0 :
568 : 0 : glBindTexture(GL_TEXTURE_2D_ARRAY, m_TexId);
569 : 0 : CheckGLErrors;
570 : 0 :
571 : 0 : if (m_useImmutable) {
572 : 0 : // Allocation immuable de tous les niveaux
573 : 0 : glTexStorage3D(GL_TEXTURE_2D_ARRAY, m_MipCount, m_InternalFormat, m_Width, m_Height, m_Layers);
574 : 0 : CheckGLErrors;
575 : 0 :
576 : 0 : // Rien n?est ?rempli? mais tous les niveaux existent. On n?active la fen?tre [BASE..MAX]
577 : 0 : // qu?une fois des niveaux marqu?s comme d?clar?s (upload effectu?s).
578 : 0 : // On pourrait au choix marquer level 0 comme d?clar? imm?diatement si tu veux sampler tout de suite.
579 : 0 : } else {
580 : 0 : // Fallback : aucune alloc ici, on allouera paresseusement par niveau via TexImage3D
581 : 0 : // ? la premi?re demande (m_ensureLevelDeclared).
582 : 0 : }
583 : 0 :
584 : 0 : glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
585 : 0 : CheckGLErrors;
586 : 0 :
587 : 0 : // Pr?pare free-list
588 : 0 : m_FreeList.clear();
589 : 0 : m_FreeList.reserve((size_t)vLayers);
590 : 0 : for (int i = vLayers - 1; i >= 0; --i) {
591 : 0 : m_FreeList.push_back(i);
592 : 0 : }
593 : 0 :
594 : 0 : return check();
595 : 0 : }
596 : :
597 : 0 : void unit() {
598 : 0 : if (m_TexId != 0U) {
599 : 0 : glDeleteTextures(1, &m_TexId);
600 : 0 : CheckGLErrors;
601 : 0 : m_TexId = 0U;
602 : 0 : }
603 : 0 : m_Width = m_Height = m_Layers = 0;
604 : 0 : m_InternalFormat = GL_RGBA8;
605 : 0 : m_AllocFormat = GL_RGBA;
606 : 0 : m_PixelFormat = GL_UNSIGNED_BYTE;
607 : 0 : m_MipCount = 0;
608 : 0 : m_useImmutable = false;
609 : 0 : m_FreeList.clear();
610 : 0 : m_LevelDeclared.clear();
611 : 0 : m_MinDeclaredLevel = m_MaxDeclaredLevel = -1;
612 : 0 : }
613 : :
614 : : // Alloue une layer libre et uploade le mip 'vLevel' avec 'vpPixels'
615 : : // Retourne l'index de layer :
616 : : // -1 : erreur
617 : : // -2 : saturation
618 : 0 : int addLayer(const GLsizei vLevel, const void* vpPixels) {
619 : 0 : if (m_TexId == 0U || vpPixels == nullptr) {
620 : 0 : return -1;
621 : 0 : }
622 : 0 : if (vLevel < 0 || vLevel >= m_MipCount) {
623 : 0 : return -1;
624 : 0 : }
625 : 0 : if (m_FreeList.empty()) {
626 : 0 : return -2;
627 : 0 : }
628 : 0 :
629 : 0 : const int layer = m_FreeList.back();
630 : 0 : m_FreeList.pop_back();
631 : 0 :
632 : 0 : if (!m_ensureLevelDeclared(vLevel)) {
633 : 0 : m_FreeList.push_back(layer);
634 : 0 : return -1;
635 : 0 : }
636 : 0 :
637 : 0 : const GLsizei w = std::max<GLsizei>(1, m_Width >> vLevel);
638 : 0 : const GLsizei h = std::max<GLsizei>(1, m_Height >> vLevel);
639 : 0 :
640 : 0 : glBindTexture(GL_TEXTURE_2D_ARRAY, m_TexId);
641 : 0 : CheckGLErrors;
642 : 0 : glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
643 : 0 : CheckGLErrors;
644 : 0 : glTexSubImage3D(GL_TEXTURE_2D_ARRAY, vLevel, 0, 0, layer, w, h, 1, m_AllocFormat, m_PixelFormat, vpPixels);
645 : 0 : CheckGLErrors;
646 : 0 : glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
647 : 0 : CheckGLErrors;
648 : 0 :
649 : 0 : m_enableMipTrilinearIfPossible(m_filter);
650 : 0 :
651 : 0 : return layer;
652 : 0 : }
653 : :
654 : : // Met ? jour un mip sur une layer existante
655 : 0 : bool uploadLayer(const GLint vLayer, const GLsizei vLevel, const void* vpPixels) {
656 : 0 : if (m_TexId == 0U || vpPixels == nullptr) {
657 : 0 : return false;
658 : 0 : }
659 : 0 : if (vLayer < 0 || vLayer >= m_Layers) {
660 : 0 : return false;
661 : 0 : }
662 : 0 : if (vLevel < 0 || vLevel >= m_MipCount) {
663 : 0 : return false;
664 : 0 : }
665 : 0 : if (!m_ensureLevelDeclared(vLevel)) {
666 : 0 : return false;
667 : 0 : }
668 : 0 :
669 : 0 : const GLsizei w = std::max<GLsizei>(1, m_Width >> vLevel);
670 : 0 : const GLsizei h = std::max<GLsizei>(1, m_Height >> vLevel);
671 : 0 :
672 : 0 : glBindTexture(GL_TEXTURE_2D_ARRAY, m_TexId);
673 : 0 : CheckGLErrors;
674 : 0 : glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
675 : 0 : CheckGLErrors;
676 : 0 : glTexSubImage3D(GL_TEXTURE_2D_ARRAY, vLevel, 0, 0, vLayer, w, h, 1, m_AllocFormat, m_PixelFormat, vpPixels);
677 : 0 : CheckGLErrors;
678 : 0 : glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
679 : 0 : CheckGLErrors;
680 : 0 :
681 : 0 : m_enableMipTrilinearIfPossible(m_filter);
682 : 0 : return true;
683 : 0 : }
684 : :
685 : : // Lib?re la layer (r?utilisable par addLayer)
686 : 0 : void removeLayer(const GLint vLayer) {
687 : 0 : if (m_TexId == 0U) {
688 : 0 : return;
689 : 0 : }
690 : 0 : if (vLayer < 0 || vLayer >= m_Layers) {
691 : 0 : return;
692 : 0 : }
693 : 0 : const bool found = (std::find(m_FreeList.begin(), m_FreeList.end(), vLayer) != m_FreeList.end());
694 : 0 : if (!found) {
695 : 0 : m_FreeList.push_back(vLayer);
696 : 0 : }
697 : 0 : }
698 : :
699 : : // Param?tres (wrap/filter) ? safe ? appeler ? tout moment
700 : 0 : void setParameters(const std::string& vWrap, const std::string& vFilter) {
701 : 0 : if (m_TexId == 0U) {
702 : 0 : return;
703 : 0 : }
704 : 0 : m_setParameters(vWrap, vFilter);
705 : 0 : m_enableMipTrilinearIfPossible(vFilter);
706 : 0 : }
707 : :
708 : 0 : bool check() const { return (glIsTexture(m_TexId) == GL_TRUE); }
709 : 0 : GLuint getTexId() const { return m_TexId; }
710 : 0 : GLsizei getWidth() const { return m_Width; }
711 : 0 : GLsizei getHeight() const { return m_Height; }
712 : 0 : GLsizei getLayers() const { return m_Layers; }
713 : 0 : GLsizei getMipCount() const { return m_MipCount; }
714 : :
715 : : private:
716 : 0 : static GLsizei m_computeMipCount(const GLsizei vSx, const GLsizei vSy) {
717 : 0 : auto s = (vSx > vSy ? vSx : vSy);
718 : 0 : GLsizei m = 1;
719 : 0 : while (s > 1) {
720 : 0 : s >>= 1;
721 : 0 : ++m;
722 : 0 : }
723 : 0 : return m;
724 : 0 : }
725 : :
726 : 0 : void m_updateLodClamp() {
727 : 0 : if (m_TexId == 0U) {
728 : 0 : return;
729 : 0 : }
730 : 0 : if (m_MinDeclaredLevel < 0 || m_MaxDeclaredLevel < 0) {
731 : 0 : return;
732 : 0 : }
733 : 0 : glBindTexture(GL_TEXTURE_2D_ARRAY, m_TexId);
734 : 0 : CheckGLErrors;
735 : 0 : glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_BASE_LEVEL, m_MinDeclaredLevel);
736 : 0 : CheckGLErrors;
737 : 0 : glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, m_MaxDeclaredLevel);
738 : 0 : CheckGLErrors;
739 : 0 : glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
740 : 0 : CheckGLErrors;
741 : 0 : }
742 : :
743 : 0 : bool m_ensureLevelDeclared(const GLsizei vLevel) {
744 : 0 : if (vLevel < 0 || vLevel >= m_MipCount) {
745 : 0 : return false;
746 : 0 : }
747 : 0 : if (m_LevelDeclared.empty()) {
748 : 0 : return false;
749 : 0 : }
750 : 0 : if (m_LevelDeclared[(size_t)vLevel]) {
751 : 0 : m_updateLodClamp();
752 : 0 : return true;
753 : 0 : }
754 : 0 :
755 : 0 : if (!m_useImmutable) {
756 : 0 : // Fallback : on alloue le niveau (toutes layers) avec data=nullptr
757 : 0 : const GLsizei w = std::max<GLsizei>(1, m_Width >> vLevel);
758 : 0 : const GLsizei h = std::max<GLsizei>(1, m_Height >> vLevel);
759 : 0 :
760 : 0 : glBindTexture(GL_TEXTURE_2D_ARRAY, m_TexId);
761 : 0 : CheckGLErrors;
762 : 0 : glTexImage3D(GL_TEXTURE_2D_ARRAY, vLevel, m_InternalFormat, w, h, m_Layers, 0, m_AllocFormat, m_PixelFormat, nullptr);
763 : 0 : CheckGLErrors;
764 : 0 : glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
765 : 0 : CheckGLErrors;
766 : 0 : } // en immutable, rien ? allouer : les niveaux existent d?j?
767 : 0 :
768 : 0 : m_LevelDeclared[(size_t)vLevel] = true;
769 : 0 :
770 : 0 : if (m_MinDeclaredLevel < 0 || vLevel < m_MinDeclaredLevel) {
771 : 0 : m_MinDeclaredLevel = vLevel;
772 : 0 : }
773 : 0 : if (m_MaxDeclaredLevel < 0 || vLevel > m_MaxDeclaredLevel) {
774 : 0 : m_MaxDeclaredLevel = vLevel;
775 : 0 : }
776 : 0 :
777 : 0 : m_updateLodClamp();
778 : 0 : return true;
779 : 0 : }
780 : :
781 : 0 : void m_setParameters(const std::string& vWrap, const std::string& vFilter) {
782 : 0 : glBindTexture(GL_TEXTURE_2D_ARRAY, m_TexId);
783 : 0 : CheckGLErrors;
784 : 0 :
785 : 0 : // Wrap
786 : 0 : GLenum wrap = GL_CLAMP_TO_EDGE;
787 : 0 : if (vWrap == "repeat") {
788 : 0 : wrap = GL_REPEAT;
789 : 0 : } else if (vWrap == "mirror") {
790 : 0 : wrap = GL_MIRRORED_REPEAT;
791 : 0 : }
792 : 0 : glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, wrap);
793 : 0 : CheckGLErrors;
794 : 0 : glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, wrap);
795 : 0 : CheckGLErrors;
796 : 0 :
797 : 0 : // Filter
798 : 0 : const bool linear = (vFilter != "nearest");
799 : 0 : glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, linear ? GL_LINEAR : GL_NEAREST);
800 : 0 : CheckGLErrors;
801 : 0 : glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, linear ? GL_LINEAR : GL_NEAREST);
802 : 0 : CheckGLErrors;
803 : 0 :
804 : 0 : // Fen?tre LOD initiale : [0 .. m_MipCount-1] si mipmap, sinon [0..0].
805 : 0 : const GLint initialBase = 0;
806 : 0 : const GLint initialMax = (m_useMipMapping && m_MipCount > 0) ? (m_MipCount - 1) : 0;
807 : 0 : glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_BASE_LEVEL, initialBase);
808 : 0 : CheckGLErrors;
809 : 0 : glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, initialMax);
810 : 0 : CheckGLErrors;
811 : 0 :
812 : 0 : glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
813 : 0 : CheckGLErrors;
814 : 0 : }
815 : :
816 : 0 : void m_enableMipTrilinearIfPossible(const std::string& vFilter) {
817 : 0 : if (!m_useMipMapping) {
818 : 0 : return;
819 : 0 : }
820 : 0 : if (m_MinDeclaredLevel < 0 || m_MaxDeclaredLevel < 0) {
821 : 0 : return;
822 : 0 : }
823 : 0 : if (m_MaxDeclaredLevel <= m_MinDeclaredLevel) {
824 : 0 : return; // un seul niveau
825 : 0 : }
826 : 0 : glBindTexture(GL_TEXTURE_2D_ARRAY, m_TexId);
827 : 0 : CheckGLErrors;
828 : 0 : const bool linear = (vFilter != "nearest");
829 : 0 : glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, linear ? GL_LINEAR_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_NEAREST);
830 : 0 : CheckGLErrors;
831 : 0 : glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
832 : 0 : CheckGLErrors;
833 : 0 : }
834 : : };
835 : :
836 : : } // namespace gl
837 : : } // namespace ez
|