2

I'm trying to using cairo from wayland client. The reference guide from https://jan.newmarch.name/Wayland/Cairo/

Unfortunately, my system doesn't have cairo-gl, and I don't want to manually compile Cairo with gl support. So I decided to directly bind the Cairo surface data to the EGL surface.

below is the entire source code.

// utils.c

#include "utils.h"

double pixel_to_pango_size(double pixel)
{
    return (pixel * 0.75) * PANGO_SCALE;
}

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wayland-client.h>
#include <wayland-egl.h>
#include <EGL/egl.h>
#include <GLES2/gl2.h>

#include "xdg-shell.h"

#include <cairo/cairo.h>
#include <pango/pangocairo.h>

#include "utils.h"

struct wl_display *display = NULL;
struct wl_compositor *compositor = NULL;
struct wl_surface *surface;
struct zxdg_shell_v6 *xdg_shell = NULL;
struct zxdg_surface_v6 *xdg_surface;
struct zxdg_toplevel_v6 *xdg_toplevel;
struct wl_egl_window *egl_window;
struct wl_region *region;

EGLDisplay egl_display;
EGLConfig egl_conf;
EGLSurface egl_surface;
EGLContext egl_context;

cairo_surface_t *cairo_surface;
cairo_device_t *cairo_device;

const char* egl_error_string(int err)
{
    switch (err) {
    case EGL_SUCCESS:
        return "EGL_SUCCESS";
    case EGL_NOT_INITIALIZED:
        return "EGL_NOT_INITIALIZED";
    case EGL_BAD_ACCESS:
        return "EGL_BAD_ACCESS";
    case EGL_BAD_ALLOC:
        return "EGL_BAD_ALLOC";
    case EGL_BAD_ATTRIBUTE:
        return "EGL_BAD_ATTRIBUTE";
    case EGL_BAD_CONTEXT:
        return "EGL_BAD_CONTEXT";
    case EGL_BAD_CONFIG:
        return "EGL_BAD_CONFIG";
    case EGL_BAD_CURRENT_SURFACE:
        return "EGL_BAD_CURRENT_SURFACE";
    case EGL_BAD_DISPLAY:
        return "EGL_BAD_DISPLAY";
    case EGL_BAD_SURFACE:
        return "EGL_BAD_SURFACE";
    case EGL_BAD_MATCH:
        return "EGL_BAD_MATCH";
    case EGL_BAD_PARAMETER:
        return "EGL_BAD_PARAMETER";
    case EGL_BAD_NATIVE_PIXMAP:
        return "EGL_BAD_NATIVE_PIXMAP";
    case EGL_BAD_NATIVE_WINDOW:
        return "EGL_BAD_NATIVE_WINDOW";
    case EGL_CONTEXT_LOST:
        return "EGL_CONTEXT_LOST";
    default:
        return "UNKNOWN ERROR!!";
    }
}

//==============
// Xdg
//==============
void xdg_toplevel_configure_handler(void *data,
        struct zxdg_toplevel_v6 *xdg_toplevel, int32_t width, int32_t height,
        struct wl_array *states)
{
    printf("Configure: %dx%d\n", width, height);
}

void xdg_toplevel_close_handler(void *data,
        struct zxdg_toplevel_v6 *xdg_toplevel)
{
    printf("Close.\n");
}

const struct zxdg_toplevel_v6_listener xdg_toplevel_listener = {
    .configure = xdg_toplevel_configure_handler,
    .close = xdg_toplevel_close_handler,
};

void xdg_surface_configure_handler(void *data,
        struct zxdg_surface_v6 *xdg_surface, uint32_t serial)
{
    fprintf(stderr, " = xdg_surface_configure_handler(). serial: %d\n",
        serial);
    zxdg_surface_v6_ack_configure(xdg_surface, serial);
}

const struct zxdg_surface_v6_listener xdg_surface_listener = {
    .configure = xdg_surface_configure_handler,
};

void xdg_shell_ping_handler(void *data, struct zxdg_shell_v6 *xdg_shell,
        uint32_t serial)
{
    zxdg_shell_v6_pong(xdg_shell, serial);
    printf("Pong!\n");
}

const struct zxdg_shell_v6_listener xdg_shell_listener = {
    .ping = xdg_shell_ping_handler,
};

//==============
// Global
//==============
static void global_registry_handler(void *data, struct wl_registry *registry,
        uint32_t id, const char *interface, uint32_t version)
{
    if (strcmp(interface, "wl_compositor") == 0) {
        fprintf(stderr, "Interface is <wl_compositor>.\n");
        compositor = wl_registry_bind(
            registry,
            id,
            &wl_compositor_interface,
            version
        );
    } else if (strcmp(interface, "zxdg_shell_v6") == 0) {
        fprintf(stderr, "Interface is <zxdg_shell_v6>.\n");
        xdg_shell = wl_registry_bind(
            registry, id, &zxdg_shell_v6_interface, 1);
    }
}

static void global_registry_remover(void *data, struct wl_registry *registry,
        uint32_t id)
{
    printf("Got a registry losing event for <%d>\n", id);
}

static const struct wl_registry_listener registry_listener = {
    global_registry_handler,
    global_registry_remover
};

//================
// Cairo / Pango
//================
static void draw_text(cairo_t *cr)
{
    PangoLayout *layout;
    PangoFontDescription *desc;

    layout = pango_cairo_create_layout(cr);

    pango_layout_set_text(layout, "おはよう!", -1);

    desc = pango_font_description_from_string("serif");
    pango_font_description_set_size(desc, pixel_to_pango_size(16));

    pango_layout_set_font_description(layout, desc);
    pango_font_description_free(desc);

    cairo_save(cr);

    cairo_set_source_rgb(cr, 1.0, 0.0, 0.0);

    pango_cairo_update_layout(cr, layout);

    // pango_layout_get_size(layout, &width, &height);
    cairo_move_to(cr, 0, 0);
    pango_cairo_show_layout(cr, layout);

    cairo_restore(cr);

    g_object_unref(layout);
}

static void init_cairo()
{
//    cairo_device = cairo_egl_device_create(egl_display, egl_context);
}

//==============
// EGL
//==============

static void create_opaque_region()
{
    fprintf(stderr, " = Begin create_opaque_region()\n");

    region = wl_compositor_create_region(compositor);
    wl_region_add(
        region,
        0,
        0,
        480,
        360
    );
    wl_surface_set_opaque_region(surface, region);

    fprintf(stderr, " = End create_opaque_region()\n");
}

static void create_window()
{
    egl_window = wl_egl_window_create(surface, 480, 360);

    if (egl_window == EGL_NO_SURFACE) {
        exit(1);
    }

    egl_surface =
        eglCreateWindowSurface(egl_display, egl_conf, egl_window, NULL);
    if (egl_surface == NULL) {
        fprintf(stderr, "Can't create EGL window surface.\n");
    }

    // Cairo
    cairo_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 480, 360);
    cairo_t *cr = cairo_create(cairo_surface);
    int err = cairo_status(cr);
    if (err != CAIRO_STATUS_SUCCESS) {
        fprintf(stderr, "Cairo error on create %s\n",
            cairo_status_to_string(err));
    }

    cairo_set_source_rgb(cr, 1.0, 1.0, 0.0);
    cairo_paint(cr);
    draw_text(cr);

    unsigned char *data = cairo_image_surface_get_data(cairo_surface);

    if (eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context)) {
        fprintf(stderr, "Made current.\n");
    } else {
        fprintf(stderr, "Made current failed!\n");
    }

    GLuint texture;
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 480, 360, 0, GL_RGBA, GL_UNSIGNED_BYTE,
        data);
    GLenum gl_error = glGetError();
    fprintf(stderr, "glGetError() returns %d\n", gl_error);

    err = eglBindTexImage(egl_display, egl_surface, EGL_BACK_BUFFER);
    int egl_error = eglGetError();
    fprintf(stderr, "eglGetError() returns %s.\n", egl_error_string(egl_error)); // It prints EGL_BAD_SURFACE
    if (err != EGL_TRUE) {
        fprintf(stderr, "Can't bind EGL tex image.\n");
        exit(1); // Program exited here
    }

    if (eglSwapBuffers(egl_display, egl_surface)) {
        fprintf(stderr, "Swapped buffers.\n");
    } else {
        fprintf(stderr, "Swapped buffers failed!\n");
    }
}

static void init_egl()
{
    EGLint major, minor, count ,n, size;
    EGLConfig *configs;
    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_ES2_BIT,
        EGL_NONE,
    };

    static const EGLint context_attribs[] = {
        EGL_CONTEXT_CLIENT_VERSION, 2,
        EGL_NONE,
    };

    egl_display = eglGetDisplay((EGLNativeDisplayType)display);
    if (egl_display == EGL_NO_DISPLAY) {
        exit(1);
    }

    if (eglInitialize(egl_display, &major, &minor) != EGL_TRUE) {
        exit(1);
    }
    printf("EGL major: %d, minor: %d.\n", major, minor);

    eglGetConfigs(egl_display, NULL, 0, &count);
    printf("EGL has %d configs.\n", count);

    configs = calloc(count, sizeof *configs);

    eglChooseConfig(egl_display, config_attribs, configs, count, &n);

    for (int i = 0; i < n; ++i) {
        eglGetConfigAttrib(
            egl_display,
            configs[i],
            EGL_BUFFER_SIZE,
            &size
        );
        printf("Buffer size for config %d is %d.\n", i, size);

        // Just choose     the first one
        egl_conf = configs[i];
        break;
    }

    egl_context = eglCreateContext(
        egl_display,
        egl_conf,
        EGL_NO_CONTEXT,
        context_attribs
    );
    if (egl_context == NULL) {
        fprintf(stderr, "Failed create EGL context.\n");
        exit(1);
    }
}

int main(int argc, char *argv[])
{
    display = wl_display_connect(NULL);

    struct wl_registry *registry = wl_display_get_registry(display);
    wl_registry_add_listener(registry, &registry_listener, NULL);

    wl_display_dispatch(display);
    wl_display_roundtrip(display);

    surface = wl_compositor_create_surface(compositor);

    zxdg_shell_v6_add_listener(xdg_shell, &xdg_shell_listener, NULL);

    xdg_surface = zxdg_shell_v6_get_xdg_surface(xdg_shell, surface);
    zxdg_surface_v6_add_listener(xdg_surface, &xdg_surface_listener, NULL);

    xdg_toplevel = zxdg_surface_v6_get_toplevel(xdg_surface);
    zxdg_toplevel_v6_add_listener(xdg_toplevel, &xdg_toplevel_listener, NULL);

    wl_surface_commit(surface);

    // Wait for the surface to be configured.
    wl_display_roundtrip(display);

    create_opaque_region();
    init_egl();
    create_window();

    wl_surface_commit(surface);

    while (wl_display_dispatch(display) != -1) {
        ;
    }

    wl_display_disconnect(display);

    return 0;
}

The problem occurs call eglBindTexImage().

I found some hint from EGL api docs,

EGL_BAD_SURFACE is generated if surface is not an EGL surface, or is not a pbuffer surface supporting texture binding.

I know the surface is not a pbuffer surface but a window surface. But I have no idea to render pbuffer surface inside a window surface.

Some example codes I searched are all using single EGL surface, not multiple.

hardboiled65
  • 161
  • 2
  • 10

0 Answers0