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