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.
454 lines
16 KiB
C++
454 lines
16 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 <AzCore/Debug/Profiler.h>
|
|
|
|
#include "RenderMesh.h"
|
|
|
|
#include "PostProcess/PostEffects.h"
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
namespace
|
|
{
|
|
inline uint32 GetCurrentRenderFrameID(const SRenderingPassInfo& passInfo)
|
|
{
|
|
return gRenDev->m_RP.m_TI[passInfo.ThreadID()].m_nFrameUpdateID;
|
|
};
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void CRenderMesh::Render(CRenderObject* pObj, const SRenderingPassInfo& passInfo, const SRendItemSorter& rendItemSorter)
|
|
{
|
|
_smart_ptr<IMaterial> pMaterial = pObj->m_pCurrMaterial;
|
|
|
|
if (!pMaterial || !m_nVerts || !m_nInds || m_Chunks.empty() || (m_nFlags & FRM_ALLOCFAILURE) != 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
FUNCTION_PROFILER_FAST(GetISystem(), PROFILE_RENDERER, g_bProfilerEnabled);
|
|
|
|
IF (!CanRender(), 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CRenderer* __restrict rd = gRenDev;
|
|
bool bSkinned = (GetChunksSkinned().size() && (pObj->m_ObjFlags & (FOB_SKINNED)));
|
|
|
|
uint64 nMeshSubSetMask = 0;
|
|
#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)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_nLastRenderFrameID = GetCurrentRenderFrameID(passInfo);
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
if (!m_meshSubSetIndices.empty() && abs((int)m_nLastRenderFrameID - (int)m_nLastSubsetGCRenderFrameID) > DELETE_SUBSET_MESHES_AFTER_NOTUSED_FRAMES)
|
|
{
|
|
m_deferredSubsetGarbageCollection[passInfo.ThreadID()].push_back(this);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool bRenderBreakableWithMultipleDrawCalls = false;
|
|
if (pObj->m_ObjFlags & FOB_MESH_SUBSET_INDICES && m_nVerts >= 3)
|
|
{
|
|
SRenderObjData* pOD = pObj->GetObjData();
|
|
if (pOD)
|
|
{
|
|
if (pOD->m_nSubObjHideMask != 0)
|
|
{
|
|
IRenderMesh* pRM = GetRenderMeshForSubsetMask(pOD, pOD->m_nSubObjHideMask, pMaterial, passInfo);
|
|
// if pRM is null, it means that this subset rendermesh is not computed yet, thus we render it with multiple draw calls
|
|
if (pRM)
|
|
{
|
|
static_cast<CRenderMesh*>(pRM)->CRenderMesh::Render(pObj, passInfo, rendItemSorter);
|
|
pOD->m_nSubObjHideMask = 0;
|
|
return;
|
|
}
|
|
// compute the needed mask
|
|
const uint32 ni = m_ChunksSubObjects.size();
|
|
nMeshSubSetMask = pOD->m_nSubObjHideMask & (((uint64)1 << ni) - 1);
|
|
pOD->m_nSubObjHideMask = 0;
|
|
bRenderBreakableWithMultipleDrawCalls = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
int nList = EFSLIST_GENERAL;
|
|
|
|
const int nAW = (pObj->m_ObjFlags & FOB_AFTER_WATER) || (pObj->m_ObjFlags & FOB_NEAREST) ? 1 : 0;
|
|
|
|
//////////////
|
|
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;
|
|
// for rendering with multiple drawcalls, use ChunksSubObjects
|
|
if (bRenderBreakableWithMultipleDrawCalls)
|
|
{
|
|
pChunks = &m_ChunksSubObjects;
|
|
}
|
|
|
|
const uint32 ni = (uint32)pChunks->size();
|
|
|
|
CRenderChunk* pPrevChunk = NULL;
|
|
for (uint32 i = 0; i < ni; i++)
|
|
{
|
|
CRenderChunk* pChunk = &pChunks->at(i);
|
|
CRendElementBase* __restrict pREMesh = pChunk->pRE;
|
|
|
|
SShaderItem& ShaderItem = pMaterial->GetShaderItem(pChunk->m_nMatID);
|
|
CShaderResources* pR = (CShaderResources*)ShaderItem.m_pShaderResources;
|
|
CShader* __restrict pS = (CShader*)ShaderItem.m_pShader;
|
|
|
|
if (pR && pR->IsDeforming())
|
|
{
|
|
pObj->m_ObjFlags |= FOB_MOTION_BLUR;
|
|
}
|
|
|
|
// don't render this chunk if the hide mask for it is set
|
|
if (bRenderBreakableWithMultipleDrawCalls && (nMeshSubSetMask & ((uint64)1 << pChunk->nSubObjectIndex)))
|
|
{
|
|
goto SkipChunk;
|
|
}
|
|
|
|
if (pREMesh == NULL || pS == NULL || pR == NULL)
|
|
{
|
|
goto SkipChunk;
|
|
}
|
|
|
|
if (pS->m_Flags2 & EF2_NODRAW)
|
|
{
|
|
goto SkipChunk;
|
|
}
|
|
|
|
if (passInfo.IsShadowPass() && (pR->m_ResFlags & MTL_FLAG_NOSHADOW))
|
|
{
|
|
goto SkipChunk;
|
|
}
|
|
|
|
if (passInfo.IsShadowPass() && !passInfo.IsDisableRenderChunkMerge() && CRenderMesh::RenderChunkMergeAbleInShadowPass(pPrevChunk, pChunk, pMaterial))
|
|
{
|
|
continue; // skip the merged chunk, but keep the PrevChunkReference for further merging
|
|
}
|
|
PrefetchLine(pREMesh, 0);
|
|
PrefetchLine(pObj, 0);
|
|
rd->EF_AddEf_NotVirtual(pREMesh, ShaderItem, pObj, passInfo, nList, nAW, rendItemSorter);
|
|
|
|
pPrevChunk = pChunk;
|
|
continue;
|
|
SkipChunk:
|
|
pPrevChunk = NULL;
|
|
}
|
|
}
|
|
|
|
void CRenderMesh::AddShadowPassMergedChunkIndicesAndVertices(CRenderChunk* pCurrentChunk, _smart_ptr<IMaterial> pMaterial, int& rNumVertices, int& rNumIndices)
|
|
{
|
|
if (m_Chunks.size() == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (gRenDev->m_RP.m_pCurObject->m_ObjFlags & (FOB_SKINNED))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (pMaterial == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
AUTO_LOCK(pMaterial->GetSubMaterialResizeLock());
|
|
for (uint32 i = (uint32)(pCurrentChunk - &m_Chunks[0]) + 1; i < (uint32)m_Chunks.size(); ++i)
|
|
{
|
|
if (!CRenderMesh::RenderChunkMergeAbleInShadowPass(pCurrentChunk, &m_Chunks[i], pMaterial))
|
|
{
|
|
return;
|
|
}
|
|
|
|
rNumVertices += m_Chunks[i].nNumVerts;
|
|
rNumIndices += m_Chunks[i].nNumIndices;
|
|
}
|
|
}
|
|
|
|
bool CRenderMesh::RenderChunkMergeAbleInShadowPass(CRenderChunk* pPreviousChunk, CRenderChunk* pCurrentChunk, _smart_ptr<IMaterial> pMaterial)
|
|
{
|
|
if IsCVarConstAccess(constexpr) (!CRenderer::CV_r_MergeShadowDrawcalls)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (pPreviousChunk == NULL || pCurrentChunk == NULL || pMaterial == NULL)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
SShaderItem& rCurrentShaderItem = pMaterial->GetShaderItem(pCurrentChunk->m_nMatID);
|
|
SShaderItem& rPreviousShaderItem = pMaterial->GetShaderItem(pPreviousChunk->m_nMatID);
|
|
|
|
CShaderResources* pCurrentShaderResource = (CShaderResources*)rCurrentShaderItem.m_pShaderResources;
|
|
CShaderResources* pPreviousShaderResource = (CShaderResources*)rPreviousShaderItem.m_pShaderResources;
|
|
|
|
CShader* pCurrentShader = (CShader*)rCurrentShaderItem.m_pShader;
|
|
CShader* pPreviousShader = (CShader*)rPreviousShaderItem.m_pShader;
|
|
|
|
if (!pCurrentShaderResource || !pPreviousShaderResource || !pCurrentShader || !pPreviousShader)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool bCurrentAlphaTested = pCurrentShaderResource->CShaderResources::IsAlphaTested();
|
|
bool bPreviousAlphaTested = pPreviousShaderResource->CShaderResources::IsAlphaTested();
|
|
|
|
if (bCurrentAlphaTested != bPreviousAlphaTested)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (bCurrentAlphaTested)
|
|
{
|
|
const SEfResTexture* pCurrentResTex = pCurrentShaderResource->GetTextureResource(EFTT_DIFFUSE);
|
|
const SEfResTexture* pPreviousResTex = pPreviousShaderResource->GetTextureResource(EFTT_DIFFUSE);
|
|
|
|
const CTexture* pCurrentDiffuseTex = pCurrentResTex ? pCurrentResTex->m_Sampler.m_pTex : nullptr;
|
|
const CTexture* pPreviousDiffuseTex = pPreviousResTex ? pPreviousResTex->m_Sampler.m_pTex : nullptr;
|
|
|
|
if (pCurrentDiffuseTex != pPreviousDiffuseTex)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (((pPreviousShaderResource->m_ResFlags & MTL_FLAG_NOSHADOW) != 0) || ((pCurrentShaderResource->m_ResFlags & MTL_FLAG_NOSHADOW) != 0))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ((pPreviousShaderResource->m_ResFlags & MTL_FLAG_2SIDED) != (pCurrentShaderResource->m_ResFlags & MTL_FLAG_2SIDED))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (((pPreviousShader->m_Flags & EF_NODRAW) != 0) || ((pCurrentShader->m_Flags & EF_NODRAW) != 0))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// break-ability support
|
|
IRenderMesh* CRenderMesh::GetRenderMeshForSubsetMask([[maybe_unused]] SRenderObjData* pOD, uint64 nMeshSubSetMask, _smart_ptr<IMaterial> pMaterial, const SRenderingPassInfo& passInfo)
|
|
{
|
|
// TODO: If only one bit is set in mask - there is no need to build new index buffer - small part of main index buffer can be re-used
|
|
// TODO: Add auto releasing of not used for long time index buffers
|
|
// TODO: Take into account those induces when computing render mesh memory size for CGF streaming
|
|
// TODO: Support for multiple materials
|
|
|
|
assert(nMeshSubSetMask != 0);
|
|
|
|
IRenderMesh* pSrcRM = this;
|
|
TRenderChunkArray& renderChunks = m_ChunksSubObjects;
|
|
|
|
uint32 nChunkCount = renderChunks.size();
|
|
nMeshSubSetMask &= (((uint64)1 << nChunkCount) - 1);
|
|
|
|
// try to find the index mesh in the already finished list
|
|
const MeshSubSetIndices::iterator meshSubSet = m_meshSubSetIndices.find(nMeshSubSetMask);
|
|
|
|
if (meshSubSet != m_meshSubSetIndices.end())
|
|
{
|
|
return meshSubSet->second;
|
|
}
|
|
|
|
// subset mesh was not found, start job to create one
|
|
SMeshSubSetIndicesJobEntry* pSubSetJob = m_meshSubSetRenderMeshJobs[passInfo.ThreadID()].push_back_new();
|
|
pSubSetJob->m_pSrcRM = pSrcRM;
|
|
pSubSetJob->m_pIndexRM = NULL;
|
|
pSubSetJob->m_nMeshSubSetMask = nMeshSubSetMask;
|
|
|
|
pSubSetJob->jobExecutor.StartJob([pSubSetJob]()
|
|
{
|
|
pSubSetJob->CreateSubSetRenderMesh();
|
|
}); // Legacy JobManager used SJobState::SetBlocking
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void CRenderMesh::FinalizeRendItems(int nThreadID)
|
|
{
|
|
// perform all requiered garbage collections
|
|
m_deferredSubsetGarbageCollection[nThreadID].CoalesceMemory();
|
|
for (size_t i = 0; i < m_deferredSubsetGarbageCollection[nThreadID].size(); ++i)
|
|
{
|
|
if (m_deferredSubsetGarbageCollection[nThreadID][i])
|
|
{
|
|
m_deferredSubsetGarbageCollection[nThreadID][i]->GarbageCollectSubsetRenderMeshes();
|
|
}
|
|
}
|
|
m_deferredSubsetGarbageCollection[nThreadID].resize(0);
|
|
|
|
// add all newly generated subset meshes
|
|
bool bJobsStillRunning = false;
|
|
size_t nNumSubSetRenderMeshJobs = m_meshSubSetRenderMeshJobs[nThreadID].size();
|
|
for (size_t i = 0; i < nNumSubSetRenderMeshJobs; ++i)
|
|
{
|
|
SMeshSubSetIndicesJobEntry& rSubSetJob = m_meshSubSetRenderMeshJobs[nThreadID][i];
|
|
if (rSubSetJob.jobExecutor.IsRunning())
|
|
{
|
|
bJobsStillRunning = true;
|
|
}
|
|
else if (rSubSetJob.m_pSrcRM) // finished job, which needs to be assigned
|
|
{
|
|
CRenderMesh* pSrcMesh = static_cast<CRenderMesh*>(rSubSetJob.m_pSrcRM.get());
|
|
// check that we didn't create the same subset mesh twice, if we did, clean up the duplicate
|
|
if (pSrcMesh->m_meshSubSetIndices.find(rSubSetJob.m_nMeshSubSetMask) == pSrcMesh->m_meshSubSetIndices.end())
|
|
{
|
|
pSrcMesh->m_meshSubSetIndices.insert(std::make_pair(rSubSetJob.m_nMeshSubSetMask, rSubSetJob.m_pIndexRM));
|
|
}
|
|
|
|
// mark job as assigned
|
|
rSubSetJob.m_pIndexRM = NULL;
|
|
rSubSetJob.m_pSrcRM = NULL;
|
|
}
|
|
}
|
|
if (!bJobsStillRunning)
|
|
{
|
|
m_meshSubSetRenderMeshJobs[nThreadID].resize(0);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
void CRenderMesh::ClearJobResources()
|
|
{
|
|
for (int i = 0; i < RT_COMMAND_BUF_COUNT; ++i)
|
|
{
|
|
for (size_t j = 0; j < m_deferredSubsetGarbageCollection[i].size(); ++j)
|
|
{
|
|
if (m_deferredSubsetGarbageCollection[i][j])
|
|
{
|
|
m_deferredSubsetGarbageCollection[i][j]->GarbageCollectSubsetRenderMeshes();
|
|
}
|
|
}
|
|
stl::free_container(m_deferredSubsetGarbageCollection[i]);
|
|
|
|
for (size_t j = 0; j < m_deferredSubsetGarbageCollection[i].size(); ++j)
|
|
{
|
|
m_meshSubSetRenderMeshJobs[i][j].jobExecutor.WaitForCompletion();
|
|
}
|
|
stl::free_container(m_meshSubSetRenderMeshJobs[i]);
|
|
}
|
|
}
|
|
|
|
void SMeshSubSetIndicesJobEntry::CreateSubSetRenderMesh()
|
|
{
|
|
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Renderer);
|
|
|
|
CRenderMesh* pSrcMesh = static_cast<CRenderMesh*>(m_pSrcRM.get());
|
|
|
|
TRenderChunkArray& renderChunks = pSrcMesh->m_ChunksSubObjects;
|
|
uint32 nChunkCount = renderChunks.size();
|
|
|
|
|
|
pSrcMesh->LockForThreadAccess();
|
|
if (vtx_idx* pInds = pSrcMesh->GetIndexPtr(FSL_READ))
|
|
{
|
|
TRenderChunkArray newChunks;
|
|
newChunks.reserve(3);
|
|
|
|
int nMatId = -1;
|
|
PodArray<vtx_idx> lstIndices;
|
|
for (uint32 c = 0; c < nChunkCount; c++)
|
|
{
|
|
CRenderChunk& srcChunk = renderChunks[c];
|
|
if (0 == (m_nMeshSubSetMask & ((uint64)1 << srcChunk.nSubObjectIndex)))
|
|
{
|
|
uint32 nLastIndex = lstIndices.size();
|
|
lstIndices.AddList(&pInds[srcChunk.nFirstIndexId], srcChunk.nNumIndices);
|
|
if (newChunks.empty() || nMatId != srcChunk.m_nMatID)
|
|
{
|
|
// New chunk needed.
|
|
newChunks.push_back(srcChunk);
|
|
newChunks.back().nFirstIndexId = nLastIndex;
|
|
newChunks.back().nNumIndices = 0;
|
|
newChunks.back().nNumVerts = 0;
|
|
newChunks.back().pRE = 0;
|
|
}
|
|
nMatId = srcChunk.m_nMatID;
|
|
newChunks.back().nNumIndices += srcChunk.nNumIndices;
|
|
newChunks.back().nNumVerts = max((int)srcChunk.nFirstVertId + (int)srcChunk.nNumVerts - (int)newChunks.back().nFirstVertId, (int)newChunks.back().nNumVerts);
|
|
}
|
|
}
|
|
pSrcMesh->UnLockForThreadAccess();
|
|
|
|
IRenderMesh::SInitParamerers params;
|
|
SVF_P3S_C4B_T2S tempVertex;
|
|
params.pVertBuffer = &tempVertex;
|
|
params.nVertexCount = 1;
|
|
params.vertexFormat = eVF_P3S_C4B_T2S;
|
|
params.pIndices = lstIndices.GetElements();
|
|
params.nIndexCount = lstIndices.Count();
|
|
params.nPrimetiveType = prtTriangleList;
|
|
params.eType = eRMT_Static;
|
|
params.nRenderChunkCount = 1;
|
|
params.bOnlyVideoBuffer = false;
|
|
params.bPrecache = false;
|
|
_smart_ptr<IRenderMesh> pIndexMesh = gRenDev->CreateRenderMesh(pSrcMesh->m_sType, pSrcMesh->m_sSource, ¶ms);
|
|
pIndexMesh->SetVertexContainer(pSrcMesh);
|
|
if (!newChunks.empty())
|
|
{
|
|
pIndexMesh->SetRenderChunks(&newChunks.front(), newChunks.size(), false);
|
|
pIndexMesh->SetBBox(pSrcMesh->m_vBoxMin, pSrcMesh->m_vBoxMax);
|
|
}
|
|
m_pIndexRM = pIndexMesh;
|
|
}
|
|
}
|
|
|