diff --git a/CMakeLists.txt b/CMakeLists.txt index 6ca87dde74..4ae74971ee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2073,4 +2073,12 @@ if(BUILD_UNIT_TESTS) target_include_directories(unit_macros PRIVATE ${CMAKE_SOURCE_DIR}/src) add_test(NAME unit_macros COMMAND unit_macros) set_tests_properties(unit_macros PROPERTIES LABELS "unit;validation") + + add_executable(unit_vk_postfx_sanitize + tests/unit/test_vk_postfx_sanitize.c + src/renderers/vulkan/vk_postfx_sanitize.c) + target_include_directories(unit_vk_postfx_sanitize PRIVATE ${CMAKE_SOURCE_DIR}/src) + target_link_libraries(unit_vk_postfx_sanitize PRIVATE m) + add_test(NAME unit_vk_postfx_sanitize COMMAND unit_vk_postfx_sanitize) + set_tests_properties(unit_vk_postfx_sanitize PROPERTIES LABELS "unit;validation") endif() diff --git a/src/renderers/vulkan/vk_postfx_params.c b/src/renderers/vulkan/vk_postfx_params.c index 51f57db0c0..51105422f6 100644 --- a/src/renderers/vulkan/vk_postfx_params.c +++ b/src/renderers/vulkan/vk_postfx_params.c @@ -2,6 +2,7 @@ #include "vk.h" #include "vk_postfx.h" #include "vk_postfx_params.h" +#include "vk_postfx_sanitize.h" #include "vk_temporal.h" #include "vk_util.h" #include @@ -121,20 +122,24 @@ void vk_update_postfx_params( uint32_t cmd_index ) r_localExposure_strength = ri.Cvar_Get( "r_localExposure_strength", "0.35", 0 ); r_localExposure_shadowClamp = ri.Cvar_Get( "r_localExposure_shadowClamp", "1.5", 0 ); r_localExposure_highlightClamp = ri.Cvar_Get( "r_localExposure_highlightClamp", "1.5", 0 ); - params.autoExposureParams[0] = vk.temporal.hasValidLuminance ? vk.temporal.filteredAvgLogLuminance : - log2f( fmaxf( 1e-4f, ( r_autoExposure_target ? r_autoExposure_target->value : 1.0f ) / - fmaxf( r_exposure ? r_exposure->value : 1.0f, 1e-4f ) ) ); - params.autoExposureParams[1] = fmaxf( r_autoExposure_target ? r_autoExposure_target->value : 1.0f, 1e-4f ); - params.autoExposureParams[2] = fmaxf( r_autoExposure_min ? r_autoExposure_min->value : 0.5f, 0.01f ); - params.autoExposureParams[3] = fmaxf( r_autoExposure_max ? r_autoExposure_max->value : 4.0f, params.autoExposureParams[2] ); + vk_postfx_sanitize_auto_exposure_params( + vk.temporal.hasValidLuminance ? 1 : 0, + vk.temporal.filteredAvgLogLuminance, + r_autoExposure_target ? r_autoExposure_target->value : 1.0f, + r_exposure ? r_exposure->value : 1.0f, + r_autoExposure_min ? r_autoExposure_min->value : 0.5f, + r_autoExposure_max ? r_autoExposure_max->value : 4.0f, + params.autoExposureParams ); params.localExposureParams[0] = ( r_localExposure && r_localExposure->integer ) ? 1.0f : 0.0f; params.localExposureParams[1] = Com_Clamp( 0.0f, 1.0f, r_localExposure_strength ? r_localExposure_strength->value : 0.35f ); params.localExposureParams[2] = Com_Clamp( 0.0f, 3.0f, r_localExposure_shadowClamp ? r_localExposure_shadowClamp->value : 1.5f ); params.localExposureParams[3] = Com_Clamp( 0.0f, 3.0f, r_localExposure_highlightClamp ? r_localExposure_highlightClamp->value : 1.5f ); - params.taaParams[0] = vk.temporal.hasValidTAAHistory ? 1.0f : 0.0f; - params.taaParams[1] = Com_Clamp( 0.0f, 0.99f, r_taa_feedbackStationary ? r_taa_feedbackStationary->value : 0.92f ); - params.taaParams[2] = Com_Clamp( 0.0f, 0.99f, r_taa_feedbackMotion ? r_taa_feedbackMotion->value : 0.72f ); - params.taaParams[3] = Com_Clamp( 0.0f, 1.0f, r_taa_sharpen ? r_taa_sharpen->value : 0.12f ); + vk_postfx_sanitize_taa_params( + vk.temporal.hasValidTAAHistory ? 1 : 0, + r_taa_feedbackStationary ? r_taa_feedbackStationary->value : 0.92f, + r_taa_feedbackMotion ? r_taa_feedbackMotion->value : 0.72f, + r_taa_sharpen ? r_taa_sharpen->value : 0.12f, + params.taaParams ); if ( backEnd.projection2D || !tr.world || backEnd.viewParms.portalView != PV_NONE ) { Com_Memcpy( vk.postfx_params_ptr[cmd_index], ¶ms, sizeof( params ) ); diff --git a/src/renderers/vulkan/vk_postfx_sanitize.c b/src/renderers/vulkan/vk_postfx_sanitize.c new file mode 100644 index 0000000000..62083f6ae9 --- /dev/null +++ b/src/renderers/vulkan/vk_postfx_sanitize.c @@ -0,0 +1,56 @@ +#include "vk_postfx_sanitize.h" +#include + +static float vk_postfx_clamp( float min_value, float max_value, float value ) +{ + if ( value < min_value ) { + return min_value; + } + if ( value > max_value ) { + return max_value; + } + return value; +} + +void vk_postfx_sanitize_taa_params( int has_valid_history, + float stationary_feedback, + float motion_feedback, + float sharpen, + float out_taa_params[4] ) +{ + if ( !out_taa_params ) { + return; + } + + out_taa_params[0] = has_valid_history ? 1.0f : 0.0f; + out_taa_params[1] = vk_postfx_clamp( 0.0f, 0.99f, stationary_feedback ); + out_taa_params[2] = vk_postfx_clamp( 0.0f, 0.99f, motion_feedback ); + out_taa_params[3] = vk_postfx_clamp( 0.0f, 1.0f, sharpen ); +} + +void vk_postfx_sanitize_auto_exposure_params( int has_valid_luminance, + float filtered_avg_log_luminance, + float auto_target_luminance, + float manual_exposure, + float min_exposure, + float max_exposure, + float out_auto_exposure_params[4] ) +{ + const float safe_manual_exposure = fmaxf( manual_exposure, 1e-4f ); + const float safe_target = fmaxf( auto_target_luminance, 1e-4f ); + const float safe_min_exposure = fmaxf( min_exposure, 0.01f ); + const float safe_max_exposure = fmaxf( max_exposure, safe_min_exposure ); + + if ( !out_auto_exposure_params ) { + return; + } + + if ( has_valid_luminance ) { + out_auto_exposure_params[0] = filtered_avg_log_luminance; + } else { + out_auto_exposure_params[0] = log2f( safe_target / safe_manual_exposure ); + } + out_auto_exposure_params[1] = safe_target; + out_auto_exposure_params[2] = safe_min_exposure; + out_auto_exposure_params[3] = safe_max_exposure; +} diff --git a/src/renderers/vulkan/vk_postfx_sanitize.h b/src/renderers/vulkan/vk_postfx_sanitize.h new file mode 100644 index 0000000000..e24f608d27 --- /dev/null +++ b/src/renderers/vulkan/vk_postfx_sanitize.h @@ -0,0 +1,26 @@ +#ifndef VK_POSTFX_SANITIZE_H +#define VK_POSTFX_SANITIZE_H + +#ifdef __cplusplus +extern "C" { +#endif + +void vk_postfx_sanitize_taa_params( int has_valid_history, + float stationary_feedback, + float motion_feedback, + float sharpen, + float out_taa_params[4] ); + +void vk_postfx_sanitize_auto_exposure_params( int has_valid_luminance, + float filtered_avg_log_luminance, + float auto_target_luminance, + float manual_exposure, + float min_exposure, + float max_exposure, + float out_auto_exposure_params[4] ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/tests/unit/test_vk_postfx_sanitize.c b/tests/unit/test_vk_postfx_sanitize.c new file mode 100644 index 0000000000..40ebbc8b12 --- /dev/null +++ b/tests/unit/test_vk_postfx_sanitize.c @@ -0,0 +1,49 @@ +#include +#include + +#include "renderers/vulkan/vk_postfx_sanitize.h" + +#define ASSERT_TRUE(cond, msg) do { \ + if ( !( cond ) ) { \ + fprintf( stderr, "FAIL: %s\n", msg ); \ + return 1; \ + } \ +} while ( 0 ) + +static int almost_equal( float a, float b ) +{ + return fabsf( a - b ) < 1e-6f; +} + +int main( void ) +{ + float taa[4] = { 0.0f }; + float exposure[4] = { 0.0f }; + + vk_postfx_sanitize_taa_params( 1, 1.3f, -0.1f, 1.4f, taa ); + ASSERT_TRUE( almost_equal( taa[0], 1.0f ), "taa history flag" ); + ASSERT_TRUE( almost_equal( taa[1], 0.99f ), "taa stationary clamp high" ); + ASSERT_TRUE( almost_equal( taa[2], 0.0f ), "taa motion clamp low" ); + ASSERT_TRUE( almost_equal( taa[3], 1.0f ), "taa sharpen clamp high" ); + + vk_postfx_sanitize_taa_params( 0, 0.92f, 0.72f, 0.12f, taa ); + ASSERT_TRUE( almost_equal( taa[0], 0.0f ), "taa history disabled" ); + ASSERT_TRUE( almost_equal( taa[1], 0.92f ), "taa stationary passthrough" ); + ASSERT_TRUE( almost_equal( taa[2], 0.72f ), "taa motion passthrough" ); + ASSERT_TRUE( almost_equal( taa[3], 0.12f ), "taa sharpen passthrough" ); + + vk_postfx_sanitize_auto_exposure_params( 0, 2.0f, 0.0f, 0.0f, -1.0f, -3.0f, exposure ); + ASSERT_TRUE( almost_equal( exposure[0], 0.0f ), "auto exposure fallback log2 target/manual" ); + ASSERT_TRUE( almost_equal( exposure[1], 1e-4f ), "auto exposure target floor" ); + ASSERT_TRUE( almost_equal( exposure[2], 0.01f ), "auto exposure min floor" ); + ASSERT_TRUE( almost_equal( exposure[3], 0.01f ), "auto exposure max floored to min" ); + + vk_postfx_sanitize_auto_exposure_params( 1, 1.5f, 0.8f, 2.0f, 0.25f, 4.0f, exposure ); + ASSERT_TRUE( almost_equal( exposure[0], 1.5f ), "auto exposure uses valid luminance" ); + ASSERT_TRUE( almost_equal( exposure[1], 0.8f ), "auto exposure target passthrough" ); + ASSERT_TRUE( almost_equal( exposure[2], 0.25f ), "auto exposure min passthrough" ); + ASSERT_TRUE( almost_equal( exposure[3], 4.0f ), "auto exposure max passthrough" ); + + printf( "PASS: unit_vk_postfx_sanitize\n" ); + return 0; +}