Update atomics compression algorithm

In general, we want to use this algorithm only with self-contained compression
lists. As such, we've added all of the proper synchronization primitives in
the list object itself. That way, different threads that are working on the
same list will be able to communicate. Ideally, this should eliminate the
number of user-space context switches that happen. Whether or not this is
faster than the other synchronization algorithms that we've tried remains
to be seen...
This commit is contained in:
Pavel Krajcevski 2013-03-09 13:34:10 -05:00
parent abd3961a09
commit 435f935de3
4 changed files with 50 additions and 99 deletions

View File

@ -64,6 +64,7 @@
//-------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------
#include "BC7Config.h" #include "BC7Config.h"
#include "CompressionJob.h"
class BlockStatManager; class BlockStatManager;
@ -124,15 +125,10 @@ namespace BC7C
#endif #endif
#ifdef HAS_ATOMICS #ifdef HAS_ATOMICS
// This is a threadsafe version of the compression function. Once it is called on a certain block of data, it will // This is a threadsafe version of the compression function that is designed to compress a list of
// compress the entire amount of data. However, if the function is called multiple times from multiple threads then they // textures. If this function is called with the same argument from multiple threads, they will work
// will all dispatch to compress the data that they can and the one that finishes the compression resets the function. // together to compress all of the images in the list.
// void CompressAtomic(CompressionJobList &);
// The function should be used as follows:
// for(int i = 0; i < NTHREADS; i++) {
// startThread(function, args);
// join_threads();
void CompressImageBC7Atomic(const unsigned char *inBuf, unsigned char *outBuf, unsigned int width, unsigned int height);
#endif #endif
// Decompress the image given as BC7 data to R8G8B8A8 format. Width and Height are the dimensions of the image in pixels. // Decompress the image given as BC7 data to R8G8B8A8 format. Width and Height are the dimensions of the image in pixels.

View File

@ -91,13 +91,6 @@
#include <cfloat> #include <cfloat>
#include <ctime> #include <ctime>
#ifdef _MSC_VER
# define ALIGN(x) __declspec( align(x) )
#else
# define ALIGN(x) __attribute__((aligned(x)))
#endif
#define ALIGN_SSE ALIGN(16)
// #define USE_PCA_FOR_SHAPE_ESTIMATION // #define USE_PCA_FOR_SHAPE_ESTIMATION
enum EBlockStats { enum EBlockStats {
@ -1424,6 +1417,8 @@ namespace BC7C
static int gQualityLevel = 50; static int gQualityLevel = 50;
void SetQualityLevel(int q) { void SetQualityLevel(int q) {
gQualityLevel = std::max(0, q); gQualityLevel = std::max(0, q);
const int kMaxIters = BC7CompressionMode::kMaxAnnealingIterations;
BC7CompressionMode::MaxAnnealingIterations = std::min(kMaxIters, GetQualityLevel());
} }
int GetQualityLevel() { return gQualityLevel; } int GetQualityLevel() { return gQualityLevel; }
@ -1556,72 +1551,33 @@ namespace BC7C
#endif #endif
// Variables used for synchronization in threadsafe implementation. // Variables used for synchronization in threadsafe implementation.
static ALIGN(32) uint32 _currentBlock = 0; void CompressAtomic(CompressionJobList &cjl) {
static ALIGN(32) uint32 _initialized = 0;
static const unsigned char *_inBuf; uint32 jobIdx;
static unsigned char *_outBuf; while((jobIdx = cjl.m_CurrentJobIndex) < cjl.GetNumJobs()) {
static bool _initializedFlag = false;
void CompressImageBC7Atomic( // !HACK! ... Microsoft has this defined
const unsigned char *inBuf, #undef GetJob
unsigned char *outBuf,
unsigned int width,
unsigned int height
) {
bool myData = false; const CompressionJob *cj = cjl.GetJob(jobIdx);
while(!myData) { const uint32 nBlocks = (cj->height * cj->width) / 16;
// Have we initialized any data?
if(!TestAndSet(&_initialized)) {
// I'm the first one here... initialize MY data...
const int kMaxIters = BC7CompressionMode::kMaxAnnealingIterations;
BC7CompressionMode::MaxAnnealingIterations = std::min(kMaxIters, GetQualityLevel());
_currentBlock = 0;
_inBuf = inBuf;
_outBuf = outBuf;
myData = true;
_initializedFlag = true;
}
// We've initialized data... is it mine?
else if(_inBuf == inBuf && _outBuf == outBuf) {
myData = true;
}
const uint32 nBlocks = (height * width) / 16;
// Make sure that whoever is initializing data is working on it...
while(!_initializedFlag && _currentBlock < nBlocks) {
YieldThread();
}
// Help finish whatever texture we're compressing before we start again on my work... // Help finish whatever texture we're compressing before we start again on my work...
uint32 blockIdx; uint32 blockIdx;
while((blockIdx = FetchAndAdd(&_currentBlock)) < nBlocks) { while((blockIdx = FetchAndAdd(&(cjl.m_CurrentBlockIndex))) < nBlocks) {
unsigned char *out = _outBuf + (16 * blockIdx); unsigned char *out = cj->outBuf + (16 * blockIdx);
const unsigned char *in = _inBuf + (64 * blockIdx); const unsigned char *in = cj->inBuf + (64 * blockIdx);
CompressBC7Block((const uint32 *)in, out); CompressBC7Block((const uint32 *)in, out);
YieldThread(); // Just to give other threads a chance to make some progress
} }
// If we've allocated someone to compress the last block, then reset the initialization... if(TestAndSet(cjl.GetFinishedFlag(jobIdx))) {
if(blockIdx == nBlocks) { cjl.m_CurrentJobIndex++;
_initializedFlag = false; cjl.m_CurrentBlockIndex = 0;
ResetTestAndSet(&_initialized);
}
else if(blockIdx > nBlocks) {
// Wait for last block to finish..
while(_initialized) {
YieldThread();
}
} }
// Wait until this texture finishes.
while(cjl.m_CurrentJobIndex = jobIdx);
} }
} }
#endif // HAS_ATOMICS #endif // HAS_ATOMICS

View File

@ -45,10 +45,10 @@
#define _TEX_COMP_H_ #define _TEX_COMP_H_
#include "CompressedImage.h" #include "CompressedImage.h"
#include "CompressionJob.h"
// Forward declarations // Forward declarations
class ImageFile; class ImageFile;
class CompressedImage;
class BlockStatManager; class BlockStatManager;
struct SCompressionSettings { struct SCompressionSettings {

View File

@ -105,11 +105,6 @@ static CompressionFunc ChooseFuncFromSettings(const SCompressionSettings &s) {
return BC7C::CompressImageBC7SIMD; return BC7C::CompressImageBC7SIMD;
} }
#endif #endif
#ifdef HAS_ATOMICS
if(s.bUseAtomics)
return BC7C::CompressImageBC7Atomic;
#endif
return BC7C::CompressImageBC7; return BC7C::CompressImageBC7;
} }
break; break;
@ -162,38 +157,30 @@ static double CompressImageInSerial(
} }
class AtomicThreadUnit : public TCCallable { class AtomicThreadUnit : public TCCallable {
const unsigned char *const m_InBuf; CompressionJobList &m_CompressionJobList;
unsigned char *m_OutBuf;
const unsigned int m_Height;
const unsigned int m_Width;
TCBarrier *m_Barrier; TCBarrier *m_Barrier;
const unsigned int m_NumCompressions;
CompressionFunc m_CmpFnc; CompressionFunc m_CmpFnc;
public: public:
AtomicThreadUnit( AtomicThreadUnit(
const unsigned char *const inBuf, CompressionJobList &_cjl,
unsigned char *outBuf,
const unsigned int height,
const unsigned int width,
TCBarrier *barrier, TCBarrier *barrier,
const unsigned int nCompressions,
CompressionFunc f CompressionFunc f
) : TCCallable(), ) : TCCallable(),
m_InBuf(inBuf), m_CompressionJobList(_cjl),
m_OutBuf(outBuf),
m_Height(height),
m_Width(width),
m_Barrier(barrier), m_Barrier(barrier),
m_NumCompressions(nCompressions),
m_CmpFnc(f) m_CmpFnc(f)
{ } { }
virtual ~AtomicThreadUnit() { } virtual ~AtomicThreadUnit() { }
virtual void operator()() { virtual void operator()() {
m_Barrier->Wait(); m_Barrier->Wait();
for(uint32 i = 0; i < m_NumCompressions; i++) if(m_CmpFnc == BC7C::Compress) {
(*m_CmpFnc)(m_InBuf, m_OutBuf, m_Width, m_Height); BC7C::CompressAtomic(m_CompressionJobList);
}
else {
assert(!"I don't know what we're compressing...");
}
} }
}; };
@ -205,23 +192,35 @@ static double CompressImageWithAtomics(
) { ) {
CompressionFunc f = ChooseFuncFromSettings(settings); CompressionFunc f = ChooseFuncFromSettings(settings);
// Setup compression list...
const int nTimes = settings.iNumCompressions; const int nTimes = settings.iNumCompressions;
CompressionJobList cjl (nTimes);
for(int i = 0; i < nTimes; i++) {
if(!cjl.AddJob(CompressionJob(imgData, outBuf, height, width))) {
assert(!"Error adding compression job to job list!");
}
}
const int nThreads = settings.iNumThreads; const int nThreads = settings.iNumThreads;
// Allocate resources... // Allocate resources...
TCBarrier barrier (nThreads); TCBarrier barrier (nThreads+1);
TCThread **threads = (TCThread **)malloc(nThreads * sizeof(TCThread *)); TCThread **threads = (TCThread **)malloc(nThreads * sizeof(TCThread *));
AtomicThreadUnit **units = (AtomicThreadUnit **)malloc(nThreads * sizeof(AtomicThreadUnit *)); AtomicThreadUnit **units = (AtomicThreadUnit **)malloc(nThreads * sizeof(AtomicThreadUnit *));
// Launch threads... // Launch threads...
StopWatch sw;
sw.Start();
for(int i = 0; i < nThreads; i++) { for(int i = 0; i < nThreads; i++) {
AtomicThreadUnit *u = new AtomicThreadUnit(imgData, outBuf, height, width, &barrier, nTimes, f); AtomicThreadUnit *u = new AtomicThreadUnit(cjl, &barrier, f);
threads[i] = new TCThread(*u); threads[i] = new TCThread(*u);
units[i] = u; units[i] = u;
} }
// Wait here to make sure that our timer is correct...
barrier.Wait();
StopWatch sw;
sw.Start();
// Wait for threads to finish // Wait for threads to finish
for(int i = 0; i < nThreads; i++) { for(int i = 0; i < nThreads; i++) {
threads[i]->Join(); threads[i]->Join();