/*
** A scratch program for experimenting with the OpenGL API/behavior
*/

#include "base/base_inc.h"
#include "os/os_inc.h"

#include "gfx/gfx_abs.h"

#include "opengl/ogl_inc.h"

#include "art/color.h"

#include "base/base_inc.c"
#include "base/base_big_functions.c"
#include "os/os_inc.c"

#include "gfx/win32/win32_gfx_impl.h"
#include "gfx/win32/win32_gfx_impl.c"

#include "opengl/ogl_inc.c"

#include "art/color.c"

////////////////////////////////
// NOTE(allen): Experiment Configurations

#define FINE_GEOMETRY 0
#define FINE_GEOMETRY_ALPHA 0.5

////////////////////////////////
// NOTE(allen): OpenGL Renderer State

#define R_Flag_SharpCornerTL (1 << 0)
#define R_Flag_SharpCornerTR (1 << 1)
#define R_Flag_SharpCornerBL (1 << 2)
#define R_Flag_SharpCornerBR (1 << 3)
#define R_Flag_HzGradient    (1 << 4)

typedef struct Rect{
  // shape
  V2F32 p0;
  V2F32 p1;
  F32 radius;
  F32 thick;
  F32 theta;
  
  // color
  V4F32 c[2];
  
  // uv
  V2F32 uv0;
  V2F32 uv1;
  
  // flags
  U32 flags;
  
  // clip
  V2F32 clip0;
  V2F32 clip1;
} Rect;

typedef struct Vertex{
  // primitive shape
  V2F32 p;
  
  // shape
  V2F32 r_center;
  V2F32 r_half_dim;
  F32 radius;
  F32 thick;
  F32 theta;
  
  // color
  U32 c[2];
  
  // uv
  V2F32 uv0;
  V2F32 uv1;
  
  // flags
  U32 flags;
  
  // clip
  V2F32 clip0;
  V2F32 clip1;
} Vertex;

#define GLSL_UNPACK_COLOR(u32c, c0, c1, c2, c3) \
"float " c0 " = ((" u32c " >>  0)&0xFFu)/255.0;\n"   \
"float " c1 " = ((" u32c " >>  8)&0xFFu)/255.0;\n"   \
"float " c2 " = ((" u32c " >> 16)&0xFFu)/255.0;\n"   \
"float " c3 " = ((" u32c " >> 24)&0xFFu)/255.0;\n"

static char scratch_glsl_vert_vshader[] =
"#version 330\n"
"uniform vec2 u_view_xform;\n"
"layout (location = 0) in vec2 v_p;\n"
"layout (location = 1) in uint v_c;\n"
"out vec4 f_c;\n"
"void main(){\n"
// compute normalized pos
"vec2 norm_pos = v_p*u_view_xform + vec2(-1.0, -1.0);\n"
// unpack color
GLSL_UNPACK_COLOR("v_c", "c0", "c1", "c2", "c3")
// fill outputs
"gl_Position = vec4(norm_pos, 0.0, 1.0);\n"
"f_c = vec4(c0, c1, c2, c3);\n"
"}\n"
;

static char scratch_glsl_vert_fshader[] =
"#version 330\n"
"in vec4 f_c;\n"
"out vec4  out_color;\n"
"void main(){\n"
"out_color = f_c;\n"
"}\n"
;

OGL_Shader vert_vshader = {0};
OGL_Shader vert_fshader = {0};
OGL_Shader vert_program = {0};

GLint vert_u_view_xform = -1;



static char scratch_glsl_sdf_vshader[] =
"#version 330\n"
"uniform vec2 u_view_xform;\n"
"layout (location = 0)  in vec2  v_p;\n"
"layout (location = 1)  in vec2  v_rect_center;\n"
"layout (location = 2)  in vec2  v_rect_half_dim;\n"
"layout (location = 3)  in float v_radius;\n"
"layout (location = 4)  in float v_thick;\n"
"layout (location = 5)  in float v_theta;\n"
"layout (location = 6)  in uint  v_c0;\n"
"layout (location = 7)  in uint  v_c1;\n"
"layout (location = 8)  in uint  v_flags;\n"
"layout (location = 9)  in vec4  v_uvrect;\n"
"layout (location = 10) in vec4  v_clip_rect;\n"
"flat out vec2  f_rect_center;\n"
"flat out vec2  f_rect_half_dim;\n"
"flat out float f_radius;\n"
"flat out float f_thick;\n"
"flat out float f_theta;\n"
"flat out vec4  f_c0;\n"
"flat out vec4  f_c1;\n"
"flat out uint  f_flags;\n"
"flat out vec4  f_uvrect;\n"
"flat out vec4  f_clip_rect;\n"
"void main(){\n"
// compute normalized pos
"vec2 norm_pos = v_p*u_view_xform + vec2(-1.0, -1.0);\n"
// unpack colors
GLSL_UNPACK_COLOR("v_c0", "c00", "c01", "c02", "c03")
GLSL_UNPACK_COLOR("v_c1", "c10", "c11", "c12", "c13")
// fill outputs
"gl_Position = vec4(norm_pos, 0.0, 1.0);\n"
"f_rect_center   = v_rect_center;\n"
"f_rect_half_dim = v_rect_half_dim;\n"
"f_radius = v_radius;\n"
"f_thick = v_thick;\n"
"f_theta = v_theta;\n"
"f_c0 = vec4(c00, c01, c02, c03);\n"
"f_c1 = vec4(c10, c11, c12, c13);\n"
"f_flags = v_flags;\n"
"f_uvrect = v_uvrect;\n"
"f_clip_rect = v_clip_rect;\n"
"}\n"
;

static char scratch_glsl_sdf_fshader[] =
"#version 330\n"
"uniform sampler2D u_tex;\n"
"flat in vec2  f_rect_center;\n"
"flat in vec2  f_rect_half_dim;\n"
"flat in float f_radius;\n"
"flat in float f_thick;\n"
"flat in float f_theta;\n"
"flat in vec4  f_c0;\n"
"flat in vec4  f_c1;\n"
"flat in uint  f_flags;\n"
"flat in vec4  f_uvrect;\n"
"flat in vec4  f_clip_rect;\n"
"in vec4  gl_FragCoord;\n"
"out vec4 out_color;\n"
"void main(){\n"

// discard fragments outside clip rect
"if ("
"(gl_FragCoord.x <  f_clip_rect.x) ||"
"(gl_FragCoord.y <  f_clip_rect.y) ||"
"(gl_FragCoord.x >= f_clip_rect.z) ||"
"(gl_FragCoord.y >= f_clip_rect.w)){ discard; }\n"

// label main properties
"float half_thick = f_thick*0.5;\n"
"bool hz_gradient = ((f_flags&0x10u) != 0u);\n"

// apply p rotation
"vec2  q = gl_FragCoord.xy - f_rect_center;\n"
"float sin_theta = sin(f_theta);\n"
"float cos_theta = cos(f_theta);\n"
"vec2  p = vec2("
"cos_theta*q.x + sin_theta*q.y,"
"-sin_theta*q.x + cos_theta*q.y);\n"

// modify radius for fragment's quadrant
// 10
// 32
"float rad = f_radius;\n"
"uint quadrant = uint(p.x < 0) + 2u*uint(p.y < 0);\n"
"if ((f_flags&(1u << quadrant)) != 0u){ rad = 0; }\n"

// setup rectangle relative sides
"float r_b = -f_rect_half_dim.y + rad;\n"
"float r_t = -r_b;\n"
"float r_l = -f_rect_half_dim.x + rad;\n"
"float r_r = -r_l;\n"

// (p * rect) -> distance
"float r_in_x = max(r_l - p.x, p.x - r_r);\n"
"float r_in_y = max(r_b - p.y, p.y - r_t);\n"
"float r_in_max = max(r_in_x, r_in_y);\n"
"float r_in_dist = min(0, r_in_max);\n"

"vec2  r_ex_np = vec2(clamp(p.x, r_l, r_r), clamp(p.y, r_b, r_t));\n"
"vec2  r_ex_d = p - r_ex_np;\n"
"float r_ex_dist = sqrt(r_ex_d.x*r_ex_d.x + r_ex_d.y*r_ex_d.y);\n"

"float r_dist = r_ex_dist + r_in_dist;\n"

"float dist = r_dist - rad;\n"

// (distance * thick) -> outline_distance
" if (half_thick > 0) dist = abs(dist + half_thick) - half_thick;"

// distance -> alpha
"float sdf_a_unclamped = (0.5 - dist);" // a = unlerp(0.5, dist, -0.5)
"float sdf_a = clamp(sdf_a_unclamped, 0, 1);\n"

// texture sampling
" float uv_xtu = (p.x + f_rect_half_dim.x)/(2*f_rect_half_dim.x);\n"
" float uv_ytu = (p.y + f_rect_half_dim.y)/(2*f_rect_half_dim.y);\n"
" float s = 1;\n"
" if (f_uvrect.z > 0){\n"
"  ivec2 texdim = textureSize(u_tex, 0);\n"
"  vec2  htex = vec2(0.5/texdim.x, 0.5/texdim.y);\n"
"  float uv_xu = f_uvrect.x + (f_uvrect.z - f_uvrect.x)*uv_xtu;\n"
"  float uv_yu = f_uvrect.y + (f_uvrect.w - f_uvrect.y)*uv_ytu;\n"
"  float uv_x = clamp(uv_xu, f_uvrect.x + htex.x, f_uvrect.z - htex.x);\n"
"  float uv_y = clamp(uv_yu, f_uvrect.y + htex.y, f_uvrect.w - htex.y);\n"
"  s = texture(u_tex, vec2(uv_x, uv_y)).r;\n"
" }\n"

// color interpolation
" float color_tu = uv_ytu;\n"
" if (hz_gradient) color_tu = uv_xtu;\n"
" float color_t = clamp(color_tu, 0, 1);\n"
" vec4 color = f_c0 + (f_c1 - f_c0)*color_t;\n"

// final color
" out_color = vec4(color.rgb, sdf_a*color.a*s);\n"

"}\n"
;

OGL_Shader sdf_vshader = {0};
OGL_Shader sdf_fshader = {0};
OGL_Shader sdf_program = {0};

GLint sdf_u_view_xform = -1;
GLint sdf_u_tex = -1;




GLuint vertex_buffer = 0;
GLuint fallback_texture = 0;
GLuint main_framebuffer = 0;
GLuint main_canvas_texture = 0;

GLuint test_texture = 0;

function void
setup_opengl_state(void){
  ArenaTemp scratch = arena_get_scratch(0, 0);
  
  ogl_begin(0);
  
  // setup GPU program "vert"
  {
    vert_vshader =
      ogl_make_shader(scratch.arena,
                      scratch_glsl_vert_vshader,
                      GL_VERTEX_SHADER);
    if (vert_vshader.log.size > 0){
      gfx_message_box(str8_lit("error"), vert_vshader.log);
      os_exit_process(1);
    }
  }
  
  {
    vert_fshader =
      ogl_make_shader(scratch.arena,
                      scratch_glsl_vert_fshader,
                      GL_FRAGMENT_SHADER);
    if (vert_fshader.log.size > 0){
      gfx_message_box(str8_lit("error"), vert_fshader.log);
      os_exit_process(1);
    }
  }
  
  {
    GLuint shaders[2];
    shaders[0] = vert_vshader.handle;
    shaders[1] = vert_fshader.handle;
    vert_program = ogl_make_program(scratch.arena, shaders, 2);
    if (vert_program.log.size > 0){
      gfx_message_box(str8_lit("error"), vert_program.log);
      os_exit_process(1);
    }
  }
  
  // setup GPU program "sdf"
  {
    sdf_vshader =
      ogl_make_shader(scratch.arena,
                      scratch_glsl_sdf_vshader,
                      GL_VERTEX_SHADER);
    if (sdf_vshader.log.size > 0){
      gfx_message_box(str8_lit("error"), sdf_vshader.log);
      os_exit_process(1);
    }
  }
  
  {
    sdf_fshader =
      ogl_make_shader(scratch.arena,
                      scratch_glsl_sdf_fshader,
                      GL_FRAGMENT_SHADER);
    if (sdf_fshader.log.size > 0){
      gfx_message_box(str8_lit("error"), sdf_fshader.log);
      os_exit_process(1);
    }
  }
  
  {
    GLuint shaders[2];
    shaders[0] = sdf_vshader.handle;
    shaders[1] = sdf_fshader.handle;
    sdf_program = ogl_make_program(scratch.arena, shaders, 2);
    if (sdf_program.log.size > 0){
      gfx_message_box(str8_lit("error"), sdf_program.log);
      os_exit_process(1);
    }
  }
  
  // extract uniform locations
  {
    vert_u_view_xform =
      glGetUniformLocation(vert_program.handle, "u_view_xform");
  }
  
  {
    sdf_u_view_xform =
      glGetUniformLocation(sdf_program.handle, "u_view_xform");
    sdf_u_tex =
      glGetUniformLocation(sdf_program.handle, "u_tex");
  }
  
  if (glGetError() != 0){
    gfx_message_box(str8_lit("error"), str8_lit("failed to locate uniforms"));
    os_exit_process(1);
  }
  
  // setup vertex array object
  {
    GLuint vao = 0;
    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);
    if (glGetError() != 0 || vao == 0){
      gfx_message_box(str8_lit("error"),
                      str8_lit("failed to setup vertex array object"));
      os_exit_process(1);
    }
  }
  
  // setup buffers
  {
    glGenBuffers(1, &vertex_buffer);
    glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
    if (glGetError() != 0 || vertex_buffer == 0){
      gfx_message_box(str8_lit("error"), str8_lit("failed to vertex buffer"));
      os_exit_process(1);
    }
  }
  
  // setup blend mode
  {
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glEnable(GL_BLEND);
    if (glGetError() != 0){
      gfx_message_box(str8_lit("error"), str8_lit("failed to setup blending"));
      os_exit_process(1);
    }
  }
  
  // set texture pack & unpack alignment to 1
  {
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    if (glGetError() != 0){
      gfx_message_box(str8_lit("error"), str8_lit("failed to set pixel unpack"));
      os_exit_process(1);
    }
    glPixelStorei(GL_PACK_ALIGNMENT, 1);
    if (glGetError() != 0){
      gfx_message_box(str8_lit("error"), str8_lit("failed to set pixel pack"));
      os_exit_process(1);
    }
  }
  
  // setup a fallback texture
  {
    U8 *texture_data = push_array(scratch.arena, U8, 16);
    for (U64 i = 0; i < 16; i += 1){
      texture_data[i] = 0xFF;
    }
    
    glGenTextures(1, &fallback_texture);
    glBindTexture(GL_TEXTURE_2D, fallback_texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, 4, 4, 0,
                 GL_RED, GL_UNSIGNED_BYTE, texture_data);
    
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    
    if (glGetError() != 0 || fallback_texture == 0){
      gfx_message_box(str8_lit("error"), str8_lit("failed to setup texture"));
      os_exit_process(1);
    }
  }
  
#if 0
  // setup a multi-sample texture
  {
    glGenTextures(1, &main_canvas_texture);
    
    glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, main_canvas_texture);
    glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 8, GL_RGB,
                            2048, 2048, true);
    
    if (glGetError() != 0 || main_canvas_texture == 0){
      gfx_message_box(str8_lit("error"),
                      str8_lit("failed to setup main canvas texture"));
      os_exit_process(1);
    }
  }
#else
  // setup a normal texture
  {
    glGenTextures(1, &main_canvas_texture);
    
    glBindTexture(GL_TEXTURE_2D, main_canvas_texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 2048, 2048, 0, GL_RGB, GL_FLOAT, 0);
    
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  }
#endif
  
  // setup a frame buffer
  {
    glGenFramebuffers(1, &main_framebuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, main_framebuffer);
    glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                         main_canvas_texture, 0);
    
    GLenum framebuffer_status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
    
    if (glGetError() != 0 || main_framebuffer == 0){
      gfx_message_box(str8_lit("error"),
                      str8_lit("failed to setup main framebuffer"));
      os_exit_process(1);
    }
    
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
  }
  
  // setup a test texture
#if 1
  {
    F32 *texture_data = push_array(scratch.arena, F32, 64*64);
    U32 stride = 64;
    
    // half bright
    {
      F32 *tl = texture_data + stride*32;
      for (U32 y = 0; y < 32; y += 1){
        F32 *row = tl + y*stride;
        for (U32 x = 0; x < 32; x += 1){
          F32 *ptr = row + x;
          *ptr = 0.5f;
        }
      }
    }
    
    // checkerboard
    {
      F32 *tl = texture_data + 32;
      for (U32 y = 0; y < 32; y += 1){
        F32 *row = tl + y*stride;
        for (U32 x = 0; x < 32; x += 1){
          F32 *ptr = row + x;
          U32 c = ((x/8) + (y/8))%2;
          if (c == 0){
            *ptr = 0.0f;
          }
          else{
            *ptr = 1.0f;
          }
        }
      }
    }
    
    // top half
    {
      F32 *tl = texture_data;
      for (U32 y = 0; y < 32; y += 1){
        F32 *row = tl + y*stride;
        for (U32 x = 0; x < 32; x += 1){
          F32 *ptr = row + x;
          if (y >= 16){
            *ptr = 1.0f;
          }
          else{
            *ptr = 0.0f;
          }
        }
      }
    }
    
    // outside edge
    {
      F32 *tl = texture_data + stride*32 + 32;
      for (U32 y = 0; y < 32; y += 1){
        F32 *row = tl + y*stride;
        for (U32 x = 0; x < 32; x += 1){
          F32 *ptr = row + x;
          if (y == 0 || y == 31 ||
              x == 0 || x == 31){
            *ptr = 1.0f;
          }
          else{
            *ptr = 0.0f;
          }
        }
      }
    }
    
    glGenTextures(1, &test_texture);
    glBindTexture(GL_TEXTURE_2D, test_texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, 64, 64, 0,
                 GL_RED, GL_FLOAT, texture_data);
    
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    
    if (glGetError() != 0 || test_texture == 0){
      gfx_message_box(str8_lit("error"), str8_lit("failed to setup texture"));
      os_exit_process(1);
    }
  }
#endif
  
  ogl_end();
  
  arena_release_scratch(&scratch);
}

function void
draw_geometry(Vertex *v, U32 count, V2F32 windim){
  glBufferData(GL_ARRAY_BUFFER, count*sizeof(*v), v, GL_STREAM_DRAW);
  
  glUseProgram(vert_program.handle);
  glUniform2f(vert_u_view_xform, 2.f/windim.x, 2.f/windim.y);
  
  glEnableVertexAttribArray(0);
  glVertexAttribPointer(0, 2, GL_FLOAT, 0, sizeof(Vertex),
                        PtrFromInt(OffsetOfMember(Vertex, p)));
  
  glEnableVertexAttribArray(1);
  glVertexAttribIPointer(1, 1, GL_UNSIGNED_INT, sizeof(Vertex),
                         PtrFromInt(OffsetOfMember(Vertex, c[0])));
  
  glDrawArrays(GL_TRIANGLES, 0, count);
}

function void
draw_velrects__preprocess(Rect *r, U32 count, Vertex *v_out){
  for (U32 i = 0; i < count; i += 1){
    Vertex *vv = v_out + i*6;
    
    V2F32 p0 = r[i].p0;
    V2F32 p1 = r[i].p1;
    F32   radius_raw = r[i].radius;
    F32   thick_raw = r[i].thick;
    F32   theta = r[i].theta*tau_F32;
    V4F32 c0_raw = r[i].c[0];
    V4F32 c1_raw = r[i].c[1];
    U32   flags = r[i].flags;
    V2F32 uv0 = r[i].uv0;
    V2F32 uv1 = r[i].uv1;
    V2F32 clip0 = r[i].clip0;
    V2F32 clip1 = r[i].clip1;
    
    // transform corners to center & half dim
    V2F32 center   = v2f32_scalar_mul(0.5f, v2f32_add(p0, p1));
    V2F32 half_dim = v2f32_scalar_mul(0.5f, v2f32_sub(p0, p1));
    
    // clamp radius
    F32 x_span = p1.x - p0.x;
    F32 y_span = p1.y - p0.y;
    F32 min_span = Min(x_span, y_span);
    F32 max_radius = min_span*0.5f;
    F32 radius = ClampTop(radius_raw, max_radius);
    
    // threshold & clamp the thick
    F32 thick = thick_raw;
    {
      if (thick < 1.f){
        thick = 0.f;
      }
      thick = ClampTop(thick, 10000.f);
    }
    
    // set up enclosing primitive
    {
      V2F32 p0c = p0;
      V2F32 p1c = p1;
      
      V2F32 p00g = v2f32(floor_F32(p0c.x), floor_F32(p0c.y));
      V2F32 p11g = v2f32(ceil_F32(p1c.x),  ceil_F32(p1c.y));
      V2F32 p01g = v2f32(p00g.x, p11g.y);
      V2F32 p10g = v2f32(p11g.x, p00g.y);
      
      if (theta != 0.f){
        F32 sin_theta = sin_F32(theta);
        F32 cos_theta = cos_F32(theta);
        
        V2F32 vx = v2f32( half_dim.x*cos_theta, half_dim.x*sin_theta);
        V2F32 vy = v2f32(-half_dim.y*sin_theta, half_dim.y*cos_theta);
        
        p00g = v2f32_sub(v2f32_sub(center, vx), vy);
        p01g = v2f32_add(v2f32_sub(center, vx), vy);
        p10g = v2f32_sub(v2f32_add(center, vx), vy);
        p11g = v2f32_add(v2f32_add(center, vx), vy);
      }
      
      vv[0].p = p00g;
      vv[1].p = p01g;
      vv[2].p = p10g;
      vv[3].p = p01g;
      vv[4].p = p10g;
      vv[5].p = p11g;
    }
    
    // pack the colors
    U32 c0 = color_u32_from_4f32(V4_EXPANDED(c0_raw));
    U32 c1 = color_u32_from_4f32(V4_EXPANDED(c1_raw));
    
#if 1
    // expand clip if all zeroes
    if (clip1.x == 0 && clip1.y == 0){
      clip1.x = 1000000.f;
      clip1.y = 1000000.f;
    }
#endif
    
    // fill vv
    for (U32 j = 0; j < 6; j += 1){
      vv[j].r_center   = center;
      vv[j].r_half_dim = half_dim;
      vv[j].radius = radius;
      vv[j].thick  = thick;
      vv[j].theta  = theta;
      vv[j].c[0] = c0;
      vv[j].c[1] = c1;
      vv[j].flags = flags;
      vv[j].uv0 = uv0;
      vv[j].uv1 = uv1;
      vv[j].clip0 = clip0;
      vv[j].clip1 = clip1;
    }
  }
}

function void
draw_sdfs(Rect *r, U32 count, GLuint texture, V2F32 windim){
  if (texture == 0){
    texture = fallback_texture;
  }
  
  ArenaTemp scratch = arena_get_scratch(0, 0);
  
  Vertex *v = push_array(scratch.arena, Vertex, count*6);
  draw_velrects__preprocess(r, count, v);
  
  glBufferData(GL_ARRAY_BUFFER, count*6*sizeof(*v), v, GL_STREAM_DRAW);
  
  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_2D, texture);
  
  glUseProgram(sdf_program.handle);
  glUniform2f(sdf_u_view_xform, 2.f/windim.x, 2.f/windim.y);
  glUniform1i(sdf_u_tex, 0);
  
  glEnableVertexAttribArray(0);
  glVertexAttribPointer(0, 2, GL_FLOAT, 0, sizeof(Vertex),
                        PtrFromInt(OffsetOfMember(Vertex, p)));
  
  glEnableVertexAttribArray(1);
  glVertexAttribPointer(1, 2, GL_FLOAT, 0, sizeof(Vertex),
                        PtrFromInt(OffsetOfMember(Vertex, r_center)));
  
  glEnableVertexAttribArray(2);
  glVertexAttribPointer(2, 2, GL_FLOAT, 0, sizeof(Vertex),
                        PtrFromInt(OffsetOfMember(Vertex, r_half_dim)));
  
  glEnableVertexAttribArray(3);
  glVertexAttribPointer(3, 1, GL_FLOAT, 0, sizeof(Vertex),
                        PtrFromInt(OffsetOfMember(Vertex, radius)));
  
  glEnableVertexAttribArray(4);
  glVertexAttribPointer(4, 1, GL_FLOAT, 0, sizeof(Vertex),
                        PtrFromInt(OffsetOfMember(Vertex, thick)));
  
  glEnableVertexAttribArray(5);
  glVertexAttribPointer(5, 1, GL_FLOAT, 0, sizeof(Vertex),
                        PtrFromInt(OffsetOfMember(Vertex, theta)));
  
  glEnableVertexAttribArray(6);
  glVertexAttribIPointer(6, 1, GL_UNSIGNED_INT, sizeof(Vertex),
                         PtrFromInt(OffsetOfMember(Vertex, c[0])));
  
  glEnableVertexAttribArray(7);
  glVertexAttribIPointer(7, 1, GL_UNSIGNED_INT, sizeof(Vertex),
                         PtrFromInt(OffsetOfMember(Vertex, c[1])));
  
  glEnableVertexAttribArray(8);
  glVertexAttribIPointer(8, 1, GL_UNSIGNED_INT, sizeof(Vertex),
                         PtrFromInt(OffsetOfMember(Vertex, flags)));
  
  glEnableVertexAttribArray(9);
  glVertexAttribPointer(9, 4, GL_FLOAT, 0, sizeof(Vertex),
                        PtrFromInt(OffsetOfMember(Vertex, uv0)));
  
  glEnableVertexAttribArray(10);
  glVertexAttribPointer(10, 4, GL_FLOAT, 0, sizeof(Vertex),
                        PtrFromInt(OffsetOfMember(Vertex, clip0)));
  
  glDrawArrays(GL_TRIANGLES, 0, count*6);
  
  arena_release_scratch(&scratch);
}

////////////////////////////////
// NOTE(allen): Main

typedef struct TestCtx{
  F32 t;
  F32 t_prev;
  B32 debug_mode;
} TestCtx;

function void
window_resize_handler(GFX_Window *window, U32 width, U32 height){
  // begin render
  ogl_begin(window);
  
  // render
  glClearColor(1.f, 0.f, 0.f, 1.f);
  glClear(GL_COLOR_BUFFER_BIT);
  
  // end render
  ogl_end();
}

function void
fill_test_ctx(U64 frame_counter, TestCtx *ctx){
  // setup time
  ctx->t_prev = (frame_counter % 900)/900.f;
  ctx->t = ((frame_counter + 1) % 900)/900.f;
  
  // toggle debug mode
  ctx->debug_mode = 1;
  if (frame_counter % 600 >= 300){
    ctx->debug_mode = 0;
  }
}

function void
fill_test_rects(TestCtx *ctx, Rect *test_rects){
  F32 t = ctx->t;
  F32 t_prev = ctx->t_prev;
  
  U32 shape_i = 0;
  
  // srgb test gradient
#if 0
  {
    test_rects[shape_i].p0 = v2f32(550.f, 350.f);
    test_rects[shape_i].p1 = v2f32(700.f, 380.f);
    test_rects[shape_i].c[0] = v4f32(1.f, 1.f, 1.f, 1.f);
    test_rects[shape_i].c[1] = v4f32(1.f, 1.f, 1.f, 0.f);
    test_rects[shape_i].flags = R_Flag_HzGradient;
    shape_i += 1;
  }
#endif
  
  // eliptical moving square
#if 0
  {
    V2F32 p = v2f32(200 + cos_F32(t*tau_F32*5)*200,
                    300 + sin_F32(t*tau_F32*5)*300);
    V2F32 p_prev = v2f32(200 + cos_F32(t_prev*tau_F32*5)*200,
                         300 + sin_F32(t_prev*tau_F32*5)*300);
    
    test_rects[shape_i].p0  = p;
    test_rects[shape_i].p1  = p + v2f32(5.f, 5.f);
    test_rects[shape_i].c[0] = v4f32(1.f, 1.0f, 0.f, 1.f);
    test_rects[shape_i].c[1] = v4f32(0.f, 0.2f, 1.0f, 1.f);
    shape_i += 1;
  }
#endif
  
  // still thin lines
#if 0
  {
    F32 x[4] = { 1.f, 3.25f, 5.5f, 7.75f };
    F32 y_min = 8.f;
    F32 y_max = 13.f;
    
    for (U32 i = 0; i < 4; i += 1){
      test_rects[shape_i].p0  = v2f32(x[i], y_min);
      test_rects[shape_i].p1  = v2f32(x[i] + 1.f, y_max);
      test_rects[shape_i].c[0] = v4f32(1.f, 1.f, 1.f, 1.f);
      test_rects[shape_i].c[1] = v4f32(1.f, 1.f, 1.f, 1.f);
      shape_i += 1;
    }
  }
#endif
  
  // slow thin line
#if 0
  {
    F32 x = 5 + sin_F32(t*tau_F32)*5;
    F32 x_prev = 5 + sin_F32(t_prev*tau_F32)*5;
    F32 y_min = -5.f;
    F32 y_max =  5.f;
    
    F32 lx = x;
    F32 rx = x + 1;
    
    F32 vel_x = x - x_prev;
    F32 speed_x = abs_F32(vel_x);
    if (0.005f <= speed_x){
      if (vel_x < 0.f){
        lx -= 0.2f;
      }
      else{
        rx += 0.2f;
      }
    }
    
    test_rects[shape_i].p0  = v2f32(lx, y_min);
    test_rects[shape_i].p1  = v2f32(rx, y_max);
    test_rects[shape_i].c[0] = v4f32(1.f, 1.f, 1.f, 1.f);
    test_rects[shape_i].c[1] = v4f32(1.f, 1.f, 1.f, 1.f);
    shape_i += 1;
  }
#endif
  
  // still square outline
#if 1
  {
    //F32 theta = 0.03f;
    F32 theta = t*0.3f;
    
    F32 x = 50;
    F32 y = 50;
    V2F32 vel = v2f32(0, 0);
    V2F32 hdim = v2f32(50.f, 1.f);
    test_rects[shape_i].p0   = v2f32(x - hdim.x, y - hdim.y);
    test_rects[shape_i].p1   = v2f32(x + hdim.x, y + hdim.y);
    test_rects[shape_i].c[0] = v4f32(1.f, 1.f, 0.f, 1.f);
    test_rects[shape_i].c[1] = v4f32(0.f, 1.f, 1.f, 1.f);
    test_rects[shape_i].radius = 1.f;
    test_rects[shape_i].thick = 10.f;
    test_rects[shape_i].theta = theta;
    test_rects[shape_i].flags = R_Flag_HzGradient;
    shape_i += 1;
  }
#endif
  
  // slow square outline
#if 0
  {
    F32 x      = 5 + sin_F32(t*tau_F32)*5;
    F32 y      = 5 + sin_F32(t*tau_F32*2)*2;
    
    F32 hdim = 6;
    
    V2F32 p = v2f32(x, y);
    
    test_rects[shape_i].p0  = v2f32(x - hdim, y - hdim);
    test_rects[shape_i].p1  = v2f32(x + hdim, y + hdim);
    test_rects[shape_i].c[0] = v4f32(1.f, 0.f, 0.f, 0.5f);
    test_rects[shape_i].c[1] = v4f32(0.f, 1.f, 0.f, 0.2f);
    test_rects[shape_i].radius = 0.f;
    test_rects[shape_i].thick = 2.f;
    shape_i += 1;
  }
#endif
  
  // textured squares
#if 0
  {
    F32 x      = 300.f + sin_F32(t*tau_F32)*15;
    F32 x_prev = 300.f + sin_F32(t_prev*tau_F32)*15;
    F32 y      = 300.f + sin_F32(t*tau_F32*2)*6;
    F32 y_prev = 300.f + sin_F32(t_prev*tau_F32*2)*6;
    
    V2F32 p = v2f32(x, y);
    F32 spacing = 70.f;
    
    F32 hdim = 16;
    
    test_rects[shape_i].p0 = v2f32(p.x - hdim, p.y - hdim);
    test_rects[shape_i].p1 = v2f32(p.x + hdim, p.y + hdim);
    test_rects[shape_i].uv0 = v2f32(0.0f, 0.0f);
    test_rects[shape_i].uv1 = v2f32(0.5f, 0.5f);
    shape_i += 1;
    
    p.x += spacing;
    
    test_rects[shape_i].p0 = v2f32(p.x - hdim, p.y - hdim);
    test_rects[shape_i].p1 = v2f32(p.x + hdim, p.y + hdim);
    test_rects[shape_i].uv0 = v2f32(0.5f, 0.0f);
    test_rects[shape_i].uv1 = v2f32(1.0f, 0.5f);
    shape_i += 1;
    
    p.x -= spacing;
    p.y += spacing;
    
    test_rects[shape_i].p0 = v2f32(p.x - hdim, p.y - hdim);
    test_rects[shape_i].p1 = v2f32(p.x + hdim, p.y + hdim);
    test_rects[shape_i].uv0 = v2f32(0.0f, 0.5f);
    test_rects[shape_i].uv1 = v2f32(0.5f, 1.0f);
    shape_i += 1;
    
    p.x += spacing;
    
    test_rects[shape_i].p0 = v2f32(p.x - hdim, p.y - hdim);
    test_rects[shape_i].p1 = v2f32(p.x + hdim, p.y + hdim);
    test_rects[shape_i].uv0 = v2f32(0.5f, 0.5f);
    test_rects[shape_i].uv1 = v2f32(1.0f, 1.0f);
    shape_i += 1;
    
    
    for (U32 i = shape_i - 4; i < shape_i; i += 1){
      test_rects[i].c[0] = v4f32(0.7f, 0.0f, 0.0f, 1.f);
      test_rects[i].c[1] = v4f32(0.5f, 0.5f, 0.5f, 1.f);
      if ((i%2) == 1){
        test_rects[i].flags |= R_Flag_HzGradient;
      }
    }
    
  }
#endif
}

function void
shift_rect_geometry(Rect *out, Rect *in, U64 count, V2F32 base){
  for (U64 i = 0; i < count; i += 1){
    out[i].p0     = v2f32_add(in[i].p0, base);
    out[i].p1     = v2f32_add(in[i].p1, base);
    out[i].c[0]   = in[i].c[0];
    out[i].c[1]   = in[i].c[1];
    out[i].uv0    = in[i].uv0;
    out[i].uv1    = in[i].uv1;
    out[i].radius = in[i].radius;
    out[i].thick  = in[i].thick;
    out[i].theta  = in[i].theta;
    out[i].flags  = in[i].flags;
    if (in[i].clip1.x != 0 || in[i].clip1.y != 0){
      out[i].clip0 = v2f32_add(in[i].clip0, base);
      out[i].clip1 = v2f32_add(in[i].clip1, base);
    }
    else{
      out[i].clip0 = in[i].clip0;
      out[i].clip1 = in[i].clip1;
    }
  }
}

function void
draw_10_by_10(U8 *buf, V2F32 botleft, F32 grid_size, V2F32 window_dim){
  // outline
  {
    V2F32 p00 = v2f32_sub(botleft, v2f32(2.f, 2.f));
    V2F32 p11 = v2f32_add(botleft,
                          v2f32(2.f + grid_size*10.f, 2.f + grid_size*10.f));
    V2F32 p01 = v2f32(p11.x, p00.y);
    V2F32 p10 = v2f32(p00.x, p11.y);
    Vertex v[6];
    v[0].p = p00; v[1].p = p01; v[2].p = p10;
    v[3].p = p01; v[4].p = p10; v[5].p = p11;
    for (U32 i = 0; i < 6; i += 1){
      v[i].c[0] = 0xFFFFFFFF;
      v[i].c[1] = 0xFFFFFFFF;
    }
    
    draw_geometry(v, 6, window_dim);
  }
  
  // main grid
  for (U32 y = 0; y < 10; y += 1){
    for (U32 x = 0; x < 10; x += 1){
      V2F32 p00 = v2f32_add(botleft, v2f32_scalar_mul(grid_size, v2f32(x, y)));
      V2F32 p01 = v2f32_add(p00, v2f32(grid_size,  0.f));
      V2F32 p10 = v2f32_add(p00, v2f32( 0.f, grid_size));
      V2F32 p11 = v2f32(p01.x, p10.y);
      
      //U8 *color = buf + 4*(x + 10*(9 - y));
      U8 *color = buf + 4*(x + 10*y);
      
      U32 color_u32 = *(U32*)color;
      
      Vertex v[6];
      v[0].p = p00; v[1].p = p01; v[2].p = p10;
      v[3].p = p01; v[4].p = p10; v[5].p = p11;
      for (U32 i = 0; i < 6; i += 1){
        v[i].c[0] = color_u32;
        v[i].c[1] = color_u32;
      }
      
      draw_geometry(v, 6, window_dim);
    }
  }
}

function void
draw_fine_rect_geometry(Rect *test_rects, U32 count, F32 alpha,
                        V2F32 botleft, F32 grid_size,
                        V2F32 window_dim){
  ArenaTemp scratch = arena_get_scratch(0, 0);
  
  // setup color
  U32 alpha_b = (U32)(alpha * 256);
  U32 alpha_c = Clamp(0, alpha_b, 255);
  U32 color = 0x00FF00FF | (alpha_c << 24);
  
  // draw "fine" geometry from rectangle-based geometry
  Vertex *fine_geometry = push_array(scratch.arena, Vertex, 6*count);
  for (U32 i = 0; i < count; i += 1){
    V2F32 p0 = v2f32_add(botleft, v2f32_scalar_mul(grid_size, test_rects[i].p0));
    V2F32 p1 = v2f32_add(botleft, v2f32_scalar_mul(grid_size, test_rects[i].p1));
    
    Vertex *vv = fine_geometry + i*6;
    vv[0].p = p0;
    vv[1].p = v2f32(p0.x, p1.y);
    vv[2].p = v2f32(p1.x, p0.y);
    vv[3].p = v2f32(p0.x, p1.y);
    vv[4].p = v2f32(p1.x, p0.y);
    vv[5].p = p1;
    
    for (U32 j = 0; j < 6; j += 1){
      vv[j].c[0] = color;
      vv[j].c[1] = color;
    }
  }
  draw_geometry(fine_geometry, 6*count, window_dim);
  
  arena_release_scratch(&scratch);
}

function void
dump_10_by_10(FILE *out, U8 *buf){
  for (U32 y = 0; y < 10; y += 1){
    for (U32 x = 0; x < 10; x += 1){
      U8 *color = buf + 4*(x + 10*y);
      fprintf(out, "x:%02d y:%02d; R:%.3f G:%.3f B:%.3f\n",
              x, y, color[0]/255.f, color[1]/255.f, color[2]/255.f);
    }
  }
  fflush(out);
}

function void
fused_visual_dump_test_path(V2S32 window_dim_s32, U64 frame_counter){
  V2F32 window_dim = v2f32(V2_EXPANDED(window_dim_s32));
  
  // normal clear
  glViewport(0, 0, window_dim_s32.x, window_dim_s32.y);
  glBindFramebuffer(GL_FRAMEBUFFER, main_framebuffer);
  glClearColor(0.f, 0.f, 0.f, 1.f);
  glClear(GL_COLOR_BUFFER_BIT);
  
  // setup test context
  TestCtx ctx = {0};
  fill_test_ctx(frame_counter, &ctx);
  
  // test geometry from parameters
  Rect test_rects[20] = {0};
  fill_test_rects(&ctx, test_rects);
  
  // draw test geometry
  {
    // shift geometry to an easier place to view it
    Rect shifted_rects[ArrayCount(test_rects)];
    shift_rect_geometry(shifted_rects, test_rects,
                        ArrayCount(test_rects),
                        v2f32(100.f, 100.f));
    
    // draw geometry
    draw_sdfs(shifted_rects, ArrayCount(shifted_rects), test_texture,
              window_dim);
  }
  
  // resolve main canvas onto window
  {
    glEnable(GL_FRAMEBUFFER_SRGB);
    
    glBindFramebuffer(GL_READ_FRAMEBUFFER, main_framebuffer);
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
    
    glBlitFramebuffer(0, 0, window_dim_s32.x, window_dim_s32.y,
                      0, 0, window_dim_s32.x, window_dim_s32.y,
                      GL_COLOR_BUFFER_BIT,
                      GL_NEAREST);
    
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    
    glDisable(GL_FRAMEBUFFER_SRGB);
  }
  
  // read 10x10 block
  U8 buf[10*10*4];
  glReadPixels(100, 100, 10, 10, GL_RGBA, GL_UNSIGNED_BYTE, buf);
  
  // re-create top 10x10 visually
  {
    V2F32 botleft = v2f32(600.f, 100.f);
    F32 grid_size = 20.f;
    
    draw_10_by_10(buf, botleft, grid_size, window_dim);
    
#if FINE_GEOMETRY
    draw_fine_rect_geometry(test_rects, ArrayCount(test_rects),
                            FINE_GEOMETRY_ALPHA,
                            botleft, grid_size,
                            window_dim);
#endif
  }
  
#if 1
  // dump top 10x10 on first frame
  if (frame_counter == 0){
    dump_10_by_10(stdout, buf);
  }
#endif
}

int
WinMain(HINSTANCE hInstance,
        HINSTANCE hPrevInstance,
        LPSTR lpCmdLine,
        int nShowCmd){
  w32_WinMain_init(hInstance, hPrevInstance, lpCmdLine, nShowCmd);
  
  Arena *arena = arena_alloc();
  Arena *frame_arena = arena_alloc();
  
  // begin error block
  er_accum_begin();
  
  // initialize graphics
  gfx_init();
  gfx_set_resizing_hook(window_resize_handler);
  ogl_init();
  
  // setup opengl state
  setup_opengl_state();
  
  // create window
  GFX_Window *window = gfx_window_create();
  ogl_equip_window(window);
  
  // check for any errors during initialization
  {
    ArenaTemp scratch = arena_get_scratch(0, 0);
    String8 error = er_accum_end(scratch.arena);
    if (error.size != 0){
      gfx_message_box(str8_lit("Initialization Error"), error);
      os_exit_process(1);
    }
    arena_release_scratch(&scratch);
  }
  
  // frame controls
  U64 frame_counter = 0;
  
  // begin UI loop
  gfx_window_set_visible(window, 1);
  for (;;){
    arena_pop_to(frame_arena, 0);
    
    // begin error block
    er_accum_begin();
    
    // wait for input
    if (!gfx_peek_input()){
      break;
    }
    
    // draw gl window
    if (gfx_window_is_valid(window)){
      
      // get window dim
      V2S32 window_dim_s32 = {0};
      {
        RectS32 rect = {0};
        if (gfx_window_get_inner_rect(window, &rect)){
          window_dim_s32 = dim_from_rects32(rect);
        }
      }
      
      // begin render
      ogl_begin(window);
      
      // run a test
      fused_visual_dump_test_path(window_dim_s32, frame_counter);
      //compare_pixel_coverage_circle_and_sdf_circle(window_dim_s32, frame_counter);
      
      // end render
      ogl_end();
    }
    
    // check for any errors during frame
    {
      ArenaTemp scratch = arena_get_scratch(0, 0);
      String8 error = er_accum_end(scratch.arena);
      if (error.size != 0){
        gfx_message_box(str8_lit("Frame Error"), error);
        os_exit_process(1);
      }
      arena_release_scratch(&scratch);
    }
    
    // end frame
    frame_counter += 1;
  }
  
  return(0);
}

//$ graphical //
