#include "config.h"

#include "gskglbufferprivate.h"

/* {{{ GskGLBuffer */

struct _GskGLBuffer
{
  GskGpuBuffer parent_instance;

  GLenum target;
  GLuint buffer_id;
  guchar *data;
};

G_DEFINE_TYPE (GskGLBuffer, gsk_gl_buffer, GSK_TYPE_GPU_BUFFER)

static void
gsk_gl_buffer_finalize (GObject *object)
{
  GskGLBuffer *self = GSK_GL_BUFFER (object);

  glDeleteBuffers (1, &self->buffer_id);

  G_OBJECT_CLASS (gsk_gl_buffer_parent_class)->finalize (object);
}

static guchar *
gsk_gl_buffer_map (GskGpuBuffer *buffer)
{
  GskGLBuffer *self = GSK_GL_BUFFER (buffer);

  return self->data;
}

static void
gsk_gl_buffer_unmap (GskGpuBuffer *buffer,
                     gsize         used)
{
}

static void
gsk_gl_buffer_class_init (GskGLBufferClass *klass)
{
  GskGpuBufferClass *buffer_class = GSK_GPU_BUFFER_CLASS (klass);
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

  buffer_class->map = gsk_gl_buffer_map;
  buffer_class->unmap = gsk_gl_buffer_unmap;

  gobject_class->finalize = gsk_gl_buffer_finalize;
}

static void
gsk_gl_buffer_init (GskGLBuffer *self)
{
}

void
gsk_gl_buffer_bind (GskGLBuffer *self)
{
  glBindBuffer (self->target, self->buffer_id);
}

void
gsk_gl_buffer_bind_range (GskGLBuffer *self,
                          GLuint       index,
                          GLintptr     offset,
                          GLsizeiptr   size)
{
  glBindBufferRange (self->target,
                     index,
                     self->buffer_id,
                     offset,
                     size);
}

static void
gsk_gl_buffer_setup (GskGLBuffer *self,
                     GLenum       target,
                     gsize        size)
{
  gsk_gpu_buffer_setup (GSK_GPU_BUFFER (self), size);

  self->target = target;

  glGenBuffers (1, &self->buffer_id);
}

/* }}} */
/* {{{ GskGLMappedBuffer */

struct _GskGLMappedBuffer
{
  GskGLBuffer parent_instance;
};

G_DEFINE_TYPE (GskGLMappedBuffer, gsk_gl_mapped_buffer, GSK_TYPE_GL_BUFFER)

static void
gsk_gl_mapped_buffer_finalize (GObject *object)
{
  GskGLBuffer *self = GSK_GL_BUFFER (object);

  gsk_gl_buffer_bind (self);
  glUnmapBuffer (self->target);

  G_OBJECT_CLASS (gsk_gl_mapped_buffer_parent_class)->finalize (object);
}

static void
gsk_gl_mapped_buffer_class_init (GskGLMappedBufferClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->finalize = gsk_gl_mapped_buffer_finalize;
}

static void
gsk_gl_mapped_buffer_init (GskGLMappedBuffer *self)
{
}

GskGpuBuffer *
gsk_gl_mapped_buffer_new (GLenum target,
                          gsize  size)
{
  GskGLBuffer *self;

  self = g_object_new (GSK_TYPE_GL_MAPPED_BUFFER, NULL);

  gsk_gl_buffer_setup (self, target, size);
  gsk_gl_buffer_bind (self);

  glBufferStorage (target,
                   size,
                   NULL,
                   GL_MAP_READ_BIT | GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT);
  self->data = glMapBufferRange (target,
                                 0,
                                 size,
                                 GL_MAP_READ_BIT | GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT);

  return GSK_GPU_BUFFER (self);
}

/* }}} */
/* {{{ GskGLCopiedBuffer */

struct _GskGLCopiedBuffer
{
  GskGLBuffer parent_instance;
};

G_DEFINE_TYPE (GskGLCopiedBuffer, gsk_gl_copied_buffer, GSK_TYPE_GL_BUFFER)

static void
gsk_gl_copied_buffer_finalize (GObject *object)
{
  GskGLBuffer *self = GSK_GL_BUFFER (object);

  g_free (self->data);

  G_OBJECT_CLASS (gsk_gl_copied_buffer_parent_class)->finalize (object);
}

static void
gsk_gl_copied_buffer_unmap (GskGpuBuffer *buffer,
                            gsize         used)
{
  GskGLBuffer *self = GSK_GL_BUFFER (buffer);

  if (used == 0)
    return;

  gsk_gl_buffer_bind (self);

  glBufferSubData (self->target, 0, used, self->data);
}

static void
gsk_gl_copied_buffer_class_init (GskGLCopiedBufferClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  GskGpuBufferClass *buffer_class = GSK_GPU_BUFFER_CLASS (klass);

  gobject_class->finalize = gsk_gl_copied_buffer_finalize;

  buffer_class->unmap = gsk_gl_copied_buffer_unmap;
}

static void
gsk_gl_copied_buffer_init (GskGLCopiedBuffer *self)
{
}

GskGpuBuffer *
gsk_gl_copied_buffer_new (GLenum target,
                          gsize  size)
{
  GskGLBuffer *self;

  self = g_object_new (GSK_TYPE_GL_COPIED_BUFFER, NULL);

  gsk_gl_buffer_setup (self, target, size);
  gsk_gl_buffer_bind (self);

  glBufferData (target, size, NULL, GL_STATIC_DRAW);
  self->data = malloc (size);

  return GSK_GPU_BUFFER (self);
}

/* }}} */
