#include "PreviewRenderer.hpp"

#include <opengl/scoped.hpp>
#include <noggit/rendering/Primitives.hpp>
#include <noggit/Selection.h>
#include <noggit/tool_enums.hpp>
#include <noggit/AsyncLoader.h>

#include <vector>
#include <cmath>
#include <stdexcept>
#include <limits>
#include <thread>
#include <chrono>

#include <QSettings>
#include <QColor>
#include <QMatrix4x4>
#include <QVector3D>


using namespace Noggit::Ui::Tools;


PreviewRenderer::PreviewRenderer(int width, int height, Noggit::NoggitRenderContext context, QWidget* parent)
  :  Noggit::Ui::Tools::ViewportManager::Viewport(parent)
  , _camera (glm::vec3(0.0f, 0.0f, 0.0f), math::degrees(0.0f), math::degrees(0.0f))
  , _settings (new QSettings())
  , _width(width)
  , _height(height)
  , _liquid_texture_manager(context)
{
  _context = context;
  _cache = {};

  OpenGL::context::save_current_context const context_save (::gl);
  _offscreen_context.create();

  _fmt.setSamples(1);
  _fmt.setInternalTextureFormat(GL_RGBA8);
  _fmt.setAttachment(QOpenGLFramebufferObject::Depth);

  _offscreen_surface.create();
  _offscreen_context.makeCurrent(&_offscreen_surface);

  OpenGL::context::scoped_setter const context_set (::gl, &_offscreen_context);

  _light_dir = glm::vec3(0.0f, 1.0f, 0.0f);
}

void PreviewRenderer::setModel(std::string const &filename)
{
  _filename = filename;
  _model_instances.clear();
  _wmo_instances.clear();

  // add new model instance
  QString q_filename = QString(filename.c_str());

  if (q_filename.endsWith(".wmo"))
  {
    auto& instance = _wmo_instances.emplace_back(filename, _context);
    instance.wmo->wait_until_loaded();
    instance.recalcExtents();

  }
  else if (q_filename.endsWith(".m2"))
  {
    auto& instance = _model_instances.emplace_back(filename, _context);
    instance.model->wait_until_loaded();
    instance.recalcExtents();
  }
  else
  {
    throw std::logic_error("Preview renderer only supports viewing M2 and WMO for now.");
  }

  _lighting_needs_update = true;

  auto diffuse_color = _settings->value("assetBrowser/diffuse_light",
    QVariant::fromValue(QColor::fromRgbF(1.0f, 0.532352924f, 0.0f))).value<QColor>();
  _diffuse_light = {static_cast<float>(diffuse_color.redF()),
                    static_cast<float>(diffuse_color.greenF()),
                    static_cast<float>(diffuse_color.blueF())};

 auto ambient_color = _settings->value("assetBrowser/ambient_light",
     QVariant::fromValue(QColor::fromRgbF(0.407770514f, 0.508424163f, 0.602650642f))).value<QColor>();

 _ambient_light = {static_cast<float>(ambient_color.redF()),
                   static_cast<float>(ambient_color.greenF()),
                   static_cast<float>(ambient_color.blueF())};

  auto background_color = _settings->value("assetBrowser/background_color",
     QVariant::fromValue(QColor(127, 127, 127))).value<QColor>();

  _background_color = {static_cast<float>(background_color.redF()),
                       static_cast<float>(background_color.greenF()),
                       static_cast<float>(background_color.blueF())};

  resetCamera();
}

void PreviewRenderer::setModelOffscreen(std::string const& filename)
{
  OpenGL::context::save_current_context const context_save (::gl);
  _offscreen_context.makeCurrent(&_offscreen_surface);
  OpenGL::context::scoped_setter const context_set (::gl, &_offscreen_context);

  setModel(filename);
}


void PreviewRenderer::resetCamera(float x, float y, float z, float roll, float yaw, float pitch)
{
  _camera.reset(x, y, z, roll, yaw, pitch);
  float radius = 0.f;

  std::vector<glm::vec3> extents = calcSceneExtents();
  _camera.position = (extents[0] + extents[1]) / 2.0f;
  radius = std::max(glm::distance(_camera.position, extents[0]), glm::distance(_camera.position, extents[1]));

  float distance_factor = abs( radius / sin(_camera.fov()._ / 2.f));
  _camera.move_forward_factor(-1.f, distance_factor);

}


void PreviewRenderer::draw()
{
  if (!_uploaded)
  [[unlikely]]
  {  
    upload();
  }

  gl.clearColor(_background_color.r, _background_color.g, _background_color.b, 1.0f);
  gl.depthMask(GL_TRUE);
  gl.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  float culldistance = 10000000;

  auto mv = model_view();
  auto proj = projection();

  glm::mat4x4 const mvp(proj * mv);
  math::frustum const frustum (glm::transpose(mvp));

  updateMVPUniformBlock(mv, proj);

  if (_lighting_needs_update)
    updateLightingUniformBlock();

  gl.enable(GL_DEPTH_TEST);
  gl.depthFunc(GL_LEQUAL);
  gl.enable(GL_BLEND);
  gl.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

  // draw WMOs
  std::unordered_map<std::string, std::vector<ModelInstance*>> _wmo_doodads;

  if (_draw_wmo.get() && !_wmo_instances.empty())
  {
    /* set anim time only once per frame
    {
      OpenGL::Scoped::use_program water_shader {_liquid_render->shader_program()};
      water_shader.uniform("animtime", _animtime / 2880.f);

      //water_shader.uniform("model_view", model_view().transposed());
      //water_shader.uniform("projection", projection().transposed());

      //water_shader.uniform("ocean_color_light", ocean_color_light);
      //water_shader.uniform("ocean_color_dark", ocean_color_dark);
      //water_shader.uniform("river_color_light", river_color_light);
      //water_shader.uniform("river_color_dark", river_color_dark);
      //water_shader.uniform("use_transform", 1);
    }

     */

    {
      OpenGL::Scoped::use_program wmo_program{*_wmo_program.get()};

      wmo_program.uniform("camera", glm::vec3(_camera.position.x, _camera.position.y, _camera.position.z));


      for (auto& wmo_instance : _wmo_instances)
      {
        wmo_instance.wmo->wait_until_loaded();
        wmo_instance.wmo->waitForChildrenLoaded();
        wmo_instance.ensureExtents();
        wmo_instance.draw(
            wmo_program, model_view(), projection(), frustum, culldistance,
            _camera.position, _draw_boxes.get(), _draw_models.get() 
            , false, false, 0, false, display_mode::in_3D
            , true, true, false, false
        );

        auto doodads = wmo_instance.get_doodads(true);

        if (doodads)
        {
          for (auto& pair : *doodads)
          {
            for (auto& doodad : pair.second)
              _wmo_doodads[doodad.model->file_key().filepath()].push_back(&doodad);
          }
        }

     }

    }

  }

  // draw M2
  std::unordered_map<Model*, std::size_t> model_boxes_to_draw;

  if (_draw_models.get() && !(_model_instances.empty() && _wmo_doodads.empty()))
  {
    if (_draw_animated.get())
      ModelManager::resetAnim();

    OpenGL::Scoped::use_program m2_shader {*_m2_instanced_program.get()};

    OpenGL::M2RenderState model_render_state;
    model_render_state.tex_arrays = { 0, 0 };
    model_render_state.tex_indices = { 0, 0 };
    model_render_state.tex_unit_lookups = { 0, 0 };
    gl.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    gl.disable(GL_BLEND);
    gl.depthMask(GL_TRUE);
    m2_shader.uniform("blend_mode", 0);
    m2_shader.uniform("unfogged", static_cast<int>(model_render_state.unfogged));
    m2_shader.uniform("unlit", static_cast<int>(model_render_state.unlit));
    m2_shader.uniform("tex_unit_lookup_1", 0);
    m2_shader.uniform("tex_unit_lookup_2", 0);
    m2_shader.uniform("pixel_shader", 0);

    std::vector<ModelInstance*> instance{ nullptr };
    std::vector<glm::mat4x4> instance_mtx{ glm::mat4x4(1)};

    for (auto& model_instance : _model_instances)
    {
      model_instance.model->wait_until_loaded();
      model_instance.model->waitForChildrenLoaded();
      instance[0] = &model_instance;
      instance_mtx[0] = model_instance.transformMatrix();

      model_instance.model->renderer()->draw(
        mv
        , instance_mtx
        , m2_shader
        , model_render_state
        , frustum
        , culldistance
        , _camera.position
        , _animtime
        , _draw_boxes.get()
        , model_boxes_to_draw
        , display_mode::in_3D
        , false
        , _draw_animated.get()
        , true
        , false
      );
    }

    for (auto& it : _wmo_doodads)
    {
      instance_mtx.clear();
      
      for (auto& instance : it.second)
      {
        instance_mtx.push_back(instance->transformMatrix());
      }

      it.second[0]->model->renderer()->draw(
          mv
          , instance_mtx
          , m2_shader
          , model_render_state
          , frustum
          , culldistance
          , _camera.position
          , _animtime
          , _draw_boxes.get()
          , model_boxes_to_draw
          , display_mode::in_3D
          , false
          , _draw_animated.get()
          , false
          , false
      );
    }


    if(_draw_boxes.get() && !model_boxes_to_draw.empty())
    {
      OpenGL::Scoped::use_program m2_box_shader{ *_m2_box_program.get() };

      OpenGL::Scoped::bool_setter<GL_LINE_SMOOTH, GL_TRUE> const line_smooth;
      gl.hint (GL_LINE_SMOOTH_HINT, GL_NICEST);

      for (auto& it : model_boxes_to_draw)
      {
        glm::vec4 color = it.first->is_hidden()
                                ? glm::vec4(0.f, 0.f, 1.f, 1.f)
                                : ( it.first->use_fake_geometry()
                                    ? glm::vec4(1.f, 0.f, 0.f, 1.f)
                                    : glm::vec4(0.75f, 0.75f, 0.75f, 1.f)
                                )
        ;

        m2_box_shader.uniform("color", color);
        it.first->renderer()->drawBox(m2_box_shader, it.second);
      }
    }
  }


  gl.bindVertexArray(0);
  gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

  // model particles

  /*
  if (_draw_animated.get() && !model_with_particles.empty())
  {
    OpenGL::Scoped::bool_setter<GL_CULL_FACE, GL_FALSE> const cull;
    OpenGL::Scoped::depth_mask_setter<GL_FALSE> const depth_mask;

    OpenGL::Scoped::use_program particles_shader {*_m2_particles_program.get()};

    particles_shader.uniform("model_view_projection", mvp);
    particles_shader.uniform("tex", 0);
    OpenGL::texture::set_active_texture(0);

    for (auto& it : model_with_particles)
    {
      it.first->draw_particles(model_view().transposed(), particles_shader, it.second);
    }
  }

  if (_draw_animated.get() && !model_with_particles.empty())
  {
    OpenGL::Scoped::bool_setter<GL_CULL_FACE, GL_FALSE> const cull;
    OpenGL::Scoped::depth_mask_setter<GL_FALSE> const depth_mask;

    OpenGL::Scoped::use_program ribbon_shader {*_m2_ribbons_program.get()};

    ribbon_shader.uniform("model_view_projection", mvp);
    ribbon_shader.uniform("tex", 0);

    gl.blendFunc(GL_SRC_ALPHA, GL_ONE);

    for (auto& it : model_with_particles)
    {
      it.first->draw_ribbons(ribbon_shader, it.second);
    }
  }

  */

  gl.enable(GL_BLEND);
  gl.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

  if (_draw_grid.get())
  {
    _grid.draw(mvp, glm::vec3(0.f, 0.f, 0.f),
               glm::vec4(0.7f, 0.7f, 0.7f, 1.0f), 30.f);

  }

}

glm::mat4x4 PreviewRenderer::model_view() const
{
  return _camera.look_at_matrix();
}

glm::mat4x4 PreviewRenderer::projection() const
{
  float far_z = _settings->value("view_distance", 2000.f).toFloat();
  return glm::perspective(_camera.fov()._, aspect_ratio(), 1.f, far_z);
}

float PreviewRenderer::aspect_ratio() const
{
  return static_cast<float>(_width) / static_cast<float>(_height);
}

std::vector<glm::vec3> PreviewRenderer::calcSceneExtents()
{
  glm::vec3 min = {std::numeric_limits<float>::max(),
                         std::numeric_limits<float>::max(),
                         std::numeric_limits<float>::max()};

  glm::vec3 max = {std::numeric_limits<float>::min(),
                         std::numeric_limits<float>::min(),
                         std::numeric_limits<float>::min()};

  for (auto& instance : _model_instances)
  {
    for (int i = 0; i < 3; ++i)
    {
      min[i] = std::min(instance.getExtents()[0][i], min[i]);
      max[i] = std::max(instance.getExtents()[1][i], max[i]);
    }
  }

  for (auto& instance : _wmo_instances)
  {
    for (int i = 0; i < 3; ++i)
    {
      min[i] = std::min(instance.getExtents()[0][i], min[i]);
      max[i] = std::max(instance.getExtents()[1][i], max[i]);
    }
  }

  return std::move(std::vector<glm::vec3>{min, max});
}

QPixmap* PreviewRenderer::renderToPixmap()
{
  std::tuple<std::string, int, int> const curEntry{_filename, _width, _height};
  auto it{_cache.find(curEntry)};

  if(it != _cache.end())
    return &it->second;

  OpenGL::context::save_current_context const context_save (::gl);

  _offscreen_context.makeCurrent(&_offscreen_surface);

  OpenGL::context::scoped_setter const context_set (::gl, &_offscreen_context);

  QOpenGLFramebufferObject pixel_buffer(_width, _height, _fmt);
  pixel_buffer.bind();

  gl.viewport(0, 0, _width, _height);
  gl.clearColor(_background_color.r, _background_color.g, _background_color.b, 1.f);
  gl.depthMask(GL_TRUE);
  gl.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  tick(1.0f);
  draw();

  auto async_loader = AsyncLoader::instance;

  if (async_loader->is_loading())
  {
    // wait for the loader to finish
    // do
    // {
    //   std::this_thread::sleep_for(std::chrono::milliseconds(1));
    // } while (async_loader->is_loading());

    // redraw
    gl.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    draw();
  }

  // Clearing alpha from image
  gl.colorMask(false, false, false, true);
  gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
  glClear(GL_COLOR_BUFFER_BIT);
  gl.colorMask(true, true, true, true);

  QPixmap result{};
  result = std::move(QPixmap::fromImage(pixel_buffer.toImage()));
  pixel_buffer.release();

  if (result.isNull())
  {
    throw std::runtime_error("failed rendering " + _filename + " to pixmap");
  }

  return &(_cache[curEntry] = std::move(result));
}

void PreviewRenderer::setLightDirection(float y, float z)
{
  _light_dir = {1.f, 0.5f, 0.f};
  QMatrix4x4 matrix = QMatrix4x4();
  matrix.rotate(z, 0.f, 1.f, 0.f);
  matrix.rotate(y, 1.f, 0.f, 0.f);

  QVector3D light_dir = {_light_dir.x, _light_dir.y, _light_dir.z};
  light_dir = matrix * light_dir;

  _light_dir.x = light_dir.x();
  _light_dir.y = light_dir.y();
  _light_dir.z = light_dir.z();

  _lighting_needs_update = true;
}


void PreviewRenderer::update_emitters(float dt)
{
  while (dt > 0.1f)
  {
    ModelManager::updateEmitters(0.1f);
    dt -= 0.1f;
  }
  ModelManager::updateEmitters(dt);
}

void PreviewRenderer::tick(float dt)
{
  dt = std::min(dt, 1.0f);

  _animtime += dt * 1000.0f;

  if (_draw_animated.get())
  {
    update_emitters(dt);
  }
}


void PreviewRenderer::upload()
{
  _buffers.upload();

  // m2

  _m2_program.reset
  (new OpenGL::program
    { { GL_VERTEX_SHADER,   OpenGL::shader::src_from_qrc("m2_vs") }
      , { GL_FRAGMENT_SHADER, OpenGL::shader::src_from_qrc("m2_fs") }
    }
  );

  {
    OpenGL::Scoped::use_program m2_shader{ *_m2_program.get() };
    m2_shader.uniform("bone_matrices", 0);
    m2_shader.uniform("tex1", 1);
    m2_shader.uniform("tex2", 2);

    m2_shader.bind_uniform_block("matrices", 0);
    gl.bindBuffer(GL_UNIFORM_BUFFER, _mvp_ubo);
    gl.bufferData(GL_UNIFORM_BUFFER, sizeof(OpenGL::MVPUniformBlock), NULL, GL_DYNAMIC_DRAW);
    gl.bindBufferRange(GL_UNIFORM_BUFFER, OpenGL::ubo_targets::MVP, _mvp_ubo, 0, sizeof(OpenGL::MVPUniformBlock));
    gl.bindBuffer(GL_UNIFORM_BUFFER, 0);

    m2_shader.bind_uniform_block("lighting", 1);
    gl.bindBuffer(GL_UNIFORM_BUFFER, _lighting_ubo);
    gl.bufferData(GL_UNIFORM_BUFFER, sizeof(OpenGL::LightingUniformBlock), NULL, GL_DYNAMIC_DRAW);
    gl.bindBufferRange(GL_UNIFORM_BUFFER, OpenGL::ubo_targets::LIGHTING, _lighting_ubo, 0, sizeof(OpenGL::LightingUniformBlock));
    gl.bindBuffer(GL_UNIFORM_BUFFER, 0);
  }

  // m2 instaced
  
  _m2_instanced_program.reset
  (new OpenGL::program
    { { GL_VERTEX_SHADER,   OpenGL::shader::src_from_qrc("m2_vs", {"instanced"}) }
        , { GL_FRAGMENT_SHADER, OpenGL::shader::src_from_qrc("m2_fs") }
    }
  );

  {
    OpenGL::Scoped::use_program m2_shader_instanced{ *_m2_instanced_program.get() };
    m2_shader_instanced.bind_uniform_block("matrices", 0);
    m2_shader_instanced.bind_uniform_block("lighting", 1);
    m2_shader_instanced.uniform("bone_matrices", 0);
    m2_shader_instanced.uniform("tex1", 1);
    m2_shader_instanced.uniform("tex2", 2);
  }
 
  // m2 box

  _m2_box_program.reset
  (new OpenGL::program
    { { GL_VERTEX_SHADER,   OpenGL::shader::src_from_qrc("m2_box_vs") }
        , { GL_FRAGMENT_SHADER, OpenGL::shader::src_from_qrc("m2_box_fs") }
    }
  );

  {
    OpenGL::Scoped::use_program m2_box_shader{ *_m2_box_program.get() };
    m2_box_shader.bind_uniform_block("matrices", 0);
  }


  /*
  

  _m2_ribbons_program.reset
  (new OpenGL::program
    { { GL_VERTEX_SHADER,   OpenGL::shader::src_from_qrc("ribbon_vs") }
        , { GL_FRAGMENT_SHADER, OpenGL::shader::src_from_qrc("ribbon_fs") }
    }
  );
  

  _m2_particles_program.reset
  (new OpenGL::program
    { { GL_VERTEX_SHADER,   OpenGL::shader::src_from_qrc("particle_vs") }
        , { GL_FRAGMENT_SHADER, OpenGL::shader::src_from_qrc("particle_fs") }
    }
  );

  */

  // wmo
  
  _wmo_program.reset
  (new OpenGL::program
    { { GL_VERTEX_SHADER,   OpenGL::shader::src_from_qrc("wmo_vs") }
        , { GL_FRAGMENT_SHADER, OpenGL::shader::src_from_qrc("wmo_fs") }
    }
  );

  {
    std::vector<int> samplers{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };

    OpenGL::Scoped::use_program wmo_program{ *_wmo_program.get() };
    wmo_program.uniform("render_batches_tex", 0);
    wmo_program.uniform("texture_samplers", samplers);
    wmo_program.bind_uniform_block("matrices", 0);
    wmo_program.bind_uniform_block("lighting", 1);
  }

  // liquid
  _liquid_texture_manager.upload();
  _liquid_program.reset
    (new OpenGL::program
      { { GL_VERTEX_SHADER,   OpenGL::shader::src_from_qrc("liquid_vs") }
          , { GL_FRAGMENT_SHADER, OpenGL::shader::src_from_qrc("liquid_fs") }
      }
    );

  {
    OpenGL::Scoped::use_program liquid_render{ *_liquid_program.get() };

    //setupLiquidChunkBuffers();
    //setupLiquidChunkVAO(liquid_render);

    static std::vector<int> samplers{ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };

    liquid_render.bind_uniform_block("matrices", 0);
    liquid_render.bind_uniform_block("lighting", 1);
    liquid_render.bind_uniform_block("liquid_layers_params", 4);
    liquid_render.uniform("vertex_data", 0);
    liquid_render.uniform("texture_samplers", samplers);

  }

  setModel("world/wmo/azeroth/buildings/human_farm/farm.wmo");

  auto background_color = _settings->value("assetBrowser/background_color",
    QVariant::fromValue(QColor(127, 127, 127))).value<QColor>();

  _background_color = { static_cast<float>(background_color.redF()),
                       static_cast<float>(background_color.greenF()),
                       static_cast<float>(background_color.blueF()) };
 
  _uploaded = true;

}


void PreviewRenderer::unload()
{
  _buffers.unload();

  _m2_program.reset();
  _m2_instanced_program.reset();
  _m2_particles_program.reset();
  _m2_ribbons_program.reset();
  _m2_box_program.reset();
  _wmo_program.reset();
  _liquid_program.reset();
  _liquid_texture_manager.unload();

  _model_instances.clear();
  _wmo_instances.clear();

  _uploaded = false;

}

void PreviewRenderer::unloadOpenglData()
{
  if (_offscreen_mode)
  {
    OpenGL::context::save_current_context const context_save (::gl);
    _offscreen_context.makeCurrent(&_offscreen_surface);
    OpenGL::context::scoped_setter const context_set (::gl, &_offscreen_context);

    unload();
    return;
  }

  makeCurrent();
  OpenGL::context::scoped_setter const _ (::gl, context());

  ModelManager::unload_all(_context);
  WMOManager::unload_all(_context);
  TextureManager::unload_all(_context);

  unload();
}

void Noggit::Ui::Tools::PreviewRenderer::updateLightingUniformBlock()
{

  glm::vec4 ocean_color_light(glm::vec3(1.0f, 1.0f, 1.0f), 1.f);
  glm::vec4 ocean_color_dark(glm::vec3(1.0f, 1.0f, 1.0f), 1.f);
  glm::vec4 river_color_light(glm::vec3(1.0f, 1.0f, 1.0f), 1.f);
  glm::vec4 river_color_dark(glm::vec3(1.0f, 1.0f, 1.0f), 1.f);

  _lighting_ubo_data.DiffuseColor_FogStart = { _diffuse_light.x,_diffuse_light.y,_diffuse_light.z, 0};
  _lighting_ubo_data.AmbientColor_FogEnd = { _ambient_light.x, _ambient_light.y, _ambient_light.z, 0};
  _lighting_ubo_data.FogColor_FogOn = { 0, 0, 0, 0};
  _lighting_ubo_data.LightDir_FogRate = { _light_dir.x, _light_dir.y, _light_dir.z, 1.0f};
  _lighting_ubo_data.OceanColorLight = { 1.0f, 1.0f, 1.0f, 1.0f };
  _lighting_ubo_data.OceanColorDark = { 1.0f, 1.0f, 1.0f, 1.0f };
  _lighting_ubo_data.RiverColorLight = { 1.0f, 1.0f, 1.0f, 1.0f };
  _lighting_ubo_data.RiverColorDark = { 1.0f, 1.0f, 1.0f, 1.0f };

  gl.bindBuffer(GL_UNIFORM_BUFFER, _lighting_ubo);
  gl.bufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(OpenGL::LightingUniformBlock), &_lighting_ubo_data);
  
  _lighting_needs_update = false;
}

void Noggit::Ui::Tools::PreviewRenderer::updateMVPUniformBlock(const glm::mat4x4& model_view, const glm::mat4x4& projection)
{
  _mvp_ubo_data.model_view = model_view;
  _mvp_ubo_data.projection = projection;

  gl.bindBuffer(GL_UNIFORM_BUFFER, _mvp_ubo);
  gl.bufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(OpenGL::MVPUniformBlock), &_mvp_ubo_data);

}
