diff --git a/Source/Engine/Graphics/Enums.h b/Source/Engine/Graphics/Enums.h
index 8444ec5fa..b45ebdf32 100644
--- a/Source/Engine/Graphics/Enums.h
+++ b/Source/Engine/Graphics/Enums.h
@@ -75,6 +75,11 @@ API_ENUM() enum class RendererType
///
PS5 = 12,
+ ///
+ /// WebGPU
+ ///
+ WebGPU = 13,
+
API_ENUM(Attributes="HideInEditor")
MAX
};
@@ -131,6 +136,11 @@ API_ENUM() enum class ShaderProfile
///
PS5 = 8,
+ ///
+ /// WebGPU
+ ///
+ WebGPU = 9,
+
API_ENUM(Attributes="HideInEditor")
MAX
};
diff --git a/Source/Engine/Graphics/Graphics.Build.cs b/Source/Engine/Graphics/Graphics.Build.cs
index caebebe50..015e2c01d 100644
--- a/Source/Engine/Graphics/Graphics.Build.cs
+++ b/Source/Engine/Graphics/Graphics.Build.cs
@@ -97,8 +97,7 @@ public class Graphics : EngineModule
Log.WarningOnce(string.Format("Building for {0} without Vulkan rendering backend (Vulkan SDK is missing)", options.Platform.Target), ref _logMissingVulkanSDK);
break;
case TargetPlatform.Web:
- options.PrivateDependencies.Add("GraphicsDeviceNull");
- // TODO: add WebGPU
+ options.PrivateDependencies.Add("GraphicsDeviceWebGPU");
break;
default: throw new InvalidPlatformException(options.Platform.Target);
}
diff --git a/Source/Engine/Graphics/Graphics.cpp b/Source/Engine/Graphics/Graphics.cpp
index 6e27833b0..4d5dcecd7 100644
--- a/Source/Engine/Graphics/Graphics.cpp
+++ b/Source/Engine/Graphics/Graphics.cpp
@@ -175,6 +175,11 @@ bool GraphicsService::Init()
extern GPUDevice* CreateGPUDevicePS5();
if (!device)
device = CreateGPUDevicePS5();
+#endif
+#if GRAPHICS_API_WEBGPU
+ extern GPUDevice* CreateGPUDeviceWebGPU();
+ if (!device)
+ device = CreateGPUDeviceWebGPU();
#endif
}
diff --git a/Source/Engine/Graphics/RenderTools.cpp b/Source/Engine/Graphics/RenderTools.cpp
index 949668771..0d10380a2 100644
--- a/Source/Engine/Graphics/RenderTools.cpp
+++ b/Source/Engine/Graphics/RenderTools.cpp
@@ -58,6 +58,9 @@ const Char* ToString(RendererType value)
case RendererType::PS5:
result = TEXT("PS5");
break;
+ case RendererType::WebGPU:
+ result = TEXT("WebGPU");
+ break;
default:
result = TEXT("?");
}
@@ -96,6 +99,9 @@ const Char* ToString(ShaderProfile value)
case ShaderProfile::PS5:
result = TEXT("PS5");
break;
+ case ShaderProfile::WebGPU:
+ result = TEXT("WebGPU");
+ break;
default:
result = TEXT("?");
}
@@ -268,6 +274,7 @@ FeatureLevel RenderTools::GetFeatureLevel(ShaderProfile profile)
case ShaderProfile::GLSL_440:
case ShaderProfile::GLSL_410:
case ShaderProfile::Unknown:
+ case ShaderProfile::WebGPU:
return FeatureLevel::ES2;
default:
return FeatureLevel::ES2;
diff --git a/Source/Engine/Graphics/Shaders/Cache/ShaderCacheManager.cpp b/Source/Engine/Graphics/Shaders/Cache/ShaderCacheManager.cpp
index 112324b2c..8a10b81a9 100644
--- a/Source/Engine/Graphics/Shaders/Cache/ShaderCacheManager.cpp
+++ b/Source/Engine/Graphics/Shaders/Cache/ShaderCacheManager.cpp
@@ -27,6 +27,7 @@ const Char* ShaderProfileCacheDirNames[] =
TEXT("PS4"), // PS4
TEXT("DX_SM6"), // DirectX_SM6
TEXT("PS5"), // PS5
+ TEXT("WEB"), // WebGPU
// @formatter:on
};
diff --git a/Source/Engine/Graphics/Textures/GPUSampler.cpp b/Source/Engine/Graphics/Textures/GPUSampler.cpp
index 208095de0..a57098c09 100644
--- a/Source/Engine/Graphics/Textures/GPUSampler.cpp
+++ b/Source/Engine/Graphics/Textures/GPUSampler.cpp
@@ -19,6 +19,7 @@ void GPUSamplerDescription::Clear()
{
Platform::MemoryClear(this, sizeof(GPUSamplerDescription));
MaxMipLevel = MAX_float;
+ MaxAnisotropy = 1.0f;
}
bool GPUSamplerDescription::Equals(const GPUSamplerDescription& other) const
diff --git a/Source/Engine/GraphicsDevice/WebGPU/GPUAdapterWebGPU.h b/Source/Engine/GraphicsDevice/WebGPU/GPUAdapterWebGPU.h
new file mode 100644
index 000000000..1666c4bf8
--- /dev/null
+++ b/Source/Engine/GraphicsDevice/WebGPU/GPUAdapterWebGPU.h
@@ -0,0 +1,56 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#if GRAPHICS_API_WEBGPU
+
+#include "Engine/Graphics/GPUAdapter.h"
+#include "Engine/Utilities/StringConverter.h"
+#include "IncludeWebGPU.h"
+
+///
+/// Graphics Device adapter implementation for Web GPU backend.
+///
+class GPUAdapterWebGPU : public GPUAdapter
+{
+public:
+ WGPUAdapter Adapter;
+ WGPUAdapterInfo Info;
+
+ GPUAdapterWebGPU(WGPUAdapter adapter)
+ : Adapter(adapter)
+ , Info(WGPU_ADAPTER_INFO_INIT)
+ {
+ wgpuAdapterGetInfo(adapter, &Info);
+ }
+
+ ~GPUAdapterWebGPU()
+ {
+ wgpuAdapterRelease(Adapter);
+ }
+
+public:
+ // [GPUAdapter]
+ bool IsValid() const override
+ {
+ return true;
+ }
+ void* GetNativePtr() const override
+ {
+ return Adapter;
+ }
+ uint32 GetVendorId() const override
+ {
+ return Info.vendorID;
+ }
+ String GetDescription() const override
+ {
+ return String::Format(TEXT("{} {} {} {}"), WEBGPU_TO_STR(Info.vendor), WEBGPU_TO_STR(Info.architecture), WEBGPU_TO_STR(Info.device), WEBGPU_TO_STR(Info.description)).TrimTrailing();
+ }
+ Version GetDriverVersion() const override
+ {
+ return Platform::GetSystemVersion();
+ }
+};
+
+#endif
diff --git a/Source/Engine/GraphicsDevice/WebGPU/GPUBufferWebGPU.cpp b/Source/Engine/GraphicsDevice/WebGPU/GPUBufferWebGPU.cpp
new file mode 100644
index 000000000..57c657531
--- /dev/null
+++ b/Source/Engine/GraphicsDevice/WebGPU/GPUBufferWebGPU.cpp
@@ -0,0 +1,127 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#if GRAPHICS_API_WEBGPU
+
+#include "GPUBufferWebGPU.h"
+#include "RenderToolsWebGPU.h"
+#include "Engine/Core/Log.h"
+
+GPUBufferWebGPU::GPUBufferWebGPU(GPUDeviceWebGPU* device, const StringView& name)
+ : GPUResourceWebGPU(device, name)
+{
+}
+
+GPUBufferView* GPUBufferWebGPU::View() const
+{
+ return (GPUBufferView*)&_view;
+}
+
+void* GPUBufferWebGPU::Map(GPUResourceMapMode mode)
+{
+ ASSERT(!_mapped);
+ WGPUMapMode mapMode = 0;
+ if (EnumHasAnyFlags(mode, GPUResourceMapMode::Read))
+ mapMode |= WGPUMapMode_Read;
+ if (EnumHasAnyFlags(mode, GPUResourceMapMode::Write))
+ mapMode |= WGPUMapMode_Write;
+ AsyncCallbackWebGPU mapRequest(WGPU_BUFFER_MAP_CALLBACK_INFO_INIT);
+ mapRequest.Info.callback = [](WGPUMapAsyncStatus status, WGPUStringView message, WGPU_NULLABLE void* userdata1, WGPU_NULLABLE void* userdata2)
+ {
+ AsyncCallbackDataWebGPU& userData = *reinterpret_cast(userdata1);
+ userData.Call(status == WGPUMapAsyncStatus_Success, status, message);
+ };
+ wgpuBufferMapAsync(Buffer, mapMode, 0, _desc.Size, mapRequest.Info);
+ auto mapRequestResult = mapRequest.Wait();
+ if (mapRequestResult == WGPUWaitStatus_TimedOut)
+ {
+ LOG(Error, "WebGPU buffer map request has timed out after {}s", mapRequest.Data.WaitTime);
+ return nullptr;
+ }
+ if (mapRequestResult == WGPUWaitStatus_Error)
+ return nullptr;
+ _mapped = true;
+ if (EnumHasNoneFlags(mode, GPUResourceMapMode::Write))
+ return (void*)wgpuBufferGetConstMappedRange(Buffer, 0, _desc.Size);
+ return wgpuBufferGetMappedRange(Buffer, 0, _desc.Size);
+}
+
+void GPUBufferWebGPU::Unmap()
+{
+ ASSERT(_mapped);
+ _mapped = false;
+ wgpuBufferUnmap(Buffer);
+}
+
+bool GPUBufferWebGPU::OnInit()
+{
+ // Create buffer
+ WGPUBufferDescriptor bufferDesc = WGPU_BUFFER_DESCRIPTOR_INIT;
+#if GPU_ENABLE_RESOURCE_NAMING
+ _name.Set(_namePtr, _nameSize);
+ bufferDesc.label = { _name.Get(), (size_t)_name.Length() };
+#endif
+ if (EnumHasAllFlags(_desc.Flags, GPUBufferFlags::IndexBuffer))
+ bufferDesc.usage |= WGPUBufferUsage_Index;
+ else if (EnumHasAllFlags(_desc.Flags, GPUBufferFlags::VertexBuffer))
+ bufferDesc.usage |= WGPUBufferUsage_Vertex;
+ else if (EnumHasAllFlags(_desc.Flags, GPUBufferFlags::Argument))
+ bufferDesc.usage |= WGPUBufferUsage_Indirect;
+ if (IsUnorderedAccess())
+ bufferDesc.usage |= WGPUBufferUsage_Storage;
+ switch (_desc.Usage)
+ {
+ case GPUResourceUsage::Default:
+ if (!_desc.InitData)
+ bufferDesc.usage |= WGPUBufferUsage_CopyDst;
+ break;
+ case GPUResourceUsage::Dynamic:
+ bufferDesc.usage |= WGPUBufferUsage_MapWrite;
+ break;
+ case GPUResourceUsage::StagingUpload:
+ bufferDesc.usage |= WGPUBufferUsage_MapWrite | WGPUBufferUsage_CopySrc;
+ break;
+ case GPUResourceUsage::StagingReadback:
+ bufferDesc.usage |= WGPUBufferUsage_MapRead;
+ break;
+ case GPUResourceUsage::Staging:
+ bufferDesc.usage |= WGPUBufferUsage_MapRead | WGPUBufferUsage_MapWrite | WGPUBufferUsage_CopySrc;
+ break;
+ }
+ bufferDesc.size = _desc.Size;
+ bufferDesc.mappedAtCreation = _desc.InitData != nullptr;
+ Buffer = wgpuDeviceCreateBuffer(_device->Device, &bufferDesc);
+ if (!Buffer)
+ return true;
+ _memoryUsage = _desc.Size;
+
+ // Initialize with a data if provided
+ if (_desc.InitData)
+ {
+ //wgpuBufferWriteMappedRange(Buffer, 0, _desc.InitData, _desc.Size);
+ Platform::MemoryCopy(wgpuBufferGetMappedRange(Buffer, 0, _desc.Size), _desc.InitData, _desc.Size);
+ wgpuBufferUnmap(Buffer);
+ }
+
+ // Create view
+ _view.Set(this, Buffer);
+
+ return false;
+}
+
+void GPUBufferWebGPU::OnReleaseGPU()
+{
+ if (Buffer)
+ {
+ wgpuBufferDestroy(Buffer);
+ wgpuBufferRelease(Buffer);
+ Buffer = nullptr;
+ }
+#if GPU_ENABLE_RESOURCE_NAMING
+ _name.Clear();
+#endif
+
+ // Base
+ GPUBuffer::OnReleaseGPU();
+}
+
+#endif
diff --git a/Source/Engine/GraphicsDevice/WebGPU/GPUBufferWebGPU.h b/Source/Engine/GraphicsDevice/WebGPU/GPUBufferWebGPU.h
new file mode 100644
index 000000000..951c96335
--- /dev/null
+++ b/Source/Engine/GraphicsDevice/WebGPU/GPUBufferWebGPU.h
@@ -0,0 +1,68 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#if GRAPHICS_API_WEBGPU
+
+#include "Engine/Graphics/GPUBuffer.h"
+#include "GPUDeviceWebGPU.h"
+
+///
+/// The buffer view for Web GPU backend.
+///
+///
+class GPUBufferViewWebGPU : public GPUBufferView
+{
+public:
+ // Handle to the WebGPU buffer object.
+ WGPUBuffer Buffer = nullptr;
+ GPUResourceViewPtrWebGPU Ptr;
+
+ void Set(GPUBuffer* parent, WGPUBuffer buffer)
+ {
+ _parent = parent;
+ Buffer = buffer;
+ Ptr = { this, nullptr };
+ }
+
+public:
+ // [GPUResourceView]
+ void* GetNativePtr() const override
+ {
+ return (void*)&Ptr;
+ }
+};
+
+///
+/// GPU buffer for Web GPU backend.
+///
+///
+class GPUBufferWebGPU : public GPUResourceWebGPU
+{
+private:
+ GPUBufferViewWebGPU _view;
+ bool _mapped = false;
+#if GPU_ENABLE_RESOURCE_NAMING
+ StringAnsi _name;
+#endif
+
+public:
+ GPUBufferWebGPU(GPUDeviceWebGPU* device, const StringView& name);
+
+public:
+ // Handle to the WebGPU buffer object.
+ WGPUBuffer Buffer = nullptr;
+
+public:
+ // [GPUBuffer]
+ GPUBufferView* View() const override;
+ void* Map(GPUResourceMapMode mode) override;
+ void Unmap() override;
+
+protected:
+ // [GPUBuffer]
+ bool OnInit() override;
+ void OnReleaseGPU() override;
+};
+
+#endif
diff --git a/Source/Engine/GraphicsDevice/WebGPU/GPUContextWebGPU.cpp b/Source/Engine/GraphicsDevice/WebGPU/GPUContextWebGPU.cpp
new file mode 100644
index 000000000..7d3971051
--- /dev/null
+++ b/Source/Engine/GraphicsDevice/WebGPU/GPUContextWebGPU.cpp
@@ -0,0 +1,817 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#if GRAPHICS_API_WEBGPU
+
+#include "GPUContextWebGPU.h"
+#include "GPUShaderWebGPU.h"
+#include "GPUShaderProgramWebGPU.h"
+#include "GPUPipelineStateWebGPU.h"
+#include "GPUTextureWebGPU.h"
+#include "GPUBufferWebGPU.h"
+#include "GPUSamplerWebGPU.h"
+#include "GPUVertexLayoutWebGPU.h"
+#include "RenderToolsWebGPU.h"
+#include "Engine/Core/Math/Viewport.h"
+#include "Engine/Core/Math/Rectangle.h"
+#include "Engine/Profiler/ProfilerCPU.h"
+#include "Engine/Profiler/RenderStats.h"
+#include "Engine/Graphics/PixelFormatExtensions.h"
+
+// Ensure to match the indirect commands arguments layout
+static_assert(sizeof(GPUDispatchIndirectArgs) == sizeof(uint32) * 3, "Wrong size of GPUDrawIndirectArgs.");
+static_assert(OFFSET_OF(GPUDispatchIndirectArgs, ThreadGroupCountX) == sizeof(uint32) * 0, "Wrong offset for GPUDrawIndirectArgs::ThreadGroupCountX");
+static_assert(OFFSET_OF(GPUDispatchIndirectArgs, ThreadGroupCountY) == sizeof(uint32) * 1, "Wrong offset for GPUDrawIndirectArgs::ThreadGroupCountY");
+static_assert(OFFSET_OF(GPUDispatchIndirectArgs, ThreadGroupCountZ) == sizeof(uint32) * 2, "Wrong offset for GPUDrawIndirectArgs::ThreadGroupCountZ");
+//
+static_assert(sizeof(GPUDrawIndirectArgs) == sizeof(uint32) * 4, "Wrong size of GPUDrawIndirectArgs.");
+static_assert(OFFSET_OF(GPUDrawIndirectArgs, VerticesCount) == sizeof(uint32) * 0, "Wrong offset for GPUDrawIndirectArgs::VerticesCount");
+static_assert(OFFSET_OF(GPUDrawIndirectArgs, InstanceCount) == sizeof(uint32) * 1, "Wrong offset for GPUDrawIndirectArgs::InstanceCount");
+static_assert(OFFSET_OF(GPUDrawIndirectArgs, StartVertex) == sizeof(uint32) * 2, "Wrong offset for GPUDrawIndirectArgs::StartVertex");
+static_assert(OFFSET_OF(GPUDrawIndirectArgs, StartInstance) == sizeof(uint32) * 3, "Wrong offset for GPUDrawIndirectArgs::StartInstance");
+//
+static_assert(sizeof(GPUDrawIndexedIndirectArgs) == sizeof(uint32) * 5, "Wrong size of GPUDrawIndexedIndirectArgs.");
+static_assert(OFFSET_OF(GPUDrawIndexedIndirectArgs, IndicesCount) == sizeof(uint32) * 0, "Wrong offset for GPUDrawIndexedIndirectArgs::IndicesCount");
+static_assert(OFFSET_OF(GPUDrawIndexedIndirectArgs, InstanceCount) == sizeof(uint32) * 1, "Wrong offset for GPUDrawIndexedIndirectArgs::InstanceCount");
+static_assert(OFFSET_OF(GPUDrawIndexedIndirectArgs, StartIndex) == sizeof(uint32) * 2, "Wrong offset for GPUDrawIndexedIndirectArgs::StartIndex");
+static_assert(OFFSET_OF(GPUDrawIndexedIndirectArgs, StartVertex) == sizeof(uint32) * 3, "Wrong offset for GPUDrawIndexedIndirectArgs::StartVertex");
+static_assert(OFFSET_OF(GPUDrawIndexedIndirectArgs, StartInstance) == sizeof(uint32) * 4, "Wrong offset for GPUDrawIndexedIndirectArgs::StartInstance");
+
+GPUContextWebGPU::GPUContextWebGPU(GPUDeviceWebGPU* device)
+ : GPUContext(device)
+ , _device(device)
+{
+ _vertexBufferNullLayout = WGPU_VERTEX_BUFFER_LAYOUT_INIT;
+}
+
+GPUContextWebGPU::~GPUContextWebGPU()
+{
+ CHECK(Encoder == nullptr);
+}
+
+void GPUContextWebGPU::FrameBegin()
+{
+ // Base
+ GPUContext::FrameBegin();
+
+ // Setup
+ _renderPassDirty = false;
+ _pipelineDirty = false;
+ _bindGroupDirty = false;
+ _indexBufferDirty = false;
+ _vertexBufferDirty = false;
+ _indexBuffer32Bit = false;
+ _blendFactorDirty = false;
+ _blendFactorSet = false;
+ _renderTargetCount = 0;
+ _vertexBufferCount = 0;
+ _stencilRef = 0;
+ _blendFactor = Float4::One;
+ _viewport = Viewport(Float2::Zero);
+ _scissorRect = Rectangle::Empty;
+ _renderPass = nullptr;
+ _depthStencil = nullptr;
+ _pipelineState = nullptr;
+ Platform::MemoryClear(&_pipelineKey, sizeof(_pipelineKey));
+ Platform::MemoryClear(&_indexBuffer, sizeof(_indexBuffer));
+ Platform::MemoryClear(&_vertexBuffers, sizeof(_vertexBuffers));
+ Platform::MemoryClear(&_renderTargets, sizeof(_renderTargets));
+ Platform::MemoryClear(&_constantBuffers, sizeof(_constantBuffers));
+ Platform::MemoryClear(&_shaderResources, sizeof(_shaderResources));
+ Platform::MemoryClear(&_storageResources, sizeof(_storageResources));
+ _pendingClears.Clear();
+
+ // Create command encoder
+ WGPUCommandEncoderDescriptor encoderDesc = WGPU_COMMAND_ENCODER_DESCRIPTOR_INIT;
+ Encoder = wgpuDeviceCreateCommandEncoder(_device->Device, &encoderDesc);
+ ASSERT(Encoder);
+
+ // Bind static samplers
+ for (int32 i = 0; i < ARRAY_COUNT(_device->DefaultSamplers); i++)
+ _samplers[i] = _device->DefaultSamplers[i];
+}
+
+void GPUContextWebGPU::FrameEnd()
+{
+ // Base
+ GPUContext::FrameEnd();
+
+ // Flush command encoder to the command buffer and submit them on a queue
+ Flush();
+}
+
+void* GPUContextWebGPU::GetNativePtr() const
+{
+ return Encoder;
+}
+
+bool GPUContextWebGPU::IsDepthBufferBinded()
+{
+ return _depthStencil != nullptr;
+}
+
+void GPUContextWebGPU::Clear(GPUTextureView* rt, const Color& color)
+{
+ auto& clear = _pendingClears.AddOne();
+ clear.View = (GPUTextureViewWebGPU*)rt;
+ Platform::MemoryCopy(clear.RGBA, color.Raw, sizeof(color.Raw));
+}
+
+void GPUContextWebGPU::ClearDepth(GPUTextureView* depthBuffer, float depthValue, uint8 stencilValue)
+{
+ auto& clear = _pendingClears.AddOne();
+ clear.View = (GPUTextureViewWebGPU*)depthBuffer;
+ clear.Depth = depthValue;
+ clear.Stencil = stencilValue;
+}
+
+void GPUContextWebGPU::ClearUA(GPUBuffer* buf, const Float4& value)
+{
+ MISSING_CODE("GPUContextWebGPU::ClearUA");
+}
+
+void GPUContextWebGPU::ClearUA(GPUBuffer* buf, const uint32 value[4])
+{
+ MISSING_CODE("GPUContextWebGPU::ClearUA");
+}
+
+void GPUContextWebGPU::ClearUA(GPUTexture* texture, const uint32 value[4])
+{
+ MISSING_CODE("GPUContextWebGPU::ClearUA");
+}
+
+void GPUContextWebGPU::ClearUA(GPUTexture* texture, const Float4& value)
+{
+ MISSING_CODE("GPUContextWebGPU::ClearUA");
+}
+
+void GPUContextWebGPU::ResetRenderTarget()
+{
+ if (_renderTargetCount != 0 || _depthStencil)
+ {
+ _renderPassDirty = true;
+ _renderTargetCount = 0;
+ _depthStencil = nullptr;
+ }
+}
+
+void GPUContextWebGPU::SetRenderTarget(GPUTextureView* rt)
+{
+ auto rtWebGPU = (GPUTextureViewWebGPU*)rt;
+ int32 newRtCount = rtWebGPU ? 1 : 0;
+ if (_renderTargetCount != newRtCount || _renderTargets[0] != rtWebGPU || _depthStencil != nullptr)
+ {
+ _renderPassDirty = true;
+ _renderTargetCount = newRtCount;
+ _depthStencil = nullptr;
+ _renderTargets[0] = rtWebGPU;
+ }
+}
+
+void GPUContextWebGPU::SetRenderTarget(GPUTextureView* depthBuffer, GPUTextureView* rt)
+{
+ auto depthBufferGPU = (GPUTextureViewWebGPU*)depthBuffer;
+ auto rtWebGPU = (GPUTextureViewWebGPU*)rt;
+ int32 newRtCount = rtWebGPU ? 1 : 0;
+ if (_renderTargetCount != newRtCount || _renderTargets[0] != rtWebGPU || _depthStencil != depthBufferGPU)
+ {
+ _renderPassDirty = true;
+ _renderTargetCount = newRtCount;
+ _depthStencil = depthBufferGPU;
+ _renderTargets[0] = rtWebGPU;
+ }
+}
+
+void GPUContextWebGPU::SetRenderTarget(GPUTextureView* depthBuffer, const Span& rts)
+{
+ ASSERT(Math::IsInRange(rts.Length(), 1, GPU_MAX_RT_BINDED));
+ auto depthBufferGPU = (GPUTextureViewWebGPU*)depthBuffer;
+ if (_renderTargetCount != rts.Length() || _depthStencil != depthBufferGPU || Platform::MemoryCompare(_renderTargets, rts.Get(), rts.Length() * sizeof(void*)) != 0)
+ {
+ _renderPassDirty = true;
+ _renderTargetCount = rts.Length();
+ _depthStencil = depthBufferGPU;
+ Platform::MemoryCopy(_renderTargets, rts.Get(), rts.Length() * sizeof(void*));
+ }
+}
+
+void GPUContextWebGPU::SetBlendFactor(const Float4& value)
+{
+ if (_blendFactor != value)
+ {
+ _blendFactorDirty = true;
+ _blendFactor = value;
+ _blendFactorSet = value != Float4::One;
+ }
+}
+
+void GPUContextWebGPU::SetStencilRef(uint32 value)
+{
+ if (_stencilRef != value)
+ {
+ _stencilRef = value;
+ if (_renderPass)
+ wgpuRenderPassEncoderSetStencilReference(_renderPass, value);
+ }
+}
+
+void GPUContextWebGPU::ResetSR()
+{
+ Platform::MemoryClear(_shaderResources, sizeof(_shaderResources));
+}
+
+void GPUContextWebGPU::ResetUA()
+{
+ Platform::MemoryClear(_storageResources, sizeof(_storageResources));
+}
+
+void GPUContextWebGPU::ResetCB()
+{
+ _bindGroupDirty = false;
+ Platform::MemoryClear(_constantBuffers, sizeof(_constantBuffers));
+}
+
+void GPUContextWebGPU::BindCB(int32 slot, GPUConstantBuffer* cb)
+{
+ ASSERT(slot >= 0 && slot < GPU_MAX_CB_BINDED);
+ auto cbWebGPU = (GPUConstantBufferWebGPU*)cb;
+ if (_constantBuffers[slot] != cbWebGPU)
+ {
+ _bindGroupDirty = true;
+ _constantBuffers[slot] = cbWebGPU;
+ }
+}
+
+void GPUContextWebGPU::BindSR(int32 slot, GPUResourceView* view)
+{
+ ASSERT(slot >= 0 && slot < GPU_MAX_SR_BINDED);
+ if (_shaderResources[slot] != view)
+ {
+ _bindGroupDirty = true;
+ _shaderResources[slot] = view;
+ if (view)
+ *view->LastRenderTime = _lastRenderTime;
+ }
+}
+
+void GPUContextWebGPU::BindUA(int32 slot, GPUResourceView* view)
+{
+ ASSERT(slot >= 0 && slot < GPU_MAX_UA_BINDED);
+ if (_storageResources[slot] != view)
+ {
+ _bindGroupDirty = true;
+ _storageResources[slot] = view;
+ if (view)
+ *view->LastRenderTime = _lastRenderTime;
+ }
+}
+
+void GPUContextWebGPU::BindVB(const Span& vertexBuffers, const uint32* vertexBuffersOffsets, GPUVertexLayout* vertexLayout)
+{
+ ASSERT(vertexBuffers.Length() <= GPU_MAX_VB_BINDED);
+ _vertexBufferDirty = true;
+ _vertexBufferCount = vertexBuffers.Length();
+ _pipelineKey.VertexBufferCount = vertexBuffers.Length();
+ for (int32 i = 0; i < vertexBuffers.Length(); i++)
+ {
+ auto vbWebGPU = (GPUBufferWebGPU*)vertexBuffers.Get()[i];
+ _vertexBuffers[i].Buffer = vbWebGPU ? vbWebGPU->Buffer : nullptr;
+ _vertexBuffers[i].Offset = vertexBuffersOffsets ? vertexBuffersOffsets[i] : 0;
+ _vertexBuffers[i].Size = vbWebGPU ? vbWebGPU->GetSize() : 0;
+ _pipelineKey.VertexBuffers[i] = vbWebGPU && vbWebGPU->GetVertexLayout() ? &((GPUVertexLayoutWebGPU*)vbWebGPU->GetVertexLayout())->Layout : &_vertexBufferNullLayout;
+ }
+}
+
+void GPUContextWebGPU::BindIB(GPUBuffer* indexBuffer)
+{
+ auto ibWebGPU = (GPUBufferWebGPU*)indexBuffer;
+ _indexBufferDirty = true;
+ _indexBuffer32Bit = indexBuffer->GetFormat() == PixelFormat::R32_UInt;
+ _indexBuffer.Buffer = ibWebGPU->Buffer;
+ _indexBuffer.Offset = 0;
+ _indexBuffer.Size = indexBuffer->GetSize();
+}
+
+void GPUContextWebGPU::BindSampler(int32 slot, GPUSampler* sampler)
+{
+ ASSERT(slot >= 0 && slot < GPU_MAX_SAMPLER_BINDED);
+ auto samplerWebGPU = (GPUSamplerWebGPU*)sampler;
+ if (_samplers[slot] != samplerWebGPU)
+ {
+ _bindGroupDirty = true;
+ _samplers[slot] = samplerWebGPU;
+ }
+}
+
+void GPUContextWebGPU::UpdateCB(GPUConstantBuffer* cb, const void* data)
+{
+ ASSERT(data && cb);
+ auto cbWebGPU = static_cast(cb);
+ const uint32 size = cbWebGPU->GetSize();
+ if (size != 0)
+ {
+ wgpuQueueWriteBuffer(_device->Queue, cbWebGPU->Buffer, 0, data, size);
+ }
+}
+
+void GPUContextWebGPU::Dispatch(GPUShaderProgramCS* shader, uint32 threadGroupCountX, uint32 threadGroupCountY, uint32 threadGroupCountZ)
+{
+ OnDispatch(shader);
+ MISSING_CODE("GPUContextWebGPU::Dispatch");
+ RENDER_STAT_DISPATCH_CALL();
+}
+
+void GPUContextWebGPU::DispatchIndirect(GPUShaderProgramCS* shader, GPUBuffer* bufferForArgs, uint32 offsetForArgs)
+{
+ ASSERT(bufferForArgs && EnumHasAnyFlags(bufferForArgs->GetFlags(), GPUBufferFlags::Argument));
+ auto bufferForArgsWebGPU = (GPUBufferWebGPU*)bufferForArgs;
+ OnDispatch(shader);
+ MISSING_CODE("GPUContextWebGPU::Dispatch");
+ RENDER_STAT_DISPATCH_CALL();
+}
+
+void GPUContextWebGPU::ResolveMultisample(GPUTexture* sourceMultisampleTexture, GPUTexture* destTexture, int32 sourceSubResource, int32 destSubResource, PixelFormat format)
+{
+ ASSERT(sourceMultisampleTexture && sourceMultisampleTexture->IsMultiSample());
+ ASSERT(destTexture && !destTexture->IsMultiSample());
+
+ // TODO: do it via a render pass (see WGPURenderPassColorAttachment::resolveTarget)
+ MISSING_CODE("GPUContextWebGPU::ResolveMultisample");
+}
+
+void GPUContextWebGPU::DrawInstanced(uint32 verticesCount, uint32 instanceCount, int32 startInstance, int32 startVertex)
+{
+ OnDrawCall();
+ wgpuRenderPassEncoderDraw(_renderPass, verticesCount, instanceCount, startVertex, startInstance);
+ RENDER_STAT_DRAW_CALL(verticesCount * instanceCount, verticesCount * instanceCount / 3);
+}
+
+void GPUContextWebGPU::DrawIndexedInstanced(uint32 indicesCount, uint32 instanceCount, int32 startInstance, int32 startVertex, int32 startIndex)
+{
+ OnDrawCall();
+ wgpuRenderPassEncoderDrawIndexed(_renderPass, indicesCount, instanceCount, startIndex, startVertex, startInstance);
+ RENDER_STAT_DRAW_CALL(indicesCount * instanceCount, indicesCount / 3 * instanceCount);
+}
+
+void GPUContextWebGPU::DrawInstancedIndirect(GPUBuffer* bufferForArgs, uint32 offsetForArgs)
+{
+ ASSERT(bufferForArgs && EnumHasAnyFlags(bufferForArgs->GetFlags(), GPUBufferFlags::Argument));
+ const auto bufferForArgsWebGPU = static_cast(bufferForArgs);
+ OnDrawCall();
+ wgpuRenderPassEncoderDrawIndirect(_renderPass, bufferForArgsWebGPU->Buffer, offsetForArgs);
+ RENDER_STAT_DRAW_CALL(0, 0);
+}
+
+void GPUContextWebGPU::DrawIndexedInstancedIndirect(GPUBuffer* bufferForArgs, uint32 offsetForArgs)
+{
+ ASSERT(bufferForArgs && EnumHasAnyFlags(bufferForArgs->GetFlags(), GPUBufferFlags::Argument));
+ const auto bufferForArgsWebGPU = static_cast(bufferForArgs);
+ OnDrawCall();
+ wgpuRenderPassEncoderDrawIndexedIndirect(_renderPass, bufferForArgsWebGPU->Buffer, offsetForArgs);
+ RENDER_STAT_DRAW_CALL(0, 0);
+}
+
+uint64 GPUContextWebGPU::BeginQuery(GPUQueryType type)
+{
+ // TODO: impl timer/occlusion queries
+ return 0;
+}
+
+void GPUContextWebGPU::EndQuery(uint64 queryID)
+{
+}
+
+void GPUContextWebGPU::SetViewport(const Viewport& viewport)
+{
+ _viewport = viewport;
+ if (_renderPass)
+ wgpuRenderPassEncoderSetViewport(_renderPass, viewport.X, viewport.Y, viewport.Width, viewport.Height, viewport.MinDepth, viewport.MaxDepth);
+}
+
+void GPUContextWebGPU::SetScissor(const Rectangle& scissorRect)
+{
+ _scissorRect = scissorRect;
+ if (_renderPass)
+ wgpuRenderPassEncoderSetScissorRect(_renderPass, (uint32_t)scissorRect.GetX(), (uint32_t)scissorRect.GetY(), (uint32_t)scissorRect.GetWidth(), (uint32_t)scissorRect.GetHeight());
+}
+
+void GPUContextWebGPU::SetDepthBounds(float minDepth, float maxDepth)
+{
+}
+
+GPUPipelineState* GPUContextWebGPU::GetState() const
+{
+ return _pipelineState;
+}
+
+void GPUContextWebGPU::SetState(GPUPipelineState* state)
+{
+ if (_pipelineState != state)
+ {
+ _pipelineState = (GPUPipelineStateWebGPU*)state;
+ _pipelineDirty = true;
+ }
+}
+
+void GPUContextWebGPU::ResetState()
+{
+ if (!Encoder)
+ return;
+
+ ResetRenderTarget();
+ ResetSR();
+ ResetUA();
+ ResetCB();
+ SetState(nullptr);
+
+ FlushState();
+}
+
+void GPUContextWebGPU::FlushState()
+{
+ // Flush pending clears
+ for (auto& clear : _pendingClears)
+ ManualClear(clear);
+ _pendingClears.Clear();
+}
+
+void GPUContextWebGPU::Flush()
+{
+ if (!Encoder)
+ return;
+ PROFILE_CPU();
+
+ WGPUCommandBufferDescriptor commandBufferDesc = WGPU_COMMAND_BUFFER_DESCRIPTOR_INIT;
+ WGPUCommandBuffer commandBuffer = wgpuCommandEncoderFinish(Encoder, &commandBufferDesc);
+ wgpuCommandEncoderRelease(Encoder);
+ if (commandBuffer)
+ {
+ wgpuQueueSubmit(_device->Queue, 1, &commandBuffer);
+ wgpuCommandBufferRelease(commandBuffer);
+ }
+}
+
+void GPUContextWebGPU::UpdateBuffer(GPUBuffer* buffer, const void* data, uint32 size, uint32 offset)
+{
+ ASSERT(data);
+ ASSERT(buffer && buffer->GetSize() >= size);
+ auto bufferWebGPU = (GPUBufferWebGPU*)buffer;
+ wgpuQueueWriteBuffer(_device->Queue, bufferWebGPU->Buffer, offset, data, size);
+}
+
+void GPUContextWebGPU::CopyBuffer(GPUBuffer* dstBuffer, GPUBuffer* srcBuffer, uint32 size, uint32 dstOffset, uint32 srcOffset)
+{
+ ASSERT(dstBuffer && srcBuffer);
+ auto srcBufferWebGPU = (GPUBufferWebGPU*)srcBuffer;
+ auto dstBufferWebGPU = (GPUBufferWebGPU*)dstBuffer;
+ wgpuCommandEncoderCopyBufferToBuffer(Encoder, srcBufferWebGPU->Buffer, srcOffset, dstBufferWebGPU->Buffer, dstOffset, size);
+}
+
+void GPUContextWebGPU::UpdateTexture(GPUTexture* texture, int32 arrayIndex, int32 mipIndex, const void* data, uint32 rowPitch, uint32 slicePitch)
+{
+ ASSERT(texture && texture->IsAllocated() && data);
+ auto textureWebGPU = (GPUTextureWebGPU*)texture;
+ ASSERT_LOW_LAYER(textureWebGPU->Texture && wgpuTextureGetUsage(textureWebGPU->Texture) & WGPUTextureUsage_CopyDst);
+ ASSERT(!texture->IsVolume()); // TODO: impl uploading volume textures (handle write size properly)
+
+ int32 mipWidth, mipHeight, mipDepth;
+ texture->GetMipSize(mipIndex, mipWidth, mipHeight, mipDepth);
+
+ WGPUTexelCopyTextureInfo copyInfo = WGPU_TEXEL_COPY_TEXTURE_INFO_INIT;
+ copyInfo.texture = textureWebGPU->Texture;
+ copyInfo.mipLevel = mipIndex;
+ copyInfo.aspect = WGPUTextureAspect_All;
+ WGPUTexelCopyBufferLayout dataLayout = WGPU_TEXEL_COPY_BUFFER_LAYOUT_INIT;
+ dataLayout.bytesPerRow = rowPitch;
+ dataLayout.rowsPerImage = mipHeight;
+ WGPUExtent3D writeSize = { (uint32_t)mipWidth, (uint32_t)mipHeight, 1 };
+ wgpuQueueWriteTexture(_device->Queue, ©Info, data, slicePitch, &dataLayout, &writeSize);
+}
+
+void GPUContextWebGPU::CopyTexture(GPUTexture* dstResource, uint32 dstSubresource, uint32 dstX, uint32 dstY, uint32 dstZ, GPUTexture* srcResource, uint32 srcSubresource)
+{
+ ASSERT(dstResource && srcResource);
+ auto srcTextureWebGPU = (GPUTextureWebGPU*)srcResource;
+ auto dstTextureWebGPU = (GPUTextureWebGPU*)dstResource;
+ ASSERT_LOW_LAYER(dstTextureWebGPU->Texture && wgpuTextureGetUsage(dstTextureWebGPU->Texture) & WGPUTextureUsage_CopyDst);
+ ASSERT_LOW_LAYER(srcTextureWebGPU->Texture && wgpuTextureGetUsage(srcTextureWebGPU->Texture) & WGPUTextureUsage_CopySrc);
+
+ // TODO: handle array/depth slices
+ const int32 srcMipIndex = srcSubresource % srcTextureWebGPU->MipLevels();
+ const int32 dstMipIndex = dstSubresource % srcTextureWebGPU->MipLevels();
+
+ int32 srcMipWidth, srcMipHeight, srcMipDepth;
+ srcTextureWebGPU->GetMipSize(srcMipIndex, srcMipWidth, srcMipHeight, srcMipDepth);
+
+ WGPUTexelCopyTextureInfo srcInfo = WGPU_TEXEL_COPY_TEXTURE_INFO_INIT;
+ srcInfo.texture = srcTextureWebGPU->Texture;
+ srcInfo.mipLevel = srcMipIndex;
+ srcInfo.aspect = WGPUTextureAspect_All;
+ WGPUTexelCopyTextureInfo dstInfo = WGPU_TEXEL_COPY_TEXTURE_INFO_INIT;
+ dstInfo.texture = dstTextureWebGPU->Texture;
+ dstInfo.mipLevel = dstMipIndex;
+ dstInfo.origin = { dstX, dstY, dstZ };
+ dstInfo.aspect = WGPUTextureAspect_All;
+ WGPUExtent3D copySize = { (uint32_t)srcMipWidth, (uint32_t)srcMipHeight, (uint32_t)srcMipDepth };
+ wgpuCommandEncoderCopyTextureToTexture(Encoder, &srcInfo, &dstInfo, ©Size);
+}
+
+void GPUContextWebGPU::ResetCounter(GPUBuffer* buffer)
+{
+ MISSING_CODE("GPUContextWebGPU::ResetCounter");
+}
+
+void GPUContextWebGPU::CopyCounter(GPUBuffer* dstBuffer, uint32 dstOffset, GPUBuffer* srcBuffer)
+{
+ MISSING_CODE("GPUContextWebGPU::CopyCounter");
+}
+
+void GPUContextWebGPU::CopyResource(GPUResource* dstResource, GPUResource* srcResource)
+{
+ ASSERT(dstResource && srcResource);
+ auto dstTexture = Cast(dstResource);
+ auto srcTexture = Cast(srcResource);
+ if (srcTexture && dstTexture)
+ {
+ // Texture -> Texture
+ ASSERT(srcTexture->MipLevels() == dstTexture->MipLevels());
+ ASSERT(srcTexture->ArraySize() == 1); // TODO: implement copying texture arrays
+ for (int32 mipLevel = 0; mipLevel < srcTexture->MipLevels(); mipLevel++)
+ CopyTexture(dstTexture, mipLevel, 0, 0, 0, srcTexture, mipLevel);
+ }
+ else if (srcTexture)
+ {
+ // Texture -> Buffer
+ auto srcTextureWebGPU = (GPUTextureWebGPU*)srcResource;
+ auto dstBufferWebGPU = (GPUBufferWebGPU*)dstResource;
+ MISSING_CODE("GPUContextWebGPU::CopyResource: texture -> buffer"); // TODO: impl this
+ }
+ else if (dstTexture)
+ {
+ // Buffer -> Texture
+ auto srcBufferWebGPU = (GPUBufferWebGPU*)srcResource;
+ auto dstTextureWebGPU = (GPUTextureWebGPU*)dstResource;
+ MISSING_CODE("GPUContextWebGPU::CopyResource: buffer -> texture"); // TODO: impl this
+ }
+ else
+ {
+ // Buffer -> Buffer
+ auto srcBufferWebGPU = (GPUBufferWebGPU*)srcResource;
+ auto dstBufferWebGPU = (GPUBufferWebGPU*)dstResource;
+ uint64 size = Math::Min(srcBufferWebGPU->GetSize(), dstBufferWebGPU->GetSize());
+ wgpuCommandEncoderCopyBufferToBuffer(Encoder, srcBufferWebGPU->Buffer, 0, dstBufferWebGPU->Buffer, 0, size);
+ }
+}
+
+void GPUContextWebGPU::CopySubresource(GPUResource* dstResource, uint32 dstSubresource, GPUResource* srcResource, uint32 srcSubresource)
+{
+ ASSERT(dstResource && srcResource);
+ auto dstTexture = Cast(dstResource);
+ auto srcTexture = Cast(srcResource);
+ if (srcTexture && dstTexture)
+ {
+ // Texture -> Texture
+ CopyTexture(dstTexture, dstSubresource, 0, 0, 0, srcTexture, srcSubresource);
+ }
+ else if (srcTexture)
+ {
+ // Texture -> Buffer
+ auto srcTextureWebGPU = (GPUTextureWebGPU*)srcResource;
+ auto dstBufferWebGPU = (GPUBufferWebGPU*)dstResource;
+ MISSING_CODE("GPUContextWebGPU::CopyResource: texture -> buffer"); // TODO: impl this
+ }
+ else if (dstTexture)
+ {
+ // Buffer -> Texture
+ auto srcBufferWebGPU = (GPUBufferWebGPU*)srcResource;
+ auto dstTextureWebGPU = (GPUTextureWebGPU*)dstResource;
+ MISSING_CODE("GPUContextWebGPU::CopyResource: buffer -> texture"); // TODO: impl this
+ }
+ else
+ {
+ // Buffer -> Buffer
+ ASSERT(dstSubresource == 0 && srcSubresource == 0);
+ auto srcBufferWebGPU = (GPUBufferWebGPU*)srcResource;
+ auto dstBufferWebGPU = (GPUBufferWebGPU*)dstResource;
+ uint64 size = Math::Min(srcBufferWebGPU->GetSize(), dstBufferWebGPU->GetSize());
+ wgpuCommandEncoderCopyBufferToBuffer(Encoder, srcBufferWebGPU->Buffer, 0, dstBufferWebGPU->Buffer, 0, size);
+ }
+}
+
+bool GPUContextWebGPU::FindClear(const GPUTextureViewWebGPU* view, PendingClear& clear)
+{
+ for (auto& e : _pendingClears)
+ {
+ if (e.View == view)
+ {
+ clear = e;
+ return true;
+ }
+ }
+ return false;
+}
+
+void GPUContextWebGPU::ManualClear(const PendingClear& clear)
+{
+ // End existing pass (if any)
+ if (_renderPass)
+ {
+ wgpuRenderPassEncoderEnd(_renderPass);
+ wgpuRenderPassEncoderRelease(_renderPass);
+ _renderPass = nullptr;
+ }
+
+ // Clear with a render pass
+ WGPURenderPassColorAttachment colorAttachment;
+ WGPURenderPassDepthStencilAttachment depthStencilAttachment;
+ WGPURenderPassDescriptor renderPassDesc = WGPU_RENDER_PASS_DESCRIPTOR_INIT;
+ if (((GPUTextureWebGPU*)clear.View->GetParent())->IsDepthStencil())
+ {
+ renderPassDesc.depthStencilAttachment = &depthStencilAttachment;
+ depthStencilAttachment = WGPU_RENDER_PASS_DEPTH_STENCIL_ATTACHMENT_INIT;
+ depthStencilAttachment.view = clear.View->View;
+ depthStencilAttachment.depthLoadOp = WGPULoadOp_Clear;
+ depthStencilAttachment.depthStoreOp = WGPUStoreOp_Store;
+ depthStencilAttachment.depthClearValue = clear.Depth;
+ depthStencilAttachment.stencilClearValue = clear.Stencil;
+ }
+ else
+ {
+ renderPassDesc.colorAttachmentCount = 1;
+ renderPassDesc.colorAttachments = &colorAttachment;
+ colorAttachment = WGPU_RENDER_PASS_COLOR_ATTACHMENT_INIT;
+ colorAttachment.view = clear.View->View;
+ colorAttachment.depthSlice = clear.View->DepthSlice;
+ colorAttachment.loadOp = WGPULoadOp_Clear;
+ colorAttachment.storeOp = WGPUStoreOp_Store;
+ if (clear.View->HasStencil)
+ {
+ depthStencilAttachment.stencilLoadOp = WGPULoadOp_Clear;
+ depthStencilAttachment.stencilStoreOp = WGPUStoreOp_Store;
+ depthStencilAttachment.stencilClearValue = clear.Stencil;
+ }
+ colorAttachment.clearValue = { clear.RGBA[0], clear.RGBA[1], clear.RGBA[2], clear.RGBA[3] };
+ }
+ auto renderPass = wgpuCommandEncoderBeginRenderPass(Encoder, &renderPassDesc);
+ wgpuRenderPassEncoderEnd(renderPass);
+ wgpuRenderPassEncoderRelease(renderPass);
+}
+
+void GPUContextWebGPU::OnDrawCall()
+{
+ // Clear textures that are not bind to the render pass
+ auto renderTargets = ToSpan(_renderTargets, _renderTargetCount);
+ for (int32 i = _pendingClears.Count() - 1; i >= 0; i--)
+ {
+ auto clear = _pendingClears[i];
+ if (clear.View != _depthStencil && SpanContains(renderTargets, clear.View))
+ {
+ ManualClear(clear);
+ _pendingClears.RemoveAt(i);
+ }
+ }
+
+ // Check if need to start a new render pass
+ if (_renderPassDirty)
+ {
+ _renderPassDirty = false;
+
+ // End existing pass (if any)
+ if (_renderPass)
+ {
+ wgpuRenderPassEncoderEnd(_renderPass);
+ wgpuRenderPassEncoderRelease(_renderPass);
+ }
+
+ // Start a new render pass
+ WGPURenderPassColorAttachment colorAttachments[GPU_MAX_RT_BINDED];
+ WGPURenderPassDepthStencilAttachment depthStencilAttachment = WGPU_RENDER_PASS_DEPTH_STENCIL_ATTACHMENT_INIT;
+ WGPURenderPassDescriptor renderPassDesc = WGPU_RENDER_PASS_DESCRIPTOR_INIT;
+ renderPassDesc.colorAttachmentCount = _renderTargetCount;
+ renderPassDesc.colorAttachments = colorAttachments;
+ PendingClear clear;
+ _pipelineKey.MultiSampleCount = 1;
+ _pipelineKey.RenderTargetCount = _renderTargetCount;
+ for (int32 i = 0; i < renderPassDesc.colorAttachmentCount; i++)
+ {
+ auto& colorAttachment = colorAttachments[i];
+ colorAttachment = WGPU_RENDER_PASS_COLOR_ATTACHMENT_INIT;
+ auto renderTarget = _renderTargets[i];
+ colorAttachment.view = renderTarget->View;
+ colorAttachment.depthSlice = renderTarget->DepthSlice;
+ colorAttachment.loadOp = WGPULoadOp_Load;
+ colorAttachment.storeOp = WGPUStoreOp_Store;
+ if (FindClear(_depthStencil, clear))
+ {
+ colorAttachment.loadOp = WGPULoadOp_Clear;
+ colorAttachment.clearValue = { clear.RGBA[0], clear.RGBA[1], clear.RGBA[2], clear.RGBA[3] };
+ }
+ _pipelineKey.MultiSampleCount = (int32)renderTarget->GetMSAA();
+ _pipelineKey.RenderTargetFormats[i] = RenderToolsWebGPU::ToTextureFormat(renderTarget->GetFormat());
+ }
+ if (_depthStencil)
+ {
+ renderPassDesc.depthStencilAttachment = &depthStencilAttachment;
+ depthStencilAttachment.view = _depthStencil->View;
+ depthStencilAttachment.depthLoadOp = WGPULoadOp_Load;
+ depthStencilAttachment.depthStoreOp = _depthStencil->ReadOnly ? WGPUStoreOp_Discard : WGPUStoreOp_Store;
+ depthStencilAttachment.depthReadOnly = _depthStencil->ReadOnly;
+ if (_depthStencil->HasStencil)
+ {
+ depthStencilAttachment.stencilLoadOp = WGPULoadOp_Load;
+ depthStencilAttachment.stencilStoreOp = _depthStencil->ReadOnly ? WGPUStoreOp_Discard : WGPUStoreOp_Store;
+ depthStencilAttachment.depthReadOnly = _depthStencil->ReadOnly;
+ }
+ else
+ {
+ depthStencilAttachment.stencilClearValue = 0;
+ depthStencilAttachment.stencilLoadOp = WGPULoadOp_Clear;
+ depthStencilAttachment.stencilStoreOp = WGPUStoreOp_Discard;
+ depthStencilAttachment.stencilReadOnly = true;
+ }
+ if (FindClear(_depthStencil, clear))
+ {
+ depthStencilAttachment.depthLoadOp = WGPULoadOp_Clear;
+ depthStencilAttachment.depthClearValue = clear.Depth;
+ if (_depthStencil->HasStencil)
+ {
+ depthStencilAttachment.stencilLoadOp = WGPULoadOp_Clear;
+ depthStencilAttachment.stencilClearValue = clear.Stencil;
+ }
+ }
+ _pipelineKey.DepthStencilFormat = RenderToolsWebGPU::ToTextureFormat(_depthStencil->GetFormat());
+ }
+ else
+ {
+ _pipelineKey.DepthStencilFormat = WGPUTextureFormat_Undefined;
+ }
+ _renderPass = wgpuCommandEncoderBeginRenderPass(Encoder, &renderPassDesc);
+ ASSERT(_renderPass);
+
+ // Discard texture clears (done manually or via render pass)
+ _pendingClears.Clear();
+
+ // Apply pending state
+ if (_stencilRef != 0)
+ {
+ wgpuRenderPassEncoderSetStencilReference(_renderPass, _stencilRef);
+ }
+ auto scissorRect = _scissorRect;
+ // TODO: skip calling this if scissorRect is default (0, 0, attachment width, attachment height)
+ wgpuRenderPassEncoderSetScissorRect(_renderPass, (uint32_t)scissorRect.GetX(), (uint32_t)scissorRect.GetY(), (uint32_t)scissorRect.GetWidth(), (uint32_t)scissorRect.GetHeight());
+ auto viewport = _viewport;
+ // TODO: skip calling this if viewport is default (0, 0, attachment width, attachment height, 0, 1)
+ wgpuRenderPassEncoderSetViewport(_renderPass, viewport.X, viewport.Y, viewport.Width, viewport.Height, viewport.MinDepth, viewport.MaxDepth);
+
+ // Auto-dirty pipeline when new render pass starts
+ if (_pipelineState)
+ _pipelineDirty = true;
+ _indexBufferDirty = true;
+ _vertexBufferDirty = true;
+ _bindGroupDirty = true;
+ if (_blendFactorSet)
+ _blendFactorDirty = true;
+ }
+
+ // Flush rendering states
+ if (_pipelineDirty)
+ {
+ _pipelineDirty = false;
+ WGPURenderPipeline pipeline = _pipelineState ? _pipelineState->GetPipeline(_pipelineKey) : nullptr;
+ wgpuRenderPassEncoderSetPipeline(_renderPass, pipeline);
+ RENDER_STAT_PS_STATE_CHANGE();
+ }
+ if (_indexBufferDirty)
+ {
+ _indexBufferDirty = false;
+ wgpuRenderPassEncoderSetIndexBuffer(_renderPass, _indexBuffer.Buffer, _indexBuffer32Bit ? WGPUIndexFormat_Uint32 : WGPUIndexFormat_Uint16, _indexBuffer.Offset, _indexBuffer.Size);
+ }
+ if (_vertexBufferDirty)
+ {
+ _vertexBufferDirty = false;
+ for (int32 i = 0; i < _vertexBufferCount; i++)
+ {
+ auto vb = _vertexBuffers[i];
+ wgpuRenderPassEncoderSetVertexBuffer(_renderPass, i, vb.Buffer, vb.Offset, vb.Size);
+ }
+ }
+ if (_blendFactorDirty)
+ {
+ _blendFactorDirty = false;
+ WGPUColor color = { _blendFactor.X, _blendFactor.Y, _blendFactor.Z, _blendFactor.W };
+ wgpuRenderPassEncoderSetBlendConstant(_renderPass, &color);
+ }
+ if (_bindGroupDirty)
+ {
+ _bindGroupDirty = false;
+ // TODO: bind _samplers
+ // TODO: bind _constantBuffers
+ // TODO: bind _shaderResources
+ }
+}
+
+void GPUContextWebGPU::OnDispatch(GPUShaderProgramCS* shader)
+{
+ // TODO: add compute shaders support
+}
+
+#endif
diff --git a/Source/Engine/GraphicsDevice/WebGPU/GPUContextWebGPU.h b/Source/Engine/GraphicsDevice/WebGPU/GPUContextWebGPU.h
new file mode 100644
index 000000000..ae7093627
--- /dev/null
+++ b/Source/Engine/GraphicsDevice/WebGPU/GPUContextWebGPU.h
@@ -0,0 +1,144 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#include "Engine/Graphics/GPUContext.h"
+#include "Engine/Core/Math/Vector4.h"
+#include "GPUDeviceWebGPU.h"
+#include "GPUPipelineStateWebGPU.h"
+
+#if GRAPHICS_API_WEBGPU
+
+class GPUBufferWebGPU;
+class GPUSamplerWebGPU;
+class GPUTextureViewWebGPU;
+class GPUVertexLayoutWebGPU;
+class GPUConstantBufferWebGPU;
+
+///
+/// GPU Context for Web GPU backend.
+///
+class GPUContextWebGPU : public GPUContext
+{
+private:
+ struct BufferBind
+ {
+ WGPUBuffer Buffer;
+ uint32 Size, Offset;
+ };
+
+ struct PendingClear
+ {
+ GPUTextureViewWebGPU* View;
+ union
+ {
+ float RGBA[4];
+ struct
+ {
+ float Depth;
+ uint32 Stencil;
+ };
+ };
+ };
+
+ GPUDeviceWebGPU* _device;
+ WGPUVertexBufferLayout _vertexBufferNullLayout;
+
+ // State tracking
+ int32 _renderPassDirty : 1;
+ int32 _pipelineDirty : 1;
+ int32 _bindGroupDirty : 1;
+ int32 _vertexBufferDirty : 1;
+ int32 _indexBufferDirty : 1;
+ int32 _indexBuffer32Bit : 1;
+ int32 _blendFactorDirty : 1;
+ int32 _blendFactorSet : 1;
+ int32 _renderTargetCount : 3;
+ int32 _vertexBufferCount : 3;
+ uint32 _stencilRef;
+ Float4 _blendFactor;
+ Viewport _viewport;
+ Rectangle _scissorRect;
+ WGPURenderPassEncoder _renderPass;
+ GPUTextureViewWebGPU* _depthStencil;
+ GPUTextureViewWebGPU* _renderTargets[GPU_MAX_RT_BINDED];
+ GPUConstantBufferWebGPU* _constantBuffers[GPU_MAX_CB_BINDED];
+ GPUSamplerWebGPU* _samplers[GPU_MAX_SAMPLER_BINDED];
+ BufferBind _indexBuffer;
+ BufferBind _vertexBuffers[GPU_MAX_VB_BINDED];
+ GPUPipelineStateWebGPU* _pipelineState;
+ GPUPipelineStateWebGPU::Key _pipelineKey;
+ Array> _pendingClears;
+ GPUResourceView* _shaderResources[GPU_MAX_SR_BINDED];
+ GPUResourceView* _storageResources[GPU_MAX_SR_BINDED];
+
+public:
+ GPUContextWebGPU(GPUDeviceWebGPU* device);
+ ~GPUContextWebGPU();
+
+public:
+ // Handle to the WebGPU command encoder object.
+ WGPUCommandEncoder Encoder = nullptr;
+
+private:
+ bool FindClear(const GPUTextureViewWebGPU* view, PendingClear& clear);
+ void ManualClear(const PendingClear& clear);
+ void OnDrawCall();
+ void OnDispatch(GPUShaderProgramCS* shader);
+
+public:
+ // [GPUContext]
+ void FrameBegin() override;
+ void FrameEnd() override;
+ void* GetNativePtr() const override;
+ bool IsDepthBufferBinded() override;
+ void Clear(GPUTextureView* rt, const Color& color) override;
+ void ClearDepth(GPUTextureView* depthBuffer, float depthValue, uint8 stencilValue) override;
+ void ClearUA(GPUBuffer* buf, const Float4& value) override;
+ void ClearUA(GPUBuffer* buf, const uint32 value[4]) override;
+ void ClearUA(GPUTexture* texture, const uint32 value[4]) override;
+ void ClearUA(GPUTexture* texture, const Float4& value) override;
+ void ResetRenderTarget() override;
+ void SetRenderTarget(GPUTextureView* rt) override;
+ void SetRenderTarget(GPUTextureView* depthBuffer, GPUTextureView* rt) override;
+ void SetRenderTarget(GPUTextureView* depthBuffer, const Span& rts) override;
+ void SetBlendFactor(const Float4& value) override;
+ void SetStencilRef(uint32 value) override;
+ void ResetSR() override;
+ void ResetUA() override;
+ void ResetCB() override;
+ void BindCB(int32 slot, GPUConstantBuffer* cb) override;
+ void BindSR(int32 slot, GPUResourceView* view) override;
+ void BindUA(int32 slot, GPUResourceView* view) override;
+ void BindVB(const Span& vertexBuffers, const uint32* vertexBuffersOffsets = nullptr, GPUVertexLayout* vertexLayout = nullptr) override;
+ void BindIB(GPUBuffer* indexBuffer) override;
+ void BindSampler(int32 slot, GPUSampler* sampler) override;
+ void UpdateCB(GPUConstantBuffer* cb, const void* data) override;
+ void Dispatch(GPUShaderProgramCS* shader, uint32 threadGroupCountX, uint32 threadGroupCountY, uint32 threadGroupCountZ) override;
+ void DispatchIndirect(GPUShaderProgramCS* shader, GPUBuffer* bufferForArgs, uint32 offsetForArgs) override;
+ void ResolveMultisample(GPUTexture* sourceMultisampleTexture, GPUTexture* destTexture, int32 sourceSubResource, int32 destSubResource, PixelFormat format) override;
+ void DrawInstanced(uint32 verticesCount, uint32 instanceCount, int32 startInstance, int32 startVertex) override;
+ void DrawIndexedInstanced(uint32 indicesCount, uint32 instanceCount, int32 startInstance, int32 startVertex, int32 startIndex) override;
+ void DrawInstancedIndirect(GPUBuffer* bufferForArgs, uint32 offsetForArgs) override;
+ void DrawIndexedInstancedIndirect(GPUBuffer* bufferForArgs, uint32 offsetForArgs) override;
+ uint64 BeginQuery(GPUQueryType type) override;
+ void EndQuery(uint64 queryID) override;
+ void SetViewport(const Viewport& viewport) override;
+ void SetScissor(const Rectangle& scissorRect) override;
+ void SetDepthBounds(float minDepth, float maxDepth) override;
+ GPUPipelineState* GetState() const override;
+ void SetState(GPUPipelineState* state) override;
+ void ResetState() override;
+ void FlushState() override;
+ void Flush() override;
+ void UpdateBuffer(GPUBuffer* buffer, const void* data, uint32 size, uint32 offset) override;
+ void CopyBuffer(GPUBuffer* dstBuffer, GPUBuffer* srcBuffer, uint32 size, uint32 dstOffset, uint32 srcOffset) override;
+ void UpdateTexture(GPUTexture* texture, int32 arrayIndex, int32 mipIndex, const void* data, uint32 rowPitch, uint32 slicePitch) override;
+ void CopyTexture(GPUTexture* dstResource, uint32 dstSubresource, uint32 dstX, uint32 dstY, uint32 dstZ, GPUTexture* srcResource, uint32 srcSubresource) override;
+ void ResetCounter(GPUBuffer* buffer) override;
+ void CopyCounter(GPUBuffer* dstBuffer, uint32 dstOffset, GPUBuffer* srcBuffer) override;
+ void CopyResource(GPUResource* dstResource, GPUResource* srcResource) override;
+ void CopySubresource(GPUResource* dstResource, uint32 dstSubresource, GPUResource* srcResource, uint32 srcSubresource) override;
+};
+
+#endif
diff --git a/Source/Engine/GraphicsDevice/WebGPU/GPUDeviceWebGPU.cpp b/Source/Engine/GraphicsDevice/WebGPU/GPUDeviceWebGPU.cpp
new file mode 100644
index 000000000..130c8d789
--- /dev/null
+++ b/Source/Engine/GraphicsDevice/WebGPU/GPUDeviceWebGPU.cpp
@@ -0,0 +1,581 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#if GRAPHICS_API_WEBGPU
+
+#include "GPUDeviceWebGPU.h"
+#include "GPUShaderWebGPU.h"
+#include "GPUContextWebGPU.h"
+#include "GPUPipelineStateWebGPU.h"
+#include "GPUTextureWebGPU.h"
+#include "GPUBufferWebGPU.h"
+#include "GPUSamplerWebGPU.h"
+#include "GPUAdapterWebGPU.h"
+#include "GPUVertexLayoutWebGPU.h"
+#include "GPUSwapChainWebGPU.h"
+#include "RenderToolsWebGPU.h"
+#include "Engine/Core/Log.h"
+#include "Engine/Core/Utilities.h"
+#if BUILD_DEBUG
+#include "Engine/Core/Collections/Sorting.h"
+#endif
+#include "Engine/Graphics/PixelFormatExtensions.h"
+#include "Engine/Profiler/ProfilerMemory.h"
+#include
+
+GPUVertexLayoutWebGPU::GPUVertexLayoutWebGPU(GPUDeviceWebGPU* device, const Elements& elements, bool explicitOffsets)
+ : GPUResourceBase(device, StringView::Empty)
+{
+ SetElements(elements, explicitOffsets);
+ Layout = WGPU_VERTEX_BUFFER_LAYOUT_INIT;
+ Layout.stepMode = WGPUVertexStepMode_Vertex;
+ Layout.arrayStride = GetStride();
+ Layout.attributeCount = elements.Count();
+ Layout.attributes = Attributes;
+ const VertexElement* srcElements = GetElements().Get();
+ for (int32 i = 0; i < elements.Count(); i++)
+ {
+ const VertexElement& src = srcElements[i];
+ WGPUVertexAttribute& dst = Attributes[i];
+ dst.nextInChain = nullptr;
+ dst.format = RenderToolsWebGPU::ToVertexFormat(src.Format);
+ dst.offset = src.Offset;
+ dst.shaderLocation = src.Slot;
+ if (src.PerInstance)
+ Layout.stepMode = WGPUVertexStepMode_Instance;
+ }
+}
+
+GPUDeviceWebGPU::GPUDeviceWebGPU(WGPUInstance instance, GPUAdapterWebGPU* adapter)
+ : GPUDevice(RendererType::WebGPU, ShaderProfile::WebGPU)
+ , Adapter(adapter)
+ , WebGPUInstance(instance)
+{
+}
+
+bool GPUDeviceWebGPU::Init()
+{
+ // Init device limits
+ Array> features;
+#define CHECK_FEATURE(feature) if (wgpuAdapterHasFeature(Adapter->Adapter, feature)) features.Add(feature)
+ CHECK_FEATURE(WGPUFeatureName_CoreFeaturesAndLimits);
+ CHECK_FEATURE(WGPUFeatureName_DepthClipControl);
+ CHECK_FEATURE(WGPUFeatureName_Depth32FloatStencil8);
+ CHECK_FEATURE(WGPUFeatureName_TextureCompressionBC);
+ CHECK_FEATURE(WGPUFeatureName_TextureCompressionBCSliced3D);
+ CHECK_FEATURE(WGPUFeatureName_TextureCompressionETC2);
+ CHECK_FEATURE(WGPUFeatureName_TextureCompressionASTC);
+ CHECK_FEATURE(WGPUFeatureName_TextureCompressionASTCSliced3D);
+ CHECK_FEATURE(WGPUFeatureName_TimestampQuery);
+ CHECK_FEATURE(WGPUFeatureName_IndirectFirstInstance);
+ CHECK_FEATURE(WGPUFeatureName_RG11B10UfloatRenderable);
+ CHECK_FEATURE(WGPUFeatureName_BGRA8UnormStorage);
+ CHECK_FEATURE(WGPUFeatureName_Float32Filterable);
+ CHECK_FEATURE(WGPUFeatureName_Float32Blendable);
+ CHECK_FEATURE(WGPUFeatureName_ClipDistances);
+ CHECK_FEATURE(WGPUFeatureName_TextureFormatsTier1);
+ CHECK_FEATURE(WGPUFeatureName_TextureFormatsTier2);
+ CHECK_FEATURE(WGPUFeatureName_PrimitiveIndex);
+ CHECK_FEATURE(WGPUFeatureName_MultiDrawIndirect);
+#undef CHECK_FEATURE
+#if BUILD_DEBUG
+ LOG(Info, "WebGPU features:");
+#define LOG_FEATURE(feature) if (features.Contains(feature)) LOG(Info, " > {}", TEXT(#feature))
+ LOG_FEATURE(WGPUFeatureName_CoreFeaturesAndLimits);
+ LOG_FEATURE(WGPUFeatureName_DepthClipControl);
+ LOG_FEATURE(WGPUFeatureName_Depth32FloatStencil8);
+ LOG_FEATURE(WGPUFeatureName_TextureCompressionBC);
+ LOG_FEATURE(WGPUFeatureName_TextureCompressionBCSliced3D);
+ LOG_FEATURE(WGPUFeatureName_TextureCompressionETC2);
+ LOG_FEATURE(WGPUFeatureName_TextureCompressionASTC);
+ LOG_FEATURE(WGPUFeatureName_TextureCompressionASTCSliced3D);
+ LOG_FEATURE(WGPUFeatureName_TimestampQuery);
+ LOG_FEATURE(WGPUFeatureName_IndirectFirstInstance);
+ LOG_FEATURE(WGPUFeatureName_RG11B10UfloatRenderable);
+ LOG_FEATURE(WGPUFeatureName_BGRA8UnormStorage);
+ LOG_FEATURE(WGPUFeatureName_Float32Filterable);
+ LOG_FEATURE(WGPUFeatureName_Float32Blendable);
+ LOG_FEATURE(WGPUFeatureName_ClipDistances);
+ LOG_FEATURE(WGPUFeatureName_TextureFormatsTier1);
+ LOG_FEATURE(WGPUFeatureName_TextureFormatsTier2);
+ LOG_FEATURE(WGPUFeatureName_PrimitiveIndex);
+ LOG_FEATURE(WGPUFeatureName_MultiDrawIndirect);
+#undef LOG_FEATURE
+#endif
+ for (int32 i = 0; i < (int32)PixelFormat::MAX; i++)
+ FeaturesPerFormat[i] = FormatFeatures(MSAALevel::None, FormatSupport::None);
+ WGPULimits limits = WGPU_LIMITS_INIT;
+ if (wgpuAdapterGetLimits(Adapter->Adapter, &limits) == WGPUStatus_Success)
+ {
+ Limits.HasDepthClip = features.Contains(WGPUFeatureName_DepthClipControl);
+ Limits.HasReadOnlyDepth = true;
+ Limits.MaximumTexture1DSize = Math::Min(GPU_MAX_TEXTURE_SIZE, limits.maxTextureDimension1D);
+ Limits.MaximumTexture2DSize = Math::Min(GPU_MAX_TEXTURE_SIZE, limits.maxTextureDimension2D);
+ Limits.MaximumTexture3DSize = Math::Min(GPU_MAX_TEXTURE_SIZE, limits.maxTextureDimension3D);
+ Limits.MaximumMipLevelsCount = Math::Min(GPU_MAX_TEXTURE_MIP_LEVELS, (int32)log2(limits.maxTextureDimension2D));
+ Limits.MaximumTexture1DArraySize = Limits.MaximumTexture2DArraySize = Math::Min(GPU_MAX_TEXTURE_ARRAY_SIZE, limits.maxTextureArrayLayers);
+ if (limits.maxTextureArrayLayers >= 6)
+ Limits.MaximumTextureCubeSize = Limits.MaximumTexture2DSize;
+
+ // Formats support based on: https://gpuweb.github.io/gpuweb/#plain-color-formats
+ auto supportsBuffer =
+ FormatSupport::Buffer |
+ FormatSupport::InputAssemblyIndexBuffer |
+ FormatSupport::InputAssemblyVertexBuffer;
+ auto supportsTexture =
+ FormatSupport::Texture1D |
+ FormatSupport::Texture2D |
+ FormatSupport::Texture3D |
+ FormatSupport::TextureCube |
+ FormatSupport::ShaderLoad |
+ FormatSupport::ShaderSample |
+ FormatSupport::ShaderSampleComparison |
+ FormatSupport::ShaderGather |
+ FormatSupport::ShaderGatherComparison |
+ FormatSupport::Mip;
+ auto supportsRender =
+ FormatSupport::RenderTarget |
+ FormatSupport::Blendable;
+ auto supportsDepth =
+ FormatSupport::DepthStencil;
+ auto supportsMultisampling =
+ FormatSupport::MultisampleRenderTarget |
+ FormatSupport::MultisampleLoad;
+ auto supportsMSAA =
+ FormatSupport::MultisampleRenderTarget |
+ FormatSupport::MultisampleResolve |
+ FormatSupport::MultisampleLoad;
+
+#define ADD_FORMAT(pixelFormat, support)
+ FeaturesPerFormat[(int32)PixelFormat::R8_UNorm].Support |= supportsBuffer | supportsTexture | supportsRender | supportsMSAA;
+ FeaturesPerFormat[(int32)PixelFormat::R8_UInt].Support |= supportsBuffer | supportsTexture | supportsRender;
+ FeaturesPerFormat[(int32)PixelFormat::R8_SInt].Support |= supportsBuffer | supportsTexture | supportsRender;
+ FeaturesPerFormat[(int32)PixelFormat::R16_UInt].Support |= supportsBuffer | supportsTexture;
+ FeaturesPerFormat[(int32)PixelFormat::R16_SInt].Support |= supportsBuffer | supportsTexture;
+ FeaturesPerFormat[(int32)PixelFormat::R16_Float].Support |= supportsBuffer | supportsTexture | supportsRender | supportsMSAA;
+ FeaturesPerFormat[(int32)PixelFormat::R8G8_UNorm].Support |= supportsBuffer | supportsTexture;
+ FeaturesPerFormat[(int32)PixelFormat::R8G8_SNorm].Support |= supportsBuffer | supportsTexture;
+ FeaturesPerFormat[(int32)PixelFormat::R8G8_UInt].Support |= supportsBuffer | supportsTexture;
+ FeaturesPerFormat[(int32)PixelFormat::R8G8_SInt].Support |= supportsBuffer | supportsTexture;
+ FeaturesPerFormat[(int32)PixelFormat::R32_Float].Support |= supportsBuffer | supportsTexture | FormatSupport::RenderTarget;
+ FeaturesPerFormat[(int32)PixelFormat::R32_Float].Support & ~FormatSupport::ShaderSample;
+ FeaturesPerFormat[(int32)PixelFormat::R32_UInt].Support |= supportsBuffer | supportsTexture | FormatSupport::RenderTarget;
+ FeaturesPerFormat[(int32)PixelFormat::R32_SInt].Support |= supportsBuffer | supportsTexture | FormatSupport::RenderTarget;
+ FeaturesPerFormat[(int32)PixelFormat::R16G16_UInt].Support |= supportsBuffer | supportsTexture;
+ FeaturesPerFormat[(int32)PixelFormat::R16G16_SInt].Support |= supportsBuffer | supportsTexture;
+ FeaturesPerFormat[(int32)PixelFormat::R16G16_Float].Support |= supportsBuffer | supportsTexture | supportsRender | supportsMSAA;
+ FeaturesPerFormat[(int32)PixelFormat::R8G8B8A8_UNorm].Support |= supportsBuffer | supportsTexture | supportsRender | supportsMSAA;
+ FeaturesPerFormat[(int32)PixelFormat::R8G8B8A8_UNorm_sRGB].Support |= supportsBuffer | supportsTexture | supportsRender | supportsMSAA;
+ FeaturesPerFormat[(int32)PixelFormat::R8G8B8A8_SNorm].Support |= supportsBuffer | supportsTexture;
+ FeaturesPerFormat[(int32)PixelFormat::R8G8B8A8_UInt].Support |= supportsBuffer | supportsTexture;
+ FeaturesPerFormat[(int32)PixelFormat::R8G8B8A8_SInt].Support |= supportsBuffer | supportsTexture;
+ FeaturesPerFormat[(int32)PixelFormat::B8G8R8A8_UNorm].Support |= supportsBuffer | supportsTexture | supportsRender | supportsMSAA;
+ FeaturesPerFormat[(int32)PixelFormat::R10G10B10A2_UInt].Support |= supportsBuffer | supportsTexture;
+ FeaturesPerFormat[(int32)PixelFormat::R10G10B10A2_UNorm].Support |= supportsBuffer | supportsTexture | supportsRender | supportsMSAA;
+ FeaturesPerFormat[(int32)PixelFormat::R11G11B10_Float].Support |= supportsBuffer | supportsTexture;
+ FeaturesPerFormat[(int32)PixelFormat::R9G9B9E5_SharedExp].Support |= supportsBuffer | supportsTexture;
+ FeaturesPerFormat[(int32)PixelFormat::R32G32_Float].Support |= supportsBuffer | supportsTexture | FormatSupport::RenderTarget;
+ FeaturesPerFormat[(int32)PixelFormat::R32G32_Float].Support & ~FormatSupport::ShaderSample;
+ FeaturesPerFormat[(int32)PixelFormat::R32G32_UInt].Support |= supportsBuffer | supportsTexture | FormatSupport::RenderTarget;
+ FeaturesPerFormat[(int32)PixelFormat::R32G32_SInt].Support |= supportsBuffer | supportsTexture | FormatSupport::RenderTarget;
+ FeaturesPerFormat[(int32)PixelFormat::R16G16B16A16_UInt].Support |= supportsBuffer | supportsTexture;
+ FeaturesPerFormat[(int32)PixelFormat::R16G16B16A16_SInt].Support |= supportsBuffer | supportsTexture;
+ FeaturesPerFormat[(int32)PixelFormat::R16G16B16A16_Float].Support |= supportsBuffer | supportsTexture | supportsRender;
+ FeaturesPerFormat[(int32)PixelFormat::R32G32B32A32_Float].Support |= supportsBuffer | supportsTexture | FormatSupport::RenderTarget;
+ FeaturesPerFormat[(int32)PixelFormat::R32G32B32A32_Float].Support & ~FormatSupport::ShaderSample;
+ FeaturesPerFormat[(int32)PixelFormat::R32G32B32A32_UInt].Support |= supportsBuffer | supportsTexture | FormatSupport::RenderTarget;
+ FeaturesPerFormat[(int32)PixelFormat::R32G32B32A32_SInt].Support |= supportsBuffer | supportsTexture | FormatSupport::RenderTarget;
+ FeaturesPerFormat[(int32)PixelFormat::D16_UNorm].Support |= supportsBuffer | supportsTexture | supportsDepth;
+ FeaturesPerFormat[(int32)PixelFormat::D24_UNorm_S8_UInt].Support |= supportsBuffer | supportsTexture | supportsDepth;
+ FeaturesPerFormat[(int32)PixelFormat::D32_Float].Support |= supportsBuffer | supportsTexture | supportsDepth;
+ if (features.Contains(WGPUFeatureName_CoreFeaturesAndLimits))
+ {
+ FeaturesPerFormat[(int32)PixelFormat::B8G8R8A8_UNorm_sRGB].Support |= supportsBuffer | supportsTexture | supportsRender | supportsMSAA;
+ FeaturesPerFormat[(int32)PixelFormat::R8_UInt].Support |= supportsMultisampling;
+ FeaturesPerFormat[(int32)PixelFormat::R8_SInt].Support |= supportsMultisampling;
+ FeaturesPerFormat[(int32)PixelFormat::R8G8_UInt].Support |= supportsMultisampling;
+ FeaturesPerFormat[(int32)PixelFormat::R8G8_SInt].Support |= supportsMultisampling;
+ FeaturesPerFormat[(int32)PixelFormat::R8G8B8A8_UInt].Support |= supportsMultisampling;
+ FeaturesPerFormat[(int32)PixelFormat::R8G8B8A8_SInt].Support |= supportsMultisampling;
+ FeaturesPerFormat[(int32)PixelFormat::R16_UInt].Support |= supportsMultisampling;
+ FeaturesPerFormat[(int32)PixelFormat::R16_SInt].Support |= supportsMultisampling;
+ FeaturesPerFormat[(int32)PixelFormat::R16G16_UInt].Support |= supportsMultisampling;
+ FeaturesPerFormat[(int32)PixelFormat::R16G16_SInt].Support |= supportsMultisampling;
+ FeaturesPerFormat[(int32)PixelFormat::R16G16B16A16_UInt].Support |= supportsMultisampling;
+ FeaturesPerFormat[(int32)PixelFormat::R16G16B16A16_SInt].Support |= supportsMultisampling;
+ FeaturesPerFormat[(int32)PixelFormat::R16G16B16A16_Float].Support |= supportsMSAA;
+ FeaturesPerFormat[(int32)PixelFormat::R32_Float].Support |= supportsMultisampling;
+ FeaturesPerFormat[(int32)PixelFormat::R10G10B10A2_UInt].Support |= supportsMultisampling;
+ }
+ if (features.Contains(WGPUFeatureName_TextureFormatsTier1))
+ {
+ FeaturesPerFormat[(int32)PixelFormat::R8_SNorm].Support |= supportsBuffer | supportsTexture;
+ FeaturesPerFormat[(int32)PixelFormat::R16_UNorm].Support |= supportsBuffer | supportsTexture | supportsRender | supportsMSAA;
+ FeaturesPerFormat[(int32)PixelFormat::R16_SNorm].Support |= supportsBuffer | supportsTexture;
+ FeaturesPerFormat[(int32)PixelFormat::R16G16_UNorm].Support |= supportsBuffer | supportsTexture | supportsRender | supportsMultisampling;
+ FeaturesPerFormat[(int32)PixelFormat::R16G16_SNorm].Support |= supportsBuffer | supportsTexture | supportsRender | supportsMultisampling;
+ FeaturesPerFormat[(int32)PixelFormat::R16G16B16A16_UNorm].Support |= supportsBuffer | supportsTexture | supportsRender | supportsMultisampling;
+ FeaturesPerFormat[(int32)PixelFormat::R16G16B16A16_SNorm].Support |= supportsBuffer | supportsTexture | supportsRender | supportsMultisampling;
+ }
+ if (features.Contains(WGPUFeatureName_TextureFormatsTier2))
+ {
+ }
+ if (features.Contains(WGPUFeatureName_Depth32FloatStencil8))
+ {
+ FeaturesPerFormat[(int32)PixelFormat::D32_Float_S8X24_UInt].Support |= supportsBuffer | supportsTexture | supportsDepth;
+ }
+ if (features.Contains(WGPUFeatureName_Float32Blendable))
+ {
+ FeaturesPerFormat[(int32)PixelFormat::R32_Float].Support |= FormatSupport::Blendable;
+ FeaturesPerFormat[(int32)PixelFormat::R32G32_Float].Support |= FormatSupport::Blendable;
+ FeaturesPerFormat[(int32)PixelFormat::R32G32B32A32_Float].Support |= FormatSupport::Blendable;
+ }
+ if (features.Contains(WGPUFeatureName_Float32Filterable))
+ {
+ FeaturesPerFormat[(int32)PixelFormat::R32_Float].Support |= FormatSupport::ShaderSample;
+ FeaturesPerFormat[(int32)PixelFormat::R32G32_Float].Support |= FormatSupport::ShaderSample;
+ FeaturesPerFormat[(int32)PixelFormat::R32G32B32A32_Float].Support |= FormatSupport::ShaderSample;
+ }
+ if (features.Contains(WGPUFeatureName_RG11B10UfloatRenderable))
+ {
+ FeaturesPerFormat[(int32)PixelFormat::R11G11B10_Float].Support |= supportsRender | supportsMSAA;
+ }
+ if (features.Contains(WGPUFeatureName_TextureCompressionBC))
+ {
+ auto supportBc =
+ FormatSupport::Texture2D |
+ FormatSupport::ShaderLoad |
+ FormatSupport::ShaderSample |
+ FormatSupport::Mip;
+ if (features.Contains(WGPUFeatureName_TextureCompressionBCSliced3D))
+ supportBc |= FormatSupport::Texture3D;
+ FeaturesPerFormat[(int32)PixelFormat::BC1_UNorm].Support |= supportBc;
+ FeaturesPerFormat[(int32)PixelFormat::BC1_UNorm_sRGB].Support |= supportBc;
+ FeaturesPerFormat[(int32)PixelFormat::BC2_UNorm].Support |= supportBc;
+ FeaturesPerFormat[(int32)PixelFormat::BC2_UNorm_sRGB].Support |= supportBc;
+ FeaturesPerFormat[(int32)PixelFormat::BC3_UNorm].Support |= supportBc;
+ FeaturesPerFormat[(int32)PixelFormat::BC3_UNorm_sRGB].Support |= supportBc;
+ FeaturesPerFormat[(int32)PixelFormat::BC4_UNorm].Support |= supportBc;
+ FeaturesPerFormat[(int32)PixelFormat::BC4_SNorm].Support |= supportBc;
+ FeaturesPerFormat[(int32)PixelFormat::BC5_UNorm].Support |= supportBc;
+ FeaturesPerFormat[(int32)PixelFormat::BC5_SNorm].Support |= supportBc;
+ FeaturesPerFormat[(int32)PixelFormat::BC6H_Uf16].Support |= supportBc;
+ FeaturesPerFormat[(int32)PixelFormat::BC6H_Sf16].Support |= supportBc;
+ FeaturesPerFormat[(int32)PixelFormat::BC7_UNorm].Support |= supportBc;
+ FeaturesPerFormat[(int32)PixelFormat::BC7_UNorm_sRGB].Support |= supportBc;
+ }
+ if (features.Contains(WGPUFeatureName_TextureCompressionASTC))
+ {
+ auto supportAstc =
+ FormatSupport::Texture2D |
+ FormatSupport::ShaderLoad |
+ FormatSupport::ShaderSample |
+ FormatSupport::Mip;
+ if (features.Contains(WGPUFeatureName_TextureCompressionASTCSliced3D))
+ supportAstc |= FormatSupport::Texture3D;
+ FeaturesPerFormat[(int32)PixelFormat::ASTC_4x4_UNorm].Support |= supportAstc;
+ FeaturesPerFormat[(int32)PixelFormat::ASTC_4x4_UNorm_sRGB].Support |= supportAstc;
+ FeaturesPerFormat[(int32)PixelFormat::ASTC_6x6_UNorm].Support |= supportAstc;
+ FeaturesPerFormat[(int32)PixelFormat::ASTC_6x6_UNorm_sRGB].Support |= supportAstc;
+ FeaturesPerFormat[(int32)PixelFormat::ASTC_8x8_UNorm].Support |= supportAstc;
+ FeaturesPerFormat[(int32)PixelFormat::ASTC_8x8_UNorm_sRGB].Support |= supportAstc;
+ FeaturesPerFormat[(int32)PixelFormat::ASTC_10x10_UNorm].Support |= supportAstc;
+ FeaturesPerFormat[(int32)PixelFormat::ASTC_10x10_UNorm_sRGB].Support |= supportAstc;
+ }
+ for (auto& e : FeaturesPerFormat)
+ {
+ if (EnumHasAllFlags(e.Support, FormatSupport::MultisampleRenderTarget))
+ e.MSAALevelMax = MSAALevel::X4;
+ }
+#undef ADD_FORMAT
+
+#if BUILD_DEBUG
+ LOG(Info, "WebGPU limits:");
+ LOG(Info, " > maxTextureDimension1D = {}", limits.maxTextureDimension1D);
+ LOG(Info, " > maxTextureDimension2D = {}", limits.maxTextureDimension2D);
+ LOG(Info, " > maxTextureDimension3D = {}", limits.maxTextureDimension3D);
+ LOG(Info, " > maxTextureArrayLayers = {}", limits.maxTextureArrayLayers);
+ LOG(Info, " > maxBindGroups = {}", limits.maxBindGroups);
+ LOG(Info, " > maxBindGroupsPlusVertexBuffers = {}", limits.maxBindGroupsPlusVertexBuffers);
+ LOG(Info, " > maxBindingsPerBindGroup = {}", limits.maxBindingsPerBindGroup);
+ LOG(Info, " > maxDynamicUniformBuffersPerPipelineLayout = {}", limits.maxDynamicUniformBuffersPerPipelineLayout);
+ LOG(Info, " > maxDynamicStorageBuffersPerPipelineLayout = {}", limits.maxDynamicStorageBuffersPerPipelineLayout);
+ LOG(Info, " > maxSampledTexturesPerShaderStage = {}", limits.maxSampledTexturesPerShaderStage);
+ LOG(Info, " > maxSamplersPerShaderStage = {}", limits.maxSamplersPerShaderStage);
+ LOG(Info, " > maxStorageBuffersPerShaderStage = {}", limits.maxStorageBuffersPerShaderStage);
+ LOG(Info, " > maxStorageTexturesPerShaderStage = {}", limits.maxStorageTexturesPerShaderStage);
+ LOG(Info, " > maxUniformBuffersPerShaderStage = {}", limits.maxUniformBuffersPerShaderStage);
+ LOG(Info, " > maxUniformBufferBindingSize = {}", limits.maxUniformBufferBindingSize);
+ LOG(Info, " > maxStorageBufferBindingSize = {}", limits.maxStorageBufferBindingSize);
+ LOG(Info, " > minUniformBufferOffsetAlignment = {}", limits.minUniformBufferOffsetAlignment);
+ LOG(Info, " > minStorageBufferOffsetAlignment = {}", limits.minStorageBufferOffsetAlignment);
+ LOG(Info, " > maxVertexBuffers = {}", limits.maxVertexBuffers);
+ LOG(Info, " > maxBufferSize = {}", limits.maxBufferSize);
+ LOG(Info, " > maxVertexAttributes = {}", limits.maxVertexAttributes);
+ LOG(Info, " > maxVertexBufferArrayStride = {}", limits.maxVertexBufferArrayStride);
+ LOG(Info, " > maxInterStageShaderVariables = {}", limits.maxInterStageShaderVariables);
+ LOG(Info, " > maxColorAttachments = {}", limits.maxColorAttachments);
+ LOG(Info, " > maxComputeWorkgroupStorageSize = {}", limits.maxComputeWorkgroupStorageSize);
+ LOG(Info, " > maxComputeInvocationsPerWorkgroup = {}", limits.maxComputeInvocationsPerWorkgroup);
+ LOG(Info, " > maxComputeWorkgroupSize = {}x{}x{}", limits.maxComputeWorkgroupSizeX, limits.maxComputeWorkgroupSizeY, limits.maxComputeWorkgroupSizeZ);
+ LOG(Info, " > maxComputeWorkgroupsPerDimension = {}", limits.maxComputeWorkgroupsPerDimension);
+ LOG(Info, " > maxImmediateSize = {}", limits.maxImmediateSize);
+#endif
+ }
+
+ // Inti device options
+ WGPUDeviceDescriptor deviceDesc = WGPU_DEVICE_DESCRIPTOR_INIT;
+ deviceDesc.requiredLimits = &limits;
+ deviceDesc.requiredFeatureCount = features.Count();
+ deviceDesc.requiredFeatures = features.Get();
+ deviceDesc.deviceLostCallbackInfo.mode = WGPUCallbackMode_AllowSpontaneous;
+ deviceDesc.deviceLostCallbackInfo.callback = [](WGPUDevice const* device, WGPUDeviceLostReason reason, WGPUStringView message, WGPU_NULLABLE void* userdata1, WGPU_NULLABLE void* userdata2)
+ {
+ if (reason == WGPUDeviceLostReason_Destroyed ||
+ reason == WGPUDeviceLostReason_FailedCreation ||
+ reason == WGPUDeviceLostReason_CallbackCancelled)
+ return;
+ Platform::Fatal(String::Format(TEXT("WebGPU device lost! {}, 0x:{:x}"), WEBGPU_TO_STR(message), (uint32)reason), nullptr, FatalErrorType::GPUHang);
+ };
+ deviceDesc.uncapturedErrorCallbackInfo.callback = [](WGPUDevice const* device, WGPUErrorType type, WGPUStringView message, WGPU_NULLABLE void* userdata1, WGPU_NULLABLE void* userdata2)
+ {
+ if (type == WGPUErrorType_OutOfMemory)
+ {
+ Platform::Fatal(String::Format(TEXT("WebGPU device run out of memory! {}"), WEBGPU_TO_STR(message)), nullptr, FatalErrorType::GPUOutOfMemory);
+ }
+#if !BUILD_RELEASE
+ else if (type == WGPUErrorType_Validation)
+ {
+ LOG(Warning, "WebGPU Validation: {}", WEBGPU_TO_STR(message));
+ }
+ else
+ {
+ LOG(Info, "WebGPU: {}", WEBGPU_TO_STR(message));
+ }
+#endif
+ };
+
+ // Create device
+ struct DeviceUserData : AsyncCallbackDataWebGPU
+ {
+ WGPUDevice Device = nullptr;
+ };
+ AsyncCallbackWebGPU deviceRequest(WGPU_REQUEST_DEVICE_CALLBACK_INFO_INIT);
+#if GPU_ENABLE_RESOURCE_NAMING
+ deviceDesc.label = WEBGPU_STR("Flax Device");
+ deviceDesc.defaultQueue.label = WEBGPU_STR("Flax Queue");
+#endif
+ deviceRequest.Info.callback = [](WGPURequestDeviceStatus status, WGPUDevice device, WGPUStringView message, WGPU_NULLABLE void* userdata1, WGPU_NULLABLE void* userdata2)
+ {
+ DeviceUserData& userData = *reinterpret_cast(userdata1);
+ userData.Device = device;
+ userData.Call(status == WGPURequestDeviceStatus_Success, status, message);
+ };
+ wgpuAdapterRequestDevice(Adapter->Adapter, &deviceDesc, deviceRequest.Info);
+ auto deviceRequestResult = deviceRequest.Wait();
+ if (deviceRequestResult == WGPUWaitStatus_TimedOut)
+ {
+ LOG(Fatal, "WebGPU device request has timed out after {}s", deviceRequest.Data.WaitTime);
+ return true;
+ }
+ if (deviceRequestResult == WGPUWaitStatus_Error)
+ {
+ LOG(Fatal, "Failed to get WebGPU device (browser might not support it). Error: {}, 0x{:x}", deviceRequest.Data.Message, deviceRequest.Data.Status);
+ return true;
+ }
+ Device = deviceRequest.Data.Device;
+
+ TotalGraphicsMemory = 1024 * 1024 * 1024; // Dummy 1GB
+
+ // Create default samplers
+ auto samplerDesc = GPUSamplerDescription::New();
+#define INIT_SAMPLER(slot, filter, addressMode, compare) \
+ DefaultSamplers[slot] = New(this); \
+ samplerDesc.Filter = filter; \
+ samplerDesc.AddressU = samplerDesc.AddressV = samplerDesc.AddressW = addressMode; \
+ samplerDesc.ComparisonFunction = compare; \
+ DefaultSamplers[slot]->Init(samplerDesc)
+ INIT_SAMPLER(0, GPUSamplerFilter::Trilinear, GPUSamplerAddressMode::Clamp, GPUSamplerCompareFunction::Never);
+ INIT_SAMPLER(1, GPUSamplerFilter::Point, GPUSamplerAddressMode::Clamp, GPUSamplerCompareFunction::Never);
+ INIT_SAMPLER(2, GPUSamplerFilter::Trilinear, GPUSamplerAddressMode::Wrap, GPUSamplerCompareFunction::Never);
+ INIT_SAMPLER(3, GPUSamplerFilter::Point, GPUSamplerAddressMode::Wrap, GPUSamplerCompareFunction::Never);
+ INIT_SAMPLER(4, GPUSamplerFilter::Point, GPUSamplerAddressMode::Clamp, GPUSamplerCompareFunction::Less);
+ INIT_SAMPLER(5, GPUSamplerFilter::Trilinear, GPUSamplerAddressMode::Clamp, GPUSamplerCompareFunction::Less);
+#undef INIT_SAMPLER
+
+ // Setup commands processing
+ Queue = wgpuDeviceGetQueue(Device);
+ _mainContext = New(this);
+
+ _state = DeviceState::Ready;
+ return GPUDevice::Init();
+}
+
+GPUDeviceWebGPU::~GPUDeviceWebGPU()
+{
+ // Ensure to be disposed
+ GPUDeviceWebGPU::Dispose();
+}
+
+GPUDevice* CreateGPUDeviceWebGPU()
+{
+ // Create instance
+ WGPUInstanceDescriptor instanceDesc = WGPU_INSTANCE_DESCRIPTOR_INIT;
+ WGPUInstance instance = wgpuCreateInstance(&instanceDesc);
+ if (!instance)
+ {
+ LOG(Error, "Failed to create instance for WebGPU (browser might not support it).");
+ return nullptr;
+ }
+
+ // Init adapter options
+ WGPURequestAdapterOptions adapterOptions = WGPU_REQUEST_ADAPTER_OPTIONS_INIT;
+#if !PLATFORM_WEB
+ adapterOptions.powerPreference = WGPUPowerPreference_HighPerformance;
+#endif
+
+ // Create adapter
+ struct AdapterUserData : AsyncCallbackDataWebGPU
+ {
+ WGPUAdapter Adapter = nullptr;
+ };
+ AsyncCallbackWebGPU adapterRequest(WGPU_REQUEST_ADAPTER_CALLBACK_INFO_INIT);
+ adapterRequest.Info.callback = [](WGPURequestAdapterStatus status, WGPUAdapter adapter, WGPUStringView message, WGPU_NULLABLE void* userdata1, WGPU_NULLABLE void* userdata2)
+ {
+ AdapterUserData& userData = *reinterpret_cast(userdata1);
+ userData.Adapter = adapter;
+ userData.Call(status == WGPURequestAdapterStatus_Success, status, message);
+ };
+ wgpuInstanceRequestAdapter(instance, &adapterOptions, adapterRequest.Info);
+ auto adapterRequestResult = adapterRequest.Wait();
+ if (adapterRequestResult == WGPUWaitStatus_TimedOut)
+ {
+ LOG(Fatal, "WebGPU adapter request has timed out after {}s", adapterRequest.Data.WaitTime);
+ return nullptr;
+ }
+ if (adapterRequestResult == WGPUWaitStatus_Error)
+ {
+ LOG(Fatal, "Failed to get WebGPU adapter (browser might not support it). Error: {}, 0x{:x}", adapterRequest.Data.Message, adapterRequest.Data.Status);
+ return nullptr;
+ }
+
+ // Create device
+ auto device = New(instance, New(adapterRequest.Data.Adapter));
+ if (device->Init())
+ {
+ LOG(Warning, "Graphics Device init failed");
+ Delete(device);
+ return nullptr;
+ }
+
+ return device;
+}
+
+void GPUDeviceWebGPU::Dispose()
+{
+ GPUDeviceLock lock(this);
+ if (_state == DeviceState::Disposed)
+ return;
+
+ // Begin destruction
+ _state = DeviceState::Disposing;
+ WaitForGPU();
+ preDispose();
+
+ // Clear device resources
+ SAFE_DELETE_GPU_RESOURCES(DefaultSamplers);
+ SAFE_DELETE(_mainContext);
+ SAFE_DELETE(Adapter);
+ if (Queue)
+ {
+ wgpuQueueRelease(Queue);
+ Queue = nullptr;
+ }
+ if (Device)
+ {
+ wgpuDeviceRelease(Device);
+ Device = nullptr;
+ }
+ wgpuInstanceRelease(WebGPUInstance);
+
+ // End destruction
+ GPUDevice::Dispose();
+ _state = DeviceState::Disposed;
+}
+
+void GPUDeviceWebGPU::WaitForGPU()
+{
+}
+
+bool GPUDeviceWebGPU::GetQueryResult(uint64 queryID, uint64& result, bool wait)
+{
+ // TODO: impl queries
+ return false;
+}
+
+GPUTexture* GPUDeviceWebGPU::CreateTexture(const StringView& name)
+{
+ PROFILE_MEM(GraphicsTextures);
+ return New(this, name);
+}
+
+GPUShader* GPUDeviceWebGPU::CreateShader(const StringView& name)
+{
+ PROFILE_MEM(GraphicsShaders);
+ return New(this, name);
+}
+
+GPUPipelineState* GPUDeviceWebGPU::CreatePipelineState()
+{
+ PROFILE_MEM(GraphicsCommands);
+ return New(this);
+}
+
+GPUTimerQuery* GPUDeviceWebGPU::CreateTimerQuery()
+{
+ return nullptr;
+}
+
+GPUBuffer* GPUDeviceWebGPU::CreateBuffer(const StringView& name)
+{
+ PROFILE_MEM(GraphicsBuffers);
+ return New(this, name);
+}
+
+GPUSampler* GPUDeviceWebGPU::CreateSampler()
+{
+ return New(this);
+}
+
+GPUVertexLayout* GPUDeviceWebGPU::CreateVertexLayout(const VertexElements& elements, bool explicitOffsets)
+{
+ return New(this, elements, explicitOffsets);
+}
+
+GPUSwapChain* GPUDeviceWebGPU::CreateSwapChain(Window* window)
+{
+ return New(this, window);
+}
+
+GPUConstantBuffer* GPUDeviceWebGPU::CreateConstantBuffer(uint32 size, const StringView& name)
+{
+ PROFILE_MEM(GraphicsShaders);
+ WGPUBuffer buffer = nullptr;
+ if (size)
+ {
+ WGPUBufferDescriptor desc = WGPU_BUFFER_DESCRIPTOR_INIT;
+#if GPU_ENABLE_RESOURCE_NAMING
+ desc.label = WEBGPU_STR("Uniform");
+#endif
+ desc.size = size;
+ desc.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Uniform;
+ buffer = wgpuDeviceCreateBuffer(Device, &desc);
+ if (buffer == nullptr)
+ {
+ LOG(Error, "Failed to create uniform buffer '{}' of size {} bytes", name, size);
+ return nullptr;
+ }
+ }
+ return New(this, size, buffer, name);
+}
+
+#endif
diff --git a/Source/Engine/GraphicsDevice/WebGPU/GPUDeviceWebGPU.h b/Source/Engine/GraphicsDevice/WebGPU/GPUDeviceWebGPU.h
new file mode 100644
index 000000000..325d0166c
--- /dev/null
+++ b/Source/Engine/GraphicsDevice/WebGPU/GPUDeviceWebGPU.h
@@ -0,0 +1,84 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#if GRAPHICS_API_WEBGPU
+
+#include "Engine/Graphics/GPUDevice.h"
+#include "Engine/Graphics/GPUResource.h"
+#include "IncludeWebGPU.h"
+
+class GPUContextWebGPU;
+class GPUAdapterWebGPU;
+class GPUSamplerWebGPU;
+
+///
+/// Implementation of Graphics Device for Web GPU backend.
+///
+class GPUDeviceWebGPU : public GPUDevice
+{
+private:
+ GPUContextWebGPU* _mainContext = nullptr;
+
+public:
+ GPUDeviceWebGPU(WGPUInstance instance, GPUAdapterWebGPU* adapter);
+ ~GPUDeviceWebGPU();
+
+public:
+ GPUAdapterWebGPU* Adapter = nullptr;
+ WGPUInstance WebGPUInstance;
+ WGPUDevice Device = nullptr;
+ WGPUQueue Queue = nullptr;
+ GPUSamplerWebGPU* DefaultSamplers[6] = {};
+
+public:
+ // [GPUDeviceDX]
+ GPUContext* GetMainContext() override
+ {
+ return (GPUContext*)_mainContext;
+ }
+ GPUAdapter* GetAdapter() const override
+ {
+ return (GPUAdapter*)Adapter;
+ }
+ void* GetNativePtr() const override
+ {
+ return Device;
+ }
+ bool Init() override;
+ void Dispose() override;
+ void WaitForGPU() override;
+ bool GetQueryResult(uint64 queryID, uint64& result, bool wait = false) override;
+ GPUTexture* CreateTexture(const StringView& name) override;
+ GPUShader* CreateShader(const StringView& name) override;
+ GPUPipelineState* CreatePipelineState() override;
+ GPUTimerQuery* CreateTimerQuery() override;
+ GPUBuffer* CreateBuffer(const StringView& name) override;
+ GPUSampler* CreateSampler() override;
+ GPUVertexLayout* CreateVertexLayout(const VertexElements& elements, bool explicitOffsets) override;
+ GPUSwapChain* CreateSwapChain(Window* window) override;
+ GPUConstantBuffer* CreateConstantBuffer(uint32 size, const StringView& name) override;
+};
+
+///
+/// GPU resource implementation for Web GPU backend.
+///
+template
+class GPUResourceWebGPU : public GPUResourceBase
+{
+public:
+ GPUResourceWebGPU(GPUDeviceWebGPU* device, const StringView& name) noexcept
+ : GPUResourceBase(device, name)
+ {
+ }
+};
+
+struct GPUResourceViewPtrWebGPU
+{
+ class GPUBufferViewWebGPU* BufferView;
+ class GPUTextureViewWebGPU* TextureView;
+};
+
+extern GPUDevice* CreateGPUDeviceWebGPU();
+
+#endif
diff --git a/Source/Engine/GraphicsDevice/WebGPU/GPUPipelineStateWebGPU.cpp b/Source/Engine/GraphicsDevice/WebGPU/GPUPipelineStateWebGPU.cpp
new file mode 100644
index 000000000..81cda279a
--- /dev/null
+++ b/Source/Engine/GraphicsDevice/WebGPU/GPUPipelineStateWebGPU.cpp
@@ -0,0 +1,276 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#if GRAPHICS_API_WEBGPU
+
+#include "GPUPipelineStateWebGPU.h"
+#include "GPUVertexLayoutWebGPU.h"
+#include "Engine/Core/Log.h"
+#include "Engine/Core/Math/Color32.h"
+#include "Engine/Profiler/ProfilerCPU.h"
+#include "Engine/Profiler/ProfilerMemory.h"
+
+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 : _pipelines)
+ wgpuRenderPipelineRelease(e.Value);
+ _pipelines.Clear();
+}
+
+uint32 GetHash(const GPUPipelineStateWebGPU::Key& key)
+{
+ static_assert(sizeof(GPUPipelineStateWebGPU::Key) == sizeof(uint64) * 4, "Invalid PSO key size.");
+ uint32 hash = GetHash(key.Packed[0]);
+ CombineHash(hash, GetHash(key.Packed[1]));
+ return hash;
+}
+
+WGPURenderPipeline GPUPipelineStateWebGPU::GetPipeline(const Key& key)
+{
+ 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
+
+ // Build final pipeline description
+ _depthStencilDesc.format = (WGPUTextureFormat)key.DepthStencilFormat;
+ PipelineDesc.multisample.count = key.MultiSampleCount;
+ _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];
+ for (int32 i = 0; i < PipelineDesc.vertex.bufferCount; i++)
+ buffers[i] = *key.VertexBuffers[i];
+ PipelineDesc.vertex.buffers = buffers;
+
+ // 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;
+}
+
+bool GPUPipelineStateWebGPU::IsValid() const
+{
+ return _memoryUsage != 0;
+}
+
+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
+ 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;
+ PipelineDesc.fragment = &_fragmentDesc;
+ _fragmentDesc = WGPU_FRAGMENT_STATE_INIT;
+ _fragmentDesc.targets = _colorTargets;
+ _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);
+ 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;
+ }
+ for (auto& e : _colorTargets)
+ {
+ e = WGPU_COLOR_TARGET_STATE_INIT;
+ e.blend = &_blendState;
+ e.writeMask = writeMask;
+ }
+
+ // 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);
+}
+
+#endif
diff --git a/Source/Engine/GraphicsDevice/WebGPU/GPUPipelineStateWebGPU.h b/Source/Engine/GraphicsDevice/WebGPU/GPUPipelineStateWebGPU.h
new file mode 100644
index 000000000..6ed046e47
--- /dev/null
+++ b/Source/Engine/GraphicsDevice/WebGPU/GPUPipelineStateWebGPU.h
@@ -0,0 +1,77 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#include "Engine/Graphics/GPUPipelineState.h"
+#include "GPUShaderProgramWebGPU.h"
+#include "GPUDeviceWebGPU.h"
+
+#if GRAPHICS_API_WEBGPU
+
+///
+/// Graphics pipeline state object for Web GPU backend.
+///
+class GPUPipelineStateWebGPU : public GPUResourceWebGPU
+{
+public:
+ // Batches render context state for the pipeline state. Used as a key for caching created pipelines.
+ struct Key
+ {
+ union
+ {
+ struct
+ {
+ uint16 DepthStencilFormat : 7;
+ uint16 MultiSampleCount : 3;
+ uint16 RenderTargetCount : 3;
+ uint16 VertexBufferCount : 3;
+ uint8 RenderTargetFormats[GPU_MAX_RT_BINDED];
+ struct WGPUVertexBufferLayout* VertexBuffers[GPU_MAX_VB_BINDED];
+ };
+ uint64 Packed[4];
+ };
+
+ FORCE_INLINE bool operator==(const Key& other) const
+ {
+ return Platform::MemoryCompare(&Packed, &other.Packed, sizeof(Packed)) == 0;
+ }
+ };
+
+private:
+#if GPU_ENABLE_RESOURCE_NAMING
+ DebugName _debugName;
+#endif
+ WGPUDepthStencilState _depthStencilDesc;
+ WGPUFragmentState _fragmentDesc;
+ WGPUBlendState _blendState;
+ WGPUColorTargetState _colorTargets[GPU_MAX_RT_BINDED];
+ WGPUVertexBufferLayout _vertexBuffers[GPU_MAX_VB_BINDED];
+ Dictionary _pipelines;
+
+public:
+ GPUShaderProgramVSWebGPU* VS = nullptr;
+ GPUShaderProgramPSWebGPU* PS = nullptr;
+ WGPURenderPipelineDescriptor PipelineDesc;
+
+public:
+ GPUPipelineStateWebGPU(GPUDeviceWebGPU* device)
+ : GPUResourceWebGPU(device, StringView::Empty)
+ {
+ }
+
+public:
+ WGPURenderPipeline GetPipeline(const Key& key);
+
+public:
+ // [GPUPipelineState]
+ bool IsValid() const override;
+ bool Init(const Description& desc) override;
+
+protected:
+ // [GPUResourceWebGPU]
+ void OnReleaseGPU() final override;
+};
+
+uint32 GetHash(const GPUPipelineStateWebGPU::Key& key);
+
+#endif
diff --git a/Source/Engine/GraphicsDevice/WebGPU/GPUSamplerWebGPU.cpp b/Source/Engine/GraphicsDevice/WebGPU/GPUSamplerWebGPU.cpp
new file mode 100644
index 000000000..fbffd9a30
--- /dev/null
+++ b/Source/Engine/GraphicsDevice/WebGPU/GPUSamplerWebGPU.cpp
@@ -0,0 +1,84 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#if GRAPHICS_API_WEBGPU
+
+#include "GPUSamplerWebGPU.h"
+
+WGPUAddressMode ToAddressMode(GPUSamplerAddressMode value)
+{
+ switch (value)
+ {
+ case GPUSamplerAddressMode::Wrap:
+ return WGPUAddressMode_Repeat;
+ case GPUSamplerAddressMode::Clamp:
+ return WGPUAddressMode_ClampToEdge;
+ case GPUSamplerAddressMode::Mirror:
+ return WGPUAddressMode_MirrorRepeat;
+ default:
+ return WGPUAddressMode_Undefined;
+ }
+}
+
+WGPUCompareFunction ToCompareFunction(GPUSamplerCompareFunction value)
+{
+ switch (value)
+ {
+ case GPUSamplerCompareFunction::Never:
+ return WGPUCompareFunction_Never;
+ case GPUSamplerCompareFunction::Less:
+ return WGPUCompareFunction_Less;
+ default:
+ return WGPUCompareFunction_Undefined;
+ }
+}
+
+bool GPUSamplerWebGPU::OnInit()
+{
+ WGPUSamplerDescriptor samplerDesc = WGPU_SAMPLER_DESCRIPTOR_INIT;
+ samplerDesc.addressModeU = ToAddressMode(_desc.AddressU);
+ samplerDesc.addressModeV = ToAddressMode(_desc.AddressV);
+ samplerDesc.addressModeW = ToAddressMode(_desc.AddressW);
+ switch (_desc.Filter)
+ {
+ case GPUSamplerFilter::Point:
+ samplerDesc.magFilter = samplerDesc.magFilter = WGPUFilterMode_Nearest;
+ samplerDesc.mipmapFilter = WGPUMipmapFilterMode_Nearest;
+ break;
+ case GPUSamplerFilter::Bilinear:
+ samplerDesc.magFilter = samplerDesc.magFilter = WGPUFilterMode_Linear;
+ samplerDesc.mipmapFilter = WGPUMipmapFilterMode_Nearest;
+ break;
+ case GPUSamplerFilter::Trilinear:
+ samplerDesc.magFilter = samplerDesc.magFilter = WGPUFilterMode_Linear;
+ samplerDesc.mipmapFilter = WGPUMipmapFilterMode_Linear;
+ break;
+ case GPUSamplerFilter::Anisotropic:
+ samplerDesc.magFilter = samplerDesc.magFilter = WGPUFilterMode_Linear;
+ samplerDesc.mipmapFilter = WGPUMipmapFilterMode_Linear;
+ break;
+ }
+ samplerDesc.lodMinClamp = _desc.MinMipLevel;
+ samplerDesc.lodMaxClamp = _desc.MaxMipLevel;
+ samplerDesc.compare = ToCompareFunction(_desc.ComparisonFunction);
+ samplerDesc.maxAnisotropy = _desc.MaxAnisotropy;
+ Sampler = wgpuDeviceCreateSampler(_device->Device, &samplerDesc);
+ if (Sampler == nullptr)
+ return true;
+ _memoryUsage = 100;
+
+ return false;
+}
+
+void GPUSamplerWebGPU::OnReleaseGPU()
+{
+ if (Sampler)
+ {
+ wgpuSamplerRelease(Sampler);
+ Sampler = nullptr;
+ }
+
+ // Base
+ GPUSampler::OnReleaseGPU();
+}
+
+#endif
diff --git a/Source/Engine/GraphicsDevice/WebGPU/GPUSamplerWebGPU.h b/Source/Engine/GraphicsDevice/WebGPU/GPUSamplerWebGPU.h
new file mode 100644
index 000000000..ac341debd
--- /dev/null
+++ b/Source/Engine/GraphicsDevice/WebGPU/GPUSamplerWebGPU.h
@@ -0,0 +1,30 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#include "Engine/Graphics/Textures/GPUSampler.h"
+#include "GPUDeviceWebGPU.h"
+
+#if GRAPHICS_API_WEBGPU
+
+///
+/// Sampler object for Web GPU backend.
+///
+class GPUSamplerWebGPU : public GPUResourceBase
+{
+public:
+ GPUSamplerWebGPU(GPUDeviceWebGPU* device)
+ : GPUResourceBase(device, StringView::Empty)
+ {
+ }
+
+public:
+ WGPUSampler Sampler = nullptr;
+
+protected:
+ // [GPUSampler]
+ bool OnInit() override;
+ void OnReleaseGPU() override;
+};
+
+#endif
diff --git a/Source/Engine/GraphicsDevice/WebGPU/GPUShaderProgramWebGPU.h b/Source/Engine/GraphicsDevice/WebGPU/GPUShaderProgramWebGPU.h
new file mode 100644
index 000000000..3630ea580
--- /dev/null
+++ b/Source/Engine/GraphicsDevice/WebGPU/GPUShaderProgramWebGPU.h
@@ -0,0 +1,65 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#include "Engine/Graphics/Shaders/GPUShaderProgram.h"
+#include "Engine/Core/Types/DataContainer.h"
+#include "Engine/Core/Collections/Dictionary.h"
+
+#if GRAPHICS_API_WEBGPU
+
+///
+/// Shaders base class for Web GPU backend.
+///
+template
+class GPUShaderProgramWebGPU : public BaseType
+{
+public:
+ GPUShaderProgramWebGPU(const GPUShaderProgramInitializer& initializer)
+ {
+ BaseType::Init(initializer);
+ }
+
+ ~GPUShaderProgramWebGPU()
+ {
+ }
+
+public:
+ // [BaseType]
+ uint32 GetBufferSize() const override
+ {
+ return 0;
+ }
+ void* GetBufferHandle() const override
+ {
+ return nullptr;
+ }
+};
+
+///
+/// Vertex Shader for Web GPU backend.
+///
+class GPUShaderProgramVSWebGPU : public GPUShaderProgramWebGPU
+{
+public:
+ GPUShaderProgramVSWebGPU(const GPUShaderProgramInitializer& initializer, GPUVertexLayout* inputLayout, GPUVertexLayout* vertexLayout, Span bytecode)
+ : GPUShaderProgramWebGPU(initializer)
+ {
+ InputLayout = inputLayout;
+ Layout = vertexLayout;
+ }
+};
+
+///
+/// Pixel Shader for Web GPU backend.
+///
+class GPUShaderProgramPSWebGPU : public GPUShaderProgramWebGPU
+{
+public:
+ GPUShaderProgramPSWebGPU(const GPUShaderProgramInitializer& initializer)
+ : GPUShaderProgramWebGPU(initializer)
+ {
+ }
+};
+
+#endif
diff --git a/Source/Engine/GraphicsDevice/WebGPU/GPUShaderWebGPU.cpp b/Source/Engine/GraphicsDevice/WebGPU/GPUShaderWebGPU.cpp
new file mode 100644
index 000000000..11ebfcf8e
--- /dev/null
+++ b/Source/Engine/GraphicsDevice/WebGPU/GPUShaderWebGPU.cpp
@@ -0,0 +1,62 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#if GRAPHICS_API_WEBGPU
+
+#include "GPUShaderWebGPU.h"
+#include "GPUShaderProgramWebGPU.h"
+#include "GPUVertexLayoutWebGPU.h"
+#include "Engine/Serialization/MemoryReadStream.h"
+
+GPUConstantBufferWebGPU::GPUConstantBufferWebGPU(GPUDeviceWebGPU* device, uint32 size, WGPUBuffer buffer, const StringView& name) noexcept
+ : GPUResourceWebGPU(device, name)
+{
+ _size = _memoryUsage = size;
+ Buffer = buffer;
+}
+
+GPUConstantBufferWebGPU::~GPUConstantBufferWebGPU()
+{
+ if (Buffer)
+ wgpuBufferRelease(Buffer);
+}
+
+void GPUConstantBufferWebGPU::OnReleaseGPU()
+{
+ if (Buffer)
+ {
+ wgpuBufferRelease(Buffer);
+ Buffer = nullptr;
+ }
+}
+
+GPUShaderProgram* GPUShaderWebGPU::CreateGPUShaderProgram(ShaderStage type, const GPUShaderProgramInitializer& initializer, Span bytecode, MemoryReadStream& stream)
+{
+ GPUShaderProgram* shader = nullptr;
+ switch (type)
+ {
+ case ShaderStage::Vertex:
+ {
+ GPUVertexLayout* inputLayout, *vertexLayout;
+ ReadVertexLayout(stream, inputLayout, vertexLayout);
+ MISSING_CODE("create vertex shader");
+ shader = New(initializer, inputLayout, vertexLayout, bytecode);
+ break;
+ }
+ case ShaderStage::Pixel:
+ {
+ MISSING_CODE("create pixel shader");
+ shader = New(initializer);
+ break;
+ }
+ }
+ return shader;
+}
+
+void GPUShaderWebGPU::OnReleaseGPU()
+{
+ _cbs.Clear();
+
+ GPUShader::OnReleaseGPU();
+}
+
+#endif
diff --git a/Source/Engine/GraphicsDevice/WebGPU/GPUShaderWebGPU.h b/Source/Engine/GraphicsDevice/WebGPU/GPUShaderWebGPU.h
new file mode 100644
index 000000000..c50cba515
--- /dev/null
+++ b/Source/Engine/GraphicsDevice/WebGPU/GPUShaderWebGPU.h
@@ -0,0 +1,48 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#if GRAPHICS_API_WEBGPU
+
+#include "Engine/Graphics/Shaders/GPUShader.h"
+#include "Engine/Graphics/Shaders/GPUConstantBuffer.h"
+#include "GPUDeviceWebGPU.h"
+
+///
+/// Constant Buffer for Web GPU backend.
+///
+class GPUConstantBufferWebGPU : public GPUResourceWebGPU
+{
+public:
+ GPUConstantBufferWebGPU(GPUDeviceWebGPU* device, uint32 size, WGPUBuffer buffer, const StringView& name) noexcept;
+ ~GPUConstantBufferWebGPU();
+
+public:
+ WGPUBuffer Buffer;
+
+public:
+ // [GPUResourceWebGPU]
+ void OnReleaseGPU() final override;
+};
+
+///
+/// Shader for Web GPU backend.
+///
+class GPUShaderWebGPU : public GPUResourceWebGPU
+{
+private:
+ Array> _cbs;
+
+public:
+ GPUShaderWebGPU(GPUDeviceWebGPU* device, const StringView& name)
+ : GPUResourceWebGPU(device, name)
+ {
+ }
+
+protected:
+ // [GPUShader]
+ GPUShaderProgram* CreateGPUShaderProgram(ShaderStage type, const GPUShaderProgramInitializer& initializer, Span bytecode, MemoryReadStream& stream) override;
+ void OnReleaseGPU() override;
+};
+
+#endif
diff --git a/Source/Engine/GraphicsDevice/WebGPU/GPUSwapChainWebGPU.cpp b/Source/Engine/GraphicsDevice/WebGPU/GPUSwapChainWebGPU.cpp
new file mode 100644
index 000000000..b99bdd8e6
--- /dev/null
+++ b/Source/Engine/GraphicsDevice/WebGPU/GPUSwapChainWebGPU.cpp
@@ -0,0 +1,179 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#if GRAPHICS_API_WEBGPU
+
+#include "GPUSwapChainWebGPU.h"
+#include "GPUAdapterWebGPU.h"
+#include "RenderToolsWebGPU.h"
+#include "Engine/Core/Log.h"
+#include "Engine/Core/Types/Span.h"
+#include "Engine/Platform/Window.h"
+#include "Engine/Graphics/RenderTools.h"
+#include "Engine/Profiler/ProfilerCPU.h"
+#include "Engine/Profiler/ProfilerMemory.h"
+#include "Engine/Scripting/Enums.h"
+
+GPUSwapChainWebGPU::GPUSwapChainWebGPU(GPUDeviceWebGPU* device, Window* window)
+ : GPUResourceWebGPU(device, StringView::Empty)
+{
+ _window = window;
+}
+
+void GPUSwapChainWebGPU::OnReleaseGPU()
+{
+ // Release data
+ PROFILE_MEM_DEC(Graphics, _memoryUsage);
+ auto surfaceTexture = _surfaceView.Texture;
+ _surfaceView.Release();
+ if (surfaceTexture)
+ {
+ wgpuTextureRelease(surfaceTexture);
+ }
+ if (Surface)
+ {
+ wgpuSurfaceRelease(Surface);
+ Surface = nullptr;
+ }
+ _width = _height = 0;
+ _memoryUsage = 0;
+}
+
+bool GPUSwapChainWebGPU::IsFullscreen()
+{
+ return _window->IsFullscreen();
+}
+
+void GPUSwapChainWebGPU::SetFullscreen(bool isFullscreen)
+{
+ _window->SetIsFullscreen(true);
+}
+
+GPUTextureView* GPUSwapChainWebGPU::GetBackBufferView()
+{
+ if (!_surfaceView.Texture)
+ {
+ // Get current texture for the surface
+ WGPUSurfaceTexture surfaceTexture = WGPU_SURFACE_TEXTURE_INIT;
+ wgpuSurfaceGetCurrentTexture(Surface, &surfaceTexture);
+ bool hasSurfaceTexture = surfaceTexture.status == WGPUSurfaceGetCurrentTextureStatus_SuccessOptimal || surfaceTexture.status == WGPUSurfaceGetCurrentTextureStatus_SuccessSuboptimal;
+ ASSERT(hasSurfaceTexture);
+ _surfaceView.Texture = surfaceTexture.texture;
+
+ // Create view
+ WGPUTextureViewDescriptor viewDesc = WGPU_TEXTURE_VIEW_DESCRIPTOR_INIT;
+#if GPU_ENABLE_RESOURCE_NAMING
+ viewDesc.label = WEBGPU_STR("Flax Surface");
+#endif
+ viewDesc.format = wgpuTextureGetFormat(surfaceTexture.texture);
+ viewDesc.dimension = WGPUTextureViewDimension_2D;
+ viewDesc.mipLevelCount = 1;
+ viewDesc.arrayLayerCount = 1;
+ viewDesc.aspect = WGPUTextureAspect_All;
+ viewDesc.usage = wgpuTextureGetUsage(surfaceTexture.texture);
+ _surfaceView.Create(surfaceTexture.texture, &viewDesc);
+ }
+ return &_surfaceView;
+}
+
+void GPUSwapChainWebGPU::Present(bool vsync)
+{
+ PROFILE_CPU();
+ ZoneColor(TracyWaitZoneColor);
+
+#if !PLATFORM_WEB
+ // Present frame
+ wgpuSurfacePresent(Surface);
+#endif
+
+ // Release the texture
+ auto surfaceTexture = _surfaceView.Texture;
+ ASSERT(surfaceTexture);
+ _surfaceView.Release();
+ wgpuTextureRelease(surfaceTexture);
+
+ // Base
+ GPUSwapChain::Present(vsync);
+}
+
+bool GPUSwapChainWebGPU::Resize(int32 width, int32 height)
+{
+ if (width == _width && height == _height)
+ return false;
+ _device->WaitForGPU();
+ GPUDeviceLock lock(_device);
+#if GPU_ENABLE_DIAGNOSTICS
+ LOG(Info, "Resizing WebGPU surface to: {}x{}", width, height);
+#endif
+
+ // Ensure to have a surface
+ if (!Surface)
+ {
+ WGPUEmscriptenSurfaceSourceCanvasHTMLSelector canvasDesc = WGPU_EMSCRIPTEN_SURFACE_SOURCE_CANVAS_HTML_SELECTOR_INIT;
+ canvasDesc.selector = WEBGPU_STR(WEB_CANVAS_ID);
+ WGPUSurfaceDescriptor surfaceDesc = WGPU_SURFACE_DESCRIPTOR_INIT;
+ surfaceDesc.nextInChain = &canvasDesc.chain;
+#if GPU_ENABLE_RESOURCE_NAMING
+ surfaceDesc.label = WEBGPU_STR("Flax Surface");
+#endif
+ Surface = wgpuInstanceCreateSurface(_device->WebGPUInstance, &surfaceDesc);
+ if (!Surface)
+ {
+ LOG(Fatal, "Failed to create WebGPU surface for the HTML selector '{}'", TEXT(WEB_CANVAS_ID));
+ return true;
+ }
+ }
+
+ // Setup surface configuration (based on capabilities)
+ WGPUSurfaceCapabilities capabilities = WGPU_SURFACE_CAPABILITIES_INIT;
+ wgpuSurfaceGetCapabilities(Surface, _device->Adapter->Adapter, &capabilities);
+ auto formats = ToSpan(capabilities.formats, capabilities.formatCount);
+ auto presentModes = ToSpan(capabilities.presentModes, capabilities.presentModeCount);
+ auto alphaModes = ToSpan(capabilities.alphaModes, capabilities.alphaModeCount);
+ WGPUSurfaceConfiguration configuration = WGPU_SURFACE_CONFIGURATION_INIT;
+ configuration.device = _device->Device;
+ configuration.usage = WGPUTextureUsage_RenderAttachment;
+#if GPU_USE_WINDOW_SRV
+ configuration.usage |= WGPUTextureUsage_TextureBinding;
+#endif
+ configuration.width = width;
+ configuration.height = height;
+ configuration.format = WGPUTextureFormat_RGBA8Unorm;
+ configuration.viewFormats = &configuration.format;
+ configuration.viewFormatCount = 1;
+ _format = PixelFormat::R8G8B8A8_UNorm;
+ if (SpanContains(formats, RenderToolsWebGPU::ToTextureFormat(GPU_BACK_BUFFER_PIXEL_FORMAT)))
+ {
+ _format = GPU_BACK_BUFFER_PIXEL_FORMAT;
+ configuration.format = RenderToolsWebGPU::ToTextureFormat(_format);
+ }
+ else if (formats.Length() != 0)
+ {
+ configuration.format = formats[0];
+ _format = RenderToolsWebGPU::ToPixelFormat(configuration.format);
+ }
+ if (_window->GetSettings().SupportsTransparency && SpanContains(alphaModes, WGPUCompositeAlphaMode_Premultiplied))
+ configuration.alphaMode = WGPUCompositeAlphaMode_Premultiplied;
+ else if (SpanContains(alphaModes, WGPUCompositeAlphaMode_Opaque))
+ configuration.alphaMode = WGPUCompositeAlphaMode_Opaque;
+ if (SpanContains(presentModes, WGPUPresentMode_Mailbox))
+ configuration.presentMode = WGPUPresentMode_Mailbox;
+ if (SpanContains(presentModes, WGPUPresentMode_Fifo))
+ configuration.presentMode = WGPUPresentMode_Fifo;
+ else if (presentModes.Length() != 0)
+ configuration.presentMode = presentModes[0];
+ wgpuSurfaceCapabilitiesFreeMembers(capabilities);
+
+ // Configure surface
+ wgpuSurfaceConfigure(Surface, &configuration);
+
+ // Init
+ _surfaceView.Init(this, _format, MSAALevel::None);
+ _width = width;
+ _height = height;
+ _memoryUsage = RenderTools::CalculateTextureMemoryUsage(_format, _width, _height, 1);
+ PROFILE_MEM_INC(Graphics, _memoryUsage);
+
+ return false;
+}
+
+#endif
diff --git a/Source/Engine/GraphicsDevice/WebGPU/GPUSwapChainWebGPU.h b/Source/Engine/GraphicsDevice/WebGPU/GPUSwapChainWebGPU.h
new file mode 100644
index 000000000..b3bc82a9c
--- /dev/null
+++ b/Source/Engine/GraphicsDevice/WebGPU/GPUSwapChainWebGPU.h
@@ -0,0 +1,41 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#include "GPUDeviceWebGPU.h"
+#include "GPUTextureWebGPU.h"
+#include "Engine/Graphics/GPUSwapChain.h"
+
+#if GRAPHICS_API_WEBGPU
+
+///
+/// Graphics Device rendering output for Web GPU backend.
+///
+class GPUSwapChainWebGPU : public GPUResourceWebGPU
+{
+ friend class WindowsWindow;
+ friend class GPUContextWebGPU;
+ friend GPUDeviceWebGPU;
+private:
+ GPUTextureViewWebGPU _surfaceView;
+
+public:
+ GPUSwapChainWebGPU(GPUDeviceWebGPU* device, Window* window);
+
+public:
+ WGPUSurface Surface = nullptr;
+
+public:
+ // [GPUSwapChain]
+ bool IsFullscreen() override;
+ void SetFullscreen(bool isFullscreen) override;
+ GPUTextureView* GetBackBufferView() override;
+ void Present(bool vsync) override;
+ bool Resize(int32 width, int32 height) override;
+
+protected:
+ // [GPUResourceWebGPU]
+ void OnReleaseGPU() final override;
+};
+
+#endif
diff --git a/Source/Engine/GraphicsDevice/WebGPU/GPUTextureWebGPU.cpp b/Source/Engine/GraphicsDevice/WebGPU/GPUTextureWebGPU.cpp
new file mode 100644
index 000000000..695c72d31
--- /dev/null
+++ b/Source/Engine/GraphicsDevice/WebGPU/GPUTextureWebGPU.cpp
@@ -0,0 +1,286 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#if GRAPHICS_API_WEBGPU
+
+#include "GPUTextureWebGPU.h"
+#include "RenderToolsWebGPU.h"
+#include "Engine/Core/Log.h"
+#include "Engine/Graphics/PixelFormatExtensions.h"
+
+void GPUTextureViewWebGPU::Create(WGPUTexture texture, WGPUTextureViewDescriptor const* desc)
+{
+ if (View)
+ wgpuTextureViewRelease(View);
+ Texture = texture;
+ View = wgpuTextureCreateView(texture, desc);
+ if (!View)
+ {
+#if GPU_ENABLE_RESOURCE_NAMING
+ LOG(Error, "Failed to create a view for texture '{}'", GetParent() ? GetParent()->GetName() : StringView::Empty);
+#endif
+ }
+}
+
+void GPUTextureViewWebGPU::Release()
+{
+ if (View)
+ {
+ wgpuTextureViewRelease(View);
+ View = nullptr;
+ }
+ Texture = nullptr;
+}
+
+bool GPUTextureWebGPU::OnInit()
+{
+ // Create texture
+ WGPUTextureDescriptor textureDesc = WGPU_TEXTURE_DESCRIPTOR_INIT;
+#if GPU_ENABLE_RESOURCE_NAMING
+ _name.Set(_namePtr, _nameSize);
+ textureDesc.label = { _name.Get(), (size_t)_name.Length() };
+#endif
+ if (!IsDepthStencil())
+ textureDesc.usage |= WGPUTextureUsage_CopyDst;
+ if (IsStaging())
+ textureDesc.usage |= WGPUTextureUsage_CopySrc | WGPUTextureUsage_CopyDst;
+ if (IsShaderResource())
+ textureDesc.usage |= WGPUTextureUsage_TextureBinding;
+ if (IsUnorderedAccess())
+ textureDesc.usage |= WGPUTextureUsage_StorageBinding;
+ if (IsRenderTarget() || IsDepthStencil())
+ textureDesc.usage |= WGPUTextureUsage_RenderAttachment;
+ textureDesc.size.width = _desc.Width;
+ textureDesc.size.height = _desc.Height;
+ textureDesc.size.depthOrArrayLayers = _desc.Depth;
+ switch (_desc.Dimensions)
+ {
+ case TextureDimensions::Texture:
+ _viewDimension = IsArray() ? WGPUTextureViewDimension_2DArray : WGPUTextureViewDimension_2D;
+ textureDesc.dimension = WGPUTextureDimension_2D;
+ break;
+ case TextureDimensions::VolumeTexture:
+ _viewDimension = WGPUTextureViewDimension_3D;
+ textureDesc.dimension = WGPUTextureDimension_3D;
+ break;
+ case TextureDimensions::CubeTexture:
+ _viewDimension = IsArray() ? WGPUTextureViewDimension_CubeArray : WGPUTextureViewDimension_Cube;
+ textureDesc.dimension = WGPUTextureDimension_2D;
+ textureDesc.size.depthOrArrayLayers *= 6; // Each cubemap uses 6 array slices
+ break;
+ }
+ textureDesc.format = RenderToolsWebGPU::ToTextureFormat(Format());
+ textureDesc.mipLevelCount = _desc.MipLevels;
+ textureDesc.sampleCount = (uint32_t)_desc.MultiSampleLevel;
+ textureDesc.viewFormats = &textureDesc.format;
+ textureDesc.viewFormatCount = 1;
+ _format = textureDesc.format;
+ _usage = textureDesc.usage;
+ Texture = wgpuDeviceCreateTexture(_device->Device, &textureDesc);
+ if (!Texture)
+ return true;
+
+ // Update memory usage
+ _memoryUsage = calculateMemoryUsage();
+
+ // Initialize handles to the resource
+ if (IsRegularTexture())
+ {
+ // 'Regular' texture is using only one handle (texture/cubemap)
+ _handlesPerSlice.Resize(1, false);
+ }
+ else
+ {
+ // Create all handles
+ InitHandles();
+ }
+
+ // Setup metadata for the views
+ bool hasStencil = PixelFormatExtensions::HasStencil(Format());
+ if (hasStencil)
+ {
+ _handleArray.HasStencil = hasStencil;
+ _handleVolume.HasStencil = hasStencil;
+ _handleReadOnlyDepth.HasStencil = hasStencil;
+ _handleStencil.HasStencil = hasStencil;
+ for (auto& e : _handlesPerSlice)
+ e.HasStencil = hasStencil;
+ for (auto& q : _handlesPerMip)
+ for (auto& e : q)
+ e.HasStencil = hasStencil;
+ }
+
+ return false;
+}
+
+void GPUTextureWebGPU::OnResidentMipsChanged()
+{
+ // Update the view to handle base mip level as highest resident mip
+ WGPUTextureViewDescriptor viewDesc = WGPU_TEXTURE_VIEW_DESCRIPTOR_INIT;
+ viewDesc.format = _format;
+ viewDesc.usage = _usage;
+ viewDesc.dimension = _viewDimension;
+ viewDesc.baseMipLevel = MipLevels() - ResidentMipLevels();
+ viewDesc.mipLevelCount = ResidentMipLevels();
+ viewDesc.arrayLayerCount = ArraySize();
+ viewDesc.aspect = WGPUTextureAspect_All;
+ GPUTextureViewWebGPU& view = IsVolume() ? _handleVolume : _handlesPerSlice[0];
+ if (view.GetParent() == nullptr)
+ view.Init(this, _desc.Format, _desc.MultiSampleLevel);
+ view.Create(Texture, &viewDesc);
+}
+
+void GPUTextureWebGPU::OnReleaseGPU()
+{
+ _handlesPerMip.Resize(0, false);
+ _handlesPerSlice.Resize(0, false);
+ _handleArray.Release();
+ _handleVolume.Release();
+ _handleReadOnlyDepth.Release();
+ _handleStencil.Release();
+ if (Texture)
+ {
+ wgpuTextureDestroy(Texture);
+ wgpuTextureRelease(Texture);
+ Texture = nullptr;
+ }
+#if GPU_ENABLE_RESOURCE_NAMING
+ _name.Clear();
+#endif
+
+ // Base
+ GPUTexture::OnReleaseGPU();
+}
+
+void GPUTextureWebGPU::InitHandles()
+{
+ WGPUTextureViewDescriptor viewDesc = WGPU_TEXTURE_VIEW_DESCRIPTOR_INIT;
+#if GPU_ENABLE_RESOURCE_NAMING
+ viewDesc.label = { _name.Get(), (size_t)_name.Length() };
+#endif
+ viewDesc.format = _format;
+ viewDesc.usage = _usage;
+ viewDesc.dimension = _viewDimension;
+ viewDesc.mipLevelCount = MipLevels();
+ viewDesc.arrayLayerCount = ArraySize();
+ viewDesc.aspect = WGPUTextureAspect_All;
+
+ auto format = Format();
+ auto msaa = MultiSampleLevel();
+
+ if (IsVolume())
+ {
+ // Create handle for whole 3d texture
+ {
+ auto& view = _handleVolume;
+ view.Init(this, format, msaa);
+ view.Create(Texture, &viewDesc);
+ }
+
+ // Init per slice views
+ _handlesPerSlice.Resize(Depth(), false);
+ viewDesc.dimension = WGPUTextureViewDimension_2D;
+ if (_desc.HasPerSliceViews() && IsRenderTarget())
+ {
+ for (int32 sliceIndex = 0; sliceIndex < Depth(); sliceIndex++)
+ {
+ auto& view = _handlesPerSlice[sliceIndex];
+ view.Init(this, format, msaa);
+ view.Create(Texture, &viewDesc);
+ view.DepthSlice = sliceIndex;
+ }
+ }
+ viewDesc.dimension = _viewDimension;
+ }
+ else if (IsArray())
+ {
+ // Create whole array handle
+ {
+ auto& view = _handleArray;
+ view.Init(this, format, msaa);
+ view.Create(Texture, &viewDesc);
+ }
+
+ // Create per array slice handles
+ _handlesPerSlice.Resize(ArraySize(), false);
+ viewDesc.dimension = WGPUTextureViewDimension_2D;
+ for (int32 arrayIndex = 0; arrayIndex < _handlesPerSlice.Count(); arrayIndex++)
+ {
+ viewDesc.baseArrayLayer = arrayIndex;
+ viewDesc.arrayLayerCount = 1;
+ auto& view = _handlesPerSlice[arrayIndex];
+ view.Init(this, format, msaa);
+ view.Create(Texture, &viewDesc);
+ }
+ viewDesc.baseArrayLayer = 0;
+ viewDesc.arrayLayerCount = MipLevels();
+ viewDesc.dimension = _viewDimension;
+ }
+ else
+ {
+ // Create single handle for the whole texture
+ _handlesPerSlice.Resize(1, false);
+ auto& view = _handlesPerSlice[0];
+ view.Init(this, format, msaa);
+ view.Create(Texture, &viewDesc);
+ }
+
+ // Init per mip map handles
+ if (HasPerMipViews())
+ {
+ // Init handles
+ _handlesPerMip.Resize(ArraySize(), false);
+ viewDesc.dimension = WGPUTextureViewDimension_2D;
+ for (int32 arrayIndex = 0; arrayIndex < _handlesPerMip.Count(); arrayIndex++)
+ {
+ auto& slice = _handlesPerMip[arrayIndex];
+ slice.Resize(MipLevels(), false);
+ viewDesc.baseArrayLayer = arrayIndex;
+ viewDesc.arrayLayerCount = 1;
+ viewDesc.mipLevelCount = 1;
+ for (int32 mipIndex = 0; mipIndex < slice.Count(); mipIndex++)
+ {
+ auto& view = slice[mipIndex];
+ viewDesc.baseMipLevel = mipIndex;
+ view.Init(this, format, msaa);
+ view.Create(Texture, &viewDesc);
+ }
+ }
+ viewDesc.dimension = _viewDimension;
+ }
+
+ // Read-only depth-stencil
+ if (EnumHasAnyFlags(_desc.Flags, GPUTextureFlags::ReadOnlyDepthView))
+ {
+ auto& view = _handleReadOnlyDepth;
+ view.Init(this, format, msaa);
+ view.Create(Texture, &viewDesc);
+ view.ReadOnly = true;
+ }
+
+ // Stencil view
+ if (IsDepthStencil() && IsShaderResource() && PixelFormatExtensions::HasStencil(format))
+ {
+ PixelFormat stencilFormat = format;
+ switch (format)
+ {
+ case PixelFormat::D24_UNorm_S8_UInt:
+ stencilFormat = PixelFormat::X24_Typeless_G8_UInt;
+ break;
+ case PixelFormat::D32_Float_S8X24_UInt:
+ stencilFormat = PixelFormat::X32_Typeless_G8X24_UInt;
+ break;
+ }
+ viewDesc.aspect = WGPUTextureAspect_StencilOnly;
+ viewDesc.format = WGPUTextureFormat_Stencil8;
+ _handleStencil.Init(this, stencilFormat, msaa);
+ _handleStencil.Create(Texture, &viewDesc);
+ }
+}
+
+bool GPUTextureWebGPU::GetData(int32 arrayIndex, int32 mipMapIndex, TextureMipData& data, uint32 mipRowPitch)
+{
+ // TODO: no implemented
+ return true;
+}
+
+#endif
diff --git a/Source/Engine/GraphicsDevice/WebGPU/GPUTextureWebGPU.h b/Source/Engine/GraphicsDevice/WebGPU/GPUTextureWebGPU.h
new file mode 100644
index 000000000..0492f868d
--- /dev/null
+++ b/Source/Engine/GraphicsDevice/WebGPU/GPUTextureWebGPU.h
@@ -0,0 +1,127 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#include "Engine/Core/Collections/Array.h"
+#include "Engine/Graphics/Textures/GPUTexture.h"
+#include "GPUDeviceWebGPU.h"
+#include "IncludeWebGPU.h"
+
+#if GRAPHICS_API_WEBGPU
+
+///
+/// The texture view for Web GPU backend.
+///
+///
+class GPUTextureViewWebGPU : public GPUTextureView
+{
+public:
+ GPUTextureViewWebGPU()
+ {
+ Ptr = { nullptr, this };
+ }
+
+ ~GPUTextureViewWebGPU()
+ {
+ Release();
+ }
+
+public:
+ // Handle to the WebGPU texture object.
+ WGPUTexture Texture = nullptr;
+ // Handle to the WebGPU texture view object.
+ WGPUTextureView View = nullptr;
+ bool HasStencil = false;
+ bool ReadOnly = false;
+ uint32 DepthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
+ GPUResourceViewPtrWebGPU Ptr;
+
+public:
+ using GPUTextureView::Init;
+ void Create(WGPUTexture texture, WGPUTextureViewDescriptor const* desc = nullptr);
+ void Release();
+
+public:
+ // [GPUResourceView]
+ void* GetNativePtr() const override
+ {
+ return (void*)&Ptr;
+ }
+};
+
+///
+/// Texture object for Web GPU backend.
+///
+class GPUTextureWebGPU : public GPUResourceWebGPU
+{
+private:
+ GPUTextureViewWebGPU _handleArray;
+ GPUTextureViewWebGPU _handleVolume;
+ GPUTextureViewWebGPU _handleReadOnlyDepth;
+ GPUTextureViewWebGPU _handleStencil;
+ Array _handlesPerSlice; // [slice]
+ Array> _handlesPerMip; // [slice][mip]
+#if GPU_ENABLE_RESOURCE_NAMING
+ StringAnsi _name;
+#endif
+ WGPUTextureFormat _format = WGPUTextureFormat_Undefined;
+ WGPUTextureViewDimension _viewDimension = WGPUTextureViewDimension_Undefined;
+ WGPUTextureUsage _usage = 0;
+
+public:
+ GPUTextureWebGPU(GPUDeviceWebGPU* device, const StringView& name)
+ : GPUResourceWebGPU(device, name)
+ {
+ }
+
+public:
+ // Handle to the WebGPU texture object.
+ WGPUTexture Texture = nullptr;
+
+public:
+ // [GPUTexture]
+ GPUTextureView* View(int32 arrayOrDepthIndex) const override
+ {
+ return (GPUTextureView*)&_handlesPerSlice[arrayOrDepthIndex];
+ }
+ GPUTextureView* View(int32 arrayOrDepthIndex, int32 mipMapIndex) const override
+ {
+ return (GPUTextureView*)&_handlesPerMip[arrayOrDepthIndex][mipMapIndex];
+ }
+ GPUTextureView* ViewArray() const override
+ {
+ ASSERT(ArraySize() > 1);
+ return (GPUTextureView*)&_handleArray;
+ }
+ GPUTextureView* ViewVolume() const override
+ {
+ ASSERT(IsVolume());
+ return (GPUTextureView*)&_handleVolume;
+ }
+ GPUTextureView* ViewReadOnlyDepth() const override
+ {
+ ASSERT(_desc.Flags & GPUTextureFlags::ReadOnlyDepthView);
+ return (GPUTextureView*)&_handleReadOnlyDepth;
+ }
+ GPUTextureView* ViewStencil() const override
+ {
+ ASSERT(_desc.Flags & GPUTextureFlags::DepthStencil);
+ return (GPUTextureView*)&_handleStencil;
+ }
+ void* GetNativePtr() const override
+ {
+ return Texture;
+ }
+ bool GetData(int32 arrayIndex, int32 mipMapIndex, TextureMipData& data, uint32 mipRowPitch) override;
+
+protected:
+ // [GPUTexture]
+ bool OnInit() override;
+ void OnResidentMipsChanged() override;
+ void OnReleaseGPU() override;
+
+private:
+ void InitHandles();
+};
+
+#endif
diff --git a/Source/Engine/GraphicsDevice/WebGPU/GPUVertexLayoutWebGPU.h b/Source/Engine/GraphicsDevice/WebGPU/GPUVertexLayoutWebGPU.h
new file mode 100644
index 000000000..a2ca54b2a
--- /dev/null
+++ b/Source/Engine/GraphicsDevice/WebGPU/GPUVertexLayoutWebGPU.h
@@ -0,0 +1,23 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#if GRAPHICS_API_WEBGPU
+
+#include "Engine/Graphics/Shaders/GPUVertexLayout.h"
+#include "GPUDeviceWebGPU.h"
+
+///
+/// Vertex layout object for Web GPU backend.
+///
+class GPUVertexLayoutWebGPU : public GPUResourceBase
+{
+public:
+ GPUVertexLayoutWebGPU(GPUDeviceWebGPU* device, const Elements& elements, bool explicitOffsets);
+
+public:
+ WGPUVertexBufferLayout Layout;
+ WGPUVertexAttribute Attributes[GPU_MAX_VS_ELEMENTS];
+};
+
+#endif
diff --git a/Source/Engine/GraphicsDevice/WebGPU/GraphicsDeviceWebGPU.Build.cs b/Source/Engine/GraphicsDevice/WebGPU/GraphicsDeviceWebGPU.Build.cs
new file mode 100644
index 000000000..76b871ba3
--- /dev/null
+++ b/Source/Engine/GraphicsDevice/WebGPU/GraphicsDeviceWebGPU.Build.cs
@@ -0,0 +1,24 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+using System.IO;
+using Flax.Build.NativeCpp;
+using Flax.Build.Platforms;
+
+///
+/// WebGPU graphics backend module.
+///
+public class GraphicsDeviceWebGPU : GraphicsDeviceBaseModule
+{
+ ///
+ public override void Setup(BuildOptions options)
+ {
+ base.Setup(options);
+
+ var port = "--use-port=emdawnwebgpu:cpp_bindings=false";
+ options.CompileEnv.CustomArgs.Add(port);
+ options.LinkEnv.CustomArgs.Add("-sASYNCIFY");
+ options.OutputFiles.Add(port);
+ options.PublicDefinitions.Add("GRAPHICS_API_WEBGPU");
+ options.PrivateIncludePaths.Add(Path.Combine(EmscriptenSdk.Instance.EmscriptenPath, "emscripten/cache/ports/emdawnwebgpu/emdawnwebgpu_pkg/webgpu/include"));
+ }
+}
diff --git a/Source/Engine/GraphicsDevice/WebGPU/IncludeWebGPU.h b/Source/Engine/GraphicsDevice/WebGPU/IncludeWebGPU.h
new file mode 100644
index 000000000..a7f3a4ec7
--- /dev/null
+++ b/Source/Engine/GraphicsDevice/WebGPU/IncludeWebGPU.h
@@ -0,0 +1,26 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#if GRAPHICS_API_WEBGPU
+
+// Fix Intellisense errors when Emsicpten SDK is not visible
+#ifndef UINT32_C
+#define UINT32_C(x) (x ## U)
+#endif
+#ifndef __SIZE_MAX__
+#define __SIZE_MAX__ (static_cast(-1))
+#endif
+#ifndef SIZE_MAX
+#define SIZE_MAX __SIZE_MAX__
+#endif
+
+#include
+
+// Utiltiy macro to convert WGPUStringView into UTF-16 string (on stack)
+#define WEBGPU_TO_STR(strView) StringAsUTF16<>(strView.data, strView.data ? strView.length : 0).Get()
+
+// Utiltiy macro to get WGPUStringView for a text constant
+#define WEBGPU_STR(str) { str, ARRAY_COUNT(str) - 1 }
+
+#endif
diff --git a/Source/Engine/GraphicsDevice/WebGPU/RenderToolsWebGPU.cpp b/Source/Engine/GraphicsDevice/WebGPU/RenderToolsWebGPU.cpp
new file mode 100644
index 000000000..ca9e86af3
--- /dev/null
+++ b/Source/Engine/GraphicsDevice/WebGPU/RenderToolsWebGPU.cpp
@@ -0,0 +1,241 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#if GRAPHICS_API_WEBGPU
+
+#include "RenderToolsWebGPU.h"
+#include "Engine/Graphics/PixelFormat.h"
+
+WGPUVertexFormat RenderToolsWebGPU::ToVertexFormat(PixelFormat format)
+{
+ switch (format)
+ {
+ // @formatter:off
+ case PixelFormat::R8_UInt: return WGPUVertexFormat_Uint8;
+ case PixelFormat::R8G8_UInt: return WGPUVertexFormat_Uint8x2;
+ case PixelFormat::R8G8B8A8_UInt: return WGPUVertexFormat_Uint8x4;
+ case PixelFormat::R8_SInt: return WGPUVertexFormat_Sint8;
+ case PixelFormat::R8G8_SInt: return WGPUVertexFormat_Sint8x2;
+ case PixelFormat::R8G8B8A8_SInt: return WGPUVertexFormat_Sint8x4;
+ case PixelFormat::R8_UNorm: return WGPUVertexFormat_Unorm8;
+ case PixelFormat::R8G8_UNorm: return WGPUVertexFormat_Unorm8x2;
+ case PixelFormat::R8G8B8A8_UNorm: return WGPUVertexFormat_Unorm8x4;
+ case PixelFormat::R8_SNorm: return WGPUVertexFormat_Snorm8;
+ case PixelFormat::R8G8_SNorm: return WGPUVertexFormat_Snorm8x2;
+ case PixelFormat::R8G8B8A8_SNorm: return WGPUVertexFormat_Snorm8x4;
+ case PixelFormat::R16_UInt: return WGPUVertexFormat_Uint16;
+ case PixelFormat::R16G16_UInt: return WGPUVertexFormat_Uint16x2;
+ case PixelFormat::R16G16B16A16_UInt: return WGPUVertexFormat_Uint16x4;
+ case PixelFormat::R16_SInt: return WGPUVertexFormat_Sint16;
+ case PixelFormat::R16G16_SInt: return WGPUVertexFormat_Sint16x2;
+ case PixelFormat::R16G16B16A16_SInt: return WGPUVertexFormat_Sint16x4;
+ case PixelFormat::R16_UNorm: return WGPUVertexFormat_Unorm16;
+ case PixelFormat::R16G16_UNorm: return WGPUVertexFormat_Unorm16x2;
+ case PixelFormat::R16G16B16A16_UNorm: return WGPUVertexFormat_Unorm16x4;
+ case PixelFormat::R16_SNorm: return WGPUVertexFormat_Snorm16;
+ case PixelFormat::R16G16_SNorm: return WGPUVertexFormat_Snorm16x2;
+ case PixelFormat::R16G16B16A16_SNorm: return WGPUVertexFormat_Snorm16x4;
+ case PixelFormat::R16_Float: return WGPUVertexFormat_Float16;
+ case PixelFormat::R16G16_Float: return WGPUVertexFormat_Float16x2;
+ case PixelFormat::R16G16B16A16_Float: return WGPUVertexFormat_Float16x4;
+ case PixelFormat::R32_Float: return WGPUVertexFormat_Float32;
+ case PixelFormat::R32G32_Float: return WGPUVertexFormat_Float32x2;
+ case PixelFormat::R32G32B32_Float: return WGPUVertexFormat_Float32x3;
+ case PixelFormat::R32G32B32A32_Float: return WGPUVertexFormat_Float32x4;
+ case PixelFormat::R32_UInt: return WGPUVertexFormat_Uint32;
+ case PixelFormat::R32G32_UInt: return WGPUVertexFormat_Uint32x2;
+ case PixelFormat::R32G32B32_UInt: return WGPUVertexFormat_Uint32x3;
+ case PixelFormat::R32G32B32A32_UInt: return WGPUVertexFormat_Uint32x4;
+ case PixelFormat::R32_SInt: return WGPUVertexFormat_Sint32;
+ case PixelFormat::R32G32_SInt: return WGPUVertexFormat_Sint32x2;
+ case PixelFormat::R32G32B32_SInt: return WGPUVertexFormat_Sint32x3;
+ case PixelFormat::R32G32B32A32_SInt: return WGPUVertexFormat_Sint32x4;
+ case PixelFormat::R10G10B10A2_UNorm: return WGPUVertexFormat_Unorm10_10_10_2;
+ case PixelFormat::B8G8R8A8_UNorm: return WGPUVertexFormat_Unorm8x4BGRA;
+ default: return WGPUVertexFormat_Force32;
+ // @formatter:on
+ }
+}
+
+WGPUTextureFormat RenderToolsWebGPU::ToTextureFormat(PixelFormat format)
+{
+ switch (format)
+ {
+ // @formatter:off
+ case PixelFormat::R32G32B32A32_Typeless:
+ case PixelFormat::R32G32B32A32_Float: return WGPUTextureFormat_RGBA32Float;
+ case PixelFormat::R32G32B32A32_UInt: return WGPUTextureFormat_RGBA32Uint;
+ case PixelFormat::R32G32B32A32_SInt: return WGPUTextureFormat_RGBA32Sint;
+ case PixelFormat::R16G16B16A16_Typeless:
+ case PixelFormat::R16G16B16A16_Float: return WGPUTextureFormat_RGBA16Float;
+ case PixelFormat::R16G16B16A16_UNorm: return WGPUTextureFormat_RGBA16Unorm;
+ case PixelFormat::R16G16B16A16_UInt: return WGPUTextureFormat_RGBA16Uint;
+ case PixelFormat::R16G16B16A16_SNorm: return WGPUTextureFormat_RGBA16Snorm;
+ case PixelFormat::R16G16B16A16_SInt: return WGPUTextureFormat_RGBA16Sint;
+ case PixelFormat::R32G32_Typeless:
+ case PixelFormat::R32G32_Float: return WGPUTextureFormat_RG32Float;
+ case PixelFormat::R32G32_UInt: return WGPUTextureFormat_RG32Uint;
+ case PixelFormat::R32G32_SInt: return WGPUTextureFormat_RG32Sint;
+ case PixelFormat::R32G8X24_Typeless:
+ case PixelFormat::D32_Float_S8X24_UInt:
+ case PixelFormat::R32_Float_X8X24_Typeless:
+ case PixelFormat::X32_Typeless_G8X24_UInt: return WGPUTextureFormat_Depth32FloatStencil8;
+ case PixelFormat::R10G10B10A2_Typeless:
+ case PixelFormat::R10G10B10A2_UNorm: return WGPUTextureFormat_RGB10A2Unorm;
+ case PixelFormat::R10G10B10A2_UInt: return WGPUTextureFormat_RGB10A2Uint;
+ case PixelFormat::R11G11B10_Float: return WGPUTextureFormat_RG11B10Ufloat;
+ case PixelFormat::R8G8B8A8_Typeless:
+ case PixelFormat::R8G8B8A8_UNorm: return WGPUTextureFormat_RGBA8Unorm;
+ case PixelFormat::R8G8B8A8_UNorm_sRGB: return WGPUTextureFormat_RGBA8UnormSrgb;
+ case PixelFormat::R8G8B8A8_UInt: return WGPUTextureFormat_RGBA8Uint;
+ case PixelFormat::R8G8B8A8_SNorm: return WGPUTextureFormat_RGBA8Snorm;
+ case PixelFormat::R8G8B8A8_SInt: return WGPUTextureFormat_RGBA8Sint;
+ case PixelFormat::R16G16_Typeless:
+ case PixelFormat::R16G16_Float: return WGPUTextureFormat_RG16Float;
+ case PixelFormat::R16G16_UNorm: return WGPUTextureFormat_RG16Unorm;
+ case PixelFormat::R16G16_UInt: return WGPUTextureFormat_RG16Uint;
+ case PixelFormat::R16G16_SNorm: return WGPUTextureFormat_RG16Snorm;
+ case PixelFormat::R16G16_SInt: return WGPUTextureFormat_RG16Sint;
+ case PixelFormat::D32_Float: return WGPUTextureFormat_Depth32Float;
+ case PixelFormat::R32_Typeless:
+ case PixelFormat::R32_Float: return WGPUTextureFormat_R32Float;
+ case PixelFormat::R32_UInt: return WGPUTextureFormat_R32Uint;
+ case PixelFormat::R32_SInt: return WGPUTextureFormat_R32Sint;
+ case PixelFormat::R24G8_Typeless:
+ case PixelFormat::D24_UNorm_S8_UInt:
+ case PixelFormat::R24_UNorm_X8_Typeless:
+ case PixelFormat::X24_Typeless_G8_UInt: return WGPUTextureFormat_Depth24PlusStencil8;
+ case PixelFormat::R8G8_Typeless:
+ case PixelFormat::R8G8_UNorm: return WGPUTextureFormat_RG8Unorm;
+ case PixelFormat::R8G8_UInt: return WGPUTextureFormat_RG8Uint;
+ case PixelFormat::R8G8_SNorm: return WGPUTextureFormat_RG8Snorm;
+ case PixelFormat::R8G8_SInt: return WGPUTextureFormat_RG8Sint;
+ case PixelFormat::R16_Typeless:
+ case PixelFormat::R16_Float: return WGPUTextureFormat_R16Float;
+ case PixelFormat::D16_UNorm: return WGPUTextureFormat_Depth16Unorm;
+ case PixelFormat::R16_UNorm: return WGPUTextureFormat_R16Unorm;
+ case PixelFormat::R16_UInt: return WGPUTextureFormat_R16Uint;
+ case PixelFormat::R16_SNorm: return WGPUTextureFormat_R16Snorm;
+ case PixelFormat::R16_SInt: return WGPUTextureFormat_R16Sint;
+ case PixelFormat::R8_Typeless:
+ case PixelFormat::R8_UNorm: return WGPUTextureFormat_R8Unorm;
+ case PixelFormat::R8_UInt: return WGPUTextureFormat_R8Uint;
+ case PixelFormat::R8_SNorm: return WGPUTextureFormat_R8Snorm;
+ case PixelFormat::R8_SInt: return WGPUTextureFormat_R8Sint;
+ case PixelFormat::R9G9B9E5_SharedExp: return WGPUTextureFormat_RGB9E5Ufloat;
+ case PixelFormat::BC1_Typeless:
+ case PixelFormat::BC1_UNorm: return WGPUTextureFormat_BC1RGBAUnorm;
+ case PixelFormat::BC1_UNorm_sRGB: return WGPUTextureFormat_BC1RGBAUnormSrgb;
+ case PixelFormat::BC2_Typeless:
+ case PixelFormat::BC2_UNorm: return WGPUTextureFormat_BC2RGBAUnorm;
+ case PixelFormat::BC2_UNorm_sRGB: return WGPUTextureFormat_BC2RGBAUnormSrgb;
+ case PixelFormat::BC3_Typeless:
+ case PixelFormat::BC3_UNorm: return WGPUTextureFormat_BC3RGBAUnorm;
+ case PixelFormat::BC3_UNorm_sRGB: return WGPUTextureFormat_BC3RGBAUnormSrgb;
+ case PixelFormat::BC4_Typeless:
+ case PixelFormat::BC4_UNorm: return WGPUTextureFormat_BC4RUnorm;
+ case PixelFormat::BC4_SNorm: return WGPUTextureFormat_BC4RSnorm;
+ case PixelFormat::BC5_Typeless:
+ case PixelFormat::BC5_UNorm: return WGPUTextureFormat_BC5RGUnorm;
+ case PixelFormat::BC5_SNorm: return WGPUTextureFormat_BC5RGSnorm;
+ case PixelFormat::B8G8R8A8_Typeless:
+ case PixelFormat::B8G8R8X8_Typeless:
+ case PixelFormat::B8G8R8A8_UNorm:
+ case PixelFormat::B8G8R8X8_UNorm: return WGPUTextureFormat_BGRA8Unorm;
+ case PixelFormat::B8G8R8A8_UNorm_sRGB:
+ case PixelFormat::B8G8R8X8_UNorm_sRGB: return WGPUTextureFormat_BGRA8UnormSrgb;
+ case PixelFormat::BC6H_Typeless:
+ case PixelFormat::BC6H_Uf16: return WGPUTextureFormat_BC6HRGBUfloat;
+ case PixelFormat::BC6H_Sf16: return WGPUTextureFormat_BC6HRGBFloat;
+ case PixelFormat::BC7_Typeless:
+ case PixelFormat::BC7_UNorm: return WGPUTextureFormat_BC7RGBAUnorm;
+ case PixelFormat::BC7_UNorm_sRGB: return WGPUTextureFormat_BC7RGBAUnormSrgb;
+ case PixelFormat::ASTC_4x4_UNorm: return WGPUTextureFormat_ASTC4x4Unorm;
+ case PixelFormat::ASTC_4x4_UNorm_sRGB: return WGPUTextureFormat_ASTC4x4UnormSrgb;
+ case PixelFormat::ASTC_6x6_UNorm: return WGPUTextureFormat_ASTC6x6Unorm;
+ case PixelFormat::ASTC_6x6_UNorm_sRGB: return WGPUTextureFormat_ASTC6x6UnormSrgb;
+ case PixelFormat::ASTC_8x8_UNorm: return WGPUTextureFormat_ASTC8x8Unorm;
+ case PixelFormat::ASTC_8x8_UNorm_sRGB: return WGPUTextureFormat_ASTC8x8UnormSrgb;
+ case PixelFormat::ASTC_10x10_UNorm: return WGPUTextureFormat_ASTC10x10Unorm;
+ case PixelFormat::ASTC_10x10_UNorm_sRGB: return WGPUTextureFormat_ASTC10x10UnormSrgb;
+ default: return WGPUTextureFormat_Undefined;
+ // @formatter:on
+ }
+}
+
+PixelFormat RenderToolsWebGPU::ToPixelFormat(WGPUTextureFormat format)
+{
+ switch (format)
+ {
+ // @formatter:off
+ case WGPUTextureFormat_R8Unorm: return PixelFormat::R8_UNorm;
+ case WGPUTextureFormat_R8Snorm: return PixelFormat::R8_SNorm;
+ case WGPUTextureFormat_R8Uint: return PixelFormat::R8_UInt;
+ case WGPUTextureFormat_R8Sint: return PixelFormat::R8_SInt;
+ case WGPUTextureFormat_R16Unorm: return PixelFormat::R16_UNorm;
+ case WGPUTextureFormat_R16Snorm: return PixelFormat::R16_SNorm;
+ case WGPUTextureFormat_R16Uint: return PixelFormat::R16_UInt;
+ case WGPUTextureFormat_R16Sint: return PixelFormat::R16_SInt;
+ case WGPUTextureFormat_R16Float: return PixelFormat::R16_Float;
+ case WGPUTextureFormat_R32Float: return PixelFormat::R32_Float;
+ case WGPUTextureFormat_R32Uint: return PixelFormat::R32_UInt;
+ case WGPUTextureFormat_R32Sint: return PixelFormat::R32_SInt;
+ case WGPUTextureFormat_RG16Unorm: return PixelFormat::R16G16_UNorm;
+ case WGPUTextureFormat_RG16Snorm: return PixelFormat::R16G16_SNorm;
+ case WGPUTextureFormat_RG16Uint: return PixelFormat::R16G16_UInt;
+ case WGPUTextureFormat_RG16Sint: return PixelFormat::R16G16_SInt;
+ case WGPUTextureFormat_RG16Float: return PixelFormat::R16G16_Float;
+ case WGPUTextureFormat_RGBA8Unorm: return PixelFormat::R8G8B8A8_UNorm;
+ case WGPUTextureFormat_RGBA8UnormSrgb: return PixelFormat::R8G8B8A8_UNorm_sRGB;
+ case WGPUTextureFormat_RGBA8Snorm: return PixelFormat::R8G8B8A8_SNorm;
+ case WGPUTextureFormat_RGBA8Uint: return PixelFormat::R8G8B8A8_UInt;
+ case WGPUTextureFormat_RGBA8Sint: return PixelFormat::R8G8B8A8_SInt;
+ case WGPUTextureFormat_BGRA8Unorm: return PixelFormat::B8G8R8A8_UNorm;
+ case WGPUTextureFormat_BGRA8UnormSrgb: return PixelFormat::B8G8R8A8_UNorm_sRGB;
+ case WGPUTextureFormat_RGB10A2Uint: return PixelFormat::R10G10B10A2_UInt;
+ case WGPUTextureFormat_RGB10A2Unorm: return PixelFormat::R10G10B10A2_UNorm;
+ case WGPUTextureFormat_RG11B10Ufloat: return PixelFormat::R11G11B10_Float;
+ case WGPUTextureFormat_RGB9E5Ufloat: return PixelFormat::R9G9B9E5_SharedExp;
+ case WGPUTextureFormat_RG32Float: return PixelFormat::R32G32_Float;
+ case WGPUTextureFormat_RG32Uint: return PixelFormat::R32G32_UInt;
+ case WGPUTextureFormat_RG32Sint: return PixelFormat::R32G32_SInt;
+ case WGPUTextureFormat_RGBA16Unorm: return PixelFormat::R16G16B16A16_UNorm;
+ case WGPUTextureFormat_RGBA16Snorm: return PixelFormat::R16G16B16A16_SNorm;
+ case WGPUTextureFormat_RGBA16Uint: return PixelFormat::R16G16B16A16_UInt;
+ case WGPUTextureFormat_RGBA16Sint: return PixelFormat::R16G16B16A16_SInt;
+ case WGPUTextureFormat_RGBA16Float: return PixelFormat::R16G16B16A16_Float;
+ case WGPUTextureFormat_RGBA32Float: return PixelFormat::R32G32B32A32_Float;
+ case WGPUTextureFormat_RGBA32Uint: return PixelFormat::R32G32B32A32_UInt;
+ case WGPUTextureFormat_RGBA32Sint: return PixelFormat::R32G32B32A32_SInt;
+ case WGPUTextureFormat_Depth16Unorm: return PixelFormat::D16_UNorm;
+ case WGPUTextureFormat_Depth24Plus:
+ case WGPUTextureFormat_Depth24PlusStencil8: return PixelFormat::D24_UNorm_S8_UInt;
+ case WGPUTextureFormat_Depth32Float: return PixelFormat::D32_Float;
+ case WGPUTextureFormat_Depth32FloatStencil8: return PixelFormat::D32_Float_S8X24_UInt;
+ case WGPUTextureFormat_BC1RGBAUnorm: return PixelFormat::BC1_UNorm;
+ case WGPUTextureFormat_BC1RGBAUnormSrgb: return PixelFormat::BC1_UNorm_sRGB;
+ case WGPUTextureFormat_BC2RGBAUnorm: return PixelFormat::BC2_UNorm;
+ case WGPUTextureFormat_BC2RGBAUnormSrgb: return PixelFormat::BC2_UNorm_sRGB;
+ case WGPUTextureFormat_BC3RGBAUnorm: return PixelFormat::BC3_UNorm;
+ case WGPUTextureFormat_BC3RGBAUnormSrgb: return PixelFormat::BC3_UNorm_sRGB;
+ case WGPUTextureFormat_BC4RUnorm: return PixelFormat::BC4_UNorm;
+ case WGPUTextureFormat_BC4RSnorm: return PixelFormat::BC4_SNorm;
+ case WGPUTextureFormat_BC5RGUnorm: return PixelFormat::BC5_UNorm;
+ case WGPUTextureFormat_BC5RGSnorm: return PixelFormat::BC5_SNorm;
+ case WGPUTextureFormat_BC6HRGBUfloat: return PixelFormat::BC6H_Uf16;
+ case WGPUTextureFormat_BC6HRGBFloat: return PixelFormat::BC6H_Sf16;
+ case WGPUTextureFormat_BC7RGBAUnorm: return PixelFormat::BC7_UNorm;
+ case WGPUTextureFormat_BC7RGBAUnormSrgb: return PixelFormat::BC7_UNorm_sRGB;
+ case WGPUTextureFormat_ASTC4x4Unorm: return PixelFormat::ASTC_4x4_UNorm;
+ case WGPUTextureFormat_ASTC4x4UnormSrgb: return PixelFormat::ASTC_4x4_UNorm_sRGB;
+ case WGPUTextureFormat_ASTC6x6Unorm: return PixelFormat::ASTC_6x6_UNorm;
+ case WGPUTextureFormat_ASTC6x6UnormSrgb: return PixelFormat::ASTC_6x6_UNorm_sRGB;
+ case WGPUTextureFormat_ASTC8x8Unorm: return PixelFormat::ASTC_8x8_UNorm;
+ case WGPUTextureFormat_ASTC8x8UnormSrgb: return PixelFormat::ASTC_8x8_UNorm_sRGB;
+ case WGPUTextureFormat_ASTC10x10Unorm: return PixelFormat::ASTC_10x10_UNorm;
+ case WGPUTextureFormat_ASTC10x10UnormSrgb: return PixelFormat::ASTC_10x10_UNorm_sRGB;
+ default: return PixelFormat::Unknown;
+ // @formatter:on
+ }
+}
+
+#endif
diff --git a/Source/Engine/GraphicsDevice/WebGPU/RenderToolsWebGPU.h b/Source/Engine/GraphicsDevice/WebGPU/RenderToolsWebGPU.h
new file mode 100644
index 000000000..6a0e374a0
--- /dev/null
+++ b/Source/Engine/GraphicsDevice/WebGPU/RenderToolsWebGPU.h
@@ -0,0 +1,74 @@
+// Copyright (c) Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#if GRAPHICS_API_WEBGPU
+
+#include "Engine/Core/Types/String.h"
+#include "IncludeWebGPU.h"
+#include
+
+enum class PixelFormat : unsigned;
+
+///
+/// User data used by AsyncCallbackWebGPU to track state adn result.
+///
+struct AsyncCallbackDataWebGPU
+{
+ String Message;
+ uint32 Status = 0;
+ intptr Result = 0;
+ double WaitTime = 0;
+
+ FORCE_INLINE void Call(bool success, uint32 status, WGPUStringView message)
+ {
+ Status = status;
+ if (!success)
+ Message.Set(message.data, message.data ? (int32)message.length : 0);
+ Platform::AtomicStore(&Result, success ? 1 : 2);
+ }
+};
+
+///
+/// Helper utility to run WebGPU APIs that use async callback in sync by waiting on the spontaneous call back with an active-waiting loop.
+///
+template
+struct AsyncCallbackWebGPU
+{
+ UserData Data;
+ CallbackInfo Info;
+
+ AsyncCallbackWebGPU(CallbackInfo callbackDefault)
+ : Info(callbackDefault)
+ {
+ Info.mode = WGPUCallbackMode_AllowSpontaneous;
+ Info.userdata1 = &Data;
+ }
+
+ WGPUWaitStatus Wait()
+ {
+ auto startTime = Platform::GetTimeSeconds();
+ int32 ticksLeft = 500; // Wait max 5 second
+ while (Platform::AtomicRead(&Data.Result) == 0 && ticksLeft-- > 0)
+ emscripten_sleep(10);
+ if (ticksLeft <= 0)
+ {
+ Data.WaitTime = Platform::GetTimeSeconds() - startTime;
+ return WGPUWaitStatus_TimedOut;
+ }
+ return Data.Result == 1 ? WGPUWaitStatus_Success : WGPUWaitStatus_Error;
+ }
+};
+
+///
+/// Set of utilities for rendering on Web GPU platform.
+///
+class RenderToolsWebGPU
+{
+public:
+ static WGPUVertexFormat ToVertexFormat(PixelFormat format);
+ static WGPUTextureFormat ToTextureFormat(PixelFormat format);
+ static PixelFormat ToPixelFormat(WGPUTextureFormat format);
+};
+
+#endif