#version 450

/* This pass:
 * Composes the previous passes
 * Does masks, spot, bezel, vignette, background image (and so on)
 */

#define DEBUG_TEXTURE in_glow_pass

// #define DEBUG_PRINT_VALUE
// #define PRINTED_VALUE global.FinalViewportSize.x
// #define PRINTED_VALUE texture(DEBUG_TEXTURE, vec2(0.5)).a
// #define PRINTED_VALUE (1000.0/(global.FrameTimeDelta/1000.0)) 
    
// #define DEBUG_QUAD_SPLIT
// #define DEBUG_DUAL_SPLIT_X
// #define DEBUG_DUAL_SPLIT_Y
// #define DEBUG_DUAL_SPLIT_Y_GAMMA
// #define DEBUG_DUAL_SPLIT_MIRROR_X
// #define DEBUG_DUAL_SPLIT_MIRROR_X_GAMMA
// #define DEBUG_SHOW_CLIP


#include "config.inc"

#pragma stage vertex
layout(location = 0) in vec4 Position;
layout(location = 1) in vec2 TexCoord;
layout(location = 0) noperspective out vec2 vTexCoord;
layout(location = 1) noperspective out vec2 vOutputCoord;
layout(location = 2) noperspective out vec2 vBg_img_coords;
layout(location = 3) flat out float vIsRotated;
layout(location = 4) flat out float vScanlinePeriod_multiplier;
layout(location = 5) flat out float vInterlace_offset;
layout(location = 6) flat out float vIn_aspect;
layout(location = 7) flat out float vDo_Tate;
layout(location = 8) flat out vec3 vDotMat_Grid_Color;
layout(location = 9) flat out vec4 vPG_offsets_and_size;
layout(location = 10) noperspective out float vInteger_fragment_x;
layout(location = 11) noperspective out vec2 vPG_freq_base_screen;
layout(location = 12) flat out vec2 vPG_OriginalSize_tated_mul_pi;
layout(location = 13) flat out float vDotMat_Grid_Sharpness;
layout(location = 14) flat out float vPG_mask_height;
layout(location = 15) flat out float vDeltaRenderOk;
layout(location = 16) flat out float vDo_pixelgrid_h;
layout(location = 17) noperspective out float vFake_scanline_period;
layout(location = 18) flat out float vPG_y_shift;
layout(location = 19) noperspective out vec2 vUnrotated_Output_coords;
#ifdef DO_ADPT_STROBE_PLUGIN
    layout(location = 20) flat out float vAdaptive_strobe_oscillator;
#endif
    
#ifdef DEBUG_PRINT_VALUE
    layout(location = 31) flat out float vfprintvalue;
#endif

// I_am_a_placeholder_keep_reading;
//..to remind you that location = 31 is the last accepted by vulkan
//or Retroarch crashes or nvidia starts to give Xids.
//unsure if the issue is with retroarch or with vulkan specs.
//If in short, consider to pack more datas into vec2,3,4.
//But bear in mind that exceding the limit leads to
//slowdowns.

#include "includes/functions.include.slang"

#define PI_15 4.71238898038469 //pi*1.5

vec2 get_unrotated_output_coords(vec2 in_coords) {
	#ifdef _HAS_ROTATION_UNIFORM
        uint Rotation=params.Rotation;
	#else
		uint Rotation=uint(is_rotated_old());
	#endif
    //0
    //nothing to do.
    
    //90
    if (Rotation == 1) {
        in_coords.xy = in_coords.yx;
        in_coords.y = 1 - in_coords.y;
    } else
    
    //180
    if (Rotation == 2) {
        in_coords = 1 - in_coords;
    } else
    
    //270
    if (Rotation == 3) {
        in_coords.xy = in_coords.yx;      
        in_coords.x = 1 - in_coords.x;
    } 
    
    return in_coords;
}

vec2 get_bg_image_coords(vec2 in_vOutputCoord) {
    vec2 bg_img_coords = in_vOutputCoord;

	#ifdef _HAS_ROTATION_UNIFORM
        uint Rotation=params.Rotation;
	#else
		uint Rotation=uint(is_rotated_old());
	#endif
    
    
    //   0 = auto
    //   1, -1 = identity
    // >+1 = manual rotation for straight games
    // <-1 = manual rotation for rotated games

    //user selected auto rotation handling, so query params.Rotation.
    if (BG_IMAGE_ROTATION == 0.0 ) {
        
        //0
        //nothing to do.
        
        //90
        if (Rotation == 1) {
            bg_img_coords.xy = bg_img_coords.yx;
            bg_img_coords.y = 1 - bg_img_coords.y;
        } else
        
        //180
        if (Rotation == 2) {
            bg_img_coords = 1 - bg_img_coords;
        } else
        
        //270
        if (Rotation == 3) {
            bg_img_coords.xy = bg_img_coords.yx;      
            bg_img_coords.x = 1 - bg_img_coords.x;
        } 
        
        bg_img_coords += vec2( BG_IMAGE_OFFX,BG_IMAGE_OFFY);
        bg_img_coords=zoom(bg_img_coords, BG_IMAGE_ZOOM);
        return bg_img_coords;
    }


    //User forced a rotation, handle manually.
    
    //User selected a negative value (force rotation),
    //flip coords.
    //This is needed for correct aspect handling on rotated games.
    if (BG_IMAGE_ROTATION < 0.0) {
        bg_img_coords.xy = bg_img_coords.yx;
        bg_img_coords.y = 1 - bg_img_coords.y;
    }

    if (abs(BG_IMAGE_ROTATION) == 1.0) // identity
        bg_img_coords.xy = bg_img_coords.xy;

    else if (abs(BG_IMAGE_ROTATION) == 2.0) // +180
       bg_img_coords.xy =   vec2(1-bg_img_coords.x, 1 - bg_img_coords.y);

    else if (abs(BG_IMAGE_ROTATION) == 3.0) // 0 mirror x
       bg_img_coords.xy =   vec2( 1 - bg_img_coords.x, bg_img_coords.y);

    else if (abs(BG_IMAGE_ROTATION) == 4.0) // 0 mirror y
       bg_img_coords.xy =   vec2( bg_img_coords.x, 1 - bg_img_coords.y);

    else if (abs(BG_IMAGE_ROTATION) == 5.0) // +90
        bg_img_coords.xy = vec2(bg_img_coords.y, 1- bg_img_coords.x);

    else if (abs(BG_IMAGE_ROTATION) == 6.0) // +270
       bg_img_coords.xy =   vec2( 1- bg_img_coords.y, bg_img_coords.x);

    else if (abs(BG_IMAGE_ROTATION) == 7.0) // 90 mirror x
       bg_img_coords.xy =   vec2(bg_img_coords.y, bg_img_coords.x);

    else if (abs(BG_IMAGE_ROTATION) == 8.0) // +90 mirror y
        bg_img_coords.xy = vec2(1-bg_img_coords.y, 1- bg_img_coords.x);

    bg_img_coords += vec2( BG_IMAGE_OFFX,BG_IMAGE_OFFY);
    bg_img_coords=zoom(bg_img_coords, BG_IMAGE_ZOOM);
    return bg_img_coords;
}

void main() {
    gl_Position = global.MVP * Position;
    bool bIsRotated = is_rotated();
    vIsRotated = float(bIsRotated);

    vDo_Tate = need_tate(bIsRotated);

    vIn_aspect = get_in_aspect();

    #ifdef ANTIBURN_COMPLETE
        vec2 antiburn_TexCoord = antiburn_screen(TexCoord);
    #else
        vec2 antiburn_TexCoord = TexCoord;
    #endif

    //Calculate vTexcoord as fractional or integer scaled?
        bool bNeed_NO_integer_scale = need_NO_integer_scale();
        if ( bNeed_NO_integer_scale )
            vTexCoord = get_scaled_coords_aspect(antiburn_TexCoord,global.FinalViewportSize, vIn_aspect, bIsRotated);
        else
            vTexCoord = integer_scale(antiburn_TexCoord, vIn_aspect, bIsRotated, vDo_Tate, GAME_GEOM_INT_SCALE-1.0 )
            + vec2(0,params.OriginalSize.w/4.0); //<-- last offset aligns the sampling point and the scanline
    

        
    //Global zoom/shift:
    if (DO_GLOBAL_SHZO == 1.0) {
        if (bNeed_NO_integer_scale) {
            //zoom + shift
            vTexCoord = zoom(vTexCoord + vec2(-GLOBAL_OFFX, -GLOBAL_OFFY), GLOBAL_ZOOM );
        } else {
            //Just shift with integer sFcaling
            vTexCoord += vec2(-GLOBAL_OFFX, -GLOBAL_OFFY);
        }
    }

    vOutputCoord = antiburn_TexCoord ;

    vBg_img_coords = get_bg_image_coords(vOutputCoord);
    vUnrotated_Output_coords = get_unrotated_output_coords(vOutputCoord);
    
    //Help scanline/pixelgrid code too:
        bool bIs_Interlaced = is_interlaced();

        //Scanline period:
            vScanlinePeriod_multiplier = 1.0; //Base 1x period
            
            #ifdef SCANLINES_FREQUENCY_MULTIPLIER_OVERRIDE
                vScanlinePeriod_multiplier *= SCANLINES_FREQUENCY_MULTIPLIER_OVERRIDE;
            #endif
            
            vInterlace_offset = 0.0; //No interlace

            if (scanlines_will_alternate(bIs_Interlaced))  {
                //Halve the scanline period, since they will interlace:
                vScanlinePeriod_multiplier = 0.5;
                vInterlace_offset = PI_15*0.5;
                //Alternate scanlines on even/odd frames
                if (frame_is_even())
                    vInterlace_offset += PI_15;
            }

    //Pixelgrid: get mask type; .rgb contains layout, .a contains the size.
        vPG_offsets_and_size = PG_get_hmask_preset() ;

       //scale offsets to be used by sin:
        vPG_offsets_and_size.rgb *= (pi / vPG_offsets_and_size.a);
        
    //Pixelgrid: Calc base freqs
            vec2 PG_OutputCoord_tated;
            vec2 PG_OutputSize_tated;
        //Tate ?
            if (vDo_Tate == 0.0) { 
                PG_OutputSize_tated   = params.OutputSize.xy;
                PG_OutputCoord_tated  = vOutputCoord.xy + vec2(0.00001);
                vPG_OriginalSize_tated_mul_pi = params.OriginalSize.xy * pi;
            } else {
                PG_OutputSize_tated   = params.OutputSize.yx;
                PG_OutputCoord_tated  = vOutputCoord.yx;
                vPG_OriginalSize_tated_mul_pi = params.OriginalSize.yx * pi;
            }

        vInteger_fragment_x = PG_OutputCoord_tated.x * PG_OutputSize_tated.x; //will be floored in fragment shader.

        //the following unfloored coords are needed by sin/cos later to understand if we are on an even or odd mask
            float rgbw_size;
            if (PIXELGRID_OFFSET_CORE * PIXELGRID_OFFSET_CORE_W > 0.0)
                rgbw_size = 1/PIXELGRID_OFFSET_CORE_W;
            else
                rgbw_size = 1/vPG_offsets_and_size.a;
    
            vPG_freq_base_screen = pi * vec2(rgbw_size, 0.5) * (PG_OutputCoord_tated * PG_OutputSize_tated);

    //pixelgrid: doublescan?
                if (need_doublescan() )
                    vPG_OriginalSize_tated_mul_pi.y *= 2.0;

    //Pixelgrid: adapt period multiplier
        float PG_period_multiplier_x = 1/PIXELGRID_MUL_X;
        if (PG_period_multiplier_x < 0.0) PG_period_multiplier_x = 1/-PG_period_multiplier_x;

        //if using core coords for rgb mask and tvl is not 0.0, compute multiplier to match tvl.
        if (PIXELGRID_SIZE_W == 0.0 && PIXELGRID_TVL_X > 0.0)
            PG_period_multiplier_x = PIXELGRID_TVL_X / params.OriginalSize.x;
            
        //apply it:
            vPG_OriginalSize_tated_mul_pi.x *=  PG_period_multiplier_x ; //for core coords
            vPG_freq_base_screen.x *= PG_period_multiplier_x ; //for screen coords

    //Pixelgrid Y mask: adapt mask height
        vPG_mask_height = PIXELGRID_Y_MASK_HEIGHT;
        if (vPG_mask_height < 0.0) vPG_mask_height = 1/-vPG_mask_height;

    //Pixelgrid Y mask: Select an y shift that gives the sharpest lines
        bool y_mask_align_to_screen_lines = (PIXELGRID_Y_MASK_SHIFT < 0.0)      && //Engage when param is < 0.0 (auto) and
                                            (PIXELGRID_Y_MASK_COORDS == 1.0)    && //only when when using screen coords and
                                            (fract(1.0/vPG_mask_height) == 0.0);   //only when the multiplier is integer
    
        if (y_mask_align_to_screen_lines ) {
            vPG_y_shift = vPG_mask_height * (pi / 4.0);
        } else {
            vPG_y_shift = PIXELGRID_Y_MASK_SHIFT * pi ;
        }
        
        
    //Pixelgrid: do horizontal if enabled, but skip it if user doesn't want it on interlaced content.
        vDo_pixelgrid_h = float( pixelgrid_h_needed() );


    //Dot matrix: measure the final picture size to choose a right sharpness level
        vDotMat_Grid_Sharpness = DOT_M_G_SHARP;

        if (DOT_M_G_SHARP == 0.0)
            vDotMat_Grid_Sharpness = get_dotmat_auto_sharpness(vDo_Tate,bNeed_NO_integer_scale,bIsRotated,vIn_aspect);

    //Dot matrix: Calc dotmat grid color
        vDotMat_Grid_Color = vec3(DOT_M_G_BRT);
        if (DO_DOT_MATRIX + DO_CCORRECTION > 1.1) {
            vDotMat_Grid_Color = color_tools(vec3(DOT_M_G_BRT), kelvin2rgb(TEMPERATURE));
            //Since we modify grid brightness via a specific use parameter, explicitely multiply it by that:
                vDotMat_Grid_Color *= DOT_M_G_BRT ;
        }

/*
    if (mod(params.FrameCount,100) > 80)
        vfprintvalue = vDotMat_Grid_Sharpness*10.0;
    else
        vfprintvalue = get_zooms_modifier(vDo_Tate, bNeed_NO_integer_scale, bIsRotated, vIn_aspect, global.FinalViewportSize.xy).y*10.0;
        }
*/
    vDeltaRenderOk = is_deltarender_allowed(bIs_Interlaced); //carries DR mandatory conditions, including forced refreshed frames
        
    // Fake or Faker integer scanlines:
    // We are going to compute an amount of lines that is integer multiple of the screen lines and that
    // is near to the real input/core lines
    vFake_scanline_period = 0.0;
    
    
    // For automatic Fake scanline managment, we compute a treshold under which
    // fake integer scanlines will be used.
    // (Logic is skipped if integer scaling is requested.)
    bool very_lowres_y = fn_very_lowres_y(params.OutputSize.xy, params.OriginalSize.xyzw, vDo_Tate, bIsRotated, !bNeed_NO_integer_scale, vIn_aspect, vScanlinePeriod_multiplier);
                                      
    if ( fake_scanlines_needed(very_lowres_y, bIs_Interlaced) ) {
        vec4 out_size = params.OutputSize;
        vec4 ori_size = params.OriginalSize;
        vec2 out_coord = vOutputCoord;
        vec2 zoom_modifier = get_zooms_modifier(vDo_Tate, bNeed_NO_integer_scale, bIsRotated, vIn_aspect, global.FinalViewportSize.xy);
        if (vDo_Tate == 1.0) {
            out_size.xy = out_size.yx;
            ori_size.xyzw = ori_size.yxwz;
            out_coord.xy = out_coord.yx;
            zoom_modifier.xy = zoom_modifier.yx;
        }

        // Modify the y line size ?
            float target_line_size = ori_size.w ;
            if ( need_doublescan() ) {
                target_line_size /= 2.0; //halve line size for doublescan
            } else if (PIXELGRID_INTR_DISABLE_Y == -2.0 && bIs_Interlaced) {
                target_line_size /= 1.5; //reduce line size for fakeR scanlines on interlaced screens.
            }
            
        // compute a rounded number of lines that integer fits the screen
            float rounded_denominator = round( zoom_modifier.y * params.OriginalSize.y * target_line_size);
                  rounded_denominator = max(rounded_denominator, 2.0); // avoid division by 0  and ensure (, 2.0)
                                                                       // scanline gaps are drawn even on tiny output sizes.
            
            float fake_scanlines_numlines = out_size.y / rounded_denominator;
            
        //Compute the final fake scanline period
            vFake_scanline_period = pi * fake_scanlines_numlines * out_coord.y;
                
        //This somehow aligns better the scanline to the screen line:
            vInterlace_offset+=1.0;
    }
    
    //Fragment shader will use that just one time that way. => precalc.
    vPG_offsets_and_size.a = pi/vPG_offsets_and_size.a;

    #ifdef DO_ADPT_STROBE_PLUGIN
        float iFrameMod = 0.0;
        //+1 should take care of oscillator invertion to avoid voltage accumuation in IPS panels (?)
        if (ADPT_LCD_RETENTION_FIX > 0.0)
            iFrameMod = mod(frame_subframe(), global.TotalSubFrames * ADPT_LCD_RETENTION_FIX + 1) ;
        else
            iFrameMod = mod(frame_subframe(), global.TotalSubFrames * 300.0); //mod needed to avoid drifting sin precision errors
        
        vAdaptive_strobe_oscillator = cos(iFrameMod * pi);
    #endif

}




#pragma stage fragment
layout(location = 0) noperspective in vec2 vTexCoord;
layout(location = 1) noperspective in vec2 vOutputCoord;
layout(location = 2) noperspective in vec2 vBg_img_coords;
layout(location = 3) flat in float vIsRotated;
layout(location = 4) flat in float vScanlinePeriod_multiplier;
layout(location = 5) flat in float vInterlace_offset;
layout(location = 6) flat in float vIn_aspect;
layout(location = 7) flat in float vDo_Tate;
layout(location = 8) flat in vec3 vDotMat_Grid_Color;
layout(location = 9) flat in vec4 vPG_offsets_and_size;
layout(location = 10) noperspective in float vInteger_fragment_x;
layout(location = 11) noperspective in vec2 vPG_freq_base_screen;
layout(location = 12) flat in vec2 vPG_OriginalSize_tated_mul_pi;
layout(location = 13) flat in float vDotMat_Grid_Sharpness;
layout(location = 14) flat in float vPG_mask_height;
layout(location = 15) flat in float vDeltaRenderOk;
layout(location = 16) flat in float vDo_pixelgrid_h;
layout(location = 17) noperspective in float vFake_scanline_period;
layout(location = 18) flat in float vPG_y_shift;
layout(location = 19) noperspective in vec2 vUnrotated_Output_coords;
#ifdef DO_ADPT_STROBE_PLUGIN
    layout(location = 20) flat in float vAdaptive_strobe_oscillator;
#endif


#ifdef DEBUG_PRINT_VALUE
    layout(location = 31) flat in float vfprintvalue;
#endif

layout(location = 0)     out vec4 FragColor;


layout(set = 0, binding = 1)  uniform sampler2D backdrop;
layout(set = 0, binding = 2)  uniform sampler2D bloom_pass_final;
layout(set = 0, binding = 3)  uniform sampler2D reflected_blurred_pass;
layout(set = 0, binding = 4)  uniform sampler2D ambi_temporal_pass;
layout(set = 0, binding = 5)  uniform sampler2D avglum_pass;
layout(set = 0, binding = 6)  uniform sampler2D monitor_body_straight;
layout(set = 0, binding = 7)  uniform sampler2D monitor_body_curved;
layout(set = 0, binding = 8)  uniform sampler2D bg_under;
layout(set = 0, binding = 9)  uniform sampler2D bg_over;
layout(set = 0, binding = 10) uniform sampler2D in_glow_pass_x;
layout(set = 0, binding = 11) uniform sampler2D in_glow_pass;
layout(set = 0, binding = 12) uniform sampler2D halo_pass;
layout(set = 0, binding = 13) uniform sampler2D helper_pass;

#ifndef D3D_WORKAROUND
    layout(set = 0, binding = 15) uniform sampler2D final_passFeedback;
#endif



#define RECT01 vec4(0.0, 0.0, 1.0, 1.0)
#define HALF_PI 1.5707963267949
#define QUARTER_PI 0.785398163397448

#include "includes/functions.include.slang"


vec2 vOutputCoord_adapted;
vec2 get_vOutputCoord_adapted() {
    return vOutputCoord_adapted;
}

vec2 get_scaled_coords_for_bezel(vec2 co, float isrotated) {

    co = zoomout_coords(co, -BEZEL_FRAME_ZOOM); //FIXME COULD BE FASTER.
    //co.y = zoom1D(co.y, BEZEL_ASPECT_CORRECTION );
    //co = zoomxy(co, vec2(1+SERVICE1*0.001, 1-SERVICE1*0.001));
    if (DO_TILT == 1.0) {
        return tilt_bezel( co, isrotated, TILT_X);
    } else {
        return co;
    }
    
}

vec3 fn_pixel_nightify(vec3 color_in, float strength, vec3 ambilight) {
       
    if (strength == 0.0) return color_in;

    //color_in = clamp(color_in, 0.0, 1.0); //useless because bezel_color already clamps and fg/bg images are in 0-1 range.

    vec3 color_hsv_in = rgb2hsv(color_in);

    //If there is an ambientlight, then the strangth has to be lowered:
    vec3 strength_vec3 = vec3(strength - ambilight);
    strength_vec3 = clamp(strength_vec3, 0.0,1.0); //clamp under 1.0 should be useless, max(x,0.0) should suffice.
                                                   //but cycles somehow increases, so keep clamp.
    
    vec3 color_hsv_min = color_hsv_in;
    color_hsv_min.yz = scale_to_range_vec2(color_hsv_min.yz, -0.1, 0.1);

    vec3 color_rgb_min = hsv2rgb(color_hsv_min);


    vec3 pixel_out = mix(color_in, color_rgb_min, strength_vec3);

   //It could make sense to higher the contrast when ambient light hits the picture,
    //Maybe this could be used independently on the nightify feature?
    /*vec3 new_contrast = ambilight;
    vec3 new_brightness = ambilight*0.0;
    pixel_out = scale_to_range_vec3(pixel_out, -new_contrast, 1+new_contrast) + new_brightness;*/

    return pixel_out;
}


vec3 morph_shape_full_vec3(vec3 shape, vec3 l, float steep, float gamma ) {
    vec3 l_pow = pow(l,vec3(gamma));
    vec3 l2 = min(l_pow * 16, steep); //min faster than clamp
    vec3 s1 = pow(shape, inversesqrt(l2));
    //vec3 s2 = (1-s1) * l_pow;
        //==  lpow - lpow * s1;
        //== -lpow * s1 + lpow;
    vec3 s2 = fma(-l_pow, s1,  l_pow); //ASM PROOF, faster.
    return (s2/(steep)) + s1;
}


vec3 morph_shape_full_no_steepness_vec3(vec3 shape, vec3 l, float gamma) {
    vec3 l_pow = pow(l,vec3(gamma));
    vec3 l2 = clamp(l_pow * 16, 0,1); //clamp faster than min.
    vec3 s1 = pow(shape, inversesqrt(l2));
    vec3 s2 = (1-s1) * l_pow;
        // == 1*l_pow  - s1*l_pow
        // == -l_pow*s1 + l_pow
    //vec3 s2 = fma(s1, -l_pow, l_pow); //ASM PROOF, slower.
    return s2 + s1;
}


vec3 downsample( sampler2D tex, vec2 uv, vec4 sourcesize,  float sharpness_add ) {
    vec2 sharpness = vec2(1.0)+sharpness_add;
    vec2 scale = sourcesize.xy * sharpness;

    vec2 iuv = floor(uv * scale);
    vec2 bottomleft  =    iuv;
    vec2 bottomright =  ( iuv + vec2(1.0,0.0)) / scale;
    vec2 topleft     =  ( iuv + vec2(0.0,1.0)) / scale;
    vec2 topright    =  ( iuv + vec2(1.0,1.0)) / scale;

    bottomleft /= scale;

    vec2 dist = (uv - bottomleft)*scale;

    vec3 bl = texture(tex, bottomleft).xyz   ;
    vec3 br = texture(tex, bottomright).xyz  ;
    vec3 tl = texture(tex, topleft).xyz      ;
    vec3 tr = texture(tex, topright).xyz     ;

    vec3 tA = mix( bl, br, dist.x );
    vec3 tB = mix( tl, tr, dist.x );
    return mix( tA, tB, dist.y );

}

vec3 downsample_x( sampler2D tex, vec2 uv, vec4 sourcesize, float sharpness_add ) {

    float sharpness = 1.0+sharpness_add;

    vec2 scale = vec2(sourcesize.x * sharpness, 1.0);

    vec2 iuv = vec2( floor(uv.x * scale.x), uv.y);

    vec2 bottomleft  =    iuv;
    vec2 bottomright =  ( iuv + vec2(1.0,0.0)) / scale;

    bottomleft /= scale;

    vec2 dist = (uv - bottomleft)*scale;

    vec3 bl = texture(tex, bottomleft).xyz   ;
    vec3 br = texture(tex, bottomright).xyz  ;

    return mix( bl, br, dist.x );

}


vec4 fn_pixel_grid(vec2 in_coords, vec3 pixel_in, float min_inLum, float max_inLum) {
    //^ 15fps ^

    //Tate ? (other outputsize,originalsize and outputcoords "tated" in vertex shader.)
        in_coords.xy = mix_step(in_coords.xy, in_coords.yx, vDo_Tate);
       //in_coords = in_coords - global.flick_and_noise_passSize.zw*0.5; //<-- needed if for whatever reason you want to draw scanlines and vgaps in first_pass

        vec2 freq_base_core = in_coords * vPG_OriginalSize_tated_mul_pi;

        float decon_y_strength = DO_SHIFT_RGB * NEW_SCALEMOD_Y;
        if (vFake_scanline_period > 0.0) {
            freq_base_core.y = vFake_scanline_period;
            decon_y_strength = 0.0;
        }

    //Screen coords:
    //Floor x screen coords when using non 1.0x sizes.
    //y screen coords appear to work just fine even if we don't floor them,
        vec2 freq_base_screen;
        #define PG_freq_base_screen_x vPG_offsets_and_size.a
        //if (PIXELGRID_MUL_X == 1.0) {
        //    freq_base_screen = vec2( (PG_freq_base_screen_x) * floor(vInteger_fragment_x), vPG_freq_base_screen_unfloored.y);
        //} else {
            freq_base_screen = vPG_freq_base_screen;
        //}

    //Switch frequency between core and screen and apply asked multipliers
        vec2 freq_base = freq_base_core;
        //vec2 freq_base_unfloored = freq_base_core;
        vec2 freq_base_unfloored = freq_base_core;
        if (PIXELGRID_SIZE_W == 1.0) {
            freq_base.x = freq_base_screen.x;
            freq_base_unfloored.x = vPG_freq_base_screen.x;
        }
    
    // Shadowmask
    // Works by staggering odd rows by 1.5X triad
    // Pay attention to phosphor width; if too low may, vanify shadowmask.
    vec3 PG_offsets = vPG_offsets_and_size.rgb + //base offsets staggered (+)
                float( PIXELGRID_DO_SHADOWMASK *                                      // user wants shadowmask
                       sin(freq_base_screen.y * 2.0 / PIXELGRID_SHADOWMASK_H ) > 0.0  // sin > 0.0 means the row is even
                      ) * PIXELGRID_SHADOWMASK_SHIFT ;                                // stagger by 1.5

        
    vec3 freq_rgb = freq_base.xxx - PG_offsets;

    //The following is needed by scanlines and vertical mask
    //sin() tell use if the current cell position is odd or even:

        //FIXME: is_even should be a single phosphor property.
        //If i manage to find a way to do it, i could use
        //decon_stagger.r,g,b to stagger single phosphors.
        //And that way, i could blend staggered triads together
        //without "cuts" between them.
        //FIXME FIXME: Unsure if the previous statement is still valid.

        float sin_check_offset = sin(freq_base_unfloored.x);
        float is_even = step(sin_check_offset, 0.0);

    //Scanlines and fake slotmask =========================================================================
        vec3 rgb_h = vec3(1.0);
        vec3 dedot_mix = vec3(0.0);
        
        vec3 phosphor_height = vec3(1.0);
        if (vDo_pixelgrid_h > 0.5) {
            //Scanlines: compute each phosphor height according to input luminance
                phosphor_height = map_range(pixel_in.rgb, min_inLum, max_inLum, PIXELGRID_MIN_H, PIXELGRID_MAX_H);
                
            //We can offset trias to emulate fake core level slotmask by applying the optional offset to emulate the slotmask on even cells
                float fakeslot_stagger_offset =  is_even * PIXELGRID_OFFSET_CORE * pi;
            //get 3 staggers for y deconvergence.
                 vec3 decon_stagger = vec3(DECON_RY,DECON_GY,DECON_BY) * decon_y_strength;
                 //vec3 decon_stagger = vec3(DECON_RY,DECON_GY,DECON_BY) * texture(helper_pass, in_coords).x;
 
            #ifdef HQ_DECON
                //Sync decon stagger to deconvergence set in shift_and_bleed pass.
                float dist_from_edge = distance_from_edge_linear(in_coords, EDGE_DEFOCUS_AREA_INV);
                float offset_multiplier = mix(EDGE_DEFOCUS, 0.0, dist_from_edge);
                decon_stagger = (vec3(DECON_RY,DECON_GY,DECON_BY) * offset_multiplier) * decon_y_strength;
            #endif
          
            //Compute final sin mask with staggering
            vec3 rgb_h_sin = sin( (freq_base_core.y * vScanlinePeriod_multiplier) + fakeslot_stagger_offset - decon_stagger + vInterlace_offset );

            //make it positve with doubled frequency:
                rgb_h_sin = (rgb_h_sin * rgb_h_sin);

            //Compute dedot mix here for use in h mask and vmask2 later
                //dedot_mix = vec3(  PIXELGRID_H_DEDOT - PIXELGRID_H_DEDOT * rgb_h_sin.x);
                dedot_mix = vec3( fma( -PIXELGRID_H_DEDOT, rgb_h_sin.x, PIXELGRID_H_DEDOT)); //ASM PROOF: SAME
                dedot_mix = dedot_mix *(pixel_in.rgb/max_inLum);
                                
            //Finally get 3 sines out of the previous one by applying height modifiers
                phosphor_height = pow(phosphor_height, vec3(PIXELGRID_GAMMA_H));
                rgb_h = morph_shape_full_vec3(rgb_h_sin, phosphor_height, PIXELGRID_NO_INTERBLEED_H, 1);

                rgb_h = mix(vec3(1.0), rgb_h, DO_PIXELGRID_H);
                
                rgb_h = clamp(rgb_h, 0.0,1.0); //max(x,0.0) should suffice, but somehos produces slower code.
                
                #ifdef HQ_DECON
                    // Y Defocusing causes scanlines gaps to blend, so mix the scanlines mask with the original color
                    // since the original color will be multiplied again for rgb_h later on, pow it to 0.9 to avoid excessive gamma increase.
                    
                        float mixing_factor =         // the mixing factor will depend on:
                              (DO_SHIFT_RGB           // the fact that we are doing defocusing
                            * EDGE_DEFOCUS_DO_BLUR) 
                            * ((1-dist_from_edge)     // the distance from the center
                            * DO_PIXELGRID_H) ;       //the scanline strength
                    rgb_h = mix( rgb_h,  mix(vec3(1.0), pixel_in.rgb, 0.9), mixing_factor);
                #endif
        }



    //Horizontal Triad Mask: ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
        vec3 rgb_w = vec3(1.0);
        vec3 rgb_grille_basal = vec3(0.0);

        if (DO_PIXELGRID_W > 0.0) { //4%
            // get 3 sines in black/white,   | sin(freq_rgb)
            // make them always positive and double frequency  | sin(freq_rgb) * sin(freq_rgb)
            // and scale Max width according to pre-gains:
                vec3 phosphor_width = map_range(pixel_in.rgb, min_inLum, max_inLum, PIXELGRID_MIN_W, PIXELGRID_MAX_W);
        
            //Get final phosphor width:
                rgb_w = morph_shape_full_no_steepness_vec3( sin(freq_rgb) * sin(freq_rgb), phosphor_width, PIXELGRID_GAMMA_W); //2%
                
            //Dedot rgb mask between h mask.
            //This work only when there are dots and have the countereffect when there are not.
            //This happen because flattening the rgb mask will make unflattened rgb triads
            //more evident, so this has to be used only when needed.
                rgb_w = mix(rgb_w, phosphor_width, dedot_mix);

            //lower strength via user parameter?
                rgb_w = mix(vec3(1.0), rgb_w, DO_PIXELGRID_W);
                
            //"export" naked vertical grille
                rgb_grille_basal = (rgb_w * rgb_w) * (rgb_w * rgb_w) * (PIXELGRID_BASAL_GRID*0.0025);
                
        }


    //Vertical mask .:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.
    // can be used for slotmask,aperturegrille ... or scanlines, but they would have a boxed shape.
        vec3 darklines_mask=vec3(1.0);

        if (PIXELGRID_Y_MASK > 0.0) {
            //Compute phosphors height mask with the right reference coords.
            // Faster than if/then (ASM proof and tests):
            float ph_mask_height = vPG_mask_height * mix(freq_base_core.y, freq_base_screen.y, PIXELGRID_Y_MASK_COORDS);
            /*float ph_mask_height;
            if ( PIXELGRID_Y_MASK_COORDS == 1.0 )
                ph_mask_height = freq_base_screen.y * vPG_mask_height;
            else
                ph_mask_height = freq_base_core.y * vPG_mask_height;
            */

            
            //phosphors mask stagger
                #define PH_MASH_OFFSET  (PIXELGRID_Y_MASK_OFFSET * HALF_PI )

                
            //Use abs(cos) (later 1-abs(cos) to get horizontal gaps, use pow to make them steep.
            //Stagger them by PH_MASH_OFFSET on even triads
                float mask_shape = abs(cos(  vPG_y_shift  + ph_mask_height  + (PH_MASH_OFFSET * antiburn_flip_even(is_even))) ) ;
                mask_shape = pow(mask_shape, PIXELGRID_Y_MASK_STEEP);


                // We can make the stagger dark line less pronunced
                // on its sides so that lateral staggered color
                // can bleed over.
                // but you really need to have God eyes to notice any improvement, if any imho.
                // Also, this would probably alter the colors (?)
                // So keep it disabled.
                //    float Bleed_force = SERVICE1; //0.02 seems a realistic value.
                //    float side_visibility = pow(pow(sin_check_offset,2.0),Bleed_force);
                //    mask_shape = mask_shape* side_visibility;
                // return vec4(mask_shape);

            //Get per channel input luminance by clamping pixel_in and applying hmask
                vec3 lum = clamp(pixel_in.rgb * rgb_w, 0.0, 1.0); //2 cycles less when using max or min, didn't bother.
            //Adapt the luminance effect via input parameter
                vec3 lum_adapted = (1-lum*PIXELGRID_Y_MASK_ON_WHITE);
            //Calc final mask visibility as the minimum between tha configured visibility and luminosity effect
                vec3 V=min( lum_adapted, vec3(PIXELGRID_Y_MASK) );
                
                //SlotMask de-Hive take 1: draws slotmask only on edges on dark parts. 109.45
                //vec3 V_EVERYWHERE = V;
                //vec3 V_JUST_EDGES =  V * (1-rgb_h);
                //V = mix(V_EVERYWHERE, V_JUST_EDGES, pixel_in.rgb*max_inLum);

                //SlotMask de-Hive Take 2: simply modulates slotmask visibility on dark colors. 109.5
                //V=V* pow(pixel_in.rgb,vec3(SERVICE1));

                //SlotMask de-Hive Take 3, takes care of phosphor height and scanlines visibility. 109.2
                //vec3 V_dehived = V*pow(phosphor_height,vec3(PIXELGRID_Y_MASK_OFFSET_DEHIVE));
                //V = mix(V, V_dehived, vec3(DO_PIXELGRID_H));

                //SlotMask de-Hive Take 4, takes care of phosphor height,scanlines visibility and keep edge slotmask.
                //the slower, but way better, keep this.
                vec3 V_JUST_EDGES =  V * (1-rgb_h);
                vec3 V_dehived = mix(V_JUST_EDGES, V, pow(phosphor_height,vec3(PIXELGRID_Y_MASK_OFFSET_DEHIVE)));
                V = mix(V, V_dehived, vec3(DO_PIXELGRID_H));
                
            //apply user visibility modifier to the mask
                darklines_mask = 1-(mask_shape*V);
            //Try to get a more brillant/sparkling look by highering the darklines
            //effect (sub) and pushing the source color, useful for slotmasks.
                vec3 PG_spark = (PIXELGRID_Y_SPARK * V) * (0.5/vPG_mask_height);         
                darklines_mask = (darklines_mask-0.5) * (1+PG_spark) + 0.5 ; 
        }

        

    //Finally put H mask, Scanlines and darklines masks together:
        vec3 mask = rgb_w * rgb_h * darklines_mask;


    //Apply Overmask:
            mask = PIXELGRID_OVERMASK * (mask - 0.5) + 0.5;
            pixel_in.rgb = mix(pixel_in.rgb, pixel_in.rgb * PIXELGRID_OVERMASK, PIXELGRID_OVERMASK);
            
            //mask = mix(vec3(1.0), mask * PIXELGRID_OVERMASK * 1.2, PIXELGRID_OVERMASK);
            
            
    //Apply the mask to pixel_in and clamp the minimum to the unexcited grille.
        vec3 mask_and_grille = max(mask * pixel_in.rgb, rgb_grille_basal);

    //return it with alpha channel containing the mask itself, so that halo can selectively light it.
        return vec4( mask_and_grille , dot(rgb_h, vec3(0.3333)));
}


vec4 fn_pixel_dotmat(vec2 coords, vec3 pixel_in) {

    //Base angle, depends on original size:
        vec2 angle_base = coords * pi * params.OriginalSize.xy; //FIXME here we want .yy to make integer scaling


    //Set the grid sharpness from vertex shader:
        float s_grid = vDotMat_Grid_Sharpness;


    //Generate bw grid:
        vec2 grid = cos(angle_base);
        grid = grid * grid;
        grid = pow(grid, vec2(s_grid));
        grid = 1 - grid;

    //Combine gridx and gridy into a single one
        //float fgrid = min(grid.x, grid.y);
        float fgrid = grid.x* grid.y;

    //Modify strength for "paint on background" option

    //Smoothly fadeout grid on background
        //The fadeout size:
            float fade_size = 0.3;

        //We invert the smooth logic if threshold is negative. this allow to fadeout
        //the grid on bright or dark backgrounds.
            float lum_scaled = dot(pixel_in, vec3(0.33333));
            lum_scaled = clamp(lum_scaled, 0.0, 1.0);
            lum_scaled = mix_step(1-lum_scaled, lum_scaled, float(DOT_M_G_TRESH > 0.0));

        float smooth_threshold = abs(DOT_M_G_TRESH);
        vec2 smooth_range  = vec2(smooth_threshold, smooth_threshold+fade_size);
        float grid_smoothstep = 1-smoothstep(smooth_range.x, smooth_range.y, lum_scaled);

    //Adapt grid strength
        //reduce grid strength when grid sharpness is low.
            float m = normalize_range(vDotMat_Grid_Sharpness, DOT_M_SHARP_AUTO_MIN, DOT_M_SHARP_AUTO_MAX);
            m=pow(m,0.25);
            float dot_m_g_str = mix(DOT_M_G_STR*0.4, DOT_M_G_STR, m);
            float grid_str = dot_m_g_str * grid_smoothstep;


    //Apply strength modifier
        float fgrid_adpt = mix(1.0, fgrid, grid_str);

    //Output is the mix of the gap and the rgb masked pixel in.
    //In the alpha channel we put the grid because it could be selectiveli brightened by halo
    return vec4(
                 mix(vDotMat_Grid_Color, pixel_in, fgrid_adpt),
                 fgrid_adpt
               );

}

float get_clamped_white_reference(vec3 pixel_in){
    float white_reference = max(max(pixel_in.r,pixel_in.g),pixel_in.b);
//     white_reference = min(white_reference,1.0); //Clamp here for both vmask and darklines.
    return white_reference;
}

vec4 textureLod_wrap(sampler2D tex, vec2 co, float lod, float wrap_mode) {
    #ifndef DISALLOW_BG_IMAGE_TEXTURE_WRAP_IN_SHADER
        /*
        // Mirrored repeat, once, useless since is done by default
        if (co.x > 1.0 || co.x < 0.0)
            co.x = 1- mod(co.x, 1.0);
        if (co.y > 1.0 || co.y < 0.0)
            co.y = 1- mod(co.y, 1.0);
        */

        if (wrap_mode == 1.0) {
            //Clamp to border, black.
            //bool bOutside = (co.x < 0.0 || co.x > 1.0 || co.y < 0.0 || co.y > 1.0 ) ;
            //if (bOutside) return vec4(0.0,0.0,0.0,1.0);
            
            if (is_first_outside_rect(co, vec4(0.0,0.0,1.0,1.0))) //ASM PROOF, FASTER.
                return vec4(0.0,0.0,0.0,1.0);

        } else if (wrap_mode == 2.0) {
            //Clamp to edge:
            co = clamp(co, 0.00, 1.0);
        } else if (wrap_mode == 3.0) {
            //Repeat no mirror:
            co = mod(co, 1.0);
        }
    #endif
    return textureLod(tex, co, lod);
}



/*vec3 pixel_blank_alternate_v2(vec2 co, vec4 texsize, float strength, vec3 c) {

	c = pow(c, vec3(2.2));

    vec3 S=vec3(strength);
    vec3 ref = clamp(c, 0.0,1.0);
    S = min(S, vec3(1.0)-ref);
    S = min(S, ref);

    float line = co.y * texsize.y;
    float column = co.x * texsize.x;

    float l_period_half = ALT_BLANK_PERIOD / 2;

    float sinlinea= sin(line*pi/ALT_BLANK_PERIOD);
    sinlinea*=sinlinea;

    float sincolonna= sin(column*pi/ALT_BLANK_PERIOD);
    sincolonna*=sincolonna;


    bool linea_pari = sinlinea > 0.5;
    bool frame_pari =  mod(float(params.FrameCount),2.0  ) == 1 ;

    if (frame_pari) {
        if (linea_pari)
            S = -S;
                else
            S = +S;
	} else {

		if (!frame_pari) {
			if (linea_pari)
				S = S;
					else
				S = -S;
		}
    }

    c = c+S;

	c = pow(c, vec3(1/2.2));

    return c;
}


vec3 pixel_blank_alternate(vec3 strength, vec3 pixel_in) {
    //strength *= 1-pixel_in; <<--- less on bright areas?
    strength =  1-strength;

    float square_wave_line = float(mod((vTexCoord.y+0.0001) * params.OutputSize.y, ALT_BLANK_PERIOD) > ALT_BLANK_PERIOD / 2);
    if ( mod(params.FrameCount, 2.0) == 1.0) {
        return min(square_wave_line + strength, vec3(1.0));
    } else {
        return min(1 - square_wave_line + strength, vec3(1.0));
    }

}
*/

vec3 bezel_color(float lum)  {
    //Colorize bezel frame
    vec3 col = vec3(BEZEL_R,BEZEL_G,BEZEL_B) + lum;
    float con_max = fma(0.5, BEZEL_CON, 0.5); // <<- same as: float con_max =  0.5 * BEZEL_CON + 0.5;
    col= scale_to_range_vec3(col, -con_max+1, con_max); 
    return clamp(col,0.0,1.0);
}


float fuzzyrect(vec2 uv, vec2 size, float radius, float blur) {
    vec2 hSize = size / 2.0 - radius;
    float d = length(max(abs(uv - vec2(0.5)),hSize)-hSize);
    //return smoothstep(-radius-blur, -radius+blur, -d);
    return smoothstep_fast(-radius-blur, -radius+blur, -d);
}


float create_ambi_colorize_shade(vec2 co) {
    float blur = AMBI_OVER_BEZEL_SIZE;
    vec2 size = vec2(1.0, 2 - AMBI_OVER_BEZEL_AR_CORRECTION)-blur;
    float radius = 0.0;

    return  1 - min ( fuzzyrect(co, size,  radius,  blur) * 2, 1.0);
}




//////////////////////////////////////////////////////////////////////////////////////////////////////

vec4 fn_pixel_fgbg_image(sampler2D smp, out vec2 image_coords, out float image_aspect) {
    vec2 tex_size = textureSize(smp, 0); // * BG_ZOOM;
    image_aspect = tex_size.x/tex_size.y; //FIXME image_aspect needs to be inverted for rotated contents.
                                          //get_scaled_coords_aspect_fgbg() already does that, so we can take the inverted
                                          //value from there.

    //FIXME isrotated deve essere true anche quando l'immagine di sfondo è ruotata.
    //vec2 fg_image_offset=vec2(BG_IMAGE_OFFX,BG_IMAGE_OFFY);
    //image_coords = get_scaled_coords_aspect(vBg_img_coords + fg_image_offset, global.FinalViewportSize, image_aspect, bool(vIsRotated));

    //bool rotated = bool(vIsRotated) || BG_IMAGE_ROTATION > 4.5;
    bool rotated_bg = BG_IMAGE_ROTATION > 3.5;

    image_coords = get_scaled_coords_aspect_fgbg(vBg_img_coords, global.FinalViewportSize, image_aspect, rotated_bg, vIsRotated, vIn_aspect);


    float bg_over_lod = log2(tex_size.y * global.FinalViewportSize.w);
    vec4 pixel_bgover = textureLod_wrap(smp, image_coords, bg_over_lod, BG_IMAGE_WRAP_MODE);
    return pixel_bgover;
}

vec3 light_over_image(vec3 light, vec3 image, float black_illumination) {
    //Simulates illumination.
    //It works by adding the light on the image.
    //It will add less light on dark colors (

    //mitigate clipping by lowering light on bright images:
    //light = light * (1 - max(max(image.r,image.g),image.b) )   ; //1313/20079
    //light =   light - light*max(max(image.r,image.g),image.b); //ASM PROOF FASTER 1310 20046
    //light = -light * max(max(image.r,image.g),image.b) + light ; //ASM PROOF FASTER
    light = fma(vec3(max(max(image.r,image.g),image.b)),
            -light, 
            light );

    vec3 light_on_black = black_illumination * light;
    vec3 modulated_on_black = image.rgb +  ( light  * image.rgb ) + light_on_black;


    return modulated_on_black;
}

vec3 ambi_blend_image(vec4 image, vec3 ambi, float blend_mode) {
    //mix or add ambient light with image, also allow force colorization in add mode.
    if (DO_AMBILIGHT == 0.0) return image.rgb;

    // Fake a transparent image when force colorization is requested
    // So that we can use the same code used for alpha blend.
    // Also multiply AMBI_BG_IMAGE_FORCE * AMBI_BG_IMAGE_BLEND_MODE to skip
    // force colorization when mode blend mode is not "ADD".
        float image_alpha_adapted = image.a - (AMBI_BG_IMAGE_FORCE * AMBI_BG_IMAGE_BLEND_MODE);

    if (AMBI_BG_IMAGE_BLEND_MODE == 0.0) {
        image.rgb = mix(ambi.rgb, image.rgb, image_alpha_adapted);
    } else {
        //vec3 ambi_alpha_masked = ambi.rgb *  (1 -image_alpha_adapted);
            // (ambi.rgb * -image_alpha_adapted) + (ambi * 1)
        vec3 ambi_alpha_masked = fma(ambi.rgb, vec3(-image_alpha_adapted),  ambi.rgb) ; //ASM PROOF: FASTER
        image.rgb = light_over_image(ambi_alpha_masked, image.rgb, AMBI_ADD_ON_BLACK);
    }
    return image.rgb;
}

float gaussian_coefficient(float x, float sigma) {
    //restituisce un coefficiente gaussiano per x compreso tra 0 ed 1
    float coefficient = 1.0 / sqrt(2.0 * 3.14159265358979323846 * sigma);
    float exponent = -((x * x) / (2.0 * sigma));
    return coefficient * exp(exponent);
}


vec3 fn_pixel_content(vec2 coords) { 
    
    #ifndef ANTIBURN_COMPLETE
        coords = antiburn(coords);
    #endif
        
    vec2 warpedglow = vec2(0.0);
    
    #ifndef SKIP_WARPEDGLOW
        warpedglow.y = texture(in_glow_pass, coords + delta_render_enabled() * eps ).a;
        // ^^ Delta render performance suffer alot if not adding eps here on my haswell for unknown reasons.
        // ...probably some failed compiler optimization issue.
    
        warpedglow.x = texture(in_glow_pass_x, coords).a;
        
        //Modulate warpedglow x via scanline?
        if (IN_GLOW_WARPSHARP_X < 0.0) {
            float freq = vPG_OriginalSize_tated_mul_pi.y * 2.0;
            if (vDo_Tate == 0.0)
                warpedglow.x *= 0.25 - 0.75 * cos(coords.y * freq); //warpedglow.x = warpedglow.x * mix( 1.0, -cos(coords.y * freq),0.75);
                    else
                warpedglow.y *= 0.25 - 0.75 * cos(coords.x * freq);//warpedglow.y = warpedglow.y * mix( 1.0, -cos(coords.x * freq),0.75);
        }
    #endif
    
    coords += warpedglow.xy;

    //#define USE_QUILEZ   
    #ifdef USE_QUILEZ
        vec3 pixel_out = texture(in_glow_pass, coords_QULEZ(coords, global.upscale_passSize)).rgb;
    #else
        vec3 pixel_out = texture(in_glow_pass, coords).rgb;
    #endif
        
    float dot_mat_or_pixelgrid = 1.0; //init dot grid or scanline mask to 1.0
    
    //Pixel grid
        if (DO_PIXELGRID == 1.0) {
            //1264,20488,22262, 108.5
            float vMax_inLum = mix( 1.0, 
                                    apply_contrast_brightness(1.0, CONTRAST, BRIGHTNESS) * max(IN_GLOW_POWER, 1.0), 
                                    DO_CCORRECTION );           
            //1268,20501,22268 108.3
            /*float vMax_inLum = 1.0;
            if (DO_CCORRECTION == 1.0)
                 vMax_inLum = apply_contrast_brightness(1.0, CONTRAST, BRIGHTNESS) * max(IN_GLOW_POWER, 1.0);
            */
            vec4 pixel_grid = fn_pixel_grid(coords, pixel_out.rgb, 0.0, vMax_inLum);
            //grid mask only is needed by halo to selectively light the grid.
                dot_mat_or_pixelgrid = pixel_grid.a;
                pixel_out.rgb = pixel_grid.rgb;
        }
    //Dot mask
        if (DO_DOT_MATRIX == 1.0) {
            vec4 pixel_dotmat = fn_pixel_dotmat(coords, pixel_out.rgb);
            //grid mask only is needed by halo to selectively light the grid.
                dot_mat_or_pixelgrid = pixel_dotmat.a;
            //rgb channel goes to pixel_out
            pixel_out.rgb = pixel_dotmat.rgb;
        }

    //Halo
        if (DO_HALO == 1.0 ) {
            vec3 pixel_haloed = texture(halo_pass, coords).rgb;                
            pixel_out.rgb = pixel_out.rgb + pixel_haloed * mix( dot_mat_or_pixelgrid, 1.0, HALO_VS_SCAN);            
        }

    // Apply gamma out:
        pixel_out.rgb = pow(max(pixel_out.rgb, vec3(0.0)),vec3(GAMMA_OUT));

    //Bloom
        if (DO_BLOOM == 1.0 ) {
            vec3 bloomed=texture(bloom_pass_final, coords).rgb ;
            vec3 bloomed_adj = mix(bloomed * (1-pixel_out.rgb), bloomed, BLOOM_OVER_WHITE); 
            pixel_out.rgb = bloomed_adj + pixel_out.rgb;
            if (BLOOM_BYPASS == 1.0) pixel_out.rgb = bloomed_adj;
        }


    return pixel_out.rgb;
}

bool is_bezel_paint_area(vec2 lcoords_for_bezel) {
    // Can we skip inner blank tube?  Can we skip outside border?
    if (is_first_outside_rect(lcoords_for_bezel, RECT01))
        return false;
    #define _INNER_BLANK_RECT vec4(1-BEZEL_TUBE_BLANK_SIZE, 1-BEZEL_TUBE_BLANK_SIZE, BEZEL_TUBE_BLANK_SIZE, BEZEL_TUBE_BLANK_SIZE)
    if (is_first_inside_rect(lcoords_for_bezel, _INNER_BLANK_RECT)) 
        return false;
    
    return true;
}

vec4 fn_pixel_bezel(vec2 coords_for_bezel, vec2 coords_for_mirror, float nightify_str, vec3 pixel_ambilight) {
    
    vec4 bezel_in;

    //Sample main bezel texture:
    //We assume straight and curved textures are the same size. WARNING
        #ifndef BEZEL_RES
            vec2 bezel_lut_size = textureSize(monitor_body_curved, 0);
            float bezel_frame_lod = log2(bezel_lut_size.y * (BEZEL_FRAME_ZOOM+1.0) * global.FinalViewportSize.w);
        #else
            //float bezel_frame_lod = log2(    fma( BEZEL_RES.y,  BEZEL_FRAME_ZOOM,  BEZEL_RES.y) * global.FinalViewportSize.w); //ASM PROOF: UNDECIDED
            float bezel_frame_lod = log2( BEZEL_RES.y * (BEZEL_FRAME_ZOOM+1.0) * global.FinalViewportSize.w); 
        #endif

        
        //Add +1 to lod when using tilt to avoid aliasing along specular straight lines on the bezel frame.
        if (BEZEL_USE_STRAIGHT < 0.5)
            bezel_in = textureLod(monitor_body_curved,coords_for_bezel,bezel_frame_lod + DO_TILT);
                else
            bezel_in = textureLod(monitor_body_straight,coords_for_bezel,bezel_frame_lod + DO_TILT);


    //Exit if the bezel is completely transparent (the tube)
        if (bezel_in.a == 0.0) return vec4(0.0);



    float lut_specular = bezel_in.g * BEZEL_SPCL_STRENGTH;
    float reflection_modifier = 0.0;
    vec4 pixel_mirrored = vec4(0.0);

    // Calculate Reflections, can be skipped if blue channel is 0:
        if (bezel_in.b > 0.0) {
            
        //Reflections: Calculate the bezel roughness to apply to the reflecting area. (bezel_in.g expresses the specularity)
            #define ROUGHNESS_ADAPT (2/1080.0 * BEZEL_ROUGHNESS)
            float roughness = (1 - clamp(lut_specular * 10, 0.0, 1.0)) * //Skip drawing roughness over specular, it looks bad
                              (random_fast(vTexCoord) * ROUGHNESS_ADAPT) - ROUGHNESS_ADAPT * 0.5;
                              
            //Reflections:
                reflection_modifier = bezel_in.b; //bezel_in.b expresses how much the area is reflective

            //Sample the reflection pass with small offset to simulate the roughness
                pixel_mirrored = texture(reflected_blurred_pass, coords_for_mirror + roughness);
            //Show less reflections in the corners
                // (all done in reflection pass)

            //Push it over the specular areas and apply the reflection modifier
                pixel_mirrored.rgb = pixel_mirrored.rgb * (1 + lut_specular);
                //pixel_mirrored.rgb = pixel_mirrored.rgb + pixel_mirrored.rgb * lut_specular; //ASM PROOF: SLOWER
                //pixel_mirrored.rgb = fma( pixel_mirrored.rgb, vec3(lut_specular), +pixel_mirrored.rgb); //ASM PROOF: SLOWER
                
            //apply shades over reflection area
                pixel_mirrored.rgb += pixel_mirrored.a;
        }

    //Yes, we already passed ambilight as parameter, but if geometry content is overridden
    //then ambientlight may have not been sampled yet due to some skip logic made in the main()
       // if (DO_AMBILIGHT == 1.0) 
       //     pixel_ambilight = texture(ambi_temporal_pass, vOutputCoord).rgb;

    //Apply nightification, the strength is modulated by ambient light.
        //Use a shaded box to apply ambilight on external borders only
            float ambi_colorize_shade = create_ambi_colorize_shade(coords_for_bezel);


            vec3 ambi_over_bezel = (AMBI_OVER_BEZEL * pixel_ambilight) *
                                   (bezel_in.a * ambi_colorize_shade) *
                                   (1-reflection_modifier) ;
                                   
    //Colorize the bezel (bezel.r expresses the luminance)
        vec3 bezel_out = bezel_color(bezel_in.r);
    //Nightify the bezel
        bezel_out.rgb = fn_pixel_nightify(bezel_out.rgb, nightify_str, ambi_over_bezel )   ;


    //Add reflections after nightification
            bezel_out += (pixel_mirrored.rgb * reflection_modifier);

        //Apply ambient light over the bezel
            bezel_out = light_over_image(ambi_over_bezel, bezel_out, AMBI_ADD_ON_BLACK);

    //Diffuse the light over specular areas, we use a mipmap with low precision.
        if (lut_specular > 0.0) {
            vec3 pixel_avglum = texture(avglum_pass, coords_for_mirror).rgb;
            #ifdef D3D_WORKAROUND
                pixel_avglum = vec3(0.5);
            #endif
            bezel_out = bezel_out + (pixel_avglum * lut_specular) ;
        }

    return vec4(bezel_out, bezel_in.a);
}

float quasirandom(float n) {
    //n has to be integer for this to work.
  // https://extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences/
  return fract(n*0.754877626895905);
    //return abs(sin(n));
}

vec2 quasirandom(vec2 n) {
    //n has to be integer for this to work.
  // https://extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences/
  return fract(n*0.754877626895905);
    //return abs(sin(n));
}

vec3 antighost(vec3 c) {
    #ifdef ANTIBURN_X
        return c;
    #endif

    #ifdef ANTIBURN_X
        return c;
    #endif

	#ifndef D3D_WORKAROUND
		if (!bdelta_render_enabled()) {
				if (frame_is_even() && (vInterlace_offset == 0.0)) {
					vec3 feedback = texture(final_passFeedback, vOutputCoord).rgb;
					vec3 diff=(feedback - c)*LCD_ANTIGHOSTING;
					//diff = sign(diff) * min( abs(diff), vec3(LCD_ANTIGHOSTING_CEIL));
                    diff = clamp(diff, -LCD_ANTIGHOSTING_CEIL, LCD_ANTIGHOSTING_CEIL); //probably faster.
					c = mix(c, c-diff, 0.5);
				}
		}
	#endif
	return c;
}

float led_halo(vec2 co, float image_aspect, vec2 center, float smoothshade, float led_aspect, float led_shape) {
    vec2  delta = (co - center) * vec2(image_aspect, 1.0/led_aspect);
    float dist_circle = length(delta);
    float dist_square   = max(abs(delta.x), abs(delta.y));
    float d = mix(dist_circle, dist_square, led_shape);
    float hv = 1.0 - smoothstep(0.0, smoothshade, d);
    return hv * hv;
}

vec3 apply_power_led( vec4 bgimage, vec3 bgimage_nonight, vec2 image_coords, float image_aspect, vec2 led_pos, float led_hue, float halo_size, float led_aspect, float led_shape, float controller, float led_exposure, bool source_led_off ) {

    halo_size*=0.1;

    //vec4 led_area = vec4( led_pos - halo_size, led_pos + halo_size );  //maybe a bit slower?
    vec2 extent = vec2( halo_size / image_aspect, halo_size * led_aspect );
    vec4 led_area = vec4( led_pos - extent, led_pos + extent );
    
    if (is_first_outside_rect(image_coords, led_area)) return bgimage.rgb;

    if (source_led_off) {
        if (controller < 0.5) {
            //no need to do anything.
            return bgimage.rgb;
        } else {
            //put led on:

             vec3 led_on = pow(bgimage_nonight, vec3(1.0));
             led_on = saturation_set_gain(led_on, 15.0);
             //led_on = clamp(led_on, vec3(0.0), vec3(1.0));

            bgimage.rgb = mix(led_on,           //  on controller: _low_
                            bgimage.rgb,       //then mix it with original image...
                            bgimage.a   //...depending on the alpha value
                            );
        }
    }

    #define _LED_HALO_POWER 0.5          //how much the halo around the led is visible

    //get led rgb color from Hue:
    vec3  led_color = clamp(abs(mod(led_hue * 6.0 + vec3(0.0, 4.0, 2.0), 6.0) - 3.0) - 1.0, 0.0, 1.0);

    //when led_exposure is negative, modulate maximum exposure depending on the difference between
    //the nightified image and the original one.
    if (led_exposure < 0.0) {
        // Exposure driven by full scene average lum
        float scene_lum = texture(avglum_pass,FULL_SCENE_LUM_SAMPLING_POINT).a;
        led_exposure = mix(0.0, abs(led_exposure) , 1-scene_lum);
    }

    //Bw halo shape
    float halo_bw   = led_halo(image_coords, image_aspect, led_pos, halo_size, led_aspect, led_shape) * _LED_HALO_POWER *  (led_exposure/10.0+1);


    // blend led halo with the underlying image less halo on bright background.
    // This works well when coupled with the nightify and even ambientlight function applied,
    bgimage.rgb = bgimage.rgb + led_color * halo_bw * (1-bgimage.rgb);


    //overexpose led light?
    bgimage_nonight = mix(bgimage_nonight.rgb, vec3(1.0), led_exposure);


    bgimage.rgb = mix(bgimage.rgb, vec3(1.0), led_exposure*(1-bgimage.a));


    float m = (1.0 - bgimage.a) * halo_bw; // where image is transparent and halo is high
    bgimage.rgb = mix(bgimage.rgb, bgimage_nonight.rgb, m);      // apply no night light

    return bgimage.rgb;
}


void main() {

    // Early whole frameskipping
    #ifndef D3D_WORKAROUND
        bool skipframe = false;
        if (skip_subframe()) skipframe = true;
        if (fps_halver_is_skipped_frame()) skipframe = true;
            
        if (skipframe) {
            FragColor.rgb = texture(final_passFeedback, vOutputCoord).rgb ;
            return;
        }
    #endif
  
    // Initial content coords
    vec2 co_content = vTexCoord;

    // Tilt?
    if (DO_TILT == 1.0)
        co_content = tilt(co_content, vIsRotated, TILT_X);
    
    
    // Curvature: has to come after inner zoom or bezel curved border will not match
    // content curved border when inner zoom changes.
    if ( curvature_needed() > 0.0 ) {
        co_content = Warp_koko(co_content, vec2(GEOM_WARP_X, GEOM_WARP_Y*float(need_NO_integer_scale())), 0.5);
    }

    
    // content_mask is a mask used to save GPU cycles by skipping already processed areas.
    // It is initialized as inner/outer border mask. 
    float content_mask = 0.0;
    if (DO_CURVATURE == 1.0)  {
        content_mask = fn_border(co_content);
    } else {
        // Since we're wrapping to border (to show black border when zooming out via content override),
        // edge sampling will be darker and this is bad for dmg look, so we mangle 1/4 input pixels.
        // maybe in te future this will go away and dmg will have its proper shader/preset.
        //#define eps2 (global.upscale_passSize.zw*0.5)
        //#define RECTEPS vec4(eps2.x, eps2.y, 1.0-eps2.x, 1.0-eps2.y)
        //content_mask = float(is_first_inside_rect(co_content, RECTEPS));
        //...in the end I decided that the best way is that affected presets (Gameboy DMG) will
        //reference a preset that wrap with mirror repeat.
        content_mask = float(is_first_inside_rect(co_content, RECT01));
    }

    // Mirror/reflection coords need to be calculated here, before geom override, but after curvature.
    // It is still not perfect but a reasonable tradeoff by now.
    vec2 co_mirror = zoom(co_content, 1/BEZEL_REFL_ZOOMOUT_ROOM);

    // Apply other content coords modifiers
    if (DO_GAME_GEOM_OVERRIDE == 1.0)
        co_content = content_geom_override_zoom(co_content, GAME_GEOM_ASPECT, vIn_aspect, GAME_GEOM_VSHIFT, GAME_GEOM_HSHIFT, GAME_GEOM_ZOOM);
 
    
    // If delta render is active, return the pass feedback when color didn't changed.
    #ifndef D3D_WORKAROUND
        if (delta_render_enabled() == 1.0) {
            bool reuse_old = false;
            if (vDeltaRenderOk > 0.0 && content_mask > 1.0 - eps ){
                reuse_old = (vDeltaRenderOk > 0.0) && (texture(in_glow_pass, co_content).a == 0.0); 
            }

            if (reuse_old) {
                FragColor.rgb = texture(final_passFeedback, vOutputCoord).rgb;
                return;
            }
        }
    #endif
        
    // Dynamic lum dependant full screen zoom and autocrop
    #ifndef D3D_WORKAROUND
        if (DO_DYNZOOM > 0.0)
            co_content = zoom(co_content, get_dyn_zoom(avglum_pass) );
    
        if (AUTOCROP_MAX > 0.0)
            co_content = zoom(co_content, get_autocrop_zoom(avglum_pass) );
    #endif

    
    // Draw content only over the alpha mask, and sample ambientlight outside it.
    vec3 pixel_ambi = vec3(0.0);
    vec3 pixel_under_content = vec3(0.0);
    vec3 pixel_out = vec3(0.0);
    
    if (content_mask > 0.5) {
        
        pixel_out = fn_pixel_content(co_content);
        
            
        if (DO_BACKDROP * BACKDROP_JUST_TUBE > 0.0) {
            vec3 blended = vec3(0.0);
            vec4 backdrop_img = pixel_overlay_image_stretch(backdrop, co_content);
            if (BACKDROP_BLEND_MODE == 1.0) {
                blended = (pixel_out.rgb + DO_BACKDROP) * backdrop_img.rgb;
            } else {
                blended = pixel_out.rgb + ( (DO_BACKDROP * backdrop_img.rgb) * (1.0 - pixel_out.rgb) );
            }
            pixel_out = mix( pixel_out.rgb, blended, backdrop_img.a );
           
        }
        
        if ( DO_SPOT + DO_VIGNETTE > 0.0 ) {
            float perlin = (random_fast(vTexCoord)-0.5) * (1/255.0);
            vec2 vignette_spot = texture(helper_pass, vTexCoord).zw;
            pixel_out = pixel_out * vignette_spot.x;
            //pixel_out = mix(pixel_out+perlin, vec3(content_mask), vec3(vignette_spot.y) );
            pixel_out = mix(pixel_out+abs(perlin), pixel_out+vec3(content_mask), vec3(vignette_spot.y) );
        }
        
            #ifdef DO_ADPT_STROBE_PLUGIN
                pixel_out = adaptive_strobe(pixel_out, vAdaptive_strobe_oscillator, final_passFeedback, vOutputCoord);
            #else
                #ifdef DO_LCD_ANTIGHOSTING_PLUGIN
                    pixel_out = antighost(pixel_out);
                #endif
            #endif
        

    } else {

        // Can we halve border update to spare some cycles?
        bool skip_border = false;
        #ifdef HALVE_BORDER_UPDATE
            // By using !frame_is_even() and not frame_is_even(), we solve the issue of inner frame jagged lines
            // when using HALVE_BORDER_UPDATE + DELTA_RENDER
            if (!frame_is_even()) skip_border = true;
        #endif
        
        // We can skip border update on subframes.
        // The following may seem redundant because we skip whole subframes already
        // But when we use strobing, we always reach this point/code.
        bool skip_because_is_subframe = false;
        #ifdef SUBFRAMES_OPTIMIZATIONS
            if (is_subframe()) skip_border = true;
        #endif
            
        #ifndef D3D_WORKAROUND
            if ( skip_border ) {
                FragColor.rgb = texture(final_passFeedback, vOutputCoord).rgb;
                return;
            }
        #endif

        // Ambient light
        if (DO_AMBILIGHT == 1.0) {
            pixel_ambi = texture(ambi_temporal_pass, vOutputCoord).rgb ;
            
            // Debanding when not using bg images.
            #ifndef SKIPRANDOM
            if (DO_BG_IMAGE != 1.0) {
                const float noise_power = -NOISEPOWER_AMBI;
                float dynamicseed = mod(params.FrameCount, 30.0001);
                float anoise1 = random_fast(vTexCoord*dynamicseed) * noise_power;
                // anoise1 *=  (1 - pixel_ambi.g);
                //anoise1 =  (anoise1 - anoise1 * pixel_ambi.g); //ASM PROOF FASTER
                anoise1 =  fma(-anoise1, pixel_ambi.g, anoise1); //ASM PROOF FASTER
                pixel_ambi += anoise1;
            }
            #endif
            
            //pixel_under_content = clamp(pixel_ambi, 0.0,1.0) * (1-content_mask); //<-- this one is better because will antialias borders
                                                                                    //    better (via canvas-busy) when ambientlight > 0.0
            //pixel_under_content = pixel_ambi * (1-content_mask);
            //pixel_under_content = pixel_ambi - content_mask * pixel_ambi; //ASM PROOF: FASTER
            pixel_under_content = fma(vec3(-content_mask), pixel_ambi, pixel_ambi); //ASM PROOF: FASTER
        }
    }

    // Apply smooth border to soften jagged edges.
    pixel_out *= content_mask;
       
    
    // This represents the inner bezel alpha (when using bezel)
    // or the pixel_content canvas (when not using bezel)
    // It is used to draw spot on the whole inner tube even when toe content does not
    // fill the whole tube.
    float content_and_bezel_mask = content_mask;

    // Draw Bezel
    vec2 co_bezel = vec2(0.0);
    if (DO_BEZEL == 1.0) {
        
        co_bezel = get_scaled_coords_for_bezel(vTexCoord, vIsRotated);
        
        if (is_bezel_paint_area(co_bezel)) {
            
            vec4 pixel_bezel = fn_pixel_bezel(co_bezel, co_mirror, BG_IMAGE_NIGHTIFY * DO_BG_IMAGE, pixel_ambi);
            pixel_out = mix(pixel_out, pixel_bezel.rgb, pixel_bezel.a);
        
            // Update alpha mask.
            // We can't use the bezel alpha channel to update the alpha mask since it is transparent
            // on the tube and we don't want to paint anything there, so use a dumb rect and add it to content_mask:
            float rect_bezel = float(is_first_inside_rect(co_bezel, vec4(BEZEL_FRAME_SURROUND_MARGIN, BEZEL_FRAME_SURROUND_MARGIN, 1.0 - BEZEL_FRAME_SURROUND_MARGIN, 1.0 - BEZEL_FRAME_SURROUND_MARGIN)));
            content_and_bezel_mask = max(pixel_bezel.a, rect_bezel);
        }
        //FragColor = vec4(content_and_bezel_mask); return; // <- uncomment to debug BEZEL_FRAME_SURROUND_MARGIN
    }

    // Background image have no need to paint if not in the outer border:
    if ( (DO_BG_IMAGE == 1.0 && BG_IMAGE_OVER == 0.0) && (content_and_bezel_mask < 1.0) ) {
        vec2 image_coords; //it gets the value from fn_pixel_fgbg_image output parameter.
        float image_size ; //(unused here, it gets the value from fn_pixel_fgbg_image output parameter.
        vec4 pixel_bg_image = fn_pixel_fgbg_image(bg_under, image_coords, image_size);

        vec3 anti_nightify_ambi = (pixel_ambi * AMBI_BG_IMAGE_BLEND_MODE) *
                                   max(AMBI_BG_IMAGE_FORCE, 1-pixel_bg_image.a);

        pixel_bg_image.rgb = fn_pixel_nightify(pixel_bg_image.rgb, BG_IMAGE_NIGHTIFY, anti_nightify_ambi );
        pixel_bg_image.rgb = ambi_blend_image(pixel_bg_image, pixel_ambi, AMBI_BG_IMAGE_BLEND_MODE);

        pixel_under_content = pixel_bg_image.rgb;
    }


    // Blend content and border:
    pixel_out = mix (pixel_under_content, pixel_out, content_and_bezel_mask);

    //Foreground image
    float pixel_fg_image_alpha = 0.0;
    if (DO_BG_IMAGE + BG_IMAGE_OVER == 2.0) {
        vec2  image_coords; //it gets the value from fn_pixel_fgbg_image output parameter.
        float image_aspect; //it gets the value from fn_pixel_fgbg_image output parameter.
        vec4 pixel_fg_image = fn_pixel_fgbg_image(bg_over, image_coords, image_aspect);
        vec3 anti_nightify_ambi = (pixel_ambi * AMBI_BG_IMAGE_BLEND_MODE) *
                                   max(AMBI_BG_IMAGE_FORCE, 1-pixel_fg_image.a);

        #if defined(USE_LED1) || defined(USE_LED2) || defined(USE_LED3)
            vec3 pixel_fg_image_no_night = pixel_fg_image.rgb;
        #endif

        pixel_fg_image.rgb = fn_pixel_nightify(pixel_fg_image.rgb, BG_IMAGE_NIGHTIFY, anti_nightify_ambi );


        // tl:dr:
        //   dont "black" the transparent areas outside the game content when we use AMBI_BG_IMAGE_BLEND_MODE ADD blend mode.
        //   Do only for add mode, since some inplace presets relies on the blackness with AMBI_BG_IMAGE_BLEND_MODE mix mode.
        // long explaination:
        //   We want to show the underlying game content when the image is transparent.
        //   This mean that alpha channel reveals the underlying content, but doing that even
        //   when the underlyinig content is void is not only pointless, but even counterproductive.
        //   Think at when we want to use ambient lights to *light* the part of the image
        //   that have a low alpha channel, that part would be black.
        if (DO_AMBILIGHT + AMBI_BG_IMAGE_BLEND_MODE == 2.0) {
            float ambi_mask = create_ambi_colorize_shade(co_bezel);
            float alpha_selective = clamp( pixel_fg_image.a + (1-content_and_bezel_mask) , 0.0, 1.0); 
            //float alpha_selective = min( pixel_fg_image.a + (1-content_and_bezel_mask) , 1.0);  //<- this is enough, but cycles won't scale.
            
            pixel_out = mix(pixel_out, pixel_fg_image.rgb, alpha_selective );
            float fg_image_alpha_adapted = max(pixel_fg_image.a - AMBI_BG_IMAGE_FORCE, 0.0);
            vec3 light = pixel_ambi.rgb * (ambi_mask) * (1- fg_image_alpha_adapted);
            pixel_out = light_over_image(light, pixel_out, AMBI_ADD_ON_BLACK);
        } else {
            pixel_out = mix(pixel_out, pixel_fg_image.rgb, pixel_fg_image.a);
        }
        pixel_fg_image_alpha = pixel_fg_image.a;

        if (content_and_bezel_mask < 1) { // Who said big branches are bad? Spare 1fps per led @Haswell.
            #ifdef USE_LED1
                if (global.LED1_SIZE != 0.0)
                    pixel_out.rgb = apply_power_led( vec4(pixel_out.rgb, pixel_fg_image_alpha),  pixel_fg_image_no_night, image_coords, image_aspect,
                                                     vec2(global.LED1_X, global.LED1_Y), global.LED1_HUE, global.LED1_SIZE, global.LED1_ASPECT,
                                                     global.LED1_SHAPE, 1.0, global.LED1_EXPOSURE, false );
            #endif

            #ifdef USE_LED2
                if (global.LED2_SIZE != 0.0)
                pixel_out.rgb = apply_power_led( vec4(pixel_out.rgb, pixel_fg_image_alpha),  pixel_fg_image_no_night, image_coords, image_aspect,
                                                 vec2(global.LED2_X, global.LED2_Y), global.LED2_HUE, global.LED2_SIZE, global.LED2_ASPECT,
                                                 global.LED2_SHAPE, 1.0, global.LED2_EXPOSURE, false );
            #endif

            #ifdef USE_LED3
                if (global.LED3_SIZE != 0.0) {
                    float led_controller = 1.0; // Init var since denise can "power on" leds on drive access
                    bool source_led_is_off = false;    // and in that case the led on the source image is off.
                    #ifdef _USE_DENISE_EMU
                        led_controller = global.LedDriveState; //under Denise led3 blinks on drive access.
                        source_led_is_off = true;
                    #endif
                    #ifdef _DEBUG_DENISE_EMU
                        float t = 10000.0; //timespan
                        float f = 5.0; //frequency
                        led_controller = random_fast(vec2(   float(floor(mod(params.FrameCount,t)/t*(t/f))/t)     ));
                        //float(mod(params.FrameCount,100)>75);
                        source_led_is_off = true;
                    #endif
                    pixel_out.rgb = apply_power_led( vec4(pixel_out.rgb, pixel_fg_image_alpha),  pixel_fg_image_no_night, image_coords, image_aspect,
                                                     vec2(global.LED3_X, global.LED3_Y), global.LED3_HUE, global.LED3_SIZE, global.LED3_ASPECT,
                                                     global.LED3_SHAPE, led_controller, global.LED3_EXPOSURE, source_led_is_off );
                }
            #endif
        }
    }

    
   // Backdrop
   if ( (1-BACKDROP_JUST_TUBE) + DO_BACKDROP > 1.0 ) {
        vec3 backdrop_img = pixel_backdrop_image(backdrop, vUnrotated_Output_coords, global.FinalViewportSize );
        pixel_out += backdrop_img * (1-pixel_fg_image_alpha);
    }
    
    
    #ifndef RENDER_BOGUS_FRAMES
        // Some cores set first frame size to 1x1 which may cause glitches.
        // This has less performance impact if kept here, at the end of the shader.
        if (params.OriginalSize.x < 4) {
            FragColor.rgb = vec3(0.0); //float(params.FrameCount > 1.0);
            return;
        }
    #endif

    // Debug functions:
        #ifdef DEBUG_QUAD_SPLIT
            if ( vOutputCoord.x < 0.5  && vOutputCoord.y > 0.5 || vOutputCoord.x > 0.5  && vOutputCoord.y < 0.5 )
            pixel_out = texture(DEBUG_TEXTURE,vOutputCoord).rgb;
        #endif

        #ifdef DEBUG_DUAL_SPLIT_Y
            if (vTexCoord.y < 0.5) pixel_out = texture(DEBUG_TEXTURE,vTexCoord).rgb;
        #endif

        #ifdef DEBUG_DUAL_SPLIT_Y_GAMMA
            if (vTexCoord.y < 0.5) pixel_out = pow(texture(DEBUG_TEXTURE,vTexCoord).rgb, vec3(1.08));
        #endif

        #ifdef DEBUG_DUAL_SPLIT_X
            if (vTexCoord.x > 0.5) pixel_out = texture(DEBUG_TEXTURE,vTexCoord).rgb;
        #endif

        #ifdef DEBUG_DUAL_SPLIT_MIRROR_X
            if (vTexCoord.x > 0.5) pixel_out = texture(DEBUG_TEXTURE,vec2(1-vTexCoord.x, vTexCoord.y) ).rgb;
        #endif

        #ifdef DEBUG_DUAL_SPLIT_MIRROR_X_GAMMA
            if (vTexCoord.x > 0.5) pixel_out = pow(texture(DEBUG_TEXTURE,vec2(1-vTexCoord.x, vTexCoord.y) ).rgb, vec3(1.11));
        #endif

        #ifdef DEBUG_PRINT_VALUE
            float maxdigits = 10.0;
            float decimalplaces = 2.0;
            #ifdef PRINTED_VALUE
                float fvalue = PRINTED_VALUE;
            #else
                float fvalue = vfprintvalue;
            #endif
            vec2 vFragCoord = vec2( floor(vOutputCoord.x * params.OutputSize.x),
                                floor(vOutputCoord.y * params.OutputSize.y));
            pixel_out += PrintValueVec3( vTexCoord, vFragCoord, fvalue, maxdigits, decimalplaces );
        #endif

        #ifdef DEBUG_SHOW_CLIP
            float clip = max(max(pixel_out.r, pixel_out.g), pixel_out.b) ;
            if (clip  > 1.0)
                pixel_out = 1-pixel_out;//;-clip;
        #endif
    
    FragColor.rgb = pixel_out;

}
