Reduce decoding time of embedded gltf + Fix base64 encode bug 35/287635/7
authorEunki, Hong <eunkiki.hong@samsung.com>
Wed, 1 Feb 2023 23:21:24 +0000 (08:21 +0900)
committerEunki, Hong <eunkiki.hong@samsung.com>
Fri, 17 Feb 2023 05:02:37 +0000 (14:02 +0900)
Let we don't copy the full string during decode base64.
+
Let we print '=' tags end of encoded base64 result, so
we can decode the encode result at common external library.

Change-Id: I2c671512ffcd986e30a4e773dacdd6e62dca2f31
Signed-off-by: Eunki, Hong <eunkiki.hong@samsung.com>
automated-tests/src/dali-toolkit/utc-Dali-Builder.cpp
dali-scene3d/public-api/loader/buffer-definition.cpp
dali-scene3d/public-api/loader/material-definition.cpp
dali-toolkit/devel-api/builder/base64-encoding.cpp
dali-toolkit/devel-api/builder/base64-encoding.h

index 802f7b4..f332890 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2023 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -1774,7 +1774,69 @@ int UtcDaliBase64EncodingP(void)
 
   std::string output;
   DALI_TEST_CHECK(value.Get(output));
-  DALI_TEST_EQUALS(output, "AAAAAAEAAAACAAAAAwAAAAQAAAAFAAAAAAAAAP////8", TEST_LOCATION);
+  DALI_TEST_EQUALS(output, "AAAAAAEAAAACAAAAAwAAAAQAAAAFAAAAAAAAAP////8=", TEST_LOCATION);
+
+  std::cout << "Output data:  " << output << std::endl;
+
+  END_TEST;
+}
+
+int UtcDaliBase64EncodingP2(void)
+{
+  std::vector<uint8_t> data = {0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, std::numeric_limits<uint8_t>::min(), std::numeric_limits<uint8_t>::min(), std::numeric_limits<uint8_t>::min(), std::numeric_limits<uint8_t>::min(), std::numeric_limits<uint8_t>::max(), std::numeric_limits<uint8_t>::max(), std::numeric_limits<uint8_t>::max(), std::numeric_limits<uint8_t>::max()};
+
+  Property::Value value;
+  EncodeBase64PropertyData(value, data);
+
+  std::cout << "Input data:  ";
+  std::ostream_iterator<uint32_t> out_it(std::cout, ", ");
+  std::copy(data.begin(), data.end(), out_it);
+  std::cout << std::endl;
+
+  std::string output;
+  DALI_TEST_CHECK(value.Get(output));
+  DALI_TEST_EQUALS(output, "AAAAAAEAAAACAAAAAwAAAAQAAAAFAAAAAAAAAP////8=", TEST_LOCATION);
+
+  std::cout << "Output data:  " << output << std::endl;
+
+  END_TEST;
+}
+
+int UtcDaliBase64EncodingP3(void)
+{
+  std::string originalData = "Something Longer than 64 ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/==  length is 106";
+  originalData.push_back(-4);
+  originalData.push_back(-7); // some kind of non-ascii.
+  originalData.push_back(0);
+  originalData.push_back(0);
+  originalData.push_back(2);
+  originalData.push_back(2);
+  originalData.push_back(2);
+
+  std::vector<uint8_t> data(originalData.begin(), originalData.end());
+
+  Dali::Property::Value value;
+  EncodeBase64PropertyData(value, data);
+
+  std::cout << "Input data:  ";
+  std::ostream_iterator<uint8_t> out_it(std::cout, ", ");
+  std::copy(data.begin(), data.end(), out_it);
+  std::cout << std::endl;
+
+  std::string           output;
+  Dali::Property::Array array;
+  DALI_TEST_CHECK(value.GetArray());
+  array = *value.GetArray();
+  DALI_TEST_EQUALS(array.Count(), 3, TEST_LOCATION);
+  DALI_TEST_CHECK(array[0].Get(output));
+  std::cout << "first string :  " << output << std::endl;
+  DALI_TEST_EQUALS(output, "U29tZXRoaW5nIExvbmdlciB0aGFuIDY0IEFCQ0RFRkdISUpLTE1OT1BRUlNUVVZX", TEST_LOCATION);
+  DALI_TEST_CHECK(array[1].Get(output));
+  std::cout << "second string : " << output << std::endl;
+  DALI_TEST_EQUALS(output, "WFlaYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU2Nzg5Ky89PSAgbGVu", TEST_LOCATION);
+  DALI_TEST_CHECK(array[2].Get(output));
+  std::cout << "third string : " << output << std::endl;
+  DALI_TEST_EQUALS(output, "Z3RoIGlzIDEwNvz5AAACAgI=", TEST_LOCATION);
 
   std::cout << "Output data:  " << output << std::endl;
 
@@ -1793,14 +1855,22 @@ int UtcDaliBase64EncodingN(void)
   DALI_TEST_CHECK(value.Get(output));
   DALI_TEST_EQUALS(output.empty(), true, TEST_LOCATION);
 
+  std::vector<uint8_t> data2;
+  EncodeBase64PropertyData(value, data2);
+
+  DALI_TEST_CHECK(value.Get(output));
+  DALI_TEST_EQUALS(output.empty(), true, TEST_LOCATION);
+
   END_TEST;
 }
 
 template<typename T>
 int b64l(std::vector<T>& data)
 {
-  auto lengthInBytes = 4 * data.size();
-  return ceil(lengthInBytes * 1.33333f);
+  auto lengthInBytes = sizeof(T) * data.size();
+  // base64 encode each 3-byte as 4-byte.
+  // return ceil(lengthInBytes / 3) * 4
+  return (lengthInBytes + 2) / 3 * 4;
 }
 
 int UtcDaliBase64EncodingP02(void)
@@ -1969,6 +2039,10 @@ int UtcDaliBase64DecodingN01(void)
   std::vector<uint32_t> outputData;
   DecodeBase64PropertyData(value, outputData);
   DALI_TEST_EQUALS(outputData.size(), 0, TEST_LOCATION);
+
+  std::vector<uint8_t> outputData2;
+  DecodeBase64PropertyData(value, outputData2);
+  DALI_TEST_EQUALS(outputData2.size(), 0, TEST_LOCATION);
   END_TEST;
 }
 
@@ -1985,6 +2059,10 @@ int UtcDaliBase64DecodingN02(void)
   std::vector<uint32_t> outputData;
   DecodeBase64PropertyData(value, outputData);
   DALI_TEST_EQUALS(outputData.size(), 0, TEST_LOCATION);
+
+  std::vector<uint8_t> outputData2;
+  DecodeBase64PropertyData(value, outputData2);
+  DALI_TEST_EQUALS(outputData2.size(), 0, TEST_LOCATION);
   END_TEST;
 }
 
@@ -2002,3 +2080,33 @@ int UtcDaliBase64DecodingP01(void)
 
   END_TEST;
 }
+
+int UtcDaliBase64DecodingP02(void)
+{
+  tet_infoline("Test decoding string of known data gives expected result");
+
+  std::string          testInput("//////7+/v4DAgEA");
+  std::vector<uint8_t> expectedResults = {0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0x03, 0x02, 0x01, 0x00};
+
+  std::vector<uint8_t> outputData;
+  DecodeBase64PropertyData(Property::Value(testInput), outputData);
+
+  DALI_TEST_EQUALS(std::equal(expectedResults.begin(), expectedResults.end(), outputData.begin()), true, TEST_LOCATION);
+
+  END_TEST;
+}
+
+int UtcDaliBase64DecodingFromString(void)
+{
+  tet_infoline("Test decoding string of known data gives expected result");
+
+  std::string          testInput("//////7+/v4DAgEA");
+  std::vector<uint8_t> expectedResults = {0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0x03, 0x02, 0x01, 0x00};
+
+  std::vector<uint8_t> outputData;
+  DecodeBase64FromString(testInput, outputData);
+
+  DALI_TEST_EQUALS(std::equal(expectedResults.begin(), expectedResults.end(), outputData.begin()), true, TEST_LOCATION);
+
+  END_TEST;
+}
index ee89bdc..1704927 100644 (file)
@@ -38,7 +38,7 @@ static constexpr std::string_view EMBEDDED_DATA_BASE64_ENCODING_TYPE   = "base64
 
 struct BufferDefinition::Impl
 {
-  std::vector<uint8_t>             buffer;
+  std::vector<uint8_t>              buffer;
   std::shared_ptr<Dali::FileStream> stream;
 };
 
@@ -85,9 +85,9 @@ void BufferDefinition::LoadBuffer()
       if(position != std::string::npos)
       {
         position += EMBEDDED_DATA_BASE64_ENCODING_TYPE.length();
-        std::string data = mUri.substr(position);
+        std::string_view data = std::string_view(mUri).substr(position);
         mImpl.get()->buffer.clear();
-        Dali::Toolkit::DecodeBase64PropertyData(data, mImpl.get()->buffer);
+        Dali::Toolkit::DecodeBase64FromString(data, mImpl.get()->buffer);
         mImpl.get()->stream = std::make_shared<Dali::FileStream>(reinterpret_cast<uint8_t*>(mImpl.get()->buffer.data()), mByteLength, FileStream::READ | FileStream::BINARY);
         mIsEmbedded         = true;
       }
index bf764ff..58ab65d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2023 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -91,9 +91,9 @@ Dali::PixelData LoadImageResource(const std::string& resourcePath,
     if(position != std::string::npos)
     {
       position += EMBEDDED_DATA_BASE64_ENCODING_TYPE.length();
-      std::string          data = textureDefinition.mImageUri.substr(position);
+      std::string_view     data = std::string_view(textureDefinition.mImageUri).substr(position);
       std::vector<uint8_t> buffer;
-      Dali::Toolkit::DecodeBase64PropertyData(data, buffer);
+      Dali::Toolkit::DecodeBase64FromString(data, buffer);
       uint32_t bufferSize = buffer.size();
 
       Dali::Devel::PixelBuffer pixelBuffer = Dali::LoadImageFromBuffer(reinterpret_cast<uint8_t*>(buffer.data()), bufferSize, textureDefinition.mMinImageDimensions, fittingMode, textureDefinition.mSamplingMode, orientationCorrection);
@@ -187,8 +187,7 @@ MaterialDefinition::LoadRaw(const std::string& imagesPath)
 
   // Load textures
   auto iTexture   = mTextureStages.begin();
-  auto checkStage = [&](uint32_t flags)
-  {
+  auto checkStage = [&](uint32_t flags) {
     return iTexture != mTextureStages.end() && MaskMatch(iTexture->mSemantic, flags);
   };
 
@@ -323,7 +322,7 @@ TextureSet MaterialDefinition::Load(const EnvironmentDefinition::Vector& environ
   uint32_t n = 0;
   for(auto& tData : raw.mTextures)
   {
-    auto& pixels  = tData.mPixels;
+    auto&   pixels = tData.mPixels;
     Texture texture;
     if(pixels)
     {
@@ -379,8 +378,7 @@ TextureSet MaterialDefinition::Load(const EnvironmentDefinition::Vector& environ
 
 bool MaterialDefinition::CheckTextures(uint32_t flags) const
 {
-  return std::find_if(mTextureStages.begin(), mTextureStages.end(), [flags](const TextureStage& ts)
-                      { return MaskMatch(ts.mSemantic, flags); }) != mTextureStages.end();
+  return std::find_if(mTextureStages.begin(), mTextureStages.end(), [flags](const TextureStage& ts) { return MaskMatch(ts.mSemantic, flags); }) != mTextureStages.end();
 }
 
 } // namespace Loader
index 2de438a..17b823a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2023 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -75,12 +75,13 @@ bool DecodeBase64PropertyData(const Property::Value& value, std::vector<uint32_t
 
   if(GetStringFromProperty(value, encodedString))
   {
-    std::vector<unsigned char> outputTmpData;
-    outputTmpData.reserve(ceil(encodedString.size() * 0.75f));
+    std::vector<uint8_t> outputTmpData;
+    // Output required at least ceil(length * 3 / 4)
+    outputData.reserve((encodedString.size() * 3 + 3) / 4);
     bn::decode_b64(encodedString.begin(), encodedString.end(), std::back_inserter(outputTmpData));
 
     outputData.clear();
-    uint32_t outputSize = outputTmpData.size() / sizeof(uint32_t) + static_cast<uint32_t>(!!(outputTmpData.size() % sizeof(uint32_t)));
+    uint32_t outputSize = (outputTmpData.size() + sizeof(uint32_t) - 1) / sizeof(uint32_t);
     outputData.resize(outputSize);
     // Treat as a block of data
     memcpy(&outputData[0], &outputTmpData[0], outputTmpData.size());
@@ -97,7 +98,8 @@ bool DecodeBase64PropertyData(const Property::Value& value, std::vector<uint8_t>
 
   if(GetStringFromProperty(value, encodedString))
   {
-    outputData.reserve(ceil(encodedString.size() * 0.75f));
+    // Output required at least ceil(length * 3 / 4)
+    outputData.reserve((encodedString.size() * 3 + 3) / 4);
     bn::decode_b64(encodedString.begin(), encodedString.end(), std::back_inserter(outputData));
 
     decoded = true;
@@ -105,6 +107,15 @@ bool DecodeBase64PropertyData(const Property::Value& value, std::vector<uint8_t>
   return decoded;
 }
 
+bool DecodeBase64FromString(const std::string_view& encodedString, std::vector<uint8_t>& outputData)
+{
+  // Output required at least ceil(length * 3 / 4)
+  outputData.reserve((encodedString.size() * 3 + 3) >> 2);
+  bn::decode_b64(encodedString.begin(), encodedString.end(), std::back_inserter(outputData));
+
+  return true; // Always success.
+}
+
 void EncodeBase64PropertyData(Property::Value& value, const std::vector<uint32_t>& inputData)
 {
   std::ostringstream oss;
@@ -114,11 +125,61 @@ void EncodeBase64PropertyData(Property::Value& value, const std::vector<uint32_t
                  std::ostream_iterator<unsigned char>(oss, ""));
 
   std::string encodedString = oss.str();
+
+  // Add padding
+  int paddingLength = (4 - (encodedString.length() % 4)) % 4;
+  if(paddingLength > 0)
+  {
+    while(paddingLength--)
+    {
+      oss << '=';
+    }
+    encodedString = oss.str();
+  }
+
+  if(encodedString.length() > MAX_PROPERTY_STRING_LENGTH)
+  {
+    // cut string up into blocks of MAX_PROPERTY_STRING_LENGTH and store to an array
+    auto numStrings = (encodedString.length() + MAX_PROPERTY_STRING_LENGTH - 1) / MAX_PROPERTY_STRING_LENGTH;
+
+    Property::Array array;
+    for(auto i = 0u; i < numStrings; ++i)
+    {
+      array.PushBack(encodedString.substr(i * MAX_PROPERTY_STRING_LENGTH, MAX_PROPERTY_STRING_LENGTH));
+    }
+    value = array;
+  }
+  else
+  {
+    value = encodedString;
+  }
+}
+
+void EncodeBase64PropertyData(Property::Value& value, const std::vector<uint8_t>& inputData)
+{
+  std::ostringstream oss;
+
+  bn::encode_b64(reinterpret_cast<const uint8_t*>(&inputData[0]),
+                 reinterpret_cast<const uint8_t*>(&inputData[0] + inputData.size()),
+                 std::ostream_iterator<char>(oss, ""));
+
+  std::string encodedString = oss.str();
+
+  // Add padding
+  int paddingLength = (4 - (encodedString.length() % 4)) % 4;
+  if(paddingLength > 0)
+  {
+    while(paddingLength--)
+    {
+      oss << '=';
+    }
+    encodedString = oss.str();
+  }
+
   if(encodedString.length() > MAX_PROPERTY_STRING_LENGTH)
   {
     // cut string up into blocks of MAX_PROPERTY_STRING_LENGTH and store to an array
-    auto numStrings = encodedString.length() / MAX_PROPERTY_STRING_LENGTH +
-                      ((encodedString.length() % MAX_PROPERTY_STRING_LENGTH) != 0);
+    auto numStrings = (encodedString.length() + MAX_PROPERTY_STRING_LENGTH - 1) / MAX_PROPERTY_STRING_LENGTH;
 
     Property::Array array;
     for(auto i = 0u; i < numStrings; ++i)
index c7067ed..566bded 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_TOOLKIT_BASE64_ENCODING_H
 
 /*
- * Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2023 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@
 // EXTERNAL INCLUDES
 #include <dali/public-api/common/vector-wrapper.h>
 #include <dali/public-api/object/property.h>
+#include <string_view>
 
 // INTERNAL INCLUDES
 #include <dali-toolkit/public-api/dali-toolkit-common.h>
@@ -56,6 +57,18 @@ DALI_TOOLKIT_API bool DecodeBase64PropertyData(const Property::Value& value, std
 DALI_TOOLKIT_API bool DecodeBase64PropertyData(const Property::Value& value, std::vector<uint8_t>& outputData);
 
 /**
+ * @brief Parses a std::string_view to retrieve an array of uint8_t data.
+ *
+ * Data can be encoded using the base64 encoding scheme to allow it to be used
+ * in JSON (The property system maps to JSON types).
+ *
+ * @param[in] encodedString The input string to decode
+ * @param[out] outputData The output data block
+ * @return True if a data block was decoded successfully.
+ */
+DALI_TOOLKIT_API bool DecodeBase64FromString(const std::string_view& encodedString, std::vector<uint8_t>& outputData);
+
+/**
  * @brief Convert a block of uint32_t data into a Property::STRING or ARRAY of STRINGs
  * encoded using base64. This allows the data to be mapped to JSON easily.
  *
@@ -64,6 +77,15 @@ DALI_TOOLKIT_API bool DecodeBase64PropertyData(const Property::Value& value, std
  */
 DALI_TOOLKIT_API void EncodeBase64PropertyData(Property::Value& value, const std::vector<uint32_t>& inputData);
 
+/**
+ * @brief Convert a block of uint8_t data into a Property::STRING or ARRAY of STRINGs
+ * encoded using base64. This allows the data to be mapped to JSON easily.
+ *
+ * @param[out] value The value to write data into (to avoid copying).
+ * @param[in] inputData The input
+ */
+DALI_TOOLKIT_API void EncodeBase64PropertyData(Property::Value& value, const std::vector<uint8_t>& inputData);
+
 } // namespace Toolkit
 
 } // namespace Dali