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