[wayland_libdecor_egl]

main
Allen Webster 2026-02-20 13:05:06 -08:00
parent 3933378536
commit 197708e186
3 changed files with 654 additions and 75 deletions

576
wayland_libdecor_egl.c Executable file
View File

@ -0,0 +1,576 @@
#if 0
mkdir -p build
clang -o build/demo -g wayland_libdecor_egl.c -I/usr/include/libdecor-0 -ldecor-0 -lwayland-client -lwayland-egl -lEGL
exit 0
#endif
/*
** Reading From:
** (1) Wayland Docs https://wayland.freedesktop.org/docs/html/
** (2) XDG shell Docs https://wayland.app/protocols/xdg-shell#xdg_wm_base
** (egl) EGL spec https://registry.khronos.org/EGL/sdk/docs/man/
**
** (nodocs-wl_egl) I cannot find any documentation for wl_egl_ except for
** headers and example code.
** (nodocs-libdecor) not officially documented, deductions from reading
** headers and example code.
*/
/* [1] IMPORTANT NOTE
**
** (1) /ch04.html#sect-Protocol-Code-Generation
** " The interfaces, requests and events are defined in
** protocol/wayland.xml. This xml is used to generate the function
** prototypes that can be used by clients and compositors. "
**
** (1) /ch04.html#sect-Protocol-Basic-Principles
** " The Wayland protocol is an asynchronous object oriented protocol.
** All requests are method invocations on some object.
** ...
** The protocol is message-based. A message sent by a client to
** the server is called request. A message from the server to a
** client is called event. "
**
**~
**~ I have yet to see any place where it is *defined* how the xml
**~ is transformed into function prototypes. So I'm putting notes of
**~ my own inferences here.
**~
**~ From the client's perspective:
**~
**~ A request `R(...)` on an object interface `O` becomes the
**~ function `O_R(O *obj, ...)`.
**~ ! Sometimes the arguments for `R(...)` include an argument that
**~ doesn't appear to have a documented type (at least in (1)).
**~ We can also refer to the xml and see such arguments have
**~ the type "new_id". These seem to actually be a return value
**~ that does not appear in the generated signature as an id,
**~ but instead is translated into a typed return pointer.
**~ If the documentation does explicitly write the "new_id" type
**~ that also gets rewritten to a typed return.
**~
**~ An event `E(...)` on an object interface `O` will becomes a
**~ callback with the signature `(*)(void *data, O *obj, ...)`.
**~
**~ The collection of event callbacks on an object interface become
**~ `struct wl_O_listener` using the names `E` of the events as the
**~ field names.
**~
**~ A function for adding the listener to the object is also generated
**~ `wl_O_add_listener(O *obj, struct wl_O_listener *listener, void *data)`
**~
**~ Each object interface `O` becomes a `wl_O_interface` which (I think)
**~ is a global variable vtable representation of the interface.
**~
*/
#include <wayland-client.h>
#include <libdecor.h>
#include <wayland-egl.h>
/*~ NOTE: wayland-egl.h *before* EGL/ */
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GL/glcorearb.h>
#include <stdio.h>
#include <string.h>
// X(N:name,R:return,P:params)
#define GL_FUNCS_XLIST(X)\
X(glDrawBuffer, void, (GLenum buf)) \
X(glViewport, void, (GLint x, GLint y, GLsizei w, GLsizei h)) \
X(glClear, void, (GLbitfield mask)) \
X(glClearColor, void, (GLfloat r, GLfloat g, GLfloat b, GLfloat a))
#define X(N,R,P) R (*N)P = 0;
GL_FUNCS_XLIST(X)
#undef X
typedef struct Ctx{
/* globals */
struct wl_display *wl_display;
struct wl_registry *wl_registry;
struct wl_compositor *wl_compositor;
struct xdg_wm_base *xdg_wm_base;
struct libdecor *libdecor;
/* window */
struct wl_surface *wl_surface;
struct libdecor_frame *libdecor_frame;
struct wl_egl_window *wl_egl_window;
int configured;
int w;
int h;
int close_signal;
//struct xdg_surface *xdg_surface;
//struct xdg_toplevel *xdg_toplevel;
//struct wl_region *wl_region;
EGLDisplay egl_display;
EGLContext egl_context;
EGLSurface egl_surface;
} Ctx;
static Ctx ctx = {0};
#if 0
/* (2) xdg_wm_base::ping */
static void
wlevent__xdg_wm_base_ping(void *data, struct xdg_wm_base *xdg_wm_base,
uint32_t serial){
xdg_wm_base_pong(xdg_wm_base, serial);
}
const struct xdg_wm_base_listener xdg_wm_base_listener = {
wlevent__xdg_wm_base_ping,
};
/* (2) xdg_surface::configure
** " marks the end of a configure sequence ...
** Clients should arrange their surface for the new states, and then
** send an ack_configure request with the serial sent in this
** configure event at some point before committing the new surface. "
*/
static void
wlevent__xdg_surface_configure(void *data, struct xdg_surface *xdg_surface,
uint32_t serial){
xdg_surface_ack_configure(xdg_surface, serial);
wl_surface_commit(ctx.wl_surface);
}
const struct xdg_surface_listener xdg_surface_listener = {
wlevent__xdg_surface_configure,
};
/* (2) xdg_toplevel::configure
** " This configure event asks the client to resize its toplevel surface
** or to change its state. The configured state should not be applied
** immediately. See xdg_surface.configure for details. "
** @see:(xdg_surface::configure)
*/
static void
wlevent__xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel,
int32_t width, int32_t height,
struct wl_array *states){
if (width > 0 && height > 0){
/* (nodocs-wl_egl) */
wl_egl_window_resize(ctx.wl_egl_window, width, height, 0, 0);
}
}
/* (2) xdg_toplevel::close
** " sent by the compositor when the user wants the surface to be closed. "
*/
static void
wlevent__xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel){
ctx.close_signal = 1;
}
/* (2) xdg_toplevel::configure_bounds
** " sent [...] to communicate the bounds a window geometry size is
** recommended to constrain to "
**~ NOTE: basically "tells you the monitor size".
** @see:xdg_surface.configure
*/
static void
wlevent__xdg_toplevel_configure_bounds(void *data, struct xdg_toplevel *xdg_toplevel,
int32_t width, int32_t height){
}
/* (2) xdg_toplevel::wm_capabilities
** " advertises the capabilities supported by the compositor "
**~ NOTE: basically "tells you the monitor size".
** @see:xdg_surface.configure
*/
static void
wlevent__xdg_toplevel_wm_capabilities(void *data, struct xdg_toplevel *xdg_toplevel,
struct wl_array *capabilities){
}
const struct xdg_toplevel_listener xdg_toplevel_listener = {
wlevent__xdg_toplevel_configure,
wlevent__xdg_toplevel_close,
wlevent__xdg_toplevel_configure_bounds,
wlevent__xdg_toplevel_wm_capabilities,
};
#endif
/* (1) Appendix A: wl_registry::global
** " The event notifies the client that a global object with the given
** name is now available "
*/
static void
wlevent__wl_registry_global(void *data, struct wl_registry *registry,
uint32_t name, const char *interface,
uint32_t version){
/* (1) Appendix A: wl_registry::bind
** " Binds a new, client-created object to the server "
*/
if (strcmp(interface, "wl_compositor") == 0){
ctx.wl_compositor = (struct wl_compositor*)
wl_registry_bind(registry, name, &wl_compositor_interface, 1);
}
#if 0
else if (strcmp(interface, "xdg_wm_base") == 0){
ctx.xdg_wm_base = (struct xdg_wm_base*)
wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);
/* [1] */
xdg_wm_base_add_listener(ctx.xdg_wm_base, &xdg_wm_base_listener, 0);
}
#endif
}
/* (1) Appendix A: wl_registry::global_remove */
static void
wlevent__wl_registry_global_remove(void *data, struct wl_registry *registry,
uint32_t name){}
const struct wl_registry_listener wl_registry_listener = {
wlevent__wl_registry_global,
wlevent__wl_registry_global_remove,
};
/* (nodocs) */
static void
libdecorevent__error(struct libdecor *libdecor, enum libdecor_error error,
const char *msg){}
struct libdecor_interface libdecor_interface = {
libdecorevent__error
};
/* (nodocs) */
static void
libdecorevent__frame_configure(struct libdecor_frame *frame,
struct libdecor_configuration *config,
void *udata){
int w = ctx.w;
int h = ctx.h;
/* (nodocs) */
if (libdecor_configuration_get_content_size(config, frame, &w, &h)){
/* (nodocs) */
struct libdecor_state *state = libdecor_state_new(w, h);
/* (nodocs) */
libdecor_frame_commit(frame, state, config);
/* (nodocs) */
libdecor_state_free(state);
}
ctx.configured = 1;
ctx.w = w;
ctx.h = h;
}
/* (nodocs) */
static void
libdecorevent__frame_close(struct libdecor_frame *frame, void *udata){
ctx.close_signal = 1;
}
/* (nodocs) */
static void
libdecorevent__frame_commit(struct libdecor_frame *frame, void *udata){
wl_surface_commit(ctx.wl_surface);
}
struct libdecor_frame_interface libdecor_frame_interface = {
libdecorevent__frame_configure,
libdecorevent__frame_close,
libdecorevent__frame_commit,
};
int main(){
/*~ NOTE:
**~ initialize Wayland, Libdecor, & EGL
*/
/* (1) Appendix B: wl_display_connect
** " Connect to a Wayland display. "
*/
ctx.wl_display = wl_display_connect(0);
if (ctx.wl_display == 0){
printf("wl_display_connect failed\n");
}
/* (1) Appendix A: wl_display::get_registry
** " creates a registry object that allows the client to list
** and bind the global objects available from the compositor "
*/
if (ctx.wl_display != 0){
ctx.wl_registry = wl_display_get_registry(ctx.wl_display);
if (ctx.wl_registry == 0){
printf("wl_display_get_registry failed\n");
}
}
if (ctx.wl_registry != 0){
/* [1] */
wl_registry_add_listener(ctx.wl_registry, &wl_registry_listener, 0);
/* (1) Appendix B: wl_display::dispatch
** " Dispatch events on the default event queue. If the default
** event queue is empty, this function blocks until there are
** events to be read from the display fd. "
*/
wl_display_dispatch(ctx.wl_display);
/* (1) Appendix B: wl_display_roundtrip
** " Block until all pending request are processed by the server "
*/
wl_display_roundtrip(ctx.wl_display);
if (ctx.wl_compositor == 0){
printf("failed to get wl_compositor\n");
}
}
/* (nodocs) */
if (ctx.wl_display != 0 && ctx.wl_compositor != 0){
ctx.libdecor = libdecor_new(ctx.wl_display, &libdecor_interface);
}
int opengl_load_success = 0;
if (ctx.libdecor != 0){
/* (egl) eglGetDisplay
** " obtains the EGL display connection for the native display "
*/
ctx.egl_display = eglGetDisplay(ctx.wl_display);
if (ctx.egl_display == 0){
printf("eglGetDisplay failed\n");
}
/* (egl) eglInitialize
** " initializes* the EGL display connection obtained with eglGetDisplay "
*/
int egl_init_success = 0;
EGLint major = 0, minor = 0;
if (ctx.egl_display != 0){
egl_init_success = eglInitialize(ctx.egl_display, &major, &minor);
if (!egl_init_success){
printf("eglInitialize failed\n");
}
}
// print version
if (egl_init_success){
printf("EGL version %d.%d\n", major, minor);
if (major < 1 || (major == 1 && minor < 5)){
printf("version 1.5 or higher\n");
egl_init_success = 0;
}
}
/* (egl) eglBindAPI
** " defines the current rendering API for EGL in the thread it is
** called from "
*/
EGLBoolean bind_api_success = 0;
if (egl_init_success){
bind_api_success = eglBindAPI(EGL_OPENGL_API);
}
/* (egl) eglCreateContext
** " creates an EGL rendering context for the current rendering API
** (as set with eglBindAPI) and returns a handle to the context "
*/
if (bind_api_success){
EGLint attr[] = {
EGL_CONTEXT_MAJOR_VERSION, 3,
EGL_CONTEXT_MINOR_VERSION, 3,
EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT,
EGL_NONE,
};
ctx.egl_context = eglCreateContext(ctx.egl_display, EGL_NO_CONFIG_KHR, EGL_NO_CONTEXT, attr);
if (ctx.egl_context == EGL_NO_CONTEXT){
printf("eglCreateContext failed\n");
}
}
/* (egl) eglMakeCurrent
** " binds context to the current rendering thread "
*/
EGLBoolean make_current_success = 0;
if (ctx.egl_context != 0){
make_current_success = eglMakeCurrent(ctx.egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx.egl_context);
if (!make_current_success){
printf("eglMakeCurrent failed\n");
}
}
/* (egl) eglGetProcAddress
** " returns the address of the client API or EGL function "
*/
if (make_current_success){
opengl_load_success = 1;
#define X(N,R,P) N = (R(*)P)(eglGetProcAddress(#N)); if (N == 0) opengl_load_success = 0;
GL_FUNCS_XLIST(X)
#undef X
if (!opengl_load_success){
printf("GL procedure loading failed\n");
}
}
}
/*~ NOTE:
**~ Create a window
*/
if (opengl_load_success){
ctx.w = 640;
ctx.h = 480;
/* (1) Appendix A: wl_compositor::create_surface
** " create new surface "
*/
ctx.wl_surface = wl_compositor_create_surface(ctx.wl_compositor);
if (ctx.wl_surface == 0){
printf("wl_compositor_create_surface failed\n");
}
}
if (ctx.wl_surface != 0){
/* (nodocs-libdecor) */
ctx.libdecor_frame = libdecor_decorate(ctx.libdecor, ctx.wl_surface,
&libdecor_frame_interface, 0);
}
if (ctx.libdecor_frame != 0){
/* (nodocs-libdecor) */
libdecor_frame_set_title(ctx.libdecor_frame, "Example Window");
/* (nodocs-libdecor) */
libdecor_frame_map(ctx.libdecor_frame);
/* (nodocs-wl_egl) */
ctx.wl_egl_window = wl_egl_window_create(ctx.wl_surface, ctx.w, ctx.h);
if (ctx.wl_egl_window == EGL_NO_SURFACE){
printf("wl_egl_window_create failed\n");
}
}
if (ctx.wl_egl_window != EGL_NO_SURFACE){
/* (nodocs-wl_egl) eglChooseConfig
** " returns in configs a list of all EGL frame buffer configurations
** that match the attributes specified in attrib_list "
*/
EGLConfig configs[64];
EGLint config_cap = sizeof(configs)/sizeof(*configs);
EGLint config_count = 0;
{
EGLint attributes[] = {
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_CONFORMANT, EGL_OPENGL_BIT,
EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_DEPTH_SIZE, 24,
EGL_STENCIL_SIZE, 8,
EGL_NONE,
};
if (!eglChooseConfig(ctx.egl_display, attributes, configs, config_cap, &config_count)){
printf("eglChooseConfig failed\n");
config_count = 0;
}
}
/* (egl) eglCreateWindowSurface
** " creates an on-screen EGL window surface and returns a handle to it "
*/
{
EGLint attributes[] = {
EGL_RENDER_BUFFER, EGL_BACK_BUFFER,
EGL_NONE,
};
for (EGLint i = 0; i < config_count; i += 1){
ctx.egl_surface = eglCreateWindowSurface(ctx.egl_display, configs[i], ctx.wl_egl_window, attributes);
if (ctx.egl_surface != EGL_NO_SURFACE){
break;
}
}
if (ctx.egl_surface == EGL_NO_SURFACE){
printf("eglCreateWindowSurface failed\n");
}
}
}
/* (egl) eglMakeCurrent */
EGLBoolean make_current_success2 = 0;
if (ctx.egl_surface != EGL_NO_SURFACE){
make_current_success2 = eglMakeCurrent(ctx.egl_display, ctx.egl_surface, ctx.egl_surface, ctx.egl_context);
}
/* (egl) eglSwapInterval
** " specifies the minimum number of video frame periods per buffer swap
** for the window associated with the current context "
*/
EGLBoolean swap_interval_success = 0;
if (make_current_success2){
swap_interval_success = eglSwapInterval(ctx.egl_display, 1);
if (!swap_interval_success){
printf("eglSwapInterval failed\n");
}
}
/* (egl) eglSwapBuffers
**~ NOTE: swap before loop for libdecor_dispatch (nodocs-libdecor)
*/
EGLBoolean initial_swap_success = 0;
if (swap_interval_success){
initial_swap_success = eglSwapBuffers(ctx.egl_display, ctx.egl_surface);
if (!initial_swap_success){
printf("eglSwapBuffers failed\n");
}
}
/*~ NOTE: Main loop */
int exit_loop = 0;
if (!initial_swap_success){
exit_loop = 1;
}
for (;!exit_loop;){
/* (nodocs-libdecor) */
libdecor_dispatch(ctx.libdecor, -1);
if (ctx.close_signal){
exit_loop = 1;
}
/* (nodocs-wl_egl) */
wl_egl_window_resize(ctx.wl_egl_window, ctx.w, ctx.h, 0, 0);
/*~ NOTE: render */
{
glDrawBuffer(GL_BACK);
glViewport(0, 0, 640, 480);
glClearColor(0.40f, 0.90f, 0.15f, 1.f);
glClear(GL_COLOR_BUFFER_BIT);
}
/* (egl) eglSwapBuffers
** " back-buffered window surface, then the color buffer is copied
** (posted) to the native window associated with that surface "
*/
EGLBoolean swap_success = eglSwapBuffers(ctx.egl_display, ctx.egl_surface);
if (!swap_success){
printf("eglSwapBuffers failed\n");
}
}
/* (1) #Client-classwl__display_1a9150a7e3213a58b469a6966e60a9f108
** " Close the connection to display "
*/
if (ctx.wl_display != 0){
wl_display_disconnect(ctx.wl_display);
}
return(0);
}

View File

@ -262,8 +262,9 @@ int main(){
wl_registry_add_listener(ctx.wl_registry, &wl_registry_listener, 0);
/* (1) Appendix B: wl_display::dispatch
** " Dispatch events on the default event queue.
** ... this function blocks until there are events to be read ... "
** " Dispatch events on the default event queue. If the default
** event queue is empty, this function blocks until there are
** events to be read from the display fd. "
*/
wl_display_dispatch(ctx.wl_display);

146
x11_egl.c
View File

@ -30,7 +30,7 @@ GL_FUNCS_XLIST(X)
int main(int argc, char **argv){
/*~ NOTE:
**~ initialize X11 and EGL API's
**~ initialize X11 and EGL
*/
/* (1) /display/opening.html
@ -90,10 +90,6 @@ int main(int argc, char **argv){
bind_api_success = eglBindAPI(EGL_OPENGL_API);
}
/*~ NOTE:
**~ Create OpenGL context & link OpenGL procedures
*/
/* (2) eglCreateContext
** " creates an EGL rendering context for the current rendering API
** (as set with eglBindAPI) and returns a handle to the context "
@ -218,7 +214,7 @@ int main(int argc, char **argv){
}
}
if (surface != 0){
if (surface != EGL_NO_SURFACE){
/* (1) /ICC/client-to-window-manager/XStoreName.html
** " assigns the name passed to window_name to the specified window "
*/
@ -229,80 +225,86 @@ int main(int argc, char **argv){
**~ NOTE: This makes the window visible on the screen.
*/
XMapWindow(display, window);
}
/* (2) eglMakeCurrent */
EGLBoolean make_current_success2 = 0;
if (surface != EGL_NO_SURFACE){
make_current_success2 = eglMakeCurrent(egl_display, surface, surface, context);
/* (2) eglMakeCurrent */
EGLBoolean make_current_success2 = 0;
if (surface != EGL_NO_SURFACE){
make_current_success2 = eglMakeCurrent(egl_display, surface, surface, context);
}
/* (egl) eglSwapInterval
** " specifies the minimum number of video frame periods per buffer swap
** for the window associated with the current context "
*/
EGLBoolean swap_interval_success = 0;
if (make_current_success2){
swap_interval_success = eglSwapInterval(ctx.egl_display, 1);
if (!swap_interval_success){
printf("eglSwapInterval failed\n");
}
}
/*~ NOTE: Main loop */
int exit_loop = 0;
if (!swap_interval_success){
exit_loop = 1;
}
for (;!exit_loop;){
/* (1) /event-handling/XPending.html
** " returns the number of events that have been received from the X
** server but have not been removed from the event queue "
**~ NOTE: The docs say this returns the number of events, but it's
** easier, and possibly more reliable to just use it to check if
** there is at lesat one input.
*/
for (;XPending(display) > 0;){
/* (1) /event-handling/manipulating-event-queue/XNextEvent.html
** " copies the first event from the event queue into the specified
** XEvent structure and then removes it from the queue "
*/
XEvent event;
XNextEvent(display, &event);
/* (1) /events/structures.html
** " The XEvent structure is a union of the individual structures
** declared for each event type. Depending on the type, you should
** access members of each event by using the XEvent union. "
*/
switch (event.type){
case ClientMessage: {
Atom atom = event.xclient.data.l[0];
if (atom == atom__WM_DELETE_WINDOW){
exit_loop = 1;
}
}break;
}
}
/* (2) eglSwapInterval
** " Specifies the minimum number of video frames that are displayed before
** a buffer swap will occur "
/* (1) /window-information/XGetWindowAttributes.html
** " returns the current attributes for the specified window "
*/
EGLBoolean swap_interval_success = 0;
if (make_current_success2){
swap_interval_success = eglSwapInterval(egl_display, 1);
XWindowAttributes window_attr = {0};
if (!XGetWindowAttributes(display, window, &window_attr)){
printf("XGetWindowAttributes failed\n");
}
/*~ NOTE: Main loop */
int exit_loop = 0;
for (;!exit_loop;){
/*~ NOTE: render */
if (window_attr.width > 0 && window_attr.height > 0){
glDrawBuffer(GL_BACK);
glViewport(0, 0, window_attr.width, window_attr.height);
glClearColor(0.90f, 0.15f, 0.40f, 1.f);
glClear(GL_COLOR_BUFFER_BIT);
}
/* (1) /event-handling/XPending.html
** " returns the number of events that have been received from the X
** server but have not been removed from the event queue "
**~ NOTE: The docs say this returns the number of events, but it's
** easier, and possibly more reliable to just use it to check if
** there is at lesat one input.
/* (2) eglSwapBuffers
** " back-buffered window surface, then the color buffer is copied
** (posted) to the native window associated with that surface "
*/
for (;XPending(display) > 0;){
/* (1) /event-handling/manipulating-event-queue/XNextEvent.html
** " copies the first event from the event queue into the specified
** XEvent structure and then removes it from the queue "
*/
XEvent event;
XNextEvent(display, &event);
/* (1) /events/structures.html
** " The XEvent structure is a union of the individual structures
** declared for each event type. Depending on the type, you should
** access members of each event by using the XEvent union. "
*/
switch (event.type){
case ClientMessage: {
Atom atom = event.xclient.data.l[0];
if (atom == atom__WM_DELETE_WINDOW){
exit_loop = 1;
}
}break;
}
}
/* (1) /window-information/XGetWindowAttributes.html
** " returns the current attributes for the specified window "
*/
XWindowAttributes window_attr = {0};
if (!XGetWindowAttributes(display, window, &window_attr)){
printf("XGetWindowAttributes failed\n");
}
/*~ NOTE: render */
if (window_attr.width > 0 && window_attr.height > 0){
glDrawBuffer(GL_BACK);
glViewport(0, 0, window_attr.width, window_attr.height);
glClearColor(0.90f, 0.15f, 0.40f, 1.f);
glClear(GL_COLOR_BUFFER_BIT);
}
/* (2) eglSwapBuffers
** " back-buffered window surface, then the color buffer is copied
** (posted) to the native window associated with that surface "
*/
EGLBoolean swap_success = eglSwapBuffers(egl_display, surface);
if (!swap_success){
printf("eglSwapBuffers failed\n");
}
EGLBoolean swap_success = eglSwapBuffers(egl_display, surface);
if (!swap_success){
printf("eglSwapBuffers failed\n");
}
}