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