diff --git a/src/games/tacticalbreachwizards/DICE.hlsl b/src/games/tacticalbreachwizards/DICE.hlsl new file mode 100644 index 00000000..55ae6160 --- /dev/null +++ b/src/games/tacticalbreachwizards/DICE.hlsl @@ -0,0 +1,252 @@ +#include "./shared.h" + +static const float HDR10_MaxWhiteNits = 10000.0f; +static const float FLT_MAX = asfloat(0x7F7FFFFF); // 3.402823466e+38f + +float max3(float a, float b, float c) { + return max(a, max(b, c)); +} + +float max3(float3 v) { + return max3(v.x, v.y, v.z); +} + +static const float PQ_constant_M1 = 0.1593017578125f; +static const float PQ_constant_M2 = 78.84375f; +static const float PQ_constant_C1 = 0.8359375f; +static const float PQ_constant_C2 = 18.8515625f; +static const float PQ_constant_C3 = 18.6875f; + +// PQ (Perceptual Quantizer - ST.2084) encode/decode used for HDR10 BT.2100. +// Clamp type: +// 0 None +// 1 Remove negative numbers +// 2 Remove numbers beyond 0-1 +// 3 Mirror negative numbers +float3 Linear_to_PQ(float3 LinearColor, int clampType = 0) { + float3 LinearColorSign = sign(LinearColor); + if (clampType == 1) { + LinearColor = max(LinearColor, 0.f); + } else if (clampType == 2) { + LinearColor = saturate(LinearColor); + } else if (clampType == 3) { + LinearColor = abs(LinearColor); + } + float3 colorPow = pow(LinearColor, PQ_constant_M1); + float3 numerator = PQ_constant_C1 + PQ_constant_C2 * colorPow; + float3 denominator = 1.f + PQ_constant_C3 * colorPow; + float3 pq = pow(numerator / denominator, PQ_constant_M2); + if (clampType == 3) { + return pq * LinearColorSign; + } + return pq; +} + +float3 PQ_to_Linear(float3 ST2084Color, int clampType = 0) { + float3 ST2084ColorSign = sign(ST2084Color); + if (clampType == 1) { + ST2084Color = max(ST2084Color, 0.f); + } else if (clampType == 2) { + ST2084Color = saturate(ST2084Color); + } else if (clampType == 3) { + ST2084Color = abs(ST2084Color); + } + float3 colorPow = pow(ST2084Color, 1.f / PQ_constant_M2); + float3 numerator = max(colorPow - PQ_constant_C1, 0.f); + float3 denominator = PQ_constant_C2 - (PQ_constant_C3 * colorPow); + float3 linearColor = pow(numerator / denominator, 1.f / PQ_constant_M1); + if (clampType == 3) { + return linearColor * ST2084ColorSign; + } + return linearColor; +} + +// Applies exponential ("Photographic") luminance/luma compression. +// The pow can modulate the curve without changing the values around the edges. +// The max is the max possible range to compress from, to not lose any output +// range if the input range was limited. +float rangeCompress(float X, float Max = asfloat(0x7F7FFFFF)) { + // Branches are for static parameters optimizations + if (Max == FLT_MAX) { + // This does e^X. We expect X to be between 0 and 1. + return 1.f - exp(-X); + } + const float lostRange = exp(-Max); + const float restoreRangeScale = 1.f / (1.f - lostRange); + return (1.f - exp(-X)) * restoreRangeScale; +} + +// Refurbished DICE HDR tonemapper (per channel or luminance). +// Expects "InValue" to be >= "ShoulderStart" and "OutMaxValue" to be > +// "ShoulderStart". +float luminanceCompress(float InValue, float OutMaxValue, + float ShoulderStart = 0.f, + bool ConsiderMaxValue = false, + float InMaxValue = asfloat(0x7F7FFFFF)) { + const float compressableValue = InValue - ShoulderStart; + const float compressableRange = InMaxValue - ShoulderStart; + const float compressedRange = OutMaxValue - ShoulderStart; + const float possibleOutValue = + ShoulderStart + compressedRange * rangeCompress(compressableValue / compressedRange, ConsiderMaxValue ? (compressableRange / compressedRange) : FLT_MAX); +#if 1 + return possibleOutValue; +#else // Enable this branch if "InValue" can be smaller than "ShoulderStart" + return (InValue <= ShoulderStart) ? InValue : possibleOutValue; +#endif +} + +#define DICE_TYPE_BY_LUMINANCE_RGB 0 +// Doing the DICE compression in PQ (either on luminance or each color channel) +// produces a curve that is closer to our "perception" and leaves more detail +// highlights without overly compressing them +#define DICE_TYPE_BY_LUMINANCE_PQ 1 +// Modern HDR displays clip individual rgb channels beyond their "white" peak +// brightness, like, if the peak brightness is 700 nits, any r g b color beyond +// a value of 700/80 will be clipped (not acknowledged, it won't make a +// difference). Tonemapping by luminance, is generally more perception accurate +// but can then generate rgb colors "out of range". This setting fixes them up, +// though it's optional as it's working based on assumptions on how current +// displays work, which might not be true anymore in the future. Note that this +// can create some steep (rough, quickly changing) gradients on very bright +// colors. +#define DICE_TYPE_BY_LUMINANCE_PQ_CORRECT_CHANNELS_BEYOND_PEAK_WHITE 2 +// This might look more like classic SDR tonemappers and is closer to how modern +// TVs and Monitors play back colors (usually they clip each individual channel +// to the peak brightness value, though in their native panel color space, or +// current SDR/HDR mode color space). Overall, this seems to handle bright +// gradients more smoothly, even if it shifts hues more (and generally +// desaturating). +#define DICE_TYPE_BY_CHANNEL_PQ 3 + +struct DICESettings { + uint Type; + // Determines where the highlights curve (shoulder) starts. + // Values between 0.25 and 0.5 are good with DICE by PQ (any type). + // With linear/rgb DICE this barely makes a difference, zero is a good default + // but (e.g.) 0.5 would also work. This should always be between 0 and 1. + float ShoulderStart; + + // For "Type == DICE_TYPE_BY_LUMINANCE_PQ_CORRECT_CHANNELS_BEYOND_PEAK_WHITE" + // only: The sum of these needs to be <= 1, both within 0 and 1. The closer + // the sum is to 1, the more each color channel will be containted within its + // peak range. + float DesaturationAmount; + float DarkeningAmount; +}; + +DICESettings DefaultDICESettings() { + DICESettings Settings; + Settings.Type = DICE_TYPE_BY_CHANNEL_PQ; + Settings.ShoulderStart = + (Settings.Type > DICE_TYPE_BY_LUMINANCE_RGB) + ? (1.f / 3.f) + : 0.f; // TODOFT3: increase value!!! (did I already?) + Settings.DesaturationAmount = 1.0 / 3.0; + Settings.DarkeningAmount = 1.0 / 3.0; + return Settings; +} + +// Tonemapper inspired from DICE. Can work by luminance to maintain hue. +// Takes scRGB colors with a white level (the value of 1 1 1) of 80 nits (sRGB) +// (to not be confused with paper white). Paper white is expected to have +// already been multiplied in. +float3 DICETonemap(float3 Color, float PeakWhite, + const DICESettings Settings /*= DefaultDICESettings()*/) { + const float sourceLuminance = renodx::color::y::from::BT709(Color); + + if (Settings.Type != DICE_TYPE_BY_LUMINANCE_RGB) { + static const float HDR10_MaxWhite = + HDR10_MaxWhiteNits / renodx::color::srgb::REFERENCE_WHITE; + + // We could first convert the peak white to PQ and then apply the "shoulder + // start" alpha to it (in PQ), but tests showed scaling it in linear + // actually produces a better curve and more consistently follows the peak + // across different values + const float shoulderStartPQ = + Linear_to_PQ((Settings.ShoulderStart * PeakWhite) / HDR10_MaxWhite).x; + if (Settings.Type == DICE_TYPE_BY_LUMINANCE_PQ || Settings.Type == DICE_TYPE_BY_LUMINANCE_PQ_CORRECT_CHANNELS_BEYOND_PEAK_WHITE) { + const float sourceLuminanceNormalized = sourceLuminance / HDR10_MaxWhite; + const float sourceLuminancePQ = + Linear_to_PQ(sourceLuminanceNormalized, 1).x; + + if (sourceLuminancePQ > shoulderStartPQ) // Luminance below the shoulder (or below zero) don't + // need to be adjusted + { + const float peakWhitePQ = Linear_to_PQ(PeakWhite / HDR10_MaxWhite).x; + + const float compressedLuminancePQ = + luminanceCompress(sourceLuminancePQ, peakWhitePQ, shoulderStartPQ); + const float compressedLuminanceNormalized = + PQ_to_Linear(compressedLuminancePQ).x; + Color *= compressedLuminanceNormalized / sourceLuminanceNormalized; + + if (Settings.Type == DICE_TYPE_BY_LUMINANCE_PQ_CORRECT_CHANNELS_BEYOND_PEAK_WHITE) { + float3 Color_BT2020 = renodx::color::bt2020::from::BT709(Color); + if (any(Color_BT2020 > PeakWhite)) // Optional "optimization" branch + { + float colorLuminance = renodx::color::y::from::BT2020(Color_BT2020); + float colorLuminanceInExcess = colorLuminance - PeakWhite; + float maxColorInExcess = + max3(Color_BT2020) - PeakWhite; // This is guaranteed to be >= + // "colorLuminanceInExcess" + float brightnessReduction = saturate(renodx::math::SafeDivision( + PeakWhite, max3(Color_BT2020), + 1)); // Fall back to one in case of division by zero + float desaturateAlpha = saturate(renodx::math::SafeDivision( + maxColorInExcess, maxColorInExcess - colorLuminanceInExcess, + 0)); // Fall back to zero in case of division by zero + Color_BT2020 = lerp(Color_BT2020, colorLuminance, + desaturateAlpha * Settings.DesaturationAmount); + Color_BT2020 = + lerp(Color_BT2020, Color_BT2020 * brightnessReduction, + Settings.DarkeningAmount); // Also reduce the brightness to + // partially maintain the hue, + // at the cost of brightness + Color = renodx::color::bt709::from::BT2020(Color_BT2020); + } + } + } + } else // DICE_TYPE_BY_CHANNEL_PQ + { + const float peakWhitePQ = Linear_to_PQ(PeakWhite / HDR10_MaxWhite).x; + + // Tonemap in BT.2020 to more closely match the primaries of modern + // displays + const float3 sourceColorNormalized = + renodx::color::bt2020::from::BT709(Color) / HDR10_MaxWhite; + const float3 sourceColorPQ = Linear_to_PQ(sourceColorNormalized, 1); + + [unroll] + for (uint i = 0; i < 3; + i++) // TODO LUMA: optimize? will the shader compile already convert + // this to float3? Or should we already make a version with no + // branches that works in float3? + { + if (sourceColorPQ[i] > shoulderStartPQ) // Colors below the shoulder (or below zero) don't + // need to be adjusted + { + const float compressedColorPQ = + luminanceCompress(sourceColorPQ[i], peakWhitePQ, shoulderStartPQ); + const float compressedColorNormalized = + PQ_to_Linear(compressedColorPQ).x; + Color[i] = renodx::color::bt709::from::BT2020( + Color[i] * (compressedColorNormalized / sourceColorNormalized[i])) + .x; + } + } + } + } else // DICE_TYPE_BY_LUMINANCE_RGB + { + const float shoulderStart = + PeakWhite * Settings.ShoulderStart; // From alpha to linear range + if (sourceLuminance > shoulderStart) // Luminances below the shoulder (or below zero) don't + // need to be adjusted + { + const float compressedLuminance = + luminanceCompress(sourceLuminance, PeakWhite, shoulderStart); + Color *= compressedLuminance / sourceLuminance; + } + } + + return Color; +} diff --git a/src/games/tacticalbreachwizards/LUT3DBakerNoTonemap_0x995B320A.cs_5_0.hlsl b/src/games/tacticalbreachwizards/LUT3DBakerNoTonemap_0x995B320A.cs_5_0.hlsl new file mode 100644 index 00000000..5d22f31f --- /dev/null +++ b/src/games/tacticalbreachwizards/LUT3DBakerNoTonemap_0x995B320A.cs_5_0.hlsl @@ -0,0 +1,139 @@ +#include "./common.hlsl" + +// https://github.com/Unity-Technologies/Graphics/blob/e42df452b62857a60944aed34f02efa1bda50018/com.unity.postprocessing/PostProcessing/Shaders/Builtins/Lut3DBaker.compute +// KGenLUT3D_NoTonemap + +Texture2D t0 : register(t0); + +SamplerState s0_s : register(s0); + +RWTexture3D u0 : register(u0); + +cbuffer cb0 : register(b0) { + float4 cb0[10]; +} + +#define cmp - + +[numthreads(4, 4, 4)] +void main(uint3 vThreadID: SV_DispatchThreadID) { + float4 r0, r1, r2, r3; + uint4 bitmask, uiDest; + float4 fDest; + + r0.rgb = (uint3)vThreadID.rgb; + r1.rgb = cmp(r0.rgb < cb0[0].rrr); + r0.a = r1.g ? r1.r : 0; + r0.a = r1.b ? r0.a : 0; + if (r0.a != 0) { + // (start) ColorGrade + // (start) LogGrade + // Contrast(r0.rgb, ACEScc_MIDGRAY, cb0[3].b) + r0.rgb = r0.rgb * cb0[0].ggg; + + float3 preContrast = r0.rgb; + + r0.rgb = r0.rgb + float3(-0.413588405, -0.413588405, -0.413588405); // ACEScc_MIDGRAY = 0.4135884 + r0.rgb = r0.rgb * cb0[3].bbb + float3(0.413588405, 0.413588405, 0.413588405); + + r0.rgb = lerp(preContrast, r0.rgb, injectedData.colorGradeLUTStrength); + + r0.rgb = lutShaper(r0.rgb, true); + + float3 preCG = r0.rgb; + + // (start) LinearGrade + // WhiteBalance(r0.rgb, cb0[1].rgb) + r1.r = dot(float3(0.390405, 0.549941, 0.00892631989), r0.rgb); + r1.g = dot(float3(0.0708416030, 0.963172, 0.00135775004), r0.rgb); + r1.b = dot(float3(0.0231081992, 0.128021, 0.936245), r0.rgb); + r0.rgb = r1.rgb * cb0[1].rgb; + r1.r = dot(float3(2.858470, -1.628790, -0.024891), r0.rgb); + r1.g = dot(float3(-0.210182, 1.158200, 0.000324280991), r0.rgb); + r1.b = dot(float3(-0.041812, -0.118169, 1.068670), r0.rgb); + // ColorFilter + r0.rgb = r1.rgb * cb0[2].rgb; + // ChannelMixer(r0.rgb, cb0[4].rgb, cb0[5].rgb, cb0[6].rgb) + r1.r = dot(r0.rgb, cb0[4].rgb); + r1.g = dot(r0.rgb, cb0[5].rgb); + r1.b = dot(r0.rgb, cb0[6].rgb); + // LiftGammaGainHDR(r1.rgb, cb0[7].rgb, cb0[8].rgb, cb0[9].rgb) + r0.rgb = r1.rgb * cb0[9].rgb + cb0[7].rgb; + r1.rgb = saturate(r1.rgb * renodx::math::FLT_MAX + 0.5) * 2.0 - 1.0; + r0.rgb = pow(abs(r0.rgb), cb0[8].rgb); + r0.rgb = r0.rgb * r1.rgb; + // Do NOT feed negative values to RgbToHsv or they'll wrap around + r0.rgb = max(0, r0.rgb); + // RgbToHsv + r0.a = cmp(r0.g >= r0.b); + r0.a = r0.a ? 1.00000 : 0; + r1.rg = r0.bg; + r1.ba = float2(-1, 0.666666687); + r2.rg = r0.gb + -r1.rg; + r2.ba = float2(1, -1); + r1.rgba = r0.aaaa * r2.rgba + r1.rgba; + r0.a = cmp(r0.r >= r1.r); + r0.a = r0.a ? 1.00000 : 0; + r2.rgb = r1.rga; + r2.a = r0.r; + r1.rga = r2.agr; + r1.rgba = -r2.rgba + r1.rgba; + r1.rgba = r0.aaaa * r1.rgba + r2.rgba; + r0.a = min(r1.g, r1.a); + r0.a = -r0.a + r1.r; + r1.g = -r1.g + r1.a; + r1.a = r0.a * 6 + 0.0001; + r1.g = r1.g / r1.a; + r1.g = r1.g + r1.b; + r2.r = abs(r1.g); + r1.g = r1.r + 0.0001; + r2.b = r0.a / r1.g; + // Hue Vs Sat + r2.ga = float2(0.25, 0.25); + r0.a = t0.SampleLevel(s0_s, r2.rg, 0).g; + r0.a = saturate(r0.a); + r0.a = r0.a + r0.a; + // Sat Vs Sat + r1.g = t0.SampleLevel(s0_s, r2.ba, 0).b; + r1.g = saturate(r1.g); + r0.a = dot(r1.gg, r0.aa); + // Lum Vs Sat + r3.r = dot(r0.rgb, float3(0.212672904, 0.715152204, 0.072175)); + r3.ga = float2(0.25, 0.25); + r0.r = t0.SampleLevel(s0_s, r3.rg, 0).a; + r0.r = saturate(r0.r); + r0.r = r0.a * r0.r; + // Hue Vs Hue + r3.b = r2.r + cb0[3].r; + r0.g = t0.SampleLevel(s0_s, r3.ba, 0).r; + r0.g = saturate(r0.g); + r0.g = r3.b + r0.g; + r0.gba = r0.ggg + float3(-0.5, 0.5, -1.5); + r1.g = cmp(r0.g < 0); + r1.b = cmp(1 < r0.g); + r0.g = r1.b ? r0.a : r0.g; + r0.g = r1.g ? r0.b : r0.g; + // HsvToRgb(r0.gba) + r0.gba = r0.ggg + float3(1, 0.666666687, 0.333333343); + r0.gba = frac(r0.gba); + r0.gba = r0.gba * float3(6, 6, 6) + float3(-3, -3, -3); + r0.gba = saturate(abs(r0.gba) + float3(-1, -1, -1)); + r0.gba = r0.gba + float3(-1, -1, -1); + r0.gba = r2.bbb * r0.gba + float3(1, 1, 1); + + // Saturation(r0.gba, cb0[3].g * r0.r) + r1.gba = r0.gba * r1.rrr; + r0.r = dot(cb0[3].gg, r0.rr); + r1.g = dot(r1.gba, float3(0.212672904, 0.715152204, 0.072175)); + r0.gba = r1.rrr * r0.gba + -r1.ggg; + r0.rgb = r0.rrr * r0.gba + r1.ggg; + + r0.rgb = lerp(preCG, r0.rgb, injectedData.colorGradeLUTStrength); + + r0.rgb = applyUserTonemap(r0.rgb); + + r0.a = 1; + u0[vThreadID.xyz] = r0.rgba; + } + return; +} diff --git a/src/games/tacticalbreachwizards/addon.cpp b/src/games/tacticalbreachwizards/addon.cpp new file mode 100644 index 00000000..bbdb7d4a --- /dev/null +++ b/src/games/tacticalbreachwizards/addon.cpp @@ -0,0 +1,359 @@ +/* + * Copyright (C) 2023 Carlos Lopez + * SPDX-License-Identifier: MIT + */ + +#define ImTextureID ImU64 + +#define DEBUG_LEVEL_0 + +#include + +#include +#include + +#include "../../mods/shader.hpp" +#include "../../mods/swapchain.hpp" +#include "../../utils/date.hpp" +#include "../../utils/settings.hpp" +#include "./shared.h" + +namespace { + +renodx::mods::shader::CustomShaders custom_shaders = { + CustomShaderEntry(0xC8C2F1A0), + CustomShaderEntry(0x995B320A), + CustomShaderEntry(0xD863ED6A), +}; + +ShaderInjectData shader_injection; +const std::string build_date = __DATE__; +const std::string build_time = __TIME__; + +renodx::utils::settings::Settings settings = { + new renodx::utils::settings::Setting{ + .key = "toneMapType", + .binding = &shader_injection.toneMapType, + .value_type = renodx::utils::settings::SettingValueType::INTEGER, + .default_value = 3.f, + .can_reset = true, + .label = "Tone Mapper", + .section = "Tone Mapping", + .tooltip = "Sets the tone mapper type", + .labels = {"Vanilla", "None", "ACES", "RenoDRT", "Reinhard+"}, + .tint = 0x3FD9B9, + }, + 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", + .tint = 0x927A13, + .min = 48.f, + .max = 4000.f, + }, + new renodx::utils::settings::Setting{ + .key = "toneMapGameNits", + .binding = &shader_injection.toneMapGameNits, + .default_value = 203.f, + .label = "Game Brightness", + .section = "Tone Mapping", + .tooltip = "Sets the value of 100% white in nits", + .tint = 0x927A13, + .min = 48.f, + .max = 500.f, + }, + new renodx::utils::settings::Setting{ + .key = "toneMapUINits", + .binding = &shader_injection.toneMapUINits, + .default_value = 203.f, + .label = "UI Brightness", + .section = "Tone Mapping", + .tooltip = "Sets the brightness of UI and HUD elements in nits", + .tint = 0x927A13, + .min = 48.f, + .max = 500.f, + }, + new renodx::utils::settings::Setting{ + .key = "colorGradeExposure", + .binding = &shader_injection.colorGradeExposure, + .default_value = 1.f, + .label = "Exposure", + .section = "Color Grading", + .tint = 0x610512, + .max = 10.f, + .format = "%.2f", + }, + new renodx::utils::settings::Setting{ + .key = "colorGradeHighlights", + .binding = &shader_injection.colorGradeHighlights, + .default_value = 50.f, + .label = "Highlights", + .section = "Color Grading", + .tint = 0x610512, + .max = 100.f, + .parse = [](float value) { return value * 0.02f; }, + }, + new renodx::utils::settings::Setting{ + .key = "colorGradeShadows", + .binding = &shader_injection.colorGradeShadows, + .default_value = 50.f, + .label = "Shadows", + .section = "Color Grading", + .tint = 0x610512, + .max = 100.f, + .parse = [](float value) { return value * 0.02f; }, + }, + new renodx::utils::settings::Setting{ + .key = "colorGradeContrast", + .binding = &shader_injection.colorGradeContrast, + .default_value = 50.f, + .label = "Contrast", + .section = "Color Grading", + .tint = 0x610512, + .max = 100.f, + .parse = [](float value) { return value * 0.02f; }, + }, + new renodx::utils::settings::Setting{ + .key = "colorGradeSaturation", + .binding = &shader_injection.colorGradeSaturation, + .default_value = 50.f, + .label = "Saturation", + .section = "Color Grading", + .tint = 0x610512, + .max = 100.f, + .parse = [](float value) { return value * 0.02f; }, + }, + new renodx::utils::settings::Setting{ + .key = "colorGradeBlowout", + .binding = &shader_injection.colorGradeBlowout, + .default_value = 50.f, + .label = "Blowout", + .section = "Color Grading", + .tooltip = "Controls highlight desaturation due to overexposure.", + .tint = 0x610512, + .max = 100.f, + .is_enabled = []() { return shader_injection.toneMapType >= 3.f; }, + .parse = [](float value) { return (value * 0.02f) - 1.f; }, + }, + new renodx::utils::settings::Setting{ + .key = "colorGradeFlare", + .binding = &shader_injection.colorGradeFlare, + .default_value = 0.f, + .label = "Flare", + .section = "Color Grading", + .tooltip = "Embrace the darkness... (Gently.)", + .tint = 0x896895, + .max = 100.f, + .is_enabled = []() { return shader_injection.toneMapType == 3.f; }, + .parse = [](float value) { return value * 0.02f; }, + }, + new renodx::utils::settings::Setting{ + .key = "colorGradeLUTStrength", + .binding = &shader_injection.colorGradeLUTStrength, + .default_value = 100.f, + .label = "LUT Strength", + .section = "Color Grading", + .tint = 0x610512, + .max = 100.f, + .parse = [](float value) { return value * 0.01f; }, + }, + new renodx::utils::settings::Setting{ + .key = "fxBloom", + .binding = &shader_injection.fxBloom, + .default_value = 50.f, + .label = "Bloom", + .section = "Effects", + .tint = 0xEC4E1B, + .max = 100.f, + .parse = [](float value) { return value * 0.02f; }, + }, + new renodx::utils::settings::Setting{ + .key = "fxVignette", + .binding = &shader_injection.fxVignette, + .default_value = 50.f, + .label = "Vignette", + .section = "Effects", + .tint = 0xEC4E1B, + .max = 100.f, + .parse = [](float value) { return value * 0.02f; }, + }, + new renodx::utils::settings::Setting{ + .key = "fxFilmGrain", + .binding = &shader_injection.fxFilmGrain, + .default_value = 0.f, + .label = "Film Grain", + .section = "Effects", + .tint = 0xEC4E1B, + .max = 100.f, + .parse = [](float value) { return value * 0.02f; }, + }, + new renodx::utils::settings::Setting{ + .value_type = renodx::utils::settings::SettingValueType::TEXT, + .label = "RenoDX by ShortFuse, game mod by Ritsu. Shout-out to HDR Den & Not Voosh for (lending) their code!", + .section = "About", + }, + new renodx::utils::settings::Setting{ + .value_type = renodx::utils::settings::SettingValueType::BUTTON, + .label = "HDR Den Discord", + .section = "About", + .group = "button-line-1", + .tint = 0x5865F2, + .on_change = []() { + static const std::string obfuscated_link = std::string("start https://discord.gg/XUhv") + std::string("tR54yc"); + system(obfuscated_link.c_str()); + }, + }, + new renodx::utils::settings::Setting{ + .value_type = renodx::utils::settings::SettingValueType::BUTTON, + .label = "Github", + .section = "About", + .group = "button-line-1", + .on_change = []() { + ShellExecute(0, "open", "https://github.com/clshortfuse/renodx", 0, 0, SW_SHOW); + }, + }, + new renodx::utils::settings::Setting{ + .value_type = renodx::utils::settings::SettingValueType::BUTTON, + .label = "ShortFuse's Ko-Fi", + .section = "About", + .group = "button-line-1", + .tint = 0xFF5F5F, + .on_change = []() { + ShellExecute(0, "open", "https://ko-fi.com/shortfuse", 0, 0, SW_SHOW); + }, + }, + new renodx::utils::settings::Setting{ + .value_type = renodx::utils::settings::SettingValueType::BUTTON, + .label = "HDR Den's Ko-Fi", + .section = "About", + .group = "button-line-1", + .tint = 0xFF5F5F, + .on_change = []() { + ShellExecute(0, "open", "https://ko-fi.com/hdrden", 0, 0, SW_SHOW); + }, + }, + new renodx::utils::settings::Setting{ + .value_type = renodx::utils::settings::SettingValueType::TEXT, + .label = "This build was compiled on " + build_date + " at " + build_time + ".", + .section = "About", + }, +}; + +void OnPresetOff() { + renodx::utils::settings::UpdateSetting("toneMapType", 0.f); + renodx::utils::settings::UpdateSetting("toneMapPeakNits", 203.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("colorGradeHighlights", 50.f); + renodx::utils::settings::UpdateSetting("colorGradeShadows", 50.f); + renodx::utils::settings::UpdateSetting("colorGradeContrast", 50.f); + renodx::utils::settings::UpdateSetting("colorGradeSaturation", 50.f); + renodx::utils::settings::UpdateSetting("colorGradeBlowout", 0.f); + renodx::utils::settings::UpdateSetting("colorGradeLUTStrength", 100.f); + renodx::utils::settings::UpdateSetting("fxVignette", 50.f); +} + +bool fired_on_init_swapchain = false; + +void OnInitSwapchain(reshade::api::swapchain* swapchain) { + if (fired_on_init_swapchain) return; + fired_on_init_swapchain = true; + auto peak = renodx::utils::swapchain::GetPeakNits(swapchain); + if (peak.has_value()) { + settings[1]->default_value = peak.value(); + settings[1]->can_reset = true; + } +} + +auto start = std::chrono::steady_clock::now(); + +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 end = std::chrono::steady_clock::now(); + shader_injection.elapsedTime = std::chrono::duration_cast(end - start).count(); +} + +} // namespace + +// NOLINTBEGIN(readability-identifier-naming) + +extern "C" __declspec(dllexport) constexpr const char* NAME = "RenoDX"; +extern "C" __declspec(dllexport) constexpr const char* DESCRIPTION = "RenoDX for Tactical Breach Wizards"; + +// NOLINTEND(readability-identifier-naming) + +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::force_pipeline_cloning = true; + renodx::mods::swapchain::force_borderless = false; + renodx::mods::swapchain::prevent_full_screen = false; + renodx::mods::shader::allow_multiple_push_constants = true; + renodx::mods::swapchain::use_resource_cloning = true; + renodx::mods::swapchain::swap_chain_proxy_vertex_shader = __swap_chain_proxy_vertex_shader; + renodx::mods::swapchain::swap_chain_proxy_pixel_shader = __swap_chain_proxy_pixel_shader; + + // RG11B10_float (UAV stuff) + renodx::mods::swapchain::swap_chain_upgrade_targets.push_back({.old_format = reshade::api::format::r11g11b10_float, + .new_format = reshade::api::format::r16g16b16a16_float, + .ignore_size = true, + .view_upgrades = { + {{reshade::api::resource_usage::shader_resource, + reshade::api::format::r11g11b10_float}, + reshade::api::format::r16g16b16a16_float}, + {{reshade::api::resource_usage::unordered_access, + reshade::api::format::r11g11b10_float}, + reshade::api::format::r16g16b16a16_float}, + {{reshade::api::resource_usage::render_target, + reshade::api::format::r11g11b10_float}, + reshade::api::format::r16g16b16a16_float}, + }}); + + // RGBA8_typeless + renodx::mods::swapchain::swap_chain_upgrade_targets.push_back({ + .old_format = reshade::api::format::r8g8b8a8_typeless, + .new_format = reshade::api::format::r16g16b16a16_typeless, + .ignore_size = true, + }); + + /* renodx::mods::swapchain::swap_chain_upgrade_targets.push_back({ + .old_format = reshade::api::format::r8g8b8a8_typeless, + .new_format = reshade::api::format::r16g16b16a16_float, + .index = 0, + .aspect_ratio = 16.f / 9.f, + }); + renodx::mods::swapchain::swap_chain_upgrade_targets.push_back({ + .old_format = reshade::api::format::r11g11b10_float, + .new_format = reshade::api::format::r16g16b16a16_float, + .dimensions = {.width = 640, .height = 360}, + }); */ + + reshade::register_event(OnInitSwapchain); + reshade::register_event(OnPresent); + + break; + case DLL_PROCESS_DETACH: + reshade::unregister_addon(h_module); + reshade::unregister_event(OnInitSwapchain); + break; + } + + renodx::utils::settings::Use(fdw_reason, &settings, &OnPresetOff); + + renodx::mods::swapchain::Use(fdw_reason, &shader_injection); + + renodx::mods::shader::Use(fdw_reason, custom_shaders, &shader_injection); + + return TRUE; +} \ No newline at end of file diff --git a/src/games/tacticalbreachwizards/common.hlsl b/src/games/tacticalbreachwizards/common.hlsl new file mode 100644 index 00000000..f99527e3 --- /dev/null +++ b/src/games/tacticalbreachwizards/common.hlsl @@ -0,0 +1,162 @@ +#include "./DICE.hlsl" +#include "./shared.h" + +//-----EFFECTS-----// +float3 applyFilmGrain(float3 outputColor, float2 screen) { + float3 grainedColor = renodx::effects::ApplyFilmGrain( + outputColor, + screen, + frac(injectedData.elapsedTime / 1000.f), + injectedData.fxFilmGrain * 0.03f, + 1.f); + return grainedColor; +} + +float UpgradeToneMapRatio(float ap1_color_hdr, float ap1_color_sdr, float ap1_post_process_color) { + if (ap1_color_hdr < ap1_color_sdr) { + // If substracting (user contrast or paperwhite) scale down instead + // Should only apply on mismatched HDR + return ap1_color_hdr / ap1_color_sdr; + } else { + float ap1_delta = ap1_color_hdr - ap1_color_sdr; + ap1_delta = max(0, ap1_delta); // Cleans up NaN + const float ap1_new = ap1_post_process_color + ap1_delta; + + const bool ap1_valid = (ap1_post_process_color > 0); // Cleans up NaN and ignore black + return ap1_valid ? (ap1_new / ap1_post_process_color) : 0; + } +} +float3 UpgradeToneMapPerChannel(float3 color_hdr, float3 color_sdr, float3 post_process_color, float post_process_strength) { + // float ratio = 1.f; + + float3 bt2020_hdr = max(0, renodx::color::bt2020::from::BT709(color_hdr)); + float3 bt2020_sdr = max(0, renodx::color::bt2020::from::BT709(color_sdr)); + float3 bt2020_post_process = max(0, renodx::color::bt2020::from::BT709(post_process_color)); + + float3 ratio = float3( + UpgradeToneMapRatio(bt2020_hdr.r, bt2020_sdr.r, bt2020_post_process.r), + UpgradeToneMapRatio(bt2020_hdr.g, bt2020_sdr.g, bt2020_post_process.g), + UpgradeToneMapRatio(bt2020_hdr.b, bt2020_sdr.b, bt2020_post_process.b)); + + float3 color_scaled = max(0, bt2020_post_process * ratio); + color_scaled = renodx::color::bt709::from::BT2020(color_scaled); + float peak_correction = saturate(1.f - renodx::color::y::from::BT2020(bt2020_post_process)); + color_scaled = renodx::color::correct::Hue(color_scaled, post_process_color, peak_correction); + return lerp(color_hdr, color_scaled, post_process_strength); +} + +//-----SCALING-----// +float3 PostToneMapScale(float3 color) { + color = renodx::color::srgb::EncodeSafe(color); + color = renodx::color::gamma::DecodeSafe(color); + color *= injectedData.toneMapGameNits / injectedData.toneMapUINits; + color = renodx::color::gamma::EncodeSafe(color); + return color; +} + +float3 FinalizeOutput(float3 color) { + color = renodx::color::gamma::DecodeSafe(color); + color *= injectedData.toneMapUINits; + color = min(color, injectedData.toneMapPeakNits); // Clamp UI or Videos + + color = renodx::color::bt709::clamp::BT2020(color); + color = color / 80.f; + + return color; +} + +float3 lutShaper(float3 color, bool builder = false) { + color = builder ? renodx::color::pq::Decode(color, 100.f) + : renodx::color::pq::Encode(color, 100.f); + + return color; +} + +float3 UIScale(float3 color) { + color = renodx::color::correct::GammaSafe(color); + color *= injectedData.toneMapUINits / 80.f; + color = renodx::color::correct::GammaSafe(color, true); + + return color; +} + +//-----TONEMAP-----// +float3 applyReinhardPlus(float3 color, renodx::tonemap::Config RhConfig) { + float RhPeak = RhConfig.peak_nits / RhConfig.game_nits; + if (RhConfig.gamma_correction == 1.f) { + RhPeak = renodx::color::correct::Gamma(RhPeak, true); + } + + color = renodx::color::ap1::from::BT709(color); + float y = renodx::color::y::from::AP1(color * RhConfig.exposure); + color = renodx::color::grade::UserColorGrading(color, RhConfig.exposure, RhConfig.highlights, RhConfig.shadows, RhConfig.contrast); + color = renodx::tonemap::ReinhardScalable(color, RhPeak, 0.f, 0.18f, RhConfig.mid_gray_value); + color = renodx::color::bt709::from::AP1(color); + if (RhConfig.reno_drt_blowout != 0.f || RhConfig.saturation != 1.f) { + float3 perceptual_new; + + if (RhConfig.reno_drt_hue_correction_method == 0u) { + perceptual_new = renodx::color::oklab::from::BT709(color); + } else if (RhConfig.reno_drt_hue_correction_method == 1u) { + perceptual_new = renodx::color::ictcp::from::BT709(color); + } else if (RhConfig.reno_drt_hue_correction_method == 2u) { + perceptual_new = renodx::color::dtucs::uvY::from::BT709(color).zxy; + } + + if (RhConfig.reno_drt_blowout != 0.f) { + perceptual_new.yz *= lerp(1.f, 0.f, saturate(pow(y / (10000.f / 100.f), (1.f - RhConfig.reno_drt_blowout)))); + } + + perceptual_new.yz *= RhConfig.saturation; + + if (RhConfig.reno_drt_hue_correction_method == 0u) { + color = renodx::color::bt709::from::OkLab(perceptual_new); + } else if (RhConfig.reno_drt_hue_correction_method == 1u) { + color = renodx::color::bt709::from::ICtCp(perceptual_new); + } else if (RhConfig.reno_drt_hue_correction_method == 2u) { + color = renodx::color::bt709::from::dtucs::uvY(perceptual_new.yzx); + } + } + color = renodx::color::bt709::clamp::AP1(color); + return color; +} + +float3 applyUserTonemap(float3 untonemapped) { + float3 outputColor = untonemapped; + float midGray = renodx::color::y::from::BT709(renodx::tonemap::unity::BT709(float3(0.18f, 0.18f, 0.18f))); + float3 hueCorrectionColor = renodx::tonemap::unity::BT709(outputColor); + renodx::tonemap::Config config = renodx::tonemap::config::Create(); + + config.type = injectedData.toneMapType; + config.peak_nits = injectedData.toneMapPeakNits; + config.game_nits = injectedData.toneMapGameNits; + config.gamma_correction = 1.f; + config.exposure = injectedData.colorGradeExposure; + config.highlights = injectedData.colorGradeHighlights; + config.shadows = injectedData.colorGradeShadows; + config.contrast = injectedData.colorGradeContrast; + config.saturation = injectedData.colorGradeSaturation; + config.mid_gray_value = midGray; + config.mid_gray_nits = midGray * 100; + config.reno_drt_blowout = injectedData.colorGradeBlowout; + config.reno_drt_flare = 0.010 * injectedData.colorGradeFlare; + config.reno_drt_tone_map_method = renodx::tonemap::renodrt::config::tone_map_method::DANIELE; + + if (injectedData.toneMapType >= 3.f) { + outputColor = renodx::color::correct::Hue(outputColor, hueCorrectionColor, 1.f); + } + if (injectedData.toneMapType == 2.f) { // ACES + config.contrast *= 0.75f; + config.saturation *= 0.8f; + } + if (injectedData.toneMapType == 4.f) { + // Reinhard+ + // We trust in Voosh defaults + config.saturation *= 1.1f; + outputColor = applyReinhardPlus(outputColor, config); + } else { + outputColor = renodx::tonemap::config::Apply(outputColor, config); + } + + return outputColor; +} diff --git a/src/games/tacticalbreachwizards/glow_composite_0xD863ED6A.ps_4_0.hlsl b/src/games/tacticalbreachwizards/glow_composite_0xD863ED6A.ps_4_0.hlsl new file mode 100644 index 00000000..805d9c78 --- /dev/null +++ b/src/games/tacticalbreachwizards/glow_composite_0xD863ED6A.ps_4_0.hlsl @@ -0,0 +1,40 @@ +#include "./common.hlsl" + +// ---- Created with 3Dmigoto v1.3.16 on Fri Jan 3 12:33:59 2025 +Texture2D t1 : register(t1); + +Texture2D t0 : register(t0); + +SamplerState s1_s : register(s1); + +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, + float2 w1: TEXCOORD1, + out float4 o0: SV_TARGET0) { + float4 r0, r1; + uint4 bitmask, uiDest; + float4 fDest; + + r0.xy = w1.xy * cb0[5].xy + cb0[5].zw; + r0.xyzw = t1.Sample(s1_s, r0.xy).xyzw; + r0.xyz = cb0[6].xxx * r0.xyz * injectedData.fxBloom; + r1.xy = v1.xy * cb0[3].xy + cb0[3].zw; + r1.xyzw = t0.Sample(s0_s, r1.xy).xyzw; + o0.xyz = r0.xyz * cb0[6].yzw + r1.xyz; + if (injectedData.fxFilmGrain > 0.f) { + o0.rgb = applyFilmGrain(o0.rgb, w1); + } + o0.rgb = PostToneMapScale(o0.rgb); + o0.w = 1; + return; +} diff --git a/src/games/tacticalbreachwizards/output_0xC8C2F1A0.ps_4_0.hlsl b/src/games/tacticalbreachwizards/output_0xC8C2F1A0.ps_4_0.hlsl new file mode 100644 index 00000000..0fa02153 --- /dev/null +++ b/src/games/tacticalbreachwizards/output_0xC8C2F1A0.ps_4_0.hlsl @@ -0,0 +1,117 @@ +#include "./common.hlsl" + +// ---- Created with 3Dmigoto v1.3.16 on Thu Jan 2 22:18:43 2025 +Texture2D t3 : register(t3); + +Texture3D t2 : register(t2); + +Texture2D t1 : register(t1); + +Texture2D t0 : register(t0); + +SamplerState s3_s : register(s3); + +SamplerState s2_s : register(s2); + +SamplerState s1_s : register(s1); + +SamplerState s0_s : register(s0); + +cbuffer cb0 : register(b0) { + float4 cb0[43]; +} + +// 3Dmigoto declarations +#define cmp - + +void main( + float4 v0: SV_POSITION0, + float2 v1: TEXCOORD0, + float2 w1: TEXCOORD1, + out float4 o0: SV_Target0) { + float4 r0, r1, r2, r3; + uint4 bitmask, uiDest; + float4 fDest; + + r0.xyzw = t1.Sample(s1_s, v1.xy).xyzw; + r1.xyzw = t0.Sample(s0_s, w1.xy).xyzw; + r0.yzw = float3(0.0773993805, 0.0773993805, 0.0773993805) * r1.xyz; + r2.xyz = float3(0.0549999997, 0.0549999997, 0.0549999997) + r1.xyz; + r2.xyz = float3(0.947867334, 0.947867334, 0.947867334) * r2.xyz; + r2.xyz = max(float3(1.1920929e-07, 1.1920929e-07, 1.1920929e-07), abs(r2.xyz)); + r2.xyz = log2(r2.xyz); + r2.xyz = float3(2.4000001, 2.4000001, 2.4000001) * r2.xyz; + r2.xyz = exp2(r2.xyz); + r1.xyz = cmp(float3(0.0404499993, 0.0404499993, 0.0404499993) >= r1.xyz); + r0.yzw = r1.xyz ? r0.yzw : r2.xyz; + r0.xyz = r0.yzw * r0.xxx; + r0.w = cmp(cb0[40].y < 0.5); + if (r0.w != 0) { + r1.xy = -cb0[38].xy + v1.xy; + r1.yz = cb0[39].xx * abs(r1.yx) * min(1, injectedData.fxVignette); + r0.w = cb0[22].x / cb0[22].y; + r0.w = -1 + r0.w; + r0.w = cb0[39].w * r0.w + 1; + r1.x = r1.z * r0.w; + r1.xy = saturate(r1.xy); + r1.xy = log2(r1.xy); + r1.xy = cb0[39].zz * r1.xy; + r1.xy = exp2(r1.xy); + r0.w = dot(r1.xy, r1.xy); + r0.w = 1 + -r0.w; + r0.w = max(0, r0.w); + r0.w = log2(r0.w); + r0.w = cb0[39].y * r0.w * max(1, injectedData.fxVignette); + r0.w = exp2(r0.w); + r1.xyz = float3(1, 1, 1) + -cb0[37].xyz; + r1.xyz = r0.www * r1.xyz + cb0[37].xyz; + r2.xyz = r1.xyz * r0.xyz; + r1.x = -1 + r1.w; + r2.w = r0.w * r1.x + 1; + } else { + r3.xyzw = t3.Sample(s3_s, v1.xy).xyzw; + r1.xyz = float3(1, 1, 1) + -cb0[37].xyz; + r1.xyz = r3.www * r1.xyz + cb0[37].xyz; + r1.xyz = r0.xyz * r1.xyz + -r0.xyz; + r2.xyz = cb0[40].xxx * r1.xyz + r0.xyz; + r0.x = -1 + r1.w; + r2.w = r3.w * r0.x + 1; + } + r0.xyzw = cb0[36].zzzz * r2.xyzw; + + float3 lutInput = lutShaper(r0.rgb); + float3 lutColor = renodx::lut::Sample(t2, s2_s, lutInput); + o0.rgb = lutColor; + + if (false) { + r0.xyz = r0.xyz * float3(5.55555582, 5.55555582, 5.55555582) + float3(0.0479959995, 0.0479959995, 0.0479959995); + r0.xyz = log2(r0.xyz); + r0.xyz = saturate(r0.xyz * float3(0.0734997839, 0.0734997839, 0.0734997839) + float3(0.386036009, 0.386036009, 0.386036009)); + r0.xyz = cb0[36].yyy * r0.xyz; + r1.x = 0.5 * cb0[36].x; + r0.xyz = r0.xyz * cb0[36].xxx + r1.xxx; + r1.xyzw = t2.Sample(s2_s, r0.xyz).xyzw; + r0.x = cmp(0.5 < cb0[42].x); + if (r0.x != 0) { + r0.xyz = saturate(r1.xyz); + o0.w = dot(r0.xyz, float3(0.212672904, 0.715152204, 0.0721750036)); + } else { + o0.w = r0.w; + } + r0.xyz = float3(12.9200001, 12.9200001, 12.9200001) * r1.xyz; + r2.xyz = max(float3(1.1920929e-07, 1.1920929e-07, 1.1920929e-07), abs(r1.xyz)); + r2.xyz = log2(r2.xyz); + r2.xyz = float3(0.416666657, 0.416666657, 0.416666657) * r2.xyz; + r2.xyz = exp2(r2.xyz); + r2.xyz = r2.xyz * float3(1.05499995, 1.05499995, 1.05499995) + float3(-0.0549999997, -0.0549999997, -0.0549999997); + r1.xyz = cmp(float3(0.00313080009, 0.00313080009, 0.00313080009) >= r1.xyz); + o0.xyz = r1.xyz ? r0.xyz : r2.xyz; + } + + if (injectedData.toneMapType == 0.f) { + o0.rgb = saturate(o0.rgb); + } + o0.w = r0.w; + + return; +} diff --git a/src/games/tacticalbreachwizards/output_0xEB23B0ED.ps_5_0.hlsl b/src/games/tacticalbreachwizards/output_0xEB23B0ED.ps_5_0.hlsl new file mode 100644 index 00000000..9b247907 --- /dev/null +++ b/src/games/tacticalbreachwizards/output_0xEB23B0ED.ps_5_0.hlsl @@ -0,0 +1,39 @@ +// ---- Created with 3Dmigoto v1.3.16 on Fri Jan 3 12:27:45 2025 +Texture2D t1 : register(t1); + +Texture2D t0 : register(t0); + +SamplerState s1_s : register(s1); + +SamplerState s0_s : register(s0); + +cbuffer cb0 : register(b0) { + float4 cb0[29]; +} + +// 3Dmigoto declarations +#define cmp - + +void main( + float4 v0: SV_POSITION0, + float2 v1: TEXCOORD0, + float2 w1: TEXCOORD1, + out float4 o0: SV_Target0) { + float4 r0, r1; + uint4 bitmask, uiDest; + float4 fDest; + + r0.xy = v1.xy * cb0[28].xy + cb0[28].zw; + r0.x = t0.Sample(s0_s, r0.xy).w; + r0.x = r0.x * 2 + -1; + r0.y = 1 + -abs(r0.x); + r0.x = saturate(r0.x * 3.40282347e+38 + 0.5); + r0.x = r0.x * 2 + -1; + r0.y = sqrt(r0.y); + r0.y = 1 + -r0.y; + r0.x = r0.x * r0.y; + r1.xyzw = t1.Sample(s1_s, w1.xy).xyzw; + o0.xyz = r0.xxx * float3(0.00392156886, 0.00392156886, 0.00392156886) + r1.xyz; + o0.w = r1.w; + return; +} diff --git a/src/games/tacticalbreachwizards/shared.h b/src/games/tacticalbreachwizards/shared.h new file mode 100644 index 00000000..ef632a39 --- /dev/null +++ b/src/games/tacticalbreachwizards/shared.h @@ -0,0 +1,35 @@ +#ifndef SRC_TACTICAL_BREACH_WIZARDS_SHARED_H_ +#define SRC_TACTICAL_BREACH_WIZARDS_SHARED_H_ + +#ifndef __cplusplus +#include "../../shaders/renodx.hlsl" +#endif + +// Must be 32bit aligned +// Should be 4x32 +struct ShaderInjectData { + float toneMapType; + float toneMapPeakNits; + float toneMapGameNits; + float toneMapUINits; + float colorGradeExposure; + float colorGradeHighlights; + float colorGradeShadows; + float colorGradeContrast; + float colorGradeSaturation; + float colorGradeBlowout; + float colorGradeFlare; + float colorGradeLUTStrength; + float fxBloom; + float fxVignette; + float fxFilmGrain; + float elapsedTime; +}; + +#ifndef __cplusplus +cbuffer cb13 : register(b13) { + ShaderInjectData injectedData : packoffset(c0); +} +#endif + +#endif // SRC_TACTICAL_BREACH_WIZARDS_SHARED_H_ \ No newline at end of file diff --git a/src/games/tacticalbreachwizards/swap_chain_proxy_pixel_shader.ps_4_0.hlsl b/src/games/tacticalbreachwizards/swap_chain_proxy_pixel_shader.ps_4_0.hlsl new file mode 100644 index 00000000..1743d2d2 --- /dev/null +++ b/src/games/tacticalbreachwizards/swap_chain_proxy_pixel_shader.ps_4_0.hlsl @@ -0,0 +1,12 @@ +#include "./common.hlsl" +#include "./shared.h" + +Texture2D t0 : register(t0); +SamplerState s0 : register(s0); +float4 main(float4 vpos: SV_POSITION, float2 uv: TEXCOORD0) + : SV_TARGET { + float4 color = t0.Sample(s0, uv); + color.rgb = FinalizeOutput(color.rgb); + color.a = 1.f; + return color; +} diff --git a/src/games/tacticalbreachwizards/swap_chain_proxy_vertex_shader.vs_4_0.hlsl b/src/games/tacticalbreachwizards/swap_chain_proxy_vertex_shader.vs_4_0.hlsl new file mode 100644 index 00000000..aaee1e17 --- /dev/null +++ b/src/games/tacticalbreachwizards/swap_chain_proxy_vertex_shader.vs_4_0.hlsl @@ -0,0 +1,6 @@ +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); +} \ No newline at end of file