1 /**
2 * @file
3 */
4
5 /*
6 Copyright (C) 1997-2001 Id Software, Inc.
7
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License
10 as published by the Free Software Foundation; either version 2
11 of the License, or (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16
17 See the GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22
23 */
24
25 #include "r_local.h"
26 #include "r_error.h"
27 #include "r_geoscape.h"
28 #include "../../shared/images.h"
29 #include "../cl_screen.h"
30
31 #define MAX_IMAGEHASH 256
32 static image_t* imageHash[MAX_IMAGEHASH];
33
34 #define IMAGE_ARRAY_SIZE 128
35 typedef struct imageArray_s {
36 image_t images[IMAGE_ARRAY_SIZE];
37 struct imageArray_s* next;
38 } imageArray_t;
39
40 imageArray_t r_images;
41 int r_numImages;
42
43 /* Wicked for()-loop to go through all images in the r_images linked list. Parameters are: (int i, image_t* image, imageArray_t* imageArray) */
44 #define FOR_EACH_IMAGE(i, image, imageArray) \
45 for (i = 0, imageArray = &r_images, image = &imageArray->images[0]; i < r_numImages; i++, image++, \
46 (i % IMAGE_ARRAY_SIZE) ? 0 : (image = (imageArray = imageArray->next) ? &imageArray->images[0] : nullptr))
47
48
49 /* generic environment map */
50 image_t* r_envmaptextures[MAX_ENVMAPTEXTURES];
51
52 /* lense flares */
53 image_t* r_flaretextures[NUM_FLARETEXTURES];
54
55 #define MAX_TEXTURE_SIZE 8192
56
57 /**
58 * @brief Free previously loaded materials and their stages
59 * @sa R_LoadMaterials
60 */
R_ImageClearMaterials(void)61 void R_ImageClearMaterials (void)
62 {
63 int i;
64 image_t* image;
65 imageArray_t* images;
66
67 /* clear previously loaded materials */
68 FOR_EACH_IMAGE(i, image, images) {
69 material_t* m = &image->material;
70 materialStage_t* s = m->stages;
71
72 while (s) { /* free the stages chain */
73 materialStage_t* ss = s->next;
74 Mem_Free(s);
75 s = ss;
76 }
77
78 *m = defaultMaterial;
79 }
80 }
81
82 /**
83 * @brief Shows all loaded images
84 */
R_ImageList_f(void)85 void R_ImageList_f (void)
86 {
87 int i, cnt;
88 image_t* image;
89 imageArray_t* images;
90 int texels;
91
92 Com_Printf("------------------\n");
93 texels = 0;
94 cnt = 0;
95
96 FOR_EACH_IMAGE(i, image, images) {
97 const char* type;
98 if (!image->texnum)
99 continue;
100 cnt++;
101 texels += image->upload_width * image->upload_height;
102 switch (image->type) {
103 case it_effect:
104 type = "EF";
105 break;
106 case it_skin:
107 type = "SK";
108 break;
109 case it_wrappic:
110 type = "WR";
111 break;
112 case it_chars:
113 type = "CH";
114 break;
115 case it_static:
116 type = "ST";
117 break;
118 case it_normalmap:
119 type = "NM";
120 break;
121 case it_material:
122 type = "MA";
123 break;
124 case it_lightmap:
125 type = "LM";
126 break;
127 case it_world:
128 type = "WO";
129 break;
130 case it_pic:
131 type = "PI";
132 break;
133 default:
134 type = " ";
135 break;
136 }
137
138 Com_Printf("%s %4i %4i RGB: %5i idx: %s\n", type, image->upload_width, image->upload_height, image->texnum, image->name);
139 }
140 Com_Printf("Total textures: %i/%i (max textures: %i)\n", cnt, r_numImages, MAX_GL_TEXTURES);
141 Com_Printf("Total texel count (not counting mipmaps): %i\n", texels);
142 }
143
144 /**
145 * @brief Generic image-data loading fucntion.
146 * @param[in] name (Full) pathname to the image to load. Extension (if given) will be ignored.
147 * @param[out] pic Image data.
148 * @param[out] width Width of the loaded image.
149 * @param[out] height Height of the loaded image.
150 * @sa R_FindImage
151 */
R_LoadImage(const char * name,byte ** pic,int * width,int * height)152 void R_LoadImage (const char* name, byte** pic, int* width, int* height)
153 {
154 char filenameTemp[MAX_QPATH];
155 SDL_Surface* surf;
156
157 if (Q_strnull(name))
158 Com_Error(ERR_FATAL, "R_LoadImage: nullptr name");
159
160 Com_StripExtension(name, filenameTemp, sizeof(filenameTemp));
161
162 if ((surf = Img_LoadImage(filenameTemp))) {
163 const size_t size = (surf->w * surf->h) * 4;
164 *width = surf->w;
165 *height = surf->h;
166 *pic = Mem_PoolAllocTypeN(byte, size, vid_imagePool);
167 memcpy(*pic, surf->pixels, size);
168 SDL_FreeSurface(surf);
169 }
170 }
171
R_ScaleTexture(const unsigned * in,int inwidth,int inheight,unsigned * out,int outwidth,int outheight)172 void R_ScaleTexture (const unsigned *in, int inwidth, int inheight, unsigned *out, int outwidth, int outheight)
173 {
174 int i, j;
175 unsigned frac;
176 unsigned p1[MAX_TEXTURE_SIZE], p2[MAX_TEXTURE_SIZE];
177 const unsigned fracstep = inwidth * 0x10000 / outwidth;
178
179 assert(outwidth <= MAX_TEXTURE_SIZE);
180
181 frac = fracstep >> 2;
182 for (i = 0; i < outwidth; i++) {
183 p1[i] = 4 * (frac >> 16);
184 frac += fracstep;
185 }
186 frac = 3 * (fracstep >> 2);
187 for (i = 0; i < outwidth; i++) {
188 p2[i] = 4 * (frac >> 16);
189 frac += fracstep;
190 }
191
192 for (i = 0; i < outheight; i++, out += outwidth) {
193 const int index = inwidth * (int) ((i + 0.25) * inheight / outheight);
194 const unsigned *inrow = in + index;
195 const int index2 = inwidth * (int) ((i + 0.75) * inheight / outheight);
196 const unsigned *inrow2 = in + index2;
197
198 assert(index < inwidth * inheight);
199 assert(index2 < inwidth * inheight);
200
201 for (j = 0; j < outwidth; j++) {
202 const byte* pix1 = (const byte*) inrow + p1[j];
203 const byte* pix2 = (const byte*) inrow + p2[j];
204 const byte* pix3 = (const byte*) inrow2 + p1[j];
205 const byte* pix4 = (const byte*) inrow2 + p2[j];
206 ((byte*) (out + j))[0] = (pix1[0] + pix2[0] + pix3[0] + pix4[0]) >> 2;
207 ((byte*) (out + j))[1] = (pix1[1] + pix2[1] + pix3[1] + pix4[1]) >> 2;
208 ((byte*) (out + j))[2] = (pix1[2] + pix2[2] + pix3[2] + pix4[2]) >> 2;
209 ((byte*) (out + j))[3] = (pix1[3] + pix2[3] + pix3[3] + pix4[3]) >> 2;
210 }
211 }
212 }
213
214 /**
215 * @brief Calculates the texture size that should be used to upload the texture data
216 * @param[in] width The width of the source texture data
217 * @param[in] height The heigt of the source texture data
218 * @param[out] scaledWidth The resulting width - can be the same as the given @c width
219 * @param[out] scaledHeight The resulting height - can be the same as the given @c height
220 */
R_GetScaledTextureSize(int width,int height,int * scaledWidth,int * scaledHeight)221 void R_GetScaledTextureSize (int width, int height, int* scaledWidth, int* scaledHeight)
222 {
223 for (*scaledWidth = 1; *scaledWidth < width; *scaledWidth <<= 1) {}
224 for (*scaledHeight = 1; *scaledHeight < height; *scaledHeight <<= 1) {}
225
226 while (*scaledWidth > r_config.maxTextureSize || *scaledHeight > r_config.maxTextureSize) {
227 *scaledWidth >>= 1;
228 *scaledHeight >>= 1;
229 }
230
231 if (*scaledWidth > MAX_TEXTURE_SIZE)
232 *scaledWidth = MAX_TEXTURE_SIZE;
233 else if (*scaledWidth < 1)
234 *scaledWidth = 1;
235
236 if (*scaledHeight > MAX_TEXTURE_SIZE)
237 *scaledHeight = MAX_TEXTURE_SIZE;
238 else if (*scaledHeight < 1)
239 *scaledHeight = 1;
240 }
241
R_IsClampedImageType(imagetype_t type)242 inline static bool R_IsClampedImageType (imagetype_t type)
243 {
244 return type == it_pic || type == it_worldrelated;
245 }
246
247 /**
248 * @brief Uploads the opengl texture to the server
249 * @param[in] data Must be in RGBA format
250 * @param width Width of the image
251 * @param height Height of the image
252 * @param[in,out] image Pointer to the image structure to initialize
253 */
R_UploadTexture(const unsigned * data,int width,int height,image_t * image)254 void R_UploadTexture (const unsigned *data, int width, int height, image_t* image)
255 {
256 unsigned *scaled = nullptr;
257 int scaledWidth, scaledHeight;
258 #ifdef GL_VERSION_ES_CM_1_0
259 GLint texFormat = GL_RGB;
260 #else
261 GLint texFormat = r_config.gl_compressed_solid_format ? r_config.gl_compressed_solid_format : r_config.gl_solid_format;
262 #endif
263 int i, c;
264 const byte* scan;
265 const bool mipmap = (image->type != it_pic && image->type != it_worldrelated && image->type != it_chars);
266 const bool clamp = R_IsClampedImageType(image->type);
267
268 /* scan the texture for any non-255 alpha */
269 c = width * height;
270 /* set scan to the first alpha byte */
271 for (i = 0, scan = ((const byte*) data) + 3; i < c; i++, scan += 4) {
272 if (*scan != 255) {
273 #ifdef GL_VERSION_ES_CM_1_0
274 texFormat = GL_RGBA;
275 #else
276 texFormat = r_config.gl_compressed_alpha_format ? r_config.gl_compressed_alpha_format : r_config.gl_alpha_format;
277 #endif
278 image->has_alpha = true;
279 break;
280 }
281 }
282
283 R_GetScaledTextureSize(width, height, &scaledWidth, &scaledHeight);
284
285 image->upload_width = scaledWidth; /* after power of 2 and scales */
286 image->upload_height = scaledHeight;
287
288 /* some images need very little attention (pics, fonts, etc..) */
289 if (!mipmap && scaledWidth == width && scaledHeight == height) {
290 /* no mipmapping for these images to save memory */
291 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
292 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
293 if (clamp) {
294 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
295 R_CheckError();
296 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
297 R_CheckError();
298 }
299 glTexImage2D(GL_TEXTURE_2D, 0, texFormat, scaledWidth, scaledHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
300 return;
301 }
302
303 if (scaledWidth != width || scaledHeight != height) { /* whereas others need to be scaled */
304 scaled = Mem_PoolAllocTypeN(unsigned, scaledWidth * scaledHeight, vid_imagePool);
305 R_ScaleTexture(data, width, height, scaled, scaledWidth, scaledHeight);
306 }
307
308 /* and mipmapped */
309 if (mipmap) {
310 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, r_config.gl_filter_min);
311 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, r_config.gl_filter_max);
312 glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
313 if (r_config.anisotropic) {
314 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, r_config.maxAnisotropic);
315 R_CheckError();
316 }
317 #ifndef GL_VERSION_ES_CM_1_0
318 if (r_texture_lod->integer && r_config.lod_bias) {
319 glTexEnvf(GL_TEXTURE_FILTER_CONTROL_EXT, GL_TEXTURE_LOD_BIAS_EXT, r_texture_lod->value);
320 R_CheckError();
321 }
322 #endif
323 } else {
324 if (r_config.anisotropic) {
325 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1);
326 R_CheckError();
327 }
328 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, r_config.gl_filter_max);
329 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, r_config.gl_filter_max);
330 }
331 R_CheckError();
332
333 if (clamp) {
334 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
335 R_CheckError();
336 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
337 R_CheckError();
338 }
339
340 glTexImage2D(GL_TEXTURE_2D, 0, texFormat, scaledWidth, scaledHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, scaled ? scaled : data);
341 R_CheckError();
342
343 if (scaled)
344 Mem_Free(scaled);
345 }
346
347 /**
348 * @brief Applies blurring to a texture
349 */
R_SoftenTexture(byte * in,int width,int height,int bpp)350 void R_SoftenTexture (byte* in, int width, int height, int bpp)
351 {
352 int i, j, k;
353 const int size = width * height * bpp;
354
355 /* soften into a copy of the original image, as in-place would be incorrect */
356 byte* const out = Mem_PoolAllocTypeN(byte, size, vid_imagePool);
357 if (!out)
358 Com_Error(ERR_FATAL, "R_SoftenTexture: failed on allocation of %i bytes", width * height * bpp);
359
360 memcpy(out, in, size);
361
362 for (i = 1; i < height - 1; i++) {
363 for (j = 1; j < width - 1; j++) {
364 const byte* src = in + ((i * width) + j) * bpp; /* current input pixel */
365
366 const byte* u = (src - (width * bpp)); /* and it's neighbors */
367 const byte* d = (src + (width * bpp));
368 const byte* l = (src - (1 * bpp));
369 const byte* r = (src + (1 * bpp));
370
371 byte* dest = out + ((i * width) + j) * bpp; /* current output pixel */
372
373 for (k = 0; k < bpp; k++)
374 dest[k] = (u[k] + d[k] + l[k] + r[k]) / 4;
375 }
376 }
377
378 /* copy the softened image over the input image, and free it */
379 memcpy(in, out, size);
380 Mem_Free(out);
381 }
382
R_UploadAlpha(const image_t * image,const byte * alphaData)383 void R_UploadAlpha (const image_t* image, const byte* alphaData)
384 {
385 R_BindTexture(image->texnum);
386
387 glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, image->width, image->height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, alphaData);
388 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, r_config.gl_filter_max);
389 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, r_config.gl_filter_max);
390 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
391 if (image->type == it_wrappic)
392 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
393 }
394
R_DeleteImage(image_t * image)395 static inline void R_DeleteImage (image_t* image)
396 {
397 const unsigned int hash = Com_HashKey(image->name, MAX_IMAGEHASH);
398 for (image_t** anchor = &imageHash[hash]; *anchor; anchor = &(*anchor)->hash_next) {
399 if (Q_streq((*anchor)->name, image->name)) {
400 HASH_Delete(anchor);
401 break;
402 }
403 }
404
405 /* free it */
406 glDeleteTextures(1, &image->texnum);
407 R_CheckError();
408
409 OBJZERO(*image);
410 }
411
R_GetImage(const char * name)412 image_t* R_GetImage (const char* name)
413 {
414 image_t* image;
415 const unsigned hash = Com_HashKey(name, MAX_IMAGEHASH);
416
417 /* look for it */
418 for (image = imageHash[hash]; image; image = image->hash_next)
419 if (Q_streq(name, image->name))
420 return image;
421
422 return nullptr;
423 }
424
425 /**
426 * @brief Creates a new image from RGBA data. Stores it in the gltextures array
427 * and also uploads it.
428 * @note This is also used as an entry point for the generated r_noTexture
429 * @param[in] name The name of the newly created image
430 * @param[in] pic The RGBA data of the image
431 * @param[in] width The width of the image (power of two, please)
432 * @param[in] height The height of the image (power of two, please)
433 * @param[in] type The image type @sa imagetype_t
434 */
R_LoadImageData(const char * name,const byte * pic,int width,int height,imagetype_t type)435 image_t* R_LoadImageData (const char* name, const byte* pic, int width, int height, imagetype_t type)
436 {
437 image_t* image;
438 imageArray_t* images;
439 int i;
440 size_t len;
441 unsigned hash;
442
443 len = strlen(name);
444 if (len >= sizeof(image->name))
445 Com_Error(ERR_DROP, "R_LoadImageData: \"%s\" is too long", name);
446 if (len == 0)
447 Com_Error(ERR_DROP, "R_LoadImageData: name is empty");
448
449 /* look for it */
450 image = R_GetImage(name);
451 if (image) {
452 assert(image->texnum);
453 Com_Printf("R_LoadImageData: image '%s' is already uploaded\n", name);
454 return image;
455 }
456
457 /* find a free image_t, using a wicked for()-loop */
458 FOR_EACH_IMAGE(i, image, images) {
459 if (!image->texnum)
460 break;
461 }
462
463 if (i == r_numImages) {
464 /* Did we run out of space in the current array? Add a new array chunk */
465 if (r_numImages % IMAGE_ARRAY_SIZE == 0) {
466 for (images = &r_images; images->next;)
467 images = images->next;
468 images->next = Mem_AllocType(imageArray_t);
469 image = &images->next->images[0];
470 }
471 r_numImages++;
472 }
473 OBJZERO(*image);
474 image->material = defaultMaterial;
475 image->has_alpha = false;
476 image->type = type;
477 image->width = width;
478 image->height = height;
479
480 /** @todo Instead of this hack, unit tests' build should link to the dummy GL driver */
481 #ifdef COMPILE_UNITTESTS
482 {
483 static int texnum = 0;
484 image->texnum = ++texnum;
485 }
486 #else
487 glGenTextures(1, &image->texnum);
488 #endif
489
490 Q_strncpyz(image->name, name, sizeof(image->name));
491 /* drop extension */
492 if (len >= 4 && image->name[len - 4] == '.') {
493 image->name[len - 4] = '\0';
494 Com_Printf("Image with extension: '%s'\n", name);
495 }
496
497 hash = Com_HashKey(image->name, MAX_IMAGEHASH);
498 HASH_Add(imageHash, image, hash);
499
500 if (pic) {
501 R_BindTexture(image->texnum);
502 R_UploadTexture((const unsigned *) pic, width, height, image);
503 }
504 return image;
505 }
506
R_RenderToTexture(const char * name,int x,int y,int w,int h)507 image_t* R_RenderToTexture (const char* name, int x, int y, int w, int h)
508 {
509 image_t* img = R_GetImage(name);
510 const bool dimensionDiffer = img != nullptr && img->width != w && img->height != h;
511 if (img == nullptr || dimensionDiffer) {
512 if (dimensionDiffer) {
513 R_DeleteImage(img);
514 }
515 byte* const buf = Mem_PoolAllocTypeN(byte, w * h * 4, vid_imagePool);
516 img = R_LoadImageData(name, buf, w, h, it_effect);
517 Mem_Free(buf);
518 }
519
520 glFlush();
521 #ifndef GL_VERSION_ES_CM_1_0
522 glReadBuffer(GL_BACK);
523 #endif
524 R_SelectTexture(&texunit_diffuse);
525 R_BindTexture(img->texnum);
526 glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, x, y, w, h, 0);
527
528 return img;
529 }
530
531 /**
532 * @brief Set up new image type and change texturemapping paramenters accordingly
533 * @note Mipmapping mode is not updated
534 */
R_ChangeImageType(image_t * img,imagetype_t type)535 static void R_ChangeImageType(image_t* img, imagetype_t type)
536 {
537 if (!img)
538 return;
539
540 img->type = type;
541 R_BindTexture(img->texnum);
542
543 if (R_IsClampedImageType(type)) {
544 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
545 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
546 } else {
547 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
548 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
549 }
550 }
551
552 /**
553 * @brief Finds or loads the given image
554 * @sa R_RegisterImage
555 * @param[in] pname Image name Path relative to the game dir (e.g. textures/tex_common/nodraw)
556 * @param[in] type The type of the image. This has influence on image filters and texture
557 * parameters when uploading the image data
558 * @note the image name has to be at least 5 chars long
559 * @sa R_LoadTGA
560 * @sa R_LoadJPG
561 * @sa R_LoadPNG
562 */
R_FindImage(const char * pname,imagetype_t type)563 image_t* R_FindImage (const char* pname, imagetype_t type)
564 {
565 char lname[MAX_QPATH];
566 image_t* image;
567 SDL_Surface* surf;
568
569 if (!pname || !pname[0])
570 Com_Error(ERR_FATAL, "R_FindImage: nullptr name");
571
572 /* drop extension */
573 Com_StripExtension(pname, lname, sizeof(lname));
574
575 image = R_GetImage(lname);
576 if (image) {
577 /* Warn if game tries to use same image with different texture mapping modes */
578 if (R_IsClampedImageType(image->type) != R_IsClampedImageType(type)) { /** @todo should also check the mipmapping */
579 Com_Printf("Warning: inconsistent usage of image %s (%i,%i)\n", image->name, image->type, type);
580 R_ChangeImageType(image, type);
581 R_ChangeImageType(image->normalmap, type);
582 R_ChangeImageType(image->roughnessmap, type);
583 R_ChangeImageType(image->specularmap, type);
584 R_ChangeImageType(image->glowmap, type);
585 }
586 return image;
587 }
588
589 if ((surf = Img_LoadImage(lname))) {
590 image = R_LoadImageData(lname, (byte*)surf->pixels, surf->w, surf->h, type);
591 SDL_FreeSurface(surf);
592 if (image->type == it_world) {
593 image->normalmap = R_FindImage(va("%s_nm", image->name), it_normalmap);
594 if (image->normalmap == r_noTexture)
595 image->normalmap = nullptr;
596 }
597 if (image->type != it_glowmap) {
598 image->glowmap = R_FindImage(va("%s_gm", image->name), it_glowmap);
599 if (image->glowmap == r_noTexture)
600 image->glowmap = nullptr;
601 }
602 if (image->type != it_normalmap) {
603 image->normalmap = R_FindImage(va("%s_nm", image->name), it_normalmap);
604 if (image->normalmap == r_noTexture)
605 image->normalmap = nullptr;
606 }
607 if (image->type != it_specularmap) {
608 image->specularmap = R_FindImage(va("%s_sm", image->name), it_specularmap);
609 if (image->specularmap == r_noTexture)
610 image->specularmap = nullptr;
611 }
612 if (image->type != it_roughnessmap) {
613 image->roughnessmap = R_FindImage(va("%s_rm", image->name), it_roughnessmap);
614 if (image->roughnessmap == r_noTexture)
615 image->roughnessmap = nullptr;
616 }
617 }
618
619 /* no fitting texture found */
620 if (!image)
621 image = r_noTexture;
622
623 return image;
624 }
625
626 /**
627 * @brief Searches for an image in the image array
628 * @param[in] name The name of the image relative to pics/
629 * @note name may not be null and has to be longer than 4 chars
630 * @return nullptr on error or image_t pointer on success
631 * @sa R_FindImage
632 */
R_FindPics(const char * name)633 const image_t* R_FindPics (const char* name)
634 {
635 const image_t* image = R_FindImage(va("pics/%s", name), it_pic);
636 if (image == r_noTexture)
637 return nullptr;
638 return image;
639 }
640
R_ImageExists(const char * pname,...)641 bool R_ImageExists (const char* pname, ...)
642 {
643 char const* const* const types = Img_GetImageTypes();
644 int i;
645 char filename[MAX_QPATH];
646 va_list ap;
647
648 va_start(ap, pname);
649 Q_vsnprintf(filename, sizeof(filename), pname, ap);
650 va_end(ap);
651
652 for (i = 0; types[i]; i++) {
653 if (FS_CheckFile("%s.%s", filename, types[i]) != -1)
654 return true;
655 }
656 return false;
657 }
658
659 /**
660 * @brief Returns an index of the image pointer in the r_images linked list, as if r_images would be a plain contiguous array
661 * @param imagePtr The image pointer
662 */
R_GetImageIndex(image_t * imagePtr)663 int R_GetImageIndex (image_t* imagePtr)
664 {
665 imageArray_t* images;
666
667 for (images = &r_images; images; images = images->next) {
668 if (imagePtr >= &images->images[0] && imagePtr <= &images->images[IMAGE_ARRAY_SIZE - 1])
669 return imagePtr - &images->images[0];
670 }
671
672 return -1;
673 }
674
675 /**
676 * @brief Returns an image pointer from the r_images linked list, as if r_images would be a plain contiguous array
677 * @param i The image index inside r_images
678 */
R_GetImageAtIndex(int i)679 image_t* R_GetImageAtIndex (int i)
680 {
681 imageArray_t* images;
682
683 if (i >= r_numImages || i < 0)
684 return nullptr;
685
686 for (images = &r_images; i >= IMAGE_ARRAY_SIZE; i -= IMAGE_ARRAY_SIZE)
687 images = images->next;
688
689 return &images->images[i];
690 }
691
692 /**
693 * @brief Free the image and its assigned maps (roughness, normal, specular, glow - if there are any)
694 * @param image The image that should be freed
695 */
R_FreeImage(image_t * image)696 void R_FreeImage (image_t* image)
697 {
698 /* free image slot */
699 if (!image || !image->texnum)
700 return;
701
702 /* also free the several maps if they are loaded */
703 if (image->normalmap)
704 R_DeleteImage(image->normalmap);
705 if (image->glowmap)
706 R_DeleteImage(image->glowmap);
707 if (image->roughnessmap)
708 R_DeleteImage(image->roughnessmap);
709 if (image->specularmap)
710 R_DeleteImage(image->specularmap);
711 R_DeleteImage(image);
712 }
713
714 /**
715 * @brief Any image that is a mesh or world texture will be removed here
716 * @sa R_ShutdownImages
717 */
R_FreeWorldImages(void)718 void R_FreeWorldImages (void)
719 {
720 int i;
721 image_t* image;
722 imageArray_t* images;
723
724 R_CheckError();
725 /* Wicked for()-loop (tm) */
726 FOR_EACH_IMAGE(i, image, images) {
727 if (image->type < it_world)
728 continue; /* keep them */
729
730 /* free it */
731 R_FreeImage(image);
732 }
733 }
734
R_InitImages(void)735 void R_InitImages (void)
736 {
737 int i;
738
739 r_numImages = 0;
740
741 for (i = 0; i < MAX_ENVMAPTEXTURES; i++) {
742 r_envmaptextures[i] = R_FindImage(va("pics/envmaps/envmap_%i", i), it_effect);
743 if (r_envmaptextures[i] == r_noTexture)
744 Com_Error(ERR_FATAL, "Could not load environment map %i", i);
745 }
746
747 for (i = 0; i < NUM_FLARETEXTURES; i++) {
748 r_flaretextures[i] = R_FindImage(va("pics/flares/flare_%i", i), it_effect);
749 if (r_flaretextures[i] == r_noTexture)
750 Com_Error(ERR_FATAL, "Could not load lens flare %i", i);
751 }
752 }
753
754 /**
755 * @sa R_FreeWorldImages
756 */
R_ShutdownImages(void)757 void R_ShutdownImages (void)
758 {
759 int i;
760 image_t* image;
761 imageArray_t* images;
762
763 R_CheckError();
764 /* Wicked for()-loop (tm) */
765 FOR_EACH_IMAGE(i, image, images) {
766 if (!image->texnum)
767 continue; /* free image_t slot */
768 R_DeleteImage(image);
769 }
770 OBJZERO(imageHash);
771 OBJZERO(r_images);
772 r_numImages = 0;
773 }
774
R_ReloadImageData(image_t * image)775 static void R_ReloadImageData (image_t* image)
776 {
777 SDL_Surface* surf;
778 if (image == r_noTexture || !image || !image->texnum)
779 return;
780
781 surf = Img_LoadImage(image->name);
782 if (!surf) {
783 Com_Printf("R_ReloadImageData: unable to load image %s\n", image->name);
784 surf = SDL_CreateRGBSurface(0, image->width, image->height, 32,
785 0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff);
786 SDL_FillRect(surf, nullptr, 0x99ff33ff); /* A random color */
787 }
788 glGenTextures(1, &image->texnum);
789 R_BindTexture(image->texnum);
790 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
791 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
792 R_UploadTexture((unsigned *)surf->pixels, surf->w, surf->h, image);
793 SDL_FreeSurface(surf);
794 }
795
R_ReloadImages(void)796 void R_ReloadImages (void)
797 {
798 int i;
799 image_t* image;
800 imageArray_t* images;
801
802 R_CheckError();
803 glEnable(GL_TEXTURE_2D);
804 FOR_EACH_IMAGE(i, image, images) {
805 if (i % 5 == 0) {
806 SCR_DrawLoadingScreen(false, i * 100 / r_numImages);
807 }
808 R_ReloadImageData(image);
809 R_ReloadImageData(image->normalmap);
810 R_ReloadImageData(image->glowmap);
811 R_ReloadImageData(image->specularmap);
812 R_ReloadImageData(image->roughnessmap);
813 }
814 }
815
816 typedef struct {
817 const char* name;
818 int minimize, maximize;
819 } glTextureMode_t;
820
821 static const glTextureMode_t gl_texture_modes[] = {
822 {"GL_NEAREST", GL_NEAREST, GL_NEAREST}, /* no filtering, no mipmaps */
823 {"GL_LINEAR", GL_LINEAR, GL_LINEAR}, /* bilinear filtering, no mipmaps */
824 {"GL_NEAREST_MIPMAP_NEAREST", GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST}, /* no filtering, mipmaps */
825 {"GL_LINEAR_MIPMAP_NEAREST", GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR}, /* bilinear filtering, mipmaps */
826 {"GL_NEAREST_MIPMAP_LINEAR", GL_NEAREST_MIPMAP_LINEAR, GL_NEAREST}, /* trilinear filtering, mipmaps */
827 {"GL_LINEAR_MIPMAP_LINEAR", GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR} /* bilinear filtering, trilinear filtering, mipmaps */
828 };
829
R_TextureMode(const char * string)830 void R_TextureMode (const char* string)
831 {
832 int i;
833 image_t* image;
834 imageArray_t* images;
835 const size_t size = lengthof(gl_texture_modes);
836 const glTextureMode_t* mode;
837
838 mode = nullptr;
839 for (i = 0; i < size; i++) {
840 mode = &gl_texture_modes[i];
841 if (!Q_strcasecmp(mode->name, string))
842 break;
843 }
844
845 if (mode == nullptr) {
846 Com_Printf("bad filter name\n");
847 return;
848 }
849
850 r_config.gl_filter_min = mode->minimize;
851 r_config.gl_filter_max = mode->maximize;
852
853 /* Wicked for()-loop (tm) */
854 FOR_EACH_IMAGE(i, image, images) {
855 if (image->type == it_pic || image->type == it_worldrelated || image->type == it_chars)
856 continue; /* no mipmaps */
857
858 R_BindTexture(image->texnum);
859 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mode->minimize);
860 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mode->maximize);
861 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, r_config.maxAnisotropic);
862 R_CheckError();
863 }
864 }
865
866 #ifdef GL_VERSION_ES_CM_1_0
R_TextureSolidMode(const char * string)867 void R_TextureSolidMode (const char* string)
868 {
869 }
R_TextureAlphaMode(const char * string)870 void R_TextureAlphaMode (const char* string)
871 {
872 }
873 #else
874 typedef struct {
875 const char* name;
876 int mode;
877 } gltmode_t;
878
879 static const gltmode_t gl_alpha_modes[] = {
880 {"GL_RGBA", GL_RGBA},
881 {"GL_RGBA8", GL_RGBA8},
882 {"GL_RGB5_A1", GL_RGB5_A1},
883 {"GL_RGBA4", GL_RGBA4},
884 {"GL_RGBA2", GL_RGBA2},
885 {"GL_LUMINANCE4_ALPHA4", GL_LUMINANCE4_ALPHA4},
886 {"GL_LUMINANCE6_ALPHA2", GL_LUMINANCE6_ALPHA2},
887 {"GL_LUMINANCE8_ALPHA8", GL_LUMINANCE8_ALPHA8},
888 {"GL_LUMINANCE12_ALPHA4", GL_LUMINANCE12_ALPHA4},
889 {"GL_LUMINANCE12_ALPHA12", GL_LUMINANCE12_ALPHA12},
890 {"GL_LUMINANCE16_ALPHA16", GL_LUMINANCE16_ALPHA16}
891 };
892
893
R_TextureAlphaMode(const char * string)894 void R_TextureAlphaMode (const char* string)
895 {
896 int i;
897 const size_t size = lengthof(gl_alpha_modes);
898
899 for (i = 0; i < size; i++) {
900 const gltmode_t* mode = &gl_alpha_modes[i];
901 if (!Q_strcasecmp(mode->name, string)) {
902 r_config.gl_alpha_format = mode->mode;
903 return;
904 }
905 }
906
907 Com_Printf("bad alpha texture mode name (%s)\n", string);
908 }
909
910 static const gltmode_t gl_solid_modes[] = {
911 {"GL_RGB", GL_RGB},
912 {"GL_RGB8", GL_RGB8},
913 {"GL_RGB5", GL_RGB5},
914 {"GL_RGB4", GL_RGB4},
915 {"GL_R3_G3_B2", GL_R3_G3_B2},
916 {"GL_RGB2", GL_RGB2_EXT},
917 {"GL_RGB4", GL_RGB4_EXT},
918 {"GL_RGB5", GL_RGB5_EXT},
919 {"GL_RGB8", GL_RGB8_EXT},
920 {"GL_RGB10", GL_RGB10_EXT},
921 {"GL_RGB12", GL_RGB12_EXT},
922 {"GL_RGB16", GL_RGB16_EXT},
923 {"GL_LUMINANCE", GL_LUMINANCE},
924 {"GL_LUMINANCE4", GL_LUMINANCE4},
925 {"GL_LUMINANCE8", GL_LUMINANCE8},
926 {"GL_LUMINANCE12", GL_LUMINANCE12},
927 {"GL_LUMINANCE16", GL_LUMINANCE16}
928 };
929
R_TextureSolidMode(const char * string)930 void R_TextureSolidMode (const char* string)
931 {
932 int i;
933 const size_t size = lengthof(gl_solid_modes);
934
935 for (i = 0; i < size; i++) {
936 const gltmode_t* mode = &gl_solid_modes[i];
937 if (!Q_strcasecmp(mode->name, string)) {
938 r_config.gl_solid_format = mode->mode;
939 return;
940 }
941 }
942
943 Com_Printf("bad solid texture mode name (%s)\n", string);
944 }
945 #endif
946