diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 91275f4e6..e6589e548 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -540,6 +540,10 @@ void Bed3D::render_prusa(const std::string &key, bool bottom) const } #endif // !ENABLE_COMPRESSED_TEXTURES } +#if ENABLE_COMPRESSED_TEXTURES + else if (m_texture.unsent_compressed_data_available()) + m_texture.send_compressed_data_to_gpu(); +#endif // ENABLE_COMPRESSED_TEXTURES if (!bottom) { diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp index 9c1e1f22e..cc6bdc715 100644 --- a/src/slic3r/GUI/GLTexture.cpp +++ b/src/slic3r/GUI/GLTexture.cpp @@ -12,6 +12,12 @@ #include #include +#if ENABLE_COMPRESSED_TEXTURES +#include + +#define STB_DXT_IMPLEMENTATION +#include "stb_dxt/stb_dxt.h" +#endif // ENABLE_COMPRESSED_TEXTURES #include "nanosvg/nanosvg.h" #include "nanosvg/nanosvgrast.h" @@ -21,6 +27,86 @@ namespace Slic3r { namespace GUI { +#if ENABLE_COMPRESSED_TEXTURES +void GLTexture::Compressor::reset() +{ + // force compression completion, if any + m_abort_compressing = true; + // wait for compression completion, if any + while (m_is_compressing) {} + + m_levels.clear(); +} + +void GLTexture::Compressor::add_level(unsigned int w, unsigned int h, const std::vector& data) +{ + m_levels.emplace_back(w, h, data); +} + +void GLTexture::Compressor::start_compressing() +{ + m_is_compressing = true; + m_abort_compressing = false; + std::thread t(&GLTexture::Compressor::compress, this); + t.detach(); +} + +bool GLTexture::Compressor::unsent_compressed_data_available() const +{ + for (const Level& level : m_levels) + { + if (!level.sent_to_gpu && (level.compressed_data.size() > 0)) + return true; + } + + return false; +} + +void GLTexture::Compressor::send_compressed_data_to_gpu() +{ + glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); + glsafe(::glBindTexture(GL_TEXTURE_2D, m_texture.m_id)); + for (int i = 0; i < (int)m_levels.size(); ++i) + { + Level& level = m_levels[i]; + if (!level.sent_to_gpu && (level.compressed_data.size() > 0)) + { + glsafe(::glCompressedTexSubImage2D(GL_TEXTURE_2D, (GLint)i, 0, 0, (GLsizei)level.w, (GLsizei)level.h, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)level.compressed_data.size(), (const GLvoid*)level.compressed_data.data())); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, i)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (i > 0) ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR)); + level.sent_to_gpu = true; + // we are done with the compressed data, we can discard it + level.compressed_data.clear(); + } + } + glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); +} + +void GLTexture::Compressor::compress() +{ + // reference: https://github.com/Cyan4973/RygsDXTc + + for (Level& level : m_levels) + { + if (m_abort_compressing) + break; + + // stb_dxt library, despite claiming that the needed size of the destination buffer is equal to (source buffer size)/4, + // crashes if doing so, so we start with twice the required size + level.compressed_data = std::vector(level.w * level.h * 2, 0); + int compressed_size = 0; + rygCompress(level.compressed_data.data(), level.src_data.data(), level.w, level.h, 1, compressed_size); + level.compressed_data.resize(compressed_size); + + // we are done with the source data, we can discard it + level.src_data.clear(); + } + + m_is_compressing = false; + m_abort_compressing = false; +} +#endif // ENABLE_COMPRESSED_TEXTURES + GLTexture::Quad_UVs GLTexture::FullTextureUVs = { { 0.0f, 1.0f }, { 1.0f, 1.0f }, { 1.0f, 0.0f }, { 0.0f, 0.0f } }; GLTexture::GLTexture() @@ -28,6 +114,9 @@ GLTexture::GLTexture() , m_width(0) , m_height(0) , m_source("") +#if ENABLE_COMPRESSED_TEXTURES + , m_compressor(*this) +#endif // ENABLE_COMPRESSED_TEXTURES { } @@ -249,8 +338,23 @@ void GLTexture::reset() m_width = 0; m_height = 0; m_source = ""; +#if ENABLE_COMPRESSED_TEXTURES + m_compressor.reset(); +#endif // ENABLE_COMPRESSED_TEXTURES } +#if ENABLE_COMPRESSED_TEXTURES +bool GLTexture::unsent_compressed_data_available() const +{ + return m_compressor.unsent_compressed_data_available(); +} + +void GLTexture::send_compressed_data_to_gpu() +{ + m_compressor.send_compressed_data_to_gpu(); +} +#endif // ENABLE_COMPRESSED_TEXTURES + void GLTexture::render_texture(unsigned int tex_id, float left, float right, float bottom, float top) { render_sub_texture(tex_id, left, right, bottom, top, FullTextureUVs); @@ -416,6 +520,8 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, unsigned int max_size_px) #endif // ENABLE_COMPRESSED_TEXTURES { + bool compression_enabled = compress && GLEW_EXT_texture_compression_s3tc; + NSVGimage* image = nsvgParseFromFile(filename.c_str(), "px", 96.0f); if (image == nullptr) { @@ -428,6 +534,22 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, uns m_width = (int)(scale * image->width); m_height = (int)(scale * image->height); + +#if ENABLE_COMPRESSED_TEXTURES + if (compression_enabled) + { + // the stb_dxt compression library seems to like only texture sizes which are a multiple of 4 + int width_rem = m_width % 4; + int height_rem = m_height % 4; + + if (width_rem != 0) + m_width += (4 - width_rem); + + if (height_rem != 0) + m_height += (4 - height_rem); + } +#endif // ENABLE_COMPRESSED_TEXTURES + int n_pixels = m_width * m_height; if (n_pixels <= 0) @@ -462,8 +584,13 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, uns glsafe(::glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy)); } - if (compress && GLEW_EXT_texture_compression_s3tc) - glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); + if (compression_enabled) + { + // initializes the texture on GPU + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); + // and send the uncompressed data to the compressor + m_compressor.add_level((unsigned int)m_width, (unsigned int)m_height, data); + } else glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); #else @@ -475,7 +602,8 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, uns int lod_w = m_width; int lod_h = m_height; GLint level = 0; - while ((lod_w > 1) || (lod_h > 1)) + // we do not need to generate all levels down to 1x1 + while ((lod_w > 64) || (lod_h > 64)) { ++level; @@ -483,10 +611,19 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, uns lod_h = std::max(lod_h / 2, 1); scale /= 2.0f; +#if ENABLE_COMPRESSED_TEXTURES + data.resize(lod_w * lod_h * 4); +#endif // ENABLE_COMPRESSED_TEXTURES + nsvgRasterize(rast, image, 0, 0, scale, data.data(), lod_w, lod_h, lod_w * 4); #if ENABLE_COMPRESSED_TEXTURES - if (compress && GLEW_EXT_texture_compression_s3tc) - glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); + if (compression_enabled) + { + // initializes the texture on GPU + glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); + // and send the uncompressed data to the compressor + m_compressor.add_level((unsigned int)lod_w, (unsigned int)lod_h, data); + } else glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); #else @@ -494,8 +631,15 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, uns #endif // ENABLE_COMPRESSED_TEXTURES } - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, level)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)); +#if ENABLE_COMPRESSED_TEXTURES + if (!compression_enabled) + { +#endif // ENABLE_COMPRESSED_TEXTURES + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, level)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)); +#if ENABLE_COMPRESSED_TEXTURES + } +#endif // ENABLE_COMPRESSED_TEXTURES } else { @@ -508,6 +652,12 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, uns m_source = filename; +#if ENABLE_COMPRESSED_TEXTURES + if (compression_enabled) + // start asynchronous compression + m_compressor.start_compressing(); +#endif // ENABLE_COMPRESSED_TEXTURES + nsvgDeleteRasterizer(rast); nsvgDelete(image); diff --git a/src/slic3r/GUI/GLTexture.hpp b/src/slic3r/GUI/GLTexture.hpp index 032d19121..f5cf13a45 100644 --- a/src/slic3r/GUI/GLTexture.hpp +++ b/src/slic3r/GUI/GLTexture.hpp @@ -11,6 +11,42 @@ namespace GUI { class GLTexture { +#if ENABLE_COMPRESSED_TEXTURES + class Compressor + { + struct Level + { + unsigned int w; + unsigned int h; + std::vector src_data; + std::vector compressed_data; + bool sent_to_gpu; + + Level(unsigned int w, unsigned int h, const std::vector& data) : w(w), h(h), src_data(data), sent_to_gpu(false) {} + }; + + GLTexture& m_texture; + std::vector m_levels; + bool m_is_compressing; + bool m_abort_compressing; + + public: + explicit Compressor(GLTexture& texture) : m_texture(texture), m_is_compressing(false), m_abort_compressing(false) {} + + void reset(); + + void add_level(unsigned int w, unsigned int h, const std::vector& data); + + void start_compressing(); + + bool unsent_compressed_data_available() const; + void send_compressed_data_to_gpu(); + + private: + void compress(); + }; +#endif // ENABLE_COMPRESSED_TEXTURES + public: struct UV { @@ -33,6 +69,9 @@ namespace GUI { int m_width; int m_height; std::string m_source; +#if ENABLE_COMPRESSED_TEXTURES + Compressor m_compressor; +#endif // ENABLE_COMPRESSED_TEXTURES public: GLTexture(); @@ -66,6 +105,11 @@ namespace GUI { const std::string& get_source() const { return m_source; } +#if ENABLE_COMPRESSED_TEXTURES + bool unsent_compressed_data_available() const; + void send_compressed_data_to_gpu(); +#endif // ENABLE_COMPRESSED_TEXTURES + static void render_texture(unsigned int tex_id, float left, float right, float bottom, float top); static void render_sub_texture(unsigned int tex_id, float left, float right, float bottom, float top, const Quad_UVs& uvs); @@ -80,6 +124,8 @@ namespace GUI { #if ENABLE_COMPRESSED_TEXTURES bool load_from_png(const std::string& filename, bool use_mipmaps, bool compress); bool load_from_svg(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px); + + friend class Compressor; #else bool load_from_png(const std::string& filename, bool use_mipmaps); bool load_from_svg(const std::string& filename, bool use_mipmaps, unsigned int max_size_px); diff --git a/src/stb_dxt/stb_dxt.h b/src/stb_dxt/stb_dxt.h new file mode 100644 index 000000000..db19110f4 --- /dev/null +++ b/src/stb_dxt/stb_dxt.h @@ -0,0 +1,1049 @@ +// stb_dxt.h - Real-Time DXT1/DXT5 compressor +// Based on original by fabian "ryg" giesen v1.04 +// Custom version, modified by Yann Collet +// +/* + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - RygsDXTc source repository : http://code.google.com/p/rygsdxtc/ + +*/ +// use '#define STB_DXT_IMPLEMENTATION' before including to create the implementation +// +// USAGE: +// call stb_compress_dxt_block() for every block (you must pad) +// source should be a 4x4 block of RGBA data in row-major order; +// A is ignored if you specify alpha=0; you can turn on dithering +// and "high quality" using mode. +// +// version history: +// v1.06 - (cyan) implement Fabian Giesen's comments +// v1.05 - (cyan) speed optimizations +// v1.04 - (ryg) default to no rounding bias for lerped colors (as per S3TC/DX10 spec); +// single color match fix (allow for inexact color interpolation); +// optimal DXT5 index finder; "high quality" mode that runs multiple refinement steps. +// v1.03 - (stb) endianness support +// v1.02 - (stb) fix alpha encoding bug +// v1.01 - (stb) fix bug converting to RGB that messed up quality, thanks ryg & cbloom +// v1.00 - (stb) first release + +#ifndef STB_INCLUDE_STB_DXT_H +#define STB_INCLUDE_STB_DXT_H + + +//******************************************************************* +// Enable custom Optimisations +// Comment this define if you want to revert to ryg's original code +#define NEW_OPTIMISATIONS +//******************************************************************* + +// compression mode (bitflags) +#define STB_DXT_NORMAL 0 +#define STB_DXT_DITHER 1 // use dithering. dubious win. never use for normal maps and the like! +#define STB_DXT_HIGHQUAL 2 // high quality mode, does two refinement steps instead of 1. ~30-40% slower. + +// The original signature has been modified by adding the parameter compressed_size which returns +// the size in bytes of the compressed data contained into dst +void rygCompress(unsigned char *dst, unsigned char *src, int w, int h, int isDxt5, int& compressed_size); + +// TODO remove these, not working properly.. +void rygCompressYCoCg( unsigned char *dst, unsigned char *src, int w, int h ); +void linearize( unsigned char * dst, const unsigned char * src, int n ); + +void stb_compress_dxt_block(unsigned char *dest, const unsigned char *src, int alpha, int mode); +#define STB_COMPRESS_DXT_BLOCK + +#ifdef STB_DXT_IMPLEMENTATION + +// configuration options for DXT encoder. set them in the project/makefile or just define +// them at the top. + +// STB_DXT_USE_ROUNDING_BIAS +// use a rounding bias during color interpolation. this is closer to what "ideal" +// interpolation would do but doesn't match the S3TC/DX10 spec. old versions (pre-1.03) +// implicitly had this turned on. +// +// in case you're targeting a specific type of hardware (e.g. console programmers): +// NVidia and Intel GPUs (as of 2010) as well as DX9 ref use DXT decoders that are closer +// to STB_DXT_USE_ROUNDING_BIAS. AMD/ATI, S3 and DX10 ref are closer to rounding with no bias. +// you also see "(a*5 + b*3) / 8" on some old GPU designs. +// #define STB_DXT_USE_ROUNDING_BIAS + +#include +#include +#include +#include // memset +#include +#include +#include + + +static unsigned char stb__Expand5[32]; +static unsigned char stb__Expand6[64]; +static unsigned char stb__OMatch5[256][2]; +static unsigned char stb__OMatch6[256][2]; +static unsigned char stb__QuantRBTab[256+16]; +static unsigned char stb__QuantGTab[256+16]; + +static int stb__Mul8Bit(int a, int b) +{ + int t = a*b + 128; + return (t + (t >> 8)) >> 8; +} + +static void stb__From16Bit(unsigned char *out, unsigned short v) +{ + int rv = (v & 0xf800) >> 11; + int gv = (v & 0x07e0) >> 5; + int bv = (v & 0x001f) >> 0; + + out[0] = stb__Expand5[rv]; + out[1] = stb__Expand6[gv]; + out[2] = stb__Expand5[bv]; + out[3] = 0; +} + +static unsigned short stb__As16Bit(int r, int g, int b) +{ + return (stb__Mul8Bit(r,31) << 11) + (stb__Mul8Bit(g,63) << 5) + stb__Mul8Bit(b,31); +} + +// linear interpolation at 1/3 point between a and b, using desired rounding type +static int stb__Lerp13(int a, int b) +{ +#ifdef STB_DXT_USE_ROUNDING_BIAS + // with rounding bias + return a + stb__Mul8Bit(b-a, 0x55); +#else + // without rounding bias + // replace "/ 3" by "* 0xaaab) >> 17" if your compiler sucks or you really need every ounce of speed. + return (2*a + b) / 3; +#endif +} + +// lerp RGB color +static void stb__Lerp13RGB(unsigned char *out, unsigned char *p1, unsigned char *p2) +{ + out[0] = stb__Lerp13(p1[0], p2[0]); + out[1] = stb__Lerp13(p1[1], p2[1]); + out[2] = stb__Lerp13(p1[2], p2[2]); +} + +/****************************************************************************/ + +// compute table to reproduce constant colors as accurately as possible +static void stb__PrepareOptTable(unsigned char *Table,const unsigned char *expand,int size) +{ + int i,mn,mx; + for (i=0;i<256;i++) { + int bestErr = 256; + for (mn=0;mn> 4)]; + ep1[0] = bp[ 0] - dp[ 0]; + dp[ 4] = quant[bp[ 4] + ((7*ep1[0] + 3*ep2[2] + 5*ep2[1] + ep2[0]) >> 4)]; + ep1[1] = bp[ 4] - dp[ 4]; + dp[ 8] = quant[bp[ 8] + ((7*ep1[1] + 3*ep2[3] + 5*ep2[2] + ep2[1]) >> 4)]; + ep1[2] = bp[ 8] - dp[ 8]; + dp[12] = quant[bp[12] + ((7*ep1[2] + 5*ep2[3] + ep2[2]) >> 4)]; + ep1[3] = bp[12] - dp[12]; + bp += 16; + dp += 16; + et = ep1, ep1 = ep2, ep2 = et; // swap + } + } +} + +// The color matching function +static unsigned int stb__MatchColorsBlock(unsigned char *block, unsigned char *color,int dither) +{ + unsigned int mask = 0; + int dirr = color[0*4+0] - color[1*4+0]; + int dirg = color[0*4+1] - color[1*4+1]; + int dirb = color[0*4+2] - color[1*4+2]; + int dots[16]; + int stops[4]; + int i; + int c0Point, halfPoint, c3Point; + + for(i=0;i<16;i++) + dots[i] = block[i*4+0]*dirr + block[i*4+1]*dirg + block[i*4+2]*dirb; + + for(i=0;i<4;i++) + stops[i] = color[i*4+0]*dirr + color[i*4+1]*dirg + color[i*4+2]*dirb; + + // think of the colors as arranged on a line; project point onto that line, then choose + // next color out of available ones. we compute the crossover points for "best color in top + // half"/"best in bottom half" and then the same inside that subinterval. + // + // relying on this 1d approximation isn't always optimal in terms of euclidean distance, + // but it's very close and a lot faster. + // http://cbloomrants.blogspot.com/2008/12/12-08-08-dxtc-summary.html + + c0Point = (stops[1] + stops[3]) >> 1; + halfPoint = (stops[3] + stops[2]) >> 1; + c3Point = (stops[2] + stops[0]) >> 1; + + if(!dither) + { + // the version without dithering is straightforward + +#ifdef NEW_OPTIMISATIONS + const int indexMap[8] = { 0 << 30,2 << 30,0 << 30,2 << 30,3 << 30,3 << 30,1 << 30,1 << 30 }; + + for(int i=0;i<16;i++) + { + int dot = dots[i]; + mask >>= 2; + + int bits =( (dot < halfPoint) ? 4 : 0 ) + | ( (dot < c0Point) ? 2 : 0 ) + | ( (dot < c3Point) ? 1 : 0 ); + + mask |= indexMap[bits]; + } + +#else + for (i=15;i>=0;i--) { + int dot = dots[i]; + mask <<= 2; + + if(dot < halfPoint) + mask |= (dot < c0Point) ? 1 : 3; + else + mask |= (dot < c3Point) ? 2 : 0; + } +#endif + + } else { + // with floyd-steinberg dithering + int err[8],*ep1 = err,*ep2 = err+4; + int *dp = dots, y; + + c0Point <<= 4; + halfPoint <<= 4; + c3Point <<= 4; + for(i=0;i<8;i++) + err[i] = 0; + + for(y=0;y<4;y++) + { + int dot,lmask,step; + + dot = (dp[0] << 4) + (3*ep2[1] + 5*ep2[0]); + if(dot < halfPoint) + step = (dot < c0Point) ? 1 : 3; + else + step = (dot < c3Point) ? 2 : 0; + ep1[0] = dp[0] - stops[step]; + lmask = step; + + dot = (dp[1] << 4) + (7*ep1[0] + 3*ep2[2] + 5*ep2[1] + ep2[0]); + if(dot < halfPoint) + step = (dot < c0Point) ? 1 : 3; + else + step = (dot < c3Point) ? 2 : 0; + ep1[1] = dp[1] - stops[step]; + lmask |= step<<2; + + dot = (dp[2] << 4) + (7*ep1[1] + 3*ep2[3] + 5*ep2[2] + ep2[1]); + if(dot < halfPoint) + step = (dot < c0Point) ? 1 : 3; + else + step = (dot < c3Point) ? 2 : 0; + ep1[2] = dp[2] - stops[step]; + lmask |= step<<4; + + dot = (dp[3] << 4) + (7*ep1[2] + 5*ep2[3] + ep2[2]); + if(dot < halfPoint) + step = (dot < c0Point) ? 1 : 3; + else + step = (dot < c3Point) ? 2 : 0; + ep1[3] = dp[3] - stops[step]; + lmask |= step<<6; + + dp += 4; + mask |= lmask << (y*8); + { int *et = ep1; ep1 = ep2; ep2 = et; } // swap + } + } + + return mask; +} + +// The color optimization function. (Clever code, part 1) +static void stb__OptimizeColorsBlock(unsigned char *block, unsigned short *pmax16, unsigned short *pmin16) +{ + unsigned char *minp, *maxp; + double magn; + int v_r,v_g,v_b; + static const int nIterPower = 4; + float covf[6],vfr,vfg,vfb; + + // determine color distribution + int cov[6]; + int mu[3],min[3],max[3]; + int ch,i,iter; + + for(ch=0;ch<3;ch++) + { + const unsigned char *bp = ((const unsigned char *) block) + ch; + int muv,minv,maxv; + +#ifdef NEW_OPTIMISATIONS +# define MIN(a,b) (int)a + ( ((int)b-a) & ( ((int)b-a) >> 31 ) ) +# define MAX(a,b) (int)a + ( ((int)b-a) & ( ((int)a-b) >> 31 ) ) +# define RANGE(a,b,n) int min##n = MIN(a,b); int max##n = a+b - min##n; muv += a+b; +# define MINMAX(a,b,n) int min##n = MIN(min##a, min##b); int max##n = MAX(max##a, max##b); + + muv = 0; + RANGE(bp[0], bp[4], 1); + RANGE(bp[8], bp[12], 2); + RANGE(bp[16], bp[20], 3); + RANGE(bp[24], bp[28], 4); + RANGE(bp[32], bp[36], 5); + RANGE(bp[40], bp[44], 6); + RANGE(bp[48], bp[52], 7); + RANGE(bp[56], bp[60], 8); + + MINMAX(1,2,9); + MINMAX(3,4,10); + MINMAX(5,6,11); + MINMAX(7,8,12); + + MINMAX(9,10,13); + MINMAX(11,12,14); + + minv = MIN(min13,min14); + maxv = MAX(max13,max14); + +#else + muv = minv = maxv = bp[0]; + for(i=4;i<64;i+=4) + { + muv += bp[i]; + if (bp[i] < minv) minv = bp[i]; + else if (bp[i] > maxv) maxv = bp[i]; + } +#endif + + mu[ch] = (muv + 8) >> 4; + min[ch] = minv; + max[ch] = maxv; + } + + // determine covariance matrix + for (i=0;i<6;i++) + cov[i] = 0; + + for (i=0;i<16;i++) + { + int r = block[i*4+0] - mu[0]; + int g = block[i*4+1] - mu[1]; + int b = block[i*4+2] - mu[2]; + + cov[0] += r*r; + cov[1] += r*g; + cov[2] += r*b; + cov[3] += g*g; + cov[4] += g*b; + cov[5] += b*b; + } + + // convert covariance matrix to float, find principal axis via power iter + for(i=0;i<6;i++) + covf[i] = cov[i] / 255.0f; + + vfr = (float) (max[0] - min[0]); + vfg = (float) (max[1] - min[1]); + vfb = (float) (max[2] - min[2]); + + for(iter=0;iter magn) magn = fabs(vfg); + if (fabs(vfb) > magn) magn = fabs(vfb); + + if(magn < 4.0f) + { // too small, default to luminance + v_r = 299; // JPEG YCbCr luma coefs, scaled by 1000. + v_g = 587; + v_b = 114; + } else { + magn = 512.0 / magn; + v_r = (int) (vfr * magn); + v_g = (int) (vfg * magn); + v_b = (int) (vfb * magn); + } + + +#ifdef NEW_OPTIMISATIONS + // Pick colors at extreme points + int mind, maxd; + mind = maxd = block[0]*v_r + block[1]*v_g + block[2]*v_b; + minp = maxp = block; + for(i=1;i<16;i++) + { + int dot = block[i*4+0]*v_r + block[i*4+1]*v_g + block[i*4+2]*v_b; + + if (dot < mind) { + mind = dot; + minp = block+i*4; + continue; + } + + if (dot > maxd) { + maxd = dot; + maxp = block+i*4; + } + } +#else + int mind = 0x7fffffff,maxd = -0x7fffffff; + // Pick colors at extreme points + for(i=0;i<16;i++) + { + int dot = block[i*4+0]*v_r + block[i*4+1]*v_g + block[i*4+2]*v_b; + + if (dot < mind) { + mind = dot; + minp = block+i*4; + } + + if (dot > maxd) { + maxd = dot; + maxp = block+i*4; + } + } +#endif + + *pmax16 = stb__As16Bit(maxp[0],maxp[1],maxp[2]); + *pmin16 = stb__As16Bit(minp[0],minp[1],minp[2]); +} + +inline static int stb__sclamp(float y, int p0, int p1) +{ + int x = (int) y; + +#ifdef NEW_OPTIMISATIONS + x = x>p1 ? p1 : x; + return x p1) return p1; + return x; +#endif +} + +// The refinement function. (Clever code, part 2) +// Tries to optimize colors to suit block contents better. +// (By solving a least squares system via normal equations+Cramer's rule) +static int stb__RefineBlock(unsigned char *block, unsigned short *pmax16, unsigned short *pmin16, unsigned int mask) +{ + static const int w1Tab[4] = { 3,0,2,1 }; + static const int prods[4] = { 0x090000,0x000900,0x040102,0x010402 }; + // ^some magic to save a lot of multiplies in the accumulating loop... + // (precomputed products of weights for least squares system, accumulated inside one 32-bit register) + + float frb,fg; + unsigned short oldMin, oldMax, min16, max16; + int i, akku = 0, xx,xy,yy; + int At1_r,At1_g,At1_b; + int At2_r,At2_g,At2_b; + unsigned int cm = mask; + + oldMin = *pmin16; + oldMax = *pmax16; + + if((mask ^ (mask<<2)) < 4) // all pixels have the same index? + { + // yes, linear system would be singular; solve using optimal + // single-color match on average color + int r = 8, g = 8, b = 8; + for (i=0;i<16;++i) { + r += block[i*4+0]; + g += block[i*4+1]; + b += block[i*4+2]; + } + + r >>= 4; g >>= 4; b >>= 4; + + max16 = (stb__OMatch5[r][0]<<11) | (stb__OMatch6[g][0]<<5) | stb__OMatch5[b][0]; + min16 = (stb__OMatch5[r][1]<<11) | (stb__OMatch6[g][1]<<5) | stb__OMatch5[b][1]; + } else { + At1_r = At1_g = At1_b = 0; + At2_r = At2_g = At2_b = 0; + for (i=0;i<16;++i,cm>>=2) + { + int step = cm&3; + int w1 = w1Tab[step]; + int r = block[i*4+0]; + int g = block[i*4+1]; + int b = block[i*4+2]; + + akku += prods[step]; + At1_r += w1*r; + At1_g += w1*g; + At1_b += w1*b; + At2_r += r; + At2_g += g; + At2_b += b; + } + + At2_r = 3*At2_r - At1_r; + At2_g = 3*At2_g - At1_g; + At2_b = 3*At2_b - At1_b; + + // extract solutions and decide solvability + xx = akku >> 16; + yy = (akku >> 8) & 0xff; + xy = (akku >> 0) & 0xff; + + frb = 3.0f * 31.0f / 255.0f / (xx*yy - xy*xy); + fg = frb * 63.0f / 31.0f; + + // solve. + max16 = stb__sclamp((At1_r*yy - At2_r*xy)*frb+0.5f,0,31) << 11; + max16 |= stb__sclamp((At1_g*yy - At2_g*xy)*fg +0.5f,0,63) << 5; + max16 |= stb__sclamp((At1_b*yy - At2_b*xy)*frb+0.5f,0,31) << 0; + + min16 = stb__sclamp((At2_r*xx - At1_r*xy)*frb+0.5f,0,31) << 11; + min16 |= stb__sclamp((At2_g*xx - At1_g*xy)*fg +0.5f,0,63) << 5; + min16 |= stb__sclamp((At2_b*xx - At1_b*xy)*frb+0.5f,0,31) << 0; + } + + *pmin16 = min16; + *pmax16 = max16; + return oldMin != min16 || oldMax != max16; +} + +// Color block compression +static void stb__CompressColorBlock(unsigned char *dest, unsigned char *block, int mode) +{ + unsigned int mask; + int i; + int dither; + int refinecount; + unsigned short max16, min16; + unsigned char dblock[16*4],color[4*4]; + + dither = mode & STB_DXT_DITHER; + refinecount = (mode & STB_DXT_HIGHQUAL) ? 2 : 1; + + // check if block is constant + for (i=1;i<16;i++) + if (((unsigned int *) block)[i] != ((unsigned int *) block)[0]) + break; + + if(i == 16) + { // constant color + int r = block[0], g = block[1], b = block[2]; + mask = 0xaaaaaaaa; + max16 = (stb__OMatch5[r][0]<<11) | (stb__OMatch6[g][0]<<5) | stb__OMatch5[b][0]; + min16 = (stb__OMatch5[r][1]<<11) | (stb__OMatch6[g][1]<<5) | stb__OMatch5[b][1]; + } else + { + // first step: compute dithered version for PCA if desired + if(dither) + stb__DitherBlock(dblock,block); + + // second step: pca+map along principal axis + stb__OptimizeColorsBlock(dither ? dblock : block,&max16,&min16); + if (max16 != min16) + { + stb__EvalColors(color,max16,min16); + mask = stb__MatchColorsBlock(block,color,dither); + } else + mask = 0; + + // third step: refine (multiple times if requested) + for (i=0;i> 8); + dest[2] = (unsigned char) (min16); + dest[3] = (unsigned char) (min16 >> 8); + dest[4] = (unsigned char) (mask); + dest[5] = (unsigned char) (mask >> 8); + dest[6] = (unsigned char) (mask >> 16); + dest[7] = (unsigned char) (mask >> 24); +} + +// Alpha block compression (this is easy for a change) +static void stb__CompressAlphaBlock(unsigned char *dest,unsigned char *src,int mode) +{ + int i,dist,bias,dist4,dist2,bits,mask; + + // find min/max color + int mn,mx; + + mn = mx = src[3]; + for (i=1;i<16;i++) + { + if (src[i*4+3] < mn) mn = src[i*4+3]; + else if (src[i*4+3] > mx) mx = src[i*4+3]; + } + + // encode them + ((unsigned char *)dest)[0] = mx; + ((unsigned char *)dest)[1] = mn; + dest += 2; + +#ifdef NEW_OPTIMISATIONS + // mono-alpha shortcut + if (mn==mx) + { + *(unsigned short*)dest = 0; + dest += 2; + *(unsigned int*)dest = 0; + return; + } +#endif + + // determine bias and emit color indices + // given the choice of mx/mn, these indices are optimal: + // http://fgiesen.wordpress.com/2009/12/15/dxt5-alpha-block-index-determination/ + dist = mx-mn; + //printf("mn = %i; mx = %i; dist = %i\n", mn, mx, dist); + dist4 = dist*4; + dist2 = dist*2; + bias = (dist < 8) ? (dist - 1) : (dist/2 + 2); + bias -= mn * 7; + bits = 0, mask=0; + + for (i=0;i<16;i++) + { + int a = src[i*4+3]*7 + bias; + int ind,t; + + // select index. this is a "linear scale" lerp factor between 0 (val=min) and 7 (val=max). + t = (a >= dist4) ? -1 : 0; ind = t & 4; a -= dist4 & t; + t = (a >= dist2) ? -1 : 0; ind += t & 2; a -= dist2 & t; + ind += (a >= dist); + + // turn linear scale into DXT index (0/1 are extremal pts) + ind = -ind & 7; + ind ^= (2 > ind); + + // write index + mask |= ind << bits; + if((bits += 3) >= 8) + { + *dest++ = mask; + mask >>= 8; + bits -= 8; + } + } +} + + +static void stb__InitDXT() +{ + int i; + for(i=0;i<32;i++) + stb__Expand5[i] = (i<<3)|(i>>2); + + for(i=0;i<64;i++) + stb__Expand6[i] = (i<<2)|(i>>4); + + for(i=0;i<256+16;i++) + { + int v = i-8 < 0 ? 0 : i-8 > 255 ? 255 : i-8; + stb__QuantRBTab[i] = stb__Expand5[stb__Mul8Bit(v,31)]; + stb__QuantGTab[i] = stb__Expand6[stb__Mul8Bit(v,63)]; + } + + stb__PrepareOptTable(&stb__OMatch5[0][0],stb__Expand5,32); + stb__PrepareOptTable(&stb__OMatch6[0][0],stb__Expand6,64); +} + + +void stb_compress_dxt_block(unsigned char *dest, const unsigned char *src, int alpha, int mode) +{ + static int init=1; + if (init) + { + stb__InitDXT(); + init=0; + } + + if (alpha) + { + stb__CompressAlphaBlock(dest,(unsigned char*) src,mode); + dest += 8; + } + + stb__CompressColorBlock(dest,(unsigned char*) src,mode); +} + +int imin(int x, int y) { return (x < y) ? x : y; } + + + + + +static void extractBlock(const unsigned char *src, int x, int y, + int w, int h, unsigned char *block) +{ + int i, j; + +#ifdef NEW_OPTIMISATIONS + if ((w-x >=4) && (h-y >=4)) + { + // Full Square shortcut + src += x*4; + src += y*w*4; + for (i=0; i < 4; ++i) + { + *(unsigned int*)block = *(unsigned int*) src; block += 4; src += 4; + *(unsigned int*)block = *(unsigned int*) src; block += 4; src += 4; + *(unsigned int*)block = *(unsigned int*) src; block += 4; src += 4; + *(unsigned int*)block = *(unsigned int*) src; block += 4; + src += (w*4) - 12; + } + return; + } +#endif + + int bw = imin(w - x, 4); + int bh = imin(h - y, 4); + int bx, by; + + const int rem[] = + { + 0, 0, 0, 0, + 0, 1, 0, 1, + 0, 1, 2, 0, + 0, 1, 2, 3 + }; + + for(i = 0; i < 4; ++i) + { + by = rem[(bh - 1) * 4 + i] + y; + for(j = 0; j < 4; ++j) + { + bx = rem[(bw - 1) * 4 + j] + x; + block[(i * 4 * 4) + (j * 4) + 0] = + src[(by * (w * 4)) + (bx * 4) + 0]; + block[(i * 4 * 4) + (j * 4) + 1] = + src[(by * (w * 4)) + (bx * 4) + 1]; + block[(i * 4 * 4) + (j * 4) + 2] = + src[(by * (w * 4)) + (bx * 4) + 2]; + block[(i * 4 * 4) + (j * 4) + 3] = + src[(by * (w * 4)) + (bx * 4) + 3]; + } + } +} + + // should be a pretty optimized 0-255 clamper +inline static unsigned char clamp255( int n ) +{ + if( n > 255 ) n = 255; + if( n < 0 ) n = 0; + return n; +} + + +void rgbToYCoCgBlock( unsigned char * dst, const unsigned char * src ) +{ + // Calculate Co and Cg extents + int extents = 0; + int n = 0; + int iY, iCo, iCg; //, r, g, b; + int blockCo[16]; + int blockCg[16]; + int i; + + const unsigned char *px = src; + for(i=0;i extents) extents = -iCo; + if( iCo > extents) extents = iCo; + if(-iCg > extents) extents = -iCg; + if( iCg > extents) extents = iCg; + + blockCo[n] = iCo; + blockCg[n++] = iCg; + + px += 4; + } + + // Co = -510..510 + // Cg = -510..510 + float scaleFactor = 1.0f; + if(extents > 127) + scaleFactor = (float)extents * 4.0f / 510.0f; + + // Convert to quantized scalefactor + unsigned char scaleFactorQuantized = (unsigned char)(ceil((scaleFactor - 1.0f) * 31.0f / 3.0f)); + + // Unquantize + scaleFactor = 1.0f + (float)(scaleFactorQuantized / 31.0f) * 3.0f; + + unsigned char bVal = (unsigned char)((scaleFactorQuantized << 3) | (scaleFactorQuantized >> 2)); + + unsigned char *outPx = dst; + + n = 0; + px = src; + /* + for(i=0;i<16;i++) + { + // Calculate components + iY = ( px[0] + (px[1]<<1) + px[2] + 2 ) / 4; + iCo = ((blockCo[n] / scaleFactor) + 128); + iCg = ((blockCg[n] / scaleFactor) + 128); + + if(iCo < 0) iCo = 0; else if(iCo > 255) iCo = 255; + if(iCg < 0) iCg = 0; else if(iCg > 255) iCg = 255; + if(iY < 0) iY = 0; else if(iY > 255) iY = 255; + + px += 4; + + outPx[0] = (unsigned char)iCo; + outPx[1] = (unsigned char)iCg; + outPx[2] = bVal; + outPx[3] = (unsigned char)iY; + + outPx += 4; + }*/ + for(i=0;i<16;i++) + { + // Calculate components + int r = px[0]; + int g = (px[1] + 1) >> 1; + int b = px[2]; + int tmp = (2 + r + b) >> 2; + + // Co + iCo = clamp255( 128 + ((r - b + 1) >> 1) ); + // Y + iY = clamp255( g + tmp ); + // Cg + iCg = clamp255( 128 + g - tmp ); + + px += 4; + + outPx[0] = (unsigned char)iCo; + outPx[1] = (unsigned char)iCg; + outPx[2] = bVal; + outPx[3] = (unsigned char)iY; + + outPx += 4; + } + +} + + +void rygCompress(unsigned char *dst, unsigned char *src, int w, int h, int isDxt5, int& compressed_size) +{ + + unsigned char block[64]; + int x, y; + + unsigned char* initial_dst = dst; + + for (y = 0; y < h; y += 4) + { + for(x = 0; x < w; x += 4) + { + extractBlock(src, x, y, w, h, block); + stb_compress_dxt_block(dst, block, isDxt5, STB_DXT_NORMAL); + dst += isDxt5 ? 16 : 8; + } + } + + compressed_size = dst - initial_dst; +} + +void rygCompressYCoCg( unsigned char *dst, unsigned char *src, int w, int h ) +{ + unsigned char block[64]; + unsigned char ycocgblock[64]; + int x, y; + + for(y = 0; y < h; y += 4) + { + for(x = 0; x < w; x += 4) + { + extractBlock(src, x, y, w, h, block); + rgbToYCoCgBlock(ycocgblock,block); + stb_compress_dxt_block(dst, ycocgblock, 1, 10); + dst += 16; + } + } + +} + +static void stbgl__compress(unsigned char *p, unsigned char *rgba, int w, int h, int isDxt5) +{ + int i,j,y,y2; + int alpha = isDxt5; + + for (j=0; j < w; j += 4) { + int x=4; + for (i=0; i < h; i += 4) { + unsigned char block[16*4]; + if (i+3 >= w) x = w-i; + for (y=0; y < 4; ++y) { + if (j+y >= h) break; + memcpy(block+y*16, rgba + w*4*(j+y) + i*4, x*4); + } + if (x < 4) { + switch (x) { + case 0: assert(0); + case 1: + for (y2=0; y2 < y; ++y2) { + memcpy(block+y2*16+1*4, block+y2*16+0*4, 4); + memcpy(block+y2*16+2*4, block+y2*16+0*4, 8); + } + break; + case 2: + for (y2=0; y2 < y; ++y2) + memcpy(block+y2*16+2*4, block+y2*16+0*4, 8); + break; + case 3: + for (y2=0; y2 < y; ++y2) + memcpy(block+y2*16+3*4, block+y2*16+1*4, 4); + break; + } + } + y2 = 0; + for(; y<4; ++y,++y2) + memcpy(block+y*16, block+y2*16, 4*4); + stb_compress_dxt_block(p, block, alpha, 10); + p += alpha ? 16 : 8; + } + } + // assert(p <= end); +} + +static inline unsigned char linearize(unsigned char inByte) +{ + float srgbVal = ((float)inByte) / 255.0f; + float linearVal; + + if(srgbVal < 0.04045) + linearVal = srgbVal / 12.92f; + else + linearVal = pow( (srgbVal + 0.055f) / 1.055f, 2.4f); + + return (unsigned char)(floor(sqrt(linearVal)* 255.0 + 0.5)); +} + +void linearize( unsigned char * dst, const unsigned char * src, int n ) +{ + n*=4; + for( int i = 0; i < n; i++ ) + dst[i] = linearize(src[i]); +} + + + +#endif // STB_DXT_IMPLEMENTATION + +#endif // STB_INCLUDE_STB_DXT_H