]> Dogcows Code - chaz/yoink/blob - src/Moof/Texture.cc
bugfix: resource file searching was broken
[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 Video* video = Video::current();
125 ASSERT(video && "should have a video context set");
126
127 // we want to know when the GL context is recreated
128 Dispatch& dispatch = Dispatch::global();
129 mNewContextDispatch = dispatch.addTarget("video.newcontext",
130 boost::bind(&Impl::contextRecreated, this));
131 }
132
133 ~Impl()
134 {
135 unloadFromGL();
136 }
137
138
139 /**
140 * Adapted from some public domain code. This stuff is common enough
141 * that it really should be included in SDL_image... We need this
142 * because images loaded with SDL_image aren't exactly GL-ready right
143 * out of the box. This method makes them ready.
144 */
145
146 /*
147 static SDL_Surface* prepareImageForGL(SDL_Surface* surface)
148 {
149 int w = powerOfTwo(surface->w);
150 int h = powerOfTwo(surface->h);
151
152 // 2. OpenGL textures make more sense within the coordinate system
153 // when they are "upside down," so let's flip it.
154
155 flipSurface(surface);
156
157 // 1. OpenGL images must (generally) have dimensions of a
158 // power-of-two. If this one doesn't, we can at least be more
159 // friendly by expanding the dimensions so that they are, though
160 // there will be some empty space within the range of normal
161 // texture coordinates. It's better if textures are the right size
162 // to begin with.
163
164 SDL_Surface* image = SDL_CreateRGBSurface
165 (
166 SDL_SWSURFACE,
167 w, h,
168 32,
169 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
170 0x000000FF,
171 0x0000FF00,
172 0x00FF0000,
173 0xFF000000
174 #else
175 0xFF000000,
176 0x00FF0000,
177 0x0000FF00,
178 0x000000FF
179 #endif
180 );
181
182 if (!image)
183 {
184 return 0;
185 }
186
187 Uint32 savedFlags = surface->flags&(SDL_SRCALPHA|SDL_RLEACCELOK);
188 Uint8 savedAlpha = surface->format->alpha;
189 if (savedFlags & SDL_SRCALPHA)
190 {
191 SDL_SetAlpha(surface, 0, 0);
192 }
193
194 SDL_Rect srcArea, destArea;
195 srcArea.x = 0; destArea.x = 0;
196 srcArea.y = 0; destArea.y = h - surface->h;
197 srcArea.w = surface->w;
198 srcArea.h = surface->h;
199 SDL_BlitSurface(surface, &srcArea, image, &destArea);
200
201 if (savedFlags & SDL_SRCALPHA)
202 {
203 SDL_SetAlpha(surface, savedFlags, savedAlpha);
204 }
205
206 return image;
207 }
208 */
209
210 /**
211 * Use SDL_image to load images from file. A surface with the image
212 * data is returned.
213 * @return Image data.
214 */
215
216 void init(const std::string& name)
217 {
218 std::string path(name);
219
220 Texture::getPath(path);
221
222 mImage = Image::alloc(path);
223 if (!mImage->isValid())
224 {
225 logWarning << "texture not found: " << path << std::endl;
226 Error(Error::RESOURCE_NOT_FOUND, path).raise();
227 }
228
229 mImage->flip();
230
231 Mf::Script script;
232
233 importLogFunctions(script);
234 bindScriptConstants(script);
235
236 if (script.doString(mImage->getComment()) != Mf::Script::SUCCESS)
237 {
238 std::string str;
239 script[-1].get(str);
240 Mf::logWarning(str);
241 }
242 else
243 {
244 Mf::logInfo << "loading tiles from texture " << path
245 << std::endl;
246
247 Mf::Script::Slot globals = script.getGlobalTable();
248 Mf::Script::Slot top = script[-1];
249
250 globals.pushField("tiles_s");
251 top.get(mTilesS);
252
253 globals.pushField("tiles_t");
254 top.get(mTilesT);
255
256 globals.pushField("min_filter");
257 top.get(mMinFilter);
258
259 globals.pushField("mag_filter");
260 top.get(mMagFilter);
261
262 globals.pushField("wrap_s");
263 top.get(mWrapS);
264
265 globals.pushField("wrap_t");
266 top.get(mWrapT);
267 }
268 }
269
270
271 /**
272 * Upload the image to GL so that it will be accessible by a much more
273 * manageable handle and hopefully reside in video memory.
274 */
275
276 void uploadToGL()
277 {
278 if (mObject)
279 {
280 // already loaded
281 return;
282 }
283
284 glGenTextures(1, &mObject);
285 glBindTexture(GL_TEXTURE_2D, mObject);
286
287 glTexImage2D
288 //gluBuild2DMipmaps
289 (
290 GL_TEXTURE_2D,
291 0,
292 mImage->getMode(),
293 //3,
294 mImage->getWidth(),
295 mImage->getHeight(),
296 0,
297 mImage->getMode(),
298 GL_UNSIGNED_BYTE,
299 mImage->getPixels()
300 );
301
302 setProperties();
303 }
304
305
306 /**
307 * Sets some texture properties such as the filters and external
308 * coordinate behavior.
309 */
310
311 void setProperties()
312 {
313 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mMinFilter);
314 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mMagFilter);
315 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, mWrapS);
316 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, mWrapT);
317 }
318
319 inline void setMinFilter(GLuint filter)
320 {
321 bind();
322 mMinFilter = filter;
323 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mMinFilter);
324 }
325
326 inline void setMagFilter(GLuint filter)
327 {
328 bind();
329 mMagFilter = filter;
330 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mMagFilter);
331 }
332
333 inline void setWrapS(GLuint wrap)
334 {
335 bind();
336 mWrapS = wrap;
337 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, mWrapS);
338 }
339
340 inline void setWrapT(GLuint wrap)
341 {
342 bind();
343 mWrapT = wrap;
344 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, mWrapT);
345 }
346
347
348 inline void bind()
349 {
350 if (mObject == 0)
351 {
352 uploadToGL();
353 }
354 if (mObject != gObject)
355 {
356 glBindTexture(GL_TEXTURE_2D, mObject);
357 gObject = mObject;
358 }
359 }
360
361
362 bool getTileCoords(Texture::TileIndex index, Scalar coords[8]) const
363 {
364 // make sure the index represents a real tile
365 if (index >= mTilesS * mTilesT) return false;
366
367 Scalar w = 1.0 / Scalar(mTilesS);
368 Scalar h = 1.0 / Scalar(mTilesT);
369
370 coords[0] = Scalar(index % mTilesS) * w;
371 coords[1] = (Scalar(mTilesT - 1) - Scalar(index / mTilesS)) * h;
372 coords[2] = coords[0] + w;
373 coords[3] = coords[1];
374 coords[4] = coords[2];
375 coords[5] = coords[1] + h;
376 coords[6] = coords[0];
377 coords[7] = coords[5];
378
379 return true;
380 }
381
382 ImageP mImage;
383
384 GLuint mMinFilter; ///< Minification filter.
385 GLuint mMagFilter; ///< Magnification filter.
386 GLuint mWrapS; ///< Wrapping behavior horizontally.
387 GLuint mWrapT; ///< Wrapping behavior vertically.
388 unsigned mTilesS;
389 unsigned mTilesT;
390
391 GLuint mObject; ///< GL texture handle.
392 static GLuint gObject; ///< Global GL texture handle.
393
394 Dispatch::Handle mNewContextDispatch;
395 };
396
397 GLuint Texture::Impl::gObject = 0;
398
399
400 Texture::Texture(const std::string& name) : // TODO hmm..
401 Image(name),
402 // pass through
403 mImpl(Texture::Impl::getInstance(name)) {}
404
405
406 /**
407 * Bind the GL texture for mapping, etc.
408 */
409
410 void Texture::bind() const
411 {
412 // pass through
413 mImpl->bind();
414 }
415
416
417 /**
418 * Get the texture object, for the curious.
419 */
420
421 GLuint Texture::getObject() const
422 {
423 // pass through
424 return mImpl->mObject;
425 }
426
427
428 void Texture::resetBind()
429 {
430 glBindTexture(GL_TEXTURE_2D, 0);
431 Impl::gObject = 0;
432 }
433
434
435 void Texture::setMinFilter(GLuint filter)
436 {
437 // pass through
438 mImpl->setMinFilter(filter);
439 }
440
441 void Texture::setMagFilter(GLuint filter)
442 {
443 // pass through
444 mImpl->setMagFilter(filter);
445 }
446
447 void Texture::setWrapS(GLuint wrap)
448 {
449 // pass through
450 mImpl->setWrapS(wrap);
451 }
452
453 void Texture::setWrapT(GLuint wrap)
454 {
455 // pass through
456 mImpl->setWrapT(wrap);
457 }
458
459
460 bool Texture::getTileCoords(TileIndex index, Scalar coords[8]) const
461 {
462 // pass through
463 return mImpl->getTileCoords(index, coords);
464 }
465
466 bool Texture::getTileCoords(TileIndex index, Scalar coords[8],
467 Orientation orientation) const
468 {
469 if (getTileCoords(index, coords))
470 {
471 if (orientation & FLIP)
472 {
473 // this looks kinda weird, but it's just swapping in a way that
474 // doesn't require an intermediate variable
475 coords[1] = coords[5];
476 coords[5] = coords[3];
477 coords[3] = coords[7];
478 coords[7] = coords[5];
479 }
480 if (orientation & REVERSE)
481 {
482 coords[0] = coords[2];
483 coords[2] = coords[6];
484 coords[4] = coords[6];
485 coords[6] = coords[0];
486 }
487
488 return true;
489 }
490
491 return false;
492 }
493
494
495 bool Texture::getPath(std::string& name)
496 {
497 return Resource::getPath(name, "textures/", "png");
498 }
499
500
501 } // namespace Mf
502
This page took 0.053625 seconds and 4 git commands to generate.