You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
5234 lines
163 KiB
C++
5234 lines
163 KiB
C++
/*
|
|
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
|
* its licensors.
|
|
*
|
|
* For complete copyright and license terms please see the LICENSE at the root of this
|
|
* distribution (the "License"). All use of this software is governed by the License,
|
|
* or, if provided, by the license below or the license accompanying this file. Do not
|
|
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
*
|
|
*/
|
|
// Original file Copyright Crytek GMBH or its affiliates, used under license.
|
|
|
|
#include "RenderDll_precompiled.h"
|
|
#include "I3DEngine.h"
|
|
#include "IIndexedMesh.h"
|
|
#include "CGFContent.h"
|
|
#include "GeomQuery.h"
|
|
#include "RenderMesh.h"
|
|
#include "PostProcess/PostEffects.h"
|
|
#include "QTangent.h"
|
|
#include <Common/Memory/VRAMDrillerBus.h>
|
|
|
|
#if !defined(NULL_RENDERER)
|
|
#include "XRenderD3D9/DriverD3D.h"
|
|
#endif
|
|
|
|
#define RENDERMESH_ASYNC_MEMCPY_THRESHOLD (1 << 10)
|
|
#define MESH_DATA_DEFAULT_ALIGN (128u)
|
|
|
|
#ifdef MESH_TESSELLATION_RENDERER
|
|
// Rebuilding the adjaency information needs access to system copies of the buffers. Needs fixing
|
|
#undef BUFFER_ENABLE_DIRECT_ACCESS
|
|
#define BUFFER_ENABLE_DIRECT_ACCESS 0
|
|
#endif
|
|
|
|
namespace
|
|
{
|
|
class CConditionalLock
|
|
{
|
|
public:
|
|
CConditionalLock(CryCriticalSection& lock, bool doConditionalLock)
|
|
: m_critSection(lock)
|
|
, m_doConditionalLock(doConditionalLock)
|
|
{
|
|
if (m_doConditionalLock)
|
|
{
|
|
m_critSection.Lock();
|
|
}
|
|
}
|
|
~CConditionalLock()
|
|
{
|
|
if (m_doConditionalLock)
|
|
{
|
|
m_critSection.Unlock();
|
|
}
|
|
}
|
|
|
|
private:
|
|
CryCriticalSection& m_critSection;
|
|
bool m_doConditionalLock = false;
|
|
};
|
|
|
|
static inline void RelinkTail(util::list<CRenderMesh>& instance, util::list<CRenderMesh>& list, int threadId)
|
|
{
|
|
// Conditional lock logic - When multi-threaded rendering is enabled,
|
|
// this data is double buffered and we must only lock when attempting
|
|
// to modify the fill thread data. Only the render thread should
|
|
// access the process thread data so we don't need to lock in that case.
|
|
// In single-threaded rendering (editor) we must always lock because
|
|
// the data is not double buffered.
|
|
bool isRenderThread = gRenDev->m_pRT->IsRenderThread();
|
|
bool doConditionalLock = !isRenderThread || threadId == gRenDev->m_pRT->CurThreadFill() || CRenderer::CV_r_multithreaded == 0;
|
|
|
|
CConditionalLock lock(CRenderMesh::m_sLinkLock, doConditionalLock);
|
|
instance.relink_tail(&list);
|
|
}
|
|
|
|
struct SMeshPool
|
|
{
|
|
IGeneralMemoryHeap* m_MeshDataPool;
|
|
IGeneralMemoryHeap* m_MeshInstancePool;
|
|
void* m_MeshDataMemory;
|
|
void* m_MeshInstanceMemory;
|
|
CryCriticalSection m_MeshPoolCS;
|
|
SMeshPoolStatistics m_MeshDataPoolStats;
|
|
SMeshPool()
|
|
: m_MeshPoolCS()
|
|
, m_MeshDataPool()
|
|
, m_MeshInstancePool()
|
|
, m_MeshDataMemory()
|
|
, m_MeshInstanceMemory()
|
|
, m_MeshDataPoolStats()
|
|
{}
|
|
};
|
|
static SMeshPool s_MeshPool;
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
static void* AllocateMeshData(size_t nSize, size_t nAlign = MESH_DATA_DEFAULT_ALIGN, [[maybe_unused]] bool bFlush = false)
|
|
{
|
|
nSize = (nSize + (nAlign - 1)) & ~(nAlign - 1);
|
|
|
|
if (s_MeshPool.m_MeshDataPool && s_MeshPool.m_MeshDataPoolStats.nPoolSize > nSize)
|
|
{
|
|
try_again:
|
|
s_MeshPool.m_MeshPoolCS.Lock();
|
|
void* ptr = s_MeshPool.m_MeshDataPool->Memalign(nAlign, nSize, "RENDERMESH_POOL");
|
|
if (ptr)
|
|
{
|
|
s_MeshPool.m_MeshDataPoolStats.nPoolInUse += s_MeshPool.m_MeshDataPool->UsableSize(ptr);
|
|
s_MeshPool.m_MeshDataPoolStats.nPoolInUsePeak =
|
|
std::max(
|
|
s_MeshPool.m_MeshDataPoolStats.nPoolInUsePeak,
|
|
s_MeshPool.m_MeshDataPoolStats.nPoolInUse);
|
|
s_MeshPool.m_MeshPoolCS.Unlock();
|
|
return ptr;
|
|
}
|
|
else
|
|
{
|
|
s_MeshPool.m_MeshPoolCS.Unlock();
|
|
// Clean up the stale mesh temporary data - and do it from the main thread.
|
|
if (gRenDev->m_pRT->IsMainThread() && CRenderMesh::ClearStaleMemory(false, gRenDev->m_RP.m_nFillThreadID))
|
|
{
|
|
goto try_again;
|
|
}
|
|
else if (gRenDev->m_pRT->IsRenderThread() && CRenderMesh::ClearStaleMemory(false, gRenDev->m_RP.m_nProcessThreadID))
|
|
{
|
|
goto try_again;
|
|
}
|
|
}
|
|
s_MeshPool.m_MeshPoolCS.Lock();
|
|
s_MeshPool.m_MeshDataPoolStats.nFallbacks += nSize;
|
|
s_MeshPool.m_MeshPoolCS.Unlock();
|
|
}
|
|
return CryModuleMemalign(nSize, nAlign);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
static void FreeMeshData(void* ptr)
|
|
{
|
|
if (ptr == NULL)
|
|
{
|
|
return;
|
|
}
|
|
{
|
|
AUTO_LOCK(s_MeshPool.m_MeshPoolCS);
|
|
size_t nSize = 0u;
|
|
if (s_MeshPool.m_MeshDataPool && (nSize = s_MeshPool.m_MeshDataPool->Free(ptr)) > 0)
|
|
{
|
|
s_MeshPool.m_MeshDataPoolStats.nPoolInUse -=
|
|
(nSize < s_MeshPool.m_MeshDataPoolStats.nPoolInUse) ? nSize : s_MeshPool.m_MeshDataPoolStats.nPoolInUse;
|
|
return;
|
|
}
|
|
}
|
|
CryModuleMemalignFree(ptr);
|
|
}
|
|
|
|
template<typename Type>
|
|
static Type* AllocateMeshData(size_t nCount = 1)
|
|
{
|
|
void* pStorage = AllocateMeshData(sizeof(Type) * nCount, std::max((size_t)TARGET_DEFAULT_ALIGN, (size_t)__alignof(Type)));
|
|
if (!pStorage)
|
|
{
|
|
return NULL;
|
|
}
|
|
Type* pTypeArray = reinterpret_cast<Type*>(pStorage);
|
|
for (size_t i = 0; i < nCount; ++i)
|
|
{
|
|
new (pTypeArray + i)Type;
|
|
}
|
|
return pTypeArray;
|
|
}
|
|
|
|
static bool InitializePool()
|
|
{
|
|
if (gRenDev->CV_r_meshpoolsize > 0)
|
|
{
|
|
if (s_MeshPool.m_MeshDataPool || s_MeshPool.m_MeshDataMemory)
|
|
{
|
|
CryFatalError("render meshpool already initialized");
|
|
return false;
|
|
}
|
|
size_t poolSize = static_cast<size_t>(gRenDev->CV_r_meshpoolsize) * 1024U;
|
|
s_MeshPool.m_MeshDataMemory = CryModuleMemalign(poolSize, 128u);
|
|
if (!s_MeshPool.m_MeshDataMemory)
|
|
{
|
|
CryFatalError("could not allocate render meshpool");
|
|
return false;
|
|
}
|
|
|
|
// Initialize the actual pool
|
|
s_MeshPool.m_MeshDataPool = gEnv->pSystem->GetIMemoryManager()->CreateGeneralMemoryHeap(
|
|
s_MeshPool.m_MeshDataMemory, poolSize, "RENDERMESH_POOL");
|
|
s_MeshPool.m_MeshDataPoolStats.nPoolSize = poolSize;
|
|
}
|
|
if (gRenDev->CV_r_meshinstancepoolsize && !s_MeshPool.m_MeshInstancePool)
|
|
{
|
|
size_t poolSize = static_cast<size_t>(gRenDev->CV_r_meshinstancepoolsize) * 1024U;
|
|
s_MeshPool.m_MeshInstanceMemory = CryModuleMemalign(poolSize, 128u);
|
|
if (!s_MeshPool.m_MeshInstanceMemory)
|
|
{
|
|
CryFatalError("could not allocate render mesh instance pool");
|
|
return false;
|
|
}
|
|
|
|
s_MeshPool.m_MeshInstancePool = gEnv->pSystem->GetIMemoryManager()->CreateGeneralMemoryHeap(
|
|
s_MeshPool.m_MeshInstanceMemory, poolSize, "RENDERMESH_INSTANCE_POOL");
|
|
s_MeshPool.m_MeshDataPoolStats.nInstancePoolInUse = 0;
|
|
s_MeshPool.m_MeshDataPoolStats.nInstancePoolInUsePeak = 0;
|
|
s_MeshPool.m_MeshDataPoolStats.nInstancePoolSize = gRenDev->CV_r_meshinstancepoolsize * 1024;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void ShutdownPool()
|
|
{
|
|
if (s_MeshPool.m_MeshDataPool)
|
|
{
|
|
s_MeshPool.m_MeshDataPool->Release();
|
|
s_MeshPool.m_MeshDataPool = NULL;
|
|
}
|
|
if (s_MeshPool.m_MeshDataMemory)
|
|
{
|
|
CryModuleMemalignFree(s_MeshPool.m_MeshDataMemory);
|
|
s_MeshPool.m_MeshDataMemory = NULL;
|
|
}
|
|
if (s_MeshPool.m_MeshInstancePool)
|
|
{
|
|
s_MeshPool.m_MeshInstancePool->Cleanup();
|
|
s_MeshPool.m_MeshInstancePool->Release();
|
|
s_MeshPool.m_MeshInstancePool = NULL;
|
|
}
|
|
if (s_MeshPool.m_MeshInstanceMemory)
|
|
{
|
|
CryModuleMemalignFree(s_MeshPool.m_MeshInstanceMemory);
|
|
s_MeshPool.m_MeshInstanceMemory = NULL;
|
|
}
|
|
}
|
|
|
|
static void* AllocateMeshInstanceData(size_t size, size_t align)
|
|
{
|
|
if (s_MeshPool.m_MeshInstancePool)
|
|
{
|
|
if (void* ptr = s_MeshPool.m_MeshInstancePool->Memalign(align, size, "rendermesh instance data"))
|
|
{
|
|
# if !defined(_RELEASE)
|
|
AUTO_LOCK(s_MeshPool.m_MeshPoolCS);
|
|
s_MeshPool.m_MeshDataPoolStats.nInstancePoolInUsePeak = std::max(
|
|
s_MeshPool.m_MeshDataPoolStats.nInstancePoolInUsePeak,
|
|
s_MeshPool.m_MeshDataPoolStats.nInstancePoolInUse += size);
|
|
# endif
|
|
return ptr;
|
|
}
|
|
}
|
|
return CryModuleMemalign(size, align);
|
|
}
|
|
|
|
static void FreeMeshInstanceData(void* ptr)
|
|
{
|
|
if (s_MeshPool.m_MeshInstancePool)
|
|
{
|
|
size_t size = s_MeshPool.m_MeshInstancePool->UsableSize(ptr);
|
|
if (size)
|
|
{
|
|
# if !defined(_RELEASE)
|
|
AUTO_LOCK(s_MeshPool.m_MeshPoolCS);
|
|
s_MeshPool.m_MeshDataPoolStats.nInstancePoolInUse -= size;
|
|
# endif
|
|
s_MeshPool.m_MeshInstancePool->Free(ptr);
|
|
return;
|
|
}
|
|
}
|
|
CryModuleMemalignFree(ptr);
|
|
}
|
|
}
|
|
|
|
#define alignup(alignment, value) ((((uintptr_t)(value)) + ((alignment) - 1)) & (~((uintptr_t)(alignment) - 1)))
|
|
#define alignup16(value) alignup(16, value)
|
|
|
|
namespace
|
|
{
|
|
inline uint32 GetCurrentRenderFrameID()
|
|
{
|
|
ASSERT_IS_MAIN_THREAD(gRenDev->m_pRT);
|
|
return gRenDev->m_RP.m_TI[gRenDev->m_RP.m_nFillThreadID].m_nFrameUpdateID;
|
|
};
|
|
}
|
|
|
|
#if defined(USE_VBIB_PUSH_DOWN)
|
|
static inline bool VidMemPushDown(void* pDst, const void* pSrc, size_t nSize, void* pDst1 = NULL, const void* pSrc1 = NULL, size_t nSize1 = 0, int cachePosStride = -1, void* pFP16Dst = NULL, uint32 nVerts = 0)
|
|
{
|
|
}
|
|
#define ASSERT_LOCK
|
|
static std::vector<CRenderMesh*> g_MeshCleanupVec; //for cleanup of meshes itself
|
|
#else
|
|
#define ASSERT_LOCK assert((m_nVerts == 0) || pData)
|
|
#endif
|
|
|
|
#if !defined(_RELEASE)
|
|
ILINE void CheckVideoBufferAccessViolation([[maybe_unused]] CRenderMesh& mesh)
|
|
{
|
|
//LogWarning("CRenderMesh::LockVB: accessing video buffer for cgf=%s",mesh.GetSourceName());
|
|
}
|
|
#define MESSAGE_VIDEO_BUFFER_ACC_ATTEMPT CheckVideoBufferAccessViolation(*this)
|
|
#else
|
|
#define MESSAGE_VIDEO_BUFFER_ACC_ATTEMPT
|
|
#endif
|
|
|
|
CryCriticalSection CRenderMesh::m_sLinkLock;
|
|
|
|
// Additional streams stride
|
|
int32 CRenderMesh::m_cSizeStream[VSF_NUM] =
|
|
{
|
|
-1,
|
|
sizeof(SPipTangents), // VSF_TANGENTS
|
|
sizeof(SPipQTangents), // VSF_QTANGENTS
|
|
sizeof(SVF_W4B_I4S), // VSF_HWSKIN_INFO
|
|
sizeof(SVF_P3F), // VSF_VERTEX_VELOCITY
|
|
# if ENABLE_NORMALSTREAM_SUPPORT
|
|
sizeof(SPipNormal), // VSF_NORMALS
|
|
#endif
|
|
};
|
|
|
|
util::list<CRenderMesh> CRenderMesh::m_MeshList;
|
|
util::list<CRenderMesh> CRenderMesh::m_MeshGarbageList[MAX_RELEASED_MESH_FRAMES];
|
|
util::list<CRenderMesh> CRenderMesh::m_MeshDirtyList[2];
|
|
util::list<CRenderMesh> CRenderMesh::m_MeshModifiedList[2];
|
|
|
|
int CRenderMesh::Release()
|
|
{
|
|
long refCnt = CryInterlockedDecrement(&m_nRefCounter);
|
|
# if !defined(_RELEASE)
|
|
if (refCnt < 0)
|
|
{
|
|
CryLogAlways("CRenderMesh::Release() called so many times on rendermesh that refcount became negative");
|
|
if (CRenderer::CV_r_BreakOnError)
|
|
{
|
|
__debugbreak();
|
|
}
|
|
}
|
|
# endif
|
|
if (refCnt == 0)
|
|
{
|
|
AUTO_LOCK(m_sLinkLock);
|
|
# if !defined(_RELEASE)
|
|
if (m_nFlags & FRM_RELEASED)
|
|
{
|
|
CryLogAlways("CRenderMesh::Release() mesh already in the garbage list (double delete pending)");
|
|
if (CRenderer::CV_r_BreakOnError)
|
|
{
|
|
__debugbreak();
|
|
}
|
|
}
|
|
# endif
|
|
m_nFlags |= FRM_RELEASED;
|
|
int nFrame = gRenDev->GetFrameID(false);
|
|
util::list<CRenderMesh>* garbage = &CRenderMesh::m_MeshGarbageList[nFrame & (MAX_RELEASED_MESH_FRAMES - 1)];
|
|
m_Chain.relink_tail(garbage);
|
|
}
|
|
|
|
return refCnt;
|
|
}
|
|
|
|
CRenderMesh::CRenderMesh()
|
|
{
|
|
#if defined(USE_VBIB_PUSH_DOWN)
|
|
m_VBIBFramePushID = 0;
|
|
#endif
|
|
memset(m_VBStream, 0x0, sizeof(m_VBStream));
|
|
|
|
SMeshStream* streams = (SMeshStream*)AllocateMeshInstanceData(sizeof(SMeshStream) * VSF_NUM, 64u);
|
|
for (signed i = 0; i < VSF_NUM; ++i)
|
|
{
|
|
new (m_VBStream[i] = &streams[i])SMeshStream();
|
|
}
|
|
|
|
m_keepSysMesh = false;
|
|
m_nLastRenderFrameID = 0;
|
|
m_nLastSubsetGCRenderFrameID = 0;
|
|
m_nThreadAccessCounter = 0;
|
|
for (size_t i = 0; i < 2; ++i)
|
|
{
|
|
m_asyncUpdateState[i] = m_asyncUpdateStateCounter[i] = 0;
|
|
}
|
|
|
|
# if !defined(_RELEASE) && defined(RM_CATCH_EXCESSIVE_LOCKS)
|
|
m_lockTime = 0.f;
|
|
# endif
|
|
}
|
|
|
|
CRenderMesh::CRenderMesh (const char* szType, const char* szSourceName, bool bLock)
|
|
{
|
|
memset(m_VBStream, 0x0, sizeof(m_VBStream));
|
|
|
|
SMeshStream* streams = (SMeshStream*)AllocateMeshInstanceData(sizeof(SMeshStream) * VSF_NUM, 64u);
|
|
for (signed i = 0; i < VSF_NUM; ++i)
|
|
{
|
|
new (m_VBStream[i] = &streams[i])SMeshStream();
|
|
}
|
|
|
|
m_keepSysMesh = false;
|
|
m_nRefCounter = 0;
|
|
m_nLastRenderFrameID = 0;
|
|
m_nLastSubsetGCRenderFrameID = 0;
|
|
|
|
m_sType = szType;
|
|
m_sSource = szSourceName;
|
|
|
|
m_vBoxMin = m_vBoxMax = Vec3(0, 0, 0); //used for hw occlusion test
|
|
m_nVerts = 0;
|
|
m_nInds = 0;
|
|
m_vertexFormat = AZ::Vertex::Format(eVF_P3F_C4B_T2F);
|
|
m_pVertexContainer = NULL;
|
|
|
|
{
|
|
AUTO_LOCK(m_sLinkLock);
|
|
m_Chain.relink_tail(&m_MeshList);
|
|
}
|
|
m_nPrimetiveType = eptTriangleList;
|
|
|
|
// m_nFrameRender = 0;
|
|
//m_nFrameUpdate = 0;
|
|
m_nClientTextureBindID = 0;
|
|
#ifdef RENDER_MESH_TRIANGLE_HASH_MAP_SUPPORT
|
|
m_pTrisMap = NULL;
|
|
#endif
|
|
|
|
m_pCachePos = NULL;
|
|
m_nFrameRequestCachePos = 0;
|
|
m_nFlagsCachePos = 0;
|
|
|
|
m_nFrameRequestCacheUVs = 0;
|
|
m_nFlagsCacheUVs = 0;
|
|
|
|
_SetRenderMeshType(eRMT_Static);
|
|
|
|
m_nFlags = 0;
|
|
m_fGeometricMeanFaceArea = 0.f;
|
|
m_nLod = 0;
|
|
|
|
#if defined(USE_VBIB_PUSH_DOWN)
|
|
m_VBIBFramePushID = 0;
|
|
#endif
|
|
|
|
m_nThreadAccessCounter = 0;
|
|
for (size_t i = 0; i < 2; ++i)
|
|
{
|
|
m_asyncUpdateState[i] = m_asyncUpdateStateCounter[i] = 0;
|
|
}
|
|
|
|
IF (bLock, 0)//when called from another thread like the Streaming AsyncCallbackCGF, we need to lock it
|
|
{
|
|
LockForThreadAccess();
|
|
}
|
|
}
|
|
|
|
void CRenderMesh::Cleanup()
|
|
{
|
|
FreeDeviceBuffers(false);
|
|
FreeSystemBuffers();
|
|
|
|
m_meshSubSetIndices.clear();
|
|
|
|
if (m_pVertexContainer)
|
|
{
|
|
m_pVertexContainer->m_lstVertexContainerUsers.Delete(this);
|
|
m_pVertexContainer = NULL;
|
|
}
|
|
|
|
for (int i = 0; i < m_lstVertexContainerUsers.Count(); i++)
|
|
{
|
|
if (m_lstVertexContainerUsers[i]->GetVertexContainer() == this)
|
|
{
|
|
m_lstVertexContainerUsers[i]->m_pVertexContainer = NULL;
|
|
}
|
|
}
|
|
m_lstVertexContainerUsers.Clear();
|
|
|
|
ReleaseRenderChunks(&m_ChunksSkinned);
|
|
ReleaseRenderChunks(&m_ChunksSubObjects);
|
|
ReleaseRenderChunks(&m_Chunks);
|
|
|
|
m_ChunksSubObjects.clear();
|
|
m_Chunks.clear();
|
|
|
|
#ifdef RENDER_MESH_TRIANGLE_HASH_MAP_SUPPORT
|
|
SAFE_DELETE(m_pTrisMap);
|
|
#endif
|
|
|
|
for (size_t i = 0; i < 2; ++i)
|
|
{
|
|
m_asyncUpdateState[i] = m_asyncUpdateStateCounter[i] = 0;
|
|
}
|
|
|
|
for (int i = 0; i < VSF_NUM; ++i)
|
|
{
|
|
if (m_VBStream[i])
|
|
{
|
|
m_VBStream[i]->~SMeshStream();
|
|
}
|
|
}
|
|
FreeMeshInstanceData(m_VBStream[0]);
|
|
memset(m_VBStream, 0, sizeof(m_VBStream));
|
|
|
|
for (size_t j = 0; j < 2; ++j)
|
|
{
|
|
for (size_t i = 0, end = m_CreatedBoneIndices[j].size(); i < end; ++i)
|
|
{
|
|
delete[] m_CreatedBoneIndices[j][i].pStream;
|
|
}
|
|
}
|
|
for (size_t i = 0, end = m_RemappedBoneIndices.size(); i < end; ++i)
|
|
{
|
|
if (m_RemappedBoneIndices[i].refcount && m_RemappedBoneIndices[i].guid != ~0u)
|
|
{
|
|
CryLogAlways("remapped bone indices with refcount '%d' still around for '%s 0x%p\n", m_RemappedBoneIndices[i].refcount, m_sSource.c_str(), this);
|
|
}
|
|
if (m_RemappedBoneIndices[i].buffer != ~0u)
|
|
{
|
|
// Unregister the allocation with the VRAM driller
|
|
void* address = reinterpret_cast<void*>(m_RemappedBoneIndices[i].buffer);
|
|
EBUS_EVENT(Render::Debug::VRAMDrillerBus, UnregisterAllocation, address);
|
|
|
|
gRenDev->m_DevBufMan.Destroy(m_RemappedBoneIndices[i].buffer);
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
CRenderMesh::~CRenderMesh()
|
|
{
|
|
// make sure to stop and delete all mesh subset indice tasks
|
|
ASSERT_IS_RENDER_THREAD(gRenDev->m_pRT);
|
|
|
|
int nThreadID = gRenDev->m_RP.m_nProcessThreadID;
|
|
for (int i = 0; i < RT_COMMAND_BUF_COUNT; SyncAsyncUpdate(i++))
|
|
{
|
|
;
|
|
}
|
|
|
|
// make sure no subset rendermesh job is still running which uses this mesh
|
|
for (int j = 0; j < RT_COMMAND_BUF_COUNT; ++j)
|
|
{
|
|
size_t nNumSubSetRenderMeshJobs = m_meshSubSetRenderMeshJobs[j].size();
|
|
for (size_t i = 0; i < nNumSubSetRenderMeshJobs; ++i)
|
|
{
|
|
SMeshSubSetIndicesJobEntry& rSubSetJob = m_meshSubSetRenderMeshJobs[j][i];
|
|
if (rSubSetJob.m_pSrcRM == this)
|
|
{
|
|
rSubSetJob.jobExecutor.WaitForCompletion();
|
|
rSubSetJob.m_pSrcRM = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
// remove ourself from deferred subset mesh garbage collection
|
|
for (size_t i = 0; i < m_deferredSubsetGarbageCollection[nThreadID].size(); ++i)
|
|
{
|
|
if (m_deferredSubsetGarbageCollection[nThreadID][i] == this)
|
|
{
|
|
m_deferredSubsetGarbageCollection[nThreadID][i] = NULL;
|
|
}
|
|
}
|
|
|
|
assert(m_nThreadAccessCounter == 0);
|
|
|
|
{
|
|
AUTO_LOCK(m_sLinkLock);
|
|
for (int i = 0; i < 2; ++i)
|
|
{
|
|
m_Dirty[i].erase(), m_Modified[i].erase();
|
|
}
|
|
m_Chain.erase();
|
|
}
|
|
|
|
Cleanup();
|
|
}
|
|
|
|
void CRenderMesh::ReleaseRenderChunks(TRenderChunkArray* pChunks)
|
|
{
|
|
if (pChunks)
|
|
{
|
|
for (size_t i = 0, c = pChunks->size(); i != c; ++i)
|
|
{
|
|
CRenderChunk& rChunk = pChunks->at(i);
|
|
|
|
if (rChunk.pRE)
|
|
{
|
|
CREMeshImpl* pRE = static_cast<CREMeshImpl*>(rChunk.pRE);
|
|
pRE->Release(false);
|
|
pRE->m_pRenderMesh = NULL;
|
|
rChunk.pRE = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SMeshStream* CRenderMesh::GetVertexStream(int nStream, uint32 nFlags)
|
|
{
|
|
SMeshStream*& pMS = m_VBStream[nStream];
|
|
IF (!pMS && (nFlags & FSL_WRITE), 0)
|
|
{
|
|
pMS = new (AllocateMeshInstanceData(sizeof(SMeshStream), alignof(SMeshStream)))SMeshStream;
|
|
}
|
|
return pMS;
|
|
}
|
|
|
|
void* CRenderMesh::LockVB(int nStream, uint32 nFlags, int nVerts, int* nStride, [[maybe_unused]] bool prefetchIB, [[maybe_unused]] bool inplaceCachePos)
|
|
{
|
|
FUNCTION_PROFILER_RENDERER;
|
|
# if !defined(_RELEASE)
|
|
if (!m_nThreadAccessCounter)
|
|
{
|
|
CryLogAlways("rendermesh must be locked via LockForThreadAccess() before LockIB/VB is called");
|
|
if (CRenderer::CV_r_BreakOnError)
|
|
{
|
|
__debugbreak();
|
|
}
|
|
}
|
|
#endif
|
|
const int threadId = gRenDev->m_RP.m_nFillThreadID;
|
|
|
|
if (!CanRender()) // if allocation failure suffered, don't lock anything anymore
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
SREC_AUTO_LOCK(m_sResLock);//need lock as resource must not be updated concurrently
|
|
SMeshStream* MS = GetVertexStream(nStream, nFlags);
|
|
|
|
#if defined(USE_VBIB_PUSH_DOWN)
|
|
m_VBIBFramePushID = gRenDev->m_RP.m_TI[gRenDev->m_RP.m_nFillThreadID].m_nFrameUpdateID;
|
|
if (nFlags == FSL_SYSTEM_CREATE || nFlags == FSL_SYSTEM_UPDATE)
|
|
{
|
|
MS->m_nLockFlags &= ~FSL_VBIBPUSHDOWN;
|
|
}
|
|
#endif
|
|
|
|
assert(nVerts <= (int)m_nVerts);
|
|
if (nVerts > (int)m_nVerts)
|
|
{
|
|
nVerts = m_nVerts;
|
|
}
|
|
if (nStride)
|
|
{
|
|
*nStride = GetStreamStride(nStream);
|
|
}
|
|
|
|
m_nFlags |= FRM_READYTOUPLOAD;
|
|
|
|
byte* pD;
|
|
|
|
if (nFlags == FSL_SYSTEM_CREATE)
|
|
{
|
|
lSysCreate:
|
|
RelinkTail(m_Modified[threadId], m_MeshModifiedList[threadId], threadId);
|
|
if (!MS->m_pUpdateData)
|
|
{
|
|
uint32 nSize = GetStreamSize(nStream);
|
|
pD = reinterpret_cast<byte*>(AllocateMeshData(nSize));
|
|
if (!pD)
|
|
{
|
|
return NULL;
|
|
}
|
|
MS->m_pUpdateData = pD;
|
|
}
|
|
else
|
|
{
|
|
pD = (byte*)MS->m_pUpdateData;
|
|
}
|
|
//MS->m_nFrameRequest = nFrame;
|
|
MS->m_nLockFlags = (FSL_SYSTEM_CREATE | (MS->m_nLockFlags & FSL_LOCKED));
|
|
return pD;
|
|
}
|
|
else
|
|
if (nFlags == FSL_SYSTEM_UPDATE)
|
|
{
|
|
lSysUpdate:
|
|
RelinkTail(m_Modified[threadId], m_MeshModifiedList[threadId], threadId);
|
|
if (!MS->m_pUpdateData)
|
|
{
|
|
MESSAGE_VIDEO_BUFFER_ACC_ATTEMPT;
|
|
CopyStreamToSystemForUpdate(*MS, GetStreamSize(nStream));
|
|
}
|
|
assert(nStream || MS->m_pUpdateData);
|
|
if (!MS->m_pUpdateData)
|
|
{
|
|
return NULL;
|
|
}
|
|
//MS->m_nFrameRequest = nFrame;
|
|
pD = (byte*)MS->m_pUpdateData;
|
|
MS->m_nLockFlags = (nFlags | (MS->m_nLockFlags & FSL_LOCKED));
|
|
return pD;
|
|
}
|
|
else if (nFlags == FSL_READ)
|
|
{
|
|
if (!MS)
|
|
{
|
|
return NULL;
|
|
}
|
|
RelinkTail(m_Dirty[threadId], m_MeshDirtyList[threadId], threadId);
|
|
if (MS->m_pUpdateData)
|
|
{
|
|
pD = (byte*)MS->m_pUpdateData;
|
|
return pD;
|
|
}
|
|
nFlags = FSL_READ | FSL_VIDEO;
|
|
}
|
|
|
|
if (nFlags == (FSL_READ | FSL_VIDEO))
|
|
{
|
|
if (!MS)
|
|
{
|
|
return NULL;
|
|
}
|
|
RelinkTail(m_Dirty[threadId], m_MeshDirtyList[threadId], threadId);
|
|
# if !BUFFER_ENABLE_DIRECT_ACCESS || defined(NULL_RENDERER)
|
|
if (gRenDev->m_pRT && gRenDev->m_pRT->IsMultithreaded())
|
|
{
|
|
// Always use system copy in MT mode
|
|
goto lSysUpdate;
|
|
}
|
|
else
|
|
# endif
|
|
{
|
|
buffer_handle_t nVB = MS->m_nID;
|
|
if (nVB == ~0u)
|
|
{
|
|
return NULL;
|
|
}
|
|
// Try to lock device buffer in single-threaded mode
|
|
if (!MS->m_pLockedData)
|
|
{
|
|
MESSAGE_VIDEO_BUFFER_ACC_ATTEMPT;
|
|
MS->m_pLockedData = gRenDev->m_DevBufMan.BeginRead(nVB);
|
|
if (MS->m_pLockedData)
|
|
{
|
|
MS->m_nLockFlags |= FSL_LOCKED;
|
|
}
|
|
}
|
|
if (MS->m_pLockedData)
|
|
{
|
|
++MS->m_nLockCount;
|
|
pD = (byte*)MS->m_pLockedData;
|
|
return pD;
|
|
}
|
|
}
|
|
}
|
|
if (nFlags == (FSL_VIDEO_CREATE))
|
|
{
|
|
// Only consoles support direct uploading to vram, but as the data is not
|
|
// double buffered in the non-dynamic case, only creation is supported
|
|
// DX11 has to use the deferred context to call map, which is not threadsafe
|
|
// DX9 can experience huge stalls if resources are used while rendering is performed
|
|
buffer_handle_t nVB = ~0u;
|
|
# if BUFFER_ENABLE_DIRECT_ACCESS && !defined(NULL_RENDERER)
|
|
nVB = MS->m_nID;
|
|
int nFrame = gRenDev->m_RP.m_TI[gRenDev->m_RP.m_nFillThreadID].m_nFrameUpdateID;
|
|
if ((nVB != ~0u && (MS->m_nFrameCreate != nFrame || MS->m_nElements != m_nVerts)) || !CRenderer::CV_r_buffer_enable_lockless_updates)
|
|
# endif
|
|
goto lSysCreate;
|
|
RelinkTail(m_Modified[threadId], m_MeshModifiedList[threadId], threadId);
|
|
if (nVB == ~0u && !CreateVidVertices(nStream))
|
|
{
|
|
RT_AllocationFailure("Create VB-Stream", GetStreamSize(nStream, m_nVerts));
|
|
return NULL;
|
|
}
|
|
nVB = MS->m_nID;
|
|
if (!MS->m_pLockedData)
|
|
{
|
|
MESSAGE_VIDEO_BUFFER_ACC_ATTEMPT;
|
|
if ((MS->m_pLockedData = gRenDev->m_DevBufMan.BeginWrite(nVB)) == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
MS->m_nLockFlags |= FSL_DIRECT | FSL_LOCKED;
|
|
}
|
|
++MS->m_nLockCount;
|
|
pD = (byte*)MS->m_pLockedData;
|
|
return pD;
|
|
}
|
|
if (nFlags == (FSL_VIDEO_UPDATE))
|
|
{
|
|
goto lSysUpdate;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
vtx_idx* CRenderMesh::LockIB(uint32 nFlags, int nOffset, [[maybe_unused]] int nInds)
|
|
{
|
|
FUNCTION_PROFILER_RENDERER;
|
|
|
|
byte* pD;
|
|
# if !defined(_RELEASE)
|
|
if (!m_nThreadAccessCounter)
|
|
{
|
|
CryLogAlways("rendermesh must be locked via LockForThreadAccess() before LockIB/VB is called");
|
|
if (CRenderer::CV_r_BreakOnError)
|
|
{
|
|
__debugbreak();
|
|
}
|
|
}
|
|
#endif
|
|
if (!CanRender()) // if allocation failure suffered, don't lock anything anymore
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
const int threadId = gRenDev->m_RP.m_nFillThreadID;
|
|
|
|
int nFrame = gRenDev->m_RP.m_TI[gRenDev->m_RP.m_nFillThreadID].m_nFrameUpdateID;
|
|
SREC_AUTO_LOCK(m_sResLock);//need lock as resource must not be updated concurrently
|
|
|
|
#if defined(USE_VBIB_PUSH_DOWN)
|
|
m_VBIBFramePushID = gRenDev->m_RP.m_TI[gRenDev->m_RP.m_nFillThreadID].m_nFrameUpdateID;
|
|
if (nFlags == FSL_SYSTEM_CREATE || nFlags == FSL_SYSTEM_UPDATE)
|
|
{
|
|
m_IBStream.m_nLockFlags &= ~FSL_VBIBPUSHDOWN;
|
|
}
|
|
#endif
|
|
m_nFlags |= FRM_READYTOUPLOAD;
|
|
|
|
assert(nInds <= (int)m_nInds);
|
|
if (nFlags == FSL_SYSTEM_CREATE)
|
|
{
|
|
lSysCreate:
|
|
RelinkTail(m_Modified[threadId], m_MeshModifiedList[threadId], threadId);
|
|
if (!m_IBStream.m_pUpdateData)
|
|
{
|
|
uint32 nSize = m_nInds * sizeof(vtx_idx);
|
|
pD = reinterpret_cast<byte*>(AllocateMeshData(nSize));
|
|
if (!pD)
|
|
{
|
|
return NULL;
|
|
}
|
|
m_IBStream.m_pUpdateData = (vtx_idx*)pD;
|
|
}
|
|
else
|
|
{
|
|
pD = (byte*)m_IBStream.m_pUpdateData;
|
|
}
|
|
//m_IBStream.m_nFrameRequest = nFrame;
|
|
m_IBStream.m_nLockFlags = (nFlags | (m_IBStream.m_nLockFlags & FSL_LOCKED));
|
|
return (vtx_idx*)&pD[nOffset];
|
|
}
|
|
else
|
|
if (nFlags == FSL_SYSTEM_UPDATE)
|
|
{
|
|
lSysUpdate:
|
|
RelinkTail(m_Modified[threadId], m_MeshModifiedList[threadId], threadId);
|
|
if (!m_IBStream.m_pUpdateData)
|
|
{
|
|
MESSAGE_VIDEO_BUFFER_ACC_ATTEMPT;
|
|
CopyStreamToSystemForUpdate(m_IBStream, sizeof(vtx_idx) * m_nInds);
|
|
}
|
|
assert(m_IBStream.m_pUpdateData);
|
|
if (!m_IBStream.m_pUpdateData)
|
|
{
|
|
return NULL;
|
|
}
|
|
//m_IBStream.m_nFrameRequest = nFrame;
|
|
pD = (byte*)m_IBStream.m_pUpdateData;
|
|
m_IBStream.m_nLockFlags = (nFlags | (m_IBStream.m_nLockFlags & FSL_LOCKED));
|
|
return (vtx_idx*)&pD[nOffset];
|
|
}
|
|
else if (nFlags == FSL_READ)
|
|
{
|
|
RelinkTail(m_Dirty[threadId], m_MeshDirtyList[threadId], threadId);
|
|
if (m_IBStream.m_pUpdateData)
|
|
{
|
|
pD = (byte*)m_IBStream.m_pUpdateData;
|
|
return (vtx_idx*)&pD[nOffset];
|
|
}
|
|
nFlags = FSL_READ | FSL_VIDEO;
|
|
}
|
|
|
|
if (nFlags == (FSL_READ | FSL_VIDEO))
|
|
{
|
|
RelinkTail(m_Dirty[threadId], m_MeshDirtyList[threadId], threadId);
|
|
buffer_handle_t nIB = m_IBStream.m_nID;
|
|
if (nIB == ~0u)
|
|
{
|
|
return NULL;
|
|
}
|
|
if (gRenDev->m_pRT && gRenDev->m_pRT->IsMultithreaded())
|
|
{
|
|
// Always use system copy in MT mode
|
|
goto lSysUpdate;
|
|
}
|
|
else
|
|
{
|
|
// TODO: make smart caching mesh algorithm for consoles
|
|
if (!m_IBStream.m_pLockedData)
|
|
{
|
|
MESSAGE_VIDEO_BUFFER_ACC_ATTEMPT;
|
|
m_IBStream.m_pLockedData = gRenDev->m_DevBufMan.BeginRead(nIB);
|
|
if (m_IBStream.m_pLockedData)
|
|
{
|
|
m_IBStream.m_nLockFlags |= FSL_LOCKED;
|
|
}
|
|
}
|
|
if (m_IBStream.m_pLockedData)
|
|
{
|
|
pD = (byte*)m_IBStream.m_pLockedData;
|
|
++m_IBStream.m_nLockCount;
|
|
return (vtx_idx*)&pD[nOffset];
|
|
}
|
|
}
|
|
}
|
|
if (nFlags == (FSL_VIDEO_CREATE))
|
|
{
|
|
// Only consoles support direct uploading to vram, but as the data is not
|
|
// double buffered, only creation is supported
|
|
// DX11 has to use the deferred context to call map, which is not threadsafe
|
|
// DX9 can experience huge stalls if resources are used while rendering is performed
|
|
buffer_handle_t nIB = -1;
|
|
# if BUFFER_ENABLE_DIRECT_ACCESS && !defined(NULL_RENDERER)
|
|
nIB = m_IBStream.m_nID;
|
|
if ((nIB != ~0u && (m_IBStream.m_nFrameCreate || m_IBStream.m_nElements != m_nInds)) || !CRenderer::CV_r_buffer_enable_lockless_updates)
|
|
# endif
|
|
goto lSysCreate;
|
|
RelinkTail(m_Modified[threadId], m_MeshModifiedList[threadId], threadId);
|
|
if (m_IBStream.m_nID == ~0u)
|
|
{
|
|
size_t bufferSize = m_nInds * sizeof(vtx_idx);
|
|
nIB = (m_IBStream.m_nID = gRenDev->m_DevBufMan.Create(BBT_INDEX_BUFFER, (BUFFER_USAGE)m_eType, bufferSize));
|
|
m_IBStream.m_nFrameCreate = nFrame;
|
|
|
|
// Register the allocation with the VRAM driller
|
|
void* address = reinterpret_cast<void*>(nIB);
|
|
const char* bufferName = GetSourceName();
|
|
EBUS_EVENT(Render::Debug::VRAMDrillerBus, RegisterAllocation, address, bufferSize, bufferName, Render::Debug::VRAM_CATEGORY_BUFFER, Render::Debug::VRAM_SUBCATEGORY_BUFFER_INDEX_BUFFER);
|
|
}
|
|
if (nIB == ~0u)
|
|
{
|
|
RT_AllocationFailure("Create IB-Stream", m_nInds * sizeof(vtx_idx));
|
|
return NULL;
|
|
}
|
|
m_IBStream.m_nElements = m_nInds;
|
|
if (!m_IBStream.m_pLockedData)
|
|
{
|
|
MESSAGE_VIDEO_BUFFER_ACC_ATTEMPT;
|
|
if ((m_IBStream.m_pLockedData = gRenDev->m_DevBufMan.BeginWrite(nIB)) == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
m_IBStream.m_nLockFlags |= FSL_DIRECT | FSL_LOCKED;
|
|
}
|
|
++m_IBStream.m_nLockCount;
|
|
pD = (byte*)m_IBStream.m_pLockedData;
|
|
return (vtx_idx*)&pD[nOffset];
|
|
}
|
|
if (nFlags == (FSL_VIDEO_UPDATE))
|
|
{
|
|
goto lSysUpdate;
|
|
}
|
|
|
|
assert(0);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
ILINE void CRenderMesh::UnlockVB(int nStream)
|
|
{
|
|
SREC_AUTO_LOCK(m_sResLock);
|
|
SMeshStream* pMS = GetVertexStream(nStream, 0);
|
|
if (pMS && pMS->m_nLockFlags & FSL_LOCKED)
|
|
{
|
|
assert(pMS->m_nLockCount);
|
|
if ((--pMS->m_nLockCount) == 0)
|
|
{
|
|
gRenDev->m_DevBufMan.EndReadWrite(pMS->m_nID);
|
|
pMS->m_nLockFlags &= ~FSL_LOCKED;
|
|
pMS->m_pLockedData = NULL;
|
|
}
|
|
}
|
|
if (pMS && (pMS->m_nLockFlags & FSL_WRITE) && (pMS->m_nLockFlags & (FSL_SYSTEM_CREATE | FSL_SYSTEM_UPDATE)))
|
|
{
|
|
pMS->m_nLockFlags &= ~(FSL_SYSTEM_CREATE | FSL_SYSTEM_UPDATE);
|
|
pMS->m_nFrameRequest = gRenDev->m_RP.m_TI[gRenDev->m_RP.m_nFillThreadID].m_nFrameUpdateID;
|
|
}
|
|
}
|
|
|
|
ILINE void CRenderMesh::UnlockIB()
|
|
{
|
|
SREC_AUTO_LOCK(m_sResLock);
|
|
if (m_IBStream.m_nLockFlags & FSL_LOCKED)
|
|
{
|
|
assert(m_IBStream.m_nLockCount);
|
|
if ((--m_IBStream.m_nLockCount) == 0)
|
|
{
|
|
gRenDev->m_DevBufMan.EndReadWrite(m_IBStream.m_nID);
|
|
m_IBStream.m_nLockFlags &= ~FSL_LOCKED;
|
|
m_IBStream.m_pLockedData = NULL;
|
|
}
|
|
}
|
|
if ((m_IBStream.m_nLockFlags & FSL_WRITE) && (m_IBStream.m_nLockFlags & (FSL_SYSTEM_CREATE | FSL_SYSTEM_UPDATE)))
|
|
{
|
|
m_IBStream.m_nLockFlags &= ~(FSL_SYSTEM_CREATE | FSL_SYSTEM_UPDATE);
|
|
m_IBStream.m_nFrameRequest = gRenDev->m_RP.m_TI[gRenDev->m_RP.m_nFillThreadID].m_nFrameUpdateID;
|
|
}
|
|
}
|
|
|
|
void CRenderMesh::UnlockStream(int nStream)
|
|
{
|
|
UnlockVB(nStream);
|
|
SREC_AUTO_LOCK(m_sResLock);
|
|
|
|
if (nStream == VSF_GENERAL)
|
|
{
|
|
if (m_nFlagsCachePos && m_pCachePos)
|
|
{
|
|
uint32 i;
|
|
int nStride;
|
|
byte* pDst = (byte*)LockVB(nStream, FSL_SYSTEM_UPDATE, m_nVerts, &nStride);
|
|
assert(pDst);
|
|
if (pDst)
|
|
{
|
|
for (i = 0; i < m_nVerts; i++)
|
|
{
|
|
Vec3f16* pVDst = (Vec3f16*)pDst;
|
|
*pVDst = m_pCachePos[i];
|
|
pDst += nStride;
|
|
}
|
|
}
|
|
m_nFlagsCachePos = 0;
|
|
}
|
|
|
|
if (m_nFlagsCacheUVs && m_UVCache.size() > 0)
|
|
{
|
|
int nStride;
|
|
byte* streamStart = (byte*)LockVB(nStream, FSL_SYSTEM_UPDATE, m_nVerts, &nStride);
|
|
|
|
// Iterate over the cached uv coordinates and write them to the vertex buffer stream
|
|
for (int uvSet = 0; uvSet < m_UVCache.size(); ++uvSet)
|
|
{
|
|
if (m_UVCache[uvSet])
|
|
{
|
|
uint texCoordOffset = 0;
|
|
GetVertexFormat().TryCalculateOffset(texCoordOffset, AZ::Vertex::AttributeUsage::TexCoord, uvSet);
|
|
byte* texCoord = streamStart + texCoordOffset;
|
|
|
|
assert(streamStart);
|
|
if (streamStart)
|
|
{
|
|
for (uint32 i = 0; i < m_nVerts; i++)
|
|
{
|
|
Vec2f16* pVDst = (Vec2f16*)texCoord;
|
|
*pVDst = m_UVCache[uvSet][i];
|
|
texCoord += nStride;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
m_nFlagsCacheUVs = 0;
|
|
}
|
|
}
|
|
|
|
SMeshStream* pMS = GetVertexStream(nStream, 0);
|
|
IF (pMS, 1)
|
|
{
|
|
pMS->m_nLockFlags &= ~(FSL_WRITE | FSL_READ | FSL_SYSTEM | FSL_VIDEO);
|
|
}
|
|
}
|
|
void CRenderMesh::UnlockIndexStream()
|
|
{
|
|
|
|
UnlockIB();
|
|
m_IBStream.m_nLockFlags &= ~(FSL_WRITE | FSL_READ | FSL_SYSTEM | FSL_VIDEO);
|
|
}
|
|
|
|
bool CRenderMesh::CopyStreamToSystemForUpdate(SMeshStream& MS, size_t nSize)
|
|
{
|
|
FUNCTION_PROFILER_RENDERER;
|
|
SREC_AUTO_LOCK(m_sResLock);
|
|
if (!MS.m_pUpdateData)
|
|
{
|
|
buffer_handle_t nVB = MS.m_nID;
|
|
if (nVB == ~0u)
|
|
{
|
|
return false;
|
|
}
|
|
void* pSrc = MS.m_pLockedData;
|
|
if (!pSrc)
|
|
{
|
|
pSrc = gRenDev->m_DevBufMan.BeginRead(nVB);
|
|
MS.m_nLockFlags |= FSL_LOCKED;
|
|
}
|
|
assert(pSrc);
|
|
if (!pSrc)
|
|
{
|
|
return false;
|
|
}
|
|
++MS.m_nLockCount;
|
|
byte* pD = reinterpret_cast<byte*>(AllocateMeshData(nSize, MESH_DATA_DEFAULT_ALIGN, false));
|
|
if (pD)
|
|
{
|
|
cryMemcpy(pD, pSrc, nSize);
|
|
if (MS.m_nLockFlags & FSL_LOCKED)
|
|
{
|
|
if ((--MS.m_nLockCount) == 0)
|
|
{
|
|
MS.m_nLockFlags &= ~FSL_LOCKED;
|
|
MS.m_pLockedData = NULL;
|
|
gRenDev->m_DevBufMan.EndReadWrite(nVB);
|
|
}
|
|
}
|
|
MS.m_pUpdateData = pD;
|
|
m_nFlags |= FRM_READYTOUPLOAD;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
size_t CRenderMesh::SetMesh_Int(CMesh& mesh, [[maybe_unused]] int nSecColorsSetOffset, uint32 flags)
|
|
{
|
|
LOADING_TIME_PROFILE_SECTION;
|
|
char* pVBuff = NULL;
|
|
SPipTangents* pTBuff = NULL;
|
|
SPipQTangents* pQTBuff = NULL;
|
|
SVF_P3F* pVelocities = NULL;
|
|
SPipNormal* pNBuff = NULL;
|
|
uint32 nVerts = mesh.GetVertexCount();
|
|
uint32 nInds = mesh.GetIndexCount();
|
|
vtx_idx* pInds = NULL;
|
|
|
|
//AUTO_LOCK(m_sResLock);//need a resource lock as mesh could be reseted due to allocation failure
|
|
LockForThreadAccess();
|
|
|
|
ReleaseRenderChunks(&m_ChunksSkinned);
|
|
|
|
m_vBoxMin = mesh.m_bbox.min;
|
|
m_vBoxMax = mesh.m_bbox.max;
|
|
|
|
m_fGeometricMeanFaceArea = mesh.m_geometricMeanFaceArea;
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Initialize Render Chunks.
|
|
//////////////////////////////////////////////////////////////////////////
|
|
uint32 numSubsets = mesh.GetSubSetCount();
|
|
|
|
uint32 numChunks = 0;
|
|
for (uint32 i = 0; i < numSubsets; i++)
|
|
{
|
|
if (mesh.m_subsets[i].nNumIndices == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (mesh.m_subsets[i].nMatFlags & MTL_FLAG_NODRAW)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
++numChunks;
|
|
}
|
|
m_Chunks.reserve(numChunks);
|
|
|
|
// Determine the vertex format of each chunk based on the properties of the mesh
|
|
mesh.SetSubmeshVertexFormats();
|
|
|
|
for (uint32 i = 0; i < numSubsets; i++)
|
|
{
|
|
CRenderChunk ChunkInfo;
|
|
|
|
if (mesh.m_subsets[i].nNumIndices == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (mesh.m_subsets[i].nMatFlags & MTL_FLAG_NODRAW)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//add empty chunk, because PodArray is not working with STL-vectors
|
|
m_Chunks.push_back(ChunkInfo);
|
|
|
|
uint32 num = m_Chunks.size();
|
|
CRenderChunk* pChunk = &m_Chunks[num - 1];
|
|
|
|
pChunk->nFirstIndexId = mesh.m_subsets[i].nFirstIndexId;
|
|
pChunk->nNumIndices = mesh.m_subsets[i].nNumIndices;
|
|
pChunk->nFirstVertId = mesh.m_subsets[i].nFirstVertId;
|
|
pChunk->nNumVerts = mesh.m_subsets[i].nNumVerts;
|
|
pChunk->m_nMatID = mesh.m_subsets[i].nMatID;
|
|
pChunk->m_nMatFlags = mesh.m_subsets[i].nMatFlags;
|
|
pChunk->m_vertexFormat = mesh.m_subsets[i].vertexFormat;
|
|
if (mesh.m_subsets[i].nPhysicalizeType == PHYS_GEOM_TYPE_NONE)
|
|
{
|
|
pChunk->m_nMatFlags |= MTL_FLAG_NOPHYSICALIZE;
|
|
}
|
|
|
|
float texelAreaDensity = 1.0f;
|
|
|
|
if (!(flags & FSM_IGNORE_TEXELDENSITY))
|
|
{
|
|
float posArea;
|
|
float texArea;
|
|
const char* errorText = "";
|
|
|
|
if (mesh.m_subsets[i].fTexelDensity > 0.00001f)
|
|
{
|
|
texelAreaDensity = mesh.m_subsets[i].fTexelDensity;
|
|
}
|
|
else
|
|
{
|
|
const bool ok = mesh.ComputeSubsetTexMappingAreas(i, posArea, texArea, errorText);
|
|
if (ok)
|
|
{
|
|
texelAreaDensity = texArea / posArea;
|
|
}
|
|
else
|
|
{
|
|
// Commented out to prevent spam (contact Moritz Finck or Marco Corbetta for details)
|
|
// gEnv->pLog->LogError("Failed to compute texture mapping density for mesh '%s': ?%s", GetSourceName(), errorText);
|
|
}
|
|
}
|
|
}
|
|
|
|
pChunk->m_texelAreaDensity = texelAreaDensity;
|
|
|
|
#define VALIDATE_CHUCKS
|
|
#if defined(_DEBUG) && defined(VALIDATE_CHUCKS)
|
|
size_t indStart(pChunk->nFirstIndexId);
|
|
size_t indEnd(pChunk->nFirstIndexId + pChunk->nNumIndices);
|
|
for (size_t j(indStart); j < indEnd; ++j)
|
|
{
|
|
size_t vtxStart(pChunk->nFirstVertId);
|
|
size_t vtxEnd(pChunk->nFirstVertId + pChunk->nNumVerts);
|
|
size_t curIndex0(mesh.m_pIndices[ j ]); // absolute indexing
|
|
size_t curIndex1(mesh.m_pIndices[ j ] + vtxStart); // relative indexing using base vertex index
|
|
AZ_Assert((curIndex0 >= vtxStart && curIndex0 < vtxEnd) || (curIndex1 >= vtxStart && curIndex1 < vtxEnd), "Index is out of mesh vertices' range!");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Create RenderElements.
|
|
//////////////////////////////////////////////////////////////////////////
|
|
int nCurChunk = 0;
|
|
for (int i = 0; i < mesh.GetSubSetCount(); i++)
|
|
{
|
|
SMeshSubset& subset = mesh.m_subsets[i];
|
|
if (subset.nNumIndices == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (subset.nMatFlags & MTL_FLAG_NODRAW)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
CRenderChunk* pRenderChunk = &m_Chunks[nCurChunk++];
|
|
CREMeshImpl* pRenderElement = (CREMeshImpl*) gRenDev->EF_CreateRE(eDATA_Mesh);
|
|
|
|
// Cross link render chunk with render element.
|
|
pRenderChunk->pRE = pRenderElement;
|
|
AssignChunk(pRenderChunk, pRenderElement);
|
|
if (subset.nNumVerts <= 500 && !mesh.m_pBoneMapping && !(flags & FSM_NO_TANGENTS))
|
|
{
|
|
pRenderElement->mfUpdateFlags(FCEF_MERGABLE);
|
|
}
|
|
|
|
if (mesh.m_pBoneMapping)
|
|
{
|
|
pRenderElement->mfUpdateFlags(FCEF_SKINNED);
|
|
}
|
|
}
|
|
if (mesh.m_pBoneMapping)
|
|
{
|
|
m_nFlags |= FRM_SKINNED;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Create system vertex buffer in system memory.
|
|
//////////////////////////////////////////////////////////////////////////
|
|
#if ENABLE_NORMALSTREAM_SUPPORT
|
|
if (flags & FSM_ENABLE_NORMALSTREAM)
|
|
{
|
|
m_nFlags |= FRM_ENABLE_NORMALSTREAM;
|
|
}
|
|
#endif
|
|
|
|
m_nVerts = nVerts;
|
|
m_nInds = 0;
|
|
m_vertexFormat = mesh.GetMeshGroupVertexFormat();
|
|
|
|
pVBuff = (char*)LockVB(VSF_GENERAL, FSL_VIDEO_CREATE);
|
|
// stop initializing if allocation failed
|
|
if (pVBuff == NULL)
|
|
{
|
|
m_nVerts = 0;
|
|
goto error;
|
|
}
|
|
|
|
# if ENABLE_NORMALSTREAM_SUPPORT
|
|
if (m_nFlags & FRM_ENABLE_NORMALSTREAM)
|
|
{
|
|
pNBuff = (SPipNormal*)LockVB(VSF_NORMALS, FSL_VIDEO_CREATE);
|
|
}
|
|
# endif
|
|
|
|
if (!(flags & FSM_NO_TANGENTS))
|
|
{
|
|
if (mesh.m_pQTangents)
|
|
{
|
|
pQTBuff = (SPipQTangents*)LockVB(VSF_QTANGENTS, FSL_VIDEO_CREATE);
|
|
}
|
|
else
|
|
{
|
|
pTBuff = (SPipTangents*)LockVB(VSF_TANGENTS, FSL_VIDEO_CREATE);
|
|
}
|
|
|
|
// stop initializing if allocation failed
|
|
if (pTBuff == NULL && pQTBuff == NULL)
|
|
{
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Copy indices.
|
|
//////////////////////////////////////////////////////////////////////////
|
|
m_nInds = nInds;
|
|
pInds = LockIB(FSL_VIDEO_CREATE);
|
|
|
|
// stop initializing if allocation failed
|
|
if (m_nInds && pInds == NULL)
|
|
{
|
|
m_nInds = 0;
|
|
goto error;
|
|
}
|
|
|
|
if (flags & FSM_VERTEX_VELOCITY)
|
|
{
|
|
pVelocities = (SVF_P3F*)LockVB(VSF_VERTEX_VELOCITY, FSL_VIDEO_CREATE);
|
|
|
|
// stop initializing if allocation failed
|
|
if (pVelocities == NULL)
|
|
{
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
|
|
// Copy data to mesh
|
|
{
|
|
SSetMeshIntData setMeshIntData =
|
|
{
|
|
&mesh,
|
|
pVBuff,
|
|
pTBuff,
|
|
pQTBuff,
|
|
pVelocities,
|
|
nVerts,
|
|
nInds,
|
|
pInds,
|
|
flags,
|
|
pNBuff
|
|
};
|
|
SetMesh_IntImpl(setMeshIntData);
|
|
}
|
|
|
|
|
|
// unlock all streams
|
|
UnlockVB(VSF_GENERAL);
|
|
#if ENABLE_NORMALSTREAM_SUPPORT
|
|
if (m_nFlags & FRM_ENABLE_NORMALSTREAM)
|
|
{
|
|
UnlockVB(VSF_NORMALS);
|
|
}
|
|
# endif
|
|
UnlockIB();
|
|
|
|
if (!(flags & FSM_NO_TANGENTS))
|
|
{
|
|
if (mesh.m_pQTangents)
|
|
{
|
|
UnlockVB(VSF_QTANGENTS);
|
|
}
|
|
else
|
|
{
|
|
UnlockVB(VSF_TANGENTS);
|
|
}
|
|
}
|
|
|
|
if (flags & FSM_VERTEX_VELOCITY)
|
|
{
|
|
UnlockVB(VSF_VERTEX_VELOCITY);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Copy skin-streams.
|
|
//////////////////////////////////////////////////////////////////////////
|
|
if (mesh.m_pBoneMapping)
|
|
{
|
|
SetSkinningDataCharacter(mesh, mesh.m_pBoneMapping, mesh.m_pExtraBoneMapping);
|
|
}
|
|
|
|
// Create device buffers immediately in non-multithreaded mode
|
|
if (!gRenDev->m_pRT->IsMultithreaded() && (flags & FSM_CREATE_DEVICE_MESH))
|
|
{
|
|
CheckUpdate(VSM_MASK);
|
|
}
|
|
|
|
UnLockForThreadAccess();
|
|
return Size(SIZE_ONLY_SYSTEM);
|
|
|
|
error:
|
|
UnLockForThreadAccess();
|
|
RT_AllocationFailure("Generic Streaming Error", 0);
|
|
return ~0U;
|
|
}
|
|
|
|
size_t CRenderMesh::SetMesh(CMesh& mesh, int nSecColorsSetOffset, uint32 flags, bool requiresLock)
|
|
{
|
|
LOADING_TIME_PROFILE_SECTION;
|
|
|
|
size_t resultingSize = ~0U;
|
|
# ifdef USE_VBIB_PUSH_DOWN
|
|
requiresLock = true;
|
|
# endif
|
|
if (requiresLock)
|
|
{
|
|
SREC_AUTO_LOCK(m_sResLock);
|
|
resultingSize = SetMesh_Int(mesh, nSecColorsSetOffset, flags);
|
|
}
|
|
else
|
|
{
|
|
resultingSize = SetMesh_Int(mesh, nSecColorsSetOffset, flags);
|
|
}
|
|
|
|
return resultingSize;
|
|
}
|
|
|
|
void CRenderMesh::SetSkinningDataVegetation(struct SMeshBoneMapping_uint8* pBoneMapping)
|
|
{
|
|
LockForThreadAccess();
|
|
SVF_W4B_I4S* pSkinBuff = (SVF_W4B_I4S*)LockVB(VSF_HWSKIN_INFO, FSL_VIDEO_CREATE);
|
|
|
|
// stop initializing if allocation failed
|
|
if (pSkinBuff == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (uint32 i = 0; i < m_nVerts; i++)
|
|
{
|
|
// get bone IDs
|
|
uint16 b0 = pBoneMapping[i].boneIds[0];
|
|
uint16 b1 = pBoneMapping[i].boneIds[1];
|
|
uint16 b2 = pBoneMapping[i].boneIds[2];
|
|
uint16 b3 = pBoneMapping[i].boneIds[3];
|
|
|
|
// get weights
|
|
const uint8 w0 = pBoneMapping[i].weights[0];
|
|
const uint8 w1 = pBoneMapping[i].weights[1];
|
|
const uint8 w2 = pBoneMapping[i].weights[2];
|
|
const uint8 w3 = pBoneMapping[i].weights[3];
|
|
|
|
// if weight is zero set bone ID to zero as the bone has no influence anyway,
|
|
// this will fix some issue with incorrectly exported models (e.g. system freezes on ATI cards when access invalid bones)
|
|
if (w0 == 0)
|
|
{
|
|
b0 = 0;
|
|
}
|
|
if (w1 == 0)
|
|
{
|
|
b1 = 0;
|
|
}
|
|
if (w2 == 0)
|
|
{
|
|
b2 = 0;
|
|
}
|
|
if (w3 == 0)
|
|
{
|
|
b3 = 0;
|
|
}
|
|
|
|
pSkinBuff[i].indices[0] = b0;
|
|
pSkinBuff[i].indices[1] = b1;
|
|
pSkinBuff[i].indices[2] = b2;
|
|
pSkinBuff[i].indices[3] = b3;
|
|
|
|
pSkinBuff[i].weights.bcolor[0] = w0;
|
|
pSkinBuff[i].weights.bcolor[1] = w1;
|
|
pSkinBuff[i].weights.bcolor[2] = w2;
|
|
pSkinBuff[i].weights.bcolor[3] = w3;
|
|
|
|
// if (pBSStreamTemp)
|
|
// pSkinBuff[i].boneSpace = pBSStreamTemp[i];
|
|
}
|
|
UnlockVB(VSF_HWSKIN_INFO);
|
|
UnLockForThreadAccess();
|
|
|
|
CreateRemappedBoneIndicesPair(~0u, m_ChunksSkinned);
|
|
}
|
|
|
|
void CRenderMesh::SetSkinningDataCharacter([[maybe_unused]] CMesh& mesh, struct SMeshBoneMapping_uint16* pBoneMapping, [[maybe_unused]] struct SMeshBoneMapping_uint16* pExtraBoneMapping)
|
|
{
|
|
SVF_W4B_I4S* pSkinBuff = (SVF_W4B_I4S*)LockVB(VSF_HWSKIN_INFO, FSL_VIDEO_CREATE);
|
|
|
|
// stop initializing if allocation failed
|
|
if (pSkinBuff == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (uint32 i = 0; i < m_nVerts; i++)
|
|
{
|
|
// get bone IDs
|
|
uint16 b0 = pBoneMapping[i].boneIds[0];
|
|
uint16 b1 = pBoneMapping[i].boneIds[1];
|
|
uint16 b2 = pBoneMapping[i].boneIds[2];
|
|
uint16 b3 = pBoneMapping[i].boneIds[3];
|
|
|
|
// get weights
|
|
const uint8 w0 = pBoneMapping[i].weights[0];
|
|
const uint8 w1 = pBoneMapping[i].weights[1];
|
|
const uint8 w2 = pBoneMapping[i].weights[2];
|
|
const uint8 w3 = pBoneMapping[i].weights[3];
|
|
|
|
// if weight is zero set bone ID to zero as the bone has no influence anyway,
|
|
// this will fix some issue with incorrectly exported models (e.g. system freezes on ATI cards when access invalid bones)
|
|
if (w0 == 0)
|
|
{
|
|
b0 = 0;
|
|
}
|
|
if (w1 == 0)
|
|
{
|
|
b1 = 0;
|
|
}
|
|
if (w2 == 0)
|
|
{
|
|
b2 = 0;
|
|
}
|
|
if (w3 == 0)
|
|
{
|
|
b3 = 0;
|
|
}
|
|
|
|
pSkinBuff[i].indices[0] = b0;
|
|
pSkinBuff[i].indices[1] = b1;
|
|
pSkinBuff[i].indices[2] = b2;
|
|
pSkinBuff[i].indices[3] = b3;
|
|
|
|
pSkinBuff[i].weights.bcolor[0] = w0;
|
|
pSkinBuff[i].weights.bcolor[1] = w1;
|
|
pSkinBuff[i].weights.bcolor[2] = w2;
|
|
pSkinBuff[i].weights.bcolor[3] = w3;
|
|
}
|
|
|
|
UnlockVB(VSF_HWSKIN_INFO);
|
|
|
|
#if !defined(NULL_RENDERER)
|
|
if (pExtraBoneMapping && m_extraBonesBuffer.m_numElements == 0 && m_nVerts)
|
|
{
|
|
std::vector<SVF_W4B_I4S> pExtraBones;
|
|
pExtraBones.resize(m_nVerts);
|
|
for (uint32 i = 0; i < m_nVerts; i++)
|
|
{
|
|
// get bone IDs
|
|
uint16 b0 = pExtraBoneMapping[i].boneIds[0];
|
|
uint16 b1 = pExtraBoneMapping[i].boneIds[1];
|
|
uint16 b2 = pExtraBoneMapping[i].boneIds[2];
|
|
uint16 b3 = pExtraBoneMapping[i].boneIds[3];
|
|
|
|
// get weights
|
|
const uint8 w0 = pExtraBoneMapping[i].weights[0];
|
|
const uint8 w1 = pExtraBoneMapping[i].weights[1];
|
|
const uint8 w2 = pExtraBoneMapping[i].weights[2];
|
|
const uint8 w3 = pExtraBoneMapping[i].weights[3];
|
|
|
|
if (w0 == 0)
|
|
{
|
|
b0 = 0;
|
|
}
|
|
if (w1 == 0)
|
|
{
|
|
b1 = 0;
|
|
}
|
|
if (w2 == 0)
|
|
{
|
|
b2 = 0;
|
|
}
|
|
if (w3 == 0)
|
|
{
|
|
b3 = 0;
|
|
}
|
|
|
|
pExtraBones[i].indices[0] = b0;
|
|
pExtraBones[i].indices[1] = b1;
|
|
pExtraBones[i].indices[2] = b2;
|
|
pExtraBones[i].indices[3] = b3;
|
|
|
|
pExtraBones[i].weights.bcolor[0] = w0;
|
|
pExtraBones[i].weights.bcolor[1] = w1;
|
|
pExtraBones[i].weights.bcolor[2] = w2;
|
|
pExtraBones[i].weights.bcolor[3] = w3;
|
|
}
|
|
|
|
m_extraBonesBuffer.Create(pExtraBones.size(), sizeof(SVF_W4B_I4S), DXGI_FORMAT_UNKNOWN, DX11BUF_STRUCTURED | DX11BUF_BIND_SRV, &pExtraBones[0]);
|
|
}
|
|
#endif
|
|
|
|
CreateRemappedBoneIndicesPair(~0u, m_Chunks);
|
|
}
|
|
|
|
uint CRenderMesh::GetSkinningWeightCount() const
|
|
{
|
|
#if !defined(NULL_RENDERER)
|
|
if (_HasVBStream(VSF_HWSKIN_INFO))
|
|
{
|
|
return m_extraBonesBuffer.m_numElements > 0 ? 8 : 4;
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
IIndexedMesh* CRenderMesh::GetIndexedMesh(IIndexedMesh* pIdxMesh)
|
|
{
|
|
struct MeshDataLock
|
|
{
|
|
MeshDataLock(CRenderMesh* pMesh)
|
|
: _Mesh(pMesh) { _Mesh->LockForThreadAccess(); }
|
|
~MeshDataLock() { _Mesh->UnLockForThreadAccess(); }
|
|
CRenderMesh* _Mesh;
|
|
};
|
|
MeshDataLock _lock(this);
|
|
|
|
if (!pIdxMesh)
|
|
{
|
|
pIdxMesh = gEnv->p3DEngine->CreateIndexedMesh();
|
|
}
|
|
|
|
// catch failed allocation of IndexedMesh
|
|
if (pIdxMesh == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
CMesh* const pMesh = pIdxMesh->GetMesh();
|
|
|
|
uint32 numTexCoords = m_vertexFormat.GetAttributeUsageCount(AZ::Vertex::AttributeUsage::TexCoord);
|
|
|
|
// These functions also re-allocate the streams
|
|
pIdxMesh->SetVertexCount(m_nVerts);
|
|
pIdxMesh->SetTexCoordCount(m_nVerts, numTexCoords);
|
|
pIdxMesh->SetTangentCount(m_nVerts);
|
|
pIdxMesh->SetIndexCount(m_nInds);
|
|
pIdxMesh->SetSubSetCount(m_Chunks.size());
|
|
|
|
strided_pointer<Vec3> pVtx;
|
|
strided_pointer<SPipTangents> pTangs;
|
|
pVtx.data = (Vec3*)GetPosPtr(pVtx.iStride, FSL_READ);
|
|
|
|
bool texCoordAllocationSucceeded = true;
|
|
AZStd::vector<strided_pointer<Vec2>> stridedTexCoordPointers;
|
|
stridedTexCoordPointers.resize(numTexCoords);
|
|
for (int streamIndex = 0; streamIndex < stridedTexCoordPointers.size(); ++streamIndex)
|
|
{
|
|
stridedTexCoordPointers[streamIndex].data = (Vec2*)GetUVPtr(stridedTexCoordPointers[streamIndex].iStride, FSL_READ, streamIndex);
|
|
if (stridedTexCoordPointers[streamIndex].data == nullptr || !pMesh->GetStreamPtr<SMeshTexCoord>(CMesh::TEXCOORDS, streamIndex))
|
|
{
|
|
texCoordAllocationSucceeded = false;
|
|
break;
|
|
}
|
|
}
|
|
pTangs.data = (SPipTangents*)GetTangentPtr(pTangs.iStride, FSL_READ);
|
|
|
|
// don't copy if some src, or dest buffer is NULL (can happen because of failed allocations)
|
|
if (pVtx.data == NULL || (pMesh->m_pPositions == NULL && pMesh->m_pPositionsF16 == NULL) ||
|
|
!texCoordAllocationSucceeded ||
|
|
pTangs.data == NULL || pMesh->m_pTangents == NULL)
|
|
{
|
|
UnlockStream(VSF_GENERAL);
|
|
delete pIdxMesh;
|
|
return NULL;
|
|
}
|
|
|
|
|
|
for (uint32 i = 0; i < m_nVerts; i++)
|
|
{
|
|
pMesh->m_pPositions[i] = pVtx[i];
|
|
pMesh->m_pNorms [i] = SMeshNormal(Vec3(0, 0, 1));//pNorm[i];
|
|
pMesh->m_pTangents [i] = SMeshTangents(pTangs[i]);
|
|
}
|
|
|
|
for (int streamIndex = 0; streamIndex < stridedTexCoordPointers.size(); ++streamIndex)
|
|
{
|
|
SMeshTexCoord* meshTextureCoordinates = pMesh->GetStreamPtr<SMeshTexCoord>(CMesh::TEXCOORDS, streamIndex);
|
|
for (uint32 i = 0; i < m_nVerts; ++i)
|
|
{
|
|
meshTextureCoordinates[i] = SMeshTexCoord(stridedTexCoordPointers[streamIndex][i]);
|
|
}
|
|
}
|
|
|
|
uint offset = 0;
|
|
if (m_vertexFormat.TryCalculateOffset(offset, AZ::Vertex::AttributeUsage::Color))
|
|
{
|
|
strided_pointer<SMeshColor> pColors;
|
|
pColors.data = (SMeshColor*)GetColorPtr(pColors.iStride, FSL_READ);
|
|
pIdxMesh->SetColorCount(m_nVerts);
|
|
SMeshColor* colorStream = pMesh->GetStreamPtr<SMeshColor>(CMesh::COLORS);
|
|
for (int i = 0; i < (int)m_nVerts; i++)
|
|
{
|
|
colorStream[i] = pColors[i];
|
|
}
|
|
}
|
|
UnlockStream(VSF_GENERAL);
|
|
|
|
vtx_idx* pInds = GetIndexPtr(FSL_READ);
|
|
for (int i = 0; i < (int)m_nInds; i++)
|
|
{
|
|
pMesh->m_pIndices[i] = pInds[i];
|
|
}
|
|
UnlockIndexStream();
|
|
|
|
SVF_W4B_I4S* pSkinBuff = (SVF_W4B_I4S*)LockVB(VSF_HWSKIN_INFO, FSL_READ);
|
|
if (pSkinBuff)
|
|
{
|
|
pIdxMesh->AllocateBoneMapping();
|
|
for (int i = 0; i < (int)m_nVerts; i++)
|
|
{
|
|
for (int j = 0; j < 4; j++)
|
|
{
|
|
pMesh->m_pBoneMapping[i].boneIds[j] = pSkinBuff[i].indices[j];
|
|
pMesh->m_pBoneMapping[i].weights[j] = pSkinBuff[i].weights.bcolor[j];
|
|
}
|
|
}
|
|
UnlockVB(VSF_HWSKIN_INFO);
|
|
}
|
|
|
|
for (int i = 0; i < (int)m_Chunks.size(); i++)
|
|
{
|
|
pIdxMesh->SetSubsetIndexVertexRanges(i, m_Chunks[i].nFirstIndexId, m_Chunks[i].nNumIndices, m_Chunks[i].nFirstVertId, m_Chunks[i].nNumVerts);
|
|
pIdxMesh->SetSubsetMaterialId(i, m_Chunks[i].m_nMatID);
|
|
const int nMatFlags = m_Chunks[i].m_nMatFlags;
|
|
const int nPhysicalizeType = (nMatFlags & MTL_FLAG_NOPHYSICALIZE)
|
|
? PHYS_GEOM_TYPE_NONE
|
|
: ((nMatFlags & MTL_FLAG_NODRAW) ? PHYS_GEOM_TYPE_OBSTRUCT : PHYS_GEOM_TYPE_DEFAULT);
|
|
pIdxMesh->SetSubsetMaterialProperties(i, nMatFlags, nPhysicalizeType, m_Chunks[i].m_vertexFormat);
|
|
|
|
const SMeshSubset& mss = pIdxMesh->GetSubSet(i);
|
|
Vec3 vCenter;
|
|
vCenter.zero();
|
|
for (uint32 j = mss.nFirstIndexId; j < mss.nFirstIndexId + mss.nNumIndices; j++)
|
|
{
|
|
vCenter += pMesh->m_pPositions[pMesh->m_pIndices[j]];
|
|
}
|
|
if (mss.nNumIndices)
|
|
{
|
|
vCenter /= (float)mss.nNumIndices;
|
|
}
|
|
float fRadius = 0;
|
|
for (uint32 j = mss.nFirstIndexId; j < mss.nFirstIndexId + mss.nNumIndices; j++)
|
|
{
|
|
fRadius = max(fRadius, (pMesh->m_pPositions[pMesh->m_pIndices[j]] - vCenter).len2());
|
|
}
|
|
fRadius = sqrt_tpl(fRadius);
|
|
pIdxMesh->SetSubsetBounds(i, vCenter, fRadius);
|
|
}
|
|
|
|
return pIdxMesh;
|
|
}
|
|
|
|
void CRenderMesh::GenerateQTangents()
|
|
{
|
|
// FIXME: This needs to be cleaned up. Breakable foliage shouldn't need both streams, and this shouldn't be duplicated
|
|
// between here and CryAnimation.
|
|
LockForThreadAccess();
|
|
int srcStride = 0;
|
|
void* pSrcD = LockVB(VSF_TANGENTS, FSL_READ, 0, &srcStride);
|
|
if (pSrcD)
|
|
{
|
|
int dstStride = 0;
|
|
void* pDstD = LockVB(VSF_QTANGENTS, FSL_VIDEO_CREATE, 0, &dstStride);
|
|
assert(pDstD);
|
|
if (pDstD)
|
|
{
|
|
SPipTangents* pTangents = (SPipTangents*)pSrcD;
|
|
SPipQTangents* pQTangents = (SPipQTangents*)pDstD;
|
|
MeshTangentsFrameToQTangents(
|
|
pTangents, srcStride, m_nVerts,
|
|
pQTangents, dstStride);
|
|
}
|
|
UnlockVB(VSF_QTANGENTS);
|
|
}
|
|
UnlockVB(VSF_TANGENTS);
|
|
UnLockForThreadAccess();
|
|
}
|
|
|
|
void CRenderMesh::CreateChunksSkinned()
|
|
{
|
|
ReleaseRenderChunks(&m_ChunksSkinned);
|
|
|
|
TRenderChunkArray& arrSrcMats = m_Chunks;
|
|
TRenderChunkArray& arrNewMats = m_ChunksSkinned;
|
|
arrNewMats.resize (arrSrcMats.size());
|
|
for (int i = 0; i < arrSrcMats.size(); ++i)
|
|
{
|
|
CRenderChunk& rSrcMat = arrSrcMats[i];
|
|
CRenderChunk& rNewMat = arrNewMats[i];
|
|
rNewMat = rSrcMat;
|
|
CREMeshImpl* re = (CREMeshImpl*) rSrcMat.pRE;
|
|
if (re)
|
|
{
|
|
rNewMat.pRE = (CREMeshImpl*) gRenDev->EF_CreateRE(eDATA_Mesh);
|
|
CRendElement* pNext = rNewMat.pRE->m_NextGlobal;
|
|
CRendElement* pPrev = rNewMat.pRE->m_PrevGlobal;
|
|
*(CREMeshImpl*)rNewMat.pRE = *re;
|
|
if (rNewMat.pRE->m_pChunk) // affects the source mesh!! will only work correctly if the source is deleted after copying
|
|
{
|
|
rNewMat.pRE->m_pChunk = &rNewMat;
|
|
}
|
|
rNewMat.pRE->m_NextGlobal = pNext;
|
|
rNewMat.pRE->m_PrevGlobal = pPrev;
|
|
rNewMat.pRE->m_pRenderMesh = this;
|
|
rNewMat.pRE->m_CustomData = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
int CRenderMesh::GetRenderChunksCount(_smart_ptr<IMaterial> pMaterial, int& nRenderTrisCount)
|
|
{
|
|
int nCount = 0;
|
|
nRenderTrisCount = 0;
|
|
|
|
const uint32 ni = (uint32)m_Chunks.size();
|
|
for (uint32 i = 0; i < ni; i++)
|
|
{
|
|
CRenderChunk* pChunk = &m_Chunks[i];
|
|
CRendElementBase* pREMesh = pChunk->pRE;
|
|
|
|
SShaderItem* pShaderItem = &pMaterial->GetShaderItem(pChunk->m_nMatID);
|
|
|
|
CShaderResources* pR = (CShaderResources*)pShaderItem->m_pShaderResources;
|
|
CShader* pS = (CShader*)pShaderItem->m_pShader;
|
|
if (pREMesh && pS && pR)
|
|
{
|
|
if (pChunk->m_nMatFlags & MTL_FLAG_NODRAW)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (pS->m_Flags2 & EF2_NODRAW)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (pChunk->nNumIndices)
|
|
{
|
|
nRenderTrisCount += pChunk->nNumIndices / 3;
|
|
nCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nCount;
|
|
}
|
|
|
|
void CRenderMesh::CopyTo(IRenderMesh* _pDst, int nAppendVtx, [[maybe_unused]] bool bDynamic, bool fullCopy)
|
|
{
|
|
CRenderMesh* pDst = (CRenderMesh*)_pDst;
|
|
#ifdef USE_VBIB_PUSH_DOWN
|
|
SREC_AUTO_LOCK(m_sResLock);
|
|
#endif
|
|
TRenderChunkArray& arrSrcMats = m_Chunks;
|
|
TRenderChunkArray& arrNewMats = pDst->m_Chunks;
|
|
//pDst->m_bMaterialsWasCreatedInRenderer = true;
|
|
arrNewMats.resize (arrSrcMats.size());
|
|
int i;
|
|
for (i = 0; i < arrSrcMats.size(); ++i)
|
|
{
|
|
CRenderChunk& rSrcMat = arrSrcMats[i];
|
|
CRenderChunk& rNewMat = arrNewMats[i];
|
|
rNewMat = rSrcMat;
|
|
rNewMat.nNumVerts += ((m_nVerts - 2 - rNewMat.nNumVerts - rNewMat.nFirstVertId) >> 31) & nAppendVtx;
|
|
CREMeshImpl* re = (CREMeshImpl*) rSrcMat.pRE;
|
|
if (re)
|
|
{
|
|
AZ_Assert(!re->m_CustomData, "Trying to copy a render mesh after custom data has been set."); // Custom data cannot be copied over (used by Terrain and Decals)
|
|
rNewMat.pRE = (CREMeshImpl*) gRenDev->EF_CreateRE(eDATA_Mesh);
|
|
CRendElement* pNext = rNewMat.pRE->m_NextGlobal;
|
|
CRendElement* pPrev = rNewMat.pRE->m_PrevGlobal;
|
|
*(CREMeshImpl*)rNewMat.pRE = *re;
|
|
if (rNewMat.pRE->m_pChunk) // affects the source mesh!! will only work correctly if the source is deleted after copying
|
|
{
|
|
rNewMat.pRE->m_pChunk = &rNewMat;
|
|
rNewMat.pRE->m_pChunk->nNumVerts += ((m_nVerts - 2 - re->m_pChunk->nNumVerts - re->m_pChunk->nFirstVertId) >> 31) & nAppendVtx;
|
|
}
|
|
rNewMat.pRE->m_NextGlobal = pNext;
|
|
rNewMat.pRE->m_PrevGlobal = pPrev;
|
|
rNewMat.pRE->m_pRenderMesh = pDst;
|
|
rNewMat.pRE->m_CustomData = NULL;
|
|
}
|
|
}
|
|
LockForThreadAccess();
|
|
pDst->LockForThreadAccess();
|
|
pDst->m_nVerts = m_nVerts + nAppendVtx;
|
|
if (fullCopy)
|
|
{
|
|
pDst->m_vertexFormat = m_vertexFormat;
|
|
for (i = 0; i < VSF_NUM; i++)
|
|
{
|
|
void* pSrcD = LockVB(i, FSL_READ);
|
|
if (pSrcD)
|
|
{
|
|
void* pDstD = pDst->LockVB(i, FSL_VIDEO_CREATE);
|
|
assert(pDstD);
|
|
if (pDstD)
|
|
{
|
|
cryMemcpy(pDstD, pSrcD, GetStreamSize(i), MC_CPU_TO_GPU);
|
|
}
|
|
pDst->UnlockVB(i);
|
|
}
|
|
UnlockVB(i);
|
|
}
|
|
|
|
pDst->m_nInds = m_nInds;
|
|
void* pSrcD = LockIB(FSL_READ);
|
|
if (pSrcD)
|
|
{
|
|
void* pDstD = pDst->LockIB(FSL_VIDEO_CREATE);
|
|
assert(pDstD);
|
|
if (pDstD)
|
|
{
|
|
cryMemcpy(pDstD, pSrcD, m_nInds * sizeof(vtx_idx), MC_CPU_TO_GPU);
|
|
}
|
|
pDst->UnlockIB();
|
|
}
|
|
|
|
pDst->m_eType = m_eType;
|
|
pDst->m_nFlags = m_nFlags;
|
|
}
|
|
UnlockIB();
|
|
UnLockForThreadAccess();
|
|
pDst->UnLockForThreadAccess();
|
|
}
|
|
|
|
// set effector for all chunks
|
|
void CRenderMesh::SetCustomTexID(int nCustomTID)
|
|
{
|
|
if (m_Chunks.size())
|
|
{
|
|
for (int i = 0; i < m_Chunks.size(); i++)
|
|
{
|
|
CRenderChunk* pChunk = &m_Chunks[i];
|
|
if (pChunk->pRE)
|
|
{
|
|
pChunk->pRE->m_CustomTexBind[0] = nCustomTID;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CRenderMesh::SetChunk(int nIndex, CRenderChunk& inChunk)
|
|
{
|
|
if (!inChunk.nNumIndices || !inChunk.nNumVerts || m_nInds == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CRenderChunk* pRenderChunk = NULL;
|
|
|
|
if (nIndex < 0 || nIndex >= m_Chunks.size())
|
|
{
|
|
// add new chunk
|
|
CRenderChunk matinfo;
|
|
m_Chunks.push_back(matinfo);
|
|
pRenderChunk = &m_Chunks.back();
|
|
|
|
pRenderChunk->pRE = (CREMeshImpl*) gRenDev->EF_CreateRE(eDATA_Mesh);
|
|
pRenderChunk->pRE->m_CustomTexBind[0] = m_nClientTextureBindID;
|
|
}
|
|
else
|
|
{
|
|
// use present chunk
|
|
pRenderChunk = &m_Chunks[nIndex];
|
|
}
|
|
|
|
pRenderChunk->m_nMatID = inChunk.m_nMatID;
|
|
pRenderChunk->m_nMatFlags = inChunk.m_nMatFlags;
|
|
|
|
pRenderChunk->nFirstIndexId = inChunk.nFirstIndexId;
|
|
pRenderChunk->nNumIndices = MAX(inChunk.nNumIndices, 0);
|
|
pRenderChunk->nFirstVertId = inChunk.nFirstVertId;
|
|
pRenderChunk->nNumVerts = MAX(inChunk.nNumVerts, 0);
|
|
pRenderChunk->nSubObjectIndex = inChunk.nSubObjectIndex;
|
|
|
|
pRenderChunk->m_texelAreaDensity = inChunk.m_texelAreaDensity;
|
|
|
|
pRenderChunk->m_vertexFormat = inChunk.m_vertexFormat;
|
|
|
|
// update chunk RE
|
|
if (pRenderChunk->pRE)
|
|
{
|
|
AssignChunk(pRenderChunk, (CREMeshImpl*) pRenderChunk->pRE);
|
|
}
|
|
CRY_ASSERT(!pRenderChunk->pRE || pRenderChunk->pRE->m_pChunk->nFirstIndexId < 60000); // TODO: do we need to compare with 60000 if vertex indices are 32-bit?
|
|
CRY_ASSERT(pRenderChunk->nFirstIndexId + pRenderChunk->nNumIndices <= m_nInds);
|
|
}
|
|
|
|
void CRenderMesh::SetChunk(_smart_ptr<IMaterial> pNewMat, int nFirstVertId, int nVertCount, int nFirstIndexId, int nIndexCount, float texelAreaDensity, const AZ::Vertex::Format& vertexFormat, int nIndex)
|
|
{
|
|
CRenderChunk chunk;
|
|
|
|
if (pNewMat)
|
|
{
|
|
chunk.m_nMatFlags = pNewMat->GetFlags();
|
|
}
|
|
|
|
if (nIndex < 0 || nIndex >= m_Chunks.size())
|
|
{
|
|
chunk.m_nMatID = m_Chunks.size();
|
|
}
|
|
else
|
|
{
|
|
chunk.m_nMatID = nIndex;
|
|
}
|
|
|
|
chunk.nFirstVertId = nFirstVertId;
|
|
chunk.nNumVerts = nVertCount;
|
|
|
|
chunk.nFirstIndexId = nFirstIndexId;
|
|
chunk.nNumIndices = nIndexCount;
|
|
|
|
chunk.m_texelAreaDensity = texelAreaDensity;
|
|
chunk.m_vertexFormat = vertexFormat;
|
|
SetChunk(nIndex, chunk);
|
|
}
|
|
|
|
//================================================================================================================
|
|
|
|
bool CRenderMesh::PrepareCachePos()
|
|
{
|
|
if (!m_pCachePos && m_vertexFormat.Has16BitFloatPosition())
|
|
{
|
|
m_pCachePos = AllocateMeshData<Vec3>(m_nVerts);
|
|
if (m_pCachePos)
|
|
{
|
|
m_nFrameRequestCachePos = gRenDev->m_RP.m_TI[gRenDev->m_RP.m_nFillThreadID].m_nFrameUpdateID;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CRenderMesh::CreateCachePos(byte* pSrc, uint32 nStrideSrc, uint nFlags)
|
|
{
|
|
PROFILE_FRAME(Mesh_CreateCachePos);
|
|
if (m_vertexFormat.Has16BitFloatPosition())
|
|
{
|
|
#ifdef USE_VBIB_PUSH_DOWN
|
|
SREC_AUTO_LOCK(m_sResLock);//on USE_VBIB_PUSH_DOWN tick is executed in renderthread
|
|
#endif
|
|
m_nFlagsCachePos = (nFlags & FSL_WRITE) != 0;
|
|
m_nFrameRequestCachePos = gRenDev->m_RP.m_TI[gRenDev->m_RP.m_nFillThreadID].m_nFrameUpdateID;
|
|
if ((nFlags & FSL_READ) && m_pCachePos)
|
|
{
|
|
return true;
|
|
}
|
|
if ((nFlags == FSL_SYSTEM_CREATE) && m_pCachePos)
|
|
{
|
|
return true;
|
|
}
|
|
if (!m_pCachePos)
|
|
{
|
|
m_pCachePos = AllocateMeshData<Vec3>(m_nVerts);
|
|
}
|
|
if (m_pCachePos)
|
|
{
|
|
if (nFlags == FSL_SYSTEM_UPDATE || (nFlags & FSL_READ))
|
|
{
|
|
for (uint32 i = 0; i < m_nVerts; i++)
|
|
{
|
|
Vec3f16* pVSrc = (Vec3f16*)pSrc;
|
|
m_pCachePos[i] = pVSrc->ToVec3();
|
|
pSrc += nStrideSrc;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CRenderMesh::CreateUVCache(byte* source, uint32 sourceStride, uint flags, uint32 uvSetIndex)
|
|
{
|
|
PROFILE_FRAME(Mesh_CreateUVCache);
|
|
|
|
AZ::Vertex::AttributeType attributeType = AZ::Vertex::AttributeType::NumTypes;
|
|
uint offset = 0;
|
|
bool hasUVSetAtIndex = m_vertexFormat.TryGetAttributeOffsetAndType(AZ::Vertex::AttributeUsage::TexCoord, uvSetIndex, offset, attributeType);
|
|
|
|
// If the vertex format has a uv set at the given index
|
|
// And the vertex format uses 16 bit floats to represent that uv set
|
|
if (hasUVSetAtIndex && (attributeType == AZ::Vertex::AttributeType::Float16_2))
|
|
{
|
|
m_nFlagsCacheUVs = (flags & FSL_WRITE) != 0;
|
|
m_nFrameRequestCacheUVs = gRenDev->m_RP.m_TI[gRenDev->m_RP.m_nFillThreadID].m_nFrameUpdateID;
|
|
// Increase the size of the cached uvs vector if it is not big enough for the given uv set, and fill any new slots with nullptr
|
|
if (uvSetIndex >= m_UVCache.size())
|
|
{
|
|
m_UVCache.resize(uvSetIndex + 1, nullptr);
|
|
}
|
|
|
|
// Return true if the uv cache has already been created and does not need to be updated
|
|
if ((flags & FSL_READ) && m_UVCache[uvSetIndex])
|
|
{
|
|
return true;
|
|
}
|
|
if ((flags == FSL_SYSTEM_CREATE) && m_UVCache[uvSetIndex])
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// If the cache doesn't exist yet, allocate memory for it
|
|
if (!m_UVCache[uvSetIndex])
|
|
{
|
|
m_UVCache[uvSetIndex] = AllocateMeshData<Vec2>(m_nVerts);
|
|
}
|
|
|
|
if (m_UVCache[uvSetIndex])
|
|
{
|
|
// If the new or existing cache needs to be updated, fill it with the source data
|
|
if (flags == FSL_SYSTEM_UPDATE || (flags & FSL_READ))
|
|
{
|
|
source += offset;
|
|
|
|
for (uint32 i = 0; i < m_nVerts; i++)
|
|
{
|
|
Vec2f16* pVSrc = (Vec2f16*)source;
|
|
m_UVCache[uvSetIndex][i] = pVSrc->ToVec2();
|
|
source += sourceStride;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
byte* CRenderMesh::GetPosPtrNoCache(int32& nStride, uint32 nFlags)
|
|
{
|
|
int nStr = 0;
|
|
byte* pData = NULL;
|
|
pData = (byte*)LockVB(VSF_GENERAL, nFlags, 0, &nStr, true);
|
|
ASSERT_LOCK;
|
|
if (!pData)
|
|
{
|
|
return NULL;
|
|
}
|
|
nStride = nStr;
|
|
return pData;
|
|
}
|
|
|
|
byte* CRenderMesh::GetPosPtr(int32& nStride, uint32 nFlags)
|
|
{
|
|
PROFILE_FRAME(Mesh_GetPosPtr);
|
|
int nStr = 0;
|
|
byte* pData = NULL;
|
|
pData = (byte*)LockVB(VSF_GENERAL, nFlags, 0, &nStr, true, true);
|
|
ASSERT_LOCK;
|
|
if (!pData)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
if (!CreateCachePos(pData, nStr, nFlags))
|
|
{
|
|
nStride = nStr;
|
|
return pData;
|
|
}
|
|
|
|
pData = (byte*)m_pCachePos;
|
|
nStride = sizeof(Vec3);
|
|
return pData;
|
|
}
|
|
|
|
vtx_idx* CRenderMesh::GetIndexPtr(uint32 nFlags, int32 nOffset)
|
|
{
|
|
vtx_idx* pData = LockIB(nFlags, nOffset, 0);
|
|
assert((m_nInds == 0) || pData);
|
|
return pData;
|
|
}
|
|
|
|
byte* CRenderMesh::GetColorPtr(int32& nStride, uint32 nFlags)
|
|
{
|
|
PROFILE_FRAME(Mesh_GetColorPtr);
|
|
int nStr = 0;
|
|
byte* pData = (byte*)LockVB(VSF_GENERAL, nFlags, 0, &nStr);
|
|
ASSERT_LOCK;
|
|
if (!pData)
|
|
{
|
|
return NULL;
|
|
}
|
|
nStride = nStr;
|
|
uint colorOffset = 0;
|
|
if (_GetVertexFormat().TryCalculateOffset(colorOffset, AZ::Vertex::AttributeUsage::Color))
|
|
{
|
|
return &pData[colorOffset];
|
|
}
|
|
return NULL;
|
|
}
|
|
byte* CRenderMesh::GetNormPtr(int32& nStride, uint32 nFlags)
|
|
{
|
|
PROFILE_FRAME(Mesh_GetNormPtr);
|
|
int nStr = 0;
|
|
byte* pData = 0;
|
|
# if ENABLE_NORMALSTREAM_SUPPORT
|
|
pData = (byte*)LockVB(VSF_NORMALS, nFlags, 0, &nStr);
|
|
if (pData)
|
|
{
|
|
nStride = sizeof(Vec3);
|
|
return pData;
|
|
}
|
|
# endif
|
|
pData = (byte*)LockVB(VSF_GENERAL, nFlags, 0, &nStr);
|
|
ASSERT_LOCK;
|
|
if (!pData)
|
|
{
|
|
return NULL;
|
|
}
|
|
nStride = nStr;
|
|
uint normalOffset = 0;
|
|
if (_GetVertexFormat().TryCalculateOffset(normalOffset, AZ::Vertex::AttributeUsage::Normal))
|
|
{
|
|
return &pData[normalOffset];
|
|
}
|
|
return NULL;
|
|
}
|
|
byte* CRenderMesh::GetUVPtrNoCache(int32& nStride, uint32 nFlags, uint32 uvSetIndex)
|
|
{
|
|
PROFILE_FRAME(Mesh_GetUVPtrNoCache);
|
|
int nStr = 0;
|
|
byte* pData = (byte*)LockVB(VSF_GENERAL, nFlags, 0, &nStr);
|
|
ASSERT_LOCK;
|
|
if (!pData)
|
|
{
|
|
return NULL;
|
|
}
|
|
nStride = nStr;
|
|
uint texCoordOffset = 0;
|
|
if (_GetVertexFormat().TryCalculateOffset(texCoordOffset, AZ::Vertex::AttributeUsage::TexCoord, uvSetIndex))
|
|
{
|
|
return &pData[texCoordOffset];
|
|
}
|
|
return NULL;
|
|
}
|
|
byte* CRenderMesh::GetUVPtr(int32& nStride, uint32 nFlags, uint32 uvSetIndex)
|
|
{
|
|
PROFILE_FRAME(Mesh_GetUVPtr);
|
|
int nStr = 0;
|
|
byte* pData = (byte*)LockVB(VSF_GENERAL, nFlags, 0, &nStr);
|
|
ASSERT_LOCK;
|
|
if (!pData)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
bool result;
|
|
{
|
|
SREC_AUTO_LOCK(m_sResLock);
|
|
result = CreateUVCache(pData, nStr, nFlags, uvSetIndex);
|
|
}
|
|
|
|
if (!result)
|
|
{
|
|
nStride = nStr;
|
|
uint texCoordOffset = 0;
|
|
if (_GetVertexFormat().TryCalculateOffset(texCoordOffset, AZ::Vertex::AttributeUsage::TexCoord, uvSetIndex))
|
|
{
|
|
return &pData[texCoordOffset];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pData = (byte*)m_UVCache[uvSetIndex];
|
|
nStride = sizeof(Vec2);
|
|
return pData;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
byte* CRenderMesh::GetTangentPtr(int32& nStride, uint32 nFlags)
|
|
{
|
|
PROFILE_FRAME(Mesh_GetTangentPtr);
|
|
int nStr = 0;
|
|
byte* pData = (byte*)LockVB(VSF_TANGENTS, nFlags, 0, &nStr);
|
|
//ASSERT_LOCK;
|
|
if (!pData)
|
|
{
|
|
pData = (byte*)LockVB(VSF_QTANGENTS, nFlags, 0, &nStr);
|
|
}
|
|
if (!pData)
|
|
{
|
|
return NULL;
|
|
}
|
|
nStride = nStr;
|
|
return pData;
|
|
}
|
|
byte* CRenderMesh::GetQTangentPtr(int32& nStride, uint32 nFlags)
|
|
{
|
|
PROFILE_FRAME(Mesh_GetQTangentPtr);
|
|
int nStr = 0;
|
|
byte* pData = (byte*)LockVB(VSF_QTANGENTS, nFlags, 0, &nStr);
|
|
//ASSERT_LOCK;
|
|
if (!pData)
|
|
{
|
|
return NULL;
|
|
}
|
|
nStride = nStr;
|
|
return pData;
|
|
}
|
|
|
|
byte* CRenderMesh::GetHWSkinPtr(int32& nStride, uint32 nFlags, [[maybe_unused]] bool remapped)
|
|
{
|
|
PROFILE_FRAME(Mesh_GetHWSkinPtr);
|
|
int nStr = 0;
|
|
byte* pData = (byte*)LockVB(VSF_HWSKIN_INFO, nFlags, 0, &nStr);
|
|
if (!pData)
|
|
{
|
|
return NULL;
|
|
}
|
|
nStride = nStr;
|
|
return pData;
|
|
}
|
|
|
|
byte* CRenderMesh::GetVelocityPtr(int32& nStride, uint32 nFlags)
|
|
{
|
|
PROFILE_FRAME(Mesh_GetMorphTargetPtr);
|
|
int nStr = 0;
|
|
byte* pData = (byte*)LockVB(VSF_VERTEX_VELOCITY, nFlags, 0, &nStr);
|
|
ASSERT_LOCK;
|
|
if (!pData)
|
|
{
|
|
return NULL;
|
|
}
|
|
nStride = nStr;
|
|
return pData;
|
|
}
|
|
|
|
bool CRenderMesh::IsEmpty()
|
|
{
|
|
ASSERT_IS_MAIN_THREAD(gRenDev->m_pRT)
|
|
SMeshStream * pMS = GetVertexStream(VSF_GENERAL, 0);
|
|
return (!m_nVerts || (!pMS || pMS->m_nID == ~0u || !pMS->m_pUpdateData) || (!_HasIBStream() && !m_IBStream.m_pUpdateData));
|
|
}
|
|
|
|
//================================================================================================================
|
|
|
|
bool CRenderMesh::CheckUpdate(uint32 nStreamMask)
|
|
{
|
|
CRenderMesh* pRM = _GetVertexContainer();
|
|
if (pRM)
|
|
{
|
|
return gRenDev->m_pRT->RC_CheckUpdate2(this, pRM, nStreamMask);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void CRenderMesh::RT_AllocationFailure([[maybe_unused]] const char* sPurpose, [[maybe_unused]] uint32 nSize)
|
|
{
|
|
SREC_AUTO_LOCK(m_sResLock);
|
|
Cleanup();
|
|
m_nVerts = 0;
|
|
m_nInds = 0;
|
|
m_nFlags |= FRM_ALLOCFAILURE;
|
|
# if !defined(_RELEASE) && !defined(NULL_RENDERER)
|
|
CryLogAlways("rendermesh '%s(%s)' suffered from a buffer allocation failure for \"%s\" size %d bytes on thread 0x%" PRI_THREADID, m_sSource.c_str(), m_sType.c_str(), sPurpose, nSize, CryGetCurrentThreadId());
|
|
# endif
|
|
}
|
|
|
|
|
|
#ifdef MESH_TESSELLATION_RENDERER
|
|
namespace
|
|
{
|
|
template<class VecPos>
|
|
class SAdjVertCompare
|
|
{
|
|
public:
|
|
SAdjVertCompare(const AZ::Vertex::Format& vertexFormat)
|
|
: m_vertexFormat(vertexFormat)
|
|
{}
|
|
inline bool operator()(const int i1, const int i2) const
|
|
{
|
|
uint stride = m_vertexFormat.GetStride();
|
|
uint offset = 0;
|
|
m_vertexFormat.TryCalculateOffset(offset, AZ::Vertex::AttributeUsage::Position);
|
|
const VecPos* pVert1 = (const VecPos*)&m_pVerts[i1 * stride + offset];
|
|
const VecPos* pVert2 = (const VecPos*)&m_pVerts[i2 * stride + offset];
|
|
if (pVert1->x < pVert2->x)
|
|
{
|
|
return true;
|
|
}
|
|
if (pVert1->x > pVert2->x)
|
|
{
|
|
return false;
|
|
}
|
|
if (pVert1->y < pVert2->y)
|
|
{
|
|
return true;
|
|
}
|
|
if (pVert1->y > pVert2->y)
|
|
{
|
|
return false;
|
|
}
|
|
return pVert1->z < pVert2->z;
|
|
}
|
|
const byte* m_pVerts;
|
|
AZ::Vertex::Format m_vertexFormat;
|
|
};
|
|
}
|
|
|
|
template<class VecPos, class VecUV>
|
|
void CRenderMesh::BuildAdjacency(const byte* pVerts, const AZ::Vertex::Format& vertexFormat, unsigned int nVerts, const vtx_idx* pIndexBuffer, uint nTrgs, std::vector<VecUV>& pTxtAdjBuffer)
|
|
{
|
|
SAdjVertCompare<VecPos> compare(vertexFormat);
|
|
compare.m_pVerts = pVerts;
|
|
|
|
// this array will contain indices of vertices sorted by float3 position - so that we could find vertices with equal positions (they have to be adjacent in the array)
|
|
std::vector<int> arrSortedVertIDs;
|
|
// we allocate a bit more (by one) because we will need extra element for scan operation (lower)
|
|
arrSortedVertIDs.resize(nVerts + 1);
|
|
for (int iv = 0; iv < nVerts; ++iv)
|
|
{
|
|
arrSortedVertIDs[iv] = iv;
|
|
}
|
|
|
|
std::sort(arrSortedVertIDs.begin(), arrSortedVertIDs.end() - 1, compare);
|
|
|
|
// Get vertex stride, offset, and bytelength for position and texture coordinates
|
|
uint stride = vertexFormat.GetStride();
|
|
uint positionOffset = 0;
|
|
vertexFormat.TryCalculateOffset(positionOffset, AZ::Vertex::AttributeUsage::Position);
|
|
uint texCoordOffset = 0;
|
|
vertexFormat.TryCalculateOffset(texCoordOffset, AZ::Vertex::AttributeUsage::TexCoord);
|
|
uint texCoordByteLength = vertexFormat.GetAttributeByteLength(AZ::Vertex::AttributeUsage::TexCoord);
|
|
|
|
// compute how many unique vertices are there, also setup links from each vertex to master vertex
|
|
std::vector<int> arrLinkToMaster;
|
|
arrLinkToMaster.resize(nVerts);
|
|
int nUniqueVertices = 0;
|
|
for (int iv0 = 0; iv0 < nVerts; )
|
|
{
|
|
int iMaster = arrSortedVertIDs[iv0];
|
|
arrLinkToMaster[iMaster] = iMaster;
|
|
int iv1 = iv0 + 1;
|
|
for (; iv1 < nVerts; ++iv1)
|
|
{
|
|
// if slave vertex != master vertex
|
|
if (*(VecPos*)&pVerts[arrSortedVertIDs[iv1] * stride + positionOffset] != *(VecPos*)&pVerts[iMaster * stride + positionOffset])
|
|
{
|
|
break;
|
|
}
|
|
arrLinkToMaster[arrSortedVertIDs[iv1]] = iMaster;
|
|
}
|
|
iv0 = iv1;
|
|
++nUniqueVertices;
|
|
}
|
|
if (nUniqueVertices == nVerts)
|
|
{ // no need to recode anything - the mesh is perfect
|
|
return;
|
|
}
|
|
// compute how many triangles connect to every master vertex
|
|
std::vector<int>& arrConnectedTrianglesCount = arrSortedVertIDs;
|
|
for (int i = 0; i < arrConnectedTrianglesCount.size(); ++i)
|
|
{
|
|
arrConnectedTrianglesCount[i] = 0;
|
|
}
|
|
for (int it = 0; it < nTrgs; ++it)
|
|
{
|
|
const vtx_idx* pTrg = &pIndexBuffer[it * 3];
|
|
int iMasterVertex0 = arrLinkToMaster[pTrg[0]];
|
|
int iMasterVertex1 = arrLinkToMaster[pTrg[1]];
|
|
int iMasterVertex2 = arrLinkToMaster[pTrg[2]];
|
|
if (iMasterVertex0 == iMasterVertex1 || iMasterVertex0 == iMasterVertex2 || iMasterVertex1 == iMasterVertex2)
|
|
{
|
|
continue; // degenerate triangle - skip it
|
|
}
|
|
++arrConnectedTrianglesCount[iMasterVertex0];
|
|
++arrConnectedTrianglesCount[iMasterVertex1];
|
|
++arrConnectedTrianglesCount[iMasterVertex2];
|
|
}
|
|
// scan
|
|
std::vector<int>& arrFirstConnectedTriangle = arrSortedVertIDs;
|
|
for (int iv = 0; iv < nVerts; ++iv)
|
|
{
|
|
arrFirstConnectedTriangle[iv + 1] += arrFirstConnectedTriangle[iv];
|
|
}
|
|
{
|
|
int iTmp = arrFirstConnectedTriangle[0];
|
|
arrFirstConnectedTriangle[0] = 0;
|
|
for (int iv = 0; iv < nVerts; ++iv)
|
|
{
|
|
int iTmp1 = arrFirstConnectedTriangle[iv + 1];
|
|
arrFirstConnectedTriangle[iv + 1] = iTmp;
|
|
iTmp = iTmp1;
|
|
}
|
|
}
|
|
// create a list of triangles for each master vertex
|
|
std::vector<int> arrConnectedTriangles;
|
|
arrConnectedTriangles.resize(arrFirstConnectedTriangle[nVerts]);
|
|
for (int it = 0; it < nTrgs; ++it)
|
|
{
|
|
const vtx_idx* pTrg = &pIndexBuffer[it * 3];
|
|
int iMasterVertex0 = arrLinkToMaster[pTrg[0]];
|
|
int iMasterVertex1 = arrLinkToMaster[pTrg[1]];
|
|
int iMasterVertex2 = arrLinkToMaster[pTrg[2]];
|
|
if (iMasterVertex0 == iMasterVertex1 || iMasterVertex0 == iMasterVertex2 || iMasterVertex1 == iMasterVertex2)
|
|
{
|
|
continue; // degenerate triangle - skip it
|
|
}
|
|
arrConnectedTriangles[arrFirstConnectedTriangle[iMasterVertex0]++] = it;
|
|
arrConnectedTriangles[arrFirstConnectedTriangle[iMasterVertex1]++] = it;
|
|
arrConnectedTriangles[arrFirstConnectedTriangle[iMasterVertex2]++] = it;
|
|
}
|
|
// return scan array to initial state
|
|
{
|
|
int iTmp = arrFirstConnectedTriangle[0];
|
|
arrFirstConnectedTriangle[0] = 0;
|
|
for (int iv = 0; iv < nVerts; ++iv)
|
|
{
|
|
int iTmp1 = arrFirstConnectedTriangle[iv + 1];
|
|
arrFirstConnectedTriangle[iv + 1] = iTmp;
|
|
iTmp = iTmp1;
|
|
}
|
|
}
|
|
// find matches for boundary edges
|
|
for (int it = 0; it < nTrgs; ++it)
|
|
{
|
|
const vtx_idx* pTrg = &pIndexBuffer[it * 3];
|
|
for (int ie = 0; ie < 3; ++ie)
|
|
{
|
|
// fix the corner here
|
|
{
|
|
int ivCorner = pTrg[ie];
|
|
memcpy(&pTxtAdjBuffer[it * 12 + 9 + ie], &pVerts[arrLinkToMaster[ivCorner] * stride + texCoordOffset], texCoordByteLength);
|
|
}
|
|
// proceed with fixing the edges
|
|
int iv0 = pTrg[ie];
|
|
int iMasterVertex0 = arrLinkToMaster[iv0];
|
|
|
|
int iv1 = pTrg[(ie + 1) % 3];
|
|
int iMasterVertex1 = arrLinkToMaster[iv1];
|
|
if (iMasterVertex0 == iMasterVertex1)
|
|
{ // some degenerate case - skip it
|
|
continue;
|
|
}
|
|
// find a triangle that has both iMasterVertex0 and iMasterVertex1
|
|
for (int i0 = arrFirstConnectedTriangle[iMasterVertex0]; i0 < arrFirstConnectedTriangle[iMasterVertex0 + 1]; ++i0)
|
|
{
|
|
int iOtherTriangle = arrConnectedTriangles[i0];
|
|
if (iOtherTriangle >= it) // we are going to stick to this other triangle only if it's index is less than ours
|
|
{
|
|
continue;
|
|
}
|
|
const vtx_idx* pOtherTrg = &pIndexBuffer[iOtherTriangle * 3];
|
|
int iRecode0 = -1;
|
|
int iRecode1 = -1;
|
|
// setup recode indices
|
|
for (int ieOther = 0; ieOther < 3; ++ieOther)
|
|
{
|
|
if (arrLinkToMaster[pOtherTrg[ieOther]] == iMasterVertex0)
|
|
{
|
|
iRecode0 = pOtherTrg[ieOther];
|
|
}
|
|
else if (arrLinkToMaster[pOtherTrg[ieOther]] == iMasterVertex1)
|
|
{
|
|
iRecode1 = pOtherTrg[ieOther];
|
|
}
|
|
}
|
|
if (iRecode0 != -1 && iRecode1 != -1)
|
|
{ // this triangle is our neighbor
|
|
memcpy(&pTxtAdjBuffer[it * 12 + 3 + ie * 2], &pVerts[iRecode0 * stride + texCoordOffset], texCoordByteLength);
|
|
memcpy(&pTxtAdjBuffer[it * 12 + 3 + ie * 2 + 1], &pVerts[iRecode1 * stride + texCoordOffset], texCoordByteLength);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif //#ifdef MESH_TESSELLATION_RENDERER
|
|
|
|
bool CRenderMesh::RT_CheckUpdate(CRenderMesh* pVContainer, uint32 nStreamMask, [[maybe_unused]] bool bTessellation, bool stall)
|
|
{
|
|
PrefetchLine(&m_IBStream, 0);
|
|
|
|
CRenderer* rd = gRenDev;
|
|
int nThreadID = rd->m_RP.m_nProcessThreadID;
|
|
int nFrame = rd->m_RP.m_TI[nThreadID].m_nFrameUpdateID;
|
|
bool bSkinned = (m_nFlags & (FRM_SKINNED | FRM_SKINNEDNEXTDRAW)) != 0;
|
|
|
|
if (nStreamMask & 0x80000000) // Disable skinning in instancing mode
|
|
{
|
|
bSkinned = false;
|
|
}
|
|
|
|
m_nFlags &= ~FRM_SKINNEDNEXTDRAW;
|
|
|
|
IF (!CanRender(), 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FUNCTION_PROFILER_RENDER_FLAT
|
|
AZ_TRACE_METHOD();
|
|
PrefetchVertexStreams();
|
|
|
|
PrefetchLine(pVContainer->m_VBStream, 0);
|
|
SMeshStream* pMS = pVContainer->GetVertexStream(VSF_GENERAL, 0);
|
|
|
|
if ((m_pVertexContainer || m_nVerts > 2) && pMS)
|
|
{
|
|
PrefetchLine(pVContainer->m_VBStream, 128);
|
|
if (pMS->m_pUpdateData && pMS->m_nFrameAccess != nFrame)
|
|
{
|
|
pMS->m_nFrameAccess = nFrame;
|
|
if (pMS->m_nFrameRequest > pMS->m_nFrameUpdate)
|
|
{
|
|
{
|
|
PROFILE_FRAME(Mesh_CheckUpdateUpdateGBuf);
|
|
if (!(pMS->m_nLockFlags & FSL_WRITE))
|
|
{
|
|
if (!pVContainer->UpdateVidVertices(VSF_GENERAL, stall))
|
|
{
|
|
RT_AllocationFailure("Update General Stream", GetStreamSize(VSF_GENERAL, m_nVerts));
|
|
return false;
|
|
}
|
|
pMS->m_nFrameUpdate = nFrame;
|
|
}
|
|
else
|
|
{
|
|
if (pMS->m_nID == ~0u)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Set both tangent flags
|
|
if (nStreamMask & VSM_TANGENTS)
|
|
{
|
|
nStreamMask |= VSM_TANGENTS;
|
|
}
|
|
|
|
// Additional streams updating
|
|
if (nStreamMask & VSM_MASK)
|
|
{
|
|
int i;
|
|
uint32 iMask = 1;
|
|
|
|
for (i = 1; i < VSF_NUM; i++)
|
|
{
|
|
iMask = iMask << 1;
|
|
|
|
pMS = pVContainer->GetVertexStream(i, 0);
|
|
if ((nStreamMask & iMask) && pMS)
|
|
{
|
|
if (pMS->m_pUpdateData && pMS->m_nFrameAccess != nFrame)
|
|
{
|
|
pMS->m_nFrameAccess = nFrame;
|
|
if (pMS->m_nFrameRequest > pMS->m_nFrameUpdate)
|
|
{
|
|
// Update the device buffer
|
|
PROFILE_FRAME(Mesh_CheckUpdateUpdateGBuf);
|
|
if (!(pMS->m_nLockFlags & FSL_WRITE))
|
|
{
|
|
if (!pVContainer->UpdateVidVertices(i, stall))
|
|
{
|
|
RT_AllocationFailure("Update VB Stream", GetStreamSize(i, m_nVerts));
|
|
return false;
|
|
}
|
|
if (i == VSF_HWSKIN_INFO && pVContainer->m_RemappedBoneIndices.size() == 1)
|
|
{
|
|
// Just locking the VB stream doesn't store any information about the GUID so
|
|
// if the HWSKIN_INFO has been locked and we only have one set of remapped bone
|
|
// indices we will assume they are the same set. This is a valid assumption in non
|
|
// legacy code, as we will only have the one set loaded from the asset.
|
|
if (!gRenDev->m_DevBufMan.UpdateBuffer(pVContainer->m_RemappedBoneIndices[0].buffer,
|
|
pMS->m_pUpdateData,
|
|
pVContainer->GetVerticesCount() * pVContainer->GetStreamStride(VSF_HWSKIN_INFO)))
|
|
{
|
|
RT_AllocationFailure("Update VB Stream", GetStreamSize(i, m_nVerts));
|
|
return false;
|
|
}
|
|
}
|
|
pMS->m_nFrameUpdate = nFrame;
|
|
}
|
|
else
|
|
if (i != VSF_HWSKIN_INFO)
|
|
{
|
|
if (pMS->m_nID == ~0u)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}//if (m_pVertexContainer || m_nVerts > 2)
|
|
|
|
m_IBStream.m_nFrameAccess = nFrame;
|
|
const bool bIndUpdateNeeded = (m_IBStream.m_pUpdateData != NULL) && (m_IBStream.m_nFrameRequest > m_IBStream.m_nFrameUpdate);
|
|
if (bIndUpdateNeeded)
|
|
{
|
|
PROFILE_FRAME(Mesh_CheckUpdate_UpdateInds);
|
|
if (!(pVContainer->m_IBStream.m_nLockFlags & FSL_WRITE))
|
|
{
|
|
if (!UpdateVidIndices(m_IBStream, stall))
|
|
{
|
|
RT_AllocationFailure("Update IB Stream", m_nInds * sizeof(vtx_idx));
|
|
return false;
|
|
}
|
|
m_IBStream.m_nFrameUpdate = nFrame;
|
|
}
|
|
else if (pVContainer->m_IBStream.m_nID == ~0u)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
#ifdef MESH_TESSELLATION_RENDERER
|
|
// Build UV adjacency
|
|
if ((bTessellation && m_adjBuffer.m_numElements == 0) // if needed and not built already
|
|
|| (bIndUpdateNeeded && m_adjBuffer.m_numElements > 0)) // if already built but needs update
|
|
{
|
|
if (!(pVContainer->m_IBStream.m_nLockFlags & FSL_WRITE)
|
|
&& (pVContainer->_HasVBStream(VSF_NORMALS)))
|
|
{
|
|
if (m_vertexFormat.Has16BitFloatTextureCoordinates())
|
|
{
|
|
UpdateUVCoordsAdjacency<Vec3f16, Vec2f16>(m_IBStream, m_vertexFormat);
|
|
}
|
|
else if (m_vertexFormat.Has32BitFloatTextureCoordinates())
|
|
{
|
|
UpdateUVCoordsAdjacency<Vec3, Vec2>(m_IBStream, m_vertexFormat);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
const int threadId = gRenDev->m_RP.m_nProcessThreadID;
|
|
for (size_t i = 0, end0 = pVContainer->m_DeletedBoneIndices[threadId].size(); i < end0; ++i)
|
|
{
|
|
for (size_t j = 0, end1 = pVContainer->m_RemappedBoneIndices.size(); j < end1; ++j)
|
|
{
|
|
if (pVContainer->m_RemappedBoneIndices[j].guid == pVContainer->m_DeletedBoneIndices[threadId][i])
|
|
{
|
|
SBoneIndexStream& stream = pVContainer->m_RemappedBoneIndices[j];
|
|
if (stream.buffer != ~0u)
|
|
{
|
|
// Unregister the allocation with the VRAM driller
|
|
void* address = reinterpret_cast<void*>(stream.buffer);
|
|
EBUS_EVENT(Render::Debug::VRAMDrillerBus, UnregisterAllocation, address);
|
|
|
|
gRenDev->m_DevBufMan.Destroy(stream.buffer);
|
|
}
|
|
pVContainer->m_RemappedBoneIndices.erase(pVContainer->m_RemappedBoneIndices.begin() + j);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
pVContainer->m_DeletedBoneIndices[threadId].clear();
|
|
for (size_t i = 0, end = pVContainer->m_CreatedBoneIndices[threadId].size(); i < end; ++i)
|
|
{
|
|
bool bFound = false;
|
|
SBoneIndexStreamRequest& rBoneIndexStreamRequest = pVContainer->m_CreatedBoneIndices[threadId][i];
|
|
const uint32 guid = rBoneIndexStreamRequest.guid;
|
|
for (size_t j = 0, numIndices = m_RemappedBoneIndices.size(); j < numIndices; ++j)
|
|
{
|
|
if (m_RemappedBoneIndices[j].guid == guid && m_RemappedBoneIndices[j].refcount)
|
|
{
|
|
bFound = true;
|
|
++m_RemappedBoneIndices[j].refcount;
|
|
}
|
|
}
|
|
|
|
if (bFound == false)
|
|
{
|
|
size_t bufferSize = pVContainer->GetVerticesCount() * pVContainer->GetStreamStride(VSF_HWSKIN_INFO);
|
|
SBoneIndexStream stream;
|
|
stream.buffer = gRenDev->m_DevBufMan.Create(BBT_VERTEX_BUFFER, BU_STATIC, bufferSize);
|
|
stream.guid = guid;
|
|
stream.refcount = rBoneIndexStreamRequest.refcount;
|
|
gRenDev->m_DevBufMan.UpdateBuffer(stream.buffer, rBoneIndexStreamRequest.pStream, pVContainer->GetVerticesCount() * pVContainer->GetStreamStride(VSF_HWSKIN_INFO));
|
|
pVContainer->m_RemappedBoneIndices.push_back(stream);
|
|
|
|
// Register the allocation with the VRAM driller
|
|
void* address = reinterpret_cast<void*>(stream.buffer);
|
|
const char* bufferName = GetSourceName();
|
|
EBUS_EVENT(Render::Debug::VRAMDrillerBus, RegisterAllocation, address, bufferSize, bufferName, Render::Debug::VRAM_CATEGORY_BUFFER, Render::Debug::VRAM_SUBCATEGORY_BUFFER_VERTEX_BUFFER);
|
|
}
|
|
|
|
delete[] rBoneIndexStreamRequest.pStream;
|
|
}
|
|
pVContainer->m_CreatedBoneIndices[threadId].clear();
|
|
|
|
return true;
|
|
}
|
|
|
|
void CRenderMesh::ReleaseVB(int nStream)
|
|
{
|
|
UnlockVB(nStream);
|
|
if (SMeshStream* pMS = GetVertexStream(nStream, 0))
|
|
{
|
|
if (pMS->m_nID != ~0u)
|
|
{
|
|
// Unregister the allocation with the VRAM driller
|
|
void* address = reinterpret_cast<void*>(pMS->m_nID);
|
|
EBUS_EVENT(Render::Debug::VRAMDrillerBus, UnregisterAllocation, address);
|
|
|
|
gRenDev->m_DevBufMan.Destroy(pMS->m_nID);
|
|
pMS->m_nID = ~0u;
|
|
}
|
|
pMS->m_nElements = 0;
|
|
pMS->m_nFrameUpdate = -1;
|
|
pMS->m_nFrameCreate = -1;
|
|
}
|
|
}
|
|
|
|
void CRenderMesh::ReleaseIB()
|
|
{
|
|
UnlockIB();
|
|
if (m_IBStream.m_nID != ~0u)
|
|
{
|
|
// Unregister the allocation with the VRAM driller
|
|
void* address = reinterpret_cast<void*>(m_IBStream.m_nID);
|
|
EBUS_EVENT(Render::Debug::VRAMDrillerBus, UnregisterAllocation, address);
|
|
|
|
gRenDev->m_DevBufMan.Destroy(m_IBStream.m_nID);
|
|
m_IBStream.m_nID = ~0u;
|
|
}
|
|
m_IBStream.m_nElements = 0;
|
|
m_IBStream.m_nFrameUpdate = -1;
|
|
m_IBStream.m_nFrameCreate = -1;
|
|
}
|
|
|
|
bool CRenderMesh::UpdateIndices_Int(
|
|
const vtx_idx* pNewInds
|
|
, int nInds
|
|
, int nOffsInd
|
|
, uint32 copyFlags)
|
|
{
|
|
AZ_TRACE_METHOD();
|
|
|
|
//LockVB operates now on a per mesh lock, any thread may access
|
|
//ASSERT_IS_MAIN_THREAD(gRenDev->m_pRT)
|
|
|
|
//SREC_AUTO_LOCK(m_sResLock);
|
|
|
|
// Resize the index buffer
|
|
if (m_nInds != nInds)
|
|
{
|
|
FreeIB();
|
|
m_nInds = nInds;
|
|
}
|
|
if (!nInds)
|
|
{
|
|
assert(!m_IBStream.m_pUpdateData);
|
|
return true;
|
|
}
|
|
|
|
vtx_idx* pDst = LockIB(FSL_VIDEO_CREATE, 0, nInds);
|
|
if (pDst && pNewInds)
|
|
{
|
|
if (copyFlags & FSL_ASYNC_DEFER_COPY && nInds * sizeof(vtx_idx) < RENDERMESH_ASYNC_MEMCPY_THRESHOLD)
|
|
{
|
|
cryAsyncMemcpy(
|
|
&pDst[nOffsInd],
|
|
pNewInds,
|
|
nInds * sizeof(vtx_idx),
|
|
MC_CPU_TO_GPU | copyFlags,
|
|
SetAsyncUpdateState());
|
|
}
|
|
else
|
|
{
|
|
cryMemcpy(
|
|
&pDst[nOffsInd],
|
|
pNewInds,
|
|
nInds * sizeof(vtx_idx),
|
|
MC_CPU_TO_GPU);
|
|
UnlockIndexStream();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CRenderMesh::UpdateVertices_Int(
|
|
const void* pVertBuffer
|
|
, int nVertCount
|
|
, int nOffset
|
|
, int nStream
|
|
, uint32 copyFlags)
|
|
{
|
|
AZ_TRACE_METHOD();
|
|
//LockVB operates now on a per mesh lock, any thread may access
|
|
// ASSERT_IS_MAIN_THREAD(gRenDev->m_pRT)
|
|
|
|
int nStride;
|
|
|
|
//SREC_AUTO_LOCK(m_sResLock);
|
|
|
|
// Resize the vertex buffer
|
|
if (m_nVerts != nVertCount)
|
|
{
|
|
for (int i = 0; i < VSF_NUM; i++)
|
|
{
|
|
FreeVB(i);
|
|
}
|
|
m_nVerts = nVertCount;
|
|
}
|
|
if (!m_nVerts)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
byte* pDstVB = (byte*)LockVB(nStream, FSL_VIDEO_CREATE, nVertCount, &nStride);
|
|
assert((nVertCount == 0) || pDstVB);
|
|
if (pDstVB && pVertBuffer)
|
|
{
|
|
if (copyFlags & FSL_ASYNC_DEFER_COPY && nStride * nVertCount < RENDERMESH_ASYNC_MEMCPY_THRESHOLD)
|
|
{
|
|
cryAsyncMemcpy(
|
|
&pDstVB[nOffset],
|
|
pVertBuffer,
|
|
nStride * nVertCount,
|
|
MC_CPU_TO_GPU | copyFlags,
|
|
SetAsyncUpdateState());
|
|
}
|
|
else
|
|
{
|
|
cryMemcpy(
|
|
&pDstVB[nOffset],
|
|
pVertBuffer,
|
|
nStride * nVertCount,
|
|
MC_CPU_TO_GPU);
|
|
UnlockStream(nStream);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CRenderMesh::UpdateVertices(
|
|
const void* pVertBuffer
|
|
, int nVertCount
|
|
, int nOffset
|
|
, int nStream
|
|
, uint32 copyFlags
|
|
, bool requiresLock)
|
|
{
|
|
bool result = false;
|
|
if (requiresLock)
|
|
{
|
|
SREC_AUTO_LOCK(m_sResLock);
|
|
result = UpdateVertices_Int(pVertBuffer, nVertCount, nOffset, nStream, copyFlags);
|
|
}
|
|
else
|
|
{
|
|
result = UpdateVertices_Int(pVertBuffer, nVertCount, nOffset, nStream, copyFlags);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool CRenderMesh::UpdateIndices(
|
|
const vtx_idx* pNewInds
|
|
, int nInds
|
|
, int nOffsInd
|
|
, uint32 copyFlags
|
|
, bool requiresLock)
|
|
{
|
|
bool result = false;
|
|
if (requiresLock)
|
|
{
|
|
SREC_AUTO_LOCK(m_sResLock);
|
|
result = UpdateIndices_Int(pNewInds, nInds, nOffsInd, copyFlags);
|
|
}
|
|
else
|
|
{
|
|
result = UpdateIndices_Int(pNewInds, nInds, nOffsInd, copyFlags);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool CRenderMesh::UpdateVidIndices(SMeshStream& IBStream, [[maybe_unused]] bool stall)
|
|
{
|
|
SCOPED_RENDERER_ALLOCATION_NAME_HINT(GetSourceName());
|
|
AZ_TRACE_METHOD();
|
|
|
|
assert(gRenDev->m_pRT->IsRenderThread());
|
|
|
|
SREC_AUTO_LOCK(m_sResLock);
|
|
|
|
assert(gRenDev->m_pRT->IsRenderThread());
|
|
|
|
int nInds = m_nInds;
|
|
|
|
if (!nInds)
|
|
{
|
|
// 0 size index buffer creation crashes on deprecated platform
|
|
assert(nInds);
|
|
return false;
|
|
}
|
|
|
|
if (IBStream.m_nElements != m_nInds && _HasIBStream())
|
|
{
|
|
ReleaseIB();
|
|
}
|
|
|
|
if (IBStream.m_nID == ~0u)
|
|
{
|
|
const size_t bufferSize = nInds * sizeof(vtx_idx);
|
|
IBStream.m_nID = gRenDev->m_DevBufMan.Create(BBT_INDEX_BUFFER, (BUFFER_USAGE)m_eType, bufferSize);
|
|
IBStream.m_nElements = m_nInds;
|
|
IBStream.m_nFrameCreate = gRenDev->m_RP.m_TI[gRenDev->m_pRT->IsMainThread() ? gRenDev->m_RP.m_nFillThreadID : gRenDev->m_RP.m_nProcessThreadID].m_nFrameUpdateID;
|
|
|
|
// Register the allocation with the VRAM driller
|
|
void* address = reinterpret_cast<void*>(IBStream.m_nID);
|
|
const char* bufferName = GetSourceName();
|
|
EBUS_EVENT(Render::Debug::VRAMDrillerBus, RegisterAllocation, address, bufferSize, bufferName, Render::Debug::VRAM_CATEGORY_BUFFER, Render::Debug::VRAM_SUBCATEGORY_BUFFER_INDEX_BUFFER);
|
|
}
|
|
if (IBStream.m_nID != ~0u)
|
|
{
|
|
UnlockIndexStream();
|
|
if (m_IBStream.m_pUpdateData)
|
|
{
|
|
return gRenDev->m_DevBufMan.UpdateBuffer(IBStream.m_nID, IBStream.m_pUpdateData, m_nInds * sizeof(vtx_idx));
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CRenderMesh::CreateVidVertices(int nStream)
|
|
{
|
|
SCOPED_RENDERER_ALLOCATION_NAME_HINT(GetSourceName());
|
|
AZ_TRACE_METHOD();
|
|
|
|
SREC_AUTO_LOCK(m_sResLock);
|
|
|
|
if (gRenDev->m_bDeviceLost)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
assert (!_HasVBStream(nStream));
|
|
SMeshStream* pMS = GetVertexStream(nStream, FSL_WRITE);
|
|
int nSize = GetStreamSize(nStream, m_nVerts);
|
|
pMS->m_nID = gRenDev->m_DevBufMan.Create(BBT_VERTEX_BUFFER, (BUFFER_USAGE)m_eType, nSize);
|
|
pMS->m_nElements = m_nVerts;
|
|
pMS->m_nFrameCreate = gRenDev->m_RP.m_TI
|
|
[gRenDev->m_pRT->IsMainThread()
|
|
? gRenDev->m_RP.m_nFillThreadID
|
|
: gRenDev->m_RP.m_nProcessThreadID].m_nFrameUpdateID;
|
|
|
|
// Register the allocation with the VRAM driller
|
|
void* address = reinterpret_cast<void*>(pMS->m_nID);
|
|
const char* bufferName = GetSourceName();
|
|
EBUS_EVENT(Render::Debug::VRAMDrillerBus, RegisterAllocation, address, nSize, bufferName, Render::Debug::VRAM_CATEGORY_BUFFER, Render::Debug::VRAM_SUBCATEGORY_BUFFER_VERTEX_BUFFER);
|
|
|
|
return (pMS->m_nID != ~0u);
|
|
}
|
|
|
|
bool CRenderMesh::UpdateVidVertices(int nStream, [[maybe_unused]] bool stall)
|
|
{
|
|
AZ_TRACE_METHOD();
|
|
|
|
assert(gRenDev->m_pRT->IsRenderThread());
|
|
|
|
SREC_AUTO_LOCK(m_sResLock);
|
|
|
|
assert(nStream < VSF_NUM);
|
|
SMeshStream* pMS = GetVertexStream(nStream, FSL_WRITE);
|
|
|
|
if (m_nVerts != pMS->m_nElements && _HasVBStream(nStream))
|
|
{
|
|
ReleaseVB(nStream);
|
|
}
|
|
|
|
if (pMS->m_nID == ~0u)
|
|
{
|
|
if (!CreateVidVertices(nStream))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
if (pMS->m_nID != ~0u)
|
|
{
|
|
UnlockStream(nStream);
|
|
if (pMS->m_pUpdateData)
|
|
{
|
|
return gRenDev->m_DevBufMan.UpdateBuffer(pMS->m_nID, pMS->m_pUpdateData, GetStreamSize(nStream));
|
|
;
|
|
}
|
|
else
|
|
{
|
|
assert(0);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#ifdef MESH_TESSELLATION_RENDERER
|
|
template<class VecPos, class VecUV>
|
|
bool CRenderMesh::UpdateUVCoordsAdjacency(SMeshStream& IBStream, const AZ::Vertex::Format& vertexFormat)
|
|
{
|
|
SCOPED_RENDERER_ALLOCATION_NAME_HINT(GetSourceName());
|
|
AZ_TRACE_METHOD();
|
|
|
|
assert(gRenDev->m_pRT->IsRenderThread());
|
|
|
|
SREC_AUTO_LOCK(m_sResLock);
|
|
|
|
int nInds = m_nInds * 4;
|
|
|
|
if (!nInds)
|
|
{
|
|
// 0 size index buffer creation crashes on deprecated platform
|
|
assert(nInds);
|
|
return false;
|
|
}
|
|
|
|
SMeshStream* pMS = GetVertexStream(VSF_GENERAL);
|
|
|
|
if (IBStream.m_nID != ~0u && pMS)
|
|
{
|
|
if (m_IBStream.m_pUpdateData)
|
|
{
|
|
bool bRes = true;
|
|
std::vector<VecUV> pTxtAdjBuffer;
|
|
|
|
// create triangles with adjacency
|
|
byte* pVertexStream = (byte*)pMS->m_pUpdateData;
|
|
uint stride = vertexFormat.GetStride();
|
|
uint offset = 0;
|
|
// Only handle one uv set for now.
|
|
if ((m_vertexFormat.TryCalculateOffset(offset, AZ::Vertex::AttributeUsage::TexCoord)) && pVertexStream)
|
|
{
|
|
int nTrgs = m_nInds / 3;
|
|
pTxtAdjBuffer.resize(nTrgs * 12);
|
|
int nVerts = GetNumVerts();
|
|
for (int n = 0; n < nTrgs; ++n)
|
|
{
|
|
// fill in the dummy adjacency first
|
|
VecUV* pDst = &pTxtAdjBuffer[n * 12];
|
|
vtx_idx* pSrc = (vtx_idx*)m_IBStream.m_pUpdateData + n * 3;
|
|
// triangle itself
|
|
memcpy(&pDst[0], &pVertexStream[pSrc[0] * stride + offset], sizeof(VecUV));
|
|
memcpy(&pDst[1], &pVertexStream[pSrc[1] * stride + offset], sizeof(VecUV));
|
|
memcpy(&pDst[2], &pVertexStream[pSrc[2] * stride + offset], sizeof(VecUV));
|
|
|
|
// adjacency by edges
|
|
memcpy(&pDst[3], &pVertexStream[pSrc[0] * stride + offset], sizeof(VecUV));
|
|
memcpy(&pDst[4], &pVertexStream[pSrc[1] * stride + offset], sizeof(VecUV));
|
|
memcpy(&pDst[5], &pVertexStream[pSrc[1] * stride + offset], sizeof(VecUV));
|
|
memcpy(&pDst[6], &pVertexStream[pSrc[2] * stride + offset], sizeof(VecUV));
|
|
memcpy(&pDst[7], &pVertexStream[pSrc[2] * stride + offset], sizeof(VecUV));
|
|
memcpy(&pDst[8], &pVertexStream[pSrc[0] * stride + offset], sizeof(VecUV));
|
|
// adjacency by corners
|
|
memcpy(&pDst[9], &pVertexStream[pSrc[0] * stride + offset], sizeof(VecUV));
|
|
memcpy(&pDst[10], &pVertexStream[pSrc[1] * stride + offset], sizeof(VecUV));
|
|
memcpy(&pDst[11], &pVertexStream[pSrc[2] * stride + offset], sizeof(VecUV));
|
|
}
|
|
|
|
// now real adjacency is computed
|
|
BuildAdjacency<VecPos, VecUV>(pVertexStream, vertexFormat, nVerts, (vtx_idx*)m_IBStream.m_pUpdateData, nTrgs, pTxtAdjBuffer);
|
|
|
|
m_adjBuffer.Create(pTxtAdjBuffer.size(), sizeof(Vec2f16), DXGI_FORMAT_R16G16_FLOAT, DX11BUF_BIND_SRV, &pTxtAdjBuffer[0]);
|
|
|
|
if constexpr (sizeof(VecUV) == sizeof(Vec2f16))
|
|
{
|
|
m_adjBuffer.Create(pTxtAdjBuffer.size(), sizeof(VecUV), DXGI_FORMAT_R16G16_FLOAT, DX11BUF_BIND_SRV, &pTxtAdjBuffer[0]);
|
|
}
|
|
else
|
|
{
|
|
m_adjBuffer.Create(pTxtAdjBuffer.size(), sizeof(VecUV), DXGI_FORMAT_R32G32_FLOAT, DX11BUF_BIND_SRV, &pTxtAdjBuffer[0]);
|
|
}
|
|
|
|
// HS needs to know iPatchID offset for each drawcall, so we pass this offset in the constant buffer
|
|
// currently texture buffer is created, but when parser support for cbuffer is added (AI: AndreyK), we
|
|
// need to change the below call to:
|
|
// ((CREMeshImpl*) m_Chunks[iChunk].pRE)->m_tessCB.Create(4, sizeof(int), (DXGI_FORMAT)0, &myBuffer[0], D3D11_BIND_CONSTANT_BUFFER);
|
|
for (int iChunk = 0; iChunk < m_Chunks.size(); ++iChunk)
|
|
{
|
|
int myBuffer[4];
|
|
myBuffer[0] = m_Chunks[iChunk].nFirstIndexId / 3;
|
|
((CREMeshImpl*) m_Chunks[iChunk].pRE)->m_tessCB.Create(4, sizeof(int), DXGI_FORMAT_R32_SINT, DX11BUF_BIND_SRV, &myBuffer[0]);
|
|
}
|
|
}
|
|
|
|
return bRes;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
#endif //#ifdef MESH_TESSELLATION_RENDERER
|
|
|
|
void CRenderMesh::Render(const SRendParams& rParams, CRenderObject* pObj, _smart_ptr<IMaterial> pMaterial, const SRenderingPassInfo& passInfo, bool bSkinned)
|
|
{
|
|
FUNCTION_PROFILER_FAST(GetISystem(), PROFILE_RENDERER, g_bProfilerEnabled);
|
|
|
|
IF (!CanRender(), 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int nList = rParams.nRenderList;
|
|
int nAW = rParams.nAfterWater;
|
|
CRenderer* rd = gRenDev;
|
|
|
|
#if !defined(_RELEASE)
|
|
const char* szExcl = CRenderer::CV_r_excludemesh->GetString();
|
|
if (szExcl[0] && m_sSource)
|
|
{
|
|
char szMesh[1024];
|
|
cry_strcpy(szMesh, this->m_sSource);
|
|
azstrlwr(szMesh, AZ_ARRAY_SIZE(szMesh));
|
|
if (szExcl[0] == '!')
|
|
{
|
|
if (!strstr(&szExcl[1], m_sSource))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
if (strstr(szExcl, m_sSource))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (rd->m_pDefaultMaterial && pMaterial)
|
|
{
|
|
pMaterial = rd->m_pDefaultMaterial;
|
|
}
|
|
|
|
assert(pMaterial);
|
|
|
|
if (!pMaterial || !m_nVerts || !m_nInds || m_Chunks.empty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
pObj->m_pRenderNode = rParams.pRenderNode;
|
|
pObj->m_pCurrMaterial = pMaterial;
|
|
|
|
if (rParams.nHUDSilhouettesParams || rParams.nVisionParams || rParams.pInstance)
|
|
{
|
|
SRenderObjData* pOD = rd->EF_GetObjData(pObj, true, passInfo.ThreadID());
|
|
|
|
pOD->m_nHUDSilhouetteParams = rParams.nHUDSilhouettesParams;
|
|
pOD->m_uniqueObjectId = reinterpret_cast<uintptr_t>(rParams.pInstance);
|
|
}
|
|
|
|
|
|
assert(!(pObj->m_ObjFlags & FOB_BENDED));
|
|
|
|
const bool bSG = passInfo.IsShadowPass();
|
|
|
|
if (gRenDev->CV_r_MotionVectors && passInfo.IsGeneralPass() && ((pObj->m_ObjFlags & FOB_DYNAMIC_OBJECT) != 0))
|
|
{
|
|
CMotionBlur::SetupObject(pObj, passInfo);
|
|
}
|
|
|
|
TRenderChunkArray* pChunks = bSkinned ? &m_ChunksSkinned : &m_Chunks;
|
|
|
|
if (pChunks)
|
|
{
|
|
const uint32 ni = (uint32)pChunks->size();
|
|
for (uint32 i = 0; i < ni; i++)
|
|
{
|
|
CRenderChunk* pChunk = &pChunks->at(i);
|
|
CRendElementBase* pREMesh = pChunk->pRE;
|
|
|
|
SShaderItem& ShaderItem = pMaterial->GetShaderItem(pChunk->m_nMatID);
|
|
|
|
CShaderResources* pR = (CShaderResources*)ShaderItem.m_pShaderResources;
|
|
CShader* pS = (CShader*)ShaderItem.m_pShader;
|
|
if (pREMesh && pS && pR)
|
|
{
|
|
if (pS->m_Flags2 & EF2_NODRAW)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (bSG && (pMaterial->GetSafeSubMtl(pChunk->m_nMatID)->GetFlags() & MTL_FLAG_NOSHADOW))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
rd->EF_AddEf_NotVirtual(pREMesh, ShaderItem, pObj, passInfo, nList, nAW, SRendItemSorter(rParams.rendItemSorter));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void CRenderMesh::SetREUserData(float* pfCustomData, [[maybe_unused]] float fFogScale, [[maybe_unused]] float fAlpha)
|
|
{
|
|
for (int i = 0; i < m_Chunks.size(); i++)
|
|
{
|
|
if (m_Chunks[i].pRE)
|
|
{
|
|
m_Chunks[i].pRE->m_CustomData = pfCustomData;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CRenderMesh::AddRenderElements(_smart_ptr<IMaterial> pIMatInfo, CRenderObject* pObj, const SRenderingPassInfo& passInfo, int nList, int nAW)
|
|
{
|
|
SRendItemSorter rendItemSorter = passInfo.IsShadowPass() ? SRendItemSorter::CreateShadowPassRendItemSorter(passInfo) : SRendItemSorter::CreateRendItemSorter(passInfo);
|
|
|
|
assert(!(pObj->m_ObjFlags & FOB_BENDED));
|
|
//assert (!pObj->GetInstanceInfo(0));
|
|
assert(pIMatInfo);
|
|
|
|
if (!_GetVertexContainer()->m_nVerts || !m_Chunks.size() || !pIMatInfo)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (gRenDev->m_pDefaultMaterial && gRenDev->m_pTerrainDefaultMaterial)
|
|
{
|
|
pIMatInfo = gRenDev->m_pDefaultMaterial;
|
|
}
|
|
|
|
for (int i = 0; i < m_Chunks.size(); i++)
|
|
{
|
|
CRenderChunk* pChunk = &m_Chunks[i];
|
|
CREMeshImpl* pOrigRE = (CREMeshImpl*) pChunk->pRE;
|
|
|
|
// get material
|
|
|
|
SShaderItem& shaderItem = pIMatInfo->GetShaderItem(pChunk->m_nMatID);
|
|
|
|
// if (nTechniqueID > 0)
|
|
// shaderItem.m_nTechnique = shaderItem.m_pShader->GetTechniqueID(shaderItem.m_nTechnique, nTechniqueID);
|
|
|
|
if (shaderItem.m_pShader && pOrigRE)// && pMat->nNumIndices)
|
|
{
|
|
TArray<CRendElementBase*>* pREs = shaderItem.m_pShader->GetREs(shaderItem.m_nTechnique);
|
|
|
|
assert(pOrigRE->m_pChunk->nFirstIndexId < 60000);
|
|
|
|
if (!pREs || !pREs->Num())
|
|
{
|
|
gRenDev->EF_AddEf_NotVirtual(pOrigRE, shaderItem, pObj, passInfo, nList, nAW, rendItemSorter);
|
|
}
|
|
else
|
|
{
|
|
gRenDev->EF_AddEf_NotVirtual(pREs->Get(0), shaderItem, pObj, passInfo, nList, nAW, rendItemSorter);
|
|
}
|
|
}
|
|
} //i
|
|
}
|
|
|
|
void CRenderMesh::AddRE(_smart_ptr<IMaterial> pMaterial, CRenderObject* obj, IShader* ef, const SRenderingPassInfo& passInfo, int nList, int nAW, const SRendItemSorter& rendItemSorter)
|
|
{
|
|
if (!m_nVerts || !m_Chunks.size())
|
|
{
|
|
return;
|
|
}
|
|
|
|
assert(!(obj->m_ObjFlags & FOB_BENDED));
|
|
|
|
for (int i = 0; i < m_Chunks.size(); i++)
|
|
{
|
|
if (!m_Chunks[i].pRE)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
SShaderItem& SH = pMaterial->GetShaderItem();
|
|
if (ef)
|
|
{
|
|
SH.m_pShader = ef;
|
|
}
|
|
if (SH.m_pShader)
|
|
{
|
|
assert(m_Chunks[i].pRE->m_pChunk->nFirstIndexId < 60000);
|
|
|
|
TArray<CRendElementBase*>* pRE = SH.m_pShader->GetREs(SH.m_nTechnique);
|
|
if (!pRE || !pRE->Num())
|
|
{
|
|
gRenDev->EF_AddEf_NotVirtual(m_Chunks[i].pRE, SH, obj, passInfo, nList, nAW, rendItemSorter);
|
|
}
|
|
else
|
|
{
|
|
gRenDev->EF_AddEf_NotVirtual(SH.m_pShader->GetREs(SH.m_nTechnique)->Get(0), SH, obj, passInfo, nList, nAW, rendItemSorter);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
size_t CRenderMesh::GetMemoryUsage(ICrySizer* pSizer, EMemoryUsageArgument nType) const
|
|
{
|
|
size_t nSize = 0;
|
|
switch (nType)
|
|
{
|
|
case MEM_USAGE_COMBINED:
|
|
nSize = Size(SIZE_ONLY_SYSTEM) + Size(SIZE_VB | SIZE_IB);
|
|
break;
|
|
case MEM_USAGE_ONLY_SYSTEM:
|
|
nSize = Size(SIZE_ONLY_SYSTEM);
|
|
break;
|
|
case MEM_USAGE_ONLY_VIDEO:
|
|
nSize = Size(SIZE_VB | SIZE_IB);
|
|
return nSize;
|
|
break;
|
|
case MEM_USAGE_ONLY_STREAMS:
|
|
nSize = Size(SIZE_ONLY_SYSTEM) + Size(SIZE_VB | SIZE_IB);
|
|
|
|
if (pSizer)
|
|
{
|
|
SIZER_COMPONENT_NAME(pSizer, "STREAM MESH");
|
|
pSizer->AddObject((void*)this, nSize);
|
|
}
|
|
|
|
// Not add overhead allocations.
|
|
return nSize;
|
|
break;
|
|
}
|
|
|
|
{
|
|
nSize += sizeof(*this);
|
|
for (int i = 0; i < (int)m_Chunks.capacity(); i++)
|
|
{
|
|
if (i < m_Chunks.size())
|
|
{
|
|
nSize += m_Chunks[i].Size();
|
|
}
|
|
else
|
|
{
|
|
nSize += sizeof(CRenderChunk);
|
|
}
|
|
}
|
|
for (int i = 0; i < (int)m_ChunksSkinned.capacity(); i++)
|
|
{
|
|
if (i < m_ChunksSkinned.size())
|
|
{
|
|
nSize += m_ChunksSkinned.at(i).Size();
|
|
}
|
|
else
|
|
{
|
|
nSize += sizeof(CRenderChunk);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pSizer)
|
|
{
|
|
pSizer->AddObject((void*)this, nSize);
|
|
|
|
#ifdef RENDER_MESH_TRIANGLE_HASH_MAP_SUPPORT
|
|
if (m_pTrisMap)
|
|
{
|
|
SIZER_COMPONENT_NAME(pSizer, "Hash map");
|
|
nSize += stl::size_of_map(*m_pTrisMap);
|
|
}
|
|
#endif
|
|
|
|
for (MeshSubSetIndices::const_iterator it = m_meshSubSetIndices.begin(); it != m_meshSubSetIndices.end(); ++it)
|
|
{
|
|
// Collect memory usage for index sub-meshes.
|
|
it->second->GetMemoryUsage(pSizer, nType);
|
|
}
|
|
}
|
|
|
|
return nSize;
|
|
}
|
|
|
|
void CRenderMesh::GetMemoryUsage(ICrySizer* pSizer) const
|
|
{
|
|
pSizer->AddObject(this, sizeof(*this));
|
|
{
|
|
SIZER_COMPONENT_NAME(pSizer, "Vertex Data");
|
|
for (uint32 i = 0; i < VSF_NUM; i++)
|
|
{
|
|
if (SMeshStream* pMS = GetVertexStream(i, 0))
|
|
{
|
|
if (pMS->m_pUpdateData)
|
|
{
|
|
pSizer->AddObject(pMS->m_pUpdateData, GetStreamSize(i));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
SIZER_COMPONENT_NAME(pSizer, "FP16 Cache");
|
|
if (m_pCachePos)
|
|
{
|
|
pSizer->AddObject(m_pCachePos, m_nVerts * sizeof(Vec3));
|
|
}
|
|
for (int i = 0; i < m_UVCache.size(); ++i)
|
|
{
|
|
if (m_UVCache[i])
|
|
{
|
|
pSizer->AddObject(m_UVCache[i], m_nVerts * sizeof(Vec2));
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
SIZER_COMPONENT_NAME(pSizer, "Mesh Chunks");
|
|
pSizer->AddObject(m_Chunks);
|
|
}
|
|
{
|
|
SIZER_COMPONENT_NAME(pSizer, "Mesh Skinned Chunks");
|
|
pSizer->AddObject(m_ChunksSkinned);
|
|
}
|
|
|
|
#ifdef RENDER_MESH_TRIANGLE_HASH_MAP_SUPPORT
|
|
{
|
|
SIZER_COMPONENT_NAME(pSizer, "Hash map");
|
|
pSizer->AddObject(m_pTrisMap);
|
|
}
|
|
#endif
|
|
for (MeshSubSetIndices::const_iterator it = m_meshSubSetIndices.begin(); it != m_meshSubSetIndices.end(); ++it)
|
|
{
|
|
// Collect memory usage for index sub-meshes.
|
|
it->second->GetMemoryUsage(pSizer);
|
|
}
|
|
}
|
|
|
|
int CRenderMesh::GetAllocatedBytes(bool bVideoMem) const
|
|
{
|
|
if (!bVideoMem)
|
|
{
|
|
return Size(SIZE_ONLY_SYSTEM);
|
|
}
|
|
else
|
|
{
|
|
return Size(SIZE_VB | SIZE_IB);
|
|
}
|
|
}
|
|
|
|
int CRenderMesh::GetTextureMemoryUsage(const _smart_ptr<IMaterial> pMaterial, ICrySizer* pSizer, bool bStreamedIn) const
|
|
{
|
|
// If no input material use internal render mesh material.
|
|
if (!pMaterial)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int textureSize = 0;
|
|
std::set<const CTexture*> used;
|
|
for (int a = 0; a < m_Chunks.size(); a++)
|
|
{
|
|
const CRenderChunk* pChunk = &m_Chunks[a];
|
|
|
|
// Override default material
|
|
const SShaderItem& shaderItem = pMaterial->GetShaderItem(pChunk->m_nMatID);
|
|
if (!shaderItem.m_pShaderResources)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const CShaderResources* pRes = (const CShaderResources*)shaderItem.m_pShaderResources;
|
|
for (auto iter = pRes->m_TexturesResourcesMap.begin(); iter != pRes->m_TexturesResourcesMap.end(); ++iter)
|
|
{
|
|
const CTexture* pTexture = iter->second.m_Sampler.m_pTex;
|
|
if (!pTexture)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (used.find(pTexture) != used.end()) // Already used in size calculation.
|
|
{
|
|
continue;
|
|
}
|
|
used.insert(pTexture);
|
|
|
|
int nTexSize = bStreamedIn ? pTexture->GetDeviceDataSize() : pTexture->GetDataSize();
|
|
textureSize += nTexSize;
|
|
|
|
if (pSizer)
|
|
{
|
|
pSizer->AddObject(pTexture, nTexSize);
|
|
}
|
|
}
|
|
}
|
|
return textureSize;
|
|
}
|
|
|
|
float CRenderMesh::GetAverageTrisNumPerChunk(_smart_ptr<IMaterial> pMat)
|
|
{
|
|
float fTrisNum = 0;
|
|
float fChunksNum = 0;
|
|
|
|
for (int m = 0; m < m_Chunks.size(); m++)
|
|
{
|
|
const CRenderChunk& chunk = m_Chunks[m];
|
|
if ((chunk.m_nMatFlags & MTL_FLAG_NODRAW) || !chunk.pRE)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
_smart_ptr<IMaterial> pCustMat;
|
|
if (pMat && chunk.m_nMatID < pMat->GetSubMtlCount())
|
|
{
|
|
pCustMat = pMat->GetSubMtl(chunk.m_nMatID);
|
|
}
|
|
else
|
|
{
|
|
pCustMat = pMat;
|
|
}
|
|
|
|
if (!pCustMat)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const IShader* pShader = pCustMat->GetShaderItem().m_pShader;
|
|
|
|
if (!pShader)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (pShader->GetFlags2() & EF2_NODRAW)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
fTrisNum += chunk.nNumIndices / 3;
|
|
fChunksNum++;
|
|
}
|
|
|
|
return fChunksNum ? (fTrisNum / fChunksNum) : 0;
|
|
}
|
|
|
|
void CRenderMesh::InitTriHash(_smart_ptr<IMaterial> pMaterial)
|
|
{
|
|
#ifdef RENDER_MESH_TRIANGLE_HASH_MAP_SUPPORT
|
|
|
|
SAFE_DELETE(m_pTrisMap);
|
|
m_pTrisMap = new TrisMap;
|
|
|
|
int nPosStride = 0;
|
|
int nIndCount = m_nInds;
|
|
const byte* pPositions = GetPosPtr(nPosStride, FSL_READ);
|
|
const vtx_idx* pIndices = GetIndexPtr(FSL_READ);
|
|
|
|
iLog->Log("CRenderMesh::InitTriHash: Tris=%d, Verts=%d, Name=%s ...", nIndCount / 3, GetVerticesCount(), GetSourceName() ? GetSourceName() : "Null");
|
|
|
|
if (pIndices && pPositions && m_Chunks.size() && nIndCount && GetVerticesCount())
|
|
{
|
|
for (uint32 ii = 0; ii < (uint32)m_Chunks.size(); ii++)
|
|
{
|
|
CRenderChunk* pChunk = &m_Chunks[ii];
|
|
|
|
if (pChunk->m_nMatFlags & MTL_FLAG_NODRAW || !pChunk->pRE)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// skip transparent and alpha test
|
|
const SShaderItem& shaderItem = pMaterial->GetShaderItem(pChunk->m_nMatID);
|
|
if (!shaderItem.IsZWrite() || !shaderItem.m_pShaderResources || shaderItem.m_pShaderResources->IsAlphaTested())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (shaderItem.m_pShader && shaderItem.m_pShader->GetFlags() & EF_DECAL)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
uint32 nFirstIndex = pChunk->nFirstIndexId;
|
|
uint32 nLastIndex = pChunk->nFirstIndexId + pChunk->nNumIndices;
|
|
|
|
for (uint32 i = nFirstIndex; i < nLastIndex; i += 3)
|
|
{
|
|
int32 I0 = pIndices[i + 0];
|
|
int32 I1 = pIndices[i + 1];
|
|
int32 I2 = pIndices[i + 2];
|
|
|
|
Vec3 v0 = *(Vec3*)&pPositions[nPosStride * I0];
|
|
Vec3 v1 = *(Vec3*)&pPositions[nPosStride * I1];
|
|
Vec3 v2 = *(Vec3*)&pPositions[nPosStride * I2];
|
|
|
|
AABB triBox;
|
|
triBox.min = triBox.max = v0;
|
|
triBox.Add(v1);
|
|
triBox.Add(v2);
|
|
|
|
float fRayLen = CRenderer::CV_r_RenderMeshHashGridUnitSize / 2;
|
|
triBox.min -= Vec3(fRayLen, fRayLen, fRayLen);
|
|
triBox.max += Vec3(fRayLen, fRayLen, fRayLen);
|
|
|
|
AABB aabbCell;
|
|
|
|
aabbCell.min = triBox.min / CRenderer::CV_r_RenderMeshHashGridUnitSize;
|
|
aabbCell.min.x = floor(aabbCell.min.x);
|
|
aabbCell.min.y = floor(aabbCell.min.y);
|
|
aabbCell.min.z = floor(aabbCell.min.z);
|
|
|
|
aabbCell.max = triBox.max / CRenderer::CV_r_RenderMeshHashGridUnitSize;
|
|
aabbCell.max.x = ceil(aabbCell.max.x);
|
|
aabbCell.max.y = ceil(aabbCell.max.y);
|
|
aabbCell.max.z = ceil(aabbCell.max.z);
|
|
|
|
for (float x = aabbCell.min.x; x < aabbCell.max.x; x++)
|
|
{
|
|
for (float y = aabbCell.min.y; y < aabbCell.max.y; y++)
|
|
{
|
|
for (float z = aabbCell.min.z; z < aabbCell.max.z; z++)
|
|
{
|
|
AABB cellBox;
|
|
cellBox.min = Vec3(x, y, z) * CRenderer::CV_r_RenderMeshHashGridUnitSize;
|
|
cellBox.max = cellBox.min + Vec3(CRenderer::CV_r_RenderMeshHashGridUnitSize, CRenderer::CV_r_RenderMeshHashGridUnitSize, CRenderer::CV_r_RenderMeshHashGridUnitSize);
|
|
cellBox.min -= Vec3(fRayLen, fRayLen, fRayLen);
|
|
cellBox.max += Vec3(fRayLen, fRayLen, fRayLen);
|
|
if (!Overlap::AABB_Triangle(cellBox, v0, v1, v2))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
int key = (int)(x * 256.f * 256.f + y * 256.f + z);
|
|
PodArray<std::pair<int, int> >* pTris = &(*m_pTrisMap)[key];
|
|
std::pair<int, int> t(i, pChunk->m_nMatID);
|
|
if (pTris->Find(t) < 0)
|
|
{
|
|
pTris->Add(t);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
iLog->LogPlus(" ok (%" PRISIZE_T ")", m_pTrisMap->size());
|
|
|
|
#endif
|
|
}
|
|
|
|
|
|
const PodArray<std::pair<int, int> >* CRenderMesh::GetTrisForPosition([[maybe_unused]] const Vec3& vPos, _smart_ptr<IMaterial> pMaterial)
|
|
{
|
|
#ifdef RENDER_MESH_TRIANGLE_HASH_MAP_SUPPORT
|
|
|
|
if (!m_pTrisMap)
|
|
{
|
|
AUTO_LOCK(m_getTrisForPositionLock);
|
|
if (!m_pTrisMap)
|
|
{
|
|
InitTriHash(pMaterial);
|
|
}
|
|
}
|
|
|
|
Vec3 vCellMin = vPos / CRenderer::CV_r_RenderMeshHashGridUnitSize;
|
|
vCellMin.x = floor(vCellMin.x);
|
|
vCellMin.y = floor(vCellMin.y);
|
|
vCellMin.z = floor(vCellMin.z);
|
|
|
|
int key = (int)(vCellMin.x * 256.f * 256.f + vCellMin.y * 256.f + vCellMin.z);
|
|
|
|
const TrisMap::iterator& iter = (*m_pTrisMap).find(key);
|
|
if (iter != (*m_pTrisMap).end())
|
|
{
|
|
return &iter->second;
|
|
}
|
|
#else
|
|
AZ_Assert(false, "NOT IMPLEMENTED: CRenderMesh::GetTrisForPosition(const Vec3& vPos, _smart_ptr<IMaterial> pMaterial)");
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
void CRenderMesh::UpdateBBoxFromMesh()
|
|
{
|
|
PROFILE_FRAME(UpdateBBoxFromMesh);
|
|
|
|
AABB aabb;
|
|
aabb.Reset();
|
|
|
|
int nVertCount = _GetVertexContainer()->GetVerticesCount();
|
|
int nPosStride = 0;
|
|
const byte* pPositions = GetPosPtr(nPosStride, FSL_READ);
|
|
const vtx_idx* pIndices = GetIndexPtr(FSL_READ);
|
|
|
|
if (!pIndices || !pPositions)
|
|
{
|
|
assert(!"Mesh is not ready");
|
|
return;
|
|
}
|
|
|
|
for (int32 a = 0; a < m_Chunks.size(); a++)
|
|
{
|
|
CRenderChunk* pChunk = &m_Chunks[a];
|
|
|
|
if (pChunk->m_nMatFlags & MTL_FLAG_NODRAW || !pChunk->pRE)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
uint32 nFirstIndex = pChunk->nFirstIndexId;
|
|
uint32 nLastIndex = pChunk->nFirstIndexId + pChunk->nNumIndices;
|
|
|
|
for (uint32 i = nFirstIndex; i < nLastIndex; i++)
|
|
{
|
|
int32 I0 = pIndices[i];
|
|
if (I0 < nVertCount)
|
|
{
|
|
Vec3 v0 = *(Vec3*)&pPositions[nPosStride * I0];
|
|
aabb.Add(v0);
|
|
}
|
|
else
|
|
{
|
|
assert(!"Index is out of range");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!aabb.IsReset())
|
|
{
|
|
m_vBoxMax = aabb.max;
|
|
m_vBoxMin = aabb.min;
|
|
}
|
|
}
|
|
|
|
static void ExtractBoneIndicesAndWeights(uint16* outIndices, Vec4& outWeights, const JointIdType* aBoneRemap, const uint16* indices, UCol weights)
|
|
{
|
|
// Get 8-bit weights as floats
|
|
outWeights[0] = weights.bcolor[0];
|
|
outWeights[1] = weights.bcolor[1];
|
|
outWeights[2] = weights.bcolor[2];
|
|
outWeights[3] = weights.bcolor[3];
|
|
|
|
// Get bone indices
|
|
if (aBoneRemap)
|
|
{
|
|
outIndices[0] = aBoneRemap[indices[0]];
|
|
outIndices[1] = aBoneRemap[indices[1]];
|
|
outIndices[2] = aBoneRemap[indices[2]];
|
|
outIndices[3] = aBoneRemap[indices[3]];
|
|
}
|
|
else
|
|
{
|
|
outIndices[0] = indices[0];
|
|
outIndices[1] = indices[1];
|
|
outIndices[2] = indices[2];
|
|
outIndices[3] = indices[3];
|
|
}
|
|
}
|
|
|
|
static void BlendDualQuats(DualQuat& outBone, Array<const DualQuat> aBoneLocs, const uint16* indices, const Vec4 outWeights)
|
|
{
|
|
outBone = aBoneLocs[indices[0]] * outWeights[0] +
|
|
aBoneLocs[indices[1]] * outWeights[1] +
|
|
aBoneLocs[indices[2]] * outWeights[2] +
|
|
aBoneLocs[indices[3]] * outWeights[3];
|
|
|
|
outBone.Normalize();
|
|
}
|
|
|
|
static void BlendMatrices(Matrix34& outBone, Array<const Matrix34> aBoneLocs, const uint16* indices, const Vec4 outWeights)
|
|
{
|
|
outBone = (aBoneLocs[indices[0]] * outWeights[0]) +
|
|
(aBoneLocs[indices[1]] * outWeights[1]) +
|
|
(aBoneLocs[indices[2]] * outWeights[2]) +
|
|
(aBoneLocs[indices[3]] * outWeights[3]);
|
|
}
|
|
|
|
struct PosNormData
|
|
{
|
|
strided_pointer<Vec3> aPos;
|
|
strided_pointer<Vec3> aNorm;
|
|
strided_pointer<SVF_P3S_N4B_C4B_T2S> aVert;
|
|
strided_pointer<SPipQTangents> aQTan;
|
|
strided_pointer<SPipTangents> aTan2;
|
|
|
|
void GetPosNorm(PosNorm& ran, int nV)
|
|
{
|
|
// Position
|
|
ran.vPos = aPos[nV];
|
|
|
|
// Normal
|
|
if (aNorm.data)
|
|
{
|
|
ran.vNorm = aNorm[nV];
|
|
}
|
|
else if (aVert.data)
|
|
{
|
|
ran.vNorm = aVert[nV].normal.GetN();
|
|
}
|
|
else if (aTan2.data)
|
|
{
|
|
ran.vNorm = aTan2[nV].GetN();
|
|
}
|
|
else if (aQTan.data)
|
|
{
|
|
ran.vNorm = aQTan[nV].GetN();
|
|
}
|
|
}
|
|
};
|
|
|
|
// To do: replace with VSF_MORPHBUDDY support
|
|
#define SKIN_MORPHING 0
|
|
|
|
struct SkinnedPosNormData
|
|
: PosNormData
|
|
{
|
|
SSkinningData const* pSkinningData;
|
|
|
|
#if SKIN_MORPHING
|
|
strided_pointer<SVF_P3F_P3F_I4B> aMorphing;
|
|
#endif
|
|
|
|
strided_pointer<SVF_W4B_I4S> aSkinning;
|
|
|
|
|
|
SkinnedPosNormData() {}
|
|
|
|
void GetPosNorm(PosNorm& ran, int nV)
|
|
{
|
|
PosNormData::GetPosNorm(ran, nV);
|
|
|
|
#if SKIN_MORPHING
|
|
if (aShapeDeform && aMorphing)
|
|
{
|
|
SVF_P3F_P3F_I4B const& morph = aMorphing[nV];
|
|
uint8 idx = (uint8)morph.index.dcolor;
|
|
float fDeform = aShapeDeform[idx];
|
|
if (fDeform < 0.0f)
|
|
{
|
|
ran.vPos = morph.thin * (-fDeform) + ran.vPos * (fDeform + 1);
|
|
}
|
|
else if (fDeform > 0.f)
|
|
{
|
|
ran.vPos = ran.vPos * (1 - fDeform) + morph.fat * fDeform;
|
|
}
|
|
}
|
|
|
|
/*if (!g_arrExtMorphStream.empty())
|
|
ran.vPos += g_arrExtMorphStream[nV];*/
|
|
#endif
|
|
|
|
// Skinning
|
|
if (pSkinningData)
|
|
{
|
|
uint16 indices[4];
|
|
Vec4 weights;
|
|
ExtractBoneIndicesAndWeights(indices, weights, pSkinningData->pRemapTable, &aSkinning[nV].indices[0], aSkinning[nV].weights);
|
|
|
|
if (pSkinningData->nHWSkinningFlags & eHWS_Skinning_Matrix)
|
|
{
|
|
Matrix34 m;
|
|
BlendMatrices(
|
|
m,
|
|
ArrayT(pSkinningData->pBoneMatrices, (int)pSkinningData->nNumBones),
|
|
indices,
|
|
weights);
|
|
ran <<= m;
|
|
}
|
|
else
|
|
{
|
|
// Transform the vertex with skinning.
|
|
DualQuat dq;
|
|
BlendDualQuats(
|
|
dq,
|
|
ArrayT(pSkinningData->pBoneQuatsS, (int)pSkinningData->nNumBones),
|
|
indices,
|
|
weights);
|
|
ran <<= dq;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
float CRenderMesh::GetExtent(EGeomForm eForm)
|
|
{
|
|
if (eForm == GeomForm_Vertices)
|
|
{
|
|
return (float)m_nVerts;
|
|
}
|
|
CGeomExtent& ext = m_Extents.Make(eForm);
|
|
if (!ext)
|
|
{
|
|
LockForThreadAccess();
|
|
|
|
vtx_idx* pInds = GetIndexPtr(FSL_READ);
|
|
strided_pointer<Vec3> aPos;
|
|
aPos.data = (Vec3*)GetPosPtr(aPos.iStride, FSL_READ);
|
|
if (pInds && aPos.data)
|
|
{
|
|
// Iterate chunks to track renderable verts
|
|
bool* aValidVerts = new bool[m_nVerts];
|
|
memset(aValidVerts, 0, m_nVerts);
|
|
TRenderChunkArray& aChunks = !m_ChunksSkinned.empty() ? m_ChunksSkinned : m_Chunks;
|
|
for (int c = 0; c < aChunks.size(); ++c)
|
|
{
|
|
const CRenderChunk& chunk = aChunks[c];
|
|
if (chunk.pRE && !(chunk.m_nMatFlags & (MTL_FLAG_NODRAW | MTL_FLAG_REQUIRE_FORWARD_RENDERING)))
|
|
{
|
|
assert(uint32(chunk.nFirstVertId + chunk.nNumVerts) <= m_nVerts);
|
|
memset(aValidVerts + chunk.nFirstVertId, 1, chunk.nNumVerts);
|
|
}
|
|
}
|
|
|
|
int nParts = TriMeshPartCount(eForm, GetIndicesCount());
|
|
ext.ReserveParts(nParts);
|
|
for (int i = 0; i < nParts; i++)
|
|
{
|
|
int aIndices[3];
|
|
Vec3 aVec[3];
|
|
for (int v = TriIndices(aIndices, i, eForm) - 1; v >= 0; v--)
|
|
{
|
|
aVec[v] = aPos[ pInds[aIndices[v]] ];
|
|
}
|
|
ext.AddPart(aValidVerts[ pInds[aIndices[0]] ] ? max(TriExtent(eForm, aVec), 0.f) : 0.f);
|
|
}
|
|
delete[] aValidVerts;
|
|
}
|
|
|
|
UnlockStream(VSF_GENERAL);
|
|
UnlockIndexStream();
|
|
UnLockForThreadAccess();
|
|
}
|
|
return ext.TotalExtent();
|
|
}
|
|
|
|
void CRenderMesh::GetRandomPos(PosNorm& ran, EGeomForm eForm, SSkinningData const* pSkinning)
|
|
{
|
|
LockForThreadAccess();
|
|
|
|
SkinnedPosNormData vdata;
|
|
vdata.aPos.data = (Vec3*)GetPosPtr(vdata.aPos.iStride, FSL_READ);
|
|
if (vdata.aPos.data)
|
|
{
|
|
// Check possible sources for normals.
|
|
#if ENABLE_NORMALSTREAM_SUPPORT
|
|
if (!GetStridedArray(vdata.aNorm, VSF_NORMALS))
|
|
#endif
|
|
if (_GetVertexFormat() != eVF_P3S_N4B_C4B_T2S || !GetStridedArray(vdata.aVert, VSF_GENERAL))
|
|
{
|
|
if (!GetStridedArray(vdata.aTan2, VSF_TANGENTS))
|
|
{
|
|
GetStridedArray(vdata.aQTan, VSF_QTANGENTS);
|
|
}
|
|
}
|
|
|
|
vtx_idx* pInds = GetIndexPtr(FSL_READ);
|
|
|
|
vdata.pSkinningData = pSkinning;
|
|
if (vdata.pSkinningData)
|
|
{
|
|
GetStridedArray(vdata.aSkinning, VSF_HWSKIN_INFO);
|
|
#if SKIN_MORPHING
|
|
GetStridedArray(vdata.aMorphing, VSF_HWSKIN_SHAPEDEFORM_INFO);
|
|
#endif
|
|
}
|
|
|
|
// Choose part.
|
|
if (eForm == GeomForm_Vertices)
|
|
{
|
|
if (m_nInds == 0)
|
|
{
|
|
ran.zero();
|
|
}
|
|
else
|
|
{
|
|
int nIndex = cry_random(0U, m_nInds - 1);
|
|
vdata.GetPosNorm(ran, pInds[nIndex]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CGeomExtent const& extent = m_Extents[eForm];
|
|
if (!extent.NumParts())
|
|
{
|
|
ran.zero();
|
|
}
|
|
else
|
|
{
|
|
int aIndices[3];
|
|
int nPart = extent.RandomPart();
|
|
int nVerts = TriIndices(aIndices, nPart, eForm);
|
|
|
|
// Extract vertices, blend.
|
|
PosNorm aRan[3];
|
|
while (--nVerts >= 0)
|
|
{
|
|
vdata.GetPosNorm(aRan[nVerts], pInds[aIndices[nVerts]]);
|
|
}
|
|
TriRandomPos(ran, eForm, aRan, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
UnLockForThreadAccess();
|
|
UnlockStream(VSF_GENERAL);
|
|
UnlockStream(VSF_QTANGENTS);
|
|
UnlockStream(VSF_TANGENTS);
|
|
UnlockStream(VSF_HWSKIN_INFO);
|
|
}
|
|
|
|
int CRenderChunk::Size() const
|
|
{
|
|
size_t nSize = sizeof(*this);
|
|
return static_cast<int>(nSize);
|
|
}
|
|
|
|
void CRenderMesh::Size(uint32 nFlags, ICrySizer* pSizer) const
|
|
{
|
|
uint32 i;
|
|
if (!nFlags) // System size
|
|
{
|
|
for (i = 0; i < VSF_NUM; i++)
|
|
{
|
|
if (SMeshStream* pMS = GetVertexStream(i))
|
|
{
|
|
if (pMS->m_pUpdateData)
|
|
{
|
|
pSizer->AddObject(pMS->m_pUpdateData, GetStreamSize(i));
|
|
}
|
|
}
|
|
}
|
|
if (m_IBStream.m_pUpdateData)
|
|
{
|
|
pSizer->AddObject(m_IBStream.m_pUpdateData, m_nInds * sizeof(vtx_idx));
|
|
}
|
|
|
|
if (m_pCachePos)
|
|
{
|
|
pSizer->AddObject(m_pCachePos, m_nVerts * sizeof(Vec3));
|
|
}
|
|
}
|
|
}
|
|
|
|
size_t CRenderMesh::Size(uint32 nFlags) const
|
|
{
|
|
size_t nSize = 0;
|
|
uint32 i;
|
|
if (nFlags == SIZE_ONLY_SYSTEM) // System size
|
|
{
|
|
for (i = 0; i < VSF_NUM; i++)
|
|
{
|
|
if (SMeshStream* pMS = GetVertexStream(i))
|
|
{
|
|
if (pMS->m_pUpdateData)
|
|
{
|
|
nSize += GetStreamSize(i);
|
|
}
|
|
}
|
|
}
|
|
if (m_IBStream.m_pUpdateData)
|
|
{
|
|
nSize += m_nInds * sizeof(vtx_idx);
|
|
}
|
|
|
|
if (m_pCachePos)
|
|
{
|
|
nSize += m_nVerts * sizeof(Vec3);
|
|
}
|
|
}
|
|
if (nFlags & SIZE_VB) // VB size
|
|
{
|
|
for (i = 0; i < VSF_NUM; i++)
|
|
{
|
|
if (_HasVBStream(i))
|
|
{
|
|
nSize += GetStreamSize(i);
|
|
}
|
|
}
|
|
}
|
|
if (nFlags & SIZE_IB) // IB size
|
|
{
|
|
if (_HasIBStream())
|
|
{
|
|
nSize += m_nInds * sizeof(vtx_idx);
|
|
}
|
|
}
|
|
|
|
return nSize;
|
|
}
|
|
|
|
void CRenderMesh::FreeDeviceBuffers(bool bRestoreSys)
|
|
{
|
|
uint32 i;
|
|
|
|
for (i = 0; i < VSF_NUM; i++)
|
|
{
|
|
if (_HasVBStream(i))
|
|
{
|
|
if (bRestoreSys)
|
|
{
|
|
LockForThreadAccess();
|
|
void* pSrc = LockVB(i, FSL_READ | FSL_VIDEO);
|
|
void* pDst = LockVB(i, FSL_SYSTEM_CREATE);
|
|
cryMemcpy(pDst, pSrc, GetStreamSize(i));
|
|
UnLockForThreadAccess();
|
|
}
|
|
ReleaseVB(i);
|
|
}
|
|
}
|
|
|
|
if (_HasIBStream())
|
|
{
|
|
if (bRestoreSys)
|
|
{
|
|
LockForThreadAccess();
|
|
void* pSrc = LockIB(FSL_READ | FSL_VIDEO);
|
|
void* pDst = LockIB(FSL_SYSTEM_CREATE);
|
|
cryMemcpy(pDst, pSrc, m_nInds * sizeof(vtx_idx));
|
|
UnLockForThreadAccess();
|
|
}
|
|
ReleaseIB();
|
|
}
|
|
}
|
|
|
|
void CRenderMesh::FreeVB(int nStream)
|
|
{
|
|
if (SMeshStream* pMS = GetVertexStream(nStream))
|
|
{
|
|
if (pMS->m_pUpdateData)
|
|
{
|
|
FreeMeshData(pMS->m_pUpdateData);
|
|
pMS->m_pUpdateData = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CRenderMesh::FreeIB()
|
|
{
|
|
if (m_IBStream.m_pUpdateData)
|
|
{
|
|
FreeMeshData(m_IBStream.m_pUpdateData);
|
|
m_IBStream.m_pUpdateData = NULL;
|
|
}
|
|
}
|
|
|
|
void CRenderMesh::FreeSystemBuffers()
|
|
{
|
|
uint32 i;
|
|
|
|
for (i = 0; i < VSF_NUM; i++)
|
|
{
|
|
FreeVB(i);
|
|
}
|
|
FreeIB();
|
|
|
|
FreeMeshData(m_pCachePos);
|
|
m_pCachePos = NULL;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CRenderMesh::DebugDraw(const struct SGeometryDebugDrawInfo& info, uint32 nVisibleChunksMask, [[maybe_unused]] float fExtrdueScale)
|
|
{
|
|
IRenderAuxGeom* pRenderAuxGeom = gEnv->pRenderer->GetIRenderAuxGeom();
|
|
LockForThreadAccess();
|
|
|
|
const Matrix34& mat = info.tm;
|
|
|
|
const bool bNoCull = info.bNoCull;
|
|
const bool bNoLines = info.bNoLines;
|
|
const bool bExtrude = info.bExtrude;
|
|
|
|
SAuxGeomRenderFlags prevRenderFlags = pRenderAuxGeom->GetRenderFlags();
|
|
SAuxGeomRenderFlags renderFlags = prevRenderFlags;
|
|
renderFlags.SetDepthWriteFlag(e_DepthWriteOff);
|
|
if (bNoCull)
|
|
{
|
|
renderFlags.SetCullMode(e_CullModeNone);
|
|
}
|
|
pRenderAuxGeom->SetRenderFlags(renderFlags);
|
|
|
|
const ColorB lineColor = info.lineColor;
|
|
const ColorB color = info.color;
|
|
|
|
#ifdef WIN32
|
|
const unsigned int kMaxBatchSize = 20000;
|
|
static std::vector<Vec3> s_vertexBuffer;
|
|
static std::vector<vtx_idx> s_indexBuffer;
|
|
s_vertexBuffer.reserve(kMaxBatchSize);
|
|
s_indexBuffer.reserve(kMaxBatchSize * 2);
|
|
s_vertexBuffer.resize(0);
|
|
s_indexBuffer.resize(0);
|
|
uint32 currentIndexBase = 0;
|
|
#endif
|
|
|
|
const int32 chunkCount = m_Chunks.size();
|
|
for (int32 currentChunkIndex = 0; currentChunkIndex < chunkCount; ++currentChunkIndex)
|
|
{
|
|
const CRenderChunk* pChunk = &m_Chunks[currentChunkIndex];
|
|
|
|
if (pChunk->m_nMatFlags & MTL_FLAG_NODRAW || !pChunk->pRE)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!((1 << currentChunkIndex) & nVisibleChunksMask))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
int posStride = 1;
|
|
const byte* pPositions = GetPosPtr(posStride, FSL_READ);
|
|
const vtx_idx* pIndices = GetIndexPtr(FSL_READ);
|
|
const uint32 numVertices = GetVerticesCount();
|
|
const uint32 indexStep = 3;
|
|
uint32 numIndices = pChunk->nNumIndices;
|
|
|
|
// Make sure number of indices is a multiple of 3.
|
|
uint32 indexRemainder = numIndices % indexStep;
|
|
if (indexRemainder != 0)
|
|
{
|
|
numIndices -= indexRemainder;
|
|
}
|
|
|
|
const uint32 firstIndex = pChunk->nFirstIndexId;
|
|
const uint32 lastIndex = firstIndex + pChunk->nNumIndices;
|
|
|
|
for (uint32 i = firstIndex; i < lastIndex; i += indexStep)
|
|
{
|
|
int32 I0 = pIndices[ i ];
|
|
int32 I1 = pIndices[ i + 1 ];
|
|
int32 I2 = pIndices[ i + 2 ];
|
|
|
|
assert((uint32)I0 < numVertices);
|
|
assert((uint32)I1 < numVertices);
|
|
assert((uint32)I2 < numVertices);
|
|
|
|
Vec3 v0, v1, v2;
|
|
v0 = *(Vec3*)&pPositions[ posStride * I0 ];
|
|
v1 = *(Vec3*)&pPositions[ posStride * I1 ];
|
|
v2 = *(Vec3*)&pPositions[ posStride * I2 ];
|
|
|
|
v0 = mat.TransformPoint(v0);
|
|
v1 = mat.TransformPoint(v1);
|
|
v2 = mat.TransformPoint(v2);
|
|
|
|
if (bExtrude)
|
|
{
|
|
// Push vertices towards the camera to alleviate z-fighting.
|
|
Vec3 cameraPosition = gEnv->pRenderer->GetCamera().GetPosition();
|
|
const float VertexToCameraOffsetAmount = 0.02f;
|
|
v0 = Lerp(v0, cameraPosition, VertexToCameraOffsetAmount);
|
|
v1 = Lerp(v1, cameraPosition, VertexToCameraOffsetAmount);
|
|
v2 = Lerp(v2, cameraPosition, VertexToCameraOffsetAmount);
|
|
}
|
|
|
|
#ifdef WIN32
|
|
s_vertexBuffer.push_back(v0);
|
|
s_vertexBuffer.push_back(v1);
|
|
s_vertexBuffer.push_back(v2);
|
|
|
|
if (!bNoLines)
|
|
{
|
|
s_indexBuffer.push_back(currentIndexBase);
|
|
s_indexBuffer.push_back(currentIndexBase + 1);
|
|
s_indexBuffer.push_back(currentIndexBase + 1);
|
|
s_indexBuffer.push_back(currentIndexBase + 2);
|
|
s_indexBuffer.push_back(currentIndexBase + 2);
|
|
s_indexBuffer.push_back(currentIndexBase);
|
|
|
|
currentIndexBase += indexStep;
|
|
}
|
|
|
|
// Need to limit batch size, because the D3D aux geometry renderer
|
|
// can only process 32768 vertices and 65536 indices
|
|
const size_t vertexBufferSize = s_vertexBuffer.size();
|
|
const bool bOverLimit = vertexBufferSize > kMaxBatchSize;
|
|
const bool bLastTriangle = (i == (lastIndex - indexStep));
|
|
|
|
if (bOverLimit || bLastTriangle)
|
|
{
|
|
// Draw all triangles of the chunk in one go for better batching
|
|
pRenderAuxGeom->DrawTriangles(&s_vertexBuffer[0], s_vertexBuffer.size(), color);
|
|
|
|
if (!bNoLines)
|
|
{
|
|
const size_t indexBufferSize = s_indexBuffer.size();
|
|
pRenderAuxGeom->DrawLines(&s_vertexBuffer[0], vertexBufferSize, &s_indexBuffer[0], indexBufferSize, lineColor);
|
|
|
|
s_indexBuffer.resize(0);
|
|
currentIndexBase = 0;
|
|
}
|
|
|
|
s_vertexBuffer.resize(0);
|
|
}
|
|
#else
|
|
pRenderAuxGeom->DrawTriangle(v0, color, v1, color, v2, color);
|
|
|
|
if (!bNoLines)
|
|
{
|
|
pRenderAuxGeom->DrawLine(v0, lineColor, v1, lineColor);
|
|
pRenderAuxGeom->DrawLine(v1, lineColor, v2, lineColor);
|
|
pRenderAuxGeom->DrawLine(v2, lineColor, v0, lineColor);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
pRenderAuxGeom->SetRenderFlags(prevRenderFlags);
|
|
UnLockForThreadAccess();
|
|
}
|
|
|
|
|
|
//===========================================================================================================
|
|
void CRenderMesh::PrintMeshLeaks()
|
|
{
|
|
AUTO_LOCK(m_sLinkLock);
|
|
for (util::list<CRenderMesh>* iter = CRenderMesh::m_MeshList.next; iter != &CRenderMesh::m_MeshList; iter = iter->next)
|
|
{
|
|
CRenderMesh* pRM = iter->item<& CRenderMesh::m_Chain>();
|
|
Warning("--- CRenderMesh '%s' leak after level unload", (!pRM->m_sSource.empty() ? pRM->m_sSource.c_str() : "NO_NAME"));
|
|
__debugbreak();
|
|
}
|
|
}
|
|
|
|
bool CRenderMesh::ClearStaleMemory(bool bLocked, int threadId)
|
|
{
|
|
FUNCTION_PROFILER(gEnv->pSystem, PROFILE_RENDERER);
|
|
bool cleared = false;
|
|
bool bKeepSystem = false;
|
|
CConditionalLock lock(m_sLinkLock, !bLocked);
|
|
// Clean up the stale mesh temporary data
|
|
for (util::list<CRenderMesh>* iter = m_MeshDirtyList[threadId].next, * pos = iter->next; iter != &m_MeshDirtyList[threadId]; iter = pos, pos = pos->next)
|
|
{
|
|
CRenderMesh* pRM = iter->item<& CRenderMesh::m_Dirty>(threadId);
|
|
if (pRM->m_sResLock.TryLock() == false)
|
|
{
|
|
continue;
|
|
}
|
|
// If the mesh data is still being read, skip it. The stale data will be picked up at a later point
|
|
if (pRM->m_nThreadAccessCounter)
|
|
{
|
|
# if !defined(_RELEASE) && defined(RM_CATCH_EXCESSIVE_LOCKS)
|
|
if (gEnv->pTimer->GetAsyncTime().GetSeconds() - pRM->m_lockTime > 32.f)
|
|
{
|
|
CryError("data lock for mesh '%s:%s' held longer than 32 seconds", (pRM->m_sType ? pRM->m_sType : "unknown"), (pRM->m_sSource ? pRM->m_sSource : "unknown"));
|
|
if (CRenderer::CV_r_BreakOnError)
|
|
{
|
|
__debugbreak();
|
|
}
|
|
}
|
|
# endif
|
|
goto dirty_done;
|
|
}
|
|
|
|
bKeepSystem = pRM->m_keepSysMesh;
|
|
|
|
if (!bKeepSystem && pRM->m_pCachePos)
|
|
{
|
|
FreeMeshData(pRM->m_pCachePos);
|
|
pRM->m_pCachePos = NULL;
|
|
cleared = true;
|
|
}
|
|
|
|
// In DX11 we cannot lock device buffers efficiently from the MT,
|
|
// so we have to keep system copy. On UMA systems we can clear the buffer
|
|
// and access VRAM directly
|
|
#if BUFFER_ENABLE_DIRECT_ACCESS && !defined(NULL_RENDERER)
|
|
if (!bKeepSystem)
|
|
{
|
|
for (int i = 0; i < VSF_NUM; i++)
|
|
{
|
|
pRM->FreeVB(i);
|
|
}
|
|
pRM->FreeIB();
|
|
cleared = true;
|
|
}
|
|
#endif
|
|
|
|
// ToDo: only remove this mesh from the dirty list if no stream contains dirty data anymore
|
|
pRM->m_Dirty[threadId].erase();
|
|
dirty_done:
|
|
pRM->m_sResLock.Unlock();
|
|
}
|
|
|
|
return cleared;
|
|
}
|
|
|
|
void CRenderMesh::UpdateModifiedMeshes(bool bLocked, int threadId)
|
|
{
|
|
AZ_TRACE_METHOD();
|
|
FUNCTION_PROFILER_LEGACYONLY(gEnv->pSystem, PROFILE_RENDERER);
|
|
|
|
//
|
|
// DX12 synchronizes render mesh updates at this point, because we can
|
|
// batch multiple copies together and potentially forward them off
|
|
// to a copy command list.
|
|
//
|
|
#ifdef CRY_USE_DX12
|
|
const bool bBlock = true;
|
|
#else
|
|
const bool bBlock = gRenDev->m_pRT->m_eVideoThreadMode != SRenderThread::eVTM_Disabled;
|
|
#endif
|
|
|
|
CConditionalLock lock(m_sLinkLock, !bLocked);
|
|
// Update device buffers on modified meshes
|
|
for (util::list<CRenderMesh>* iter = m_MeshModifiedList[threadId].next, * pos = iter->next; iter != &m_MeshModifiedList[threadId]; iter = pos, pos = pos->next)
|
|
{
|
|
CRenderMesh* pRM = iter->item<& CRenderMesh::m_Modified>(threadId);
|
|
|
|
#ifdef CRY_USE_DX12
|
|
pRM->m_sResLock.Lock();
|
|
const bool bDoUpdate = true;
|
|
#else
|
|
if (pRM->m_sResLock.TryLock() == false)
|
|
{
|
|
continue;
|
|
}
|
|
const bool bDoUpdate = !pRM->m_nThreadAccessCounter;
|
|
#endif
|
|
|
|
if (bDoUpdate)
|
|
{
|
|
if (pRM->SyncAsyncUpdate(gRenDev->m_RP.m_nProcessThreadID, bBlock) == true)
|
|
{
|
|
// ToDo :
|
|
// - mark the mesh to not update itself if depending on how the async update was scheduled
|
|
// - returns true if no streams need further processing
|
|
if (pRM->RT_CheckUpdate(pRM, VSM_MASK, false, true))
|
|
{
|
|
pRM->m_Modified[threadId].erase();
|
|
}
|
|
}
|
|
}
|
|
pRM->m_sResLock.Unlock();
|
|
}
|
|
}
|
|
|
|
// Mesh garbage collector
|
|
void CRenderMesh::UpdateModified()
|
|
{
|
|
SRenderThread* pRT = gRenDev->m_pRT;
|
|
ASSERT_IS_RENDER_THREAD(pRT);
|
|
const int threadId = gRenDev->m_RP.m_nProcessThreadID;
|
|
|
|
// Call the update and clear functions with bLocked == true even if the lock
|
|
// was previously released in the above scope. The resasoning behind this is
|
|
// that only the renderthread can access the below lists as they are double
|
|
// buffered. Note: As the Lock/Unlock functions can come from the mainthread
|
|
// and from any other thread, they still have guarded against contention!
|
|
// The only exception to this is if we have no render thread, as there is no
|
|
// double buffering in that case - so always lock.
|
|
|
|
UpdateModifiedMeshes(pRT->IsMultithreaded(), threadId);
|
|
}
|
|
|
|
|
|
// Mesh garbage collector
|
|
void CRenderMesh::Tick()
|
|
{
|
|
ASSERT_IS_RENDER_THREAD(gRenDev->m_pRT)
|
|
const threadID threadId = gRenDev->m_pRT->IsMultithreaded() ? gRenDev->m_RP.m_nProcessThreadID : threadID(1);
|
|
int nFrame = gRenDev->m_RP.m_TI[gRenDev->m_RP.m_nProcessThreadID].m_nFrameUpdateID;
|
|
|
|
// Remove deleted meshes from the list completely
|
|
bool deleted = false;
|
|
{
|
|
AUTO_LOCK(m_sLinkLock);
|
|
util::list<CRenderMesh>* garbage = &CRenderMesh::m_MeshGarbageList[nFrame & (MAX_RELEASED_MESH_FRAMES - 1)];
|
|
while (garbage != garbage->prev)
|
|
{
|
|
CRenderMesh* pRM = garbage->next->item<& CRenderMesh::m_Chain>();
|
|
SAFE_DELETE(pRM);
|
|
deleted = true;
|
|
}
|
|
}
|
|
// If an instance pool is used, try to reclaim any used pages if there are any
|
|
if (deleted && s_MeshPool.m_MeshInstancePool)
|
|
{
|
|
s_MeshPool.m_MeshInstancePool->Cleanup();
|
|
}
|
|
|
|
// Call the clear functions with bLocked == true even if the lock
|
|
// was previously released in the above scope. The resasoning behind this is
|
|
// that only the renderthread can access the below lists as they are double
|
|
// buffered. Note: As the Lock/Unlock functions can come from the mainthread
|
|
// and from any other thread, they still have guarded against contention!
|
|
|
|
ClearStaleMemory(true, threadId);
|
|
}
|
|
|
|
void CRenderMesh::Initialize()
|
|
{
|
|
InitializePool();
|
|
}
|
|
|
|
void CRenderMesh::ShutDown()
|
|
{
|
|
if IsCVarConstAccess(constexpr) (CRenderer::CV_r_releaseallresourcesonexit)
|
|
{
|
|
AUTO_LOCK(m_sLinkLock);
|
|
while (&CRenderMesh::m_MeshList != CRenderMesh::m_MeshList.prev)
|
|
{
|
|
CRenderMesh* pRM = CRenderMesh::m_MeshList.next->item<& CRenderMesh::m_Chain>();
|
|
PREFAST_ASSUME(pRM);
|
|
if IsCVarConstAccess(constexpr) (CRenderer::CV_r_printmemoryleaks)
|
|
{
|
|
float fSize = pRM->Size(SIZE_ONLY_SYSTEM) / 1024.0f / 1024.0f;
|
|
iLog->Log("Warning: CRenderMesh::ShutDown: RenderMesh leak %s: %0.3fMb", pRM->m_sSource.c_str(), fSize);
|
|
}
|
|
SAFE_RELEASE_FORCE(pRM);
|
|
}
|
|
}
|
|
new (&CRenderMesh::m_MeshList) util::list<CRenderMesh>();
|
|
new (&CRenderMesh::m_MeshGarbageList) util::list<CRenderMesh>();
|
|
new (&CRenderMesh::m_MeshDirtyList) util::list<CRenderMesh>();
|
|
new (&CRenderMesh::m_MeshModifiedList) util::list<CRenderMesh>();
|
|
|
|
ShutdownPool();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CRenderMesh::KeepSysMesh(bool keep)
|
|
{
|
|
m_keepSysMesh = keep;
|
|
}
|
|
|
|
void CRenderMesh::UnKeepSysMesh()
|
|
{
|
|
m_keepSysMesh = false;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CRenderMesh::SetVertexContainer(IRenderMesh* pBuf)
|
|
{
|
|
if (m_pVertexContainer)
|
|
{
|
|
((CRenderMesh*)m_pVertexContainer)->m_lstVertexContainerUsers.Delete(this);
|
|
}
|
|
|
|
m_pVertexContainer = (CRenderMesh*)pBuf;
|
|
|
|
if (m_pVertexContainer && ((CRenderMesh*)m_pVertexContainer)->m_lstVertexContainerUsers.Find(this) < 0)
|
|
{
|
|
((CRenderMesh*)m_pVertexContainer)->m_lstVertexContainerUsers.Add(this);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CRenderMesh::AssignChunk(CRenderChunk* pChunk, CREMeshImpl* pRE)
|
|
{
|
|
pRE->m_pChunk = pChunk;
|
|
pRE->m_pRenderMesh = this;
|
|
pRE->m_nFirstIndexId = pChunk->nFirstIndexId;
|
|
pRE->m_nNumIndices = pChunk->nNumIndices;
|
|
pRE->m_nFirstVertId = pChunk->nFirstVertId;
|
|
pRE->m_nNumVerts = pChunk->nNumVerts;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CRenderMesh::InitRenderChunk(CRenderChunk& rChunk)
|
|
{
|
|
AZ_Assert(rChunk.nNumIndices > 0, "Render chunk must have > 0 indices");
|
|
AZ_Assert(rChunk.nNumVerts > 0, "Render chunk must have > 0 vertices");
|
|
|
|
if (!rChunk.pRE)
|
|
{
|
|
rChunk.pRE = (CREMeshImpl*) gRenDev->EF_CreateRE(eDATA_Mesh);
|
|
rChunk.pRE->m_CustomTexBind[0] = m_nClientTextureBindID;
|
|
}
|
|
|
|
// update chunk RE
|
|
if (rChunk.pRE)
|
|
{
|
|
AssignChunk(&rChunk, (CREMeshImpl*) rChunk.pRE);
|
|
}
|
|
AZ_Assert(rChunk.nFirstIndexId + rChunk.nNumIndices <= m_nInds, "First index of the chunk + number of indices for the chunk must be <= the total number of indices for the mesh.");
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CRenderMesh::SetRenderChunks(CRenderChunk* pInputChunksArray, int nCount, bool bSubObjectChunks)
|
|
{
|
|
TRenderChunkArray* pChunksArray = &m_Chunks;
|
|
if (bSubObjectChunks)
|
|
{
|
|
pChunksArray = &m_ChunksSubObjects;
|
|
}
|
|
|
|
ReleaseRenderChunks(pChunksArray);
|
|
|
|
pChunksArray->resize(nCount);
|
|
for (int i = 0; i < nCount; i++)
|
|
{
|
|
CRenderChunk& rChunk = (*pChunksArray)[i];
|
|
rChunk = pInputChunksArray[i];
|
|
InitRenderChunk(rChunk);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CRenderMesh::GarbageCollectSubsetRenderMeshes()
|
|
{
|
|
uint32 nFrameID = GetCurrentRenderFrameID();
|
|
m_nLastSubsetGCRenderFrameID = nFrameID;
|
|
for (MeshSubSetIndices::iterator it = m_meshSubSetIndices.begin(); it != m_meshSubSetIndices.end(); )
|
|
{
|
|
IRenderMesh* pRM = it->second;
|
|
if (abs((int)nFrameID - (int)((CRenderMesh*)pRM)->m_nLastRenderFrameID) > DELETE_SUBSET_MESHES_AFTER_NOTUSED_FRAMES)
|
|
{
|
|
// this mesh not relevant anymore.
|
|
it = m_meshSubSetIndices.erase(it);
|
|
}
|
|
else
|
|
{
|
|
++it;
|
|
}
|
|
}
|
|
}
|
|
|
|
volatile int* CRenderMesh::SetAsyncUpdateState()
|
|
{
|
|
SREC_AUTO_LOCK(m_sResLock);
|
|
ASSERT_IS_MAIN_THREAD(gRenDev->m_pRT);
|
|
int threadID = gRenDev->m_RP.m_nFillThreadID;
|
|
if (m_asyncUpdateStateCounter[threadID] == 0)
|
|
{
|
|
m_asyncUpdateStateCounter[threadID] = 1;
|
|
LockForThreadAccess();
|
|
}
|
|
CryInterlockedIncrement(&m_asyncUpdateState[threadID]);
|
|
|
|
for (auto& chunk : m_Chunks)
|
|
{
|
|
if (chunk.pRE)
|
|
{
|
|
chunk.pRE->mfUpdateFlags(FCEF_DIRTY);
|
|
}
|
|
}
|
|
|
|
return &m_asyncUpdateState[threadID];
|
|
}
|
|
|
|
bool CRenderMesh::SyncAsyncUpdate(int threadID, bool block)
|
|
{
|
|
// If this mesh is being asynchronously prepared, wait for the job to finish prior to uploading
|
|
// the vertices to vram.
|
|
SREC_AUTO_LOCK(m_sResLock);
|
|
if (m_asyncUpdateStateCounter[threadID])
|
|
{
|
|
{
|
|
AZ_TRACE_METHOD();
|
|
FRAME_PROFILER_LEGACYONLY("CRenderMesh::SyncAsyncUpdate() sync", gEnv->pSystem, PROFILE_RENDERER);
|
|
int iter = 0;
|
|
while (m_asyncUpdateState[threadID])
|
|
{
|
|
if (!block)
|
|
{
|
|
return false;
|
|
}
|
|
CrySleep(iter > 10 ? 1 : 0);
|
|
++iter;
|
|
}
|
|
}
|
|
UnlockStream(VSF_GENERAL);
|
|
UnlockStream(VSF_TANGENTS);
|
|
UnlockStream(VSF_VERTEX_VELOCITY);
|
|
# if ENABLE_NORMALSTREAM_SUPPORT
|
|
UnlockStream(VSF_NORMALS);
|
|
# endif
|
|
UnlockIndexStream();
|
|
m_asyncUpdateStateCounter[threadID] = 0;
|
|
UnLockForThreadAccess();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void CRenderMesh::CreateRemappedBoneIndicesPair(const uint pairGuid, const TRenderChunkArray& Chunks)
|
|
{
|
|
SREC_AUTO_LOCK(m_sResLock);
|
|
// check already created remapped bone indices
|
|
for (size_t i = 0, end = m_RemappedBoneIndices.size(); i < end; ++i)
|
|
{
|
|
if (m_RemappedBoneIndices[i].guid == pairGuid && m_RemappedBoneIndices[i].refcount)
|
|
{
|
|
++m_RemappedBoneIndices[i].refcount;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// check already currently in flight remapped bone indices
|
|
const int threadId = gRenDev->m_RP.m_nFillThreadID;
|
|
for (size_t i = 0, end = m_CreatedBoneIndices[threadId].size(); i < end; ++i)
|
|
{
|
|
if (m_CreatedBoneIndices[threadId][i].guid == pairGuid)
|
|
{
|
|
++m_CreatedBoneIndices[threadId][i].refcount;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
IRenderMesh::ThreadAccessLock access_lock(this);
|
|
|
|
int32 stride;
|
|
vtx_idx* indices = GetIndexPtr(FSL_READ);
|
|
uint32 vtxCount = GetVerticesCount();
|
|
std::vector<bool> touched(vtxCount, false);
|
|
const SVF_W4B_I4S* pIndicesWeights = (SVF_W4B_I4S*)GetHWSkinPtr(stride, FSL_READ);
|
|
SVF_W4B_I4S* remappedIndices = new SVF_W4B_I4S[vtxCount];
|
|
|
|
for (size_t j = 0; j < Chunks.size(); ++j)
|
|
{
|
|
const CRenderChunk& chunk = Chunks[j];
|
|
for (size_t k = chunk.nFirstIndexId; k < chunk.nFirstIndexId + chunk.nNumIndices; ++k)
|
|
{
|
|
const vtx_idx vIdx = indices[k];
|
|
if (touched[vIdx])
|
|
{
|
|
continue;
|
|
}
|
|
touched[vIdx] = true;
|
|
#if 1
|
|
for (int l = 0; l < 4; ++l)
|
|
{
|
|
remappedIndices[vIdx].weights.bcolor[l] = pIndicesWeights[vIdx].weights.bcolor[l];
|
|
remappedIndices[vIdx].indices[l] = pIndicesWeights[vIdx].indices[l];
|
|
}
|
|
#else
|
|
// Sokov: The code below doesn't look really safe/logical:
|
|
// 1) Calling CreateRemappedBoneIndicesPair() assumes that bones are used,
|
|
// so why to check m_bUsesBones here? Do we have cases when some chunks use bones
|
|
// and some others do not use bones?
|
|
// 2) There is a risk, that some unaware code uses bones but forgets to
|
|
// set m_bUsesBones to 'true' so it leads to killing all linking (see '= 0;').
|
|
if (chunk.m_bUsesBones)
|
|
{
|
|
for (int l = 0; l < 4; ++l)
|
|
{
|
|
remappedIndices[vIdx].weights.bcolor[l] = pIndicesWeights[vIdx].weights.bcolor[l];
|
|
remappedIndices[vIdx].indices[l] = pIndicesWeights[vIdx].indices[l];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int l = 0; l < 4; ++l)
|
|
{
|
|
remappedIndices[vIdx].weights.bcolor[l] = 0;
|
|
remappedIndices[vIdx].indices[l] = 0;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
UnlockStream(VSF_HWSKIN_INFO);
|
|
UnlockIndexStream();
|
|
|
|
m_CreatedBoneIndices[threadId].push_back(SBoneIndexStreamRequest(pairGuid, remappedIndices));
|
|
RelinkTail(m_Modified[threadId], m_MeshModifiedList[threadId], threadId);
|
|
}
|
|
|
|
|
|
void CRenderMesh::CreateRemappedBoneIndicesPair(const DynArray<JointIdType>& arrRemapTable, const uint pairGuid)
|
|
{
|
|
SREC_AUTO_LOCK(m_sResLock);
|
|
// check already created remapped bone indices
|
|
for (size_t i = 0, end = m_RemappedBoneIndices.size(); i < end; ++i)
|
|
{
|
|
if (m_RemappedBoneIndices[i].guid == pairGuid && m_RemappedBoneIndices[i].refcount)
|
|
{
|
|
++m_RemappedBoneIndices[i].refcount;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// check already currently in flight remapped bone indices
|
|
const int threadId = gRenDev->m_RP.m_nFillThreadID;
|
|
for (size_t i = 0, end = m_CreatedBoneIndices[threadId].size(); i < end; ++i)
|
|
{
|
|
if (m_CreatedBoneIndices[threadId][i].guid == pairGuid)
|
|
{
|
|
++m_CreatedBoneIndices[threadId][i].refcount;
|
|
return;
|
|
}
|
|
}
|
|
|
|
IRenderMesh::ThreadAccessLock access_lock(this);
|
|
|
|
int32 stride;
|
|
vtx_idx* const indices = GetIndexPtr(FSL_READ);
|
|
uint32 vtxCount = GetVerticesCount();
|
|
std::vector<bool> touched(vtxCount, false);
|
|
const SVF_W4B_I4S* pIndicesWeights = (const SVF_W4B_I4S*)GetHWSkinPtr(stride, FSL_READ);
|
|
SVF_W4B_I4S* remappedIndices = new SVF_W4B_I4S[vtxCount];
|
|
|
|
for (size_t j = 0; j < m_Chunks.size(); ++j)
|
|
{
|
|
const CRenderChunk& chunk = m_Chunks[j];
|
|
for (size_t k = chunk.nFirstIndexId; k < chunk.nFirstIndexId + chunk.nNumIndices; ++k)
|
|
{
|
|
const vtx_idx vIdx = indices[k];
|
|
if (touched[vIdx])
|
|
{
|
|
continue;
|
|
}
|
|
touched[vIdx] = true;
|
|
#if 1
|
|
for (int l = 0; l < 4; ++l)
|
|
{
|
|
remappedIndices[vIdx].weights.bcolor[l] = pIndicesWeights[vIdx].weights.bcolor[l];
|
|
remappedIndices[vIdx].indices[l] = arrRemapTable[pIndicesWeights[vIdx].indices[l]];
|
|
}
|
|
#else
|
|
// Sokov: The code below doesn't look really safe/logical:
|
|
// 1) Calling CreateRemappedBoneIndicesPair() assumes that bones are used,
|
|
// so why to check m_bUsesBones here? Do we have cases when some chunks use bones
|
|
// and some others do not use bones?
|
|
// 2) There is a risk, that some unaware code uses bones but forgets to
|
|
// set m_bUsesBones to 'true' so it leads to killing all linking (see '= 0;').
|
|
if (chunk.m_bUsesBones)
|
|
{
|
|
for (int l = 0; l < 4; ++l)
|
|
{
|
|
remappedIndices[vIdx].weights.bcolor[l] = pIndicesWeights[vIdx].weights.bcolor[l];
|
|
remappedIndices[vIdx].indices[l] = arrRemapTable[pIndicesWeights[vIdx].indices[l]];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int l = 0; l < 4; ++l)
|
|
{
|
|
remappedIndices[vIdx].weights.bcolor[l] = 0;
|
|
remappedIndices[vIdx].indices[l] = 0;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
UnlockStream(VSF_HWSKIN_INFO);
|
|
UnlockIndexStream();
|
|
|
|
m_CreatedBoneIndices[threadId].push_back(SBoneIndexStreamRequest(pairGuid, remappedIndices));
|
|
RelinkTail(m_Modified[threadId], m_MeshModifiedList[threadId], threadId);
|
|
}
|
|
|
|
void CRenderMesh::ReleaseRemappedBoneIndicesPair(const uint pairGuid)
|
|
{
|
|
if (gRenDev->m_pRT->IsMultithreaded() && gRenDev->m_pRT->IsMainThread(true))
|
|
{
|
|
gRenDev->m_pRT->RC_ReleaseRemappedBoneIndices(this, pairGuid);
|
|
return;
|
|
}
|
|
|
|
SREC_AUTO_LOCK(m_sResLock);
|
|
size_t deleted = ~0u;
|
|
const int threadId = gRenDev->m_RP.m_nProcessThreadID;
|
|
bool bFound = false;
|
|
|
|
for (size_t i = 0, end = m_RemappedBoneIndices.size(); i < end; ++i)
|
|
{
|
|
if (m_RemappedBoneIndices[i].guid == pairGuid)
|
|
{
|
|
bFound = true;
|
|
if (--m_RemappedBoneIndices[i].refcount == 0)
|
|
{
|
|
deleted = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (deleted != ~0u)
|
|
{
|
|
m_DeletedBoneIndices[threadId].push_back(pairGuid);
|
|
RelinkTail(m_Modified[threadId], m_MeshModifiedList[threadId], threadId);
|
|
}
|
|
|
|
// Check for created but not yet remapped bone indices
|
|
if (!bFound)
|
|
{
|
|
for (size_t i = 0, end = m_CreatedBoneIndices[threadId].size(); i < end; ++i)
|
|
{
|
|
if (m_CreatedBoneIndices[threadId][i].guid == pairGuid)
|
|
{
|
|
bFound = true;
|
|
if (--m_CreatedBoneIndices[threadId][i].refcount == 0)
|
|
{
|
|
deleted = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (deleted != ~0u)
|
|
{
|
|
m_DeletedBoneIndices[threadId].push_back(pairGuid);
|
|
RelinkTail(m_Modified[threadId], m_MeshModifiedList[threadId], threadId);
|
|
}
|
|
}
|
|
}
|
|
// Notes: Cry's LockForThreadAccess is not locking anything here, it only increments m_nThreadAccessCounter but m_nThreadAccessCounter is not used to block multithreading access to CRenderMesh
|
|
// This is dangerous as the name of function suggests a lock but it actually doesn't lock. There might be quite a few misuse of this function and as the result the data is not protected in multithread.
|
|
// The system needs a deeper overhauling later.
|
|
void CRenderMesh::LockForThreadAccess()
|
|
{
|
|
++m_nThreadAccessCounter;
|
|
|
|
# if !defined(_RELEASE) && defined(RM_CATCH_EXCESSIVE_LOCKS)
|
|
m_lockTime = (m_lockTime > 0.f) ? m_lockTime : gEnv->pTimer->GetAsyncTime().GetSeconds();
|
|
# endif
|
|
}
|
|
void CRenderMesh::UnLockForThreadAccess()
|
|
{
|
|
--m_nThreadAccessCounter;
|
|
if (m_nThreadAccessCounter < 0)
|
|
{
|
|
__debugbreak(); // if this triggers, a mismatch betweend rendermesh thread access lock/unlock has occured
|
|
}
|
|
# if !defined(_RELEASE) && defined(RM_CATCH_EXCESSIVE_LOCKS)
|
|
m_lockTime = 0.f;
|
|
# endif
|
|
}
|
|
void CRenderMesh::GetPoolStats(SMeshPoolStatistics* stats)
|
|
{
|
|
memcpy(stats, &s_MeshPool.m_MeshDataPoolStats, sizeof(SMeshPoolStatistics));
|
|
}
|
|
|
|
void* CRenderMesh::operator new(size_t size)
|
|
{
|
|
return AllocateMeshInstanceData(size, alignof(CRenderMesh));
|
|
}
|
|
|
|
void CRenderMesh::operator delete(void* ptr)
|
|
{
|
|
FreeMeshInstanceData(ptr);
|
|
}
|
|
|
|
#if !defined(NULL_RENDERER)
|
|
D3DBuffer* CRenderMesh::_GetD3DVB(int nStream, size_t* offs) const
|
|
{
|
|
if (SMeshStream* pMS = GetVertexStream(nStream))
|
|
{
|
|
IF (pMS->m_nID != ~0u, 1)
|
|
{
|
|
return gRenDev->m_DevBufMan.GetD3D(pMS->m_nID, offs);
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
D3DBuffer* CRenderMesh::_GetD3DIB(size_t* offs) const
|
|
{
|
|
IF (m_IBStream.m_nID != ~0u, 1)
|
|
{
|
|
return gRenDev->m_DevBufMan.GetD3D(m_IBStream.m_nID, offs);
|
|
}
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
|
|
void CRenderMesh::BindStreamsToRenderPipeline()
|
|
{
|
|
#if !defined(NULL_RENDERER)
|
|
CRenderMesh *pRenderMesh = this;
|
|
|
|
HRESULT h;
|
|
CD3D9Renderer *rd = (CD3D9Renderer*)gRenDev;
|
|
CRenderMesh *pRM = pRenderMesh->_GetVertexContainer();
|
|
void* pIB = NULL;
|
|
size_t nOffs = 0;
|
|
|
|
size_t streamStride[VSF_NUM] = { 0 };
|
|
size_t offset[VSF_NUM] = { 0 };
|
|
const void *pVB[VSF_NUM] = { NULL };
|
|
|
|
pIB = rd->m_DevBufMan.GetD3D(pRenderMesh->GetIBStream(), &nOffs);
|
|
pVB[0] = rd->m_DevBufMan.GetD3D(pRM->GetVBStream(VSF_GENERAL), &offset[0]);
|
|
h = rd->FX_SetVStream(0, pVB[0], offset[0], pRM->GetStreamStride(VSF_GENERAL));
|
|
|
|
for (int i = 1, mask = 1 << 1; i < VSF_NUM; ++i, mask <<= 1)
|
|
{
|
|
if (rd->m_RP.m_FlagsStreams_Stream & (mask) && pRM->_HasVBStream(i))
|
|
{
|
|
streamStride[i] = pRM->GetStreamStride(i);
|
|
pVB[i] = rd->m_DevBufMan.GetD3D(pRM->GetVBStream(i), &offset[i]);
|
|
}
|
|
}
|
|
|
|
for (int i = 1, mask = 1 << 1; i < VSF_NUM; ++i, mask <<= 1)
|
|
{
|
|
if (rd->m_RP.m_FlagsStreams_Stream & (mask) && pVB[i])
|
|
{
|
|
rd->m_RP.m_PersFlags1 |= RBPF1_USESTREAM << i;
|
|
h = rd->FX_SetVStream(i, pVB[i], offset[i], streamStride[i]);
|
|
}
|
|
else if (rd->m_RP.m_PersFlags1 & (RBPF1_USESTREAM << i))
|
|
{
|
|
rd->m_RP.m_PersFlags1 &= ~(RBPF1_USESTREAM << i);
|
|
h = rd->FX_SetVStream(i, NULL, 0, 0);
|
|
}
|
|
}
|
|
|
|
#ifdef MESH_TESSELLATION_RENDERER
|
|
if (CHWShader_D3D::s_pCurInstHS && pRenderMesh->m_adjBuffer.GetShaderResourceView())
|
|
{
|
|
// This buffer contains texture coordinates for triangle itself and its neighbors, in total 12 texture coordinates per triangle.
|
|
D3DShaderResourceView* SRVs[] = { pRenderMesh->m_adjBuffer.GetShaderResourceView() };
|
|
gcpRendD3D->GetDeviceContext().DSSetShaderResources(15, 1, SRVs);
|
|
}
|
|
#endif
|
|
|
|
// 8 weights skinning. setting the last 4 weights
|
|
if (pRenderMesh->m_extraBonesBuffer.GetShaderResourceView())
|
|
{
|
|
D3DShaderResourceView* SRVs[] = { pRenderMesh->m_extraBonesBuffer.GetShaderResourceView() };
|
|
gcpRendD3D->GetDeviceContext().VSSetShaderResources(14, 1, SRVs);
|
|
}
|
|
|
|
assert(pIB);
|
|
h = rd->FX_SetIStream(pIB, nOffs, (sizeof(vtx_idx) == 2 ? Index16 : Index32));
|
|
|
|
#endif //if !defined(NULL_RENDERER)
|
|
}
|
|
|
|
bool CRenderMesh::GetRemappedSkinningData([[maybe_unused]] uint32 guid, [[maybe_unused]] CRendElementBase::SGeometryStreamInfo &streamInfo)
|
|
{
|
|
#if !defined(NULL_RENDERER)
|
|
size_t offset = 0;
|
|
const void *pVB = 0;
|
|
|
|
for (size_t i = 0, end = m_RemappedBoneIndices.size(); i < end; ++i)
|
|
{
|
|
CRenderMesh::SBoneIndexStream &stream = m_RemappedBoneIndices[i];
|
|
if (stream.guid != guid)
|
|
continue;
|
|
if (stream.buffer != ~0u)
|
|
{
|
|
pVB = gRenDev->m_DevBufMan.GetD3D(stream.buffer, &offset);
|
|
streamInfo.nOffset = offset;
|
|
streamInfo.nStride = GetStreamStride(VSF_HWSKIN_INFO);
|
|
streamInfo.pStream = pVB;
|
|
return true;
|
|
}
|
|
}
|
|
#endif //if !defined(NULL_RENDERER)
|
|
return m_RemappedBoneIndices.size() == 0u;
|
|
}
|
|
|
|
bool CRenderMesh::FillGeometryInfo([[maybe_unused]] CRendElementBase::SGeometryInfo &geom)
|
|
{
|
|
#if !defined(NULL_RENDERER)
|
|
|
|
CRenderMesh *pRenderMeshForVertices = _GetVertexContainer();
|
|
size_t nOffs = 0;
|
|
|
|
if (!_HasIBStream())
|
|
return false;
|
|
|
|
if (!pRenderMeshForVertices->CanRender())
|
|
return false;
|
|
|
|
geom.indexStream.pStream = gRenDev->m_DevBufMan.GetD3D(GetIBStream(), &nOffs);
|
|
geom.indexStream.nOffset = nOffs;
|
|
geom.indexStream.nStride = (sizeof(vtx_idx) == 2 ? Index16 : Index32);
|
|
geom.streamMask = 0;
|
|
geom.nMaxVertexStreams = 0;
|
|
for (int nStream = 0; nStream < VSF_NUM; ++nStream)
|
|
{
|
|
if (pRenderMeshForVertices->_HasVBStream(nStream))
|
|
{
|
|
nOffs = 0;
|
|
geom.vertexStream[nStream].pStream = gRenDev->m_DevBufMan.GetD3D(pRenderMeshForVertices->GetVBStream(nStream), &nOffs);
|
|
geom.vertexStream[nStream].nOffset = nOffs;
|
|
geom.vertexStream[nStream].nStride = pRenderMeshForVertices->GetStreamStride(nStream);
|
|
|
|
if (geom.vertexStream[nStream].pStream)
|
|
{
|
|
geom.nMaxVertexStreams = nStream + 1;
|
|
}
|
|
|
|
//geom.streamMask |= 1 << nStream;
|
|
}
|
|
else
|
|
{
|
|
geom.vertexStream[nStream].pStream = 0;
|
|
geom.vertexStream[nStream].nOffset = 0;
|
|
geom.vertexStream[nStream].nStride = 0;
|
|
}
|
|
}
|
|
|
|
if (m_RemappedBoneIndices.size() > 0)
|
|
{
|
|
if (GetRemappedSkinningData(geom.bonesRemapGUID, geom.vertexStream[VSF_HWSKIN_INFO]))
|
|
{
|
|
if (geom.nMaxVertexStreams <= VSF_HWSKIN_INFO)
|
|
{
|
|
geom.nMaxVertexStreams = VSF_HWSKIN_INFO + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
geom.pSkinningExtraBonesBuffer = &m_extraBonesBuffer;
|
|
|
|
#ifdef MESH_TESSELLATION_RENDERER
|
|
geom.pTessellationAdjacencyBuffer = &m_adjBuffer;
|
|
#else
|
|
geom.pTessellationAdjacencyBuffer = nullptr;
|
|
#endif
|
|
|
|
#endif //if !defined(NULL_RENDERER)
|
|
|
|
return true;
|
|
}
|
|
|
|
CThreadSafeRendererContainer<CRenderMesh*> CRenderMesh::m_deferredSubsetGarbageCollection[RT_COMMAND_BUF_COUNT];
|
|
CThreadSafeRendererContainer<SMeshSubSetIndicesJobEntry> CRenderMesh::m_meshSubSetRenderMeshJobs[RT_COMMAND_BUF_COUNT];
|
|
|
|
|
|
#if RENDERMESH_CPP_TRAIT_BUFFER_ENABLE_DIRECT_ACCESS || defined(CRY_USE_DX12)
|
|
#ifdef BUFFER_ENABLE_DIRECT_ACCESS
|
|
#undef BUFFER_ENABLE_DIRECT_ACCESS
|
|
#define BUFFER_ENABLE_DIRECT_ACCESS 1
|
|
#endif
|
|
#endif
|
|
|