// SPDX-License-Identifier: MPL-2.0 // Copyright (c) Yuxuan Shui #include #include #include #include #include #include #include // for xcb_render_fixed_t, XXX #include "backend/backend.h" #include "common.h" #include "compiler.h" #include "config.h" #include "kernel.h" #include "log.h" #include "region.h" #include "string_utils.h" #include "types.h" #include "utils.h" #include "backend/backend_common.h" #include "backend/gl/gl_common.h" GLuint gl_create_shader(GLenum shader_type, const char *shader_str) { log_trace("===\n%s\n===", shader_str); bool success = false; GLuint shader = glCreateShader(shader_type); if (!shader) { log_error("Failed to create shader with type %#x.", shader_type); goto end; } glShaderSource(shader, 1, &shader_str, NULL); glCompileShader(shader); // Get shader status { GLint status = GL_FALSE; glGetShaderiv(shader, GL_COMPILE_STATUS, &status); if (status == GL_FALSE) { GLint log_len = 0; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_len); if (log_len) { char log[log_len + 1]; glGetShaderInfoLog(shader, log_len, NULL, log); log_error("Failed to compile shader with type %d: %s", shader_type, log); } goto end; } } success = true; end: if (shader && !success) { glDeleteShader(shader); shader = 0; } gl_check_err(); return shader; } GLuint gl_create_program(const GLuint *const shaders, int nshaders) { bool success = false; GLuint program = glCreateProgram(); if (!program) { log_error("Failed to create program."); goto end; } for (int i = 0; i < nshaders; ++i) { glAttachShader(program, shaders[i]); } glLinkProgram(program); // Get program status { GLint status = GL_FALSE; glGetProgramiv(program, GL_LINK_STATUS, &status); if (status == GL_FALSE) { GLint log_len = 0; glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_len); if (log_len) { char log[log_len + 1]; glGetProgramInfoLog(program, log_len, NULL, log); log_error("Failed to link program: %s", log); } goto end; } } success = true; end: if (program) { for (int i = 0; i < nshaders; ++i) { glDetachShader(program, shaders[i]); } } if (program && !success) { glDeleteProgram(program); program = 0; } gl_check_err(); return program; } /** * @brief Create a program from NULL-terminated arrays of vertex and fragment shader * strings. */ GLuint gl_create_program_from_strv(const char **vert_shaders, const char **frag_shaders) { int vert_count, frag_count; for (vert_count = 0; vert_shaders && vert_shaders[vert_count]; ++vert_count) { } for (frag_count = 0; frag_shaders && frag_shaders[frag_count]; ++frag_count) { } GLuint prog = 0; auto shaders = (GLuint *)ccalloc(vert_count + frag_count, GLuint); for (int i = 0; i < vert_count; ++i) { shaders[i] = gl_create_shader(GL_VERTEX_SHADER, vert_shaders[i]); if (shaders[i] == 0) { goto out; } } for (int i = 0; i < frag_count; ++i) { shaders[vert_count + i] = gl_create_shader(GL_FRAGMENT_SHADER, frag_shaders[i]); if (shaders[vert_count + i] == 0) { goto out; } } prog = gl_create_program(shaders, vert_count + frag_count); out: for (int i = 0; i < vert_count + frag_count; ++i) { if (shaders[i] != 0) { glDeleteShader(shaders[i]); } } free(shaders); gl_check_err(); return prog; } /** * @brief Create a program from vertex and fragment shader strings. */ GLuint gl_create_program_from_str(const char *vert_shader_str, const char *frag_shader_str) { const char *vert_shaders[2] = {vert_shader_str, NULL}; const char *frag_shaders[2] = {frag_shader_str, NULL}; return gl_create_program_from_strv(vert_shaders, frag_shaders); } void gl_destroy_window_shader(backend_t *backend_data attr_unused, void *shader) { if (!shader) { return; } auto pprogram = (gl_win_shader_t *)shader; if (pprogram->prog) { glDeleteProgram(pprogram->prog); pprogram->prog = 0; } gl_check_err(); free(shader); } /* * @brief Implements recursive part of gl_average_texture_color. * * @note In order to reduce number of textures which needs to be * allocated and deleted during this recursive render * we reuse the same two textures for render source and * destination simply by alterating between them. * Unfortunately on first iteration source_texture might * be read-only. In this case we will select auxiliary_texture as * destination_texture in order not to touch that read-only source * texture in following render iteration. * Otherwise we simply will switch source and destination textures * between each other on each render iteration. */ static GLuint _gl_average_texture_color(backend_t *base, GLuint source_texture, GLuint destination_texture, GLuint auxiliary_texture, GLuint fbo, int width, int height) { const int max_width = 1; const int max_height = 1; const int from_width = next_power_of_two(width); const int from_height = next_power_of_two(height); const int to_width = from_width > max_width ? from_width / 2 : from_width; const int to_height = from_height > max_height ? from_height / 2 : from_height; // Prepare coordinates GLint coord[] = { // top left 0, 0, // vertex coord 0, 0, // texture coord // top right to_width, 0, // vertex coord width, 0, // texture coord // bottom right to_width, to_height, // vertex coord width, height, // texture coord // bottom left 0, to_height, // vertex coord 0, height, // texture coord }; glBufferSubData(GL_ARRAY_BUFFER, 0, (long)sizeof(*coord) * 16, coord); // Prepare framebuffer for new render iteration glBindTexture(GL_TEXTURE_2D, destination_texture); glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, destination_texture, 0); gl_check_fb_complete(GL_FRAMEBUFFER); // Bind source texture as downscaling shader uniform input glBindTexture(GL_TEXTURE_2D, source_texture); // Render into framebuffer glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, NULL); // Have we downscaled enough? GLuint result; if (to_width > max_width || to_height > max_height) { GLuint new_source_texture = destination_texture; GLuint new_destination_texture = auxiliary_texture != 0 ? auxiliary_texture : source_texture; result = _gl_average_texture_color(base, new_source_texture, new_destination_texture, 0, fbo, to_width, to_height); } else { result = destination_texture; } return result; } /* * @brief Builds a 1x1 texture which has color corresponding to the average of all * pixels of img by recursively rendering into texture of quorter the size (half * width and half height). * Returned texture must not be deleted, since it's owned by the gl_image. It will be * deleted when the gl_image is released. */ static GLuint gl_average_texture_color(backend_t *base, struct backend_image *img) { auto gd = (struct gl_data *)base; auto inner = (struct gl_texture *)img->inner; // Prepare textures which will be used for destination and source of rendering // during downscaling. const int texture_count = ARR_SIZE(inner->auxiliary_texture); if (!inner->auxiliary_texture[0]) { assert(!inner->auxiliary_texture[1]); glGenTextures(texture_count, inner->auxiliary_texture); glActiveTexture(GL_TEXTURE0); for (int i = 0; i < texture_count; i++) { glBindTexture(GL_TEXTURE_2D, inner->auxiliary_texture[i]); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, (GLint[]){0, 0, 0, 0}); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, inner->width, inner->height, 0, GL_BGR, GL_UNSIGNED_BYTE, NULL); } } // Prepare framebuffer used for rendering and bind it GLuint fbo; glGenFramebuffers(1, &fbo); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); glDrawBuffer(GL_COLOR_ATTACHMENT0); // Enable shaders glUseProgram(gd->brightness_shader.prog); glUniform2f(glGetUniformLocationChecked(gd->brightness_shader.prog, "texsize"), (GLfloat)inner->width, (GLfloat)inner->height); // Prepare vertex attributes 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]); glEnableVertexAttribArray(vert_coord_loc); glEnableVertexAttribArray(vert_in_texcoord_loc); glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL); glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2)); // Allocate buffers for render input GLint coord[16] = {0}; GLuint indices[] = {0, 1, 2, 2, 3, 0}; glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * 16, coord, GL_DYNAMIC_DRAW); glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * 6, indices, GL_STATIC_DRAW); // Do actual recursive render to 1x1 texture GLuint result_texture = _gl_average_texture_color( base, inner->texture, inner->auxiliary_texture[0], inner->auxiliary_texture[1], fbo, inner->width, inner->height); // Cleanup vertex attributes glDisableVertexAttribArray(vert_coord_loc); glDisableVertexAttribArray(vert_in_texcoord_loc); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glDeleteBuffers(2, bo); glBindVertexArray(0); glDeleteVertexArrays(1, &vao); // Cleanup shaders glUseProgram(0); // Cleanup framebuffers glDeleteFramebuffers(1, &fbo); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); glDrawBuffer(GL_BACK); // Cleanup render textures glBindTexture(GL_TEXTURE_2D, 0); gl_check_err(); return result_texture; } /** * 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 OpenGL coordinate system (important!). * @param reg_tgt the clip region, in Xorg coordinate system * @param reg_visible ignored */ static void _gl_compose(backend_t *base, struct backend_image *img, GLuint target, struct backend_image *mask, coord_t mask_offset, GLint *coord, GLuint *indices, int nrects) { // FIXME(yshui) breaks when `mask` and `img` doesn't have the same y_inverted // value. but we don't ever hit this problem because all of our // images and masks are y_inverted. auto gd = (struct gl_data *)base; auto inner = (struct gl_texture *)img->inner; auto mask_texture = mask ? ((struct gl_texture *)mask->inner)->texture : gd->default_mask_texture; if (!img || !inner->texture) { log_error("Missing texture."); return; } GLuint brightness = 0; if (img->max_brightness < 1.0) { brightness = gl_average_texture_color(base, img); } auto win_shader = inner->shader; if (!win_shader) { win_shader = gd->default_shader; } assert(win_shader); assert(win_shader->prog); glUseProgram(win_shader->prog); if (win_shader->uniform_opacity >= 0) { glUniform1f(win_shader->uniform_opacity, (float)img->opacity); } if (win_shader->uniform_invert_color >= 0) { glUniform1i(win_shader->uniform_invert_color, img->color_inverted); } if (win_shader->uniform_tex >= 0) { glUniform1i(win_shader->uniform_tex, 0); } if (win_shader->uniform_dim >= 0) { glUniform1f(win_shader->uniform_dim, (float)img->dim); } if (win_shader->uniform_brightness >= 0) { glUniform1i(win_shader->uniform_brightness, 1); } if (win_shader->uniform_max_brightness >= 0) { glUniform1f(win_shader->uniform_max_brightness, (float)img->max_brightness); } if (win_shader->uniform_corner_radius >= 0) { glUniform1f(win_shader->uniform_corner_radius, (float)img->corner_radius); } if (win_shader->uniform_border_width >= 0) { auto border_width = img->border_width; if (border_width > img->corner_radius) { border_width = 0; } glUniform1f(win_shader->uniform_border_width, (float)border_width); } if (win_shader->uniform_time >= 0) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); glUniform1f(win_shader->uniform_time, (float)ts.tv_sec * 1000.0F + (float)ts.tv_nsec / 1.0e6F); } glUniform1i(win_shader->uniform_mask_tex, 2); glUniform2f(win_shader->uniform_mask_offset, (float)mask_offset.x, (float)mask_offset.y); if (mask != NULL) { glUniform1i(win_shader->uniform_mask_inverted, mask->color_inverted); glUniform1f(win_shader->uniform_mask_corner_radius, (GLfloat)mask->corner_radius); } else { glUniform1i(win_shader->uniform_mask_inverted, 0); glUniform1f(win_shader->uniform_mask_corner_radius, 0); } glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_2D, mask_texture); // 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 glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, brightness); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, inner->texture); 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(vert_coord_loc); glEnableVertexAttribArray(vert_in_texcoord_loc); glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL); glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2)); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, target); glDrawElements(GL_TRIANGLES, nrects * 6, GL_UNSIGNED_INT, NULL); glDisableVertexAttribArray(vert_coord_loc); glDisableVertexAttribArray(vert_in_texcoord_loc); glBindVertexArray(0); glDeleteVertexArrays(1, &vao); // Cleanup glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_2D, 0); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, 0); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, 0); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); glDrawBuffer(GL_BACK); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glDeleteBuffers(2, bo); glUseProgram(0); gl_check_err(); return; } /// Convert rectangles in X coordinates to OpenGL vertex and texture coordinates /// @param[in] nrects, rects rectangles /// @param[in] image_dst origin of the OpenGL texture, affect the calculated texture /// coordinates /// @param[in] extend_height height of the drawing extent /// @param[in] texture_height height of the OpenGL texture /// @param[in] root_height height of the back buffer /// @param[in] y_inverted whether the texture is y inverted /// @param[out] coord, indices output void x_rect_to_coords(int nrects, const rect_t *rects, coord_t image_dst, int extent_height, int texture_height, int root_height, bool y_inverted, GLint *coord, GLuint *indices) { image_dst.y = root_height - image_dst.y; image_dst.y -= extent_height; for (int i = 0; i < nrects; i++) { // Y-flip. Note after this, crect.y1 > crect.y2 rect_t crect = rects[i]; crect.y1 = root_height - crect.y1; crect.y2 = root_height - crect.y2; // Calculate texture coordinates // (texture_x1, texture_y1), texture coord for the _bottom left_ corner GLint texture_x1 = crect.x1 - image_dst.x, texture_y1 = crect.y2 - image_dst.y, texture_x2 = texture_x1 + (crect.x2 - crect.x1), texture_y2 = texture_y1 + (crect.y1 - crect.y2); // X pixmaps might be Y inverted, invert the texture coordinates if (y_inverted) { texture_y1 = texture_height - texture_y1; texture_y2 = texture_height - texture_y2; } // Vertex coordinates auto vx1 = crect.x1; auto vy1 = crect.y2; auto vx2 = crect.x2; auto vy2 = crect.y1; // log_trace("Rect %d: %f, %f, %f, %f -> %d, %d, %d, %d", // ri, rx, ry, rxe, rye, rdx, rdy, rdxe, rdye); memcpy(&coord[i * 16], ((GLint[][2]){ {vx1, vy1}, {texture_x1, texture_y1}, {vx2, vy1}, {texture_x2, texture_y1}, {vx2, vy2}, {texture_x2, texture_y2}, {vx1, vy2}, {texture_x1, texture_y2}, }), sizeof(GLint[2]) * 8); GLuint u = (GLuint)(i * 4); memcpy(&indices[i * 6], ((GLuint[]){u + 0, u + 1, u + 2, u + 2, u + 3, u + 0}), sizeof(GLuint) * 6); } } // TODO(yshui) make use of reg_visible void gl_compose(backend_t *base, void *image_data, coord_t image_dst, void *mask, coord_t mask_dst, const region_t *reg_tgt, const region_t *reg_visible attr_unused) { auto gd = (struct gl_data *)base; struct backend_image *img = image_data; auto inner = (struct gl_texture *)img->inner; // Painting int nrects; const rect_t *rects; rects = pixman_region32_rectangles((region_t *)reg_tgt, &nrects); if (!nrects) { // Nothing to paint return; } // Until we start to use glClipControl, reg_tgt, dst_x and dst_y and // in a different coordinate system than the one OpenGL uses. // OpenGL window coordinate (or NDC) has the origin at the lower left of the // 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 auto coord = ccalloc(nrects * 16, GLint); auto indices = ccalloc(nrects * 6, GLuint); coord_t mask_offset = {.x = mask_dst.x - image_dst.x, .y = mask_dst.y - image_dst.y}; x_rect_to_coords(nrects, rects, image_dst, inner->height, inner->height, gd->height, inner->y_inverted, coord, indices); _gl_compose(base, img, gd->back_fbo, mask, mask_offset, coord, indices, nrects); free(indices); free(coord); } /** * Load a GLSL main program from shader strings. */ static bool gl_win_shader_from_stringv(const char **vshader_strv, const char **fshader_strv, gl_win_shader_t *ret) { // Build program ret->prog = gl_create_program_from_strv(vshader_strv, fshader_strv); if (!ret->prog) { log_error("Failed to create GLSL program."); gl_check_err(); return false; } // Get uniform addresses bind_uniform(ret, opacity); bind_uniform(ret, invert_color); bind_uniform(ret, tex); bind_uniform(ret, dim); bind_uniform(ret, brightness); bind_uniform(ret, max_brightness); bind_uniform(ret, corner_radius); bind_uniform(ret, border_width); bind_uniform(ret, time); bind_uniform(ret, mask_tex); bind_uniform(ret, mask_offset); bind_uniform(ret, mask_inverted); bind_uniform(ret, mask_corner_radius); gl_check_err(); return true; } /** * Callback to run on root window size change. */ void gl_resize(struct gl_data *gd, int width, int height) { GLint viewport_dimensions[2]; glGetIntegerv(GL_MAX_VIEWPORT_DIMS, viewport_dimensions); gd->height = height; gd->width = width; assert(viewport_dimensions[0] >= gd->width); assert(viewport_dimensions[1] >= gd->height); glBindTexture(GL_TEXTURE_2D, gd->back_texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, width, height, 0, GL_BGR, GL_UNSIGNED_BYTE, NULL); gl_check_err(); } /// Fill a given region in bound framebuffer. /// @param[in] y_inverted whether the y coordinates in `clip` should be inverted static void _gl_fill(backend_t *base, struct color c, const region_t *clip, GLuint target, int height, bool y_inverted) { static const GLuint fill_vert_in_coord_loc = 0; int nrects; const rect_t *rect = pixman_region32_rectangles((region_t *)clip, &nrects); auto gd = (struct gl_data *)base; GLuint vao; glGenVertexArrays(1, &vao); glBindVertexArray(vao); GLuint bo[2]; glGenBuffers(2, bo); glUseProgram(gd->fill_shader.prog); glUniform4f(gd->fill_shader.color_loc, (GLfloat)c.red, (GLfloat)c.green, (GLfloat)c.blue, (GLfloat)c.alpha); glEnableVertexAttribArray(fill_vert_in_coord_loc); glBindBuffer(GL_ARRAY_BUFFER, bo[0]); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]); auto coord = ccalloc(nrects * 8, GLint); auto indices = ccalloc(nrects * 6, GLuint); for (int i = 0; i < nrects; i++) { GLint y1 = y_inverted ? height - rect[i].y2 : rect[i].y1, y2 = y_inverted ? height - rect[i].y1 : rect[i].y2; // clang-format off memcpy(&coord[i * 8], ((GLint[][2]){ {rect[i].x1, y1}, {rect[i].x2, y1}, {rect[i].x2, y2}, {rect[i].x1, y2}}), sizeof(GLint[2]) * 4); // clang-format on indices[i * 6 + 0] = (GLuint)i * 4 + 0; indices[i * 6 + 1] = (GLuint)i * 4 + 1; indices[i * 6 + 2] = (GLuint)i * 4 + 2; indices[i * 6 + 3] = (GLuint)i * 4 + 2; indices[i * 6 + 4] = (GLuint)i * 4 + 3; indices[i * 6 + 5] = (GLuint)i * 4 + 0; } glBufferData(GL_ARRAY_BUFFER, nrects * 8 * (long)sizeof(*coord), coord, GL_STREAM_DRAW); glBufferData(GL_ELEMENT_ARRAY_BUFFER, nrects * 6 * (long)sizeof(*indices), indices, GL_STREAM_DRAW); glVertexAttribPointer(fill_vert_in_coord_loc, 2, GL_INT, GL_FALSE, sizeof(*coord) * 2, (void *)0); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, target); glDrawElements(GL_TRIANGLES, nrects * 6, GL_UNSIGNED_INT, NULL); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glDisableVertexAttribArray(fill_vert_in_coord_loc); glBindVertexArray(0); glDeleteVertexArrays(1, &vao); glDeleteBuffers(2, bo); free(indices); free(coord); gl_check_err(); } void gl_fill(backend_t *base, struct color c, const region_t *clip) { auto gd = (struct gl_data *)base; return _gl_fill(base, c, clip, gd->back_fbo, gd->height, true); } void *gl_make_mask(backend_t *base, geometry_t size, const region_t *reg) { auto tex = ccalloc(1, struct gl_texture); auto img = default_new_backend_image(size.width, size.height); tex->width = size.width; tex->height = size.height; tex->texture = gl_new_texture(GL_TEXTURE_2D); tex->has_alpha = false; tex->y_inverted = true; img->inner = (struct backend_image_inner_base *)tex; img->inner->refcount = 1; glBindTexture(GL_TEXTURE_2D, tex->texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, size.width, size.height, 0, GL_RED, GL_UNSIGNED_BYTE, NULL); glBindTexture(GL_TEXTURE_2D, 0); GLuint fbo; glBlendFunc(GL_ONE, GL_ZERO); glGenFramebuffers(1, &fbo); glBindFramebuffer(GL_FRAMEBUFFER, fbo); glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex->texture, 0); glDrawBuffer(GL_COLOR_ATTACHMENT0); glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT); _gl_fill(base, (struct color){1, 1, 1, 1}, reg, fbo, size.height, false); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); glDeleteFramebuffers(1, &fbo); return img; } static void gl_release_image_inner(backend_t *base, struct gl_texture *inner) { auto gd = (struct gl_data *)base; if (inner->user_data) { gd->release_user_data(base, inner); } assert(inner->user_data == NULL); glDeleteTextures(1, &inner->texture); glDeleteTextures(2, inner->auxiliary_texture); free(inner); gl_check_err(); } void gl_release_image(backend_t *base, void *image_data) { struct backend_image *wd = image_data; auto inner = (struct gl_texture *)wd->inner; inner->refcount--; assert(inner->refcount >= 0); if (inner->refcount == 0) { gl_release_image_inner(base, inner); } free(wd); } void *gl_create_window_shader(backend_t *backend_data attr_unused, const char *source) { auto win_shader = (gl_win_shader_t *)ccalloc(1, gl_win_shader_t); const char *vert_shaders[2] = {vertex_shader, NULL}; const char *frag_shaders[4] = {win_shader_glsl, masking_glsl, source, NULL}; if (!gl_win_shader_from_stringv(vert_shaders, frag_shaders, win_shader)) { free(win_shader); return NULL; } GLint viewport_dimensions[2]; glGetIntegerv(GL_MAX_VIEWPORT_DIMS, viewport_dimensions); // Set projection matrix to gl viewport dimensions so we can use screen // coordinates for all vertices // Note: OpenGL matrices are column major GLfloat projection_matrix[4][4] = {{2.0F / (GLfloat)viewport_dimensions[0], 0, 0, 0}, {0, 2.0F / (GLfloat)viewport_dimensions[1], 0, 0}, {0, 0, 0, 0}, {-1, -1, 0, 1}}; int pml = glGetUniformLocationChecked(win_shader->prog, "projection"); glUseProgram(win_shader->prog); glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); glUseProgram(0); return win_shader; } uint64_t gl_get_shader_attributes(backend_t *backend_data attr_unused, void *shader) { auto win_shader = (gl_win_shader_t *)shader; uint64_t ret = 0; if (glGetUniformLocation(win_shader->prog, "time") >= 0) { ret |= SHADER_ATTRIBUTE_ANIMATED; } return ret; } bool gl_init(struct gl_data *gd, session_t *ps) { // Initialize GLX data structure glDisable(GL_DEPTH_TEST); glDepthMask(GL_FALSE); glEnable(GL_BLEND); // X pixmap is in premultiplied alpha, so we might just as well use it too. // Thanks to derhass for help. glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // Initialize stencil buffer glDisable(GL_STENCIL_TEST); glStencilMask(0x1); glStencilFunc(GL_EQUAL, 0x1, 0x1); // Set gl viewport to the maximum supported size so we won't have to worry about // it later on when the screen is resized. The corresponding projection matrix can // be set now and won't have to be updated. Since fragments outside the target // buffer are skipped anyways, this should have no impact on performance. GLint viewport_dimensions[2]; glGetIntegerv(GL_MAX_VIEWPORT_DIMS, viewport_dimensions); glViewport(0, 0, viewport_dimensions[0], viewport_dimensions[1]); // Clear screen glClearColor(0.0F, 0.0F, 0.0F, 1.0F); glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glGenFramebuffers(1, &gd->back_fbo); glGenTextures(1, &gd->back_texture); if (!gd->back_fbo || !gd->back_texture) { log_error("Failed to generate a framebuffer object"); return false; } glBindTexture(GL_TEXTURE_2D, gd->back_texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glBindTexture(GL_TEXTURE_2D, 0); gd->default_mask_texture = gl_new_texture(GL_TEXTURE_2D); if (!gd->default_mask_texture) { log_error("Failed to generate a default mask texture"); return false; } glBindTexture(GL_TEXTURE_2D, gd->default_mask_texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, 1, 1, 0, GL_RED, GL_UNSIGNED_BYTE, (GLbyte[]){'\xff'}); glBindTexture(GL_TEXTURE_2D, 0); // Initialize shaders gd->default_shader = gl_create_window_shader(NULL, win_shader_default); if (!gd->default_shader) { log_error("Failed to create window shaders"); return false; } // Set projection matrix to gl viewport dimensions so we can use screen // coordinates for all vertices // Note: OpenGL matrices are column major GLfloat projection_matrix[4][4] = {{2.0F / (GLfloat)viewport_dimensions[0], 0, 0, 0}, {0, 2.0F / (GLfloat)viewport_dimensions[1], 0, 0}, {0, 0, 0, 0}, {-1, -1, 0, 1}}; gd->fill_shader.prog = gl_create_program_from_str(fill_vert, fill_frag); gd->fill_shader.color_loc = glGetUniformLocation(gd->fill_shader.prog, "color"); int pml = glGetUniformLocationChecked(gd->fill_shader.prog, "projection"); glUseProgram(gd->fill_shader.prog); glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); glUseProgram(0); gd->present_prog = gl_create_program_from_str(present_vertex_shader, dummy_frag); if (!gd->present_prog) { log_error("Failed to create the present shader"); return false; } pml = glGetUniformLocationChecked(gd->present_prog, "projection"); glUseProgram(gd->present_prog); glUniform1i(glGetUniformLocationChecked(gd->present_prog, "tex"), 0); glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); glUseProgram(0); gd->shadow_shader.prog = gl_create_program_from_str(present_vertex_shader, shadow_colorization_frag); gd->shadow_shader.uniform_color = glGetUniformLocationChecked(gd->shadow_shader.prog, "color"); pml = glGetUniformLocationChecked(gd->shadow_shader.prog, "projection"); glUseProgram(gd->shadow_shader.prog); glUniform1i(glGetUniformLocationChecked(gd->shadow_shader.prog, "tex"), 0); glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); glUseProgram(0); glBindFragDataLocation(gd->shadow_shader.prog, 0, "out_color"); gd->brightness_shader.prog = gl_create_program_from_str(interpolating_vert, interpolating_frag); if (!gd->brightness_shader.prog) { log_error("Failed to create the brightness shader"); return false; } pml = glGetUniformLocationChecked(gd->brightness_shader.prog, "projection"); glUseProgram(gd->brightness_shader.prog); glUniform1i(glGetUniformLocationChecked(gd->brightness_shader.prog, "tex"), 0); glUniformMatrix4fv(pml, 1, false, projection_matrix[0]); glUseProgram(0); // Set up the size of the back texture gl_resize(gd, ps->root_width, ps->root_height); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, gd->back_fbo); glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gd->back_texture, 0); glDrawBuffer(GL_COLOR_ATTACHMENT0); if (!gl_check_fb_complete(GL_FRAMEBUFFER)) { return false; } glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); gd->logger = gl_string_marker_logger_new(); if (gd->logger) { log_add_target_tls(gd->logger); } const char *vendor = (const char *)glGetString(GL_VENDOR); log_debug("GL_VENDOR = %s", vendor); if (strcmp(vendor, "NVIDIA Corporation") == 0) { log_info("GL vendor is NVIDIA, don't use glFinish"); gd->is_nvidia = true; } else { gd->is_nvidia = false; } gd->has_robustness = gl_has_extension("GL_ARB_robustness"); gl_check_err(); return true; } void gl_deinit(struct gl_data *gd) { if (gd->logger) { log_remove_target_tls(gd->logger); gd->logger = NULL; } if (gd->default_shader) { gl_destroy_window_shader(&gd->base, gd->default_shader); gd->default_shader = NULL; } gl_check_err(); } GLuint gl_new_texture(GLenum target) { GLuint texture; glGenTextures(1, &texture); if (!texture) { log_error("Failed to generate texture"); return 0; } glBindTexture(target, texture); glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_REPEAT); glBindTexture(target, 0); return texture; } /// Actually duplicate a texture into a new one, if this texture is shared static inline void gl_image_decouple(backend_t *base, struct backend_image *img) { if (img->inner->refcount == 1) { return; } auto gd = (struct gl_data *)base; auto inner = (struct gl_texture *)img->inner; auto new_tex = ccalloc(1, struct gl_texture); new_tex->texture = gl_new_texture(GL_TEXTURE_2D); new_tex->y_inverted = true; new_tex->height = inner->height; new_tex->width = inner->width; new_tex->refcount = 1; new_tex->user_data = gd->decouple_texture_user_data(base, inner->user_data); glBindTexture(GL_TEXTURE_2D, new_tex->texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, new_tex->width, new_tex->height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL); glBindTexture(GL_TEXTURE_2D, 0); assert(gd->present_prog); glUseProgram(gd->present_prog); glBindTexture(GL_TEXTURE_2D, inner->texture); 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); gl_check_fb_complete(GL_DRAW_FRAMEBUFFER); glClearColor(0, 0, 0, 0); glClear(GL_COLOR_BUFFER_BIT); // clang-format off GLint coord[] = { // top left 0, 0, // vertex coord 0, 0, // texture coord // top right new_tex->width, 0, // vertex coord new_tex->width, 0, // texture coord // bottom right new_tex->width, new_tex->height, new_tex->width, new_tex->height, // bottom left 0, new_tex->height, 0, new_tex->height, }; // clang-format on GLuint indices[] = {0, 1, 2, 2, 3, 0}; 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) * 16, coord, GL_STATIC_DRAW); glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * 6, indices, GL_STATIC_DRAW); glEnableVertexAttribArray(vert_coord_loc); glEnableVertexAttribArray(vert_in_texcoord_loc); glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL); glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2)); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, NULL); glDisableVertexAttribArray(vert_coord_loc); glDisableVertexAttribArray(vert_in_texcoord_loc); glBindVertexArray(0); glDeleteVertexArrays(1, &vao); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glDeleteBuffers(2, bo); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); glDeleteFramebuffers(1, &fbo); glBindTexture(GL_TEXTURE_2D, 0); glUseProgram(0); gl_check_err(); img->inner = (struct backend_image_inner_base *)new_tex; inner->refcount--; } static void gl_image_apply_alpha(backend_t *base, struct backend_image *img, const region_t *reg_op, double alpha) { // Result color = 0 (GL_ZERO) + alpha (GL_CONSTANT_ALPHA) * original color auto inner = (struct gl_texture *)img->inner; glBlendFunc(GL_ZERO, GL_CONSTANT_ALPHA); glBlendColor(0, 0, 0, (GLclampf)alpha); GLuint fbo; glGenFramebuffers(1, &fbo); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, inner->texture, 0); glDrawBuffer(GL_COLOR_ATTACHMENT0); _gl_fill(base, (struct color){0, 0, 0, 0}, reg_op, fbo, inner->height, !inner->y_inverted); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); glDeleteFramebuffers(1, &fbo); } void gl_present(backend_t *base, const region_t *region) { auto gd = (struct gl_data *)base; int nrects; const rect_t *rect = pixman_region32_rectangles((region_t *)region, &nrects); auto coord = ccalloc(nrects * 8, GLint); auto indices = ccalloc(nrects * 6, GLuint); for (int i = 0; i < nrects; i++) { // clang-format off memcpy(&coord[i * 8], ((GLint[]){rect[i].x1, gd->height - rect[i].y2, rect[i].x2, gd->height - rect[i].y2, rect[i].x2, gd->height - rect[i].y1, rect[i].x1, gd->height - rect[i].y1}), sizeof(GLint) * 8); // clang-format on GLuint u = (GLuint)(i * 4); memcpy(&indices[i * 6], ((GLuint[]){u + 0, u + 1, u + 2, u + 2, u + 3, u + 0}), sizeof(GLuint) * 6); } glUseProgram(gd->present_prog); glBindTexture(GL_TEXTURE_2D, gd->back_texture); GLuint vao; glGenVertexArrays(1, &vao); glBindVertexArray(vao); GLuint bo[2]; glGenBuffers(2, bo); glEnableVertexAttribArray(vert_coord_loc); glBindBuffer(GL_ARRAY_BUFFER, bo[0]); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]); glBufferData(GL_ARRAY_BUFFER, (long)sizeof(GLint) * nrects * 8, coord, GL_STREAM_DRAW); glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(GLuint) * nrects * 6, indices, GL_STREAM_DRAW); glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 2, NULL); glDrawElements(GL_TRIANGLES, nrects * 6, GL_UNSIGNED_INT, NULL); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glBindVertexArray(0); glDeleteBuffers(2, bo); glDeleteVertexArrays(1, &vao); free(coord); free(indices); } bool gl_image_op(backend_t *base, enum image_operations op, void *image_data, const region_t *reg_op, const region_t *reg_visible attr_unused, void *arg) { struct backend_image *tex = image_data; switch (op) { case IMAGE_OP_APPLY_ALPHA: gl_image_decouple(base, tex); assert(tex->inner->refcount == 1); gl_image_apply_alpha(base, tex, reg_op, *(double *)arg); break; } return true; } bool gl_set_image_property(backend_t *backend_data, enum image_properties prop, void *image_data, void *args) { if (prop != IMAGE_PROPERTY_CUSTOM_SHADER) { return default_set_image_property(backend_data, prop, image_data, args); } struct backend_image *img = image_data; auto inner = (struct gl_texture *)img->inner; inner->shader = args; return true; } struct gl_shadow_context { double radius; void *blur_context; }; struct backend_shadow_context *gl_create_shadow_context(backend_t *base, double radius) { auto ctx = ccalloc(1, struct gl_shadow_context); ctx->radius = radius; struct gaussian_blur_args args = { .size = (int)radius, .deviation = gaussian_kernel_std_for_size(radius, 0.5 / 256.0), }; ctx->blur_context = gl_create_blur_context(base, BLUR_METHOD_GAUSSIAN, &args); return (struct backend_shadow_context *)ctx; } void gl_destroy_shadow_context(backend_t *base attr_unused, struct backend_shadow_context *ctx) { auto ctx_ = (struct gl_shadow_context *)ctx; gl_destroy_blur_context(base, (struct backend_blur_context *)ctx_->blur_context); free(ctx_); } void *gl_shadow_from_mask(backend_t *base, void *mask, struct backend_shadow_context *sctx, struct color color) { log_debug("Create shadow from mask"); auto gd = (struct gl_data *)base; auto img = (struct backend_image *)mask; auto inner = (struct gl_texture *)img->inner; auto gsctx = (struct gl_shadow_context *)sctx; int radius = (int)gsctx->radius; auto new_inner = ccalloc(1, struct gl_texture); new_inner->width = inner->width + radius * 2; new_inner->height = inner->height + radius * 2; new_inner->texture = gl_new_texture(GL_TEXTURE_2D); new_inner->has_alpha = inner->has_alpha; new_inner->y_inverted = true; auto new_img = default_new_backend_image(new_inner->width, new_inner->height); new_img->inner = (struct backend_image_inner_base *)new_inner; new_img->inner->refcount = 1; // Render the mask to a texture, so inversion and corner radius can be // applied. auto source_texture = gl_new_texture(GL_TEXTURE_2D); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, source_texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, new_inner->width, new_inner->height, 0, GL_RED, GL_UNSIGNED_BYTE, NULL); glBindTexture(GL_TEXTURE_2D, 0); GLuint fbo; glGenFramebuffers(1, &fbo); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, source_texture, 0); glDrawBuffer(GL_COLOR_ATTACHMENT0); if (img->color_inverted) { // If the mask is inverted, clear the source_texture to white, so the // "outside" of the mask would be correct glClearColor(1, 1, 1, 1); } else { glClearColor(0, 0, 0, 1); } glClear(GL_COLOR_BUFFER_BIT); { // clang-format off // interleaved vertex coordinates and texture coordinates GLint coords[] = {radius , radius , 0 , 0, radius + inner->width, radius , inner->width, 0, radius + inner->width, radius + inner->height, inner->width, inner->height, radius , radius + inner->height, 0 , inner->height,}; // clang-format on GLuint indices[] = {0, 1, 2, 2, 3, 0}; _gl_compose(base, mask, fbo, NULL, (coord_t){0}, coords, indices, 1); } gl_check_err(); glActiveTexture(GL_TEXTURE0); auto tmp_texture = gl_new_texture(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, tmp_texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, new_inner->width, new_inner->height, 0, GL_RED, GL_UNSIGNED_BYTE, NULL); glBindTexture(GL_TEXTURE_2D, 0); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tmp_texture, 0); region_t reg_blur; pixman_region32_init_rect(®_blur, 0, 0, (unsigned int)new_inner->width, (unsigned int)new_inner->height); // gl_blur expects reg_blur to be in X coordinate system (i.e. y flipped), but we // are covering the whole texture so we don't need to worry about that. gl_blur_impl(1.0, gsctx->blur_context, NULL, (coord_t){0}, ®_blur, NULL, source_texture, (geometry_t){.width = new_inner->width, .height = new_inner->height}, fbo, gd->default_mask_texture); pixman_region32_fini(®_blur); // Colorize the shadow with color. log_debug("Colorize shadow"); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, new_inner->texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, new_inner->width, new_inner->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, new_inner->texture, 0); glClearColor(0, 0, 0, 0); glClear(GL_COLOR_BUFFER_BIT); glBindTexture(GL_TEXTURE_2D, tmp_texture); glUseProgram(gd->shadow_shader.prog); glUniform4f(gd->shadow_shader.uniform_color, (GLfloat)color.red, (GLfloat)color.green, (GLfloat)color.blue, (GLfloat)color.alpha); // clang-format off GLuint indices[] = {0, 1, 2, 2, 3, 0}; GLint coord[] = {0 , 0 , new_inner->width , 0 , new_inner->width , new_inner->height, 0 , new_inner->height,}; // clang-format on 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) * 8, coord, GL_STATIC_DRAW); glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * 6, indices, GL_STATIC_DRAW); glEnableVertexAttribArray(vert_coord_loc); glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 2, NULL); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, NULL); glDisableVertexAttribArray(vert_coord_loc); glBindVertexArray(0); glDeleteVertexArrays(1, &vao); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glDeleteBuffers(2, bo); glDeleteTextures(1, (GLuint[]){source_texture, tmp_texture}); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); glDeleteFramebuffers(1, &fbo); gl_check_err(); return new_img; } enum device_status gl_device_status(backend_t *base) { auto gd = (struct gl_data *)base; if (!gd->has_robustness) { return DEVICE_STATUS_NORMAL; } if (glGetGraphicsResetStatusARB() == GL_NO_ERROR) { return DEVICE_STATUS_NORMAL; } return DEVICE_STATUS_RESETTING; }