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