Compare commits

...

45 Commits

Author SHA1 Message Date
Geoffrey McRae
82e0b7b6ab [doc] readme updated with PsExec information 2020-08-09 20:11:19 +10:00
Geoffrey McRae
2e1b0f2550 [all] update the LGMP submodule 2020-08-09 18:13:43 +10:00
Geoffrey McRae
3302d353cf [client] always use spice mouse host mode
Since we only ever use offset movements as SPICE doesn't properly
support absolute x/y positional information without a virtual tablet
device (which breaks relative mode needed for capture), just always run
in this mode. This fixes an issue when the spice guest tools are
installed and the mouse fails to work when not captured.
2020-08-09 16:17:08 +10:00
Geoffrey McRae
1899d9f1da [client] reset the frame time when we get a frame signal
This stops a duplicate frame rendering bug due to failure to discipline
based on the signal timing.
2020-08-09 15:55:12 +10:00
Geoffrey McRae
fb9b772db0 [client] we are getting the clock anyway, just reset the time 2020-08-09 15:54:45 +10:00
Geoffrey McRae
302b988524 [client] use atomics to track frame counts and avoid extra signals 2020-08-09 15:14:17 +10:00
Geoffrey McRae
19c2fe9b5e Revert "[common] linux: improve event mechanics"
The logic here is wrong, this should be done externally as multiple
waiters will cause issues
2020-08-09 14:44:00 +10:00
Geoffrey McRae
88d25ee98c [common] linux: improve event mechanics 2020-08-09 13:26:55 +10:00
Geoffrey McRae
0f2ecdf5f1 [obs] cosmetic 2020-08-09 12:31:56 +10:00
Geoffrey McRae
3511fb8d59 [obs] microsttuer fix, be sure to always grab the latest frame 2020-08-09 12:29:52 +10:00
Geoffrey McRae
1d6d640b6e [host] dxgi: default to using the acquire lock 2020-08-07 20:31:46 +10:00
Geoffrey McRae
977d7b277d [host] dxgi: boost GPU thread priority if possible 2020-08-07 19:44:00 +10:00
Geoffrey McRae
be7820303f [common] fixed debug formatting across platforms 2020-08-03 15:05:35 +10:00
Geoffrey McRae
43503222c7 [common] framebuffer: fixed incorrect streaming usage 2020-08-03 14:41:57 +10:00
Geoffrey McRae
85b8c12abf [common] adjust framebuffer read/write strategy for better cache usage 2020-08-03 12:33:08 +10:00
Geoffrey McRae
7af053497e [common] unroll the framebuffer write loop and increase the chunk size 2020-08-03 12:24:17 +10:00
Geoffrey McRae
9e3a42cb62 [host] don't stop the timer when restarting capture 2020-08-03 12:04:50 +10:00
Geoffrey McRae
aa32c5ffad [common] framebuffer: added missing header include 2020-08-03 11:58:38 +10:00
Geoffrey McRae
62d1bd1ea2 [common] framebuffer: use stream load instead of plain load 2020-08-03 11:55:38 +10:00
Geoffrey McRae
2329e993ee [common] fixed framebuffer write SIMD code performance 2020-08-03 11:44:24 +10:00
Geoffrey McRae
da655b86c3 [common] improve frambuffer copy to avoid cache pollution (SIMD) 2020-08-03 11:16:30 +10:00
Max Sistemich
c5ff8bd4ce [common] linux: implement timers 2020-07-25 00:38:15 +10:00
Geoffrey McRae
06aee158de [client] egl: make better use of atomics and fix modulus bug 2020-07-24 17:39:16 +10:00
Samuel Bowman
bd42445ea7 [client] add option to capture input on start 2020-07-17 08:39:32 +10:00
Geoffrey McRae
ede96fa486 [client] egl: don't map the texture until it's needed
The texture buffer may still be in use if we try to re-map it
immediately, instead only map when we need it mapped, and unmap
immediately after advancing the offset allowing the render thread to
continue while the unmap operation occurs
2020-05-30 16:50:27 +10:00
Geoffrey McRae
67dec216d2 [host] search the applications local directory for the config 2020-05-30 12:31:26 +10:00
Geoffrey McRae
fcbdf7ba4f [client] egl: fix non-streaming texture updates 2020-05-29 16:54:25 +10:00
Geoffrey McRae
e8c949c1e7 [client] egl: dont re-setup the fps texture on each update 2020-05-29 16:47:21 +10:00
Geoffrey McRae
28c93ef5ac [client] egl: don't unmap/map all buffers for each frame 2020-05-29 15:48:59 +10:00
Geoffrey McRae
d7921c5d5f [client] report the host version on mismatch if possible 2020-05-29 14:24:06 +10:00
Geoffrey McRae
6d296f2b44 [client] stop people running the client as root 2020-05-29 14:18:02 +10:00
Geoffrey McRae
553e2830bb [client/host] share the host version with the client for diagnostics 2020-05-29 14:14:31 +10:00
Geoffrey McRae
667ab981ba [host] send the latest cusror information when a new client connects 2020-05-25 14:37:02 +10:00
Geoffrey McRae
bc7871f630 [c-host] renamed finall to just plain host 2020-05-25 13:42:43 +10:00
Geoffrey McRae
d579705b10 [misc] minor readme update 2020-05-22 22:53:21 +10:00
Geoffrey McRae
94d383a8c1 [obs] remove useless advance operation 2020-05-22 22:51:41 +10:00
Geoffrey McRae
08062e3fc3 [client] check for underflow when checking frame time 2020-05-22 22:02:44 +10:00
Geoffrey McRae
4441427943 [client] implemented better clock drift correction 2020-05-22 20:45:59 +10:00
Geoffrey McRae
f5da432d38 [client] put back the fps correction from drift/skew 2020-05-22 18:39:19 +10:00
Geoffrey McRae
60f665a65c [client] more fps limiter fixes 2020-05-22 18:28:16 +10:00
Geoffrey McRae
9b6174793a [client] revert cusror update render trigger
While it makes the mouse a bit nicer it causes frame skips during cursor
movement.
2020-05-22 18:16:48 +10:00
Geoffrey McRae
dedab38b99 [client] rename fpsLimit to fpsMin 2020-05-22 18:15:17 +10:00
Geoffrey McRae
4580b18b04 [client] fix the fps limiter 2020-05-22 18:06:29 +10:00
Geoffrey McRae
88dad36449 [client] allow mouse movements to trigger render updates
Now EGL is lockless we can allow cursor updates to trigger frame updates
directly.
2020-05-22 18:00:18 +10:00
Geoffrey McRae
075c82b32c [client] egl: fix context binding enabling a lock free implementation 2020-05-22 17:47:19 +10:00
54 changed files with 638 additions and 336 deletions

View File

@@ -28,12 +28,12 @@ git repository!
Please also be sure to see the following files for more information
* [client/README.md](client/README.md)
* [c-host/README.md](c-host/README.md)
* [host/README.md](host/README.md)
* [module/README.md](module/README.md)
## Obtaining and using Looking Glass
Please see https://looking-glass.hostfission.com/quickstart
Please see https://looking-glass.hostfission.com/wiki/
## Latest Version

View File

@@ -1 +1 @@
B1-209-g26eea64689+1
B2-rc2-33-g2e1b0f2550+1

View File

@@ -35,6 +35,23 @@ Should this all go well you should be left with the file `looking-glass-client`
## Usage Tips
### High priority capture using DXGI and Secure Desktop (UAC) capture support
By default Windows gives priority to the foreground application for any GPU
work which causes issues with capture if the foreground application is consuming
100% of the available GPU resources. The looking glass host application is able
to increase the kernel GPU thread to realtime priority which fixes this, but in
order to do so it must run as the `SYSTEM` user account. To do this, please use
`PsExec` from SysInternals (Microsoft), for example:
PsExec64.exe -s -i -d looking-glass-host.exe
This will also enable the host application to capture the secure desktop which
includes things like the lock screen and UAC prompts.
A future update (likely Beta 3) will include a service launcher for the Looking
Glass host which will remove the need for `PsExec`.
### Key Bindings
By default Looking Glass uses the `Scroll Lock` key as the escape key for commands as well as the input capture mode toggle, this can be changed using the `-m` switch if you desire a different key.

View File

@@ -109,8 +109,6 @@ typedef struct LG_Renderer
LG_RendererOnFrameEvent on_frame_event;
LG_RendererOnAlert on_alert;
LG_RendererRender render_startup;
LG_RendererRender render_begin;
LG_RendererRender render_end;
LG_RendererRender render;
LG_RendererUpdateFPS update_fps;
}

View File

@@ -18,7 +18,6 @@ Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "desktop.h"
#include "egl.h"
#include "common/debug.h"
#include "common/option.h"
#include "common/locking.h"
@@ -211,7 +210,6 @@ bool egl_desktop_update(EGL_Desktop * desktop, const bool sourceChanged, const L
desktop->width = format.width;
desktop->height = format.height;
egl_lock(desktop->egl);
if (!egl_texture_setup(
desktop->texture,
pixFmt,
@@ -221,27 +219,20 @@ bool egl_desktop_update(EGL_Desktop * desktop, const bool sourceChanged, const L
true // streaming texture
))
{
egl_unlock(desktop->egl);
DEBUG_ERROR("Failed to setup the desktop texture");
return false;
}
egl_unlock(desktop->egl);
}
if (!egl_texture_update_from_frame(desktop->texture, frame))
return false;
egl_lock(desktop->egl);
enum EGL_TexStatus status;
if ((status = egl_texture_process(desktop->texture)) != EGL_TEX_STATUS_OK)
{
if (status != EGL_TEX_STATUS_NOTREADY)
{
DEBUG_ERROR("Failed to process the desktop texture");
egl_unlock(desktop->egl);
}
}
egl_unlock(desktop->egl);
return true;
}

View File

@@ -59,7 +59,6 @@ struct Inst
EGLDisplay display;
EGLConfig configs;
EGLSurface surface;
LG_Lock lock;
EGLContext context, frameContext;
EGL_Desktop * desktop; // the desktop
@@ -171,7 +170,6 @@ bool egl_create(void ** opaque, const LG_RendererParams params)
this->scaleY = 1.0f;
this->screenScaleX = 1.0f;
this->screenScaleY = 1.0f;
LG_LOCK_INIT(this->lock);
this->font = LG_Fonts[0];
if (!this->font->create(&this->fontObj, NULL, 16))
@@ -315,6 +313,27 @@ bool egl_on_frame_event(void * opaque, const LG_RendererFormat format, const Fra
this->useNearest = this->width < format.width || this->height < format.height;
/* this event runs in a second thread so we need to init it here */
if (!this->frameContext)
{
static EGLint attrs[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
if (!(this->frameContext = eglCreateContext(this->display, this->configs, this->context, attrs)))
{
DEBUG_ERROR("Failed to create the frame context");
return false;
}
if (!eglMakeCurrent(this->display, EGL_NO_SURFACE, EGL_NO_SURFACE, this->frameContext))
{
DEBUG_ERROR("Failed to make the frame context current");
return false;
}
}
if (!egl_desktop_update(this->desktop, sourceChanged, format, frame))
{
DEBUG_INFO("Failed to to update the desktop");
@@ -506,47 +525,6 @@ bool egl_render_startup(void * opaque, SDL_Window * window)
return true;
}
void egl_lock(void * opaque)
{
struct Inst * this = (struct Inst *)opaque;
if (!this->frameContext)
{
static EGLint attrs[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
if (!(this->frameContext = eglCreateContext(this->display, this->configs, this->context, attrs)))
DEBUG_ERROR("Failed to create the frame context");
}
LG_LOCK(this->lock);
eglMakeCurrent(this->display, this->surface, this->surface, this->frameContext);
}
void egl_unlock(void * opaque)
{
struct Inst * this = (struct Inst *)opaque;
eglMakeCurrent(this->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
LG_UNLOCK(this->lock);
}
bool egl_render_begin(void * opaque, SDL_Window * window)
{
struct Inst * this = (struct Inst *)opaque;
LG_LOCK(this->lock);
return eglMakeCurrent(this->display, this->surface, this->surface, this->context);
}
bool egl_render_end(void * opaque, SDL_Window * window)
{
struct Inst * this = (struct Inst *)opaque;
bool ret = eglMakeCurrent(this->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
LG_UNLOCK(this->lock);
return ret;
}
bool egl_render(void * opaque, SDL_Window * window)
{
struct Inst * this = (struct Inst *)opaque;
@@ -623,8 +601,6 @@ struct LG_Renderer LGR_EGL =
.on_frame_event = egl_on_frame_event,
.on_alert = egl_on_alert,
.render_startup = egl_render_startup,
.render_begin = egl_render_begin,
.render_end = egl_render_end,
.render = egl_render,
.update_fps = egl_update_fps
};

View File

@@ -1,28 +0,0 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2020 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 <stdbool.h>
#include "interface/renderer.h"
/* helpers to lock and make current the secondary context for the desktop */
void egl_lock(void * opaque);
void egl_unlock(void * opaque);

View File

@@ -44,6 +44,7 @@ struct EGL_FPS
EGL_Model * model;
bool ready;
int iwidth, iheight;
float width, height;
// uniforms
@@ -144,14 +145,22 @@ void egl_fps_update(EGL_FPS * fps, const float avgFPS, const float renderFPS)
return;
}
egl_texture_setup(
fps->texture,
EGL_PF_BGRA,
bmp->width ,
bmp->height,
bmp->width * bmp->bpp,
false
);
if (fps->iwidth != bmp->width || fps->iheight != bmp->height)
{
fps->iwidth = bmp->width;
fps->iheight = bmp->height;
fps->width = (float)bmp->width;
fps->height = (float)bmp->height;
egl_texture_setup(
fps->texture,
EGL_PF_BGRA,
bmp->width ,
bmp->height,
bmp->width * bmp->bpp,
false
);
}
egl_texture_update
(
@@ -159,10 +168,7 @@ void egl_fps_update(EGL_FPS * fps, const float avgFPS, const float renderFPS)
bmp->pixels
);
fps->width = bmp->width;
fps->height = bmp->height;
fps->ready = true;
fps->font->release(fps->fontObj, bmp);
}
@@ -187,4 +193,4 @@ void egl_fps_render(EGL_FPS * fps, const float scaleX, const float scaleY)
egl_model_render(fps->model);
glDisable(GL_BLEND);
}
}

View File

@@ -30,7 +30,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include <SDL2/SDL_egl.h>
#define TEXTURE_COUNT 3
/* this must be a multiple of 2 */
#define TEXTURE_COUNT 2
struct Tex
{
@@ -41,19 +42,9 @@ struct Tex
GLsync sync;
};
union TexState
struct TexState
{
_Atomic(uint32_t) v;
struct
{
/*
* w = write
* u = upload
* s = schedule
* d = display
*/
_Atomic(int8_t) w, u, s, d;
};
_Atomic(uint8_t) w, u, s, d;
};
struct EGL_Texture
@@ -73,8 +64,9 @@ struct EGL_Texture
GLenum dataType;
size_t pboBufferSize;
union TexState state;
struct Tex tex[TEXTURE_COUNT];
struct TexState state;
int textureCount;
struct Tex tex[TEXTURE_COUNT];
};
bool egl_texture_init(EGL_Texture ** texture)
@@ -98,13 +90,17 @@ void egl_texture_free(EGL_Texture ** texture)
if ((*texture)->planeCount > 0)
glDeleteSamplers((*texture)->planeCount, (*texture)->samplers);
for(int i = 0; i < ((*texture)->streaming ? TEXTURE_COUNT : 1); ++i)
for(int i = 0; i < (*texture)->textureCount; ++i)
{
struct Tex * t = &(*texture)->tex[i];
if (t->hasPBO)
{
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, t->pbo);
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
if ((*texture)->tex[i].map)
{
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
(*texture)->tex[i].map = NULL;
}
glDeleteBuffers(1, &t->pbo);
if (t->sync)
glDeleteSync(t->sync);
@@ -119,52 +115,68 @@ void egl_texture_free(EGL_Texture ** texture)
*texture = NULL;
}
static bool egl_texture_map(EGL_Texture * texture)
static bool egl_texture_map(EGL_Texture * texture, uint8_t i)
{
// release old PBOs and delete and re-create the buffers
for(int i = 0; i < TEXTURE_COUNT; ++i)
{
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->tex[i].pbo);
texture->tex[i].map = glMapBufferRange(
GL_PIXEL_UNPACK_BUFFER,
0,
texture->pboBufferSize,
GL_MAP_WRITE_BIT |
GL_MAP_UNSYNCHRONIZED_BIT |
GL_MAP_INVALIDATE_BUFFER_BIT
);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->tex[i].pbo);
texture->tex[i].map = glMapBufferRange(
GL_PIXEL_UNPACK_BUFFER,
0,
texture->pboBufferSize,
GL_MAP_WRITE_BIT |
GL_MAP_UNSYNCHRONIZED_BIT |
GL_MAP_INVALIDATE_BUFFER_BIT
);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
if (!texture->tex[i].map)
{
EGL_ERROR("glMapBufferRange failed for %d of %lu bytes", i, texture->pboBufferSize);
return false;
}
if (!texture->tex[i].map)
{
EGL_ERROR("glMapBufferRange failed for %d of %lu bytes", i, texture->pboBufferSize);
return false;
}
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
return true;
}
static void egl_texture_unmap(EGL_Texture * texture)
static void egl_texture_unmap(EGL_Texture * texture, uint8_t i)
{
for(int i = 0; i < TEXTURE_COUNT; ++i)
{
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->tex[i].pbo);
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
}
if (!texture->tex[i].map)
return;
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->tex[i].pbo);
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
texture->tex[i].map = NULL;
}
bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_t width, size_t height, size_t stride, bool streaming)
{
int planeCount;
texture->pixFmt = pixFmt;
texture->width = width;
texture->height = height;
texture->stride = stride;
texture->streaming = streaming;
texture->ready = false;
atomic_store_explicit(&texture->state.v, 0, memory_order_relaxed);
if (texture->streaming)
{
for(int i = 0; i < texture->textureCount; ++i)
{
egl_texture_unmap(texture, i);
if (texture->tex[i].hasPBO)
{
glDeleteBuffers(1, &texture->tex[i].pbo);
texture->tex[i].hasPBO = false;
}
}
}
texture->pixFmt = pixFmt;
texture->width = width;
texture->height = height;
texture->stride = stride;
texture->streaming = streaming;
texture->textureCount = streaming ? TEXTURE_COUNT : 1;
texture->ready = false;
atomic_store_explicit(&texture->state.w, 0, memory_order_relaxed);
atomic_store_explicit(&texture->state.u, 0, memory_order_relaxed);
atomic_store_explicit(&texture->state.s, 0, memory_order_relaxed);
atomic_store_explicit(&texture->state.d, 0, memory_order_relaxed);
switch(pixFmt)
{
@@ -237,7 +249,7 @@ bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_
if (texture->planeCount > 0)
glDeleteSamplers(texture->planeCount, texture->samplers);
for(int i = 0; i < TEXTURE_COUNT; ++i)
for(int i = 0; i < texture->textureCount; ++i)
{
if (texture->planeCount > 0)
glDeleteTextures(texture->planeCount, texture->tex[i].t);
@@ -256,7 +268,7 @@ bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_
texture->planeCount = planeCount;
}
for(int i = 0; i < (streaming ? TEXTURE_COUNT : 1); ++i)
for(int i = 0; i < texture->textureCount; ++i)
{
for(int p = 0; p < planeCount; ++p)
{
@@ -267,18 +279,11 @@ bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_
}
glBindTexture(GL_TEXTURE_2D, 0);
egl_texture_unmap(texture);
if (!streaming)
return true;
// release old PBOs and delete and re-create the buffers
for(int i = 0; i < TEXTURE_COUNT; ++i)
for(int i = 0; i < texture->textureCount; ++i)
{
if (texture->tex[i].hasPBO)
{
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->tex[i].pbo);
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
glDeleteBuffers(1, &texture->tex[i].pbo);
}
glGenBuffers(1, &texture->tex[i].pbo);
texture->tex[i].hasPBO = true;
@@ -291,9 +296,6 @@ bool egl_texture_setup(EGL_Texture * texture, enum EGL_PixelFormat pixFmt, size_
);
}
if (!egl_texture_map(texture))
return false;
return true;
}
@@ -314,23 +316,25 @@ bool egl_texture_update(EGL_Texture * texture, const uint8_t * buffer)
{
if (texture->streaming)
{
union TexState s;
s.v = atomic_load_explicit(&texture->state.v, memory_order_acquire);
const uint8_t sw =
atomic_load_explicit(&texture->state.w, memory_order_acquire);
const uint8_t next = (s.w + 1) % TEXTURE_COUNT;
if (next == s.u)
if (atomic_load_explicit(&texture->state.u, memory_order_acquire) == sw + 1)
{
egl_warn_slow();
return true;
}
memcpy(texture->tex[s.w].map, buffer, texture->pboBufferSize);
atomic_store_explicit(&texture->state.w, next, memory_order_release);
const uint8_t t = sw % TEXTURE_COUNT;
if (!egl_texture_map(texture, t))
return EGL_TEX_STATUS_ERROR;
memcpy(texture->tex[t].map, buffer, texture->pboBufferSize);
atomic_fetch_add_explicit(&texture->state.w, 1, memory_order_release);
egl_texture_unmap(texture, t);
}
else
{
/* Non streaming, this is NOT thread safe */
for(int p = 0; p < texture->planeCount; ++p)
{
glBindTexture(GL_TEXTURE_2D, texture->tex[0].t[p]);
@@ -348,19 +352,22 @@ bool egl_texture_update_from_frame(EGL_Texture * texture, const FrameBuffer * fr
if (!texture->streaming)
return false;
union TexState s;
s.v = atomic_load_explicit(&texture->state.v, memory_order_acquire);
const uint8_t sw =
atomic_load_explicit(&texture->state.w, memory_order_acquire);
const uint8_t next = (s.w + 1) % TEXTURE_COUNT;
if (next == s.u)
if (atomic_load_explicit(&texture->state.u, memory_order_acquire) == sw + 1)
{
egl_warn_slow();
return true;
}
const uint8_t t = sw % TEXTURE_COUNT;
if (!egl_texture_map(texture, t))
return EGL_TEX_STATUS_ERROR;
framebuffer_read(
frame,
texture->tex[s.w].map,
texture->tex[t].map,
texture->stride,
texture->height,
texture->width,
@@ -368,7 +375,9 @@ bool egl_texture_update_from_frame(EGL_Texture * texture, const FrameBuffer * fr
texture->stride
);
atomic_store_explicit(&texture->state.w, next, memory_order_release);
atomic_fetch_add_explicit(&texture->state.w, 1, memory_order_release);
egl_texture_unmap(texture, t);
return true;
}
@@ -377,84 +386,87 @@ enum EGL_TexStatus egl_texture_process(EGL_Texture * texture)
if (!texture->streaming)
return EGL_TEX_STATUS_OK;
union TexState s;
s.v = atomic_load_explicit(&texture->state.v, memory_order_acquire);
const uint8_t su =
atomic_load_explicit(&texture->state.u, memory_order_acquire);
const uint8_t nextu = (s.u + 1) % TEXTURE_COUNT;
if (s.u == s.w || nextu == s.s || nextu == s.d)
const uint8_t nextu = su + 1;
if (
su == atomic_load_explicit(&texture->state.w, memory_order_acquire) ||
nextu == atomic_load_explicit(&texture->state.s, memory_order_acquire) ||
nextu == atomic_load_explicit(&texture->state.d, memory_order_acquire))
return texture->ready ? EGL_TEX_STATUS_OK : EGL_TEX_STATUS_NOTREADY;
/* update the texture */
egl_texture_unmap(texture);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->tex[s.u].pbo);
const uint8_t t = su % TEXTURE_COUNT;
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->tex[t].pbo);
for(int p = 0; p < texture->planeCount; ++p)
{
glBindTexture(GL_TEXTURE_2D, texture->tex[s.u].t[p]);
glBindTexture(GL_TEXTURE_2D, texture->tex[t].t[p]);
glPixelStorei(GL_UNPACK_ROW_LENGTH, texture->planes[p][2]);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, texture->planes[p][0], texture->planes[p][1],
texture->format, texture->dataType, (const void *)texture->offsets[p]);
}
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
/* create a fence to prevent usage before the update is complete */
texture->tex[s.u].sync =
glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
texture->tex[t].sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
atomic_store_explicit(&texture->state.u, nextu, memory_order_release);
/* remap the for the next update */
egl_texture_map(texture);
/* we must flush to ensure the sync is in the command buffer */
glFlush();
texture->ready = true;
atomic_fetch_add_explicit(&texture->state.u, 1, memory_order_release);
return EGL_TEX_STATUS_OK;
}
enum EGL_TexStatus egl_texture_bind(EGL_Texture * texture)
{
union TexState s;
s.v = atomic_load_explicit(&texture->state.v, memory_order_acquire);
uint8_t ss = atomic_load_explicit(&texture->state.s, memory_order_acquire);
uint8_t sd = atomic_load_explicit(&texture->state.d, memory_order_acquire);
if (texture->streaming)
{
if (!texture->ready)
return EGL_TEX_STATUS_NOTREADY;
if (texture->tex[s.s].sync != 0)
const uint8_t t = ss % TEXTURE_COUNT;
if (texture->tex[t].sync != 0)
{
switch(glClientWaitSync(texture->tex[s.s].sync, 0, 20000000))
switch(glClientWaitSync(texture->tex[t].sync, 0, 20000000)) // 20ms
{
case GL_ALREADY_SIGNALED:
case GL_CONDITION_SATISFIED:
glDeleteSync(texture->tex[s.s].sync);
texture->tex[s.s].sync = 0;
glDeleteSync(texture->tex[t].sync);
texture->tex[t].sync = 0;
s.s = (s.s + 1) % TEXTURE_COUNT;
atomic_store_explicit(&texture->state.s, s.s, memory_order_release);
ss = atomic_fetch_add_explicit(&texture->state.s, 1,
memory_order_release) + 1;
break;
case GL_TIMEOUT_EXPIRED:
break;
case GL_WAIT_FAILED:
glDeleteSync(texture->tex[s.s].sync);
texture->tex[s.s].sync = 0;
case GL_INVALID_VALUE:
glDeleteSync(texture->tex[t].sync);
texture->tex[t].sync = 0;
EGL_ERROR("glClientWaitSync failed");
return EGL_TEX_STATUS_ERROR;
}
}
const int8_t nextd = (s.d + 1) % TEXTURE_COUNT;
if (s.d != s.s && nextd != s.s)
{
s.d = nextd;
atomic_store_explicit(&texture->state.d, nextd, memory_order_release);
}
if (ss != sd && ss != sd+1)
sd = atomic_fetch_add_explicit(&texture->state.d, 1,
memory_order_release) + 1;
}
const uint8_t t = sd % TEXTURE_COUNT;
for(int i = 0; i < texture->planeCount; ++i)
{
glActiveTexture(GL_TEXTURE0 + i);
glBindTexture(GL_TEXTURE_2D, texture->tex[s.d].t[i]);
glBindTexture(GL_TEXTURE_2D, texture->tex[t].t[i]);
glBindSampler(i, texture->samplers[i]);
}

View File

@@ -50,4 +50,4 @@ bool egl_texture_update (EGL_Texture * texture, const uint8_t * bu
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);
int egl_texture_count (EGL_Texture * texture);

View File

@@ -175,8 +175,8 @@ static struct Option options[] =
},
{
.module = "win",
.name = "fpsLimit",
.description = "Frame rate limit (0 = disable - not recommended, -1 = auto detect)",
.name = "fpsMin",
.description = "Frame rate minimum (0 = disable - not recommended, -1 = auto detect)",
.shortopt = 'K',
.type = OPTION_TYPE_INT,
.value.x_int = -1,
@@ -309,6 +309,13 @@ static struct Option options[] =
.type = OPTION_TYPE_BOOL,
.value.x_bool = true
},
{
.module = "spice",
.name = "captureOnStart",
.description = "Capture mouse and keyboard on start",
.type = OPTION_TYPE_BOOL,
.value.x_bool = false
},
{0}
};
@@ -382,7 +389,7 @@ bool config_load(int argc, char * argv[])
params.borderless = option_get_bool ("win", "borderless" );
params.fullscreen = option_get_bool ("win", "fullScreen" );
params.maximize = option_get_bool ("win", "maximize" );
params.fpsLimit = option_get_int ("win", "fpsLimit" );
params.fpsMin = option_get_int ("win", "fpsMin" );
params.showFPS = option_get_bool ("win", "showFPS" );
params.ignoreQuit = option_get_bool ("win", "ignoreQuit" );
params.noScreensaver = option_get_bool ("win", "noScreensaver");
@@ -413,6 +420,7 @@ bool config_load(int argc, char * argv[])
}
params.scaleMouseInput = option_get_bool("spice", "scaleCursor");
params.captureOnStart = option_get_bool("spice", "captureOnStart");
}
return true;

View File

@@ -34,6 +34,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include <stdint.h>
#include <stdbool.h>
#include <assert.h>
#include <stdatomic.h>
#if SDL_VIDEO_DRIVER_X11_XINPUT2
// because SDL2 sucks and we need to turn it off
@@ -69,6 +70,8 @@ static LGThread *t_cursor = NULL;
static LGThread *t_frame = NULL;
static SDL_Cursor *cursor = NULL;
static atomic_uint a_framesPending = 0;
struct AppState state;
// this structure is initialized in config.c
@@ -148,37 +151,22 @@ static int renderThread(void * unused)
/* signal to other threads that the renderer is ready */
lgSignalEvent(e_startup);
unsigned int resyncCheck = 0;
int resyncCheck = 0;
struct timespec time;
clock_gettime(CLOCK_REALTIME, &time);
while(state.running)
{
// if our clock is too far out of sync, resync it
// this can happen when switching to/from a TTY, or due to clock drift
// we only check this once every 100 frames
if (state.frameTime > 0 && ++resyncCheck == 100)
if (state.frameTime > 0)
{
resyncCheck = 0;
struct timespec tmp;
clock_gettime(CLOCK_REALTIME, &tmp);
if (tmp.tv_nsec - time.tv_nsec < 0)
if (++resyncCheck == 100)
{
tmp.tv_sec -= time.tv_sec - 1;
tmp.tv_nsec = 1000000000 + tmp.tv_nsec - time.tv_nsec;
}
else
{
tmp.tv_sec -= time.tv_sec;
tmp.tv_nsec -= time.tv_nsec;
resyncCheck = 0;
clock_gettime(CLOCK_REALTIME, &time);
}
tsAdd(&time, state.frameTime);
}
if (state.lgr->render_begin && !state.lgr->render_begin(state.lgrData,
state.window))
break;
if (state.lgrResize)
{
if (state.lgr)
@@ -198,20 +186,16 @@ static int renderThread(void * unused)
if (state.renderTime > 1e9)
{
const float avgUPS = 1000.0f / (((float)state.renderTime / state.frameCount ) / 1e6f);
const float avgUPS = 1000.0f / (((float)state.renderTime / atomic_load_explicit(&state.frameCount, memory_order_acquire)) / 1e6f);
const float avgFPS = 1000.0f / (((float)state.renderTime / state.renderCount) / 1e6f);
state.lgr->update_fps(state.lgrData, avgUPS, avgFPS);
atomic_store_explicit(&state.frameCount, 0, memory_order_release);
state.renderTime = 0;
state.frameCount = 0;
state.renderCount = 0;
}
}
if (state.lgr->render_end && !state.lgr->render_end(state.lgrData,
state.window))
break;
if (!state.resizeDone && state.resizeTimeout < microtime())
{
SDL_SetWindowSize(
@@ -222,16 +206,23 @@ static int renderThread(void * unused)
state.resizeDone = true;
}
uint64_t nsec = time.tv_nsec + state.frameTime;
if(nsec > 1e9)
if (state.frameTime > 0)
{
time.tv_nsec = nsec - 1e9;
++time.tv_sec;
}
else
time.tv_nsec = nsec;
/* if there are frames pending already, don't wait on the event */
if (atomic_load_explicit(&a_framesPending, memory_order_acquire) > 0)
if (atomic_fetch_sub_explicit(&a_framesPending, 1, memory_order_release) > 1)
continue;
lgWaitEventAbs(e_frame, &time);
if (lgWaitEventAbs(e_frame, &time))
{
if (state.frameTime > 0)
{
resyncCheck = 0;
clock_gettime(CLOCK_REALTIME, &time);
tsAdd(&time, state.frameTime);
}
}
}
}
state.running = false;
@@ -464,9 +455,12 @@ static int frameThread(void * unused)
DEBUG_ERROR("renderer on frame event returned failure");
break;
}
atomic_fetch_add_explicit(&state.frameCount, 1, memory_order_relaxed);
if (atomic_fetch_add_explicit(&a_framesPending, 1, memory_order_relaxed) == 0)
lgSignalEvent(e_frame);
lgmpClientMessageDone(queue);
++state.frameCount;
lgSignalEvent(e_frame);
}
lgmpClientUnsubscribe(&queue);
@@ -935,7 +929,6 @@ int eventFilter(void * userdata, SDL_Event * event)
if (params.useSpiceInput)
{
state.serverMode = !state.serverMode;
spice_mouse_mode(state.serverMode);
SDL_SetWindowGrab(state.window, state.serverMode);
DEBUG_INFO("Server Mode: %s", state.serverMode ? "on" : "off");
@@ -1242,6 +1235,7 @@ static int lg_run()
return -1;
}
spice_mouse_mode(true);
if (!lgCreateThread("spiceThread", spiceThread, NULL, &t_spice))
{
DEBUG_ERROR("spice create thread failed");
@@ -1326,24 +1320,13 @@ static int lg_run()
// ensure renderer viewport is aware of the current window size
updatePositionInfo();
//Auto detect active monitor refresh rate for FPS Limit if no FPS Limit was passed.
if (params.fpsLimit == -1)
{
SDL_DisplayMode current;
if (SDL_GetCurrentDisplayMode(SDL_GetWindowDisplayIndex(state.window), &current) == 0)
{
state.frameTime = 1e9 / current.refresh_rate;
}
else
{
DEBUG_WARN("Unable to capture monitor refresh rate using the default FPS Limit: 60");
state.frameTime = 1e9 / 60;
}
}
// use a default of 60FPS now that frame updates are host update triggered
if (params.fpsMin == -1)
state.frameTime = 1e9 / 60;
else
{
DEBUG_INFO("Using the FPS Limit from args: %d", params.fpsLimit);
state.frameTime = 1e9 / params.fpsLimit;
DEBUG_INFO("Using the FPS minimum from args: %d", params.fpsMin);
state.frameTime = 1e9 / params.fpsMin;
}
register_key_binds();
@@ -1421,6 +1404,13 @@ static int lg_run()
SDL_ShowCursor(SDL_DISABLE);
}
if (params.captureOnStart)
{
state.serverMode = true;
SDL_SetWindowGrab(state.window, state.serverMode);
DEBUG_INFO("Server Mode: %s", state.serverMode ? "on" : "off");
}
// setup the startup condition
if (!(e_startup = lgCreateEvent(false, 0)))
{
@@ -1501,19 +1491,29 @@ static int lg_run()
if (!state.running)
return -1;
if (udataSize != sizeof(KVMFR) ||
memcmp(udata->magic, KVMFR_MAGIC, sizeof(udata->magic)) != 0 ||
udata->version != KVMFR_VERSION)
const bool magicMatches = memcmp(udata->magic, KVMFR_MAGIC, sizeof(udata->magic)) == 0;
if (udataSize != sizeof(KVMFR) || !magicMatches || udata->version != KVMFR_VERSION)
{
DEBUG_BREAK();
DEBUG_ERROR("The host application is not compatible with this client");
DEBUG_ERROR("Expected KVMFR version %d", KVMFR_VERSION);
DEBUG_ERROR("This is not a Looking Glass error, do not report this");
DEBUG_ERROR("Please install the matching host application for this client");
if (magicMatches)
{
DEBUG_ERROR("Expected KVMFR version %d, got %d", KVMFR_VERSION, udata->version);
if (udata->version >= 2)
DEBUG_ERROR("Host version: %s", udata->hostver);
}
else
DEBUG_ERROR("Invalid KVMFR magic");
DEBUG_BREAK();
return -1;
}
DEBUG_INFO("Host ready, starting session");
DEBUG_INFO("Host ready, reported version: %s", udata->hostver);
DEBUG_INFO("Starting session");
if (!lgCreateThread("cursorThread", cursorThread, NULL, &t_cursor))
{
@@ -1607,6 +1607,12 @@ static void lg_shutdown()
int main(int argc, char * argv[])
{
if (getuid() == 0)
{
DEBUG_ERROR("Do not run looking glass as root!");
return -1;
}
DEBUG_INFO("Looking Glass (" BUILD_VERSION ")");
DEBUG_INFO("Locking Method: " LG_LOCK_MODE);

View File

@@ -18,6 +18,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <stdbool.h>
#include <stdatomic.h>
#include <SDL2/SDL.h>
#include "interface/app.h"
@@ -75,11 +76,11 @@ struct AppState
PLGMPClientQueue frameQueue;
PLGMPClientQueue pointerQueue;
uint64_t frameTime;
uint64_t lastFrameTime;
uint64_t renderTime;
uint64_t frameCount;
uint64_t renderCount;
atomic_uint_least64_t frameTime;
uint64_t lastFrameTime;
uint64_t renderTime;
uint64_t frameCount;
uint64_t renderCount;
uint64_t resizeTimeout;
@@ -109,7 +110,7 @@ struct AppParams
bool center;
int x, y;
unsigned int w, h;
unsigned int fpsLimit;
unsigned int fpsMin;
bool showFPS;
bool useSpiceInput;
bool useSpiceClipboard;
@@ -124,6 +125,7 @@ struct AppParams
bool grabKeyboard;
SDL_Scancode escapeKey;
bool showAlerts;
bool captureOnStart;
unsigned int cursorPollInterval;
unsigned int framePollInterval;
@@ -151,4 +153,4 @@ struct KeybindHandle
// forwards
extern struct AppState state;
extern struct AppParams params;
extern struct AppParams params;

View File

@@ -51,12 +51,13 @@ typedef enum CursorType
CursorType;
#define KVMFR_MAGIC "KVMFR---"
#define KVMFR_VERSION 1
#define KVMFR_VERSION 2
typedef struct KVMFR
{
char magic[8];
uint32_t version;
char hostver[32];
}
KVMFR;

View File

@@ -53,7 +53,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
sizeof(s) > 20 && (s)[sizeof(s)-21] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 20 : \
sizeof(s) > 21 && (s)[sizeof(s)-22] == DIRECTORY_SEPARATOR ? (s) + sizeof(s) - 21 : (s))
#define DEBUG_PRINT(type, fmt, ...) do {fprintf(stderr, "%" PRId64 " " type " %20s:%-4u | %-30s | " fmt "\n", microtime(), STRIPPATH(__FILE__), __LINE__, __FUNCTION__, ##__VA_ARGS__);} while (0)
#define DEBUG_PRINT(type, fmt, ...) do {fprintf(stderr, "%12" PRId64 " " type " %20s:%-4u | %-30s | " fmt "\n", microtime(), STRIPPATH(__FILE__), __LINE__, __FUNCTION__, ##__VA_ARGS__);} while (0)
#define DEBUG_BREAK() DEBUG_PRINT("[ ]", "%s", "================================================================================")
#define DEBUG_INFO(fmt, ...) DEBUG_PRINT("[I]", fmt, ##__VA_ARGS__)

View File

@@ -66,6 +66,42 @@ static inline void nsleep(uint64_t ns)
};
nanosleep(&ts, NULL);
}
static inline void tsDiff(struct timespec *diff, const struct timespec *left,
const struct timespec *right)
{
diff->tv_sec = left->tv_sec - right->tv_sec;
diff->tv_nsec = left->tv_nsec - right->tv_nsec;
if (diff->tv_nsec < 0)
{
--diff->tv_sec;
diff->tv_nsec += 1000000000;
}
}
static inline uint32_t __iter_div_u64_rem(uint64_t dividend, uint32_t divisor, uint64_t *remainder)
{
uint32_t ret = 0;
while (dividend >= divisor) {
/* The following asm() prevents the compiler from
optimising this loop into a modulo operation. */
asm("" : "+rm"(dividend));
dividend -= divisor;
ret++;
}
*remainder = dividend;
return ret;
}
static inline void tsAdd(struct timespec *a, uint64_t ns)
{
a->tv_sec += __iter_div_u64_rem(a->tv_nsec + ns, 1000000000L, &ns);
a->tv_nsec = ns;
}
#endif
typedef bool (*LGTimerFn)(void * udata);

View File

@@ -22,8 +22,10 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include <string.h>
#include <stdatomic.h>
#include <emmintrin.h>
#include <smmintrin.h>
#define FB_CHUNK_SIZE 1024
#define FB_CHUNK_SIZE 1048576
struct stFrameBuffer
{
@@ -35,17 +37,19 @@ const size_t FrameBufferStructSize = sizeof(FrameBuffer);
void framebuffer_wait(const FrameBuffer * frame, size_t size)
{
while(atomic_load_explicit(&frame->wp, memory_order_relaxed) != size) {}
while(atomic_load_explicit(&frame->wp, memory_order_acquire) != size) {}
}
bool framebuffer_read(const FrameBuffer * frame, void * dst, size_t dstpitch,
size_t height, size_t width, size_t bpp, size_t pitch)
bool framebuffer_read(const FrameBuffer * frame, void * restrict dst,
size_t dstpitch, size_t height, size_t width, size_t bpp, size_t pitch)
{
uint8_t *d = (uint8_t*)dst;
uint8_t * restrict d = (uint8_t*)dst;
uint_least32_t rp = 0;
size_t y = 0;
const size_t linewidth = width * bpp;
const size_t blocks = linewidth / 64;
const size_t left = linewidth % 64;
while(y < height)
{
@@ -53,13 +57,37 @@ bool framebuffer_read(const FrameBuffer * frame, void * dst, size_t dstpitch,
/* spinlock */
do
wp = atomic_load_explicit(&frame->wp, memory_order_relaxed);
wp = atomic_load_explicit(&frame->wp, memory_order_acquire);
while(wp - rp < pitch);
memcpy(d, frame->data + rp, linewidth);
_mm_mfence();
__m128i * restrict s = (__m128i *)(frame->data + rp);
for(int i = 0; i < blocks; ++i)
{
__m128i *_d = (__m128i *)d;
__m128i *_s = (__m128i *)s;
__m128i v1 = _mm_stream_load_si128(_s + 0);
__m128i v2 = _mm_stream_load_si128(_s + 1);
__m128i v3 = _mm_stream_load_si128(_s + 2);
__m128i v4 = _mm_stream_load_si128(_s + 3);
_mm_storeu_si128(_d + 0, v1);
_mm_storeu_si128(_d + 1, v2);
_mm_storeu_si128(_d + 2, v3);
_mm_storeu_si128(_d + 3, v4);
d += 64;
s += 4;
}
if (left)
{
memcpy(d, s, left);
d += left;
}
rp += pitch;
d += dstpitch;
d += dstpitch - linewidth;
++y;
}
@@ -79,7 +107,7 @@ bool framebuffer_read_fn(const FrameBuffer * frame, size_t height, size_t width,
/* spinlock */
do
wp = atomic_load_explicit(&frame->wp, memory_order_relaxed);
wp = atomic_load_explicit(&frame->wp, memory_order_acquire);
while(wp - rp < pitch);
if (!fn(opaque, frame->data + rp, linewidth))
@@ -97,18 +125,47 @@ bool framebuffer_read_fn(const FrameBuffer * frame, size_t height, size_t width,
*/
void framebuffer_prepare(FrameBuffer * frame)
{
atomic_store(&frame->wp, 0);
atomic_store_explicit(&frame->wp, 0, memory_order_release);
}
bool framebuffer_write(FrameBuffer * frame, const void * src, size_t size)
bool framebuffer_write(FrameBuffer * frame, const void * restrict src, size_t size)
{
__m128i * restrict s = (__m128i *)src;
__m128i * restrict d = (__m128i *)frame->data;
size_t wp = 0;
_mm_mfence();
/* copy in chunks */
while(size)
while(size > 63)
{
size_t copy = size < FB_CHUNK_SIZE ? FB_CHUNK_SIZE : size;
memcpy(frame->data + frame->wp, src, copy);
atomic_fetch_add(&frame->wp, copy);
size -= copy;
__m128i *_d = (__m128i *)d;
__m128i *_s = (__m128i *)s;
__m128i v1 = _mm_stream_load_si128(_s + 0);
__m128i v2 = _mm_stream_load_si128(_s + 1);
__m128i v3 = _mm_stream_load_si128(_s + 2);
__m128i v4 = _mm_stream_load_si128(_s + 3);
_mm_store_si128(_d + 0, v1);
_mm_store_si128(_d + 1, v2);
_mm_store_si128(_d + 2, v3);
_mm_store_si128(_d + 3, v4);
s += 4;
d += 4;
size -= 64;
wp += 64;
if (wp % FB_CHUNK_SIZE == 0)
atomic_store_explicit(&frame->wp, wp, memory_order_release);
}
if(size)
{
memcpy(frame->data + wp, s, size);
wp += size;
}
atomic_store_explicit(&frame->wp, wp, memory_order_release);
return true;
}

View File

@@ -11,6 +11,7 @@ add_library(lg_common_platform_code STATIC
thread.c
event.c
ivshmem.c
time.c
)
if(ENABLE_BACKTRACE)
@@ -20,4 +21,5 @@ endif()
target_link_libraries(lg_common_platform_code
lg_common
pthread
rt
)

View File

@@ -0,0 +1,109 @@
/*
Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2020-2020 Max Sistemich <maximilian.sistemich@rwth-aachen.de>
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/time.h"
#include "common/debug.h"
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
struct LGTimer
{
LGTimerFn fn;
void * udata;
timer_t id;
bool running;
};
static void TimerProc(union sigval arg)
{
LGTimer * timer = (LGTimer *)arg.sival_ptr;
if (!timer->fn(timer->udata))
{
if (timer_delete(timer->id))
DEBUG_ERROR("failed to destroy the timer: %s", strerror(errno));
timer->running = false;
}
}
bool lgCreateTimer(const unsigned int intervalMS, LGTimerFn fn,
void * udata, LGTimer ** result)
{
LGTimer * ret = malloc(sizeof(LGTimer));
if (!ret)
{
DEBUG_ERROR("failed to malloc LGTimer struct");
return false;
}
ret->fn = fn;
ret->udata = udata;
ret->running = true;
struct sigevent sev =
{
.sigev_notify = SIGEV_THREAD,
.sigev_notify_function = &TimerProc,
.sigev_value.sival_ptr = ret,
};
if (timer_create(CLOCK_MONOTONIC, &sev, &ret->id))
{
DEBUG_ERROR("failed to create timer: %s", strerror(errno));
free(ret);
return false;
}
struct timespec interval =
{
.tv_sec = 0,
.tv_nsec = intervalMS * 1000 * 1000,
};
struct itimerspec spec =
{
.it_interval = interval,
.it_value = interval,
};
if (timer_settime(ret->id, 0, &spec, NULL))
{
DEBUG_ERROR("failed to set timer: %s", strerror(errno));
timer_delete(ret->id);
free(ret);
return false;
}
*result = ret;
return true;
}
void lgTimerDestroy(LGTimer * timer)
{
if (timer->running)
{
if (timer_delete(timer->id))
DEBUG_ERROR("failed to destroy the timer: %s", strerror(errno));
}
free(timer);
}

View File

@@ -37,7 +37,7 @@ void DebugWinError(const char * file, const unsigned int line, const char * func
if (buffer[i] == '\n' || buffer[i] == '\r')
buffer[i] = 0;
fprintf(stderr, "[E] %20s:%-4u | %-30s | %s: 0x%08x (%s)\n", file, line, function, desc, (int)status, buffer);
fprintf(stderr, "%12" PRId64 " [E] %20s:%-4u | %-30s | %s: 0x%08x (%s)\n", microtime(), file, line, function, desc, (int)status, buffer);
LocalFree(buffer);
}
@@ -63,4 +63,4 @@ bool IsWindows8()
return
(CompareWindowsVersion(6, 3) == TRUE) ||
(CompareWindowsVersion(6, 2) == TRUE);
}
}

View File

@@ -27,3 +27,4 @@ void app_quit();
// these must be implemented for each OS
const char * os_getExecutable();
const char * os_getDataPath();

View File

@@ -30,6 +30,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
struct app
{
const char * executable;
char * dataPath;
};
struct app app = { 0 };
@@ -37,7 +38,13 @@ struct app app = { 0 };
int main(int argc, char * argv[])
{
app.executable = argv[0];
struct passwd * pw = getpwuid(getuid());
alloc_sprintf(&app.dataPath, "%s/", pw->pw_dir);
int result = app_main(argc, argv);
free(app.dataPath);
return result;
}
@@ -56,4 +63,9 @@ bool app_init()
const char * os_getExecutable()
{
return app.executable;
}
}
const char * os_getDataPath()
{
return app.dataPath;
}

View File

@@ -32,6 +32,18 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "dxgi_extra.h"
typedef enum _D3DKMT_SCHEDULINGPRIORITYCLASS
{
D3DKMT_SCHEDULINGPRIORITYCLASS_IDLE,
D3DKMT_SCHEDULINGPRIORITYCLASS_BELOW_NORMAL,
D3DKMT_SCHEDULINGPRIORITYCLASS_NORMAL,
D3DKMT_SCHEDULINGPRIORITYCLASS_ABOVE_NORMAL,
D3DKMT_SCHEDULINGPRIORITYCLASS_HIGH,
D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME
}
D3DKMT_SCHEDULINGPRIORITYCLASS;
typedef NTSTATUS WINAPI (*PD3DKMTSetProcessSchedulingPriorityClass)(HANDLE, D3DKMT_SCHEDULINGPRIORITYCLASS);
#define LOCKED(x) INTERLOCKED_SECTION(this->deviceContextLock, x)
enum TextureState
@@ -130,9 +142,9 @@ static void dxgi_initOptions()
{
.module = "dxgi",
.name = "useAcquireLock",
.description = "Enable locking around `AcquireFrame` (use if freezing, may lower performance)",
.description = "Enable locking around `AcquireFrame` (EXPERIMENTAL, leave enabled if you're not sure!)",
.type = OPTION_TYPE_BOOL,
.value.x_bool = false
.value.x_bool = true
},
{0}
};
@@ -385,6 +397,24 @@ static bool dxgi_init()
// bump up our priority
{
HMODULE gdi32 = GetModuleHandleA("GDI32");
if (gdi32)
{
PD3DKMTSetProcessSchedulingPriorityClass fn =
(PD3DKMTSetProcessSchedulingPriorityClass)GetProcAddress(gdi32, "D3DKMTSetProcessSchedulingPriorityClass");
if (fn)
{
status = fn(GetCurrentProcess(), D3DKMT_SCHEDULINGPRIORITYCLASS_REALTIME);
if (FAILED(status))
{
DEBUG_INFO("Failed to set realtime GPU priority, this is not an error!");
DEBUG_INFO("To fix this run LG using the PsExec SysInternals tool from Microsoft.");
DEBUG_INFO("https://docs.microsoft.com/en-us/sysinternals/downloads/psexec");
}
}
}
IDXGIDevice * dxgi;
status = ID3D11Device_QueryInterface(this->device, &IID_IDXGIDevice, (void **)&dxgi);
if (FAILED(status))

View File

@@ -303,6 +303,24 @@ const char * os_getExecutable()
return app.executable;
}
const char * os_getDataPath()
{
static char path[MAX_PATH] = { 0 };
if (!path[0])
{
if (!GetModuleFileName(NULL, path, MAX_PATH))
return NULL;
char *p = strrchr(path, '\\');
if (!p)
return NULL;
++p;
*p = '\0';
}
return path;
}
HWND os_getMessageWnd()
{
return app.messageWnd;

View File

@@ -38,6 +38,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include <stdlib.h>
#include <string.h>
#define CONFIG_FILE "looking-glass-host.ini"
#define ALIGN_DN(x) ((uintptr_t)(x) & ~0x7F)
#define ALIGN_UP(x) ALIGN_DN(x + 0x7F)
@@ -66,6 +68,8 @@ struct app
PLGMPHostQueue pointerQueue;
PLGMPMemory pointerMemory[LGMP_Q_POINTER_LEN];
LG_Lock pointerLock;
CapturePointer pointerInfo;
PLGMPMemory pointerShape;
bool pointerShapeValid;
unsigned int pointerIndex;
@@ -202,12 +206,6 @@ static int frameThread(void * opaque)
bool startThreads()
{
app.running = true;
if (!lgCreateTimer(100, lgmpTimer, NULL, &app.lgmpTimer))
{
DEBUG_ERROR("Failed to create the LGMP timer");
return false;
}
if (!lgCreateThread("FrameThread", frameThread, NULL, &app.frameThread))
{
DEBUG_ERROR("Failed to create the frame thread");
@@ -231,12 +229,6 @@ bool stopThreads()
}
app.frameThread = NULL;
if (app.lgmpTimer)
{
lgTimerDestroy(app.lgmpTimer);
app.lgmpTimer = NULL;
}
return ok;
}
@@ -279,7 +271,7 @@ bool captureGetPointerBuffer(void ** data, uint32_t * size)
// spin until there is room
while(lgmpHostQueuePending(app.pointerQueue) == LGMP_Q_POINTER_LEN)
{
DEBUG_INFO("pending");
usleep(1);
if (!app.running)
return false;
}
@@ -290,14 +282,13 @@ bool captureGetPointerBuffer(void ** data, uint32_t * size)
return true;
}
void capturePostPointerBuffer(CapturePointer pointer)
static void sendPointer(bool newClient)
{
PLGMPMemory mem;
const bool newClient = lgmpHostQueueNewSubs(app.pointerQueue) > 0;
if (pointer.shapeUpdate || newClient)
if (app.pointerInfo.shapeUpdate || newClient)
{
if (pointer.shapeUpdate)
if (app.pointerInfo.shapeUpdate)
{
// swap the latest shape buffer out of rotation
PLGMPMemory tmp = app.pointerShape;
@@ -309,32 +300,31 @@ void capturePostPointerBuffer(CapturePointer pointer)
mem = app.pointerShape;
}
else
{
mem = app.pointerMemory[app.pointerIndex];
if (++app.pointerIndex == LGMP_Q_POINTER_LEN)
app.pointerIndex = 0;
}
if (++app.pointerIndex == LGMP_Q_POINTER_LEN)
app.pointerIndex = 0;
uint32_t flags = 0;
KVMFRCursor *cursor = lgmpHostMemPtr(mem);
if (pointer.positionUpdate)
if (app.pointerInfo.positionUpdate || newClient)
{
flags |= CURSOR_FLAG_POSITION;
cursor->x = pointer.x;
cursor->y = pointer.y;
cursor->x = app.pointerInfo.x;
cursor->y = app.pointerInfo.y;
}
if (pointer.visible)
if (app.pointerInfo.visible)
flags |= CURSOR_FLAG_VISIBLE;
if (pointer.shapeUpdate)
if (app.pointerInfo.shapeUpdate)
{
// remember which slot has the latest shape
cursor->width = pointer.width;
cursor->height = pointer.height;
cursor->pitch = pointer.pitch;
switch(pointer.format)
cursor->width = app.pointerInfo.width;
cursor->height = app.pointerInfo.height;
cursor->pitch = app.pointerInfo.pitch;
switch(app.pointerInfo.format)
{
case CAPTURE_FMT_COLOR : cursor->type = CURSOR_TYPE_COLOR ; break;
case CAPTURE_FMT_MONO : cursor->type = CURSOR_TYPE_MONOCHROME ; break;
@@ -348,7 +338,7 @@ void capturePostPointerBuffer(CapturePointer pointer)
app.pointerShapeValid = true;
}
if ((pointer.shapeUpdate || newClient) && app.pointerShapeValid)
if ((app.pointerInfo.shapeUpdate || newClient) && app.pointerShapeValid)
flags |= CURSOR_FLAG_SHAPE;
LGMP_STATUS status;
@@ -361,10 +351,31 @@ void capturePostPointerBuffer(CapturePointer pointer)
}
DEBUG_ERROR("lgmpHostQueuePost Failed (Pointer): %s", lgmpStatusString(status));
return;
break;
}
}
void capturePostPointerBuffer(CapturePointer pointer)
{
LG_LOCK(app.pointerLock);
int x = app.pointerInfo.x;
int y = app.pointerInfo.y;
memcpy(&app.pointerInfo, &pointer, sizeof(CapturePointer));
/* if there was not a position update, restore the x & y */
if (!pointer.positionUpdate)
{
app.pointerInfo.x = x;
app.pointerInfo.y = y;
}
sendPointer(false);
LG_UNLOCK(app.pointerLock);
}
// this is called from the platform specific startup routine
int app_main(int argc, char * argv[])
{
@@ -379,7 +390,22 @@ int app_main(int argc, char * argv[])
CaptureInterfaces[i]->initOptions();
// try load values from a config file
option_load("looking-glass-host.ini");
const char * dataPath = os_getDataPath();
if (!dataPath)
{
option_free();
DEBUG_ERROR("Failed to get the application's data path");
return -1;
}
const size_t len = strlen(dataPath) + sizeof(CONFIG_FILE) + 1;
char configFile[len];
snprintf(configFile, sizeof(configFile), "%s%s", dataPath, CONFIG_FILE);
DEBUG_INFO("Looking for configuration file at: %s", configFile);
if (option_load(configFile))
DEBUG_INFO("Configuration file loaded");
else
DEBUG_INFO("Configuration file not found or invalid");
// parse the command line arguments
if (!option_parse(argc, argv))
@@ -414,9 +440,10 @@ int app_main(int argc, char * argv[])
DEBUG_INFO("Max Pointer Size : %u KiB", (unsigned int)MAX_POINTER_SIZE / 1024);
DEBUG_INFO("KVMFR Version : %u", KVMFR_VERSION);
KVMFR udata = {
const KVMFR udata = {
.magic = KVMFR_MAGIC,
.version = KVMFR_VERSION
.version = KVMFR_VERSION,
.hostver = BUILD_VERSION
};
LGMP_STATUS status;
@@ -498,6 +525,14 @@ int app_main(int argc, char * argv[])
app.iface = iface;
LG_LOCK_INIT(app.pointerLock);
if (!lgCreateTimer(100, lgmpTimer, NULL, &app.lgmpTimer))
{
DEBUG_ERROR("Failed to create the LGMP timer");
goto fail;
}
if (!captureStart())
{
exitcode = -1;
@@ -513,6 +548,13 @@ int app_main(int argc, char * argv[])
}
app.reinit = false;
if (lgmpHostQueueNewSubs(app.pointerQueue) > 0)
{
LG_LOCK(app.pointerLock);
sendPointer(true);
LG_UNLOCK(app.pointerLock);
}
switch(iface->capture())
{
case CAPTURE_RESULT_OK:
@@ -539,7 +581,10 @@ int app_main(int argc, char * argv[])
finish:
stopThreads();
exit:
lgTimerDestroy(app.lgmpTimer);
LG_LOCK_FREE(app.pointerLock);
iface->deinit();
iface->free();

View File

@@ -1,3 +1,5 @@
#define _GNU_SOURCE //needed for pthread_setname_np
#include <obs/obs-module.h>
#include <obs/util/threading.h>
@@ -193,6 +195,7 @@ static void lgUpdate(void * data, obs_data_t * settings)
this->state = STATE_STARTING;
pthread_create(&this->frameThread, NULL, frameThread, this);
pthread_setname_np(this->frameThread, "LGFrameThread");
}
static void lgVideoTick(void * data, float seconds)