Line data Source code
1 : /* Copyright (C) 2021 Wildfire Games.
2 : *
3 : * Permission is hereby granted, free of charge, to any person obtaining
4 : * a copy of this software and associated documentation files (the
5 : * "Software"), to deal in the Software without restriction, including
6 : * without limitation the rights to use, copy, modify, merge, publish,
7 : * distribute, sublicense, and/or sell copies of the Software, and to
8 : * permit persons to whom the Software is furnished to do so, subject to
9 : * the following conditions:
10 : *
11 : * The above copyright notice and this permission notice shall be included
12 : * in all copies or substantial portions of the Software.
13 : *
14 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 : * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 : * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17 : * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18 : * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19 : * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20 : * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 : */
22 :
23 : #include "precompiled.h"
24 : #include "ogl_tex.h"
25 :
26 : #include "lib/app_hooks.h"
27 : #include "lib/bits.h"
28 : #include "lib/ogl.h"
29 : #include "lib/res/h_mgr.h"
30 : #include "lib/sysdep/gfx.h"
31 : #include "lib/tex/tex.h"
32 :
33 : #include <cstdio>
34 :
35 : //----------------------------------------------------------------------------
36 : // OpenGL helper routines
37 : //----------------------------------------------------------------------------
38 :
39 : static bool filter_valid(GLint filter)
40 : {
41 0 : switch(filter)
42 : {
43 : case GL_NEAREST:
44 : case GL_LINEAR:
45 : case GL_NEAREST_MIPMAP_NEAREST:
46 : case GL_LINEAR_MIPMAP_NEAREST:
47 : case GL_NEAREST_MIPMAP_LINEAR:
48 : case GL_LINEAR_MIPMAP_LINEAR:
49 : return true;
50 0 : default:
51 0 : return false;
52 : }
53 : }
54 :
55 :
56 : static bool wrap_valid(GLint wrap)
57 : {
58 0 : switch(wrap)
59 : {
60 : #if !CONFIG2_GLES
61 : case GL_CLAMP:
62 : case GL_CLAMP_TO_BORDER:
63 : #endif
64 : case GL_CLAMP_TO_EDGE:
65 : case GL_REPEAT:
66 : case GL_MIRRORED_REPEAT:
67 : return true;
68 0 : default:
69 0 : return false;
70 : }
71 : }
72 :
73 :
74 : static bool are_mipmaps_needed(size_t width, size_t height, GLint filter)
75 : {
76 : // can't upload the entire texture; we're going to skip some
77 : // levels until it no longer exceeds the OpenGL dimension limit.
78 0 : if((GLint)width > ogl_max_tex_size || (GLint)height > ogl_max_tex_size)
79 : return true;
80 :
81 0 : switch(filter)
82 : {
83 : case GL_NEAREST_MIPMAP_NEAREST:
84 : case GL_LINEAR_MIPMAP_NEAREST:
85 : case GL_NEAREST_MIPMAP_LINEAR:
86 : case GL_LINEAR_MIPMAP_LINEAR:
87 : return true;
88 0 : default:
89 0 : return false;
90 : }
91 : }
92 :
93 :
94 : static bool fmt_is_s3tc(GLenum fmt)
95 : {
96 0 : switch(fmt)
97 : {
98 : case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
99 : case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
100 : case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
101 : case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
102 : return true;
103 0 : default:
104 0 : return false;
105 : }
106 : }
107 :
108 :
109 : // determine OpenGL texture format, given <bpp> and Tex <flags>.
110 0 : static GLint choose_fmt(size_t bpp, size_t flags)
111 : {
112 0 : const bool alpha = (flags & TEX_ALPHA) != 0;
113 0 : const bool bgr = (flags & TEX_BGR ) != 0;
114 0 : const bool grey = (flags & TEX_GREY ) != 0;
115 0 : const size_t dxt = flags & TEX_DXT;
116 :
117 : // S3TC
118 0 : if(dxt != 0)
119 : {
120 0 : switch(dxt)
121 : {
122 : case DXT1A:
123 : return GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
124 0 : case 1:
125 0 : return GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
126 0 : case 3:
127 0 : return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
128 0 : case 5:
129 0 : return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
130 0 : default:
131 0 : DEBUG_WARN_ERR(ERR::LOGIC); // invalid DXT value
132 0 : return 0;
133 : }
134 : }
135 :
136 : // uncompressed
137 0 : switch(bpp)
138 : {
139 0 : case 8:
140 0 : ENSURE(grey);
141 : return GL_LUMINANCE;
142 : case 16:
143 : return GL_LUMINANCE_ALPHA;
144 0 : case 24:
145 0 : ENSURE(!alpha);
146 : #if CONFIG2_GLES
147 : // GLES never supports BGR
148 : ENSURE(!bgr);
149 : return GL_RGB;
150 : #else
151 0 : return bgr? GL_BGR : GL_RGB;
152 : #endif
153 0 : case 32:
154 0 : ENSURE(alpha);
155 : // GLES can support BGRA via GL_EXT_texture_format_BGRA8888
156 : // (TODO: can we rely on support for that extension?)
157 0 : return bgr? GL_BGRA_EXT : GL_RGBA;
158 0 : default:
159 0 : DEBUG_WARN_ERR(ERR::LOGIC); // invalid bpp
160 : return 0;
161 : }
162 :
163 : UNREACHABLE;
164 : }
165 :
166 :
167 : //----------------------------------------------------------------------------
168 : // quality mechanism
169 : //----------------------------------------------------------------------------
170 :
171 : static GLint default_filter = GL_LINEAR; // one of the GL *minify* filters
172 : static int default_q_flags = OGL_TEX_FULL_QUALITY; // OglTexQualityFlags
173 :
174 : static bool q_flags_valid(int q_flags)
175 : {
176 0 : const size_t bits = OGL_TEX_FULL_QUALITY|OGL_TEX_HALF_BPP|OGL_TEX_HALF_RES;
177 : // unrecognized bits are set - invalid
178 0 : if((q_flags & ~bits) != 0)
179 : return false;
180 : // "full quality" but other reduction bits are set - invalid
181 0 : if(q_flags & OGL_TEX_FULL_QUALITY && q_flags & ~OGL_TEX_FULL_QUALITY)
182 : return false;
183 : return true;
184 : }
185 :
186 :
187 : // change default settings - these affect performance vs. quality.
188 : // may be overridden for individual textures via parameter to
189 : // ogl_tex_upload or ogl_tex_set_filter, respectively.
190 : //
191 : // pass 0 to keep the current setting; defaults and legal values are:
192 : // - q_flags: OGL_TEX_FULL_QUALITY; combination of OglTexQualityFlags
193 : // - filter: GL_LINEAR; any valid OpenGL minification filter
194 0 : void ogl_tex_set_defaults(int q_flags, GLint filter)
195 : {
196 0 : if(q_flags)
197 : {
198 0 : ENSURE(q_flags_valid(q_flags));
199 0 : default_q_flags = q_flags;
200 : }
201 :
202 0 : if(filter)
203 : {
204 0 : ENSURE(filter_valid(filter));
205 0 : default_filter = filter;
206 : }
207 0 : }
208 :
209 :
210 : // choose an internal format for <fmt> based on the given q_flags.
211 0 : static GLint choose_int_fmt(GLenum fmt, int q_flags)
212 : {
213 : // true => 4 bits per component; otherwise, 8
214 0 : const bool half_bpp = (q_flags & OGL_TEX_HALF_BPP) != 0;
215 :
216 : // early-out for S3TC textures: they don't need an internal format
217 : // (because upload is via glCompressedTexImage2DARB), but we must avoid
218 : // triggering the default case below. we might as well return a
219 : // meaningful value (i.e. int_fmt = fmt).
220 0 : if(fmt_is_s3tc(fmt))
221 0 : return fmt;
222 :
223 : #if CONFIG2_GLES
224 :
225 : UNUSED2(half_bpp);
226 :
227 : // GLES only supports internal format == external format
228 : return fmt;
229 :
230 : #else
231 :
232 0 : switch(fmt)
233 : {
234 : // 8bpp
235 0 : case GL_LUMINANCE:
236 0 : return half_bpp? GL_LUMINANCE4 : GL_LUMINANCE8;
237 0 : case GL_INTENSITY:
238 0 : return half_bpp? GL_INTENSITY4 : GL_INTENSITY8;
239 0 : case GL_ALPHA:
240 0 : return half_bpp? GL_ALPHA4 : GL_ALPHA8;
241 :
242 : // 16bpp
243 0 : case GL_LUMINANCE_ALPHA:
244 0 : return half_bpp? GL_LUMINANCE4_ALPHA4 : GL_LUMINANCE8_ALPHA8;
245 :
246 : // 24bpp
247 0 : case GL_RGB:
248 0 : case GL_BGR: // note: BGR can't be used as internal format
249 0 : return half_bpp? GL_RGB4 : GL_RGB8;
250 :
251 : // 32bpp
252 0 : case GL_RGBA:
253 0 : case GL_BGRA: // note: BGRA can't be used as internal format
254 0 : return half_bpp? GL_RGBA4 : GL_RGBA8;
255 :
256 0 : default:
257 0 : {
258 0 : wchar_t buf[100];
259 0 : swprintf_s(buf, ARRAY_SIZE(buf), L"choose_int_fmt: fmt 0x%x isn't covered! please add it", fmt);
260 0 : DEBUG_DISPLAY_ERROR(buf);
261 0 : DEBUG_WARN_ERR(ERR::LOGIC); // given fmt isn't covered! please add it.
262 : // fall back to a reasonable default
263 0 : return half_bpp? GL_RGB4 : GL_RGB8;
264 : }
265 : }
266 :
267 : UNREACHABLE;
268 :
269 : #endif // #if CONFIG2_GLES
270 : }
271 :
272 :
273 : //----------------------------------------------------------------------------
274 : // texture state to allow seamless reload
275 : //----------------------------------------------------------------------------
276 :
277 : // see "Texture Parameters" in docs.
278 :
279 : // all GL state tied to the texture that must be reapplied after reload.
280 : // (this mustn't get too big, as it's stored in the already sizeable OglTex)
281 : struct OglTexState
282 : {
283 : // glTexParameter
284 : // note: there are more options, but they do not look to
285 : // be important and will not be applied after a reload!
286 : // in particular, LOD_BIAS isn't needed because that is set for
287 : // the entire texturing unit via glTexEnv.
288 : // .. texture filter
289 : // note: this is the minification filter value; magnification filter
290 : // is GL_NEAREST if it's GL_NEAREST, otherwise GL_LINEAR.
291 : // we don't store mag_filter explicitly because it
292 : // doesn't appear useful - either apps can tolerate LINEAR, or
293 : // mipmaps aren't called for and filter could be NEAREST anyway).
294 : GLint filter;
295 : // .. wrap mode
296 : GLint wrap_s;
297 : GLint wrap_t;
298 : // .. anisotropy
299 : // note: ignored unless EXT_texture_filter_anisotropic is supported.
300 : GLfloat anisotropy;
301 : };
302 :
303 :
304 : // fill the given state object with default values.
305 : static void state_set_to_defaults(OglTexState* ots)
306 : {
307 0 : ots->filter = default_filter;
308 0 : ots->wrap_s = GL_REPEAT;
309 0 : ots->wrap_t = GL_REPEAT;
310 0 : ots->anisotropy = 1.0f;
311 : }
312 :
313 :
314 : // send all state to OpenGL (actually the currently bound texture).
315 : // called from ogl_tex_upload.
316 0 : static void state_latch(OglTexState* ots)
317 : {
318 : // filter
319 0 : const GLint filter = ots->filter;
320 0 : glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
321 0 : const GLint mag_filter = (filter == GL_NEAREST)? GL_NEAREST : GL_LINEAR;
322 0 : glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter);
323 :
324 : // wrap
325 0 : const GLint wrap_s = ots->wrap_s;
326 0 : const GLint wrap_t = ots->wrap_t;
327 0 : glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap_s);
328 0 : glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap_t);
329 : // .. only CLAMP and REPEAT are guaranteed to be available.
330 : // if we're using one of the others, we squelch the error that
331 : // may have resulted if this GL implementation is old.
332 : #if !CONFIG2_GLES
333 0 : if((wrap_s != GL_CLAMP && wrap_s != GL_REPEAT) || (wrap_t != GL_CLAMP && wrap_t != GL_REPEAT))
334 0 : ogl_SquelchError(GL_INVALID_ENUM);
335 : #endif
336 :
337 : // anisotropy
338 0 : const GLfloat anisotropy = ots->anisotropy;
339 0 : if (anisotropy != 1.0f && ogl_tex_has_anisotropy())
340 : {
341 0 : glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisotropy);
342 : }
343 0 : }
344 :
345 :
346 : //----------------------------------------------------------------------------
347 : // texture resource object
348 : //----------------------------------------------------------------------------
349 :
350 : // ideally we would split OglTex into data and state objects as in
351 : // SndData / VSrc. this gives us the benefits of caching while still
352 : // leaving each "instance" (state object, which owns a data reference)
353 : // free to change its state. however, unlike in OpenAL, there is no state
354 : // independent of the data object - all parameters are directly tied to the
355 : // GL texture object. therefore, splitting them up is impossible.
356 : // (we shouldn't even keep the texel data in memory since that's already
357 : // covered by the FS cache).
358 : //
359 : // given that multiple "instances" share the state stored here, we conclude:
360 : // - a refcount is necessary to prevent ogl_tex_upload from freeing
361 : // <t> as long as other instances are active.
362 : // - concurrent use risks cross-talk (if the 2nd "instance" changes state and
363 : // the first is reloaded, its state may change to that of the 2nd)
364 : //
365 : // as bad as it sounds, the latter issue isn't a problem: we do not expect
366 : // multiple instances of the same texture where someone changes its filter.
367 : // even if it is reloaded, the differing state is not critical.
368 : // the alternative is even worse: disabling *all* caching/reuse would
369 : // really hurt performance and h_mgr doesn't support only disallowing
370 : // reuse of active objects (this would break the index lookup code, since
371 : // multiple instances may then exist).
372 :
373 : // note: make sure these values fit inside OglTex.flags (only 16 bits)
374 : enum OglTexFlags
375 : {
376 : // "the texture is currently uploaded"; reset in dtor.
377 : OT_IS_UPLOADED = 1,
378 :
379 : // "the enclosed Tex object is valid and holds a texture";
380 : // reset in dtor and after ogl_tex_upload's tex_free.
381 : OT_TEX_VALID = 2,
382 : //size_t tex_valid : 1;
383 :
384 : // "reload() should automatically re-upload the texture" (because
385 : // it had been uploaded before the reload); never reset.
386 : OT_NEED_AUTO_UPLOAD = 4,
387 :
388 : // (used for validating flags)
389 : OT_ALL_FLAGS = OT_IS_UPLOADED|OT_TEX_VALID|OT_NEED_AUTO_UPLOAD
390 : };
391 :
392 : struct OglTex
393 : {
394 : Tex t;
395 :
396 : // allocated by OglTex_reload; indicates the texture is currently uploaded.
397 : GLuint id;
398 :
399 : // ogl_tex_upload calls choose_fmt to determine these from <t>.
400 : // however, its caller may override those values via parameters.
401 : // note: these are stored here to allow retrieving via ogl_tex_get_format;
402 : // they are only used within ogl_tex_upload.
403 : GLenum fmt;
404 : GLint int_fmt;
405 :
406 : OglTexState state;
407 :
408 : // OglTexQualityFlags
409 : u8 q_flags;
410 :
411 : // to which Texture Mapping Unit was this bound?
412 : u8 tmu;
413 :
414 : u16 flags;
415 :
416 : u32 uploaded_size;
417 : };
418 :
419 : H_TYPE_DEFINE(OglTex);
420 :
421 0 : static void OglTex_init(OglTex* ot, va_list args)
422 : {
423 0 : Tex* wrapped_tex = va_arg(args, Tex*);
424 0 : if(wrapped_tex)
425 : {
426 0 : ot->t = *wrapped_tex;
427 : // indicate ot->t is now valid, thus skipping loading from file.
428 : // note: ogl_tex_wrap prevents actual reloads from happening.
429 0 : ot->flags |= OT_TEX_VALID;
430 : }
431 :
432 0 : state_set_to_defaults(&ot->state);
433 0 : ot->q_flags = default_q_flags;
434 0 : }
435 :
436 0 : static void OglTex_dtor(OglTex* ot)
437 : {
438 0 : if(ot->flags & OT_TEX_VALID)
439 : {
440 0 : ot->t.free();
441 0 : ot->flags &= ~OT_TEX_VALID;
442 : }
443 :
444 : // note: do not check if OT_IS_UPLOADED is set, because we allocate
445 : // OglTex.id without necessarily having done an upload.
446 0 : glDeleteTextures(1, &ot->id);
447 0 : ot->id = 0;
448 0 : ot->flags &= ~OT_IS_UPLOADED;
449 0 : ot->uploaded_size = 0;
450 0 : }
451 :
452 0 : static Status OglTex_reload(OglTex* ot, const PIVFS& vfs, const VfsPath& pathname, Handle h)
453 : {
454 : // we're reusing a freed but still in-memory OglTex object
455 0 : if(ot->flags & OT_IS_UPLOADED)
456 : return INFO::OK;
457 :
458 : // if we don't already have the texture in memory (*), load from file.
459 : // * this happens if the texture is "wrapped".
460 0 : if(!(ot->flags & OT_TEX_VALID))
461 : {
462 0 : std::shared_ptr<u8> file; size_t fileSize;
463 0 : RETURN_STATUS_IF_ERR(vfs->LoadFile(pathname, file, fileSize));
464 0 : if(ot->t.decode(file, fileSize) >= 0)
465 0 : ot->flags |= OT_TEX_VALID;
466 : }
467 :
468 0 : glGenTextures(1, &ot->id);
469 :
470 : // if it had already been uploaded before this reload,
471 : // re-upload it (this also does state_latch).
472 0 : if(ot->flags & OT_NEED_AUTO_UPLOAD)
473 0 : (void)ogl_tex_upload(h);
474 :
475 : return INFO::OK;
476 : }
477 :
478 0 : static Status OglTex_validate(const OglTex* ot)
479 : {
480 0 : if(ot->flags & OT_TEX_VALID)
481 : {
482 0 : RETURN_STATUS_IF_ERR(ot->t.validate());
483 :
484 : // width, height
485 : // (note: this is done here because tex.cpp doesn't impose any
486 : // restrictions on dimensions, while OpenGL does).
487 0 : size_t w = ot->t.m_Width;
488 0 : size_t h = ot->t.m_Height;
489 : // .. == 0; texture file probably not loaded successfully.
490 0 : if(w == 0 || h == 0)
491 0 : WARN_RETURN(ERR::_11);
492 : // .. not power-of-2.
493 : // note: we can't work around this because both NV_texture_rectangle
494 : // and subtexture require work for the client (changing tex coords).
495 : // TODO: ARB_texture_non_power_of_two
496 0 : if(!is_pow2(w) || !is_pow2(h))
497 0 : WARN_RETURN(ERR::_13);
498 :
499 : // no longer verify dimensions are less than ogl_max_tex_size,
500 : // because we just use the higher mip levels in that case.
501 : }
502 :
503 : // texture state
504 0 : if(!filter_valid(ot->state.filter))
505 0 : WARN_RETURN(ERR::_14);
506 0 : if(!wrap_valid(ot->state.wrap_s))
507 0 : WARN_RETURN(ERR::_15);
508 0 : if(!wrap_valid(ot->state.wrap_t))
509 0 : WARN_RETURN(ERR::_16);
510 :
511 : // misc
512 0 : if(!q_flags_valid(ot->q_flags))
513 0 : WARN_RETURN(ERR::_17);
514 0 : if(ot->tmu >= 128) // unexpected that there will ever be this many
515 0 : WARN_RETURN(ERR::_18);
516 0 : if(ot->flags > OT_ALL_FLAGS)
517 0 : WARN_RETURN(ERR::_19);
518 : // .. note: don't check ot->fmt and ot->int_fmt - they aren't set
519 : // until during ogl_tex_upload.
520 :
521 : return INFO::OK;
522 : }
523 :
524 0 : static Status OglTex_to_string(const OglTex* ot, wchar_t* buf)
525 : {
526 0 : swprintf_s(buf, H_STRING_LEN, L"OglTex id=%u flags=%x", ot->id, (unsigned int)ot->flags);
527 0 : return INFO::OK;
528 : }
529 :
530 :
531 : // load and return a handle to the texture given in <pathname>.
532 : // for a list of supported formats, see tex.h's tex_load.
533 0 : Handle ogl_tex_load(const PIVFS& vfs, const VfsPath& pathname, size_t flags)
534 : {
535 0 : Tex* wrapped_tex = 0; // we're loading from file
536 0 : return h_alloc(H_OglTex, vfs, pathname, flags, wrapped_tex);
537 : }
538 :
539 : // make the given Tex object ready for use as an OpenGL texture
540 : // and return a handle to it. this will be as if its contents
541 : // had been loaded by ogl_tex_load.
542 : //
543 : // we need only add bookkeeping information and "wrap" it in
544 : // a resource object (accessed via Handle), hence the name.
545 : //
546 : // <fn> isn't strictly needed but should describe the texture so that
547 : // h_filename will return a meaningful comment for debug purposes.
548 : // note: because we cannot guarantee that callers will pass distinct
549 : // "filenames", caching is disabled for the created object. this avoids
550 : // mistakenly reusing previous objects that share the same comment.
551 0 : Handle ogl_tex_wrap(Tex* t, const PIVFS& vfs, const VfsPath& pathname, size_t flags)
552 : {
553 : // this object may not be backed by a file ("may", because
554 : // someone could do tex_load and then ogl_tex_wrap).
555 : // if h_mgr asks for a reload, the dtor will be called but
556 : // we won't be able to reconstruct it. therefore, disallow reloads.
557 : // (they are improbable anyway since caller is supposed to pass a
558 : // 'descriptive comment' instead of filename, but don't rely on that)
559 : // also disable caching as explained above.
560 0 : flags |= RES_DISALLOW_RELOAD|RES_NO_CACHE;
561 0 : return h_alloc(H_OglTex, vfs, pathname, flags, t);
562 : }
563 :
564 :
565 : // free all resources associated with the texture and make further
566 : // use of it impossible. (subject to refcount)
567 12 : Status ogl_tex_free(Handle& ht)
568 : {
569 12 : return h_free(ht, H_OglTex);
570 : }
571 :
572 :
573 : //----------------------------------------------------------------------------
574 : // state setters (see "Texture Parameters" in docs)
575 : //----------------------------------------------------------------------------
576 :
577 : // we require the below functions be called before uploading; this avoids
578 : // potentially redundant glTexParameter calls (we'd otherwise need to always
579 : // set defaults because we don't know if an override is forthcoming).
580 :
581 : // raise a debug warning if the texture has already been uploaded
582 : // (except in the few cases where this is allowed; see below).
583 : // this is so that you will notice incorrect usage - only one instance of a
584 : // texture should be active at a time, because otherwise they vie for
585 : // control of one shared OglTexState.
586 0 : static void warn_if_uploaded(Handle ht, const OglTex* ot)
587 : {
588 : #ifndef NDEBUG
589 : // we do not require users of this module to remember if they've
590 : // already uploaded a texture (inconvenient). since they also can't
591 : // tell if the texture was newly loaded (due to h_alloc interface),
592 : // we have to squelch this warning in 2 cases:
593 : // - it's ogl_tex_loaded several times (i.e. refcount > 1) and the
594 : // caller (typically a higher-level LoadTexture) is setting filter etc.
595 : // - caller is using our Handle as a caching mechanism, and calls
596 : // ogl_tex_set_* before every use of the texture. note: this
597 : // need not fall under the above check, e.g. if freed but cached.
598 : // workaround is that ogl_tex_set_* won't call us if the
599 : // same state values are being set (harmless anyway).
600 : intptr_t refs = h_get_refcnt(ht);
601 : if(refs > 1)
602 : return; // don't complain
603 :
604 : if(ot->flags & OT_IS_UPLOADED)
605 : DEBUG_WARN_ERR(ERR::LOGIC); // ogl_tex_set_*: texture already uploaded and shouldn't be changed
606 : #else
607 : // (prevent warnings; the alternative of wrapping all call sites in
608 : // #ifndef is worse)
609 0 : UNUSED2(ht);
610 0 : UNUSED2(ot);
611 : #endif
612 0 : }
613 :
614 :
615 : // override default filter (as set above) for this texture.
616 : // must be called before uploading (raises a warning if called afterwards).
617 : // filter is as defined by OpenGL; it is applied for both minification and
618 : // magnification (for rationale and details, see OglTexState)
619 0 : Status ogl_tex_set_filter(Handle ht, GLint filter)
620 : {
621 0 : H_DEREF(ht, OglTex, ot);
622 :
623 0 : if(!filter_valid(filter))
624 0 : WARN_RETURN(ERR::INVALID_PARAM);
625 :
626 0 : if(ot->state.filter != filter)
627 : {
628 0 : warn_if_uploaded(ht, ot);
629 0 : ot->state.filter = filter;
630 : }
631 : return INFO::OK;
632 : }
633 :
634 :
635 : // override default wrap mode (GL_REPEAT) for this texture.
636 : // must be called before uploading (raises a warning if called afterwards).
637 : // wrap is as defined by OpenGL.
638 0 : Status ogl_tex_set_wrap(Handle ht, GLint wrap_s, GLint wrap_t)
639 : {
640 0 : H_DEREF(ht, OglTex, ot);
641 :
642 0 : if(!wrap_valid(wrap_s))
643 0 : WARN_RETURN(ERR::INVALID_PARAM);
644 :
645 0 : if(!wrap_valid(wrap_t))
646 0 : WARN_RETURN(ERR::INVALID_PARAM);
647 :
648 0 : if(ot->state.wrap_s != wrap_s || ot->state.wrap_t != wrap_t)
649 : {
650 0 : warn_if_uploaded(ht, ot);
651 0 : ot->state.wrap_s = wrap_s;
652 0 : ot->state.wrap_t = wrap_t;
653 : }
654 : return INFO::OK;
655 : }
656 :
657 :
658 : // override default anisotropy for this texture.
659 : // must be called before uploading (raises a warning if called afterwards).
660 0 : Status ogl_tex_set_anisotropy(Handle ht, GLfloat anisotropy)
661 : {
662 0 : H_DEREF(ht, OglTex, ot);
663 :
664 0 : if(anisotropy < 1.0f)
665 0 : WARN_RETURN(ERR::INVALID_PARAM);
666 :
667 0 : if(ot->state.anisotropy != anisotropy)
668 : {
669 0 : warn_if_uploaded(ht, ot);
670 0 : ot->state.anisotropy = anisotropy;
671 : }
672 : return INFO::OK;
673 : }
674 :
675 :
676 : //----------------------------------------------------------------------------
677 : // upload
678 : //----------------------------------------------------------------------------
679 :
680 : // OpenGL has several features that are helpful for uploading but not
681 : // available in all implementations. we check for their presence but
682 : // provide for user override (in case they don't work on a card/driver
683 : // combo we didn't test).
684 :
685 : // tristate; -1 is undecided
686 : static int have_auto_mipmap_gen = -1;
687 : static int have_s3tc = -1;
688 : static int have_anistropy = -1;
689 :
690 : // override the default decision and force/disallow use of the
691 : // given feature. should be called from ah_override_gl_upload_caps.
692 0 : void ogl_tex_override(OglTexOverrides what, OglTexAllow allow)
693 : {
694 0 : ENSURE(allow == OGL_TEX_ENABLE || allow == OGL_TEX_DISABLE);
695 0 : const bool enable = (allow == OGL_TEX_ENABLE);
696 :
697 0 : switch(what)
698 : {
699 0 : case OGL_TEX_S3TC:
700 0 : have_s3tc = enable;
701 0 : break;
702 0 : case OGL_TEX_AUTO_MIPMAP_GEN:
703 0 : have_auto_mipmap_gen = enable;
704 0 : break;
705 0 : case OGL_TEX_ANISOTROPY:
706 0 : have_anistropy = enable;
707 0 : break;
708 0 : default:
709 0 : DEBUG_WARN_ERR(ERR::LOGIC); // invalid <what>
710 : break;
711 : }
712 0 : }
713 :
714 :
715 : // detect caps (via OpenGL extension list) and give an app_hook the chance to
716 : // override this (e.g. via list of card/driver combos on which S3TC breaks).
717 : // called once from the first ogl_tex_upload.
718 0 : static void detect_gl_upload_caps()
719 : {
720 : // note: gfx_card will be empty if running in quickstart mode;
721 : // in that case, we won't be able to check for known faulty cards.
722 :
723 : // detect features, but only change the variables if they were at
724 : // "undecided" (if overrides were set before this, they must remain).
725 0 : if(have_auto_mipmap_gen == -1)
726 : {
727 0 : have_auto_mipmap_gen = ogl_HaveExtension("GL_SGIS_generate_mipmap");
728 : }
729 0 : if(have_s3tc == -1)
730 : {
731 : #if CONFIG2_GLES
732 : // some GLES implementations have GL_EXT_texture_compression_dxt1
733 : // but that only supports DXT1 so we can't use it.
734 : have_s3tc = ogl_HaveExtensions(0, "GL_EXT_texture_compression_s3tc", NULL) == 0;
735 : #else
736 : // note: we don't bother checking for GL_S3_s3tc - it is incompatible
737 : // and irrelevant (was never widespread).
738 0 : have_s3tc = ogl_HaveExtensions(0, "GL_ARB_texture_compression", "GL_EXT_texture_compression_s3tc", NULL) == 0;
739 : #endif
740 : }
741 0 : if(have_anistropy == -1)
742 : {
743 0 : have_anistropy = ogl_HaveExtension("GL_EXT_texture_filter_anisotropic");
744 : }
745 :
746 : // allow app hook to make ogl_tex_override calls
747 0 : if(AH_IS_DEFINED(override_gl_upload_caps))
748 : {
749 0 : ah_override_gl_upload_caps();
750 : }
751 : // no app hook defined - have our own crack at blacklisting some hardware.
752 : else
753 : {
754 0 : const std::wstring cardName = gfx::CardName();
755 : // rationale: janwas's laptop's S3 card blows up if S3TC is used
756 : // (oh, the irony). it'd be annoying to have to share this between all
757 : // projects, hence this default implementation here.
758 0 : if(cardName == L"S3 SuperSavage/IXC 1014")
759 0 : ogl_tex_override(OGL_TEX_S3TC, OGL_TEX_DISABLE);
760 : }
761 0 : }
762 :
763 :
764 : // take care of mipmaps. if they are called for by <filter>, either
765 : // arrange for OpenGL to create them, or see to it that the Tex object
766 : // contains them (if need be, creating them in software).
767 : // sets *plevels_to_skip to influence upload behavior (depending on
768 : // whether mipmaps are needed and the quality settings).
769 : // returns 0 to indicate success; otherwise, caller must disable
770 : // mipmapping by switching filter to e.g. GL_LINEAR.
771 0 : static Status get_mipmaps(Tex* t, GLint filter, int q_flags, int* plevels_to_skip)
772 : {
773 : // decisions:
774 : // .. does filter call for uploading mipmaps?
775 0 : const bool need_mipmaps = are_mipmaps_needed(t->m_Width, t->m_Height, filter);
776 : // .. does the image data include mipmaps? (stored as separate
777 : // images after the regular texels)
778 0 : const bool includes_mipmaps = (t->m_Flags & TEX_MIPMAPS) != 0;
779 : // .. is this texture in S3TC format? (more generally, "compressed")
780 0 : const bool is_s3tc = (t->m_Flags & TEX_DXT) != 0;
781 :
782 0 : *plevels_to_skip = TEX_BASE_LEVEL_ONLY;
783 0 : if(!need_mipmaps)
784 : return INFO::OK;
785 :
786 : // image already contains pregenerated mipmaps; we need do nothing.
787 : // this is the nicest case, because they are fastest to load
788 : // (no extra processing needed) and typically filtered better than
789 : // if automatically generated.
790 0 : if(includes_mipmaps)
791 0 : *plevels_to_skip = 0; // t contains mipmaps
792 : // OpenGL supports automatic generation; we need only
793 : // activate that and upload the base image.
794 : #if !CONFIG2_GLES
795 0 : else if(have_auto_mipmap_gen)
796 : {
797 : // note: we assume GL_GENERATE_MIPMAP and GL_GENERATE_MIPMAP_SGIS
798 : // have the same values - it's heavily implied by the spec
799 : // governing 'promoted' ARB extensions and just plain makes sense.
800 0 : glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
801 : }
802 : #endif
803 : // image is S3TC-compressed and the previous 2 alternatives weren't
804 : // available; we're going to cheat and just disable mipmapping.
805 : // rationale: having tex_transform add mipmaps would be slow (since
806 : // all<->all transforms aren't implemented, it'd have to decompress
807 : // from S3TC first), and DDS images ought to include mipmaps!
808 0 : else if(is_s3tc)
809 : return ERR::FAIL; // NOWARN
810 : // image is uncompressed and we're on an old OpenGL implementation;
811 : // we will generate mipmaps in software.
812 : else
813 : {
814 0 : RETURN_STATUS_IF_ERR(t->transform_to(t->m_Flags|TEX_MIPMAPS));
815 0 : *plevels_to_skip = 0; // t contains mipmaps
816 : }
817 :
818 : // t contains mipmaps; we can apply our resolution reduction trick:
819 0 : if(*plevels_to_skip == 0)
820 : {
821 : // if OpenGL's texture dimension limit is too small, use the
822 : // higher mipmap levels. NB: the minimum guaranteed size is
823 : // far too low, and menu background textures may be large.
824 0 : GLint w = (GLint)t->m_Width, h = (GLint)t->m_Height;
825 0 : while(w > ogl_max_tex_size || h > ogl_max_tex_size)
826 : {
827 0 : (*plevels_to_skip)++;
828 0 : w /= 2; h /= 2; // doesn't matter if either dimension drops to 0
829 : }
830 :
831 : // this saves texture memory by skipping some of the lower
832 : // (high-resolution) mip levels.
833 : //
834 : // note: we don't just use GL_TEXTURE_BASE_LEVEL because it would
835 : // require uploading unused levels, which is wasteful.
836 : // .. can be expanded to reduce to 1/4, 1/8 by encoding factor in q_flags.
837 0 : if(q_flags & OGL_TEX_HALF_RES)
838 0 : (*plevels_to_skip)++;
839 : }
840 :
841 : return INFO::OK;
842 : }
843 :
844 :
845 : // tex_util_foreach_mipmap callbacks: upload the given level to OpenGL.
846 :
847 : struct UploadParams
848 : {
849 : GLenum fmt;
850 : GLint int_fmt;
851 : u32* uploaded_size;
852 : };
853 :
854 0 : static void upload_level(size_t level, size_t level_w, size_t level_h, const u8* RESTRICT level_data, size_t level_data_size, void* RESTRICT cbData)
855 : {
856 0 : const UploadParams* up = (const UploadParams*)cbData;
857 0 : glTexImage2D(GL_TEXTURE_2D, (GLint)level, up->int_fmt, (GLsizei)level_w, (GLsizei)level_h, 0, up->fmt, GL_UNSIGNED_BYTE, level_data);
858 0 : *up->uploaded_size += (u32)level_data_size;
859 0 : }
860 :
861 0 : static void upload_compressed_level(size_t level, size_t level_w, size_t level_h, const u8* RESTRICT level_data, size_t level_data_size, void* RESTRICT cbData)
862 : {
863 0 : const UploadParams* up = (const UploadParams*)cbData;
864 0 : pglCompressedTexImage2DARB(GL_TEXTURE_2D, (GLint)level, up->fmt, (GLsizei)level_w, (GLsizei)level_h, 0, (GLsizei)level_data_size, level_data);
865 0 : *up->uploaded_size += (u32)level_data_size;
866 0 : }
867 :
868 : // upload the texture in the specified (internal) format.
869 : // split out of ogl_tex_upload because it was too big.
870 : //
871 : // pre: <t> is valid for OpenGL use; texture is bound.
872 0 : static void upload_impl(Tex* t, GLenum fmt, GLint int_fmt, int levels_to_skip, u32* uploaded_size)
873 : {
874 0 : const GLsizei w = (GLsizei)t->m_Width;
875 0 : const GLsizei h = (GLsizei)t->m_Height;
876 0 : const size_t bpp = t->m_Bpp;
877 0 : const u8* data = (const u8*)t->get_data();
878 0 : const UploadParams up = { fmt, int_fmt, uploaded_size };
879 :
880 0 : if(t->m_Flags & TEX_DXT)
881 0 : tex_util_foreach_mipmap(w, h, bpp, data, levels_to_skip, 4, upload_compressed_level, (void*)&up);
882 : else
883 0 : tex_util_foreach_mipmap(w, h, bpp, data, levels_to_skip, 1, upload_level, (void*)&up);
884 0 : }
885 :
886 :
887 : // upload the texture to OpenGL.
888 : // if not 0, parameters override the following:
889 : // fmt_ovr : OpenGL format (e.g. GL_RGB) decided from bpp / Tex flags;
890 : // q_flags_ovr : global default "quality vs. performance" flags;
891 : // int_fmt_ovr : internal format (e.g. GL_RGB8) decided from fmt / q_flags.
892 : //
893 : // side effects:
894 : // - enables texturing on TMU 0 and binds the texture to it;
895 : // - frees the texel data! see ogl_tex_get_data.
896 0 : Status ogl_tex_upload(const Handle ht, GLenum fmt_ovr, int q_flags_ovr, GLint int_fmt_ovr)
897 : {
898 0 : ONCE(detect_gl_upload_caps());
899 :
900 0 : H_DEREF(ht, OglTex, ot);
901 0 : Tex* t = &ot->t;
902 0 : ENSURE(q_flags_valid(q_flags_ovr));
903 : // we don't bother verifying *fmt_ovr - there are too many values
904 :
905 : // upload already happened; no work to do.
906 : // (this also happens if a cached texture is "loaded")
907 0 : if(ot->flags & OT_IS_UPLOADED)
908 : return INFO::OK;
909 :
910 0 : if(ot->flags & OT_TEX_VALID)
911 : {
912 : // decompress S3TC if that's not supported by OpenGL.
913 0 : if((t->m_Flags & TEX_DXT) && !have_s3tc)
914 0 : (void)t->transform_to(t->m_Flags & ~TEX_DXT);
915 :
916 : // determine fmt and int_fmt, allowing for user override.
917 0 : ot->fmt = choose_fmt(t->m_Bpp, t->m_Flags);
918 0 : if(fmt_ovr) ot->fmt = fmt_ovr;
919 0 : if(q_flags_ovr) ot->q_flags = q_flags_ovr;
920 0 : ot->int_fmt = choose_int_fmt(ot->fmt, ot->q_flags);
921 0 : if(int_fmt_ovr) ot->int_fmt = int_fmt_ovr;
922 :
923 0 : ot->uploaded_size = 0;
924 :
925 : // now actually send to OpenGL:
926 0 : ogl_WarnIfError();
927 0 : {
928 : // (note: we know ht is valid due to H_DEREF, but ogl_tex_bind can
929 : // fail in debug builds if OglTex.id isn't a valid texture name)
930 0 : RETURN_STATUS_IF_ERR(ogl_tex_bind(ht, ot->tmu));
931 0 : int levels_to_skip;
932 0 : if(get_mipmaps(t, ot->state.filter, ot->q_flags, &levels_to_skip) < 0)
933 : // error => disable mipmapping
934 0 : ot->state.filter = GL_LINEAR;
935 : // (note: if first time, applies our defaults/previous overrides;
936 : // otherwise, replays all state changes)
937 0 : state_latch(&ot->state);
938 0 : upload_impl(t, ot->fmt, ot->int_fmt, levels_to_skip, &ot->uploaded_size);
939 : }
940 0 : ogl_WarnIfError();
941 :
942 0 : ot->flags |= OT_IS_UPLOADED;
943 :
944 : // see rationale for <refs> at declaration of OglTex.
945 0 : intptr_t refs = h_get_refcnt(ht);
946 0 : if(refs == 1)
947 : {
948 : // note: we verify above that OT_TEX_VALID is set
949 0 : ot->flags &= ~OT_TEX_VALID;
950 : }
951 : }
952 :
953 0 : ot->flags |= OT_NEED_AUTO_UPLOAD;
954 :
955 0 : return INFO::OK;
956 : }
957 :
958 :
959 : //----------------------------------------------------------------------------
960 : // getters
961 : //----------------------------------------------------------------------------
962 :
963 : // retrieve texture dimensions and bits per pixel.
964 : // all params are optional and filled if non-NULL.
965 0 : Status ogl_tex_get_size(Handle ht, size_t* w, size_t* h, size_t* bpp)
966 : {
967 0 : H_DEREF(ht, OglTex, ot);
968 :
969 0 : if(w)
970 0 : *w = ot->t.m_Width;
971 0 : if(h)
972 0 : *h = ot->t.m_Height;
973 0 : if(bpp)
974 0 : *bpp = ot->t.m_Bpp;
975 : return INFO::OK;
976 : }
977 :
978 :
979 : // retrieve TexFlags and the corresponding OpenGL format.
980 : // the latter is determined during ogl_tex_upload and is 0 before that.
981 : // all params are optional and filled if non-NULL.
982 0 : Status ogl_tex_get_format(Handle ht, size_t* flags, GLenum* fmt)
983 : {
984 0 : H_DEREF(ht, OglTex, ot);
985 :
986 0 : if(flags)
987 0 : *flags = ot->t.m_Flags;
988 0 : if(fmt)
989 : {
990 0 : ENSURE(ot->flags & OT_IS_UPLOADED);
991 0 : *fmt = ot->fmt;
992 : }
993 : return INFO::OK;
994 : }
995 :
996 :
997 : // retrieve pointer to texel data.
998 : //
999 : // note: this memory is freed after a successful ogl_tex_upload for
1000 : // this texture. after that, the pointer we retrieve is NULL but
1001 : // the function doesn't fail (negative return value) by design.
1002 : // if you still need to get at the data, add a reference before
1003 : // uploading it or read directly from OpenGL (discouraged).
1004 0 : Status ogl_tex_get_data(Handle ht, u8** p)
1005 : {
1006 0 : H_DEREF(ht, OglTex, ot);
1007 :
1008 0 : *p = ot->t.get_data();
1009 0 : return INFO::OK;
1010 : }
1011 :
1012 0 : Status ogl_tex_get_uploaded_size(Handle ht, size_t* size)
1013 : {
1014 0 : H_DEREF(ht, OglTex, ot);
1015 :
1016 0 : *size = ot->uploaded_size;
1017 0 : return INFO::OK;
1018 : }
1019 :
1020 : // retrieve color of 1x1 mipmap level
1021 0 : extern Status ogl_tex_get_average_color(Handle ht, u32* p)
1022 : {
1023 0 : H_DEREF(ht, OglTex, ot);
1024 0 : warn_if_uploaded(ht, ot);
1025 :
1026 0 : *p = ot->t.get_average_color();
1027 0 : return INFO::OK;
1028 : }
1029 :
1030 : //----------------------------------------------------------------------------
1031 : // misc API
1032 : //----------------------------------------------------------------------------
1033 :
1034 : // bind the texture to the specified unit [number] in preparation for
1035 : // using it in rendering. if <ht> is 0, texturing is disabled instead.
1036 : //
1037 : // side effects:
1038 : // - changes the active texture unit;
1039 : // - (if return value is 0:) texturing was enabled/disabled on that unit.
1040 : //
1041 : // notes:
1042 : // - assumes multitexturing is available.
1043 : // - not necessary before calling ogl_tex_upload!
1044 : // - on error, the unit's texture state is unchanged; see implementation.
1045 0 : Status ogl_tex_bind(Handle ht, size_t unit)
1046 : {
1047 : // note: there are many call sites of glActiveTextureARB, so caching
1048 : // those and ignoring redundant sets isn't feasible.
1049 0 : pglActiveTextureARB((int)(GL_TEXTURE0+unit));
1050 :
1051 : // special case: disable texturing
1052 0 : if(ht == 0)
1053 : {
1054 : #if !CONFIG2_GLES
1055 0 : glDisable(GL_TEXTURE_2D);
1056 : #endif
1057 0 : return INFO::OK;
1058 : }
1059 :
1060 : // if this fails, the texture unit's state remains unchanged.
1061 : // we don't bother catching that and disabling texturing because a
1062 : // debug warning is raised anyway, and it's quite unlikely.
1063 0 : H_DEREF(ht, OglTex, ot);
1064 0 : ot->tmu = (u8)unit;
1065 :
1066 : // if 0, there's a problem in the OglTex reload/dtor logic.
1067 : // binding it results in whiteness, which can have many causes;
1068 : // we therefore complain so this one can be ruled out.
1069 0 : ENSURE(ot->id != 0);
1070 :
1071 : #if !CONFIG2_GLES
1072 0 : glEnable(GL_TEXTURE_2D);
1073 : #endif
1074 0 : glBindTexture(GL_TEXTURE_2D, ot->id);
1075 0 : return INFO::OK;
1076 : }
1077 :
1078 0 : Status ogl_tex_get_texture_id(Handle ht, GLuint* id)
1079 : {
1080 0 : *id = 0;
1081 0 : H_DEREF(ht, OglTex, ot);
1082 0 : *id = ot->id;
1083 0 : return INFO::OK;
1084 : }
1085 :
1086 : // apply the specified transforms (as in tex_transform) to the image.
1087 : // must be called before uploading (raises a warning if called afterwards).
1088 0 : Status ogl_tex_transform(Handle ht, size_t transforms)
1089 : {
1090 0 : H_DEREF(ht, OglTex, ot);
1091 0 : Status ret = ot->t.transform(transforms);
1092 0 : return ret;
1093 : }
1094 :
1095 :
1096 : // change the pixel format to that specified by <new_flags>.
1097 : // (note: this is equivalent to ogl_tex_transform(ht, ht_flags^new_flags).
1098 0 : Status ogl_tex_transform_to(Handle ht, size_t new_flags)
1099 : {
1100 0 : H_DEREF(ht, OglTex, ot);
1101 0 : Status ret = ot->t.transform_to(new_flags);
1102 0 : return ret;
1103 : }
1104 :
1105 :
1106 : // return whether native S3TC support is available
1107 0 : bool ogl_tex_has_s3tc()
1108 : {
1109 : // ogl_tex_upload must be called before this
1110 0 : ENSURE(have_s3tc != -1);
1111 :
1112 0 : return (have_s3tc != 0);
1113 : }
1114 :
1115 : // return whether anisotropic filtering support is available
1116 0 : bool ogl_tex_has_anisotropy()
1117 : {
1118 : // ogl_tex_upload must be called before this
1119 0 : ENSURE(have_anistropy != -1);
1120 :
1121 0 : return (have_anistropy != 0);
1122 : }
|