/** * @file llimagedxt.cpp * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #include "linden_common.h" #include "llimagedxt.h" #include "llmemory.h" //static void LLImageDXT::checkMinWidthHeight(EFileFormat format, S32& width, S32& height) { S32 mindim = (format >= FORMAT_DXT1 && format <= FORMAT_DXR5) ? 4 : 1; width = llmax(width, mindim); height = llmax(height, mindim); } //static S32 LLImageDXT::formatBits(EFileFormat format) { switch (format) { case FORMAT_DXT1: return 4; case FORMAT_DXR1: return 4; case FORMAT_I8: return 8; case FORMAT_A8: return 8; case FORMAT_DXT3: return 8; case FORMAT_DXR3: return 8; case FORMAT_DXR5: return 8; case FORMAT_DXT5: return 8; case FORMAT_RGB8: return 24; case FORMAT_RGBA8: return 32; default: LL_ERRS() << "LLImageDXT::Unknown format: " << format << LL_ENDL; return 0; } }; //static S32 LLImageDXT::formatBytes(EFileFormat format, S32 width, S32 height) { checkMinWidthHeight(format, width, height); S32 bytes = ((width*height*formatBits(format)+7)>>3); S32 aligned = (bytes+3)&~3; return aligned; } //static S32 LLImageDXT::formatComponents(EFileFormat format) { switch (format) { case FORMAT_DXT1: return 3; case FORMAT_DXR1: return 3; case FORMAT_I8: return 1; case FORMAT_A8: return 1; case FORMAT_DXT3: return 4; case FORMAT_DXR3: return 4; case FORMAT_DXT5: return 4; case FORMAT_DXR5: return 4; case FORMAT_RGB8: return 3; case FORMAT_RGBA8: return 4; default: LL_ERRS() << "LLImageDXT::Unknown format: " << format << LL_ENDL; return 0; } }; // static LLImageDXT::EFileFormat LLImageDXT::getFormat(S32 fourcc) { switch(fourcc) { case 0x20203849: return FORMAT_I8; case 0x20203841: return FORMAT_A8; case 0x20424752: return FORMAT_RGB8; case 0x41424752: return FORMAT_RGBA8; case 0x31525844: return FORMAT_DXR1; case 0x32525844: return FORMAT_DXR2; case 0x33525844: return FORMAT_DXR3; case 0x34525844: return FORMAT_DXR4; case 0x35525844: return FORMAT_DXR5; case 0x31545844: return FORMAT_DXT1; case 0x32545844: return FORMAT_DXT2; case 0x33545844: return FORMAT_DXT3; case 0x34545844: return FORMAT_DXT4; case 0x35545844: return FORMAT_DXT5; default: return FORMAT_UNKNOWN; } } //static S32 LLImageDXT::getFourCC(EFileFormat format) { switch(format) { case FORMAT_I8: return 0x20203849; case FORMAT_A8: return 0x20203841; case FORMAT_RGB8: return 0x20424752; case FORMAT_RGBA8: return 0x41424752; case FORMAT_DXR1: return 0x31525844; case FORMAT_DXR2: return 0x32525844; case FORMAT_DXR3: return 0x33525844; case FORMAT_DXR4: return 0x34525844; case FORMAT_DXR5: return 0x35525844; case FORMAT_DXT1: return 0x31545844; case FORMAT_DXT2: return 0x32545844; case FORMAT_DXT3: return 0x33545844; case FORMAT_DXT4: return 0x34545844; case FORMAT_DXT5: return 0x35545844; default: return 0x00000000; } } //static void LLImageDXT::calcDiscardWidthHeight(S32 discard_level, EFileFormat format, S32& width, S32& height) { while (discard_level > 0 && width > 1 && height > 1) { discard_level--; width >>= 1; height >>= 1; } checkMinWidthHeight(format, width, height); } //static S32 LLImageDXT::calcNumMips(S32 width, S32 height) { S32 nmips = 0; while (width > 0 && height > 0) { width >>= 1; height >>= 1; nmips++; } return nmips; } //============================================================================ LLImageDXT::LLImageDXT() : LLImageFormatted(IMG_CODEC_DXT), mFileFormat(FORMAT_UNKNOWN), mHeaderSize(0) { } LLImageDXT::~LLImageDXT() { } // virtual bool LLImageDXT::updateData() { resetLastError(); U8* data = getData(); S32 data_size = getDataSize(); if (!data || !data_size) { setLastError("LLImageDXT uninitialized"); return false; } S32 width, height, miplevelmax; dxtfile_header_t* header = (dxtfile_header_t*)data; if (header->fourcc != 0x20534444) { dxtfile_header_old_t* oldheader = (dxtfile_header_old_t*)header; mHeaderSize = sizeof(dxtfile_header_old_t); mFileFormat = EFileFormat(oldheader->format); miplevelmax = llmin(oldheader->maxlevel,MAX_IMAGE_MIP); width = oldheader->maxwidth; height = oldheader->maxheight; } else { mHeaderSize = sizeof(dxtfile_header_t); mFileFormat = getFormat(header->pixel_fmt.fourcc); miplevelmax = llmin(header->num_mips-1,MAX_IMAGE_MIP); width = header->maxwidth; height = header->maxheight; } if (data_size < mHeaderSize) { LL_ERRS() << "LLImageDXT: not enough data" << LL_ENDL; } S32 ncomponents = formatComponents(mFileFormat); setSize(width, height, ncomponents); S32 discard = calcDiscardLevelBytes(data_size); discard = llmin(discard, miplevelmax); setDiscardLevel(discard); return true; } // discard: 0 = largest (last) mip S32 LLImageDXT::getMipOffset(S32 discard) { if (mFileFormat >= FORMAT_DXT1 && mFileFormat <= FORMAT_DXT5) { LL_ERRS() << "getMipOffset called with old (unsupported) format" << LL_ENDL; } S32 width = getWidth(), height = getHeight(); S32 num_mips = calcNumMips(width, height); discard = llclamp(discard, 0, num_mips-1); S32 last_mip = num_mips-1-discard; llassert(mHeaderSize > 0); S32 offset = mHeaderSize; for (S32 mipidx = num_mips-1; mipidx >= 0; mipidx--) { if (mipidx < last_mip) { offset += formatBytes(mFileFormat, width, height); } width >>= 1; height >>= 1; } return offset; } void LLImageDXT::setFormat() { S32 ncomponents = getComponents(); switch (ncomponents) { case 3: mFileFormat = FORMAT_DXR1; break; case 4: mFileFormat = FORMAT_DXR3; break; default: LL_ERRS() << "LLImageDXT::setFormat called with ncomponents = " << ncomponents << LL_ENDL; } mHeaderSize = calcHeaderSize(); } // virtual bool LLImageDXT::decode(LLImageRaw* raw_image, F32 time) { // *TODO: Test! This has been tweaked since its intial inception, // but we don't use it any more! llassert_always(raw_image); if (mFileFormat >= FORMAT_DXT1 && mFileFormat <= FORMAT_DXR5) { LL_WARNS() << "Attempt to decode compressed LLImageDXT to Raw (unsupported)" << LL_ENDL; return false; } S32 width = getWidth(), height = getHeight(); S32 ncomponents = getComponents(); U8* data = NULL; if (mDiscardLevel >= 0) { data = getData() + getMipOffset(mDiscardLevel); calcDiscardWidthHeight(mDiscardLevel, mFileFormat, width, height); } else { data = getData() + getMipOffset(0); } S32 image_size = formatBytes(mFileFormat, width, height); if ((!getData()) || (data + image_size > getData() + getDataSize())) { setLastError("LLImageDXT trying to decode an image with not enough data!"); return false; } if (!raw_image->resize(width, height, ncomponents)) { setLastError("llImageDXT failed to resize image!"); return false; } memcpy(raw_image->getData(), data, image_size); /* Flawfinder: ignore */ return true; } bool LLImageDXT::getMipData(LLPointer<LLImageRaw>& raw, S32 discard) { if (discard < 0) { discard = mDiscardLevel; } else if (discard < mDiscardLevel) { LL_ERRS() << "Request for invalid discard level" << LL_ENDL; } U8* data = getData() + getMipOffset(discard); S32 width = 0; S32 height = 0; calcDiscardWidthHeight(discard, mFileFormat, width, height); raw = new LLImageRaw(data, width, height, getComponents()); return true; } bool LLImageDXT::encodeDXT(const LLImageRaw* raw_image, F32 time, bool explicit_mips) { llassert_always(raw_image); S32 ncomponents = raw_image->getComponents(); EFileFormat format; switch (ncomponents) { case 1: format = FORMAT_A8; break; case 3: format = FORMAT_RGB8; break; case 4: format = FORMAT_RGBA8; break; default: LL_ERRS() << "LLImageDXT::encode: Unhandled channel number: " << ncomponents << LL_ENDL; return 0; } S32 width = raw_image->getWidth(); S32 height = raw_image->getHeight(); if (explicit_mips) { height = (height/3)*2; } setSize(width, height, ncomponents); mHeaderSize = sizeof(dxtfile_header_t); mFileFormat = format; S32 nmips = calcNumMips(width, height); S32 w = width; S32 h = height; S32 totbytes = mHeaderSize; for (S32 mip=0; mip<nmips; mip++) { totbytes += formatBytes(format,w,h); w >>= 1; h >>= 1; } allocateData(totbytes); U8* data = getData(); dxtfile_header_t* header = (dxtfile_header_t*)data; llassert(mHeaderSize > 0); memset(header, 0, mHeaderSize); header->fourcc = 0x20534444; header->pixel_fmt.fourcc = getFourCC(format); header->num_mips = nmips; header->maxwidth = width; header->maxheight = height; U8* prev_mipdata = 0; w = width, h = height; for (S32 mip=0; mip<nmips; mip++) { U8* mipdata = data + getMipOffset(mip); S32 bytes = formatBytes(format, w, h); if (mip==0) { memcpy(mipdata, raw_image->getData(), bytes); /* Flawfinder: ignore */ } else if (explicit_mips) { extractMip(raw_image->getData(), mipdata, width, height, w, h, format); } else { generateMip(prev_mipdata, mipdata, w, h, ncomponents); } w >>= 1; h >>= 1; checkMinWidthHeight(format, w, h); prev_mipdata = mipdata; } return true; } // virtual bool LLImageDXT::encode(const LLImageRaw* raw_image, F32 time) { return encodeDXT(raw_image, time, false); } // virtual bool LLImageDXT::convertToDXR() { EFileFormat newformat = FORMAT_UNKNOWN; switch (mFileFormat) { case FORMAT_DXR1: case FORMAT_DXR2: case FORMAT_DXR3: case FORMAT_DXR4: case FORMAT_DXR5: return false; // nothing to do case FORMAT_DXT1: newformat = FORMAT_DXR1; break; case FORMAT_DXT2: newformat = FORMAT_DXR2; break; case FORMAT_DXT3: newformat = FORMAT_DXR3; break; case FORMAT_DXT4: newformat = FORMAT_DXR4; break; case FORMAT_DXT5: newformat = FORMAT_DXR5; break; default: LL_WARNS() << "convertToDXR: can not convert format: " << llformat("0x%08x",getFourCC(mFileFormat)) << LL_ENDL; return false; } mFileFormat = newformat; S32 width = getWidth(), height = getHeight(); S32 nmips = calcNumMips(width,height); S32 total_bytes = getDataSize(); U8* olddata = getData(); U8* newdata = (U8*)ll_aligned_malloc_16(total_bytes); if (!newdata) { LL_ERRS() << "Out of memory in LLImageDXT::convertToDXR()" << LL_ENDL; return false; } llassert(total_bytes > 0); memset(newdata, 0, total_bytes); memcpy(newdata, olddata, mHeaderSize); /* Flawfinder: ignore */ for (S32 mip=0; mip<nmips; mip++) { S32 bytes = formatBytes(mFileFormat, width, height); S32 newoffset = getMipOffset(mip); S32 oldoffset = mHeaderSize + (total_bytes - newoffset - bytes); memcpy(newdata + newoffset, olddata + oldoffset, bytes); /* Flawfinder: ignore */ width >>= 1; height >>= 1; } dxtfile_header_t* header = (dxtfile_header_t*)newdata; header->pixel_fmt.fourcc = getFourCC(newformat); setData(newdata, total_bytes); updateData(); return true; } // virtual S32 LLImageDXT::calcHeaderSize() { return llmax(sizeof(dxtfile_header_old_t), sizeof(dxtfile_header_t)); } // virtual S32 LLImageDXT::calcDataSize(S32 discard_level) { if (mFileFormat == FORMAT_UNKNOWN) { LL_ERRS() << "calcDataSize called with unloaded LLImageDXT" << LL_ENDL; return 0; } if (discard_level < 0) { discard_level = mDiscardLevel; } S32 bytes = getMipOffset(discard_level); // size of header + previous mips S32 w = getWidth() >> discard_level; S32 h = getHeight() >> discard_level; bytes += formatBytes(mFileFormat,w,h); return bytes; } //============================================================================ //static void LLImageDXT::extractMip(const U8 *indata, U8* mipdata, int width, int height, int mip_width, int mip_height, EFileFormat format) { int initial_offset = formatBytes(format, width, height); int line_width = formatBytes(format, width, 1); int mip_line_width = formatBytes(format, mip_width, 1); int line_offset = 0; for (int ww=width>>1; ww>mip_width; ww>>=1) { line_offset += formatBytes(format, ww, 1); } for (int h=0;h<mip_height;++h) { int start_offset = initial_offset + line_width * h + line_offset; memcpy(mipdata + mip_line_width*h, indata + start_offset, mip_line_width); /* Flawfinder: ignore */ } } //============================================================================