[client] all: add screen rotation support win:rotate

Currently only supports EGL, if there is enough demand for OpenGL
support this can be added later.

Closes #231
This commit is contained in:
Geoffrey McRae 2021-01-15 12:40:59 +11:00
parent c40a81ddf4
commit 72c86d7125
11 changed files with 252 additions and 73 deletions

View File

@ -57,6 +57,15 @@ typedef enum LG_RendererSupport
} }
LG_RendererSupport; LG_RendererSupport;
typedef enum LG_RendererRotate
{
LG_ROTATE_UP,
LG_ROTATE_DOWN,
LG_ROTATE_LEFT,
LG_ROTATE_RIGHT
}
LG_RendererRotate;
typedef struct LG_RendererFormat typedef struct LG_RendererFormat
{ {
FrameType type; // frame type FrameType type; // frame type
@ -65,6 +74,7 @@ typedef struct LG_RendererFormat
unsigned int stride; // scanline width (zero if compresed) unsigned int stride; // scanline width (zero if compresed)
unsigned int pitch; // scanline bytes (or compressed size) unsigned int pitch; // scanline bytes (or compressed size)
unsigned int bpp; // bits per pixel (zero if compressed) unsigned int bpp; // bits per pixel (zero if compressed)
LG_RendererRotate rotate; // output rotation
} }
LG_RendererFormat; LG_RendererFormat;
@ -98,7 +108,7 @@ typedef void (* LG_RendererDeInitialize )(void * opaque);
typedef bool (* LG_RendererSupports )(void * opaque, LG_RendererSupport support); typedef bool (* LG_RendererSupports )(void * opaque, LG_RendererSupport support);
typedef void (* LG_RendererOnRestart )(void * opaque); typedef void (* LG_RendererOnRestart )(void * opaque);
typedef void (* LG_RendererOnResize )(void * opaque, const int width, const int height, const LG_RendererRect destRect); typedef void (* LG_RendererOnResize )(void * opaque, const int width, const int height, const LG_RendererRect destRect);
typedef bool (* LG_RendererOnMouseShape )(void * opaque, const LG_RendererCursor cursor, const int width, const int height, const int pitch, const uint8_t * data); typedef bool (* LG_RendererOnMouseShape )(void * opaque, const LG_RendererCursor cursor, const int width, const int height, const LG_RendererRotate rotate, const int pitch, const uint8_t * data);
typedef bool (* LG_RendererOnMouseEvent )(void * opaque, const bool visible , const int x, const int y); typedef bool (* LG_RendererOnMouseEvent )(void * opaque, const bool visible , const int x, const int y);
typedef bool (* LG_RendererOnFrameFormat)(void * opaque, const LG_RendererFormat format, bool useDMA); typedef bool (* LG_RendererOnFrameFormat)(void * opaque, const LG_RendererFormat format, bool useDMA);
typedef bool (* LG_RendererOnFrame )(void * opaque, const FrameBuffer * frame, int dmaFD); typedef bool (* LG_RendererOnFrame )(void * opaque, const FrameBuffer * frame, int dmaFD);

View File

@ -39,6 +39,7 @@ struct CursorTex
struct EGL_Texture * texture; struct EGL_Texture * texture;
struct EGL_Shader * shader; struct EGL_Shader * shader;
GLuint uMousePos; GLuint uMousePos;
GLuint uRotate;
GLuint uCBMode; GLuint uCBMode;
}; };
@ -56,6 +57,7 @@ struct EGL_Cursor
// cursor state // cursor state
bool visible; bool visible;
float x, y, w, h; float x, y, w, h;
int rotate;
int cbMode; int cbMode;
struct CursorTex norm; struct CursorTex norm;
@ -87,11 +89,61 @@ static bool egl_cursor_tex_init(struct CursorTex * t,
} }
t->uMousePos = egl_shader_get_uniform_location(t->shader, "mouse" ); t->uMousePos = egl_shader_get_uniform_location(t->shader, "mouse" );
t->uRotate = egl_shader_get_uniform_location(t->shader, "rotate");
t->uCBMode = egl_shader_get_uniform_location(t->shader, "cbMode"); t->uCBMode = egl_shader_get_uniform_location(t->shader, "cbMode");
return true; return true;
} }
static inline void egl_cursor_tex_uniforms(EGL_Cursor * cursor, struct CursorTex * t, bool mono)
{
float x, y, w, h;
switch(cursor->rotate)
{
case LG_ROTATE_UP:
x = cursor->x;
y = cursor->y;
w = cursor->w;
h = cursor->h;
break;
case LG_ROTATE_DOWN:
x = -cursor->x;
y = -cursor->y;
w = cursor->w;
h = cursor->h;
break;
case LG_ROTATE_LEFT:
x = cursor->y;
y = -cursor->x;
w = cursor->h;
h = cursor->w;
break;
case LG_ROTATE_RIGHT:
x = -cursor->y;
y = cursor->x;
w = cursor->h;
h = cursor->w;
break;
}
if (mono)
{
glUniform4f(t->uMousePos, x, y, w, h / 2);
glUniform1i(t->uRotate , cursor->rotate);
glUniform1i(t->uCBMode , cursor->cbMode);
}
else
{
glUniform4f(t->uMousePos, x, y, w, h);
glUniform1i(t->uRotate , cursor->rotate);
glUniform1i(t->uCBMode , cursor->cbMode);
}
}
static void egl_cursor_tex_free(struct CursorTex * t) static void egl_cursor_tex_free(struct CursorTex * t)
{ {
egl_texture_free(&t->texture); egl_texture_free(&t->texture);
@ -150,7 +202,9 @@ void egl_cursor_free(EGL_Cursor ** cursor)
*cursor = NULL; *cursor = NULL;
} }
bool egl_cursor_set_shape(EGL_Cursor * cursor, const LG_RendererCursor type, const int width, const int height, const int stride, const uint8_t * data) bool egl_cursor_set_shape(EGL_Cursor * cursor, const LG_RendererCursor type,
const int width, const int height, const LG_RendererRotate rotate,
const int stride, const uint8_t * data)
{ {
LG_LOCK(cursor->lock); LG_LOCK(cursor->lock);
@ -158,6 +212,7 @@ bool egl_cursor_set_shape(EGL_Cursor * cursor, const LG_RendererCursor type, con
cursor->width = width; cursor->width = width;
cursor->height = (type == LG_CURSOR_MONOCHROME ? height / 2 : height); cursor->height = (type == LG_CURSOR_MONOCHROME ? height / 2 : height);
cursor->stride = stride; cursor->stride = stride;
cursor->rotate = rotate;
const size_t size = height * stride; const size_t size = height * stride;
if (size > cursor->dataSize) if (size > cursor->dataSize)
@ -238,9 +293,9 @@ void egl_cursor_render(EGL_Cursor * cursor)
xor[y * cursor->width + x] = xorMask; xor[y * cursor->width + x] = xorMask;
} }
egl_texture_setup (cursor->norm.texture , EGL_PF_BGRA, cursor->width, cursor->height, cursor->width * 4, false, false); egl_texture_setup (cursor->norm.texture, EGL_PF_BGRA, cursor->width, cursor->height, cursor->width * 4, false, false);
egl_texture_setup (cursor->mono.texture, EGL_PF_BGRA, cursor->width, cursor->height, cursor->width * 4, false, false); egl_texture_setup (cursor->mono.texture, EGL_PF_BGRA, cursor->width, cursor->height, cursor->width * 4, false, false);
egl_texture_update(cursor->norm.texture , (uint8_t *)and); egl_texture_update(cursor->norm.texture, (uint8_t *)and);
egl_texture_update(cursor->mono.texture, (uint8_t *)xor); egl_texture_update(cursor->mono.texture, (uint8_t *)xor);
break; break;
} }
@ -254,14 +309,13 @@ void egl_cursor_render(EGL_Cursor * cursor)
case LG_CURSOR_MONOCHROME: case LG_CURSOR_MONOCHROME:
{ {
egl_shader_use(cursor->norm.shader); egl_shader_use(cursor->norm.shader);
glUniform4f(cursor->norm.uMousePos, cursor->x, cursor->y, cursor->w, cursor->h / 2); egl_cursor_tex_uniforms(cursor, &cursor->norm, true);;
glUniform1i(cursor->norm.uCBMode , cursor->cbMode);
glBlendFunc(GL_ZERO, GL_SRC_COLOR); glBlendFunc(GL_ZERO, GL_SRC_COLOR);
egl_model_set_texture(cursor->model, cursor->norm.texture); egl_model_set_texture(cursor->model, cursor->norm.texture);
egl_model_render(cursor->model); egl_model_render(cursor->model);
egl_shader_use(cursor->mono.shader); egl_shader_use(cursor->mono.shader);
glUniform4f(cursor->mono.uMousePos, cursor->x, cursor->y, cursor->w, cursor->h / 2); egl_cursor_tex_uniforms(cursor, &cursor->mono, true);;
glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO); glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
egl_model_set_texture(cursor->model, cursor->mono.texture); egl_model_set_texture(cursor->model, cursor->mono.texture);
egl_model_render(cursor->model); egl_model_render(cursor->model);
@ -271,8 +325,7 @@ void egl_cursor_render(EGL_Cursor * cursor)
case LG_CURSOR_COLOR: case LG_CURSOR_COLOR:
{ {
egl_shader_use(cursor->norm.shader); egl_shader_use(cursor->norm.shader);
glUniform4f(cursor->norm.uMousePos, cursor->x, cursor->y, cursor->w, cursor->h); egl_cursor_tex_uniforms(cursor, &cursor->norm, false);
glUniform1i(cursor->norm.uCBMode , cursor->cbMode);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
egl_model_render(cursor->model); egl_model_render(cursor->model);
break; break;
@ -281,7 +334,7 @@ void egl_cursor_render(EGL_Cursor * cursor)
case LG_CURSOR_MASKED_COLOR: case LG_CURSOR_MASKED_COLOR:
{ {
egl_shader_use(cursor->mono.shader); egl_shader_use(cursor->mono.shader);
glUniform4f(cursor->mono.uMousePos, cursor->x, cursor->y, cursor->w, cursor->h); egl_cursor_tex_uniforms(cursor, &cursor->mono, false);
glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO); glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
egl_model_render(cursor->model); egl_model_render(cursor->model);
break; break;

View File

@ -28,7 +28,15 @@ typedef struct EGL_Cursor EGL_Cursor;
bool egl_cursor_init(EGL_Cursor ** cursor); bool egl_cursor_init(EGL_Cursor ** cursor);
void egl_cursor_free(EGL_Cursor ** cursor); void egl_cursor_free(EGL_Cursor ** cursor);
bool egl_cursor_set_shape(EGL_Cursor * cursor, const LG_RendererCursor type, const int width, const int height, const int stride, const uint8_t * data); bool egl_cursor_set_shape(
EGL_Cursor * cursor,
const LG_RendererCursor type,
const int width,
const int height,
const LG_RendererRotate rotate,
const int stride,
const uint8_t * data);
void egl_cursor_set_size (EGL_Cursor * cursor, const float x, const float y); void egl_cursor_set_size (EGL_Cursor * cursor, const float x, const float y);
void egl_cursor_set_state(EGL_Cursor * cursor, const bool visible, const float x, const float y); void egl_cursor_set_state(EGL_Cursor * cursor, const bool visible, const float x, const float y);
void egl_cursor_render (EGL_Cursor * cursor); void egl_cursor_render (EGL_Cursor * cursor);

View File

@ -1,6 +1,6 @@
/* /*
Looking Glass - KVM FrameRelay (KVMFR) Client Looking Glass - KVM FrameRelay (KVMFR) Client
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com> Copyright (C) 2017-2021 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.com https://looking-glass.hostfission.com
This program is free software; you can redistribute it and/or modify it under This program is free software; you can redistribute it and/or modify it under
@ -34,13 +34,13 @@ Place, Suite 330, Boston, MA 02111-1307 USA
// these headers are auto generated by cmake // these headers are auto generated by cmake
#include "desktop.vert.h" #include "desktop.vert.h"
#include "desktop_rgb.frag.h" #include "desktop_rgb.frag.h"
#include "desktop_yuv.frag.h"
struct DesktopShader struct DesktopShader
{ {
EGL_Shader * shader; EGL_Shader * shader;
GLint uDesktopPos; GLint uDesktopPos;
GLint uDesktopSize; GLint uDesktopSize;
GLint uRotate;
GLint uNearest; GLint uNearest;
GLint uNV, uNVGain; GLint uNV, uNVGain;
GLint uCBMode; GLint uCBMode;
@ -56,10 +56,10 @@ struct EGL_Desktop
// internals // internals
int width, height; int width, height;
LG_RendererRotate rotate;
// shader instances // shader instances
struct DesktopShader shader_generic; struct DesktopShader shader_generic;
struct DesktopShader shader_yuv;
// night vision // night vision
KeybindHandle kbNV; KeybindHandle kbNV;
@ -91,6 +91,7 @@ static bool egl_init_desktop_shader(
shader->uDesktopPos = egl_shader_get_uniform_location(shader->shader, "position"); shader->uDesktopPos = egl_shader_get_uniform_location(shader->shader, "position");
shader->uDesktopSize = egl_shader_get_uniform_location(shader->shader, "size" ); shader->uDesktopSize = egl_shader_get_uniform_location(shader->shader, "size" );
shader->uRotate = egl_shader_get_uniform_location(shader->shader, "rotate" );
shader->uNearest = egl_shader_get_uniform_location(shader->shader, "nearest" ); shader->uNearest = egl_shader_get_uniform_location(shader->shader, "nearest" );
shader->uNV = egl_shader_get_uniform_location(shader->shader, "nv" ); shader->uNV = egl_shader_get_uniform_location(shader->shader, "nv" );
shader->uNVGain = egl_shader_get_uniform_location(shader->shader, "nvGain" ); shader->uNVGain = egl_shader_get_uniform_location(shader->shader, "nvGain" );
@ -203,6 +204,7 @@ bool egl_desktop_setup(EGL_Desktop * desktop, const LG_RendererFormat format, bo
desktop->width = format.width; desktop->width = format.width;
desktop->height = format.height; desktop->height = format.height;
desktop->rotate = format.rotate;
if (!egl_texture_setup( if (!egl_texture_setup(
desktop->texture, desktop->texture,
@ -252,6 +254,7 @@ bool egl_desktop_render(EGL_Desktop * desktop, const float x, const float y, con
const struct DesktopShader * shader = desktop->shader; const struct DesktopShader * shader = desktop->shader;
egl_shader_use(shader->shader); egl_shader_use(shader->shader);
glUniform4f(shader->uDesktopPos , x, y, scaleX, scaleY); glUniform4f(shader->uDesktopPos , x, y, scaleX, scaleY);
glUniform1i(shader->uRotate , desktop->rotate);
glUniform1i(shader->uNearest , nearest ? 1 : 0); glUniform1i(shader->uNearest , nearest ? 1 : 0);
glUniform2f(shader->uDesktopSize, desktop->width, desktop->height); glUniform2f(shader->uDesktopSize, desktop->width, desktop->height);

View File

@ -318,10 +318,12 @@ void egl_on_resize(void * opaque, const int width, const int height, const LG_Re
); );
} }
bool egl_on_mouse_shape(void * opaque, const LG_RendererCursor cursor, const int width, const int height, const int pitch, const uint8_t * data) bool egl_on_mouse_shape(void * opaque, const LG_RendererCursor cursor,
const int width, const int height, const LG_RendererRotate rotate,
const int pitch, const uint8_t * data)
{ {
struct Inst * this = (struct Inst *)opaque; struct Inst * this = (struct Inst *)opaque;
if (!egl_cursor_set_shape(this->cursor, cursor, width, height, pitch, data)) if (!egl_cursor_set_shape(this->cursor, cursor, width, height, rotate, pitch, data))
{ {
DEBUG_ERROR("Failed to update the cursor shape"); DEBUG_ERROR("Failed to update the cursor shape");
return false; return false;

View File

@ -2,6 +2,7 @@
in highp vec2 uv; in highp vec2 uv;
out highp vec4 color; out highp vec4 color;
uniform int rotate;
uniform sampler2D sampler1; uniform sampler2D sampler1;
@ -9,7 +10,28 @@ uniform int cbMode;
void main() void main()
{ {
color = texture(sampler1, uv); highp vec2 ruv;
if (rotate == 0) // up
{
ruv = uv;
}
else if (rotate == 1) // down
{
ruv.x = -uv.x + 1.0f;
ruv.y = -uv.y + 1.0f;
}
else if (rotate == 2) // left
{
ruv.x = -uv.y + 1.0f;
ruv.y = uv.x;
}
else if (rotate == 3) // right
{
ruv.x = uv.y;
ruv.y = -uv.x + 1.0f;
}
color = texture(sampler1, ruv);
if (cbMode > 0) if (cbMode > 0)
{ {

View File

@ -7,6 +7,7 @@ uniform sampler2D sampler1;
uniform int nearest; uniform int nearest;
uniform highp vec2 size; uniform highp vec2 size;
uniform int rotate;
uniform int nv; uniform int nv;
uniform highp float nvGain; uniform highp float nvGain;
@ -14,10 +15,31 @@ uniform int cbMode;
void main() void main()
{ {
highp vec2 ruv;
if (rotate == 0) // up
{
ruv = uv;
}
else if (rotate == 1) // down
{
ruv.x = -uv.x + 1.0f;
ruv.y = -uv.y + 1.0f;
}
else if (rotate == 2) // left
{
ruv.x = -uv.y + 1.0f;
ruv.y = uv.x;
}
else if (rotate == 3) // right
{
ruv.x = uv.y;
ruv.y = -uv.x + 1.0f;
}
if(nearest == 1) if(nearest == 1)
color = texture(sampler1, uv); color = texture(sampler1, ruv);
else else
color = texelFetch(sampler1, ivec2(uv * size), 0); color = texelFetch(sampler1, ivec2(ruv * size), 0);
if (cbMode > 0) if (cbMode > 0)
{ {

View File

@ -346,7 +346,7 @@ void opengl_on_resize(void * opaque, const int width, const int height, const LG
} }
} }
bool opengl_on_mouse_shape(void * opaque, const LG_RendererCursor cursor, const int width, const int height, const int pitch, const uint8_t * data) bool opengl_on_mouse_shape(void * opaque, const LG_RendererCursor cursor, const int width, const int height, const LG_RendererRotate rotate, const int pitch, const uint8_t * data)
{ {
struct Inst * this = (struct Inst *)opaque; struct Inst * this = (struct Inst *)opaque;
if (!this) if (!this)

View File

@ -38,6 +38,9 @@ static bool optSizeParse (struct Option * opt, const char * str);
static StringList optSizeValues (struct Option * opt); static StringList optSizeValues (struct Option * opt);
static char * optSizeToString (struct Option * opt); static char * optSizeToString (struct Option * opt);
static char * optScancodeToString(struct Option * opt); static char * optScancodeToString(struct Option * opt);
static bool optRotateParse (struct Option * opt, const char * str);
static StringList optRotateValues (struct Option * opt);
static char * optRotateToString (struct Option * opt);
static void doLicense(); static void doLicense();
@ -234,6 +237,15 @@ static struct Option options[] =
.type = OPTION_TYPE_BOOL, .type = OPTION_TYPE_BOOL,
.value.x_bool = false, .value.x_bool = false,
}, },
{
.module = "win",
.name = "rotate",
.description = "Rotate the displayed image",
.type = OPTION_TYPE_CUSTOM,
.parser = optRotateParse,
.getValues = optRotateValues,
.toString = optRotateToString
},
// input options // input options
{ {
@ -664,3 +676,46 @@ static char * optScancodeToString(struct Option * opt)
alloc_sprintf(&str, "%d = %s", opt->value.x_int, SDL_GetScancodeName(opt->value.x_int)); alloc_sprintf(&str, "%d = %s", opt->value.x_int, SDL_GetScancodeName(opt->value.x_int));
return str; return str;
} }
static bool optRotateParse(struct Option * opt, const char * str)
{
if (!str)
return false;
if (strcasecmp(str, "up" ) == 0) params.winRotate = LG_ROTATE_UP;
else if (strcasecmp(str, "down" ) == 0) params.winRotate = LG_ROTATE_DOWN;
else if (strcasecmp(str, "left" ) == 0) params.winRotate = LG_ROTATE_LEFT;
else if (strcasecmp(str, "right") == 0) params.winRotate = LG_ROTATE_RIGHT;
else
return false;
return true;
}
static StringList optRotateValues(struct Option * opt)
{
StringList sl = stringlist_new(false);
stringlist_push(sl, "UP" );
stringlist_push(sl, "DOWN" );
stringlist_push(sl, "LEFT" );
stringlist_push(sl, "RIGHT");
return sl;
}
static char * optRotateToString(struct Option * opt)
{
const char * str;
switch(params.winRotate)
{
case LG_ROTATE_UP : str = "UP" ; break;
case LG_ROTATE_DOWN : str = "DOWN" ; break;
case LG_ROTATE_LEFT : str = "LEFT" ; break;
case LG_ROTATE_RIGHT: str = "RIGHT"; break;
default:
return NULL;
}
return strdup(str);
}

View File

@ -379,6 +379,7 @@ static int cursorThread(void * unused)
cursorType, cursorType,
cursor->width, cursor->width,
cursor->height, cursor->height,
params.winRotate,
cursor->pitch, cursor->pitch,
data) data)
) )
@ -558,6 +559,8 @@ static int frameThread(void * unused)
frame->width, frame->height, frame->width, frame->height,
frame->stride, frame->pitch); frame->stride, frame->pitch);
lgrFormat.rotate = params.winRotate;
if (!g_state.lgr->on_frame_format(g_state.lgrData, lgrFormat, useDMA)) if (!g_state.lgr->on_frame_format(g_state.lgrData, lgrFormat, useDMA))
{ {
DEBUG_ERROR("renderer failed to configure format"); DEBUG_ERROR("renderer failed to configure format");

View File

@ -109,6 +109,7 @@ struct AppParams
unsigned int w, h; unsigned int w, h;
int fpsMin; int fpsMin;
bool showFPS; bool showFPS;
LG_RendererRotate winRotate;
bool useSpiceInput; bool useSpiceInput;
bool useSpiceClipboard; bool useSpiceClipboard;
const char * spiceHost; const char * spiceHost;