1 ///////////////////////////////////////////////////////////////////////////////
2 // Copyright (C) 2004-2011 by The Allacrost Project
3 // Copyright (C) 2012-2016 by Bertram (Valyria Tear)
4 // All Rights Reserved
5 //
6 // This code is licensed under the GNU GPL version 2. It is free software
7 // and you may modify it and/or redistribute it under the terms of this license.
8 // See http://www.gnu.org/copyleft/gpl.html for details.
9 ///////////////////////////////////////////////////////////////////////////////
10
11 /** ****************************************************************************
12 *** \file tex_mgmt.h
13 *** \author Raj Sharma, roos@allacrost.org
14 *** \author Yohann Ferreira, yohann ferreira orange fr
15 *** \brief Source file for texture management code
16 *** ***************************************************************************/
17
18 #include "texture.h"
19
20 #include "video.h"
21
22 #include "utils/utils_common.h"
23
24 #include <cassert>
25
26 using namespace vt_utils;
27
28 namespace vt_video
29 {
30
31 namespace private_video
32 {
33
34 // -----------------------------------------------------------------------------
35 // TexSheet class
36 // -----------------------------------------------------------------------------
37
TexSheet(uint32_t sheet_width,uint32_t sheet_height,GLuint sheet_id,TexSheetType sheet_type,bool sheet_static)38 TexSheet::TexSheet(uint32_t sheet_width, uint32_t sheet_height, GLuint sheet_id, TexSheetType sheet_type, bool sheet_static) :
39 width(sheet_width),
40 height(sheet_height),
41 tex_id(sheet_id),
42 type(sheet_type),
43 is_static(sheet_static),
44 smoothed(false),
45 loaded(true)
46 {
47 Smooth();
48 }
49
~TexSheet()50 TexSheet::~TexSheet()
51 {
52 // Unload the OpenGL texture from memory.
53 TextureManager->_DeleteTexture(tex_id);
54 }
55
Unload()56 bool TexSheet::Unload()
57 {
58 if (loaded == false) {
59 IF_PRINT_WARNING(VIDEO_DEBUG) << "attempted to unload an already unloaded texture sheet" << std::endl;
60 return false;
61 }
62
63 TextureManager->_DeleteTexture(tex_id);
64 tex_id = INVALID_TEXTURE_ID;
65 loaded = false;
66 return true;
67 }
68
Reload()69 bool TexSheet::Reload()
70 {
71 if (loaded) {
72 if(VIDEO_DEBUG)
73 IF_PRINT_WARNING(VIDEO_DEBUG) << "attempted to load an already loaded texture sheet" << std::endl;
74 return false;
75 }
76
77 // Create new OpenGL texture.
78 GLuint id = TextureManager->_CreateBlankGLTexture(width, height);
79
80 if (id == INVALID_TEXTURE_ID) {
81 PRINT_ERROR << "call to TextureController::_CreateBlankGLTexture() failed" << std::endl;
82 return false;
83 }
84
85 tex_id = id;
86
87 // Restore texture smoothing if applied.
88 bool was_smoothed = smoothed;
89 smoothed = false;
90 Smooth(was_smoothed);
91
92 // Reload all of the images that belong to this texture
93 if(TextureManager->_ReloadImagesToSheet(this) == false) {
94 PRINT_ERROR << "call to TextureController::_ReloadImagesToSheet() failed" << std::endl;
95 return false;
96 }
97
98 loaded = true;
99 return true;
100 }
101
CopyRect(int32_t x,int32_t y,ImageMemory & data)102 bool TexSheet::CopyRect(int32_t x, int32_t y, ImageMemory& data)
103 {
104 TextureManager->_BindTexture(tex_id);
105
106 data.GlTexSubImage(x, y);
107
108 if(VideoManager->CheckGLError()) {
109 IF_PRINT_WARNING(VIDEO_DEBUG) << "an OpenGL error occured: " << VideoManager->CreateGLErrorString() << std::endl;
110 return false;
111 }
112
113 return true;
114 }
115
CopyScreenRect(int32_t x,int32_t y,const ScreenRect & screen_rect)116 bool TexSheet::CopyScreenRect(int32_t x, int32_t y, const ScreenRect &screen_rect)
117 {
118 TextureManager->_BindTexture(tex_id);
119
120 glCopyTexSubImage2D(
121 GL_TEXTURE_2D, // target
122 0, // level
123 x, // x offset within tex sheet
124 y, // y offset within tex sheet
125 screen_rect.left, // left starting pixel of the screen to copy
126 screen_rect.top, // top starting pixel of the screen to copy
127 screen_rect.width, // width in pixels of image
128 screen_rect.height // height in pixels of image
129 );
130
131 if(VideoManager->CheckGLError()) {
132 IF_PRINT_WARNING(VIDEO_DEBUG) << "an OpenGL error occured: " << VideoManager->CreateGLErrorString() << std::endl;
133 return false;
134 }
135
136 return true;
137 }
138
Smooth(bool flag)139 void TexSheet::Smooth(bool flag)
140 {
141 // If setting has changed, set the appropriate filtering
142 if(smoothed != flag) {
143 smoothed = flag;
144 GLenum filtering_type = smoothed ? GL_LINEAR : GL_NEAREST;
145
146 TextureManager->_BindTexture(tex_id);
147 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering_type);
148 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering_type);
149 }
150 }
151
DEBUG_Draw() const152 void TexSheet::DEBUG_Draw() const
153 {
154 // The vertex positions.
155 float vertex_positions[] =
156 {
157 1.0f, 1.0f, 0.0f, // Vertex One.
158 0.0f, 1.0f, 0.0f, // Vertex Two.
159 0.0f, 0.0f, 0.0f, // Vertex Three.
160 1.0f, 0.0f, 0.0f // Vertex Four.
161 };
162
163 // The vertex texture coordinates.
164 float vertex_texture_coordinates[] =
165 {
166 0.0f, 1.0f, // Vertex One.
167 1.0f, 1.0f, // Vertex Two.
168 1.0f, 0.0f, // Vertex Three.
169 0.0f, 0.0f // Vertex Four.
170 };
171
172 // The vertex colors.
173 float vertex_colors[] =
174 {
175 1.0f, 1.0f, 1.0f, 1.0f, // Vertex One.
176 1.0f, 1.0f, 1.0f, 1.0f, // Vertex Two.
177 1.0f, 1.0f, 1.0f, 1.0f, // Vertex Three.
178 1.0f, 1.0f, 1.0f, 1.0f // Vertex Four.
179 };
180
181 // Enable texturing and bind the texture.
182 VideoManager->DisableBlending();
183 VideoManager->EnableTexture2D();
184 TextureManager->_BindTexture(tex_id);
185
186 // Load the solid shader program.
187 gl::ShaderProgram* shader_program = VideoManager->LoadShaderProgram(gl::shader_programs::Solid);
188 assert(shader_program != nullptr);
189
190 // Draw a black background.
191 VideoManager->DrawSprite(shader_program, vertex_positions, vertex_texture_coordinates, vertex_colors, ::vt_video::Color::black);
192
193 // Unload the shader program.
194 VideoManager->UnloadShaderProgram();
195
196 // Load the sprite shader program.
197 shader_program = VideoManager->LoadShaderProgram(gl::shader_programs::Sprite);
198 assert(shader_program != nullptr);
199
200 // Draw the image.
201 VideoManager->DrawSprite(shader_program, vertex_positions, vertex_texture_coordinates, vertex_colors);
202
203 // Unload the shader program.
204 VideoManager->UnloadShaderProgram();
205 }
206
207 // -----------------------------------------------------------------------------
208 // FixedTexSheet class
209 // -----------------------------------------------------------------------------
210
FixedTexSheet(int32_t sheet_width,int32_t sheet_height,GLuint sheet_id,TexSheetType sheet_type,bool sheet_static,int32_t img_width,int32_t img_height)211 FixedTexSheet::FixedTexSheet(int32_t sheet_width, int32_t sheet_height, GLuint sheet_id, TexSheetType sheet_type, bool sheet_static, int32_t img_width, int32_t img_height) :
212 TexSheet(sheet_width, sheet_height, sheet_id, sheet_type, sheet_static),
213 _texture_width(img_width),
214 _texture_height(img_height)
215 {
216 // Set all the dimensions
217 _block_width = width / _texture_width;
218 _block_height = height / _texture_height;
219
220 // Allocate the blocks array
221 int32_t num_blocks = _block_width * _block_height;
222 _blocks = new FixedTexNode[num_blocks];
223
224 // Construct the linked list of open blocks
225 _open_list_head = &_blocks[0];
226 _open_list_tail = &_blocks[num_blocks - 1];
227
228 for(int32_t i = 0; i < num_blocks - 1; i++) {
229 _blocks[i].image = nullptr;
230 _blocks[i].next = &_blocks[i + 1];
231 _blocks[i].block_index = i;
232 }
233
234 _open_list_tail->image = nullptr;
235 _open_list_tail->next = nullptr;
236 _open_list_tail->block_index = num_blocks - 1;
237 }
238
~FixedTexSheet()239 FixedTexSheet::~FixedTexSheet()
240 {
241 if (GetNumberTextures() != 0)
242 IF_PRINT_WARNING(VIDEO_DEBUG) << "texture sheet being deleted when it has a non-zero allocated texture count: " << GetNumberTextures() << std::endl;
243
244 if (_blocks != nullptr) {
245 delete[] _blocks;
246 _blocks = nullptr;
247 }
248 }
249
AddTexture(BaseTexture * img,ImageMemory & data)250 bool FixedTexSheet::AddTexture(BaseTexture *img, ImageMemory &data)
251 {
252 if (InsertTexture(img) == false)
253 return false;
254
255 // Copy the pixel data for the texture over.
256 if (CopyRect(img->x, img->y, data) == false) {
257 IF_PRINT_WARNING(VIDEO_DEBUG) << "VIDEO ERROR: CopyRect() failed in TexSheet::AddImage()!" << std::endl;
258 return false;
259 }
260
261 return true;
262 }
263
InsertTexture(BaseTexture * img)264 bool FixedTexSheet::InsertTexture(BaseTexture *img)
265 {
266 if(img == nullptr) {
267 IF_PRINT_WARNING(VIDEO_DEBUG) << "nullptr pointer was given as function argument" << std::endl;
268 return false;
269 }
270
271 // Retrieve the node from the head of the list to use for this texture
272 FixedTexNode *node = _RemoveOpenNode();
273 if(node == nullptr) // This condition indicates that there are no remaining free nodes on the open list
274 return false;
275
276 // Check if there's already an image allocated at this block (an image was freed earlier, but not removed)
277 // If so, we must now remove it from memory
278 if(node->image != nullptr) {
279 // TODO: TextureManager needs to have the image element removed from its map containers
280 node->image = nullptr;
281 }
282
283 // Calculate the texture's pixel coordinates in the sheet given this node's block index
284 img->x = _texture_width * (node->block_index % _block_width);
285 img->y = _texture_height * (node->block_index / _block_width);
286
287 // Calculate the u,v coordinates
288 float sheet_width = static_cast<float>(width);
289 float sheet_height = static_cast<float>(height);
290
291 img->u1 = static_cast<float>(img->x + 0.5f) / sheet_width;
292 img->u2 = static_cast<float>(img->x + img->width - 0.5f) / sheet_width;
293 img->v1 = static_cast<float>(img->y + 0.5f) / sheet_height;
294 img->v2 = static_cast<float>(img->y + img->height - 0.5f) / sheet_height;
295
296 img->texture_sheet = this;
297 node->image = img;
298 return true;
299 } // bool FixedTexSheet::InsertTexture(BaseTexture* img)
300
301
302
RemoveTexture(BaseTexture * img)303 void FixedTexSheet::RemoveTexture(BaseTexture *img)
304 {
305 if(img == nullptr) {
306 IF_PRINT_WARNING(VIDEO_DEBUG) << "nullptr pointer was given as function argument" << std::endl;
307 return;
308 }
309
310 int32_t block_index = _CalculateBlockIndex(img);
311
312 // Check to make sure the block is actually owned by this image
313 if(_blocks[block_index].image != img) {
314 IF_PRINT_WARNING(VIDEO_DEBUG) << "tried to remove a fixed block not owned by the image" << std::endl;
315 return;
316 }
317
318 _blocks[block_index].image = nullptr;
319 _AddOpenNode(&_blocks[block_index]);
320 }
321
322
323
FreeTexture(BaseTexture * img)324 void FixedTexSheet::FreeTexture(BaseTexture *img)
325 {
326 if(img == nullptr) {
327 IF_PRINT_WARNING(VIDEO_DEBUG) << "nullptr pointer was given as function argument" << std::endl;
328 return;
329 }
330
331 int32_t block_index = _CalculateBlockIndex(img);
332
333 // Check to make sure the block is actually owned by this image
334 if(_blocks[block_index].image != img) {
335 IF_PRINT_WARNING(VIDEO_DEBUG) << "tried to remove a fixed block not owned by the image" << std::endl;
336 return;
337 }
338
339 // Unliked the RemoveTexture call, we do not set the block's image to nullptr here
340 _AddOpenNode(&_blocks[block_index]);
341 }
342
343
344
RestoreTexture(BaseTexture * img)345 void FixedTexSheet::RestoreTexture(BaseTexture *img)
346 {
347 if(img == nullptr) {
348 IF_PRINT_WARNING(VIDEO_DEBUG) << "nullptr pointer was given as function argument" << std::endl;
349 return;
350 }
351
352 // Go through the list of open nodes and find the node with this image
353 FixedTexNode *last = nullptr;
354 FixedTexNode *now = _open_list_head;
355
356 while(now != nullptr) {
357 // If we found the texture, update the list so that the containing node temporarily becomes
358 // the head of the open list, then remove that node from the list head
359 if(now->image == img) {
360 if(last != nullptr) {
361 last = now->next;
362 now->next = _open_list_head;
363 _open_list_head = now;
364 }
365
366 _RemoveOpenNode();
367 return;
368 }
369
370 last = now;
371 now = now->next;
372 }
373
374 IF_PRINT_WARNING(VIDEO_DEBUG) << "failed to restore, texture was not found in open list" << std::endl;
375 }
376
377
378
GetNumberTextures()379 uint32_t FixedTexSheet::GetNumberTextures()
380 {
381 uint32_t num_blocks = 0;
382
383 for(int32_t i = 0; i < _block_width * _block_height; i++) {
384 if(_blocks[i].image != nullptr) {
385 num_blocks++;
386 }
387 }
388
389 return num_blocks;
390 }
391
392
393
_CalculateBlockIndex(BaseTexture * img)394 int32_t FixedTexSheet::_CalculateBlockIndex(BaseTexture *img)
395 {
396 int32_t block_x = img->x / _texture_width;
397 int32_t block_y = img->y / _texture_height;
398
399 return (block_x + _block_width * block_y);
400 }
401
402
403
_AddOpenNode(FixedTexNode * node)404 void FixedTexSheet::_AddOpenNode(FixedTexNode *node)
405 {
406 if(_open_list_tail != nullptr) {
407 _open_list_tail->next = node;
408 _open_list_tail = node;
409 _open_list_tail->next = nullptr;
410 } else {
411 _open_list_head = node;
412 _open_list_tail = node;
413 _open_list_tail->next = nullptr;
414 }
415 }
416
417
418
_RemoveOpenNode()419 FixedTexNode *FixedTexSheet::_RemoveOpenNode()
420 {
421 if(_open_list_head == nullptr)
422 return nullptr;
423
424 FixedTexNode *node = _open_list_head;
425 _open_list_head = _open_list_head->next;
426 node->next = nullptr;
427
428 // This condition means we just removed the last open block, so set the tail pointer to nullptr as well
429 if(_open_list_head == nullptr) {
430 _open_list_tail = nullptr;
431 }
432
433 return node;
434 }
435
436 // -----------------------------------------------------------------------------
437 // VariableTexSheet class
438 // -----------------------------------------------------------------------------
439
VariableTexSheet(int32_t sheet_width,int32_t sheet_height,GLuint sheet_id,TexSheetType sheet_type,bool sheet_static)440 VariableTexSheet::VariableTexSheet(int32_t sheet_width, int32_t sheet_height, GLuint sheet_id, TexSheetType sheet_type, bool sheet_static) :
441 TexSheet(sheet_width, sheet_height, sheet_id, sheet_type, sheet_static)
442 {
443 _block_width = width / 16;
444 _block_height = height / 16;
445 _blocks = new VariableTexNode[_block_width * _block_height];
446 }
447
~VariableTexSheet()448 VariableTexSheet::~VariableTexSheet()
449 {
450 if (GetNumberTextures() != 0)
451 IF_PRINT_WARNING(VIDEO_DEBUG) << "texture sheet being deleted when it has a non-zero allocated texture count: " << GetNumberTextures() << std::endl;
452
453 if (_blocks != nullptr) {
454 delete [] _blocks;
455 _blocks = nullptr;
456 }
457 }
458
AddTexture(BaseTexture * img,ImageMemory & data)459 bool VariableTexSheet::AddTexture(BaseTexture *img, ImageMemory &data)
460 {
461 if(InsertTexture(img) == false)
462 return false;
463
464 // Copy the pixel data for the texture over
465 if(CopyRect(img->x, img->y, data) == false) {
466 IF_PRINT_WARNING(VIDEO_DEBUG) << "VIDEO ERROR: CopyRect() failed in TexSheet::AddImage()!" << std::endl;
467 return false;
468 }
469
470 return true;
471 }
472
473
474
InsertTexture(BaseTexture * img)475 bool VariableTexSheet::InsertTexture(BaseTexture *img)
476 {
477 if(img == nullptr) {
478 IF_PRINT_WARNING(VIDEO_DEBUG) << "nullptr pointer was given as function argument" << std::endl;
479 return false;
480 }
481
482 // Don't allow insertions into a texture sheet containing a texture larger than 512x512.
483 // Texture sheets with this property may only be used by one texture at a time
484 if(_block_width > 32 || _block_height > 32) { // 32 blocks == 512 pixels
485 if(_blocks[0].free_image == false)
486 return false;
487 }
488
489 // Attempt to find an open region in the texture sheet to fit this texture
490 int32_t block_x = -1, block_y = -1;
491 int32_t w = (img->width + 15) / 16;
492 int32_t h = (img->height + 15) / 16;
493
494 // This is a brute force algorithm to try and find space to allocate the texture.
495 // If this becomes a bottleneck, we may wish to use a more intellegent algorithm here.
496 bool continue_search = true;
497 for(int32_t y = 0; y < _block_height - h + 1 && continue_search; y++) {
498 for(int32_t x = 0; x < _block_width - w + 1; x++) {
499 int32_t furthest_blocker = -1;
500
501 bool continue_neighbor_search = true;
502 for(int32_t dy = 0; dy < h && continue_neighbor_search; dy++) {
503 for(int32_t dx = 0; dx < w; dx++) {
504 if(_blocks[(x + dx) + ((y + dy) * _block_width)].free_image == false) {
505 furthest_blocker = x + dx;
506 continue_neighbor_search = false;
507 break;
508 }
509 }
510 }
511
512 if(furthest_blocker == -1) {
513 block_x = x;
514 block_y = y;
515 continue_search = false;
516 break;
517 }
518 }
519 }
520
521 // If either of these conditions is true, it means we were unable to allocate enough space to insert this texture
522 if(block_x == -1 || block_y == -1)
523 return false;
524
525 // Go through each block that is to be occupied by the new texture and set its properties
526 for(int32_t y = block_y; y < block_y + h; y++) {
527 for(int32_t x = block_x; x < block_x + w; x++) {
528 int32_t index = x + (y * _block_width);
529
530 // If the texture pointer for the block is not nullptr, this means it contains a freed texture.
531 // Now we must remove that texture entirely since we are overwriting at least one of its blocks.
532 if(_blocks[index].image) {
533 RemoveTexture(_blocks[index].image);
534 }
535
536 _blocks[index].free_image = false;
537 _blocks[index].image = img;
538 }
539 }
540
541 // Calculate the pixel and uv coordinates for the newly inserted texture
542 img->x = block_x * 16;
543 img->y = block_y * 16;
544
545 float sheet_width = static_cast<float>(width);
546 float sheet_height = static_cast<float>(height);
547
548 img->u1 = static_cast<float>(img->x + 0.5f) / sheet_width;
549 img->u2 = static_cast<float>(img->x + img->width - 0.5f) / sheet_width;
550 img->v1 = static_cast<float>(img->y + 0.5f) / sheet_height;
551 img->v2 = static_cast<float>(img->y + img->height - 0.5f) / sheet_height;
552
553 img->texture_sheet = this;
554 _textures.insert(img);
555
556 return true;
557 } // bool VariableTexSheet::InsertTexture(BaseTexture* img)
558
559
560
RemoveTexture(BaseTexture * img)561 void VariableTexSheet::RemoveTexture(BaseTexture *img)
562 {
563 _SetBlockProperties(img, nullptr, true);
564 _textures.erase(img);
565 }
566
567
568
_SetBlockProperties(BaseTexture * tex,BaseTexture * new_tex,bool free)569 void VariableTexSheet::_SetBlockProperties(BaseTexture *tex, BaseTexture *new_tex, bool free)
570 {
571 if(tex == nullptr) {
572 IF_PRINT_WARNING(VIDEO_DEBUG) << "nullptr pointer was given as function argument" << std::endl;
573 return;
574 }
575
576 if(_textures.find(tex) == _textures.end()) {
577 IF_PRINT_WARNING(VIDEO_DEBUG) << "texture pointer argument was not contained within this texture sheet" << std::endl;
578 }
579
580 // Calculate upper-left corner in blocks
581 int32_t block_x = tex->x / 16;
582 int32_t block_y = tex->y / 16;
583
584 // Calculate width and height in blocks
585 int32_t w = (tex->width + 15) / 16;
586 int32_t h = (tex->height + 15) / 16;
587
588 for(int32_t y = block_y; y < block_y + h; y++) {
589 for(int32_t x = block_x; x < block_x + w; x++) {
590 int32_t index = x + y * _block_width;
591 if(_blocks[index].image == tex) {
592 _blocks[index].free_image = free;
593 _blocks[index].image = new_tex;
594 }
595 }
596 }
597 }
598
599 } // namespace private_video
600
601 } // namespace vt_video
602