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