]> Dogcows Code - chaz/yoink/blob - src/Moof/Texture.cc
7d82e2d093416cf5190475a803de81873405d662
[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 = Texture::getPath(name);
219
220 mImage = Image::alloc(path);
221 if (!mImage->isValid())
222 {
223 logWarning << "texture not found: " << path << std::endl;
224 Error(Error::RESOURCE_NOT_FOUND, path).raise();
225 }
226
227 mImage->flip();
228
229 Mf::Script script;
230
231 importLogFunctions(script);
232 bindScriptConstants(script);
233
234 if (script.doString(mImage->getComment()) != Mf::Script::SUCCESS)
235 {
236 std::string str;
237 script[-1].get(str);
238 Mf::logWarning(str);
239 }
240 else
241 {
242 Mf::logInfo << "loading tiles from texture " << path
243 << std::endl;
244
245 Mf::Script::Slot globals = script.getGlobalTable();
246 Mf::Script::Slot top = script[-1];
247
248 globals.pushField("tiles_s");
249 top.get(mTilesS);
250
251 globals.pushField("tiles_t");
252 top.get(mTilesT);
253
254 globals.pushField("min_filter");
255 top.get(mMinFilter);
256
257 globals.pushField("mag_filter");
258 top.get(mMagFilter);
259
260 globals.pushField("wrap_s");
261 top.get(mWrapS);
262
263 globals.pushField("wrap_t");
264 top.get(mWrapT);
265 }
266 }
267
268
269 /**
270 * Upload the image to GL so that it will be accessible by a much more
271 * manageable handle and hopefully reside in video memory.
272 */
273
274 void uploadToGL()
275 {
276 if (mObject)
277 {
278 // already loaded
279 return;
280 }
281
282 glGenTextures(1, &mObject);
283 glBindTexture(GL_TEXTURE_2D, mObject);
284
285 glTexImage2D
286 //gluBuild2DMipmaps
287 (
288 GL_TEXTURE_2D,
289 0,
290 mImage->getMode(),
291 //3,
292 mImage->getWidth(),
293 mImage->getHeight(),
294 0,
295 mImage->getMode(),
296 GL_UNSIGNED_BYTE,
297 mImage->getPixels()
298 );
299
300 setProperties();
301 }
302
303
304 /**
305 * Sets some texture properties such as the filters and external
306 * coordinate behavior.
307 */
308
309 void setProperties()
310 {
311 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mMinFilter);
312 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mMagFilter);
313 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, mWrapS);
314 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, mWrapT);
315 }
316
317 inline void setMinFilter(GLuint filter)
318 {
319 bind();
320 mMinFilter = filter;
321 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mMinFilter);
322 }
323
324 inline void setMagFilter(GLuint filter)
325 {
326 bind();
327 mMagFilter = filter;
328 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mMagFilter);
329 }
330
331 inline void setWrapS(GLuint wrap)
332 {
333 bind();
334 mWrapS = wrap;
335 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, mWrapS);
336 }
337
338 inline void setWrapT(GLuint wrap)
339 {
340 bind();
341 mWrapT = wrap;
342 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, mWrapT);
343 }
344
345
346 inline void bind()
347 {
348 if (mObject == 0)
349 {
350 uploadToGL();
351 }
352 if (mObject != gObject)
353 {
354 glBindTexture(GL_TEXTURE_2D, mObject);
355 gObject = mObject;
356 }
357 }
358
359
360 bool getTileCoords(Texture::TileIndex index, Scalar coords[8]) const
361 {
362 // make sure the index represents a real tile
363 if (index >= mTilesS * mTilesT) return false;
364
365 Scalar w = 1.0 / Scalar(mTilesS);
366 Scalar h = 1.0 / Scalar(mTilesT);
367
368 coords[0] = Scalar(index % mTilesS) * w;
369 coords[1] = (Scalar(mTilesT - 1) - Scalar(index / mTilesS)) * h;
370 coords[2] = coords[0] + w;
371 coords[3] = coords[1];
372 coords[4] = coords[2];
373 coords[5] = coords[1] + h;
374 coords[6] = coords[0];
375 coords[7] = coords[5];
376
377 return true;
378 }
379
380 ImageP mImage;
381
382 GLuint mMinFilter; ///< Minification filter.
383 GLuint mMagFilter; ///< Magnification filter.
384 GLuint mWrapS; ///< Wrapping behavior horizontally.
385 GLuint mWrapT; ///< Wrapping behavior vertically.
386 unsigned mTilesS;
387 unsigned mTilesT;
388
389 GLuint mObject; ///< GL texture handle.
390 static GLuint gObject; ///< Global GL texture handle.
391
392 Dispatch::Handle mNewContextDispatch;
393 };
394
395 GLuint Texture::Impl::gObject = 0;
396
397
398 Texture::Texture(const std::string& name) :
399 Image(Texture::getPath(name)),
400 // pass through
401 mImpl(Texture::Impl::getInstance(name)) {}
402
403
404 /**
405 * Bind the GL texture for mapping, etc.
406 */
407
408 void Texture::bind() const
409 {
410 // pass through
411 mImpl->bind();
412 }
413
414
415 /**
416 * Get the texture object, for the curious.
417 */
418
419 GLuint Texture::getObject() const
420 {
421 // pass through
422 return mImpl->mObject;
423 }
424
425
426 void Texture::resetBind()
427 {
428 glBindTexture(GL_TEXTURE_2D, 0);
429 Impl::gObject = 0;
430 }
431
432
433 void Texture::setMinFilter(GLuint filter)
434 {
435 // pass through
436 mImpl->setMinFilter(filter);
437 }
438
439 void Texture::setMagFilter(GLuint filter)
440 {
441 // pass through
442 mImpl->setMagFilter(filter);
443 }
444
445 void Texture::setWrapS(GLuint wrap)
446 {
447 // pass through
448 mImpl->setWrapS(wrap);
449 }
450
451 void Texture::setWrapT(GLuint wrap)
452 {
453 // pass through
454 mImpl->setWrapT(wrap);
455 }
456
457
458 bool Texture::getTileCoords(TileIndex index, Scalar coords[8]) const
459 {
460 // pass through
461 return mImpl->getTileCoords(index, coords);
462 }
463
464 bool Texture::getTileCoords(TileIndex index, Scalar coords[8],
465 Orientation orientation) const
466 {
467 if (getTileCoords(index, coords))
468 {
469 if (orientation & FLIP)
470 {
471 // this looks kinda weird, but it's just swapping in a way that
472 // doesn't require an intermediate variable
473 coords[1] = coords[5];
474 coords[5] = coords[3];
475 coords[3] = coords[7];
476 coords[7] = coords[5];
477 }
478 if (orientation & REVERSE)
479 {
480 coords[0] = coords[2];
481 coords[2] = coords[6];
482 coords[4] = coords[6];
483 coords[6] = coords[0];
484 }
485
486 return true;
487 }
488
489 return false;
490 }
491
492
493 std::string Texture::getPath(const std::string& name)
494 {
495 if (boost::find_last(name, ".png"))
496 {
497 return Resource::getPath(name);
498 }
499 else
500 {
501 std::string path("textures/");
502 path += name;
503 path += ".png";
504 return Resource::getPath(path);
505 }
506 }
507
508
509 } // namespace Mf
510
This page took 0.048943 seconds and 3 git commands to generate.