[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.
This commit is contained in:
Geoffrey McRae 2019-10-01 23:17:20 +10:00
parent 6d2c464436
commit bca54ab1f6
17 changed files with 358 additions and 277 deletions

View File

@ -1 +1 @@
fetch-4-ge93bd7a3bf+1 fetch-6-g59013b2e3f+1

View File

@ -21,6 +21,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include "common/framebuffer.h"
typedef enum CaptureResult typedef enum CaptureResult
{ {
@ -55,7 +56,6 @@ typedef struct CaptureFrame
unsigned int pitch; unsigned int pitch;
unsigned int stride; unsigned int stride;
CaptureFormat format; CaptureFormat format;
void * data;
} }
CaptureFrame; CaptureFrame;
@ -84,7 +84,8 @@ typedef struct CaptureInterface
unsigned int (*getMaxFrameSize)(); unsigned int (*getMaxFrameSize)();
CaptureResult (*capture )(); CaptureResult (*capture )();
CaptureResult (*getFrame )(CaptureFrame * frame ); CaptureResult (*waitFrame )(CaptureFrame * frame );
CaptureResult (*getFrame )(FrameBuffer frame );
CaptureResult (*getPointer)(CapturePointer * pointer); CaptureResult (*getPointer)(CapturePointer * pointer);
} }
CaptureInterface; CaptureInterface;

View File

@ -766,7 +766,7 @@ static CaptureResult dxgi_capture()
return CAPTURE_RESULT_OK; return CAPTURE_RESULT_OK;
} }
static CaptureResult dxgi_getFrame(CaptureFrame * frame) static CaptureResult dxgi_waitFrame(CaptureFrame * frame)
{ {
assert(this); assert(this);
assert(this->initialized); assert(this->initialized);
@ -778,7 +778,6 @@ static CaptureResult dxgi_getFrame(CaptureFrame * frame)
if (this->stop) if (this->stop)
return CAPTURE_RESULT_REINIT; return CAPTURE_RESULT_REINIT;
// only reset the event if we used the texture
os_resetEvent(tex->mapped); os_resetEvent(tex->mapped);
frame->width = this->width; frame->width = this->width;
@ -787,7 +786,16 @@ static CaptureResult dxgi_getFrame(CaptureFrame * frame)
frame->stride = this->stride; frame->stride = this->stride;
frame->format = this->format; 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); os_signalEvent(tex->free);
if (++this->texRIndex == this->maxTextures) if (++this->texRIndex == this->maxTextures)
@ -867,6 +875,7 @@ struct CaptureInterface Capture_DXGI =
.free = dxgi_free, .free = dxgi_free,
.getMaxFrameSize = dxgi_getMaxFrameSize, .getMaxFrameSize = dxgi_getMaxFrameSize,
.capture = dxgi_capture, .capture = dxgi_capture,
.waitFrame = dxgi_waitFrame,
.getFrame = dxgi_getFrame, .getFrame = dxgi_getFrame,
.getPointer = dxgi_getPointer .getPointer = dxgi_getPointer
}; };

View File

@ -23,6 +23,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "windows/debug.h" #include "windows/debug.h"
#include "windows/mousehook.h" #include "windows/mousehook.h"
#include "common/option.h" #include "common/option.h"
#include "common/framebuffer.h"
#include <assert.h> #include <assert.h>
#include <stdlib.h> #include <stdlib.h>
#include <windows.h> #include <windows.h>
@ -236,7 +237,7 @@ static CaptureResult nvfbc_capture()
return CAPTURE_RESULT_OK; return CAPTURE_RESULT_OK;
} }
static CaptureResult nvfbc_getFrame(CaptureFrame * frame) static CaptureResult nvfbc_waitFrame(CaptureFrame * frame)
{ {
if (!os_waitEvent(this->frameEvent, 1000)) if (!os_waitEvent(this->frameEvent, 1000))
return CAPTURE_RESULT_TIMEOUT; return CAPTURE_RESULT_TIMEOUT;
@ -266,7 +267,16 @@ static CaptureResult nvfbc_getFrame(CaptureFrame * frame)
#endif #endif
frame->format = this->grabInfo.bIsHDR ? CAPTURE_FMT_RGBA10 : CAPTURE_FMT_BGRA; 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; return CAPTURE_RESULT_OK;
} }
@ -310,6 +320,7 @@ struct CaptureInterface Capture_NVFBC =
.free = nvfbc_free, .free = nvfbc_free,
.getMaxFrameSize = nvfbc_getMaxFrameSize, .getMaxFrameSize = nvfbc_getMaxFrameSize,
.capture = nvfbc_capture, .capture = nvfbc_capture,
.waitFrame = nvfbc_waitFrame,
.getFrame = nvfbc_getFrame, .getFrame = nvfbc_getFrame,
.getPointer = nvfbc_getPointer .getPointer = nvfbc_getPointer
}; };

View File

@ -49,7 +49,7 @@ struct app
uint8_t * frames; uint8_t * frames;
unsigned int frameSize; unsigned int frameSize;
uint8_t * frame[MAX_FRAMES]; FrameBuffer frame[MAX_FRAMES];
unsigned int frameOffset[MAX_FRAMES]; unsigned int frameOffset[MAX_FRAMES];
bool running; bool running;
@ -168,9 +168,7 @@ static int frameThread(void * opaque)
while(app.running) while(app.running)
{ {
frame.data = app.frame[frameIndex]; switch(app.iface->waitFrame(&frame))
switch(app.iface->getFrame(&frame))
{ {
case CAPTURE_RESULT_OK: case CAPTURE_RESULT_OK:
break; break;
@ -226,7 +224,9 @@ static int frameThread(void * opaque)
fi->dataPos = app.frameOffset[frameIndex]; fi->dataPos = app.frameOffset[frameIndex];
frameValid = true; frameValid = true;
framebuffer_prepare(app.frame[frameIndex]);
INTERLOCKED_OR8(&fi->flags, KVMFR_FRAME_FLAG_UPDATE); INTERLOCKED_OR8(&fi->flags, KVMFR_FRAME_FLAG_UPDATE);
app.iface->getFrame(app.frame[frameIndex]);
if (++frameIndex == MAX_FRAMES) if (++frameIndex == MAX_FRAMES)
frameIndex = 0; frameIndex = 0;
@ -369,8 +369,8 @@ int app_main(int argc, char * argv[])
for (int i = 0; i < MAX_FRAMES; ++i) for (int i = 0; i < MAX_FRAMES; ++i)
{ {
app.frame [i] = app.frames + i * app.frameSize; app.frame [i] = (FrameBuffer)(app.frames + i * app.frameSize);
app.frameOffset[i] = app.frame[i] - shmemMap; 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]); DEBUG_INFO("Frame %d : 0x%" PRIXPTR " (0x%08x)", i, (uintptr_t)app.frame[i], app.frameOffset[i]);
} }

View File

@ -26,6 +26,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "app.h" #include "app.h"
#include "common/KVMFR.h" #include "common/KVMFR.h"
#include "common/framebuffer.h"
#define IS_LG_RENDERER_VALID(x) \ #define IS_LG_RENDERER_VALID(x) \
((x)->get_name && \ ((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 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_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_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 void (* LG_RendererOnAlert )(void * opaque, const LG_MsgAlert alert, const char * message, bool ** closeFlag);
typedef bool (* LG_RendererRender )(void * opaque, SDL_Window *window); typedef bool (* LG_RendererRender )(void * opaque, SDL_Window *window);
typedef void (* LG_RendererUpdateFPS )(void * opaque, const float avgUPS, const float avgFPS); typedef void (* LG_RendererUpdateFPS )(void * opaque, const float avgUPS, const float avgFPS);

View File

@ -60,7 +60,7 @@ struct EGL_Desktop
enum EGL_PixelFormat pixFmt; enum EGL_PixelFormat pixFmt;
unsigned int width, height; unsigned int width, height;
unsigned int pitch; unsigned int pitch;
const uint8_t * data; FrameBuffer frame;
bool update; bool update;
// night vision // night vision
@ -181,7 +181,7 @@ void egl_desktop_free(EGL_Desktop ** desktop)
*desktop = NULL; *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) if (sourceChanged)
{ {
@ -217,7 +217,7 @@ bool egl_desktop_prepare_update(EGL_Desktop * desktop, const bool sourceChanged,
desktop->width = format.width; desktop->width = format.width;
desktop->height = format.height; desktop->height = format.height;
desktop->pitch = format.pitch; desktop->pitch = format.pitch;
desktop->data = data; desktop->frame = frame;
desktop->update = true; desktop->update = true;
/* defer the actual update as the format has changed and we need to issue GL commands first */ /* 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 */ /* 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) 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) if (desktop->update)
{ {
desktop->update = false; desktop->update = false;
egl_texture_update(desktop->texture, desktop->data); egl_texture_update_from_frame(desktop->texture, desktop->frame);
} }
} }

View File

@ -28,6 +28,6 @@ typedef struct EGL_Desktop EGL_Desktop;
bool egl_desktop_init(EGL_Desktop ** desktop); bool egl_desktop_init(EGL_Desktop ** desktop);
void egl_desktop_free(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); 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); bool egl_desktop_render(EGL_Desktop * desktop, const float x, const float y, const float scaleX, const float scaleY, const bool nearest);

View File

@ -296,7 +296,7 @@ bool egl_on_mouse_event(void * opaque, const bool visible, const int x, const in
return true; 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; struct Inst * this = (struct Inst *)opaque;
this->sourceChanged = ( 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; 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"); DEBUG_INFO("Failed to prepare to update the desktop");
return false; return false;

View File

@ -20,6 +20,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "texture.h" #include "texture.h"
#include "common/debug.h" #include "common/debug.h"
#include "common/locking.h" #include "common/locking.h"
#include "common/framebuffer.h"
#include "debug.h" #include "debug.h"
#include "utils.h" #include "utils.h"
@ -278,6 +279,24 @@ bool egl_texture_update(EGL_Texture * texture, const uint8_t * buffer)
return true; 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) enum EGL_TexStatus egl_texture_process(EGL_Texture * texture)
{ {
if (!texture->streaming) if (!texture->streaming)

View File

@ -21,6 +21,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include <stdbool.h> #include <stdbool.h>
#include "shader.h" #include "shader.h"
#include "common/framebuffer.h"
#include <GL/gl.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_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 (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_process(EGL_Texture * texture);
enum EGL_TexStatus egl_texture_bind (EGL_Texture * texture); enum EGL_TexStatus egl_texture_bind (EGL_Texture * texture);
int egl_texture_count (EGL_Texture * texture); int egl_texture_count (EGL_Texture * texture);

View File

@ -32,8 +32,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "common/debug.h" #include "common/debug.h"
#include "common/option.h" #include "common/option.h"
#include "common/framebuffer.h"
#include "utils.h" #include "utils.h"
#include "lg-decoders.h"
#include "dynamic/fonts.h" #include "dynamic/fonts.h"
#include "ll.h" #include "ll.h"
@ -122,8 +122,8 @@ struct Inst
GLuint vboFormat; GLuint vboFormat;
GLuint dataFormat; GLuint dataFormat;
size_t texSize; size_t texSize;
const LG_Decoder* decoder; size_t texPos;
void * decoderData; FrameBuffer frame;
uint64_t drawStart; uint64_t drawStart;
bool hasBuffers; bool hasBuffers;
@ -140,7 +140,6 @@ struct Inst
bool hasTextures, hasFrames; bool hasTextures, hasFrames;
GLuint frames[BUFFER_COUNT]; GLuint frames[BUFFER_COUNT];
GLsync fences[BUFFER_COUNT]; GLsync fences[BUFFER_COUNT];
void * decoderFrames[BUFFER_COUNT];
GLuint textures[TEXTURE_COUNT]; GLuint textures[TEXTURE_COUNT];
struct ll * alerts; struct ll * alerts;
int alertList; int alertList;
@ -362,7 +361,7 @@ bool opengl_on_mouse_event(void * opaque, const bool visible, const int x, const
return false; 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; struct Inst * this = (struct Inst *)opaque;
if (!this) if (!this)
@ -394,12 +393,7 @@ bool opengl_on_frame_event(void * opaque, const LG_RendererFormat format, const
LG_UNLOCK(this->formatLock); LG_UNLOCK(this->formatLock);
LG_LOCK(this->syncLock); LG_LOCK(this->syncLock);
if (!this->decoder->decode(this->decoderData, data, format.pitch)) this->frame = frame;
{
DEBUG_ERROR("decode returned failure");
LG_UNLOCK(this->syncLock);
return false;
}
this->frameUpdate = true; this->frameUpdate = true;
LG_UNLOCK(this->syncLock); LG_UNLOCK(this->syncLock);
@ -849,13 +843,21 @@ static bool configure(struct Inst * this, SDL_Window *window)
switch(this->format.type) switch(this->format.type)
{ {
case FRAME_TYPE_BGRA: case FRAME_TYPE_BGRA:
case FRAME_TYPE_RGBA: this->intFormat = GL_RGBA8;
case FRAME_TYPE_RGBA10: this->vboFormat = GL_BGRA;
this->decoder = &LGD_NULL; this->dataFormat = GL_UNSIGNED_BYTE;
break; break;
case FRAME_TYPE_YUV420: case FRAME_TYPE_RGBA:
this->decoder = &LGD_YUV420; 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; break;
default: default:
@ -863,128 +865,73 @@ static bool configure(struct Inst * this, SDL_Window *window)
return false; 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 // calculate the texture size in bytes
this->texSize = this->texSize = this->format.height * this->format.pitch;
this->format.height * this->texPos = 0;
this->decoder->get_frame_pitch(this->decoderData);
// generate the pixel unpack buffers if the decoder isn't going to do it for us glGenBuffers(BUFFER_COUNT, this->vboID);
if (!this->decoder->has_gl) if (check_gl_error("glGenBuffers"))
{ {
glGenBuffers(BUFFER_COUNT, this->vboID); LG_UNLOCK(this->formatLock);
if (check_gl_error("glGenBuffers")) return false;
{ }
LG_UNLOCK(this->formatLock); this->hasBuffers = true;
return false;
}
this->hasBuffers = true;
if (this->amdPinnedMemSupport) if (this->amdPinnedMemSupport)
{ {
const int pagesize = getpagesize(); const int pagesize = getpagesize();
this->texPixels[0] = memalign(pagesize, this->texSize * BUFFER_COUNT); this->texPixels[0] = memalign(pagesize, this->texSize * BUFFER_COUNT);
memset(this->texPixels[0], 0, this->texSize * BUFFER_COUNT); memset(this->texPixels[0], 0, this->texSize * BUFFER_COUNT);
for(int i = 1; i < BUFFER_COUNT; ++i) for(int i = 1; i < BUFFER_COUNT; ++i)
this->texPixels[i] = this->texPixels[0] + this->texSize; 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]); LG_UNLOCK(this->formatLock);
return false;
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;
}
} }
glBindBuffer(GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD, 0); glBufferData(
} GL_EXTERNAL_VIRTUAL_MEMORY_BUFFER_AMD,
else this->texSize,
{ this->texPixels[i],
for(int i = 0; i < BUFFER_COUNT; ++i) GL_STREAM_DRAW);
if (check_gl_error("glBufferData"))
{ {
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, this->vboID[i]); LG_UNLOCK(this->formatLock);
if (check_gl_error("glBindBuffer")) return false;
{
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);
} }
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 // create the frame textures
@ -1023,26 +970,11 @@ static bool configure(struct Inst * this, SDL_Window *window)
return false; return false;
} }
if (this->decoder->has_gl) // configure the texture
{ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE);
if (!this->decoder->init_gl_texture( glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE);
this->decoderData, glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
GL_TEXTURE_2D, glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
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);
}
// create the display lists // create the display lists
glNewList(this->texList + i, GL_COMPILE); glNewList(this->texList + i, GL_COMPILE);
@ -1082,19 +1014,6 @@ static void deconfigure(struct Inst * this)
if (this->hasFrames) 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); glDeleteTextures(BUFFER_COUNT, this->frames);
this->hasFrames = false; 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; this->configured = false;
} }
@ -1277,6 +1190,23 @@ static void update_mouse_shape(struct Inst * this, bool * newShape)
LG_UNLOCK(this->mouseLock); 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) static bool draw_frame(struct Inst * this)
{ {
LG_LOCK(this->syncLock); LG_LOCK(this->syncLock);
@ -1293,96 +1223,70 @@ static bool draw_frame(struct Inst * this)
LG_UNLOCK(this->syncLock); LG_UNLOCK(this->syncLock);
LG_LOCK(this->formatLock); LG_LOCK(this->formatLock);
if (this->decoder->has_gl) if (glIsSync(this->fences[this->texIndex]))
{ {
if (!this->decoder->update_gl_texture( switch(glClientWaitSync(this->fences[this->texIndex], 0, GL_TIMEOUT_IGNORED))
this->decoderData,
this->decoderFrames[this->texIndex]
))
{ {
LG_UNLOCK(this->formatLock); case GL_ALREADY_SIGNALED:
DEBUG_ERROR("Failed to update the texture from the decoder"); break;
return false;
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])) DEBUG_ERROR("texIndex: %u, width: %u, height: %u, vboFormat: %x, texSize: %lu",
{ this->texIndex, this->format.width, this->format.height, this->vboFormat, this->texSize
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)
); );
// 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 && ( const bool mipmap = this->opt.mipmap && (
(this->format.width > this->destRect.w) || (this->format.width > this->destRect.w) ||
(this->format.height > this->destRect.h)); (this->format.height > this->destRect.h));

View File

@ -377,8 +377,8 @@ static int frameThread(void * unused)
updatePositionInfo(); updatePositionInfo();
} }
const uint8_t * data = (const uint8_t *)state.shm + header.dataPos; FrameBuffer frame = (FrameBuffer)((uint8_t *)state.shm + header.dataPos);
if (!state.lgr->on_frame_event(state.lgrData, lgrFormat, data)) if (!state.lgr->on_frame_event(state.lgrData, lgrFormat, frame))
{ {
DEBUG_ERROR("renderer on frame event returned failure"); DEBUG_ERROR("renderer on frame event returned failure");
break; break;

View File

@ -13,6 +13,7 @@ set(COMMON_SOURCES
src/stringutils.c src/stringutils.c
src/stringlist.c src/stringlist.c
src/option.c src/option.c
src/framebuffer.c
) )
set(LINUX_SOURCES set(LINUX_SOURCES

View File

@ -49,7 +49,7 @@ CursorType;
typedef struct KVMFRCursor typedef struct KVMFRCursor
{ {
uint8_t flags; // KVMFR_CURSOR_FLAGS volatile uint8_t flags; // KVMFR_CURSOR_FLAGS
int16_t x, y; // cursor x & y position int16_t x, y; // cursor x & y position
uint32_t version; // shape version uint32_t version; // shape version
@ -65,7 +65,7 @@ KVMFRCursor;
typedef struct KVMFRFrame typedef struct KVMFRFrame
{ {
uint8_t flags; // KVMFR_FRAME_FLAGS volatile uint8_t flags; // KVMFR_FRAME_FLAGS
FrameType type; // the frame data type FrameType type; // the frame data type
uint32_t width; // the width uint32_t width; // the width
uint32_t height; // the height uint32_t height; // the height
@ -83,7 +83,7 @@ typedef struct KVMFRHeader
{ {
char magic[sizeof(KVMFR_HEADER_MAGIC)]; char magic[sizeof(KVMFR_HEADER_MAGIC)];
uint32_t version; // version of this structure 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 KVMFRFrame frame; // the frame information
KVMFRCursor cursor; // the cursor information KVMFRCursor cursor; // the cursor information
} }

View File

@ -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);

85
common/src/framebuffer.c Normal file
View File

@ -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;
}