1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2 
3 
4 #include "TextureAtlas.h"
5 
6 #include "Bitmap.h"
7 #include "LegacyAtlasAlloc.h"
8 #include "QuadtreeAtlasAlloc.h"
9 #include "Rendering/GlobalRendering.h"
10 #include "Rendering/GL/myGL.h"
11 #include "Rendering/GL/PBO.h"
12 #include "System/FileSystem/FileHandler.h"
13 #include "System/Log/ILog.h"
14 #include "System/Util.h"
15 #include "System/Exceptions.h"
16 
17 #include <list>
18 #include <cstring>
19 
20 CR_BIND(AtlasedTexture, )
21 CR_REG_METADATA(AtlasedTexture,
22 		(CR_MEMBER(x), CR_MEMBER(y), CR_MEMBER(z), CR_MEMBER(w)))
23 
24 CR_BIND(GroundFXTexture, )
25 CR_REG_METADATA(GroundFXTexture,
26 		(CR_MEMBER(x), CR_MEMBER(y), CR_MEMBER(z), CR_MEMBER(w)))
27 
28 // texture spacing in the atlas (in pixels)
29 #define TEXMARGIN 2
30 
31 bool CTextureAtlas::debug;
32 
CTextureAtlas(unsigned int allocType)33 CTextureAtlas::CTextureAtlas(unsigned int allocType)
34 	: atlasAllocator(NULL)
35 	, atlasTexID(0)
36 	, initialized(false)
37 	, freeTexture(true)
38 {
39 	switch (allocType) {
40 		case ATLAS_ALLOC_LEGACY: { atlasAllocator = new CLegacyAtlasAlloc(); } break;
41 		case ATLAS_ALLOC_QUADTREE: { atlasAllocator = new CQuadtreeAtlasAlloc(); } break;
42 		default: { assert(false); } break;
43 	}
44 
45 	atlasAllocator->SetNonPowerOfTwo(globalRendering->supportNPOTs);
46 	// atlasAllocator->SetMaxSize(globalRendering->maxTextureSize, globalRendering->maxTextureSize);
47 }
48 
~CTextureAtlas()49 CTextureAtlas::~CTextureAtlas()
50 {
51 	if (freeTexture) {
52 		glDeleteTextures(1, &atlasTexID);
53 	}
54 	delete atlasAllocator;
55 }
56 
AddTex(std::string name,int xsize,int ysize,TextureType texType)57 void* CTextureAtlas::AddTex(std::string name, int xsize, int ysize, TextureType texType)
58 {
59 	const int gpp = GetBPP(texType);
60 	const size_t data_size = xsize * ysize * gpp / 8;
61 	MemTex* memtex = new MemTex;
62 	memtex->xsize = xsize;
63 	memtex->ysize = ysize;
64 	memtex->texType = texType;
65 	memtex->data = new char[data_size];
66 	StringToLowerInPlace(name);
67 	memtex->names.push_back(name);
68 	memtextures.push_back(memtex);
69 
70 	atlasAllocator->AddEntry(name, int2(xsize, ysize));
71 
72 	return memtex->data;
73 }
74 
AddTexFromMem(std::string name,int xsize,int ysize,TextureType texType,void * data)75 int CTextureAtlas::AddTexFromMem(std::string name, int xsize, int ysize, TextureType texType, void* data)
76 {
77 	void* memdata = AddTex(name, xsize, ysize, texType);
78 	const int gpp = GetBPP(texType);
79 	const size_t data_size = xsize * ysize * gpp / 8;
80 	std::memcpy(memdata, data, data_size);
81 	return 1;
82 }
83 
AddTexFromFile(std::string name,std::string file)84 int CTextureAtlas::AddTexFromFile(std::string name, std::string file)
85 {
86 	StringToLowerInPlace(name);
87 
88 	// if the file is already loaded, use that instead
89 	std::string lcFile = StringToLower(file);
90 	std::map<std::string, MemTex*>::iterator it = files.find(lcFile);
91 	if (it != files.end()) {
92 		MemTex* memtex = it->second;
93 		memtex->names.push_back(name);
94 		return 1;
95 	}
96 
97 
98 	CBitmap bitmap;
99 	if (!bitmap.Load(file)) {
100 		throw content_error("Could not load texture from file " + file);
101 	}
102 
103 	if (bitmap.channels != 4 || bitmap.compressed) {
104 		// only suport RGBA for now
105 		throw content_error("Unsupported bitmap format in file " + file);
106 	}
107 
108 	const int ret = AddTexFromMem(name, bitmap.xsize, bitmap.ysize, RGBA32, bitmap.mem);
109 	if (ret == 1) {
110 		files[lcFile] = memtextures.back();
111 	}
112 	return ret;
113 }
114 
Finalize()115 bool CTextureAtlas::Finalize()
116 {
117 	const bool success = atlasAllocator->Allocate();
118 
119 	if (success)
120 		CreateTexture();
121 
122 	for (std::vector<MemTex*>::iterator it = memtextures.begin(); it != memtextures.end(); ++it) {
123 		delete[] (char*)(*it)->data;
124 		delete (*it);
125 	}
126 	memtextures.clear();
127 
128 	return success;
129 }
130 
CreateTexture()131 void CTextureAtlas::CreateTexture()
132 {
133 	const int2 atlasSize = atlasAllocator->GetAtlasSize();
134 
135 	PBO pbo;
136 	pbo.Bind();
137 	pbo.New(atlasSize.x * atlasSize.y * 4);
138 
139 	unsigned char* data = (unsigned char*)pbo.MapBuffer(GL_WRITE_ONLY);
140 
141 	{
142 		// make spacing between textures black transparent to avoid ugly lines with linear filtering
143 		std::memset(data, 0, atlasSize.x * atlasSize.y * 4);
144 
145 		for (std::vector<MemTex*>::iterator it = memtextures.begin(); it != memtextures.end(); ++it) {
146 			const float4 texCoords = atlasAllocator->GetTexCoords((*it)->names[0]);
147 			const float4 absCoords = atlasAllocator->GetEntry((*it)->names[0]);
148 			const int xpos = absCoords.x;
149 			const int ypos = absCoords.y;
150 
151 			AtlasedTexture tex(texCoords);
152 			for (size_t n = 0; n < (*it)->names.size(); ++n) {
153 				textures[(*it)->names[n]] = tex;
154 			}
155 
156 			for (int y = 0; y < (*it)->ysize; ++y) {
157 				int* dst = ((int*)data) + xpos + (ypos + y) * atlasSize.x;
158 				int* src = ((int*)(*it)->data) + y * (*it)->xsize;
159 				memcpy(dst, src, (*it)->xsize * 4);
160 			}
161 		}
162 
163 		if (debug) {
164 			CBitmap tex(data, atlasSize.x, atlasSize.y);
165 			tex.Save(name + "-" + IntToString(atlasSize.x) + "x" + IntToString(atlasSize.y) + ".png");
166 		}
167 	}
168 
169 	pbo.UnmapBuffer();
170 
171 	const int maxMipMaps = atlasAllocator->GetMaxMipMaps();
172 	glGenTextures(1, &atlasTexID);
173 		glBindTexture(GL_TEXTURE_2D, atlasTexID);
174 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
175 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (maxMipMaps > 0) ? GL_LINEAR_MIPMAP_NEAREST : GL_NEAREST);
176 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,     GL_CLAMP_TO_EDGE);
177 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,     GL_CLAMP_TO_EDGE);
178 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL,  maxMipMaps);
179 		if (maxMipMaps > 0) {
180 			glBuildMipmaps(GL_TEXTURE_2D, GL_RGBA8, atlasSize.x, atlasSize.y, GL_RGBA, GL_UNSIGNED_BYTE, pbo.GetPtr()); //FIXME disable texcompression //FIXME 2 PBO!!!!
181 		} else {
182 			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, atlasSize.x, atlasSize.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, pbo.GetPtr());
183 		}
184 
185 	pbo.Invalidate();
186 	pbo.Unbind();
187 
188 	initialized = true;
189 }
190 
GetBPP(TextureType texType)191 int CTextureAtlas::GetBPP(TextureType texType)
192 {
193 	switch(texType) {
194 		case RGBA32:
195 			return 32;
196 		default:
197 			return 32;
198 	}
199 }
200 
BindTexture()201 void CTextureAtlas::BindTexture()
202 {
203 	glBindTexture(GL_TEXTURE_2D, atlasTexID);
204 }
205 
TextureExists(const std::string & name)206 bool CTextureAtlas::TextureExists(const std::string& name)
207 {
208 	return textures.find(StringToLower(name)) != textures.end();
209 }
210 
211 
GetTexture(const std::string & name)212 AtlasedTexture& CTextureAtlas::GetTexture(const std::string& name)
213 {
214 	return textures[StringToLower(name)];
215 }
216 
217 
GetTextureWithBackup(const std::string & name,const std::string & backupName)218 AtlasedTexture& CTextureAtlas::GetTextureWithBackup(const std::string& name, const std::string& backupName)
219 {
220 	if (textures.find(StringToLower(name)) != textures.end()) {
221 		return textures[StringToLower(name)];
222 	} else {
223 		return textures[StringToLower(backupName)];
224 	}
225 }
226 
GetSize() const227 int2 CTextureAtlas::GetSize() const {
228 	return (atlasAllocator->GetAtlasSize());
229 }
230 
231