Add shader compiler for WebGPU

Use existing Vulkan compiler to generate SPIR-V and convert it into WGSL with tint compiler
https://github.com/google/dawn/releases/tag/v20260219.200501
This commit is contained in:
Wojtek Figat
2026-02-24 17:55:26 +01:00
parent 9158e1c270
commit 9be8589437
17 changed files with 337 additions and 50 deletions

View File

@@ -166,8 +166,14 @@ WGPURenderPipeline GPUPipelineStateWebGPU::GetPipeline(const Key& key)
for (int32 i = 0; i < _fragmentDesc.targetCount; i++)
_colorTargets[i].format = (WGPUTextureFormat)key.RenderTargetFormats[i];
WGPUVertexBufferLayout buffers[GPU_MAX_VB_BINDED];
PipelineDesc.vertex.bufferCount = key.VertexBufferCount;
int32 shaderLocation = 0;
for (int32 i = 0; i < PipelineDesc.vertex.bufferCount; i++)
{
buffers[i] = *key.VertexBuffers[i];
for (int32 j = 0; j < buffers[i].attributeCount; j++)
((WGPUVertexAttribute&)buffers[i].attributes[j]).shaderLocation = shaderLocation++;
}
PipelineDesc.vertex.buffers = buffers;
// Create object
@@ -196,13 +202,10 @@ bool GPUPipelineStateWebGPU::Init(const Description& desc)
if (IsValid())
OnReleaseGPU();
// Cache shaders
VS = (GPUShaderProgramVSWebGPU*)desc.VS;
PS = (GPUShaderProgramPSWebGPU*)desc.PS;
// 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
@@ -257,17 +260,24 @@ bool GPUPipelineStateWebGPU::Init(const Description& desc)
writeMask |= WGPUColorWriteMask_Blue;
if (EnumHasAllFlags(desc.BlendMode.RenderTargetWriteMask, BlendingMode::ColorWrite::Alpha))
writeMask |= WGPUColorWriteMask_Alpha;
}
for (auto& e : _colorTargets)
} for (auto& e : _colorTargets)
{
e = WGPU_COLOR_TARGET_STATE_INIT;
e.blend = &_blendState;
if (desc.BlendMode.BlendEnable)
e.blend = &_blendState;
e.writeMask = writeMask;
}
// Cache shaders
VS = (GPUShaderProgramVSWebGPU*)desc.VS;
PipelineDesc.vertex.module = VS->ShaderModule;
PS = (GPUShaderProgramPSWebGPU*)desc.PS;
if (PS)
{
_fragmentDesc.module = PS->ShaderModule;
}
// TODO: set resources binding into PipelineDesc.layout
// TODO: set vertex shader into PipelineDesc.vertex
// TODO: set pixel shader into PipelineDesc.fragment
_memoryUsage = 1;
return GPUPipelineState::Init(desc);

View File

@@ -2,6 +2,7 @@
#pragma once
#include "Engine/Core/Collections/Dictionary.h"
#include "Engine/Graphics/GPUPipelineState.h"
#include "GPUShaderProgramWebGPU.h"
#include "GPUDeviceWebGPU.h"

View File

@@ -2,12 +2,12 @@
#pragma once
#include "Engine/Graphics/Shaders/GPUShaderProgram.h"
#include "Engine/Core/Types/DataContainer.h"
#include "Engine/Core/Collections/Dictionary.h"
#if GRAPHICS_API_WEBGPU
#include "Engine/Graphics/Shaders/GPUShaderProgram.h"
#include "Engine/GraphicsDevice/Vulkan/Types.h"
#include <webgpu/webgpu.h>
/// <summary>
/// Shaders base class for Web GPU backend.
/// </summary>
@@ -15,15 +15,22 @@ template<typename BaseType>
class GPUShaderProgramWebGPU : public BaseType
{
public:
GPUShaderProgramWebGPU(const GPUShaderProgramInitializer& initializer)
GPUShaderProgramWebGPU(const GPUShaderProgramInitializer& initializer, const SpirvShaderDescriptorInfo& descriptorInfo, WGPUShaderModule shaderModule)
: DescriptorInfo(descriptorInfo)
, ShaderModule(shaderModule)
{
BaseType::Init(initializer);
}
~GPUShaderProgramWebGPU()
{
wgpuShaderModuleRelease(ShaderModule);
}
public:
SpirvShaderDescriptorInfo DescriptorInfo;
WGPUShaderModule ShaderModule;
public:
// [BaseType]
uint32 GetBufferSize() const override
@@ -32,7 +39,7 @@ public:
}
void* GetBufferHandle() const override
{
return nullptr;
return ShaderModule;
}
};
@@ -42,8 +49,8 @@ public:
class GPUShaderProgramVSWebGPU : public GPUShaderProgramWebGPU<GPUShaderProgramVS>
{
public:
GPUShaderProgramVSWebGPU(const GPUShaderProgramInitializer& initializer, GPUVertexLayout* inputLayout, GPUVertexLayout* vertexLayout, Span<byte> bytecode)
: GPUShaderProgramWebGPU(initializer)
GPUShaderProgramVSWebGPU(const GPUShaderProgramInitializer& initializer, GPUVertexLayout* inputLayout, GPUVertexLayout* vertexLayout, const SpirvShaderDescriptorInfo& descriptorInfo, WGPUShaderModule shaderModule)
: GPUShaderProgramWebGPU(initializer, descriptorInfo, shaderModule)
{
InputLayout = inputLayout;
Layout = vertexLayout;
@@ -56,8 +63,8 @@ public:
class GPUShaderProgramPSWebGPU : public GPUShaderProgramWebGPU<GPUShaderProgramPS>
{
public:
GPUShaderProgramPSWebGPU(const GPUShaderProgramInitializer& initializer)
: GPUShaderProgramWebGPU(initializer)
GPUShaderProgramPSWebGPU(const GPUShaderProgramInitializer& initializer, const SpirvShaderDescriptorInfo& descriptorInfo, WGPUShaderModule shaderModule)
: GPUShaderProgramWebGPU(initializer, descriptorInfo, shaderModule)
{
}
};

View File

@@ -5,6 +5,9 @@
#include "GPUShaderWebGPU.h"
#include "GPUShaderProgramWebGPU.h"
#include "GPUVertexLayoutWebGPU.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Types/DataContainer.h"
#include "Engine/GraphicsDevice/Vulkan/Types.h"
#include "Engine/Serialization/MemoryReadStream.h"
GPUConstantBufferWebGPU::GPUConstantBufferWebGPU(GPUDeviceWebGPU* device, uint32 size, WGPUBuffer buffer, const StringView& name) noexcept
@@ -31,6 +34,34 @@ void GPUConstantBufferWebGPU::OnReleaseGPU()
GPUShaderProgram* GPUShaderWebGPU::CreateGPUShaderProgram(ShaderStage type, const GPUShaderProgramInitializer& initializer, Span<byte> bytecode, MemoryReadStream& stream)
{
// Extract the SPIR-V shader header from the cache
SpirvShaderHeader* header = (SpirvShaderHeader*)bytecode.Get();
bytecode = bytecode.Slice(sizeof(SpirvShaderHeader));
// Extract the WGSL shader
BytesContainer wgsl;
ASSERT(header->Type == SpirvShaderHeader::Types::WGSL);
wgsl.Link(bytecode);
// Create a shader module
WGPUShaderSourceWGSL shaderCodeDesc = WGPU_SHADER_SOURCE_WGSL_INIT;
shaderCodeDesc.code = { (const char*)wgsl.Get(), (size_t)wgsl.Length() - 1 };
WGPUShaderModuleDescriptor shaderDesc = WGPU_SHADER_MODULE_DESCRIPTOR_INIT;
shaderDesc.nextInChain = &shaderCodeDesc.chain;
#if GPU_ENABLE_RESOURCE_NAMING
shaderDesc.label = { initializer.Name.Get(), (size_t)initializer.Name.Length() };
#endif
WGPUShaderModule shaderModule = wgpuDeviceCreateShaderModule(_device->Device, &shaderDesc);
if (!shaderModule)
{
LOG(Error, "Failed to create a shader module");
#if GPU_ENABLE_DIAGNOSTICS
LOG_STR(Warning, String((char*)wgsl.Get(), wgsl.Length()));
#endif
return nullptr;
}
// Create a shader program
GPUShaderProgram* shader = nullptr;
switch (type)
{
@@ -38,14 +69,12 @@ GPUShaderProgram* GPUShaderWebGPU::CreateGPUShaderProgram(ShaderStage type, cons
{
GPUVertexLayout* inputLayout, *vertexLayout;
ReadVertexLayout(stream, inputLayout, vertexLayout);
MISSING_CODE("create vertex shader");
shader = New<GPUShaderProgramVSWebGPU>(initializer, inputLayout, vertexLayout, bytecode);
shader = New<GPUShaderProgramVSWebGPU>(initializer, inputLayout, vertexLayout, header->DescriptorInfo, shaderModule);
break;
}
case ShaderStage::Pixel:
{
MISSING_CODE("create pixel shader");
shader = New<GPUShaderProgramPSWebGPU>(initializer);
shader = New<GPUShaderProgramPSWebGPU>(initializer, header->DescriptorInfo, shaderModule);
break;
}
}