Symphony Of Empires
texture.cpp
Go to the documentation of this file.
1 // Eng3D - General purpouse game engine
2 // Copyright (C) 2021, Eng3D contributors
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program. If not, see <https://www.gnu.org/licenses/>.
16 //
17 // ----------------------------------------------------------------------------
18 // Name:
19 // texture.cpp
20 //
21 // Abstract:
22 // Defines some functions to bind a BinaryImage to an OpenGL object
23 // by using the OpenGL API for managing the resources of it.
24 // ----------------------------------------------------------------------------
25 
26 #include <algorithm>
27 #include <cassert>
28 #include <SDL_ttf.h>
29 
30 #ifdef E3D_BACKEND_OPENGL
31 # include <GL/glew.h>
32 # include <GL/gl.h>
33 #elif defined E3D_BACKEND_GLES
34 # include <GLES3/gl3.h>
35 #endif
36 
37 #include "eng3d/texture.hpp"
38 #include "eng3d/framebuffer.hpp"
39 #include "eng3d/utils.hpp"
40 #include "eng3d/state.hpp"
41 #include "eng3d/log.hpp"
42 
43 //
44 // Texture
45 //
47 #if defined E3D_BACKEND_OPENGL || defined E3D_BACKEND_GLES
48  if(id)
49  delete_gputex();
50 #endif
51  if(managed) {
52  auto& s = Eng3D::State::get_instance();
53  const std::scoped_lock lock(s.tex_man.unuploaded_lock);
54  auto it = std::find_if(s.tex_man.unuploaded_textures.begin(), s.tex_man.unuploaded_textures.end(), [this](const auto& e) {
55  return e.texture == this;
56  });
57  if(it != s.tex_man.unuploaded_textures.end())
58  s.tex_man.unuploaded_textures.erase(it);
59  }
60 }
61 
65  width = 8;
66  height = 8;
67  buffer = std::make_unique<uint32_t[]>(width * height);
68  if(buffer.get() == nullptr)
69  CXX_THROW(TextureException, "Dummy", "Out of memory for dummy texture");
70 
71  // Fill in with a permutation pattern of pink and black
72  // This should be autovectorized by gcc
73  for(size_t i = 0; i < width * height; i++)
74  buffer.get()[i] = ((i * 8) << 16) | (i * 16);
75 }
76 
77 void Eng3D::Texture::_upload(TextureOptions options) {
78 #if defined E3D_BACKEND_OPENGL || defined E3D_BACKEND_GLES
79  if(id) delete_gputex();
80 
81  glGenTextures(1, &id);
82  glBindTexture(GL_TEXTURE_2D, id);
83 
84  const auto texture_options_to_gl = [](const auto x) {
85  switch(x) {
86  case Eng3D::TextureOptions::Format::RGBA:
87  return GL_RGBA;
88  case Eng3D::TextureOptions::Format::RED:
89  return GL_RED;
90  case Eng3D::TextureOptions::Format::SRGB:
91  return GL_SRGB;
92  case Eng3D::TextureOptions::Format::RGB32F:
93  return GL_RGB32F;
94  case Eng3D::TextureOptions::Format::SRGB_ALPHA:
95  #ifdef E3D_BACKEND_GLES
96  return GL_RGBA;
97  #else
98  return GL_SRGB_ALPHA;
99  #endif
100  default:
101  return 0;
102  }
103  return 0;
104  };
105 
106  GLuint internal_format = texture_options_to_gl(options.internal_format);
107 
108 #ifndef E3D_BACKEND_GLES
110  // Compress the texture if it can't be edited, this is only available on normal OpenGL through
111  if(!options.editable && options.compressed) {
112  switch(internal_format) {
113  case GL_ALPHA:
114  internal_format = GL_COMPRESSED_ALPHA;
115  break;
116  case GL_LUMINANCE:
117  internal_format = GL_COMPRESSED_LUMINANCE;
118  break;
119  case GL_LUMINANCE_ALPHA:
120  internal_format = GL_COMPRESSED_LUMINANCE_ALPHA;
121  break;
122  case GL_INTENSITY:
123  internal_format = GL_COMPRESSED_INTENSITY;
124  break;
125  case GL_RED:
126  internal_format = GL_COMPRESSED_RED;
127  break;
128  case GL_RGB:
129  internal_format = GL_COMPRESSED_RGB;
130  break;
131  case GL_RGBA:
132  internal_format = GL_COMPRESSED_RGBA;
133  break;
134  case GL_SRGB:
135  internal_format = GL_COMPRESSED_SRGB;
136  break;
137  case GL_SRGB_ALPHA:
138  internal_format = GL_COMPRESSED_SRGB_ALPHA;
139  break;
140  case GL_RG:
141  internal_format = GL_COMPRESSED_RG;
142  break;
143  default:
144  break;
145  }
146  }
147 #endif
148  GLuint format = texture_options_to_gl(options.format);
149 
150  GLuint type = 0;
151  switch(options.type) {
152  case Eng3D::TextureOptions::Type::UNSIGNED_BYTE:
153  type = GL_UNSIGNED_BYTE;
154  break;
155  default:
156  break;
157  }
158 
159  GLuint wrap_s = 0;
160  switch(options.wrap_s) {
161  case Eng3D::TextureOptions::Wrap::REPEAT:
162  wrap_s = GL_REPEAT;
163  break;
164  case Eng3D::TextureOptions::Wrap::CLAMP_TO_EDGE:
165  wrap_s = GL_CLAMP_TO_EDGE;
166  break;
167  default:
168  break;
169  }
170 
171  GLuint wrap_t = 0;
172  switch(options.wrap_t) {
173  case Eng3D::TextureOptions::Wrap::REPEAT:
174  wrap_t = GL_REPEAT;
175  break;
176  case Eng3D::TextureOptions::Wrap::CLAMP_TO_EDGE:
177  wrap_t = GL_CLAMP_TO_EDGE;
178  break;
179  default:
180  break;
181  }
182 
183  GLuint min_filter = 0;
184  switch(options.min_filter) {
185  case Eng3D::TextureOptions::Filter::NEAREST:
186  min_filter = GL_NEAREST;
187  break;
188  case Eng3D::TextureOptions::Filter::LINEAR:
189  min_filter = GL_LINEAR;
190  break;
191  case Eng3D::TextureOptions::Filter::LINEAR_MIPMAP:
192  min_filter = GL_LINEAR_MIPMAP_LINEAR;
193  break;
194  case Eng3D::TextureOptions::Filter::NEAREST_MIPMAP:
195  min_filter = GL_NEAREST_MIPMAP_NEAREST;
196  break;
197  case Eng3D::TextureOptions::Filter::NEAREST_LINEAR_MIPMAP:
198  min_filter = GL_NEAREST_MIPMAP_LINEAR;
199  break;
200  case Eng3D::TextureOptions::Filter::LINEAR_NEAREST_MIPMAP:
201  min_filter = GL_LINEAR_MIPMAP_NEAREST;
202  break;
203  default:
204  break;
205  }
206 
207  GLuint mag_filter = 0;
208  switch(options.mag_filter) {
209  case Eng3D::TextureOptions::Filter::NEAREST:
210  mag_filter = GL_NEAREST;
211  break;
212  case Eng3D::TextureOptions::Filter::LINEAR:
213  mag_filter = GL_LINEAR;
214  break;
215  case Eng3D::TextureOptions::Filter::LINEAR_MIPMAP:
216  mag_filter = GL_LINEAR_MIPMAP_LINEAR;
217  break;
218  case Eng3D::TextureOptions::Filter::NEAREST_MIPMAP:
219  mag_filter = GL_NEAREST_MIPMAP_NEAREST;
220  break;
221  case Eng3D::TextureOptions::Filter::NEAREST_LINEAR_MIPMAP:
222  mag_filter = GL_NEAREST_MIPMAP_LINEAR;
223  break;
224  case Eng3D::TextureOptions::Filter::LINEAR_NEAREST_MIPMAP:
225  mag_filter = GL_LINEAR_MIPMAP_NEAREST;
226  break;
227  default:
228  break;
229  }
230 
231  glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, 0, format, type, buffer.get());
232  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap_s);
233  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap_t);
234  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter);
235  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter);
236 
237  if(!options.editable) {
238 #ifdef E3D_DEBUG
239  GLint result;
240  glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED, &result);
241  if(result == 0) {
242  Eng3D::Log::debug("opengl", Eng3D::translate_format("Couldn't compress texture of %u x %u", width, height));
243  } else {
244  Eng3D::Log::debug("opengl", Eng3D::translate_format("Compressed texture of %u x %u", width, height));
245  }
246 #endif
247  // We will free up the texture if we don't plan on editing it since it's on the GPU now
248  buffer.reset();
249  }
250 #endif
251  switch(options.min_filter) {
252  case Eng3D::TextureOptions::Filter::LINEAR_MIPMAP:
253  case Eng3D::TextureOptions::Filter::NEAREST_MIPMAP:
254  case Eng3D::TextureOptions::Filter::NEAREST_LINEAR_MIPMAP:
255  case Eng3D::TextureOptions::Filter::LINEAR_NEAREST_MIPMAP:
256  this->gen_mipmaps();
257  break;
258  default:
259  break;
260  }
261 }
262 
264 void Eng3D::Texture::_upload(SDL_Surface* surface) {
265  if(surface->w == 0 || surface->h == 0) return;
266  assert(surface->format != nullptr);
267 
268  this->buffer.reset();
269  this->width = static_cast<size_t>(surface->w);
270  this->height = static_cast<size_t>(surface->h);
271 
272  int colors = surface->format->BytesPerPixel;
273 #if defined E3D_BACKEND_OPENGL || defined E3D_BACKEND_GLES
274  GLuint texture_format;
275  if(colors == 4) {
276  // Alpha
277  if(surface->format->Rmask == 0x000000ff) {
278  texture_format = GL_RGBA;
279  } else {
280 #ifdef GL_BGRA
281  texture_format = GL_BGRA;
282 #else
283  texture_format = GL_RGBA; //CXX_THROW(std::runtime_error, "Unsupported texture format");
284 #endif
285  }
286  } else {
287  // No alpha
288  if(surface->format->Rmask == 0x000000ff) {
289  texture_format = GL_RGB;
290  } else {
291 #ifdef GL_BGR
292  texture_format = GL_BGR;
293 #else
294  texture_format = GL_RGB; //CXX_THROW(std::runtime_error, "Unsupported texture format");
295 #endif
296  }
297  }
298 
299  int alignment = 8;
300  while(surface->pitch % alignment)
301  alignment >>= 1; // x%1==0 for any x
302  glPixelStorei(GL_UNPACK_ALIGNMENT, alignment);
303 
304  int expected_pitch = (surface->w * surface->format->BytesPerPixel + alignment - 1) / alignment * alignment;
305  if(surface->pitch - expected_pitch >= alignment) {
306  // Alignment alone wont't solve it now
307  glPixelStorei(GL_UNPACK_ROW_LENGTH, surface->pitch / surface->format->BytesPerPixel);
308  } else {
309  glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
310  }
311 
312  glGenTextures(1, &id);
313  glBindTexture(GL_TEXTURE_2D, id);
314  glTexImage2D(GL_TEXTURE_2D, 0, colors, surface->w, surface->h, 0, texture_format, GL_UNSIGNED_BYTE, surface->pixels);
315  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
316  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
317  glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
318  glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
319 #endif
320  this->gen_mipmaps();
321  SDL_FreeSurface(surface);
322 }
323 
325 #if defined E3D_BACKEND_OPENGL || defined E3D_BACKEND_GLES
326  glBindTexture(GL_TEXTURE_2D, id);
327  glGenerateMipmap(GL_TEXTURE_2D);
328 #endif
329 }
330 
332 void Eng3D::Texture::bind() const {
333 #if defined E3D_BACKEND_OPENGL || defined E3D_BACKEND_GLES
334  glBindTexture(GL_TEXTURE_2D, id);
335 #endif
336 }
337 
340 #if defined E3D_BACKEND_OPENGL || defined E3D_BACKEND_GLES
341  glDeleteTextures(1, &id);
342 #endif
343 }
344 
345 #define STB_IMAGE_WRITE_STATIC 1
346 #define STB_IMAGE_WRITE_IMPLEMENTATION 1
347 extern "C" {
348 #include "eng3d/stb_image_write.h"
349 }
350 void Eng3D::Texture::to_file(const std::string& filename) {
351  int channel_count = 4;
352  int stride = channel_count * width;
353  int data_size = stride * height;
354 
355 #if defined E3D_BACKEND_OPENGL
356  glActiveTexture(GL_TEXTURE0);
357  glBindTexture(GL_TEXTURE_2D, id);
358 #endif
359  uint8_t* data = (uint8_t*)malloc(data_size);
360 #if defined E3D_BACKEND_OPENGL
361  glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
362 #endif
363  stbi_write_png(filename.c_str(), width, height, channel_count, data, stride);
364 }
365 
366 //
367 // Texture array
368 //
371 #if defined E3D_BACKEND_OPENGL || defined E3D_BACKEND_GLES
372  glGenTextures(1, &id);
373  glBindTexture(GL_TEXTURE_2D_ARRAY, id);
374 
375  // set up texture handle parameters
376  // glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_BASE_LEVEL, 0); // !single image!
377  // glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, 1); // !single image! mat->mips == 1
378  glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
379  glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
380  glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_REPEAT);
381  glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_REPEAT);
382 
383  size_t p_dx = width / tiles_x; // pixels of each tile in x
384  size_t p_dy = height / tiles_y; // pixels of each tile in y
385  glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, p_dx, p_dy, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
386 
387  glPixelStorei(GL_UNPACK_ROW_LENGTH, width);
388  glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, height);
389 
390  for(size_t x = 0; x < tiles_x; x++)
391  for(size_t y = 0; y < tiles_y; y++)
392  glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, x * tiles_x + y, p_dx, p_dy, 1, GL_RGBA, GL_UNSIGNED_BYTE, buffer.get() + (x * p_dy * width + y * p_dx));
393  //glGenerateMipmap(GL_TEXTURE_2D_ARRAY);
394 
395  // unbind texture handle
396  glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
397  glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
398  glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0);
399 #endif
400 }
401 
402 //
403 // Texture manager
404 //
406 {
407  const std::scoped_lock lock(this->unuploaded_lock);
408  // Surfaces have to be properly deallocated before calling SDL_Quit()
409  for(auto& request : this->unuploaded_textures) {
410  if(request.surface != nullptr) {
411  SDL_FreeSurface(request.surface);
412  request.surface = nullptr;
413  }
414  }
415 }
416 
417 std::shared_ptr<Eng3D::Texture> Eng3D::TextureManager::get_white() {
418  if(white.get() == nullptr) {
419  white = std::make_shared<Eng3D::Texture>(1, 1);
420  white->buffer.get()[0] = 0xFFFFFFFF;
421  white->upload();
422  }
423  return std::shared_ptr<Eng3D::Texture>(white);
424 }
425 
432 std::shared_ptr<Eng3D::Texture> Eng3D::TextureManager::load(const std::string& path, TextureOptions options) {
433  // Find texture when wanting to be loaded and load texture from cached texture list
434  auto key = std::make_pair(path, options);
435  auto it = textures.find(key);
436  if(it != textures.end()) return (*it).second;
437 
438  Eng3D::Log::debug("texture", "Loaded and cached texture " + path);
439 
440  // Otherwise texture is not in our control, so we create a new texture
441  std::shared_ptr<Eng3D::Texture> tex;
442  try {
443  tex = std::make_shared<Eng3D::Texture>(path);
444  } catch(const BinaryImageException&) {
445  tex = std::make_shared<Eng3D::Texture>();
446  tex->create_dummy();
447  }
448  tex->managed = true;
449  tex->upload(options);
450  textures[key] = tex;
451  return textures[key];
452 }
453 
454 std::shared_ptr<Eng3D::Texture> Eng3D::TextureManager::load(std::shared_ptr<Eng3D::IO::Asset::Base> asset, TextureOptions options) {
455  return this->load(asset.get() != nullptr ? asset->get_abs_path() : "", options);
456 }
457 
458 std::shared_ptr<Eng3D::Texture> Eng3D::TextureManager::gen_text(Eng3D::TrueType::Font& font, Eng3D::Color color, const std::string& msg) {
459  if(msg.empty()) return this->get_white();
460 
461  // Find texture when wanting to be loaded and load texture from cached texture list
462  auto it = text_textures.find(msg);
463  if(it != text_textures.end()) return it->second;
464 
465  Eng3D::Log::debug("texture", "Loaded and cached text texture for " + msg);
466 
467  // Otherwise texture is not in our control, so we create a new texture
468  auto tex = std::make_shared<Eng3D::Texture>();
469  Eng3D::Log::debug("ttf", "Creating text for \"" + msg + "\"");
470  const SDL_Color white_color{
471  static_cast<Uint8>(color.r * 255.f), static_cast<Uint8>(color.g * 255.f),
472  static_cast<Uint8>(color.b * 255.f), 0 };
473  auto* surface = TTF_RenderUTF8_Blended(static_cast<TTF_Font*>(font.sdl_font), msg.c_str(), white_color);
474  if(surface == nullptr)
475  CXX_THROW(std::runtime_error, Eng3D::translate_format("Cannot create text surface: %s", TTF_GetError()));
476  Eng3D::Log::debug("ttf", "Sucessfully created text");
477  tex->managed = true;
478  // Required so UI widgets can resize properly!
479  tex->width = surface->w;
480  tex->height = surface->h;
481  tex->upload(surface);
482 
483  text_textures[msg] = tex;
484  return text_textures[msg];
485 }
static State & get_instance()
Definition: state.cpp:514
void upload()
Uploads the TextureArray to the driver.
Definition: texture.cpp:370
void bind() const
Binds the texture to the current OpenGL context.
Definition: texture.cpp:332
void to_file(const std::string &filename) override
Definition: texture.cpp:350
void create_dummy()
This dummy texture helps to avoid crashes due to missing buffers or so, and also gives visual aid of ...
Definition: texture.cpp:64
void delete_gputex()
Deletes the OpenGL representation of this texture.
Definition: texture.cpp:339
~Texture() override
Definition: texture.cpp:46
void gen_mipmaps() const
Definition: texture.cpp:324
std::shared_ptr< Eng3D::Texture > load(const std::string &path, TextureOptions options=default_options)
Finds a texture in the list of a texture manager if the texture is already in the list we load the sa...
Definition: texture.cpp:432
std::shared_ptr< Eng3D::Texture > get_white()
Definition: texture.cpp:417
std::shared_ptr< Eng3D::Texture > gen_text(Eng3D::TrueType::Font &font, Eng3D::Color color, const std::string &msg)
Definition: texture.cpp:458
enum Eng3D::TextureOptions::Filter min_filter
enum Eng3D::TextureOptions::Wrap wrap_s
enum Eng3D::TextureOptions::Type type
enum Eng3D::TextureOptions::Format format
void debug(const std::string_view category, const std::string_view msg)
Definition: log.cpp:58
std::string translate_format(const std::string_view format, Args &&... args)
String formatter, with translation.
Definition: string.hpp:128
void load(GameState &gs, const std::string &savefile_path)
STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes)
Primitive color type used through the engine.
Definition: color.hpp:32
float g
Definition: color.hpp:33
float b
Definition: color.hpp:33
float r
Definition: color.hpp:33
#define CXX_THROW(class,...)
Definition: utils.hpp:98