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 #include <MTStaticVector.h>
27 
28 #include <squish.h>
29 #include <string.h>
30 #include <math.h>
31 
32 
33 /*
34 #ifdef _WIN32
35 
36 #include <conio.h>
37 
38 #else
39 
40 #include <stdio.h>
41 #include <termios.h>
42 #include <unistd.h>
43 #include <fcntl.h>
44 
45 int _kbhit(void)
46 {
47 	struct termios oldt, newt;
48 	int ch;
49 	int oldf;
50 
51 	tcgetattr(STDIN_FILENO, &oldt);
52 	newt = oldt;
53 	newt.c_lflag &= ~(ICANON | ECHO);
54 	tcsetattr(STDIN_FILENO, TCSANOW, &newt);
55 	oldf = fcntl(STDIN_FILENO, F_GETFL, 0);
56 	fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);
57 
58 	ch = getchar();
59 
60 	tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
61 	fcntl(STDIN_FILENO, F_SETFL, oldf);
62 
63 	if(ch != EOF)
64 	{
65 		ungetc(ch, stdin);
66 		return 1;
67 	}
68 
69 	return 0;
70 }
71 #endif
72 */
73 
74 #ifdef MT_INSTRUMENTED_BUILD
75 
76 
77 
78 class Microprofile : public MT::IProfilerEventListener
79 {
80 	virtual void OnThreadCreated(uint32 workerIndex) override
81 	{
82 		MT_UNUSED(workerIndex);
83 	}
84 
85 	virtual void OnThreadStarted(uint32 workerIndex) override
86 	{
87 		MT_UNUSED(workerIndex);
88 	}
89 
90 	virtual void OnThreadStoped(uint32 workerIndex) override
91 	{
92 		MT_UNUSED(workerIndex);
93 	}
94 
95 	virtual void OnThreadIdleBegin(uint32 workerIndex) override
96 	{
97 		MT_UNUSED(workerIndex);
98 	}
99 
100 	virtual void OnThreadIdleEnd(uint32 workerIndex) override
101 	{
102 		MT_UNUSED(workerIndex);
103 	}
104 
105 	virtual void NotifyTaskExecuteStateChanged(MT::Color::Type debugColor, const mt_char* debugID, MT::TaskExecuteState::Type type) override
106 	{
107 		MT_UNUSED(debugColor);
108 		MT_UNUSED(debugID);
109 		MT_UNUSED(type);
110 
111 	}
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::TaskPriority::NORMAL, 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::TaskPriority::NORMAL, 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 
282 			// use StaticVector as subtask container. beware stack overflow!
283 			MT::StaticVector<CompressDxtBlock, 1024> subTasks;
284 
285 			for (uint32 blkY = 0; blkY < blkHeight; blkY++)
286 			{
287 				for (uint32 blkX = 0; blkX < blkWidth; blkX++)
288 				{
289 					uint32 blockIndex = blkY * blkWidth + blkX;
290 					subTasks.PushBack( CompressDxtBlock(blkX * 4, blkY * 4, stride, srcPixels, dxtBlocks, blockIndex * 8) );
291 				}
292 			}
293 
294 			context.RunSubtasksAndYield(MT::TaskGroup::Default(), &subTasks[0], subTasks.Size());
295 
296 			if (pIsFinished != nullptr)
297 			{
298 				pIsFinished->Store(1);
299 			}
300 
301 		}
302 	};
303 
304 
305 
306 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
307 	struct DecompressDxtBlock
308 	{
309 		MT_DECLARE_TASK(DecompressDxtBlock, MT::StackRequirements::STANDARD, MT::TaskPriority::NORMAL, MT::Color::Red);
310 
311 		MT::ArrayView<uint8> srcBlocks;
312 		MT::ArrayView<uint8> dstPixels;
313 
314 		int dstX;
315 		int dstY;
316 
317 		int stride;
318 		int srcBlockOffset;
319 
320 		DecompressDxtBlock(int _dstX, int _dstY, int _stride, const MT::ArrayView<uint8> & _dstPixels, const MT::ArrayView<uint8> & _srcBlocks, int _srcBlockOffset)
321 			: srcBlocks(_srcBlocks)
322 			, dstPixels(_dstPixels)
323 		{
324 			dstX = _dstX;
325 			dstY = _dstY;
326 			stride = _stride;
327 			srcBlockOffset = _srcBlockOffset;
328 		}
329 
330 		DecompressDxtBlock(DecompressDxtBlock&& other)
331 			: srcBlocks(other.srcBlocks)
332 			, dstPixels(other.dstPixels)
333 			, dstX(other.dstX)
334 			, dstY(other.dstY)
335 			, stride(other.stride)
336 			, srcBlockOffset(other.srcBlockOffset)
337 		{
338 			other.dstX = -1;
339 			other.dstY = -1;
340 			other.stride = -1;
341 			other.srcBlockOffset = -1;
342 		}
343 
344 		~DecompressDxtBlock()
345 		{
346 			dstX = -1;
347 			dstY = -1;
348 			stride = -1;
349 			srcBlockOffset = -1;
350 		}
351 
352 
353 		void Do(MT::FiberContext&)
354 		{
355 			// 16 pixels of output
356 			uint32 pixels[4*4];
357 
358 			// copy dxt1 block from image
359 			for (int y = 0; y < 4; y++)
360 			{
361 				for (int x = 0; x < 4; x++)
362 				{
363 					squish::Decompress((squish::u8 *)&pixels[0], &srcBlocks[srcBlockOffset], squish::kDxt1);
364 
365 					int posX = dstX + x;
366 					int posY = dstY + y;
367 
368 					int index = posY * stride + (posX * 3);
369 
370 					uint32 pixel = pixels[y * 4 + x];
371 
372 					MT_ASSERT(index >= 0 && ((size_t)(index + 2) < MT_ARRAY_SIZE(EmbeddedImage::lenaColor)), "Invalid index");
373 
374 					dstPixels[index + 0] = (pixel & 0xFF);
375 					dstPixels[index + 1] = (pixel >> 8 & 0xFF);
376 					dstPixels[index + 2] = (pixel >> 16 & 0xFF);
377 				}
378 			}
379 
380 		}
381 	};
382 
383 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
384 	struct DecompressDxt
385 	{
386 		MT_DECLARE_TASK(DecompressDxt, MT::StackRequirements::EXTENDED, MT::TaskPriority::NORMAL, MT::Color::Yellow);
387 
388 		MT::ArrayView<uint8> dxtBlocks;
389 		MT::ArrayView<uint8> decompressedImage;
390 
391 		uint32 blkWidth;
392 		uint32 blkHeight;
393 
394 
395 		DecompressDxt(const MT::ArrayView<uint8> & _dxtBlocks, uint32 dxtBlocksCountWidth, uint32 dxtBlocksCountHeight)
396 			: dxtBlocks(_dxtBlocks)
397 		{
398 			blkWidth = dxtBlocksCountWidth;
399 			blkHeight = dxtBlocksCountHeight;
400 
401 			// dxt1 block = 16 rgb pixels = 48 bytes
402 			uint32 bytesCount = blkWidth * blkHeight * 48;
403 			decompressedImage = MT::ArrayView<uint8>( MT::Memory::Alloc(bytesCount), bytesCount);
404 		}
405 
406 		~DecompressDxt()
407 		{
408 			void* pDxtBlocks = dxtBlocks.GetRawData();
409 			if (pDxtBlocks)
410 			{
411 				MT::Memory::Free(pDxtBlocks);
412 			}
413 
414 			void* pDecompressedImage = decompressedImage.GetRawData();
415 			if (pDecompressedImage)
416 			{
417 				MT::Memory::Free(pDecompressedImage);
418 			}
419 
420 		}
421 
422 		void Do(MT::FiberContext& context)
423 		{
424 			// use StaticVector as subtask container. beware stack overflow!
425 			MT::StaticVector<DecompressDxtBlock, 1024> subTasks;
426 
427 			int stride = blkWidth * 4 * 3;
428 
429 			for (uint32 blkY = 0; blkY < blkHeight; blkY++)
430 			{
431 				for (uint32 blkX = 0; blkX < blkWidth; blkX++)
432 				{
433 					uint32 blockIndex = blkY * blkWidth + blkX;
434 					subTasks.PushBack( DecompressDxtBlock(blkX * 4, blkY * 4, stride, decompressedImage, dxtBlocks, blockIndex * 8) );
435 				}
436 			}
437 
438 			context.RunSubtasksAndYield(MT::TaskGroup::Default(), &subTasks[0], subTasks.Size());
439 		}
440 
441 	};
442 
443 
444 	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
445 	void Wait(MT::TaskScheduler & scheduler)
446 	{
447 		//emulate game loop
448 		for(;;)
449 		{
450 			bool waitDone = scheduler.WaitAll(33);
451 			if (waitDone)
452 			{
453 				break;
454 			}
455 		}
456 	}
457 
458 
459 
460 /*
461 	// dxt compressor Hiload test (for profiling purposes)
462 	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
463 	TEST(HiloadDxtTest)
464 	{
465 		MT::Atomic32<uint32> isFinished1;
466 		MT::Atomic32<uint32> isFinished2;
467 
468 		static_assert(MT_ARRAY_SIZE(EmbeddedImage::lenaColor) == 49152, "Image size is invalid");
469 
470 		int stride = 384;
471 
472 		MT::ArrayView<uint8> srcImage((void*)&EmbeddedImage::lenaColor[0], MT_ARRAY_SIZE(EmbeddedImage::lenaColor));
473 
474 		CompressDxt compressTask1(128, 128, stride, srcImage, &isFinished1);
475 		MT_ASSERT ((compressTask1.width & 3) == 0 && (compressTask1.height & 3) == 0, "Image size must be a multiple of 4");
476 
477 		CompressDxt compressTask2(128, 128, stride, srcImage, &isFinished2);
478 		MT_ASSERT ((compressTask2.width & 3) == 0 && (compressTask2.height & 3) == 0, "Image size must be a multiple of 4");
479 
480 #ifdef MT_INSTRUMENTED_BUILD
481 		Microprofile profiler;
482 		MT::TaskScheduler scheduler(0, nullptr, &profiler);
483 #else
484 		MT::TaskScheduler scheduler;
485 #endif
486 
487 		int workersCount = (int)scheduler.GetWorkersCount();
488 		printf("Scheduler started, %d workers\n", workersCount);
489 
490 		isFinished1.Store(0);
491 		isFinished2.Store(0);
492 
493 		printf("HiloadDxtTest\n");
494 		scheduler.RunAsync(MT::TaskGroup::Default(), &compressTask1, 1);
495 		scheduler.RunAsync(MT::TaskGroup::Default(), &compressTask2, 1);
496 
497 		for(;;)
498 		{
499 			if (isFinished1.Load() != 0)
500 			{
501 				isFinished1.Store(0);
502 				scheduler.RunAsync(MT::TaskGroup::Default(), &compressTask1, 1);
503 			}
504 
505 			if (isFinished2.Load() != 0)
506 			{
507 				isFinished2.Store(0);
508 				scheduler.RunAsync(MT::TaskGroup::Default(), &compressTask2, 1);
509 			}
510 
511 			MT::Thread::Sleep(1);
512 		}
513 	}
514 */
515 
516 	// dxt compressor complex test
517 	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
518 	TEST(RunComplexDxtTest)
519 	{
520 		static_assert(MT_ARRAY_SIZE(EmbeddedImage::lenaColor) == 49152, "Image size is invalid");
521 
522 		int stride = 384;
523 
524 		MT::ArrayView<uint8> srcImage((void*)&EmbeddedImage::lenaColor[0], MT_ARRAY_SIZE(EmbeddedImage::lenaColor));
525 
526 		CompressDxt compressTask(128, 128, stride, srcImage);
527 		MT_ASSERT ((compressTask.width & 3) == 0 && (compressTask.height & 3) == 0, "Image size must be a multiple of 4");
528 
529 #ifdef MT_INSTRUMENTED_BUILD
530 		Microprofile profiler;
531 		MT::TaskScheduler scheduler(0, nullptr, &profiler);
532 #else
533 		MT::TaskScheduler scheduler;
534 #endif
535 
536 		int workersCount = (int)scheduler.GetWorkersCount();
537 		printf("Scheduler started, %d workers\n", workersCount);
538 
539 		printf("Compress image\n");
540 		scheduler.RunAsync(MT::TaskGroup::Default(), &compressTask, 1);
541 
542 		Wait(scheduler);
543 
544 		DecompressDxt decompressTask(compressTask.dxtBlocks, compressTask.blkWidth, compressTask.blkHeight);
545 		compressTask.dxtBlocks = MT::ArrayView<uint8>(); //transfer memory ownership to Decompress task
546 
547 		printf("Decompress image\n");
548 		scheduler.RunAsync(MT::TaskGroup::Default(), &decompressTask, 1);
549 
550 		Wait(scheduler);
551 
552 /*
553 		//save compressed image
554 		{
555 			FILE * file = fopen("lena_dxt1.dds", "w+b");
556 			fwrite(&EmbeddedImage::ddsHeader[0], MT_ARRAY_SIZE(EmbeddedImage::ddsHeader), 1, file);
557 			fwrite(decompressTask.dxtBlocks, decompressTask.blkWidth * decompressTask.blkHeight * 8, 1, file);
558 			fclose(file);
559 		}
560 
561 		//save uncompressed image
562 		{
563 			FILE * file = fopen("lena_rgb.raw", "w+b");
564 			fwrite(decompressTask.decompressedImage, decompressTask.blkWidth * decompressTask.blkHeight * 48, 1, file);
565 			fclose(file);
566 		}
567 */
568 
569 		printf("Compare images\n");
570 		bool imagesEqual = CompareImagesPSNR(&srcImage[0], &decompressTask.decompressedImage[0], MT_ARRAY_SIZE(EmbeddedImage::lenaColor), 8.0);
571 		CHECK_EQUAL(true, imagesEqual);
572 
573 /*
574 #ifdef MT_INSTRUMENTED_BUILD
575 		// waiting for profiler attach
576 		printf("Press any key to continue\n");
577 		while(true)
578 		{
579 			if (_kbhit() != 0)
580 			{
581 				break;
582 			}
583 		}
584 #endif
585 */
586 	}
587 
588 
589 }
590