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 #ifdef _WIN32 33 34 #include <conio.h> 35 36 #else 37 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 MT::Atomic32<uint32>* pIsFinished; 257 258 259 CompressDxt(uint32 _width, uint32 _height, uint32 _stride, const MT::ArrayView<uint8> & _srcPixels, MT::Atomic32<uint32>* _pIsFinished = nullptr) 260 : srcPixels(_srcPixels) 261 , pIsFinished(_pIsFinished) 262 { 263 width = _width; 264 height = _height; 265 stride = _stride; 266 267 blkWidth = width >> 2; 268 blkHeight = height >> 2; 269 270 int dxtBlocksTotalSizeInBytes = blkWidth * blkHeight * 8; // 8 bytes = 64 bits per block (dxt1) 271 dxtBlocks = MT::ArrayView<uint8>( MT::Memory::Alloc( dxtBlocksTotalSizeInBytes ), dxtBlocksTotalSizeInBytes); 272 } 273 274 ~CompressDxt() 275 { 276 void* pDxtBlocks = dxtBlocks.GetRawData(); 277 if (pDxtBlocks) 278 { 279 MT::Memory::Free(pDxtBlocks); 280 } 281 } 282 283 284 void Do(MT::FiberContext& context) 285 { 286 // use stack_array as subtask container. beware stack overflow! 287 MT::StackArray<CompressDxtBlock, 1024> subTasks; 288 289 for (uint32 blkY = 0; blkY < blkHeight; blkY++) 290 { 291 for (uint32 blkX = 0; blkX < blkWidth; blkX++) 292 { 293 uint32 blockIndex = blkY * blkWidth + blkX; 294 subTasks.PushBack( CompressDxtBlock(blkX * 4, blkY * 4, stride, srcPixels, dxtBlocks, blockIndex * 8) ); 295 } 296 } 297 298 context.RunSubtasksAndYield(MT::TaskGroup::Default(), &subTasks[0], subTasks.Size()); 299 300 if (pIsFinished != nullptr) 301 { 302 pIsFinished->Store(1); 303 } 304 } 305 }; 306 307 308 309 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 310 struct DecompressDxtBlock 311 { 312 MT_DECLARE_TASK(DecompressDxtBlock, MT::StackRequirements::STANDARD, MT::Color::Red); 313 314 MT::ArrayView<uint8> srcBlocks; 315 MT::ArrayView<uint8> dstPixels; 316 317 int dstX; 318 int dstY; 319 320 int stride; 321 int srcBlockOffset; 322 323 DecompressDxtBlock(int _dstX, int _dstY, int _stride, const MT::ArrayView<uint8> & _dstPixels, const MT::ArrayView<uint8> & _srcBlocks, int _srcBlockOffset) 324 : srcBlocks(_srcBlocks) 325 , dstPixels(_dstPixels) 326 { 327 dstX = _dstX; 328 dstY = _dstY; 329 stride = _stride; 330 srcBlockOffset = _srcBlockOffset; 331 } 332 333 DecompressDxtBlock(DecompressDxtBlock&& other) 334 : srcBlocks(other.srcBlocks) 335 , dstPixels(other.dstPixels) 336 , dstX(other.dstX) 337 , dstY(other.dstY) 338 , stride(other.stride) 339 , srcBlockOffset(other.srcBlockOffset) 340 { 341 other.dstX = -1; 342 other.dstY = -1; 343 other.stride = -1; 344 other.srcBlockOffset = -1; 345 } 346 347 ~DecompressDxtBlock() 348 { 349 dstX = -1; 350 dstY = -1; 351 stride = -1; 352 srcBlockOffset = -1; 353 } 354 355 356 void Do(MT::FiberContext&) 357 { 358 // 16 pixels of output 359 uint32 pixels[4*4]; 360 361 // copy dxt1 block from image 362 for (int y = 0; y < 4; y++) 363 { 364 for (int x = 0; x < 4; x++) 365 { 366 squish::Decompress((squish::u8 *)&pixels[0], &srcBlocks[srcBlockOffset], squish::kDxt1); 367 368 int posX = dstX + x; 369 int posY = dstY + y; 370 371 int index = posY * stride + (posX * 3); 372 373 uint32 pixel = pixels[y * 4 + x]; 374 375 MT_ASSERT(index >= 0 && ((size_t)(index + 2) < MT_ARRAY_SIZE(EmbeddedImage::lenaColor)), "Invalid index"); 376 377 dstPixels[index + 0] = (pixel & 0xFF); 378 dstPixels[index + 1] = (pixel >> 8 & 0xFF); 379 dstPixels[index + 2] = (pixel >> 16 & 0xFF); 380 } 381 } 382 383 } 384 }; 385 386 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 387 struct DecompressDxt 388 { 389 MT_DECLARE_TASK(DecompressDxt, MT::StackRequirements::EXTENDED, MT::Color::Yellow); 390 391 MT::ArrayView<uint8> dxtBlocks; 392 MT::ArrayView<uint8> decompressedImage; 393 394 uint32 blkWidth; 395 uint32 blkHeight; 396 397 398 DecompressDxt(const MT::ArrayView<uint8> & _dxtBlocks, uint32 dxtBlocksCountWidth, uint32 dxtBlocksCountHeight) 399 : dxtBlocks(_dxtBlocks) 400 { 401 blkWidth = dxtBlocksCountWidth; 402 blkHeight = dxtBlocksCountHeight; 403 404 // dxt1 block = 16 rgb pixels = 48 bytes 405 uint32 bytesCount = blkWidth * blkHeight * 48; 406 decompressedImage = MT::ArrayView<uint8>( MT::Memory::Alloc(bytesCount), bytesCount); 407 } 408 409 ~DecompressDxt() 410 { 411 void* pDxtBlocks = dxtBlocks.GetRawData(); 412 if (pDxtBlocks) 413 { 414 MT::Memory::Free(pDxtBlocks); 415 } 416 417 void* pDecompressedImage = decompressedImage.GetRawData(); 418 if (pDecompressedImage) 419 { 420 MT::Memory::Free(pDecompressedImage); 421 } 422 423 } 424 425 void Do(MT::FiberContext& context) 426 { 427 // use stack_array as subtask container. beware stack overflow! 428 MT::StackArray<DecompressDxtBlock, 1024> subTasks; 429 430 int stride = blkWidth * 4 * 3; 431 432 for (uint32 blkY = 0; blkY < blkHeight; blkY++) 433 { 434 for (uint32 blkX = 0; blkX < blkWidth; blkX++) 435 { 436 uint32 blockIndex = blkY * blkWidth + blkX; 437 subTasks.PushBack( DecompressDxtBlock(blkX * 4, blkY * 4, stride, decompressedImage, dxtBlocks, blockIndex * 8) ); 438 } 439 } 440 441 context.RunSubtasksAndYield(MT::TaskGroup::Default(), &subTasks[0], subTasks.Size()); 442 } 443 444 }; 445 446 447 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 448 void Wait(MT::TaskScheduler & scheduler) 449 { 450 //emulate game loop 451 for(;;) 452 { 453 bool waitDone = scheduler.WaitAll(33); 454 if (waitDone) 455 { 456 break; 457 } 458 } 459 } 460 461 462 /* 463 // dxt compressor Hiload test (for profiling purposes) 464 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 465 TEST(HiloadDxtTest) 466 { 467 MT::Atomic32<uint32> isFinished1; 468 MT::Atomic32<uint32> isFinished2; 469 470 static_assert(MT_ARRAY_SIZE(EmbeddedImage::lenaColor) == 49152, "Image size is invalid"); 471 472 int stride = 384; 473 474 MT::ArrayView<uint8> srcImage((void*)&EmbeddedImage::lenaColor[0], MT_ARRAY_SIZE(EmbeddedImage::lenaColor)); 475 476 CompressDxt compressTask1(128, 128, stride, srcImage, &isFinished1); 477 MT_ASSERT ((compressTask1.width & 3) == 0 && (compressTask1.height & 3) == 0, "Image size must be a multiple of 4"); 478 479 CompressDxt compressTask2(128, 128, stride, srcImage, &isFinished2); 480 MT_ASSERT ((compressTask2.width & 3) == 0 && (compressTask2.height & 3) == 0, "Image size must be a multiple of 4"); 481 482 MT::TaskScheduler scheduler; 483 484 int workersCount = (int)scheduler.GetWorkersCount(); 485 printf("Scheduler started, %d workers\n", workersCount); 486 487 isFinished1.Store(0); 488 isFinished2.Store(0); 489 490 printf("HiloadDxtTest\n"); 491 scheduler.RunAsync(MT::TaskGroup::Default(), &compressTask1, 1); 492 scheduler.RunAsync(MT::TaskGroup::Default(), &compressTask2, 1); 493 494 for(;;) 495 { 496 if (isFinished1.Load() != 0) 497 { 498 isFinished1.Store(0); 499 scheduler.RunAsync(MT::TaskGroup::Default(), &compressTask1, 1); 500 } 501 502 if (isFinished2.Load() != 0) 503 { 504 isFinished2.Store(0); 505 scheduler.RunAsync(MT::TaskGroup::Default(), &compressTask2, 1); 506 } 507 508 MT::Thread::Sleep(1); 509 } 510 } 511 */ 512 513 // dxt compressor complex test 514 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 515 TEST(RunComplexDxtTest) 516 { 517 static_assert(MT_ARRAY_SIZE(EmbeddedImage::lenaColor) == 49152, "Image size is invalid"); 518 519 int stride = 384; 520 521 MT::ArrayView<uint8> srcImage((void*)&EmbeddedImage::lenaColor[0], MT_ARRAY_SIZE(EmbeddedImage::lenaColor)); 522 523 CompressDxt compressTask(128, 128, stride, srcImage); 524 MT_ASSERT ((compressTask.width & 3) == 0 && (compressTask.height & 3) == 0, "Image size must be a multiple of 4"); 525 526 #ifdef MT_INSTRUMENTED_BUILD 527 Microprofile profiler; 528 MT::TaskScheduler scheduler(0, nullptr, &profiler); 529 #else 530 MT::TaskScheduler scheduler; 531 #endif 532 533 int workersCount = (int)scheduler.GetWorkersCount(); 534 printf("Scheduler started, %d workers\n", workersCount); 535 536 printf("Compress image\n"); 537 scheduler.RunAsync(MT::TaskGroup::Default(), &compressTask, 1); 538 539 Wait(scheduler); 540 541 DecompressDxt decompressTask(compressTask.dxtBlocks, compressTask.blkWidth, compressTask.blkHeight); 542 compressTask.dxtBlocks = MT::ArrayView<uint8>(); //transfer memory ownership to Decompress task 543 544 printf("Decompress image\n"); 545 scheduler.RunAsync(MT::TaskGroup::Default(), &decompressTask, 1); 546 547 Wait(scheduler); 548 549 /* 550 //save compressed image 551 { 552 FILE * file = fopen("lena_dxt1.dds", "w+b"); 553 fwrite(&EmbeddedImage::ddsHeader[0], MT_ARRAY_SIZE(EmbeddedImage::ddsHeader), 1, file); 554 fwrite(decompressTask.dxtBlocks, decompressTask.blkWidth * decompressTask.blkHeight * 8, 1, file); 555 fclose(file); 556 } 557 558 //save uncompressed image 559 { 560 FILE * file = fopen("lena_rgb.raw", "w+b"); 561 fwrite(decompressTask.decompressedImage, decompressTask.blkWidth * decompressTask.blkHeight * 48, 1, file); 562 fclose(file); 563 } 564 */ 565 566 printf("Compare images\n"); 567 bool imagesEqual = CompareImagesPSNR(&srcImage[0], &decompressTask.decompressedImage[0], MT_ARRAY_SIZE(EmbeddedImage::lenaColor), 8.0); 568 CHECK_EQUAL(true, imagesEqual); 569 570 /* 571 #ifdef MT_INSTRUMENTED_BUILD 572 // waiting for profiler attach 573 printf("Press any key to continue\n"); 574 while(true) 575 { 576 if (_kbhit() != 0) 577 { 578 break; 579 } 580 } 581 #endif 582 */ 583 } 584 585 586 } 587