diff options
Diffstat (limited to 'indra/llimage')
| -rw-r--r-- | indra/llimage/CMakeLists.txt | 4 | ||||
| -rw-r--r-- | indra/llimage/llimage.cpp | 67 | ||||
| -rw-r--r-- | indra/llimage/llimage.h | 13 | ||||
| -rw-r--r-- | indra/llimage/llimagedxt.cpp | 19 | ||||
| -rw-r--r-- | indra/llimage/llimagej2c.cpp | 5 | ||||
| -rw-r--r-- | indra/llimage/llimagejpeg.cpp | 9 | ||||
| -rw-r--r-- | indra/llimage/llimageworker.cpp | 202 | ||||
| -rw-r--r-- | indra/llimage/llimageworker.h | 95 | ||||
| -rw-r--r-- | indra/llimage/tests/llimageworker_test.cpp | 260 | 
9 files changed, 508 insertions, 166 deletions
diff --git a/indra/llimage/CMakeLists.txt b/indra/llimage/CMakeLists.txt index 0635ddd5f5..22be4078a1 100644 --- a/indra/llimage/CMakeLists.txt +++ b/indra/llimage/CMakeLists.txt @@ -3,6 +3,7 @@  project(llimage)  include(00-Common) +include(LLAddBuildTest)  include(LLCommon)  include(LLImage)  include(LLMath) @@ -59,3 +60,6 @@ target_link_libraries(llimage      ${PNG_LIBRARIES}      ${ZLIB_LIBRARIES}      ) + +# Add tests +#ADD_BUILD_TEST(llimageworker llimage) diff --git a/indra/llimage/llimage.cpp b/indra/llimage/llimage.cpp index 73c23fa8d8..575ad5363d 100644 --- a/indra/llimage/llimage.cpp +++ b/indra/llimage/llimage.cpp @@ -55,13 +55,9 @@ std::string LLImage::sLastErrorMessage;  LLMutex* LLImage::sMutex = NULL;  //static -void LLImage::initClass(LLWorkerThread* workerthread) +void LLImage::initClass()  {  	sMutex = new LLMutex(NULL); -	if (workerthread) -	{ -		LLImageWorker::initImageWorker(workerthread); -	}  	LLImageJ2C::openDSO();  } @@ -69,7 +65,6 @@ void LLImage::initClass(LLWorkerThread* workerthread)  void LLImage::cleanupClass()  {  	LLImageJ2C::closeDSO(); -	LLImageWorker::cleanupImageWorker();  	delete sMutex;  	sMutex = NULL;  } @@ -316,6 +311,21 @@ void LLImageRaw::deleteData()  	LLImageBase::deleteData();  } +void LLImageRaw::setDataAndSize(U8 *data, S32 width, S32 height, S8 components)  +{  +	if(data == getData()) +	{ +		return ; +	} + +	deleteData(); + +	LLImageBase::setSize(width, height, components) ; +	LLImageBase::setDataAndSize(data, width * height * components) ; +	 +	sGlobalRawMemory += getDataSize(); +} +  BOOL LLImageRaw::resize(U16 width, U16 height, S8 components)  {  	if ((getWidth() == width) && (getHeight() == height) && (getComponents() == components)) @@ -816,6 +826,51 @@ void LLImageRaw::copyScaled( LLImageRaw* src )  	}  } +//scale down image by not blending a pixel with its neighbors. +BOOL LLImageRaw::scaleDownWithoutBlending( S32 new_width, S32 new_height) +{ +	LLMemType mt1(mMemType); + +	S8 c = getComponents() ; +	llassert((1 == c) || (3 == c) || (4 == c) ); + +	S32 old_width = getWidth(); +	S32 old_height = getHeight(); +	 +	S32 new_data_size = old_width * new_height * c ; +	llassert_always(new_data_size > 0); + +	F32 ratio_x = (F32)old_width / new_width ; +	F32 ratio_y = (F32)old_height / new_height ; +	if( ratio_x < 1.0f || ratio_y < 1.0f ) +	{ +		return TRUE;  // Nothing to do. +	} +	ratio_x -= 1.0f ; +	ratio_y -= 1.0f ; + +	U8* new_data = new U8[new_data_size] ; +	llassert_always(new_data != NULL) ; + +	U8* old_data = getData() ; +	S32 i, j, k, s, t; +	for(i = 0, s = 0, t = 0 ; i < new_height ; i++) +	{ +		for(j = 0 ; j < new_width ; j++) +		{ +			for(k = 0 ; k < c ; k++) +			{ +				new_data[s++] = old_data[t++] ; +			} +			t += (S32)(ratio_x * c + 0.1f) ; +		} +		t += (S32)(ratio_y * old_width * c + 0.1f) ; +	} + +	setDataAndSize(new_data, new_width, new_height, c) ; +	 +	return TRUE ; +}  BOOL LLImageRaw::scale( S32 new_width, S32 new_height, BOOL scale_image_data )  { diff --git a/indra/llimage/llimage.h b/indra/llimage/llimage.h index 8108553107..686f583886 100644 --- a/indra/llimage/llimage.h +++ b/indra/llimage/llimage.h @@ -50,7 +50,8 @@ const S32 MAX_IMAGE_AREA = MAX_IMAGE_SIZE * MAX_IMAGE_SIZE;  const S32 MAX_IMAGE_COMPONENTS = 8;  const S32 MAX_IMAGE_DATA_SIZE = MAX_IMAGE_AREA * MAX_IMAGE_COMPONENTS; -// Note!  These CANNOT be changed without invalidating the viewer VFS files, I think? +// Note!  These CANNOT be changed without modifying simulator code +// *TODO: change both to 1024 when SIM texture fetching is deprecated  const S32 FIRST_PACKET_SIZE = 600;  const S32 MAX_IMG_PACKET_SIZE = 1000; @@ -61,7 +62,6 @@ const S32 MAX_IMG_PACKET_SIZE = 1000;  class LLImageFormatted;  class LLImageRaw;  class LLColor4U; -class LLWorkerThread;  typedef enum e_image_codec  { @@ -82,7 +82,7 @@ typedef enum e_image_codec  class LLImage  {  public: -	static void initClass(LLWorkerThread* workerthread); +	static void initClass();  	static void cleanupClass();  	static const std::string& getLastError(); @@ -131,7 +131,7 @@ public:  protected:  	// special accessor to allow direct setting of mData and mDataSize by LLImageFormatted -	void setDataAndSize(U8 *data, S32 size) { mData = data; mDataSize = size; }; +	void setDataAndSize(U8 *data, S32 size) { mData = data; mDataSize = size; }  public:  	static void generateMip(const U8 *indata, U8* mipdata, int width, int height, S32 nchannels); @@ -192,6 +192,7 @@ public:  	void contractToPowerOfTwo(S32 max_dim = MAX_IMAGE_SIZE, BOOL scale_image = TRUE);  	void biasedScaleToPowerOfTwo(S32 max_dim = MAX_IMAGE_SIZE);  	BOOL scale( S32 new_width, S32 new_height, BOOL scale_image = TRUE ); +	BOOL scaleDownWithoutBlending( S32 new_width, S32 new_height) ;  	// Fill the buffer with a constant color  	void fill( const LLColor4U& color ); @@ -240,6 +241,8 @@ protected:  	U8	fastFractionalMult(U8 a,U8 b); +	void setDataAndSize(U8 *data, S32 width, S32 height, S8 components) ; +  public:  	static S32 sGlobalRawMemory;  	static S32 sRawImageCount; @@ -310,7 +313,7 @@ protected:  protected:  	S8 mCodec;  	S8 mDecoding; -	S8 mDecoded; +	S8 mDecoded;  // unused, but changing LLImage layout requires recompiling static Mac/Linux libs. 2009-01-30 JC  	S8 mDiscardLevel;  public: diff --git a/indra/llimage/llimagedxt.cpp b/indra/llimage/llimagedxt.cpp index 1ce4517a0d..0aa6840ff6 100644 --- a/indra/llimage/llimagedxt.cpp +++ b/indra/llimage/llimagedxt.cpp @@ -264,6 +264,8 @@ void LLImageDXT::setFormat()  // 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) @@ -274,8 +276,17 @@ BOOL LLImageDXT::decode(LLImageRaw* raw_image, F32 time)  	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); -	U8* data = getData() + getMipOffset(0);  	if ((!getData()) || (data + image_size > getData() + getDataSize()))  	{ @@ -300,10 +311,8 @@ BOOL LLImageDXT::getMipData(LLPointer<LLImageRaw>& raw, S32 discard)  		llerrs << "Request for invalid discard level" << llendl;  	}  	U8* data = getData() + getMipOffset(discard); -	// I'm not sure these are the correct initial values for height and width, -	// but previously they were being used uninitialized. JC -	S32 width = raw->getWidth(); -	S32 height = raw->getHeight(); +	S32 width = 0; +	S32 height = 0;  	calcDiscardWidthHeight(discard, mFileFormat, width, height);  	raw = new LLImageRaw(data, width, height, getComponents());  	return TRUE; diff --git a/indra/llimage/llimagej2c.cpp b/indra/llimage/llimagej2c.cpp index 2352c8edd7..74f08b2d0b 100644 --- a/indra/llimage/llimagej2c.cpp +++ b/indra/llimage/llimagej2c.cpp @@ -283,6 +283,7 @@ BOOL LLImageJ2C::decode(LLImageRaw *raw_imagep, F32 decode_time)  } +// Returns TRUE to mean done, whether successful or not.  BOOL LLImageJ2C::decodeChannels(LLImageRaw *raw_imagep, F32 decode_time, S32 first_channel, S32 max_channel_count )  {  	LLMemType mt1(mMemType); @@ -295,7 +296,7 @@ BOOL LLImageJ2C::decodeChannels(LLImageRaw *raw_imagep, F32 decode_time, S32 fir  	if (!getData() || (getDataSize() < 16))  	{  		setLastError("LLImageJ2C uninitialized"); -		res = FALSE; +		res = TRUE; // done  	}  	else  	{ @@ -348,7 +349,7 @@ BOOL LLImageJ2C::encode(const LLImageRaw *raw_imagep, const char* comment_text,  //static  S32 LLImageJ2C::calcHeaderSizeJ2C()  { -	return 600; //2048; // ??? hack... just needs to be >= actual header size... +	return FIRST_PACKET_SIZE; // Hack. just needs to be >= actual header size...  }  //static diff --git a/indra/llimage/llimagejpeg.cpp b/indra/llimage/llimagejpeg.cpp index fa0dd3ff05..79ea79cc07 100644 --- a/indra/llimage/llimagejpeg.cpp +++ b/indra/llimage/llimagejpeg.cpp @@ -188,6 +188,7 @@ void LLImageJPEG::decodeTermSource (j_decompress_ptr cinfo)  } +// Returns true when done, whether or not decode was successful.  BOOL LLImageJPEG::decode(LLImageRaw* raw_image, F32 decode_time)  {  	llassert_always(raw_image); @@ -198,7 +199,7 @@ BOOL LLImageJPEG::decode(LLImageRaw* raw_image, F32 decode_time)  	if (!getData() || (0 == getDataSize()))  	{  		setLastError("LLImageJPEG trying to decode an image with no data!"); -		return FALSE; +		return TRUE;  // done  	}  	S32 row_stride = 0; @@ -226,7 +227,7 @@ BOOL LLImageJPEG::decode(LLImageRaw* raw_image, F32 decode_time)  	if(setjmp(sSetjmpBuffer))  	{  		jpeg_destroy_decompress(&cinfo); -		return FALSE; +		return TRUE; // done  	}  	try  	{ @@ -320,7 +321,7 @@ BOOL LLImageJPEG::decode(LLImageRaw* raw_image, F32 decode_time)  	catch (int)  	{  		jpeg_destroy_decompress(&cinfo); -		return FALSE; +		return TRUE; // done  	}  	// Check to see whether any corrupt-data warnings occurred @@ -328,7 +329,7 @@ BOOL LLImageJPEG::decode(LLImageRaw* raw_image, F32 decode_time)  	{  		// TODO: extract the warning to find out what went wrong.  		setLastError( "Unable to decode JPEG image."); -		return FALSE; +		return TRUE; // done  	}  	return TRUE; diff --git a/indra/llimage/llimageworker.cpp b/indra/llimage/llimageworker.cpp index 532e996188..86d41515e7 100644 --- a/indra/llimage/llimageworker.cpp +++ b/indra/llimage/llimageworker.cpp @@ -37,152 +37,138 @@  //---------------------------------------------------------------------------- -//static -LLWorkerThread* LLImageWorker::sWorkerThread = NULL; -S32 LLImageWorker::sCount = 0; +// MAIN THREAD +LLImageDecodeThread::LLImageDecodeThread(bool threaded) +	: LLQueuedThread("imagedecode", threaded) +{ +	mCreationMutex = new LLMutex(getAPRPool()); +} -//static -void LLImageWorker::initImageWorker(LLWorkerThread* workerthread) +// MAIN THREAD +// virtual +S32 LLImageDecodeThread::update(U32 max_time_ms)  { -	sWorkerThread = workerthread; +	LLMutexLock lock(mCreationMutex); +	for (creation_list_t::iterator iter = mCreationList.begin(); +		 iter != mCreationList.end(); ++iter) +	{ +		creation_info& info = *iter; +		ImageRequest* req = new ImageRequest(info.handle, info.image, +											 info.priority, info.discard, info.needs_aux, +											 info.responder); +		addRequest(req); +	} +	mCreationList.clear(); +	S32 res = LLQueuedThread::update(max_time_ms); +	return res;  } -//static -void LLImageWorker::cleanupImageWorker() +LLImageDecodeThread::handle_t LLImageDecodeThread::decodeImage(LLImageFormatted* image,  +	U32 priority, S32 discard, BOOL needs_aux, Responder* responder) +{ +	LLMutexLock lock(mCreationMutex); +	handle_t handle = generateHandle(); +	mCreationList.push_back(creation_info(handle, image, priority, discard, needs_aux, responder)); +	return handle; +} + +// Used by unit test only +// Returns the size of the mutex guarded list as an indication of sanity +S32 LLImageDecodeThread::tut_size() +{ +	LLMutexLock lock(mCreationMutex); +	S32 res = mCreationList.size(); +	return res; +} + +LLImageDecodeThread::Responder::~Responder()  {  }  //---------------------------------------------------------------------------- -LLImageWorker::LLImageWorker(LLImageFormatted* image, U32 priority, -							 S32 discard, -							 LLPointer<LLResponder> responder) -	: LLWorkerClass(sWorkerThread, "Image"), +LLImageDecodeThread::ImageRequest::ImageRequest(handle_t handle, LLImageFormatted* image,  +												U32 priority, S32 discard, BOOL needs_aux, +												LLImageDecodeThread::Responder* responder) +	: LLQueuedThread::QueuedRequest(handle, priority, FLAG_AUTO_COMPLETE),  	  mFormattedImage(image), -	  mDecodedType(-1),  	  mDiscardLevel(discard), -	  mPriority(priority), +	  mNeedsAux(needs_aux), +	  mDecodedRaw(FALSE), +	  mDecodedAux(FALSE),  	  mResponder(responder)  { -	++sCount;  } -LLImageWorker::~LLImageWorker() +LLImageDecodeThread::ImageRequest::~ImageRequest()  { -	mDecodedImage = NULL; +	mDecodedImageRaw = NULL; +	mDecodedImageAux = NULL;  	mFormattedImage = NULL; -	--sCount;  }  //---------------------------------------------------------------------------- -//virtual, main thread -void LLImageWorker::startWork(S32 param) -{ -	llassert_always(mDecodedImage.isNull()); -	mDecodedType = -1; -} -bool LLImageWorker::doWork(S32 param) +// Returns true when done, whether or not decode was successful. +bool LLImageDecodeThread::ImageRequest::processRequest()  { -	bool decoded = false; -	if(mDecodedImage.isNull()) +	const F32 decode_time_slice = .1f; +	bool done = true; +	if (!mDecodedRaw && mFormattedImage.notNull())  	{ -		if (!mFormattedImage->updateData()) -		{ -			mDecodedType = -2; // failed -			return true; -		} -		if (mDiscardLevel >= 0) +		// Decode primary channels +		if (mDecodedImageRaw.isNull())  		{ -			mFormattedImage->setDiscardLevel(mDiscardLevel); -		} -		if (!(mFormattedImage->getWidth() * mFormattedImage->getHeight() * mFormattedImage->getComponents())) -		{ -			decoded = true; // failed -		} -		else -		{ -			mDecodedImage = new LLImageRaw(); // allow possibly smaller size set during decoding +			// parse formatted header +			if (!mFormattedImage->updateData()) +			{ +				return true; // done (failed) +			} +			if (!(mFormattedImage->getWidth() * mFormattedImage->getHeight() * mFormattedImage->getComponents())) +			{ +				return true; // done (failed) +			} +			if (mDiscardLevel >= 0) +			{ +				mFormattedImage->setDiscardLevel(mDiscardLevel); +			} +			mDecodedImageRaw = new LLImageRaw(mFormattedImage->getWidth(), +											  mFormattedImage->getHeight(), +											  mFormattedImage->getComponents());  		} +		done = mFormattedImage->decode(mDecodedImageRaw, decode_time_slice); // 1ms +		mDecodedRaw = done;  	} -	if (!decoded) +	if (done && mNeedsAux && !mDecodedAux && mFormattedImage.notNull())  	{ -		if (param == 0) -		{ -			// Decode primary channels -			decoded = mFormattedImage->decode(mDecodedImage, .1f); // 1ms -		} -		else +		// Decode aux channel +		if (!mDecodedImageAux)  		{ -			// Decode aux channel -			decoded = mFormattedImage->decodeChannels(mDecodedImage, .1f, param, param); // 1ms +			mDecodedImageAux = new LLImageRaw(mFormattedImage->getWidth(), +											  mFormattedImage->getHeight(), +											  1);  		} +		done = mFormattedImage->decodeChannels(mDecodedImageAux, decode_time_slice, 4, 4); // 1ms +		mDecodedAux = done;  	} -	if (decoded) -	{ -		// Call the callback immediately; endWork doesn't get called until ckeckWork -		if (mResponder.notNull()) -		{ -			bool success = (!wasAborted() && mDecodedImage.notNull() && mDecodedImage->getDataSize() != 0); -			mResponder->completed(success); -		} -	} -	return decoded; -} -void LLImageWorker::endWork(S32 param, bool aborted) -{ -	if (mDecodedType != -2) -	{ -		mDecodedType = aborted ? -2 : param; -	} +	return done;  } -//---------------------------------------------------------------------------- - - -BOOL LLImageWorker::requestDecodedAuxData(LLPointer<LLImageRaw>& raw, S32 channel, S32 discard) +void LLImageDecodeThread::ImageRequest::finishRequest(bool completed)  { -	// For most codecs, only mDiscardLevel data is available. -	//  (see LLImageDXT for exception) -	if (discard >= 0 && discard != mFormattedImage->getDiscardLevel()) -	{ -		llerrs << "Request for invalid discard level" << llendl; -	} -	checkWork(); -	if (mDecodedType == -2) +	if (mResponder.notNull())  	{ -		return TRUE; // aborted, done -	} -	if (mDecodedType != channel) -	{ -		if (!haveWork()) -		{ -			addWork(channel, mPriority); -		} -		return FALSE; -	} -	else -	{ -		llassert_always(!haveWork()); -		llassert_always(mDecodedType == channel); -		raw = mDecodedImage; // smart pointer acquires ownership of data -		mDecodedImage = NULL; -		return TRUE; +		bool success = completed && mDecodedRaw && (!mNeedsAux || mDecodedAux); +		mResponder->completed(success, mDecodedImageRaw, mDecodedImageAux);  	} +	// Will automatically be deleted  } -BOOL LLImageWorker::requestDecodedData(LLPointer<LLImageRaw>& raw, S32 discard) +// Used by unit test only +// Checks that a responder exists for this instance so that something can happen when completion is reached +bool LLImageDecodeThread::ImageRequest::tut_isOK()  { -	if (mFormattedImage->getCodec() == IMG_CODEC_DXT) -	{ -		// special case -		LLImageDXT* imagedxt = (LLImageDXT*)((LLImageFormatted*)mFormattedImage); -		return imagedxt->getMipData(raw, discard); -	} -	else -	{ -		return requestDecodedAuxData(raw, 0, discard); -	} +	return mResponder.notNull();  } diff --git a/indra/llimage/llimageworker.h b/indra/llimage/llimageworker.h index 0d66695d6e..6a5b86a277 100644 --- a/indra/llimage/llimageworker.h +++ b/indra/llimage/llimageworker.h @@ -37,49 +37,72 @@  #include "llpointer.h"  #include "llworkerthread.h" -class LLImageWorker : public LLWorkerClass +class LLImageDecodeThread : public LLQueuedThread  {  public: -	static void initImageWorker(LLWorkerThread* workerthread); -	static void cleanupImageWorker(); -	 -public: -	static LLWorkerThread* getWorkerThread() { return sWorkerThread; } - -	// LLWorkerThread -public: -	LLImageWorker(LLImageFormatted* image, U32 priority, S32 discard, -				  LLPointer<LLResponder> responder); -	~LLImageWorker(); - -	// called from WORKER THREAD, returns TRUE if done -	/*virtual*/ bool doWork(S32 param); -	 -	BOOL requestDecodedData(LLPointer<LLImageRaw>& raw, S32 discard = -1); -	BOOL requestDecodedAuxData(LLPointer<LLImageRaw>& raw, S32 channel, S32 discard = -1); -	void releaseDecodedData(); -	void cancelDecode(); +	class Responder : public LLThreadSafeRefCount +	{ +	protected: +		virtual ~Responder(); +	public: +		virtual void completed(bool success, LLImageRaw* raw, LLImageRaw* aux) = 0; +	}; -private: -	// called from MAIN THREAD -	/*virtual*/ void startWork(S32 param); // called from addWork() -	/*virtual*/ void endWork(S32 param, bool aborted); // called from doWork() +	class ImageRequest : public LLQueuedThread::QueuedRequest +	{ +	protected: +		virtual ~ImageRequest(); // use deleteRequest() +		 +	public: +		ImageRequest(handle_t handle, LLImageFormatted* image, +					 U32 priority, S32 discard, BOOL needs_aux, +					 LLImageDecodeThread::Responder* responder); -protected: -	LLPointer<LLImageFormatted> mFormattedImage; -	LLPointer<LLImageRaw> mDecodedImage; -	S32 mDecodedType; -	S32 mDiscardLevel; +		/*virtual*/ bool processRequest(); +		/*virtual*/ void finishRequest(bool completed); -private: -	U32 mPriority; -	LLPointer<LLResponder> mResponder; +		// Used by unit tests to check the consitency of the request instance +		bool tut_isOK(); +		 +	private: +		// input +		LLPointer<LLImageFormatted> mFormattedImage; +		S32 mDiscardLevel; +		BOOL mNeedsAux; +		// output +		LLPointer<LLImageRaw> mDecodedImageRaw; +		LLPointer<LLImageRaw> mDecodedImageAux; +		BOOL mDecodedRaw; +		BOOL mDecodedAux; +		LLPointer<LLImageDecodeThread::Responder> mResponder; +	}; -protected: -	static LLWorkerThread* sWorkerThread; -  public: -	static S32 sCount; +	LLImageDecodeThread(bool threaded = true); +	handle_t decodeImage(LLImageFormatted* image, +						 U32 priority, S32 discard, BOOL needs_aux, +						 Responder* responder); +	S32 update(U32 max_time_ms); + +	// Used by unit tests to check the consistency of the thread instance +	S32 tut_size(); +	 +private: +	struct creation_info +	{ +		handle_t handle; +		LLPointer<LLImageFormatted> image; +		U32 priority; +		S32 discard; +		BOOL needs_aux; +		LLPointer<Responder> responder; +		creation_info(handle_t h, LLImageFormatted* i, U32 p, S32 d, BOOL aux, Responder* r) +			: handle(h), image(i), priority(p), discard(d), needs_aux(aux), responder(r) +		{} +	}; +	typedef std::list<creation_info> creation_list_t; +	creation_list_t mCreationList; +	LLMutex* mCreationMutex;  };  #endif diff --git a/indra/llimage/tests/llimageworker_test.cpp b/indra/llimage/tests/llimageworker_test.cpp new file mode 100644 index 0000000000..cc44696a45 --- /dev/null +++ b/indra/llimage/tests/llimageworker_test.cpp @@ -0,0 +1,260 @@ +/**  + * @file llimageworker_test.cpp + * @author Merov Linden + * @date 2009-04-28 + * + * $LicenseInfo:firstyear=2006&license=viewergpl$ + *  + * Copyright (c) 2006-2009, Linden Research, Inc. + *  + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab.  Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + *  + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + *  + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + *  + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +// Precompiled header: almost always required for newview cpp files +#include <list> +#include <map> +#include <algorithm> +// Class to test +#include "../llimageworker.h" +// For timer class +#include "../llcommon/lltimer.h" +// Tut header +#include "../test/lltut.h" + +// ------------------------------------------------------------------------------------------- +// Stubbing: Declarations required to link and run the class being tested +// Notes:  +// * Add here stubbed implementation of the few classes and methods used in the class to be tested +// * Add as little as possible (let the link errors guide you) +// * Do not make any assumption as to how those classes or methods work (i.e. don't copy/paste code) +// * A simulator for a class can be implemented here. Please comment and document thoroughly. + +LLImageBase::LLImageBase() {} +LLImageBase::~LLImageBase() {} +void LLImageBase::dump() { } +void LLImageBase::sanityCheck() { } +void LLImageBase::deleteData() { } +U8* LLImageBase::allocateData(S32 size) { return NULL; } +U8* LLImageBase::reallocateData(S32 size) { return NULL; } + +LLImageRaw::LLImageRaw(U16 width, U16 height, S8 components) { } +LLImageRaw::~LLImageRaw() { } +void LLImageRaw::deleteData() { } +U8* LLImageRaw::allocateData(S32 size) { return NULL; } +U8* LLImageRaw::reallocateData(S32 size) { return NULL; } + +// End Stubbing +// ------------------------------------------------------------------------------------------- + +// ------------------------------------------------------------------------------------------- +// TUT +// ------------------------------------------------------------------------------------------- + +namespace tut +{ +	// Test wrapper declarations + +	// Note: We derive the responder class for 2 reasons: +	// 1. It's a pure virtual class and we can't compile without completed() being implemented +	// 2. We actually need a responder to test that the thread work test completed +	// We implement this making no assumption on what's done in the thread or worker +	// though, just that the responder's completed() method is called in the end. +	// Note on responders: responders are ref counted and *will* be deleted by the request they are  +	// attached to when the queued request is deleted. The recommended way of using them is to  +	// create them when creating a request, put a callback method in completed() and not rely on  +	// anything to survive in the responder object once completed() has been called. Let the request +	// do the deletion and clean up itself. +	class responder_test : public LLImageDecodeThread::Responder +	{ +		public: +			responder_test(bool* res) +			{  +				done = res; +				*done = false; +			} +			virtual void completed(bool success, LLImageRaw* raw, LLImageRaw* aux) +			{ +				*done = true; +			} +		private: +			// This is what can be thought of as the minimal implementation of a responder +			// Done will be switched to true when completed() is called and can be tested +			// outside the responder. A better way of doing this is to store a callback here. +			bool* done; +	}; + +	// Test wrapper declaration : decode thread +	struct imagedecodethread_test +	{ +		// Instance to be tested +		LLImageDecodeThread* mThread; + +		// Constructor and destructor of the test wrapper +		imagedecodethread_test() +		{ +			mThread = NULL; +		} +		~imagedecodethread_test() +		{ +			delete mThread; +		} +	}; + +	// Test wrapper declaration : image worker +	// Note: this class is not meant to be instantiated outside an LLImageDecodeThread instance +	// but it's not a bad idea to get its public API a good shake as part of a thorough unit test set. +	// Some gotcha with the destructor though (see below). +	struct imagerequest_test +	{ +		// Instance to be tested +		LLImageDecodeThread::ImageRequest* mRequest; +		bool done; + +		// Constructor and destructor of the test wrapper +		imagerequest_test() +		{ +			done = false; +			mRequest = new LLImageDecodeThread::ImageRequest(0, 0, +											 LLQueuedThread::PRIORITY_NORMAL, 0, FALSE, +											 new responder_test(&done)); +		} +		~imagerequest_test() +		{ +			// We should delete the object *but*, because its destructor is protected, that cannot be +			// done from outside an LLImageDecodeThread instance... So we leak memory here... It's fine... +			//delete mRequest; +		} +	}; + +	// Tut templating thingamagic: test group, object and test instance +	typedef test_group<imagedecodethread_test> imagedecodethread_t; +	typedef imagedecodethread_t::object imagedecodethread_object_t; +	tut::imagedecodethread_t tut_imagedecodethread("imagedecodethread"); + +	typedef test_group<imagerequest_test> imagerequest_t; +	typedef imagerequest_t::object imagerequest_object_t; +	tut::imagerequest_t tut_imagerequest("imagerequest"); + +	// --------------------------------------------------------------------------------------- +	// Test functions +	// Notes: +	// * Test as many as you possibly can without requiring a full blown simulation of everything +	// * The tests are executed in sequence so the test instance state may change between calls +	// * Remember that you cannot test private methods with tut +	// --------------------------------------------------------------------------------------- + +	// --------------------------------------------------------------------------------------- +	// Test the LLImageDecodeThread interface +	// --------------------------------------------------------------------------------------- +	// +	// Note on Unit Testing Queued Thread Classes +	// +	// Since methods on such a class are called on a separate loop and that we can't insert tut +	// ensure() calls in there, we exercise the class with 2 sets of tests: +	// - 1: Test as a single threaded instance: We declare the class but ask for no thread +	//   to be spawned (easy with LLThreads since there's a boolean argument on the constructor +	//   just for that). We can then unit test each public method like we do on a normal class. +	// - 2: Test as a threaded instance: We let the thread launch and check that its external  +	//   behavior is as expected (i.e. it runs, can accept a work order and processes +	//   it). Typically though there's no guarantee that this exercises all the methods of the +	//   class which is why we also need the previous "non threaded" set of unit tests for +	//   complete coverage. +	// +	// --------------------------------------------------------------------------------------- + +	template<> template<> +	void imagedecodethread_object_t::test<1>() +	{ +		// Test a *non threaded* instance of the class +		mThread = new LLImageDecodeThread(false); +		ensure("LLImageDecodeThread: non threaded constructor failed", mThread != NULL); +		// Test that we start with an empty list right at creation +		ensure("LLImageDecodeThread: non threaded init state incorrect", mThread->tut_size() == 0); +		// Insert something in the queue +		bool done = false; +		LLImageDecodeThread::handle_t decodeHandle = mThread->decodeImage(NULL, LLQueuedThread::PRIORITY_NORMAL, 0, FALSE, new responder_test(&done)); +		// Verifies we got a valid handle +		ensure("LLImageDecodeThread: non threaded decodeImage(), returned handle is null", decodeHandle != 0); +		// Verifies that we do now have something in the queued list +		ensure("LLImageDecodeThread: non threaded decodeImage() insertion in threaded list failed", mThread->tut_size() == 1); +		// Trigger queue handling "manually" (on a threaded instance, this is done on the thread loop) +		S32 res = mThread->update(0); +		// Verifies that we successfully handled the list +		ensure("LLImageDecodeThread: non threaded update() list handling test failed", res == 0); +		// Verifies that the list is now empty +		ensure("LLImageDecodeThread: non threaded update() list emptying test failed", mThread->tut_size() == 0); +	} + +	template<> template<> +	void imagedecodethread_object_t::test<2>() +	{ +		// Test a *threaded* instance of the class +		mThread = new LLImageDecodeThread(true); +		ensure("LLImageDecodeThread: threaded constructor failed", mThread != NULL); +		// Test that we start with an empty list right at creation +		ensure("LLImageDecodeThread: threaded init state incorrect", mThread->tut_size() == 0); +		// Insert something in the queue +		bool done = false; +		LLImageDecodeThread::handle_t decodeHandle = mThread->decodeImage(NULL, LLQueuedThread::PRIORITY_NORMAL, 0, FALSE, new responder_test(&done)); +		// Verifies we get back a valid handle +		ensure("LLImageDecodeThread:  threaded decodeImage(), returned handle is null", decodeHandle != 0); +		// Wait a little so to simulate the main thread doing something on its main loop... +		ms_sleep(500);		// 500 milliseconds +		// Verifies that the responder has *not* been called yet in the meantime +		ensure("LLImageDecodeThread: responder creation failed", done == false); +		// Ask the thread to update: that means tells the queue to check itself and creates work requests +		mThread->update(1); +		// Wait till the thread has time to handle the work order (though it doesn't do much per work order...) +		const U32 INCREMENT_TIME = 500;				// 500 milliseconds +		const U32 MAX_TIME = 20 * INCREMENT_TIME;	// Do the loop 20 times max, i.e. wait 10 seconds but no more +		U32 total_time = 0; +		while ((done == false) && (total_time < MAX_TIME)) +		{ +			ms_sleep(INCREMENT_TIME); +			total_time += INCREMENT_TIME; +		} +		// Verifies that the responder has now been called +		ensure("LLImageDecodeThread: threaded work unit not processed", done == true); +	} + +	// --------------------------------------------------------------------------------------- +	// Test the LLImageDecodeThread::ImageRequest interface +	// --------------------------------------------------------------------------------------- +	 +	template<> template<> +	void imagerequest_object_t::test<1>() +	{ +		// Test that we start with a correct request at creation +		ensure("LLImageDecodeThread::ImageRequest::ImageRequest() constructor test failed", mRequest->tut_isOK()); +		bool res = mRequest->processRequest(); +		// Verifies that we processed the request successfully +		ensure("LLImageDecodeThread::ImageRequest::processRequest() processing request test failed", res == true); +		// Check that we can call the finishing call safely +		try { +			mRequest->finishRequest(false); +		} catch (...) { +			fail("LLImageDecodeThread::ImageRequest::finishRequest() test failed"); +		} +	} +}  | 
