[client] egl: keep the mouse cursor 1:1 when downscaling

This keeps the cursor a usable size when the guest is running a high
resolution and downscaling (ie, 4K -> FHD).
This commit is contained in:
Geoffrey McRae 2021-10-22 23:19:56 +11:00
parent 74444f8eed
commit d69069fb09
8 changed files with 137 additions and 52 deletions

View File

@ -139,8 +139,8 @@ typedef struct LG_RendererOps
/* called when the mouse has moved or changed visibillity /* called when the mouse has moved or changed visibillity
* Context: cursorThread */ * Context: cursorThread */
bool (*onMouseEvent)(LG_Renderer * renderer, const bool visible, const int x, bool (*onMouseEvent)(LG_Renderer * renderer, const bool visible, int x, int y,
const int y); const int hx, const int hy);
/* called when the frame format has changed /* called when the frame format has changed
* Context: frameThread */ * Context: frameThread */

View File

@ -42,6 +42,7 @@ struct CursorTex
struct EGL_Texture * texture; struct EGL_Texture * texture;
struct EGL_Shader * shader; struct EGL_Shader * shader;
GLuint uMousePos; GLuint uMousePos;
GLuint uScale;
GLuint uRotate; GLuint uRotate;
GLuint uCBMode; GLuint uCBMode;
}; };
@ -73,7 +74,9 @@ struct EGL_Cursor
int cbMode; int cbMode;
_Atomic(struct CursorPos) pos; _Atomic(struct CursorPos) pos;
_Atomic(struct CursorPos) hs;
_Atomic(struct CursorSize) size; _Atomic(struct CursorSize) size;
_Atomic(float) scale;
struct CursorTex norm; struct CursorTex norm;
struct CursorTex mono; struct CursorTex mono;
@ -103,28 +106,22 @@ static bool cursorTexInit(struct CursorTex * t,
return false; return false;
} }
t->uMousePos = egl_shaderGetUniform(t->shader, "mouse" ); t->uMousePos = egl_shaderGetUniform(t->shader, "mouse" );
t->uRotate = egl_shaderGetUniform(t->shader, "rotate"); t->uScale = egl_shaderGetUniform(t->shader, "scale" );
t->uCBMode = egl_shaderGetUniform(t->shader, "cbMode"); t->uRotate = egl_shaderGetUniform(t->shader, "rotate" );
t->uCBMode = egl_shaderGetUniform(t->shader, "cbMode" );
return true; return true;
} }
static inline void setCursorTexUniforms(EGL_Cursor * cursor, static inline void setCursorTexUniforms(EGL_Cursor * cursor,
struct CursorTex * t, bool mono, float x, float y, float w, float h) struct CursorTex * t, bool mono, float x, float y,
float w, float h, float scale)
{ {
if (mono) glUniform4f(t->uMousePos, x, y, w, mono ? h / 2 : h);
{ glUniform1f(t->uScale , scale);
glUniform4f(t->uMousePos, x, y, w, h / 2); glUniform1i(t->uRotate , cursor->rotate);
glUniform1i(t->uRotate , cursor->rotate); glUniform1i(t->uCBMode , cursor->cbMode);
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 cursorTexFree(struct CursorTex * t) static void cursorTexFree(struct CursorTex * t)
@ -166,9 +163,12 @@ bool egl_cursorInit(EGL_Cursor ** cursor)
(*cursor)->cbMode = option_get_int("egl", "cbMode"); (*cursor)->cbMode = option_get_int("egl", "cbMode");
struct CursorPos pos = { .x = 0, .y = 0 }; struct CursorPos pos = { .x = 0, .y = 0 };
struct CursorPos hs = { .x = 0, .y = 0 };
struct CursorSize size = { .w = 0, .h = 0 }; struct CursorSize size = { .w = 0, .h = 0 };
atomic_init(&(*cursor)->pos, pos); atomic_init(&(*cursor)->pos , pos );
atomic_init(&(*cursor)->size, size); atomic_init(&(*cursor)->hs , hs );
atomic_init(&(*cursor)->size , size);
atomic_init(&(*cursor)->scale, 1.0f);
return true; return true;
} }
@ -229,11 +229,19 @@ void egl_cursorSetSize(EGL_Cursor * cursor, const float w, const float h)
atomic_store(&cursor->size, size); atomic_store(&cursor->size, size);
} }
void egl_cursorSetState(EGL_Cursor * cursor, const bool visible, const float x, const float y) void egl_cursorSetScale(EGL_Cursor * cursor, const float scale)
{
atomic_store(&cursor->scale, scale);
}
void egl_cursorSetState(EGL_Cursor * cursor, const bool visible,
const float x, const float y, const float hx, const float hy)
{ {
cursor->visible = visible; cursor->visible = visible;
struct CursorPos pos = { .x = x, .y = y}; struct CursorPos pos = { .x = x , .y = y };
struct CursorPos hs = { .x = hx, .y = hy };
atomic_store(&cursor->pos, pos); atomic_store(&cursor->pos, pos);
atomic_store(&cursor->hs , hs);
} }
struct CursorState egl_cursorRender(EGL_Cursor * cursor, struct CursorState egl_cursorRender(EGL_Cursor * cursor,
@ -256,7 +264,8 @@ struct CursorState egl_cursorRender(EGL_Cursor * cursor,
case LG_CURSOR_COLOR: case LG_CURSOR_COLOR:
{ {
egl_textureSetup(cursor->norm.texture, EGL_PF_BGRA, cursor->width, cursor->height, cursor->stride); egl_textureSetup(cursor->norm.texture, EGL_PF_BGRA,
cursor->width, cursor->height, cursor->stride);
egl_textureUpdate(cursor->norm.texture, data); egl_textureUpdate(cursor->norm.texture, data);
egl_modelSetTexture(cursor->model, cursor->norm.texture); egl_modelSetTexture(cursor->model, cursor->norm.texture);
break; break;
@ -264,10 +273,11 @@ struct CursorState egl_cursorRender(EGL_Cursor * cursor,
case LG_CURSOR_MONOCHROME: case LG_CURSOR_MONOCHROME:
{ {
uint32_t and[cursor->width * cursor->height]; uint32_t and[cursor->height][cursor->width];
uint32_t xor[cursor->width * cursor->height]; uint32_t xor[cursor->height][cursor->width];
for(int y = 0; y < cursor->height; ++y) for(int y = 0; y < cursor->height; ++y)
{
for(int x = 0; x < cursor->width; ++x) for(int x = 0; x < cursor->width; ++x)
{ {
const uint8_t * srcAnd = data + (cursor->stride * y) + (x / 8); const uint8_t * srcAnd = data + (cursor->stride * y) + (x / 8);
@ -276,12 +286,15 @@ struct CursorState egl_cursorRender(EGL_Cursor * cursor,
const uint32_t andMask = (*srcAnd & mask) ? 0xFFFFFFFF : 0xFF000000; const uint32_t andMask = (*srcAnd & mask) ? 0xFFFFFFFF : 0xFF000000;
const uint32_t xorMask = (*srcXor & mask) ? 0x00FFFFFF : 0x00000000; const uint32_t xorMask = (*srcXor & mask) ? 0x00FFFFFF : 0x00000000;
and[y * cursor->width + x] = andMask; and[y][x] = andMask;
xor[y * cursor->width + x] = xorMask; xor[y][x] = xorMask;
} }
}
egl_textureSetup (cursor->norm.texture, EGL_PF_BGRA, cursor->width, cursor->height, cursor->width * 4); egl_textureSetup(cursor->norm.texture, EGL_PF_BGRA,
egl_textureSetup (cursor->mono.texture, EGL_PF_BGRA, cursor->width, cursor->height, cursor->width * 4); cursor->width, cursor->height, sizeof(and[0]));
egl_textureSetup(cursor->mono.texture, EGL_PF_BGRA,
cursor->width, cursor->height, sizeof(xor[0]));
egl_textureUpdate(cursor->norm.texture, (uint8_t *)and); egl_textureUpdate(cursor->norm.texture, (uint8_t *)and);
egl_textureUpdate(cursor->mono.texture, (uint8_t *)xor); egl_textureUpdate(cursor->mono.texture, (uint8_t *)xor);
break; break;
@ -292,8 +305,15 @@ struct CursorState egl_cursorRender(EGL_Cursor * cursor,
cursor->rotate = rotate; cursor->rotate = rotate;
struct CursorPos pos = atomic_load(&cursor->pos); struct CursorPos pos = atomic_load(&cursor->pos );
struct CursorSize size = atomic_load(&cursor->size); float scale = atomic_load(&cursor->scale);
struct CursorPos hs = atomic_load(&cursor->hs );
struct CursorSize size = atomic_load(&cursor->size );
pos.x -= hs.x * scale;
pos.y -= hs.y * scale;
size.w *= scale;
size.h *= scale;
struct CursorState state = { struct CursorState state = {
.visible = true, .visible = true,
@ -342,13 +362,15 @@ struct CursorState egl_cursorRender(EGL_Cursor * cursor,
case LG_CURSOR_MONOCHROME: case LG_CURSOR_MONOCHROME:
{ {
egl_shaderUse(cursor->norm.shader); egl_shaderUse(cursor->norm.shader);
setCursorTexUniforms(cursor, &cursor->norm, true, pos.x, pos.y, size.w, size.h); setCursorTexUniforms(cursor, &cursor->norm, true, pos.x, pos.y,
size.w, size.h, scale);
glBlendFunc(GL_ZERO, GL_SRC_COLOR); glBlendFunc(GL_ZERO, GL_SRC_COLOR);
egl_modelSetTexture(cursor->model, cursor->norm.texture); egl_modelSetTexture(cursor->model, cursor->norm.texture);
egl_modelRender(cursor->model); egl_modelRender(cursor->model);
egl_shaderUse(cursor->mono.shader); egl_shaderUse(cursor->mono.shader);
setCursorTexUniforms(cursor, &cursor->mono, true, pos.x, pos.y, size.w, size.h); setCursorTexUniforms(cursor, &cursor->mono, true, pos.x, pos.y,
size.w, size.h, scale);
glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO); glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
egl_modelSetTexture(cursor->model, cursor->mono.texture); egl_modelSetTexture(cursor->model, cursor->mono.texture);
egl_modelRender(cursor->model); egl_modelRender(cursor->model);
@ -358,7 +380,8 @@ struct CursorState egl_cursorRender(EGL_Cursor * cursor,
case LG_CURSOR_COLOR: case LG_CURSOR_COLOR:
{ {
egl_shaderUse(cursor->norm.shader); egl_shaderUse(cursor->norm.shader);
setCursorTexUniforms(cursor, &cursor->norm, false, pos.x, pos.y, size.w, size.h); setCursorTexUniforms(cursor, &cursor->norm, false, pos.x, pos.y,
size.w, size.h, scale);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
egl_modelRender(cursor->model); egl_modelRender(cursor->model);
break; break;
@ -367,7 +390,8 @@ struct CursorState egl_cursorRender(EGL_Cursor * cursor,
case LG_CURSOR_MASKED_COLOR: case LG_CURSOR_MASKED_COLOR:
{ {
egl_shaderUse(cursor->mono.shader); egl_shaderUse(cursor->mono.shader);
setCursorTexUniforms(cursor, &cursor->mono, false, pos.x, pos.y, size.w, size.h); setCursorTexUniforms(cursor, &cursor->mono, false, pos.x, pos.y,
size.w, size.h, scale);
glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO); glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
egl_modelRender(cursor->model); egl_modelRender(cursor->model);
break; break;

View File

@ -45,8 +45,10 @@ bool egl_cursorSetShape(
void egl_cursorSetSize(EGL_Cursor * cursor, const float x, const float y); void egl_cursorSetSize(EGL_Cursor * cursor, const float x, const float y);
void egl_cursorSetScale(EGL_Cursor * cursor, const float scale);
void egl_cursorSetState(EGL_Cursor * cursor, const bool visible, void egl_cursorSetState(EGL_Cursor * cursor, const bool visible,
const float x, const float y); const float x, const float y, const float hx, const float hy);
struct CursorState egl_cursorRender(EGL_Cursor * cursor, struct CursorState egl_cursorRender(EGL_Cursor * cursor,
LG_RendererRotate rotate, int width, int height); LG_RendererRotate rotate, int width, int height);

View File

@ -103,9 +103,11 @@ struct Inst
bool cursorVisible; bool cursorVisible;
int cursorX , cursorY; int cursorX , cursorY;
int cursorHX , cursorHY;
float mouseWidth , mouseHeight; float mouseWidth , mouseHeight;
float mouseScaleX, mouseScaleY; float mouseScaleX, mouseScaleY;
bool showDamage; bool showDamage;
bool scalePointer;
struct CursorState cursorLast; struct CursorState cursorLast;
@ -197,6 +199,13 @@ static struct Option egl_options[] =
.type = OPTION_TYPE_BOOL, .type = OPTION_TYPE_BOOL,
.value.x_bool = false .value.x_bool = false
}, },
{
.module = "egl",
.name = "scalePointer",
.description = "Keep the pointer size 1:1 when downscaling",
.type = OPTION_TYPE_BOOL,
.value.x_bool = true
},
{0} {0}
}; };
@ -381,8 +390,10 @@ static void egl_calc_mouse_state(struct Inst * this)
egl_cursorSetState( egl_cursorSetState(
this->cursor, this->cursor,
this->cursorVisible, this->cursorVisible,
(((float)this->cursorX * this->mouseScaleX) - 1.0f) * this->scaleX, (((float)this->cursorX * this->mouseScaleX) - 1.0f) * this->scaleX,
(((float)this->cursorY * this->mouseScaleY) - 1.0f) * this->scaleY (((float)this->cursorY * this->mouseScaleY) - 1.0f) * this->scaleY,
((float)this->cursorHX * this->mouseScaleX) * this->scaleX,
((float)this->cursorHY * this->mouseScaleY) * this->scaleY
); );
break; break;
@ -391,8 +402,10 @@ static void egl_calc_mouse_state(struct Inst * this)
egl_cursorSetState( egl_cursorSetState(
this->cursor, this->cursor,
this->cursorVisible, this->cursorVisible,
(((float)this->cursorX * this->mouseScaleX) - 1.0f) * this->scaleY, (((float)this->cursorX * this->mouseScaleX) - 1.0f) * this->scaleY,
(((float)this->cursorY * this->mouseScaleY) - 1.0f) * this->scaleX (((float)this->cursorY * this->mouseScaleY) - 1.0f) * this->scaleX,
((float)this->cursorHX * this->mouseScaleX) * this->scaleY,
((float)this->cursorHY * this->mouseScaleY) * this->scaleX
); );
break; break;
} }
@ -465,6 +478,12 @@ static void egl_onResize(LG_Renderer * renderer, const int width, const int heig
this->screenScaleY = 1.0f / this->height; this->screenScaleY = 1.0f / this->height;
egl_calc_mouse_state(this); egl_calc_mouse_state(this);
if (this->scalePointer)
{
float scale = max(1.0f,
this->formatValid ? (float)this->format.width / this->width : 1.0f);
egl_cursorSetScale(this->cursor, scale);
}
INTERLOCKED_SECTION(this->desktopDamageLock, { INTERLOCKED_SECTION(this->desktopDamageLock, {
this->desktopDamage[this->desktopDamageIdx].count = -1; this->desktopDamage[this->desktopDamageIdx].count = -1;
@ -497,12 +516,15 @@ static bool egl_onMouseShape(LG_Renderer * renderer, const LG_RendererCursor cur
return true; return true;
} }
static bool egl_onMouseEvent(LG_Renderer * renderer, const bool visible, const int x, const int y) static bool egl_onMouseEvent(LG_Renderer * renderer, const bool visible,
int x, int y, const int hx, const int hy)
{ {
struct Inst * this = UPCAST(struct Inst, renderer); struct Inst * this = UPCAST(struct Inst, renderer);
this->cursorVisible = visible; this->cursorVisible = visible;
this->cursorX = x; this->cursorX = x + hx;
this->cursorY = y; this->cursorY = y + hy;
this->cursorHX = hx;
this->cursorHY = hy;
egl_calc_mouse_state(this); egl_calc_mouse_state(this);
return true; return true;
} }
@ -534,6 +556,12 @@ static bool egl_onFrameFormat(LG_Renderer * renderer, const LG_RendererFormat fo
} }
} }
if (this->scalePointer)
{
float scale = max(1.0f, (float)format.width / this->width);
egl_cursorSetScale(this->cursor, scale);
}
egl_update_scale_type(this); egl_update_scale_type(this);
egl_damageSetup(this->damage, format.width, format.height); egl_damageSetup(this->damage, format.width, format.height);
@ -818,6 +846,8 @@ static bool egl_renderStartup(LG_Renderer * renderer, bool useDMA)
if (this->noSwapDamage) if (this->noSwapDamage)
DEBUG_WARN("egl:noSwapDamage specified, disabling swap buffers with damage."); DEBUG_WARN("egl:noSwapDamage specified, disabling swap buffers with damage.");
this->scalePointer = option_get_bool("egl", "scalePointer");
if (!g_egl_dynProcs.glEGLImageTargetTexture2DOES) if (!g_egl_dynProcs.glEGLImageTargetTexture2DOES)
DEBUG_INFO("glEGLImageTargetTexture2DOES unavilable, DMA support disabled"); DEBUG_INFO("glEGLImageTargetTexture2DOES unavilable, DMA support disabled");
else if (!g_egl_dynProcs.eglCreateImage || !g_egl_dynProcs.eglDestroyImage) else if (!g_egl_dynProcs.eglCreateImage || !g_egl_dynProcs.eglDestroyImage)

View File

@ -5,11 +5,25 @@ in vec2 uv;
out vec4 color; out vec4 color;
uniform sampler2D sampler1; uniform sampler2D sampler1;
uniform float scale;
void main() void main()
{ {
vec4 tmp = texture(sampler1, uv); vec4 tmp;
if (scale > 1.0)
{
vec2 ts = vec2(textureSize(sampler1, 0));
vec2 px = (uv - (0.5 / ts)) * ts;
if (px.x < 0.0 || px.y < 0.0)
discard;
tmp = texelFetch(sampler1, ivec2(px), 0);
}
else
tmp = texture(sampler1, uv);
if (tmp.rgb == vec3(0.0, 0.0, 0.0)) if (tmp.rgb == vec3(0.0, 0.0, 0.0))
discard; discard;
color = tmp; color = tmp;
} }

View File

@ -3,16 +3,26 @@ precision mediump float;
#include "color_blind.h" #include "color_blind.h"
in vec2 uv; in vec2 uv;
out vec4 color; out vec4 color;
uniform sampler2D sampler1; uniform sampler2D sampler1;
uniform float scale;
uniform int cbMode; uniform int cbMode;
void main() void main()
{ {
color = texture(sampler1, uv); if (scale > 1.0)
{
vec2 ts = vec2(textureSize(sampler1, 0));
vec2 px = (uv - (0.5 / ts)) * ts;
if (px.x < 0.0 || px.y < 0.0)
discard;
color = texelFetch(sampler1, ivec2(px), 0);
}
else
color = texture(sampler1, uv);
if (cbMode > 0) if (cbMode > 0)
color = cbTransform(color, cbMode); color = cbTransform(color, cbMode);

View File

@ -338,7 +338,8 @@ bool opengl_onMouseShape(LG_Renderer * renderer, const LG_RendererCursor cursor,
return true; return true;
} }
bool opengl_onMouseEvent(LG_Renderer * renderer, const bool visible, const int x, const int y) bool opengl_onMouseEvent(LG_Renderer * renderer, const bool visible,
int x, int y, const int hx, const int hy)
{ {
struct Inst * this = UPCAST(struct Inst, renderer); struct Inst * this = UPCAST(struct Inst, renderer);

View File

@ -341,7 +341,9 @@ int main_cursorThread(void * unused)
RENDERER(onMouseEvent, RENDERER(onMouseEvent,
g_cursor.guest.visible && (g_cursor.draw || !g_params.useSpiceInput), g_cursor.guest.visible && (g_cursor.draw || !g_params.useSpiceInput),
g_cursor.guest.x, g_cursor.guest.x,
g_cursor.guest.y g_cursor.guest.y,
g_cursor.guest.hx,
g_cursor.guest.hy
); );
if (!g_state.stopVideo) if (!g_state.stopVideo)
@ -456,7 +458,9 @@ int main_cursorThread(void * unused)
RENDERER(onMouseEvent, RENDERER(onMouseEvent,
g_cursor.guest.visible && (g_cursor.draw || !g_params.useSpiceInput), g_cursor.guest.visible && (g_cursor.draw || !g_params.useSpiceInput),
g_cursor.guest.x, g_cursor.guest.x,
g_cursor.guest.y g_cursor.guest.y,
g_cursor.guest.hx,
g_cursor.guest.hy
); );
if (g_params.mouseRedraw && g_cursor.guest.visible && !g_state.stopVideo) if (g_params.mouseRedraw && g_cursor.guest.visible && !g_state.stopVideo)