Files
UnrealEngine/Engine/Source/Runtime/D3D12RHI/Private/D3D12VertexDeclaration.cpp
2025-05-18 13:04:45 +08:00

222 lines
9.1 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
D3D12VertexDeclaration.cpp: D3D vertex declaration RHI implementation.
=============================================================================*/
#include "D3D12RHIPrivate.h"
/**
* Key used to look up vertex declarations in the cache.
*/
struct FD3D12VertexDeclarationKey
{
/** Vertex elements in the declaration. */
FD3D12VertexElements VertexElements;
/** Hash of the vertex elements. */
uint32 Hash;
/** Hash of the vertex elements, without strides. */
uint32 HashNoStrides;
uint16 StreamStrides[MaxVertexElementCount];
/** Initialization constructor. */
explicit FD3D12VertexDeclarationKey(const FVertexDeclarationElementList& InElements)
{
uint16 UsedStreamsMask = 0;
FMemory::Memzero(StreamStrides);
for (int32 ElementIndex = 0; ElementIndex < InElements.Num(); ElementIndex++)
{
const FVertexElement& Element = InElements[ElementIndex];
D3D12_INPUT_ELEMENT_DESC D3DElement = { 0 };
D3DElement.InputSlot = Element.StreamIndex;
D3DElement.AlignedByteOffset = Element.Offset;
switch (Element.Type)
{
case VET_Float1: D3DElement.Format = DXGI_FORMAT_R32_FLOAT; break;
case VET_Float2: D3DElement.Format = DXGI_FORMAT_R32G32_FLOAT; break;
case VET_Float3: D3DElement.Format = DXGI_FORMAT_R32G32B32_FLOAT; break;
case VET_Float4: D3DElement.Format = DXGI_FORMAT_R32G32B32A32_FLOAT; break;
case VET_PackedNormal: D3DElement.Format = DXGI_FORMAT_R8G8B8A8_SNORM; break; //TODO: uint32 doesn't work because D3D12 squishes it to 0 in the IA-VS conversion
case VET_UByte4: D3DElement.Format = DXGI_FORMAT_R8G8B8A8_UINT; break; //TODO: SINT, blendindices
case VET_UByte4N: D3DElement.Format = DXGI_FORMAT_R8G8B8A8_UNORM; break;
case VET_Color: D3DElement.Format = DXGI_FORMAT_B8G8R8A8_UNORM; break;
case VET_Short2: D3DElement.Format = DXGI_FORMAT_R16G16_SINT; break;
case VET_Short4: D3DElement.Format = DXGI_FORMAT_R16G16B16A16_SINT; break;
case VET_Short2N: D3DElement.Format = DXGI_FORMAT_R16G16_SNORM; break;
case VET_Half2: D3DElement.Format = DXGI_FORMAT_R16G16_FLOAT; break;
case VET_Half4: D3DElement.Format = DXGI_FORMAT_R16G16B16A16_FLOAT; break;
case VET_Short4N: D3DElement.Format = DXGI_FORMAT_R16G16B16A16_SNORM; break;
case VET_UShort2: D3DElement.Format = DXGI_FORMAT_R16G16_UINT; break;
case VET_UShort4: D3DElement.Format = DXGI_FORMAT_R16G16B16A16_UINT; break;
case VET_UShort2N: D3DElement.Format = DXGI_FORMAT_R16G16_UNORM; break;
case VET_UShort4N: D3DElement.Format = DXGI_FORMAT_R16G16B16A16_UNORM; break;
case VET_URGB10A2N: D3DElement.Format = DXGI_FORMAT_R10G10B10A2_UNORM; break;
case VET_UInt: D3DElement.Format = DXGI_FORMAT_R32_UINT; break;
default: UE_LOG(LogD3D12RHI, Fatal, TEXT("Unknown RHI vertex element type %u"), (uint8)InElements[ElementIndex].Type);
};
// We don't assign D3DElement.SemanticName here, because it's a constant string and we don't want to hash pointers. For best debugging experience,
// we want to get a consistent hash value across sessions. Therefore it's assigned below, after hashing.
D3DElement.SemanticIndex = Element.AttributeIndex;
D3DElement.InputSlotClass = Element.bUseInstanceIndex ? D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA : D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA;
// This is a divisor to apply to the instance index used to read from this stream.
D3DElement.InstanceDataStepRate = Element.bUseInstanceIndex ? 1 : 0;
if ((UsedStreamsMask & 1 << Element.StreamIndex) != 0)
{
ensure(StreamStrides[Element.StreamIndex] == Element.Stride);
}
else
{
UsedStreamsMask = UsedStreamsMask | (1 << Element.StreamIndex);
StreamStrides[Element.StreamIndex] = Element.Stride;
}
VertexElements.Add(D3DElement);
}
// Sort by stream then offset.
struct FCompareDesc
{
FORCEINLINE bool operator()(const D3D12_INPUT_ELEMENT_DESC& A, const D3D12_INPUT_ELEMENT_DESC &B) const
{
if (A.InputSlot != B.InputSlot)
{
return A.InputSlot < B.InputSlot;
}
if (A.AlignedByteOffset != B.AlignedByteOffset)
{
return A.AlignedByteOffset < B.AlignedByteOffset;
}
return A.SemanticIndex < B.SemanticIndex;
}
};
Algo::Sort(VertexElements, FCompareDesc());
// Hash once.
Hash = FCrc::MemCrc_DEPRECATED(VertexElements.GetData(), VertexElements.Num()*sizeof(D3D12_INPUT_ELEMENT_DESC));
HashNoStrides = Hash;
Hash = FCrc::MemCrc_DEPRECATED(StreamStrides, sizeof(StreamStrides), Hash);
// Assign all the SemanticName after hashing. It's a constant string, always the same, so no need to hash the data.
for (int32 ElementIndex = 0; ElementIndex < VertexElements.Num(); ElementIndex++)
{
VertexElements[ElementIndex].SemanticName = "ATTRIBUTE";
}
}
};
/** Hashes the array of D3D12 vertex element descriptions. */
uint32 GetTypeHash(const FD3D12VertexDeclarationKey& Key)
{
return Key.Hash;
}
/** Compare two vertex declaration keys. */
bool operator==(const FD3D12VertexDeclarationKey& A, const FD3D12VertexDeclarationKey& B)
{
return A.VertexElements == B.VertexElements
&& !FMemory::Memcmp(A.StreamStrides, B.StreamStrides, sizeof(A.StreamStrides));
}
/** Global cache of vertex declarations. */
struct FVertexDeclarationCache
{
FORCEINLINE FVertexDeclarationRHIRef* Find(FD3D12VertexDeclarationKey Key)
{
FScopeLock RWGuard(&LockGuard);
return Cache.Find(Key);
}
FORCEINLINE FVertexDeclarationRHIRef& Add(const FD3D12VertexDeclarationKey& InKey, const FVertexDeclarationRHIRef& InValue)
{
FScopeLock RWGuard(&LockGuard);
return Cache.Add(InKey, InValue);
}
FORCEINLINE FVertexDeclarationRHIRef* FindOrAdd(const FD3D12VertexDeclarationKey& InKey)
{
FScopeLock RWGuard(&LockGuard);
FVertexDeclarationRHIRef* VertexDeclarationRefPtr = Cache.Find(InKey);
if (VertexDeclarationRefPtr == nullptr)
{
VertexDeclarationRefPtr = &Cache.Add(InKey, new FD3D12VertexDeclaration(InKey.VertexElements, InKey.StreamStrides, InKey.Hash, InKey.HashNoStrides));
}
return VertexDeclarationRefPtr;
}
FCriticalSection LockGuard;
TMap<FD3D12VertexDeclarationKey, FVertexDeclarationRHIRef> Cache;
};
FVertexDeclarationCache GVertexDeclarationCache;
FVertexDeclarationRHIRef FD3D12DynamicRHI::RHICreateVertexDeclaration(const FVertexDeclarationElementList& Elements)
{
// Construct a key from the elements.
FD3D12VertexDeclarationKey Key(Elements);
// Check for a cached vertex declaration. Add to the cache if it doesn't exist.
FVertexDeclarationRHIRef* VertexDeclarationRefPtr = GVertexDeclarationCache.FindOrAdd(Key);
// The cached declaration must match the input declaration!
check(VertexDeclarationRefPtr);
check(IsValidRef(*VertexDeclarationRefPtr));
FD3D12VertexDeclaration* D3D12VertexDeclaration = (FD3D12VertexDeclaration*)VertexDeclarationRefPtr->GetReference();
checkSlow(D3D12VertexDeclaration->VertexElements == Key.VertexElements);
return *VertexDeclarationRefPtr;
}
bool FD3D12VertexDeclaration::GetInitializer(FVertexDeclarationElementList& Init)
{
const int32 NumVertElems = VertexElements.Num();
Init.Empty(NumVertElems);
Init.AddUninitialized(NumVertElems);
for (int32 Idx = 0; Idx < NumVertElems; ++Idx)
{
const D3D12_INPUT_ELEMENT_DESC& Elem = VertexElements[Idx];
FVertexElement& Out = Init[Idx];
Out.StreamIndex = Elem.InputSlot;
Out.Offset = Elem.AlignedByteOffset;
switch (Elem.Format)
{
case DXGI_FORMAT_R32_FLOAT: Out.Type = VET_Float1; break;
case DXGI_FORMAT_R32G32_FLOAT: Out.Type = VET_Float2; break;
case DXGI_FORMAT_R32G32B32_FLOAT: Out.Type = VET_Float3; break;
case DXGI_FORMAT_R32G32B32A32_FLOAT: Out.Type = VET_Float4; break;
case DXGI_FORMAT_R8G8B8A8_SNORM: Out.Type = VET_PackedNormal; break;
case DXGI_FORMAT_R8G8B8A8_UINT: Out.Type = VET_UByte4; break;
case DXGI_FORMAT_R8G8B8A8_UNORM: Out.Type = VET_UByte4N; break;
case DXGI_FORMAT_B8G8R8A8_UNORM: Out.Type = VET_Color; break;
case DXGI_FORMAT_R16G16_SINT: Out.Type = VET_Short2; break;
case DXGI_FORMAT_R16G16B16A16_SINT: Out.Type = VET_Short4; break;
case DXGI_FORMAT_R16G16_SNORM: Out.Type = VET_Short2N; break;
case DXGI_FORMAT_R16G16_FLOAT: Out.Type = VET_Half2; break;
case DXGI_FORMAT_R16G16B16A16_FLOAT: Out.Type = VET_Half4; break;
case DXGI_FORMAT_R16G16B16A16_SNORM: Out.Type = VET_Short4N; break;
case DXGI_FORMAT_R16G16_UINT: Out.Type = VET_UShort2; break;
case DXGI_FORMAT_R16G16B16A16_UINT: Out.Type = VET_UShort4; break;
case DXGI_FORMAT_R16G16_UNORM: Out.Type = VET_UShort2N; break;
case DXGI_FORMAT_R16G16B16A16_UNORM: Out.Type = VET_UShort4N; break;
case DXGI_FORMAT_R10G10B10A2_UNORM: Out.Type = VET_URGB10A2N; break;
case DXGI_FORMAT_R32_UINT: Out.Type = VET_UInt; break;
default:
UE_LOG(LogD3D12RHI, Fatal, TEXT("Unknown D3D vertex element type %u"), Elem.Format);
}
Out.AttributeIndex = Elem.SemanticIndex;
Out.Stride = StreamStrides[Elem.InputSlot];
Out.bUseInstanceIndex = Elem.InputSlotClass == D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA;
}
return true;
}