Index: ps/trunk/source/lib/res/graphics/ogl_tex.cpp =================================================================== --- ps/trunk/source/lib/res/graphics/ogl_tex.cpp (revision 26367) +++ ps/trunk/source/lib/res/graphics/ogl_tex.cpp (nonexistent) @@ -1,1107 +0,0 @@ -/* Copyright (C) 2021 Wildfire Games. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -#include "precompiled.h" -#include "ogl_tex.h" - -#include "lib/app_hooks.h" -#include "lib/bits.h" -#include "lib/ogl.h" -#include "lib/res/h_mgr.h" -#include "lib/tex/tex.h" - -#include - -//---------------------------------------------------------------------------- -// OpenGL helper routines -//---------------------------------------------------------------------------- - -static bool filter_valid(GLint filter) -{ - switch(filter) - { - case GL_NEAREST: - case GL_LINEAR: - case GL_NEAREST_MIPMAP_NEAREST: - case GL_LINEAR_MIPMAP_NEAREST: - case GL_NEAREST_MIPMAP_LINEAR: - case GL_LINEAR_MIPMAP_LINEAR: - return true; - default: - return false; - } -} - - -static bool wrap_valid(GLint wrap) -{ - switch(wrap) - { -#if !CONFIG2_GLES - case GL_CLAMP: -#endif - case GL_CLAMP_TO_BORDER: - case GL_CLAMP_TO_EDGE: - case GL_REPEAT: - case GL_MIRRORED_REPEAT: - return true; - default: - return false; - } -} - - -static bool are_mipmaps_needed(size_t width, size_t height, GLint filter) -{ - // can't upload the entire texture; we're going to skip some - // levels until it no longer exceeds the OpenGL dimension limit. - if((GLint)width > ogl_max_tex_size || (GLint)height > ogl_max_tex_size) - return true; - - switch(filter) - { - case GL_NEAREST_MIPMAP_NEAREST: - case GL_LINEAR_MIPMAP_NEAREST: - case GL_NEAREST_MIPMAP_LINEAR: - case GL_LINEAR_MIPMAP_LINEAR: - return true; - default: - return false; - } -} - - -static bool fmt_is_s3tc(GLenum fmt) -{ - switch(fmt) - { - case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: - case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: - case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: - case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: - return true; - default: - return false; - } -} - - -// determine OpenGL texture format, given and Tex . -static GLint choose_fmt(size_t bpp, size_t flags) -{ - const bool alpha = (flags & TEX_ALPHA) != 0; - const bool bgr = (flags & TEX_BGR ) != 0; - const bool grey = (flags & TEX_GREY ) != 0; - const size_t dxt = flags & TEX_DXT; - - // S3TC - if(dxt != 0) - { - switch(dxt) - { - case DXT1A: - return GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; - case 1: - return GL_COMPRESSED_RGB_S3TC_DXT1_EXT; - case 3: - return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; - case 5: - return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; - default: - DEBUG_WARN_ERR(ERR::LOGIC); // invalid DXT value - return 0; - } - } - - // uncompressed - switch(bpp) - { - case 8: - ENSURE(grey); - return GL_LUMINANCE; - case 16: - return GL_LUMINANCE_ALPHA; - case 24: - ENSURE(!alpha); -#if CONFIG2_GLES - // GLES never supports BGR - ENSURE(!bgr); - return GL_RGB; -#else - return bgr? GL_BGR : GL_RGB; -#endif - case 32: - ENSURE(alpha); - // GLES can support BGRA via GL_EXT_texture_format_BGRA8888 - // (TODO: can we rely on support for that extension?) - return bgr? GL_BGRA_EXT : GL_RGBA; - default: - DEBUG_WARN_ERR(ERR::LOGIC); // invalid bpp - return 0; - } - - UNREACHABLE; -} - - -//---------------------------------------------------------------------------- -// quality mechanism -//---------------------------------------------------------------------------- - -static GLint default_filter = GL_LINEAR; // one of the GL *minify* filters -static int default_q_flags = OGL_TEX_FULL_QUALITY; // OglTexQualityFlags - -static bool q_flags_valid(int q_flags) -{ - const size_t bits = OGL_TEX_FULL_QUALITY|OGL_TEX_HALF_BPP|OGL_TEX_HALF_RES; - // unrecognized bits are set - invalid - if((q_flags & ~bits) != 0) - return false; - // "full quality" but other reduction bits are set - invalid - if(q_flags & OGL_TEX_FULL_QUALITY && q_flags & ~OGL_TEX_FULL_QUALITY) - return false; - return true; -} - - -// change default settings - these affect performance vs. quality. -// may be overridden for individual textures via parameter to -// ogl_tex_upload or ogl_tex_set_filter, respectively. -// -// pass 0 to keep the current setting; defaults and legal values are: -// - q_flags: OGL_TEX_FULL_QUALITY; combination of OglTexQualityFlags -// - filter: GL_LINEAR; any valid OpenGL minification filter -void ogl_tex_set_defaults(int q_flags, GLint filter) -{ - if(q_flags) - { - ENSURE(q_flags_valid(q_flags)); - default_q_flags = q_flags; - } - - if(filter) - { - ENSURE(filter_valid(filter)); - default_filter = filter; - } -} - - -// choose an internal format for based on the given q_flags. -static GLint choose_int_fmt(GLenum fmt, int q_flags) -{ - // true => 4 bits per component; otherwise, 8 - const bool half_bpp = (q_flags & OGL_TEX_HALF_BPP) != 0; - - // early-out for S3TC textures: they don't need an internal format - // (because upload is via glCompressedTexImage2DARB), but we must avoid - // triggering the default case below. we might as well return a - // meaningful value (i.e. int_fmt = fmt). - if(fmt_is_s3tc(fmt)) - return fmt; - -#if CONFIG2_GLES - - UNUSED2(half_bpp); - - // GLES only supports internal format == external format - return fmt; - -#else - - switch(fmt) - { - // 8bpp - case GL_LUMINANCE: - return half_bpp? GL_LUMINANCE4 : GL_LUMINANCE8; - case GL_INTENSITY: - return half_bpp? GL_INTENSITY4 : GL_INTENSITY8; - case GL_ALPHA: - return half_bpp? GL_ALPHA4 : GL_ALPHA8; - - // 16bpp - case GL_LUMINANCE_ALPHA: - return half_bpp? GL_LUMINANCE4_ALPHA4 : GL_LUMINANCE8_ALPHA8; - - // 24bpp - case GL_RGB: - case GL_BGR: // note: BGR can't be used as internal format - return half_bpp? GL_RGB4 : GL_RGB8; - - // 32bpp - case GL_RGBA: - case GL_BGRA: // note: BGRA can't be used as internal format - return half_bpp? GL_RGBA4 : GL_RGBA8; - - default: - { - wchar_t buf[100]; - swprintf_s(buf, ARRAY_SIZE(buf), L"choose_int_fmt: fmt 0x%x isn't covered! please add it", fmt); - DEBUG_DISPLAY_ERROR(buf); - DEBUG_WARN_ERR(ERR::LOGIC); // given fmt isn't covered! please add it. - // fall back to a reasonable default - return half_bpp? GL_RGB4 : GL_RGB8; - } - } - - UNREACHABLE; - -#endif // #if CONFIG2_GLES -} - - -//---------------------------------------------------------------------------- -// texture state to allow seamless reload -//---------------------------------------------------------------------------- - -// see "Texture Parameters" in docs. - -// all GL state tied to the texture that must be reapplied after reload. -// (this mustn't get too big, as it's stored in the already sizeable OglTex) -struct OglTexState -{ - // glTexParameter - // note: there are more options, but they do not look to - // be important and will not be applied after a reload! - // in particular, LOD_BIAS isn't needed because that is set for - // the entire texturing unit via glTexEnv. - // .. texture filter - // note: this is the minification filter value; magnification filter - // is GL_NEAREST if it's GL_NEAREST, otherwise GL_LINEAR. - // we don't store mag_filter explicitly because it - // doesn't appear useful - either apps can tolerate LINEAR, or - // mipmaps aren't called for and filter could be NEAREST anyway). - GLint filter; - // .. wrap mode - GLint wrap_s; - GLint wrap_t; - // .. anisotropy - // note: ignored unless EXT_texture_filter_anisotropic is supported. - GLfloat anisotropy; -}; - - -// fill the given state object with default values. -static void state_set_to_defaults(OglTexState* ots) -{ - ots->filter = default_filter; - ots->wrap_s = GL_REPEAT; - ots->wrap_t = GL_REPEAT; - ots->anisotropy = 1.0f; -} - - -// send all state to OpenGL (actually the currently bound texture). -// called from ogl_tex_upload. -static void state_latch(OglTexState* ots) -{ - // filter - const GLint filter = ots->filter; - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); - const GLint mag_filter = (filter == GL_NEAREST)? GL_NEAREST : GL_LINEAR; - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter); - - // wrap - const GLint wrap_s = ots->wrap_s; - const GLint wrap_t = ots->wrap_t; - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap_s); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap_t); - // .. only CLAMP and REPEAT are guaranteed to be available. - // if we're using one of the others, we squelch the error that - // may have resulted if this GL implementation is old. -#if !CONFIG2_GLES - if((wrap_s != GL_CLAMP && wrap_s != GL_REPEAT) || (wrap_t != GL_CLAMP && wrap_t != GL_REPEAT)) - ogl_SquelchError(GL_INVALID_ENUM); -#endif - - // anisotropy - const GLfloat anisotropy = ots->anisotropy; - if (anisotropy != 1.0f && ogl_tex_has_anisotropy()) - { - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisotropy); - } -} - - -//---------------------------------------------------------------------------- -// texture resource object -//---------------------------------------------------------------------------- - -// ideally we would split OglTex into data and state objects as in -// SndData / VSrc. this gives us the benefits of caching while still -// leaving each "instance" (state object, which owns a data reference) -// free to change its state. however, unlike in OpenAL, there is no state -// independent of the data object - all parameters are directly tied to the -// GL texture object. therefore, splitting them up is impossible. -// (we shouldn't even keep the texel data in memory since that's already -// covered by the FS cache). -// -// given that multiple "instances" share the state stored here, we conclude: -// - a refcount is necessary to prevent ogl_tex_upload from freeing -// as long as other instances are active. -// - concurrent use risks cross-talk (if the 2nd "instance" changes state and -// the first is reloaded, its state may change to that of the 2nd) -// -// as bad as it sounds, the latter issue isn't a problem: we do not expect -// multiple instances of the same texture where someone changes its filter. -// even if it is reloaded, the differing state is not critical. -// the alternative is even worse: disabling *all* caching/reuse would -// really hurt performance and h_mgr doesn't support only disallowing -// reuse of active objects (this would break the index lookup code, since -// multiple instances may then exist). - -// note: make sure these values fit inside OglTex.flags (only 16 bits) -enum OglTexFlags -{ - // "the texture is currently uploaded"; reset in dtor. - OT_IS_UPLOADED = 1, - - // "the enclosed Tex object is valid and holds a texture"; - // reset in dtor and after ogl_tex_upload's tex_free. - OT_TEX_VALID = 2, - //size_t tex_valid : 1; - - // "reload() should automatically re-upload the texture" (because - // it had been uploaded before the reload); never reset. - OT_NEED_AUTO_UPLOAD = 4, - - // (used for validating flags) - OT_ALL_FLAGS = OT_IS_UPLOADED|OT_TEX_VALID|OT_NEED_AUTO_UPLOAD -}; - -struct OglTex -{ - Tex t; - - // allocated by OglTex_reload; indicates the texture is currently uploaded. - GLuint id; - - // ogl_tex_upload calls choose_fmt to determine these from . - // however, its caller may override those values via parameters. - // note: these are stored here to allow retrieving via ogl_tex_get_format; - // they are only used within ogl_tex_upload. - GLenum fmt; - GLint int_fmt; - - OglTexState state; - - // OglTexQualityFlags - u8 q_flags; - - // to which Texture Mapping Unit was this bound? - u8 tmu; - - u16 flags; - - u32 uploaded_size; -}; - -H_TYPE_DEFINE(OglTex); - -static void OglTex_init(OglTex* ot, va_list args) -{ - Tex* wrapped_tex = va_arg(args, Tex*); - if(wrapped_tex) - { - ot->t = *wrapped_tex; - // indicate ot->t is now valid, thus skipping loading from file. - // note: ogl_tex_wrap prevents actual reloads from happening. - ot->flags |= OT_TEX_VALID; - } - - state_set_to_defaults(&ot->state); - ot->q_flags = default_q_flags; -} - -static void OglTex_dtor(OglTex* ot) -{ - if(ot->flags & OT_TEX_VALID) - { - ot->t.free(); - ot->flags &= ~OT_TEX_VALID; - } - - // note: do not check if OT_IS_UPLOADED is set, because we allocate - // OglTex.id without necessarily having done an upload. - glDeleteTextures(1, &ot->id); - ot->id = 0; - ot->flags &= ~OT_IS_UPLOADED; - ot->uploaded_size = 0; -} - -static Status OglTex_reload(OglTex* ot, const PIVFS& vfs, const VfsPath& pathname, Handle h) -{ - // we're reusing a freed but still in-memory OglTex object - if(ot->flags & OT_IS_UPLOADED) - return INFO::OK; - - // if we don't already have the texture in memory (*), load from file. - // * this happens if the texture is "wrapped". - if(!(ot->flags & OT_TEX_VALID)) - { - std::shared_ptr file; size_t fileSize; - RETURN_STATUS_IF_ERR(vfs->LoadFile(pathname, file, fileSize)); - if(ot->t.decode(file, fileSize) >= 0) - ot->flags |= OT_TEX_VALID; - } - - glGenTextures(1, &ot->id); - - // if it had already been uploaded before this reload, - // re-upload it (this also does state_latch). - if(ot->flags & OT_NEED_AUTO_UPLOAD) - (void)ogl_tex_upload(h); - -#if KHR_DEBUG_ENABLED - const std::string name = pathname.string8(); - glBindTexture(GL_TEXTURE_2D, ot->id); - glObjectLabel(GL_TEXTURE, ot->id, name.size(), name.c_str()); -#endif - - - return INFO::OK; -} - -static Status OglTex_validate(const OglTex* ot) -{ - if(ot->flags & OT_TEX_VALID) - { - RETURN_STATUS_IF_ERR(ot->t.validate()); - - // width, height - // (note: this is done here because tex.cpp doesn't impose any - // restrictions on dimensions, while OpenGL does). - size_t w = ot->t.m_Width; - size_t h = ot->t.m_Height; - // .. == 0; texture file probably not loaded successfully. - if(w == 0 || h == 0) - WARN_RETURN(ERR::_11); - // .. not power-of-2. - // note: we can't work around this because both NV_texture_rectangle - // and subtexture require work for the client (changing tex coords). - // TODO: ARB_texture_non_power_of_two - if(!is_pow2(w) || !is_pow2(h)) - WARN_RETURN(ERR::_13); - - // no longer verify dimensions are less than ogl_max_tex_size, - // because we just use the higher mip levels in that case. - } - - // texture state - if(!filter_valid(ot->state.filter)) - WARN_RETURN(ERR::_14); - if(!wrap_valid(ot->state.wrap_s)) - WARN_RETURN(ERR::_15); - if(!wrap_valid(ot->state.wrap_t)) - WARN_RETURN(ERR::_16); - - // misc - if(!q_flags_valid(ot->q_flags)) - WARN_RETURN(ERR::_17); - if(ot->tmu >= 128) // unexpected that there will ever be this many - WARN_RETURN(ERR::_18); - if(ot->flags > OT_ALL_FLAGS) - WARN_RETURN(ERR::_19); - // .. note: don't check ot->fmt and ot->int_fmt - they aren't set - // until during ogl_tex_upload. - - return INFO::OK; -} - -static Status OglTex_to_string(const OglTex* ot, wchar_t* buf) -{ - swprintf_s(buf, H_STRING_LEN, L"OglTex id=%u flags=%x", ot->id, (unsigned int)ot->flags); - return INFO::OK; -} - - -// load and return a handle to the texture given in . -// for a list of supported formats, see tex.h's tex_load. -Handle ogl_tex_load(const PIVFS& vfs, const VfsPath& pathname, size_t flags) -{ - Tex* wrapped_tex = 0; // we're loading from file - return h_alloc(H_OglTex, vfs, pathname, flags, wrapped_tex); -} - -// make the given Tex object ready for use as an OpenGL texture -// and return a handle to it. this will be as if its contents -// had been loaded by ogl_tex_load. -// -// we need only add bookkeeping information and "wrap" it in -// a resource object (accessed via Handle), hence the name. -// -// isn't strictly needed but should describe the texture so that -// h_filename will return a meaningful comment for debug purposes. -// note: because we cannot guarantee that callers will pass distinct -// "filenames", caching is disabled for the created object. this avoids -// mistakenly reusing previous objects that share the same comment. -Handle ogl_tex_wrap(Tex* t, const PIVFS& vfs, const VfsPath& pathname, size_t flags) -{ - // this object may not be backed by a file ("may", because - // someone could do tex_load and then ogl_tex_wrap). - // if h_mgr asks for a reload, the dtor will be called but - // we won't be able to reconstruct it. therefore, disallow reloads. - // (they are improbable anyway since caller is supposed to pass a - // 'descriptive comment' instead of filename, but don't rely on that) - // also disable caching as explained above. - flags |= RES_DISALLOW_RELOAD|RES_NO_CACHE; - return h_alloc(H_OglTex, vfs, pathname, flags, t); -} - - -// free all resources associated with the texture and make further -// use of it impossible. (subject to refcount) -Status ogl_tex_free(Handle& ht) -{ - return h_free(ht, H_OglTex); -} - - -//---------------------------------------------------------------------------- -// state setters (see "Texture Parameters" in docs) -//---------------------------------------------------------------------------- - -// we require the below functions be called before uploading; this avoids -// potentially redundant glTexParameter calls (we'd otherwise need to always -// set defaults because we don't know if an override is forthcoming). - -// raise a debug warning if the texture has already been uploaded -// (except in the few cases where this is allowed; see below). -// this is so that you will notice incorrect usage - only one instance of a -// texture should be active at a time, because otherwise they vie for -// control of one shared OglTexState. -static void warn_if_uploaded(Handle ht, const OglTex* ot) -{ -#ifndef NDEBUG - // we do not require users of this module to remember if they've - // already uploaded a texture (inconvenient). since they also can't - // tell if the texture was newly loaded (due to h_alloc interface), - // we have to squelch this warning in 2 cases: - // - it's ogl_tex_loaded several times (i.e. refcount > 1) and the - // caller (typically a higher-level LoadTexture) is setting filter etc. - // - caller is using our Handle as a caching mechanism, and calls - // ogl_tex_set_* before every use of the texture. note: this - // need not fall under the above check, e.g. if freed but cached. - // workaround is that ogl_tex_set_* won't call us if the - // same state values are being set (harmless anyway). - intptr_t refs = h_get_refcnt(ht); - if(refs > 1) - return; // don't complain - - if(ot->flags & OT_IS_UPLOADED) - DEBUG_WARN_ERR(ERR::LOGIC); // ogl_tex_set_*: texture already uploaded and shouldn't be changed -#else - // (prevent warnings; the alternative of wrapping all call sites in - // #ifndef is worse) - UNUSED2(ht); - UNUSED2(ot); -#endif -} - - -// override default filter (as set above) for this texture. -// must be called before uploading (raises a warning if called afterwards). -// filter is as defined by OpenGL; it is applied for both minification and -// magnification (for rationale and details, see OglTexState) -Status ogl_tex_set_filter(Handle ht, GLint filter) -{ - H_DEREF(ht, OglTex, ot); - - if(!filter_valid(filter)) - WARN_RETURN(ERR::INVALID_PARAM); - - if(ot->state.filter != filter) - { - warn_if_uploaded(ht, ot); - ot->state.filter = filter; - } - return INFO::OK; -} - - -// override default wrap mode (GL_REPEAT) for this texture. -// must be called before uploading (raises a warning if called afterwards). -// wrap is as defined by OpenGL. -Status ogl_tex_set_wrap(Handle ht, GLint wrap_s, GLint wrap_t) -{ - H_DEREF(ht, OglTex, ot); - - if(!wrap_valid(wrap_s)) - WARN_RETURN(ERR::INVALID_PARAM); - - if(!wrap_valid(wrap_t)) - WARN_RETURN(ERR::INVALID_PARAM); - - if(ot->state.wrap_s != wrap_s || ot->state.wrap_t != wrap_t) - { - warn_if_uploaded(ht, ot); - ot->state.wrap_s = wrap_s; - ot->state.wrap_t = wrap_t; - } - return INFO::OK; -} - - -// override default anisotropy for this texture. -// must be called before uploading (raises a warning if called afterwards). -Status ogl_tex_set_anisotropy(Handle ht, GLfloat anisotropy) -{ - H_DEREF(ht, OglTex, ot); - - if(anisotropy < 1.0f) - WARN_RETURN(ERR::INVALID_PARAM); - - if(ot->state.anisotropy != anisotropy) - { - warn_if_uploaded(ht, ot); - ot->state.anisotropy = anisotropy; - } - return INFO::OK; -} - - -//---------------------------------------------------------------------------- -// upload -//---------------------------------------------------------------------------- - -// OpenGL has several features that are helpful for uploading but not -// available in all implementations. we check for their presence but -// provide for user override (in case they don't work on a card/driver -// combo we didn't test). - -// tristate; -1 is undecided -static int have_auto_mipmap_gen = -1; -static int have_s3tc = -1; -static int have_anistropy = -1; - -// override the default decision and force/disallow use of the -// given feature. -void ogl_tex_override(OglTexOverrides what, OglTexAllow allow) -{ - ENSURE(allow == OGL_TEX_ENABLE || allow == OGL_TEX_DISABLE); - const bool enable = (allow == OGL_TEX_ENABLE); - - switch(what) - { - case OGL_TEX_S3TC: - have_s3tc = enable; - break; - case OGL_TEX_AUTO_MIPMAP_GEN: - have_auto_mipmap_gen = enable; - break; - case OGL_TEX_ANISOTROPY: - have_anistropy = enable; - break; - default: - DEBUG_WARN_ERR(ERR::LOGIC); // invalid - break; - } -} - - -// detect caps (via OpenGL extension list) and give an app_hook the chance to -// override this (e.g. via list of card/driver combos on which S3TC breaks). -// called once from the first ogl_tex_upload. -static void detect_gl_upload_caps() -{ - // note: gfx_card will be empty if running in quickstart mode; - // in that case, we won't be able to check for known faulty cards. - - // detect features, but only change the variables if they were at - // "undecided" (if overrides were set before this, they must remain). - if(have_auto_mipmap_gen == -1) - { - have_auto_mipmap_gen = ogl_HaveExtension("GL_SGIS_generate_mipmap"); - } - if(have_s3tc == -1) - { -#if CONFIG2_GLES - // some GLES implementations have GL_EXT_texture_compression_dxt1 - // but that only supports DXT1 so we can't use it. - have_s3tc = ogl_HaveExtensions(0, "GL_EXT_texture_compression_s3tc", NULL) == 0; -#else - // note: we don't bother checking for GL_S3_s3tc - it is incompatible - // and irrelevant (was never widespread). - have_s3tc = ogl_HaveExtensions(0, "GL_ARB_texture_compression", "GL_EXT_texture_compression_s3tc", NULL) == 0; -#endif - } - if(have_anistropy == -1) - { - have_anistropy = ogl_HaveExtension("GL_EXT_texture_filter_anisotropic"); - } -} - - -// take care of mipmaps. if they are called for by , either -// arrange for OpenGL to create them, or see to it that the Tex object -// contains them (if need be, creating them in software). -// sets *plevels_to_skip to influence upload behavior (depending on -// whether mipmaps are needed and the quality settings). -// returns 0 to indicate success; otherwise, caller must disable -// mipmapping by switching filter to e.g. GL_LINEAR. -static Status get_mipmaps(Tex* t, GLint filter, int q_flags, int* plevels_to_skip) -{ - // decisions: - // .. does filter call for uploading mipmaps? - const bool need_mipmaps = are_mipmaps_needed(t->m_Width, t->m_Height, filter); - // .. does the image data include mipmaps? (stored as separate - // images after the regular texels) - const bool includes_mipmaps = (t->m_Flags & TEX_MIPMAPS) != 0; - // .. is this texture in S3TC format? (more generally, "compressed") - const bool is_s3tc = (t->m_Flags & TEX_DXT) != 0; - - *plevels_to_skip = TEX_BASE_LEVEL_ONLY; - if(!need_mipmaps) - return INFO::OK; - - // image already contains pregenerated mipmaps; we need do nothing. - // this is the nicest case, because they are fastest to load - // (no extra processing needed) and typically filtered better than - // if automatically generated. - if(includes_mipmaps) - *plevels_to_skip = 0; // t contains mipmaps - // OpenGL supports automatic generation; we need only - // activate that and upload the base image. -#if !CONFIG2_GLES - else if(have_auto_mipmap_gen) - { - // note: we assume GL_GENERATE_MIPMAP and GL_GENERATE_MIPMAP_SGIS - // have the same values - it's heavily implied by the spec - // governing 'promoted' ARB extensions and just plain makes sense. - glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE); - } -#endif - // image is S3TC-compressed and the previous 2 alternatives weren't - // available; we're going to cheat and just disable mipmapping. - // rationale: having tex_transform add mipmaps would be slow (since - // all<->all transforms aren't implemented, it'd have to decompress - // from S3TC first), and DDS images ought to include mipmaps! - else if(is_s3tc) - return ERR::FAIL; // NOWARN - // image is uncompressed and we're on an old OpenGL implementation; - // we will generate mipmaps in software. - else - { - RETURN_STATUS_IF_ERR(t->transform_to(t->m_Flags|TEX_MIPMAPS)); - *plevels_to_skip = 0; // t contains mipmaps - } - - // t contains mipmaps; we can apply our resolution reduction trick: - if(*plevels_to_skip == 0) - { - // if OpenGL's texture dimension limit is too small, use the - // higher mipmap levels. NB: the minimum guaranteed size is - // far too low, and menu background textures may be large. - GLint w = (GLint)t->m_Width, h = (GLint)t->m_Height; - while(w > ogl_max_tex_size || h > ogl_max_tex_size) - { - (*plevels_to_skip)++; - w /= 2; h /= 2; // doesn't matter if either dimension drops to 0 - } - - // this saves texture memory by skipping some of the lower - // (high-resolution) mip levels. - // - // note: we don't just use GL_TEXTURE_BASE_LEVEL because it would - // require uploading unused levels, which is wasteful. - // .. can be expanded to reduce to 1/4, 1/8 by encoding factor in q_flags. - if(q_flags & OGL_TEX_HALF_RES) - (*plevels_to_skip)++; - } - - return INFO::OK; -} - - -// tex_util_foreach_mipmap callbacks: upload the given level to OpenGL. - -struct UploadParams -{ - GLenum fmt; - GLint int_fmt; - u32* uploaded_size; -}; - -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) -{ - const UploadParams* up = (const UploadParams*)cbData; - glTexImage2D(GL_TEXTURE_2D, (GLint)level, up->int_fmt, (GLsizei)level_w, (GLsizei)level_h, 0, up->fmt, GL_UNSIGNED_BYTE, level_data); - *up->uploaded_size += (u32)level_data_size; -} - -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) -{ - const UploadParams* up = (const UploadParams*)cbData; - glCompressedTexImage2DARB(GL_TEXTURE_2D, (GLint)level, up->fmt, (GLsizei)level_w, (GLsizei)level_h, 0, (GLsizei)level_data_size, level_data); - *up->uploaded_size += (u32)level_data_size; -} - -// upload the texture in the specified (internal) format. -// split out of ogl_tex_upload because it was too big. -// -// pre: is valid for OpenGL use; texture is bound. -static void upload_impl(Tex* t, GLenum fmt, GLint int_fmt, int levels_to_skip, u32* uploaded_size) -{ - const GLsizei w = (GLsizei)t->m_Width; - const GLsizei h = (GLsizei)t->m_Height; - const size_t bpp = t->m_Bpp; - const u8* data = (const u8*)t->get_data(); - const UploadParams up = { fmt, int_fmt, uploaded_size }; - - if(t->m_Flags & TEX_DXT) - tex_util_foreach_mipmap(w, h, bpp, data, levels_to_skip, 4, upload_compressed_level, (void*)&up); - else - tex_util_foreach_mipmap(w, h, bpp, data, levels_to_skip, 1, upload_level, (void*)&up); -} - - -// upload the texture to OpenGL. -// if not 0, parameters override the following: -// fmt_ovr : OpenGL format (e.g. GL_RGB) decided from bpp / Tex flags; -// q_flags_ovr : global default "quality vs. performance" flags; -// int_fmt_ovr : internal format (e.g. GL_RGB8) decided from fmt / q_flags. -// -// side effects: -// - enables texturing on TMU 0 and binds the texture to it; -// - frees the texel data! see ogl_tex_get_data. -Status ogl_tex_upload(const Handle ht, GLenum fmt_ovr, int q_flags_ovr, GLint int_fmt_ovr) -{ - ONCE(detect_gl_upload_caps()); - - H_DEREF(ht, OglTex, ot); - Tex* t = &ot->t; - ENSURE(q_flags_valid(q_flags_ovr)); - // we don't bother verifying *fmt_ovr - there are too many values - - // upload already happened; no work to do. - // (this also happens if a cached texture is "loaded") - if(ot->flags & OT_IS_UPLOADED) - return INFO::OK; - - if(ot->flags & OT_TEX_VALID) - { - // decompress S3TC if that's not supported by OpenGL. - if((t->m_Flags & TEX_DXT) && !have_s3tc) - (void)t->transform_to(t->m_Flags & ~TEX_DXT); - - // determine fmt and int_fmt, allowing for user override. - ot->fmt = choose_fmt(t->m_Bpp, t->m_Flags); - if(fmt_ovr) ot->fmt = fmt_ovr; - if(q_flags_ovr) ot->q_flags = q_flags_ovr; - ot->int_fmt = choose_int_fmt(ot->fmt, ot->q_flags); - if(int_fmt_ovr) ot->int_fmt = int_fmt_ovr; - - ot->uploaded_size = 0; - - // now actually send to OpenGL: - ogl_WarnIfError(); - { - // (note: we know ht is valid due to H_DEREF, but ogl_tex_bind can - // fail in debug builds if OglTex.id isn't a valid texture name) - RETURN_STATUS_IF_ERR(ogl_tex_bind(ht, ot->tmu)); - int levels_to_skip; - if(get_mipmaps(t, ot->state.filter, ot->q_flags, &levels_to_skip) < 0) - // error => disable mipmapping - ot->state.filter = GL_LINEAR; - // (note: if first time, applies our defaults/previous overrides; - // otherwise, replays all state changes) - state_latch(&ot->state); - upload_impl(t, ot->fmt, ot->int_fmt, levels_to_skip, &ot->uploaded_size); - } - ogl_WarnIfError(); - - ot->flags |= OT_IS_UPLOADED; - - // see rationale for at declaration of OglTex. - intptr_t refs = h_get_refcnt(ht); - if(refs == 1) - { - // note: we verify above that OT_TEX_VALID is set - ot->flags &= ~OT_TEX_VALID; - } - } - - ot->flags |= OT_NEED_AUTO_UPLOAD; - - return INFO::OK; -} - - -//---------------------------------------------------------------------------- -// getters -//---------------------------------------------------------------------------- - -// retrieve texture dimensions and bits per pixel. -// all params are optional and filled if non-NULL. -Status ogl_tex_get_size(Handle ht, size_t* w, size_t* h, size_t* bpp) -{ - H_DEREF(ht, OglTex, ot); - - if(w) - *w = ot->t.m_Width; - if(h) - *h = ot->t.m_Height; - if(bpp) - *bpp = ot->t.m_Bpp; - return INFO::OK; -} - - -// retrieve TexFlags and the corresponding OpenGL format. -// the latter is determined during ogl_tex_upload and is 0 before that. -// all params are optional and filled if non-NULL. -Status ogl_tex_get_format(Handle ht, size_t* flags, GLenum* fmt) -{ - H_DEREF(ht, OglTex, ot); - - if(flags) - *flags = ot->t.m_Flags; - if(fmt) - { - ENSURE(ot->flags & OT_IS_UPLOADED); - *fmt = ot->fmt; - } - return INFO::OK; -} - - -// retrieve pointer to texel data. -// -// note: this memory is freed after a successful ogl_tex_upload for -// this texture. after that, the pointer we retrieve is NULL but -// the function doesn't fail (negative return value) by design. -// if you still need to get at the data, add a reference before -// uploading it or read directly from OpenGL (discouraged). -Status ogl_tex_get_data(Handle ht, u8** p) -{ - H_DEREF(ht, OglTex, ot); - - *p = ot->t.get_data(); - return INFO::OK; -} - -Status ogl_tex_get_uploaded_size(Handle ht, size_t* size) -{ - H_DEREF(ht, OglTex, ot); - - *size = ot->uploaded_size; - return INFO::OK; -} - -// retrieve color of 1x1 mipmap level -extern Status ogl_tex_get_average_color(Handle ht, u32* p) -{ - H_DEREF(ht, OglTex, ot); - warn_if_uploaded(ht, ot); - - *p = ot->t.get_average_color(); - return INFO::OK; -} - -//---------------------------------------------------------------------------- -// misc API -//---------------------------------------------------------------------------- - -// bind the texture to the specified unit [number] in preparation for -// using it in rendering. if is 0, texturing is disabled instead. -// -// side effects: -// - changes the active texture unit; -// - (if return value is 0:) texturing was enabled/disabled on that unit. -// -// notes: -// - assumes multitexturing is available. -// - not necessary before calling ogl_tex_upload! -// - on error, the unit's texture state is unchanged; see implementation. -Status ogl_tex_bind(Handle ht, size_t unit) -{ - // note: there are many call sites of glActiveTextureARB, so caching - // those and ignoring redundant sets isn't feasible. - glActiveTextureARB((int)(GL_TEXTURE0+unit)); - - // special case: resets the active texture. - if(ht == 0) - { - glBindTexture(GL_TEXTURE_2D, 0); - return INFO::OK; - } - - // if this fails, the texture unit's state remains unchanged. - // we don't bother catching that and disabling texturing because a - // debug warning is raised anyway, and it's quite unlikely. - H_DEREF(ht, OglTex, ot); - ot->tmu = (u8)unit; - - // if 0, there's a problem in the OglTex reload/dtor logic. - // binding it results in whiteness, which can have many causes; - // we therefore complain so this one can be ruled out. - ENSURE(ot->id != 0); - - glBindTexture(GL_TEXTURE_2D, ot->id); - return INFO::OK; -} - -Status ogl_tex_get_texture_id(Handle ht, GLuint* id) -{ - *id = 0; - H_DEREF(ht, OglTex, ot); - *id = ot->id; - return INFO::OK; -} - -// apply the specified transforms (as in tex_transform) to the image. -// must be called before uploading (raises a warning if called afterwards). -Status ogl_tex_transform(Handle ht, size_t transforms) -{ - H_DEREF(ht, OglTex, ot); - Status ret = ot->t.transform(transforms); - return ret; -} - - -// change the pixel format to that specified by . -// (note: this is equivalent to ogl_tex_transform(ht, ht_flags^new_flags). -Status ogl_tex_transform_to(Handle ht, size_t new_flags) -{ - H_DEREF(ht, OglTex, ot); - Status ret = ot->t.transform_to(new_flags); - return ret; -} - - -// return whether native S3TC support is available -bool ogl_tex_has_s3tc() -{ - // ogl_tex_upload must be called before this - ENSURE(have_s3tc != -1); - - return (have_s3tc != 0); -} - -// return whether anisotropic filtering support is available -bool ogl_tex_has_anisotropy() -{ - // ogl_tex_upload must be called before this - ENSURE(have_anistropy != -1); - - return (have_anistropy != 0); -} Property changes on: ps/trunk/source/lib/res/graphics/ogl_tex.cpp ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property Index: ps/trunk/source/lib/res/graphics/ogl_tex.h =================================================================== --- ps/trunk/source/lib/res/graphics/ogl_tex.h (revision 26367) +++ ps/trunk/source/lib/res/graphics/ogl_tex.h (nonexistent) @@ -1,488 +0,0 @@ -/* Copyright (C) 2021 Wildfire Games. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* - * wrapper for all OpenGL texturing calls. provides caching, hotloading - * and lifetime management. - */ - -/* - -[KEEP IN SYNC WITH WIKI!] - -Introduction ------------- - -This module simplifies use of textures in OpenGL. An easy-to-use -load/upload/bind/free API is provided, which completely replaces -direct access to OpenGL's texturing calls. - -It basically wraps tex.cpp's texture info in a resource object -(see h_mgr.h) that maintains associated GL state and provides for -reference counting, caching, hotloading and safe access. -Additionally, the upload step provides for trading quality vs. speed -and works around older hardware/drivers. - - -Texture Parameters ------------------- - -OpenGL textures are conditioned on several parameters including -filter and wrap mode. These are typically set once when the texture is -created, but must survive reloads (1). To that end, all state (2) is -set via ogl_tex_set_* (instead of direct glTexParameter calls) and -re-applied after a reload. - -(1) the purpose of hotloading is to permit artists to see their changes - in-game without having to restart the map. reloads where the - texture looks different due to changed state are useless. - -(2) currently only filter and wrap mode. no other glTexParameter - settings are used ATM; if that changes, add to OglTexState. - - -Uploading to OpenGL -------------------- - -.. deserves some clarification. This entails calling glTexImage2D -(or its variants for mipmaps/compressed textures) and transfers -texture parameters and data from system memory to OpenGL -(and thereby usually video memory). - -In so doing, choices are made as to the texture's internal representation -(how it is stored in vmem) - in particular, the bit depth. -This can trade performance (more/less data to copy) for quality -(fidelity to original). - -We provide a mechanism that applies defaults to all uploads; -this allows a global "quality" setting that can boost performance on -older graphics cards without requiring anything else to be changed. -Textures with specific quality needs can override this via -ogl_tex_set_* or ogl_tex_upload parameters. - -Finally, provision is made for coping with hardware/drivers lacking support -for S3TC decompression or mipmap generation: that can be done in software, -if necessary. This avoids the need for alternate asset formats and -lowers hardware requirements. While such cards probably won't run -the app very well (due to their age and lack of other capabilities), -this does make possible developing/testing on older machines/laptops. - - -Caching and Texture Instances ------------------------------ - -Caching is both an advantage and drawback. When opening the same -texture twice without previously freeing it, a reference to the -first instance is returned. Therefore, be advised that concurrent use of the -same texture but with differing parameters (e.g. upload quality) followed by -a reload of the first instance will result in using the wrong parameters. -For background and rationale why this is acceptable, see struct OglTex. - - -Example Usage -------------- - -Note: to keep the examples simple, we leave out error handling by -ignoring all return values. Each function will still raise a warning -(assert) if it fails and passing e.g. invalid Handles will only cause -the next function to fail, but real apps should check and report errors. - -1) Basic usage: load texture from file. - - Handle hTexture = ogl_tex_load("filename.dds"); - (void)ogl_tex_upload(hTexture); - - [when rendering:] - (void)ogl_tex_bind(hTexture); - [.. do something with OpenGL that uses the currently bound texture] - - [at exit:] - * (done automatically, but this avoids it showing up as a leak) - (void)ogl_tex_free(hTexture); - - -2) Advanced usage: wrap existing texture data, override filter, - specify internal_format and use multitexturing. - - Tex t; - const size_t flags = 0; * image is plain RGB, default orientation - void* data = [pre-existing image] - (void)tex_wrap(w, h, 24, flags, data, &t); - Handle hCompositeAlphaMap = ogl_tex_wrap(&t, "(alpha map composite)"); - (void)ogl_tex_set_filter(hCompositeAlphaMap, GL_LINEAR); - (void)ogl_tex_upload(hCompositeAlphaMap, 0, 0, GL_INTENSITY); - * (your responsibility! tex_wrap attaches a reference but it is - * removed by ogl_tex_upload.) - free(data); - - [when rendering:] - (void)ogl_tex_bind(hCompositeAlphaMap, 1); - [.. do something with OpenGL that uses the currently bound texture] - - [at exit:] - * (done automatically, but this avoids it showing up as a leak) - (void)ogl_tex_free(hCompositeAlphaMap); - -*/ - -#ifndef INCLUDED_OGL_TEX -#define INCLUDED_OGL_TEX - -#include "lib/res/handle.h" -#include "lib/file/vfs/vfs.h" -#include "lib/ogl.h" -#include "lib/tex/tex.h" - - -// -// quality mechanism -// - -/** -* Quality flags for texture uploads. -* Specify any of them to override certain aspects of the default. -*/ -enum OglTexQualityFlags -{ - /** - * emphatically require full quality for this texture. - * (q_flags are invalid if this is set together with any other bit) - * rationale: the value 0 is used to indicate "use default flags" in - * ogl_tex_upload and ogl_tex_set_defaults, so this is the only - * way we can say "disregard default and do not reduce anything". - */ - OGL_TEX_FULL_QUALITY = 0x20, - - /** - * store the texture at half the normal bit depth - * (4 bits per pixel component, as opposed to 8). - * this increases performance on older graphics cards due to - * decreased size in vmem. it has no effect on - * compressed textures because they have a fixed internal format. - */ - OGL_TEX_HALF_BPP = 0x10, - - /** - * store the texture at half its original resolution. - * this increases performance on older graphics cards due to - * decreased size in vmem. - * this is useful for also reducing quality of compressed textures, - * which are not affected by OGL_TEX_HALF_BPP. - * currently only implemented for images that contain mipmaps - * (otherwise, we'd have to resample, which is slow). - * note: scaling down to 1/4, 1/8, .. is easily possible without - * extra work, so we leave some bits free for that. - */ - OGL_TEX_HALF_RES = 0x01 -}; - -/** -* Change default settings - these affect performance vs. quality. -* May be overridden for individual textures via parameter to -* ogl_tex_upload or ogl_tex_set_filter, respectively. -* -* @param q_flags quality flags. Pass 0 to keep the current setting -* (initially OGL_TEX_FULL_QUALITY), or any combination of -* OglTexQualityFlags. -* @param filter mag/minification filter. Pass 0 to keep the current setting -* (initially GL_LINEAR), or any valid OpenGL minification filter. -*/ -extern void ogl_tex_set_defaults(int q_flags, GLint filter); - - -// -// open/close -// - -/** -* Load and return a handle to the texture. -* -* @param vfs -* @param pathname -* @param flags h_alloc flags. -* @return Handle to texture or negative Status -* for a list of supported formats, see tex.h's tex_load. -*/ -extern Handle ogl_tex_load(const PIVFS& vfs, const VfsPath& pathname, size_t flags = 0); - -/** -* Make the Tex object ready for use as an OpenGL texture -* and return a handle to it. This will be as if its contents -* had been loaded by ogl_tex_load. -* -* @param t Texture object. -* @param vfs -* @param pathname filename or description of texture. not strictly needed, -* but would allow h_filename to return meaningful info for -* purposes of debugging. -* @param flags -* @return Handle to texture or negative Status -* -* note: because we cannot guarantee that callers will pass distinct -* "filenames", caching is disabled for the created object. this avoids -* mistakenly reusing previous objects that share the same comment. -* -* we need only add bookkeeping information and "wrap" it in -* a resource object (accessed via Handle), hence the name. -*/ -extern Handle ogl_tex_wrap(Tex* t, const PIVFS& vfs, const VfsPath& pathname, size_t flags = 0); - -/** -* Release this texture reference. When the count reaches zero, all of -* its associated resources are freed and further use made impossible. -* -* @param ht Texture handle. -* @return Status -*/ -extern Status ogl_tex_free(Handle& ht); - - -// -// set texture parameters -// - -// these must be called before uploading; this simplifies -// things and avoids calling glTexParameter twice. - -/** -* Override default filter (see {@link #ogl_tex_set_defaults}) for -* this texture. -* -* @param ht Texture handle -* @param filter OpenGL minification and magnification filter -* (rationale: see {@link OglTexState}) -* @return Status -* -* Must be called before uploading (raises a warning if called afterwards). -*/ -extern Status ogl_tex_set_filter(Handle ht, GLint filter); - -/** -* Override default wrap mode (GL_REPEAT) for this texture. -* -* @param ht Texture handle -* @param wrap_s OpenGL wrap mode for S coordinates -* @param wrap_t OpenGL wrap mode for T coordinates -* @return Status -* -* Must be called before uploading (raises a warning if called afterwards). -*/ -extern Status ogl_tex_set_wrap(Handle ht, GLint wrap_s, GLint wrap_t); - -/** -* Override default maximum anisotropic filtering for this texture. -* -* @param ht Texture handle -* @param anisotropy Anisotropy value (must not be less than 1.0; should -* usually be a power of two) -* @return Status -* -* Must be called before uploading (raises a warning if called afterwards). -*/ -extern Status ogl_tex_set_anisotropy(Handle ht, GLfloat anisotropy); - - -// -// upload -// - -enum OglTexOverrides -{ - OGL_TEX_S3TC, - OGL_TEX_AUTO_MIPMAP_GEN, - OGL_TEX_ANISOTROPY -}; - -enum OglTexAllow -{ - OGL_TEX_DISABLE, - OGL_TEX_ENABLE -}; - -/** -* Override the default decision and force/disallow use of the -* given feature. -* -* @param what Feature to influence. -* @param allow Disable/enable flag. -*/ -extern void ogl_tex_override(OglTexOverrides what, OglTexAllow allow); - -/** -* Upload texture to OpenGL. -* -* @param ht Texture handle -* @param fmt_ovr optional override for OpenGL format (e.g. GL_RGB), -* which is decided from bpp / Tex flags -* @param q_flags_ovr optional override for global default -* OglTexQualityFlags -* @param int_fmt_ovr optional override for OpenGL internal format -* (e.g. GL_RGB8), which is decided from fmt / q_flags. -* @return Status. -* -* Side Effects: -* - enables texturing on TMU 0 and binds the texture to it; -* - frees the texel data! see ogl_tex_get_data. -*/ -extern Status ogl_tex_upload(const Handle ht, GLenum fmt_ovr = 0, int q_flags_ovr = 0, GLint int_fmt_ovr = 0); - - -// -// return information about the texture -// - -/** -* Retrieve dimensions and bit depth of the texture. -* -* @param ht Texture handle -* @param w optional; will be filled with width -* @param h optional; will be filled with height -* @param bpp optional; will be filled with bits per pixel -* @return Status -*/ -extern Status ogl_tex_get_size(Handle ht, size_t* w, size_t* h, size_t* bpp); - -/** -* Retrieve pixel format of the texture. -* -* @param ht Texture handle -* @param flags optional; will be filled with TexFlags -* @param fmt optional; will be filled with GL format -* (it is determined during ogl_tex_upload and 0 before then) -* @return Status -*/ -extern Status ogl_tex_get_format(Handle ht, size_t* flags, GLenum* fmt); - -/** -* Retrieve pixel data of the texture. -* -* @param ht Texture handle -* @param p will be filled with pointer to texels. -* @return Status -* -* Note: this memory is freed after a successful ogl_tex_upload for -* this texture. After that, the pointer we retrieve is NULL but -* the function doesn't fail (negative return value) by design. -* If you still need to get at the data, add a reference before -* uploading it or read directly from OpenGL (discouraged). -*/ -extern Status ogl_tex_get_data(Handle ht, u8** p); - -/** -* Retrieve number of bytes uploaded for the texture, including mipmaps. -* size will be 0 if the texture has not been uploaded yet. -* -* @param ht Texture handle -* @param size Will be filled with size in bytes -* @return Status -*/ -extern Status ogl_tex_get_uploaded_size(Handle ht, size_t* size); - -/** - * Retrieve ARGB value of 1x1 mipmap level of the texture, - * i.e. the average color of the whole texture. - * - * @param ht Texture handle - * @param p will be filled with ARGB value (or 0 if texture does not have mipmaps) - * @return Status - * - * Must be called before uploading (raises a warning if called afterwards). - */ -extern Status ogl_tex_get_average_color(Handle ht, u32* p); - - -// -// misc -// - -/** -* Bind texture to the specified unit in preparation for using it in -* rendering. -* -* @param ht Texture handle. If 0, texturing is disabled on this unit. -* @param unit Texture Mapping Unit number, typically 0 for the first. -* @return Status -* -* Side Effects: -* - changes the active texture unit; -* - (if successful) texturing was enabled/disabled on that unit. -* -* Notes: -* - assumes multitexturing is available. -* - not necessary before calling ogl_tex_upload! -* - on error, the unit's texture state is unchanged; see implementation. -*/ -extern Status ogl_tex_bind(Handle ht, size_t unit = 0); - -/** -* Return the GL handle of the loaded texture in *id, or 0 on failure. -*/ -extern Status ogl_tex_get_texture_id(Handle ht, GLuint* id); - -/** -* (partially) Transform pixel format of the texture. -* -* @param ht Texture handle. -* @param flags the TexFlags that are to be @em changed. -* @return Status -* @see tex_transform -* -* Must be called before uploading (raises a warning if called afterwards). -*/ -extern Status ogl_tex_transform(Handle ht, size_t flags); - -/** -* Transform pixel format of the texture. -* -* @param ht Texture handle. -* @param new_flags Flags desired new TexFlags indicating pixel format. -* @return Status -* @see tex_transform -* -* Must be called before uploading (raises a warning if called afterwards). -* -* Note: this is equivalent to ogl_tex_transform(ht, ht_flags^new_flags). -*/ -extern Status ogl_tex_transform_to(Handle ht, size_t new_flags); - -/** - * Return whether native S3TC texture compression support is available. - * If not, textures will be decompressed automatically, hurting performance. - * - * @return true if native S3TC supported. - * - * ogl_tex_upload must be called at least once before this. - */ -extern bool ogl_tex_has_s3tc(); - -/** - * Return whether anisotropic filtering support is available. - * (The anisotropy might still be disabled or overridden by the driver - * configuration.) - * - * @return true if anisotropic filtering supported. - * - * ogl_tex_upload must be called at least once before this. - */ -extern bool ogl_tex_has_anisotropy(); - -#endif // #ifndef INCLUDED_OGL_TEX Property changes on: ps/trunk/source/lib/res/graphics/ogl_tex.h ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property Index: ps/trunk/source/lib/tex/tex.cpp =================================================================== --- ps/trunk/source/lib/tex/tex.cpp (revision 26367) +++ ps/trunk/source/lib/tex/tex.cpp (revision 26368) @@ -1,815 +1,809 @@ /* Copyright (C) 2022 Wildfire Games. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "precompiled.h" #include "tex.h" #include "lib/allocators/shared_ptr.h" #include "lib/bits.h" #include "lib/sysdep/cpu.h" #include "lib/tex/tex_codec.h" #include "lib/timer.h" #include #include #include static const StatusDefinition texStatusDefinitions[] = { { ERR::TEX_FMT_INVALID, L"Invalid/unsupported texture format" }, { ERR::TEX_INVALID_COLOR_TYPE, L"Invalid color type" }, { ERR::TEX_NOT_8BIT_PRECISION, L"Not 8-bit channel precision" }, { ERR::TEX_INVALID_LAYOUT, L"Unsupported texel layout, e.g. right-to-left" }, { ERR::TEX_COMPRESSED, L"Unsupported texture compression" }, { WARN::TEX_INVALID_DATA, L"Warning: invalid texel data encountered" }, { ERR::TEX_INVALID_SIZE, L"Texture size is incorrect" }, { INFO::TEX_CODEC_CANNOT_HANDLE, L"Texture codec cannot handle the given format" } }; STATUS_ADD_DEFINITIONS(texStatusDefinitions); //----------------------------------------------------------------------------- // validation //----------------------------------------------------------------------------- // be careful not to use other tex_* APIs here because they call us. Status Tex::validate() const { if(m_Flags & TEX_UNDEFINED_FLAGS) WARN_RETURN(ERR::_1); - // pixel data (only check validity if the image is still in memory; - // ogl_tex frees the data after uploading to GL) + // pixel data (only check validity if the image is still in memory). if(m_Data) { // file size smaller than header+pixels. // possible causes: texture file header is invalid, // or file wasn't loaded completely. if(m_DataSize < m_Ofs + m_Width*m_Height*m_Bpp/8) WARN_RETURN(ERR::_2); } // bits per pixel // (we don't bother checking all values; a sanity check is enough) if(m_Bpp % 4 || m_Bpp > 32) WARN_RETURN(ERR::_3); // flags // .. DXT value const size_t dxt = m_Flags & TEX_DXT; if(dxt != 0 && dxt != 1 && dxt != DXT1A && dxt != 3 && dxt != 5) WARN_RETURN(ERR::_4); // .. orientation const size_t orientation = m_Flags & TEX_ORIENTATION; if(orientation == (TEX_BOTTOM_UP|TEX_TOP_DOWN)) WARN_RETURN(ERR::_5); return INFO::OK; } #define CHECK_TEX(t) RETURN_STATUS_IF_ERR((t->validate())) // check if the given texture format is acceptable: 8bpp grey, // 24bpp color or 32bpp color+alpha (BGR / upside down are permitted). // basically, this is the "plain" format understood by all codecs and // tex_codec_plain_transform. Status tex_validate_plain_format(size_t bpp, size_t flags) { const bool alpha = (flags & TEX_ALPHA ) != 0; const bool grey = (flags & TEX_GREY ) != 0; const bool dxt = (flags & TEX_DXT ) != 0; const bool mipmaps = (flags & TEX_MIPMAPS) != 0; if(dxt || mipmaps) WARN_RETURN(ERR::TEX_FMT_INVALID); // grey must be 8bpp without alpha, or it's invalid. if(grey) { if(bpp == 8 && !alpha) return INFO::OK; WARN_RETURN(ERR::TEX_FMT_INVALID); } if(bpp == 24 && !alpha) return INFO::OK; if(bpp == 32 && alpha) return INFO::OK; WARN_RETURN(ERR::TEX_FMT_INVALID); } //----------------------------------------------------------------------------- // mipmaps //----------------------------------------------------------------------------- void tex_util_foreach_mipmap(size_t w, size_t h, size_t bpp, const u8* pixels, int levels_to_skip, size_t data_padding, MipmapCB cb, void* RESTRICT cbData) { ENSURE(levels_to_skip >= 0 || levels_to_skip == TEX_BASE_LEVEL_ONLY); size_t level_w = w, level_h = h; const u8* level_data = pixels; // we iterate through the loop (necessary to skip over image data), // but do not actually call back until the requisite number of // levels have been skipped (i.e. level == 0). int level = (levels_to_skip == TEX_BASE_LEVEL_ONLY)? 0 : -levels_to_skip; // until at level 1x1: for(;;) { // used to skip past this mip level in const size_t level_dataSize = (size_t)(round_up(level_w, data_padding) * round_up(level_h, data_padding) * bpp/8); if(level >= 0) cb((size_t)level, level_w, level_h, level_data, level_dataSize, cbData); level_data += level_dataSize; // 1x1 reached - done if(level_w == 1 && level_h == 1) break; level_w /= 2; level_h /= 2; // if the texture is non-square, one of the dimensions will become // 0 before the other. to satisfy OpenGL's expectations, change it // back to 1. if(level_w == 0) level_w = 1; if(level_h == 0) level_h = 1; level++; // special case: no mipmaps, we were only supposed to call for // the base level if(levels_to_skip == TEX_BASE_LEVEL_ONLY) break; } } struct CreateLevelData { size_t num_components; size_t prev_level_w; size_t prev_level_h; const u8* prev_level_data; size_t prev_level_dataSize; }; // uses 2x2 box filter static void create_level(size_t level, size_t level_w, size_t level_h, const u8* RESTRICT level_data, size_t level_dataSize, void* RESTRICT cbData) { CreateLevelData* cld = (CreateLevelData*)cbData; const size_t src_w = cld->prev_level_w; const size_t src_h = cld->prev_level_h; const u8* src = cld->prev_level_data; u8* dst = (u8*)level_data; // base level - must be copied over from source buffer if(level == 0) { ENSURE(level_dataSize == cld->prev_level_dataSize); memcpy(dst, src, level_dataSize); } else { const size_t num_components = cld->num_components; const size_t dx = num_components, dy = dx*src_w; // special case: image is too small for 2x2 filter if(cld->prev_level_w == 1 || cld->prev_level_h == 1) { // image is either a horizontal or vertical line. // their memory layout is the same (packed pixels), so no special // handling is needed; just pick max dimension. for(size_t y = 0; y < std::max(src_w, src_h); y += 2) { for(size_t i = 0; i < num_components; i++) { *dst++ = (src[0]+src[dx]+1)/2; src += 1; } src += dx; // skip to next pixel (since box is 2x2) } } // normal else { for(size_t y = 0; y < src_h; y += 2) { for(size_t x = 0; x < src_w; x += 2) { for(size_t i = 0; i < num_components; i++) { *dst++ = (src[0]+src[dx]+src[dy]+src[dx+dy]+2)/4; src += 1; } src += dx; // skip to next pixel (since box is 2x2) } src += dy; // skip to next row (since box is 2x2) } } ENSURE(dst == level_data + level_dataSize); ENSURE(src == cld->prev_level_data + cld->prev_level_dataSize); } cld->prev_level_data = level_data; cld->prev_level_dataSize = level_dataSize; cld->prev_level_w = level_w; cld->prev_level_h = level_h; } static Status add_mipmaps(Tex* t, size_t w, size_t h, size_t bpp, void* newData, size_t dataSize) { // this code assumes the image is of POT dimension; we don't // go to the trouble of implementing image scaling because - // the only place this is used (ogl_tex_upload) requires POT anyway. + // the only place this is used (backend textures) requires POT anyway. if(!is_pow2(w) || !is_pow2(h)) WARN_RETURN(ERR::TEX_INVALID_SIZE); t->m_Flags |= TEX_MIPMAPS; // must come before tex_img_size! const size_t mipmap_size = t->img_size(); std::shared_ptr mipmapData; AllocateAligned(mipmapData, mipmap_size); CreateLevelData cld = { bpp/8, w, h, (const u8*)newData, dataSize }; tex_util_foreach_mipmap(w, h, bpp, mipmapData.get(), 0, 1, create_level, &cld); t->m_Data = mipmapData; t->m_DataSize = mipmap_size; t->m_Ofs = 0; return INFO::OK; } //----------------------------------------------------------------------------- // pixel format conversion (transformation) //----------------------------------------------------------------------------- TIMER_ADD_CLIENT(tc_plain_transform); // handles BGR and row flipping in "plain" format (see below). // // called by codecs after they get their format-specific transforms out of // the way. note that this approach requires several passes over the image, // but is much easier to maintain than providing all<->all conversion paths. // // somewhat optimized (loops are hoisted, cache associativity accounted for) static Status plain_transform(Tex* t, size_t transforms) { -TIMER_ACCRUE(tc_plain_transform); - - // (this is also called directly instead of through ogl_tex, so - // we need to validate) + TIMER_ACCRUE(tc_plain_transform); CHECK_TEX(t); // extract texture info const size_t w = t->m_Width, h = t->m_Height, bpp = t->m_Bpp; const size_t flags = t->m_Flags; u8* const srcStorage = t->get_data(); // sanity checks (not errors, we just can't handle these cases) // .. unknown transform if(transforms & ~(TEX_BGR|TEX_ORIENTATION|TEX_MIPMAPS|TEX_ALPHA)) return INFO::TEX_CODEC_CANNOT_HANDLE; // .. data is not in "plain" format RETURN_STATUS_IF_ERR(tex_validate_plain_format(bpp, flags)); // .. nothing to do if(!transforms) return INFO::OK; const size_t srcSize = t->img_size(); size_t dstSize = srcSize; if(transforms & TEX_ALPHA) { // add alpha channel if(bpp == 24) { dstSize = (srcSize / 3) * 4; t->m_Bpp = 32; } // remove alpha channel else if(bpp == 32) { return INFO::TEX_CODEC_CANNOT_HANDLE; } // can't have alpha with grayscale else { return INFO::TEX_CODEC_CANNOT_HANDLE; } } // allocate copy of the image data. // rationale: L1 cache is typically A2 => swapping in-place with a // line buffer leads to thrashing. we'll assume the whole texture*2 // fits in cache, allocate a copy, and transfer directly from there. // // this is necessary even when not flipping because the initial data // is read-only. std::shared_ptr dstStorage; AllocateAligned(dstStorage, dstSize); // setup row source/destination pointers (simplifies outer loop) u8* dst = (u8*)dstStorage.get(); const u8* src; const size_t pitch = w * bpp/8; // source bpp (not necessarily dest bpp) // .. avoid y*pitch multiply in row loop; instead, add row_ofs. ssize_t row_ofs = (ssize_t)pitch; // flipping rows (0,1,2 -> 2,1,0) if(transforms & TEX_ORIENTATION) { src = (const u8*)srcStorage+srcSize-pitch; // last row row_ofs = -(ssize_t)pitch; } // adding/removing alpha channel (can't convert in-place) else if(transforms & TEX_ALPHA) { src = (const u8*)srcStorage; } // do other transforms in-place else { src = (const u8*)dstStorage.get(); memcpy(dstStorage.get(), srcStorage, srcSize); } // no conversion necessary if(!(transforms & (TEX_BGR | TEX_ALPHA))) { if(src != dst) // avoid overlapping memcpy if not flipping rows { for(size_t y = 0; y < h; y++) { memcpy(dst, src, pitch); dst += pitch; src += row_ofs; } } } // RGB -> BGRA, BGR -> RGBA else if(bpp == 24 && (transforms & TEX_ALPHA) && (transforms & TEX_BGR)) { for(size_t y = 0; y < h; y++) { for(size_t x = 0; x < w; x++) { // need temporaries in case src == dst (i.e. not flipping) const u8 b = src[0], g = src[1], r = src[2]; dst[0] = r; dst[1] = g; dst[2] = b; dst[3] = 0xFF; dst += 4; src += 3; } src += row_ofs - pitch; // flip? previous row : stay } } // RGB -> RGBA, BGR -> BGRA else if(bpp == 24 && (transforms & TEX_ALPHA) && !(transforms & TEX_BGR)) { for(size_t y = 0; y < h; y++) { for(size_t x = 0; x < w; x++) { // need temporaries in case src == dst (i.e. not flipping) const u8 r = src[0], g = src[1], b = src[2]; dst[0] = r; dst[1] = g; dst[2] = b; dst[3] = 0xFF; dst += 4; src += 3; } src += row_ofs - pitch; // flip? previous row : stay } } // RGB <-> BGR else if(bpp == 24 && !(transforms & TEX_ALPHA)) { for(size_t y = 0; y < h; y++) { for(size_t x = 0; x < w; x++) { // need temporaries in case src == dst (i.e. not flipping) const u8 b = src[0], g = src[1], r = src[2]; dst[0] = r; dst[1] = g; dst[2] = b; dst += 3; src += 3; } src += row_ofs - pitch; // flip? previous row : stay } } // RGBA <-> BGRA else if(bpp == 32 && !(transforms & TEX_ALPHA)) { for(size_t y = 0; y < h; y++) { for(size_t x = 0; x < w; x++) { // need temporaries in case src == dst (i.e. not flipping) const u8 b = src[0], g = src[1], r = src[2], a = src[3]; dst[0] = r; dst[1] = g; dst[2] = b; dst[3] = a; dst += 4; src += 4; } src += row_ofs - pitch; // flip? previous row : stay } } else { debug_warn(L"unsupported transform"); return INFO::TEX_CODEC_CANNOT_HANDLE; } t->m_Data = dstStorage; t->m_DataSize = dstSize; t->m_Ofs = 0; if(!(t->m_Flags & TEX_MIPMAPS) && transforms & TEX_MIPMAPS) RETURN_STATUS_IF_ERR(add_mipmaps(t, w, h, bpp, dstStorage.get(), dstSize)); CHECK_TEX(t); return INFO::OK; } TIMER_ADD_CLIENT(tc_transform); // change the pixel format by flipping the state of all TEX_* flags // that are set in transforms. Status Tex::transform(size_t transforms) { TIMER_ACCRUE(tc_transform); CHECK_TEX(this); const size_t target_flags = m_Flags ^ transforms; size_t remaining_transforms; for(;;) { remaining_transforms = target_flags ^ m_Flags; // we're finished (all required transforms have been done) if(remaining_transforms == 0) return INFO::OK; Status ret = tex_codec_transform(this, remaining_transforms); UpdateMIPLevels(); if(ret != INFO::OK) break; } // last chance RETURN_STATUS_IF_ERR(plain_transform(this, remaining_transforms)); UpdateMIPLevels(); return INFO::OK; } // change the pixel format to the new format specified by . // (note: this is equivalent to transform(t, t->flags^new_flags). Status Tex::transform_to(size_t new_flags) { // transform takes care of validating const size_t transforms = m_Flags ^ new_flags; return transform(transforms); } //----------------------------------------------------------------------------- // image orientation //----------------------------------------------------------------------------- // see "Default Orientation" in docs. static int global_orientation = TEX_TOP_DOWN; // set the orientation (either TEX_BOTTOM_UP or TEX_TOP_DOWN) to which // all loaded images will automatically be converted // (excepting file formats that don't specify their orientation, i.e. DDS). void tex_set_global_orientation(int o) { ENSURE(o == TEX_TOP_DOWN || o == TEX_BOTTOM_UP); global_orientation = o; } static void flip_to_global_orientation(Tex* t) { // (can't use normal CHECK_TEX due to void return) WARN_IF_ERR(t->validate()); size_t orientation = t->m_Flags & TEX_ORIENTATION; // if codec knows which way around the image is (i.e. not DDS): if(orientation) { // flip image if necessary size_t transforms = orientation ^ global_orientation; WARN_IF_ERR(plain_transform(t, transforms)); } // indicate image is at global orientation. this is still done even // if the codec doesn't know: the default orientation should be chosen // to make that work correctly (see "Default Orientation" in docs). t->m_Flags = (t->m_Flags & ~TEX_ORIENTATION) | global_orientation; // (can't use normal CHECK_TEX due to void return) WARN_IF_ERR(t->validate()); } // indicate if the orientation specified by matches // dst_orientation (if the latter is 0, then the global_orientation). // (we ask for src_flags instead of src_orientation so callers don't // have to mask off TEX_ORIENTATION) bool tex_orientations_match(size_t src_flags, size_t dst_orientation) { const size_t src_orientation = src_flags & TEX_ORIENTATION; if(dst_orientation == 0) dst_orientation = global_orientation; return (src_orientation == dst_orientation); } //----------------------------------------------------------------------------- // misc. API //----------------------------------------------------------------------------- // indicate if 's extension is that of a texture format // supported by Tex::load. case-insensitive. // // rationale: Tex::load complains if the given file is of an // unsupported type. this API allows users to preempt that warning // (by checking the filename themselves), and also provides for e.g. // enumerating only images in a file picker. // an alternative might be a flag to suppress warning about invalid files, // but this is open to misuse. bool tex_is_known_extension(const VfsPath& pathname) { const ITexCodec* dummy; // found codec for it => known extension const OsPath extension = pathname.Extension(); if(tex_codec_for_filename(extension, &dummy) == INFO::OK) return true; return false; } // store the given image data into a Tex object; this will be as if // it had been loaded via Tex::load. // // rationale: support for in-memory images is necessary for // emulation of glCompressedTexImage2D and useful overall. // however, we don't want to provide an alternate interface for each API; // these would have to be changed whenever fields are added to Tex. // instead, provide one entry point for specifying images. // // we need only add bookkeeping information and "wrap" it in // our Tex struct, hence the name. Status Tex::wrap(size_t w, size_t h, size_t bpp, size_t flags, const std::shared_ptr& data, size_t ofs) { m_Width = w; m_Height = h; m_Bpp = bpp; m_Flags = flags; m_Data = data; m_DataSize = ofs + w*h*bpp/8; m_Ofs = ofs; CHECK_TEX(this); UpdateMIPLevels(); return INFO::OK; } // free all resources associated with the image and make further // use of it impossible. void Tex::free() { // do not validate - this is called from Tex::load if loading // failed, so not all fields may be valid. m_Data.reset(); - // do not zero out the fields! that could lead to trouble since - // ogl_tex_upload followed by ogl_tex_free is legit, but would - // cause OglTex_validate to fail (since its Tex.w is == 0). + // TODO: refactor fields zeroing. } //----------------------------------------------------------------------------- // getters //----------------------------------------------------------------------------- // returns a pointer to the image data (pixels), taking into account any // header(s) that may come before it. u8* Tex::get_data() { // (can't use normal CHECK_TEX due to u8* return value) WARN_IF_ERR(validate()); u8* p = m_Data.get(); if(!p) return nullptr; return p + m_Ofs; } // returns color of 1x1 mipmap level u32 Tex::get_average_color() const { // require mipmaps if(!(m_Flags & TEX_MIPMAPS)) return 0; // find the total size of image data size_t size = img_size(); // compute the size of the last (1x1) mipmap level const size_t data_padding = (m_Flags & TEX_DXT)? 4 : 1; size_t last_level_size = (size_t)(data_padding * data_padding * m_Bpp/8); // construct a new texture based on the current one, // but only include the last mipmap level // do this so that we can use the general conversion methods for the pixel data Tex basetex = *this; uint8_t *data = new uint8_t[last_level_size]; memcpy(data, m_Data.get() + m_Ofs + size - last_level_size, last_level_size); std::shared_ptr sdata(data, ArrayDeleter()); basetex.wrap(1, 1, m_Bpp, m_Flags, sdata, 0); // convert to BGRA WARN_IF_ERR(basetex.transform_to(TEX_BGR | TEX_ALPHA)); // extract components into u32 ENSURE(basetex.m_DataSize >= basetex.m_Ofs+4); u8 b = basetex.m_Data.get()[basetex.m_Ofs]; u8 g = basetex.m_Data.get()[basetex.m_Ofs+1]; u8 r = basetex.m_Data.get()[basetex.m_Ofs+2]; u8 a = basetex.m_Data.get()[basetex.m_Ofs+3]; return b + (g << 8) + (r << 16) + (a << 24); } static void add_level_size(size_t UNUSED(level), size_t UNUSED(level_w), size_t UNUSED(level_h), const u8* RESTRICT UNUSED(level_data), size_t level_dataSize, void* RESTRICT cbData) { size_t* ptotal_size = (size_t*)cbData; *ptotal_size += level_dataSize; } // return total byte size of the image pixels. (including mipmaps!) // this is preferable to calculating manually because it's // less error-prone (e.g. confusing bits_per_pixel with bytes). size_t Tex::img_size() const { // (can't use normal CHECK_TEX due to size_t return value) WARN_IF_ERR(validate()); const int levels_to_skip = (m_Flags & TEX_MIPMAPS)? 0 : TEX_BASE_LEVEL_ONLY; const size_t data_padding = (m_Flags & TEX_DXT)? 4 : 1; size_t out_size = 0; tex_util_foreach_mipmap(m_Width, m_Height, m_Bpp, 0, levels_to_skip, data_padding, add_level_size, &out_size); return out_size; } // return the minimum header size (i.e. offset to pixel data) of the // file format indicated by 's extension (that is all it need contain: // e.g. ".bmp"). returns 0 on error (i.e. no codec found). // this can be used to optimize calls to tex_write: when allocating the // buffer that will hold the image, allocate this much extra and // pass the pointer as base+hdr_size. this allows writing the header // directly into the output buffer and makes for zero-copy IO. size_t tex_hdr_size(const VfsPath& filename) { const ITexCodec* c; const OsPath extension = filename.Extension(); WARN_RETURN_STATUS_IF_ERR(tex_codec_for_filename(extension, &c)); return c->hdr_size(0); } //----------------------------------------------------------------------------- // read/write from memory and disk //----------------------------------------------------------------------------- Status Tex::decode(const std::shared_ptr& Data, size_t DataSize) { const ITexCodec* c; RETURN_STATUS_IF_ERR(tex_codec_for_header(Data.get(), DataSize, &c)); // make sure the entire header is available const size_t min_hdr_size = c->hdr_size(0); if(DataSize < min_hdr_size) WARN_RETURN(ERR::TEX_INCOMPLETE_HEADER); const size_t hdr_size = c->hdr_size(Data.get()); if(DataSize < hdr_size) WARN_RETURN(ERR::TEX_INCOMPLETE_HEADER); m_Data = Data; m_DataSize = DataSize; m_Ofs = hdr_size; RETURN_STATUS_IF_ERR(c->decode(Data.get(), DataSize, this)); // sanity checks if(!m_Width || !m_Height || m_Bpp > 32) WARN_RETURN(ERR::TEX_FMT_INVALID); if(m_DataSize < m_Ofs + img_size()) WARN_RETURN(ERR::TEX_INVALID_SIZE); flip_to_global_orientation(this); CHECK_TEX(this); UpdateMIPLevels(); return INFO::OK; } Status Tex::encode(const OsPath& extension, DynArray* da) { CHECK_TEX(this); WARN_RETURN_STATUS_IF_ERR(tex_validate_plain_format(m_Bpp, m_Flags)); // we could be clever here and avoid the extra alloc if our current // memory block ensued from the same kind of texture file. this is // most likely the case if in_img == get_data() + c->hdr_size(0). // this would make for zero-copy IO. const size_t max_out_size = img_size()*4 + 256*KiB; RETURN_STATUS_IF_ERR(da_alloc(da, max_out_size)); const ITexCodec* c; WARN_RETURN_STATUS_IF_ERR(tex_codec_for_filename(extension, &c)); // encode into Status err = c->encode(this, da); if(err < 0) { (void)da_free(da); WARN_RETURN(err); } return INFO::OK; } void Tex::UpdateMIPLevels() { m_MIPLevels.clear(); if (m_Flags & TEX_MIPMAPS) { // We add one because we need to account the smallest 1x1 level. m_MIPLevels.reserve(ceil_log2(std::max(m_Width, m_Height)) + 1); } u8* levelData = m_Data.get(); levelData += m_Ofs; const u32 dataPadding = (m_Flags & TEX_DXT) != 0 ? 4 : 1; u32 levelWidth = m_Width, levelHeight = m_Height; for (u32 level = 0; ; ++level) { const u32 levelDataSize = round_up(levelWidth, dataPadding) * round_up(levelHeight, dataPadding) * m_Bpp / 8; m_MIPLevels.emplace_back(); m_MIPLevels.back().data = levelData; m_MIPLevels.back().dataSize = levelDataSize; m_MIPLevels.back().width = levelWidth; m_MIPLevels.back().height = levelHeight; if (!(m_Flags & TEX_MIPMAPS)) break; if (levelWidth == 1 && levelHeight == 1) break; levelData += levelDataSize; levelWidth = std::max(levelWidth / 2, 1); levelHeight = std::max(levelHeight / 2, 1); } }