/* * 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. * */ #include "StdAfx.h" #include "PostEffectGroup.h" #include class BlendVisitor { private: bool m_enable; float m_blendAmount; public: void Init(bool enable, float blendAmount) { m_enable = enable; m_blendAmount = blendAmount; } PostEffectGroupParam operator()(float base, float blend) { return base * (1.f - m_blendAmount) + blend * m_blendAmount; } PostEffectGroupParam operator()(const Vec4& base, const Vec4& blend) { return base * (1.f - m_blendAmount) + blend * m_blendAmount; } PostEffectGroupParam operator()(const AZStd::string& base, const AZStd::string& blend) { return m_enable ? blend : base; } template PostEffectGroupParam operator()(const T&, const U&) { CRY_ASSERT(false); return 0.f; } }; class SyncVisitor { private: const AZStd::string* m_name; public: void SetName(const AZStd::string& name) { m_name = &name; } void operator()(float param) { if (gEnv && gEnv->p3DEngine) { gEnv->p3DEngine->SetPostEffectParam(m_name->c_str(), param); } } void operator()(const Vec4& param) { if (gEnv && gEnv->p3DEngine) { gEnv->p3DEngine->SetPostEffectParamVec4(m_name->c_str(), param); } } void operator()(const AZStd::string& param) { if (gEnv && gEnv->p3DEngine) { gEnv->p3DEngine->SetPostEffectParamString(m_name->c_str(), param.c_str()); } } }; PostEffectGroup::PostEffectGroup(class PostEffectGroupManager* manager, const char* name, GroupPriority priority, bool hold, float fadeDistance) : m_manager(manager) , m_name(name) , m_enable(false) , m_priority(priority) , m_hold(hold) , m_fadeDistance(fadeDistance) , m_lastUpdateFrame(gEnv->nMainFrameID) , m_enableDuration(0.f) , m_disableDuration(1000.f) , m_strength(0.f) { EBUS_EVENT_RESULT(m_id, AZ::Data::AssetCatalogRequestBus, GetAssetIdByPath, m_name.c_str(), AZ::AzTypeInfo::Uuid(), true); } void PostEffectGroup::SetEnable(bool enable) { if (m_enable == enable) { return; } m_enable = enable; if (m_enable) { m_lastUpdateFrame = gEnv->nMainFrameID; m_enableDuration = 0.f; m_manager->Sort(); } else { m_disableDuration = 0.f; } // Our m_enable state has changed. Alert the PostEffectGroupManager of this. m_manager->SetGroupToggledThisFrame(this); } void PostEffectGroup::StopEffect() { m_enable = false; m_enableDuration = 0.f; m_disableDuration = m_blendOut.GetKeyRangeEnd(); } void PostEffectGroup::SetParam(const char* name, const PostEffectGroupParam& value) { m_params[name] = value; if (m_enable) { m_lastUpdateFrame = gEnv->nMainFrameID; m_manager->Sort(); } } void PostEffectGroup::ClearParams() { m_params.clear(); m_manager->ClearParameterCache(); if (m_enable) { m_lastUpdateFrame = gEnv->nMainFrameID; m_manager->Sort(); } } void PostEffectGroup::ApplyAtPosition(const Vec3& position) { float distance = (position - gEnv->pSystem->GetViewCamera().GetPosition()).len(); if (distance < m_fadeDistance) { m_strength += 1.f - distance / m_fadeDistance; } } void PostEffectGroup::BlendWith(AZStd::unordered_map& paramMap) { AZ_TRACE_METHOD(); if (!m_enable && m_disableDuration >= m_blendOut.GetKeyRangeEnd()) { return; } m_enableDuration += gEnv->pTimer->GetFrameTime(); if (!m_enable) { m_disableDuration += gEnv->pTimer->GetFrameTime(); } if (m_enable && !m_hold && m_enableDuration >= m_blendIn.GetKeyRangeEnd()) { m_enable = false; m_disableDuration = m_enableDuration - m_blendIn.GetKeyRangeEnd(); } for (auto& param : m_params) { auto mapElementIt = paramMap.find(param.first); constexpr size_t dummyTypeIndex = 3; if (mapElementIt == paramMap.end() || mapElementIt->second.index() == dummyTypeIndex) { paramMap[param.first] = param.second; } else { BlendVisitor blendVisitor; float blendInAmount = 1.f, blendOutAmount = 1.f; if (!m_blendIn.empty() && m_enableDuration < m_blendIn.GetKeyRangeEnd()) { m_blendIn.InterpolateFloat(m_enableDuration, blendInAmount); } if (!m_enable) // m_blendOut.empty() is already checked above { m_blendOut.InterpolateFloat(m_disableDuration, blendOutAmount); } blendVisitor.Init(m_enable, blendInAmount * blendOutAmount * (m_fadeDistance ? m_strength : 1.f)); mapElementIt->second = AZStd::visit(blendVisitor, mapElementIt->second, param.second); } } } void PostEffectGroup::ClearSplines() { m_blendIn.Clear(); m_blendOut.Clear(); } PostEffectGroupManager::PostEffectGroupManager() { // Note that the priority of the Base group is 1, this is to prevent invalid ordering with the "Default" group. PostEffectGroup* base = new PostEffectGroup(this, "Base", PostEffectGroup::kPriorityBase, true, 0.f); base->SetEnable(true); m_groups.push_back(std::unique_ptr(base)); gEnv->pRenderer->RegisterSyncWithMainListener(this); if (gEnv->IsEditor()) { // Only monitor assets in the editor. AzFramework::AssetCatalogEventBus::Handler::BusConnect(); } } PostEffectGroupManager::~PostEffectGroupManager() { gEnv->pRenderer->RemoveSyncWithMainListener(this); if (gEnv->IsEditor()) { AzFramework::AssetCatalogEventBus::Handler::BusDisconnect(); } } IPostEffectGroup* PostEffectGroupManager::GetGroup(const char* name) { for (auto& group : m_groups) { if (strcmp(group->GetName(), name) == 0) { return group.get(); } } // Group not loaded, so try to load it from XML // Load on demand instead of at startup so that users can place the XML file in any CryPak path return LoadGroup(name); } IPostEffectGroup* PostEffectGroupManager::GetGroup(const unsigned int index) { if (index < m_groups.size()) { return m_groups[index].get(); } return nullptr; } const unsigned int PostEffectGroupManager::GetGroupCount() { return m_groups.size(); } IPostEffectGroup* PostEffectGroupManager::LoadGroup(const char* name, PostEffectGroup* groupToLoadInto) { XmlNodeRef root = GetISystem()->LoadXmlFromFile(name); unsigned int priority; bool hold = false; float fadeDistance = 0.f; if (!root) { CryWarning(VALIDATOR_MODULE_3DENGINE, VALIDATOR_ERROR, "Can't open post effect group '%s'.", name); return nullptr; } if (!root->isTag("PostEffectGroup")) { CryWarning(VALIDATOR_MODULE_3DENGINE, VALIDATOR_ERROR, "Post effect group '%s' is missing 'PostEffectGroup' root tag", name); return nullptr; } if (!root->getAttr("priority", priority)) { CryWarning(VALIDATOR_MODULE_3DENGINE, VALIDATOR_ERROR, "Post effect group '%s' is missing 'priority' attribute", name); return nullptr; } root->getAttr("hold", hold); root->getAttr("fadeDistance", fadeDistance); PostEffectGroup* group = groupToLoadInto; if (!group) { // No pointer was sent in, so create a new PostEffectGroup and add it to the list. group = new PostEffectGroup(this, name, static_cast(priority), hold, fadeDistance); m_groups.push_back(std::unique_ptr(group)); } else { // Existing PostEffectGroup was sent in, reset its properties so that we can load onto it again. // This is only called from OnCatalogAssetChanged, so should only happen in the editor. group->StopEffect(); group->SetPriority(static_cast(priority)); group->SetHold(hold); group->SetFadeDistance(fadeDistance); group->ClearSplines(); } for (int i = 0; i < root->getChildCount(); i++) { XmlNodeRef node = root->getChild(i); const char* effectName; if (node->isTag("Effect") && node->getAttr("name", &effectName)) { // Set effect params for (int j = 0; j < node->getChildCount(); j++) { XmlNodeRef paramNode = node->getChild(j); const char* paramName; if (paramNode->isTag("Param") && paramNode->getAttr("name", ¶mName)) { std::string paramFullName = std::string(effectName) + "_" + paramName; float floatValue; Vec4 vec4Value; const char* stringValue; if (paramNode->getAttr("floatValue", floatValue)) { group->SetParam(paramFullName.c_str(), floatValue); } else if (paramNode->getAttr("vec4Value", vec4Value)) { group->SetParam(paramFullName.c_str(), vec4Value); } else if (paramNode->getAttr("colorValue", vec4Value)) { group->SetParam(("clr_" + paramFullName).c_str(), vec4Value); } else if (paramNode->getAttr("stringValue", &stringValue)) { group->SetParam(paramFullName.c_str(), stringValue); } else if (paramNode->getAttr("textureValue", &stringValue)) { group->SetParam(("tex_" + paramFullName).c_str(), stringValue); } else { CryWarning(VALIDATOR_MODULE_3DENGINE, VALIDATOR_WARNING, "Post effect group '%s' effect '%s' param '%s' needs either a floatValue, vec4Value, colorValue, stringValue, or textureValue attribute", name, effectName, paramName); } } else { CryWarning(VALIDATOR_MODULE_3DENGINE, VALIDATOR_WARNING, "Post effect group '%s' effect '%s' must contain Param tags with a name attribute", name, effectName); } } continue; } ISplineInterpolator* spline = node->isTag("BlendIn") ? group->GetBlendIn() : node->isTag("BlendOut") ? group->GetBlendOut() : nullptr; if (spline) { // Add blend spline keys for (int j = 0; j < node->getChildCount(); j++) { XmlNodeRef keyNode = node->getChild(j); float time, value; if (keyNode->isTag("Key") && keyNode->getAttr("time", time) && keyNode->getAttr("value", value)) { spline->InsertKeyFloat(time, value); } else { CryWarning(VALIDATOR_MODULE_3DENGINE, VALIDATOR_WARNING, "Post effect group '%s' blend spline key must be of form ", name); } } // Set blend curve type const char* curveAttr = node->getAttr("curve"); ESplineKeyTangentType curveType = SPLINE_KEY_TANGENT_NONE; if (strcmp(curveAttr, "linear") == 0) { curveType = SPLINE_KEY_TANGENT_LINEAR; } else if (strcmp(curveAttr, "step") == 0) { curveType = SPLINE_KEY_TANGENT_STEP; } else if (strcmp(curveAttr, "") != 0 && strcmp(curveAttr, "smooth") != 0) { CryWarning(VALIDATOR_MODULE_3DENGINE, VALIDATOR_WARNING, "Post effect group '%s' %s spline has unrecognized curve '%s'. Expecting 'smooth', 'linear', or 'step'.", name, node->getTag(), curveAttr); } for (int j = 0; j < spline->GetKeyCount(); j++) { spline->SetKeyFlags(j, (curveType << SPLINE_KEY_TANGENT_IN_SHIFT) | (curveType << SPLINE_KEY_TANGENT_OUT_SHIFT)); } continue; } CryWarning(VALIDATOR_MODULE_3DENGINE, VALIDATOR_WARNING, "Unrecognized XML tag in post effect group '%s'", name); } return group; } void PostEffectGroupManager::OnCatalogAssetChanged(const AZ::Data::AssetId& assetId) { for (auto& group : m_groups) { if (group->GetAssetId() == assetId) { // Reload this asset. Sending in the pointer will cause LoadGroup to load in place. LoadGroup(group->GetName(), group.get()); return; } } } void PostEffectGroupManager::BlendWithParameterCache() { AZ_TRACE_METHOD(); for (auto& param : m_paramCache) { param.second = '\0'; } for (auto& group : m_groups) { group->BlendWith(m_paramCache); group->ResetStrength(); } } void PostEffectGroupManager::ClearParameterCache() { m_paramCache.clear(); } void PostEffectGroupManager::SyncMainWithRender() { AZ_TRACE_METHOD(); if (gEnv->IsEditor()) { BlendWithParameterCache(); } // Flip our buffers and clear m_fillThreadIndex = (m_fillThreadIndex + 1) & 1; m_groupsToggledThisFrame[m_fillThreadIndex].clear(); SyncVisitor syncVisitor; for (auto& param : m_paramCache) { syncVisitor.SetName(param.first); AZStd::visit(syncVisitor, param.second); } // Swap buffers in lower level system gEnv->pRenderer->SyncPostEffects(); } void PostEffectGroupManager::Sort() { // Sort effect groups by priority then time updated, so that they'll be blended in that order std::sort(m_groups.begin(), m_groups.end(), [](const std::unique_ptr& a, const std::unique_ptr& b) { if (a->GetPriority() == b->GetPriority()) { return a->GetLastUpdateFrame() < b->GetLastUpdateFrame(); } return a->GetPriority() < b->GetPriority(); }); } const PostEffectGroupList& PostEffectGroupManager::GetGroupsToggledThisFrame() { unsigned int processThreadIndex = (m_fillThreadIndex + 1) & 1; return m_groupsToggledThisFrame[processThreadIndex]; } void PostEffectGroupManager::SetGroupToggledThisFrame( IPostEffectGroup* group ) { m_groupsToggledThisFrame[m_fillThreadIndex].push_back(group); }