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