diff --git a/filament/backend/CMakeLists.txt b/filament/backend/CMakeLists.txt index 3e70b28dc3..6669898637 100644 --- a/filament/backend/CMakeLists.txt +++ b/filament/backend/CMakeLists.txt @@ -259,6 +259,8 @@ if (FILAMENT_SUPPORTS_WEBGPU) src/webgpu/WebGPUDriver.h src/webgpu/WebGPUHandles.cpp src/webgpu/WebGPUHandles.h + src/webgpu/WebGPUPipelineCreation.cpp + src/webgpu/WebGPUPipelineCreation.h src/webgpu/WebGPUSwapChain.cpp src/webgpu/WebGPUSwapChain.h src/webgpu/WGPUProgram.cpp diff --git a/filament/backend/src/webgpu/WebGPUDriver.cpp b/filament/backend/src/webgpu/WebGPUDriver.cpp index 5e9b91d8dc..484dd6fa39 100644 --- a/filament/backend/src/webgpu/WebGPUDriver.cpp +++ b/filament/backend/src/webgpu/WebGPUDriver.cpp @@ -16,6 +16,7 @@ #include "webgpu/WebGPUDriver.h" +#include "WebGPUPipelineCreation.h" #include "WebGPUSwapChain.h" #include @@ -24,18 +25,20 @@ #include "private/backend/Dispatcher.h" #include #include +#include #include #include +#include #include #include #include #include +#include #include #include -#include #include #include #include @@ -859,6 +862,44 @@ void WebGPUDriver::blit( } void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) { + const auto* program = handleCast(pipelineState.program); + assert_invariant(program); + assert_invariant(program->computeShaderModule == nullptr && + "WebGPU backend does not (yet) support compute pipelines."); + FILAMENT_CHECK_POSTCONDITION(program->vertexShaderModule) + << "WebGPU backend requires a vertex shader module for a render pipeline"; + std::array bindGroupLayouts{}; + assert_invariant(bindGroupLayouts.size() >= pipelineState.pipelineLayout.setLayout.size()); + size_t bindGroupLayoutCount = 0; + for (size_t i = 0; i < bindGroupLayouts.size(); i++) { + const auto handle = pipelineState.pipelineLayout.setLayout[bindGroupLayoutCount]; + if (handle.getId() == HandleBase::nullid) { + continue; + } + bindGroupLayouts[bindGroupLayoutCount++] = + handleCast(handle)->getLayout(); + } + std::stringstream layoutLabelStream; + layoutLabelStream << program->name.c_str() << " layout"; + const auto layoutLabel = layoutLabelStream.str(); + const wgpu::PipelineLayoutDescriptor layoutDescriptor{ + .label = wgpu::StringView(layoutLabel), + .bindGroupLayoutCount = bindGroupLayoutCount, + .bindGroupLayouts = bindGroupLayouts.data() + // TODO investigate immediateDataRangeByteSize + }; + const wgpu::PipelineLayout layout = mDevice.CreatePipelineLayout(&layoutDescriptor); + FILAMENT_CHECK_POSTCONDITION(layout) + << "Failed to create wgpu::PipelineLayout for render pipeline for " + << layoutDescriptor.label; + auto const* vertexBufferInfo = handleCast(pipelineState.vertexBufferInfo); + assert_invariant(vertexBufferInfo); + const wgpu::RenderPipeline pipeline = createWebGPURenderPipeline(mDevice, *program, + *vertexBufferInfo, layout, pipelineState.rasterState, pipelineState.stencilState, + pipelineState.polygonOffset, pipelineState.primitiveType, mSwapChain->getColorFormat(), + mSwapChain->getDepthFormat()); + // TODO: uncomment once we have a valid pipeline to set + // mRenderPassEncoder.SetPipeline(pipeline); } void WebGPUDriver::bindRenderPrimitive(Handle rph) { diff --git a/filament/backend/src/webgpu/WebGPUPipelineCreation.cpp b/filament/backend/src/webgpu/WebGPUPipelineCreation.cpp new file mode 100644 index 0000000000..cb908f97ef --- /dev/null +++ b/filament/backend/src/webgpu/WebGPUPipelineCreation.cpp @@ -0,0 +1,254 @@ +/* + * 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 "WebGPUPipelineCreation.h" + +#include "WebGPUHandles.h" + +#include +#include + +#include +#include + +#include + +#include +#include +#include + +namespace filament::backend { + +namespace { + +constexpr wgpu::PrimitiveTopology toWebGPU(PrimitiveType primitiveType) { + switch (primitiveType) { + case PrimitiveType::POINTS: + return wgpu::PrimitiveTopology::PointList; + case PrimitiveType::LINES: + return wgpu::PrimitiveTopology::LineList; + case PrimitiveType::LINE_STRIP: + return wgpu::PrimitiveTopology::LineStrip; + case PrimitiveType::TRIANGLES: + return wgpu::PrimitiveTopology::TriangleList; + case PrimitiveType::TRIANGLE_STRIP: + return wgpu::PrimitiveTopology::TriangleStrip; + } +} + +constexpr wgpu::CullMode toWebGPU(CullingMode cullMode) { + switch (cullMode) { + case CullingMode::NONE: + return wgpu::CullMode::None; + case CullingMode::FRONT: + return wgpu::CullMode::Front; + case CullingMode::BACK: + return wgpu::CullMode::Back; + case CullingMode::FRONT_AND_BACK: + // no WegGPU equivalent of front and back + FILAMENT_CHECK_POSTCONDITION(false) + << "WebGPU does not support CullingMode::FRONT_AND_BACK"; + return wgpu::CullMode::Undefined; + } +} + +constexpr wgpu::CompareFunction toWebGPU(SamplerCompareFunc compareFunction) { + switch (compareFunction) { + case SamplerCompareFunc::LE: + return wgpu::CompareFunction::LessEqual; + case SamplerCompareFunc::GE: + return wgpu::CompareFunction::GreaterEqual; + case SamplerCompareFunc::L: + return wgpu::CompareFunction::Less; + case SamplerCompareFunc::G: + return wgpu::CompareFunction::Greater; + case SamplerCompareFunc::E: + return wgpu::CompareFunction::Equal; + case SamplerCompareFunc::NE: + return wgpu::CompareFunction::NotEqual; + case SamplerCompareFunc::A: + return wgpu::CompareFunction::Always; + case SamplerCompareFunc::N: + return wgpu::CompareFunction::Never; + } +} + +constexpr wgpu::StencilOperation toWebGPU(StencilOperation stencilOp) { + switch (stencilOp) { + case StencilOperation::KEEP: + return wgpu::StencilOperation::Keep; + case StencilOperation::ZERO: + return wgpu::StencilOperation::Zero; + case StencilOperation::REPLACE: + return wgpu::StencilOperation::Replace; + case StencilOperation::INCR: + return wgpu::StencilOperation::IncrementClamp; + case StencilOperation::INCR_WRAP: + return wgpu::StencilOperation::IncrementWrap; + case StencilOperation::DECR: + return wgpu::StencilOperation::DecrementClamp; + case StencilOperation::DECR_WRAP: + return wgpu::StencilOperation::DecrementWrap; + case StencilOperation::INVERT: + return wgpu::StencilOperation::Invert; + } +} + +constexpr wgpu::BlendOperation toWebGPU(BlendEquation blendOp) { + switch (blendOp) { + case BlendEquation::ADD: + return wgpu::BlendOperation::Add; + case BlendEquation::SUBTRACT: + return wgpu::BlendOperation::Subtract; + case BlendEquation::REVERSE_SUBTRACT: + return wgpu::BlendOperation::ReverseSubtract; + case BlendEquation::MIN: + return wgpu::BlendOperation::Min; + case BlendEquation::MAX: + return wgpu::BlendOperation::Max; + } +} + +constexpr wgpu::BlendFactor toWebGPU(BlendFunction blendFunction) { + switch (blendFunction) { + case BlendFunction::ZERO: + return wgpu::BlendFactor::Zero; + case BlendFunction::ONE: + return wgpu::BlendFactor::One; + case BlendFunction::SRC_COLOR: + return wgpu::BlendFactor::Src; + case BlendFunction::ONE_MINUS_SRC_COLOR: + return wgpu::BlendFactor::OneMinusSrc; + case BlendFunction::DST_COLOR: + return wgpu::BlendFactor::Dst; + case BlendFunction::ONE_MINUS_DST_COLOR: + return wgpu::BlendFactor::OneMinusDst; + case BlendFunction::SRC_ALPHA: + return wgpu::BlendFactor::SrcAlpha; + case BlendFunction::ONE_MINUS_SRC_ALPHA: + return wgpu::BlendFactor::OneMinusSrcAlpha; + case BlendFunction::DST_ALPHA: + return wgpu::BlendFactor::DstAlpha; + case BlendFunction::ONE_MINUS_DST_ALPHA: + return wgpu::BlendFactor::OneMinusDstAlpha; + case BlendFunction::SRC_ALPHA_SATURATE: + return wgpu::BlendFactor::SrcAlphaSaturated; + } +} + +}// namespace + +wgpu::RenderPipeline createWebGPURenderPipeline(wgpu::Device const& device, + WGPUProgram const& program, WGPUVertexBufferInfo const& vertexBufferInfo, + wgpu::PipelineLayout const& layout, RasterState const& rasterState, + StencilState const& stencilState, PolygonOffset const& polygonOffset, + PrimitiveType primitiveType, wgpu::TextureFormat colorFormat, + wgpu::TextureFormat depthFormat) { + assert_invariant(program.vertexShaderModule); + const wgpu::DepthStencilState depthStencilState { + .format = depthFormat, + .depthWriteEnabled = rasterState.depthWrite, + .depthCompare = toWebGPU(rasterState.depthFunc), + .stencilFront = { + .compare = toWebGPU(stencilState.front.stencilFunc), + .failOp = toWebGPU(stencilState.front.stencilOpStencilFail), + .depthFailOp = toWebGPU(stencilState.front.stencilOpDepthFail), + .passOp = toWebGPU(stencilState.front.stencilOpDepthStencilPass), + }, + .stencilBack = { + .compare = toWebGPU(stencilState.back.stencilFunc), + .failOp = toWebGPU(stencilState.back.stencilOpStencilFail), + .depthFailOp = toWebGPU(stencilState.back.stencilOpDepthFail), + .passOp = toWebGPU(stencilState.back.stencilOpDepthStencilPass), + }, + .stencilReadMask = 0, + .stencilWriteMask = stencilState.stencilWrite ? 0xFFFFFFFF : 0, + .depthBias = static_cast(polygonOffset.constant), + .depthBiasSlopeScale = polygonOffset.slope, + .depthBiasClamp = 0.0f + }; + std::stringstream pipelineLabelStream; + pipelineLabelStream << program.name.c_str() << " pipeline"; + const auto pipelineLabel = pipelineLabelStream.str(); + wgpu::RenderPipelineDescriptor pipelineDescriptor{ + .label = wgpu::StringView(pipelineLabel), + .layout = layout, + .vertex = { + .module = program.vertexShaderModule, + .entryPoint = "main", + .constantCount = program.constants.size(), + .constants = program.constants.data(), + .bufferCount = vertexBufferInfo.getVertexBufferLayoutSize(), + .buffers = vertexBufferInfo.getVertexBufferLayout() + }, + .primitive = { + .topology = toWebGPU(primitiveType), + // TODO should we assume some constant format here or is there a way to get + // this from PipelineState somehow or elsewhere? + // Perhaps, cache/assert format from index buffers as they are requested? + .stripIndexFormat = wgpu::IndexFormat::Undefined, + .frontFace = rasterState.inverseFrontFaces ? wgpu::FrontFace::CW : wgpu::FrontFace::CCW, + .cullMode = toWebGPU(rasterState.culling), + // TODO no depth clamp in WebGPU supported directly. unclippedDepth is close, so we are + // starting there + .unclippedDepth = !rasterState.depthClamp && + device.HasFeature(wgpu::FeatureName::DepthClipControl) + }, + .depthStencil = &depthStencilState, + .multisample = { + .count = 1, // TODO need to get this from the render target + .mask = 0xFFFFFFFF, + .alphaToCoverageEnabled = rasterState.alphaToCoverage + }, + .fragment = nullptr // will add below if fragment module is included + }; + wgpu::FragmentState fragmentState = {}; + std::array colorTargets {}; + const wgpu::BlendState blendState { + .color = { + .operation = toWebGPU(rasterState.blendEquationRGB), + .srcFactor = toWebGPU(rasterState.blendFunctionSrcRGB), + .dstFactor = toWebGPU(rasterState.blendFunctionDstRGB) + }, + .alpha = { + .operation = toWebGPU(rasterState.blendEquationAlpha), + .srcFactor = toWebGPU(rasterState.blendFunctionSrcAlpha), + .dstFactor = toWebGPU(rasterState.blendFunctionDstAlpha) + } + }; + if (program.fragmentShaderModule != nullptr) { + fragmentState.module = program.fragmentShaderModule; + fragmentState.entryPoint = "main"; + fragmentState.constantCount = program.constants.size(), + fragmentState.constants = program.constants.data(), + fragmentState.targetCount = 1; // TODO need to get this from the render target + assert_invariant(fragmentState.targetCount <= MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT); + for (size_t targetIndex = 0; targetIndex < fragmentState.targetCount; targetIndex++) { + auto& colorTarget = colorTargets[targetIndex]; + colorTarget.format = colorFormat; + colorTarget.blend = rasterState.hasBlending() ? &blendState : nullptr; + colorTarget.writeMask = + rasterState.colorWrite ? wgpu::ColorWriteMask::All : wgpu::ColorWriteMask::None; + } + pipelineDescriptor.fragment = &fragmentState; + } + const wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDescriptor); + FILAMENT_CHECK_POSTCONDITION(pipeline) + << "Failed to create render pipeline for " << pipelineDescriptor.label; + return pipeline; +} + +}// namespace filament::backend diff --git a/filament/backend/src/webgpu/WebGPUPipelineCreation.h b/filament/backend/src/webgpu/WebGPUPipelineCreation.h new file mode 100644 index 0000000000..cdcc36a985 --- /dev/null +++ b/filament/backend/src/webgpu/WebGPUPipelineCreation.h @@ -0,0 +1,46 @@ +/* + * 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_WEBGPUPIPELINECREATION_H +#define TNT_FILAMENT_BACKEND_WEBGPUPIPELINECREATION_H + +#include + +namespace wgpu { +class Device; +class PipelineLayout; +class RenderPipeline; +enum class TextureFormat : uint32_t; +}// namespace wgpu + +namespace filament::backend { + +struct PolygonOffset; +enum class PrimitiveType : uint8_t; +struct RasterState; +struct StencilState; + +class WGPUVertexBufferInfo; +class WGPUProgram; + +[[nodiscard]] wgpu::RenderPipeline createWebGPURenderPipeline(wgpu::Device const&, + WGPUProgram const&, WGPUVertexBufferInfo const&, wgpu::PipelineLayout const&, + RasterState const&, StencilState const&, PolygonOffset const&, PrimitiveType, + wgpu::TextureFormat colorFormat, wgpu::TextureFormat depthFormat); + +}// namespace filament::backend + +#endif// TNT_FILAMENT_BACKEND_WEBGPUPIPELINECREATION_H diff --git a/filament/backend/src/webgpu/WebGPUSwapChain.cpp b/filament/backend/src/webgpu/WebGPUSwapChain.cpp index 6bdedd3331..f9809ebe5e 100644 --- a/filament/backend/src/webgpu/WebGPUSwapChain.cpp +++ b/filament/backend/src/webgpu/WebGPUSwapChain.cpp @@ -73,7 +73,8 @@ void printSurfaceCapabilitiesDetails(wgpu::SurfaceCapabilities const& capabiliti #endif #if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM) -void printSurfaceConfiguration(wgpu::SurfaceConfiguration const& config) { +void printSurfaceConfiguration(wgpu::SurfaceConfiguration const& config, + wgpu::TextureFormat depthFormat) { std::stringstream formatStream{}; formatStream << config.format; std::stringstream usageStream{}; @@ -82,6 +83,8 @@ void printSurfaceConfiguration(wgpu::SurfaceConfiguration const& config) { alphaModeStream << config.alphaMode; std::stringstream presentModeStream{}; presentModeStream << config.presentMode; + std::stringstream depthFormatStream; + depthFormatStream << depthFormat; FWGPU_LOGI << "WebGPU surface configuration:" << utils::io::endl; FWGPU_LOGI << " surface format: " << formatStream.str() << utils::io::endl; FWGPU_LOGI << " surface usage: " << usageStream.str() << utils::io::endl; @@ -98,10 +101,11 @@ void printSurfaceConfiguration(wgpu::SurfaceConfiguration const& config) { FWGPU_LOGI << " surface width: " << config.width << utils::io::endl; FWGPU_LOGI << " surface height: " << config.height << utils::io::endl; FWGPU_LOGI << " surface present mode: " << presentModeStream.str() << utils::io::endl; + FWGPU_LOGI << "WebGPU selected depth format: " << depthFormatStream.str() << utils::io::endl; } #endif -wgpu::TextureFormat selectColorFormat(size_t availableFormatsCount, +constexpr wgpu::TextureFormat selectColorFormat(size_t availableFormatsCount, wgpu::TextureFormat const* availableFormats, bool useSRGBColorSpace) { const std::array expectedColorFormats = useSRGBColorSpace ? @@ -119,7 +123,21 @@ wgpu::TextureFormat selectColorFormat(size_t availableFormatsCount, return *firstFoundColorFormat; } -wgpu::PresentMode selectPresentMode(size_t availablePresentModesCount, +constexpr wgpu::TextureFormat selectDepthFormat(bool depth32FloatStencil8Enabled, + bool needStencil) { + if (needStencil) { + if (depth32FloatStencil8Enabled) { + return wgpu::TextureFormat::Depth32FloatStencil8; + } else { + return wgpu::TextureFormat::Depth24PlusStencil8; + } + } else { + // other options: Depth16Unorm or Depth24Plus + return wgpu::TextureFormat::Depth32Float; + } +} + +constexpr wgpu::PresentMode selectPresentMode(size_t availablePresentModesCount, wgpu::PresentMode const* availablePresentModes) { // Verify that our chosen present mode is supported. In practice all devices support the FIFO // mode, but we check for it anyway for completeness. (and to avoid validation warnings) @@ -133,7 +151,7 @@ wgpu::PresentMode selectPresentMode(size_t availablePresentModesCount, return desiredPresentMode; } -wgpu::CompositeAlphaMode selectAlphaMode(size_t availableAlphaModesCount, +constexpr wgpu::CompositeAlphaMode selectAlphaMode(size_t availableAlphaModesCount, wgpu::CompositeAlphaMode const* availableAlphaModes) { bool autoAvailable = false; bool inheritAvailable = false; @@ -209,7 +227,7 @@ void initConfig(wgpu::SurfaceConfiguration& config, wgpu::Device const& device, namespace filament::backend { WebGPUSwapChain::WebGPUSwapChain(wgpu::Surface&& surface, wgpu::Extent2D const& surfaceSize, - wgpu::Adapter& adapter, wgpu::Device& device, uint64_t flags) + wgpu::Adapter const& adapter, wgpu::Device const& device, uint64_t flags) : mSurface(surface) { wgpu::SurfaceCapabilities capabilities = {}; if (!mSurface.GetCapabilities(adapter, &capabilities)) { @@ -220,7 +238,13 @@ WebGPUSwapChain::WebGPUSwapChain(wgpu::Surface&& surface, wgpu::Extent2D const& #endif } const bool useSRGBColorSpace = (flags & SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0; + const bool needStencil = (flags & SWAP_CHAIN_HAS_STENCIL_BUFFER) != 0; initConfig(mConfig, device, capabilities, surfaceSize, useSRGBColorSpace); + mDepthFormat = selectDepthFormat(device.HasFeature(wgpu::FeatureName::Depth32FloatStencil8), + needStencil); +#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM) + printSurfaceConfiguration(mConfig, mDepthFormat); +#endif mSurface.Configure(&mConfig); } @@ -235,12 +259,12 @@ void WebGPUSwapChain::setExtent(wgpu::Extent2D const& currentSurfaceSize) { if (mConfig.width != currentSurfaceSize.width || mConfig.height != currentSurfaceSize.height) { mConfig.width = currentSurfaceSize.width; mConfig.height = currentSurfaceSize.height; -#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM) - printSurfaceConfiguration(mConfig); -#endif FWGPU_LOGD << "Resizing to width " << mConfig.width << " height " << mConfig.height << utils::io::endl; - // TODO we may need to ensure no surface texture is flight when we do this. some +#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM) + printSurfaceConfiguration(mConfig, mDepthFormat); +#endif + // TODO we may need to ensure no surface texture is in flight when we do this. some // synchronization may be necessary mSurface.Configure(&mConfig); } @@ -257,7 +281,7 @@ wgpu::TextureView WebGPUSwapChain::getCurrentSurfaceTextureView( // Create a view for this surface texture // TODO: review these initiliazations as webgpu pipeline gets mature wgpu::TextureViewDescriptor textureViewDescriptor = { - .label = "texture_view", + .label = "surface_texture_view", .format = surfaceTexture.texture.GetFormat(), .dimension = wgpu::TextureViewDimension::e2D, .baseMipLevel = 0, diff --git a/filament/backend/src/webgpu/WebGPUSwapChain.h b/filament/backend/src/webgpu/WebGPUSwapChain.h index fa772a3bda..47ed7313ed 100644 --- a/filament/backend/src/webgpu/WebGPUSwapChain.h +++ b/filament/backend/src/webgpu/WebGPUSwapChain.h @@ -29,10 +29,14 @@ namespace filament::backend { class WebGPUSwapChain final : public Platform::SwapChain, HwSwapChain { public: WebGPUSwapChain(wgpu::Surface&& surface, wgpu::Extent2D const& surfaceSize, - wgpu::Adapter& adapter, wgpu::Device& device, uint64_t flags); + wgpu::Adapter const& adapter, wgpu::Device const& device, uint64_t flags); ~WebGPUSwapChain(); - wgpu::TextureView getCurrentSurfaceTextureView(wgpu::Extent2D const&); + [[nodiscard]] wgpu::TextureFormat getColorFormat() const { return mConfig.format; } + + [[nodiscard]] wgpu::TextureFormat getDepthFormat() const { return mDepthFormat; } + + [[nodiscard]] wgpu::TextureView getCurrentSurfaceTextureView(wgpu::Extent2D const&); void present(); @@ -41,6 +45,7 @@ private: wgpu::Surface mSurface = {}; wgpu::SurfaceConfiguration mConfig = {}; + wgpu::TextureFormat mDepthFormat = wgpu::TextureFormat::Undefined; }; } // namespace filament::backend diff --git a/filament/backend/src/webgpu/platform/WebGPUPlatform.cpp b/filament/backend/src/webgpu/platform/WebGPUPlatform.cpp index be2c5f0233..6cc264d055 100644 --- a/filament/backend/src/webgpu/platform/WebGPUPlatform.cpp +++ b/filament/backend/src/webgpu/platform/WebGPUPlatform.cpp @@ -98,10 +98,23 @@ wgpu::Adapter WebGPUPlatform::requestAdapter(wgpu::Surface const& surface) { } wgpu::Device WebGPUPlatform::requestDevice(wgpu::Adapter const& adapter) { - // TODO consider passing required features and/or limits + // TODO consider passing limits + constexpr std::array desiredFeatures = { + wgpu::FeatureName::DepthClipControl, + wgpu::FeatureName::Depth32FloatStencil8, + wgpu::FeatureName::CoreFeaturesAndLimits }; + std::vector requiredFeatures; + requiredFeatures.reserve(desiredFeatures.size()); + wgpu::SupportedFeatures supportedFeatures; + adapter.GetFeatures(&supportedFeatures); + std::set_intersection(supportedFeatures.features, + supportedFeatures.features + supportedFeatures.featureCount, desiredFeatures.begin(), + desiredFeatures.end(), std::back_inserter(requiredFeatures)); wgpu::DeviceDescriptor deviceDescriptor{}; deviceDescriptor.label = "graphics_device"; deviceDescriptor.defaultQueue.label = "default_queue"; + deviceDescriptor.requiredFeatureCount = requiredFeatures.size(); + deviceDescriptor.requiredFeatures = requiredFeatures.data(); deviceDescriptor.SetDeviceLostCallback(wgpu::CallbackMode::AllowSpontaneous, [](wgpu::Device const&, wgpu::DeviceLostReason const& reason, wgpu::StringView message) {