mirror of
https://github.com/gnif/LookingGlass.git
synced 2025-11-17 15:38:45 +00:00
Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82e0b7b6ab | ||
|
|
2e1b0f2550 | ||
|
|
3302d353cf | ||
|
|
1899d9f1da | ||
|
|
fb9b772db0 | ||
|
|
302b988524 | ||
|
|
19c2fe9b5e | ||
|
|
88d25ee98c | ||
|
|
0f2ecdf5f1 | ||
|
|
3511fb8d59 | ||
|
|
1d6d640b6e | ||
|
|
977d7b277d | ||
|
|
be7820303f | ||
|
|
43503222c7 | ||
|
|
85b8c12abf | ||
|
|
7af053497e | ||
|
|
9e3a42cb62 | ||
|
|
aa32c5ffad | ||
|
|
62d1bd1ea2 | ||
|
|
2329e993ee | ||
|
|
da655b86c3 | ||
|
|
c5ff8bd4ce | ||
|
|
06aee158de | ||
|
|
bd42445ea7 | ||
|
|
ede96fa486 | ||
|
|
67dec216d2 | ||
|
|
fcbdf7ba4f | ||
|
|
e8c949c1e7 | ||
|
|
28c93ef5ac | ||
|
|
d7921c5d5f | ||
|
|
6d296f2b44 | ||
|
|
553e2830bb | ||
|
|
667ab981ba | ||
|
|
bc7871f630 | ||
|
|
d579705b10 | ||
|
|
94d383a8c1 | ||
|
|
08062e3fc3 | ||
|
|
4441427943 | ||
|
|
f5da432d38 | ||
|
|
60f665a65c | ||
|
|
9b6174793a | ||
|
|
dedab38b99 | ||
|
|
4580b18b04 | ||
|
|
88dad36449 | ||
|
|
075c82b32c |
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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), ¤t) == 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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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__)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
109
common/src/platform/linux/time.c
Normal file
109
common/src/platform/linux/time.c
Normal 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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
0
c-host/.gitignore → host/.gitignore
vendored
0
c-host/.gitignore → host/.gitignore
vendored
@@ -27,3 +27,4 @@ void app_quit();
|
||||
|
||||
// these must be implemented for each OS
|
||||
const char * os_getExecutable();
|
||||
const char * os_getDataPath();
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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))
|
||||
@@ -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;
|
||||
@@ -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();
|
||||
3
obs/lg.c
3
obs/lg.c
@@ -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)
|
||||
|
||||
Submodule repos/LGMP updated: 19efde39f6...6a41b4c237
Reference in New Issue
Block a user