Add initial base implementation for WebGPU rendering backend

This commit is contained in:
Wojtek Figat
2026-02-23 11:49:45 +01:00
parent 4ca10c7869
commit 6081ed35bc
29 changed files with 3565 additions and 2 deletions

View File

@@ -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 <emscripten/emscripten.h>
GPUVertexLayoutWebGPU::GPUVertexLayoutWebGPU(GPUDeviceWebGPU* device, const Elements& elements, bool explicitOffsets)
: GPUResourceBase<GPUDeviceWebGPU, GPUVertexLayout>(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<WGPUFeatureName, FixedAllocation<32>> 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<int32>(GPU_MAX_TEXTURE_SIZE, limits.maxTextureDimension1D);
Limits.MaximumTexture2DSize = Math::Min<int32>(GPU_MAX_TEXTURE_SIZE, limits.maxTextureDimension2D);
Limits.MaximumTexture3DSize = Math::Min<int32>(GPU_MAX_TEXTURE_SIZE, limits.maxTextureDimension3D);
Limits.MaximumMipLevelsCount = Math::Min<int32>(GPU_MAX_TEXTURE_MIP_LEVELS, (int32)log2(limits.maxTextureDimension2D));
Limits.MaximumTexture1DArraySize = Limits.MaximumTexture2DArraySize = Math::Min<int32>(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<WGPURequestDeviceCallbackInfo, DeviceUserData> 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<DeviceUserData*>(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<GPUSamplerWebGPU>(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<GPUContextWebGPU>(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<WGPURequestAdapterCallbackInfo, AdapterUserData> 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<AdapterUserData*>(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<GPUDeviceWebGPU>(instance, New<GPUAdapterWebGPU>(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<GPUTextureWebGPU>(this, name);
}
GPUShader* GPUDeviceWebGPU::CreateShader(const StringView& name)
{
PROFILE_MEM(GraphicsShaders);
return New<GPUShaderWebGPU>(this, name);
}
GPUPipelineState* GPUDeviceWebGPU::CreatePipelineState()
{
PROFILE_MEM(GraphicsCommands);
return New<GPUPipelineStateWebGPU>(this);
}
GPUTimerQuery* GPUDeviceWebGPU::CreateTimerQuery()
{
return nullptr;
}
GPUBuffer* GPUDeviceWebGPU::CreateBuffer(const StringView& name)
{
PROFILE_MEM(GraphicsBuffers);
return New<GPUBufferWebGPU>(this, name);
}
GPUSampler* GPUDeviceWebGPU::CreateSampler()
{
return New<GPUSamplerWebGPU>(this);
}
GPUVertexLayout* GPUDeviceWebGPU::CreateVertexLayout(const VertexElements& elements, bool explicitOffsets)
{
return New<GPUVertexLayoutWebGPU>(this, elements, explicitOffsets);
}
GPUSwapChain* GPUDeviceWebGPU::CreateSwapChain(Window* window)
{
return New<GPUSwapChainWebGPU>(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<GPUConstantBufferWebGPU>(this, size, buffer, name);
}
#endif