1 // The MIT License (MIT)
2 //
3 // 	Copyright (c) 2015 Sergey Makeev, Vadim Slyusarev
4 //
5 // 	Permission is hereby granted, free of charge, to any person obtaining a copy
6 // 	of this software and associated documentation files (the "Software"), to deal
7 // 	in the Software without restriction, including without limitation the rights
8 // 	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 // 	copies of the Software, and to permit persons to whom the Software is
10 // 	furnished to do so, subject to the following conditions:
11 //
12 //  The above copyright notice and this permission notice shall be included in
13 // 	all copies or substantial portions of the Software.
14 //
15 // 	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 // 	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 // 	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 // 	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 // 	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 // 	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 // 	THE SOFTWARE.
22 
23 
24 #include <MTConfig.h>
25 
26 
27 #include "Tests.h"
28 #include <UnitTest++.h>
29 #include <MTScheduler.h>
30 #include <MTStaticVector.h>
31 
32 #include "../Profiler/Profiler.h"
33 
34 #include <squish.h>
35 #include <string.h>
36 #include <math.h>
37 
38 
39 
40 namespace EmbeddedImage
41 {
42 	#include "LenaDxt/LenaColor.h"
43 	#include "LenaDxt/HeaderDDS.h"
44 }
45 
46 
CompareImagesPSNR(uint8 * img1,uint8 * img2,uint32 bytesCount,double psnrThreshold)47 bool CompareImagesPSNR(uint8 * img1, uint8 * img2, uint32 bytesCount, double psnrThreshold)
48 {
49 	double mse = 0.0;
50 
51 	for (uint32 i = 0; i < bytesCount; i++)
52 	{
53 		double error = (double)img1[0] - (double)img2[1];
54 		mse += (error * error);
55 	}
56 
57 	mse = mse / (double)bytesCount;
58 
59 	if (mse > 0.0)
60 	{
61 		double psnr = 10.0 * log10(255.0*255.0/mse);
62 		if (psnr < psnrThreshold)
63 		{
64 			return false;
65 		}
66 	}
67 
68 	return true;
69 }
70 
71 
72 
SUITE(DxtTests)73 SUITE(DxtTests)
74 {
75 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
76 	struct CompressDxtBlock
77 	{
78 		MT_DECLARE_TASK(CompressDxtBlock, MT::StackRequirements::STANDARD, MT::TaskPriority::NORMAL, MT::Color::Blue);
79 
80 		MT::ArrayView<uint8> srcPixels;
81 		MT::ArrayView<uint8> dstBlocks;
82 
83 		int srcX;
84 		int srcY;
85 
86 		int stride;
87 		int dstBlockOffset;
88 
89 		CompressDxtBlock(int _srcX, int _srcY, int _stride, const MT::ArrayView<uint8> & _srcPixels, const MT::ArrayView<uint8> & _dstBlocks, int _dstBlockOffset)
90 			: srcPixels(_srcPixels)
91 			, dstBlocks(_dstBlocks)
92 		{
93 				srcX = _srcX;
94 				srcY = _srcY;
95 				stride = _stride;
96 				dstBlockOffset = _dstBlockOffset;
97 		}
98 
99 		CompressDxtBlock(CompressDxtBlock&& other)
100 			: srcPixels(other.srcPixels)
101 			, dstBlocks(other.dstBlocks)
102 			, srcX(other.srcX)
103 			, srcY(other.srcY)
104 			, stride(other.stride)
105 			, dstBlockOffset(other.dstBlockOffset)
106 		{
107 			other.srcX = -1;
108 			other.srcY = -1;
109 			other.stride = -1;
110 			other.dstBlockOffset = -1;
111 		}
112 
113 		~CompressDxtBlock()
114 		{
115 			srcX = -1;
116 			srcY = -1;
117 			stride = -1;
118 			dstBlockOffset = -1;
119 		}
120 
121 		void Do(MT::FiberContext&)
122 		{
123 			// 16 pixels of input
124 			uint32 pixels[4*4];
125 
126 			// copy dxt1 block from image
127 			for (int y = 0; y < 4; y++)
128 			{
129 				for (int x = 0; x < 4; x++)
130 				{
131 					int posX = srcX + x;
132 					int posY = srcY + y;
133 
134 					int index = posY * stride + (posX * 3);
135 
136 					MT_ASSERT(index >= 0 && ((size_t)(index + 2) < MT_ARRAY_SIZE(EmbeddedImage::lenaColor)), "Invalid index");
137 
138 					uint8 r = srcPixels[index + 0];
139 					uint8 g = srcPixels[index + 1];
140 					uint8 b = srcPixels[index + 2];
141 
142 					uint32 color = 0xFF000000 | ((b << 16) | (g << 8) | (r));
143 
144 					pixels[y * 4 + x] = color;
145 				}
146 			}
147 
148 			// compress the 4x4 block using DXT1 compression
149 			squish::Compress( (squish::u8 *)&pixels[0], &dstBlocks[dstBlockOffset], squish::kDxt1 );
150 		}
151 	};
152 
153 
154 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
155 	struct CompressDxt
156 	{
157 		MT_DECLARE_TASK(CompressDxt, MT::StackRequirements::EXTENDED, MT::TaskPriority::NORMAL, MT::Color::Aqua);
158 
159 		uint32 width;
160 		uint32 height;
161 		uint32 stride;
162 
163 		uint32 blkWidth;
164 		uint32 blkHeight;
165 
166 		uint32 passCount;
167 
168 		MT::ArrayView<uint8> srcPixels;
169 		MT::ArrayView<uint8> dxtBlocks;
170 		MT::Atomic32<uint32>* pIsFinished;
171 
172 
173 		CompressDxt(uint32 _width, uint32 _height, uint32 _stride, const MT::ArrayView<uint8> & _srcPixels, MT::Atomic32<uint32>* _pIsFinished = nullptr, uint32 _passCount = 1)
174 			: srcPixels(_srcPixels)
175 			, pIsFinished(_pIsFinished)
176 		{
177 			passCount = _passCount;
178 
179 			width = _width;
180 			height = _height;
181 			stride = _stride;
182 
183 			blkWidth = width >> 2;
184 			blkHeight = height >> 2;
185 
186 			int dxtBlocksTotalSizeInBytes = blkWidth * blkHeight * 8; // 8 bytes = 64 bits per block (dxt1)
187 			dxtBlocks = MT::ArrayView<uint8>( MT::Memory::Alloc( dxtBlocksTotalSizeInBytes ), dxtBlocksTotalSizeInBytes);
188 		}
189 
190 		~CompressDxt()
191 		{
192 			void* pDxtBlocks = dxtBlocks.GetRawData();
193 			if (pDxtBlocks)
194 			{
195 				MT::Memory::Free(pDxtBlocks);
196 			}
197 		}
198 
199 
200 		void Do(MT::FiberContext& context)
201 		{
202 			for(uint32 i = 0; i < passCount; i++)
203 			{
204 				// use StaticVector as subtask container. beware stack overflow!
205 				MT::StaticVector<CompressDxtBlock, 1024> subTasks;
206 
207 				for (uint32 blkY = 0; blkY < blkHeight; blkY++)
208 				{
209 					for (uint32 blkX = 0; blkX < blkWidth; blkX++)
210 					{
211 						uint32 blockIndex = blkY * blkWidth + blkX;
212 						subTasks.PushBack( CompressDxtBlock(blkX * 4, blkY * 4, stride, srcPixels, dxtBlocks, blockIndex * 8) );
213 					}
214 				}
215 
216 				context.RunSubtasksAndYield(MT::TaskGroup::Default(), &subTasks[0], subTasks.Size());
217 			}
218 
219 
220 			if (pIsFinished != nullptr)
221 			{
222 				pIsFinished->Store(1);
223 			}
224 		}
225 	};
226 
227 
228 
229 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
230 	struct DecompressDxtBlock
231 	{
232 		MT_DECLARE_TASK(DecompressDxtBlock, MT::StackRequirements::STANDARD, MT::TaskPriority::NORMAL, MT::Color::Red);
233 
234 		MT::ArrayView<uint8> srcBlocks;
235 		MT::ArrayView<uint8> dstPixels;
236 
237 		int dstX;
238 		int dstY;
239 
240 		int stride;
241 		int srcBlockOffset;
242 
243 		DecompressDxtBlock(int _dstX, int _dstY, int _stride, const MT::ArrayView<uint8> & _dstPixels, const MT::ArrayView<uint8> & _srcBlocks, int _srcBlockOffset)
244 			: srcBlocks(_srcBlocks)
245 			, dstPixels(_dstPixels)
246 		{
247 			dstX = _dstX;
248 			dstY = _dstY;
249 			stride = _stride;
250 			srcBlockOffset = _srcBlockOffset;
251 		}
252 
253 		DecompressDxtBlock(DecompressDxtBlock&& other)
254 			: srcBlocks(other.srcBlocks)
255 			, dstPixels(other.dstPixels)
256 			, dstX(other.dstX)
257 			, dstY(other.dstY)
258 			, stride(other.stride)
259 			, srcBlockOffset(other.srcBlockOffset)
260 		{
261 			other.dstX = -1;
262 			other.dstY = -1;
263 			other.stride = -1;
264 			other.srcBlockOffset = -1;
265 		}
266 
267 		~DecompressDxtBlock()
268 		{
269 			dstX = -1;
270 			dstY = -1;
271 			stride = -1;
272 			srcBlockOffset = -1;
273 		}
274 
275 
276 		void Do(MT::FiberContext&)
277 		{
278 			// 16 pixels of output
279 			uint32 pixels[4*4];
280 
281 			// copy dxt1 block from image
282 			for (int y = 0; y < 4; y++)
283 			{
284 				for (int x = 0; x < 4; x++)
285 				{
286 					squish::Decompress((squish::u8 *)&pixels[0], &srcBlocks[srcBlockOffset], squish::kDxt1);
287 
288 					int posX = dstX + x;
289 					int posY = dstY + y;
290 
291 					int index = posY * stride + (posX * 3);
292 
293 					uint32 pixel = pixels[y * 4 + x];
294 
295 					MT_ASSERT(index >= 0 && ((size_t)(index + 2) < MT_ARRAY_SIZE(EmbeddedImage::lenaColor)), "Invalid index");
296 
297 					dstPixels[index + 0] = (pixel & 0xFF);
298 					dstPixels[index + 1] = (pixel >> 8 & 0xFF);
299 					dstPixels[index + 2] = (pixel >> 16 & 0xFF);
300 				}
301 			}
302 
303 		}
304 	};
305 
306 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
307 	struct DecompressDxt
308 	{
309 		MT_DECLARE_TASK(DecompressDxt, MT::StackRequirements::EXTENDED, MT::TaskPriority::NORMAL, MT::Color::Yellow);
310 
311 		MT::ArrayView<uint8> dxtBlocks;
312 		MT::ArrayView<uint8> decompressedImage;
313 
314 		uint32 blkWidth;
315 		uint32 blkHeight;
316 
317 
318 		DecompressDxt(const MT::ArrayView<uint8> & _dxtBlocks, uint32 dxtBlocksCountWidth, uint32 dxtBlocksCountHeight)
319 			: dxtBlocks(_dxtBlocks)
320 		{
321 			blkWidth = dxtBlocksCountWidth;
322 			blkHeight = dxtBlocksCountHeight;
323 
324 			// dxt1 block = 16 rgb pixels = 48 bytes
325 			uint32 bytesCount = blkWidth * blkHeight * 48;
326 			decompressedImage = MT::ArrayView<uint8>( MT::Memory::Alloc(bytesCount), bytesCount);
327 		}
328 
329 		~DecompressDxt()
330 		{
331 			void* pDxtBlocks = dxtBlocks.GetRawData();
332 			if (pDxtBlocks)
333 			{
334 				MT::Memory::Free(pDxtBlocks);
335 			}
336 
337 			void* pDecompressedImage = decompressedImage.GetRawData();
338 			if (pDecompressedImage)
339 			{
340 				MT::Memory::Free(pDecompressedImage);
341 			}
342 
343 		}
344 
345 		void Do(MT::FiberContext& context)
346 		{
347 			// use StaticVector as subtask container. beware stack overflow!
348 			MT::StaticVector<DecompressDxtBlock, 1024> subTasks;
349 
350 			int stride = blkWidth * 4 * 3;
351 
352 			for (uint32 blkY = 0; blkY < blkHeight; blkY++)
353 			{
354 				for (uint32 blkX = 0; blkX < blkWidth; blkX++)
355 				{
356 					uint32 blockIndex = blkY * blkWidth + blkX;
357 					subTasks.PushBack( DecompressDxtBlock(blkX * 4, blkY * 4, stride, decompressedImage, dxtBlocks, blockIndex * 8) );
358 				}
359 			}
360 
361 			context.RunSubtasksAndYield(MT::TaskGroup::Default(), &subTasks[0], subTasks.Size());
362 		}
363 
364 	};
365 
366 
367 	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
368 	void Wait(MT::TaskScheduler & scheduler)
369 	{
370 		//emulate game loop
371 		for(;;)
372 		{
373 			bool waitDone = scheduler.WaitAll(33);
374 			if (waitDone)
375 			{
376 				break;
377 			}
378 		}
379 	}
380 
381 
382 /*
383 	// dxt compressor Hiload test (for profiling purposes)
384 	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
385 	TEST(HiloadDxtTest)
386 	{
387 		MT::Atomic32<uint32> isFinished1;
388 		MT::Atomic32<uint32> isFinished2;
389 
390 		static_assert(MT_ARRAY_SIZE(EmbeddedImage::lenaColor) == 49152, "Image size is invalid");
391 
392 		int stride = 384;
393 
394 		MT::ArrayView<uint8> srcImage((void*)&EmbeddedImage::lenaColor[0], MT_ARRAY_SIZE(EmbeddedImage::lenaColor));
395 
396 		CompressDxt compressTask1(128, 128, stride, srcImage, &isFinished1);
397 		MT_ASSERT ((compressTask1.width & 3) == 0 && (compressTask1.height & 3) == 0, "Image size must be a multiple of 4");
398 
399 		CompressDxt compressTask2(128, 128, stride, srcImage, &isFinished2);
400 		MT_ASSERT ((compressTask2.width & 3) == 0 && (compressTask2.height & 3) == 0, "Image size must be a multiple of 4");
401 
402 #ifdef MT_INSTRUMENTED_BUILD
403 		MT::TaskScheduler scheduler(0, nullptr, GetProfiler());
404 #else
405 		MT::TaskScheduler scheduler;
406 #endif
407 
408 		int workersCount = (int)scheduler.GetWorkersCount();
409 		printf("Scheduler started, %d workers\n", workersCount);
410 
411 		isFinished1.Store(0);
412 		isFinished2.Store(0);
413 
414 		printf("HiloadDxtTest\n");
415 		scheduler.RunAsync(MT::TaskGroup::Default(), &compressTask1, 1);
416 		scheduler.RunAsync(MT::TaskGroup::Default(), &compressTask2, 1);
417 
418 		for(;;)
419 		{
420 			if (isFinished1.Load() != 0)
421 			{
422 				isFinished1.Store(0);
423 				scheduler.RunAsync(MT::TaskGroup::Default(), &compressTask1, 1);
424 			}
425 
426 			if (isFinished2.Load() != 0)
427 			{
428 				isFinished2.Store(0);
429 				scheduler.RunAsync(MT::TaskGroup::Default(), &compressTask2, 1);
430 			}
431 
432 			MT::Thread::Sleep(1);
433 		}
434 	}
435 */
436 
437 
438 /*
439 	// dxt compressor stress test (for profiling purposes)
440 	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
441 	TEST(DxtStressTest)
442 	{
443 		static_assert(MT_ARRAY_SIZE(EmbeddedImage::lenaColor) == 49152, "Image size is invalid");
444 
445 		int stride = 384;
446 
447 		MT::ArrayView<uint8> srcImage((void*)&EmbeddedImage::lenaColor[0], MT_ARRAY_SIZE(EmbeddedImage::lenaColor));
448 
449 		uint32 passCount = 10;
450 
451 		CompressDxt compressTask(128, 128, stride, srcImage, nullptr, passCount);
452 		MT_ASSERT ((compressTask.width & 3) == 0 && (compressTask.height & 3) == 0, "Image size must be a multiple of 4");
453 
454 #ifdef MT_INSTRUMENTED_BUILD
455 		MT::TaskScheduler scheduler(0, nullptr, GetProfiler());
456 #else
457 		MT::TaskScheduler scheduler;
458 #endif
459 
460 		int workersCount = (int)scheduler.GetWorkersCount();
461 		printf("Scheduler started, %d workers\n", workersCount);
462 
463 		printf("DxtStressTest\n");
464 		scheduler.RunAsync(MT::TaskGroup::Default(), &compressTask, 1);
465 
466 		CHECK(scheduler.WaitAll(10000000));
467 	}
468 */
469 
470 	// dxt compressor complex test
471 	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
472 	TEST(RunComplexDxtTest)
473 	{
474 		static_assert(MT_ARRAY_SIZE(EmbeddedImage::lenaColor) == 49152, "Image size is invalid");
475 
476 		int stride = 384;
477 
478 		MT::ArrayView<uint8> srcImage((void*)&EmbeddedImage::lenaColor[0], MT_ARRAY_SIZE(EmbeddedImage::lenaColor));
479 
480 		CompressDxt compressTask(128, 128, stride, srcImage);
481 		MT_ASSERT ((compressTask.width & 3) == 0 && (compressTask.height & 3) == 0, "Image size must be a multiple of 4");
482 
483 		MT::TaskScheduler scheduler;
484 
485 		int workersCount = (int)scheduler.GetWorkersCount();
486 		printf("Scheduler started, %d workers\n", workersCount);
487 
488 		printf("Compress image\n");
489 		scheduler.RunAsync(MT::TaskGroup::Default(), &compressTask, 1);
490 
491 		Wait(scheduler);
492 
493 		DecompressDxt decompressTask(compressTask.dxtBlocks, compressTask.blkWidth, compressTask.blkHeight);
494 		compressTask.dxtBlocks = MT::ArrayView<uint8>(); //transfer memory ownership to Decompress task
495 
496 		printf("Decompress image\n");
497 		scheduler.RunAsync(MT::TaskGroup::Default(), &decompressTask, 1);
498 
499 		Wait(scheduler);
500 
501 /*
502 		//save compressed image
503 		{
504 			FILE * file = fopen("lena_dxt1.dds", "w+b");
505 			fwrite(&EmbeddedImage::ddsHeader[0], MT_ARRAY_SIZE(EmbeddedImage::ddsHeader), 1, file);
506 			fwrite(decompressTask.dxtBlocks, decompressTask.blkWidth * decompressTask.blkHeight * 8, 1, file);
507 			fclose(file);
508 		}
509 
510 		//save uncompressed image
511 		{
512 			FILE * file = fopen("lena_rgb.raw", "w+b");
513 			fwrite(decompressTask.decompressedImage, decompressTask.blkWidth * decompressTask.blkHeight * 48, 1, file);
514 			fclose(file);
515 		}
516 */
517 
518 		printf("Compare images\n");
519 		bool imagesEqual = CompareImagesPSNR(&srcImage[0], &decompressTask.decompressedImage[0], MT_ARRAY_SIZE(EmbeddedImage::lenaColor), 8.0);
520 		CHECK_EQUAL(true, imagesEqual);
521 
522 /*
523 #ifdef MT_INSTRUMENTED_BUILD
524 		// waiting for profiler attach
525 		printf("Press any key to continue\n");
526 		while(true)
527 		{
528 			if (_kbhit() != 0)
529 			{
530 				break;
531 			}
532 		}
533 #endif
534 */
535 	}
536 
537 
538 }
539