1 // Copyright 2013-2018 the openage authors. See copying.md for legal info.
2 
3 #include "texture.h"
4 
5 #include <SDL2/SDL.h>
6 #include <SDL2/SDL_image.h>
7 
8 #include <cmath>
9 #include <cstdio>
10 
11 #include "log/log.h"
12 #include "error/error.h"
13 #include "util/csv.h"
14 #include "coord/phys.h"
15 
16 namespace openage {
17 
18 // TODO: remove these global variables!!!
19 // definition of the shaders,
20 // they are "external" in the header.
21 namespace texture_shader {
22 shader::Program *program;
23 GLint texture, tex_coord;
24 }
25 
26 namespace teamcolor_shader {
27 shader::Program *program;
28 GLint texture, tex_coord;
29 GLint player_id_var, alpha_marker_var, player_color_var;
30 }
31 
32 namespace alphamask_shader {
33 shader::Program *program;
34 GLint base_texture, mask_texture, base_coord, mask_coord, show_mask;
35 }
36 
Texture(int width,int height,std::unique_ptr<uint32_t[]> data)37 Texture::Texture(int width, int height, std::unique_ptr<uint32_t[]> data)
38 	:
39 	use_metafile{false} {
40 	ENSURE(glGenBuffers != nullptr, "gl not initialized properly");
41 
42 	this->w = width;
43 	this->h = height;
44 	this->buffer = std::make_unique<gl_texture_buffer>();
45 	this->buffer->transferred = false;
46 	this->buffer->texture_format_in = GL_RGBA8;
47 	this->buffer->texture_format_out = GL_RGBA;
48 	this->buffer->data = std::move(data);
49 	this->subtextures.push_back({0, 0, this->w, this->h, this->w/2, this->h/2});
50 }
51 
Texture(const util::Path & filename,bool use_metafile)52 Texture::Texture(const util::Path &filename, bool use_metafile)
53 	:
54 	use_metafile{use_metafile},
55 	filename{filename} {
56 
57 	// load the texture upon creation
58 	this->load();
59 }
60 
load()61 void Texture::load() {
62 	// TODO: use libpng directly.
63 	SDL_Surface *surface;
64 
65 	// TODO: this will break if there is no native path.
66 	//       but then we need to load the image
67 	//       from the buffer provided by this->filename.open_r().read().
68 
69 	std::string native_path = this->filename.resolve_native_path();
70 	surface = IMG_Load(native_path.c_str());
71 
72 	if (!surface) {
73 		throw Error(
74 			MSG(err) <<
75 			"SDL_Image could not load texture from "
76 			<< this->filename << " (= " << native_path << "): "
77 			<< IMG_GetError()
78 		);
79 	} else {
80 		log::log(MSG(dbg) << "Texture has been loaded from " << native_path);
81 	}
82 
83 	this->buffer = std::make_unique<gl_texture_buffer>();
84 
85 	// glTexImage2D format determination
86 	switch (surface->format->BytesPerPixel) {
87 	case 3: // RGB 24 bit
88 		this->buffer->texture_format_in = GL_RGB8;
89 		this->buffer->texture_format_out
90 		= surface->format->Rmask == 0x000000ff
91 		? GL_RGB
92 		: GL_BGR;
93 		break;
94 	case 4: // RGBA 32 bit
95 		this->buffer->texture_format_in = GL_RGBA8;
96 		this->buffer->texture_format_out
97 		= surface->format->Rmask == 0x000000ff
98 		? GL_RGBA
99 		: GL_BGRA;
100 		break;
101 	default:
102 		throw Error(MSG(err) <<
103 			"Unknown texture bit depth for " << this->filename << ": " <<
104 			surface->format->BytesPerPixel << " bytes per pixel");
105 
106 		break;
107 	}
108 
109 	this->w = surface->w;
110 	this->h = surface->h;
111 
112 	// temporary buffer for pixel data
113 	this->buffer->transferred = false;
114 	this->buffer->data = std::make_unique<uint32_t[]>(this->w * this->h);
115 	std::memcpy(
116 		this->buffer->data.get(),
117 		surface->pixels,
118 		this->w * this->h * surface->format->BytesPerPixel
119 	);
120 	SDL_FreeSurface(surface);
121 
122 	if (use_metafile) {
123 		// get subtexture information from the exported metainfo file
124 		this->subtextures = util::read_csv_file<gamedata::subtexture>(
125 			filename.with_suffix(".docx")
126 		);
127 	}
128 	else {
129 		// we don't have a subtexture description file.
130 		// use the whole image as one texture then.
131 		gamedata::subtexture s{0, 0, this->w, this->h, this->w/2, this->h/2};
132 
133 		this->subtextures.push_back(s);
134 	}
135 }
136 
make_gl_texture(int iformat,int oformat,int w,int h,void * data) const137 GLuint Texture::make_gl_texture(int iformat, int oformat, int w, int h, void *data) const {
138 	// generate 1 texture handle
139 	GLuint textureid;
140 	glGenTextures(1, &textureid);
141 	glBindTexture(GL_TEXTURE_2D, textureid);
142 
143 	// sdl surface -> opengl texture
144 	glTexImage2D(
145 		GL_TEXTURE_2D, 0,
146 		iformat, w, h, 0,
147 		oformat, GL_UNSIGNED_BYTE, data
148 	);
149 
150 	// settings for later drawing
151 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
152 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
153 
154 	return textureid;
155 }
156 
load_in_glthread() const157 void Texture::load_in_glthread() const {
158 	if (not this->buffer->transferred) {
159 		this->buffer->id = this->make_gl_texture(
160 			this->buffer->texture_format_in,
161 			this->buffer->texture_format_out,
162 			this->w,
163 			this->h,
164 			this->buffer->data.get()
165 		);
166 		this->buffer->data = nullptr;
167 		glGenBuffers(1, &this->buffer->vertbuf);
168 		this->buffer->transferred = true;
169 	}
170 }
171 
172 
unload()173 void Texture::unload() {
174 	glDeleteTextures(1, &this->buffer->id);
175 	glDeleteBuffers(1, &this->buffer->vertbuf);
176 }
177 
178 
reload()179 void Texture::reload() {
180 	this->unload();
181 	this->load();
182 }
183 
184 
~Texture()185 Texture::~Texture() {
186 	this->unload();
187 }
188 
189 
fix_hotspots(unsigned x,unsigned y)190 void Texture::fix_hotspots(unsigned x, unsigned y) {
191 	for (auto &subtexture : this->subtextures) {
192 		subtexture.cx = x;
193 		subtexture.cy = y;
194 	}
195 }
196 
197 
draw(const coord::CoordManager & mgr,const coord::camhud pos,unsigned int mode,bool mirrored,int subid,unsigned player) const198 void Texture::draw(const coord::CoordManager &mgr, const coord::camhud pos,
199                    unsigned int mode, bool mirrored,
200                    int subid, unsigned player) const {
201 	this->draw(pos.to_viewport(mgr), mode, mirrored, subid, player, nullptr, -1);
202 }
203 
204 
draw(const coord::CoordManager & mgr,coord::camgame pos,unsigned int mode,bool mirrored,int subid,unsigned player) const205 void Texture::draw(const coord::CoordManager &mgr, coord::camgame pos,
206                    unsigned int mode,  bool mirrored,
207                    int subid, unsigned player) const {
208 	this->draw(pos.to_viewport(mgr), mode, mirrored, subid, player, nullptr, -1);
209 }
210 
211 
draw(const coord::CoordManager & mgr,coord::phys3 pos,unsigned int mode,bool mirrored,int subid,unsigned player) const212 void Texture::draw(const coord::CoordManager &mgr, coord::phys3 pos,
213                    unsigned int mode,  bool mirrored,
214                    int subid, unsigned player) const {
215 	this->draw(pos.to_viewport(mgr), mode, mirrored, subid, player, nullptr, -1);
216 }
217 
218 
draw(const coord::CoordManager & mgr,const Terrain & terrain,coord::tile pos,unsigned int mode,int subid,Texture * alpha_texture,int alpha_subid) const219 void Texture::draw(const coord::CoordManager &mgr, const Terrain &terrain,
220                    coord::tile pos, unsigned int mode, int subid,
221                    Texture *alpha_texture, int alpha_subid) const {
222 
223 	// currently used for drawing terrain tiles.
224 	this->draw(pos.to_viewport(mgr, terrain), mode, false,
225 	           subid, 0, alpha_texture, alpha_subid);
226 }
227 
228 
draw(coord::viewport pos,unsigned int mode,bool mirrored,int subid,unsigned player,Texture * alpha_texture,int alpha_subid) const229 void Texture::draw(coord::viewport pos,
230                    unsigned int mode, bool mirrored,
231                    int subid, unsigned player,
232                    Texture *alpha_texture, int alpha_subid) const {
233 
234 	this->load_in_glthread();
235 
236 	glColor4f(1, 1, 1, 1);
237 
238 	bool use_playercolors = false;
239 	bool use_alphashader = false;
240 	const gamedata::subtexture *mtx;
241 
242 	int *pos_id, *texcoord_id, *masktexcoord_id;
243 
244 	// is this texture drawn with an alpha mask?
245 	if ((mode & ALPHAMASKED) && alpha_subid >= 0 && alpha_texture != nullptr) {
246 		alphamask_shader::program->use();
247 
248 		// bind the alpha mask texture to slot 1
249 		glActiveTexture(GL_TEXTURE1);
250 		glEnable(GL_TEXTURE_2D);
251 		glBindTexture(GL_TEXTURE_2D, alpha_texture->get_texture_id());
252 
253 		// get the alphamask subtexture (the blend mask!)
254 		mtx = alpha_texture->get_subtexture(alpha_subid);
255 		pos_id = &alphamask_shader::program->pos_id;
256 		texcoord_id = &alphamask_shader::base_coord;
257 		masktexcoord_id = &alphamask_shader::mask_coord;
258 		use_alphashader = true;
259 	}
260 	// is this texure drawn with replaced pixels for team coloring?
261 	else if (mode & PLAYERCOLORED) {
262 		teamcolor_shader::program->use();
263 
264 		//set the desired player id in the shader
265 		glUniform1i(teamcolor_shader::player_id_var, player);
266 		pos_id = &teamcolor_shader::program->pos_id;
267 		texcoord_id = &teamcolor_shader::tex_coord;
268 		use_playercolors = true;
269 	}
270 	// mkay, we just draw the plain texture otherwise.
271 	else {
272 		texture_shader::program->use();
273 		pos_id = &texture_shader::program->pos_id;
274 		texcoord_id = &texture_shader::tex_coord;
275 	}
276 
277 	glActiveTexture(GL_TEXTURE0);
278 	glEnable(GL_TEXTURE_2D);
279 	glBindTexture(GL_TEXTURE_2D, this->buffer->id);
280 
281 	const gamedata::subtexture *tx = this->get_subtexture(subid);
282 
283 	int left, right, top, bottom;
284 
285 	// coordinates where the texture will be drawn on screen.
286 	bottom  = pos.y      - (tx->h - tx->cy);
287 	top     = bottom + tx->h;
288 
289 	if (not mirrored) {
290 		left  = pos.x - tx->cx;
291 		right = left + tx->w;
292 	} else {
293 		left  = pos.x + tx->cx;
294 		right = left - tx->w;
295 	}
296 
297 	// convert the texture boundaries to float
298 	// these will be the vertex coordinates.
299 	float leftf, rightf, topf, bottomf;
300 	leftf   = (float) left;
301 	rightf  = (float) right;
302 	topf    = (float) top;
303 	bottomf = (float) bottom;
304 
305 	// subtexture coordinates
306 	// left, right, top and bottom bounds as coordinates
307 	// these pick the requested area out of the big texture.
308 	float txl, txr, txt, txb;
309 	this->get_subtexture_coordinates(tx, &txl, &txr, &txt, &txb);
310 
311 	float mtxl=0, mtxr=0, mtxt=0, mtxb=0;
312 	if (use_alphashader) {
313 		alpha_texture->get_subtexture_coordinates(mtx, &mtxl, &mtxr, &mtxt, &mtxb);
314 	}
315 
316 	// this array will be uploaded to the GPU.
317 	// it contains all dynamic vertex data (position, tex coordinates, mask coordinates)
318 	float vdata[] {
319 		leftf,  topf,
320 		leftf,  bottomf,
321 		rightf, bottomf,
322 		rightf, topf,
323 		txl,    txt,
324 		txl,    txb,
325 		txr,    txb,
326 		txr,    txt,
327 		mtxl,   mtxt,
328 		mtxl,   mtxb,
329 		mtxr,   mtxb,
330 		mtxr,   mtxt
331 	};
332 
333 
334 	// store vertex buffer data, TODO: prepare this sometime earlier.
335 	glBindBuffer(GL_ARRAY_BUFFER, this->buffer->vertbuf);
336 	glBufferData(GL_ARRAY_BUFFER, sizeof(vdata), vdata, GL_STREAM_DRAW);
337 
338 	// enable vertex buffer and bind it to the vertex attribute
339 	glEnableVertexAttribArray(*pos_id);
340 	glEnableVertexAttribArray(*texcoord_id);
341 	if (use_alphashader) {
342 		glEnableVertexAttribArray(*masktexcoord_id);
343 	}
344 
345 	// set data types, offsets in the vdata array
346 	glVertexAttribPointer(*pos_id,      2, GL_FLOAT, GL_FALSE, 0, (void *)(0));
347 	glVertexAttribPointer(*texcoord_id, 2, GL_FLOAT, GL_FALSE, 0, (void *)(sizeof(float) * 8));
348 	if (use_alphashader) {
349 		glVertexAttribPointer(*masktexcoord_id, 2, GL_FLOAT, GL_FALSE, 0, (void *)(sizeof(float) * 8 * 2));
350 	}
351 
352 	// draw the vertex array
353 	glDrawArrays(GL_QUADS, 0, 4);
354 
355 
356 	// unbind the current buffer
357 	glBindBuffer(GL_ARRAY_BUFFER, 0);
358 
359 	glDisableVertexAttribArray(*pos_id);
360 	glDisableVertexAttribArray(*texcoord_id);
361 	if (use_alphashader) {
362 		glDisableVertexAttribArray(*masktexcoord_id);
363 	}
364 
365 	// disable the shaders.
366 	if (use_playercolors) {
367 		teamcolor_shader::program->stopusing();
368 	} else if (use_alphashader) {
369 		alphamask_shader::program->stopusing();
370 		glActiveTexture(GL_TEXTURE1);
371 		glDisable(GL_TEXTURE_2D);
372 	} else {
373 		texture_shader::program->stopusing();
374 	}
375 
376 	glActiveTexture(GL_TEXTURE0);
377 	glDisable(GL_TEXTURE_2D);
378 
379 	////////////////////////////////////////
380 	/*
381 	int size = 2;
382 	float r = 1.0f, g = 1.0f, b = 0.0f;
383 	glPushMatrix();
384 	glTranslatef(leftf, bottomf, 0);
385 	glColor3f(r, g, b);
386 	glBegin(GL_TRIANGLES);
387 	glVertex3f(-size, -size, 0);
388 	glVertex3f(-size, size, 0);
389 	glVertex3f(size, size, 0);
390 	glVertex3f(size, size, 0);
391 	glVertex3f(-size, -size, 0);
392 	glVertex3f(size, -size, 0);
393 	glEnd();
394 	glPopMatrix();
395 	*/
396 	////////////////////////////////////////
397 }
398 
399 
get_subtexture(uint64_t subid) const400 const gamedata::subtexture *Texture::get_subtexture(uint64_t subid) const {
401 	if (subid < this->subtextures.size()) {
402 		return &this->subtextures[subid];
403 	}
404 	else {
405 		throw Error{
406 			ERR << "Unknown subtexture requested for texture "
407 			    << this->filename << ": " << subid
408 		};
409 	}
410 }
411 
412 
get_subtexture_coordinates(uint64_t subid,float * txl,float * txr,float * txt,float * txb) const413 void Texture::get_subtexture_coordinates(uint64_t subid,
414                                          float *txl, float *txr,
415                                          float *txt, float *txb) const {
416 	const gamedata::subtexture *tx = this->get_subtexture(subid);
417 	this->get_subtexture_coordinates(tx, txl, txr, txt, txb);
418 }
419 
420 
get_subtexture_coordinates(const gamedata::subtexture * tx,float * txl,float * txr,float * txt,float * txb) const421 void Texture::get_subtexture_coordinates(const gamedata::subtexture *tx,
422                                          float *txl, float *txr,
423                                          float *txt, float *txb) const {
424 	*txl = ((float)tx->x)           /this->w;
425 	*txr = ((float)(tx->x + tx->w)) /this->w;
426 	*txt = ((float)tx->y)           /this->h;
427 	*txb = ((float)(tx->y + tx->h)) /this->h;
428 }
429 
430 
get_subtexture_count() const431 size_t Texture::get_subtexture_count() const {
432 	return this->subtextures.size();
433 }
434 
435 
get_subtexture_size(uint64_t subid,int * w,int * h) const436 void Texture::get_subtexture_size(uint64_t subid, int *w, int *h) const {
437 	const gamedata::subtexture *subtex = this->get_subtexture(subid);
438 	*w = subtex->w;
439 	*h = subtex->h;
440 }
441 
442 
get_texture_id() const443 GLuint Texture::get_texture_id() const {
444 	this->load_in_glthread();
445 	return this->buffer->id;
446 }
447 
448 } // openage
449