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