2018-09-22 06:26:10 +00:00
|
|
|
/*
|
|
|
|
Looking Glass - KVM FrameRelay (KVMFR) Client
|
2019-02-22 11:16:14 +00:00
|
|
|
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
|
2018-09-22 06:26:10 +00:00
|
|
|
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
|
|
|
|
*/
|
|
|
|
|
2019-03-28 00:02:36 +00:00
|
|
|
#include "interface/renderer.h"
|
2018-11-19 22:50:09 +00:00
|
|
|
|
2019-04-11 01:12:59 +00:00
|
|
|
#include "common/debug.h"
|
2019-05-21 05:03:59 +00:00
|
|
|
#include "common/option.h"
|
2019-05-23 06:56:13 +00:00
|
|
|
#include "common/sysinfo.h"
|
2020-01-17 03:35:08 +00:00
|
|
|
#include "common/time.h"
|
2020-05-21 01:44:56 +00:00
|
|
|
#include "common/locking.h"
|
2018-09-24 09:48:11 +00:00
|
|
|
#include "utils.h"
|
2019-03-28 08:56:14 +00:00
|
|
|
#include "dynamic/fonts.h"
|
2018-09-22 06:26:10 +00:00
|
|
|
|
|
|
|
#include <SDL2/SDL_syswm.h>
|
|
|
|
#include <SDL2/SDL_egl.h>
|
2019-03-26 06:29:49 +00:00
|
|
|
|
|
|
|
#if defined(SDL_VIDEO_DRIVER_WAYLAND)
|
2019-01-01 13:04:40 +00:00
|
|
|
#include <wayland-egl.h>
|
2019-03-26 06:29:49 +00:00
|
|
|
#endif
|
2018-09-22 06:26:10 +00:00
|
|
|
|
2021-01-19 15:06:49 +00:00
|
|
|
#include <assert.h>
|
|
|
|
|
2021-01-15 09:30:03 +00:00
|
|
|
#include "app.h"
|
2021-01-24 01:05:18 +00:00
|
|
|
#include "dynprocs.h"
|
2019-03-28 00:02:36 +00:00
|
|
|
#include "model.h"
|
|
|
|
#include "shader.h"
|
|
|
|
#include "desktop.h"
|
|
|
|
#include "cursor.h"
|
|
|
|
#include "fps.h"
|
|
|
|
#include "splash.h"
|
|
|
|
#include "alert.h"
|
2018-12-15 13:54:37 +00:00
|
|
|
|
2018-12-15 23:57:01 +00:00
|
|
|
#define SPLASH_FADE_TIME 1000000
|
|
|
|
#define ALERT_TIMEOUT 2000000
|
2018-09-22 06:26:10 +00:00
|
|
|
|
|
|
|
struct Options
|
|
|
|
{
|
2018-09-22 08:00:52 +00:00
|
|
|
bool vsync;
|
2020-12-31 04:31:24 +00:00
|
|
|
bool doubleBuffer;
|
2018-09-22 06:26:10 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
struct Inst
|
|
|
|
{
|
2020-10-29 15:32:25 +00:00
|
|
|
bool dmaSupport;
|
2018-09-22 06:26:10 +00:00
|
|
|
LG_RendererParams params;
|
|
|
|
struct Options opt;
|
|
|
|
|
2019-01-01 13:04:40 +00:00
|
|
|
EGLNativeWindowType nativeWind;
|
|
|
|
EGLDisplay display;
|
|
|
|
EGLConfig configs;
|
|
|
|
EGLSurface surface;
|
2020-05-21 01:44:56 +00:00
|
|
|
EGLContext context, frameContext;
|
2018-09-22 06:26:10 +00:00
|
|
|
|
2018-12-12 10:41:51 +00:00
|
|
|
EGL_Desktop * desktop; // the desktop
|
|
|
|
EGL_Cursor * cursor; // the mouse cursor
|
|
|
|
EGL_FPS * fps; // the fps display
|
2018-12-15 13:54:37 +00:00
|
|
|
EGL_Splash * splash; // the splash screen
|
2018-12-15 23:57:01 +00:00
|
|
|
EGL_Alert * alert; // the alert display
|
2018-09-22 06:26:10 +00:00
|
|
|
|
2018-09-23 10:45:20 +00:00
|
|
|
LG_RendererFormat format;
|
2021-01-19 09:36:43 +00:00
|
|
|
bool formatValid;
|
2020-08-11 04:45:08 +00:00
|
|
|
bool start;
|
2018-12-15 13:54:37 +00:00
|
|
|
uint64_t waitFadeTime;
|
|
|
|
bool waitDone;
|
2018-09-24 09:48:11 +00:00
|
|
|
|
2018-12-15 23:57:01 +00:00
|
|
|
bool showAlert;
|
|
|
|
uint64_t alertTimeout;
|
|
|
|
bool useCloseFlag;
|
|
|
|
bool closeFlag;
|
|
|
|
|
2021-01-18 15:44:56 +00:00
|
|
|
int width, height;
|
|
|
|
LG_RendererRect destRect;
|
|
|
|
LG_RendererRotate rotate; //client side rotation
|
2018-11-19 22:50:09 +00:00
|
|
|
|
2018-12-15 23:57:01 +00:00
|
|
|
float translateX , translateY;
|
|
|
|
float scaleX , scaleY;
|
|
|
|
float splashRatio;
|
|
|
|
float screenScaleX, screenScaleY;
|
2019-04-19 01:23:51 +00:00
|
|
|
bool useNearest;
|
2018-10-03 16:31:37 +00:00
|
|
|
|
2019-05-28 04:06:15 +00:00
|
|
|
bool cursorVisible;
|
|
|
|
int cursorX , cursorY;
|
2018-12-12 07:53:55 +00:00
|
|
|
float mouseWidth , mouseHeight;
|
2018-09-24 09:48:11 +00:00
|
|
|
float mouseScaleX, mouseScaleY;
|
2018-11-19 22:50:09 +00:00
|
|
|
|
|
|
|
const LG_Font * font;
|
|
|
|
LG_FontObj fontObj;
|
2018-09-22 06:26:10 +00:00
|
|
|
};
|
|
|
|
|
[client] fix hang in eglSwapBuffers when exiting while not visible
eglSwapBuffers is allowed to block when called with a nonzero interval
parameter. On Wayland, Mesa will block until a frame callback arrives.
If an application is not visible, a compositor is free to not schedule
frame callbacks (in order to save CPU time rendering something that is
entirely invisible).
Currently, starting Looking Glass from a terminal, hiding it
entirely, and sending ^C will cause Looking Glass to hang joining the
render thread until the window is made visible again.
Calling eglDestroySurface is insufficient to unblock eglSwapBuffers, as
it attempts to grab the same underlying mutex.
Instead, this commit makes it so that we pass a 0 interval to
eglSwapBuffers when running on Wayland, such that we don't block waiting
for a frame callback. This is not entirely ideal as it *does* mean
Looking Glass submits buffers while hidden, but it seems better than
hanging on exit.
It also forces opengl:vsync and egl:vsync flags to off when running on
Wayland, as they are meaningless there.
2021-01-04 02:09:55 +00:00
|
|
|
static bool egl_vsync_option_validator(struct Option * opt, const char ** error)
|
|
|
|
{
|
|
|
|
if (opt->value.x_bool && getenv("WAYLAND_DISPLAY"))
|
|
|
|
{
|
|
|
|
DEBUG_WARN("Cannot disable vsync on Wayland, forcing egl:vsync=off");
|
|
|
|
opt->value.x_bool = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
2018-09-24 09:48:11 +00:00
|
|
|
|
2019-05-21 05:03:59 +00:00
|
|
|
static struct Option egl_options[] =
|
|
|
|
{
|
|
|
|
{
|
|
|
|
.module = "egl",
|
|
|
|
.name = "vsync",
|
|
|
|
.description = "Enable vsync",
|
|
|
|
.type = OPTION_TYPE_BOOL,
|
[client] fix hang in eglSwapBuffers when exiting while not visible
eglSwapBuffers is allowed to block when called with a nonzero interval
parameter. On Wayland, Mesa will block until a frame callback arrives.
If an application is not visible, a compositor is free to not schedule
frame callbacks (in order to save CPU time rendering something that is
entirely invisible).
Currently, starting Looking Glass from a terminal, hiding it
entirely, and sending ^C will cause Looking Glass to hang joining the
render thread until the window is made visible again.
Calling eglDestroySurface is insufficient to unblock eglSwapBuffers, as
it attempts to grab the same underlying mutex.
Instead, this commit makes it so that we pass a 0 interval to
eglSwapBuffers when running on Wayland, such that we don't block waiting
for a frame callback. This is not entirely ideal as it *does* mean
Looking Glass submits buffers while hidden, but it seems better than
hanging on exit.
It also forces opengl:vsync and egl:vsync flags to off when running on
Wayland, as they are meaningless there.
2021-01-04 02:09:55 +00:00
|
|
|
.value.x_bool = false,
|
2021-01-04 03:44:33 +00:00
|
|
|
.validator = &egl_vsync_option_validator
|
2019-05-21 05:03:59 +00:00
|
|
|
},
|
2019-05-27 08:39:21 +00:00
|
|
|
{
|
|
|
|
.module = "egl",
|
|
|
|
.name = "doubleBuffer",
|
|
|
|
.description = "Enable double buffering",
|
|
|
|
.type = OPTION_TYPE_BOOL,
|
2020-05-19 07:34:24 +00:00
|
|
|
.value.x_bool = false
|
2019-05-27 08:39:21 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
.module = "egl",
|
|
|
|
.name = "multisample",
|
|
|
|
.description = "Enable Multisampling",
|
|
|
|
.type = OPTION_TYPE_BOOL,
|
|
|
|
.value.x_bool = true
|
|
|
|
},
|
2019-05-23 06:32:18 +00:00
|
|
|
{
|
|
|
|
.module = "egl",
|
|
|
|
.name = "nvGainMax",
|
|
|
|
.description = "The maximum night vision gain",
|
|
|
|
.type = OPTION_TYPE_INT,
|
|
|
|
.value.x_int = 1
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.module = "egl",
|
|
|
|
.name = "nvGain",
|
|
|
|
.description = "The initial night vision gain at startup",
|
|
|
|
.type = OPTION_TYPE_INT,
|
|
|
|
.value.x_int = 0
|
|
|
|
},
|
2020-11-08 19:59:54 +00:00
|
|
|
{
|
|
|
|
.module = "egl",
|
|
|
|
.name = "cbMode",
|
2020-11-08 20:42:59 +00:00
|
|
|
.description = "Color Blind Mode (0 = Off, 1 = Protanope, 2 = Deuteranope, 3 = Tritanope)",
|
2020-11-08 19:59:54 +00:00
|
|
|
.type = OPTION_TYPE_INT,
|
|
|
|
.value.x_int = 0
|
|
|
|
},
|
2019-05-21 05:03:59 +00:00
|
|
|
{0}
|
|
|
|
};
|
|
|
|
|
2018-09-24 09:48:11 +00:00
|
|
|
void update_mouse_shape(struct Inst * this);
|
|
|
|
|
2021-01-14 06:05:26 +00:00
|
|
|
const char * egl_get_name(void)
|
2018-09-22 06:26:10 +00:00
|
|
|
{
|
|
|
|
return "EGL";
|
|
|
|
}
|
|
|
|
|
2021-01-14 06:05:26 +00:00
|
|
|
void egl_setup(void)
|
2019-05-21 05:03:59 +00:00
|
|
|
{
|
|
|
|
option_register(egl_options);
|
|
|
|
}
|
|
|
|
|
2018-09-22 06:26:10 +00:00
|
|
|
bool egl_create(void ** opaque, const LG_RendererParams params)
|
|
|
|
{
|
2021-01-04 04:38:50 +00:00
|
|
|
// Fail if running on Wayland so that OpenGL is used instead. Wayland-EGL
|
|
|
|
// is broken (https://github.com/gnif/LookingGlass/issues/306) and isn't
|
|
|
|
// fixable until SDL is dropped entirely. Until then, the OpenGL renderer
|
|
|
|
// "mostly works".
|
|
|
|
if (getenv("WAYLAND_DISPLAY"))
|
|
|
|
return false;
|
|
|
|
|
2020-11-29 10:43:28 +00:00
|
|
|
// check if EGL is even available
|
|
|
|
if (!eglQueryString(EGL_NO_DISPLAY, EGL_VERSION))
|
|
|
|
return false;
|
|
|
|
|
2018-09-22 06:26:10 +00:00
|
|
|
// create our local storage
|
|
|
|
*opaque = malloc(sizeof(struct Inst));
|
|
|
|
if (!*opaque)
|
|
|
|
{
|
|
|
|
DEBUG_INFO("Failed to allocate %lu bytes", sizeof(struct Inst));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
memset(*opaque, 0, sizeof(struct Inst));
|
|
|
|
|
|
|
|
// safe off parameteres and init our default option values
|
|
|
|
struct Inst * this = (struct Inst *)*opaque;
|
2019-05-21 05:03:59 +00:00
|
|
|
memcpy(&this->params, ¶ms, sizeof(LG_RendererParams));
|
|
|
|
|
2020-12-31 04:31:24 +00:00
|
|
|
this->opt.vsync = option_get_bool("egl", "vsync");
|
|
|
|
this->opt.doubleBuffer = option_get_bool("egl", "doubleBuffer");
|
2018-09-22 06:26:10 +00:00
|
|
|
|
2018-12-15 23:57:01 +00:00
|
|
|
this->translateX = 0;
|
|
|
|
this->translateY = 0;
|
|
|
|
this->scaleX = 1.0f;
|
|
|
|
this->scaleY = 1.0f;
|
|
|
|
this->screenScaleX = 1.0f;
|
|
|
|
this->screenScaleY = 1.0f;
|
2018-09-24 09:48:11 +00:00
|
|
|
|
2018-11-19 22:50:09 +00:00
|
|
|
this->font = LG_Fonts[0];
|
2019-05-23 09:58:12 +00:00
|
|
|
if (!this->font->create(&this->fontObj, NULL, 16))
|
2018-11-19 22:50:09 +00:00
|
|
|
{
|
|
|
|
DEBUG_ERROR("Failed to create a font instance");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-09-22 06:26:10 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool egl_initialize(void * opaque, Uint32 * sdlFlags)
|
|
|
|
{
|
2020-12-31 04:31:24 +00:00
|
|
|
struct Inst * this = (struct Inst *)opaque;
|
|
|
|
DEBUG_INFO("Double buffering is %s", this->opt.doubleBuffer ? "on" : "off");
|
2018-09-22 06:26:10 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void egl_deinitialize(void * opaque)
|
|
|
|
{
|
|
|
|
struct Inst * this = (struct Inst *)opaque;
|
|
|
|
|
2018-11-19 22:50:09 +00:00
|
|
|
if (this->font && this->fontObj)
|
|
|
|
this->font->destroy(this->fontObj);
|
|
|
|
|
2018-12-12 10:41:51 +00:00
|
|
|
egl_desktop_free(&this->desktop);
|
|
|
|
egl_cursor_free (&this->cursor);
|
|
|
|
egl_fps_free (&this->fps );
|
2018-12-15 13:54:37 +00:00
|
|
|
egl_splash_free (&this->splash);
|
2018-12-15 23:57:01 +00:00
|
|
|
egl_alert_free (&this->alert );
|
2018-09-24 09:48:11 +00:00
|
|
|
|
2020-05-21 01:44:56 +00:00
|
|
|
LG_LOCK_FREE(this->lock);
|
|
|
|
|
2021-01-24 08:39:00 +00:00
|
|
|
eglMakeCurrent(this->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
|
|
|
|
|
|
|
if (this->frameContext)
|
|
|
|
eglDestroyContext(this->display, this->frameContext);
|
|
|
|
|
|
|
|
if (this->context)
|
|
|
|
eglDestroyContext(this->display, this->context);
|
|
|
|
|
|
|
|
eglTerminate(this->display);
|
|
|
|
|
2018-09-22 06:26:10 +00:00
|
|
|
free(this);
|
|
|
|
}
|
|
|
|
|
2020-10-29 15:32:25 +00:00
|
|
|
bool egl_supports(void * opaque, LG_RendererSupport flag)
|
|
|
|
{
|
|
|
|
struct Inst * this = (struct Inst *)opaque;
|
|
|
|
|
|
|
|
switch(flag)
|
|
|
|
{
|
|
|
|
case LG_SUPPORTS_DMABUF:
|
|
|
|
return this->dmaSupport;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-11 04:30:44 +00:00
|
|
|
void egl_on_restart(void * opaque)
|
|
|
|
{
|
|
|
|
struct Inst * this = (struct Inst *)opaque;
|
|
|
|
|
|
|
|
eglDestroyContext(this->display, this->frameContext);
|
|
|
|
this->frameContext = NULL;
|
2020-08-11 04:45:08 +00:00
|
|
|
this->start = false;
|
2020-08-11 04:30:44 +00:00
|
|
|
}
|
|
|
|
|
2021-01-18 15:44:56 +00:00
|
|
|
static void egl_calc_mouse_size(struct Inst * this)
|
|
|
|
{
|
2021-01-19 09:36:43 +00:00
|
|
|
if (!this->formatValid)
|
|
|
|
return;
|
|
|
|
|
2021-01-18 15:44:56 +00:00
|
|
|
int w, h;
|
|
|
|
switch(this->format.rotate)
|
|
|
|
{
|
|
|
|
case LG_ROTATE_0:
|
|
|
|
case LG_ROTATE_180:
|
|
|
|
this->mouseScaleX = 2.0f / this->format.width;
|
|
|
|
this->mouseScaleY = 2.0f / this->format.height;
|
|
|
|
w = this->format.width;
|
|
|
|
h = this->format.height;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LG_ROTATE_90:
|
|
|
|
case LG_ROTATE_270:
|
|
|
|
this->mouseScaleX = 2.0f / this->format.height;
|
|
|
|
this->mouseScaleY = 2.0f / this->format.width;
|
|
|
|
w = this->format.height;
|
|
|
|
h = this->format.width;
|
|
|
|
break;
|
2021-01-19 15:06:49 +00:00
|
|
|
|
|
|
|
default:
|
|
|
|
assert(!"unreachable");
|
2021-01-18 15:44:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
switch((this->format.rotate + this->rotate) % LG_ROTATE_MAX)
|
|
|
|
{
|
|
|
|
case LG_ROTATE_0:
|
|
|
|
case LG_ROTATE_180:
|
|
|
|
egl_cursor_set_size(this->cursor,
|
|
|
|
(this->mouseWidth * (1.0f / w)) * this->scaleX,
|
|
|
|
(this->mouseHeight * (1.0f / h)) * this->scaleY
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LG_ROTATE_90:
|
|
|
|
case LG_ROTATE_270:
|
|
|
|
egl_cursor_set_size(this->cursor,
|
|
|
|
(this->mouseWidth * (1.0f / w)) * this->scaleY,
|
|
|
|
(this->mouseHeight * (1.0f / h)) * this->scaleX
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void egl_calc_mouse_state(struct Inst * this)
|
|
|
|
{
|
2021-01-19 09:36:43 +00:00
|
|
|
if (!this->formatValid)
|
|
|
|
return;
|
|
|
|
|
2021-01-18 15:44:56 +00:00
|
|
|
switch((this->format.rotate + this->rotate) % LG_ROTATE_MAX)
|
|
|
|
{
|
|
|
|
case LG_ROTATE_0:
|
|
|
|
case LG_ROTATE_180:
|
|
|
|
egl_cursor_set_state(
|
|
|
|
this->cursor,
|
|
|
|
this->cursorVisible,
|
|
|
|
(((float)this->cursorX * this->mouseScaleX) - 1.0f) * this->scaleX,
|
|
|
|
(((float)this->cursorY * this->mouseScaleY) - 1.0f) * this->scaleY
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LG_ROTATE_90:
|
|
|
|
case LG_ROTATE_270:
|
|
|
|
egl_cursor_set_state(
|
|
|
|
this->cursor,
|
|
|
|
this->cursorVisible,
|
|
|
|
(((float)this->cursorX * this->mouseScaleX) - 1.0f) * this->scaleY,
|
|
|
|
(((float)this->cursorY * this->mouseScaleY) - 1.0f) * this->scaleX
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void egl_on_resize(void * opaque, const int width, const int height,
|
|
|
|
const LG_RendererRect destRect, LG_RendererRotate rotate)
|
2018-09-22 06:26:10 +00:00
|
|
|
{
|
2018-09-24 09:48:11 +00:00
|
|
|
struct Inst * this = (struct Inst *)opaque;
|
|
|
|
|
2018-11-19 22:50:09 +00:00
|
|
|
this->width = width;
|
|
|
|
this->height = height;
|
2021-01-18 15:44:56 +00:00
|
|
|
this->rotate = rotate;
|
|
|
|
|
2018-11-19 22:50:09 +00:00
|
|
|
memcpy(&this->destRect, &destRect, sizeof(LG_RendererRect));
|
|
|
|
|
2018-09-22 06:26:10 +00:00
|
|
|
glViewport(0, 0, width, height);
|
2018-09-24 09:48:11 +00:00
|
|
|
|
2018-10-03 16:31:37 +00:00
|
|
|
if (destRect.valid)
|
|
|
|
{
|
|
|
|
this->translateX = 1.0f - (((destRect.w / 2) + destRect.x) * 2) / (float)width;
|
|
|
|
this->translateY = 1.0f - (((destRect.h / 2) + destRect.y) * 2) / (float)height;
|
|
|
|
this->scaleX = (float)destRect.w / (float)width;
|
|
|
|
this->scaleY = (float)destRect.h / (float)height;
|
|
|
|
}
|
|
|
|
|
2021-01-18 15:44:56 +00:00
|
|
|
egl_calc_mouse_size(this);
|
2018-12-15 13:54:37 +00:00
|
|
|
|
2018-12-15 23:57:01 +00:00
|
|
|
this->splashRatio = (float)width / (float)height;
|
|
|
|
this->screenScaleX = 1.0f / width;
|
|
|
|
this->screenScaleY = 1.0f / height;
|
2019-05-28 04:06:15 +00:00
|
|
|
|
2021-01-18 15:44:56 +00:00
|
|
|
egl_calc_mouse_state(this);
|
2018-09-22 06:26:10 +00:00
|
|
|
}
|
|
|
|
|
2021-01-15 01:40:59 +00:00
|
|
|
bool egl_on_mouse_shape(void * opaque, const LG_RendererCursor cursor,
|
2021-01-18 15:44:56 +00:00
|
|
|
const int width, const int height,
|
2021-01-15 01:40:59 +00:00
|
|
|
const int pitch, const uint8_t * data)
|
2018-09-22 06:26:10 +00:00
|
|
|
{
|
2018-09-24 09:48:11 +00:00
|
|
|
struct Inst * this = (struct Inst *)opaque;
|
2021-01-18 15:44:56 +00:00
|
|
|
|
|
|
|
if (!egl_cursor_set_shape(this->cursor, cursor, width, height, pitch, data))
|
2018-09-24 09:48:11 +00:00
|
|
|
{
|
2018-12-12 07:53:55 +00:00
|
|
|
DEBUG_ERROR("Failed to update the cursor shape");
|
|
|
|
return false;
|
2018-09-24 09:48:11 +00:00
|
|
|
}
|
|
|
|
|
2018-12-12 07:53:55 +00:00
|
|
|
this->mouseWidth = width;
|
|
|
|
this->mouseHeight = height;
|
2021-01-18 15:44:56 +00:00
|
|
|
egl_calc_mouse_size(this);
|
2018-09-24 09:48:11 +00:00
|
|
|
|
2018-09-22 06:26:10 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-05-28 04:06:15 +00:00
|
|
|
bool egl_on_mouse_event(void * opaque, const bool visible, const int x, const int y)
|
2018-09-22 06:26:10 +00:00
|
|
|
{
|
2018-09-24 09:48:11 +00:00
|
|
|
struct Inst * this = (struct Inst *)opaque;
|
2019-05-28 04:06:15 +00:00
|
|
|
this->cursorVisible = visible;
|
|
|
|
this->cursorX = x;
|
|
|
|
this->cursorY = y;
|
2021-01-18 15:44:56 +00:00
|
|
|
egl_calc_mouse_state(this);
|
2018-09-22 06:26:10 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-10-29 15:32:25 +00:00
|
|
|
bool egl_on_frame_format(void * opaque, const LG_RendererFormat format, bool useDMA)
|
2018-09-22 06:26:10 +00:00
|
|
|
{
|
|
|
|
struct Inst * this = (struct Inst *)opaque;
|
2020-10-12 08:43:29 +00:00
|
|
|
memcpy(&this->format, &format, sizeof(LG_RendererFormat));
|
2021-01-19 09:36:43 +00:00
|
|
|
this->formatValid = true;
|
2019-04-19 01:23:51 +00:00
|
|
|
|
2020-05-22 07:47:19 +00:00
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-12 08:43:29 +00:00
|
|
|
this->useNearest = this->width < format.width || this->height < format.height;
|
2020-10-29 15:32:25 +00:00
|
|
|
return egl_desktop_setup(this->desktop, format, useDMA);
|
2020-10-12 08:43:29 +00:00
|
|
|
}
|
|
|
|
|
2020-10-29 11:57:04 +00:00
|
|
|
bool egl_on_frame(void * opaque, const FrameBuffer * frame, int dmaFd)
|
2020-10-12 08:43:29 +00:00
|
|
|
{
|
|
|
|
struct Inst * this = (struct Inst *)opaque;
|
|
|
|
|
2020-10-29 15:32:25 +00:00
|
|
|
if (!egl_desktop_update(this->desktop, frame, dmaFd))
|
2018-12-12 10:41:51 +00:00
|
|
|
{
|
2020-05-21 01:44:56 +00:00
|
|
|
DEBUG_INFO("Failed to to update the desktop");
|
2018-12-12 10:41:51 +00:00
|
|
|
return false;
|
2018-09-23 05:48:44 +00:00
|
|
|
}
|
|
|
|
|
2020-08-11 04:45:08 +00:00
|
|
|
this->start = true;
|
2018-09-22 06:26:10 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-03-30 01:26:06 +00:00
|
|
|
void egl_on_alert(void * opaque, const LG_MsgAlert alert, const char * message, bool ** closeFlag)
|
2018-09-22 06:26:10 +00:00
|
|
|
{
|
2018-12-15 23:57:01 +00:00
|
|
|
struct Inst * this = (struct Inst *)opaque;
|
|
|
|
|
|
|
|
static const uint32_t colors[] =
|
|
|
|
{
|
|
|
|
0x0000CCCC, // LG_ALERT_INFO
|
|
|
|
0x00CC00CC, // LG_ALERT_SUCCESS
|
|
|
|
0xCC7F00CC, // LG_ALERT_WARNING
|
|
|
|
0xFF0000CC // LG_ALERT_ERROR
|
|
|
|
};
|
|
|
|
|
|
|
|
if (alert > LG_ALERT_ERROR || alert < 0)
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("Invalid alert value");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
egl_alert_set_color(this->alert, colors[alert]);
|
|
|
|
egl_alert_set_text (this->alert, message );
|
|
|
|
|
|
|
|
if (closeFlag)
|
|
|
|
{
|
|
|
|
this->useCloseFlag = true;
|
|
|
|
*closeFlag = &this->closeFlag;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
this->useCloseFlag = false;
|
|
|
|
this->alertTimeout = microtime() + ALERT_TIMEOUT;
|
|
|
|
}
|
|
|
|
|
|
|
|
this->showAlert = true;
|
2018-09-22 06:26:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool egl_render_startup(void * opaque, SDL_Window * window)
|
|
|
|
{
|
|
|
|
struct Inst * this = (struct Inst *)opaque;
|
|
|
|
|
|
|
|
SDL_SysWMinfo wminfo;
|
|
|
|
SDL_VERSION(&wminfo.version);
|
|
|
|
if (!SDL_GetWindowWMInfo(window, &wminfo))
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("SDL_GetWindowWMInfo failed");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-01-25 05:04:33 +00:00
|
|
|
egl_dynProcsInit();
|
2020-01-17 00:50:00 +00:00
|
|
|
|
2021-01-25 05:04:33 +00:00
|
|
|
EGLNativeDisplayType native;
|
|
|
|
EGLenum platform;
|
2020-01-17 00:50:00 +00:00
|
|
|
|
2019-01-01 13:04:40 +00:00
|
|
|
switch(wminfo.subsystem)
|
|
|
|
{
|
|
|
|
case SDL_SYSWM_X11:
|
2021-01-25 05:04:33 +00:00
|
|
|
native = (EGLNativeDisplayType)wminfo.info.x11.display;
|
|
|
|
platform = EGL_PLATFORM_X11_KHR;
|
2019-01-01 13:04:40 +00:00
|
|
|
this->nativeWind = (EGLNativeWindowType)wminfo.info.x11.window;
|
|
|
|
break;
|
|
|
|
|
2019-03-26 06:29:49 +00:00
|
|
|
#if defined(SDL_VIDEO_DRIVER_WAYLAND)
|
2019-01-01 13:04:40 +00:00
|
|
|
case SDL_SYSWM_WAYLAND:
|
|
|
|
{
|
|
|
|
int width, height;
|
|
|
|
SDL_GetWindowSize(window, &width, &height);
|
2021-01-25 05:04:33 +00:00
|
|
|
native = (EGLNativeDisplayType)wminfo.info.wl.display;
|
|
|
|
platform = EGL_PLATFORM_WAYLAND_KHR;
|
|
|
|
this->nativeWind = (EGLNativeWindowType)wl_egl_window_create(
|
|
|
|
wminfo.info.wl.surface, width, height);
|
2019-01-01 13:04:40 +00:00
|
|
|
break;
|
|
|
|
}
|
2019-03-26 06:29:49 +00:00
|
|
|
#endif
|
2019-01-01 13:04:40 +00:00
|
|
|
|
|
|
|
default:
|
|
|
|
DEBUG_ERROR("Unsupported subsystem");
|
|
|
|
return false;
|
|
|
|
}
|
2018-09-22 06:26:10 +00:00
|
|
|
|
2021-01-25 05:04:33 +00:00
|
|
|
const char *early_exts = eglQueryString(NULL, EGL_EXTENSIONS);
|
|
|
|
if (strstr(early_exts, "EGL_KHR_platform_base") != NULL &&
|
|
|
|
g_dynprocs.eglGetPlatformDisplay)
|
|
|
|
{
|
|
|
|
DEBUG_INFO("Using eglGetPlatformDisplay");
|
|
|
|
this->display = g_dynprocs.eglGetPlatformDisplay(platform, native, NULL);
|
|
|
|
}
|
|
|
|
else if (strstr(early_exts, "EGL_EXT_platform_base") != NULL &&
|
|
|
|
g_dynprocs.eglGetPlatformDisplayEXT)
|
|
|
|
{
|
|
|
|
DEBUG_INFO("Using eglGetPlatformDisplayEXT");
|
|
|
|
this->display = g_dynprocs.eglGetPlatformDisplayEXT(platform, native, NULL);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DEBUG_INFO("Using eglGetDisplay");
|
|
|
|
this->display = eglGetDisplay(native);
|
|
|
|
}
|
|
|
|
|
2018-09-22 06:26:10 +00:00
|
|
|
if (this->display == EGL_NO_DISPLAY)
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("eglGetDisplay failed");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-10-29 15:32:25 +00:00
|
|
|
int maj, min;
|
|
|
|
if (!eglInitialize(this->display, &maj, &min))
|
2018-09-22 06:26:10 +00:00
|
|
|
{
|
|
|
|
DEBUG_ERROR("Unable to initialize EGL");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-01-15 09:30:03 +00:00
|
|
|
int maxSamples = 1;
|
|
|
|
if (option_get_bool("egl", "multisample"))
|
|
|
|
{
|
|
|
|
if (app_getProp(LG_DS_MAX_MULTISAMPLE, &maxSamples) && maxSamples > 1)
|
|
|
|
{
|
|
|
|
if (maxSamples > 4)
|
|
|
|
maxSamples = 4;
|
|
|
|
|
|
|
|
DEBUG_INFO("Multisampling enabled, max samples: %d", maxSamples);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-22 06:26:10 +00:00
|
|
|
EGLint attr[] =
|
|
|
|
{
|
2019-05-27 08:39:21 +00:00
|
|
|
EGL_BUFFER_SIZE , 32,
|
2018-09-25 13:04:29 +00:00
|
|
|
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
|
2021-01-15 09:30:03 +00:00
|
|
|
EGL_SAMPLE_BUFFERS , maxSamples > 0 ? 1 : 0,
|
|
|
|
EGL_SAMPLES , maxSamples,
|
2018-09-22 06:26:10 +00:00
|
|
|
EGL_NONE
|
|
|
|
};
|
|
|
|
|
|
|
|
EGLint num_config;
|
|
|
|
if (!eglChooseConfig(this->display, attr, &this->configs, 1, &num_config))
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("Failed to choose config (eglError: 0x%x)", eglGetError());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-12-31 04:31:24 +00:00
|
|
|
const EGLint surfattr[] =
|
|
|
|
{
|
|
|
|
EGL_RENDER_BUFFER, this->opt.doubleBuffer ? EGL_BACK_BUFFER : EGL_SINGLE_BUFFER,
|
|
|
|
EGL_NONE
|
|
|
|
};
|
|
|
|
|
|
|
|
this->surface = eglCreateWindowSurface(this->display, this->configs, this->nativeWind, surfattr);
|
2018-09-22 06:26:10 +00:00
|
|
|
if (this->surface == EGL_NO_SURFACE)
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("Failed to create EGL surface (eglError: 0x%x)", eglGetError());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
EGLint ctxattr[] =
|
|
|
|
{
|
|
|
|
EGL_CONTEXT_CLIENT_VERSION, 2,
|
|
|
|
EGL_NONE
|
|
|
|
};
|
|
|
|
|
|
|
|
this->context = eglCreateContext(this->display, this->configs, EGL_NO_CONTEXT, ctxattr);
|
|
|
|
if (this->context == EGL_NO_CONTEXT)
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("Failed to create EGL context (eglError: 0x%x)", eglGetError());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-12-31 04:31:24 +00:00
|
|
|
EGLint rb = 0;
|
|
|
|
eglQuerySurface(this->display, this->surface, EGL_RENDER_BUFFER, &rb);
|
|
|
|
switch(rb)
|
|
|
|
{
|
|
|
|
case EGL_SINGLE_BUFFER:
|
|
|
|
DEBUG_INFO("Single buffer mode");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case EGL_BACK_BUFFER:
|
|
|
|
DEBUG_INFO("Back buffer mode");
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
DEBUG_WARN("Unknown render buffer mode: %d", rb);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2018-09-22 06:26:10 +00:00
|
|
|
eglMakeCurrent(this->display, this->surface, this->surface, this->context);
|
2020-10-29 15:32:25 +00:00
|
|
|
const char *client_exts = eglQueryString(this->display, EGL_EXTENSIONS);
|
2021-01-09 10:25:41 +00:00
|
|
|
const char *vendor = (const char *)glGetString(GL_VENDOR);
|
2020-10-29 15:32:25 +00:00
|
|
|
|
|
|
|
DEBUG_INFO("EGL : %d.%d", maj, min);
|
2021-01-09 10:25:41 +00:00
|
|
|
DEBUG_INFO("Vendor : %s", vendor);
|
2020-10-29 15:32:25 +00:00
|
|
|
DEBUG_INFO("Renderer : %s", glGetString(GL_RENDERER));
|
|
|
|
DEBUG_INFO("Version : %s", glGetString(GL_VERSION ));
|
|
|
|
DEBUG_INFO("EGL APIs : %s", eglQueryString(this->display, EGL_CLIENT_APIS));
|
|
|
|
DEBUG_INFO("Extensions: %s", client_exts);
|
2018-09-22 06:26:10 +00:00
|
|
|
|
2021-01-24 01:05:18 +00:00
|
|
|
if (g_dynprocs.glEGLImageTargetTexture2DOES)
|
|
|
|
{
|
|
|
|
if (strstr(client_exts, "EGL_EXT_image_dma_buf_import") != NULL)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* As of version 455.45.01 NVidia started advertising support for this
|
|
|
|
* feature, however even on the latest version 460.27.04 this is still
|
|
|
|
* broken and does not work, until this is fixed and we have way to detect
|
|
|
|
* this early just disable dma for all NVIDIA devices.
|
|
|
|
*
|
|
|
|
* ref: https://forums.developer.nvidia.com/t/egl-ext-image-dma-buf-import-broken-egl-bad-alloc-with-tons-of-free-ram/165552
|
|
|
|
*/
|
2021-01-24 08:19:43 +00:00
|
|
|
if (strstr(vendor, "NVIDIA") != NULL)
|
|
|
|
DEBUG_WARN("NVIDIA driver detected, ignoring broken DMA support");
|
|
|
|
else
|
2021-01-24 01:05:18 +00:00
|
|
|
this->dmaSupport = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DEBUG_INFO("glEGLImageTargetTexture2DOES unavilable, DMA support disabled");
|
2021-01-09 10:25:41 +00:00
|
|
|
}
|
2018-09-22 06:26:10 +00:00
|
|
|
|
2018-12-12 10:41:51 +00:00
|
|
|
eglSwapInterval(this->display, this->opt.vsync ? 1 : 0);
|
2018-09-23 05:48:44 +00:00
|
|
|
|
2020-10-29 15:32:25 +00:00
|
|
|
if (!egl_desktop_init(&this->desktop, this->display))
|
2018-12-12 10:41:51 +00:00
|
|
|
{
|
|
|
|
DEBUG_ERROR("Failed to initialize the desktop");
|
2018-09-22 08:00:52 +00:00
|
|
|
return false;
|
2018-12-12 10:41:51 +00:00
|
|
|
}
|
2018-09-24 09:48:11 +00:00
|
|
|
|
2018-12-12 07:53:55 +00:00
|
|
|
if (!egl_cursor_init(&this->cursor))
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("Failed to initialize the cursor");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-12-12 09:04:43 +00:00
|
|
|
if (!egl_fps_init(&this->fps, this->font, this->fontObj))
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("Failed to initialize the FPS display");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-12-15 13:54:37 +00:00
|
|
|
if (!egl_splash_init(&this->splash))
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("Failed to initialize the splash screen");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-12-15 23:57:01 +00:00
|
|
|
if (!egl_alert_init(&this->alert, this->font, this->fontObj))
|
|
|
|
{
|
|
|
|
DEBUG_ERROR("Failed to initialize the alert display");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-09-22 06:26:10 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-01-18 15:44:56 +00:00
|
|
|
bool egl_render(void * opaque, SDL_Window * window, LG_RendererRotate rotate)
|
2018-09-22 06:26:10 +00:00
|
|
|
{
|
|
|
|
struct Inst * this = (struct Inst *)opaque;
|
|
|
|
|
|
|
|
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
|
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
|
|
|
2020-08-11 04:45:08 +00:00
|
|
|
if (this->start && egl_desktop_render(this->desktop,
|
|
|
|
this->translateX, this->translateY,
|
|
|
|
this->scaleX , this->scaleY ,
|
2021-01-18 15:44:56 +00:00
|
|
|
this->useNearest,
|
|
|
|
rotate))
|
2019-06-12 12:36:00 +00:00
|
|
|
{
|
|
|
|
if (!this->waitFadeTime)
|
2020-07-19 04:29:29 +00:00
|
|
|
{
|
|
|
|
if (!this->params.quickSplash)
|
|
|
|
this->waitFadeTime = microtime() + SPLASH_FADE_TIME;
|
|
|
|
else
|
|
|
|
this->waitDone = true;
|
|
|
|
}
|
2021-01-18 15:44:56 +00:00
|
|
|
|
|
|
|
egl_cursor_render(this->cursor,
|
|
|
|
(this->format.rotate + rotate) % LG_ROTATE_MAX);
|
2019-06-12 12:36:00 +00:00
|
|
|
}
|
2018-11-19 22:50:09 +00:00
|
|
|
|
2018-12-15 13:54:37 +00:00
|
|
|
if (!this->waitDone)
|
|
|
|
{
|
2018-12-16 01:17:12 +00:00
|
|
|
float a = 1.0f;
|
2018-12-15 13:54:37 +00:00
|
|
|
if (!this->waitFadeTime)
|
|
|
|
a = 1.0f;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
uint64_t t = microtime();
|
|
|
|
if (t > this->waitFadeTime)
|
|
|
|
this->waitDone = true;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
uint64_t delta = this->waitFadeTime - t;
|
2018-12-15 23:57:01 +00:00
|
|
|
a = 1.0f / SPLASH_FADE_TIME * delta;
|
2018-12-15 13:54:37 +00:00
|
|
|
}
|
|
|
|
}
|
2019-01-01 23:36:17 +00:00
|
|
|
|
|
|
|
if (!this->waitDone)
|
|
|
|
egl_splash_render(this->splash, a, this->splashRatio);
|
2018-12-15 23:57:01 +00:00
|
|
|
}
|
2020-08-11 04:54:48 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
if (!this->start)
|
|
|
|
egl_splash_render(this->splash, 1.0f, this->splashRatio);
|
|
|
|
}
|
2018-12-15 23:57:01 +00:00
|
|
|
|
|
|
|
if (this->showAlert)
|
|
|
|
{
|
|
|
|
bool close = false;
|
|
|
|
if (this->useCloseFlag)
|
|
|
|
close = this->closeFlag;
|
|
|
|
else if (this->alertTimeout < microtime())
|
|
|
|
close = true;
|
|
|
|
|
|
|
|
if (close)
|
|
|
|
this->showAlert = false;
|
|
|
|
else
|
|
|
|
egl_alert_render(this->alert, this->screenScaleX, this->screenScaleY);
|
2018-12-15 13:54:37 +00:00
|
|
|
}
|
|
|
|
|
2018-12-15 23:57:01 +00:00
|
|
|
egl_fps_render(this->fps, this->screenScaleX, this->screenScaleY);
|
2018-09-22 06:26:10 +00:00
|
|
|
eglSwapBuffers(this->display, this->surface);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-12-12 12:59:22 +00:00
|
|
|
void egl_update_fps(void * opaque, const float avgUPS, const float avgFPS)
|
2018-11-19 18:26:51 +00:00
|
|
|
{
|
2018-11-19 22:50:09 +00:00
|
|
|
struct Inst * this = (struct Inst *)opaque;
|
|
|
|
if (!this->params.showFPS)
|
|
|
|
return;
|
|
|
|
|
2018-12-12 12:59:22 +00:00
|
|
|
egl_fps_update(this->fps, avgUPS, avgFPS);
|
2018-11-19 18:26:51 +00:00
|
|
|
}
|
|
|
|
|
2018-09-22 06:26:10 +00:00
|
|
|
struct LG_Renderer LGR_EGL =
|
|
|
|
{
|
2020-10-12 08:43:29 +00:00
|
|
|
.get_name = egl_get_name,
|
|
|
|
.setup = egl_setup,
|
|
|
|
.create = egl_create,
|
|
|
|
.initialize = egl_initialize,
|
|
|
|
.deinitialize = egl_deinitialize,
|
2020-10-29 15:32:25 +00:00
|
|
|
.supports = egl_supports,
|
2020-10-12 08:43:29 +00:00
|
|
|
.on_restart = egl_on_restart,
|
|
|
|
.on_resize = egl_on_resize,
|
|
|
|
.on_mouse_shape = egl_on_mouse_shape,
|
|
|
|
.on_mouse_event = egl_on_mouse_event,
|
|
|
|
.on_frame_format = egl_on_frame_format,
|
|
|
|
.on_frame = egl_on_frame,
|
|
|
|
.on_alert = egl_on_alert,
|
|
|
|
.render_startup = egl_render_startup,
|
|
|
|
.render = egl_render,
|
|
|
|
.update_fps = egl_update_fps
|
2020-01-17 00:50:00 +00:00
|
|
|
};
|