// // Copyright 2023 The ANGLE Project Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // LoadToNative_unittest.cpp: Unit tests for pixel loading functions. #include #include #include "common/debug.h" #include "common/mathutil.h" #include "image_util/loadimage.h" using namespace angle; using namespace testing; namespace { template void initializeRGBInput(std::vector &rgbInput, size_t width, size_t height, size_t depth, size_t inputPixelBytes, size_t inputByteOffset, size_t inputRowPitch, size_t inputDepthPitch) { ASSERT(rgbInput.size() == inputDepthPitch * depth + inputByteOffset); for (size_t z = 0; z < depth; z++) { for (size_t y = 0; y < height; y++) { for (size_t x = 0; x < width; x++) { size_t inputIndex = inputByteOffset + z * inputDepthPitch + y * inputRowPitch + x * inputPixelBytes; rgbInput[inputIndex] = x % 256; rgbInput[inputIndex + 1] = y % 256; rgbInput[inputIndex + 2] = z % 256; } } } } template void verifyRGBToRGBAResults(const char *strCase, std::vector &rgbInput, std::vector &rgbaOutput, size_t width, size_t height, size_t depth, size_t inputPixelBytes, size_t inputByteOffset, size_t inputRowPitch, size_t inputDepthPitch, size_t outputPixelBytes, size_t outputByteOffset, size_t outputRowPitch, size_t outputDepthPitch) { ASSERT(rgbInput.size() == inputDepthPitch * depth + inputByteOffset); ASSERT(rgbaOutput.size() == outputDepthPitch * depth + outputByteOffset); for (size_t z = 0; z < depth; z++) { for (size_t y = 0; y < height; y++) { for (size_t x = 0; x < width; x++) { size_t inputIndex = inputByteOffset + z * inputDepthPitch + y * inputRowPitch + x * inputPixelBytes; size_t outputIndex = outputByteOffset + z * outputDepthPitch + y * outputRowPitch + x * outputPixelBytes; bool rMatch = rgbInput[inputIndex] == rgbaOutput[outputIndex]; bool gMatch = rgbInput[inputIndex + 1] == rgbaOutput[outputIndex + 1]; bool bMatch = rgbInput[inputIndex + 2] == rgbaOutput[outputIndex + 2]; bool aMatch = rgbaOutput[outputIndex + 3] == fourthValue; EXPECT_TRUE(rMatch && gMatch && bMatch && aMatch) << "Case " << strCase << ": Mismatch at Index (" << x << ", " << y << ", " << z << ")" << std::endl << "Expected output: (" << static_cast(rgbInput[inputIndex]) << ", " << static_cast(rgbInput[inputIndex + 1]) << ", " << static_cast(rgbInput[inputIndex + 2]) << ", " << static_cast(fourthValue) << ")" << std::endl << "Actual output: (" << static_cast(rgbaOutput[outputIndex]) << ", " << static_cast(rgbaOutput[outputIndex + 1]) << ", " << static_cast(rgbaOutput[outputIndex + 2]) << ", " << static_cast(rgbaOutput[outputIndex + 3]) << ")"; } } } } template void TestLoadUbyteRGBToRGBA(ImageLoadContext &context, const char *strCase, size_t width, size_t height, size_t depth, size_t inputByteOffset, size_t outputByteOffset, size_t inputRowAlignment) { constexpr uint8_t kInitialByteValue = 0xAA; size_t inputPixelBytes = 3; size_t inputRowPitch = rx::roundUpPow2(width * inputPixelBytes, inputRowAlignment); size_t inputDepthPitch = height * inputRowPitch; size_t inputActualBytes = depth * inputDepthPitch; size_t outputPixelBytes = 4; size_t outputRowPitch = width * outputPixelBytes; size_t outputDepthPitch = height * outputRowPitch; size_t outputActualBytes = depth * outputDepthPitch; // Prepare the RGB input and RGBA output for copy. The offset values are used to add unused // bytes to the beginning of the input and output data, in order to test address alignments. std::vector rgbInput(inputByteOffset + inputActualBytes, kInitialByteValue); initializeRGBInput(rgbInput, width, height, depth, inputPixelBytes, inputByteOffset, inputRowPitch, inputDepthPitch); std::vector rgbaOutput(outputByteOffset + outputActualBytes, kInitialByteValue); // Call loading function. LoadToNative3To4( context, width, height, depth, rgbInput.data() + inputByteOffset, inputRowPitch, inputDepthPitch, rgbaOutput.data() + outputByteOffset, outputRowPitch, outputDepthPitch); // Compare the input and output data. verifyRGBToRGBAResults( strCase, rgbInput, rgbaOutput, width, height, depth, inputPixelBytes, inputByteOffset, inputRowPitch, inputDepthPitch, outputPixelBytes, outputByteOffset, outputRowPitch, outputDepthPitch); } template void TestLoadSbyteRGBToRGBA(ImageLoadContext &context, const char *strCase, size_t width, size_t height, size_t depth, size_t inputByteOffset, size_t outputByteOffset, size_t inputRowAlignment) { constexpr int8_t kInitialByteValue = 0xAA; size_t inputPixelBytes = 3; size_t inputRowPitch = rx::roundUpPow2(width * inputPixelBytes, inputRowAlignment); size_t inputDepthPitch = height * inputRowPitch; size_t inputActualBytes = depth * inputDepthPitch; size_t outputPixelBytes = 4; size_t outputRowPitch = width * outputPixelBytes; size_t outputDepthPitch = height * outputRowPitch; size_t outputActualBytes = depth * outputDepthPitch; // Prepare the RGB input and RGBA output for copy. The offset values are used to add unused // bytes to the beginning of the input and output data, in order to test address alignments. std::vector rgbInput(inputByteOffset + inputActualBytes, kInitialByteValue); initializeRGBInput(rgbInput, width, height, depth, inputPixelBytes, inputByteOffset, inputRowPitch, inputDepthPitch); std::vector rgbaOutput(outputByteOffset + outputActualBytes, kInitialByteValue); // Call loading function. LoadToNative3To4( context, width, height, depth, reinterpret_cast(rgbInput.data() + inputByteOffset), inputRowPitch, inputDepthPitch, reinterpret_cast(rgbaOutput.data() + outputByteOffset), outputRowPitch, outputDepthPitch); // Compare the input and output data. verifyRGBToRGBAResults(strCase, rgbInput, rgbaOutput, width, height, depth, inputPixelBytes, inputByteOffset, inputRowPitch, inputDepthPitch, outputPixelBytes, outputByteOffset, outputRowPitch, outputDepthPitch); } void TestLoadByteRGBToRGBAForAllCases(ImageLoadContext &context, size_t inputCase, size_t width, size_t height, size_t depth, size_t inputByteOffset, size_t outputByteOffset, size_t inputRowAlignment) { std::string strCaseUFF = "UFF_" + std::to_string(inputCase); TestLoadUbyteRGBToRGBA<0xFF>(context, strCaseUFF.c_str(), width, height, depth, inputByteOffset, outputByteOffset, inputRowAlignment); std::string strCaseU01 = "U01_" + std::to_string(inputCase); TestLoadUbyteRGBToRGBA<0x01>(context, strCaseU01.c_str(), width, height, depth, inputByteOffset, outputByteOffset, inputRowAlignment); std::string strCaseS7F = "S7F_" + std::to_string(inputCase); TestLoadSbyteRGBToRGBA<0x7F>(context, strCaseS7F.c_str(), width, height, depth, inputByteOffset, outputByteOffset, inputRowAlignment); std::string strCaseS01 = "S01_" + std::to_string(inputCase); TestLoadSbyteRGBToRGBA<0x01>(context, strCaseS01.c_str(), width, height, depth, inputByteOffset, outputByteOffset, inputRowAlignment); } // Tests the ubyte (0xFF) RGB to RGBA loading function for one RGB pixel. TEST(LoadToNative3To4, LoadUbyteRGBToRGBADataOnePixelWithFourthCompOfFF) { ImageLoadContext context; uint8_t rgbInput[] = {1, 2, 3}; uint8_t rgbaOutput[] = {0, 0, 0, 0}; constexpr uint8_t kFourthValue = 0xFF; LoadToNative3To4(context, 1, 1, 1, rgbInput, 3, 3, rgbaOutput, 4, 4); EXPECT_TRUE(rgbaOutput[0] == rgbInput[0] && rgbaOutput[1] == rgbInput[1] && rgbaOutput[2] == rgbInput[2] && rgbaOutput[3] == kFourthValue) << "Pixel mismatch"; } // Tests the ubyte (0x01) RGB to RGBA loading function for one RGB pixel. TEST(LoadToNative3To4, LoadUbyteRGBToRGBADataOnePixelWithFourthCompOf01) { ImageLoadContext context; uint8_t rgbInput[] = {1, 2, 3}; uint8_t rgbaOutput[] = {0, 0, 0, 0}; constexpr uint8_t kFourthValue = 0x01; LoadToNative3To4(context, 1, 1, 1, rgbInput, 3, 3, rgbaOutput, 4, 4); EXPECT_TRUE(rgbaOutput[0] == rgbInput[0] && rgbaOutput[1] == rgbInput[1] && rgbaOutput[2] == rgbInput[2] && rgbaOutput[3] == kFourthValue) << "Pixel mismatch"; } // Tests the sbyte (0x7F) RGB to RGBA loading function for one RGB pixel. TEST(LoadToNative3To4, LoadSbyteRGBToRGBADataOnePixelWithFourthCompOf7F) { ImageLoadContext context; int8_t rgbInput[] = {1, 2, 3}; int8_t rgbaOutput[] = {0, 0, 0, 0}; constexpr uint8_t kFourthValue = 0x01; LoadToNative3To4(context, 1, 1, 1, reinterpret_cast(rgbInput), 3, 3, reinterpret_cast(rgbaOutput), 4, 4); EXPECT_TRUE(rgbaOutput[0] == rgbInput[0] && rgbaOutput[1] == rgbInput[1] && rgbaOutput[2] == rgbInput[2] && rgbaOutput[3] == static_cast(kFourthValue)) << "Pixel mismatch"; } // Tests the sbyte (0x01) RGB to RGBA loading function for one RGB pixel. TEST(LoadToNative3To4, LoadSbyteRGBToRGBADataOnePixelWithFourthCompOf01) { ImageLoadContext context; int8_t rgbInput[] = {1, 2, 3}; int8_t rgbaOutput[] = {0, 0, 0, 0}; constexpr uint8_t kFourthValue = 0x01; LoadToNative3To4(context, 1, 1, 1, reinterpret_cast(rgbInput), 3, 3, reinterpret_cast(rgbaOutput), 4, 4); EXPECT_TRUE(rgbaOutput[0] == rgbInput[0] && rgbaOutput[1] == rgbInput[1] && rgbaOutput[2] == rgbInput[2] && rgbaOutput[3] == static_cast(kFourthValue)) << "Pixel mismatch"; } // Tests the ubyte (0xFF) RGB to RGBA loading function for 4 RGB pixels, which should be read // together. TEST(LoadToNative3To4, LoadUbyteRGBToRGBADataFourPixelsWithFourthCompOfFF) { ImageLoadContext context; constexpr uint8_t kPixelCount = 4; std::vector rgbInput(kPixelCount * 3); std::vector rgbaOutput(kPixelCount * 4, 0); size_t index = 0; for (auto &inputComponent : rgbInput) { inputComponent = ++index; } constexpr uint8_t kFourthValue = 0xFF; LoadToNative3To4(context, 4, 1, 1, rgbInput.data(), 3, 3, rgbaOutput.data(), 4, 4); for (index = 0; index < kPixelCount; index++) { EXPECT_TRUE(rgbaOutput[index * 4] == rgbInput[index * 3] && rgbaOutput[index * 4 + 1] == rgbInput[index * 3 + 1] && rgbaOutput[index * 4 + 2] == rgbInput[index * 3 + 2] && rgbaOutput[index * 4 + 3] == kFourthValue) << "Mismatch at pixel " << index; } } // Tests the ubyte (0x01) RGB to RGBA loading function for 4 RGB pixels, which should be read // together. TEST(LoadToNative3To4, LoadUbyteRGBToRGBADataFourPixelsWithFourthCompOf01) { ImageLoadContext context; constexpr uint8_t kPixelCount = 4; std::vector rgbInput(kPixelCount * 3); std::vector rgbaOutput(kPixelCount * 4, 0); size_t index = 0; for (auto &inputComponent : rgbInput) { inputComponent = ++index; } constexpr uint8_t kFourthValue = 0x01; LoadToNative3To4(context, 4, 1, 1, rgbInput.data(), 3, 3, rgbaOutput.data(), 4, 4); for (index = 0; index < kPixelCount; index++) { EXPECT_TRUE(rgbaOutput[index * 4] == rgbInput[index * 3] && rgbaOutput[index * 4 + 1] == rgbInput[index * 3 + 1] && rgbaOutput[index * 4 + 2] == rgbInput[index * 3 + 2] && rgbaOutput[index * 4 + 3] == kFourthValue) << "Mismatch at pixel " << index; } } // Tests the sbyte (0x7F) RGB to RGBA loading function for 4 RGB pixels, which should be read // together. TEST(LoadToNative3To4, LoadSbyteRGBToRGBADataFourPixelsWithFourthCompOf7F) { ImageLoadContext context; constexpr uint8_t kPixelCount = 4; std::vector rgbInput(kPixelCount * 3); std::vector rgbaOutput(kPixelCount * 4, 0); size_t index = 0; for (auto &inputComponent : rgbInput) { inputComponent = ++index; } constexpr uint8_t kFourthValue = 0x01; LoadToNative3To4(context, 4, 1, 1, reinterpret_cast(rgbInput.data()), 3, 3, reinterpret_cast(rgbaOutput.data()), 4, 4); for (index = 0; index < kPixelCount; index++) { EXPECT_TRUE(rgbaOutput[index * 4] == rgbInput[index * 3] && rgbaOutput[index * 4 + 1] == rgbInput[index * 3 + 1] && rgbaOutput[index * 4 + 2] == rgbInput[index * 3 + 2] && rgbaOutput[index * 4 + 3] == static_cast(kFourthValue)) << "Mismatch at pixel " << index; } } // Tests the sbyte (0x01) RGB to RGBA loading function for 4 RGB pixels, which should be read // together. TEST(LoadToNative3To4, LoadSbyteRGBToRGBADataFourPixelsWithFourthCompOf01) { ImageLoadContext context; constexpr uint8_t kPixelCount = 4; std::vector rgbInput(kPixelCount * 3); std::vector rgbaOutput(kPixelCount * 4, 0); size_t index = 0; for (auto &inputComponent : rgbInput) { inputComponent = ++index; } constexpr uint8_t kFourthValue = 0x01; LoadToNative3To4(context, 4, 1, 1, reinterpret_cast(rgbInput.data()), 3, 3, reinterpret_cast(rgbaOutput.data()), 4, 4); for (index = 0; index < kPixelCount; index++) { EXPECT_TRUE(rgbaOutput[index * 4] == rgbInput[index * 3] && rgbaOutput[index * 4 + 1] == rgbInput[index * 3 + 1] && rgbaOutput[index * 4 + 2] == rgbInput[index * 3 + 2] && rgbaOutput[index * 4 + 3] == static_cast(kFourthValue)) << "Mismatch at pixel " << index; } } // Tests the byte RGB to RGBA loading function when the width is 4-byte aligned. This loading // function can copy 4 bytes at a time in a row. TEST(LoadToNative3To4, LoadByteRGBToRGBADataAlignedWidth) { ImageLoadContext context; size_t alignedTestWidths[] = {4, 20, 128, 1000, 4096}; for (auto &width : alignedTestWidths) { ASSERT(width % 4 == 0); TestLoadByteRGBToRGBAForAllCases(context, width, width, 3, 1, 0, 0, 1); } } // Tests the byte RGB to RGBA loading function when the width is not 4-byte aligned, which will // cause the loading function to copy some bytes in the beginning and end of some rows individually. TEST(LoadToNative3To4, LoadByteRGBToRGBADataUnalignedWidth) { ImageLoadContext context; size_t unalignedTestWidths[] = {5, 22, 127, 1022, 4097}; for (auto &width : unalignedTestWidths) { ASSERT(width % 4 != 0); TestLoadByteRGBToRGBAForAllCases(context, width, width, 3, 1, 0, 0, 1); } } // Tests the byte RGB to RGBA loading function when there is depth. TEST(LoadToNative3To4, LoadByteRGBToRGBADataWithDepth) { ImageLoadContext context; size_t unalignedTestDepths[] = {3}; for (auto &depth : unalignedTestDepths) { TestLoadByteRGBToRGBAForAllCases(context, depth, 3, 3, depth, 0, 0, 1); } } // Tests the byte RGB to RGBA loading function when the width is less than 4 bytes. Therefore the // loading function will copy data one byte at a time. TEST(LoadToNative3To4, LoadByteRGBToRGBADataWidthLessThanUint32) { ImageLoadContext context; size_t smallTestWidths[] = {1, 2, 3}; for (auto &width : smallTestWidths) { TestLoadByteRGBToRGBAForAllCases(context, width, width, 3, 1, 0, 0, 1); } } // Tests the byte RGB to RGBA loading function when when the width is 4-byte-aligned and the input // address has an offset. TEST(LoadToNative3To4, LoadByteRGBToRGBAWithAlignedWidthAndInputAddressOffset) { ImageLoadContext context; size_t inputOffsetList[] = {1, 2, 3}; for (auto &inputOffset : inputOffsetList) { TestLoadByteRGBToRGBAForAllCases(context, inputOffset, 8, 8, 1, inputOffset, 0, 1); } } // Tests the byte RGB to RGBA loading function when when the width is not 4-byte-aligned and the // input address has an offset. TEST(LoadToNative3To4, LoadByteRGBToRGBAWithUnalignedWidthAndInputAddressOffset) { ImageLoadContext context; size_t inputOffsetList[] = {1, 2, 3}; for (auto &inputOffset : inputOffsetList) { TestLoadByteRGBToRGBAForAllCases(context, inputOffset, 7, 7, 1, inputOffset, 0, 1); } } // Tests the byte RGB to RGBA loading function when the width is 4-byte-aligned and the output // address has an offset. TEST(LoadToNative3To4, LoadByteRGBToRGBAWithAlignedWidthAndOutputAddressOffset) { ImageLoadContext context; size_t outputOffsetList[] = {1, 2, 3}; for (auto &outputOffset : outputOffsetList) { TestLoadByteRGBToRGBAForAllCases(context, outputOffset, 8, 8, 1, 0, outputOffset, 1); } } // Tests the byte RGB to RGBA loading function when the width is not 4-byte-aligned and the output // address has an offset. TEST(LoadToNative3To4, LoadByteRGBToRGBAWithUnalignedWidthAndOutputAddressOffset) { ImageLoadContext context; size_t outputOffsetList[] = {1, 2, 3}; for (auto &outputOffset : outputOffsetList) { TestLoadByteRGBToRGBAForAllCases(context, outputOffset, 7, 7, 1, 0, outputOffset, 1); } } // Tests the byte RGB to RGBA loading function when the width is 4-byte-aligned and the input row // alignment is 4. TEST(LoadToNative3To4, LoadByteRGBToRGBAWithAlignedWidthAndAlignment4) { ImageLoadContext context; size_t inputRowAlignmentList[] = {4}; for (auto &alignment : inputRowAlignmentList) { TestLoadByteRGBToRGBAForAllCases(context, alignment, 4, 4, 1, 0, 0, alignment); } } // Tests the byte RGB to RGBA loading function when the width is not 4-byte-aligned and the input // row alignment is 4. TEST(LoadToNative3To4, LoadByteRGBToRGBAWithUnalignedWidthAndAlignment4) { ImageLoadContext context; size_t inputRowAlignmentList[] = {4}; for (auto &alignment : inputRowAlignmentList) { TestLoadByteRGBToRGBAForAllCases(context, alignment, 5, 5, 1, 0, 0, alignment); } } } // namespace