#include <config.h>
#if HAVE_GL
#include <FL/platform.H>
#include <FL/Fl_Image_Surface.H>
#include "../../Fl_Gl_Choice.H"
#include "Fl_Wayland_Window_Driver.H"
#include "Fl_Wayland_Graphics_Driver.H"
#include "Fl_Wayland_Gl_Window_Driver.H"
#ifdef FLTK_USE_X11
# include "../X11/Fl_X11_Gl_Window_Driver.H"
#endif
#include <wayland-egl.h>
#include <EGL/egl.h>
#include <FL/gl.h>
class Fl_Wayland_Gl_Choice : public Fl_Gl_Choice {
friend class Fl_Wayland_Gl_Window_Driver;
private:
EGLConfig egl_conf;
public:
Fl_Wayland_Gl_Choice(int m, const int *alistp, Fl_Gl_Choice *n) : Fl_Gl_Choice(m, alistp, n) {
egl_conf = 0;
}
};
struct gl_start_support { struct wl_surface *surface;
struct wl_subsurface *subsurface;
struct wl_egl_window *egl_window;
EGLSurface egl_surface;
};
static EGLConfig wld_egl_conf = NULL;
static EGLint swap_interval_ = 1;
static EGLint max_swap_interval = 1000;
static EGLint min_swap_interval = 0;
EGLDisplay Fl_Wayland_Gl_Window_Driver::egl_display = EGL_NO_DISPLAY;
Fl_Wayland_Gl_Window_Driver::Fl_Wayland_Gl_Window_Driver(Fl_Gl_Window *win) :
Fl_Gl_Window_Driver(win) {
if (egl_display == EGL_NO_DISPLAY) init();
egl_window = NULL;
egl_surface = NULL;
need_swap = false;
}
Fl_Gl_Window_Driver *Fl_Gl_Window_Driver::newGlWindowDriver(Fl_Gl_Window *w)
{
#ifdef FLTK_USE_X11
if (!Fl_Wayland_Screen_Driver::wl_display) return new Fl_X11_Gl_Window_Driver(w);
#endif
return new Fl_Wayland_Gl_Window_Driver(w);
}
void Fl_Wayland_Gl_Window_Driver::init() {
EGLint major, minor;
if (!fl_wl_display()) fl_open_display();
egl_display = eglGetDisplay((EGLNativeDisplayType) fl_wl_display());
if (egl_display == EGL_NO_DISPLAY) {
Fl::fatal("Can't create egl display\n");
}
if (eglInitialize(egl_display, &major, &minor) != EGL_TRUE) {
Fl::fatal("Can't initialise egl display\n");
}
eglBindAPI(EGL_OPENGL_API);
}
Fl_Gl_Choice *Fl_Wayland_Gl_Window_Driver::find(int m, const int *alistp)
{
m |= FL_DOUBLE;
Fl_Wayland_Gl_Choice *g = (Fl_Wayland_Gl_Choice*)Fl_Gl_Window_Driver::find_begin(
m, alistp);
if (g) return g;
EGLint n;
EGLint config_attribs[] = {
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
EGL_DEPTH_SIZE, 0, EGL_SAMPLE_BUFFERS, 0, EGL_STENCIL_SIZE, 0, EGL_ALPHA_SIZE, 0, EGL_NONE
};
if (m & FL_DEPTH) config_attribs[11] = 1;
if (m & FL_MULTISAMPLE) config_attribs[13] = 1;
if (m & FL_STENCIL) config_attribs[15] = 1;
if (m & FL_ALPHA) config_attribs[17] = (m & FL_RGB8) ? 8 : 1;
g = new Fl_Wayland_Gl_Choice(m, alistp, first);
eglChooseConfig(egl_display, config_attribs, &(g->egl_conf), 1, &n);
if (n == 0 && (m & FL_MULTISAMPLE)) {
config_attribs[13] = 0;
eglChooseConfig(egl_display, config_attribs, &(g->egl_conf), 1, &n);
}
if (n == 0) {
Fl::fatal("failed to choose an EGL config\n");
}
eglGetConfigAttrib(egl_display, g->egl_conf, EGL_MAX_SWAP_INTERVAL, &max_swap_interval);
eglGetConfigAttrib(egl_display, g->egl_conf, EGL_MIN_SWAP_INTERVAL, &min_swap_interval);
first = g;
return g;
}
GLContext Fl_Wayland_Gl_Window_Driver::create_gl_context(Fl_Window* window,
const Fl_Gl_Choice* g) {
GLContext shared_ctx = 0;
if (context_list && nContext) shared_ctx = context_list[0];
static const EGLint context_attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
GLContext ctx = (GLContext)eglCreateContext(egl_display,
((Fl_Wayland_Gl_Choice*)g)->egl_conf,
(shared_ctx ? (EGLContext)shared_ctx : EGL_NO_CONTEXT),
context_attribs);
if (ctx) {
add_context(ctx);
}
return ctx;
}
void Fl_Wayland_Gl_Window_Driver::set_gl_context(Fl_Window* w, GLContext context) {
struct wld_window *win = fl_wl_xid(w);
if (!win) return;
Fl_Wayland_Window_Driver *dr = Fl_Wayland_Window_Driver::driver(w);
EGLSurface target_egl_surface = NULL;
if (egl_surface) target_egl_surface = egl_surface;
else if (dr->gl_start_support_) target_egl_surface = dr->gl_start_support_->egl_surface;
if (!target_egl_surface) { dr->gl_start_support_ = new struct gl_start_support;
float s = Fl::screen_scale(w->screen_num());
Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver();
dr->gl_start_support_->surface =
wl_compositor_create_surface(scr_driver->wl_compositor);
dr->gl_start_support_->subsurface = wl_subcompositor_get_subsurface(
scr_driver->wl_subcompositor, dr->gl_start_support_->surface, win->wl_surface);
wl_subsurface_set_position(dr->gl_start_support_->subsurface, w->x() * s, w->y() * s);
wl_subsurface_place_above(dr->gl_start_support_->subsurface, win->wl_surface);
dr->gl_start_support_->egl_window = wl_egl_window_create(
dr->gl_start_support_->surface, w->w() * s, w->h() * s);
target_egl_surface = dr->gl_start_support_->egl_surface = eglCreateWindowSurface(
egl_display, wld_egl_conf, dr->gl_start_support_->egl_window, NULL);
}
GLContext current_context = eglGetCurrentContext();
if (context != current_context || w != cached_window) {
cached_window = w;
if (eglMakeCurrent(egl_display, target_egl_surface, target_egl_surface,
(EGLContext)context)) {
} else {
Fl::error("eglMakeCurrent() failed\n");
}
}
if (!(mode() & FL_ALPHA)) { GLfloat vals[4];
glGetFloatv(GL_COLOR_CLEAR_VALUE, vals);
if (vals[3] == 0.) glClearColor(vals[0], vals[1], vals[2], 1.);
}
}
void Fl_Wayland_Gl_Window_Driver::delete_gl_context(GLContext context) {
GLContext current_context = eglGetCurrentContext();
if (current_context == context) {
cached_window = 0;
}
if (current_context == (EGLContext)context) {
eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
}
eglDestroyContext(egl_display, (EGLContext)context);
eglDestroySurface(egl_display, egl_surface);
egl_surface = NULL;
wl_egl_window_destroy(egl_window);
egl_window = NULL;
del_context(context);
}
void Fl_Wayland_Gl_Window_Driver::make_overlay_current() {
glDrawBuffer(GL_FRONT);
}
void Fl_Wayland_Gl_Window_Driver::redraw_overlay() {
pWindow->redraw();
}
void Fl_Wayland_Gl_Window_Driver::make_current_before() {
if (!egl_window) {
struct wld_window *win = fl_wl_xid(pWindow);
struct wl_surface *surface = win->wl_surface;
int W = pWindow->pixel_w();
int H = pWindow->pixel_h();
int scale = Fl_Wayland_Window_Driver::driver(pWindow)->wld_scale();
egl_window = wl_egl_window_create(surface, (W/scale)*scale, (H/scale)*scale);
if (egl_window == EGL_NO_SURFACE) {
Fl::fatal("Can't create egl window with wl_egl_window_create()\n");
}
Fl_Wayland_Gl_Choice *g = (Fl_Wayland_Gl_Choice*)this->g();
egl_surface = eglCreateWindowSurface(egl_display, g->egl_conf, egl_window, NULL);
wl_surface_set_buffer_scale(surface, scale);
if (mode() & FL_ALPHA) wl_surface_set_opaque_region(surface, NULL);
if (pWindow->parent()) win = fl_wl_xid(pWindow->top_window());
while (wl_list_empty(&win->outputs)) wl_display_dispatch(fl_wl_display());
}
}
float Fl_Wayland_Gl_Window_Driver::pixels_per_unit()
{
int ns = pWindow->screen_num();
int wld_scale = (pWindow->shown() ?
Fl_Wayland_Window_Driver::driver(pWindow)->wld_scale() : 1);
return wld_scale * Fl::screen_driver()->scale(ns);
}
int Fl_Wayland_Gl_Window_Driver::mode_(int m, const int *a) {
mode(m | FL_DOUBLE);
return 1;
}
void Fl_Wayland_Gl_Window_Driver::surface_frame_done(void *data, struct wl_callback *cb, uint32_t time) {
Fl_Wayland_Gl_Window_Driver *gl_dr = (Fl_Wayland_Gl_Window_Driver *)data;
wl_callback_destroy(cb);
struct wld_window *window = fl_wl_xid(gl_dr->pWindow);
window->frame_cb = NULL;
if (gl_dr->need_swap) {
eglSwapBuffers(Fl_Wayland_Gl_Window_Driver::egl_display, gl_dr->egl_surface);
gl_dr->need_swap = false;
}
}
static const struct wl_callback_listener surface_frame_listener = {
.done = Fl_Wayland_Gl_Window_Driver::surface_frame_done,
};
void Fl_Wayland_Gl_Window_Driver::swap_buffers() {
if (overlay()) {
static bool overlay_buffer = true;
int wo = pWindow->pixel_w(), ho = pWindow->pixel_h();
GLint matrixmode;
GLfloat pos[4];
glGetIntegerv(GL_MATRIX_MODE, &matrixmode);
glGetFloatv(GL_CURRENT_RASTER_POSITION, pos); glMatrixMode(GL_PROJECTION); glPushMatrix();
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
glScalef(2.0f/wo, 2.0f/ho, 1.0f);
glTranslatef(-wo/2.0f, -ho/2.0f, 0.0f); glRasterPos2i(0,0); {
glReadBuffer(overlay_buffer?GL_BACK:GL_FRONT);
glDrawBuffer(overlay_buffer?GL_FRONT:GL_BACK);
overlay_buffer = ! overlay_buffer;
glCopyPixels(0, 0, wo, ho, GL_COLOR);
}
glPopMatrix(); glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(matrixmode);
glRasterPos3f(pos[0], pos[1], pos[2]); if (!overlay_buffer) return; }
if (egl_surface) {
Fl_Window *parent = pWindow->parent() ? pWindow->window() : NULL;
struct wld_window *parent_xid = parent ? fl_wl_xid(parent) : NULL;
if (parent_xid) { struct wld_window *xid = fl_wl_xid(pWindow);
if (xid->frame_cb) {
need_swap = true;
return;
}
if (!parent_xid->frame_cb) {
xid->frame_cb = wl_surface_frame(xid->wl_surface);
wl_callback_add_listener(xid->frame_cb, &surface_frame_listener, this);
}
}
eglSwapBuffers(Fl_Wayland_Gl_Window_Driver::egl_display, egl_surface);
need_swap = false;
}
}
class Fl_Wayland_Gl_Plugin : public Fl_Wayland_Plugin {
public:
Fl_Wayland_Gl_Plugin() : Fl_Wayland_Plugin(name()) { }
const char *name() FL_OVERRIDE { return "gl.wayland.fltk.org"; }
void do_swap(Fl_Window *w) FL_OVERRIDE {
Fl_Gl_Window_Driver *gldr = Fl_Gl_Window_Driver::driver(w->as_gl_window());
if (gldr->overlay() == w) gldr->swap_buffers();
}
void invalidate(Fl_Window *w) FL_OVERRIDE {
w->as_gl_window()->valid(0);
}
void terminate() FL_OVERRIDE {
if (Fl_Wayland_Gl_Window_Driver::egl_display != EGL_NO_DISPLAY) {
eglTerminate(Fl_Wayland_Gl_Window_Driver::egl_display);
}
}
void destroy(struct gl_start_support *gl_start_support_) FL_OVERRIDE {
eglDestroySurface(Fl_Wayland_Gl_Window_Driver::egl_display,
gl_start_support_->egl_surface);
wl_egl_window_destroy(gl_start_support_->egl_window);
wl_subsurface_destroy(gl_start_support_->subsurface);
wl_surface_destroy(gl_start_support_->surface);
delete gl_start_support_;
}
};
static Fl_Wayland_Gl_Plugin Gl_Overlay_Plugin;
void Fl_Wayland_Gl_Window_Driver::resize(int is_a_resize, int W, int H) {
if (!egl_window) return;
float f = Fl::screen_scale(pWindow->screen_num());
int s = Fl_Wayland_Window_Driver::driver(pWindow)->wld_scale();
W = int(W * f) * s; H = int(H * f) * s;
int W2, H2;
wl_egl_window_get_attached_size(egl_window, &W2, &H2);
if (W2 != W || H2 != H) {
struct wld_window *xid = fl_wl_xid(pWindow);
if (xid->kind == Fl_Wayland_Window_Driver::DECORATED && !xid->frame_cb) {
xid->frame_cb = wl_surface_frame(xid->wl_surface);
wl_callback_add_listener(xid->frame_cb,
Fl_Wayland_Graphics_Driver::p_surface_frame_listener, xid);
}
wl_egl_window_resize(egl_window, W, H, 0, 0);
}
}
char Fl_Wayland_Gl_Window_Driver::swap_type() {
return copy;
}
void Fl_Wayland_Gl_Window_Driver::gl_visual(Fl_Gl_Choice *c) {
Fl_Gl_Window_Driver::gl_visual(c);
wld_egl_conf = ((Fl_Wayland_Gl_Choice*)c)->egl_conf;
}
void Fl_Wayland_Gl_Window_Driver::gl_start() {
float f = Fl::screen_scale(Fl_Window::current()->screen_num());
int W = Fl_Window::current()->w() * f;
int H = Fl_Window::current()->h() * f;
int W2, H2;
Fl_Wayland_Window_Driver *dr = Fl_Wayland_Window_Driver::driver(Fl_Window::current());
wl_egl_window_get_attached_size(dr->gl_start_support_->egl_window, &W2, &H2);
if (W2 != W || H2 != H) {
wl_egl_window_resize(dr->gl_start_support_->egl_window, W, H, 0, 0);
}
glClearColor(0., 0., 0., 0.);
glClear(GL_COLOR_BUFFER_BIT);
}
void Fl_Wayland_Gl_Window_Driver::swap_interval(int interval) {
if (interval < min_swap_interval) interval = min_swap_interval;
if (interval > max_swap_interval) interval = max_swap_interval;
if (egl_display && eglSwapInterval(egl_display, interval))
swap_interval_ = interval;
}
int Fl_Wayland_Gl_Window_Driver::swap_interval() const {
return swap_interval_;
}
FL_EXPORT EGLContext fl_wl_glcontext(GLContext rc) { return (EGLContext)rc; }
#endif