Fix rendering various visuals on WebGPU

This commit is contained in:
Wojtek Figat
2026-03-02 20:36:33 +01:00
parent b36c757753
commit b191d3918e
36 changed files with 458 additions and 220 deletions

View File

@@ -60,6 +60,8 @@ GPUContextWebGPU::GPUContextWebGPU(GPUDeviceWebGPU* device)
GPUContextWebGPU::~GPUContextWebGPU()
{
if (Encoder)
Flush();
CHECK(Encoder == nullptr);
}
@@ -120,6 +122,10 @@ void GPUContextWebGPU::FrameEnd()
void GPUContextWebGPU::EventBegin(const Char* name)
{
// Cannot insert commands in encoder during render pass
if (_renderPass)
EndRenderPass();
StringAsANSI<> nameAnsi(name);
wgpuCommandEncoderPushDebugGroup(Encoder, { nameAnsi.Get(), (size_t)nameAnsi.Length() });
}
@@ -252,17 +258,19 @@ void GPUContextWebGPU::SetStencilRef(uint32 value)
void GPUContextWebGPU::ResetSR()
{
_bindGroupDirty = true;
Platform::MemoryClear(_shaderResources, sizeof(_shaderResources));
}
void GPUContextWebGPU::ResetUA()
{
_bindGroupDirty = true;
Platform::MemoryClear(_storageResources, sizeof(_storageResources));
}
void GPUContextWebGPU::ResetCB()
{
_bindGroupDirty = false;
_bindGroupDirty = true;
Platform::MemoryClear(_constantBuffers, sizeof(_constantBuffers));
}
@@ -425,14 +433,14 @@ void GPUContextWebGPU::EndQuery(uint64 queryID)
void GPUContextWebGPU::SetViewport(const Viewport& viewport)
{
_viewport = viewport;
if (_renderPass)
if (_renderPass && !_renderPassDirty)
wgpuRenderPassEncoderSetViewport(_renderPass, viewport.X, viewport.Y, viewport.Width, viewport.Height, viewport.MinDepth, viewport.MaxDepth);
}
void GPUContextWebGPU::SetScissor(const Rectangle& scissorRect)
{
_scissorRect = scissorRect;
if (_renderPass)
if (_renderPass && !_renderPassDirty)
wgpuRenderPassEncoderSetScissorRect(_renderPass, (uint32_t)scissorRect.GetX(), (uint32_t)scissorRect.GetY(), (uint32_t)scissorRect.GetWidth(), (uint32_t)scissorRect.GetHeight());
}
@@ -490,6 +498,7 @@ void GPUContextWebGPU::Flush()
WGPUCommandBufferDescriptor commandBufferDesc = WGPU_COMMAND_BUFFER_DESCRIPTOR_INIT;
WGPUCommandBuffer commandBuffer = wgpuCommandEncoderFinish(Encoder, &commandBufferDesc);
wgpuCommandEncoderRelease(Encoder);
Encoder = nullptr;
if (commandBuffer)
{
wgpuQueueSubmit(_device->Queue, 1, &commandBuffer);
@@ -567,8 +576,7 @@ void GPUContextWebGPU::CopyTexture(GPUTexture* dstResource, uint32 dstSubresourc
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);
ASSERT_LOW_LAYER(dstTextureWebGPU->Texture && srcTextureWebGPU->Texture);
const int32 srcMipIndex = srcSubresource % srcTextureWebGPU->MipLevels();
const int32 dstMipIndex = dstSubresource % srcTextureWebGPU->MipLevels();
@@ -578,18 +586,45 @@ void GPUContextWebGPU::CopyTexture(GPUTexture* dstResource, uint32 dstSubresourc
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.origin.z = srcArrayIndex;
srcInfo.aspect = WGPUTextureAspect_All;
WGPUTexelCopyTextureInfo dstInfo = WGPU_TEXEL_COPY_TEXTURE_INFO_INIT;
dstInfo.texture = dstTextureWebGPU->Texture;
dstInfo.mipLevel = dstMipIndex;
dstInfo.origin = { dstX, dstY, dstZ + dstArrayIndex };
dstInfo.aspect = WGPUTextureAspect_All;
WGPUExtent3D copySize = { (uint32_t)srcMipWidth, (uint32_t)srcMipHeight, (uint32_t)srcMipDepth };
wgpuCommandEncoderCopyTextureToTexture(Encoder, &srcInfo, &dstInfo, &copySize);
if (dstTextureWebGPU->Usage & WGPUTextureUsage_CopyDst && srcTextureWebGPU->Usage & WGPUTextureUsage_CopySrc)
{
// Direct copy
WGPUTexelCopyTextureInfo srcInfo = WGPU_TEXEL_COPY_TEXTURE_INFO_INIT;
srcInfo.texture = srcTextureWebGPU->Texture;
srcInfo.mipLevel = srcMipIndex;
srcInfo.origin.z = srcArrayIndex;
srcInfo.aspect = WGPUTextureAspect_All;
WGPUTexelCopyTextureInfo dstInfo = WGPU_TEXEL_COPY_TEXTURE_INFO_INIT;
dstInfo.texture = dstTextureWebGPU->Texture;
dstInfo.mipLevel = dstMipIndex;
dstInfo.origin = { dstX, dstY, dstZ + dstArrayIndex };
dstInfo.aspect = WGPUTextureAspect_All;
WGPUExtent3D copySize = { (uint32_t)srcMipWidth, (uint32_t)srcMipHeight, (uint32_t)srcMipDepth };
wgpuCommandEncoderCopyTextureToTexture(Encoder, &srcInfo, &dstInfo, &copySize);
}
else if (dstTextureWebGPU->Usage & WGPUTextureUsage_RenderAttachment && srcTextureWebGPU->Usage & WGPUTextureUsage_TextureBinding)
{
// Copy via drawing
ResetRenderTarget();
SetViewportAndScissors(srcMipWidth, srcMipHeight);
SetState(_device->GetCopyLinearPS());
if (srcSubresource == 0 && dstSubresource == 0)
{
SetRenderTarget(dstTextureWebGPU->View(0));
BindSR(0, srcTextureWebGPU->View(0));
}
else
{
ASSERT(dstTextureWebGPU->HasPerMipViews() && srcResource->HasPerMipViews());
SetRenderTarget(dstTextureWebGPU->View(dstArrayIndex, dstMipIndex));
BindSR(0, srcTextureWebGPU->View(srcArrayIndex, srcMipIndex));
}
DrawFullscreenTriangle();
}
else
{
LOG(Fatal, "Cannot copy texture {} to {}", srcTextureWebGPU->GetDescription().ToString(), dstTextureWebGPU->GetDescription().ToString());
}
}
void GPUContextWebGPU::ResetCounter(GPUBuffer* buffer)
@@ -615,9 +650,15 @@ void GPUContextWebGPU::CopyResource(GPUResource* dstResource, GPUResource* srcRe
{
// 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);
ASSERT(srcTexture->ArraySize() == dstTexture->ArraySize());
for (int32 arraySlice = 0; arraySlice < srcTexture->ArraySize(); arraySlice++)
{
for (int32 mipLevel = 0; mipLevel < srcTexture->MipLevels(); mipLevel++)
{
uint32 subresource = arraySlice * srcTexture->MipLevels() + mipLevel;
CopyTexture(dstTexture, subresource, 0, 0, 0, srcTexture, subresource);
}
}
}
else if (srcTexture)
{
@@ -751,7 +792,7 @@ void GPUContextWebGPU::OnDrawCall()
}
// Check if need to start a new render pass
if (_renderPassDirty)
if (_renderPassDirty || !_renderPass)
{
FlushRenderPass();
}
@@ -815,13 +856,14 @@ void GPUContextWebGPU::FlushRenderPass()
// Start a new render pass
WGPURenderPassColorAttachment colorAttachments[GPU_MAX_RT_BINDED];
WGPURenderPassDepthStencilAttachment depthStencilAttachment = WGPU_RENDER_PASS_DEPTH_STENCIL_ATTACHMENT_INIT;
WGPURenderPassDepthStencilAttachment depthStencilAttachment;
WGPURenderPassDescriptor renderPassDesc = WGPU_RENDER_PASS_DESCRIPTOR_INIT;
renderPassDesc.colorAttachmentCount = _renderTargetCount;
renderPassDesc.colorAttachments = colorAttachments;
PendingClear clear;
_pipelineKey.MultiSampleCount = 1;
_pipelineKey.RenderTargetCount = _renderTargetCount;
GPUTextureViewSizeWebGPU attachmentSize;
for (int32 i = 0; i < renderPassDesc.colorAttachmentCount; i++)
{
auto& colorAttachment = colorAttachments[i];
@@ -838,43 +880,54 @@ void GPUContextWebGPU::FlushRenderPass()
}
_pipelineKey.MultiSampleCount = (int32)renderTarget->GetMSAA();
_pipelineKey.RenderTargetFormats[i] = renderTarget->Format;
attachmentSize.Set(renderTarget->RenderSize);
}
if (_depthStencil)
{
auto renderTarget = _depthStencil;
renderPassDesc.depthStencilAttachment = &depthStencilAttachment;
depthStencilAttachment.view = _depthStencil->ViewRender;
depthStencilAttachment.depthLoadOp = WGPULoadOp_Load;
depthStencilAttachment.depthStoreOp = _depthStencil->ReadOnly ? WGPUStoreOp_Discard : WGPUStoreOp_Store;
depthStencilAttachment.depthReadOnly = _depthStencil->ReadOnly;
if (_depthStencil->HasStencil)
depthStencilAttachment = WGPU_RENDER_PASS_DEPTH_STENCIL_ATTACHMENT_INIT;
depthStencilAttachment.view = renderTarget->ViewRender;
depthStencilAttachment.depthLoadOp = renderTarget->ReadOnly ? WGPULoadOp_Undefined : WGPULoadOp_Load;
depthStencilAttachment.depthStoreOp = renderTarget->ReadOnly ? WGPUStoreOp_Undefined : WGPUStoreOp_Store;
depthStencilAttachment.depthReadOnly = renderTarget->ReadOnly;
if (renderTarget->HasStencil)
{
depthStencilAttachment.stencilLoadOp = WGPULoadOp_Load;
depthStencilAttachment.stencilStoreOp = _depthStencil->ReadOnly ? WGPUStoreOp_Discard : WGPUStoreOp_Store;
depthStencilAttachment.depthReadOnly = _depthStencil->ReadOnly;
depthStencilAttachment.stencilLoadOp = renderTarget->ReadOnly ? WGPULoadOp_Undefined : WGPULoadOp_Load;
depthStencilAttachment.stencilStoreOp = renderTarget->ReadOnly ? WGPUStoreOp_Undefined : WGPUStoreOp_Store;
depthStencilAttachment.depthReadOnly = renderTarget->ReadOnly;
depthStencilAttachment.stencilReadOnly = renderTarget->ReadOnly;
}
else
{
depthStencilAttachment.stencilClearValue = 0;
depthStencilAttachment.stencilLoadOp = WGPULoadOp_Clear;
depthStencilAttachment.stencilStoreOp = WGPUStoreOp_Discard;
depthStencilAttachment.stencilLoadOp = WGPULoadOp_Undefined;
depthStencilAttachment.stencilStoreOp = WGPUStoreOp_Undefined;
depthStencilAttachment.stencilReadOnly = true;
}
if (FindClear(_depthStencil, clear))
if (!renderTarget->ReadOnly && FindClear(renderTarget, clear))
{
depthStencilAttachment.depthLoadOp = WGPULoadOp_Clear;
depthStencilAttachment.depthClearValue = clear.Depth;
if (_depthStencil->HasStencil)
if (renderTarget->HasStencil)
{
depthStencilAttachment.stencilLoadOp = WGPULoadOp_Clear;
depthStencilAttachment.stencilClearValue = clear.Stencil;
}
}
_pipelineKey.DepthStencilFormat = _depthStencil->Format;
else
{
depthStencilAttachment.depthClearValue = 0.0f;
depthStencilAttachment.stencilClearValue = 0;
}
_pipelineKey.DepthStencilFormat = renderTarget->Format;
attachmentSize.Set(renderTarget->RenderSize);
}
else
{
_pipelineKey.DepthStencilFormat = WGPUTextureFormat_Undefined;
}
ASSERT(attachmentSize.Packed != 0);
_renderPass = wgpuCommandEncoderBeginRenderPass(Encoder, &renderPassDesc);
ASSERT(_renderPass);
@@ -885,11 +938,11 @@ void GPUContextWebGPU::FlushRenderPass()
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());
if (scissorRect != Rectangle(0, 0, attachmentSize.Width, attachmentSize.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);
if (viewport != Viewport(Float2(attachmentSize.Width, attachmentSize.Height)))
wgpuRenderPassEncoderSetViewport(_renderPass, viewport.X, viewport.Y, viewport.Width, viewport.Height, viewport.MinDepth, viewport.MaxDepth);
// Auto-dirty pipeline when new render pass starts
if (_pipelineState)
@@ -937,6 +990,7 @@ void GPUContextWebGPU::FlushBindGroup()
break;
}
case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
{
ASSERT_LOW_LAYER(descriptor.BindingType == SpirvShaderResourceBindingType::SRV);
auto view = _shaderResources[descriptor.Slot];
@@ -952,7 +1006,19 @@ void GPUContextWebGPU::FlushBindGroup()
LOG(Error, "Missing resource {} at slot {} of binding space {}", (int32)descriptor.ResourceType, descriptor.Slot, (int32)descriptor.BindingType);
CRASH;
}
view = defaultTexture->View(0);
switch (descriptor.ResourceType)
{
case SpirvShaderResourceType::Texture3D:
view = defaultTexture->ViewVolume();
break;
case SpirvShaderResourceType::Texture1DArray:
case SpirvShaderResourceType::Texture2DArray:
view = defaultTexture->ViewArray();
break;
default:
view = defaultTexture->View(0);
break;
}
ptr = (GPUResourceViewPtrWebGPU*)view->GetNativePtr();
entry.textureView = ptr->TextureView->View;
}
@@ -976,6 +1042,7 @@ void GPUContextWebGPU::FlushBindGroup()
}
break;
}
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
{
GPUConstantBufferWebGPU* uniform = _constantBuffers[descriptor.Slot];
@@ -983,20 +1050,10 @@ void GPUContextWebGPU::FlushBindGroup()
{
entry.buffer = uniform->Allocation.Buffer;
entry.size = uniform->AllocationSize;
_dynamicOffsets.Add(uniform->Allocation.Offset);
}
else
CRASH; // TODO: add dummy buffer as fallback
break;
}
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
{
GPUConstantBufferWebGPU* uniform = _constantBuffers[descriptor.Slot];
if (uniform && uniform->Allocation.Buffer)
{
entry.buffer = uniform->Allocation.Buffer;
entry.offset = uniform->Allocation.Offset;
entry.size = uniform->AllocationSize;
if (descriptor.DescriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER)
entry.offset = uniform->Allocation.Offset;
else
_dynamicOffsets.Add(uniform->Allocation.Offset);
}
else
CRASH; // TODO: add dummy buffer as fallback
@@ -1015,9 +1072,21 @@ void GPUContextWebGPU::FlushBindGroup()
// Create a bind group
bindGroupDesc.entryCount = _bindGroupEntries.Count();
bindGroupDesc.entries = entriesPtr;
#if BUILD_DEBUG
for (int32 i = 0; i < bindGroupDesc.entryCount; i++)
{
auto& e = bindGroupDesc.entries[i];
if ((e.buffer != nullptr) + (e.sampler != nullptr) + (e.textureView != nullptr) != 1)
{
LOG(Error, "Invalid binding in group {} at index {} ({})", groupIndex, i, _pipelineState->GetName());
LOG(Error, " > sampler: {}", (uint32)e.sampler);
LOG(Error, " > textureView: {}", (uint32)e.textureView);
LOG(Error, " > buffer: {}", (uint32)e.buffer);
}
}
#endif
WGPUBindGroup bindGroup = wgpuDeviceCreateBindGroup(_device->Device, &bindGroupDesc);
_unusedBindGroups.Add(bindGroup);
// TODO: cache and release them
// Bind group
wgpuRenderPassEncoderSetBindGroup(_renderPass, groupIndex, bindGroup, _dynamicOffsets.Count(), _dynamicOffsets.Get());

View File

@@ -28,22 +28,6 @@ GPUVertexLayoutWebGPU::GPUVertexLayoutWebGPU(GPUDeviceWebGPU* device, const Elem
: 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;
if (src.PerInstance)
Layout.stepMode = WGPUVertexStepMode_Instance;
}
}
GPUDataUploaderWebGPU::Allocation GPUDataUploaderWebGPU::Allocate(uint32 size, WGPUBufferUsage usage, uint32 alignment)
@@ -183,6 +167,7 @@ bool GPUDeviceWebGPU::Init()
if (wgpuAdapterGetLimits(Adapter->Adapter, &limits) == WGPUStatus_Success)
{
MinUniformBufferOffsetAlignment = limits.minUniformBufferOffsetAlignment;
Limits.HasDrawIndirect = true;
Limits.HasDepthAsSRV = true;
Limits.HasReadOnlyDepth = true;
Limits.HasDepthClip = features.Contains(WGPUFeatureName_DepthClipControl);
@@ -431,6 +416,9 @@ bool GPUDeviceWebGPU::Init()
{
LOG(Info, "WebGPU: {}", WEBGPU_TO_STR(message));
}
static int32 LogSpamLeft = 20;
if (LogSpamLeft-- < 0)
CRASH; // Too many errors
#endif
};

View File

@@ -11,6 +11,8 @@
#include "Engine/Profiler/ProfilerMemory.h"
#include "Engine/Graphics/PixelFormatExtensions.h"
#define WEBGPU_LOG_PSO 0
WGPUCompareFunction ToCompareFunction(ComparisonFunc value)
{
switch (value)
@@ -158,10 +160,9 @@ void GPUPipelineStateWebGPU::OnReleaseGPU()
uint32 GetHash(const GPUPipelineStateWebGPU::Key& key)
{
static_assert(sizeof(GPUPipelineStateWebGPU::Key) == sizeof(uint64) * 3, "Invalid PSO key size.");
static_assert(sizeof(GPUPipelineStateWebGPU::Key) == sizeof(uint64) * 2, "Invalid PSO key size.");
uint32 hash = GetHash(key.Packed[0]);
CombineHash(hash, GetHash(key.Packed[1]));
CombineHash(hash, GetHash(key.Packed[2]));
return hash;
}
@@ -175,6 +176,9 @@ WGPURenderPipeline GPUPipelineStateWebGPU::GetPipeline(const Key& key, GPUResour
#if GPU_ENABLE_RESOURCE_NAMING
ZoneText(_debugName.Get(), _debugName.Count() - 1);
#endif
#if WEBGPU_LOG_PSO
LOG(Info, "[WebGPU] GetPipeline: '{}'", String(_debugName.Get(), _debugName.Count() - 1));
#endif
// Lazy-init layout (cannot do it during Init as texture samplers that access eg. depth need to explicitly use UnfilterableFloat)
if (!PipelineDesc.layout)
@@ -182,10 +186,14 @@ WGPURenderPipeline GPUPipelineStateWebGPU::GetPipeline(const Key& key, GPUResour
// Build final pipeline description
_depthStencilDesc.format = (WGPUTextureFormat)key.DepthStencilFormat;
PipelineDesc.depthStencil = key.DepthStencilFormat ? &_depthStencilDesc : nullptr; // Unbind depth stencil state when no debug buffer is bound
PipelineDesc.multisample.count = key.MultiSampleCount;
_fragmentDesc.targetCount = key.RenderTargetCount;
for (int32 i = 0; i < _fragmentDesc.targetCount; i++)
_colorTargets[i].format = (WGPUTextureFormat)key.RenderTargetFormats[i];
if (PS)
{
_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];
if (key.VertexLayout)
{
@@ -253,6 +261,10 @@ WGPURenderPipeline GPUPipelineStateWebGPU::GetPipeline(const Key& key, GPUResour
void GPUPipelineStateWebGPU::InitLayout(GPUResourceView* shaderResources[GPU_MAX_SR_BINDED])
{
#if WEBGPU_LOG_PSO
// Debug log for PSOs with specific name
const bool log = true;// StringAnsiView(_debugName.Get(), _debugName.Count() - 1).Contains("PS_HalfDepth");
#endif
// Count the biggest bind group entries (for all shaders) to allocate reused memory
int32 maxEntriesCount = 0;
@@ -276,6 +288,11 @@ void GPUPipelineStateWebGPU::InitLayout(GPUResourceView* shaderResources[GPU_MAX
int32 entriesCount = descriptors->DescriptorTypesCount;
Platform::MemoryClear(entries.Get(), sizeof(WGPUBindGroupLayoutEntry) * entriesCount);
auto visibility = groupIndex == 0 ? WGPUShaderStage_Vertex : WGPUShaderStage_Fragment;
#if WEBGPU_LOG_PSO
if (log)
LOG(Info, " > group {} - {}", groupIndex, groupIndex == 0 ? TEXT("Vertex") : TEXT("Fragment"));
const Char* samplerType = TEXT("?");
#endif
for (int32 index = 0; index < entriesCount; index++)
{
auto& descriptor = descriptors->DescriptorTypes[index];
@@ -287,16 +304,87 @@ void GPUPipelineStateWebGPU::InitLayout(GPUResourceView* shaderResources[GPU_MAX
{
case VK_DESCRIPTOR_TYPE_SAMPLER:
entry.sampler.type = WGPUSamplerBindingType_Undefined;
if (descriptor.Slot == 4 || descriptor.Slot == 5) // Hack for ShadowSampler and ShadowSamplerLinear (this could get binded samplers table just like for shaderResources)
entry.sampler.type = WGPUSamplerBindingType_Comparison;
#if WEBGPU_LOG_PSO
switch (entry.sampler.type)
{
case WGPUSamplerBindingType_BindingNotUsed:
samplerType = TEXT("BindingNotUsed");
break;
case WGPUSamplerBindingType_Undefined:
samplerType = TEXT("Undefined");
break;
case WGPUSamplerBindingType_Filtering:
samplerType = TEXT("Filtering");
break;
case WGPUSamplerBindingType_NonFiltering:
samplerType = TEXT("NonFiltering");
break;
case WGPUSamplerBindingType_Comparison:
samplerType = TEXT("Comparison");
break;
}
if (log)
LOG(Info, " > [{}] sampler ({})", entry.binding, samplerType);
#endif
break;
case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
entry.texture.sampleType = WGPUTextureSampleType_Undefined;
if (shaderResources[descriptor.Slot])
{
// Hack to use the sample type directly from the view which allows to fix incorrect Depth Buffer reading that allows only manual Load when UnfilterableFloat is used (see SAMPLE_RT_LOAD)
// Hack to use the sample type directly from the view which allows to fix incorrect Depth Buffer reading that allows only manual Load when UnfilterableFloat is used (see SAMPLE_RT_DEPTH)
auto ptr = (GPUResourceViewPtrWebGPU*)shaderResources[descriptor.Slot]->GetNativePtr();
if (ptr && ptr->TextureView)
entry.texture.sampleType = ptr->TextureView->SampleType;
}
#if WEBGPU_LOG_PSO
if (log)
{
switch (entry.texture.sampleType)
{
case WGPUTextureSampleType_BindingNotUsed:
samplerType = TEXT("BindingNotUsed");
break;
case WGPUTextureSampleType_Undefined:
samplerType = TEXT("Undefined");
break;
case WGPUTextureSampleType_Float:
samplerType = TEXT("Float");
break;
case WGPUTextureSampleType_UnfilterableFloat:
samplerType = TEXT("UnfilterableFloat");
break;
case WGPUTextureSampleType_Depth:
samplerType = TEXT("Depth");
break;
case WGPUTextureSampleType_Sint:
samplerType = TEXT("Sint");
break;
case WGPUTextureSampleType_Uint:
samplerType = TEXT("Uint");
break;
}
switch (descriptor.ResourceType)
{
case SpirvShaderResourceType::Texture1D:
LOG(Info, " > [{}] texture 1D ({})", entry.binding, samplerType);
break;
case SpirvShaderResourceType::Texture2D:
LOG(Info, " > [{}] texture 2D ({})", entry.binding, samplerType);
break;
case SpirvShaderResourceType::Texture3D:
LOG(Info, " > [{}] texture 3D ({})", entry.binding, samplerType);
break;
case SpirvShaderResourceType::TextureCube:
LOG(Info, " > [{}] texture Cube ({})", entry.binding, samplerType);
break;
case SpirvShaderResourceType::Texture2DArray:
LOG(Info, " > [{}] texture 2D array ({})", entry.binding, samplerType);
break;
}
}
#endif
switch (descriptor.ResourceType)
{
case SpirvShaderResourceType::Texture1D:
@@ -326,11 +414,19 @@ void GPUPipelineStateWebGPU::InitLayout(GPUResourceView* shaderResources[GPU_MAX
entry.buffer.type = WGPUBufferBindingType_ReadOnlyStorage;
else
entry.buffer.type = WGPUBufferBindingType_Storage;
#if WEBGPU_LOG_PSO
if (log)
LOG(Info, " > [{}] storage buffer (read-only = {}, dynamic = {})", entry.binding, entry.buffer.type == WGPUBufferBindingType_ReadOnlyStorage, entry.buffer.hasDynamicOffset);
#endif
break;
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
entry.buffer.hasDynamicOffset = true;
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
entry.buffer.type = WGPUBufferBindingType_Uniform;
#if WEBGPU_LOG_PSO
if (log)
LOG(Info, " > [{}] uniform buffer (dynamic = {})", entry.binding, entry.buffer.hasDynamicOffset);
#endif
break;
default:
#if GPU_ENABLE_DIAGNOSTICS
@@ -414,33 +510,33 @@ bool GPUPipelineStateWebGPU::Init(const Description& desc)
}
}
PipelineDesc.multisample.alphaToCoverageEnabled = desc.BlendMode.AlphaToCoverageEnable;
PipelineDesc.fragment = &_fragmentDesc;
_fragmentDesc = WGPU_FRAGMENT_STATE_INIT;
_fragmentDesc.targets = _colorTargets;
Platform::MemoryClear(&_colorTargets, sizeof(_colorTargets));
if (desc.BlendMode.BlendEnable)
{
_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);
for (auto& e : _colorTargets)
e.blend = &_blendState;
}
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;
}
if (desc.PS)
{
PipelineDesc.fragment = &_fragmentDesc;
_fragmentDesc = WGPU_FRAGMENT_STATE_INIT;
_fragmentDesc.targets = _colorTargets;
Platform::MemoryClear(&_colorTargets, sizeof(_colorTargets));
if (desc.BlendMode.BlendEnable)
{
_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);
for (auto& e : _colorTargets)
e.blend = &_blendState;
}
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;
}
uint16 outputsCount = desc.PS->GetBindings().OutputsCount;
for (uint16 rtIndex = 0; rtIndex < outputsCount; rtIndex++)
_colorTargets[rtIndex].writeMask = writeMask;

View File

@@ -28,7 +28,7 @@ public:
uint8 RenderTargetFormats[GPU_MAX_RT_BINDED];
class GPUVertexLayoutWebGPU* VertexLayout;
};
uint64 Packed[3];
uint64 Packed[2];
};
FORCE_INLINE bool operator==(const Key& other) const

View File

@@ -41,19 +41,19 @@ bool GPUSamplerWebGPU::OnInit()
switch (_desc.Filter)
{
case GPUSamplerFilter::Point:
samplerDesc.magFilter = samplerDesc.magFilter = WGPUFilterMode_Nearest;
samplerDesc.magFilter = samplerDesc.minFilter = WGPUFilterMode_Nearest;
samplerDesc.mipmapFilter = WGPUMipmapFilterMode_Nearest;
break;
case GPUSamplerFilter::Bilinear:
samplerDesc.magFilter = samplerDesc.magFilter = WGPUFilterMode_Linear;
samplerDesc.magFilter = samplerDesc.minFilter = WGPUFilterMode_Linear;
samplerDesc.mipmapFilter = WGPUMipmapFilterMode_Nearest;
break;
case GPUSamplerFilter::Trilinear:
samplerDesc.magFilter = samplerDesc.magFilter = WGPUFilterMode_Linear;
samplerDesc.magFilter = samplerDesc.minFilter = WGPUFilterMode_Linear;
samplerDesc.mipmapFilter = WGPUMipmapFilterMode_Linear;
break;
case GPUSamplerFilter::Anisotropic:
samplerDesc.magFilter = samplerDesc.magFilter = WGPUFilterMode_Linear;
samplerDesc.magFilter = samplerDesc.minFilter = WGPUFilterMode_Linear;
samplerDesc.mipmapFilter = WGPUMipmapFilterMode_Linear;
break;
}

View File

@@ -70,7 +70,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);
}
return &_surfaceView;
}

View File

@@ -32,12 +32,38 @@ WGPUTextureFormat DropStencil(WGPUTextureFormat format)
}
}
void GPUTextureViewWebGPU::Create(WGPUTexture texture, WGPUTextureViewDescriptor const* desc)
void SetWebGPUTextureViewSampler(GPUTextureView* view, uint32 samplerType)
{
((GPUTextureViewWebGPU*)view)->SampleType = (WGPUTextureSampleType)samplerType;
}
void GPUTextureViewWebGPU::Create(WGPUTexture texture, const WGPUTextureViewDescriptor& desc)
{
if (View)
wgpuTextureViewRelease(View);
Texture = texture;
View = wgpuTextureCreateView(texture, desc);
auto viewDesc = desc;
auto renderDesc = desc;
auto separateViews = false;
// Render views cannot have more than 1 mip levels count
if (desc.usage & WGPUTextureUsage_RenderAttachment && renderDesc.mipLevelCount > 1)
{
renderDesc.mipLevelCount = 1;
separateViews = true;
}
// Depth-stencil textures expose depth-only when binding to shaders (unless via custom _handleStencil view) so make separate ViewRender for rendering with all components
if (desc.aspect == WGPUTextureAspect_All && ::HasStencil(desc.format))
{
viewDesc.aspect = WGPUTextureAspect_DepthOnly;
viewDesc.format = DropStencil(viewDesc.format);
separateViews = true;
}
// Create views
View = wgpuTextureCreateView(texture, &viewDesc);
if (!View)
{
#if GPU_ENABLE_RESOURCE_NAMING
@@ -46,18 +72,13 @@ void GPUTextureViewWebGPU::Create(WGPUTexture texture, WGPUTextureViewDescriptor
LOG(Error, "Failed to create a view for texture");
#endif
}
ViewRender = View;
if (separateViews)
ViewRender = wgpuTextureCreateView(texture, &renderDesc);
else
ViewRender = View;
// Depth-stencil textures expose depth-only when binding to shaders (unless via custom _handleStencil view) so make separate ViewRender for rendering with all components
if (desc && desc->aspect == WGPUTextureAspect_All && ::HasStencil(desc->format))
{
auto depthOnlyDesc = *desc;
depthOnlyDesc.aspect = WGPUTextureAspect_DepthOnly;
depthOnlyDesc.format = DropStencil(depthOnlyDesc.format);
View = wgpuTextureCreateView(texture, &depthOnlyDesc);
}
Format = desc ? desc->format : wgpuTextureGetFormat(texture);
// Cache metadata
Format = desc.format;
switch (Format)
{
case WGPUTextureFormat_Depth16Unorm:
@@ -76,6 +97,8 @@ void GPUTextureViewWebGPU::Create(WGPUTexture texture, WGPUTextureViewDescriptor
SampleType = WGPUTextureSampleType_Undefined;
break;
}
RenderSize.Width = Math::Max<uint16>(wgpuTextureGetWidth(Texture) >> renderDesc.baseMipLevel, 1);
RenderSize.Height = Math::Max<uint16>(wgpuTextureGetHeight(Texture) >> renderDesc.baseMipLevel, 1);
}
void GPUTextureViewWebGPU::Release()
@@ -113,21 +136,22 @@ bool GPUTextureWebGPU::OnInit()
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;
textureDesc.size.depthOrArrayLayers = _desc.ArraySize;
break;
case TextureDimensions::VolumeTexture:
_viewDimension = WGPUTextureViewDimension_3D;
textureDesc.dimension = WGPUTextureDimension_3D;
textureDesc.size.depthOrArrayLayers = _desc.Depth;
break;
case TextureDimensions::CubeTexture:
_viewDimension = _desc.ArraySize > 6 ? WGPUTextureViewDimension_CubeArray : WGPUTextureViewDimension_Cube;
textureDesc.dimension = WGPUTextureDimension_2D;
textureDesc.size.depthOrArrayLayers *= 6; // Each cubemap uses 6 array slices
textureDesc.size.depthOrArrayLayers = _desc.ArraySize;
break;
}
textureDesc.format = RenderToolsWebGPU::ToTextureFormat(Format());
@@ -136,7 +160,7 @@ bool GPUTextureWebGPU::OnInit()
textureDesc.viewFormats = &textureDesc.format;
textureDesc.viewFormatCount = 1;
_format = textureDesc.format;
_usage = textureDesc.usage;
Usage = textureDesc.usage;
Texture = wgpuDeviceCreateTexture(_device->Device, &textureDesc);
if (!Texture)
return true;
@@ -179,7 +203,7 @@ 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.usage = Usage;
viewDesc.dimension = _viewDimension;
viewDesc.baseMipLevel = MipLevels() - ResidentMipLevels();
viewDesc.mipLevelCount = ResidentMipLevels();
@@ -188,7 +212,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);
}
void GPUTextureWebGPU::OnReleaseGPU()
@@ -220,7 +244,7 @@ void GPUTextureWebGPU::InitHandles()
viewDesc.label = { _name.Get(), (size_t)_name.Length() };
#endif
viewDesc.format = _format;
viewDesc.usage = _usage;
viewDesc.usage = Usage;
viewDesc.dimension = _viewDimension;
viewDesc.mipLevelCount = MipLevels();
viewDesc.arrayLayerCount = ArraySize();
@@ -235,7 +259,7 @@ void GPUTextureWebGPU::InitHandles()
{
auto& view = _handleVolume;
view.Init(this, format, msaa);
view.Create(Texture, &viewDesc);
view.Create(Texture, viewDesc);
}
// Init per slice views
@@ -249,7 +273,7 @@ void GPUTextureWebGPU::InitHandles()
//viewDesc.arrayLayerCount = 1;
auto& view = _handlesPerSlice[sliceIndex];
view.Init(this, format, msaa);
view.Create(Texture, &viewDesc);
view.Create(Texture, viewDesc);
view.DepthSlice = sliceIndex;
}
}
@@ -263,7 +287,7 @@ void GPUTextureWebGPU::InitHandles()
{
auto& view = _handleArray;
view.Init(this, format, msaa);
view.Create(Texture, &viewDesc);
view.Create(Texture, viewDesc);
}
// Create per array slice handles
@@ -275,7 +299,7 @@ void GPUTextureWebGPU::InitHandles()
viewDesc.arrayLayerCount = 1;
auto& view = _handlesPerSlice[arrayIndex];
view.Init(this, format, msaa);
view.Create(Texture, &viewDesc);
view.Create(Texture, viewDesc);
}
viewDesc.baseArrayLayer = 0;
viewDesc.arrayLayerCount = MipLevels();
@@ -287,7 +311,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);
}
// Init per mip map handles
@@ -308,7 +332,7 @@ void GPUTextureWebGPU::InitHandles()
auto& view = slice[mipIndex];
viewDesc.baseMipLevel = mipIndex;
view.Init(this, format, msaa);
view.Create(Texture, &viewDesc);
view.Create(Texture, viewDesc);
}
}
viewDesc.dimension = _viewDimension;
@@ -319,7 +343,7 @@ void GPUTextureWebGPU::InitHandles()
{
auto& view = _handleReadOnlyDepth;
view.Init(this, format, msaa);
view.Create(Texture, &viewDesc);
view.Create(Texture, viewDesc);
view.ReadOnly = true;
}
@@ -339,7 +363,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);
}
}

View File

@@ -9,6 +9,28 @@
#if GRAPHICS_API_WEBGPU
struct GPUTextureViewSizeWebGPU
{
union
{
struct
{
uint16 Width, Height;
};
uint32 Packed = 0;
};
FORCE_INLINE void Set(GPUTextureViewSizeWebGPU other)
{
if (Packed == 0)
Packed = other.Packed;
else
{
ASSERT(Packed == other.Packed);
}
}
};
/// <summary>
/// The texture view for Web GPU backend.
/// </summary>
@@ -36,13 +58,14 @@ public:
bool HasStencil = false;
bool ReadOnly = false;
uint32 DepthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
GPUTextureViewSizeWebGPU RenderSize;
WGPUTextureFormat Format = WGPUTextureFormat_Undefined;
WGPUTextureSampleType SampleType = WGPUTextureSampleType_Undefined;
GPUResourceViewPtrWebGPU Ptr;
public:
using GPUTextureView::Init;
void Create(WGPUTexture texture, WGPUTextureViewDescriptor const* desc = nullptr);
void Create(WGPUTexture texture, const WGPUTextureViewDescriptor& desc);
void Release();
public:
@@ -70,7 +93,6 @@ private:
#endif
WGPUTextureFormat _format = WGPUTextureFormat_Undefined;
WGPUTextureViewDimension _viewDimension = WGPUTextureViewDimension_Undefined;
WGPUTextureUsage _usage = 0;
public:
GPUTextureWebGPU(GPUDeviceWebGPU* device, const StringView& name)
@@ -81,6 +103,8 @@ public:
public:
// Handle to the WebGPU texture object.
WGPUTexture Texture = nullptr;
// Usage flags fo the created texture.
WGPUTextureUsage Usage = 0;
public:
// [GPUTexture]

View File

@@ -14,10 +14,6 @@ class GPUVertexLayoutWebGPU : public GPUResourceBase<GPUDeviceWebGPU, GPUVertexL
{
public:
GPUVertexLayoutWebGPU(GPUDeviceWebGPU* device, const Elements& elements, bool explicitOffsets);
public:
WGPUVertexBufferLayout Layout;
WGPUVertexAttribute Attributes[GPU_MAX_VS_ELEMENTS];
};
#endif