// Copyright (c) Wojciech Figat. All rights reserved. #if GRAPHICS_API_WEBGPU #include "GPUPipelineStateWebGPU.h" #include "GPUTextureWebGPU.h" #include "GPUVertexLayoutWebGPU.h" #include "RenderToolsWebGPU.h" #include "Engine/Core/Log.h" #include "Engine/Engine/Engine.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Profiler/ProfilerMemory.h" #include "Engine/Graphics/PixelFormatExtensions.h" #include "Engine/Utilities/Crc.h" #define WEBGPU_LOG_PSO 0 #define WEBGPU_LOG_BIND_GROUPS 0 WGPUCompareFunction ToCompareFunction(ComparisonFunc value) { switch (value) { case ComparisonFunc::Never: return WGPUCompareFunction_Never; case ComparisonFunc::Less: return WGPUCompareFunction_Less; case ComparisonFunc::Equal: return WGPUCompareFunction_Equal; case ComparisonFunc::LessEqual: return WGPUCompareFunction_LessEqual; case ComparisonFunc::Greater: return WGPUCompareFunction_Greater; case ComparisonFunc::NotEqual: return WGPUCompareFunction_NotEqual; case ComparisonFunc::GreaterEqual: return WGPUCompareFunction_GreaterEqual; case ComparisonFunc::Always: return WGPUCompareFunction_Always; default: return WGPUCompareFunction_Undefined; } } WGPUStencilOperation ToStencilOperation(StencilOperation value) { switch (value) { case StencilOperation::Keep: return WGPUStencilOperation_Keep; case StencilOperation::Zero: return WGPUStencilOperation_Zero; case StencilOperation::Replace: return WGPUStencilOperation_Replace; case StencilOperation::IncrementSaturated: return WGPUStencilOperation_IncrementClamp; case StencilOperation::DecrementSaturated: return WGPUStencilOperation_DecrementClamp; case StencilOperation::Invert: return WGPUStencilOperation_Invert; case StencilOperation::Increment: return WGPUStencilOperation_IncrementWrap; case StencilOperation::Decrement: return WGPUStencilOperation_DecrementWrap; default: return WGPUStencilOperation_Undefined; } } WGPUBlendFactor ToBlendFactor(BlendingMode::Blend value) { switch (value) { case BlendingMode::Blend::Zero: return WGPUBlendFactor_Zero; case BlendingMode::Blend::One: return WGPUBlendFactor_One; case BlendingMode::Blend::SrcColor: return WGPUBlendFactor_Src; case BlendingMode::Blend::InvSrcColor: return WGPUBlendFactor_OneMinusSrc; case BlendingMode::Blend::SrcAlpha: return WGPUBlendFactor_SrcAlpha; case BlendingMode::Blend::InvSrcAlpha: return WGPUBlendFactor_OneMinusSrcAlpha; case BlendingMode::Blend::DestAlpha: return WGPUBlendFactor_DstAlpha; case BlendingMode::Blend::InvDestAlpha: return WGPUBlendFactor_OneMinusDstAlpha; case BlendingMode::Blend::DestColor: return WGPUBlendFactor_Dst; case BlendingMode::Blend::InvDestColor: return WGPUBlendFactor_OneMinusDst; case BlendingMode::Blend::SrcAlphaSat: return WGPUBlendFactor_SrcAlphaSaturated; case BlendingMode::Blend::BlendFactor: return WGPUBlendFactor_Constant; case BlendingMode::Blend::BlendInvFactor: return WGPUBlendFactor_OneMinusConstant; case BlendingMode::Blend::Src1Color: return WGPUBlendFactor_Src1; case BlendingMode::Blend::InvSrc1Color: return WGPUBlendFactor_OneMinusSrc1; case BlendingMode::Blend::Src1Alpha: return WGPUBlendFactor_Src1Alpha; case BlendingMode::Blend::InvSrc1Alpha: return WGPUBlendFactor_OneMinusSrc1Alpha; default: return WGPUBlendFactor_Undefined; } } WGPUBlendComponent ToBlendComponent(BlendingMode::Operation blendOp, BlendingMode::Blend srcBlend, BlendingMode::Blend dstBlend) { WGPUBlendComponent result; switch (blendOp) { case BlendingMode::Operation::Add: result.operation = WGPUBlendOperation_Add; break; case BlendingMode::Operation::Subtract: result.operation = WGPUBlendOperation_Subtract; break; case BlendingMode::Operation::RevSubtract: result.operation = WGPUBlendOperation_ReverseSubtract; break; case BlendingMode::Operation::Min: result.operation = WGPUBlendOperation_Min; break; case BlendingMode::Operation::Max: result.operation = WGPUBlendOperation_Max; break; default: result.operation = WGPUBlendOperation_Undefined; break; } result.srcFactor = ToBlendFactor(srcBlend); result.dstFactor = ToBlendFactor(dstBlend); return result; } void GPUPipelineStateWebGPU::OnReleaseGPU() { VS = nullptr; PS = nullptr; for (auto& e : _bindGroups) wgpuBindGroupRelease(e.Value); _bindGroups.Clear(); for (auto& e : _pipelines) wgpuRenderPipelineRelease(e.Value); _pipelines.Clear(); for (auto& e : BindGroupLayouts) { if (e) { wgpuBindGroupLayoutRelease(e); e = nullptr; } } if (PipelineDesc.layout) { wgpuPipelineLayoutRelease(PipelineDesc.layout); PipelineDesc.layout = nullptr; } Platform::MemoryClear(&BindGroupDescriptors, sizeof(BindGroupDescriptors)); } uint32 GetHash(const GPUPipelineStateWebGPU::PipelineKey& key) { static_assert(sizeof(GPUPipelineStateWebGPU::PipelineKey) == sizeof(uint64) * 2, "Invalid PSO key size."); uint32 hash = GetHash(key.Packed[0]); CombineHash(hash, GetHash(key.Packed[1])); return hash; } uint32 GetHash(const GPUPipelineStateWebGPU::BindGroupKey& key) { return key.Hash; } bool GPUPipelineStateWebGPU::BindGroupKey::operator==(const BindGroupKey& other) const { return Hash == other.Hash && Layout == other.Layout && EntriesCount == other.EntriesCount && Platform::MemoryCompare(&Entries, &other.Entries, EntriesCount * sizeof(WGPUBindGroupEntry)) == 0 && Platform::MemoryCompare(&Versions, &other.Versions, EntriesCount * sizeof(uint8)) == 0; } WGPURenderPipeline GPUPipelineStateWebGPU::GetPipeline(const PipelineKey& key, GPUResourceView* shaderResources[GPU_MAX_SR_BINDED]) { WGPURenderPipeline pipeline; if (_pipelines.TryGet(key, pipeline)) return pipeline; PROFILE_CPU(); PROFILE_MEM(GraphicsCommands); #if GPU_ENABLE_RESOURCE_NAMING ZoneText(_debugName.Get(), _debugName.Count() - 1); #endif #if WEBGPU_LOG_PSO LOG(Info, "[WebGPU] GetPipeline: '{}'", String(_debugName.Get(), _debugName.Count() - 1)); #endif // Lazy-init layout (cannot do it during Init as texture samplers that access eg. depth need to explicitly use UnfilterableFloat) if (!PipelineDesc.layout) InitLayout(shaderResources); // Build final pipeline description _depthStencilDesc.format = (WGPUTextureFormat)key.DepthStencilFormat; PipelineDesc.depthStencil = key.DepthStencilFormat ? &_depthStencilDesc : nullptr; // Unbind depth stencil state when no debug buffer is bound PipelineDesc.multisample.count = key.MultiSampleCount; if (PS) { _fragmentDesc.targetCount = key.RenderTargetCount; for (int32 i = 0; i < _fragmentDesc.targetCount; i++) _colorTargets[i].format = (WGPUTextureFormat)key.RenderTargetFormats[i]; } WGPUVertexBufferLayout buffers[GPU_MAX_VB_BINDED]; if (key.VertexLayout) { // Combine input layout of Vertex Buffers with the destination layout used by the Vertex Shader GPUVertexLayoutWebGPU* mergedVertexLayout = key.VertexLayout; if (!mergedVertexLayout) mergedVertexLayout = (GPUVertexLayoutWebGPU*)VS->Layout; // Fallback to shader-specified layout (if using old APIs) if (VS->InputLayout) mergedVertexLayout = (GPUVertexLayoutWebGPU*)GPUVertexLayout::Merge(mergedVertexLayout, VS->InputLayout, false, true, -1, true); // Build attributes list WGPUVertexAttribute attributes[GPU_MAX_VS_ELEMENTS]; PipelineDesc.vertex.bufferCount = 0; PipelineDesc.vertex.buffers = buffers; int32 attributeIndex = 0; auto& elements = mergedVertexLayout->GetElements(); for (int32 bufferIndex = 0; bufferIndex < GPU_MAX_VB_BINDED; bufferIndex++) { auto& buffer = buffers[bufferIndex]; buffer.nextInChain = nullptr; buffer.stepMode = WGPUVertexStepMode_Vertex; buffer.arrayStride = 0; buffer.attributeCount = 0; buffer.attributes = attributes + attributeIndex; for (int32 i = 0; i < elements.Count(); i++) { const VertexElement& element = elements[i]; if (element.Slot != bufferIndex) continue; WGPUVertexAttribute& dst = attributes[attributeIndex++]; buffer.attributeCount++; dst.nextInChain = nullptr; dst.format = RenderToolsWebGPU::ToVertexFormat(element.Format); dst.offset = element.Offset; dst.shaderLocation = i; // Elements are sorted to match Input Layout order of Vertex Shader as provided by GPUVertexLayout::Merge if (element.PerInstance) buffer.stepMode = WGPUVertexStepMode_Instance; buffer.arrayStride = Math::Max(buffer.arrayStride, element.Offset + PixelFormatExtensions::SizeInBytes(element.Format)); PipelineDesc.vertex.bufferCount = Math::Max(PipelineDesc.vertex.bufferCount, bufferIndex + 1); } } } else { // No vertex input PipelineDesc.vertex.bufferCount = 0; PipelineDesc.vertex.buffers = nullptr; } // Create object pipeline = wgpuDeviceCreateRenderPipeline(_device->Device, &PipelineDesc); if (!pipeline) { #if GPU_ENABLE_RESOURCE_NAMING LOG(Error, "wgpuDeviceCreateRenderPipeline failed for {}", String(_debugName.Get(), _debugName.Count() - 1)); #endif return nullptr; } // Cache it _pipelines.Add(key, pipeline); return pipeline; } WGPUBindGroup GPUPipelineStateWebGPU::GetBindGroup(BindGroupKey& key) { // Generate a hash for the key key.LastFrameUsed = Engine::FrameCount; key.Hash = Crc::MemCrc32(&key.Entries, key.EntriesCount * sizeof(WGPUBindGroupEntry)); CombineHash(key.Hash, GetHash(key.EntriesCount)); CombineHash(key.Hash, GetHash(key.Layout)); CombineHash(key.Hash, Crc::MemCrc32(&key.Versions, key.EntriesCount * sizeof(uint8))); // Lookup for existing bind group WGPUBindGroup bindGroup; auto found = _bindGroups.Find(key); if (found.IsNotEnd()) { // Get cached bind group and update the last usage frame bindGroup = found->Value; found->Key.LastFrameUsed = key.LastFrameUsed; // Periodically remove old bind groups (unused for some time) if (key.LastFrameUsed - _lastFrameBindGroupsGC > 100) { _lastFrameBindGroupsGC = key.LastFrameUsed; int32 freed = 0; for (auto it = _bindGroups.Begin(); it.IsNotEnd(); ++it) { if (key.LastFrameUsed - it->Key.LastFrameUsed > 50) { freed++; wgpuBindGroupRelease(it->Value); _bindGroups.Remove(it); } } #if WEBGPU_LOG_BIND_GROUPS if (freed > 0) { LOG(Info, "[WebGPU] Removed {} old entries from '{}'", freed, String(_debugName.Get(), _debugName.Count() - 1)); } #endif } return bindGroup; } PROFILE_CPU(); PROFILE_MEM(GraphicsCommands); #if GPU_ENABLE_RESOURCE_NAMING ZoneText(_debugName.Get(), _debugName.Count() - 1); #endif #if WEBGPU_LOG_BIND_GROUPS LOG(Info, "[WebGPU] GetBindGroup: '{}', hash: {}", String(_debugName.Get(), _debugName.Count() - 1), key.Hash); #endif // Build description WGPUBindGroupDescriptor desc = WGPU_BIND_GROUP_DESCRIPTOR_INIT; #if GPU_ENABLE_RESOURCE_NAMING desc.label = PipelineDesc.label; #endif desc.layout = key.Layout; desc.entryCount = key.EntriesCount; desc.entries = key.Entries; // Create object bindGroup = wgpuDeviceCreateBindGroup(_device->Device, &desc); if (!bindGroup) { #if GPU_ENABLE_RESOURCE_NAMING LOG(Error, "wgpuDeviceCreateBindGroup failed for {}", String(_debugName.Get(), _debugName.Count() - 1)); #endif return nullptr; } #if WEBGPU_LOG_BIND_GROUPS // Debug detection of hash collisions int32 collisions = 0, equalLayout = 0, equalEntries = 0, equalVersions = 0; for (auto& e : _bindGroups) { auto& other = e.Key; if (key.Hash == other.Hash) { collisions++; if (key.Layout == other.Layout) equalLayout++; if (key.EntriesCount == other.EntriesCount && Platform::MemoryCompare(&key.Entries, &other.Entries, key.EntriesCount * sizeof(WGPUBindGroupEntry)) == 0) equalEntries++; if (key.EntriesCount == other.EntriesCount && Platform::MemoryCompare(&key.Versions, &other.Versions, key.EntriesCount * sizeof(uint8)) == 0) equalVersions++; } } if (collisions > 1) LOG(Error, "> Hash colllision! {}/{} (capacity: {}), equalLayout: {}, equalEntries: {}, equalVersions: {}", collisions, _bindGroups.Count(), _bindGroups.Capacity(), equalLayout, equalEntries, equalVersions); #endif // Cache it _bindGroups.Add(key, bindGroup); return bindGroup; } void GPUPipelineStateWebGPU::InitLayout(GPUResourceView* shaderResources[GPU_MAX_SR_BINDED]) { #if WEBGPU_LOG_PSO // Debug log for PSOs with specific name const bool log = true;// StringAnsiView(_debugName.Get(), _debugName.Count() - 1).Contains("PS_HalfDepth"); #endif // Count the biggest bind group entries (for all shaders) to allocate reused memory int32 maxEntriesCount = 0; for (int32 groupIndex = 0; groupIndex < ARRAY_COUNT(BindGroupDescriptors); groupIndex++) { auto descriptors = BindGroupDescriptors[groupIndex]; if (descriptors && maxEntriesCount < descriptors->DescriptorTypesCount) maxEntriesCount = (int32)descriptors->DescriptorTypesCount; } Array> entries; entries.Resize(maxEntriesCount); // Setup bind groups WGPUBindGroupLayoutEntry* entriesPtr = entries.Get(); for (int32 groupIndex = 0; groupIndex < ARRAY_COUNT(BindGroupDescriptors); groupIndex++) { auto descriptors = BindGroupDescriptors[groupIndex]; if (!descriptors || descriptors->DescriptorTypesCount == 0) continue; int32 entriesCount = descriptors->DescriptorTypesCount; Platform::MemoryClear(entries.Get(), sizeof(WGPUBindGroupLayoutEntry) * entriesCount); auto visibility = groupIndex == 0 ? WGPUShaderStage_Vertex : WGPUShaderStage_Fragment; #if WEBGPU_LOG_PSO if (log) LOG(Info, " > group {} - {}", groupIndex, groupIndex == 0 ? TEXT("Vertex") : TEXT("Fragment")); const Char* samplerType = TEXT("?"); #endif for (int32 index = 0; index < entriesCount; index++) { auto& descriptor = descriptors->DescriptorTypes[index]; auto& entry = entriesPtr[index]; entry.binding = descriptor.Binding; entry.bindingArraySize = descriptor.Count; entry.visibility = visibility; switch (descriptor.DescriptorType) { case VK_DESCRIPTOR_TYPE_SAMPLER: entry.sampler.type = WGPUSamplerBindingType_Undefined; if (descriptor.Slot == 4 || descriptor.Slot == 5) // Hack for ShadowSampler and ShadowSamplerLinear (this could get binded samplers table just like for shaderResources) entry.sampler.type = WGPUSamplerBindingType_Comparison; #if WEBGPU_LOG_PSO switch (entry.sampler.type) { case WGPUSamplerBindingType_BindingNotUsed: samplerType = TEXT("BindingNotUsed"); break; case WGPUSamplerBindingType_Undefined: samplerType = TEXT("Undefined"); break; case WGPUSamplerBindingType_Filtering: samplerType = TEXT("Filtering"); break; case WGPUSamplerBindingType_NonFiltering: samplerType = TEXT("NonFiltering"); break; case WGPUSamplerBindingType_Comparison: samplerType = TEXT("Comparison"); break; } if (log) LOG(Info, " > [{}] sampler ({})", entry.binding, samplerType); #endif break; case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE: entry.texture.sampleType = WGPUTextureSampleType_Undefined; if (shaderResources[descriptor.Slot]) { // Hack to use the sample type directly from the view which allows to fix incorrect Depth Buffer reading that allows only manual Load when UnfilterableFloat is used (see SAMPLE_RT_DEPTH) auto ptr = (GPUResourceViewPtrWebGPU*)shaderResources[descriptor.Slot]->GetNativePtr(); if (ptr && ptr->TextureView) entry.texture.sampleType = ptr->TextureView->SampleType; } #if WEBGPU_LOG_PSO if (log) { switch (entry.texture.sampleType) { case WGPUTextureSampleType_BindingNotUsed: samplerType = TEXT("BindingNotUsed"); break; case WGPUTextureSampleType_Undefined: samplerType = TEXT("Undefined"); break; case WGPUTextureSampleType_Float: samplerType = TEXT("Float"); break; case WGPUTextureSampleType_UnfilterableFloat: samplerType = TEXT("UnfilterableFloat"); break; case WGPUTextureSampleType_Depth: samplerType = TEXT("Depth"); break; case WGPUTextureSampleType_Sint: samplerType = TEXT("Sint"); break; case WGPUTextureSampleType_Uint: samplerType = TEXT("Uint"); break; } switch (descriptor.ResourceType) { case SpirvShaderResourceType::Texture1D: LOG(Info, " > [{}] texture 1D ({})", entry.binding, samplerType); break; case SpirvShaderResourceType::Texture2D: LOG(Info, " > [{}] texture 2D ({})", entry.binding, samplerType); break; case SpirvShaderResourceType::Texture3D: LOG(Info, " > [{}] texture 3D ({})", entry.binding, samplerType); break; case SpirvShaderResourceType::TextureCube: LOG(Info, " > [{}] texture Cube ({})", entry.binding, samplerType); break; case SpirvShaderResourceType::Texture2DArray: LOG(Info, " > [{}] texture 2D array ({})", entry.binding, samplerType); break; } } #endif switch (descriptor.ResourceType) { case SpirvShaderResourceType::Texture1D: entry.texture.viewDimension = WGPUTextureViewDimension_1D; break; case SpirvShaderResourceType::Texture2D: entry.texture.viewDimension = WGPUTextureViewDimension_2D; break; case SpirvShaderResourceType::Texture3D: entry.texture.viewDimension = WGPUTextureViewDimension_3D; break; case SpirvShaderResourceType::TextureCube: entry.texture.viewDimension = WGPUTextureViewDimension_Cube; break; case SpirvShaderResourceType::Texture1DArray: CRASH; // Not supported TODO: add error at compile time (in ShaderCompilerWebGPU::Write) break; case SpirvShaderResourceType::Texture2DArray: entry.texture.viewDimension = WGPUTextureViewDimension_2DArray; break; } break; case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC: entry.buffer.hasDynamicOffset = true; case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER: if (descriptor.BindingType == SpirvShaderResourceBindingType::SRV) entry.buffer.type = WGPUBufferBindingType_ReadOnlyStorage; else entry.buffer.type = WGPUBufferBindingType_Storage; #if WEBGPU_LOG_PSO if (log) LOG(Info, " > [{}] storage buffer (read-only = {}, dynamic = {})", entry.binding, entry.buffer.type == WGPUBufferBindingType_ReadOnlyStorage, entry.buffer.hasDynamicOffset); #endif break; case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC: entry.buffer.hasDynamicOffset = true; case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER: entry.buffer.type = WGPUBufferBindingType_Uniform; #if WEBGPU_LOG_PSO if (log) LOG(Info, " > [{}] uniform buffer (dynamic = {})", entry.binding, entry.buffer.hasDynamicOffset); #endif break; default: #if GPU_ENABLE_DIAGNOSTICS LOG(Fatal, "Unknown descriptor type: {} used as {} in '{}'", (uint32)descriptor.DescriptorType, (uint32)descriptor.BindingType, String(_debugName.Get(), _debugName.Count() - 1)); #else CRASH; #endif return; } } // Create a bind group layout WGPUBindGroupLayoutDescriptor bindGroupLayoutDesc = WGPU_BIND_GROUP_LAYOUT_DESCRIPTOR_INIT; bindGroupLayoutDesc.entryCount = entriesCount; bindGroupLayoutDesc.entries = entriesPtr; BindGroupLayouts[groupIndex] = wgpuDeviceCreateBindGroupLayout(_device->Device, &bindGroupLayoutDesc); } // Create the pipeline layout WGPUPipelineLayoutDescriptor layoutDesc = WGPU_PIPELINE_LAYOUT_DESCRIPTOR_INIT; #if GPU_ENABLE_RESOURCE_NAMING layoutDesc.label = PipelineDesc.label; #endif layoutDesc.bindGroupLayoutCount = GPUBindGroupsWebGPU::GraphicsMax; layoutDesc.bindGroupLayouts = BindGroupLayouts; PipelineDesc.layout = wgpuDeviceCreatePipelineLayout(_device->Device, &layoutDesc); if (!PipelineDesc.layout) { LOG(Error, "wgpuDeviceCreatePipelineLayout failed"); return; } } bool GPUPipelineStateWebGPU::IsValid() const { return _memoryUsage != 0; } bool GPUPipelineStateWebGPU::Init(const Description& desc) { if (IsValid()) OnReleaseGPU(); // Initialize description (without dynamic state from context such as render targets, vertex buffers, etc.) PipelineDesc = WGPU_RENDER_PIPELINE_DESCRIPTOR_INIT; #if GPU_ENABLE_RESOURCE_NAMING DebugDesc = desc; GetDebugName(_debugName); PipelineDesc.label = { _debugName.Get(), (size_t)_debugName.Count() - 1 }; #endif PipelineDesc.primitive.topology = WGPUPrimitiveTopology_TriangleList; PipelineDesc.primitive.frontFace = WGPUFrontFace_CW; switch (desc.CullMode) { case CullMode::Normal: PipelineDesc.primitive.cullMode = WGPUCullMode_Back; break; case CullMode::Inverted: PipelineDesc.primitive.cullMode = WGPUCullMode_Front; break; case CullMode::TwoSided: PipelineDesc.primitive.cullMode = WGPUCullMode_None; break; } PipelineDesc.primitive.unclippedDepth = !desc.DepthClipEnable && _device->Limits.HasDepthClip; if (desc.DepthEnable || desc.StencilEnable) { PipelineDesc.depthStencil = &_depthStencilDesc; _depthStencilDesc = WGPU_DEPTH_STENCIL_STATE_INIT; _depthStencilDesc.depthWriteEnabled = desc.DepthEnable && desc.DepthWriteEnable ? WGPUOptionalBool_True : WGPUOptionalBool_False; _depthStencilDesc.depthCompare = ToCompareFunction(desc.DepthFunc); if (desc.StencilEnable) { _depthStencilDesc.stencilFront.compare = ToCompareFunction(desc.StencilFunc); _depthStencilDesc.stencilFront.failOp = ToStencilOperation(desc.StencilFailOp); _depthStencilDesc.stencilFront.depthFailOp = ToStencilOperation(desc.StencilDepthFailOp); _depthStencilDesc.stencilFront.passOp = ToStencilOperation(desc.StencilPassOp); _depthStencilDesc.stencilBack = _depthStencilDesc.stencilFront; _depthStencilDesc.stencilReadMask = desc.StencilReadMask; _depthStencilDesc.stencilWriteMask = desc.StencilWriteMask; } } PipelineDesc.multisample.alphaToCoverageEnabled = desc.BlendMode.AlphaToCoverageEnable; if (desc.PS) { PipelineDesc.fragment = &_fragmentDesc; _fragmentDesc = WGPU_FRAGMENT_STATE_INIT; _fragmentDesc.targets = _colorTargets; Platform::MemoryClear(&_colorTargets, sizeof(_colorTargets)); if (desc.BlendMode.BlendEnable) { _blendState = WGPU_BLEND_STATE_INIT; _blendState.color = ToBlendComponent(desc.BlendMode.BlendOp, desc.BlendMode.SrcBlend, desc.BlendMode.DestBlend); _blendState.alpha = ToBlendComponent(desc.BlendMode.BlendOpAlpha, desc.BlendMode.SrcBlendAlpha, desc.BlendMode.DestBlendAlpha); for (auto& e : _colorTargets) e.blend = &_blendState; } WGPUColorWriteMask writeMask = WGPUColorWriteMask_All; if (desc.BlendMode.RenderTargetWriteMask != BlendingMode::ColorWrite::All) { writeMask = 0; if (EnumHasAllFlags(desc.BlendMode.RenderTargetWriteMask, BlendingMode::ColorWrite::Red)) writeMask |= WGPUColorWriteMask_Red; if (EnumHasAllFlags(desc.BlendMode.RenderTargetWriteMask, BlendingMode::ColorWrite::Green)) writeMask |= WGPUColorWriteMask_Green; if (EnumHasAllFlags(desc.BlendMode.RenderTargetWriteMask, BlendingMode::ColorWrite::Blue)) writeMask |= WGPUColorWriteMask_Blue; if (EnumHasAllFlags(desc.BlendMode.RenderTargetWriteMask, BlendingMode::ColorWrite::Alpha)) writeMask |= WGPUColorWriteMask_Alpha; } uint16 outputsCount = desc.PS->GetBindings().OutputsCount; for (uint16 rtIndex = 0; rtIndex < outputsCount; rtIndex++) _colorTargets[rtIndex].writeMask = writeMask; } // Cache shaders VS = (GPUShaderProgramVSWebGPU*)desc.VS; BindGroupDescriptors[GPUBindGroupsWebGPU::Vertex] = &VS->DescriptorInfo; PipelineDesc.vertex.module = VS->ShaderModule; PS = (GPUShaderProgramPSWebGPU*)desc.PS; if (PS) { BindGroupDescriptors[GPUBindGroupsWebGPU::Pixel] = &PS->DescriptorInfo; _fragmentDesc.module = PS->ShaderModule; } _memoryUsage = 1; return GPUPipelineState::Init(desc); } #endif