Add option --out_dir to specify output dir

Also copies the filepath.* libraries from glbench for better
file path management.

BUG=chromium:936705
TEST=Compile and run

Change-Id: I3ef5a16964637501d7007076a73ce6be7966ddc8
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/vkbench/+/2533451
Auto-Submit: Po-Hsien Wang <pwang@chromium.org>
Reviewed-by: Ilja H. Friedel <ihf@chromium.org>
Commit-Queue: Ilja H. Friedel <ihf@chromium.org>
Tested-by: Ilja H. Friedel <ihf@chromium.org>
diff --git a/src/clearTest.cc b/src/clearTest.cc
index 67aec4e..f8b2488 100644
--- a/src/clearTest.cc
+++ b/src/clearTest.cc
@@ -101,7 +101,7 @@
   frame_buffer_ = device.createFramebuffer(frame_buffer_info);
 }
 
-void ClearTest::SaveImage(std::string filename) const {
+void ClearTest::SaveImage(FilePath file_path) const {
   const vk::Device device = vk->GetDevice();
   vkImage* dest = vk->GetReadableImage(img_, img_size_, img_format_);
   DEFER(delete dest);
@@ -110,10 +110,9 @@
   const char* data =
       (const char*)device.mapMemory(dest->GetMemory(), 0, VK_WHOLE_SIZE);
   data += sub_resource_layout.offset;
-  SavePPM(filename, data, img_size_.width, img_size_.height,
+  SavePPM(file_path, data, img_size_.width, img_size_.height,
           sub_resource_layout.rowPitch);
-
-  DEBUG("Saved image to %s", filename.c_str());
+  DEBUG("Saved image to %s", file_path.value().c_str());
 }
 
 }  // namespace vkbench
diff --git a/src/clearTest.h b/src/clearTest.h
index 124c69e..3650cec 100644
--- a/src/clearTest.h
+++ b/src/clearTest.h
@@ -24,7 +24,7 @@
   virtual double FormatMeasurement(double time) override {
     return img_size_.width * img_size_.height / time;
   }
-  void SaveImage(std::string filename) const override;
+  void SaveImage(FilePath file_path) const override;
 
  protected:
   void Initialize() override;
diff --git a/src/drawSizeTest.cc b/src/drawSizeTest.cc
index 26315a0..a376abb 100644
--- a/src/drawSizeTest.cc
+++ b/src/drawSizeTest.cc
@@ -4,7 +4,7 @@
 
 #include "drawSizeTest.h"
 
-extern std::string g_spirv_dir;
+extern FilePath g_spirv_dir;
 
 namespace vkbench {
 void DrawSizeTest::Initialize() {
@@ -79,9 +79,9 @@
 
 void DrawSizeTest::CreateGraphicsPipeline() {
   std::string vert_shader_code =
-      readFile(g_spirv_dir + "drawSizeTest.vert.spv");
+      readShaderFile("drawSizeTest.vert.spv");
   std::string frag_shader_code =
-      readFile(g_spirv_dir + "drawSizeTest.frag.spv");
+      readShaderFile("drawSizeTest.frag.spv");
   vk::Device device = vk->GetDevice();
 
   vk::ShaderModule vert_shader_module =
@@ -168,7 +168,7 @@
   frame_buffer_ = device.createFramebuffer(frame_buffer_info);
 }
 
-void DrawSizeTest::SaveImage(std::string filename) const {
+void DrawSizeTest::SaveImage(FilePath file_path) const {
   const vk::Device device = vk->GetDevice();
   vkImage* dest = vk->GetReadableImage(img_, img_size_, img_format_);
   DEFER(delete dest);
@@ -177,10 +177,9 @@
   const char* data =
       (const char*)device.mapMemory(dest->GetMemory(), 0, VK_WHOLE_SIZE);
   data += sub_resource_layout.offset;
-  SavePPM(filename, data, img_size_.width, img_size_.height,
+  SavePPM(file_path, data, img_size_.width, img_size_.height,
           sub_resource_layout.rowPitch);
-
-  DEBUG("Saved image to %s", filename.c_str());
+  DEBUG("Saved image to %s", file_path.value().c_str());
 }
 
 }  // namespace vkbench
diff --git a/src/drawSizeTest.h b/src/drawSizeTest.h
index 214a679..743fc3c 100644
--- a/src/drawSizeTest.h
+++ b/src/drawSizeTest.h
@@ -22,7 +22,7 @@
   const char* Name() const override { return name_; }
   const char* Desp() const override { return desp_; }
   const char* Unit() const override { return "us"; }
-  void SaveImage(std::string filename) const override;
+  void SaveImage(FilePath file_path) const override;
 
  protected:
   void Initialize() override;
diff --git a/src/filepath.cc b/src/filepath.cc
new file mode 100644
index 0000000..cd51c58
--- /dev/null
+++ b/src/filepath.cc
@@ -0,0 +1,166 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "filepath.h"
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <vector>
+#include "utils.h"
+
+inline void AppendToString(std::string* target, std::string& source) {
+  target->append(source);
+}
+
+FilePath FilePath::DirName() {
+  FilePath new_path(path_);
+
+  std::string::size_type last_separator =
+      new_path.path_.find_last_of(kSeparators, std::string::npos);
+
+  unsigned int letter = 0;
+  if (last_separator == std::string::npos) {
+    // path_ is in the current directory.
+    new_path.path_.resize(letter + 1);
+  } else if (last_separator == letter + 1) {
+    // path_ is in the root directory.
+    new_path.path_.resize(letter + 2);
+  } else if (last_separator == letter + 2 &&
+             IsSeparator(new_path.path_[letter + 1])) {
+    // path_ is in "//" (possibly with a drive letter); leave the double
+    // separator intact indicating alternate root.
+    new_path.path_.resize(letter + 3);
+  } else if (last_separator != 0) {
+    // path_ is somewhere else, trim the basename.
+    new_path.path_.resize(last_separator);
+  }
+  new_path.StripTrailingSeparatorsInternal();
+  if (!new_path.path_.length())
+    new_path.path_ = kCurrentDirectory;
+
+  return new_path;
+}
+
+const std::string& FilePath::value() const {
+  return this->path_;
+}
+
+bool FilePath::IsSeparator(char character) {
+  for (size_t i = 0; i < strlen(kSeparators) - 1; ++i) {
+    if (character == kSeparators[i]) {
+      return true;
+    }
+  }
+  return false;
+}
+
+FilePath FilePath::Append(const FilePath& path) {
+  // return FilePath(this->path_ + path.path_);
+  std::string component = path.value();
+  std::string appended = component;
+  std::string without_nuls;
+
+  std::string::size_type nul_pos = component.find(kStringTerminator);
+  if (nul_pos != std::string::npos) {
+    // without_nuls = component.substr(0, nul_pos);
+    appended = std::string(without_nuls);
+  }
+
+  // DCHECK(appended.length() <= 0 || appended[0] !=
+  // this->separator.c_str()[0]);
+  if (path_.compare(kCurrentDirectory) == 0 && !appended.empty()) {
+    // Append normally doesn't do any normalization, but as a special case,
+    // when appending to kCurrentDirectory, just return a new path for the
+    // component argument.  Appending component to kCurrentDirectory would
+    // serve no purpose other than needlessly lengthening the path, and
+    // it's likely in practice to wind up with FilePath objects containing
+    // only kCurrentDirectory when calling DirName on a single relative path
+    // component.
+    return FilePath(appended);
+  }
+  FilePath new_path(path_);
+  new_path.StripTrailingSeparatorsInternal();
+
+  // Don't append a separator if the path is empty (indicating the current
+  // directory) or if the path component is empty (indicating nothing to
+  // append).
+  if (!appended.empty() && !new_path.path_.empty()) {
+    // Don't append a separator if the path still ends with a trailing
+    // separator after stripping (indicating the root directory).
+    char tmp = new_path.path_.back();
+    if (!IsSeparator(tmp)) {
+      // Don't append a separator if the path is just a drive letter.
+      if (FindDriveLetter(new_path.path_) + 1 != new_path.path_.length()) {
+        new_path.path_.append(1, kSeparators[0]);
+      }
+    }
+  }
+  AppendToString(&new_path.path_, appended);
+  return new_path;
+}
+
+void FilePath::StripTrailingSeparatorsInternal() {
+  // If there is no drive letter, start will be 1, which will prevent
+  // stripping the leading separator if there is only one separator.  If there
+  // is a drive letter, start will be set appropriately to prevent stripping
+  // the first separator following the drive letter, if a separator
+  // immediately follows the drive letter.
+  std::string::size_type start = FindDriveLetter(path_) + 2;
+
+  std::string::size_type last_stripped = std::string::npos;
+  for (std::string::size_type pos = path_.length();
+       pos > start && IsSeparator(path_[pos - 1]); --pos) {
+    // If the string only has two separators and they're at the beginning,
+    // don't strip them, unless the string began with more than two
+    // separators.
+    if (pos != start + 1 || last_stripped == start + 2 ||
+        !IsSeparator(path_[start - 1])) {
+      path_.resize(pos - 1);
+      last_stripped = pos;
+    }
+  }
+}
+
+std::string::size_type FindDriveLetter(std::string path) {
+#if defined(FILE_PATH_USES_DRIVE_LETTERS)
+  // This is dependent on an ASCII-based character set, but that's a
+  // reasonable assumption.  iswalpha can be too inclusive here.
+  if (path.length() >= 2 && path[1] == L':' &&
+      ((path[0] >= L'A' && path[0] <= L'Z') ||
+       (path[0] >= L'a' && path[0] <= L'z'))) {
+    return 1;
+  }
+#endif  // FILE_PATH_USES_DRIVE_LETTERS
+  return std::string::npos;
+}
+
+bool CreateDirectory(FilePath& full_path) {
+  std::vector<FilePath> subpaths;
+
+  // Collect a list of all parent directories.
+  FilePath last_path = full_path;
+  subpaths.push_back(full_path);
+  for (FilePath path = full_path.DirName(); path.value() != last_path.value();
+       path = path.DirName()) {
+    subpaths.push_back(path);
+    last_path = path;
+  }
+
+  // Iterate through the parents and create the missing ones.
+  for (auto i = subpaths.rbegin(); i != subpaths.rend(); ++i) {
+    if (check_dir_existence(i->value().c_str()))
+      continue;
+    if (mkdir(i->value().c_str(), 0700) == 0)
+      continue;
+    // Mkdir failed, but it might have failed with EEXIST, or some other error
+    // due to the the directory appearing out of thin air. This can occur if
+    // two processes are trying to create the same file system tree at the same
+    // time. Check to see if it exists and make sure it is a directory.
+    if (!check_dir_existence(i->value().c_str())) {
+      return false;
+    }
+  }
+  return true;
+}
+
diff --git a/src/filepath.h b/src/filepath.h
new file mode 100644
index 0000000..00e6144
--- /dev/null
+++ b/src/filepath.h
@@ -0,0 +1,33 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BENCH_GL_FILES_PATH_H_
+#define BENCH_GL_FILES_PATH_H_
+
+#include <iostream>
+
+class FilePath {
+ public:
+  FilePath() { this->path_ = std::string(""); }
+  FilePath(const FilePath& that) { this->path_ = that.path_; }
+  FilePath(std::string path) { this->path_ = path; }
+  FilePath(const char* path) { this->path_ = path; }
+  ~FilePath() = default;
+
+  FilePath DirName();
+  const std::string& value() const;
+  bool IsSeparator(char character);
+  FilePath Append(const FilePath& path);
+  void StripTrailingSeparatorsInternal();
+
+ private:
+  std::string path_;
+  char kStringTerminator = '\0';
+  char kSeparators[2] = "/";
+  char kCurrentDirectory[2] = ".";
+};
+bool CreateDirectory(FilePath&);
+std::string::size_type FindDriveLetter(std::string path);
+
+#endif  // BENCH_GL_FILES_PATH_H_
diff --git a/src/main.cc b/src/main.cc
index b92b9e0..fcdce34 100644
--- a/src/main.cc
+++ b/src/main.cc
@@ -12,6 +12,7 @@
 #include "clearTest.h"
 #include "constant.h"
 #include "drawSizeTest.h"
+#include "filepath.h"
 #include "submitTest.h"
 #include "utils.h"
 
@@ -27,7 +28,9 @@
 // possible.
 int g_hasty = false;
 // g_spirv_dir is the path to the folder contains spirv code for test.
-std::string g_spirv_dir = "shaders/";
+FilePath g_spirv_dir = FilePath("shaders");
+// g_out_dir is the path to the folder to store the output image.
+FilePath g_out_dir = FilePath("");
 
 // kLongOptions defines the options for argument options.
 const static struct option kLongOptions[] = {
@@ -35,6 +38,7 @@
     {"tests", required_argument, nullptr, 't'},
     {"blacklist", required_argument, nullptr, 'b'},
     {"spirv_dir", required_argument, nullptr, 's'},
+    {"out_dir", required_argument, nullptr, 'o'},
     {"help", no_argument, nullptr, 'h'},
     {"list", no_argument, &g_list, 1},
     {"vlayer", no_argument, &g_vlayer, 1},
@@ -101,7 +105,7 @@
   score = test->FormatMeasurement(score);
   LOG("@RESULT: %46s = %10.2f %-15s", test->Name(), score, test->Unit());
   try {
-    test->SaveImage(((std::string)test->Name()));
+    test->SaveImage(g_out_dir.Append(FilePath(test->Name())));
   } catch (std::runtime_error err) {
     DEBUG("Get runtime_error while SaveImage: %s.", err.what());
   }
@@ -117,7 +121,8 @@
   --verbose              Show verbose messages.
   --vlayer               Enable vulkan verification layer.
   --hasty                Enable hasty mode.
-  --spirv_dir            Path to SPIRV code for test.(default: shaders/)),")
+  --spirv_dir            Path to SPIRV code for test.(default: shaders/)
+  --out_dir              Path to the output directory.),")
 }
 
 bool prefixFind(std::vector<std::string> list, std::string name) {
@@ -145,7 +150,9 @@
     } else if (c == 'b') {
       disabled_tests = SplitString(std::string(optarg), ':');
     } else if (c == 's') {
-      g_spirv_dir = std::string(optarg);
+      g_spirv_dir = FilePath(optarg);
+    } else if (c == 'o') {
+      g_out_dir = FilePath(optarg);
     } else if (c == '?' || c == 'h') {
       PrintHelp();
       return false;
diff --git a/src/testBase.h b/src/testBase.h
index 424e021..5dfc816 100644
--- a/src/testBase.h
+++ b/src/testBase.h
@@ -8,8 +8,9 @@
 #include <vector>
 #include <vulkan/vulkan.hpp>
 
-#include "utils.h"
+#include "filepath.h"
 #include "vkBase.h"
+#include "utils.h"
 
 namespace vkbench {
 class testBase {
@@ -25,7 +26,7 @@
   virtual double FormatMeasurement(double time) { return time; }
 
   // SaveImage saves the rendered image to file_name.
-  virtual void SaveImage(std::string file_name) const {
+  virtual void SaveImage(FilePath file_path) const {
     throw std::runtime_error("Not Implemented!");
   };
 
@@ -38,7 +39,7 @@
   // Test body. Time spent will be recorded.
   virtual void Run() = 0;
   // Test cleanup after looping Run. Time spent here will be recorded.
-  virtual void Cleanup() {};
+  virtual void Cleanup(){};
   // Free and Destroy any resources allocated during Initialize. Time
   // spent here will not be recorded.
   virtual void Destroy() = 0;
diff --git a/src/utils.cc b/src/utils.cc
index 0aa683b..240dde3 100644
--- a/src/utils.cc
+++ b/src/utils.cc
@@ -14,6 +14,8 @@
 
 #include "utils.h"
 
+extern FilePath g_spirv_dir;
+
 const std::string kTemperatureScript =
     "/usr/local/autotest/bin/temperature.py --maximum";
 
@@ -26,11 +28,11 @@
   LOG("# DateTime: %s", buffer)
 }
 
-std::string readFile(const std::string& filename) {
-  std::ifstream file(filename, std::ios::ate | std::ios::binary);
-
+std::string readShaderFile(const std::string& filename) {
+  FilePath file_path = g_spirv_dir.Append(FilePath(filename));
+  std::ifstream file(file_path.value(), std::ios::ate | std::ios::binary);
   if (!file.is_open()) {
-    throw std::runtime_error("Failed to open " + filename);
+    throw std::runtime_error("Failed to open " + file_path.value());
   }
   DEFER(file.close());
 
@@ -50,14 +52,17 @@
   return device.createShaderModule(create_info);
 }
 
-// SavePPM saves data into filename in PPM format. It assumes data is in
+// SavePPM saves data into file_path in PPM format. It assumes data is in
 // R8G8B8A8_UNORM format.
-void SavePPM(std::string filename,
+void SavePPM(FilePath file_path,
              const char* data,
              uint32_t width,
              uint32_t height,
              vk::DeviceSize row_pitch) {
-  std::ofstream file(filename, std::ios::binary | std::ios::out);
+  FilePath directory = file_path.DirName();
+  CreateDirectory(directory);
+
+  std::ofstream file(file_path.value(), std::ios::binary | std::ios::out);
   DEFER(file.close());
 
   file << "P6\n" << width << "\n" << height << "\n" << 255 << std::endl;
@@ -110,3 +115,19 @@
     return empty_value;
   return !(find(list.begin(), list.end(), std::string(value)) == list.end());
 }
+
+bool check_file_existence(const char* file_path, struct stat* buffer) {
+    struct stat local_buf;
+    bool exist = stat(file_path, &local_buf) == 0;
+    if (buffer && exist)
+        memcpy(buffer, &local_buf, sizeof(local_buf));
+    return exist;
+}
+
+bool check_dir_existence(const char* file_path) {
+    struct stat buffer;
+    bool exist = check_file_existence(file_path, &buffer);
+    if (!exist)
+        return false;
+    return S_ISDIR(buffer.st_mode);
+}
diff --git a/src/utils.h b/src/utils.h
index a6337d2..72453fe 100644
--- a/src/utils.h
+++ b/src/utils.h
@@ -7,17 +7,20 @@
 
 #include <stdarg.h>
 #include <stdio.h>
+#include <sys/stat.h>
 #include <sys/time.h>
 #include <functional>
 #include <string>
 #include <vector>
 #include <vulkan/vulkan.hpp>
 
+#include "filepath.h"
+
 extern int g_verbose;
 void PrintDateTime();
 std::vector<std::string> SplitString(const std::string& kInput, char delimiter);
-std::string readFile(const std::string& filename);
-void SavePPM(std::string, const char*, uint32_t, uint32_t, vk::DeviceSize);
+std::string readShaderFile(const std::string& filename);
+void SavePPM(FilePath, const char*, uint32_t, uint32_t, vk::DeviceSize);
 vk::ShaderModule CreateShaderModule(const vk::Device& device, std::string code);
 
 inline uint64_t GetUTime() {
@@ -29,6 +32,8 @@
 bool IsItemInVector(const std::vector<std::string>& list,
                     const char* value,
                     bool empty_value);
+bool check_file_existence(const char* file_path, struct stat* buffer = NULL);
+bool check_dir_existence(const char* file_path);
 
 inline void DbgPrintf(const char* filename,
                       int line,