Add GPUVertexLayout to graphics backends

This commit is contained in:
Wojtek Figat
2024-12-13 09:20:01 +01:00
parent cedf4b1eb5
commit fc4e6f4972
31 changed files with 605 additions and 93 deletions

View File

@@ -25,6 +25,9 @@
// Maximum amount of binded vertex buffers at the same time
#define GPU_MAX_VB_BINDED 4
// Maximum amount of vertex shader input elements in a layout
#define GPU_MAX_VS_ELEMENTS 16
// Maximum amount of thread groups per dimension for compute dispatch
#define GPU_MAX_CS_DISPATCH_THREAD_GROUPS 65535
@@ -33,6 +36,7 @@
// Enable/disable assertion for graphics layers
#define GPU_ENABLE_ASSERTION 1
#define GPU_ENABLE_ASSERTION_LOW_LAYERS (!BUILD_RELEASE)
// Enable/disable dynamic textures quality streaming
#define GPU_ENABLE_TEXTURES_STREAMING 1

View File

@@ -479,6 +479,8 @@ void GPUDevice::DumpResourcesToLog() const
LOG_STR(Info, output.ToStringView());
}
extern void ClearVertexLayoutCache();
void GPUDevice::preDispose()
{
Locker.Lock();
@@ -494,6 +496,7 @@ void GPUDevice::preDispose()
SAFE_DELETE_GPU_RESOURCE(_res->PS_Clear);
SAFE_DELETE_GPU_RESOURCE(_res->PS_DecodeYUY2);
SAFE_DELETE_GPU_RESOURCE(_res->FullscreenTriangleVB);
ClearVertexLayoutCache();
Locker.Unlock();

View File

@@ -23,6 +23,7 @@ class GPUBuffer;
class GPUSampler;
class GPUPipelineState;
class GPUConstantBuffer;
class GPUVertexLayout;
class GPUTasksContext;
class GPUTasksExecutor;
class GPUSwapChain;
@@ -396,6 +397,13 @@ public:
/// <returns>The sampler.</returns>
API_FUNCTION() virtual GPUSampler* CreateSampler() = 0;
/// <summary>
/// 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;
typedef Array<VertexElement, FixedAllocation<GPU_MAX_VS_ELEMENTS>> VertexElements;
/// <summary>
/// Creates the native window swap chain.
/// </summary>

View File

@@ -6,12 +6,16 @@
#include "Engine/Core/Enums.h"
#include "Engine/Graphics/Enums.h"
// [Deprecated in v1.10]
#define INPUT_LAYOUT_ELEMENT_ALIGN 0xffffffff
// [Deprecated in v1.10]
#define INPUT_LAYOUT_ELEMENT_PER_VERTEX_DATA 0
// [Deprecated in v1.10]
#define INPUT_LAYOUT_ELEMENT_PER_INSTANCE_DATA 1
/// <summary>
/// Maximum amount of input elements for vertex shader programs
/// [Deprecated in v1.10]
/// </summary>
#define VERTEX_SHADER_MAX_INPUT_ELEMENTS 16
@@ -26,7 +30,8 @@
#define SHADER_PERMUTATIONS_MAX_PARAMS_COUNT 4
/// <summary>
/// Maximum allowed amount of constant buffers that can be binded to the pipeline (maximum slot index is MAX_CONSTANT_BUFFER_SLOTS-1)
/// Maximum allowed amount of constant buffers that can be binded to the pipeline (maximum slot index is MAX_CONSTANT_BUFFER_SLOTS-1)
/// [Deprecated in v1.10]
/// </summary>
#define MAX_CONSTANT_BUFFER_SLOTS 4

View File

@@ -5,6 +5,7 @@
#include "Engine/Core/Log.h"
#include "Engine/Core/Math/Math.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/Shaders/GPUVertexLayout.h"
#include "Engine/Serialization/MemoryReadStream.h"
static FORCE_INLINE uint32 HashPermutation(const StringAnsiView& name, int32 permutationIndex)
@@ -12,6 +13,21 @@ static FORCE_INLINE uint32 HashPermutation(const StringAnsiView& name, int32 per
return GetHash(name) * 37 + permutationIndex;
}
void GPUShaderProgram::Init(const GPUShaderProgramInitializer& initializer)
{
_name = initializer.Name;
_bindings = initializer.Bindings;
_flags = initializer.Flags;
#if !BUILD_RELEASE
_owner = initializer.Owner;
#endif
}
GPUShaderProgramVS::~GPUShaderProgramVS()
{
SAFE_DELETE(Layout);
}
GPUShader::GPUShader()
: GPUResource(SpawnParams(Guid::New(), TypeInitializer))
{
@@ -104,33 +120,32 @@ bool GPUShader::Create(MemoryReadStream& stream)
// Constant Buffers
const byte constantBuffersCount = stream.ReadByte();
const byte maximumConstantBufferSlot = stream.ReadByte();
if (constantBuffersCount > 0)
for (int32 i = 0; i < constantBuffersCount; i++)
{
ASSERT(maximumConstantBufferSlot < MAX_CONSTANT_BUFFER_SLOTS);
for (int32 i = 0; i < constantBuffersCount; i++)
// Load info
const byte slotIndex = stream.ReadByte();
if (slotIndex >= GPU_MAX_CB_BINDED)
{
// Load info
const byte slotIndex = stream.ReadByte();
uint32 size;
stream.ReadUint32(&size);
// Create CB
#if GPU_ENABLE_RESOURCE_NAMING
String name = String::Format(TEXT("{}.CB{}"), ToString(), i);
#else
String name;
#endif
ASSERT(_constantBuffers[slotIndex] == nullptr);
const auto cb = GPUDevice::Instance->CreateConstantBuffer(size, name);
if (cb == nullptr)
{
LOG(Warning, "Failed to create shader constant buffer.");
return true;
}
_constantBuffers[slotIndex] = cb;
LOG(Warning, "Failed to create shader constant buffer.");
return true;
}
uint32 size;
stream.ReadUint32(&size);
// Create CB
#if GPU_ENABLE_RESOURCE_NAMING
String cbName = String::Format(TEXT("{}.CB{}"), ToString(), i);
#else
String cbName;
#endif
ASSERT(_constantBuffers[slotIndex] == nullptr);
const auto cb = GPUDevice::Instance->CreateConstantBuffer(size, cbName);
if (cb == nullptr)
{
LOG(Warning, "Failed to create shader constant buffer.");
return true;
}
_constantBuffers[slotIndex] = cb;
}
// Don't read additional data

View File

@@ -12,7 +12,7 @@ class GPUShaderProgram;
/// <summary>
/// The runtime version of the shaders cache supported by the all graphics back-ends. The same for all the shader cache formats (easier to sync and validate).
/// </summary>
#define GPU_SHADER_CACHE_VERSION 9
#define GPU_SHADER_CACHE_VERSION 10
/// <summary>
/// The GPU resource with shader programs that can run on the GPU and are able to perform rendering calculation using textures, vertices and other resources.
@@ -23,7 +23,7 @@ API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API GPUShader : public GPUResource
protected:
Dictionary<uint32, GPUShaderProgram*> _shaders;
GPUConstantBuffer* _constantBuffers[MAX_CONSTANT_BUFFER_SLOTS];
GPUConstantBuffer* _constantBuffers[GPU_MAX_CB_BINDED];
GPUShader();

View File

@@ -7,6 +7,7 @@
#include "Config.h"
class GPUShader;
class GPUVertexLayout;
/// <summary>
/// The shader program metadata container. Contains description about resources used by the shader.
@@ -18,17 +19,17 @@ struct FLAXENGINE_API ShaderBindings
uint32 UsedSRsMask;
uint32 UsedUAsMask;
bool IsUsingCB(uint32 slotIndex) const
FORCE_INLINE bool IsUsingCB(uint32 slotIndex) const
{
return (UsedCBsMask & (1u << slotIndex)) != 0u;
}
bool IsUsingSR(uint32 slotIndex) const
FORCE_INLINE bool IsUsingSR(uint32 slotIndex) const
{
return (UsedSRsMask & (1u << slotIndex)) != 0u;
}
bool IsUsingUA(uint32 slotIndex) const
FORCE_INLINE bool IsUsingUA(uint32 slotIndex) const
{
return (UsedUAsMask & (1u << slotIndex)) != 0u;
}
@@ -57,15 +58,7 @@ protected:
GPUShader* _owner;
#endif
void Init(const GPUShaderProgramInitializer& initializer)
{
_name = initializer.Name;
_bindings = initializer.Bindings;
_flags = initializer.Flags;
#if !BUILD_RELEASE
_owner = initializer.Owner;
#endif
}
void Init(const GPUShaderProgramInitializer& initializer);
public:
/// <summary>
@@ -122,8 +115,18 @@ public:
/// </summary>
class GPUShaderProgramVS : public GPUShaderProgram
{
public:
~GPUShaderProgramVS();
// Vertex elements input layout defined explicitly in the shader.
// It's optional as it's been deprecated in favor or layouts defined by vertex buffers to allow data customizations.
// Can be overriden by the vertex buffers provided upon draw call.
// [Deprecated in v1.10]
GPUVertexLayout* Layout = nullptr;
public:
// Input element run-time data (see VertexShaderMeta::InputElement for compile-time data)
// [Deprecated in v1.10]
PACK_STRUCT(struct InputElement
{
byte Type; // VertexShaderMeta::InputType
@@ -135,14 +138,15 @@ public:
uint32 InstanceDataStepRate; // 0 if per-vertex
});
public:
/// <summary>
/// Gets input layout description handle (platform dependent).
/// [Deprecated in v1.10]
/// </summary>
virtual void* GetInputLayout() const = 0;
/// <summary>
/// Gets input layout description size (in bytes).
/// [Deprecated in v1.10]
/// </summary>
virtual byte GetInputLayoutSize() const = 0;
@@ -173,7 +177,7 @@ public:
class GPUShaderProgramHS : public GPUShaderProgram
{
protected:
int32 _controlPointsCount;
int32 _controlPointsCount = 0;
public:
/// <summary>

View File

@@ -0,0 +1,103 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#include "GPUVertexLayout.h"
#if GPU_ENABLE_ASSERTION_LOW_LAYERS
#include "Engine/Core/Log.h"
#endif
#include "Engine/Core/Collections/Dictionary.h"
#include "Engine/Graphics/GPUDevice.h"
#if GPU_ENABLE_RESOURCE_NAMING
#include "Engine/Scripting/Enums.h"
#endif
// VertexElement has been designed to be POD and memory-comparable for faster hashing and comparision.
struct VertexElementRaw
{
uint32 Words[2];
};
static_assert(sizeof(VertexElement) == sizeof(VertexElementRaw), "Incorrect size of the VertexElement!");
namespace
{
CriticalSection CacheLocker;
Dictionary<uint32, GPUVertexLayout*> LayoutCache;
}
String VertexElement::ToString() const
{
#if GPU_ENABLE_RESOURCE_NAMING
return String::Format(TEXT("{}, format {}, offset {}, per-instance {}, slot {}"), ScriptingEnum::ToString(Type), ScriptingEnum::ToString(Format), Offset, PerInstance, Slot);
#else
return TEXT("VertexElement");
#endif
}
bool VertexElement::operator==(const VertexElement& other) const
{
auto thisRaw = (const VertexElementRaw*)this;
auto otherRaw = (const VertexElementRaw*)&other;
return thisRaw->Words[0] == otherRaw->Words[0] && thisRaw->Words[1] == otherRaw->Words[1];
}
uint32 GetHash(const VertexElement& key)
{
auto keyRaw = (const VertexElementRaw*)&key;
uint32 hash = keyRaw->Words[0];
CombineHash(hash, keyRaw->Words[1]);
return hash;
}
GPUVertexLayout::GPUVertexLayout()
: GPUResource(SpawnParams(Guid::New(), TypeInitializer))
{
}
GPUVertexLayout* GPUVertexLayout::Get(const Elements& elements)
{
// Hash input layout
uint32 hash = 0;
for (const VertexElement& element : elements)
{
CombineHash(hash, GetHash(element));
}
// Lookup existing cache
CacheLocker.Lock();
GPUVertexLayout* result;
if (!LayoutCache.TryGet(hash, result))
{
result = GPUDevice::Instance->CreateVertexLayout(elements);
if (!result)
{
#if GPU_ENABLE_ASSERTION_LOW_LAYERS
for (auto& e : elements)
LOG(Error, " {}", e.ToString());
#endif
LOG(Error, "Failed to create vertex layout");
CacheLocker.Unlock();
return nullptr;
}
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;
}
void ClearVertexLayoutCache()
{
for (const auto& e : LayoutCache)
Delete(e.Value);
LayoutCache.Clear();
}

View File

@@ -0,0 +1,44 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#pragma once
#include "VertexElement.h"
#include "Engine/Graphics/GPUResource.h"
#include "Engine/Core/Collections/Array.h"
/// <summary>
/// Defines input layout of vertex buffer data passed to the Vertex Shader.
/// </summary>
API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API GPUVertexLayout : public GPUResource
{
DECLARE_SCRIPTING_TYPE_NO_SPAWN(GPUVertexLayout);
typedef Array<VertexElement, FixedAllocation<GPU_MAX_VS_ELEMENTS>> Elements;
protected:
Elements _elements;
GPUVertexLayout();
public:
/// <summary>
/// Gets the list of elements used by this layout.
/// </summary>
API_PROPERTY() FORCE_INLINE const Array<VertexElement, FixedAllocation<GPU_MAX_VS_ELEMENTS>>& GetElements() const
{
return _elements;
}
/// <summary>
/// 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>
/// <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);
public:
// [GPUResource]
GPUResourceType GetResourceType() const override
{
return GPUResourceType::Descriptor;
}
};

View File

@@ -0,0 +1,86 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#pragma once
#include "Engine/Graphics/PixelFormat.h"
/// <summary>
/// Vertex buffer data element. Defines access to data passed to Vertex Shader.
/// </summary>
API_STRUCT(NoDefault)
PACK_BEGIN() struct FLAXENGINE_API VertexElement
{
DECLARE_SCRIPTING_TYPE_MINIMAL(VertexElement);
/// <summary>
/// Types of vertex elements.
/// </summary>
API_ENUM() enum class Types : byte
{
// Undefined.
Unknown = 0,
// Vertex position.
Position = 1,
// Vertex color.
Color = 2,
// Vertex normal vector.
Normal = 3,
// Vertex tangent vector.
Tangent = 4,
// Skinned bone blend indices.
BlendIndices = 5,
// Skinned bone blend weights.
BlendWeight = 6,
// Primary texture coordinate (UV).
TexCoord0 = 7,
// Additional texture coordinate (UV1).
TexCoord1 = 8,
// Additional texture coordinate (UV2).
TexCoord2 = 9,
// Additional texture coordinate (UV3).
TexCoord3 = 10,
// Additional texture coordinate (UV4).
TexCoord4 = 11,
// Additional texture coordinate (UV5).
TexCoord5 = 12,
// Additional texture coordinate (UV6).
TexCoord6 = 13,
// Additional texture coordinate (UV7).
TexCoord7 = 14,
// General purpose attribute (at index 0).
Attribute0 = 15,
// General purpose attribute (at index 1).
Attribute1 = 16,
// General purpose attribute (at index 2).
Attribute2 = 17,
// General purpose attribute (at index 3).
Attribute3 = 18,
// Texture coordinate.
TexCoord = TexCoord0,
// General purpose attribute.
Attribute = Attribute0,
MAX
};
// Type of the vertex element data.
Types Type;
// Index of the input vertex buffer slot (as provided in GPUContext::BindVB).
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;
// 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).
byte PerInstance;
// Format of the vertex element data.
PixelFormat Format;
String ToString() const;
bool operator==(const VertexElement& other) const;
FORCE_INLINE bool operator!=(const VertexElement& other) const
{
return !operator==(other);
}
} PACK_END();
uint32 FLAXENGINE_API GetHash(const VertexElement& key);