#pragma BLENDER_REQUIRE(gpu_shader_compositor_texture_utilities.glsl)

/* This shader reduces the given texture into a smaller texture of a size equal to the number of
 * work groups. In particular, each work group reduces its contents into a single value and writes
 * that value to a single pixel in the output image. The shader can be dispatched multiple times to
 * eventually reduce the image into a single pixel.
 *
 * The shader works by loading the whole data of each work group into a linear array, then it
 * reduces the second half of the array onto the first half of the array, then it reduces the
 * second quarter of the array onto the first quarter or the array, and so on until only one
 * element remains. The following figure illustrates the process for sum reduction on 8 elements.
 *
 *     .---. .---. .---. .---. .---. .---. .---. .---.
 *     | 0 | | 1 | | 2 | | 3 | | 4 | | 5 | | 6 | | 7 |  Original data.
 *     '---' '---' '---' '---' '---' '---' '---' '---'
 *       |.____|_____|_____|_____|     |     |     |
 *       ||    |.____|_____|___________|     |     |
 *       ||    ||    |.____|_________________|     |
 *       ||    ||    ||    |.______________________|  <--First reduction. Stride = 4.
 *       ||    ||    ||    ||
 *     .---. .---. .---. .----.
 *     | 4 | | 6 | | 8 | | 10 |                       <--Data after first reduction.
 *     '---' '---' '---' '----'
 *       |.____|_____|     |
 *       ||    |.__________|                          <--Second reduction. Stride = 2.
 *       ||    ||
 *     .----. .----.
 *     | 12 | | 16 |                                  <--Data after second reduction.
 *     '----' '----'
 *       |.____|
 *       ||                                           <--Third reduction. Stride = 1.
 *     .----.
 *     | 28 |
 *     '----'                                         <--Data after third reduction.
 *
 *
 * The shader is generic enough to implement many types of reductions. This is done by using macros
 * that the developer should define to implement a certain reduction operation. Those include,
 * TYPE, IDENTITY, INITIALIZE, LOAD, and REDUCE. See the implementation below for more information
 * as well as the compositor_parallel_reduction_info.hh for example reductions operations. */

/* Doing the reduction in shared memory is faster, so create a shared array where the whole data
 * of the work group will be loaded and reduced. The 2D structure of the work group is irrelevant
 * for reduction, so we just load the data in a 1D array to simplify reduction. The developer is
 * expected to define the TYPE macro to be a float or a vec4, depending on the type of data being
 * reduced. */
const uint reduction_size = gl_WorkGroupSize.x * gl_WorkGroupSize.y;
shared TYPE reduction_data[reduction_size];

void main()
{
  /* Load the data from the texture, while returning IDENTITY for out of bound coordinates. The
   * developer is expected to define the IDENTITY macro to be a vec4 that does not affect the
   * output of the reduction. For instance, sum reductions have an identity of vec4(0.0), while
   * max value reductions have an identity of vec4(FLT_MIN). */
  vec4 value = texture_load(input_tx, ivec2(gl_GlobalInvocationID.xy), IDENTITY);

  /* Initialize the shared array given the previously loaded value. This step can be different
   * depending on whether this is the initial reduction pass or a latter one. Indeed, the input
   * texture for the initial reduction is the source texture itself, while the input texture to a
   * latter reduction pass is an intermediate texture after one or more reductions have happened.
   * This is significant because the data being reduced might be computed from the original data
   * and different from it, for instance, when summing the luminance of an image, the original data
   * is a vec4 color, while the reduced data is a float luminance value. So for the initial
   * reduction pass, the luminance will be computed from the color, reduced, then stored into an
   * intermediate float texture. On the other hand, for latter reduction passes, the luminance will
   * be loaded directly and reduced without extra processing. So the developer is expected to
   * define the INITIALIZE and LOAD macros to be expressions that derive the needed value from the
   * loaded value for the initial reduction pass and latter ones respectively. */
  reduction_data[gl_LocalInvocationIndex] = is_initial_reduction ? INITIALIZE(value) : LOAD(value);

  /* Reduce the reduction data by half on every iteration until only one element remains. See the
   * above figure for an intuitive understanding of the stride value. */
  for (uint stride = reduction_size / 2; stride > 0; stride /= 2) {
    barrier();

    /* Only the threads up to the current stride should be active as can be seen in the diagram
     * above. */
    if (gl_LocalInvocationIndex >= stride) {
      continue;
    }

    /* Reduce each two elements that are stride apart, writing the result to the element with the
     * lower index, as can be seen in the diagram above. The developer is expected to define the
     * REDUCE macro to be a commutative and associative binary operator suitable for parallel
     * reduction. */
    reduction_data[gl_LocalInvocationIndex] = REDUCE(
        reduction_data[gl_LocalInvocationIndex], reduction_data[gl_LocalInvocationIndex + stride]);
  }

  /* Finally, the result of the reduction is available as the first element in the reduction data,
   * write it to the pixel corresponding to the work group, making sure only the one thread writes
   * it. */
  barrier();
  if (gl_LocalInvocationIndex == 0) {
    imageStore(output_img, ivec2(gl_WorkGroupID.xy), vec4(reduction_data[0]));
  }
}
