webgpu: enable choosing vulkan backend on MacOS (#9106)

- Add a Configuration struct to the WebGPUPlatform class.  This
   allows for client-side setting of WebGPU backend configurations.
 - Add a configuration for forcing the wgpu backend to pick a
   certain backend.
 - Add Vulkan as a potential backend for WebGPU + MacOS.
 - Locally modify Dawn so that it does not assume only switfshader
   is available for Vulkan on MacOS.
 - Enable vulkan support for Dawn (third_party/dawn/tnt/CMakeLists.txt)
 - Plumb option to pick different WebGPU backend through
   FilamentApp and gltf_viewer.
This commit is contained in:
Powei Feng
2025-08-19 21:50:17 -07:00
committed by GitHub
parent a150fabace
commit 45fcea101f
12 changed files with 130 additions and 26 deletions

View File

@@ -30,7 +30,7 @@ namespace filament::backend {
* A Platform interface, handling the environment-specific concerns, e.g. OS, for creating a WebGPU
* driver (backend).
*/
class WebGPUPlatform final : public Platform {
class WebGPUPlatform : public Platform {
public:
WebGPUPlatform();
~WebGPUPlatform() override = default;
@@ -52,6 +52,14 @@ public:
// either returns a valid device or panics
[[nodiscard]] wgpu::Device requestDevice(wgpu::Adapter const& adapter);
struct Configuration {
wgpu::BackendType forceBackendType = wgpu::BackendType::Undefined;
};
[[nodiscard]] virtual Configuration getConfiguration() const noexcept {
return {};
}
protected:
[[nodiscard]] Driver* createDriver(void* sharedContext,
const Platform::DriverConfig& driverConfig) noexcept override;

View File

@@ -505,6 +505,7 @@ struct AdapterDetailsHash final {
// selects one preferred adapter or panics if none can be found
wgpu::Adapter selectPreferredAdapter(
WebGPUPlatform::Configuration const& configuration,
std::unordered_set<AdapterDetails, AdapterDetailsHash> const& compatibleAdapters) {
// for each unique adapter...
AdapterDetails const* selectedAdapter = nullptr;
@@ -512,6 +513,10 @@ wgpu::Adapter selectPreferredAdapter(
// choose the most desirable adapter that meets the minimum requirements...
for (AdapterDetails const& details: compatibleAdapters) {
if (configuration.forceBackendType != wgpu::BackendType::Undefined &&
configuration.forceBackendType != details.info.backendType) {
continue;
}
if (!adapterMeetsMinimumRequirements(details)) {
continue;
}
@@ -587,7 +592,7 @@ wgpu::Adapter WebGPUPlatform::requestAdapter(wgpu::Surface const& surface) {
}
const std::unordered_set<AdapterDetails, AdapterDetailsHash> compatibleAdapters =
requestCompatibleAdapters(mInstance, requests);
return selectPreferredAdapter(compatibleAdapters);
return selectPreferredAdapter(getConfiguration(), compatibleAdapters);
}
wgpu::Device WebGPUPlatform::requestDevice(wgpu::Adapter const& adapter) {

View File

@@ -38,7 +38,12 @@ std::vector<wgpu::RequestAdapterOptions> WebGPUPlatform::getAdapterOptions() {
constexpr std::array powerPreferences = {
wgpu::PowerPreference::HighPerformance,
wgpu::PowerPreference::LowPower };
constexpr std::array backendTypes = { wgpu::BackendType::Metal };
constexpr std::array backendTypes = {
wgpu::BackendType::Metal,
// To enable software rasterization on MacOS, we need to ensure Vulkan adapters are
// available.
wgpu::BackendType::Vulkan,
};
constexpr std::array forceFallbackAdapters = { false, true };
constexpr size_t totalCombinations =
powerPreferences.size() * backendTypes.size() * forceFallbackAdapters.size();

View File

@@ -141,6 +141,10 @@ include_directories(${PUBLIC_HDR_DIR})
add_library(${TARGET} STATIC ${PUBLIC_HDRS} ${SRCS})
if (FILAMENT_SUPPORTS_WEBGPU)
target_link_libraries(${TARGET} PRIVATE dawncpp_headers)
endif()
target_link_libraries(${TARGET} PUBLIC ${LIBS} filamentapp-resources)
target_include_directories(${TARGET} PUBLIC ${PUBLIC_HDR_DIR})

View File

@@ -37,8 +37,15 @@ struct Config {
int stereoscopicEyeCount = 2;
uint8_t samples = 1;
// Provided to indicate GPU preference for vulkan
// Indicate GPU preference for vulkan
std::string vulkanGPUHint;
// Note that WebGPU has its own enums for backends, but to avoid leaking webgpu headers to
// consumers of FilamentApp, we just overload the Engine::Backend enum.
using WebGPUBackend = filament::Engine::Backend;
// Force a backend for webgpu.
WebGPUBackend forcedWebGPUBackend = WebGPUBackend::DEFAULT;
};
#endif // TNT_FILAMENT_SAMPLE_CONFIG_H

View File

@@ -49,13 +49,18 @@ class ImGuiHelper;
class IBL;
class MeshAssimp;
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
// For customizing the vulkan backend
namespace filament::backend {
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
class VulkanPlatform;
}
#endif
#if defined(FILAMENT_SUPPORTS_WEBGPU)
class WebGPUPlatform;
#endif
}
class FilamentApp {
public:
using SetupCallback = std::function<void(filament::Engine*, filament::View*, filament::Scene*)>;
@@ -261,6 +266,11 @@ private:
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
filament::backend::VulkanPlatform* mVulkanPlatform = nullptr;
#endif
#if defined(FILAMENT_SUPPORTS_WEBGPU)
filament::backend::WebGPUPlatform* mWebGPUPlatform = nullptr;
#endif
};
#endif // TNT_FILAMENT_SAMPLE_FILAMENTAPP_H

View File

@@ -1,4 +1,3 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
@@ -27,6 +26,7 @@
#include <imgui.h>
#include <utils/EntityManager.h>
#include <utils/Logger.h>
#include <utils/Panic.h>
#include <utils/Path.h>
@@ -48,6 +48,10 @@
#include <backend/platforms/VulkanPlatform.h>
#endif
#if defined(FILAMENT_SUPPORTS_WEBGPU)
#include <backend/platforms/WebGPUPlatform.h>
#endif
#include <filagui/ImGuiHelper.h>
#include <filamentapp/Cube.h>
@@ -103,6 +107,36 @@ private:
};
#endif
#if defined(FILAMENT_SUPPORTS_WEBGPU)
class FilamentAppWebGPUPlatform : public WebGPUPlatform {
public:
FilamentAppWebGPUPlatform(Config::WebGPUBackend backend)
: mBackend(backend) {}
virtual WebGPUPlatform::Configuration getConfiguration() const noexcept override {
WebGPUPlatform::Configuration config = {};
switch (mBackend) {
case Config::WebGPUBackend::VULKAN:
config.forceBackendType = wgpu::BackendType::Vulkan;
break;
case Config::WebGPUBackend::METAL:
config.forceBackendType = wgpu::BackendType::Metal;
break;
case Config::WebGPUBackend::DEFAULT:
break;
default:
LOG(ERROR) << "FilamentApp: Unsupported webgpu backend was selected(="
<< (int) mBackend << "). Selection is ignored.";
break;
}
return config;
}
private:
Config::WebGPUBackend const mBackend;
};
#endif
} // anonymous namespace
FilamentApp& FilamentApp::get() {
@@ -532,6 +566,13 @@ void FilamentApp::run(const Config& config, SetupCallback setupCallback,
delete mVulkanPlatform;
}
#endif
#if defined(FILAMENT_SUPPORTS_WEBGPU)
if (mWebGPUPlatform) {
delete mWebGPUPlatform;
}
#endif
}
// RELATIVE_ASSET_PATH is set inside samples/CMakeLists.txt and used to support multi-configuration
@@ -662,21 +703,25 @@ FilamentApp::Window::Window(FilamentApp* filamentApp,
engineConfig.stereoscopicType = Engine::StereoscopicType::NONE;
#endif
Platform* platform = nullptr;
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
if (backend == Engine::Backend::VULKAN) {
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
mFilamentApp->mVulkanPlatform =
new FilamentAppVulkanPlatform(config.vulkanGPUHint.c_str());
return Engine::Builder()
.backend(backend)
.platform(mFilamentApp->mVulkanPlatform)
.featureLevel(config.featureLevel)
.config(&engineConfig)
.build();
#endif
platform = mFilamentApp->mVulkanPlatform =
new FilamentAppVulkanPlatform(config.vulkanGPUHint.c_str());
}
#endif
#if defined(FILAMENT_SUPPORTS_WEBGPU)
if (backend == Engine::Backend::WEBGPU) {
platform = mFilamentApp->mWebGPUPlatform =
new FilamentAppWebGPUPlatform(config.forcedWebGPUBackend);
}
#endif
return Engine::Builder()
.backend(backend)
.featureLevel(config.featureLevel)
.platform(platform)
.config(&engineConfig)
.build();
};

View File

@@ -196,7 +196,9 @@ static void printUsage(char* name) {
" a substring to match against the device name\n\n"
" --screenshot-as-ppm, -d\n"
" export PPM as oppose to TIFF screenshots\n\n"
" --webgpu-backend=<backend>, -w\n"
" You can force WebGPU to select a backend of your choice. Provided that the platform\n"
" supports this backend. (See -a for argument options).\n\n"
);
const std::string from("SHOWCASE");
for (size_t pos = usage.find(from); pos != std::string::npos; pos = usage.find(from, pos)) {
@@ -215,7 +217,7 @@ static std::ifstream::pos_type getFileSize(const char* filename) {
}
static int handleCommandLineArguments(int argc, char* argv[], App* app) {
static constexpr const char* OPTSTR = "ha:f:i:usc:rt:b:evg:d";
static constexpr const char* OPTSTR = "ha:f:i:usc:rt:b:evg:dw:";
static const struct option OPTIONS[] = {
{ "help", no_argument, nullptr, 'h' },
{ "api", required_argument, nullptr, 'a' },
@@ -232,6 +234,7 @@ static int handleCommandLineArguments(int argc, char* argv[], App* app) {
{ "split-view", no_argument, nullptr, 'v' },
{ "vulkan-gpu-hint", required_argument, nullptr, 'g' },
{ "screenshot-as-ppm", no_argument, nullptr, 'd' },
{ "webgpu-backend", required_argument, nullptr, 'w' },
{ nullptr, 0, nullptr, 0 }
};
int opt;
@@ -313,6 +316,10 @@ static int handleCommandLineArguments(int argc, char* argv[], App* app) {
app->screenshotAsPPM = true;
break;
}
case 'w': {
app->config.forcedWebGPUBackend = samples::parseArgumentsForBackend(arg);
break;
}
}
}
if (app->config.headless && app->batchFile.empty()) {

View File

@@ -545,12 +545,6 @@ std::vector<Ref<PhysicalDeviceBase>> Backend::DiscoverPhysicalDevices(
std::vector<Ref<PhysicalDeviceBase>> physicalDevices;
InstanceBase* instance = GetInstance();
for (ICD icd : kICDs) {
#if DAWN_PLATFORM_IS(MACOS)
// On Mac, we don't expect non-Swiftshader Vulkan to be available.
if (icd == ICD::None) {
continue;
}
#endif // DAWN_PLATFORM_IS(MACOS)
if (options->forceFallbackAdapter && icd != ICD::SwiftShader) {
continue;
}

View File

@@ -15,6 +15,7 @@ set(DAWN_GLSLANG_DIR ${EXTERNAL}/glslang/)
set(DAWN_DXC_ENABLE_ASSERTS_IN_NDEBUG OFF)
set(DAWN_BUILD_MONOLITHIC_LIBRARY STATIC)
set(TINT_BUILD_CMD_TOOLS OFF)
set(DAWN_ENABLE_VULKAN ON)
if (FILAMENT_BUILD_FILAMAT)
set(TINT_BUILD_SPV_READER ON)

View File

@@ -18,6 +18,7 @@ cd ..
rm -rf dawn
mv dawn_copy dawn
patch -p2 < dawn/tnt/dawn-generator-CMakeList.patch
patch -p2 < dawn/tnt/remove-vk-macos-restriction.patch
# remove redundant 3rd party dependencies with Filament itself
rm -rf \
dawn/third_party/abseil-cpp \
@@ -36,4 +37,4 @@ version in the new Dawn commit.
Above steps are fragile and can cause compilation or functional breakage. Please be sure to test Filament before uploading your CL.
Make sure to fix any new issues that are caused by this update. dawn/tnt/CMakeLists.txt may need to be edited for
an update.
an update.

View File

@@ -0,0 +1,17 @@
diff --git a/third_party/dawn/src/dawn/native/vulkan/BackendVk.cpp b/third_party/dawn/src/dawn/native/vulkan/BackendVk.cpp
index 4411af890..d0d9d3e26 100644
--- a/third_party/dawn/src/dawn/native/vulkan/BackendVk.cpp
+++ b/third_party/dawn/src/dawn/native/vulkan/BackendVk.cpp
@@ -545,12 +545,6 @@ std::vector<Ref<PhysicalDeviceBase>> Backend::DiscoverPhysicalDevices(
std::vector<Ref<PhysicalDeviceBase>> physicalDevices;
InstanceBase* instance = GetInstance();
for (ICD icd : kICDs) {
-#if DAWN_PLATFORM_IS(MACOS)
- // On Mac, we don't expect non-Swiftshader Vulkan to be available.
- if (icd == ICD::None) {
- continue;
- }
-#endif // DAWN_PLATFORM_IS(MACOS)
if (options->forceFallbackAdapter && icd != ICD::SwiftShader) {
continue;
}