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