ST_engine  0.3-ALPHA
renderer_sdl.cpp
1 /* This file is part of the "ST" project.
2  * You may use, distribute or modify this code under the terms
3  * of the GNU General Public License version 2.
4  * See LICENCE.txt in the root directory of the project.
5  *
6  * Author: Maxim Atanasov
7  * E-mail: maxim.atanasov@protonmail.com
8  *
9  */
10 
11 #include "font_cache.hpp"
12 #include "texture.hpp"
13 #include <renderer_sdl.hpp>
14 
15 namespace ST::renderer_sdl {
16  void cache_font(TTF_Font *Font, uint16_t font_and_size);
17 
18  void process_surfaces(std::vector<std::pair<uint16_t, SDL_Surface *>> &surfaces_pairs);
19 }
20 
21 static const uint16_t ATLAS_SIZE = 4096;
22 static const uint16_t atlas_grid_size = ATLAS_SIZE / 32;
23 
24 #ifdef TESTING
25 SDL_Renderer *sdl_renderer;
26 #else
27 static SDL_Renderer *sdl_renderer;
28 #endif
29 
30 //reference to a window
31 static SDL_Window *window;
32 
33 //height and width of the renderer
34 static int16_t width;
35 static int16_t height;
36 
37 //Textures with no corresponding surface in our assets need to be freed
38 #ifdef TESTING
39 ska::bytell_hash_map<uint16_t, ST::renderer_sdl::texture> textures{};
40 #else
41 static ska::bytell_hash_map<uint16_t, ST::renderer_sdl::texture> textures{};
42 #endif
43 
44 static ska::bytell_hash_map<uint16_t, SDL_Surface *> *surfaces_pointer;
45 static ska::bytell_hash_map<uint16_t, TTF_Font *> *fonts_pointer;
46 
47 
48 //the fonts in this table do not need to be cleaned - these are just pointer to Fonts stored in the asset_manager and
49 //that will handle the cleanup
50 static ska::bytell_hash_map<uint16_t, TTF_Font *> fonts{};
51 
52 //we do however need to clean up the cache as that lives on the GPU
53 static ska::bytell_hash_map<uint16_t, std::vector<ST::renderer_sdl::texture>> fonts_cache{};
54 
55 static bool vsync = false;
56 
57 static bool singleton_initialized = false;
58 
66 int8_t ST::renderer_sdl::initialize(SDL_Window *r_window, int16_t r_width, int16_t r_height) {
67  font_cache::set_max(100);
68 
69  if (singleton_initialized) {
70  throw std::runtime_error("The renderer cannot be initialized more than once!");
71  } else {
72  singleton_initialized = true;
73  }
74 
75  //initialize renderer
76  window = r_window;
77  width = r_width;
78  height = r_height;
79  if (vsync) {
80  sdl_renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE |
81  SDL_RENDERER_PRESENTVSYNC);
82  } else {
83  sdl_renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE);
84  }
85  SDL_RenderSetLogicalSize(sdl_renderer, width, height);
86  SDL_SetRenderDrawBlendMode(sdl_renderer, SDL_BLENDMODE_BLEND);
87  SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "2"); //Linear texture filtering
88  set_draw_color(0, 0, 0, 255);
89  return 0;
90 }
91 
96  for (auto &it: fonts) {
97  if (it.second != nullptr) {
98  if (fonts[it.first] != nullptr) {
99  for (auto &k: fonts_cache[it.first]) {
100  if (k.atlas != nullptr) {
101  SDL_DestroyTexture(k.atlas);
102  k.atlas = nullptr;
103  }
104  }
105  fonts[it.first] = nullptr;
106  }
107  it.second = nullptr;
108  }
109  }
110  for (auto &it: textures) {
111  if (it.second.atlas != nullptr) {
112  SDL_DestroyTexture(it.second.atlas);
113  it.second.atlas = nullptr;
114  }
115  }
116  font_cache::clear();
117  font_cache::close();
118  SDL_DestroyRenderer(sdl_renderer);
119  sdl_renderer = nullptr;
120  singleton_initialized = false;
121 }
122 
135 uint16_t
136 ST::renderer_sdl::draw_text_lru_cached(uint16_t font, const std::string &arg2, int x, int y, SDL_Color color_font) {
137  TTF_Font *_font = fonts[font];
138  int32_t texW = 0;
139  if (_font != nullptr) [[likely]] {
140  int32_t texH;
141  SDL_Texture *cached_texture = font_cache::get_cached_string(arg2, font);
142  if (cached_texture !=
143  nullptr) { //if the given string (with same size and font) is already cached, get it from cache
144  SDL_QueryTexture(cached_texture, nullptr, nullptr, &texW, &texH);
145  SDL_Rect Rect = {x, y - texH, texW, texH};
146  SDL_SetTextureColorMod(cached_texture, color_font.r, color_font.g, color_font.b);
147  SDL_RenderCopy(sdl_renderer, cached_texture, nullptr, &Rect);
148  } else { //else create a texture, render it, and then cache it - this is costly, so pick a good cache size
149  SDL_Surface *text = TTF_RenderUTF8_Blended(_font, arg2.c_str(), color_font);
150  SDL_Texture *texture = SDL_CreateTextureFromSurface(sdl_renderer, text);
151  SDL_QueryTexture(texture, nullptr, nullptr, &texW, &texH);
152  SDL_Rect Rect = {x, y - texH, texW, texH};
153  SDL_SetTextureColorMod(texture, color_font.r, color_font.g, color_font.b);
154  SDL_RenderCopy(sdl_renderer, texture, nullptr, &Rect);
155  SDL_FreeSurface(text);
156  font_cache::cache_string(arg2, texture, font);
157  }
158  }
159  return static_cast<uint16_t>(texW);
160 }
161 
175 uint16_t ST::renderer_sdl::draw_text_cached_glyphs(uint16_t font, const std::string &text, const int x, const int y,
176  const SDL_Color color_font) {
177  int32_t tempX = 0;
178  auto cached_vector = fonts_cache.find(font);
179  if (cached_vector != fonts_cache.end()) [[likely]] {
180  std::vector<ST::renderer_sdl::texture> tempVector = cached_vector->second;
181  if (!tempVector.empty()) [[likely]] {
182  tempX = x;
183  const char *arg3 = text.c_str();
184  for (int j = 0; arg3[j] != 0; j++) {
185  ST::renderer_sdl::texture glyph = tempVector.at(static_cast<unsigned int>(arg3[j] - 32));
186  SDL_Texture *texture = glyph.atlas;
187  SDL_Rect dstRect = {tempX, y - glyph.height, glyph.width, glyph.height};
188  SDL_Rect srcRect = {glyph.atlas_h_offset, glyph.atlas_v_offset, glyph.width, glyph.height};
189  SDL_SetTextureColorMod(texture, color_font.r, color_font.g, color_font.b);
190  SDL_RenderCopy(sdl_renderer, texture, &srcRect, &dstRect);
191  tempX += glyph.width;
192  }
193  }
194  }
195  return static_cast<uint16_t>(tempX - x);
196 }
197 
202 void ST::renderer_sdl::upload_surfaces(ska::bytell_hash_map<uint16_t, SDL_Surface *> *surfaces) {
203  if (surfaces != nullptr) {
204  surfaces_pointer = surfaces;
205  //Clear all textures, when adding surfaces. This is far from optimal and additional logic should be implemented to check which atlases are to be cleared and
206  //re-created.
207  for (auto &it: textures) {
208  if (it.second.atlas != nullptr) {
209  SDL_DestroyTexture(it.second.atlas);
210  it.second.atlas = nullptr;
211  }
212  }
213  std::vector<std::pair<uint16_t, SDL_Surface *>> surfaces_pairs{};
214  for (auto &it: *surfaces) {
215  surfaces_pairs.emplace_back(it.first, it.second);
216  }
217  process_surfaces(surfaces_pairs);
218  }
219 }
220 
221 //Helper function for sorting surfaces by their width and height.
222 bool
223 sort_by_surface_width_height(const std::pair<uint16_t, SDL_Surface *> &a, const std::pair<uint16_t, SDL_Surface *> &b) {
224  if (a.second->w == b.second->w) {
225  return a.second->h > b.second->h;
226  }
227  return a.second->w > b.second->w;
228 }
229 
236 void ST::renderer_sdl::process_surfaces(std::vector<std::pair<uint16_t, SDL_Surface *>> &surfaces_pairs) {
237  if (surfaces_pairs.empty()) {
238  return;
239  } else if (surfaces_pairs.size() == 1) { //Only one surface in vector => no point in creating an atlas.
240  auto tex = ST::renderer_sdl::texture{};
241  tex.width = surfaces_pairs.back().second->w;
242  tex.height = surfaces_pairs.back().second->h;
243  tex.atlas_v_offset = 0;
244  tex.atlas_h_offset = 0;
245  tex.atlas = SDL_CreateTextureFromSurface(sdl_renderer, surfaces_pairs.back().second);
246  textures[surfaces_pairs.back().first] = tex;
247  return;
248  }
249  sort(surfaces_pairs.begin(), surfaces_pairs.end(), sort_by_surface_width_height);
250 
251  std::vector<std::pair<uint16_t, SDL_Surface *>> remaining_surface_pairs{};
252  uint8_t atlas[atlas_grid_size][atlas_grid_size] = {0};
253  int h_index = 0;
254  int v_index = 0;
255 
256  SDL_Texture *atlas_texture = SDL_CreateTexture(sdl_renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STATIC,
257  ATLAS_SIZE, ATLAS_SIZE);
258  SDL_SetTextureBlendMode(atlas_texture, SDL_BLENDMODE_BLEND);
259  for (auto &it: surfaces_pairs) {
260  int t_width = it.second->w / 32;
261  int t_height = it.second->h / 32;
262 
263  //Texture either too large for atlas or has dimensions not power of 2
264  if ((it.second->w & (it.second->w - 1)) != 0 || (it.second->h & (it.second->h - 1)) != 0 ||
265  t_width > atlas_grid_size || t_height > atlas_grid_size) {
266  auto tex = ST::renderer_sdl::texture{};
267  tex.width = it.second->w;
268  tex.height = it.second->h;
269  tex.atlas_v_offset = 0;
270  tex.atlas_h_offset = 0;
271  tex.atlas = SDL_CreateTextureFromSurface(sdl_renderer, it.second);
272  textures[it.first] = tex;
273  continue;
274  }
275 
276  while (atlas[h_index][v_index] != 0) {
277  ++h_index;
278  if (h_index == atlas_grid_size) {
279  h_index = 0;
280  ++v_index;
281 
282  if (v_index == atlas_grid_size) {
283  remaining_surface_pairs.emplace_back(it);
284  break;
285  }
286  }
287  }
288 
289  if (v_index + t_height > atlas_grid_size) {
290  remaining_surface_pairs.emplace_back(it);
291  continue;
292  }
293 
294  for (int i = h_index; i < h_index + t_width; ++i) {
295  for (int j = v_index; j < v_index + t_height; ++j) {
296  atlas[i][j] = 1;
297  }
298  }
299 
300  //Blit to the larger surface
301  auto tex = ST::renderer_sdl::texture{};
302  tex.width = it.second->w;
303  tex.height = it.second->h;
304  tex.atlas_h_offset = h_index * 32;
305  tex.atlas_v_offset = v_index * 32;
306  tex.atlas = atlas_texture;
307  textures[it.first] = tex;
308 
309  SDL_Rect dst_rect = {tex.atlas_h_offset, tex.atlas_v_offset, tex.width, tex.height};
310  SDL_UpdateTexture(atlas_texture, &dst_rect, it.second->pixels, it.second->pitch);
311 
312  h_index += t_width;
313  if (h_index == atlas_grid_size) {
314  h_index = 0;
315  v_index += 1;
316  }
317  }
318  ST::renderer_sdl::process_surfaces(remaining_surface_pairs);
319 }
320 
324 void ST::renderer_sdl::upload_fonts(ska::bytell_hash_map<uint16_t, TTF_Font *> *fonts_t) {
325  if (fonts_t != nullptr) {
326  fonts_pointer = fonts_t;
327  for (auto &it: *fonts_t) {
328  if (it.second == nullptr) {
329  for (auto &k: fonts_cache[it.first]) {
330  if (k.atlas != nullptr) {
331  SDL_DestroyTexture(k.atlas);
332  k.atlas = nullptr;
333  }
334  }
335  fonts[it.first] = nullptr;
336  } else if (it.second != nullptr) {
337  if (fonts[it.first] != nullptr) {
338  for (auto &k: fonts_cache[it.first]) {
339  if (k.atlas != nullptr) {
340  SDL_DestroyTexture(k.atlas);
341  k.atlas = nullptr;
342  }
343  }
344  fonts[it.first] = nullptr;
345  }
346  fonts[it.first] = it.second;
347  cache_font(fonts[it.first], it.first);
348  }
349  }
350  }
351 }
352 
361 void ST::renderer_sdl::cache_font(TTF_Font *Font, uint16_t font_and_size) {
362  SDL_Color color_font = {255, 255, 255, 255};
363  char temp[2];
364  temp[1] = 0;
365  std::vector<ST::renderer_sdl::texture> result_glyphs{};
366  std::vector<SDL_Surface *> glyph_surfaces{};
367  result_glyphs.reserve(95);
368  glyph_surfaces.reserve(95);
369  int32_t total_width = 0;
370  int32_t max_height = 0;
371  for (char j = 32; j < 127; j++) {
372  temp[0] = j;
373  SDL_Surface *glyph_surface = TTF_RenderUTF8_Blended(Font, temp, color_font);
374  total_width += glyph_surface->w;
375  if (glyph_surface->h > max_height) {
376  max_height = glyph_surface->h;
377  }
378  glyph_surfaces.emplace_back(glyph_surface);
379  }
380  SDL_Texture *atlas_texture = SDL_CreateTexture(sdl_renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STATIC,
381  total_width, max_height);
382  SDL_SetTextureBlendMode(atlas_texture, SDL_BLENDMODE_BLEND);
383  int atlas_offset = 0;
384  for (auto surface: glyph_surfaces) {
386  glyph.width = surface->w;
387  glyph.height = surface->h;
388  glyph.atlas_h_offset = atlas_offset;
389  glyph.atlas_v_offset = 0;
390  glyph.atlas = atlas_texture;
391  result_glyphs.push_back(glyph);
392  SDL_Rect dst_rect = {glyph.atlas_h_offset, glyph.atlas_v_offset, glyph.width, glyph.height};
393  SDL_UpdateTexture(atlas_texture, &dst_rect, surface->pixels, surface->pitch);
394  SDL_FreeSurface(surface);
395  atlas_offset += glyph.width;
396  }
397  fonts_cache[font_and_size] = result_glyphs;
398 }
399 
404  SDL_RendererInfo info;
405  SDL_GetRendererInfo(sdl_renderer, &info);
406  if (!(info.flags & SDL_RENDERER_PRESENTVSYNC)) { // NOLINT(hicpp-signed-bitwise)
407  vsync = true;
408  close();
409  initialize(window, width, height);
410  upload_surfaces(surfaces_pointer);
411  upload_fonts(fonts_pointer);
412  }
413 }
414 
419  SDL_RendererInfo info;
420  SDL_GetRendererInfo(sdl_renderer, &info);
421  if (info.flags & SDL_RENDERER_PRESENTVSYNC) { // NOLINT(hicpp-signed-bitwise)
422  vsync = false;
423  close();
424  initialize(window, width, height);
425  upload_surfaces(surfaces_pointer);
426  upload_fonts(fonts_pointer);
427  }
428 }
429 
430 //INLINED METHODS
431 
438 void ST::renderer_sdl::draw_texture(const uint16_t arg, int32_t x, int32_t y) {
439  auto data = textures.find(arg);
440  if (data != textures.end()) {
441  auto texture = data->second;
442  SDL_Rect src_rect = {texture.atlas_h_offset, texture.atlas_v_offset, texture.width, texture.height};
443  SDL_Rect dst_rect = {x, y - texture.height, texture.width, texture.height};
444  SDL_RenderCopy(sdl_renderer, texture.atlas, &src_rect, &dst_rect);
445  }
446 }
447 
454 void ST::renderer_sdl::draw_texture_scaled(const uint16_t arg, int32_t x, int32_t y, float scale_x, float scale_y) {
455  auto data = textures.find(arg);
456  if (data != textures.end()) {
457  auto texture = data->second;
458  SDL_Rect src_rect = {texture.atlas_h_offset, texture.atlas_v_offset, texture.width, texture.height};
459  SDL_Rect dst_rect = {x,
460  y - static_cast<int>(static_cast<float>(texture.height) * scale_y),
461  static_cast<int>(static_cast<float>(texture.width) * scale_x),
462  static_cast<int>(static_cast<float>(texture.height) * scale_y)};
463 
464  SDL_RenderCopy(sdl_renderer, texture.atlas, &src_rect, &dst_rect);
465  }
466 }
467 
476 void ST::renderer_sdl::draw_rectangle_filled(int32_t x, int32_t y, int32_t w, int32_t h, SDL_Color color) {
477  SDL_Rect Rect = {x, y, w, h};
478  SDL_SetRenderDrawColor(sdl_renderer, color.r, color.g, color.b, color.a);
479  SDL_RenderFillRect(sdl_renderer, &Rect);
480  SDL_SetRenderDrawColor(sdl_renderer, 0, 0, 0, 255);
481 }
482 
491 void ST::renderer_sdl::draw_rectangle(int32_t x, int32_t y, int32_t w, int32_t h, SDL_Color color) {
492  SDL_Rect Rect = {x, y, w, h};
493  SDL_SetRenderDrawColor(sdl_renderer, color.r, color.g, color.b, color.a);
494  SDL_RenderDrawRect(sdl_renderer, &Rect);
495  SDL_SetRenderDrawColor(sdl_renderer, 0, 0, 0, 255);
496 }
497 
502 void ST::renderer_sdl::draw_background(const uint16_t arg) {
503  auto data = textures.find(arg);
504  if (data != textures.end()) {
505  auto texture = data->second;
506  SDL_Rect src_rect = {texture.atlas_h_offset, texture.atlas_v_offset, texture.width, texture.height};
507  SDL_RenderCopy(sdl_renderer, texture.atlas, &src_rect, nullptr);
508  }
509 }
510 
515 void ST::renderer_sdl::draw_background_parallax(const uint16_t arg, const uint16_t offset) {
516  auto data = textures.find(arg);
517  if (data != textures.end()) {
518  auto texture = data->second;
519 
520  float bg_ratio = (float) texture.width / (float) width;
521  int src_offset = (int) ((float) offset * bg_ratio);
522 
523  SDL_Rect dst_rect1 = {0, 0, width - offset, height};
524  SDL_Rect src_rect1 = {texture.atlas_h_offset + src_offset, texture.atlas_v_offset, texture.width - src_offset,
525  texture.height};
526  SDL_Rect src_rect2 = {texture.atlas_h_offset, texture.atlas_v_offset, src_offset, texture.height};
527  SDL_Rect dst_rect2 = {width - offset, 0, offset, height};
528 
529  SDL_RenderCopy(sdl_renderer, texture.atlas, &src_rect1, &dst_rect1);
530  SDL_RenderCopy(sdl_renderer, texture.atlas, &src_rect2, &dst_rect2);
531  }
532 }
533 
544 void ST::renderer_sdl::draw_sprite(uint16_t arg, int32_t x, int32_t y, uint8_t sprite, uint8_t animation,
545  uint8_t animation_num, uint8_t sprite_num) {
546  auto data = textures.find(arg);
547  if (data != textures.end()) {
548  auto texture = data->second;
549 
550  int temp1 = texture.height / animation_num;
551  int temp2 = texture.width / sprite_num;
552  SDL_Rect dst_rect = {x, y - temp1, temp2, temp1};
553  SDL_Rect src_rect = {texture.atlas_h_offset + (sprite * temp2),
554  texture.atlas_v_offset + (temp1 * (animation - 1)), temp2, temp1};
555  SDL_RenderCopy(sdl_renderer, texture.atlas, &src_rect, &dst_rect);
556  }
557 }
558 
569 void ST::renderer_sdl::draw_sprite_scaled(uint16_t arg, int32_t x, int32_t y, uint8_t sprite, uint8_t animation,
570  uint8_t animation_num, uint8_t sprite_num, float scale_x, float scale_y) {
571  auto data = textures.find(arg);
572  if (data != textures.end()) {
573  auto texture = data->second;
574 
575  int temp1 = texture.height / animation_num;
576  int temp2 = texture.width / sprite_num;
577  SDL_Rect dst_rect = {x,
578  y - static_cast<int>(static_cast<float>(temp1) * scale_y),
579  static_cast<int>(static_cast<float>(temp2) * scale_x),
580  static_cast<int>(static_cast<float>(temp1) * scale_y)};
581  SDL_Rect src_rect = {texture.atlas_h_offset + (sprite * temp2),
582  texture.atlas_v_offset + (temp1 * (animation - 1)), temp2, temp1};
583  SDL_RenderCopy(sdl_renderer, texture.atlas, &src_rect, &dst_rect);
584  }
585 }
586 
594 void ST::renderer_sdl::draw_overlay(uint16_t arg, uint8_t sprite, uint8_t sprite_num) {
595  auto data = textures.find(arg);
596  if (data != textures.end()) {
597  auto texture = data->second;
598 
599  SDL_Rect src_rect = {sprite * (texture.width / sprite_num), 0, texture.width / sprite_num, texture.height};
600  SDL_RenderCopy(sdl_renderer, texture.atlas, &src_rect, nullptr);
601  }
602 }
603 
607 void ST::renderer_sdl::clear_screen(SDL_Color color) {
608  SDL_SetRenderDrawColor(sdl_renderer, color.r, color.g, color.b, color.a);
609  SDL_RenderClear(sdl_renderer);
610  SDL_SetRenderDrawColor(sdl_renderer, 0, 0, 0, 0);
611 }
612 
617  SDL_RenderClear(sdl_renderer);
618 }
619 
627 void ST::renderer_sdl::set_draw_color(uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
628  SDL_SetRenderDrawColor(sdl_renderer, r, g, b, a);
629 }
630 
635  SDL_RenderPresent(sdl_renderer);
636 }
637 
643 void ST::renderer_sdl::set_resolution(int16_t r_width, int16_t r_height) {
644  width = r_width;
645  height = r_height;
646  SDL_RenderSetLogicalSize(sdl_renderer, width, height);
647 }
648 
649 
The renderer for the engine.
uint16_t draw_text_lru_cached(uint16_t font, const std::string &arg2, int x, int y, SDL_Color color_font)
void upload_fonts(ska::bytell_hash_map< uint16_t, TTF_Font * > *fonts)
void cache_font(TTF_Font *Font, uint16_t font_and_size)
void upload_surfaces(ska::bytell_hash_map< uint16_t, SDL_Surface * > *surfaces)
void draw_overlay(uint16_t arg, uint8_t sprite, uint8_t sprite_num)
void draw_sprite_scaled(uint16_t arg, int32_t x, int32_t y, uint8_t sprite, uint8_t animation, uint8_t animation_num, uint8_t sprite_num, float scale_x, float scale_y)
void draw_sprite(uint16_t arg, int32_t x, int32_t y, uint8_t sprite, uint8_t animation, uint8_t animation_num, uint8_t sprite_num)
void clear_screen(SDL_Color color)
void draw_texture(uint16_t arg, int32_t x, int32_t y)
uint16_t draw_text_cached_glyphs(uint16_t font, const std::string &text, int x, int y, SDL_Color color_font)
void draw_background_parallax(uint16_t arg, uint16_t offset)
int8_t initialize(SDL_Window *win, int16_t width, int16_t height)
void draw_texture_scaled(uint16_t arg, int32_t x, int32_t y, float scale_x, float scale_y)
void set_resolution(int16_t r_width, int16_t r_height)
void draw_background(uint16_t arg)
void process_surfaces(std::vector< std::pair< uint16_t, SDL_Surface * >> &surfaces_pairs)
void draw_rectangle(int32_t x, int32_t y, int32_t w, int32_t h, SDL_Color color)
void set_draw_color(uint8_t, uint8_t, uint8_t, uint8_t)
void draw_rectangle_filled(int32_t x, int32_t y, int32_t w, int32_t h, SDL_Color color)
This struct represents text objects in the game.
Definition: text.hpp:23