#ifndef INCLUDE_TEXTURE_SAMPLING
    #define INCLUDE_TEXTURE_SAMPLING
    
    #include "/include/checker.glsl"

    #ifdef INDIRECT_LIGHTING_RES
        vec4 sampleHistory (in sampler2D tex, in vec3 currPos, in vec3 normal, in vec2 uv, in vec2 texSize)
        {   
            vec2 texel = ((uv * texSize
                #if INDIRECT_LIGHTING_RES == 2
                    - checker2x2(frameCounter - 1)
                #elif INDIRECT_LIGHTING_RES == 4
                    - checker4x4(frameCounter - 1)
                #endif
            ) - 0.5) * rcp(INDIRECT_LIGHTING_RES);

            vec4 samples = vec4(0.0);
            float weights = 0.0;

            for (int i = 0; i < 4; i++) {
                ivec2 offset = ivec2(i & 1, i >> 1);
                ivec2 sampleCoord = clamp(ivec2(texel) + offset, ivec2(0), ivec2(floor(internalScreenSize * rcp(INDIRECT_LIGHTING_RES))) - 1);
                ivec2 sampleTexel = clamp(sampleCoord * INDIRECT_LIGHTING_RES
                    #if INDIRECT_LIGHTING_RES == 2
                        + checker2x2(frameCounter - 1)
                    #elif INDIRECT_LIGHTING_RES == 4
                        + checker4x4(frameCounter - 1)
                    #endif
                , ivec2(0), ivec2(internalScreenSize) - 1);

                vec4 sampleData = texelFetch(tex, sampleCoord, 0);

                if (!any(isnan(sampleData)) && sampleData != vec4(0.0)) {
                    vec3 depth = normal * dot(normal, currPos + cameraVelocity);

                    float sampleWeight = exp(
                        -1024.0 * length(depth - texelFetch(colortex0, sampleTexel, 0).rgb) * inversesqrt(lengthSquared(depth))
                    )
                        * (1.0 - abs(fract(texel.x) - offset.x)) 
                        * (1.0 - abs(fract(texel.y) - offset.y));

                    samples += sampleWeight * sampleData;
                    weights += sampleWeight;
                }
            }

            if (weights > 0.0001 && !any(isnan(samples))) return samples / weights;
            else return vec4(0.0, 0.0, 0.0, 1.0);
        }

        vec3 upsampleRadiance (vec3 currPos, vec3 geoNormal, vec3 textureNormal)
        {   
            #if INDIRECT_LIGHTING_RES == 1
                return texelFetch(colortex2, ivec2(gl_FragCoord.xy), 0).rgb;
            #else
                vec2 uv = gl_FragCoord.xy * rcp(INDIRECT_LIGHTING_RES) - 0.5;
                vec3 radiance = vec3(0.0);
                float weights = 0.0;

                for (int i = 0; i < 4; i++) {
                    ivec2 offset = ivec2(i & 1, i >> 1);
                    ivec2 sampleCoord = ivec2(uv) + offset;
                    ivec2 texel = clamp(INDIRECT_LIGHTING_RES * sampleCoord 
                        #if INDIRECT_LIGHTING_RES == 2
                            + checker2x2(frameCounter)
                        #elif INDIRECT_LIGHTING_RES == 4
                            + checker4x4(frameCounter)
                        #endif
                    , ivec2(0), ivec2(internalScreenSize) - 1);

                    vec3 sampleNormal = octDecode(unpack2x8(texelFetch(colortex9, texel, 0).x & 65535u));
                    vec3 sampleData = texelFetch(colortex2, sampleCoord, 0).rgb;

                    float sampleWeight = exp(-1.0 * (
                        + 16.0 * abs(dot(currPos.xyz - screenToPlayerPos(vec3(vec2(texel + 0.5) * internalTexelSize, texelFetch(depthtex1, texel, 0).x)).xyz, geoNormal))
                        + 4.0  * (-dot(sampleNormal, textureNormal) * 0.5 + 0.5)
                    ))
                        * (1.0 - abs(fract(uv.x) - offset.x)) 
                        * (1.0 - abs(fract(uv.y) - offset.y));

                    radiance += sampleWeight * sampleData;
                    weights += sampleWeight;
                }

                if (weights > 0.0001 && !any(isnan(radiance))) return radiance / weights;
                else return vec3(0.0);
            #endif
        }
    #endif

    vec4 cubic (float v)
    {
        vec4 n = vec4(1.0, 2.0, 3.0, 4.0) - v;
        vec4 s = n * n * n;
        float x = s.x;
        float y = s.y - 4.0 * s.x;
        float z = s.z - 4.0 * s.y + 6.0 * s.x;
        float w = 6.0 - x - y - z;
        return vec4(x, y, z, w) * rcp(6.0);
    }

    vec4 texBicubic (sampler2D sampler, vec2 texCoords, vec2 texSize)
    {
        vec2 invTexSize = 1.0 / texSize;
   
        texCoords = texCoords * texSize - 0.5;

        vec2 fxy = fract(texCoords);
        texCoords -= fxy;

        vec4 xcubic = cubic(fxy.x);
        vec4 ycubic = cubic(fxy.y);

        vec4 c = texCoords.xxyy + vec2 (-0.5, +1.5).xyxy;
    
        vec4 s = vec4(xcubic.xz + xcubic.yw, ycubic.xz + ycubic.yw);
        vec4 offset = c + vec4 (xcubic.yw, ycubic.yw) / s;
    
        offset *= invTexSize.xxyy;
    
        vec4 sample0 = texture(sampler, offset.xz);
        vec4 sample1 = texture(sampler, offset.yz);
        vec4 sample2 = texture(sampler, offset.xw);
        vec4 sample3 = texture(sampler, offset.yw);

        float sx = s.x / (s.x + s.y);
        float sy = s.z / (s.z + s.w);

        return mix(mix(sample3, sample2, sx), mix(sample1, sample0, sx),sy);
    }

    // The following code is licensed under the MIT license: https://gist.github.com/TheRealMJP/bc503b0b87b643d3505d41eab8b332ae

    /*
        MIT License

        Copyright (c) 2019 MJP

        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:

        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.

        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
    */

    // Samples a texture with Catmull-Rom filtering, using 9 texture fetches instead of 16.
    // See http://vec3.ca/bicubic-filtering-in-fewer-taps/ for more details
    vec4 texCatmullRom (in sampler2D linearSampler, in vec2 uv, in vec2 texSize)
    {
        // We're going to sample a a 4x4 grid of texels surrounding the target UV coordinate. We'll do this by rounding
        // down the sample location to get the exact center of our "starting" texel. The starting texel will be at
        // location [1, 1] in the grid, where [0, 0] is the top left corner.
        vec2 samplePos = uv * texSize;
        vec2 texPos1 = floor(samplePos - 0.5) + 0.5;

        // Compute the fractional offset from our starting texel to our original sample location, which we'll
        // feed into the Catmull-Rom spline function to get our filter weights.
        vec2 f = samplePos - texPos1;

        // Compute the Catmull-Rom weights using the fractional offset that we calculated earlier.
        // These equations are pre-expanded based on our knowledge of where the texels will be located,
        // which lets us avoid having to evaluate a piece-wise function.
        vec2 w0 = f * (-0.5 + f * (1.0 - 0.5 * f));
        vec2 w1 = 1.0 + f * f * (-2.5 + 1.5 * f);
        vec2 w2 = f * (0.5 + f * (2.0 - 1.5 * f));
        vec2 w3 = f * f * (-0.5 + 0.5 * f);

        // Work out weighting factors and sampling offsets that will let us use bilinear filtering to
        // simultaneously evaluate the middle 2 samples from the 4x4 grid.
        vec2 w12 = w1 + w2;
        vec2 offset12 = w2 / (w1 + w2);

        // Compute the final UV coordinates we'll use for sampling the texture
        vec2 texPos0 = texPos1 - 1;
        vec2 texPos3 = texPos1 + 2;
        vec2 texPos12 = texPos1 + offset12;

        texPos0 /= texSize;
        texPos3 /= texSize;
        texPos12 /= texSize;

        vec4 result = vec4(0.0);
        result += textureLod(linearSampler, vec2(texPos0.x, texPos0.y), 0.0) * w0.x * w0.y;
        result += textureLod(linearSampler, vec2(texPos12.x, texPos0.y), 0.0) * w12.x * w0.y;
        result += textureLod(linearSampler, vec2(texPos3.x, texPos0.y), 0.0) * w3.x * w0.y;

        result += textureLod(linearSampler, vec2(texPos0.x, texPos12.y), 0.0) * w0.x * w12.y;
        result += textureLod(linearSampler, vec2(texPos12.x, texPos12.y), 0.0) * w12.x * w12.y;
        result += textureLod(linearSampler, vec2(texPos3.x, texPos12.y), 0.0) * w3.x * w12.y;

        result += textureLod(linearSampler, vec2(texPos0.x, texPos3.y), 0.0) * w0.x * w3.y;
        result += textureLod(linearSampler, vec2(texPos12.x, texPos3.y), 0.0) * w12.x * w3.y;
        result += textureLod(linearSampler, vec2(texPos3.x, texPos3.y), 0.0) * w3.x * w3.y;

        return result;
    }

#endif