// == Contrast Adaptive Sharpening =============================================
// LICENSE
// =======
// Copyright (c) 2017-2019 Advanced Micro Devices, Inc. All rights reserved.
// -------
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,
// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
// -------
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
// Software.
// -------
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE

//Initial port to ReShade: SLSNe    https://gist.github.com/SLSNe/bbaf2d77db0b2a2a0755df581b3cf00c

//Optimizations by Marty McFly:
//	vectorized math, even with scalar gcn hardware this should work
//	out the same, order of operations has not changed
//	For some reason, it went from 64 to 48 instructions, a lot of MOV gone
//	Also modified the way the final window is calculated
//
//	reordered min() and max() operations, from 11 down to 9 registers    
//
//	restructured final weighting, 49 -> 48 instructions
//
//	delayed RCP to replace SQRT with RSQRT
//
//	removed the saturate() from the control var as it is clamped
//	by UI manager already, 48 -> 47 instructions
//
//	replaced tex2D with tex2Doffset intrinsic (address offset by immediate integer)
//	47 -> 43 instructions
//	9 -> 8 registers

//Further modified by OopyDoopy and Lord of Lunacy:
//	Changed wording in the UI for the existing variable and added a new variable and relevant code to adjust sharpening strength.

//Fix by Lord of Lunacy:
//	Made the shader use a linear colorspace rather than sRGB, as recommended by the original AMD documentation from FidelityFX.

//Modified by CeeJay.dk:
//	Included a label and tooltip description. I followed AMDs official naming guidelines for FidelityFX.
//
//	Used gather trick to reduce the number of texture operations by one (9 -> 8). It's now 42 -> 51 instructions but still faster
//	because of the texture operation that was optimized away.

// Optimized for VR by fholger:
// Disabled the gather trick as it causes issues in some VR games (for reasons unknown)
// Added basic color adjustment options
// Use a foveated rendering approach to only sharpen the parts of the image that matter

uniform float CASContrast <
    ui_category = "Sharpening";
	ui_type = "drag";
    ui_label = "Contrast Adaptation";
    ui_tooltip = "Adjusts the range the shader adapts to high contrast (0 is not all the way off).  Higher values = more high contrast sharpening.";
	ui_min = 0.0; ui_max = 1.0;
> = 0.5;

uniform float MaxDelta <
    ui_category = "Sharpening";
    ui_type = "drag";
    ui_label = "Max color delta";
    ui_tooltip = "The maximum amount the sharpened color value may differ from its original value. Lower values may help reduce oversharpening in scenes with small details.";
    ui_min = 0.0; ui_max = 1.0;
> = 1.0;

uniform float Sharpening <
    ui_category = "Sharpening";
	ui_type = "drag";
    ui_label = "Sharpening intensity";
    ui_tooltip = "Adjusts sharpening intensity by averaging the original pixels to the sharpened result.  1.0 is the unmodified default.";
	ui_min = 0.0; ui_max = 1.0;
> = 1.0;

#include "ReShade.fxh"
texture TexColor : COLOR;
sampler sTexColor {Texture = TexColor; SRGBTexture = true;};

float3 CASPass(float4 vpos, float2 texcoord)
{    
    // fetch a 3x3 neighborhood around the pixel 'e',
    //  a b c
    //  d(e)f
    //  g h i
    
    float3 a = tex2Doffset(sTexColor, texcoord, int2(-1, -1)).rgb;
    float3 b = tex2Doffset(sTexColor, texcoord, int2(0, -1)).rgb;
    float3 c = tex2Doffset(sTexColor, texcoord, int2(1, -1)).rgb;
    float3 d = tex2Doffset(sTexColor, texcoord, int2(-1, 0)).rgb;
    
    float3 g = tex2Doffset(sTexColor, texcoord, int2(-1, 1)).rgb;
 
#if 0  // this causes issues in VR for some reason
	float4 red_efhi = tex2Dgather(sTexColor, texcoord, 0);
	float4 green_efhi = tex2Dgather(sTexColor, texcoord, 1);
	float4 blue_efhi = tex2Dgather(sTexColor, texcoord, 2);
	
    float3 e = float3( red_efhi.w, green_efhi.w, blue_efhi.w);
    float3 f = float3( red_efhi.z, green_efhi.z, blue_efhi.z);
    float3 h = float3( red_efhi.x, green_efhi.x, blue_efhi.x);
    float3 i = float3( red_efhi.y, green_efhi.y, blue_efhi.y);

#else // If DX9
    float3 e = tex2D(sTexColor, texcoord).rgb;
    float3 f = tex2Doffset(sTexColor, texcoord, int2(1, 0)).rgb;

    float3 h = tex2Doffset(sTexColor, texcoord, int2(0, 1)).rgb;
    float3 i = tex2Doffset(sTexColor, texcoord, int2(1, 1)).rgb;

#endif    

	// Soft min and max.
	//  a b c             b
	//  d e f * 0.5  +  d e f * 0.5
	//  g h i             h
    // These are 2.0x bigger (factored out the extra multiply).
    float3 mnRGB = min(min(min(d, e), min(f, b)), h);
    float3 mnRGB2 = min(mnRGB, min(min(a, c), min(g, i)));
    mnRGB += mnRGB2;

    float3 mxRGB = max(max(max(d, e), max(f, b)), h);
    float3 mxRGB2 = max(mxRGB, max(max(a, c), max(g, i)));
    mxRGB += mxRGB2;

    // Smooth minimum distance to signal limit divided by smooth max.
    float3 rcpMRGB = rcp(mxRGB);
    float3 ampRGB = saturate(min(mnRGB, 2.0 - mxRGB) * rcpMRGB);    
    
    // Shaping amount of sharpening.
    ampRGB = rsqrt(ampRGB);
    
    float peak = -3.0 * CASContrast + 8.0;
    float3 wRGB = -rcp(ampRGB * peak);

    float3 rcpWeightRGB = rcp(4.0 * wRGB + 1.0);

    //                          0 w 0
    //  Filter shape:           w 1 w
    //                          0 w 0  
    float3 window = (b + d) + (f + h);
    float3 outColor = saturate((window * wRGB + e) * rcpWeightRGB);
    outColor = e + clamp(outColor - e, -MaxDelta, MaxDelta);
    
	return lerp(e, outColor, Sharpening);
}
// =============================================================================

// == Color adjustments ========================================================
uniform float Contrast <
    ui_category = "Color adjustments";
    ui_type = "drag";
    ui_label = "Contrast";
    ui_min = 0.0; ui_max = 2.0;
> = 1.0;
uniform float Brightness <
    ui_category = "Color adjustments";
    ui_type = "drag";
    ui_label = "Brightness";
    ui_min = 0.0; ui_max = 2.0;
> = 1.0;
uniform float Saturation <
    ui_category = "Color adjustments";
    ui_type = "drag";
    ui_label = "Color saturation";
    ui_min = 0.0; ui_max = 2.0;
> = 1.0;

float3 VRColorAdjustmentPass(float3 color)
{
    color = lerp(0.5, color, Contrast) + (Brightness - 1);
    if (Saturation != 1.0)
    {
        float intensity = dot(float3(0.2125, 0.7154, 0.0721), color.rgb);
        color = lerp(intensity, color, Saturation);
    }
    return color;
}
// =============================================================================

uniform float SharpeningRadius <
    ui_category = "Performance";
    ui_type = "drag";
    ui_label = "Sharpening radius";
    ui_tooltip = "The radius from the eye center at which sharpening gets disabled. You can lower this for a minor performance improvement.";
    ui_min = 0.0; ui_max = 1.5;
> = 1.0;

uniform bool SingleImage <
    ui_category = "Performance";
    ui_label = "Single image for both eyes";
    ui_tooltip = "For the radius setting to work properly, you need to specify if both eye views are contained in a single image or submitted as separate images. If the sharpening effect looks wrong, toggle this setting and see if it improves.";
> = true;


float3 VRSharpenColorPass(float4 vpos : SV_Position, float2 texcoord : TexCoord) : SV_Target
{
    float2 fromCenter;
    if (SingleImage) 
    {
        fromCenter = float2(texcoord.x < 0.5 ? 0.25 : 0.75, 0.5) - texcoord;
        fromCenter.x *= 2;
    } else
        fromCenter = float2(0.5, 0.5) - texcoord;
    float distSqr = dot(fromCenter, fromCenter) * 4;

    float3 color;
    if (Sharpening != 0 && distSqr < SharpeningRadius*SharpeningRadius)
        color = CASPass(vpos, texcoord);
    else
        color = tex2D(sTexColor, texcoord).rgb;
    
    return VRColorAdjustmentPass(color);
}

technique VRSharpenColor <
	ui_label = "VR Image Sharpening and Color adjustment";
>
{
	pass
	{
		VertexShader = PostProcessVS;
		PixelShader = VRSharpenColorPass;
		SRGBWriteEnable = true;
	}
}
