1 /*
2 This file is part of Warzone 2100.
3 Copyright (C) 1999-2004 Eidos Interactive
4 Copyright (C) 2005-2020 Warzone 2100 Project
5
6 Warzone 2100 is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 Warzone 2100 is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Warzone 2100; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 #include "lib/framework/frame.h"
22 #include "lib/framework/frameresource.h"
23
24 #include "lib/ivis_opengl/ivisdef.h"
25 #include "lib/ivis_opengl/piestate.h"
26 #include "lib/ivis_opengl/tex.h"
27 #include "lib/ivis_opengl/piepalette.h"
28 #include "lib/ivis_opengl/png_util.h"
29
30 #include "screen.h"
31
32 #include <algorithm>
33 #include <unordered_map>
34
35 #if defined(__clang__)
36 # pragma clang diagnostic push
37 # pragma clang diagnostic ignored "-Wcast-align"
38 #endif
39 #if defined(__GNUC__)
40 # pragma GCC diagnostic push
41 # pragma GCC diagnostic ignored "-Wcast-qual"
42 #endif
43
44 #include "3rdparty/stb_image_resize.h"
45
46 #if defined(__GNUC__)
47 # pragma GCC diagnostic pop
48 #endif
49 #if defined(__clang__)
50 # pragma clang diagnostic pop
51 #endif
52
53 //*************************************************************************
54
55 struct iTexPage
56 {
57 std::string name;
58 gfx_api::texture* id = nullptr;
59
60 iTexPage() = default;
61
iTexPageiTexPage62 iTexPage(iTexPage&& input)
63 {
64 std::swap(name, input.name);
65 id = input.id;
66 input.id = nullptr;
67 }
68
~iTexPageiTexPage69 ~iTexPage()
70 {
71 if (id)
72 delete id;
73 }
74
75 iTexPage (const iTexPage &) = delete;
76 iTexPage & operator = (const iTexPage &) = delete;
77 };
78
79 std::vector<iTexPage> _TEX_PAGE;
80 std::unordered_map<std::string, size_t> _NAME_TO_TEX_PAGE_MAP;
81
82 //*************************************************************************
83
pie_Texture(size_t page)84 gfx_api::texture& pie_Texture(size_t page)
85 {
86 return *(_TEX_PAGE[page].id);
87 }
88
pie_NumberOfPages()89 size_t pie_NumberOfPages()
90 {
91 return _TEX_PAGE.size();
92 }
93
94 // Add a new texture page to the list
pie_ReserveTexture(const char * name,const size_t & width,const size_t & height)95 size_t pie_ReserveTexture(const char *name, const size_t& width, const size_t& height)
96 {
97 iTexPage tex;
98 tex.name = name;
99 _TEX_PAGE.push_back(std::move(tex));
100 size_t page = _TEX_PAGE.size() - 1;
101 _NAME_TO_TEX_PAGE_MAP[name] = page;
102 return page;
103 }
104
pie_AssignTexture(size_t page,gfx_api::texture * texture)105 void pie_AssignTexture(size_t page, gfx_api::texture* texture)
106 {
107 if (_TEX_PAGE[page].id)
108 delete _TEX_PAGE[page].id;
109 _TEX_PAGE[page].id = texture;
110 }
111
pie_AddTexPage_Impl(iV_Image * s,const char * filename,bool gameTexture,size_t page)112 static size_t pie_AddTexPage_Impl(iV_Image *s, const char *filename, bool gameTexture, size_t page)
113 {
114 ASSERT(s && filename, "Bad input parameter");
115
116 _NAME_TO_TEX_PAGE_MAP[filename] = page;
117 debug(LOG_TEXTURE, "%s page=%zu", filename, page);
118
119 if (gameTexture) // this is a game texture, use texture compression
120 {
121 gfx_api::pixel_format format = iV_getPixelFormat(s);
122 if (_TEX_PAGE[page].id)
123 delete _TEX_PAGE[page].id;
124 size_t mip_count = floor(log(std::max(s->width, s->height))) + 1;
125 _TEX_PAGE[page].id = gfx_api::context::get().create_texture(mip_count, s->width, s->height, format, filename);
126 pie_Texture(page).upload_and_generate_mipmaps(0u, 0u, s->width, s->height, iV_getPixelFormat(s), s->bmp);
127 }
128 else // this is an interface texture, do not use compression
129 {
130 if (_TEX_PAGE[page].id)
131 delete _TEX_PAGE[page].id;
132 _TEX_PAGE[page].id = gfx_api::context::get().create_texture(1, s->width, s->height, gfx_api::pixel_format::FORMAT_RGBA8_UNORM_PACK8, filename);
133 pie_Texture(page).upload(0u, 0u, 0u, s->width, s->height, iV_getPixelFormat(s), s->bmp);
134 }
135
136 // it is uploaded, we do not need it anymore
137 free(s->bmp);
138 s->bmp = nullptr;
139
140 /* Send back the texpage number so we can store it in the IMD */
141 return page;
142 }
143
pie_AddTexPage(iV_Image * s,const char * filename,bool gameTexture)144 size_t pie_AddTexPage(iV_Image *s, const char *filename, bool gameTexture)
145 {
146 ASSERT(s && filename, "Bad input parameter");
147 ASSERT(_NAME_TO_TEX_PAGE_MAP.count(filename) == 0, "tex page %s already exists", filename);
148 iTexPage tex;
149 size_t page = _TEX_PAGE.size();
150 tex.name = filename;
151 _TEX_PAGE.push_back(std::move(tex));
152
153 return pie_AddTexPage_Impl(s, filename, gameTexture, page);
154 }
155
pie_AddTexPage(iV_Image * s,const char * filename,bool gameTexture,size_t page)156 size_t pie_AddTexPage(iV_Image *s, const char *filename, bool gameTexture, size_t page)
157 {
158 ASSERT(s && filename, "Bad input parameter");
159
160 // replace
161 _NAME_TO_TEX_PAGE_MAP.erase(_TEX_PAGE[page].name);
162 _TEX_PAGE[page].name = filename;
163
164 return pie_AddTexPage_Impl(s, filename, gameTexture, page);
165 }
166
167 /*!
168 * Turns filename into a pagename if possible
169 * \param[in,out] filename Filename to pagify
170 */
pie_MakeTexPageName(const std::string & filename)171 std::string pie_MakeTexPageName(const std::string& filename)
172 {
173 size_t c = filename.find(iV_TEXNAME_TCSUFFIX);
174 if (c != std::string::npos)
175 {
176 return filename.substr(0, c + 7);
177 }
178 c = filename.find('-', 5);
179 if (c != std::string::npos)
180 {
181 return filename.substr(0, c);
182 }
183 return filename;
184 }
185
186 /*!
187 * Turns page filename into a pagename + tc mask if possible
188 * \param[in,out] filename Filename to pagify
189 */
pie_MakeTexPageTCMaskName(const std::string & filename)190 std::string pie_MakeTexPageTCMaskName(const std::string& filename)
191 {
192 std::string result = filename;
193 if (filename.rfind("page-", 0) == 0)
194 {
195 // filename starts with "page-"
196 size_t i;
197 for (i = 5; i < filename.size() - 1 && isdigit(filename[i]); i++) {}
198 result = filename.substr(0, i);
199 result += iV_TEXNAME_TCSUFFIX;
200 }
201 return result;
202 }
203
scaleImageMaxSize(iV_Image * s,int maxWidth,int maxHeight)204 bool scaleImageMaxSize(iV_Image *s, int maxWidth, int maxHeight)
205 {
206 if ((maxWidth <= 0 || s->width <= maxWidth) && (maxHeight <= 0 || s->height <= maxHeight))
207 {
208 return false;
209 }
210
211 double scalingRatio;
212 double widthRatio = (double)maxWidth / (double)s->width;
213 double heightRatio = (double)maxHeight / (double)s->height;
214 if (maxWidth > 0 && maxHeight > 0)
215 {
216 scalingRatio = std::min<double>(widthRatio, heightRatio);
217 }
218 else
219 {
220 scalingRatio = (maxWidth > 0) ? widthRatio : heightRatio;
221 }
222
223 int output_w = static_cast<int>(s->width * scalingRatio);
224 int output_h = static_cast<int>(s->height * scalingRatio);
225
226 unsigned char *output_pixels = (unsigned char *)malloc(static_cast<size_t>(output_w) * static_cast<size_t>(output_h) * s->depth);
227 stbir_resize_uint8(s->bmp, s->width, s->height, 0,
228 output_pixels, output_w, output_h, 0,
229 s->depth);
230 free(s->bmp);
231 s->width = output_w;
232 s->height = output_h;
233 s->bmp = output_pixels;
234
235 return true;
236 }
237
238 /** Retrieve the texture number for a given texture resource.
239 *
240 * @note We keep textures in a separate data structure _TEX_PAGE apart from the
241 * normal resource system.
242 *
243 * @param filename The filename of the texture page to search for.
244 * @param compression If we need to load it, should we use texture compression?
245 * @param maxWidth If we need to load it, should we limit the texture width? (Resizes and preserves the texture image's aspect ratio)
246 * @param maxHeight If we need to load it, should we limit the texture height? (Resizes and preserves the texture image's aspect ratio)
247 *
248 * @return a non-negative index number for the texture, negative if no texture
249 * with the given filename could be found
250 */
iV_GetTexture(const char * filename,bool compression,int maxWidth,int maxHeight)251 optional<size_t> iV_GetTexture(const char *filename, bool compression, int maxWidth /*= -1*/, int maxHeight /*= -1*/)
252 {
253 ASSERT(filename != nullptr, "filename must not be null");
254 iV_Image sSprite;
255
256 /* Have we already loaded this one then? */
257 std::string path = pie_MakeTexPageName(filename);
258 const auto it = _NAME_TO_TEX_PAGE_MAP.find(path);
259 if (it != _NAME_TO_TEX_PAGE_MAP.end())
260 {
261 return it->second;
262 }
263
264 // Try to load it
265 std::string loadPath = "texpages/";
266 loadPath += filename;
267 if (!iV_loadImage_PNG(loadPath.c_str(), &sSprite))
268 {
269 debug(LOG_ERROR, "Failed to load %s", loadPath.c_str());
270 return nullopt;
271 }
272 scaleImageMaxSize(&sSprite, maxWidth, maxHeight);
273 size_t page = pie_AddTexPage(&sSprite, path.c_str(), compression);
274 resDoResLoadCallback(); // ensure loading screen doesn't freeze when loading large images
275 return optional<size_t>(page);
276 }
277
replaceTexture(const WzString & oldfile,const WzString & newfile)278 bool replaceTexture(const WzString &oldfile, const WzString &newfile)
279 {
280 // Load new one to replace it
281 iV_Image image;
282 if (!iV_loadImage_PNG(WzString("texpages/" + newfile).toUtf8().c_str(), &image))
283 {
284 debug(LOG_ERROR, "Failed to load image: %s", newfile.toUtf8().c_str());
285 return false;
286 }
287 std::string tmpname = pie_MakeTexPageName(oldfile.toUtf8());
288 // Have we already loaded this one?
289 const auto it = _NAME_TO_TEX_PAGE_MAP.find(tmpname);
290 if (it != _NAME_TO_TEX_PAGE_MAP.end())
291 {
292 gfx_api::context::get().debugStringMarker("Replacing texture");
293 size_t page = it->second;
294 debug(LOG_TEXTURE, "Replacing texture %s with %s from index %zu (tex id %u)", it->first.c_str(), newfile.toUtf8().c_str(), page, _TEX_PAGE[page].id->id());
295 tmpname = pie_MakeTexPageName(newfile.toUtf8());
296 pie_AddTexPage(&image, tmpname.c_str(), true, page);
297 iV_unloadImage(&image);
298 return true;
299 }
300 iV_unloadImage(&image);
301 debug(LOG_ERROR, "Nothing to replace!");
302 return false;
303 }
304
pie_TexShutDown()305 void pie_TexShutDown()
306 {
307 // TODO, lazy deletions for faster loading of next level
308 debug(LOG_TEXTURE, "Cleaning out %u textures", static_cast<unsigned>(_TEX_PAGE.size()));
309 _TEX_PAGE.clear();
310 _NAME_TO_TEX_PAGE_MAP.clear();
311 }
312
pie_TexInit()313 void pie_TexInit()
314 {
315 debug(LOG_TEXTURE, "pie_TexInit successful");
316 }
317
iV_unloadImage(iV_Image * image)318 void iV_unloadImage(iV_Image *image)
319 {
320 if (image)
321 {
322 if (image->bmp)
323 {
324 free(image->bmp);
325 image->bmp = nullptr;
326 }
327 }
328 else
329 {
330 debug(LOG_ERROR, "Tried to free invalid image!");
331 }
332 }
333
iV_getPixelFormat(const iV_Image * image)334 gfx_api::pixel_format iV_getPixelFormat(const iV_Image *image)
335 {
336 switch (image->depth)
337 {
338 case 3:
339 return gfx_api::pixel_format::FORMAT_RGB8_UNORM_PACK8;
340 case 4:
341 return gfx_api::pixel_format::FORMAT_RGBA8_UNORM_PACK8;
342 default:
343 debug(LOG_FATAL, "iV_getPixelFormat: Unsupported image depth: %u", image->depth);
344 return gfx_api::pixel_format::invalid;
345 }
346 }
347