Delete useless function

Delete useless function

#define EPSILON 0.00001

typedef struct PRNG {
    ulong m_seed[16];
    ulong m_p;
} PRNG;

typedef struct Camera {
    float3 m_pos;
    float3 m_dir;
    float3 m_world_up;
    float3 m_right;
    float3 m_up;
    float m_canvas_width;
    float m_canvas_height;
    float3 m_canvas_center_pos;
    float3 m_canvas_dir_x;
    float3 m_canvas_dir_y;
    float m_near_plane_dist;
    float m_far_plane_dist;
    int m_screen_width;
    int m_screen_height;
    float m_vertical_fov;
    float m_horizontal_fov;
} Camera;

typedef struct Material {
    float3 m_reflectance;
    float3 m_emittance;
} Material;

typedef struct Triangle {
    float3 m_v1;
    float3 m_v2;
    float3 m_v3;
    Material m_material;
    float3 m_normal;
    float3 m_centroid;
    float m_area;
} Triangle;

// typedef struct FlatStructure {
//     Triangle *m_triangles;
//     unsigned m_num_triangles;
// } FlatStructure;

typedef struct Ray {
    float3 m_origin;
    float3 m_dir;
    float3 m_invdir;
} Ray;

typedef struct IntersectionInfo {
    bool m_has_intersected;
    float3 m_pos;
    float3 m_normal;
    Ray m_incoming_ray;
    Material m_material;
} IntersectionInfo;

typedef struct AABB {
    float3 m_min;
    float3 m_max;
} AABB;

// From http://xorshift.di.unimi.it/xorshift1024star.c
inline ulong xorshift1024star(__global PRNG *prng) {
    ulong s0 = prng->m_seed[prng->m_p];
    ulong s1 = prng->m_seed[prng->m_p = (prng->m_p + 1) & 15];
    s1 ^= s1 << 31; // a
    s1 ^= s1 >> 11; // b
    s0 ^= s0 >> 30; // c
    return (prng->m_seed[prng->m_p] = s0 ^ s1) * 1181783497276652981L * get_global_id(0) * get_global_id(1);
}

inline float rand_range(__global PRNG *prng, const float min, const float max) {
    return min + ((float)xorshift1024star(prng)) / (float)(ULONG_MAX / (max - min));
}

inline float3 uniform_sample_sphere(__global PRNG *prng) {
    float3 rand_vec = (float3)(rand_range(prng, -1.f, 1.f), rand_range(prng, -1.f, 1.f),
                               rand_range(prng, -1.f, 1.f));
    return normalize(rand_vec);
}

inline float3 uniform_sample_sphere2(__global PRNG *prng) {
    float s = rand_range(prng, 0.f, 1.f) * M_PI_F * 2.f;
    float t = rand_range(prng, -1.f, 1.f);
    float2 v = (float2)(sin(s), cos(s));
    v *= sqrt(1.f - t * t);
    return (float3)(v.x, v.y, t);
}

inline float3 oriented_hemisphere_sample(__global PRNG *prng, const float3 dir) {
    float3 v = uniform_sample_sphere(prng);
    return v * sign(dot(v, dir));
}

inline float3 reflect(float3 incident, float3 normal) {
    return incident - 2.f * normal * dot(normal, incident);
}

inline float3 refract(float3 incident, float3 normal, float eta) {
    float k = 1.f - eta * eta * (1.f - dot(normal, incident) * dot(normal, incident));
    if (k < 0.f)
        return (float3)(0.f);
    else
        return eta * incident - (eta * dot(normal, incident) + sqrt(k)) * normal;
}

inline bool AABB_is_null(const AABB *aabb) {
    bool min_null = length(aabb->m_min) == 0;
    bool max_null = length(aabb->m_max) == 0;
    return min_null && max_null;
}

inline float3 AABB_min(const AABB *aabb) {
    return aabb->m_min;
}

inline float3 AABB_max(const AABB *aabb) {
    return aabb->m_max;
}

inline float3 AABB_diagonal(const AABB *aabb) {
    return aabb->m_max - aabb->m_min;
}

inline float3 AABB_center(const AABB *aabb) {
    return aabb->m_min + (AABB_diagonal(aabb) * 0.5f);
}

// std_array<glm_vec3, 8> AABB_vertices(AABB &aabb) {
//     std_array<glm_vec3, 8> result;
//     result[0] = {aabb.m_min.x, aabb.m_min.y, aabb.m_min.z}; // lower left front
//     result[1] = {aabb.m_max.x, aabb.m_min.y, aabb.m_min.z}; // lower right front
//     result[2] = {aabb.m_min.x, aabb.m_max.y, aabb.m_min.z}; // upper left front
//     result[3] = {aabb.m_max.x, aabb.m_max.y, aabb.m_min.z}; // upper right front
//     result[4] = {aabb.m_min.x, aabb.m_min.y, aabb.m_max.z}; // lower left back
//     result[5] = {aabb.m_max.x, aabb.m_min.y, aabb.m_max.z}; // lower right back
//     result[6] = {aabb.m_min.x, aabb.m_max.y, aabb.m_max.z}; // upper left back
//     result[7] = {aabb.m_max.x, aabb.m_max.y, aabb.m_max.z}; // upper right back
//     return result;
// }

inline void AABB_extend(AABB *aabb, float3 point) {
    aabb->m_min = min(point, aabb->m_min);
    aabb->m_max = max(point, aabb->m_max);
}

inline bool AABB_overlaps(const AABB *first, const AABB *second) {
    return first->m_max.x > second->m_min.x && first->m_min.x < second->m_max.x &&
           first->m_max.y > second->m_min.y && first->m_min.y < second->m_max.y &&
           first->m_max.z > second->m_min.z && first->m_min.z < second->m_max.z;
}

inline void AABB_reset(AABB *aabb) {
    aabb->m_min = 0;
    aabb->m_max = 0;
}

inline float2 Camera_screenspace_to_camspace(__constant const Camera *camera, unsigned x,
                                             unsigned y) {
    float rel_x = -(x - camera->m_screen_width / 2.f) / camera->m_screen_width;
    float rel_y = -(y - camera->m_screen_height / 2.f) / camera->m_screen_height;
    return (float2)(rel_x, rel_y);
}

inline int2 Camera_camspace_to_screenspace(__constant const Camera *camera, int2 coords) {
    int screen_x = round(0.5f * (camera->m_screen_width - 2.f * camera->m_screen_width * coords.x));
    int screen_y =
        round(0.5f * (camera->m_screen_height - 2.f * camera->m_screen_height * coords.y));
    return (int2)(screen_x, screen_y);
}

inline float3 Camera_camspace_to_worldspace(__constant const Camera *camera, float2 rel_pos) {
    float3 worldspace = camera->m_canvas_center_pos + (rel_pos.x * camera->m_canvas_dir_x) +
                        (rel_pos.y * camera->m_canvas_dir_y);
    return worldspace;
}

// float2 Camera_worldspace_to_camspace(const Camera *camera, float3 world_pos_on_canvas) {
//     float3 canvas_center_to_point = world_pos_on_canvas - Camera_canvas_center_pos(camera);
//
//     // Manually calculate angle between the positive y-axis on the canvas and the world point
//     float3 ay = canvas_center_to_point;
//     float3 by = Camera_up(camera);
//     float3 cy = cross(ay, by);
//     float angle1y = atan(dot(ay, by), length(cy));
//
//     // Manually calculate angle between the positive x-axis on the canvas and the world point
//     float3 ax = canvas_center_to_point;
//     float3 bx = Camera_right(camera);
//     float3 cx = cross(ax, bx);
//     float angle1x = atan(length(cx), dot(ax, bx));
//
//     float angle2x = glm_pi<float>() - (glm_half_pi<float>() + angle1x);
//     float len = length(canvas_center_to_point);
//     float x = sin(angle2x) * len;
//     float y = sin(angle1y) * len;
//     float rel_x = -(2 * x) / Camera_canvas_width(camera);
//     float rel_y = -(2 * y) / Camera_canvas_height(camera);
//     return (float2)(rel_x, -rel_y);
// }

// glm_vec3 Camera_worldpoint_to_worldspace(const Camera *camera, glm_vec3 world_point) {
//     auto ray_to_cam = pos(camera) - world_point;
//     float dist = 0;
//     bool collided = glm_intersectRayPlane(world_point, glm_normalize(ray_to_cam),
//                                           canvas_center_pos(camera), dir(camera), dist);
//     if (collided) {
//         return world_point + glm_normalize(ray_to_cam) * dist;
//     } else {
//         return glm_vec3(0);
//     }
// }

// From
// http://www.scratchapixel.com/lessons/3d-basic-rendering/minimal-ray-tracer-rendering-simple-shapes/ray-box-intersection
inline bool intersect_ray_aabb(const Ray *ray, const AABB *aabb) {
    float tmin, tmax, tymin, tymax, tzmin, tzmax;

    float3 bounds[2];
    bounds[0] = AABB_min(aabb);
    bounds[1] = AABB_max(aabb);

    char3 sign;
    sign.x = (ray->m_invdir.x < 0);
    sign.y = (ray->m_invdir.y < 0);
    sign.z = (ray->m_invdir.z < 0);

    tmin = (bounds[sign.x].x - ray->m_origin.x) * ray->m_invdir.x;
    tmax = (bounds[1 - sign.x].x - ray->m_origin.x) * ray->m_invdir.x;
    tymin = (bounds[sign.y].y - ray->m_origin.y) * ray->m_invdir.y;
    tymax = (bounds[1 - sign.y].y - ray->m_origin.y) * ray->m_invdir.y;

    if ((tmin > tymax) || (tymin > tmax))
        return false;
    if (tymin > tmin)
        tmin = tymin;
    if (tymax < tmax)
        tmax = tymax;

    tzmin = (bounds[sign.z].z - ray->m_origin.z) * ray->m_invdir.z;
    tzmax = (bounds[1 - sign.z].z - ray->m_origin.z) * ray->m_invdir.z;

    if ((tmin > tzmax) || (tzmin > tmax))
        return false;
    if (tzmin > tmin)
        tmin = tzmin;
    if (tzmax < tmax)
        tmax = tzmax;

    return true;
}

// Möller-Trumbore intersection algorithm
// (see https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm)
inline bool intersect_ray_triangle(const Ray *ray, __constant const Triangle *triangle,
                                   float *dist) {
    // Calculate edges of triangle from v0.
    float3 e0 = triangle->m_v2 - triangle->m_v1;
    float3 e1 = triangle->m_v3 - triangle->m_v1;

    // Calculate determinant to check whether the ray is in the newly calculated plane made up from
    // e0 and e1.
    float3 pvec = cross(ray->m_dir, e1);
    float det = dot(e0, pvec);

    // Check whether determinant is close to 0. If that is the case, the ray is in the same plane as
    // the triangle itself which means that they can't collide. This effectively disables backface
    // culling for which we would instead only check whether det < epsilon.
    if (det > -EPSILON && det < EPSILON)
        return false;

    float inv_det = 1.f / det;

    // Calculate distance from v1 to ray origin
    float3 tvec = ray->m_origin - triangle->m_v1;

    // Calculate u parameter and test bound
    float u = dot(tvec, pvec) * inv_det;

    // Check whether the intersection lies outside of the triangle
    if (u < 0.f || u > 1.f)
        return false;

    // Prepare to test v parameter
    float3 qvec = cross(tvec, e0);

    // Calculate v parameter and test bound
    float v = dot(ray->m_dir, qvec) * inv_det;

    // Check whether the intersection lies outside of the triangle
    if (v < 0.f || u + v > 1.f)
        return false;

    float t = dot(e1, qvec) * inv_det;

    if (t > EPSILON) {
        *dist = t - EPSILON;
        return true;
    }

    // If we end up here, there was no hit
    return false;
}

inline IntersectionInfo Scene_intersect(__constant Triangle *accel_struct, const uint num_triangles,
                                        Ray *ray) {
    IntersectionInfo intersect_info; // TODO use proper constructor
    intersect_info.m_has_intersected = false;

    // Keep track of closest triangle
    float closest_dist = FLT_MAX;
    Triangle closest_triangle;
    // for (const auto &shape : FlatStructure::shapes(flatstruct)) {
    //     if (intersect_ray_aabb(ray, Shape::aabb(shape))) {
    //         for (auto &tri : Shape::triangles(shape)) {
    for (unsigned i = 0; i < num_triangles; i++) {
        float dist_to_intersect;
        __constant Triangle *tri = &(accel_struct[i]);
        bool intersected = intersect_ray_triangle(ray, tri, &dist_to_intersect);
        if (intersected) {
            // Find closest triangle
            if (dist_to_intersect < closest_dist) {
                closest_dist = dist_to_intersect;
                closest_triangle = *tri;

                intersect_info.m_has_intersected = true;
                intersect_info.m_pos = ray->m_origin + ray->m_dir * closest_dist;
                intersect_info.m_incoming_ray = *ray;
                intersect_info.m_normal = closest_triangle.m_normal;
                intersect_info.m_material = closest_triangle.m_material;
            }
        }
    }
    //     }
    // }

    return intersect_info;
}

__kernel void renderer_trace_pixel_color(__write_only __global float4 *output, const int width,
                                         const unsigned max_depth, __global PRNG *prng,
                                         __constant Camera *camera,
                                         __constant Triangle *accel_struct,
                                         const uint num_triangles) {
    int x = get_global_id(0);
    int y = get_global_id(1);
    int index = y * width + x;

    float2 rel_pos = Camera_screenspace_to_camspace(camera, x, y);
    float3 world_pos = Camera_camspace_to_worldspace(camera, rel_pos);
    float3 ray_dir = normalize(world_pos - camera->m_pos);

    float3 ret_color = (float3)(0.f);
    Ray next_ray = {world_pos, ray_dir}; // TODO Call constructor
    float3 brdf = (float3)(1.f);
    for (unsigned depth = 0; depth < max_depth; depth++) {
        IntersectionInfo intersect_info = Scene_intersect(accel_struct, num_triangles, &next_ray);
        if (intersect_info.m_has_intersected) {
            // ret_color = intersect_info.m_material.m_reflectance;
            // break;

            // Get the local radiance only on first bounce
            float3 local_radiance;
            // if (depth == 0) {
            // auto ray = ray_pos - impact_pos;
            // float dist2 = glm_dot(ray, ray);
            // auto cos_area = glm_dot(-ray_dir, tri->m_normal) * tri->m_area;
            // auto solid_angle = cos_area / glm_max(dist2, 1e-6f);
            //
            // if (cos_area > 0.0)
            //     local_radiance = tri->m_emittance * solid_angle;
            //     local_radiance = intersect_info.m_material.m_emittance;
            // }

            local_radiance = intersect_info.m_material.m_emittance;

            // Emitter sample
            // TODO
            // glm_vec3 illumination;

            float3 normal =
                intersect_info.m_normal *
                -sign(dot(intersect_info.m_normal, intersect_info.m_incoming_ray.m_dir));

            // Find new random direction for diffuse reflection
            float3 new_ray_dir = oriented_hemisphere_sample(prng, normal);

            // TODO: We already calculate this in `oriented_hemisphere_sample`, might be more
            // efficient to use the same dotproduct. Run benchmarks on this.
            float cos_theta = dot(new_ray_dir, normal);

            ret_color += brdf * local_radiance;
            brdf *= 2.f * intersect_info.m_material.m_reflectance * cos_theta;

            // Make a new ray
            next_ray.m_origin = intersect_info.m_pos;
            next_ray.m_dir = new_ray_dir;
        } else {
            break;
        }
    }

    output[index] = (float4)(ret_color.x, ret_color.y, ret_color.z, 1.f);
    // output[index] = (float4)(rand_range(prng, 0.f, 1.f), rand_range(prng, 0.f, 1.f),
    //                          rand_range(prng, 0.f, 1.f), 1.f);
}

#define EPSILON 0.00001

typedef struct PRNG {
    ulong m_seed[16];
    ulong m_p;
} PRNG;

typedef struct Camera {
    float3 m_pos;
    float3 m_dir;
    float3 m_world_up;
    float3 m_right;
    float3 m_up;
    float m_canvas_width;
    float m_canvas_height;
    float3 m_canvas_center_pos;
    float3 m_canvas_dir_x;
    float3 m_canvas_dir_y;
    float m_near_plane_dist;
    float m_far_plane_dist;
    int m_screen_width;
    int m_screen_height;
    float m_vertical_fov;
    float m_horizontal_fov;
} Camera;

typedef struct Material {
    float3 m_reflectance;
    float3 m_emittance;
} Material;

typedef struct Triangle {
    float3 m_v1;
    float3 m_v2;
    float3 m_v3;
    Material m_material;
    float3 m_normal;
    float3 m_centroid;
    float m_area;
} Triangle;

// typedef struct FlatStructure {
//     Triangle *m_triangles;
//     unsigned m_num_triangles;
// } FlatStructure;

typedef struct Ray {
    float3 m_origin;
    float3 m_dir;
    float3 m_invdir;
} Ray;

typedef struct IntersectionInfo {
    bool m_has_intersected;
    float3 m_pos;
    float3 m_normal;
    Ray m_incoming_ray;
    Material m_material;
} IntersectionInfo;

typedef struct AABB {
    float3 m_min;
    float3 m_max;
} AABB;

// From http://xorshift.di.unimi.it/xorshift1024star.c
inline ulong xorshift1024star(__global PRNG *prng) {
    ulong s0 = prng->m_seed[prng->m_p];
    ulong s1 = prng->m_seed[prng->m_p = (prng->m_p + 1) & 15];
    s1 ^= s1 << 31; // a
    s1 ^= s1 >> 11; // b
    s0 ^= s0 >> 30; // c
    return (prng->m_seed[prng->m_p] = s0 ^ s1) * 1181783497276652981L * get_global_id(0) * get_global_id(1);
}

inline float rand_range(__global PRNG *prng, const float min, const float max) {
    return min + ((float)xorshift1024star(prng)) / (float)(ULONG_MAX / (max - min));
}

inline float3 uniform_sample_sphere(__global PRNG *prng) {
    float3 rand_vec = (float3)(rand_range(prng, -1.f, 1.f), rand_range(prng, -1.f, 1.f),
                               rand_range(prng, -1.f, 1.f));
    return normalize(rand_vec);
}

inline float3 uniform_sample_sphere2(__global PRNG *prng) {
    float s = rand_range(prng, 0.f, 1.f) * M_PI_F * 2.f;
    float t = rand_range(prng, -1.f, 1.f);
    float2 v = (float2)(sin(s), cos(s));
    v *= sqrt(1.f - t * t);
    return (float3)(v.x, v.y, t);
}

inline float3 oriented_hemisphere_sample(__global PRNG *prng, const float3 dir) {
    float3 v = uniform_sample_sphere(prng);
    return v * sign(dot(v, dir));
}

inline float3 cosine_weighted_hemisphere(__global PRNG *prng) {
    float u1 = rand_range(prng, 0.f, 1.f);
    float u2 = rand_range(prng, 0.f, 1.f);
    const float r = sqrt(u1);
    const float theta = 2.f * M_PI * u2;

    const float x = r * cos(theta);
    const float y = r * sin(theta);

    return (float3)(x, y, sqrt(1.f - u1));
}

inline float3 reflect(float3 incident, float3 normal) {
    return incident - 2.f * normal * dot(normal, incident);
}

inline float3 refract(float3 incident, float3 normal, float eta) {
    float k = 1.f - eta * eta * (1.f - dot(normal, incident) * dot(normal, incident));
    if (k < 0.f)
        return (float3)(0.f);
    else
        return eta * incident - (eta * dot(normal, incident) + sqrt(k)) * normal;
}

inline bool AABB_is_null(const AABB *aabb) {
    bool min_null = length(aabb->m_min) == 0;
    bool max_null = length(aabb->m_max) == 0;
    return min_null && max_null;
}

inline float3 AABB_min(const AABB *aabb) {
    return aabb->m_min;
}

inline float3 AABB_max(const AABB *aabb) {
    return aabb->m_max;
}

inline float3 AABB_diagonal(const AABB *aabb) {
    return aabb->m_max - aabb->m_min;
}

inline float3 AABB_center(const AABB *aabb) {
    return aabb->m_min + (AABB_diagonal(aabb) * 0.5f);
}

// std_array<glm_vec3, 8> AABB_vertices(AABB &aabb) {
//     std_array<glm_vec3, 8> result;
//     result[0] = {aabb.m_min.x, aabb.m_min.y, aabb.m_min.z}; // lower left front
//     result[1] = {aabb.m_max.x, aabb.m_min.y, aabb.m_min.z}; // lower right front
//     result[2] = {aabb.m_min.x, aabb.m_max.y, aabb.m_min.z}; // upper left front
//     result[3] = {aabb.m_max.x, aabb.m_max.y, aabb.m_min.z}; // upper right front
//     result[4] = {aabb.m_min.x, aabb.m_min.y, aabb.m_max.z}; // lower left back
//     result[5] = {aabb.m_max.x, aabb.m_min.y, aabb.m_max.z}; // lower right back
//     result[6] = {aabb.m_min.x, aabb.m_max.y, aabb.m_max.z}; // upper left back
//     result[7] = {aabb.m_max.x, aabb.m_max.y, aabb.m_max.z}; // upper right back
//     return result;
// }

inline void AABB_extend(AABB *aabb, float3 point) {
    aabb->m_min = min(point, aabb->m_min);
    aabb->m_max = max(point, aabb->m_max);
}

inline bool AABB_overlaps(const AABB *first, const AABB *second) {
    return first->m_max.x > second->m_min.x && first->m_min.x < second->m_max.x &&
           first->m_max.y > second->m_min.y && first->m_min.y < second->m_max.y &&
           first->m_max.z > second->m_min.z && first->m_min.z < second->m_max.z;
}

inline void AABB_reset(AABB *aabb) {
    aabb->m_min = 0;
    aabb->m_max = 0;
}

inline float2 Camera_screenspace_to_camspace(__constant const Camera *camera, unsigned x,
                                             unsigned y) {
    float rel_x = -(x - camera->m_screen_width / 2.f) / camera->m_screen_width;
    float rel_y = -(y - camera->m_screen_height / 2.f) / camera->m_screen_height;
    return (float2)(rel_x, rel_y);
}

inline int2 Camera_camspace_to_screenspace(__constant const Camera *camera, int2 coords) {
    int screen_x = round(0.5f * (camera->m_screen_width - 2.f * camera->m_screen_width * coords.x));
    int screen_y =
        round(0.5f * (camera->m_screen_height - 2.f * camera->m_screen_height * coords.y));
    return (int2)(screen_x, screen_y);
}

inline float3 Camera_camspace_to_worldspace(__constant const Camera *camera, float2 rel_pos) {
    float3 worldspace = camera->m_canvas_center_pos + (rel_pos.x * camera->m_canvas_dir_x) +
                        (rel_pos.y * camera->m_canvas_dir_y);
    return worldspace;
}

// float2 Camera_worldspace_to_camspace(const Camera *camera, float3 world_pos_on_canvas) {
//     float3 canvas_center_to_point = world_pos_on_canvas - Camera_canvas_center_pos(camera);
//
//     // Manually calculate angle between the positive y-axis on the canvas and the world point
//     float3 ay = canvas_center_to_point;
//     float3 by = Camera_up(camera);
//     float3 cy = cross(ay, by);
//     float angle1y = atan(dot(ay, by), length(cy));
//
//     // Manually calculate angle between the positive x-axis on the canvas and the world point
//     float3 ax = canvas_center_to_point;
//     float3 bx = Camera_right(camera);
//     float3 cx = cross(ax, bx);
//     float angle1x = atan(length(cx), dot(ax, bx));
//
//     float angle2x = glm_pi<float>() - (glm_half_pi<float>() + angle1x);
//     float len = length(canvas_center_to_point);
//     float x = sin(angle2x) * len;
//     float y = sin(angle1y) * len;
//     float rel_x = -(2 * x) / Camera_canvas_width(camera);
//     float rel_y = -(2 * y) / Camera_canvas_height(camera);
//     return (float2)(rel_x, -rel_y);
// }

// glm_vec3 Camera_worldpoint_to_worldspace(const Camera *camera, glm_vec3 world_point) {
//     auto ray_to_cam = pos(camera) - world_point;
//     float dist = 0;
//     bool collided = glm_intersectRayPlane(world_point, glm_normalize(ray_to_cam),
//                                           canvas_center_pos(camera), dir(camera), dist);
//     if (collided) {
//         return world_point + glm_normalize(ray_to_cam) * dist;
//     } else {
//         return glm_vec3(0);
//     }
// }

// From
// http://www.scratchapixel.com/lessons/3d-basic-rendering/minimal-ray-tracer-rendering-simple-shapes/ray-box-intersection
inline bool intersect_ray_aabb(const Ray *ray, const AABB *aabb) {
    float tmin, tmax, tymin, tymax, tzmin, tzmax;

    float3 bounds[2];
    bounds[0] = AABB_min(aabb);
    bounds[1] = AABB_max(aabb);

    char3 sign;
    sign.x = (ray->m_invdir.x < 0);
    sign.y = (ray->m_invdir.y < 0);
    sign.z = (ray->m_invdir.z < 0);

    tmin = (bounds[sign.x].x - ray->m_origin.x) * ray->m_invdir.x;
    tmax = (bounds[1 - sign.x].x - ray->m_origin.x) * ray->m_invdir.x;
    tymin = (bounds[sign.y].y - ray->m_origin.y) * ray->m_invdir.y;
    tymax = (bounds[1 - sign.y].y - ray->m_origin.y) * ray->m_invdir.y;

    if ((tmin > tymax) || (tymin > tmax))
        return false;
    if (tymin > tmin)
        tmin = tymin;
    if (tymax < tmax)
        tmax = tymax;

    tzmin = (bounds[sign.z].z - ray->m_origin.z) * ray->m_invdir.z;
    tzmax = (bounds[1 - sign.z].z - ray->m_origin.z) * ray->m_invdir.z;

    if ((tmin > tzmax) || (tzmin > tmax))
        return false;
    if (tzmin > tmin)
        tmin = tzmin;
    if (tzmax < tmax)
        tmax = tzmax;

    return true;
}

// Möller-Trumbore intersection algorithm
// (see https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm)
inline bool intersect_ray_triangle(const Ray *ray, __constant const Triangle *triangle,
                                   float *dist) {
    // Calculate edges of triangle from v0.
    float3 e0 = triangle->m_v2 - triangle->m_v1;
    float3 e1 = triangle->m_v3 - triangle->m_v1;

    // Calculate determinant to check whether the ray is in the newly calculated plane made up from
    // e0 and e1.
    float3 pvec = cross(ray->m_dir, e1);
    float det = dot(e0, pvec);

    // Check whether determinant is close to 0. If that is the case, the ray is in the same plane as
    // the triangle itself which means that they can't collide. This effectively disables backface
    // culling for which we would instead only check whether det < epsilon.
    if (det > -EPSILON && det < EPSILON)
        return false;

    float inv_det = 1.f / det;

    // Calculate distance from v1 to ray origin
    float3 tvec = ray->m_origin - triangle->m_v1;

    // Calculate u parameter and test bound
    float u = dot(tvec, pvec) * inv_det;

    // Check whether the intersection lies outside of the triangle
    if (u < 0.f || u > 1.f)
        return false;

    // Prepare to test v parameter
    float3 qvec = cross(tvec, e0);

    // Calculate v parameter and test bound
    float v = dot(ray->m_dir, qvec) * inv_det;

    // Check whether the intersection lies outside of the triangle
    if (v < 0.f || u + v > 1.f)
        return false;

    float t = dot(e1, qvec) * inv_det;

    if (t > EPSILON) {
        *dist = t - EPSILON;
        return true;
    }

    // If we end up here, there was no hit
    return false;
}

inline IntersectionInfo Scene_intersect(__constant Triangle *accel_struct, const uint num_triangles,
                                        Ray *ray) {
    IntersectionInfo intersect_info; // TODO use proper constructor
    intersect_info.m_has_intersected = false;

    // Keep track of closest triangle
    float closest_dist = FLT_MAX;
    Triangle closest_triangle;
    // for (const auto &shape : FlatStructure::shapes(flatstruct)) {
    //     if (intersect_ray_aabb(ray, Shape::aabb(shape))) {
    //         for (auto &tri : Shape::triangles(shape)) {
    for (unsigned i = 0; i < num_triangles; i++) {
        float dist_to_intersect;
        __constant Triangle *tri = &(accel_struct[i]);
        bool intersected = intersect_ray_triangle(ray, tri, &dist_to_intersect);
        if (intersected) {
            // Find closest triangle
            if (dist_to_intersect < closest_dist) {
                closest_dist = dist_to_intersect;
                closest_triangle = *tri;

                intersect_info.m_has_intersected = true;
                intersect_info.m_pos = ray->m_origin + ray->m_dir * closest_dist;
                intersect_info.m_incoming_ray = *ray;
                intersect_info.m_normal = closest_triangle.m_normal;
                intersect_info.m_material = closest_triangle.m_material;
            }
        }
    }
    //     }
    // }

    return intersect_info;
}

__kernel void renderer_trace_pixel_color(__write_only __global float4 *output, const int width,
                                         const unsigned max_depth, __global PRNG *prng,
                                         __constant Camera *camera,
                                         __constant Triangle *accel_struct,
                                         const uint num_triangles) {
    int x = get_global_id(0);
    int y = get_global_id(1);
    int index = y * width + x;

    float2 rel_pos = Camera_screenspace_to_camspace(camera, x, y);
    float3 world_pos = Camera_camspace_to_worldspace(camera, rel_pos);
    float3 ray_dir = normalize(world_pos - camera->m_pos);

    float3 ret_color = (float3)(0.f);
    Ray next_ray = {world_pos, ray_dir}; // TODO Call constructor
    float3 brdf = (float3)(1.f);
    for (unsigned depth = 0; depth < max_depth; depth++) {
        IntersectionInfo intersect_info = Scene_intersect(accel_struct, num_triangles, &next_ray);
        if (intersect_info.m_has_intersected) {
            // ret_color = intersect_info.m_material.m_reflectance;
            // break;

            // Get the local radiance only on first bounce
            float3 local_radiance;
            // if (depth == 0) {
            // auto ray = ray_pos - impact_pos;
            // float dist2 = glm_dot(ray, ray);
            // auto cos_area = glm_dot(-ray_dir, tri->m_normal) * tri->m_area;
            // auto solid_angle = cos_area / glm_max(dist2, 1e-6f);
            //
            // if (cos_area > 0.0)
            //     local_radiance = tri->m_emittance * solid_angle;
            //     local_radiance = intersect_info.m_material.m_emittance;
            // }

            local_radiance = intersect_info.m_material.m_emittance;

            // Emitter sample
            // TODO
            // glm_vec3 illumination;

            float3 normal =
                intersect_info.m_normal *
                -sign(dot(intersect_info.m_normal, intersect_info.m_incoming_ray.m_dir));

            // Find new random direction for diffuse reflection
            float3 new_ray_dir = oriented_hemisphere_sample(prng, normal);

            // TODO: We already calculate this in `oriented_hemisphere_sample`, might be more
            // efficient to use the same dotproduct. Run benchmarks on this.
            float cos_theta = dot(new_ray_dir, normal);

            ret_color += brdf * local_radiance;
            brdf *= 2.f * intersect_info.m_material.m_reflectance * cos_theta;

            // Make a new ray
            next_ray.m_origin = intersect_info.m_pos;
            next_ray.m_dir = new_ray_dir;
        } else {
            break;
        }
    }

    output[index] = (float4)(ret_color.x, ret_color.y, ret_color.z, 1.f);
    // output[index] = (float4)(rand_range(prng, 0.f, 1.f), rand_range(prng, 0.f, 1.f),
    //                          rand_range(prng, 0.f, 1.f), 1.f);
}


Fix secondary emissive

Fix secondary emissive

/**********************************************************************
Copyright (c) 2016 Advanced Micro Devices, Inc. All rights reserved.

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.
********************************************************************/
#include <../App/CL/utils.cl>
#include <../App/CL/random.cl>
#include <../App/CL/payload.cl>
#include <../App/CL/texture.cl>
#include <../App/CL/sampling.cl>
#include <../App/CL/normalmap.cl>
#include <../App/CL/bxdf.cl>
#include <../App/CL/light.cl>
#include <../App/CL/camera.cl>
#include <../App/CL/scene.cl>
#include <../App/CL/material.cl>
#include <../App/CL/volumetrics.cl>
#include <../App/CL/path.cl>

#define CRAZY_LOW_THROUGHPUT 0.0f
#define CRAZY_HIGH_RADIANCE 10.f
#define CRAZY_HIGH_DISTANCE 1000000.f
#define CRAZY_LOW_DISTANCE 0.001f
#define REASONABLE_RADIANCE(x) (clamp((x), 0.f, CRAZY_HIGH_RADIANCE))
#define NON_BLACK(x) (length(x) > 0.f)

// This kernel only handles scattered paths.
// It applies direct illumination and generates
// path continuation if multiscattering is enabled.
__kernel void ShadeVolume(
	// Ray batch
	__global ray const* rays,
	// Intersection data
	__global Intersection const* isects,
	// Hit indices
	__global int const* hitindices,
	// Pixel indices
	__global int const* pixelindices,
	// Number of rays
	__global int const* numhits,
	// Vertices
	__global float3 const* vertices,
	// Normals
	__global float3 const* normals,
	// UVs
	__global float2 const* uvs,
	// Indices
	__global int const* indices,
	// Shapes
	__global Shape const* shapes,
	// Material IDs
	__global int const* materialids,
	// Materials
	__global Material const* materials,
	// Textures
	TEXTURE_ARG_LIST,
	// Environment texture index
	int envmapidx,
	// Envmap multiplier
	float envmapmul,
	// Emissives
	__global Emissive const* emissives,
	// Number of emissive objects
	int numemissives,
	// RNG seed
	int rngseed,
	// Sampler state
	__global SobolSampler* samplers,
	// Sobol matrices
	__global uint const* sobolmat,
	// Current bounce
	int bounce,
	// Volume data
	__global Volume const* volumes,
	// Shadow rays
	__global ray* shadowrays,
	// Light samples
	__global float3* lightsamples,
	// Path throughput
	__global Path* paths,
	// Indirect rays (next path segment)
	__global ray* indirectrays,
	// Radiance
	__global float3* output
	)
{
	int globalid = get_global_id(0);

	Scene scene =
	{
		vertices,
		normals,
		uvs,
		indices,
		shapes,
		materialids,
		materials,
		emissives,
		envmapidx,
		envmapmul,
		numemissives
	};

	if (globalid < *numhits)
	{
		// Fetch index
		int hitidx = hitindices[globalid];
		int pixelidx = pixelindices[globalid];
		Intersection isect = isects[hitidx];

		__global Path* path = paths + pixelidx;

		// Only apply to scattered paths
		if (!Path_IsScattered(path))
		{
			return;
		}

		// Fetch incoming ray
		float3 o = rays[hitidx].o.xyz;
		float3 wi = rays[hitidx].d.xyz;

#ifdef SOBOL
		__global SobolSampler* sampler = samplers + pixelidx;
		float sample0 = SobolSampler_Sample1D(sampler->seq, GetSampleDim(bounce, kVolumeLight), sampler->s0, sobolmat);
		float2 sample1;
		sample1.x = SobolSampler_Sample1D(sampler->seq, GetSampleDim(bounce, kVolumeLightU), sampler->s0, sobolmat);
		sample1.y = SobolSampler_Sample1D(sampler->seq, GetSampleDim(bounce, kVolumeLightV), sampler->s0, sobolmat);
#ifdef MULTISCATTER
        float2 sample2;
        sample2.x = SobolSampler_Sample1D(sampler->seq, GetSampleDim(bounce, kVolumeIndirectU), sampler->s0, sobolmat);
        sample2.y = SobolSampler_Sample1D(sampler->seq, GetSampleDim(bounce, kVolumeIndirectV), sampler->s0, sobolmat);
#endif
#else
		// Prepare RNG for sampling
		Rng rng;
		InitRng(rngseed + (globalid << 2) * 157 + 13, &rng);
		float sample0 = UniformSampler_Sample2D(&rng).x;
		float2 sample1 = UniformSampler_Sample2D(&rng);
#ifdef MULTISCATTER
        float2 sample2 = UniformSampler_Sample2D(&rng);
#endif
#endif
		// Here we know that volidx != -1 since this is a precondition
		// for scattering event
		int volidx = Path_GetVolumeIdx(path);

		// Sample light source
		float pdf = 0.f;
		float selection_pdf = 0.f;
		float3 wo;

		int lightidx = Scene_SampleLight(&scene, sample0, &selection_pdf);

		// Here we need fake differential geometry for light sampling procedure
		DifferentialGeometry dg;
		// put scattering position in there (it is along the current ray at isect.distance
		// since EvaluateVolume has put it there
		dg.p = o + wi * Intersection_GetDistance(isects + hitidx);
		// Get light sample intencity
		float3 le = Light_Sample(lightidx, &scene, &dg, TEXTURE_ARGS, sample1, &wo, &pdf);

		// Generate shadow ray
		float shadow_ray_length = 0.999f * length(wo);
		Ray_Init(shadowrays + globalid, dg.p, normalize(wo), shadow_ray_length, 0.f, 0xFFFFFFFF);

		// Evaluate volume transmittion along the shadow ray (it is incorrect if the light source is outside of the
		// current volume, but in this case it will be discarded anyway since the intersection at the outer bound
		// of a current volume), so the result is fully correct.
		float3 tr = Volume_Transmittance(&volumes[volidx], &shadowrays[globalid], shadow_ray_length);

		// Volume emission is applied only if the light source is in the current volume(this is incorrect since the light source might be
		// outside of a volume and we have to compute fraction of ray in this case, but need to figure out how)
		float3 r = Volume_Emission(&volumes[volidx], &shadowrays[globalid], shadow_ray_length);

		// This is the estimate coming from a light source
		// TODO: remove hardcoded phase func and sigma
		r += tr * le * volumes[volidx].sigma_s * PhaseFunction_Uniform(wi, normalize(wo)) / pdf / selection_pdf;

		// Only if we have some radiance compute the visibility ray
		if (NON_BLACK(tr) && NON_BLACK(r) && pdf > 0.f)
		{
			// Put lightsample result
			lightsamples[globalid] = REASONABLE_RADIANCE(r * Path_GetThroughput(path));
		}
		else
		{
			// Nothing to compute
			lightsamples[globalid] = 0.f;
			// Otherwise make it incative to save intersector cycles (hopefully)
			Ray_SetInactive(shadowrays + globalid);
		}

#ifdef MULTISCATTER
		// This is highly brute-force
		// TODO: investigate importance sampling techniques here
		wo = Sample_MapToSphere(sample2);
		pdf = 1.f / (4.f * PI);

		// Generate new path segment
		Ray_Init(indirectrays + globalid, dg.p, normalize(wo), CRAZY_HIGH_DISTANCE, 0.f, 0xFFFFFFFF);

		// Update path throughput multiplying by phase function.
		Path_MulThroughput(path, volumes[volidx].sigma_s * PhaseFunction_Uniform(wi, normalize(wo)) / pdf);
#else
		// Single-scattering mode only,
		// kill the path and compact away on next iteration
		Path_Kill(path);
		Ray_SetInactive(indirectrays + globalid);
#endif
	}
}

// Handle ray-surface interaction possibly generating path continuation.
// This is only applied to non-scattered paths.
__kernel void ShadeSurface(
	// Ray batch
	__global ray const* rays,
	// Intersection data
	__global Intersection const* isects,
	// Hit indices
	__global int const* hitindices,
	// Pixel indices
	__global int const* pixelindices,
	// Number of rays
	__global int const* numhits,
	// Vertices
	__global float3 const* vertices,
	// Normals
	__global float3 const* normals,
	// UVs
	__global float2 const* uvs,
	// Indices
	__global int const* indices,
	// Shapes
	__global Shape const* shapes,
	// Material IDs
	__global int const* materialids,
	// Materials
	__global Material const* materials,
	// Textures
	TEXTURE_ARG_LIST,
	// Environment texture index
	int envmapidx,
	// Envmap multiplier
	float envmapmul,
	// Emissives
	__global Emissive const* emissives,
	// Number of emissive objects
	int numemissives,
	// RNG seed
	int rngseed,
	// Sampler states
	__global SobolSampler* samplers,
	// Sobol matrices
	__global uint const* sobolmat,
	// Current bounce
	int bounce,
    // Volume data
    __global Volume const* volumes,
	// Shadow rays
	__global ray* shadowrays,
	// Light samples
	__global float3* lightsamples,
	// Path throughput
	__global Path* paths,
	// Indirect rays
	__global ray* indirectrays,
	// Radiance
	__global float3* output
	)
{
	int globalid = get_global_id(0);

	Scene scene =
	{
		vertices,
		normals,
		uvs,
		indices,
		shapes,
		materialids,
		materials,
		emissives,
		envmapidx,
		envmapmul,
		numemissives
	};

	// Only applied to active rays after compaction
	if (globalid < *numhits)
	{
		// Fetch index
		int hitidx = hitindices[globalid];
		int pixelidx = pixelindices[globalid];
		Intersection isect = isects[hitidx];

		__global Path* path = paths + pixelidx;

		// Early exit for scattered paths
		if (Path_IsScattered(path))
		{
			return;
		}

		// Fetch incoming ray direction
		float3 wi = -normalize(rays[hitidx].d.xyz);
#ifdef SOBOL
		// Sample light
		__global SobolSampler* sampler = samplers + pixelidx;

		float2 sample0;
		sample0.x = SobolSampler_Sample1D(sampler->seq, GetSampleDim(bounce, kBrdf), sampler->s0, sobolmat);
		sample0.y = SobolSampler_Sample1D(sampler->seq, GetSampleDim(bounce, kLight), sampler->s0, sobolmat);

		float2 sample1;
		sample1.x = SobolSampler_Sample1D(sampler->seq, GetSampleDim(bounce, kLightU), sampler->s0, sobolmat);
		sample1.y = SobolSampler_Sample1D(sampler->seq, GetSampleDim(bounce, kLightV), sampler->s0, sobolmat);

		float2 sample2;
		sample2.x = SobolSampler_Sample1D(sampler->seq, GetSampleDim(bounce, kBrdfU), sampler->s0, sobolmat);
		sample2.y = SobolSampler_Sample1D(sampler->seq, GetSampleDim(bounce, kBrdfV), sampler->s0, sobolmat);

		float2 sample3;
		sample3.x = SobolSampler_Sample1D(sampler->seq, GetSampleDim(bounce, kIndirectU), sampler->s0, sobolmat);
		sample3.y = SobolSampler_Sample1D(sampler->seq, GetSampleDim(bounce, kIndirectV), sampler->s0, sobolmat);

		float sample4 = SobolSampler_Sample1D(sampler->seq, GetSampleDim(bounce, kRR), sampler->s0, sobolmat);
#else
		// Prepare RNG
		Rng rng;
		InitRng(rngseed + (globalid << 2) * 157 + 13, &rng);
		float2 sample0 = UniformSampler_Sample2D(&rng);
		float2 sample1 = UniformSampler_Sample2D(&rng);
		float2 sample2 = UniformSampler_Sample2D(&rng);
		float2 sample3 = UniformSampler_Sample2D(&rng);
		float  sample4 = UniformSampler_Sample2D(&rng).x;
#endif

        // Fill surface data
        DifferentialGeometry diffgeo;
        FillDifferentialGeometry(&scene, &isect, &diffgeo);

		// Check if we are hitting from the inside
		float ndotwi = dot(diffgeo.n, wi);
		int twosided = diffgeo.mat.twosided;
		if (twosided && ndotwi < 0.f)
		{
			// Reverse normal and tangents in this case
			// but not for BTDFs, since BTDFs rely
			// on normal direction in order to arrange
			// indices of refraction
			diffgeo.n = -diffgeo.n;
			diffgeo.dpdu = -diffgeo.dpdu;
			diffgeo.dpdv = -diffgeo.dpdv;
		}

		// Select BxDF
        Material_Select(&scene, wi, TEXTURE_ARGS, sample0.x, &diffgeo);
        //ApplyBumpMap(&diffgeo, TEXTURE_ARGS);

		// Terminate if emissive
		if (Bxdf_IsEmissive(&diffgeo) )
		{
			if (ndotwi > 0.f)
			{
				// TODO: need to account for volume extinction just in case
                output[pixelidx] += Path_GetThroughput(path) * Emissive_GetLe(&diffgeo, TEXTURE_ARGS) * ndotwi;
			}

			Path_Kill(path);
			Ray_SetInactive(shadowrays + globalid);
			Ray_SetInactive(indirectrays + globalid);

			lightsamples[globalid] = 0;
			return;
		}

		ndotwi = dot(diffgeo.n, wi);
		float s = Bxdf_IsBtdf(&diffgeo) ? (-sign(ndotwi)) : 1.f;
		if (!twosided && ndotwi < 0.f && !Bxdf_IsBtdf(&diffgeo))
		{
			// Reverse normal and tangents in this case
			// but not for BTDFs, since BTDFs rely
			// on normal direction in order to arrange
			// indices of refraction
			diffgeo.n = -diffgeo.n;
			diffgeo.dpdu = -diffgeo.dpdu;
			diffgeo.dpdv = -diffgeo.dpdv;
		}

		// TODO: this is test code, need to
		// maintain proper volume stack here
		//if (Bxdf_IsBtdf(&diffgeo))
		//{
			// If we entering set the volume
			//path->volume = ndotwi > 0.f ? 0 : -1;
		//}

        // Check if we need to apply normal map
        //ApplyNormalMap(&diffgeo, TEXTURE_ARGS);
		ApplyBumpMap(&diffgeo, TEXTURE_ARGS);
        float lightpdf = 0.f;
        float bxdflightpdf = 0.f;
        float bxdfpdf = 0.f;
        float lightbxdfpdf = 0.f;
        float selection_pdf = 0.f;
		float3 radiance = 0.f;
		float3 lightwo;
		float3 bxdfwo;
		float3 wo;

		int lightidx = Scene_SampleLight(&scene, sample0.y, &selection_pdf);

		// Sample light
        float3 le = Light_Sample(lightidx, &scene, &diffgeo, TEXTURE_ARGS, sample1, &lightwo, &lightpdf);
        lightbxdfpdf = Bxdf_GetPdf(&diffgeo, wi, normalize(lightwo), TEXTURE_ARGS);
        float lightweight = PowerHeuristic(1, lightpdf, 1, lightbxdfpdf);

        // Sample BxDF
		float3 bxdf = Bxdf_Sample(&diffgeo, wi, TEXTURE_ARGS, sample2, &bxdfwo, &bxdfpdf);
        bxdflightpdf = Light_GetPdf(lightidx, &scene, &diffgeo, bxdfwo, TEXTURE_ARGS);
        float bxdfweight = PowerHeuristic(1, bxdfpdf, 1, bxdflightpdf);

        // Apply MIS to account for both
		float3 throughput = Path_GetThroughput(path);

		if (NON_BLACK(le) && lightpdf > 0.0f && !Bxdf_IsSingular(&diffgeo))
        {
            wo = lightwo;
			float ndotwo = fabs(dot(diffgeo.n, normalize(wo)));
            radiance = le * Bxdf_Evaluate(&diffgeo, wi, normalize(wo), TEXTURE_ARGS) * throughput * ndotwo * lightweight / lightpdf / selection_pdf;
        }

		// If we have some light here generate a shadow ray
		if (NON_BLACK(radiance))
		{
			// Generate shadow ray
			float shadow_ray_length = (1.f - 2.f * CRAZY_LOW_DISTANCE) * length(wo);
			float3 shadow_ray_dir = normalize(wo);
			float3 shadow_ray_o = diffgeo.p + CRAZY_LOW_DISTANCE * s * diffgeo.n;
			int shadow_ray_mask = Bxdf_IsSingular(&diffgeo) ? 0xFFFFFFFF : 0x0000FFFF;

			Ray_Init(shadowrays + globalid, shadow_ray_o, shadow_ray_dir, shadow_ray_length, 0.f, shadow_ray_mask);

            // Apply the volume to shadow ray if needed
			int volidx = Path_GetVolumeIdx(path);
			if (volidx != -1)
			{
				radiance *= Volume_Transmittance(&volumes[volidx], &shadowrays[globalid], shadow_ray_length);
				radiance += Volume_Emission(&volumes[volidx], &shadowrays[globalid], shadow_ray_length) * throughput;
			}

			// And write the light sample
			lightsamples[globalid] = REASONABLE_RADIANCE(radiance);
		}
		else
		{
			// Otherwise save some intersector cycles
			Ray_SetInactive(shadowrays + globalid);
			lightsamples[globalid] = 0;
		}

        // Apply Russian roulette
        float q = min(0.5f,
                      // Luminance
                      0.2126f * throughput.x + 0.7152f * throughput.y + 0.0722f * throughput.z);
        // Only if it is 3+ bounce
        bool rr_apply = bounce > 3;
        bool rr_stop = sample4 > q && rr_apply;

        if (rr_apply)
        {
            Path_MulThroughput(path, 1.f / q);
        }

        bxdfwo = normalize(bxdfwo);
        float3 t = bxdf * fabs(dot(diffgeo.n, bxdfwo)) * bxdfweight;

		// Only continue if we have non-zero throughput & pdf
		if (NON_BLACK(t) && bxdfpdf > 0.f && !rr_stop)
		{
			// Update the throughput
			Path_MulThroughput(path, t / bxdfpdf);

			// Generate ray
			float3 indirect_ray_dir = bxdfwo;
			float3 indirect_ray_o = diffgeo.p + CRAZY_LOW_DISTANCE * s * diffgeo.n;

			Ray_Init(indirectrays + globalid, indirect_ray_o, indirect_ray_dir, CRAZY_HIGH_DISTANCE, 0.f, 0xFFFFFFFF);
		}
		else
		{
			// Otherwise kill the path
			Path_Kill(path);
			Ray_SetInactive(indirectrays + globalid);
		}
    }
}

///< Illuminate missing rays
__kernel void ShadeMiss(
                        // Ray batch
                        __global ray const* rays,
                        // Intersection data
                        __global Intersection const* isects,
                        // Pixel indices
                        __global int const* pixelindices,
                        // Number of rays
                        int numrays,
                        // Textures
                        TEXTURE_ARG_LIST,
                        // Environment texture index
                        int envmapidx,
                        //
                        __global Path const* paths,
                        __global Volume const* volumes,
                        // Output values
                        __global float4* output
                        )
{
    int globalid = get_global_id(0);

    if (globalid < numrays)
    {
        int pixelidx = pixelindices[globalid];

        // In case of a miss
        if (isects[globalid].shapeid < 0)
        {
            // Multiply by throughput
			int volidx = paths[pixelidx].volume;

            if (volidx == -1)
                output[pixelidx].xyz += Texture_SampleEnvMap(rays[globalid].d.xyz, TEXTURE_ARGS_IDX(envmapidx));
            else
            {
                output[pixelidx].xyz += Texture_SampleEnvMap(rays[globalid].d.xyz, TEXTURE_ARGS_IDX(envmapidx)) *
                Volume_Transmittance(&volumes[volidx], &rays[globalid], rays[globalid].o.w);

                output[pixelidx].xyz += Volume_Emission(&volumes[volidx], &rays[globalid], rays[globalid].o.w);
            }
        }

		output[pixelidx].w += 1.f;
    }
}

///< Handle light samples and visibility info and add contribution to final buffer
__kernel void GatherLightSamples(
                                 // Pixel indices
                                 __global int const* pixelindices,
                                 // Number of rays
                                 __global int* numrays,
                                 // Shadow rays hits
                                 __global int const* shadowhits,
                                 // Light samples
                                 __global float3 const* lightsamples,
                                 // throughput
                                 __global Path const* paths,
                                 // Radiance sample buffer
                                 __global float4* output
                                 )
{
    int globalid = get_global_id(0);

    if (globalid < *numrays)
    {
        // Get pixel id for this sample set
        int pixelidx = pixelindices[globalid];

			// Prepare accumulator variable
			float3 radiance = make_float3(0.f, 0.f, 0.f);

			// Start collecting samples
			{
				// If shadow ray didn't hit anything and reached skydome
				if (shadowhits[globalid] == -1)
				{
					// Add its contribution to radiance accumulator
					radiance += lightsamples[globalid];
				}
			}

			// Divide by number of light samples (samples already have built-in throughput)
			output[pixelidx].xyz += radiance;
    }
}

///< Handle light samples and visibility info and add contribution to final buffer
__kernel void GatherOcclusion(
                              // Pixel indices
                              __global int const* pixelindices,
                              // Number of rays
                              __global int const* numrays,
                              // Shadow rays hits
                              __global int const* shadowhits,
                              // Light samples
                              __global float3 const* lightsamples,
                              // throughput
                              __global float3 const* throughput,
                              // Radiance sample buffer
                              __global float4* output
                              )
{
    int globalid = get_global_id(0);

    if (globalid < *numrays)
    {
        // Get pixel id for this sample set
        int pixelidx = pixelindices[globalid];

        float visibility = (shadowhits[globalid] == 1) ? 0.f : 1.f;

        output[pixelidx].xyz += visibility;
        output[pixelidx].w += 1.f;
    }
}

///< Restore pixel indices after compaction
__kernel void RestorePixelIndices(
                                  // Compacted indices
                                  __global int const* compacted_indices,
                                  // Number of compacted indices
                                  __global int* numitems,
                                  // Previous pixel indices
                                  __global int const* previndices,
                                  // New pixel indices
                                  __global int* newindices
                                  )
{
    int globalid = get_global_id(0);

    // Handle only working subset
    if (globalid < *numitems)
    {
        newindices[globalid] = previndices[compacted_indices[globalid]];
    }
}

///< Restore pixel indices after compaction
__kernel void FilterPathStream(
                                 // Intersections
                                 __global Intersection const* isects,
                                 // Number of compacted indices
                                 __global int const* numitems,
                                 // Pixel indices
                                 __global int const* pixelindices,
                                 // Paths
								 __global Path* paths,
								 // Predicate
                                 __global int* predicate
                                 )
{
    int globalid = get_global_id(0);

    // Handle only working subset
    if (globalid < *numitems)
    {
		int pixelidx = pixelindices[globalid];

        __global Path* path = paths + pixelidx;

		if (Path_IsAlive(path))
		{
            bool kill = (length(Path_GetThroughput(path)) < CRAZY_LOW_THROUGHPUT);

            if (!kill)
            {
                predicate[globalid] = isects[globalid].shapeid >= 0 ? 1 : 0;
            }
            else
            {
                Path_Kill(path);
                predicate[globalid] = 0;
            }
		}
		else
		{
			predicate[globalid] = 0;
		}
    }
}

///< Illuminate missing rays
__kernel void ShadeBackground(
                        // Ray batch
                        __global ray const* rays,
                        // Intersection data
                        __global Intersection const* isects,
                        // Pixel indices
                        __global int const* pixelindices,
                        // Number of rays
                        __global int const* numrays,
                        // Textures
                        TEXTURE_ARG_LIST,
                        // Environment texture index
                        int envmapidx,
                        //
                        __global Path const* paths,
                        __global Volume const* volumes,
                        // Output values
                        __global float4* output
                        )
{
    int globalid = get_global_id(0);

    if (globalid < *numrays)
    {
        int pixelidx = pixelindices[globalid];

        __global Path* path = paths + pixelidx;

        // In case of a miss
        if (isects[globalid].shapeid < 0 && Path_IsAlive(path))
        {
            float3 t = Path_GetThroughput(path);
            output[pixelidx].xyz += Texture_SampleEnvMap(rays[globalid].d.xyz, TEXTURE_ARGS_IDX(envmapidx)) * t;
        }
    }
}

// Copy data to interop texture if supported
__kernel void AccumulateData(
	__global float4 const* srcdata,
	int numelems,
	__global float4* dstdata
	)
{
	int gid = get_global_id(0);

	if (gid < numelems)
	{
		float4 v = srcdata[gid];
		dstdata[gid] += v;
	}
}

//__kernel void ApplySelectiveBlur(
//	__global float4 const* data,
//	int imgwidth,
//	int imgheight,
//	float alpha,
//	__global float const* k,
//	__global float4* output_data
//	)
//{
//#define FILTER_RADIUS 3
//	int gidx = get_global_id(0);
//	int gidy = get_global_id(1);
//	int gid = gidy * imgwidth + gidx;
//	int gid1 = gidy * imgwidth + gidx + 1;
//	int gid2 = gidy * imgwidth + gidx - 1;
//	int gid3 = (gidy + 1) * imgwidth + gidx;
//	int gid4 = (gidy - 1) * imgwidth + gidx;
//
//
//	if (gidx < imgwidth && gidy < imgheight)
//	{
//
//		int xstart = max(gidx - FILTER_RADIUS, 0);
//		int xend = min(gidx + FILTER_RADIUS, imgwidth);
//
//		int ystart = max(gidy - FILTER_RADIUS, 0);
//		int yend = min(gidy + FILTER_RADIUS, imgheight);
//
//		float3 cv = data[gid].xyz;
//		float3 d1 = fabs(data[gid1].xyz - cv);
//		float3 d2 = fabs(data[gid2].xyz - cv);
//		float3 d3 = fabs(data[gid3].xyz - cv);
//		float3 d4 = fabs(data[gid4].xyz - cv);
//
//
//		/*if (d1.x > alpha ||
//			d1.y > alpha ||
//			d1.z > alpha ||
//			d2.x > alpha ||
//			d2.y > alpha ||
//			d2.z > alpha ||
//			d3.x > alpha ||
//			d3.y > alpha ||
//			d3.z > alpha ||
//			d4.x > alpha ||
//			d4.y > alpha ||
//			d4.z > alpha
//			)
//
//		{
//			output_data[gid] = data[gid];
//			return;
//		}*/
//
//		float3 v = 0.f;
//		for (int x = xstart; x <= xend; ++x)
//			for (int y = ystart; y <= yend; ++y)
//			{
//				float3 pv = data[y * imgwidth + x].xyz;
//				int fx = x - gidx + FILTER_RADIUS;
//				int fy = y - gidy + FILTER_RADIUS;
//				v += pv * k[fy * 7 + fx];
//			}
//
//		output_data[gid].xyz = v;
//		output_data[gid].w = data[gid].w;
//	}
//#undef FILTER_RADIUS
//}

// Copy data to interop texture if supported
__kernel void ApplyGammaAndCopyData(
                                    __global float4 const* data,
                                    int imgwidth,
                                    int imgheight,
                                    float gamma,
                                    write_only image2d_t img
                                    )
{
    int gid = get_global_id(0);

    int gidx = gid % imgwidth;
    int gidy = gid / imgwidth;

    if (gidx < imgwidth && gidy < imgheight)
    {
		float4 v = data[gid];
		float4 val = clamp(native_powr(v / v.w, 1.f / gamma), 0.f, 1.f);
        write_imagef(img, make_int2(gidx, gidy), val);
    }
}

/**********************************************************************
Copyright (c) 2016 Advanced Micro Devices, Inc. All rights reserved.

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.
********************************************************************/
#include <../App/CL/utils.cl>
#include <../App/CL/random.cl>
#include <../App/CL/payload.cl>
#include <../App/CL/texture.cl>
#include <../App/CL/sampling.cl>
#include <../App/CL/normalmap.cl>
#include <../App/CL/bxdf.cl>
#include <../App/CL/light.cl>
#include <../App/CL/camera.cl>
#include <../App/CL/scene.cl>
#include <../App/CL/material.cl>
#include <../App/CL/volumetrics.cl>
#include <../App/CL/path.cl>

#define CRAZY_LOW_THROUGHPUT 0.0f
#define CRAZY_HIGH_RADIANCE 10.f
#define CRAZY_HIGH_DISTANCE 1000000.f
#define CRAZY_LOW_DISTANCE 0.001f
#define REASONABLE_RADIANCE(x) (clamp((x), 0.f, CRAZY_HIGH_RADIANCE))
#define NON_BLACK(x) (length(x) > 0.f)

// This kernel only handles scattered paths.
// It applies direct illumination and generates
// path continuation if multiscattering is enabled.
__kernel void ShadeVolume(
	// Ray batch
	__global ray const* rays,
	// Intersection data
	__global Intersection const* isects,
	// Hit indices
	__global int const* hitindices,
	// Pixel indices
	__global int const* pixelindices,
	// Number of rays
	__global int const* numhits,
	// Vertices
	__global float3 const* vertices,
	// Normals
	__global float3 const* normals,
	// UVs
	__global float2 const* uvs,
	// Indices
	__global int const* indices,
	// Shapes
	__global Shape const* shapes,
	// Material IDs
	__global int const* materialids,
	// Materials
	__global Material const* materials,
	// Textures
	TEXTURE_ARG_LIST,
	// Environment texture index
	int envmapidx,
	// Envmap multiplier
	float envmapmul,
	// Emissives
	__global Emissive const* emissives,
	// Number of emissive objects
	int numemissives,
	// RNG seed
	int rngseed,
	// Sampler state
	__global SobolSampler* samplers,
	// Sobol matrices
	__global uint const* sobolmat,
	// Current bounce
	int bounce,
	// Volume data
	__global Volume const* volumes,
	// Shadow rays
	__global ray* shadowrays,
	// Light samples
	__global float3* lightsamples,
	// Path throughput
	__global Path* paths,
	// Indirect rays (next path segment)
	__global ray* indirectrays,
	// Radiance
	__global float3* output
	)
{
	int globalid = get_global_id(0);

	Scene scene =
	{
		vertices,
		normals,
		uvs,
		indices,
		shapes,
		materialids,
		materials,
		emissives,
		envmapidx,
		envmapmul,
		numemissives
	};

	if (globalid < *numhits)
	{
		// Fetch index
		int hitidx = hitindices[globalid];
		int pixelidx = pixelindices[globalid];
		Intersection isect = isects[hitidx];

		__global Path* path = paths + pixelidx;

		// Only apply to scattered paths
		if (!Path_IsScattered(path))
		{
			return;
		}

		// Fetch incoming ray
		float3 o = rays[hitidx].o.xyz;
		float3 wi = rays[hitidx].d.xyz;

#ifdef SOBOL
		__global SobolSampler* sampler = samplers + pixelidx;
		float sample0 = SobolSampler_Sample1D(sampler->seq, GetSampleDim(bounce, kVolumeLight), sampler->s0, sobolmat);
		float2 sample1;
		sample1.x = SobolSampler_Sample1D(sampler->seq, GetSampleDim(bounce, kVolumeLightU), sampler->s0, sobolmat);
		sample1.y = SobolSampler_Sample1D(sampler->seq, GetSampleDim(bounce, kVolumeLightV), sampler->s0, sobolmat);
#ifdef MULTISCATTER
        float2 sample2;
        sample2.x = SobolSampler_Sample1D(sampler->seq, GetSampleDim(bounce, kVolumeIndirectU), sampler->s0, sobolmat);
        sample2.y = SobolSampler_Sample1D(sampler->seq, GetSampleDim(bounce, kVolumeIndirectV), sampler->s0, sobolmat);
#endif
#else
		// Prepare RNG for sampling
		Rng rng;
		InitRng(rngseed + (globalid << 2) * 157 + 13, &rng);
		float sample0 = UniformSampler_Sample2D(&rng).x;
		float2 sample1 = UniformSampler_Sample2D(&rng);
#ifdef MULTISCATTER
        float2 sample2 = UniformSampler_Sample2D(&rng);
#endif
#endif
		// Here we know that volidx != -1 since this is a precondition
		// for scattering event
		int volidx = Path_GetVolumeIdx(path);

		// Sample light source
		float pdf = 0.f;
		float selection_pdf = 0.f;
		float3 wo;

		int lightidx = Scene_SampleLight(&scene, sample0, &selection_pdf);

		// Here we need fake differential geometry for light sampling procedure
		DifferentialGeometry dg;
		// put scattering position in there (it is along the current ray at isect.distance
		// since EvaluateVolume has put it there
		dg.p = o + wi * Intersection_GetDistance(isects + hitidx);
		// Get light sample intencity
		float3 le = Light_Sample(lightidx, &scene, &dg, TEXTURE_ARGS, sample1, &wo, &pdf);

		// Generate shadow ray
		float shadow_ray_length = 0.999f * length(wo);
		Ray_Init(shadowrays + globalid, dg.p, normalize(wo), shadow_ray_length, 0.f, 0xFFFFFFFF);

		// Evaluate volume transmittion along the shadow ray (it is incorrect if the light source is outside of the
		// current volume, but in this case it will be discarded anyway since the intersection at the outer bound
		// of a current volume), so the result is fully correct.
		float3 tr = Volume_Transmittance(&volumes[volidx], &shadowrays[globalid], shadow_ray_length);

		// Volume emission is applied only if the light source is in the current volume(this is incorrect since the light source might be
		// outside of a volume and we have to compute fraction of ray in this case, but need to figure out how)
		float3 r = Volume_Emission(&volumes[volidx], &shadowrays[globalid], shadow_ray_length);

		// This is the estimate coming from a light source
		// TODO: remove hardcoded phase func and sigma
		r += tr * le * volumes[volidx].sigma_s * PhaseFunction_Uniform(wi, normalize(wo)) / pdf / selection_pdf;

		// Only if we have some radiance compute the visibility ray
		if (NON_BLACK(tr) && NON_BLACK(r) && pdf > 0.f)
		{
			// Put lightsample result
			lightsamples[globalid] = REASONABLE_RADIANCE(r * Path_GetThroughput(path));
		}
		else
		{
			// Nothing to compute
			lightsamples[globalid] = 0.f;
			// Otherwise make it incative to save intersector cycles (hopefully)
			Ray_SetInactive(shadowrays + globalid);
		}

#ifdef MULTISCATTER
		// This is highly brute-force
		// TODO: investigate importance sampling techniques here
		wo = Sample_MapToSphere(sample2);
		pdf = 1.f / (4.f * PI);

		// Generate new path segment
		Ray_Init(indirectrays + globalid, dg.p, normalize(wo), CRAZY_HIGH_DISTANCE, 0.f, 0xFFFFFFFF);

		// Update path throughput multiplying by phase function.
		Path_MulThroughput(path, volumes[volidx].sigma_s * PhaseFunction_Uniform(wi, normalize(wo)) / pdf);
#else
		// Single-scattering mode only,
		// kill the path and compact away on next iteration
		Path_Kill(path);
		Ray_SetInactive(indirectrays + globalid);
#endif
	}
}

// Handle ray-surface interaction possibly generating path continuation.
// This is only applied to non-scattered paths.
__kernel void ShadeSurface(
	// Ray batch
	__global ray const* rays,
	// Intersection data
	__global Intersection const* isects,
	// Hit indices
	__global int const* hitindices,
	// Pixel indices
	__global int const* pixelindices,
	// Number of rays
	__global int const* numhits,
	// Vertices
	__global float3 const* vertices,
	// Normals
	__global float3 const* normals,
	// UVs
	__global float2 const* uvs,
	// Indices
	__global int const* indices,
	// Shapes
	__global Shape const* shapes,
	// Material IDs
	__global int const* materialids,
	// Materials
	__global Material const* materials,
	// Textures
	TEXTURE_ARG_LIST,
	// Environment texture index
	int envmapidx,
	// Envmap multiplier
	float envmapmul,
	// Emissives
	__global Emissive const* emissives,
	// Number of emissive objects
	int numemissives,
	// RNG seed
	int rngseed,
	// Sampler states
	__global SobolSampler* samplers,
	// Sobol matrices
	__global uint const* sobolmat,
	// Current bounce
	int bounce,
    // Volume data
    __global Volume const* volumes,
	// Shadow rays
	__global ray* shadowrays,
	// Light samples
	__global float3* lightsamples,
	// Path throughput
	__global Path* paths,
	// Indirect rays
	__global ray* indirectrays,
	// Radiance
	__global float3* output
	)
{
	int globalid = get_global_id(0);

	Scene scene =
	{
		vertices,
		normals,
		uvs,
		indices,
		shapes,
		materialids,
		materials,
		emissives,
		envmapidx,
		envmapmul,
		numemissives
	};

	// Only applied to active rays after compaction
	if (globalid < *numhits)
	{
		// Fetch index
		int hitidx = hitindices[globalid];
		int pixelidx = pixelindices[globalid];
		Intersection isect = isects[hitidx];

		__global Path* path = paths + pixelidx;

		// Early exit for scattered paths
		if (Path_IsScattered(path))
		{
			return;
		}

		// Fetch incoming ray direction
		float3 wi = -normalize(rays[hitidx].d.xyz);
#ifdef SOBOL
		// Sample light
		__global SobolSampler* sampler = samplers + pixelidx;

		float2 sample0;
		sample0.x = SobolSampler_Sample1D(sampler->seq, GetSampleDim(bounce, kBrdf), sampler->s0, sobolmat);
		sample0.y = SobolSampler_Sample1D(sampler->seq, GetSampleDim(bounce, kLight), sampler->s0, sobolmat);

		float2 sample1;
		sample1.x = SobolSampler_Sample1D(sampler->seq, GetSampleDim(bounce, kLightU), sampler->s0, sobolmat);
		sample1.y = SobolSampler_Sample1D(sampler->seq, GetSampleDim(bounce, kLightV), sampler->s0, sobolmat);

		float2 sample2;
		sample2.x = SobolSampler_Sample1D(sampler->seq, GetSampleDim(bounce, kBrdfU), sampler->s0, sobolmat);
		sample2.y = SobolSampler_Sample1D(sampler->seq, GetSampleDim(bounce, kBrdfV), sampler->s0, sobolmat);

		float2 sample3;
		sample3.x = SobolSampler_Sample1D(sampler->seq, GetSampleDim(bounce, kIndirectU), sampler->s0, sobolmat);
		sample3.y = SobolSampler_Sample1D(sampler->seq, GetSampleDim(bounce, kIndirectV), sampler->s0, sobolmat);

		float sample4 = SobolSampler_Sample1D(sampler->seq, GetSampleDim(bounce, kRR), sampler->s0, sobolmat);
#else
		// Prepare RNG
		Rng rng;
		InitRng(rngseed + (globalid << 2) * 157 + 13, &rng);
		float2 sample0 = UniformSampler_Sample2D(&rng);
		float2 sample1 = UniformSampler_Sample2D(&rng);
		float2 sample2 = UniformSampler_Sample2D(&rng);
		float2 sample3 = UniformSampler_Sample2D(&rng);
		float  sample4 = UniformSampler_Sample2D(&rng).x;
#endif

        // Fill surface data
        DifferentialGeometry diffgeo;
        FillDifferentialGeometry(&scene, &isect, &diffgeo);

		// Check if we are hitting from the inside
		float ndotwi = dot(diffgeo.n, wi);
		int twosided = diffgeo.mat.twosided;
		if (twosided && ndotwi < 0.f)
		{
			// Reverse normal and tangents in this case
			// but not for BTDFs, since BTDFs rely
			// on normal direction in order to arrange
			// indices of refraction
			diffgeo.n = -diffgeo.n;
			diffgeo.dpdu = -diffgeo.dpdu;
			diffgeo.dpdv = -diffgeo.dpdv;
		}

		// Select BxDF
        Material_Select(&scene, wi, TEXTURE_ARGS, sample0.x, &diffgeo);
        //ApplyBumpMap(&diffgeo, TEXTURE_ARGS);

		// Terminate if emissive
		if (Bxdf_IsEmissive(&diffgeo))
		{
			if (bounce == 0)
			{
				// TODO: need to account for volume extinction just in case
				output[pixelidx] += Path_GetThroughput(path) * Emissive_GetLe(&diffgeo, TEXTURE_ARGS);
			}

			Path_Kill(path);
			Ray_SetInactive(shadowrays + globalid);
			Ray_SetInactive(indirectrays + globalid);

			lightsamples[globalid] = 0;
			return;
		}

		ndotwi = dot(diffgeo.n, wi);
		float s = Bxdf_IsBtdf(&diffgeo) ? (-sign(ndotwi)) : 1.f;
		if (!twosided && ndotwi < 0.f && !Bxdf_IsBtdf(&diffgeo))
		{
			// Reverse normal and tangents in this case
			// but not for BTDFs, since BTDFs rely
			// on normal direction in order to arrange
			// indices of refraction
			diffgeo.n = -diffgeo.n;
			diffgeo.dpdu = -diffgeo.dpdu;
			diffgeo.dpdv = -diffgeo.dpdv;
		}

		// TODO: this is test code, need to
		// maintain proper volume stack here
		//if (Bxdf_IsBtdf(&diffgeo))
		//{
			// If we entering set the volume
			//path->volume = ndotwi > 0.f ? 0 : -1;
		//}

        // Check if we need to apply normal map
        //ApplyNormalMap(&diffgeo, TEXTURE_ARGS);
		ApplyBumpMap(&diffgeo, TEXTURE_ARGS);
        float lightpdf = 0.f;
        float bxdflightpdf = 0.f;
        float bxdfpdf = 0.f;
        float lightbxdfpdf = 0.f;
        float selection_pdf = 0.f;
		float3 radiance = 0.f;
		float3 lightwo;
		float3 bxdfwo;
		float3 wo;

		int lightidx = Scene_SampleLight(&scene, sample0.y, &selection_pdf);

		// Sample light
        float3 le = Light_Sample(lightidx, &scene, &diffgeo, TEXTURE_ARGS, sample1, &lightwo, &lightpdf);
        lightbxdfpdf = Bxdf_GetPdf(&diffgeo, wi, normalize(lightwo), TEXTURE_ARGS);
        float lightweight = PowerHeuristic(1, lightpdf, 1, lightbxdfpdf);

        // Sample BxDF
		float3 bxdf = Bxdf_Sample(&diffgeo, wi, TEXTURE_ARGS, sample2, &bxdfwo, &bxdfpdf);
        bxdflightpdf = Light_GetPdf(lightidx, &scene, &diffgeo, bxdfwo, TEXTURE_ARGS);
        float bxdfweight = PowerHeuristic(1, bxdfpdf, 1, bxdflightpdf);

        // Apply MIS to account for both
		float3 throughput = Path_GetThroughput(path);

		if (NON_BLACK(le) && lightpdf > 0.0f && !Bxdf_IsSingular(&diffgeo))
        {
            wo = lightwo;
			float ndotwo = fabs(dot(diffgeo.n, normalize(wo)));
            radiance = le * Bxdf_Evaluate(&diffgeo, wi, normalize(wo), TEXTURE_ARGS) * throughput * ndotwo * lightweight / lightpdf / selection_pdf;
        }

		// If we have some light here generate a shadow ray
		if (NON_BLACK(radiance))
		{
			// Generate shadow ray
			float shadow_ray_length = (1.f - 2.f * CRAZY_LOW_DISTANCE) * length(wo);
			float3 shadow_ray_dir = normalize(wo);
			float3 shadow_ray_o = diffgeo.p + CRAZY_LOW_DISTANCE * s * diffgeo.n;
			int shadow_ray_mask = Bxdf_IsSingular(&diffgeo) ? 0xFFFFFFFF : 0x0000FFFF;

			Ray_Init(shadowrays + globalid, shadow_ray_o, shadow_ray_dir, shadow_ray_length, 0.f, shadow_ray_mask);

            // Apply the volume to shadow ray if needed
			int volidx = Path_GetVolumeIdx(path);
			if (volidx != -1)
			{
				radiance *= Volume_Transmittance(&volumes[volidx], &shadowrays[globalid], shadow_ray_length);
				radiance += Volume_Emission(&volumes[volidx], &shadowrays[globalid], shadow_ray_length) * throughput;
			}

			// And write the light sample
			lightsamples[globalid] = REASONABLE_RADIANCE(radiance);
		}
		else
		{
			// Otherwise save some intersector cycles
			Ray_SetInactive(shadowrays + globalid);
			lightsamples[globalid] = 0;
		}

        // Apply Russian roulette
        float q = min(0.5f,
                      // Luminance
                      0.2126f * throughput.x + 0.7152f * throughput.y + 0.0722f * throughput.z);
        // Only if it is 3+ bounce
        bool rr_apply = bounce > 3;
        bool rr_stop = sample4 > q && rr_apply;

        if (rr_apply)
        {
            Path_MulThroughput(path, 1.f / q);
        }

        bxdfwo = normalize(bxdfwo);
        float3 t = bxdf * fabs(dot(diffgeo.n, bxdfwo)) * bxdfweight;

		// Only continue if we have non-zero throughput & pdf
		if (NON_BLACK(t) && bxdfpdf > 0.f && !rr_stop)
		{
			// Update the throughput
			Path_MulThroughput(path, t / bxdfpdf);

			// Generate ray
			float3 indirect_ray_dir = bxdfwo;
			float3 indirect_ray_o = diffgeo.p + CRAZY_LOW_DISTANCE * s * diffgeo.n;

			Ray_Init(indirectrays + globalid, indirect_ray_o, indirect_ray_dir, CRAZY_HIGH_DISTANCE, 0.f, 0xFFFFFFFF);
		}
		else
		{
			// Otherwise kill the path
			Path_Kill(path);
			Ray_SetInactive(indirectrays + globalid);
		}
    }
}

///< Illuminate missing rays
__kernel void ShadeMiss(
                        // Ray batch
                        __global ray const* rays,
                        // Intersection data
                        __global Intersection const* isects,
                        // Pixel indices
                        __global int const* pixelindices,
                        // Number of rays
                        int numrays,
                        // Textures
                        TEXTURE_ARG_LIST,
                        // Environment texture index
                        int envmapidx,
                        //
                        __global Path const* paths,
                        __global Volume const* volumes,
                        // Output values
                        __global float4* output
                        )
{
    int globalid = get_global_id(0);

    if (globalid < numrays)
    {
        int pixelidx = pixelindices[globalid];

        // In case of a miss
        if (isects[globalid].shapeid < 0)
        {
            // Multiply by throughput
			int volidx = paths[pixelidx].volume;

            if (volidx == -1)
                output[pixelidx].xyz += Texture_SampleEnvMap(rays[globalid].d.xyz, TEXTURE_ARGS_IDX(envmapidx));
            else
            {
                output[pixelidx].xyz += Texture_SampleEnvMap(rays[globalid].d.xyz, TEXTURE_ARGS_IDX(envmapidx)) *
                Volume_Transmittance(&volumes[volidx], &rays[globalid], rays[globalid].o.w);

                output[pixelidx].xyz += Volume_Emission(&volumes[volidx], &rays[globalid], rays[globalid].o.w);
            }
        }

		output[pixelidx].w += 1.f;
    }
}

///< Handle light samples and visibility info and add contribution to final buffer
__kernel void GatherLightSamples(
                                 // Pixel indices
                                 __global int const* pixelindices,
                                 // Number of rays
                                 __global int* numrays,
                                 // Shadow rays hits
                                 __global int const* shadowhits,
                                 // Light samples
                                 __global float3 const* lightsamples,
                                 // throughput
                                 __global Path const* paths,
                                 // Radiance sample buffer
                                 __global float4* output
                                 )
{
    int globalid = get_global_id(0);

    if (globalid < *numrays)
    {
        // Get pixel id for this sample set
        int pixelidx = pixelindices[globalid];

			// Prepare accumulator variable
			float3 radiance = make_float3(0.f, 0.f, 0.f);

			// Start collecting samples
			{
				// If shadow ray didn't hit anything and reached skydome
				if (shadowhits[globalid] == -1)
				{
					// Add its contribution to radiance accumulator
					radiance += lightsamples[globalid];
				}
			}

			// Divide by number of light samples (samples already have built-in throughput)
			output[pixelidx].xyz += radiance;
    }
}

///< Handle light samples and visibility info and add contribution to final buffer
__kernel void GatherOcclusion(
                              // Pixel indices
                              __global int const* pixelindices,
                              // Number of rays
                              __global int const* numrays,
                              // Shadow rays hits
                              __global int const* shadowhits,
                              // Light samples
                              __global float3 const* lightsamples,
                              // throughput
                              __global float3 const* throughput,
                              // Radiance sample buffer
                              __global float4* output
                              )
{
    int globalid = get_global_id(0);

    if (globalid < *numrays)
    {
        // Get pixel id for this sample set
        int pixelidx = pixelindices[globalid];

        float visibility = (shadowhits[globalid] == 1) ? 0.f : 1.f;

        output[pixelidx].xyz += visibility;
        output[pixelidx].w += 1.f;
    }
}

///< Restore pixel indices after compaction
__kernel void RestorePixelIndices(
                                  // Compacted indices
                                  __global int const* compacted_indices,
                                  // Number of compacted indices
                                  __global int* numitems,
                                  // Previous pixel indices
                                  __global int const* previndices,
                                  // New pixel indices
                                  __global int* newindices
                                  )
{
    int globalid = get_global_id(0);

    // Handle only working subset
    if (globalid < *numitems)
    {
        newindices[globalid] = previndices[compacted_indices[globalid]];
    }
}

///< Restore pixel indices after compaction
__kernel void FilterPathStream(
                                 // Intersections
                                 __global Intersection const* isects,
                                 // Number of compacted indices
                                 __global int const* numitems,
                                 // Pixel indices
                                 __global int const* pixelindices,
                                 // Paths
								 __global Path* paths,
								 // Predicate
                                 __global int* predicate
                                 )
{
    int globalid = get_global_id(0);

    // Handle only working subset
    if (globalid < *numitems)
    {
		int pixelidx = pixelindices[globalid];

        __global Path* path = paths + pixelidx;

		if (Path_IsAlive(path))
		{
            bool kill = (length(Path_GetThroughput(path)) < CRAZY_LOW_THROUGHPUT);

            if (!kill)
            {
                predicate[globalid] = isects[globalid].shapeid >= 0 ? 1 : 0;
            }
            else
            {
                Path_Kill(path);
                predicate[globalid] = 0;
            }
		}
		else
		{
			predicate[globalid] = 0;
		}
    }
}

///< Illuminate missing rays
__kernel void ShadeBackground(
                        // Ray batch
                        __global ray const* rays,
                        // Intersection data
                        __global Intersection const* isects,
                        // Pixel indices
                        __global int const* pixelindices,
                        // Number of rays
                        __global int const* numrays,
                        // Textures
                        TEXTURE_ARG_LIST,
                        // Environment texture index
                        int envmapidx,
                        //
                        __global Path const* paths,
                        __global Volume const* volumes,
                        // Output values
                        __global float4* output
                        )
{
    int globalid = get_global_id(0);

    if (globalid < *numrays)
    {
        int pixelidx = pixelindices[globalid];

        __global Path* path = paths + pixelidx;

        // In case of a miss
        if (isects[globalid].shapeid < 0 && Path_IsAlive(path))
        {
            float3 t = Path_GetThroughput(path);
            output[pixelidx].xyz += Texture_SampleEnvMap(rays[globalid].d.xyz, TEXTURE_ARGS_IDX(envmapidx)) * t;
        }
    }
}

// Copy data to interop texture if supported
__kernel void AccumulateData(
	__global float4 const* srcdata,
	int numelems,
	__global float4* dstdata
	)
{
	int gid = get_global_id(0);

	if (gid < numelems)
	{
		float4 v = srcdata[gid];
		dstdata[gid] += v;
	}
}

//__kernel void ApplySelectiveBlur(
//	__global float4 const* data,
//	int imgwidth,
//	int imgheight,
//	float alpha,
//	__global float const* k,
//	__global float4* output_data
//	)
//{
//#define FILTER_RADIUS 3
//	int gidx = get_global_id(0);
//	int gidy = get_global_id(1);
//	int gid = gidy * imgwidth + gidx;
//	int gid1 = gidy * imgwidth + gidx + 1;
//	int gid2 = gidy * imgwidth + gidx - 1;
//	int gid3 = (gidy + 1) * imgwidth + gidx;
//	int gid4 = (gidy - 1) * imgwidth + gidx;
//
//
//	if (gidx < imgwidth && gidy < imgheight)
//	{
//
//		int xstart = max(gidx - FILTER_RADIUS, 0);
//		int xend = min(gidx + FILTER_RADIUS, imgwidth);
//
//		int ystart = max(gidy - FILTER_RADIUS, 0);
//		int yend = min(gidy + FILTER_RADIUS, imgheight);
//
//		float3 cv = data[gid].xyz;
//		float3 d1 = fabs(data[gid1].xyz - cv);
//		float3 d2 = fabs(data[gid2].xyz - cv);
//		float3 d3 = fabs(data[gid3].xyz - cv);
//		float3 d4 = fabs(data[gid4].xyz - cv);
//
//
//		/*if (d1.x > alpha ||
//			d1.y > alpha ||
//			d1.z > alpha ||
//			d2.x > alpha ||
//			d2.y > alpha ||
//			d2.z > alpha ||
//			d3.x > alpha ||
//			d3.y > alpha ||
//			d3.z > alpha ||
//			d4.x > alpha ||
//			d4.y > alpha ||
//			d4.z > alpha
//			)
//
//		{
//			output_data[gid] = data[gid];
//			return;
//		}*/
//
//		float3 v = 0.f;
//		for (int x = xstart; x <= xend; ++x)
//			for (int y = ystart; y <= yend; ++y)
//			{
//				float3 pv = data[y * imgwidth + x].xyz;
//				int fx = x - gidx + FILTER_RADIUS;
//				int fy = y - gidy + FILTER_RADIUS;
//				v += pv * k[fy * 7 + fx];
//			}
//
//		output_data[gid].xyz = v;
//		output_data[gid].w = data[gid].w;
//	}
//#undef FILTER_RADIUS
//}

// Copy data to interop texture if supported
__kernel void ApplyGammaAndCopyData(
                                    __global float4 const* data,
                                    int imgwidth,
                                    int imgheight,
                                    float gamma,
                                    write_only image2d_t img
                                    )
{
    int gid = get_global_id(0);

    int gidx = gid % imgwidth;
    int gidy = gid / imgwidth;

    if (gidx < imgwidth && gidy < imgheight)
    {
		float4 v = data[gid];
		float4 val = clamp(native_powr(v / v.w, 1.f / gamma), 0.f, 1.f);
        write_imagef(img, make_int2(gidx, gidy), val);
    }
}