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