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