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