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