From 88fe9ba186b89d8e4a6b94cb923ac46b3f7781d1 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 23 Mar 2026 18:37:58 +0100 Subject: [PATCH] Fix WebGPU crashes when resizing canvas --- .../GraphicsDevice/WebGPU/GPUBufferWebGPU.cpp | 3 +- .../WebGPU/GPUContextWebGPU.cpp | 4 +- .../GraphicsDevice/WebGPU/GPUDeviceWebGPU.h | 25 +++++++++++- .../WebGPU/GPUPipelineStateWebGPU.cpp | 11 ++++- .../WebGPU/GPUShaderProgramWebGPU.h | 2 +- .../WebGPU/GPUSwapChainWebGPU.cpp | 2 +- .../WebGPU/GPUTextureWebGPU.cpp | 40 +++++++++++-------- .../GraphicsDevice/WebGPU/GPUTextureWebGPU.h | 3 +- .../Flax.Build/Platforms/Web/WebToolchain.cs | 1 + 9 files changed, 65 insertions(+), 26 deletions(-) diff --git a/Source/Engine/GraphicsDevice/WebGPU/GPUBufferWebGPU.cpp b/Source/Engine/GraphicsDevice/WebGPU/GPUBufferWebGPU.cpp index 5e1132a73..8d0a7c643 100644 --- a/Source/Engine/GraphicsDevice/WebGPU/GPUBufferWebGPU.cpp +++ b/Source/Engine/GraphicsDevice/WebGPU/GPUBufferWebGPU.cpp @@ -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(); diff --git a/Source/Engine/GraphicsDevice/WebGPU/GPUContextWebGPU.cpp b/Source/Engine/GraphicsDevice/WebGPU/GPUContextWebGPU.cpp index dae0d98ba..fac265832 100644 --- a/Source/Engine/GraphicsDevice/WebGPU/GPUContextWebGPU.cpp +++ b/Source/Engine/GraphicsDevice/WebGPU/GPUContextWebGPU.cpp @@ -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 diff --git a/Source/Engine/GraphicsDevice/WebGPU/GPUDeviceWebGPU.h b/Source/Engine/GraphicsDevice/WebGPU/GPUDeviceWebGPU.h index 4527cb0a7..f6ca4960b 100644 --- a/Source/Engine/GraphicsDevice/WebGPU/GPUDeviceWebGPU.h +++ b/Source/Engine/GraphicsDevice/WebGPU/GPUDeviceWebGPU.h @@ -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(); diff --git a/Source/Engine/GraphicsDevice/WebGPU/GPUPipelineStateWebGPU.cpp b/Source/Engine/GraphicsDevice/WebGPU/GPUPipelineStateWebGPU.cpp index 55519d637..568847bf2 100644 --- a/Source/Engine/GraphicsDevice/WebGPU/GPUPipelineStateWebGPU.cpp +++ b/Source/Engine/GraphicsDevice/WebGPU/GPUPipelineStateWebGPU.cpp @@ -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; } diff --git a/Source/Engine/GraphicsDevice/WebGPU/GPUShaderProgramWebGPU.h b/Source/Engine/GraphicsDevice/WebGPU/GPUShaderProgramWebGPU.h index 8d1fba61e..4d6c294d0 100644 --- a/Source/Engine/GraphicsDevice/WebGPU/GPUShaderProgramWebGPU.h +++ b/Source/Engine/GraphicsDevice/WebGPU/GPUShaderProgramWebGPU.h @@ -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; }; diff --git a/Source/Engine/GraphicsDevice/WebGPU/GPUSwapChainWebGPU.cpp b/Source/Engine/GraphicsDevice/WebGPU/GPUSwapChainWebGPU.cpp index e92a999db..c1ae07c6f 100644 --- a/Source/Engine/GraphicsDevice/WebGPU/GPUSwapChainWebGPU.cpp +++ b/Source/Engine/GraphicsDevice/WebGPU/GPUSwapChainWebGPU.cpp @@ -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; } diff --git a/Source/Engine/GraphicsDevice/WebGPU/GPUTextureWebGPU.cpp b/Source/Engine/GraphicsDevice/WebGPU/GPUTextureWebGPU.cpp index 513dcaf17..0549f6169 100644 --- a/Source/Engine/GraphicsDevice/WebGPU/GPUTextureWebGPU.cpp +++ b/Source/Engine/GraphicsDevice/WebGPU/GPUTextureWebGPU.cpp @@ -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); } } diff --git a/Source/Engine/GraphicsDevice/WebGPU/GPUTextureWebGPU.h b/Source/Engine/GraphicsDevice/WebGPU/GPUTextureWebGPU.h index 2eeab53eb..13a14f51e 100644 --- a/Source/Engine/GraphicsDevice/WebGPU/GPUTextureWebGPU.h +++ b/Source/Engine/GraphicsDevice/WebGPU/GPUTextureWebGPU.h @@ -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) diff --git a/Source/Tools/Flax.Build/Platforms/Web/WebToolchain.cs b/Source/Tools/Flax.Build/Platforms/Web/WebToolchain.cs index 33ed097bd..d539f034e 100644 --- a/Source/Tools/Flax.Build/Platforms/Web/WebToolchain.cs +++ b/Source/Tools/Flax.Build/Platforms/Web/WebToolchain.cs @@ -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");