diff --git a/src/games/gow2018/addon.cpp b/src/games/gow2018/addon.cpp index aeb29af7..a27e7ee6 100644 --- a/src/games/gow2018/addon.cpp +++ b/src/games/gow2018/addon.cpp @@ -12,27 +12,25 @@ #include #include -#include // Game BT.2020 Conversion + PQ Encoding w/ DLSS/FSR +#include // Tonemap + PQ w/ DLSS/FSR #include // LUT w/ TAA #include // LUT w/ DLSS/FSR -#include // Gamma Slider + Paper White + Tonemap -#include // Game BT.2020 Conversion + PQ Encoding w/ TAA +#include // Tonemap + PQ w/ TAA + #include #include "../../mods/shader.hpp" -#include "../../mods/swapchain.hpp" +// #include "../../mods/swapchain.hpp" #include "../../utils/settings.hpp" #include "./shared.h" namespace { renodx::mods::shader::CustomShaders custom_shaders = { - - CustomShaderEntry(0x7818463E), // LUT w/ DLSS/FSR CustomShaderEntry(0x6FE3FEEA), // LUT w/ TAA - CustomShaderEntry(0x279D11F6), // Game BT.2020 Conversion + PQ Encoding w/ DLSS/FSR - CustomShaderEntry(0xF4EFA04D), // Game BT.2020 Conversion + PQ Encoding w/ TAA - CustomShaderEntry(0xB59D6558), // Gamma Slider + Paper White + Tonemap + CustomShaderEntry(0x7818463E), // LUT w/ DLSS/FSR + CustomShaderEntry(0xF4EFA04D), // Tonemap + PQ w/ TAA + CustomShaderEntry(0x279D11F6), // Tonemap + PQ w/ DLSS/FSR }; ShaderInjectData shader_injection; @@ -73,18 +71,6 @@ renodx::utils::settings::Settings settings = { .max = 500.f, .is_enabled = []() { return shader_injection.toneMapType != 0; }, }, - new renodx::utils::settings::Setting{ - .key = "toneMapUINits", - .binding = &shader_injection.toneMapUINits, - .default_value = 203.f, - .can_reset = false, - .label = "UI Brightness", - .section = "Tone Mapping", - .tooltip = "Sets the brightness of UI and HUD elements in nits", - .min = 48.f, - .max = 500.f, - .is_enabled = []() { return shader_injection.toneMapType != 0; }, - }, new renodx::utils::settings::Setting{ .key = "toneMapHueCorrection", .binding = &shader_injection.toneMapHueCorrection, @@ -170,7 +156,7 @@ renodx::utils::settings::Settings settings = { new renodx::utils::settings::Setting{ .key = "colorGradeLUTScaling", .binding = &shader_injection.colorGradeLUTScaling, - .default_value = 100.f, + .default_value = 75.f, .label = "LUT Scaling", .section = "Color Grading", .tooltip = "Scales the color grade LUT to full range when size is clamped.", @@ -184,7 +170,6 @@ void OnPresetOff() { renodx::utils::settings::UpdateSetting("toneMapType", 0); renodx::utils::settings::UpdateSetting("toneMapPeakNits", 1000.f); renodx::utils::settings::UpdateSetting("toneMapGameNits", 203.f); - renodx::utils::settings::UpdateSetting("toneMapUINits", 203.f); renodx::utils::settings::UpdateSetting("colorGradeExposure", 1.f); renodx::utils::settings::UpdateSetting("toneMapHueCorrection", 0.f); renodx::utils::settings::UpdateSetting("colorGradeHighlights", 50.f); @@ -210,10 +195,6 @@ BOOL APIENTRY DllMain(HMODULE h_module, DWORD fdw_reason, LPVOID lpv_reserved) { renodx::mods::shader::force_pipeline_cloning = true; renodx::mods::shader::expected_constant_buffer_index = 11; - renodx::mods::swapchain::swap_chain_upgrade_targets.push_back({.old_format = reshade::api::format::r10g10b10a2_unorm, - .new_format = reshade::api::format::r16g16b16a16_float, - .index = 0}); - if (!reshade::register_addon(h_module)) return FALSE; break; @@ -223,7 +204,6 @@ BOOL APIENTRY DllMain(HMODULE h_module, DWORD fdw_reason, LPVOID lpv_reserved) { } renodx::utils::settings::Use(fdw_reason, &settings, &OnPresetOff); - renodx::mods::swapchain::Use(fdw_reason); // scRGB swapchain renodx::mods::shader::Use(fdw_reason, custom_shaders, &shader_injection); return TRUE; diff --git a/src/games/gow2018/final_gamma_0xB59D6558.ps_5_0.hlsl b/src/games/gow2018/final_gamma_0xB59D6558.ps_5_0.hlsl deleted file mode 100644 index 6b4d8c8e..00000000 --- a/src/games/gow2018/final_gamma_0xB59D6558.ps_5_0.hlsl +++ /dev/null @@ -1,35 +0,0 @@ -#include "./DICE.hlsl" -#include "./shared.h" - -cbuffer ConstBuf__passData : register(b0) { - struct GammaAdjustPassData { - float gamma; - } - resourceTables__passData; -} - -Texture2D inputTexture : register(t0); - -void main( - in float4 SV_POSITION: SV_POSITION, - in float2 TEXCOORD0: TEXCOORD0, - in float2 TEXCOORD1: TEXCOORD1, - out float4 SV_TARGET0: SV_TARGET0) { - uint3 positionAsUint = uint3(uint2(SV_POSITION.xy), 0u); - - float4 inputColor = inputTexture.Load(positionAsUint); - - SV_TARGET0.a = inputColor.a; - - SV_TARGET0.rgb = renodx::color::bt2020::from::PQ(inputColor.rgb); - SV_TARGET0.rgb = renodx::color::bt709::from::BT2020(SV_TARGET0.rgb / 80.f) * 10000.f; - if (injectedData.toneMapType != 0) { - SV_TARGET0.rgb *= injectedData.toneMapUINits / 306.f; - if (injectedData.toneMapType > 1) { // DICE tonemap - DICESettings config = DefaultDICESettings(); - config.Type = 3u; - config.ShoulderStart = 0.5f; - SV_TARGET0.rgb = DICETonemap(SV_TARGET0.rgb, injectedData.toneMapPeakNits / 80.f, config); - } - } -} diff --git a/src/games/gow2018/game_pq_0x279D11F6.ps_5_0.hlsl b/src/games/gow2018/game_pq_0x279D11F6.ps_5_0.hlsl index 2537ade0..734efa27 100644 --- a/src/games/gow2018/game_pq_0x279D11F6.ps_5_0.hlsl +++ b/src/games/gow2018/game_pq_0x279D11F6.ps_5_0.hlsl @@ -1,4 +1,5 @@ #include "./shared.h" +#include "./tonemapper.hlsl" // ---- Created with 3Dmigoto v1.3.16 on Fri Aug 30 19:53:29 2024 Texture2D t16 : register(t16); @@ -14,21 +15,6 @@ cbuffer cb0 : register(b0) { // 3Dmigoto declarations #define cmp - -float ColorGradeSmoothClamp(float x) { - const float u = 0.525; - - float q = (2.0 - u - 1.0 / u + x * (2.0 + 2.0 / u - x / u)) / 4.0; - - return (abs(1.0 - x) < u) ? q : saturate(x); -} -float3 ColorGradeSmoothClamp(float3 color) { - float3 outputColor; - outputColor.r = ColorGradeSmoothClamp(color.r); - outputColor.g = ColorGradeSmoothClamp(color.g); - outputColor.b = ColorGradeSmoothClamp(color.b); - return outputColor; -} - void main( float4 v0: SV_POSITION0, float2 v1: TEXCOORD0, @@ -82,12 +68,16 @@ void main( injectedData.toneMapHueCorrection, // hue correction renodx::tonemap::uncharted2::BT709(r0.rgb)); - r0.xyz = renodx::color::correct::GammaSafe(r0.xyz); // linearize with 2.2 instead of srgb + r0.xyz = renodx::color::correct::GammaSafe(r0.xyz); // linearize with 2.2 instead of srgb + + if (injectedData.toneMapType == 2) { // DICE tonemap + r0.rgb = applyDICE(r0.rgb); + } + r0.xyz = renodx::color::bt2020::from::BT709(r0.xyz); // Convert to BT.2020 r0.xyz = max(0, r0.xyz); // Clamp needed to prevent artifacts - r0.xyz *= injectedData.toneMapGameNits / injectedData.toneMapUINits; - o0.xyz = renodx::color::pq::Encode(r0.xyz, 306.f); // Set paper white to match UI - } else { // Original BT.2020 + PQ code + o0.xyz = renodx::color::pq::Encode(r0.xyz, injectedData.toneMapGameNits); + } else { // Original BT.2020 + PQ code r0.xyz = max(float3(0, 0, 0), r0.xyz); // BT.2020 r0.w = dot(float3(0.627403915, 0.329283029, 0.0433130674), r0.xyz); @@ -114,7 +104,6 @@ void main( r2.xyz = r0.xyz * r2.xyz + float3(10668.4043, 10668.4043, 10668.4043); r0.xyz = r0.xyz * r2.xyz + float3(1, 1, 1); o0.xyz = r1.xyz / r0.xyz; - o0.xyz = saturate(o0.xyz); // previously clamped by unorm } o0.w = 1; return; diff --git a/src/games/gow2018/game_pq_taa_0xF4EFA04D.ps_5_0.hlsl b/src/games/gow2018/game_pq_taa_0xF4EFA04D.ps_5_0.hlsl index 966c2a7f..263faff1 100644 --- a/src/games/gow2018/game_pq_taa_0xF4EFA04D.ps_5_0.hlsl +++ b/src/games/gow2018/game_pq_taa_0xF4EFA04D.ps_5_0.hlsl @@ -1,4 +1,5 @@ #include "./shared.h" +#include "./tonemapper.hlsl" // ---- Created with 3Dmigoto v1.3.16 on Sun Oct 13 15:49:48 2024 Texture2D t16 : register(t16); @@ -106,12 +107,16 @@ void main( injectedData.toneMapHueCorrection, // hue correction renodx::tonemap::uncharted2::BT709(r0.rgb)); - r0.xyz = renodx::color::correct::GammaSafe(r0.xyz); // linearize with 2.2 instead of srgb + r0.xyz = renodx::color::correct::GammaSafe(r0.xyz); // linearize with 2.2 instead of srgb + + if (injectedData.toneMapType == 2) { // DICE tonemap + r0.rgb = applyDICE(r0.rgb); + } + r0.xyz = renodx::color::bt2020::from::BT709(r0.xyz); // Convert to BT.2020 r0.xyz = max(0, r0.xyz); // Clamp needed to prevent artifacts - r0.xyz *= injectedData.toneMapGameNits / injectedData.toneMapUINits; - o0.xyz = renodx::color::pq::Encode(r0.xyz, 306.f); // Set paper white to match UI - } else { // Original BT.2020 + PQ code + o0.xyz = renodx::color::pq::Encode(r0.xyz, injectedData.toneMapGameNits); + } else { // Original BT.2020 + PQ code r0.xyz = max(float3(0, 0, 0), r0.xyz); r0.w = dot(float3(0.627403915, 0.329283029, 0.0433130674), r0.xyz); r1.x = dot(float3(0.069097288, 0.919540405, 0.0113623161), r0.xyz); diff --git a/src/games/gow2018/lut_0x7818463E.ps_5_0.hlsl b/src/games/gow2018/lut_0x7818463E.ps_5_0.hlsl index fa382ad2..48a1b65f 100644 --- a/src/games/gow2018/lut_0x7818463E.ps_5_0.hlsl +++ b/src/games/gow2018/lut_0x7818463E.ps_5_0.hlsl @@ -155,71 +155,62 @@ void main( r0.z = cmp(0 != cb0[0].w); r1.xyz = r0.zzz ? float3(0, 0, 0) : r1.xyz; - float3 lutInputColor = r1.xyz; - if (injectedData.toneMapType == 0) { - // convert arri logc800 - r2.xyz = cmp(float3(0.0105910003, 0.0105910003, 0.0105910003) < r1.xyz); - r3.xyzw = r1.xxyy * float4(5.55555582, 5.3676548, 5.55555582, 5.3676548) + float4(0.0522719994, 0.0928089991, 0.0522719994, 0.0928089991); - r1.xy = log2(r3.xz); - r1.xy = r1.xy * float2(0.0744116008, 0.0744116008) + float2(0.385536999, 0.385536999); - r3.xy = r2.xy ? r1.xy : r3.yw; - r1.xy = r1.zz * float2(5.55555582, 5.3676548) + float2(0.0522719994, 0.0928089991); - r0.z = log2(r1.x); - r0.z = r0.z * 0.0744116008 + 0.385536999; - r3.z = r2.z ? r0.z : r1.y; - // Sample 64x64x64 LUT - r1.xyz = r3.xyz * float3(0.984375, 0.984375, 0.984375) + float3(0.0078125, 0.0078125, 0.0078125); - r1.xyz = t0.SampleLevel(s1_s, r1.xyz, 0).xyz; - // back to linear - r2.xyz = cmp(float3(0.149658203, 0.149658203, 0.149658203) < r1.xyz); - r3.xyzw = float4(-0.385536999, -0.0928089991, -0.385536999, -0.0928089991) + r1.xxyy; - r3.xyzw = float4(13.4387865, 0.186301097, 13.4387865, 0.186301097) * r3.xyzw; - r1.xy = exp2(r3.xz); - r1.xy = float2(-0.0522719994, -0.0522719994) + r1.xy; - r1.xy = float2(0.179999992, 0.179999992) * r1.xy; - r3.xy = r2.xy ? r1.xy : r3.yw; - r1.xy = float2(-0.385536999, -0.0928089991) + r1.zz; - r1.xy = float2(13.4387865, 0.186301097) * r1.xy; - r0.z = exp2(r1.x); - r0.z = -0.0522719994 + r0.z; - r0.z = 0.179999992 * r0.z; - r3.z = r2.z ? r0.z : r1.y; - - r3.xyz = lerp(lutInputColor, r3.xyz, injectedData.colorGradeLUTStrength); // LUT Strength - r1.xyz = max(0, r3.xyz); - } else { - lutInputColor = renodx::color::grade::UserColorGrading( - lutInputColor, + if (injectedData.toneMapType != 0) { + r1.xyz = renodx::color::grade::UserColorGrading( + r1.xyz, injectedData.colorGradeExposure, // exposure injectedData.colorGradeHighlights, // highlights injectedData.colorGradeShadows, // shadows injectedData.colorGradeContrast, // contrast 1.f, // saturation, applied later - 0.f); // dechroma, applied later - - renodx::lut::Config lut_config = renodx::lut::config::Create( - s1_s, - 1.f, // do LUT strength after - 0.f, // do LUT scaling after - renodx::lut::config::type::ARRI_C800, - renodx::lut::config::type::ARRI_C800, - 64); - float3 lutOutputColor = renodx::lut::Sample(t0, lut_config, lutInputColor); - - // Cleans up raised black floor - if (injectedData.colorGradeLUTScaling && injectedData.colorGradeLUTStrength) { - float3 minBlack = renodx::color::arri::logc::c800::Decode(t0.SampleLevel(s1_s, renodx::color::arri::logc::c800::Encode((0.f).xxx), 0.0f).rgb); - const float lutMinY = renodx::color::y::from::BT709(max(0, minBlack)); - if (lutMinY > 0) { - float3 correctedBlack = renodx::lut::CorrectBlack(lutInputColor, lutOutputColor, lutMinY, 0.f); - lutOutputColor = lerp(lutOutputColor, correctedBlack, injectedData.colorGradeLUTScaling); - } - } - - r1.xyz = lerp(lutInputColor, lutOutputColor, injectedData.colorGradeLUTStrength); + 0.f, // dechroma, applied later + 0.f); // hue correction, applied later + } + float3 lutInputColor = r1.xyz; - r1.xyz = max(0, r1.xyz); // DLSS/FSR clamps to BT.709 + // convert arri logc800 + r2.xyz = cmp(float3(0.0105910003, 0.0105910003, 0.0105910003) < r1.xyz); + r3.xyzw = r1.xxyy * float4(5.55555582, 5.3676548, 5.55555582, 5.3676548) + float4(0.0522719994, 0.0928089991, 0.0522719994, 0.0928089991); + r1.xy = log2(r3.xz); + r1.xy = r1.xy * float2(0.0744116008, 0.0744116008) + float2(0.385536999, 0.385536999); + r3.xy = r2.xy ? r1.xy : r3.yw; + r1.xy = r1.zz * float2(5.55555582, 5.3676548) + float2(0.0522719994, 0.0928089991); + r0.z = log2(r1.x); + r0.z = r0.z * 0.0744116008 + 0.385536999; + r3.z = r2.z ? r0.z : r1.y; + // Sample 64x64x64 LUT + r1.xyz = r3.xyz * float3(0.984375, 0.984375, 0.984375) + float3(0.0078125, 0.0078125, 0.0078125); + r1.xyz = t0.SampleLevel(s1_s, r1.xyz, 0).xyz; + // back to linear + r2.xyz = cmp(float3(0.149658203, 0.149658203, 0.149658203) < r1.xyz); + r3.xyzw = float4(-0.385536999, -0.0928089991, -0.385536999, -0.0928089991) + r1.xxyy; + r3.xyzw = float4(13.4387865, 0.186301097, 13.4387865, 0.186301097) * r3.xyzw; + r1.xy = exp2(r3.xz); + r1.xy = float2(-0.0522719994, -0.0522719994) + r1.xy; + r1.xy = float2(0.179999992, 0.179999992) * r1.xy; + r3.xy = r2.xy ? r1.xy : r3.yw; + r1.xy = float2(-0.385536999, -0.0928089991) + r1.zz; + r1.xy = float2(13.4387865, 0.186301097) * r1.xy; + r0.z = exp2(r1.x); + r0.z = -0.0522719994 + r0.z; + r0.z = 0.179999992 * r0.z; + r3.z = r2.z ? r0.z : r1.y; + + r1.xyz = max(0, r3.xyz); + + float3 lutOutputColor = r1.xyz; + + if (injectedData.toneMapType != 0 && injectedData.colorGradeLUTScaling > 0 && injectedData.colorGradeLUTStrength > 0) { + float3 minBlack = renodx::color::arri::logc::c800::Decode(t0.SampleLevel(s1_s, renodx::color::arri::logc::c800::Encode((0.f).xxx), 0.0f).rgb); + + float lutMinY = renodx::color::y::from::BT709(max(0, minBlack)); + if (lutMinY > 0) { + float3 correctedBlack = renodx::lut::CorrectBlack(lutInputColor, lutOutputColor, lutMinY, 0.f); + lutOutputColor = lerp(lutOutputColor, correctedBlack, injectedData.colorGradeLUTScaling); + } } + r1.xyz = lerp(lutInputColor.rgb, lutOutputColor, injectedData.colorGradeLUTStrength); // LUT Strength + r1.xyz = max(0, r1.xyz); // DLSS/FSR clamps to BT.709 anyway r0.z = max(r1.y, r1.z); r0.z = max(r1.x, r0.z); diff --git a/src/games/gow2018/shared.h b/src/games/gow2018/shared.h index 83ffd443..ce794569 100644 --- a/src/games/gow2018/shared.h +++ b/src/games/gow2018/shared.h @@ -11,7 +11,6 @@ struct ShaderInjectData { float toneMapType; float toneMapPeakNits; float toneMapGameNits; - float toneMapUINits; float toneMapHueCorrection; float colorGradeExposure; float colorGradeHighlights; diff --git a/src/games/gow2018/tonemapper.hlsl b/src/games/gow2018/tonemapper.hlsl new file mode 100644 index 00000000..faefe8cc --- /dev/null +++ b/src/games/gow2018/tonemapper.hlsl @@ -0,0 +1,13 @@ +#include "./DICE.hlsl" +#include "./shared.h" + +float3 applyDICE(float3 untonemapped) { + DICESettings config = DefaultDICESettings(); + config.Type = 3u; + config.ShoulderStart = 0.5f; + + const float paperWhite = injectedData.toneMapGameNits / renodx::color::srgb::REFERENCE_WHITE; + const float peakWhite = injectedData.toneMapPeakNits / renodx::color::srgb::REFERENCE_WHITE; + + return DICETonemap(untonemapped * paperWhite, peakWhite, config) / paperWhite; +} \ No newline at end of file diff --git a/src/games/thetownoflight/X_Final_0xFFFFFFFD.vs_5_0.hlsl b/src/games/thetownoflight/X_Final_0xFFFFFFFD.vs_5_0.hlsl new file mode 100644 index 00000000..49a8458d --- /dev/null +++ b/src/games/thetownoflight/X_Final_0xFFFFFFFD.vs_5_0.hlsl @@ -0,0 +1,5 @@ +void main(uint id: SV_VERTEXID, out float4 pos: SV_POSITION, out float2 uv: TEXCOORD0) { + uv.x = (id == 1) ? 2.0 : 0.0; + uv.y = (id == 2) ? 2.0 : 0.0; + pos = float4(uv * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0); +} diff --git a/src/games/thetownoflight/X_Final_0xFFFFFFFE.ps_5_0.hlsl b/src/games/thetownoflight/X_Final_0xFFFFFFFE.ps_5_0.hlsl new file mode 100644 index 00000000..0464c2ec --- /dev/null +++ b/src/games/thetownoflight/X_Final_0xFFFFFFFE.ps_5_0.hlsl @@ -0,0 +1,17 @@ +#include "./shared.h" + +SamplerState sourceSampler_s : register(s0); +Texture2D sourceTexture : register(t0); + +void main( + float4 vpos: SV_Position, + float2 texcoord: TEXCOORD, + out float4 output: SV_Target0) { + float4 color = sourceTexture.Sample(sourceSampler_s, texcoord.xy); + + // Linearize with 2.2 Gamma and scale paper white + float3 linearizedColor = renodx::color::gamma::DecodeSafe(color.rgb, 2.2); + linearizedColor *= injectedData.toneMapUINits / renodx::color::srgb::REFERENCE_WHITE; + + output.rgba = float4(linearizedColor, color.a); +} diff --git a/src/games/thetownoflight/addon.cpp b/src/games/thetownoflight/addon.cpp index e1936e47..f10f97be 100644 --- a/src/games/thetownoflight/addon.cpp +++ b/src/games/thetownoflight/addon.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2024 Filippo Tarpini + * Copyright (C) 2024 Musa Haji * SPDX-License-Identifier: MIT */ @@ -9,32 +10,329 @@ #include #include +#include -#include +#include // Custom final VS +#include // Custom final PS #include #include + #include "../../mods/shader.hpp" #include "../../mods/swapchain.hpp" #include "../../utils/settings.hpp" +#include "./shared.h" namespace { renodx::mods::shader::CustomShaders custom_shaders = { CustomShaderEntry(0x9D6291BC), // Color grading LUT + fog + fade - CustomShaderEntry(0xB103EAA6), // Post process (e.g. contrast) + user gamma adjustment (defaulted to 1) + CustomShaderEntry(0xB103EAA6), // Post process and user gamma adjustment (defaulted to 1) + CustomShaderEntry(0xE61B6A3B), // Overlay effect in main menu }; +ShaderInjectData shader_injection; + +renodx::utils::settings::Settings settings = { + new renodx::utils::settings::Setting{ + .key = "toneMapPeakNits", + .binding = &shader_injection.toneMapPeakNits, + .default_value = 1000.f, + .can_reset = false, + .label = "Peak Brightness", + .section = "Tone Mapping", + .tooltip = "Sets the value of peak white in nits", + .min = 48.f, + .max = 4000.f, + }, + new renodx::utils::settings::Setting{ + .key = "toneMapGameNits", + .binding = &shader_injection.toneMapGameNits, + .default_value = 203.f, + .can_reset = false, + .label = "Game Brightness", + .section = "Tone Mapping", + .tooltip = "Sets the value of 100% white in nits", + .min = 48.f, + .max = 500.f, + }, + new renodx::utils::settings::Setting{ + .key = "toneMapUINits", + .binding = &shader_injection.toneMapUINits, + .default_value = 203.f, + .can_reset = false, + .label = "UI Brightness", + .section = "Tone Mapping", + .tooltip = "Sets the brightness of UI and HUD elements in nits", + .min = 48.f, + .max = 500.f, + }, + new renodx::utils::settings::Setting{ + .value_type = renodx::utils::settings::SettingValueType::BUTTON, + .label = "Discord", + .section = "Links", + .group = "button-line-1", + .tint = 0x5865F2, + .on_change = []() { + system("start https://discord.gg/5WZXDpmbpP"); + }, + }, + new renodx::utils::settings::Setting{ + .value_type = renodx::utils::settings::SettingValueType::BUTTON, + .label = "Github", + .section = "Links", + .group = "button-line-1", + .on_change = []() { + system("start https://github.com/clshortfuse/renodx"); + }, + }, + new renodx::utils::settings::Setting{ + .value_type = renodx::utils::settings::SettingValueType::BUTTON, + .label = "Buy Pumbo a Coffee", + .section = "Links", + .group = "button-line-1", + .tint = 0xFF5F5F, + .on_change = []() { + system("start https://buymeacoffee.com/realfiloppi"); + }, + }, +}; + +void OnPresetOff() { + renodx::utils::settings::UpdateSetting("toneMapPeakNits", 203.f); + renodx::utils::settings::UpdateSetting("toneMapGameNits", 203.f); + renodx::utils::settings::UpdateSetting("toneMapUINits", 203.f); +} + } // namespace -extern "C" __declspec(dllexport) constexpr const char* NAME = "RenoDX"; -extern "C" __declspec(dllexport) constexpr const char* DESCRIPTION = "RenoDX for The Town of Light"; +// NOLINTBEGIN(readability-identifier-naming) + +extern "C" __declspec(dllexport) const char* NAME = "RenoDX"; +extern "C" __declspec(dllexport) const char* DESCRIPTION = "RenoDX for The Town of Light"; + +// NOLINTEND(readability-identifier-naming) + +// Final shader [ty Ersh/FF14] +struct __declspec(uuid("1228220F-364A-46A2-BB29-1CCE591A018A")) DeviceData { + reshade::api::effect_runtime* main_runtime = nullptr; + std::atomic_bool rendered_effects = false; + std::vector swapchain_rtvs; + reshade::api::pipeline final_pipeline = {}; + reshade::api::resource final_texture = {}; + reshade::api::resource_view final_texture_view = {}; + reshade::api::sampler final_texture_sampler = {}; + reshade::api::pipeline_layout final_layout = {}; + reshade::api::pipeline_layout copy_layout = {}; +}; + +constexpr reshade::api::pipeline_layout PIPELINE_LAYOUT{0}; + +void OnInitDevice(reshade::api::device* device) { + auto& data = device->create_private_data(); + + // create pipeline + { + std::vector subobjects; + + reshade::api::shader_desc vs_desc = {}; + vs_desc.code = _0xFFFFFFFD; + vs_desc.code_size = sizeof(_0xFFFFFFFD); + subobjects.push_back({reshade::api::pipeline_subobject_type::vertex_shader, 1, &vs_desc}); + + reshade::api::shader_desc ps_desc = {}; + ps_desc.code = _0xFFFFFFFE; + ps_desc.code_size = sizeof(_0xFFFFFFFE); + subobjects.push_back({reshade::api::pipeline_subobject_type::pixel_shader, 1, &ps_desc}); + + reshade::api::format format = reshade::api::format::r16g16b16a16_float; + subobjects.push_back({reshade::api::pipeline_subobject_type::render_target_formats, 1, &format}); + + uint32_t num_vertices = 3; + subobjects.push_back({reshade::api::pipeline_subobject_type::max_vertex_count, 1, &num_vertices}); + + auto topology = reshade::api::primitive_topology::triangle_list; + subobjects.push_back({reshade::api::pipeline_subobject_type::primitive_topology, 1, &topology}); + + reshade::api::blend_desc blend_state = {}; + subobjects.push_back({reshade::api::pipeline_subobject_type::blend_state, 1, &blend_state}); + + reshade::api::rasterizer_desc rasterizer_state = {}; + rasterizer_state.cull_mode = reshade::api::cull_mode::none; + subobjects.push_back({reshade::api::pipeline_subobject_type::rasterizer_state, 1, &rasterizer_state}); + + reshade::api::depth_stencil_desc depth_stencil_state = {}; + depth_stencil_state.depth_enable = false; + depth_stencil_state.depth_write_mask = false; + depth_stencil_state.depth_func = reshade::api::compare_op::always; + depth_stencil_state.stencil_enable = false; + depth_stencil_state.front_stencil_read_mask = 0xFF; + depth_stencil_state.front_stencil_write_mask = 0xFF; + depth_stencil_state.front_stencil_func = depth_stencil_state.back_stencil_func; + depth_stencil_state.front_stencil_fail_op = depth_stencil_state.back_stencil_fail_op; + depth_stencil_state.front_stencil_depth_fail_op = depth_stencil_state.back_stencil_depth_fail_op; + depth_stencil_state.front_stencil_pass_op = depth_stencil_state.back_stencil_pass_op; + depth_stencil_state.back_stencil_read_mask = 0xFF; + depth_stencil_state.back_stencil_write_mask = 0xFF; + depth_stencil_state.back_stencil_func = reshade::api::compare_op::always; + depth_stencil_state.back_stencil_fail_op = reshade::api::stencil_op::keep; + depth_stencil_state.back_stencil_depth_fail_op = reshade::api::stencil_op::keep; + depth_stencil_state.back_stencil_pass_op = reshade::api::stencil_op::keep; + + subobjects.push_back({reshade::api::pipeline_subobject_type::depth_stencil_state, 1, &depth_stencil_state}); + + device->create_pipeline(PIPELINE_LAYOUT, static_cast(subobjects.size()), subobjects.data(), &data.final_pipeline); + } + + // create layout + { + reshade::api::pipeline_layout_param new_params; + new_params.type = reshade::api::pipeline_layout_param_type::push_constants; + new_params.push_constants.count = 1; + new_params.push_constants.dx_register_index = 13; + new_params.push_constants.visibility = reshade::api::shader_stage::vertex | reshade::api::shader_stage::pixel | reshade::api::shader_stage::compute; + device->create_pipeline_layout(1, &new_params, &data.final_layout); + } + + { + reshade::api::pipeline_layout_param new_params; + new_params.type = reshade::api::pipeline_layout_param_type::push_constants; + new_params.push_constants.count = 1; + new_params.push_constants.dx_register_index = 12; + new_params.push_constants.visibility = reshade::api::shader_stage::vertex | reshade::api::shader_stage::pixel | reshade::api::shader_stage::compute; + device->create_pipeline_layout(1, &new_params, &data.copy_layout); + } +} + +void OnDestroyDevice(reshade::api::device* device) { + auto& data = device->get_private_data(); + + device->destroy_pipeline(data.final_pipeline); + device->destroy_pipeline_layout(data.final_layout); + + device->destroy_private_data(); +} + +void OnInitSwapchain(reshade::api::swapchain* swapchain) { + auto device = swapchain->get_device(); + auto& data = device->get_private_data(); + + for (int i = 0; i < swapchain->get_back_buffer_count(); ++i) { + auto back_buffer_resource = swapchain->get_back_buffer(i); + auto back_buffer_desc = device->get_resource_desc(back_buffer_resource); + auto desc = reshade::api::resource_view_desc(reshade::api::resource_view_type::texture_2d, reshade::api::format_to_default_typed(back_buffer_desc.texture.format), 0, 1, 0, 1); + device->create_resource_view(back_buffer_resource, reshade::api::resource_usage::render_target, desc, &data.swapchain_rtvs.emplace_back()); + } + + // create copy target + { + auto back_buffer_resource = swapchain->get_back_buffer(0); + auto back_buffer_desc = device->get_resource_desc(back_buffer_resource); + reshade::api::resource_desc desc = {}; + desc.type = reshade::api::resource_type::texture_2d; + desc.texture = { + back_buffer_desc.texture.width, + back_buffer_desc.texture.height, + 1, + 1, + reshade::api::format_to_typeless(back_buffer_desc.texture.format), + 1, + }; + desc.heap = reshade::api::memory_heap::gpu_only; + desc.usage = reshade::api::resource_usage::copy_dest | reshade::api::resource_usage::shader_resource; + desc.flags = reshade::api::resource_flags::none; + device->create_resource(desc, nullptr, reshade::api::resource_usage::shader_resource, &data.final_texture); + device->create_resource_view(data.final_texture, reshade::api::resource_usage::shader_resource, reshade::api::resource_view_desc(reshade::api::format_to_default_typed(desc.texture.format)), &data.final_texture_view); + device->create_sampler({}, &data.final_texture_sampler); + } +} + +void OnDestroySwapchain(reshade::api::swapchain* swapchain) { + auto device = swapchain->get_device(); + auto& data = device->get_private_data(); + + for (const auto& rtv : data.swapchain_rtvs) { + device->destroy_resource_view(rtv); + } + + data.swapchain_rtvs.clear(); + + device->destroy_sampler(data.final_texture_sampler); + device->destroy_resource_view(data.final_texture_view); + device->destroy_resource(data.final_texture); +} + +// more or less the same as what reshade does to render its techniques +void OnPresent(reshade::api::command_queue* queue, reshade::api::swapchain* swapchain, const reshade::api::rect* source_rect, const reshade::api::rect* dest_rect, uint32_t dirty_rect_count, const reshade::api::rect* dirty_rects) { + auto device = queue->get_device(); + auto cmd_list = queue->get_immediate_command_list(); + + auto& data = device->get_private_data(); + + auto back_buffer_resource = swapchain->get_current_back_buffer(); + auto back_buffer_desc = device->get_resource_desc(back_buffer_resource); + + // copy backbuffer + { + const reshade::api::resource resources[2] = {back_buffer_resource, data.final_texture}; + const reshade::api::resource_usage state_old[2] = {reshade::api::resource_usage::render_target, reshade::api::resource_usage::shader_resource}; + const reshade::api::resource_usage state_new[2] = {reshade::api::resource_usage::copy_source, reshade::api::resource_usage::copy_dest}; + + cmd_list->barrier(2, resources, state_old, state_new); + cmd_list->copy_texture_region(back_buffer_resource, 0, nullptr, data.final_texture, 0, nullptr); + cmd_list->barrier(2, resources, state_new, state_old); + } + + cmd_list->bind_pipeline(reshade::api::pipeline_stage::all_graphics, data.final_pipeline); + + cmd_list->barrier(back_buffer_resource, reshade::api::resource_usage::shader_resource, reshade::api::resource_usage::render_target); + + reshade::api::render_pass_render_target_desc render_target = {}; + render_target.view = data.swapchain_rtvs.at(swapchain->get_current_back_buffer_index()); + cmd_list->begin_render_pass(1, &render_target, nullptr); + + cmd_list->push_descriptors(reshade::api::shader_stage::all_graphics, PIPELINE_LAYOUT, 0, reshade::api::descriptor_table_update{{}, 0, 0, 1, reshade::api::descriptor_type::texture_shader_resource_view, &data.final_texture_view}); + cmd_list->push_descriptors(reshade::api::shader_stage::all_graphics, PIPELINE_LAYOUT, 0, reshade::api::descriptor_table_update{{}, 0, 0, 1, reshade::api::descriptor_type::sampler, &data.final_texture_sampler}); + + // push the renodx settings + cmd_list->push_constants(reshade::api::shader_stage::all_graphics, data.final_layout, 0, 0, sizeof(shader_injection) / 4, &shader_injection); + + const reshade::api::viewport viewport = { + 0.0f, 0.0f, + static_cast(back_buffer_desc.texture.width), + static_cast(back_buffer_desc.texture.height), + 0.0f, 1.0f}; + cmd_list->bind_viewports(0, 1, &viewport); + + cmd_list->draw(3, 1, 0, 0); + cmd_list->end_render_pass(); + + cmd_list->barrier(back_buffer_resource, reshade::api::resource_usage::render_target, reshade::api::resource_usage::shader_resource); + + // reset the copy tracker, entirely unrelated to the final shader above + // track_next_copy = false; // We dont need this, just pasted from FF14 code + // shader_injection.copyTracker = 0; // We dont need this, just pasted from FF14 code +} BOOL APIENTRY DllMain(HMODULE h_module, DWORD fdw_reason, LPVOID lpv_reserved) { switch (fdw_reason) { case DLL_PROCESS_ATTACH: if (!reshade::register_addon(h_module)) return FALSE; + renodx::mods::shader::expected_constant_buffer_index = 13; + + // renodx::mods::shader::force_pipeline_cloning = true; + // renodx::mods::shader::trace_unmodified_shaders = true; + renodx::mods::swapchain::force_borderless = false; + // renodx::mods::swapchain::prevent_full_screen = true; + + // Final shader + reshade::register_event(OnInitDevice); + reshade::register_event(OnDestroyDevice); + reshade::register_event(OnInitSwapchain); + reshade::register_event(OnDestroySwapchain); + reshade::register_event(OnPresent); + renodx::mods::swapchain::swap_chain_upgrade_targets.push_back({ .old_format = reshade::api::format::r8g8b8a8_unorm, .new_format = reshade::api::format::r16g16b16a16_float, @@ -63,17 +361,16 @@ BOOL APIENTRY DllMain(HMODULE h_module, DWORD fdw_reason, LPVOID lpv_reserved) { #endif break; - case DLL_PROCESS_DETACH: reshade::unregister_addon(h_module); break; } - // TODO(Filoppi): add user shader settings for tonemapping (paper white, peak brightness), and allow selecting between sRGB vs 2.2 gamma - // TODO(Filoppi): add a final shader pass that does linearization, at the moment the mod requires an external ReShade shader to be linearized (with gamma 2.2) and for paper white scaling (80->203 nits) + renodx::utils::settings::Use(fdw_reason, &settings, &OnPresetOff); renodx::mods::swapchain::Use(fdw_reason); - renodx::mods::shader::Use(fdw_reason, custom_shaders); + + renodx::mods::shader::Use(fdw_reason, custom_shaders, &shader_injection); return TRUE; } diff --git a/src/games/thetownoflight/colorGradingAndFog_0x9D6291BC.ps_4_0.hlsl b/src/games/thetownoflight/colorGradingAndFog_0x9D6291BC.ps_4_0.hlsl index a1e7a588..d0384d5f 100644 --- a/src/games/thetownoflight/colorGradingAndFog_0x9D6291BC.ps_4_0.hlsl +++ b/src/games/thetownoflight/colorGradingAndFog_0x9D6291BC.ps_4_0.hlsl @@ -93,7 +93,9 @@ float4 sampleLUTWithExtrapolation(Texture2D lut, SamplerState samplerSta return clampedSample; } -void main(float4 v0 : SV_POSITION0, float2 v1 : TEXCOORD0, float2 w1 : TEXCOORD1, out float4 outColor : SV_Target0) { +static bool TonemapHDR = true; + +void main(float4 v0: SV_POSITION0, float2 v1: TEXCOORD0, float2 w1: TEXCOORD1, out float4 outColor: SV_Target0) { const bool vanilla = false; // Turn on for vanilla behaviour const bool extrapolateLUTsMethod = vanilla ? -1 : 1; @@ -151,5 +153,25 @@ void main(float4 v0 : SV_POSITION0, float2 v1 : TEXCOORD0, float2 w1 : TEXCOORD1 someFogVar2 = someFogVar4 + someFogVar2; outColor.xyz = lerp(someFogVar2.xxx, fogColorLutted, cb0[9].xxx); // Fade to (or away from) color outColor.w = sceneColor.w; + + // Tonemapping might also help to fix some scenes that end burning through the UI, possibly because the scene (background) had extremely high values + if (TonemapHDR) { + const float paperWhite = injectedData.toneMapGameNits / renodx::color::srgb::REFERENCE_WHITE; + float3 linearColor = renodx::color::gamma::DecodeSafe(outColor.rgb, 2.2); + linearColor *= paperWhite; + + const float peakWhite = injectedData.toneMapPeakNits / renodx::color::srgb::REFERENCE_WHITE; + const float highlightsShoulderStart = paperWhite; // Don't tonemap the "SDR" range (in luminance), we want to keep it looking as it used to look in SDR + linearColor = renodx::tonemap::dice::BT709(linearColor, peakWhite, highlightsShoulderStart); + + linearColor /= paperWhite; + + // Scale game paper white by ratio of UI paper white to allow for separate sliders + linearColor *= injectedData.toneMapGameNits / injectedData.toneMapUINits; + + outColor.rgb = renodx::color::gamma::EncodeSafe(linearColor, 2.2); + } + // Leave output in gamma space and with a paper white of 80 nits even for HDR so we can blend in the UI just like in SDR (in gamma space) and linearize with an extra pass added at the end. + return; -} \ No newline at end of file +} diff --git a/src/games/thetownoflight/grungeFilter_0xE61B6A3B.ps_4_0.hlsl b/src/games/thetownoflight/grungeFilter_0xE61B6A3B.ps_4_0.hlsl new file mode 100644 index 00000000..dc818336 --- /dev/null +++ b/src/games/thetownoflight/grungeFilter_0xE61B6A3B.ps_4_0.hlsl @@ -0,0 +1,54 @@ +#include "./shared.h" + +Texture2D t0 : register(t0); + +SamplerState s0_s : register(s0); + +cbuffer cb0 : register(b0) { + float4 cb0[7]; +} + +// 3Dmigoto declarations +#define cmp - + +void main( + float4 v0: SV_POSITION0, + float2 v1: TEXCOORD0, + out float4 o0: SV_Target0) { + float4 r0, r1, r2; + uint4 bitmask, uiDest; + float4 fDest; + + r0.xyzw = t0.Sample(s0_s, v1.xy).xyzw; + + // normalize white level to the same range as SDR (theoretically 80 nits) + r0.rgb = renodx::color::gamma::DecodeSafe(r0.rgb, 2.2f); + r0.rgb /= injectedData.toneMapGameNits / injectedData.toneMapUINits; + r0.rgb = renodx::color::gamma::EncodeSafe(r0.rgb, 2.2f); + +#if 0 // remove unecessary saturate() + r0.xyzw = saturate(r0.xyzw); +#endif + r1.xyz = float3(1, 1, 1) + -r0.xyz; + + r1.xyz = renodx::math::SafePow(r1.xyz, cb0[6].x); // fix to allow negative scRGB values + + r1.w = exp2(cb0[6].x); + r1.w = 0.5 * r1.w; + r1.xyz = -r1.xyz * r1.www + float3(1, 1, 1); + + r2.xyz = renodx::math::SafePow(r0.xyz, cb0[6].x); // fix to allow negative scRGB values + + r1.xyz = -r2.xyz * r1.www + r1.xyz; + r2.xyz = r2.xyz * r1.www; + r0.xyz = cmp(r0.xyz >= float3(0.5, 0.5, 0.5)); + o0.w = r0.w; + r0.xyz = r0.xyz ? float3(1, 1, 1) : 0; + o0.xyz = r0.xyz * r1.xyz + r2.xyz; + + o0.rgb = renodx::color::gamma::DecodeSafe(o0.rgb, 2.2f); + o0.rgb *= injectedData.toneMapGameNits / injectedData.toneMapUINits; + o0.rgb = renodx::color::gamma::EncodeSafe(o0.rgb, 2.2f); + + return; +} diff --git a/src/games/thetownoflight/postProcessAndGammaAdjustment_0xB103EAA6.ps_4_0.hlsl b/src/games/thetownoflight/postProcessAndGammaAdjustment_0xB103EAA6.ps_4_0.hlsl index c1b812ed..42015abe 100644 --- a/src/games/thetownoflight/postProcessAndGammaAdjustment_0xB103EAA6.ps_4_0.hlsl +++ b/src/games/thetownoflight/postProcessAndGammaAdjustment_0xB103EAA6.ps_4_0.hlsl @@ -8,44 +8,43 @@ cbuffer cb0 : register(b0) { float4 cb0[8]; } -static bool HDR = true; -static bool TonemapHDR = true; -static float PaperWhiteNits = renodx::color::bt2408::REFERENCE_WHITE; -static float PeakWhiteNits = 1000.0; - -// Final shader before UI draws in, this can be used as tonemapping shader -void main(float4 v0 : SV_POSITION0, float2 v1 : TEXCOORD0, out float4 o0 : SV_Target0) { - float4 r0 = t0.Sample(s0_s, v1.xy).xyzw; - - // Some contrast adjustments: - float3 r1 = cb0[7].xyz; - r0.xyz = r0.xyz * cb0[6].xxx + -r1.xyz; -#if 1 - r0.xyz = r0.xyz * cb0[6].yyy + r1.xyz; -#else // Vanilla: has unnecessary "sature()" - r0.xyz = saturate(r0.xyz * cb0[6].yyy + r1.xyz); -#endif - -#if 1 // User secondary gamma (applies in gamma space) (defaults to 1) (fixed to allow negative scRGB values just in case) - o0.xyzw = float4(pow(abs(r0.xyz), cb0[6].zzz) * sign(r0.xyz), saturate(r0.w)); +// 3Dmigoto declarations +#define cmp - + +// Final shader before UI draws in when user brightness slider, or other post +// process parameter driven by the game, are not neutral. + +// Unfortunately this runs after tonemapping because this shader doesn't always +// run, and determining when and when it would not run in earlier passes is +// hard, so the color peak could go beyond the peak display brightness, +// but ultimately it doesn't really matter +void main( + float4 v0: SV_POSITION0, + float2 v1: TEXCOORD0, + out float4 o0: SV_Target0) { + float4 r0, r1; + + r0.xyzw = t0.Sample(s0_s, v1.xy).xyzw; + + // normalize white level to the same range as SDR (theoretically 80 nits) + r0.rgb = renodx::color::gamma::DecodeSafe(r0.rgb, 2.2f); + r0.rgb /= injectedData.toneMapGameNits / injectedData.toneMapUINits; + r0.rgb = renodx::color::gamma::EncodeSafe(r0.rgb, 2.2f); + + r1.xyz = cb0[7].xyz; + r1.w = r0.w; + r0.xyzw = r0.xyzw * cb0[6].xxxx + -r1.xyzw; + r0.xyzw = r0.xyzw * cb0[6].yyyy + r1.xyzw; // remove unecessary saturate() + +#if 0 // Gamma slider (applies in gamma space) (defaults to 1) (fixed to allow negative scRGB values just in case) + o0.xyzw = float4(renodx::math::SafePow(r0.xyz, cb0[6].zzz), saturate(r0.w)); #else - o0.xyzw = r0.xyzw; + o0.rgba = float4(r0.rgb, saturate(r0.a)); #endif - // Tonemapping might also help to fix some scenes that end burning through the UI, possibly because the scene (background) had extremely high values - if (HDR && TonemapHDR) { - const float paperWhite = PaperWhiteNits / renodx::color::srgb::REFERENCE_WHITE; - float3 linearColor = pow(abs(o0.xyz), 2.2) * sign(o0.xyz); - linearColor *= paperWhite; - - const float peakWhite = PeakWhiteNits / renodx::color::srgb::REFERENCE_WHITE; - const float highlightsShoulderStart = paperWhite; // Don't tonemap the "SDR" range (in luminance), we want to keep it looking as it used to look in SDR - linearColor = renodx::tonemap::dice::BT709(linearColor, peakWhite, highlightsShoulderStart); - - linearColor /= paperWhite; - o0.xyz = pow(abs(linearColor), 1.0 / 2.2) * sign(linearColor); - } - // Leave output in gamma space and with a paper white of 80 nits even for HDR so we can blend in the UI just like in SDR (in gamma space) and linearize with an extra pass added at the end. + o0.rgb = renodx::color::gamma::DecodeSafe(o0.rgb, 2.2f); + o0.rgb *= injectedData.toneMapGameNits / injectedData.toneMapUINits; + o0.rgb = renodx::color::gamma::EncodeSafe(o0.rgb, 2.2f); return; -} \ No newline at end of file +} diff --git a/src/games/thetownoflight/shared.h b/src/games/thetownoflight/shared.h index f5acc534..3fa0af86 100644 --- a/src/games/thetownoflight/shared.h +++ b/src/games/thetownoflight/shared.h @@ -1,3 +1,22 @@ +#ifndef SRC_THETOWNOFLIGHT_SHARED_H_ +#define SRC_THETOWNOFLIGHT_SHARED_H_ + #ifndef __cplusplus #include "../../shaders/renodx.hlsl" -#endif \ No newline at end of file +#endif + +// Must be 32bit aligned +// Should be 4x32 +struct ShaderInjectData { + float toneMapPeakNits; + float toneMapGameNits; + float toneMapUINits; +}; + +#ifndef __cplusplus +cbuffer cb13 : register(b13) { + ShaderInjectData injectedData : packoffset(c0); +} +#endif + +#endif // SRC_THETOWNOFLIGHT_SHARED_H_