diff --git a/src/backend/backend.h b/src/backend/backend.h index aadf014..6d91e8a 100644 --- a/src/backend/backend.h +++ b/src/backend/backend.h @@ -42,7 +42,7 @@ enum image_operations { IMAGE_OP_APPLY_ALPHA_ALL, // Change the effective size of the image, without touching the backing image // itself. When the image is used, the backing image should be tiled to fill its - // effective size. `reg_op` and `reg_visibile` is ignored. `arg` is two integers, + // effective size. `reg_op` and `reg_visible` is ignored. `arg` is two integers, // width and height, in that order. IMAGE_OP_RESIZE_TILE, }; @@ -80,6 +80,11 @@ struct backend_operations { // =========== Rendering ============ + // NOTE: general idea about reg_paint/reg_op vs reg_visible is that reg_visible is + // merely a hint. Ignoring reg_visible entirely don't affect the correctness of + // the operation performed. OTOH reg_paint/reg_op is part of the parameters of the + // operation, and must be honored in order to complete the operation correctly. + /// Called before when a new frame starts. /// /// Optional @@ -93,7 +98,7 @@ struct backend_operations { * @param image_data the image to paint * @param dst_x, dst_y the top left corner of the image in the target * @param reg_paint the clip region, in target coordinates - * @param reg_visibile the visible region, in target coordinates + * @param reg_visible the visible region, in target coordinates */ void (*compose)(backend_t *backend_data, void *image_data, int dst_x, int dst_y, const region_t *reg_paint, const region_t *reg_visible); diff --git a/src/backend/gl/gl_common.c b/src/backend/gl/gl_common.c index 088513d..d59da3c 100644 --- a/src/backend/gl/gl_common.c +++ b/src/backend/gl/gl_common.c @@ -152,15 +152,15 @@ static void gl_free_prog_main(gl_win_shader_t *pprogram) { * Render a region with texture data. * * @param ptex the texture + * @param target the framebuffer to render into * @param dst_x,dst_y the top left corner of region where this texture - * should go. In Xorg coordinate system (important!). - * @param reg_tgt the clip region, also in Xorg coordinate system + * should go. In OpenGL coordinate system (important!). + * @param reg_tgt the clip region, in Xorg coordinate system * @param reg_visible ignored */ -void gl_compose(backend_t *base, void *image_data, int dst_x, int dst_y, - const region_t *reg_tgt, const region_t *reg_visible) { +static void _gl_compose(backend_t *base, struct gl_image *img, GLuint target, int dst_x, + int dst_y, GLfloat *coord, GLuint *indices, int nrects) { - struct gl_image *ptex = image_data; struct gl_data *gd = (void *)base; // Until we start to use glClipControl, reg_tgt, dst_x and dst_y and @@ -169,11 +169,89 @@ void gl_compose(backend_t *base, void *image_data, int dst_x, int dst_y, // screen, with y axis pointing up; Xorg has the origin at the upper left of the // screen, with y axis pointing down. We have to do some coordinate conversion in // this function - if (!ptex || !ptex->inner->texture) { + if (!img || !img->inner->texture) { log_error("Missing texture."); return; } + bool dual_texture = false; + + assert(gd->win_shader.prog); + glUseProgram(gd->win_shader.prog); + if (gd->win_shader.unifm_opacity >= 0) { + glUniform1f(gd->win_shader.unifm_opacity, (float)img->opacity); + } + if (gd->win_shader.unifm_invert_color >= 0) { + glUniform1i(gd->win_shader.unifm_invert_color, img->color_inverted); + } + if (gd->win_shader.unifm_tex >= 0) { + glUniform1i(gd->win_shader.unifm_tex, 0); + } + if (gd->win_shader.unifm_dim >= 0) { + glUniform1f(gd->win_shader.unifm_dim, (float)img->dim); + } + + // log_trace("Draw: %d, %d, %d, %d -> %d, %d (%d, %d) z %d\n", + // x, y, width, height, dx, dy, ptex->width, ptex->height, z); + + // Bind texture + glBindTexture(GL_TEXTURE_2D, img->inner->texture); + if (dual_texture) { + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, img->inner->texture); + glActiveTexture(GL_TEXTURE0); + } + + GLuint vao; + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + + GLuint bo[2]; + glGenBuffers(2, bo); + glBindBuffer(GL_ARRAY_BUFFER, bo[0]); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]); + glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * nrects * 16, coord, GL_STATIC_DRAW); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * nrects * 6, + indices, GL_STATIC_DRAW); + + glEnableVertexAttribArray((GLuint)gd->win_shader.coord_loc); + glEnableVertexAttribArray((GLuint)gd->win_shader.in_texcoord); + glVertexAttribPointer((GLuint)gd->win_shader.coord_loc, 2, GL_FLOAT, GL_FALSE, + sizeof(GLfloat) * 4, NULL); + glVertexAttribPointer((GLuint)gd->win_shader.in_texcoord, 2, GL_FLOAT, GL_FALSE, + sizeof(GLfloat) * 4, (void *)(sizeof(GLfloat) * 2)); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, target); + glDrawElements(GL_TRIANGLES, nrects * 6, GL_UNSIGNED_INT, NULL); + glDisableVertexAttribArray((GLuint)gd->win_shader.coord_loc); + glDisableVertexAttribArray((GLuint)gd->win_shader.in_texcoord); + glBindVertexArray(0); + glDeleteVertexArrays(1, &vao); + + // Cleanup + glBindTexture(GL_TEXTURE_2D, 0); + + if (dual_texture) { + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE0); + } + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + glDeleteBuffers(2, bo); + + glUseProgram(0); + + gl_check_err(); + + return; +} + +void gl_compose(backend_t *base, void *image_data, int dst_x, int dst_y, + const region_t *reg_tgt, const region_t *reg_visible) { + struct gl_data *gd = (void *)base; + struct gl_image *img = image_data; + // Painting int nrects; const rect_t *rects; @@ -183,41 +261,9 @@ void gl_compose(backend_t *base, void *image_data, int dst_x, int dst_y, return; } - // dst_y is the top coordinate, in OpenGL, it is the upper bound of the y - // coordinate. - dst_y = gd->height - dst_y; - auto dst_y2 = dst_y - ptex->inner->height; - - bool dual_texture = false; - - assert(gd->win_shader.prog); - glUseProgram(gd->win_shader.prog); - if (gd->win_shader.unifm_opacity >= 0) { - glUniform1f(gd->win_shader.unifm_opacity, (float)ptex->opacity); - } - if (gd->win_shader.unifm_invert_color >= 0) { - glUniform1i(gd->win_shader.unifm_invert_color, ptex->color_inverted); - } - if (gd->win_shader.unifm_tex >= 0) { - glUniform1i(gd->win_shader.unifm_tex, 0); - } - if (gd->win_shader.unifm_dim >= 0) { - glUniform1f(gd->win_shader.unifm_dim, (float)ptex->dim); - } - - // log_trace("Draw: %d, %d, %d, %d -> %d, %d (%d, %d) z %d\n", - // x, y, width, height, dx, dy, ptex->width, ptex->height, z); - - // Bind texture - glBindTexture(GL_TEXTURE_2D, ptex->inner->texture); - if (dual_texture) { - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, ptex->inner->texture); - glActiveTexture(GL_TEXTURE0); - } auto coord = ccalloc(nrects * 16, GLfloat); auto indices = ccalloc(nrects * 6, GLuint); - + dst_y = gd->height - dst_y - img->inner->height; for (int i = 0; i < nrects; ++i) { // Y-flip. Note after this, crect.y1 > crect.y2 rect_t crect = rects[i]; @@ -227,22 +273,22 @@ void gl_compose(backend_t *base, void *image_data, int dst_x, int dst_y, // Calculate texture coordinates // (texture_x1, texture_y1), texture coord for the _bottom left_ corner auto texture_x1 = (GLfloat)(crect.x1 - dst_x); - auto texture_y1 = (GLfloat)(crect.y2 - dst_y2); + auto texture_y1 = (GLfloat)(crect.y2 - dst_y); auto texture_x2 = texture_x1 + (GLfloat)(crect.x2 - crect.x1); auto texture_y2 = texture_y1 + (GLfloat)(crect.y1 - crect.y2); // X pixmaps might be Y inverted, invert the texture coordinates - if (ptex->inner->y_inverted) { - texture_y1 = (GLfloat)ptex->inner->height - texture_y1; - texture_y2 = (GLfloat)ptex->inner->height - texture_y2; + if (img->inner->y_inverted) { + texture_y1 = (GLfloat)img->inner->height - texture_y1; + texture_y2 = (GLfloat)img->inner->height - texture_y2; } // GL_TEXTURE_2D coordinates are normalized // TODO use texelFetch - texture_x1 /= (GLfloat)ptex->inner->width; - texture_y1 /= (GLfloat)ptex->inner->height; - texture_x2 /= (GLfloat)ptex->inner->width; - texture_y2 /= (GLfloat)ptex->inner->height; + texture_x1 /= (GLfloat)img->inner->width; + texture_y1 /= (GLfloat)img->inner->height; + texture_x2 /= (GLfloat)img->inner->width; + texture_y2 /= (GLfloat)img->inner->height; // Vertex coordinates auto vx1 = (GLfloat)crect.x1; @@ -270,52 +316,10 @@ void gl_compose(backend_t *base, void *image_data, int dst_x, int dst_y, memcpy(&indices[i * 6], (GLuint[]){u + 0, u + 1, u + 2, u + 2, u + 3, u + 0}, sizeof(GLuint) * 6); } - - GLuint vao; - glGenVertexArrays(1, &vao); - glBindVertexArray(vao); - - GLuint bo[2]; - glGenBuffers(2, bo); - glBindBuffer(GL_ARRAY_BUFFER, bo[0]); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]); - glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * nrects * 16, coord, GL_STATIC_DRAW); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * nrects * 6, - indices, GL_STATIC_DRAW); - - glEnableVertexAttribArray((GLuint)gd->win_shader.coord_loc); - glEnableVertexAttribArray((GLuint)gd->win_shader.in_texcoord); - glVertexAttribPointer((GLuint)gd->win_shader.coord_loc, 2, GL_FLOAT, GL_FALSE, - sizeof(GLfloat) * 4, NULL); - glVertexAttribPointer((GLuint)gd->win_shader.in_texcoord, 2, GL_FLOAT, GL_FALSE, - sizeof(GLfloat) * 4, (void *)(sizeof(GLfloat) * 2)); - glDrawElements(GL_TRIANGLES, nrects * 6, GL_UNSIGNED_INT, NULL); - glDisableVertexAttribArray((GLuint)gd->win_shader.coord_loc); - glDisableVertexAttribArray((GLuint)gd->win_shader.in_texcoord); - glBindVertexArray(0); - glDeleteVertexArrays(1, &vao); - - // Cleanup - glBindTexture(GL_TEXTURE_2D, 0); - - if (dual_texture) { - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, 0); - glActiveTexture(GL_TEXTURE0); - } - - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - glDeleteBuffers(2, bo); + _gl_compose(base, img, 0, dst_x, dst_y, coord, indices, nrects); free(indices); free(coord); - - glUseProgram(0); - - gl_check_err(); - - return; } /** @@ -642,6 +646,33 @@ void gl_fill(backend_t *base, double r, double g, double b, double a, const regi glDeleteBuffers(2, bo); } +void gl_release_image(backend_t *base, void *image_data) { + struct gl_image *wd = image_data; + struct gl_data *gl = (void *)base; + wd->inner->refcount--; + assert(wd->inner->refcount >= 0); + if (wd->inner->refcount > 0) { + free(wd); + return; + } + + gl->release_user_data(base, wd->inner); + assert(wd->inner->user_data == NULL); + + glDeleteTextures(1, &wd->inner->texture); + free(wd->inner); + free(wd); + gl_check_err(); +} + +void *gl_copy(backend_t *base, const void *image_data, const region_t *reg_visible) { + const struct gl_image *img = image_data; + auto new_img = ccalloc(1, struct gl_image); + *new_img = *img; + new_img->inner->refcount++; + return new_img; +} + /** * Initialize GL blur filters. */ @@ -894,10 +925,68 @@ GLuint gl_new_texture(GLenum target) { } /// Decouple `img` from the image it references, also applies all the lazy operations -static inline void gl_image_decouple(struct gl_image *img) { +static inline void gl_image_decouple(backend_t *base, struct gl_image *img) { if (img->inner->refcount == 1) { return; } + + struct gl_data *gl = (void *)base; + auto new_tex = cmalloc(struct gl_texture); + + glGenTextures(1, &new_tex->texture); + glBindTexture(GL_TEXTURE_2D, new_tex->texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, img->inner->width, img->inner->height, 0, + GL_BGRA, GL_UNSIGNED_BYTE, NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + new_tex->y_inverted = true; + new_tex->height = img->inner->height; + new_tex->width = img->inner->width; + new_tex->refcount = 1; + new_tex->user_data = gl->decouple_texture_user_data(base, img->inner->user_data); + + GLuint fbo; + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + new_tex->texture, 0); + glDrawBuffer(GL_COLOR_ATTACHMENT0); + glClearColor(0, 0, 0, 0); + glClear(GL_COLOR_BUFFER_BIT); + + // clang-format off + GLfloat coord[] = { + // top left + 0, 0, // vertex coord + 0, 0, // texture coord + + // top right + (GLfloat)img->inner->width, 0, // vertex coord + 1, 0, // texture coord + + // bottom right + (GLfloat)img->inner->width, (GLfloat)img->inner->height, + 1, 1, + + // bottom left + 0, (GLfloat)img->inner->height, + 0, 1 + }; + // clang-format on + + _gl_compose(base, img, fbo, 0, 0, coord, (GLuint[]){0, 1, 2, 2, 3, 0}, 1); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + glDeleteFramebuffers(1, &fbo); + + img->inner->refcount--; + img->inner = new_tex; + + // Clear lazy operation flags + img->color_inverted = false; + img->dim = 0; + img->opacity = 1; } /// stub for backend_operations::image_op @@ -912,7 +1001,7 @@ bool gl_image_op(backend_t *base, enum image_operations op, void *image_data, break; case IMAGE_OP_APPLY_ALPHA_ALL: tex->opacity *= *(double *)arg; break; case IMAGE_OP_APPLY_ALPHA: - gl_image_decouple(tex); + gl_image_decouple(base, tex); log_warn("IMAGE_OP_APPLY_ALPHA not implemented yet"); break; case IMAGE_OP_RESIZE_TILE: diff --git a/src/backend/gl/gl_common.h b/src/backend/gl/gl_common.h index 8cbdd78..efc81d5 100644 --- a/src/backend/gl/gl_common.h +++ b/src/backend/gl/gl_common.h @@ -47,7 +47,7 @@ struct gl_texture { GLuint texture; int width, height; bool y_inverted; - unsigned depth; + void *user_data; }; /// @brief Wrapper of a binded GLX texture. @@ -78,6 +78,13 @@ struct gl_data { // Temporary fbo used for blurring GLuint blur_fbo; + /// Called when an gl_texture is decoupled from the texture it refers. Returns + /// the decoupled user_data + void *(*decouple_texture_user_data)(backend_t *base, void *user_data); + + /// Release the user data attached to a gl_texture + void (*release_user_data)(backend_t *base, struct gl_texture *); + struct log_target *logger; }; @@ -117,6 +124,10 @@ GLuint gl_new_texture(GLenum target); bool gl_image_op(backend_t *base, enum image_operations op, void *image_data, const region_t *reg_op, const region_t *reg_visible, void *arg); +void gl_release_image(backend_t *base, void *image_data); + +void *gl_copy(backend_t *base, const void *image_data, const region_t *reg_visible); + bool gl_blur(backend_t *base, double opacity, const region_t *reg_blur, const region_t *reg_visible); diff --git a/src/backend/gl/glx.c b/src/backend/gl/glx.c index 68db55b..67e3fcb 100644 --- a/src/backend/gl/glx.c +++ b/src/backend/gl/glx.c @@ -33,8 +33,7 @@ #include "win.h" #include "x.h" -struct _glx_image_data { - struct gl_image gl; +struct _glx_pixmap { GLXPixmap glpixmap; xcb_pixmap_t pixmap; bool owned; @@ -160,38 +159,30 @@ struct glx_fbconfig_info *glx_find_fbconfig(Display *dpy, int screen, struct xvi /** * Free a glx_texture_t. */ -void glx_release_image(backend_t *base, void *image_data) { - struct _glx_image_data *wd = image_data; +static void glx_release_image(backend_t *base, struct gl_texture *tex) { struct _glx_data *gd = (void *)base; - wd->gl.inner->refcount--; - if (wd->gl.inner->refcount != 0) { - free(wd); - return; - } + + struct _glx_pixmap *p = tex->user_data; // Release binding - if (wd->glpixmap && wd->gl.inner->texture) { - glBindTexture(GL_TEXTURE_2D, wd->gl.inner->texture); - glXReleaseTexImageEXT(gd->display, wd->glpixmap, GLX_FRONT_LEFT_EXT); + if (p->glpixmap && tex->texture) { + glBindTexture(GL_TEXTURE_2D, tex->texture); + glXReleaseTexImageEXT(gd->display, p->glpixmap, GLX_FRONT_LEFT_EXT); glBindTexture(GL_TEXTURE_2D, 0); } // Free GLX Pixmap - if (wd->glpixmap) { - glXDestroyPixmap(gd->display, wd->glpixmap); - wd->glpixmap = 0; + if (p->glpixmap) { + glXDestroyPixmap(gd->display, p->glpixmap); + p->glpixmap = 0; } - if (wd->owned) { - xcb_free_pixmap(base->c, wd->pixmap); - wd->pixmap = XCB_NONE; + if (p->owned) { + xcb_free_pixmap(base->c, p->pixmap); + p->pixmap = XCB_NONE; } - glDeleteTextures(1, &wd->gl.inner->texture); - free(wd->gl.inner); - - // Free structure itself - free(wd); - gl_check_err(); + free(p); + tex->user_data = NULL; } /** @@ -211,6 +202,14 @@ void glx_deinit(backend_t *base) { free(gd); } +static void *glx_decouple_user_data(backend_t * attr_unused base, void * attr_unused ud) { + auto ret = cmalloc(struct _glx_pixmap); + ret->owned = false; + ret->glpixmap = 0; + ret->pixmap = 0; + return ret; +} + /** * Initialize OpenGL. */ @@ -325,6 +324,9 @@ static backend_t *glx_init(session_t *ps) { goto end; } + gd->gl.decouple_texture_user_data = glx_decouple_user_data; + gd->gl.release_user_data = glx_release_image; + success = true; end: @@ -343,6 +345,7 @@ end: static void * glx_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool owned) { struct _glx_data *gd = (void *)base; + struct _glx_pixmap *glxpixmap = NULL; // Retrieve pixmap parameters, if they aren't provided if (fmt.visual_depth > OPENGL_MAX_DEPTH) { log_error("Requested depth %d higher than max possible depth %d.", @@ -362,11 +365,10 @@ glx_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b } log_trace("Binding pixmap %#010x", pixmap); - auto wd = ccalloc(1, struct _glx_image_data); - wd->pixmap = pixmap; - wd->gl.inner = ccalloc(1, struct gl_texture); - wd->gl.inner->width = wd->gl.ewidth = r->width; - wd->gl.inner->height = wd->gl.eheight = r->height; + auto wd = ccalloc(1, struct gl_image); + wd->inner = ccalloc(1, struct gl_texture); + wd->inner->width = wd->ewidth = r->width; + wd->inner->height = wd->eheight = r->height; free(r); auto fbcfg = glx_find_fbconfig(gd->display, gd->screen, fmt); @@ -394,37 +396,41 @@ glx_bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, b 0, }; - wd->gl.inner->y_inverted = fbcfg->y_inverted; + wd->inner->y_inverted = fbcfg->y_inverted; - wd->glpixmap = glXCreatePixmap(gd->display, fbcfg->cfg, wd->pixmap, attrs); + glxpixmap = cmalloc(struct _glx_pixmap); + glxpixmap->pixmap = pixmap; + glxpixmap->glpixmap = glXCreatePixmap(gd->display, fbcfg->cfg, pixmap, attrs); + glxpixmap->owned = owned; free(fbcfg); - if (!wd->glpixmap) { + if (!glxpixmap->glpixmap) { log_error("Failed to create glpixmap for pixmap %#010x", pixmap); goto err; } - log_trace("GLXPixmap %#010lx", wd->glpixmap); + log_trace("GLXPixmap %#010lx", glxpixmap->glpixmap); // Create texture - wd->gl.inner->texture = gl_new_texture(GL_TEXTURE_2D); - wd->gl.inner->depth = (unsigned int)fmt.visual_depth; - wd->gl.opacity = 1; - wd->gl.color_inverted = false; - wd->gl.dim = 0; - wd->gl.has_alpha = fmt.alpha_size != 0; - wd->gl.inner->refcount = 1; - wd->owned = owned; - glBindTexture(GL_TEXTURE_2D, wd->gl.inner->texture); - glXBindTexImageEXT(gd->display, wd->glpixmap, GLX_FRONT_LEFT_EXT, NULL); + wd->inner->user_data = glxpixmap; + wd->inner->texture = gl_new_texture(GL_TEXTURE_2D); + wd->opacity = 1; + wd->color_inverted = false; + wd->dim = 0; + wd->has_alpha = fmt.alpha_size != 0; + wd->inner->refcount = 1; + glBindTexture(GL_TEXTURE_2D, wd->inner->texture); + glXBindTexImageEXT(gd->display, glxpixmap->glpixmap, GLX_FRONT_LEFT_EXT, NULL); glBindTexture(GL_TEXTURE_2D, 0); gl_check_err(); return wd; err: - if (wd->glpixmap) { - glXDestroyPixmap(gd->display, wd->glpixmap); + if (glxpixmap && glxpixmap->glpixmap) { + glXDestroyPixmap(gd->display, glxpixmap->glpixmap); } + free(glxpixmap); + if (owned) { xcb_free_pixmap(base->c, pixmap); } @@ -452,22 +458,14 @@ static int glx_buffer_age(backend_t *base) { return (int)val ?: -1; } -static void *glx_copy(backend_t *base, const void *image_data, const region_t *reg_visible) { - const struct _glx_image_data *img = image_data; - auto new_img = ccalloc(1, struct _glx_image_data); - *new_img = *img; - new_img->gl.inner->refcount++; - return new_img; -} - struct backend_operations glx_ops = { .init = glx_init, .deinit = glx_deinit, .bind_pixmap = glx_bind_pixmap, - .release_image = glx_release_image, + .release_image = gl_release_image, .compose = gl_compose, .image_op = gl_image_op, - .copy = glx_copy, + .copy = gl_copy, .blur = gl_blur, .is_image_transparent = gl_is_image_transparent, .present = glx_present,