952ea1f881c4deaf802281b4a961844eb8714b4b
[xonotic/darkplaces.git] / gl_textures.c
1 #include "quakedef.h"
2
3 cvar_t          gl_max_size = {"gl_max_size", "1024"};
4 cvar_t          gl_picmip = {"gl_picmip", "0"};
5 cvar_t          gl_lerpimages = {"gl_lerpimages", "1"};
6 cvar_t          r_upload = {"r_upload", "1"};
7
8 int             gl_filter_min = GL_LINEAR_MIPMAP_LINEAR; //NEAREST;
9 int             gl_filter_max = GL_LINEAR;
10
11
12 int             texels;
13
14 // 4096x4096
15 #define MAXMIPS 12
16
17 typedef struct
18 {
19         int             texnum;
20         int             totaltexels;
21         byte    *texels[MAXMIPS];
22         unsigned short texelsize[MAXMIPS][2];
23         char    identifier[64];
24         short   width, height;
25 // LordHavoc: CRC to identify cache mismatchs
26         unsigned short crc;
27         char    mipmap;
28         char    alpha;
29         char    bytesperpixel;
30         char    lerped; // whether this texture was uploaded with or without interpolation
31 } gltexture_t;
32
33 #define MAX_GLTEXTURES  4096
34 gltexture_t     gltextures[MAX_GLTEXTURES];
35 int                     numgltextures;
36
37 typedef struct
38 {
39         char *name;
40         int     minimize, maximize;
41 } glmode_t;
42
43 glmode_t modes[] = {
44         {"GL_NEAREST", GL_NEAREST, GL_NEAREST},
45         {"GL_LINEAR", GL_LINEAR, GL_LINEAR},
46         {"GL_NEAREST_MIPMAP_NEAREST", GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST},
47         {"GL_LINEAR_MIPMAP_NEAREST", GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR},
48         {"GL_NEAREST_MIPMAP_LINEAR", GL_NEAREST_MIPMAP_LINEAR, GL_NEAREST},
49         {"GL_LINEAR_MIPMAP_LINEAR", GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR}
50 };
51
52 /*
53 ===============
54 Draw_TextureMode_f
55 ===============
56 */
57 void Draw_TextureMode_f (void)
58 {
59         int             i;
60         gltexture_t     *glt;
61
62         if (Cmd_Argc() == 1)
63         {
64                 for (i=0 ; i< 6 ; i++)
65                         if (gl_filter_min == modes[i].minimize)
66                         {
67                                 Con_Printf ("%s\n", modes[i].name);
68                                 return;
69                         }
70                 Con_Printf ("current filter is unknown???\n");
71                 return;
72         }
73
74         for (i=0 ; i< 6 ; i++)
75         {
76                 if (!Q_strcasecmp (modes[i].name, Cmd_Argv(1) ) )
77                         break;
78         }
79         if (i == 6)
80         {
81                 Con_Printf ("bad filter name\n");
82                 return;
83         }
84
85         gl_filter_min = modes[i].minimize;
86         gl_filter_max = modes[i].maximize;
87
88         if (!r_upload.value)
89                 return;
90         // change all the existing mipmap texture objects
91         for (i=0, glt=gltextures ; i<numgltextures ; i++, glt++)
92         {
93                 if (glt->mipmap)
94                 {
95                         glBindTexture(GL_TEXTURE_2D, glt->texnum);
96                         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min);
97                         glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max);
98                 }
99         }
100 }
101
102 void GL_TextureStats_Print(char *name, int total, int crc, int mip, int alpha)
103 {
104         char n[64];
105         int c = 0;
106         if (!name[0])
107                 name = "<unnamed>";
108         while (name[c] && c < 28)
109                 n[c++] = name[c];
110         while (c < 28)
111                 n[c++] = ' ';
112         n[c] = 0;
113         Con_Printf("%s %5i %04X %s %s\n", n, total, crc, mip ? "yes" : "no ", alpha ? "yes  " : "no   ");
114 }
115
116 void GL_TextureStats_f(void)
117 {
118         int i, t = 0;
119         gltexture_t *glt;
120         Con_Printf("name                        kbytes crc  mip alpha\n");
121         for (i = 0, glt = gltextures;i < numgltextures;i++, glt++)
122         {
123                 GL_TextureStats_Print(glt->identifier, ((glt->totaltexels * 4) + 512) >> 10, glt->crc, glt->mipmap, glt->alpha);
124                 t += glt->totaltexels;
125         }
126         Con_Printf("%i textures, totalling %.3f mbytes\n", numgltextures, t / 1024.0 / 1024.0);
127 }
128
129 void GL_TextureStats_PrintTotal(void)
130 {
131         int i, t = 0;
132         gltexture_t *glt;
133         for (i = 0, glt = gltextures;i < numgltextures;i++, glt++)
134                 t += glt->totaltexels;
135         Con_Printf("%i textures, totalling %.3f mbytes\n", numgltextures, t / 1024.0 / 1024.0);
136 }
137
138 extern int buildnumber;
139
140 char engineversion[40];
141
142 void GL_UploadTexture (gltexture_t *glt);
143 void gl_textures_start()
144 {
145         int i;
146         gltexture_t *glt;
147         for (i=0, glt=gltextures ; i<numgltextures ; i++, glt++)
148                 GL_UploadTexture(glt);
149 }
150
151 void gl_textures_shutdown()
152 {
153 }
154
155 void GL_Textures_Init (void)
156 {
157         Cmd_AddCommand("r_texturestats", GL_TextureStats_f);
158         Cvar_RegisterVariable (&gl_max_size);
159         Cvar_RegisterVariable (&gl_picmip);
160         Cvar_RegisterVariable (&gl_lerpimages);
161         Cvar_RegisterVariable (&r_upload);
162 #ifdef NORENDER
163         r_upload.value = 0;
164 #endif
165
166         // 3dfx can only handle 256 wide textures
167         if (!Q_strncasecmp ((char *)gl_renderer, "3dfx",4) || strstr((char *)gl_renderer, "Glide"))
168                 Cvar_Set ("gl_max_size", "256");
169
170         Cmd_AddCommand ("gl_texturemode", &Draw_TextureMode_f);
171
172         R_RegisterModule("GL_Textures", gl_textures_start, gl_textures_shutdown);
173 }
174
175 /*
176 ================
177 GL_FindTexture
178 ================
179 */
180 int GL_FindTexture (char *identifier)
181 {
182         int             i;
183         gltexture_t     *glt;
184
185         for (i=0, glt=gltextures ; i<numgltextures ; i++, glt++)
186         {
187                 if (!strcmp (identifier, glt->identifier))
188                         return gltextures[i].texnum;
189         }
190
191         return -1;
192 }
193
194 void GL_ResampleTextureLerpLine (byte *in, byte *out, int inwidth, int outwidth)
195 {
196         int             j, xi, oldx = 0, f, fstep, l1, l2, endx;
197         fstep = (int) (inwidth*65536.0f/outwidth);
198         endx = (inwidth-1);
199         for (j = 0,f = 0;j < outwidth;j++, f += fstep)
200         {
201                 xi = (int) f >> 16;
202                 if (xi != oldx)
203                 {
204                         in += (xi - oldx) * 4;
205                         oldx = xi;
206                 }
207                 if (xi < endx)
208                 {
209                         l2 = f & 0xFFFF;
210                         l1 = 0x10000 - l2;
211                         *out++ = (byte) ((in[0] * l1 + in[4] * l2) >> 16);
212                         *out++ = (byte) ((in[1] * l1 + in[5] * l2) >> 16);
213                         *out++ = (byte) ((in[2] * l1 + in[6] * l2) >> 16);
214                         *out++ = (byte) ((in[3] * l1 + in[7] * l2) >> 16);
215                 }
216                 else // last pixel of the line has no pixel to lerp to
217                 {
218                         *out++ = in[0];
219                         *out++ = in[1];
220                         *out++ = in[2];
221                         *out++ = in[3];
222                 }
223         }
224 }
225
226 /*
227 ================
228 GL_ResampleTexture
229 ================
230 */
231 void GL_ResampleTexture (void *indata, int inwidth, int inheight, void *outdata,  int outwidth, int outheight)
232 {
233         if (gl_lerpimages.value)
234         {
235                 int             i, j, yi, oldy, f, fstep, l1, l2, endy = (inheight-1);
236                 byte    *inrow, *out, *row1, *row2;
237                 out = outdata;
238                 fstep = (int) (inheight*65536.0f/outheight);
239
240                 row1 = qmalloc(outwidth*4);
241                 row2 = qmalloc(outwidth*4);
242                 inrow = indata;
243                 oldy = 0;
244                 GL_ResampleTextureLerpLine (inrow, row1, inwidth, outwidth);
245                 GL_ResampleTextureLerpLine (inrow + inwidth*4, row2, inwidth, outwidth);
246                 for (i = 0, f = 0;i < outheight;i++,f += fstep)
247                 {
248                         yi = f >> 16;
249                         if (yi != oldy)
250                         {
251                                 inrow = (byte *)indata + inwidth*4*yi;
252                                 if (yi == oldy+1)
253                                         memcpy(row1, row2, outwidth*4);
254                                 else
255                                         GL_ResampleTextureLerpLine (inrow, row1, inwidth, outwidth);
256                                 if (yi < endy)
257                                         GL_ResampleTextureLerpLine (inrow + inwidth*4, row2, inwidth, outwidth);
258                                 else
259                                         memcpy(row2, row1, outwidth*4);
260                                 oldy = yi;
261                         }
262                         if (yi < endy)
263                         {
264                                 l2 = f & 0xFFFF;
265                                 l1 = 0x10000 - l2;
266                                 for (j = 0;j < outwidth;j++)
267                                 {
268                                         *out++ = (byte) ((*row1++ * l1 + *row2++ * l2) >> 16);
269                                         *out++ = (byte) ((*row1++ * l1 + *row2++ * l2) >> 16);
270                                         *out++ = (byte) ((*row1++ * l1 + *row2++ * l2) >> 16);
271                                         *out++ = (byte) ((*row1++ * l1 + *row2++ * l2) >> 16);
272                                 }
273                                 row1 -= outwidth*4;
274                                 row2 -= outwidth*4;
275                         }
276                         else // last line has no pixels to lerp to
277                         {
278                                 for (j = 0;j < outwidth;j++)
279                                 {
280                                         *out++ = *row1++;
281                                         *out++ = *row1++;
282                                         *out++ = *row1++;
283                                         *out++ = *row1++;
284                                 }
285                                 row1 -= outwidth*4;
286                         }
287                 }
288                 qfree(row1);
289                 qfree(row2);
290         }
291         else
292         {
293                 int             i, j;
294                 unsigned        frac, fracstep;
295                 byte    *inrow, *out, *inpix;
296                 out = outdata;
297
298                 fracstep = inwidth*0x10000/outwidth;
299                 for (i=0 ; i<outheight ; i++)
300                 {
301                         inrow = (byte *)indata + inwidth*(i*inheight/outheight)*4;
302                         frac = fracstep >> 1;
303                         for (j=0 ; j<outwidth ; j+=4)
304                         {
305                                 inpix = inrow + ((frac >> 14) & ~3);*out++ = qgamma[*inpix++];*out++ = qgamma[*inpix++];*out++ = qgamma[*inpix++];*out++ =       *inpix++ ;frac += fracstep;
306                                 inpix = inrow + ((frac >> 14) & ~3);*out++ = qgamma[*inpix++];*out++ = qgamma[*inpix++];*out++ = qgamma[*inpix++];*out++ =       *inpix++ ;frac += fracstep;
307                                 inpix = inrow + ((frac >> 14) & ~3);*out++ = qgamma[*inpix++];*out++ = qgamma[*inpix++];*out++ = qgamma[*inpix++];*out++ =       *inpix++ ;frac += fracstep;
308                                 inpix = inrow + ((frac >> 14) & ~3);*out++ = qgamma[*inpix++];*out++ = qgamma[*inpix++];*out++ = qgamma[*inpix++];*out++ =       *inpix++ ;frac += fracstep;
309                         }
310                 }
311         }
312 }
313
314 void GL_FreeTexels(gltexture_t *glt)
315 {
316         if (glt->texels[0])
317                 qfree(glt->texels[0]);
318         glt->texels[0] = 0;
319 }
320
321 void GL_AllocTexels(gltexture_t *glt, int width, int height, int mipmapped)
322 {
323         int i, w, h, size, done;
324         if (glt->texels[0])
325                 GL_FreeTexels(glt);
326         glt->texelsize[0][0] = width;
327         glt->texelsize[0][1] = height;
328         if (mipmapped)
329         {
330                 size = 0;
331                 w = width;h = height;
332                 i = 0;
333                 done = false;
334                 for (i = 0;i < MAXMIPS;i++)
335                 {
336                         glt->texelsize[i][0] = w;
337                         glt->texelsize[i][1] = h;
338                         glt->texels[i] = (void *)size;
339                         size += w*h*4;
340                         if (w > 1)
341                         {
342                                 w >>= 1;
343                                 if (h > 1)
344                                         h >>= 1;
345                         }
346                         else if (h > 1)
347                                 h >>= 1;
348                         else
349                         {
350                                 i++;
351                                 break;
352                         }
353                 }
354                 glt->totaltexels = size;
355                 while (i < MAXMIPS)
356                         glt->texels[i++] = NULL;
357                 glt->texels[0] = qmalloc(size);
358                 for (i = 1;i < MAXMIPS && glt->texels[i];i++)
359                         glt->texels[i] += (int) glt->texels[0];
360         }
361         else
362         {
363                 size = width*height*4;
364                 glt->totaltexels = size;
365                 glt->texels[0] = qmalloc(size);
366                 for (i = 1;i < MAXMIPS;i++)
367                         glt->texels[i] = NULL;
368         }
369         if (!glt->texels[0])
370                 Sys_Error("GL_AllocTexels: out of memory\n");
371 }
372
373 // in can be the same as out
374 void GL_MipReduce(byte *in, byte *out, int width, int height, int destwidth, int destheight)
375 {
376         int x, y, width2, height2, nextrow;
377         if (width > destwidth)
378         {
379                 if (height > destheight)
380                 {
381                         // reduce both
382                         width2 = width >> 1;
383                         height2 = height >> 1;
384                         nextrow = width << 2;
385                         for (y = 0;y < height2;y++)
386                         {
387                                 for (x = 0;x < width2;x++)
388                                 {
389                                         out[0] = (byte) ((in[0] + in[4] + in[nextrow  ] + in[nextrow+4]) >> 2);
390                                         out[1] = (byte) ((in[1] + in[5] + in[nextrow+1] + in[nextrow+5]) >> 2);
391                                         out[2] = (byte) ((in[2] + in[6] + in[nextrow+2] + in[nextrow+6]) >> 2);
392                                         out[3] = (byte) ((in[3] + in[7] + in[nextrow+3] + in[nextrow+7]) >> 2);
393                                         out += 4;
394                                         in += 8;
395                                 }
396                                 in += nextrow; // skip a line
397                         }
398                 }
399                 else
400                 {
401                         // reduce width
402                         width2 = width >> 1;
403                         for (y = 0;y < height;y++)
404                         {
405                                 for (x = 0;x < width2;x++)
406                                 {
407                                         out[0] = (byte) ((in[0] + in[4]) >> 1);
408                                         out[1] = (byte) ((in[1] + in[5]) >> 1);
409                                         out[2] = (byte) ((in[2] + in[6]) >> 1);
410                                         out[3] = (byte) ((in[3] + in[7]) >> 1);
411                                         out += 4;
412                                         in += 8;
413                                 }
414                         }
415                 }
416         }
417         else
418         {
419                 if (height > destheight)
420                 {
421                         // reduce height
422                         height2 = height >> 1;
423                         nextrow = width << 2;
424                         for (y = 0;y < height2;y++)
425                         {
426                                 for (x = 0;x < width;x++)
427                                 {
428                                         out[0] = (byte) ((in[0] + in[nextrow  ]) >> 1);
429                                         out[1] = (byte) ((in[1] + in[nextrow+1]) >> 1);
430                                         out[2] = (byte) ((in[2] + in[nextrow+2]) >> 1);
431                                         out[3] = (byte) ((in[3] + in[nextrow+3]) >> 1);
432                                         out += 4;
433                                         in += 4;
434                                 }
435                                 in += nextrow; // skip a line
436                         }
437                 }
438                 else
439                         Sys_Error("GL_MipReduce: desired size already achieved\n");
440         }
441 }
442
443 void GL_UploadTexture (gltexture_t *glt)
444 {
445         int mip, width, height;
446         if (!r_upload.value)
447                 return;
448         glBindTexture(GL_TEXTURE_2D, glt->texnum);
449         width = glt->width;
450         height = glt->height;
451         for (mip = 0;mip < MAXMIPS && glt->texels[mip];mip++)
452                 glTexImage2D(GL_TEXTURE_2D, mip, glt->alpha ? 4 : 3, glt->texelsize[mip][0], glt->texelsize[mip][1], 0, GL_RGBA, GL_UNSIGNED_BYTE, glt->texels[mip]);
453         if (glt->mipmap)
454         {
455                 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min);
456                 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max);
457         }
458         else
459         {
460                 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_max);
461                 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max);
462         }
463         glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
464 }
465
466 /*
467 ================
468 GL_LoadTexture
469 ================
470 */
471 int GL_LoadTexture (char *identifier, int width, int height, byte *data, qboolean mipmap, qboolean alpha, int bytesperpixel)
472 {
473         unsigned short  crc;
474         int                             i, width2, height2, width3, height3, w, h, mip;
475         gltexture_t             *glt;
476
477         if (isDedicated)
478                 return 1;
479
480         // LordHavoc: do a CRC to confirm the data really is the same as previous occurances.
481         crc = CRC_Block(data, width*height*bytesperpixel);
482         // see if the texture is already present
483         if (identifier[0])
484         {
485                 for (i=0, glt=gltextures ; i<numgltextures ; i++, glt++)
486                 {
487                         if (!strcmp (identifier, glt->identifier))
488                         {
489                                 // LordHavoc: everyone hates cache mismatchs, so I fixed it
490                                 if (crc != glt->crc || width != glt->width || height != glt->height)
491                                 {
492                                         Con_DPrintf("GL_LoadTexture: cache mismatch, replacing old texture\n");
493                                         goto GL_LoadTexture_setup; // drop out with glt pointing to the texture to replace
494                                         //Sys_Error ("GL_LoadTexture: cache mismatch");
495                                 }
496                                 if ((gl_lerpimages.value != 0) != glt->lerped)
497                                         goto GL_LoadTexture_setup; // drop out with glt pointing to the texture to replace
498                                 return glt->texnum;
499                         }
500                 }
501         }
502         // LordHavoc: although this could be an else condition as it was in the original id code,
503         //            it is more clear this way
504         // LordHavoc: check if there are still slots available
505         if (numgltextures >= MAX_GLTEXTURES)
506                 Sys_Error ("GL_LoadTexture: ran out of texture slots (%d)\n", MAX_GLTEXTURES);
507         glt = &gltextures[numgltextures++];
508
509         strcpy (glt->identifier, identifier);
510         glt->texnum = texture_extension_number;
511         texture_extension_number++;
512 // LordHavoc: label to drop out of the loop into the setup code
513 GL_LoadTexture_setup:
514         // calculate power of 2 size
515         width2 = 1;while (width2 < width) width2 <<= 1;
516         height2 = 1;while (height2 < height) height2 <<= 1;
517         // calculate final size (mipmapped downward to this)
518         width3 = width2 >> (int) gl_picmip.value;
519         height3 = height2 >> (int) gl_picmip.value;
520         while (width3 > (int) gl_max_size.value) width3 >>= 1;
521         while (height3 > (int) gl_max_size.value) height3 >>= 1;
522         if (width3 < 1) width3 = 1;
523         if (height3 < 1) height3 = 1;
524
525         // final storage
526         GL_AllocTexels(glt, width3, height3, mipmap);
527         glt->crc = crc; // LordHavoc: used to verify textures are identical
528         glt->width = width;
529         glt->height = height;
530         glt->mipmap = mipmap;
531         glt->bytesperpixel = bytesperpixel;
532         glt->lerped = gl_lerpimages.value != 0;
533         glt->alpha = false; // updated later
534         if (width == width3 && height == height3) // perfect match
535         {
536                 if (bytesperpixel == 1) // 8bit
537                         Image_Copy8bitRGBA(data, glt->texels[0], width*height, d_8to24table);
538                 else
539                         Image_CopyRGBAGamma(data, glt->texels[0], width*height);
540         }
541         else if (width == width2 && height == height2) // perfect match for top level, but needs to be reduced
542         {
543                 byte *temptexels2;
544                 temptexels2 = qmalloc(width2*height2*4); // scaleup buffer
545                 if (bytesperpixel == 1) // 8bit
546                         Image_Copy8bitRGBA(data, temptexels2, width*height, d_8to24table);
547                 else
548                         Image_CopyRGBAGamma(data, temptexels2, width*height);
549                 while (width2 > width3 || height2 > height3)
550                 {
551                         w = width2;h = height2;
552                         if (width2 > width3) width2 >>= 1;
553                         if (height2 > height3) height2 >>= 1;
554                         if (width2 <= width3 && height2 <= height3) // size achieved
555                                 GL_MipReduce(temptexels2, glt->texels[0], w, h, width3, height3);
556                         else
557                                 GL_MipReduce(temptexels2, temptexels2, w, h, width3, height3);
558                 }
559                 qfree(temptexels2);
560         }
561         else // scaling...
562         {
563                 byte *temptexels;
564                 // pre-scaleup buffer
565                 temptexels = qmalloc(width*height*4);
566                 if (bytesperpixel == 1) // 8bit
567                         Image_Copy8bitRGBA(data, temptexels, width*height, d_8to24table);
568                 else
569                         Image_CopyRGBAGamma(data, temptexels, width*height);
570                 if (width2 != width3 || height2 != height3) // reduced by gl_pic_mip or gl_max_size
571                 {
572                         byte *temptexels2;
573                         temptexels2 = qmalloc(width2*height2*4); // scaleup buffer
574                         GL_ResampleTexture(temptexels, width, height, temptexels2, width2, height2);
575                         while (width2 > width3 || height2 > height3)
576                         {
577                                 w = width2;h = height2;
578                                 if (width2 > width3) width2 >>= 1;
579                                 if (height2 > height3) height2 >>= 1;
580                                 if (width2 <= width3 && height2 <= height3) // size achieved
581                                         GL_MipReduce(temptexels2, glt->texels[0], w, h, width3, height3);
582                                 else
583                                         GL_MipReduce(temptexels2, temptexels2, w, h, width3, height3);
584                         }
585                         qfree(temptexels2);
586                 }
587                 else // copy directly
588                         GL_ResampleTexture(temptexels, width, height, glt->texels[0], width2, height2);
589                 qfree(temptexels);
590         }
591         if (alpha)
592         {
593                 byte    *in = glt->texels[0] + 3;
594                 for (i = 0;i < width*height;i++, in += 4)
595                         if (*in < 255)
596                         {
597                                 glt->alpha = true;
598                                 break;
599                         }
600         }
601         // this loop is skipped if there are no mipmaps to generate
602         for (mip = 1;mip < MAXMIPS && glt->texels[mip];mip++)
603                 GL_MipReduce(glt->texels[mip-1], glt->texels[mip], glt->texelsize[mip-1][0], glt->texelsize[mip-1][1], 1, 1);
604         GL_UploadTexture(glt);
605         GL_FreeTexels(glt);
606
607 //      if (bytesperpixel == 1) // 8bit
608 //              GL_Upload8 (data, width, height, mipmap, alpha);
609 //      else // 32bit
610 //              GL_Upload32 (data, width, height, mipmap, true);
611
612         return glt->texnum;
613 }
614
615 /*
616 ================
617 GL_LoadPicTexture
618 ================
619 */
620 int GL_LoadPicTexture (qpic_t *pic)
621 {
622         return GL_LoadTexture ("", pic->width, pic->height, pic->data, false, true, 1);
623 }
624
625 int GL_GetTextureSlots (int count)
626 {
627         gltexture_t             *glt, *first;
628
629         first = glt = &gltextures[numgltextures];
630         while (count--)
631         {
632                 glt->identifier[0] = 0;
633                 glt->texnum = texture_extension_number++;
634                 glt->crc = 0;
635                 glt->width = 0;
636                 glt->height = 0;
637                 glt->bytesperpixel = 0;
638                 glt++;
639                 numgltextures++;
640         }
641         return first->texnum;
642 }