]> Dogcows Code - chaz/yoink/blob - src/Moof/Texture.cc
scene drawing correctly implemented; new classes
[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 "Dispatcher.hh"
37 #include "Mippleton.hh"
38 #include "OpenGL.hh"
39 #include "Texture.hh"
40
41
42 namespace Mf {
43
44
45 /**
46 * The texture implementation just contains all the information about the image
47 * which is worth having in memory. The image data itself is not worth keeping
48 * in memory if the texture has been loaded to GL, but the name of the resource
49 * is retained so that it can be reloaded if necessary. The implementation is a
50 * mippleton so that multiple texture objects can share the same internal
51 * objects and avoid having duplicate textures loaded to GL.
52 */
53
54 class Texture::TextureImpl : public Mippleton<TextureImpl>
55 {
56
57 /**
58 * Delete the texture (if it is loaded) from GL.
59 */
60
61 void unloadFromGL()
62 {
63 if (object_)
64 {
65 if (object_ == globalObject_)
66 {
67 globalObject_ = 0;
68 }
69
70 glDeleteTextures(1, &object_);
71 object_ = 0;
72 }
73 }
74
75 /**
76 * If the GL context was recreated, we need to reload the texture. This may
77 * involve reading it from disk again, but hopefully the OS was smart enough
78 * to cache it if the client has plenty of RAM.
79 */
80
81 void contextRecreated(const Notification& note)
82 {
83 object_ = globalObject_ = 0;
84 uploadToGL();
85 }
86
87 /**
88 * This is a helper method used by some of the texture loading code. It
89 * returns the first power of two which is greater than the input value.
90 */
91
92 static int powerOfTwo(int input)
93 {
94 int value = 1;
95
96 while (value < input)
97 {
98 value <<= 1;
99 }
100 return value;
101 }
102
103
104 static void flipSurface(SDL_Surface* image)
105 {
106 unsigned char* pixels = (Uint8*)(image->pixels);
107
108 unsigned pitch = image->pitch;
109 unsigned char line[pitch];
110
111 int yBegin = 0;
112 int yEnd = image->h - 1;
113
114 if (SDL_MUSTLOCK(image)) SDL_LockSurface(image);
115 while (yBegin < yEnd)
116 {
117 memcpy(line, pixels + pitch * yBegin, pitch);
118 memcpy(pixels + pitch * yBegin, pixels + pitch * yEnd, pitch);
119 memcpy(pixels + pitch * yEnd, line, pitch);
120 yBegin++;
121 yEnd--;
122 }
123 if (SDL_MUSTLOCK(image)) SDL_UnlockSurface(image);
124 }
125
126 public:
127
128 /**
129 * Construction is initialization.
130 */
131
132 explicit TextureImpl(const std::string& name) :
133 Mippleton<TextureImpl>(name),
134 surface_(0),
135 width_(0),
136 height_(0),
137 mode_(0),
138 minFilter_(GL_NEAREST),
139 magFilter_(GL_NEAREST),
140 wrapS_(GL_CLAMP),
141 wrapT_(GL_CLAMP),
142 object_(0)
143 {
144 loadFromFile();
145
146 // we want to know when the GL context is recreated
147 Dispatcher::instance().addHandler("video.context_recreated",
148 boost::bind(&TextureImpl::contextRecreated, this, _1), this);
149 }
150
151 ~TextureImpl()
152 {
153 if (surface_)
154 {
155 SDL_FreeSurface(surface_);
156 }
157
158 unloadFromGL();
159
160 Dispatcher::instance().removeHandler(this);
161 }
162
163
164 /**
165 * Adapted from some public domain code. This stuff is common enough that
166 * it really should be included in SDL_image... We need this because images
167 * loaded with SDL_image aren't exactly GL-ready right out of the box. This
168 * method makes them ready.
169 */
170
171 static SDL_Surface* prepareImageForGL(SDL_Surface* surface)
172 {
173 int w = powerOfTwo(surface->w);
174 int h = powerOfTwo(surface->h);
175
176 // 2. OpenGL textures make more sense within the coordinate system when
177 // they are "upside down," so let's flip it.
178
179 flipSurface(surface);
180
181 // 1. OpenGL images must (generally) have dimensions of a power-of-two.
182 // If this one doesn't, we can at least be more friendly by expanding
183 // the dimensions so that they are, though there will be some empty
184 // space within the range of normal texture coordinates. It's better if
185 // textures are the right size to begin with.
186
187 SDL_Surface* image = SDL_CreateRGBSurface
188 (
189 SDL_SWSURFACE,
190 w, h,
191 32,
192 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
193 0x000000FF,
194 0x0000FF00,
195 0x00FF0000,
196 0xFF000000
197 #else
198 0xFF000000,
199 0x00FF0000,
200 0x0000FF00,
201 0x000000FF
202 #endif
203 );
204
205 if (!image)
206 {
207 return 0;
208 }
209
210 Uint32 savedFlags = surface->flags&(SDL_SRCALPHA|SDL_RLEACCELOK);
211 Uint8 savedAlpha = surface->format->alpha;
212 if (savedFlags & SDL_SRCALPHA)
213 {
214 SDL_SetAlpha(surface, 0, 0);
215 }
216
217 SDL_Rect srcArea, destArea;
218 srcArea.x = 0; destArea.x = 0;
219 srcArea.y = 0; destArea.y = h - surface->h;
220 srcArea.w = surface->w;
221 srcArea.h = surface->h;
222 SDL_BlitSurface(surface, &srcArea, image, &destArea);
223
224 if (savedFlags & SDL_SRCALPHA)
225 {
226 SDL_SetAlpha(surface, savedFlags, savedAlpha);
227 }
228
229 return image;
230 }
231
232 /**
233 * Use SDL_image to load images from file. A surface with the image data is
234 * returned.
235 * @return Image data.
236 */
237
238 void loadFromFile()
239 {
240 SDL_Surface* surface;
241
242 surface = IMG_Load(Texture::getPathToResource(getName()).c_str());
243
244 if (!surface)
245 {
246 throw Texture::Exception("loading from file failed");
247 }
248
249 SDL_Surface* temp = prepareImageForGL(surface);
250 SDL_FreeSurface(surface);
251
252 if (!temp)
253 {
254 throw Texture::Exception("uploading to opengl failed");
255 }
256
257 if (temp->format->BytesPerPixel == 3)
258 {
259 mode_ = GL_RGB;
260 }
261 else if (temp->format->BytesPerPixel == 4)
262 {
263 mode_ = GL_RGBA;
264 }
265 else
266 {
267 SDL_FreeSurface(temp);
268 throw Texture::Exception("incompatible color mode");
269 }
270
271 width_ = temp->w;
272 height_ = temp->h;
273
274 surface_ = temp;
275 }
276
277
278 /**
279 * Upload the image to GL so that it will be accessible by a much more
280 * manageable handle and hopefully reside in video memory.
281 */
282
283 void uploadToGL()
284 {
285 if (object_)
286 {
287 // already loaded
288 return;
289 }
290
291 if (!surface_) loadFromFile();
292
293 glGenTextures(1, &object_);
294 glBindTexture(GL_TEXTURE_2D, object_);
295
296 glTexImage2D
297 (
298 GL_TEXTURE_2D,
299 0,
300 mode_,
301 surface_->w,
302 surface_->h,
303 0,
304 mode_,
305 GL_UNSIGNED_BYTE,
306 surface_->pixels
307 );
308
309 setProperties();
310
311 SDL_FreeSurface(surface_);
312 surface_ = 0;
313 }
314
315
316 /**
317 * Sets some texture properties such as the filters and external coordinate
318 * behavior.
319 */
320
321 void setProperties()
322 {
323 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter_);
324 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter_);
325 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapS_);
326 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapT_);
327 }
328
329 inline void setMinFilter(GLuint filter)
330 {
331 bind();
332 minFilter_ = filter;
333 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter_);
334 }
335
336 inline void setMagFilter(GLuint filter)
337 {
338 bind();
339 magFilter_ = filter;
340 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter_);
341 }
342
343 inline void setWrapS(GLuint wrap)
344 {
345 bind();
346 wrapS_ = wrap;
347 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapS_);
348 }
349
350 inline void setWrapT(GLuint wrap)
351 {
352 bind();
353 wrapT_ = wrap;
354 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapT_);
355 }
356
357
358 inline void bind()
359 {
360 if (object_ == 0)
361 {
362 uploadToGL();
363 }
364 if (object_ != globalObject_)
365 {
366 glBindTexture(GL_TEXTURE_2D, object_);
367 globalObject_ = object_;
368 }
369 }
370
371
372 SDL_Surface* surface_;
373 unsigned width_; ///< Horizontal dimension of the image.
374 unsigned height_; ///< Vertical dimension.
375
376 GLuint mode_; ///< Depth of the image, GL_RGB or GL_RGBA.
377 GLuint minFilter_; ///< Minifcation filter.
378 GLuint magFilter_; ///< Magnification filter.
379 GLuint wrapS_; ///< Wrapping behavior horizontally.
380 GLuint wrapT_; ///< Wrapping behavior vertically.
381
382 GLuint object_; ///< GL texture handle.
383 static GLuint globalObject_; ///< Global GL texture handle.
384 };
385
386 GLuint Texture::TextureImpl::globalObject_ = 0;
387
388
389 Texture::Texture(const std::string& name) :
390 // pass through
391 impl_(Texture::TextureImpl::retain(name), &Texture::TextureImpl::release)
392 {}
393
394
395 /**
396 * Bind the GL texture for mapping, etc.
397 */
398
399 void Texture::bind() const
400 {
401 // pass through
402 impl_->bind();
403 }
404
405
406 /**
407 * Get the texture object, for the curious.
408 */
409
410 GLuint Texture::getObject() const
411 {
412 // pass through
413 return impl_->object_;
414 }
415
416
417 unsigned Texture::getWidth() const
418 {
419 // pass through
420 return impl_->width_;
421 }
422
423 unsigned Texture::getHeight() const
424 {
425 // pass through
426 return impl_->height_;
427 }
428
429
430 void Texture::setMinFilter(GLuint filter)
431 {
432 // pass through
433 impl_->setMinFilter(filter);
434 }
435
436 void Texture::setMagFilter(GLuint filter)
437 {
438 // pass through
439 impl_->setMagFilter(filter);
440 }
441
442 void Texture::setWrapS(GLuint wrap)
443 {
444 // pass through
445 impl_->setWrapS(wrap);
446 }
447
448 void Texture::setWrapT(GLuint wrap)
449 {
450 // pass through
451 impl_->setWrapT(wrap);
452 }
453
454
455 std::string Texture::getPathToResource(const std::string& name)
456 {
457 // TODO named resources must be png for now
458 return Resource::getPathToResource("textures/" + name + ".png");
459 }
460
461
462 } // namespace Mf
463
464 /** vim: set ts=4 sw=4 tw=80: *************************************************/
465
This page took 0.048979 seconds and 4 git commands to generate.