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.
o3de/Code/CryEngine/RenderDll/XRenderD3D9/GraphicsPipeline/MotionBlur.cpp

416 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 "MotionBlur.h"
#include "DriverD3D.h"
#include "D3DPostProcess.h"
//////////////////////////////////////////////////////////////////////////
// Old Pipeline Pass
AZStd::unique_ptr<CMotionBlur::ObjectMap> CMotionBlur::m_Objects[CMotionBlur::s_maxObjectBuffers];
CThreadSafeRendererContainer<CMotionBlur::ObjectMap::value_type> CMotionBlur::m_FillData[RT_COMMAND_BUF_COUNT];
void CMotionBlur::GetPrevObjToWorldMat(CRenderObject* renderObject, Matrix44A& worldMatrix)
{
assert(renderObject);
#if AZ_RENDER_TO_TEXTURE_GEM_ENABLED
const int threadId = gRenDev->m_RP.m_nProcessThreadID;
// RTT does not support motion blur yet
if (gRenDev->m_RP.m_TI[threadId].m_PersFlags & RBPF_RENDER_SCENE_TO_TEXTURE)
{
worldMatrix = renderObject->m_II.m_Matrix;
return;
}
#endif // if AZ_RENDER_TO_TEXTURE_GEM_ENABLED
if (renderObject->m_ObjFlags & FOB_HAS_PREVMATRIX)
{
const SRenderObjData* renderObjectData = renderObject->GetObjData();
const uintptr_t objectId = renderObjectData ? renderObjectData->m_uniqueObjectId : 0;
#if AZ_RENDER_TO_TEXTURE_GEM_ENABLED
const AZ::u32 objectIndex = GetPrevBufferIndex();
#else
const AZ::u32 frameId = gRenDev->GetFrameID(false);
const AZ::u32 objectIndex = (frameId - 1) % CMotionBlur::s_maxObjectBuffers;
#endif // if AZ_RENDER_TO_TEXTURE_GEM_ENABLED
auto it = m_Objects[objectIndex]->find(objectId);
if (it != m_Objects[objectIndex]->end())
{
worldMatrix = it->second.m_worldMatrix;
return;
}
}
worldMatrix = renderObject->m_II.m_Matrix;
}
void CMotionBlur::OnBeginFrame()
{
assert(!gRenDev->m_pRT || gRenDev->m_pRT->IsMainThread());
#if AZ_RENDER_TO_TEXTURE_GEM_ENABLED
const AZ::u32 threadId = gRenDev->m_RP.m_nFillThreadID;
if (gRenDev->m_RP.m_TI[threadId].m_PersFlags & RBPF_RENDER_SCENE_TO_TEXTURE)
{
// RTT does not support motion blur yet in render targets yet
return;
}
const AZ::u32 frameId = gRenDev->GetCameraFrameID();
const AZ::u32 objectIndex = GetCurrentBufferIndex();
#else
const AZ::u32 frameId = gRenDev->GetFrameID(false);
const AZ::u32 objectIndex = frameId % CMotionBlur::s_maxObjectBuffers;
#endif // if AZ_RENDER_TO_TEXTURE_GEM_ENABLED
m_Objects[objectIndex]->erase_if([frameId](const VectorMap<uintptr_t, MotionBlurObjectParameters >::value_type& object)
{
return (frameId - object.second.m_updateFrameId) > s_discardThreshold;
});
}
void CMotionBlur::InsertNewElements()
{
AZ::u32 nThreadID = gRenDev->m_RP.m_nProcessThreadID;
if (m_FillData[nThreadID].empty())
{
return;
}
#if AZ_RENDER_TO_TEXTURE_GEM_ENABLED
const AZ::u32 nObjFrameWriteID = GetCurrentBufferIndex();
#else
const AZ::u32 nFrameID = gRenDev->GetFrameID(false);
const AZ::u32 nObjFrameWriteID = (nFrameID - 1) % CMotionBlur::s_maxObjectBuffers;
#endif //if AZ_RENDER_TO_TEXTURE_GEM_ENABLED
m_FillData[nThreadID].CoalesceMemory();
m_Objects[nObjFrameWriteID]->insert(&m_FillData[nThreadID][0], &m_FillData[nThreadID][0] + m_FillData[nThreadID].size());
m_FillData[nThreadID].resize(0);
}
void CMotionBlur::FreeData()
{
for (int i = 0; i < RT_COMMAND_BUF_COUNT; ++i)
{
m_FillData[i].clear();
}
for (size_t i = 0; i < CMotionBlur::s_maxObjectBuffers; ++i)
{
// m_Objects is a static object that is initialized in CMotionBlur::CMotionBlur, which is not guaranteed to be called by an application.
// CMotionBlur::FreeData is a static cleanup function that is called regardless if CMotionBlur was created or not, so check to verify
// we have valid data in m_Objects before trying to destruct the containers.
if (m_Objects[i].get())
{
stl::reconstruct((*m_Objects[i]));
}
}
}
bool CD3D9Renderer::FX_MotionVectorGeneration(bool bEnable)
{
bool bQualityCheck = CPostEffectsMgr::CheckPostProcessQuality(eRQ_Medium, eSQ_Medium);
if (!bQualityCheck)
{
return false;
}
if IsCVarConstAccess(constexpr) (!CV_r_MotionVectors)
{
return false;
}
if (bEnable)
{
GetUtils().Log(" +++ Begin object motion vector generation +++ \n");
// Re-use scene target rendertarget for velocity buffer
RT_SetViewport(0, 0, CTexture::s_ptexSceneTarget->GetWidth(), CTexture::s_ptexSceneTarget->GetHeight());
m_RP.m_PersFlags2 |= RBPF2_MOTIONBLURPASS;
}
else
{
FX_ResetPipe();
gcpRendD3D->RT_SetViewport(0, 0, gcpRendD3D->GetWidth(), gcpRendD3D->GetHeight());
m_RP.m_PersFlags2 &= ~RBPF2_MOTIONBLURPASS;
GetUtils().Log(" +++ End object motion vector generation +++ \n");
}
return true;
}
void CMotionBlur::RenderObjectsVelocity()
{
PROFILE_LABEL_SCOPE("OBJECTS VELOCITY");
auto renderTarget = GetUtils().GetVelocityObjectRT();
auto depthTarget = &gcpRendD3D->m_DepthBufferOrig;
// Make sure the depth target is at least as large as the render target.
// Since the render target lags behind by a frame this might not be the case
// when resolution is changed from higher res to lower res.
if (renderTarget != nullptr && depthTarget != nullptr &&
renderTarget->GetWidth() <= depthTarget->nWidth &&
renderTarget->GetHeight() <= depthTarget->nHeight)
{
// Render object velocities
//The render targets are already in memory for gmem mode
if (!gcpRendD3D->FX_GetEnabledGmemPath(nullptr))
{
gcpRendD3D->FX_PushRenderTarget(0, renderTarget, depthTarget);
}
uint64 nSaveFlagsShader_RT = gRenDev->m_RP.m_FlagsShader_RT;
int iTempX, iTempY, iWidth, iHeight;
gcpRendD3D->GetViewport(&iTempX, &iTempY, &iWidth, &iHeight);
const bool bAllowMotionVectors = CRenderer::CV_r_MotionVectors > 0;
if (bAllowMotionVectors)
{
uint32 nBatchMask = 0;
// Check for moving geometry
if (!CRenderer::CV_r_MotionBlurGBufferVelocity)
{
nBatchMask |= SRendItem::BatchFlags(EFSLIST_GENERAL, gRenDev->m_RP.m_pRLD);
nBatchMask |= SRendItem::BatchFlags(EFSLIST_SKIN, gRenDev->m_RP.m_pRLD);
}
nBatchMask |= SRendItem::BatchFlags(EFSLIST_TRANSP, gRenDev->m_RP.m_pRLD);
if (nBatchMask & FB_MOTIONBLUR)
{
IRenderElement* pPrevRE = gRenDev->m_RP.m_pRE;
gRenDev->m_RP.m_pRE = NULL;
if (!gcpRendD3D->FX_MotionVectorGeneration(true))
{
return;
}
if (!CRenderer::CV_r_MotionBlurGBufferVelocity)
{
gcpRendD3D->FX_ProcessRenderList(EFSLIST_GENERAL, FB_MOTIONBLUR);
gcpRendD3D->FX_ProcessRenderList(EFSLIST_SKIN, FB_MOTIONBLUR);
}
gcpRendD3D->FX_ProcessRenderList(EFSLIST_TRANSP, FB_MOTIONBLUR);
gcpRendD3D->FX_MotionVectorGeneration(false);
gRenDev->m_RP.m_pRE = pPrevRE;
}
}
gRenDev->m_RP.m_FlagsShader_RT = nSaveFlagsShader_RT;
if (!gcpRendD3D->FX_GetEnabledGmemPath(nullptr))
{
gcpRendD3D->FX_PopRenderTarget(0);
}
}
}
//////////////////////////////////////////////////////////////////////////
// New Pipeline Pass
void CMotionBlurPass::Init()
{
}
void CMotionBlurPass::Shutdown()
{
Reset();
}
void CMotionBlurPass::Reset()
{
m_passMotionBlur.Reset();
m_passCopy.Reset();
m_passPacking.Reset();
m_passTileGen1.Reset();
m_passTileGen2.Reset();
m_passNeighborMax.Reset();
}
float CMotionBlurPass::ComputeMotionScale()
{
static float storedMotionScale = 0.0f;
if (gEnv->pTimer->IsTimerPaused(ITimer::ETIMER_GAME))
{
return storedMotionScale;
}
// The length of the generated motion vectors is proportional to the current time step, so we need
// to rescale the motion vectors to simulate a constant camera exposure time
float exposureTime = 1.0f / std::max(CRenderer::CV_r_MotionBlurShutterSpeed, 1e-6f);
float timeStep = std::max(gEnv->pTimer->GetFrameTime(), 1e-6f);
exposureTime *= gEnv->pTimer->GetTimeScale();
storedMotionScale = exposureTime / timeStep;
return storedMotionScale;
}
void CMotionBlurPass::Execute()
{
// Added a check to make sure we're only running the new pipeline motion blur while the new pipeline is enabled.
if (CRenderer::CV_r_GraphicsPipeline <= 0)
{
return;
}
PROFILE_LABEL_SCOPE("MOTION_BLUR");
CD3D9Renderer* rd = gcpRendD3D;
CShader* pShader = CShaderMan::s_shPostMotionBlur;
int vpX, vpY, vpWidth, vpHeight;
rd->GetViewport(&vpX, &vpY, &vpWidth, &vpHeight);
// Check if DOF is enabled
CDepthOfField* pDofRenderTech = (CDepthOfField*)PostEffectMgr()->GetEffect(ePFX_eDepthOfField);
DepthOfFieldParameters dofParameters = pDofRenderTech->GetParameters();
const bool bGatherDofEnabled = CRenderer::CV_r_dof > 0 && dofParameters.m_bEnabled;
Matrix44A mViewProjPrev = CMotionBlur::GetPrevView();
Matrix44 mViewProj = GetUtils().m_pView;
mViewProjPrev = mViewProjPrev * GetUtils().m_pProj * GetUtils().m_pScaleBias;
mViewProjPrev.Transpose();
CTexture* pVelocityRT = CTexture::s_ptexVelocity;
float tileCountX = (float)CTexture::s_ptexVelocityTiles[1]->GetWidth();
float tileCountY = (float)CTexture::s_ptexVelocityTiles[1]->GetHeight();
static CCryNameR motionBlurParamName("vMotionBlurParams");
int texStateLinear = CTexture::GetTexState(STexState(FILTER_LINEAR, true));
int texStatePoint = CTexture::GetTexState(STexState(FILTER_POINT, true));
{
PROFILE_LABEL_SCOPE("PACK VELOCITY");
static CCryNameTSCRC techPackVelocities("PackVelocities");
static CCryNameR viewProjPrevName("mViewProjPrev");
static CCryNameR dirBlurName("vDirectionalBlur");
static CCryNameR radBlurName("vRadBlurParam");
CMotionBlur* pMB = (CMotionBlur*)PostEffectMgr()->GetEffect(ePFX_eMotionBlur);
const float maxRange = 32.0f;
const float amount = clamp_tpl<float>(pMB->m_pRadBlurAmount->GetParam() / maxRange, 0.0f, 1.0f);
const float radius = 1.0f / clamp_tpl<float>(pMB->m_pRadBlurRadius->GetParam(), 1e-6f, 2.0f);
const Vec4 blurDir = pMB->m_pDirectionalBlurVec->GetParamVec4();
const Vec4 dirBlurParam = Vec4(blurDir.x * (maxRange / (float)vpWidth), blurDir.y * (maxRange / (float)vpHeight), (float)vpWidth / (float)vpHeight, 1.0f);
const Vec4 radBlurParam = Vec4(pMB->m_pRadBlurScreenPosX->GetParam() * dirBlurParam.z, pMB->m_pRadBlurScreenPosY->GetParam(), radius * amount, amount);
const bool bRadialBlur = amount + (blurDir.x * blurDir.x) + (blurDir.y * blurDir.y) > 1.0f / (float)vpWidth;
m_passPacking.SetRenderTarget(0, pVelocityRT);
m_passPacking.SetTechnique(pShader, techPackVelocities, bRadialBlur ? g_HWSR_MaskBit[HWSR_SAMPLE0] : 0);
m_passPacking.SetState(GS_NODEPTHTEST);
m_passPacking.SetTextureSamplerPair(0, CTexture::s_ptexZTarget, texStatePoint);
m_passPacking.SetTextureSamplerPair(1, CTexture::s_ptexHDRTarget, texStatePoint);
m_passPacking.SetTextureSamplerPair(2, GetUtils().GetVelocityObjectRT(), texStatePoint);
m_passPacking.SetRequireWorldPos(true);
m_passPacking.BeginConstantUpdate();
mViewProjPrev.Transpose();
pShader->FXSetPSFloat(viewProjPrevName, (Vec4*)mViewProjPrev.GetData(), 4);
pShader->FXSetPSFloat(dirBlurName, &dirBlurParam, 1);
pShader->FXSetPSFloat(radBlurName, &radBlurParam, 1);
const Vec4 motionBlurParams = Vec4(ComputeMotionScale(), 1.0f / tileCountX, 1.0f / tileCountX * CRenderer::CV_r_MotionBlurCameraMotionScale, 0);
pShader->FXSetPSFloat(motionBlurParamName, &motionBlurParams, 1);
m_passPacking.Execute();
}
{
PROFILE_LABEL_SCOPE("VELOCITY TILES");
static CCryNameTSCRC techVelocityTileGen("VelocityTileGen");
static CCryNameTSCRC techTileNeighborhood("VelocityTileNeighborhood");
// Tile generation first pass
{
m_passTileGen1.SetRenderTarget(0, CTexture::s_ptexVelocityTiles[0]);
m_passTileGen1.SetTechnique(pShader, techVelocityTileGen, 0);
m_passTileGen1.SetState(GS_NODEPTHTEST);
m_passTileGen1.SetTextureSamplerPair(0, pVelocityRT, texStatePoint);
m_passTileGen1.BeginConstantUpdate();
Vec4 params = Vec4((float)pVelocityRT->GetWidth(), (float)pVelocityRT->GetHeight(), ceilf((float)gcpRendD3D->GetWidth() / tileCountX), 0);
pShader->FXSetPSFloat(motionBlurParamName, &params, 1);
m_passTileGen1.Execute();
}
// Tile generation second pass
{
m_passTileGen2.SetRenderTarget(0, CTexture::s_ptexVelocityTiles[1]);
m_passTileGen2.SetTechnique(pShader, techVelocityTileGen, 0);
m_passTileGen2.SetState(GS_NODEPTHTEST);
m_passTileGen2.SetTextureSamplerPair(0, CTexture::s_ptexVelocityTiles[0], texStatePoint);
m_passTileGen2.BeginConstantUpdate();
Vec4 params = Vec4((float)CTexture::s_ptexVelocityTiles[0]->GetWidth(), (float)CTexture::s_ptexVelocityTiles[0]->GetHeight(), ceilf((float)gcpRendD3D->GetHeight() / tileCountY), 1);
pShader->FXSetPSFloat(motionBlurParamName, &params, 1);
m_passTileGen2.Execute();
}
// Neighborhood max
{
m_passNeighborMax.SetRenderTarget(0, CTexture::s_ptexVelocityTiles[2]);
m_passNeighborMax.SetTechnique(pShader, techTileNeighborhood, 0);
m_passNeighborMax.SetState(GS_NODEPTHTEST);
m_passNeighborMax.SetTextureSamplerPair(0, CTexture::s_ptexVelocityTiles[1], texStatePoint);
m_passNeighborMax.BeginConstantUpdate();
Vec4 params = Vec4(1.0f / tileCountX, 1.0f / tileCountY, 0, 0);
pShader->FXSetPSFloat(motionBlurParamName, &params, 1);
m_passNeighborMax.Execute();
}
}
{
PROFILE_LABEL_SCOPE("MOTION VECTOR APPLY");
static CCryNameTSCRC techMotionBlur("MotionBlur");
if (bGatherDofEnabled)
{
m_passCopy.Execute(CTexture::s_ptexHDRTarget, CTexture::s_ptexSceneTargetR11G11B10F[0]);
}
uint64 rtMask = 0;
rtMask |= (CRenderer::CV_r_MotionBlurQuality >= 2) ? g_HWSR_MaskBit[HWSR_SAMPLE2] : 0;
rtMask |= (CRenderer::CV_r_MotionBlurQuality == 1) ? g_HWSR_MaskBit[HWSR_SAMPLE1] : 0;
m_passMotionBlur.SetRenderTarget(0, CTexture::s_ptexHDRTarget);
m_passMotionBlur.SetTechnique(pShader, techMotionBlur, rtMask);
m_passMotionBlur.SetState(GS_NODEPTHTEST | GS_BLSRC_ONE | GS_BLDST_ONEMINUSSRCALPHA);
m_passMotionBlur.SetTextureSamplerPair(0, bGatherDofEnabled ? CTexture::s_ptexSceneTargetR11G11B10F[0] : CTexture::s_ptexHDRTargetPrev, texStateLinear);
m_passMotionBlur.SetTextureSamplerPair(1, pVelocityRT, texStatePoint);
m_passMotionBlur.SetTextureSamplerPair(2, CTexture::s_ptexVelocityTiles[2], texStatePoint);
m_passMotionBlur.BeginConstantUpdate();
Vec4 params = Vec4(1.0f / tileCountX, 1.0f / tileCountY, 0, 0);
pShader->FXSetPSFloat(motionBlurParamName, &params, 1);
m_passMotionBlur.Execute();
}
}