]> Dogcows Code - chaz/yoink/blob - src/Moof/Texture.cc
reformatting
[chaz/yoink] / src / Moof / Texture.cc
1
2 /*] Copyright (c) 2009-2010, Charles McGarvey [**************************
3 **] All rights reserved.
4 *
5 * vi:ts=4 sw=4 tw=75
6 *
7 * Distributable under the terms and conditions of the 2-clause BSD license;
8 * see the file COPYING for a complete text of the license.
9 *
10 **************************************************************************/
11
12 #include <cstdio> // FILE
13 #include <cstring> // strncmp
14
15 #include <boost/algorithm/string.hpp>
16 #include <boost/bind.hpp>
17
18 #include "Dispatch.hh"
19 #include "Error.hh"
20 #include "Manager.hh"
21 #include "Log.hh"
22 #include "OpenGL.hh"
23 #include "Script.hh"
24 #include "Texture.hh"
25 #include "Video.hh"
26
27
28 namespace Mf {
29
30
31 /**
32 * The texture implementation just contains all the information about the
33 * image which is worth having in memory. The image data itself is not
34 * worth keeping in memory if the texture has been loaded to GL, but the
35 * name of the resource is retained so that it can be reloaded if
36 * necessary. The implementation is a manager so that multiple texture
37 * objects can share the same internal objects and avoid having duplicate
38 * textures loaded to GL.
39 */
40
41 class Texture::Impl : public Manager<Impl>
42 {
43
44 /**
45 * Delete the texture (if it is loaded) from GL.
46 */
47
48 void unloadFromGL()
49 {
50 if (mObject)
51 {
52 if (mObject == gObject)
53 {
54 gObject = 0;
55 }
56
57 glDeleteTextures(1, &mObject);
58 mObject = 0;
59 }
60 }
61
62 /**
63 * If the GL context was recreated, we need to reload the texture.
64 * This may involve reading it from disk again, but hopefully the OS
65 * was smart enough to cache it if the client has plenty of RAM.
66 */
67
68 void contextRecreated()
69 {
70 mObject = gObject = 0;
71 uploadToGL();
72 }
73
74 /**
75 * This is a helper method used by some of the texture loading code.
76 * It returns the first power of two which is greater than the input
77 * value.
78 */
79
80 static int powerOfTwo(int input)
81 {
82 int value = 1;
83
84 while (value < input)
85 {
86 value <<= 1;
87 }
88 return value;
89 }
90
91
92 static void bindScriptConstants(Mf::Script& script)
93 {
94 script.push(GL_CLAMP); script.set("CLAMP");
95 script.push(GL_REPEAT); script.set("REPEAT");
96 script.push(GL_LINEAR); script.set("LINEAR");
97 script.push(GL_NEAREST); script.set("NEAREST");
98 script.push(GL_LINEAR_MIPMAP_LINEAR);
99 script.set("LINEAR_MIPMAP_LINEAR");
100 script.push(GL_LINEAR_MIPMAP_NEAREST);
101 script.set("LINEAR_MIPMAP_NEAREST");
102 script.push(GL_NEAREST_MIPMAP_LINEAR);
103 script.set("NEAREST_MIPMAP_LINEAR");
104 script.push(GL_NEAREST_MIPMAP_NEAREST);
105 script.set("NEAREST_MIPMAP_NEAREST");
106 }
107
108 public:
109
110 /**
111 * Construction is initialization.
112 */
113
114 Impl() :
115 mMinFilter(GL_NEAREST),
116 mMagFilter(GL_NEAREST),
117 mWrapS(GL_CLAMP),
118 mWrapT(GL_CLAMP),
119 mTilesS(1),
120 mTilesT(1),
121 mObject(0)
122 {
123 // make sure we have a video context
124 ASSERT(video &&
125 "cannot load textures without a current video context");
126
127 // we want to know when the GL context is recreated
128 mDispatchHandler = core.addHandler("video.newcontext",
129 boost::bind(&Impl::contextRecreated, this));
130 }
131
132 ~Impl()
133 {
134 unloadFromGL();
135 }
136
137
138 /**
139 * Adapted from some public domain code. This stuff is common enough
140 * that it really should be included in SDL_image... We need this
141 * because images loaded with SDL_image aren't exactly GL-ready right
142 * out of the box. This method makes them ready.
143 */
144
145 /*
146 static SDL_Surface* prepareImageForGL(SDL_Surface* surface)
147 {
148 int w = powerOfTwo(surface->w);
149 int h = powerOfTwo(surface->h);
150
151 // 2. OpenGL textures make more sense within the coordinate system
152 // when they are "upside down," so let's flip it.
153
154 flipSurface(surface);
155
156 // 1. OpenGL images must (generally) have dimensions of a
157 // power-of-two. If this one doesn't, we can at least be more
158 // friendly by expanding the dimensions so that they are, though
159 // there will be some empty space within the range of normal
160 // texture coordinates. It's better if textures are the right size
161 // to begin with.
162
163 SDL_Surface* image = SDL_CreateRGBSurface
164 (
165 SDL_SWSURFACE,
166 w, h,
167 32,
168 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
169 0x000000FF,
170 0x0000FF00,
171 0x00FF0000,
172 0xFF000000
173 #else
174 0xFF000000,
175 0x00FF0000,
176 0x0000FF00,
177 0x000000FF
178 #endif
179 );
180
181 if (!image)
182 {
183 return 0;
184 }
185
186 Uint32 savedFlags = surface->flags&(SDL_SRCALPHA|SDL_RLEACCELOK);
187 Uint8 savedAlpha = surface->format->alpha;
188 if (savedFlags & SDL_SRCALPHA)
189 {
190 SDL_SetAlpha(surface, 0, 0);
191 }
192
193 SDL_Rect srcArea, destArea;
194 srcArea.x = 0; destArea.x = 0;
195 srcArea.y = 0; destArea.y = h - surface->h;
196 srcArea.w = surface->w;
197 srcArea.h = surface->h;
198 SDL_BlitSurface(surface, &srcArea, image, &destArea);
199
200 if (savedFlags & SDL_SRCALPHA)
201 {
202 SDL_SetAlpha(surface, savedFlags, savedAlpha);
203 }
204
205 return image;
206 }
207 */
208
209 /**
210 * Use SDL_image to load images from file. A surface with the image
211 * data is returned.
212 * @return Image data.
213 */
214
215 void init(const std::string& name)
216 {
217 std::string path = Texture::getPath(name);
218
219 mImage = Image::alloc(path);
220 if (!mImage->isValid())
221 {
222 logWarning << "texture not found: " << path << std::endl;
223 Error(Error::RESOURCE_NOT_FOUND, path).raise();
224 }
225
226 mImage->flip();
227
228 Mf::Script script;
229
230 importLogFunctions(script);
231 bindScriptConstants(script);
232
233 if (script.doString(mImage->getComment()) != Mf::Script::SUCCESS)
234 {
235 std::string str;
236 script[-1].get(str);
237 Mf::logWarning(str);
238 }
239 else
240 {
241 Mf::logInfo << "loading tiles from texture " << path
242 << std::endl;
243
244 Mf::Script::Slot globals = script.getGlobalTable();
245 Mf::Script::Slot top = script[-1];
246
247 globals.pushField("tiles_s");
248 top.get(mTilesS);
249
250 globals.pushField("tiles_t");
251 top.get(mTilesT);
252
253 globals.pushField("min_filter");
254 top.get(mMinFilter);
255
256 globals.pushField("mag_filter");
257 top.get(mMagFilter);
258
259 globals.pushField("wrap_s");
260 top.get(mWrapS);
261
262 globals.pushField("wrap_t");
263 top.get(mWrapT);
264 }
265 }
266
267
268 /**
269 * Upload the image to GL so that it will be accessible by a much more
270 * manageable handle and hopefully reside in video memory.
271 */
272
273 void uploadToGL()
274 {
275 if (mObject)
276 {
277 // already loaded
278 return;
279 }
280
281 glGenTextures(1, &mObject);
282 glBindTexture(GL_TEXTURE_2D, mObject);
283
284 glTexImage2D
285 //gluBuild2DMipmaps
286 (
287 GL_TEXTURE_2D,
288 0,
289 mImage->getMode(),
290 //3,
291 mImage->getWidth(),
292 mImage->getHeight(),
293 0,
294 mImage->getMode(),
295 GL_UNSIGNED_BYTE,
296 mImage->getPixels()
297 );
298
299 setProperties();
300 }
301
302
303 /**
304 * Sets some texture properties such as the filters and external
305 * coordinate behavior.
306 */
307
308 void setProperties()
309 {
310 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mMinFilter);
311 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mMagFilter);
312 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, mWrapS);
313 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, mWrapT);
314 }
315
316 inline void setMinFilter(GLuint filter)
317 {
318 bind();
319 mMinFilter = filter;
320 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mMinFilter);
321 }
322
323 inline void setMagFilter(GLuint filter)
324 {
325 bind();
326 mMagFilter = filter;
327 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mMagFilter);
328 }
329
330 inline void setWrapS(GLuint wrap)
331 {
332 bind();
333 mWrapS = wrap;
334 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, mWrapS);
335 }
336
337 inline void setWrapT(GLuint wrap)
338 {
339 bind();
340 mWrapT = wrap;
341 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, mWrapT);
342 }
343
344
345 inline void bind()
346 {
347 if (mObject == 0)
348 {
349 uploadToGL();
350 }
351 if (mObject != gObject)
352 {
353 glBindTexture(GL_TEXTURE_2D, mObject);
354 gObject = mObject;
355 }
356 }
357
358
359 bool getTileCoords(Texture::TileIndex index, Scalar coords[8]) const
360 {
361 // make sure the index represents a real tile
362 if (index >= mTilesS * mTilesT) return false;
363
364 Scalar w = 1.0 / Scalar(mTilesS);
365 Scalar h = 1.0 / Scalar(mTilesT);
366
367 coords[0] = Scalar(index % mTilesS) * w;
368 coords[1] = (Scalar(mTilesT - 1) - Scalar(index / mTilesS)) * h;
369 coords[2] = coords[0] + w;
370 coords[3] = coords[1];
371 coords[4] = coords[2];
372 coords[5] = coords[1] + h;
373 coords[6] = coords[0];
374 coords[7] = coords[5];
375
376 return true;
377 }
378
379 ImageP mImage;
380
381 GLuint mMinFilter; ///< Minification filter.
382 GLuint mMagFilter; ///< Magnification filter.
383 GLuint mWrapS; ///< Wrapping behavior horizontally.
384 GLuint mWrapT; ///< Wrapping behavior vertically.
385 unsigned mTilesS;
386 unsigned mTilesT;
387
388 GLuint mObject; ///< GL texture handle.
389 static GLuint gObject; ///< Global GL texture handle.
390
391 Dispatch::Handler mDispatchHandler;
392 };
393
394 GLuint Texture::Impl::gObject = 0;
395
396
397 Texture::Texture(const std::string& name) :
398 Image(Texture::getPath(name)),
399 // pass through
400 mImpl(Texture::Impl::getInstance(name)) {}
401
402
403 /**
404 * Bind the GL texture for mapping, etc.
405 */
406
407 void Texture::bind() const
408 {
409 // pass through
410 mImpl->bind();
411 }
412
413
414 /**
415 * Get the texture object, for the curious.
416 */
417
418 GLuint Texture::getObject() const
419 {
420 // pass through
421 return mImpl->mObject;
422 }
423
424
425 void Texture::resetBind()
426 {
427 glBindTexture(GL_TEXTURE_2D, 0);
428 Impl::gObject = 0;
429 }
430
431
432 void Texture::setMinFilter(GLuint filter)
433 {
434 // pass through
435 mImpl->setMinFilter(filter);
436 }
437
438 void Texture::setMagFilter(GLuint filter)
439 {
440 // pass through
441 mImpl->setMagFilter(filter);
442 }
443
444 void Texture::setWrapS(GLuint wrap)
445 {
446 // pass through
447 mImpl->setWrapS(wrap);
448 }
449
450 void Texture::setWrapT(GLuint wrap)
451 {
452 // pass through
453 mImpl->setWrapT(wrap);
454 }
455
456
457 bool Texture::getTileCoords(TileIndex index, Scalar coords[8]) const
458 {
459 // pass through
460 return mImpl->getTileCoords(index, coords);
461 }
462
463 bool Texture::getTileCoords(TileIndex index, Scalar coords[8],
464 Orientation orientation) const
465 {
466 if (getTileCoords(index, coords))
467 {
468 if (orientation & FLIP)
469 {
470 // this looks kinda weird, but it's just swapping in a way that
471 // doesn't require an intermediate variable
472 coords[1] = coords[5];
473 coords[5] = coords[3];
474 coords[3] = coords[7];
475 coords[7] = coords[5];
476 }
477 if (orientation & REVERSE)
478 {
479 coords[0] = coords[2];
480 coords[2] = coords[6];
481 coords[4] = coords[6];
482 coords[6] = coords[0];
483 }
484
485 return true;
486 }
487
488 return false;
489 }
490
491
492 std::string Texture::getPath(const std::string& name)
493 {
494 if (boost::find_last(name, ".png"))
495 {
496 return Resource::getPath(name);
497 }
498 else
499 {
500 std::string path("textures/");
501 path += name;
502 path += ".png";
503 return Resource::getPath(path);
504 }
505 }
506
507
508 } // namespace Mf
509
This page took 0.050496 seconds and 4 git commands to generate.