#version 400 compatibility

/*
====================================================================================================

    Copyright (C) 2020 RRe36

    All Rights Reserved unless otherwise explicitly stated.


    By downloading this you have agreed to the license and terms of use.
    These can be found inside the included license-file
    or here: https://rre36.com/copyright-license

    Violating these terms may be penalized with actions according to the Digital Millennium
    Copyright Act (DMCA), the Information Society Directive and/or similar laws
    depending on your country.

====================================================================================================
*/

/*DRAWBUFFERS:023*/
layout(location = 0) out vec4 sceneColor;
layout(location = 1) out vec4 sceneData1;
layout(location = 2) out vec4 sceneTranslucents;

#include "/lib/head.glsl"
#include "/lib/util/encoders.glsl"
#include "/lib/util/colorspace.glsl"

const int shadowMapResolution   = 2048;     //[512 1024 1536 2048 2560 3072 3584 4096 6144 8192 16384]
const float shadowDistance      = 128.0;

const bool shadowHardwareFiltering = false;

in vec2 coord;

flat in mat4x3 lightColor;

uniform sampler2D colortex0;
uniform sampler2D colortex1;
uniform sampler2D colortex2;
uniform sampler2D colortex3;
uniform sampler2D colortex6;

#ifdef rtaoEnabled
uniform sampler2D colortex7;
#endif

uniform sampler2D noisetex;

uniform sampler2D depthtex0;
uniform sampler2D depthtex2;

uniform sampler2D shadowtex0;
uniform sampler2D shadowtex1;
uniform sampler2D shadowcolor0;

uniform int frameCounter;

uniform float aspectRatio;
uniform float far, near;
uniform float lightFlip;
uniform float sunAngle;
uniform float viewHeight, viewWidth;

uniform vec2 taaOffset;
uniform vec2 viewSize, pixelSize;

uniform vec3 lightvec, lightvecView;
uniform vec3 upvec, upvecView;

uniform mat4 gbufferModelView, gbufferModelViewInverse;
uniform mat4 gbufferProjection, gbufferProjectionInverse;
uniform mat4 shadowModelView, shadowModelViewInverse;
uniform mat4 shadowProjection, shadowProjectionInverse;


/* ------ includes ------ */
#include "/lib/util/transforms.glsl"

#define FUTIL_LINDEPTH
#define FUTIL_MAT16
#define FUTIL_LIGHTMAP
#include "/lib/fUtil.glsl"

#define ATROUS_POSTRT
#include "/lib/util/kernel.glsl"
#include "/lib/light/atrousLightmap.glsl"

#include "/lib/light/diffuse.glsl"

#include "/lib/light/warp.glsl"

#include "/lib/frag/bluenoise.glsl"
#include "/lib/frag/gradnoise.glsl"

#define SOLIDPASS

#include "/lib/light/shadow.glsl"

#include "/lib/atmos/phase.glsl"

#include "/lib/light/subsurface.glsl"

#include "/lib/light/contactShadow.glsl"

#include "/lib/light/brdf.glsl"

vec4 textureBilateral(sampler2D tex, sampler2D depth, const int lod, float fdepth) {
    vec4 data   = vec4(0.0);
    float sum   = 0.0;
    ivec2 posD  = ivec2(coord*viewSize);
    ivec2 posT  = ivec2(coord*viewSize*rcp(float(lod)));
    vec3 zmult  = vec3((far*near)*2.0, far+near, far-near);
        fdepth  = depthLinear(fdepth);
    
    for (int i = -1; i<2; i++) {
        for (int j = -1; j<2; j++) {
            ivec2 tcDepth = posD + ivec2(i, j)*lod;
            float dsample = depthLinear(texelFetch(depth, tcDepth, 0).x);
            float w     = abs(dsample-fdepth)*zmult.x<1.0 ? 1.0 : 1e-5;
            ivec2 ct    = posT + ivec2(i, j);
            data       += texelFetch(tex, ct, 0)*w;
            sum        += w;
        }
    }
    data *= rcp(sum);

    return data;
}

float bayer2e(vec2 a){
    a = floor(a);
    return fract( dot(a, vec2(.5, a.y * .75)) );
}
#define bayer4e(a)   (bayer2e( .5*(a))*.25+bayer2e(a))

#define m vec3(31,63,31)
float encode3x8(vec3 a){
    float dither = bayer4e(gl_FragCoord.xy);
    a += (dither-.5) / m;
    a = saturate(a);
    ivec3 b = ivec3(a*m);
    return float( b.r|(b.g<<5)|(b.b<<11) ) / 65535.;
}
#undef m

vec2 decodeLabBlue(float data) {
    data *= 255.0;

    float opacity    = linStep(data, 65.0, 255.0);
    float porosity   = data <= (64.5) ? linStep(data, 0.0, 64.0) : 0.0;

    return vec2(opacity, porosity);
}

vec3 fauxPorosity(vec3 albedo, float wetness, float porosity) {
    //wetness = 1.0;
    vec3 wetAlbedo      = colorSaturation(mix(albedo * sqrt(albedo), sqr(albedo), getLuma(albedo)), 0.85);
        //wetAlbedo       = albedo;
    float frcBounced    = 0.7 * porosity;
        wetAlbedo      = (1.0 - frcBounced) * wetAlbedo / (1.0 - frcBounced * wetAlbedo);

    return mix(albedo, wetAlbedo, wetness);
}

float readCloudShadowmap(sampler2D shadowmap, vec3 position) {
    position    = mat3(shadowModelView) * position;
    position   /= cloudShadowmapRenderDistance;
    position.xy = position.xy * 0.5 + 0.5;

    position.xy /= max(viewSize / cloudShadowmapResolution, 1.0);

    return texture(shadowmap, position.xy).a;
}

void main() {
    sceneColor          = stex(colortex0);
    float sceneDepth    = stex(depthtex0).x;

    vec4 tex2           = stex(colortex2);
    vec4 materialData   = vec4(decode2x8(tex2.y), decode2x8(tex2.z));
        tex2.y          = encode3x8(sqrt(saturate(sceneColor.rgb)));

    vec3 albedo     = sceneColor.rgb;

    #if DEBUG_VIEW!=4
    if (landMask(sceneDepth)) {
        //vec3 albedo     = sceneColor.rgb;
        vec4 tex1       = stex(colortex1);
        vec2 sceneLmap  = decode2x8(tex1.z);
        vec3 sceneNormal = decodeNormal(tex1.xy);
        vec3 viewNormal = mat3(gbufferModelView) * sceneNormal;

        int matID       = decodeMatID16(tex2.x);

        //#if DEBUG_VIEW==1
        //sceneColor.rgb    = vec3(0.25);
        //#endif

        vec2 materialAux = decodeLabBlue(materialData.w);

        #ifndef resourcepackPorosity
            materialAux.y = 0.66;
        #endif

        #ifndef resourcepackSSS
            materialAux.x = 0.0;
        #endif

        sceneColor.rgb  = fauxPorosity(sceneColor.rgb, materialData.b, materialAux.y);

        vec3 viewPos    = screenToViewSpace(vec3(coord, sceneDepth));
        vec3 viewDir    = normalize(viewPos);
        vec3 scenePos   = viewToSceneSpace(viewPos);

        float rtao      = 1.0;

        #ifdef lightmapSmoothingEnabled
        vec4 tex3       = lightmap3x3(colortex3, depthtex0, colortex6, coord, 1);
            sceneLmap   = tex3.xy;
            sceneColor.a = tex3.z;
            rtao        = tex3.a;
        #else
            rtao        = stex(colortex3).a;
        #endif

        #ifndef rtaoEnabled
            rtao    = 1.0;
        #endif

        float ao        = sqr(sceneColor.a)*0.66+0.34;

        #ifdef ambientOcclusionToggle
            /* -- */
        #endif
        
        #if (defined ambientOcclusionToggle && !defined rtaoEnabled) 
            vec4 upscale3 = textureBilateral(colortex3, depthtex0, 2, sceneDepth);
                ao   *= upscale3.w;
        #endif
        
        /* ------ lighting calculation ------ */
        //~6 fps
        
        vec2 lmap       = sceneLmap;
        lmap.y          = cube(lmap.y);

        #if diffuseModel == 0
        float diff      = diffuseHammon(viewNormal, -viewDir, lightvecView, 1.0);
        #else
        float diff      = diffuseLambert(sceneNormal, lightvec);
        #endif

        #ifndef subsurfaceScatteringEnabled
            if (matID == 2) diff = diff*0.3+0.7 / pi;
            if (matID == 4) diff = sqrt(diff);
        #endif

        shadowData shadow = shadowData(1.0, vec3(1.0), vec3(0.0));

        #ifdef subsurfaceScatteringEnabled
            bool isSSS = matID == 2 || matID == 4;
            if (isSSS || materialAux.x > 0.002) shadow = getShadowSubsurface(diff>0.0, scenePos, normalize(viewPos), sceneColor.rgb, max(float(isSSS), materialAux.x));
            else shadow = getShadowRegular(diff>0.0, scenePos);    //~3fps, almost for free
        #else
            shadow = getShadowRegular(diff>0.0, scenePos);    //~3fps, almost for free
        #endif

        float diffLit       = min(diff, shadow.shadow);

        #ifdef contactShadowsEnabled
            if (diffLit > 0.0) diffLit *= getContactShadow(depthtex0, viewPos, ditherBluenoise(), sceneDepth, dot(viewNormal, viewDir), lightvecView);
            //if (diffLit > 0.0) diffLit *= getRTShadow(viewPos, ditherBluenoise());
        #endif

        #ifdef cloudShadowsEnabled
        float cloudShadows  = readCloudShadowmap(colortex6, scenePos);
        #else
        const float cloudShadows = 1.0;
        #endif

        vec3 directCol1     = (sunAngle<0.5 ? lightColor[0] : lightColor[2]) * lightFlip;
        vec3 directCol      = directCol1 * cloudShadows;
        vec3 directLight    = diffLit * shadow.color * directCol;
        vec3 specDirectLight = directLight;

        materialProperties material     = decodeLabBasic(materialData.xy);

        #ifdef subsurfaceScatteringEnabled
            directLight    += shadow.subsurfaceScatter * directCol;
        #endif

        vec2 indirectOcclusion  = vec2(sqr(lmap.y), lmap.y);

        #ifdef RTAO_NOSKYMAP
            indirectOcclusion   = vec2(1.0);
        #endif

        #if (defined ambientOcclusionToggle && defined rtaoEnabled) 
            indirectOcclusion   = mix(vec2(0.0), indirectOcclusion, vec2(rtao, sqrt(rtao)));
        #endif

        #ifdef lightleakWorkaroundToggle
            directLight    *= sstep(sceneLmap.y, 0.1, 0.2);
        #endif

        float nDotUp    = dot(vec3(0.0, 1.0, 0.0), sceneNormal);
        float skylightLambert   = saturate(nDotUp + 1.0) * 0.7 + 0.3;
        float diffuseBounceLambert = saturate(-nDotUp * 0.5 + 0.5) * 0.36 + 0.6;

        vec3 indirectLight  = lightColor[1] * skylightLambert;
            indirectLight  += directCol1 * 0.03;
            indirectLight  *= indirectOcclusion.x;
            indirectLight  += indirectOcclusion.y * directCol1 * diffuseBounceLambert * (0.06 + (1.0 - cloudShadows) * 0.05);
            indirectLight  += vec3(0.5, 0.7, 1.0) * (0.08 * minLightLuma) * rtao;

        #ifndef rtaoEnabled
            indirectLight  *= ao;
        #endif

        #ifdef rtaoEnabled
            ao  *= rtao * 0.66 + 0.34;
        #endif

        vec3 result     = directLight + indirectLight;
            result     += getBlocklightMap(lightColor[3], lmap.x) * ao;

        #ifdef emitterBoostEnabled
        if (matID == 5) {
            bool hand       = stex(depthtex0).x < stex(depthtex2).x;
            float albedoLum     = getLuma(sceneColor.rgb);
            vec3 emitterCol     = mix(lightColor[3], vec3(v3avg(lightColor[3])), emitterRecoloring);
                //emitterCol     *= cubeSmooth(saturate(v3avg(sceneColor.rgb) * 1.5));
                emitterCol    *= saturate(mix(cube(albedoLum) * normalize(sceneColor.rgb), sceneColor.rgb, albedoLum));
            result += emitterCol * pi * (1.0 - float(hand));
        }
        #endif

        sceneColor.rgb *= result;

        #if DEBUG_VIEW == 1
            sceneColor.rgb = 0.5 * result;
        #endif

        #ifdef specularBRDFEnabled
        if (diffLit > 0.0) {
            vec3 brdf   = BRDF(-viewDir, lightvecView, viewNormal, material, albedo) * specDirectLight;

            #ifdef lightleakWorkaroundToggle
                brdf   *= sstep(sceneLmap.y, 0.1, 0.2);
            #endif

            //vec3 fresnel = BRDFfresnel(-viewDir, viewNormal, material);
            sceneColor.rgb = sceneColor.rgb + brdf;
            //sceneColor.rgb *= fresnel;
        }
        #endif

        #ifdef deffNAN
        if (isnan3(sceneColor.rgb) || isinf3(sceneColor.rgb)) sceneColor.g = 100.0;
        #endif

        //sceneColor.rgb = vec3(indirectOcclusion) * directCol * 0.5;

        //sceneColor.rgb    = shadow.subsurfaceScatter;
        //sceneColor.rgb  = vec3(getContactShadow(depthtex0, viewPos, ditherBluenoise(), sceneDepth, dot(viewNormal, viewDir)));

        #if DEBUG_VIEW==2
            sceneColor.rgb    = vec3(indirectOcclusion.x) * (sunAngle<0.5 ? lightColor[0] : lightColor[2]);

            #ifdef DEBUG_WITH_ALBEDO
                sceneColor.rgb *= albedo;
            #endif
        #endif

        #if DEBUG_VIEW==3
            sceneColor.rgb    = indirectLight;

            #ifdef DEBUG_WITH_ALBEDO
                sceneColor.rgb *= albedo;
            #endif
        #endif

        //sceneColor.rgb  = vec3(sceneDepth);
    }
    #endif

    sceneColor  = makeDrawbuffer(sceneColor);
    sceneData1  = clampDrawbuffer(tex2);
    sceneTranslucents  = vec4(0.0);
}