**Refactor meshes format to support custom vertex layouts and new flexible api to access mesh data**

#3044 #2667
This commit is contained in:
Wojtek Figat
2025-01-06 22:47:19 +01:00
parent 29bfef677f
commit db4d7d2a05
65 changed files with 4428 additions and 3106 deletions

View File

@@ -400,6 +400,12 @@ namespace FlaxEngine
Format = format;
}
/// <inheritdoc />
public override string ToString()
{
return string.Format("{0}, {1}, offset {2}, {3}, slot {3}", Type, Format, Offset, PerInstance != 0 ? "per-instance" : "per-vertex", Slot);
}
/// <inheritdoc />
public bool Equals(VertexElement other)
{

View File

@@ -68,4 +68,11 @@ public:
/// The list of shape vertices.
/// </summary>
Array<BlendShapeVertex> Vertices;
void LoadHeader(class ReadStream& stream, byte headerVersion);
void Load(ReadStream& stream, byte meshVersion);
#if USE_EDITOR
void SaveHeader(class WriteStream& stream) const;
void Save(WriteStream& stream) const;
#endif
};

View File

@@ -31,21 +31,21 @@ public:
}
template<typename IndexType>
void Init(uint32 vertices, uint32 triangles, const Float3* positions, const IndexType* indices)
void Init(uint32 vertices, uint32 triangles, const Float3* positions, const IndexType* indices, uint32 positionsStride = sizeof(Float3))
{
Triangles.Clear();
Triangles.EnsureCapacity(triangles, false);
const IndexType* it = indices;
for (uint32 i = 0; i < triangles; i++)
{
auto i0 = *(it++);
auto i1 = *(it++);
auto i2 = *(it++);
const IndexType i0 = *(it++);
const IndexType i1 = *(it++);
const IndexType i2 = *(it++);
if (i0 < vertices && i1 < vertices && i2 < vertices)
{
Triangles.Add({ positions[i0], positions[i1], positions[i2] });
#define GET_POS(idx) *(const Float3*)((const byte*)positions + positionsStride * idx)
Triangles.Add({ GET_POS(i0), GET_POS(i1), GET_POS(i2) });
#undef GET_POS
}
}
}

View File

@@ -16,7 +16,10 @@
#define MODEL_MAX_MESHES 4096
// Maximum amount of texture channels (UVs) per vertex
#define MODEL_MAX_UVS 4
#define MODEL_MAX_UV 4
// Maximum amount of vertex buffers (VBs) per mesh
#define MODEL_MAX_VB 3
// Enable/disable precise mesh collision testing (with in-build vertex buffer caching, this will increase memory usage)
#define MODEL_USE_PRECISE_MESH_INTERSECTS (USE_EDITOR)

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#include "Mesh.h"
#include "MeshAccessor.h"
#include "MeshDeformation.h"
#include "ModelInstanceEntry.h"
#include "Engine/Content/Assets/Material.h"
@@ -11,16 +12,14 @@
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/RenderTools.h"
#include "Engine/Graphics/Shaders/GPUVertexLayout.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Renderer/RenderList.h"
#include "Engine/Scripting/ManagedCLR/MCore.h"
#include "Engine/Serialization/MemoryReadStream.h"
#include "Engine/Threading/Task.h"
#include "Engine/Threading/Threading.h"
#if USE_EDITOR
#include "Engine/Renderer/GBufferPass.h"
#endif
PRAGMA_DISABLE_DEPRECATION_WARNINGS
GPUVertexLayout* VB0ElementType18::GetLayout()
{
return GPUVertexLayout::Get({
@@ -44,77 +43,100 @@ GPUVertexLayout* VB2ElementType18::GetLayout()
{ VertexElement::Types::Color, 2, 0, 0, PixelFormat::R8G8B8A8_UNorm },
});
}
PRAGMA_ENABLE_DEPRECATION_WARNINGS
namespace
{
template<typename IndexType>
bool UpdateMesh(Mesh* mesh, uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const IndexType* triangles, const Float3* normals, const Float3* tangents, const Float2* uvs, const Color32* colors)
bool UpdateMesh(MeshBase* mesh, uint32 vertexCount, uint32 triangleCount, PixelFormat indexFormat, const Float3* vertices, const void* triangles, const Float3* normals, const Float3* tangents, const Float2* uvs, const Color32* colors)
{
auto model = mesh->GetModel();
auto model = mesh->GetModelBase();
CHECK_RETURN(model && model->IsVirtual(), true);
CHECK_RETURN(triangles && vertices, true);
MeshAccessor accessor;
// Index Buffer
{
if (accessor.AllocateBuffer(MeshBufferType::Index, triangleCount, indexFormat))
return true;
auto indexStream = accessor.Index();
ASSERT(indexStream.IsLinear(indexFormat));
indexStream.SetLinear(triangles);
}
// Pack mesh data into vertex buffers
Array<VB1ElementType> vb1;
Array<VB2ElementType> vb2;
vb1.Resize(vertexCount);
// Vertex Buffer 0 (position-only)
{
GPUVertexLayout* vb0layout = GPUVertexLayout::Get({ { VertexElement::Types::Position, 0, 0, 0, PixelFormat::R32G32B32_Float } });
if (accessor.AllocateBuffer(MeshBufferType::Vertex0, vertexCount, vb0layout))
return true;
auto positionStream = accessor.Position();
ASSERT(positionStream.IsLinear(PixelFormat::R32G32B32_Float));
positionStream.SetLinear(vertices);
}
// Vertex Buffer 1 (general purpose components)
GPUVertexLayout::Elements vb1elements;
if (normals)
{
vb1elements.Add({ VertexElement::Types::Normal, 1, 0, 0, PixelFormat::R10G10B10A2_UNorm });
if (tangents)
{
for (uint32 i = 0; i < vertexCount; i++)
{
const Float3 normal = normals[i];
const Float3 tangent = tangents[i];
auto& v = vb1.Get()[i];
RenderTools::CalculateTangentFrame(v.Normal, v.Tangent, normal, tangent);
}
}
else
{
for (uint32 i = 0; i < vertexCount; i++)
{
const Float3 normal = normals[i];
auto& v = vb1.Get()[i];
RenderTools::CalculateTangentFrame(v.Normal, v.Tangent, normal);
}
}
}
else
{
// Set default tangent frame
const auto n = Float1010102(Float3::UnitZ);
const auto t = Float1010102(Float3::UnitX);
for (uint32 i = 0; i < vertexCount; i++)
{
vb1.Get()[i].Normal = n;
vb1.Get()[i].Tangent = t;
}
vb1elements.Add({ VertexElement::Types::Tangent, 1, 0, 0, PixelFormat::R10G10B10A2_UNorm });
}
if (uvs)
vb1elements.Add({ VertexElement::Types::TexCoord, 1, 0, 0, PixelFormat::R16G16_Float });
if (vb1elements.HasItems())
{
for (uint32 i = 0; i < vertexCount; i++)
vb1.Get()[i].TexCoord = Half2(uvs[i]);
}
else
{
auto v = Half2::Zero;
for (uint32 i = 0; i < vertexCount; i++)
vb1.Get()[i].TexCoord = v;
}
{
auto v = Half2::Zero;
for (uint32 i = 0; i < vertexCount; i++)
vb1.Get()[i].LightmapUVs = v;
}
if (colors)
{
vb2.Resize(vertexCount);
for (uint32 i = 0; i < vertexCount; i++)
vb2.Get()[i].Color = colors[i];
GPUVertexLayout* vb1layout = GPUVertexLayout::Get(vb1elements);
if (accessor.AllocateBuffer(MeshBufferType::Vertex1, vertexCount, vb1layout))
return true;
if (normals)
{
auto normalStream = accessor.Normal();
if (tangents)
{
auto tangentStream = accessor.Tangent();
for (uint32 i = 0; i < vertexCount; i++)
{
const Float3 normal = normals[i];
const Float3 tangent = tangents[i];
Float3 n;
Float4 t;
RenderTools::CalculateTangentFrame(n, t, normal, tangent);
normalStream.SetFloat3(i, n);
tangentStream.SetFloat4(i, t);
}
}
else
{
for (uint32 i = 0; i < vertexCount; i++)
{
const Float3 normal = normals[i];
Float3 n;
Float4 t;
RenderTools::CalculateTangentFrame(n, t, normal);
normalStream.SetFloat3(i, n);
}
}
}
if (uvs)
{
auto uvsStream = accessor.TexCoord();
for (uint32 i = 0; i < vertexCount; i++)
uvsStream.SetFloat2(i, uvs[i]);
}
}
return mesh->UpdateMesh(vertexCount, triangleCount, (VB0ElementType*)vertices, vb1.Get(), vb2.HasItems() ? vb2.Get() : nullptr, triangles);
// Vertex Buffer 2 (color-only)
if (colors)
{
GPUVertexLayout* vb2layout = GPUVertexLayout::Get({{ VertexElement::Types::Color, 2, 0, 0, PixelFormat::R8G8B8A8_UNorm }});
if (accessor.AllocateBuffer(MeshBufferType::Vertex2, vertexCount, vb2layout))
return true;
auto colorStream = accessor.Color();
ASSERT(colorStream.IsLinear(PixelFormat::R8G8B8A8_UNorm));
colorStream.SetLinear(colors);
}
return accessor.UpdateMesh(mesh);
}
#if !COMPILE_WITHOUT_CSHARP
@@ -125,21 +147,38 @@ namespace
ASSERT((uint32)MCore::Array::GetLength(trianglesObj) / 3 >= triangleCount);
auto vertices = MCore::Array::GetAddress<Float3>(verticesObj);
auto triangles = MCore::Array::GetAddress<IndexType>(trianglesObj);
const PixelFormat indexFormat = sizeof(IndexType) == 4 ? PixelFormat::R32_UInt : PixelFormat::R16_UInt;
const auto normals = normalsObj ? MCore::Array::GetAddress<Float3>(normalsObj) : nullptr;
const auto tangents = tangentsObj ? MCore::Array::GetAddress<Float3>(tangentsObj) : nullptr;
const auto uvs = uvObj ? MCore::Array::GetAddress<Float2>(uvObj) : nullptr;
const auto colors = colorsObj ? MCore::Array::GetAddress<Color32>(colorsObj) : nullptr;
return UpdateMesh<IndexType>(mesh, vertexCount, triangleCount, vertices, triangles, normals, tangents, uvs, colors);
return UpdateMesh(mesh, vertexCount, triangleCount, indexFormat, vertices, triangles, normals, tangents, uvs, colors);
}
#endif
}
bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const uint32* ib)
{
PRAGMA_DISABLE_DEPRECATION_WARNINGS
return UpdateMesh(vertexCount, triangleCount, vb0, vb1, vb2, ib, false);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const uint16* ib)
{
PRAGMA_DISABLE_DEPRECATION_WARNINGS
return UpdateMesh(vertexCount, triangleCount, vb0, vb1, vb2, ib, true);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const void* ib, bool use16BitIndices)
{
Release();
// Setup GPU resources
PRAGMA_DISABLE_DEPRECATION_WARNINGS
const bool failed = Load(vertexCount, triangleCount, vb0, vb1, vb2, ib, use16BitIndices);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
if (!failed)
{
// Calculate mesh bounds
@@ -152,12 +191,12 @@ bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0Element
bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const uint16* triangles, const Float3* normals, const Float3* tangents, const Float2* uvs, const Color32* colors)
{
return ::UpdateMesh<uint16>(this, vertexCount, triangleCount, vertices, triangles, normals, tangents, uvs, colors);
return ::UpdateMesh(this, vertexCount, triangleCount, PixelFormat::R16_UInt, vertices, triangles, normals, tangents, uvs, colors);
}
bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const uint32* triangles, const Float3* normals, const Float3* tangents, const Float2* uvs, const Color32* colors)
{
return ::UpdateMesh<uint32>(this, vertexCount, triangleCount, vertices, triangles, normals, tangents, uvs, colors);
return ::UpdateMesh(this, vertexCount, triangleCount, PixelFormat::R16_UInt, vertices, triangles, normals, tangents, uvs, colors);
}
bool Mesh::Load(uint32 vertices, uint32 triangles, const void* vb0, const void* vb1, const void* vb2, const void* ib, bool use16BitIndexBuffer)
@@ -169,9 +208,11 @@ bool Mesh::Load(uint32 vertices, uint32 triangles, const void* vb0, const void*
if (vb2)
vbData.Add(vb2);
Array<GPUVertexLayout*, FixedAllocation<3>> vbLayout;
PRAGMA_DISABLE_DEPRECATION_WARNINGS
vbLayout.Add(VB0ElementType::GetLayout());
vbLayout.Add(VB1ElementType::GetLayout());
vbLayout.Add(VB2ElementType::GetLayout());
PRAGMA_ENABLE_DEPRECATION_WARNINGS
return Init(vertices, triangles, vbData, ib, use16BitIndexBuffer, vbLayout);
}
@@ -251,7 +292,7 @@ void Mesh::Draw(const RenderContext& renderContext, const DrawInfo& info, float
for (int32 meshIndex = 0; meshIndex < _index; meshIndex++)
vertexOffset += ((Model*)_model)->LODs[_lodIndex].Meshes[meshIndex].GetVertexCount();
drawCall.Geometry.VertexBuffers[2] = info.VertexColors[_lodIndex];
drawCall.Geometry.VertexBuffersOffsets[2] = vertexOffset * sizeof(VB2ElementType);
drawCall.Geometry.VertexBuffersOffsets[2] = vertexOffset * sizeof(Color32);
}
drawCall.Draw.IndicesCount = _triangles * 3;
drawCall.InstanceCount = 1;
@@ -314,7 +355,7 @@ void Mesh::Draw(const RenderContextBatch& renderContextBatch, const DrawInfo& in
for (int32 meshIndex = 0; meshIndex < _index; meshIndex++)
vertexOffset += ((Model*)_model)->LODs[_lodIndex].Meshes[meshIndex].GetVertexCount();
drawCall.Geometry.VertexBuffers[2] = info.VertexColors[_lodIndex];
drawCall.Geometry.VertexBuffersOffsets[2] = vertexOffset * sizeof(VB2ElementType);
drawCall.Geometry.VertexBuffersOffsets[2] = vertexOffset * sizeof(Color32);
}
drawCall.Draw.IndicesCount = _triangles * 3;
drawCall.InstanceCount = 1;
@@ -364,96 +405,6 @@ void Mesh::Release()
MeshBase::Release();
}
bool Mesh::DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const
{
if (_cachedVertexBuffers[0].IsEmpty())
{
PROFILE_CPU();
auto model = GetModel();
ScopeLock lock(model->Locker);
if (model->IsVirtual())
{
LOG(Error, "Cannot access CPU data of virtual models. Use GPU data download.");
return true;
}
// Fetch chunk with data from drive/memory
const auto chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(GetLODIndex());
if (model->LoadChunk(chunkIndex))
return true;
const auto chunk = model->GetChunk(chunkIndex);
if (!chunk)
{
LOG(Error, "Missing chunk.");
return true;
}
MemoryReadStream stream(chunk->Get(), chunk->Size());
// Seek to find mesh location
for (int32 i = 0; i <= _index; i++)
{
// #MODEL_DATA_FORMAT_USAGE
uint32 vertices;
stream.ReadUint32(&vertices);
uint32 triangles;
stream.ReadUint32(&triangles);
uint32 indicesCount = triangles * 3;
bool use16BitIndexBuffer = indicesCount <= MAX_uint16;
uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
if (vertices == 0 || triangles == 0)
{
LOG(Error, "Invalid mesh data.");
return true;
}
auto vb0 = stream.Move<VB0ElementType>(vertices);
auto vb1 = stream.Move<VB1ElementType>(vertices);
bool hasColors = stream.ReadBool();
VB2ElementType18* vb2 = nullptr;
if (hasColors)
{
vb2 = stream.Move<VB2ElementType18>(vertices);
}
auto ib = stream.Move<byte>(indicesCount * ibStride);
if (i != _index)
continue;
// Cache mesh data
_cachedIndexBufferCount = indicesCount;
_cachedIndexBuffer.Set(ib, indicesCount * ibStride);
_cachedVertexBuffers[0].Set((const byte*)vb0, vertices * sizeof(VB0ElementType));
_cachedVertexBuffers[1].Set((const byte*)vb1, vertices * sizeof(VB1ElementType));
if (hasColors)
_cachedVertexBuffers[2].Set((const byte*)vb2, vertices * sizeof(VB2ElementType));
break;
}
}
switch (type)
{
case MeshBufferType::Index:
result.Link(_cachedIndexBuffer);
count = _cachedIndexBufferCount;
break;
case MeshBufferType::Vertex0:
result.Link(_cachedVertexBuffers[0]);
count = _cachedVertexBuffers[0].Count() / sizeof(VB0ElementType);
break;
case MeshBufferType::Vertex1:
result.Link(_cachedVertexBuffers[1]);
count = _cachedVertexBuffers[1].Count() / sizeof(VB1ElementType);
break;
case MeshBufferType::Vertex2:
result.Link(_cachedVertexBuffers[2]);
count = _cachedVertexBuffers[2].Count() / sizeof(VB2ElementType);
break;
default:
return true;
}
return false;
}
#if !COMPILE_WITHOUT_CSHARP
bool Mesh::UpdateMeshUInt(int32 vertexCount, int32 triangleCount, const MArray* verticesObj, const MArray* trianglesObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj, const MArray* colorsObj)
@@ -466,135 +417,80 @@ bool Mesh::UpdateMeshUShort(int32 vertexCount, int32 triangleCount, const MArray
return ::UpdateMesh<uint16>(this, (uint32)vertexCount, (uint32)triangleCount, verticesObj, trianglesObj, normalsObj, tangentsObj, uvObj, colorsObj);
}
// [Deprecated in v1.10]
enum class InternalBufferType
{
VB0 = 0,
VB1 = 1,
VB2 = 2,
IB16 = 3,
IB32 = 4,
};
MArray* Mesh::DownloadBuffer(bool forceGpu, MTypeObject* resultType, int32 typeI)
{
auto mesh = this;
auto type = (InternalBufferType)typeI;
auto model = mesh->GetModel();
ScopeLock lock(model->Locker);
// [Deprecated in v1.10]
ScopeLock lock(GetModelBase()->Locker);
// Virtual assets always fetch from GPU memory
forceGpu |= model->IsVirtual();
if (!mesh->IsInitialized() && forceGpu)
// Get vertex buffers data from the mesh (CPU or GPU)
MeshAccessor accessor;
MeshBufferType bufferTypes[1] = { MeshBufferType::Vertex0 };
switch ((InternalBufferType)typeI)
{
LOG(Error, "Cannot load mesh data from GPU if it's not loaded.");
return nullptr;
}
MeshBufferType bufferType;
switch (type)
{
case InternalBufferType::VB0:
bufferType = MeshBufferType::Vertex0;
break;
case InternalBufferType::VB1:
bufferType = MeshBufferType::Vertex1;
bufferTypes[0] = MeshBufferType::Vertex1;
break;
case InternalBufferType::VB2:
bufferType = MeshBufferType::Vertex2;
bufferTypes[0] = MeshBufferType::Vertex2;
break;
case InternalBufferType::IB16:
case InternalBufferType::IB32:
bufferType = MeshBufferType::Index;
break;
default:
}
if (accessor.LoadMesh(this, forceGpu, ToSpan(bufferTypes, 1)))
return nullptr;
}
BytesContainer data;
int32 dataCount;
if (forceGpu)
{
// Get data from GPU
// TODO: support reusing the input memory buffer to perform a single copy from staging buffer to the input CPU buffer
auto task = mesh->DownloadDataGPUAsync(bufferType, data);
if (task == nullptr)
return nullptr;
task->Start();
model->Locker.Unlock();
if (task->Wait())
{
LOG(Error, "Task failed.");
return nullptr;
}
model->Locker.Lock();
// Extract elements count from result data
switch (bufferType)
{
case MeshBufferType::Index:
dataCount = data.Length() / (Use16BitIndexBuffer() ? sizeof(uint16) : sizeof(uint32));
break;
case MeshBufferType::Vertex0:
dataCount = data.Length() / sizeof(VB0ElementType);
break;
case MeshBufferType::Vertex1:
dataCount = data.Length() / sizeof(VB1ElementType);
break;
case MeshBufferType::Vertex2:
dataCount = data.Length() / sizeof(VB2ElementType);
break;
}
}
else
{
// Get data from CPU
if (DownloadDataCPU(bufferType, data, dataCount))
return nullptr;
}
auto positionStream = accessor.Position();
auto texCoordStream = accessor.TexCoord();
auto lightmapUVsStream = accessor.TexCoord(1);
auto normalStream = accessor.Normal();
auto tangentStream = accessor.Tangent();
auto colorStream = accessor.Color();
auto count = GetVertexCount();
// Convert into managed array
MArray* result = MCore::Array::New(MCore::Type::GetClass(INTERNAL_TYPE_OBJECT_GET(resultType)), dataCount);
MArray* result = MCore::Array::New(MCore::Type::GetClass(INTERNAL_TYPE_OBJECT_GET(resultType)), count);
void* managedArrayPtr = MCore::Array::GetAddress(result);
const int32 elementSize = data.Length() / dataCount;
switch (type)
switch ((InternalBufferType)typeI)
{
PRAGMA_DISABLE_DEPRECATION_WARNINGS
case InternalBufferType::VB0:
CHECK_RETURN(positionStream.IsValid(), nullptr);
for (int32 i = 0; i < count; i++)
{
auto& dst = ((VB0ElementType*)managedArrayPtr)[i];
dst.Position = positionStream.GetFloat3(i);
}
break;
case InternalBufferType::VB1:
for (int32 i = 0; i < count; i++)
{
auto& dst = ((VB1ElementType*)managedArrayPtr)[i];
if (texCoordStream.IsValid())
dst.TexCoord = texCoordStream.GetFloat2(i);
if (normalStream.IsValid())
dst.Normal = normalStream.GetFloat3(i);
if (tangentStream.IsValid())
dst.Tangent = tangentStream.GetFloat4(i);
if (lightmapUVsStream.IsValid())
dst.LightmapUVs = lightmapUVsStream.GetFloat2(i);
}
break;
case InternalBufferType::VB2:
{
Platform::MemoryCopy(managedArrayPtr, data.Get(), data.Length());
break;
}
case InternalBufferType::IB16:
{
if (elementSize == sizeof(uint16))
if (colorStream.IsValid())
{
Platform::MemoryCopy(managedArrayPtr, data.Get(), data.Length());
}
else
{
auto dst = (uint16*)managedArrayPtr;
auto src = (uint32*)data.Get();
for (int32 i = 0; i < dataCount; i++)
dst[i] = src[i];
for (int32 i = 0; i < count; i++)
{
auto& dst = ((VB2ElementType*)managedArrayPtr)[i];
dst.Color = Color32(colorStream.GetFloat4(i));
}
}
break;
}
case InternalBufferType::IB32:
{
if (elementSize == sizeof(uint16))
{
auto dst = (uint32*)managedArrayPtr;
auto src = (uint16*)data.Get();
for (int32 i = 0; i < dataCount; i++)
dst[i] = src[i];
}
else
{
Platform::MemoryCopy(managedArrayPtr, data.Get(), data.Length());
}
break;
}
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
return result;

View File

@@ -9,7 +9,9 @@ namespace FlaxEngine
{
/// <summary>
/// The Vertex Buffer 0 structure format.
/// [Deprecated in v1.10]
/// </summary>
[Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")]
public struct Vertex0
{
/// <summary>
@@ -20,7 +22,9 @@ namespace FlaxEngine
/// <summary>
/// The Vertex Buffer 1 structure format.
/// [Deprecated in v1.10]
/// </summary>
[Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")]
public struct Vertex1
{
/// <summary>
@@ -46,7 +50,9 @@ namespace FlaxEngine
/// <summary>
/// The Vertex Buffer 2 structure format.
/// [Deprecated in v1.10]
/// </summary>
[Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")]
public struct Vertex2
{
/// <summary>
@@ -57,7 +63,9 @@ namespace FlaxEngine
/// <summary>
/// The raw Vertex Buffer structure format.
/// [Deprecated in v1.10]
/// </summary>
[Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")]
public struct Vertex
{
/// <summary>
@@ -425,6 +433,10 @@ namespace FlaxEngine
UpdateMesh(Utils.ConvertCollection(vertices), triangles, Utils.ConvertCollection(normals), Utils.ConvertCollection(tangents), Utils.ConvertCollection(uv), colors);
}
/// <summary>
/// [Deprecated in v1.10]
/// </summary>
[Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")]
internal enum InternalBufferType
{
VB0 = 0,
@@ -436,9 +448,11 @@ namespace FlaxEngine
/// <summary>
/// Downloads the first vertex buffer that contains mesh vertices data. To download data from GPU set <paramref name="forceGpu"/> to true and call this method from the thread other than main thread (see <see cref="Platform.IsInMainThread"/>).
/// [Deprecated in v1.10]
/// </summary>
/// <param name="forceGpu">If set to <c>true</c> the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data.</param>
/// <returns>The gathered data.</returns>
[Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")]
public Vertex0[] DownloadVertexBuffer0(bool forceGpu = false)
{
var result = (Vertex0[])Internal_DownloadBuffer(__unmanagedPtr, forceGpu, typeof(Vertex0), (int)InternalBufferType.VB0);
@@ -449,9 +463,11 @@ namespace FlaxEngine
/// <summary>
/// Downloads the second vertex buffer that contains mesh vertices data. To download data from GPU set <paramref name="forceGpu"/> to true and call this method from the thread other than main thread (see <see cref="Platform.IsInMainThread"/>).
/// [Deprecated in v1.10]
/// </summary>
/// <param name="forceGpu">If set to <c>true</c> the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data.</param>
/// <returns>The gathered data.</returns>
[Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")]
public Vertex1[] DownloadVertexBuffer1(bool forceGpu = false)
{
var result = (Vertex1[])Internal_DownloadBuffer(__unmanagedPtr, forceGpu, typeof(Vertex1), (int)InternalBufferType.VB1);
@@ -462,12 +478,14 @@ namespace FlaxEngine
/// <summary>
/// Downloads the third vertex buffer that contains mesh vertices data. To download data from GPU set <paramref name="forceGpu"/> to true and call this method from the thread other than main thread (see <see cref="Platform.IsInMainThread"/>).
/// [Deprecated in v1.10]
/// </summary>
/// <remarks>
/// If mesh has no vertex colors (stored in vertex buffer 2) the returned value is null.
/// </remarks>
/// <param name="forceGpu">If set to <c>true</c> the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data.</param>
/// <returns>The gathered data or null if mesh has no vertex colors.</returns>
[Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")]
public Vertex2[] DownloadVertexBuffer2(bool forceGpu = false)
{
if (!HasVertexColors)
@@ -480,14 +498,13 @@ namespace FlaxEngine
/// <summary>
/// Downloads the raw vertex buffer that contains mesh vertices data. To download data from GPU set <paramref name="forceGpu"/> to true and call this method from the thread other than main thread (see <see cref="Platform.IsInMainThread"/>).
/// [Deprecated in v1.10]
/// </summary>
/// <param name="forceGpu">If set to <c>true</c> the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data.</param>
/// <returns>The gathered data.</returns>
[Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")]
public Vertex[] DownloadVertexBuffer(bool forceGpu = false)
{
// TODO: perform data conversion on C++ side to make it faster
// TODO: implement batched data download (3 buffers at once) to reduce stall
var vb0 = DownloadVertexBuffer0(forceGpu);
var vb1 = DownloadVertexBuffer1(forceGpu);
var vb2 = DownloadVertexBuffer2(forceGpu);
@@ -519,33 +536,5 @@ namespace FlaxEngine
return result;
}
/// <summary>
/// Downloads the index buffer that contains mesh triangles data. To download data from GPU set <paramref name="forceGpu"/> to true and call this method from the thread other than main thread (see <see cref="Platform.IsInMainThread"/>).
/// </summary>
/// <remarks>If mesh index buffer format (see <see cref="MeshBase.IndexBufferFormat"/>) is <see cref="PixelFormat.R16_UInt"/> then it's faster to call .</remarks>
/// <param name="forceGpu">If set to <c>true</c> the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data.</param>
/// <returns>The gathered data.</returns>
public uint[] DownloadIndexBuffer(bool forceGpu = false)
{
var result = (uint[])Internal_DownloadBuffer(__unmanagedPtr, forceGpu, typeof(uint), (int)InternalBufferType.IB32);
if (result == null)
throw new Exception("Failed to download mesh data.");
return result;
}
/// <summary>
/// Downloads the index buffer that contains mesh triangles data. To download data from GPU set <paramref name="forceGpu"/> to true and call this method from the thread other than main thread (see <see cref="Platform.IsInMainThread"/>).
/// </summary>
/// <remarks>If mesh index buffer format (see <see cref="MeshBase.IndexBufferFormat"/>) is <see cref="PixelFormat.R32_UInt"/> then data won't be downloaded.</remarks>
/// <param name="forceGpu">If set to <c>true</c> the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data.</param>
/// <returns>The gathered data.</returns>
public ushort[] DownloadIndexBufferUShort(bool forceGpu = false)
{
var result = (ushort[])Internal_DownloadBuffer(__unmanagedPtr, forceGpu, typeof(ushort), (int)InternalBufferType.IB16);
if (result == null)
throw new Exception("Failed to download mesh data.");
return result;
}
}
}

View File

@@ -43,13 +43,14 @@ public:
}
/// <summary>
/// Lightmap texture coordinates channel index.
/// Lightmap texture coordinates channel index. Value -1 indicates that channel is not available.
/// </summary>
API_FIELD() int32 LightmapUVsIndex = -1;
public:
/// <summary>
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
/// [Deprecated in v1.10]
/// </summary>
/// <param name="vertexCount">The amount of vertices in the vertex buffer.</param>
/// <param name="triangleCount">The amount of triangles in the index buffer.</param>
@@ -58,13 +59,12 @@ public:
/// <param name="vb2">The third vertex buffer data.</param>
/// <param name="ib">The index buffer in clockwise order.</param>
/// <returns>True if failed, otherwise false.</returns>
FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const uint32* ib)
{
return UpdateMesh(vertexCount, triangleCount, vb0, vb1, vb2, ib, false);
}
DEPRECATED("Use MeshAccessor or UpdateMesh with separate vertex attribute arrays instead.")
bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const uint32* ib);
/// <summary>
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
/// [Deprecated in v1.10]
/// </summary>
/// <param name="vertexCount">The amount of vertices in the vertex buffer.</param>
/// <param name="triangleCount">The amount of triangles in the index buffer.</param>
@@ -73,15 +73,14 @@ public:
/// <param name="vb2">The third vertex buffer data.</param>
/// <param name="ib">The index buffer in clockwise order.</param>
/// <returns>True if failed, otherwise false.</returns>
FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const uint16* ib)
{
return UpdateMesh(vertexCount, triangleCount, vb0, vb1, vb2, ib, true);
}
DEPRECATED("Use MeshAccessor or UpdateMesh with separate vertex attribute arrays instead.")
bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const uint16* ib);
/// <summary>
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
/// Can be used only for virtual assets (see <see cref="Asset.IsVirtual"/> and <see cref="Content.CreateVirtualAsset{T}"/>).
/// Mesh data will be cached and uploaded to the GPU with a delay.
/// [Deprecated in v1.10]
/// </summary>
/// <param name="vertexCount">The amount of vertices in the vertex buffer.</param>
/// <param name="triangleCount">The amount of triangles in the index buffer.</param>
@@ -91,6 +90,7 @@ public:
/// <param name="ib">The index buffer in clockwise order.</param>
/// <param name="use16BitIndices">True if index buffer uses 16-bit index buffer, otherwise 32-bit.</param>
/// <returns>True if failed, otherwise false.</returns>
DEPRECATED("Use MeshAccessor or UpdateMesh with separate vertex attribute arrays instead.")
bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const void* ib, bool use16BitIndices);
/// <summary>
@@ -174,7 +174,6 @@ public:
// [MeshBase]
bool Init(uint32 vertices, uint32 triangles, const Array<const void*, FixedAllocation<3>>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array<GPUVertexLayout*, FixedAllocation<3>>& vbLayout) override;
void Release() override;
bool DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const override;
private:
// Internal bindings

View File

@@ -0,0 +1,728 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
using System;
using System.Runtime.InteropServices;
namespace FlaxEngine
{
/// <summary>
/// General purpose utility for accessing mesh data (both read and write).
/// </summary>
public class MeshAccessor
{
/// <summary>
/// Mesh data stream.
/// </summary>
public unsafe ref struct Stream
{
private Span<byte> _data;
private PixelFormat _format;
private int _stride;
private readonly PixelFormatSampler _sampler;
internal Stream(Span<byte> data, PixelFormat format, int stride)
{
_data = data;
_stride = stride;
if (PixelFormatSampler.Get(format, out _sampler))
{
_format = format;
}
else if (format != PixelFormat.Unknown)
{
Debug.LogError($"Unsupported pixel format '{format}' to sample vertex attribute.");
}
}
/// <summary>
/// Gets the raw data block.
/// </summary>
public Span<byte> Data => _data;
/// <summary>
/// Gets the format of the data.
/// </summary>
public PixelFormat Format => _format;
/// <summary>
/// Gets the stride (in bytes) of the data.
/// </summary>
public int Stride => _stride;
/// <summary>
/// Gets the count of the items in the stride.
/// </summary>
public int Count => _data.Length / _stride;
/// <summary>
/// Returns true if stream is valid.
/// </summary>
public bool IsValid => _format != PixelFormat.Unknown;
/// <summary>
/// Checks if the stream can use <seealso cref="SetLinear"/> via a single memory copy.
/// </summary>
/// <param name="expectedFormat">Source data format.</param>
/// <returns>True if stream is linear and format matches expected input data.</returns>
public bool IsLinear(PixelFormat expectedFormat)
{
return _format == expectedFormat && _stride == PixelFormatExtensions.SizeInBytes(_format);
}
/// <summary>
/// Reads a float value from a given item.
/// </summary>
/// <param name="index">Zero-based index of the item.</param>
/// <returns>Loaded value.</returns>
public float GetFloat(int index)
{
fixed (byte* data = _data)
return _sampler.Read(data + index * _stride).X;
}
/// <summary>
/// Reads a Float2 value from a given item.
/// </summary>
/// <param name="index">Zero-based index of the item.</param>
/// <returns>Loaded value.</returns>
public Float2 GetFloat2(int index)
{
fixed (byte* data = _data)
return new Float2(_sampler.Read(data + index * _stride));
}
/// <summary>
/// Reads a Float3 value from a given item.
/// </summary>
/// <param name="index">Zero-based index of the item.</param>
/// <returns>Loaded value.</returns>
public Float3 GetFloat3(int index)
{
fixed (byte* data = _data)
return new Float3(_sampler.Read(data + index * _stride));
}
/// <summary>
/// Reads a Float4 value from a given item.
/// </summary>
/// <param name="index">Zero-based index of the item.</param>
/// <returns>Loaded value.</returns>
public Float4 GetFloat4(int index)
{
fixed (byte* data = _data)
return _sampler.Read(data + index * _stride);
}
/// <summary>
/// Writes a float value to a given item.
/// </summary>
/// <param name="index">Zero-based index of the item.</param>
/// <param name="value">Value to assign.</param>
public void SetFloat(int index, float value)
{
var v = new Float4(value);
fixed (byte* data = _data)
_sampler.Write(data + index * _stride, ref v);
}
/// <summary>
/// Writes a Float2 value to a given item.
/// </summary>
/// <param name="index">Zero-based index of the item.</param>
/// <param name="value">Value to assign.</param>
public void SetFloat2(int index, Float2 value)
{
var v = new Float4(value, 0.0f, 0.0f);
fixed (byte* data = _data)
_sampler.Write(data + index * _stride, ref v);
}
/// <summary>
/// Writes a Float3 value to a given item.
/// </summary>
/// <param name="index">Zero-based index of the item.</param>
/// <param name="value">Value to assign.</param>
public void SetFloat3(int index, Float3 value)
{
var v = new Float4(value, 0.0f);
fixed (byte* data = _data)
_sampler.Write(data + index * _stride, ref v);
}
/// <summary>
/// Writes a Float4 value to a given item.
/// </summary>
/// <param name="index">Zero-based index of the item.</param>
/// <param name="value">Value to assign.</param>
public void SetFloat4(int index, Float4 value)
{
fixed (byte* data = _data)
_sampler.Write(data + index * _stride, ref value);
}
/// <summary>
/// Copies a linear block of data into the stream. Assumes <seealso cref="IsLinear"/> returned true for the format of the input data.
/// </summary>
/// <remarks>Check input data and stream type with IsLinear before calling.</remarks>
/// <param name="data">Pointer to the source data.</param>
public void SetLinear(IntPtr data)
{
new Span<byte>(data.ToPointer(), _data.Length).CopyTo(_data);
}
/// <summary>
/// Copies the contents of the input <see cref="T:System.Span`1"/> into the elements of this stream.
/// </summary>
/// <param name="src">The source <see cref="T:System.Span`1"/>.</param>
public void Set(Span<Float2> src)
{
if (IsLinear(PixelFormat.R32G32_Float))
{
src.CopyTo(MemoryMarshal.Cast<byte, Float2>(_data));
}
else
{
var count = Count;
fixed (byte* data = _data)
{
for (int i = 0; i < count; i++)
{
var v = new Float4(src[i], 0.0f, 0.0f);
_sampler.Write(data + i * _stride, ref v);
}
}
}
}
/// <summary>
/// Copies the contents of the input <see cref="T:System.Span`1"/> into the elements of this stream.
/// </summary>
/// <param name="src">The source <see cref="T:System.Span`1"/>.</param>
public void Set(Span<Float3> src)
{
if (IsLinear(PixelFormat.R32G32B32_Float))
{
src.CopyTo(MemoryMarshal.Cast<byte, Float3>(_data));
}
else
{
var count = Count;
fixed (byte* data = _data)
{
for (int i = 0; i < count; i++)
{
var v = new Float4(src[i], 0.0f);
_sampler.Write(data + i * _stride, ref v);
}
}
}
}
/// <summary>
/// Copies the contents of the input <see cref="T:System.Span`1"/> into the elements of this stream.
/// </summary>
/// <param name="src">The source <see cref="T:System.Span`1"/>.</param>
public void Set(Span<Color> src)
{
if (IsLinear(PixelFormat.R32G32B32A32_Float))
{
src.CopyTo(MemoryMarshal.Cast<byte, Color>(_data));
}
else
{
var count = Count;
fixed (byte* data = _data)
{
for (int i = 0; i < count; i++)
{
var v = (Float4)src[i];
_sampler.Write(data + i * _stride, ref v);
}
}
}
}
/// <summary>
/// Copies the contents of this stream into a destination <see cref="T:System.Span`1" />.
/// </summary>
/// <param name="dst">The destination <see cref="T:System.Span`1" />.</param>
public void CopyTo(Span<Float2> dst)
{
if (IsLinear(PixelFormat.R32G32_Float))
{
_data.CopyTo(MemoryMarshal.Cast<Float2, byte>(dst));
}
else
{
var count = Count;
fixed (byte* data = _data)
{
for (int i = 0; i < count; i++)
{
dst[i] = new Float2(_sampler.Read(data + i * _stride));
}
}
}
}
/// <summary>
/// Copies the contents of this stream into a destination <see cref="T:System.Span`1" />.
/// </summary>
/// <param name="dst">The destination <see cref="T:System.Span`1" />.</param>
public void CopyTo(Span<Float3> dst)
{
if (IsLinear(PixelFormat.R32G32B32_Float))
{
_data.CopyTo(MemoryMarshal.Cast<Float3, byte>(dst));
}
else
{
var count = Count;
fixed (byte* data = _data)
{
for (int i = 0; i < count; i++)
{
dst[i] = new Float3(_sampler.Read(data + i * _stride));
}
}
}
}
/// <summary>
/// Copies the contents of this stream into a destination <see cref="T:System.Span`1" />.
/// </summary>
/// <param name="dst">The destination <see cref="T:System.Span`1" />.</param>
public void CopyTo(Span<Color> dst)
{
if (IsLinear(PixelFormat.R32G32B32A32_Float))
{
_data.CopyTo(MemoryMarshal.Cast<Color, byte>(dst));
}
else
{
var count = Count;
fixed (byte* data = _data)
{
for (int i = 0; i < count; i++)
{
dst[i] = (Color)_sampler.Read(data + i * _stride);
}
}
}
}
}
private byte[][] _data = new byte[(int)MeshBufferType.MAX][];
private PixelFormat[] _formats = new PixelFormat[(int)MeshBufferType.MAX];
private GPUVertexLayout[] _layouts = new GPUVertexLayout[(int)MeshBufferType.MAX];
/// <summary>
/// Loads the data from the mesh.
/// </summary>
/// <param name="mesh">The source mesh object to access.</param>
/// <param name="forceGpu">If set to <c>true</c> the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data.</param>
/// <param name="buffers">Custom list of mesh buffers to load. Use empty value to access all of them.</param>
/// <returns>True if failed, otherwise false.</returns>
public bool LoadMesh(MeshBase mesh, bool forceGpu = false, Span<MeshBufferType> buffers = new())
{
if (mesh == null)
return true;
Span<MeshBufferType> allBuffers = stackalloc MeshBufferType[(int)MeshBufferType.MAX] { MeshBufferType.Index, MeshBufferType.Vertex0, MeshBufferType.Vertex1, MeshBufferType.Vertex2 };
var buffersLocal = buffers.IsEmpty ? allBuffers : buffers;
if (mesh.DownloadData(buffersLocal.ToArray(), out var meshBuffers, out var meshLayouts, forceGpu))
return true;
for (int i = 0; i < buffersLocal.Length; i++)
{
_data[(int)buffersLocal[i]] = meshBuffers[i];
_layouts[(int)buffersLocal[i]] = meshLayouts[i];
}
_formats[(int)MeshBufferType.Index] = mesh.Use16BitIndexBuffer ? PixelFormat.R16_UInt : PixelFormat.R32_UInt;
return false;
}
/// <summary>
/// Loads the data from the provided mesh buffer.
/// </summary>
/// <param name="bufferType">Type of the mesh buffer to load.</param>
/// <param name="bufferData">Data used by that buffer.</param>
/// <param name="layout">Layout of the elements in the buffer.</param>
/// <returns>True if failed, otherwise false.</returns>
public bool LoadBuffer(MeshBufferType bufferType, Span<byte> bufferData, GPUVertexLayout layout)
{
if (bufferData.IsEmpty)
return true;
if (layout == null)
return true;
int idx = (int)bufferType;
_data[idx] = bufferData.ToArray();
_layouts[idx] = layout;
return false;
}
/// <summary>
/// Allocates the data for the mesh vertex buffer.
/// </summary>
/// <param name="bufferType">Type of the mesh buffer to initialize.</param>
/// <param name="count">Amount of items in the buffer.</param>
/// <param name="layout">Layout of the elements in the buffer.</param>
/// <returns>True if failed, otherwise false.</returns>
public bool AllocateBuffer(MeshBufferType bufferType, int count, GPUVertexLayout layout)
{
if (count <= 0)
return true;
if (layout == null)
return true;
int idx = (int)bufferType;
_data[idx] = new byte[count * layout.Stride];
_layouts[idx] = layout;
return false;
}
/// <summary>
/// Allocates the data for the mesh buffer.
/// </summary>
/// <param name="bufferType">Type of the mesh buffer to initialize.</param>
/// <param name="count">Amount of items in the buffer.</param>
/// <param name="format">Format of the elements in the buffer.</param>
/// <returns>True if failed, otherwise false.</returns>
public bool AllocateBuffer(MeshBufferType bufferType, int count, PixelFormat format)
{
if (count <= 0)
return true;
int stride = PixelFormatExtensions.SizeInBytes(format);
if (stride <= 0)
return true;
int idx = (int)bufferType;
_data[idx] = new byte[count * stride];
_formats[idx] = format;
return false;
}
/// <summary>
/// Updates the mesh vertex and index buffers with data assigned to the accessor (eg. via AllocateBuffer).
/// </summary>
/// <param name="mesh">The target mesh to update.</param>
/// <param name="calculateBounds">True if auto-calculate mesh local bounding box based on input positions stream. Otherwise, mesh bound swill stay unchanged.</param>
/// <returns>True if failed, otherwise false.</returns>
public unsafe bool UpdateMesh(MeshBase mesh, bool calculateBounds = true)
{
if (mesh == null)
return true;
int IB = (int)MeshBufferType.Index;
int VB0 = (int)MeshBufferType.Vertex0;
int VB1 = (int)MeshBufferType.Vertex1;
int VB2 = (int)MeshBufferType.Vertex2;
uint vertices = 0, triangles = 0;
fixed (byte* data0Ptr = _data[0])
fixed (byte* data1Ptr = _data[1])
fixed (byte* data2Ptr = _data[2])
fixed (byte* data3Ptr = _data[3])
{
Span<IntPtr> dataPtr = stackalloc IntPtr[(int)MeshBufferType.MAX] { new IntPtr(data0Ptr), new IntPtr(data1Ptr), new IntPtr(data2Ptr), new IntPtr(data3Ptr) };
IntPtr ibData = IntPtr.Zero;
bool use16BitIndexBuffer = false;
IntPtr[] vbData = new IntPtr[3];
GPUVertexLayout[] vbLayout = new GPUVertexLayout[3];
if (_data[VB0] != null)
{
vbData[0] = dataPtr[VB0];
vbLayout[0] = _layouts[VB0];
vertices = (uint)_data[VB0].Length / _layouts[VB0].Stride;
}
if (_data[VB1] != null)
{
vbData[1] = dataPtr[VB1];
vbLayout[1] = _layouts[VB1];
vertices = (uint)_data[VB1].Length / _layouts[VB1].Stride;
}
if (_data[VB2] != null)
{
vbData[2] = dataPtr[VB2];
vbLayout[2] = _layouts[VB2];
vertices = (uint)_data[VB2].Length / _layouts[VB2].Stride;
}
if (_data[IB] != null && _formats[IB] != PixelFormat.Unknown)
{
ibData = dataPtr[IB];
use16BitIndexBuffer = _formats[IB] == PixelFormat.R16_UInt;
triangles = (uint)(_data[IB].Length / PixelFormatExtensions.SizeInBytes(_formats[IB]));
}
if (mesh.Init(vertices, triangles, vbData, ibData, use16BitIndexBuffer, vbLayout))
return true;
}
if (calculateBounds)
{
// Calculate mesh bounds
BoundingBox bounds;
var positionStream = Position();
if (!positionStream.IsValid)
return true;
if (positionStream.IsLinear(PixelFormat.R32G32B32_Float))
{
Span<byte> positionData = positionStream.Data;
BoundingBox.FromPoints(MemoryMarshal.Cast<byte, Float3>(positionData), out bounds);
}
else
{
Float3 min = Float3.Maximum, max = Float3.Minimum;
for (int i = 0; i < vertices; i++)
{
Float3 pos = positionStream.GetFloat3(i);
Float3.Min(ref min, ref pos, out min);
Float3.Max(ref max, ref pos, out max);
}
bounds = new BoundingBox(min, max);
}
mesh.SetBounds(ref bounds);
}
return false;
}
/// <summary>
/// Access stream with index buffer.
/// </summary>
/// <returns>Mesh data stream (might be invalid if data is not provided).</returns>
public Stream Index()
{
Span<byte> data = new Span<byte>();
PixelFormat format = PixelFormat.Unknown;
int stride = 0;
var ib = _data[(int)MeshBufferType.Index];
if (ib != null)
{
data = ib;
format = _formats[(int)MeshBufferType.Index];
stride = PixelFormatExtensions.SizeInBytes(format);
}
return new Stream(data, format, stride);
}
/// <summary>
/// Access stream with a specific vertex attribute.
/// </summary>
/// <param name="attribute">Type of the attribute.</param>
/// <returns>Mesh data stream (might be invalid if attribute is not provided).</returns>
public Stream Attribute(VertexElement.Types attribute)
{
Span<byte> data = new Span<byte>();
PixelFormat format = PixelFormat.Unknown;
int stride = 0;
for (int vbIndex = 0; vbIndex < 3 && format == PixelFormat.Unknown; vbIndex++)
{
int idx = vbIndex + 1;
var layout = _layouts[idx];
if (!layout)
continue;
foreach (VertexElement e in layout.Elements)
{
var vb = _data[idx];
if (e.Type == attribute && vb != null)
{
data = new Span<byte>(vb).Slice(e.Offset);
format = e.Format;
stride = (int)layout.Stride;
break;
}
}
}
return new Stream(data, format, stride);
}
/// <summary>
/// Access stream with vertex position attribute.
/// </summary>
/// <returns>Mesh data stream (might be invalid if attribute is not provided).</returns>
public Stream Position()
{
return Attribute(VertexElement.Types.Position);
}
/// <summary>
/// Access stream with vertex color attribute.
/// </summary>
/// <returns>Mesh data stream (might be invalid if attribute is not provided).</returns>
public Stream Color()
{
return Attribute(VertexElement.Types.Color);
}
/// <summary>
/// Access stream with vertex normal vector attribute.
/// </summary>
/// <returns>Mesh data stream (might be invalid if attribute is not provided).</returns>
public Stream Normal()
{
return Attribute(VertexElement.Types.Normal);
}
/// <summary>
/// Access stream with vertex tangent vector attribute.
/// </summary>
/// <returns>Mesh data stream (might be invalid if attribute is not provided).</returns>
public Stream Tangent()
{
return Attribute(VertexElement.Types.Tangent);
}
/// <summary>
/// Access stream with vertex skeleton bones blend indices attribute.
/// </summary>
/// <returns>Mesh data stream (might be invalid if attribute is not provided).</returns>
public Stream BlendIndices()
{
return Attribute(VertexElement.Types.BlendIndices);
}
/// <summary>
/// Access stream with vertex skeleton bones blend weights attribute.
/// </summary>
/// <returns>Mesh data stream (might be invalid if attribute is not provided).</returns>
public Stream BlendWeights()
{
return Attribute(VertexElement.Types.BlendWeights);
}
/// <summary>
/// Access stream with vertex texture coordinates attribute (specific UV channel).
/// </summary>
/// <param name="channel">UV channel index (zero-based).</param>
/// <returns>Mesh data stream (might be invalid if attribute is not provided).</returns>
public Stream TexCoord(int channel = 0)
{
return Attribute((VertexElement.Types)((byte)VertexElement.Types.TexCoord0 + channel));
}
/// <summary>
/// Gets or sets the vertex positions. Null if <see cref="VertexElement.Types.Position"/> does not exist in vertex buffers of the mesh.
/// </summary>
/// <remarks>Uses <see cref="Position"/> stream to read or write data to the vertex buffer.</remarks>
public Float3[] Positions
{
get => GetStreamFloat3(VertexElement.Types.Position);
set => SetStreamFloat3(VertexElement.Types.Position, value);
}
/// <summary>
/// Gets or sets the vertex colors. Null if <see cref="VertexElement.Types.Color"/> does not exist in vertex buffers of the mesh.
/// </summary>
/// <remarks>Uses <see cref="Color"/> stream to read or write data to the vertex buffer.</remarks>
public Color[] Colors
{
get => GetStreamColor(VertexElement.Types.Color);
set => SetStreamColor(VertexElement.Types.Color, value);
}
/// <summary>
/// Gets or sets the vertex normal vectors (unpacked, normalized). Null if <see cref="VertexElement.Types.Normal"/> does not exist in vertex buffers of the mesh.
/// </summary>
/// <remarks>Uses <see cref="Normal"/> stream to read or write data to the vertex buffer.</remarks>
public Float3[] Normals
{
get => GetStreamFloat3(VertexElement.Types.Normal, UnpackNormal);
set => SetStreamFloat3(VertexElement.Types.Normal, value, PackNormal);
}
/// <summary>
/// Gets or sets the vertex UVs (texcoord channel 0). Null if <see cref="VertexElement.Types.TexCoord"/> does not exist in vertex buffers of the mesh.
/// </summary>
/// <remarks>Uses <see cref="TexCoord"/> stream to read or write data to the vertex buffer.</remarks>
public Float2[] TexCoords
{
get => GetStreamFloat2(VertexElement.Types.TexCoord);
set => SetStreamFloat2(VertexElement.Types.TexCoord, value);
}
private delegate void TransformDelegate3(ref Float3 value);
private Float3[] GetStreamFloat3(VertexElement.Types attribute, TransformDelegate3 transform = null)
{
Float3[] result = null;
var stream = Attribute(attribute);
if (stream.IsValid)
{
result = new Float3[stream.Count];
stream.CopyTo(result);
if (transform != null)
{
for (int i = 0; i < result.Length; i++)
transform(ref result[i]);
}
}
return result;
}
private void SetStreamFloat3(VertexElement.Types attribute, Float3[] value, TransformDelegate3 transform = null)
{
var stream = Attribute(attribute);
if (stream.IsValid)
{
if (transform != null)
{
// TODO: transform in-place?
value = (Float3[])value.Clone();
for (int i = 0; i < value.Length; i++)
transform(ref value[i]);
}
stream.Set(value);
}
}
private Float2[] GetStreamFloat2(VertexElement.Types attribute)
{
Float2[] result = null;
var stream = Attribute(attribute);
if (stream.IsValid)
{
result = new Float2[stream.Count];
stream.CopyTo(result);
}
return result;
}
private void SetStreamFloat2(VertexElement.Types attribute, Float2[] value)
{
var stream = Attribute(attribute);
if (stream.IsValid)
{
stream.Set(value);
}
}
private Color[] GetStreamColor(VertexElement.Types attribute)
{
Color[] result = null;
var stream = Attribute(attribute);
if (stream.IsValid)
{
result = new Color[stream.Count];
stream.CopyTo(result);
}
return result;
}
private void SetStreamColor(VertexElement.Types attribute, Color[] value)
{
var stream = Attribute(attribute);
if (stream.IsValid)
{
stream.Set(value);
}
}
private static void UnpackNormal(ref Float3 value)
{
// [0; 1] -> [-1; 1]
value = value * 2.0f - 1.0f;
}
private static void PackNormal(ref Float3 value)
{
// [-1; 1] -> [0; 1]
value = value * 0.5f + 0.5f;
}
}
}

View File

@@ -0,0 +1,185 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#pragma once
#include "Types.h"
#include "Engine/Core/Types/DataContainer.h"
#include "Engine/Graphics/PixelFormat.h"
#include "Engine/Graphics/PixelFormatSampler.h"
#include "Engine/Graphics/Shaders/VertexElement.h"
/// <summary>
/// General purpose utility for accessing mesh data (both read and write).
/// </summary>
class FLAXENGINE_API MeshAccessor
{
public:
/// <summary>
/// Mesh data stream.
/// </summary>
struct Stream
{
friend MeshAccessor;
private:
Span<byte> _data;
PixelFormat _format;
int32 _stride;
PixelFormatSampler _sampler;
Stream(Span<byte> data, PixelFormat format, int32 stride);
public:
Span<byte> GetData() const;
PixelFormat GetFormat() const;
int32 GetStride() const;
int32 GetCount() const;
bool IsValid() const;
bool IsLinear(PixelFormat expectedFormat) const;
FORCE_INLINE float GetFloat(int32 index) const
{
return _sampler.Read(_data.Get() + index * _stride).X;
}
FORCE_INLINE Float2 GetFloat2(int32 index) const
{
return Float2(_sampler.Read(_data.Get() + index * _stride));
}
FORCE_INLINE Float3 GetFloat3(int32 index) const
{
return Float3(_sampler.Read(_data.Get() + index * _stride));
}
FORCE_INLINE Float4 GetFloat4(int32 index) const
{
return _sampler.Read(_data.Get() + index * _stride);
}
FORCE_INLINE void SetFloat(int32 index, const float& value)
{
_sampler.Write(_data.Get() + index * _stride, Float4(value));
}
FORCE_INLINE void SetFloat2(int32 index, const Float2& value)
{
_sampler.Write(_data.Get() + index * _stride, Float4(value));
}
FORCE_INLINE void SetFloat3(int32 index, const Float3& value)
{
_sampler.Write(_data.Get() + index * _stride, Float4(value));
}
FORCE_INLINE void SetFloat4(int32 index, const Float4& value)
{
_sampler.Write(_data.Get() + index * _stride, value);
}
// Check input data and stream type with IsLinear before calling.
void SetLinear(const void* data);
};
private:
BytesContainer _data[(int32)MeshBufferType::MAX];
PixelFormat _formats[(int32)MeshBufferType::MAX] = {};
GPUVertexLayout* _layouts[(int32)MeshBufferType::MAX] = {};
public:
/// <summary>
/// Loads the data from the mesh.
/// </summary>
/// <param name="mesh">The source mesh object to access.</param>
/// <param name="forceGpu">If set to <c>true</c> the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data.</param>
/// <param name="buffers">Custom list of mesh buffers to load. Use empty value to access all of them.</param>
/// <returns>True if failed, otherwise false.</returns>
bool LoadMesh(const MeshBase* mesh, bool forceGpu = false, Span<MeshBufferType> buffers = Span<MeshBufferType>());
/// <summary>
/// Loads the data from the provided mesh buffer.
/// </summary>
/// <param name="bufferType">Type of the mesh buffer to load.</param>
/// <param name="bufferData">Data used by that buffer.</param>
/// <param name="layout">Layout of the elements in the buffer.</param>
/// <returns>True if failed, otherwise false.</returns>
bool LoadBuffer(MeshBufferType bufferType, Span<byte> bufferData, GPUVertexLayout* layout);
// Used internally via ModelBase::LoadMesh.
bool LoadFromMeshData(const void* meshDataPtr);
/// <summary>
/// Allocates the data for the mesh vertex buffer.
/// </summary>
/// <param name="bufferType">Type of the mesh buffer to initialize.</param>
/// <param name="count">Amount of items in the buffer.</param>
/// <param name="layout">Layout of the elements in the buffer.</param>
/// <returns>True if failed, otherwise false.</returns>
bool AllocateBuffer(MeshBufferType bufferType, int32 count, GPUVertexLayout* layout);
/// <summary>
/// Allocates the data for the mesh buffer.
/// </summary>
/// <param name="bufferType">Type of the mesh buffer to initialize.</param>
/// <param name="count">Amount of items in the buffer.</param>
/// <param name="format">Format of the elements in the buffer.</param>
/// <returns>True if failed, otherwise false.</returns>
bool AllocateBuffer(MeshBufferType bufferType, int32 count, PixelFormat format);
/// <summary>
/// Updates the mesh vertex and index buffers with data assigned to the accessor (eg. via AllocateBuffer).
/// </summary>
/// <param name="mesh">The target mesh to update.</param>
/// <param name="calculateBounds">True if auto-calculate mesh local bounding box based on input positions stream. Otherwise, mesh bound swill stay unchanged.</param>
/// <returns>True if failed, otherwise false.</returns>
bool UpdateMesh(MeshBase* mesh, bool calculateBounds = true);
public:
// Access stream with index buffer.
Stream Index();
// Access stream with a specific vertex attribute.
Stream Attribute(VertexElement::Types attribute);
// Access stream with vertex position attribute.
FORCE_INLINE Stream Position()
{
return Attribute(VertexElement::Types::Position);
}
// Access stream with vertex color attribute.
FORCE_INLINE Stream Color()
{
return Attribute(VertexElement::Types::Color);
}
// Access stream with vertex normal vector attribute.
FORCE_INLINE Stream Normal()
{
return Attribute(VertexElement::Types::Normal);
}
// Access stream with vertex tangent vector attribute.
FORCE_INLINE Stream Tangent()
{
return Attribute(VertexElement::Types::Tangent);
}
// Access stream with vertex skeleton bones blend indices attribute.
FORCE_INLINE Stream BlendIndices()
{
return Attribute(VertexElement::Types::BlendIndices);
}
// Access stream with vertex skeleton bones blend weights attribute.
FORCE_INLINE Stream BlendWeights()
{
return Attribute(VertexElement::Types::BlendWeights);
}
// Access stream with vertex texture coordinates attribute (specific UV channel).
FORCE_INLINE Stream TexCoord(int32 channel = 0)
{
return Attribute((VertexElement::Types)((byte)VertexElement::Types::TexCoord0 + channel));
}
};

View File

@@ -1,14 +1,23 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#include "MeshBase.h"
#include "MeshAccessor.h"
#include "Engine/Core/Log.h"
#include "Engine/Content/Assets/ModelBase.h"
#include "Engine/Core/Math/Transform.h"
#include "Engine/Graphics/GPUBuffer.h"
#include "Engine/Graphics/GPUContext.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/PixelFormatExtensions.h"
#include "Engine/Graphics/Shaders/GPUVertexLayout.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Renderer/DrawCall.h"
#include "Engine/Scripting/Enums.h"
#include "Engine/Scripting/ManagedCLR/MCore.h"
#include "Engine/Serialization/MemoryReadStream.h"
#include "Engine/Threading/Task.h"
static_assert(MODEL_MAX_VB == 3, "Update code in mesh to match amount of vertex buffers.");
namespace
{
@@ -28,6 +37,252 @@ namespace
#endif
}
MeshAccessor::Stream::Stream(Span<byte> data, PixelFormat format, int32 stride)
: _data(data)
, _format(PixelFormat::Unknown)
, _stride(stride)
{
auto sampler = PixelFormatSampler::Get(format);
if (sampler)
{
_format = format;
_sampler = *sampler;
}
else if (format != PixelFormat::Unknown)
{
LOG(Error, "Unsupported pixel format '{}' to sample vertex attribute.", ScriptingEnum::ToString(format));
}
}
Span<byte> MeshAccessor::Stream::GetData() const
{
return _data;
}
PixelFormat MeshAccessor::Stream::GetFormat() const
{
return _format;
}
int32 MeshAccessor::Stream::GetStride() const
{
return _stride;
}
int32 MeshAccessor::Stream::GetCount() const
{
return _data.Length() / _stride;
}
bool MeshAccessor::Stream::IsValid() const
{
return _format != PixelFormat::Unknown;
}
bool MeshAccessor::Stream::IsLinear(PixelFormat expectedFormat) const
{
return _format == expectedFormat && _stride == PixelFormatExtensions::SizeInBytes(_format);
}
void MeshAccessor::Stream::SetLinear(const void* data)
{
Platform::MemoryCopy(_data.Get(), data, _data.Length());
}
bool MeshAccessor::LoadMesh(const MeshBase* mesh, bool forceGpu, Span<MeshBufferType> buffers)
{
CHECK_RETURN(mesh, true);
constexpr MeshBufferType allBuffers[(int32)MeshBufferType::MAX] = { MeshBufferType::Index, MeshBufferType::Vertex0, MeshBufferType::Vertex1, MeshBufferType::Vertex2 };
if (buffers.IsInvalid())
buffers = Span<MeshBufferType>(allBuffers, ARRAY_COUNT(allBuffers));
Array<BytesContainer, FixedAllocation<4>> meshBuffers;
Array<GPUVertexLayout*, FixedAllocation<4>> meshLayouts;
if (mesh->DownloadData(buffers, meshBuffers, meshLayouts, forceGpu))
return true;
for (int32 i = 0; i < buffers.Length(); i++)
{
_data[(int32)buffers[i]] = MoveTemp(meshBuffers[i]);
_layouts[(int32)buffers[i]] = meshLayouts[i];
}
_formats[(int32)MeshBufferType::Index] = mesh->Use16BitIndexBuffer() ? PixelFormat::R16_UInt : PixelFormat::R32_UInt;
return false;
}
bool MeshAccessor::LoadBuffer(MeshBufferType bufferType, Span<byte> bufferData, GPUVertexLayout* layout)
{
CHECK_RETURN(layout, true);
CHECK_RETURN(bufferData.IsValid(), true);
const int32 idx = (int32)bufferType;
_data[idx].Link(bufferData);
_layouts[idx] = layout;
return false;
}
bool MeshAccessor::LoadFromMeshData(const void* meshDataPtr)
{
if (!meshDataPtr)
return true;
const auto& meshData = *(const ModelBase::MeshData*)meshDataPtr;
if (meshData.VBData.Count() != meshData.VBLayout.Count())
return true;
_data[(int32)MeshBufferType::Index].Link((const byte*)meshData.IBData, meshData.IBStride * meshData.Triangles * 3);
if (meshData.VBData.Count() > 0 && meshData.VBLayout[0])
{
constexpr int32 idx = (int32)MeshBufferType::Vertex0;
_data[idx].Link((const byte*)meshData.VBData[0], meshData.VBLayout[0]->GetStride() * meshData.Vertices);
_layouts[idx] = meshData.VBLayout[0];
}
if (meshData.VBData.Count() > 1 && meshData.VBLayout[1])
{
constexpr int32 idx = (int32)MeshBufferType::Vertex1;
_data[idx].Link((const byte*)meshData.VBData[1], meshData.VBLayout[1]->GetStride() * meshData.Vertices);
_layouts[idx] = meshData.VBLayout[1];
}
if (meshData.VBData.Count() > 2 && meshData.VBLayout[2])
{
constexpr int32 idx = (int32)MeshBufferType::Vertex2;
_data[idx].Link((const byte*)meshData.VBData[2], meshData.VBLayout[2]->GetStride() * meshData.Vertices);
_layouts[idx] = meshData.VBLayout[2];
}
return false;
}
bool MeshAccessor::AllocateBuffer(MeshBufferType bufferType, int32 count, GPUVertexLayout* layout)
{
CHECK_RETURN(count, true);
CHECK_RETURN(layout, true);
const int32 idx = (int32)bufferType;
_data[idx].Allocate(count * layout->GetStride());
_layouts[idx] = layout;
return false;
}
bool MeshAccessor::AllocateBuffer(MeshBufferType bufferType, int32 count, PixelFormat format)
{
CHECK_RETURN(count, true);
const int32 stride = PixelFormatExtensions::SizeInBytes(format);
CHECK_RETURN(stride, true);
const int32 idx = (int32)bufferType;
_data[idx].Allocate(count * stride);
_formats[idx] = format;
return false;
}
bool MeshAccessor::UpdateMesh(MeshBase* mesh, bool calculateBounds)
{
CHECK_RETURN(mesh, true);
constexpr int32 IB = (int32)MeshBufferType::Index;
constexpr int32 VB0 = (int32)MeshBufferType::Vertex0;
constexpr int32 VB1 = (int32)MeshBufferType::Vertex1;
constexpr int32 VB2 = (int32)MeshBufferType::Vertex2;
uint32 vertices = 0, triangles = 0;
const void* ibData = nullptr;
bool use16BitIndexBuffer = false;
Array<const void*, FixedAllocation<3>> vbData;
Array<GPUVertexLayout*, FixedAllocation<3>> vbLayout;
vbData.Resize(3);
vbLayout.Resize(3);
vbData.SetAll(nullptr);
vbLayout.SetAll(nullptr);
if (_data[VB0].IsValid())
{
vbData[0] = _data[VB0].Get();
vbLayout[0] = _layouts[VB0];
vertices = _data[VB0].Length() / _layouts[VB0]->GetStride();
}
if (_data[VB1].IsValid())
{
vbData[1] = _data[VB1].Get();
vbLayout[1] = _layouts[VB1];
vertices = _data[VB1].Length() / _layouts[VB1]->GetStride();
}
if (_data[VB2].IsValid())
{
vbData[2] = _data[VB2].Get();
vbLayout[2] = _layouts[VB2];
vertices = _data[VB2].Length() / _layouts[VB2]->GetStride();
}
if (_data[IB].IsValid() && _formats[IB] != PixelFormat::Unknown)
{
ibData = _data[IB].Get();
use16BitIndexBuffer = _formats[IB] == PixelFormat::R16_UInt;
triangles = _data[IB].Length() / PixelFormatExtensions::SizeInBytes(_formats[IB]);
}
if (mesh->Init(vertices, triangles, vbData, ibData, use16BitIndexBuffer, vbLayout))
return true;
if (calculateBounds)
{
// Calculate mesh bounds
BoundingBox bounds;
auto positionStream = Position();
CHECK_RETURN(positionStream.IsValid(), true);
if (positionStream.IsLinear(PixelFormat::R32G32B32_Float))
{
Span<byte> positionData = positionStream.GetData();
BoundingBox::FromPoints((const Float3*)positionData.Get(), positionData.Length() / sizeof(Float3), bounds);
}
else
{
Float3 min = Float3::Maximum, max = Float3::Minimum;
for (uint32 i = 0; i < vertices; i++)
{
Float3 pos = positionStream.GetFloat3(i);
Float3::Min(min, pos, min);
Float3::Max(max, pos, max);
}
bounds = BoundingBox(min, max);
}
mesh->SetBounds(bounds);
}
return false;
}
MeshAccessor::Stream MeshAccessor::Index()
{
Span<byte> data;
PixelFormat format = PixelFormat::Unknown;
int32 stride = 0;
auto& ib = _data[(int32)MeshBufferType::Index];
if (ib.IsValid())
{
data = ib;
format = _formats[(int32)MeshBufferType::Index];
stride = PixelFormatExtensions::SizeInBytes(format);
}
return Stream(data, format, stride);
}
MeshAccessor::Stream MeshAccessor::Attribute(VertexElement::Types attribute)
{
Span<byte> data;
PixelFormat format = PixelFormat::Unknown;
int32 stride = 0;
for (int32 vbIndex = 0; vbIndex < 3 && format == PixelFormat::Unknown; vbIndex++)
{
static_assert((int32)MeshBufferType::Vertex0 == 1, "Update code.");
const int32 idx = vbIndex + 1;
auto layout = _layouts[idx];
if (!layout)
continue;
for (const VertexElement& e : layout->GetElements())
{
auto& vb = _data[idx];
if (e.Type == attribute && vb.IsValid())
{
data = vb.Slice(e.Offset);
format = e.Format;
stride = layout->GetStride();
break;
}
}
}
return Stream(data, format, stride);
}
MeshBase::~MeshBase()
{
SAFE_DELETE_GPU_RESOURCE(_vertexBuffers[0]);
@@ -70,7 +325,12 @@ void MeshBase::SetBounds(const BoundingBox& box, const BoundingSphere& sphere)
}
}
bool MeshBase::Init(uint32 vertices, uint32 triangles, const Array<const void*, FixedAllocation<3>>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array<GPUVertexLayout*, FixedAllocation<3>>& vbLayout)
GPUVertexLayout* MeshBase::GetVertexLayout() const
{
return GPUVertexLayout::Get(Span<GPUBuffer*>(_vertexBuffers, MODEL_MAX_VB));
}
bool MeshBase::Init(uint32 vertices, uint32 triangles, const Array<const void*, FixedAllocation<MODEL_MAX_VB>>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array<GPUVertexLayout*, FixedAllocation<MODEL_MAX_VB>>& vbLayout)
{
CHECK_RETURN(vbData.HasItems() && vertices, true);
CHECK_RETURN(ibData, true);
@@ -126,9 +386,10 @@ bool MeshBase::Init(uint32 vertices, uint32 triangles, const Array<const void*,
_triangles = triangles;
_vertices = vertices;
_use16BitIndexBuffer = use16BitIndexBuffer;
_cachedVertexBuffers[0].Clear();
_cachedVertexBuffers[1].Clear();
_cachedVertexBuffers[2].Clear();
_cachedVertexBuffers[0].Release();
_cachedVertexBuffers[1].Release();
_cachedVertexBuffers[2].Release();
Platform::MemoryClear(_cachedVertexLayouts, sizeof(_cachedVertexLayouts));
return false;
@@ -154,10 +415,11 @@ void MeshBase::Release()
_triangles = 0;
_vertices = 0;
_use16BitIndexBuffer = false;
_cachedIndexBuffer.Resize(0);
_cachedVertexBuffers[0].Clear();
_cachedVertexBuffers[1].Clear();
_cachedVertexBuffers[2].Clear();
_cachedIndexBuffer.Release();
_cachedVertexBuffers[0].Release();
_cachedVertexBuffers[1].Release();
_cachedVertexBuffers[2].Release();
Platform::MemoryClear(_cachedVertexLayouts, sizeof(_cachedVertexLayouts));
}
bool MeshBase::UpdateTriangles(uint32 triangleCount, const void* ib, bool use16BitIndices)
@@ -243,7 +505,7 @@ bool MeshBase::Intersects(const Ray& ray, const Transform& transform, Real& dist
#endif
}
bool MeshBase::DownloadDataGPU(MeshBufferType type, BytesContainer& result) const
bool MeshBase::DownloadDataGPU(MeshBufferType type, BytesContainer& result, GPUVertexLayout** layout) const
{
GPUBuffer* buffer = nullptr;
switch (type)
@@ -253,18 +515,24 @@ bool MeshBase::DownloadDataGPU(MeshBufferType type, BytesContainer& result) cons
break;
case MeshBufferType::Vertex0:
buffer = _vertexBuffers[0];
if (layout && buffer)
*layout = buffer->GetVertexLayout();
break;
case MeshBufferType::Vertex1:
buffer = _vertexBuffers[1];
if (layout && buffer)
*layout = buffer->GetVertexLayout();
break;
case MeshBufferType::Vertex2:
buffer = _vertexBuffers[2];
if (layout && buffer)
*layout = buffer->GetVertexLayout();
break;
}
return buffer && buffer->DownloadData(result);
}
Task* MeshBase::DownloadDataGPUAsync(MeshBufferType type, BytesContainer& result) const
Task* MeshBase::DownloadDataGPUAsync(MeshBufferType type, BytesContainer& result, GPUVertexLayout** layout) const
{
GPUBuffer* buffer = nullptr;
switch (type)
@@ -274,17 +542,163 @@ Task* MeshBase::DownloadDataGPUAsync(MeshBufferType type, BytesContainer& result
break;
case MeshBufferType::Vertex0:
buffer = _vertexBuffers[0];
if (layout && buffer)
*layout = buffer->GetVertexLayout();
break;
case MeshBufferType::Vertex1:
buffer = _vertexBuffers[1];
if (layout && buffer)
*layout = buffer->GetVertexLayout();
break;
case MeshBufferType::Vertex2:
buffer = _vertexBuffers[2];
if (layout && buffer)
*layout = buffer->GetVertexLayout();
break;
}
return buffer ? buffer->DownloadDataAsync(result) : nullptr;
}
bool MeshBase::DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count, GPUVertexLayout** layout) const
{
if (_cachedVertexBuffers[0].IsInvalid())
{
PROFILE_CPU();
auto model = GetModelBase();
ScopeLock lock(model->Locker);
if (model->IsVirtual())
{
LOG(Error, "Cannot access CPU data of virtual models. Use GPU data download.");
return true;
}
// Fetch chunk with data from drive/memory
const auto chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(GetLODIndex());
if (model->LoadChunk(chunkIndex))
return true;
const auto chunk = model->GetChunk(chunkIndex);
if (!chunk)
{
LOG(Error, "Missing chunk.");
return true;
}
MemoryReadStream stream(chunk->Get(), chunk->Size());
ModelBase::MeshData meshData;
// Seek to find mesh location
byte meshVersion = stream.ReadByte();
for (int32 meshIndex = 0; meshIndex <= _index; meshIndex++)
{
if (model->LoadMesh(stream, meshVersion, model->GetMesh(meshIndex, _lodIndex), &meshData))
return true;
if (meshIndex != _index)
continue;
// Cache mesh data
_cachedVertexBufferCount = meshData.Vertices;
_cachedIndexBufferCount = (int32)meshData.Triangles * 3;
_cachedIndexBuffer.Copy((const byte*)meshData.IBData, _cachedIndexBufferCount * (int32)meshData.IBStride);
for (int32 vb = 0; vb < meshData.VBData.Count(); vb++)
{
_cachedVertexBuffers[vb].Copy((const byte*)meshData.VBData[vb], (int32)(meshData.VBLayout[vb]->GetStride() * meshData.Vertices));
_cachedVertexLayouts[vb] = meshData.VBLayout[vb];
}
break;
}
}
switch (type)
{
case MeshBufferType::Index:
result.Link(_cachedIndexBuffer);
count = _cachedIndexBufferCount;
break;
case MeshBufferType::Vertex0:
result.Link(_cachedVertexBuffers[0]);
count = _cachedVertexBufferCount;
if (layout)
*layout = _cachedVertexLayouts[0];
break;
case MeshBufferType::Vertex1:
result.Link(_cachedVertexBuffers[1]);
count = _cachedVertexBufferCount;
if (layout)
*layout = _cachedVertexLayouts[1];
break;
case MeshBufferType::Vertex2:
result.Link(_cachedVertexBuffers[2]);
count = _cachedVertexBufferCount;
if (layout)
*layout = _cachedVertexLayouts[2];
break;
default:
return true;
}
return false;
}
bool MeshBase::DownloadData(Span<MeshBufferType> types, Array<BytesContainer, FixedAllocation<4>>& buffers, Array<GPUVertexLayout*, FixedAllocation<4>>& layouts, bool forceGpu) const
{
PROFILE_CPU();
buffers.Resize(types.Length());
layouts.Resize(types.Length());
layouts.SetAll(nullptr);
auto model = _model;
model->Locker.Lock();
// Virtual assets always fetch from GPU memory
forceGpu |= model->IsVirtual();
if (forceGpu)
{
if (!IsInitialized())
{
model->Locker.Unlock();
LOG(Error, "Cannot load mesh data from GPU if it's not loaded.");
return true;
}
// Get data from GPU (start of series of async tasks that copy GPU-read data into staging buffers)
Array<Task*, FixedAllocation<(int32)MeshBufferType::MAX>> tasks;
for (int32 i = 0; i < types.Length(); i++)
{
auto task = DownloadDataGPUAsync(types[i], buffers[i], &layouts[i]);
if (!task)
{
model->Locker.Unlock();
return true;
}
task->Start();
tasks.Add(task);
}
// Wait for async tasks
model->Locker.Unlock();
if (Task::WaitAll(tasks))
{
LOG(Error, "Failed to download mesh data from GPU.");
return true;
}
model->Locker.Lock();
}
else
{
// Get data from CPU
for (int32 i = 0; i < types.Length(); i++)
{
int32 count = 0;
if (DownloadDataCPU(types[i], buffers[i], count, &layouts[i]))
{
model->Locker.Unlock();
return true;
}
}
}
model->Locker.Unlock();
return false;
}
void MeshBase::GetDrawCallGeometry(DrawCall& drawCall) const
{
drawCall.Geometry.IndexBuffer = _indexBuffer;
@@ -324,4 +738,85 @@ bool MeshBase::UpdateTrianglesUShort(int32 triangleCount, const MArray* triangle
return ::UpdateTriangles<uint16>(this, triangleCount, trianglesObj);
}
MArray* MeshBase::DownloadIndexBuffer(bool forceGpu, MTypeObject* resultType, bool use16Bit)
{
ScopeLock lock(GetModelBase()->Locker);
// Get index buffer data from the mesh (CPU or GPU)
MeshAccessor accessor;
MeshBufferType bufferTypes[1] = { MeshBufferType::Index };
if (accessor.LoadMesh(this, forceGpu, ToSpan(bufferTypes, 1)))
return nullptr;
auto indexStream = accessor.Index();
if (!indexStream.IsValid())
return nullptr;
auto indexData = indexStream.GetData();
auto indexCount = indexStream.GetCount();
auto indexStride = indexStream.GetStride();
// Convert into managed array
MArray* result = MCore::Array::New(MCore::Type::GetClass(INTERNAL_TYPE_OBJECT_GET(resultType)), indexCount);
void* managedArrayPtr = MCore::Array::GetAddress(result);
if (use16Bit)
{
if (indexStride == sizeof(uint16))
{
Platform::MemoryCopy(managedArrayPtr, indexData.Get(), indexData.Length());
}
else
{
auto dst = (uint16*)managedArrayPtr;
auto src = (const uint32*)indexData.Get();
for (int32 i = 0; i < indexCount; i++)
dst[i] = src[i];
}
}
else
{
if (indexStride == sizeof(uint16))
{
auto dst = (uint32*)managedArrayPtr;
auto src = (const uint16*)indexData.Get();
for (int32 i = 0; i < indexCount; i++)
dst[i] = src[i];
}
else
{
Platform::MemoryCopy(managedArrayPtr, indexData.Get(), indexData.Length());
}
}
return result;
}
bool MeshBase::DownloadData(int32 count, MeshBufferType* types, BytesContainer& buffer0, BytesContainer& buffer1, BytesContainer& buffer2, BytesContainer& buffer3, GPUVertexLayout*& layout0, GPUVertexLayout*& layout1, GPUVertexLayout*& layout2, GPUVertexLayout*& layout3, bool forceGpu) const
{
layout0 = layout1 = layout2 = layout3 = nullptr;
Array<BytesContainer, FixedAllocation<4>> meshBuffers;
Array<GPUVertexLayout*, FixedAllocation<4>> meshLayouts;
if (DownloadData(Span<MeshBufferType>(types, count), meshBuffers, meshLayouts, forceGpu))
return true;
if (count > 0)
{
buffer0 = meshBuffers[0];
layout0 = meshLayouts[0];
if (count > 1)
{
buffer1 = meshBuffers[1];
layout1 = meshLayouts[1];
if (count > 2)
{
buffer2 = meshBuffers[2];
layout2 = meshLayouts[2];
if (count > 3)
{
buffer3 = meshBuffers[3];
layout3 = meshLayouts[3];
}
}
}
}
return false;
}
#endif

View File

@@ -92,5 +92,84 @@ namespace FlaxEngine
if (Internal_UpdateTrianglesUShort(__unmanagedPtr, triangles.Count / 3, Utils.ExtractArrayFromList(triangles)))
throw new Exception("Failed to update mesh data.");
}
/// <summary>
/// Downloads the index buffer that contains mesh triangles data. To download data from GPU set <paramref name="forceGpu"/> to true and call this method from the thread other than main thread (see <see cref="Platform.IsInMainThread"/>).
/// </summary>
/// <remarks>If mesh index buffer format (see <see cref="IndexBufferFormat"/>) is <see cref="PixelFormat.R16_UInt"/> then it's faster to call .</remarks>
/// <param name="forceGpu">If set to <c>true</c> the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data.</param>
/// <returns>The gathered data.</returns>
public uint[] DownloadIndexBuffer(bool forceGpu = false)
{
var result = (uint[])Internal_DownloadIndexBuffer(__unmanagedPtr, forceGpu, typeof(uint), false);
if (result == null)
throw new Exception("Failed to download mesh data.");
return result;
}
/// <summary>
/// Downloads the index buffer that contains mesh triangles data. To download data from GPU set <paramref name="forceGpu"/> to true and call this method from the thread other than main thread (see <see cref="Platform.IsInMainThread"/>).
/// </summary>
/// <remarks>If mesh index buffer format (see <see cref="IndexBufferFormat"/>) is <see cref="PixelFormat.R32_UInt"/> then data won't be downloaded.</remarks>
/// <param name="forceGpu">If set to <c>true</c> the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data.</param>
/// <returns>The gathered data.</returns>
public ushort[] DownloadIndexBufferUShort(bool forceGpu = false)
{
var result = (ushort[])Internal_DownloadIndexBuffer(__unmanagedPtr, forceGpu, typeof(ushort), true);
if (result == null)
throw new Exception("Failed to download mesh data.");
return result;
}
/// <summary>
/// Extracts mesh buffers data.
/// </summary>
/// <param name="types">List of buffers to load.</param>
/// <param name="buffers">The result mesh buffers.</param>
/// <param name="layouts">The result layouts of the vertex buffers.</param>
/// <param name="forceGpu">If set to <c>true</c> the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data.</param>
/// <returns>True if failed, otherwise false</returns>
public unsafe bool DownloadData(Span<MeshBufferType> types, out byte[][] buffers, out GPUVertexLayout[] layouts, bool forceGpu = false)
{
if (types == null)
throw new ArgumentNullException(nameof(types));
buffers = null;
layouts = null;
var count = types.Length;
fixed (MeshBufferType* typesPtr = types)
{
if (Internal_DownloadData(__unmanagedPtr,
count, typesPtr,
out byte[] buffer0, out byte[] buffer1, out byte[] buffer2, out byte[] buffer3,
out GPUVertexLayout layout0, out GPUVertexLayout layout1, out GPUVertexLayout layout2, out GPUVertexLayout layout3,
forceGpu,
out int __buffer0Count, out int __buffer1Count, out int __buffer2Count, out int __buffer3Count
))
return true;
buffers = new byte[count][];
layouts = new GPUVertexLayout[count];
if (count > 0)
{
buffers[0] = buffer0;
layouts[0] = layout0;
if (count > 1)
{
buffers[1] = buffer1;
layouts[1] = layout1;
if (count > 2)
{
buffers[2] = buffer2;
layouts[2] = layout2;
if (count > 3)
{
buffers[3] = buffer3;
layouts[3] = layout3;
}
}
}
}
}
return false;
}
}
}

View File

@@ -5,7 +5,6 @@
#include "Engine/Core/Math/BoundingBox.h"
#include "Engine/Core/Math/BoundingSphere.h"
#include "Engine/Core/Types/DataContainer.h"
#include "Engine/Core/Collections/Array.h"
#include "Engine/Graphics/Enums.h"
#include "Engine/Graphics/Models/Types.h"
#include "Engine/Level/Types.h"
@@ -47,12 +46,13 @@ protected:
bool _use16BitIndexBuffer = false;
bool _hasBounds = false;
GPUBuffer* _vertexBuffers[3] = {};
GPUBuffer* _vertexBuffers[MODEL_MAX_VB] = {};
GPUBuffer* _indexBuffer = nullptr;
mutable Array<byte> _cachedVertexBuffers[3];
mutable Array<byte> _cachedIndexBuffer;
mutable int32 _cachedIndexBufferCount = 0;
mutable BytesContainer _cachedVertexBuffers[MODEL_MAX_VB];
mutable GPUVertexLayout* _cachedVertexLayouts[MODEL_MAX_VB] = {};
mutable BytesContainer _cachedIndexBuffer;
mutable int32 _cachedIndexBufferCount = 0, _cachedVertexBufferCount = 0;
#if MODEL_USE_PRECISE_MESH_INTERSECTS
CollisionProxy _collisionProxy;
@@ -177,19 +177,19 @@ public:
/// Sets the mesh bounds.
/// </summary>
/// <param name="box">The bounding box.</param>
void SetBounds(const BoundingBox& box);
API_FUNCTION() void SetBounds(API_PARAM(ref) const BoundingBox& box);
/// <summary>
/// Sets the mesh bounds.
/// </summary>
/// <param name="box">The bounding box.</param>
/// <param name="sphere">The bounding sphere.</param>
void SetBounds(const BoundingBox& box, const BoundingSphere& sphere);
API_FUNCTION() void SetBounds(API_PARAM(ref) const BoundingBox& box, API_PARAM(ref) const BoundingSphere& sphere);
/// <summary>
/// Gets the index buffer.
/// </summary>
FORCE_INLINE GPUBuffer* GetIndexBuffer() const
API_FUNCTION() FORCE_INLINE GPUBuffer* GetIndexBuffer() const
{
return _indexBuffer;
}
@@ -199,23 +199,29 @@ public:
/// </summary>
/// <param name="index">The bind slot index.</param>
/// <returns>The buffer or null if not used.</returns>
FORCE_INLINE GPUBuffer* GetVertexBuffer(int32 index) const
API_FUNCTION() FORCE_INLINE GPUBuffer* GetVertexBuffer(int32 index) const
{
return _vertexBuffers[index];
}
/// <summary>
/// Gets the vertex buffers layout. Made out of all buffers used by this mesh.
/// </summary>
/// <returns>The vertex layout.</returns>
API_PROPERTY() GPUVertexLayout* GetVertexLayout() const;
public:
/// <summary>
/// Initializes the mesh buffers.
/// </summary>
/// <param name="vertices">Amount of vertices in the vertex buffer.</param>
/// <param name="triangles">Amount of triangles in the index buffer.</param>
/// <param name="vbData">Array with pointers to vertex buffers initial data (layout defined by <paramref name="vertexLayout"/>).</param>
/// <param name="vbData">Array with pointers to vertex buffers initial data (layout defined by <paramref name="vbLayout"/>).</param>
/// <param name="ibData">Pointer to index buffer data. Data is uint16 or uint32 depending on <paramref name="use16BitIndexBuffer"/> value.</param>
/// <param name="use16BitIndexBuffer">True to use 16-bit indices for the index buffer (true: uint16, false: uint32).</param>
/// <param name="vbLayout">Layout descriptors for the vertex buffers attributes (one for each vertex buffer).</param>
/// <returns>True if failed, otherwise false.</returns>
virtual bool Init(uint32 vertices, uint32 triangles, const Array<const void*, FixedAllocation<3>>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array<GPUVertexLayout*, FixedAllocation<3>>& vbLayout);
API_FUNCTION(Sealed) virtual bool Init(uint32 vertices, uint32 triangles, const Array<const void*, FixedAllocation<MODEL_MAX_VB>>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array<GPUVertexLayout*, FixedAllocation<MODEL_MAX_VB>>& vbLayout);
/// <summary>
/// Releases the mesh data (GPU buffers and local cache).
@@ -290,16 +296,18 @@ public:
/// </summary>
/// <param name="type">Buffer type</param>
/// <param name="result">The result data</param>
/// <param name="layout">The result layout of the vertex buffer (optional).</param>
/// <returns>True if failed, otherwise false</returns>
bool DownloadDataGPU(MeshBufferType type, BytesContainer& result) const;
bool DownloadDataGPU(MeshBufferType type, BytesContainer& result, GPUVertexLayout** layout = nullptr) const;
/// <summary>
/// Extracts mesh buffer data from GPU in the async task.
/// </summary>
/// <param name="type">Buffer type</param>
/// <param name="result">The result data</param>
/// <param name="layout">The result layout of the vertex buffer (optional).</param>
/// <returns>Created async task used to gather the buffer data.</returns>
Task* DownloadDataGPUAsync(MeshBufferType type, BytesContainer& result) const;
Task* DownloadDataGPUAsync(MeshBufferType type, BytesContainer& result, GPUVertexLayout** layout = nullptr) const;
/// <summary>
/// Extract mesh buffer data from CPU. Cached internally.
@@ -307,8 +315,19 @@ public:
/// <param name="type">Buffer type</param>
/// <param name="result">The result data</param>
/// <param name="count">The amount of items inside the result buffer.</param>
/// <param name="layout">The result layout of the vertex buffer (optional).</param>
/// <returns>True if failed, otherwise false</returns>
virtual bool DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const = 0;
bool DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count, GPUVertexLayout** layout = nullptr) const;
/// <summary>
/// Extracts mesh buffers data.
/// </summary>
/// <param name="types">List of buffers to load.</param>
/// <param name="buffers">The result mesh buffers.</param>
/// <param name="layouts">The result layouts of the vertex buffers.</param>
/// <param name="forceGpu">If set to <c>true</c> the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data.</param>
/// <returns>True if failed, otherwise false</returns>
bool DownloadData(Span<MeshBufferType> types, Array<BytesContainer, FixedAllocation<4>>& buffers, Array<GPUVertexLayout*, FixedAllocation<4>>& layouts, bool forceGpu = false) const;
public:
/// <summary>
@@ -423,5 +442,7 @@ private:
#if !COMPILE_WITHOUT_CSHARP
API_FUNCTION(NoProxy) bool UpdateTrianglesUInt(int32 triangleCount, const MArray* trianglesObj);
API_FUNCTION(NoProxy) bool UpdateTrianglesUShort(int32 triangleCount, const MArray* trianglesObj);
API_FUNCTION(NoProxy) MArray* DownloadIndexBuffer(bool forceGpu, MTypeObject* resultType, bool use16Bit);
API_FUNCTION(NoProxy) bool DownloadData(int32 count, MeshBufferType* types, API_PARAM(Out) BytesContainer& buffer0, API_PARAM(Out) BytesContainer& buffer1, API_PARAM(Out) BytesContainer& buffer2, API_PARAM(Out) BytesContainer& buffer3, API_PARAM(Out) GPUVertexLayout*& layout0, API_PARAM(Out) GPUVertexLayout*& layout1, API_PARAM(Out) GPUVertexLayout*& layout2, API_PARAM(Out) GPUVertexLayout*& layout3, bool forceGpu) const;
#endif
};

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#include "MeshDeformation.h"
#include "MeshAccessor.h"
#include "Engine/Graphics/Models/MeshBase.h"
#include "Engine/Profiler/ProfilerCPU.h"
@@ -29,6 +30,11 @@ FORCE_INLINE static uint32 GetKey(int32 lodIndex, int32 meshIndex, MeshBufferTyp
return key.Value;
}
bool MeshDeformationData::LoadMeshAccessor(MeshAccessor& accessor) const
{
return accessor.LoadBuffer(Type, ToSpan(VertexBuffer.Data), VertexBuffer.GetLayout());
}
void MeshDeformation::GetBounds(int32 lodIndex, int32 meshIndex, BoundingBox& bounds) const
{
const auto key = GetKey(lodIndex, meshIndex, MeshBufferType::Vertex0);

View File

@@ -32,6 +32,8 @@ struct MeshDeformationData
~MeshDeformationData()
{
}
bool LoadMeshAccessor(class MeshAccessor& accessor) const;
};
/// <summary>

View File

@@ -6,9 +6,6 @@
#include "Engine/Core/Math/BoundingSphere.h"
#include "Engine/Animations/CurveSerialization.h"
#include "Engine/Serialization/WriteStream.h"
#include "Engine/Debug/Exceptions/ArgumentNullException.h"
#include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h"
#include "Engine/Debug/Exceptions/InvalidOperationException.h"
void MeshData::Clear()
{
@@ -71,6 +68,7 @@ void MeshData::Release()
BlendShapes.Resize(0);
}
PRAGMA_DISABLE_DEPRECATION_WARNINGS
void MeshData::InitFromModelVertices(ModelVertex19* vertices, uint32 verticesCount)
{
Positions.Resize(verticesCount, false);
@@ -160,6 +158,7 @@ void MeshData::InitFromModelVertices(VB0ElementType18* vb0, VB1ElementType18* vb
vb1++;
}
}
PRAGMA_ENABLE_DEPRECATION_WARNINGS
void MeshData::SetIndexBuffer(void* data, uint32 indicesCount)
{
@@ -181,237 +180,52 @@ void MeshData::SetIndexBuffer(void* data, uint32 indicesCount)
}
}
bool MeshData::Pack2Model(WriteStream* stream) const
{
// Validate input
if (stream == nullptr)
{
LOG(Error, "Invalid input.");
return true;
}
// Cache size
uint32 verticiecCount = Positions.Count();
uint32 indicesCount = Indices.Count();
uint32 trianglesCount = indicesCount / 3;
bool use16Bit = indicesCount <= MAX_uint16;
if (verticiecCount == 0 || trianglesCount == 0 || indicesCount % 3 != 0)
{
LOG(Error, "Empty mesh! Triangles: {0}, Verticies: {1}.", trianglesCount, verticiecCount);
return true;
}
// Validate data structure
bool hasUVs = UVs.HasItems();
if (hasUVs && UVs.Count() != verticiecCount)
{
LOG(Error, "Invalid size of {0} stream.", TEXT("UVs"));
return true;
}
bool hasNormals = Normals.HasItems();
if (hasNormals && Normals.Count() != verticiecCount)
{
LOG(Error, "Invalid size of {0} stream.", TEXT("Normals"));
return true;
}
bool hasTangents = Tangents.HasItems();
if (hasTangents && Tangents.Count() != verticiecCount)
{
LOG(Error, "Invalid size of {0} stream.", TEXT("Tangents"));
return true;
}
bool hasBitangentSigns = BitangentSigns.HasItems();
if (hasBitangentSigns && BitangentSigns.Count() != verticiecCount)
{
LOG(Error, "Invalid size of {0} stream.", TEXT("BitangentSigns"));
return true;
}
bool hasLightmapUVs = LightmapUVs.HasItems();
if (hasLightmapUVs && LightmapUVs.Count() != verticiecCount)
{
LOG(Error, "Invalid size of {0} stream.", TEXT("LightmapUVs"));
return true;
}
bool hasVertexColors = Colors.HasItems();
if (hasVertexColors && Colors.Count() != verticiecCount)
{
LOG(Error, "Invalid size of {0} stream.", TEXT("Colors"));
return true;
}
// Vertices
stream->WriteUint32(verticiecCount);
// Triangles
stream->WriteUint32(trianglesCount);
// Vertex Buffer 0
stream->WriteBytes(Positions.Get(), sizeof(Float3) * verticiecCount);
// Vertex Buffer 1
VB1ElementType vb1;
for (uint32 i = 0; i < verticiecCount; i++)
{
// Get vertex components
Float2 uv = hasUVs ? UVs[i] : Float2::Zero;
Float3 normal = hasNormals ? Normals[i] : Float3::UnitZ;
Float3 tangent = hasTangents ? Tangents[i] : Float3::UnitX;
Float2 lightmapUV = hasLightmapUVs ? LightmapUVs[i] : Float2::Zero;
Float3 bitangentSign = hasBitangentSigns ? BitangentSigns[i] : Float3::Dot(Float3::Cross(Float3::Normalize(Float3::Cross(normal, tangent)), normal), tangent);
// Write vertex
vb1.TexCoord = Half2(uv);
vb1.Normal = Float1010102(normal * 0.5f + 0.5f, 0);
vb1.Tangent = Float1010102(tangent * 0.5f + 0.5f, static_cast<byte>(bitangentSign < 0 ? 1 : 0));
vb1.LightmapUVs = Half2(lightmapUV);
stream->WriteBytes(&vb1, sizeof(vb1));
}
// Vertex Buffer 2
stream->WriteBool(hasVertexColors);
if (hasVertexColors)
{
VB2ElementType vb2;
for (uint32 i = 0; i < verticiecCount; i++)
{
vb2.Color = Color32(Colors[i]);
stream->WriteBytes(&vb2, sizeof(vb2));
}
}
// Index Buffer
if (use16Bit)
{
for (uint32 i = 0; i < indicesCount; i++)
stream->WriteUint16(Indices[i]);
}
else
{
stream->WriteBytes(Indices.Get(), sizeof(uint32) * indicesCount);
}
return false;
}
bool MeshData::Pack2SkinnedModel(WriteStream* stream) const
{
// Validate input
if (stream == nullptr)
{
LOG(Error, "Invalid input.");
return true;
}
// Cache size
uint32 verticiecCount = Positions.Count();
uint32 indicesCount = Indices.Count();
uint32 trianglesCount = indicesCount / 3;
bool use16Bit = indicesCount <= MAX_uint16;
if (verticiecCount == 0 || trianglesCount == 0 || indicesCount % 3 != 0)
{
LOG(Error, "Empty mesh! Triangles: {0}, Verticies: {1}.", trianglesCount, verticiecCount);
return true;
}
// Validate data structure
bool hasUVs = UVs.HasItems();
if (hasUVs && UVs.Count() != verticiecCount)
{
LOG(Error, "Invalid size of {0} stream.", TEXT( "UVs"));
return true;
}
bool hasNormals = Normals.HasItems();
if (hasNormals && Normals.Count() != verticiecCount)
{
LOG(Error, "Invalid size of {0} stream.", TEXT("Normals"));
return true;
}
bool hasTangents = Tangents.HasItems();
if (hasTangents && Tangents.Count() != verticiecCount)
{
LOG(Error, "Invalid size of {0} stream.", TEXT("Tangents"));
return true;
}
bool hasBitangentSigns = BitangentSigns.HasItems();
if (hasBitangentSigns && BitangentSigns.Count() != verticiecCount)
{
LOG(Error, "Invalid size of {0} stream.", TEXT("BitangentSigns"));
return true;
}
if (BlendIndices.Count() != verticiecCount)
{
LOG(Error, "Invalid size of {0} stream.", TEXT("BlendIndices"));
return true;
}
if (BlendWeights.Count() != verticiecCount)
{
LOG(Error, "Invalid size of {0} stream.", TEXT("BlendWeights"));
return true;
}
// Vertices
stream->WriteUint32(verticiecCount);
// Triangles
stream->WriteUint32(trianglesCount);
// Blend Shapes
stream->WriteUint16(BlendShapes.Count());
for (const auto& blendShape : BlendShapes)
{
stream->WriteBool(blendShape.UseNormals);
stream->WriteUint32(blendShape.MinVertexIndex);
stream->WriteUint32(blendShape.MaxVertexIndex);
stream->WriteUint32(blendShape.Vertices.Count());
stream->WriteBytes(blendShape.Vertices.Get(), blendShape.Vertices.Count() * sizeof(BlendShapeVertex));
}
// Vertex Buffer
VB0SkinnedElementType vb;
for (uint32 i = 0; i < verticiecCount; i++)
{
// Get vertex components
Float2 uv = hasUVs ? UVs[i] : Float2::Zero;
Float3 normal = hasNormals ? Normals[i] : Float3::UnitZ;
Float3 tangent = hasTangents ? Tangents[i] : Float3::UnitX;
Float3 bitangentSign = hasBitangentSigns ? BitangentSigns[i] : Float3::Dot(Float3::Cross(Float3::Normalize(Float3::Cross(normal, tangent)), normal), tangent);
Int4 blendIndices = BlendIndices[i];
Float4 blendWeights = BlendWeights[i];
// Write vertex
vb.Position = Positions[i];
vb.TexCoord = Half2(uv);
vb.Normal = Float1010102(normal * 0.5f + 0.5f, 0);
vb.Tangent = Float1010102(tangent * 0.5f + 0.5f, static_cast<byte>(bitangentSign < 0 ? 1 : 0));
vb.BlendIndices = Color32(blendIndices.X, blendIndices.Y, blendIndices.Z, blendIndices.W);
vb.BlendWeights = Half4(blendWeights);
stream->WriteBytes(&vb, sizeof(vb));
}
// Index Buffer
if (use16Bit)
{
for (uint32 i = 0; i < indicesCount; i++)
stream->WriteUint16(Indices[i]);
}
else
{
stream->WriteBytes(Indices.Get(), sizeof(uint32) * indicesCount);
}
return false;
}
void MeshData::CalculateBox(BoundingBox& result) const
{
if (Positions.HasItems())
BoundingBox::FromPoints(Positions.Get(), Positions.Count(), result);
else
result = BoundingBox::Zero;
}
void MeshData::CalculateSphere(BoundingSphere& result) const
{
if (Positions.HasItems())
BoundingSphere::FromPoints(Positions.Get(), Positions.Count(), result);
else
result = BoundingSphere::Empty;
}
void MeshData::CalculateBounds(BoundingBox& box, BoundingSphere& sphere) const
{
if (Positions.HasItems())
{
// Merged code of BoundingBox::FromPoints and BoundingSphere::FromPoints within a single loop
const Float3* points = Positions.Get();
const int32 pointsCount = Positions.Count();
Float3 min = points[0];
Float3 max = min;
Float3 center = min;
for (int32 i = 1; i < pointsCount; i++)
Float3::Add(points[i], center, center);
center /= (float)pointsCount;
float radiusSq = Float3::DistanceSquared(center, min);
for (int32 i = 1; i < pointsCount; i++)
{
Float3::Min(min, points[i], min);
Float3::Max(max, points[i], max);
const float distance = Float3::DistanceSquared(center, points[i]);
if (distance > radiusSq)
radiusSq = distance;
}
box = BoundingBox(min, max);
sphere = BoundingSphere(center, Math::Sqrt(radiusSq));
}
else
{
box = BoundingBox::Zero;
sphere = BoundingSphere::Empty;
}
}
void MeshData::TransformBuffer(const Matrix& matrix)
@@ -616,276 +430,3 @@ void ModelData::TransformBuffer(const Matrix& matrix)
}
}
}
#if USE_EDITOR
bool ModelData::Pack2ModelHeader(WriteStream* stream) const
{
// Validate input
if (stream == nullptr)
{
Log::ArgumentNullException();
return true;
}
const int32 lodCount = GetLODsCount();
if (lodCount == 0 || lodCount > MODEL_MAX_LODS)
{
Log::ArgumentOutOfRangeException();
return true;
}
if (Materials.IsEmpty())
{
Log::ArgumentOutOfRangeException(TEXT("MaterialSlots"), TEXT("Material slots collection cannot be empty."));
return true;
}
// Min Screen Size
stream->WriteFloat(MinScreenSize);
// Amount of material slots
stream->WriteInt32(Materials.Count());
// For each material slot
for (int32 materialSlotIndex = 0; materialSlotIndex < Materials.Count(); materialSlotIndex++)
{
auto& slot = Materials[materialSlotIndex];
stream->Write(slot.AssetID);
stream->WriteByte(static_cast<byte>(slot.ShadowsMode));
stream->WriteString(slot.Name, 11);
}
// Amount of LODs
stream->WriteByte(lodCount);
// For each LOD
for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++)
{
auto& lod = LODs[lodIndex];
// Screen Size
stream->WriteFloat(lod.ScreenSize);
// Amount of meshes
const int32 meshes = lod.Meshes.Count();
if (meshes == 0)
{
LOG(Warning, "Empty LOD.");
return true;
}
if (meshes > MODEL_MAX_MESHES)
{
LOG(Warning, "Too many meshes per LOD.");
return true;
}
stream->WriteUint16(meshes);
// For each mesh
for (int32 meshIndex = 0; meshIndex < meshes; meshIndex++)
{
auto& mesh = *lod.Meshes[meshIndex];
// Material Slot
stream->WriteInt32(mesh.MaterialSlotIndex);
// Box
BoundingBox box;
mesh.CalculateBox(box);
stream->WriteBoundingBox(box);
// Sphere
BoundingSphere sphere;
mesh.CalculateSphere(sphere);
stream->WriteBoundingSphere(sphere);
// TODO: calculate Sphere and Box at once - make it faster using SSE
// Has Lightmap UVs
stream->WriteBool(mesh.LightmapUVs.HasItems());
}
}
return false;
}
bool ModelData::Pack2SkinnedModelHeader(WriteStream* stream) const
{
// Validate input
if (stream == nullptr)
{
Log::ArgumentNullException();
return true;
}
const int32 lodCount = GetLODsCount();
if (lodCount > MODEL_MAX_LODS)
{
Log::ArgumentOutOfRangeException();
return true;
}
// Version
stream->WriteByte(1);
// Min Screen Size
stream->WriteFloat(MinScreenSize);
// Amount of material slots
stream->WriteInt32(Materials.Count());
// For each material slot
for (int32 materialSlotIndex = 0; materialSlotIndex < Materials.Count(); materialSlotIndex++)
{
auto& slot = Materials[materialSlotIndex];
stream->Write(slot.AssetID);
stream->WriteByte(static_cast<byte>(slot.ShadowsMode));
stream->WriteString(slot.Name, 11);
}
// Amount of LODs
stream->WriteByte(lodCount);
// For each LOD
for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++)
{
auto& lod = LODs[lodIndex];
// Screen Size
stream->WriteFloat(lod.ScreenSize);
// Amount of meshes
const int32 meshes = lod.Meshes.Count();
if (meshes > MODEL_MAX_MESHES)
{
LOG(Warning, "Too many meshes per LOD.");
return true;
}
stream->WriteUint16(meshes);
// For each mesh
for (int32 meshIndex = 0; meshIndex < meshes; meshIndex++)
{
auto& mesh = *lod.Meshes[meshIndex];
// Material Slot
stream->WriteInt32(mesh.MaterialSlotIndex);
// Box
BoundingBox box;
mesh.CalculateBox(box);
stream->WriteBoundingBox(box);
// Sphere
BoundingSphere sphere;
mesh.CalculateSphere(sphere);
stream->WriteBoundingSphere(sphere);
// TODO: calculate Sphere and Box at once - make it faster using SSE
// Blend Shapes
const int32 blendShapes = mesh.BlendShapes.Count();
stream->WriteUint16(blendShapes);
for (int32 blendShapeIndex = 0; blendShapeIndex < blendShapes; blendShapeIndex++)
{
auto& blendShape = mesh.BlendShapes[blendShapeIndex];
stream->WriteString(blendShape.Name, 13);
stream->WriteFloat(blendShape.Weight);
}
}
}
// Skeleton
{
stream->WriteInt32(Skeleton.Nodes.Count());
// For each node
for (int32 nodeIndex = 0; nodeIndex < Skeleton.Nodes.Count(); nodeIndex++)
{
auto& node = Skeleton.Nodes[nodeIndex];
stream->Write(node.ParentIndex);
stream->WriteTransform(node.LocalTransform);
stream->WriteString(node.Name, 71);
}
stream->WriteInt32(Skeleton.Bones.Count());
// For each bone
for (int32 boneIndex = 0; boneIndex < Skeleton.Bones.Count(); boneIndex++)
{
auto& bone = Skeleton.Bones[boneIndex];
stream->Write(bone.ParentIndex);
stream->Write(bone.NodeIndex);
stream->WriteTransform(bone.LocalTransform);
stream->Write(bone.OffsetMatrix);
}
}
// Retargeting
{
stream->WriteInt32(0);
}
return false;
}
bool ModelData::Pack2AnimationHeader(WriteStream* stream, int32 animIndex) const
{
// Validate input
if (stream == nullptr || animIndex < 0 || animIndex >= Animations.Count())
{
Log::ArgumentNullException();
return true;
}
auto& anim = Animations.Get()[animIndex];
if (anim.Duration <= ZeroTolerance || anim.FramesPerSecond <= ZeroTolerance)
{
Log::InvalidOperationException(TEXT("Invalid animation duration."));
return true;
}
if (anim.Channels.IsEmpty())
{
Log::ArgumentOutOfRangeException(TEXT("Channels"), TEXT("Animation channels collection cannot be empty."));
return true;
}
// Info
stream->WriteInt32(103); // Header version (for fast version upgrades without serialization format change)
stream->WriteDouble(anim.Duration);
stream->WriteDouble(anim.FramesPerSecond);
stream->WriteByte((byte)anim.RootMotionFlags);
stream->WriteString(anim.RootNodeName, 13);
// Animation channels
stream->WriteInt32(anim.Channels.Count());
for (int32 i = 0; i < anim.Channels.Count(); i++)
{
auto& channel = anim.Channels[i];
stream->WriteString(channel.NodeName, 172);
Serialization::Serialize(*stream, channel.Position);
Serialization::Serialize(*stream, channel.Rotation);
Serialization::Serialize(*stream, channel.Scale);
}
// Animation events
stream->WriteInt32(anim.Events.Count());
for (auto& e : anim.Events)
{
stream->WriteString(e.First, 172);
stream->WriteInt32(e.Second.GetKeyframes().Count());
for (const auto& k : e.Second.GetKeyframes())
{
stream->WriteFloat(k.Time);
stream->WriteFloat(k.Value.Duration);
stream->WriteStringAnsi(k.Value.TypeName, 17);
stream->WriteJson(k.Value.JsonData);
}
}
// Nested animations
stream->WriteInt32(0);
return false;
}
#endif

View File

@@ -39,9 +39,11 @@ public:
Array<Float3> Positions;
/// <summary>
/// Texture coordinates
/// Texture coordinates (list of channels)
/// </summary>
// TODO: multiple UVs
Array<Float2> UVs;
Array<Float2> LightmapUVs; // TODO: remove this and move to UVs
/// <summary>
/// Normals vector
@@ -65,11 +67,6 @@ public:
/// </summary>
Array<uint32> Indices;
/// <summary>
/// Lightmap UVs
/// </summary>
Array<Float2> LightmapUVs;
/// <summary>
/// Vertex colors
/// </summary>
@@ -91,12 +88,17 @@ public:
Array<BlendShape> BlendShapes;
/// <summary>
/// Global translation for this mesh to be at it's local origin.
/// Lightmap texture coordinates channel index. Value -1 indicates that channel is not available.
/// </summary>
int32 LightmapUVsIndex = -1;
/// <summary>
/// Global translation for this mesh to be at its local origin.
/// </summary>
Vector3 OriginTranslation = Vector3::Zero;
/// <summary>
/// Orientation for this mesh at it's local origin.
/// Orientation for this mesh at its local origin.
/// </summary>
Quaternion OriginOrientation = Quaternion::Identity;
@@ -105,15 +107,6 @@ public:
/// </summary>
Vector3 Scaling = Vector3::One;
public:
/// <summary>
/// Determines whether this instance has any mesh data.
/// </summary>
FORCE_INLINE bool HasData() const
{
return Indices.HasItems();
}
public:
/// <summary>
/// Clear arrays
@@ -142,6 +135,7 @@ public:
void Release();
public:
PRAGMA_DISABLE_DEPRECATION_WARNINGS
/// <summary>
/// Init from model vertices array
/// </summary>
@@ -165,6 +159,7 @@ public:
/// <param name="vb2">Array of data for vertex buffer 2</param>
/// <param name="verticesCount">Amount of vertices</param>
void InitFromModelVertices(VB0ElementType18* vb0, VB1ElementType18* vb1, VB2ElementType18* vb2, uint32 verticesCount);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
/// <summary>
/// Sets the index buffer data.
@@ -174,20 +169,6 @@ public:
void SetIndexBuffer(void* data, uint32 indicesCount);
public:
/// <summary>
/// Pack mesh data to the stream
/// </summary>
/// <param name="stream">Output stream</param>
/// <returns>True if cannot save data, otherwise false</returns>
bool Pack2Model(WriteStream* stream) const;
/// <summary>
/// Pack skinned mesh data to the stream
/// </summary>
/// <param name="stream">Output stream</param>
/// <returns>True if cannot save data, otherwise false</returns>
bool Pack2SkinnedModel(WriteStream* stream) const;
/// <summary>
/// Calculate bounding box for the mesh
/// </summary>
@@ -200,9 +181,14 @@ public:
/// <param name="result">Output sphere</param>
void CalculateSphere(BoundingSphere& result) const;
public:
#if COMPILE_WITH_MODEL_TOOL
/// <summary>
/// Calculates bounding box and sphere for the mesh.
/// </summary>
/// <param name="box">Output box.</param>
/// <param name="sphere">Output sphere.</param>
void CalculateBounds(BoundingBox& box, BoundingSphere& sphere) const;
#if COMPILE_WITH_MODEL_TOOL
/// <summary>
/// Generate lightmap uvs for the mesh entry
/// </summary>
@@ -246,7 +232,6 @@ public:
/// </summary>
/// <returns>The area sum of all mesh triangles.</returns>
float CalculateTrianglesArea() const;
#endif
/// <summary>
@@ -439,23 +424,6 @@ public:
/// </summary>
Array<AnimationData> Animations;
public:
/// <summary>
/// Gets the valid level of details count.
/// </summary>
FORCE_INLINE int32 GetLODsCount() const
{
return LODs.Count();
}
/// <summary>
/// Determines whether this instance has valid skeleton structure.
/// </summary>
FORCE_INLINE bool HasSkeleton() const
{
return Skeleton.Bones.HasItems();
}
public:
/// <summary>
/// Automatically calculates the screen size for every model LOD for a proper transitions.
@@ -467,29 +435,4 @@ public:
/// </summary>
/// <param name="matrix">The matrix to use for the transformation.</param>
void TransformBuffer(const Matrix& matrix);
#if USE_EDITOR
public:
/// <summary>
/// Pack mesh data to the header stream
/// </summary>
/// <param name="stream">Output stream</param>
/// <returns>True if cannot save data, otherwise false</returns>
bool Pack2ModelHeader(WriteStream* stream) const;
/// <summary>
/// Pack skinned mesh data to the header stream
/// </summary>
/// <param name="stream">Output stream</param>
/// <returns>True if cannot save data, otherwise false</returns>
bool Pack2SkinnedModelHeader(WriteStream* stream) const;
/// <summary>
/// Pack animation data to the header stream
/// </summary>
/// <param name="stream">Output stream</param>
/// <param name="animIndex">Index of animation.</param>
/// <returns>True if cannot save data, otherwise false</returns>
bool Pack2AnimationHeader(WriteStream* stream, int32 animIndex = 0) const;
#endif
};

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#include "SkinnedMesh.h"
#include "MeshAccessor.h"
#include "MeshDeformation.h"
#include "ModelInstanceEntry.h"
#include "Engine/Content/Assets/Material.h"
@@ -13,12 +14,11 @@
#include "Engine/Graphics/Shaders/GPUVertexLayout.h"
#include "Engine/Level/Scene/Scene.h"
#include "Engine/Renderer/RenderList.h"
#include "Engine/Serialization/MemoryReadStream.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Scripting/ManagedCLR/MCore.h"
#include "Engine/Threading/Task.h"
#include "Engine/Threading/Threading.h"
PRAGMA_DISABLE_DEPRECATION_WARNINGS
GPUVertexLayout* VB0SkinnedElementType2::GetLayout()
{
return GPUVertexLayout::Get({
@@ -31,6 +31,125 @@ GPUVertexLayout* VB0SkinnedElementType2::GetLayout()
});
}
PRAGMA_ENABLE_DEPRECATION_WARNINGS
namespace
{
bool UpdateMesh(MeshBase* mesh, uint32 vertexCount, uint32 triangleCount, PixelFormat indexFormat, const Float3* vertices, const void* triangles, const Int4* blendIndices, const Float4* blendWeights, const Float3* normals, const Float3* tangents, const Float2* uvs, const Color32* colors)
{
auto model = mesh->GetModelBase();
CHECK_RETURN(model && model->IsVirtual(), true);
CHECK_RETURN(triangles && vertices, true);
MeshAccessor accessor;
// Index Buffer
{
if (accessor.AllocateBuffer(MeshBufferType::Index, triangleCount, indexFormat))
return true;
auto indexStream = accessor.Index();
ASSERT(indexStream.IsLinear(indexFormat));
indexStream.SetLinear(triangles);
}
// Vertex Buffer
{
GPUVertexLayout::Elements vb0elements;
vb0elements.Add({ VertexElement::Types::Position, 0, 0, 0, PixelFormat::R32G32B32_Float });
if (normals)
{
vb0elements.Add({ VertexElement::Types::Normal, 0, 0, 0, PixelFormat::R10G10B10A2_UNorm });
if (tangents)
vb0elements.Add({ VertexElement::Types::Tangent, 0, 0, 0, PixelFormat::R10G10B10A2_UNorm });
}
vb0elements.Add({ VertexElement::Types::BlendIndices, 0, 0, 0, PixelFormat::R8G8B8A8_UInt });
vb0elements.Add({ VertexElement::Types::BlendWeights, 0, 0, 0, PixelFormat::R16G16B16A16_Float });
if (uvs)
vb0elements.Add({ VertexElement::Types::TexCoord, 0, 0, 0, PixelFormat::R16G16_Float });
if (colors)
vb0elements.Add({ VertexElement::Types::Color, 0, 0, 0, PixelFormat::R8G8B8A8_UNorm });
GPUVertexLayout* vb0layout = GPUVertexLayout::Get(vb0elements);
if (accessor.AllocateBuffer(MeshBufferType::Vertex0, vertexCount, vb0layout))
return true;
auto positionStream = accessor.Position();
ASSERT(positionStream.IsLinear(PixelFormat::R32G32B32_Float));
positionStream.SetLinear(vertices);
if (normals)
{
auto normalStream = accessor.Normal();
if (tangents)
{
auto tangentStream = accessor.Tangent();
for (uint32 i = 0; i < vertexCount; i++)
{
const Float3 normal = normals[i];
const Float3 tangent = tangents[i];
Float3 n;
Float4 t;
RenderTools::CalculateTangentFrame(n, t, normal, tangent);
normalStream.SetFloat3(i, n);
tangentStream.SetFloat4(i, t);
}
}
else
{
for (uint32 i = 0; i < vertexCount; i++)
{
const Float3 normal = normals[i];
Float3 n;
Float4 t;
RenderTools::CalculateTangentFrame(n, t, normal);
normalStream.SetFloat3(i, n);
}
}
}
{
auto blendIndicesStream = accessor.BlendIndices();
auto blendWeightsStream = accessor.BlendWeights();
for (uint32 i = 0; i < vertexCount; i++)
{
blendIndicesStream.SetFloat4(i, blendIndices[i]);
blendWeightsStream.SetFloat4(i, blendWeights[i]);
}
}
if (uvs)
{
auto uvsStream = accessor.TexCoord();
for (uint32 i = 0; i < vertexCount; i++)
uvsStream.SetFloat2(i, uvs[i]);
}
if (colors)
{
auto colorStream = accessor.Color();
for (uint32 i = 0; i < vertexCount; i++)
colorStream.SetFloat4(i, Float4(Color(colors[i]))); // TODO: optimize with direct memory copy
}
}
return accessor.UpdateMesh(mesh);
}
#if !COMPILE_WITHOUT_CSHARP
template<typename IndexType>
bool UpdateMesh(SkinnedMesh* mesh, uint32 vertexCount, uint32 triangleCount, const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj, const MArray* colorsObj)
{
ASSERT((uint32)MCore::Array::GetLength(verticesObj) >= vertexCount);
ASSERT((uint32)MCore::Array::GetLength(trianglesObj) / 3 >= triangleCount);
auto vertices = MCore::Array::GetAddress<Float3>(verticesObj);
auto triangles = MCore::Array::GetAddress<IndexType>(trianglesObj);
const PixelFormat indexFormat = sizeof(IndexType) == 4 ? PixelFormat::R32_UInt : PixelFormat::R16_UInt;
const auto blendIndices = MCore::Array::GetAddress<Int4>(blendIndicesObj);
const auto blendWeights = MCore::Array::GetAddress<Float4>(blendWeightsObj);
const auto normals = normalsObj ? MCore::Array::GetAddress<Float3>(normalsObj) : nullptr;
const auto tangents = tangentsObj ? MCore::Array::GetAddress<Float3>(tangentsObj) : nullptr;
const auto uvs = uvObj ? MCore::Array::GetAddress<Float2>(uvObj) : nullptr;
const auto colors = colorsObj ? MCore::Array::GetAddress<Color32>(colorsObj) : nullptr;
return UpdateMesh(mesh, vertexCount, triangleCount, indexFormat, vertices, triangles, blendIndices, blendWeights, normals, tangents, uvs, colors);
}
#endif
}
void SkeletonData::Swap(SkeletonData& other)
{
Nodes.Swap(other.Nodes);
@@ -99,14 +218,39 @@ bool SkinnedMesh::Load(uint32 vertices, uint32 triangles, const void* vb0, const
Array<const void*, FixedAllocation<3>> vbData;
vbData.Add(vb0);
Array<GPUVertexLayout*, FixedAllocation<3>> vbLayout;
PRAGMA_DISABLE_DEPRECATION_WARNINGS
vbLayout.Add(VB0SkinnedElementType::GetLayout());
PRAGMA_ENABLE_DEPRECATION_WARNINGS
return Init(vertices, triangles, vbData, ib, use16BitIndexBuffer, vbLayout);
}
bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const int32* ib)
{
PRAGMA_DISABLE_DEPRECATION_WARNINGS
return UpdateMesh(vertexCount, triangleCount, vb, ib, false);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const uint32* ib)
{
PRAGMA_DISABLE_DEPRECATION_WARNINGS
return UpdateMesh(vertexCount, triangleCount, vb, ib, false);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const uint16* ib)
{
PRAGMA_DISABLE_DEPRECATION_WARNINGS
return UpdateMesh(vertexCount, triangleCount, vb, ib, true);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const void* ib, bool use16BitIndices)
{
// Setup GPU resources
PRAGMA_DISABLE_DEPRECATION_WARNINGS
const bool failed = Load(vertexCount, triangleCount, vb, ib, use16BitIndices);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
if (!failed)
{
// Calculate mesh bounds
@@ -117,6 +261,16 @@ bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0
return failed;
}
bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const uint16* triangles, const Int4* blendIndices, const Float4* blendWeights, const Float3* normals, const Float3* tangents, const Float2* uvs, const Color32* colors)
{
return ::UpdateMesh(this, vertexCount, triangleCount, PixelFormat::R16_UInt, vertices, triangles, blendIndices, blendWeights, normals, tangents, uvs, colors);
}
bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const uint32* triangles, const Int4* blendIndices, const Float4* blendWeights, const Float3* normals, const Float3* tangents, const Float2* uvs, const Color32* colors)
{
return ::UpdateMesh(this, vertexCount, triangleCount, PixelFormat::R16_UInt, vertices, triangles, blendIndices, blendWeights, normals, tangents, uvs, colors);
}
void SkinnedMesh::Draw(const RenderContext& renderContext, const DrawInfo& info, float lodDitherFactor) const
{
const auto& entry = info.Buffer->At(_materialSlotIndex);
@@ -212,297 +366,70 @@ void SkinnedMesh::Draw(const RenderContextBatch& renderContextBatch, const DrawI
void SkinnedMesh::Release()
{
MeshBase::Release();
BlendShapes.Clear();
}
bool SkinnedMesh::DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const
{
if (_cachedVertexBuffers[0].IsEmpty())
{
PROFILE_CPU();
auto model = GetSkinnedModel();
ScopeLock lock(model->Locker);
if (model->IsVirtual())
{
LOG(Error, "Cannot access CPU data of virtual models. Use GPU data download");
return true;
}
// Fetch chunk with data from drive/memory
const auto chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(_lodIndex);
if (model->LoadChunk(chunkIndex))
return true;
const auto chunk = model->GetChunk(chunkIndex);
if (!chunk)
{
LOG(Error, "Missing chunk.");
return true;
}
MemoryReadStream stream(chunk->Get(), chunk->Size());
// Seek to find mesh location
byte version = stream.ReadByte();
for (int32 i = 0; i <= _index; i++)
{
// #MODEL_DATA_FORMAT_USAGE
uint32 vertices;
stream.ReadUint32(&vertices);
uint32 triangles;
stream.ReadUint32(&triangles);
uint16 blendShapesCount;
stream.ReadUint16(&blendShapesCount);
for (int32 blendShapeIndex = 0; blendShapeIndex < blendShapesCount; blendShapeIndex++)
{
uint32 minVertexIndex, maxVertexIndex;
bool useNormals = stream.ReadBool();
stream.ReadUint32(&minVertexIndex);
stream.ReadUint32(&maxVertexIndex);
uint32 blendShapeVertices;
stream.ReadUint32(&blendShapeVertices);
auto blendShapeVerticesData = stream.Move<byte>(blendShapeVertices * sizeof(BlendShapeVertex));
}
uint32 indicesCount = triangles * 3;
bool use16BitIndexBuffer = indicesCount <= MAX_uint16;
uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
if (vertices == 0 || triangles == 0)
{
LOG(Error, "Invalid mesh data.");
return true;
}
auto vb0 = stream.Move<VB0SkinnedElementType>(vertices);
auto ib = stream.Move<byte>(indicesCount * ibStride);
if (i != _index)
continue;
// Cache mesh data
_cachedIndexBufferCount = indicesCount;
_cachedIndexBuffer.Set(ib, indicesCount * ibStride);
_cachedVertexBuffers[0].Set((const byte*)vb0, vertices * sizeof(VB0SkinnedElementType));
break;
}
}
switch (type)
{
case MeshBufferType::Index:
result.Link(_cachedIndexBuffer);
count = _cachedIndexBufferCount;
break;
case MeshBufferType::Vertex0:
result.Link(_cachedVertexBuffers[0]);
count = _cachedVertexBuffers[0].Count() / sizeof(VB0SkinnedElementType);
break;
default:
return true;
}
return false;
}
#if !COMPILE_WITHOUT_CSHARP
template<typename IndexType>
bool UpdateMesh(SkinnedMesh* mesh, const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj)
bool SkinnedMesh::UpdateMeshUInt(int32 vertexCount, int32 triangleCount, const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj, const MArray* colorsObj)
{
auto model = mesh->GetSkinnedModel();
ASSERT(model && model->IsVirtual() && verticesObj && trianglesObj && blendIndicesObj && blendWeightsObj);
// Get buffers data
const auto vertexCount = (uint32)MCore::Array::GetLength(verticesObj);
const auto triangleCount = (uint32)MCore::Array::GetLength(trianglesObj) / 3;
auto vertices = MCore::Array::GetAddress<Float3>(verticesObj);
auto ib = MCore::Array::GetAddress<IndexType>(trianglesObj);
auto blendIndices = MCore::Array::GetAddress<Int4>(blendIndicesObj);
auto blendWeights = MCore::Array::GetAddress<Float4>(blendWeightsObj);
Array<VB0SkinnedElementType> vb;
vb.Resize(vertexCount);
for (uint32 i = 0; i < vertexCount; i++)
vb.Get()[i].Position = vertices[i];
if (normalsObj)
{
const auto normals = MCore::Array::GetAddress<Float3>(normalsObj);
if (tangentsObj)
{
const auto tangents = MCore::Array::GetAddress<Float3>(tangentsObj);
for (uint32 i = 0; i < vertexCount; i++)
{
const Float3 normal = normals[i];
const Float3 tangent = tangents[i];
auto& v = vb.Get()[i];
RenderTools::CalculateTangentFrame(v.Normal, v.Tangent, normal, tangent);
}
}
else
{
for (uint32 i = 0; i < vertexCount; i++)
{
const Float3 normal = normals[i];
auto& v = vb.Get()[i];
RenderTools::CalculateTangentFrame(v.Normal, v.Tangent, normal);
}
}
}
else
{
const auto n = Float1010102(Float3::UnitZ);
const auto t = Float1010102(Float3::UnitX);
for (uint32 i = 0; i < vertexCount; i++)
{
vb[i].Normal = n;
vb[i].Tangent = t;
}
}
if (uvObj)
{
const auto uvs = MCore::Array::GetAddress<Float2>(uvObj);
for (uint32 i = 0; i < vertexCount; i++)
vb[i].TexCoord = Half2(uvs[i]);
}
else
{
auto v = Half2::Zero;
for (uint32 i = 0; i < vertexCount; i++)
vb[i].TexCoord = v;
}
for (uint32 i = 0; i < vertexCount; i++)
{
auto v = blendIndices[i];
vb[i].BlendIndices = Color32(v.X, v.Y, v.Z, v.W);
}
for (uint32 i = 0; i < vertexCount; i++)
{
auto v = blendWeights[i];
vb[i].BlendWeights = Half4(v);
}
return mesh->UpdateMesh(vertexCount, triangleCount, vb.Get(), ib);
return ::UpdateMesh<uint32>(this, (uint32)vertexCount, (uint32)triangleCount, verticesObj, trianglesObj, blendIndicesObj, blendWeightsObj, normalsObj, tangentsObj, uvObj, colorsObj);
}
bool SkinnedMesh::UpdateMeshUInt(const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj)
bool SkinnedMesh::UpdateMeshUShort(int32 vertexCount, int32 triangleCount, const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj, const MArray* colorsObj)
{
return ::UpdateMesh<uint32>(this, verticesObj, trianglesObj, blendIndicesObj, blendWeightsObj, normalsObj, tangentsObj, uvObj);
}
bool SkinnedMesh::UpdateMeshUShort(const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj)
{
return ::UpdateMesh<uint16>(this, verticesObj, trianglesObj, blendIndicesObj, blendWeightsObj, normalsObj, tangentsObj, uvObj);
return ::UpdateMesh<uint16>(this, (uint32)vertexCount, (uint32)triangleCount, verticesObj, trianglesObj, blendIndicesObj, blendWeightsObj, normalsObj, tangentsObj, uvObj, colorsObj);
}
// [Deprecated in v1.10]
enum class InternalBufferType
{
VB0 = 0,
IB16 = 3,
IB32 = 4,
};
MArray* SkinnedMesh::DownloadBuffer(bool forceGpu, MTypeObject* resultType, int32 typeI)
{
SkinnedMesh* mesh = this;
InternalBufferType type = (InternalBufferType)typeI;
auto model = mesh->GetSkinnedModel();
ScopeLock lock(model->Locker);
// [Deprecated in v1.10]
ScopeLock lock(GetModelBase()->Locker);
// Virtual assets always fetch from GPU memory
forceGpu |= model->IsVirtual();
if (!mesh->IsInitialized() && forceGpu)
{
LOG(Error, "Cannot load mesh data from GPU if it's not loaded.");
// Get vertex buffers data from the mesh (CPU or GPU)
MeshAccessor accessor;
MeshBufferType bufferTypes[1] = { MeshBufferType::Vertex0 };
if (accessor.LoadMesh(this, forceGpu, ToSpan(bufferTypes, 1)))
return nullptr;
}
MeshBufferType bufferType;
switch (type)
{
case InternalBufferType::VB0:
bufferType = MeshBufferType::Vertex0;
break;
case InternalBufferType::IB16:
case InternalBufferType::IB32:
bufferType = MeshBufferType::Index;
break;
default:
return nullptr;
}
BytesContainer data;
int32 dataCount;
if (forceGpu)
{
// Get data from GPU
// TODO: support reusing the input memory buffer to perform a single copy from staging buffer to the input CPU buffer
auto task = mesh->DownloadDataGPUAsync(bufferType, data);
if (task == nullptr)
return nullptr;
task->Start();
model->Locker.Unlock();
if (task->Wait())
{
LOG(Error, "Task failed.");
return nullptr;
}
model->Locker.Lock();
// Extract elements count from result data
switch (bufferType)
{
case MeshBufferType::Index:
dataCount = data.Length() / (Use16BitIndexBuffer() ? sizeof(uint16) : sizeof(uint32));
break;
case MeshBufferType::Vertex0:
dataCount = data.Length() / sizeof(VB0SkinnedElementType);
break;
}
}
else
{
// Get data from CPU
if (DownloadDataCPU(bufferType, data, dataCount))
return nullptr;
}
auto positionStream = accessor.Position();
auto texCoordStream = accessor.TexCoord();
auto normalStream = accessor.Normal();
auto tangentStream = accessor.Tangent();
auto blendIndicesStream = accessor.BlendIndices();
auto BlendWeightsStream = accessor.BlendWeights();
auto count = GetVertexCount();
// Convert into managed array
MArray* result = MCore::Array::New(MCore::Type::GetClass(INTERNAL_TYPE_OBJECT_GET(resultType)), dataCount);
MArray* result = MCore::Array::New(MCore::Type::GetClass(INTERNAL_TYPE_OBJECT_GET(resultType)), count);
void* managedArrayPtr = MCore::Array::GetAddress(result);
const int32 elementSize = data.Length() / dataCount;
switch (type)
switch ((InternalBufferType)typeI)
{
PRAGMA_DISABLE_DEPRECATION_WARNINGS
case InternalBufferType::VB0:
{
Platform::MemoryCopy(managedArrayPtr, data.Get(), data.Length());
break;
}
case InternalBufferType::IB16:
{
if (elementSize == sizeof(uint16))
for (int32 i = 0; i < count; i++)
{
Platform::MemoryCopy(managedArrayPtr, data.Get(), data.Length());
}
else
{
auto dst = (uint16*)managedArrayPtr;
auto src = (uint32*)data.Get();
for (int32 i = 0; i < dataCount; i++)
dst[i] = src[i];
auto& dst = ((VB0SkinnedElementType*)managedArrayPtr)[i];
dst.Position = positionStream.GetFloat3(i);
if (texCoordStream.IsValid())
dst.TexCoord = texCoordStream.GetFloat2(i);
if (normalStream.IsValid())
dst.Normal = normalStream.GetFloat3(i);
if (tangentStream.IsValid())
dst.Tangent = tangentStream.GetFloat4(i);
if (blendIndicesStream.IsValid())
dst.BlendIndices = Color32(blendIndicesStream.GetFloat4(i));
if (BlendWeightsStream.IsValid())
dst.BlendWeights = Half4(BlendWeightsStream.GetFloat4(i));
}
break;
}
case InternalBufferType::IB32:
{
if (elementSize == sizeof(uint16))
{
auto dst = (uint32*)managedArrayPtr;
auto src = (uint16*)data.Get();
for (int32 i = 0; i < dataCount; i++)
dst[i] = src[i];
}
else
{
Platform::MemoryCopy(managedArrayPtr, data.Get(), data.Length());
}
break;
}
PRAGMA_ENABLE_DEPRECATION_WARNINGS
}
return result;

View File

@@ -8,7 +8,9 @@ namespace FlaxEngine
{
/// <summary>
/// The Vertex Buffer 0 structure format.
/// [Deprecated in v1.10]
/// </summary>
[Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")]
public struct Vertex0
{
/// <summary>
@@ -44,7 +46,9 @@ namespace FlaxEngine
/// <summary>
/// The raw Vertex Buffer structure format.
/// [Deprecated in v1.10]
/// </summary>
[Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")]
public struct Vertex
{
/// <summary>
@@ -100,7 +104,8 @@ namespace FlaxEngine
/// <param name="normals">The normal vectors (per vertex).</param>
/// <param name="tangents">The normal vectors (per vertex). Use null to compute them from normal vectors.</param>
/// <param name="uv">The texture coordinates (per vertex).</param>
public void UpdateMesh(Float3[] vertices, int[] triangles, Int4[] blendIndices, Float4[] blendWeights, Float3[] normals = null, Float3[] tangents = null, Float2[] uv = null)
/// <param name="colors">The vertex colors (per vertex).</param>
public void UpdateMesh(Float3[] vertices, int[] triangles, Int4[] blendIndices, Float4[] blendWeights, Float3[] normals = null, Float3[] tangents = null, Float2[] uv = null, Color32[] colors = null)
{
if (!ParentSkinnedModel.IsVirtual)
throw new InvalidOperationException("Only virtual skinned models can be updated at runtime.");
@@ -118,8 +123,10 @@ namespace FlaxEngine
throw new ArgumentException("If you specify tangents then you need to also provide normals for the mesh.");
if (uv != null && uv.Length != vertices.Length)
throw new ArgumentOutOfRangeException(nameof(uv));
if (colors != null && colors.Length != vertices.Length)
throw new ArgumentOutOfRangeException(nameof(colors));
if (Internal_UpdateMeshUInt(__unmanagedPtr, vertices, triangles, blendIndices, blendWeights, normals, tangents, uv))
if (Internal_UpdateMeshUInt(__unmanagedPtr, vertices.Length, triangles.Length / 3, vertices, triangles, blendIndices, blendWeights, normals, tangents, uv, colors))
throw new Exception("Failed to update mesh data.");
}
@@ -135,7 +142,8 @@ namespace FlaxEngine
/// <param name="normals">The normal vectors (per vertex).</param>
/// <param name="tangents">The normal vectors (per vertex). Use null to compute them from normal vectors.</param>
/// <param name="uv">The texture coordinates (per vertex).</param>
public void UpdateMesh(Float3[] vertices, uint[] triangles, Int4[] blendIndices, Float4[] blendWeights, Float3[] normals = null, Float3[] tangents = null, Float2[] uv = null)
/// <param name="colors">The vertex colors (per vertex).</param>
public void UpdateMesh(Float3[] vertices, uint[] triangles, Int4[] blendIndices, Float4[] blendWeights, Float3[] normals = null, Float3[] tangents = null, Float2[] uv = null, Color32[] colors = null)
{
if (!ParentSkinnedModel.IsVirtual)
throw new InvalidOperationException("Only virtual skinned models can be updated at runtime.");
@@ -153,8 +161,10 @@ namespace FlaxEngine
throw new ArgumentException("If you specify tangents then you need to also provide normals for the mesh.");
if (uv != null && uv.Length != vertices.Length)
throw new ArgumentOutOfRangeException(nameof(uv));
if (colors != null && colors.Length != vertices.Length)
throw new ArgumentOutOfRangeException(nameof(colors));
if (Internal_UpdateMeshUInt(__unmanagedPtr, vertices, triangles, blendIndices, blendWeights, normals, tangents, uv))
if (Internal_UpdateMeshUInt(__unmanagedPtr, vertices.Length, triangles.Length / 3, vertices, triangles, blendIndices, blendWeights, normals, tangents, uv, colors))
throw new Exception("Failed to update mesh data.");
}
@@ -170,7 +180,8 @@ namespace FlaxEngine
/// <param name="normals">The normal vectors (per vertex).</param>
/// <param name="tangents">The tangent vectors (per vertex). Use null to compute them from normal vectors.</param>
/// <param name="uv">The texture coordinates (per vertex).</param>
public void UpdateMesh(Float3[] vertices, ushort[] triangles, Int4[] blendIndices, Float4[] blendWeights, Float3[] normals = null, Float3[] tangents = null, Float2[] uv = null)
/// <param name="colors">The vertex colors (per vertex).</param>
public void UpdateMesh(Float3[] vertices, ushort[] triangles, Int4[] blendIndices, Float4[] blendWeights, Float3[] normals = null, Float3[] tangents = null, Float2[] uv = null, Color32[] colors = null)
{
if (!ParentSkinnedModel.IsVirtual)
throw new InvalidOperationException("Only virtual skinned models can be updated at runtime.");
@@ -188,8 +199,10 @@ namespace FlaxEngine
throw new ArgumentException("If you specify tangents then you need to also provide normals for the mesh.");
if (uv != null && uv.Length != vertices.Length)
throw new ArgumentOutOfRangeException(nameof(uv));
if (colors != null && colors.Length != vertices.Length)
throw new ArgumentOutOfRangeException(nameof(colors));
if (Internal_UpdateMeshUShort(__unmanagedPtr, vertices, triangles, blendIndices, blendWeights, normals, tangents, uv))
if (Internal_UpdateMeshUShort(__unmanagedPtr, vertices.Length, triangles.Length / 3, vertices, triangles, blendIndices, blendWeights, normals, tangents, uv, colors))
throw new Exception("Failed to update mesh data.");
}
@@ -250,18 +263,22 @@ namespace FlaxEngine
UpdateMesh(Utils.ConvertCollection(vertices), triangles, blendIndices, Utils.ConvertCollection(blendWeights), Utils.ConvertCollection(normals), Utils.ConvertCollection(tangents), Utils.ConvertCollection(uv));
}
/// <summary>
/// [Deprecated in v1.10]
/// </summary>
[Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")]
internal enum InternalBufferType
{
VB0 = 0,
IB16 = 3,
IB32 = 4,
}
/// <summary>
/// Downloads the first vertex buffer that contains mesh vertices data. To download data from GPU set <paramref name="forceGpu"/> to true and call this method from the thread other than main thread (see <see cref="Platform.IsInMainThread"/>).
/// [Deprecated in v1.10]
/// </summary>
/// <param name="forceGpu">If set to <c>true</c> the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data.</param>
/// <returns>The gathered data.</returns>
[Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")]
public Vertex0[] DownloadVertexBuffer0(bool forceGpu = false)
{
var result = (Vertex0[])Internal_DownloadBuffer(__unmanagedPtr, forceGpu, typeof(Vertex0), (int)InternalBufferType.VB0);
@@ -272,13 +289,13 @@ namespace FlaxEngine
/// <summary>
/// Downloads the raw vertex buffer that contains mesh vertices data. To download data from GPU set <paramref name="forceGpu"/> to true and call this method from the thread other than main thread (see <see cref="Platform.IsInMainThread"/>).
/// [Deprecated in v1.10]
/// </summary>
/// <param name="forceGpu">If set to <c>true</c> the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data.</param>
/// <returns>The gathered data.</returns>
[Obsolete("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.")]
public Vertex[] DownloadVertexBuffer(bool forceGpu = false)
{
// TODO: perform data conversion on C++ side to make it faster
var vb0 = DownloadVertexBuffer0(forceGpu);
var vertices = vb0.Length;
@@ -299,33 +316,5 @@ namespace FlaxEngine
return result;
}
/// <summary>
/// Downloads the index buffer that contains mesh triangles data. To download data from GPU set <paramref name="forceGpu"/> to true and call this method from the thread other than main thread (see <see cref="Platform.IsInMainThread"/>).
/// </summary>
/// <remarks>If mesh index buffer format (see <see cref="MeshBase.IndexBufferFormat"/>) is <see cref="PixelFormat.R16_UInt"/> then it's faster to call .</remarks>
/// <param name="forceGpu">If set to <c>true</c> the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data.</param>
/// <returns>The gathered data.</returns>
public uint[] DownloadIndexBuffer(bool forceGpu = false)
{
var result = (uint[])Internal_DownloadBuffer(__unmanagedPtr, forceGpu, typeof(uint), (int)InternalBufferType.IB32);
if (result == null)
throw new Exception("Failed to download mesh data.");
return result;
}
/// <summary>
/// Downloads the index buffer that contains mesh triangles data. To download data from GPU set <paramref name="forceGpu"/> to true and call this method from the thread other than main thread (see <see cref="Platform.IsInMainThread"/>).
/// </summary>
/// <remarks>If mesh index buffer format (see <see cref="MeshBase.IndexBufferFormat"/>) is <see cref="PixelFormat.R32_UInt"/> then data won't be downloaded.</remarks>
/// <param name="forceGpu">If set to <c>true</c> the data will be downloaded from the GPU, otherwise it can be loaded from the drive (source asset file) or from memory (if cached). Downloading mesh from GPU requires this call to be made from the other thread than main thread. Virtual assets are always downloaded from GPU memory due to lack of dedicated storage container for the asset data.</param>
/// <returns>The gathered data.</returns>
public ushort[] DownloadIndexBufferUShort(bool forceGpu = false)
{
var result = (ushort[])Internal_DownloadBuffer(__unmanagedPtr, forceGpu, typeof(ushort), (int)InternalBufferType.IB16);
if (result == null)
throw new Exception("Failed to download mesh data.");
return result;
}
}
}

View File

@@ -51,45 +51,43 @@ public:
public:
/// <summary>
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
/// [Deprecated in v1.10]
/// </summary>
/// <param name="vertexCount">The amount of vertices in the vertex buffer.</param>
/// <param name="triangleCount">The amount of triangles in the index buffer.</param>
/// <param name="vb">The vertex buffer data.</param>
/// <param name="ib">The index buffer in clockwise order.</param>
/// <returns>True if failed, otherwise false.</returns>
FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const int32* ib)
{
return UpdateMesh(vertexCount, triangleCount, vb, ib, false);
}
DEPRECATED("Use MeshAccessor or UpdateMesh with separate vertex attribute arrays instead.")
bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const int32* ib);
/// <summary>
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
/// [Deprecated in v1.10]
/// </summary>
/// <param name="vertexCount">The amount of vertices in the vertex buffer.</param>
/// <param name="triangleCount">The amount of triangles in the index buffer.</param>
/// <param name="vb">The vertex buffer data.</param>
/// <param name="ib">The index buffer in clockwise order.</param>
/// <returns>True if failed, otherwise false.</returns>
FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const uint32* ib)
{
return UpdateMesh(vertexCount, triangleCount, vb, ib, false);
}
DEPRECATED("Use MeshAccessor or UpdateMesh with separate vertex attribute arrays instead.")
bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const uint32* ib);
/// <summary>
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
/// [Deprecated in v1.10]
/// </summary>
/// <param name="vertexCount">The amount of vertices in the vertex buffer.</param>
/// <param name="triangleCount">The amount of triangles in the index buffer.</param>
/// <param name="vb">The vertex buffer data.</param>
/// <param name="ib">The index buffer, clockwise order.</param>
/// <returns>True if failed, otherwise false.</returns>
FORCE_INLINE bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const uint16* ib)
{
return UpdateMesh(vertexCount, triangleCount, vb, ib, true);
}
DEPRECATED("Use MeshAccessor or UpdateMesh with separate vertex attribute arrays instead.")
bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const uint16* ib);
/// <summary>
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
/// [Deprecated in v1.10]
/// </summary>
/// <param name="vertexCount">The amount of vertices in the vertex buffer.</param>
/// <param name="triangleCount">The amount of triangles in the index buffer.</param>
@@ -97,8 +95,45 @@ public:
/// <param name="ib">The index buffer in clockwise order.</param>
/// <param name="use16BitIndices">True if index buffer uses 16-bit index buffer, otherwise 32-bit.</param>
/// <returns>True if failed, otherwise false.</returns>
DEPRECATED("Use MeshAccessor or Load with separate vertex attribute arrays instead.")
bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const void* ib, bool use16BitIndices);
/// <summary>
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
/// Can be used only for virtual assets (see <see cref="Asset.IsVirtual"/> and <see cref="Content.CreateVirtualAsset{T}"/>).
/// Mesh data will be cached and uploaded to the GPU with a delay.
/// </summary>
/// <param name="vertexCount">The amount of vertices in the vertex buffer.</param>
/// <param name="triangleCount">The amount of triangles in the index buffer.</param>
/// <param name="vertices">The mesh vertices positions. Cannot be null.</param>
/// <param name="triangles">The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null.</param>
/// <param name="blendIndices">The skeletal bones indices to use for skinning.</param>
/// <param name="blendWeights">The skeletal bones weights to use for skinning (matches blendIndices).</param>
/// <param name="normals">The normal vectors (per vertex).</param>
/// <param name="tangents">The normal vectors (per vertex). Use null to compute them from normal vectors.</param>
/// <param name="uvs">The texture coordinates (per vertex).</param>
/// <param name="colors">The vertex colors (per vertex).</param>
/// <returns>True if failed, otherwise false.</returns>
bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const uint16* triangles, const Int4* blendIndices, const Float4* blendWeights, const Float3* normals = nullptr, const Float3* tangents = nullptr, const Float2* uvs = nullptr, const Color32* colors = nullptr);
/// <summary>
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
/// Can be used only for virtual assets (see <see cref="Asset.IsVirtual"/> and <see cref="Content.CreateVirtualAsset{T}"/>).
/// Mesh data will be cached and uploaded to the GPU with a delay.
/// </summary>
/// <param name="vertexCount">The amount of vertices in the vertex buffer.</param>
/// <param name="triangleCount">The amount of triangles in the index buffer.</param>
/// <param name="vertices">The mesh vertices positions. Cannot be null.</param>
/// <param name="triangles">The mesh index buffer (clockwise triangles). Uses 32-bit stride buffer. Cannot be null.</param>
/// <param name="blendIndices">The skeletal bones indices to use for skinning.</param>
/// <param name="blendWeights">The skeletal bones weights to use for skinning (matches blendIndices).</param>
/// <param name="normals">The normal vectors (per vertex).</param>
/// <param name="tangents">The normal vectors (per vertex). Use null to compute them from normal vectors.</param>
/// <param name="uvs">The texture coordinates (per vertex).</param>
/// <param name="colors">The vertex colors (per vertex).</param>
/// <returns>True if failed, otherwise false.</returns>
bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const uint32* triangles, const Int4* blendIndices, const Float4* blendWeights, const Float3* normals = nullptr, const Float3* tangents = nullptr, const Float2* uvs = nullptr, const Color32* colors = nullptr);
public:
/// <summary>
/// Draws the mesh.
@@ -119,13 +154,12 @@ public:
public:
// [MeshBase]
void Release() override;
bool DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const override;
private:
// Internal bindings
#if !COMPILE_WITHOUT_CSHARP
API_FUNCTION(NoProxy) bool UpdateMeshUInt(const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj);
API_FUNCTION(NoProxy) bool UpdateMeshUShort(const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj);
API_FUNCTION(NoProxy) bool UpdateMeshUInt(int32 vertexCount, int32 triangleCount, const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj, const MArray* colorsObj);
API_FUNCTION(NoProxy) bool UpdateMeshUShort(int32 vertexCount, int32 triangleCount, const MArray* verticesObj, const MArray* trianglesObj, const MArray* blendIndicesObj, const MArray* blendWeightsObj, const MArray* normalsObj, const MArray* tangentsObj, const MArray* uvObj, const MArray* colorsObj);
API_FUNCTION(NoProxy) MArray* DownloadBuffer(bool forceGpu, MTypeObject* resultType, int32 typeI);
#endif
};

View File

@@ -46,7 +46,7 @@ API_ENUM(Attributes="HideInEditor") enum class ModelLightmapUVsSource
/// <summary>
/// The mesh buffer types.
/// </summary>
enum class MeshBufferType
API_ENUM(Attributes="HideInEditor") enum class MeshBufferType
{
/// <summary>
/// The index buffer.
@@ -73,7 +73,7 @@ enum class MeshBufferType
// Vertex structure for all models (versioned)
// [Deprecated in v1.10]
PACK_STRUCT(struct ModelVertex19
PACK_STRUCT(struct DEPRECATED("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.") ModelVertex19
{
Float3 Position;
Half2 TexCoord;
@@ -83,11 +83,13 @@ PACK_STRUCT(struct ModelVertex19
Color32 Color;
});
PRAGMA_DISABLE_DEPRECATION_WARNINGS
// [Deprecated in v1.10]
typedef ModelVertex19 ModelVertex;
PRAGMA_ENABLE_DEPRECATION_WARNINGS
// [Deprecated in v1.10]
struct RawModelVertex
struct DEPRECATED("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.") RawModelVertex
{
Float3 Position;
Float2 TexCoord;
@@ -100,7 +102,7 @@ struct RawModelVertex
// For vertex data we use three buffers: one with positions, one with other attributes, and one with colors
// [Deprecated in v1.10]
PACK_STRUCT(struct VB0ElementType18
PACK_STRUCT(struct DEPRECATED("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.") VB0ElementType18
{
Float3 Position;
@@ -108,7 +110,7 @@ PACK_STRUCT(struct VB0ElementType18
});
// [Deprecated in v1.10]
PACK_STRUCT(struct VB1ElementType18
PACK_STRUCT(struct DEPRECATED("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.") VB1ElementType18
{
Half2 TexCoord;
Float1010102 Normal;
@@ -119,23 +121,25 @@ PACK_STRUCT(struct VB1ElementType18
});
// [Deprecated in v1.10]
PACK_STRUCT(struct VB2ElementType18
PACK_STRUCT(struct DEPRECATED("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.") VB2ElementType18
{
Color32 Color;
static GPUVertexLayout* GetLayout();
});
PRAGMA_DISABLE_DEPRECATION_WARNINGS
// [Deprecated in v1.10]
typedef VB0ElementType18 VB0ElementType;
// [Deprecated in v1.10]
typedef VB1ElementType18 VB1ElementType;
// [Deprecated in v1.10]
typedef VB2ElementType18 VB2ElementType;
PRAGMA_ENABLE_DEPRECATION_WARNINGS
// Vertex structure for all skinned models (versioned)
// [Deprecated in v1.10]
PACK_STRUCT(struct SkinnedModelVertex1
PACK_STRUCT(struct DEPRECATED("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.") SkinnedModelVertex1
{
Float3 Position;
Half2 TexCoord;
@@ -145,11 +149,13 @@ PACK_STRUCT(struct SkinnedModelVertex1
Color32 BlendWeights;
});
PRAGMA_DISABLE_DEPRECATION_WARNINGS
// [Deprecated in v1.10]
typedef SkinnedModelVertex1 SkinnedModelVertex;
PRAGMA_ENABLE_DEPRECATION_WARNINGS
// [Deprecated in v1.10]
struct RawSkinnedModelVertex
struct DEPRECATED("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.") RawSkinnedModelVertex
{
Float3 Position;
Float2 TexCoord;
@@ -161,7 +167,7 @@ struct RawSkinnedModelVertex
};
// [Deprecated on 28.04.2023, expires on 01.01.2024]
PACK_STRUCT(struct VB0SkinnedElementType1
PACK_STRUCT(struct DEPRECATED("Use newer format.") VB0SkinnedElementType1
{
Float3 Position;
Half2 TexCoord;
@@ -172,7 +178,7 @@ PACK_STRUCT(struct VB0SkinnedElementType1
});
// [Deprecated in v1.10]
PACK_STRUCT(struct VB0SkinnedElementType2
PACK_STRUCT(struct DEPRECATED("Use new MeshAccessor and depend on GPUVertexLayout when accessing mesh data.") VB0SkinnedElementType2
{
Float3 Position;
Half2 TexCoord;
@@ -184,5 +190,7 @@ PACK_STRUCT(struct VB0SkinnedElementType2
static GPUVertexLayout* GetLayout();
});
PRAGMA_DISABLE_DEPRECATION_WARNINGS
// [Deprecated in v1.10]
typedef VB0SkinnedElementType2 VB0SkinnedElementType;
PRAGMA_ENABLE_DEPRECATION_WARNINGS

View File

@@ -1496,8 +1496,8 @@ void PixelFormatExtensions::GetSamplerInternal(PixelFormat format, int32& pixelS
if (const PixelFormatSampler* sampler = PixelFormatSampler::Get(format))
{
pixelSize = sampler->PixelSize;
*read = sampler->Read;
*write = sampler->Write;
*read = (void*)sampler->Read;
*write = (void*)sampler->Write;
}
}

View File

@@ -557,6 +557,26 @@ float RenderTools::ComputeTemporalTime()
}
void RenderTools::CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10G10B10A2& resultTangent, const Float3& normal)
{
// [Deprecated in v1.10]
Float3 n;
Float4 t;
CalculateTangentFrame(n, t, normal);
resultNormal = Float1010102(n, 0);
resultTangent = Float1010102(t);
}
void RenderTools::CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10G10B10A2& resultTangent, const Float3& normal, const Float3& tangent)
{
// [Deprecated in v1.10]
Float3 n;
Float4 t;
CalculateTangentFrame(n, t, normal, tangent);
resultNormal = Float1010102(n, 0);
resultTangent = Float1010102(t);
}
void RenderTools::CalculateTangentFrame(Float3& resultNormal, Float4& resultTangent, const Float3& normal)
{
// Calculate tangent
const Float3 c1 = Float3::Cross(normal, Float3::UnitZ);
@@ -568,19 +588,19 @@ void RenderTools::CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10
const byte sign = static_cast<byte>(Float3::Dot(Float3::Cross(bitangent, normal), tangent) < 0.0f ? 1 : 0);
// Set tangent frame
resultNormal = Float1010102(normal * 0.5f + 0.5f, 0);
resultTangent = Float1010102(tangent * 0.5f + 0.5f, sign);
resultNormal = normal * 0.5f + 0.5f;
resultTangent = Float4(tangent * 0.5f + 0.5f, sign);
}
void RenderTools::CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10G10B10A2& resultTangent, const Float3& normal, const Float3& tangent)
void RenderTools::CalculateTangentFrame(Float3& resultNormal, Float4& resultTangent, const Float3& normal, const Float3& tangent)
{
// Calculate bitangent sign
const Float3 bitangent = Float3::Normalize(Float3::Cross(normal, tangent));
const byte sign = static_cast<byte>(Float3::Dot(Float3::Cross(bitangent, normal), tangent) < 0.0f ? 1 : 0);
// Set tangent frame
resultNormal = Float1010102(normal * 0.5f + 0.5f, 0);
resultTangent = Float1010102(tangent * 0.5f + 0.5f, sign);
resultNormal = normal * 0.5f + 0.5f;
resultTangent = Float4(tangent * 0.5f + 0.5f, sign);
}
void RenderTools::ComputeSphereModelDrawMatrix(const RenderView& view, const Float3& position, float radius, Matrix& resultWorld, bool& resultIsViewInside)

View File

@@ -130,8 +130,14 @@ public:
// Returns 0-1 value based on unscaled draw time for temporal effects to reduce artifacts from screen-space dithering when using Temporal Anti-Aliasing.
static float ComputeTemporalTime();
static void CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10G10B10A2& resultTangent, const Float3& normal);
static void CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10G10B10A2& resultTangent, const Float3& normal, const Float3& tangent);
// [Deprecated in v1.10]
DEPRECATED("Use CalculateTangentFrame with unpacked Float3/Float4.") static void CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10G10B10A2& resultTangent, const Float3& normal);
// [Deprecated in v1.10]
DEPRECATED("Use CalculateTangentFrame with unpacked Float3/Float4.") static void CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10G10B10A2& resultTangent, const Float3& normal, const Float3& tangent);
// Result normal/tangent are already packed into [0;1] range.
static void CalculateTangentFrame(Float3& resultNormal, Float4& resultTangent, const Float3& normal);
static void CalculateTangentFrame(Float3& resultNormal, Float4& resultTangent, const Float3& normal, const Float3& tangent);
static void ComputeSphereModelDrawMatrix(const RenderView& view, const Float3& position, float radius, Matrix& resultWorld, bool& resultIsViewInside);
};

View File

@@ -1,9 +1,7 @@
// 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/Core/Math/Math.h"
#include "Engine/Core/Types/Span.h"