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