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 /**
22 * @file texture.c
23 * This is where we do texture atlas generation.
24 */
25
26 #include "lib/framework/frame.h"
27
28 #include <string.h>
29 #include <physfs.h>
30
31 #include "lib/framework/file.h"
32 #include "lib/framework/string_ext.h"
33
34 #include "lib/ivis_opengl/pietypes.h"
35 #include "lib/ivis_opengl/piestate.h"
36 #include "lib/ivis_opengl/tex.h"
37 #include "lib/ivis_opengl/piepalette.h"
38 #include "lib/ivis_opengl/screen.h"
39
40 #include "display3ddef.h"
41 #include "texture.h"
42 #include "radar.h"
43 #include "map.h"
44
45
46 #define MIPMAP_LEVELS 4
47 #define MIPMAP_MAX 128
48
49 /* Texture page and coordinates for each tile */
50 TILE_TEX_INFO tileTexInfo[MAX_TILES];
51
52 static size_t firstPage; // the last used page before we start adding terrain textures
53 size_t terrainPage; // texture ID of the terrain page
54 static int mipmap_max, mipmap_levels;
55 static int maxTextureSize = 2048; ///< the maximum size texture we will create
56
setTextureSize(int texSize)57 void setTextureSize(int texSize)
58 {
59 if (texSize < 16 || texSize % 16 != 0)
60 {
61 debug(LOG_ERROR, "Attempted to set bad texture size %d! Ignored.", texSize);
62 return;
63 }
64 maxTextureSize = texSize;
65 debug(LOG_TEXTURE, "texture size set to %d", texSize);
66 }
67
getTextureSize()68 int getTextureSize()
69 {
70 return maxTextureSize;
71 }
72
73 // Generate a new texture page both in the texture page table, and on the graphics card
newPage(const char * name,int level,int width,int height,int count)74 static size_t newPage(const char *name, int level, int width, int height, int count)
75 {
76 size_t texPage = firstPage + ((count + 1) / TILES_IN_PAGE);
77
78 if (texPage == pie_NumberOfPages())
79 {
80 // We need to create a new texture page; create it and increase texture table to store it
81 pie_ReserveTexture(name, width, height);
82 pie_AssignTexture(texPage,
83 gfx_api::context::get().create_texture(mipmap_levels, width, height,
84 gfx_api::pixel_format::FORMAT_RGBA8_UNORM_PACK8));
85 }
86 terrainPage = texPage;
87 return texPage;
88 }
89
texLoad(const char * fileName)90 bool texLoad(const char *fileName)
91 {
92 char fullPath[PATH_MAX], partialPath[PATH_MAX], *buffer;
93 unsigned int i, j, k, size;
94 int texPage;
95
96 firstPage = pie_NumberOfPages();
97
98 ASSERT_OR_RETURN(false, MIPMAP_MAX == TILE_WIDTH && MIPMAP_MAX == TILE_HEIGHT, "Bad tile sizes");
99
100 // store the filename so we can later determine which tileset we are using
101 if (tilesetDir)
102 {
103 free(tilesetDir);
104 }
105 tilesetDir = strdup(fileName);
106
107 // reset defaults
108 mipmap_max = MIPMAP_MAX;
109 mipmap_levels = MIPMAP_LEVELS;
110
111 int32_t max_texture_size = gfx_api::context::get().get_context_value(gfx_api::context::context_value::MAX_TEXTURE_SIZE);
112
113 while (max_texture_size < mipmap_max * TILES_IN_PAGE_COLUMN)
114 {
115 mipmap_max /= 2;
116 mipmap_levels--;
117 debug(LOG_ERROR, "Max supported texture size %dx%d is too low - reducing texture detail to %dx%d.",
118 (int)max_texture_size, (int)max_texture_size, mipmap_max, mipmap_max);
119 ASSERT(mipmap_levels > 0, "Supported texture size %d is too low to load any mipmap levels!",
120 (int)max_texture_size);
121 if (mipmap_levels == 0)
122 {
123 exit(1);
124 }
125 }
126 while (maxTextureSize < mipmap_max)
127 {
128 mipmap_max /= 2;
129 mipmap_levels--;
130 debug(LOG_TEXTURE, "Downgrading texture quality to %d due to user setting %d", mipmap_max, maxTextureSize);
131 }
132
133 /* Get and set radar colours */
134
135 sprintf(fullPath, "%s.radar", fileName);
136 if (!loadFile(fullPath, &buffer, &size))
137 {
138 debug(LOG_FATAL, "texLoad: Could not find radar colours at %s", fullPath);
139 abort(); // cannot recover; we could possibly generate a random palette?
140 }
141 i = 0; // tile
142 j = 0; // place in buffer
143 do
144 {
145 unsigned int r, g, b;
146 int cnt = 0;
147
148 k = sscanf(buffer + j, "%2x%2x%2x%n", &r, &g, &b, &cnt);
149 j += cnt;
150 if (k >= 3)
151 {
152 radarColour(i, r, g, b);
153 }
154 i++; // next tile
155 }
156 while (k >= 3 && j + 6 < size);
157 free(buffer);
158
159 /* Now load the actual tiles */
160
161 i = mipmap_max; // i is used to keep track of the tile dimensions
162 for (j = 0; j < mipmap_levels; j++)
163 {
164 int xOffset = 0, yOffset = 0; // offsets into the texture atlas
165 int xSize = 1;
166 int ySize = 1;
167 const int xLimit = TILES_IN_PAGE_COLUMN * i;
168 const int yLimit = TILES_IN_PAGE_ROW * i;
169
170 // pad width and height into ^2 values
171 while (xLimit > (xSize *= 2)) {}
172 while (yLimit > (ySize *= 2)) {}
173
174 // Generate the empty texture buffer in VRAM
175 texPage = newPage(fileName, j, xSize, ySize, 0);
176
177 sprintf(partialPath, "%s-%d", fileName, i);
178
179 // Load until we cannot find anymore of them
180 for (k = 0; k < MAX_TILES; k++)
181 {
182 iV_Image tile;
183
184 snprintf(fullPath, sizeof(fullPath), "%s/tile-%02d.png", partialPath, k);
185 if (PHYSFS_exists(fullPath)) // avoid dire warning
186 {
187 bool retval = iV_loadImage_PNG(fullPath, &tile);
188 ASSERT_OR_RETURN(false, retval, "Could not load %s!", fullPath);
189 }
190 else
191 {
192 // no more textures in this set
193 ASSERT_OR_RETURN(false, k > 0, "Could not find %s", fullPath);
194 break;
195 }
196 // Insert into texture page
197 pie_Texture(texPage).upload(j, xOffset, yOffset, tile.width, tile.height, gfx_api::pixel_format::FORMAT_RGBA8_UNORM_PACK8, tile.bmp);
198 free(tile.bmp);
199 if (i == mipmap_max) // dealing with main texture page; so register coordinates
200 {
201 tileTexInfo[k].uOffset = (float)xOffset / (float)xSize;
202 tileTexInfo[k].vOffset = (float)yOffset / (float)ySize;
203 tileTexInfo[k].texPage = texPage;
204 debug(LOG_TEXTURE, " texLoad: Registering k=%d i=%d u=%f v=%f xoff=%d yoff=%d xsize=%d ysize=%d tex=%d (%s)",
205 k, i, tileTexInfo[k].uOffset, tileTexInfo[k].vOffset, xOffset, yOffset, xSize, ySize, texPage, fullPath);
206 }
207 xOffset += i; // i is width of tile
208 if (xOffset + i > xLimit)
209 {
210 yOffset += i; // i is also height of tile
211 xOffset = 0;
212 }
213 if (yOffset + i > yLimit)
214 {
215 /* Change to next texture page */
216 xOffset = 0;
217 yOffset = 0;
218 debug(LOG_TEXTURE, "texLoad: Extra page added at %d for %s, was page %d, opengl id %u",
219 k, partialPath, texPage, (unsigned)pie_Texture(texPage).id());
220 texPage = newPage(fileName, j, xSize, ySize, k);
221 }
222 }
223 debug(LOG_TEXTURE, "texLoad: Found %d textures for %s mipmap level %d, added to page %d, opengl id %u",
224 k, partialPath, i, texPage, (unsigned)pie_Texture(texPage).id());
225 i /= 2; // halve the dimensions for the next series; OpenGL mipmaps start with largest at level zero
226 }
227 return true;
228 }
229