Add initial base implementation for WebGPU rendering backend

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

View File

@@ -0,0 +1,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"
/// <summary>
/// Graphics Device adapter implementation for Web GPU backend.
/// </summary>
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

View File

@@ -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<WGPUBufferMapCallbackInfo> 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<AsyncCallbackDataWebGPU*>(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

View File

@@ -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"
/// <summary>
/// The buffer view for Web GPU backend.
/// </summary>
/// <seealso cref="GPUBufferView" />
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;
}
};
/// <summary>
/// GPU buffer for Web GPU backend.
/// </summary>
/// <seealso cref="GPUResourceWebGPU" />
class GPUBufferWebGPU : public GPUResourceWebGPU<GPUBuffer>
{
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

View File

@@ -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<GPUTextureView*>& 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<GPUBuffer*>& 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<GPUConstantBufferWebGPU*>(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<GPUBufferWebGPU*>(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<GPUBufferWebGPU*>(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, &copyInfo, 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, &copySize);
}
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<GPUTexture>(dstResource);
auto srcTexture = Cast<GPUTexture>(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<GPUTexture>(dstResource);
auto srcTexture = Cast<GPUTexture>(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

View File

@@ -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;
/// <summary>
/// GPU Context for Web GPU backend.
/// </summary>
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<PendingClear, FixedAllocation<16>> _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<GPUTextureView*>& 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<GPUBuffer*>& 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

View File

@@ -0,0 +1,581 @@
// Copyright (c) Wojciech Figat. All rights reserved.
#if GRAPHICS_API_WEBGPU
#include "GPUDeviceWebGPU.h"
#include "GPUShaderWebGPU.h"
#include "GPUContextWebGPU.h"
#include "GPUPipelineStateWebGPU.h"
#include "GPUTextureWebGPU.h"
#include "GPUBufferWebGPU.h"
#include "GPUSamplerWebGPU.h"
#include "GPUAdapterWebGPU.h"
#include "GPUVertexLayoutWebGPU.h"
#include "GPUSwapChainWebGPU.h"
#include "RenderToolsWebGPU.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Utilities.h"
#if BUILD_DEBUG
#include "Engine/Core/Collections/Sorting.h"
#endif
#include "Engine/Graphics/PixelFormatExtensions.h"
#include "Engine/Profiler/ProfilerMemory.h"
#include <emscripten/emscripten.h>
GPUVertexLayoutWebGPU::GPUVertexLayoutWebGPU(GPUDeviceWebGPU* device, const Elements& elements, bool explicitOffsets)
: GPUResourceBase<GPUDeviceWebGPU, GPUVertexLayout>(device, StringView::Empty)
{
SetElements(elements, explicitOffsets);
Layout = WGPU_VERTEX_BUFFER_LAYOUT_INIT;
Layout.stepMode = WGPUVertexStepMode_Vertex;
Layout.arrayStride = GetStride();
Layout.attributeCount = elements.Count();
Layout.attributes = Attributes;
const VertexElement* srcElements = GetElements().Get();
for (int32 i = 0; i < elements.Count(); i++)
{
const VertexElement& src = srcElements[i];
WGPUVertexAttribute& dst = Attributes[i];
dst.nextInChain = nullptr;
dst.format = RenderToolsWebGPU::ToVertexFormat(src.Format);
dst.offset = src.Offset;
dst.shaderLocation = src.Slot;
if (src.PerInstance)
Layout.stepMode = WGPUVertexStepMode_Instance;
}
}
GPUDeviceWebGPU::GPUDeviceWebGPU(WGPUInstance instance, GPUAdapterWebGPU* adapter)
: GPUDevice(RendererType::WebGPU, ShaderProfile::WebGPU)
, Adapter(adapter)
, WebGPUInstance(instance)
{
}
bool GPUDeviceWebGPU::Init()
{
// Init device limits
Array<WGPUFeatureName, FixedAllocation<32>> features;
#define CHECK_FEATURE(feature) if (wgpuAdapterHasFeature(Adapter->Adapter, feature)) features.Add(feature)
CHECK_FEATURE(WGPUFeatureName_CoreFeaturesAndLimits);
CHECK_FEATURE(WGPUFeatureName_DepthClipControl);
CHECK_FEATURE(WGPUFeatureName_Depth32FloatStencil8);
CHECK_FEATURE(WGPUFeatureName_TextureCompressionBC);
CHECK_FEATURE(WGPUFeatureName_TextureCompressionBCSliced3D);
CHECK_FEATURE(WGPUFeatureName_TextureCompressionETC2);
CHECK_FEATURE(WGPUFeatureName_TextureCompressionASTC);
CHECK_FEATURE(WGPUFeatureName_TextureCompressionASTCSliced3D);
CHECK_FEATURE(WGPUFeatureName_TimestampQuery);
CHECK_FEATURE(WGPUFeatureName_IndirectFirstInstance);
CHECK_FEATURE(WGPUFeatureName_RG11B10UfloatRenderable);
CHECK_FEATURE(WGPUFeatureName_BGRA8UnormStorage);
CHECK_FEATURE(WGPUFeatureName_Float32Filterable);
CHECK_FEATURE(WGPUFeatureName_Float32Blendable);
CHECK_FEATURE(WGPUFeatureName_ClipDistances);
CHECK_FEATURE(WGPUFeatureName_TextureFormatsTier1);
CHECK_FEATURE(WGPUFeatureName_TextureFormatsTier2);
CHECK_FEATURE(WGPUFeatureName_PrimitiveIndex);
CHECK_FEATURE(WGPUFeatureName_MultiDrawIndirect);
#undef CHECK_FEATURE
#if BUILD_DEBUG
LOG(Info, "WebGPU features:");
#define LOG_FEATURE(feature) if (features.Contains(feature)) LOG(Info, " > {}", TEXT(#feature))
LOG_FEATURE(WGPUFeatureName_CoreFeaturesAndLimits);
LOG_FEATURE(WGPUFeatureName_DepthClipControl);
LOG_FEATURE(WGPUFeatureName_Depth32FloatStencil8);
LOG_FEATURE(WGPUFeatureName_TextureCompressionBC);
LOG_FEATURE(WGPUFeatureName_TextureCompressionBCSliced3D);
LOG_FEATURE(WGPUFeatureName_TextureCompressionETC2);
LOG_FEATURE(WGPUFeatureName_TextureCompressionASTC);
LOG_FEATURE(WGPUFeatureName_TextureCompressionASTCSliced3D);
LOG_FEATURE(WGPUFeatureName_TimestampQuery);
LOG_FEATURE(WGPUFeatureName_IndirectFirstInstance);
LOG_FEATURE(WGPUFeatureName_RG11B10UfloatRenderable);
LOG_FEATURE(WGPUFeatureName_BGRA8UnormStorage);
LOG_FEATURE(WGPUFeatureName_Float32Filterable);
LOG_FEATURE(WGPUFeatureName_Float32Blendable);
LOG_FEATURE(WGPUFeatureName_ClipDistances);
LOG_FEATURE(WGPUFeatureName_TextureFormatsTier1);
LOG_FEATURE(WGPUFeatureName_TextureFormatsTier2);
LOG_FEATURE(WGPUFeatureName_PrimitiveIndex);
LOG_FEATURE(WGPUFeatureName_MultiDrawIndirect);
#undef LOG_FEATURE
#endif
for (int32 i = 0; i < (int32)PixelFormat::MAX; i++)
FeaturesPerFormat[i] = FormatFeatures(MSAALevel::None, FormatSupport::None);
WGPULimits limits = WGPU_LIMITS_INIT;
if (wgpuAdapterGetLimits(Adapter->Adapter, &limits) == WGPUStatus_Success)
{
Limits.HasDepthClip = features.Contains(WGPUFeatureName_DepthClipControl);
Limits.HasReadOnlyDepth = true;
Limits.MaximumTexture1DSize = Math::Min<int32>(GPU_MAX_TEXTURE_SIZE, limits.maxTextureDimension1D);
Limits.MaximumTexture2DSize = Math::Min<int32>(GPU_MAX_TEXTURE_SIZE, limits.maxTextureDimension2D);
Limits.MaximumTexture3DSize = Math::Min<int32>(GPU_MAX_TEXTURE_SIZE, limits.maxTextureDimension3D);
Limits.MaximumMipLevelsCount = Math::Min<int32>(GPU_MAX_TEXTURE_MIP_LEVELS, (int32)log2(limits.maxTextureDimension2D));
Limits.MaximumTexture1DArraySize = Limits.MaximumTexture2DArraySize = Math::Min<int32>(GPU_MAX_TEXTURE_ARRAY_SIZE, limits.maxTextureArrayLayers);
if (limits.maxTextureArrayLayers >= 6)
Limits.MaximumTextureCubeSize = Limits.MaximumTexture2DSize;
// Formats support based on: https://gpuweb.github.io/gpuweb/#plain-color-formats
auto supportsBuffer =
FormatSupport::Buffer |
FormatSupport::InputAssemblyIndexBuffer |
FormatSupport::InputAssemblyVertexBuffer;
auto supportsTexture =
FormatSupport::Texture1D |
FormatSupport::Texture2D |
FormatSupport::Texture3D |
FormatSupport::TextureCube |
FormatSupport::ShaderLoad |
FormatSupport::ShaderSample |
FormatSupport::ShaderSampleComparison |
FormatSupport::ShaderGather |
FormatSupport::ShaderGatherComparison |
FormatSupport::Mip;
auto supportsRender =
FormatSupport::RenderTarget |
FormatSupport::Blendable;
auto supportsDepth =
FormatSupport::DepthStencil;
auto supportsMultisampling =
FormatSupport::MultisampleRenderTarget |
FormatSupport::MultisampleLoad;
auto supportsMSAA =
FormatSupport::MultisampleRenderTarget |
FormatSupport::MultisampleResolve |
FormatSupport::MultisampleLoad;
#define ADD_FORMAT(pixelFormat, support)
FeaturesPerFormat[(int32)PixelFormat::R8_UNorm].Support |= supportsBuffer | supportsTexture | supportsRender | supportsMSAA;
FeaturesPerFormat[(int32)PixelFormat::R8_UInt].Support |= supportsBuffer | supportsTexture | supportsRender;
FeaturesPerFormat[(int32)PixelFormat::R8_SInt].Support |= supportsBuffer | supportsTexture | supportsRender;
FeaturesPerFormat[(int32)PixelFormat::R16_UInt].Support |= supportsBuffer | supportsTexture;
FeaturesPerFormat[(int32)PixelFormat::R16_SInt].Support |= supportsBuffer | supportsTexture;
FeaturesPerFormat[(int32)PixelFormat::R16_Float].Support |= supportsBuffer | supportsTexture | supportsRender | supportsMSAA;
FeaturesPerFormat[(int32)PixelFormat::R8G8_UNorm].Support |= supportsBuffer | supportsTexture;
FeaturesPerFormat[(int32)PixelFormat::R8G8_SNorm].Support |= supportsBuffer | supportsTexture;
FeaturesPerFormat[(int32)PixelFormat::R8G8_UInt].Support |= supportsBuffer | supportsTexture;
FeaturesPerFormat[(int32)PixelFormat::R8G8_SInt].Support |= supportsBuffer | supportsTexture;
FeaturesPerFormat[(int32)PixelFormat::R32_Float].Support |= supportsBuffer | supportsTexture | FormatSupport::RenderTarget;
FeaturesPerFormat[(int32)PixelFormat::R32_Float].Support & ~FormatSupport::ShaderSample;
FeaturesPerFormat[(int32)PixelFormat::R32_UInt].Support |= supportsBuffer | supportsTexture | FormatSupport::RenderTarget;
FeaturesPerFormat[(int32)PixelFormat::R32_SInt].Support |= supportsBuffer | supportsTexture | FormatSupport::RenderTarget;
FeaturesPerFormat[(int32)PixelFormat::R16G16_UInt].Support |= supportsBuffer | supportsTexture;
FeaturesPerFormat[(int32)PixelFormat::R16G16_SInt].Support |= supportsBuffer | supportsTexture;
FeaturesPerFormat[(int32)PixelFormat::R16G16_Float].Support |= supportsBuffer | supportsTexture | supportsRender | supportsMSAA;
FeaturesPerFormat[(int32)PixelFormat::R8G8B8A8_UNorm].Support |= supportsBuffer | supportsTexture | supportsRender | supportsMSAA;
FeaturesPerFormat[(int32)PixelFormat::R8G8B8A8_UNorm_sRGB].Support |= supportsBuffer | supportsTexture | supportsRender | supportsMSAA;
FeaturesPerFormat[(int32)PixelFormat::R8G8B8A8_SNorm].Support |= supportsBuffer | supportsTexture;
FeaturesPerFormat[(int32)PixelFormat::R8G8B8A8_UInt].Support |= supportsBuffer | supportsTexture;
FeaturesPerFormat[(int32)PixelFormat::R8G8B8A8_SInt].Support |= supportsBuffer | supportsTexture;
FeaturesPerFormat[(int32)PixelFormat::B8G8R8A8_UNorm].Support |= supportsBuffer | supportsTexture | supportsRender | supportsMSAA;
FeaturesPerFormat[(int32)PixelFormat::R10G10B10A2_UInt].Support |= supportsBuffer | supportsTexture;
FeaturesPerFormat[(int32)PixelFormat::R10G10B10A2_UNorm].Support |= supportsBuffer | supportsTexture | supportsRender | supportsMSAA;
FeaturesPerFormat[(int32)PixelFormat::R11G11B10_Float].Support |= supportsBuffer | supportsTexture;
FeaturesPerFormat[(int32)PixelFormat::R9G9B9E5_SharedExp].Support |= supportsBuffer | supportsTexture;
FeaturesPerFormat[(int32)PixelFormat::R32G32_Float].Support |= supportsBuffer | supportsTexture | FormatSupport::RenderTarget;
FeaturesPerFormat[(int32)PixelFormat::R32G32_Float].Support & ~FormatSupport::ShaderSample;
FeaturesPerFormat[(int32)PixelFormat::R32G32_UInt].Support |= supportsBuffer | supportsTexture | FormatSupport::RenderTarget;
FeaturesPerFormat[(int32)PixelFormat::R32G32_SInt].Support |= supportsBuffer | supportsTexture | FormatSupport::RenderTarget;
FeaturesPerFormat[(int32)PixelFormat::R16G16B16A16_UInt].Support |= supportsBuffer | supportsTexture;
FeaturesPerFormat[(int32)PixelFormat::R16G16B16A16_SInt].Support |= supportsBuffer | supportsTexture;
FeaturesPerFormat[(int32)PixelFormat::R16G16B16A16_Float].Support |= supportsBuffer | supportsTexture | supportsRender;
FeaturesPerFormat[(int32)PixelFormat::R32G32B32A32_Float].Support |= supportsBuffer | supportsTexture | FormatSupport::RenderTarget;
FeaturesPerFormat[(int32)PixelFormat::R32G32B32A32_Float].Support & ~FormatSupport::ShaderSample;
FeaturesPerFormat[(int32)PixelFormat::R32G32B32A32_UInt].Support |= supportsBuffer | supportsTexture | FormatSupport::RenderTarget;
FeaturesPerFormat[(int32)PixelFormat::R32G32B32A32_SInt].Support |= supportsBuffer | supportsTexture | FormatSupport::RenderTarget;
FeaturesPerFormat[(int32)PixelFormat::D16_UNorm].Support |= supportsBuffer | supportsTexture | supportsDepth;
FeaturesPerFormat[(int32)PixelFormat::D24_UNorm_S8_UInt].Support |= supportsBuffer | supportsTexture | supportsDepth;
FeaturesPerFormat[(int32)PixelFormat::D32_Float].Support |= supportsBuffer | supportsTexture | supportsDepth;
if (features.Contains(WGPUFeatureName_CoreFeaturesAndLimits))
{
FeaturesPerFormat[(int32)PixelFormat::B8G8R8A8_UNorm_sRGB].Support |= supportsBuffer | supportsTexture | supportsRender | supportsMSAA;
FeaturesPerFormat[(int32)PixelFormat::R8_UInt].Support |= supportsMultisampling;
FeaturesPerFormat[(int32)PixelFormat::R8_SInt].Support |= supportsMultisampling;
FeaturesPerFormat[(int32)PixelFormat::R8G8_UInt].Support |= supportsMultisampling;
FeaturesPerFormat[(int32)PixelFormat::R8G8_SInt].Support |= supportsMultisampling;
FeaturesPerFormat[(int32)PixelFormat::R8G8B8A8_UInt].Support |= supportsMultisampling;
FeaturesPerFormat[(int32)PixelFormat::R8G8B8A8_SInt].Support |= supportsMultisampling;
FeaturesPerFormat[(int32)PixelFormat::R16_UInt].Support |= supportsMultisampling;
FeaturesPerFormat[(int32)PixelFormat::R16_SInt].Support |= supportsMultisampling;
FeaturesPerFormat[(int32)PixelFormat::R16G16_UInt].Support |= supportsMultisampling;
FeaturesPerFormat[(int32)PixelFormat::R16G16_SInt].Support |= supportsMultisampling;
FeaturesPerFormat[(int32)PixelFormat::R16G16B16A16_UInt].Support |= supportsMultisampling;
FeaturesPerFormat[(int32)PixelFormat::R16G16B16A16_SInt].Support |= supportsMultisampling;
FeaturesPerFormat[(int32)PixelFormat::R16G16B16A16_Float].Support |= supportsMSAA;
FeaturesPerFormat[(int32)PixelFormat::R32_Float].Support |= supportsMultisampling;
FeaturesPerFormat[(int32)PixelFormat::R10G10B10A2_UInt].Support |= supportsMultisampling;
}
if (features.Contains(WGPUFeatureName_TextureFormatsTier1))
{
FeaturesPerFormat[(int32)PixelFormat::R8_SNorm].Support |= supportsBuffer | supportsTexture;
FeaturesPerFormat[(int32)PixelFormat::R16_UNorm].Support |= supportsBuffer | supportsTexture | supportsRender | supportsMSAA;
FeaturesPerFormat[(int32)PixelFormat::R16_SNorm].Support |= supportsBuffer | supportsTexture;
FeaturesPerFormat[(int32)PixelFormat::R16G16_UNorm].Support |= supportsBuffer | supportsTexture | supportsRender | supportsMultisampling;
FeaturesPerFormat[(int32)PixelFormat::R16G16_SNorm].Support |= supportsBuffer | supportsTexture | supportsRender | supportsMultisampling;
FeaturesPerFormat[(int32)PixelFormat::R16G16B16A16_UNorm].Support |= supportsBuffer | supportsTexture | supportsRender | supportsMultisampling;
FeaturesPerFormat[(int32)PixelFormat::R16G16B16A16_SNorm].Support |= supportsBuffer | supportsTexture | supportsRender | supportsMultisampling;
}
if (features.Contains(WGPUFeatureName_TextureFormatsTier2))
{
}
if (features.Contains(WGPUFeatureName_Depth32FloatStencil8))
{
FeaturesPerFormat[(int32)PixelFormat::D32_Float_S8X24_UInt].Support |= supportsBuffer | supportsTexture | supportsDepth;
}
if (features.Contains(WGPUFeatureName_Float32Blendable))
{
FeaturesPerFormat[(int32)PixelFormat::R32_Float].Support |= FormatSupport::Blendable;
FeaturesPerFormat[(int32)PixelFormat::R32G32_Float].Support |= FormatSupport::Blendable;
FeaturesPerFormat[(int32)PixelFormat::R32G32B32A32_Float].Support |= FormatSupport::Blendable;
}
if (features.Contains(WGPUFeatureName_Float32Filterable))
{
FeaturesPerFormat[(int32)PixelFormat::R32_Float].Support |= FormatSupport::ShaderSample;
FeaturesPerFormat[(int32)PixelFormat::R32G32_Float].Support |= FormatSupport::ShaderSample;
FeaturesPerFormat[(int32)PixelFormat::R32G32B32A32_Float].Support |= FormatSupport::ShaderSample;
}
if (features.Contains(WGPUFeatureName_RG11B10UfloatRenderable))
{
FeaturesPerFormat[(int32)PixelFormat::R11G11B10_Float].Support |= supportsRender | supportsMSAA;
}
if (features.Contains(WGPUFeatureName_TextureCompressionBC))
{
auto supportBc =
FormatSupport::Texture2D |
FormatSupport::ShaderLoad |
FormatSupport::ShaderSample |
FormatSupport::Mip;
if (features.Contains(WGPUFeatureName_TextureCompressionBCSliced3D))
supportBc |= FormatSupport::Texture3D;
FeaturesPerFormat[(int32)PixelFormat::BC1_UNorm].Support |= supportBc;
FeaturesPerFormat[(int32)PixelFormat::BC1_UNorm_sRGB].Support |= supportBc;
FeaturesPerFormat[(int32)PixelFormat::BC2_UNorm].Support |= supportBc;
FeaturesPerFormat[(int32)PixelFormat::BC2_UNorm_sRGB].Support |= supportBc;
FeaturesPerFormat[(int32)PixelFormat::BC3_UNorm].Support |= supportBc;
FeaturesPerFormat[(int32)PixelFormat::BC3_UNorm_sRGB].Support |= supportBc;
FeaturesPerFormat[(int32)PixelFormat::BC4_UNorm].Support |= supportBc;
FeaturesPerFormat[(int32)PixelFormat::BC4_SNorm].Support |= supportBc;
FeaturesPerFormat[(int32)PixelFormat::BC5_UNorm].Support |= supportBc;
FeaturesPerFormat[(int32)PixelFormat::BC5_SNorm].Support |= supportBc;
FeaturesPerFormat[(int32)PixelFormat::BC6H_Uf16].Support |= supportBc;
FeaturesPerFormat[(int32)PixelFormat::BC6H_Sf16].Support |= supportBc;
FeaturesPerFormat[(int32)PixelFormat::BC7_UNorm].Support |= supportBc;
FeaturesPerFormat[(int32)PixelFormat::BC7_UNorm_sRGB].Support |= supportBc;
}
if (features.Contains(WGPUFeatureName_TextureCompressionASTC))
{
auto supportAstc =
FormatSupport::Texture2D |
FormatSupport::ShaderLoad |
FormatSupport::ShaderSample |
FormatSupport::Mip;
if (features.Contains(WGPUFeatureName_TextureCompressionASTCSliced3D))
supportAstc |= FormatSupport::Texture3D;
FeaturesPerFormat[(int32)PixelFormat::ASTC_4x4_UNorm].Support |= supportAstc;
FeaturesPerFormat[(int32)PixelFormat::ASTC_4x4_UNorm_sRGB].Support |= supportAstc;
FeaturesPerFormat[(int32)PixelFormat::ASTC_6x6_UNorm].Support |= supportAstc;
FeaturesPerFormat[(int32)PixelFormat::ASTC_6x6_UNorm_sRGB].Support |= supportAstc;
FeaturesPerFormat[(int32)PixelFormat::ASTC_8x8_UNorm].Support |= supportAstc;
FeaturesPerFormat[(int32)PixelFormat::ASTC_8x8_UNorm_sRGB].Support |= supportAstc;
FeaturesPerFormat[(int32)PixelFormat::ASTC_10x10_UNorm].Support |= supportAstc;
FeaturesPerFormat[(int32)PixelFormat::ASTC_10x10_UNorm_sRGB].Support |= supportAstc;
}
for (auto& e : FeaturesPerFormat)
{
if (EnumHasAllFlags(e.Support, FormatSupport::MultisampleRenderTarget))
e.MSAALevelMax = MSAALevel::X4;
}
#undef ADD_FORMAT
#if BUILD_DEBUG
LOG(Info, "WebGPU limits:");
LOG(Info, " > maxTextureDimension1D = {}", limits.maxTextureDimension1D);
LOG(Info, " > maxTextureDimension2D = {}", limits.maxTextureDimension2D);
LOG(Info, " > maxTextureDimension3D = {}", limits.maxTextureDimension3D);
LOG(Info, " > maxTextureArrayLayers = {}", limits.maxTextureArrayLayers);
LOG(Info, " > maxBindGroups = {}", limits.maxBindGroups);
LOG(Info, " > maxBindGroupsPlusVertexBuffers = {}", limits.maxBindGroupsPlusVertexBuffers);
LOG(Info, " > maxBindingsPerBindGroup = {}", limits.maxBindingsPerBindGroup);
LOG(Info, " > maxDynamicUniformBuffersPerPipelineLayout = {}", limits.maxDynamicUniformBuffersPerPipelineLayout);
LOG(Info, " > maxDynamicStorageBuffersPerPipelineLayout = {}", limits.maxDynamicStorageBuffersPerPipelineLayout);
LOG(Info, " > maxSampledTexturesPerShaderStage = {}", limits.maxSampledTexturesPerShaderStage);
LOG(Info, " > maxSamplersPerShaderStage = {}", limits.maxSamplersPerShaderStage);
LOG(Info, " > maxStorageBuffersPerShaderStage = {}", limits.maxStorageBuffersPerShaderStage);
LOG(Info, " > maxStorageTexturesPerShaderStage = {}", limits.maxStorageTexturesPerShaderStage);
LOG(Info, " > maxUniformBuffersPerShaderStage = {}", limits.maxUniformBuffersPerShaderStage);
LOG(Info, " > maxUniformBufferBindingSize = {}", limits.maxUniformBufferBindingSize);
LOG(Info, " > maxStorageBufferBindingSize = {}", limits.maxStorageBufferBindingSize);
LOG(Info, " > minUniformBufferOffsetAlignment = {}", limits.minUniformBufferOffsetAlignment);
LOG(Info, " > minStorageBufferOffsetAlignment = {}", limits.minStorageBufferOffsetAlignment);
LOG(Info, " > maxVertexBuffers = {}", limits.maxVertexBuffers);
LOG(Info, " > maxBufferSize = {}", limits.maxBufferSize);
LOG(Info, " > maxVertexAttributes = {}", limits.maxVertexAttributes);
LOG(Info, " > maxVertexBufferArrayStride = {}", limits.maxVertexBufferArrayStride);
LOG(Info, " > maxInterStageShaderVariables = {}", limits.maxInterStageShaderVariables);
LOG(Info, " > maxColorAttachments = {}", limits.maxColorAttachments);
LOG(Info, " > maxComputeWorkgroupStorageSize = {}", limits.maxComputeWorkgroupStorageSize);
LOG(Info, " > maxComputeInvocationsPerWorkgroup = {}", limits.maxComputeInvocationsPerWorkgroup);
LOG(Info, " > maxComputeWorkgroupSize = {}x{}x{}", limits.maxComputeWorkgroupSizeX, limits.maxComputeWorkgroupSizeY, limits.maxComputeWorkgroupSizeZ);
LOG(Info, " > maxComputeWorkgroupsPerDimension = {}", limits.maxComputeWorkgroupsPerDimension);
LOG(Info, " > maxImmediateSize = {}", limits.maxImmediateSize);
#endif
}
// Inti device options
WGPUDeviceDescriptor deviceDesc = WGPU_DEVICE_DESCRIPTOR_INIT;
deviceDesc.requiredLimits = &limits;
deviceDesc.requiredFeatureCount = features.Count();
deviceDesc.requiredFeatures = features.Get();
deviceDesc.deviceLostCallbackInfo.mode = WGPUCallbackMode_AllowSpontaneous;
deviceDesc.deviceLostCallbackInfo.callback = [](WGPUDevice const* device, WGPUDeviceLostReason reason, WGPUStringView message, WGPU_NULLABLE void* userdata1, WGPU_NULLABLE void* userdata2)
{
if (reason == WGPUDeviceLostReason_Destroyed ||
reason == WGPUDeviceLostReason_FailedCreation ||
reason == WGPUDeviceLostReason_CallbackCancelled)
return;
Platform::Fatal(String::Format(TEXT("WebGPU device lost! {}, 0x:{:x}"), WEBGPU_TO_STR(message), (uint32)reason), nullptr, FatalErrorType::GPUHang);
};
deviceDesc.uncapturedErrorCallbackInfo.callback = [](WGPUDevice const* device, WGPUErrorType type, WGPUStringView message, WGPU_NULLABLE void* userdata1, WGPU_NULLABLE void* userdata2)
{
if (type == WGPUErrorType_OutOfMemory)
{
Platform::Fatal(String::Format(TEXT("WebGPU device run out of memory! {}"), WEBGPU_TO_STR(message)), nullptr, FatalErrorType::GPUOutOfMemory);
}
#if !BUILD_RELEASE
else if (type == WGPUErrorType_Validation)
{
LOG(Warning, "WebGPU Validation: {}", WEBGPU_TO_STR(message));
}
else
{
LOG(Info, "WebGPU: {}", WEBGPU_TO_STR(message));
}
#endif
};
// Create device
struct DeviceUserData : AsyncCallbackDataWebGPU
{
WGPUDevice Device = nullptr;
};
AsyncCallbackWebGPU<WGPURequestDeviceCallbackInfo, DeviceUserData> deviceRequest(WGPU_REQUEST_DEVICE_CALLBACK_INFO_INIT);
#if GPU_ENABLE_RESOURCE_NAMING
deviceDesc.label = WEBGPU_STR("Flax Device");
deviceDesc.defaultQueue.label = WEBGPU_STR("Flax Queue");
#endif
deviceRequest.Info.callback = [](WGPURequestDeviceStatus status, WGPUDevice device, WGPUStringView message, WGPU_NULLABLE void* userdata1, WGPU_NULLABLE void* userdata2)
{
DeviceUserData& userData = *reinterpret_cast<DeviceUserData*>(userdata1);
userData.Device = device;
userData.Call(status == WGPURequestDeviceStatus_Success, status, message);
};
wgpuAdapterRequestDevice(Adapter->Adapter, &deviceDesc, deviceRequest.Info);
auto deviceRequestResult = deviceRequest.Wait();
if (deviceRequestResult == WGPUWaitStatus_TimedOut)
{
LOG(Fatal, "WebGPU device request has timed out after {}s", deviceRequest.Data.WaitTime);
return true;
}
if (deviceRequestResult == WGPUWaitStatus_Error)
{
LOG(Fatal, "Failed to get WebGPU device (browser might not support it). Error: {}, 0x{:x}", deviceRequest.Data.Message, deviceRequest.Data.Status);
return true;
}
Device = deviceRequest.Data.Device;
TotalGraphicsMemory = 1024 * 1024 * 1024; // Dummy 1GB
// Create default samplers
auto samplerDesc = GPUSamplerDescription::New();
#define INIT_SAMPLER(slot, filter, addressMode, compare) \
DefaultSamplers[slot] = New<GPUSamplerWebGPU>(this); \
samplerDesc.Filter = filter; \
samplerDesc.AddressU = samplerDesc.AddressV = samplerDesc.AddressW = addressMode; \
samplerDesc.ComparisonFunction = compare; \
DefaultSamplers[slot]->Init(samplerDesc)
INIT_SAMPLER(0, GPUSamplerFilter::Trilinear, GPUSamplerAddressMode::Clamp, GPUSamplerCompareFunction::Never);
INIT_SAMPLER(1, GPUSamplerFilter::Point, GPUSamplerAddressMode::Clamp, GPUSamplerCompareFunction::Never);
INIT_SAMPLER(2, GPUSamplerFilter::Trilinear, GPUSamplerAddressMode::Wrap, GPUSamplerCompareFunction::Never);
INIT_SAMPLER(3, GPUSamplerFilter::Point, GPUSamplerAddressMode::Wrap, GPUSamplerCompareFunction::Never);
INIT_SAMPLER(4, GPUSamplerFilter::Point, GPUSamplerAddressMode::Clamp, GPUSamplerCompareFunction::Less);
INIT_SAMPLER(5, GPUSamplerFilter::Trilinear, GPUSamplerAddressMode::Clamp, GPUSamplerCompareFunction::Less);
#undef INIT_SAMPLER
// Setup commands processing
Queue = wgpuDeviceGetQueue(Device);
_mainContext = New<GPUContextWebGPU>(this);
_state = DeviceState::Ready;
return GPUDevice::Init();
}
GPUDeviceWebGPU::~GPUDeviceWebGPU()
{
// Ensure to be disposed
GPUDeviceWebGPU::Dispose();
}
GPUDevice* CreateGPUDeviceWebGPU()
{
// Create instance
WGPUInstanceDescriptor instanceDesc = WGPU_INSTANCE_DESCRIPTOR_INIT;
WGPUInstance instance = wgpuCreateInstance(&instanceDesc);
if (!instance)
{
LOG(Error, "Failed to create instance for WebGPU (browser might not support it).");
return nullptr;
}
// Init adapter options
WGPURequestAdapterOptions adapterOptions = WGPU_REQUEST_ADAPTER_OPTIONS_INIT;
#if !PLATFORM_WEB
adapterOptions.powerPreference = WGPUPowerPreference_HighPerformance;
#endif
// Create adapter
struct AdapterUserData : AsyncCallbackDataWebGPU
{
WGPUAdapter Adapter = nullptr;
};
AsyncCallbackWebGPU<WGPURequestAdapterCallbackInfo, AdapterUserData> adapterRequest(WGPU_REQUEST_ADAPTER_CALLBACK_INFO_INIT);
adapterRequest.Info.callback = [](WGPURequestAdapterStatus status, WGPUAdapter adapter, WGPUStringView message, WGPU_NULLABLE void* userdata1, WGPU_NULLABLE void* userdata2)
{
AdapterUserData& userData = *reinterpret_cast<AdapterUserData*>(userdata1);
userData.Adapter = adapter;
userData.Call(status == WGPURequestAdapterStatus_Success, status, message);
};
wgpuInstanceRequestAdapter(instance, &adapterOptions, adapterRequest.Info);
auto adapterRequestResult = adapterRequest.Wait();
if (adapterRequestResult == WGPUWaitStatus_TimedOut)
{
LOG(Fatal, "WebGPU adapter request has timed out after {}s", adapterRequest.Data.WaitTime);
return nullptr;
}
if (adapterRequestResult == WGPUWaitStatus_Error)
{
LOG(Fatal, "Failed to get WebGPU adapter (browser might not support it). Error: {}, 0x{:x}", adapterRequest.Data.Message, adapterRequest.Data.Status);
return nullptr;
}
// Create device
auto device = New<GPUDeviceWebGPU>(instance, New<GPUAdapterWebGPU>(adapterRequest.Data.Adapter));
if (device->Init())
{
LOG(Warning, "Graphics Device init failed");
Delete(device);
return nullptr;
}
return device;
}
void GPUDeviceWebGPU::Dispose()
{
GPUDeviceLock lock(this);
if (_state == DeviceState::Disposed)
return;
// Begin destruction
_state = DeviceState::Disposing;
WaitForGPU();
preDispose();
// Clear device resources
SAFE_DELETE_GPU_RESOURCES(DefaultSamplers);
SAFE_DELETE(_mainContext);
SAFE_DELETE(Adapter);
if (Queue)
{
wgpuQueueRelease(Queue);
Queue = nullptr;
}
if (Device)
{
wgpuDeviceRelease(Device);
Device = nullptr;
}
wgpuInstanceRelease(WebGPUInstance);
// End destruction
GPUDevice::Dispose();
_state = DeviceState::Disposed;
}
void GPUDeviceWebGPU::WaitForGPU()
{
}
bool GPUDeviceWebGPU::GetQueryResult(uint64 queryID, uint64& result, bool wait)
{
// TODO: impl queries
return false;
}
GPUTexture* GPUDeviceWebGPU::CreateTexture(const StringView& name)
{
PROFILE_MEM(GraphicsTextures);
return New<GPUTextureWebGPU>(this, name);
}
GPUShader* GPUDeviceWebGPU::CreateShader(const StringView& name)
{
PROFILE_MEM(GraphicsShaders);
return New<GPUShaderWebGPU>(this, name);
}
GPUPipelineState* GPUDeviceWebGPU::CreatePipelineState()
{
PROFILE_MEM(GraphicsCommands);
return New<GPUPipelineStateWebGPU>(this);
}
GPUTimerQuery* GPUDeviceWebGPU::CreateTimerQuery()
{
return nullptr;
}
GPUBuffer* GPUDeviceWebGPU::CreateBuffer(const StringView& name)
{
PROFILE_MEM(GraphicsBuffers);
return New<GPUBufferWebGPU>(this, name);
}
GPUSampler* GPUDeviceWebGPU::CreateSampler()
{
return New<GPUSamplerWebGPU>(this);
}
GPUVertexLayout* GPUDeviceWebGPU::CreateVertexLayout(const VertexElements& elements, bool explicitOffsets)
{
return New<GPUVertexLayoutWebGPU>(this, elements, explicitOffsets);
}
GPUSwapChain* GPUDeviceWebGPU::CreateSwapChain(Window* window)
{
return New<GPUSwapChainWebGPU>(this, window);
}
GPUConstantBuffer* GPUDeviceWebGPU::CreateConstantBuffer(uint32 size, const StringView& name)
{
PROFILE_MEM(GraphicsShaders);
WGPUBuffer buffer = nullptr;
if (size)
{
WGPUBufferDescriptor desc = WGPU_BUFFER_DESCRIPTOR_INIT;
#if GPU_ENABLE_RESOURCE_NAMING
desc.label = WEBGPU_STR("Uniform");
#endif
desc.size = size;
desc.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Uniform;
buffer = wgpuDeviceCreateBuffer(Device, &desc);
if (buffer == nullptr)
{
LOG(Error, "Failed to create uniform buffer '{}' of size {} bytes", name, size);
return nullptr;
}
}
return New<GPUConstantBufferWebGPU>(this, size, buffer, name);
}
#endif

View File

@@ -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;
/// <summary>
/// Implementation of Graphics Device for Web GPU backend.
/// </summary>
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;
};
/// <summary>
/// GPU resource implementation for Web GPU backend.
/// </summary>
template<class BaseType>
class GPUResourceWebGPU : public GPUResourceBase<GPUDeviceWebGPU, BaseType>
{
public:
GPUResourceWebGPU(GPUDeviceWebGPU* device, const StringView& name) noexcept
: GPUResourceBase<GPUDeviceWebGPU, BaseType>(device, name)
{
}
};
struct GPUResourceViewPtrWebGPU
{
class GPUBufferViewWebGPU* BufferView;
class GPUTextureViewWebGPU* TextureView;
};
extern GPUDevice* CreateGPUDeviceWebGPU();
#endif

View File

@@ -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

View File

@@ -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
/// <summary>
/// Graphics pipeline state object for Web GPU backend.
/// </summary>
class GPUPipelineStateWebGPU : public GPUResourceWebGPU<GPUPipelineState>
{
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<Key, WGPURenderPipeline> _pipelines;
public:
GPUShaderProgramVSWebGPU* VS = nullptr;
GPUShaderProgramPSWebGPU* PS = nullptr;
WGPURenderPipelineDescriptor PipelineDesc;
public:
GPUPipelineStateWebGPU(GPUDeviceWebGPU* device)
: GPUResourceWebGPU<GPUPipelineState>(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

View File

@@ -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

View File

@@ -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
/// <summary>
/// Sampler object for Web GPU backend.
/// </summary>
class GPUSamplerWebGPU : public GPUResourceBase<GPUDeviceWebGPU, GPUSampler>
{
public:
GPUSamplerWebGPU(GPUDeviceWebGPU* device)
: GPUResourceBase<GPUDeviceWebGPU, GPUSampler>(device, StringView::Empty)
{
}
public:
WGPUSampler Sampler = nullptr;
protected:
// [GPUSampler]
bool OnInit() override;
void OnReleaseGPU() override;
};
#endif

View File

@@ -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
/// <summary>
/// Shaders base class for Web GPU backend.
/// </summary>
template<typename BaseType>
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;
}
};
/// <summary>
/// Vertex Shader for Web GPU backend.
/// </summary>
class GPUShaderProgramVSWebGPU : public GPUShaderProgramWebGPU<GPUShaderProgramVS>
{
public:
GPUShaderProgramVSWebGPU(const GPUShaderProgramInitializer& initializer, GPUVertexLayout* inputLayout, GPUVertexLayout* vertexLayout, Span<byte> bytecode)
: GPUShaderProgramWebGPU(initializer)
{
InputLayout = inputLayout;
Layout = vertexLayout;
}
};
/// <summary>
/// Pixel Shader for Web GPU backend.
/// </summary>
class GPUShaderProgramPSWebGPU : public GPUShaderProgramWebGPU<GPUShaderProgramPS>
{
public:
GPUShaderProgramPSWebGPU(const GPUShaderProgramInitializer& initializer)
: GPUShaderProgramWebGPU(initializer)
{
}
};
#endif

View File

@@ -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<byte> 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<GPUShaderProgramVSWebGPU>(initializer, inputLayout, vertexLayout, bytecode);
break;
}
case ShaderStage::Pixel:
{
MISSING_CODE("create pixel shader");
shader = New<GPUShaderProgramPSWebGPU>(initializer);
break;
}
}
return shader;
}
void GPUShaderWebGPU::OnReleaseGPU()
{
_cbs.Clear();
GPUShader::OnReleaseGPU();
}
#endif

View File

@@ -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"
/// <summary>
/// Constant Buffer for Web GPU backend.
/// </summary>
class GPUConstantBufferWebGPU : public GPUResourceWebGPU<GPUConstantBuffer>
{
public:
GPUConstantBufferWebGPU(GPUDeviceWebGPU* device, uint32 size, WGPUBuffer buffer, const StringView& name) noexcept;
~GPUConstantBufferWebGPU();
public:
WGPUBuffer Buffer;
public:
// [GPUResourceWebGPU]
void OnReleaseGPU() final override;
};
/// <summary>
/// Shader for Web GPU backend.
/// </summary>
class GPUShaderWebGPU : public GPUResourceWebGPU<GPUShader>
{
private:
Array<GPUConstantBufferWebGPU, FixedAllocation<GPU_MAX_CB_BINDED>> _cbs;
public:
GPUShaderWebGPU(GPUDeviceWebGPU* device, const StringView& name)
: GPUResourceWebGPU(device, name)
{
}
protected:
// [GPUShader]
GPUShaderProgram* CreateGPUShaderProgram(ShaderStage type, const GPUShaderProgramInitializer& initializer, Span<byte> bytecode, MemoryReadStream& stream) override;
void OnReleaseGPU() override;
};
#endif

View File

@@ -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

View File

@@ -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
/// <summary>
/// Graphics Device rendering output for Web GPU backend.
/// </summary>
class GPUSwapChainWebGPU : public GPUResourceWebGPU<GPUSwapChain>
{
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

View File

@@ -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

View File

@@ -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
/// <summary>
/// The texture view for Web GPU backend.
/// </summary>
/// <seealso cref="GPUTextureView" />
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;
}
};
/// <summary>
/// Texture object for Web GPU backend.
/// </summary>
class GPUTextureWebGPU : public GPUResourceWebGPU<GPUTexture>
{
private:
GPUTextureViewWebGPU _handleArray;
GPUTextureViewWebGPU _handleVolume;
GPUTextureViewWebGPU _handleReadOnlyDepth;
GPUTextureViewWebGPU _handleStencil;
Array<GPUTextureViewWebGPU> _handlesPerSlice; // [slice]
Array<Array<GPUTextureViewWebGPU>> _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<GPUTexture>(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

View File

@@ -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"
/// <summary>
/// Vertex layout object for Web GPU backend.
/// </summary>
class GPUVertexLayoutWebGPU : public GPUResourceBase<GPUDeviceWebGPU, GPUVertexLayout>
{
public:
GPUVertexLayoutWebGPU(GPUDeviceWebGPU* device, const Elements& elements, bool explicitOffsets);
public:
WGPUVertexBufferLayout Layout;
WGPUVertexAttribute Attributes[GPU_MAX_VS_ELEMENTS];
};
#endif

View File

@@ -0,0 +1,24 @@
// Copyright (c) Wojciech Figat. All rights reserved.
using System.IO;
using Flax.Build.NativeCpp;
using Flax.Build.Platforms;
/// <summary>
/// WebGPU graphics backend module.
/// </summary>
public class GraphicsDeviceWebGPU : GraphicsDeviceBaseModule
{
/// <inheritdoc />
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"));
}
}

View File

@@ -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<intptr>(-1))
#endif
#ifndef SIZE_MAX
#define SIZE_MAX __SIZE_MAX__
#endif
#include <webgpu/webgpu.h>
// 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

View File

@@ -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

View File

@@ -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 <emscripten/emscripten.h>
enum class PixelFormat : unsigned;
/// <summary>
/// User data used by AsyncCallbackWebGPU to track state adn result.
/// </summary>
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);
}
};
/// <summary>
/// Helper utility to run WebGPU APIs that use async callback in sync by waiting on the spontaneous call back with an active-waiting loop.
/// </summary>
template<typename CallbackInfo, typename UserData = AsyncCallbackDataWebGPU>
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;
}
};
/// <summary>
/// Set of utilities for rendering on Web GPU platform.
/// </summary>
class RenderToolsWebGPU
{
public:
static WGPUVertexFormat ToVertexFormat(PixelFormat format);
static WGPUTextureFormat ToTextureFormat(PixelFormat format);
static PixelFormat ToPixelFormat(WGPUTextureFormat format);
};
#endif