Skip to content

Commit

Permalink
WebGPU: reworked dynamic offsets handling (close #539)
Browse files Browse the repository at this point in the history
  • Loading branch information
TheMostDiligent committed Jul 30, 2024
1 parent 6db118b commit 9aa69de
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 93 deletions.
35 changes: 22 additions & 13 deletions Graphics/GraphicsEngineWebGPU/include/DeviceContextWebGPUImpl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -372,9 +372,6 @@ class DeviceContextWebGPUImpl final : public DeviceContextBase<EngineWebGPUImplT

WGPUBuffer PrepareForIndirectCommand(IBuffer* pAttribsBuffer, Uint64& IdirectBufferOffset);

template <PIPELINE_TYPE PipelineType, typename CmdEncoderType>
void CommitSRBs(CmdEncoderType CmdEncoder, Uint32 CommitSRBMask);

void CommitGraphicsPSO(WGPURenderPassEncoder CmdEncoder);

void CommitComputePSO(WGPUComputePassEncoder CmdEncoder);
Expand All @@ -388,7 +385,7 @@ class DeviceContextWebGPUImpl final : public DeviceContextBase<EngineWebGPUImplT
void CommitScissorRects(WGPURenderPassEncoder CmdEncoder);

template <typename CmdEncoderType>
void CommitBindGroups(CmdEncoderType CmdEncoder);
void CommitBindGroups(CmdEncoderType CmdEncoder, Uint32 CommitSRBMask);

UploadMemoryManagerWebGPU::Allocation AllocateUploadMemory(Uint64 Size, Uint64 Alignment = 16);

Expand Down Expand Up @@ -453,24 +450,36 @@ class DeviceContextWebGPUImpl final : public DeviceContextBase<EngineWebGPUImplT
{
struct BindGroupInfo
{
WGPUBindGroup wgpuBindGroup = nullptr;
std::vector<uint32_t> DynamicOffsets;
WGPUBindGroup wgpuBindGroup = nullptr;

// The total number of resources with dynamic offsets, given by pSignature->GetDynamicOffsetCount().
// Note that this is not the actual number of dynamic buffers in the resource cache.
Uint32 DynamicOffsetCount = 0;

// Bind index to use with wgpuEncoderSetBindGroup
Uint32 BindIndex = ~0u;

bool IsActive() const
{
return BindIndex != BindGroupInfo{}.BindIndex;
}
void MakeInactive()
{
BindIndex = BindGroupInfo{}.BindIndex;
}
};
// Bind groups for each resource signature.
// NOTE: bind groups in this array are not indexed by the bind group
// index in the pipeline layout, but by the resource signature index.
std::array<BindGroupInfo, PipelineStateWebGPUImpl::MaxBindGroupsInPipeline> BindGroups = {};

// Bind groups that are used by the current pipeline.
Uint32 ActiveBindGroups = 0;
Uint32 DirtyBindGroups = (1u << BindGroups.size()) - 1u;
std::array<std::array<BindGroupInfo, PipelineResourceSignatureWebGPUImpl::MAX_BIND_GROUPS>, MAX_RESOURCE_SIGNATURES> BindGroups = {};

void Reset()
{
*this = WebGPUResourceBindInfo{};
}
} m_BindInfo;

// Memory to store dynamic buffer offsets for wgpuEncoderSetBindGroup.
std::vector<Uint32> m_DynamicBufferOffsets;

struct PendingClears
{
using RenderTargetClearColors = std::array<float[4], MAX_RENDER_TARGETS>;
Expand Down
169 changes: 89 additions & 80 deletions Graphics/GraphicsEngineWebGPU/src/DeviceContextWebGPUImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,37 +147,55 @@ void DeviceContextWebGPUImpl::SetPipelineState(IPipelineState* pPipelineState)

m_EncoderState.Invalidate(WebGPUEncoderState::CMD_ENCODER_STATE_PIPELINE_STATE);


Uint32 DvpCompatibleSRBCount = 0;
PrepareCommittedResources(m_BindInfo, DvpCompatibleSRBCount);
// Commit all SRBs when PSO changes
m_BindInfo.StaleSRBMask |= m_BindInfo.ActiveSRBMask;

const Uint32 SignatureCount = m_pPipelineState->GetResourceSignatureCount();

Uint32 DbgActiveBindGroupIndex = 0;
m_BindInfo.ActiveBindGroups = 0;
Uint32 ActiveBindGroupIndex = 0;
Uint32 MaxDynamicOffsetCount = 0;
for (Uint32 i = 0; i < SignatureCount; ++i)
{
PipelineResourceSignatureWebGPUImpl* pSign = m_pPipelineState->GetResourceSignature(i);
if (pSign == nullptr)
if (pSign == nullptr || pSign->GetNumBindGroups() == 0)
{
for (WebGPUResourceBindInfo::BindGroupInfo& BindGroup : m_BindInfo.BindGroups[i])
{
// Make the group inactive, but do not reset wgpuBindGroup - it might be used by a PSO that is set later
BindGroup.MakeInactive();
}
continue;
}

VERIFY(m_pPipelineState->GetPipelineLayout().GetFirstBindGroupIndex(i) == DbgActiveBindGroupIndex, "Bind group index mismatch");
Uint32 BGIndex = i * PipelineResourceSignatureWebGPUImpl::MAX_BIND_GROUPS;
VERIFY_EXPR(i == pSign->GetDesc().BindingIndex);
VERIFY_EXPR(m_BindInfo.ActiveSRBMask & (1u << i));

VERIFY(m_pPipelineState->GetPipelineLayout().GetFirstBindGroupIndex(i) == ActiveBindGroupIndex, "Bind group index mismatch");
for (PipelineResourceSignatureWebGPUImpl::BIND_GROUP_ID BindGroupId : {PipelineResourceSignatureWebGPUImpl::BIND_GROUP_ID_STATIC_MUTABLE,
PipelineResourceSignatureWebGPUImpl::BIND_GROUP_ID_DYNAMIC})
{
WebGPUResourceBindInfo::BindGroupInfo& BindGroup = m_BindInfo.BindGroups[i][BindGroupId];
if (pSign->HasBindGroup(BindGroupId))
{
// Reserve space for dynamic offsets
m_BindInfo.BindGroups[BGIndex].DynamicOffsets.resize(pSign->GetDynamicOffsetCount(BindGroupId));
m_BindInfo.ActiveBindGroups |= 1u << (BGIndex++);
++DbgActiveBindGroupIndex;
const Uint32 DynamicOffsetCount = pSign->GetDynamicOffsetCount(BindGroupId);
MaxDynamicOffsetCount = std::max(MaxDynamicOffsetCount, DynamicOffsetCount);

BindGroup.DynamicOffsetCount = DynamicOffsetCount;
BindGroup.BindIndex = ActiveBindGroupIndex++;
}
else
{
// Make the group inactive, but do not reset wgpuBindGroup - it might be used by a PSO that is set later
BindGroup.MakeInactive();
}
}
}
VERIFY(m_pPipelineState->GetPipelineLayout().GetBindGroupCount() == DbgActiveBindGroupIndex, "Bind group count mismatch");
(void)DbgActiveBindGroupIndex;

m_BindInfo.DirtyBindGroups = m_BindInfo.ActiveBindGroups;
VERIFY(m_pPipelineState->GetPipelineLayout().GetBindGroupCount() == ActiveBindGroupIndex, "Bind group count mismatch");

Uint32 DvpCompatibleSRBCount = 0;
PrepareCommittedResources(m_BindInfo, DvpCompatibleSRBCount);
m_DynamicBufferOffsets.resize(MaxDynamicOffsetCount);
}

void DeviceContextWebGPUImpl::TransitionShaderResources(IShaderResourceBinding* pShaderResourceBinding)
Expand All @@ -201,12 +219,15 @@ void DeviceContextWebGPUImpl::DvpValidateCommittedShaderResources()
if (pSign == nullptr || pSign->GetNumBindGroups() == 0)
continue; // Skip null and empty signatures

const Uint32 BGCount = pSign->GetNumBindGroups();
for (Uint32 bg = 0; bg < BGCount; ++bg)
VERIFY(i == pSign->GetDesc().BindingIndex, "Resource signature index mismatch");
for (PipelineResourceSignatureWebGPUImpl::BIND_GROUP_ID BindGroupId : {PipelineResourceSignatureWebGPUImpl::BIND_GROUP_ID_STATIC_MUTABLE,
PipelineResourceSignatureWebGPUImpl::BIND_GROUP_ID_DYNAMIC})
{
DEV_CHECK_ERR(m_BindInfo.BindGroups[i * 2 + bg].wgpuBindGroup,
"bind group with index ", bg, " is not bound for resource signature '",
pSign->GetDesc().Name, "', binding index ", i, ".");
const WebGPUResourceBindInfo::BindGroupInfo& BindGroup = m_BindInfo.BindGroups[i][BindGroupId];
DEV_CHECK_ERR(BindGroup.IsActive() == pSign->HasBindGroup(BindGroupId),
"Active bind group flag mismatch for resource signature '", pSign->GetDesc().Name, "', binding index ", i, ", bind group id ", BindGroupId, ".");
DEV_CHECK_ERR(!BindGroup.IsActive() || BindGroup.wgpuBindGroup != nullptr,
"Bind group is not initialized for resource signature '", pSign->GetDesc().Name, "', binding index ", i, ", bind group id ", BindGroupId, ".");
}
}

Expand Down Expand Up @@ -243,15 +264,17 @@ void DeviceContextWebGPUImpl::CommitShaderResources(IShaderResourceBinding*
for (PipelineResourceSignatureWebGPUImpl::BIND_GROUP_ID BindGroupId : {PipelineResourceSignatureWebGPUImpl::BIND_GROUP_ID_STATIC_MUTABLE,
PipelineResourceSignatureWebGPUImpl::BIND_GROUP_ID_DYNAMIC})
{
WebGPUResourceBindInfo::BindGroupInfo& BindGroup = m_BindInfo.BindGroups[SRBIndex][BindGroupId];
if (pSignature->HasBindGroup(BindGroupId))
{
m_BindInfo.BindGroups[SRBIndex * 2 + BGIndex].wgpuBindGroup =
ResourceCache.UpdateBindGroup(wgpuDevice, BGIndex, pSignature->GetWGPUBindGroupLayout(BindGroupId));
m_BindInfo.DirtyBindGroups |= 1u << (SRBIndex * 2 + BGIndex);
BindGroup.wgpuBindGroup = ResourceCache.UpdateBindGroup(wgpuDevice, BGIndex, pSignature->GetWGPUBindGroupLayout(BindGroupId));
++BGIndex;
}
else
{
BindGroup.wgpuBindGroup = nullptr;
}
}

VERIFY_EXPR(BGIndex == ResourceCache.GetNumBindGroups());
}

Expand All @@ -265,49 +288,47 @@ void SetBindGroup(WGPUComputePassEncoder Encoder, uint32_t GroupIndex, WGPUBindG
}

template <typename CmdEncoderType>
void DeviceContextWebGPUImpl::CommitBindGroups(CmdEncoderType CmdEncoder)
{
// Bind groups in m_EncoderState.BindGroups are indexed by SRB index rather than bind group index
// in the pipeline layout.
m_BindInfo.DirtyBindGroups &= m_BindInfo.ActiveBindGroups;
Uint32 DirtyBindGroups = m_BindInfo.DirtyBindGroups;
while (m_BindInfo.DirtyBindGroups != 0)
{
// m_BindInfo.DirtyBindGroups is indexed by SRB index
const Uint32 SrcBindGroupIndex = PlatformMisc::GetLSB(m_BindInfo.DirtyBindGroups);
// Count the number of active groups that are bound before the current group.
// Note that there may be inactive groups if, for example, signature at preceding index
// does not use dynamic resources.
const Uint32 BindGroupIndex = PlatformMisc::CountOneBits(m_BindInfo.ActiveBindGroups & ((1u << SrcBindGroupIndex) - 1u));
VERIFY_EXPR(BindGroupIndex < m_BindInfo.BindGroups.size());
WebGPUResourceBindInfo::BindGroupInfo& BindGroup = m_BindInfo.BindGroups[SrcBindGroupIndex];

// There are two bind groups per SRB: one for static/mutable and one for dynamic resources
const Uint32 SRBIndex = SrcBindGroupIndex / 2;
const Uint32 BindGroupCacheIndex = SrcBindGroupIndex & 0x01u;

const ShaderResourceCacheWebGPU* ResourceCache = m_BindInfo.ResourceCaches[SRBIndex];
VERIFY_EXPR(ResourceCache != nullptr);
std::vector<uint32_t>& DynamicOffsets = BindGroup.DynamicOffsets;
const Uint32 NumOffsetsWritten = ResourceCache->GetDynamicBufferOffsets(GetContextId(), DynamicOffsets, BindGroupCacheIndex);
VERIFY(NumOffsetsWritten == DynamicOffsets.size(),
"The number of dynamic offsets written (", NumOffsetsWritten, ") does not match the expected number (", DynamicOffsets.size(),
"). This likely indicates mismatch between the SRB and the PSO");
(void)NumOffsetsWritten;

if (WGPUBindGroup wgpuBindGroup = BindGroup.wgpuBindGroup)
{
SetBindGroup(CmdEncoder, BindGroupIndex, wgpuBindGroup, DynamicOffsets.size(), DynamicOffsets.data());
}
else
void DeviceContextWebGPUImpl::CommitBindGroups(CmdEncoderType CmdEncoder, Uint32 CommitSRBMask)
{
VERIFY(CommitSRBMask != 0, "This method should not be called when there is nothing to commit");

const Uint32 FirstSign = PlatformMisc::GetLSB(CommitSRBMask);
const Uint32 LastSign = PlatformMisc::GetMSB(CommitSRBMask);
VERIFY_EXPR(LastSign < m_pPipelineState->GetResourceSignatureCount());

for (Uint32 sign = FirstSign; sign <= LastSign; ++sign)
{
Uint32 BindGroupCacheIndex = 0;
for (PipelineResourceSignatureWebGPUImpl::BIND_GROUP_ID BindGroupId : {PipelineResourceSignatureWebGPUImpl::BIND_GROUP_ID_STATIC_MUTABLE,
PipelineResourceSignatureWebGPUImpl::BIND_GROUP_ID_DYNAMIC})
{
DEV_ERROR("Active bind group at index ", SrcBindGroupIndex, " is not initialized");
const WebGPUResourceBindInfo::BindGroupInfo& BindGroup = m_BindInfo.BindGroups[sign][BindGroupId];
if (!BindGroup.IsActive())
continue;

const ShaderResourceCacheWebGPU* ResourceCache = m_BindInfo.ResourceCaches[sign];
VERIFY_EXPR(ResourceCache != nullptr);
const Uint32 NumOffsetsWritten = ResourceCache->GetDynamicBufferOffsets(GetContextId(), m_DynamicBufferOffsets, BindGroupCacheIndex++);
VERIFY(NumOffsetsWritten == BindGroup.DynamicOffsetCount,
"The number of dynamic offsets written (", NumOffsetsWritten, ") does not match the expected number (", BindGroup.DynamicOffsetCount,
"). This likely indicates mismatch between the SRB and the PSO");
(void)NumOffsetsWritten;

if (WGPUBindGroup wgpuBindGroup = BindGroup.wgpuBindGroup)
{
SetBindGroup(CmdEncoder, BindGroup.BindIndex, wgpuBindGroup, BindGroup.DynamicOffsetCount, m_DynamicBufferOffsets.data());
}
else
{
DEV_ERROR("Active bind group at index ", BindGroup.BindIndex, " is not initialized");
}
}
m_BindInfo.DirtyBindGroups &= ~(1u << SrcBindGroupIndex);
}

// TEMPORARY: always commit bind groups until we track dynamic offsets
m_BindInfo.DirtyBindGroups = DirtyBindGroups;
// Note that there is one global dynamic buffer from which all dynamic resources are suballocated,
// and this buffer is not resizable, so the buffer handle can never change.

m_BindInfo.StaleSRBMask &= ~m_BindInfo.ActiveSRBMask;
}

void DeviceContextWebGPUImpl::InvalidateState()
Expand Down Expand Up @@ -1409,7 +1430,7 @@ void DeviceContextWebGPUImpl::GenerateMips(ITextureView* pTexView)
if (m_pPipelineState)
{
m_EncoderState.Invalidate(WebGPUEncoderState::CMD_ENCODER_STATE_ALL);
m_BindInfo.DirtyBindGroups = m_BindInfo.ActiveBindGroups;
m_BindInfo.StaleSRBMask |= m_BindInfo.ActiveSRBMask;
}

auto& MipGenerator = m_pDevice->GetMipsGenerator();
Expand Down Expand Up @@ -1845,7 +1866,7 @@ void DeviceContextWebGPUImpl::CommitSubpassRenderTargets()
void DeviceContextWebGPUImpl::ClearEncoderState()
{
m_EncoderState.Clear();
m_BindInfo.DirtyBindGroups = m_BindInfo.ActiveBindGroups;
m_BindInfo.StaleSRBMask |= m_BindInfo.ActiveSRBMask;
}

void DeviceContextWebGPUImpl::ClearAttachment(Int32 RTIndex,
Expand Down Expand Up @@ -1895,12 +1916,6 @@ void DeviceContextWebGPUImpl::ClearAttachment(Int32 RTIndex,
m_EncoderState.Invalidate(WebGPUEncoderState::CMD_ENCODER_STATE_PIPELINE_STATE);
}

template <PIPELINE_TYPE, typename CmdEncoderType>
void DeviceContextWebGPUImpl::CommitSRBs(CmdEncoderType CmdEncoder, Uint32 CommitSRBMask)
{
// TODO
}

WGPURenderPassEncoder DeviceContextWebGPUImpl::PrepareForDraw(DRAW_FLAGS Flags)
{
#ifdef DILIGENT_DEVELOPMENT
Expand All @@ -1915,9 +1930,6 @@ WGPURenderPassEncoder DeviceContextWebGPUImpl::PrepareForDraw(DRAW_FLAGS Flags)
if (!m_EncoderState.IsUpToDate(WebGPUEncoderState::CMD_ENCODER_STATE_PIPELINE_STATE))
CommitGraphicsPSO(wgpuRenderCmdEncoder);

if (auto CommitSRBMask = m_BindInfo.GetCommitMask(Flags & DRAW_FLAG_DYNAMIC_RESOURCE_BUFFERS_INTACT))
CommitSRBs<PIPELINE_TYPE_GRAPHICS>(wgpuRenderCmdEncoder, CommitSRBMask);

if (!m_EncoderState.IsUpToDate(WebGPUEncoderState::CMD_ENCODER_STATE_VERTEX_BUFFERS) || (m_EncoderState.HasDynamicVertexBuffers && (Flags & DRAW_FLAG_DYNAMIC_RESOURCE_BUFFERS_INTACT) == 0))
CommitVertexBuffers(wgpuRenderCmdEncoder);

Expand Down Expand Up @@ -1945,8 +1957,8 @@ WGPURenderPassEncoder DeviceContextWebGPUImpl::PrepareForDraw(DRAW_FLAGS Flags)
m_EncoderState.SetUpToDate(WebGPUEncoderState::CMD_ENCODER_STATE_STENCIL_REF);
}

if (m_BindInfo.DirtyBindGroups != 0)
CommitBindGroups(wgpuRenderCmdEncoder);
if (auto CommitSRBMask = m_BindInfo.GetCommitMask(Flags & DRAW_FLAG_DYNAMIC_RESOURCE_BUFFERS_INTACT))
CommitBindGroups(wgpuRenderCmdEncoder, CommitSRBMask);

return wgpuRenderCmdEncoder;
}
Expand All @@ -1973,10 +1985,7 @@ WGPUComputePassEncoder DeviceContextWebGPUImpl::PrepareForDispatchCompute()
CommitComputePSO(wgpuComputeCmdEncoder);

if (auto CommitSRBMask = m_BindInfo.GetCommitMask())
CommitSRBs<PIPELINE_TYPE_COMPUTE>(wgpuComputeCmdEncoder, CommitSRBMask);

if (m_BindInfo.DirtyBindGroups != 0)
CommitBindGroups(wgpuComputeCmdEncoder);
CommitBindGroups(wgpuComputeCmdEncoder, CommitSRBMask);

return wgpuComputeCmdEncoder;
}
Expand Down

0 comments on commit 9aa69de

Please sign in to comment.