/**
 ******************************************************************************
 * Xenia : Xbox 360 Emulator Research Project                                 *
 ******************************************************************************
 * Copyright 2015 Ben Vanik. All rights reserved.                             *
 * Released under the BSD license - see LICENSE in the root for more details. *
 ******************************************************************************
 */

#include "xenia/ui/gl/gl_profiler_display.h"

#include <algorithm>
#include <string>

#include "third_party/microprofile/microprofile.h"
#include "third_party/microprofile/microprofileui.h"

#include "xenia/base/assert.h"
#include "xenia/base/math.h"

namespace xe {
namespace ui {
namespace gl {

#define MICROPROFILE_MAX_VERTICES (16 << 10)
#define MICROPROFILE_NUM_QUERIES (8 << 10)
#define MAX_FONT_CHARS 256
#define Q0(d, member, v) d[0].member = v
#define Q1(d, member, v) \
  d[1].member = v;       \
  d[3].member = v
#define Q2(d, member, v) d[4].member = v
#define Q3(d, member, v) \
  d[2].member = v;       \
  d[5].member = v

const int FONT_TEX_X = 1024;
const int FONT_TEX_Y = 9;

const uint8_t profiler_font[] = {
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x78, 0x38, 0x78,
    0x7c, 0x7c, 0x3c, 0x44, 0x38, 0x04, 0x44, 0x40, 0x44, 0x44, 0x38, 0x78,
    0x38, 0x78, 0x38, 0x7c, 0x44, 0x44, 0x44, 0x44, 0x44, 0x7c, 0x00, 0x00,
    0x40, 0x00, 0x04, 0x00, 0x18, 0x00, 0x40, 0x10, 0x08, 0x40, 0x30, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x38, 0x10, 0x38, 0x7c, 0x08, 0x7c, 0x1c, 0x7c, 0x38, 0x38,
    0x10, 0x28, 0x28, 0x10, 0x00, 0x20, 0x10, 0x08, 0x10, 0x10, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x20, 0x38, 0x38, 0x70, 0x00,
    0x1c, 0x10, 0x00, 0x1c, 0x10, 0x70, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x28, 0x44, 0x44, 0x44, 0x40, 0x40, 0x40, 0x44,
    0x10, 0x04, 0x48, 0x40, 0x6c, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x10,
    0x44, 0x44, 0x44, 0x44, 0x44, 0x04, 0x00, 0x00, 0x40, 0x00, 0x04, 0x00,
    0x24, 0x00, 0x40, 0x00, 0x00, 0x40, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x30,
    0x44, 0x04, 0x18, 0x40, 0x20, 0x04, 0x44, 0x44, 0x10, 0x28, 0x28, 0x3c,
    0x44, 0x50, 0x10, 0x10, 0x08, 0x54, 0x10, 0x00, 0x00, 0x00, 0x04, 0x00,
    0x00, 0x08, 0x00, 0x10, 0x44, 0x44, 0x40, 0x40, 0x04, 0x28, 0x00, 0x30,
    0x10, 0x18, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x44, 0x44, 0x40, 0x44, 0x40, 0x40, 0x40, 0x44, 0x10, 0x04, 0x50, 0x40,
    0x54, 0x64, 0x44, 0x44, 0x44, 0x44, 0x40, 0x10, 0x44, 0x44, 0x44, 0x28,
    0x28, 0x08, 0x00, 0x38, 0x78, 0x3c, 0x3c, 0x38, 0x20, 0x38, 0x78, 0x30,
    0x18, 0x44, 0x10, 0x6c, 0x78, 0x38, 0x78, 0x3c, 0x5c, 0x3c, 0x3c, 0x44,
    0x44, 0x44, 0x44, 0x44, 0x7c, 0x00, 0x4c, 0x10, 0x04, 0x08, 0x28, 0x78,
    0x40, 0x08, 0x44, 0x44, 0x10, 0x00, 0x7c, 0x50, 0x08, 0x50, 0x00, 0x20,
    0x04, 0x38, 0x10, 0x00, 0x00, 0x00, 0x08, 0x10, 0x10, 0x10, 0x7c, 0x08,
    0x08, 0x54, 0x40, 0x20, 0x04, 0x44, 0x00, 0x30, 0x10, 0x18, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x78, 0x40, 0x44,
    0x78, 0x78, 0x40, 0x7c, 0x10, 0x04, 0x60, 0x40, 0x54, 0x54, 0x44, 0x78,
    0x44, 0x78, 0x38, 0x10, 0x44, 0x44, 0x54, 0x10, 0x10, 0x10, 0x00, 0x04,
    0x44, 0x40, 0x44, 0x44, 0x78, 0x44, 0x44, 0x10, 0x08, 0x48, 0x10, 0x54,
    0x44, 0x44, 0x44, 0x44, 0x60, 0x40, 0x10, 0x44, 0x44, 0x44, 0x28, 0x44,
    0x08, 0x00, 0x54, 0x10, 0x18, 0x18, 0x48, 0x04, 0x78, 0x10, 0x38, 0x3c,
    0x10, 0x00, 0x28, 0x38, 0x10, 0x20, 0x00, 0x20, 0x04, 0x10, 0x7c, 0x00,
    0x7c, 0x00, 0x10, 0x00, 0x00, 0x20, 0x00, 0x04, 0x10, 0x5c, 0x40, 0x10,
    0x04, 0x00, 0x00, 0x60, 0x10, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x7c, 0x44, 0x40, 0x44, 0x40, 0x40, 0x4c, 0x44,
    0x10, 0x04, 0x50, 0x40, 0x44, 0x4c, 0x44, 0x40, 0x54, 0x50, 0x04, 0x10,
    0x44, 0x44, 0x54, 0x28, 0x10, 0x20, 0x00, 0x3c, 0x44, 0x40, 0x44, 0x7c,
    0x20, 0x44, 0x44, 0x10, 0x08, 0x70, 0x10, 0x54, 0x44, 0x44, 0x44, 0x44,
    0x40, 0x38, 0x10, 0x44, 0x44, 0x54, 0x10, 0x44, 0x10, 0x00, 0x64, 0x10,
    0x20, 0x04, 0x7c, 0x04, 0x44, 0x20, 0x44, 0x04, 0x10, 0x00, 0x7c, 0x14,
    0x20, 0x54, 0x00, 0x20, 0x04, 0x38, 0x10, 0x10, 0x00, 0x00, 0x20, 0x10,
    0x10, 0x10, 0x7c, 0x08, 0x10, 0x58, 0x40, 0x08, 0x04, 0x00, 0x00, 0x30,
    0x10, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x44, 0x44, 0x44, 0x44, 0x40, 0x40, 0x44, 0x44, 0x10, 0x44, 0x48, 0x40,
    0x44, 0x44, 0x44, 0x40, 0x48, 0x48, 0x44, 0x10, 0x44, 0x28, 0x6c, 0x44,
    0x10, 0x40, 0x00, 0x44, 0x44, 0x40, 0x44, 0x40, 0x20, 0x3c, 0x44, 0x10,
    0x08, 0x48, 0x10, 0x54, 0x44, 0x44, 0x44, 0x44, 0x40, 0x04, 0x12, 0x4c,
    0x28, 0x54, 0x28, 0x3c, 0x20, 0x00, 0x44, 0x10, 0x40, 0x44, 0x08, 0x44,
    0x44, 0x20, 0x44, 0x08, 0x00, 0x00, 0x28, 0x78, 0x44, 0x48, 0x00, 0x10,
    0x08, 0x54, 0x10, 0x10, 0x00, 0x00, 0x40, 0x00, 0x10, 0x08, 0x00, 0x10,
    0x00, 0x40, 0x40, 0x04, 0x04, 0x00, 0x00, 0x30, 0x10, 0x18, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x78, 0x38, 0x78,
    0x7c, 0x40, 0x3c, 0x44, 0x38, 0x38, 0x44, 0x7c, 0x44, 0x44, 0x38, 0x40,
    0x34, 0x44, 0x38, 0x10, 0x38, 0x10, 0x44, 0x44, 0x10, 0x7c, 0x00, 0x3c,
    0x78, 0x3c, 0x3c, 0x3c, 0x20, 0x04, 0x44, 0x38, 0x48, 0x44, 0x38, 0x44,
    0x44, 0x38, 0x78, 0x3c, 0x40, 0x78, 0x0c, 0x34, 0x10, 0x6c, 0x44, 0x04,
    0x7c, 0x00, 0x38, 0x38, 0x7c, 0x38, 0x08, 0x38, 0x38, 0x20, 0x38, 0x70,
    0x10, 0x00, 0x28, 0x10, 0x00, 0x34, 0x00, 0x08, 0x10, 0x10, 0x00, 0x20,
    0x00, 0x10, 0x00, 0x00, 0x20, 0x04, 0x00, 0x20, 0x10, 0x3c, 0x70, 0x00,
    0x1c, 0x00, 0x7c, 0x1c, 0x10, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x38, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x04,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};

GLProfilerDisplay::GLProfilerDisplay(xe::ui::Window* window)
    : window_(window),
      vertex_buffer_(MICROPROFILE_MAX_VERTICES * sizeof(Vertex) * 10,
                     sizeof(Vertex)) {
  if (!SetupFont() || !SetupState() || !SetupShaders()) {
    // Hrm.
    assert_always();
  }

  window_->on_painted.AddListener([this](UIEvent* e) { Profiler::Present(); });

  // Pass through mouse events.
  window_->on_mouse_down.AddListener([this](MouseEvent* e) {
    if (Profiler::is_visible()) {
      Profiler::OnMouseDown(e->button() == MouseEvent::Button::kLeft,
                            e->button() == MouseEvent::Button::kRight);
      e->set_handled(true);
      window_->Invalidate();
    }
  });
  window_->on_mouse_up.AddListener([this](MouseEvent* e) {
    if (Profiler::is_visible()) {
      Profiler::OnMouseUp();
      e->set_handled(true);
      window_->Invalidate();
    }
  });
  window_->on_mouse_move.AddListener([this](MouseEvent* e) {
    if (Profiler::is_visible()) {
      Profiler::OnMouseMove(e->x(), e->y());
      e->set_handled(true);
      window_->Invalidate();
    }
  });
  window_->on_mouse_wheel.AddListener([this](MouseEvent* e) {
    if (Profiler::is_visible()) {
      Profiler::OnMouseWheel(e->x(), e->y(), -e->dy());
      e->set_handled(true);
      window_->Invalidate();
    }
  });

  // Watch for toggle/mode keys and such.
  window_->on_key_down.AddListener([this](KeyEvent* e) {
    if (Profiler::is_visible()) {
      Profiler::OnKeyDown(e->key_code());
      e->set_handled(true);
      window_->Invalidate();
    }
  });
  window_->on_key_up.AddListener([this](KeyEvent* e) {
    if (Profiler::is_visible()) {
      Profiler::OnKeyUp(e->key_code());
      e->set_handled(true);
      window_->Invalidate();
    }
  });
}

bool GLProfilerDisplay::SetupFont() {
  // Setup font lookup table.
  for (uint32_t i = 0; i < xe::countof(font_description_.char_offsets); ++i) {
    font_description_.char_offsets[i] = 206;
  }
  for (uint32_t i = 'A'; i <= 'Z'; ++i) {
    font_description_.char_offsets[i] = (i - 'A') * 8 + 1;
  }
  for (uint32_t i = 'a'; i <= 'z'; ++i) {
    font_description_.char_offsets[i] = (i - 'a') * 8 + 217;
  }
  for (uint32_t i = '0'; i <= '9'; ++i) {
    font_description_.char_offsets[i] = (i - '0') * 8 + 433;
  }
  for (uint32_t i = '!'; i <= '/'; ++i) {
    font_description_.char_offsets[i] = (i - '!') * 8 + 513;
  }
  for (uint32_t i = ':'; i <= '@'; ++i) {
    font_description_.char_offsets[i] = (i - ':') * 8 + 625 + 8;
  }
  for (uint32_t i = '['; i <= '_'; ++i) {
    font_description_.char_offsets[i] = (i - '[') * 8 + 681 + 8;
  }
  for (uint32_t i = '{'; i <= '~'; ++i) {
    font_description_.char_offsets[i] = (i - '{') * 8 + 721 + 8;
  }

  // Unpack font bitmap into an RGBA texture.
  const int UNPACKED_SIZE = FONT_TEX_X * FONT_TEX_Y * 4;
  uint32_t unpacked[UNPACKED_SIZE];
  int idx = 0;
  int end = FONT_TEX_X * FONT_TEX_Y / 8;
  for (int i = 0; i < end; i++) {
    uint8_t b = profiler_font[i];
    for (int j = 0; j < 8; ++j) {
      unpacked[idx++] = b & 0x80 ? 0xFFFFFFFFu : 0;
      b <<= 1;
    }
  }

  glCreateTextures(GL_TEXTURE_2D, 1, &font_texture_);
  glTextureParameteri(font_texture_, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTextureParameteri(font_texture_, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTextureParameteri(font_texture_, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTextureParameteri(font_texture_, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  glTextureStorage2D(font_texture_, 1, GL_RGBA8, FONT_TEX_X, FONT_TEX_Y);
  glTextureSubImage2D(font_texture_, 0, 0, 0, FONT_TEX_X, FONT_TEX_Y, GL_RGBA,
                      GL_UNSIGNED_BYTE, unpacked);

  font_handle_ = glGetTextureHandleARB(font_texture_);
  glMakeTextureHandleResidentARB(font_handle_);

  return true;
}

bool GLProfilerDisplay::SetupState() {
  if (!vertex_buffer_.Initialize()) {
    return false;
  }
  return true;
}

bool GLProfilerDisplay::SetupShaders() {
  const std::string header =
      R"(
#version 450
#extension GL_ARB_bindless_texture : require
#extension GL_ARB_explicit_uniform_location : require
#extension GL_ARB_shading_language_420pack : require
precision highp float;
precision highp int;
layout(std140, column_major) uniform;
layout(std430, column_major) buffer;
)";
  const std::string vertex_shader_source = header +
                                           R"(
layout(location = 0) uniform mat4 projection_matrix;
layout(location = 0) in vec2 in_pos;
layout(location = 1) in vec4 in_color;
layout(location = 2) in vec2 in_uv;
layout(location = 0) out vec4 vtx_color;
layout(location = 1) out vec2 vtx_uv;
void main() {
  gl_Position = projection_matrix * vec4(in_pos.xy, 0.0, 1.0);
  vtx_color = in_color;
  vtx_uv = in_uv;
})";
  const std::string fragment_shader_source = header +
                                             R"(
layout(location = 1, bindless_sampler) uniform sampler2D font_texture;
layout(location = 2) uniform float font_height;
layout(location = 0) in vec4 vtx_color;
layout(location = 1) in vec2 vtx_uv;
layout(location = 0) out vec4 oC;
void main() {
  if (vtx_uv.x > 1.0) {
    oC = vtx_color;
  } else {
    vec4 color = texture(font_texture, vtx_uv);
    oC = color.rgba * vtx_color;
    if (color.a < 0.5) {
      vec4 c1 = texture(font_texture, vtx_uv + vec2(0.0, font_height));
      oC = vec4(0, 0, 0, c1.a);
    }
  }
})";

  GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER);
  const char* vertex_shader_source_ptr = vertex_shader_source.c_str();
  GLint vertex_shader_source_length = GLint(vertex_shader_source.size());
  glShaderSource(vertex_shader, 1, &vertex_shader_source_ptr,
                 &vertex_shader_source_length);
  glCompileShader(vertex_shader);

  GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
  const char* fragment_shader_source_ptr = fragment_shader_source.c_str();
  GLint fragment_shader_source_length = GLint(fragment_shader_source.size());
  glShaderSource(fragment_shader, 1, &fragment_shader_source_ptr,
                 &fragment_shader_source_length);
  glCompileShader(fragment_shader);

  program_ = glCreateProgram();
  glAttachShader(program_, vertex_shader);
  glAttachShader(program_, fragment_shader);
  glLinkProgram(program_);
  glDeleteShader(vertex_shader);
  glDeleteShader(fragment_shader);

  glProgramUniformHandleui64ARB(program_, 1, font_handle_);
  glProgramUniform1f(program_, 2, 1.0f / FONT_TEX_Y);

  glCreateVertexArrays(1, &vao_);
  glEnableVertexArrayAttrib(vao_, 0);
  glVertexArrayAttribBinding(vao_, 0, 0);
  glVertexArrayAttribFormat(vao_, 0, 2, GL_FLOAT, GL_FALSE,
                            offsetof(Vertex, x));
  glEnableVertexArrayAttrib(vao_, 1);
  glVertexArrayAttribBinding(vao_, 1, 0);
  glVertexArrayAttribFormat(vao_, 1, 4, GL_UNSIGNED_BYTE, GL_TRUE,
                            offsetof(Vertex, color));
  glEnableVertexArrayAttrib(vao_, 2);
  glVertexArrayAttribBinding(vao_, 2, 0);
  glVertexArrayAttribFormat(vao_, 2, 2, GL_FLOAT, GL_FALSE,
                            offsetof(Vertex, u));
  glVertexArrayVertexBuffer(vao_, 0, vertex_buffer_.handle(), 0,
                            sizeof(Vertex));

  return true;
}

GLProfilerDisplay::~GLProfilerDisplay() {
  vertex_buffer_.Shutdown();
  glMakeTextureHandleNonResidentARB(font_handle_);
  glDeleteTextures(1, &font_texture_);
  glDeleteVertexArrays(1, &vao_);
  glDeleteProgram(program_);
}

uint32_t GLProfilerDisplay::width() const { return window_->width(); }

uint32_t GLProfilerDisplay::height() const { return window_->height(); }

void GLProfilerDisplay::Begin() {
  glEnablei(GL_BLEND, 0);
  glBlendFunci(0, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  glDisable(GL_DEPTH_TEST);
  glDisable(GL_STENCIL_TEST);
  glDisable(GL_SCISSOR_TEST);

  glViewport(0, 0, width(), height());

  float left = 0.0f;
  float right = static_cast<float>(width());
  float bottom = static_cast<float>(height());
  float top = 0.0f;
  float z_near = -1.0f;
  float z_far = 1.0f;
  float projection[16] = {0};
  projection[0] = 2.0f / (right - left);
  projection[5] = 2.0f / (top - bottom);
  projection[10] = -2.0f / (z_far - z_near);
  projection[12] = -(right + left) / (right - left);
  projection[13] = -(top + bottom) / (top - bottom);
  projection[14] = -(z_far + z_near) / (z_far - z_near);
  projection[15] = 1.0f;
  glProgramUniformMatrix4fv(program_, 0, 1, GL_FALSE, projection);

  glUseProgram(program_);
  glBindVertexArray(vao_);
}

void GLProfilerDisplay::End() {
  Flush();
  glUseProgram(0);
  glBindVertexArray(0);
}

GLProfilerDisplay::Vertex* GLProfilerDisplay::BeginVertices(size_t count) {
  if (draw_command_count_ + 1 > kMaxCommands) {
    Flush();
  }
  size_t total_length = sizeof(Vertex) * count;
  if (!vertex_buffer_.CanAcquire(total_length)) {
    Flush();
  }
  current_allocation_ = vertex_buffer_.Acquire(total_length);
  return reinterpret_cast<Vertex*>(current_allocation_.host_ptr);
}

void GLProfilerDisplay::EndVertices(GLenum prim_type) {
  size_t vertex_count = current_allocation_.length / sizeof(Vertex);

  if (draw_command_count_ &&
      draw_commands_[draw_command_count_ - 1].prim_type == prim_type) {
    // Coalesce.
    auto& prev_command = draw_commands_[draw_command_count_ - 1];
    prev_command.vertex_count += vertex_count;
  } else {
    auto& command = draw_commands_[draw_command_count_++];
    command.prim_type = prim_type;
    command.vertex_offset = current_allocation_.offset / sizeof(Vertex);
    command.vertex_count = vertex_count;
  }

  vertex_buffer_.Commit(std::move(current_allocation_));
}

void GLProfilerDisplay::Flush() {
  if (!draw_command_count_) {
    return;
  }
  vertex_buffer_.Flush();
  for (size_t i = 0; i < draw_command_count_; ++i) {
    glDrawArrays(draw_commands_[i].prim_type,
                 GLint(draw_commands_[i].vertex_offset),
                 GLsizei(draw_commands_[i].vertex_count));
  }
  draw_command_count_ = 0;
  // TODO(benvanik): don't finish here.
  vertex_buffer_.WaitUntilClean();
}

void GLProfilerDisplay::DrawBox(int x0, int y0, int x1, int y1, uint32_t color,
                                BoxType type) {
  auto v = BeginVertices(6);
  if (type == BoxType::kFlat) {
    color =
        ((color & 0xff) << 16) | ((color >> 16) & 0xff) | (0xff00ff00 & color);
    Q0(v, x, static_cast<float>(x0));
    Q0(v, y, static_cast<float>(y0));
    Q0(v, color, color);
    Q0(v, u, 2.0f);
    Q0(v, v, 2.0f);
    Q1(v, x, static_cast<float>(x1));
    Q1(v, y, static_cast<float>(y0));
    Q1(v, color, color);
    Q1(v, u, 2.0f);
    Q1(v, v, 2.0f);
    Q2(v, x, static_cast<float>(x1));
    Q2(v, y, static_cast<float>(y1));
    Q2(v, color, color);
    Q2(v, u, 2.0f);
    Q2(v, v, 2.0f);
    Q3(v, x, static_cast<float>(x0));
    Q3(v, y, static_cast<float>(y1));
    Q3(v, color, color);
    Q3(v, u, 2.0f);
    Q3(v, v, 2.0f);
  } else {
    uint32_t r = 0xff & (color >> 16);
    uint32_t g = 0xff & (color >> 8);
    uint32_t b = 0xff & color;
    uint32_t nMax = std::max(std::max(std::max(r, g), b), 30u);
    uint32_t nMin = std::min(std::min(std::min(r, g), b), 180u);

    uint32_t r0 = 0xff & ((r + nMax) / 2);
    uint32_t g0 = 0xff & ((g + nMax) / 2);
    uint32_t b0 = 0xff & ((b + nMax) / 2);

    uint32_t r1 = 0xff & ((r + nMin) / 2);
    uint32_t g1 = 0xff & ((g + nMin) / 2);
    uint32_t b1 = 0xff & ((b + nMin) / 2);
    uint32_t color0 = (r0 << 0) | (g0 << 8) | (b0 << 16) | (0xff000000 & color);
    uint32_t color1 = (r1 << 0) | (g1 << 8) | (b1 << 16) | (0xff000000 & color);
    Q0(v, x, static_cast<float>(x0));
    Q0(v, y, static_cast<float>(y0));
    Q0(v, color, color0);
    Q0(v, u, 2.0f);
    Q0(v, v, 2.0f);
    Q1(v, x, static_cast<float>(x1));
    Q1(v, y, static_cast<float>(y0));
    Q1(v, color, color0);
    Q1(v, u, 3.0f);
    Q1(v, v, 2.0f);
    Q2(v, x, static_cast<float>(x1));
    Q2(v, y, static_cast<float>(y1));
    Q2(v, color, color1);
    Q2(v, u, 3.0f);
    Q2(v, v, 3.0f);
    Q3(v, x, static_cast<float>(x0));
    Q3(v, y, static_cast<float>(y1));
    Q3(v, color, color1);
    Q3(v, u, 2.0f);
    Q3(v, v, 3.0f);
  }
  EndVertices(GL_TRIANGLES);
}

void GLProfilerDisplay::DrawLine2D(uint32_t count, float* vertices,
                                   uint32_t color) {
  if (!count || !vertices) {
    return;
  }
  auto v = BeginVertices(2 * (count - 1));
  color = 0xff000000 | ((color & 0xff) << 16) | (color & 0xff00ff00) |
          ((color >> 16) & 0xff);
  for (uint32_t i = 0; i < count - 1; ++i) {
    v[0].x = vertices[i * 2];
    v[0].y = vertices[i * 2 + 1];
    v[0].color = color;
    v[0].u = 2.0f;
    v[0].v = 2.0f;
    v[1].x = vertices[(i + 1) * 2];
    v[1].y = vertices[(i + 1) * 2 + 1];
    v[1].color = color;
    v[1].u = 2.0f;
    v[1].v = 2.0f;
    v += 2;
  }
  EndVertices(GL_LINES);
}

void GLProfilerDisplay::DrawText(int x, int y, uint32_t color, const char* text,
                                 size_t text_length) {
  if (!text_length) {
    return;
  }

  const float fOffsetU = 5.0f / 1024.0f;
  float fX = static_cast<float>(x);
  float fY = static_cast<float>(y);
  float fY2 = fY + (MICROPROFILE_TEXT_HEIGHT + 1);

  auto v = BeginVertices(6 * text_length);
  const char* pStr = text;
  color = 0xff000000 | ((color & 0xff) << 16) | (color & 0xff00) |
          ((color >> 16) & 0xff);

  for (size_t j = 0; j < text_length; ++j) {
    int16_t char_offset = font_description_.char_offsets[*pStr++];
    float fOffset = char_offset / 1024.0f;
    Q0(v, x, fX);
    Q0(v, y, fY);
    Q0(v, color, color);
    Q0(v, u, fOffset);
    Q0(v, v, 0.0f);

    Q1(v, x, fX + MICROPROFILE_TEXT_WIDTH);
    Q1(v, y, fY);
    Q1(v, color, color);
    Q1(v, u, fOffset + fOffsetU);
    Q1(v, v, 0.0f);

    Q2(v, x, fX + MICROPROFILE_TEXT_WIDTH);
    Q2(v, y, fY2);
    Q2(v, color, color);
    Q2(v, u, fOffset + fOffsetU);
    Q2(v, v, 1.0f);

    Q3(v, x, fX);
    Q3(v, y, fY2);
    Q3(v, color, color);
    Q3(v, u, fOffset);
    Q3(v, v, 1.0f);

    fX += MICROPROFILE_TEXT_WIDTH + 1;
    v += 6;
  }

  EndVertices(GL_TRIANGLES);
}

}  // namespace gl
}  // namespace ui
}  // namespace xe
