////////////////////////////////
// NOTE(allen): Implementation Limitations

#if !defined(OGL_IMPL_MAX_CANVAS_W)
# define OGL_IMPL_MAX_CANVAS_W 2048
#endif
#if !defined(OGL_IMPL_MAX_CANVAS_H)
# define OGL_IMPL_MAX_CANVAS_H 2048
#endif


////////////////////////////////
// NOTE(allen): GLSL Shaders

#define R_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 r_glsl_vshader[] =
"#version 330\n"
"uniform vec2 u_view_xform;\n"
"layout (location = 0)  in vec2  v_template_p;\n"
"layout (location = 1)  in vec4  v_rect;\n"
"layout (location = 2)  in float v_radius;\n"
"layout (location = 3)  in float v_thick;\n"
"layout (location = 4)  in float v_theta;\n"
"layout (location = 5)  in uint  v_c0;\n"
"layout (location = 6)  in uint  v_c1;\n"
"layout (location = 7)  in uint  v_flags;\n"
"layout (location = 8)  in vec4  v_uvrect;\n"
"layout (location = 9)  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"
// unpack colors
R_GLSL_UNPACK_COLOR("v_c0", "c00", "c01", "c02", "c03")
R_GLSL_UNPACK_COLOR("v_c1", "c10", "c11", "c12", "c13")
// rect -> centa & half dim
"vec2 center   = (v_rect.xy + v_rect.zw)*0.5;\n"
"vec2 half_dim = (v_rect.zw - v_rect.xy)*0.5;\n"
// theta -> sin(theta) & cos(theta)
"float stheta = sin(v_theta);\n"
"float ctheta = cos(v_theta);\n"
// chunk that varries with the template (the rest is all flat)
// TODO(allen): optimization opportunities here probably
"vec2 q = center + half_dim*v_template_p;\n"
"if (v_template_p.x < 0){ q.x = floor(q.x); }\n"
"else                   { q.x = ceil(q.x); }\n"
"if (v_template_p.y < 0){ q.y = floor(q.y); }\n"
"else                   { q.y = ceil(q.y); }\n"
"vec2 qr = q - center;\n"
"vec2 pr = vec2(ctheta*qr.x - stheta*qr.y, stheta*qr.x + ctheta*qr.y);\n"
"vec2 p  = center + pr;\n"
"vec2 norm_pos = p*u_view_xform + vec2(-1.0, -1.0);\n"
"gl_Position = vec4(norm_pos, 0.0, 1.0);\n"
// fill outputs
"f_rect_center   = center;\n"
"f_rect_half_dim = 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 r_glsl_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 ( ((f_flags&0x20u) != 0u) &&"
"((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"

// clamp radius
"float half_short_side = min(f_rect_half_dim.x, f_rect_half_dim.y);\n"
"float rad = min(f_radius, half_short_side);\n"

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

// modify radius for fragment's quadrant
// 10
// 32
"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"
//" out_color = vec4(1, 1, 1, 1);\n"

"}\n"
;


////////////////////////////////
// NOTE(allen): Render API Implementation

//// backend hider API ////

function B32
r_backend_is_inited(void){
  return(ogl_is_inited());
}

function B32
r_backend_init(void){
  return(ogl_init());
}

function B32
r_backend_equip_window(GFX_Window *window){
  return(ogl_equip_window(window));
}

function void
r_backend_begin(GFX_Window *window){
  ogl_begin(window);
}

function void
r_backend_end(void){
  ogl_end();
}

//// rendering ////

global OGL_Renderer r_gl_renderer = {0};

function B32
r_renderer_is_inited(void){
  return(r_gl_renderer.initialized);
}

function B32
r_renderer_init(void){
  B32 error = 0;
  
  // determine whether to proceed with initialization
  B32 proceed = 1;
  {
    if (!ogl_is_inited()){
      error = 1;
      proceed = 0;
    }
    if (r_renderer_is_inited()){
      proceed = 0;
    }
  }
  
  // main init path
  if (proceed){
    ArenaTemp scratch = arena_get_scratch(0, 0);
    ogl_begin(0);
    
    // setup GPU program
    OGL_Shader vshader = {0};
    if (!error){
      vshader = ogl_make_shader(scratch.arena, r_glsl_vshader, GL_VERTEX_SHADER);
      if (vshader.log.size > 0){
        er_emit(vshader.log);
        error = 1;
      }
    }
    
    OGL_Shader fshader = {0};
    if (!error){
      fshader = ogl_make_shader(scratch.arena, r_glsl_fshader, GL_FRAGMENT_SHADER);
      if (fshader.log.size > 0){
        er_emit(fshader.log);
        error = 1;
      }
    }
    
    OGL_Shader program = {0};
    if (!error){
      GLuint shaders[2];
      shaders[0] = vshader.handle;
      shaders[1] = fshader.handle;
      program = ogl_make_program(scratch.arena, shaders, 2);
      if (program.log.size > 0){
        er_emit(program.log);
        error = 1;
      }
    }
    
    // extract uniform locations
    GLint u_view_xform = -1;
    GLint u_tex = -1;
    if (!error){
      u_view_xform = glGetUniformLocation(program.handle, "u_view_xform");
      u_tex        = glGetUniformLocation(program.handle, "u_tex"       );
    }
    
    // setup vertex array object
    GLuint vao = 0;
    if (!error){
      glGenVertexArrays(1, &vao);
      glBindVertexArray(vao);
      if (glGetError() != 0 || vao == 0){
        er_emit(str8_lit("failed to setup vertex array object"));
        error = 1;
      }
    }
    
    // setup buffers
    GLuint vertex_buffer = 0;
    if (!error){
      glGenBuffers(1, &vertex_buffer);
      glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
      if (glGetError() != 0 || vertex_buffer == 0){
        er_emit(str8_lit("failed to setup vertex buffer"));
        error = 1;
      }
    }
    
    // setup blend mode
    if (!error){
      glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
      glEnable(GL_BLEND);
      if (glGetError() != 0){
        er_emit(str8_lit("failed to setup blending"));
        error = 1;
      }
    }
    
    // set texture pack & unpack alignment to 1
    if (!error){
      glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
      glPixelStorei(GL_PACK_ALIGNMENT, 1);
      if (glGetError() != 0){
        er_emit(str8_lit("failed to set pixel pack/unpack"));
        error = 1;
      }
    }
    
    // setup a fallback texture
    GLuint fallback_texture = 0;
    if (!error){
      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){
        er_emit(str8_lit("failed to setup texture"));
        error = 1;
      }
    }
    
    // setup a frame buffer
    GLuint main_framebuffer = 0;
    GLuint canvas_texture = 0;
    {
      glGenTextures(1, &canvas_texture);
      glBindTexture(GL_TEXTURE_2D, canvas_texture);
      glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB,
                   OGL_IMPL_MAX_CANVAS_W, OGL_IMPL_MAX_CANVAS_H, 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);
      
      glGenFramebuffers(1, &main_framebuffer);
      glBindFramebuffer(GL_FRAMEBUFFER, main_framebuffer);
      glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                           canvas_texture, 0);
      
      if (glGetError() != 0 || main_framebuffer == 0){
        er_emit(str8_lit("failed to setup main framebuffer"));
        error = 1;
      }
      
      glBindFramebuffer(GL_FRAMEBUFFER, 0);
    }
    
    // clear gl errors
    for (;glGetError() != 0;);
    
    // if error, cleanup
    if (error){
      if (program.handle != 0){
        glDeleteProgram(program.handle);
      }
      if (vao != 0){
        glDeleteVertexArrays(1, &vao);
      }
      if (vertex_buffer != 0){
        glDeleteBuffers(1, &vertex_buffer);
      }
      if (fallback_texture != 0){
        glDeleteTextures(1, &fallback_texture);
      }
      if (canvas_texture != 0){
        glDeleteTextures(1, &canvas_texture);
      }
      if (main_framebuffer != 0){
        glDeleteFramebuffers(1, &main_framebuffer);
      }
    }
    
    // no matter what, cleanup "intermediates"
    {
      if (vshader.handle != 0){
        glDeleteShader(vshader.handle);
      }
      if (fshader.handle != 0){
        glDeleteShader(fshader.handle);
      }
    }
    
    // if no error, fill results
    if (!error){
      r_gl_renderer.initialized = 1;
      r_gl_renderer.program = program.handle;
      r_gl_renderer.u_view_xform = u_view_xform;
      r_gl_renderer.u_tex = u_tex;
      r_gl_renderer.vertex_buffer = vertex_buffer;
      r_gl_renderer.fallback_texture = fallback_texture;
      r_gl_renderer.canvas_texture = canvas_texture;
      r_gl_renderer.main_framebuffer = main_framebuffer;
    }
    
    ogl_end();
    arena_release_scratch(&scratch);
  }
  
  // success is returned IFF render is initialized
  Assert((!error) == r_renderer_is_inited());
  
  // fill result
  B32 result = (!error);
  return(result);
}

function B32
r_canvas_init(V2S32 dim){
  B32 error = 0;
  
  // api check
  if (!r_renderer_is_inited()){
    er_emit(str8_lit("renderer is not initialized"));
    error = 1;
  }
  
  // dimensions check
  if (!error){
    if (dim.x > OGL_IMPL_MAX_CANVAS_W ||
        dim.y > OGL_IMPL_MAX_CANVAS_H){
      er_emit(str8_lit("requested canvas dimension is too big"));
      error = 1;
    }
  }
  
  // main path
  if (!error){
    glViewport(0, 0, dim.x, dim.y);
    glBindFramebuffer(GL_FRAMEBUFFER, r_gl_renderer.main_framebuffer);
    glClearColor(0.f, 0.f, 0.f, 1.f);
    glClear(GL_COLOR_BUFFER_BIT);
    r_gl_renderer.dim = dim;
    
    // gl error check
    if (glGetError() != 0){
      for (;glGetError() != 0;);
      er_emit(str8_lit("gl error"));
      error = 1;
    }
  }
  
  // fill result
  B32 result = (!error);
  return(result);
}

function B32
r_show_canvas_on_activated_window(void){
  B32 error = 0;
  
  // api check
  if (!r_renderer_is_inited()){
    er_emit(str8_lit("renderer is not initialized"));
    error = 1;
  }
  
  // main path
  if (!error){
    glEnable(GL_FRAMEBUFFER_SRGB);
    
    glBindFramebuffer(GL_READ_FRAMEBUFFER, r_gl_renderer.main_framebuffer);
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
    
    glBlitFramebuffer(0, 0, r_gl_renderer.dim.x, r_gl_renderer.dim.y,
                      0, 0, r_gl_renderer.dim.x, r_gl_renderer.dim.y,
                      GL_COLOR_BUFFER_BIT,
                      GL_NEAREST);
    
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    
    glDisable(GL_FRAMEBUFFER_SRGB);
    
    // gl error check
    if (glGetError() != 0){
      for (;glGetError() != 0;);
      er_emit(str8_lit("gl error"));
      error = 1;
    }
  }
  
  // fill result
  B32 result = (!error);
  return(result);
}

function B32
r_submit_quads(R_QuadNode *first_node, U64 total_count,
               R_Texture *texture_ptr){
  B32 error = 0;
  
  // api check
  if (!r_renderer_is_inited()){
    er_emit(str8_lit("renderer is not initialized"));
    error = 1;
  }
  
  if (!error){
    if (total_count > 0){
      // resolve texture
      GLuint texture = (GLuint)IntFromPtr(texture_ptr);
      if (!glIsTexture(texture)){
        texture = r_gl_renderer.fallback_texture;
      }
      
      // fill vertex buffer
      U64 data_size = sizeof(V2F32)*6 + sizeof(R_Quad)*total_count;
      glBufferData(GL_ARRAY_BUFFER, data_size, 0, GL_STREAM_DRAW);
      GLintptr triangles_off = 0;
      GLintptr quads_off = 0;
      {
        U64 buf_cursor = 0;
        
        // set triangle data
        local V2F32 triangles[6] = {
          {-1.f, +1.f}, {+1.f, +1.f}, {-1.f, -1.f},
          {+1.f, +1.f}, {-1.f, -1.f}, {+1.f, -1.f},
        };
        
        triangles_off = buf_cursor;
        glBufferSubData(GL_ARRAY_BUFFER, buf_cursor, sizeof(triangles), triangles);
        buf_cursor += sizeof(triangles);
        
        // set quad data
        quads_off = buf_cursor;
        for (R_QuadNode *node = first_node;
             node != 0;
             node = node->next){
          U64 size = node->count*sizeof(R_Quad);
          glBufferSubData(GL_ARRAY_BUFFER, buf_cursor, size, node->quads);
          buf_cursor += size;
        }
      }
      
      // call GPU program
      glUseProgram(r_gl_renderer.program);
      glUniform2f(r_gl_renderer.u_view_xform,
                  2.f/r_gl_renderer.dim.x, 2.f/r_gl_renderer.dim.y);
      glUniform1i(r_gl_renderer.u_tex, 0);
      glActiveTexture(GL_TEXTURE0);
      glBindTexture(GL_TEXTURE_2D, texture);
      
      glEnableVertexAttribArray(0);
      glVertexAttribDivisor(0, 0);
      glVertexAttribPointer(0, 2, GL_FLOAT, 0, sizeof(V2F32),
                            PtrFromInt(triangles_off));
      
      glEnableVertexAttribArray(1);
      glVertexAttribDivisor(1, 1);
      glVertexAttribPointer(1, 4, GL_FLOAT, 0, sizeof(R_Quad),
                            PtrFromInt(quads_off +
                                       OffsetOfMember(R_Quad, xy)));
      
      glEnableVertexAttribArray(2);
      glVertexAttribDivisor(2, 1);
      glVertexAttribPointer(2, 1, GL_FLOAT, 0, sizeof(R_Quad),
                            PtrFromInt(quads_off +
                                       OffsetOfMember(R_Quad, radius)));
      
      glEnableVertexAttribArray(3);
      glVertexAttribDivisor(3, 1);
      glVertexAttribPointer(3, 1, GL_FLOAT, 0, sizeof(R_Quad),
                            PtrFromInt(quads_off +
                                       OffsetOfMember(R_Quad, thick)));
      
      glEnableVertexAttribArray(4);
      glVertexAttribDivisor(4, 1);
      glVertexAttribPointer(4, 1, GL_FLOAT, 0, sizeof(R_Quad),
                            PtrFromInt(quads_off +
                                       OffsetOfMember(R_Quad, theta)));
      
      glEnableVertexAttribArray(5);
      glVertexAttribDivisor(5, 1);
      glVertexAttribIPointer(5, 1, GL_UNSIGNED_INT, sizeof(R_Quad),
                             PtrFromInt(quads_off +
                                        OffsetOfMember(R_Quad, c[0])));
      
      glEnableVertexAttribArray(6);
      glVertexAttribDivisor(6, 1);
      glVertexAttribIPointer(6, 1, GL_UNSIGNED_INT, sizeof(R_Quad),
                             PtrFromInt(quads_off +
                                        OffsetOfMember(R_Quad, c[1])));
      
      glEnableVertexAttribArray(7);
      glVertexAttribDivisor(7, 1);
      glVertexAttribIPointer(7, 1, GL_UNSIGNED_INT, sizeof(R_Quad),
                             PtrFromInt(quads_off +
                                        OffsetOfMember(R_Quad, flags)));
      
      glEnableVertexAttribArray(8);
      glVertexAttribDivisor(8, 1);
      glVertexAttribPointer(8, 4, GL_FLOAT, 0, sizeof(R_Quad),
                            PtrFromInt(quads_off +
                                       OffsetOfMember(R_Quad, uv)));
      
      glEnableVertexAttribArray(9);
      glVertexAttribDivisor(9, 1);
      glVertexAttribPointer(9, 4, GL_FLOAT, 0, sizeof(R_Quad),
                            PtrFromInt(quads_off +
                                       OffsetOfMember(R_Quad, clip)));
      
      glDrawArraysInstanced(GL_TRIANGLES, 0, 6, total_count);
      
      // gl error check
      if (glGetError() != 0){
        for (;glGetError() != 0;);
        er_emit(str8_lit("gl error"));
        error = 1;
      }
    }
  }
  
  // fill result
  B32 result = (!error);
  return(result);
}

function R_Texture*
r_texture_create(void *data, U32 w, U32 h){
  Assert(IsPow2OrZero(w) && IsPow2OrZero(h) && w != 0 && h != 0);
  
  R_Texture *result = 0;
  
  // api check
  if (!r_renderer_is_inited()){
    er_emit(str8_lit("renderer is not initialized"));
  }
  
  // create texture
  else{
    GLuint texture = 0;
    
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, w, h, 0,
                 GL_RED, GL_UNSIGNED_BYTE, 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);
    
    // handle errors
    if (glGetError() != 0){
      if (texture != 0){
        glDeleteTextures(1, &texture);
        texture = 0;
      }
      for (;glGetError() != 0;);
      er_emit(str8_lit("gl error"));
    }
    
    // fill result
    result = (R_Texture*)PtrFromInt(texture);
  }
  
  return(result);
}

function B32
r_texture_update(R_Texture *texture_ptr, void *data, U32 w, U32 h,
                 U32 x, U32 y){
  B32 error = 0;
  
  // api check
  if (!r_renderer_is_inited()){
    er_emit(str8_lit("renderer is not initialized"));
    error = 1;
  }
  
  // update texture
  if (!error){
    GLuint texture = (GLuint)IntFromPtr(texture_ptr);
    if (glIsTexture(texture)){
      glBindTexture(GL_TEXTURE_2D, texture);
      glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h,
                      GL_RED, GL_UNSIGNED_BYTE, data);
      
      // gl error check
      if (glGetError() != 0){
        for (;glGetError() != 0;);
        er_emit(str8_lit("gl error"));
        error = 1;
      }
    }
  }
  
  // fill result
  B32 result = (!error);
  return(result);
}

function B32
r_texture_destroy(R_Texture *texture_ptr){
  B32 error = 0;
  
  // api check
  if (!r_renderer_is_inited()){
    er_emit(str8_lit("renderer is not initialized"));
    error = 1;
  }
  
  // delete texture
  if (!error){
    GLuint texture = (GLuint)IntFromPtr(texture_ptr);
    if (glIsTexture(texture)){
      glDeleteTextures(1, &texture);
      
      // gl error check
      if (glGetError() != 0){
        for (;glGetError() != 0;);
        er_emit(str8_lit("gl error"));
        error = 1;
      }
    }
  }
  
  // fill result
  B32 result = (!error);
  return(result);
}

function B32
r_texture_valid(R_Texture *texture_ptr){
  B32 result = 0;
  // api check
  if (!r_renderer_is_inited()){
    er_emit(str8_lit("renderer is not initialized"));
  }
  // texture check
  else{
    GLuint texture = (GLuint)IntFromPtr(texture_ptr);
    result = glIsTexture(texture);
  }
  return(result);
}
