//
// Created by Macx on 2018/10/6.
//

#include <vector>
#include <cmath>
#include <cstdlib>
#include <cstddef>
#include <limits>
#include <algorithm>
#include <time.h>
//#include <Eigen/Dense>
//#include <Eigen/Geometry>
#include <iostream>
#include "Model.h"
#include <ofxProfiler.h>
#include <GL/glcorearb.h>
#include "Math.hpp"
#include "Constants.hpp"
#include "tiny_obj_loader.h"

#if defined __linux__ || defined __APPLE__
// "Compiled for Linux
#else
// Windows doesn't define these values by default, Linux does
#define M_PI 3.141592653589793f
#define INFINITY 1e8
#endif

#if defined __linux__ || defined __APPLE__
#include <SDL.h>
#include <zconf.h>
#else
#include "SDL.h"
#undef min
#undef max
#endif

#define WITH_TEXTURE 1

const S32 width = 800;
const S32 height = 800;
const S32 depth = 255;

//using Eigen::Vec3f;
//using Eigen::Mat4f;
using namespace FW;

SDL_PixelFormat *pixFormat = SDL_AllocFormat(SDL_PIXELFORMAT_RGBA8888);

U32 WHITE = SDL_MapRGBA(pixFormat, 255, 255, 255, 255);
U32 BLACK = SDL_MapRGBA(pixFormat, 0, 0, 0, 255);
U32 RED = SDL_MapRGBA(pixFormat, 255, 0, 0, 255);
U32 GREEN = SDL_MapRGBA(pixFormat, 0, 255, 0, 255);
U32 BLUE = SDL_MapRGBA(pixFormat, 0, 0, 255, 255);

Vec3f light_dir(1, -1, 1);
Vec3f eye(1, 1, 3);
Vec3f center(0, 0, 0);

Model *model = NULL;
SDL_Window *gWindow = NULL;
SDL_Renderer *gRender = NULL;
SDL_Texture *gTexture = NULL;

void init() {
  SDL_Init(SDL_INIT_VIDEO);
  gWindow =
      SDL_CreateWindow("SDL", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
                       width, height, SDL_WINDOW_SHOWN);

  gRender = SDL_CreateRenderer(
      gWindow, -1, SDL_RENDERER_SOFTWARE | SDL_RENDERER_TARGETTEXTURE);

  gTexture = SDL_CreateTexture(gRender, SDL_PIXELFORMAT_RGBA8888,
                               SDL_TEXTUREACCESS_STREAMING, width, height);
}

void close() {
  SDL_DestroyRenderer(gRender);
  SDL_DestroyTexture(gTexture);
  SDL_DestroyWindow(gWindow);
  SDL_Quit();
}

void Clear(U32 *pix, U32 color) {
  for (S32 i = 0; i < height; ++i) {
    for (S32 j = 0; j < width; ++j) {
      pix[i * width + j] = color;
    }
  }
}

void put_pixel(U32 *pix, U32 color, S32 x, S32 y) {
  pix[(height - y - 1) * width + x] = color;
}

void Line(S32 x0, S32 y0, S32 x1, S32 y1, U32 *pix, U32 color) {
  bool steep = false;
  if (FW::abs(x0 - x1) < FW::abs(y0 - y1)) {
    FW::swap(x0, y0);
    FW::swap(x1, y1);
    steep = true;
  }
  if (x0 > x1) {
    FW::swap(x0, x1);
    FW::swap(y0, y1);
  }

  for (S32 x = x0; x <= x1; x++) {
    F32 t = (x - x0) / (F32) (x1 - x0);
    S32 y = y0 * (1.f - t) + y1 * t;
    if (steep) {
      put_pixel(pix, color, y, x);
    } else {
      put_pixel(pix, color, x, y);
    }
  }
}

void Triangle(Vec2i t0, Vec2i t1, Vec2i t2, U32 *pix, U32 color) {
  if (t0.y == t1.y && t0.y == t2.y) return; // i dont care about degenerate triangles
  if (t0.y > t1.y) FW::swap(t0, t1);
  if (t0.y > t2.y) FW::swap(t0, t2);
  if (t1.y > t2.y) FW::swap(t1, t2);
  S32 total_height = t2.y - t0.y;
  for (S32 i = 0; i < total_height; i++) {
    bool second_half = i > t1.y - t0.y || t1.y == t0.y;
    S32 segment_height = second_half ? t2.y - t1.y : t1.y - t0.y;
    F32 alpha = (F32) i / total_height;
    F32 beta = (F32) (i - (second_half ? t1.y - t0.y : 0))
        / segment_height; // be careful: with above conditions no division by zero here
    Vec2i A = t0 + ((t2 - t0) * alpha);
    Vec2i B = second_half ? t1 + ((t2 - t1) * beta) : t0 + ((t1 - t0) *
        beta);
    if (A.x > B.x) FW::swap(A, B);
    for (S32 j = A.x; j <= B.x; j++) {
      put_pixel(pix, color, j, t0.y + i); // attention, due to S32 casts t0.y+i != A.y
    }
  }
}

Vec3f barycentric(Vec3i A, Vec3i B, Vec3i C, Vec3i P) {
  Vec3i s[2];
  for (S32 i = 2; i--;) {
    s[i][0] = C[i] - A[i];
    s[i][1] = B[i] - A[i];
    s[i][2] = A[i] - P[i];
  }
  Vec3f u = FW::cross((Vec3f)s[0], (Vec3f)s[1]);
  if (FW::abs(u[2]) != 0) // dont forget that u[2] is integer. If it is zero then triangle ABC is degenerate
  {
    return Vec3f(1.f - (u.x + u.y) / u.z, u.y / u.z, u.x / u.z);
  }
  return Vec3f(-1, 1, 1); // in this case generate negative coordinates, it will be thrown away by the rasterizator
}

F32 Det(Vec3f A, Vec3f B, Vec3f C) {
  return (B.x - A.x) * (C.y - A.y) - (B.y - A.y) * (C.x - A.x);
}

void TriangleFast2(Vec3f *pts, S32 *zbuffer, U32 *pix, U32 *colors) {
  Vec2i bboxmin(FW_S32_MAX, FW_S32_MAX);
  Vec2i bboxmax(FW_S32_MIN, FW_S32_MIN);
  Vec2i clamp(width - 1, height - 1);

  for (S32 i = 0; i < 3; i++) {
    for (S32 j = 0; j < 2; j++) {
      bboxmin[j] = FW::max(0, FW::min(bboxmin[j], (S32) (pts[i][j] + 0.5)));
      bboxmax[j] = FW::min(clamp[j], FW::max(bboxmax[j], (S32) (pts[i][j] + 0.5)));
    }
  }

  U8 r[3], g[3], b[3], a[3];
  for (S32 i = 0; i < 3; i++) {
    SDL_GetRGBA(colors[i], pixFormat, &r[i], &g[i], &b[i], &a[i]);
  }

  F32 z_inv_0 = 1.0f / pts[0][2];
  F32 z_inv_1 = 1.0f / pts[1][2];
  F32 z_inv_2 = 1.0f / pts[2][2];

  F32 y_0 = pts[0][1];
  F32 y_1 = pts[1][1];
  F32 y_2 = pts[2][1];

  F32 x_0 = pts[0][0];
  F32 x_1 = pts[1][0];
  F32 x_2 = pts[2][0];

  Vec2f n_pq(pts[0].y - pts[1].y, pts[1].x - pts[0].x);
  Vec2f n_qr(pts[1].y - pts[2].y, pts[2].x - pts[1].x);
  Vec2f n_rp(pts[2].y - pts[0].y, pts[0].x - pts[2].x);
  Vec2f v_p = pts[0].getXY();
  Vec2f v_q = pts[1].getXY();
  Vec2f v_r = pts[2].getXY();

  F32 c_pq = -1.f * (n_pq.dot(v_p));
  F32 c_qr = -1.f * (n_qr.dot(v_q));
  F32 c_rp = -1.f * (n_rp.dot(v_r));

  F32 Den = Det(pts[0], pts[1], pts[2]);

  F32 D_Zpinv_x = ((y_2 - y_0) * (z_inv_1 - z_inv_0) + (y_0 - y_1) * (z_inv_2 - z_inv_0)) / Den;
  F32 D_Zpinv_y = ((x_0 - x_2) * (z_inv_1 - z_inv_0) + (x_1 - x_0) * (z_inv_2 - z_inv_0)) / Den;

  F32 D_Zpinv_x_r =
      ((y_2 - y_0) * (z_inv_1 * r[1] - z_inv_0 * r[0]) + (y_0 - y_1) * (z_inv_2 * r[2] - z_inv_0 * r[0])) / Den;
  F32 D_Zpinv_y_r =
      ((x_0 - x_2) * (z_inv_1 * r[1] - z_inv_0 * r[0]) + (x_1 - x_0) * (z_inv_2 * r[2] - z_inv_0 * r[0])) / Den;

  F32 D_Zpinv_x_g =
      ((y_2 - y_0) * (z_inv_1 * g[1] - z_inv_0 * g[0]) + (y_0 - y_1) * (z_inv_2 * g[2] - z_inv_0 * g[0])) / Den;
  F32 D_Zpinv_y_g =
      ((x_0 - x_2) * (z_inv_1 * g[1] - z_inv_0 * g[0]) + (x_1 - x_0) * (z_inv_2 * g[2] - z_inv_0 * g[0])) / Den;

  F32 D_Zpinv_x_b =
      ((y_2 - y_0) * (z_inv_1 * b[1] - z_inv_0 * b[0]) + (y_0 - y_1) * (z_inv_2 * b[2] - z_inv_0 * b[0])) / Den;
  F32 D_Zpinv_y_b =
      ((x_0 - x_2) * (z_inv_1 * b[1] - z_inv_0 * b[0]) + (x_1 - x_0) * (z_inv_2 * b[2] - z_inv_0 * b[0])) / Den;

  F32 z_p;
  F32 z_inv_p;

  z_inv_p = z_inv_0 + (bboxmin.x - x_0) * D_Zpinv_x + (bboxmin.y - y_0) * D_Zpinv_y;
  z_p = 1.0f / z_inv_p;
  F32 r_i_p = (r[0] * z_inv_0 + (bboxmin.x - x_0) * D_Zpinv_x_r + (bboxmin.y - y_0) * D_Zpinv_y_r);
  F32 g_i_p = (g[0] * z_inv_0 + (bboxmin.x - x_0) * D_Zpinv_x_g + (bboxmin.y - y_0) * D_Zpinv_y_g);
  F32 b_i_p = (b[0] * z_inv_0 + (bboxmin.x - x_0) * D_Zpinv_x_b + (bboxmin.y - y_0) * D_Zpinv_y_b);

  Vec3i P;
  for (P.x = bboxmin.x; P.x <= bboxmax.x; P.x++) {
    F32 z_inv_p_base = z_inv_p;
    F32 r_i_p_base = r_i_p;
    F32 g_i_p_base = g_i_p;
    F32 b_i_p_base = b_i_p;
    z_inv_p += D_Zpinv_x;
    r_i_p += D_Zpinv_x_r;
    g_i_p += D_Zpinv_x_g;
    b_i_p += D_Zpinv_x_b;
    for (P.y = bboxmin.y; P.y <= bboxmax.y; P.y++) {
      P.z = 0;

      z_p = 1.0f / z_inv_p_base;
      P.z = z_p;
      U32 color = SDL_MapRGBA(pixFormat, r_i_p_base * z_p, g_i_p_base * z_p, b_i_p_base * z_p, 255);

      z_inv_p_base += D_Zpinv_y;
      r_i_p_base += D_Zpinv_y_r;
      g_i_p_base += D_Zpinv_y_g;
      b_i_p_base += D_Zpinv_y_b;

      Vec2f s = P.getXY();
      F32 e_pq_s = n_pq.dot(s) + c_pq;
      if (e_pq_s >= 0) {
        F32 e_qr_s = n_qr.dot(s) + c_qr;
        if (e_qr_s >= 0) {
          F32 e_rp_s = n_rp.dot(s) + c_rp;
          if (e_rp_s < 0)
            continue;
        } else continue;
      } else continue;

      S32 idx = P.x + P.y * width;
      if (zbuffer[idx] < P.z) {
        zbuffer[idx] = P.z;
        put_pixel(pix, color, P.x, P.y);
      }
    }
  }
}
#if 0
void Triangle(Vec3i *pts, S32 *zbuffer, U32 *pix, U32 *colors) {
  Vec2i bboxmin(FW::numeric_limits<S32>::max, FW::numeric_limits<S32>::max);
  Vec2i bboxmax(-FW::numeric_limits<S32>::max, -FW::numeric_limits<S32>::max);
  Vec2i clamp(width - 1, height - 1);
  for (S32 i = 0; i < 3; i++) {
    for (S32 j = 0; j < 2; j++) {
      bboxmin[j] = FW::max(0, FW::min(bboxmin[j], pts[i][j]));
      bboxmax[j] = FW::min(clamp[j], FW::max(bboxmax[j], pts[i][j]));
    }
  }
  Vec3i P;
  F32 z_inv;
  for (P.x = bboxmin.x; P.x <= bboxmax.x; P.x++) {
    for (P.y = bboxmin.y; P.y <= bboxmax.y; P.y++) {
      Vec3f bc_screen = barycentric(pts[0], pts[1], pts[2], P);
      if (bc_screen.x < 0 || bc_screen.y < 0 || bc_screen.z < 0) continue;
      P.z = 0;
      z_inv = 0;
      for (S32 i = 0; i < 3; i++) z_inv += 1.0 / pts[i][2] * bc_screen[i];
      U8 r_add = 0, g_add = 0, b_add = 0, a_add = 0;
      {
        U8 r, g, b, a;
        for (S32 i = 0; i < 3; i++) {
          SDL_GetRGBA(colors[i], pixFormat, &r, &g, &b, &a);
          F32 pfx = bc_screen[i] / pts[i][2] / z_inv;
          r_add += r * pfx;
          g_add += g * pfx;
          b_add += b * pfx;
          a_add += a * pfx;
        }
      }
      P.z = 1.0 / z_inv;
      U32 color = SDL_MapRGBA(pixFormat, r_add, g_add, b_add, a_add);
      S32 idx = P.x + P.y * width;
      if (zbuffer[idx] < P.z) {
        zbuffer[idx] = P.z;
        put_pixel(pix, color, P.x, P.y);
      }
    }
  }
}
#endif // 0

//template<class A, class B>
//inline A lerp(const A &a, const A &b, const B &t) { return (A) (a * ((B) 1 - t) + b * t); }

Vec3i setupPleq(const Vec3f& values, const Vec2i& v0, const Vec2i& d1, const Vec2i& d2, S32 area, int samplesLog2)
{
  F64 t0 = (F64)values.x;
  F64 t1 = (F64)values.y - t0;
  F64 t2 = (F64)values.z - t0;
  F64 xc = (t1 * (F64)d2.y - t2 * (F64)d1.y) / (F64)area;
  F64 yc = (t2 * (F64)d1.x - t1 * (F64)d2.x) / (F64)area;

  Vec2i center = (v0 * 2 + min(d1.x, d2.x, 0) + max(d1.x, d2.x, 0)) >> (CR_SUBPIXEL_LOG2 - samplesLog2 + 1);
  Vec2i vc = v0 - (center << (CR_SUBPIXEL_LOG2 - samplesLog2));

  Vec3i pleq;
  pleq.x = (U32)(S64)FW::floor(xc * exp2(CR_SUBPIXEL_LOG2 - samplesLog2) + 0.5);
  pleq.y = (U32)(S64)FW::floor(yc * exp2(CR_SUBPIXEL_LOG2 - samplesLog2) + 0.5);
  pleq.z = (U32)(S64)FW::floor(t0 - xc * (F64)vc.x - yc * (F64)vc.y + 0.5);
  pleq.z -= pleq.x * center.x + pleq.y * center.y;
  return pleq;
}

bool setupTriangle(Vec3f *pts) {

  Vec3f v0 = pts[0];
  Vec3f v1 = pts[1];
  Vec3f v2 = pts[2];

  Vec2i p0 = Vec2i(v0.x, v0.y);
  Vec2i p1 = Vec2i(v1.x, v1.y);
  Vec2i p2 = Vec2i(v2.x, v2.y);
  Vec2i d1 = p1 - p0;
  Vec2i d2 = p2 - p0;

  S32 area = d1.x * d2.y - d1.y * d2.x;

  if (area <= 0)
    return false;

  Vec3f zvert = FW::lerp(Vec3f(CR_DEPTH_MIN), Vec3f(CR_DEPTH_MIN), Vec3f(v0.z, v1.z, v2.z)* 0.5f + 0.5f);
//  zvert.print();
  return true;
}

void TriangleFast3(Vec3f *pts, S32 *zbuffer, U32 *pix, U32 *colors) {
  Vec2i bboxmin(FW_S32_MAX, FW_S32_MAX);
  Vec2i bboxmax(FW_S32_MIN, FW_S32_MIN);
  Vec2i clamp(width - 1, height - 1);

  for (S32 i = 0; i < 3; i++) {
    for (S32 j = 0; j < 2; j++) {
      bboxmin[j] = FW::max(0, FW::min(bboxmin[j], (S32) (pts[i][j] + 0.5)));
      bboxmax[j] = FW::min(clamp[j], FW::max(bboxmax[j], (S32) (pts[i][j] + 0.5)));
    }
  }

  U8 r[3], g[3], b[3], a[3];
  for (S32 i = 0; i < 3; i++) {
    SDL_GetRGBA(colors[i], pixFormat, &r[i], &g[i], &b[i], &a[i]);
  }

  F32 z_inv_0 = 1.0f / pts[0][2];
  F32 z_inv_1 = 1.0f / pts[1][2];
  F32 z_inv_2 = 1.0f / pts[2][2];

  F32 y_0 = pts[0][1];
  F32 y_1 = pts[1][1];
  F32 y_2 = pts[2][1];

  F32 x_0 = pts[0][0];
  F32 x_1 = pts[1][0];
  F32 x_2 = pts[2][0];

  Vec2f n_pq(pts[0].y - pts[1].y, pts[1].x - pts[0].x);
  Vec2f n_qr(pts[1].y - pts[2].y, pts[2].x - pts[1].x);
  Vec2f n_rp(pts[2].y - pts[0].y, pts[0].x - pts[2].x);
  Vec2f v_p = pts[0].getXY();
  Vec2f v_q = pts[1].getXY();
  Vec2f v_r = pts[2].getXY();

  F32 c_pq = -1.f * (n_pq.dot(v_p));
  F32 c_qr = -1.f * (n_qr.dot(v_q));
  F32 c_rp = -1.f * (n_rp.dot(v_r));
  setupTriangle(pts);
  F32 Den = Det(pts[0], pts[1], pts[2]);
  F32 Den_1 = 1.0f / Den;

  F32 D_Zpinv_x = ((y_2 - y_0) * (z_inv_1 - z_inv_0) + (y_0 - y_1) * (z_inv_2 - z_inv_0)) * Den_1;
  F32 D_Zpinv_y = ((x_0 - x_2) * (z_inv_1 - z_inv_0) + (x_1 - x_0) * (z_inv_2 - z_inv_0)) * Den_1;

  F32 D_Zpinv_x_r =
      ((y_2 - y_0) * (z_inv_1 * r[1] - z_inv_0 * r[0]) + (y_0 - y_1) * (z_inv_2 * r[2] - z_inv_0 * r[0])) * Den_1;
  F32 D_Zpinv_y_r =
      ((x_0 - x_2) * (z_inv_1 * r[1] - z_inv_0 * r[0]) + (x_1 - x_0) * (z_inv_2 * r[2] - z_inv_0 * r[0])) * Den_1;

  F32 D_Zpinv_x_g =
      ((y_2 - y_0) * (z_inv_1 * g[1] - z_inv_0 * g[0]) + (y_0 - y_1) * (z_inv_2 * g[2] - z_inv_0 * g[0])) * Den_1;
  F32 D_Zpinv_y_g =
      ((x_0 - x_2) * (z_inv_1 * g[1] - z_inv_0 * g[0]) + (x_1 - x_0) * (z_inv_2 * g[2] - z_inv_0 * g[0])) * Den_1;

  F32 D_Zpinv_x_b =
      ((y_2 - y_0) * (z_inv_1 * b[1] - z_inv_0 * b[0]) + (y_0 - y_1) * (z_inv_2 * b[2] - z_inv_0 * b[0])) * Den_1;
  F32 D_Zpinv_y_b =
      ((x_0 - x_2) * (z_inv_1 * b[1] - z_inv_0 * b[0]) + (x_1 - x_0) * (z_inv_2 * b[2] - z_inv_0 * b[0])) * Den_1;

  F32 z_p;
  F32 z_inv_p;

  z_inv_p = z_inv_0 + (bboxmin.x - x_0) * D_Zpinv_x + (bboxmin.y - y_0) * D_Zpinv_y;
  z_p = 1.0 / z_inv_p;
  F32 r_i_p = (r[0] * z_inv_0 + (bboxmin.x - x_0) * D_Zpinv_x_r + (bboxmin.y - y_0) * D_Zpinv_y_r);
  F32 g_i_p = (g[0] * z_inv_0 + (bboxmin.x - x_0) * D_Zpinv_x_g + (bboxmin.y - y_0) * D_Zpinv_y_g);
  F32 b_i_p = (b[0] * z_inv_0 + (bboxmin.x - x_0) * D_Zpinv_x_b + (bboxmin.y - y_0) * D_Zpinv_y_b);

  Vec3i P;
  for (P.x = bboxmin.x; P.x <= bboxmax.x; P.x++) {
    F32 z_inv_p_base = z_inv_p;
    F32 r_i_p_base = r_i_p;
    F32 g_i_p_base = g_i_p;
    F32 b_i_p_base = b_i_p;

    z_inv_p += D_Zpinv_x;
    r_i_p += D_Zpinv_x_r;
    g_i_p += D_Zpinv_x_g;
    b_i_p += D_Zpinv_x_b;

    for (P.y = bboxmin.y; P.y <= bboxmax.y; P.y++) {
      P.z = 0;

      z_p = 1.0 / z_inv_p_base;
      P.z = z_p;
      U32 color = SDL_MapRGBA(pixFormat, r_i_p_base * z_p, g_i_p_base * z_p, b_i_p_base * z_p, 255);

      z_inv_p_base += D_Zpinv_y;
      r_i_p_base += D_Zpinv_y_r;
      g_i_p_base += D_Zpinv_y_g;
      b_i_p_base += D_Zpinv_y_b;

      Vec2f s = P.getXY();
      F32 e_pq_s = n_pq.dot(s) + c_pq;
      if (e_pq_s >= 0) {
        F32 e_qr_s = n_qr.dot(s) + c_qr;
        if (e_qr_s >= 0) {
          F32 e_rp_s = n_rp.dot(s) + c_rp;
          if (e_rp_s < 0)
            continue;
        } else continue;
      } else continue;

      S32 idx = P.x + P.y * width;
      if (zbuffer[idx] < P.z) {
        zbuffer[idx] = P.z;
        put_pixel(pix, color, P.x, P.y);
      }
    }
  }
}

template<typename T>
class Device {
 public:
  void sr_glViewport(GLint x,
                     GLint y,
                     GLsizei width,
                     GLsizei height) {
    viewPort.m_x = x;
    viewPort.m_y = y;
    viewPort.m_width = width;
    viewPort.m_height = height;
  }

  void sr_glDepthRange(T nearVal,
                       T farVal) {
    viewPort.m_nearVal = nearVal;
    viewPort.m_farVal = farVal;
  }
 private:
  struct ViewPort {
    GLint m_x = 0;
    GLint m_y = 0;
    GLsizei m_width = 1;
    GLsizei m_height = 1;
    T m_nearVal = 0.0;
    T m_farVal = 1.0;
  };
 protected:
  ViewPort viewPort;
};

Mat4f viewport(S32 x, S32 y, S32 w, S32 h) {
  Mat4f m;
  m.setIdentity();
  m(0, 3) = x + w / 2.f;
  m(1, 3) = y + h / 2.f;
  m(2, 3) = depth / 2.f;

  m(0, 0) = w / 2.f;
  m(1, 1) = h / 2.f;
  m(2, 2) = depth / 2.f;
  return m;
}

Vec3f world2screen(Vec3f v) {
  return Vec3f(S32((v.x + 1.f) * width / 2.f + .5f), S32((v.y + 1.f) * height / 2.f + .5f), v.z);
}

Mat4f lookat(Vec3f eye, Vec3f center, Vec3f up) {
  Vec3f z = (eye - center).normalized();
  z.normalize();
  Vec3f x = cross(up,z);
  x.normalize();
  Vec3f y = z.cross(x);
  y.normalize();
  Mat4f res;
  res.setIdentity();
  for (S32 i = 0; i < 3; i++) {
    res(0, i) = x[i];
    res(1, i) = y[i];
    res(2, i) = z[i];
    res(i, 3) = -center[i];
  }
  return res;
}

S32 main(S32 argc, char **argv) {
  if (2 == argc) {
    model = new Model(argv[1]);
  } else {
#if defined __linux__ || defined __APPLE__
    model = new Model("../../obj/african_head.obj");
#else
    model = new Model("../obj/african_head.obj");
#endif
  }

//add device
  Device<GLfloat> m_device;
  m_device.sr_glViewport(0, 0, 800, 800);
  m_device.sr_glDepthRange(0, 1);

  S32 *zbuffer = new S32[width * height];
  light_dir.normalize();
  void *pix;
  S32 pitch;

  init();

  S32 x_current(0);
  S32 y_current(0);

  // Main loop flag
  bool quit = false;

  // Event handler
  SDL_Event e;

  F32 deltaX(1), deltaY(1), deltaZ(3);

  eye = Vec3f( deltaX, deltaY, deltaZ);

  // timer
  clock_t start(0), finish(0);
  double duration;
  S32 frame_count(0);
  // While application is running
  while (!quit) {
    PROFILE_START_FRAME;
    PROFILE_BEGIN("FRAME");
    PROFILE_BEGIN("Prepare");
    frame_count++;
    if (frame_count == 60) {
      finish = clock();
      duration = (finish - start) / CLOCKS_PER_SEC;
      FW::printf("%f  FPS\n", 60 / duration);
      start = clock();
      frame_count = 0;
    }
    // Handle events on queue
    while (SDL_PollEvent(&e) != 0) {
      // User requests quit
      if (e.type == SDL_QUIT || e.key.keysym.sym == SDLK_ESCAPE) {
        quit = true;
      }
      if (e.key.keysym.sym == SDLK_UP) {
        deltaY += 0.2f;
        break;
      }
      if (e.key.keysym.sym == SDLK_DOWN) {
        deltaY -= 0.2f;
        break;
      }
      if (e.key.keysym.sym == SDLK_RIGHT) {
        deltaX += 0.2f;
        break;
      }
      if (e.key.keysym.sym == SDLK_LEFT) {
        deltaX -= 0.2f;
        break;
      }
      if (e.key.keysym.sym == SDLK_w) {
        deltaZ -= 0.2f;
        break;
      }
      if (e.key.keysym.sym == SDLK_s) {
        deltaZ += 0.2f;
        break;
      }
      if (e.type == SDL_MOUSEMOTION || e.button.button == SDL_BUTTON_LEFT) {
        x_current = e.motion.x;
        y_current = e.motion.y;
      }
    }

    SDL_LockTexture(gTexture, NULL, &pix, &pitch);
    // Render begin

    // Clear color
    Clear((U32 *) pix, BLACK);

    // Eye and center position
    eye = Vec3f(deltaX, deltaY, deltaZ);

    Mat4f ModelView = lookat(eye, center, Vec3f(0, 1, 0));
    Mat4f Projection;
    Projection.setIdentity();
    Mat4f ViewPort = viewport(width / 8, height / 8, width * 3 / 4, height * 3 / 4);
    Projection(3, 2) = -1.f / (eye - center).length();
    Mat4f z = ViewPort * Projection * ModelView;

    for (S32 i = 0; i < width * height; i++) {
      zbuffer[i] = std::numeric_limits<S32>::min();
    }
    PROFILE_END();
    PROFILE_BEGIN("Render face");
    // Loop over shapes

    for (S32 i = 0; i < model->nfaces(); i++) {
      std::vector<tinyobj::index_t> face = model->m_faces[i];
      Vec3f screen_coords[3];
      Vec3f world_coords[3];
      U32 colors[3];
      Vec3f n_v[3];
      F32 intensity[3];

      // load texture data
      for (S32 j = 0; j < 3; ++j) {
        tinyobj::index_t _idx = face[j];
        Vec3f v = model->vert(_idx);
        n_v[j] = model->normal(_idx);

        Vec4f v_(v,1);
//        v_ << v, 1;
        Vec4f v_temp;
        v_temp = ViewPort * Projection * ModelView * v_;
        v_temp = v_temp / v_temp.w;
        screen_coords[j] = (v_temp).getXYZ();

        world_coords[j] = v;
        intensity[j] = fabs(n_v[j].dot(light_dir));

    // Render face
//    for (S32 i = 0; i < model->nfaces(); i++) {
//      std::vector<S32> face = model->face(i);
//      Vec3f screen_coords[3];
//      Vec3f world_coords[3];
//      U32 colors[3];
//      Vec3f n_v[3];
//      F32 intensity[3];
//
//      // load texture data
//      for (S32 j = 0; j < 3; ++j) {
//        Vec3f v = model->vert(face[j]);
//        n_v[j] = model->normal(i, j);
//
//        Vec4f v_(v,1);
////        v_ << v, 1;
//        Vec4f v_temp;
//        v_temp = ViewPort * Projection * ModelView * v_;
//        v_temp = v_temp / v_temp.w;
//        screen_coords[j] = (v_temp).getXYZ();
//
//        world_coords[j] = v;
//        intensity[j] = fabs(n_v[j].dot(light_dir));

#if  defined(WITH_TEXTURE)
        if (intensity[j] > 0) {
          colors[j] = SDL_MapRGBA(pixFormat,
                                  static_cast<U8>(model->diffuse(model->uv(_idx))[2] * intensity[j]),
                                  static_cast<U8>(model->diffuse(model->uv(_idx))[1] * intensity[j]),
                                  static_cast<U8>(model->diffuse(model->uv(_idx))[0] * intensity[j]),
                                  model->diffuse(model->uv(_idx))[3]);
        } else {
          colors[j] = SDL_MapRGBA(pixFormat,
                                  model->diffuse(model->uv(_idx))[2],
                                  model->diffuse(model->uv(_idx))[1],
                                  model->diffuse(model->uv(_idx))[0],
                                  model->diffuse(model->uv(_idx))[3]);
        }
#else
		if (intensity[j] > 0) {
			colors[j] = SDL_MapRGBA(pixFormat,
				static_cast<U8>(255.0 * intensity[j]),
				static_cast<U8>(255.0 * intensity[j]),
				static_cast<U8>(255.0 * intensity[j]),
				255);
		}
#endif

      }

      // back face culling
      Vec3f AB = screen_coords[0] - screen_coords[1];
      Vec3f AC = screen_coords[0] - screen_coords[2];
      Vec3f N = AC.cross(AB);
      if (N.z > 0) {
        continue;
      }

      // tessellation
//      Vec3i screen_coords_i[3];
//      screen_coords_i[0] = screen_coords[0];
//      screen_coords_i[1] = screen_coords[1];
//      screen_coords_i[2] = screen_coords[2];
      TriangleFast3(screen_coords, zbuffer, (U32 *) pix, colors);
    }
    PROFILE_END();
//    Vec3f world_coords[3];
//    world_coords[0] << 0, 0, 0.99;
//    world_coords[1] << 1, 0.5, 0.99;
//    world_coords[2] << 0.5, 1, -0.99;
//
//    Vec3f screen_coords[3];
//
//    U32 colors[3];
//    colors[0] = RED;
//    colors[1] = GREEN;
//    colors[2] = BLUE;
//    for (S32 j = 0; j < 3; ++j) {
//      Vec4f v_;
//      v_ << world_coords[j], 1;
//      Vec4f v_temp;
//      v_temp = ViewPort * Projection * ModelView * v_;
//      v_temp = v_temp / v_temp.w;
//      screen_coords[j] = v_temp.block(0, 0, 3, 1);
//    }

    // tessellation
//    TriangleFast2(screen_coords, zbuffer, (U32 *) pix, colors);
    PROFILE_BEGIN("Render present");
    // Render end
    SDL_UnlockTexture(gTexture);
    SDL_RenderCopy(gRender, gTexture, NULL, NULL);
    SDL_RenderPresent(gRender);
    PROFILE_END();
    PROFILE_END();
    cout << ofxProfiler::getResults();

#if defined __linux__ || defined __APPLE__
    usleep(10);
#else
    Sleep(1);
#endif //

  }

  { // dump z-buffer (debugging purposes only)
    TGAImage zbimage(width, height, TGAImage::GRAYSCALE);
    for (S32 i = 0; i < width; i++) {
      for (S32 j = 0; j < height; j++) {
        unsigned char zzzz = zbuffer[i + j * width];
        zbimage.set(i, j, TGAColor(&zzzz, 1));
      }
    }
    zbimage.flip_vertically(); // i want to have the origin at the left bottom corner of the image
    zbimage.write_tga_file("zbuffer.tga");
  }

  close();
  return 0;
}


// Copyright (c) 2017 Graphcore Ltd. All rights reserved.
#include "popnnCycleEstimators.hpp"

#include "PerformanceEstimation.hpp"
#include "PoolingDefUtil.hpp"
#include "poplibs_support/FlopEstimation.hpp"
#include "popnn/NonLinearity.hpp"
#include "popnn/NonLinearityDefUtil.hpp"
#include "popnn/PoolingDef.hpp"
#include <poplibs_support/Algorithm.hpp>

#include <boost/range/adaptor/transformed.hpp>
#include <boost/range/algorithm/max_element.hpp>
#include <boost/range/irange.hpp>
#include <cassert>
#include <cmath>

using namespace poplar;
using namespace poplibs_support;

// Macro to create entries in cycle estimator table
#define INSTANTIATE_NL_CYCLE_ESTIMATOR(v)                                      \
  CYCLE_ESTIMATOR_ENTRY(popnn, v, FLOAT, popnn::NonLinearityType::GELU),       \
      CYCLE_ESTIMATOR_ENTRY(popnn, v, HALF, popnn::NonLinearityType::GELU),    \
      CYCLE_ESTIMATOR_ENTRY(popnn, v, FLOAT, popnn::NonLinearityType::SWISH),  \
      CYCLE_ESTIMATOR_ENTRY(popnn, v, HALF, popnn::NonLinearityType::SWISH)

#define INSTANTIATE_NL_GRAD_CYCLE_ESTIMATOR(v)                                 \
  CYCLE_ESTIMATOR_ENTRY(popnn, v, FLOAT, popnn::NonLinearityType::SIGMOID),    \
      CYCLE_ESTIMATOR_ENTRY(popnn, v, HALF, popnn::NonLinearityType::SIGMOID), \
      CYCLE_ESTIMATOR_ENTRY(popnn, v, FLOAT, popnn::NonLinearityType::RELU),   \
      CYCLE_ESTIMATOR_ENTRY(popnn, v, HALF, popnn::NonLinearityType::RELU),    \
      CYCLE_ESTIMATOR_ENTRY(popnn, v, FLOAT, popnn::NonLinearityType::TANH),   \
      CYCLE_ESTIMATOR_ENTRY(popnn, v, HALF, popnn::NonLinearityType::TANH),    \
      CYCLE_ESTIMATOR_ENTRY(popnn, v, FLOAT, popnn::NonLinearityType::GELU),   \
      CYCLE_ESTIMATOR_ENTRY(popnn, v, HALF, popnn::NonLinearityType::GELU),    \
      CYCLE_ESTIMATOR_ENTRY(popnn, v, FLOAT, popnn::NonLinearityType::SWISH),  \
      CYCLE_ESTIMATOR_ENTRY(popnn, v, HALF, popnn::NonLinearityType::SWISH)

namespace popnn {

static std::uint64_t nonlinearityFlops(const NonLinearityType &nlType) {
  // assume single flop for all non-linearities other than GELU
  if (nlType == NonLinearityType::GELU) {
    return 8;
  } else if (nlType == NonLinearityType::SWISH) {
    return 2;
  }
  return 1;
}

static std::uint64_t nonlinearityGradFlops(const NonLinearityType &nlType) {
  switch (nlType) {
  case NonLinearityType::GELU:
    return 15;
  case NonLinearityType::SWISH:
    return 5;
  case NonLinearityType::RELU:
    return 1;
  case NonLinearityType::SIGMOID:
    return 2;
  case NonLinearityType::TANH:
    return 2;
  default:
    throw poputil::poplibs_error("Unhandled non-linearity type");
  }
}

VertexPerfEstimate
nonLinearity1DCycleEstimator(const VertexIntrospector &vertex,
                             const Target &target, const Type &type,
                             const NonLinearityType &nlType, bool inPlace) {
  bool isNonInPlaceSwish = nlType == NonLinearityType::SWISH && inPlace;
  bool isFloat = type == FLOAT;
  CODELET_FIELD(data);
  const auto numWorkers = target.getNumWorkerContexts();
  const auto vectorWidth = target.getVectorWidth(type);
  const auto n = data.size();

  const auto numVectors = n / vectorWidth;
  const auto remainder = n % vectorWidth;

  // If any worker handles an extra vector due to the remainder
  // we take the longest worker hence rounded up.
  const auto vectorsPerWorker = (numVectors + numWorkers - 1) / numWorkers;

  const auto opCycles = getNonLinearityOpCycles(nlType, isFloat, false);
  const auto vectorLoopCycles = getNonLinearityOpCycles(nlType, isFloat, true);

  // These cycle estimates follow the aligned path. Slightly optimistic.
  // The cost of misalignment is ~9 cycles for half, less for float.
  std::uint64_t cycles = 9; // Supervisor vertex overhead
  std::uint64_t workerCycles =
      3 + // Load input pointer, output pointer and size
              isNonInPlaceSwish
          ? 1
          : 0 +     // Branch into shared code
                5 + // Divide & Remainder to split work between workers
                2 + // Get worker ID
                2 + // Check 64-bit aligned and branch
                5 + // Setup remainders and size for worker
                3 + // Offset worker's pointers and branch if done
                (vectorsPerWorker ? 1 : 0) *
                    (2 + opCycles + // Warm up pipeline, rpt
                     (vectorsPerWorker - 1) * vectorLoopCycles + 1 +
                     opCycles); // Handle remaining element from pipeline

  // possibly unpack pointers
  workerCycles +=
      poputil::internal::getUnpackCost(data.getProfilerVectorLayout(0));

  // Add remainder handling cycles. This handling could be slightly overlapped
  // with other workers if the worker doing the remainder had less vector
  // work than the others. Some of these transcendental ops may take
  // less time anyway so we'll just stick with the simpler estimation.
  if (isFloat) {
    workerCycles +=
        3 + // Test worker ID to handle remainder, test remainder, branch
        ((remainder & 1) ? 1 : 0) * (2 + opCycles); // Handle 32-bit remainder
  } else {
    workerCycles +=
        2 + // Test worker ID to handle remainder with
        1 + // branch for 32-bit remainder
        ((remainder & 2) ? 1 : 0) * (2 + opCycles) + // Handle 32-bit remainder
        1 + // branch for 16-bit remainder
        ((remainder & 1) ? 1 : 0) * (3 + opCycles); // Handle 16-bit remainder
  }

  std::uint64_t flops = n * nonlinearityFlops(nlType);

  return {cycles + (workerCycles * numWorkers),
          convertToTypeFlops(flops, type)};
}

VertexPerfEstimate
MAKE_PERF_ESTIMATOR_NAME(NonLinearity1D)(const VertexIntrospector &vertex,
                                         const Target &target, const Type &type,
                                         const NonLinearityType &nlType) {
  return nonLinearity1DCycleEstimator(vertex, target, type, nlType, false);
}
VertexPerfEstimate MAKE_PERF_ESTIMATOR_NAME(NonLinearity1DInPlace)(
    const VertexIntrospector &vertex, const Target &target, const Type &type,
    const NonLinearityType &nlType) {
  return nonLinearity1DCycleEstimator(vertex, target, type, nlType, true);
}

VertexPerfEstimate MAKE_PERF_ESTIMATOR_NAME(NonLinearityGrad1D)(
    const VertexIntrospector &vertex, const Target &target, const Type &type,
    const NonLinearityType &nlType) {
  bool isFloat = type == FLOAT;
  const auto vectorWidth = target.getVectorWidth(type);
  const auto numWorkers = target.getNumWorkerContexts();
  CODELET_FIELD(inGrad);
  CODELET_FIELD(outGrad);
  CODELET_FIELD(out);
  const auto n = inGrad.size();
  assert(outGrad.size() == n);
  assert(out.size() == n);

  const auto inGradLayout = inGrad.getProfilerVectorLayout(0);
  assert(inGradLayout == outGrad.getProfilerVectorLayout(0) &&
         inGradLayout == out.getProfilerVectorLayout(0));

  const auto numVectors = n / vectorWidth;
  const auto remainder = n % vectorWidth;
  const auto vectorsPerWorker = (numVectors + numWorkers - 1) / numWorkers;
  const auto opCycles = getNonLinearityGradOpCycles(nlType, isFloat, false);
  const auto vectorCycles = getNonLinearityGradOpCycles(nlType, isFloat, true);

  std::uint64_t cycles = 9; // Supervisor vertex overhead
  std::uint64_t workerCycles =
      3 + // Load vertex state
      5 + // Split work between workers
      2 + // Get worker ID
      3 + // Add remaining vectors to relevant workers
      3 + // Offset pointers to data
      3 + // Pre-load inputs, and generate ones if needed
      1 + // Branch if no vectors
      (vectorsPerWorker ? 1 : 0) * (4 + // Warm up the pipeline
                                    (vectorsPerWorker - 1) * vectorCycles +
                                    1); // Store remaining element

  // get real pointers from scaled pointers
  if (inGradLayout == layout::Vector::ScaledPtr64) {
    workerCycles += poputil::internal::getUnpackCost(inGradLayout) + 2;
  }

  if (isFloat) {
    workerCycles += 2 + // Pick a worker to handle the remainder, branch
                    2 + // Check for remainder
                    (remainder ? 1 : 0) * (opCycles + 1);
  } else {
    workerCycles += 2 + // Pick a worker to handle remainders, branch
                    2 + // Check for 32-bit remainder
                    ((remainder & 2) ? 1 : 0) * (opCycles + 2) +
                    2 + // Check for 16-bit remainder
                    ((remainder & 1) ? 1 : 0) * (opCycles + 4);
  }

  return {cycles + (workerCycles * numWorkers),
          convertToTypeFlops(n * nonlinearityGradFlops(nlType), type)};
}

VertexPerfEstimate
nonLinearity2DCycleEstimator(const VertexIntrospector &vertex,
                             const Target &target, const Type &type,
                             const NonLinearityType &nlType, bool inPlace) {
  bool isNonInPlaceSwish = nlType == NonLinearityType::SWISH && inPlace;
  bool isFloat = type == FLOAT;
  const auto vectorWidth = target.getVectorWidth(type);
  CODELET_FIELD(data);
  const auto n0 = data.size();
  assert(n0 > 0);

  std::uint64_t cycles = 5; // Vertex overhead

  const auto opCycles = getNonLinearityOpCycles(nlType, isFloat, false);
  const auto vectorLoopCycles = getNonLinearityOpCycles(nlType, isFloat, true);

  cycles += isNonInPlaceSwish
                ? 1
                : 0 +     // Load out pointer
                      2 + // Load base pointer, DeltaN pointer
                      5 + // Unpack base pointer, n0, DeltaN pointer
                      2;  // Set mask for inner loop, sub for brnzdec

  // Following 64-bit aligned path
  unsigned totalElements = 0;
  const std::uint64_t flopsPerElement = nonlinearityFlops(nlType);
  for (std::size_t i = 0; i < n0; ++i) {
    const auto n1 = data[i].size();
    const auto numVectors = n1 / vectorWidth;
    const auto remainder = n1 % vectorWidth;

    cycles += 4 +                 // Load DeltaN, calculate inner pointer and n1
              1 +                 // Load next out ptr or copy data ptr
              (isFloat ? 0 : 2) + // Test 32-bit aligned
              (isFloat ? 2 : 3) + // Test 64-bit aligned
              2 +                 // Shift to get num vectors, branch if 0
              (numVectors ? 1 : 0) * (2 + opCycles + // Warm up pipeline
                                      (numVectors - 1) * vectorLoopCycles + 1 +
                                      opCycles); // Handle last element

    if (isFloat) {
      cycles += 2 + // Check for remainder, branch
                (remainder ? 1 : 0) * (2 + opCycles);
    } else {
      cycles += 2 + // Check for 32-bit remainder, branch
                ((remainder & 2) ? 1 : 0) * (2 + opCycles) +
                2 + // Check for 16-bit remainder, branch
                ((remainder & 1) ? 1 : 0) * (3 + opCycles);
    }

    cycles += 1; // brnzdec

    // assume one flop per element regardless of non-linearity type
    totalElements += n1;
  }

  return {cycles, convertToTypeFlops(totalElements * flopsPerElement, type)};
}
VertexPerfEstimate
MAKE_PERF_ESTIMATOR_NAME(NonLinearity2D)(const VertexIntrospector &vertex,
                                         const Target &target, const Type &type,
                                         const NonLinearityType &nlType) {
  return nonLinearity2DCycleEstimator(vertex, target, type, nlType, false);
}
VertexPerfEstimate MAKE_PERF_ESTIMATOR_NAME(NonLinearity2DInPlace)(
    const VertexIntrospector &vertex, const Target &target, const Type &type,
    const NonLinearityType &nlType) {
  return nonLinearity2DCycleEstimator(vertex, target, type, nlType, true);
}

VertexPerfEstimate MAKE_PERF_ESTIMATOR_NAME(NonLinearityGrad2D)(
    const VertexIntrospector &vertex, const Target &target, const Type &type,
    const NonLinearityType &nlType) {
  bool isFloat = type == FLOAT;
  const auto vectorWidth = target.getVectorWidth(type);
  CODELET_FIELD(inGrad);
  CODELET_FIELD(outGrad);
  CODELET_FIELD(out);
  const auto n0 = inGrad.size();
  assert(outGrad.size() == n0);
  assert(out.size() == n0);
  assert(n0 > 0);

  std::uint64_t cycles = 5; // Vertex overhead

  cycles += 4 + // Load vertex state
            3 + // Load DeltaN base/n0, generate ones if needed
            3 + // Calculate DeltaN pointer
            2;  // Set mask for inner loop, sub for brnzdec

  const auto opCycles = getNonLinearityGradOpCycles(nlType, isFloat, false);
  const auto vectorCycles = getNonLinearityGradOpCycles(nlType, isFloat, true);

  unsigned totalElements = 0;
  for (std::size_t i = 0; i < n0; ++i) {
    const auto n1 = inGrad[i].size();
    assert(outGrad[i].size() == n1);
    assert(out[i].size() == n1);
    const auto numVectors = n1 / vectorWidth;
    const auto remainder = n1 % vectorWidth;

    cycles += 6 + // Load DeltaN, calculate inner pointer/n1, shift for n1 vecs
              3 + // Pre-load inputs for pipeline, branch if 0
              (numVectors ? 1 : 0) * (4 + // Warm up pipeline
                                          // Store last element
                                      (numVectors - 1) * vectorCycles + 1);

    if (isFloat) {
      cycles += 2 + // Check for remainder
                (remainder ? 1 : 0) * (1 + opCycles);
    } else {
      cycles += 2 + // Check for 32-bit remainder
                ((remainder & 2) ? 1 : 0) * (2 + opCycles) +
                2 + // Check for 16-bit remainder
                ((remainder & 1) ? 1 : 0) * (4 + opCycles);
    }
    totalElements += n1;
    cycles += 1; // brnzdec
  }

  return {cycles, convertToTypeFlops(
                      totalElements * nonlinearityGradFlops(nlType), type)};
}

VertexPerfEstimate
poolingCycleEstimator(const VertexIntrospector &vertex, const Target &target,
                      const Type &dataType, const PoolingType &pType,
                      bool isBwdPass, bool isGradientScale = false) {
  CODELET_SCALAR_VAL(initInfo, unsigned short);
  CODELET_SCALAR_VAL(chansPerGroupDM1, unsigned short);
  CODELET_SCALAR_VAL(numChanGroupsM1, unsigned short);
  CODELET_VECTOR_VALS(startPos, unsigned short);
  CODELET_VECTOR_2D_VALS(workList, unsigned short);
  CODELET_FIELD(out);
  CODELET_FIELD(in);

  const auto numWorkers = target.getNumWorkerContexts();

  const auto startPosLayout =
      vertex.getFieldInfo("startPos").getProfilerVectorLayout(0);
  const auto workListLayout =
      vertex.getFieldInfo("workList").getProfilerVectorListLayout();

  UnpackCosts unpackCosts;
  unpackCosts.outLayout =
      poputil::internal::getUnpackCost(out.getProfilerVectorLayout(0));

  unpackCosts.inLayout =
      poputil::internal::getUnpackCost(in.getProfilerVectorLayout(0));
  unpackCosts.fwdOutLayout = unpackCosts.outLayout;
  unpackCosts.fwdInLayout = unpackCosts.inLayout;

  unpackCosts.startPosLayout = poputil::internal::getUnpackCost(startPosLayout);
  unpackCosts.workListLayout = poputil::internal::getUnpackCost(workListLayout);

  auto cycles = getPoolingCycles(
      initInfo, chansPerGroupDM1 + 1, numChanGroupsM1, startPos, workList,
      unpackCosts, pType == PoolingType::MAX, isBwdPass, numWorkers);

  // compute flops
  unsigned totalWorkItems = 0;
  for (unsigned wId = 0; wId < numWorkers; ++wId) {
    const unsigned numRows =
        wId == 0 ? startPos[0] : startPos[wId] - startPos[wId - 1];
    for (unsigned row = 0; row < numRows; ++row) {
      const unsigned sPos = wId == 0 ? 0 : startPos[wId - 1];
      for (unsigned w = 0; w != workList[sPos + row].size(); w += 3) {
        totalWorkItems += workList[sPos + row][w + 2];
      }
    }
  }
  std::uint64_t numChanGroups = numChanGroupsM1 + 1;
  const auto scaleFactor = dataType == poplar::HALF ? 4 : 2;
  const auto chansPerGroup = (chansPerGroupDM1 + 1) * scaleFactor;
  std::uint64_t flops = 0;

  if (dataType == FLOAT || dataType == HALF) {
    flops += numChanGroups * totalWorkItems * chansPerGroup;

    if (pType == PoolingType::AVG || pType == PoolingType::SUM ||
        isGradientScale) {
      // additional flops for division/scale
      flops += numChanGroups * chansPerGroup * initInfo;
    }
  }
  return {cycles, convertToTypeFlops(flops, dataType)};
}

VertexPerfEstimate
MAKE_PERF_ESTIMATOR_NAME(MaxPooling)(const VertexIntrospector &vertex,
                                     const Target &target, const Type &type) {
  return poolingCycleEstimator(vertex, target, type, PoolingType::MAX, false);
}

VertexPerfEstimate MAKE_PERF_ESTIMATOR_NAME(MaxPoolingGradientScale)(
    const VertexIntrospector &vertex, const Target &target, const Type &type) {
  return poolingCycleEstimator(vertex, target, type, PoolingType::MAX, false,
                               true);
}

VertexPerfEstimate
MAKE_PERF_ESTIMATOR_NAME(SumPooling)(const VertexIntrospector &vertex,
                                     const Target &target, const Type &type) {
  return poolingCycleEstimator(vertex, target, type, PoolingType::SUM, false);
}

VertexPerfEstimate MAKE_PERF_ESTIMATOR_NAME(SelectiveScaling)(
    const VertexIntrospector &vertex, const Target &target, const Type &type) {
  // TODO: T5436 Improve this estimate.
  return 10;
}

VertexPerfEstimate MAKE_PERF_ESTIMATOR_NAME(MaxPoolingGrad)(
    const VertexIntrospector &vertex, const Target &target, const Type &type) {
  (void)type;
  return poolingCycleEstimator(vertex, target, type, PoolingType::MAX, true);
}

VertexPerfEstimate MAKE_PERF_ESTIMATOR_NAME(LossSumSquaredTransform)(
    const VertexIntrospector &vertex, const Target &target,
    const Type &fpType) {
  const bool isFloat = fpType == FLOAT;
  const auto size = vertex.getFieldInfo("probs").size();
  const auto isSoftmax = false;
  std::uint64_t flops = static_cast<std::uint64_t>(size) * 3;
  auto cycles = getLossTransformCycles(isFloat, isSoftmax, size);
  return {cycles, convertToTypeFlops(flops, fpType)};
}

VertexPerfEstimate MAKE_PERF_ESTIMATOR_NAME(LossCrossEntropyTransform)(
    const VertexIntrospector &vertex, const Target &target,
    const Type &fpType) {
  const bool isFloat = fpType == FLOAT;
  const auto size = vertex.getFieldInfo("probs").size();
  const auto isSoftmax = true;
  std::uint64_t flops = static_cast<std::uint64_t>(size) * 6 + 2;
  auto cycles = getLossTransformCycles(isFloat, isSoftmax, size);
  return {cycles, convertToTypeFlops(flops, fpType)};
}

VertexPerfEstimate MAKE_PERF_ESTIMATOR_NAME(ReduceMaxClassGather)(
    const VertexIntrospector &vertex, const Target &target, const Type &inType,
    const Type &labelType) {
  std::uint64_t supervisorCycles = 5 + // Vertex overhead
                                   4;  // Supervisor call + sync
  CODELET_FIELD(activations);
  CODELET_SCALAR_VAL(size, unsigned);
  CODELET_SCALAR_VAL(workerSize, unsigned);
  const auto numWorkers = target.getNumWorkerContexts();
  // Check the divisor chosen is large enough to process all inputs
  // with the target number of workers and the grain size.
  assert(workerSize * numWorkers >= size);
  std::uint64_t cycles;
  if (inType == FLOAT || inType == HALF) {
    // Assembly, supervisor implementation
    // Size is the size of the whole tensor, divisor indicates the region an
    // individual worker operates on.  So each worker does divisor inner loop
    // passes unless size is small, in which case one worker does size inner
    // loop passes, and all the others do nothing.
    cycles = 3 + // Load acts pointer, size, divisor
             2 + // Get worker ID
             4 + // Calculate the worker's region
             3 + // Calculate N, sub 1 for first element, branch if no work.
             1 + // Offset pointer for worker
             3 + // Load first element as max, setup pointers
             1 + // rpt
             std::min(workerSize - 1, size - 1) * 3 +
             3 + // Handle remaining element from loop
             6 + // Calculate max index from max act pointer
             4;  // Load maxValue/maxIndex pointers, store (+f16->f32 for half)
  } else {
    // Compiled, 1 worker (pseudo supervisor) version for other types
    const auto nOutputs = (size + workerSize - 1) / workerSize;
    cycles = 22 +                                // Net overhead
             nOutputs * ((workerSize * 6) + 25); // Inner, outer loop overhead
  }
  return {cycles * numWorkers + supervisorCycles,
          convertToTypeFlops(size, inType)};
}

VertexPerfEstimate MAKE_PERF_ESTIMATOR_NAME(ReduceMaxNClassGather)(
    const VertexIntrospector &vertex, const Target &target, const Type &fpType,
    const bool sorted) {
  CODELET_FIELD(activations);
  CODELET_SCALAR_VAL(divisorLog2, unsigned short);
  CODELET_SCALAR_VAL(numK, unsigned short);

#ifndef NDEBUG
  const auto numWorkers = target.getNumWorkerContexts();
#endif
  const auto divisor = (1u << divisorLog2);
  // Check the divisor chosen is large enough to process all inputs
  // with the target number of workers and the grain size.
  assert(divisor * numWorkers >= activations.size());

  const auto nOutputs = (activations.size() + divisor - 1) / divisor;

  std::uint64_t cycles = 10 + // Initial set up.
                         2;   // Enter nOutputsloop.

  // Gather is assumed to have (roughly) the same cycles as sparse but in a
  // loop. It also doesn't benefit from compile time optimizations.
  for (unsigned i = 0; i < nOutputs; ++i) {
    cycles += 23; // Rough estimate for the first add.

    // For the first K we have a guaranteed push op.
    for (unsigned i = 1; i < numK; ++i) {
      cycles += 13 +              // Setup
                std::log(i) * 20; // log(i) loop.
    }

    // Assumes the worst case. This would be the case of activations being
    // sorted in assending order.
    cycles += (activations.size() - numK) * (13 + std::log(numK) * 20);

    // As we are working on the indices we do a bit at the end to store the
    // actual values as well and transform the indices.
    cycles += 8 * numK;

    if (sorted) {
      for (int i = numK; i >= 1; --i) {
        cycles += 10;               // Setup.
        cycles += 20 * std::log(i); // log(k-1) pop operation.
      }
    }
  }

  return cycles;
}

VertexPerfEstimate MAKE_PERF_ESTIMATOR_NAME(ReduceMaxNClassSparse)(
    const VertexIntrospector &vertex, const Target &target, const Type &type,
    const bool sorted) {
  CODELET_SCALAR_VAL(numK, unsigned short);
  CODELET_FIELD(activations);

  std::uint64_t cycles = 10 + // Initial set up.
                         2;   // Enter N loop.

  cycles += 23; // Rough estimate for the first add.

  // For the first K we have a guaranteed push op.
  for (int i = 1; i < numK; ++i) {
    cycles += 13 +              // Setup
              std::log(i) * 20; // log(i) loop.
  }

  // Assumes the worst case. This would be the case of activations being
  // sorted in assending order.
  cycles += (activations.size() - numK) * (13 + std::log(numK) * 20);

  // As we are working on the indices we do a bit at the end to store the actual
  // values as well and transform the indices.
  cycles += 8 * numK;

  // Sorting is very expensive but even if requested by the user it will ony be
  // performed on the very last reduction.
  if (sorted) {
    for (int i = numK; i >= 1; --i) {
      cycles += 10;               // Setup.
      cycles += 20 * std::log(i); // log(k-1) pop operation.
    }
  }
  return cycles;
}

VertexPerfEstimate MAKE_PERF_ESTIMATOR_NAME(ReduceMaxClassSparse)(
    const VertexIntrospector &vertex, const Target &target,
    const Type &inOutType, const Type &labelType) {
  std::uint64_t cycles = 5; // Vertex overhead
  CODELET_FIELD(activations);
  CODELET_FIELD(labels);
  const auto numActs = activations.size();
  assert(numActs == labels.size());
  if (inOutType == HALF || inOutType == FLOAT) {
    // Assembly implementation
    cycles += 2 + // Load acts start/end pointer
              3 + // Calculate N, sub 1 for first element
              3 + // Load first element as max, setup pointers
              1 + // rpt
              (numActs - 1) * 3 + 3 + // Handle remaining element from loop
              6 + // Calculate max index from max act pointer
              4;  // Load maxValue/maxIndex pointers, store
  } else {
    // Compiled versions for other types
    cycles += 18 +         // Net overhead
              numActs * 6; // Loop cycles
  }
  return cycles;
}

VertexPerfEstimate MAKE_PERF_ESTIMATOR_NAME(ReduceMinClassGather)(
    const VertexIntrospector &vertex, const Target &target, const Type &inType,
    const Type &labelType) {
  std::uint64_t supervisorCycles = 5 + // Vertex overhead
                                   4;  // Supervisor call + sync
  CODELET_FIELD(activations);
  CODELET_SCALAR_VAL(size, unsigned);
  CODELET_SCALAR_VAL(workerSize, unsigned);
  const auto numWorkers = target.getNumWorkerContexts();
  // Check the divisor chosen is large enough to process all inputs
  // with the target number of workers and the grain size.
  assert(workerSize * numWorkers >= size);
  std::uint64_t cycles;
  if (inType == FLOAT || inType == HALF) {
    // Assembly, supervisor implementation
    // Size is the size of the whole tensor, divisor indicates the region an
    // individual worker operates on.  So each worker does divisor inner loop
    // passes unless size is small, in which case one worker does size inner
    // loop passes, and all the others do nothing.
    cycles = 3 + // Load acts pointer, size, divisor
             2 + // Get worker ID
             4 + // Calculate the worker's region
             3 + // Calculate N, sub 1 for first element, branch if no work.
             1 + // Offset pointer for worker
             3 + // Load first element as max, setup pointers
             1 + // rpt
             std::min(workerSize - 1, size - 1) * 3 +
             3 + // Handle remaining element from loop
             6 + // Calculate min index from min act pointer
             4;  // Load minValue/minIndex pointers, store (+f16->f32 for half)
  } else {
    // Compiled, 1 worker (pseudo supervisor) version for other types
    const auto nOutputs = (size + workerSize - 1) / workerSize;
    cycles = 22 +                                // Net overhead
             nOutputs * ((workerSize * 6) + 25); // Inner, outer loop overhead
  }
  return {cycles * numWorkers + supervisorCycles,
          convertToTypeFlops(size, inType)};
}

VertexPerfEstimate MAKE_PERF_ESTIMATOR_NAME(ReduceMinClassSparse)(
    const VertexIntrospector &vertex, const Target &target,
    const Type &inOutType, const Type &labelType) {
  std::uint64_t cycles = 5; // Vertex overhead
  CODELET_FIELD(activations);
  CODELET_FIELD(labels);
  const auto numActs = activations.size();
  assert(numActs == labels.size());
  if (inOutType == HALF || inOutType == FLOAT) {
    // Assembly implementation
    cycles += 2 + // Load acts start/end pointer
              3 + // Calculate N, sub 1 for first element
              3 + // Load first element as max, setup pointers
              1 + // rpt
              (numActs - 1) * 3 + 3 + // Handle remaining element from loop
              6 + // Calculate min index from min act pointer
              4;  // Load minValue/minIndex pointers, store
  } else {
    // Compiled version for other types
    cycles += 18 +         // Total overhead
              numActs * 6; // Loop cycles
  }
  return {cycles, convertToTypeFlops(numActs, inOutType)};
}

VertexPerfEstimate
MAKE_PERF_ESTIMATOR_NAME(CalcAccuracy)(const VertexIntrospector &vertex,
                                       const Target &target,
                                       const Type &labelType) {
  std::uint64_t cycles = 5; // Vertex overhead
  CODELET_FIELD(maxPerBatch);
  CODELET_FIELD(expected);
  const auto batchSize = maxPerBatch.size();
  assert(batchSize == expected.size());

  cycles += 4 + // Load maxPerBatch start/end, sub, shift for num elements
            2 + // Load expected and numCorrect pointer
            1 + // Load initial numCorrect value
            1;  // rpt

  cycles += batchSize * (2 + // Load maxPerBatch/expected
                         1 + // cmpeq
                         1); // add

  cycles += 1; // Store final numCorrect

  // calc accuracy does no FP operations
  return cycles;
}
VertexPerfEstimate MAKE_PERF_ESTIMATOR_NAME(CTCAlpha)(
    const VertexIntrospector &vertex, const Target &target, const Type &inType,
    const Type &outType, const Type symbolType, const bool isLastLabel) {
  CODELET_FIELD(label);
  // Label contains the previous symbol as a dependency, not an extra result
  return {alphaCycles(1, label.size() - 1, false),
          alphaFlops(1, label.size() - 1, outType, false)};
}
VertexPerfEstimate MAKE_PERF_ESTIMATOR_NAME(CTCBeta)(
    const VertexIntrospector &vertex, const Target &target, const Type &inType,
    const Type &outType, const Type symbolType, const bool isFirstLabel) {
  CODELET_FIELD(label);
  // Label contains the previous symbol as a dependency, not an extra result
  return {betaCycles(1, label.size() - 1, false),
          betaFlops(1, label.size() - 1, outType, false)};
}
VertexPerfEstimate MAKE_PERF_ESTIMATOR_NAME(CTCGradGivenAlpha)(
    const VertexIntrospector &vertex, const Target &target, const Type &inType,
    const Type &outType, const Type symbolType, const bool isFirstLabel) {
  CODELET_FIELD(label);
  // Label contains the previous symbol as a dependency, not an extra result
  return {gradGivenAlphaCycles(1, label.size() - 1, false),
          gradGivenAlphaFlops(1, label.size() - 1, outType, false)};
}
VertexPerfEstimate MAKE_PERF_ESTIMATOR_NAME(CTCGradGivenBeta)(
    const VertexIntrospector &vertex, const Target &target, const Type &inType,
    const Type &outType, const Type symbolType, const bool isLastLabel) {
  CODELET_FIELD(label);
  // Label contains the previous symbol as a dependency, not an extra result
  return {gradGivenBetaCycles(1, label.size() - 1, false),
          gradGivenBetaFlops(1, label.size() - 1, outType, false)};
}

VertexPerfEstimate MAKE_PERF_ESTIMATOR_NAME(CTCGenerateCopyCandidates)(
    const VertexIntrospector &vertex, const Target &target, const Type &inType,
    const Type &partialsType, const Type symbolType) {
  // TODO: cycle estimator
  return {0, 0};
}
VertexPerfEstimate MAKE_PERF_ESTIMATOR_NAME(CTCGenerateExtendCandidates)(
    const VertexIntrospector &vertex, const Target &target, const Type &inType,
    const Type &partialsType, const Type symbolType) {
  // TODO: cycle estimator
  return {0, 0};
}
VertexPerfEstimate MAKE_PERF_ESTIMATOR_NAME(CTCMergeCandidates)(
    const VertexIntrospector &vertex, const Target &target,
    const Type &partialsType, const Type symbolType) {
  // TODO: cycle estimator
  return {0, 0};
}
VertexPerfEstimate MAKE_PERF_ESTIMATOR_NAME(CTCSelectCopyCandidates)(
    const VertexIntrospector &vertex, const Target &target,
    const Type &partialsType, const Type symbolType) {
  // TODO: cycle estimator
  return {0, 0};
}
VertexPerfEstimate MAKE_PERF_ESTIMATOR_NAME(CTCSelectExtendCandidates)(
    const VertexIntrospector &vertex, const Target &target,
    const Type &partialsType, const Type symbolType) {
  // TODO: cycle estimator
  return {0, 0};
}
VertexPerfEstimate MAKE_PERF_ESTIMATOR_NAME(CTCRankCandidates)(
    const VertexIntrospector &vertex, const Target &target,
    const Type &partialsType, const Type symbolType) {
  CODELET_SCALAR_VAL(totalCandidates, unsigned);
  CODELET_SCALAR_VAL(firstCandidateToRank, unsigned);
  CODELET_SCALAR_VAL(lastCandidateToRank, unsigned);
  CODELET_SCALAR_VAL(beamwidth, unsigned);

  const std::size_t supervisorCycles =
      14 +                  // Vertex state load
      (5 + 6) * beamwidth + // Initialise to zero loop
      8;                    // runall, exit if complete

  const auto numToRank = lastCandidateToRank - firstCandidateToRank;
  const auto numWorkers = target.getNumWorkerContexts();
  const auto maxToRankPerWorker =
      poplibs_support::ceildiv(numToRank, numWorkers);

  std::size_t workerCycles = 13;           // Non loop
  workerCycles += maxToRankPerWorker * 28; // Outer loop body, including odd one
  workerCycles +=
      maxToRankPerWorker * 3 * ((totalCandidates - 1) / 2); // rpt loop content

  workerCycles += 15; // Assume each worker will copy only once

  std::size_t flops = totalCandidates * numToRank * 2;
  return {supervisorCycles + workerCycles * numWorkers, flops};
}
VertexPerfEstimate MAKE_PERF_ESTIMATOR_NAME(CTCReduceCandidates)(
    const VertexIntrospector &vertex, const Target &target,
    const Type &partialsType, const Type symbolType) {
  CODELET_SCALAR_VAL(totalCandidates, unsigned);

  const std::size_t supervisorCycles = 24 + // Exit if complete
                                       12;  // runall, exit

  std::size_t workerCycles = 13 + // Pre loop, processing 1
                             (totalCandidates - 1) / 2 + // Actual loop
                             6;                          // Post loop

  const auto numWorkers = target.getNumWorkerContexts();
  const unsigned numItemsToReduce = 5;
  return {supervisorCycles + workerCycles * numWorkers,
          totalCandidates * numItemsToReduce};
}
VertexPerfEstimate MAKE_PERF_ESTIMATOR_NAME(CTCUpdate)(
    const VertexIntrospector &vertex, const Target &target,
    const Type &partialsType, const Type symbolType) {

  CODELET_SCALAR_VAL(beamwidth, unsigned);

  const std::size_t supervisorCycles = 12 + // Load pointers, vertex state
                                       16;  // Exit branch and function calls
  // Slowest worker path
  const std::size_t workerCycles = 16 +            // Pre loop
                                   beamwidth * 7 + // Loop body
                                   1;              // Post loop

  const auto numWorkers = target.getNumWorkerContexts();
  return {supervisorCycles + numWorkers * workerCycles, 0};
}
VertexPerfEstimate
MAKE_PERF_ESTIMATOR_NAME(CTCGenerateOutput)(const VertexIntrospector &vertex,
                                            const Target &target,
                                            const Type symbolType) {
  // TODO: cycle estimator
  return {0, 0};
}

poputil::internal::PerfEstimatorTable makePerfFunctionTable() {
  return {
      CYCLE_ESTIMATOR_ENTRY(popnn, LossSumSquaredTransform, FLOAT),
      CYCLE_ESTIMATOR_ENTRY(popnn, LossSumSquaredTransform, HALF),

      CYCLE_ESTIMATOR_ENTRY(popnn, LossCrossEntropyTransform, FLOAT),
      CYCLE_ESTIMATOR_ENTRY(popnn, LossCrossEntropyTransform, HALF),

      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMaxClassGather, FLOAT, UNSIGNED_INT),
      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMaxClassGather, HALF, UNSIGNED_INT),
      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMaxClassGather, INT, UNSIGNED_INT),
      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMaxClassGather, UNSIGNED_INT,
                            UNSIGNED_INT),
      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMaxClassGather, FLOAT, INT),
      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMaxClassGather, HALF, INT),
      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMaxClassGather, INT, INT),
      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMaxClassGather, UNSIGNED_INT, INT),

      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMaxClassSparse, FLOAT, UNSIGNED_INT),
      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMaxClassSparse, UNSIGNED_INT,
                            UNSIGNED_INT),
      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMaxClassSparse, UNSIGNED_INT, INT),
      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMaxClassSparse, FLOAT, INT),
      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMaxClassSparse, INT, UNSIGNED_INT),
      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMaxClassSparse, INT, INT),

      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMaxNClassGather, FLOAT, false),
      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMaxNClassGather, FLOAT, true),

      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMaxNClassGather, HALF, false),
      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMaxNClassGather, HALF, true),

      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMaxNClassGather, INT, false),
      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMaxNClassGather, INT, true),

      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMaxNClassGather, UNSIGNED_INT, false),
      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMaxNClassGather, UNSIGNED_INT, true),

      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMaxNClassSparse, FLOAT, false),
      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMaxNClassSparse, FLOAT, true),

      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMaxNClassSparse, HALF, false),
      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMaxNClassSparse, HALF, true),

      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMaxNClassSparse, INT, false),
      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMaxNClassSparse, INT, true),

      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMaxNClassSparse, UNSIGNED_INT, false),
      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMaxNClassSparse, UNSIGNED_INT, true),

      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMinClassGather, FLOAT, UNSIGNED_INT),
      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMinClassGather, HALF, UNSIGNED_INT),
      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMinClassGather, INT, UNSIGNED_INT),
      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMinClassGather, UNSIGNED_INT,
                            UNSIGNED_INT),
      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMinClassGather, FLOAT, INT),
      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMinClassGather, HALF, INT),
      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMinClassGather, INT, INT),
      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMinClassGather, UNSIGNED_INT, INT),

      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMinClassSparse, FLOAT, UNSIGNED_INT),
      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMinClassSparse, INT, UNSIGNED_INT),
      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMinClassSparse, UNSIGNED_INT,
                            UNSIGNED_INT),
      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMinClassSparse, FLOAT, INT),
      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMinClassSparse, UNSIGNED_INT, INT),
      CYCLE_ESTIMATOR_ENTRY(popnn, ReduceMinClassSparse, INT, INT),

      CYCLE_ESTIMATOR_ENTRY(popnn, CalcAccuracy, UNSIGNED_INT),
      CYCLE_ESTIMATOR_ENTRY(popnn, CalcAccuracy, INT),

      CYCLE_ESTIMATOR_ENTRY(popnn, MaxPoolingGrad, FLOAT),
      CYCLE_ESTIMATOR_ENTRY(popnn, MaxPoolingGrad, HALF),

      CYCLE_ESTIMATOR_ENTRY(popnn, SumPooling, FLOAT),
      CYCLE_ESTIMATOR_ENTRY(popnn, SumPooling, HALF),

      CYCLE_ESTIMATOR_ENTRY(popnn, MaxPooling, FLOAT),
      CYCLE_ESTIMATOR_ENTRY(popnn, MaxPooling, HALF),

      CYCLE_ESTIMATOR_ENTRY(popnn, MaxPoolingGradientScale, FLOAT),
      CYCLE_ESTIMATOR_ENTRY(popnn, MaxPoolingGradientScale, HALF),

      CYCLE_ESTIMATOR_ENTRY(popnn, SelectiveScaling, FLOAT),
      CYCLE_ESTIMATOR_ENTRY(popnn, SelectiveScaling, HALF),

      CYCLE_ESTIMATOR_ENTRY(popnn, CTCAlpha, FLOAT, FLOAT, UNSIGNED_INT, true),
      CYCLE_ESTIMATOR_ENTRY(popnn, CTCAlpha, HALF, HALF, UNSIGNED_INT, true),
      CYCLE_ESTIMATOR_ENTRY(popnn, CTCAlpha, HALF, FLOAT, UNSIGNED_INT, true),
      CYCLE_ESTIMATOR_ENTRY(popnn, CTCBeta, FLOAT, FLOAT, UNSIGNED_INT, true),
      CYCLE_ESTIMATOR_ENTRY(popnn, CTCBeta, HALF, HALF, UNSIGNED_INT, true),
      CYCLE_ESTIMATOR_ENTRY(popnn, CTCBeta, HALF, FLOAT, UNSIGNED_INT, true),
      CYCLE_ESTIMATOR_ENTRY(popnn, CTCGradGivenAlpha, FLOAT, FLOAT,
                            UNSIGNED_INT, true),
      CYCLE_ESTIMATOR_ENTRY(popnn, CTCGradGivenAlpha, HALF, HALF, UNSIGNED_INT,
                            true),
      CYCLE_ESTIMATOR_ENTRY(popnn, CTCGradGivenAlpha, HALF, FLOAT, UNSIGNED_INT,
                            true),
      CYCLE_ESTIMATOR_ENTRY(popnn, CTCGradGivenBeta, FLOAT, FLOAT, UNSIGNED_INT,
                            true),
      CYCLE_ESTIMATOR_ENTRY(popnn, CTCGradGivenBeta, HALF, HALF, UNSIGNED_INT,
                            true),
      CYCLE_ESTIMATOR_ENTRY(popnn, CTCGradGivenBeta, HALF, FLOAT, UNSIGNED_INT,
                            true),

      CYCLE_ESTIMATOR_ENTRY(popnn, CTCAlpha, FLOAT, FLOAT, UNSIGNED_INT, false),
      CYCLE_ESTIMATOR_ENTRY(popnn, CTCAlpha, HALF, HALF, UNSIGNED_INT, false),
      CYCLE_ESTIMATOR_ENTRY(popnn, CTCAlpha, HALF, FLOAT, UNSIGNED_INT, false),
      CYCLE_ESTIMATOR_ENTRY(popnn, CTCBeta, FLOAT, FLOAT, UNSIGNED_INT, false),
      CYCLE_ESTIMATOR_ENTRY(popnn, CTCBeta, HALF, HALF, UNSIGNED_INT, false),
      CYCLE_ESTIMATOR_ENTRY(popnn, CTCBeta, HALF, FLOAT, UNSIGNED_INT, false),
      CYCLE_ESTIMATOR_ENTRY(popnn, CTCGradGivenAlpha, FLOAT, FLOAT,
                            UNSIGNED_INT, false),
      CYCLE_ESTIMATOR_ENTRY(popnn, CTCGradGivenAlpha, HALF, HALF, UNSIGNED_INT,
                            false),
      CYCLE_ESTIMATOR_ENTRY(popnn, CTCGradGivenAlpha, HALF, FLOAT, UNSIGNED_INT,
                            false),
      CYCLE_ESTIMATOR_ENTRY(popnn, CTCGradGivenBeta, FLOAT, FLOAT, UNSIGNED_INT,
                            false),
      CYCLE_ESTIMATOR_ENTRY(popnn, CTCGradGivenBeta, HALF, HALF, UNSIGNED_INT,
                            false),
      CYCLE_ESTIMATOR_ENTRY(popnn, CTCGradGivenBeta, HALF, FLOAT, UNSIGNED_INT,
                            false),
      CYCLE_ESTIMATOR_ENTRY(popnn, CTCGenerateCopyCandidates, FLOAT, FLOAT,
                            UNSIGNED_INT),
      CYCLE_ESTIMATOR_ENTRY(popnn, CTCGenerateCopyCandidates, HALF, FLOAT,
                            UNSIGNED_INT),
      CYCLE_ESTIMATOR_ENTRY(popnn, CTCGenerateCopyCandidates, HALF, HALF,
                            UNSIGNED_INT),
      CYCLE_ESTIMATOR_ENTRY(popnn, CTCGenerateExtendCandidates, FLOAT, FLOAT,
                            UNSIGNED_INT),
      CYCLE_ESTIMATOR_ENTRY(popnn, CTCGenerateExtendCandidates, HALF, FLOAT,
                            UNSIGNED_INT),
      CYCLE_ESTIMATOR_ENTRY(popnn, CTCGenerateExtendCandidates, HALF, HALF,
                            UNSIGNED_INT),
      CYCLE_ESTIMATOR_ENTRY(popnn, CTCMergeCandidates, FLOAT, UNSIGNED_INT),
      CYCLE_ESTIMATOR_ENTRY(popnn, CTCMergeCandidates, HALF, UNSIGNED_INT),

      CYCLE_ESTIMATOR_ENTRY(popnn, CTCSelectCopyCandidates, FLOAT,
                            UNSIGNED_INT),
      CYCLE_ESTIMATOR_ENTRY(popnn, CTCSelectCopyCandidates, HALF, UNSIGNED_INT),

      CYCLE_ESTIMATOR_ENTRY(popnn, CTCSelectExtendCandidates, FLOAT,
                            UNSIGNED_INT),
      CYCLE_ESTIMATOR_ENTRY(popnn, CTCSelectExtendCandidates, HALF,
                            UNSIGNED_INT),

      CYCLE_ESTIMATOR_ENTRY(popnn, CTCRankCandidates, FLOAT, UNSIGNED_INT),
      CYCLE_ESTIMATOR_ENTRY(popnn, CTCRankCandidates, HALF, UNSIGNED_INT),

      CYCLE_ESTIMATOR_ENTRY(popnn, CTCReduceCandidates, FLOAT, UNSIGNED_INT),
      CYCLE_ESTIMATOR_ENTRY(popnn, CTCReduceCandidates, HALF, UNSIGNED_INT),

      CYCLE_ESTIMATOR_ENTRY(popnn, CTCUpdate, HALF, UNSIGNED_INT),
      CYCLE_ESTIMATOR_ENTRY(popnn, CTCUpdate, FLOAT, UNSIGNED_INT),
      CYCLE_ESTIMATOR_ENTRY(popnn, CTCGenerateOutput, UNSIGNED_INT),

      INSTANTIATE_NL_GRAD_CYCLE_ESTIMATOR(NonLinearityGrad1D),
      INSTANTIATE_NL_CYCLE_ESTIMATOR(NonLinearity1DInPlace),
      CYCLE_ESTIMATOR_ENTRY(popnn, NonLinearity1D, FLOAT,
                            popnn::NonLinearityType::SWISH),
      CYCLE_ESTIMATOR_ENTRY(popnn, NonLinearity1D, HALF,
                            popnn::NonLinearityType::SWISH),

      INSTANTIATE_NL_GRAD_CYCLE_ESTIMATOR(NonLinearityGrad2D),
      INSTANTIATE_NL_CYCLE_ESTIMATOR(NonLinearity2DInPlace),
      CYCLE_ESTIMATOR_ENTRY(popnn, NonLinearity2D, FLOAT,
                            popnn::NonLinearityType::SWISH),
      CYCLE_ESTIMATOR_ENTRY(popnn, NonLinearity2D, HALF,
                            popnn::NonLinearityType::SWISH)};
}

} // end namespace popnn