Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
5961f9e
WIP: HDR
Filoppi Apr 19, 2025
15fe8c5
Fix shader build
Filoppi Apr 19, 2025
a1f0f98
Fix shader
Filoppi Apr 20, 2025
609a5f4
Fix textures not reloading when toggling the button live
Filoppi Apr 20, 2025
37ca741
More
Filoppi Apr 21, 2025
ba12836
Fix mac
Filoppi Apr 21, 2025
1d4a024
Remove old hacky HDR implementation
Filoppi Apr 22, 2025
834cbce
Remove unnecessary force SW blends
Filoppi Apr 22, 2025
d89c9b5
Hide dithering in HDR
Filoppi Apr 22, 2025
634e15d
Remove hacky DX11 blends
Filoppi Apr 22, 2025
66e32b1
Fix HDR render api toggle
Filoppi Apr 22, 2025
a456050
Improve clamping/round for shuffles
Filoppi Apr 22, 2025
60d3476
Shader improvements
Filoppi Apr 22, 2025
2775467
Revert "Remove unnecessary force SW blends"
Filoppi Apr 22, 2025
2a3c2ad
Add HDR custom textures support
Filoppi Apr 23, 2025
6ec8c4e
Minor polish
Filoppi Apr 23, 2025
a8d2839
Always quantize HDR textures alphas (fixes Jak games shadows)
Filoppi Apr 23, 2025
876d506
Add linear tfx sampler
Filoppi Apr 24, 2025
c11c5cf
Specify default filter
Filoppi Apr 24, 2025
3bff2ec
Add new flag to HW blends combinations to specify whichs ones are sub…
Filoppi Apr 25, 2025
f378e6c
Add multiple HDR modes for quick compatibility testing
Filoppi Apr 26, 2025
d6cf691
WIP Stretch textures
Filoppi Apr 26, 2025
35a4389
More WIP
Filoppi Apr 26, 2025
c894f5d
Grey out dithering (instead of hiding it) in HDR
Filoppi Apr 26, 2025
5037901
Make sure CopyRect() doesn't crash if it's reading or writing to out …
Filoppi Apr 26, 2025
441aa7c
Fix negative blend flag
Filoppi Apr 26, 2025
7ef984b
Lower max paper white to 400 as 500 could go beyond the peak brightness
Filoppi Apr 26, 2025
e769729
Fix LUT extrapolation reading the size from the wrong texture
Filoppi Apr 26, 2025
bfcebba
Polish
Filoppi Apr 26, 2025
133e9c6
WIP double shuffle detection (e.g. swap g<->a or r<->g without any ot…
Filoppi Apr 26, 2025
de22613
Fix blend flags properly
Filoppi Apr 27, 2025
ffb19b5
Conflict fixes
Filoppi Apr 27, 2025
815f0e4
Explain texture age
Filoppi Apr 27, 2025
1e4bba5
Simplify hazard copy intersection handling
Filoppi Apr 27, 2025
825dff4
Improve LUT extrapolation
Filoppi Apr 27, 2025
2e596f6
Fix FSMASK decimals
Filoppi Apr 27, 2025
5ebdf07
Polish
Filoppi Apr 27, 2025
79ea166
Clamp alpha in HDR for extra safety, some textures accumulate every f…
Filoppi Apr 27, 2025
ba1c176
Fix max brightness
Filoppi Apr 27, 2025
4bd54a8
Fix uint mismatch warning
Filoppi Apr 27, 2025
a90fc04
make DX shader compiler also write log on disc when there's just warn…
Filoppi Apr 27, 2025
85cadc6
Improving float to int rounding
Filoppi Apr 28, 2025
e86af4b
Fix shader error printing out the wrong name
Filoppi May 5, 2025
37fe7c0
Split up HDR into 4 different presets (including "Off", which is iden…
Filoppi May 5, 2025
5af5cea
Some HDR fixes
Filoppi May 5, 2025
9718dac
Fix HDR peak brightness not applying correctly
Filoppi May 11, 2025
f739816
Add SDR->HDR hue restoration and improve the HDR in SDR look (by givi…
Filoppi May 11, 2025
1bd6aa0
Better support for Mac OS
Filoppi May 11, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 41 additions & 34 deletions bin/resources/shaders/common/fxaa.fx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
#define FXAA_GLSL_VK 0
#endif

#ifndef PS_HDR
#define PS_HDR 0
#endif

#define UHQ_FXAA 1 //High Quality Fast Approximate Anti Aliasing. Adapted for GS from Timothy Lottes FXAA 3.11.
#define FxaaSubpixMax 0.0 //[0.00 to 1.00] Amount of subpixel aliasing removal. 0.00: Edge only antialiasing (no blurring)
#define FxaaEarlyExit 1 //[0 or 1] Use Fxaa early exit pathing. When disabled, the entire scene is antialiased(FSAA). 0 is off, 1 is on.
Expand Down Expand Up @@ -102,47 +106,30 @@ struct FxaaTex { SamplerState smpl; Texture2D tex; };
#define FXAA_QUALITY_P11 8.0
#define FXAA_QUALITY_P12 8.0

#define DEFAULT_GAMMA 2.2

/*------------------------------------------------------------------------------
[GAMMA PREPASS CODE SECTION]
------------------------------------------------------------------------------*/
float RGBLuminance(float3 color)
{
const float3 lumCoeff = float3(0.2126729, 0.7151522, 0.0721750);
const float3 lumCoeff = float3(0.2126, 0.7152, 0.0722);
return dot(color.rgb, lumCoeff);
}

float3 RGBGammaToLinear(float3 color, float gamma)
{
color = FxaaSat(color);
color.r = (color.r <= 0.0404482362771082) ?
color.r / 12.92 : pow((color.r + 0.055) / 1.055, gamma);
color.g = (color.g <= 0.0404482362771082) ?
color.g / 12.92 : pow((color.g + 0.055) / 1.055, gamma);
color.b = (color.b <= 0.0404482362771082) ?
color.b / 12.92 : pow((color.b + 0.055) / 1.055, gamma);

return color;
}

float3 LinearToRGBGamma(float3 color, float gamma)
{
color = FxaaSat(color);
color.r = (color.r <= 0.00313066844250063) ?
color.r * 12.92 : 1.055 * pow(color.r, 1.0 / gamma) - 0.055;
color.g = (color.g <= 0.00313066844250063) ?
color.g * 12.92 : 1.055 * pow(color.g, 1.0 / gamma) - 0.055;
color.b = (color.b <= 0.00313066844250063) ?
color.b * 12.92 : 1.055 * pow(color.b, 1.0 / gamma) - 0.055;

return color;
}

float4 PreGammaPass(float4 color)
{
const float GammaConst = 2.233;
color.rgb = RGBGammaToLinear(color.rgb, GammaConst);
color.rgb = LinearToRGBGamma(color.rgb, GammaConst);
#if !PS_HDR
// PS2 games didn't expect sRGB decoding from the display (which is different than raw gamma 2.2).
// HD TVs are all either 2.4 or 2.2. Most monitors are 2.2, not sRGB.
color.rgb = pow(abs(color.rgb), float3(DEFAULT_GAMMA, DEFAULT_GAMMA, DEFAULT_GAMMA)) * sign(color.rgb);
#endif

// Calculate the luminance in linear space
color.a = RGBLuminance(color.rgb);

// Convert back to gamma space as FXAA expects it
color.rgb = pow(abs(color.rgb), float3(1.0 / DEFAULT_GAMMA, 1.0 / DEFAULT_GAMMA, 1.0 / DEFAULT_GAMMA)) * sign(color.rgb);

return color;
}
Expand All @@ -153,16 +140,31 @@ float4 PreGammaPass(float4 color)
------------------------------------------------------------------------------*/

float FxaaLuma(float4 rgba)
{
{
rgba.w = RGBLuminance(rgba.xyz);
#if PS_HDR
// In HDR, the source color was linear, so given that calculating luminance
// in linear space is better (more accurate), do so and then apply gamma to the luminance
rgba.w = pow(max(rgba.w, 0.0), 1.0 / DEFAULT_GAMMA);
#endif
return rgba.w;
}

float4 FxaaEncode(float4 rgba)
{
#if PS_HDR
// Convert from linear to gamma space as FXAA expects it
rgba.rgb = pow(abs(rgba.rgb), float3(1.0 / DEFAULT_GAMMA, 1.0 / DEFAULT_GAMMA, 1.0 / DEFAULT_GAMMA)) * sign(rgba.rgb);
#endif
return rgba;
}

float4 FxaaPixelShader(float2 pos, FxaaTex tex, float2 fxaaRcpFrame, float fxaaSubpix, float fxaaEdgeThreshold, float fxaaEdgeThresholdMin)
{
float2 posM = pos;
float4 rgbyM = FxaaTexTop(tex, posM);
rgbyM.w = RGBLuminance(rgbyM.xyz);
rgbyM.w = FxaaLuma(rgbyM);
rgbyM = FxaaEncode(rgbyM);
#define lumaM rgbyM.w

float lumaS = FxaaLuma(FxaaTexOff(tex, posM, int2( 0, 1), fxaaRcpFrame.xy));
Expand Down Expand Up @@ -433,7 +435,7 @@ float4 FxaaPixelShader(float2 pos, FxaaTex tex, float2 fxaaRcpFrame, float fxaaS
if(!horzSpan) posM.x += pixelOffsetSubpix * lengthSign;
if( horzSpan) posM.y += pixelOffsetSubpix * lengthSign;

return float4(FxaaTexTop(tex, posM).xyz, lumaM);
return float4(FxaaEncode(FxaaTexTop(tex, posM)).xyz, lumaM);
}

#if (FXAA_GLSL_130 == 1 || FXAA_GLSL_VK == 1)
Expand Down Expand Up @@ -475,6 +477,9 @@ void main()
vec4 color = texture(TextureSampler, PSin_t);
color = PreGammaPass(color);
color = FxaaPass(color, PSin_t);
#if PS_HDR
color.rgb = pow(abs(color.rgb), vec3(DEFAULT_GAMMA, DEFAULT_GAMMA, DEFAULT_GAMMA)) * sign(color.rgb);
#endif

SV_Target0 = float4(color.rgb, 1.0);
}
Expand All @@ -485,9 +490,11 @@ PS_OUTPUT main(VS_OUTPUT input)
PS_OUTPUT output;

float4 color = Texture.Sample(TextureSampler, input.t);

color = PreGammaPass(color);
color = FxaaPass(color, input.t);
#if PS_HDR
color.rgb = pow(abs(color.rgb), DEFAULT_GAMMA) * sign(color.rgb);
#endif

output.c = float4(color.rgb, 1.0);

Expand Down
232 changes: 232 additions & 0 deletions bin/resources/shaders/dx11/colorcorrect.fx
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+

#ifndef PS_HDR_INPUT
#define PS_HDR_INPUT 0
#endif
#ifndef PS_HDR_OUTPUT
#define PS_HDR_OUTPUT 0
#endif

//TODO
#define PS_HDR_TEST 0

Texture2D Texture;
SamplerState Sampler;

cbuffer cb0 : register(b0)
{
float4 correction;
float4 adjustment;
};

// SMPTE 170M - BT.601 (NTSC-M) -> BT.709
static const float3x3 from_NTSCM = float3x3(
0.939497225737661, 0.0502268452914346, 0.0102759289709032,
0.0177558637510127, 0.965824605885027, 0.0164195303639603,
-0.00162163209967010, -0.00437400622653655, 1.00599563832621);

// ARIB TR-B9 (9300K+27MPCD with chromatic adaptation) (NTSC-J) -> BT.709
static const float3x3 from_NTSCJ = float3x3(
0.823613036967492, -0.0943227111084757, 0.00799341532931119,
0.0289258355537324, 1.02310733489462, 0.00243547111576797,
-0.00569501554980891, 0.0161828357559315, 1.22328453915712);

// EBU - BT.470BG/BT.601 (PAL) -> BT.709
static const float3x3 from_PAL = float3x3(
1.04408168421813, -0.0440816842181253, 0.000000000000000,
0.000000000000000, 1.00000000000000, 0.000000000000000,
0.000000000000000, 0.0118044782106489, 0.988195521789351);

static const float3x3 BT709_2_BT2020 = float3x3(
0.627403914928436279296875f, 0.3292830288410186767578125f, 0.0433130674064159393310546875f,
0.069097287952899932861328125f, 0.9195404052734375f, 0.011362315155565738677978515625f,
0.01639143936336040496826171875f, 0.08801330626010894775390625f, 0.895595252513885498046875f);

static const float3x3 BT2020_2_BT709 = float3x3(
1.66049098968505859375f, -0.58764111995697021484375f, -0.072849862277507781982421875f,
-0.12455047667026519775390625f, 1.13289988040924072265625f, -0.0083494223654270172119140625f,
-0.01815076358616352081298828125f, -0.100578896701335906982421875f, 1.11872971057891845703125f);

// Applies exponential ("Photographic") luminance/luma compression.
float RangeCompress(float X)
{
// Branches are for static parameters optimizations
// This does e^X. We expect X to be between 0 and 1.
return 1.f - exp(-X);
}

// Refurbished DICE HDR tonemapper (per channel or luminance).
float LuminanceCompress(
float InValue,
float OutMaxValue,
float ShoulderStart = 0.f)
{
const float compressableValue = InValue - ShoulderStart;
const float compressedRange = OutMaxValue - ShoulderStart;
const float possibleOutValue = ShoulderStart + compressedRange * RangeCompress(compressableValue / compressedRange);
return (InValue <= ShoulderStart) ? InValue : possibleOutValue;
}

// BT.709
float luminance(float3 c)
{
return dot(c, float3(0.2125, 0.7154, 0.0721));
}

/*
** Contrast, saturation, brightness
** Code of this function is from TGM's shader pack
** http://irrlicht.sourceforge.net/phpBB2/viewtopic.php?t=21057
*/

// For all settings: 1.0 = 100% 0.5=50% 1.5 = 150%
float4 ContrastSaturationBrightness(float4 color) // Ported to HLSL
{
float brt = adjustment.x;
float con = adjustment.y;
float sat = adjustment.z;

#if 1 // For linear space in/out
float3 AvgLumin = 0.18; // Mid gray
#else
// Increase or decrease these values to adjust r, g and b color channels separately
const float AvgLumR = 0.5;
const float AvgLumG = 0.5;
const float AvgLumB = 0.5;
float3 AvgLumin = float3(AvgLumR, AvgLumG, AvgLumB);
#endif
float3 brtColor = color.rgb * brt;
float intensity = luminance(brtColor);
float3 satColor = lerp(intensity, brtColor, sat);
float3 conColor = lerp(AvgLumin, satColor, con);

color.rgb = conColor;
return color;
}

struct PS_INPUT
{
float4 p : SV_Position;
float2 t : TEXCOORD0;
};

// AdvancedAutoHDR pass to generate some HDR brightness out of an SDR signal.
// This is hue conserving and only really affects highlights.
// "SDRColor" is meant to be in "SDR range" (linear), as in, a value of 1 matching SDR white (something between 80, 100, 203, 300 nits, or whatever else)
// From: https://github.com/Filoppi/PumboAutoHDR (MIT)
float3 PumboAutoHDR(float3 SDRColor, float PeakWhiteNits = 400.f, float PaperWhiteNits = 203.f, float ShoulderPow = 3.5f)
{
const float SDRRatio = luminance(SDRColor);
// Limit AutoHDR brightness, it won't look good beyond a certain level.
// The paper white multiplier is applied later so we account for that.
const float AutoHDRMaxWhite = max(min(PeakWhiteNits, 1000.f) / PaperWhiteNits, 1.f);
const float AutoHDRShoulderRatio = saturate(SDRRatio);
const float AutoHDRExtraRatio = pow(max(AutoHDRShoulderRatio, 0.f), ShoulderPow) * (AutoHDRMaxWhite - 1.f);
const float AutoHDRTotalRatio = SDRRatio + AutoHDRExtraRatio;
return SDRColor * (SDRRatio > 0.f ? (AutoHDRTotalRatio / SDRRatio) : 1.f);
}

float4 ps_main(PS_INPUT input) : SV_Target0
{
float4 c = Texture.Sample(Sampler, input.t);

#if PS_HDR_INPUT // Clamp negative colors, they weren't meant to be
c.rgb = max(c.rgb, 0.f);
#endif

// Linearize
c.rgb = pow(abs(c.rgb), correction.x) * sign(c.rgb);

#if PS_HDR_OUTPUT && 0 // Print HDR colors
if (any(c.rgb > 1.0))
{
c.rgb = float3(1, 0, 1);
}
#endif

// Convert to BT.709 from the user specified game color space
if (correction.y == 1.f)
{
c.rgb = mul(c.rgb, from_NTSCM);
}
else if (correction.y == 2.f)
{
c.rgb = mul(c.rgb, from_NTSCJ);
}
else if (correction.y == 3.f)
{
c.rgb = mul(c.rgb, from_PAL);
}

float3 sdrColor = saturate(c.rgb);

//TODO: expose setting? In VK too. Also... clean this all around (move define, remove branches)
#define PS_FAKE_HDR 1
#if PS_HDR_OUTPUT && PS_FAKE_HDR
// If the game doesn't have many bright highlights, the dynamic range is relatively low, this helps alleviate that
#if PS_HDR_INPUT // "Fake" HDR
const float normalizationPoint = 0.333; // Found empyrically
const float fakeHDRIntensity = 0.25; // Found empyrically
float cLuminanceOriginal = luminance(c.rgb);
float cLuminance = cLuminanceOriginal / normalizationPoint;
cLuminance = cLuminance > 1.0 ? pow(cLuminance, 1.0 + fakeHDRIntensity) : cLuminance;
cLuminance *= normalizationPoint;
if (input.t.x >= 0.5f || !PS_HDR_TEST)
c.rgb *= cLuminanceOriginal == 0.f ? 1.f : (cLuminance / cLuminanceOriginal);
#else // AutoHDR
float HDRPaperWhite = correction.z;
if (input.t.x >= 0.5f || !PS_HDR_TEST)
c.rgb = PumboAutoHDR(c.rgb, 750.0, HDRPaperWhite * 80.0);
#endif
if (input.t.x <= 0.5f && PS_HDR_TEST)
c.rgb = saturate(c.rgb);
#endif // PS_HDR_OUTPUT && PS_FAKE_HDR

#if PS_HDR_INPUT // Display map
// Restore the SDR hue (rgb ratio) up to 50% to avoid highlights completely changing colors in unexpected ways
float sdrLuminance = luminance(sdrColor);
float hdrLuminance = luminance(c.rgb);
sdrColor *= sdrLuminance != 0.0 ? (hdrLuminance / sdrLuminance) : 1.0; // Scale up the SDR clipped color to have the HDR color luminance
c.rgb = lerp(c.rgb, sdrColor, 0.5);

// In HDR, we only compress the range above SDR (1), in SDR, we compress the top 20% range, to avoid clipping and retain HDR detail.
float shoulderStart = 1.f;
#if PS_HDR_OUTPUT
// Tonemapping should be done in the color space of the output display (e.g. BT.2020 in HDR and BT.709 in SDR),
// because displays usually clip individual rgb values to the peak brightness value of HDR.
c.rgb = mul(BT709_2_BT2020, c.rgb);
#else // !PS_HDR_OUTPUT
c.rgb *= 0.75f; // Scale down the SDR image to leave more range for highlights (anything beyond 0.75 will be HDR in SDR), this would only look good on 10bit monitors that can go beyond ~200 nits
shoulderStart = 0.667f;
#endif // PS_HDR_OUTPUT

float peakWhite = correction.w;

// Tonemap in gamma space (this specific formula looks better with it) and by channel, to best retain the original color hues (even if we do it in a different color space).
const float tonemapGamma = 2.75; // Found empyrically, any value between 1 and 3 is good, with 2-3 being especially good
c.rgb = pow(abs(c.rgb), 1.0 / tonemapGamma) * sign(c.rgb);
peakWhite = pow(peakWhite, 1.0 / tonemapGamma);

c.r = LuminanceCompress(c.r, peakWhite, shoulderStart);
c.g = LuminanceCompress(c.g, peakWhite, shoulderStart);
c.b = LuminanceCompress(c.b, peakWhite, shoulderStart);

c.rgb = pow(abs(c.rgb), tonemapGamma) * sign(c.rgb);

#if PS_HDR_OUTPUT
c.rgb = mul(BT2020_2_BT709, c.rgb);
#endif // PS_HDR_OUTPUT
#endif // PS_HDR_INPUT

c = ContrastSaturationBrightness(c);

#if PS_HDR_OUTPUT
// Leave as linear, for scRGB HDR
#else
// Convert to Gamma 2.2 (not sRGB)
c.rgb = pow(max(c.rgb, 0.0), 1.0 / 2.2);
#endif

return c;
}
Loading
Loading