From bca54ab1f6aae0a6807a0d1fc56e75cd35848b2c Mon Sep 17 00:00:00 2001
From: Geoffrey McRae <geoff@hostfission.com>
Date: Tue, 1 Oct 2019 23:17:20 +1000
Subject: [PATCH] [client/host] added new asyncronous memory copy

This changes the method of the memory copy from the host application to
the guest. Instead of performing a full copy from the capture device
into shared memory, and then flagging the new frame, we instead set a
write pointer, flag the client that there is a new frame and then copy
in chunks of 1024 bytes until the entire frame is copied. The client
upon seeing the new frame flag begins to poll at high frequency the
write pointer and upon each update copies as much as it can into the
texture.

This should improve latency but also slightly increase CPU usage on the
client due to the high frequency polling.
---
 VERSION                                       |   2 +-
 c-host/include/interface/capture.h            |   5 +-
 .../platform/Windows/capture/DXGI/src/dxgi.c  |  15 +-
 .../Windows/capture/NVFBC/src/nvfbc.c         |  15 +-
 c-host/src/app.c                              |  12 +-
 client/include/interface/renderer.h           |   3 +-
 client/renderers/EGL/desktop.c                |  10 +-
 client/renderers/EGL/desktop.h                |   2 +-
 client/renderers/EGL/egl.c                    |   4 +-
 client/renderers/EGL/texture.c                |  19 +
 client/renderers/EGL/texture.h                |   2 +
 client/renderers/OpenGL/opengl.c              | 402 +++++++-----------
 client/src/main.c                             |   4 +-
 common/CMakeLists.txt                         |   1 +
 common/include/common/KVMFR.h                 |   6 +-
 common/include/common/framebuffer.h           |  48 +++
 common/src/framebuffer.c                      |  85 ++++
 17 files changed, 358 insertions(+), 277 deletions(-)
 create mode 100644 common/include/common/framebuffer.h
 create mode 100644 common/src/framebuffer.c

diff --git a/VERSION b/VERSION
index 22dd0453..abc8c589 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-fetch-4-ge93bd7a3bf+1
\ No newline at end of file
+fetch-6-g59013b2e3f+1
\ No newline at end of file
diff --git a/c-host/include/interface/capture.h b/c-host/include/interface/capture.h
index 535c04f9..daa6040b 100644
--- a/c-host/include/interface/capture.h
+++ b/c-host/include/interface/capture.h
@@ -21,6 +21,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
 
 #include <stdbool.h>
 #include <stdint.h>
+#include "common/framebuffer.h"
 
 typedef enum CaptureResult
 {
@@ -55,7 +56,6 @@ typedef struct CaptureFrame
   unsigned int   pitch;
   unsigned int   stride;
   CaptureFormat  format;
-  void         * data;
 }
 CaptureFrame;
 
@@ -84,7 +84,8 @@ typedef struct CaptureInterface
   unsigned int  (*getMaxFrameSize)();
 
   CaptureResult (*capture   )();
-  CaptureResult (*getFrame  )(CaptureFrame   * frame  );
+  CaptureResult (*waitFrame )(CaptureFrame   * frame  );
+  CaptureResult (*getFrame  )(FrameBuffer      frame  );
   CaptureResult (*getPointer)(CapturePointer * pointer);
 }
 CaptureInterface;
\ No newline at end of file
diff --git a/c-host/platform/Windows/capture/DXGI/src/dxgi.c b/c-host/platform/Windows/capture/DXGI/src/dxgi.c
index 3aac2dde..01c9ac33 100644
--- a/c-host/platform/Windows/capture/DXGI/src/dxgi.c
+++ b/c-host/platform/Windows/capture/DXGI/src/dxgi.c
@@ -766,7 +766,7 @@ static CaptureResult dxgi_capture()
   return CAPTURE_RESULT_OK;
 }
 
-static CaptureResult dxgi_getFrame(CaptureFrame * frame)
+static CaptureResult dxgi_waitFrame(CaptureFrame * frame)
 {
   assert(this);
   assert(this->initialized);
@@ -778,7 +778,6 @@ static CaptureResult dxgi_getFrame(CaptureFrame * frame)
   if (this->stop)
     return CAPTURE_RESULT_REINIT;
 
-  // only reset the event if we used the texture
   os_resetEvent(tex->mapped);
 
   frame->width  = this->width;
@@ -787,7 +786,16 @@ static CaptureResult dxgi_getFrame(CaptureFrame * frame)
   frame->stride = this->stride;
   frame->format = this->format;
 
-  memcpy(frame->data, tex->map.pData, this->pitch * this->height);
+  return CAPTURE_RESULT_OK;
+}
+
+static CaptureResult dxgi_getFrame(FrameBuffer frame)
+{
+  assert(this);
+  assert(this->initialized);
+
+  Texture * tex = &this->texture[this->texRIndex];
+  framebuffer_write(frame, tex->map.pData, this->pitch * this->height);
   os_signalEvent(tex->free);
 
   if (++this->texRIndex == this->maxTextures)
@@ -867,6 +875,7 @@ struct CaptureInterface Capture_DXGI =
   .free            = dxgi_free,
   .getMaxFrameSize = dxgi_getMaxFrameSize,
   .capture         = dxgi_capture,
+  .waitFrame       = dxgi_waitFrame,
   .getFrame        = dxgi_getFrame,
   .getPointer      = dxgi_getPointer
 };
\ No newline at end of file
diff --git a/c-host/platform/Windows/capture/NVFBC/src/nvfbc.c b/c-host/platform/Windows/capture/NVFBC/src/nvfbc.c
index 8c76f138..cf0a1031 100644
--- a/c-host/platform/Windows/capture/NVFBC/src/nvfbc.c
+++ b/c-host/platform/Windows/capture/NVFBC/src/nvfbc.c
@@ -23,6 +23,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
 #include "windows/debug.h"
 #include "windows/mousehook.h"
 #include "common/option.h"
+#include "common/framebuffer.h"
 #include <assert.h>
 #include <stdlib.h>
 #include <windows.h>
@@ -236,7 +237,7 @@ static CaptureResult nvfbc_capture()
   return CAPTURE_RESULT_OK;
 }
 
-static CaptureResult nvfbc_getFrame(CaptureFrame * frame)
+static CaptureResult nvfbc_waitFrame(CaptureFrame * frame)
 {
   if (!os_waitEvent(this->frameEvent, 1000))
     return CAPTURE_RESULT_TIMEOUT;
@@ -266,7 +267,16 @@ static CaptureResult nvfbc_getFrame(CaptureFrame * frame)
 #endif
 
   frame->format = this->grabInfo.bIsHDR ? CAPTURE_FMT_RGBA10 : CAPTURE_FMT_BGRA;
-  memcpy(frame->data, this->frameBuffer, frame->pitch * frame->height);
+  return CAPTURE_RESULT_OK;
+}
+
+static CaptureResult nvfbc_getFrame(FrameBuffer frame)
+{
+  framebuffer_write(
+    frame,
+    this->frameBuffer,
+    this->grabInfo.dwHeight * this->grabInfo.dwBufferWidth * 4
+  );
   return CAPTURE_RESULT_OK;
 }
 
@@ -310,6 +320,7 @@ struct CaptureInterface Capture_NVFBC =
   .free            = nvfbc_free,
   .getMaxFrameSize = nvfbc_getMaxFrameSize,
   .capture         = nvfbc_capture,
+  .waitFrame       = nvfbc_waitFrame,
   .getFrame        = nvfbc_getFrame,
   .getPointer      = nvfbc_getPointer
 };
\ No newline at end of file
diff --git a/c-host/src/app.c b/c-host/src/app.c
index 89c3e7a7..771a02a0 100644
--- a/c-host/src/app.c
+++ b/c-host/src/app.c
@@ -49,7 +49,7 @@ struct app
 
   uint8_t     * frames;
   unsigned int  frameSize;
-  uint8_t     * frame[MAX_FRAMES];
+  FrameBuffer   frame[MAX_FRAMES];
   unsigned int  frameOffset[MAX_FRAMES];
 
   bool             running;
@@ -168,9 +168,7 @@ static int frameThread(void * opaque)
 
   while(app.running)
   {
-    frame.data = app.frame[frameIndex];
-
-    switch(app.iface->getFrame(&frame))
+    switch(app.iface->waitFrame(&frame))
     {
       case CAPTURE_RESULT_OK:
         break;
@@ -226,7 +224,9 @@ static int frameThread(void * opaque)
     fi->dataPos = app.frameOffset[frameIndex];
     frameValid  = true;
 
+    framebuffer_prepare(app.frame[frameIndex]);
     INTERLOCKED_OR8(&fi->flags, KVMFR_FRAME_FLAG_UPDATE);
+    app.iface->getFrame(app.frame[frameIndex]);
 
     if (++frameIndex == MAX_FRAMES)
       frameIndex = 0;
@@ -369,8 +369,8 @@ int app_main(int argc, char * argv[])
 
   for (int i = 0; i < MAX_FRAMES; ++i)
   {
-    app.frame      [i] = app.frames + i * app.frameSize;
-    app.frameOffset[i] = app.frame[i] - shmemMap;
+    app.frame      [i] = (FrameBuffer)(app.frames + i * app.frameSize);
+    app.frameOffset[i] = (uint8_t *)app.frame[i] - shmemMap;
     DEBUG_INFO("Frame %d          : 0x%" PRIXPTR " (0x%08x)", i, (uintptr_t)app.frame[i], app.frameOffset[i]);
   }
 
diff --git a/client/include/interface/renderer.h b/client/include/interface/renderer.h
index d4ab9293..f5e80e4c 100644
--- a/client/include/interface/renderer.h
+++ b/client/include/interface/renderer.h
@@ -26,6 +26,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
 
 #include "app.h"
 #include "common/KVMFR.h"
+#include "common/framebuffer.h"
 
 #define IS_LG_RENDERER_VALID(x) \
   ((x)->get_name       && \
@@ -89,7 +90,7 @@ typedef void         (* LG_RendererDeInitialize)(void * opaque);
 typedef void         (* LG_RendererOnResize    )(void * opaque, const int width, const int height, const LG_RendererRect destRect);
 typedef bool         (* LG_RendererOnMouseShape)(void * opaque, const LG_RendererCursor cursor, const int width, const int height, const int pitch, const uint8_t * data);
 typedef bool         (* LG_RendererOnMouseEvent)(void * opaque, const bool visible , const int x, const int y);
-typedef bool         (* LG_RendererOnFrameEvent)(void * opaque, const LG_RendererFormat format, const uint8_t * data);
+typedef bool         (* LG_RendererOnFrameEvent)(void * opaque, const LG_RendererFormat format, const FrameBuffer frame);
 typedef void         (* LG_RendererOnAlert     )(void * opaque, const LG_MsgAlert alert, const char * message, bool ** closeFlag);
 typedef bool         (* LG_RendererRender      )(void * opaque, SDL_Window *window);
 typedef void         (* LG_RendererUpdateFPS   )(void * opaque, const float avgUPS, const float avgFPS);
diff --git a/client/renderers/EGL/desktop.c b/client/renderers/EGL/desktop.c
index b684cca9..8821705b 100644
--- a/client/renderers/EGL/desktop.c
+++ b/client/renderers/EGL/desktop.c
@@ -60,7 +60,7 @@ struct EGL_Desktop
   enum EGL_PixelFormat pixFmt;
   unsigned int         width, height;
   unsigned int         pitch;
-  const uint8_t      * data;
+  FrameBuffer          frame;
   bool                 update;
 
   // night vision
@@ -181,7 +181,7 @@ void egl_desktop_free(EGL_Desktop ** desktop)
   *desktop = NULL;
 }
 
-bool egl_desktop_prepare_update(EGL_Desktop * desktop, const bool sourceChanged, const LG_RendererFormat format, const uint8_t * data)
+bool egl_desktop_prepare_update(EGL_Desktop * desktop, const bool sourceChanged, const LG_RendererFormat format, const FrameBuffer frame)
 {
   if (sourceChanged)
   {
@@ -217,7 +217,7 @@ bool egl_desktop_prepare_update(EGL_Desktop * desktop, const bool sourceChanged,
     desktop->width  = format.width;
     desktop->height = format.height;
     desktop->pitch  = format.pitch;
-    desktop->data   = data;
+    desktop->frame  = frame;
     desktop->update = true;
 
     /* defer the actual update as the format has changed and we need to issue GL commands first */
@@ -226,7 +226,7 @@ bool egl_desktop_prepare_update(EGL_Desktop * desktop, const bool sourceChanged,
   }
 
   /* update the texture now */
-  return egl_texture_update(desktop->texture, data);
+  return egl_texture_update_from_frame(desktop->texture, frame);
 }
 
 void egl_desktop_perform_update(EGL_Desktop * desktop, const bool sourceChanged)
@@ -253,7 +253,7 @@ void egl_desktop_perform_update(EGL_Desktop * desktop, const bool sourceChanged)
   if (desktop->update)
   {
     desktop->update = false;
-    egl_texture_update(desktop->texture, desktop->data);
+    egl_texture_update_from_frame(desktop->texture, desktop->frame);
   }
 }
 
diff --git a/client/renderers/EGL/desktop.h b/client/renderers/EGL/desktop.h
index 4738539a..09de74ef 100644
--- a/client/renderers/EGL/desktop.h
+++ b/client/renderers/EGL/desktop.h
@@ -28,6 +28,6 @@ typedef struct EGL_Desktop EGL_Desktop;
 bool egl_desktop_init(EGL_Desktop ** desktop);
 void egl_desktop_free(EGL_Desktop ** desktop);
 
-bool egl_desktop_prepare_update(EGL_Desktop * desktop, const bool sourceChanged, const LG_RendererFormat format, const uint8_t * data);
+bool egl_desktop_prepare_update(EGL_Desktop * desktop, const bool sourceChanged, const LG_RendererFormat format, const FrameBuffer frame);
 void egl_desktop_perform_update(EGL_Desktop * desktop, const bool sourceChanged);
 bool egl_desktop_render(EGL_Desktop * desktop, const float x, const float y, const float scaleX, const float scaleY, const bool nearest);
\ No newline at end of file
diff --git a/client/renderers/EGL/egl.c b/client/renderers/EGL/egl.c
index 27f0a1b3..42012d82 100644
--- a/client/renderers/EGL/egl.c
+++ b/client/renderers/EGL/egl.c
@@ -296,7 +296,7 @@ bool egl_on_mouse_event(void * opaque, const bool visible, const int x, const in
   return true;
 }
 
-bool egl_on_frame_event(void * opaque, const LG_RendererFormat format, const uint8_t * data)
+bool egl_on_frame_event(void * opaque, const LG_RendererFormat format, const FrameBuffer frame)
 {
   struct Inst * this = (struct Inst *)opaque;
   this->sourceChanged = (
@@ -312,7 +312,7 @@ bool egl_on_frame_event(void * opaque, const LG_RendererFormat format, const uin
 
   this->useNearest = this->width < format.width || this->height < format.height;
 
-  if (!egl_desktop_prepare_update(this->desktop, this->sourceChanged, format, data))
+  if (!egl_desktop_prepare_update(this->desktop, this->sourceChanged, format, frame))
   {
     DEBUG_INFO("Failed to prepare to update the desktop");
     return false;
diff --git a/client/renderers/EGL/texture.c b/client/renderers/EGL/texture.c
index 1c735663..98897631 100644
--- a/client/renderers/EGL/texture.c
+++ b/client/renderers/EGL/texture.c
@@ -20,6 +20,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
 #include "texture.h"
 #include "common/debug.h"
 #include "common/locking.h"
+#include "common/framebuffer.h"
 #include "debug.h"
 #include "utils.h"
 
@@ -278,6 +279,24 @@ bool egl_texture_update(EGL_Texture * texture, const uint8_t * buffer)
   return true;
 }
 
+bool egl_texture_update_from_frame(EGL_Texture * texture, const FrameBuffer frame)
+{
+  if (!texture->streaming)
+    return false;
+
+  if (texture->pboCount == 2)
+    return true;
+
+  framebuffer_read(frame, texture->pboMap[texture->pboWIndex], texture->pboBufferSize);
+  texture->pboSync[texture->pboWIndex] = 0;
+
+  if (++texture->pboWIndex == 2)
+    texture->pboWIndex = 0;
+  INTERLOCKED_INC(&texture->pboCount);
+
+  return true;
+}
+
 enum EGL_TexStatus egl_texture_process(EGL_Texture * texture)
 {
   if (!texture->streaming)
diff --git a/client/renderers/EGL/texture.h b/client/renderers/EGL/texture.h
index 8f204b6a..b8ee49e0 100644
--- a/client/renderers/EGL/texture.h
+++ b/client/renderers/EGL/texture.h
@@ -21,6 +21,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
 
 #include <stdbool.h>
 #include "shader.h"
+#include "common/framebuffer.h"
 
 #include <GL/gl.h>
 
@@ -46,6 +47,7 @@ void egl_texture_free(EGL_Texture ** tex);
 
 bool               egl_texture_setup  (EGL_Texture * texture, enum EGL_PixelFormat pixfmt, size_t width, size_t height, size_t stride, bool streaming);
 bool               egl_texture_update (EGL_Texture * texture, const uint8_t * buffer);
+bool               egl_texture_update_from_frame(EGL_Texture * texture, const FrameBuffer frame);
 enum EGL_TexStatus egl_texture_process(EGL_Texture * texture);
 enum EGL_TexStatus egl_texture_bind          (EGL_Texture * texture);
 int                egl_texture_count         (EGL_Texture * texture);
\ No newline at end of file
diff --git a/client/renderers/OpenGL/opengl.c b/client/renderers/OpenGL/opengl.c
index a1f1bde4..7768bd21 100644
--- a/client/renderers/OpenGL/opengl.c
+++ b/client/renderers/OpenGL/opengl.c
@@ -32,8 +32,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA
 
 #include "common/debug.h"
 #include "common/option.h"
+#include "common/framebuffer.h"
 #include "utils.h"
-#include "lg-decoders.h"
 #include "dynamic/fonts.h"
 #include "ll.h"
 
@@ -122,8 +122,8 @@ struct Inst
   GLuint            vboFormat;
   GLuint            dataFormat;
   size_t            texSize;
-  const LG_Decoder* decoder;
-  void            * decoderData;
+  size_t            texPos;
+  FrameBuffer       frame;
 
   uint64_t          drawStart;
   bool              hasBuffers;
@@ -140,7 +140,6 @@ struct Inst
   bool              hasTextures, hasFrames;
   GLuint            frames[BUFFER_COUNT];
   GLsync            fences[BUFFER_COUNT];
-  void            * decoderFrames[BUFFER_COUNT];
   GLuint            textures[TEXTURE_COUNT];
   struct ll       * alerts;
   int               alertList;
@@ -362,7 +361,7 @@ bool opengl_on_mouse_event(void * opaque, const bool visible, const int x, const
   return false;
 }
 
-bool opengl_on_frame_event(void * opaque, const LG_RendererFormat format, const uint8_t * data)
+bool opengl_on_frame_event(void * opaque, const LG_RendererFormat format, const FrameBuffer frame)
 {
   struct Inst * this = (struct Inst *)opaque;
   if (!this)
@@ -394,12 +393,7 @@ bool opengl_on_frame_event(void * opaque, const LG_RendererFormat format, const
   LG_UNLOCK(this->formatLock);
 
   LG_LOCK(this->syncLock);
-  if (!this->decoder->decode(this->decoderData, data, format.pitch))
-  {
-    DEBUG_ERROR("decode returned failure");
-    LG_UNLOCK(this->syncLock);
-    return false;
-  }
+  this->frame       = frame;
   this->frameUpdate = true;
   LG_UNLOCK(this->syncLock);
 
@@ -849,13 +843,21 @@ static bool configure(struct Inst * this, SDL_Window *window)
   switch(this->format.type)
   {
     case FRAME_TYPE_BGRA:
-    case FRAME_TYPE_RGBA:
-    case FRAME_TYPE_RGBA10:
-      this->decoder = &LGD_NULL;
+      this->intFormat  = GL_RGBA8;
+      this->vboFormat  = GL_BGRA;
+      this->dataFormat = GL_UNSIGNED_BYTE;
       break;
 
-    case FRAME_TYPE_YUV420:
-      this->decoder = &LGD_YUV420;
+    case FRAME_TYPE_RGBA:
+      this->intFormat  = GL_RGBA8;
+      this->vboFormat  = GL_RGBA;
+      this->dataFormat = GL_UNSIGNED_BYTE;
+      break;
+
+    case FRAME_TYPE_RGBA10:
+      this->intFormat  = GL_RGB10_A2;
+      this->vboFormat  = GL_RGBA;
+      this->dataFormat = GL_UNSIGNED_INT_2_10_10_10_REV;
       break;
 
     default:
@@ -863,128 +865,73 @@ static bool configure(struct Inst * this, SDL_Window *window)
       return false;
   }
 
-  DEBUG_INFO("Using decoder: %s", this->decoder->name);
-
-  if (!this->decoder->create(&this->decoderData))
-  {
-    DEBUG_ERROR("Failed to create the decoder");
-    return false;
-  }
-
-  if (!this->decoder->initialize(
-    this->decoderData,
-    this->format,
-    window))
-  {
-    DEBUG_ERROR("Failed to initialize decoder");
-    return false;
-  }
-
-  switch(this->decoder->get_out_format(this->decoderData))
-  {
-    case LG_OUTPUT_BGRA:
-      this->intFormat  = GL_RGBA8;
-      this->vboFormat  = GL_BGRA;
-      this->dataFormat = GL_UNSIGNED_BYTE;
-      break;
-
-    case LG_OUTPUT_RGBA:
-      this->intFormat  = GL_RGBA8;
-      this->vboFormat  = GL_RGBA;
-      this->dataFormat = GL_UNSIGNED_BYTE;
-      break;
-
-    case LG_OUTPUT_RGBA10:
-      this->intFormat  = GL_RGB10_A2;
-      this->vboFormat  = GL_RGBA;
-      this->dataFormat = GL_UNSIGNED_INT_2_10_10_10_REV;
-      break;
-
-    case LG_OUTPUT_YUV420:
-      // fixme
-      this->intFormat  = GL_RGBA8;
-      this->vboFormat  = GL_BGRA;
-      this->dataFormat = GL_UNSIGNED_BYTE;
-      break;
-
-    default:
-      DEBUG_ERROR("Format not supported");
-      LG_UNLOCK(this->formatLock);
-      return false;
-  }
-
   // calculate the texture size in bytes
-  this->texSize =
-    this->format.height *
-    this->decoder->get_frame_pitch(this->decoderData);
+  this->texSize = this->format.height * this->format.pitch;
+  this->texPos  = 0;
 
-  // generate the pixel unpack buffers if the decoder isn't going to do it for us
-  if (!this->decoder->has_gl)
+  glGenBuffers(BUFFER_COUNT, this->vboID);
+  if (check_gl_error("glGenBuffers"))
   {
-    glGenBuffers(BUFFER_COUNT, this->vboID);
-    if (check_gl_error("glGenBuffers"))
-    {
-      LG_UNLOCK(this->formatLock);
-      return false;
-    }
-    this->hasBuffers = true;
+    LG_UNLOCK(this->formatLock);
+    return false;
+  }
+  this->hasBuffers = true;
 
-    if (this->amdPinnedMemSupport)
-    {
-      const int pagesize = getpagesize();
-      this->texPixels[0] = memalign(pagesize, this->texSize * BUFFER_COUNT);
-      memset(this->texPixels[0], 0, this->texSize * BUFFER_COUNT);
-      for(int i = 1; i < BUFFER_COUNT; ++i)
-        this->texPixels[i] = this->texPixels[0] + this->texSize;
+  if (this->amdPinnedMemSupport)
+  {
+    const int pagesize = getpagesize();
+    this->texPixels[0] = memalign(pagesize, this->texSize * BUFFER_COUNT);
+    memset(this->texPixels[0], 0, this->texSize * BUFFER_COUNT);
+    for(int i = 1; i < BUFFER_COUNT; ++i)
+      this->texPixels[i] = this->texPixels[0] + this->texSize;
 
-      for(int i = 0; i < BUFFER_COUNT; ++i)
+    for(int i = 0; i < BUFFER_COUNT; ++i)
+    {
+      glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, this->vboID[i]);
+
+      if (check_gl_error("glBindBuffer"))
       {
-        glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, this->vboID[i]);
-
-        if (check_gl_error("glBindBuffer"))
-        {
-          LG_UNLOCK(this->formatLock);
-          return false;
-        }
-        glBufferData(
-          GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD,
-          this->texSize,
-          this->texPixels[i],
-          GL_STREAM_DRAW);
-
-        if (check_gl_error("glBufferData"))
-        {
-          LG_UNLOCK(this->formatLock);
-          return false;
-        }
+        LG_UNLOCK(this->formatLock);
+        return false;
       }
-      glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, 0);
-    }
-    else
-    {
-      for(int i = 0; i < BUFFER_COUNT; ++i)
+      glBufferData(
+        GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD,
+        this->texSize,
+        this->texPixels[i],
+        GL_STREAM_DRAW);
+
+      if (check_gl_error("glBufferData"))
       {
-        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->vboID[i]);
-        if (check_gl_error("glBindBuffer"))
-        {
-          LG_UNLOCK(this->formatLock);
-          return false;
-        }
-
-        glBufferData(
-          GL_PIXEL_UNPACK_BUFFER,
-          this->texSize,
-          NULL,
-          GL_STREAM_DRAW
-        );
-        if (check_gl_error("glBufferData"))
-        {
-          LG_UNLOCK(this->formatLock);
-          return false;
-        }
+        LG_UNLOCK(this->formatLock);
+        return false;
       }
-      glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
     }
+    glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, 0);
+  }
+  else
+  {
+    for(int i = 0; i < BUFFER_COUNT; ++i)
+    {
+      glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->vboID[i]);
+      if (check_gl_error("glBindBuffer"))
+      {
+        LG_UNLOCK(this->formatLock);
+        return false;
+      }
+
+      glBufferData(
+        GL_PIXEL_UNPACK_BUFFER,
+        this->texSize,
+        NULL,
+        GL_STREAM_DRAW
+      );
+      if (check_gl_error("glBufferData"))
+      {
+        LG_UNLOCK(this->formatLock);
+        return false;
+      }
+    }
+    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
   }
 
   // create the frame textures
@@ -1023,26 +970,11 @@ static bool configure(struct Inst * this, SDL_Window *window)
       return false;
     }
 
-    if (this->decoder->has_gl)
-    {
-      if (!this->decoder->init_gl_texture(
-        this->decoderData,
-        GL_TEXTURE_2D,
-        this->frames[i],
-        &this->decoderFrames[i]))
-      {
-        LG_UNLOCK(this->formatLock);
-        return false;
-      }
-    }
-    else
-    {
-      // configure the texture
-      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S    , GL_CLAMP_TO_EDGE);
-      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T    , GL_CLAMP_TO_EDGE);
-      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-    }
+    // configure the texture
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S    , GL_CLAMP_TO_EDGE);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T    , GL_CLAMP_TO_EDGE);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
 
     // create the display lists
     glNewList(this->texList + i, GL_COMPILE);
@@ -1082,19 +1014,6 @@ static void deconfigure(struct Inst * this)
 
   if (this->hasFrames)
   {
-    if (this->decoder->has_gl)
-    {
-      for(int i = 0; i < BUFFER_COUNT; ++i)
-      {
-        if (this->decoderFrames[i])
-          this->decoder->free_gl_texture(
-            this->decoderData,
-            this->decoderFrames[i]
-          );
-        this->decoderFrames[i] = NULL;
-      }
-    }
-
     glDeleteTextures(BUFFER_COUNT, this->frames);
     this->hasFrames = false;
   }
@@ -1121,12 +1040,6 @@ static void deconfigure(struct Inst * this)
     }
   }
 
-  if (this->decoderData)
-  {
-    this->decoder->destroy(this->decoderData);
-    this->decoderData = NULL;
-  }
-
   this->configured = false;
 }
 
@@ -1277,6 +1190,23 @@ static void update_mouse_shape(struct Inst * this, bool * newShape)
   LG_UNLOCK(this->mouseLock);
 }
 
+static bool opengl_buffer_fn(void * opaque, const void * data, size_t size)
+{
+  struct Inst * this = (struct Inst *)opaque;
+
+  // update the buffer, this performs a DMA transfer if possible
+  glBufferSubData(
+    GL_PIXEL_UNPACK_BUFFER,
+    this->texPos,
+    size,
+    data
+  );
+  check_gl_error("glBufferSubData");
+
+  this->texPos += size;
+  return true;
+}
+
 static bool draw_frame(struct Inst * this)
 {
   LG_LOCK(this->syncLock);
@@ -1293,96 +1223,70 @@ static bool draw_frame(struct Inst * this)
   LG_UNLOCK(this->syncLock);
 
   LG_LOCK(this->formatLock);
-  if (this->decoder->has_gl)
+  if (glIsSync(this->fences[this->texIndex]))
   {
-    if (!this->decoder->update_gl_texture(
-      this->decoderData,
-      this->decoderFrames[this->texIndex]
-    ))
+    switch(glClientWaitSync(this->fences[this->texIndex], 0, GL_TIMEOUT_IGNORED))
     {
-      LG_UNLOCK(this->formatLock);
-      DEBUG_ERROR("Failed to update the texture from the decoder");
-      return false;
+      case GL_ALREADY_SIGNALED:
+        break;
+
+      case GL_CONDITION_SATISFIED:
+        DEBUG_WARN("Had to wait for the sync");
+        break;
+
+      case GL_TIMEOUT_EXPIRED:
+        DEBUG_WARN("Timeout expired, DMA transfers are too slow!");
+        break;
+
+      case GL_WAIT_FAILED:
+        DEBUG_ERROR("Wait failed %s", gluErrorString(glGetError()));
+        break;
     }
+
+    glDeleteSync(this->fences[this->texIndex]);
+    this->fences[this->texIndex] = NULL;
   }
-  else
+
+  glBindTexture(GL_TEXTURE_2D, this->frames[this->texIndex]);
+  glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->vboID[this->texIndex]);
+
+  glPixelStorei(GL_UNPACK_ALIGNMENT  , 4);
+  glPixelStorei(GL_UNPACK_ROW_LENGTH , this->format.stride);
+
+  this->texPos = 0;
+  framebuffer_read_fn(
+    this->frame,
+    opengl_buffer_fn,
+    this->format.height * this->format.stride,
+    this
+  );
+
+  // update the texture
+  glTexSubImage2D(
+    GL_TEXTURE_2D,
+    0,
+    0,
+    0,
+    this->format.width ,
+    this->format.height,
+    this->vboFormat,
+    this->dataFormat,
+    (void*)0
+  );
+  if (check_gl_error("glTexSubImage2D"))
   {
-    if (glIsSync(this->fences[this->texIndex]))
-    {
-      switch(glClientWaitSync(this->fences[this->texIndex], 0, GL_TIMEOUT_IGNORED))
-      {
-        case GL_ALREADY_SIGNALED:
-          break;
-
-        case GL_CONDITION_SATISFIED:
-          DEBUG_WARN("Had to wait for the sync");
-          break;
-
-        case GL_TIMEOUT_EXPIRED:
-          DEBUG_WARN("Timeout expired, DMA transfers are too slow!");
-          break;
-
-        case GL_WAIT_FAILED:
-          DEBUG_ERROR("Wait failed %s", gluErrorString(glGetError()));
-          break;
-      }
-
-      glDeleteSync(this->fences[this->texIndex]);
-      this->fences[this->texIndex] = NULL;
-    }
-
-    const uint8_t * data = this->decoder->get_buffer(this->decoderData);
-    if (!data)
-    {
-      LG_UNLOCK(this->formatLock);
-      DEBUG_ERROR("Failed to get the buffer from the decoder");
-      return false;
-    }
-
-    glBindTexture(GL_TEXTURE_2D, this->frames[this->texIndex]);
-    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->vboID[this->texIndex]);
-
-    glPixelStorei(GL_UNPACK_ALIGNMENT  , 4);
-    glPixelStorei(GL_UNPACK_ROW_LENGTH ,
-      this->decoder->get_frame_stride(this->decoderData)
+    DEBUG_ERROR("texIndex: %u, width: %u, height: %u, vboFormat: %x, texSize: %lu",
+      this->texIndex, this->format.width, this->format.height, this->vboFormat, this->texSize
     );
-
-    // update the buffer, this performs a DMA transfer if possible
-    glBufferSubData(
-      GL_PIXEL_UNPACK_BUFFER,
-      0,
-      this->texSize,
-      data
-    );
-    check_gl_error("glBufferSubData");
-
-    // update the texture
-    glTexSubImage2D(
-      GL_TEXTURE_2D,
-      0,
-      0,
-      0,
-      this->format.width ,
-      this->format.height,
-      this->vboFormat,
-      this->dataFormat,
-      (void*)0
-    );
-    if (check_gl_error("glTexSubImage2D"))
-    {
-      DEBUG_ERROR("texIndex: %u, width: %u, height: %u, vboFormat: %x, texSize: %lu",
-        this->texIndex, this->format.width, this->format.height, this->vboFormat, this->texSize
-      );
-    }
-
-    // set a fence so we don't overwrite a buffer in use
-    this->fences[this->texIndex] =
-      glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
-
-    // unbind the buffer
-    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
   }
 
+  // set a fence so we don't overwrite a buffer in use
+  this->fences[this->texIndex] =
+    glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
+
+  // unbind the buffer
+  glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+
   const bool mipmap = this->opt.mipmap && (
     (this->format.width  > this->destRect.w) ||
     (this->format.height > this->destRect.h));
diff --git a/client/src/main.c b/client/src/main.c
index eee3ac04..7ca51d25 100644
--- a/client/src/main.c
+++ b/client/src/main.c
@@ -377,8 +377,8 @@ static int frameThread(void * unused)
       updatePositionInfo();
     }
 
-    const uint8_t * data = (const uint8_t *)state.shm + header.dataPos;
-    if (!state.lgr->on_frame_event(state.lgrData, lgrFormat, data))
+    FrameBuffer frame = (FrameBuffer)((uint8_t *)state.shm + header.dataPos);
+    if (!state.lgr->on_frame_event(state.lgrData, lgrFormat, frame))
     {
       DEBUG_ERROR("renderer on frame event returned failure");
       break;
diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
index 86af720c..7b22d8ef 100644
--- a/common/CMakeLists.txt
+++ b/common/CMakeLists.txt
@@ -13,6 +13,7 @@ set(COMMON_SOURCES
   src/stringutils.c
   src/stringlist.c
   src/option.c
+  src/framebuffer.c
 )
 
 set(LINUX_SOURCES
diff --git a/common/include/common/KVMFR.h b/common/include/common/KVMFR.h
index c4c3d194..286b567c 100644
--- a/common/include/common/KVMFR.h
+++ b/common/include/common/KVMFR.h
@@ -49,7 +49,7 @@ CursorType;
 
 typedef struct KVMFRCursor
 {
-  uint8_t    flags;       // KVMFR_CURSOR_FLAGS
+  volatile uint8_t flags; // KVMFR_CURSOR_FLAGS
   int16_t    x, y;        // cursor x & y position
 
   uint32_t   version;     // shape version
@@ -65,7 +65,7 @@ KVMFRCursor;
 
 typedef struct KVMFRFrame
 {
-  uint8_t     flags;       // KVMFR_FRAME_FLAGS
+  volatile uint8_t flags;  // KVMFR_FRAME_FLAGS
   FrameType   type;        // the frame data type
   uint32_t    width;       // the width
   uint32_t    height;      // the height
@@ -83,7 +83,7 @@ typedef struct KVMFRHeader
 {
   char        magic[sizeof(KVMFR_HEADER_MAGIC)];
   uint32_t    version;     // version of this structure
-  uint8_t     flags;       // KVMFR_HEADER_FLAGS
+  volatile uint8_t flags;  // KVMFR_HEADER_FLAGS
   KVMFRFrame  frame;       // the frame information
   KVMFRCursor cursor;      // the cursor information
 }
diff --git a/common/include/common/framebuffer.h b/common/include/common/framebuffer.h
new file mode 100644
index 00000000..9dcf15f6
--- /dev/null
+++ b/common/include/common/framebuffer.h
@@ -0,0 +1,48 @@
+/*
+KVMGFX Client - A KVM Client for VGA Passthrough
+Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
+https://looking-glass.hostfission.com
+
+This program is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free Software
+Foundation; either version 2 of the License, or (at your option) any later
+version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#pragma once
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+typedef struct stFrameBuffer * FrameBuffer;
+
+typedef bool (*FrameBufferReadFn)(void * opaque, const void * src, size_t size);
+
+/**
+ * Read data from the KVMFRFrame into the dst buffer
+ */
+bool framebuffer_read(const FrameBuffer frame, void * dst, size_t size);
+
+/**
+ * Read data from the KVMFRFrame using a callback
+ */
+bool framebuffer_read_fn(const FrameBuffer frame, FrameBufferReadFn fn, size_t size, void * opaque);
+
+/**
+ * Prepare the framebuffer for writing
+ */
+void framebuffer_prepare(const FrameBuffer frame);
+
+/**
+ * Write data from the src buffer into the KVMFRFrame
+ */
+bool framebuffer_write(const FrameBuffer frame, const void * src, size_t size);
\ No newline at end of file
diff --git a/common/src/framebuffer.c b/common/src/framebuffer.c
new file mode 100644
index 00000000..7d9a7da4
--- /dev/null
+++ b/common/src/framebuffer.c
@@ -0,0 +1,85 @@
+/*
+KVMGFX Client - A KVM Client for VGA Passthrough
+Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
+https://looking-glass.hostfission.com
+
+This program is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free Software
+Foundation; either version 2 of the License, or (at your option) any later
+version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "common/framebuffer.h"
+#include "common/debug.h"
+
+#include <string.h>
+#define FB_CHUNK_SIZE 1024
+
+struct stFrameBuffer
+{
+  uint64_t  wp;
+  uint8_t   data[0];
+};
+
+bool framebuffer_read(const FrameBuffer frame, void * dst, size_t size)
+{
+  uint64_t rp = 0;
+  while(rp < size)
+  {
+    /* spinlock */
+    while(rp == frame->wp) { }
+
+    /* copy what we can */
+    uint64_t avail = frame->wp - rp;
+    memcpy(dst, frame->data + rp, avail);
+    rp += avail;
+  }
+  return true;
+}
+
+bool framebuffer_read_fn(const FrameBuffer frame, FrameBufferReadFn fn, size_t size, void * opaque)
+{
+  uint64_t rp = 0;
+  while(rp < size)
+  {
+    /* spinlock */
+    while(rp == frame->wp) { }
+
+    /* copy what we can */
+    uint64_t avail = frame->wp - rp;
+    if (!fn(opaque, frame->data + rp, avail))
+      return false;
+    rp += avail;
+  }
+
+  return true;
+}
+
+/**
+ * Prepare the framebuffer for writing
+ */
+void framebuffer_prepare(const FrameBuffer frame)
+{
+  frame->wp = 0;
+}
+
+bool framebuffer_write(FrameBuffer frame, const void * src, size_t size)
+{
+  /* copy in chunks */
+  while(size)
+  {
+    size_t copy = size < FB_CHUNK_SIZE ? FB_CHUNK_SIZE : size;
+    memcpy(frame->data + frame->wp, src, copy);
+    __sync_fetch_and_add(&frame->wp, copy);
+    size -= copy;
+  }
+  return true;
+}
\ No newline at end of file