uniform_float uGamma {
    default = 2.2;
    min = 0.1;
    max = 4.0;
    step = 0.01;
    display_name = "#{BuiltInShaders:GammaLevelName}";
    description = "#{BuiltInShaders:GammaLevelDescription}";
}
uniform_float uThreshold {
    default = 0.35;
    min = 0.0;
    max = 1.0;
    step = 0.01;
    description = "#{BuiltInShaders:ThresholdLevelName}";
    display_name = "#{BuiltInShaders:ThresholdLevelDescription}";
}
uniform_float uClamp {
    default = 1.0;
    min = 0.0;
    max = 1.0;
    step = 0.01;
    description = "#{BuiltInShaders:BloomClampLevelName}";
    display_name = "#{BuiltInShaders:BloomClampLevelDescription}";
}
uniform_float uSkyFactor {
    default = 0.5;
    min = 0.0;
    max = 2.0;
    step = 0.01;
    description = "#{BuiltInShaders:SkyFactorLevelName}";
    display_name = "#{BuiltInShaders:SkyFactorLevelDescription}";
}
uniform_float uRadius {
    default = 0.5;
    min = 0.0;
    max = 1.0;
    step = 0.01;
    description = "#{BuiltInShaders:RadiusLevelName}";
    display_name = "#{BuiltInShaders:RadiusLevelDescription}";
}
uniform_float uStrength {
    default = 0.25;
    min = 0.0;
    max = 1.0;
    step = 0.01;
    display_name = "#{BuiltInShaders:StrengthLevelName}";
    description = "#{BuiltInShaders:StrengthLevelDescription}";
}

shared {
    float scramblify(float x)
    {
        x = fract(x);
        x = x + 4.0;
        x = x*x;
        x = x*x;
        return fract(x);
    }
    float scramblev2_inner(vec2 v, float z)
    {
        return scramblify(v.x*0.6491 + v.y*0.029 + z);
    }

    float time = fract(omw.simulationTime);
    vec4 scramblev2(vec2 v)
    {
        v *= 61.12;
        vec2 fwup = vec2(scramblev2_inner(v, fract(time)), scramblev2_inner(v, fract(time*fract(v.x)) + 0.18943));
        return vec4(0.5) - vec4(fwup, fwup);
    }
    
    float gauss(float x)
    {
        return exp(-x*x);
    }
    float calculate_radius(vec2 texcoord)
    {
        float radius = uRadius * 0.2;
        radius *= omw.resolution.y;
        radius = max(radius, 0.1);
        // hack: make the radius wider on the screen edges
        // (makes things in the corner of the screen look less "wrong" with not-extremely-low FOVs)
        radius *= pow(texcoord.x*2.0-1.0, 2)+1.0;
        radius *= pow(texcoord.y*2.0-1.0, 2)+1.0;
        return radius;
    }
    vec3 powv(vec3 a, float x)
    {
        return pow(a, vec3(x));
    }
}

render_target RT_NoMipmap {
    width_ratio = 0.25;
    height_ratio = 0.25;
    internal_format = rgb16f;
    source_type = float;
    source_format = rgb;
    mipmaps = false;
    min_filter = nearest;
    mag_filter = nearest;
}

render_target RT_Horizontal {
    width_ratio = 0.25;
    height_ratio = 0.25;
    internal_format = rgb16f;
    source_type = float;
    source_format = rgb;
    mipmaps = false;
    min_filter = nearest;
    mag_filter = nearest;
}

render_target RT_Vertical {
    width_ratio = 0.25;
    height_ratio = 0.25;
    internal_format = rgb16f;
    source_type = float;
    source_format = rgb;
    mipmaps = false;
    min_filter = linear;
    mag_filter = linear;
}

fragment nomipmap(target=RT_NoMipmap) {
    omw_In vec2 omw_TexCoord;

    void main()
    {
        // downsample into a smaller buffer with mipmapping disabled (no need for it + might interfere with the blurring process)
        // do the gamma compression step while we're at it
        vec3 sample = omw_GetLastShader(omw_TexCoord).rgb;
        bool is_sky = (omw_GetLinearDepth(omw_TexCoord) > omw.far*0.999);
        float factor = is_sky ? uSkyFactor : 1.0;
        vec3 ret_sample = powv(sample, uGamma);
        factor *= (!is_sky && (dot(sample, vec3(1.0/3.0)) < uThreshold)) ? 0.0 : 1.0;
        ret_sample *= factor;
        omw_FragColor = vec4(ret_sample, 1.0);
    }
}

fragment horizontal(target=RT_Horizontal, rt1=RT_NoMipmap) {
    omw_In vec2 omw_TexCoord;

    void main()
    {
        // gaussian blur, horizontal step
        float radius = calculate_radius(omw_TexCoord);
        int radius_i = int(ceil(radius));
        vec3 sum = vec3(0.0);
        float normalize = 0.0;
        for(int x = -radius_i; x <= radius_i; x += 1)
        {
            float strength = gauss(float(x)/radius*2.0);
            normalize += strength;
            vec2 coord = omw_TexCoord + vec2(float(x), 0.0) / omw.resolution.xy;
            vec3 sample = omw_Texture2D(RT_NoMipmap, coord).rgb;
            sum += strength * sample;
        }
        sum /= normalize;

        omw_FragColor = vec4(sum, 1.0);
    }
}

fragment vertical(target=RT_Vertical, rt1=RT_Horizontal) {
    omw_In vec2 omw_TexCoord;

    void main()
    {
        // gaussian blur, vertical step
        float radius = calculate_radius(omw_TexCoord);
        int radius_i = int(ceil(radius));
        vec3 sum = vec3(0.0);
        float normalize = 0.0;
        for(int y = -radius_i; y <= radius_i; y += 1)
        {
            float strength = gauss(float(y)/radius*2.0);
            normalize += strength;
            vec2 coord = omw_TexCoord + vec2(0.0, float(y)) / omw.resolution.xy;
            vec3 sample = omw_Texture2D(RT_Horizontal, coord).rgb;
            sum += strength * sample;
        }
        sum /= normalize;
        
        omw_FragColor = vec4(sum, 1.0);
    }
}

fragment final(rt1=RT_Vertical) {
    omw_In vec2 omw_TexCoord;

    void main()
    {
        vec3 color = omw_Texture2D(RT_Vertical, omw_TexCoord).rgb;
        // add dithering (in gamma-compressed light, because monitors are sRGB)
        color = powv(color, 1.0/uGamma);
        color += (scramblev2(omw_TexCoord).rgb/255.0 - vec3(0.5/255.0))*2.0;
        color = max(vec3(0.0), color);
        // also do clamping in gamma-compressed light
        float color_luma = dot(color, vec3(1.0/3.0));
        if(uClamp == 0.0)
            color *= 0.0;
        else if(color_luma > uClamp)
            color /= color_luma/uClamp;
        color = powv(color, uGamma);
        
        // add bloom to base color in linear light
        vec3 base_color = powv(omw_GetLastShader(omw_TexCoord).rgb, uGamma);
        vec3 add_color = base_color + color * uStrength * 0.5;
        omw_FragColor = vec4(powv(add_color, 1.0/uGamma), 1.0);
    }
}

technique {
    passes = nomipmap, horizontal, vertical, final;
    description = "#{BuiltInShaders:BloomDescription}";
    author = "OpenMW";
    version = "1.0";
}