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