#ifndef SSPT_GLSL
    #define SSPT_GLSL a

    vec3 cosineHemisphereSample(vec3 N, vec2 rnd) {
        float r = sqrt(rnd.x);
        float theta = 2.0 * PI * rnd.y;

        vec3 up = abs(N.y) < 0.999 ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0);
        vec3 T = normalize(cross(up, N));
        vec3 B = cross(N, T);

        return normalize(T * (r * cos(theta)) + B * (r * sin(theta)) + N * sqrt(max0(1.0 - rnd.x)));
    }

    vec3 mainRender(vec3 viewPos, vec3 worldNormal, vec3 albedo) {
        vec2 rnd = vec2(whiteNoise, RandWellons());
        vec3 rayDirWorld = cosineHemisphereSample(worldNormal, rnd);
        vec3 rayDirView = normalize(mat3(gbufferModelView) * rayDirWorld);

        vec2 hitScreenPos = vec2(0.0);
        bool isHit = false;
        screenRayTracingDDA(viewPos, rayDirView, hitScreenPos, isHit);

        vec3 radiance = vec3(0.0);
        if(isHit) {
            vec2 prevUV = getPreCoord(hitScreenPos);
            radiance = texture(colortex7, outScreen(prevUV) ? hitScreenPos : prevUV).rgb;
        }

        return radiance * albedo;
    }

    vec3 sspt(vec3 viewPos, vec3 worldPos, vec3 worldNormal, vec3 albedo, out vec4 historyOut) {
        vec3 newSample = mainRender(viewPos, worldNormal, albedo);

        vec3 preScreenPos = getPreScreenPos(worldPos);

        vec3 result = newSample;
        float count = 1.0;
        if(inScreen(preScreenPos.xy)) {
            vec4 history = texture(colortex11, preScreenPos.xy);
            count = min(history.a + 1.0, SSPT_MAX_FRAMES);
            result = mix(history.rgb, newSample, 1.0 / count);
        }

        historyOut = vec4(max0(result), count);
        return max0(result) * SSPT_INTENSITY;
    }

#endif