R"(
#version 420
#extension GL_ARB_separate_shader_objects : enable

%preprocessor

#ifdef VULKAN
#define USE_UBO 1
#endif

#define SAMPLER_MODE_NONE      0
#define SAMPLER_MODE_FONT2D    1
#define SAMPLER_MODE_FONT3D    2
#define SAMPLER_MODE_TEXTURE2D 3

#ifdef VULKAN
	layout(set=0, binding=1) uniform sampler2D fs0;
	layout(set=0, binding=2) uniform sampler2DArray fs1;
#else
	layout(binding=31) uniform sampler2D fs0;
	layout(binding=30) uniform sampler2DArray fs1;
#endif

layout(location=0) in vec2 tc0;
layout(location=1) in vec4 color;
layout(location=2) in vec4 clip_rect;

layout(location=0) out vec4 ocol;

#if USE_UBO
layout(%push_block) uniform FragmentConfiguration
{
	%push_block_offset
	uint fragment_config;
	float timestamp;
	float blur_intensity;
};
#else
	uniform uint fragment_config;
	uniform float timestamp;
	uniform float blur_intensity;
#endif

struct config_t
{
	bool clip_fragments;
	bool use_pulse_glow;
	uint sampler_mode;
};

config_t unpack_fragment_options()
{
	config_t result;
	result.clip_fragments = bitfieldExtract(fragment_config, 0, 1) != 0;
	result.use_pulse_glow = bitfieldExtract(fragment_config, 1, 1) != 0;
	result.sampler_mode = bitfieldExtract(fragment_config, 2, 2);
	return result;
}

vec4 blur_sample(sampler2D tex, vec2 coord, vec2 tex_offset)
{
	vec2 coords[9];
	coords[0] = coord - tex_offset;
	coords[1] = coord + vec2(0., -tex_offset.y);
	coords[2] = coord + vec2(tex_offset.x, -tex_offset.y);
	coords[3] = coord + vec2(-tex_offset.x, 0.);
	coords[4] = coord;
	coords[5] = coord + vec2(tex_offset.x, 0.);
	coords[6] = coord + vec2(-tex_offset.x, tex_offset.y);
	coords[7] = coord + vec2(0., tex_offset.y);
	coords[8] = coord + tex_offset;

	float weights[9] =
	{
		1., 2., 1.,
		2., 4., 2.,
		1., 2., 1.
	};

	vec4 blurred = vec4(0.);
	for (int n = 0; n < 9; ++n)
	{
		blurred += texture(tex, coords[n]) * weights[n];
	}

	return blurred / 16.f;
}

vec4 sample_image(sampler2D tex, vec2 coord, float blur_strength)
{
	vec4 original = texture(tex, coord);
	if (blur_strength == 0) return original;

	vec2 constraints = 1.f / vec2(640, 360);
	vec2 res_offset = 1.f / textureSize(fs0, 0);
	vec2 tex_offset = max(res_offset, constraints);

	// Sample triangle pattern and average
	// TODO: Nicer looking gaussian blur with less sampling
	vec4 blur0 = blur_sample(tex, coord + vec2(-res_offset.x, 0.), tex_offset);
	vec4 blur1 = blur_sample(tex, coord + vec2(res_offset.x, 0.), tex_offset);
	vec4 blur2 = blur_sample(tex, coord + vec2(0., res_offset.y), tex_offset);

	vec4 blurred = blur0 + blur1 + blur2;
	blurred /= 3.;
	return mix(original, blurred, blur_strength);
}

void main()
{
	config_t config = unpack_fragment_options();
	if (config.clip_fragments)
	{
		if (gl_FragCoord.x < clip_rect.x || gl_FragCoord.x > clip_rect.z ||
			gl_FragCoord.y < clip_rect.y || gl_FragCoord.y > clip_rect.w)
		{
			discard;
			return;
		}
	}

	vec4 diff_color = color;
	if (config.use_pulse_glow)
	{
		diff_color.a *= (sin(timestamp) + 1.f) * 0.5f;
	}

	switch (config.sampler_mode)
	{
	default:
	case SAMPLER_MODE_NONE:
		ocol = diff_color;
		break;
	case SAMPLER_MODE_FONT2D:
		ocol = texture(fs0, tc0).rrrr * diff_color;
		break;
	case SAMPLER_MODE_FONT3D:
		ocol = texture(fs1, vec3(tc0.x, fract(tc0.y), trunc(tc0.y))).rrrr * diff_color;
		break;
	case SAMPLER_MODE_TEXTURE2D:
#ifdef VULKAN
		ocol = sample_image(fs0, tc0, blur_intensity).bgra * diff_color;
#else
		ocol = sample_image(fs0, tc0, blur_intensity).rgba * diff_color;
#endif
		break;
	}
}
)"
