LookingGlass/client/renderers/EGL/desktop.c
Geoffrey McRae bca54ab1f6 [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.
2019-10-09 13:53:02 +11:00

284 lines
7.5 KiB
C

/*
Looking Glass - KVM FrameRelay (KVMFR) Client
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
cahe 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 "desktop.h"
#include "common/debug.h"
#include "common/option.h"
#include "utils.h"
#include "texture.h"
#include "shader.h"
#include "model.h"
#include <stdlib.h>
#include <string.h>
#include "interface/app.h"
// these headers are auto generated by cmake
#include "desktop.vert.h"
#include "desktop_rgb.frag.h"
#include "desktop_yuv.frag.h"
struct DesktopShader
{
EGL_Shader * shader;
GLint uDesktopPos;
GLint uDesktopSize;
GLint uNearest;
GLint uNV, uNVGain;
};
struct EGL_Desktop
{
EGL_Texture * texture;
struct DesktopShader * shader; // the active shader
EGL_Model * model;
// shader instances
struct DesktopShader shader_generic;
struct DesktopShader shader_yuv;
// internals
LG_Lock updateLock;
enum EGL_PixelFormat pixFmt;
unsigned int width, height;
unsigned int pitch;
FrameBuffer frame;
bool update;
// night vision
KeybindHandle kbNV;
int nvMax;
int nvGain;
};
// forwards
void egl_desktop_toggle_nv(SDL_Scancode key, void * opaque);
static bool egl_init_desktop_shader(
struct DesktopShader * shader,
const char * vertex_code , size_t vertex_size,
const char * fragment_code, size_t fragment_size
)
{
if (!egl_shader_init(&shader->shader))
return false;
if (!egl_shader_compile(shader->shader,
vertex_code , vertex_size,
fragment_code, fragment_size))
{
return false;
}
shader->uDesktopPos = egl_shader_get_uniform_location(shader->shader, "position");
shader->uDesktopSize = egl_shader_get_uniform_location(shader->shader, "size" );
shader->uNearest = egl_shader_get_uniform_location(shader->shader, "nearest" );
shader->uNV = egl_shader_get_uniform_location(shader->shader, "nv" );
shader->uNVGain = egl_shader_get_uniform_location(shader->shader, "nvGain" );
return true;
}
bool egl_desktop_init(EGL_Desktop ** desktop)
{
*desktop = (EGL_Desktop *)malloc(sizeof(EGL_Desktop));
if (!*desktop)
{
DEBUG_ERROR("Failed to malloc EGL_Desktop");
return false;
}
memset(*desktop, 0, sizeof(EGL_Desktop));
if (!egl_texture_init(&(*desktop)->texture))
{
DEBUG_ERROR("Failed to initialize the desktop texture");
return false;
}
if (!egl_init_desktop_shader(
&(*desktop)->shader_generic,
b_shader_desktop_vert , b_shader_desktop_vert_size,
b_shader_desktop_rgb_frag, b_shader_desktop_rgb_frag_size))
{
DEBUG_ERROR("Failed to initialize the generic desktop shader");
return false;
}
if (!egl_init_desktop_shader(
&(*desktop)->shader_yuv,
b_shader_desktop_vert , b_shader_desktop_vert_size,
b_shader_desktop_yuv_frag, b_shader_desktop_yuv_frag_size))
{
DEBUG_ERROR("Failed to initialize the yuv desktop shader");
return false;
}
if (!egl_model_init(&(*desktop)->model))
{
DEBUG_ERROR("Failed to initialize the desktop model");
return false;
}
egl_model_set_default((*desktop)->model);
egl_model_set_texture((*desktop)->model, (*desktop)->texture);
LG_LOCK_INIT((*desktop)->updateLock);
(*desktop)->kbNV = app_register_keybind(SDL_SCANCODE_N, egl_desktop_toggle_nv, *desktop);
(*desktop)->nvMax = option_get_int("egl", "nvGainMax");
(*desktop)->nvGain = option_get_int("egl", "nvGain" );
return true;
}
void egl_desktop_toggle_nv(SDL_Scancode key, void * opaque)
{
EGL_Desktop * desktop = (EGL_Desktop *)opaque;
if (desktop->nvGain++ == desktop->nvMax)
desktop->nvGain = 0;
if (desktop->nvGain == 0) app_alert(LG_ALERT_INFO, "NV Disabled");
else if (desktop->nvGain == 1) app_alert(LG_ALERT_INFO, "NV Enabled");
else app_alert(LG_ALERT_INFO, "NV Gain + %d", desktop->nvGain - 1);
}
void egl_desktop_free(EGL_Desktop ** desktop)
{
if (!*desktop)
return;
LG_LOCK_FREE((*desktop)->updateLock);
egl_texture_free(&(*desktop)->texture );
egl_shader_free (&(*desktop)->shader_generic.shader);
egl_shader_free (&(*desktop)->shader_yuv.shader );
egl_model_free (&(*desktop)->model );
app_release_keybind(&(*desktop)->kbNV);
free(*desktop);
*desktop = NULL;
}
bool egl_desktop_prepare_update(EGL_Desktop * desktop, const bool sourceChanged, const LG_RendererFormat format, const FrameBuffer frame)
{
if (sourceChanged)
{
LG_LOCK(desktop->updateLock);
switch(format.type)
{
case FRAME_TYPE_BGRA:
desktop->pixFmt = EGL_PF_BGRA;
desktop->shader = &desktop->shader_generic;
break;
case FRAME_TYPE_RGBA:
desktop->pixFmt = EGL_PF_RGBA;
desktop->shader = &desktop->shader_generic;
break;
case FRAME_TYPE_RGBA10:
desktop->pixFmt = EGL_PF_RGBA10;
desktop->shader = &desktop->shader_generic;
break;
case FRAME_TYPE_YUV420:
desktop->pixFmt = EGL_PF_YUV420;
desktop->shader = &desktop->shader_yuv;
break;
default:
DEBUG_ERROR("Unsupported frame format");
LG_UNLOCK(desktop->updateLock);
return false;
}
desktop->width = format.width;
desktop->height = format.height;
desktop->pitch = format.pitch;
desktop->frame = frame;
desktop->update = true;
/* defer the actual update as the format has changed and we need to issue GL commands first */
LG_UNLOCK(desktop->updateLock);
return true;
}
/* update the texture now */
return egl_texture_update_from_frame(desktop->texture, frame);
}
void egl_desktop_perform_update(EGL_Desktop * desktop, const bool sourceChanged)
{
if (sourceChanged)
{
LG_LOCK(desktop->updateLock);
if (!egl_texture_setup(
desktop->texture,
desktop->pixFmt,
desktop->width,
desktop->height,
desktop->pitch,
true // streaming texture
))
{
DEBUG_ERROR("Failed to setup the desktop texture");
LG_UNLOCK(desktop->updateLock);
return;
}
LG_UNLOCK(desktop->updateLock);
}
if (desktop->update)
{
desktop->update = false;
egl_texture_update_from_frame(desktop->texture, desktop->frame);
}
}
bool egl_desktop_render(EGL_Desktop * desktop, const float x, const float y, const float scaleX, const float scaleY, const bool nearest)
{
if (!desktop->shader)
return false;
if (egl_texture_process(desktop->texture) != EGL_TEX_STATUS_OK)
return false;
const struct DesktopShader * shader = desktop->shader;
egl_shader_use(shader->shader);
glUniform4f(shader->uDesktopPos , x, y, scaleX, scaleY);
glUniform1i(shader->uNearest , nearest ? 1 : 0);
glUniform2f(shader->uDesktopSize, desktop->width, desktop->height);
if (desktop->nvGain)
{
glUniform1i(shader->uNV, 1);
glUniform1f(shader->uNVGain, (float)desktop->nvGain);
}
else
glUniform1i(shader->uNV, 0);
egl_model_render(desktop->model);
return true;
}