From 766a0b40779c0c45a150d17aca1230611a981509 Mon Sep 17 00:00:00 2001 From: janwas Date: Fri, 25 Jun 2004 22:18:03 +0000 Subject: [PATCH] split tex code into (file format specific) loaders and opengl texture ops like upload() This was SVN commit r601. --- source/lib/res/ogl_tex.cpp | 412 +++++++++++++++++++++ source/lib/res/ogl_tex.h | 24 ++ source/lib/res/tex.cpp | 728 +++++++++++-------------------------- source/lib/res/tex.h | 32 +- 4 files changed, 665 insertions(+), 531 deletions(-) create mode 100755 source/lib/res/ogl_tex.cpp create mode 100755 source/lib/res/ogl_tex.h diff --git a/source/lib/res/ogl_tex.cpp b/source/lib/res/ogl_tex.cpp new file mode 100755 index 0000000000..bd9d375e99 --- /dev/null +++ b/source/lib/res/ogl_tex.cpp @@ -0,0 +1,412 @@ +#include "precompiled.h" + +#include "ogl.h" +#include "res.h" + +#include "lib.h" + + +static int get_gl_fmt(TexInfo* ti, GLenum* fmt) +{ + const bool alpha = (ti->flags & TEX_ALPHA) != 0; + const bool bgr = (ti->flags & TEX_BGR) != 0; + const bool gray = (ti->flags & TEX_GRAY) != 0; + + switch(ti->flags & TEX_DXT) + { + case 1: + *fmt = alpha? GL_COMPRESSED_RGB_S3TC_DXT1_EXT : GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; + return 0; + case 3: + *fmt = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; + return 0; + case 5: + *fmt = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + return 0; + } + + switch(ti->bpp) + { + case 8: + *fmt = GL_LUMINANCE; + return 0; + + case 16: + *fmt = GL_LUMINANCE_ALPHA; + return 0; + case 24: + if(alpha) + return -1; + *fmt = bgr? GL_BGR : GL_RGB; + return 0; + case 32: + if(!alpha) + return -1; + *fmt = bgr? GL_BGRA : GL_RGBA; + return 0; + } + + return -1; +} + + +struct Tex +{ + TexInfo ti; + + // determined from TexInfo by Tex_reload + GLenum fmt; + + // allocated by Tex_reload + GLuint id; + + // set from user param by tex_upload + GLint filter; + GLenum int_fmt; +}; + +H_TYPE_DEFINE(Tex); + +static void Tex_init(Tex* t, va_list args) +{ + UNUSED(t); + UNUSED(args); +} + +static void Tex_dtor(Tex* t) +{ + tex_free(&t->ti); + + glDeleteTextures(1, &t->id); +} + + +static int Tex_reload(Tex* t, const char* fn, Handle h) +{ + if(t->id) + return 0; + + CHECK_ERR(tex_load(fn, &t->ti)); + + CHECK_ERR(get_gl_fmt(&t->ti, &t->fmt)); + + glGenTextures(1, &t->id); + // this can't realistically fail, just note that the already_loaded + // check above assumes (id > 0) <==> texture is loaded and valid + + // has been uploaded; do so again + if(t->int_fmt) + CHECK_ERR(tex_upload(h, t->filter, t->int_fmt)); + + return 0; +} + + +Handle tex_load(const char* const fn, int scope) +{ + return h_alloc(H_Tex, fn, scope); +} + + +int tex_free(Handle& ht) +{ + return h_free(ht, H_Tex); +} + + +int tex_bind(const Handle h) +{ + Tex* t = H_USER_DATA(h, Tex); + if(!t) + { + glBindTexture(GL_TEXTURE_2D, 0); + return ERR_INVALID_HANDLE; + } + +#ifndef NDEBUG + if(!t->id) + { + debug_warn("tex_bind: Tex.id is not a valid texture"); + return -1; + } +#endif + + glBindTexture(GL_TEXTURE_2D, t->id); + return 0; +} + +int tex_id(const Handle h) +{ + Tex* t = H_USER_DATA(h, Tex); + return t ? t->id : 0; +} + + + + +static int tex_validate(const uint line, const Tex* const t) +{ + const char* msg = 0; + int err = -1; + + // pointer to texture data + size_t tex_file_size; + void* tex_file = mem_get_ptr(t->ti.hm, &tex_file_size); + if(!tex_file) + msg = "texture file not loaded"; + // possible causes: texture file header is invalid, + // or file wasn't loaded completely. + if(t->ti.ofs > tex_file_size) + msg = "offset to texture data exceeds file size"; + + // width, height + GLsizei w = (GLsizei)t->ti.w; + GLsizei h = (GLsizei)t->ti.h; + // if w or h is 0, texture file probably not loaded successfully. + if(w == 0 || h == 0) + msg = "width or height is 0 - texture probably not loaded successfully"; + // greater than max supported tex dimension? + // no-op if oglInit not yet called + if(w > (GLsizei)max_tex_size || h > (GLsizei)max_tex_size) + msg = "texture dimensions exceed OpenGL implementation limit"; + // both NV_texture_rectangle and subtexture require work for the client + // (changing tex coords) => we'll just disallow non-power of 2 textures. + // TODO: ARB_texture_non_power_of_two + if(!is_pow2(w) || !is_pow2(h)) + msg = "width or height is not a power-of-2"; + + // texel format + GLenum fmt = (GLenum)t->fmt; + if(!fmt) + msg = "texel format is 0"; + // can't really check against a list of valid formats - loaders + // may define their own. not necessary anyway - if non-0, assume + // loader knows what it's doing, and that the format is valid. + + // bits per pixel + u32 bpp = t->ti.bpp; + // half-hearted sanity check: must be divisible by 4. + // don't bother checking all values. + if(bpp % 4 || bpp > 32) + msg = "invalid bpp? should be one of {4,8,16,24,32}"; + + // upload parameters, set by tex_upload(Handle), or 0 + GLint filter = t->filter; + GLenum int_fmt = t->int_fmt; + // TODO: check if valid + + if(msg) + { + debug_out("tex_validate at line %d failed: %s (error code %d)", line, msg, err); + debug_warn("tex_validate failed"); + return err; + } + + return 0; +} + +#define CHECK_TEX(t) CHECK_ERR(tex_validate(__LINE__, t)) + + +int tex_filter = GL_LINEAR; +uint tex_bpp = 32; // 16 or 32 + + +static int choose_upload_param(Tex* t, GLint* _filter, GLenum* _int_fmt) +{ + if(!*_filter) + *_filter = tex_filter; + + // internal format already explicitly requested; we're done. + if(*_int_fmt != 0) + return 0; + + GLenum int_fmt = 0; + + // choose a specific, sized internal format for common formats, + // based on the global tex_bpp setting (32 or 16 bpp textures). + // could just let the driver choose, but this gives the user + // control for performance/quality tweaking. + // + // rare formats (e.g. GL_BGR) not in the switch statements + // are handled below. + + // .. high quality, 32 bit textures (8 bits per component) + if(tex_bpp == 32) + { + switch(t->fmt) + { + case GL_RGBA: + int_fmt = GL_RGBA8; + break; + case GL_RGB: + int_fmt = GL_RGB8; + break; + case GL_LUMINANCE_ALPHA: + int_fmt = GL_LUMINANCE8_ALPHA8; + break; + case GL_ALPHA: + int_fmt = GL_ALPHA8; + break; + case GL_LUMINANCE: + int_fmt = GL_LUMINANCE8; + break; + } + } + // .. low quality, 16 bit textures (4 bits per component) + else + { + switch(t->fmt) + { + case GL_RGBA: + int_fmt = GL_RGBA4; + break; + case GL_RGB: + int_fmt = GL_RGB4; + break; + case GL_LUMINANCE_ALPHA: + int_fmt = GL_LUMINANCE4_ALPHA4; + break; + case GL_ALPHA: + int_fmt = GL_ALPHA4; + break; + case GL_LUMINANCE: + int_fmt = GL_LUMINANCE4; + break; + } + } + + // fmt wasn't in the list above, so we haven't chosen + // the internal format yet, and need to do so now. + // set it to # of components in the texture. + // note: can't use the texture data's format - + // not all can be used as an internal format! (e.g. GL_BGR) + if(!int_fmt) + int_fmt = t->ti.bpp / 8; + + *_int_fmt = int_fmt; + + return 0; +} + + +int tex_upload(const Handle ht, int filter_ovr, int int_fmt_ovr, int fmt_ovr) +{ + H_DEREF(ht, Tex, t); + + // someone's requesting upload, but has already been uploaded. + // this happens if a cached texture is "loaded". no work to do. + if(t->id && t->ti.hm <= 0) + return 0; + + CHECK_TEX(t); + + const char* fn = h_filename(ht); + if(!fn) + { + fn = "(could not determine filename)"; + debug_warn("tex_upload(Handle): h_filename failed"); + } + + // note: vars already verified by CHECK_TEX. + GLsizei w = (GLsizei)t->ti.w; + GLsizei h = (GLsizei)t->ti.h; + u32 bpp = t->ti.bpp; // not used directly in gl calls + GLenum fmt = t->fmt; + // .. reference: changed by override below and choose_upload_param + GLint& filter = t->filter; + GLenum& int_fmt = t->int_fmt; + void* tex_data = (char*)mem_get_ptr(t->ti.hm) + t->ti.ofs; + + // allow override + if(filter_ovr) filter = filter_ovr; + if(int_fmt_ovr) int_fmt = int_fmt_ovr; + if(fmt_ovr) fmt = fmt_ovr; + + CHECK_ERR(tex_bind(ht)); + // we know ht is valid (H_DEREF above), but tex_bind can + // fail in debug builds if Tex.id isn't a valid texture name + + CHECK_ERR(choose_upload_param(t, &filter, &int_fmt)); + + // set upload params + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); + const GLint mag = (filter == GL_NEAREST)? GL_NEAREST : GL_LINEAR; + // filter allows mipmaps; magnify can only be linear or nearest + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag); + + // does filter call for uploading mipmaps? + const bool mipmap = (filter == GL_NEAREST_MIPMAP_NEAREST || filter == GL_LINEAR_MIPMAP_NEAREST || + filter == GL_NEAREST_MIPMAP_LINEAR || filter == GL_LINEAR_MIPMAP_LINEAR); + + // check if SGIS_generate_mipmap is available (once) + static int sgm_avl = -1; + if(sgm_avl == -1) + sgm_avl = (int)oglExtAvail("GL_SGIS_generate_mipmap"); + + // S3TC compressed + if(fmt >= GL_COMPRESSED_RGB_S3TC_DXT1_EXT && + fmt <= GL_COMPRESSED_RGBA_S3TC_DXT5_EXT) + { + const int tex_size = w * h * bpp / 8; + // FIXME There's a bug in the file code that makes tex_file_size be + // rounded upwards to nearest multiple of 65536. Commented out until + // the file code has went through the neccessary changes. + //assert(4+sizeof(DDSURFACEDESC2)+tex_size == tex_file_size && "tex_upload: dds file size mismatch"); + + // RC, 020404: added mipmap generation for DDS textures using GL_SGIS_generate_mipmap - works fine + // on ATI cards, verified by others under NVIDIA + if(mipmap) + { + if (sgm_avl) + glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_TRUE); + else + // ack - no (easy) way of generating mipmaps for compressed textures; switch back + // to a linear minification filter + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + } + + glCompressedTexImage2DARB(GL_TEXTURE_2D, 0, fmt, w, h, 0, tex_size, tex_data); + } + // normal + else + { + // manual mipmap gen via GLU (box filter) + if(mipmap && !sgm_avl) + gluBuild2DMipmaps(GL_TEXTURE_2D, int_fmt, w, h, fmt, GL_UNSIGNED_BYTE, tex_data); + // auto mipmap gen, or no mipmap + else + { + if(mipmap) + glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_TRUE); + + glTexImage2D(GL_TEXTURE_2D, 0, int_fmt, w, h, 0, fmt, GL_UNSIGNED_BYTE, tex_data); + } + } + + mem_free_h(t->ti.hm); + + return 0; +} + + + + + +int tex_info(Handle ht, int* w, int* h, int* fmt, int* bpp, void** p) +{ + H_DEREF(ht, Tex, t); + + if(w) + *w = t->ti.w; + if(h) + *h = t->ti.h; + if(fmt) + *fmt = t->fmt; + if(bpp) + *bpp = t->ti.bpp; + if(p) + *p = mem_get_ptr(t->ti.hm); + + return 0; +} diff --git a/source/lib/res/ogl_tex.h b/source/lib/res/ogl_tex.h new file mode 100755 index 0000000000..14776cea4d --- /dev/null +++ b/source/lib/res/ogl_tex.h @@ -0,0 +1,24 @@ +#ifndef OGL_TEX_H__ +#define OGL_TEX_H__ + + +// load and return a handle to the texture given in . +// supports RAW, BMP, JP2, PNG, TGA, DDS +extern Handle tex_load(const char* const fn, int scope = 0); + +extern int tex_bind(Handle ht); +extern int tex_id(Handle ht); + +extern int tex_info(Handle ht, int* w, int* h, int *fmt, int *bpp, void** p); + +extern int tex_filter; // GL values; default: GL_LINEAR +extern unsigned int tex_bpp; // 16 or 32; default: 32 + +// upload the specified texture to OpenGL. Texture filter and internal format +// may be specified to override the global defaults. +extern int tex_upload(Handle ht, int filter_override = 0, int internal_fmt_override = 0, int format_override = 0); + +extern int tex_free(Handle& ht); + + +#endif // #ifndef OGL_TEX_H__ \ No newline at end of file diff --git a/source/lib/res/tex.cpp b/source/lib/res/tex.cpp index cb392806fa..c294896359 100755 --- a/source/lib/res/tex.cpp +++ b/source/lib/res/tex.cpp @@ -16,13 +16,12 @@ // Jan.Wassenberg@stud.uni-karlsruhe.de // http://www.stud.uni-karlsruhe.de/~urkt/ -// supported formats: DDS, TGA, PNG, JP2, BMP, RAW +// supported formats: DDS, TGA, BMP, PNG, JP2, RAW #include "precompiled.h" #include "lib.h" #include "res.h" -#include "ogl.h" #include #include @@ -53,9 +52,7 @@ #endif // NO_PNG -static const u32 INVALID_FORMAT = 0xffffffff; - // used in local variables only; never written into Tex. - +/* // filled by loader funcs => declare here struct Tex { @@ -70,11 +67,8 @@ struct Tex int filter; int int_fmt; }; +*/ -H_TYPE_DEFINE(Tex); - - -const u32 FMT_UNKNOWN = 0; ////////////////////////////////////////////////////////////////////////////// @@ -86,7 +80,8 @@ const u32 FMT_UNKNOWN = 0; #ifndef NO_DDS -// modified from ddraw header +// defs modified from ddraw header + #pragma pack(push, 1) @@ -142,93 +137,93 @@ static inline bool dds_valid(const u8* ptr, size_t size) // TODO: DXT1a? -static int dds_load(const char* fn, const u8* p, size_t size, Tex* t) +static int dds_load(const char* fn, const u8* p, size_t size, TexInfo* t) { const char* err = 0; const DDSURFACEDESC2* surf = (const DDSURFACEDESC2*)(p+4); const u32 hdr_size = 4+sizeof(DDSURFACEDESC2); + // make sure we can access all header fields if(size < hdr_size) + { err = "header not completely read"; - else - { - const u32 sd_size = read_le32(&surf->dwSize); - const u32 sd_flags = read_le32(&surf->dwFlags); - const u32 h = read_le32(&surf->dwHeight); - const u32 w = read_le32(&surf->dwWidth); - const u32 img_size = read_le32(&surf->dwLinearSize); - u32 mipmaps = read_le32(&surf->dwMipMapCount); - const u32 pf_size = read_le32(&surf->ddpfPixelFormat.dwSize); - const u32 pf_flags = read_le32(&surf->ddpfPixelFormat.dwFlags); - const u32 fourcc = surf->ddpfPixelFormat.dwFourCC; - // compared against FOURCC, which takes care of endian conversion. - - // we'll use these fields; make sure they're present below. - // note: we can't guess image dimensions if not specified - - // the image isn't necessarily square. - const u32 sd_req_flags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_PIXELFORMAT; - - // make sure fields that aren't indicated as valid are zeroed. - if(!(sd_flags & DDSD_MIPMAPCOUNT)) - mipmaps = 0; - - // MS DXTex tool doesn't set the required dwPitchOrLinearSize field - - // they can't even write out their own file format correctly. *sigh* - // we need to pass to OpenGL; it's calculated from w, h, and bpp, - // which we determine from the pixel format. - u32 bpp = 0; - u32 fmt = FMT_UNKNOWN; - - switch(fourcc) - { - case FOURCC('D','X','T','1'): - if(pf_flags & DDPF_ALPHAPIXELS) - fmt = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; - else - fmt = GL_COMPRESSED_RGB_S3TC_DXT1_EXT; - bpp = 4; - break; - case FOURCC('D','X','T','3'): - fmt = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; - bpp = 8; - break; - case FOURCC('D','X','T','5'): - fmt = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; - bpp = 8; - break; - } - - if(size < hdr_size + img_size) - err = "image not completely loaded"; - if(w % 4 || h % 4) - err = "image dimensions not padded to S3TC block size"; - if(!w || !h) - err = "width or height = 0 -- that's silly"; - if(mipmaps > 0) - err = "contains mipmaps"; - if(fmt == 0) - err = "invalid pixel format (not DXT{1,3,5})"; - if((sd_flags & sd_req_flags) != sd_req_flags) - err = "missing one or more required fields (w, h, pixel format)"; - if(sizeof(DDPIXELFORMAT) != pf_size) - err = "DDPIXELFORMAT size mismatch"; - if(sizeof(DDSURFACEDESC2) != sd_size) - err = "DDSURFACEDESC2 size mismatch"; - - t->w = w; - t->h = h; - t->fmt = fmt; - t->bpp = bpp; - t->ofs = hdr_size; - } - - if(err) - { +fail: debug_out("dds_load: %s: %s\n", fn, err); return -1; } + const u32 sd_size = read_le32(&surf->dwSize); + const u32 sd_flags = read_le32(&surf->dwFlags); + const u32 h = read_le32(&surf->dwHeight); + const u32 w = read_le32(&surf->dwWidth); + const u32 img_size = read_le32(&surf->dwLinearSize); + u32 mipmaps = read_le32(&surf->dwMipMapCount); + const u32 pf_size = read_le32(&surf->ddpfPixelFormat.dwSize); + const u32 pf_flags = read_le32(&surf->ddpfPixelFormat.dwFlags); + const u32 fourcc = surf->ddpfPixelFormat.dwFourCC; + // compared against FOURCC, which takes care of endian conversion. + + // we'll use these fields; make sure they're present below. + // note: we can't guess image dimensions if not specified - + // the image isn't necessarily square. + const u32 sd_req_flags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_PIXELFORMAT; + + // make sure fields that aren't indicated as valid are zeroed. + if(!(sd_flags & DDSD_MIPMAPCOUNT)) + mipmaps = 0; + + // MS DXTex tool doesn't set the required dwPitchOrLinearSize field - + // they can't even write out their own file format correctly. *sigh* + // we need to pass to OpenGL; it's calculated from w, h, and bpp, + // which we determine from the pixel format. + int bpp = 0; + int flags = 0; + + if(pf_flags & DDPF_ALPHAPIXELS) + flags |= TEX_ALPHA; + + switch(fourcc) + { + case FOURCC('D','X','T','1'): + bpp = 4; + flags |= 1; + break; + case FOURCC('D','X','T','3'): + bpp = 8; + flags |= 3; + break; + case FOURCC('D','X','T','5'): + bpp = 8; + flags |= 5; + break; + } + + if(size < hdr_size + img_size) + err = "image not completely loaded"; + if(w % 4 || h % 4) + err = "image dimensions not padded to S3TC block size"; + if(!w || !h) + err = "width or height = 0"; + if(mipmaps > 0) + err = "contains mipmaps"; + if(bpp == 0) + err = "invalid pixel format (not DXT{1,3,5})"; + if((sd_flags & sd_req_flags) != sd_req_flags) + err = "missing one or more required fields (w, h, pixel format)"; + if(sizeof(DDPIXELFORMAT) != pf_size) + err = "DDPIXELFORMAT size mismatch"; + if(sizeof(DDSURFACEDESC2) != sd_size) + err = "DDSURFACEDESC2 size mismatch"; + + if(err) + goto fail; + + t->ofs = hdr_size; + t->w = w; + t->h = h; + t->bpp = bpp; + t->flags = flags; return 0; } @@ -253,67 +248,54 @@ static inline bool tga_valid(const u8* ptr, size_t size) // requirements: uncompressed, direct color, bottom up -static int tga_load(const char* fn, const u8* ptr, size_t size, Tex* t) +static int tga_load(const char* fn, const u8* ptr, size_t size, TexInfo* t) { const char* err = 0; const u8 img_id_len = ptr[0]; const uint hdr_size = 18+img_id_len; + + // make sure we can access all header fields if(size < hdr_size) + { err = "header not completely read"; - else - { - const u8 type = ptr[2]; - const u16 w = read_le16(ptr+12); - const u16 h = read_le16(ptr+14); - const u8 bpp = ptr[16]; - const u8 desc = ptr[17]; - - const u8 alpha_bits = desc & 0x0f; - - const ulong img_size = (ulong)w * h * bpp / 8; - const u32 ofs = hdr_size; - - // determine format - u32 fmt = INVALID_FORMAT; - // .. grayscale - if(type == 3) - { - // 8 bit format: several are possible, we can't decide - if(bpp == 8) - fmt = FMT_UNKNOWN; - else if(bpp == 16 && alpha_bits == 8) - fmt = GL_LUMINANCE_ALPHA; - } - // .. true color - else if(type == 2) - { - if(bpp == 24 && alpha_bits == 0) - fmt = GL_BGR; - else if(bpp == 32 && alpha_bits == 8) - fmt = GL_BGRA; - } - - if(fmt == INVALID_FORMAT) - err = "invalid format or bpp"; - if(desc & 0x30) - err = "image is not bottom-up and left-to-right"; - if(size < hdr_size + img_size) - err = "size < image size"; - - t->w = w; - t->h = h; - t->fmt = fmt; - t->bpp = bpp; - t->ofs = ofs; - } - - if(err) - { +fail: debug_out("tga_load: %s: %s\n", fn, err); return -1; } + const u8 type = ptr[2]; + const u16 w = read_le16(ptr+12); + const u16 h = read_le16(ptr+14); + const u8 bpp = ptr[16]; + const u8 desc = ptr[17]; + + const u8 alpha_bits = desc & 0x0f; + const ulong img_size = (ulong)w * h * bpp / 8; + + int flags = 0; + + if(alpha_bits != 0) + flags |= TEX_ALPHA; + + // true color + if(type == 2) + flags |= TEX_BGR; + + if(desc & 0x30) + err = "image is not bottom-up and left-to-right"; + if(size < hdr_size + img_size) + err = "size < image size"; + + if(err) + goto fail; + + t->ofs = hdr_size; + t->w = w; + t->h = h; + t->bpp = bpp; + t->flags = flags; + return 0; } @@ -364,51 +346,82 @@ static inline bool bmp_valid(const u8* ptr, size_t size) // requirements: uncompressed, direct color, bottom up -static int bmp_load(const char* fn, const u8* ptr, size_t size, Tex* t) +static int bmp_load(const char* fn, const u8* ptr, size_t size, TexInfo* t) { const char* err = 0; - BITMAPFILEHEADER* bfh = (BITMAPFILEHEADER*)ptr; - BITMAPCOREHEADER2* bch = (BITMAPCOREHEADER2*)(ptr+sizeof(BITMAPFILEHEADER)); const int hdr_size = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPCOREHEADER2); + + // make sure we can access all header fields if(size < hdr_size) + { err = "header not completely read"; - else - { - const long w = read_le32(&bch->biWidth); - const long h = read_le32(&bch->biHeight); - const u16 bpp = read_le16(&bch->biBitCount); - const u32 compress = read_le32(&bch->biCompression); - const u32 ofs = read_le32(&bfh->bfOffBits); - - const u32 img_size = w * h * bpp/8; - const u32 fmt = (bpp == 24)? GL_BGR : GL_BGRA; - - if(h < 0) - err = "top-down"; - if(compress != BI_RGB) - err = "compressed"; - if(bpp < 24) - err = "not direct color"; - if(size < ofs+img_size) - err = "image not completely read"; - - t->w = w; - t->h = h; - t->fmt = fmt; - t->bpp = bpp; - t->ofs = ofs; - } - - if(err) - { +fail: debug_out("bmp_load: %s: %s\n", fn, err); return -1; } + BITMAPFILEHEADER* bfh = (BITMAPFILEHEADER*)ptr; + BITMAPCOREHEADER2* bch = (BITMAPCOREHEADER2*)(ptr+sizeof(BITMAPFILEHEADER)); + + const long w = read_le32(&bch->biWidth); + const long h = read_le32(&bch->biHeight); + const u16 bpp = read_le16(&bch->biBitCount); + const u32 compress = read_le32(&bch->biCompression); + const u32 ofs = read_le32(&bfh->bfOffBits); + + const u32 img_size = w * h * bpp/8; + + int flags = TEX_BGR; + if(bpp == 32) + flags |= TEX_ALPHA; + + if(h < 0) + err = "top-down"; + if(compress != BI_RGB) + err = "compressed"; + if(bpp < 24) + err = "not direct color"; + if(size < ofs+img_size) + err = "image not completely read"; + + if(err) + goto fail; + + t->ofs = ofs; + t->w = w; + t->h = h; + t->bpp = bpp; + t->flags = flags; + + return 0; +} + + +static int bmp_write(void*& out_buf, const u8* img, const size_t size, TexInfo* t) +{ + const size_t hdr_size = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPCOREHEADER2); + const size_t file_size = size+hdr_size; + out_buf = mem_alloc(file_size); + + BITMAPFILEHEADER* bfh = (BITMAPFILEHEADER*)out_buf; + char* type = (char*)bfh; + type[0] = 'B'; type[1] = 'M'; + bfh->bfSize = (u32)file_size; + bfh->reserved = 0; + bfh->bfOffBits = hdr_size; + + BITMAPCOREHEADER2* bch = (BITMAPCOREHEADER2*)((char*)out_buf+sizeof(BITMAPFILEHEADER)); + bch->biSize = sizeof(BITMAPCOREHEADER2); + bch->biWidth = t->w; + bch->biHeight = t->h; + bch->biPlanes = 1; + bch->biBitCount = t->bpp; + bch->biCompression = 0; + + memcpy((char*)out_buf+hdr_size, img, size); return 0; } -// TODO: no extra buffer needed here; dealloc? #endif @@ -430,29 +443,26 @@ static inline bool raw_valid(const u8* p, size_t size) } -static int raw_load(const char* fn, const u8* ptr, size_t size, Tex* t) +static int raw_load(const char* fn, const u8* ptr, size_t size, TexInfo* t) { UNUSED(ptr); - static u32 fmts[5] = { 0, 0, GL_LUMINANCE_ALPHA, GL_RGB, GL_RGBA }; - for(uint i = 1; i <= 4; i++) + // TODO: allow 8 bit format. problem: how to differentiate from 32? filename? + + for(uint i = 2; i <= 4; i++) { - u32 dim = (u32)sqrtf((float)size/i); - // TODO: differentiate 8/32 bpp + const u32 dim = (u32)sqrtf((float)size/i); if(dim*dim*i != size) continue; - const u32 w = dim; - const u32 h = dim; - const u32 fmt = fmts[i]; - const u32 bpp = i*8; - const u32 ofs = 0; + // formats are: GL_LUMINANCE_ALPHA, GL_RGB, GL_RGBA + int flags = (i == 3)? 0 : TEX_ALPHA; - t->w = w; - t->h = h; - t->fmt = fmt; - t->bpp = bpp; - t->ofs = ofs; + t->ofs = 0; + t->w = dim; + t->h = dim; + t->bpp = i*8; + t->flags = flags; return 0; } @@ -498,7 +508,7 @@ static inline bool png_valid(const u8* ptr, size_t size) // requirement: direct color -static int png_load(const char* fn, const u8* ptr, size_t size, Tex* t) +static int png_load(const char* fn, const u8* ptr, size_t size, TexInfo* t) { const char* msg = 0; int err = -1; @@ -538,14 +548,14 @@ fail: const size_t pitch = png_get_rowbytes(png_ptr, info_ptr); - const u32 fmts[8] = { 0, INVALID_FORMAT, GL_RGB, INVALID_FORMAT, GL_LUMINANCE_ALPHA, INVALID_FORMAT, GL_RGBA, INVALID_FORMAT }; - const u32 fmt = color_type < 8? fmts[color_type] : INVALID_FORMAT; const u32 bpp = (u32)(pitch / w * 8); const u32 ofs = 0; // libpng returns decoded image data; no header + int flags = (bpp == 24)? 0 : TEX_ALPHA; + if(prec != 8) msg = "channel precision != 8 bits"; - if(fmt == INVALID_FORMAT) + if(color_type & 1) msg = "color type is invalid (must be direct color)"; if(msg) { @@ -574,13 +584,13 @@ fail: png_read_end(png_ptr, info_ptr); mem_free_h(t->hm); + t->hm = img_hm; + t->ofs = ofs; t->w = w; t->h = h; - t->fmt = fmt; t->bpp = bpp; - t->ofs = ofs; - t->hm = img_hm; + t->flags = flags; err = 0; @@ -619,7 +629,7 @@ static inline bool jp2_valid(const u8* p, size_t size) } -static int jp2_load(const char* fn, const u8* ptr, size_t size, Tex* t) +static int jp2_load(const char* fn, const u8* ptr, size_t size, TexInfo* t) { const char* err = 0; @@ -634,9 +644,9 @@ static int jp2_load(const char* fn, const u8* ptr, size_t size, Tex* t) const u32 w = jas_image_cmptwidth (image, 0); const u32 h = jas_image_cmptheight(image, 0); const int prec = jas_image_cmptprec (image, 0); - const u32 fmt = GL_RGB; const u32 bpp = num_cmpts * 8; const u32 ofs = 0; // jasper returns decoded image data; no header + int flags = 0; if(depth != 8) { @@ -674,13 +684,13 @@ fail: jas_matrix_destroy(matr[cmpt]); mem_free_h(t->hm); + t->hm = img_hm; + t->ofs = ofs; t->w = w; t->h = h; - t->fmt = fmt; t->bpp = bpp; - t->ofs = ofs; - t->hm = img_hm; + t->flags = flags; return 0; } @@ -688,235 +698,8 @@ fail: #endif -static void Tex_init(Tex* t, va_list args) +int tex_load(const char* const fn, TexInfo* t) { - UNUSED(t); - UNUSED(args); -} - - -static void Tex_dtor(Tex* t) -{ - mem_free_h(t->hm); - - glDeleteTextures(1, &t->id); -} - - - -static int tex_upload(Tex* t, const char* const fn) -{ - // someone's requesting upload, but has already been uploaded. - // this happens if a cached texture is "loaded". no work to do. - if(t->id && t->hm <= 0) - return 0; - - // data we will take from Tex - GLsizei w; - GLsizei h; - GLenum fmt; - void* tex_data; - u32 bpp; // not used directly in gl calls - int filter; - int int_fmt; - - const char* err = 0; - - // pointer to texture data - size_t tex_file_size; - void* tex_file = mem_get_ptr(t->hm, &tex_file_size); - if(!tex_file) - err = "texture file not loaded"; - // possible causes: texture file header is invalid, - // or file wasn't loaded completely. - if(t->ofs > tex_file_size) - err = "offset to texture data exceeds file size"; - tex_data = (char*)tex_file + t->ofs; - - // width, height - w = (GLsizei)t->w; - h = (GLsizei)t->h; - // if w or h is 0, texture file probably not loaded successfully. - if(w == 0 || h == 0) - err = "width or height is 0 - texture probably not loaded successfully"; - // greater than max supported tex dimension? - // no-op if oglInit not yet called - if(w > (GLsizei)max_tex_size || h > (GLsizei)max_tex_size) - err = "texture dimensions exceed OpenGL implementation limit"; - // both NV_texture_rectangle and subtexture require work for the client - // (changing tex coords) => we'll just disallow non-power of 2 textures. - // TODO: ARB_texture_non_power_of_two - if(!is_pow2(w) || !is_pow2(h)) - err = "width or height is not a power-of-2"; - - // texel format - fmt = (GLenum)t->fmt; - if(!fmt) - err = "texel format is 0"; - // can't really check against a list of valid formats - loaders - // may define their own. not necessary anyway - if non-0, assume - // loader knows what it's doing, and that the format is valid. - - // bits per pixel - bpp = t->bpp; - // half-hearted sanity check: must be divisible by 4. - // don't bother checking all values. - if(bpp % 4 || bpp > 32) - err = "invalid bpp? should be one of {4,8,16,24,32}"; - - // upload parameters, set by tex_upload(Handle), or 0 - filter = t->filter; - int_fmt = t->int_fmt; - - if(err) - { - debug_out("tex_upload: %s: %s\n", fn, err); - debug_warn("tex_upload failed"); - return -1; - } - -// CHECK_ERR(tex_bind(ht)); - // we know ht is valid (H_DEREF above), but tex_bind can - // fail in debug builds if Tex.id isn't a valid texture name - - -glBindTexture(GL_TEXTURE_2D, t->id); -// HACK HACK HACK - - // set filter - if(!filter) - filter = tex_filter; - const int mag = (filter == GL_NEAREST)? GL_NEAREST : GL_LINEAR; - const bool mipmap = (filter == GL_NEAREST_MIPMAP_NEAREST || filter == GL_LINEAR_MIPMAP_NEAREST || - filter == GL_NEAREST_MIPMAP_LINEAR || filter == GL_LINEAR_MIPMAP_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag); - - - const bool has_alpha = fmt == GL_RGBA || fmt == GL_BGRA || fmt == GL_LUMINANCE_ALPHA || fmt == GL_ALPHA; - - // check if SGIS_generate_mipmap is available (once) - static int sgm_avl = -1; - if(sgm_avl == -1) - sgm_avl = oglExtAvail("GL_SGIS_generate_mipmap"); - - - // S3TC compressed - if(fmt >= GL_COMPRESSED_RGB_S3TC_DXT1_EXT && - fmt <= GL_COMPRESSED_RGBA_S3TC_DXT5_EXT) - { - const int tex_size = w * h * bpp / 8; - // FIXME There's a bug in the file code that makes tex_file_size be - // rounded upwards to nearest multiple of 65536. Commented out until - // the file code has went through the neccessary changes. - //assert(4+sizeof(DDSURFACEDESC2)+tex_size == tex_file_size && "tex_upload: dds file size mismatch"); - - // RC, 020404: added mipmap generation for DDS textures using GL_SGIS_generate_mipmap - works fine - // on ATI cards, verified by others under NVIDIA - if(mipmap) { - if (sgm_avl) { - glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_TRUE); - } else { - // ack - no (easy) way of generating mipmaps for compressed textures; switch back - // to a linear minification filter - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - } - } - - glCompressedTexImage2DARB(GL_TEXTURE_2D, 0, fmt, w, h, 0, tex_size, tex_data); - } - // normal - else - { - // internal fmt not explicitly requested: - // choose a specific, sized internal format for common formats, - // based on the global tex_bpp setting (32 or 16 bpp textures). - // could just let the driver choose, but this gives the user - // control for performance/quality tweaking. - // - // rare formats (e.g. GL_BGR) not in the switch statements - // are handled below. - if(!int_fmt) - { - // high quality, 32 bit textures (8 bits per component) - if(tex_bpp == 32) - { - switch(fmt) - { - case GL_RGBA: - int_fmt = GL_RGBA8; - break; - case GL_RGB: - int_fmt = GL_RGB8; - break; - case GL_LUMINANCE_ALPHA: - int_fmt = GL_LUMINANCE8_ALPHA8; - break; - case GL_ALPHA: - int_fmt = GL_ALPHA8; - break; - case GL_LUMINANCE: - int_fmt = GL_LUMINANCE8; - break; - } - } - // low quality, 16 bit textures (4 bits per component) - else - { - switch(fmt) - { - case GL_RGBA: - int_fmt = GL_RGBA4; - break; - case GL_RGB: - int_fmt = GL_RGB4; - break; - case GL_LUMINANCE_ALPHA: - int_fmt = GL_LUMINANCE4_ALPHA4; - break; - case GL_ALPHA: - int_fmt = GL_ALPHA4; - break; - case GL_LUMINANCE: - int_fmt = GL_LUMINANCE4; - break; - } - } - - // fmt wasn't in the list above, so we haven't chosen - // the internal format yet, and need to do so now. - // set it to # of components in the texture. - // note: can't use the texture data's format - - // not all can be used as an internal format! (e.g. GL_BGR) - if(!int_fmt) - int_fmt = bpp / 8; - } - - // manual mipmap gen via GLU (box filter) - if(mipmap && !sgm_avl) - gluBuild2DMipmaps(GL_TEXTURE_2D, int_fmt, t->w, t->h, t->fmt, GL_UNSIGNED_BYTE, tex_data); - // auto mipmap gen, or no mipmap - else - { - if(mipmap) - glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_TRUE); - - glTexImage2D(GL_TEXTURE_2D, 0, int_fmt, t->w, t->h, 0, t->fmt, GL_UNSIGNED_BYTE, tex_data); - } - } - - mem_free_h(t->hm); - - t->filter = filter; - t->int_fmt = int_fmt; - - return 0; -} - -// TEX output param is invalid if function fails -static int Tex_reload(Tex* t, const char* fn) -{ - printf("Tex_reload for %s.\n", fn); // load file void* _p = 0; size_t size; @@ -960,7 +743,7 @@ static int Tex_reload(Tex* t, const char* fn) if(raw_valid(p, size)) err = raw_load(fn, p, size, t); else #endif - ; // make sure else chain is ended + {}; // make sure else-chain is ended if(err < 0) { @@ -968,105 +751,12 @@ static int Tex_reload(Tex* t, const char* fn) return err; } - // loaders weren't able to determine type - if(t->fmt == FMT_UNKNOWN) - { - assert(t->bpp == 8); - t->fmt = GL_ALPHA; - // TODO: check file name, go to 32 bit if wrong - } - - - uint id; - glGenTextures(1, &id); - t->id = id; - // this can't realistically fail, just note that the already_loaded - // check above assumes (id > 0) <==> texture is loaded and valid - - // was already uploaded once - if(t->int_fmt) - tex_upload(t, fn); - return 0; } -Handle tex_load(const char* const fn, int scope) +int tex_free(TexInfo* t) { - return h_alloc(H_Tex, fn, scope); -} - - -int tex_bind(const Handle h) -{ - Tex* t = H_USER_DATA(h, Tex); - if(!t) - { - glBindTexture(GL_TEXTURE_2D, 0); - return ERR_INVALID_HANDLE; - } - -#ifndef NDEBUG - if(!t->id) - { - debug_warn("tex_bind: Tex.id is not a valid texture"); - return -1; - } -#endif - - glBindTexture(GL_TEXTURE_2D, t->id); - return 0; -} - -int tex_id(const Handle h) -{ - Tex* t = H_USER_DATA(h, Tex); - return t ? t->id : 0; -} - - -int tex_filter = GL_LINEAR; -uint tex_bpp = 32; // 16 or 32 - -int tex_upload(const Handle ht, int filter, int int_fmt, int fmt) -{ - H_DEREF(ht, Tex, t); - t->filter = filter; - t->int_fmt = int_fmt; - if (fmt) - t->fmt = fmt; - - const char* fn = h_filename(ht); - if(!fn) - { - fn = "(could not determine filename)"; - debug_warn("tex_upload(Handle): h_filename failed"); - } - - return tex_upload(t, fn); -} - - -int tex_free(Handle& ht) -{ - return h_free(ht, H_Tex); -} - - -int tex_info(Handle ht, int* w, int* h, int* fmt, int* bpp, void** p) -{ - H_DEREF(ht, Tex, t); - - if(w) - *w = t->w; - if(h) - *h = t->h; - if(fmt) - *fmt = t->fmt; - if(bpp) - *bpp = t->bpp; - if(p) - *p = mem_get_ptr(t->hm); - + mem_free_h(t->hm); return 0; } diff --git a/source/lib/res/tex.h b/source/lib/res/tex.h index 2e5712ecdd..ac68f7fa80 100755 --- a/source/lib/res/tex.h +++ b/source/lib/res/tex.h @@ -21,22 +21,30 @@ #include "h_mgr.h" -// load and return a handle to the texture given in . -// supports RAW, BMP, JP2, PNG, TGA, DDS -extern Handle tex_load(const char* const fn, int scope = 0); -extern int tex_bind(Handle ht); -extern int tex_id(Handle ht); -extern int tex_info(Handle ht, int* w, int* h, int *fmt, int *bpp, void** p); +// TexInfo.flags +enum +{ + TEX_DXT = 0x07, // mask; value = {1,3,5} + TEX_BGR = 8, + TEX_ALPHA = 16, + TEX_GRAY = 32, +}; -extern int tex_filter; // GL values; default: GL_LINEAR -extern unsigned int tex_bpp; // 16 or 32; default: 32 +// minimize size - stored in ogl tex resource control block +struct TexInfo +{ + Handle hm; // H_MEM handle to loaded file + size_t ofs; // offset to image data in file + u32 w : 16; + u32 h : 16; + u32 bpp : 16; + u32 flags : 16; +}; -// upload the specified texture to OpenGL. Texture filter and internal format -// may be specified to override the global defaults. -extern int tex_upload(Handle ht, int filter_override = 0, int internal_fmt_override = 0, int format_override = 0); +extern int tex_load(const char* fn, TexInfo* ti); +extern int tex_free(TexInfo* ti); -extern int tex_free(Handle& ht); #endif // __TEX_H__