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;