aboutsummaryrefslogtreecommitdiff
path: root/render
diff options
context:
space:
mode:
Diffstat (limited to 'render')
-rw-r--r--render/dmabuf.c10
-rw-r--r--render/egl.c609
-rw-r--r--render/glapi.txt19
-rw-r--r--render/gles2/pixel_format.c78
-rw-r--r--render/gles2/renderer.c655
-rw-r--r--render/gles2/shaders.c88
-rw-r--r--render/gles2/texture.c304
-rw-r--r--render/gles2/util.c38
-rw-r--r--render/meson.build37
-rw-r--r--render/wlr_renderer.c226
-rw-r--r--render/wlr_texture.c71
11 files changed, 2135 insertions, 0 deletions
diff --git a/render/dmabuf.c b/render/dmabuf.c
new file mode 100644
index 00000000..6b500748
--- /dev/null
+++ b/render/dmabuf.c
@@ -0,0 +1,10 @@
+#include <unistd.h>
+#include <wlr/render/dmabuf.h>
+
+void wlr_dmabuf_attributes_finish( struct wlr_dmabuf_attributes *attribs) {
+ for (int i = 0; i < attribs->n_planes; ++i) {
+ close(attribs->fd[i]);
+ attribs->fd[i] = -1;
+ }
+ attribs->n_planes = 0;
+}
diff --git a/render/egl.c b/render/egl.c
new file mode 100644
index 00000000..cc00dece
--- /dev/null
+++ b/render/egl.c
@@ -0,0 +1,609 @@
+#include <assert.h>
+#include <drm_fourcc.h>
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <wlr/render/egl.h>
+#include <wlr/util/log.h>
+#include <wlr/util/region.h>
+#include "glapi.h"
+
+static bool egl_get_config(EGLDisplay disp, EGLint *attribs, EGLConfig *out,
+ EGLint visual_id) {
+ EGLint count = 0, matched = 0, ret;
+
+ ret = eglGetConfigs(disp, NULL, 0, &count);
+ if (ret == EGL_FALSE || count == 0) {
+ wlr_log(WLR_ERROR, "eglGetConfigs returned no configs");
+ return false;
+ }
+
+ EGLConfig configs[count];
+
+ ret = eglChooseConfig(disp, attribs, configs, count, &matched);
+ if (ret == EGL_FALSE) {
+ wlr_log(WLR_ERROR, "eglChooseConfig failed");
+ return false;
+ }
+
+ for (int i = 0; i < matched; ++i) {
+ EGLint visual;
+ if (!eglGetConfigAttrib(disp, configs[i],
+ EGL_NATIVE_VISUAL_ID, &visual)) {
+ continue;
+ }
+
+ if (!visual_id || visual == visual_id) {
+ *out = configs[i];
+ return true;
+ }
+ }
+
+ wlr_log(WLR_ERROR, "no valid egl config found");
+ return false;
+}
+
+static enum wlr_log_importance egl_log_importance_to_wlr(EGLint type) {
+ switch (type) {
+ case EGL_DEBUG_MSG_CRITICAL_KHR: return WLR_ERROR;
+ case EGL_DEBUG_MSG_ERROR_KHR: return WLR_ERROR;
+ case EGL_DEBUG_MSG_WARN_KHR: return WLR_ERROR;
+ case EGL_DEBUG_MSG_INFO_KHR: return WLR_INFO;
+ default: return WLR_INFO;
+ }
+}
+
+static void egl_log(EGLenum error, const char *command, EGLint msg_type,
+ EGLLabelKHR thread, EGLLabelKHR obj, const char *msg) {
+ _wlr_log(egl_log_importance_to_wlr(msg_type), "[EGL] %s: %s", command, msg);
+}
+
+static bool check_egl_ext(const char *exts, const char *ext) {
+ size_t extlen = strlen(ext);
+ const char *end = exts + strlen(exts);
+
+ while (exts < end) {
+ if (*exts == ' ') {
+ exts++;
+ continue;
+ }
+ size_t n = strcspn(exts, " ");
+ if (n == extlen && strncmp(ext, exts, n) == 0) {
+ return true;
+ }
+ exts += n;
+ }
+ return false;
+}
+
+static void print_dmabuf_formats(struct wlr_egl *egl) {
+ /* Avoid log msg if extension is not present */
+ if (!egl->exts.image_dmabuf_import_modifiers_ext) {
+ return;
+ }
+
+ int *formats;
+ int num = wlr_egl_get_dmabuf_formats(egl, &formats);
+ if (num < 0) {
+ return;
+ }
+
+ char str_formats[num * 5 + 1];
+ for (int i = 0; i < num; i++) {
+ snprintf(&str_formats[i*5], (num - i) * 5 + 1, "%.4s ",
+ (char*)&formats[i]);
+ }
+ wlr_log(WLR_DEBUG, "Supported dmabuf buffer formats: %s", str_formats);
+ free(formats);
+}
+
+bool wlr_egl_init(struct wlr_egl *egl, EGLenum platform, void *remote_display,
+ EGLint *config_attribs, EGLint visual_id) {
+ if (!load_glapi()) {
+ return false;
+ }
+
+ if (eglDebugMessageControlKHR) {
+ static const EGLAttrib debug_attribs[] = {
+ EGL_DEBUG_MSG_CRITICAL_KHR, EGL_TRUE,
+ EGL_DEBUG_MSG_ERROR_KHR, EGL_TRUE,
+ EGL_DEBUG_MSG_WARN_KHR, EGL_TRUE,
+ EGL_DEBUG_MSG_INFO_KHR, EGL_TRUE,
+ EGL_NONE,
+ };
+ eglDebugMessageControlKHR(egl_log, debug_attribs);
+ }
+
+ if (eglBindAPI(EGL_OPENGL_ES_API) == EGL_FALSE) {
+ wlr_log(WLR_ERROR, "Failed to bind to the OpenGL ES API");
+ goto error;
+ }
+
+ if (platform == EGL_PLATFORM_SURFACELESS_MESA) {
+ assert(remote_display == NULL);
+ egl->display = eglGetPlatformDisplayEXT(platform, EGL_DEFAULT_DISPLAY, NULL);
+ } else {
+ egl->display = eglGetPlatformDisplayEXT(platform, remote_display, NULL);
+ }
+ if (egl->display == EGL_NO_DISPLAY) {
+ wlr_log(WLR_ERROR, "Failed to create EGL display");
+ goto error;
+ }
+
+ egl->platform = platform;
+
+ EGLint major, minor;
+ if (eglInitialize(egl->display, &major, &minor) == EGL_FALSE) {
+ wlr_log(WLR_ERROR, "Failed to initialize EGL");
+ goto error;
+ }
+
+ if (!egl_get_config(egl->display, config_attribs, &egl->config, visual_id)) {
+ wlr_log(WLR_ERROR, "Failed to get EGL config");
+ goto error;
+ }
+
+ egl->exts_str = eglQueryString(egl->display, EGL_EXTENSIONS);
+
+ wlr_log(WLR_INFO, "Using EGL %d.%d", (int)major, (int)minor);
+ wlr_log(WLR_INFO, "Supported EGL extensions: %s", egl->exts_str);
+ wlr_log(WLR_INFO, "EGL vendor: %s", eglQueryString(egl->display, EGL_VENDOR));
+
+ egl->exts.image_base_khr =
+ check_egl_ext(egl->exts_str, "EGL_KHR_image_base")
+ && eglCreateImageKHR && eglDestroyImageKHR;
+
+ egl->exts.buffer_age_ext =
+ check_egl_ext(egl->exts_str, "EGL_EXT_buffer_age");
+ egl->exts.swap_buffers_with_damage_ext =
+ (check_egl_ext(egl->exts_str, "EGL_EXT_swap_buffers_with_damage") &&
+ eglSwapBuffersWithDamageEXT);
+ egl->exts.swap_buffers_with_damage_khr =
+ (check_egl_ext(egl->exts_str, "EGL_KHR_swap_buffers_with_damage") &&
+ eglSwapBuffersWithDamageKHR);
+
+ egl->exts.image_dmabuf_import_ext =
+ check_egl_ext(egl->exts_str, "EGL_EXT_image_dma_buf_import");
+ egl->exts.image_dmabuf_import_modifiers_ext =
+ check_egl_ext(egl->exts_str, "EGL_EXT_image_dma_buf_import_modifiers")
+ && eglQueryDmaBufFormatsEXT && eglQueryDmaBufModifiersEXT;
+
+ egl->exts.image_dma_buf_export_mesa =
+ check_egl_ext(egl->exts_str, "EGL_MESA_image_dma_buf_export") &&
+ eglExportDMABUFImageQueryMESA && eglExportDMABUFImageMESA;
+
+ print_dmabuf_formats(egl);
+
+ egl->exts.bind_wayland_display_wl =
+ check_egl_ext(egl->exts_str, "EGL_WL_bind_wayland_display")
+ && eglBindWaylandDisplayWL && eglUnbindWaylandDisplayWL
+ && eglQueryWaylandBufferWL;
+
+ bool ext_context_priority =
+ check_egl_ext(egl->exts_str, "EGL_IMG_context_priority");
+
+ size_t atti = 0;
+ EGLint attribs[5];
+ attribs[atti++] = EGL_CONTEXT_CLIENT_VERSION;
+ attribs[atti++] = 2;
+
+ // On DRM, request a high priority context if possible
+ bool request_high_priority = ext_context_priority &&
+ platform == EGL_PLATFORM_GBM_MESA;
+
+ // Try to reschedule all of our rendering to be completed first. If it
+ // fails, it will fallback to the default priority (MEDIUM).
+ if (request_high_priority) {
+ attribs[atti++] = EGL_CONTEXT_PRIORITY_LEVEL_IMG;
+ attribs[atti++] = EGL_CONTEXT_PRIORITY_HIGH_IMG;
+ }
+
+ attribs[atti++] = EGL_NONE;
+ assert(atti <= sizeof(attribs)/sizeof(attribs[0]));
+
+ egl->context = eglCreateContext(egl->display, egl->config,
+ EGL_NO_CONTEXT, attribs);
+ if (egl->context == EGL_NO_CONTEXT) {
+ wlr_log(WLR_ERROR, "Failed to create EGL context");
+ goto error;
+ }
+
+ if (request_high_priority) {
+ EGLint priority = EGL_CONTEXT_PRIORITY_MEDIUM_IMG;
+ eglQueryContext(egl->display, egl->context,
+ EGL_CONTEXT_PRIORITY_LEVEL_IMG, &priority);
+ if (priority != EGL_CONTEXT_PRIORITY_HIGH_IMG) {
+ wlr_log(WLR_INFO, "Failed to obtain a high priority context");
+ } else {
+ wlr_log(WLR_DEBUG, "Obtained high priority context");
+ }
+ }
+
+ if (!eglMakeCurrent(egl->display, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ egl->context)) {
+ wlr_log(WLR_ERROR, "Failed to make EGL context current");
+ goto error;
+ }
+
+ return true;
+
+error:
+ eglMakeCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+ if (egl->display) {
+ eglTerminate(egl->display);
+ }
+ eglReleaseThread();
+ return false;
+}
+
+void wlr_egl_finish(struct wlr_egl *egl) {
+ if (egl == NULL) {
+ return;
+ }
+
+ eglMakeCurrent(egl->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+ if (egl->wl_display) {
+ assert(egl->exts.bind_wayland_display_wl);
+ eglUnbindWaylandDisplayWL(egl->display, egl->wl_display);
+ }
+
+ eglDestroyContext(egl->display, egl->context);
+ eglTerminate(egl->display);
+ eglReleaseThread();
+}
+
+bool wlr_egl_bind_display(struct wlr_egl *egl, struct wl_display *local_display) {
+ if (!egl->exts.bind_wayland_display_wl) {
+ return false;
+ }
+
+ if (eglBindWaylandDisplayWL(egl->display, local_display)) {
+ egl->wl_display = local_display;
+ return true;
+ }
+
+ return false;
+}
+
+bool wlr_egl_destroy_image(struct wlr_egl *egl, EGLImage image) {
+ if (!egl->exts.image_base_khr) {
+ return false;
+ }
+ if (!image) {
+ return true;
+ }
+ return eglDestroyImageKHR(egl->display, image);
+}
+
+EGLSurface wlr_egl_create_surface(struct wlr_egl *egl, void *window) {
+ assert(eglCreatePlatformWindowSurfaceEXT);
+ EGLSurface surf = eglCreatePlatformWindowSurfaceEXT(egl->display,
+ egl->config, window, NULL);
+ if (surf == EGL_NO_SURFACE) {
+ wlr_log(WLR_ERROR, "Failed to create EGL surface");
+ return EGL_NO_SURFACE;
+ }
+ return surf;
+}
+
+static int egl_get_buffer_age(struct wlr_egl *egl, EGLSurface surface) {
+ if (!egl->exts.buffer_age_ext) {
+ return -1;
+ }
+
+ EGLint buffer_age;
+ EGLBoolean ok = eglQuerySurface(egl->display, surface,
+ EGL_BUFFER_AGE_EXT, &buffer_age);
+ if (!ok) {
+ wlr_log(WLR_ERROR, "Failed to get EGL surface buffer age");
+ return -1;
+ }
+
+ return buffer_age;
+}
+
+bool wlr_egl_make_current(struct wlr_egl *egl, EGLSurface surface,
+ int *buffer_age) {
+ if (!eglMakeCurrent(egl->display, surface, surface, egl->context)) {
+ wlr_log(WLR_ERROR, "eglMakeCurrent failed");
+ return false;
+ }
+
+ if (buffer_age != NULL) {
+ *buffer_age = egl_get_buffer_age(egl, surface);
+ }
+ return true;
+}
+
+bool wlr_egl_is_current(struct wlr_egl *egl) {
+ return eglGetCurrentContext() == egl->context;
+}
+
+bool wlr_egl_swap_buffers(struct wlr_egl *egl, EGLSurface surface,
+ pixman_region32_t *damage) {
+ // Never block when swapping buffers on Wayland
+ if (egl->platform == EGL_PLATFORM_WAYLAND_EXT) {
+ eglSwapInterval(egl->display, 0);
+ }
+
+ EGLBoolean ret;
+ if (damage != NULL && (egl->exts.swap_buffers_with_damage_ext ||
+ egl->exts.swap_buffers_with_damage_khr)) {
+ EGLint width = 0, height = 0;
+ eglQuerySurface(egl->display, surface, EGL_WIDTH, &width);
+ eglQuerySurface(egl->display, surface, EGL_HEIGHT, &height);
+
+ pixman_region32_t flipped_damage;
+ pixman_region32_init(&flipped_damage);
+ wlr_region_transform(&flipped_damage, damage,
+ WL_OUTPUT_TRANSFORM_FLIPPED_180, width, height);
+
+ int nrects;
+ pixman_box32_t *rects =
+ pixman_region32_rectangles(&flipped_damage, &nrects);
+ EGLint egl_damage[4 * nrects];
+ for (int i = 0; i < nrects; ++i) {
+ egl_damage[4*i] = rects[i].x1;
+ egl_damage[4*i + 1] = rects[i].y1;
+ egl_damage[4*i + 2] = rects[i].x2 - rects[i].x1;
+ egl_damage[4*i + 3] = rects[i].y2 - rects[i].y1;
+ }
+
+ pixman_region32_fini(&flipped_damage);
+
+ if (egl->exts.swap_buffers_with_damage_ext) {
+ ret = eglSwapBuffersWithDamageEXT(egl->display, surface, egl_damage,
+ nrects);
+ } else {
+ ret = eglSwapBuffersWithDamageKHR(egl->display, surface, egl_damage,
+ nrects);
+ }
+ } else {
+ ret = eglSwapBuffers(egl->display, surface);
+ }
+
+ if (!ret) {
+ wlr_log(WLR_ERROR, "eglSwapBuffers failed");
+ return false;
+ }
+ return true;
+}
+
+EGLImageKHR wlr_egl_create_image_from_wl_drm(struct wlr_egl *egl,
+ struct wl_resource *data, EGLint *fmt, int *width, int *height,
+ bool *inverted_y) {
+ if (!egl->exts.bind_wayland_display_wl || !egl->exts.image_base_khr) {
+ return NULL;
+ }
+
+ if (!eglQueryWaylandBufferWL(egl->display, data, EGL_TEXTURE_FORMAT, fmt)) {
+ return NULL;
+ }
+
+ eglQueryWaylandBufferWL(egl->display, data, EGL_WIDTH, width);
+ eglQueryWaylandBufferWL(egl->display, data, EGL_HEIGHT, height);
+
+ EGLint _inverted_y;
+ if (eglQueryWaylandBufferWL(egl->display, data, EGL_WAYLAND_Y_INVERTED_WL,
+ &_inverted_y)) {
+ *inverted_y = !!_inverted_y;
+ } else {
+ *inverted_y = false;
+ }
+
+ const EGLint attribs[] = {
+ EGL_WAYLAND_PLANE_WL, 0,
+ EGL_NONE,
+ };
+ return eglCreateImageKHR(egl->display, egl->context, EGL_WAYLAND_BUFFER_WL,
+ data, attribs);
+}
+
+EGLImageKHR wlr_egl_create_image_from_dmabuf(struct wlr_egl *egl,
+ struct wlr_dmabuf_attributes *attributes) {
+ if (!egl->exts.image_base_khr || !egl->exts.image_dmabuf_import_ext) {
+ wlr_log(WLR_ERROR, "dmabuf import extension not present");
+ return NULL;
+ }
+
+ bool has_modifier = false;
+
+ // we assume the same way we assumed formats without the import_modifiers
+ // extension that mod_linear is supported. The special mod mod_invalid
+ // is sometimes used to signal modifier unawareness which is what we
+ // have here
+ if (attributes->modifier != DRM_FORMAT_MOD_INVALID &&
+ attributes->modifier != DRM_FORMAT_MOD_LINEAR) {
+ if (!egl->exts.image_dmabuf_import_modifiers_ext) {
+ wlr_log(WLR_ERROR, "dmabuf modifiers extension not present");
+ return NULL;
+ }
+ has_modifier = true;
+ }
+
+ unsigned int atti = 0;
+ EGLint attribs[50];
+ attribs[atti++] = EGL_WIDTH;
+ attribs[atti++] = attributes->width;
+ attribs[atti++] = EGL_HEIGHT;
+ attribs[atti++] = attributes->height;
+ attribs[atti++] = EGL_LINUX_DRM_FOURCC_EXT;
+ attribs[atti++] = attributes->format;
+
+ struct {
+ EGLint fd;
+ EGLint offset;
+ EGLint pitch;
+ EGLint mod_lo;
+ EGLint mod_hi;
+ } attr_names[WLR_DMABUF_MAX_PLANES] = {
+ {
+ EGL_DMA_BUF_PLANE0_FD_EXT,
+ EGL_DMA_BUF_PLANE0_OFFSET_EXT,
+ EGL_DMA_BUF_PLANE0_PITCH_EXT,
+ EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT,
+ EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT
+ }, {
+ EGL_DMA_BUF_PLANE1_FD_EXT,
+ EGL_DMA_BUF_PLANE1_OFFSET_EXT,
+ EGL_DMA_BUF_PLANE1_PITCH_EXT,
+ EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT,
+ EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT
+ }, {
+ EGL_DMA_BUF_PLANE2_FD_EXT,
+ EGL_DMA_BUF_PLANE2_OFFSET_EXT,
+ EGL_DMA_BUF_PLANE2_PITCH_EXT,
+ EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT,
+ EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT
+ }, {
+ EGL_DMA_BUF_PLANE3_FD_EXT,
+ EGL_DMA_BUF_PLANE3_OFFSET_EXT,
+ EGL_DMA_BUF_PLANE3_PITCH_EXT,
+ EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT,
+ EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT
+ }
+ };
+
+ for (int i=0; i < attributes->n_planes; i++) {
+ attribs[atti++] = attr_names[i].fd;
+ attribs[atti++] = attributes->fd[i];
+ attribs[atti++] = attr_names[i].offset;
+ attribs[atti++] = attributes->offset[i];
+ attribs[atti++] = attr_names[i].pitch;
+ attribs[atti++] = attributes->stride[i];
+ if (has_modifier) {
+ attribs[atti++] = attr_names[i].mod_lo;
+ attribs[atti++] = attributes->modifier & 0xFFFFFFFF;
+ attribs[atti++] = attr_names[i].mod_hi;
+ attribs[atti++] = attributes->modifier >> 32;
+ }
+ }
+ attribs[atti++] = EGL_NONE;
+ assert(atti < sizeof(attribs)/sizeof(attribs[0]));
+
+ return eglCreateImageKHR(egl->display, EGL_NO_CONTEXT,
+ EGL_LINUX_DMA_BUF_EXT, NULL, attribs);
+}
+
+int wlr_egl_get_dmabuf_formats(struct wlr_egl *egl,
+ int **formats) {
+ if (!egl->exts.image_dmabuf_import_ext) {
+ wlr_log(WLR_DEBUG, "dmabuf import extension not present");
+ return -1;
+ }
+
+ // when we only have the image_dmabuf_import extension we can't query
+ // which formats are supported. These two are on almost always
+ // supported; it's the intended way to just try to create buffers.
+ // Just a guess but better than not supporting dmabufs at all,
+ // given that the modifiers extension isn't supported everywhere.
+ if (!egl->exts.image_dmabuf_import_modifiers_ext) {
+ static const int fallback_formats[] = {
+ DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_XRGB8888,
+ };
+ static unsigned num = sizeof(fallback_formats) /
+ sizeof(fallback_formats[0]);
+
+ *formats = calloc(num, sizeof(int));
+ if (!*formats) {
+ wlr_log_errno(WLR_ERROR, "Allocation failed");
+ return -1;
+ }
+
+ memcpy(*formats, fallback_formats, num * sizeof(**formats));
+ return num;
+ }
+
+ EGLint num;
+ if (!eglQueryDmaBufFormatsEXT(egl->display, 0, NULL, &num)) {
+ wlr_log(WLR_ERROR, "failed to query number of dmabuf formats");
+ return -1;
+ }
+
+ *formats = calloc(num, sizeof(int));
+ if (*formats == NULL) {
+ wlr_log(WLR_ERROR, "Allocation failed: %s", strerror(errno));
+ return -1;
+ }
+
+ if (!eglQueryDmaBufFormatsEXT(egl->display, num, *formats, &num)) {
+ wlr_log(WLR_ERROR, "failed to query dmabuf format");
+ free(*formats);
+ return -1;
+ }
+ return num;
+}
+
+int wlr_egl_get_dmabuf_modifiers(struct wlr_egl *egl,
+ int format, uint64_t **modifiers) {
+ if (!egl->exts.image_dmabuf_import_ext) {
+ wlr_log(WLR_DEBUG, "dmabuf extension not present");
+ return -1;
+ }
+
+ if(!egl->exts.image_dmabuf_import_modifiers_ext) {
+ *modifiers = NULL;
+ return 0;
+ }
+
+ EGLint num;
+ if (!eglQueryDmaBufModifiersEXT(egl->display, format, 0,
+ NULL, NULL, &num)) {
+ wlr_log(WLR_ERROR, "failed to query dmabuf number of modifiers");
+ return -1;
+ }
+
+ *modifiers = calloc(num, sizeof(uint64_t));
+ if (*modifiers == NULL) {
+ wlr_log(WLR_ERROR, "Allocation failed: %s", strerror(errno));
+ return -1;
+ }
+
+ if (!eglQueryDmaBufModifiersEXT(egl->display, format, num,
+ *modifiers, NULL, &num)) {
+ wlr_log(WLR_ERROR, "failed to query dmabuf modifiers");
+ free(*modifiers);
+ return -1;
+ }
+ return num;
+}
+
+bool wlr_egl_export_image_to_dmabuf(struct wlr_egl *egl, EGLImageKHR image,
+ int32_t width, int32_t height, uint32_t flags,
+ struct wlr_dmabuf_attributes *attribs) {
+ memset(attribs, 0, sizeof(struct wlr_dmabuf_attributes));
+
+ if (!egl->exts.image_dma_buf_export_mesa) {
+ return false;
+ }
+
+ // Only one set of modifiers is returned for all planes
+ if (!eglExportDMABUFImageQueryMESA(egl->display, image,
+ (int *)&attribs->format, &attribs->n_planes, &attribs->modifier)) {
+ return false;
+ }
+ if (attribs->n_planes > WLR_DMABUF_MAX_PLANES) {
+ wlr_log(WLR_ERROR, "EGL returned %d planes, but only %d are supported",
+ attribs->n_planes, WLR_DMABUF_MAX_PLANES);
+ return false;
+ }
+
+ if (!eglExportDMABUFImageMESA(egl->display, image, attribs->fd,
+ (EGLint *)attribs->stride, (EGLint *)attribs->offset)) {
+ return false;
+ }
+
+ attribs->width = width;
+ attribs->height = height;
+ attribs->flags = flags;
+ return true;
+}
+
+bool wlr_egl_destroy_surface(struct wlr_egl *egl, EGLSurface surface) {
+ if (!surface) {
+ return true;
+ }
+ return eglDestroySurface(egl->display, surface);
+}
diff --git a/render/glapi.txt b/render/glapi.txt
new file mode 100644
index 00000000..b1166f27
--- /dev/null
+++ b/render/glapi.txt
@@ -0,0 +1,19 @@
+eglGetPlatformDisplayEXT
+eglCreatePlatformWindowSurfaceEXT
+-eglCreateImageKHR
+-eglDestroyImageKHR
+-eglQueryWaylandBufferWL
+-eglBindWaylandDisplayWL
+-eglUnbindWaylandDisplayWL
+-glEGLImageTargetTexture2DOES
+-eglSwapBuffersWithDamageEXT
+-eglSwapBuffersWithDamageKHR
+-eglQueryDmaBufFormatsEXT
+-eglQueryDmaBufModifiersEXT
+-eglExportDMABUFImageQueryMESA
+-eglExportDMABUFImageMESA
+-eglDebugMessageControlKHR
+-glDebugMessageCallbackKHR
+-glDebugMessageControlKHR
+-glPopDebugGroupKHR
+-glPushDebugGroupKHR
diff --git a/render/gles2/pixel_format.c b/render/gles2/pixel_format.c
new file mode 100644
index 00000000..a4b4c101
--- /dev/null
+++ b/render/gles2/pixel_format.c
@@ -0,0 +1,78 @@
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include "render/gles2.h"
+
+/*
+ * The wayland formats are little endian while the GL formats are big endian,
+ * so WL_SHM_FORMAT_ARGB8888 is actually compatible with GL_BGRA_EXT.
+ */
+static const struct wlr_gles2_pixel_format formats[] = {
+ {
+ .wl_format = WL_SHM_FORMAT_ARGB8888,
+ .depth = 32,
+ .bpp = 32,
+ .gl_format = GL_BGRA_EXT,
+ .gl_type = GL_UNSIGNED_BYTE,
+ .has_alpha = true,
+ },
+ {
+ .wl_format = WL_SHM_FORMAT_XRGB8888,
+ .depth = 24,
+ .bpp = 32,
+ .gl_format = GL_BGRA_EXT,
+ .gl_type = GL_UNSIGNED_BYTE,
+ .has_alpha = false,
+ },
+ {
+ .wl_format = WL_SHM_FORMAT_XBGR8888,
+ .depth = 24,
+ .bpp = 32,
+ .gl_format = GL_RGBA,
+ .gl_type = GL_UNSIGNED_BYTE,
+ .has_alpha = false,
+ },
+ {
+ .wl_format = WL_SHM_FORMAT_ABGR8888,
+ .depth = 32,
+ .bpp = 32,
+ .gl_format = GL_RGBA,
+ .gl_type = GL_UNSIGNED_BYTE,
+ .has_alpha = true,
+ },
+};
+
+static const enum wl_shm_format wl_formats[] = {
+ WL_SHM_FORMAT_ARGB8888,
+ WL_SHM_FORMAT_XRGB8888,
+ WL_SHM_FORMAT_ABGR8888,
+ WL_SHM_FORMAT_XBGR8888,
+};
+
+// TODO: more pixel formats
+
+const struct wlr_gles2_pixel_format *get_gles2_format_from_wl(
+ enum wl_shm_format fmt) {
+ for (size_t i = 0; i < sizeof(formats) / sizeof(*formats); ++i) {
+ if (formats[i].wl_format == fmt) {
+ return &formats[i];
+ }
+ }
+ return NULL;
+}
+
+const struct wlr_gles2_pixel_format *get_gles2_format_from_gl(
+ GLint gl_format, GLint gl_type, bool alpha) {
+ for (size_t i = 0; i < sizeof(formats) / sizeof(*formats); ++i) {
+ if (formats[i].gl_format == gl_format &&
+ formats[i].gl_type == gl_type &&
+ formats[i].has_alpha == alpha) {
+ return &formats[i];
+ }
+ }
+ return NULL;
+}
+
+const enum wl_shm_format *get_gles2_wl_formats(size_t *len) {
+ *len = sizeof(wl_formats) / sizeof(wl_formats[0]);
+ return wl_formats;
+}
diff --git a/render/gles2/renderer.c b/render/gles2/renderer.c
new file mode 100644
index 00000000..50689ad4
--- /dev/null
+++ b/render/gles2/renderer.c
@@ -0,0 +1,655 @@
+#include <assert.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <wayland-server-protocol.h>
+#include <wayland-util.h>
+#include <wlr/render/egl.h>
+#include <wlr/render/interface.h>
+#include <wlr/render/wlr_renderer.h>
+#include <wlr/types/wlr_matrix.h>
+#include <wlr/util/log.h>
+#include "glapi.h"
+#include "render/gles2.h"
+
+static const struct wlr_renderer_impl renderer_impl;
+
+static struct wlr_gles2_renderer *gles2_get_renderer(
+ struct wlr_renderer *wlr_renderer) {
+ assert(wlr_renderer->impl == &renderer_impl);
+ return (struct wlr_gles2_renderer *)wlr_renderer;
+}
+
+static struct wlr_gles2_renderer *gles2_get_renderer_in_context(
+ struct wlr_renderer *wlr_renderer) {
+ struct wlr_gles2_renderer *renderer = gles2_get_renderer(wlr_renderer);
+ assert(wlr_egl_is_current(renderer->egl));
+ return renderer;
+}
+
+static void gles2_begin(struct wlr_renderer *wlr_renderer, uint32_t width,
+ uint32_t height) {
+ struct wlr_gles2_renderer *renderer =
+ gles2_get_renderer_in_context(wlr_renderer);
+
+ PUSH_GLES2_DEBUG;
+
+ glViewport(0, 0, width, height);
+ renderer->viewport_width = width;
+ renderer->viewport_height = height;
+
+ // enable transparency
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+
+ // XXX: maybe we should save output projection and remove some of the need
+ // for users to sling matricies themselves
+
+ POP_GLES2_DEBUG;
+}
+
+static void gles2_end(struct wlr_renderer *wlr_renderer) {
+ gles2_get_renderer_in_context(wlr_renderer);
+ // no-op
+}
+
+static void gles2_clear(struct wlr_renderer *wlr_renderer,
+ const float color[static 4]) {
+ gles2_get_renderer_in_context(wlr_renderer);
+
+ PUSH_GLES2_DEBUG;
+ glClearColor(color[0], color[1], color[2], color[3]);
+ glClear(GL_COLOR_BUFFER_BIT);
+ POP_GLES2_DEBUG;
+}
+
+static void gles2_scissor(struct wlr_renderer *wlr_renderer,
+ struct wlr_box *box) {
+ struct wlr_gles2_renderer *renderer =
+ gles2_get_renderer_in_context(wlr_renderer);
+
+ PUSH_GLES2_DEBUG;
+ if (box != NULL) {
+ struct wlr_box gl_box;
+ wlr_box_transform(&gl_box, box, WL_OUTPUT_TRANSFORM_FLIPPED_180,
+ renderer->viewport_width, renderer->viewport_height);
+
+ glScissor(gl_box.x, gl_box.y, gl_box.width, gl_box.height);
+ glEnable(GL_SCISSOR_TEST);
+ } else {
+ glDisable(GL_SCISSOR_TEST);
+ }
+ POP_GLES2_DEBUG;
+}
+
+static void draw_quad(void) {
+ GLfloat verts[] = {
+ 1, 0, // top right
+ 0, 0, // top left
+ 1, 1, // bottom right
+ 0, 1, // bottom left
+ };
+ GLfloat texcoord[] = {
+ 1, 0, // top right
+ 0, 0, // top left
+ 1, 1, // bottom right
+ 0, 1, // bottom left
+ };
+
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, verts);
+ glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, texcoord);
+
+ glEnableVertexAttribArray(0);
+ glEnableVertexAttribArray(1);
+
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+
+ glDisableVertexAttribArray(0);
+ glDisableVertexAttribArray(1);
+}
+
+static bool gles2_render_texture_with_matrix(struct wlr_renderer *wlr_renderer,
+ struct wlr_texture *wlr_texture, const float matrix[static 9],
+ float alpha) {
+ struct wlr_gles2_renderer *renderer =
+ gles2_get_renderer_in_context(wlr_renderer);
+ struct wlr_gles2_texture *texture =
+ gles2_get_texture(wlr_texture);
+
+ struct wlr_gles2_tex_shader *shader = NULL;
+ GLenum target = 0;
+
+ switch (texture->type) {
+ case WLR_GLES2_TEXTURE_GLTEX:
+ case WLR_GLES2_TEXTURE_WL_DRM_GL:
+ if (texture->has_alpha) {
+ shader = &renderer->shaders.tex_rgba;
+ } else {
+ shader = &renderer->shaders.tex_rgbx;
+ }
+ target = GL_TEXTURE_2D;
+ break;
+ case WLR_GLES2_TEXTURE_WL_DRM_EXT:
+ case WLR_GLES2_TEXTURE_DMABUF:
+ shader = &renderer->shaders.tex_ext;
+ target = GL_TEXTURE_EXTERNAL_OES;
+
+ if (!renderer->exts.egl_image_external_oes) {
+ wlr_log(WLR_ERROR, "Failed to render texture: "
+ "GL_TEXTURE_EXTERNAL_OES not supported");
+ return false;
+ }
+ break;
+ }
+
+ // OpenGL ES 2 requires the glUniformMatrix3fv transpose parameter to be set
+ // to GL_FALSE
+ float transposition[9];
+ wlr_matrix_transpose(transposition, matrix);
+
+ PUSH_GLES2_DEBUG;
+
+ GLuint tex_id = texture->type == WLR_GLES2_TEXTURE_GLTEX ?
+ texture->gl_tex : texture->image_tex;
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(target, tex_id);
+
+ glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+ glUseProgram(shader->program);
+
+ glUniformMatrix3fv(shader->proj, 1, GL_FALSE, transposition);
+ glUniform1i(shader->invert_y, texture->inverted_y);
+ glUniform1i(shader->tex, 0);
+ glUniform1f(shader->alpha, alpha);
+
+ draw_quad();
+
+ POP_GLES2_DEBUG;
+ return true;
+}
+
+
+static void gles2_render_quad_with_matrix(struct wlr_renderer *wlr_renderer,
+ const float color[static 4], const float matrix[static 9]) {
+ struct wlr_gles2_renderer *renderer =
+ gles2_get_renderer_in_context(wlr_renderer);
+
+ // OpenGL ES 2 requires the glUniformMatrix3fv transpose parameter to be set
+ // to GL_FALSE
+ float transposition[9];
+ wlr_matrix_transpose(transposition, matrix);
+
+ PUSH_GLES2_DEBUG;
+ glUseProgram(renderer->shaders.quad.program);
+
+ glUniformMatrix3fv(renderer->shaders.quad.proj, 1, GL_FALSE, transposition);
+ glUniform4f(renderer->shaders.quad.color, color[0], color[1], color[2], color[3]);
+ draw_quad();
+ POP_GLES2_DEBUG;
+}
+
+static void gles2_render_ellipse_with_matrix(struct wlr_renderer *wlr_renderer,
+ const float color[static 4], const float matrix[static 9]) {
+ struct wlr_gles2_renderer *renderer =
+ gles2_get_renderer_in_context(wlr_renderer);
+
+ // OpenGL ES 2 requires the glUniformMatrix3fv transpose parameter to be set
+ // to GL_FALSE
+ float transposition[9];
+ wlr_matrix_transpose(transposition, matrix);
+
+ PUSH_GLES2_DEBUG;
+ glUseProgram(renderer->shaders.ellipse.program);
+
+ glUniformMatrix3fv(renderer->shaders.ellipse.proj, 1, GL_FALSE, transposition);
+ glUniform4f(renderer->shaders.ellipse.color, color[0], color[1], color[2], color[3]);
+ draw_quad();
+ POP_GLES2_DEBUG;
+}
+
+static const enum wl_shm_format *gles2_renderer_formats(
+ struct wlr_renderer *wlr_renderer, size_t *len) {
+ return get_gles2_wl_formats(len);
+}
+
+static bool gles2_format_supported(struct wlr_renderer *wlr_renderer,
+ enum wl_shm_format wl_fmt) {
+ return get_gles2_format_from_wl(wl_fmt) != NULL;
+}
+
+static bool gles2_resource_is_wl_drm_buffer(struct wlr_renderer *wlr_renderer,
+ struct wl_resource *resource) {
+ struct wlr_gles2_renderer *renderer = gles2_get_renderer(wlr_renderer);
+
+ if (!eglQueryWaylandBufferWL) {
+ return false;
+ }
+
+ EGLint fmt;
+ return eglQueryWaylandBufferWL(renderer->egl->display, resource,
+ EGL_TEXTURE_FORMAT, &fmt);
+}
+
+static void gles2_wl_drm_buffer_get_size(struct wlr_renderer *wlr_renderer,
+ struct wl_resource *buffer, int *width, int *height) {
+ struct wlr_gles2_renderer *renderer =
+ gles2_get_renderer(wlr_renderer);
+
+ if (!eglQueryWaylandBufferWL) {
+ return;
+ }
+
+ eglQueryWaylandBufferWL(renderer->egl->display, buffer, EGL_WIDTH, width);
+ eglQueryWaylandBufferWL(renderer->egl->display, buffer, EGL_HEIGHT, height);
+}
+
+static int gles2_get_dmabuf_formats(struct wlr_renderer *wlr_renderer,
+ int **formats) {
+ struct wlr_gles2_renderer *renderer = gles2_get_renderer(wlr_renderer);
+ return wlr_egl_get_dmabuf_formats(renderer->egl, formats);
+}
+
+static int gles2_get_dmabuf_modifiers(struct wlr_renderer *wlr_renderer,
+ int format, uint64_t **modifiers) {
+ struct wlr_gles2_renderer *renderer = gles2_get_renderer(wlr_renderer);
+ return wlr_egl_get_dmabuf_modifiers(renderer->egl, format, modifiers);
+}
+
+static enum wl_shm_format gles2_preferred_read_format(
+ struct wlr_renderer *wlr_renderer) {
+ struct wlr_gles2_renderer *renderer =
+ gles2_get_renderer_in_context(wlr_renderer);
+
+ GLint gl_format = -1, gl_type = -1;
+ PUSH_GLES2_DEBUG;
+ glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &gl_format);
+ glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &gl_type);
+ POP_GLES2_DEBUG;
+
+ EGLint alpha_size = -1;
+ eglGetConfigAttrib(renderer->egl->display, renderer->egl->config,
+ EGL_ALPHA_SIZE, &alpha_size);
+
+ const struct wlr_gles2_pixel_format *fmt =
+ get_gles2_format_from_gl(gl_format, gl_type, alpha_size > 0);
+ if (fmt != NULL) {
+ return fmt->wl_format;
+ }
+
+ if (renderer->exts.read_format_bgra_ext) {
+ return WL_SHM_FORMAT_XRGB8888;
+ }
+ return WL_SHM_FORMAT_XBGR8888;
+}
+
+static bool gles2_read_pixels(struct wlr_renderer *wlr_renderer,
+ enum wl_shm_format wl_fmt, uint32_t *flags, uint32_t stride,
+ uint32_t width, uint32_t height, uint32_t src_x, uint32_t src_y,
+ uint32_t dst_x, uint32_t dst_y, void *data) {
+ struct wlr_gles2_renderer *renderer =
+ gles2_get_renderer_in_context(wlr_renderer);
+
+ const struct wlr_gles2_pixel_format *fmt = get_gles2_format_from_wl(wl_fmt);
+ if (fmt == NULL) {
+ wlr_log(WLR_ERROR, "Cannot read pixels: unsupported pixel format");
+ return false;
+ }
+
+ if (fmt->gl_format == GL_BGRA_EXT && !renderer->exts.read_format_bgra_ext) {
+ wlr_log(WLR_ERROR,
+ "Cannot read pixels: missing GL_EXT_read_format_bgra extension");
+ return false;
+ }
+
+ PUSH_GLES2_DEBUG;
+
+ // Make sure any pending drawing is finished before we try to read it
+ glFinish();
+
+ glGetError(); // Clear the error flag
+
+ unsigned char *p = data + dst_y * stride;
+ uint32_t pack_stride = width * fmt->bpp / 8;
+ if (pack_stride == stride && dst_x == 0 && flags != NULL) {
+ // Under these particular conditions, we can read the pixels with only
+ // one glReadPixels call
+ glReadPixels(src_x, renderer->viewport_height - height - src_y,
+ width, height, fmt->gl_format, fmt->gl_type, p);
+ *flags = WLR_RENDERER_READ_PIXELS_Y_INVERT;
+ } else {
+ // Unfortunately GLES2 doesn't support GL_PACK_*, so we have to read
+ // the lines out row by row
+ for (size_t i = src_y; i < src_y + height; ++i) {
+ glReadPixels(src_x, src_y + height - i - 1, width, 1, fmt->gl_format,
+ fmt->gl_type, p + i * stride + dst_x * fmt->bpp / 8);
+ }
+ if (flags != NULL) {
+ *flags = 0;
+ }
+ }
+
+ POP_GLES2_DEBUG;
+
+ return glGetError() == GL_NO_ERROR;
+}
+
+static struct wlr_texture *gles2_texture_from_pixels(
+ struct wlr_renderer *wlr_renderer, enum wl_shm_format wl_fmt,
+ uint32_t stride, uint32_t width, uint32_t height, const void *data) {
+ struct wlr_gles2_renderer *renderer = gles2_get_renderer(wlr_renderer);
+ return wlr_gles2_texture_from_pixels(renderer->egl, wl_fmt, stride, width,
+ height, data);
+}
+
+static struct wlr_texture *gles2_texture_from_wl_drm(
+ struct wlr_renderer *wlr_renderer, struct wl_resource *data) {
+ struct wlr_gles2_renderer *renderer = gles2_get_renderer(wlr_renderer);
+ return wlr_gles2_texture_from_wl_drm(renderer->egl, data);
+}
+
+static struct wlr_texture *gles2_texture_from_dmabuf(
+ struct wlr_renderer *wlr_renderer,
+ struct wlr_dmabuf_attributes *attribs) {
+ struct wlr_gles2_renderer *renderer = gles2_get_renderer(wlr_renderer);
+ return wlr_gles2_texture_from_dmabuf(renderer->egl, attribs);
+}
+
+static void gles2_init_wl_display(struct wlr_renderer *wlr_renderer,
+ struct wl_display *wl_display) {
+ struct wlr_gles2_renderer *renderer =
+ gles2_get_renderer(wlr_renderer);
+ if (!wlr_egl_bind_display(renderer->egl, wl_display)) {
+ wlr_log(WLR_INFO, "failed to bind wl_display to EGL");
+ }
+}
+
+static void gles2_destroy(struct wlr_renderer *wlr_renderer) {
+ struct wlr_gles2_renderer *renderer = gles2_get_renderer(wlr_renderer);
+
+ wlr_egl_make_current(renderer->egl, EGL_NO_SURFACE, NULL);
+
+ PUSH_GLES2_DEBUG;
+ glDeleteProgram(renderer->shaders.quad.program);
+ glDeleteProgram(renderer->shaders.ellipse.program);
+ glDeleteProgram(renderer->shaders.tex_rgba.program);
+ glDeleteProgram(renderer->shaders.tex_rgbx.program);
+ glDeleteProgram(renderer->shaders.tex_ext.program);
+ POP_GLES2_DEBUG;
+
+ if (renderer->exts.debug_khr) {
+ glDisable(GL_DEBUG_OUTPUT_KHR);
+ glDebugMessageCallbackKHR(NULL, NULL);
+ }
+
+ free(renderer);
+}
+
+static const struct wlr_renderer_impl renderer_impl = {
+ .destroy = gles2_destroy,
+ .begin = gles2_begin,
+ .end = gles2_end,
+ .clear = gles2_clear,
+ .scissor = gles2_scissor,
+ .render_texture_with_matrix = gles2_render_texture_with_matrix,
+ .render_quad_with_matrix = gles2_render_quad_with_matrix,
+ .render_ellipse_with_matrix = gles2_render_ellipse_with_matrix,
+ .formats = gles2_renderer_formats,
+ .format_supported = gles2_format_supported,
+ .resource_is_wl_drm_buffer = gles2_resource_is_wl_drm_buffer,
+ .wl_drm_buffer_get_size = gles2_wl_drm_buffer_get_size,
+ .get_dmabuf_formats = gles2_get_dmabuf_formats,
+ .get_dmabuf_modifiers = gles2_get_dmabuf_modifiers,
+ .preferred_read_format = gles2_preferred_read_format,
+ .read_pixels = gles2_read_pixels,
+ .texture_from_pixels = gles2_texture_from_pixels,
+ .texture_from_wl_drm = gles2_texture_from_wl_drm,
+ .texture_from_dmabuf = gles2_texture_from_dmabuf,
+ .init_wl_display = gles2_init_wl_display,
+};
+
+void push_gles2_marker(const char *file, const char *func) {
+ if (!glPushDebugGroupKHR) {
+ return;
+ }
+
+ int len = snprintf(NULL, 0, "%s:%s", file, func) + 1;
+ char str[len];
+ snprintf(str, len, "%s:%s", file, func);
+ glPushDebugGroupKHR(GL_DEBUG_SOURCE_APPLICATION_KHR, 1, -1, str);
+}
+
+void pop_gles2_marker(void) {
+ if (glPopDebugGroupKHR) {
+ glPopDebugGroupKHR();
+ }
+}
+
+static enum wlr_log_importance gles2_log_importance_to_wlr(GLenum type) {
+ switch (type) {
+ case GL_DEBUG_TYPE_ERROR_KHR: return WLR_ERROR;
+ case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_KHR: return WLR_DEBUG;
+ case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_KHR: return WLR_ERROR;
+ case GL_DEBUG_TYPE_PORTABILITY_KHR: return WLR_DEBUG;
+ case GL_DEBUG_TYPE_PERFORMANCE_KHR: return WLR_DEBUG;
+ case GL_DEBUG_TYPE_OTHER_KHR: return WLR_DEBUG;
+ case GL_DEBUG_TYPE_MARKER_KHR: return WLR_DEBUG;
+ case GL_DEBUG_TYPE_PUSH_GROUP_KHR: return WLR_DEBUG;
+ case GL_DEBUG_TYPE_POP_GROUP_KHR: return WLR_DEBUG;
+ default: return WLR_DEBUG;
+ }
+}
+
+static void gles2_log(GLenum src, GLenum type, GLuint id, GLenum severity,
+ GLsizei len, const GLchar *msg, const void *user) {
+ _wlr_log(gles2_log_importance_to_wlr(type), "[GLES2] %s", msg);
+}
+
+static GLuint compile_shader(GLuint type, const GLchar *src) {
+ PUSH_GLES2_DEBUG;
+
+ GLuint shader = glCreateShader(type);
+ glShaderSource(shader, 1, &src, NULL);
+ glCompileShader(shader);
+
+ GLint ok;
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);
+ if (ok == GL_FALSE) {
+ glDeleteShader(shader);
+ shader = 0;
+ }
+
+ POP_GLES2_DEBUG;
+ return shader;
+}
+
+static GLuint link_program(const GLchar *vert_src, const GLchar *frag_src) {
+ PUSH_GLES2_DEBUG;
+
+ GLuint vert = compile_shader(GL_VERTEX_SHADER, vert_src);
+ if (!vert) {
+ goto error;
+ }
+
+ GLuint frag = compile_shader(GL_FRAGMENT_SHADER, frag_src);
+ if (!frag) {
+ glDeleteShader(vert);
+ goto error;
+ }
+
+ GLuint prog = glCreateProgram();
+ glAttachShader(prog, vert);
+ glAttachShader(prog, frag);
+ glLinkProgram(prog);
+
+ glDetachShader(prog, vert);
+ glDetachShader(prog, frag);
+ glDeleteShader(vert);
+ glDeleteShader(frag);
+
+ GLint ok;
+ glGetProgramiv(prog, GL_LINK_STATUS, &ok);
+ if (ok == GL_FALSE) {
+ glDeleteProgram(prog);
+ goto error;
+ }
+
+ POP_GLES2_DEBUG;
+ return prog;
+
+error:
+ POP_GLES2_DEBUG;
+ return 0;
+}
+
+static bool check_gl_ext(const char *exts, const char *ext) {
+ size_t extlen = strlen(ext);
+ const char *end = exts + strlen(exts);
+
+ while (exts < end) {
+ if (exts[0] == ' ') {
+ exts++;
+ continue;
+ }
+ size_t n = strcspn(exts, " ");
+ if (n == extlen && strncmp(ext, exts, n) == 0) {
+ return true;
+ }
+ exts += n;
+ }
+ return false;
+}
+
+extern const GLchar quad_vertex_src[];
+extern const GLchar quad_fragment_src[];
+extern const GLchar ellipse_fragment_src[];
+extern const GLchar tex_vertex_src[];
+extern const GLchar tex_fragment_src_rgba[];
+extern const GLchar tex_fragment_src_rgbx[];
+extern const GLchar tex_fragment_src_external[];
+
+struct wlr_renderer *wlr_gles2_renderer_create(struct wlr_egl *egl) {
+ if (!load_glapi()) {
+ return NULL;
+ }
+
+ struct wlr_gles2_renderer *renderer =
+ calloc(1, sizeof(struct wlr_gles2_renderer));
+ if (renderer == NULL) {
+ return NULL;
+ }
+ wlr_renderer_init(&renderer->wlr_renderer, &renderer_impl);
+
+ renderer->egl = egl;
+ if (!wlr_egl_make_current(renderer->egl, EGL_NO_SURFACE, NULL)) {
+ free(renderer);
+ return NULL;
+ }
+
+ renderer->exts_str = (const char *)glGetString(GL_EXTENSIONS);
+ wlr_log(WLR_INFO, "Using %s", glGetString(GL_VERSION));
+ wlr_log(WLR_INFO, "GL vendor: %s", glGetString(GL_VENDOR));
+ wlr_log(WLR_INFO, "Supported GLES2 extensions: %s", renderer->exts_str);
+
+ if (!check_gl_ext(renderer->exts_str, "GL_EXT_texture_format_BGRA8888")) {
+ wlr_log(WLR_ERROR, "BGRA8888 format not supported by GLES2");
+ free(renderer);
+ return NULL;
+ }
+
+ renderer->exts.read_format_bgra_ext =
+ check_gl_ext(renderer->exts_str, "GL_EXT_read_format_bgra");
+ renderer->exts.debug_khr =
+ check_gl_ext(renderer->exts_str, "GL_KHR_debug") &&
+ glDebugMessageCallbackKHR && glDebugMessageControlKHR;
+ renderer->exts.egl_image_external_oes =
+ check_gl_ext(renderer->exts_str, "GL_OES_EGL_image_external") &&
+ glEGLImageTargetTexture2DOES;
+
+ if (renderer->exts.debug_khr) {
+ glEnable(GL_DEBUG_OUTPUT_KHR);
+ glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR);
+ glDebugMessageCallbackKHR(gles2_log, NULL);
+
+ // Silence unwanted message types
+ glDebugMessageControlKHR(GL_DONT_CARE, GL_DEBUG_TYPE_POP_GROUP_KHR,
+ GL_DONT_CARE, 0, NULL, GL_FALSE);
+ glDebugMessageControlKHR(GL_DONT_CARE, GL_DEBUG_TYPE_PUSH_GROUP_KHR,
+ GL_DONT_CARE, 0, NULL, GL_FALSE);
+ }
+
+ PUSH_GLES2_DEBUG;
+
+ GLuint prog;
+ renderer->shaders.quad.program = prog =
+ link_program(quad_vertex_src, quad_fragment_src);
+ if (!renderer->shaders.quad.program) {
+ goto error;
+ }
+ renderer->shaders.quad.proj = glGetUniformLocation(prog, "proj");
+ renderer->shaders.quad.color = glGetUniformLocation(prog, "color");
+
+ renderer->shaders.ellipse.program = prog =
+ link_program(quad_vertex_src, ellipse_fragment_src);
+ if (!renderer->shaders.ellipse.program) {
+ goto error;
+ }
+ renderer->shaders.ellipse.proj = glGetUniformLocation(prog, "proj");
+ renderer->shaders.ellipse.color = glGetUniformLocation(prog, "color");
+
+ renderer->shaders.tex_rgba.program = prog =
+ link_program(tex_vertex_src, tex_fragment_src_rgba);
+ if (!renderer->shaders.tex_rgba.program) {
+ goto error;
+ }
+ renderer->shaders.tex_rgba.proj = glGetUniformLocation(prog, "proj");
+ renderer->shaders.tex_rgba.invert_y = glGetUniformLocation(prog, "invert_y");
+ renderer->shaders.tex_rgba.tex = glGetUniformLocation(prog, "tex");
+ renderer->shaders.tex_rgba.alpha = glGetUniformLocation(prog, "alpha");
+
+ renderer->shaders.tex_rgbx.program = prog =
+ link_program(tex_vertex_src, tex_fragment_src_rgbx);
+ if (!renderer->shaders.tex_rgbx.program) {
+ goto error;
+ }
+ renderer->shaders.tex_rgbx.proj = glGetUniformLocation(prog, "proj");
+ renderer->shaders.tex_rgbx.invert_y = glGetUniformLocation(prog, "invert_y");
+ renderer->shaders.tex_rgbx.tex = glGetUniformLocation(prog, "tex");
+ renderer->shaders.tex_rgbx.alpha = glGetUniformLocation(prog, "alpha");
+
+ if (renderer->exts.egl_image_external_oes) {
+ renderer->shaders.tex_ext.program = prog =
+ link_program(tex_vertex_src, tex_fragment_src_external);
+ if (!renderer->shaders.tex_ext.program) {
+ goto error;
+ }
+ renderer->shaders.tex_ext.proj = glGetUniformLocation(prog, "proj");
+ renderer->shaders.tex_ext.invert_y = glGetUniformLocation(prog, "invert_y");
+ renderer->shaders.tex_ext.tex = glGetUniformLocation(prog, "tex");
+ renderer->shaders.tex_ext.alpha = glGetUniformLocation(prog, "alpha");
+ }
+
+ POP_GLES2_DEBUG;
+
+ return &renderer->wlr_renderer;
+
+error:
+ glDeleteProgram(renderer->shaders.quad.program);
+ glDeleteProgram(renderer->shaders.ellipse.program);
+ glDeleteProgram(renderer->shaders.tex_rgba.program);
+ glDeleteProgram(renderer->shaders.tex_rgbx.program);
+ glDeleteProgram(renderer->shaders.tex_ext.program);
+
+ POP_GLES2_DEBUG;
+
+ if (renderer->exts.debug_khr) {
+ glDisable(GL_DEBUG_OUTPUT_KHR);
+ glDebugMessageCallbackKHR(NULL, NULL);
+ }
+
+ free(renderer);
+ return NULL;
+}
diff --git a/render/gles2/shaders.c b/render/gles2/shaders.c
new file mode 100644
index 00000000..01410d87
--- /dev/null
+++ b/render/gles2/shaders.c
@@ -0,0 +1,88 @@
+#include <GLES2/gl2.h>
+#include "render/gles2.h"
+
+// Colored quads
+const GLchar quad_vertex_src[] =
+"uniform mat3 proj;\n"
+"uniform vec4 color;\n"
+"attribute vec2 pos;\n"
+"attribute vec2 texcoord;\n"
+"varying vec4 v_color;\n"
+"varying vec2 v_texcoord;\n"
+"\n"
+"void main() {\n"
+" gl_Position = vec4(proj * vec3(pos, 1.0), 1.0);\n"
+" v_color = color;\n"
+" v_texcoord = texcoord;\n"
+"}\n";
+
+const GLchar quad_fragment_src[] =
+"precision mediump float;\n"
+"varying vec4 v_color;\n"
+"varying vec2 v_texcoord;\n"
+"\n"
+"void main() {\n"
+" gl_FragColor = v_color;\n"
+"}\n";
+
+// Colored ellipses
+const GLchar ellipse_fragment_src[] =
+"precision mediump float;\n"
+"varying vec4 v_color;\n"
+"varying vec2 v_texcoord;\n"
+"\n"
+"void main() {\n"
+" float l = length(v_texcoord - vec2(0.5, 0.5));\n"
+" if (l > 0.5) {\n"
+" discard;\n"
+" }\n"
+" gl_FragColor = v_color;\n"
+"}\n";
+
+// Textured quads
+const GLchar tex_vertex_src[] =
+"uniform mat3 proj;\n"
+"uniform bool invert_y;\n"
+"attribute vec2 pos;\n"
+"attribute vec2 texcoord;\n"
+"varying vec2 v_texcoord;\n"
+"\n"
+"void main() {\n"
+" gl_Position = vec4(proj * vec3(pos, 1.0), 1.0);\n"
+" if (invert_y) {\n"
+" v_texcoord = vec2(texcoord.s, 1.0 - texcoord.t);\n"
+" } else {\n"
+" v_texcoord = texcoord;\n"
+" }\n"
+"}\n";
+
+const GLchar tex_fragment_src_rgba[] =
+"precision mediump float;\n"
+"varying vec2 v_texcoord;\n"
+"uniform sampler2D tex;\n"
+"uniform float alpha;\n"
+"\n"
+"void main() {\n"
+" gl_FragColor = texture2D(tex, v_texcoord) * alpha;\n"
+"}\n";
+
+const GLchar tex_fragment_src_rgbx[] =
+"precision mediump float;\n"
+"varying vec2 v_texcoord;\n"
+"uniform sampler2D tex;\n"
+"uniform float alpha;\n"
+"\n"
+"void main() {\n"
+" gl_FragColor = vec4(texture2D(tex, v_texcoord).rgb, 1.0) * alpha;\n"
+"}\n";
+
+const GLchar tex_fragment_src_external[] =
+"#extension GL_OES_EGL_image_external : require\n\n"
+"precision mediump float;\n"
+"varying vec2 v_texcoord;\n"
+"uniform samplerExternalOES texture0;\n"
+"uniform float alpha;\n"
+"\n"
+"void main() {\n"
+" gl_FragColor = texture2D(texture0, v_texcoord) * alpha;\n"
+"}\n";
diff --git a/render/gles2/texture.c b/render/gles2/texture.c
new file mode 100644
index 00000000..d035841e
--- /dev/null
+++ b/render/gles2/texture.c
@@ -0,0 +1,304 @@
+#include <assert.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <wayland-server-protocol.h>
+#include <wayland-util.h>
+#include <wlr/render/wlr_texture.h>
+#include <wlr/render/egl.h>
+#include <wlr/render/interface.h>
+#include <wlr/types/wlr_matrix.h>
+#include <wlr/util/log.h>
+#include "glapi.h"
+#include "render/gles2.h"
+#include "util/signal.h"
+
+static const struct wlr_texture_impl texture_impl;
+
+struct wlr_gles2_texture *gles2_get_texture(
+ struct wlr_texture *wlr_texture) {
+ assert(wlr_texture->impl == &texture_impl);
+ return (struct wlr_gles2_texture *)wlr_texture;
+}
+
+struct wlr_gles2_texture *get_gles2_texture_in_context(
+ struct wlr_texture *wlr_texture) {
+ struct wlr_gles2_texture *texture = gles2_get_texture(wlr_texture);
+ if (!wlr_egl_is_current(texture->egl)) {
+ wlr_egl_make_current(texture->egl, EGL_NO_SURFACE, NULL);
+ }
+ return texture;
+}
+
+static void gles2_texture_get_size(struct wlr_texture *wlr_texture, int *width,
+ int *height) {
+ struct wlr_gles2_texture *texture = gles2_get_texture(wlr_texture);
+ *width = texture->width;
+ *height = texture->height;
+}
+
+static bool gles2_texture_is_opaque(struct wlr_texture *wlr_texture) {
+ struct wlr_gles2_texture *texture = gles2_get_texture(wlr_texture);
+ return !texture->has_alpha;
+}
+
+static bool gles2_texture_write_pixels(struct wlr_texture *wlr_texture,
+ uint32_t stride, uint32_t width, uint32_t height,
+ uint32_t src_x, uint32_t src_y, uint32_t dst_x, uint32_t dst_y,
+ const void *data) {
+ struct wlr_gles2_texture *texture =
+ get_gles2_texture_in_context(wlr_texture);
+
+ if (texture->type != WLR_GLES2_TEXTURE_GLTEX) {
+ wlr_log(WLR_ERROR, "Cannot write pixels to immutable texture");
+ return false;
+ }
+
+ const struct wlr_gles2_pixel_format *fmt =
+ get_gles2_format_from_wl(texture->wl_format);
+ assert(fmt);
+
+ // TODO: what if the unpack subimage extension isn't supported?
+ PUSH_GLES2_DEBUG;
+
+ glBindTexture(GL_TEXTURE_2D, texture->gl_tex);
+
+ glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, stride / (fmt->bpp / 8));
+ glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, src_x);
+ glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, src_y);
+
+ glTexSubImage2D(GL_TEXTURE_2D, 0, dst_x, dst_y, width, height,
+ fmt->gl_format, fmt->gl_type, data);
+
+ glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0);
+ glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, 0);
+ glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, 0);
+
+ POP_GLES2_DEBUG;
+ return true;
+}
+
+static bool gles2_texture_to_dmabuf(struct wlr_texture *wlr_texture,
+ struct wlr_dmabuf_attributes *attribs) {
+ struct wlr_gles2_texture *texture = gles2_get_texture(wlr_texture);
+
+ if (!texture->image) {
+ assert(texture->type == WLR_GLES2_TEXTURE_GLTEX);
+
+ if (!eglCreateImageKHR) {
+ return false;
+ }
+
+ texture->image = eglCreateImageKHR(texture->egl->display,
+ texture->egl->context, EGL_GL_TEXTURE_2D_KHR,
+ (EGLClientBuffer)(uintptr_t)texture->gl_tex, NULL);
+ if (texture->image == EGL_NO_IMAGE_KHR) {
+ return false;
+ }
+ }
+
+ uint32_t flags = 0;
+ if (texture->inverted_y) {
+ flags |= WLR_DMABUF_ATTRIBUTES_FLAGS_Y_INVERT;
+ }
+
+ return wlr_egl_export_image_to_dmabuf(texture->egl, texture->image,
+ texture->width, texture->height, flags, attribs);
+}
+
+static void gles2_texture_destroy(struct wlr_texture *wlr_texture) {
+ if (wlr_texture == NULL) {
+ return;
+ }
+
+ struct wlr_gles2_texture *texture = gles2_get_texture(wlr_texture);
+
+ wlr_egl_make_current(texture->egl, EGL_NO_SURFACE, NULL);
+
+ PUSH_GLES2_DEBUG;
+
+ if (texture->image_tex) {
+ glDeleteTextures(1, &texture->image_tex);
+ }
+ wlr_egl_destroy_image(texture->egl, texture->image);
+
+ if (texture->type == WLR_GLES2_TEXTURE_GLTEX) {
+ glDeleteTextures(1, &texture->gl_tex);
+ }
+
+ POP_GLES2_DEBUG;
+
+ free(texture);
+}
+
+static const struct wlr_texture_impl texture_impl = {
+ .get_size = gles2_texture_get_size,
+ .is_opaque = gles2_texture_is_opaque,
+ .write_pixels = gles2_texture_write_pixels,
+ .to_dmabuf = gles2_texture_to_dmabuf,
+ .destroy = gles2_texture_destroy,
+};
+
+struct wlr_texture *wlr_gles2_texture_from_pixels(struct wlr_egl *egl,
+ enum wl_shm_format wl_fmt, uint32_t stride, uint32_t width,
+ uint32_t height, const void *data) {
+ if (!wlr_egl_is_current(egl)) {
+ wlr_egl_make_current(egl, EGL_NO_SURFACE, NULL);
+ }
+
+ const struct wlr_gles2_pixel_format *fmt = get_gles2_format_from_wl(wl_fmt);
+ if (fmt == NULL) {
+ wlr_log(WLR_ERROR, "Unsupported pixel format %"PRIu32, wl_fmt);
+ return NULL;
+ }
+
+ struct wlr_gles2_texture *texture =
+ calloc(1, sizeof(struct wlr_gles2_texture));
+ if (texture == NULL) {
+ wlr_log(WLR_ERROR, "Allocation failed");
+ return NULL;
+ }
+ wlr_texture_init(&texture->wlr_texture, &texture_impl);
+ texture->egl = egl;
+ texture->width = width;
+ texture->height = height;
+ texture->type = WLR_GLES2_TEXTURE_GLTEX;
+ texture->has_alpha = fmt->has_alpha;
+ texture->wl_format = fmt->wl_format;
+
+ PUSH_GLES2_DEBUG;
+
+ glGenTextures(1, &texture->gl_tex);
+ glBindTexture(GL_TEXTURE_2D, texture->gl_tex);
+
+ glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, stride / (fmt->bpp / 8));
+ glTexImage2D(GL_TEXTURE_2D, 0, fmt->gl_format, width, height, 0,
+ fmt->gl_format, fmt->gl_type, data);
+ glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0);
+
+ POP_GLES2_DEBUG;
+ return &texture->wlr_texture;
+}
+
+struct wlr_texture *wlr_gles2_texture_from_wl_drm(struct wlr_egl *egl,
+ struct wl_resource *data) {
+ if (!wlr_egl_is_current(egl)) {
+ wlr_egl_make_current(egl, EGL_NO_SURFACE, NULL);
+ }
+
+ if (!glEGLImageTargetTexture2DOES) {
+ return NULL;
+ }
+
+ struct wlr_gles2_texture *texture =
+ calloc(1, sizeof(struct wlr_gles2_texture));
+ if (texture == NULL) {
+ wlr_log(WLR_ERROR, "Allocation failed");
+ return NULL;
+ }
+ wlr_texture_init(&texture->wlr_texture, &texture_impl);
+ texture->egl = egl;
+ texture->wl_drm = data;
+
+ EGLint fmt;
+ texture->wl_format = 0xFFFFFFFF; // texture can't be written anyways
+ texture->image = wlr_egl_create_image_from_wl_drm(egl, data, &fmt,
+ &texture->width, &texture->height, &texture->inverted_y);
+ if (texture->image == NULL) {
+ free(texture);
+ return NULL;
+ }
+
+ GLenum target;
+ switch (fmt) {
+ case EGL_TEXTURE_RGB:
+ case EGL_TEXTURE_RGBA:
+ target = GL_TEXTURE_2D;
+ texture->type = WLR_GLES2_TEXTURE_WL_DRM_GL;
+ texture->has_alpha = fmt == EGL_TEXTURE_RGBA;
+ break;
+ case EGL_TEXTURE_EXTERNAL_WL:
+ target = GL_TEXTURE_EXTERNAL_OES;
+ texture->type = WLR_GLES2_TEXTURE_WL_DRM_EXT;
+ texture->has_alpha = true;
+ break;
+ default:
+ wlr_log(WLR_ERROR, "Invalid or unsupported EGL buffer format");
+ free(texture);
+ return NULL;
+ }
+
+ PUSH_GLES2_DEBUG;
+
+ glGenTextures(1, &texture->image_tex);
+ glBindTexture(target, texture->image_tex);
+ glEGLImageTargetTexture2DOES(target, texture->image);
+
+ POP_GLES2_DEBUG;
+ return &texture->wlr_texture;
+}
+
+#ifndef DRM_FORMAT_BIG_ENDIAN
+#define DRM_FORMAT_BIG_ENDIAN 0x80000000
+#endif
+
+struct wlr_texture *wlr_gles2_texture_from_dmabuf(struct wlr_egl *egl,
+ struct wlr_dmabuf_attributes *attribs) {
+ if (!wlr_egl_is_current(egl)) {
+ wlr_egl_make_current(egl, EGL_NO_SURFACE, NULL);
+ }
+
+ if (!glEGLImageTargetTexture2DOES) {
+ return NULL;
+ }
+
+ if (!egl->exts.image_dmabuf_import_ext) {
+ wlr_log(WLR_ERROR, "Cannot create DMA-BUF texture: EGL extension "
+ "unavailable");
+ return NULL;
+ }
+
+ switch (attribs->format & ~DRM_FORMAT_BIG_ENDIAN) {
+ case WL_SHM_FORMAT_YUYV:
+ case WL_SHM_FORMAT_YVYU:
+ case WL_SHM_FORMAT_UYVY:
+ case WL_SHM_FORMAT_VYUY:
+ case WL_SHM_FORMAT_AYUV:
+ // TODO: YUV based formats not yet supported, require multiple images
+ return false;
+ default:
+ break;
+ }
+
+ struct wlr_gles2_texture *texture =
+ calloc(1, sizeof(struct wlr_gles2_texture));
+ if (texture == NULL) {
+ wlr_log(WLR_ERROR, "Allocation failed");
+ return NULL;
+ }
+ wlr_texture_init(&texture->wlr_texture, &texture_impl);
+ texture->egl = egl;
+ texture->width = attribs->width;
+ texture->height = attribs->height;
+ texture->type = WLR_GLES2_TEXTURE_DMABUF;
+ texture->has_alpha = true;
+ texture->wl_format = 0xFFFFFFFF; // texture can't be written anyways
+ texture->inverted_y =
+ (attribs->flags & WLR_DMABUF_ATTRIBUTES_FLAGS_Y_INVERT) != 0;
+
+ texture->image = wlr_egl_create_image_from_dmabuf(egl, attribs);
+ if (texture->image == NULL) {
+ free(texture);
+ return NULL;
+ }
+
+ PUSH_GLES2_DEBUG;
+
+ glGenTextures(1, &texture->image_tex);
+ glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture->image_tex);
+ glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, texture->image);
+
+ POP_GLES2_DEBUG;
+ return &texture->wlr_texture;
+}
diff --git a/render/gles2/util.c b/render/gles2/util.c
new file mode 100644
index 00000000..3ac777ee
--- /dev/null
+++ b/render/gles2/util.c
@@ -0,0 +1,38 @@
+#include <GLES2/gl2.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <wlr/util/log.h>
+#include "render/gles2.h"
+
+const char *gles2_strerror(GLenum err) {
+ switch (err) {
+ case GL_INVALID_ENUM:
+ return "Invalid enum";
+ case GL_INVALID_VALUE:
+ return "Invalid value";
+ case GL_INVALID_OPERATION:
+ return "Invalid operation";
+ case GL_OUT_OF_MEMORY:
+ return "Out of memory";
+ case GL_INVALID_FRAMEBUFFER_OPERATION:
+ return "Invalid framebuffer operation";
+ default:
+ return "Unknown error";
+ }
+}
+
+bool _gles2_flush_errors(const char *file, int line) {
+ GLenum err;
+ bool failure = false;
+ while ((err = glGetError()) != GL_NO_ERROR) {
+ failure = true;
+ if (err == GL_OUT_OF_MEMORY) {
+ // The OpenGL context is now undefined
+ _wlr_log(WLR_ERROR, "[%s:%d] Fatal GL error: out of memory", file, line);
+ exit(1);
+ } else {
+ _wlr_log(WLR_ERROR, "[%s:%d] GL error %d %s", file, line, err, gles2_strerror(err));
+ }
+ }
+ return failure;
+}
diff --git a/render/meson.build b/render/meson.build
new file mode 100644
index 00000000..e45ea90b
--- /dev/null
+++ b/render/meson.build
@@ -0,0 +1,37 @@
+glgen = find_program('../glgen.sh')
+
+glapi = custom_target(
+ 'glapi',
+ input: 'glapi.txt',
+ output: ['@BASENAME@.c', '@BASENAME@.h'],
+ command: [glgen, '@INPUT@', '@OUTDIR@'],
+)
+
+lib_wlr_render = static_library(
+ 'wlr_render',
+ files(
+ 'dmabuf.c',
+ 'egl.c',
+ 'gles2/pixel_format.c',
+ 'gles2/renderer.c',
+ 'gles2/shaders.c',
+ 'gles2/texture.c',
+ 'gles2/util.c',
+ 'wlr_renderer.c',
+ 'wlr_texture.c',
+ ),
+ glapi,
+ include_directories: wlr_inc,
+ dependencies: [
+ egl,
+ drm.partial_dependency(compile_args: true), # <drm_fourcc.h>
+ glesv2,
+ pixman,
+ wayland_server
+ ],
+)
+
+wlr_render = declare_dependency(
+ link_with: lib_wlr_render,
+ sources: glapi[1],
+)
diff --git a/render/wlr_renderer.c b/render/wlr_renderer.c
new file mode 100644
index 00000000..58731d7f
--- /dev/null
+++ b/render/wlr_renderer.c
@@ -0,0 +1,226 @@
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <wlr/render/gles2.h>
+#include <wlr/render/interface.h>
+#include <wlr/render/wlr_renderer.h>
+#include <wlr/types/wlr_linux_dmabuf_v1.h>
+#include <wlr/types/wlr_matrix.h>
+#include <wlr/util/log.h>
+#include "util/signal.h"
+
+void wlr_renderer_init(struct wlr_renderer *renderer,
+ const struct wlr_renderer_impl *impl) {
+ assert(impl->begin);
+ assert(impl->clear);
+ assert(impl->scissor);
+ assert(impl->render_texture_with_matrix);
+ assert(impl->render_quad_with_matrix);
+ assert(impl->render_ellipse_with_matrix);
+ assert(impl->formats);
+ assert(impl->format_supported);
+ assert(impl->texture_from_pixels);
+ renderer->impl = impl;
+
+ wl_signal_init(&renderer->events.destroy);
+}
+
+void wlr_renderer_destroy(struct wlr_renderer *r) {
+ if (!r) {
+ return;
+ }
+ wlr_signal_emit_safe(&r->events.destroy, r);
+
+ if (r->impl && r->impl->destroy) {
+ r->impl->destroy(r);
+ } else {
+ free(r);
+ }
+}
+
+void wlr_renderer_begin(struct wlr_renderer *r, int width, int height) {
+ r->impl->begin(r, width, height);
+}
+
+void wlr_renderer_end(struct wlr_renderer *r) {
+ if (r->impl->end) {
+ r->impl->end(r);
+ }
+}
+
+void wlr_renderer_clear(struct wlr_renderer *r, const float color[static 4]) {
+ r->impl->clear(r, color);
+}
+
+void wlr_renderer_scissor(struct wlr_renderer *r, struct wlr_box *box) {
+ r->impl->scissor(r, box);
+}
+
+bool wlr_render_texture(struct wlr_renderer *r, struct wlr_texture *texture,
+ const float projection[static 9], int x, int y, float alpha) {
+ struct wlr_box box = { .x = x, .y = y };
+ wlr_texture_get_size(texture, &box.width, &box.height);
+
+ float matrix[9];
+ wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL, 0,
+ projection);
+
+ return wlr_render_texture_with_matrix(r, texture, matrix, alpha);
+}
+
+bool wlr_render_texture_with_matrix(struct wlr_renderer *r,
+ struct wlr_texture *texture, const float matrix[static 9],
+ float alpha) {
+ return r->impl->render_texture_with_matrix(r, texture, matrix, alpha);
+}
+
+void wlr_render_rect(struct wlr_renderer *r, const struct wlr_box *box,
+ const float color[static 4], const float projection[static 9]) {
+ float matrix[9];
+ wlr_matrix_project_box(matrix, box, WL_OUTPUT_TRANSFORM_NORMAL, 0,
+ projection);
+
+ wlr_render_quad_with_matrix(r, color, matrix);
+}
+
+void wlr_render_quad_with_matrix(struct wlr_renderer *r,
+ const float color[static 4], const float matrix[static 9]) {
+ r->impl->render_quad_with_matrix(r, color, matrix);
+}
+
+void wlr_render_ellipse(struct wlr_renderer *r, const struct wlr_box *box,
+ const float color[static 4], const float projection[static 9]) {
+ float matrix[9];
+ wlr_matrix_project_box(matrix, box, WL_OUTPUT_TRANSFORM_NORMAL, 0,
+ projection);
+
+ wlr_render_ellipse_with_matrix(r, color, matrix);
+}
+
+void wlr_render_ellipse_with_matrix(struct wlr_renderer *r,
+ const float color[static 4], const float matrix[static 9]) {
+ r->impl->render_ellipse_with_matrix(r, color, matrix);
+}
+
+const enum wl_shm_format *wlr_renderer_get_formats(
+ struct wlr_renderer *r, size_t *len) {
+ return r->impl->formats(r, len);
+}
+
+bool wlr_renderer_resource_is_wl_drm_buffer(struct wlr_renderer *r,
+ struct wl_resource *resource) {
+ if (!r->impl->resource_is_wl_drm_buffer) {
+ return false;
+ }
+ return r->impl->resource_is_wl_drm_buffer(r, resource);
+}
+
+void wlr_renderer_wl_drm_buffer_get_size(struct wlr_renderer *r,
+ struct wl_resource *buffer, int *width, int *height) {
+ if (!r->impl->wl_drm_buffer_get_size) {
+ return;
+ }
+ return r->impl->wl_drm_buffer_get_size(r, buffer, width, height);
+}
+
+int wlr_renderer_get_dmabuf_formats(struct wlr_renderer *r,
+ int **formats) {
+ if (!r->impl->get_dmabuf_formats) {
+ return -1;
+ }
+ return r->impl->get_dmabuf_formats(r, formats);
+}
+
+int wlr_renderer_get_dmabuf_modifiers(struct wlr_renderer *r, int format,
+ uint64_t **modifiers) {
+ if (!r->impl->get_dmabuf_modifiers) {
+ return -1;
+ }
+ return r->impl->get_dmabuf_modifiers(r, format, modifiers);
+}
+
+bool wlr_renderer_read_pixels(struct wlr_renderer *r, enum wl_shm_format fmt,
+ uint32_t *flags, uint32_t stride, uint32_t width, uint32_t height,
+ uint32_t src_x, uint32_t src_y, uint32_t dst_x, uint32_t dst_y,
+ void *data) {
+ if (!r->impl->read_pixels) {
+ return false;
+ }
+ return r->impl->read_pixels(r, fmt, flags, stride, width, height,
+ src_x, src_y, dst_x, dst_y, data);
+}
+
+bool wlr_renderer_format_supported(struct wlr_renderer *r,
+ enum wl_shm_format fmt) {
+ return r->impl->format_supported(r, fmt);
+}
+
+void wlr_renderer_init_wl_display(struct wlr_renderer *r,
+ struct wl_display *wl_display) {
+ if (wl_display_init_shm(wl_display)) {
+ wlr_log(WLR_ERROR, "Failed to initialize shm");
+ return;
+ }
+
+ size_t len;
+ const enum wl_shm_format *formats = wlr_renderer_get_formats(r, &len);
+ if (formats == NULL) {
+ wlr_log(WLR_ERROR, "Failed to initialize shm: cannot get formats");
+ return;
+ }
+
+ for (size_t i = 0; i < len; ++i) {
+ // These formats are already added by default
+ if (formats[i] != WL_SHM_FORMAT_ARGB8888 &&
+ formats[i] != WL_SHM_FORMAT_XRGB8888) {
+ wl_display_add_shm_format(wl_display, formats[i]);
+ }
+ }
+
+ if (r->impl->texture_from_dmabuf) {
+ wlr_linux_dmabuf_v1_create(wl_display, r);
+ }
+
+ if (r->impl->init_wl_display) {
+ r->impl->init_wl_display(r, wl_display);
+ }
+}
+
+struct wlr_renderer *wlr_renderer_autocreate(struct wlr_egl *egl,
+ EGLenum platform, void *remote_display, EGLint *config_attribs,
+ EGLint visual_id) {
+ // Append GLES2-specific bits to the provided EGL config attributes
+ EGLint gles2_config_attribs[] = {
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+ EGL_NONE,
+ };
+
+ size_t config_attribs_len = 0; // not including terminating EGL_NONE
+ while (config_attribs != NULL &&
+ config_attribs[config_attribs_len] != EGL_NONE) {
+ ++config_attribs_len;
+ }
+
+ size_t all_config_attribs_len = config_attribs_len +
+ sizeof(gles2_config_attribs) / sizeof(gles2_config_attribs[0]);
+ EGLint all_config_attribs[all_config_attribs_len];
+ if (config_attribs_len > 0) {
+ memcpy(all_config_attribs, config_attribs,
+ config_attribs_len * sizeof(EGLint));
+ }
+ memcpy(&all_config_attribs[config_attribs_len], gles2_config_attribs,
+ sizeof(gles2_config_attribs));
+
+ if (!wlr_egl_init(egl, platform, remote_display, all_config_attribs,
+ visual_id)) {
+ wlr_log(WLR_ERROR, "Could not initialize EGL");
+ return NULL;
+ }
+
+ struct wlr_renderer *renderer = wlr_gles2_renderer_create(egl);
+ if (!renderer) {
+ wlr_egl_finish(egl);
+ }
+
+ return renderer;
+}
diff --git a/render/wlr_texture.c b/render/wlr_texture.c
new file mode 100644
index 00000000..833032c9
--- /dev/null
+++ b/render/wlr_texture.c
@@ -0,0 +1,71 @@
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <wlr/render/interface.h>
+#include <wlr/render/wlr_texture.h>
+
+void wlr_texture_init(struct wlr_texture *texture,
+ const struct wlr_texture_impl *impl) {
+ assert(impl->get_size);
+ assert(impl->write_pixels);
+ texture->impl = impl;
+}
+
+void wlr_texture_destroy(struct wlr_texture *texture) {
+ if (texture && texture->impl && texture->impl->destroy) {
+ texture->impl->destroy(texture);
+ } else {
+ free(texture);
+ }
+}
+
+struct wlr_texture *wlr_texture_from_pixels(struct wlr_renderer *renderer,
+ enum wl_shm_format wl_fmt, uint32_t stride, uint32_t width,
+ uint32_t height, const void *data) {
+ return renderer->impl->texture_from_pixels(renderer, wl_fmt, stride, width,
+ height, data);
+}
+
+struct wlr_texture *wlr_texture_from_wl_drm(struct wlr_renderer *renderer,
+ struct wl_resource *data) {
+ if (!renderer->impl->texture_from_wl_drm) {
+ return NULL;
+ }
+ return renderer->impl->texture_from_wl_drm(renderer, data);
+}
+
+struct wlr_texture *wlr_texture_from_dmabuf(struct wlr_renderer *renderer,
+ struct wlr_dmabuf_attributes *attribs) {
+ if (!renderer->impl->texture_from_dmabuf) {
+ return NULL;
+ }
+ return renderer->impl->texture_from_dmabuf(renderer, attribs);
+}
+
+void wlr_texture_get_size(struct wlr_texture *texture, int *width,
+ int *height) {
+ return texture->impl->get_size(texture, width, height);
+}
+
+bool wlr_texture_is_opaque(struct wlr_texture *texture) {
+ if (!texture->impl->is_opaque) {
+ return false;
+ }
+ return texture->impl->is_opaque(texture);
+}
+
+bool wlr_texture_write_pixels(struct wlr_texture *texture,
+ uint32_t stride, uint32_t width, uint32_t height,
+ uint32_t src_x, uint32_t src_y, uint32_t dst_x, uint32_t dst_y,
+ const void *data) {
+ return texture->impl->write_pixels(texture, stride, width, height,
+ src_x, src_y, dst_x, dst_y, data);
+}
+
+bool wlr_texture_to_dmabuf(struct wlr_texture *texture,
+ struct wlr_dmabuf_attributes *attribs) {
+ if (!texture->impl->to_dmabuf) {
+ return false;
+ }
+ return texture->impl->to_dmabuf(texture, attribs);
+}