Improve GPU vertex layout binding in case of missing element from the mesh

This commit is contained in:
Wojtek Figat
2025-01-03 01:09:25 +01:00
parent 348f17479d
commit 7b7dd9d142
17 changed files with 168 additions and 92 deletions

View File

@@ -401,7 +401,7 @@ public:
/// Creates the vertex buffer layout.
/// </summary>
/// <returns>The vertex buffer layout.</returns>
API_FUNCTION() virtual GPUVertexLayout* CreateVertexLayout(const Array<struct VertexElement, FixedAllocation<GPU_MAX_VS_ELEMENTS>>& elements) = 0;
API_FUNCTION() virtual GPUVertexLayout* CreateVertexLayout(const Array<struct VertexElement, FixedAllocation<GPU_MAX_VS_ELEMENTS>>& elements, bool explicitOffsets = false) = 0;
typedef Array<VertexElement, FixedAllocation<GPU_MAX_VS_ELEMENTS>> VertexElements;
/// <summary>

View File

@@ -45,6 +45,26 @@ namespace
CriticalSection CacheLocker;
Dictionary<uint32, GPUVertexLayout*> LayoutCache;
Dictionary<VertexBufferLayouts, GPUVertexLayout*> VertexBufferCache;
GPUVertexLayout* AddCache(const VertexBufferLayouts& key, int32 count)
{
GPUVertexLayout::Elements elements;
bool anyValid = false;
for (int32 slot = 0; slot < count; slot++)
{
if (key.Layouts[slot])
{
anyValid = true;
int32 start = elements.Count();
elements.Add(key.Layouts[slot]->GetElements());
for (int32 j = start; j < elements.Count(); j++)
elements.Get()[j].Slot = (byte)slot;
}
}
GPUVertexLayout* result = anyValid ? GPUVertexLayout::Get(elements) : nullptr;
VertexBufferCache.Add(key, result);
return result;
}
}
String VertexElement::ToString() const
@@ -76,22 +96,27 @@ GPUVertexLayout::GPUVertexLayout()
{
}
void GPUVertexLayout::SetElements(const Elements& elements, uint32 offsets[GPU_MAX_VS_ELEMENTS])
void GPUVertexLayout::SetElements(const Elements& elements, bool explicitOffsets)
{
uint32 offsets[GPU_MAX_VB_BINDED] = {};
_elements = elements;
uint32 strides[GPU_MAX_VB_BINDED] = {};
for (int32 i = 0; i < elements.Count(); i++)
for (int32 i = 0; i < _elements.Count(); i++)
{
const VertexElement& e = elements[i];
VertexElement& e = _elements[i];
ASSERT(e.Slot < GPU_MAX_VB_BINDED);
strides[e.Slot] = Math::Max(strides[e.Slot], offsets[e.Slot]);
uint32& offset = offsets[e.Slot];
if (e.Offset != 0 || explicitOffsets)
offset = e.Offset;
else
e.Offset = (byte)offset;
offset += PixelFormatExtensions::SizeInBytes(e.Format);
}
_stride = 0;
for (uint32 stride : strides)
_stride += stride;
for (uint32 offset : offsets)
_stride += offset;
}
GPUVertexLayout* GPUVertexLayout::Get(const Elements& elements)
GPUVertexLayout* GPUVertexLayout::Get(const Elements& elements, bool explicitOffsets)
{
// Hash input layout
uint32 hash = 0;
@@ -105,7 +130,7 @@ GPUVertexLayout* GPUVertexLayout::Get(const Elements& elements)
GPUVertexLayout* result;
if (!LayoutCache.TryGet(hash, result))
{
result = GPUDevice::Instance->CreateVertexLayout(elements);
result = GPUDevice::Instance->CreateVertexLayout(elements, explicitOffsets);
if (!result)
{
#if GPU_ENABLE_ASSERTION_LOW_LAYERS
@@ -118,16 +143,6 @@ GPUVertexLayout* GPUVertexLayout::Get(const Elements& elements)
}
LayoutCache.Add(hash, result);
}
#if GPU_ENABLE_ASSERTION_LOW_LAYERS
else if (result->GetElements() != elements)
{
for (auto& e : result->GetElements())
LOG(Error, " (a) {}", e.ToString());
for (auto& e : elements)
LOG(Error, " (b) {}", e.ToString());
LOG(Fatal, "Vertex layout cache collision for hash {}", hash);
}
#endif
CacheLocker.Unlock();
return result;
@@ -141,38 +156,94 @@ GPUVertexLayout* GPUVertexLayout::Get(const Span<GPUBuffer*>& vertexBuffers)
return vertexBuffers.Get()[0] ? vertexBuffers.Get()[0]->GetVertexLayout() : nullptr;
// Build hash key for set of buffers (in case there is layout sharing by different sets of buffers)
VertexBufferLayouts layouts;
for (int32 i = 0; i < vertexBuffers.Length(); i++)
layouts.Layouts[i] = vertexBuffers.Get()[i] ? vertexBuffers.Get()[i]->GetVertexLayout() : nullptr;
VertexBufferLayouts key;
for (int32 i = 0; i < vertexBuffers.Length() && i < GPU_MAX_VB_BINDED; i++)
key.Layouts[i] = vertexBuffers.Get()[i] ? vertexBuffers.Get()[i]->GetVertexLayout() : nullptr;
for (int32 i = vertexBuffers.Length(); i < GPU_MAX_VB_BINDED; i++)
layouts.Layouts[i] = nullptr;
key.Layouts[i] = nullptr;
// Lookup existing cache
CacheLocker.Lock();
GPUVertexLayout* result;
if (!VertexBufferCache.TryGet(layouts, result))
{
Elements elements;
bool anyValid = false;
for (int32 slot = 0; slot < vertexBuffers.Length(); slot++)
{
if (layouts.Layouts[slot])
{
anyValid = true;
int32 start = elements.Count();
elements.Add(layouts.Layouts[slot]->GetElements());
for (int32 j = start; j < elements.Count(); j++)
elements.Get()[j].Slot = (byte)slot;
}
}
result = anyValid ? Get(elements) : nullptr;
VertexBufferCache.Add(layouts, result);
}
if (!VertexBufferCache.TryGet(key, result))
result = AddCache(key, vertexBuffers.Length());
CacheLocker.Unlock();
return result;
}
GPUVertexLayout* GPUVertexLayout::Get(const Span<GPUVertexLayout*>& layouts)
{
if (layouts.Length() == 0)
return nullptr;
if (layouts.Length() == 1)
return layouts.Get()[0] ? layouts.Get()[0] : nullptr;
// Build hash key for set of buffers (in case there is layout sharing by different sets of buffers)
VertexBufferLayouts key;
for (int32 i = 0; i < layouts.Length() && i < GPU_MAX_VB_BINDED; i++)
key.Layouts[i] = layouts.Get()[i];
for (int32 i = layouts.Length(); i < GPU_MAX_VB_BINDED; i++)
key.Layouts[i] = nullptr;
// Lookup existing cache
CacheLocker.Lock();
GPUVertexLayout* result;
if (!VertexBufferCache.TryGet(key, result))
result = AddCache(key, layouts.Length());
CacheLocker.Unlock();
return result;
}
GPUVertexLayout* GPUVertexLayout::Merge(GPUVertexLayout* base, const GPUVertexLayout* reference)
{
if (!reference || !base || base == reference)
return base;
GPUVertexLayout* result = base;
if (base && reference)
{
bool anyMissing = false;
const Elements& baseElements = base->GetElements();
Elements newElements = baseElements;
for (const VertexElement& e : reference->GetElements())
{
bool missing = true;
for (const VertexElement& ee : baseElements)
{
if (ee.Type == e.Type)
{
missing = false;
break;
}
}
if (missing)
{
// Insert any missing elements
VertexElement ne = { e.Type, e.Slot, 0, e.PerInstance, e.Format };
if (e.Type == VertexElement::Types::TexCoord1 || e.Type == VertexElement::Types::TexCoord2 || e.Type == VertexElement::Types::TexCoord3)
{
// Alias missing texcoords with existing texcoords
for (const VertexElement& ee : newElements)
{
if (ee.Type == VertexElement::Types::TexCoord0)
{
ne = ee;
ne.Type = e.Type;
break;
}
}
}
newElements.Add(ne);
anyMissing = true;
}
}
if (anyMissing)
result = Get(newElements, true);
}
return result;
}
void ClearVertexLayoutCache()
{
for (const auto& e : LayoutCache)

View File

@@ -23,7 +23,7 @@ private:
protected:
GPUVertexLayout();
void SetElements(const Elements& elements, uint32 offsets[GPU_MAX_VS_ELEMENTS]);
void SetElements(const Elements& elements, bool explicitOffsets);
public:
/// <summary>
@@ -46,8 +46,9 @@ public:
/// Gets the vertex layout for a given list of elements. Uses internal cache to skip creating layout if it's already exists for a given list.
/// </summary>
/// <param name="elements">The list of elements for the layout.</param>
/// <param name="explicitOffsets">If set to true, input elements offsets will be used without automatic calculations (offsets with value 0).</param>
/// <returns>Vertex layout object. Doesn't need to be cleared as it's cached for an application lifetime.</returns>
API_FUNCTION() static GPUVertexLayout* Get(const Array<VertexElement, FixedAllocation<GPU_MAX_VS_ELEMENTS>>& elements);
API_FUNCTION() static GPUVertexLayout* Get(const Array<VertexElement, FixedAllocation<GPU_MAX_VS_ELEMENTS>>& elements, bool explicitOffsets = false);
/// <summary>
/// Gets the vertex layout for a given list of vertex buffers (sequence of binding slots based on layouts set on those buffers). Uses internal cache to skip creating layout if it's already exists for a given list.
@@ -56,6 +57,21 @@ public:
/// <returns>Vertex layout object. Doesn't need to be cleared as it's cached for an application lifetime.</returns>
API_FUNCTION() static GPUVertexLayout* Get(const Span<GPUBuffer*>& vertexBuffers);
/// <summary>
/// Merges list of layouts in a single one. Uses internal cache to skip creating layout if it's already exists for a given list.
/// </summary>
/// <param name="layouts">The list of layouts to merge.</param>
/// <returns>Vertex layout object. Doesn't need to be cleared as it's cached for an application lifetime.</returns>
API_FUNCTION() static GPUVertexLayout* Get(const Span<GPUVertexLayout*>& layouts);
/// <summary>
/// Merges reference vertex elements into the given set of elements to ensure the reference list is satisfied (vertex shader input requirement). Returns base layout if it's valid.
/// </summary>
/// <param name="base">The list of vertex buffers for the layout.</param>
/// <param name="reference">The list of reference inputs.</param>
/// <returns>Vertex layout object. Doesn't need to be cleared as it's cached for an application lifetime.</returns>
static GPUVertexLayout* Merge(GPUVertexLayout* base, const GPUVertexLayout* reference);
public:
// [GPUResource]
GPUResourceType GetResourceType() const override

View File

@@ -66,7 +66,7 @@ PACK_BEGIN() struct FLAXENGINE_API VertexElement
API_FIELD() Types Type;
// Index of the input vertex buffer slot (as provided in GPUContext::BindVB).
API_FIELD() byte Slot;
// Byte offset of this element relative to the start of a vertex buffer. Use value 0 to use auto-calculated offset based on previous elements in the layout (or for the first one).
// Byte offset of this element relative to the start of a vertex buffer. Use value 0 to use auto-calculated offset based on previous elements in the layout (except when explicitOffsets is false).
API_FIELD() byte Offset;
// Flag used to mark data using hardware-instancing (element will be repeated for every instance). Empty to step data per-vertex when reading input buffer stream (rather than per-instance step).
API_FIELD() byte PerInstance;

View File

@@ -148,27 +148,22 @@ static bool TryCreateDevice(IDXGIAdapter* adapter, D3D_FEATURE_LEVEL maxFeatureL
return false;
}
GPUVertexLayoutDX11::GPUVertexLayoutDX11(GPUDeviceDX11* device, const Elements& elements)
GPUVertexLayoutDX11::GPUVertexLayoutDX11(GPUDeviceDX11* device, const Elements& elements, bool explicitOffsets)
: GPUResourceBase<GPUDeviceDX11, GPUVertexLayout>(device, StringView::Empty)
, InputElementsCount(elements.Count())
{
uint32 offsets[GPU_MAX_VB_BINDED] = {};
SetElements(elements, explicitOffsets);
for (int32 i = 0; i < elements.Count(); i++)
{
const VertexElement& src = elements.Get()[i];
const VertexElement& src = GetElements().Get()[i];
D3D11_INPUT_ELEMENT_DESC& dst = InputElements[i];
uint32& offset = offsets[src.Slot];
if (src.Offset != 0)
offset = src.Offset;
dst.SemanticName = RenderToolsDX::GetVertexInputSemantic(src.Type, dst.SemanticIndex);
dst.Format = RenderToolsDX::ToDxgiFormat(src.Format);
dst.InputSlot = src.Slot;
dst.AlignedByteOffset = offset;
dst.AlignedByteOffset = src.Offset;
dst.InputSlotClass = src.PerInstance ? D3D11_INPUT_PER_INSTANCE_DATA : D3D11_INPUT_PER_VERTEX_DATA;
dst.InstanceDataStepRate = src.PerInstance ? 1 : 0;
offset += PixelFormatExtensions::SizeInBytes(src.Format);
}
SetElements(elements, offsets);
}
GPUDevice* GPUDeviceDX11::Create()
@@ -832,9 +827,9 @@ GPUSampler* GPUDeviceDX11::CreateSampler()
return New<GPUSamplerDX11>(this);
}
GPUVertexLayout* GPUDeviceDX11::CreateVertexLayout(const VertexElements& elements)
GPUVertexLayout* GPUDeviceDX11::CreateVertexLayout(const VertexElements& elements, bool explicitOffsets)
{
return New<GPUVertexLayoutDX11>(this, elements);
return New<GPUVertexLayoutDX11>(this, elements, explicitOffsets);
}
GPUSwapChain* GPUDeviceDX11::CreateSwapChain(Window* window)

View File

@@ -128,7 +128,7 @@ public:
GPUTimerQuery* CreateTimerQuery() override;
GPUBuffer* CreateBuffer(const StringView& name) override;
GPUSampler* CreateSampler() override;
GPUVertexLayout* CreateVertexLayout(const VertexElements& elements) override;
GPUVertexLayout* CreateVertexLayout(const VertexElements& elements, bool explicitOffsets) override;
GPUSwapChain* CreateSwapChain(Window* window) override;
GPUConstantBuffer* CreateConstantBuffer(uint32 size, const StringView& name) override;
};

View File

@@ -11,19 +11,23 @@
GPUShaderProgramVSDX11::~GPUShaderProgramVSDX11()
{
for (const auto& e : _cache)
e.Value->Release();
{
if (e.Value)
e.Value->Release();
}
}
ID3D11InputLayout* GPUShaderProgramVSDX11::GetInputLayout(GPUVertexLayoutDX11* vertexLayout)
{
if (!vertexLayout)
vertexLayout = (GPUVertexLayoutDX11*)Layout;
ID3D11InputLayout* inputLayout = nullptr;
if (!_cache.TryGet(vertexLayout, inputLayout))
{
if (!vertexLayout)
vertexLayout = (GPUVertexLayoutDX11*)Layout;
if (vertexLayout && vertexLayout->InputElementsCount)
{
VALIDATE_DIRECTX_CALL(vertexLayout->GetDevice()->GetDevice()->CreateInputLayout(vertexLayout->InputElements, vertexLayout->InputElementsCount, Bytecode.Get(), Bytecode.Length(), &inputLayout));
auto actualLayout = (GPUVertexLayoutDX11*)GPUVertexLayout::Merge(vertexLayout, Layout);
LOG_DIRECTX_RESULT(vertexLayout->GetDevice()->GetDevice()->CreateInputLayout(actualLayout->InputElements, actualLayout->InputElementsCount, Bytecode.Get(), Bytecode.Length(), &inputLayout));
}
_cache.Add(vertexLayout, inputLayout);
}

View File

@@ -13,7 +13,7 @@
class GPUVertexLayoutDX11 : public GPUResourceBase<GPUDeviceDX11, GPUVertexLayout>
{
public:
GPUVertexLayoutDX11(GPUDeviceDX11* device, const Elements& elements);
GPUVertexLayoutDX11(GPUDeviceDX11* device, const Elements& elements, bool explicitOffsets);
uint32 InputElementsCount;
D3D11_INPUT_ELEMENT_DESC InputElements[GPU_MAX_VS_ELEMENTS];

View File

@@ -36,27 +36,22 @@ static bool CheckDX12Support(IDXGIAdapter* adapter)
return false;
}
GPUVertexLayoutDX12::GPUVertexLayoutDX12(GPUDeviceDX12* device, const Elements& elements)
GPUVertexLayoutDX12::GPUVertexLayoutDX12(GPUDeviceDX12* device, const Elements& elements, bool explicitOffsets)
: GPUResourceDX12<GPUVertexLayout>(device, StringView::Empty)
, InputElementsCount(elements.Count())
{
uint32 offsets[GPU_MAX_VB_BINDED] = {};
SetElements(elements, explicitOffsets);
for (int32 i = 0; i < elements.Count(); i++)
{
const VertexElement& src = elements.Get()[i];
const VertexElement& src = GetElements().Get()[i];
D3D12_INPUT_ELEMENT_DESC& dst = InputElements[i];
uint32& offset = offsets[src.Slot];
if (src.Offset != 0)
offset = src.Offset;
dst.SemanticName = RenderToolsDX::GetVertexInputSemantic(src.Type, dst.SemanticIndex);
dst.Format = RenderToolsDX::ToDxgiFormat(src.Format);
dst.InputSlot = src.Slot;
dst.AlignedByteOffset = offset;
dst.AlignedByteOffset = src.Offset;
dst.InputSlotClass = src.PerInstance ? D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA : D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA;
dst.InstanceDataStepRate = src.PerInstance ? 1 : 0;
offset += PixelFormatExtensions::SizeInBytes(src.Format);
}
SetElements(elements, offsets);
}
GPUDevice* GPUDeviceDX12::Create()
@@ -868,9 +863,9 @@ GPUSampler* GPUDeviceDX12::CreateSampler()
return New<GPUSamplerDX12>(this);
}
GPUVertexLayout* GPUDeviceDX12::CreateVertexLayout(const VertexElements& elements)
GPUVertexLayout* GPUDeviceDX12::CreateVertexLayout(const VertexElements& elements, bool explicitOffsets)
{
return New<GPUVertexLayoutDX12>(this, elements);
return New<GPUVertexLayoutDX12>(this, elements, explicitOffsets);
}
GPUSwapChain* GPUDeviceDX12::CreateSwapChain(Window* window)

View File

@@ -196,7 +196,7 @@ public:
GPUTimerQuery* CreateTimerQuery() override;
GPUBuffer* CreateBuffer(const StringView& name) override;
GPUSampler* CreateSampler() override;
GPUVertexLayout* CreateVertexLayout(const VertexElements& elements) override;
GPUVertexLayout* CreateVertexLayout(const VertexElements& elements, bool explicitOffsets) override;
GPUSwapChain* CreateSwapChain(Window* window) override;
GPUConstantBuffer* CreateConstantBuffer(uint32 size, const StringView& name) override;
};

View File

@@ -13,7 +13,7 @@
class GPUVertexLayoutDX12 : public GPUResourceDX12<GPUVertexLayout>
{
public:
GPUVertexLayoutDX12(GPUDeviceDX12* device, const Elements& elements);
GPUVertexLayoutDX12(GPUDeviceDX12* device, const Elements& elements, bool explicitOffsets);
uint32 InputElementsCount;
D3D12_INPUT_ELEMENT_DESC InputElements[GPU_MAX_VS_ELEMENTS];

View File

@@ -173,7 +173,7 @@ GPUSampler* GPUDeviceNull::CreateSampler()
return New<GPUSamplerNull>();
}
GPUVertexLayout* GPUDeviceNull::CreateVertexLayout(const VertexElements& elements)
GPUVertexLayout* GPUDeviceNull::CreateVertexLayout(const VertexElements& elements, bool explicitOffsets)
{
return New<GPUVertexLayoutNull>(elements);
}

View File

@@ -47,7 +47,7 @@ public:
GPUTimerQuery* CreateTimerQuery() override;
GPUBuffer* CreateBuffer(const StringView& name) override;
GPUSampler* CreateSampler() override;
GPUVertexLayout* CreateVertexLayout(const VertexElements& elements) override;
GPUVertexLayout* CreateVertexLayout(const VertexElements& elements, bool explicitOffsets) override;
GPUSwapChain* CreateSwapChain(Window* window) override;
GPUConstantBuffer* CreateConstantBuffer(uint32 size, const StringView& name) override;
};

View File

@@ -15,7 +15,7 @@ public:
GPUVertexLayoutNull(const Elements& elements)
: GPUVertexLayout()
{
SetElements(elements, {});
SetElements(elements, false);
}
};

View File

@@ -449,10 +449,10 @@ uint32 GetHash(const FramebufferVulkan::Key& key)
return hash;
}
GPUVertexLayoutVulkan::GPUVertexLayoutVulkan(GPUDeviceVulkan* device, const Elements& elements)
GPUVertexLayoutVulkan::GPUVertexLayoutVulkan(GPUDeviceVulkan* device, const Elements& elements, bool explicitOffsets)
: GPUResourceVulkan<GPUVertexLayout>(device, StringView::Empty)
{
uint32 offsets[GPU_MAX_VB_BINDED] = {};
SetElements(elements, explicitOffsets);
for (int32 i = 0; i < GPU_MAX_VB_BINDED; i++)
{
VkVertexInputBindingDescription& binding = Bindings[i];
@@ -463,28 +463,23 @@ GPUVertexLayoutVulkan::GPUVertexLayoutVulkan(GPUDeviceVulkan* device, const Elem
uint32 bindingsCount = 0;
for (int32 i = 0; i < elements.Count(); i++)
{
const VertexElement& src = elements.Get()[i];
uint32& offset = offsets[src.Slot];
if (src.Offset != 0)
offset = src.Offset;
const VertexElement& src = GetElements().Get()[i];
const int32 size = PixelFormatExtensions::SizeInBytes(src.Format);
ASSERT_LOW_LAYER(src.Slot < GPU_MAX_VB_BINDED);
VkVertexInputBindingDescription& binding = Bindings[src.Slot];
binding.binding = src.Slot;
binding.stride = Math::Max(binding.stride, (uint32_t)(offset + size));
binding.stride = Math::Max(binding.stride, (uint32_t)(src.Offset + size));
binding.inputRate = src.PerInstance ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX;
VkVertexInputAttributeDescription& attribute = Attributes[i];
attribute.location = i;
attribute.binding = src.Slot;
attribute.format = RenderToolsVulkan::ToVulkanFormat(src.Format);
attribute.offset = offset;
attribute.offset = src.Offset;
bindingsCount = Math::Max(bindingsCount, (uint32)src.Slot + 1);
offset += size;
}
SetElements(elements, offsets);
RenderToolsVulkan::ZeroStruct(CreateInfo, VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO);
CreateInfo.vertexBindingDescriptionCount = bindingsCount;
@@ -2129,9 +2124,9 @@ GPUSampler* GPUDeviceVulkan::CreateSampler()
return New<GPUSamplerVulkan>(this);
}
GPUVertexLayout* GPUDeviceVulkan::CreateVertexLayout(const VertexElements& elements)
GPUVertexLayout* GPUDeviceVulkan::CreateVertexLayout(const VertexElements& elements, bool explicitOffsets)
{
return New<GPUVertexLayoutVulkan>(this, elements);
return New<GPUVertexLayoutVulkan>(this, elements, explicitOffsets);
}
GPUSwapChain* GPUDeviceVulkan::CreateSwapChain(Window* window)

View File

@@ -610,7 +610,7 @@ public:
GPUTimerQuery* CreateTimerQuery() override;
GPUBuffer* CreateBuffer(const StringView& name) override;
GPUSampler* CreateSampler() override;
GPUVertexLayout* CreateVertexLayout(const VertexElements& elements) override;
GPUVertexLayout* CreateVertexLayout(const VertexElements& elements, bool explicitOffsets) override;
GPUSwapChain* CreateSwapChain(Window* window) override;
GPUConstantBuffer* CreateConstantBuffer(uint32 size, const StringView& name) override;
};

View File

@@ -13,7 +13,7 @@
class GPUVertexLayoutVulkan : public GPUResourceVulkan<GPUVertexLayout>
{
public:
GPUVertexLayoutVulkan(GPUDeviceVulkan* device, const Elements& elements);
GPUVertexLayoutVulkan(GPUDeviceVulkan* device, const Elements& elements, bool explicitOffsets);
VkPipelineVertexInputStateCreateInfo CreateInfo;
VkVertexInputBindingDescription Bindings[GPU_MAX_VB_BINDED];