[wayland_egl]
parent
4062a1ec10
commit
c72e5e4769
|
|
@ -0,0 +1,389 @@
|
||||||
|
#if 0
|
||||||
|
mkdir -p build
|
||||||
|
clang -o build/demo -g wayland_egl.c -lwayland-client -lwayland-egl -lEGL
|
||||||
|
exit 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Reading From:
|
||||||
|
** (1) Wayland Docs https://wayland.freedesktop.org/docs/html/
|
||||||
|
** (2) wayland/protocol.xml \
|
||||||
|
** https://chromium.googlesource.com/external/wayland/wayland/+/refs/heads/master/protocol/wayland.xml
|
||||||
|
** (3) EGL spec https://registry.khronos.org/EGL/sdk/docs/man/
|
||||||
|
**
|
||||||
|
** (?) I cannot find any documentation for wl_egl_ except for headers
|
||||||
|
** and example usage 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 (2) 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 <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{
|
||||||
|
struct wl_display *display;
|
||||||
|
struct wl_registry *registry;
|
||||||
|
struct wl_compositor *compositor;
|
||||||
|
struct wl_surface *surface;
|
||||||
|
struct wl_region *region;
|
||||||
|
struct wl_egl_window *egl_window;
|
||||||
|
|
||||||
|
EGLDisplay egl_display;
|
||||||
|
EGLContext egl_context;
|
||||||
|
EGLSurface egl_surface;
|
||||||
|
} Ctx;
|
||||||
|
|
||||||
|
/* (1) Appendix A: wl_registry::global */
|
||||||
|
static void
|
||||||
|
wlevent_registry_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version){
|
||||||
|
printf("wlevent_registry_global: interface=%s\n", interface);
|
||||||
|
|
||||||
|
Ctx *ctx = (Ctx*)data;
|
||||||
|
|
||||||
|
/* (1) Appendix A: wl_registry::bind
|
||||||
|
** " Binds a new, client-created object to the server "
|
||||||
|
*/
|
||||||
|
if (strcmp(interface, "wl_compositor") == 0){
|
||||||
|
ctx->compositor = (struct wl_compositor*)
|
||||||
|
wl_registry_bind(registry, name, &wl_compositor_interface, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (1) Appendix A: wl_registry::global_remove */
|
||||||
|
static void
|
||||||
|
wlevent_registry_global_remove(void *data, struct wl_registry *registry, uint32_t name){}
|
||||||
|
|
||||||
|
const struct wl_registry_listener registry_listener = {
|
||||||
|
wlevent_registry_global,
|
||||||
|
wlevent_registry_global_remove,
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(){
|
||||||
|
Ctx ctx = {0};
|
||||||
|
|
||||||
|
/* (1) Appendix B: wl_display_connect
|
||||||
|
** " Connect to a Wayland display. "
|
||||||
|
*/
|
||||||
|
ctx.display = wl_display_connect(0);
|
||||||
|
if (ctx.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.display != 0){
|
||||||
|
ctx.registry = wl_display_get_registry(ctx.display);
|
||||||
|
if (ctx.registry == 0){
|
||||||
|
printf("wl_display_get_registry failed\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx.registry != 0){
|
||||||
|
/* [1] */
|
||||||
|
wl_registry_add_listener(ctx.registry, ®istry_listener, &ctx);
|
||||||
|
|
||||||
|
/* (1) Appendix B: wl_display::dispatch
|
||||||
|
** " Dispatch events on the default event queue.
|
||||||
|
** ... this function blocks until there are events to be read ... "
|
||||||
|
*/
|
||||||
|
wl_display_dispatch(ctx.display);
|
||||||
|
|
||||||
|
/* (1) Appendix B: wl_display_roundtrip
|
||||||
|
** " Block until all pending request are processed by the server "
|
||||||
|
*/
|
||||||
|
wl_display_roundtrip(ctx.display);
|
||||||
|
|
||||||
|
if (ctx.compositor == 0){
|
||||||
|
printf("failed to get wl_compositor\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int opengl_load_success = 0;
|
||||||
|
if (ctx.display != 0 && ctx.compositor != 0){
|
||||||
|
/* (3) eglGetDisplay
|
||||||
|
** " obtains the EGL display connection for the native display "
|
||||||
|
*/
|
||||||
|
ctx.egl_display = eglGetDisplay(ctx.display);
|
||||||
|
if (ctx.egl_display == 0){
|
||||||
|
printf("eglGetDisplay failed\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (3) 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (3) 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*~ NOTE:
|
||||||
|
**~ Create OpenGL context & link OpenGL procedures
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* (3) 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (3) 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (3) 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opengl_load_success){
|
||||||
|
/* (1) Appendix A: wl_compositor::create_surface
|
||||||
|
** " create new surface "
|
||||||
|
*/
|
||||||
|
ctx.surface = wl_compositor_create_surface(ctx.compositor);
|
||||||
|
if (ctx.surface == 0){
|
||||||
|
printf("wl_compositor_create_surface failed\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (1) Appendix A: wl_compositor::create_region
|
||||||
|
** " create new region "
|
||||||
|
*/
|
||||||
|
ctx.region = wl_compositor_create_region(ctx.compositor);
|
||||||
|
if (ctx.region == 0){
|
||||||
|
printf("wl_compositor_create_region failed\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx.surface != 0 && ctx.region != 0){
|
||||||
|
/* (1) Appendix A: wl_region::add
|
||||||
|
** " add rectangle to region "
|
||||||
|
*/
|
||||||
|
wl_region_add(ctx.region, 0, 0, 640, 480);
|
||||||
|
|
||||||
|
/* (1) Appendix A: wl_region::set_opaque_region
|
||||||
|
** " The opaque region is an optimization hint for the compositor
|
||||||
|
** that lets it optimize out redrawing of content behind opaque
|
||||||
|
** regions. "
|
||||||
|
*/
|
||||||
|
wl_surface_set_opaque_region(ctx.surface, ctx.region);
|
||||||
|
|
||||||
|
/* (?) */
|
||||||
|
ctx.egl_window = wl_egl_window_create(ctx.surface, 640, 480);
|
||||||
|
if (ctx.egl_window == EGL_NO_SURFACE){
|
||||||
|
printf("wl_egl_window_create failed\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx.egl_window != EGL_NO_SURFACE){
|
||||||
|
/* (3) 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (3) 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.egl_window, attributes);
|
||||||
|
if (ctx.egl_surface != EGL_NO_SURFACE){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ctx.egl_surface == EGL_NO_SURFACE){
|
||||||
|
printf("eglCreateWindowSurface failed\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (3) 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (3) eglSwapInterval
|
||||||
|
** " Specifies the minimum number of video frames that are displayed before
|
||||||
|
** a buffer swap will occur "
|
||||||
|
*/
|
||||||
|
EGLBoolean swap_interval_success = 0;
|
||||||
|
if (make_current_success2){
|
||||||
|
swap_interval_success = eglSwapInterval(ctx.egl_display, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*~ NOTE: Main loop */
|
||||||
|
int exit_loop = 0;
|
||||||
|
for (;!exit_loop;){
|
||||||
|
/* (1) Appendix B: wl_display_dispatch_pending
|
||||||
|
** " This function dispatches events on the main event queue.
|
||||||
|
** ... it doesn't block."
|
||||||
|
*/
|
||||||
|
wl_display_dispatch_pending(ctx.display);
|
||||||
|
|
||||||
|
/*~ NOTE: render */
|
||||||
|
{
|
||||||
|
glDrawBuffer(GL_BACK);
|
||||||
|
glViewport(0, 0, 640, 480);
|
||||||
|
glClearColor(0.40f, 0.90f, 0.15f, 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(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.display != 0){
|
||||||
|
wl_display_disconnect(ctx.display);
|
||||||
|
}
|
||||||
|
|
||||||
|
return(0);
|
||||||
|
}
|
||||||
|
|
||||||
13
x11_egl.c
13
x11_egl.c
|
|
@ -43,6 +43,12 @@ int main(int argc, char **argv){
|
||||||
printf("XOpenDisplay failed\n");
|
printf("XOpenDisplay failed\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* (1) /window-information/XInternAtom.html
|
||||||
|
** " returns the atom identifier associated with the specified
|
||||||
|
** atom_name string "
|
||||||
|
*/
|
||||||
|
Atom atom__WM_DELETE_WINDOW = XInternAtom(display, "WM_DELETE_WINDOW", False);
|
||||||
|
|
||||||
/* (2) eglGetPlatformDisplay
|
/* (2) eglGetPlatformDisplay
|
||||||
** " eglGetPlatformDisplay obtains an EGL display connection "
|
** " eglGetPlatformDisplay obtains an EGL display connection "
|
||||||
*/
|
*/
|
||||||
|
|
@ -131,12 +137,6 @@ int main(int argc, char **argv){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (1) /window-information/XInternAtom.html
|
|
||||||
** " returns the atom identifier associated with the specified
|
|
||||||
** atom_name string "
|
|
||||||
*/
|
|
||||||
Atom atom__WM_DELETE_WINDOW = XInternAtom(display, "WM_DELETE_WINDOW", False);
|
|
||||||
|
|
||||||
/*~ NOTE:
|
/*~ NOTE:
|
||||||
**~ Create a window and a surface for it
|
**~ Create a window and a surface for it
|
||||||
*/
|
*/
|
||||||
|
|
@ -287,6 +287,7 @@ int main(int argc, char **argv){
|
||||||
printf("XGetWindowAttributes failed\n");
|
printf("XGetWindowAttributes failed\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*~ NOTE: render */
|
||||||
if (window_attr.width > 0 && window_attr.height > 0){
|
if (window_attr.width > 0 && window_attr.height > 0){
|
||||||
glDrawBuffer(GL_BACK);
|
glDrawBuffer(GL_BACK);
|
||||||
glViewport(0, 0, window_attr.width, window_attr.height);
|
glViewport(0, 0, window_attr.width, window_attr.height);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue