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