diff --git a/UnleashedRecomp/CMakeLists.txt b/UnleashedRecomp/CMakeLists.txt index c205ff2..a38c761 100644 --- a/UnleashedRecomp/CMakeLists.txt +++ b/UnleashedRecomp/CMakeLists.txt @@ -292,6 +292,7 @@ endfunction() compile_vertex_shader(copy_vs) compile_pixel_shader(csd_filter_ps) +compile_pixel_shader(enhanced_motion_blur_ps) compile_pixel_shader(gaussian_blur_3x3) compile_pixel_shader(gaussian_blur_5x5) compile_pixel_shader(gaussian_blur_7x7) diff --git a/UnleashedRecomp/gpu/shader/enhanced_motion_blur_ps.hlsl b/UnleashedRecomp/gpu/shader/enhanced_motion_blur_ps.hlsl new file mode 100644 index 0000000..8ecf1f3 --- /dev/null +++ b/UnleashedRecomp/gpu/shader/enhanced_motion_blur_ps.hlsl @@ -0,0 +1,75 @@ +#include "../../../thirdparty/ShaderRecomp/ShaderRecomp/shader_common.h" + +#ifdef __spirv__ + +#define g_BlurRate vk::RawBufferLoad(g_PushConstants.PixelShaderConstants + 2400, 0x10) +#define g_ViewportSize vk::RawBufferLoad(g_PushConstants.PixelShaderConstants + 384, 0x10) + +#define sampColor_Texture2DDescriptorIndex vk::RawBufferLoad(g_PushConstants.SharedConstants + 0) +#define sampColor_SamplerDescriptorIndex vk::RawBufferLoad(g_PushConstants.SharedConstants + 192) + +#define sampVelocityMap_Texture2DDescriptorIndex vk::RawBufferLoad(g_PushConstants.SharedConstants + 4) +#define sampVelocityMap_SamplerDescriptorIndex vk::RawBufferLoad(g_PushConstants.SharedConstants + 196) + +#define sampZBuffer_Texture2DDescriptorIndex vk::RawBufferLoad(g_PushConstants.SharedConstants + 8) +#define sampZBuffer_SamplerDescriptorIndex vk::RawBufferLoad(g_PushConstants.SharedConstants + 200) + +#else + +cbuffer PixelShaderConstants : register(b1, space4) +{ + float4 g_BlurRate : packoffset(c150); + float4 g_ViewportSize : packoffset(c24); +}; + +cbuffer SharedConstants : register(b2, space4) +{ + uint sampColor_Texture2DDescriptorIndex : packoffset(c0.x); + uint sampColor_SamplerDescriptorIndex : packoffset(c12.x); + + uint sampVelocityMap_Texture2DDescriptorIndex : packoffset(c0.y); + uint sampVelocityMap_SamplerDescriptorIndex : packoffset(c12.y); + + uint sampZBuffer_Texture2DDescriptorIndex : packoffset(c0.z); + uint sampZBuffer_SamplerDescriptorIndex : packoffset(c12.z); + + DEFINE_SHARED_CONSTANTS(); +}; + +#endif + +float4 main(in float4 position : SV_Position, in float2 texCoord : TEXCOORD0) : SV_Target +{ + Texture2D sampColor = g_Texture2DDescriptorHeap[sampColor_Texture2DDescriptorIndex]; + Texture2D sampVelocityMap = g_Texture2DDescriptorHeap[sampVelocityMap_Texture2DDescriptorIndex]; + Texture2D sampZBuffer = g_Texture2DDescriptorHeap[sampZBuffer_Texture2DDescriptorIndex]; + + SamplerState sampColor_s = g_SamplerDescriptorHeap[sampColor_SamplerDescriptorIndex]; + SamplerState sampVelocityMap_s = g_SamplerDescriptorHeap[sampVelocityMap_SamplerDescriptorIndex]; + SamplerState sampZBuffer_s = g_SamplerDescriptorHeap[sampZBuffer_SamplerDescriptorIndex]; + + float depth = sampZBuffer.SampleLevel(sampZBuffer_s, texCoord, 0).x; + float4 velocityMap = sampVelocityMap.SampleLevel(sampVelocityMap_s, texCoord, 0); + float2 velocity = (velocityMap.xz + velocityMap.yw / 255.0) * 2.0 - 1.0; + + int sampleCount = min(64, round(length(velocity * g_ViewportSize.xy))); + float2 sampleOffset = velocity / (float) sampleCount; + + float3 color = sampColor.SampleLevel(sampColor_s, texCoord, 0).rgb; + int count = 1; + + for (int i = 1; i <= sampleCount; i++) + { + float2 sampleCoord = texCoord + sampleOffset * i; + float3 sampleColor = sampColor.SampleLevel(sampColor_s, sampleCoord, 0).rgb; + float sampleDepth = sampZBuffer.SampleLevel(sampZBuffer_s, sampleCoord, 0).x; + + if (sampleDepth - depth < 0.01) + { + color += sampleColor; + count += 1; + } + } + + return float4(color / count, g_BlurRate.x * saturate(dot(abs(velocity), g_ViewportSize.xy) / 8.0) * saturate(count - 1)); +} diff --git a/UnleashedRecomp/gpu/shader/gaussian_blur.hlsli b/UnleashedRecomp/gpu/shader/gaussian_blur.hlsli index 2e24dcd..e90b7b6 100644 --- a/UnleashedRecomp/gpu/shader/gaussian_blur.hlsli +++ b/UnleashedRecomp/gpu/shader/gaussian_blur.hlsli @@ -63,7 +63,7 @@ float4 main(in float4 iPosition : SV_Position, in float4 iTexCoord0 : TEXCOORD0) float2 offset = lerp(offsets[int(scaled)], offsets[min(int(scaled) + 1, 2)], frac(scaled)); float offsetScale = 1.0 / 0.75; float weight = ComputeWeight(lerp(-offsetScale, offsetScale, step)); - color += texture.Sample(samplerState, iTexCoord0.xy + offset) * weight; + color += texture.SampleLevel(samplerState, iTexCoord0.xy + offset, 0) * weight; weightSum += weight; } diff --git a/UnleashedRecomp/gpu/video.cpp b/UnleashedRecomp/gpu/video.cpp index 6503734..eabe028 100644 --- a/UnleashedRecomp/gpu/video.cpp +++ b/UnleashedRecomp/gpu/video.cpp @@ -36,6 +36,8 @@ #include "shader/copy_vs.hlsl.spirv.h" #include "shader/csd_filter_ps.hlsl.dxil.h" #include "shader/csd_filter_ps.hlsl.spirv.h" +#include "shader/enhanced_motion_blur_ps.hlsl.dxil.h" +#include "shader/enhanced_motion_blur_ps.hlsl.spirv.h" #include "shader/gaussian_blur_3x3.hlsl.dxil.h" #include "shader/gaussian_blur_3x3.hlsl.spirv.h" #include "shader/gaussian_blur_5x5.hlsl.dxil.h" @@ -1059,6 +1061,8 @@ static std::unique_ptr g_gaussianBlurShaders[GAUSSIAN_BLUR_COUNT]; static std::unique_ptr g_csdFilterShader; static GuestShader* g_csdShader; +static std::unique_ptr g_enhancedMotionBlurShader; + #define CREATE_SHADER(NAME) \ g_device->createShader( \ g_vulkan ? g_##NAME##_spirv : g_##NAME##_dxil, \ @@ -1461,6 +1465,9 @@ void Video::CreateHostDevice() g_csdFilterShader = std::make_unique(ResourceType::PixelShader); g_csdFilterShader->shader = CREATE_SHADER(csd_filter_ps); + g_enhancedMotionBlurShader = std::make_unique(ResourceType::PixelShader); + g_enhancedMotionBlurShader->shader = CREATE_SHADER(enhanced_motion_blur_ps); + CreateImGuiBackend(); auto gammaCorrectionShader = CREATE_SHADER(gamma_correction_ps); @@ -3902,48 +3909,54 @@ static void ProcSetPixelShader(const RenderCommand& cmd) { GuestShader* shader = cmd.setPixelShader.shader; if (shader != nullptr && - shader->shaderCacheEntry != nullptr && - shader->shaderCacheEntry->hash == 0x4294510C775F4EE8) + shader->shaderCacheEntry != nullptr) { - size_t shaderIndex = GAUSSIAN_BLUR_3X3; - - switch (Config::DepthOfFieldQuality) + if (shader->shaderCacheEntry->hash == 0x4294510C775F4EE8) { - case EDepthOfFieldQuality::Low: - shaderIndex = GAUSSIAN_BLUR_3X3; - break; + size_t shaderIndex = GAUSSIAN_BLUR_3X3; - case EDepthOfFieldQuality::Medium: - shaderIndex = GAUSSIAN_BLUR_5X5; - break; - - case EDepthOfFieldQuality::High: - shaderIndex = GAUSSIAN_BLUR_7X7; - break; - - case EDepthOfFieldQuality::Ultra: - shaderIndex = GAUSSIAN_BLUR_9X9; - break; - - default: - { - size_t height = round(g_swapChain->getHeight() * Config::ResolutionScale); - - // Use the middle point between resolutions to have the transition less noticable. - if (height >= ((2160 + 1440) / 2)) - shaderIndex = GAUSSIAN_BLUR_9X9; - else if (height >= ((1440 + 1080) / 2)) - shaderIndex = GAUSSIAN_BLUR_7X7; - else if (height >= ((1080 + 720) / 2)) - shaderIndex = GAUSSIAN_BLUR_5X5; - else + switch (Config::DepthOfFieldQuality) + { + case EDepthOfFieldQuality::Low: shaderIndex = GAUSSIAN_BLUR_3X3; + break; - break; - } - } + case EDepthOfFieldQuality::Medium: + shaderIndex = GAUSSIAN_BLUR_5X5; + break; - shader = g_gaussianBlurShaders[shaderIndex].get(); + case EDepthOfFieldQuality::High: + shaderIndex = GAUSSIAN_BLUR_7X7; + break; + + case EDepthOfFieldQuality::Ultra: + shaderIndex = GAUSSIAN_BLUR_9X9; + break; + + default: + { + size_t height = round(g_swapChain->getHeight() * Config::ResolutionScale); + + // Use the middle point between resolutions to have the transition less noticable. + if (height >= ((2160 + 1440) / 2)) + shaderIndex = GAUSSIAN_BLUR_9X9; + else if (height >= ((1440 + 1080) / 2)) + shaderIndex = GAUSSIAN_BLUR_7X7; + else if (height >= ((1080 + 720) / 2)) + shaderIndex = GAUSSIAN_BLUR_5X5; + else + shaderIndex = GAUSSIAN_BLUR_3X3; + + break; + } + } + + shader = g_gaussianBlurShaders[shaderIndex].get(); + } + else if (shader->shaderCacheEntry->hash == 0x6B9732B4CD7E7740 && Config::MotionBlur == EMotionBlur::Enhanced) + { + shader = g_enhancedMotionBlurShader.get(); + } } SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.pixelShader, shader); @@ -5474,6 +5487,12 @@ static void ModelConsumerThread() if (Config::GITextureFiltering == EGITextureFiltering::Bicubic) pipelineState.specConstants |= SPEC_CONSTANT_BICUBIC_GI_FILTER; + auto createGraphicsPipeline = [&](PipelineState& pipelineStateToCreate, const char* name) + { + SanitizePipelineState(pipelineStateToCreate); + EnqueueGraphicsPipelineCompilation(pipelineStateToCreate, emptyHolderPair, name); + }; + // Compile both MSAA and non MSAA variants to work with reflection maps. The render formats are an assumption but it should hold true. if (Config::AntiAliasing != EAntiAliasing::None && pipelineState.renderTargetFormat == RenderFormat::R16G16B16A16_FLOAT && @@ -5489,34 +5508,40 @@ static void ModelConsumerThread() msaaPipelineState.specConstants |= SPEC_CONSTANT_ALPHA_TO_COVERAGE; } - SanitizePipelineState(msaaPipelineState); - EnqueueGraphicsPipelineCompilation(msaaPipelineState, emptyHolderPair, "Precompiled Pipeline MSAA"); + createGraphicsPipeline(msaaPipelineState, "Precompiled Pipeline MSAA"); } - // Compile the custom gaussian blur shaders that we pass to the game. if (pipelineState.pixelShader != nullptr && - pipelineState.pixelShader->shaderCacheEntry != nullptr && - pipelineState.pixelShader->shaderCacheEntry->hash == 0x4294510C775F4EE8) + pipelineState.pixelShader->shaderCacheEntry != nullptr) { - for (auto& shader : g_gaussianBlurShaders) + XXH64_hash_t hash = pipelineState.pixelShader->shaderCacheEntry->hash; + + // Compile the custom gaussian blur shaders that we pass to the game. + if (hash == 0x4294510C775F4EE8) { - pipelineState.pixelShader = shader.get(); - SanitizePipelineState(pipelineState); - EnqueueGraphicsPipelineCompilation(pipelineState, emptyHolderPair, "Precompiled Gaussian Blur Pipeline"); + for (auto& shader : g_gaussianBlurShaders) + { + auto newPipelineState = pipelineState; + newPipelineState.pixelShader = shader.get(); + createGraphicsPipeline(newPipelineState, "Precompiled Gaussian Blur Pipeline"); + } + } + // Compile enhanced motion blur shader. + else if (hash == 0x6B9732B4CD7E7740) + { + auto newPipelineState = pipelineState; + newPipelineState.pixelShader = g_enhancedMotionBlurShader.get(); + createGraphicsPipeline(newPipelineState, "Precompiled Enhanced Motion Blur Pipeline"); } } - else - { - SanitizePipelineState(pipelineState); - EnqueueGraphicsPipelineCompilation(pipelineState, emptyHolderPair, "Precompiled Pipeline"); - } + + createGraphicsPipeline(pipelineState, "Precompiled Pipeline"); // Compile the CSD filter shader that we pass to the game when point filtering is used. if (pipelineState.pixelShader == g_csdShader) { pipelineState.pixelShader = g_csdFilterShader.get(); - SanitizePipelineState(pipelineState); - EnqueueGraphicsPipelineCompilation(pipelineState, emptyHolderPair, "Precompiled CSD Filter Pipeline"); + createGraphicsPipeline(pipelineState, "Precompiled CSD Filter Pipeline"); } } diff --git a/UnleashedRecomp/locale/config_locale.h b/UnleashedRecomp/locale/config_locale.h index 0c8382d..dde7c8d 100644 --- a/UnleashedRecomp/locale/config_locale.h +++ b/UnleashedRecomp/locale/config_locale.h @@ -319,10 +319,10 @@ CONFIG_DEFINE_ENUM_LOCALE(EDepthOfFieldQuality) { ELanguage::English, { - { EDepthOfFieldQuality::Auto, { "AUTO", "" } }, - { EDepthOfFieldQuality::Low, { "LOW", "" } }, - { EDepthOfFieldQuality::Medium, { "MEDIUM", "" } }, - { EDepthOfFieldQuality::High, { "HIGH", "" } }, + { EDepthOfFieldQuality::Auto, { "AUTO", "" } }, + { EDepthOfFieldQuality::Low, { "LOW", "" } }, + { EDepthOfFieldQuality::Medium, { "MEDIUM", "" } }, + { EDepthOfFieldQuality::High, { "HIGH", "" } }, { EDepthOfFieldQuality::Ultra, { "ULTRA", "" } }, } } @@ -333,6 +333,18 @@ CONFIG_DEFINE_LOCALE(MotionBlur) { ELanguage::English, { "Motion Blur", "Use per-object motion blur and radial blur." } } }; +CONFIG_DEFINE_ENUM_LOCALE(EMotionBlur) +{ + { + ELanguage::English, + { + { EMotionBlur::Off, { "OFF", "" } }, + { EMotionBlur::Original, { "ORIGINAL", "" } }, + { EMotionBlur::Enhanced, { "ENHANCED", "" } } + } + } +}; + CONFIG_DEFINE_LOCALE(XboxColourCorrection) { { ELanguage::English, { "Xbox Color Correction", "Use the warm tint from the Xbox version of the game." } } diff --git a/UnleashedRecomp/patches/video_patches.cpp b/UnleashedRecomp/patches/video_patches.cpp index 39622eb..5e407fe 100644 --- a/UnleashedRecomp/patches/video_patches.cpp +++ b/UnleashedRecomp/patches/video_patches.cpp @@ -26,5 +26,5 @@ void CSDAspectRatioMidAsmHook(PPCRegister& f1, PPCRegister& f2) bool MotionBlurMidAsmHook() { - return Config::MotionBlur; + return Config::MotionBlur != EMotionBlur::Off; } diff --git a/UnleashedRecomp/user/config.h b/UnleashedRecomp/user/config.h index d3e8241..89af4cb 100644 --- a/UnleashedRecomp/user/config.h +++ b/UnleashedRecomp/user/config.h @@ -64,7 +64,7 @@ public: CONFIG_DEFINE_ENUM_LOCALISED("Video", EShadowResolution, ShadowResolution, EShadowResolution::x4096); CONFIG_DEFINE_ENUM_LOCALISED("Video", EGITextureFiltering, GITextureFiltering, EGITextureFiltering::Bicubic); CONFIG_DEFINE_ENUM_LOCALISED("Video", EDepthOfFieldQuality, DepthOfFieldQuality, EDepthOfFieldQuality::Auto); - CONFIG_DEFINE_LOCALISED("Video", bool, MotionBlur, true); + CONFIG_DEFINE_ENUM_LOCALISED("Video", EMotionBlur, MotionBlur, EMotionBlur::Original); CONFIG_DEFINE_LOCALISED("Video", bool, XboxColourCorrection, false); CONFIG_DEFINE_ENUM_LOCALISED("Video", EMovieScaleMode, MovieScaleMode, EMovieScaleMode::Fit); CONFIG_DEFINE_ENUM_LOCALISED("Video", EUIScaleMode, UIScaleMode, EUIScaleMode::Centre); diff --git a/UnleashedRecomp/user/config_detail.h b/UnleashedRecomp/user/config_detail.h index 7151b39..455e52a 100644 --- a/UnleashedRecomp/user/config_detail.h +++ b/UnleashedRecomp/user/config_detail.h @@ -203,13 +203,27 @@ enum class EDepthOfFieldQuality : uint32_t CONFIG_DEFINE_ENUM_TEMPLATE(EDepthOfFieldQuality) { - { "Auto", EDepthOfFieldQuality::Auto }, - { "Low", EDepthOfFieldQuality::Low }, - { "Medium", EDepthOfFieldQuality::Medium }, - { "High", EDepthOfFieldQuality::High }, + { "Auto", EDepthOfFieldQuality::Auto }, + { "Low", EDepthOfFieldQuality::Low }, + { "Medium", EDepthOfFieldQuality::Medium }, + { "High", EDepthOfFieldQuality::High }, { "Ultra", EDepthOfFieldQuality::Ultra } }; +enum class EMotionBlur : uint32_t +{ + Off, + Original, + Enhanced +}; + +CONFIG_DEFINE_ENUM_TEMPLATE(EMotionBlur) +{ + { "Off", EMotionBlur::Off }, + { "Original", EMotionBlur::Original }, + { "Enhanced", EMotionBlur::Enhanced } +}; + enum class EMovieScaleMode : uint32_t { Stretch,