Add runtime search paths to Instance descriptor

So that Chrome can configure Dawn to load SwiftShader from
the bundled/module directory. The shared libraries and ICD
are packaged in a separate directory while dawn::native is
linked statically into the Chrome executable.

Change the Vulkan backend to use these paths for loading
Vulkan.

Bug: chromium:1266550
Change-Id: I40468b481881f6c249694c1c61137bc0c9b8fe76
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/78840
Reviewed-by: Loko Kung <lokokung@google.com>
Commit-Queue: Austin Eng <enga@chromium.org>
diff --git a/dawn.json b/dawn.json
index c18b3c3..f0fea53 100644
--- a/dawn.json
+++ b/dawn.json
@@ -1410,6 +1410,15 @@
         "extensible": "in",
         "members": []
     },
+    "dawn instance descriptor": {
+        "tags": ["dawn", "native"],
+        "category": "structure",
+        "chained": "in",
+        "members": [
+            {"name": "additional runtime search paths count", "type": "uint32_t", "default": 0},
+            {"name": "additional runtime search paths", "type": "char", "annotation": "const*const*", "length": "additional runtime search paths count"}
+        ]
+    },
     "vertex attribute": {
         "category": "structure",
         "extensible": false,
@@ -2378,7 +2387,8 @@
             {"value": 1000, "name": "dawn texture internal usage descriptor", "tags": ["dawn"]},
             {"value": 1001, "name": "primitive depth clamping state", "tags": ["dawn", "emscripten"]},
             {"value": 1002, "name": "dawn toggles device descriptor", "tags": ["dawn", "native"]},
-            {"value": 1003, "name": "dawn encoder internal usage descriptor", "tags": ["dawn"]}
+            {"value": 1003, "name": "dawn encoder internal usage descriptor", "tags": ["dawn"]},
+            {"value": 1004, "name": "dawn instance descriptor", "tags": ["dawn", "native"]}
         ]
     },
     "texture": {
diff --git a/src/common/SystemUtils.cpp b/src/common/SystemUtils.cpp
index 9dc066a..41a7d87 100644
--- a/src/common/SystemUtils.cpp
+++ b/src/common/SystemUtils.cpp
@@ -88,93 +88,119 @@
 #endif
 
 #if defined(DAWN_PLATFORM_WINDOWS)
-std::string GetExecutablePath() {
+std::optional<std::string> GetHModulePath(HMODULE module) {
     std::array<char, MAX_PATH> executableFileBuf;
     DWORD executablePathLen = GetModuleFileNameA(nullptr, executableFileBuf.data(),
                                                  static_cast<DWORD>(executableFileBuf.size()));
-    return executablePathLen > 0 ? std::string(executableFileBuf.data()) : "";
+    if (executablePathLen == 0) {
+        return {};
+    }
+    return executableFileBuf.data();
+}
+std::optional<std::string> GetExecutablePath() {
+    return GetHModulePath(nullptr);
 }
 #elif defined(DAWN_PLATFORM_LINUX)
-std::string GetExecutablePath() {
+std::optional<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 "";
+        return {};
     }
 
     path[result] = '\0';
     return path.data();
 }
 #elif defined(DAWN_PLATFORM_MACOS) || defined(DAWN_PLATFORM_IOS)
-std::string GetExecutablePath() {
+std::optional<std::string> GetExecutablePath() {
     uint32_t size = 0;
     _NSGetExecutablePath(nullptr, &size);
 
     std::vector<char> buffer(size + 1);
     if (_NSGetExecutablePath(buffer.data(), &size) != 0) {
-        return "";
+        return {};
     }
 
     buffer[size] = '\0';
     return buffer.data();
 }
 #elif defined(DAWN_PLATFORM_FUCHSIA)
-std::string GetExecutablePath() {
+std::optional<std::string> GetExecutablePath() {
     // TODO: Implement on Fuchsia
-    return "";
+    return {};
 }
 #elif defined(DAWN_PLATFORM_EMSCRIPTEN)
-std::string GetExecutablePath() {
-    UNREACHABLE();
-    return "";
+std::optional<std::string> GetExecutablePath() {
+    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) : "";
+std::optional<std::string> GetExecutableDirectory() {
+    std::optional<std::string> exePath = GetExecutablePath();
+    if (!exePath) {
+        return {};
+    }
+    size_t lastPathSepLoc = exePath->find_last_of(GetPathSeparator());
+    if (lastPathSepLoc == std::string::npos) {
+        return {};
+    }
+    return exePath->substr(0, lastPathSepLoc + 1);
 }
 
 #if defined(DAWN_PLATFORM_LINUX) || defined(DAWN_PLATFORM_MACOS) || defined(DAWN_PLATFORM_IOS)
-std::string GetModulePath() {
+std::optional<std::string> GetModulePath() {
     static int placeholderSymbol = 0;
     Dl_info dlInfo;
     if (dladdr(&placeholderSymbol, &dlInfo) == 0) {
-        return "";
+        return {};
     }
 
     std::array<char, PATH_MAX> absolutePath;
     if (realpath(dlInfo.dli_fname, absolutePath.data()) == NULL) {
-        return "";
+        return {};
     }
     return absolutePath.data();
 }
 #elif defined(DAWN_PLATFORM_WINDOWS)
-std::string GetModulePath() {
-    UNREACHABLE();
-    return "";
+std::optional<std::string> GetModulePath() {
+    static int placeholderSymbol = 0;
+    HMODULE module = nullptr;
+// GetModuleHandleEx is unavailable on UWP
+#    if defined(DAWN_IS_WINUWP)
+    return {};
+#    else
+    if (!GetModuleHandleExA(
+            GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
+            reinterpret_cast<LPCSTR>(&placeholderSymbol), &module)) {
+        return {};
+    }
+#    endif
+    return GetHModulePath(module);
 }
 #elif defined(DAWN_PLATFORM_FUCHSIA)
-std::string GetModulePath() {
-    UNREACHABLE();
-    return "";
+std::optional<std::string> GetModulePath() {
+    return {};
 }
 #elif defined(DAWN_PLATFORM_EMSCRIPTEN)
-std::string GetModulePath() {
-    UNREACHABLE();
-    return "";
+std::optional<std::string> GetModulePath() {
+    return {};
 }
 #else
 #    error "Implement GetModulePath for your platform."
 #endif
 
-std::string GetModuleDirectory() {
-    std::string modPath = GetModulePath();
-    size_t lastPathSepLoc = modPath.find_last_of(GetPathSeparator());
-    return lastPathSepLoc != std::string::npos ? modPath.substr(0, lastPathSepLoc + 1) : "";
+std::optional<std::string> GetModuleDirectory() {
+    std::optional<std::string> modPath = GetModulePath();
+    if (!modPath) {
+        return {};
+    }
+    size_t lastPathSepLoc = modPath->find_last_of(GetPathSeparator());
+    if (lastPathSepLoc == std::string::npos) {
+        return {};
+    }
+    return modPath->substr(0, lastPathSepLoc + 1);
 }
 
 // ScopedEnvironmentVar
diff --git a/src/common/SystemUtils.h b/src/common/SystemUtils.h
index 875c5aa..b7b9aa4 100644
--- a/src/common/SystemUtils.h
+++ b/src/common/SystemUtils.h
@@ -17,6 +17,7 @@
 
 #include "common/Platform.h"
 
+#include <optional>
 #include <string>
 
 const char* GetPathSeparator();
@@ -25,8 +26,11 @@
 std::pair<std::string, bool> GetEnvironmentVar(const char* variableName);
 bool SetEnvironmentVar(const char* variableName, const char* value);
 // Directories are always returned with a trailing path separator.
-std::string GetExecutableDirectory();
-std::string GetModuleDirectory();
+// May return std::nullopt if the path is too long, there is no current
+// module (statically linked into executable), or the function is not
+// implemented on the platform.
+std::optional<std::string> GetExecutableDirectory();
+std::optional<std::string> GetModuleDirectory();
 
 #ifdef DAWN_PLATFORM_MACOS
 void GetMacOSVersion(int32_t* majorVersion, int32_t* minorVersion = nullptr);
diff --git a/src/dawn_native/BUILD.gn b/src/dawn_native/BUILD.gn
index 1f2e920..5a18b30 100644
--- a/src/dawn_native/BUILD.gn
+++ b/src/dawn_native/BUILD.gn
@@ -331,10 +331,10 @@
     "VertexFormat.cpp",
     "VertexFormat.h",
     "dawn_platform.h",
-    "webgpu_absl_format.cpp",
-    "webgpu_absl_format.h",
     "utils/WGPUHelpers.cpp",
     "utils/WGPUHelpers.h",
+    "webgpu_absl_format.cpp",
+    "webgpu_absl_format.h",
   ]
 
   if (dawn_use_x11) {
@@ -673,6 +673,9 @@
         "//third_party/fuchsia-sdk/sdk:trace_engine",
       ]
     }
+    if (dawn_is_winuwp) {
+      defines += [ "DAWN_IS_WINUWP" ]
+    }
     if (enable_vulkan_validation_layers) {
       defines += [
         "DAWN_ENABLE_VULKAN_VALIDATION_LAYERS",
@@ -681,17 +684,11 @@
     }
     if (enable_vulkan_loader) {
       data_deps += [ "${dawn_vulkan_loader_dir}:libvulkan" ]
-      defines += [ "DAWN_ENABLE_VULKAN_LOADER" ]
     }
     if (use_swiftshader) {
-      data_deps += [
-        "${dawn_swiftshader_dir}/src/Vulkan:icd_file",
-        "${dawn_swiftshader_dir}/src/Vulkan:swiftshader_libvulkan",
-      ]
-      defines += [
-        "DAWN_ENABLE_SWIFTSHADER",
-        "DAWN_SWIFTSHADER_VK_ICD_JSON=\"${swiftshader_icd_file_name}\"",
-      ]
+      data_deps +=
+          [ "${dawn_swiftshader_dir}/src/Vulkan:swiftshader_libvulkan" ]
+      defines += [ "DAWN_ENABLE_SWIFTSHADER" ]
     }
   }
 
diff --git a/src/dawn_native/DawnNative.cpp b/src/dawn_native/DawnNative.cpp
index 498d9b1..2eb033f 100644
--- a/src/dawn_native/DawnNative.cpp
+++ b/src/dawn_native/DawnNative.cpp
@@ -187,7 +187,8 @@
 
     // Instance
 
-    Instance::Instance() : mImpl(InstanceBase::Create()) {
+    Instance::Instance(const WGPUInstanceDescriptor* desc)
+        : mImpl(InstanceBase::Create(reinterpret_cast<const InstanceDescriptor*>(desc))) {
     }
 
     Instance::~Instance() {
diff --git a/src/dawn_native/Instance.cpp b/src/dawn_native/Instance.cpp
index 7b49351..c0f4e1b 100644
--- a/src/dawn_native/Instance.cpp
+++ b/src/dawn_native/Instance.cpp
@@ -17,6 +17,8 @@
 #include "common/Assert.h"
 #include "common/GPUInfo.h"
 #include "common/Log.h"
+#include "common/SystemUtils.h"
+#include "dawn_native/ChainUtils_autogen.h"
 #include "dawn_native/ErrorData.h"
 #include "dawn_native/Surface.h"
 #include "dawn_native/ValidationUtils_autogen.h"
@@ -96,15 +98,36 @@
     // static
     InstanceBase* InstanceBase::Create(const InstanceDescriptor* descriptor) {
         Ref<InstanceBase> instance = AcquireRef(new InstanceBase);
-        if (!instance->Initialize(descriptor)) {
+        static constexpr InstanceDescriptor kDefaultDesc = {};
+        if (descriptor == nullptr) {
+            descriptor = &kDefaultDesc;
+        }
+        if (instance->ConsumedError(instance->Initialize(descriptor))) {
             return nullptr;
         }
         return instance.Detach();
     }
 
     // TODO(crbug.com/dawn/832): make the platform an initialization parameter of the instance.
-    bool InstanceBase::Initialize(const InstanceDescriptor*) {
-        return true;
+    MaybeError InstanceBase::Initialize(const InstanceDescriptor* descriptor) {
+        DAWN_TRY(ValidateSingleSType(descriptor->nextInChain, wgpu::SType::DawnInstanceDescriptor));
+        const DawnInstanceDescriptor* dawnDesc = nullptr;
+        FindInChain(descriptor->nextInChain, &dawnDesc);
+        if (dawnDesc != nullptr) {
+            for (uint32_t i = 0; i < dawnDesc->additionalRuntimeSearchPathsCount; ++i) {
+                mRuntimeSearchPaths.push_back(dawnDesc->additionalRuntimeSearchPaths[i]);
+            }
+        }
+        // Default paths to search are next to the shared library, next to the executable, and
+        // no path (just libvulkan.so).
+        if (auto p = GetModuleDirectory()) {
+            mRuntimeSearchPaths.push_back(std::move(*p));
+        }
+        if (auto p = GetExecutableDirectory()) {
+            mRuntimeSearchPaths.push_back(std::move(*p));
+        }
+        mRuntimeSearchPaths.push_back("");
+        return {};
     }
 
     void InstanceBase::APIRequestAdapter(const RequestAdapterOptions* options,
@@ -386,6 +409,10 @@
         return mDefaultPlatform.get();
     }
 
+    const std::vector<std::string>& InstanceBase::GetRuntimeSearchPaths() const {
+        return mRuntimeSearchPaths;
+    }
+
     const XlibXcbFunctions* InstanceBase::GetOrCreateXlibXcbFunctions() {
 #if defined(DAWN_USE_X11)
         if (mXlibXcbFunctions == nullptr) {
diff --git a/src/dawn_native/Instance.h b/src/dawn_native/Instance.h
index 913b2b6..f269d96 100644
--- a/src/dawn_native/Instance.h
+++ b/src/dawn_native/Instance.h
@@ -76,6 +76,8 @@
         void SetPlatform(dawn::platform::Platform* platform);
         dawn::platform::Platform* GetPlatform();
 
+        const std::vector<std::string>& GetRuntimeSearchPaths() const;
+
         // Get backend-independent libraries that need to be loaded dynamically.
         const XlibXcbFunctions* GetOrCreateXlibXcbFunctions();
 
@@ -89,7 +91,7 @@
         InstanceBase(const InstanceBase& other) = delete;
         InstanceBase& operator=(const InstanceBase& other) = delete;
 
-        bool Initialize(const InstanceDescriptor* descriptor);
+        MaybeError Initialize(const InstanceDescriptor* descriptor);
 
         // Lazily creates connections to all backends that have been compiled.
         void EnsureBackendConnection(wgpu::BackendType backendType);
@@ -99,6 +101,8 @@
         ResultOrError<Ref<AdapterBase>> RequestAdapterInternal(
             const RequestAdapterOptions* options);
 
+        std::vector<std::string> mRuntimeSearchPaths;
+
         BackendsBitset mBackendsConnected;
 
         bool mDiscoveredDefaultAdapters = false;
diff --git a/src/dawn_native/vulkan/BackendVk.cpp b/src/dawn_native/vulkan/BackendVk.cpp
index aeba2aa..79f8706 100644
--- a/src/dawn_native/vulkan/BackendVk.cpp
+++ b/src/dawn_native/vulkan/BackendVk.cpp
@@ -172,55 +172,43 @@
         ScopedEnvironmentVar vkICDFilenames;
         ScopedEnvironmentVar vkLayerPath;
 
-#if defined(DAWN_ENABLE_VULKAN_LOADER)
-        // If enabled, we use our own built Vulkan loader by specifying an absolute path to the
-        // shared library. Note that when we are currently getting the absolute path for the custom
-        // loader by getting the path to the dawn native library and traversing relative from there.
-        // This has implications for dawn tests because some of them are linking statically to
-        // dawn_native which means the "module" is actually the test as well. If the directory
-        // location of the tests change w.r.t the shared lib then this may break. Essentially we are
-        // assuming that our custom built Vulkan loader will always be in the same directory as the
-        // shared dawn native library and all test binaries that link statically.
-        const std::string resolvedVulkanLibPath = GetModuleDirectory() + kVulkanLibName;
-#else
-        const std::string resolvedVulkanLibPath = kVulkanLibName;
-#endif  // defined(DAWN_ENABLE_VULKAN_LOADER)
+        const std::vector<std::string>& searchPaths = instance->GetRuntimeSearchPaths();
+
+        auto CommaSeparatedResolvedSearchPaths = [&](const char* name) {
+            std::string list;
+            bool first = true;
+            for (const std::string& path : searchPaths) {
+                if (!first) {
+                    list += ", ";
+                }
+                first = false;
+                list += (path + name);
+            }
+            return list;
+        };
+
+        auto LoadVulkan = [&](const char* libName) -> MaybeError {
+            for (const std::string& path : searchPaths) {
+                std::string resolvedPath = path + libName;
+                if (mVulkanLib.Open(resolvedPath)) {
+                    return {};
+                }
+            }
+            return DAWN_FORMAT_INTERNAL_ERROR("Couldn't load Vulkan. Searched %s.",
+                                              CommaSeparatedResolvedSearchPaths(libName));
+        };
 
         switch (icd) {
             case ICD::None: {
-                if (!mVulkanLib.Open(resolvedVulkanLibPath)) {
-                    return DAWN_FORMAT_INTERNAL_ERROR("Couldn't load %s.", resolvedVulkanLibPath);
-                }
+                DAWN_TRY(LoadVulkan(kVulkanLibName));
+                // Succesfully loaded driver; break.
                 break;
             }
             case ICD::SwiftShader: {
 #if defined(DAWN_ENABLE_SWIFTSHADER)
-                // First try to load the system Vulkan driver, if that fails, try to load with
-                // Swiftshader. Note: The system driver could potentially be Swiftshader if it was
-                // installed.
-#    if defined(DAWN_SWIFTSHADER_VK_ICD_JSON)
-                if (mVulkanLib.Open(resolvedVulkanLibPath)) {
-                    std::string fullSwiftshaderICDPath =
-                        GetExecutableDirectory() + DAWN_SWIFTSHADER_VK_ICD_JSON;
-                    if (!vkICDFilenames.Set("VK_ICD_FILENAMES", fullSwiftshaderICDPath.c_str())) {
-                        return DAWN_FORMAT_INTERNAL_ERROR("Couldn't set VK_ICD_FILENAMES to %s.",
-                                                          fullSwiftshaderICDPath);
-                    }
-                    // Succesfully loaded driver and set VK_ICD_FILENAMES.
-                    break;
-                } else
-#    endif  // defined(DAWN_SWIFTSHADER_VK_ICD_JSON)
-            // Fallback to loading SwiftShader directly.
-                    if (mVulkanLib.Open(kSwiftshaderLibName)) {
-                    // Succesfully loaded SwiftShader.
-                    break;
-                }
-                return DAWN_FORMAT_INTERNAL_ERROR(
-                    "Failed to load SwiftShader. DAWN_SWIFTSHADER_VK_ICD_JSON was not defined and "
-                    "could not load %s.",
-                    kSwiftshaderLibName);
+                DAWN_TRY(LoadVulkan(kSwiftshaderLibName));
+                break;
 #endif  // defined(DAWN_ENABLE_SWIFTSHADER)
-
                 // ICD::SwiftShader should not be passed if SwiftShader is not enabled.
                 UNREACHABLE();
             }
@@ -228,7 +216,8 @@
 
         if (instance->IsBackendValidationEnabled()) {
 #if defined(DAWN_ENABLE_VULKAN_VALIDATION_LAYERS)
-            std::string vkDataDir = GetExecutableDirectory() + DAWN_VK_DATA_DIR;
+            auto execDir = GetExecutableDirectory();
+            std::string vkDataDir = execDir.value_or("") + DAWN_VK_DATA_DIR;
             if (!vkLayerPath.Set("VK_LAYER_PATH", vkDataDir.c_str())) {
                 return DAWN_INTERNAL_ERROR("Couldn't set VK_LAYER_PATH");
             }
diff --git a/src/include/dawn_native/DawnNative.h b/src/include/dawn_native/DawnNative.h
index 4850d8c..9f6e3c2 100644
--- a/src/include/dawn_native/DawnNative.h
+++ b/src/include/dawn_native/DawnNative.h
@@ -136,7 +136,7 @@
     // for this instance.
     class DAWN_NATIVE_EXPORT Instance {
       public:
-        Instance();
+        explicit Instance(const WGPUInstanceDescriptor* desc = nullptr);
         ~Instance();
 
         Instance(const Instance& other) = delete;
diff --git a/src/tests/unittests/SystemUtilsTests.cpp b/src/tests/unittests/SystemUtilsTests.cpp
index e754701..d60c61d 100644
--- a/src/tests/unittests/SystemUtilsTests.cpp
+++ b/src/tests/unittests/SystemUtilsTests.cpp
@@ -40,10 +40,12 @@
 
 // Tests for GetExecutableDirectory
 TEST(SystemUtilsTests, GetExecutableDirectory) {
+    auto dir = GetExecutableDirectory();
     // Test returned value is non-empty string
-    EXPECT_NE(GetExecutableDirectory(), "");
-    // Test last charecter in path
-    EXPECT_EQ(GetExecutableDirectory().back(), *GetPathSeparator());
+    EXPECT_NE(dir, std::optional{std::string("")});
+    ASSERT_NE(dir, std::nullopt);
+    // Test last character in path
+    EXPECT_EQ(dir->back(), *GetPathSeparator());
 }
 
 // Tests for ScopedEnvironmentVar