aboutsummaryrefslogtreecommitdiff
path: root/render/gles2
diff options
context:
space:
mode:
Diffstat (limited to 'render/gles2')
-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
5 files changed, 1163 insertions, 0 deletions
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;
+}