FasTC/BPTCEncoder/src/RGBAEndpointsSIMD.cpp

472 lines
17 KiB
C++
Executable file

/* FasTC
* Copyright (c) 2012 University of North Carolina at Chapel Hill. All rights reserved.
*
* Permission to use, copy, modify, and distribute this software and its documentation for educational,
* research, and non-profit purposes, without fee, and without a written agreement is hereby granted,
* provided that the above copyright notice, this paragraph, and the following four paragraphs appear
* in all copies.
*
* Permission to incorporate this software into commercial products may be obtained by contacting the
* authors or the Office of Technology Development at the University of North Carolina at Chapel Hill <otd@unc.edu>.
*
* This software program and documentation are copyrighted by the University of North Carolina at Chapel Hill.
* The software program and documentation are supplied "as is," without any accompanying services from the
* University of North Carolina at Chapel Hill or the authors. The University of North Carolina at Chapel Hill
* and the authors do not warrant that the operation of the program will be uninterrupted or error-free. The
* end-user understands that the program was developed for research purposes and is advised not to rely
* exclusively on the program for any reason.
*
* IN NO EVENT SHALL THE UNIVERSITY OF NORTH CAROLINA AT CHAPEL HILL OR THE AUTHORS BE LIABLE TO ANY PARTY FOR
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE
* USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF NORTH CAROLINA AT CHAPEL HILL OR THE
* AUTHORS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* THE UNIVERSITY OF NORTH CAROLINA AT CHAPEL HILL AND THE AUTHORS SPECIFICALLY DISCLAIM ANY WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE AND ANY
* STATUTORY WARRANTY OF NON-INFRINGEMENT. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY
* OF NORTH CAROLINA AT CHAPEL HILL AND THE AUTHORS HAVE NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
* ENHANCEMENTS, OR MODIFICATIONS.
*
* Please send all BUG REPORTS to <pavel@cs.unc.edu>.
*
* The authors may be contacted via:
*
* Pavel Krajcevski
* Dept of Computer Science
* 201 S Columbia St
* Frederick P. Brooks, Jr. Computer Science Bldg
* Chapel Hill, NC 27599-3175
* USA
*
* <http://gamma.cs.unc.edu/FasTC/>
*/
// The original lisence from the code available at the following location:
// http://software.intel.com/en-us/vcsource/samples/fast-texture-compression
//
// This code has been modified significantly from the original.
//--------------------------------------------------------------------------------------
// Copyright 2011 Intel Corporation
// All Rights Reserved
//
// Permission is granted to use, copy, distribute and prepare derivative works of this
// software for any purpose and without fee, provided, that the above copyright notice
// and this statement appear in all copies. Intel makes no representations about the
// suitability of this software for any purpose. THIS SOFTWARE IS PROVIDED "AS IS."
// INTEL SPECIFICALLY DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, AND ALL LIABILITY,
// INCLUDING CONSEQUENTIAL AND OTHER INDIRECT DAMAGES, FOR THE USE OF THIS SOFTWARE,
// INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PROPRIETARY RIGHTS, AND INCLUDING THE
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. Intel does not
// assume any responsibility for any errors which may appear in this software nor any
// responsibility to update it.
//
//--------------------------------------------------------------------------------------
#include "BC7Config.h"
#include "RGBAEndpointsSIMD.h"
#include "BC7Compressor.h"
#include "BC7CompressionModeSIMD.h"
#include <cassert>
#include <cfloat>
#ifndef HAS_SSE_POPCNT
static inline uint32 popcnt32(uint32 x) {
uint32 m1 = 0x55555555;
uint32 m2 = 0x33333333;
uint32 m3 = 0x0f0f0f0f;
x -= (x>>1) & 1;
x = (x&m2) + ((x>>2)&m2);
x = (x+(x>>4))&m3;
x += x>>8;
return (x+(x>>16)) & 0x3f;
}
#endif
///////////////////////////////////////////////////////////////////////////////
//
// RGBAVectorSIMD implementation
//
///////////////////////////////////////////////////////////////////////////////
/* Original scalar implementation:
// If the mask is all the bits, then we can just return the value.
if(mask == 0xFF) {
return val;
}
uint32 prec = CountBitsInMask(mask);
const uint32 step = 1 << (8 - prec);
assert(step-1 == uint8(~mask));
uint32 lval = val & mask;
uint32 hval = lval + step;
if(pBit >= 0) {
prec++;
lval |= !!(pBit) << (8 - prec);
hval |= !!(pBit) << (8 - prec);
}
if(lval > val) {
lval -= step;
hval -= step;
}
lval |= lval >> prec;
hval |= hval >> prec;
if(sad(val, lval) < sad(val, hval))
return lval;
else
return hval;
*/
// !TODO! AVX2 supports an instruction known as vsllv, which shifts a vector
// by the values stored in another vector. I.e. you can do something like this:
//
// __m128i shiftVals = _mm_set_epi32(1, 2, 3, 4);
// __m128i someVector = _mm_set1_epi32(1) ;
// __m128i shifted = _mm_srav_epi32 (someVector, shiftVals);
//
// and the result will be the same as __mm_Set_epi32(1, 4, 8, 16);
//
// This is useful because our color channels may have different precisions
// when we're quantizing them, such as for BC7 modes 4 and 5. Hence, we would
// want to do our quantization as accurately as possible, but currently it would
// be very hard to vectorize.
#ifdef _MSC_VER
#define ALIGN_SSE __declspec ( align(16) )
#else
#define ALIGN_SSE __attribute__((aligned(16)))
#endif
// Constants. There are two ways to specify them: either by using the _mm_set*
// intrinsics, or by defining them as aligned arrays. You want to do the former
// when you use them infrequently, and the latter when you use them multiple times
// in a short time frame (like in an inner loop)
static const __m128 kZero = _mm_set1_ps(0.0f);
static const __m128 kByteMax = _mm_set1_ps(255.0f);
static const __m128 kHalfVector = _mm_set1_ps(0.5f);
static const __m128i kOneVector = _mm_set1_epi32(1);
static const __m128i kZeroVector = _mm_set1_epi32(0);
static const ALIGN_SSE uint32 kThirtyTwoVector[4] = { 32, 32, 32, 32 };
static const __m128i kByteValMask = _mm_set_epi32(0xFF, 0xFF, 0xFF, 0xFF);
static inline __m128i sad(const __m128i &a, const __m128i &b) {
const __m128i maxab = _mm_max_epu8(a, b);
const __m128i minab = _mm_min_epu8(a, b);
return _mm_and_si128( kByteValMask, _mm_subs_epu8( maxab, minab ) );
}
__m128i RGBAVectorSIMD::ToPixel(const __m128i &qmask) const {
// !SPEED! We should figure out a way to get rid of these scalar operations.
#ifdef HAS_SSE_POPCNT
const uint32 prec = _mm_popcnt_u32(((uint32 *)(&qmask))[0]);
#else
const uint32 prec = popcnt32(((uint32 *)(&qmask))[0]);
#endif
assert(r >= 0.0f && r <= 255.0f);
assert(g >= 0.0f && g <= 255.0f);
assert(b >= 0.0f && b <= 255.0f);
assert(a >= 0.0f && a <= 255.0f);
assert(((uint32 *)(&qmask))[3] == 0xFF || ((uint32 *)(&qmask))[3] == ((uint32 *)(&qmask))[0]);
assert(((uint32 *)(&qmask))[2] == ((uint32 *)(&qmask))[1] && ((uint32 *)(&qmask))[0] == ((uint32 *)(&qmask))[1]);
const __m128i val = _mm_cvtps_epi32( _mm_add_ps(kHalfVector, vec) );
const __m128i step = _mm_slli_epi32( kOneVector, 8 - prec );
const __m128i &mask = qmask;
__m128i lval = _mm_and_si128(val, mask);
__m128i hval = _mm_add_epi32(lval, step);
const __m128i lvalShift = _mm_srli_epi32(lval, prec);
const __m128i hvalShift = _mm_srli_epi32(hval, prec);
lval = _mm_or_si128(lval, lvalShift);
hval = _mm_or_si128(hval, hvalShift);
const __m128i lvald = _mm_sub_epi32( val, lval );
const __m128i hvald = _mm_sub_epi32( hval, val );
const __m128i vd = _mm_cmplt_epi32(lvald, hvald);
__m128i ans = _mm_blendv_epi8(hval, lval, vd);
const __m128i chanExact = _mm_cmpeq_epi32(mask, kByteValMask);
ans = _mm_blendv_epi8( ans, val, chanExact );
return ans;
}
__m128i RGBAVectorSIMD::ToPixel(const __m128i &qmask, const int pBit) const {
// !SPEED! We should figure out a way to get rid of these scalar operations.
#ifdef HAS_SSE_POPCNT
const uint32 prec = _mm_popcnt_u32(((uint32 *)(&qmask))[0]);
#else
const uint32 prec = popcnt32(((uint32 *)(&qmask))[0]);
#endif
assert(r >= 0.0f && r <= 255.0f);
assert(g >= 0.0f && g <= 255.0f);
assert(b >= 0.0f && b <= 255.0f);
assert(a >= 0.0f && a <= 255.0f);
assert(((uint32 *)(&qmask))[3] == 0xFF || ((uint32 *)(&qmask))[3] == ((uint32 *)(&qmask))[0]);
assert(((uint32 *)(&qmask))[2] == ((uint32 *)(&qmask))[1] && ((uint32 *)(&qmask))[0] == ((uint32 *)(&qmask))[1]);
const __m128i val = _mm_cvtps_epi32( _mm_add_ps(kHalfVector, vec) );
const __m128i pbit = _mm_set1_epi32(!!pBit);
const __m128i &mask = qmask; // _mm_set_epi32(alphaMask, channelMask, channelMask, channelMask);
const __m128i step = _mm_slli_epi32( kOneVector, 8 - prec );
__m128i lval = _mm_and_si128( val, mask );
__m128i hval = _mm_add_epi32( lval, step );
const __m128i pBitShifted = _mm_slli_epi32(pbit, 7 - prec);
lval = _mm_or_si128(lval, pBitShifted );
hval = _mm_or_si128(hval, pBitShifted);
// These next three lines we make sure that after adding the pbit that val is
// still in between lval and hval. If it isn't, then we subtract a
// step from both. Now, val should be larger than lval and less than
// hval, but certain situations make this not always the case (e.g. val
// is 0, precision is 4 bits, and pbit is 1). Hence, we add back the
// step if it goes below zero, making it equivalent to hval and so it
// doesn't matter which we choose.
{
__m128i cmp = _mm_cmpgt_epi32(lval, val);
cmp = _mm_mullo_epi32(cmp, step);
lval = _mm_add_epi32(lval, cmp);
hval = _mm_add_epi32(hval, cmp);
cmp = _mm_cmplt_epi32(lval, kZeroVector);
cmp = _mm_mullo_epi32(cmp, step);
lval = _mm_sub_epi32(lval, cmp);
}
const __m128i lvalShift = _mm_srli_epi32(lval, prec + 1);
const __m128i hvalShift = _mm_srli_epi32(hval, prec + 1);
lval = _mm_or_si128(lval, lvalShift);
hval = _mm_or_si128(hval, hvalShift);
const __m128i lvald = _mm_sub_epi32( val, lval );
const __m128i hvald = _mm_sub_epi32( hval, val );
const __m128i vd = _mm_cmplt_epi32(lvald, hvald);
__m128i ans = _mm_blendv_epi8(hval, lval, vd);
const __m128i chanExact = _mm_cmpeq_epi32(mask, kByteValMask);
ans = _mm_blendv_epi8( ans, val, chanExact );
return ans;
}
///////////////////////////////////////////////////////////////////////////////
//
// RGBAMatrixSIMD implementation
//
///////////////////////////////////////////////////////////////////////////////
RGBAVectorSIMD RGBAMatrixSIMD::operator *(const RGBAVectorSIMD &p) const {
__m128 xVec = _mm_set1_ps( p.x );
__m128 yVec = _mm_set1_ps( p.y );
__m128 zVec = _mm_set1_ps( p.z );
__m128 wVec = _mm_set1_ps( p.w );
__m128 vec1 = _mm_mul_ps( xVec, col[0] );
__m128 vec2 = _mm_mul_ps( yVec, col[1] );
__m128 vec3 = _mm_mul_ps( zVec, col[2] );
__m128 vec4 = _mm_mul_ps( wVec, col[3] );
return RGBAVectorSIMD( _mm_add_ps( _mm_add_ps( vec1, vec2 ), _mm_add_ps( vec3, vec4 ) ) );
}
///////////////////////////////////////////////////////////////////////////////
//
// Cluster implementation
//
///////////////////////////////////////////////////////////////////////////////
RGBAClusterSIMD::RGBAClusterSIMD(const RGBAClusterSIMD &left, const RGBAClusterSIMD &right) {
assert(!(left.m_PointBitString & right.m_PointBitString));
*this = left;
for(int i = 0; i < right.m_NumPoints; i++) {
const RGBAVectorSIMD &p = right.m_DataPoints[i];
assert(m_NumPoints < kMaxNumDataPoints);
m_Total += p;
m_DataPoints[m_NumPoints++] = p;
m_Min.vec = _mm_min_ps(m_Min.vec, p.vec);
m_Max.vec = _mm_max_ps(m_Max.vec, p.vec);
}
m_PointBitString = left.m_PointBitString | right.m_PointBitString;
m_PrincipalAxisCached = false;
}
void RGBAClusterSIMD::AddPoint(const RGBAVectorSIMD &p, int idx) {
assert(m_NumPoints < kMaxNumDataPoints);
m_Total += p;
m_DataPoints[m_NumPoints++] = p;
m_PointBitString |= 1 << idx;
m_Min.vec = _mm_min_ps(m_Min.vec, p.vec);
m_Max.vec = _mm_max_ps(m_Max.vec, p.vec);
}
float RGBAClusterSIMD::QuantizedError(const RGBAVectorSIMD &p1, const RGBAVectorSIMD &p2, const uint8 nBuckets, const __m128i &bitMask, const int pbits[2], __m128i *indices) const {
// nBuckets should be a power of two.
assert(!(nBuckets & (nBuckets - 1)));
#ifdef HAS_SSE_POPCNT
const uint8 indexPrec = 8-_mm_popcnt_u32(~(nBuckets - 1) & 0xFF);
#else
const uint8 indexPrec = 8-popcnt32(~(nBuckets - 1) & 0xFF);
#endif
assert(indexPrec >= 2 && indexPrec <= 4);
typedef __m128i tInterpPair[2];
typedef tInterpPair tInterpLevel[16];
const tInterpLevel *interpVals = kBC7InterpolationValuesSIMD + (indexPrec - 1);
__m128i qp1, qp2;
if(pbits) {
qp1 = p1.ToPixel(bitMask, pbits[0]);
qp2 = p2.ToPixel(bitMask, pbits[1]);
}
else {
qp1 = p1.ToPixel(bitMask);
qp2 = p2.ToPixel(bitMask);
}
__m128 errorMetricVec = _mm_load_ps( BC7C::GetErrorMetric() );
__m128 totalError = kZero;
for(int i = 0; i < m_NumPoints; i++) {
const __m128i pixel = m_DataPoints[i].ToPixel( kByteValMask );
__m128 minError = _mm_set1_ps(FLT_MAX);
__m128i bestBucket = _mm_set1_epi32(-1);
for(int j = 0; j < nBuckets; j++) {
const __m128i jVec = _mm_set1_epi32(j);
const __m128i interp0 = (*interpVals)[j][0];
const __m128i interp1 = (*interpVals)[j][1];
const __m128i ip0 = _mm_mullo_epi32( qp1, interp0 );
const __m128i ip1 = _mm_mullo_epi32( qp2, interp1 );
const __m128i ip = _mm_add_epi32( *((const __m128i *)kThirtyTwoVector), _mm_add_epi32( ip0, ip1 ) );
const __m128i dist = sad( _mm_and_si128( _mm_srli_epi32( ip, 6 ), kByteValMask ), pixel );
__m128 errorVec = _mm_cvtepi32_ps( dist );
errorVec = _mm_mul_ps( errorVec, errorMetricVec );
errorVec = _mm_mul_ps( errorVec, errorVec );
errorVec = _mm_hadd_ps( errorVec, errorVec );
errorVec = _mm_hadd_ps( errorVec, errorVec );
const __m128 cmp = _mm_cmple_ps( errorVec, minError );
minError = _mm_blendv_ps( minError, errorVec, cmp );
bestBucket = _mm_blendv_epi8( bestBucket, jVec, _mm_castps_si128( cmp ) );
// Conceptually, once the error starts growing, it doesn't stop growing (we're moving
// farther away from the reference point along the line). Hence we can early out here.
// However, quanitzation artifacts mean that this is not ALWAYS the case, so we do suffer
// about 0.01 RMS error.
if(!((uint8 *)(&cmp))[0])
break;
}
totalError = _mm_add_ps(totalError, minError);
if(indices) ((uint32 *)indices)[i] = ((uint32 *)(&bestBucket))[0];
}
return ((float *)(&totalError))[0];
}
///////////////////////////////////////////////////////////////////////////////
//
// Utility function implementation
//
///////////////////////////////////////////////////////////////////////////////
void ClampEndpoints(RGBAVectorSIMD &p1, RGBAVectorSIMD &p2) {
p1.vec = _mm_min_ps( kByteMax, _mm_max_ps( p1.vec, kZero ) );
p2.vec = _mm_min_ps( kByteMax, _mm_max_ps( p2.vec, kZero ) );
}
void GetPrincipalAxis(const RGBAClusterSIMD &c, RGBADirSIMD &axis) {
if(c.GetNumPoints() == 2) {
axis = c.GetPoint(1) - c.GetPoint(0);
return;
}
RGBAVectorSIMD avg = c.GetTotal();
avg /= float(c.GetNumPoints());
// We use these vectors for calculating the covariance matrix...
RGBAVectorSIMD toPts[kMaxNumDataPoints];
RGBAVectorSIMD toPtsMax(-FLT_MAX);
for(int i = 0; i < c.GetNumPoints(); i++) {
toPts[i] = c.GetPoint(i) - avg;
toPtsMax.vec = _mm_max_ps(toPtsMax.vec, toPts[i].vec);
}
RGBAMatrixSIMD covMatrix;
// Compute covariance.
const float fNumPoints = float(c.GetNumPoints());
for(int i = 0; i < kNumColorChannels; i++) {
for(int j = 0; j <= i; j++) {
float sum = 0.0;
for(int k = 0; k < c.GetNumPoints(); k++) {
sum += toPts[k].c[i] * toPts[k].c[j];
}
covMatrix(i, j) = sum / fNumPoints;
covMatrix(j, i) = covMatrix(i, j);
}
}
// !SPEED! Find eigenvectors by using the power method. This is good because the
// matrix is only 4x4, which allows us to use SIMD...
RGBAVectorSIMD b = toPtsMax;
assert(b.Length() > 0);
b /= b.Length();
RGBAVectorSIMD newB = covMatrix * b;
// !HACK! If the principal eigenvector of the covariance matrix
// converges to zero, that means that the points lie equally
// spaced on a sphere in this space. In this (extremely rare)
// situation, just choose a point and use it as the principal
// direction.
const float newBlen = newB.Length();
if(newBlen < 1e-10) {
axis = toPts[0];
return;
}
for(int i = 0; i < 8; i++) {
newB = covMatrix * b;
newB.Normalize();
b = newB;
}
axis = b;
}