From b71b18988118e2c7851be5b08e8341099e0c8e8c Mon Sep 17 00:00:00 2001 From: Sultim Tsyrendashiev Date: Mon, 18 Sep 2023 23:55:17 +0100 Subject: [PATCH] VHS / Dither post effects --- Include/RTGL1/RTGL1.h | 18 ++++ Source/DrawFrameInfo.h | 2 + Source/EffectSimple_Instances.h | 52 +++++++++++ Source/ShaderManager.cpp | 2 + Source/Shaders/EfDither.comp | 147 ++++++++++++++++++++++++++++++++ Source/Shaders/EfVHS.comp | 121 ++++++++++++++++++++++++++ Source/VulkanDevice.cpp | 8 ++ Source/VulkanDevice.h | 2 + Source/VulkanDevice_Init.cpp | 6 ++ 9 files changed, 358 insertions(+) create mode 100644 Source/Shaders/EfDither.comp create mode 100644 Source/Shaders/EfVHS.comp diff --git a/Include/RTGL1/RTGL1.h b/Include/RTGL1/RTGL1.h index 8d084978..ae36fe57 100644 --- a/Include/RTGL1/RTGL1.h +++ b/Include/RTGL1/RTGL1.h @@ -943,6 +943,22 @@ typedef struct RgPostEffectCRT RgBool32 isActive; } RgPostEffectCRT; +typedef struct RgPostEffectVHS +{ + RgBool32 isActive; + float transitionDurationIn; + float transitionDurationOut; + float intensity; +} RgPostEffectVHS; + +typedef struct RgPostEffectDither +{ + RgBool32 isActive; + float transitionDurationIn; + float transitionDurationOut; + float intensity; +} RgPostEffectDither; + typedef struct RgPostEffectTeleport { RgBool32 isActive; @@ -966,6 +982,8 @@ typedef struct RgDrawFramePostEffectsParams const RgPostEffectColorTint* pColorTint; const RgPostEffectTeleport* pTeleport; const RgPostEffectCRT* pCRT; + const RgPostEffectVHS* pVHS; + const RgPostEffectDither* pDither; } RgDrawFramePostEffectsParams; typedef enum RgMediaType diff --git a/Source/DrawFrameInfo.h b/Source/DrawFrameInfo.h index f152848c..a18e7b5c 100644 --- a/Source/DrawFrameInfo.h +++ b/Source/DrawFrameInfo.h @@ -412,6 +412,8 @@ namespace detail .pColorTint = nullptr, .pTeleport = nullptr, .pCRT = nullptr, + .pVHS = nullptr, + .pDither = nullptr, }; }; diff --git a/Source/EffectSimple_Instances.h b/Source/EffectSimple_Instances.h index c8e55096..62d19173 100644 --- a/Source/EffectSimple_Instances.h +++ b/Source/EffectSimple_Instances.h @@ -245,4 +245,56 @@ struct EffectWaves final : public EffectSimple } }; + +// ------------------ // + + +struct EffectVHS_PushConst +{ + float intensity; +}; + +struct EffectVHS final : public EffectSimple< EffectVHS_PushConst > +{ + RTGL1_EFFECT_SIMPLE_INHERIT_CONSTRUCTOR( EffectVHS, "EffectVHS" ) + + bool Setup( const CommonnlyUsedEffectArguments& args, const RgPostEffectVHS* params ) + { + if( params == nullptr || params->intensity <= 0.0f ) + { + return SetupNull(); + } + + GetPush().intensity = params->intensity; + return EffectSimple::Setup( + args, params->isActive, params->transitionDurationIn, params->transitionDurationOut ); + } +}; + + +// ------------------ // + + +struct EffectDither_PushConst +{ + float intensity; +}; + +struct EffectDither final : public EffectSimple< EffectDither_PushConst > +{ + RTGL1_EFFECT_SIMPLE_INHERIT_CONSTRUCTOR( EffectDither, "EffectDither" ) + + bool Setup( const CommonnlyUsedEffectArguments& args, const RgPostEffectDither* params ) + { + if( params == nullptr || params->intensity <= 0.0f ) + { + return SetupNull(); + } + + GetPush().intensity = params->intensity; + return EffectSimple::Setup( + args, params->isActive, params->transitionDurationIn, params->transitionDurationOut ); + } +}; + } \ No newline at end of file diff --git a/Source/ShaderManager.cpp b/Source/ShaderManager.cpp index b41032c7..07cd716e 100644 --- a/Source/ShaderManager.cpp +++ b/Source/ShaderManager.cpp @@ -94,6 +94,8 @@ static ShaderModuleDefinition G_SHADERS[] = { "EffectHueShift", "EfHueShift.comp.spv" }, { "EffectCrtDemodulateEncode", "EfCrtDemodulateEncode.comp.spv" }, { "EffectCrtDecode", "EfCrtDecode.comp.spv" }, + { "EffectVHS", "EfVHS.comp.spv" }, + { "EffectDither", "EfDither.comp.spv" }, }; // clang-format on diff --git a/Source/Shaders/EfDither.comp b/Source/Shaders/EfDither.comp new file mode 100644 index 00000000..ffa51894 --- /dev/null +++ b/Source/Shaders/EfDither.comp @@ -0,0 +1,147 @@ +// Copyright (c) 2022 Sultim Tsyrendashiev +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#version 460 + +struct EffectDither_PushConst +{ + float intensity; +}; + +#define EFFECT_PUSH_CONST_T EffectDither_PushConst +#include "EfSimple.inl" + + +#define DITHER_TEXTURE_SIZE_X 36 +#define DITHER_TEXTURE_SIZE_Y 4 + +float getPattern( vec2 uv ) +{ + ivec2 at = ivec2( uv.x * DITHER_TEXTURE_SIZE_X, uv.y * DITHER_TEXTURE_SIZE_Y ); + + // pattern texture is 36x4 + at = ivec2( at.x % DITHER_TEXTURE_SIZE_X, at.y % DITHER_TEXTURE_SIZE_Y ); + + // the pixels with x=32..36 are 1.0 + if( at.x >= 32 ) + { + return 1.0; + } + + // the pixels with x=0..31 are encoded in bit mask; each line is y + const uint pattern[] = { + 0x8aaaeff, + 0x4555d, + 0x2aaabff, + 0x15557, + }; + + // test bit at.x, at.y + return ( pattern[ at.y ] & ( 1 << ( 31 - at.x ) ) ) != 0 ? 1.0 : 0.0; +} + + +// from https://github.com/jmickle66666666/PSX-Dither-Shader/blob/master/PSX%20Dither.shader (Created by https://github.com/jmickle66666666) +// and https://www.shadertoy.com/view/tlc3DM (Created by BitOfGold in 2019-12-16) + + +// ported to shaderToy by László Matuska / @BitOfGold +// from here: https://github.com/jmickle66666666/PSX-Dither-Shader/blob/master/PSX%20Dither.shader +// uses Shadertoy's 8x8 bayer dithering pattern instead of the original pattern + +// Number of colors. 32 (5 bits) per channel +const vec3 _Colors = vec3(32.0); + +float channelError(float col, float colMin, float colMax) +{ + float range = abs(colMin - colMax); + float aRange = abs(col - colMin); + return aRange /range; +} + +float ditheredChannel(float error, vec2 ditherBlockUV, float ditherSteps) +{ + error = floor(error * ditherSteps) / ditherSteps; + vec2 ditherUV = vec2(error + ditherBlockUV.x, ditherBlockUV.y); + return getPattern(ditherUV); +} + +/// YUV/RGB color space calculations + +vec3 RGBtoYUV(vec3 rgb) { + vec3 yuv; + yuv.r = rgb.r * 0.2126 + 0.7152 * rgb.g + 0.0722 * rgb.b; + yuv.g = (rgb.b - yuv.r) / 1.8556; + yuv.b = (rgb.r - yuv.r) / 1.5748; + + // Adjust to work on GPU + yuv.gb += 0.5; + + return yuv; +} + +vec3 YUVtoRGB(vec3 yuv) { + yuv.gb -= 0.5; + return vec3( + yuv.r * 1.0 + yuv.g * 0.0 + yuv.b * 1.5748, + yuv.r * 1.0 + yuv.g * -0.187324 + yuv.b * -0.468124, + yuv.r * 1.0 + yuv.g * 1.8556 + yuv.b * 0.0); +} + +vec3 ditherColor(vec3 col, vec2 uv, ivec2 windowsize) { + vec3 yuv = RGBtoYUV(col); + + vec3 col1 = floor(yuv * _Colors) / _Colors; + vec3 col2 = ceil(yuv * _Colors) / _Colors; + + // Calculate dither texture UV based on the input texture + float ditherSize = DITHER_TEXTURE_SIZE_Y; + float ditherSteps = DITHER_TEXTURE_SIZE_X / ditherSize; + + vec2 ditherBlockUV; + ditherBlockUV.x = mod(uv.x, (ditherSize / windowsize.x)); + ditherBlockUV.x /= (ditherSize / windowsize.x); + ditherBlockUV.y = mod(uv.y, (ditherSize / windowsize.y)); + ditherBlockUV.y /= (ditherSize / windowsize.y); + ditherBlockUV.x /= ditherSteps; + + yuv.x = mix(col1.x, col2.x, ditheredChannel(channelError(yuv.x, col1.x, col2.x), ditherBlockUV, ditherSteps)); + yuv.y = mix(col1.y, col2.y, ditheredChannel(channelError(yuv.y, col1.y, col2.y), ditherBlockUV, ditherSteps)); + yuv.z = mix(col1.z, col2.z, ditheredChannel(channelError(yuv.z, col1.z, col2.z), ditherBlockUV, ditherSteps)); + + return(YUVtoRGB(yuv)); +} + + +void main() +{ + const ivec2 pix = ivec2( gl_GlobalInvocationID.x, gl_GlobalInvocationID.y ); + if( !effect_isPixValid( pix ) ) + { + return; + } + + vec3 original = effect_loadFromSource( pix ); + + vec3 c = mix( original, + ditherColor( original, effect_getFramebufUV( pix ), effect_getFramebufSize() ), + getProgress() * push.custom.intensity ); + effect_storeToTarget( c, pix ); +} diff --git a/Source/Shaders/EfVHS.comp b/Source/Shaders/EfVHS.comp new file mode 100644 index 00000000..0bd6cf4e --- /dev/null +++ b/Source/Shaders/EfVHS.comp @@ -0,0 +1,121 @@ +// Copyright (c) 2022 Sultim Tsyrendashiev +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#version 460 + +struct EffectVHS_PushConst +{ + float intensity; +}; + +#define EFFECT_PUSH_CONST_T EffectVHS_PushConst +#include "EfSimple.inl" + + +// from https://www.shadertoy.com/view/XtBXDt (Created by FMS_Cat in 2015-11-10) + + +vec3 tex2D( vec2 _p ){ + ivec2 pix = ivec2( _p * effect_getFramebufSize()); + vec3 col = effect_loadFromSource( pix ).xyz; + + if ( 0.5 < abs( _p.x - 0.5 ) ) { + col = vec3( 0.1 ); + } + return col; +} + +float hash( vec2 _v ){ + return fract( sin( dot( _v, vec2( 89.44, 19.36 ) ) ) * 22189.22 ); +} + +float iHash( vec2 _v, vec2 _r ){ + float h00 = hash( vec2( floor( _v * _r + vec2( 0.0, 0.0 ) ) / _r ) ); + float h10 = hash( vec2( floor( _v * _r + vec2( 1.0, 0.0 ) ) / _r ) ); + float h01 = hash( vec2( floor( _v * _r + vec2( 0.0, 1.0 ) ) / _r ) ); + float h11 = hash( vec2( floor( _v * _r + vec2( 1.0, 1.0 ) ) / _r ) ); + vec2 ip = vec2( smoothstep( vec2( 0.0, 0.0 ), vec2( 1.0, 1.0 ), mod( _v*_r, 1. ) ) ); + return ( h00 * ( 1. - ip.x ) + h10 * ip.x ) * ( 1. - ip.y ) + ( h01 * ( 1. - ip.x ) + h11 * ip.x ) * ip.y; +} + +float noise( vec2 _v ){ + float sum = 0.; + for( int i=1; i<9; i++ ) + { + sum += iHash( _v + vec2( i ), vec2( 2. * pow( 2., float( i ) ) ) ) / pow( 2., float( i ) ); + } + return sum; +} + +vec3 vhs( vec2 uv, float time ){ + vec2 uvn = uv; + vec3 col = vec3( 0.0 ); + + // tape wave + uvn.x += ( noise( vec2( uvn.y, time ) ) - 0.5 )* 0.005; + uvn.x += ( noise( vec2( uvn.y * 100.0, time * 10.0 ) ) - 0.5 ) * 0.01; + + // tape crease + float tcPhase = clamp( ( sin( uvn.y * 8.0 - time * M_PI * 1.2 ) - 0.92 ) * noise( vec2( time ) ), 0.0, 0.01 ) * 10.0; + float tcNoise = max( noise( vec2( uvn.y * 100.0, time * 10.0 ) ) - 0.5, 0.0 ); + uvn.x = uvn.x - 0.1 * tcNoise * tcPhase; + + // switching noise + float snPhase = smoothstep( 0.03, 0.0, uvn.y ); + uvn.y += snPhase * 0.3; + uvn.x += snPhase * ( ( noise( vec2( uv.y * 100.0, time * 10.0 ) ) - 0.5 ) * 0.2 ); + + col = tex2D( uvn ); + col *= 1.0 - tcPhase * 0.5; + col = mix( + col, + col.yzx, + snPhase + ); + + // bloom + for( float x = -2.0; x < 3.5; x += 1.0 ){ + col.xyz += vec3( + tex2D( uvn + vec2( x - 2.0, 0.0 ) * 0.0035 ).x, + tex2D( uvn + vec2( x - 0.0, 0.0 ) * 0.0035 ).y, + tex2D( uvn + vec2( x + 2.0, 0.0 ) * 0.0035 ).z + ) * 0.1; + } + col *= 0.6; + + // ac beat + col *= 1.0 + clamp( noise( vec2( 0.0, uv.y + time * 0.2 ) ) * 0.6 - 0.25, 0.0, 0.1 ); + + return col; +} + +void main() +{ + const ivec2 pix = ivec2( gl_GlobalInvocationID.x, gl_GlobalInvocationID.y ); + if( !effect_isPixValid( pix ) ) + { + return; + } + + vec3 c = mix( effect_loadFromSource( pix ), + vhs( effect_getFramebufUV( pix ), globalUniform.time ), + getProgress() * push.custom.intensity * 0.5 ); + effect_storeToTarget( c, pix ); +} diff --git a/Source/VulkanDevice.cpp b/Source/VulkanDevice.cpp index 01d0f108..869bd169 100644 --- a/Source/VulkanDevice.cpp +++ b/Source/VulkanDevice.cpp @@ -726,6 +726,14 @@ void RTGL1::VulkanDevice::Render( VkCommandBuffer cmd, const RgDrawFrameInfo& dr { accum = effectRadialBlur->Apply( args, accum ); } + if( effectVHS->Setup( args, postef.pVHS ) ) + { + accum = effectVHS->Apply( args, accum ); + } + if( effectDither->Setup( args, postef.pDither ) ) + { + accum = effectDither->Apply( args, accum ); + } } // draw geometry such as HUD into an upscaled framebuf diff --git a/Source/VulkanDevice.h b/Source/VulkanDevice.h index 5a647104..7bdc70cf 100644 --- a/Source/VulkanDevice.h +++ b/Source/VulkanDevice.h @@ -209,6 +209,8 @@ class VulkanDevice std::shared_ptr< EffectTeleport > effectTeleport; std::shared_ptr< EffectCrtDemodulateEncode > effectCrtDemodulateEncode; std::shared_ptr< EffectCrtDecode > effectCrtDecode; + std::shared_ptr< EffectVHS > effectVHS; + std::shared_ptr< EffectDither > effectDither; std::shared_ptr< SamplerManager > worldSamplerManager; std::shared_ptr< SamplerManager > genericSamplerManager; diff --git a/Source/VulkanDevice_Init.cpp b/Source/VulkanDevice_Init.cpp index d31bf6c9..6254cbf3 100644 --- a/Source/VulkanDevice_Init.cpp +++ b/Source/VulkanDevice_Init.cpp @@ -446,6 +446,8 @@ RTGL1::VulkanDevice::VulkanDevice( const RgInstanceCreateInfo* info ) effectTeleport = CONSTRUCT_SIMPLE_EFFECT( EffectTeleport ); effectCrtDemodulateEncode = CONSTRUCT_SIMPLE_EFFECT( EffectCrtDemodulateEncode ); effectCrtDecode = CONSTRUCT_SIMPLE_EFFECT( EffectCrtDecode ); + effectVHS = CONSTRUCT_SIMPLE_EFFECT( EffectVHS ); + effectDither = CONSTRUCT_SIMPLE_EFFECT( EffectDither ); #undef SIMPLE_EFFECT_CONSTRUCTOR_PARAMS @@ -470,6 +472,8 @@ RTGL1::VulkanDevice::VulkanDevice( const RgInstanceCreateInfo* info ) shaderManager->Subscribe( effectTeleport ); shaderManager->Subscribe( effectCrtDemodulateEncode ); shaderManager->Subscribe( effectCrtDecode ); + shaderManager->Subscribe( effectVHS ); + shaderManager->Subscribe( effectDither ); framebuffers->Subscribe( rasterizer ); framebuffers->Subscribe( amdFsr2 ); @@ -513,6 +517,8 @@ RTGL1::VulkanDevice::~VulkanDevice() effectTeleport.reset(); effectCrtDemodulateEncode.reset(); effectCrtDecode.reset(); + effectVHS.reset(); + effectDither.reset(); denoiser.reset(); uniform.reset(); scene.reset();