/*
    Mega Bezel - Creates a graphic treatment for the game play area to give a retro feel
    Copyright (C) 2019-2022 HyperspaceMadness - HyperspaceMadness@outlook.com

    Incorporates much great feedback from the libretro forum, and thanks 
    to Hunterk who helped me get started

    See more at the libretro forum
    https://forums.libretro.com/t/hsm-mega-bezel-reflection-shader-feedback-and-updates

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see [http://www.gnu.org/licenses/].
*/

/////////////// IMPORTS ///////////////
#include "common/common-functions-bezel.inc"

// Removed because BackgroundImageSize returns vec4(0,0,0,0), this happens with all user defined textures
// layout(push_constant) uniform Push
// {
// 	vec4 BackgroundImageSize;
// } params;

/////////////// DEFINES ///////////////

#define MASK_MODE_ALL 0
#define MASK_MODE_SCREEN 1
#define MASK_MODE_TUBE 2
#define MASK_MODE_INSIDE_BEZEL 3
#define MASK_MODE_BEZEL 4
#define MASK_MODE_OUTSIDE_TUBE 5
#define MASK_MODE_FRAME 6
#define MASK_MODE_OUTSIDE_BEZEL 7
#define MASK_MODE_OUTSIDE_FRAME 8

#define CUTOUT_MODE_OFF 0
#define CUTOUT_MODE_INSIDE 1
#define CUTOUT_MODE_OUTSIDE 2

float USE_INHERITED_COORD_OFF = 0;
float USE_INHERITED_COORD_ON = 1;

vec2 VIEWPORT_COORD = vec2(0.5);

/////////////// Helper Functions ///////////////

// Return the mask for the specific mode
float GetMask(float mask_mode)
{
	float mask = 	(mask_mode == MASK_MODE_ALL) ? 1 :
					(mask_mode == MASK_MODE_SCREEN) ? TUBE_DIFFUSE_MASK :
					(mask_mode == MASK_MODE_TUBE) ? TUBE_MASK :
					(mask_mode == MASK_MODE_INSIDE_BEZEL) ? INSIDE_BEZEL_MASK :
					(mask_mode == MASK_MODE_BEZEL) ? BEZEL_MASK :
					(mask_mode == MASK_MODE_OUTSIDE_TUBE) ? OUTSIDE_TUBE_MASK_FOR_IMAGE :
					(mask_mode == MASK_MODE_FRAME) ? FRAME_MASK :
					(mask_mode == MASK_MODE_OUTSIDE_BEZEL) ? OUTSIDE_BEZEL_MASK :
					(mask_mode == MASK_MODE_OUTSIDE_FRAME) ? OUTSIDE_FRAME_MASK : 0.5;
	return mask;
}

// Assumes Opacity is already encoded in alpha
vec4 BlendModeMaskLayerMix(vec4 color_under, vec4 color_over, float blend_mode, float mask_mode, float cutout_mode, float dualscreen_mode, float layer_opacity)
{
	if ( blend_mode == 0 || (dualscreen_mode != SHOW_ON_DUALSCREEN_MODE_BOTH && dualscreen_mode != SCREEN_INDEX) )
		return color_under;
	
	float cutout_mask = 1;
	if (cutout_mode == CUTOUT_MODE_INSIDE)
		cutout_mask = CUTOUT_MASK;
	if (cutout_mode == CUTOUT_MODE_OUTSIDE)
		cutout_mask = 1 - CUTOUT_MASK;

	if (blend_mode == BLEND_MODE_OFF)
		return color_under;
	
	color_over.a *= layer_opacity * GetMask(mask_mode) * cutout_mask;

	vec4 out_color = vec4(0);

	if (blend_mode == BLEND_MODE_NORMAL)
	{
		color_over.rgb *= color_over.a;
		out_color = HSM_PreMultAlphaBlend(color_under, color_over);
	}
	else
	{
		vec4 blend_color = color_under; 
		if (blend_mode == BLEND_MODE_ADD)  	 		blend_color.rgb = color_under.rgb + color_over.rgb ;
		if (blend_mode == BLEND_MODE_MULTIPLY)  	blend_color.rgb = color_under.rgb * color_over.rgb ;

		out_color = vec4(clamp(mix(color_under.rgb, blend_color.rgb, color_over.a), 0, 1), color_under.a);
	}
	return out_color;
}

// Takes a viewport coordinate and gives a new coordinate scaled by the specific scale mode
// Takes into account the default sizes of each scale mode
vec2 HSM_GetScaledCoord(vec2 in_viewport_coord,
						vec2 in_viewport_coord_unscaled,
						float texture_aspect_mode,
						float explicit_texture_aspect,
						vec2 offset_pos,
						vec2 offset_scale,
						float layer_to_follow,
						float follow_mode,
						float scale_full_with_zoom,
						float image_aspect_mode,
						float image_fill_mode,
						float split_preserve_center,
						float split_repeat_width,
						bool apply_default_scale_offset,
						inout vec2 out_placement_coord,
						inout vec2 out_placement_scale)
{
	explicit_texture_aspect = HSM_GetAspectRatioFromMode(texture_aspect_mode, explicit_texture_aspect);

	vec2 inherited_coord = in_viewport_coord / 0.5;
	vec2 inherited_placement_coord = in_viewport_coord / 0.5;
	vec2 inherited_final_coord = in_viewport_coord / 0.5;
	vec2 inherited_scale = vec2(0.5);
	vec2 default_offset_scale = vec2(0.5);

	if (layer_to_follow == FOLLOW_LAYER_VIEWPORT)
	{
		if (scale_full_with_zoom > 0.5)	
		{
			inherited_coord = in_viewport_coord;
			inherited_placement_coord = in_viewport_coord;
		}
		else
		{
			inherited_coord = in_viewport_coord_unscaled;
			inherited_placement_coord = in_viewport_coord_unscaled;
		}

		inherited_final_coord = inherited_coord;

		inherited_scale = vec2(1, 1);
		default_offset_scale = vec2(1);
	}
	else if (layer_to_follow == FOLLOW_LAYER_TUBE_DIFFUSE)
	{
		inherited_coord = TUBE_DIFFUSE_COORD;
		inherited_placement_coord = TUBE_DIFFUSE_COORD;
		inherited_final_coord = TUBE_DIFFUSE_CURVED_COORD;

		inherited_scale = TUBE_DIFFUSE_SCALE;
		default_offset_scale = vec2(1) / DEFAULT_UNCORRECTED_SCREEN_SCALE.y;
		default_offset_scale.x *= explicit_texture_aspect / DEFAULT_SCREEN_ASPECT;
	}
	else if (layer_to_follow == FOLLOW_LAYER_BEZEL_OUTSIDE)
	{
		inherited_coord = BEZEL_OUTSIDE_COORD;
		inherited_placement_coord = BEZEL_OUTSIDE_COORD;

		inherited_scale = BEZEL_OUTSIDE_SCALE;
		inherited_final_coord = BEZEL_OUTSIDE_CURVED_COORD;

		default_offset_scale = vec2(1) / DEFAULT_UNCORRECTED_BEZEL_SCALE.y;
		default_offset_scale.x *= explicit_texture_aspect / DEFAULT_BEZEL_ASPECT;
	}
	else if (layer_to_follow == FOLLOW_LAYER_BG)
	{
		inherited_coord = BACKGROUND_COORD;
		inherited_placement_coord = BACKGROUND_COORD;
		inherited_final_coord = BACKGROUND_CURVED_COORD;

		inherited_scale = BACKGROUND_SCALE;
		default_offset_scale = vec2(1);
	}
	else if (layer_to_follow == FOLLOW_LAYER_DEVICE)
	{
		inherited_coord = DEVICE_COORD;
		inherited_placement_coord = DEVICE_COORD;
		inherited_final_coord = DEVICE_CURVED_COORD;

		inherited_scale = DEVICE_SCALE;
		default_offset_scale = vec2(1);
	}
	else if (layer_to_follow == FOLLOW_LAYER_DECAL)
	{
		inherited_coord = DECAL_COORD;
		inherited_placement_coord = DECAL_COORD;
		inherited_final_coord = DECAL_CURVED_COORD;

		inherited_scale = DECAL_SCALE;
		default_offset_scale = vec2(1);
	}

	//--------------------------------
	// RETURN if we want to use the exact same coordinate of the layer we follow
	//--------------------------------
	if (follow_mode == FOLLOW_MODE_EXACT)
	{
		out_placement_coord = HSM_AddPosScaleToCoord(inherited_placement_coord, offset_pos, offset_scale);
		out_placement_scale = inherited_scale * offset_scale;
		return HSM_AddPosScaleToCoord(inherited_final_coord, offset_pos, offset_scale);
	}

	if (apply_default_scale_offset)
	{
		offset_scale *= default_offset_scale;
	}

	float output_aspect = global.OutputSize.x / global.OutputSize.y;
	float inherited_aspect = (inherited_scale.x / inherited_scale.y) * (default_offset_scale.x / default_offset_scale.y) * output_aspect;
	
	// Find the aspect difference so the image can be shown without distortion
	// This is before the user edited scale offset
	float inherited_aspect_difference = explicit_texture_aspect / inherited_aspect;

	// Get the overall scale for the placement of the texture (No Split/Fill Yet)
	out_placement_scale = inherited_scale;
	if ( image_aspect_mode == IMAGE_ASPECT_MODE_KEEP_ASPECT )
		out_placement_scale.x *= inherited_aspect_difference;
	out_placement_scale = out_placement_scale * offset_scale;

	// inherited_coord = out_placement_coord;
	out_placement_coord = HSM_AddPosScaleToCoord(inherited_placement_coord, offset_pos, out_placement_scale / inherited_scale);

	vec2 out_coord = vec2(0.5);

	vec2 drawing_scale = out_placement_scale;
	float slide_x = 0;

	if ( image_fill_mode == FILL_MODE_SPLIT_FILL_HORIZONTAL )
	{
		float abs_ctr_coord_x = abs(out_placement_coord.x - 0.5);
		// Correct the aspect so it matches the texture and is never stretched
		float placement_aspect = out_placement_scale.x / out_placement_scale.y * output_aspect;
		float placement_aspect_difference = explicit_texture_aspect / placement_aspect;
		drawing_scale.x *= placement_aspect_difference;

		float center_width = split_preserve_center * placement_aspect_difference;
		if ( abs_ctr_coord_x > center_width)
		{
			slide_x = ((placement_aspect - explicit_texture_aspect) / placement_aspect) / 2.0;
		}

		float repeat_width = split_repeat_width * placement_aspect_difference;
		if (abs_ctr_coord_x > center_width && 
			abs_ctr_coord_x < center_width + slide_x && 
			repeat_width > 0)
		{
			if (clamp(split_repeat_width - 0.001, 0, 1) == 0)
				slide_x = (abs_ctr_coord_x - center_width);
			else
				slide_x = (abs_ctr_coord_x - 0.001 - center_width) - mod(clamp(abs_ctr_coord_x - 0.01 - center_width, 0, 1), repeat_width);
		}

		if ( out_placement_coord.x < 0.5 )
			slide_x *= -1;
		inherited_coord.x -= slide_x;
	}

	// The inherited_coord is already the coord from the inherited space
	// We only need to apply an offset from this
	out_coord = HSM_AddPosScaleToCoord(inherited_coord, offset_pos, drawing_scale / inherited_scale);
	// out_coord.x -= slide_x;

	return out_coord;
}

//TODO remove this and replace with simpler calls
float GetFade(float current_position, float corner_position, float fade_distance)
{
	return smoothstep(corner_position + fade_distance / 2, corner_position - fade_distance / 2, current_position);
}

//////////////////////////////////////////////////////////////////////////////////////////////////
#pragma stage vertex

layout(location = 0) in vec4 Position;
layout(location = 1) in vec2 TexCoord;

layout(location = 6) out vec2 vTexCoord;
layout(location = 7) out vec2 UNFLIPPED_VIEWPORT_COORD;

//////////////////////////////////////////////////////////////////////////////////////////////////
void main()
{
	gl_Position = global.MVP * Position;
	vTexCoord = TexCoord;

	UNFLIPPED_VIEWPORT_COORD = vTexCoord;

	VIEWPORT_COORD = vTexCoord * 1.0001;
}

//////////////////////////////////////////////////////////////////////////////////////////////////
#pragma stage fragment

layout(location = 6) in vec2 vTexCoord;
layout(location = 7) in vec2 UNFLIPPED_VIEWPORT_COORD;
layout(location = 8) in vec3 BEZEL_FRAME_ORIGINAL_COLOR_RGB;

layout(location = 0) out vec4 FragColor;

// Pass Framebuffer Textures
layout(set = 0, binding = 1)  uniform sampler2D InfoCachePass;

layout(set = 0, binding = 3)  uniform sampler2D BackgroundImage;
layout(set = 0, binding = 4)  uniform sampler2D BackgroundVertImage;
layout(set = 0, binding = 5)  uniform sampler2D NightLightingImage;
layout(set = 0, binding = 6)  uniform sampler2D NightLighting2Image;

layout(set = 0, binding = 7)  uniform sampler2D LEDImage;
layout(set = 0, binding = 8)  uniform sampler2D FrameTextureImage;
layout(set = 0, binding = 9)  uniform sampler2D DeviceImage;
layout(set = 0, binding = 10) uniform sampler2D DeviceVertImage;
layout(set = 0, binding = 11) uniform sampler2D DeviceLEDImage;
layout(set = 0, binding = 12) uniform sampler2D DecalImage;
layout(set = 0, binding = 13) uniform sampler2D CabinetGlassImage;
layout(set = 0, binding = 14) uniform sampler2D TopLayerImage;

#ifdef LAYERS_OVER_CRT
layout(set = 0, binding = 15) uniform sampler2D BR_LayersOverCRTPassFeedback;
#define PassFeedback BR_LayersOverCRTPassFeedback
#else
layout(set = 0, binding = 15) uniform sampler2D BR_LayersUnderCRTPassFeedback;
#define PassFeedback BR_LayersUnderCRTPassFeedback
#endif

//////////////////////////////////////////////////////////////////////////////////////////////////
void main()
{
	if (HSM_AB_COMPARE_FREEZE_GRAPHICS == 1 && HSM_GetIsInABCompareArea(vTexCoord))
	{
		FragColor = texture(PassFeedback, vTexCoord);
		return;
	}

	VIEWPORT_UNSCALED_COORD = HSM_GetViewportCoordWithFlip(vTexCoord);
	VIEWPORT_COORD = HSM_GetViewportCoordWithZoomAndPan(vTexCoord);

	HSM_UpdateGlobalScreenValuesFromCache(InfoCachePass, vTexCoord);

	vec4 feedback_color_test = texture(PassFeedback, vec2(0,0));
	if (HSM_CACHE_GRAPHICS_ON > 0.5 && feedback_color_test.a < 0 && !CACHE_INFO_CHANGED)
	{
		FragColor = texture(PassFeedback, UNFLIPPED_VIEWPORT_COORD);
		return;
	}

	// AMBIENT LIGHTING IMAGES
	vec4 ambient_image = vec4(1);
	vec4 ambient2_image = vec4(1);
	HSM_Fill_Ambient_Images(VIEWPORT_COORD, VIEWPORT_UNSCALED_COORD, TUBE_DIFFUSE_COORD, TUBE_DIFFUSE_SCALE, HSM_AMBIENT_LIGHTING_SWAP_IMAGE_MODE, NightLightingImage, NightLighting2Image, ambient_image, ambient2_image);

	TUBE_DIFFUSE_CURVED_COORD = HSM_GetCurvedCoord(SCREEN_COORD, HSM_TUBE_BLACK_EDGE_CURVATURE_SCALE, TUBE_DIFFUSE_ASPECT);

	// TODO this should probably just use TUBE_COORD
	// TODO should probably use TUBE_ASPECT
	vec2 tube_curved_coord = HSM_GetTubeCurvedCoord(TUBE_DIFFUSE_COORD, 1, TUBE_DIFFUSE_SCALE, TUBE_SCALE, TUBE_DIFFUSE_ASPECT, 1);
	vec2 tube_curved_coord_ctr = tube_curved_coord - 0.5;
	vec2 edge_mask_coord = tube_curved_coord_ctr * (1 - (HSM_BZL_INNER_EDGE_THICKNESS / vec2(TUBE_DIFFUSE_ASPECT, 1))) + 0.5;

	float bezel_corner_radius = HSM_BZL_INNER_CORNER_RADIUS_SCALE * HSM_GLOBAL_CORNER_RADIUS;
	if(HSM_BZL_USE_INDEPENDENT_CURVATURE > 0)
		bezel_corner_radius = HSM_BZL_INNER_CORNER_RADIUS_SCALE * DEFAULT_SCREEN_CORNER_RADIUS;
	
	float edge_mask =  HSM_GetCornerMask(edge_mask_coord, TUBE_DIFFUSE_ASPECT, bezel_corner_radius, HSM_BZL_INNER_EDGE_SHARPNESS);

	TUBE_MASK = HSM_GetCornerMask(tube_curved_coord, TUBE_DIFFUSE_ASPECT, bezel_corner_radius, 0.99);

	// Shrink the mask by 0.001 to clip off outer edge
	TUBE_DIFFUSE_MASK = HSM_GetCornerMask(((TUBE_DIFFUSE_CURVED_COORD - 0.5) * 1.001) + 0.5, TUBE_DIFFUSE_ASPECT, HSM_GLOBAL_CORNER_RADIUS * HSM_TUBE_BLACK_EDGE_CORNER_RADIUS_SCALE, HSM_TUBE_BLACK_EDGE_SHARPNESS);

	//----------------------------------------------------
	//  Calculate Outside mapping Coords
	//----------------------------------------------------

	/* This first big chunk is to get a mapping of the space outside of the screen which is continuous
	This is more complicated than you would expect because since we are using curved coordinates 
	there are discontinuities outside the normal screen corners, e.g. where x > 1 and y > 1
	So instead of trying to use the coordinates from the screen/tube we use a larger space 
	and subtract the screen space to see how far we are outside of the sreen
	*/

	// Additional scale to be applied to the tube scale to create an expanded mapping area 
	float outermap_scale = 2.3;

	// Get a range width from the outer tube edge to the outer edge of the outermap
	float outermap_range = 0.5 * outermap_scale * 0.7;
	vec2 outermap_screen_size_from_center = vec2(0.5, 0.5);
	vec2 outermap_warped_outside_screen_vector = (tube_curved_coord_ctr - clamp(tube_curved_coord_ctr, -0.490, 0.490)) * vec2(1 / TUBE_DIFFUSE_ASPECT, 1);
	float output_aspect = global.OutputSize.x / global.OutputSize.y;
	float outside_ratio_warped = clamp(length(outermap_warped_outside_screen_vector) / outermap_range, 0, 1);
	vec2 outermap_screen_corner_ctr_coord = vec2(0.5, -0.5);

	// Get a coordinate offset so it is centered around the corner
	vec2 outermap_coord_warped_ctr_at_screen_corner = abs(tube_curved_coord_ctr) - vec2(0.5);

	// Have to get the scale of the coordinates so we can figure out the size of the onscreen rectangle of the area 
	HSM_GetBezelCoords(TUBE_DIFFUSE_COORD, 
						TUBE_DIFFUSE_SCALE, 
						TUBE_SCALE, 
						TUBE_DIFFUSE_ASPECT, 
						true,
						BEZEL_OUTSIDE_SCALE,
						BEZEL_OUTSIDE_COORD, 
						BEZEL_OUTSIDE_CURVED_COORD, 
						FRAME_OUTSIDE_CURVED_COORD);

	OUTSIDE_BEZEL_MASK = 1 - HSM_GetCornerMask(BEZEL_OUTSIDE_CURVED_COORD, TUBE_DIFFUSE_ASPECT, HSM_GLOBAL_CORNER_RADIUS * HSM_BZL_OUTER_CORNER_RADIUS_SCALE, 0.9);

	// Get color for the frame area outside of the bezel
	vec2 frame_outside_coord_ctr = FRAME_OUTSIDE_CURVED_COORD - 0.5;
	float SHADOW_OUTSIDE_FRAME_MASK = 1 - HSM_GetCornerMask(frame_outside_coord_ctr * 1.01 + 0.5, TUBE_DIFFUSE_ASPECT, HSM_FRM_OUTER_CORNER_RADIUS, 1);
	OUTSIDE_FRAME_MASK = 1 - HSM_GetCornerMask(frame_outside_coord_ctr + 0.5, TUBE_DIFFUSE_ASPECT, HSM_FRM_OUTER_CORNER_RADIUS, 1);
	OUTSIDE_FRAME_MASK_FOR_IMAGE = 1 - HSM_GetCornerMask(frame_outside_coord_ctr * 0.999 + 0.5, TUBE_DIFFUSE_ASPECT, HSM_FRM_OUTER_CORNER_RADIUS, 1);
	// Get masks for shadows, from frame as well as sides and top and bottom of viewport
	INSIDE_BEZEL_MASK = 1 - OUTSIDE_BEZEL_MASK;
	BEZEL_MASK = INSIDE_BEZEL_MASK * (1 - TUBE_MASK);
	FRAME_MASK = OUTSIDE_BEZEL_MASK * (1 - OUTSIDE_FRAME_MASK);

#ifdef LAYERS_UNDER_CRT
	//----------------------------------------------------
	//  Calculate Corner Highlight Mask
	//----------------------------------------------------
	const float pi = 3.1415;

	// Get amount to shift the point at the outer corner to match the overall position offset
	vec2 pos_shift_offset = vec2(0, HSM_BZL_OUTER_POSITION_Y) * TUBE_DIFFUSE_SCALE.y / outermap_scale;
	pos_shift_offset *= tube_curved_coord.y > 0.5 ? 1 : -1;

	// Get the direction vector from the inner corner of the bezel pointing at the outer corner 
	vec2 corner_crease_dir = (outermap_screen_corner_ctr_coord + pos_shift_offset) / vec2(HSM_BZL_HEIGHT + 1, HSM_BZL_WIDTH + 1) - (outermap_screen_corner_ctr_coord) ;
	corner_crease_dir *= vec2(TUBE_DIFFUSE_ASPECT, 1);

	float aspect_corner_length_scale_offset = TUBE_DIFFUSE_ASPECT > 1 ? 0.9 : 1.5;
	float corner_crease_length = length(corner_crease_dir * aspect_corner_length_scale_offset);

	// A hack to adjust the angle offset, because without it the corner angle isn't pointing exactly at the corner
	// This offset is the opposite direction for vertical and horizontal aspect ratio
	float corner_rotation_offset = (SCREEN_COORD.y < 0.5) ? -HSM_REFLECT_CORNER_ROTATION_OFFSET_TOP : -HSM_REFLECT_CORNER_ROTATION_OFFSET_BOTTOM;

	if (HSM_CURVATURE_MODE == 0)
		// If we are using a 3d Curvature no offset is necessary
		corner_rotation_offset += (TUBE_DIFFUSE_ASPECT > 1) ? 2 : 3;

	// Convert direction vector to an angle so we can rotate the corner crease direction
	float corner_angle_degrees = atan(corner_crease_dir.y / corner_crease_dir.x) / (2 * pi) * 360;

	corner_angle_degrees += corner_rotation_offset;
	float corner_angle_radians = corner_angle_degrees / 360 * 2 * pi;
	corner_crease_dir = vec2(cos(corner_angle_radians), sin(corner_angle_radians));

	// Get the distance perpendicular to the crease direction so we can use it to fade later
	float distance_from_crease = HHLP_GetDistanceToLine(outermap_coord_warped_ctr_at_screen_corner.x, outermap_coord_warped_ctr_at_screen_corner.y, 1, corner_crease_dir.y / corner_crease_dir.x, 0 );

	float fade_out_to_corner = HHLP_QuadraticBezier(clamp(length(outermap_warped_outside_screen_vector) / (corner_crease_length * 2), 0, 1), vec2(0.5, HSM_REFLECT_CORNER_SPREAD_FALLOFF / 100));

	float corner_fade_width_inner = HSM_REFLECT_CORNER_INNER_SPREAD * (TUBE_DIFFUSE_SCALE.x + TUBE_DIFFUSE_SCALE.y) * bezel_corner_radius / 10 / 250 * 1.2;
	float corner_fade_width_outer = HSM_REFLECT_CORNER_OUTER_SPREAD * (TUBE_DIFFUSE_SCALE.x + TUBE_DIFFUSE_SCALE.y) * HSM_GLOBAL_CORNER_RADIUS * HSM_BZL_OUTER_CORNER_RADIUS_SCALE / 10 / 250 * 1.6;
	float corner_fade_width = (corner_fade_width_inner + fade_out_to_corner * (corner_fade_width_outer - corner_fade_width_inner));

	// Get a vector perpendicular to the crease that we can shift the crease to blend between bottom/top and sides
	vec2 corner_crease_perp_dir = normalize(vec2(corner_crease_dir.y, corner_crease_dir.x));
	vec2 corner_coord_shifted = outermap_coord_warped_ctr_at_screen_corner - corner_crease_perp_dir * corner_fade_width / 2;
	vec2 corner_crease_dir_shifted = corner_crease_dir - corner_crease_perp_dir * corner_fade_width / 2;

	// Get the distance to this shifted crease
	float distance_from_crease_shifted = HHLP_GetDistanceToLine(corner_coord_shifted.x, corner_coord_shifted.y, 1, corner_crease_dir_shifted.y / corner_crease_dir_shifted.x, 0 );

	float top_half_mask = smoothstep(0.55, 0.5, tube_curved_coord.y);
	float left_half_mask = smoothstep(0.55, 0.5, tube_curved_coord.x);

	// Get a mask which transitions between sides and top/bottom at the corner crease  
	float top_bottom_vs_sides_mask = dot(normalize(corner_coord_shifted), normalize(corner_crease_dir_shifted)) > 0 ? 1 - smoothstep(0, corner_fade_width / 2, distance_from_crease_shifted) : 1;

	// Masks isolating specific parts
	float sides_mask = 1 - top_bottom_vs_sides_mask;
	float top_mask = top_half_mask * top_bottom_vs_sides_mask;
	float bottom_mask = (1 -top_half_mask) * top_bottom_vs_sides_mask;

	float corner_mask = smoothstep(corner_fade_width / 2, 0, distance_from_crease);

	float top_corner_mask = corner_mask * top_half_mask;
	float bottom_corner_mask = corner_mask * (1 - top_half_mask);

	float frame_inner_edge_mask = (HSM_FRM_INNER_EDGE_THICKNESS == 0) ? 0 : 1 - HSM_GetCornerMask(	(BEZEL_OUTSIDE_CURVED_COORD - 0.5) * (1 + (HSM_FRM_INNER_EDGE_THICKNESS / vec2(TUBE_DIFFUSE_ASPECT, 1))) + 0.5, 
																										TUBE_DIFFUSE_ASPECT, 
																										HSM_BZL_OUTER_CORNER_RADIUS_SCALE * HSM_GLOBAL_CORNER_RADIUS, 
																										0.9);
	float outside_tube_mask_wider = 1 - HSM_GetCornerMask(tube_curved_coord_ctr * 0.996 + 0.5, TUBE_DIFFUSE_ASPECT, bezel_corner_radius, 0.9);
	float tube_shadow_mask = HSM_GetCornerMask(tube_curved_coord_ctr + 0.5, TUBE_DIFFUSE_ASPECT, bezel_corner_radius, 0);
	float tube_edge_shadow_mult = HSM_BZL_INNER_EDGE_SHADOW * (tube_shadow_mask) + (1 - HSM_BZL_INNER_EDGE_SHADOW);

	float edge_highlight_mask = 0;

	// ----------------------------------------------------
	// Generated Bezel
	// ----------------------------------------------------

	/* This first bit is to get a mapping of the space outside of the screen which is continuous
	This is more complicated than you would expect because since we are using curved coordinates 
	there are discontinuities outside the normal screen corners, e.g. where x > 1 and y > 1
	So instead of trying to use the coordinates from the screen/tube we use a larger space 
	and subtract the screen space to see how far we are outside of the sreen
	*/

	float hmbz_bezel_highlight_edge = 0.9;
	float hmbz_bezel_highlight_top = 0.2;
	float hmbz_bezel_highlight_bottom = 0.3;
	float hmbz_bezel_highlight_sides = 0.2;
	
	float hmbz_bezel_highlight_falloff_speed = 0.5;
	float hmbz_bezel_highlight_width = 0.25;

	float hmbz_bezel_edge_highlight_width = 0.8;
	
	float hmbz_bezel_brightness_frame_inner_edge = 0.014;
	float hmbz_bezel_brightness_frame_outer_edge = 0.0;
	float hmbz_brightness_shadow = 0;
	float hmbz_frame_brightness = 100;

	// Not sure why we need linearize this but it seems to have a smoother range this way
	vec3 base_color = HSM_Linearize(vec4(HSM_HSVtoRGB(vec3(HSM_BZL_COLOR_HUE, HSM_BZL_COLOR_SATURATION, HSM_BZL_COLOR_VALUE)), 1), DEFAULT_SRGB_GAMMA).rgb;
	float noise_mask = clamp(fract(sin(dot(tube_curved_coord_ctr + vec2(0.5, 0.5) + 1, vec2(12.9898, 78.233))) * 43758.5453), 0, 1);
	vec3 base_color_with_noise = mix(base_color, 1.5 * base_color * noise_mask, HSM_BZL_NOISE);
	vec3 top_color = HSM_BZL_BRIGHTNESS_MULT_TOP * HSM_BZL_BRIGHTNESS * base_color_with_noise;
	vec3 bottom_color = HSM_BZL_BRIGHTNESS_MULT_BOTTOM * HSM_BZL_BRIGHTNESS * base_color_with_noise;
	vec3 sides_color = mix(HSM_BZL_BRIGHTNESS_MULT_SIDES * HSM_BZL_BRIGHTNESS_MULT_SIDE_RIGHT * HSM_BZL_BRIGHTNESS * base_color_with_noise,
							HSM_BZL_BRIGHTNESS_MULT_SIDES * HSM_BZL_BRIGHTNESS_MULT_SIDE_LEFT * HSM_BZL_BRIGHTNESS * base_color_with_noise,
							left_half_mask);

	vec3 frame_base_color = base_color;
	vec3 frame_base_color_with_noise = base_color_with_noise;
	if (HSM_FRM_USE_INDEPENDENT_COLOR > 0)
	{
		frame_base_color = HSM_Linearize(vec4(HSM_HSVtoRGB(vec3(HSM_FRM_COLOR_HUE, HSM_FRM_COLOR_SATURATION, HSM_FRM_COLOR_VALUE)), 1), DEFAULT_SRGB_GAMMA).rgb;
		frame_base_color_with_noise = mix(frame_base_color, 1.5 * frame_base_color * noise_mask, HSM_FRM_NOISE);
	}

	vec3 frame_color = hmbz_frame_brightness / 100 * mix(frame_base_color, 1.5 * frame_base_color * noise_mask, 0.6 * HSM_FRM_NOISE);
	vec3 outside_frame_color = hmbz_brightness_shadow * base_color_with_noise;

	vec3 bezel_diffuse_color = mix(sides_color, top_color, top_mask);
	bezel_diffuse_color = mix(bezel_diffuse_color, bottom_color, bottom_mask);

	float top_center_highlight_mask 	= hmbz_bezel_highlight_top * top_mask * HHLP_QuadraticBezier(smoothstep(hmbz_bezel_highlight_width, 0, abs(tube_curved_coord_ctr.x)), vec2(0.5, hmbz_bezel_highlight_falloff_speed));
	float bottom_center_highlight_mask 	= hmbz_bezel_highlight_bottom * bottom_mask * HHLP_QuadraticBezier(smoothstep(hmbz_bezel_highlight_width, 0, abs(tube_curved_coord_ctr.x)), vec2(0.5, hmbz_bezel_highlight_falloff_speed));
	float sides_highlight_mask 			= hmbz_bezel_highlight_sides * sides_mask * HHLP_QuadraticBezier(smoothstep(hmbz_bezel_highlight_width, 0, abs(tube_curved_coord_ctr.y)), vec2(0.5, hmbz_bezel_highlight_falloff_speed));

	float edge_top_center_highlight_mask 		= hmbz_bezel_highlight_top * top_mask * HHLP_QuadraticBezier(smoothstep(hmbz_bezel_edge_highlight_width, 0, abs(tube_curved_coord_ctr.x)), vec2(0.8, 0));
	float edge_bottom_center_highlight_mask 	= hmbz_bezel_highlight_bottom * bottom_mask * HHLP_QuadraticBezier(smoothstep(hmbz_bezel_edge_highlight_width, 0, abs(tube_curved_coord_ctr.x)), vec2(0.8, 0));
	float edge_sides_highlight_mask 			= hmbz_bezel_highlight_sides * sides_mask * HHLP_QuadraticBezier(smoothstep(hmbz_bezel_edge_highlight_width, 0, abs(tube_curved_coord_ctr.y)), vec2(0.8, 0));

	edge_highlight_mask = hmbz_bezel_highlight_edge * edge_mask * (edge_top_center_highlight_mask + edge_bottom_center_highlight_mask + edge_sides_highlight_mask);

	// Combine all the individual highlights into one mask
	float combined_highlight_mask = (1 + 2.5 * HSM_BZL_NOISE) * (1 - noise_mask * 2.5 * HSM_BZL_NOISE) * (top_center_highlight_mask + bottom_center_highlight_mask + sides_highlight_mask);
	float bezel_highlight_multiplier = HSM_BZL_HIGHLIGHT * combined_highlight_mask + HSM_BZL_HIGHLIGHT * edge_highlight_mask;
	vec3 bezel_color = bezel_diffuse_color * (1 + 15 * bezel_highlight_multiplier) + 1 * bezel_highlight_multiplier;

	// Add the inner edge highlight on top of the bezel color which has it's own highlight
	float inner_edge_highlight_multiplier = hmbz_bezel_brightness_frame_inner_edge + HSM_BZL_HIGHLIGHT * 10 * hmbz_bezel_brightness_frame_inner_edge;
	vec3 frame_inner_edge_color = frame_base_color * (1 + 15 * inner_edge_highlight_multiplier) +	0.5 * inner_edge_highlight_multiplier;

	bezel_color = mix(bezel_color, frame_inner_edge_color, frame_inner_edge_mask);

	float dist_inside_outer_edge = min(0.50 - abs(frame_outside_coord_ctr.x), 0.50 - abs(frame_outside_coord_ctr.y));
	float frame_outer_edge_width = HSM_FRM_OUTER_EDGE_THICKNESS;
	vec3 frame_diffuse_color = mix(frame_color, 0.2 * frame_color, HSM_FRM_OUTER_EDGE_SHADING * smoothstep(frame_outer_edge_width, 0, dist_inside_outer_edge));

	if (HSM_FRM_TEXTURE_OPACITY > 0)
	{
		// TODO need to do Mipmapping sample?
		vec4 frame_texture_color = HSM_Linearize(texture(FrameTextureImage, FRAME_OUTSIDE_CURVED_COORD), DEFAULT_SRGB_GAMMA);
		frame_diffuse_color = HSM_BlendModeLayerMix(vec4(frame_diffuse_color, 1), frame_texture_color, HSM_FRM_TEXTURE_BLEND_MODE, HSM_FRM_TEXTURE_OPACITY).rgb;
	}

	// Composite in color from outside the bezel
	vec3 bezel_and_frame_rgb = mix(bezel_color, frame_diffuse_color, OUTSIDE_BEZEL_MASK);

	// Get masks on side of frame to multiply together to get a shadow around the frame
	// Get vector from the screen edge outward
	float frame_edge = 0.495;
	float dist_outside_frame = length(clamp(abs(frame_outside_coord_ctr * 1.01) - frame_edge, 0, 1) * vec2(TUBE_DIFFUSE_ASPECT, 1));

	vec4 frame_shadow_layer = vec4(0);
	if (HSM_FRM_OPACITY > 0.001)
		frame_shadow_layer.a = SHADOW_OUTSIDE_FRAME_MASK * HHLP_QuadraticBezier(smoothstep(HSM_FRM_SHADOW_WIDTH, 0, dist_outside_frame), vec2(1, 0));

	//----------------------------------------------------
	// Generated Bezel
	//----------------------------------------------------

	vec4 bezel_layer = vec4(0);
	vec4 frame_layer = vec4(0);

	if (HSM_BZL_OPACITY > 0 || HSM_FRM_OPACITY > 0)
	{
		// Create image of bezel & frame with outside of frame transparent
		vec4 bezel_and_frame_rgba = vec4(bezel_and_frame_rgb, 1);

		// Cut out Tube Area
		if (HSM_STATIC_LAYERS_GAMMA != 1)
			bezel_and_frame_rgba = HSM_ApplyGamma(bezel_and_frame_rgba, HSM_STATIC_LAYERS_GAMMA);

		bezel_and_frame_rgba.rgb = ApplyAmbientImages(bezel_and_frame_rgba.rgb,
															ambient_image.rgb,
															ambient2_image.rgb,
															HSM_BZL_AMBIENT_LIGHTING_MULTIPLIER,
															HSM_BZL_AMBIENT2_LIGHTING_MULTIPLIER,
															1,
															HSM_BZL_BLEND_MODE,
															HSM_AMBIENT_LIGHTING_SWAP_IMAGE_MODE);

		// Contract the tube mask to leave a little extra black ring at the edge of the bezel
		// otherwise this will show slivers of the background otherwise
		float tube_mask_contracted = HSM_GetCornerMask((tube_curved_coord - 0.5) * 1.004 + 0.5, TUBE_DIFFUSE_ASPECT, bezel_corner_radius, 0.99);
		float FRAME_AND_BEZEL_MASK = (1 - tube_mask_contracted) * (1 - OUTSIDE_FRAME_MASK);

		if (HSM_BZL_OPACITY > 0 || HSM_FRM_OPACITY > 0)
			bezel_layer = clamp(bezel_and_frame_rgba * FRAME_AND_BEZEL_MASK, 0, 1);

		frame_shadow_layer *= HSM_FRM_SHADOW_OPACITY;

		if (HSM_FRM_SHADOW_OPACITY > 0)
			bezel_layer = BlendModeMaskLayerMix(frame_shadow_layer,
													bezel_layer, 
													BLEND_MODE_NORMAL, 
													MASK_MODE_ALL,
													0, 
													SHOW_ON_DUALSCREEN_MODE_BOTH,
													1);


		float bezel_opacity_mult = HSM_BZL_OPACITY + OUTSIDE_BEZEL_MASK * (1 - HSM_BZL_OPACITY);
		float frame_opacity_mult = HSM_FRM_OPACITY + (1 - OUTSIDE_BEZEL_MASK) * (1 - HSM_FRM_OPACITY);

		bezel_layer *= bezel_opacity_mult * frame_opacity_mult;
	}

	float TUBE_MASK_EXPAND = HSM_GetCornerMask((tube_curved_coord - 0.5) * 0.997 + 0.5, TUBE_DIFFUSE_ASPECT, bezel_corner_radius, 0.99);
	
	// vec4 tube_bg_layer = vec4(0, 0, 0, TUBE_MASK_EXPAND * HSM_GetTubeOpacity());
	vec4 tube_bg_layer = vec4(0, 0, 0, 0);

// end of ifndef LAYERS_UNDER_CRT
#endif

	//-----------------------------------------------------------------------------------------
	// Background
	//-----------------------------------------------------------------------------------------
	bool bg_use_vert_image = SCREEN_ASPECT < 1 && textureSize(BackgroundVertImage, 0).y > 16 ? true : false; 
	vec2 bg_size = bg_use_vert_image ? textureSize(BackgroundVertImage, 0) : textureSize(BackgroundImage, 0);
	BACKGROUND_CURVED_COORD = HSM_GetScaledCoord(VIEWPORT_COORD,
												VIEWPORT_UNSCALED_COORD,
												TEXTURE_ASPECT_MODE_EXPLICIT,
												bg_size.x / bg_size.y,
												vec2(HSM_BG_POS_X, HSM_BG_POS_Y),
												vec2(HSM_BG_SCALE * HSM_BG_SCALE_X, HSM_BG_SCALE),
												HSM_BG_FOLLOW_LAYER,
												HSM_BG_FOLLOW_MODE,
												HSM_BG_FOLLOW_FULL_USES_ZOOM,
												HSM_BG_SCALE_KEEP_ASPECT,
												HSM_BG_FILL_MODE,
												HSM_BG_SPLIT_PRESERVE_CENTER,
												HSM_BG_SPLIT_REPEAT_WIDTH,
												true,
												BACKGROUND_COORD,
												BACKGROUND_SCALE);

	if (HSM_BG_MIRROR_WRAP == 1)
		BACKGROUND_CURVED_COORD = HSM_GetMirrorWrapCoord(BACKGROUND_CURVED_COORD);

	vec4 bg_image = vec4(0);
	if (HSM_BG_OPACITY > 0 && bg_size.y > 16)
	{
		if (bg_use_vert_image)
			bg_image = HSM_GetMipmappedTexSample(BackgroundVertImage, BACKGROUND_CURVED_COORD, BACKGROUND_SCALE, HSM_BG_MIPMAPPING_BLEND_BIAS);
		else
			bg_image = HSM_GetMipmappedTexSample(BackgroundImage, BACKGROUND_CURVED_COORD, BACKGROUND_SCALE, HSM_BG_MIPMAPPING_BLEND_BIAS);

		// Premultiply Alpha
		bg_image = HSM_GetPreMultipliedColorLinear(bg_image, HSM_BG_SOURCE_MATTE_TYPE, DEFAULT_SRGB_GAMMA);

		// HSV Adjustments
		bg_image.rgb = HSM_ApplyHSVAdjustment(bg_image.rgb, HSM_BG_HUE, HSM_BG_SATURATION, HSM_BG_BRIGHTNESS, HSM_BG_COLORIZE_ON, HSM_BG_GAMMA);

		if (HSM_STATIC_LAYERS_GAMMA != 1)
			bg_image = HSM_ApplyGamma(bg_image, HSM_STATIC_LAYERS_GAMMA);

		bg_image.rgb = ApplyAmbientImages(bg_image.rgb, 
												ambient_image.rgb,
												ambient2_image.rgb,
												HSM_BG_AMBIENT_LIGHTING_MULTIPLIER,
												HSM_BG_AMBIENT2_LIGHTING_MULTIPLIER,
												HSM_BG_APPLY_AMBIENT_IN_ADD_MODE,
												HSM_BG_BLEND_MODE,
												HSM_AMBIENT_LIGHTING_SWAP_IMAGE_MODE);
	}

	//----------------------------------------------------
	// Device Image
	//----------------------------------------------------
	bool device_use_vert_image = SCREEN_ASPECT < 1 && textureSize(BackgroundVertImage, 0).y > 16 ? true : false; 
	vec2 device_size = device_use_vert_image ? textureSize(DeviceVertImage, 0) : textureSize(DeviceImage, 0);
	DEVICE_CURVED_COORD = HSM_GetScaledCoord(VIEWPORT_COORD,
											VIEWPORT_UNSCALED_COORD,
											TEXTURE_ASPECT_MODE_EXPLICIT,
											device_size.x / device_size.y,
											vec2(HSM_DEVICE_POS_X, HSM_DEVICE_POS_Y),
											vec2(HSM_DEVICE_SCALE * HSM_DEVICE_SCALE_X, HSM_DEVICE_SCALE),
											HSM_DEVICE_FOLLOW_LAYER,
											HSM_DEVICE_FOLLOW_MODE,
											HSM_DEVICE_FOLLOW_FULL_USES_ZOOM,
											HSM_DEVICE_SCALE_KEEP_ASPECT,
											HSM_DEVICE_FILL_MODE,
											HSM_DEVICE_SPLIT_PRESERVE_CENTER,
											HSM_DEVICE_SPLIT_REPEAT_WIDTH,
											true,
											DEVICE_COORD,
											DEVICE_SCALE);

	vec4 device_image = vec4(0);
	if (HSM_DEVICE_OPACITY > 0 && device_size.y > 16)
	{
		device_image = device_use_vert_image ? 	HSM_GetMipmappedTexSample(DeviceVertImage, DEVICE_CURVED_COORD, DEVICE_SCALE, HSM_DEVICE_MIPMAPPING_BLEND_BIAS) :
											HSM_GetMipmappedTexSample(DeviceImage, DEVICE_CURVED_COORD, DEVICE_SCALE, HSM_DEVICE_MIPMAPPING_BLEND_BIAS);

		// Premultiply Alpha
		device_image = HSM_GetPreMultipliedColorLinear(device_image, HSM_DEVICE_SOURCE_MATTE_TYPE, DEFAULT_SRGB_GAMMA);

		// HSV Adjustments
		device_image.rgb = HSM_ApplyHSVAdjustment(device_image.rgb, HSM_DEVICE_HUE, HSM_DEVICE_SATURATION, HSM_DEVICE_BRIGHTNESS, HSM_DEVICE_COLORIZE_ON, HSM_DEVICE_GAMMA);

		if (HSM_STATIC_LAYERS_GAMMA != 1)
			device_image = HSM_ApplyGamma(device_image, HSM_STATIC_LAYERS_GAMMA);

		device_image.rgb = ApplyAmbientImages(device_image.rgb, 
													ambient_image.rgb,
													ambient2_image.rgb,
													HSM_DEVICE_AMBIENT_LIGHTING_MULTIPLIER,
													HSM_DEVICE_AMBIENT2_LIGHTING_MULTIPLIER,
													HSM_DEVICE_APPLY_AMBIENT_IN_ADD_MODE,
													HSM_DEVICE_BLEND_MODE,
													HSM_AMBIENT_LIGHTING_SWAP_IMAGE_MODE);
	}

	//----------------------------------------------------
	// DeviceLED Image
	//----------------------------------------------------
	vec2 deviceled_size = textureSize(DeviceLEDImage, 0);
	DEVICELED_CURVED_COORD = HSM_GetScaledCoord(VIEWPORT_COORD,
											VIEWPORT_UNSCALED_COORD,
											TEXTURE_ASPECT_MODE_EXPLICIT,
											deviceled_size.x / deviceled_size.y,
											vec2(HSM_DEVICELED_POS_X, HSM_DEVICELED_POS_Y),
											vec2(HSM_DEVICELED_SCALE * HSM_DEVICELED_SCALE_X, HSM_DEVICELED_SCALE),
											HSM_DEVICELED_FOLLOW_LAYER,
											HSM_DEVICELED_FOLLOW_MODE,
											HSM_DEVICELED_FOLLOW_FULL_USES_ZOOM,
											HSM_DEVICELED_SCALE_KEEP_ASPECT,
											HSM_DEVICELED_FILL_MODE,
											HSM_DEVICELED_SPLIT_PRESERVE_CENTER,
											HSM_DEVICELED_SPLIT_REPEAT_WIDTH,
											true,
											DEVICELED_COORD,
											DEVICELED_SCALE);

	vec4 deviceled_image = vec4(0);
	if (HSM_DEVICELED_OPACITY > 0 && deviceled_size.y > 16)
	{
		deviceled_image = HSM_GetMipmappedTexSample(DeviceLEDImage, DEVICELED_CURVED_COORD, DEVICELED_SCALE, HSM_DEVICELED_MIPMAPPING_BLEND_BIAS);

		// Premultiply Alpha
		deviceled_image = HSM_GetPreMultipliedColorLinear(deviceled_image, HSM_DEVICELED_SOURCE_MATTE_TYPE, DEFAULT_SRGB_GAMMA);

		// HSV Adjustments
		deviceled_image.rgb = HSM_ApplyHSVAdjustment(deviceled_image.rgb, HSM_DEVICELED_HUE, HSM_DEVICELED_SATURATION, HSM_DEVICELED_BRIGHTNESS, HSM_DEVICELED_COLORIZE_ON, HSM_DEVICELED_GAMMA);

		if (HSM_STATIC_LAYERS_GAMMA != 1)
			deviceled_image = HSM_ApplyGamma(deviceled_image, HSM_STATIC_LAYERS_GAMMA);

		deviceled_image.rgb = ApplyAmbientImages(deviceled_image.rgb, 
													ambient_image.rgb,
													ambient2_image.rgb,
													HSM_DEVICELED_AMBIENT_LIGHTING_MULTIPLIER,
													HSM_DEVICELED_AMBIENT2_LIGHTING_MULTIPLIER,
													HSM_DEVICELED_APPLY_AMBIENT_IN_ADD_MODE,
													HSM_DEVICELED_BLEND_MODE,
													HSM_AMBIENT_LIGHTING_SWAP_IMAGE_MODE);
	}

	//----------------------------------------------------
	// LED Image
	//----------------------------------------------------
	vec4 led_image = vec4(0);
	if (HSM_LED_OPACITY > 0)
	{
		vec2 led_size = textureSize(LEDImage, 0);
		LED_CURVED_COORD = HSM_GetScaledCoord(VIEWPORT_COORD,
											VIEWPORT_UNSCALED_COORD,
											TEXTURE_ASPECT_MODE_EXPLICIT,
											led_size.x / led_size.y, 
											vec2(HSM_LED_POS_X, HSM_LED_POS_Y), 
											vec2(HSM_LED_SCALE * HSM_LED_SCALE_X, HSM_LED_SCALE),
											HSM_LED_FOLLOW_LAYER,
											HSM_LED_FOLLOW_MODE,
											HSM_LED_FOLLOW_FULL_USES_ZOOM,
											HSM_LED_SCALE_KEEP_ASPECT, 
											HSM_LED_FILL_MODE,
											HSM_LED_SPLIT_PRESERVE_CENTER,
											HSM_LED_SPLIT_REPEAT_WIDTH,
											true,
											LED_COORD,
											LED_SCALE);

		if (HSM_LED_OPACITY > 0 && led_size.y > 16)
		{
			led_image = HSM_GetMipmappedTexSample(LEDImage, LED_CURVED_COORD, LED_SCALE, HSM_LED_MIPMAPPING_BLEND_BIAS);

			// Premultiply Alpha
			led_image = HSM_GetPreMultipliedColorLinear(led_image, HSM_LED_SOURCE_MATTE_TYPE, DEFAULT_SRGB_GAMMA);

			// HSV Adjustments
			led_image.rgb = HSM_ApplyHSVAdjustment(led_image.rgb, HSM_LED_HUE, HSM_LED_SATURATION, HSM_LED_BRIGHTNESS, HSM_LED_COLORIZE_ON, HSM_LED_GAMMA);

			// STATIC GAMMA
			if (HSM_STATIC_LAYERS_GAMMA != 1)
				led_image = HSM_ApplyGamma(led_image, HSM_STATIC_LAYERS_GAMMA);

			led_image.rgb = ApplyAmbientImages(led_image.rgb, 
													ambient_image.rgb,
													ambient2_image.rgb,
													HSM_LED_AMBIENT_LIGHTING_MULTIPLIER,
													HSM_LED_AMBIENT2_LIGHTING_MULTIPLIER,
													HSM_LED_APPLY_AMBIENT_IN_ADD_MODE,
													HSM_LED_BLEND_MODE,
													HSM_AMBIENT_LIGHTING_SWAP_IMAGE_MODE);
		}
	}

	//----------------------------------------------------
	// Decal Image
	//----------------------------------------------------
	vec2 decal_size = textureSize(DecalImage, 0);
	DECAL_CURVED_COORD = HSM_GetScaledCoord(VIEWPORT_COORD,
											VIEWPORT_UNSCALED_COORD,
											TEXTURE_ASPECT_MODE_EXPLICIT,
											decal_size.x / decal_size.y,
											vec2(HSM_DECAL_POS_X, HSM_DECAL_POS_Y), 
											vec2(HSM_DECAL_SCALE * HSM_DECAL_SCALE_X, HSM_DECAL_SCALE),
											HSM_DECAL_FOLLOW_LAYER,
											HSM_DECAL_FOLLOW_MODE,
											HSM_DECAL_FOLLOW_FULL_USES_ZOOM,
											HSM_DECAL_SCALE_KEEP_ASPECT, 
											HSM_DECAL_FILL_MODE,
											HSM_DECAL_SPLIT_PRESERVE_CENTER,
											HSM_DECAL_SPLIT_REPEAT_WIDTH,
											true,
											DECAL_COORD,
											DECAL_SCALE);
	vec4 decal_image = vec4(0);
	if (HSM_DECAL_OPACITY > 0 && decal_size.y > 16)
	{
		decal_image = HSM_GetMipmappedTexSample(DecalImage, DECAL_CURVED_COORD, DECAL_SCALE, HSM_DECAL_MIPMAPPING_BLEND_BIAS);
		
		// Premultiply Alpha
		decal_image = HSM_GetPreMultipliedColorLinear(decal_image, HSM_DECAL_SOURCE_MATTE_TYPE, DEFAULT_SRGB_GAMMA);

		// HSV Adjustments
		decal_image.rgb = HSM_ApplyHSVAdjustment(decal_image.rgb, HSM_DECAL_HUE, HSM_DECAL_SATURATION, HSM_DECAL_BRIGHTNESS, HSM_DECAL_COLORIZE_ON, HSM_DECAL_GAMMA);

		// STATIC GAMMA
		if (HSM_STATIC_LAYERS_GAMMA != 1)
			decal_image = HSM_ApplyGamma(decal_image, HSM_STATIC_LAYERS_GAMMA);

		decal_image.rgb = ApplyAmbientImages(decal_image.rgb, 
												ambient_image.rgb,
												ambient2_image.rgb,
												HSM_DECAL_AMBIENT_LIGHTING_MULTIPLIER,
												HSM_DECAL_AMBIENT2_LIGHTING_MULTIPLIER,
												HSM_DECAL_APPLY_AMBIENT_IN_ADD_MODE,
												HSM_DECAL_BLEND_MODE,
												HSM_AMBIENT_LIGHTING_SWAP_IMAGE_MODE);
	}

//----------------------------------------------------
//  ADV Get Additional Layers and Composite 
//----------------------------------------------------
	vec2 top_size = textureSize(TopLayerImage, 0);
	TOP_IMAGE_CURVED_COORD = HSM_GetScaledCoord(VIEWPORT_COORD,
												VIEWPORT_UNSCALED_COORD,
												TEXTURE_ASPECT_MODE_EXPLICIT,
												top_size.x / top_size.y,
												vec2(HSM_TOP_POS_X, HSM_TOP_POS_Y), 
												vec2(HSM_TOP_SCALE * HSM_TOP_SCALE_X, HSM_TOP_SCALE),
												HSM_TOP_FOLLOW_LAYER,
												HSM_TOP_FOLLOW_MODE,
												HSM_TOP_FOLLOW_FULL_USES_ZOOM,
												HSM_TOP_SCALE_KEEP_ASPECT, 
												HSM_TOP_FILL_MODE,
												HSM_TOP_SPLIT_PRESERVE_CENTER,
												HSM_TOP_SPLIT_REPEAT_WIDTH,
												true,
												TOP_IMAGE_COORD,
												TOP_IMAGE_SCALE);

	if (HSM_TOP_MIRROR_WRAP == 1)
		TOP_IMAGE_CURVED_COORD = HSM_GetMirrorWrapCoord(TOP_IMAGE_CURVED_COORD);

	vec4 top_image = vec4(0);
	if (HSM_TOP_OPACITY > 0 && top_size.y > 16)
	{
		// Get the top image color and masking values if needed
		top_image = HSM_GetMipmappedTexSample(TopLayerImage, TOP_IMAGE_CURVED_COORD, TOP_IMAGE_SCALE, HSM_TOP_MIPMAPPING_BLEND_BIAS);

		// Premultiply Alpha
		top_image = HSM_GetPreMultipliedColorLinear(top_image, HSM_TOP_SOURCE_MATTE_TYPE, DEFAULT_SRGB_GAMMA);

		// HSV Adjustments
		top_image.rgb = HSM_ApplyHSVAdjustment(top_image.rgb, HSM_TOP_HUE, HSM_TOP_SATURATION, HSM_TOP_BRIGHTNESS, HSM_TOP_COLORIZE_ON, HSM_TOP_GAMMA);

		// STATIC GAMMA
		if (HSM_STATIC_LAYERS_GAMMA != 1)
			top_image = HSM_ApplyGamma(top_image, HSM_STATIC_LAYERS_GAMMA);

		top_image.rgb = ApplyAmbientImages(top_image.rgb, 
											ambient_image.rgb,
											ambient2_image.rgb,
											HSM_TOP_AMBIENT_LIGHTING_MULTIPLIER,
											HSM_TOP_AMBIENT2_LIGHTING_MULTIPLIER,
											HSM_TOP_APPLY_AMBIENT_IN_ADD_MODE,
											HSM_TOP_BLEND_MODE,
											HSM_AMBIENT_LIGHTING_SWAP_IMAGE_MODE);
	}

	//----------------------------------------------------
	// Cabinet Glass Image
	//----------------------------------------------------
	vec4 cab_glass_image = vec4(0);

	if (HSM_CAB_GLASS_OPACITY > 0)
	{
		vec2 cab_glass_size = textureSize(CabinetGlassImage, 0);
		CAB_GLASS_CURVED_COORD = HSM_GetScaledCoord(VIEWPORT_COORD,
													VIEWPORT_UNSCALED_COORD,
													TEXTURE_ASPECT_MODE_EXPLICIT,
													cab_glass_size.x / cab_glass_size.y,
													vec2(HSM_CAB_GLASS_POS_X, HSM_CAB_GLASS_POS_Y), 
													vec2(HSM_CAB_GLASS_SCALE * HSM_CAB_GLASS_SCALE_X, HSM_CAB_GLASS_SCALE),
													HSM_CAB_GLASS_FOLLOW_LAYER,
													HSM_CAB_GLASS_FOLLOW_MODE,
													HSM_CAB_GLASS_FOLLOW_FULL_USES_ZOOM,
													HSM_CAB_GLASS_SCALE_KEEP_ASPECT, 
													HSM_CAB_GLASS_FILL_MODE,
													HSM_CAB_GLASS_SPLIT_PRESERVE_CENTER,
													HSM_CAB_GLASS_SPLIT_REPEAT_WIDTH,
													true,
													CAB_GLASS_COORD,
													CAB_GLASS_SCALE);

		if (HSM_CAB_GLASS_OPACITY > 0 && cab_glass_size.y > 16)
		{
			// Sample Texture
			cab_glass_image = HSM_GetMipmappedTexSample(CabinetGlassImage, CAB_GLASS_CURVED_COORD, CAB_GLASS_SCALE, HSM_CAB_GLASS_MIPMAPPING_BLEND_BIAS);

			// Premultiply Alpha
			cab_glass_image = HSM_GetPreMultipliedColorLinear(cab_glass_image, HSM_CAB_GLASS_SOURCE_MATTE_TYPE, DEFAULT_SRGB_GAMMA);

			// HSV Adjustments
			cab_glass_image.rgb = HSM_ApplyHSVAdjustment(cab_glass_image.rgb, HSM_CAB_GLASS_HUE, HSM_CAB_GLASS_SATURATION, HSM_CAB_GLASS_BRIGHTNESS, HSM_CAB_GLASS_COLORIZE_ON, HSM_CAB_GLASS_GAMMA);

			// STATIC GAMMA
			if (HSM_STATIC_LAYERS_GAMMA != 1)
				cab_glass_image = HSM_ApplyGamma(cab_glass_image, HSM_STATIC_LAYERS_GAMMA);

			cab_glass_image.rgb = ApplyAmbientImages(cab_glass_image.rgb, 
														ambient_image.rgb,
														ambient2_image.rgb,
														HSM_CAB_GLASS_AMBIENT_LIGHTING_MULTIPLIER,
														HSM_CAB_GLASS_AMBIENT2_LIGHTING_MULTIPLIER,
														HSM_CAB_GLASS_APPLY_AMBIENT_IN_ADD_MODE,
														HSM_CAB_GLASS_BLEND_MODE,
														HSM_AMBIENT_LIGHTING_SWAP_IMAGE_MODE);
		}
	}


	//-----------------------------------------------------------------------------------------
	// CUTOUT MASK
	//-----------------------------------------------------------------------------------------
	CUTOUT_MASK = 1;

	vec2 temp_scale = vec2(0.5);
	vec2 temp_coord = vec2(0.5);

	vec2 cutout_base_scale_offset = vec2(0.5);

	if ( HSM_CUTOUT_FOLLOW_LAYER == FOLLOW_LAYER_VIEWPORT )
		cutout_base_scale_offset *= vec2(DEFAULT_UNCORRECTED_BEZEL_SCALE.y * TUBE_DIFFUSE_ASPECT / output_aspect, DEFAULT_BEZEL_SCALE.y);

	vec2 cutout_coord = HSM_GetScaledCoord(VIEWPORT_COORD,
											VIEWPORT_UNSCALED_COORD,
										  	HSM_CUTOUT_ASPECT_MODE,
											HSM_CUTOUT_EXPLICIT_ASPECT,
											vec2(HSM_CUTOUT_POS_X, HSM_CUTOUT_POS_Y),
											vec2(vec2(HSM_CUTOUT_SCALE * HSM_CUTOUT_SCALE_X, HSM_CUTOUT_SCALE) * cutout_base_scale_offset),
											HSM_CUTOUT_FOLLOW_LAYER,
											FOLLOW_MODE_SCALE_AND_POS,
											HSM_CUTOUT_FOLLOW_FULL_USES_ZOOM,
											HSM_CUTOUT_KEEP_ASPECT,
											FILL_MODE_STRETCH,
											0,
											0,
											false,
											temp_coord,
											temp_scale);
	
	CUTOUT_MASK = 1 - HSM_GetCornerMask(cutout_coord, TUBE_DIFFUSE_ASPECT, HSM_CUTOUT_CORNER_RADIUS, 0.8);

	//-----------------------------------------------------------------------------------------
	// Full Viewport Vignette
	//-----------------------------------------------------------------------------------------
	vec4 vignette_layer = vec4(0);
	if (HSM_VIEWPORT_VIGNETTE_OPACITY > 0)
	{
		vec2 vignette_coord = HSM_GetScaledCoord(VIEWPORT_COORD,
												VIEWPORT_UNSCALED_COORD,
												TEXTURE_ASPECT_MODE_VIEWPORT,
												1,
												vec2(HSM_VIEWPORT_VIGNETTE_POS_X, HSM_VIEWPORT_VIGNETTE_POS_Y), 
												vec2(vec2(HSM_VIEWPORT_VIGNETTE_SCALE * HSM_VIEWPORT_VIGNETTE_SCALE_X, HSM_VIEWPORT_VIGNETTE_SCALE)), 
												HSM_VIEWPORT_VIGNETTE_FOLLOW_LAYER,
												FOLLOW_MODE_SCALE_AND_POS,
												FOLLOW_LAYER_VIEWPORT,
												0,
												FILL_MODE_STRETCH,
												0,
												0,
												false,
												temp_coord,
												temp_scale);

		vignette_layer.a += 0.75 * HHLP_QuadraticBezier(1 - HSM_GetVignetteFactor(vignette_coord, HSM_VIEWPORT_VIGNETTE_OPACITY, 1), vec2(1, 0.5));
	}


	//-----------------------------------------------------------------------------------------
	// COMPOSITE ALL LAYERS
	//-----------------------------------------------------------------------------------------

	vec4 frag_color_linear = vec4(0);

	int start_layer = 0;
	int end_layer = int(MAX_LAYER_ORDER);

#ifdef LAYERS_OVER_CRT
	start_layer = int(HSM_CRT_LAYER_ORDER);
	end_layer = int(MAX_LAYER_ORDER);
#else
	start_layer = 0;
	end_layer = int(HSM_CRT_LAYER_ORDER - 1);
#endif

	OUTSIDE_TUBE_MASK_FOR_IMAGE = 1 - HSM_GetCornerMask(tube_curved_coord_ctr * 1.003 + 0.5, TUBE_DIFFUSE_ASPECT, HSM_FRM_OUTER_CORNER_RADIUS, 1);

	for(int i=start_layer; i <= end_layer; i++)
	{
		// BACKGROUND
		if (HSM_BG_LAYER_ORDER == i)
			frag_color_linear = BlendModeMaskLayerMix(frag_color_linear, 
													bg_image, 
													HSM_BG_BLEND_MODE, 
													HSM_BG_MASK_MODE, 
													HSM_BG_CUTOUT_MODE,
													HSM_BG_DUALSCREEN_VIS_MODE,
													HSM_BG_OPACITY);

		// VIGNETTE
		if (HSM_VIEWPORT_VIGNETTE_LAYER_ORDER == i)
			frag_color_linear = BlendModeMaskLayerMix(frag_color_linear, 
													vignette_layer, 
													BLEND_MODE_NORMAL, 
													HSM_VIEWPORT_VIGNETTE_MASK_MODE, 
													HSM_VIEWPORT_VIGNETTE_CUTOUT_MODE,
													SHOW_ON_DUALSCREEN_MODE_BOTH,
													HSM_VIEWPORT_VIGNETTE_OPACITY);
		// LED IMAGE
		if (HSM_LED_LAYER_ORDER == i && HSM_LED_OPACITY > 0)
				frag_color_linear = BlendModeMaskLayerMix(frag_color_linear, 
														led_image, 
														HSM_LED_BLEND_MODE, 
														HSM_LED_MASK_MODE, 
														HSM_LED_CUTOUT_MODE, 
														HSM_LED_DUALSCREEN_VIS_MODE,
														HSM_LED_OPACITY);

		// DEVICE IMAGE
		if (HSM_DEVICE_LAYER_ORDER == i && HSM_DEVICE_OPACITY > 0)
				frag_color_linear = BlendModeMaskLayerMix(frag_color_linear, 
														device_image, 
														HSM_DEVICE_BLEND_MODE, 
														HSM_DEVICE_MASK_MODE, 
														HSM_DEVICE_CUTOUT_MODE, 
														HSM_DEVICE_DUALSCREEN_VIS_MODE,
														HSM_DEVICE_OPACITY);

		// DEVICELED IMAGE
		if (HSM_DEVICELED_LAYER_ORDER == i && HSM_DEVICELED_OPACITY > 0)
				frag_color_linear = BlendModeMaskLayerMix(frag_color_linear, 
														deviceled_image, 
														HSM_DEVICELED_BLEND_MODE, 
														HSM_DEVICELED_MASK_MODE, 
														HSM_DEVICELED_CUTOUT_MODE, 
														HSM_DEVICELED_DUALSCREEN_VIS_MODE,
														HSM_DEVICELED_OPACITY);

		// DECAL IMAGE
		if (HSM_DECAL_LAYER_ORDER == i && HSM_DECAL_OPACITY > 0)
				frag_color_linear = BlendModeMaskLayerMix(frag_color_linear, 
														decal_image, 
														HSM_DECAL_BLEND_MODE, 
														HSM_DECAL_MASK_MODE, 
														HSM_DECAL_CUTOUT_MODE, 
														HSM_DECAL_DUALSCREEN_VIS_MODE,
														HSM_DECAL_OPACITY);

		// CABINET GLASS
		if (HSM_CAB_GLASS_LAYER_ORDER == i && HSM_CAB_GLASS_OPACITY > 0)
				frag_color_linear = BlendModeMaskLayerMix(frag_color_linear, 
														cab_glass_image, 
														HSM_CAB_GLASS_BLEND_MODE, 
														HSM_CAB_GLASS_MASK_MODE, 
														HSM_CAB_GLASS_CUTOUT_MODE, 
														HSM_CAB_GLASS_DUALSCREEN_VIS_MODE,
														HSM_CAB_GLASS_OPACITY);


		// Top Layer
		if (HSM_TOP_LAYER_ORDER == i && HSM_TOP_OPACITY > 0)
				frag_color_linear = BlendModeMaskLayerMix(frag_color_linear, 
														top_image, 
														HSM_TOP_BLEND_MODE, 
														HSM_TOP_MASK_MODE, 
														HSM_TOP_CUTOUT_MODE, 
														HSM_TOP_DUALSCREEN_VIS_MODE,
														HSM_TOP_OPACITY);
	}

#ifdef LAYERS_UNDER_CRT

	// Add background behind tube
	frag_color_linear = HSM_PreMultAlphaBlend(frag_color_linear, tube_bg_layer);
	// GENERATED BEZEL LAYER
	if (HSM_BZL_OPACITY > 0 || HSM_FRM_OPACITY > 0)
		frag_color_linear = BlendModeMaskLayerMix(frag_color_linear, 
												bezel_layer, 
												HSM_BZL_BLEND_MODE, 
												MASK_MODE_ALL, 
												0, 
												SHOW_ON_DUALSCREEN_MODE_BOTH,
												1);
#endif

	//-----------------------------------------------------------------------------------------
	// MASK DEBUG DISPLAY
	//-----------------------------------------------------------------------------------------
	// Show a red overlay on the screen showing the mask for each mask mode
	if (HSM_LAYERING_DEBUG_MASK_MODE != -1)
	{
		float debug_mask = 1;
		if (HSM_LAYERING_DEBUG_MASK_MODE == -2)
			debug_mask = CUTOUT_MASK;
		else
			debug_mask = GetMask(HSM_LAYERING_DEBUG_MASK_MODE);

		frag_color_linear = HSM_PreMultAlphaBlend(frag_color_linear, vec4(1, 0, 0, 1) * 0.15 * debug_mask);
		frag_color_linear = HSM_PreMultAlphaBlend(frag_color_linear, vec4(0.05, 0.05, 0.05, 1) * 0.15 * (1 - debug_mask));
		frag_color_linear = clamp(frag_color_linear, 0, 1);
	}

	FragColor = frag_color_linear;

	// If we have calculated an image then set -1 as a flag to show that we have
	if (UNFLIPPED_VIEWPORT_COORD.x < (2 / global.OutputSize.x) && UNFLIPPED_VIEWPORT_COORD.y < (2 / global.OutputSize.y))
		FragColor.a = -1;

	return;
}
