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