TestQcms.cpp (11544B)
1 /* vim:set ts=2 sw=2 sts=2 et: */ 2 /* Any copyright is dedicated to the Public Domain. 3 * http://creativecommons.org/publicdomain/zero/1.0/ 4 */ 5 6 #include "gtest/gtest.h" 7 #include "gtest/MozGTestBench.h" 8 #include "gmock/gmock.h" 9 10 #include "mozilla/UniquePtr.h" 11 #include "mozilla/SSE.h" 12 #include "mozilla/arm.h" 13 #include "qcms.h" 14 #include "qcmsint.h" 15 16 #include <cmath> 17 18 /* SSEv1 is only included in non-Windows or non-x86-64-bit builds. */ 19 #if defined(MOZILLA_MAY_SUPPORT_SSE) && \ 20 (!(defined(_MSC_VER) && defined(_M_AMD64))) 21 # define QCMS_MAY_SUPPORT_SSE 22 #endif 23 24 using namespace mozilla; 25 26 static bool CmpRgbChannel(const uint8_t* aRef, const uint8_t* aTest, 27 size_t aIndex) { 28 return std::abs(static_cast<int8_t>(aRef[aIndex] - aTest[aIndex])) <= 1; 29 } 30 31 template <bool kSwapRB, bool kHasAlpha> 32 static bool CmpRgbBufferImpl(const uint8_t* aRefBuffer, 33 const uint8_t* aTestBuffer, size_t aPixels) { 34 const size_t pixelSize = kHasAlpha ? 4 : 3; 35 if (memcmp(aRefBuffer, aTestBuffer, aPixels * pixelSize) == 0) { 36 return true; 37 } 38 39 const size_t kRIndex = kSwapRB ? 2 : 0; 40 const size_t kGIndex = 1; 41 const size_t kBIndex = kSwapRB ? 0 : 2; 42 const size_t kAIndex = 3; 43 44 size_t remaining = aPixels; 45 const uint8_t* ref = aRefBuffer; 46 const uint8_t* test = aTestBuffer; 47 while (remaining > 0) { 48 if (!CmpRgbChannel(ref, test, kRIndex) || 49 !CmpRgbChannel(ref, test, kGIndex) || 50 !CmpRgbChannel(ref, test, kBIndex) || 51 (kHasAlpha && ref[kAIndex] != test[kAIndex])) { 52 EXPECT_EQ(test[kRIndex], ref[kRIndex]); 53 EXPECT_EQ(test[kGIndex], ref[kGIndex]); 54 EXPECT_EQ(test[kBIndex], ref[kBIndex]); 55 if (kHasAlpha) { 56 EXPECT_EQ(test[kAIndex], ref[kAIndex]); 57 } 58 return false; 59 } 60 61 --remaining; 62 ref += pixelSize; 63 test += pixelSize; 64 } 65 66 return true; 67 } 68 69 template <bool kSwapRB, bool kHasAlpha> 70 static size_t GetRgbInputBufferImpl(UniquePtr<uint8_t[]>& aOutBuffer) { 71 const uint8_t colorSamples[] = {0, 5, 16, 43, 101, 127, 182, 255}; 72 const size_t colorSampleMax = sizeof(colorSamples) / sizeof(uint8_t); 73 const size_t pixelSize = kHasAlpha ? 4 : 3; 74 const size_t pixelCount = colorSampleMax * colorSampleMax * 256 * 3; 75 76 aOutBuffer = MakeUnique<uint8_t[]>(pixelCount * pixelSize); 77 if (!aOutBuffer) { 78 return 0; 79 } 80 81 const size_t kRIndex = kSwapRB ? 2 : 0; 82 const size_t kGIndex = 1; 83 const size_t kBIndex = kSwapRB ? 0 : 2; 84 const size_t kAIndex = 3; 85 86 // Sample every red pixel value with a subset of green and blue. 87 uint8_t* color = aOutBuffer.get(); 88 for (uint16_t r = 0; r < 256; ++r) { 89 for (uint8_t g : colorSamples) { 90 for (uint8_t b : colorSamples) { 91 color[kRIndex] = r; 92 color[kGIndex] = g; 93 color[kBIndex] = b; 94 if (kHasAlpha) { 95 color[kAIndex] = 0x80; 96 } 97 color += pixelSize; 98 } 99 } 100 } 101 102 // Sample every green pixel value with a subset of red and blue. 103 for (uint8_t r : colorSamples) { 104 for (uint16_t g = 0; g < 256; ++g) { 105 for (uint8_t b : colorSamples) { 106 color[kRIndex] = r; 107 color[kGIndex] = g; 108 color[kBIndex] = b; 109 if (kHasAlpha) { 110 color[kAIndex] = 0x80; 111 } 112 color += pixelSize; 113 } 114 } 115 } 116 117 // Sample every blue pixel value with a subset of red and green. 118 for (uint8_t r : colorSamples) { 119 for (uint8_t g : colorSamples) { 120 for (uint16_t b = 0; b < 256; ++b) { 121 color[kRIndex] = r; 122 color[kGIndex] = g; 123 color[kBIndex] = b; 124 if (kHasAlpha) { 125 color[kAIndex] = 0x80; 126 } 127 color += pixelSize; 128 } 129 } 130 } 131 132 return pixelCount; 133 } 134 135 static size_t GetRgbInputBuffer(UniquePtr<uint8_t[]>& aOutBuffer) { 136 return GetRgbInputBufferImpl<false, false>(aOutBuffer); 137 } 138 139 static size_t GetRgbaInputBuffer(UniquePtr<uint8_t[]>& aOutBuffer) { 140 return GetRgbInputBufferImpl<false, true>(aOutBuffer); 141 } 142 143 static size_t GetBgraInputBuffer(UniquePtr<uint8_t[]>& aOutBuffer) { 144 return GetRgbInputBufferImpl<true, true>(aOutBuffer); 145 } 146 147 static bool CmpRgbBuffer(const uint8_t* aRefBuffer, const uint8_t* aTestBuffer, 148 size_t aPixels) { 149 return CmpRgbBufferImpl<false, false>(aRefBuffer, aTestBuffer, aPixels); 150 } 151 152 static bool CmpRgbaBuffer(const uint8_t* aRefBuffer, const uint8_t* aTestBuffer, 153 size_t aPixels) { 154 return CmpRgbBufferImpl<false, true>(aRefBuffer, aTestBuffer, aPixels); 155 } 156 157 static bool CmpBgraBuffer(const uint8_t* aRefBuffer, const uint8_t* aTestBuffer, 158 size_t aPixels) { 159 return CmpRgbBufferImpl<true, true>(aRefBuffer, aTestBuffer, aPixels); 160 } 161 162 static void ClearRgbBuffer(uint8_t* aBuffer, size_t aPixels) { 163 if (aBuffer) { 164 memset(aBuffer, 0, aPixels * 3); 165 } 166 } 167 168 static void ClearRgbaBuffer(uint8_t* aBuffer, size_t aPixels) { 169 if (aBuffer) { 170 memset(aBuffer, 0, aPixels * 4); 171 } 172 } 173 174 static UniquePtr<uint8_t[]> GetRgbOutputBuffer(size_t aPixels) { 175 UniquePtr<uint8_t[]> buffer = MakeUnique<uint8_t[]>(aPixels * 3); 176 ClearRgbBuffer(buffer.get(), aPixels); 177 return buffer; 178 } 179 180 static UniquePtr<uint8_t[]> GetRgbaOutputBuffer(size_t aPixels) { 181 UniquePtr<uint8_t[]> buffer = MakeUnique<uint8_t[]>(aPixels * 4); 182 ClearRgbaBuffer(buffer.get(), aPixels); 183 return buffer; 184 } 185 186 class GfxQcms_ProfilePairBase : public ::testing::Test { 187 protected: 188 GfxQcms_ProfilePairBase() 189 : mInProfile(nullptr), 190 mOutProfile(nullptr), 191 mTransform(nullptr), 192 mPixels(0), 193 mStorageType(QCMS_DATA_RGB_8), 194 mPrecache(false) {} 195 196 void SetUp() override { 197 // XXX: This means that we can't have qcms v2 unit test 198 // without changing the qcms API. 199 qcms_enable_iccv4(); 200 } 201 202 void TearDown() override { 203 if (mInProfile) { 204 qcms_profile_release(mInProfile); 205 } 206 if (mOutProfile) { 207 qcms_profile_release(mOutProfile); 208 } 209 if (mTransform) { 210 qcms_transform_release(mTransform); 211 } 212 } 213 214 bool SetTransform(qcms_transform* aTransform) { 215 if (mTransform) { 216 qcms_transform_release(mTransform); 217 } 218 mTransform = aTransform; 219 return !!mTransform; 220 } 221 222 bool SetTransform(qcms_data_type aType) { 223 return SetTransform(qcms_transform_create(mInProfile, aType, mOutProfile, 224 aType, QCMS_INTENT_DEFAULT)); 225 } 226 227 bool SetBuffers(qcms_data_type aType) { 228 switch (aType) { 229 case QCMS_DATA_RGB_8: 230 mPixels = GetRgbInputBuffer(mInput); 231 mRef = GetRgbOutputBuffer(mPixels); 232 mOutput = GetRgbOutputBuffer(mPixels); 233 break; 234 case QCMS_DATA_RGBA_8: 235 mPixels = GetRgbaInputBuffer(mInput); 236 mRef = GetRgbaOutputBuffer(mPixels); 237 mOutput = GetRgbaOutputBuffer(mPixels); 238 break; 239 case QCMS_DATA_BGRA_8: 240 mPixels = GetBgraInputBuffer(mInput); 241 mRef = GetRgbaOutputBuffer(mPixels); 242 mOutput = GetRgbaOutputBuffer(mPixels); 243 break; 244 default: 245 MOZ_ASSERT_UNREACHABLE("Unknown type!"); 246 break; 247 } 248 249 mStorageType = aType; 250 return mInput && mOutput && mRef && mPixels > 0u; 251 } 252 253 void ClearOutputBuffer() { 254 switch (mStorageType) { 255 case QCMS_DATA_RGB_8: 256 ClearRgbBuffer(mOutput.get(), mPixels); 257 break; 258 case QCMS_DATA_RGBA_8: 259 case QCMS_DATA_BGRA_8: 260 ClearRgbaBuffer(mOutput.get(), mPixels); 261 break; 262 default: 263 MOZ_ASSERT_UNREACHABLE("Unknown type!"); 264 break; 265 } 266 } 267 268 void ProduceRef(transform_fn_t aFn) { 269 aFn(mTransform, mInput.get(), mRef.get(), mPixels); 270 } 271 272 void CopyInputToRef() { 273 size_t pixelSize = 0; 274 switch (mStorageType) { 275 case QCMS_DATA_RGB_8: 276 pixelSize = 3; 277 break; 278 case QCMS_DATA_RGBA_8: 279 case QCMS_DATA_BGRA_8: 280 pixelSize = 4; 281 break; 282 default: 283 MOZ_ASSERT_UNREACHABLE("Unknown type!"); 284 break; 285 } 286 287 memcpy(mRef.get(), mInput.get(), mPixels * pixelSize); 288 } 289 290 void ProduceOutput(transform_fn_t aFn) { 291 ClearOutputBuffer(); 292 aFn(mTransform, mInput.get(), mOutput.get(), mPixels); 293 } 294 295 bool VerifyOutput(const UniquePtr<uint8_t[]>& aBuf) { 296 switch (mStorageType) { 297 case QCMS_DATA_RGB_8: 298 return CmpRgbBuffer(aBuf.get(), mOutput.get(), mPixels); 299 case QCMS_DATA_RGBA_8: 300 return CmpRgbaBuffer(aBuf.get(), mOutput.get(), mPixels); 301 case QCMS_DATA_BGRA_8: 302 return CmpBgraBuffer(aBuf.get(), mOutput.get(), mPixels); 303 default: 304 MOZ_ASSERT_UNREACHABLE("Unknown type!"); 305 break; 306 } 307 308 return false; 309 } 310 311 bool ProduceVerifyOutput(transform_fn_t aFn) { 312 ProduceOutput(aFn); 313 return VerifyOutput(mRef); 314 } 315 316 void PrecacheOutput() { 317 qcms_profile_precache_output_transform(mOutProfile); 318 mPrecache = true; 319 } 320 321 qcms_profile* mInProfile; 322 qcms_profile* mOutProfile; 323 qcms_transform* mTransform; 324 325 UniquePtr<uint8_t[]> mInput; 326 UniquePtr<uint8_t[]> mOutput; 327 UniquePtr<uint8_t[]> mRef; 328 size_t mPixels; 329 qcms_data_type mStorageType; 330 bool mPrecache; 331 }; 332 333 class GfxQcms_sRGB_To_sRGB : public GfxQcms_ProfilePairBase { 334 protected: 335 void SetUp() override { 336 GfxQcms_ProfilePairBase::SetUp(); 337 mInProfile = qcms_profile_sRGB(); 338 mOutProfile = qcms_profile_sRGB(); 339 } 340 }; 341 342 class GfxQcms_sRGB_To_SamsungSyncmaster : public GfxQcms_ProfilePairBase { 343 protected: 344 void SetUp() override { 345 GfxQcms_ProfilePairBase::SetUp(); 346 mInProfile = qcms_profile_sRGB(); 347 mOutProfile = qcms_profile_from_path("lcms_samsung_syncmaster.icc"); 348 } 349 }; 350 351 class GfxQcms_sRGB_To_ThinkpadW540 : public GfxQcms_ProfilePairBase { 352 protected: 353 void SetUp() override { 354 GfxQcms_ProfilePairBase::SetUp(); 355 mInProfile = qcms_profile_sRGB(); 356 mOutProfile = qcms_profile_from_path("lcms_thinkpad_w540.icc"); 357 } 358 }; 359 360 TEST_F(GfxQcms_sRGB_To_sRGB, TransformIdentity) { 361 PrecacheOutput(); 362 SetBuffers(QCMS_DATA_RGB_8); 363 SetTransform(QCMS_DATA_RGB_8); 364 qcms_transform_data(mTransform, mInput.get(), mOutput.get(), mPixels); 365 EXPECT_TRUE(VerifyOutput(mInput)); 366 } 367 368 class GfxQcmsPerf_Base : public GfxQcms_sRGB_To_ThinkpadW540 { 369 protected: 370 explicit GfxQcmsPerf_Base(qcms_data_type aType) { mStorageType = aType; } 371 372 void TransformPerf() { ProduceRef(qcms_transform_data_rgb_out_lut_precache); } 373 374 void TransformPlatformPerf() { 375 qcms_transform_data(mTransform, mInput.get(), mRef.get(), mPixels); 376 } 377 378 void SetUp() override { 379 GfxQcms_sRGB_To_ThinkpadW540::SetUp(); 380 PrecacheOutput(); 381 SetBuffers(mStorageType); 382 SetTransform(mStorageType); 383 } 384 }; 385 386 class GfxQcmsPerf_Rgb : public GfxQcmsPerf_Base { 387 protected: 388 GfxQcmsPerf_Rgb() : GfxQcmsPerf_Base(QCMS_DATA_RGB_8) {} 389 }; 390 391 class GfxQcmsPerf_Rgba : public GfxQcmsPerf_Base { 392 protected: 393 GfxQcmsPerf_Rgba() : GfxQcmsPerf_Base(QCMS_DATA_RGBA_8) {} 394 }; 395 396 class GfxQcmsPerf_Bgra : public GfxQcmsPerf_Base { 397 protected: 398 GfxQcmsPerf_Bgra() : GfxQcmsPerf_Base(QCMS_DATA_BGRA_8) {} 399 }; 400 401 MOZ_GTEST_BENCH_F(GfxQcmsPerf_Rgb, TransformC, [this] { TransformPerf(); }); 402 MOZ_GTEST_BENCH_F(GfxQcmsPerf_Rgb, TransformPlatform, 403 [this] { TransformPlatformPerf(); }); 404 MOZ_GTEST_BENCH_F(GfxQcmsPerf_Rgba, TransformC, [this] { TransformPerf(); }); 405 MOZ_GTEST_BENCH_F(GfxQcmsPerf_Rgba, TransformPlatform, 406 [this] { TransformPlatformPerf(); }); 407 MOZ_GTEST_BENCH_F(GfxQcmsPerf_Bgra, TransformC, [this] { TransformPerf(); }); 408 MOZ_GTEST_BENCH_F(GfxQcmsPerf_Bgra, TransformPlatform, 409 [this] { TransformPlatformPerf(); });