#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mbgl { namespace webgpu { namespace { std::string trimString(std::string_view value) { auto begin = value.begin(); auto end = value.end(); while (begin != end && std::isspace(static_cast(*begin))) { ++begin; } while (begin != end) { auto prev = end - 1; if (!std::isspace(static_cast(*prev))) { break; } end = prev; } return std::string(begin, end); } WGPUBlendOperation webgpuBlendOperation(const gfx::ColorBlendEquationType& equation) { switch (equation) { case gfx::ColorBlendEquationType::Add: return WGPUBlendOperation_Add; case gfx::ColorBlendEquationType::Subtract: return WGPUBlendOperation_Subtract; case gfx::ColorBlendEquationType::ReverseSubtract: return WGPUBlendOperation_ReverseSubtract; default: return WGPUBlendOperation_Add; } } WGPUBlendFactor webgpuBlendFactor(const gfx::ColorBlendFactorType& factor) { switch (factor) { case gfx::ColorBlendFactorType::Zero: return WGPUBlendFactor_Zero; case gfx::ColorBlendFactorType::One: return WGPUBlendFactor_One; case gfx::ColorBlendFactorType::SrcColor: return WGPUBlendFactor_Src; case gfx::ColorBlendFactorType::OneMinusSrcColor: return WGPUBlendFactor_OneMinusSrc; case gfx::ColorBlendFactorType::SrcAlpha: return WGPUBlendFactor_SrcAlpha; case gfx::ColorBlendFactorType::OneMinusSrcAlpha: return WGPUBlendFactor_OneMinusSrcAlpha; case gfx::ColorBlendFactorType::DstAlpha: return WGPUBlendFactor_DstAlpha; case gfx::ColorBlendFactorType::OneMinusDstAlpha: return WGPUBlendFactor_OneMinusDstAlpha; case gfx::ColorBlendFactorType::DstColor: return WGPUBlendFactor_Dst; case gfx::ColorBlendFactorType::OneMinusDstColor: return WGPUBlendFactor_OneMinusDst; case gfx::ColorBlendFactorType::SrcAlphaSaturate: return WGPUBlendFactor_SrcAlphaSaturated; case gfx::ColorBlendFactorType::ConstantColor: return WGPUBlendFactor_Constant; case gfx::ColorBlendFactorType::OneMinusConstantColor: return WGPUBlendFactor_OneMinusConstant; case gfx::ColorBlendFactorType::ConstantAlpha: return WGPUBlendFactor_Constant; case gfx::ColorBlendFactorType::OneMinusConstantAlpha: return WGPUBlendFactor_OneMinusConstant; default: return WGPUBlendFactor_One; } } WGPUPrimitiveTopology toPrimitiveTopology(gfx::DrawModeType drawModeType) { switch (drawModeType) { case gfx::DrawModeType::Points: return WGPUPrimitiveTopology_PointList; case gfx::DrawModeType::Lines: case gfx::DrawModeType::LineLoop: return WGPUPrimitiveTopology_LineList; case gfx::DrawModeType::LineStrip: return WGPUPrimitiveTopology_LineStrip; case gfx::DrawModeType::TriangleStrip: return WGPUPrimitiveTopology_TriangleStrip; case gfx::DrawModeType::TriangleFan: // WebGPU does not support triangle fan directly; approximate with triangle list return WGPUPrimitiveTopology_TriangleList; case gfx::DrawModeType::Triangles: default: return WGPUPrimitiveTopology_TriangleList; } } WGPUCompareFunction toCompareFunction(const gfx::DepthFunctionType func) { switch (func) { case gfx::DepthFunctionType::Never: return WGPUCompareFunction_Never; case gfx::DepthFunctionType::Less: return WGPUCompareFunction_Less; case gfx::DepthFunctionType::Equal: return WGPUCompareFunction_Equal; case gfx::DepthFunctionType::LessEqual: return WGPUCompareFunction_LessEqual; case gfx::DepthFunctionType::Greater: return WGPUCompareFunction_Greater; case gfx::DepthFunctionType::NotEqual: return WGPUCompareFunction_NotEqual; case gfx::DepthFunctionType::GreaterEqual: return WGPUCompareFunction_GreaterEqual; case gfx::DepthFunctionType::Always: default: return WGPUCompareFunction_Always; } } WGPUCompareFunction toCompareFunction(const gfx::StencilFunctionType func) { switch (func) { case gfx::StencilFunctionType::Never: return WGPUCompareFunction_Never; case gfx::StencilFunctionType::Less: return WGPUCompareFunction_Less; case gfx::StencilFunctionType::Equal: return WGPUCompareFunction_Equal; case gfx::StencilFunctionType::LessEqual: return WGPUCompareFunction_LessEqual; case gfx::StencilFunctionType::Greater: return WGPUCompareFunction_Greater; case gfx::StencilFunctionType::NotEqual: return WGPUCompareFunction_NotEqual; case gfx::StencilFunctionType::GreaterEqual: return WGPUCompareFunction_GreaterEqual; case gfx::StencilFunctionType::Always: default: return WGPUCompareFunction_Always; } } WGPUStencilOperation toStencilOp(const gfx::StencilOpType op) { switch (op) { case gfx::StencilOpType::Zero: return WGPUStencilOperation_Zero; case gfx::StencilOpType::Keep: return WGPUStencilOperation_Keep; case gfx::StencilOpType::Replace: return WGPUStencilOperation_Replace; case gfx::StencilOpType::Increment: return WGPUStencilOperation_IncrementClamp; case gfx::StencilOpType::Decrement: return WGPUStencilOperation_DecrementClamp; case gfx::StencilOpType::Invert: return WGPUStencilOperation_Invert; case gfx::StencilOpType::IncrementWrap: return WGPUStencilOperation_IncrementWrap; case gfx::StencilOpType::DecrementWrap: return WGPUStencilOperation_DecrementWrap; default: return WGPUStencilOperation_Keep; } } } // namespace ShaderProgram::ShaderProgram(std::string name, RendererBackend& backend_, WGPUShaderModule vertexModule, WGPUShaderModule fragmentModule) : ShaderProgramBase(), shaderName(std::move(name)), backend(backend_), vertexShaderModule(vertexModule), fragmentShaderModule(fragmentModule) { createPipelineLayout({}, {}); } // Minimal constructor for Context::createShader compatibility ShaderProgram::ShaderProgram(Context& context, const std::string& vertexSource, const std::string& fragmentSource) : ShaderProgramBase(), shaderName("ContextShader"), backend(static_cast(context.getBackend())) { WGPUDevice device = static_cast(backend.getDevice()); if (!device) return; std::string trimmedVertex = trimString(vertexSource); std::string trimmedFragment = trimString(fragmentSource); hasVertexEntryPoint = !trimmedVertex.empty(); hasFragmentEntryPoint = !trimmedFragment.empty(); // Get the prelude from common.hpp using PreludeShader = shaders::ShaderSource; std::string vertexWithPrelude; if (hasVertexEntryPoint) { vertexWithPrelude = std::string(PreludeShader::prelude) + "\n" + vertexSource; WGPUShaderSourceWGSL wgslDesc = {}; wgslDesc.chain.sType = WGPUSType_ShaderSourceWGSL; wgslDesc.chain.next = nullptr; WGPUStringView vertexCode = {vertexWithPrelude.c_str(), vertexWithPrelude.length()}; wgslDesc.code = vertexCode; WGPUShaderModuleDescriptor vertexShaderDesc = {}; WGPUStringView vertexLabel = {"Vertex Shader Module", strlen("Vertex Shader Module")}; vertexShaderDesc.label = vertexLabel; vertexShaderDesc.nextInChain = (WGPUChainedStruct*)&wgslDesc; vertexShaderModule = wgpuDeviceCreateShaderModule(device, &vertexShaderDesc); if (!vertexShaderModule) { Log::Error(Event::Render, "Failed to create vertex shader module for " + shaderName); Log::Error(Event::Render, "Vertex shader source length: " + std::to_string(vertexWithPrelude.length())); if (shaderName.find("CircleShader") != std::string::npos) { std::ofstream dump("/tmp/circle_vertex.wgsl", std::ios::trunc); dump << vertexSource; } } } std::string fragmentWithPrelude; if (hasFragmentEntryPoint) { fragmentWithPrelude = std::string(PreludeShader::prelude) + "\n" + fragmentSource; WGPUShaderSourceWGSL wgslDesc = {}; wgslDesc.chain.sType = WGPUSType_ShaderSourceWGSL; wgslDesc.chain.next = nullptr; WGPUStringView fragmentCode = {fragmentWithPrelude.c_str(), fragmentWithPrelude.length()}; wgslDesc.code = fragmentCode; WGPUShaderModuleDescriptor fragmentShaderDesc = {}; WGPUStringView fragmentLabel = {"Fragment Shader Module", strlen("Fragment Shader Module")}; fragmentShaderDesc.label = fragmentLabel; fragmentShaderDesc.nextInChain = (WGPUChainedStruct*)&wgslDesc; fragmentShaderModule = wgpuDeviceCreateShaderModule(device, &fragmentShaderDesc); if (!fragmentShaderModule) { Log::Error(Event::Render, "Failed to create fragment shader module for " + shaderName); Log::Error(Event::Render, "Fragment shader source length: " + std::to_string(fragmentWithPrelude.length())); if (shaderName.find("CircleShader") != std::string::npos) { std::ofstream dump("/tmp/circle_fragment.wgsl", std::ios::trunc); dump << fragmentSource; } } } createPipelineLayout(vertexWithPrelude, fragmentWithPrelude); } ShaderProgram::~ShaderProgram() { // Release cached pipelines for (auto& pair : renderPipelineCache) { if (pair.second) { wgpuRenderPipelineRelease(pair.second); } } renderPipelineCache.clear(); // Release resources if (pipelineLayout) { wgpuPipelineLayoutRelease(pipelineLayout); pipelineLayout = nullptr; } for (auto layout : bindGroupLayouts) { if (layout) { wgpuBindGroupLayoutRelease(layout); } } bindGroupLayouts.clear(); bindGroupOrder.clear(); bindingsPerGroup.clear(); // Release shader modules if we created them if (vertexShaderModule) { wgpuShaderModuleRelease(vertexShaderModule); vertexShaderModule = nullptr; } if (fragmentShaderModule) { wgpuShaderModuleRelease(fragmentShaderModule); fragmentShaderModule = nullptr; } } WGPURenderPipeline ShaderProgram::getRenderPipeline(const gfx::Renderable& renderable, const WGPUVertexBufferLayout* vertexLayouts, uint32_t vertexLayoutCount, const gfx::ColorMode& colorMode, const gfx::DepthMode& depthMode, const gfx::StencilMode& stencilMode, gfx::DrawModeType drawModeType, const std::optional reuseHash) { (void)renderable; // Check cache first if (reuseHash.has_value()) { auto it = renderPipelineCache.find(reuseHash.value()); if (it != renderPipelineCache.end()) { return it->second; } } // Create new pipeline WGPURenderPipeline pipeline = createPipeline( vertexLayouts, vertexLayoutCount, colorMode, depthMode, stencilMode, drawModeType); // Cache the pipeline if we have a reuse hash if (reuseHash.has_value() && pipeline) { renderPipelineCache[reuseHash.value()] = pipeline; } return pipeline; } std::optional ShaderProgram::getSamplerLocation(const size_t id) const { return (id < textureBindings.size()) ? textureBindings[id] : std::nullopt; } void ShaderProgram::initAttribute(const shaders::AttributeInfo& info) { const auto index = static_cast(info.index); #if !defined(NDEBUG) // Indexes must be unique, if there's a conflict check the `attributes` array in the shader vertexAttributes.visitAttributes([&](const gfx::VertexAttribute& attrib) { assert(attrib.getIndex() != index); }); instanceAttributes.visitAttributes([&](const gfx::VertexAttribute& attrib) { assert(attrib.getIndex() != index); }); #endif vertexAttributes.set(info.id, index, info.dataType, 1); } void ShaderProgram::initInstanceAttribute(const shaders::AttributeInfo& info) { // Index is the block index of the instance attribute const auto index = static_cast(info.index); #if !defined(NDEBUG) // Indexes must not be reused by regular attributes or uniform blocks // More than one instance attribute can have the same index, if they share the block vertexAttributes.visitAttributes([&](const gfx::VertexAttribute& attrib) { assert(attrib.getIndex() != index); }); #endif instanceAttributes.set(info.id, index, info.dataType, 1); } void ShaderProgram::initTexture(const shaders::TextureInfo& info) { assert(info.id < textureBindings.size()); if (info.id >= textureBindings.size()) { return; } textureBindings[info.id] = info.index; } const std::vector& ShaderProgram::getBindingInfosForGroup(uint32_t group) const { for (size_t i = 0; i < bindGroupOrder.size(); ++i) { if (bindGroupOrder[i] == group) { return bindingsPerGroup[i]; } } static const std::vector empty; return empty; } WGPUBindGroupLayout ShaderProgram::getBindGroupLayout(uint32_t group) const { for (size_t i = 0; i < bindGroupOrder.size(); ++i) { if (bindGroupOrder[i] == group) { return bindGroupLayouts[i]; } } return nullptr; } void ShaderProgram::createPipelineLayout(const std::string& vertexSource, const std::string& fragmentSource) { WGPUDevice device = static_cast(backend.getDevice()); if (!device) { return; } if (!vertexSource.empty() || !fragmentSource.empty()) { bindingInfos.clear(); if (!vertexSource.empty()) { analyzeShaderBindings(vertexSource, WGPUShaderStage_Vertex); } if (!fragmentSource.empty()) { analyzeShaderBindings(fragmentSource, WGPUShaderStage_Fragment); } } rebuildBindGroupLayouts(); if (pipelineLayout) { wgpuPipelineLayoutRelease(pipelineLayout); pipelineLayout = nullptr; } std::vector layoutHandles; layoutHandles.reserve(bindGroupLayouts.size()); for (const auto& layout : bindGroupLayouts) { layoutHandles.push_back(layout); } WGPUPipelineLayoutDescriptor pipelineLayoutDesc = {}; WGPUStringView pipelineLayoutLabel = {"Pipeline Layout", strlen("Pipeline Layout")}; pipelineLayoutDesc.label = pipelineLayoutLabel; pipelineLayoutDesc.bindGroupLayoutCount = layoutHandles.size(); pipelineLayoutDesc.bindGroupLayouts = layoutHandles.empty() ? nullptr : layoutHandles.data(); pipelineLayout = wgpuDeviceCreatePipelineLayout(device, &pipelineLayoutDesc); } void ShaderProgram::analyzeShaderBindings(const std::string& source, WGPUShaderStage stage) { size_t searchPos = 0; while (true) { size_t groupToken = source.find("@group", searchPos); if (groupToken == std::string::npos) { break; } size_t groupParen = source.find('(', groupToken); if (groupParen == std::string::npos) { break; } size_t groupStart = groupParen + 1; while (groupStart < source.size() && std::isspace(static_cast(source[groupStart]))) { ++groupStart; } size_t groupEnd = groupStart; while (groupEnd < source.size() && std::isdigit(static_cast(source[groupEnd]))) { ++groupEnd; } if (groupEnd == groupStart) { searchPos = groupEnd; continue; } uint32_t group = static_cast( std::strtoul(source.substr(groupStart, groupEnd - groupStart).c_str(), nullptr, 10)); size_t bindingToken = source.find("@binding", groupEnd); if (bindingToken == std::string::npos) { break; } size_t bindingParen = source.find('(', bindingToken); if (bindingParen == std::string::npos) { break; } size_t bindingStart = bindingParen + 1; while (bindingStart < source.size() && std::isspace(static_cast(source[bindingStart]))) { ++bindingStart; } size_t bindingEnd = bindingStart; while (bindingEnd < source.size() && std::isdigit(static_cast(source[bindingEnd]))) { ++bindingEnd; } if (bindingEnd == bindingStart) { searchPos = bindingEnd; continue; } uint32_t binding = static_cast( std::strtoul(source.substr(bindingStart, bindingEnd - bindingStart).c_str(), nullptr, 10)); size_t varPos = source.find("var", bindingEnd); if (varPos == std::string::npos) { break; } if (varPos > bindingEnd && std::isalpha(static_cast(source[varPos - 1]))) { searchPos = varPos + 3; continue; } size_t afterVar = varPos + 3; while (afterVar < source.size() && std::isspace(static_cast(source[afterVar]))) { ++afterVar; } BindingType type = BindingType::UniformBuffer; if (afterVar < source.size() && source[afterVar] == '<') { size_t qualifierEnd = source.find('>', afterVar); if (qualifierEnd == std::string::npos) { break; } const auto qualifier = trimString( std::string_view(source.data() + afterVar + 1, qualifierEnd - afterVar - 1)); if (qualifier.find("storage") != std::string::npos) { if (qualifier.find("read_write") != std::string::npos) { type = BindingType::StorageBuffer; } else { type = BindingType::ReadOnlyStorageBuffer; } } else { type = BindingType::UniformBuffer; } afterVar = qualifierEnd + 1; } size_t colonPos = source.find(':', afterVar); if (colonPos == std::string::npos) { break; } // Extract the variable name const auto varName = trimString(std::string_view(source.data() + afterVar, colonPos - afterVar)); size_t typeStart = colonPos + 1; size_t typeEnd = typeStart; while (typeEnd < source.size()) { char c = source[typeEnd]; if (c == ';' || c == '\n' || c == '\r') { break; } ++typeEnd; } const auto typeName = trimString(std::string_view(source.data() + typeStart, typeEnd - typeStart)); if (type == BindingType::UniformBuffer) { if (typeName.find("sampler") != std::string::npos) { type = BindingType::Sampler; } else if (typeName.find("texture") != std::string::npos) { // Check if this texture is used with textureLoad (unfilterable) or textureSample (filterable) // Look for textureLoad(varName, which indicates it's used without filtering std::string textureLoadPattern = std::string("textureLoad(") + std::string(varName); std::string textureSamplePattern = std::string("textureSample(") + std::string(varName); bool usesTextureLoad = source.find(textureLoadPattern) != std::string::npos; bool usesTextureSample = source.find(textureSamplePattern) != std::string::npos; // If only textureLoad is used (not textureSample), mark as unfilterable if (usesTextureLoad && !usesTextureSample) { type = BindingType::UnfilterableTexture; } else { type = BindingType::Texture; } } } auto existing = std::find_if(bindingInfos.begin(), bindingInfos.end(), [&](const BindingInfo& info) { return info.group == group && info.binding == binding; }); if (existing == bindingInfos.end()) { bindingInfos.push_back({group, binding, type, stage}); } else { existing->visibility |= stage; if (existing->type == BindingType::UniformBuffer && type != BindingType::UniformBuffer) { existing->type = type; } } searchPos = typeEnd; } } void ShaderProgram::rebuildBindGroupLayouts() { WGPUDevice device = static_cast(backend.getDevice()); if (!device) { return; } for (auto layout : bindGroupLayouts) { if (layout) { wgpuBindGroupLayoutRelease(layout); } } bindGroupLayouts.clear(); bindGroupOrder.clear(); bindingsPerGroup.clear(); if (bindingInfos.empty()) { bindingInfos.push_back({0u, 0u, BindingType::UniformBuffer, WGPUShaderStage_Vertex | WGPUShaderStage_Fragment}); } std::map> grouped; for (const auto& info : bindingInfos) { grouped[info.group].push_back(info); } for (auto& entry : grouped) { auto& infos = entry.second; std::sort(infos.begin(), infos.end(), [](const BindingInfo& lhs, const BindingInfo& rhs) { return lhs.binding < rhs.binding; }); std::vector layoutEntries; layoutEntries.reserve(infos.size()); for (const auto& info : infos) { WGPUBindGroupLayoutEntry layoutEntry = {}; layoutEntry.binding = info.binding; layoutEntry.visibility = info.visibility; switch (info.type) { case BindingType::UniformBuffer: layoutEntry.buffer.type = WGPUBufferBindingType_Uniform; layoutEntry.buffer.hasDynamicOffset = 0; layoutEntry.buffer.minBindingSize = 0; break; case BindingType::ReadOnlyStorageBuffer: layoutEntry.buffer.type = WGPUBufferBindingType_ReadOnlyStorage; layoutEntry.buffer.hasDynamicOffset = 0; layoutEntry.buffer.minBindingSize = 0; break; case BindingType::StorageBuffer: layoutEntry.buffer.type = WGPUBufferBindingType_Storage; layoutEntry.buffer.hasDynamicOffset = 0; layoutEntry.buffer.minBindingSize = 0; break; case BindingType::Sampler: layoutEntry.sampler.type = WGPUSamplerBindingType_Filtering; break; case BindingType::Texture: layoutEntry.texture.sampleType = WGPUTextureSampleType_Float; layoutEntry.texture.viewDimension = WGPUTextureViewDimension_2D; layoutEntry.texture.multisampled = false; break; case BindingType::UnfilterableTexture: layoutEntry.texture.sampleType = WGPUTextureSampleType_UnfilterableFloat; layoutEntry.texture.viewDimension = WGPUTextureViewDimension_2D; layoutEntry.texture.multisampled = false; break; } layoutEntries.push_back(layoutEntry); } WGPUBindGroupLayoutDescriptor desc = {}; const std::string labelStr = "Shader Bind Group " + std::to_string(entry.first); WGPUStringView labelView = {labelStr.c_str(), labelStr.length()}; desc.label = labelView; desc.entryCount = layoutEntries.size(); desc.entries = layoutEntries.data(); WGPUBindGroupLayout layout = wgpuDeviceCreateBindGroupLayout(device, &desc); if (layout) { bindGroupOrder.push_back(entry.first); bindingsPerGroup.push_back(infos); bindGroupLayouts.push_back(layout); } } } WGPURenderPipeline ShaderProgram::createPipeline(const WGPUVertexBufferLayout* vertexLayouts, uint32_t vertexLayoutCount, const gfx::ColorMode& colorMode, const gfx::DepthMode& depthMode, const gfx::StencilMode& stencilMode, gfx::DrawModeType drawModeType) { WGPUDevice device = static_cast(backend.getDevice()); if (!hasVertexEntryPoint || !hasFragmentEntryPoint) { if (!loggedMissingEntryPoint) { Log::Warning( Event::Render, "WebGPU: shader '" + shaderName + "' is missing WGSL entry points; skipping pipeline creation"); loggedMissingEntryPoint = true; } return nullptr; } if (!device || !vertexShaderModule || !fragmentShaderModule || !pipelineLayout) { Log::Error(Event::Render, "createPipeline missing requirement: device=" + std::to_string(device != nullptr) + ", vertexModule=" + std::to_string(vertexShaderModule != nullptr) + ", fragmentModule=" + std::to_string(fragmentShaderModule != nullptr) + ", pipelineLayout=" + std::to_string(pipelineLayout != nullptr)); return nullptr; } // Set up vertex state WGPUVertexState vertexState = {}; vertexState.module = vertexShaderModule; WGPUStringView vertexEntryPoint = {"main", strlen("main")}; vertexState.entryPoint = vertexEntryPoint; vertexState.bufferCount = vertexLayoutCount; vertexState.buffers = vertexLayouts; // Set up fragment state with color mode blending WGPUBlendComponent alphaBlend = {}; alphaBlend.operation = webgpuBlendOperation(gfx::ColorBlendEquationType::Add); alphaBlend.srcFactor = webgpuBlendFactor(gfx::ColorBlendFactorType::SrcAlpha); alphaBlend.dstFactor = webgpuBlendFactor(gfx::ColorBlendFactorType::OneMinusSrcAlpha); WGPUBlendComponent colorBlend = {}; colorBlend.operation = webgpuBlendOperation(gfx::ColorBlendEquationType::Add); colorBlend.srcFactor = webgpuBlendFactor(gfx::ColorBlendFactorType::SrcAlpha); colorBlend.dstFactor = webgpuBlendFactor(gfx::ColorBlendFactorType::OneMinusSrcAlpha); // Apply color mode blending if not replace if (!colorMode.blendFunction.is()) { colorMode.blendFunction.match([&](const auto& blendFunction) { colorBlend.operation = webgpuBlendOperation(gfx::ColorBlendEquationType(blendFunction.equation)); colorBlend.srcFactor = webgpuBlendFactor(gfx::ColorBlendFactorType(blendFunction.srcFactor)); colorBlend.dstFactor = webgpuBlendFactor(gfx::ColorBlendFactorType(blendFunction.dstFactor)); alphaBlend = colorBlend; }); } WGPUBlendState blendState = {}; blendState.color = colorBlend; blendState.alpha = alphaBlend; WGPUColorTargetState colorTarget = {}; auto colorFormat = backend.getColorFormat(); if (colorFormat == wgpu::TextureFormat::Undefined) { colorFormat = wgpu::TextureFormat::BGRA8Unorm; } colorTarget.format = static_cast(colorFormat); colorTarget.blend = &blendState; colorTarget.writeMask = (colorMode.mask.r ? WGPUColorWriteMask_Red : WGPUColorWriteMask_None) | (colorMode.mask.g ? WGPUColorWriteMask_Green : WGPUColorWriteMask_None) | (colorMode.mask.b ? WGPUColorWriteMask_Blue : WGPUColorWriteMask_None) | (colorMode.mask.a ? WGPUColorWriteMask_Alpha : WGPUColorWriteMask_None); WGPUFragmentState fragmentState = {}; fragmentState.module = fragmentShaderModule; WGPUStringView fragmentEntryPoint = {"main", strlen("main")}; fragmentState.entryPoint = fragmentEntryPoint; fragmentState.targetCount = 1; fragmentState.targets = &colorTarget; // Set up primitive state WGPUPrimitiveState primitiveState = {}; primitiveState.topology = toPrimitiveTopology(drawModeType); primitiveState.stripIndexFormat = (drawModeType == gfx::DrawModeType::LineStrip || drawModeType == gfx::DrawModeType::TriangleStrip) ? WGPUIndexFormat_Uint16 : WGPUIndexFormat_Undefined; primitiveState.frontFace = WGPUFrontFace_CCW; primitiveState.cullMode = WGPUCullMode_None; // Set up depth stencil state WGPUDepthStencilState depthStencilState = {}; const auto depthFormat = backend.getDepthStencilFormat(); if (depthFormat != wgpu::TextureFormat::Undefined) { depthStencilState.format = static_cast(depthFormat); depthStencilState.depthWriteEnabled = depthMode.mask == gfx::DepthMaskType::ReadWrite ? WGPUOptionalBool_True : WGPUOptionalBool_False; depthStencilState.depthCompare = toCompareFunction(depthMode.func); depthStencilState.stencilFront.compare = WGPUCompareFunction_Always; depthStencilState.stencilFront.failOp = WGPUStencilOperation_Keep; depthStencilState.stencilFront.depthFailOp = WGPUStencilOperation_Keep; depthStencilState.stencilFront.passOp = WGPUStencilOperation_Keep; depthStencilState.stencilBack = depthStencilState.stencilFront; depthStencilState.stencilReadMask = 0; depthStencilState.stencilWriteMask = 0; bool stencilEnabled = true; if (stencilMode.test.is() && stencilMode.mask == 0 && stencilMode.fail == gfx::StencilOpType::Keep && stencilMode.depthFail == gfx::StencilOpType::Keep && stencilMode.pass == gfx::StencilOpType::Keep) { stencilEnabled = false; } if (stencilEnabled) { gfx::StencilFunctionType function = gfx::StencilFunctionType::Always; uint32_t readMask = 0xFF; mapbox::util::apply_visitor( [&](const auto& test) { using TestType = std::decay_t; function = TestType::func; if constexpr (requires { test.mask; }) { readMask = test.mask; } else { readMask = 0xFF; } }, stencilMode.test); depthStencilState.stencilFront.compare = toCompareFunction(function); depthStencilState.stencilBack = depthStencilState.stencilFront; depthStencilState.stencilFront.failOp = toStencilOp(stencilMode.fail); depthStencilState.stencilFront.depthFailOp = toStencilOp(stencilMode.depthFail); depthStencilState.stencilFront.passOp = toStencilOp(stencilMode.pass); depthStencilState.stencilBack = depthStencilState.stencilFront; depthStencilState.stencilReadMask = readMask; depthStencilState.stencilWriteMask = stencilMode.mask; } if (shaderName == "ClippingMaskProgram") { depthStencilState.stencilFront.passOp = WGPUStencilOperation_Replace; depthStencilState.stencilBack = depthStencilState.stencilFront; if (depthStencilState.stencilWriteMask == 0) { depthStencilState.stencilWriteMask = 0xFF; } if (depthStencilState.stencilReadMask == 0) { depthStencilState.stencilReadMask = 0xFF; } depthStencilState.stencilFront.compare = WGPUCompareFunction_Always; depthStencilState.stencilBack = depthStencilState.stencilFront; } } // Create render pipeline WGPURenderPipelineDescriptor pipelineDesc = {}; WGPUStringView pipelineLabel = {shaderName.c_str(), shaderName.length()}; pipelineDesc.label = pipelineLabel; pipelineDesc.layout = pipelineLayout; pipelineDesc.vertex = vertexState; pipelineDesc.fragment = &fragmentState; pipelineDesc.primitive = primitiveState; pipelineDesc.depthStencil = depthFormat != wgpu::TextureFormat::Undefined ? &depthStencilState : nullptr; pipelineDesc.multisample.count = 1; pipelineDesc.multisample.mask = 0xFFFFFFFF; pipelineDesc.multisample.alphaToCoverageEnabled = 0; WGPURenderPipeline pipeline = wgpuDeviceCreateRenderPipeline(device, &pipelineDesc); if (!pipeline) { Log::Error(Event::Render, shaderName + " pipeline creation failed"); } return pipeline; } } // namespace webgpu } // namespace mbgl