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