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 #include "Tests.h"
24 #include <UnitTest++.h>
25 #include <MTScheduler.h>
26 
27 #include <squish.h>
28 #include <string.h>
29 #include <math.h>
30 
31 
32 /*
33 #ifdef _WIN32
34 
35 #include <conio.h>
36 
37 #else
38 
39 #include <stdio.h>
40 #include <termios.h>
41 #include <unistd.h>
42 #include <fcntl.h>
43 
44 int _kbhit(void)
45 {
46 	struct termios oldt, newt;
47 	int ch;
48 	int oldf;
49 
50 	tcgetattr(STDIN_FILENO, &oldt);
51 	newt = oldt;
52 	newt.c_lflag &= ~(ICANON | ECHO);
53 	tcsetattr(STDIN_FILENO, TCSANOW, &newt);
54 	oldf = fcntl(STDIN_FILENO, F_GETFL, 0);
55 	fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);
56 
57 	ch = getchar();
58 
59 	tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
60 	fcntl(STDIN_FILENO, F_SETFL, oldf);
61 
62 	if(ch != EOF)
63 	{
64 		ungetc(ch, stdin);
65 		return 1;
66 	}
67 
68 	return 0;
69 }
70 #endif
71 */
72 
73 #ifdef MT_INSTRUMENTED_BUILD
74 
75 class Microprofile : public MT::IProfilerEventListener
76 {
77 	void OnThreadCreated(uint32 workerIndex)
78 	{
79 		MT_UNUSED(workerIndex);
80 	}
81 
82 	void OnThreadStarted(uint32 workerIndex)
83 	{
84 		MT_UNUSED(workerIndex);
85 	}
86 
87 	void OnThreadStoped(uint32 workerIndex)
88 	{
89 		MT_UNUSED(workerIndex);
90 	}
91 
92 	void OnThreadIdleBegin(uint32 workerIndex)
93 	{
94 		MT_UNUSED(workerIndex);
95 	}
96 
97 	void OnThreadIdleEnd(uint32 workerIndex)
98 	{
99 		MT_UNUSED(workerIndex);
100 	}
101 
102 	void OnTaskBeginExecute(MT::Color::Type debugColor, const mt_char* debugID)
103 	{
104 		MT_UNUSED(debugColor);
105 		MT_UNUSED(debugID);
106 	}
107 
108 	void OnTaskEndExecute(MT::Color::Type debugColor, const mt_char* debugID)
109 	{
110 		MT_UNUSED(debugColor);
111 		MT_UNUSED(debugID);
112 	}
113 
114 };
115 
116 
117 #endif
118 
119 
120 
121 
122 
123 namespace EmbeddedImage
124 {
125 	#include "LenaDxt/LenaColor.h"
126 	#include "LenaDxt/HeaderDDS.h"
127 }
128 
129 
130 bool CompareImagesPSNR(uint8 * img1, uint8 * img2, uint32 bytesCount, double psnrThreshold)
131 {
132 	double mse = 0.0;
133 
134 	for (uint32 i = 0; i < bytesCount; i++)
135 	{
136 		double error = (double)img1[0] - (double)img2[1];
137 		mse += (error * error);
138 	}
139 
140 	mse = mse / (double)bytesCount;
141 
142 	if (mse > 0.0)
143 	{
144 		double psnr = 10.0 * log10(255.0*255.0/mse);
145 		if (psnr < psnrThreshold)
146 		{
147 			return false;
148 		}
149 	}
150 
151 	return true;
152 }
153 
154 
155 
156 SUITE(DxtTests)
157 {
158 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
159 	struct CompressDxtBlock
160 	{
161 		MT_DECLARE_TASK(CompressDxtBlock, MT::StackRequirements::STANDARD, MT::Color::Blue);
162 
163 		MT::ArrayView<uint8> srcPixels;
164 		MT::ArrayView<uint8> dstBlocks;
165 
166 		int srcX;
167 		int srcY;
168 
169 		int stride;
170 		int dstBlockOffset;
171 
172 		CompressDxtBlock(int _srcX, int _srcY, int _stride, const MT::ArrayView<uint8> & _srcPixels, const MT::ArrayView<uint8> & _dstBlocks, int _dstBlockOffset)
173 			: srcPixels(_srcPixels)
174 			, dstBlocks(_dstBlocks)
175 		{
176 				srcX = _srcX;
177 				srcY = _srcY;
178 				stride = _stride;
179 				dstBlockOffset = _dstBlockOffset;
180 		}
181 
182 		CompressDxtBlock(CompressDxtBlock&& other)
183 			: srcPixels(other.srcPixels)
184 			, dstBlocks(other.dstBlocks)
185 			, srcX(other.srcX)
186 			, srcY(other.srcY)
187 			, stride(other.stride)
188 			, dstBlockOffset(other.dstBlockOffset)
189 		{
190 			other.srcX = -1;
191 			other.srcY = -1;
192 			other.stride = -1;
193 			other.dstBlockOffset = -1;
194 		}
195 
196 		~CompressDxtBlock()
197 		{
198 			srcX = -1;
199 			srcY = -1;
200 			stride = -1;
201 			dstBlockOffset = -1;
202 		}
203 
204 		void Do(MT::FiberContext&)
205 		{
206 			// 16 pixels of input
207 			uint32 pixels[4*4];
208 
209 			// copy dxt1 block from image
210 			for (int y = 0; y < 4; y++)
211 			{
212 				for (int x = 0; x < 4; x++)
213 				{
214 					int posX = srcX + x;
215 					int posY = srcY + y;
216 
217 					int index = posY * stride + (posX * 3);
218 
219 					MT_ASSERT(index >= 0 && ((size_t)(index + 2) < MT_ARRAY_SIZE(EmbeddedImage::lenaColor)), "Invalid index");
220 
221 					uint8 r = srcPixels[index + 0];
222 					uint8 g = srcPixels[index + 1];
223 					uint8 b = srcPixels[index + 2];
224 
225 					uint32 color = 0xFF000000 | ((b << 16) | (g << 8) | (r));
226 
227 					pixels[y * 4 + x] = color;
228 				}
229 			}
230 
231 			// compress the 4x4 block using DXT1 compression
232 			squish::Compress( (squish::u8 *)&pixels[0], &dstBlocks[dstBlockOffset], squish::kDxt1 );
233 		}
234 	};
235 
236 
237 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
238 	struct CompressDxt
239 	{
240 		MT_DECLARE_TASK(CompressDxt, MT::StackRequirements::EXTENDED, MT::Color::Aqua);
241 
242 		uint32 width;
243 		uint32 height;
244 		uint32 stride;
245 
246 		uint32 blkWidth;
247 		uint32 blkHeight;
248 
249 		MT::ArrayView<uint8> srcPixels;
250 		MT::ArrayView<uint8> dxtBlocks;
251 		MT::Atomic32<uint32>* pIsFinished;
252 
253 
254 		CompressDxt(uint32 _width, uint32 _height, uint32 _stride, const MT::ArrayView<uint8> & _srcPixels, MT::Atomic32<uint32>* _pIsFinished = nullptr)
255 			: srcPixels(_srcPixels)
256 			, pIsFinished(_pIsFinished)
257 		{
258 			width = _width;
259 			height = _height;
260 			stride = _stride;
261 
262 			blkWidth = width >> 2;
263 			blkHeight = height >> 2;
264 
265 			int dxtBlocksTotalSizeInBytes = blkWidth * blkHeight * 8; // 8 bytes = 64 bits per block (dxt1)
266 			dxtBlocks = MT::ArrayView<uint8>( MT::Memory::Alloc( dxtBlocksTotalSizeInBytes ), dxtBlocksTotalSizeInBytes);
267 		}
268 
269 		~CompressDxt()
270 		{
271 			void* pDxtBlocks = dxtBlocks.GetRawData();
272 			if (pDxtBlocks)
273 			{
274 				MT::Memory::Free(pDxtBlocks);
275 			}
276 		}
277 
278 
279 		void Do(MT::FiberContext& context)
280 		{
281 			// use stack_array as subtask container. beware stack overflow!
282 			MT::StackArray<CompressDxtBlock, 1024> subTasks;
283 
284 			for (uint32 blkY = 0; blkY < blkHeight; blkY++)
285 			{
286 				for (uint32 blkX = 0; blkX < blkWidth; blkX++)
287 				{
288 					uint32 blockIndex = blkY * blkWidth + blkX;
289 					subTasks.PushBack( CompressDxtBlock(blkX * 4, blkY * 4, stride, srcPixels, dxtBlocks, blockIndex * 8) );
290 				}
291 			}
292 
293 			context.RunSubtasksAndYield(MT::TaskGroup::Default(), &subTasks[0], subTasks.Size());
294 
295 			if (pIsFinished != nullptr)
296 			{
297 				pIsFinished->Store(1);
298 			}
299 		}
300 	};
301 
302 
303 
304 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
305 	struct DecompressDxtBlock
306 	{
307 		MT_DECLARE_TASK(DecompressDxtBlock, MT::StackRequirements::STANDARD, MT::Color::Red);
308 
309 		MT::ArrayView<uint8> srcBlocks;
310 		MT::ArrayView<uint8> dstPixels;
311 
312 		int dstX;
313 		int dstY;
314 
315 		int stride;
316 		int srcBlockOffset;
317 
318 		DecompressDxtBlock(int _dstX, int _dstY, int _stride, const MT::ArrayView<uint8> & _dstPixels, const MT::ArrayView<uint8> & _srcBlocks, int _srcBlockOffset)
319 			: srcBlocks(_srcBlocks)
320 			, dstPixels(_dstPixels)
321 		{
322 			dstX = _dstX;
323 			dstY = _dstY;
324 			stride = _stride;
325 			srcBlockOffset = _srcBlockOffset;
326 		}
327 
328 		DecompressDxtBlock(DecompressDxtBlock&& other)
329 			: srcBlocks(other.srcBlocks)
330 			, dstPixels(other.dstPixels)
331 			, dstX(other.dstX)
332 			, dstY(other.dstY)
333 			, stride(other.stride)
334 			, srcBlockOffset(other.srcBlockOffset)
335 		{
336 			other.dstX = -1;
337 			other.dstY = -1;
338 			other.stride = -1;
339 			other.srcBlockOffset = -1;
340 		}
341 
342 		~DecompressDxtBlock()
343 		{
344 			dstX = -1;
345 			dstY = -1;
346 			stride = -1;
347 			srcBlockOffset = -1;
348 		}
349 
350 
351 		void Do(MT::FiberContext&)
352 		{
353 			// 16 pixels of output
354 			uint32 pixels[4*4];
355 
356 			// copy dxt1 block from image
357 			for (int y = 0; y < 4; y++)
358 			{
359 				for (int x = 0; x < 4; x++)
360 				{
361 					squish::Decompress((squish::u8 *)&pixels[0], &srcBlocks[srcBlockOffset], squish::kDxt1);
362 
363 					int posX = dstX + x;
364 					int posY = dstY + y;
365 
366 					int index = posY * stride + (posX * 3);
367 
368 					uint32 pixel = pixels[y * 4 + x];
369 
370 					MT_ASSERT(index >= 0 && ((size_t)(index + 2) < MT_ARRAY_SIZE(EmbeddedImage::lenaColor)), "Invalid index");
371 
372 					dstPixels[index + 0] = (pixel & 0xFF);
373 					dstPixels[index + 1] = (pixel >> 8 & 0xFF);
374 					dstPixels[index + 2] = (pixel >> 16 & 0xFF);
375 				}
376 			}
377 
378 		}
379 	};
380 
381 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
382 	struct DecompressDxt
383 	{
384 		MT_DECLARE_TASK(DecompressDxt, MT::StackRequirements::EXTENDED, MT::Color::Yellow);
385 
386 		MT::ArrayView<uint8> dxtBlocks;
387 		MT::ArrayView<uint8> decompressedImage;
388 
389 		uint32 blkWidth;
390 		uint32 blkHeight;
391 
392 
393 		DecompressDxt(const MT::ArrayView<uint8> & _dxtBlocks, uint32 dxtBlocksCountWidth, uint32 dxtBlocksCountHeight)
394 			: dxtBlocks(_dxtBlocks)
395 		{
396 			blkWidth = dxtBlocksCountWidth;
397 			blkHeight = dxtBlocksCountHeight;
398 
399 			// dxt1 block = 16 rgb pixels = 48 bytes
400 			uint32 bytesCount = blkWidth * blkHeight * 48;
401 			decompressedImage = MT::ArrayView<uint8>( MT::Memory::Alloc(bytesCount), bytesCount);
402 		}
403 
404 		~DecompressDxt()
405 		{
406 			void* pDxtBlocks = dxtBlocks.GetRawData();
407 			if (pDxtBlocks)
408 			{
409 				MT::Memory::Free(pDxtBlocks);
410 			}
411 
412 			void* pDecompressedImage = decompressedImage.GetRawData();
413 			if (pDecompressedImage)
414 			{
415 				MT::Memory::Free(pDecompressedImage);
416 			}
417 
418 		}
419 
420 		void Do(MT::FiberContext& context)
421 		{
422 			// use stack_array as subtask container. beware stack overflow!
423 			MT::StackArray<DecompressDxtBlock, 1024> subTasks;
424 
425 			int stride = blkWidth * 4 * 3;
426 
427 			for (uint32 blkY = 0; blkY < blkHeight; blkY++)
428 			{
429 				for (uint32 blkX = 0; blkX < blkWidth; blkX++)
430 				{
431 					uint32 blockIndex = blkY * blkWidth + blkX;
432 					subTasks.PushBack( DecompressDxtBlock(blkX * 4, blkY * 4, stride, decompressedImage, dxtBlocks, blockIndex * 8) );
433 				}
434 			}
435 
436 			context.RunSubtasksAndYield(MT::TaskGroup::Default(), &subTasks[0], subTasks.Size());
437 		}
438 
439 	};
440 
441 
442 	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
443 	void Wait(MT::TaskScheduler & scheduler)
444 	{
445 		//emulate game loop
446 		for(;;)
447 		{
448 			bool waitDone = scheduler.WaitAll(33);
449 			if (waitDone)
450 			{
451 				break;
452 			}
453 		}
454 	}
455 
456 /*
457 
458 	// dxt compressor Hiload test (for profiling purposes)
459 	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
460 	TEST(HiloadDxtTest)
461 	{
462 		MT::Atomic32<uint32> isFinished1;
463 		MT::Atomic32<uint32> isFinished2;
464 
465 		static_assert(MT_ARRAY_SIZE(EmbeddedImage::lenaColor) == 49152, "Image size is invalid");
466 
467 		int stride = 384;
468 
469 		MT::ArrayView<uint8> srcImage((void*)&EmbeddedImage::lenaColor[0], MT_ARRAY_SIZE(EmbeddedImage::lenaColor));
470 
471 		CompressDxt compressTask1(128, 128, stride, srcImage, &isFinished1);
472 		MT_ASSERT ((compressTask1.width & 3) == 0 && (compressTask1.height & 3) == 0, "Image size must be a multiple of 4");
473 
474 		CompressDxt compressTask2(128, 128, stride, srcImage, &isFinished2);
475 		MT_ASSERT ((compressTask2.width & 3) == 0 && (compressTask2.height & 3) == 0, "Image size must be a multiple of 4");
476 
477 		MT::TaskScheduler scheduler;
478 
479 		int workersCount = (int)scheduler.GetWorkersCount();
480 		printf("Scheduler started, %d workers\n", workersCount);
481 
482 		isFinished1.Store(0);
483 		isFinished2.Store(0);
484 
485 		printf("HiloadDxtTest\n");
486 		scheduler.RunAsync(MT::TaskGroup::Default(), &compressTask1, 1);
487 		scheduler.RunAsync(MT::TaskGroup::Default(), &compressTask2, 1);
488 
489 		for(;;)
490 		{
491 			if (isFinished1.Load() != 0)
492 			{
493 				isFinished1.Store(0);
494 				scheduler.RunAsync(MT::TaskGroup::Default(), &compressTask1, 1);
495 			}
496 
497 			if (isFinished2.Load() != 0)
498 			{
499 				isFinished2.Store(0);
500 				scheduler.RunAsync(MT::TaskGroup::Default(), &compressTask2, 1);
501 			}
502 
503 			MT::Thread::Sleep(1);
504 		}
505 	}
506 */
507 
508 	// dxt compressor complex test
509 	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
510 	TEST(RunComplexDxtTest)
511 	{
512 		static_assert(MT_ARRAY_SIZE(EmbeddedImage::lenaColor) == 49152, "Image size is invalid");
513 
514 		int stride = 384;
515 
516 		MT::ArrayView<uint8> srcImage((void*)&EmbeddedImage::lenaColor[0], MT_ARRAY_SIZE(EmbeddedImage::lenaColor));
517 
518 		CompressDxt compressTask(128, 128, stride, srcImage);
519 		MT_ASSERT ((compressTask.width & 3) == 0 && (compressTask.height & 3) == 0, "Image size must be a multiple of 4");
520 
521 #ifdef MT_INSTRUMENTED_BUILD
522 		Microprofile profiler;
523 		MT::TaskScheduler scheduler(0, nullptr, &profiler);
524 #else
525 		MT::TaskScheduler scheduler;
526 #endif
527 
528 		int workersCount = (int)scheduler.GetWorkersCount();
529 		printf("Scheduler started, %d workers\n", workersCount);
530 
531 		printf("Compress image\n");
532 		scheduler.RunAsync(MT::TaskGroup::Default(), &compressTask, 1);
533 
534 		Wait(scheduler);
535 
536 		DecompressDxt decompressTask(compressTask.dxtBlocks, compressTask.blkWidth, compressTask.blkHeight);
537 		compressTask.dxtBlocks = MT::ArrayView<uint8>(); //transfer memory ownership to Decompress task
538 
539 		printf("Decompress image\n");
540 		scheduler.RunAsync(MT::TaskGroup::Default(), &decompressTask, 1);
541 
542 		Wait(scheduler);
543 
544 /*
545 		//save compressed image
546 		{
547 			FILE * file = fopen("lena_dxt1.dds", "w+b");
548 			fwrite(&EmbeddedImage::ddsHeader[0], MT_ARRAY_SIZE(EmbeddedImage::ddsHeader), 1, file);
549 			fwrite(decompressTask.dxtBlocks, decompressTask.blkWidth * decompressTask.blkHeight * 8, 1, file);
550 			fclose(file);
551 		}
552 
553 		//save uncompressed image
554 		{
555 			FILE * file = fopen("lena_rgb.raw", "w+b");
556 			fwrite(decompressTask.decompressedImage, decompressTask.blkWidth * decompressTask.blkHeight * 48, 1, file);
557 			fclose(file);
558 		}
559 */
560 
561 		printf("Compare images\n");
562 		bool imagesEqual = CompareImagesPSNR(&srcImage[0], &decompressTask.decompressedImage[0], MT_ARRAY_SIZE(EmbeddedImage::lenaColor), 8.0);
563 		CHECK_EQUAL(true, imagesEqual);
564 
565 /*
566 #ifdef MT_INSTRUMENTED_BUILD
567 		// waiting for profiler attach
568 		printf("Press any key to continue\n");
569 		while(true)
570 		{
571 			if (_kbhit() != 0)
572 			{
573 				break;
574 			}
575 		}
576 #endif
577 */
578 	}
579 
580 
581 }
582