Fix WebGPU crashes when resizing canvas

This commit is contained in:
2026-03-23 18:37:58 +01:00
parent b756c16018
commit 88fe9ba186
9 changed files with 65 additions and 26 deletions
@@ -98,6 +98,7 @@ bool GPUBufferWebGPU::OnInit()
return true;
_memoryUsage = bufferDesc.size;
Usage = bufferDesc.usage;
_view.Ptr.ObjectVersion = _device->GetObjectVersion();
// Initialize with a data if provided
if (bufferDesc.mappedAtCreation)
@@ -128,7 +129,7 @@ void GPUBufferWebGPU::OnReleaseGPU()
#if GPU_ENABLE_RESOURCE_NAMING
_name.Clear();
#endif
_view.Ptr.Version++;
_view.Ptr.ViewVersion++;
// Base
GPUBuffer::OnReleaseGPU();
@@ -1138,7 +1138,7 @@ void GPUContextWebGPU::BuildBindGroup(uint32 groupIndex, const SpirvShaderDescri
auto entriesPtr = key.Entries;
auto versionsPtr = key.Versions;
Platform::MemoryClear(entriesPtr, entriesCount * sizeof(WGPUBindGroupEntry));
Platform::MemoryClear(versionsPtr, ((entriesCount + 3) & ~0x3) * sizeof(uint8));
Platform::MemoryClear(versionsPtr, entriesCount * sizeof(uint32));
for (int32 index = 0; index < entriesCount; index++)
{
auto& descriptor = descriptors.DescriptorTypes[index];
@@ -1205,7 +1205,7 @@ void GPUContextWebGPU::BuildBindGroup(uint32 groupIndex, const SpirvShaderDescri
{
entry.buffer = ptr->BufferView->Buffer;
entry.size = ((GPUBufferWebGPU*)view->GetParent())->GetSize();
versionsPtr[index] = (uint64)ptr->Version;
versionsPtr[index] = ptr->Version;
}
if (!entry.buffer)
entry.buffer = _device->DefaultBuffer; // Fallback
@@ -148,6 +148,21 @@ public:
GPUQueryWebGPU AllocateQuery(GPUQueryType type);
// Object version counter used to track resource view permutations (to avoid pointer collisions on WGPUTexture/WGPUBuffer inside Bind Group cache)
#ifndef __EMSCRIPTEN_PTHREADS__
mutable uint16 ObjectVersionCounter = 0;
FORCE_INLINE uint16 GetObjectVersion() const
{
return ++ObjectVersionCounter;
}
#else
mutable volatile int64 ObjectVersionCounter = 0;
FORCE_INLINE uint16 GetObjectVersion() const
{
return (uint16)(Platform::InterlockedIncrement(&ObjectVersionCounter) % MAX_uint16);
}
#endif
public:
// [GPUDeviceDX]
GPUContext* GetMainContext() override
@@ -195,7 +210,15 @@ struct GPUResourceViewPtrWebGPU
{
class GPUBufferViewWebGPU* BufferView;
class GPUTextureViewWebGPU* TextureView;
uint8 Version;
union
{
struct
{
uint16 ObjectVersion; // Object instance permutation
uint16 ViewVersion; // Specific view permutation
};
uint32 Version;
};
};
extern GPUDevice* CreateGPUDeviceWebGPU();
@@ -5,6 +5,7 @@
#define WEBGPU_LOG_PSO 0
//#define WEBGPU_LOG_PSO_NAME "PS_GBuffer" // Debug log for PSOs with specific name
#define WEBGPU_LOG_BIND_GROUPS 0
#define WEBGPU_CACHE_BIND_GROUPS 1
#include "GPUPipelineStateWebGPU.h"
#include "GPUTextureWebGPU.h"
@@ -371,9 +372,11 @@ void GPUPipelineStateWebGPU::OnReleaseGPU()
{
VS = nullptr;
PS = nullptr;
#if WEBGPU_CACHE_BIND_GROUPS
for (auto& e : _bindGroups)
wgpuBindGroupRelease(e.Value);
_bindGroups.Clear();
#endif
for (auto& e : _pipelines)
wgpuRenderPipelineRelease(e.Value);
_pipelines.Clear();
@@ -412,7 +415,7 @@ bool GPUBindGroupKeyWebGPU::operator==(const GPUBindGroupKeyWebGPU& other) const
&& Layout == other.Layout
&& EntriesCount == other.EntriesCount
&& Platform::MemoryCompare(&Entries, &other.Entries, EntriesCount * sizeof(WGPUBindGroupEntry)) == 0
&& Platform::MemoryCompare(&Versions, &other.Versions, EntriesCount * sizeof(uint8)) == 0;
&& Platform::MemoryCompare(&Versions, &other.Versions, EntriesCount * sizeof(uint32)) == 0;
}
WGPUBindGroup GPUBindGroupCacheWebGPU::Get(WGPUDevice device, GPUBindGroupKeyWebGPU& key, const StringAnsiView& debugName, uint64 gcFrames)
@@ -434,6 +437,7 @@ WGPUBindGroup GPUBindGroupCacheWebGPU::Get(WGPUDevice device, GPUBindGroupKeyWeb
// Lookup for existing bind group
WGPUBindGroup bindGroup;
#if WEBGPU_CACHE_BIND_GROUPS
auto found = _bindGroups.Find(key);
if (found.IsNotEnd())
{
@@ -463,6 +467,7 @@ WGPUBindGroup GPUBindGroupCacheWebGPU::Get(WGPUDevice device, GPUBindGroupKeyWeb
return bindGroup;
}
#endif
PROFILE_CPU();
PROFILE_MEM(GraphicsShaders);
#if GPU_ENABLE_RESOURCE_NAMING
@@ -505,7 +510,7 @@ WGPUBindGroup GPUBindGroupCacheWebGPU::Get(WGPUDevice device, GPUBindGroupKeyWeb
equalLayout++;
if (key.EntriesCount == other.EntriesCount && Platform::MemoryCompare(&key.Entries, &other.Entries, key.EntriesCount * sizeof(WGPUBindGroupEntry)) == 0)
equalEntries++;
if (key.EntriesCount == other.EntriesCount && Platform::MemoryCompare(&key.Versions, &other.Versions, key.EntriesCount * sizeof(uint8)) == 0)
if (key.EntriesCount == other.EntriesCount && Platform::MemoryCompare(&key.Versions, &other.Versions, key.EntriesCount * sizeof(uint32)) == 0)
equalVersions++;
}
}
@@ -513,8 +518,10 @@ WGPUBindGroup GPUBindGroupCacheWebGPU::Get(WGPUDevice device, GPUBindGroupKeyWeb
LOG(Error, "> Hash collision! {}/{} (capacity: {}), equalLayout: {}, equalEntries: {}, equalVersions: {}", collisions, _bindGroups.Count(), _bindGroups.Capacity(), equalLayout, equalEntries, equalVersions);
#endif
#if WEBGPU_CACHE_BIND_GROUPS
// Cache it
_bindGroups.Add(key, bindGroup);
#endif
return bindGroup;
}
@@ -26,7 +26,7 @@ struct GPUBindGroupKeyWebGPU
mutable uint64 LastFrameUsed;
WGPUBindGroupEntry Entries[64];
uint8 EntriesCount;
uint8 Versions[64]; // Versions of descriptors used to differentiate when texture residency gets changed
uint32 Versions[64]; // Versions of descriptors used to differentiate when texture residency gets changed
bool operator==(const GPUBindGroupKeyWebGPU& other) const;
};
@@ -81,7 +81,7 @@ GPUTextureView* GPUSwapChainWebGPU::GetBackBufferView()
viewDesc.arrayLayerCount = 1;
viewDesc.aspect = WGPUTextureAspect_All;
viewDesc.usage = wgpuTextureGetUsage(surfaceTexture.texture);
_surfaceView.Create(surfaceTexture.texture, viewDesc);
_surfaceView.Create(surfaceTexture.texture, viewDesc, _surfaceView.Ptr.Version + 1);
}
return &_surfaceView;
}
@@ -37,9 +37,12 @@ void SetWebGPUTextureViewSampler(GPUTextureView* view, uint32 samplerType)
((GPUTextureViewWebGPU*)view)->SampleType = (WGPUTextureSampleType)samplerType;
}
void GPUTextureViewWebGPU::Create(WGPUTexture texture, const WGPUTextureViewDescriptor& desc)
void GPUTextureViewWebGPU::Create(WGPUTexture texture, const WGPUTextureViewDescriptor& desc, uint16 version)
{
Ptr.Version++;
Ptr.ObjectVersion = version;
Ptr.ViewVersion++;
if (ViewRender && View != ViewRender)
wgpuTextureViewRelease(ViewRender);
if (View)
wgpuTextureViewRelease(View);
Texture = texture;
@@ -104,21 +107,23 @@ void GPUTextureViewWebGPU::Create(WGPUTexture texture, const WGPUTextureViewDesc
void GPUTextureViewWebGPU::Release()
{
if (View != ViewRender)
{
wgpuTextureViewRelease(ViewRender);
ViewRender = nullptr;
}
if (View)
{
wgpuTextureViewRelease(View);
if (View == ViewRender)
ViewRender = nullptr;
View = nullptr;
}
if (ViewRender)
{
wgpuTextureViewRelease(ViewRender);
ViewRender = nullptr;
}
Texture = nullptr;
HasStencil = false;
ReadOnly = false;
DepthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
Ptr.Version++;
Ptr.ViewVersion++;
}
bool GPUTextureWebGPU::OnInit()
@@ -169,6 +174,7 @@ bool GPUTextureWebGPU::OnInit()
Texture = wgpuDeviceCreateTexture(_device->Device, &textureDesc);
if (!Texture)
return true;
_version = _device->GetObjectVersion();
// Update memory usage
_memoryUsage = calculateMemoryUsage();
@@ -217,7 +223,7 @@ void GPUTextureWebGPU::OnResidentMipsChanged()
GPUTextureViewWebGPU& view = IsVolume() ? _handleVolume : _handlesPerSlice[0];
if (view.GetParent() == nullptr)
view.Init(this, _desc.Format, _desc.MultiSampleLevel);
view.Create(Texture, viewDesc);
view.Create(Texture, viewDesc, _version);
}
void GPUTextureWebGPU::OnReleaseGPU()
@@ -277,7 +283,7 @@ void GPUTextureWebGPU::InitHandles()
{
auto& view = _handleVolume;
view.Init(this, format, msaa);
view.Create(Texture, viewDesc);
view.Create(Texture, viewDesc, _version);
}
// Init per slice views
@@ -288,7 +294,7 @@ void GPUTextureWebGPU::InitHandles()
{
auto& view = _handlesPerSlice[sliceIndex];
view.Init(this, format, msaa);
view.Create(Texture, viewDesc);
view.Create(Texture, viewDesc, _version);
view.DepthSlice = sliceIndex;
}
}
@@ -299,7 +305,7 @@ void GPUTextureWebGPU::InitHandles()
{
auto& view = _handleArray;
view.Init(this, format, msaa);
view.Create(Texture, viewDesc);
view.Create(Texture, viewDesc, _version);
}
// Create per array slice handles
@@ -311,7 +317,7 @@ void GPUTextureWebGPU::InitHandles()
viewDesc.arrayLayerCount = 1;
auto& view = _handlesPerSlice[arrayIndex];
view.Init(this, format, msaa);
view.Create(Texture, viewDesc);
view.Create(Texture, viewDesc, _version);
}
viewDesc.baseArrayLayer = 0;
viewDesc.arrayLayerCount = MipLevels();
@@ -323,7 +329,7 @@ void GPUTextureWebGPU::InitHandles()
_handlesPerSlice.Resize(1, false);
auto& view = _handlesPerSlice[0];
view.Init(this, format, msaa);
view.Create(Texture, viewDesc);
view.Create(Texture, viewDesc, _version);
}
// Init per mip map handles
@@ -344,7 +350,7 @@ void GPUTextureWebGPU::InitHandles()
auto& view = slice[mipIndex];
viewDesc.baseMipLevel = mipIndex;
view.Init(this, format, msaa);
view.Create(Texture, viewDesc);
view.Create(Texture, viewDesc, _version);
}
}
viewDesc.dimension = _viewDimension;
@@ -355,7 +361,7 @@ void GPUTextureWebGPU::InitHandles()
{
auto& view = _handleReadOnlyDepth;
view.Init(this, format, msaa);
view.Create(Texture, viewDesc);
view.Create(Texture, viewDesc, _version);
view.ReadOnly = true;
}
@@ -375,7 +381,7 @@ void GPUTextureWebGPU::InitHandles()
viewDesc.aspect = WGPUTextureAspect_StencilOnly;
viewDesc.format = WGPUTextureFormat_Stencil8;
_handleStencil.Init(this, stencilFormat, msaa);
_handleStencil.Create(Texture, viewDesc);
_handleStencil.Create(Texture, viewDesc, _version);
}
}
@@ -65,7 +65,7 @@ public:
public:
using GPUTextureView::Init;
void Create(WGPUTexture texture, const WGPUTextureViewDescriptor& desc);
void Create(WGPUTexture texture, const WGPUTextureViewDescriptor& desc, uint16 version);
void Release();
public:
@@ -93,6 +93,7 @@ private:
#endif
WGPUTextureFormat _format = WGPUTextureFormat_Undefined;
WGPUTextureViewDimension _viewDimension = WGPUTextureViewDimension_Undefined;
uint16 _version = 0;
public:
GPUTextureWebGPU(GPUDeviceWebGPU* device, const StringView& name)
@@ -289,6 +289,7 @@ namespace Flax.Build.Platforms
args.AddRange(options.LinkEnv.CustomArgs);
{
args.Add(string.Format("-o \"{0}\"", outputFilePath.Replace('\\', '/')));
args.Add("-Wno-experimental");
// Debug options
//args.Add("--minify=0");