//#region Includes

#include "ReShade.fxh"
#include "ReShadeUI.fxh"

//#endregion

//#region Macros

#ifndef NEO_BLOOM_TEXTURE_SIZE
#define NEO_BLOOM_TEXTURE_SIZE 1024
#endif

// Should be ((int)log2(NEO_BLOOM_TEXTURE_SIZE) + 1)
#ifndef NEO_BLOOM_TEXTURE_MIP_LEVELS
#define NEO_BLOOM_TEXTURE_MIP_LEVELS 11
#endif

#ifndef NEO_BLOOM_BLUR_SAMPLES
#define NEO_BLOOM_BLUR_SAMPLES 13
#endif

#ifndef NEO_BLOOM_DOWN_SCALE
#define NEO_BLOOM_DOWN_SCALE 2
#endif

#ifndef NEO_BLOOM_ADAPT
#define NEO_BLOOM_ADAPT 0
#endif

#ifndef NEO_BLOOM_DEBUG
#define NEO_BLOOM_DEBUG 0
#endif

#ifndef NEO_BLOOM_LENS_DIRT
#define NEO_BLOOM_LENS_DIRT 0
#endif

#ifndef NEO_BLOOM_LENS_DIRT_TEXTURE_NAME
#define NEO_BLOOM_LENS_DIRT_TEXTURE_NAME "NeoBloom_LensDirt.png"
#endif

#ifndef NEO_BLOOM_LENS_DIRT_TEXTURE_WIDTH
#define NEO_BLOOM_LENS_DIRT_TEXTURE_WIDTH 1280
#endif

#ifndef NEO_BLOOM_LENS_DIRT_TEXTURE_HEIGHT
#define NEO_BLOOM_LENS_DIRT_TEXTURE_HEIGHT 720
#endif

#ifndef NEO_BLOOM_LENS_DIRT_ASPECT_RATIO_CORRECTION
#define NEO_BLOOM_LENS_DIRT_ASPECT_RATIO_CORRECTION 1
#endif

#ifndef NEO_BLOOM_GHOSTING
#define NEO_BLOOM_GHOSTING 0
#endif

#ifndef NEO_BLOOM_GHOSTING_DOWN_SCALE
#define NEO_BLOOM_GHOSTING_DOWN_SCALE (NEO_BLOOM_DOWN_SCALE / 4.0)
#endif

#ifndef NEO_BLOOM_DEPTH
#define NEO_BLOOM_DEPTH 0
#endif

#ifndef NEO_BLOOM_DEPTH_ANTI_FLICKER
#define NEO_BLOOM_DEPTH_ANTI_FLICKER 0
#endif

#define NEO_BLOOM_NEEDS_LAST (NEO_BLOOM_GHOSTING || NEO_BLOOM_DEPTH && NEO_BLOOM_DEPTH_ANTI_FLICKER)

//#endregion

//#region Data Types

struct BlendPassParams
{
	float4 p : SV_POSITION;
	float2 uv : TEXCOORD0;

	#if NEO_BLOOM_LENS_DIRT

	float2 lens_uv : TEXCOORD1;

	#endif
};

//#endregion

//#region Constants

static const float Pi = 3.14159;

// Each bloom means: (x, y, scale, miplevel).
static const int BloomCount = 5;
static const float4 BloomLevels[] =
{
	float4(0.0, 0.5, 0.5, 1),
	float4(0.5, 0.0, 0.25, 2),
	float4(0.75, 0.875, 0.125, 3),
	float4(0.875, 0.0, 0.0625, 5),
	float4(0.0, 0.0, 0.03, 7)
	//float4(0.0, 0.0, 0.03125, 9)
};
static const int MaxBloomLevel = BloomCount - 1;

static const int BlurSamples = NEO_BLOOM_BLUR_SAMPLES;
static const float BlurHalfSamples = BlurSamples * 0.5;

static const float2 PixelScale = 1.0;

static const float2 DirtResolution = float2(
	NEO_BLOOM_LENS_DIRT_TEXTURE_WIDTH,
	NEO_BLOOM_LENS_DIRT_TEXTURE_HEIGHT);
static const float2 DirtPixelSize = 1.0 / DirtResolution;
static const float DirtAspectRatio = DirtResolution.x * DirtPixelSize.y;
static const float DirtAspectRatioInv = 1.0 / DirtAspectRatio;

static const int DebugOption_None = 0;
static const int DebugOption_OnlyBloom = 1;
static const int DebugOption_SplitTextures = 2;
static const int DebugOption_Adaptation = 3;

#if NEO_BLOOM_ADAPT
	static const int DebugOption_DepthRange = 4;
#else
	static const int DebugOption_DepthRange = 3;
#endif

static const int AdaptMode_FinalImage = 0;
static const int AdaptMode_OnlyBloom = 1;

static const int BloomBlendMode_Mix = 0;
static const int BloomBlendMode_Addition = 1;
static const int BloomBlendMode_Screen = 2;

//#endregion

//#region Uniforms

// Bloom

uniform int _Help
<
	ui_text =
		"NeoBloom has many options and may be difficult to setup or may look "
		"bad at first, but it's designed to be very flexible to adapt to many "
		"different cases.\n"
		"Make sure to take a look at the preprocessor definitions at the "
		"bottom!\n"
		"For more specific descriptions, move the mouse cursor over the name "
		"of the option you need help with.\n"
		"\n"
		"Here's a general description of the features:\n"
		"\n"
		"  Bloom:\n"
		"    Basic options for controlling the look of bloom itself.\n"
		"\n"
		"  Adaptation:\n"
		"    Used to dynamically increase or reduce the image brightness "
		"depending on the scene, giving an HDR look.\n"
		"    Looking at a bright object, like a lamp, would cause the image to "
		"darken; lookinng at a dark spot, like a cave, would cause the "
		"image to brighten.\n"
		"\n"
		"  Blending:\n"
		"    Used to control how the different bloom textures are blended, "
		"each representing a different level-of-detail.\n"
		"    Can be used to simulate an old mid-2000s bloom, ambient light "
		"etc.\n"
		"\n"
		"  Ghosting:\n"
		"    Smoothens the bloom between frames, causing a \"motion blur\" or "
		"\"trails\" effect.\n"
		"\n"
		"  Depth:\n"
		"    Used to increase or decrease the brightness of parts of the image "
		"depending on depth.\n"
		"    Can be used for effects like brightening the sky.\n"
		"    An optional anti-flicker feature is available to help with games "
		"with depth flickering problems, which can cause bloom to flicker as "
		"well with the depth feature enabled.\n"
		"\n"
		"  HDR:\n"
		"    Options for controlling the high dynamic range simulation.\n"
		"    Useful for simulating a more foggy bloom, like an old soap opera, "
		"a high-contrast sunny look etc.\n"
		"\n"
		"  Blur:\n"
		"    Options for controlling the blurring effect used to generate the "
		"bloom textures.\n"
		"    Mostly can be left untouched.\n"
		"\n"
		"  Debug:\n"
		"    Enables testing options, like viewing the bloom texture alone, "
		"before mixing with the image.\n"
		;
	ui_category = "Help";
	ui_category_closed = true;
	ui_label = " ";
	ui_type = "radio";
>;

uniform float Intensity
<
	__UNIFORM_SLIDER_FLOAT1

	ui_label = "Intensity";
	ui_tooltip =
		"Determines how much bloom is added to the image. For HDR games you'd "
		"generally want to keep this low-ish, otherwise everything might look "
		"too bright.\n"
		"\nDefault: 1.0";
	ui_category = "Bloom";
	ui_min = 0.0;
	ui_max = 1.0;
> = 1.0;

uniform float Saturation
<
	__UNIFORM_SLIDER_FLOAT1

	ui_label = "Saturation";
	ui_tooltip =
		"Saturation of the bloom texture.\n"
		"\nDefault: 1.0";
	ui_category = "Bloom";
	ui_min = 0.0;
	ui_max = 3.0;
> = 1.0;

uniform float3 ColorFilter
<
	__UNIFORM_COLOR_FLOAT3

	ui_label = "Color Filter";
	ui_tooltip =
		"Color multiplied to the bloom, filtering it.\n"
		"Set to full white (255, 255, 255) to disable it.\n"
		"\nDefault: 255 255 255";
	ui_category = "Bloom";
> = float3(1.0, 1.0, 1.0);

uniform int BloomBlendMode
<
	__UNIFORM_COMBO_INT1

	ui_label = "Blend Mode";
	ui_tooltip =
		"Determines the formula used to blend bloom with the scene color.\n"
		"Certain blend modes may not play well with other options.\n"
		"As a fallback, addition always works.\n"
		"\nDefault: Mix";
	ui_category = "Bloom";
	ui_items = "Mix\0Addition\0Screen\0";
> = 1;

#if NEO_BLOOM_LENS_DIRT

uniform float LensDirtAmount
<
	__UNIFORM_SLIDER_FLOAT1

	ui_text =
		"Set NEO_BLOOM_DIRT to 0 to disable this feature to reduce resource "
		"usage.";
	ui_label = "Amount";
	ui_tooltip =
		"Determines how much lens dirt is added to the bloom texture.\n"
		"\nDefault: 0.0";
	ui_category = "Lens Dirt";
	ui_min = 0.0;
	ui_max = 3.0;
> = 0.0;

#endif

#if NEO_BLOOM_ADAPT

// Adaptation

uniform int AdaptMode
<
	__UNIFORM_COMBO_INT1

	ui_text =
		"Set NEO_BLOOM_ADAPT to 0 to disable this feature to reduce resource "
		"usage.";
	ui_label = "Mode";
	ui_tooltip =
		"Select different modes of how adaptation is applied.\n"
		"  Final Image:\n"
		"    Apply adaptation to the image after it was mixed with bloom.\n"
		"  Bloom Only:\n"
		"    Apply adaptation only to bloom, before mixing with the image.\n"
		"\nDefault: Final Image";
	ui_category = "Adaptation";
	ui_items = "Final Image\0Bloom Only\0";
> = 0;

uniform float AdaptAmount
<
	__UNIFORM_SLIDER_FLOAT1

	ui_label = "Amount";
	ui_tooltip =
		"How much adaptation affects the image brightness.\n"
		"\bDefault: 1.0";
	ui_category = "Adaptation";
	ui_min = 0.0;
	ui_max = 2.0;
> = 1.0;

uniform float AdaptSensitivity
<
	__UNIFORM_SLIDER_FLOAT1

	ui_label = "Sensitivity";
	ui_tooltip =
		"How sensitive is the adaptation towards bright spots?\n"
		"\nDefault: 1.0";
	ui_category = "Adaptation";
	ui_min = 0.0;
	ui_max = 2.0;
> = 1.0;

uniform float AdaptExposure
<
	__UNIFORM_SLIDER_FLOAT1

	ui_label = "Exposure";
	ui_tooltip =
		"Determines the general brightness that the effect should adapt "
		"towards.\n"
		"This is measured in f-numbers, thus 0 is the base exposure, <0 will "
		"be darker and >0 brighter.\n"
		"\nDefault: 0.0";
	ui_category = "Adaptation";
	ui_min = -3.0;
	ui_max = 3.0;
> = 0.0;

uniform bool AdaptUseLimits
<
	ui_label = "Use Limits";
	ui_tooltip =
		"Should the adaptation be limited to the minimum and maximum values "
		"specified below?\n"
		"\nDefault: On";
	ui_category = "Adaptation";
> = true;

uniform float2 AdaptLimits
<
	__UNIFORM_SLIDER_FLOAT2

	ui_label = "Limits";
	ui_tooltip =
		"The minimum and maximum values that adaptation can achieve.\n"
		"Increasing the minimum value will lessen how bright the image can "
		"become in dark scenes.\n"
		"Decreasing the maximum value will lessen how dark the image can "
		"become in bright scenes.\n"
		"\nDefault: 0.0 1.0";
	ui_category = "Adaptation";
	ui_min = 0.0;
	ui_max = 1.0;
	ui_step = 0.001;
> = float2(0.0, 1.0);

uniform float AdaptTime
<
	__UNIFORM_SLIDER_FLOAT1

	ui_label = "Time";
	ui_tooltip =
		"The time it takes for the effect to adapt.\n"
		"\nDefault: 1.0";
	ui_category = "Adaptation";
	ui_min = 0.02;
	ui_max = 3.0;
> = 1.0;

uniform float AdaptPrecision
<
	__UNIFORM_SLIDER_FLOAT1

	ui_label = "Precision";
	ui_tooltip =
		"How precise adaptation will be towards the center of the image.\n"
		"This means that 0.0 will yield an adaptation of the overall image "
		"brightness, while higher values will focus more and more towards the "
		"center pixels.\n"
		"\nDefault: 0.0";
	ui_category = "Adaptation";
	ui_min = 0.0;
	ui_max = NEO_BLOOM_TEXTURE_MIP_LEVELS;
	ui_step = 1.0;
> = 0.0;

uniform int AdaptFormula
<
	__UNIFORM_COMBO_INT1

	ui_label = "Formula";
	ui_tooltip =
		"Which formula to use when extracting brightness information from "
		"color.\n"
		"\nDefault: Luma (Linear)";
	ui_category = "Adaptation";
	ui_items = "Average\0Luminance\0Luma (Gamma)\0Luma (Linear)\0";
> = 3;

#endif

// Blending

uniform float Mean
<
	__UNIFORM_SLIDER_FLOAT1

	ui_label = "Mean";
	ui_tooltip =
		"Acts as a bias between all the bloom textures/sizes. What this means "
		"is that lower values will yield more detail bloom, while the opposite "
		"will yield big highlights.\n"
		"The more variance is specified, the less effective this setting is, "
		"so if you want to have very fine detail bloom reduce both "
		"parameters.\n"
		"\nDefault: 0.0";
	ui_category = "Blending";
	ui_min = 0.0;
	ui_max = BloomCount;
	//ui_step = 0.005;
> = 0.0;

uniform float Variance
<
	__UNIFORM_SLIDER_FLOAT1

	ui_label = "Variance";
	ui_tooltip =
		"Determines the 'variety'/'contrast' in bloom textures/sizes. This "
		"means a low variance will yield more of the bloom size specified by "
		"the mean; that is to say that having a low variance and mean will "
		"yield more fine-detail bloom.\n"
		"A high variance will diminish the effect of the mean, since it'll "
		"cause all the bloom textures to blend more equally.\n"
		"A low variance and high mean would yield an effect similar to "
		"an 'ambient light', with big blooms of light, but few details.\n"
		"\nDefault: 1.0";
	ui_category = "Blending";
	ui_min = 1.0;
	ui_max = BloomCount;
	//ui_step = 0.005;
> = BloomCount;

#if NEO_BLOOM_GHOSTING

// Last

uniform float GhostingAmount
<
	__UNIFORM_SLIDER_FLOAT1

	ui_text =
		"Set NEO_BLOOM_GHOSTING to 0 if you don't use this feature to reduce "
		"resource usage.";
	ui_label = "Amount";
	ui_tooltip =
		"Amount of ghosting applied.\n"
		"\nDefault: 0.0";
	ui_category = "Ghosting";
	ui_min = 0.0;
	ui_max = 0.999;
> = 0.0;

#endif

#if NEO_BLOOM_DEPTH

uniform float3 DepthMultiplier
<
	__UNIFORM_DRAG_FLOAT3

	ui_text =
		"Set NEO_BLOOM_DEPTH to 0 if you don't use this feature to reduce "
		"resource usage.";
	ui_label = "Multiplier";
	ui_tooltip =
		"Defines the multipliers that will be applied to each range in depth.\n"
		" - The first value defines the multiplier for near depth.\n"
		" - The second value defines the multiplier for middle depth.\n"
		" - The third value defines the multiplier for far depth.\n"
		"\nDefault: 1.0 1.0 1.0";
	ui_category = "Depth";
	ui_min = 0.0;
	ui_max = 10.0;
	ui_step = 0.01;
> = float3(1.0, 1.0, 1.0);

uniform float2 DepthRange
<
	__UNIFORM_DRAG_FLOAT2

	ui_label = "Range";
	ui_tooltip =
		"Defines the depth range for thee depth multiplier.\n"
		" - The first value defines the start of the middle depth."
		" - The second value defines the end of the middle depth and the start "
		"of the far depth."
		"\nDefault: 0.0 1.0";
	ui_category = "Depth";
	ui_min = 0.0;
	ui_max = 1.0;
	ui_step = 0.001;
> = float2(0.0, 1.0);

uniform float DepthSmoothness
<
	__UNIFORM_DRAG_FLOAT1

	ui_label = "Smoothness";
	ui_tooltip =
		"Amount of smoothness in the transition between depth ranges.\n"
		"\nDefault: 1.0";
	ui_category = "Depth";
	ui_min = 0.0;
	ui_max = 1.0;
	ui_step = 0.001;
> = 1.0;

#if NEO_BLOOM_DEPTH_ANTI_FLICKER

uniform float DepthAntiFlicker
<
	__UNIFORM_SLIDER_FLOAT1

	ui_label = "Anti-Flicker Amount";
	ui_tooltip =
		"Amount of anti-flicker to apply to the depth feature.\n"
		"Use if you notice distant bloom flickering when using high "
		"multipliers.\n"
		"\nDefault: 0.999";
	ui_min = 0.0;
	ui_max = 0.999;
> = 0.999;

#endif

#endif

// HDR

uniform float MaxBrightness
<
	__UNIFORM_DRAG_FLOAT1

	ui_label  = "Max Brightness";
	ui_tooltip =
		"tl;dr: HDR contrast.\n"
		"\nDetermines the maximum brightness a pixel can achieve from being "
		"'reverse-tonemapped', that is to say, when the effect attempts to "
		"extract HDR information from the image.\n"
		"In practice, the difference between a value of 100 and one of 1000 "
		"would be in how bright/bloomy/big a white pixel could become, like "
		"the sun or the headlights of a car.\n"
		"Lower values can also work for making a more 'balanced' bloom, where "
		"there are less harsh highlights and the entire scene is equally "
		"foggy, like an old TV show or with dirty lenses.\n"
		"\nDefault: 100.0";
	ui_category = "HDR";
	ui_min = 1.0;
	ui_max = 1000.0;
	ui_step = 1.0;
> = 100.0;

uniform bool NormalizeBrightness
<
	ui_label = "Normalize Brightness";
	ui_tooltip =
		"Whether to normalize the bloom brightness when blending with the "
		"image.\n"
		"Without it, the bloom may have very harsh bright spots.\n"
		"\nDefault: On";
	ui_category = "HDR";
> = true;

uniform bool MagicMode
<
	ui_label = "Magic Mode";
	ui_tooltip =
		"When enabled, simulates the look of MagicBloom.\n"
		"This is an experimental option and may be inconsistent with other "
		"parameters.\n"
		"\nDefault: Off";
	ui_category = "HDR";
> = false;

// Blur

uniform float Sigma
<
	__UNIFORM_DRAG_FLOAT1

	ui_label = "Sigma";
	ui_tooltip =
		"Amount of blurriness. Values too high will break the blur.\n"
		"Recommended values are between 2 and 4.\n"
		"\nDefault: 2.0";
	ui_category = "Blur";
	ui_min = 1.0;
	ui_max = 10.0;
	ui_step = 0.01;
> = 2.0;

uniform float Padding
<
	__UNIFORM_DRAG_FLOAT1

	ui_label = "Padding";
	ui_tooltip =
		"Specifies an additional padding that is added around each bloom "
		"texture internally during the blurring process.\n"
		"This serves to reduce the 'vignette-like' effect that can occur "
		"around the screen edges in the bloom textures, where brightness is "
		"lost near the edges. With padding however this is counteracted as it "
		"can simulate there being more color information beyond the edges.\n"
		"Be aware that this works by effectively reducing the bloom texture's "
		"sizes, so it can lead to loss of detail or pixelation when values "
		"specified are too high.\n"
		"\nDefault: 0.1";
	ui_category = "Blur";
	ui_min = 0.0;
	ui_max = 10.0;
	ui_step = 0.001;
> = 0.1;

#if NEO_BLOOM_DEBUG

// Debug

uniform int DebugOptions
<
	__UNIFORM_COMBO_INT1

	ui_text =
		"Set NEO_BLOOM_DEBUG to 0 if you don't use this feature to reduce "
		"resource usage.";
	ui_label = "Debug Options";
	ui_tooltip =
		"Debug options containing:\n"
		"  - Showing only the bloom texture. The 'bloom texture to show' "
		"parameter can be used to determine which bloom texture(s) to "
		"visualize.\n"
		"  - Showing the raw internal texture used to blur all the bloom "
		"'textures', visualizing all the blooms at once in scale.\n"
		"\nDefault: None";
	ui_category = "Debug";
	ui_items =
		"None\0Show Only Bloom\0Show Split Textures\0"
		#if NEO_BLOOM_ADAPT
		"Show Adaptation\0"
		#endif
		#if NEO_BLOOM_DEPTH
		"Show Depth Range\0"
		#endif
		;
> = false;

uniform int BloomTextureToShow
<
	__UNIFORM_SLIDER_INT1

	ui_label = "Bloom Texture To Show";
	ui_tooltip =
		"Which bloom texture to show with the 'Show Only Bloom' debug option.\n"
		"Set to -1 to view all textures blended.\n"
		"\nDefault: -1";
	ui_category = "Debug";
	ui_min = -1;
	ui_max = MaxBloomLevel;
> = -1;

#endif

#if NEO_BLOOM_ADAPT

uniform float FrameTime <source = "frametime";>;

#endif

//#endregion

//#region Textures

sampler BackBuffer
{
	Texture = ReShade::BackBufferTex;
	SRGBTexture = true;
};

texture NeoBloom_DownSample
{
	Width = NEO_BLOOM_TEXTURE_SIZE;
	Height = NEO_BLOOM_TEXTURE_SIZE;
	Format = RGBA16F;
	MipLevels = NEO_BLOOM_TEXTURE_MIP_LEVELS;
};
sampler DownSample
{
	Texture = NeoBloom_DownSample;
};

texture NeoBloom_TempA
{
	Width = BUFFER_WIDTH / NEO_BLOOM_DOWN_SCALE;
	Height = BUFFER_HEIGHT / NEO_BLOOM_DOWN_SCALE;
	Format = RGBA16F;
};
sampler TempA
{
	Texture = NeoBloom_TempA;
};

texture NeoBloom_TempB
{
	Width = BUFFER_WIDTH / NEO_BLOOM_DOWN_SCALE;
	Height = BUFFER_HEIGHT / NEO_BLOOM_DOWN_SCALE;
	Format = RGBA16F;
};
sampler TempB
{
	Texture = NeoBloom_TempB;
};

#if NEO_BLOOM_ADAPT

texture NeoBloom_Adapt
{
	Format = R16F;
};
sampler Adapt
{
	Texture = NeoBloom_Adapt;
	MinFilter = POINT;
	MagFilter = POINT;
};

texture NeoBloom_LastAdapt
{
	Format = R16F;
};
sampler LastAdapt
{
	Texture = NeoBloom_LastAdapt;
	MinFilter = POINT;
	MagFilter = POINT;
};

#endif

#if NEO_BLOOM_LENS_DIRT

texture NeoBloom_LensDirt
<
	source = NEO_BLOOM_LENS_DIRT_TEXTURE_NAME;
>
{
	Width = NEO_BLOOM_LENS_DIRT_TEXTURE_WIDTH;
	Height = NEO_BLOOM_LENS_DIRT_TEXTURE_HEIGHT;
};
sampler LensDirt
{
	Texture = NeoBloom_LensDirt;
};

#endif

#if NEO_BLOOM_NEEDS_LAST

texture NeoBloom_Last
{
	Width = BUFFER_WIDTH / NEO_BLOOM_GHOSTING_DOWN_SCALE;
	Height = BUFFER_HEIGHT / NEO_BLOOM_GHOSTING_DOWN_SCALE;

	#if NEO_BLOOM_GHOSTING && NEO_BLOOM_DEPTH_ANTI_FLICKER
		Format = RGBA16F;
	#else
		Format = R8;
	#endif
};
sampler Last
{
	Texture = NeoBloom_Last;
};

#if NEO_BLOOM_DEPTH

texture NeoBloom_Depth
{
	Width = BUFFER_WIDTH;
	Height = BUFFER_HEIGHT;
	Format = R8;
};
sampler Depth
{
	Texture = NeoBloom_Depth;
};

#endif

#endif

//#endregion

//#region Functions

float2 scale_uv(float2 uv, float2 scale, float2 center)
{
	return (uv - center) * scale + center;
}
float2 scale_uv(float2 uv, float2 scale)
{
	return scale_uv(uv, scale, 0.5);
}

float gaussian(float x, float o)
{
	o *= o;
	float a = 1.0 / sqrt(2.0 * Pi * o);
	float b = (x * x) / (2.0 * o);
	return a * exp(-(b));
}

float4 blur(sampler sp, float2 uv, float2 dir)
{
	float4 color = 0.0;
	float accum = 0.0;

	uv -= BlurHalfSamples * dir * ReShade::PixelSize;

	[unroll]
	for (int i = 1; i < BlurSamples; ++i)
	{
		float weight = gaussian(i - BlurHalfSamples, Sigma);

		uv += dir * ReShade::PixelSize;
		color += tex2D(sp, uv) * weight;
		accum += weight;
	}

	return color / accum;
}

float3 inv_reinhard(float3 color, float inv_max)
{
	return (color / max(1.0 - color, inv_max));
}

float3 inv_reinhard_lum(float3 color, float inv_max)
{
	float lum = max(color.r, max(color.g, color.b));
	return color * (lum / max(1.0 - lum, inv_max));
}

float3 reinhard(float3 color)
{
	return color / (1.0 + color);
}

float3 checkered_pattern(float2 uv)
{
	static const float cSize = 32.0;
	static const float3 cColorA = pow(0.15, 2.2);
	static const float3 cColorB = pow(0.5, 2.2);

	uv *= ReShade::ScreenSize;
	uv %= cSize;

	float half_size = cSize * 0.5;
	float checkered = step(uv.x, half_size) == step(uv.y, half_size);
	return (cColorA * checkered) + (cColorB * (1.0 - checkered));
}

float normal_distribution(float x, float u, float o)
{
	o *= o;

	float a = 1.0 / sqrt(2.0 * Pi * o);
	float b = x - u;
	b *= b;
	b /= 2.0 * o;

	return a * exp(-(b));
}

float get_luma_gamma(float3 color)
{
	return dot(color, float3(0.299, 0.587, 0.114));
}

float get_luma_linear(float3 color)
{
	return dot(color, float3(0.2126, 0.7152, 0.0722));
}

float3 blend_mode_screen(float3 a, float3 b, float w, float m)
{
	return m - (m - a) * (m - b * w);
}
float3 blend_mode_screen(float3 a, float3 b, float w)
{
	return blend_mode_screen(a, b, w, 1.0);
}

float3 blend_bloom(float3 color, float3 bloom)
{
	float w;
	if (NormalizeBrightness)
		w = Intensity / MaxBrightness;
	else
		w = Intensity;

	switch (BloomBlendMode)
	{
		default:
			return 0.0;
		case BloomBlendMode_Mix:
			return lerp(color, bloom, log2(w + 1.0));
		case BloomBlendMode_Addition:
			return color + bloom * w * 3.0;
		case BloomBlendMode_Screen:
			return blend_mode_screen(color, bloom, w);
	}
}

float3 uncharted2_tonemap(float3 col, float exposure) {
    static const float A = 0.15; //shoulder strength
    static const float B = 0.50; //linear strength
	static const float C = 0.10; //linear angle
	static const float D = 0.20; //toe strength
	static const float E = 0.02; //toe numerator
	static const float F = 0.30; //toe denominator
	static const float W = 11.2; //linear white point value

    col *= exposure;

    col = ((col * (A * col + C * B) + D * E) / (col * (A * col + B) + D * F)) - E / F;
    static const float white = 1.0 / (((W * (A * W + C * B) + D * E) / (W * (A * W + B) + D * F)) - E / F);
    col *= white;
    return col;
}

float3 inv_tonemap_bloom(float3 color)
{
	if (MagicMode)
		return pow(abs(color), MaxBrightness * 0.01);

	return inv_reinhard_lum(color, 1.0 / MaxBrightness);
}

float3 inv_tonemap(float3 color)
{
	if (MagicMode)
		return color;

	return inv_reinhard(color, 1.0 / MaxBrightness);
}

float3 tonemap(float3 color)
{
	if (MagicMode)
		return color;

	return reinhard(color);
}

//#endregion

//#region Shaders

#if NEO_BLOOM_DEPTH && NEO_BLOOM_DEPTH_ANTI_FLICKER

float GetDepthPS(float4 p : SV_POSITION, float2 uv : TEXCOORD) : SV_TARGET
{
	float3 depth = ReShade::GetLinearizedDepth(uv);

	#if NEO_BLOOM_GHOSTING
		float last = tex2D(Last, uv).a;
	#else
		float last = tex2D(Last, uv).r;
	#endif

	depth = lerp(depth, last, DepthAntiFlicker);

	return depth;
}

#endif

float4 DownSamplePS(float4 p : SV_POSITION, float2 uv : TEXCOORD) : SV_TARGET
{
	float4 color = tex2D(BackBuffer, uv);

	color.rgb = saturate(
		lerp(get_luma_linear(color.rgb), color.rgb, Saturation));

	color.rgb *= ColorFilter;

	color.rgb = inv_tonemap_bloom(color.rgb);

	#if NEO_BLOOM_DEPTH
		#if NEO_BLOOM_DEPTH_ANTI_FLICKER
			float3 depth = tex2D(Depth, uv).x;
		#else
			float3 depth = ReShade::GetLinearizedDepth(uv);
		#endif

		float is_near = smoothstep(
			depth - DepthSmoothness,
			depth + DepthSmoothness,
			DepthRange.x);

		float is_far = smoothstep(
			DepthRange.y - DepthSmoothness,
			DepthRange.y + DepthSmoothness, depth);

		float is_middle = (1.0 - is_near) * (1.0 - is_far);

		color.rgb *= lerp(1.0, DepthMultiplier.x, is_near);
		color.rgb *= lerp(1.0, DepthMultiplier.y, is_middle);
		color.rgb *= lerp(1.0, DepthMultiplier.z, is_far);
	#endif

	return color;
}

float4 SplitPS(float4 p : SV_POSITION, float2 uv : TEXCOORD) : SV_TARGET
{
	float4 color = 0.0;

	[unroll]
	for (int i = 0; i < BloomCount; ++i)
	{
		float4 rect = BloomLevels[i];
		float2 rect_uv = scale_uv(uv - rect.xy, 1.0 / rect.z, 0.0);
		float inbounds =
			step(0.0, rect_uv.x) * step(rect_uv.x, 1.0) *
			step(0.0, rect_uv.y) * step(rect_uv.y, 1.0);

		rect_uv = scale_uv(rect_uv, 1.0 + Padding * (i + 1), 0.5);

		float4 pixel = tex2Dlod(DownSample, float4(rect_uv, 0, rect.w));
		pixel.rgb *= inbounds;
		pixel.a = inbounds;

		color += pixel;
	}

	return color;
}

float4 BlurXPS(float4 p : SV_POSITION, float2 uv : TEXCOORD) : SV_TARGET
{
	float2 dir = PixelScale * float2(1.0, 0.0) * NEO_BLOOM_DOWN_SCALE;
	return blur(TempA, uv, dir);
}

float4 BlurYPS(float4 p : SV_POSITION, float2 uv : TEXCOORD) : SV_TARGET
{
	float2 dir = PixelScale * float2(0.0, 1.0) * NEO_BLOOM_DOWN_SCALE;
	return blur(TempB, uv, dir);
}

#if NEO_BLOOM_ADAPT

float4 CalcAdaptPS(float4 p : SV_POSITION, float2 uv : TEXCOORD) : SV_TARGET
{
	float3 color = tex2Dlod(
		DownSample,
		float4(0.5, 0.5, 0.0, NEO_BLOOM_TEXTURE_MIP_LEVELS - AdaptPrecision)
	).rgb;
	color = tonemap(color);

	float gs;
	switch (AdaptFormula)
	{
		case 0:
			gs = dot(color, 0.333);
			break;
		case 1:
			gs = max(color.r, max(color.g, color.b));
			break;
		case 2:
			gs = get_luma_gamma(color);
			break;
		case 3:
			gs = get_luma_linear(color);
			break;
	}

	gs *= AdaptSensitivity;
	gs = AdaptUseLimits ? clamp(gs, AdaptLimits.x, AdaptLimits.y) : gs;

	float last = tex2D(LastAdapt, 0.0).r;
	gs = lerp(last, gs, (FrameTime * 0.001) / AdaptTime);

	return float4(gs, 0.0, 0.0, 1.0);
}

float4 SaveAdaptPS(float4 p : SV_POSITION, float2 uv : TEXCOORD) : SV_TARGET
{
	return tex2D(Adapt, 0.0);
}

#endif

float4 JoinBloomsPS(float4 p : SV_POSITION, float2 uv : TEXCOORD) : SV_TARGET
{
	float4 bloom = 0.0;
	float accum = 0.0;

	[unroll]
	for (int i = 0; i < BloomCount; ++i)
	{
		float4 rect = BloomLevels[i];
		float2 rect_uv = scale_uv(uv, 1.0 / (1.0 + Padding * (i + 1)), 0.5);
		rect_uv = scale_uv(rect_uv + rect.xy / rect.z, rect.z, 0.0);

		float weight = normal_distribution(i, Mean, Variance);
		bloom += tex2D(TempA, rect_uv) * weight;
		accum += weight;
	}
	bloom /= accum;

	#if NEO_BLOOM_GHOSTING
		float3 last = tex2D(Last, uv).rgb;
		bloom.rgb = lerp(bloom.rgb, last.rgb, GhostingAmount);
	#endif

	return bloom;
}

#if NEO_BLOOM_NEEDS_LAST

float4 SaveLastBloomPS(
	float4 p : SV_POSITION,
	float2 uv : TEXCOORD
) : SV_TARGET
{
	float4 color = float4(0.0, 0.0, 0.0, 1.0);

	#if NEO_BLOOM_DEPTH && NEO_BLOOM_DEPTH_ANTI_FLICKER
		color = tex2D(Depth, uv).x;
	#endif

	#if NEO_BLOOM_GHOSTING
		color.rgb = tex2D(TempB, uv).rgb;
	#endif

	return color;
}

#endif

void BlendVS(uint id : SV_VERTEXID, out BlendPassParams p)
{
	PostProcessVS(id, p.p, p.uv);

	#if NEO_BLOOM_LENS_DIRT && NEO_BLOOM_LENS_DIRT_ASPECT_RATIO_CORRECTION
		float ar = BUFFER_WIDTH * BUFFER_RCP_HEIGHT;
		float ar_inv = BUFFER_HEIGHT * BUFFER_RCP_WIDTH;
		float is_horizontal = step(ar, DirtAspectRatio);
		float ratio = lerp(
			DirtAspectRatio * ar_inv,
			ar * DirtAspectRatioInv,
			is_horizontal);

		p.lens_uv = scale_uv(p.uv, float2(1.0, ratio), 0.5);
	#endif
}

float4 BlendPS(BlendPassParams p) : SV_TARGET
{
	float2 uv = p.uv;

	float4 color = tex2D(BackBuffer, uv);
	color.rgb = inv_tonemap(color.rgb);

	#if NEO_BLOOM_GHOSTING
		float4 bloom = tex2D(TempB, uv);
	#else
		float4 bloom = JoinBloomsPS(p.p, uv);
	#endif

	#if NEO_BLOOM_LENS_DIRT
		float4 dirt = tex2D(LensDirt, p.lens_uv);
		bloom.rgb = mad(dirt.rgb, bloom.rgb * LensDirtAmount, bloom.rgb);
	#endif

	#if NEO_BLOOM_DEBUG
		switch (DebugOptions)
		{
			case DebugOption_OnlyBloom:
				if (BloomTextureToShow == -1)
				{
					color.rgb = tonemap(bloom.rgb);
				}
				else
				{
					float4 rect = BloomLevels[BloomTextureToShow];
					float2 rect_uv = scale_uv(
						uv,
						1.0 / (1.0 + Padding * (BloomTextureToShow + 1)),
						0.5
					);

					rect_uv = scale_uv(rect_uv + rect.xy / rect.z, rect.z, 0.0);
					color = tex2D(TempA, rect_uv);
					color.rgb = tonemap(color.rgb);
				}

				return color;
			case DebugOption_SplitTextures:
				color = tex2D(TempA, uv);
				color.rgb = lerp(checkered_pattern(uv), color.rgb, color.a);
				color.a = 1.0;

				return color;

			#if NEO_BLOOM_ADAPT
				case DebugOption_Adaptation:
					color = tex2Dlod(
						DownSample,
						float4(
							uv,
							0.0,
							NEO_BLOOM_TEXTURE_MIP_LEVELS - AdaptPrecision)
					);
					color.rgb = tonemap(color.rgb);
					return color;
			#endif

			#if NEO_BLOOM_DEPTH
				case DebugOption_DepthRange:
					#if NEO_BLOOM_DEPTH_ANTI_FLICKER
						float depth = tex2D(Depth, uv).x;
					#else
						float depth = ReShade::GetLinearizedDepth(uv);
					#endif

					color.r = smoothstep(0.0, DepthRange.x, depth);
					color.g = smoothstep(DepthRange.x, DepthRange.y, depth);
					color.b = smoothstep(DepthRange.y, 1.0, depth);

					color.r *= smoothstep(
						depth - DepthSmoothness,
						depth + DepthSmoothness,
						DepthRange.x);

					color.g *= smoothstep(
						depth - DepthSmoothness,
						depth + DepthSmoothness,
						DepthRange.y);

					return color;
			#endif
		}
	#endif

	#if NEO_BLOOM_ADAPT
		float adapt = tex2D(Adapt, 0.0).r;
		float exposure = exp(AdaptExposure) / max(adapt, 0.001);
		exposure = lerp(1.0, exposure, AdaptAmount);

		if (MagicMode)
			bloom = uncharted2_tonemap(bloom, exposure * 0.1);

		switch (AdaptMode)
		{
			case AdaptMode_FinalImage:
				color = blend_bloom(color, bloom);
				color.rgb *= exposure;
				break;
			case AdaptMode_OnlyBloom:
				bloom.rgb *= exposure;
				color = blend_bloom(color, bloom);
				break;
		}
	#else
		if (MagicMode)
			bloom.rgb = uncharted2_tonemap(bloom.rgb, 10.0);

		color = blend_bloom(color, bloom);
	#endif

	if (!MagicMode)
		color.rgb = tonemap(color.rgb);

	return color;
}

//#endregion

//#region Technique

technique NeoBloom
{
	#if NEO_BLOOM_DEPTH && NEO_BLOOM_DEPTH_ANTI_FLICKER
		pass GetDepth
		{
			VertexShader = PostProcessVS;
			PixelShader = GetDepthPS;
			RenderTarget = NeoBloom_Depth;
		}
	#endif

	pass DownSample
	{
		VertexShader = PostProcessVS;
		PixelShader = DownSamplePS;
		RenderTarget = NeoBloom_DownSample;
	}
	pass Split
	{
		VertexShader = PostProcessVS;
		PixelShader = SplitPS;
		RenderTarget = NeoBloom_TempA;
	}
	pass BlurX
	{
		VertexShader = PostProcessVS;
		PixelShader = BlurXPS;
		RenderTarget = NeoBloom_TempB;
	}
	pass BlurY
	{
		VertexShader = PostProcessVS;
		PixelShader = BlurYPS;
		RenderTarget = NeoBloom_TempA;
	}

	#if NEO_BLOOM_ADAPT
		pass CalcAdapt
		{
			VertexShader = PostProcessVS;
			PixelShader = CalcAdaptPS;
			RenderTarget = NeoBloom_Adapt;
		}
		pass SaveAdapt
		{
			VertexShader = PostProcessVS;
			PixelShader = SaveAdaptPS;
			RenderTarget = NeoBloom_LastAdapt;
		}
	#endif

	#if NEO_BLOOM_NEEDS_LAST
		pass JoinBlooms
		{
			VertexShader = PostProcessVS;
			PixelShader = JoinBloomsPS;
			RenderTarget = NeoBloom_TempB;
		}
		pass SaveLastBloom
		{
			VertexShader = PostProcessVS;
			PixelShader = SaveLastBloomPS;
			RenderTarget = NeoBloom_Last;
		}
	#endif

	pass Blend
	{
		VertexShader = BlendVS;
		PixelShader = BlendPS;
		SRGBWriteEnable = true;
	}
}

//#endregion