diff --git a/doc/source/eve/generic-pp-effects.rst b/doc/source/eve/generic-pp-effects.rst new file mode 100644 index 000000000..1f53b2f80 --- /dev/null +++ b/doc/source/eve/generic-pp-effects.rst @@ -0,0 +1,67 @@ +Custom Post-Processing Effects +=============================== + +Trinity supports having custom post-processing effects that can be applied to the rendered scene. These effects are specified in post-process volume +objects (see Tr2PostProcessAttributes and EveChildPostProcessVolume). The Tr2PostProcessAttributes maintains a list `genericEffects` that contains +custom post-processing effects (of type `Tr2PPGenericEffect`). Each of these represents a custom post-processing pass. + +`Tr2PPGenericEffect` objects have members: + +- `effect`: The shader effect used for this post-processing pass. +- `quality`: Minimal post-processing quality setting when this effect is visible. +- `execututionSlot`: Place in the post-processing pipeline where this effect should be executed. +- `order`: The order in which this effect should be applied relative to other effects in the same execution slot. + +Effect Files (Shaders) +---------------------- + +Effect files (.fx) for custom post-processing effects are used to define the shader programs that implement the visual effects. These effects need +to have a `"Main"` technique defined, which serves as the entry point for the post-processing effect. Trinity will render a quad using this technique +to apply the effect to the scene. + +Trinity will pass pre-transform position (in clip space) and texture coordinates (0 to 1) to the vertex shader of the `"Main"` technique: + +``` +struct PostProcessVertex +{ + float4 pos : POSITION; + float2 texCoord : TEXCOORD0; +}; +``` + +The effect may use the the output of the previous post-processing step as the input texture. For that, the shader needs to define a texture 2D object +with the name `"Blit"`. It is possible to have a shader that does not use the previous post-processing output, in which case the Trinity will render +the effect into the output of the previous pass directly. This can be used for overlay/additive effects. + +The effect may use other scene-wide textures, like `DepthMap` or `VelocityMap`. Note that currently, there is no guarantee that these textures will always be +available, especially at the execution slots later in the pipeline. Normally, `DepthMap` is safe to use at most stages, but `VelocityMap` may not be available +after tonemapping, or with certain rendering settings. + +The effect may expose parameters that can be adjusted by post-processing based on the volume intensity/relative weight. Such parameters need to have +floating point type (e.g., `float`, `float2`, `float3`, `float4`) and have `bool IsBlendable = true;` annotation. When rendering the post-processing effect, +Trinity will blend these parameters based on the volume intensity/relative weight. For each effect instanced it will interpolate such parameters between their +default values, specified in HLSL code and the values specified in the effect's parameter object. + + +Blending and Grouping +--------------------- + +Generally, Trinity treats each custom post-processing effect as an individual pass. This means that different effects are applied separately, +without any automatic blending or overriding each other. + +For identical effects, Trinity tries to group them together to optimize rendering. Effects that share the same shader (along with all the non-blendable +material parameters), execution slot and quality threshold are merged together, and blended as a single post-processing effect. When custom effects are +blended together, all of their blendable parameters are blended using the normal rules similar to any other post-processing attribute. + + +Execution Slots +--------------- + +Execution slots determine the place in the post-processing pipeline where the effect should be executed: + +- `BEFORE_UPSCALING`: The effect is applied before the upscaling step or TAA in the rendering pipeline, after legacy fog and godray passes. Note, that the + input Src texture and the required output are in linear HDR color space. +- `AFTER_TONEMAP`: The effect is applied after the tonemapping step in the rendering pipeline. The input Src texture and the required output are in the + final color space, typically sRGB. Note that the scene-wide textures like `DepthMap` and `VelocityMap` may not be available at this stage, and if they + are, they may be of a different resolution that the target one, and may have camera jitter applied. + diff --git a/trinity/PostProcess/Effects/Tr2PPGenericEffect.cpp b/trinity/PostProcess/Effects/Tr2PPGenericEffect.cpp index f76d4c00a..286a1e470 100644 --- a/trinity/PostProcess/Effects/Tr2PPGenericEffect.cpp +++ b/trinity/PostProcess/Effects/Tr2PPGenericEffect.cpp @@ -1,13 +1,359 @@ // Copyright © 2026 CCP ehf. #include "Tr2PPGenericEffect.h" +#include "../Tr2PostProcessAttributes.h" +#include "../Shader/Tr2Effect.h" +#include "../Shader/Tr2Shader.h" +#include "../Shader/Parameter/Tr2FloatParameter.h" +#include "../Shader/Parameter/Tr2Vector2Parameter.h" +#include "../Shader/Parameter/Tr2Vector3Parameter.h" +#include "../Shader/Parameter/Tr2Vector4Parameter.h" -Tr2PPGenericEffect::Tr2PPGenericEffect( IRoot* lockobj ) : - m_quality( PostProcess::Quality::MEDIUM ) + +namespace +{ + +struct EffectInstance +{ + Tr2PPGenericEffect* effect; + PostProcessEnums::Priority priority; + float intensity; +}; + +struct EffectBucket +{ + EffectBucket() = default; + EffectBucket( Tr2PPGenericEffect* effect, PostProcessEnums::Priority priority, float intensity ) + { + instance.effect = effect; + effects.push_back( EffectInstance{ effect, priority, intensity } ); + for( auto& param : effect->blendableFloatParameters ) + { + instance.floatValues.push_back( param.defaultValue ); + } + for( auto& param : effect->blendableVector2Parameters ) + { + instance.vector2Values.push_back( param.defaultValue ); + } + for( auto& param : effect->blendableVector3Parameters ) + { + instance.vector3Values.push_back( param.defaultValue ); + } + for( auto& param : effect->blendableVector4Parameters ) + { + instance.vector4Values.push_back( param.defaultValue ); + } + } + + void Accumulate( const Tr2PPGenericEffect& effect, float weight ) + { + auto AccumulateValues = [&]( auto& params, auto& values ) { + for( auto& param : params ) + { + auto value = ( param.parameter->GetValue() - param.defaultValue ) * weight; + values[¶m - params.data()] += value; + } + }; + AccumulateValues( effect.blendableFloatParameters, instance.floatValues ); + AccumulateValues( effect.blendableVector2Parameters, instance.vector2Values ); + AccumulateValues( effect.blendableVector3Parameters, instance.vector3Values ); + AccumulateValues( effect.blendableVector4Parameters, instance.vector4Values ); + } + + Tr2AccumulatedGenericEffects::GenericEffectInstance instance; + std::vector effects; +}; + +/** + Partition effects into buckets of compatible effects that can be blended together. Each bucket contains a list of effects that can be merged, + along with the accumulated parameters for the bucket. */ +std::vector PopulateBuckets( Tr2PostProcess2& postprocess, std::vector& sources ) +{ + std::vector buckets; + + if( auto deprecated = postprocess.GetGenericEffectIfAvailable() ) + { + if( deprecated->IsActive() ) + { + deprecated->UpdateEffectParameters(); + if( deprecated->IsValid() ) + { + buckets.emplace_back( deprecated, PostProcessEnums::SCENE_DEFAULT_PRIORITY, 1.0f ); + } + } + } + + for( auto& src : sources ) + { + if( src->intensity <= 0 ) + { + continue; + } + + for( auto& effect : src->genericEffects ) + { + if( !effect || !effect->IsActive() ) + { + continue; + } + effect->UpdateEffectParameters(); + if( !effect->IsValid() ) + { + continue; + } + auto found = std::find_if( buckets.begin(), buckets.end(), [&]( const EffectBucket& bucket ) { + return effect->CanBeMerged( *bucket.effects.front().effect ); + } ); + if( found == buckets.end() ) + { + buckets.emplace_back( effect, src->priority, src->intensity ); + } + else + { + found->effects.push_back( EffectInstance{ effect, src->priority, src->intensity } ); + } + } + } + return buckets; +} + +/// Checks if the constant is a float# type parameter with IsBlendable annotation. Only such parameters can be blended together. +bool IsBlendableParameter( const Tr2EffectConstant& constant, const Tr2Shader& shader ) +{ + if( constant.type != Tr2EffectConstant::FLOAT || constant.elements > 1 || constant.dimension < 1 || constant.dimension > 4 ) + { + return false; + } + if( auto annotations = shader.GetParameterAnnotations( constant.name.c_str() ) ) + { + auto found = std::find_if( annotations->begin(), annotations->end(), []( const Tr2EffectParameterAnnotation& annotation ) { + return strcmp( annotation.name, Tr2PPGenericEffect::IsBlendableAnnotationName ) == 0; + } ); + return found != annotations->end() && found->type == Tr2EffectParameterAnnotation::BOOL && found->boolValue; + } + return false; +} + +} + +Tr2PPGenericEffect::Tr2PPGenericEffect( IRoot* ) +{ +} + +void Tr2PPGenericEffect::UpdateEffectParameters() +{ + if( !m_effect ) + { + return; + } + auto shader = m_effect->GetShaderStateInterface(); + if( !shader ) + { + return; + } + auto hash = m_effect->GetHashValue(); + if( hash == m_lastHashValue && m_lastShader == shader ) + { + return; + } + m_lastHashValue = hash; + m_lastShader = shader; + m_nonBlendableHash = m_effect->GetNonBlendableHashValue(); + blendableFloatParameters.clear(); + blendableVector2Parameters.clear(); + blendableVector3Parameters.clear(); + blendableVector4Parameters.clear(); + m_requiresSourceTexture = false; + + auto& desc = shader->GetEffectDescription(); + for( auto& technique : desc.techniques ) + { + for( auto& pass : technique.passes ) + { + for( auto& stage : pass.stageInputs ) + { + for( auto& constant : stage.constants ) + { + if( !IsBlendableParameter( constant, *shader ) ) + { + continue; + } + AddBlendableParameter( constant, reinterpret_cast( stage.constantValues ) ); + } + if( !m_requiresSourceTexture ) + { + m_requiresSourceTexture = find_if( stage.resources.begin(), stage.resources.end(), []( const auto& texture ) { return strcmp( texture.second.name, SourceTextureName ) == 0; } ) != stage.resources.end(); + } + } + } + } + std::sort( blendableFloatParameters.begin(), blendableFloatParameters.end(), []( const auto& a, const auto& b ) { return a.parameter->m_name < b.parameter->m_name; } ); + std::sort( blendableVector2Parameters.begin(), blendableVector2Parameters.end(), []( const auto& a, const auto& b ) { return a.parameter->m_name < b.parameter->m_name; } ); + std::sort( blendableVector3Parameters.begin(), blendableVector3Parameters.end(), []( const auto& a, const auto& b ) { return a.parameter->m_name < b.parameter->m_name; } ); + std::sort( blendableVector4Parameters.begin(), blendableVector4Parameters.end(), []( const auto& a, const auto& b ) { return a.parameter->m_name < b.parameter->m_name; } ); +} + +bool Tr2PPGenericEffect::IsValid() const { + return m_effect && m_effect->GetShaderStateInterface(); } -Tr2EffectPtr Tr2PPGenericEffect::GetEffect() const +bool Tr2PPGenericEffect::CanBeMerged( const Tr2PPGenericEffect& other ) const { - return m_effect; + if( m_executionSlot != other.m_executionSlot || m_quality != other.m_quality ) + { + return false; + } + if( m_effect->GetShaderStateInterface() != other.m_effect->GetShaderStateInterface() ) + { + return false; + } + return m_nonBlendableHash == other.m_nonBlendableHash; } + +bool Tr2PPGenericEffect::RequiresSourceTexture() const +{ + return m_requiresSourceTexture; +} + +void Tr2PPGenericEffect::AddBlendableParameter( const Tr2EffectConstant& constant, const uint8_t* defaultValues ) +{ + auto AddParameter = [&]( auto& blendableParameters ) { + using Element = typename std::decay_t::value_type; + using ParamType = typename Element::ParameterType; + using ValueType = typename Element::ValueType; + ValueType defaultValue; + memcpy( &defaultValue, defaultValues + constant.offset, sizeof( ValueType ) ); + BluePtr param = BlueCastPtr( m_effect->GetParameterByName( constant.name.c_str() ) ); + if( !param ) + { + m_effect->SetParameter( constant.name, defaultValue ); + param = BlueCastPtr( m_effect->GetParameterByName( constant.name.c_str() ) ); + if( !param ) + { + return; + } + } + blendableParameters.push_back( { param, defaultValue } ); + }; + + switch( constant.dimension ) + { + case 1: + AddParameter( blendableFloatParameters ); + break; + case 2: + AddParameter( blendableVector2Parameters ); + break; + case 3: + AddParameter( blendableVector3Parameters ); + break; + case 4: + AddParameter( blendableVector4Parameters ); + break; + default: + break; + } +} + +void Tr2AccumulatedGenericEffects::GenericEffectInstance::SetParameters() +{ + for( auto& param : effect->blendableFloatParameters ) + { + auto value = param.parameter->GetValue(); + std::swap( floatValues[¶m - effect->blendableFloatParameters.data()], value ); + param.parameter->SetValue( value ); + } + for( auto& param : effect->blendableVector2Parameters ) + { + auto value = param.parameter->GetValue(); + std::swap( vector2Values[¶m - effect->blendableVector2Parameters.data()], value ); + param.parameter->SetValue( value ); + } + for( auto& param : effect->blendableVector3Parameters ) + { + auto value = param.parameter->GetValue(); + std::swap( vector3Values[¶m - effect->blendableVector3Parameters.data()], value ); + param.parameter->SetValue( value ); + } + for( auto& param : effect->blendableVector4Parameters ) + { + auto value = param.parameter->GetValue(); + std::swap( vector4Values[¶m - effect->blendableVector4Parameters.data()], value ); + param.parameter->SetValue( value ); + } +} + +void Tr2AccumulatedGenericEffects::GenericEffectInstance::RestoreParameters() +{ + SetParameters(); +} + + + + +void AccumulateGenericEffects( Tr2PostProcess2& postprocess, std::vector& sources ) +{ + std::vector buckets = PopulateBuckets( postprocess, sources ); + + for( auto& bucket : buckets ) + { + float remainingWeight = 1.0f; + + std::sort( bucket.effects.begin(), bucket.effects.end(), []( const EffectInstance& a, const EffectInstance& b ) { + return a.priority > b.priority; + } ); + + for( auto it = begin( bucket.effects ); it != end( bucket.effects ); ) + { + // figure out the range of sources with the same priority + auto jt = it; + while( jt != end( bucket.effects ) && ( *jt ).priority == ( *it ).priority ) + { + ++jt; + } + + float totalPriorityIntensity = 0.0f; + for( auto kt = it; kt != jt; ++kt ) + { + totalPriorityIntensity += kt->intensity; + } + if( totalPriorityIntensity == 0.0f ) + { + it = jt; + continue; + } + + float normalizationFactor = 1.f / std::max( totalPriorityIntensity, 1.0f ) * remainingWeight; + + for( auto kt = it; kt != jt; ++kt ) + { + float weight = kt->intensity * normalizationFactor; + bucket.Accumulate( *kt->effect, weight ); + } + + remainingWeight -= totalPriorityIntensity; + it = jt; + if( remainingWeight <= 0 ) + { + break; + } + } + } + + for( auto& effects : postprocess.m_genericEffects.effects ) + { + effects.clear(); + } + + for( auto& bucket : buckets ) + { + postprocess.m_genericEffects.effects[bucket.instance.effect->m_executionSlot].push_back( bucket.instance ); + } + for( auto& each : postprocess.m_genericEffects.effects ) + { + std::sort( each.begin(), each.end(), []( const auto& a, const auto& b ) { + return a.effect->m_order < b.effect->m_order; + } ); + } +} \ No newline at end of file diff --git a/trinity/PostProcess/Effects/Tr2PPGenericEffect.h b/trinity/PostProcess/Effects/Tr2PPGenericEffect.h index 429beeb83..ac9f7b66d 100644 --- a/trinity/PostProcess/Effects/Tr2PPGenericEffect.h +++ b/trinity/PostProcess/Effects/Tr2PPGenericEffect.h @@ -5,6 +5,13 @@ #include "PostProcess/Effects/Tr2PPEffect.h" BLUE_DECLARE( Tr2Effect ); +BLUE_DECLARE( Tr2Shader ); +BLUE_DECLARE( Tr2FloatParameter ); +BLUE_DECLARE( Tr2Vector2Parameter ); +BLUE_DECLARE( Tr2Vector3Parameter ); +BLUE_DECLARE( Tr2Vector4Parameter ); +BLUE_DECLARE( Tr2PostProcessAttributes ); +BLUE_DECLARE( Tr2PostProcess2 ); BLUE_CLASS( Tr2PPGenericEffect ) : public Tr2PPEffect @@ -14,11 +21,72 @@ BLUE_CLASS( Tr2PPGenericEffect ) : Tr2PPGenericEffect( IRoot* lockobj = NULL ); - Tr2EffectPtr GetEffect() const; + void UpdateEffectParameters(); + bool IsValid() const; + bool CanBeMerged( const Tr2PPGenericEffect& other ) const; + bool RequiresSourceTexture() const; - PostProcess::Quality m_quality; + /// Effect used for post-processing. + Tr2EffectPtr m_effect; + /// Post-processing quality threshold for this effect. If the current post-processing quality is lower than this value, the effect will not be executed. + PostProcess::Quality m_quality = PostProcess::LOW; + + enum ExecutionSlot + { + BEFORE_UPSCALING, + AFTER_TONEMAP, + }; + static constexpr size_t ExecutionSlotCount = 2; + + /// Place in the post-processing pipeline where this effect should be executed. + ExecutionSlot m_executionSlot = BEFORE_UPSCALING; + /// Order of execution for this effect in relation to other generic effects. Lower values are executed first. + int32_t m_order = 0; + + template + struct BlendableParameter + { + using ParameterType = Param; + using ValueType = Value; + + BluePtr parameter; + Value defaultValue; + }; + + std::vector> blendableFloatParameters; + std::vector> blendableVector2Parameters; + std::vector> blendableVector3Parameters; + std::vector> blendableVector4Parameters; + + static constexpr const char* IsBlendableAnnotationName = "IsBlendable"; + static constexpr const char* SourceTextureName = "Blit"; private: - Tr2EffectPtr m_effect; + void AddBlendableParameter( const Tr2EffectConstant& constant, const uint8_t* defaultValues ); + + unsigned m_lastHashValue = 0; + unsigned m_nonBlendableHash = 0; + Tr2ShaderPtr m_lastShader; + bool m_requiresSourceTexture = false; }; TYPEDEF_BLUECLASS( Tr2PPGenericEffect ); + + +struct Tr2AccumulatedGenericEffects +{ + struct GenericEffectInstance + { + void SetParameters(); + void RestoreParameters(); + + Tr2PPGenericEffectPtr effect; + std::vector floatValues; + std::vector vector2Values; + std::vector vector3Values; + std::vector vector4Values; + }; + + std::array, Tr2PPGenericEffect::ExecutionSlotCount> effects; +}; + +void AccumulateGenericEffects( Tr2PostProcess2& postprocess, std::vector& sources ); \ No newline at end of file diff --git a/trinity/PostProcess/Effects/Tr2PPGenericEffect_Blue.cpp b/trinity/PostProcess/Effects/Tr2PPGenericEffect_Blue.cpp index 5b4fa2822..9ca3e13df 100644 --- a/trinity/PostProcess/Effects/Tr2PPGenericEffect_Blue.cpp +++ b/trinity/PostProcess/Effects/Tr2PPGenericEffect_Blue.cpp @@ -3,6 +3,22 @@ #include "StdAfx.h" #include "Tr2PPGenericEffect.h" +namespace PostProcessEnums +{ +Be::VarChooser Tr2PostProcessExecutionSlotChooser[] = { + + { "BEFORE_UPSCALING", + BeCast( Tr2PPGenericEffect::BEFORE_UPSCALING ), + "Render effect before upscaling" }, + { "AFTER_TONEMAP", + BeCast( Tr2PPGenericEffect::AFTER_TONEMAP ), + "Render effect after tonemapping" }, + { 0 } +}; +BLUE_REGISTER_ENUM_EX( "Tr2PostProcessExecutionSlot", Tr2PPGenericEffect::ExecutionSlot, Tr2PostProcessExecutionSlotChooser, ENUM_REG_ENUM_OBJECT_ON_MODULE ); +} + + BLUE_DEFINE( Tr2PPGenericEffect ); @@ -17,6 +33,8 @@ const Be::ClassInfo* Tr2PPGenericEffect::ExposeToBlue() m_effect, "The effect to use. The Tr2PostProcessRenderer passes the pre upscaled source into the Blit texture parameter of this effect", Be::READWRITE | Be::PERSIST ) + MAP_ATTRIBUTE_WITH_CHOOSER( "executionSlot", m_executionSlot, "The execution slot for this effect", Be::READWRITE | Be::PERSIST | Be::ENUM, PostProcessEnums::Tr2PostProcessExecutionSlotChooser ); + MAP_ATTRIBUTE( "order", m_order, "Affects the order of rendering for this effect in relation to other generic effects", Be::READWRITE | Be::PERSIST ); EXPOSURE_CHAINTO( Tr2PPEffect ) } diff --git a/trinity/PostProcess/Tr2PostProcess2.h b/trinity/PostProcess/Tr2PostProcess2.h index 06a76932c..c76c7c136 100644 --- a/trinity/PostProcess/Tr2PostProcess2.h +++ b/trinity/PostProcess/Tr2PostProcess2.h @@ -87,6 +87,8 @@ BLUE_CLASS( Tr2PostProcess2 ) : float m_exposureAdjustment = 0; + Tr2AccumulatedGenericEffects m_genericEffects; + private: Tr2PPSignalLossEffectPtr m_signalLoss; Tr2PPGodRaysEffectPtr m_godRays; diff --git a/trinity/PostProcess/Tr2PostProcessAttributes.cpp b/trinity/PostProcess/Tr2PostProcessAttributes.cpp index 7d28aa3e2..ade1068b3 100644 --- a/trinity/PostProcess/Tr2PostProcessAttributes.cpp +++ b/trinity/PostProcess/Tr2PostProcessAttributes.cpp @@ -48,7 +48,8 @@ Tr2PostProcessAttributes::Tr2PostProcessAttributes( IRoot* lockobj ) : depthOfFieldFocalDistance( Attribute( 0.0f ) ), depthOfFieldFocalLength( Attribute( 0.0f ) ), depthOfFieldShape( Attribute( Tr2Bokeh::Disk ) ), - depthOfFieldForegroundBlurNeeded( Attribute( false ) ) + depthOfFieldForegroundBlurNeeded( Attribute( false ) ), + PARENTLOCK( genericEffects ) { } @@ -146,6 +147,8 @@ void Tr2PostProcessAttributes::MergeInto( Tr2PostProcess2& postprocess, std::vec auto colorGain = Accumulate( &Tr2PostProcessAttributes::colorGain, sources, debugObserver ); auto colorOffset = Accumulate( &Tr2PostProcessAttributes::colorOffset, sources, debugObserver ); + AccumulateGenericEffects( postprocess, sources ); + postprocess.SetBloom( nullptr ); postprocess.SetDesaturate( nullptr ); postprocess.SetFade( nullptr ); diff --git a/trinity/PostProcess/Tr2PostProcessAttributes.h b/trinity/PostProcess/Tr2PostProcessAttributes.h index 09a5a5096..7cf957c29 100644 --- a/trinity/PostProcess/Tr2PostProcessAttributes.h +++ b/trinity/PostProcess/Tr2PostProcessAttributes.h @@ -10,6 +10,9 @@ BLUE_DECLARE( Tr2PostProcessAttributes ); BLUE_DECLARE_VECTOR( Tr2PostProcessAttributes ); +BLUE_DECLARE( Tr2PPGenericEffect ); +BLUE_DECLARE_VECTOR( Tr2PPGenericEffect ); + BLUE_CLASS( Tr2PostProcessAttributes ) : public IRoot { @@ -102,6 +105,8 @@ BLUE_CLASS( Tr2PostProcessAttributes ) : PriorityBlend::Attribute colorGamma = 1.f; PriorityBlend::Attribute colorGain = Vector3( 1, 1, 1 ); PriorityBlend::Attribute colorOffset = Vector3( 0, 0, 0 ); + + PTr2PPGenericEffectVector genericEffects; }; TYPEDEF_BLUECLASS( Tr2PostProcessAttributes ); diff --git a/trinity/PostProcess/Tr2PostProcessAttributes_Blue.cpp b/trinity/PostProcess/Tr2PostProcessAttributes_Blue.cpp index 51ede9e65..eba483f19 100644 --- a/trinity/PostProcess/Tr2PostProcessAttributes_Blue.cpp +++ b/trinity/PostProcess/Tr2PostProcessAttributes_Blue.cpp @@ -114,5 +114,7 @@ const Be::ClassInfo* Tr2PostProcessAttributes::ExposeToBlue() POSTPROCESSATTRIBUTE_DEFINE( colorGain, Color Correction, "\n:jessica-numeric-range: (0.0, 2.0)" ) POSTPROCESSATTRIBUTE_DEFINE( colorOffset, Color Correction, "\n:jessica-numeric-range: (0.0, 2.0)" ) + MAP_ATTRIBUTE( "genericEffects", genericEffects, "List of generic post-processing effects", Be::READ | Be::PERSIST ) + EXPOSURE_END() } \ No newline at end of file diff --git a/trinity/PostProcess/Tr2PostProcessRenderer.cpp b/trinity/PostProcess/Tr2PostProcessRenderer.cpp index cd759a319..c084c0ea8 100644 --- a/trinity/PostProcess/Tr2PostProcessRenderer.cpp +++ b/trinity/PostProcess/Tr2PostProcessRenderer.cpp @@ -701,11 +701,6 @@ void Tr2PostProcessRenderer::Execute( if( postProcess != nullptr ) { - if( auto genericEffect = postProcess->GetGenericEffectIfAvailable( m_quality ) ) - { - RenderGenericEffect( nonMsaaSource, sourceBuffer, renderContext, genericEffect ); - } - if( auto fog = postProcess->GetFogIfAvailable( m_quality ) ) { RenderFog( nonMsaaSource, sourceBuffer, gpuResourcePool, renderContext, fog ); @@ -717,6 +712,8 @@ void Tr2PostProcessRenderer::Execute( RenderGodRays( nonMsaaSource, depthMap, gpuResourcePool, renderContext, godrays ); } + nonMsaaSource = RenderGenericEffects( postProcess->m_genericEffects.effects[Tr2PPGenericEffect::BEFORE_UPSCALING], nonMsaaSource, gpuResourcePool, renderContext ); + if( auto dof = postProcess->GetDepthOfFieldIfAvailable( m_quality ) ) { bool temporal = upscalingInfo.temporal || postProcess->GetTaaIfAvailable( m_quality ) != nullptr; @@ -806,10 +803,12 @@ void Tr2PostProcessRenderer::Execute( RenderTonemapping( tonemappedOutput, postProcess, renderContext ); output = RenderUpscaling( tonemappedOutput, depthMap, velocity, opaqueColor, scene->GetReprojectionMatrix(), gpuResourcePool, renderContext, upscalingContext, dynamicExposure ); - depthMap = {}; - velocity = {}; - opaqueColor = {}; - + if( !postProcess || postProcess->m_genericEffects.effects[Tr2PPGenericEffect::AFTER_TONEMAP].empty() ) + { + depthMap = {}; + velocity = {}; + opaqueColor = {}; + } // need to reset the perframedata so we have the correct viewport size etc scene->ApplyUpscalingToPerFrameData( displaySize.width, displaySize.height, renderContext ); } @@ -818,6 +817,14 @@ void Tr2PostProcessRenderer::Execute( RenderTonemapping( output, postProcess, renderContext ); } + { + auto newOutput = RenderGenericEffects( postProcess->m_genericEffects.effects[Tr2PPGenericEffect::AFTER_TONEMAP], output, gpuResourcePool, renderContext ); + if( !( newOutput.Get() == output.Get() ) ) + { + DrawInto( output, Tr2LoadAction::DONT_CARE, newOutput, renderContext ); + } + } + renderContext.m_esm.SetRenderTarget( 0, destination ); if( filmGrain != nullptr ) { @@ -831,6 +838,14 @@ void Tr2PostProcessRenderer::Execute( else { RenderTonemapping( output, postProcess, renderContext ); + { + auto newOutput = RenderGenericEffects( postProcess->m_genericEffects.effects[Tr2PPGenericEffect::AFTER_TONEMAP], output, gpuResourcePool, renderContext ); + if( !( newOutput.Get() == output.Get() ) ) + { + DrawInto( output, Tr2LoadAction::DONT_CARE, newOutput, renderContext ); + } + } + Tr2Renderer::DrawTexture( renderContext, output ); } @@ -1582,19 +1597,6 @@ void Tr2PostProcessRenderer::RenderTonemapping( DrawInto( dest, Tr2LoadAction::DONT_CARE, m_tonemappingEffect, renderContext ); } -void Tr2PostProcessRenderer::RenderGenericEffect( const Tr2TextureAL& dest, const Tr2TextureAL& src, Tr2RenderContext& renderContext, Tr2PPGenericEffectPtr genericEffect ) -{ - Tr2EffectPtr effect = genericEffect->GetEffect(); - if( effect != nullptr ) - { - GPU_REGION( renderContext, "GenericEffect" ); - renderContext.m_esm.ApplyStandardStates( Tr2EffectStateManager::RM_FULLSCREEN ); - - TEMP_PARAM( effect, "Blit", src ); - DrawInto( dest, Tr2LoadAction::DONT_CARE, effect, renderContext ); - } -} - void Tr2PostProcessRenderer::RenderDepthOfField( const Tr2TextureAL& dest, Tr2GpuResourcePool& gpuResourcePool, Tr2RenderContext& renderContext, Tr2PPDepthOfFieldEffect* depthOfField, bool temporal, float upscalingAmount ) { GPU_REGION( renderContext, "DepthOfField" ); @@ -1712,3 +1714,33 @@ Tr2GpuResourcePool::Texture Tr2PostProcessRenderer::GetBlackTexture( Tr2GpuResou Tr2SubresourceData initData = { blackColor, 4 * sizeof( uint32_t ), 4 * 4 * sizeof( uint32_t ) }; return gpuResourcePool.GetPersistentTexture( "Black", 4, 4, Tr2RenderContextEnum::PIXEL_FORMAT_B8G8R8A8_UNORM, Tr2GpuUsage::SHADER_RESOURCE, &initData ); } + +Tr2GpuResourcePool::Texture Tr2PostProcessRenderer::RenderGenericEffects( std::vector& effects, const Tr2GpuResourcePool::Texture& src, Tr2GpuResourcePool& gpuResourcePool, Tr2RenderContext& renderContext ) const +{ + auto effectSrc = src; + for( auto& genericEffect : effects ) + { + if( genericEffect.effect->m_quality > m_quality ) + { + continue; + } + + GPU_REGION( renderContext, "GenericEffect" ); + renderContext.m_esm.ApplyStandardStates( Tr2EffectStateManager::RM_FULLSCREEN ); + + genericEffect.SetParameters(); + if( genericEffect.effect->RequiresSourceTexture() ) + { + auto dest = gpuResourcePool.GetTempTexture( "", src->GetWidth(), src->GetHeight(), src->GetFormat(), RENDER_TARGET ); + TEMP_PARAM( genericEffect.effect->m_effect, "Blit", effectSrc ); + DrawInto( dest, Tr2LoadAction::DONT_CARE, genericEffect.effect->m_effect, renderContext ); + effectSrc = dest; + } + else + { + DrawInto( effectSrc, Tr2LoadAction::LOAD, genericEffect.effect->m_effect, renderContext ); + } + genericEffect.RestoreParameters(); + } + return effectSrc; +} \ No newline at end of file diff --git a/trinity/PostProcess/Tr2PostProcessRenderer.h b/trinity/PostProcess/Tr2PostProcessRenderer.h index 6c296ade0..cfa7214ce 100644 --- a/trinity/PostProcess/Tr2PostProcessRenderer.h +++ b/trinity/PostProcess/Tr2PostProcessRenderer.h @@ -230,8 +230,6 @@ BLUE_CLASS( Tr2PostProcessRenderer ) : Tr2PostProcess2* activePostProcess, Tr2RenderContext& renderContext ); - void RenderGenericEffect( const Tr2TextureAL& dest, const Tr2TextureAL& src, Tr2RenderContext& renderContext, Tr2PPGenericEffectPtr genericEffect ); - // General PostProcess::Quality m_quality; @@ -248,6 +246,8 @@ BLUE_CLASS( Tr2PostProcessRenderer ) : Tr2GpuResourcePool::Texture Blur( Tr2GpuResourcePool::Texture src, Tr2GpuResourcePool & gpuResourcePool, Tr2RenderContext & renderContext, const PostProcessBlur::BlurContext& blurContext ); Tr2GpuResourcePool::Texture DownSampleDepth( const Tr2TextureAL& depth, Tr2GpuResourcePool& gpuResourcePool, Tr2RenderContext& renderContext ); + [[nodiscard]] Tr2GpuResourcePool::Texture RenderGenericEffects( std::vector & effects, const Tr2GpuResourcePool::Texture& src, Tr2GpuResourcePool& gpuResourcePool, Tr2RenderContext& renderContext ) const; + Tr2EffectPtr m_downsampleDepthEffect; std::map> m_blurEffects; diff --git a/trinity/Shader/Tr2Effect.cpp b/trinity/Shader/Tr2Effect.cpp index ec59b317a..014d6bad1 100644 --- a/trinity/Shader/Tr2Effect.cpp +++ b/trinity/Shader/Tr2Effect.cpp @@ -1549,6 +1549,39 @@ unsigned Tr2Effect::GetHashValue() const return hash; } +uint32_t Tr2Effect::GetNonBlendableHashValue() const +{ + unsigned hash = 0; + if( m_effectResource ) + { + hash = CcpHashFNV1( m_effectResource->GetPath(), wcslen( m_effectResource->GetPath() ) * sizeof( wchar_t ) ); + } + for( auto& option : m_options ) + { + hash = CcpHashFNV1( &option, sizeof( option ), hash ); + } + for( auto it = m_constParameters.begin(); it != m_constParameters.end(); ++it ) + { + auto name = it->name.c_str(); + // looks scary, but it's a constant string, so the pointer is unique + hash = CcpHashFNV1( &name, sizeof( name ), hash ); + hash = CcpHashFNV1( &it->value, sizeof( it->value ), hash ); + } + for( auto it = m_parameters.begin(); it != m_parameters.end(); ++it ) + { + if( GetBool( m_shader, ( *it )->GetParameterName(), "IsBlendable", false ) ) + { + continue; + } + hash = ( *it )->GetHashValue( hash ); + } + for( auto it = m_resources.begin(); it != m_resources.end(); ++it ) + { + hash = ( *it )->GetHashValue( hash ); + } + return hash; +} + ITriEffectParameter* Tr2Effect::FindParameterByName( const char* name ) const { CCP_STATS_ZONE( __FUNCTION__ ); diff --git a/trinity/Shader/Tr2Effect.h b/trinity/Shader/Tr2Effect.h index a2aeb33f3..30f824b30 100644 --- a/trinity/Shader/Tr2Effect.h +++ b/trinity/Shader/Tr2Effect.h @@ -116,6 +116,7 @@ BLUE_CLASS( Tr2Effect ) : void Render( IRenderCallback * cb, Tr2RenderContext & renderContext ); unsigned GetHashValue() const; + uint32_t GetNonBlendableHashValue() const; const Tr2ConstantEffectParameter* GetConstParameters( size_t& count ) const; ITriEffectParameter* GetParameterByName( const char* name ) const;