61cabd2c89296eb06e24884cd289448a7fb39ce1
[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 <cstring> // memcpy
30
31 #include <boost/bind.hpp>
32
33 #include <SDL/SDL.h>
34 #include <SDL/SDL_image.h>
35
36 #include "Dispatch.hh"
37 #include "Engine.hh"
38 #include "Exception.hh"
39 #include "Library.hh"
40 #include "Log.hh"
41 #include "OpenGL.hh"
42 #include "Texture.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 * mippleton so that multiple texture objects can share the same internal
54 * objects and avoid having duplicate textures loaded to GL.
55 */
56
57 class Texture::Impl : public Library<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 flipSurface(SDL_Surface* image)
108 {
109 unsigned char* pixels = (Uint8*)(image->pixels);
110
111 unsigned pitch = image->pitch;
112 unsigned char line[pitch];
113
114 int yBegin = 0;
115 int yEnd = image->h - 1;
116
117 if (SDL_MUSTLOCK(image)) SDL_LockSurface(image);
118 while (yBegin < yEnd)
119 {
120 memcpy(line, pixels + pitch * yBegin, pitch);
121 memcpy(pixels + pitch * yBegin, pixels + pitch * yEnd, pitch);
122 memcpy(pixels + pitch * yEnd, line, pitch);
123 yBegin++;
124 yEnd--;
125 }
126 if (SDL_MUSTLOCK(image)) SDL_UnlockSurface(image);
127 }
128
129 public:
130
131 /**
132 * Construction is initialization.
133 */
134
135 explicit Impl(const std::string& name) :
136 Library<Impl>(name),
137 mContext(0),
138 mWidth(0),
139 mHeight(0),
140 mMode(0),
141 mMinFilter(GL_NEAREST),
142 mMagFilter(GL_NEAREST),
143 mWrapS(GL_CLAMP),
144 mWrapT(GL_CLAMP),
145 mObject(0)
146 {
147 // make sure the engine is initialized
148 Engine& engine = Engine::getInstance();
149 VideoP video = engine.getVideo();
150 ASSERT(video && "cannot load textures without a current video context");
151
152 // we want to know when the GL context is recreated
153 mDispatchHandler = engine.addHandler("video.newcontext",
154 boost::bind(&Impl::contextRecreated, this));
155
156 loadFromFile();
157 }
158
159 ~Impl()
160 {
161 if (mContext)
162 {
163 SDL_FreeSurface(mContext);
164 }
165
166 unloadFromGL();
167 }
168
169
170 /**
171 * Adapted from some public domain code. This stuff is common enough that
172 * it really should be included in SDL_image... We need this because images
173 * loaded with SDL_image aren't exactly GL-ready right out of the box. This
174 * method makes them ready.
175 */
176
177 static SDL_Surface* prepareImageForGL(SDL_Surface* surface)
178 {
179 int w = powerOfTwo(surface->w);
180 int h = powerOfTwo(surface->h);
181
182 // 2. OpenGL textures make more sense within the coordinate system when
183 // they are "upside down," so let's flip it.
184
185 flipSurface(surface);
186
187 // 1. OpenGL images must (generally) have dimensions of a power-of-two.
188 // If this one doesn't, we can at least be more friendly by expanding
189 // the dimensions so that they are, though there will be some empty
190 // space within the range of normal texture coordinates. It's better if
191 // textures are the right size to begin with.
192
193 SDL_Surface* image = SDL_CreateRGBSurface
194 (
195 SDL_SWSURFACE,
196 w, h,
197 32,
198 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
199 0x000000FF,
200 0x0000FF00,
201 0x00FF0000,
202 0xFF000000
203 #else
204 0xFF000000,
205 0x00FF0000,
206 0x0000FF00,
207 0x000000FF
208 #endif
209 );
210
211 if (!image)
212 {
213 return 0;
214 }
215
216 Uint32 savedFlags = surface->flags&(SDL_SRCALPHA|SDL_RLEACCELOK);
217 Uint8 savedAlpha = surface->format->alpha;
218 if (savedFlags & SDL_SRCALPHA)
219 {
220 SDL_SetAlpha(surface, 0, 0);
221 }
222
223 SDL_Rect srcArea, destArea;
224 srcArea.x = 0; destArea.x = 0;
225 srcArea.y = 0; destArea.y = h - surface->h;
226 srcArea.w = surface->w;
227 srcArea.h = surface->h;
228 SDL_BlitSurface(surface, &srcArea, image, &destArea);
229
230 if (savedFlags & SDL_SRCALPHA)
231 {
232 SDL_SetAlpha(surface, savedFlags, savedAlpha);
233 }
234
235 return image;
236 }
237
238 /**
239 * Use SDL_image to load images from file. A surface with the image data is
240 * returned.
241 * @return Image data.
242 */
243
244 void loadFromFile()
245 {
246 SDL_Surface* surface;
247
248 surface = IMG_Load(Texture::getPath(getName()).c_str());
249
250 if (!surface)
251 {
252 logWarning << "texture not found: " << getName() << std::endl;
253 throw Exception(ErrorCode::FILE_NOT_FOUND, getName());
254 }
255
256 SDL_Surface* temp = prepareImageForGL(surface);
257 SDL_FreeSurface(surface);
258
259 if (!temp)
260 {
261 throw Exception(ErrorCode::UNKNOWN_IMAGE_FORMAT, getName());
262 }
263
264 if (temp->format->BytesPerPixel == 3)
265 {
266 mMode = GL_RGB;
267 }
268 else if (temp->format->BytesPerPixel == 4)
269 {
270 mMode = GL_RGBA;
271 }
272 else
273 {
274 SDL_FreeSurface(temp);
275 throw Exception(ErrorCode::UNKNOWN_IMAGE_FORMAT, getName());
276 }
277
278 mWidth = temp->w;
279 mHeight = temp->h;
280
281 mContext = temp;
282 }
283
284
285 /**
286 * Upload the image to GL so that it will be accessible by a much more
287 * manageable handle and hopefully reside in video memory.
288 */
289
290 void uploadToGL()
291 {
292 if (mObject)
293 {
294 // already loaded
295 return;
296 }
297
298 if (!mContext) loadFromFile();
299
300 glGenTextures(1, &mObject);
301 glBindTexture(GL_TEXTURE_2D, mObject);
302
303 glTexImage2D
304 //gluBuild2DMipmaps
305 (
306 GL_TEXTURE_2D,
307 0,
308 mMode,
309 //3,
310 mContext->w,
311 mContext->h,
312 0,
313 mMode,
314 GL_UNSIGNED_BYTE,
315 mContext->pixels
316 );
317
318 setProperties();
319
320 SDL_FreeSurface(mContext);
321 mContext = 0;
322 }
323
324
325 /**
326 * Sets some texture properties such as the filters and external coordinate
327 * behavior.
328 */
329
330 void setProperties()
331 {
332 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mMinFilter);
333 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mMagFilter);
334 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, mWrapS);
335 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, mWrapT);
336 }
337
338 inline void setMinFilter(GLuint filter)
339 {
340 bind();
341 mMinFilter = filter;
342 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mMinFilter);
343 }
344
345 inline void setMagFilter(GLuint filter)
346 {
347 bind();
348 mMagFilter = filter;
349 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mMagFilter);
350 }
351
352 inline void setWrapS(GLuint wrap)
353 {
354 bind();
355 mWrapS = wrap;
356 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, mWrapS);
357 }
358
359 inline void setWrapT(GLuint wrap)
360 {
361 bind();
362 mWrapT = wrap;
363 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, mWrapT);
364 }
365
366
367 inline void bind()
368 {
369 if (mObject == 0)
370 {
371 uploadToGL();
372 }
373 if (mObject != gObject)
374 {
375 glBindTexture(GL_TEXTURE_2D, mObject);
376 gObject = mObject;
377 }
378 }
379
380
381 SDL_Surface* mContext;
382 unsigned mWidth; ///< Horizontal dimension of the image.
383 unsigned mHeight; ///< Vertical dimension.
384
385 GLuint mMode; ///< GL_RGB or GL_RGBA.
386 GLuint mMinFilter; ///< Minifcation filter.
387 GLuint mMagFilter; ///< Magnification filter.
388 GLuint mWrapS; ///< Wrapping behavior horizontally.
389 GLuint mWrapT; ///< Wrapping behavior vertically.
390
391 GLuint mObject; ///< GL texture handle.
392 static GLuint gObject; ///< Global GL texture handle.
393
394 Dispatch::Handler mDispatchHandler;
395 };
396
397 GLuint Texture::Impl::gObject = 0;
398
399
400 Texture::Texture(const std::string& name) :
401 // pass through
402 mImpl(Texture::Impl::getInstance(name)) {}
403
404
405 /**
406 * Bind the GL texture for mapping, etc.
407 */
408
409 void Texture::bind() const
410 {
411 // pass through
412 mImpl->bind();
413 }
414
415
416 /**
417 * Get the texture object, for the curious.
418 */
419
420 GLuint Texture::getObject() const
421 {
422 // pass through
423 return mImpl->mObject;
424 }
425
426
427 void Texture::resetBind()
428 {
429 glBindTexture(GL_TEXTURE_2D, 0);
430 Impl::gObject = 0;
431 }
432
433
434 unsigned Texture::getWidth() const
435 {
436 // pass through
437 return mImpl->mWidth;
438 }
439
440 unsigned Texture::getHeight() const
441 {
442 // pass through
443 return mImpl->mHeight;
444 }
445
446
447 void Texture::setMinFilter(GLuint filter)
448 {
449 // pass through
450 mImpl->setMinFilter(filter);
451 }
452
453 void Texture::setMagFilter(GLuint filter)
454 {
455 // pass through
456 mImpl->setMagFilter(filter);
457 }
458
459 void Texture::setWrapS(GLuint wrap)
460 {
461 // pass through
462 mImpl->setWrapS(wrap);
463 }
464
465 void Texture::setWrapT(GLuint wrap)
466 {
467 // pass through
468 mImpl->setWrapT(wrap);
469 }
470
471
472 std::string Texture::getPath(const std::string& name)
473 {
474 std::string path = Resource::getPath("textures/" + name + ".png");
475 return path;
476 }
477
478
479 } // namespace Mf
480
481 /** vim: set ts=4 sw=4 tw=80: *************************************************/
482
This page took 0.047722 seconds and 3 git commands to generate.