1 | // SuperTux |
2 | // Copyright (C) 2006 Matthias Braun <matze@braunis.de> |
3 | // |
4 | // This program is free software: you can redistribute it and/or modify |
5 | // it under the terms of the GNU General Public License as published by |
6 | // the Free Software Foundation, either version 3 of the License, or |
7 | // (at your option) any later version. |
8 | // |
9 | // This program is distributed in the hope that it will be useful, |
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | // GNU General Public License for more details. |
13 | // |
14 | // You should have received a copy of the GNU General Public License |
15 | // along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | |
17 | #include "video/texture_manager.hpp" |
18 | |
19 | #include <SDL_image.h> |
20 | #include <assert.h> |
21 | #include <sstream> |
22 | |
23 | #include "math/rect.hpp" |
24 | #include "physfs/physfs_sdl.hpp" |
25 | #include "util/file_system.hpp" |
26 | #include "util/log.hpp" |
27 | #include "util/reader_document.hpp" |
28 | #include "util/reader_mapping.hpp" |
29 | #include "video/color.hpp" |
30 | #include "video/gl.hpp" |
31 | #include "video/sampler.hpp" |
32 | #include "video/sdl_surface.hpp" |
33 | #include "video/texture.hpp" |
34 | #include "video/video_system.hpp" |
35 | |
36 | namespace { |
37 | |
38 | GLenum string2wrap(const std::string& text) |
39 | { |
40 | if (text == "clamp-to-edge" ) |
41 | { |
42 | return GL_CLAMP_TO_EDGE; |
43 | } |
44 | else if (text == "repeat" ) |
45 | { |
46 | return GL_REPEAT; |
47 | } |
48 | else if (text == "mirrored-repeat" ) |
49 | { |
50 | return GL_MIRRORED_REPEAT; |
51 | } |
52 | else |
53 | { |
54 | log_warning << "unknown texture wrap: " << text << std::endl; |
55 | return GL_CLAMP_TO_EDGE; |
56 | } |
57 | } |
58 | |
59 | GLenum string2filter(const std::string& text) |
60 | { |
61 | if (text == "nearest" ) |
62 | { |
63 | return GL_NEAREST; |
64 | } |
65 | else if (text == "linear" ) |
66 | { |
67 | return GL_LINEAR; |
68 | } |
69 | else |
70 | { |
71 | log_warning << "unknown texture filter: " << text << std::endl; |
72 | return GL_LINEAR; |
73 | } |
74 | } |
75 | |
76 | } // namespace |
77 | |
78 | TextureManager::TextureManager() : |
79 | m_image_textures(), |
80 | m_surfaces() |
81 | { |
82 | } |
83 | |
84 | TextureManager::~TextureManager() |
85 | { |
86 | for (const auto& texture : m_image_textures) |
87 | { |
88 | if (!texture.second.expired()) |
89 | { |
90 | log_warning << "Texture '" << std::get<0>(texture.first) << "' not freed" << std::endl; |
91 | } |
92 | } |
93 | m_image_textures.clear(); |
94 | m_surfaces.clear(); |
95 | } |
96 | |
97 | TexturePtr |
98 | TextureManager::get(const ReaderMapping& mapping, const boost::optional<Rect>& region) |
99 | { |
100 | std::string filename; |
101 | if (!mapping.get("file" , filename)) |
102 | { |
103 | log_warning << "'file' tag missing" << std::endl; |
104 | } |
105 | else |
106 | { |
107 | filename = FileSystem::join(mapping.get_doc().get_directory(), filename); |
108 | } |
109 | |
110 | boost::optional<Rect> rect; |
111 | std::vector<int> rect_v; |
112 | if (mapping.get("rect" , rect_v)) |
113 | { |
114 | if (rect_v.size() == 4) |
115 | { |
116 | rect = Rect(rect_v[0], rect_v[1], rect_v[2], rect_v[3]); |
117 | } |
118 | else |
119 | { |
120 | log_warning << "'rect' requires four elements" << std::endl; |
121 | } |
122 | } |
123 | |
124 | GLenum wrap_s = GL_CLAMP_TO_EDGE; |
125 | GLenum wrap_t = GL_CLAMP_TO_EDGE; |
126 | |
127 | std::vector<std::string> wrap_v; |
128 | if (mapping.get("wrap" , wrap_v)) |
129 | { |
130 | if (wrap_v.size() == 1) |
131 | { |
132 | wrap_s = string2wrap(wrap_v[0]); |
133 | wrap_t = string2wrap(wrap_v[0]); |
134 | } |
135 | else if (wrap_v.size() == 2) |
136 | { |
137 | wrap_s = string2wrap(wrap_v[0]); |
138 | wrap_t = string2wrap(wrap_v[1]); |
139 | } |
140 | else |
141 | { |
142 | log_warning << "unknown number of wrap arguments" << std::endl; |
143 | } |
144 | } |
145 | |
146 | GLenum filter = GL_LINEAR; |
147 | std::string filter_s; |
148 | if (mapping.get("filter" , filter_s)) |
149 | { |
150 | filter = string2filter(filter_s); |
151 | } |
152 | |
153 | Vector animate; |
154 | std::vector<float> animate_v; |
155 | if (mapping.get("animate" , animate_v)) |
156 | { |
157 | if (animate_v.size() == 2) |
158 | { |
159 | animate.x = animate_v[0]; |
160 | animate.y = animate_v[1]; |
161 | } |
162 | } |
163 | |
164 | if (region) |
165 | { |
166 | if (!rect) |
167 | { |
168 | rect = region; |
169 | } |
170 | else |
171 | { |
172 | rect->left += region->left; |
173 | rect->top += region->top; |
174 | |
175 | rect->right = rect->left + region->get_width(); |
176 | rect->bottom = rect->top + region->get_height(); |
177 | } |
178 | } |
179 | |
180 | return get(filename, rect, Sampler(filter, wrap_s, wrap_t, animate)); |
181 | } |
182 | |
183 | TexturePtr |
184 | TextureManager::get(const std::string& _filename) |
185 | { |
186 | std::string filename = FileSystem::normalize(_filename); |
187 | Texture::Key key(filename, Rect(0, 0, 0, 0)); |
188 | auto i = m_image_textures.find(key); |
189 | |
190 | TexturePtr texture; |
191 | if (i != m_image_textures.end()) |
192 | texture = i->second.lock(); |
193 | |
194 | if (!texture) { |
195 | texture = create_image_texture(filename, Sampler()); |
196 | texture->m_cache_key = key; |
197 | m_image_textures[key] = texture; |
198 | } |
199 | |
200 | return texture; |
201 | } |
202 | |
203 | TexturePtr |
204 | TextureManager::get(const std::string& _filename, |
205 | const boost::optional<Rect>& rect, |
206 | const Sampler& sampler) |
207 | { |
208 | std::string filename = FileSystem::normalize(_filename); |
209 | Texture::Key key; |
210 | if (rect) |
211 | { |
212 | key = Texture::Key(filename, *rect); |
213 | } |
214 | else |
215 | { |
216 | key = Texture::Key(filename, Rect()); |
217 | } |
218 | |
219 | auto i = m_image_textures.find(key); |
220 | |
221 | TexturePtr texture; |
222 | if (i != m_image_textures.end()) |
223 | texture = i->second.lock(); |
224 | |
225 | if (!texture) { |
226 | if (rect) |
227 | { |
228 | texture = create_image_texture(filename, *rect, sampler); |
229 | } |
230 | else |
231 | { |
232 | texture = create_image_texture(filename, sampler); |
233 | } |
234 | texture->m_cache_key = key; |
235 | m_image_textures[key] = texture; |
236 | } |
237 | |
238 | return texture; |
239 | } |
240 | |
241 | void |
242 | TextureManager::reap_cache_entry(const Texture::Key& key) |
243 | { |
244 | auto i = m_image_textures.find(key); |
245 | if (i == m_image_textures.end()) |
246 | { |
247 | log_warning << "no cache entry for '" << std::get<0>(key) << "'" << std::endl; |
248 | } |
249 | else |
250 | { |
251 | assert(i->second.expired()); |
252 | m_image_textures.erase(i); |
253 | } |
254 | } |
255 | |
256 | TexturePtr |
257 | TextureManager::create_image_texture(const std::string& filename, const Rect& rect, const Sampler& sampler) |
258 | { |
259 | try |
260 | { |
261 | return create_image_texture_raw(filename, rect, sampler); |
262 | } |
263 | catch(const std::exception& err) |
264 | { |
265 | log_warning << "Couldn't load texture '" << filename << "' (now using dummy texture): " << err.what() << std::endl; |
266 | return create_dummy_texture(); |
267 | } |
268 | } |
269 | |
270 | const SDL_Surface& |
271 | TextureManager::get_surface(const std::string& filename) |
272 | { |
273 | auto i = m_surfaces.find(filename); |
274 | if (i != m_surfaces.end()) |
275 | { |
276 | return *i->second; |
277 | } |
278 | else |
279 | { |
280 | SDLSurfacePtr image = SDLSurface::from_file(filename); |
281 | if (!image) |
282 | { |
283 | std::ostringstream msg; |
284 | msg << "Couldn't load image '" << filename << "' :" << SDL_GetError(); |
285 | throw std::runtime_error(msg.str()); |
286 | } |
287 | |
288 | return *(m_surfaces[filename] = std::move(image)); |
289 | } |
290 | } |
291 | |
292 | TexturePtr |
293 | TextureManager::create_image_texture_raw(const std::string& filename, const Rect& rect, const Sampler& sampler) |
294 | { |
295 | const SDL_Surface& src_surface = get_surface(filename); |
296 | |
297 | SDLSurfacePtr convert; |
298 | if (src_surface.format->Rmask == 0 && |
299 | src_surface.format->Gmask == 0 && |
300 | src_surface.format->Bmask == 0 && |
301 | src_surface.format->Amask == 0) |
302 | { |
303 | log_debug << "Wrong surface format for image " << filename << ". Compensating." << std::endl; |
304 | convert.reset(SDL_ConvertSurfaceFormat(const_cast<SDL_Surface*>(&src_surface), SDL_PIXELFORMAT_RGBA8888, 0)); |
305 | } |
306 | |
307 | const SDL_Surface& surface = convert ? *convert : src_surface; |
308 | |
309 | SDLSurfacePtr subimage(SDL_CreateRGBSurfaceFrom(static_cast<uint8_t*>(surface.pixels) + |
310 | rect.top * surface.pitch + |
311 | rect.left * surface.format->BytesPerPixel, |
312 | rect.get_width(), rect.get_height(), |
313 | surface.format->BitsPerPixel, |
314 | surface.pitch, |
315 | surface.format->Rmask, |
316 | surface.format->Gmask, |
317 | surface.format->Bmask, |
318 | surface.format->Amask)); |
319 | if (!subimage) |
320 | { |
321 | throw std::runtime_error("SDL_CreateRGBSurfaceFrom() call failed" ); |
322 | } |
323 | |
324 | return VideoSystem::current()->new_texture(*subimage, sampler); |
325 | } |
326 | |
327 | TexturePtr |
328 | TextureManager::create_image_texture(const std::string& filename, const Sampler& sampler) |
329 | { |
330 | try |
331 | { |
332 | return create_image_texture_raw(filename, sampler); |
333 | } |
334 | catch (const std::exception& err) |
335 | { |
336 | log_warning << "Couldn't load texture '" << filename << "' (now using dummy texture): " << err.what() << std::endl; |
337 | return create_dummy_texture(); |
338 | } |
339 | } |
340 | |
341 | TexturePtr |
342 | TextureManager::create_image_texture_raw(const std::string& filename, const Sampler& sampler) |
343 | { |
344 | SDLSurfacePtr image = SDLSurface::from_file(filename); |
345 | if (!image) |
346 | { |
347 | std::ostringstream msg; |
348 | msg << "Couldn't load image '" << filename << "' :" << SDL_GetError(); |
349 | throw std::runtime_error(msg.str()); |
350 | } |
351 | else |
352 | { |
353 | TexturePtr texture = VideoSystem::current()->new_texture(*image, sampler); |
354 | image.reset(nullptr); |
355 | return texture; |
356 | } |
357 | } |
358 | |
359 | TexturePtr |
360 | TextureManager::create_dummy_texture() |
361 | { |
362 | const std::string dummy_texture_fname = "images/engine/missing.png" ; |
363 | |
364 | // on error, try loading placeholder file |
365 | try |
366 | { |
367 | TexturePtr tex = create_image_texture_raw(dummy_texture_fname, Sampler()); |
368 | return tex; |
369 | } |
370 | catch (const std::exception& err) |
371 | { |
372 | // on error (when loading placeholder), try using empty surface, |
373 | // when that fails to, just give up |
374 | SDLSurfacePtr image(SDL_CreateRGBSurface(0, 1024, 1024, 8, 0, 0, 0, 0)); |
375 | if (!image) |
376 | { |
377 | throw; |
378 | } |
379 | else |
380 | { |
381 | log_warning << "Couldn't load texture '" << dummy_texture_fname << "' (now using empty one): " << err.what() << std::endl; |
382 | TexturePtr texture = VideoSystem::current()->new_texture(*image); |
383 | return texture; |
384 | } |
385 | } |
386 | } |
387 | |
388 | void |
389 | TextureManager::debug_print(std::ostream& out) const |
390 | { |
391 | size_t total_texture_pixels = 0; |
392 | out << "textures:begin" << std::endl; |
393 | for(const auto& it : m_image_textures) |
394 | { |
395 | const auto& key = it.first; |
396 | |
397 | if (auto texture = it.second.lock()) { |
398 | total_texture_pixels += std::get<1>(key).get_area(); |
399 | } |
400 | |
401 | out << " texture " |
402 | << " filename:" << std::get<0>(key) << " " << std::get<1>(key) |
403 | << " " << "use_count:" << it.second.use_count() << std::endl; |
404 | } |
405 | out << "textures:end" << std::endl; |
406 | |
407 | size_t total_surface_pixels = 0; |
408 | out << "surfaces:begin" << std::endl; |
409 | for(const auto& it : m_surfaces) |
410 | { |
411 | const auto& filename = it.first; |
412 | const auto& surface = it.second; |
413 | |
414 | total_surface_pixels += surface->w * surface->h; |
415 | out << " surface filename:" << filename << " " << surface->w << "x" << surface->h << std::endl; |
416 | } |
417 | out << "surfaces:end" << std::endl; |
418 | |
419 | out << "total texture count:" << m_image_textures.size() << std::endl; |
420 | out << "total texture pixels:" << total_texture_pixels << std::endl; |
421 | |
422 | out << "total surface count:" << m_surfaces.size() << std::endl; |
423 | out << "total surface pixels:" << total_surface_pixels << std::endl; |
424 | } |
425 | |
426 | /* EOF */ |
427 | |