1 /* bzflag
2  * Copyright (c) 1993-2021 Tim Riker
3  *
4  * This package is free software;  you can redistribute it and/or
5  * modify it under the terms of the license found in the file
6  * named COPYING that should have accompanied this file.
7  *
8  * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
9  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
10  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
11  */
12 
13 #include "common.h"
14 
15 // system headers
16 #include <string>
17 #include <string.h>
18 
19 // common headers
20 #include "bzfio.h"
21 #include "StateDatabase.h"
22 #include "bzfgl.h"
23 #include "OpenGLTexture.h"
24 #include "OpenGLGState.h"
25 #include "OpenGLUtils.h"
26 
27 #ifndef _WIN32
28 typedef int64_t s64;
29 #else
30 typedef __int64 s64;
31 #endif
32 
33 
34 //
35 // OpenGLTexture::Rep
36 //
37 
38 const GLenum        OpenGLTexture::minifyFilter[] =
39 {
40     GL_NEAREST,
41     GL_NEAREST,
42     GL_LINEAR,
43     GL_NEAREST_MIPMAP_NEAREST,
44     GL_LINEAR_MIPMAP_NEAREST,
45     GL_NEAREST_MIPMAP_LINEAR,
46     GL_LINEAR_MIPMAP_LINEAR
47 };
48 const GLenum        OpenGLTexture::magnifyFilter[] =
49 {
50     GL_NEAREST,
51     GL_NEAREST,
52     GL_LINEAR,
53     GL_NEAREST,
54     GL_LINEAR,
55     GL_NEAREST,
56     GL_LINEAR
57 };
58 const char*     OpenGLTexture::configFilterNames[] =
59 {
60     "no",
61     "nearest",
62     "linear",
63     "nearestmipmapnearest",
64     "linearmipmapnearest",
65     "nearestmipmaplinear",
66     "linearmipmaplinear"
67 };
68 
69 
70 //
71 // OpenGLTexture
72 //
73 
74 const int OpenGLTexture::filterCount = Max + 1;
75 OpenGLTexture::Filter OpenGLTexture::maxFilter = Default;
76 
77 
OpenGLTexture(int _width,int _height,const GLvoid * pixels,Filter _filter,bool _repeat)78 OpenGLTexture::OpenGLTexture(int _width, int _height, const GLvoid* pixels,
79                              Filter _filter, bool _repeat) :
80     width(_width), height(_height)
81 {
82     repeat = _repeat;
83     filter = _filter;
84     list = INVALID_GL_TEXTURE_ID;
85 
86     // copy/scale the original texture image
87     setupImage((const GLubyte*)pixels);
88 
89     // get internal format
90     getBestFormat();
91 
92     // build and bind the GL texture
93     initContext();
94 
95     // watch for context recreation
96     OpenGLGState::registerContextInitializer(static_freeContext,
97             static_initContext, (void*)this);
98 }
99 
100 
~OpenGLTexture()101 OpenGLTexture::~OpenGLTexture()
102 {
103     OpenGLGState::unregisterContextInitializer(static_freeContext,
104             static_initContext, (void*)this);
105     delete[] imageMemory;
106 
107     freeContext();
108     return;
109 }
110 
111 
static_freeContext(void * that)112 void OpenGLTexture::static_freeContext(void *that)
113 {
114     ((OpenGLTexture*) that)->freeContext();
115 }
116 
117 
static_initContext(void * that)118 void OpenGLTexture::static_initContext(void *that)
119 {
120     ((OpenGLTexture*) that)->initContext();
121 }
122 
123 
freeContext()124 void OpenGLTexture::freeContext()
125 {
126     // glDeleteTextures should set binding to 0 by itself when the texture
127     //  is in use, but some stacks (Linux/glx/matrox) are broken, so play it safe
128     glBindTexture(GL_TEXTURE_2D, 0);
129 
130     if (list != INVALID_GL_TEXTURE_ID)
131     {
132         glDeleteTextures(1, &list);
133         list = INVALID_GL_TEXTURE_ID;
134     }
135     return;
136 }
137 
138 
initContext()139 void OpenGLTexture::initContext()
140 {
141     // make texture map object/list
142     glGenTextures(1, &list);
143 
144     // now make texture map display list (compute all mipmaps, if requested).
145     // compute next mipmap from current mipmap to save time.
146     setFilter(filter);
147     glBindTexture(GL_TEXTURE_2D, list);
148     if (GLEW_VERSION_1_4)
149     {
150         glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
151         glTexImage2D(GL_TEXTURE_2D, 0, internalFormat,
152                      scaledWidth, scaledHeight,
153                      0, internalFormat, GL_UNSIGNED_BYTE, image);
154     }
155     else
156     {
157         gluBuild2DMipmaps(GL_TEXTURE_2D, internalFormat,
158                           scaledWidth, scaledHeight,
159                           internalFormat, GL_UNSIGNED_BYTE, image);
160     }
161     glBindTexture(GL_TEXTURE_2D, 0);
162 
163     return;
164 }
165 
166 
setupImage(const GLubyte * pixels)167 void OpenGLTexture::setupImage(const GLubyte* pixels)
168 {
169     if (GLEW_ARB_texture_non_power_of_two)
170     {
171         scaledWidth = width;
172         scaledHeight = height;
173         // Remove check of max size. I don't want to use gluScale if GL can handle non POT
174     }
175     else
176     {
177         // align to a 2^N value
178         scaledWidth = 1;
179         scaledHeight = 1;
180         while (scaledWidth < width)
181             scaledWidth <<= 1;
182         while (scaledHeight < height)
183             scaledHeight <<= 1;
184 
185         // get maximum valid size for texture (boost to 2^m x 2^n)
186         GLint maxTextureSize;
187         glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
188 
189         // hard limit, some drivers have problems with sizes greater
190         // then this (espeically if you are using glTexSubImage2D)
191         const GLint dbMaxTexSize = BZDB.evalInt("maxTextureSize");
192         GLint bzMaxTexSize = 1;
193         // align the max size to a power of two  (wasteful)
194         while (bzMaxTexSize < dbMaxTexSize)
195             bzMaxTexSize <<= 1;
196 
197         if ((maxTextureSize < 0) || (maxTextureSize > bzMaxTexSize))
198             maxTextureSize = bzMaxTexSize;
199 
200         // clamp to the maximum size
201         if (scaledWidth > maxTextureSize)
202             scaledWidth = maxTextureSize;
203         if (scaledHeight > maxTextureSize)
204             scaledHeight = maxTextureSize;
205     }
206 
207     // copy the data into a 4-byte aligned buffer
208     GLubyte* unaligned = new GLubyte[4 * width * height + 4];
209     GLubyte* aligned = (GLubyte*)(((unsigned long)unaligned & ~3) + 4);
210     ::memcpy(aligned, pixels, 4 * width * height);
211 
212     // scale the image if required
213     if ((scaledWidth != width) || (scaledHeight != height))
214     {
215         GLubyte* unalignedScaled = new GLubyte[4 * scaledWidth * scaledHeight + 4];
216         GLubyte* alignedScaled = (GLubyte*)(((unsigned long)unalignedScaled & ~3) + 4);
217 
218         // FIXME: 0 is success, return false otherwise...
219         gluScaleImage (GL_RGBA, width, height, GL_UNSIGNED_BYTE, aligned,
220                        scaledWidth, scaledHeight, GL_UNSIGNED_BYTE, alignedScaled);
221 
222         delete[] unaligned;
223         unaligned = unalignedScaled;
224         aligned = alignedScaled;
225         logDebugMessage(1,"Scaling texture from %ix%i to %ix%i\n",
226                         width, height, scaledWidth, scaledHeight);
227     }
228 
229     // set the image
230     image = aligned;
231     imageMemory = unaligned;
232 }
233 
234 
getFilter()235 OpenGLTexture::Filter OpenGLTexture::getFilter()
236 {
237     return filter;
238 }
239 
240 
setFilter(Filter _filter)241 void OpenGLTexture::setFilter(Filter _filter)
242 {
243     filter = _filter;
244 
245     int filterIndex = (int) filter;
246     // limit filter.  try to keep nearest... filters as nearest and
247     // linear... as linear.
248     if (filterIndex > maxFilter)
249     {
250         if ((filterIndex & 1) == 1)   // nearest...
251         {
252             if ((maxFilter & 1) == 1)
253                 filterIndex = maxFilter;
254             else
255                 filterIndex = maxFilter > 0 ? maxFilter - 1 : 0;
256         }
257         else   // linear...
258         {
259             if ((maxFilter & 1) == 1)
260                 filterIndex = maxFilter - 1;
261             else
262                 filterIndex = maxFilter;
263         }
264     }
265     GLint binding;
266     glGetIntegerv (GL_TEXTURE_BINDING_2D, &binding);
267     glBindTexture(GL_TEXTURE_2D, list);
268     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minifyFilter[filterIndex]);
269     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magnifyFilter[filterIndex]);
270     if (OpenGLGState::hasAnisotropicFiltering)
271     {
272         GLint aniso = BZDB.evalInt("aniso");
273         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, aniso);
274     }
275     glBindTexture(GL_TEXTURE_2D, binding);
276 }
277 
278 
getMaxFilter()279 OpenGLTexture::Filter OpenGLTexture::getMaxFilter()
280 {
281     return maxFilter;
282 }
283 
setMaxFilter(Filter _filter)284 void OpenGLTexture::setMaxFilter(Filter _filter)
285 {
286     maxFilter = _filter;
287 }
288 
289 
execute()290 void OpenGLTexture::execute()
291 {
292     bind();
293 }
294 
295 
getFilterName(OpenGLTexture::Filter filter)296 const char* OpenGLTexture::getFilterName(OpenGLTexture::Filter filter)
297 {
298     if ((filter < 0) || (filter > Max))
299         return configFilterNames[Max];
300     else
301         return configFilterNames[filter];
302 }
303 
304 
getFilterCount()305 int OpenGLTexture::getFilterCount()
306 {
307     return filterCount;
308 }
309 
310 
getFilterNames()311 const char** OpenGLTexture::getFilterNames()
312 {
313     return configFilterNames;
314 }
315 
316 
getAspectRatio() const317 float OpenGLTexture::getAspectRatio() const
318 {
319     return ((float) height) / ((float) width);
320 }
321 
322 
bind()323 void OpenGLTexture::bind()
324 {
325     if (list != INVALID_GL_TEXTURE_ID)
326         glBindTexture(GL_TEXTURE_2D, list);
327     else
328     {
329         glBindTexture(GL_TEXTURE_2D, 0); // heh, it's the same call
330     }
331 }
332 
333 
getBestFormat()334 void OpenGLTexture::getBestFormat()
335 {
336     GLubyte* scan = image;
337     const int size = scaledWidth * scaledHeight;
338     int i;
339     bool useLuminance = true;
340     alpha = false;
341     for (i = 0; i < size; scan += 4, i++)
342     {
343         // see if all pixels are achromatic
344         if (scan[0] != scan[1] || scan[0] != scan[2])
345             useLuminance = false;
346         // see if all pixels are opaque
347         if (scan[3] != 0xff)
348             alpha = true;
349         if (!useLuminance && alpha)
350             break;
351     }
352 
353     scan = image;
354     GLubyte* scanOut = image;
355     for (i = 0; i < size; i++)
356     {
357         *scanOut++ = *scan++;
358         if (useLuminance)
359         {
360             scan++;
361             scan++;
362         }
363         else
364         {
365             *scanOut++ = *scan++;
366             *scanOut++ = *scan++;
367         }
368         if (alpha)
369             *scanOut++ = *scan++;
370         else
371             scan++;
372     }
373 
374     // pick internal format
375     internalFormat = useLuminance ?
376                      (alpha ? GL_LUMINANCE_ALPHA : GL_LUMINANCE) :
377                      (alpha ? GL_RGBA : GL_RGB);
378 }
379 
380 
381 // Local Variables: ***
382 // mode: C++ ***
383 // tab-width: 4 ***
384 // c-basic-offset: 4 ***
385 // indent-tabs-mode: nil ***
386 // End: ***
387 // ex: shiftwidth=4 tabstop=4
388