From c95f9ed7e0fa8632d7acb17d27e72ffbb6620996 Mon Sep 17 00:00:00 2001 From: Andy Hovingh <6198728+AndyHovingh@users.noreply.github.com> Date: Sun, 2 Mar 2025 11:47:26 -0600 Subject: [PATCH] WebGPU: backend components created and conditionally logged Initial groundwork in creating WebGPU backend components, namely the instance, adapter, device, and queue. If configured to do so, the backend will print out details about these components. The samples/hellotriangle.cpp was slightly modified to include a webgpu option which allows for exercising the above, but does not yet draw anything to the screen/window. NOTE: This has only been sanity tested with hello triangle on Mac OS and the Android emulator at this time, NOT IOS, Windows, or Linux yet. --- filament/backend/CMakeLists.txt | 28 +- .../backend/platforms/WebGPUPlatform.h | 59 +++ filament/backend/src/PlatformFactory.cpp | 2 +- filament/backend/src/webgpu/WebGPUConstants.h | 66 +++ filament/backend/src/webgpu/WebGPUDriver.cpp | 427 +++++++++++++++++- filament/backend/src/webgpu/WebGPUDriver.h | 51 ++- .../backend/src/webgpu/WebGPUPlatform.cpp | 36 -- filament/backend/src/webgpu/WebGPUPlatform.h | 42 -- .../src/webgpu/platform/WebGPUPlatform.cpp | 159 +++++++ .../webgpu/platform/WebGPUPlatformAndroid.cpp | 43 ++ .../webgpu/platform/WebGPUPlatformApple.mm | 81 ++++ .../webgpu/platform/WebGPUPlatformLinux.cpp | 159 +++++++ .../webgpu/platform/WebGPUPlatformWindows.cpp | 53 +++ samples/hellotriangle.cpp | 11 +- 14 files changed, 1111 insertions(+), 106 deletions(-) create mode 100644 filament/backend/include/backend/platforms/WebGPUPlatform.h create mode 100644 filament/backend/src/webgpu/WebGPUConstants.h delete mode 100644 filament/backend/src/webgpu/WebGPUPlatform.cpp delete mode 100644 filament/backend/src/webgpu/WebGPUPlatform.h create mode 100644 filament/backend/src/webgpu/platform/WebGPUPlatform.cpp create mode 100644 filament/backend/src/webgpu/platform/WebGPUPlatformAndroid.cpp create mode 100644 filament/backend/src/webgpu/platform/WebGPUPlatformApple.mm create mode 100644 filament/backend/src/webgpu/platform/WebGPUPlatformLinux.cpp create mode 100644 filament/backend/src/webgpu/platform/WebGPUPlatformWindows.cpp diff --git a/filament/backend/CMakeLists.txt b/filament/backend/CMakeLists.txt index b3539ad2e3..6f66f1f457 100644 --- a/filament/backend/CMakeLists.txt +++ b/filament/backend/CMakeLists.txt @@ -242,11 +242,31 @@ endif() if (FILAMENT_SUPPORTS_WEBGPU) list(APPEND SRCS + include/backend/platforms/WebGPUPlatform.h + src/webgpu/platform/WebGPUPlatform.cpp + src/webgpu/WebGPUConstants.h src/webgpu/WebGPUDriver.cpp src/webgpu/WebGPUDriver.h - src/webgpu/WebGPUPlatform.cpp - src/webgpu/WebGPUPlatform.h ) + if (WIN32) + list(APPEND SRCS src/webgpu/platform/WebGPUPlatformWindows.cpp) + elseif (LINUX) + list(APPEND SRCS src/webgpu/platform/WebGPUPlatformLinux.cpp) + elseif (APPLE OR IOS) + list(APPEND SRCS src/webgpu/platform/WebGPUPlatformApple.mm) + elseif (ANDROID) + list(APPEND SRCS src/webgpu/platform/WebGPUPlatformAndroid.cpp) + endif() + + if (TNT_DEV) + set(FILAMENT_WEBGPU_IMMEDIATE_ERROR_HANDLING_DEFAULT ON) + else() + set(FILAMENT_WEBGPU_IMMEDIATE_ERROR_HANDLING_DEFAULT OFF) + endif() + + option(FILAMENT_WEBGPU_IMMEDIATE_ERROR_HANDLING + "Enable immediate_error_handling for the WebGPU backend Dawn implementation" + ${FILAMENT_WEBGPU_IMMEDIATE_ERROR_HANDLING_DEFAULT}) endif() if (ANDROID) @@ -433,6 +453,10 @@ if (FILAMENT_SUPPORTS_METAL) target_compile_definitions(${TARGET} PRIVATE $<$:FILAMENT_METAL_PROFILING>) endif() +if (FILAMENT_SUPPORTS_WEBGPU) + target_compile_definitions(${TARGET} PRIVATE $<$:FILAMENT_WEBGPU_IMMEDIATE_ERROR_HANDLING>) +endif() + target_link_libraries(${TARGET} PRIVATE ${OSMESA_LINKER_FLAGS} $<$,$>:${LINUX_LINKER_OPTIMIZATION_FLAGS}> diff --git a/filament/backend/include/backend/platforms/WebGPUPlatform.h b/filament/backend/include/backend/platforms/WebGPUPlatform.h new file mode 100644 index 0000000000..8453bace32 --- /dev/null +++ b/filament/backend/include/backend/platforms/WebGPUPlatform.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * 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 TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORM_H +#define TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORM_H + +#include + +#include + +#include + +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 { +public: + WebGPUPlatform(); + ~WebGPUPlatform() override = default; + + [[nodiscard]] int getOSVersion() const noexcept final { return 0; } + + [[nodiscard]] wgpu::Instance& getInstance() noexcept { return mInstance; } + + // either returns a valid surface or panics + [[nodiscard]] wgpu::Surface createSurface(void* nativeWindow, uint64_t flags); + // either returns a valid adapter or panics + [[nodiscard]] wgpu::Adapter requestAdapter(wgpu::Surface const& surface); + // either returns a valid device or panics + [[nodiscard]] wgpu::Device requestDevice(wgpu::Adapter const& adapter); + +protected: + [[nodiscard]] Driver* createDriver(void* sharedContext, + const Platform::DriverConfig& driverConfig) noexcept override; + +private: + // we may consider having the driver own this in the future + wgpu::Instance mInstance; +}; + +}// namespace filament::backend + +#endif// TNT_FILAMENT_BACKEND_PLATFORMS_WEBGPUPLATFORM_H diff --git a/filament/backend/src/PlatformFactory.cpp b/filament/backend/src/PlatformFactory.cpp index c4a1a7ea6d..bf1ec35176 100644 --- a/filament/backend/src/PlatformFactory.cpp +++ b/filament/backend/src/PlatformFactory.cpp @@ -66,7 +66,7 @@ filament::backend::Platform* createDefaultMetalPlatform(); #include "noop/PlatformNoop.h" #if defined(FILAMENT_SUPPORTS_WEBGPU) - #include "webgpu/WebGPUPlatform.h" + #include "backend/platforms/WebGPUPlatform.h" #endif namespace filament::backend { diff --git a/filament/backend/src/webgpu/WebGPUConstants.h b/filament/backend/src/webgpu/WebGPUConstants.h new file mode 100644 index 0000000000..fbd01066f6 --- /dev/null +++ b/filament/backend/src/webgpu/WebGPUConstants.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * 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 TNT_FILAMENT_BACKEND_WEBGPUCONSTANTS_H +#define TNT_FILAMENT_BACKEND_WEBGPUCONSTANTS_H + +#include + +#include + +// FWGPU is short for Filament WebGPU + +// turn on runtime validation, namely for debugging, that would normally not run (for release) +#define FWGPU_DEBUG_VALIDATION 0x00000001 +// print/log system component details to the console, e.g. about the +// instance, surface, adapter, device, etc. +#define FWGPU_PRINT_SYSTEM 0x00000002 + +// Set this to enable logging "only" to one output stream. This is useful in the case where we want +// to debug with print statements and want ordered logging (e.g slog.i and slog.e will not appear in +// order of calls). +#define FWGPU_DEBUG_FORCE_LOG_TO_I 0x00000004 + +// Useful default combinations +#define FWGPU_DEBUG_EVERYTHING 0xFFFFFFFF + +#if defined(FILAMENT_BACKEND_DEBUG_FLAG) + #define FWGPU_DEBUG_FORWARDED_FLAG (FILAMENT_BACKEND_DEBUG_FLAG & FWGPU_DEBUG_EVERYTHING) +#else + #define FWGPU_DEBUG_FORWARDED_FLAG 0 +#endif + +#ifndef NDEBUG + #define FWGPU_DEBUG_FLAGS FWGPU_DEBUG_FORWARDED_FLAG +#else + #define FWGPU_DEBUG_FLAGS 0 +#endif + +#define FWGPU_ENABLED(flags) (((FWGPU_DEBUG_FLAGS) & (flags)) == (flags)) + +#if FWGPU_ENABLED(FWGPU_DEBUG_FORCE_LOG_TO_I) + #define FWGPU_LOGI (utils::slog.i) + #define FWGPU_LOGD FWGPU_LOGI + #define FWGPU_LOGE FWGPU_LOGI + #define FWGPU_LOGW FWGPU_LOGI +#else + #define FWGPU_LOGE (utils::slog.e) + #define FWGPU_LOGW (utils::slog.w) + #define FWGPU_LOGD (utils::slog.d) + #define FWGPU_LOGI (utils::slog.i) +#endif + +#endif// TNT_FILAMENT_BACKEND_WEBGPUCONSTANTS_H diff --git a/filament/backend/src/webgpu/WebGPUDriver.cpp b/filament/backend/src/webgpu/WebGPUDriver.cpp index 1efd2d87cc..f399753dc5 100644 --- a/filament/backend/src/webgpu/WebGPUDriver.cpp +++ b/filament/backend/src/webgpu/WebGPUDriver.cpp @@ -14,21 +14,254 @@ * limitations under the License. */ +#include "webgpu/WebGPUDriver.h" + +#include "webgpu/WebGPUConstants.h" +#include + +#include "CommandStreamDispatcher.h" +#include "DriverBase.h" +#include "private/backend/Dispatcher.h" #include #include -#include "webgpu/WebGPUDriver.h" -#include "CommandStreamDispatcher.h" +#include +#include +#include -#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include namespace filament::backend { -Driver* WebGPUDriver::create() { - return new WebGPUDriver(); +namespace { + +#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM) +void printInstanceDetails(wgpu::Instance const& instance) { + wgpu::SupportedWGSLLanguageFeatures supportedWGSLLanguageFeatures{}; + if (!instance.GetWGSLLanguageFeatures(&supportedWGSLLanguageFeatures)) { + FWGPU_LOGW << "Failed to get WebGPU instance supported WGSL language features" + << utils::io::endl; + } else { + FWGPU_LOGI << "WebGPU instance supported WGSL language features (" + << supportedWGSLLanguageFeatures.featureCount << "):" << utils::io::endl; + if (supportedWGSLLanguageFeatures.featureCount > 0 && + supportedWGSLLanguageFeatures.features != nullptr) { + std::for_each(supportedWGSLLanguageFeatures.features, + supportedWGSLLanguageFeatures.features + + supportedWGSLLanguageFeatures.featureCount, + [](wgpu::WGSLLanguageFeatureName const featureName) { + std::stringstream nameStream{}; + nameStream << featureName; + FWGPU_LOGI << " " << nameStream.str() << utils::io::endl; + }); + } + } +} +#endif + +#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM) +void printLimit(std::string_view name, const std::variant value) { + FWGPU_LOGI << " " << name.data() << ": "; + bool undefined = true; + if (std::holds_alternative(value)) { + if (std::get(value) != WGPU_LIMIT_U32_UNDEFINED) { + undefined = false; + FWGPU_LOGI << std::get(value); + } + } else if (std::holds_alternative(value)) { + if (std::get(value) != WGPU_LIMIT_U64_UNDEFINED) { + undefined = false; + FWGPU_LOGI << std::get(value); + } + } + if (undefined) { + FWGPU_LOGI << "UNDEFINED"; + } + FWGPU_LOGI << utils::io::endl; +} +#endif + +#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM) +void printLimits(wgpu::Limits const& limits) { + printLimit("maxTextureDimension1D", limits.maxTextureDimension1D); + printLimit("maxTextureDimension2D", limits.maxTextureDimension2D); + printLimit("maxTextureDimension3D", limits.maxTextureDimension3D); + printLimit("maxTextureArrayLayers", limits.maxTextureArrayLayers); + printLimit("maxBindGroups", limits.maxBindGroups); + printLimit("maxBindGroupsPlusVertexBuffers", limits.maxBindGroupsPlusVertexBuffers); + printLimit("maxBindingsPerBindGroup", limits.maxBindingsPerBindGroup); + printLimit("maxDynamicUniformBuffersPerPipelineLayout", + limits.maxDynamicUniformBuffersPerPipelineLayout); + printLimit("maxDynamicStorageBuffersPerPipelineLayout", + limits.maxDynamicStorageBuffersPerPipelineLayout); + printLimit("maxSampledTexturesPerShaderStage", limits.maxSampledTexturesPerShaderStage); + printLimit("maxSamplersPerShaderStage", limits.maxSamplersPerShaderStage); + printLimit("maxStorageBuffersPerShaderStage", limits.maxStorageBuffersPerShaderStage); + printLimit("maxStorageTexturesPerShaderStage", limits.maxStorageTexturesPerShaderStage); + printLimit("maxUniformBuffersPerShaderStage", limits.maxUniformBuffersPerShaderStage); + printLimit("maxUniformBufferBindingSize", limits.maxUniformBufferBindingSize); + printLimit("maxStorageBufferBindingSize", limits.maxStorageBufferBindingSize); + printLimit("minUniformBufferOffsetAlignment", limits.minUniformBufferOffsetAlignment); + printLimit("minStorageBufferOffsetAlignment", limits.minStorageBufferOffsetAlignment); + printLimit("maxVertexBuffers", limits.maxVertexBuffers); + printLimit("maxBufferSize", limits.maxBufferSize); + printLimit("maxVertexAttributes", limits.maxVertexAttributes); + printLimit("maxVertexBufferArrayStride", limits.maxVertexBufferArrayStride); + printLimit("maxInterStageShaderComponents", limits.maxInterStageShaderComponents); + printLimit("maxInterStageShaderVariables", limits.maxInterStageShaderVariables); + printLimit("maxColorAttachments", limits.maxColorAttachments); + printLimit("maxColorAttachmentBytesPerSample", limits.maxColorAttachmentBytesPerSample); + printLimit("maxComputeWorkgroupStorageSize", limits.maxComputeWorkgroupStorageSize); + printLimit("maxComputeInvocationsPerWorkgroup", limits.maxComputeInvocationsPerWorkgroup); + printLimit("maxComputeWorkgroupSizeX", limits.maxComputeWorkgroupSizeX); + printLimit("maxComputeWorkgroupSizeY", limits.maxComputeWorkgroupSizeY); + printLimit("maxComputeWorkgroupSizeZ", limits.maxComputeWorkgroupSizeZ); + printLimit("maxComputeWorkgroupsPerDimension", limits.maxComputeWorkgroupsPerDimension); + printLimit("maxStorageBuffersInVertexStage", limits.maxStorageBuffersInVertexStage); + printLimit("maxStorageTexturesInVertexStage", limits.maxStorageTexturesInVertexStage); + printLimit("maxStorageBuffersInFragmentStage", limits.maxStorageBuffersInFragmentStage); + printLimit("maxStorageTexturesInFragmentStage", limits.maxStorageTexturesInFragmentStage); +} +#endif + +#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM) +void printAdapterDetails(wgpu::Adapter const& adapter) { + wgpu::DawnAdapterPropertiesPowerPreference powerPreferenceProperties{}; + wgpu::AdapterInfo adapterInfo{}; + adapterInfo.nextInChain = &powerPreferenceProperties; + if (!adapter.GetInfo(&adapterInfo)) { + FWGPU_LOGW << "Failed to get WebGPU adapter info" << utils::io::endl; + } else { + std::stringstream backendTypeStream{}; + backendTypeStream << adapterInfo.backendType; + std::stringstream adapterTypeStream{}; + adapterTypeStream << adapterInfo.adapterType; + std::stringstream powerPreferenceStream{}; + powerPreferenceStream << powerPreferenceProperties.powerPreference; + FWGPU_LOGI << "WebGPU adapter info:" << utils::io::endl; + FWGPU_LOGI << " vendor: " << adapterInfo.vendor.data << utils::io::endl; + FWGPU_LOGI << " architecture: " << adapterInfo.architecture.data << utils::io::endl; + FWGPU_LOGI << " device: " << adapterInfo.device.data << utils::io::endl; + FWGPU_LOGI << " description: " << adapterInfo.description.data << utils::io::endl; + FWGPU_LOGI << " backend type: " << backendTypeStream.str().data() << utils::io::endl; + FWGPU_LOGI << " adapter type: " << adapterTypeStream.str().data() << utils::io::endl; + FWGPU_LOGI << " device ID: " << adapterInfo.deviceID << utils::io::endl; + FWGPU_LOGI << " vendor ID: " << adapterInfo.vendorID << utils::io::endl; + FWGPU_LOGI << " subgroup min size: " << adapterInfo.subgroupMinSize << utils::io::endl; + FWGPU_LOGI << " subgroup max size: " << adapterInfo.subgroupMaxSize << utils::io::endl; + FWGPU_LOGI << " compatibility mode: " << bool(adapterInfo.compatibilityMode) + << utils::io::endl; + FWGPU_LOGI << " power preference: " << powerPreferenceStream.str() << utils::io::endl; + } + wgpu::SupportedFeatures supportedFeatures{}; + adapter.GetFeatures(&supportedFeatures); + FWGPU_LOGI << "WebGPU adapter supported features (" << supportedFeatures.featureCount + << "):" << utils::io::endl; + if (supportedFeatures.featureCount > 0 && supportedFeatures.features != nullptr) { + std::for_each(supportedFeatures.features, + supportedFeatures.features + supportedFeatures.featureCount, + [](wgpu::FeatureName const name) { + std::stringstream nameStream{}; + nameStream << name; + FWGPU_LOGI << " " << nameStream.str().data() << utils::io::endl; + }); + } + wgpu::SupportedLimits supportedLimits{}; + if (!adapter.GetLimits(&supportedLimits)) { + FWGPU_LOGW << "Failed to get WebGPU adapter supported limits" << utils::io::endl; + } else { + FWGPU_LOGI << "WebGPU adapter supported limits:" << utils::io::endl; + printLimits(supportedLimits.limits); + } +} +#endif + +#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM) +void printSurfaceCapabilitiesDetails(wgpu::SurfaceCapabilities const& capabilities) { + std::stringstream usages_stream{}; + usages_stream << capabilities.usages; + FWGPU_LOGI << "WebGPU surface capabilities:" << utils::io::endl; + FWGPU_LOGI << " surface usages: " << usages_stream.str().data() << utils::io::endl; + FWGPU_LOGI << " surface formats (" << capabilities.formatCount << "):" << utils::io::endl; + if (capabilities.formatCount > 0 && capabilities.formats != nullptr) { + std::for_each(capabilities.formats, capabilities.formats + capabilities.formatCount, + [](wgpu::TextureFormat const format) { + std::stringstream format_stream{}; + format_stream << format; + FWGPU_LOGI << " " << format_stream.str().data() << utils::io::endl; + }); + } + FWGPU_LOGI << " surface present modes (" << capabilities.presentModeCount + << "):" << utils::io::endl; + if (capabilities.presentModeCount > 0 && capabilities.presentModes != nullptr) { + std::for_each(capabilities.presentModes, + capabilities.presentModes + capabilities.presentModeCount, + [](wgpu::PresentMode const presentMode) { + std::stringstream present_mode_stream{}; + present_mode_stream << presentMode; + FWGPU_LOGI << " " << present_mode_stream.str().data() << utils::io::endl; + }); + } + FWGPU_LOGI << " surface alpha modes (" << capabilities.alphaModeCount + << "):" << utils::io::endl; + if (capabilities.alphaModeCount > 0 && capabilities.alphaModes != nullptr) { + std::for_each(capabilities.alphaModes, + capabilities.alphaModes + capabilities.alphaModeCount, + [](wgpu::CompositeAlphaMode const alphaMode) { + std::stringstream alpha_mode_stream{}; + alpha_mode_stream << alphaMode; + FWGPU_LOGI << " " << alpha_mode_stream.str().data() << utils::io::endl; + }); + } +} +#endif + +#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM) +void printDeviceDetails(wgpu::Device const& device) { + wgpu::SupportedFeatures supportedFeatures{}; + device.GetFeatures(&supportedFeatures); + FWGPU_LOGI << "WebGPU device supported features (" << supportedFeatures.featureCount + << "):" << utils::io::endl; + if (supportedFeatures.featureCount > 0 && supportedFeatures.features != nullptr) { + std::for_each(supportedFeatures.features, + supportedFeatures.features + supportedFeatures.featureCount, + [](wgpu::FeatureName const name) { + std::stringstream nameStream{}; + nameStream << name; + FWGPU_LOGI << " " << nameStream.str().data() << utils::io::endl; + }); + } + wgpu::SupportedLimits supportedLimits{}; + if (!device.GetLimits(&supportedLimits)) { + FWGPU_LOGW << "Failed to get WebGPU supported device limits" << utils::io::endl; + } else { + FWGPU_LOGI << "WebGPU device supported limits:" << utils::io::endl; + printLimits(supportedLimits.limits); + } +} +#endif + +}// namespace + +Driver* WebGPUDriver::create(WebGPUPlatform& platform) noexcept { + return new WebGPUDriver(platform); } -WebGPUDriver::WebGPUDriver() noexcept = default; +WebGPUDriver::WebGPUDriver(WebGPUPlatform& platform) noexcept + : mPlatform(platform) { +#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM) + printInstanceDetails(mPlatform.getInstance()); +#endif +} WebGPUDriver::~WebGPUDriver() noexcept = default; @@ -123,6 +356,188 @@ void WebGPUDriver::destroyDescriptorSetLayout(Handle tqh) void WebGPUDriver::destroyDescriptorSet(Handle tqh) { } +Handle WebGPUDriver::createSwapChainS() noexcept { + return Handle((Handle::HandleId) mNextFakeHandle++); +} + +Handle WebGPUDriver::createSwapChainHeadlessS() noexcept { + return Handle((Handle::HandleId) mNextFakeHandle++); +} + +Handle WebGPUDriver::createTextureS() noexcept { + return Handle((Handle::HandleId) mNextFakeHandle++); +} + +Handle WebGPUDriver::importTextureS() noexcept { + return Handle((Handle::HandleId) mNextFakeHandle++); +} + +Handle WebGPUDriver::createProgramS() noexcept { + return Handle((Handle::HandleId) mNextFakeHandle++); +} + +Handle WebGPUDriver::createFenceS() noexcept { + return Handle((Handle::HandleId) mNextFakeHandle++); +} + +Handle WebGPUDriver::createTimerQueryS() noexcept { + return Handle((Handle::HandleId) mNextFakeHandle++); +} + +Handle WebGPUDriver::createIndexBufferS() noexcept { + return Handle((Handle::HandleId) mNextFakeHandle++); +} + +Handle WebGPUDriver::createTextureViewS() noexcept { + return Handle((Handle::HandleId) mNextFakeHandle++); +} + +Handle WebGPUDriver::createBufferObjectS() noexcept { + return Handle((Handle::HandleId) mNextFakeHandle++); +} + +Handle WebGPUDriver::createRenderTargetS() noexcept { + return Handle((Handle::HandleId) mNextFakeHandle++); +} + +Handle WebGPUDriver::createVertexBufferS() noexcept { + return Handle((Handle::HandleId) mNextFakeHandle++); +} + +Handle WebGPUDriver::createDescriptorSetS() noexcept { + return Handle((Handle::HandleId) mNextFakeHandle++); +} + +Handle WebGPUDriver::createRenderPrimitiveS() noexcept { + return Handle((Handle::HandleId) mNextFakeHandle++); +} + +Handle WebGPUDriver::createVertexBufferInfoS() noexcept { + return Handle((Handle::HandleId) mNextFakeHandle++); +} + +Handle WebGPUDriver::createTextureViewSwizzleS() noexcept { + return Handle((Handle::HandleId) mNextFakeHandle++); +} + +Handle WebGPUDriver::createDefaultRenderTargetS() noexcept { + return Handle((Handle::HandleId) mNextFakeHandle++); +} + +Handle WebGPUDriver::createDescriptorSetLayoutS() noexcept { + return Handle( + (Handle::HandleId) mNextFakeHandle++); +} + +Handle WebGPUDriver::createTextureExternalImageS() noexcept { + return Handle((Handle::HandleId) mNextFakeHandle++); +} + +Handle WebGPUDriver::createTextureExternalImage2S() noexcept { + return Handle((Handle::HandleId) mNextFakeHandle++); +} + +Handle WebGPUDriver::createTextureExternalImagePlaneS() noexcept { + return Handle((Handle::HandleId) mNextFakeHandle++); +} + +void WebGPUDriver::createSwapChainR(Handle sch, void* nativeWindow, uint64_t flags) { + mSurface= mPlatform.createSurface(nativeWindow, flags); + mAdapter = mPlatform.requestAdapter(mSurface); +#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM) + printAdapterDetails(mAdapter); + wgpu::SurfaceCapabilities surfaceCapabilities{}; + if (!mSurface.GetCapabilities(mAdapter, &surfaceCapabilities)) { + FWGPU_LOGW << "Failed to get WebGPU surface capabilities" << utils::io::endl; + } else { + printSurfaceCapabilitiesDetails(surfaceCapabilities); + } +#endif + mDevice = mPlatform.requestDevice(mAdapter); +#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM) + printDeviceDetails(mDevice); +#endif + mQueue = mDevice.GetQueue(); + // TODO configure the surface (maybe before or after creating the swapchain? + // how do we get the surface extent?) + // TODO actually create the swapchain + FWGPU_LOGW << "WebGPU support is still essentially a no-op at this point in development (only " + "background components have been instantiated/selected, such as surface/screen, " + "graphics device/GPU, etc.), thus nothing is being drawn to the screen." + << utils::io::endl; +#if !FWGPU_ENABLED(FWGPU_PRINT_SYSTEM) && !defined(NDEBUG) + FWGPU_LOGI << "If the FILAMENT_BACKEND_DEBUG_FLAG variable were set with the " << utils::io::hex + << FWGPU_PRINT_SYSTEM << utils::io::dec + << " bit flag on during build time the application would print system details " + "about the selected graphics device, surface, etc. To see this try " + "rebuilding Filament with that flag, e.g. ./build.sh -x " + << FWGPU_PRINT_SYSTEM << " ..." << utils::io::endl; +#endif +} + +void WebGPUDriver::createSwapChainHeadlessR(Handle sch, uint32_t width, + uint32_t height, uint64_t flags) {} + +void WebGPUDriver::createVertexBufferInfoR(Handle vbih, uint8_t bufferCount, + uint8_t attributeCount, AttributeArray attributes) {} + +void WebGPUDriver::createVertexBufferR(Handle vbh, uint32_t vertexCount, + Handle vbih) {} + +void WebGPUDriver::createIndexBufferR(Handle ibh, ElementType elementType, + uint32_t indexCount, BufferUsage usage) {} + +void WebGPUDriver::createBufferObjectR(Handle boh, uint32_t byteCount, + BufferObjectBinding bindingType, BufferUsage usage) {} + +void WebGPUDriver::createTextureR(Handle th, SamplerType target, uint8_t levels, + TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth, + TextureUsage usage) {} + +void WebGPUDriver::createTextureViewR(Handle th, Handle srch, + uint8_t baseLevel, uint8_t levelCount) {} + +void WebGPUDriver::createTextureViewSwizzleR(Handle th, Handle srch, + backend::TextureSwizzle r, backend::TextureSwizzle g, backend::TextureSwizzle b, + backend::TextureSwizzle a) {} + +void WebGPUDriver::createTextureExternalImage2R(Handle th, backend::SamplerType target, + backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage, + Platform::ExternalImageHandleRef externalImage) {} + +void WebGPUDriver::createTextureExternalImageR(Handle th, backend::SamplerType target, + backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage, + void* externalImage) {} + +void WebGPUDriver::createTextureExternalImagePlaneR(Handle th, + backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage, + void* image, uint32_t plane) {} + +void WebGPUDriver::importTextureR(Handle th, intptr_t id, SamplerType target, + uint8_t levels, TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, + uint32_t depth, TextureUsage usage) {} + +void WebGPUDriver::createRenderPrimitiveR(Handle rph, Handle vbh, + Handle ibh, PrimitiveType pt) {} + +void WebGPUDriver::createProgramR(Handle ph, Program&& program) {} + +void WebGPUDriver::createDefaultRenderTargetR(Handle rth, int) {} + +void WebGPUDriver::createRenderTargetR(Handle rth, TargetBufferFlags targets, + uint32_t width, uint32_t height, uint8_t samples, uint8_t layerCount, MRT color, + TargetBufferInfo depth, TargetBufferInfo stencil) {} + +void WebGPUDriver::createFenceR(Handle fh, int) {} + +void WebGPUDriver::createTimerQueryR(Handle tqh, int) {} + +void WebGPUDriver::createDescriptorSetLayoutR(Handle dslh, + backend::DescriptorSetLayout&& info) {} + +void WebGPUDriver::createDescriptorSetR(Handle dsh, + Handle dslh) {} + Handle WebGPUDriver::createStreamNative(void* nativeStream) { return {}; } diff --git a/filament/backend/src/webgpu/WebGPUDriver.h b/filament/backend/src/webgpu/WebGPUDriver.h index d70744b4a4..dd061d8609 100644 --- a/filament/backend/src/webgpu/WebGPUDriver.h +++ b/filament/backend/src/webgpu/WebGPUDriver.h @@ -17,26 +17,44 @@ #ifndef TNT_FILAMENT_BACKEND_WEBGPUDRIVER_H #define TNT_FILAMENT_BACKEND_WEBGPUDRIVER_H -#include "private/backend/Driver.h" +#include + #include "DriverBase.h" +#include "private/backend/Dispatcher.h" +#include "private/backend/Driver.h" +#include #include +#include + +#include + namespace filament::backend { +/** + * WebGPU backend (driver) implementation + */ class WebGPUDriver final : public DriverBase { - WebGPUDriver() noexcept; - ~WebGPUDriver() noexcept override; - Dispatcher getDispatcher() const noexcept final; - public: - static Driver* create(); + ~WebGPUDriver() noexcept override; + + [[nodiscard]] Dispatcher getDispatcher() const noexcept final; + [[nodiscard]] static Driver* create(WebGPUPlatform& platform) noexcept; private: - ShaderModel getShaderModel() const noexcept final; - ShaderLanguage getShaderLanguage() const noexcept final; + explicit WebGPUDriver(WebGPUPlatform& platform) noexcept; + [[nodiscard]] ShaderModel getShaderModel() const noexcept final; + [[nodiscard]] ShaderLanguage getShaderLanguage() const noexcept final; - uint64_t nextFakeHandle = 1; + // the platform (e.g. OS) specific aspects of the WebGPU backend are strictly only + // handled in the WebGPUPlatform + WebGPUPlatform& mPlatform; + wgpu::Surface mSurface = nullptr; + wgpu::Adapter mAdapter = nullptr; + wgpu::Device mDevice = nullptr; + wgpu::Queue mQueue = nullptr; + uint64_t mNextFakeHandle = 1; /* * Driver interface @@ -45,20 +63,19 @@ private: template friend class ConcreteDispatcher; -#define DECL_DRIVER_API(methodName, paramsDecl, params) \ +#define DECL_DRIVER_API(methodName, paramsDecl, params) \ UTILS_ALWAYS_INLINE inline void methodName(paramsDecl); -#define DECL_DRIVER_API_SYNCHRONOUS(RetType, methodName, paramsDecl, params) \ +#define DECL_DRIVER_API_SYNCHRONOUS(RetType, methodName, paramsDecl, params) \ RetType methodName(paramsDecl) override; -#define DECL_DRIVER_API_RETURN(RetType, methodName, paramsDecl, params) \ - RetType methodName##S() noexcept override { \ - return RetType((RetType::HandleId)nextFakeHandle++); } \ - UTILS_ALWAYS_INLINE inline void methodName##R(RetType, paramsDecl) { } +#define DECL_DRIVER_API_RETURN(RetType, methodName, paramsDecl, params) \ + RetType methodName##S() noexcept override; \ + UTILS_ALWAYS_INLINE inline void methodName##R(RetType, paramsDecl); #include "private/backend/DriverAPI.inc" }; -} // namespace filament +}// namespace filament::backend -#endif // TNT_FILAMENT_BACKEND_WEBGPUDRIVER_H +#endif// TNT_FILAMENT_BACKEND_WEBGPUDRIVER_H diff --git a/filament/backend/src/webgpu/WebGPUPlatform.cpp b/filament/backend/src/webgpu/WebGPUPlatform.cpp deleted file mode 100644 index 9aca1f9324..0000000000 --- a/filament/backend/src/webgpu/WebGPUPlatform.cpp +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2025 The Android Open Source Project - * - * 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 "webgpu/WebGPUPlatform.h" -#include "webgpu/WebGPUDriver.h" - -#include "utils/Log.h" - -namespace filament::backend { - -Driver* WebGPUPlatform::createDriver(void* const sharedContext , const Platform::DriverConfig& driverConfig) noexcept { - wgpu::InstanceDescriptor instance_descriptor {}; - wgpu::Instance instance = wgpu::CreateInstance(&instance_descriptor); - if (instance) { - utils::slog.i << " WebGPU instance created\n"; - } else { - utils::slog.e << " WebGPU instance failed to create\n"; - return nullptr; - } - return WebGPUDriver::create(); -} - -} // namespace filament diff --git a/filament/backend/src/webgpu/WebGPUPlatform.h b/filament/backend/src/webgpu/WebGPUPlatform.h deleted file mode 100644 index 5b26c1cfd9..0000000000 --- a/filament/backend/src/webgpu/WebGPUPlatform.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2025 The Android Open Source Project - * - * 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 TNT_FILAMENT_BACKEND_WEBGPUPLATFORM_H -#define TNT_FILAMENT_BACKEND_WEBGPUPLATFORM_H - -#include -#include - -#include "webgpu/webgpu_cpp.h" - -namespace filament::backend { - -class WebGPUPlatform - final : public Platform { -public: - - int getOSVersion() const noexcept final { return 0; } - - ~WebGPUPlatform() noexcept override = default; - -protected: - - Driver* createDriver(void* sharedContext, const Platform::DriverConfig& driverConfig) noexcept override; -}; - -} // namespace filament - -#endif // TNT_FILAMENT_BACKEND_WEBGPUPLATFORM_H diff --git a/filament/backend/src/webgpu/platform/WebGPUPlatform.cpp b/filament/backend/src/webgpu/platform/WebGPUPlatform.cpp new file mode 100644 index 0000000000..e83acb0016 --- /dev/null +++ b/filament/backend/src/webgpu/platform/WebGPUPlatform.cpp @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * 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 + +#include "webgpu/WebGPUConstants.h" +#include "webgpu/WebGPUDriver.h" + +#include +#include +#include + +#include +#include + +#include +#include + +/** + * WebGPU Backend implementation common across platforms or operating systems (at least for now). + * Some of these functions may likely be refactored to platform/OS-specific implementations + * over time as needed. The caller of the WebGPUPlatform doesn't need to care which is the case. + */ + +namespace filament::backend { + +namespace { + +//either returns a valid instance or panics +[[nodiscard]] wgpu::Instance createInstance() { + wgpu::DawnTogglesDescriptor dawnTogglesDescriptor{}; +#if defined(FILAMENT_WEBGPU_IMMEDIATE_ERROR_HANDLING) +#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM) + FWGPU_LOGI << "setting on toggle enable_immediate_error_handling" << utils::io::endl; +#endif + /** + * Have the un-captured error callback invoked immediately when an error occurs, rather than + * waiting for the next Tick. This enables using the stack trace in which the un-captured error + * occurred when breaking into the un-captured error callback. + * https://crbug.com/dawn/1789 + */ + static const char* toggleName = "enable_immediate_error_handling"; + dawnTogglesDescriptor.enabledToggleCount = 1; + dawnTogglesDescriptor.enabledToggles = &toggleName; +#endif + wgpu::InstanceDescriptor instanceDescriptor{ + .nextInChain = &dawnTogglesDescriptor, + .capabilities = { + .timedWaitAnyEnable = true// TODO consider using pure async instead + } + }; + wgpu::Instance instance = wgpu::CreateInstance(&instanceDescriptor); + FILAMENT_CHECK_POSTCONDITION(instance != nullptr) << "Unable to create webgpu instance."; + return instance; +} + +}// namespace + +wgpu::Adapter WebGPUPlatform::requestAdapter(wgpu::Surface const& surface) { + // TODO consider power preference etc. (can be custom preferences passed to the platform or + // based on whether this is a Mobile or Desktop system, + // etc...) + wgpu::RequestAdapterOptions adaptorOptions{ .compatibleSurface = surface }; + // note this just gets the first adapter + wgpu::Adapter adapter = nullptr; + wgpu::WaitStatus status = mInstance.WaitAny( + mInstance.RequestAdapter(&adaptorOptions, wgpu::CallbackMode::WaitAnyOnly, + [&adapter](wgpu::RequestAdapterStatus const status, + wgpu::Adapter const& readyAdapter, wgpu::StringView const message) { + // TODO consider more robust error handling + FILAMENT_CHECK_POSTCONDITION(status == wgpu::RequestAdapterStatus::Success) + << "Unable to request a WebGPU adapter. Status " + << static_cast(status) + << " with message: " << message.data; + adapter = readyAdapter; + }), + UINT16_MAX);// TODO define reasonable timeout (or do this asynchronously) + FILAMENT_CHECK_POSTCONDITION(status == wgpu::WaitStatus::Success) + << "Non-successful wait status requesting a WebGPU adapter " + << static_cast(status); + FILAMENT_CHECK_POSTCONDITION(adapter != nullptr) + << "Failed to get a WebGPU adapter for the platform"; + // TODO consider validating adapter has required features and/or limits + return adapter; +} + +wgpu::Device WebGPUPlatform::requestDevice(wgpu::Adapter const& adapter) { + // TODO consider passing required features and/or limits + wgpu::DeviceDescriptor deviceDescriptor{}; + deviceDescriptor.label = "graphics_device"; + deviceDescriptor.defaultQueue.label = "default_queue"; + deviceDescriptor.SetDeviceLostCallback(wgpu::CallbackMode::AllowSpontaneous, + [](wgpu::Device const&, wgpu::DeviceLostReason const& reason, + wgpu::StringView message) { + if (reason == wgpu::DeviceLostReason::Destroyed) { + FWGPU_LOGD << "WebGPU device lost due to being destroyed (expected)" + << utils::io::endl; + return; + } + // TODO try recreating the device instead of just panicking + std::stringstream reasonStream{}; + reasonStream << reason; + FILAMENT_CHECK_POSTCONDITION(reason != wgpu::DeviceLostReason::Destroyed) + << "WebGPU device lost: " << reasonStream.str() << " " << message.data; + }); + deviceDescriptor.SetUncapturedErrorCallback( + [](wgpu::Device const&, wgpu::ErrorType errorType, wgpu::StringView message) { + std::stringstream typeStream{}; + typeStream << errorType; + FWGPU_LOGE << "WebGPU device error: " << typeStream.str() << " " << message.data + << utils::io::endl; + }); + wgpu::Device device = nullptr; + wgpu::WaitStatus status = mInstance.WaitAny( + adapter.RequestDevice(&deviceDescriptor, wgpu::CallbackMode::WaitAnyOnly, + [&device](wgpu::RequestDeviceStatus const status, + wgpu::Device const& readyDevice, wgpu::StringView const message) { + FILAMENT_CHECK_POSTCONDITION(status == wgpu::RequestDeviceStatus::Success) + << "Unable to request a WebGPU device. Status: " + << static_cast(status) + << " with message: " << message.data; + device = readyDevice; + }), + UINT64_MAX);// TODO define reasonable timeout (or do this asynchronously) + FILAMENT_CHECK_POSTCONDITION(status == wgpu::WaitStatus::Success) + << "Non-successful wait status requesting a WebGPU device " + << static_cast(status); + FILAMENT_CHECK_POSTCONDITION(device != nullptr) + << "Failed to get a WebGPU device for the platform."; + return device; +} + +Driver* WebGPUPlatform::createDriver(void* const sharedContext, + const Platform::DriverConfig& /*driverConfig*/) noexcept { + if (sharedContext) { + FWGPU_LOGW << "sharedContext is ignored/unused in the WebGPU backend. A non-null " + "sharedContext was provided, but it will be ignored." + << utils::io::endl; + } + return WebGPUDriver::create(*this); +} + +WebGPUPlatform::WebGPUPlatform() + : mInstance(createInstance()) {} + +}// namespace filament::backend diff --git a/filament/backend/src/webgpu/platform/WebGPUPlatformAndroid.cpp b/filament/backend/src/webgpu/platform/WebGPUPlatformAndroid.cpp new file mode 100644 index 0000000000..28bc5c18cf --- /dev/null +++ b/filament/backend/src/webgpu/platform/WebGPUPlatformAndroid.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * 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 + +#include + +#include + +#include + +/** + * Android OS specific implementation aspects of the WebGPU backend + */ + +namespace filament::backend { + +wgpu::Surface WebGPUPlatform::createSurface(void* nativeWindow, uint64_t /*flags*/) { + wgpu::SurfaceSourceAndroidNativeWindow surfaceSourceAndroidWindow{}; + surfaceSourceAndroidWindow.window = nativeWindow; + wgpu::SurfaceDescriptor surfaceDescriptor{ + .nextInChain = &surfaceSourceAndroidWindow, + .label = "android_surface" + }; + wgpu::Surface surface = mInstance.CreateSurface(&surfaceDescriptor); + FILAMENT_CHECK_POSTCONDITION(surface != nullptr) << "Unable to create Android-backed surface."; + return surface; +} + +}// namespace filament::backend diff --git a/filament/backend/src/webgpu/platform/WebGPUPlatformApple.mm b/filament/backend/src/webgpu/platform/WebGPUPlatformApple.mm new file mode 100644 index 0000000000..6cc9f72c87 --- /dev/null +++ b/filament/backend/src/webgpu/platform/WebGPUPlatformApple.mm @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * 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 + +#include +#include + +#include + +#include + +// Platform specific includes and defines +#if defined(__APPLE__) + #include + #import +#elif defined(FILAMENT_IOS) + // Metal is not available when building for the iOS simulator on Desktop. + #define METAL_AVAILABLE __has_include() + #if METAL_AVAILABLE + #import + #import + #endif + // is this needed? + #define METALVIEW_TAG 255 +#else + #error Not a supported Apple + WebGPU platform +#endif + +/** + * Apple (Mac OS and IOS) specific implementation aspects of the WebGPU backend + */ + +namespace filament::backend { + +wgpu::Surface WebGPUPlatform::createSurface(void* nativeWindow, uint64_t /*flags*/) { + wgpu::Surface surface = nullptr; +#if defined(__APPLE__) + auto nsView = (__bridge NSView*) nativeWindow; + FILAMENT_CHECK_POSTCONDITION(nsView) << "Unable to obtain Metal-backed NSView."; + [nsView setWantsLayer:YES]; + id metalLayer = [CAMetalLayer layer]; + [nsView setLayer:metalLayer]; + wgpu::SurfaceSourceMetalLayer surfaceSourceMetalLayer{}; + surfaceSourceMetalLayer.layer = (__bridge void*) metalLayer; + wgpu::SurfaceDescriptor surfaceDescriptor = { + .nextInChain = &surfaceSourceMetalLayer, + .label = "metal_surface", + }; + surface = mInstance.CreateSurface(&surfaceDescriptor); + FILAMENT_CHECK_POSTCONDITION(surface != nullptr) << "Unable to create Metal-backed surface."; +#elif defined(FILAMENT_IOS) + CAMetalLayer* metalLayer = (CAMetalLayer*) nativeWindow; + wgpu::SurfaceSourceMetalLayer surfaceSourceMetalLayer{}; + surfaceSourceMetalLayer.layer = (__bridge void*) metalLayer; + wgpu::SurfaceDescriptor surfaceDescriptor = { + .nextInChain = &surfaceSourceMetalLayer, + .label = "metal_surface", + }; + surface = mInstance.CreateSurface(&surfaceDescriptor); + FILAMENT_CHECK_POSTCONDITION(surface != nullptr) << "Unable to create Metal-backed surface."; +#else + #error Not a supported Apple + WebGPU platform +#endif + return surface; +} + +}// namespace filament::backend diff --git a/filament/backend/src/webgpu/platform/WebGPUPlatformLinux.cpp b/filament/backend/src/webgpu/platform/WebGPUPlatformLinux.cpp new file mode 100644 index 0000000000..16623891c9 --- /dev/null +++ b/filament/backend/src/webgpu/platform/WebGPUPlatformLinux.cpp @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * 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 + +#include + +#include + +#include +#include + +#if defined(__linux__) || defined(__FreeBSD__) + #define LINUX_OR_FREEBSD 1 +#endif + +// Platform specific includes and defines +#if defined(__linux__) && defined(FILAMENT_SUPPORTS_WAYLAND) + #include + namespace { + typedef struct _wl { + struct wl_display* display; + struct wl_surface* surface; + uint32_t width; + uint32_t height; + } wl; + }// namespace +#elif defined(LINUX_OR_FREEBSD) && defined(FILAMENT_SUPPORTS_X11) + // TODO: we should allow for headless on Linux explicitly. Right now this is the headless path + // (with no FILAMENT_SUPPORTS_XCB or FILAMENT_SUPPORTS_XLIB). + #include + #if defined(FILAMENT_SUPPORTS_XCB) + #include + namespace { + typedef xcb_connection_t* (*XCB_CONNECT)(const char* displayname, int* screenp); + }// namespace + #endif + #if defined(FILAMENT_SUPPORTS_XLIB) + #include + namespace { + typedef Display* (*X11_OPEN_DISPLAY)(const char*); + }// namespace + #endif + static constexpr const char* LIBRARY_X11 = "libX11.so.6"; + namespace { + struct XEnv { + #if defined(FILAMENT_SUPPORTS_XCB) + XCB_CONNECT xcbConnect; + xcb_connection_t* connection; + #endif + #if defined(FILAMENT_SUPPORTS_XLIB) + X11_OPEN_DISPLAY openDisplay; + Display* display; + #endif + void* library = nullptr; + } g_x11; + }// namespace +#else + #error Not a supported Linux or FeeBSD + WebGPU platform +#endif + +/** + * Linux OS specific implementation aspects of the WebGPU Backend + */ + +namespace filament::backend { + +wgpu::Surface WebGPUPlatform::createSurface(void* nativeWindow, uint64_t flags) { + wgpu::Surface surface = nullptr; +#if defined(__linux__) && defined(FILAMENT_SUPPORTS_WAYLAND) + wl* ptrval = reinterpret_cast(nativeWindow); + wgpu::SurfaceSourceWaylandSurface surfaceSourceWayland{}; + surfaceSourceWayland.display = ptrval->display; + surfaceSourceWayland.surface = ptrval->surface; + wgpu::SurfaceDescriptor surfaceDescriptor{ + .nextInChain = &surfaceSourceWayland, + .label = "linux_wayland_surface" + }; + surface = mInstance.CreateSurface(&surfaceDescriptor); + FILAMENT_CHECK_POSTCONDITION(surface != nullptr) + << "Unable to create Linux Wayland-backed surface."; +#elif defined(LINUX_OR_FREEBSD) && defined(FILAMENT_SUPPORTS_X11) + if (g_x11.library == nullptr) { + g_x11.library = dlopen(LIBRARY_X11, RTLD_LOCAL | RTLD_NOW); + FILAMENT_CHECK_PRECONDITION(g_x11.library) << "Unable to open X11 library."; + #if defined(FILAMENT_SUPPORTS_XCB) + g_x11.xcbConnect = (XCB_CONNECT) dlsym(g_x11.library, "xcb_connect"); + int screen = 0; + g_x11.connection = g_x11.xcbConnect(nullptr, &screen); + #endif + #if defined(FILAMENT_SUPPORTS_XLIB) + g_x11.openDisplay = (X11_OPEN_DISPLAY) dlsym(g_x11.library, "XOpenDisplay"); + g_x11.display = g_x11.openDisplay(NULL); + FILAMENT_CHECK_PRECONDITION(g_x11.display) << "Unable to open X11 display."; + #endif + } + #if defined(FILAMENT_SUPPORTS_XCB) || defined(FILAMENT_SUPPORTS_XLIB) + bool useXcb = false; + #endif + #if defined(FILAMENT_SUPPORTS_XCB) + #if defined(FILAMENT_SUPPORTS_XLIB) + useXcb = (flags & SWAP_CHAIN_CONFIG_ENABLE_XCB) != 0; + #else + useXcb = true; + #endif + if (useXcb) { + wgpu::SurfaceSourceXCBWindow surfaceSourceXcb{}; + surfaceSourceXcb.connection = g_x11.connection; + surfaceSourceXcb.window = reinterpret_cast(nativeWindow); + wgpu::SurfaceDescriptor surfaceDescriptor{ + .nextInChain = &surfaceSourceXcb, + .label = "linux_xcb_surface" + }; + surface = mInstance.CreateSurface(&surfaceDescriptor); + FILAMENT_CHECK_POSTCONDITION(surface != nullptr) + << "Unable to create Linux (or FreeBSD) XCB-backed surface."; + } + #endif + #if defined(FILAMENT_SUPPORTS_XLIB) + if (!useXcb) { + wgpu::SurfaceSourceXlibWindow surfaceSourceXlib{}; + surfaceSourceXlib.display = g_x11.display; + surfaceSourceXlib.window = reinterpret_cast(nativeWindow); + wgpu::SurfaceDescriptor surfaceDescriptor{ + .nextInChain = &surfaceSourceXlib, + .label = "linux_xlib_surface" + }; + surface = mInstance.CreateSurface(&surfaceDescriptor); + FILAMENT_CHECK_POSTCONDITION(surface != nullptr) + << "Unable to create Linux (or FreeBSD) XLib-backed surface."; + } + #endif + FILAMENT_CHECK_POSTCONDITION(surface != nullptr) + << "Cannot create WebGPU X11 surface for Linux (or FreeBSD) OS " + "(not built with support for XCB or XLIB?)"; +#elif defined(__linux__) + FILAMENT_CHECK_POSTCONDITION(surface != nullptr) + << "Cannot create WebGPU surface for Linux (or FreeBSD) OS " + "(not built with support for Wayland or X11?)"; +#else + FILAMENT_CHECK_POSTCONDITION(surface != nullptr) + << "Not a supported (Linux) OS + WebGPU platform"; +#endif + return surface; +} + +}// namespace filament::backend diff --git a/filament/backend/src/webgpu/platform/WebGPUPlatformWindows.cpp b/filament/backend/src/webgpu/platform/WebGPUPlatformWindows.cpp new file mode 100644 index 0000000000..ad28d293c4 --- /dev/null +++ b/filament/backend/src/webgpu/platform/WebGPUPlatformWindows.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * 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 + +#include + +#include + +#include + +/** + * Windows OS specific implementation aspects of the WebGPU backend + */ + +namespace filament::backend { + +wgpu::Surface WebGPUPlatform::createSurface(void* nativeWindow, uint64_t /*flags*/) { + // TODO verify this is necessary for Dawn implementation as well: + // On (at least) NVIDIA drivers, the Vulkan implementation (specifically the call to + // vkGetPhysicalDeviceSurfaceCapabilitiesKHR()) does not correctly handle the fact that + // each native window has its own DPI_AWARENESS_CONTEXT, and erroneously uses the context + // of the calling thread. As a workaround, we set the current thread's DPI_AWARENESS_CONTEXT + // to that of the native window we've been given. This isn't a perfect solution, because an + // application could create swap chains on multiple native windows with varying DPI-awareness, + // but even then, at least one of the windows would be guaranteed to work correctly. + SetThreadDpiAwarenessContext(GetWindowDpiAwarenessContext((HWND) nativeWindow)); + wgpu::SurfaceSourceWindowsHWND surfaceSourceWin{}; + surfaceSourceWin.hinstance = GetModuleHandle(nullptr); + surfaceSourceWin.hwnd = nativeWindow; + wgpu::SurfaceDescriptor surfaceDescriptor{ + .nextInChain = &surfaceSourceWin, + .label = "windows_surface" + }; + wgpu::Surface surface = mInstance.CreateSurface(&surfaceDescriptor); + FILAMENT_CHECK_POSTCONDITION(surface != nullptr) << "Unable to create Windows-backed surface."; + return surface; +} + +}// namespace filament::backend diff --git a/samples/hellotriangle.cpp b/samples/hellotriangle.cpp index 4477429dd6..9cd3c96b74 100644 --- a/samples/hellotriangle.cpp +++ b/samples/hellotriangle.cpp @@ -77,7 +77,11 @@ static void printUsage(char* name) { " --help, -h\n" " Prints this message\n\n" " --api, -a\n" - " Specify the backend API: opengl, vulkan, or metal\n" + " Specify the backend API: opengl, vulkan, metal, or webgpu\n" + " (note: webgpu is a no-op for now, printing backend\n" + " component info if FILAMENT_BACKEND_DEBUG_FLAG, \n" + " set at build time, includes the \n" + " FWGPU_PRINT_SYSTEM bit flag 0x2)\n" ); const std::string from("HELLOTRIANGLE"); for (size_t pos = usage.find(from); pos != std::string::npos; pos = usage.find(from, pos)) { @@ -109,8 +113,11 @@ static int handleCommandLineArguments(int argc, char* argv[], App* app) { app->config.backend = Engine::Backend::VULKAN; } else if (arg == "metal") { app->config.backend = Engine::Backend::METAL; + } else if (arg == "webgpu") { + app->config.backend = Engine::Backend::WEBGPU; } else { - std::cerr << "Unrecognized backend. Must be 'opengl'|'vulkan'|'metal'.\n"; + std::cerr << "Unrecognized backend. Must be " + "'opengl'|'vulkan'|'metal'|'webgpu'.\n"; exit(1); } break;