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