Add Vulkan validation layers on Windows

Deploy self-built Vulkan validation layers instead of system installed
one. And it will reuse third_party/angle's Vulkan validation layers if
building with chromium.

Bug: dawn:150
Change-Id: I94e26f7a152fb2a1c39bcb102d60024f4d65eee6
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/11120
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/BUILD.gn b/BUILD.gn
index a5288db..59e3228 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -13,6 +13,7 @@
 # limitations under the License.
 
 import("//build_overrides/build.gni")
+import("//build_overrides/vulkan_validation_layers.gni")
 import("generator/dawn_generator.gni")
 import("scripts/dawn_component.gni")
 import("scripts/dawn_features.gni")
@@ -496,6 +497,12 @@
         "//third_party/fuchsia-sdk:vulkan_validation",
       ]
     }
+    if (dawn_enable_vulkan_validation_layers) {
+      defines = [
+        "DAWN_ENABLE_VULKAN_VALIDATION_LAYERS",
+        "DAWN_VK_DATA_DIR=\"$vulkan_data_subdir\"",
+      ]
+    }
   }
 }
 
@@ -534,6 +541,16 @@
   }
   if (dawn_enable_vulkan) {
     sources += [ "src/dawn_native/vulkan/VulkanBackend.cpp" ]
+
+    if (dawn_enable_vulkan_validation_layers) {
+      data_deps = [
+        "${dawn_vulkan_validation_layers_dir}:vulkan_validation_layers",
+      ]
+      if (!is_android) {
+        data_deps +=
+            [ "${dawn_vulkan_validation_layers_dir}:vulkan_gen_json_files" ]
+      }
+    }
   }
 }
 
@@ -795,6 +812,7 @@
     "src/tests/unittests/RingBufferAllocatorTests.cpp",
     "src/tests/unittests/SerialMapTests.cpp",
     "src/tests/unittests/SerialQueueTests.cpp",
+    "src/tests/unittests/SystemUtilsTests.cpp",
     "src/tests/unittests/ToBackendTests.cpp",
     "src/tests/unittests/validation/BindGroupValidationTests.cpp",
     "src/tests/unittests/validation/BufferValidationTests.cpp",
diff --git a/DEPS b/DEPS
index ed71745..0229c75 100644
--- a/DEPS
+++ b/DEPS
@@ -100,6 +100,18 @@
     'url': '{dawn_git}/clang-format@2451c56cd368676cdb230fd5ad11731ab859f1a3',
     'condition': 'dawn_standalone and checkout_linux',
   },
+
+  # Khronos Vulkan-Headers
+  'third_party/vulkan-headers': {
+    'url': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Headers@5b44df19e040fca0048ab30c553a8c2d2cb9623e',
+    'condition': 'dawn_standalone',
+  },
+
+  # Khronos Vulkan-ValidationLayers
+  'third_party/vulkan-validation-layers': {
+    'url': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-ValidationLayers@9fba37afae13a11bd49ae942bf82e5bf1098e381',
+    'condition': 'dawn_standalone',
+  },
 }
 
 hooks = [
diff --git a/build_overrides/dawn.gni b/build_overrides/dawn.gni
index 4d21a33..968d43b 100644
--- a/build_overrides/dawn.gni
+++ b/build_overrides/dawn.gni
@@ -31,3 +31,4 @@
 dawn_shaderc_dir = "//third_party/shaderc"
 dawn_spirv_tools_dir = "//third_party/SPIRV-Tools"
 dawn_spirv_cross_dir = "//third_party/spirv-cross"
+dawn_vulkan_validation_layers_dir = "//third_party/vulkan-validation-layers"
diff --git a/build_overrides/vulkan_validation_layers.gni b/build_overrides/vulkan_validation_layers.gni
new file mode 100644
index 0000000..d430597
--- /dev/null
+++ b/build_overrides/vulkan_validation_layers.gni
@@ -0,0 +1,24 @@
+# Copyright 2019 The Dawn Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# These are variables that are overridable by projects that include Dawn.
+# The values in this file are the defaults for when we are building from
+# Dawn's repository.
+vulkan_headers_dir = "//third_party/vulkan-headers"
+vvl_spirv_tools_dir = "//third_party/SPIRV-Tools"
+vvl_glslang_dir = "//third_party/glslang"
+
+# Subdirectories for generated files
+vulkan_data_subdir = "vulkandata"
+vulkan_gen_subdir = ""
diff --git a/scripts/dawn_features.gni b/scripts/dawn_features.gni
index aed6c75..ded8e3b 100644
--- a/scripts/dawn_features.gni
+++ b/scripts/dawn_features.gni
@@ -45,3 +45,11 @@
   # compiler, since it is a sub-class of if.
   dawn_enable_cross_reflection = false
 }
+
+# GN does not allow reading a variable defined in the same declare_args().
+# Put them in two separate declare_args() when setting the value of one
+# argument based on another.
+declare_args() {
+  # Uses our built version of Vulkan validation layers
+  dawn_enable_vulkan_validation_layers = dawn_enable_vulkan && is_win
+}
diff --git a/scripts/dawn_overrides_with_defaults.gni b/scripts/dawn_overrides_with_defaults.gni
index ab5a21d..2e69e76 100644
--- a/scripts/dawn_overrides_with_defaults.gni
+++ b/scripts/dawn_overrides_with_defaults.gni
@@ -56,3 +56,7 @@
 if (!defined(dawn_spirv_tools_dir)) {
   dawn_spirv_tools_dir = "//third_party/SPIRV-Tools"
 }
+
+if (!defined(dawn_vulkan_validaion_layers_dir)) {
+  dawn_vulkan_validaion_layers_dir = "//third_party/vulkan-validation-layers"
+}
diff --git a/src/common/BUILD.gn b/src/common/BUILD.gn
index c362b4c..fdc4377 100644
--- a/src/common/BUILD.gn
+++ b/src/common/BUILD.gn
@@ -100,6 +100,8 @@
       "SerialQueue.h",
       "SerialStorage.h",
       "SwapChainUtils.h",
+      "SystemUtils.cpp",
+      "SystemUtils.h",
       "vulkan_platform.h",
       "windows_with_undefs.h",
       "xlib_with_undefs.h",
diff --git a/src/common/SystemUtils.cpp b/src/common/SystemUtils.cpp
new file mode 100644
index 0000000..88fc7d7
--- /dev/null
+++ b/src/common/SystemUtils.cpp
@@ -0,0 +1,117 @@
+// Copyright 2019 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "common/SystemUtils.h"
+
+#if defined(DAWN_PLATFORM_WINDOWS)
+#    include <Windows.h>
+#    include <vector>
+#elif defined(DAWN_PLATFORM_LINUX)
+#    include <limits.h>
+#    include <unistd.h>
+#    include <cstdlib>
+#elif defined(DAWN_PLATFORM_MACOS)
+#    include <mach-o/dyld.h>
+#    include <vector>
+#endif
+
+#include <array>
+
+#if defined(DAWN_PLATFORM_WINDOWS)
+const char* GetPathSeparator() {
+    return "\\";
+}
+
+std::string GetEnvironmentVar(const char* variableName) {
+    // First pass a size of 0 to get the size of variable value.
+    char* tempBuf = nullptr;
+    DWORD result = GetEnvironmentVariableA(variableName, tempBuf, 0);
+    if (result == 0) {
+        return "";
+    }
+
+    // Then get variable value with its actual size.
+    std::vector<char> buffer(result + 1);
+    if (GetEnvironmentVariableA(variableName, buffer.data(), static_cast<DWORD>(buffer.size())) ==
+        0) {
+        return "";
+    }
+    return std::string(buffer.data());
+}
+
+bool SetEnvironmentVar(const char* variableName, const char* value) {
+    return SetEnvironmentVariableA(variableName, value) == TRUE;
+}
+#elif defined(DAWN_PLATFORM_POSIX)
+const char* GetPathSeparator() {
+    return "/";
+}
+
+std::string GetEnvironmentVar(const char* variableName) {
+    char* value = getenv(variableName);
+    return value == nullptr ? "" : std::string(value);
+}
+
+bool SetEnvironmentVar(const char* variableName, const char* value) {
+    return setenv(variableName, value, 1) == 0;
+}
+#else
+#    error "Implement Get/SetEnvironmentVar for your platform."
+#endif
+
+#if defined(DAWN_PLATFORM_WINDOWS)
+std::string GetExecutablePath() {
+    std::array<char, MAX_PATH> executableFileBuf;
+    DWORD executablePathLen = GetModuleFileNameA(nullptr, executableFileBuf.data(),
+                                                 static_cast<DWORD>(executableFileBuf.size()));
+    return executablePathLen > 0 ? std::string(executableFileBuf.data()) : "";
+}
+#elif defined(DAWN_PLATFORM_LINUX)
+std::string GetExecutablePath() {
+    std::array<char, PATH_MAX> path;
+    ssize_t result = readlink("/proc/self/exe", path.data(), PATH_MAX - 1);
+    if (result < 0 || static_cast<size_t>(result) >= PATH_MAX - 1) {
+        return "";
+    }
+
+    path[result] = '\0';
+    return path.data();
+}
+#elif defined(DAWN_PLATFORM_MACOS)
+std::string GetExecutablePath() {
+    uint32_t size = 0;
+    _NSGetExecutablePath(nullptr, &size);
+
+    std::vector<char> buffer(size + 1);
+    if (_NSGetExecutablePath(buffer.data(), &size) != 0) {
+        return "";
+    }
+
+    buffer[size] = '\0';
+    return buffer.data();
+}
+#elif defined(DAWN_PLATFORM_FUCHSIA)
+std::string GetExecutablePath() {
+    // TODO: Implement on Fuchsia
+    return "";
+}
+#else
+#    error "Implement GetExecutablePath for your platform."
+#endif
+
+std::string GetExecutableDirectory() {
+    std::string exePath = GetExecutablePath();
+    size_t lastPathSepLoc = exePath.find_last_of(GetPathSeparator());
+    return lastPathSepLoc != std::string::npos ? exePath.substr(0, lastPathSepLoc + 1) : "";
+}
diff --git a/src/common/SystemUtils.h b/src/common/SystemUtils.h
new file mode 100644
index 0000000..2edf1e3
--- /dev/null
+++ b/src/common/SystemUtils.h
@@ -0,0 +1,27 @@
+// Copyright 2019 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef COMMON_SYSTEMUTILS_H_
+#define COMMON_SYSTEMUTILS_H_
+
+#include "common/Platform.h"
+
+#include <string>
+
+const char* GetPathSeparator();
+std::string GetEnvironmentVar(const char* variableName);
+bool SetEnvironmentVar(const char* variableName, const char* value);
+std::string GetExecutableDirectory();
+
+#endif  // COMMON_SYSTEMUTILS_H_
diff --git a/src/dawn_native/vulkan/BackendVk.cpp b/src/dawn_native/vulkan/BackendVk.cpp
index 398569d..9ac3224 100644
--- a/src/dawn_native/vulkan/BackendVk.cpp
+++ b/src/dawn_native/vulkan/BackendVk.cpp
@@ -14,6 +14,7 @@
 
 #include "dawn_native/vulkan/BackendVk.h"
 
+#include "common/SystemUtils.h"
 #include "dawn_native/Instance.h"
 #include "dawn_native/VulkanBackend.h"
 #include "dawn_native/vulkan/AdapterVk.h"
@@ -62,6 +63,15 @@
     }
 
     MaybeError Backend::Initialize() {
+#if defined(DAWN_ENABLE_VULKAN_VALIDATION_LAYERS)
+        if (GetInstance()->IsBackendValidationEnabled()) {
+            std::string vkDataDir = GetExecutableDirectory() + DAWN_VK_DATA_DIR;
+            if (!SetEnvironmentVar("VK_LAYER_PATH", vkDataDir.c_str())) {
+                return DAWN_DEVICE_LOST_ERROR(std::string("Couldn't set VK_LAYER_PATH with ") +
+                                              vkDataDir);
+            }
+        }
+#endif
         if (!mVulkanLib.Open(kVulkanLibName)) {
             return DAWN_DEVICE_LOST_ERROR(std::string("Couldn't open ") + kVulkanLibName);
         }
diff --git a/src/tests/unittests/SystemUtilsTests.cpp b/src/tests/unittests/SystemUtilsTests.cpp
new file mode 100644
index 0000000..540f4ef
--- /dev/null
+++ b/src/tests/unittests/SystemUtilsTests.cpp
@@ -0,0 +1,41 @@
+// Copyright 2019 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <gtest/gtest.h>
+
+#include "common/SystemUtils.h"
+
+// Tests for GetEnvironmentVar
+TEST(SystemUtilsTests, GetEnvironmentVar) {
+    // Test nonexistent environment variable
+    ASSERT_EQ(GetEnvironmentVar("NonexistentEnvironmentVar"), "");
+}
+
+// Tests for SetEnvironmentVar
+TEST(SystemUtilsTests, SetEnvironmentVar) {
+    // Test new environment variable
+    ASSERT_TRUE(SetEnvironmentVar("EnvironmentVarForTest", "NewEnvironmentVarValue"));
+    ASSERT_EQ(GetEnvironmentVar("EnvironmentVarForTest"), "NewEnvironmentVarValue");
+    // Test override environment variable
+    ASSERT_TRUE(SetEnvironmentVar("EnvironmentVarForTest", "OverrideEnvironmentVarValue"));
+    ASSERT_EQ(GetEnvironmentVar("EnvironmentVarForTest"), "OverrideEnvironmentVarValue");
+}
+
+// Tests for GetExecutableDirectory
+TEST(SystemUtilsTests, GetExecutableDirectory) {
+    // Test returned value is non-empty string
+    ASSERT_NE(GetExecutableDirectory(), "");
+    // Test last charecter in path
+    ASSERT_EQ(GetExecutableDirectory().back(), *GetPathSeparator());
+}